Files
clash-proxy/src-tauri/src/config/clash.rs
Tunglies 537d27d10b fix: clippy errors with new config (#4428)
* refactor: improve code quality with clippy fixes and standardized logging

- Replace dangerous unwrap()/expect() calls with proper error handling
- Standardize logging from log:: to logging\! macro with Type:: classifications
- Fix app handle panics with graceful fallback patterns
- Improve error resilience across 35+ modules without breaking functionality
- Reduce clippy warnings from 300+ to 0 in main library code

* chore: update Cargo.toml configuration

* refactor: resolve all clippy warnings
- Fix Arc clone warnings using explicit Arc::clone syntax across 9 files
- Add #[allow(clippy::expect_used)] to test functions for appropriate expect usage
- Remove no-effect statements from debug code cleanup
- Apply clippy auto-fixes for dbg\! macro removals and path statements
- Achieve zero clippy warnings on all targets with -D warnings flag

* chore: update Cargo.toml clippy configuration

* refactor: simplify macOS job configuration and improve caching

* refactor: remove unnecessary async/await from service and proxy functions

* refactor: streamline pnpm installation in CI configuration

* refactor: simplify error handling and remove unnecessary else statements

* refactor: replace async/await with synchronous locks for core management

* refactor: add workflow_dispatch trigger to clippy job

* refactor: convert async functions to synchronous for service management

* refactor: convert async functions to synchronous for UWP tool invocation

* fix: change wrong logging

* refactor: convert proxy restoration functions to async

* Revert "refactor: convert proxy restoration functions to async"

This reverts commit b82f5d250b.

* refactor: update proxy restoration functions to return Result types

* fix: handle errors during proxy restoration and update async function signatures

* fix: handle errors during proxy restoration and update async function signatures

* refactor: update restore_pac_proxy and restore_sys_proxy functions to async

* fix: convert restore_pac_proxy and restore_sys_proxy functions to async

* fix: await restore_sys_proxy calls in proxy restoration logic

* fix: suppress clippy warnings for unused async functions in proxy restoration

* fix: suppress clippy warnings for unused async functions in proxy restoration
2025-08-18 02:02:25 +08:00

456 lines
14 KiB
Rust

use crate::config::Config;
use crate::utils::dirs::{ipc_path, path_to_str};
use crate::utils::{dirs, help};
use anyhow::Result;
use serde::{Deserialize, Serialize};
use serde_yaml::{Mapping, Value};
use std::{
net::{IpAddr, Ipv4Addr, SocketAddr},
str::FromStr,
};
#[derive(Default, Debug, Clone)]
pub struct IClashTemp(pub Mapping);
impl IClashTemp {
pub fn new() -> Self {
let template = Self::template();
match dirs::clash_path().and_then(|path| help::read_mapping(&path)) {
Ok(mut map) => {
template.0.keys().for_each(|key| {
if !map.contains_key(key) {
if let Some(value) = template.0.get(key) {
map.insert(key.clone(), value.clone());
}
}
});
// 确保 secret 字段存在且不为空
if let Some(Value::String(s)) = map.get_mut("secret") {
if s.is_empty() {
*s = "set-your-secret".to_string();
}
}
Self(Self::guard(map))
}
Err(err) => {
log::error!(target: "app", "{err}");
template
}
}
}
pub fn template() -> Self {
let mut map = Mapping::new();
let mut tun = Mapping::new();
let mut cors_map = Mapping::new();
tun.insert("enable".into(), false.into());
tun.insert("stack".into(), "gvisor".into());
tun.insert("auto-route".into(), true.into());
tun.insert("strict-route".into(), false.into());
tun.insert("auto-detect-interface".into(), true.into());
tun.insert("dns-hijack".into(), vec!["any:53"].into());
#[cfg(not(target_os = "windows"))]
map.insert("redir-port".into(), 7895.into());
#[cfg(target_os = "linux")]
map.insert("tproxy-port".into(), 7896.into());
map.insert("mixed-port".into(), 7897.into());
map.insert("socks-port".into(), 7898.into());
map.insert("port".into(), 7899.into());
map.insert("log-level".into(), "warning".into());
map.insert("allow-lan".into(), false.into());
map.insert("ipv6".into(), true.into());
map.insert("mode".into(), "rule".into());
map.insert("external-controller".into(), "127.0.0.1:9097".into());
#[cfg(unix)]
map.insert(
"external-controller-unix".into(),
Self::guard_external_controller_ipc().into(),
);
#[cfg(windows)]
map.insert(
"external-controller-pipe".into(),
Self::guard_external_controller_ipc().into(),
);
cors_map.insert("allow-private-network".into(), true.into());
cors_map.insert(
"allow-origins".into(),
vec![
"tauri://localhost",
"http://tauri.localhost",
// Only enable this in dev mode
#[cfg(feature = "verge-dev")]
"http://localhost:3000",
"https://yacd.metacubex.one",
"https://metacubex.github.io",
"https://board.zash.run.place",
]
.into(),
);
map.insert("secret".into(), "set-your-secret".into());
map.insert("tun".into(), tun.into());
map.insert("external-controller-cors".into(), cors_map.into());
map.insert("unified-delay".into(), true.into());
Self(map)
}
fn guard(mut config: Mapping) -> Mapping {
#[cfg(not(target_os = "windows"))]
let redir_port = Self::guard_redir_port(&config);
#[cfg(target_os = "linux")]
let tproxy_port = Self::guard_tproxy_port(&config);
let mixed_port = Self::guard_mixed_port(&config);
let socks_port = Self::guard_socks_port(&config);
let port = Self::guard_port(&config);
let ctrl = Self::guard_external_controller(&config);
#[cfg(unix)]
let external_controller_unix = Self::guard_external_controller_ipc();
#[cfg(windows)]
let external_controller_pipe = Self::guard_external_controller_ipc();
#[cfg(not(target_os = "windows"))]
config.insert("redir-port".into(), redir_port.into());
#[cfg(target_os = "linux")]
config.insert("tproxy-port".into(), tproxy_port.into());
config.insert("mixed-port".into(), mixed_port.into());
config.insert("socks-port".into(), socks_port.into());
config.insert("port".into(), port.into());
config.insert("external-controller".into(), ctrl.into());
#[cfg(unix)]
config.insert(
"external-controller-unix".into(),
external_controller_unix.into(),
);
#[cfg(windows)]
config.insert(
"external-controller-pipe".into(),
external_controller_pipe.into(),
);
config
}
pub fn patch_config(&mut self, patch: Mapping) {
for (key, value) in patch.into_iter() {
self.0.insert(key, value);
}
}
pub fn save_config(&self) -> Result<()> {
help::save_yaml(
&dirs::clash_path()?,
&self.0,
Some("# Generated by Clash Verge"),
)
}
pub fn get_mixed_port(&self) -> u16 {
Self::guard_mixed_port(&self.0)
}
#[allow(unused)]
pub fn get_socks_port(&self) -> u16 {
Self::guard_socks_port(&self.0)
}
#[allow(unused)]
pub fn get_port(&self) -> u16 {
Self::guard_port(&self.0)
}
pub fn get_client_info(&self) -> ClashInfo {
let config = &self.0;
ClashInfo {
mixed_port: Self::guard_mixed_port(config),
socks_port: Self::guard_socks_port(config),
port: Self::guard_port(config),
server: Self::guard_client_ctrl(config),
secret: config.get("secret").and_then(|value| match value {
Value::String(val_str) => Some(val_str.clone()),
Value::Bool(val_bool) => Some(val_bool.to_string()),
Value::Number(val_num) => Some(val_num.to_string()),
_ => None,
}),
}
}
#[cfg(not(target_os = "windows"))]
pub fn guard_redir_port(config: &Mapping) -> u16 {
let mut port = config
.get("redir-port")
.and_then(|value| match value {
Value::String(val_str) => val_str.parse().ok(),
Value::Number(val_num) => val_num.as_u64().map(|u| u as u16),
_ => None,
})
.unwrap_or(7895);
if port == 0 {
port = 7895;
}
port
}
#[cfg(target_os = "linux")]
pub fn guard_tproxy_port(config: &Mapping) -> u16 {
let mut port = config
.get("tproxy-port")
.and_then(|value| match value {
Value::String(val_str) => val_str.parse().ok(),
Value::Number(val_num) => val_num.as_u64().map(|u| u as u16),
_ => None,
})
.unwrap_or(7896);
if port == 0 {
port = 7896;
}
port
}
pub fn guard_mixed_port(config: &Mapping) -> u16 {
let raw_value = config.get("mixed-port");
let mut port = raw_value
.and_then(|value| match value {
Value::String(val_str) => val_str.parse().ok(),
Value::Number(val_num) => val_num.as_u64().map(|u| u as u16),
_ => None,
})
.unwrap_or(7897);
if port == 0 {
port = 7897;
}
port
}
pub fn guard_socks_port(config: &Mapping) -> u16 {
let mut port = config
.get("socks-port")
.and_then(|value| match value {
Value::String(val_str) => val_str.parse().ok(),
Value::Number(val_num) => val_num.as_u64().map(|u| u as u16),
_ => None,
})
.unwrap_or(7898);
if port == 0 {
port = 7898;
}
port
}
pub fn guard_port(config: &Mapping) -> u16 {
let mut port = config
.get("port")
.and_then(|value| match value {
Value::String(val_str) => val_str.parse().ok(),
Value::Number(val_num) => val_num.as_u64().map(|u| u as u16),
_ => None,
})
.unwrap_or(7899);
if port == 0 {
port = 7899;
}
port
}
pub fn guard_server_ctrl(config: &Mapping) -> String {
config
.get("external-controller")
.and_then(|value| match value.as_str() {
Some(val_str) => {
let val_str = val_str.trim();
let val = match val_str.starts_with(':') {
true => format!("127.0.0.1{val_str}"),
false => val_str.to_owned(),
};
SocketAddr::from_str(val.as_str())
.ok()
.map(|s| s.to_string())
}
None => None,
})
.unwrap_or("127.0.0.1:9097".into())
}
pub fn guard_external_controller(config: &Mapping) -> String {
// 在初始化阶段,直接返回配置中的值,不进行额外检查
// 这样可以避免在配置加载期间的循环依赖
Self::guard_server_ctrl(config)
}
pub fn guard_external_controller_with_setting(config: &Mapping) -> String {
// 检查 enable_external_controller 设置,用于运行时配置生成
let enable_external_controller = Config::verge()
.latest_ref()
.enable_external_controller
.unwrap_or(false);
if enable_external_controller {
Self::guard_server_ctrl(config)
} else {
"".into()
}
}
pub fn guard_client_ctrl(config: &Mapping) -> String {
let value = Self::guard_server_ctrl(config);
match SocketAddr::from_str(value.as_str()) {
Ok(mut socket) => {
if socket.ip().is_unspecified() {
socket.set_ip(IpAddr::V4(Ipv4Addr::new(127, 0, 0, 1)));
}
socket.to_string()
}
Err(_) => "127.0.0.1:9097".into(),
}
}
pub fn guard_external_controller_ipc() -> String {
// 总是使用当前的 IPC 路径,确保配置文件与运行时路径一致
ipc_path()
.ok()
.and_then(|path| path_to_str(&path).ok().map(|s| s.to_string()))
.unwrap_or_else(|| {
log::error!(target: "app", "Failed to get IPC path, using default");
"127.0.0.1:9090".to_string()
})
}
}
#[derive(Default, Debug, Clone, Deserialize, Serialize, PartialEq, Eq)]
pub struct ClashInfo {
/// clash core port
pub mixed_port: u16,
pub socks_port: u16,
pub port: u16,
/// same as `external-controller`
pub server: String,
/// clash secret
pub secret: Option<String>,
}
#[test]
fn test_clash_info() {
fn get_case<T: Into<Value>, D: Into<Value>>(mp: T, ec: D) -> ClashInfo {
let mut map = Mapping::new();
map.insert("mixed-port".into(), mp.into());
map.insert("external-controller".into(), ec.into());
IClashTemp(IClashTemp::guard(map)).get_client_info()
}
fn get_result<S: Into<String>>(port: u16, server: S) -> ClashInfo {
ClashInfo {
mixed_port: port,
socks_port: 7898,
port: 7899,
server: server.into(),
secret: None,
}
}
assert_eq!(
IClashTemp(IClashTemp::guard(Mapping::new())).get_client_info(),
get_result(7897, "127.0.0.1:9097")
);
assert_eq!(get_case("", ""), get_result(7897, "127.0.0.1:9097"));
assert_eq!(get_case(65537, ""), get_result(1, "127.0.0.1:9097"));
assert_eq!(
get_case(8888, "127.0.0.1:8888"),
get_result(8888, "127.0.0.1:8888")
);
assert_eq!(
get_case(8888, " :98888 "),
get_result(8888, "127.0.0.1:9097")
);
assert_eq!(
get_case(8888, "0.0.0.0:8080 "),
get_result(8888, "127.0.0.1:8080")
);
assert_eq!(
get_case(8888, "0.0.0.0:8080"),
get_result(8888, "127.0.0.1:8080")
);
assert_eq!(
get_case(8888, "[::]:8080"),
get_result(8888, "127.0.0.1:8080")
);
assert_eq!(
get_case(8888, "192.168.1.1:8080"),
get_result(8888, "192.168.1.1:8080")
);
assert_eq!(
get_case(8888, "192.168.1.1:80800"),
get_result(8888, "127.0.0.1:9097")
);
}
#[derive(Default, Debug, Clone, Deserialize, Serialize, PartialEq, Eq)]
#[serde(rename_all = "kebab-case")]
pub struct IClashExternalControllerCors {
pub allow_origins: Option<Vec<String>>,
pub allow_private_network: Option<bool>,
}
#[derive(Default, Debug, Clone, Deserialize, Serialize, PartialEq, Eq)]
#[serde(rename_all = "kebab-case")]
pub struct IClash {
pub mixed_port: Option<u16>,
pub allow_lan: Option<bool>,
pub log_level: Option<String>,
pub ipv6: Option<bool>,
pub mode: Option<String>,
pub external_controller: Option<String>,
pub secret: Option<String>,
pub dns: Option<IClashDNS>,
pub tun: Option<IClashTUN>,
pub interface_name: Option<String>,
pub external_controller_cors: Option<IClashExternalControllerCors>,
}
#[derive(Default, Debug, Clone, Deserialize, Serialize, PartialEq, Eq)]
#[serde(rename_all = "kebab-case")]
pub struct IClashTUN {
pub enable: Option<bool>,
pub stack: Option<String>,
pub auto_route: Option<bool>,
pub auto_detect_interface: Option<bool>,
pub dns_hijack: Option<Vec<String>>,
}
#[derive(Default, Debug, Clone, Deserialize, Serialize, PartialEq, Eq)]
#[serde(rename_all = "kebab-case")]
pub struct IClashDNS {
pub enable: Option<bool>,
pub listen: Option<String>,
pub default_nameserver: Option<Vec<String>>,
pub enhanced_mode: Option<String>,
pub fake_ip_range: Option<String>,
pub use_hosts: Option<bool>,
pub fake_ip_filter: Option<Vec<String>>,
pub nameserver: Option<Vec<String>>,
pub fallback: Option<Vec<String>>,
pub fallback_filter: Option<IClashFallbackFilter>,
pub nameserver_policy: Option<Vec<String>>,
}
#[derive(Default, Debug, Clone, Deserialize, Serialize, PartialEq, Eq)]
#[serde(rename_all = "kebab-case")]
pub struct IClashFallbackFilter {
pub geoip: Option<bool>,
pub geoip_code: Option<String>,
pub ipcidr: Option<Vec<String>>,
pub domain: Option<Vec<String>>,
}