diff --git a/src-tauri/src/cmds.rs b/src-tauri/src/cmds.rs index fb05e439df..e24579610d 100644 --- a/src-tauri/src/cmds.rs +++ b/src-tauri/src/cmds.rs @@ -119,8 +119,8 @@ pub fn save_profile_file(index: String, file_data: Option) -> CmdResult } #[tauri::command] -pub fn get_clash_info() -> CmdResult { - wrap_err!(Config::clash().latest().get_info()) +pub fn get_clash_info() -> CmdResult { + Ok(Config::clash().latest().get_client_info()) } #[tauri::command] diff --git a/src-tauri/src/config/clash.rs b/src-tauri/src/config/clash.rs index 549ccfcbbf..142f096441 100644 --- a/src-tauri/src/config/clash.rs +++ b/src-tauri/src/config/clash.rs @@ -2,6 +2,10 @@ 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); @@ -9,7 +13,7 @@ pub struct IClashTemp(pub Mapping); impl IClashTemp { pub fn new() -> Self { match dirs::clash_path().and_then(|path| help::read_merge_mapping(&path)) { - Ok(map) => Self(map), + Ok(map) => Self(Self::guard(map)), Err(err) => { log::error!(target: "app", "{err}"); Self::template() @@ -27,7 +31,16 @@ impl IClashTemp { map.insert("external-controller".into(), "127.0.0.1:9090".into()); map.insert("secret".into(), "".into()); - Self(map) + Self(Self::guard(map)) + } + + fn guard(mut config: Mapping) -> Mapping { + let port = Self::guard_mixed_port(&config); + let ctrl = Self::guard_server_ctrl(&config); + + config.insert("mixed-port".into(), port.into()); + config.insert("external-controller".into(), ctrl.into()); + config } pub fn patch_config(&mut self, patch: Mapping) { @@ -44,109 +57,150 @@ impl IClashTemp { ) } - pub fn get_info(&self) -> Result { - Ok(ClashInfoN::from(&self.0)) - } -} - -#[derive(Default, Debug, Clone, Deserialize, Serialize)] -pub struct ClashInfoN { - /// clash sidecar status - pub status: String, - /// clash core port - pub port: Option, - /// same as `external-controller` - pub server: Option, - /// clash secret - pub secret: Option, -} + // pub fn get_info(&self) -> ClashInfo { + // self.1.clone() + // } -impl ClashInfoN { - /// parse the clash's config.yaml - /// get some information - pub fn from(config: &Mapping) -> ClashInfoN { - let key_port_1 = Value::from("mixed-port"); - let key_port_2 = Value::from("port"); - let key_server = Value::from("external-controller"); - let key_secret = Value::from("secret"); + pub fn get_mixed_port(&self) -> u16 { + Self::guard_mixed_port(&self.0) + } - let mut status: u32 = 0; + pub fn get_client_info(&self) -> ClashInfo { + let config = &self.0; - let port = match config.get(&key_port_1) { - Some(value) => match value { + ClashInfo { + port: Self::guard_mixed_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()), - _ => { - status |= 0b1; - None - } - }, - _ => { - status |= 0b10; - None - } - }; - let port = match port { - Some(_) => port, - None => match config.get(&key_port_2) { - Some(value) => match value { - Value::String(val_str) => Some(val_str.clone()), - Value::Number(val_num) => Some(val_num.to_string()), - _ => { - status |= 0b100; - None - } - }, - _ => { - status |= 0b1000; - None - } - }, - }; + _ => None, + }), + } + } + + pub fn guard_mixed_port(config: &Mapping) -> u16 { + let mut port = config + .get("mixed-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(7890); + if port == 0 { + port = 7890; + } + port + } - // `external-controller` could be - // "127.0.0.1:9090" or ":9090" - let server = match config.get(&key_server) { - Some(value) => match value.as_str() { + pub fn guard_server_ctrl(config: &Mapping) -> String { + config + .get("external-controller") + .and_then(|value| match value.as_str() { Some(val_str) => { - if val_str.starts_with(":") { - Some(format!("127.0.0.1{val_str}")) - } else if val_str.starts_with("0.0.0.0:") { - Some(format!("127.0.0.1:{}", &val_str[8..])) - } else if val_str.starts_with("[::]:") { - Some(format!("127.0.0.1:{}", &val_str[5..])) - } else { - Some(val_str.into()) - } + 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 => { - status |= 0b10000; - None + None => None, + }) + .unwrap_or("127.0.0.1:9090".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))); } - }, - None => { - status |= 0b100000; - None + socket.to_string() } - }; + Err(_) => "127.0.0.1:9090".into(), + } + } +} - let secret = match config.get(&key_secret) { - Some(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, - }, - _ => None, - }; +#[derive(Default, Debug, Clone, Deserialize, Serialize, PartialEq, Eq)] +pub struct ClashInfo { + /// clash core port + pub port: u16, + /// same as `external-controller` + pub server: String, + /// clash secret + pub secret: Option, +} - ClashInfoN { - status: format!("{status}"), +#[test] +fn test_clash_info() { + fn get_case, D: Into>(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>(port: u16, server: S) -> ClashInfo { + ClashInfo { port, - server, - secret, + server: server.into(), + secret: None, } } + + assert_eq!( + IClashTemp(IClashTemp::guard(Mapping::new())).get_client_info(), + get_result(7890, "127.0.0.1:9090") + ); + + assert_eq!(get_case("", ""), get_result(7890, "127.0.0.1:9090")); + + assert_eq!(get_case(65537, ""), get_result(1, "127.0.0.1:9090")); + + 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:9090") + ); + + 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:9090") + ); } #[derive(Default, Debug, Clone, Deserialize, Serialize, PartialEq, Eq)] diff --git a/src-tauri/src/config/prfitem.rs b/src-tauri/src/config/prfitem.rs index 8bf8cce51a..0bfeb37d14 100644 --- a/src-tauri/src/config/prfitem.rs +++ b/src-tauri/src/config/prfitem.rs @@ -194,8 +194,7 @@ impl PrfItem { // 使用软件自己的代理 if self_proxy { - let port = Config::clash().data().get_info()?.port; - let port = port.ok_or(anyhow::anyhow!("failed to get clash info port"))?; + let port = Config::clash().data().get_mixed_port(); let proxy_scheme = format!("http://127.0.0.1:{port}"); diff --git a/src-tauri/src/core/clash_api.rs b/src-tauri/src/core/clash_api.rs index 2261d1d7a4..eab08b4aaa 100644 --- a/src-tauri/src/core/clash_api.rs +++ b/src-tauri/src/core/clash_api.rs @@ -38,25 +38,15 @@ pub async fn patch_configs(config: &Mapping) -> Result<()> { /// 根据clash info获取clash服务地址和请求头 fn clash_client_info() -> Result<(String, HeaderMap)> { - let info = { Config::clash().data().get_info()? }; - - if info.server.is_none() { - let status = &info.status; - if info.port.is_none() { - bail!("failed to parse config.yaml file with status {status}"); - } else { - bail!("failed to parse the server with status {status}"); - } - } + let client = { Config::clash().data().get_client_info() }; - let server = info.server.unwrap(); - let server = format!("http://{server}"); + let server = format!("http://{}", client.server); let mut headers = HeaderMap::new(); headers.insert("Content-Type", "application/json".parse()?); - if let Some(secret) = info.secret.as_ref() { - let secret = format!("Bearer {}", secret.clone()).parse()?; + if let Some(secret) = client.secret { + let secret = format!("Bearer {}", secret).parse()?; headers.insert("Authorization", secret); } diff --git a/src-tauri/src/core/sysopt.rs b/src-tauri/src/core/sysopt.rs index 152ce581e0..5eed1e57be 100644 --- a/src-tauri/src/core/sysopt.rs +++ b/src-tauri/src/core/sysopt.rs @@ -1,5 +1,5 @@ use crate::{config::Config, log_err}; -use anyhow::{anyhow, bail, Result}; +use anyhow::{anyhow, Result}; use auto_launch::{AutoLaunch, AutoLaunchBuilder}; use once_cell::sync::OnceCell; use parking_lot::Mutex; @@ -43,13 +43,7 @@ impl Sysopt { /// init the sysproxy pub fn init_sysproxy(&self) -> Result<()> { - let port = { Config::clash().latest().get_info()?.port }; - - if port.is_none() { - bail!("clash port is none"); - } - - let port = port.unwrap().parse::()?; + let port = { Config::clash().latest().get_mixed_port() }; let (enable, bypass) = { let verge = Config::verge(); @@ -263,23 +257,16 @@ impl Sysopt { log::debug!(target: "app", "try to guard the system proxy"); - if let Ok(info) = { Config::clash().latest().get_info() } { - match info.port.unwrap_or("".into()).parse::() { - Ok(port) => { - let sysproxy = Sysproxy { - enable: true, - host: "127.0.0.1".into(), - port, - bypass: bypass.unwrap_or(DEFAULT_BYPASS.into()), - }; - - log_err!(sysproxy.set_system_proxy()); - } - Err(_) => { - log::error!(target: "app", "failed to parse clash port in guard proxy") - } - } - } + let port = { Config::clash().latest().get_mixed_port() }; + + let sysproxy = Sysproxy { + enable: true, + host: "127.0.0.1".into(), + port, + bypass: bypass.unwrap_or(DEFAULT_BYPASS.into()), + }; + + log_err!(sysproxy.set_system_proxy()); } let mut state = guard_state.lock().await; diff --git a/src-tauri/src/feat.rs b/src-tauri/src/feat.rs index 00d2ae2425..4fc7be9a1e 100644 --- a/src-tauri/src/feat.rs +++ b/src-tauri/src/feat.rs @@ -156,7 +156,7 @@ pub async fn patch_clash(patch: Mapping) -> Result<()> { if let Some(port) = mixed_port.clone().unwrap().as_u64() { if !port_scanner::local_port_available(port as u16) { Config::clash().discard(); - bail!("the port not available"); + bail!("port already in use"); } } }