Skip to content

Commit eef3576

Browse files
committed
Reject if the HTTP header contains duplicated Content-Length values.
1 parent e823044 commit eef3576

File tree

4 files changed

+77
-1
lines changed

4 files changed

+77
-1
lines changed

.bleep

+1-1
Original file line numberDiff line numberDiff line change
@@ -1 +1 @@
1-
c21de982c54c255ff1893d71795ec08cf1928ade
1+
595e312ee6c5ab80b34cae937ff818ae880eccd2

pingora-core/src/protocols/http/v1/client.rs

+15
Original file line numberDiff line numberDiff line change
@@ -166,6 +166,20 @@ impl HttpSession {
166166
Ok(res)
167167
}
168168

169+
// Validate the response header read. This function must be called after the response header
170+
// read.
171+
fn validate_response(&self) -> Result<()> {
172+
let resp_header = self
173+
.response_header
174+
.as_ref()
175+
.expect("response header must be read");
176+
177+
// ad-hoc checks
178+
super::common::check_dup_content_length(&resp_header.headers)?;
179+
180+
Ok(())
181+
}
182+
169183
/// Read the response header from the server
170184
/// This function can be called multiple times, if the headers received are just informational
171185
/// headers.
@@ -287,6 +301,7 @@ impl HttpSession {
287301
self.buf = buf;
288302
self.upgraded = self.is_upgrade(&response_header).unwrap_or(false);
289303
self.response_header = Some(response_header);
304+
self.validate_response()?;
290305
return Ok(s);
291306
}
292307
HeaderParseState::Partial => { /* continue the loop */ }

pingora-core/src/protocols/http/v1/common.rs

+49
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@
1616
1717
use http::{header, HeaderValue};
1818
use log::warn;
19+
use pingora_error::Result;
1920
use pingora_http::{HMap, RequestHeader, ResponseHeader};
2021
use std::str;
2122
use std::time::Duration;
@@ -243,3 +244,51 @@ pub(super) fn populate_headers(
243244
}
244245
used_header_index
245246
}
247+
248+
// RFC 7230:
249+
// If a message is received without Transfer-Encoding and with
250+
// either multiple Content-Length header fields having differing
251+
// field-values or a single Content-Length header field having an
252+
// invalid value, then the message framing is invalid and the
253+
// recipient MUST treat it as an unrecoverable error.
254+
pub(super) fn check_dup_content_length(headers: &HMap) -> Result<()> {
255+
if headers.get(header::TRANSFER_ENCODING).is_some() {
256+
// If TE header, ignore CL
257+
return Ok(());
258+
}
259+
let mut cls = headers.get_all(header::CONTENT_LENGTH).into_iter();
260+
if cls.next().is_none() {
261+
// no CL header is fine.
262+
return Ok(());
263+
}
264+
if cls.next().is_some() {
265+
// duplicated CL is bad
266+
return crate::Error::e_explain(
267+
crate::ErrorType::InvalidHTTPHeader,
268+
"duplicated Content-Length header",
269+
);
270+
}
271+
Ok(())
272+
}
273+
274+
#[cfg(test)]
275+
mod test {
276+
use super::*;
277+
use http::header::{CONTENT_LENGTH, TRANSFER_ENCODING};
278+
279+
#[test]
280+
fn test_check_dup_content_length() {
281+
let mut headers = HMap::new();
282+
283+
assert!(check_dup_content_length(&headers).is_ok());
284+
285+
headers.append(CONTENT_LENGTH, "1".try_into().unwrap());
286+
assert!(check_dup_content_length(&headers).is_ok());
287+
288+
headers.append(CONTENT_LENGTH, "2".try_into().unwrap());
289+
assert!(check_dup_content_length(&headers).is_err());
290+
291+
headers.append(TRANSFER_ENCODING, "chunkeds".try_into().unwrap());
292+
assert!(check_dup_content_length(&headers).is_ok());
293+
}
294+
}

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

+12
Original file line numberDiff line numberDiff line change
@@ -247,6 +247,7 @@ impl HttpSession {
247247
self.body_reader.reinit();
248248
self.response_written = None;
249249
self.respect_keepalive();
250+
self.validate_request()?;
250251

251252
return Ok(Some(s));
252253
}
@@ -287,6 +288,17 @@ impl HttpSession {
287288
}
288289
}
289290

291+
// Validate the request header read. This function must be called after the request header
292+
// read.
293+
fn validate_request(&self) -> Result<()> {
294+
let req_header = self.req_header();
295+
296+
// ad-hoc checks
297+
super::common::check_dup_content_length(&req_header.headers)?;
298+
299+
Ok(())
300+
}
301+
290302
/// Return a reference of the `RequestHeader` this session read
291303
/// # Panics
292304
/// this function and most other functions will panic if called before [`Self::read_request()`]

0 commit comments

Comments
 (0)