Skip to content
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
2 changes: 1 addition & 1 deletion .github/workflows/build.yml
Original file line number Diff line number Diff line change
Expand Up @@ -138,7 +138,7 @@ jobs:

cargo fetch

# wreq 6.0.0-rc.29 swapped the TLS backend from boring2 to btls, so the
# wreq swapped the TLS backend from boring2 to btls, so the
# BoringSSL build script this patches now ships in btls-sys.
FILE=$(find ~/.cargo/registry/src -name "main.rs" | grep "btls-sys" | grep "build")

Expand Down
2 changes: 1 addition & 1 deletion .github/workflows/test.yml
Original file line number Diff line number Diff line change
Expand Up @@ -104,7 +104,7 @@ jobs:

cargo fetch

# wreq 6.0.0-rc.29 swapped the TLS backend from boring2 to btls, so the
# wreq swapped the TLS backend from boring2 to btls, so the
# BoringSSL build script this patches now ships in btls-sys.
FILE=$(find ~/.cargo/registry/src -name "main.rs" | grep "btls-sys" | grep "build")

Expand Down
12 changes: 6 additions & 6 deletions rust/Cargo.lock

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

6 changes: 3 additions & 3 deletions rust/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -10,8 +10,8 @@ crate-type = ["cdylib"]

[dependencies]
# HTTP client with browser impersonation
wreq = { version = "6.0.0-rc.29", default-features = false, features = ["cookies", "gzip", "brotli", "deflate", "zstd", "charset", "system-proxy", "socks", "ws", "stream", "webpki-roots", "tokio-rt"] }
wreq-util = { version = "3.0.0-rc.14", features = ["emulation-serde", "tokio-rt", "emulation-compression"] }
wreq = { version = "0.16.0", default-features = false, features = ["cookies", "gzip", "brotli", "deflate", "zstd", "charset", "system-proxy", "socks", "ws", "stream", "webpki-roots", "tokio-rt"] }
wreq-util = { version = "0.2.0", features = ["emulation-serde", "tokio-rt", "emulation-compression"] }
webpki-root-certs = "1.0.9"

# WebSocket support
Expand Down Expand Up @@ -44,7 +44,7 @@ uuid = { version = "1.23.4", features = ["v4"] }
[build-dependencies]
serde = { version = "1.0.229", features = ["derive"] }
serde_json = "1.0.151"
wreq-util = { version = "3.0.0-rc.14", features = ["emulation-serde", "tokio-rt", "emulation-compression"] }
wreq-util = { version = "0.2.0", features = ["emulation-serde", "tokio-rt", "emulation-compression"] }

[dev-dependencies]
btls = "0.5.6"
Expand Down
58 changes: 56 additions & 2 deletions rust/src/client.rs
Original file line number Diff line number Diff line change
Expand Up @@ -975,6 +975,42 @@ mod tests {

static ENV_LOCK: LazyLock<StdMutex<()>> = LazyLock::new(|| StdMutex::new(()));

#[test]
fn cookie_origin_uri_maps_websocket_schemes_to_http() {
let cases = [
(
"ws://127.0.0.1:8080/socket?a=1",
"http://127.0.0.1:8080/socket?a=1",
),
("wss://example.com/socket", "https://example.com/socket"),
("http://example.com/", "http://example.com/"),
("https://example.com/", "https://example.com/"),
];

for (input, expected) in cases {
let uri: wreq::Uri = input.parse().unwrap();
assert_eq!(cookie_origin_uri(&uri).to_string(), expected);
}
}

#[test]
fn websocket_uris_select_cookies_stored_for_the_http_origin() {
use wreq::cookie::CookieStore;

let jar = Jar::default();
jar.add("sessionCookie=jar; Path=/", "http://127.0.0.1:8080/");

let ws: wreq::Uri = "ws://127.0.0.1:8080/socket".parse().unwrap();
let cookies = jar.cookies(&cookie_origin_uri(&ws), wreq::Version::HTTP_11);

match cookies {
wreq::cookie::Cookies::Compressed(value) => {
assert_eq!(value.to_str().unwrap(), "sessionCookie=jar");
}
other => panic!("expected cookies for the ws origin, got {other:?}"),
}
}

fn base_request_options() -> RequestOptions {
RequestOptions {
url: "http://127.0.0.1".to_string(),
Expand Down Expand Up @@ -1150,6 +1186,24 @@ mod tests {
}
}

/// Map a WebSocket URI onto its HTTP origin for cookie-jar lookups.
///
/// wreq's cookie store follows RFC 6265 and only matches `http`/`https` URIs, so a `ws://` or
/// `wss://` URI would never select a cookie. Browsers scope WebSocket cookies to the equivalent
/// HTTP origin, so rewrite the scheme before consulting the jar. Non-WebSocket URIs pass through.
pub(crate) fn cookie_origin_uri(uri: &wreq::Uri) -> Cow<'_, wreq::Uri> {
// Dropping the leading "ws" turns ws://host into http://host and wss://host into https://host.
let rewritten = match uri.scheme_str() {
Some("ws") | Some("wss") => format!("http{}", &uri.to_string()["ws".len()..]),
_ => return Cow::Borrowed(uri),
};

match rewritten.parse() {
Ok(uri) => Cow::Owned(uri),
Err(_) => Cow::Borrowed(uri),
}
}

/// Get cookies from a session's jar that would be sent to the given URL
/// (RFC 6265 domain/path matching, secure filtering, expiry check).
pub fn get_session_cookies(session_id: &str, url: &str) -> Result<Vec<(String, String)>> {
Expand All @@ -1159,7 +1213,7 @@ pub fn get_session_cookies(session_id: &str, url: &str) -> Result<Vec<(String, S
let uri: wreq::Uri = url
.parse()
.with_context(|| format!("Invalid URL: {}", url))?;
let cookie_header = jar.cookies(&uri, wreq::Version::HTTP_11);
let cookie_header = jar.cookies(&cookie_origin_uri(&uri), wreq::Version::HTTP_11);

let pairs = match cookie_header {
wreq::cookie::Cookies::Compressed(header_value) => {
Expand Down Expand Up @@ -1255,7 +1309,7 @@ pub fn set_session_cookie(session_id: &str, name: &str, value: &str, url: &str)
.with_context(|| format!("Invalid URL: {}", url))?;

let jar = SESSION_MANAGER.jar_for(session_id)?;
jar.add(cookie, uri);
jar.add(cookie, cookie_origin_uri(&uri).into_owned());
Ok(())
}

Expand Down
4 changes: 2 additions & 2 deletions rust/src/custom_emulation.rs
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@ use wreq::{
};

use wreq_util::emulate::compress::{BrotliCompressor, ZlibCompressor, ZstdCompressor};
// wreq-util 3.0.0-rc.14 renamed these: the profile enum is now `Profile`, the OS enum is
// wreq-util renamed these: the profile enum is now `Profile`, the OS enum is
// `Platform`, and `EmulationOption` became `Emulation` (which collides with wreq's own
// `Emulation`, hence the aliases).
use wreq_util::{
Expand Down Expand Up @@ -267,7 +267,7 @@ pub fn resolve_custom_emulation(emulation_json: &str) -> Result<WreqEmulation> {
);
}

// wreq 6.0.0-rc.29 partitions the connection pool by `Group`, so two different custom
// wreq partitions the connection pool by `Group`, so two different custom
// emulations must not share one. Preset profiles get their group from wreq-util; derive
// ours from the config itself so identical configs pool together and different ones don't.
let mut group_hash = DefaultHasher::new();
Expand Down
5 changes: 3 additions & 2 deletions rust/src/websocket.rs
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ use wreq::header::OrigHeaderMap;
use wreq::ws::WebSocket;
use wreq::ws::message::{CloseCode, CloseFrame, Message};

use crate::client::{get_session_cookie_jar, get_transport_resolved};
use crate::client::{cookie_origin_uri, get_session_cookie_jar, get_transport_resolved};
use crate::custom_emulation::resolve_emulation;
use wreq_util::{Platform as BrowserEmulationOS, Profile as BrowserEmulation};

Expand Down Expand Up @@ -177,7 +177,8 @@ pub async fn connect_websocket_with_session(
// Extract cookies from the jar for this URL and inject as a Cookie header
let uri: wreq::Uri = url.parse().context("Failed to parse WebSocket URL")?;
// WebSocket upgrades go out over HTTP/1.1, which folds cookies into a single header.
let cookies = cookie_jar.cookies(&uri, wreq::Version::HTTP_11);
// The jar only matches http/https URIs, so look the cookies up against the HTTP origin.
let cookies = cookie_jar.cookies(&cookie_origin_uri(&uri), wreq::Version::HTTP_11);

let mut all_headers: Vec<(String, String)> = Vec::with_capacity(headers.len() + 1);
let mut cookie_segments: Vec<String> = Vec::new();
Expand Down