diff --git a/Cargo.lock b/Cargo.lock index 3810ed04..31ebfcad 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -180,7 +180,7 @@ dependencies = [ "objc2-foundation", "parking_lot 0.12.5", "percent-encoding", - "windows-sys 0.60.2", + "windows-sys 0.59.0", "wl-clipboard-rs", "x11rb", ] @@ -1423,7 +1423,7 @@ dependencies = [ "libc", "option-ext", "redox_users 0.5.2", - "windows-sys 0.61.2", + "windows-sys 0.59.0", ] [[package]] @@ -1605,7 +1605,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "39cab71617ae0d63f51a36d69f866391735b51691dbda63cf6f96d042b63efeb" dependencies = [ "libc", - "windows-sys 0.61.2", + "windows-sys 0.59.0", ] [[package]] @@ -2430,6 +2430,7 @@ dependencies = [ "ratatui", "ratatui-image", "regex", + "reqwest", "rpassword", "serde", "serde_json", @@ -2794,7 +2795,7 @@ checksum = "3640c1c38b8e4e43584d8df18be5fc6b0aa314ce6ebf51b53313d4306cca8e46" dependencies = [ "hermit-abi 0.5.2", "libc", - "windows-sys 0.61.2", + "windows-sys 0.59.0", ] [[package]] @@ -3638,7 +3639,7 @@ version = "0.50.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7957b9740744892f114936ab4a57b3f487491bbeafaf8083688b16841a4240e5" dependencies = [ - "windows-sys 0.61.2", + "windows-sys 0.59.0", ] [[package]] @@ -3900,7 +3901,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7d8fae84b431384b68627d0f9b3b1245fcf9f46f6c0e3dc902e9dce64edd1967" dependencies = [ "libc", - "windows-sys 0.61.2", + "windows-sys 0.42.0", ] [[package]] @@ -4402,7 +4403,7 @@ dependencies = [ "once_cell", "socket2 0.6.1", "tracing", - "windows-sys 0.60.2", + "windows-sys 0.59.0", ] [[package]] @@ -5047,7 +5048,7 @@ dependencies = [ "errno", "libc", "linux-raw-sys 0.11.0", - "windows-sys 0.61.2", + "windows-sys 0.59.0", ] [[package]] @@ -5599,7 +5600,7 @@ dependencies = [ "getrandom 0.3.4", "once_cell", "rustix 1.1.3", - "windows-sys 0.61.2", + "windows-sys 0.59.0", ] [[package]] diff --git a/Cargo.toml b/Cargo.toml index 19f54cd3..b6da2f4a 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -94,6 +94,11 @@ version = "0.14.0" default-features = false features = ["e2e-encryption", "sqlite", "sso-login"] +[dependencies.reqwest] +version = "0.12" +default-features = false +features = ["socks"] + [dependencies.tokio] version = "1.24.1" features = ["macros", "net", "rt-multi-thread", "sync", "time"] diff --git a/config.example.toml b/config.example.toml index c4ebd653..addc52c4 100644 --- a/config.example.toml +++ b/config.example.toml @@ -52,6 +52,11 @@ split = [ [macros."normal|visual"] "V" = "m" +[proxy] +# SOCKS5 proxy URL for all Matrix HTTP traffic (requires reqwest socks feature). +# Omit this section or leave unset to use no proxy (or the system proxy from ALL_PROXY). +# url = "socks5://127.0.0.1:1080" + [dirs] cache = "/home/user/.cache/iamb/" logs = "/home/user/.local/share/iamb/logs/" diff --git a/docs/iamb.5 b/docs/iamb.5 index 8357f965..8d1f068a 100644 --- a/docs/iamb.5 +++ b/docs/iamb.5 @@ -66,7 +66,32 @@ Configure the directories to use for data, logs, and more. See .Sx DIRECTORIES for the possible values you can set in this object. +.It Sy proxy +Configure an HTTP/SOCKS proxy for Matrix traffic. +See +.Sx PROXY . +.El +.Sh PROXY +The +.Sy [proxy] +section configures a proxy for all outgoing Matrix HTTP requests. +If unset, no proxy is used for the built-in client (environment variables +such as +.Ev ALL_PROXY +are not read by default). +.Bl -tag -width Ds +.It Sy url +Proxy URL, e.g. +.Dq socks5://127.0.0.1:1080 +for a SOCKS5 proxy. +Requires the reqwest +.Sy socks +feature (enabled in this build). .El +.Pp +.Sy proxy +can be overridden per profile under +.Sx PROFILES . .Sh PROFILES These options are configured as fields in the .Sy profiles @@ -94,6 +119,8 @@ per-profile overrides of their global values: .Sy macros .It .Sy settings +.It +.Sy proxy .El .Ss Example 1: A single profile .Bd -literal -offset indent diff --git a/src/config.rs b/src/config.rs index e7a4a47b..0f88324e 100644 --- a/src/config.rs +++ b/src/config.rs @@ -809,6 +809,20 @@ pub enum WindowLayout { Split { split: Vec }, } +#[derive(Clone, Default, Deserialize)] +pub struct ProxyConfig { + /// Proxy URL (e.g. socks5://127.0.0.1:1080). Requires reqwest's socks feature. + pub url: Option, +} + +impl ProxyConfig { + fn merge(self, other: Self) -> Self { + ProxyConfig { + url: self.url.or(other.url), + } + } +} + #[derive(Clone, Debug, Default, Deserialize, Eq, PartialEq)] #[serde(rename_all = "lowercase", tag = "style")] pub enum Layout { @@ -831,6 +845,7 @@ pub struct ProfileConfig { pub dirs: Option, pub layout: Option, pub macros: Option, + pub proxy: Option, } #[derive(Clone, Deserialize)] @@ -841,6 +856,7 @@ pub struct IambConfig { pub dirs: Option, pub layout: Option, pub macros: Option, + pub proxy: Option, } impl IambConfig { @@ -872,6 +888,8 @@ pub struct ApplicationSettings { pub dirs: DirectoryValues, pub layout: Layout, pub macros: Macros, + /// Proxy URL for the HTTP client (e.g. socks5://127.0.0.1:1080), from [proxy] url. + pub proxy_url: Option, } impl ApplicationSettings { @@ -915,6 +933,7 @@ impl ApplicationSettings { settings: global, layout, macros, + proxy: global_proxy, } = config; validate_profile_names(&profiles); @@ -958,6 +977,7 @@ impl ApplicationSettings { let macros = merge_maps(profile.macros.take(), macros).unwrap_or_default(); let layout = profile.layout.take().or(layout).unwrap_or_default(); + let proxy_url = profile.proxy.take().unwrap_or_default().merge(global_proxy.unwrap_or_default()).url; let tunables = global.unwrap_or_default(); let tunables = profile.settings.take().unwrap_or_default().merge(tunables); @@ -1011,6 +1031,7 @@ impl ApplicationSettings { dirs, layout, macros, + proxy_url, }; Ok(settings) diff --git a/src/tests.rs b/src/tests.rs index e9b05021..121ee846 100644 --- a/src/tests.rs +++ b/src/tests.rs @@ -221,11 +221,13 @@ pub fn mock_settings() -> ApplicationSettings { dirs: None, layout: None, macros: None, + proxy: None, }, tunables: mock_tunables(), dirs: mock_dirs(), layout: Default::default(), macros: HashMap::default(), + proxy_url: None, } } diff --git a/src/worker.rs b/src/worker.rs index f3c9791a..fe3964e5 100644 --- a/src/worker.rs +++ b/src/worker.rs @@ -711,14 +711,21 @@ async fn create_client_inner( let req_timeout = Duration::from_secs(settings.tunables.request_timeout); // Set up the HTTP client. - let http = reqwest::Client::builder() + let mut builder = reqwest::Client::builder() .user_agent(IAMB_USER_AGENT) .timeout(req_timeout) .pool_idle_timeout(Duration::from_secs(60)) .pool_max_idle_per_host(10) - .tcp_keepalive(Duration::from_secs(10)) - .build() - .unwrap(); + .tcp_keepalive(Duration::from_secs(10)); + + if let Some(proxy_url) = &settings.proxy_url { + builder = builder.proxy( + reqwest::Proxy::all(proxy_url.as_str()) + .expect("Invalid [proxy] url in configuration"), + ); + } + + let http = builder.build().expect("Failed to build HTTP client"); let req_config = RequestConfig::new().timeout(req_timeout).max_retry_time(req_timeout);