mirror of
https://github.com/clash-verge-rev/clash-verge-rev.git
synced 2026-04-18 16:30:32 +08:00
feat: mask sensitive parts of a subscription URL for safe logging
This commit is contained in:
parent
119aaee546
commit
fa07dfbc9a
@ -65,7 +65,7 @@ pub async fn enhance_profiles() -> CmdResult {
|
|||||||
/// 导入配置文件
|
/// 导入配置文件
|
||||||
#[tauri::command]
|
#[tauri::command]
|
||||||
pub async fn import_profile(url: std::string::String, option: Option<PrfOption>) -> CmdResult {
|
pub async fn import_profile(url: std::string::String, option: Option<PrfOption>) -> CmdResult {
|
||||||
logging!(info, Type::Cmd, "[导入订阅] 开始导入: {}", url);
|
logging!(info, Type::Cmd, "[导入订阅] 开始导入: {}", help::mask_url(&url));
|
||||||
|
|
||||||
// 直接依赖 PrfItem::from_url 自身的超时/重试逻辑,不再使用 tokio::time::timeout 包裹
|
// 直接依赖 PrfItem::from_url 自身的超时/重试逻辑,不再使用 tokio::time::timeout 包裹
|
||||||
let item = &mut match PrfItem::from_url(&url, None, None, option.as_ref()).await {
|
let item = &mut match PrfItem::from_url(&url, None, None, option.as_ref()).await {
|
||||||
@ -107,7 +107,7 @@ pub async fn import_profile(url: std::string::String, option: Option<PrfOption>)
|
|||||||
handle::Handle::notify_profile_changed(uid);
|
handle::Handle::notify_profile_changed(uid);
|
||||||
}
|
}
|
||||||
|
|
||||||
logging!(info, Type::Cmd, "[导入订阅] 导入完成: {}", url);
|
logging!(info, Type::Cmd, "[导入订阅] 导入完成: {}", help::mask_url(&url));
|
||||||
AutoBackupManager::trigger_backup(AutoBackupTrigger::ProfileChange);
|
AutoBackupManager::trigger_backup(AutoBackupTrigger::ProfileChange);
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|||||||
@ -2,6 +2,7 @@ use crate::{
|
|||||||
cmd,
|
cmd,
|
||||||
config::{Config, PrfItem, PrfOption, profiles::profiles_draft_update_item_safe},
|
config::{Config, PrfItem, PrfOption, profiles::profiles_draft_update_item_safe},
|
||||||
core::{CoreManager, handle, tray},
|
core::{CoreManager, handle, tray},
|
||||||
|
utils::help::mask_url,
|
||||||
};
|
};
|
||||||
use anyhow::{Result, bail};
|
use anyhow::{Result, bail};
|
||||||
use clash_verge_logging::{Type, logging, logging_error};
|
use clash_verge_logging::{Type, logging, logging_error};
|
||||||
@ -83,9 +84,11 @@ async fn should_update_profile(uid: &String, ignore_auto_update: bool) -> Result
|
|||||||
Type::Config,
|
Type::Config,
|
||||||
"[订阅更新] {} 是远程订阅,URL: {}",
|
"[订阅更新] {} 是远程订阅,URL: {}",
|
||||||
uid,
|
uid,
|
||||||
item.url
|
mask_url(
|
||||||
.as_ref()
|
item.url
|
||||||
.ok_or_else(|| anyhow::anyhow!("Profile URL is None"))?
|
.as_ref()
|
||||||
|
.ok_or_else(|| anyhow::anyhow!("Profile URL is None"))?
|
||||||
|
)
|
||||||
);
|
);
|
||||||
Ok(Some((
|
Ok(Some((
|
||||||
item.url.clone().ok_or_else(|| anyhow::anyhow!("Profile URL is None"))?,
|
item.url.clone().ok_or_else(|| anyhow::anyhow!("Profile URL is None"))?,
|
||||||
|
|||||||
@ -98,6 +98,57 @@ pub fn parse_str<T: FromStr>(target: &str, key: &str) -> Option<T> {
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Mask sensitive parts of a subscription URL for safe logging.
|
||||||
|
/// Examples:
|
||||||
|
/// - `https://example.com/api/v1/clash?token=abc123` → `https://example.com/api/v1/clash?token=***`
|
||||||
|
/// - `https://example.com/abc123def456ghi789/clash` → `https://example.com/***/clash`
|
||||||
|
pub fn mask_url(url: &str) -> String {
|
||||||
|
// Split off query string
|
||||||
|
let (path_part, query_part) = match url.find('?') {
|
||||||
|
Some(pos) => (&url[..pos], Some(&url[pos + 1..])),
|
||||||
|
None => (url, None),
|
||||||
|
};
|
||||||
|
|
||||||
|
// Extract scheme+host prefix (everything up to the first '/' after "://")
|
||||||
|
let host_end = path_part
|
||||||
|
.find("://")
|
||||||
|
.and_then(|scheme_end| {
|
||||||
|
path_part[scheme_end + 3..]
|
||||||
|
.find('/')
|
||||||
|
.map(|slash| scheme_end + 3 + slash)
|
||||||
|
})
|
||||||
|
.unwrap_or(path_part.len());
|
||||||
|
|
||||||
|
let scheme_and_host = &path_part[..host_end];
|
||||||
|
let path = &path_part[host_end..]; // starts with '/' or empty
|
||||||
|
|
||||||
|
let mut result = scheme_and_host.to_owned();
|
||||||
|
|
||||||
|
// Mask path segments that look like tokens (longer than 16 chars)
|
||||||
|
if !path.is_empty() {
|
||||||
|
let masked: Vec<&str> = path
|
||||||
|
.split('/')
|
||||||
|
.map(|seg| if seg.len() > 16 { "***" } else { seg })
|
||||||
|
.collect();
|
||||||
|
result.push_str(&masked.join("/"));
|
||||||
|
}
|
||||||
|
|
||||||
|
// Keep query param keys, mask values
|
||||||
|
if let Some(query) = query_part {
|
||||||
|
result.push('?');
|
||||||
|
let masked_query: Vec<String> = query
|
||||||
|
.split('&')
|
||||||
|
.map(|param| match param.find('=') {
|
||||||
|
Some(eq) => format!("{}=***", ¶m[..eq]),
|
||||||
|
None => param.to_owned(),
|
||||||
|
})
|
||||||
|
.collect();
|
||||||
|
result.push_str(&masked_query.join("&"));
|
||||||
|
}
|
||||||
|
|
||||||
|
result
|
||||||
|
}
|
||||||
|
|
||||||
/// get the last part of the url, if not found, return empty string
|
/// get the last part of the url, if not found, return empty string
|
||||||
pub fn get_last_part_and_decode(url: &str) -> Option<String> {
|
pub fn get_last_part_and_decode(url: &str) -> Option<String> {
|
||||||
let path = url.split('?').next().unwrap_or(""); // Splits URL and takes the path part
|
let path = url.split('?').next().unwrap_or(""); // Splits URL and takes the path part
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user