mirror of
https://github.com/lexmount/moli.git
synced 2026-09-25 08:01:28 +00:00
fix(fetch): request identity encoding for HTTP ranges
This commit is contained in:
@@ -6096,6 +6096,8 @@ fetch/content-length/content-length.html
|
||||
fetch/nosniff/importscripts.html
|
||||
fetch/range/blob.any.js?moli-wpt-any=dedicatedworker
|
||||
fetch/range/blob.any.js?moli-wpt-any=window
|
||||
fetch/range/general.any.js?moli-wpt-any=dedicatedworker
|
||||
fetch/range/general.any.js?moli-wpt-any=window
|
||||
fetch/security/dangling-markup/media.html
|
||||
fetch/security/dangling-markup/option.html
|
||||
fetch/security/dangling-markup/textarea.html
|
||||
|
||||
@@ -197,6 +197,12 @@ fn outgoing_request_header_bytes_for_url(
|
||||
outgoing.push((name.clone(), value.clone()));
|
||||
}
|
||||
|
||||
// Fetch selects identity whenever Range is present, even with an invalid
|
||||
// value. Explicit embedder encoding preferences still take precedence.
|
||||
if header_present(&outgoing, "range") {
|
||||
append_header_if_missing(&mut outgoing, "Accept-Encoding", "identity".to_owned());
|
||||
}
|
||||
|
||||
if request.has_browser_identity_override() {
|
||||
append_encoded_header_if_missing(
|
||||
&mut outgoing,
|
||||
|
||||
@@ -357,3 +357,170 @@ async fn current_hop_headers_expire_before_redirect_method_rewrite_in_all_transp
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn accept_encoding_values(request: &[u8]) -> Vec<&str> {
|
||||
std::str::from_utf8(request)
|
||||
.unwrap()
|
||||
.lines()
|
||||
.filter_map(|line| {
|
||||
let (name, value) = line.split_once(':')?;
|
||||
name.eq_ignore_ascii_case("accept-encoding")
|
||||
.then_some(value.trim())
|
||||
})
|
||||
.collect()
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn range_requests_select_identity_in_every_transport() -> Result<()> {
|
||||
// Presence, including an empty or malformed value, selects identity.
|
||||
// Ordinary requests before and after must still negotiate compression.
|
||||
let ranges = [
|
||||
None,
|
||||
Some("bytes=0-10"),
|
||||
Some("foo=0-10"),
|
||||
Some("foo"),
|
||||
Some(""),
|
||||
Some("bytes=-3"),
|
||||
Some("bytes=0-1,4-5"),
|
||||
None,
|
||||
];
|
||||
let listener = TcpListener::bind("127.0.0.1:0").await?;
|
||||
let url = format!("http://{}/range", listener.local_addr()?);
|
||||
let server = tokio::spawn(serve_responses(
|
||||
listener,
|
||||
vec![response(b""); ranges.len() * 3],
|
||||
));
|
||||
let client = FetchClient::new(&FetchConfig::default(), new_shared_browser_cookie_store());
|
||||
for mode in ["buffered", "html", "raw"] {
|
||||
for range in ranges {
|
||||
let headers = range
|
||||
.map(|value| vec![("rAnGe".into(), value.into())])
|
||||
.unwrap_or_default();
|
||||
let recorder = NetworkObservationRecorder::default();
|
||||
let request = Request::new("GET", &url, None, headers)?
|
||||
.with_network_observation_recorder(recorder.clone());
|
||||
fetch_in_mode(&client, request.clone(), mode).await?;
|
||||
assert!(
|
||||
request
|
||||
.request_headers
|
||||
.iter()
|
||||
.all(|(name, _)| !name.eq_ignore_ascii_case("accept-encoding"))
|
||||
);
|
||||
if range.is_some() {
|
||||
let journal = recorder.snapshot();
|
||||
assert!(
|
||||
journal
|
||||
.final_request_observation()
|
||||
.unwrap()
|
||||
.headers()
|
||||
.iter()
|
||||
.any(|(name, value)| name.eq_ignore_ascii_case("accept-encoding")
|
||||
&& value == "identity"),
|
||||
"{mode}: {range:?}"
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
let requests = server.await??;
|
||||
assert_eq!(requests.len(), ranges.len() * 3);
|
||||
for (request, range) in requests.iter().zip(ranges.into_iter().cycle()) {
|
||||
let values = accept_encoding_values(request);
|
||||
if range.is_some() {
|
||||
assert_eq!(values, ["identity"], "{range:?}");
|
||||
} else {
|
||||
assert_eq!(values.len(), 1);
|
||||
assert!(values[0].contains("gzip"), "{values:?}");
|
||||
}
|
||||
}
|
||||
assert!(client.shutdown().is_clean());
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn range_requests_respect_native_header_configuration() -> Result<()> {
|
||||
for (defaults, headers, expected) in [
|
||||
(vec![("Range", "bytes=0-10")], vec![], "identity"),
|
||||
(
|
||||
vec![("aCcEpT-EnCoDiNg", "gzip")],
|
||||
vec![("Range", "bytes=0-10")],
|
||||
"gzip",
|
||||
),
|
||||
(
|
||||
vec![("Range", "bytes=0-10")],
|
||||
vec![("Accept-Encoding", "br")],
|
||||
"br",
|
||||
),
|
||||
(
|
||||
vec![("Range", "bytes=0-10")],
|
||||
vec![("Accept-Encoding", "")],
|
||||
"",
|
||||
),
|
||||
] {
|
||||
let listener = TcpListener::bind("127.0.0.1:0").await?;
|
||||
let url = format!("http://{}/configured", listener.local_addr()?);
|
||||
let server = tokio::spawn(serve_responses(listener, vec![response(b""); 3]));
|
||||
let mut config = FetchConfig::default();
|
||||
for (name, value) in defaults {
|
||||
config.push_default_request_header(name, value);
|
||||
}
|
||||
let client = FetchClient::new(&config, new_shared_browser_cookie_store());
|
||||
for mode in ["buffered", "html", "raw"] {
|
||||
let request = Request::new(
|
||||
"GET",
|
||||
&url,
|
||||
None,
|
||||
headers
|
||||
.iter()
|
||||
.map(|(n, v)| (n.to_string(), v.to_string()))
|
||||
.collect(),
|
||||
)?;
|
||||
fetch_in_mode(&client, request, mode).await?;
|
||||
}
|
||||
for request in server.await?? {
|
||||
assert_eq!(accept_encoding_values(&request), [expected]);
|
||||
}
|
||||
assert!(client.shutdown().is_clean());
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn range_redirects_keep_identity_and_response_decompression() -> Result<()> {
|
||||
// A native HTTP caller still receives decoded bytes if a server ignores
|
||||
// the requested encoding and sends a complete gzip response.
|
||||
let gzip = b"\x1f\x8b\x08\x00\x00\x00\x00\x00\x02\xff\x4b\x49\x4d\xce\x4f\x49\x4d\x01\x00\xf6\x9a\xf0\x1a\x07\x00\x00\x00";
|
||||
let compressed = [
|
||||
format!("HTTP/1.1 200 OK\r\nContent-Encoding: gzip\r\nContent-Length: {}\r\nConnection: close\r\n\r\n", gzip.len()).as_bytes(),
|
||||
gzip,
|
||||
].concat();
|
||||
let redirect = b"HTTP/1.1 307 Redirect\r\nLocation: /final\r\nContent-Length: 0\r\nConnection: close\r\n\r\n".to_vec();
|
||||
for mode in ["html", "raw"] {
|
||||
let listener = TcpListener::bind("127.0.0.1:0").await?;
|
||||
let url = format!("http://{}/start", listener.local_addr()?);
|
||||
let server = tokio::spawn(serve_responses(
|
||||
listener,
|
||||
vec![redirect.clone(), compressed.clone(), compressed.clone()],
|
||||
));
|
||||
let client = FetchClient::new(&FetchConfig::default(), new_shared_browser_cookie_store());
|
||||
let request = Request::new(
|
||||
"GET",
|
||||
&url,
|
||||
None,
|
||||
vec![("Range".into(), "bytes=0-10".into())],
|
||||
)?;
|
||||
let (head, body) = fetch_in_mode(&client, request, mode).await?;
|
||||
assert!(head.redirected);
|
||||
assert_eq!(head.final_url.path(), "/final");
|
||||
assert_eq!(body, b"decoded");
|
||||
let (_, body) = fetch_in_mode(&client, Request::get(&url)?, mode).await?;
|
||||
assert_eq!(body, b"decoded");
|
||||
let requests = server.await??;
|
||||
assert_eq!(requests.len(), 3);
|
||||
for request in &requests[..2] {
|
||||
assert_eq!(accept_encoding_values(request), ["identity"]);
|
||||
}
|
||||
assert!(accept_encoding_values(&requests[2])[0].contains("gzip"));
|
||||
assert!(client.shutdown().is_clean());
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user