From b7a8aefbde82c6c975a8ba57be0584ce0e4158ca Mon Sep 17 00:00:00 2001 From: Dave Date: Fri, 4 Sep 2020 15:08:10 +0100 Subject: [PATCH] Loading host lists from disk --- .../simple-socks5/src/allowed_hosts.rs | 170 ++++++++++-------- 1 file changed, 91 insertions(+), 79 deletions(-) diff --git a/service-providers/simple-socks5/src/allowed_hosts.rs b/service-providers/simple-socks5/src/allowed_hosts.rs index f72c6ad1e1..b414f0e1af 100644 --- a/service-providers/simple-socks5/src/allowed_hosts.rs +++ b/service-providers/simple-socks5/src/allowed_hosts.rs @@ -1,5 +1,10 @@ -use fs::File; -use std::fs::{self}; +use fs::OpenOptions; +use io::BufReader; +use std::fs; +use std::fs::File; +use std::io; +use std::io::BufRead; +use std::path::Path; use std::path::PathBuf; /// Filters outbound requests based on what's in an `allowed_hosts` list. @@ -22,6 +27,9 @@ impl OutboundRequestFilter { } } + /// Returns `true` if a host is in the `allowed_hosts` list. + /// + /// If it's not in the list, return `false` and write it to the `unknown_hosts` storefile. pub(crate) fn check(&mut self, host: &str) -> bool { if self.allowed_hosts.contains(host) { true @@ -30,10 +38,6 @@ impl OutboundRequestFilter { false } } - - pub(crate) fn is_unknown(&self, host: &str) -> bool { - !self.allowed_hosts.contains(host) - } } /// A simple file-based store for information about allowed / unknown hosts. @@ -45,11 +49,39 @@ struct HostsStore { impl HostsStore { /// Constructs a new HostsStore - fn new(base_dir: PathBuf, filename: PathBuf, hosts: Vec) -> HostsStore { + fn new(base_dir: PathBuf, filename: PathBuf) -> HostsStore { let storefile = HostsStore::setup_storefile(base_dir, filename); + let hosts = HostsStore::load_from_storefile(&storefile).expect(&format!( + "Could not load hosts from storefile at {:?}", + storefile + )); HostsStore { storefile, hosts } } + fn append(path: &PathBuf, text: &str) { + use std::io::Write; + let mut file = OpenOptions::new() + .write(true) + .append(true) + .open(path) + .unwrap(); + + if let Err(e) = writeln!(file, "{}", text) { + eprintln!("Couldn't write to file: {}", e); + } + } + + fn append_to_file(&self, host: &str) { + fs::write(&self.storefile, host).expect(&format!( + "Could not write to storage file at {:?}", + self.storefile + )); + } + + fn contains(&self, host: &str) -> bool { + self.hosts.contains(&host.to_string()) + } + /// Returns the default base directory for the storefile. /// /// This is split out so we can easily inject our own base_dir for unit tests. @@ -59,12 +91,6 @@ impl HostsStore { .join(".nym") } - fn contains(&self, host: &str) -> bool { - self.hosts.contains(&host.to_string()) - } - - fn ensure_storefile_exists(dirpath: &PathBuf) {} - fn maybe_add(&mut self, host: &str) { if !self.contains(&host) { self.hosts.push(host.to_string()); @@ -72,10 +98,6 @@ impl HostsStore { } } - fn append_to_file(&self, host: &str) -> std::io::Result<()> { - fs::write(&self.storefile, host)?; - Ok(()) - } fn setup_storefile(base_dir: PathBuf, filename: PathBuf) -> PathBuf { let dirpath = base_dir.join("service-providers").join("socks5"); fs::create_dir_all(&dirpath).expect(&format!( @@ -90,10 +112,17 @@ impl HostsStore { storefile } - /// Reloads the allowed.list and unknown.list files into memory. Used primarily for testing. - fn reload_from_disk(&self) {} + /// Loads the storefile contents into memory. + fn load_from_storefile

(filename: P) -> io::Result> + where + P: AsRef, + { + let file = File::open(filename)?; + let reader = BufReader::new(&file); + let lines: Vec = reader.lines().collect::>().unwrap(); + Ok(lines) + } } -// Appender #[cfg(test)] mod tests { @@ -104,11 +133,11 @@ mod tests { use super::*; fn setup() -> OutboundRequestFilter { - let base_dir: PathBuf = ["/tmp/nym-tests"].iter().collect(); + let base_dir = test_base_dir(); let allowed_filename = PathBuf::from(format!("allowed-{}.list", random_string())); let unknown_filename = PathBuf::from(&format!("unknown-{}.list", random_string())); - let allowed = HostsStore::new(base_dir.clone(), allowed_filename, vec![]); - let unknown = HostsStore::new(base_dir.clone(), unknown_filename, vec![]); + let allowed = HostsStore::new(base_dir.clone(), allowed_filename); + let unknown = HostsStore::new(base_dir.clone(), unknown_filename); OutboundRequestFilter::new(allowed, unknown) } @@ -119,14 +148,6 @@ mod tests { assert_eq!(false, filter.check(&host)); } - #[test] - fn get_saved_to_file() { - let host = "unknown.com"; - let mut filter = setup(); - filter.check(host); - assert!(true, filter.is_unknown(host)); - } - #[test] fn get_appended_once_to_the_unknown_hosts_list() { let host = "unknown.com"; @@ -138,64 +159,40 @@ mod tests { assert_eq!(1, filter.unknown_hosts.hosts.len()); assert_eq!("unknown.com", filter.unknown_hosts.hosts.first().unwrap()); } - - #[test] - fn are_written_once_to_file() { - let host = "unknown.com"; - let mut filter = setup(); - filter.check(host); - let lines = lines_from_file(&filter.unknown_hosts.storefile).unwrap(); - assert_eq!(1, lines.len()); - - filter.check(host); - let lines = lines_from_file(&filter.unknown_hosts.storefile).unwrap(); - assert_eq!(1, lines.len()); - } } #[cfg(test)] mod requests_to_allowed_hosts { use super::*; fn setup() -> OutboundRequestFilter { - let base_dir: PathBuf = ["/tmp/nym"].iter().collect(); - let allowed_filename = PathBuf::from(format!("allowed-{}.list", random_string())); - let unknown_filename = PathBuf::from(&format!("unknown-{}.list", random_string())); - let allowed = HostsStore::new( - base_dir.clone(), - allowed_filename, - vec!["nymtech.net".to_string()], - ); - let unknown = HostsStore::new(base_dir.clone(), unknown_filename, vec![]); + let (allowed_storefile, base_dir1, allowed_filename) = create_test_storefile(); + let (_, base_dir2, unknown_filename) = create_test_storefile(); + HostsStore::append(&allowed_storefile, "nymtech.net"); + let allowed = HostsStore::new(base_dir1, allowed_filename.to_path_buf()); + let unknown = HostsStore::new(base_dir2, unknown_filename.to_path_buf()); OutboundRequestFilter::new(allowed, unknown) } #[test] fn are_allowed() { let host = "nymtech.net"; + let mut filter = setup(); assert_eq!(true, filter.check(host)); } - #[test] - fn are_not_unknown() { - let host = "nymtech.net"; - let mut filter = setup(); - assert_eq!(false, filter.is_unknown(host)); - } - #[test] fn are_not_appended_to_file() { - let host = "nymtech.net"; let mut filter = setup(); // test initial state - let lines = lines_from_file(&filter.allowed_hosts.storefile).unwrap(); - assert_eq!(0, lines.len()); + let lines = HostsStore::load_from_storefile(&filter.allowed_hosts.storefile).unwrap(); + assert_eq!(1, lines.len()); - filter.check(host); + filter.check("nymtech.net"); // test state after we've checked to make sure no unexpected changes - let lines = lines_from_file(&filter.allowed_hosts.storefile).unwrap(); - assert_eq!(0, lines.len()); + let lines = HostsStore::load_from_storefile(&filter.allowed_hosts.storefile).unwrap(); + assert_eq!(1, lines.len()); } } @@ -203,19 +200,34 @@ mod tests { format!("{:?}", rand::random::()) } - use io::BufReader; - use std::fs::File; - use std::io; - use std::io::BufRead; - use std::path::Path; + fn test_base_dir() -> PathBuf { + ["/tmp/nym-tests"].iter().collect() + } - fn lines_from_file

(filename: P) -> io::Result> - where - P: AsRef, - { - let file = File::open(filename)?; - let reader = BufReader::new(&file); - let lines: Vec = reader.lines().collect::>().unwrap(); - Ok(lines) + fn create_test_storefile() -> (PathBuf, PathBuf, PathBuf) { + let base_dir = test_base_dir(); + let filename = PathBuf::from(format!("hosts-store-{}.list", random_string())); + let dirpath = base_dir.join("service-providers").join("socks5"); + fs::create_dir_all(&dirpath).expect(&format!( + "could not create storage directory at {:?}", + dirpath + )); + let storefile = dirpath.join(&filename); + File::create(&storefile).unwrap(); + (storefile, base_dir, filename) + } + + #[cfg(test)] + mod creating_a_new_host_store { + use super::*; + #[test] + fn loads_its_host_list_from_storefile() { + let (storefile, base_dir, filename) = create_test_storefile(); + HostsStore::append(&storefile, "nymtech.net"); + HostsStore::append(&storefile, "edwardsnowden.com"); + + let host_store = HostsStore::new(base_dir, filename); + assert_eq!(vec!["nymtech.net", "edwardsnowden.com"], host_store.hosts); + } } }