From 79318c352dce7526824a9ad91769307a05160043 Mon Sep 17 00:00:00 2001 From: Seth Pellegrino Date: Sun, 16 Aug 2020 07:10:07 -0700 Subject: [PATCH 1/2] wip: config flattening --- src/lib.rs | 104 +++++++++++++++++++++++++++++++++++++++++-- src/option.rs | 120 +++++++++++++++++++++++++++++++++++++++++++++++--- 2 files changed, 214 insertions(+), 10 deletions(-) diff --git a/src/lib.rs b/src/lib.rs index 13a3445..883484b 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -3,8 +3,8 @@ //! use ssh2_config::SSHConfig; //! //! // Retrieve config for local SSH server -//! let sess = SSHConfig::for_host("127.0.0.1") -//! .with_config_file("/path/to/ssh/config") // equivalent to OpenSSH's `-F` +//! let sess = SSHConfig::from_file("/path/to/ssh/config") // equivalent to OpenSSH's `-F` +//! .for_host("127.0.0.1") //! .connect_with_auth(); //! //! // Make sure we're authenticated @@ -217,6 +217,8 @@ impl SSHConfig { let mut config = vec![]; for path in paths { + // TODO: we need to track the "file" boundary, since every + // file starts off as matching everything config.extend(readconf_depth( &path, ReadMeta { @@ -234,8 +236,30 @@ impl SSHConfig { Ok(Self(config)) } - pub fn for_host(_: &str) -> Self { - unimplemented!() + pub fn for_host(self: &Self, host: &str) -> Self { + fn host_matches(h1: &str, h2: &str) -> bool { + h1 == h2 || h1 == "*" || h2 == "*" + } + + let mut last_host = None; + return Self( + self.0 + .iter() + .filter(|opt| match opt { + SSHOption::Host(h) => { + last_host = Some(h); + false + } + _ => last_host.map(|h| host_matches(h, host)).unwrap_or(true), + }) + .map(|opt| opt.clone()) + .collect(), + ); + + // HashMap, SSHOption> + // 1. Get me all the IdentityFile directives + // 2. insert-or-merge + // 3. Iterate over every option and "apply" it to an ssh session } pub fn with_config_file(self: &Self, _: &str) -> Self { @@ -456,4 +480,76 @@ mod test { std::mem::discriminant(&super::Error::MaxDepthExceeded), ) } + + #[test] + fn config_matching() { + use SSHOption::*; + let cfg = SSHConfig(vec![ + Host(String::from("example.com")), + User(String::from("example_ssh_user")), + Host(String::from("*")), + User(String::from("ssh_user")), + ]); + + assert_eq!( + cfg.for_host("127.0.0.1").0, + vec![User(String::from("ssh_user"))] + ); + assert_eq!( + cfg.for_host("example.com").0, + vec![User(String::from("example_ssh_user"))] + ); + } + + #[test] + fn whatsup() { + use std::collections::HashMap; + + let input = vec![ + SSHOption::User(String::from("seth")), + SSHOption::Port(16), + SSHOption::User(String::from("eric")), + ]; + + let mut mapping: HashMap, SSHOption> = Default::default(); + + for opt in input { + use option::Merge; + let k = std::mem::discriminant(&opt); + + if let Some(last) = mapping.insert(k, opt) { + let opt = mapping.remove(&k).unwrap(); + let merged = last.merge(opt); + assert!(mapping.insert(k, merged).is_none()); + } + } + + // do something + if true { + panic!("{:?}", mapping); + } + + // mapping[std::mem::discriminant(&SendEnv(vec![]))] + + // mapping[option::Kind::Port] + + let output = Vec::::new(); + assert_eq!( + output, + vec![SSHOption::User(String::from("seth")), SSHOption::Port(16),] + ); + + let mut mapping: HashMap, SSHOption> = Default::default(); + + assert!(mapping + .insert( + std::mem::discriminant(&SSHOption::User(String::from("this is ignored"))), + SSHOption::User(String::from("seth")), + ) + .is_none()); + + // mapping.insert() + + panic!("{:?}", mapping) + } } diff --git a/src/option.rs b/src/option.rs index d6fd619..abcfbef 100644 --- a/src/option.rs +++ b/src/option.rs @@ -12,25 +12,133 @@ use std::str::MatchIndices; /// For details on the individual meaning of these options, see [ssh_config(5)][0]. /// /// [0]: https://man.openbsd.org/OpenBSD-current/man5/ssh_config.5 -#[derive(Debug, PartialEq, Eq)] +#[derive(Debug, PartialEq, Eq, Clone)] #[allow(missing_docs)] #[non_exhaustive] -// TODO: OsString for paths & env vars? +// TODO: OsString for env vars? pub enum SSHOption { - User(String), + User(User), Port(u16), Hostname(String), Host(String), - SendEnv(Vec), + SendEnv(SendEnv), Include(Include), + // IdentityFile(String) + Foo { five: u8 }, +} + +struct Opt +where + T: Kind, +{ + kind: T, + data: T::Data, +} + +trait Kind { + type Data; + + fn merge(a: Self::Data, _b: Self::Data) -> Self::Data { + a + } +} + +struct User2; + +impl Kind for User2 { + type Data = String; +} + +#[test] +fn foob() { + let foo = Opt:: { + kind: User2, + data: String::from("hi"), + }; + + // panic! +} + +// enum Kind { +// User, +// Port, +// Hostname, +// }; + +type User = String; +type SendEnv = Vec; + +#[allow(missing_docs)] +pub trait Merge +where + Self: std::marker::Sized, +{ + fn merge(self: Self, _other: Self) -> Self { + self + } +} + +// struct Merged { + +// } + +impl Merge for SSHOption { + fn merge(self: Self, other: Self) -> Self { + match (self, other) { + (a @ SSHOption::User(_), SSHOption::User(_)) => a, + (SSHOption::User(_), _) => todo!(), + + (_, _) => todo!(), + } + } +} + +fn merge_tuple(a: SSHOption, b: SSHOption) -> (SSHOption, SSHOption) { + // TODO: override for other shit + (a, b) +} + +impl Merge for User {} + +// impl Merge for SendEnv { +// fn merge(a: SendEnv, b: SendEnv) -> SendEnv { +// // a.into_iter().concat(b.into_iter()).collect() +// let mut both = vec![]; +// both.extend(a); +// both.extend(b); +// both +// } +// } + +// fn merge_two_variants(a: T, b: T) { + +// } + +#[allow(dead_code)] +mod experiment { + use super::*; + fn merge_two_sendenvs(mut a: SendEnv, b: SendEnv) -> SendEnv { + a.extend(b); + a + } + + fn merge_two_users(a: User, _b: User) -> User { + a + } + + // TODO: this is a problem, maybe + type TODO = (); + fn merge_two_includes(_a: Include, _b: Include) -> TODO { + todo!() + } } /// Env represents an environment variable pattern for the `SendEnv` directive. /// /// SendEnv may either express a positive or negative pattern to send along or /// stop the sending of a variable respectively. -#[derive(Debug, PartialEq, Eq)] +#[derive(Debug, PartialEq, Eq, Clone)] pub enum Env { /// Send is a positive pattern: "send along matching env vars" Send(String), @@ -41,7 +149,7 @@ pub enum Env { /// Include represents the nested config structure provided by an include directive. /// /// We preserve the nested structure in order to handle nested Host/Match states. -#[derive(Debug, PartialEq, Eq)] +#[derive(Debug, PartialEq, Eq, Clone)] pub enum Include { /// Paths is a list of file patterns to include. Paths(Vec), From 5aad6f71890434212b47b514f7df5eda5d565c4d Mon Sep 17 00:00:00 2001 From: Seth Pellegrino Date: Sun, 16 Aug 2020 08:41:00 -0700 Subject: [PATCH 2/2] wip: Typed keys --- src/option.rs | 47 ++++++++++++++++++++++++++++++++++++++++------- 1 file changed, 40 insertions(+), 7 deletions(-) diff --git a/src/option.rs b/src/option.rs index abcfbef..1ede06a 100644 --- a/src/option.rs +++ b/src/option.rs @@ -28,9 +28,10 @@ pub enum SSHOption { Foo { five: u8 }, } +#[derive(Debug, PartialEq, Eq)] struct Opt where - T: Kind, + T: Kind + Eq, { kind: T, data: T::Data, @@ -44,12 +45,39 @@ trait Kind { } } +#[derive(Debug, PartialEq, Eq, Hash)] +struct Magic { + __marker: std::marker::PhantomData<&'static T>, +} + +const USER: Magic = Magic { + __marker: std::marker::PhantomData, +}; +const SENDENV: Magic = Magic { + __marker: std::marker::PhantomData, +}; + +#[derive(Debug, PartialEq, Eq, Hash)] struct User2; impl Kind for User2 { type Data = String; } +#[derive(Debug, PartialEq, Eq, Hash)] +struct SendEnv2; + +impl Kind for SendEnv2 { + type Data = Vec; + + fn merge(a: Self::Data, b: Self::Data) -> Self::Data { + let mut both = vec![]; + both.extend(a); + both.extend(b); + both + } +} + #[test] fn foob() { let foo = Opt:: { @@ -57,7 +85,17 @@ fn foob() { data: String::from("hi"), }; - // panic! + let bar = Opt:: { + kind: SendEnv2, + data: vec![Env::Send(String::from("hi"))], + }; + + use std::collections::HashMap; + let mut map: HashMap<_, _> = HashMap::new(); + assert!(map.insert(USER, foo).is_none()); + assert!(map.insert(SENDENV, bar).is_none()); + + panic!("{:?}", map); } // enum Kind { @@ -94,11 +132,6 @@ impl Merge for SSHOption { } } -fn merge_tuple(a: SSHOption, b: SSHOption) -> (SSHOption, SSHOption) { - // TODO: override for other shit - (a, b) -} - impl Merge for User {} // impl Merge for SendEnv {