Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat(tls): Add preconfigured TLS settings #118

Merged
merged 6 commits into from
Aug 13, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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
Loading