From 700011688b83153773b0cfc7cd4a68ab9be93436 Mon Sep 17 00:00:00 2001 From: wonfen Date: Sun, 22 Feb 2026 07:37:40 +0800 Subject: [PATCH] feat: mask all URLs embedded in an error/log string for safe logging --- src-tauri/src/feat/profile.rs | 11 +++++++---- src-tauri/src/utils/help.rs | 35 +++++++++++++++++++++++++++++++++++ 2 files changed, 42 insertions(+), 4 deletions(-) diff --git a/src-tauri/src/feat/profile.rs b/src-tauri/src/feat/profile.rs index 8be7b6d65..5edc97148 100644 --- a/src-tauri/src/feat/profile.rs +++ b/src-tauri/src/feat/profile.rs @@ -2,7 +2,7 @@ use crate::{ cmd, config::{Config, PrfItem, PrfOption, profiles::profiles_draft_update_item_safe}, core::{CoreManager, handle, tray}, - utils::help::mask_url, + utils::help::{mask_err, mask_url}, }; use anyhow::{Result, bail}; use clash_verge_logging::{Type, logging, logging_error}; @@ -129,7 +129,8 @@ async fn perform_profile_update( logging!( warn, Type::Config, - "Warning: [订阅更新] 正常更新失败: {err},尝试使用Clash代理更新" + "Warning: [订阅更新] 正常更新失败: {},尝试使用Clash代理更新", + mask_err(&err.to_string()) ); last_err = err; } @@ -150,7 +151,8 @@ async fn perform_profile_update( logging!( warn, Type::Config, - "Warning: [订阅更新] 正常更新失败: {err},尝试使用Clash代理更新" + "Warning: [订阅更新] Clash代理更新失败: {},尝试使用系统代理更新", + mask_err(&err.to_string()) ); last_err = err; } @@ -171,7 +173,8 @@ async fn perform_profile_update( logging!( warn, Type::Config, - "Warning: [订阅更新] 正常更新失败: {err},尝试使用系统代理更新" + "Warning: [订阅更新] 系统代理更新失败: {},所有重试均已失败", + mask_err(&err.to_string()) ); last_err = err; } diff --git a/src-tauri/src/utils/help.rs b/src-tauri/src/utils/help.rs index 4c71778a6..38517e8bb 100644 --- a/src-tauri/src/utils/help.rs +++ b/src-tauri/src/utils/help.rs @@ -149,6 +149,41 @@ pub fn mask_url(url: &str) -> String { result } +/// Mask all URLs embedded in an error/log string for safe logging. +/// +/// Scans the string for `http://` or `https://` and replaces each URL +/// (terminated by whitespace or `)`, `]`, `"`, `'`) with its masked form. +/// Text between URLs is copied verbatim. +pub fn mask_err(err: &str) -> String { + let mut result = String::with_capacity(err.len()); + let mut remaining = err; + + loop { + let http = remaining.find("http://"); + let https = remaining.find("https://"); + let start = match (http, https) { + (None, None) => { + result.push_str(remaining); + break; + } + (Some(a), None) | (None, Some(a)) => a, + (Some(a), Some(b)) => a.min(b), + }; + + result.push_str(&remaining[..start]); + remaining = &remaining[start..]; + + let url_end = remaining + .find(|c: char| c.is_whitespace() || matches!(c, ')' | ']' | '"' | '\'')) + .unwrap_or(remaining.len()); + + result.push_str(&mask_url(&remaining[..url_end])); + remaining = &remaining[url_end..]; + } + + result +} + /// get the last part of the url, if not found, return empty string pub fn get_last_part_and_decode(url: &str) -> Option { let path = url.split('?').next().unwrap_or(""); // Splits URL and takes the path part