use anyhow::Result; use serde::{Deserialize, Serialize}; #[cfg(target_os = "windows")] static DEFAULT_BYPASS: &str = "localhost;127.*;10.*;172.16.*;172.17.*;172.18.*;172.19.*;172.20.*;172.21.*;172.22.*;172.23.*;172.24.*;172.25.*;172.26.*;172.27.*;172.28.*;172.29.*;172.30.*;172.31.*;192.168.*;"; #[cfg(target_os = "linux")] static DEFAULT_BYPASS: &str = "localhost,127.0.0.1/8,::1"; #[cfg(target_os = "macos")] static DEFAULT_BYPASS: &str = "192.168.0.0/16\n10.0.0.0/8\n172.16.0.0/12\n127.0.0.1\nlocalhost\n*.local\ntimestamp.apple.com\n"; #[cfg(target_os = "macos")] static MACOS_SERVICE: &str = "Wi-Fi"; #[derive(Debug, Deserialize, Serialize, Clone)] pub struct SysProxyConfig { pub enable: bool, pub server: String, pub bypass: String, } impl Default for SysProxyConfig { fn default() -> Self { SysProxyConfig { enable: false, server: String::from(""), bypass: String::from(""), } } } impl SysProxyConfig { pub fn new(enable: bool, port: String, bypass: Option) -> Self { SysProxyConfig { enable, server: format!("127.0.0.1:{}", port), bypass: bypass.unwrap_or(DEFAULT_BYPASS.into()), } } } #[cfg(target_os = "windows")] impl SysProxyConfig { /// Get the windows system proxy config pub fn get_sys() -> Result { use winreg::enums::*; use winreg::RegKey; let hkcu = RegKey::predef(HKEY_CURRENT_USER); let cur_var = hkcu.open_subkey_with_flags( "SOFTWARE\\Microsoft\\Windows\\CurrentVersion\\Internet Settings", KEY_READ, )?; Ok(SysProxyConfig { enable: cur_var.get_value::("ProxyEnable")? == 1u32, server: cur_var.get_value("ProxyServer")?, bypass: cur_var.get_value("ProxyOverride")?, }) } /// Set the windows system proxy config pub fn set_sys(&self) -> Result<()> { use winreg::enums::*; use winreg::RegKey; let hkcu = RegKey::predef(HKEY_CURRENT_USER); let cur_var = hkcu.open_subkey_with_flags( "SOFTWARE\\Microsoft\\Windows\\CurrentVersion\\Internet Settings", KEY_SET_VALUE, )?; let enable: u32 = if self.enable { 1u32 } else { 0u32 }; cur_var.set_value("ProxyEnable", &enable)?; cur_var.set_value("ProxyServer", &self.server)?; cur_var.set_value("ProxyOverride", &self.bypass)?; Ok(()) } } #[cfg(target_os = "macos")] impl SysProxyConfig { /// Get the macos system proxy config pub fn get_sys() -> Result { use std::process::Command; let http = macproxy::get_proxy(&["-getwebproxy", MACOS_SERVICE])?; let https = macproxy::get_proxy(&["-getsecurewebproxy", MACOS_SERVICE])?; let sock = macproxy::get_proxy(&["-getsocksfirewallproxy", MACOS_SERVICE])?; let mut enable = false; let mut server = "".into(); if sock.0 == "Yes" { enable = true; server = sock.1; } if https.0 == "Yes" { enable = true; server = https.1; } if http.0 == "Yes" || !enable { enable = http.0 == "Yes"; server = http.1; } let bypass_output = Command::new("networksetup") .args(["-getproxybypassdomains", MACOS_SERVICE]) .output()?; // change the format to xxx,xxx let bypass = std::str::from_utf8(&bypass_output.stdout) .unwrap_or(DEFAULT_BYPASS) .to_string() .split('\n') .collect::>() .join(","); Ok(SysProxyConfig { enable, server, bypass, }) } /// Set the macos system proxy config pub fn set_sys(&self) -> Result<()> { use std::process::Command; let enable = self.enable; let server = self.server.as_str(); let bypass = self.bypass.clone(); macproxy::set_proxy("-setwebproxy", MACOS_SERVICE, enable, server)?; macproxy::set_proxy("-setsecurewebproxy", MACOS_SERVICE, enable, server)?; macproxy::set_proxy("-setsocksfirewallproxy", MACOS_SERVICE, enable, server)?; let domains = bypass.split(",").collect::>(); Command::new("networksetup") .args([["-setproxybypassdomains", MACOS_SERVICE].to_vec(), domains].concat()) .status()?; Ok(()) } } #[cfg(target_os = "macos")] mod macproxy { use super::*; use anyhow::bail; use std::process::Command; /// use networksetup /// get the target proxy config pub(super) fn get_proxy(args: &[&str; 2]) -> Result<(String, String)> { let output = Command::new("networksetup").args(args).output()?; let stdout = std::str::from_utf8(&output.stdout)?; let enable = parse(stdout, "Enabled:"); let server = parse(stdout, "Server:"); let port = parse(stdout, "Port:"); let server = format!("{server}:{port}"); Ok((enable.into(), server)) } /// use networksetup /// set the target proxy config pub(super) fn set_proxy( target: &str, // like: -setwebproxy device: &str, enable: bool, server: &str, ) -> Result<()> { let mut split = server.split(":"); let host = split.next(); let port = split.next(); // can not parse the field if host.is_none() || port.is_none() { bail!("failed to parse the server into host:port"); } let args = vec![target, device, host.unwrap(), port.unwrap()]; Command::new("networksetup").args(&args).status()?; let target_state = String::from(target) + "state"; let enable = if enable { "on" } else { "off" }; let args = vec![target_state.as_str(), device, enable]; Command::new("networksetup").args(&args).status()?; Ok(()) } /// parse the networksetup output fn parse<'a>(target: &'a str, key: &'a str) -> &'a str { match target.find(key) { Some(idx) => { let idx = idx + key.len(); let value = &target[idx..]; let value = match value.find("\n") { Some(end) => &value[..end], None => value, }; value.trim() } None => "", } } #[test] fn test_get() { use std::process::Command; let output = Command::new("networksetup") .args(["-getwebproxy", "Wi-Fi"]) .output(); let output = output.unwrap(); let stdout = std::str::from_utf8(&output.stdout).unwrap(); let enable = parse(stdout, "Enabled:"); let server = parse(stdout, "Server:"); let port = parse(stdout, "Port:"); println!("enable: {}, server: {}, port: {}", enable, server, port); dbg!(SysProxyConfig::get_sys().unwrap()); } #[test] fn test_set() { let sysproxy = SysProxyConfig::new(true, "7890".into(), None); dbg!(sysproxy.set_sys().unwrap()); } } /// /// Linux Desktop System Proxy Supports /// by using `gsettings` #[cfg(target_os = "linux")] impl SysProxyConfig { /// Get the system proxy config [http/https/socks] pub fn get_sys() -> Result { use std::process::Command; let schema = "org.gnome.system.proxy"; // get enable let mode = Command::new("gsettings") .args(["get", schema, "mode"]) .output()?; let mode = std::str::from_utf8(&mode.stdout)?; let enable = mode == "manual"; // get bypass // Todo: parse the ignore-hosts // ['aaa', 'bbb'] -> aaa,bbb let ignore = Command::new("gsettings") .args(["get", schema, "ignore-hosts"]) .output()?; let ignore = std::str::from_utf8(&ignore.stdout)?; let bypass = ignore.to_string(); let http = Self::get_proxy("http")?; let https = Self::get_proxy("https")?; let socks = Self::get_proxy("socks")?; let mut server = "".into(); if socks.len() > 0 { server = socks; } if https.len() > 0 { server = https; } if http.len() > 0 { server = http; } Ok(SysProxyConfig { enable, server, bypass, }) } /// Get the system proxy config [http/https/socks] pub fn set_sys(&self) -> Result<()> { use anyhow::bail; use std::process::Command; let enable = self.enable; let server = self.server.as_str(); let bypass = self.bypass.clone(); let schema = "org.gnome.system.proxy"; if enable { let mut split = server.split(":"); let host = split.next(); let port = split.next(); if port.is_none() { bail!("failed to parse the port"); } let host = format!("'{}'", host.unwrap_or("127.0.0.1")); let host = host.as_str(); let port = port.unwrap(); let http = format!("{schema}.http"); Command::new("gsettings") .args(["set", http.as_str(), "host", host]) .status()?; Command::new("gsettings") .args(["set", http.as_str(), "port", port]) .status()?; let https = format!("{schema}.https"); Command::new("gsettings") .args(["set", https.as_str(), "host", host]) .status()?; Command::new("gsettings") .args(["set", https.as_str(), "port", port]) .status()?; let socks = format!("{schema}.socks"); Command::new("gsettings") .args(["set", socks.as_str(), "host", host]) .status()?; Command::new("gsettings") .args(["set", socks.as_str(), "port", port]) .status()?; // set bypass // Todo: parse the ignore-hosts // aaa,bbb,cccc -> ['aaa', 'bbb', 'ccc'] Command::new("gsettings") .args(["set", schema, "ignore-hosts", bypass.as_str()]) // todo .status()?; } let mode = if enable { "'manual'" } else { "'none'" }; Command::new("gsettings") .args(["set", schema, "mode", mode]) .status()?; Ok(()) } /// help function fn get_proxy(typ: &str) -> Result { use std::process::Command; let schema = format!("org.gnome.system.proxy.{typ}"); let schema = schema.as_str(); let host = Command::new("gsettings") .args(["get", schema, "host"]) .output()?; let host = std::str::from_utf8(&host.stdout)?; let port = Command::new("gsettings") .args(["get", schema, "port"]) .output()?; let port = std::str::from_utf8(&port.stdout)?; Ok(format!("{host}:{port}")) } }