Skip to content

Commit

Permalink
feat(tls): Add preconfigured TLS settings (#118)
Browse files Browse the repository at this point in the history
  • Loading branch information
0x676e67 authored Aug 13, 2024
1 parent 5e48411 commit 440bbdf
Show file tree
Hide file tree
Showing 46 changed files with 1,020 additions and 885 deletions.
10 changes: 5 additions & 5 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -10,13 +10,13 @@ An fast asynchronous Rust `Http`/`WebSocket` Client with `TLS`/`JA3`/`JA4`/`HTTP

- `Async` or `blocking` Clients
- `Plain`, `JSON`, `urlencoded`, `multipart` bodies
- Customizable `headers` order
- Customizable `redirect` policy
- Headers Order
- Customizable redirect policy
- Cookie Store
- `HTTP`/`HTTPS`/`SOCKS5` Proxies
- `HTTPS`/`WebSocket` via [BoringSSL](https://github.com/cloudflare/boring)
- `JA3`/`JA4`/`HTTP2` fingerprint
- Impersonate `Chrome`/`Safari`/`Edge`/`OkHttp`
- `HTTPS`/`WebSocket` via BoringSSL
- Preconfigured `TLS`/`HTTP2` settings
- `Chrome`/`Safari`/`Edge`/`OkHttp` Fingerprint

Additional learning resources include:

Expand Down
60 changes: 60 additions & 0 deletions examples/pre_configured_tls.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,60 @@
use std::error::Error;

use boring::ssl::{SslConnector, SslMethod};
use http::HeaderValue;
use rquest::{
tls::{Http2FrameSettings, TlsExtensionSettings, TlsSettings, Version},
HttpVersionPref,
};
use rquest::{PseudoOrder, SettingsOrder, StreamDependency, StreamId};

#[tokio::main]
async fn main() -> Result<(), Box<dyn Error>> {
let settings = TlsSettings {
builder: SslConnector::builder(SslMethod::tls_client())?,
extension: TlsExtensionSettings {
tls_sni: true,
http_version_pref: HttpVersionPref::Http2,
min_tls_version: Some(Version::TLS_1_0),
max_tls_version: Some(Version::TLS_1_3),
application_settings: true,
pre_shared_key: true,
enable_ech_grease: true,
permute_extensions: true,
},
http2: Http2FrameSettings {
initial_stream_window_size: Some(6291456),
initial_connection_window_size: Some(15728640),
max_concurrent_streams: Some(1000),
max_header_list_size: Some(262144),
header_table_size: Some(65536),
enable_push: None,
headers_priority: Some(StreamDependency::new(StreamId::zero(), 255, true)),
headers_pseudo_order: Some([
PseudoOrder::Method,
PseudoOrder::Scheme,
PseudoOrder::Authority,
PseudoOrder::Path,
]),
settings_order: Some([
SettingsOrder::InitialWindowSize,
SettingsOrder::MaxConcurrentStreams,
]),
},
};

// Build a client with pre-configured TLS settings
let client = rquest::Client::builder()
.use_preconfigured_tls(settings, |headers| {
headers.insert("user-agent", HeaderValue::from_static("rquest"));
})
.enable_ech_grease()
.permute_extensions()
.build()?;

// Use the API you're already familiar with
let resp = client.get("https://tls.peet.ws/api/all").send().await?;
println!("{}", resp.text().await?);

Ok(())
}
75 changes: 47 additions & 28 deletions src/async_impl/client.rs
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,7 @@ use crate::error;
use crate::into_url::{expect_uri, try_uri};
use crate::redirect::{self, remove_sensitive_headers};
#[cfg(feature = "boring-tls")]
use crate::tls::{self, BoringTlsConnector, Impersonate, Tls};
use crate::tls::{self, BoringTlsConnector, Impersonate, Tls, TlsSettings};
use crate::{IntoUrl, Method, Proxy, StatusCode, Url};
use log::{debug, trace};

Expand Down Expand Up @@ -62,11 +62,15 @@ pub struct ClientBuilder {
config: Config,
}

/// A `HttpVersionPref` is used to set the HTTP version preference.
#[derive(Debug, Clone, Copy)]
pub enum HttpVersionPref {
/// Prefer HTTP/1.1
Http1,
/// Prefer HTTP/2
#[cfg(feature = "http2")]
Http2,
/// Prefer HTTP/1 and HTTP/2
All,
}

Expand Down Expand Up @@ -266,36 +270,51 @@ impl ClientBuilder {
/// This will set the necessary headers and TLS settings.
/// This is only available with the `boring-tls` feature.
#[cfg(feature = "boring-tls")]
pub fn impersonate(mut self, impersonate: Impersonate) -> ClientBuilder {
if let Ok(settings) = tls::tls_settings(impersonate, &mut self.config.headers) {
// Set the TLS settings
self.config.tls.builder = Some(settings.builder);
self.config.tls.extension = settings.extension;

// Set the http2 version preference
#[cfg(feature = "http2")]
{
return self
.http2_initial_stream_window_size(settings.http2.initial_stream_window_size)
.http2_initial_connection_window_size(
settings.http2.initial_connection_window_size,
)
.http2_max_concurrent_streams(settings.http2.max_concurrent_streams)
.http2_max_header_list_size(settings.http2.max_header_list_size)
.http2_header_table_size(settings.http2.header_table_size)
.http2_enable_push(settings.http2.enable_push)
.http2_headers_priority(settings.http2.headers_priority)
.http2_headers_pseudo_order(settings.http2.headers_pseudo_order)
.http2_settings_order(settings.http2.settings_order);
}
pub fn impersonate(self, impersonate: Impersonate) -> ClientBuilder {
// Try to get the settings for the impersonate version
if let Ok((settings, func)) = tls::tls_settings(impersonate) {
return self.apply_tls_settings(settings, func);
}
self
}

#[cfg(not(feature = "http2"))]
{
return self;
}
/// Use the preconfigured TLS settings.
#[cfg(feature = "boring-tls")]
pub fn use_preconfigured_tls<F>(self, settings: TlsSettings, func: F) -> ClientBuilder
where
F: FnOnce(&mut HeaderMap),
{
self.apply_tls_settings(settings, func)
}

/// Apply the given TLS settings and header function.
#[cfg(feature = "boring-tls")]
fn apply_tls_settings<F>(mut self, settings: TlsSettings, func: F) -> ClientBuilder
where
F: FnOnce(&mut HeaderMap),
{
func(&mut self.config.headers);
self.config.tls.builder = Some(settings.builder);
self.config.tls.extension = settings.extension;

// Set the http2 version preference
#[cfg(feature = "http2")]
{
self.http2_initial_stream_window_size(settings.http2.initial_stream_window_size)
.http2_initial_connection_window_size(settings.http2.initial_connection_window_size)
.http2_max_concurrent_streams(settings.http2.max_concurrent_streams)
.http2_max_header_list_size(settings.http2.max_header_list_size)
.http2_header_table_size(settings.http2.header_table_size)
.http2_enable_push(settings.http2.enable_push)
.http2_headers_priority(settings.http2.headers_priority)
.http2_headers_pseudo_order(settings.http2.headers_pseudo_order)
.http2_settings_order(settings.http2.settings_order)
}

self
#[cfg(not(feature = "http2"))]
{
self
}
}

/// Enable Encrypted Client Hello (Secure SNI)
Expand Down
19 changes: 14 additions & 5 deletions src/blocking/client.rs
Original file line number Diff line number Diff line change
Expand Up @@ -15,10 +15,10 @@ use super::request::{Request, RequestBuilder};
use super::response::Response;
use super::wait;
#[cfg(feature = "boring-tls")]
use crate::tls;
#[cfg(feature = "boring-tls")]
use crate::tls::Impersonate;
use crate::tls::{Impersonate, TlsSettings, Version};
use crate::{async_impl, header, redirect, IntoUrl, Method, Proxy};
#[cfg(feature = "boring-tls")]
use header::HeaderMap;
#[cfg(feature = "http2")]
use hyper::{PseudoOrder, SettingsOrder, StreamDependency};

Expand Down Expand Up @@ -107,6 +107,15 @@ impl ClientBuilder {
self.with_inner(move |inner| inner.impersonate(ver))
}

/// Use the preconfigured TLS settings.
#[cfg(feature = "boring-tls")]
pub fn use_preconfigured_tls<F>(self, settings: TlsSettings, func: F) -> ClientBuilder
where
F: FnOnce(&mut HeaderMap),
{
self.with_inner(move |inner| inner.use_preconfigured_tls(settings, func))
}

/// Enable Encrypted Client Hello (Secure SNI)
#[cfg_attr(docsrs, doc(cfg(feature = "boring-tls")))]
pub fn enable_ech_grease(self) -> ClientBuilder {
Expand Down Expand Up @@ -699,7 +708,7 @@ impl ClientBuilder {
///
/// feature to be enabled.
#[cfg(feature = "boring-tls")]
pub fn min_tls_version(self, version: tls::Version) -> ClientBuilder {
pub fn min_tls_version(self, version: Version) -> ClientBuilder {
self.with_inner(|inner| inner.min_tls_version(version))
}

Expand All @@ -718,7 +727,7 @@ impl ClientBuilder {
///
/// feature to be enabled.
#[cfg(feature = "boring-tls")]
pub fn max_tls_version(self, version: tls::Version) -> ClientBuilder {
pub fn max_tls_version(self, version: Version) -> ClientBuilder {
self.with_inner(|inner| inner.max_tls_version(version))
}

Expand Down
15 changes: 10 additions & 5 deletions src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -9,14 +9,14 @@
//!
//! - Async and [blocking] Clients
//! - Plain bodies, [JSON](#json), [urlencoded](#forms), [multipart], [websocket](#websocket)
//! - Customizable `headers` order
//! - Headers Order
//! - Customizable [redirect policy](#redirect-policies)
//! - Cookies Store
//! - HTTP [Proxies](#proxies)
//! - Uses BoringSSL [TLS](#tls)
//! - [Impersonate](#impersonate) Chrome / Safari / Edge / OkHttp
//! - Cookies
//! - `JA3`/`JA4`/`HTTP2` fingerprint
//! - Bespoke headers order configuration
//! - [Preconfigured][Preconfigured] `TLS`/`HTTP2` settings
//! - Chrome / Safari / Edge / OkHttp [Fingerprint](#impersonate)
//! - [Changelog](https://github.com/0x676e67/rquest/blob/main/CHANGELOG.md)
//!
//! Additional learning resources include:
Expand Down Expand Up @@ -253,6 +253,7 @@
//! [serde]: http://serde.rs
//! [redirect]: crate::redirect
//! [Proxy]: ./struct.Proxy.html
//! [preconfigured]: ./struct.ClientBuilder.html#method.use_preconfigured_tls
//! [cargo-features]: https://doc.rust-lang.org/stable/cargo/reference/manifest.html#the-features-section
/// Re-export of boring to keep versions in check
Expand Down Expand Up @@ -353,10 +354,14 @@ pub use self::async_impl::websocket::{
CloseCode, Message, WebSocket, WebSocketRequestBuilder, WebSocketResponse,
};
pub use self::async_impl::{
Body, Client, ClientBuilder, Request, RequestBuilder, Response, Upgraded,
client::HttpVersionPref, Body, Client, ClientBuilder, Request, RequestBuilder, Response,
Upgraded,
};
pub use self::proxy::{NoProxy, Proxy};

#[cfg(all(feature = "boring-tls", feature = "http2"))]
pub use hyper::{PseudoOrder, SettingsOrder, StreamDependency, StreamId};

mod async_impl;
#[cfg(feature = "blocking")]
pub mod blocking;
Expand Down
37 changes: 19 additions & 18 deletions src/tls/impersonate/chrome/v100.rs
Original file line number Diff line number Diff line change
Expand Up @@ -11,27 +11,28 @@ use http::{

pub(crate) fn get_settings(
settings: ImpersonateSettings,
headers: &mut HeaderMap,
) -> TlsResult<TlsSettings> {
init_headers(headers);
Ok(TlsSettings {
builder: ChromeTlsBuilder::new(&CIPHER_LIST)?,
extension: settings.extension,
http2: Http2FrameSettings {
initial_stream_window_size: Some(6291456),
initial_connection_window_size: Some(15728640),
max_concurrent_streams: Some(1000),
max_header_list_size: Some(262144),
header_table_size: Some(65536),
enable_push: None,
headers_priority: settings.headers_priority,
headers_pseudo_order: settings.headers_pseudo_order,
settings_order: settings.settings_order,
) -> TlsResult<(TlsSettings, impl FnOnce(&mut HeaderMap))> {
Ok((
TlsSettings {
builder: ChromeTlsBuilder::new(&CIPHER_LIST)?,
extension: settings.extension,
http2: Http2FrameSettings {
initial_stream_window_size: Some(6291456),
initial_connection_window_size: Some(15728640),
max_concurrent_streams: Some(1000),
max_header_list_size: Some(262144),
header_table_size: Some(65536),
enable_push: None,
headers_priority: settings.headers_priority,
headers_pseudo_order: settings.headers_pseudo_order,
settings_order: settings.settings_order,
},
},
})
header_initializer,
))
}

fn init_headers(headers: &mut HeaderMap) {
fn header_initializer(headers: &mut HeaderMap) {
headers.insert(
"sec-ch-ua",
HeaderValue::from_static(
Expand Down
37 changes: 19 additions & 18 deletions src/tls/impersonate/chrome/v101.rs
Original file line number Diff line number Diff line change
Expand Up @@ -11,27 +11,28 @@ use http::{

pub(crate) fn get_settings(
settings: ImpersonateSettings,
headers: &mut HeaderMap,
) -> TlsResult<TlsSettings> {
init_headers(headers);
Ok(TlsSettings {
builder: ChromeTlsBuilder::new(&CIPHER_LIST)?,
extension: settings.extension,
http2: Http2FrameSettings {
initial_stream_window_size: Some(6291456),
initial_connection_window_size: Some(15728640),
max_concurrent_streams: Some(1000),
max_header_list_size: Some(262144),
header_table_size: Some(65536),
enable_push: None,
headers_priority: settings.headers_priority,
headers_pseudo_order: settings.headers_pseudo_order,
settings_order: settings.settings_order,
) -> TlsResult<(TlsSettings, impl FnOnce(&mut HeaderMap))> {
Ok((
TlsSettings {
builder: ChromeTlsBuilder::new(&CIPHER_LIST)?,
extension: settings.extension,
http2: Http2FrameSettings {
initial_stream_window_size: Some(6291456),
initial_connection_window_size: Some(15728640),
max_concurrent_streams: Some(1000),
max_header_list_size: Some(262144),
header_table_size: Some(65536),
enable_push: None,
headers_priority: settings.headers_priority,
headers_pseudo_order: settings.headers_pseudo_order,
settings_order: settings.settings_order,
},
},
})
header_initializer,
))
}

fn init_headers(headers: &mut HeaderMap) {
fn header_initializer(headers: &mut HeaderMap) {
headers.insert(
"sec-ch-ua",
HeaderValue::from_static(
Expand Down
Loading

0 comments on commit 440bbdf

Please sign in to comment.