diff --git a/Changelog.md b/Changelog.md index b121ef609..8a8b94ee9 100644 --- a/Changelog.md +++ b/Changelog.md @@ -6,14 +6,16 @@ - 修复不同来源的订阅或规则集共用缓存路径时,服务模式无法启动内核的问题 - 优化文件错误日志,显示具体文件路径和失败原因 - 修复 DNS 覆写:空内容报错、初始值覆盖已有设置、已填写字段残留原配置、空字段在高级编辑器中缺失 +- 修复 Merge 覆写 DNS 策略、过滤条件和 hosts 时,旧条目仍被保留的问题 - 修复首次以管理员身份启动后,系统服务因数据目录所有权不符而拒绝启动内核的问题 +**🖥️ Windows** + +- 修复 Windows 服务因路径或目录权限问题无法启动时,缺少重新安装服务的提示 + **🐧 Linux** - Linux 修复安装或修复服务后仍无法启动内核、开启 TUN 模式的问题 -**🖥️ Windows** - -- 修复 Windows 服务因路径或目录权限问题无法启动时,缺少重新安装服务的提示 diff --git a/src-tauri/src/enhance/merge.rs b/src-tauri/src/enhance/merge.rs index db524b8aa..c1c00fc05 100644 --- a/src-tauri/src/enhance/merge.rs +++ b/src-tauri/src/enhance/merge.rs @@ -1,5 +1,3 @@ -use clash_verge_logging::{Type, logging}; - use super::field::use_lowercase; use serde_yaml_ng::{self, Mapping, Value}; @@ -18,20 +16,19 @@ fn deep_merge(a: &mut Value, b: Value) { } } -pub fn use_merge(merge: &Mapping, config: Mapping) -> Mapping { - let mut config = Value::from(config); - let merge = use_lowercase(merge); - - deep_merge(&mut config, Value::from(merge)); - - config.as_mapping().cloned().unwrap_or_else(|| { - logging!( - error, - Type::Core, - "Failed to convert merged config to mapping, using empty mapping" - ); - Mapping::new() - }) +pub fn use_merge(merge: &Mapping, mut config: Mapping) -> Mapping { + for (key, value) in use_lowercase(merge) { + match (config.get_mut(&key), value) { + (Some(Value::Mapping(existing)), Value::Mapping(overlay)) if key.as_str() == Some("dns") => { + existing.extend(overlay); + } + (Some(existing), value) if key.as_str() != Some("hosts") => deep_merge(existing, value), + (_, value) => { + config.insert(key, value); + } + } + } + config } #[test] @@ -54,18 +51,49 @@ fn test_merge() -> anyhow::Result<()> { enable: true dns: enable: true + nameserver-policy: + new.example: 1.1.1.1 + fallback-filter: + geoip: false + hosts: + new.example: 192.0.2.2 "; let config = r" rules: - aaaaa script1: test + tun: + mtu: 1500 + dns: + nameserver: [9.9.9.9] + nameserver-policy: + old.example: 8.8.8.8 + fallback-filter: + geoip: true + domain: [old.example] + hosts: + old.example: 192.0.2.1 "; let merge = serde_yaml_ng::from_str::(merge)?; let config = serde_yaml_ng::from_str::(config)?; - let _ = serde_yaml_ng::to_string(&use_merge(&merge, config))?; + let result = use_merge(&merge, config); + assert_eq!(result["dns"]["nameserver-policy"], merge["dns"]["nameserver-policy"]); + assert_eq!(result["dns"]["fallback-filter"], merge["dns"]["fallback-filter"]); + assert_eq!(result["hosts"], merge["hosts"]); + assert_eq!(result["dns"]["nameserver"], serde_yaml_ng::to_value(["9.9.9.9"])?); + assert_eq!(result["tun"]["mtu"], Value::from(1500)); + assert_eq!(result["rules"], merge["rules"]); + assert_eq!(result["script1"], Value::from("test")); + + let cleared = serde_yaml_ng::from_str::( + "dns: {enable: false, nameserver-policy: {}, fallback-filter: {}, nameserver: []}\nhosts: {}", + )?; + let result = use_merge(&cleared, result); + assert_eq!(result["dns"], cleared["dns"]); + assert_eq!(result["hosts"], cleared["hosts"]); Ok(()) } diff --git a/src-tauri/src/enhance/mod.rs b/src-tauri/src/enhance/mod.rs index fb5e4892b..9807fe489 100644 --- a/src-tauri/src/enhance/mod.rs +++ b/src-tauri/src/enhance/mod.rs @@ -1433,6 +1433,38 @@ mod tests { serde_yaml_ng::from_str(yaml).expect("test config should be valid") } + #[test] + fn merge_replaces_dns_fields_with_or_without_dns_settings() { + let profile = mapping( + "dns: {nameserver: [9.9.9.9], nameserver-policy: {profile.example: 9.9.9.9}}\n\ + hosts: {profile.example: 192.0.2.1}", + ); + let settings = mapping( + "dns: {ipv6: true, nameserver: [8.8.8.8], nameserver-policy: {settings.example: 8.8.8.8}}\n\ + hosts: {settings.example: 192.0.2.2}", + ); + let merge = mapping( + "dns: {ipv6: false, nameserver-policy: {merge.example: 1.1.1.1}}\n\ + hosts: {merge.example: 192.0.2.3}", + ); + + for enabled in [false, true] { + let (config, owns_ipv6) = if enabled { + super::merge_dns_config(profile.clone(), settings.clone()) + } else { + (profile.clone(), false) + }; + let authoritative = AuthoritativeFields::capture(&config, &[], owns_ipv6); + let result = authoritative.enforce(super::use_merge(&merge, config)); + + assert_eq!(result["dns"]["nameserver-policy"], merge["dns"]["nameserver-policy"]); + assert_eq!(result["hosts"], merge["hosts"]); + let source = if enabled { &settings } else { &profile }; + assert_eq!(result["dns"]["nameserver"], source["dns"]["nameserver"]); + assert_eq!(result["dns"]["ipv6"], serde_yaml_ng::Value::from(enabled)); + } + } + #[tokio::test] async fn manual_overrides_follow_expected_priority() { let mut config = mapping(