├── .github ├── .DS_Store └── workflows │ └── test.yml ├── .gitignore ├── Cargo.toml ├── LICENSE ├── README.md ├── src ├── lib.rs ├── linux.rs ├── macos.rs ├── utils.rs └── windows.rs └── tests └── test.rs /.github/.DS_Store: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zzzgydi/sysproxy-rs/355d9c98e20f894e75f13049d0031d3794e9a74a/.github/.DS_Store -------------------------------------------------------------------------------- /.github/workflows/test.yml: -------------------------------------------------------------------------------- 1 | name: Test 2 | 3 | on: 4 | push: 5 | branches: 6 | - main 7 | pull_request: 8 | branches: 9 | - main 10 | 11 | env: 12 | CARGO_INCREMENTAL: 0 13 | RUST_BACKTRACE: short 14 | 15 | jobs: 16 | test: 17 | runs-on: ${{ matrix.os }} 18 | continue-on-error: true 19 | strategy: 20 | matrix: 21 | os: 22 | - macos-latest 23 | - ubuntu-latest 24 | - windows-latest 25 | steps: 26 | - name: Checkout repo 27 | uses: actions/checkout@v2 28 | 29 | - name: Install dependencies (Linux) 30 | if: matrix.os == 'ubuntu-latest' 31 | run: sudo apt install -y gsettings-desktop-schemas 32 | 33 | - name: Install Rust 34 | uses: actions-rs/toolchain@v1 35 | with: 36 | toolchain: stable 37 | profile: minimal 38 | override: true 39 | 40 | - name: Rust Cache 41 | uses: Swatinem/rust-cache@ce325b60658c1b38465c06cc965b79baf32c1e72 42 | 43 | - name: Run cargo check 44 | run: cargo check 45 | 46 | - name: Run cargo test 47 | run: | 48 | cargo test -- --nocapture 49 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | debug/ 2 | target/ 3 | Cargo.lock 4 | **/*.rs.bk 5 | *.pdb 6 | 7 | .vscode 8 | .DS_Store 9 | -------------------------------------------------------------------------------- /Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "sysproxy" 3 | version = "0.3.0" 4 | edition = "2021" 5 | authors = ["zzzgydi"] 6 | license = "MIT" 7 | readme = "README.md" 8 | repository = "https://github.com/zzzgydi/sysproxy-rs.git" 9 | keywords = ["system-proxy", "proxy", "networksetup", "gsettings"] 10 | description = "A library for set/get system proxy. Supports Windows, macOS and linux (via gsettings)." 11 | 12 | [dependencies] 13 | log = "0.4" 14 | thiserror = "1" 15 | iptools = { version = "0.3.0", optional = true } 16 | 17 | [target.'cfg(target_os = "linux")'.dependencies] 18 | xdg = "^2.5" 19 | 20 | [target.'cfg(target_os = "macos")'.dependencies] 21 | interfaces = "0.0.9" 22 | 23 | [target.'cfg(target_os = "windows")'.dependencies] 24 | winreg = { version = "0.52", features = ["transactions"] } 25 | windows = { version = "0.58", features = [ 26 | "Win32_Networking_WinInet", 27 | "Win32_NetworkManagement_Rras", 28 | "Win32_Foundation", 29 | ] } 30 | 31 | [dev-dependencies] 32 | serial_test = "3.1.1" 33 | 34 | [features] 35 | default = ["iptools"] 36 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2022 zzzgydi 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # sysproxy-rs 2 | 3 | A library for set/get system proxy. Supports Windows, macOS and linux (via gsettings/kconfig). 4 | -------------------------------------------------------------------------------- /src/lib.rs: -------------------------------------------------------------------------------- 1 | //! Get/Set system proxy. Supports Windows, macOS and linux (via gsettings). 2 | 3 | #[cfg(target_os = "linux")] 4 | mod linux; 5 | #[cfg(target_os = "macos")] 6 | mod macos; 7 | #[cfg(target_os = "windows")] 8 | mod windows; 9 | 10 | // #[cfg(feature = "utils")] 11 | pub mod utils; 12 | 13 | #[derive(Debug, Default, Clone, PartialEq, Eq)] 14 | pub struct Sysproxy { 15 | pub enable: bool, 16 | pub host: String, 17 | pub port: u16, 18 | pub bypass: String, 19 | } 20 | 21 | #[derive(Debug, Default, Clone, PartialEq, Eq)] 22 | pub struct Autoproxy { 23 | pub enable: bool, 24 | pub url: String, 25 | } 26 | 27 | #[derive(thiserror::Error, Debug)] 28 | pub enum Error { 29 | #[error("failed to parse string `{0}`")] 30 | ParseStr(String), 31 | 32 | #[error(transparent)] 33 | Io(#[from] std::io::Error), 34 | 35 | #[error("failed to get default network interface")] 36 | NetworkInterface, 37 | 38 | #[error("failed to set proxy for this environment")] 39 | NotSupport, 40 | 41 | #[cfg(target_os = "linux")] 42 | #[error(transparent)] 43 | Xdg(#[from] xdg::BaseDirectoriesError), 44 | 45 | #[cfg(target_os = "windows")] 46 | #[error("system call failed")] 47 | SystemCall(#[from] windows::Win32Error), 48 | } 49 | 50 | pub type Result = std::result::Result; 51 | 52 | impl Sysproxy { 53 | pub fn is_support() -> bool { 54 | cfg!(any( 55 | target_os = "linux", 56 | target_os = "macos", 57 | target_os = "windows", 58 | )) 59 | } 60 | } 61 | 62 | impl Autoproxy { 63 | pub fn is_support() -> bool { 64 | cfg!(any( 65 | target_os = "linux", 66 | target_os = "macos", 67 | target_os = "windows", 68 | )) 69 | } 70 | } 71 | -------------------------------------------------------------------------------- /src/linux.rs: -------------------------------------------------------------------------------- 1 | use crate::{Autoproxy, Error, Result, Sysproxy}; 2 | use std::{env, process::Command, str::from_utf8, sync::LazyLock}; 3 | 4 | const CMD_KEY: &str = "org.gnome.system.proxy"; 5 | 6 | static IS_APPIMAGE: LazyLock = LazyLock::new(|| { 7 | std::env::var("APPIMAGE").is_ok() 8 | }); 9 | 10 | impl Sysproxy { 11 | pub fn get_system_proxy() -> Result { 12 | let enable = Sysproxy::get_enable()?; 13 | 14 | let mut socks = get_proxy("socks")?; 15 | let https = get_proxy("https")?; 16 | let http = get_proxy("http")?; 17 | 18 | if socks.host.is_empty() { 19 | if !http.host.is_empty() { 20 | socks.host = http.host; 21 | socks.port = http.port; 22 | } 23 | if !https.host.is_empty() { 24 | socks.host = https.host; 25 | socks.port = https.port; 26 | } 27 | } 28 | 29 | socks.enable = enable; 30 | socks.bypass = Sysproxy::get_bypass().unwrap_or("".into()); 31 | 32 | Ok(socks) 33 | } 34 | 35 | pub fn set_system_proxy(&self) -> Result<()> { 36 | self.set_enable()?; 37 | 38 | if self.enable { 39 | self.set_socks()?; 40 | self.set_https()?; 41 | self.set_http()?; 42 | self.set_bypass()?; 43 | } 44 | 45 | Ok(()) 46 | } 47 | 48 | pub fn get_enable() -> Result { 49 | match env::var("XDG_CURRENT_DESKTOP").unwrap_or_default().as_str() { 50 | "KDE" => { 51 | let xdg_dir = xdg::BaseDirectories::new()?; 52 | let config = xdg_dir.get_config_file("kioslaverc"); 53 | let config = config.to_str().ok_or(Error::ParseStr("config".into()))?; 54 | 55 | let mode = kreadconfig() 56 | .args([ 57 | "--file", 58 | config, 59 | "--group", 60 | "Proxy Settings", 61 | "--key", 62 | "ProxyType", 63 | ]) 64 | .output()?; 65 | let mode = from_utf8(&mode.stdout) 66 | .or(Err(Error::ParseStr("mode".into())))? 67 | .trim(); 68 | Ok(mode == "1") 69 | } 70 | _ => { 71 | let mode = gsettings().args(["get", CMD_KEY, "mode"]).output()?; 72 | let mode = from_utf8(&mode.stdout) 73 | .or(Err(Error::ParseStr("mode".into())))? 74 | .trim(); 75 | Ok(mode == "'manual'") 76 | } 77 | } 78 | } 79 | 80 | pub fn get_bypass() -> Result { 81 | match env::var("XDG_CURRENT_DESKTOP").unwrap_or_default().as_str() { 82 | "KDE" => { 83 | let xdg_dir = xdg::BaseDirectories::new()?; 84 | let config = xdg_dir.get_config_file("kioslaverc"); 85 | let config = config.to_str().ok_or(Error::ParseStr("config".into()))?; 86 | 87 | let bypass = kreadconfig() 88 | .args([ 89 | "--file", 90 | config, 91 | "--group", 92 | "Proxy Settings", 93 | "--key", 94 | "NoProxyFor", 95 | ]) 96 | .output()?; 97 | let bypass = from_utf8(&bypass.stdout) 98 | .or(Err(Error::ParseStr("bypass".into())))? 99 | .trim(); 100 | 101 | let bypass = bypass 102 | .split(',') 103 | .map(|h| strip_str(h.trim())) 104 | .collect::>() 105 | .join(","); 106 | 107 | Ok(bypass) 108 | } 109 | _ => { 110 | let bypass = gsettings() 111 | .args(["get", CMD_KEY, "ignore-hosts"]) 112 | .output()?; 113 | let bypass = from_utf8(&bypass.stdout) 114 | .or(Err(Error::ParseStr("bypass".into())))? 115 | .trim(); 116 | 117 | let bypass = bypass.strip_prefix('[').unwrap_or(bypass); 118 | let bypass = bypass.strip_suffix(']').unwrap_or(bypass); 119 | 120 | let bypass = bypass 121 | .split(',') 122 | .map(|h| strip_str(h.trim())) 123 | .collect::>() 124 | .join(","); 125 | 126 | Ok(bypass) 127 | } 128 | } 129 | } 130 | 131 | pub fn get_http() -> Result { 132 | get_proxy("http") 133 | } 134 | 135 | pub fn get_https() -> Result { 136 | get_proxy("https") 137 | } 138 | 139 | pub fn get_socks() -> Result { 140 | get_proxy("socks") 141 | } 142 | 143 | pub fn set_enable(&self) -> Result<()> { 144 | match env::var("XDG_CURRENT_DESKTOP").unwrap_or_default().as_str() { 145 | "KDE" => { 146 | let xdg_dir = xdg::BaseDirectories::new()?; 147 | let config = xdg_dir.get_config_file("kioslaverc"); 148 | let config = config.to_str().ok_or(Error::ParseStr("config".into()))?; 149 | let mode = if self.enable { "1" } else { "0" }; 150 | kwriteconfig() 151 | .args([ 152 | "--file", 153 | config, 154 | "--group", 155 | "Proxy Settings", 156 | "--key", 157 | "ProxyType", 158 | mode, 159 | ]) 160 | .status()?; 161 | let gmode = if self.enable { "'manual'" } else { "'none'" }; 162 | gsettings().args(["set", CMD_KEY, "mode", gmode]).status()?; 163 | Ok(()) 164 | } 165 | _ => { 166 | let mode = if self.enable { "'manual'" } else { "'none'" }; 167 | gsettings().args(["set", CMD_KEY, "mode", mode]).status()?; 168 | Ok(()) 169 | } 170 | } 171 | } 172 | 173 | pub fn set_bypass(&self) -> Result<()> { 174 | match env::var("XDG_CURRENT_DESKTOP").unwrap_or_default().as_str() { 175 | "KDE" => { 176 | let xdg_dir = xdg::BaseDirectories::new()?; 177 | let config = xdg_dir.get_config_file("kioslaverc"); 178 | let config = config.to_str().ok_or(Error::ParseStr("config".into()))?; 179 | 180 | let bypass = self 181 | .bypass 182 | .split(',') 183 | .map(|h| { 184 | let mut host = String::from(h.trim()); 185 | if !host.starts_with('\'') && !host.starts_with('"') { 186 | host = String::from("'") + &host; 187 | } 188 | if !host.ends_with('\'') && !host.ends_with('"') { 189 | host += "'"; 190 | } 191 | host 192 | }) 193 | .collect::>() 194 | .join(", "); 195 | 196 | let bypass = format!("[{bypass}]"); 197 | 198 | gsettings() 199 | .args(["set", CMD_KEY, "ignore-hosts", bypass.as_str()]) 200 | .status()?; 201 | 202 | kwriteconfig() 203 | .args([ 204 | "--file", 205 | config, 206 | "--group", 207 | "Proxy Settings", 208 | "--key", 209 | "NoProxyFor", 210 | self.bypass.as_str(), 211 | ]) 212 | .status()?; 213 | Ok(()) 214 | } 215 | _ => { 216 | let bypass = self 217 | .bypass 218 | .split(',') 219 | .map(|h| { 220 | let mut host = String::from(h.trim()); 221 | if !host.starts_with('\'') && !host.starts_with('"') { 222 | host = String::from("'") + &host; 223 | } 224 | if !host.ends_with('\'') && !host.ends_with('"') { 225 | host += "'"; 226 | } 227 | host 228 | }) 229 | .collect::>() 230 | .join(", "); 231 | 232 | let bypass = format!("[{bypass}]"); 233 | 234 | gsettings() 235 | .args(["set", CMD_KEY, "ignore-hosts", bypass.as_str()]) 236 | .status()?; 237 | Ok(()) 238 | } 239 | } 240 | } 241 | 242 | pub fn set_http(&self) -> Result<()> { 243 | set_proxy(self, "http") 244 | } 245 | 246 | pub fn set_https(&self) -> Result<()> { 247 | set_proxy(self, "https") 248 | } 249 | 250 | pub fn set_socks(&self) -> Result<()> { 251 | set_proxy(self, "socks") 252 | } 253 | } 254 | 255 | fn gsettings() -> Command { 256 | let mut command = Command::new("gsettings"); 257 | if *IS_APPIMAGE { 258 | command.env_remove("LD_LIBRARY_PATH"); 259 | } 260 | command 261 | } 262 | 263 | fn kreadconfig() -> Command { 264 | let command = match env::var("KDE_SESSION_VERSION").unwrap_or_default().as_str() { 265 | "6" => "kreadconfig6", 266 | _ => "kreadconfig5", 267 | }; 268 | let mut command = Command::new(command); 269 | if *IS_APPIMAGE { 270 | command.env_remove("LD_LIBRARY_PATH"); 271 | } 272 | command 273 | } 274 | 275 | fn kwriteconfig() -> Command { 276 | let command = match env::var("KDE_SESSION_VERSION").unwrap_or_default().as_str() { 277 | "6" => "kwriteconfig6", 278 | _ => "kwriteconfig5", 279 | }; 280 | let mut command = Command::new(command); 281 | if *IS_APPIMAGE { 282 | command.env_remove("LD_LIBRARY_PATH"); 283 | } 284 | command 285 | } 286 | 287 | fn set_proxy(proxy: &Sysproxy, service: &str) -> Result<()> { 288 | match env::var("XDG_CURRENT_DESKTOP").unwrap_or_default().as_str() { 289 | "KDE" => { 290 | let schema = format!("{CMD_KEY}.{service}"); 291 | let schema = schema.as_str(); 292 | 293 | let host = format!("'{}'", proxy.host); 294 | let host = host.as_str(); 295 | let port = format!("{}", proxy.port); 296 | let port = port.as_str(); 297 | 298 | gsettings().args(["set", schema, "host", host]).status()?; 299 | gsettings().args(["set", schema, "port", port]).status()?; 300 | 301 | let xdg_dir = xdg::BaseDirectories::new()?; 302 | let config = xdg_dir.get_config_file("kioslaverc"); 303 | let config = config.to_str().ok_or(Error::ParseStr("config".into()))?; 304 | 305 | let key = format!("{service}Proxy"); 306 | let key = key.as_str(); 307 | 308 | let service = match service { 309 | "socks" => "socks", 310 | _ => "http", 311 | }; 312 | 313 | let host = proxy.host.to_string(); 314 | let host = host.as_str(); 315 | 316 | let schema = format!("{service}://{host} {port}"); 317 | let schema = schema.as_str(); 318 | 319 | kwriteconfig() 320 | .args([ 321 | "--file", 322 | config, 323 | "--group", 324 | "Proxy Settings", 325 | "--key", 326 | key, 327 | schema, 328 | ]) 329 | .status()?; 330 | 331 | Ok(()) 332 | } 333 | _ => { 334 | let schema = format!("{CMD_KEY}.{service}"); 335 | let schema = schema.as_str(); 336 | 337 | let host = format!("'{}'", proxy.host); 338 | let host = host.as_str(); 339 | let port = format!("{}", proxy.port); 340 | let port = port.as_str(); 341 | 342 | gsettings().args(["set", schema, "host", host]).status()?; 343 | gsettings().args(["set", schema, "port", port]).status()?; 344 | 345 | Ok(()) 346 | } 347 | } 348 | } 349 | 350 | fn get_proxy(service: &str) -> Result { 351 | match env::var("XDG_CURRENT_DESKTOP").unwrap_or_default().as_str() { 352 | "KDE" => { 353 | let xdg_dir = xdg::BaseDirectories::new()?; 354 | let config = xdg_dir.get_config_file("kioslaverc"); 355 | let config = config.to_str().ok_or(Error::ParseStr("config".into()))?; 356 | 357 | let key = format!("{service}Proxy"); 358 | let key = key.as_str(); 359 | 360 | let schema = kreadconfig() 361 | .args(["--file", config, "--group", "Proxy Settings", "--key", key]) 362 | .output()?; 363 | let schema = from_utf8(&schema.stdout) 364 | .or(Err(Error::ParseStr("schema".into())))? 365 | .trim(); 366 | let schema = schema 367 | .trim_start_matches("http://") 368 | .trim_start_matches("socks://"); 369 | let schema = schema 370 | .split_once(' ') 371 | .ok_or(Error::ParseStr("schema".into()))?; 372 | 373 | let host = strip_str(schema.0); 374 | let port = schema.1.parse().unwrap_or(80u16); 375 | 376 | Ok(Sysproxy { 377 | enable: false, 378 | host: String::from(host), 379 | port, 380 | bypass: "".into(), 381 | }) 382 | } 383 | _ => { 384 | let schema = format!("{CMD_KEY}.{service}"); 385 | let schema = schema.as_str(); 386 | 387 | let host = gsettings().args(["get", schema, "host"]).output()?; 388 | let host = from_utf8(&host.stdout) 389 | .or(Err(Error::ParseStr("host".into())))? 390 | .trim(); 391 | let host = strip_str(host); 392 | 393 | let port = gsettings().args(["get", schema, "port"]).output()?; 394 | let port = from_utf8(&port.stdout) 395 | .or(Err(Error::ParseStr("port".into())))? 396 | .trim(); 397 | let port = port.parse().unwrap_or(80u16); 398 | 399 | Ok(Sysproxy { 400 | enable: false, 401 | host: String::from(host), 402 | port, 403 | bypass: "".into(), 404 | }) 405 | } 406 | } 407 | } 408 | 409 | fn strip_str(text: &str) -> &str { 410 | text.strip_prefix('\'') 411 | .unwrap_or(text) 412 | .strip_suffix('\'') 413 | .unwrap_or(text) 414 | } 415 | 416 | impl Autoproxy { 417 | pub fn get_auto_proxy() -> Result { 418 | let (enable, url) = match env::var("XDG_CURRENT_DESKTOP").unwrap_or_default().as_str() { 419 | "KDE" => { 420 | let xdg_dir = xdg::BaseDirectories::new()?; 421 | let config = xdg_dir.get_config_file("kioslaverc"); 422 | let config = config.to_str().ok_or(Error::ParseStr("config".into()))?; 423 | 424 | let mode = kreadconfig() 425 | .args([ 426 | "--file", 427 | config, 428 | "--group", 429 | "Proxy Settings", 430 | "--key", 431 | "ProxyType", 432 | ]) 433 | .output()?; 434 | let mode = from_utf8(&mode.stdout) 435 | .or(Err(Error::ParseStr("mode".into())))? 436 | .trim(); 437 | let url = kreadconfig() 438 | .args([ 439 | "--file", 440 | config, 441 | "--group", 442 | "Proxy Settings", 443 | "--key", 444 | "Proxy Config Script", 445 | ]) 446 | .output()?; 447 | let url = from_utf8(&url.stdout) 448 | .or(Err(Error::ParseStr("url".into())))? 449 | .trim(); 450 | (mode == "2", url.to_string()) 451 | } 452 | _ => { 453 | let mode = gsettings().args(["get", CMD_KEY, "mode"]).output()?; 454 | let mode = from_utf8(&mode.stdout) 455 | .or(Err(Error::ParseStr("mode".into())))? 456 | .trim(); 457 | let url = gsettings() 458 | .args(["get", CMD_KEY, "autoconfig-url"]) 459 | .output()?; 460 | let url: &str = from_utf8(&url.stdout) 461 | .or(Err(Error::ParseStr("url".into())))? 462 | .trim(); 463 | let url = strip_str(url); 464 | (mode == "'auto'", url.to_string()) 465 | } 466 | }; 467 | 468 | Ok(Autoproxy { enable, url }) 469 | } 470 | 471 | pub fn set_auto_proxy(&self) -> Result<()> { 472 | match env::var("XDG_CURRENT_DESKTOP").unwrap_or_default().as_str() { 473 | "KDE" => { 474 | let xdg_dir = xdg::BaseDirectories::new()?; 475 | let config = xdg_dir.get_config_file("kioslaverc"); 476 | let config = config.to_str().ok_or(Error::ParseStr("config".into()))?; 477 | let mode = if self.enable { "2" } else { "0" }; 478 | kwriteconfig() 479 | .args([ 480 | "--file", 481 | config, 482 | "--group", 483 | "Proxy Settings", 484 | "--key", 485 | "ProxyType", 486 | mode, 487 | ]) 488 | .status()?; 489 | kwriteconfig() 490 | .args([ 491 | "--file", 492 | config, 493 | "--group", 494 | "Proxy Settings", 495 | "--key", 496 | "Proxy Config Script", 497 | &self.url, 498 | ]) 499 | .status()?; 500 | let gmode = if self.enable { "'auto'" } else { "'none'" }; 501 | gsettings().args(["set", CMD_KEY, "mode", gmode]).status()?; 502 | gsettings() 503 | .args(["set", CMD_KEY, "autoconfig-url", &self.url]) 504 | .status()?; 505 | } 506 | _ => { 507 | let mode = if self.enable { "'auto'" } else { "'none'" }; 508 | gsettings().args(["set", CMD_KEY, "mode", mode]).status()?; 509 | gsettings() 510 | .args(["set", CMD_KEY, "autoconfig-url", &self.url]) 511 | .status()?; 512 | } 513 | } 514 | 515 | Ok(()) 516 | } 517 | } 518 | -------------------------------------------------------------------------------- /src/macos.rs: -------------------------------------------------------------------------------- 1 | use crate::{Autoproxy, Error, Result, Sysproxy}; 2 | use log::debug; 3 | use std::net::{SocketAddr, UdpSocket}; 4 | use std::{process::Command, str::from_utf8}; 5 | 6 | impl Sysproxy { 7 | pub fn get_system_proxy() -> Result { 8 | let service = default_network_service().or_else(|e| { 9 | debug!("Failed to get network service: {:?}", e); 10 | default_network_service_by_ns() 11 | }); 12 | if let Err(e) = service { 13 | debug!("Failed to get network service by networksetup: {:?}", e); 14 | return Err(e); 15 | } 16 | let service = service.unwrap(); 17 | let service = service.as_str(); 18 | 19 | let mut socks = Sysproxy::get_socks(service)?; 20 | debug!("Getting SOCKS proxy: {:?}", socks); 21 | 22 | let http = Sysproxy::get_http(service)?; 23 | debug!("Getting HTTP proxy: {:?}", http); 24 | 25 | let https = Sysproxy::get_https(service)?; 26 | debug!("Getting HTTPS proxy: {:?}", https); 27 | 28 | let bypass = Sysproxy::get_bypass(service)?; 29 | debug!("Getting bypass domains: {:?}", bypass); 30 | 31 | socks.bypass = bypass; 32 | 33 | if !socks.enable { 34 | if http.enable { 35 | socks.enable = true; 36 | socks.host = http.host; 37 | socks.port = http.port; 38 | } 39 | if https.enable { 40 | socks.enable = true; 41 | socks.host = https.host; 42 | socks.port = https.port; 43 | } 44 | } 45 | 46 | Ok(socks) 47 | } 48 | 49 | pub fn set_system_proxy(&self) -> Result<()> { 50 | let service = default_network_service().or_else(|e| { 51 | debug!("Failed to get network service: {:?}", e); 52 | default_network_service_by_ns() 53 | }); 54 | if let Err(e) = service { 55 | debug!("Failed to get network service by networksetup: {:?}", e); 56 | return Err(e); 57 | } 58 | let service = service.unwrap(); 59 | let service = service.as_str(); 60 | 61 | debug!("Use network service: {}", service); 62 | 63 | debug!("Setting SOCKS proxy"); 64 | self.set_socks(service)?; 65 | 66 | debug!("Setting HTTP proxy"); 67 | self.set_https(service)?; 68 | 69 | debug!("Setting HTTPS proxy"); 70 | self.set_http(service)?; 71 | 72 | debug!("Setting bypass domains"); 73 | self.set_bypass(service)?; 74 | Ok(()) 75 | } 76 | 77 | pub fn get_http(service: &str) -> Result { 78 | get_proxy(ProxyType::HTTP, service) 79 | } 80 | 81 | pub fn get_https(service: &str) -> Result { 82 | get_proxy(ProxyType::HTTPS, service) 83 | } 84 | 85 | pub fn get_socks(service: &str) -> Result { 86 | get_proxy(ProxyType::SOCKS, service) 87 | } 88 | 89 | pub fn get_bypass(service: &str) -> Result { 90 | let bypass_output = Command::new("networksetup") 91 | .args(["-getproxybypassdomains", service]) 92 | .output()?; 93 | 94 | let bypass = from_utf8(&bypass_output.stdout) 95 | .or(Err(Error::ParseStr("bypass".into())))? 96 | .split('\n') 97 | .filter(|s| s.len() > 0) 98 | .collect::>() 99 | .join(","); 100 | 101 | Ok(bypass) 102 | } 103 | 104 | pub fn set_http(&self, service: &str) -> Result<()> { 105 | set_proxy(self, ProxyType::HTTP, service) 106 | } 107 | 108 | pub fn set_https(&self, service: &str) -> Result<()> { 109 | set_proxy(self, ProxyType::HTTPS, service) 110 | } 111 | 112 | pub fn set_socks(&self, service: &str) -> Result<()> { 113 | set_proxy(self, ProxyType::SOCKS, service) 114 | } 115 | 116 | pub fn set_bypass(&self, service: &str) -> Result<()> { 117 | let domains = self.bypass.split(",").collect::>(); 118 | networksetup() 119 | .args([["-setproxybypassdomains", service].to_vec(), domains].concat()) 120 | .status()?; 121 | Ok(()) 122 | } 123 | } 124 | 125 | impl Autoproxy { 126 | pub fn get_auto_proxy() -> Result { 127 | let service = default_network_service().or_else(|e| { 128 | debug!("Failed to get network service: {:?}", e); 129 | default_network_service_by_ns() 130 | }); 131 | if let Err(e) = service { 132 | debug!("Failed to get network service by networksetup: {:?}", e); 133 | return Err(e); 134 | } 135 | let service = service.unwrap(); 136 | let service = service.as_str(); 137 | 138 | let auto_output = networksetup() 139 | .args(["-getautoproxyurl", service]) 140 | .output()?; 141 | let auto = from_utf8(&auto_output.stdout) 142 | .or(Err(Error::ParseStr("auto".into())))? 143 | .trim() 144 | .split_once('\n') 145 | .ok_or(Error::ParseStr("auto".into()))?; 146 | let url = strip_str(auto.0.strip_prefix("URL: ").unwrap_or("")); 147 | let enable = auto.1 == "Enabled: Yes"; 148 | 149 | Ok(Autoproxy { 150 | enable, 151 | url: url.to_string(), 152 | }) 153 | } 154 | 155 | pub fn set_auto_proxy(&self) -> Result<()> { 156 | let service = default_network_service().or_else(|e| { 157 | debug!("Failed to get network service: {:?}", e); 158 | default_network_service_by_ns() 159 | }); 160 | if let Err(e) = service { 161 | debug!("Failed to get network service by networksetup: {:?}", e); 162 | return Err(e); 163 | } 164 | let service = service.unwrap(); 165 | let service = service.as_str(); 166 | 167 | let enable = if self.enable { "on" } else { "off" }; 168 | let url = if self.url.is_empty() { 169 | "\"\"" 170 | } else { 171 | &self.url 172 | }; 173 | networksetup() 174 | .args(["-setautoproxyurl", service, url]) 175 | .status()?; 176 | networksetup() 177 | .args(["-setautoproxystate", service, enable]) 178 | .status()?; 179 | 180 | Ok(()) 181 | } 182 | } 183 | 184 | #[derive(Debug)] 185 | enum ProxyType { 186 | HTTP, 187 | HTTPS, 188 | SOCKS, 189 | } 190 | 191 | impl ProxyType { 192 | fn to_target(&self) -> &'static str { 193 | match self { 194 | ProxyType::HTTP => "webproxy", 195 | ProxyType::HTTPS => "securewebproxy", 196 | ProxyType::SOCKS => "socksfirewallproxy", 197 | } 198 | } 199 | } 200 | 201 | fn networksetup() -> Command { 202 | Command::new("networksetup") 203 | } 204 | 205 | fn set_proxy(proxy: &Sysproxy, proxy_type: ProxyType, service: &str) -> Result<()> { 206 | let target = format!("-set{}", proxy_type.to_target()); 207 | let target = target.as_str(); 208 | 209 | let host = proxy.host.as_str(); 210 | let port = format!("{}", proxy.port); 211 | let port = port.as_str(); 212 | 213 | networksetup() 214 | .args([target, service, host, port]) 215 | .status()?; 216 | 217 | let target_state = format!("-set{}state", proxy_type.to_target()); 218 | let enable = if proxy.enable { "on" } else { "off" }; 219 | 220 | networksetup() 221 | .args([target_state.as_str(), service, enable]) 222 | .status()?; 223 | 224 | Ok(()) 225 | } 226 | 227 | fn get_proxy(proxy_type: ProxyType, service: &str) -> Result { 228 | let target = format!("-get{}", proxy_type.to_target()); 229 | let target = target.as_str(); 230 | 231 | let output = networksetup().args([target, service]).output()?; 232 | 233 | let stdout = from_utf8(&output.stdout).or(Err(Error::ParseStr("output".into())))?; 234 | let enable = parse(stdout, "Enabled:"); 235 | let enable = enable == "Yes"; 236 | 237 | let host = parse(stdout, "Server:"); 238 | let host = host.into(); 239 | 240 | let port = parse(stdout, "Port:"); 241 | let port = port.parse().or(Err(Error::ParseStr("port".into())))?; 242 | 243 | Ok(Sysproxy { 244 | enable, 245 | host, 246 | port, 247 | bypass: "".into(), 248 | }) 249 | } 250 | 251 | fn parse<'a>(target: &'a str, key: &'a str) -> &'a str { 252 | match target.find(key) { 253 | Some(idx) => { 254 | let idx = idx + key.len(); 255 | let value = &target[idx..]; 256 | let value = match value.find("\n") { 257 | Some(end) => &value[..end], 258 | None => value, 259 | }; 260 | value.trim() 261 | } 262 | None => "", 263 | } 264 | } 265 | 266 | fn strip_str<'a>(text: &'a str) -> &'a str { 267 | text.strip_prefix('"') 268 | .unwrap_or(text) 269 | .strip_suffix('"') 270 | .unwrap_or(text) 271 | } 272 | 273 | fn default_network_service() -> Result { 274 | let socket = UdpSocket::bind("0.0.0.0:0")?; 275 | socket.connect("1.1.1.1:80")?; 276 | let ip = socket.local_addr()?.ip(); 277 | let addr = SocketAddr::new(ip, 0); 278 | 279 | let interfaces = interfaces::Interface::get_all().or(Err(Error::NetworkInterface))?; 280 | let interface = interfaces 281 | .into_iter() 282 | .find(|i| i.addresses.iter().find(|a| a.addr == Some(addr)).is_some()) 283 | .map(|i| i.name.to_owned()); 284 | 285 | match interface { 286 | Some(interface) => { 287 | let service = get_server_by_order(interface)?; 288 | Ok(service) 289 | } 290 | None => Err(Error::NetworkInterface), 291 | } 292 | } 293 | 294 | fn default_network_service_by_ns() -> Result { 295 | let output = networksetup().arg("-listallnetworkservices").output()?; 296 | let stdout = from_utf8(&output.stdout).or(Err(Error::ParseStr("output".into())))?; 297 | let mut lines = stdout.split('\n'); 298 | lines.next(); // ignore the tips 299 | 300 | // get the first service 301 | match lines.next() { 302 | Some(line) => Ok(line.into()), 303 | None => Err(Error::NetworkInterface), 304 | } 305 | } 306 | 307 | #[allow(dead_code)] 308 | fn get_service_by_device(device: String) -> Result { 309 | let output = networksetup().arg("-listallhardwareports").output()?; 310 | let stdout = from_utf8(&output.stdout).or(Err(Error::ParseStr("output".into())))?; 311 | 312 | let hardware = stdout.split("Ethernet Address:").find_map(|s| { 313 | let lines = s.split("\n"); 314 | let mut hardware = None; 315 | let mut device_ = None; 316 | 317 | for line in lines { 318 | if line.starts_with("Hardware Port:") { 319 | hardware = Some(&line[15..]); 320 | } 321 | if line.starts_with("Device:") { 322 | device_ = Some(&line[8..]) 323 | } 324 | } 325 | 326 | if device == device_? { 327 | hardware 328 | } else { 329 | None 330 | } 331 | }); 332 | 333 | match hardware { 334 | Some(hardware) => Ok(hardware.into()), 335 | None => Err(Error::NetworkInterface), 336 | } 337 | } 338 | 339 | fn get_server_by_order(device: String) -> Result { 340 | let services = listnetworkserviceorder()?; 341 | let service = services 342 | .into_iter() 343 | .find(|(_, _, d)| d == &device) 344 | .map(|(s, _, _)| s); 345 | match service { 346 | Some(service) => Ok(service), 347 | None => Err(Error::NetworkInterface), 348 | } 349 | } 350 | 351 | fn listnetworkserviceorder() -> Result> { 352 | let output = networksetup().arg("-listnetworkserviceorder").output()?; 353 | let stdout = from_utf8(&output.stdout).or(Err(Error::ParseStr("output".into())))?; 354 | 355 | let mut lines = stdout.split('\n'); 356 | lines.next(); // ignore the tips 357 | 358 | let mut services = Vec::new(); 359 | let mut p: Option<(String, String, String)> = None; 360 | 361 | for line in lines { 362 | if !line.starts_with("(") { 363 | continue; 364 | } 365 | 366 | if p.is_none() { 367 | let ri = line.find(")"); 368 | if ri.is_none() { 369 | continue; 370 | } 371 | let ri = ri.unwrap(); 372 | let service = line[ri + 1..].trim(); 373 | p = Some((service.into(), "".into(), "".into())); 374 | } else { 375 | let line = &line[1..line.len() - 1]; 376 | let pi = line.find("Port:"); 377 | let di = line.find(", Device:"); 378 | if pi.is_none() || di.is_none() { 379 | continue; 380 | } 381 | let pi = pi.unwrap(); 382 | let di = di.unwrap(); 383 | let port = line[pi + 5..di].trim(); 384 | let device = line[di + 9..].trim(); 385 | let (service, _, _) = p.as_mut().unwrap(); 386 | *p.as_mut().unwrap() = (service.to_owned(), port.into(), device.into()); 387 | services.push(p.take().unwrap()); 388 | } 389 | } 390 | 391 | Ok(services) 392 | } 393 | 394 | #[test] 395 | fn test_order() { 396 | let services = listnetworkserviceorder().unwrap(); 397 | for (service, port, device) in services { 398 | println!("service: {}, port: {}, device: {}", service, port, device); 399 | } 400 | } 401 | -------------------------------------------------------------------------------- /src/utils.rs: -------------------------------------------------------------------------------- 1 | use crate::{Error, Result}; 2 | use iptools::iprange::{IPv4, IpRange, IpVer}; 3 | 4 | /// Convert ipv4 cidr to wildcard 5 | /// 6 | /// Example: 7 | /// ``` 8 | /// use sysproxy::utils::ipv4_cidr_to_wildcard; 9 | /// assert_eq!(ipv4_cidr_to_wildcard("127.0.0.1/8").unwrap(), vec!["127.*".to_string()]); 10 | /// ``` 11 | pub fn ipv4_cidr_to_wildcard(cidr: &str) -> Result> { 12 | let ip = IpRange::::new(cidr, "").or(Err(Error::ParseStr(cidr.into())))?; 13 | 14 | if ip.get_version() != IpVer::V4 { 15 | return Err(Error::ParseStr(cidr.into())); 16 | } 17 | 18 | let (start, end) = ip.get_range().unwrap(); 19 | let start = start.split('.').collect::>(); 20 | let end = end.split('.').collect::>(); 21 | 22 | let mut ret = vec![]; 23 | let mut each = String::new(); 24 | for i in 0..4 { 25 | if start[i] == end[i] { 26 | each.push_str(start[i]); 27 | if i != 3 { 28 | each.push('.'); 29 | } 30 | continue; 31 | } 32 | 33 | if start[i] == "0" && end[i] == "255" { 34 | each.push('*'); 35 | ret.push(each); 36 | break; 37 | } 38 | 39 | let s = start[i] 40 | .parse::() 41 | .or(Err(Error::ParseStr(cidr.into())))?; 42 | let e = end[i] 43 | .parse::() 44 | .or(Err(Error::ParseStr(cidr.into())))?; 45 | 46 | for j in s..e + 1 { 47 | let mut builder = each.clone(); 48 | builder.push_str(&j.to_string()); 49 | if i != 3 { 50 | builder.push_str(".*"); 51 | } 52 | ret.push(builder); 53 | } 54 | break; 55 | } 56 | Ok(ret) 57 | } 58 | 59 | #[test] 60 | fn test_ipv4_cidr_to_wildcard() { 61 | println!("{:?}", ipv4_cidr_to_wildcard("127.0.0.1/1")); 62 | println!("{:?}", ipv4_cidr_to_wildcard("127.0.0.1/2")); 63 | println!("{:?}", ipv4_cidr_to_wildcard("127.0.0.1/3")); 64 | println!("{:?}", ipv4_cidr_to_wildcard("127.0.0.1/4")); 65 | println!("{:?}", ipv4_cidr_to_wildcard("127.0.0.1/5")); 66 | println!("{:?}", ipv4_cidr_to_wildcard("127.0.0.1/6")); 67 | println!("{:?}", ipv4_cidr_to_wildcard("127.0.0.1/7")); 68 | println!("{:?}", ipv4_cidr_to_wildcard("127.0.0.1/8")); 69 | println!("{:?}", ipv4_cidr_to_wildcard("127.0.0.1/9")); 70 | println!("{:?}", ipv4_cidr_to_wildcard("127.0.0.1/10")); 71 | } 72 | -------------------------------------------------------------------------------- /src/windows.rs: -------------------------------------------------------------------------------- 1 | use crate::{Autoproxy, Error, Result, Sysproxy}; 2 | use std::ffi::c_void; 3 | use std::{mem::size_of, mem::ManuallyDrop, net::SocketAddr, str::FromStr}; 4 | use windows::core::PWSTR; 5 | use windows::Win32::Networking::WinInet::{ 6 | InternetSetOptionW, INTERNET_OPTION_PER_CONNECTION_OPTION, 7 | INTERNET_OPTION_PROXY_SETTINGS_CHANGED, INTERNET_OPTION_REFRESH, 8 | INTERNET_PER_CONN_AUTOCONFIG_URL, INTERNET_PER_CONN_FLAGS, INTERNET_PER_CONN_OPTIONW, 9 | INTERNET_PER_CONN_OPTIONW_0, INTERNET_PER_CONN_OPTION_LISTW, INTERNET_PER_CONN_PROXY_BYPASS, 10 | INTERNET_PER_CONN_PROXY_SERVER, PROXY_TYPE_AUTO_DETECT, PROXY_TYPE_AUTO_PROXY_URL, 11 | PROXY_TYPE_DIRECT, PROXY_TYPE_PROXY, 12 | }; 13 | use winreg::{enums, RegKey}; 14 | 15 | pub use windows::core::Error as Win32Error; 16 | 17 | const SUB_KEY: &str = "SOFTWARE\\Microsoft\\Windows\\CurrentVersion\\Internet Settings"; 18 | 19 | /// unset proxy 20 | fn unset_proxy() -> Result<()> { 21 | let mut p_opts = ManuallyDrop::new(Vec::::with_capacity(1)); 22 | p_opts.push(INTERNET_PER_CONN_OPTIONW { 23 | dwOption: INTERNET_PER_CONN_FLAGS, 24 | Value: { 25 | let mut v = INTERNET_PER_CONN_OPTIONW_0::default(); 26 | v.dwValue = PROXY_TYPE_DIRECT; 27 | v 28 | }, 29 | }); 30 | let opts = INTERNET_PER_CONN_OPTION_LISTW { 31 | dwSize: size_of::() as u32, 32 | dwOptionCount: 1, 33 | dwOptionError: 0, 34 | pOptions: p_opts.as_mut_ptr() as *mut INTERNET_PER_CONN_OPTIONW, 35 | pszConnection: PWSTR::null(), 36 | }; 37 | let res = apply(&opts); 38 | unsafe { 39 | ManuallyDrop::drop(&mut p_opts); 40 | } 41 | res 42 | } 43 | 44 | fn set_auto_proxy(server: String) -> Result<()> { 45 | let mut p_opts = ManuallyDrop::new(Vec::::with_capacity(2)); 46 | p_opts.push(INTERNET_PER_CONN_OPTIONW { 47 | dwOption: INTERNET_PER_CONN_FLAGS, 48 | Value: INTERNET_PER_CONN_OPTIONW_0 { 49 | dwValue: PROXY_TYPE_AUTO_DETECT | PROXY_TYPE_AUTO_PROXY_URL | PROXY_TYPE_DIRECT, 50 | }, 51 | }); 52 | 53 | let mut s = ManuallyDrop::new(server.encode_utf16().chain([0u16]).collect::>()); 54 | p_opts.push(INTERNET_PER_CONN_OPTIONW { 55 | dwOption: INTERNET_PER_CONN_AUTOCONFIG_URL, 56 | Value: INTERNET_PER_CONN_OPTIONW_0 { 57 | pszValue: PWSTR::from_raw(s.as_ptr() as *mut u16), 58 | }, 59 | }); 60 | 61 | let opts = INTERNET_PER_CONN_OPTION_LISTW { 62 | dwSize: size_of::() as u32, 63 | dwOptionCount: 2, 64 | dwOptionError: 0, 65 | pOptions: p_opts.as_mut_ptr() as *mut INTERNET_PER_CONN_OPTIONW, 66 | pszConnection: PWSTR::null(), 67 | }; 68 | 69 | let res = apply(&opts); 70 | unsafe { 71 | ManuallyDrop::drop(&mut s); 72 | ManuallyDrop::drop(&mut p_opts); 73 | } 74 | res 75 | } 76 | 77 | /// set global proxy 78 | fn set_global_proxy(server: String, bypass: String) -> Result<()> { 79 | let mut p_opts = ManuallyDrop::new(Vec::::with_capacity(3)); 80 | p_opts.push(INTERNET_PER_CONN_OPTIONW { 81 | dwOption: INTERNET_PER_CONN_FLAGS, 82 | Value: INTERNET_PER_CONN_OPTIONW_0 { 83 | dwValue: PROXY_TYPE_PROXY | PROXY_TYPE_DIRECT, 84 | }, 85 | }); 86 | 87 | let mut s = ManuallyDrop::new(server.encode_utf16().chain([0u16]).collect::>()); 88 | p_opts.push(INTERNET_PER_CONN_OPTIONW { 89 | dwOption: INTERNET_PER_CONN_PROXY_SERVER, 90 | Value: INTERNET_PER_CONN_OPTIONW_0 { 91 | pszValue: PWSTR::from_raw(s.as_ptr() as *mut u16), 92 | }, 93 | }); 94 | 95 | let mut b = ManuallyDrop::new( 96 | bypass 97 | .clone() 98 | .encode_utf16() 99 | .chain([0u16]) 100 | .collect::>(), 101 | ); 102 | p_opts.push(INTERNET_PER_CONN_OPTIONW { 103 | dwOption: INTERNET_PER_CONN_PROXY_BYPASS, 104 | Value: INTERNET_PER_CONN_OPTIONW_0 { 105 | pszValue: PWSTR::from_raw(b.as_ptr() as *mut u16), 106 | }, 107 | }); 108 | 109 | let opts = INTERNET_PER_CONN_OPTION_LISTW { 110 | dwSize: size_of::() as u32, 111 | dwOptionCount: 3, 112 | dwOptionError: 0, 113 | pOptions: p_opts.as_mut_ptr() as *mut INTERNET_PER_CONN_OPTIONW, 114 | pszConnection: PWSTR::null(), 115 | }; 116 | 117 | let res = apply(&opts); 118 | unsafe { 119 | ManuallyDrop::drop(&mut s); 120 | ManuallyDrop::drop(&mut b); 121 | ManuallyDrop::drop(&mut p_opts); 122 | } 123 | res 124 | } 125 | 126 | fn apply(options: &INTERNET_PER_CONN_OPTION_LISTW) -> Result<()> { 127 | unsafe { 128 | // setting options 129 | let opts = options as *const INTERNET_PER_CONN_OPTION_LISTW as *const c_void; 130 | InternetSetOptionW( 131 | None, 132 | INTERNET_OPTION_PER_CONNECTION_OPTION, 133 | Some(opts), 134 | size_of::() as u32, 135 | )?; 136 | // propagating changes 137 | InternetSetOptionW(None, INTERNET_OPTION_PROXY_SETTINGS_CHANGED, None, 0)?; 138 | // refreshing 139 | InternetSetOptionW(None, INTERNET_OPTION_REFRESH, None, 0)?; 140 | } 141 | Ok(()) 142 | } 143 | 144 | impl Sysproxy { 145 | pub fn get_system_proxy() -> Result { 146 | let hkcu = RegKey::predef(enums::HKEY_CURRENT_USER); 147 | let cur_var = hkcu.open_subkey_with_flags(SUB_KEY, enums::KEY_READ)?; 148 | let enable = cur_var.get_value::("ProxyEnable").unwrap_or(0u32) == 1u32; 149 | let server = cur_var 150 | .get_value::("ProxyServer") 151 | .unwrap_or("".into()); 152 | let server = server.as_str(); 153 | 154 | let (host, port) = if server.is_empty() { 155 | ("".into(), 0) 156 | } else { 157 | let socket = 158 | SocketAddr::from_str(server).or(Err(Error::ParseStr(server.to_string())))?; 159 | let host = socket.ip().to_string(); 160 | let port = socket.port(); 161 | (host, port) 162 | }; 163 | 164 | let bypass = cur_var.get_value("ProxyOverride").unwrap_or("".into()); 165 | 166 | Ok(Sysproxy { 167 | enable, 168 | host, 169 | port, 170 | bypass, 171 | }) 172 | } 173 | 174 | pub fn set_system_proxy(&self) -> Result<()> { 175 | match self.enable { 176 | true => set_global_proxy(format!("{}:{}", self.host, self.port), self.bypass.clone()), 177 | false => unset_proxy(), 178 | } 179 | } 180 | } 181 | 182 | impl Autoproxy { 183 | pub fn get_auto_proxy() -> Result { 184 | let hkcu = RegKey::predef(enums::HKEY_CURRENT_USER); 185 | let cur_var = hkcu.open_subkey_with_flags(SUB_KEY, enums::KEY_READ)?; 186 | let url = cur_var.get_value::("AutoConfigURL"); 187 | let enable = url.is_ok(); 188 | let url = url.unwrap_or("".into()); 189 | 190 | Ok(Autoproxy { enable, url }) 191 | } 192 | 193 | pub fn set_auto_proxy(&self) -> Result<()> { 194 | match self.enable { 195 | true => set_auto_proxy(self.url.clone()), 196 | false => unset_proxy(), 197 | } 198 | } 199 | } 200 | -------------------------------------------------------------------------------- /tests/test.rs: -------------------------------------------------------------------------------- 1 | #[cfg(test)] 2 | mod tests { 3 | use serial_test::serial; 4 | use sysproxy::{Autoproxy, Sysproxy}; 5 | 6 | #[test] 7 | fn test_sys_support() { 8 | assert!(Sysproxy::is_support()); 9 | } 10 | 11 | #[test] 12 | fn test_auto_support() { 13 | assert!(Autoproxy::is_support()); 14 | } 15 | 16 | #[test] 17 | fn test_sys_get() { 18 | Sysproxy::get_system_proxy().unwrap(); 19 | } 20 | 21 | #[test] 22 | fn test_auto_get() { 23 | Autoproxy::get_auto_proxy().unwrap(); 24 | } 25 | 26 | #[test] 27 | #[serial] 28 | fn test_system_enable() { 29 | let mut sysproxy = Sysproxy { 30 | enable: true, 31 | host: "127.0.0.1".into(), 32 | port: 9090, 33 | #[cfg(target_os = "windows")] 34 | bypass: "localhost;127.*".into(), 35 | #[cfg(not(target_os = "windows"))] 36 | bypass: "localhost,127.0.0.1/8".into(), 37 | }; 38 | sysproxy.set_system_proxy().unwrap(); 39 | 40 | let cur_proxy = Sysproxy::get_system_proxy().unwrap(); 41 | 42 | assert_eq!(cur_proxy, sysproxy); 43 | 44 | sysproxy.enable = false; 45 | sysproxy.set_system_proxy().unwrap(); 46 | 47 | let current = Sysproxy::get_system_proxy().unwrap(); 48 | assert_eq!(current, sysproxy); 49 | } 50 | 51 | #[test] 52 | #[serial] 53 | fn test_auto_enable() { 54 | let mut autoproxy = Autoproxy { 55 | enable: true, 56 | url: "http://127.0.0.1:1234/".into(), 57 | }; 58 | autoproxy.set_auto_proxy().unwrap(); 59 | 60 | let cur_proxy = Autoproxy::get_auto_proxy().unwrap(); 61 | 62 | assert_eq!(cur_proxy, autoproxy); 63 | 64 | autoproxy.enable = false; 65 | autoproxy.url = "".into(); 66 | autoproxy.set_auto_proxy().unwrap(); 67 | 68 | let current = Autoproxy::get_auto_proxy().unwrap(); 69 | assert_eq!(current, autoproxy); 70 | } 71 | } 72 | --------------------------------------------------------------------------------