diff --git a/src-tauri/src/component_detection.rs b/src-tauri/src/component_detection.rs index 87d12dd..5717835 100644 --- a/src-tauri/src/component_detection.rs +++ b/src-tauri/src/component_detection.rs @@ -1240,7 +1240,26 @@ fn singbox_xml_identity_matches(xml: &str) -> bool { } declaration_seen = true; } - Event::PI(_) | Event::DocType(_) | Event::GeneralRef(_) => return false, + Event::GeneralRef(reference) => { + // quick-xml emits escaped quotes (including our own WinSW XML) + // separately from text. Resolve only built-in/numeric XML entities; + // custom entities and DTDs remain forbidden. + let name = match std::str::from_utf8(reference.as_ref()) { + Ok(name) => name, + Err(_) => return false, + }; + let encoded = format!("&{name};"); + let decoded = match unescape(&encoded) { + Ok(decoded) => decoded, + Err(_) => return false, + }; + if active_field.is_some() { + active_value.push_str(&decoded); + } else if depth == 0 || root_closed { + return false; + } + } + Event::PI(_) | Event::DocType(_) => return false, Event::Eof => break, } } diff --git a/src-tauri/tests/component_detection_tests.rs b/src-tauri/tests/component_detection_tests.rs index e7c1d62..000d95c 100644 --- a/src-tauri/tests/component_detection_tests.rs +++ b/src-tauri/tests/component_detection_tests.rs @@ -712,6 +712,42 @@ fn winsw_xml() -> &'static str { "# } +#[test] +fn current_singbox_recognizes_the_native_installer_xml() { + let xml = proxywarden_lib::singbox_service::singbox_service_xml(); + for xml in [ + xml.to_owned(), + xml.replace(""", """), + xml.replace(""", """), + ] { + let inventory = inventory_singbox_with_host(¤t_singbox_host_with_xml(&xml)); + assert_eq!( + inventory.classification(), + ComponentClassification::ManagedCurrent + ); + } + for entity in ["&unknown;", """, "�", "�"] { + let xml = xml.replace(""", entity); + let inventory = inventory_singbox_with_host(¤t_singbox_host_with_xml(&xml)); + assert_ne!( + inventory.classification(), + ComponentClassification::ManagedCurrent + ); + } +} + +#[cfg(windows)] +#[test] +#[ignore = "read-only smoke test requiring an installed managed sing-box"] +fn installed_singbox_inventory_is_current() { + let inventory = proxywarden_lib::component_detection::inventory_singbox(); + assert_eq!( + inventory.classification(), + ComponentClassification::ManagedCurrent, + "{inventory:?}" + ); +} + fn current_singbox_host_with_xml(xml: &str) -> MockHost { MockHost::new() .with_path(r"C:\Program Files\ProxyWarden\components\sing-box\sing-box.exe")