From b16f7ef3eb5ecbde551fa7cc5f82d72035ed0b88 Mon Sep 17 00:00:00 2001 From: Casey Rodarmor Date: Fri, 21 Oct 2022 10:36:36 -0700 Subject: [PATCH] Reproduce Bitcoin Core's handling of cookie files Bitcoin Core uses a single call to std::getline to read the contents of cookie files, making it ignore newlines in the file, as well as ignore any lines after the first. This reproduces that behavior, so that all cookie files that work with Bitcoin Core should work with this library. --- client/Cargo.toml | 3 +++ client/src/client.rs | 46 +++++++++++++++++++++++++++++++++++--------- contrib/test.sh | 2 +- 3 files changed, 41 insertions(+), 10 deletions(-) diff --git a/client/Cargo.toml b/client/Cargo.toml index 1728b77a..7b55d406 100644 --- a/client/Cargo.toml +++ b/client/Cargo.toml @@ -28,3 +28,6 @@ jsonrpc = "0.14.0" serde = "1" serde_json = "1" bitcoin-private = "0.1.0" + +[dev-dependencies] +tempfile = "3.3.0" diff --git a/client/src/client.rs b/client/src/client.rs index edb71e30..47c648c6 100644 --- a/client/src/client.rs +++ b/client/src/client.rs @@ -10,6 +10,7 @@ use std::collections::HashMap; use std::fs::File; +use std::io::{BufRead, BufReader}; use std::iter::FromIterator; use std::path::PathBuf; use std::{fmt, result}; @@ -201,19 +202,16 @@ pub enum Auth { impl Auth { /// Convert into the arguments that jsonrpc::Client needs. pub fn get_user_pass(self) -> Result<(Option, Option)> { - use std::io::Read; match self { Auth::None => Ok((None, None)), Auth::UserPass(u, p) => Ok((Some(u), Some(p))), Auth::CookieFile(path) => { - let mut file = File::open(path)?; - let mut contents = String::new(); - file.read_to_string(&mut contents)?; - let mut split = contents.splitn(2, ":"); - Ok(( - Some(split.next().ok_or(Error::InvalidCookieFile)?.into()), - Some(split.next().ok_or(Error::InvalidCookieFile)?.into()), - )) + let line = BufReader::new(File::open(path)?) + .lines() + .next() + .ok_or(Error::InvalidCookieFile)??; + let colon = line.find(':').ok_or(Error::InvalidCookieFile)?; + Ok((Some(line[..colon].into()), Some(line[colon + 1..].into()))) } } } @@ -1429,4 +1427,34 @@ mod tests { fn test_handle_defaults() { test_handle_defaults_inner().unwrap(); } + + #[test] + fn auth_cookie_file_ignores_newline() { + let tempdir = tempfile::tempdir().unwrap(); + let path = tempdir.path().join("cookie"); + std::fs::write(&path, "foo:bar\n").unwrap(); + assert_eq!( + Auth::CookieFile(path).get_user_pass().unwrap(), + (Some("foo".into()), Some("bar".into())), + ); + } + + #[test] + fn auth_cookie_file_ignores_additional_lines() { + let tempdir = tempfile::tempdir().unwrap(); + let path = tempdir.path().join("cookie"); + std::fs::write(&path, "foo:bar\nbaz").unwrap(); + assert_eq!( + Auth::CookieFile(path).get_user_pass().unwrap(), + (Some("foo".into()), Some("bar".into())), + ); + } + + #[test] + fn auth_cookie_file_fails_if_colon_isnt_present() { + let tempdir = tempfile::tempdir().unwrap(); + let path = tempdir.path().join("cookie"); + std::fs::write(&path, "foobar").unwrap(); + assert!(matches!(Auth::CookieFile(path).get_user_pass(), Err(Error::InvalidCookieFile))); + } } diff --git a/contrib/test.sh b/contrib/test.sh index 0e9f243c..87c766d9 100755 --- a/contrib/test.sh +++ b/contrib/test.sh @@ -15,6 +15,7 @@ fi # Test pinned versions (these are from rust-bitcoin pinning for 1.48). if cargo --version | grep ${MSRV}; then + cargo update -p tempfile --precise 3.3.0 cargo update -p log --precise 0.4.18 cargo update -p serde_json --precise 1.0.99 cargo update -p serde --precise 1.0.156 @@ -36,4 +37,3 @@ else cargo test --verbose cargo build --verbose --examples fi -