Skip to content

Commit 14cbd40

Browse files
committed
fix(server): reject Requests with invalid body lengths
- Checks for that the `Transfer-Encoding` header has `chunked` as its last encoding - Makes `Transfer-Encoding` take priority over `Content-Length` - Rejects requests that contain a `Content-Length` header, but is invalid (or contains multiple with different values).
1 parent c4c89a2 commit 14cbd40

File tree

1 file changed

+63
-7
lines changed

1 file changed

+63
-7
lines changed

src/http/h1/parse.rs

+63-7
Original file line numberDiff line numberDiff line change
@@ -74,11 +74,31 @@ impl Http1Transaction for ServerTransaction {
7474

7575
fn decoder(head: &MessageHead<Self::Incoming>) -> ::Result<Decoder> {
7676
use ::header;
77-
if let Some(&header::ContentLength(len)) = head.headers.get() {
77+
// According to https://tools.ietf.org/html/rfc7230#section-3.3.3
78+
// 1. (irrelevant to Request)
79+
// 2. (irrelevant to Request)
80+
// 3. Transfer-Encoding: chunked has a chunked body.
81+
// 4. If multiple differing Content-Length headers or invalid, close connection.
82+
// 5. Content-Length header has a sized body.
83+
// 6. Length 0.
84+
// 7. (irrelevant to Request)
85+
86+
if let Some(&header::TransferEncoding(ref encodings)) = head.headers.get() {
87+
// https://tools.ietf.org/html/rfc7230#section-3.3.3
88+
// If Transfer-Encoding header is present, and 'chunked' is
89+
// not the final encoding, and this is a Request, then it is
90+
// mal-formed. A server should responsed with 400 Bad Request.
91+
if encodings.last() == Some(&header::Encoding::Chunked) {
92+
Ok(Decoder::chunked())
93+
} else {
94+
debug!("request with transfer-encoding header, but not chunked, bad request");
95+
Err(::Error::Header)
96+
}
97+
} else if let Some(&header::ContentLength(len)) = head.headers.get() {
7898
Ok(Decoder::length(len))
79-
} else if head.headers.has::<header::TransferEncoding>() {
80-
//TODO: check for Transfer-Encoding: chunked
81-
Ok(Decoder::chunked())
99+
} else if head.headers.has::<header::ContentLength>() {
100+
debug!("illegal Content-Length: {:?}", head.headers.get_raw("Content-Length"));
101+
Err(::Error::Header)
82102
} else {
83103
Ok(Decoder::length(0))
84104
}
@@ -201,7 +221,7 @@ impl Http1Transaction for ClientTransaction {
201221
// 3. Transfer-Encoding: chunked has a chunked body.
202222
// 4. If multiple differing Content-Length headers or invalid, close connection.
203223
// 5. Content-Length header has a sized body.
204-
// 6. Not Client.
224+
// 6. (irrelevant to Response)
205225
// 7. Read till EOF.
206226

207227
//TODO: need a way to pass the Method that caused this Response
@@ -223,7 +243,7 @@ impl Http1Transaction for ClientTransaction {
223243
} else if let Some(&header::ContentLength(len)) = inc.headers.get() {
224244
Ok(Decoder::length(len))
225245
} else if inc.headers.has::<header::ContentLength>() {
226-
trace!("illegal Content-Length: {:?}", inc.headers.get_raw("Content-Length"));
246+
debug!("illegal Content-Length: {:?}", inc.headers.get_raw("Content-Length"));
227247
Err(::Error::Header)
228248
} else {
229249
trace!("neither Transfer-Encoding nor Content-Length");
@@ -396,6 +416,39 @@ mod tests {
396416
assert_eq!(res.subject.1, "Howdy");
397417
}
398418

419+
420+
#[test]
421+
fn test_decoder_request() {
422+
use super::Decoder;
423+
424+
let mut head = MessageHead::<::http::RequestLine>::default();
425+
426+
head.subject.0 = ::Method::Get;
427+
assert_eq!(Decoder::length(0), ServerTransaction::decoder(&head).unwrap());
428+
head.subject.0 = ::Method::Post;
429+
assert_eq!(Decoder::length(0), ServerTransaction::decoder(&head).unwrap());
430+
431+
head.headers.set(TransferEncoding::chunked());
432+
assert_eq!(Decoder::chunked(), ServerTransaction::decoder(&head).unwrap());
433+
// transfer-encoding and content-length = chunked
434+
head.headers.set(ContentLength(10));
435+
assert_eq!(Decoder::chunked(), ServerTransaction::decoder(&head).unwrap());
436+
437+
head.headers.remove::<TransferEncoding>();
438+
assert_eq!(Decoder::length(10), ServerTransaction::decoder(&head).unwrap());
439+
440+
head.headers.set_raw("Content-Length", vec![b"5".to_vec(), b"5".to_vec()]);
441+
assert_eq!(Decoder::length(5), ServerTransaction::decoder(&head).unwrap());
442+
443+
head.headers.set_raw("Content-Length", vec![b"10".to_vec(), b"11".to_vec()]);
444+
ServerTransaction::decoder(&head).unwrap_err();
445+
446+
head.headers.remove::<ContentLength>();
447+
448+
head.headers.set_raw("Transfer-Encoding", "gzip");
449+
ServerTransaction::decoder(&head).unwrap_err();
450+
}
451+
399452
#[test]
400453
fn test_decoder_response() {
401454
use super::Decoder;
@@ -413,8 +466,11 @@ mod tests {
413466
head.headers.set(TransferEncoding::chunked());
414467
assert_eq!(Decoder::chunked(), ClientTransaction::decoder(&head).unwrap());
415468

416-
head.headers.remove::<TransferEncoding>();
469+
// transfer-encoding and content-length = chunked
417470
head.headers.set(ContentLength(10));
471+
assert_eq!(Decoder::chunked(), ClientTransaction::decoder(&head).unwrap());
472+
473+
head.headers.remove::<TransferEncoding>();
418474
assert_eq!(Decoder::length(10), ClientTransaction::decoder(&head).unwrap());
419475

420476
head.headers.set_raw("Content-Length", vec![b"5".to_vec(), b"5".to_vec()]);

0 commit comments

Comments
 (0)