Skip to content

Commit f1ff4e5

Browse files
committed
Prepare 1.5.20 release gate
1 parent f8f8b68 commit f1ff4e5

5 files changed

Lines changed: 29 additions & 35 deletions

File tree

CHANGELOG.md

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -225,6 +225,10 @@ behavior when the change improves security or project direction.
225225

226226
### Fixed
227227

228+
- Harden the vendored HTTP/1 request parser to reject requests that carry both
229+
`Transfer-Encoding` and `Content-Length`, matching Fluxheim's raw
230+
request-framing release smoke and avoiding ambiguous downstream body
231+
framing.
228232
- Document the shared protocol HTTP token grammar as case-permissive and warn
229233
when accepted IPv6 trusted-proxy CIDR entries are broader than `/32`.
230234
- Fix cache-only builds after shared cache stats moved purge-index and activity

packaging/rpm/fluxheim.spec

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -155,6 +155,8 @@ fi
155155

156156
%changelog
157157
* Sat Jun 13 2026 Fluxheim Maintainers <1921261+eldryoth@users.noreply.github.com> - 1.5.20-1
158+
- Reject ambiguous HTTP/1 request framing with both Transfer-Encoding and
159+
Content-Length in the vendored request parser.
158160
- Allow provider IPv6 trusted-proxy ranges such as Cloudflare's
159161
2a06:98c0::/29 after the 1.5.19 config-crate split tightened validation too
160162
far.

release-notes/RELEASE_NOTES_1.5.20.md

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -220,6 +220,10 @@ and carries forward the post-1.5.19 trusted-proxy validation fix.
220220

221221
## Fixed
222222

223+
- Hardened the vendored HTTP/1 request parser to reject requests that carry
224+
both `Transfer-Encoding` and `Content-Length`, matching Fluxheim's raw
225+
request-framing release smoke and avoiding ambiguous downstream body
226+
framing.
223227
- Documented the shared protocol HTTP token grammar as case-permissive and
224228
added a warning when accepted IPv6 trusted-proxy CIDR entries are broader
225229
than `/32`.

scripts/smoke_request_framing.sh

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -161,9 +161,9 @@ cases = [
161161
{400, 411},
162162
),
163163
(
164-
"chunked_without_content_length",
164+
"valid_chunked_without_content_length",
165165
b"POST / HTTP/1.1\r\nHost: static.test\r\nTransfer-Encoding: chunked\r\nConnection: close\r\n\r\n0\r\n\r\n",
166-
{411},
166+
{404},
167167
),
168168
(
169169
"content_length_over_limit",

vendor/pingora-core/src/protocols/http/v1/server.rs

Lines changed: 17 additions & 33 deletions
Original file line numberDiff line numberDiff line change
@@ -260,16 +260,13 @@ impl HttpSession {
260260
let contains_content_length =
261261
request_header.headers.contains_key(CONTENT_LENGTH);
262262

263-
// Transfer encoding overrides content length, so when
264-
// both are present, we can remove content length. This
265-
// is per https://datatracker.ietf.org/doc/html/rfc9112#section-6.3
266-
//
267-
// RFC 9112 Section 6.1 (https://datatracker.ietf.org/doc/html/rfc9112#section-6.1-15)
268-
// also requires us to disable keepalive when both headers are present.
269263
let has_both_te_and_cl =
270264
contains_content_length && contains_transfer_encoding;
271265
if has_both_te_and_cl {
272-
request_header.remove_header(&CONTENT_LENGTH);
266+
return Error::e_explain(
267+
InvalidHTTPHeader,
268+
"request contains both Transfer-Encoding and Content-Length",
269+
);
273270
}
274271

275272
self.buf = buf;
@@ -279,10 +276,6 @@ impl HttpSession {
279276
self.response_written = None;
280277
self.respect_keepalive();
281278

282-
// Disable keepalive if both Transfer-Encoding and Content-Length were present
283-
if has_both_te_and_cl {
284-
self.set_keepalive(None);
285-
}
286279
self.validate_request()?;
287280

288281
return Ok(Some(s));
@@ -1662,18 +1655,24 @@ mod tests_stream {
16621655
.read(input2.as_bytes())
16631656
.build();
16641657
let mut http_stream = HttpSession::new(Box::new(mock_io));
1665-
let _ = http_stream.read_request().await.unwrap();
1658+
let read_result = http_stream.read_request().await;
16661659

16671660
match (content_length_header, transfer_encoding_header) {
1668-
(Some(_) | None, Some(_)) => {
1661+
(Some(_), Some(_)) => {
1662+
assert!(read_result.is_err());
1663+
}
1664+
(None, Some(_)) => {
1665+
read_result.unwrap();
16691666
assert!(http_stream.get_header(TRANSFER_ENCODING).is_some());
16701667
assert!(http_stream.get_header(CONTENT_LENGTH).is_none());
16711668
}
16721669
(Some(_), None) => {
1670+
read_result.unwrap();
16731671
assert!(http_stream.get_header(TRANSFER_ENCODING).is_none());
16741672
assert!(http_stream.get_header(CONTENT_LENGTH).is_some());
16751673
}
16761674
_ => {
1675+
read_result.unwrap();
16771676
assert!(http_stream.get_header(CONTENT_LENGTH).is_none());
16781677
assert!(http_stream.get_header(TRANSFER_ENCODING).is_none());
16791678
}
@@ -2505,10 +2504,7 @@ mod tests_stream {
25052504
}
25062505

25072506
#[tokio::test]
2508-
async fn test_te_and_cl_disables_keepalive() {
2509-
// When both Transfer-Encoding and Content-Length are present,
2510-
// we must disable keepalive per RFC 9112 Section 6.1
2511-
// https://datatracker.ietf.org/doc/html/rfc9112#section-6.1-15
2507+
async fn test_te_and_cl_is_rejected() {
25122508
let input = b"POST / HTTP/1.1\r\n\
25132509
Host: pingora.org\r\n\
25142510
Transfer-Encoding: chunked\r\n\
@@ -2520,22 +2516,10 @@ hello\r\n\
25202516
\r\n";
25212517
let mock_io = Builder::new().read(&input[..]).build();
25222518
let mut http_stream = HttpSession::new(Box::new(mock_io));
2523-
http_stream.read_request().await.unwrap();
2524-
2525-
// Keepalive should be disabled
2526-
assert_eq!(http_stream.keepalive_timeout, KeepaliveStatus::Off);
2527-
2528-
// Content-Length header should have been removed
2529-
assert!(!http_stream
2530-
.req_header()
2531-
.headers
2532-
.contains_key(CONTENT_LENGTH));
2533-
2534-
// Transfer-Encoding should still be present
2535-
assert!(http_stream
2536-
.req_header()
2537-
.headers
2538-
.contains_key(TRANSFER_ENCODING));
2519+
let error = http_stream.read_request().await.unwrap_err();
2520+
assert!(error
2521+
.to_string()
2522+
.contains("request contains both Transfer-Encoding and Content-Length"));
25392523
}
25402524

25412525
#[tokio::test]

0 commit comments

Comments
 (0)