mirror of
https://github.com/chatmail/core.git
synced 2026-05-17 05:46:30 +03:00
Use SOCKS5 configuration for HTTP requests
This commit is contained in:
@@ -6,6 +6,7 @@
|
|||||||
- Use read/write timeouts instead of per-command timeouts for SMTP #3985
|
- Use read/write timeouts instead of per-command timeouts for SMTP #3985
|
||||||
- Cache DNS results for SMTP connections #3985
|
- Cache DNS results for SMTP connections #3985
|
||||||
- Prefer TLS over STARTTLS during autoconfiguration #4021
|
- Prefer TLS over STARTTLS during autoconfiguration #4021
|
||||||
|
- Use SOCKS5 configuration for HTTP requests #4017
|
||||||
|
|
||||||
## Fixes
|
## Fixes
|
||||||
- Fix Securejoin for multiple devices on a joining side #3982
|
- Fix Securejoin for multiple devices on a joining side #3982
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
use anyhow::{anyhow, format_err};
|
use anyhow::{anyhow, format_err};
|
||||||
|
|
||||||
use crate::context::Context;
|
use crate::context::Context;
|
||||||
|
use crate::socks::Socks5Config;
|
||||||
|
|
||||||
pub async fn read_url(context: &Context, url: &str) -> anyhow::Result<String> {
|
pub async fn read_url(context: &Context, url: &str) -> anyhow::Result<String> {
|
||||||
match read_url_inner(context, url).await {
|
match read_url_inner(context, url).await {
|
||||||
@@ -16,7 +17,8 @@ pub async fn read_url(context: &Context, url: &str) -> anyhow::Result<String> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
pub async fn read_url_inner(context: &Context, url: &str) -> anyhow::Result<String> {
|
pub async fn read_url_inner(context: &Context, url: &str) -> anyhow::Result<String> {
|
||||||
let client = crate::http::get_client()?;
|
let socks5_config = Socks5Config::from_database(&context.sql).await?;
|
||||||
|
let client = crate::http::get_client(socks5_config)?;
|
||||||
let mut url = url.to_string();
|
let mut url = url.to_string();
|
||||||
|
|
||||||
// Follow up to 10 http-redirects
|
// Follow up to 10 http-redirects
|
||||||
|
|||||||
19
src/http.rs
19
src/http.rs
@@ -4,10 +4,21 @@ use std::time::Duration;
|
|||||||
|
|
||||||
use anyhow::Result;
|
use anyhow::Result;
|
||||||
|
|
||||||
|
use crate::socks::Socks5Config;
|
||||||
|
|
||||||
const HTTP_TIMEOUT: Duration = Duration::from_secs(30);
|
const HTTP_TIMEOUT: Duration = Duration::from_secs(30);
|
||||||
|
|
||||||
pub(crate) fn get_client() -> Result<reqwest::Client> {
|
pub(crate) fn get_client(socks5_config: Option<Socks5Config>) -> Result<reqwest::Client> {
|
||||||
Ok(reqwest::ClientBuilder::new()
|
let builder = reqwest::ClientBuilder::new().timeout(HTTP_TIMEOUT);
|
||||||
.timeout(HTTP_TIMEOUT)
|
let builder = if let Some(socks5_config) = socks5_config {
|
||||||
.build()?)
|
let proxy = reqwest::Proxy::all(socks5_config.to_url())?;
|
||||||
|
builder.proxy(proxy)
|
||||||
|
} else {
|
||||||
|
// Disable usage of "system" proxy configured via environment variables.
|
||||||
|
// It is enabled by default in `reqwest`, see
|
||||||
|
// <https://docs.rs/reqwest/0.11.14/reqwest/struct.ClientBuilder.html#method.no_proxy>
|
||||||
|
// for documentation.
|
||||||
|
builder.no_proxy()
|
||||||
|
};
|
||||||
|
Ok(builder.build()?)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -162,7 +162,7 @@ impl LoginParam {
|
|||||||
.await?
|
.await?
|
||||||
.and_then(|provider_id| get_provider_by_id(&provider_id));
|
.and_then(|provider_id| get_provider_by_id(&provider_id));
|
||||||
|
|
||||||
let socks5_config = Socks5Config::from_database(context).await?;
|
let socks5_config = Socks5Config::from_database(&context.sql).await?;
|
||||||
|
|
||||||
Ok(LoginParam {
|
Ok(LoginParam {
|
||||||
addr,
|
addr,
|
||||||
|
|||||||
@@ -12,6 +12,7 @@ use crate::config::Config;
|
|||||||
use crate::context::Context;
|
use crate::context::Context;
|
||||||
use crate::provider;
|
use crate::provider;
|
||||||
use crate::provider::Oauth2Authorizer;
|
use crate::provider::Oauth2Authorizer;
|
||||||
|
use crate::socks::Socks5Config;
|
||||||
use crate::tools::time;
|
use crate::tools::time;
|
||||||
|
|
||||||
const OAUTH2_GMAIL: Oauth2 = Oauth2 {
|
const OAUTH2_GMAIL: Oauth2 = Oauth2 {
|
||||||
@@ -158,7 +159,8 @@ pub async fn get_oauth2_access_token(
|
|||||||
}
|
}
|
||||||
|
|
||||||
// ... and POST
|
// ... and POST
|
||||||
let client = crate::http::get_client()?;
|
let socks5_config = Socks5Config::from_database(&context.sql).await?;
|
||||||
|
let client = crate::http::get_client(socks5_config)?;
|
||||||
|
|
||||||
let response: Response = match client.post(post_url).form(&post_param).send().await {
|
let response: Response = match client.post(post_url).form(&post_param).send().await {
|
||||||
Ok(resp) => match resp.json().await {
|
Ok(resp) => match resp.json().await {
|
||||||
@@ -284,7 +286,8 @@ impl Oauth2 {
|
|||||||
// "verified_email": true,
|
// "verified_email": true,
|
||||||
// "picture": "https://lh4.googleusercontent.com/-Gj5jh_9R0BY/AAAAAAAAAAI/AAAAAAAAAAA/IAjtjfjtjNA/photo.jpg"
|
// "picture": "https://lh4.googleusercontent.com/-Gj5jh_9R0BY/AAAAAAAAAAI/AAAAAAAAAAA/IAjtjfjtjNA/photo.jpg"
|
||||||
// }
|
// }
|
||||||
let client = match crate::http::get_client() {
|
let socks5_config = Socks5Config::from_database(&context.sql).await.ok()?;
|
||||||
|
let client = match crate::http::get_client(socks5_config) {
|
||||||
Ok(cl) => cl,
|
Ok(cl) => cl,
|
||||||
Err(err) => {
|
Err(err) => {
|
||||||
warn!(context, "failed to get HTTP client: {}", err);
|
warn!(context, "failed to get HTTP client: {}", err);
|
||||||
|
|||||||
@@ -22,6 +22,7 @@ use crate::context::Context;
|
|||||||
use crate::key::Fingerprint;
|
use crate::key::Fingerprint;
|
||||||
use crate::message::Message;
|
use crate::message::Message;
|
||||||
use crate::peerstate::Peerstate;
|
use crate::peerstate::Peerstate;
|
||||||
|
use crate::socks::Socks5Config;
|
||||||
use crate::tools::time;
|
use crate::tools::time;
|
||||||
use crate::{token, EventType};
|
use crate::{token, EventType};
|
||||||
|
|
||||||
@@ -377,7 +378,11 @@ struct CreateAccountErrorResponse {
|
|||||||
#[allow(clippy::indexing_slicing)]
|
#[allow(clippy::indexing_slicing)]
|
||||||
async fn set_account_from_qr(context: &Context, qr: &str) -> Result<()> {
|
async fn set_account_from_qr(context: &Context, qr: &str) -> Result<()> {
|
||||||
let url_str = &qr[DCACCOUNT_SCHEME.len()..];
|
let url_str = &qr[DCACCOUNT_SCHEME.len()..];
|
||||||
let response = crate::http::get_client()?.post(url_str).send().await?;
|
let socks5_config = Socks5Config::from_database(&context.sql).await?;
|
||||||
|
let response = crate::http::get_client(socks5_config)?
|
||||||
|
.post(url_str)
|
||||||
|
.send()
|
||||||
|
.await?;
|
||||||
let response_status = response.status();
|
let response_status = response.status();
|
||||||
let response_text = response.text().await.with_context(|| {
|
let response_text = response.text().await.with_context(|| {
|
||||||
format!("Cannot create account, request to {url_str:?} failed: empty response")
|
format!("Cannot create account, request to {url_str:?} failed: empty response")
|
||||||
|
|||||||
52
src/socks.rs
52
src/socks.rs
@@ -9,11 +9,13 @@ use fast_socks5::client::{Config, Socks5Stream};
|
|||||||
use fast_socks5::util::target_addr::ToTargetAddr;
|
use fast_socks5::util::target_addr::ToTargetAddr;
|
||||||
use fast_socks5::AuthenticationMethod;
|
use fast_socks5::AuthenticationMethod;
|
||||||
use fast_socks5::Socks5Command;
|
use fast_socks5::Socks5Command;
|
||||||
|
use percent_encoding::{utf8_percent_encode, NON_ALPHANUMERIC};
|
||||||
use tokio::net::TcpStream;
|
use tokio::net::TcpStream;
|
||||||
use tokio_io_timeout::TimeoutStream;
|
use tokio_io_timeout::TimeoutStream;
|
||||||
|
|
||||||
use crate::context::Context;
|
use crate::context::Context;
|
||||||
use crate::net::connect_tcp;
|
use crate::net::connect_tcp;
|
||||||
|
use crate::sql::Sql;
|
||||||
|
|
||||||
#[derive(Default, Debug, Clone, PartialEq, Eq)]
|
#[derive(Default, Debug, Clone, PartialEq, Eq)]
|
||||||
pub struct Socks5Config {
|
pub struct Socks5Config {
|
||||||
@@ -24,9 +26,7 @@ pub struct Socks5Config {
|
|||||||
|
|
||||||
impl Socks5Config {
|
impl Socks5Config {
|
||||||
/// Reads SOCKS5 configuration from the database.
|
/// Reads SOCKS5 configuration from the database.
|
||||||
pub async fn from_database(context: &Context) -> Result<Option<Self>> {
|
pub async fn from_database(sql: &Sql) -> Result<Option<Self>> {
|
||||||
let sql = &context.sql;
|
|
||||||
|
|
||||||
let enabled = sql.get_raw_config_bool("socks5_enabled").await?;
|
let enabled = sql.get_raw_config_bool("socks5_enabled").await?;
|
||||||
if enabled {
|
if enabled {
|
||||||
let host = sql.get_raw_config("socks5_host").await?.unwrap_or_default();
|
let host = sql.get_raw_config("socks5_host").await?.unwrap_or_default();
|
||||||
@@ -55,6 +55,20 @@ impl Socks5Config {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Converts SOCKS5 configuration into URL.
|
||||||
|
pub fn to_url(&self) -> String {
|
||||||
|
// `socks5h` means that hostname is resolved into address by the proxy
|
||||||
|
// and DNS requests should not leak.
|
||||||
|
let mut url = "socks5h://".to_string();
|
||||||
|
if let Some((username, password)) = &self.user_password {
|
||||||
|
let username_urlencoded = utf8_percent_encode(username, NON_ALPHANUMERIC).to_string();
|
||||||
|
let password_urlencoded = utf8_percent_encode(password, NON_ALPHANUMERIC).to_string();
|
||||||
|
url += &format!("{username_urlencoded}:{password_urlencoded}@");
|
||||||
|
}
|
||||||
|
url += &format!("{}:{}", self.host, self.port);
|
||||||
|
url
|
||||||
|
}
|
||||||
|
|
||||||
/// If `load_dns_cache` is true, loads cached DNS resolution results.
|
/// If `load_dns_cache` is true, loads cached DNS resolution results.
|
||||||
/// Use this only if the connection is going to be protected with TLS checks.
|
/// Use this only if the connection is going to be protected with TLS checks.
|
||||||
pub async fn connect(
|
pub async fn connect(
|
||||||
@@ -103,3 +117,35 @@ impl fmt::Display for Socks5Config {
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[cfg(test)]
|
||||||
|
mod tests {
|
||||||
|
use super::*;
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_socks5h_url() {
|
||||||
|
let config = Socks5Config {
|
||||||
|
host: "127.0.0.1".to_string(),
|
||||||
|
port: 9050,
|
||||||
|
user_password: None,
|
||||||
|
};
|
||||||
|
assert_eq!(config.to_url(), "socks5h://127.0.0.1:9050");
|
||||||
|
|
||||||
|
let config = Socks5Config {
|
||||||
|
host: "example.org".to_string(),
|
||||||
|
port: 1080,
|
||||||
|
user_password: Some(("root".to_string(), "toor".to_string())),
|
||||||
|
};
|
||||||
|
assert_eq!(config.to_url(), "socks5h://root:toor@example.org:1080");
|
||||||
|
|
||||||
|
let config = Socks5Config {
|
||||||
|
host: "example.org".to_string(),
|
||||||
|
port: 1080,
|
||||||
|
user_password: Some(("root".to_string(), "foo/?\\@".to_string())),
|
||||||
|
};
|
||||||
|
assert_eq!(
|
||||||
|
config.to_url(),
|
||||||
|
"socks5h://root:foo%2F%3F%5C%40@example.org:1080"
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user