mirror of
https://github.com/mountain-loop/yaak.git
synced 2026-09-15 14:21:53 +02:00
Fix responses with more than 100 headers failing on HTTP/1 (#658)
This commit is contained in:
Generated
+34
-74
@@ -685,6 +685,12 @@ version = "0.22.1"
|
|||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "72b3254f16251a8381aa12e40e3c4d2f0199f8c6508fbecb9d91f575e0fbb8c6"
|
checksum = "72b3254f16251a8381aa12e40e3c4d2f0199f8c6508fbecb9d91f575e0fbb8c6"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "base64"
|
||||||
|
version = "0.23.1"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "ac07cdecf99051d9a5238b80f35af32cdeba5b336e55d957b318b50137e18da5"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "base64-simd"
|
name = "base64-simd"
|
||||||
version = "0.8.0"
|
version = "0.8.0"
|
||||||
@@ -2942,7 +2948,7 @@ dependencies = [
|
|||||||
"libc",
|
"libc",
|
||||||
"libgit2-sys",
|
"libgit2-sys",
|
||||||
"log 0.4.29",
|
"log 0.4.29",
|
||||||
"openssl-probe",
|
"openssl-probe 0.1.6",
|
||||||
"openssl-sys",
|
"openssl-sys",
|
||||||
"url",
|
"url",
|
||||||
]
|
]
|
||||||
@@ -4471,17 +4477,17 @@ dependencies = [
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "native-tls"
|
name = "native-tls"
|
||||||
version = "0.2.14"
|
version = "0.2.18"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "87de3442987e9dbec73158d5c715e7ad9072fda936bb03d19d7fa10e00520f0e"
|
checksum = "465500e14ea162429d264d44189adc38b199b62b1c21eea9f69e4b73cb03bbf2"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"libc",
|
"libc",
|
||||||
"log 0.4.29",
|
"log 0.4.29",
|
||||||
"openssl",
|
"openssl",
|
||||||
"openssl-probe",
|
"openssl-probe 0.2.1",
|
||||||
"openssl-sys",
|
"openssl-sys",
|
||||||
"schannel",
|
"schannel",
|
||||||
"security-framework 2.11.1",
|
"security-framework 3.5.1",
|
||||||
"security-framework-sys",
|
"security-framework-sys",
|
||||||
"tempfile",
|
"tempfile",
|
||||||
]
|
]
|
||||||
@@ -5055,6 +5061,12 @@ version = "0.1.6"
|
|||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "d05e27ee213611ffe7d6348b942e8f942b37114c00cc03cec254295a4a17852e"
|
checksum = "d05e27ee213611ffe7d6348b942e8f942b37114c00cc03cec254295a4a17852e"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "openssl-probe"
|
||||||
|
version = "0.2.1"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "7c87def4c32ab89d880effc9e097653c8da5d6ef28e6b539d313baaacfbafcbe"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "openssl-src"
|
name = "openssl-src"
|
||||||
version = "300.6.1+3.6.3"
|
version = "300.6.1+3.6.3"
|
||||||
@@ -6717,12 +6729,11 @@ dependencies = [
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "reqwest"
|
name = "reqwest"
|
||||||
version = "0.12.20"
|
version = "0.13.5"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "eabf4c97d9130e2bf606614eb937e86edac8292eaa6f422f995d7e8de1eb1813"
|
checksum = "16a1cfa75cc186dd73d5818e510e042e40927bccc9c236b061cea97e1eb08029"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"async-compression",
|
"base64 0.23.1",
|
||||||
"base64 0.22.1",
|
|
||||||
"bytes",
|
"bytes",
|
||||||
"encoding_rs",
|
"encoding_rs",
|
||||||
"futures-core",
|
"futures-core",
|
||||||
@@ -6744,6 +6755,7 @@ dependencies = [
|
|||||||
"pin-project-lite",
|
"pin-project-lite",
|
||||||
"rustls",
|
"rustls",
|
||||||
"rustls-pki-types",
|
"rustls-pki-types",
|
||||||
|
"rustls-platform-verifier",
|
||||||
"serde",
|
"serde",
|
||||||
"serde_json",
|
"serde_json",
|
||||||
"serde_urlencoded",
|
"serde_urlencoded",
|
||||||
@@ -6758,46 +6770,7 @@ dependencies = [
|
|||||||
"url",
|
"url",
|
||||||
"wasm-bindgen",
|
"wasm-bindgen",
|
||||||
"wasm-bindgen-futures",
|
"wasm-bindgen-futures",
|
||||||
"wasm-streams 0.4.2",
|
"wasm-streams",
|
||||||
"web-sys",
|
|
||||||
]
|
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "reqwest"
|
|
||||||
version = "0.13.3"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "62e0021ea2c22aed41653bc7e1419abb2c97e038ff2c33d0e1309e49a97deec0"
|
|
||||||
dependencies = [
|
|
||||||
"base64 0.22.1",
|
|
||||||
"bytes",
|
|
||||||
"futures-core",
|
|
||||||
"futures-util",
|
|
||||||
"http",
|
|
||||||
"http-body",
|
|
||||||
"http-body-util",
|
|
||||||
"hyper",
|
|
||||||
"hyper-rustls",
|
|
||||||
"hyper-util",
|
|
||||||
"js-sys",
|
|
||||||
"log 0.4.29",
|
|
||||||
"percent-encoding",
|
|
||||||
"pin-project-lite",
|
|
||||||
"rustls",
|
|
||||||
"rustls-pki-types",
|
|
||||||
"rustls-platform-verifier",
|
|
||||||
"serde",
|
|
||||||
"serde_json",
|
|
||||||
"sync_wrapper",
|
|
||||||
"tokio",
|
|
||||||
"tokio-rustls",
|
|
||||||
"tokio-util",
|
|
||||||
"tower 0.5.2",
|
|
||||||
"tower-http",
|
|
||||||
"tower-service",
|
|
||||||
"url",
|
|
||||||
"wasm-bindgen",
|
|
||||||
"wasm-bindgen-futures",
|
|
||||||
"wasm-streams 0.5.0",
|
|
||||||
"web-sys",
|
"web-sys",
|
||||||
]
|
]
|
||||||
|
|
||||||
@@ -7431,7 +7404,7 @@ version = "0.8.1"
|
|||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "7fcff2dd52b58a8d98a70243663a0d234c4e2b79235637849d15913394a247d3"
|
checksum = "7fcff2dd52b58a8d98a70243663a0d234c4e2b79235637849d15913394a247d3"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"openssl-probe",
|
"openssl-probe 0.1.6",
|
||||||
"rustls-pki-types",
|
"rustls-pki-types",
|
||||||
"schannel",
|
"schannel",
|
||||||
"security-framework 3.5.1",
|
"security-framework 3.5.1",
|
||||||
@@ -7517,11 +7490,11 @@ dependencies = [
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "schannel"
|
name = "schannel"
|
||||||
version = "0.1.27"
|
version = "0.1.29"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "1f29ebaa345f945cec9fbbc532eb307f0fdad8161f281b6369539c8d84876b3d"
|
checksum = "91c1b7e4904c873ef0710c1f407dde2e6287de2bebc1bbbf7d430bb7cbffd939"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"windows-sys 0.59.0",
|
"windows-sys 0.61.2",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
@@ -8596,7 +8569,7 @@ dependencies = [
|
|||||||
"percent-encoding",
|
"percent-encoding",
|
||||||
"plist",
|
"plist",
|
||||||
"raw-window-handle",
|
"raw-window-handle",
|
||||||
"reqwest 0.13.3",
|
"reqwest",
|
||||||
"serde",
|
"serde",
|
||||||
"serde_json",
|
"serde_json",
|
||||||
"serde_repr",
|
"serde_repr",
|
||||||
@@ -8886,7 +8859,7 @@ dependencies = [
|
|||||||
"minisign-verify",
|
"minisign-verify",
|
||||||
"osakit",
|
"osakit",
|
||||||
"percent-encoding",
|
"percent-encoding",
|
||||||
"reqwest 0.13.3",
|
"reqwest",
|
||||||
"rustls",
|
"rustls",
|
||||||
"semver",
|
"semver",
|
||||||
"serde",
|
"serde",
|
||||||
@@ -10204,19 +10177,6 @@ dependencies = [
|
|||||||
"unicode-ident",
|
"unicode-ident",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "wasm-streams"
|
|
||||||
version = "0.4.2"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "15053d8d85c7eccdbefef60f06769760a563c7f0a9d6902a13d35c7800b0ad65"
|
|
||||||
dependencies = [
|
|
||||||
"futures-util",
|
|
||||||
"js-sys",
|
|
||||||
"wasm-bindgen",
|
|
||||||
"wasm-bindgen-futures",
|
|
||||||
"web-sys",
|
|
||||||
]
|
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "wasm-streams"
|
name = "wasm-streams"
|
||||||
version = "0.5.0"
|
version = "0.5.0"
|
||||||
@@ -11240,7 +11200,7 @@ name = "yaak-api"
|
|||||||
version = "0.1.0"
|
version = "0.1.0"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"log 0.4.29",
|
"log 0.4.29",
|
||||||
"reqwest 0.12.20",
|
"reqwest",
|
||||||
"sysproxy",
|
"sysproxy",
|
||||||
"thiserror 2.0.17",
|
"thiserror 2.0.17",
|
||||||
"yaak-common",
|
"yaak-common",
|
||||||
@@ -11265,7 +11225,7 @@ dependencies = [
|
|||||||
"r2d2",
|
"r2d2",
|
||||||
"r2d2_sqlite",
|
"r2d2_sqlite",
|
||||||
"rand 0.9.5",
|
"rand 0.9.5",
|
||||||
"reqwest 0.12.20",
|
"reqwest",
|
||||||
"rlimit",
|
"rlimit",
|
||||||
"serde",
|
"serde",
|
||||||
"serde_json",
|
"serde_json",
|
||||||
@@ -11351,7 +11311,7 @@ dependencies = [
|
|||||||
"oxc_resolver",
|
"oxc_resolver",
|
||||||
"predicates",
|
"predicates",
|
||||||
"rand 0.8.7",
|
"rand 0.8.7",
|
||||||
"reqwest 0.12.20",
|
"reqwest",
|
||||||
"rolldown",
|
"rolldown",
|
||||||
"schemars 0.8.22",
|
"schemars 0.8.22",
|
||||||
"serde",
|
"serde",
|
||||||
@@ -11515,7 +11475,7 @@ dependencies = [
|
|||||||
"log 0.4.29",
|
"log 0.4.29",
|
||||||
"mime_guess",
|
"mime_guess",
|
||||||
"native-tls",
|
"native-tls",
|
||||||
"reqwest 0.12.20",
|
"reqwest",
|
||||||
"serde",
|
"serde",
|
||||||
"serde_json",
|
"serde_json",
|
||||||
"tempfile",
|
"tempfile",
|
||||||
@@ -11538,7 +11498,7 @@ version = "0.1.0"
|
|||||||
dependencies = [
|
dependencies = [
|
||||||
"chrono",
|
"chrono",
|
||||||
"log 0.4.29",
|
"log 0.4.29",
|
||||||
"reqwest 0.12.20",
|
"reqwest",
|
||||||
"serde",
|
"serde",
|
||||||
"serde_json",
|
"serde_json",
|
||||||
"tauri",
|
"tauri",
|
||||||
@@ -11611,7 +11571,7 @@ dependencies = [
|
|||||||
"md5 0.7.0",
|
"md5 0.7.0",
|
||||||
"path-slash",
|
"path-slash",
|
||||||
"rand 0.9.5",
|
"rand 0.9.5",
|
||||||
"reqwest 0.12.20",
|
"reqwest",
|
||||||
"serde",
|
"serde",
|
||||||
"serde_json",
|
"serde_json",
|
||||||
"sha2",
|
"sha2",
|
||||||
|
|||||||
+8
-1
@@ -47,7 +47,14 @@ chrono = "0.4.42"
|
|||||||
hex = "0.4.3"
|
hex = "0.4.3"
|
||||||
keyring = "3.6.3"
|
keyring = "3.6.3"
|
||||||
log = "0.4.29"
|
log = "0.4.29"
|
||||||
reqwest = "0.12.20"
|
reqwest = { version = "0.13.5", default-features = false, features = [
|
||||||
|
# reqwest 0.13 switched `default-tls` from native-tls to rustls; keep the
|
||||||
|
# rest of the old default set and stay on native-tls.
|
||||||
|
"charset",
|
||||||
|
"http2",
|
||||||
|
"system-proxy",
|
||||||
|
"native-tls",
|
||||||
|
] }
|
||||||
rustls = { version = "0.23.34", default-features = false }
|
rustls = { version = "0.23.34", default-features = false }
|
||||||
rustls-platform-verifier = "0.6.2"
|
rustls-platform-verifier = "0.6.2"
|
||||||
schemars = { version = "0.8.22", features = ["chrono"] }
|
schemars = { version = "0.8.22", features = ["chrono"] }
|
||||||
|
|||||||
@@ -22,7 +22,7 @@ include_dir = "0.7"
|
|||||||
keyring = { workspace = true, features = ["apple-native", "windows-native", "sync-secret-service"] }
|
keyring = { workspace = true, features = ["apple-native", "windows-native", "sync-secret-service"] }
|
||||||
log = { workspace = true }
|
log = { workspace = true }
|
||||||
rand = "0.8"
|
rand = "0.8"
|
||||||
reqwest = { workspace = true }
|
reqwest = { workspace = true, features = ["form"] }
|
||||||
rolldown = "0.1.0"
|
rolldown = "0.1.0"
|
||||||
oxc_resolver = "=11.10.0"
|
oxc_resolver = "=11.10.0"
|
||||||
schemars = { workspace = true }
|
schemars = { workspace = true }
|
||||||
|
|||||||
@@ -48,7 +48,8 @@ reqwest = { workspace = true, features = [
|
|||||||
"brotli",
|
"brotli",
|
||||||
"deflate",
|
"deflate",
|
||||||
"json",
|
"json",
|
||||||
"rustls-tls-manual-roots-no-provider",
|
"query",
|
||||||
|
"rustls-no-provider",
|
||||||
"socks",
|
"socks",
|
||||||
"http2",
|
"http2",
|
||||||
] }
|
] }
|
||||||
|
|||||||
@@ -20,9 +20,8 @@ log = { workspace = true }
|
|||||||
mime_guess = "2.0.5"
|
mime_guess = "2.0.5"
|
||||||
native-tls = { version = "0.2", features = ["alpn"] }
|
native-tls = { version = "0.2", features = ["alpn"] }
|
||||||
reqwest = { workspace = true, features = [
|
reqwest = { workspace = true, features = [
|
||||||
"rustls-tls-manual-roots-no-provider",
|
"rustls-no-provider",
|
||||||
"native-tls",
|
"native-tls",
|
||||||
"native-tls-alpn",
|
|
||||||
"socks",
|
"socks",
|
||||||
"http2",
|
"http2",
|
||||||
"stream",
|
"stream",
|
||||||
@@ -41,3 +40,4 @@ yaak-tls = { workspace = true }
|
|||||||
|
|
||||||
[dev-dependencies]
|
[dev-dependencies]
|
||||||
tempfile = "3"
|
tempfile = "3"
|
||||||
|
tokio = { workspace = true, features = ["net"] }
|
||||||
|
|||||||
@@ -10,8 +10,14 @@ use yaak_tls::{
|
|||||||
|
|
||||||
pub const HTTP2_MAX_RESPONSE_HEADER_LIST_SIZE: u32 = 1024 * 1024;
|
pub const HTTP2_MAX_RESPONSE_HEADER_LIST_SIZE: u32 = 1024 * 1024;
|
||||||
|
|
||||||
|
/// hyper caps HTTP/1 responses at 100 headers, which real servers exceed.
|
||||||
|
/// https://yaak.app/feedback/posts/when-response-headers-exceed-a-certain-count-hyper-throws-error
|
||||||
|
pub const HTTP1_MAX_RESPONSE_HEADERS: usize = 1024;
|
||||||
|
|
||||||
fn client_builder() -> ClientBuilder {
|
fn client_builder() -> ClientBuilder {
|
||||||
Client::builder().http2_max_header_list_size(HTTP2_MAX_RESPONSE_HEADER_LIST_SIZE)
|
Client::builder()
|
||||||
|
.http2_max_header_list_size(HTTP2_MAX_RESPONSE_HEADER_LIST_SIZE)
|
||||||
|
.http1_max_headers(HTTP1_MAX_RESPONSE_HEADERS)
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Clone)]
|
#[derive(Clone)]
|
||||||
@@ -399,3 +405,100 @@ UFNAXPsoutUompC1Z57bKA6OKM2hRANCAATCYYKhzgHEaRaGsYVjJSoXvoroL8qe
|
|||||||
assert!(build_native_tls_identity(Some(empty)).unwrap().is_none());
|
assert!(build_native_tls_identity(Some(empty)).unwrap().is_none());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[cfg(test)]
|
||||||
|
mod header_limit_tests {
|
||||||
|
use super::*;
|
||||||
|
use tokio::io::{AsyncReadExt, AsyncWriteExt};
|
||||||
|
use tokio::net::TcpListener;
|
||||||
|
|
||||||
|
/// Accepts one connection and replies with a response head of exactly
|
||||||
|
/// `total_headers` fields. The trailing `Content-Length` is one of them,
|
||||||
|
/// because hyper counts every field against its limit.
|
||||||
|
async fn serve_response_with_header_count(total_headers: usize) -> String {
|
||||||
|
let listener = TcpListener::bind("127.0.0.1:0").await.unwrap();
|
||||||
|
let addr = listener.local_addr().unwrap();
|
||||||
|
|
||||||
|
tokio::spawn(async move {
|
||||||
|
let (mut socket, _) = listener.accept().await.unwrap();
|
||||||
|
|
||||||
|
// Read the request head so the client is not writing into a closed socket
|
||||||
|
let mut buf = [0u8; 4096];
|
||||||
|
loop {
|
||||||
|
let n = socket.read(&mut buf).await.unwrap();
|
||||||
|
if n == 0 || buf[..n].windows(4).any(|w| w == b"\r\n\r\n") {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
let mut response = String::from("HTTP/1.1 200 OK\r\n");
|
||||||
|
for i in 0..total_headers - 1 {
|
||||||
|
response.push_str(&format!("X-Test-{i}: value-{i}\r\n"));
|
||||||
|
}
|
||||||
|
response.push_str("Content-Length: 0\r\n\r\n");
|
||||||
|
|
||||||
|
// A response the client rejects mid-parse closes the socket under
|
||||||
|
// us, so a failed write here is an expected outcome, not a fault
|
||||||
|
let _ = socket.write_all(response.as_bytes()).await;
|
||||||
|
let _ = socket.flush().await;
|
||||||
|
});
|
||||||
|
|
||||||
|
format!("http://{addr}/")
|
||||||
|
}
|
||||||
|
|
||||||
|
fn options() -> HttpConnectionOptions {
|
||||||
|
HttpConnectionOptions {
|
||||||
|
id: "test".to_string(),
|
||||||
|
validate_certificates: true,
|
||||||
|
http_version: HttpVersion::Http1,
|
||||||
|
proxy: HttpConnectionProxySetting::Disabled,
|
||||||
|
client_certificate: None,
|
||||||
|
dns_overrides: Vec::new(),
|
||||||
|
address_filter: None,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// hyper defaults to 100 header fields per HTTP/1 response and fails the
|
||||||
|
/// whole request past that, which is what users hit in the field.
|
||||||
|
#[tokio::test]
|
||||||
|
async fn responses_with_more_than_100_headers_are_accepted() {
|
||||||
|
let url = serve_response_with_header_count(150).await;
|
||||||
|
let (client, _resolver) = options().build_client().unwrap();
|
||||||
|
|
||||||
|
let response = client
|
||||||
|
.inner()
|
||||||
|
.get(&url)
|
||||||
|
.send()
|
||||||
|
.await
|
||||||
|
.expect("request with 150 response headers should succeed");
|
||||||
|
|
||||||
|
assert_eq!(response.status(), 200);
|
||||||
|
assert_eq!(response.headers().len(), 150);
|
||||||
|
for i in 0..149 {
|
||||||
|
assert_eq!(
|
||||||
|
response.headers().get(format!("x-test-{i}")).unwrap(),
|
||||||
|
format!("value-{i}").as_str(),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Pins both sides of the boundary. The sizes are written out rather than
|
||||||
|
/// derived from `HTTP1_MAX_RESPONSE_HEADERS`, so retuning the limit trips
|
||||||
|
/// this test instead of silently moving with it.
|
||||||
|
#[tokio::test]
|
||||||
|
async fn the_limit_is_exactly_1024_header_fields() {
|
||||||
|
let (client, _resolver) = options().build_client().unwrap();
|
||||||
|
|
||||||
|
let at_limit = serve_response_with_header_count(1024).await;
|
||||||
|
let response = client
|
||||||
|
.inner()
|
||||||
|
.get(&at_limit)
|
||||||
|
.send()
|
||||||
|
.await
|
||||||
|
.expect("a response with 1024 header fields should succeed");
|
||||||
|
assert_eq!(response.headers().len(), 1024);
|
||||||
|
|
||||||
|
let over_limit = serve_response_with_header_count(1025).await;
|
||||||
|
assert!(client.inner().get(&over_limit).send().await.is_err());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user