@@ -1,7 +1,15 @@
|
||||
#[cfg(windows)]
|
||||
use proxywarden_lib::component_detection::SystemProxyfierDetectionHost;
|
||||
use proxywarden_lib::component_detection::{
|
||||
detect_proxyfier_install_with_host, detect_singbox_install_with_host,
|
||||
proxyfier_component_from_detection, singbox_component_from_detection, ProxyfierDetectionHost,
|
||||
ProxyfierEngine, RegistryInstallEntry,
|
||||
has_additional_matching_legacy_proxifyre_service_with_host, inventory_proxyfier_with_host,
|
||||
inventory_singbox_with_host, matches_legacy_proxifyre_2_2_1_manifest,
|
||||
proxyfier_component_from_detection, proxyfier_component_from_inventory,
|
||||
service_executable_from_path_name, singbox_component_from_detection, LegacyPackageFileIdentity,
|
||||
ProxyfierDetectionHost, ProxyfierEngine, RegistryInstallEntry, LEGACY_PROXIFYRE_2_2_1_MANIFEST,
|
||||
};
|
||||
use proxywarden_lib::component_inventory::{
|
||||
BinaryIdentityEvidence, ComponentClassification, OWNERSHIP_MISMATCH,
|
||||
};
|
||||
use proxywarden_lib::models::ComponentState;
|
||||
use std::{
|
||||
@@ -18,7 +26,9 @@ fn detects_existing_proxifyre_from_registry_install_location() {
|
||||
.with_service_path(
|
||||
"ProxiFyreService",
|
||||
r#""C:\Tools\ProxiFyre\ProxiFyre.exe" --service"#,
|
||||
);
|
||||
)
|
||||
.with_known_binary(r"C:\Tools\ProxiFyre\ProxiFyre.exe")
|
||||
.with_version(r"C:\Tools\ProxiFyre\ProxiFyre.exe", "2.2.1.0");
|
||||
|
||||
let detected = detect_proxyfier_install_with_host(&host)
|
||||
.expect("existing ProxiFyre install should be detected");
|
||||
@@ -32,6 +42,7 @@ fn detects_existing_proxifyre_from_registry_install_location() {
|
||||
assert!(detected.running);
|
||||
assert_eq!(detected.service_name, Some("ProxiFyreService".to_string()));
|
||||
assert_eq!(detected.service_status, Some("running".to_string()));
|
||||
assert_eq!(detected.version, Some("2.2.1.0".to_string()));
|
||||
|
||||
let component = proxyfier_component_from_detection(Some(&detected));
|
||||
assert_eq!(component.state, ComponentState::Running);
|
||||
@@ -40,9 +51,72 @@ fn detects_existing_proxifyre_from_registry_install_location() {
|
||||
assert_eq!(component.path, Some(r"C:\Tools\ProxiFyre".to_string()));
|
||||
assert_eq!(component.service_name, Some("ProxiFyreService".to_string()));
|
||||
assert_eq!(component.service_status, Some("running".to_string()));
|
||||
assert_eq!(component.version, Some("2.2.1.0".to_string()));
|
||||
assert!(component.problems.is_empty());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn detects_current_proxifyre_only_with_strong_marker_and_exact_service_path() {
|
||||
let root = r"C:\Program Files\ProxyWarden\components\ProxiFyre";
|
||||
let executable = r"C:\Program Files\ProxyWarden\components\ProxiFyre\ProxiFyre.exe";
|
||||
let marker = serde_json::json!({
|
||||
"manager": "ProxyWarden",
|
||||
"component": "proxifyre",
|
||||
"serviceName": "ProxiFyreService",
|
||||
"installRoot": root,
|
||||
"packetFilterInstalledByProxyWarden": false
|
||||
})
|
||||
.to_string();
|
||||
let host = MockHost::new()
|
||||
.with_path(root)
|
||||
.with_path(executable)
|
||||
.with_text(
|
||||
r"C:\Program Files\ProxyWarden\components\ProxiFyre\proxywarden-component.json",
|
||||
&marker,
|
||||
)
|
||||
.with_stopped_service_path(
|
||||
"ProxiFyreService",
|
||||
r#""C:\Program Files\ProxyWarden\components\ProxiFyre\ProxiFyre.exe" --service"#,
|
||||
)
|
||||
.with_version(executable, "2.4.0.0");
|
||||
|
||||
let detected = detect_proxyfier_install_with_host(&host).expect("managed current ProxiFyre");
|
||||
assert_eq!(detected.install_dir, PathBuf::from(root));
|
||||
assert_eq!(detected.version, Some("2.4.0.0".to_string()));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn current_proxifyre_with_foreign_same_name_service_is_ownership_mismatch() {
|
||||
let root = r"C:\Program Files\ProxyWarden\components\ProxiFyre";
|
||||
let executable = r"C:\Program Files\ProxyWarden\components\ProxiFyre\ProxiFyre.exe";
|
||||
let marker = serde_json::json!({
|
||||
"manager": "ProxyWarden",
|
||||
"component": "proxifyre",
|
||||
"serviceName": "ProxiFyreService",
|
||||
"installRoot": root,
|
||||
"packetFilterInstalledByProxyWarden": false
|
||||
})
|
||||
.to_string();
|
||||
let host = MockHost::new()
|
||||
.with_path(root)
|
||||
.with_path(executable)
|
||||
.with_text(
|
||||
r"C:\Program Files\ProxyWarden\components\ProxiFyre\proxywarden-component.json",
|
||||
&marker,
|
||||
)
|
||||
.with_service_path(
|
||||
"ProxiFyreService",
|
||||
r#""C:\Foreign\ProxiFyre.exe" --service"#,
|
||||
);
|
||||
|
||||
let inventory = inventory_proxyfier_with_host(&host);
|
||||
assert_eq!(inventory.classification(), ComponentClassification::Foreign);
|
||||
assert_eq!(
|
||||
inventory.selected_candidate().unwrap().issues[0].code,
|
||||
OWNERSHIP_MISMATCH
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn ignores_empty_common_proxifyre_folder_without_executable() {
|
||||
let host = MockHost::new().with_path(r"C:\Tools\ProxiFyre");
|
||||
@@ -65,19 +139,14 @@ fn ignores_plain_proxifier_install() {
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn env_override_can_point_to_portable_proxifyre_install() {
|
||||
fn env_override_does_not_make_portable_proxifyre_managed() {
|
||||
let host = MockHost::new()
|
||||
.with_env("PROXYWARDEN_PROXIFYRE_ROOT", r"D:\Portable\ProxiFyre")
|
||||
.with_path(r"D:\Portable\ProxiFyre\ProxiFyre.exe");
|
||||
|
||||
let detected = detect_proxyfier_install_with_host(&host)
|
||||
.expect("env override should be checked before common paths");
|
||||
|
||||
assert_eq!(detected.engine, ProxyfierEngine::ProxiFyre);
|
||||
assert_eq!(
|
||||
detected.executable_path,
|
||||
PathBuf::from(r"D:\Portable\ProxiFyre\ProxiFyre.exe")
|
||||
);
|
||||
assert!(detect_proxyfier_install_with_host(&host).is_none());
|
||||
let inventory = inventory_proxyfier_with_host(&host);
|
||||
assert_eq!(inventory.classification(), ComponentClassification::Foreign);
|
||||
}
|
||||
|
||||
#[test]
|
||||
@@ -88,7 +157,8 @@ fn reports_stopped_proxifyre_service_when_executable_exists() {
|
||||
.with_stopped_service_path(
|
||||
"ProxiFyreService",
|
||||
r#""C:\Tools\ProxiFyre\ProxiFyre.exe" --service"#,
|
||||
);
|
||||
)
|
||||
.with_known_binary(r"C:\Tools\ProxiFyre\ProxiFyre.exe");
|
||||
|
||||
let detected =
|
||||
detect_proxyfier_install_with_host(&host).expect("proxifyre executable should be detected");
|
||||
@@ -102,6 +172,45 @@ fn reports_stopped_proxifyre_service_when_executable_exists() {
|
||||
assert!(component.problems.is_empty());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn primary_and_alias_same_root_remain_discoverable_but_cutover_is_ambiguous() {
|
||||
let root = Path::new(r"C:\Tools\ProxiFyre");
|
||||
let executable = root.join("ProxiFyre.exe");
|
||||
let host = MockHost::new()
|
||||
.with_path(executable.to_str().expect("fixture path"))
|
||||
.with_service_path(
|
||||
"ProxiFyreService",
|
||||
r#""C:\Tools\ProxiFyre\ProxiFyre.exe" -displayname "ProxiFyre Service" -servicename ProxiFyreService"#,
|
||||
)
|
||||
.with_service_path(
|
||||
"ProxiFyre",
|
||||
r#""C:\Tools\ProxiFyre\ProxiFyre.exe" --service"#,
|
||||
)
|
||||
.with_known_binary(executable.to_str().expect("fixture path"));
|
||||
|
||||
assert_eq!(
|
||||
inventory_proxyfier_with_host(&host).classification(),
|
||||
ComponentClassification::ManagedLegacy,
|
||||
"Task 5 discovery/Start/Stop classification stays unchanged"
|
||||
);
|
||||
assert!(has_additional_matching_legacy_proxifyre_service_with_host(
|
||||
&host, root
|
||||
));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn any_present_alias_service_makes_the_strict_service_set_ambiguous() {
|
||||
let root = Path::new(r"C:\Tools\ProxiFyre");
|
||||
for host in [
|
||||
MockHost::new().with_service("ProxiFyre"),
|
||||
MockHost::new().with_service_path("ProxiFyre", r#""C:\Foreign\ProxiFyre.exe" --service"#),
|
||||
] {
|
||||
assert!(has_additional_matching_legacy_proxifyre_service_with_host(
|
||||
&host, root
|
||||
));
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn missing_proxyfier_returns_install_action_status() {
|
||||
let component = proxyfier_component_from_detection(None);
|
||||
@@ -121,11 +230,15 @@ fn ignores_known_service_name_when_path_points_to_foreign_binary() {
|
||||
r#""C:\Foreign\ProxiFyre.exe" --service"#,
|
||||
);
|
||||
|
||||
let detected =
|
||||
detect_proxyfier_install_with_host(&host).expect("executable should still be detected");
|
||||
|
||||
assert!(!detected.running);
|
||||
assert_eq!(detected.service_status, None);
|
||||
assert!(detect_proxyfier_install_with_host(&host).is_none());
|
||||
let inventory = inventory_proxyfier_with_host(&host);
|
||||
let candidate = inventory.selected_candidate().expect("foreign collision");
|
||||
assert_eq!(candidate.classification, ComponentClassification::Foreign);
|
||||
assert_eq!(candidate.issues[0].code, OWNERSHIP_MISMATCH);
|
||||
let component = proxyfier_component_from_inventory(&inventory);
|
||||
assert_eq!(component.state, ComponentState::Error);
|
||||
assert!(!component.running);
|
||||
assert!(component.actions.is_empty());
|
||||
}
|
||||
|
||||
#[test]
|
||||
@@ -135,18 +248,32 @@ fn ignores_known_service_name_without_path_metadata() {
|
||||
.with_path(r"C:\Tools\ProxiFyre\ProxiFyre.exe")
|
||||
.with_service("ProxiFyreService");
|
||||
|
||||
let detected =
|
||||
detect_proxyfier_install_with_host(&host).expect("executable should still be detected");
|
||||
|
||||
assert!(!detected.running);
|
||||
assert_eq!(detected.service_status, None);
|
||||
assert!(detect_proxyfier_install_with_host(&host).is_none());
|
||||
let inventory = inventory_proxyfier_with_host(&host);
|
||||
assert_eq!(inventory.classification(), ComponentClassification::Foreign);
|
||||
assert_eq!(
|
||||
inventory.selected_candidate().unwrap().issues[0].code,
|
||||
OWNERSHIP_MISMATCH
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn detects_running_local_singbox_from_default_install_root_and_service() {
|
||||
let host = MockHost::new()
|
||||
.with_path(r"C:\Program Files\ProxyWarden\components\sing-box\sing-box.exe")
|
||||
.with_service("ProxyWardenSingBox");
|
||||
.with_path(r"C:\Program Files\ProxyWarden\components\sing-box\ProxyWardenSingBox.exe")
|
||||
.with_text(
|
||||
r"C:\Program Files\ProxyWarden\components\sing-box\ProxyWardenSingBox.xml",
|
||||
winsw_xml(),
|
||||
)
|
||||
.with_service_path(
|
||||
"ProxyWardenSingBox",
|
||||
r#""C:\Program Files\ProxyWarden\components\sing-box\ProxyWardenSingBox.exe""#,
|
||||
)
|
||||
.with_version(
|
||||
r"C:\Program Files\ProxyWarden\components\sing-box\sing-box.exe",
|
||||
"1.11.0.0",
|
||||
);
|
||||
|
||||
let detected =
|
||||
detect_singbox_install_with_host(&host).expect("existing sing-box should be detected");
|
||||
@@ -157,6 +284,7 @@ fn detects_running_local_singbox_from_default_install_root_and_service() {
|
||||
);
|
||||
assert_eq!(detected.service_name, "ProxyWardenSingBox");
|
||||
assert!(detected.running);
|
||||
assert_eq!(detected.version, Some("1.11.0.0".to_string()));
|
||||
|
||||
let component = singbox_component_from_detection(Some(&detected));
|
||||
assert_eq!(component.state, ComponentState::Running);
|
||||
@@ -170,30 +298,99 @@ fn detects_running_local_singbox_from_default_install_root_and_service() {
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn detects_stopped_local_singbox_from_env_override() {
|
||||
fn current_singbox_with_foreign_same_name_service_is_ownership_mismatch() {
|
||||
let host = MockHost::new()
|
||||
.with_path(r"C:\Program Files\ProxyWarden\components\sing-box\sing-box.exe")
|
||||
.with_path(r"C:\Program Files\ProxyWarden\components\sing-box\ProxyWardenSingBox.exe")
|
||||
.with_text(
|
||||
r"C:\Program Files\ProxyWarden\components\sing-box\ProxyWardenSingBox.xml",
|
||||
winsw_xml(),
|
||||
)
|
||||
.with_service_path(
|
||||
"ProxyWardenSingBox",
|
||||
r#""C:\Foreign\ProxyWardenSingBox.exe""#,
|
||||
);
|
||||
|
||||
let inventory = inventory_singbox_with_host(&host);
|
||||
assert_eq!(inventory.classification(), ComponentClassification::Foreign);
|
||||
assert_eq!(
|
||||
inventory.selected_candidate().unwrap().issues[0].code,
|
||||
OWNERSHIP_MISMATCH
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn winsw_identity_cannot_be_spoofed_by_comments_or_unrelated_nodes() {
|
||||
let spoofed_xml = r#"<service>
|
||||
<!-- <id>ProxyWardenSingBox</id> -->
|
||||
<!-- <executable>%BASE%\sing-box.exe</executable> -->
|
||||
<metadata><arguments>run -c "%BASE%\config.json"</arguments></metadata>
|
||||
<id>ForeignService</id>
|
||||
<executable>C:\Foreign\sing-box.exe</executable>
|
||||
<arguments>run -c "C:\Foreign\config.json"</arguments>
|
||||
</service>"#;
|
||||
let host = MockHost::new()
|
||||
.with_path(r"C:\Program Files\ProxyWarden\components\sing-box\sing-box.exe")
|
||||
.with_path(r"C:\Program Files\ProxyWarden\components\sing-box\ProxyWardenSingBox.exe")
|
||||
.with_text(
|
||||
r"C:\Program Files\ProxyWarden\components\sing-box\ProxyWardenSingBox.xml",
|
||||
spoofed_xml,
|
||||
)
|
||||
.with_service_path(
|
||||
"ProxyWardenSingBox",
|
||||
r#""C:\Program Files\ProxyWarden\components\sing-box\ProxyWardenSingBox.exe""#,
|
||||
);
|
||||
|
||||
let inventory = inventory_singbox_with_host(&host);
|
||||
assert_eq!(
|
||||
inventory.classification(),
|
||||
ComponentClassification::Incomplete
|
||||
);
|
||||
assert!(detect_singbox_install_with_host(&host).is_none());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn winsw_identity_rejects_duplicate_dtd_cdata_second_root_and_oversized_xml() {
|
||||
let oversized = format!(
|
||||
"<service><id>ProxyWardenSingBox</id><executable>%BASE%\\sing-box.exe</executable><arguments>run -c \"%BASE%\\config.json\"</arguments><description>{}</description></service>",
|
||||
"x".repeat(65 * 1024)
|
||||
);
|
||||
let invalid_xml = vec![
|
||||
r#"<service><id>ProxyWardenSingBox</id><id>ProxyWardenSingBox</id><executable>%BASE%\sing-box.exe</executable><arguments>run -c "%BASE%\config.json"</arguments></service>"#.to_string(),
|
||||
r#"<!DOCTYPE service [<!ENTITY owned "ProxyWardenSingBox">]><service><id>&owned;</id><executable>%BASE%\sing-box.exe</executable><arguments>run -c "%BASE%\config.json"</arguments></service>"#.to_string(),
|
||||
r#"<service><id><![CDATA[ProxyWardenSingBox]]></id><executable>%BASE%\sing-box.exe</executable><arguments>run -c "%BASE%\config.json"</arguments></service>"#.to_string(),
|
||||
format!("{}<service></service>", winsw_xml()),
|
||||
oversized,
|
||||
];
|
||||
|
||||
for xml in invalid_xml {
|
||||
let host = current_singbox_host_with_xml(&xml);
|
||||
assert!(
|
||||
detect_singbox_install_with_host(&host).is_none(),
|
||||
"unsafe WinSW XML was accepted"
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn winsw_identity_accepts_xml_declaration_bom_and_current_extra_nodes() {
|
||||
let xml = "\u{feff}<?xml version=\"1.0\" encoding=\"utf-8\"?><service><id>ProxyWardenSingBox</id><name>ProxyWarden Local sing-box</name><executable>%BASE%\\sing-box.exe</executable><arguments>run -c \"%BASE%\\config.json\"</arguments><log mode=\"roll-by-size\"><keepFiles>4</keepFiles></log><onfailure action=\"restart\" /></service>".to_string();
|
||||
let host = current_singbox_host_with_xml(&xml);
|
||||
|
||||
assert!(detect_singbox_install_with_host(&host).is_some());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn portable_singbox_env_override_remains_foreign() {
|
||||
let host = MockHost::new()
|
||||
.with_env("PROXYWARDEN_SINGBOX_ROOT", r"D:\Portable\sing-box")
|
||||
.with_path(r"D:\Portable\sing-box\sing-box.exe");
|
||||
|
||||
let detected = detect_singbox_install_with_host(&host).expect("env override should be checked");
|
||||
let component = singbox_component_from_detection(Some(&detected));
|
||||
|
||||
assert!(detect_singbox_install_with_host(&host).is_none());
|
||||
assert_eq!(
|
||||
detected.executable_path,
|
||||
PathBuf::from(r"D:\Portable\sing-box\sing-box.exe")
|
||||
inventory_singbox_with_host(&host).classification(),
|
||||
ComponentClassification::Foreign
|
||||
);
|
||||
assert_eq!(component.state, ComponentState::Stopped);
|
||||
assert!(component.installed);
|
||||
assert!(!component.running);
|
||||
assert_eq!(
|
||||
component.service_name,
|
||||
Some("ProxyWardenSingBox".to_string())
|
||||
);
|
||||
assert_eq!(component.service_status, Some("stopped".to_string()));
|
||||
assert!(component
|
||||
.problems
|
||||
.iter()
|
||||
.any(|problem| problem.contains("остановлена")));
|
||||
}
|
||||
|
||||
#[test]
|
||||
@@ -207,6 +404,153 @@ fn missing_local_singbox_returns_optional_install_action_status() {
|
||||
assert!(component.problems.is_empty());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn parses_service_pathname_without_accepting_malformed_quotes() {
|
||||
assert_eq!(
|
||||
service_executable_from_path_name(
|
||||
r#""C:\Program Files\ProxyWarden\components\sing-box\ProxyWardenSingBox.exe" install"#,
|
||||
),
|
||||
Some(PathBuf::from(
|
||||
r"C:\Program Files\ProxyWarden\components\sing-box\ProxyWardenSingBox.exe"
|
||||
))
|
||||
);
|
||||
assert_eq!(
|
||||
service_executable_from_path_name(r"C:\Tools\ProxiFyre\ProxiFyre.exe --service"),
|
||||
Some(PathBuf::from(r"C:\Tools\ProxiFyre\ProxiFyre.exe"))
|
||||
);
|
||||
assert!(service_executable_from_path_name(r#""C:\Broken\ProxiFyre.exe --service"#).is_none());
|
||||
assert!(service_executable_from_path_name(" ").is_none());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn frozen_proxifyre_manifest_matches_all_ten_files_and_nothing_less() {
|
||||
let observed = LEGACY_PROXIFYRE_2_2_1_MANIFEST
|
||||
.iter()
|
||||
.rev()
|
||||
.map(|file| LegacyPackageFileIdentity {
|
||||
relative_path: PathBuf::from(file.relative_path.to_ascii_uppercase()),
|
||||
size: file.size,
|
||||
sha256: file.sha256.to_ascii_uppercase(),
|
||||
})
|
||||
.collect::<Vec<_>>();
|
||||
assert!(matches_legacy_proxifyre_2_2_1_manifest(&observed));
|
||||
|
||||
let mut missing = observed.clone();
|
||||
missing.pop();
|
||||
assert!(!matches_legacy_proxifyre_2_2_1_manifest(&missing));
|
||||
|
||||
let mut wrong_size = observed.clone();
|
||||
wrong_size[0].size += 1;
|
||||
assert!(!matches_legacy_proxifyre_2_2_1_manifest(&wrong_size));
|
||||
|
||||
let mut wrong_hash = observed.clone();
|
||||
wrong_hash[0].sha256 = "0".repeat(64);
|
||||
assert!(!matches_legacy_proxifyre_2_2_1_manifest(&wrong_hash));
|
||||
|
||||
let mut extra = observed.clone();
|
||||
extra.push(LegacyPackageFileIdentity {
|
||||
relative_path: PathBuf::from("unexpected.dll"),
|
||||
size: 1,
|
||||
sha256: "0".repeat(64),
|
||||
});
|
||||
assert!(!matches_legacy_proxifyre_2_2_1_manifest(&extra));
|
||||
|
||||
let mut duplicate = observed;
|
||||
duplicate[0] = duplicate[1].clone();
|
||||
assert!(!matches_legacy_proxifyre_2_2_1_manifest(&duplicate));
|
||||
}
|
||||
|
||||
#[test]
|
||||
#[cfg(windows)]
|
||||
fn reads_windows_pe_file_version_without_executing_binary() {
|
||||
let windows_dir = std::env::var("WINDIR").expect("WINDIR on Windows");
|
||||
let notepad = PathBuf::from(windows_dir)
|
||||
.join("System32")
|
||||
.join("notepad.exe");
|
||||
let version = SystemProxyfierDetectionHost
|
||||
.file_version(¬epad)
|
||||
.expect("notepad PE version");
|
||||
|
||||
assert_eq!(version.split('.').count(), 4);
|
||||
assert!(version
|
||||
.split('.')
|
||||
.all(|segment| segment.parse::<u32>().is_ok()));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn production_component_detection_has_only_native_windows_owners() {
|
||||
let source = include_str!("../src/component_detection.rs");
|
||||
let source_lower = source.to_ascii_lowercase();
|
||||
|
||||
for forbidden in [
|
||||
"command_no_window(",
|
||||
"get-process",
|
||||
"get-service",
|
||||
"get-ciminstance",
|
||||
"\"powershell\"",
|
||||
"std::process::command",
|
||||
"extern \"system\"",
|
||||
"#[link(",
|
||||
] {
|
||||
assert!(
|
||||
!source_lower.contains(forbidden),
|
||||
"production detection still contains shell boundary: {forbidden}"
|
||||
);
|
||||
}
|
||||
for native_owner in [
|
||||
"CreateToolhelp32Snapshot",
|
||||
"OpenSCManagerW",
|
||||
"QueryServiceStatusEx",
|
||||
"QueryServiceConfigW",
|
||||
"winreg::",
|
||||
] {
|
||||
assert!(
|
||||
source.contains(native_owner),
|
||||
"native detection owner is missing: {native_owner}"
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
#[cfg(windows)]
|
||||
fn native_process_inventory_finds_the_running_test_binary() {
|
||||
let executable_name = std::env::current_exe()
|
||||
.expect("current test executable")
|
||||
.file_name()
|
||||
.expect("current test executable file name")
|
||||
.to_string_lossy()
|
||||
.into_owned();
|
||||
|
||||
assert!(SystemProxyfierDetectionHost.process_running(&executable_name));
|
||||
assert!(SystemProxyfierDetectionHost.process_running(&executable_name.to_ascii_uppercase()));
|
||||
assert!(SystemProxyfierDetectionHost.process_running(executable_name.trim_end_matches(".exe")));
|
||||
}
|
||||
|
||||
#[test]
|
||||
#[cfg(windows)]
|
||||
fn native_service_inventory_reads_status_and_path_from_scm() {
|
||||
let service = SystemProxyfierDetectionHost
|
||||
.service_info("EventLog")
|
||||
.expect("Windows EventLog service should be queryable without elevation");
|
||||
|
||||
assert_eq!(service.name, "EventLog");
|
||||
assert!(matches!(
|
||||
service.status.as_str(),
|
||||
"stopped"
|
||||
| "start pending"
|
||||
| "stop pending"
|
||||
| "running"
|
||||
| "continue pending"
|
||||
| "pause pending"
|
||||
| "paused"
|
||||
| "unknown"
|
||||
));
|
||||
assert!(service
|
||||
.path_name
|
||||
.as_deref()
|
||||
.is_some_and(|path| !path.trim().is_empty()));
|
||||
}
|
||||
|
||||
#[derive(Default)]
|
||||
struct MockHost {
|
||||
env: HashMap<String, String>,
|
||||
@@ -214,6 +558,9 @@ struct MockHost {
|
||||
processes: HashSet<String>,
|
||||
services: HashMap<String, String>,
|
||||
service_paths: HashMap<String, String>,
|
||||
texts: HashMap<String, String>,
|
||||
known_binaries: HashSet<String>,
|
||||
versions: HashMap<String, String>,
|
||||
registry: Vec<RegistryInstallEntry>,
|
||||
}
|
||||
|
||||
@@ -267,6 +614,24 @@ impl MockHost {
|
||||
});
|
||||
self
|
||||
}
|
||||
|
||||
fn with_text(mut self, path: &str, contents: &str) -> Self {
|
||||
self.paths.insert(normalize_path(path));
|
||||
self.texts
|
||||
.insert(normalize_path(path), contents.to_string());
|
||||
self
|
||||
}
|
||||
|
||||
fn with_known_binary(mut self, path: &str) -> Self {
|
||||
self.known_binaries.insert(normalize_path(path));
|
||||
self
|
||||
}
|
||||
|
||||
fn with_version(mut self, path: &str, version: &str) -> Self {
|
||||
self.versions
|
||||
.insert(normalize_path(path), version.to_string());
|
||||
self
|
||||
}
|
||||
}
|
||||
|
||||
impl ProxyfierDetectionHost for MockHost {
|
||||
@@ -306,8 +671,57 @@ impl ProxyfierDetectionHost for MockHost {
|
||||
fn registry_install_entries(&self) -> Vec<RegistryInstallEntry> {
|
||||
self.registry.clone()
|
||||
}
|
||||
|
||||
fn read_text(&self, path: &Path) -> Option<String> {
|
||||
self.texts
|
||||
.get(&normalize_path(&path.display().to_string()))
|
||||
.cloned()
|
||||
}
|
||||
|
||||
fn file_version(&self, path: &Path) -> Option<String> {
|
||||
self.versions
|
||||
.get(&normalize_path(&path.display().to_string()))
|
||||
.cloned()
|
||||
}
|
||||
|
||||
fn binary_identity(
|
||||
&self,
|
||||
_component_id: &proxywarden_lib::models::ComponentId,
|
||||
path: &Path,
|
||||
) -> BinaryIdentityEvidence {
|
||||
if self
|
||||
.known_binaries
|
||||
.contains(&normalize_path(&path.display().to_string()))
|
||||
{
|
||||
BinaryIdentityEvidence::KnownPackage
|
||||
} else {
|
||||
BinaryIdentityEvidence::Unknown
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn normalize_path(path: &str) -> String {
|
||||
path.replace('/', "\\").to_ascii_lowercase()
|
||||
}
|
||||
|
||||
fn winsw_xml() -> &'static str {
|
||||
r#"<service>
|
||||
<id>ProxyWardenSingBox</id>
|
||||
<executable>%BASE%\sing-box.exe</executable>
|
||||
<arguments>run -c "%BASE%\config.json"</arguments>
|
||||
</service>"#
|
||||
}
|
||||
|
||||
fn current_singbox_host_with_xml(xml: &str) -> MockHost {
|
||||
MockHost::new()
|
||||
.with_path(r"C:\Program Files\ProxyWarden\components\sing-box\sing-box.exe")
|
||||
.with_path(r"C:\Program Files\ProxyWarden\components\sing-box\ProxyWardenSingBox.exe")
|
||||
.with_text(
|
||||
r"C:\Program Files\ProxyWarden\components\sing-box\ProxyWardenSingBox.xml",
|
||||
xml,
|
||||
)
|
||||
.with_service_path(
|
||||
"ProxyWardenSingBox",
|
||||
r#""C:\Program Files\ProxyWarden\components\sing-box\ProxyWardenSingBox.exe""#,
|
||||
)
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user