use std::collections::HashMap; use std::fs::read_to_string; struct LineLexer { linenum: usize, content: Vec, current: String, previous: String, } impl LineLexer { pub fn new(data: String) -> Self { Self { linenum: 0, content: data.lines().map(String::from).collect(), current: String::new(), previous: String::new(), } } pub fn next(&mut self) -> Option { let line = self.content.get(self.linenum); if line.is_some() { self.previous = self.current.clone(); self.current = line.unwrap().trim().to_string(); self.linenum += 1; } line.map(|s| s.clone()) } pub fn next_if_nonempty(&mut self) -> Option { let l = self.next(); if l.is_none() || l.as_ref().unwrap().len() > 0 { l } else { None } } pub fn next_nonempty(&mut self) -> Option { loop { let l = self.next(); if l.is_none() { return l; } if l.as_ref().unwrap().len() > 0 { return l; } } } } #[derive(Debug)] pub struct TinySSHHost { hostname: String, entries: HashMap, } impl TinySSHHost { pub fn new(hostname: String) -> Self { TinySSHHost { hostname, entries: HashMap::new(), } } pub fn get(&self, k: &str) -> Option<&String> { self.entries.get(&k.to_string()) } pub fn can_be_proxy(&self) -> bool { self.get("DynamicForward").is_some() } } pub struct TinySSHConfig { hosts: Vec, } impl TinySSHConfig { pub fn parse>(filename: S) -> Result { let data = read_to_string(filename.into()).map_err(|e| e.to_string())?; let mut tsc = Self { hosts: vec![] }; let mut lexer = LineLexer::new(data); loop { let line = lexer.next_nonempty(); if line.is_none() { break; } let mut host = if lexer.current.starts_with("Host") { match lexer.current.split_whitespace().nth(1) { Some(h) => TinySSHHost::new(h.trim().to_string()), None => return Err("No hostname found".to_string()), } } else { return Err("Wrong section order".to_string()); }; loop { let l = lexer.next_if_nonempty(); if l.is_none() { break; } let l = l.unwrap(); let mut param: Vec = l.trim().split(' ').map(String::from).collect(); if param.len() != 2 { return Err(format!("Wrong param format: {}", l)); } let v = param.pop().unwrap(); let k = param.pop().unwrap(); host.entries.insert(k, v); } tsc.hosts.push(host); } Ok(tsc) } pub fn get_host(&self, host: &str) -> Option<&TinySSHHost> { self.hosts.iter().find(|h| h.hostname == host) } }