Codex Handbook
windows-sandbox-rs/src/ssh_config_dependencies.rs 249 lines
use std::collections::HashSet;use std::path::Path;use std::path::PathBuf;const SSH_PROFILE_PATH_DIRECTIVES: &[&str] = &[    "certificatefile",    "controlpath",    "globalknownhostsfile",    "identityagent",    "identityfile",    "revokedhostkeys",    "userknownhostsfile",];pub(crate) fn ssh_config_dependency_paths(user_profile: &Path) -> Vec<PathBuf> {    let ssh_dir = user_profile.join(".ssh");    let mut paths = vec![ssh_dir.join("config")];    visit_config(        &ssh_dir.join("config"),        user_profile,        &ssh_dir,        &mut HashSet::new(),        &mut paths,        /*depth*/ 0,    );    paths}fn visit_config(    path: &Path,    user_profile: &Path,    ssh_dir: &Path,    visited: &mut HashSet<PathBuf>,    paths: &mut Vec<PathBuf>,    depth: usize,) {    if depth == 32 {        return;    }    let key = std::fs::canonicalize(path).unwrap_or_else(|_| path.to_path_buf());    if !visited.insert(key) {        return;    }    let Ok(contents) = std::fs::read_to_string(path) else {        return;    };    for (key, args) in contents.lines().filter_map(directive) {        match key.to_ascii_lowercase().as_str() {            "include" => {                for arg in args {                    for include in include_paths(&arg, user_profile, ssh_dir) {                        paths.push(include.clone());                        visit_config(&include, user_profile, ssh_dir, visited, paths, depth + 1);                    }                }            }            key if SSH_PROFILE_PATH_DIRECTIVES.contains(&key) => {                for arg in args {                    if let Some(path) =                        profile_path_arg(&arg, user_profile, /*relative_base*/ None)                    {                        paths.push(path);                    }                }            }            _ => {}        }    }}fn include_paths(arg: &str, user_profile: &Path, ssh_dir: &Path) -> Vec<PathBuf> {    let Some(pattern_path) = profile_path_arg(arg, user_profile, Some(ssh_dir)) else {        return Vec::new();    };    let pattern = pattern_path.to_string_lossy().replace('\\', "/");    let Ok(paths) = glob::glob(&pattern) else {        return Vec::new();    };    paths.filter_map(Result::ok).collect()}fn directive(line: &str) -> Option<(String, Vec<String>)> {    let mut words = words(line);    let first = words.first()?.to_string();    if let Some((key, value)) = first.split_once('=')        && !key.is_empty()    {        let mut args = Vec::new();        if !value.is_empty() {            args.push(value.to_string());        }        args.extend(words.drain(1..));        Some((key.to_string(), args))    } else {        let key = words.remove(0);        if let Some(arg) = words.first_mut()            && let Some(value) = arg.strip_prefix('=')        {            *arg = value.to_string();        }        words.retain(|arg| !arg.is_empty());        Some((key, words))    }}fn words(line: &str) -> Vec<String> {    let mut out = Vec::new();    let mut word = String::new();    let mut quote = None;    let mut chars = line.chars().peekable();    while let Some(ch) = chars.next() {        match ch {            '#' if quote.is_none() => break,            '\'' | '"' if quote == Some(ch) => quote = None,            '\'' | '"' if quote.is_none() => quote = Some(ch),            '\\' => {                if let Some(&next) = chars.peek() {                    if matches!(next, '\'' | '"' | '\\') || (quote.is_none() && next == ' ') {                        if let Some(escaped) = chars.next() {                            word.push(escaped);                        }                    } else {                        word.push(ch);                    }                } else {                    word.push(ch);                }            }            ch if ch.is_whitespace() && quote.is_none() => {                if !word.is_empty() {                    out.push(std::mem::take(&mut word));                }            }            ch => word.push(ch),        }    }    if !word.is_empty() {        out.push(word);    }    out}fn profile_path_arg(    arg: &str,    user_profile: &Path,    relative_base: Option<&Path>,) -> Option<PathBuf> {    if arg.eq_ignore_ascii_case("none") {        return None;    }    if arg == "~" || arg == "%d" || arg == "${HOME}" {        return Some(user_profile.to_path_buf());    }    if let Some(rest) = arg        .strip_prefix("~/")        .or_else(|| arg.strip_prefix(r"~\"))        .or_else(|| arg.strip_prefix("%d/"))        .or_else(|| arg.strip_prefix(r"%d\"))        .or_else(|| arg.strip_prefix("${HOME}/"))        .or_else(|| arg.strip_prefix(r"${HOME}\"))    {        return Some(user_profile.join(rest));    }    let path = PathBuf::from(arg);    if path.is_absolute() {        Some(path)    } else {        relative_base.map(|base| base.join(path))    }}#[cfg(test)]mod tests {    use super::ssh_config_dependency_paths;    use pretty_assertions::assert_eq;    use std::fs;    use std::path::PathBuf;    use tempfile::TempDir;    #[test]    fn collects_path_directive_profile_entries() {        let tmp = TempDir::new().expect("tempdir");        let home = tmp.path();        fs::create_dir_all(home.join(".ssh")).expect("create .ssh");        fs::write(            home.join(".ssh/config"),            r#"Host devbox  IdentityFile ~/.keys/id_ed25519  IdentityFile '~/.keys/quoted key'  CertificateFile = %d/.certs/devbox-cert.pub  UserKnownHostsFile ${HOME}/.known_hosts_custom  GlobalKnownHostsFile ~/.global_known_hosts  ControlPath ~/.control/%h-%p-%r  IdentityAgent=%d/.agent/socket  RevokedHostKeys ~/.revoked/keys"#,        )        .expect("write config");        assert_eq!(            vec![                home.join(".ssh/config"),                home.join(".keys/id_ed25519"),                home.join(".keys/quoted key"),                home.join(".certs/devbox-cert.pub"),                home.join(".known_hosts_custom"),                home.join(".global_known_hosts"),                home.join(".control/%h-%p-%r"),                home.join(".agent/socket"),                home.join(".revoked/keys"),            ],            slash_paths(ssh_config_dependency_paths(home))        );    }    #[test]    fn recursively_collects_include_dependencies() {        let tmp = TempDir::new().expect("tempdir");        let home = tmp.path();        let ssh_dir = home.join(".ssh");        fs::create_dir_all(ssh_dir.join("conf.d")).expect("create conf.d");        fs::write(ssh_dir.join("config"), "Include conf.d/*.conf\n").expect("write config");        fs::write(            ssh_dir.join("conf.d/devbox.conf"),            "CertificateFile ~/.included/devbox-cert.pub\n",        )        .expect("write include");        assert_eq!(            vec![                home.join(".ssh/config"),                ssh_dir.join("conf.d/devbox.conf"),                home.join(".included/devbox-cert.pub"),            ],            slash_paths(ssh_config_dependency_paths(home))        );    }    fn slash_paths(paths: Vec<PathBuf>) -> Vec<PathBuf> {        paths            .into_iter()            .map(|path| PathBuf::from(path.to_string_lossy().replace('\\', "/")))            .collect()    }}