Skip to content

Commit

Permalink
remove reqwest
Browse files Browse the repository at this point in the history
  • Loading branch information
link2xt committed Aug 28, 2024
1 parent cfe0752 commit 1f29e77
Show file tree
Hide file tree
Showing 5 changed files with 16 additions and 184 deletions.
61 changes: 1 addition & 60 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

4 changes: 1 addition & 3 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -83,7 +83,6 @@ quick-xml = "0.36"
quoted_printable = "0.5"
rand = { workspace = true }
regex = { workspace = true }
reqwest = { version = "0.12.5", features = ["json"] }
rusqlite = { workspace = true, features = ["sqlcipher"] }
rust-hsluv = "0.1"
sanitize-filename = { workspace = true }
Expand Down Expand Up @@ -199,8 +198,7 @@ default = ["vendored"]
internals = []
vendored = [
"async-native-tls/vendored",
"rusqlite/bundled-sqlcipher-vendored-openssl",
"reqwest/native-tls-vendored"
"rusqlite/bundled-sqlcipher-vendored-openssl"
]

[lints.rust]
Expand Down
7 changes: 0 additions & 7 deletions src/net.rs
Original file line number Diff line number Diff line change
Expand Up @@ -28,13 +28,6 @@ use tls::wrap_tls;
/// This constant should be more than the largest expected RTT.
pub(crate) const TIMEOUT: Duration = Duration::from_secs(60);

/// Transaction timeout, e.g. for a GET or POST request
/// together with all connection attempts.
///
/// This is the worst case time user has to wait on a very slow network
/// after clicking a button and before getting an error message.
pub(crate) const TRANSACTION_TIMEOUT: Duration = Duration::from_secs(300);

/// TTL for caches in seconds.
pub(crate) const CACHE_TTL: u64 = 30 * 24 * 60 * 60;

Expand Down
113 changes: 14 additions & 99 deletions src/net/http.rs
Original file line number Diff line number Diff line change
@@ -1,27 +1,15 @@
//! # HTTP module.
use std::sync::Arc;

use anyhow::{anyhow, bail, Context as _, Result};
use bytes::Bytes;
use http_body_util::BodyExt;
use hyper_util::rt::TokioIo;
use mime::Mime;
use once_cell::sync::Lazy;
use serde::Serialize;

use crate::context::Context;
use crate::net::lookup_host_with_cache;
use crate::net::session::SessionStream;
use crate::net::tls::wrap_tls;
use crate::socks::Socks5Config;

static LETSENCRYPT_ROOT: Lazy<reqwest::tls::Certificate> = Lazy::new(|| {
reqwest::tls::Certificate::from_der(include_bytes!(
"../../assets/root-certificates/letsencrypt/isrgrootx1.der"
))
.unwrap()
});

/// HTTP(S) GET response.
#[derive(Debug)]
Expand Down Expand Up @@ -145,86 +133,6 @@ pub async fn read_url_blob(context: &Context, url: &str) -> Result<Response> {
Err(anyhow!("Followed 10 redirections"))
}

struct CustomResolver {
context: Context,

/// Whether to return cached results or not.
/// If resolver can be used for URLs
/// without TLS, e.g. HTTP URLs from HTML email,
/// this must be false. If TLS is used
/// and certificate hostnames are checked,
/// it is safe to load cache.
load_cache: bool,
}

impl CustomResolver {
fn new(context: Context, load_cache: bool) -> Self {
Self {
context,
load_cache,
}
}
}

impl reqwest::dns::Resolve for CustomResolver {
fn resolve(&self, hostname: reqwest::dns::Name) -> reqwest::dns::Resolving {
let context = self.context.clone();
let load_cache = self.load_cache;
Box::pin(async move {
let port = 443; // Actual port does not matter.

let socket_addrs =
lookup_host_with_cache(&context, hostname.as_str(), port, "", load_cache).await;
match socket_addrs {
Ok(socket_addrs) => {
let addrs: reqwest::dns::Addrs = Box::new(socket_addrs.into_iter());

Ok(addrs)
}
Err(err) => Err(err.into()),
}
})
}
}

async fn get_client(context: &Context, load_cache: bool) -> Result<reqwest::Client> {
let socks5_config = Socks5Config::from_database(&context.sql).await?;
let resolver = Arc::new(CustomResolver::new(context.clone(), load_cache));

// `reqwest` uses `hyper-util` crate internally which implements
// [Happy Eyeballs](https://datatracker.ietf.org/doc/html/rfc6555) algorithm.
// On a dual-stack host it starts IPv4 connection attempts in parallel
// to IPv6 connection attempts after 300 ms.
// In the worst case of all connection attempts
// timing out this allows to try four IPv6 and four IPv4
// addresses before request expires
// if request timeout is set to 5 minutes
// and connection timeout is set to 1 minute.
//
// We do not set write timeout because `reqwest`
// does not support it, but request timeout
// should prevent deadlocks if the server
// does not read the data.
let builder = reqwest::ClientBuilder::new()
.connect_timeout(super::TIMEOUT)
.read_timeout(super::TIMEOUT)
.timeout(super::TRANSACTION_TIMEOUT)
.add_root_certificate(LETSENCRYPT_ROOT.clone())
.dns_resolver(resolver);

let builder = if let Some(socks5_config) = socks5_config {
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()?)
}

/// Sends an empty POST request to the URL.
///
/// Follows redirections.
Expand Down Expand Up @@ -281,13 +189,20 @@ pub(crate) async fn post_empty(context: &Context, url: &str) -> Result<(String,
/// Returns true if successful HTTP response code was returned.
#[allow(dead_code)]
pub(crate) async fn post_string(context: &Context, url: &str, body: String) -> Result<bool> {
let load_cache = true;
let response = get_client(context, load_cache)
.await?
.post(url)
.body(body)
.send()
.await?;
let parsed_url = url
.parse::<hyper::Uri>()
.with_context(|| format!("Failed to parse URL {url:?}"))?;

let mut sender = get_http_sender(context, parsed_url.clone()).await?;
let authority = parsed_url
.authority()
.context("URL has no authority")?
.clone();

let request = hyper::Request::post(parsed_url.path())
.header(hyper::header::HOST, authority.as_str())
.body(body)?;
let response = sender.send_request(request).await?;

Ok(response.status().is_success())
}
Expand Down
15 changes: 0 additions & 15 deletions src/socks.rs
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,6 @@ use fast_socks5::client::{Config, Socks5Stream};
use fast_socks5::util::target_addr::ToTargetAddr;
use fast_socks5::AuthenticationMethod;
use fast_socks5::Socks5Command;
use percent_encoding::{utf8_percent_encode, NON_ALPHANUMERIC};
use tokio::net::TcpStream;
use tokio_io_timeout::TimeoutStream;

Expand Down Expand Up @@ -54,20 +53,6 @@ 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.
/// Use this only if the connection is going to be protected with TLS checks.
pub async fn connect(
Expand Down

0 comments on commit 1f29e77

Please sign in to comment.