pub const PUTTY_PATHS: &[&str] = &[
r"Software\SimonTatham\PuTTY\Sessions",
r"Software\SimonTatham\PuTTY\SshHostKeys",
r"Software\SimonTatham\PuTTY\Jumplist\Recent sessions",
];
pub const WINSCP_PATHS: &[&str] = &[
r"Software\Martin Prikryl\WinSCP 2\Sessions",
r"Software\Martin Prikryl\WinSCP 2\Configuration",
];
pub const ONEDRIVE_PATHS: &[&str] = &[
r"Software\Microsoft\OneDrive",
r"Software\Microsoft\OneDrive\Accounts\Personal",
r"Software\Microsoft\OneDrive\Accounts\Business1",
r"SOFTWARE\Policies\Microsoft\Windows\OneDrive",
r"SOFTWARE\Microsoft\OneDrive",
];
pub const DROPBOX_PATHS: &[&str] = &[
r"Software\Dropbox",
r"Software\Dropbox\ks\client",
r"SOFTWARE\Dropbox",
r"SOFTWARE\Microsoft\Windows\CurrentVersion\Uninstall\Dropbox",
];
pub const CHROME_PATHS: &[&str] = &[
r"Software\Google\Chrome",
r"SOFTWARE\Google\Chrome",
r"SOFTWARE\Policies\Google\Chrome",
r"SOFTWARE\Policies\Google\Chrome\ExtensionInstallForcelist",
r"SOFTWARE\Google\Update\Clients",
r"SOFTWARE\Clients\StartMenuInternet\Google Chrome",
];
pub const KITTY_PATHS: &[&str] = &[
r"Software\9bis.com\KiTTY\Sessions",
r"Software\9bis.com\KiTTY\SshHostKeys",
];
pub fn all_third_party_paths() -> impl Iterator<Item = &'static str> {
PUTTY_PATHS
.iter()
.chain(WINSCP_PATHS.iter())
.chain(ONEDRIVE_PATHS.iter())
.chain(DROPBOX_PATHS.iter())
.chain(CHROME_PATHS.iter())
.chain(KITTY_PATHS.iter())
.copied()
}
pub fn is_third_party_artifact_path(path: &str) -> bool {
let lower = path.to_ascii_lowercase();
all_third_party_paths().any(|entry| lower.contains(&entry.to_ascii_lowercase()))
}
pub fn identify_application(path: &str) -> Option<&'static str> {
let lower = path.to_ascii_lowercase();
let matches = |entries: &[&str]| {
entries
.iter()
.any(|e| lower.contains(&e.to_ascii_lowercase()))
};
if matches(PUTTY_PATHS) {
Some("PuTTY")
} else if matches(KITTY_PATHS) {
Some("KiTTY")
} else if matches(WINSCP_PATHS) {
Some("WinSCP")
} else if matches(ONEDRIVE_PATHS) {
Some("OneDrive")
} else if matches(DROPBOX_PATHS) {
Some("Dropbox")
} else if matches(CHROME_PATHS) {
Some("Chrome")
} else {
None
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn putty_paths_contains_sessions_key() {
assert!(PUTTY_PATHS.contains(&r"Software\SimonTatham\PuTTY\Sessions"));
}
#[test]
fn onedrive_paths_contains_hkcu_key() {
assert!(ONEDRIVE_PATHS.contains(&r"Software\Microsoft\OneDrive"));
}
#[test]
fn all_third_party_paths_not_empty() {
assert!(
all_third_party_paths().next().is_some(),
"all_third_party_paths() must yield at least one entry"
);
}
#[test]
fn all_third_party_paths_covers_all_tools() {
let all: Vec<_> = all_third_party_paths().collect();
for path in [
PUTTY_PATHS[0],
WINSCP_PATHS[0],
ONEDRIVE_PATHS[0],
DROPBOX_PATHS[0],
CHROME_PATHS[0],
KITTY_PATHS[0],
] {
assert!(
all.contains(&path),
"Missing path in all_third_party_paths: {path}"
);
}
}
#[test]
fn is_third_party_artifact_path_putty_matches() {
assert!(
is_third_party_artifact_path(r"Software\SimonTatham\PuTTY\Sessions\my-server"),
"PuTTY sessions path must match"
);
}
#[test]
fn is_third_party_artifact_path_case_insensitive() {
assert!(
is_third_party_artifact_path(r"software\simontatham\putty\sessions"),
"Match must be case-insensitive"
);
}
#[test]
fn is_third_party_artifact_path_unrelated_returns_false() {
assert!(
!is_third_party_artifact_path(r"SOFTWARE\Microsoft\Office"),
"Unrelated path must not match"
);
}
#[test]
fn identify_application_putty() {
assert_eq!(
identify_application(r"Software\SimonTatham\PuTTY\SshHostKeys"),
Some("PuTTY"),
"Should identify PuTTY"
);
}
#[test]
fn identify_application_unknown_returns_none() {
assert_eq!(
identify_application(r"SOFTWARE\SomethingElse\Unknown"),
None,
"Unknown path should return None"
);
}
}