mirror of
https://github.com/clash-verge-rev/clash-verge-rev.git
synced 2026-10-22 19:52:14 +08:00
fix: limit mihomo yaml wildcard quoting to fake-ip-filter
This commit is contained in:
@@ -1,5 +1,4 @@
|
||||
use crate::constants::files::DNS_CONFIG;
|
||||
use crate::utils::yaml_emitter;
|
||||
use crate::{config::Config, process::AsyncHandler, utils::dirs};
|
||||
use anyhow::Error;
|
||||
use arc_swap::{ArcSwap, ArcSwapOption};
|
||||
@@ -288,7 +287,7 @@ pub async fn create_backup() -> Result<(String, PathBuf), Error> {
|
||||
obj.remove("webdav_url");
|
||||
}
|
||||
zip.start_file(dirs::VERGE_CONFIG, options)?;
|
||||
zip.write_all(yaml_emitter::to_mihomo_config_string(&verge_config)?.as_bytes())?;
|
||||
zip.write_all(serde_yaml_ng::to_string(&verge_config)?.as_bytes())?;
|
||||
|
||||
let dns_config_path = dirs::app_home_dir()?.join(DNS_CONFIG);
|
||||
if dns_config_path.exists() {
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
use crate::{config::with_encryption, enhance::seq::SeqMap, utils::yaml_emitter};
|
||||
use crate::{config::with_encryption, enhance::seq::SeqMap};
|
||||
use anyhow::{Context as _, Result, anyhow, bail};
|
||||
use clash_verge_logging::{Type, logging};
|
||||
use nanoid::nanoid;
|
||||
@@ -59,7 +59,7 @@ pub async fn read_seq_map(path: &PathBuf) -> Result<SeqMap> {
|
||||
/// save the data to the file
|
||||
/// can set `prefix` string to add some comments
|
||||
pub async fn save_yaml<T: Serialize + Sync>(path: &PathBuf, data: &T, prefix: Option<&str>) -> Result<()> {
|
||||
let data_str = with_encryption(|| async { yaml_emitter::to_mihomo_config_string(data) }).await?;
|
||||
let data_str = with_encryption(|| async { serde_yaml_ng::to_string(data) }).await?;
|
||||
|
||||
let yaml_str = match prefix {
|
||||
Some(prefix) => format!("{prefix}\n\n{data_str}"),
|
||||
|
||||
@@ -1,104 +1,113 @@
|
||||
use serde::{Serialize, ser};
|
||||
use serde::Serialize;
|
||||
use serde_yaml_ng::Result;
|
||||
|
||||
pub fn to_mihomo_config_string<T: Serialize>(data: &T) -> Result<String> {
|
||||
let value = serde_yaml_ng::to_value(data)?;
|
||||
Ok(emit_value(&value, 0, &DEFAULT_OPTIONS)? + "\n")
|
||||
let yaml = serde_yaml_ng::to_string(data)?;
|
||||
Ok(quote_fake_ip_filter_wildcards(&yaml))
|
||||
}
|
||||
|
||||
#[derive(Clone)]
|
||||
struct Options {
|
||||
checkout_wildcard: bool,
|
||||
fn quote_fake_ip_filter_wildcards(yaml: &str) -> String {
|
||||
let mut result = String::with_capacity(yaml.len());
|
||||
let mut in_fake_ip_filter = false;
|
||||
let mut fake_ip_filter_indent = 0;
|
||||
|
||||
for line in yaml.lines() {
|
||||
let indent = leading_spaces(line);
|
||||
let trimmed = &line[indent..];
|
||||
|
||||
if in_fake_ip_filter && indent <= fake_ip_filter_indent && !trimmed.starts_with("- ") {
|
||||
in_fake_ip_filter = false;
|
||||
}
|
||||
|
||||
if !in_fake_ip_filter && trimmed == "fake-ip-filter:" {
|
||||
in_fake_ip_filter = true;
|
||||
fake_ip_filter_indent = indent;
|
||||
result.push_str(line);
|
||||
result.push('\n');
|
||||
continue;
|
||||
}
|
||||
|
||||
if in_fake_ip_filter && indent >= fake_ip_filter_indent && trimmed.starts_with("- ") {
|
||||
result.push_str(&line[..indent]);
|
||||
result.push_str("- ");
|
||||
result.push_str("e_wildcard_item(&trimmed[2..]));
|
||||
result.push('\n');
|
||||
continue;
|
||||
}
|
||||
|
||||
result.push_str(line);
|
||||
result.push('\n');
|
||||
}
|
||||
|
||||
result
|
||||
}
|
||||
|
||||
const DEFAULT_OPTIONS: Options = Options {
|
||||
checkout_wildcard: false,
|
||||
};
|
||||
fn leading_spaces(line: &str) -> usize {
|
||||
line.len() - line.trim_start_matches(' ').len()
|
||||
}
|
||||
|
||||
fn serde_yaml_ng_to_string_without_last<T: ?Sized + ser::Serialize>(value: &T) -> Result<String> {
|
||||
let value = serde_yaml_ng::to_string(value)?;
|
||||
if value.ends_with('\n') {
|
||||
Ok(value[..value.len() - 1].to_string())
|
||||
fn quote_wildcard_item(item: &str) -> String {
|
||||
if !item.starts_with('\"') && !item.starts_with('\'') && contains_wildcard(item) {
|
||||
quote_string(item)
|
||||
} else {
|
||||
Ok(value)
|
||||
item.to_string()
|
||||
}
|
||||
}
|
||||
|
||||
fn emit_value(value: &serde_yaml_ng::Value, depth: usize, options: &Options) -> Result<String> {
|
||||
match value {
|
||||
serde_yaml_ng::Value::Null => serde_yaml_ng_to_string_without_last(&value),
|
||||
serde_yaml_ng::Value::Bool(b) => serde_yaml_ng_to_string_without_last(&b),
|
||||
serde_yaml_ng::Value::Number(number) => serde_yaml_ng_to_string_without_last(&number),
|
||||
serde_yaml_ng::Value::String(s) => emit_string(s, options),
|
||||
serde_yaml_ng::Value::Sequence(values) => {
|
||||
if values.is_empty() {
|
||||
return Ok("[]".to_string());
|
||||
}
|
||||
let mut result = String::new();
|
||||
let mut first = true;
|
||||
for value in values {
|
||||
if !first || depth > 0 {
|
||||
result.push('\n');
|
||||
}
|
||||
first = false;
|
||||
result.push_str(&" ".repeat(depth));
|
||||
result.push_str("- ");
|
||||
result.push_str(&emit_value(value, depth + 1, options)?);
|
||||
}
|
||||
Ok(result)
|
||||
}
|
||||
serde_yaml_ng::Value::Mapping(mapping) => {
|
||||
if mapping.is_empty() {
|
||||
return Ok("{}".to_string());
|
||||
}
|
||||
let mut result = String::new();
|
||||
let mut first = true;
|
||||
for (key, value) in mapping {
|
||||
if !first || depth > 0 {
|
||||
result.push('\n');
|
||||
}
|
||||
first = false;
|
||||
result.push_str(&" ".repeat(depth));
|
||||
let new_key = emit_value(key, depth + 1, options)?;
|
||||
result.push_str(&new_key);
|
||||
result.push_str(": ");
|
||||
let mut value_emiter_options = options.clone();
|
||||
if need_checkout_wildcard(key) {
|
||||
value_emiter_options.checkout_wildcard = true;
|
||||
}
|
||||
result.push_str(&emit_value(value, depth + 1, &value_emiter_options)?);
|
||||
}
|
||||
Ok(result)
|
||||
}
|
||||
serde_yaml_ng::Value::Tagged(tagged_value) => {
|
||||
let mut result = String::new();
|
||||
result.push_str(&format!("!{} ", tagged_value.tag));
|
||||
result.push_str(&emit_value(&tagged_value.value, depth, options)?);
|
||||
Ok(result)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn emit_string(s: &str, options: &Options) -> Result<String> {
|
||||
let mut s = serde_yaml_ng_to_string_without_last(s)?;
|
||||
if !s.starts_with('\"') && !s.starts_with('\'') && options.checkout_wildcard && contains_wildcard(&s) {
|
||||
s = quote_string(&s)
|
||||
}
|
||||
Ok(s)
|
||||
}
|
||||
|
||||
fn quote_string(s: &str) -> String {
|
||||
"\'".to_string() + s.replace('\'', "''").as_str() + "\'"
|
||||
}
|
||||
|
||||
fn need_checkout_wildcard(key: &serde_yaml_ng::Value) -> bool {
|
||||
if let serde_yaml_ng::Value::String(key_str) = key {
|
||||
key_str == "fake-ip-filter"
|
||||
} else {
|
||||
false
|
||||
}
|
||||
}
|
||||
|
||||
fn contains_wildcard(value: &str) -> bool {
|
||||
value.contains('*') || value.starts_with('+') || value.starts_with('.')
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::to_mihomo_config_string;
|
||||
|
||||
fn roundtrip(input: &str) {
|
||||
let value: serde_yaml_ng::Value = serde_yaml_ng::from_str(input).expect("input yaml should parse");
|
||||
let output = to_mihomo_config_string(&value).expect("yaml should serialize");
|
||||
let reparsed: serde_yaml_ng::Value = serde_yaml_ng::from_str(&output).expect("serialized yaml should parse");
|
||||
|
||||
assert_eq!(reparsed, value);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn quotes_fake_ip_filter_wildcards_without_changing_values() {
|
||||
let input = r#"
|
||||
dns:
|
||||
fake-ip-filter:
|
||||
- "*.lan"
|
||||
- "+.market.xiaomi.com"
|
||||
- ".example.com"
|
||||
- "time.*.com"
|
||||
- "plain.example.com"
|
||||
"#;
|
||||
|
||||
roundtrip(input);
|
||||
|
||||
let value: serde_yaml_ng::Value = serde_yaml_ng::from_str(input).expect("input yaml should parse");
|
||||
let output = to_mihomo_config_string(&value).expect("yaml should serialize");
|
||||
|
||||
assert!(output.contains("- '*.lan'"));
|
||||
assert!(output.contains("- '+.market.xiaomi.com'"));
|
||||
assert!(output.contains("- '.example.com'"));
|
||||
assert!(output.contains("- 'time.*.com'"));
|
||||
assert!(output.contains("- plain.example.com"));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn nested_multiline_strings_roundtrip() {
|
||||
roundtrip(
|
||||
r#"
|
||||
items:
|
||||
- name: demo
|
||||
desc: |
|
||||
line1
|
||||
line2
|
||||
"#,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user