Skip to content

Commit

Permalink
Merge branch 'main' into main
Browse files Browse the repository at this point in the history
  • Loading branch information
zzzgydi authored Apr 16, 2024
2 parents b34c417 + 31f96dd commit 6d781f2
Show file tree
Hide file tree
Showing 9 changed files with 356 additions and 57 deletions.
10 changes: 8 additions & 2 deletions .github/workflows/test.yml
Original file line number Diff line number Diff line change
@@ -1,6 +1,12 @@
name: Test

on: [push, pull_request]
on:
push:
branches:
- main
pull_request:
branches:
- main

env:
CARGO_INCREMENTAL: 0
Expand Down Expand Up @@ -35,4 +41,4 @@ jobs:

- name: Run cargo test
run: |
cargo test
cargo test -- --nocapture
10 changes: 8 additions & 2 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -1,2 +1,8 @@
/target
/Cargo.lock
debug/
target/
Cargo.lock
**/*.rs.bk
*.pdb

.vscode
.DS_Store
14 changes: 12 additions & 2 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,9 @@ keywords = ["system-proxy", "proxy", "networksetup", "gsettings"]
description = "A library for set/get system proxy. Supports Windows, macOS and linux (via gsettings)."

[dependencies]
log = "0.4"
thiserror = "1"
iptools = { version = "0.2.4", optional = true }

[target.'cfg(target_os = "linux")'.dependencies]
xdg = "^2.5"
Expand All @@ -19,5 +21,13 @@ xdg = "^2.5"
interfaces = "0.0.8"

[target.'cfg(target_os = "windows")'.dependencies]
winreg = {version = "0.10", features = ["transactions"]}
winapi = {version = "0.3.9", features= ["wininet"]}
winreg = { version = "0.52", features = ["transactions"] }
windows = { version = "0.52", features = [
"Win32_Networking_WinInet",
"Win32_NetworkManagement_Rras",
"Win32_Foundation",
] }


[features]
default = ["iptools"]
11 changes: 9 additions & 2 deletions src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,9 @@ mod macos;
#[cfg(target_os = "windows")]
mod windows;

// #[cfg(feature = "utils")]
pub mod utils;

#[derive(Debug, Default, Clone, PartialEq, Eq)]
pub struct Sysproxy {
pub enable: bool,
Expand All @@ -17,8 +20,8 @@ pub struct Sysproxy {

#[derive(thiserror::Error, Debug)]
pub enum Error {
#[error("failed to parse string")]
ParseStr,
#[error("failed to parse string `{0}`")]
ParseStr(String),

#[error(transparent)]
Io(#[from] std::io::Error),
Expand All @@ -32,6 +35,10 @@ pub enum Error {
#[cfg(target_os = "linux")]
#[error(transparent)]
Xdg(#[from] xdg::BaseDirectoriesError),

#[cfg(target_os = "windows")]
#[error("system call failed")]
SystemCall(#[from] windows::Win32Error),
}

pub type Result<T> = std::result::Result<T, Error>;
Expand Down
28 changes: 17 additions & 11 deletions src/linux.rs
Original file line number Diff line number Diff line change
Expand Up @@ -46,13 +46,13 @@ impl Sysproxy {
match env::var("XDG_CURRENT_DESKTOP").unwrap_or_default().as_str() {
"GNOME" => {
let mode = gsettings().args(["get", CMD_KEY, "mode"]).output()?;
let mode = from_utf8(&mode.stdout).or(Err(Error::ParseStr))?.trim();
let mode = from_utf8(&mode.stdout).or(Err(Error::ParseStr("mode".into())))?.trim();
Ok(mode == "'manual'")
}
"KDE" => {
let xdg_dir = xdg::BaseDirectories::new()?;
let config = xdg_dir.get_config_file("kioslaverc");
let config = config.to_str().ok_or(Error::ParseStr)?;
let config = config.to_str().ok_or(Error::ParseStr("config".into()))?;

let mode = kreadconfig()
.args([
Expand All @@ -64,7 +64,7 @@ impl Sysproxy {
"ProxyType",
])
.output()?;
let mode = from_utf8(&mode.stdout).or(Err(Error::ParseStr))?.trim();
let mode = from_utf8(&mode.stdout).or(Err(Error::ParseStr("mode".into())))?.trim();
Ok(mode == "1")
}
_ => Err(Error::NotSupport),
Expand All @@ -77,7 +77,9 @@ impl Sysproxy {
let bypass = gsettings()
.args(["get", CMD_KEY, "ignore-hosts"])
.output()?;
let bypass = from_utf8(&bypass.stdout).or(Err(Error::ParseStr))?.trim();
let bypass = from_utf8(&bypass.stdout)
.or(Err(Error::ParseStr("bypass".into())))?
.trim();

let bypass = bypass.strip_prefix('[').unwrap_or(bypass);
let bypass = bypass.strip_suffix(']').unwrap_or(bypass);
Expand All @@ -93,7 +95,7 @@ impl Sysproxy {
"KDE" => {
let xdg_dir = xdg::BaseDirectories::new()?;
let config = xdg_dir.get_config_file("kioslaverc");
let config = config.to_str().ok_or(Error::ParseStr)?;
let config = config.to_str().ok_or(Error::ParseStr("config".into()))?;

let bypass = kreadconfig()
.args([
Expand All @@ -105,7 +107,7 @@ impl Sysproxy {
"NoProxyFor",
])
.output()?;
let bypass = from_utf8(&bypass.stdout).or(Err(Error::ParseStr))?.trim();
let bypass = from_utf8(&bypass.stdout).or(Err(Error::ParseStr("bypass".into())))?.trim();

let bypass = bypass
.split(',')
Expand Down Expand Up @@ -295,11 +297,15 @@ fn get_proxy(service: &str) -> Result<Sysproxy> {
let schema = schema.as_str();

let host = gsettings().args(["get", schema, "host"]).output()?;
let host = from_utf8(&host.stdout).or(Err(Error::ParseStr))?.trim();
let host = from_utf8(&host.stdout)
.or(Err(Error::ParseStr("host".into())))?
.trim();
let host = strip_str(host);

let port = gsettings().args(["get", schema, "port"]).output()?;
let port = from_utf8(&port.stdout).or(Err(Error::ParseStr))?.trim();
let port = from_utf8(&port.stdout)
.or(Err(Error::ParseStr("port".into())))?
.trim();
let port = port.parse().unwrap_or(80u16);

Ok(Sysproxy {
Expand All @@ -312,19 +318,19 @@ fn get_proxy(service: &str) -> Result<Sysproxy> {
"KDE" => {
let xdg_dir = xdg::BaseDirectories::new()?;
let config = xdg_dir.get_config_file("kioslaverc");
let config = config.to_str().ok_or(Error::ParseStr)?;
let config = config.to_str().ok_or(Error::ParseStr("config".into()))?;

let key = format!("{service}Proxy");
let key = key.as_str();

let schema = kreadconfig()
.args(["--file", config, "--group", "Proxy Settings", "--key", key])
.output()?;
let schema = from_utf8(&schema.stdout).or(Err(Error::ParseStr))?.trim();
let schema = from_utf8(&schema.stdout).or(Err(Error::ParseStr("schema".into())))?.trim();
let schema = schema
.trim_start_matches("http://")
.trim_start_matches("socks://");
let schema = schema.split_once(' ').ok_or(Error::ParseStr)?;
let schema = schema.split_once(' ').ok_or(Error::ParseStr("schema".into()))?;

let host = strip_str(schema.0);
let port = schema.1.parse().unwrap_or(80u16);
Expand Down
113 changes: 105 additions & 8 deletions src/macos.rs
Original file line number Diff line number Diff line change
@@ -1,16 +1,32 @@
use crate::{Error, Result, Sysproxy};
use log::debug;
use std::net::{SocketAddr, UdpSocket};
use std::{process::Command, str::from_utf8};

impl Sysproxy {
pub fn get_system_proxy() -> Result<Sysproxy> {
let service = default_network_service().or_else(|_| default_network_service_by_ns())?;
let service = default_network_service().or_else(|e| {
debug!("Failed to get network service: {:?}", e);
default_network_service_by_ns()
});
if let Err(e) = service {
debug!("Failed to get network service by networksetup: {:?}", e);
return Err(e);
}
let service = service.unwrap();
let service = service.as_str();

let mut socks = Sysproxy::get_socks(service)?;
debug!("Getting SOCKS proxy: {:?}", socks);

let http = Sysproxy::get_http(service)?;
debug!("Getting HTTP proxy: {:?}", http);

let https = Sysproxy::get_https(service)?;
debug!("Getting HTTPS proxy: {:?}", https);

let bypass = Sysproxy::get_bypass(service)?;
debug!("Getting bypass domains: {:?}", bypass);

socks.bypass = bypass;

Expand All @@ -31,12 +47,29 @@ impl Sysproxy {
}

pub fn set_system_proxy(&self) -> Result<()> {
let service = default_network_service().or_else(|_| default_network_service_by_ns())?;
let service = default_network_service().or_else(|e| {
debug!("Failed to get network service: {:?}", e);
default_network_service_by_ns()
});
if let Err(e) = service {
debug!("Failed to get network service by networksetup: {:?}", e);
return Err(e);
}
let service = service.unwrap();
let service = service.as_str();

debug!("Use network service: {}", service);

debug!("Setting SOCKS proxy");
self.set_socks(service)?;

debug!("Setting HTTP proxy");
self.set_https(service)?;

debug!("Setting HTTPS proxy");
self.set_http(service)?;

debug!("Setting bypass domains");
self.set_bypass(service)?;
Ok(())
}
Expand All @@ -59,7 +92,7 @@ impl Sysproxy {
.output()?;

let bypass = from_utf8(&bypass_output.stdout)
.or(Err(Error::ParseStr))?
.or(Err(Error::ParseStr("bypass".into())))?
.split('\n')
.filter(|s| s.len() > 0)
.collect::<Vec<&str>>()
Expand Down Expand Up @@ -138,15 +171,15 @@ fn get_proxy(proxy_type: ProxyType, service: &str) -> Result<Sysproxy> {

let output = networksetup().args([target, service]).output()?;

let stdout = from_utf8(&output.stdout).or(Err(Error::ParseStr))?;
let stdout = from_utf8(&output.stdout).or(Err(Error::ParseStr("output".into())))?;
let enable = parse(stdout, "Enabled:");
let enable = enable == "Yes";

let host = parse(stdout, "Server:");
let host = host.into();

let port = parse(stdout, "Port:");
let port = port.parse().or(Err(Error::ParseStr))?;
let port = port.parse().or(Err(Error::ParseStr("port".into())))?;

Ok(Sysproxy {
enable,
Expand Down Expand Up @@ -185,7 +218,7 @@ fn default_network_service() -> Result<String> {

match interface {
Some(interface) => {
let service = get_service_by_device(interface)?;
let service = get_server_by_order(interface)?;
Ok(service)
}
None => Err(Error::NetworkInterface),
Expand All @@ -194,7 +227,7 @@ fn default_network_service() -> Result<String> {

fn default_network_service_by_ns() -> Result<String> {
let output = networksetup().arg("-listallnetworkservices").output()?;
let stdout = from_utf8(&output.stdout).or(Err(Error::ParseStr))?;
let stdout = from_utf8(&output.stdout).or(Err(Error::ParseStr("output".into())))?;
let mut lines = stdout.split('\n');
lines.next(); // ignore the tips

Expand All @@ -205,9 +238,10 @@ fn default_network_service_by_ns() -> Result<String> {
}
}

#[allow(dead_code)]
fn get_service_by_device(device: String) -> Result<String> {
let output = networksetup().arg("-listallhardwareports").output()?;
let stdout = from_utf8(&output.stdout).or(Err(Error::ParseStr))?;
let stdout = from_utf8(&output.stdout).or(Err(Error::ParseStr("output".into())))?;

let hardware = stdout.split("Ethernet Address:").find_map(|s| {
let lines = s.split("\n");
Expand Down Expand Up @@ -235,3 +269,66 @@ fn get_service_by_device(device: String) -> Result<String> {
None => Err(Error::NetworkInterface),
}
}

fn get_server_by_order(device: String) -> Result<String> {
let services = listnetworkserviceorder()?;
let service = services
.into_iter()
.find(|(_, _, d)| d == &device)
.map(|(s, _, _)| s);
match service {
Some(service) => Ok(service),
None => Err(Error::NetworkInterface),
}
}

fn listnetworkserviceorder() -> Result<Vec<(String, String, String)>> {
let output = networksetup().arg("-listnetworkserviceorder").output()?;
let stdout = from_utf8(&output.stdout).or(Err(Error::ParseStr("output".into())))?;

let mut lines = stdout.split('\n');
lines.next(); // ignore the tips

let mut services = Vec::new();
let mut p: Option<(String, String, String)> = None;

for line in lines {
if !line.starts_with("(") {
continue;
}

if p.is_none() {
let ri = line.find(")");
if ri.is_none() {
continue;
}
let ri = ri.unwrap();
let service = line[ri + 1..].trim();
p = Some((service.into(), "".into(), "".into()));
} else {
let line = &line[1..line.len() - 1];
let pi = line.find("Port:");
let di = line.find(", Device:");
if pi.is_none() || di.is_none() {
continue;
}
let pi = pi.unwrap();
let di = di.unwrap();
let port = line[pi + 5..di].trim();
let device = line[di + 9..].trim();
let (service, _, _) = p.as_mut().unwrap();
*p.as_mut().unwrap() = (service.to_owned(), port.into(), device.into());
services.push(p.take().unwrap());
}
}

Ok(services)
}

#[test]
fn test_order() {
let services = listnetworkserviceorder().unwrap();
for (service, port, device) in services {
println!("service: {}, port: {}, device: {}", service, port, device);
}
}
Loading

0 comments on commit 6d781f2

Please sign in to comment.