Skip to content

Commit 6915b23

Browse files
authored
Implement obsolete header line folding
This provides a configuration option to enable obs-folded headers. It defaults to off. If enabled, the header value will include all the newlines, and thus will need to be replaced with a space afterwards. Closes #37 Closes #68
1 parent 454efce commit 6915b23

File tree

1 file changed

+261
-51
lines changed

1 file changed

+261
-51
lines changed

src/lib.rs

Lines changed: 261 additions & 51 deletions
Original file line numberDiff line numberDiff line change
@@ -244,6 +244,7 @@ impl<T> Status<T> {
244244
#[derive(Clone, Debug, Default)]
245245
pub struct ParserConfig {
246246
allow_spaces_after_header_name_in_responses: bool,
247+
allow_obsolete_multiline_headers_in_responses: bool,
247248
}
248249

249250
impl ParserConfig {
@@ -256,6 +257,42 @@ impl ParserConfig {
256257
self
257258
}
258259

260+
/// Sets whether obsolete multiline headers should be allowed.
261+
///
262+
/// This is an obsolete part of HTTP/1. Use at your own risk. If you are
263+
/// building an HTTP library, the newlines (`\r` and `\n`) should be
264+
/// replaced by spaces before handing the header value to the user.
265+
///
266+
/// # Example
267+
///
268+
/// ```rust
269+
/// let buf = b"HTTP/1.1 200 OK\r\nFolded-Header: hello\r\n there \r\n\r\n";
270+
/// let mut headers = [httparse::EMPTY_HEADER; 16];
271+
/// let mut response = httparse::Response::new(&mut headers);
272+
///
273+
/// let res = httparse::ParserConfig::default()
274+
/// .allow_obsolete_multiline_headers_in_responses(true)
275+
/// .parse_response(&mut response, buf);
276+
///
277+
/// assert_eq!(res, Ok(httparse::Status::Complete(buf.len())));
278+
///
279+
/// assert_eq!(response.headers.len(), 1);
280+
/// assert_eq!(response.headers[0].name, "Folded-Header");
281+
/// assert_eq!(response.headers[0].value, b"hello\r\n there");
282+
/// ```
283+
pub fn allow_obsolete_multiline_headers_in_responses(
284+
&mut self,
285+
value: bool,
286+
) -> &mut Self {
287+
self.allow_obsolete_multiline_headers_in_responses = value;
288+
self
289+
}
290+
291+
/// Whether obsolete multiline headers should be allowed.
292+
pub fn obsolete_multiline_headers_in_responses_are_allowed(&self) -> bool {
293+
self.allow_obsolete_multiline_headers_in_responses
294+
}
295+
259296
/// Parses a response with the given config.
260297
pub fn parse_response<'headers, 'buf>(
261298
&self,
@@ -815,8 +852,7 @@ fn parse_headers_iter_uninit<'a, 'b>(
815852

816853
let mut b;
817854

818-
'value: loop {
819-
855+
let value_slice = 'value: loop {
820856
// eat white space between colon and value
821857
'whitespace_after_colon: loop {
822858
b = next!(bytes);
@@ -826,73 +862,131 @@ fn parse_headers_iter_uninit<'a, 'b>(
826862
continue 'whitespace_after_colon;
827863
} else {
828864
if !is_header_value_token(b) {
829-
break 'value;
865+
if b == b'\r' {
866+
expect!(bytes.next() == b'\n' => Err(Error::HeaderValue));
867+
} else if b != b'\n' {
868+
return Err(Error::HeaderValue);
869+
}
870+
871+
if config.allow_obsolete_multiline_headers_in_responses {
872+
match bytes.peek() {
873+
None => {
874+
// Next byte may be a space, in which case that header
875+
// is using obsolete line folding, so we may have more
876+
// whitespace to skip after colon.
877+
return Ok(Status::Partial);
878+
}
879+
Some(b' ') | Some(b'\t') => {
880+
// The space will be consumed next iteration.
881+
continue 'whitespace_after_colon;
882+
}
883+
_ => {
884+
// There is another byte after the end of the line,
885+
// but it's not whitespace, so it's probably another
886+
// header or the final line return. This header is thus
887+
// empty.
888+
},
889+
}
890+
}
891+
892+
count += bytes.pos();
893+
let whitespace_slice = bytes.slice();
894+
895+
// This produces an empty slice that points to the beginning
896+
// of the whitespace.
897+
break 'value &whitespace_slice[0..0];
830898
}
831899
break 'whitespace_after_colon;
832900
}
833901
}
834902

835-
// parse value till EOL
836-
837-
simd::match_header_value_vectored(bytes);
903+
'value_lines: loop {
904+
// parse value till EOL
905+
906+
simd::match_header_value_vectored(bytes);
907+
908+
'value_line: loop {
909+
if let Some(mut bytes8) = bytes.next_8() {
910+
macro_rules! check {
911+
($bytes:ident, $i:ident) => ({
912+
b = $bytes.$i();
913+
if !is_header_value_token(b) {
914+
break 'value_line;
915+
}
916+
});
917+
($bytes:ident) => ({
918+
check!($bytes, _0);
919+
check!($bytes, _1);
920+
check!($bytes, _2);
921+
check!($bytes, _3);
922+
check!($bytes, _4);
923+
check!($bytes, _5);
924+
check!($bytes, _6);
925+
check!($bytes, _7);
926+
})
927+
}
928+
929+
check!(bytes8);
930+
931+
continue 'value_line;
932+
}
838933

839-
macro_rules! check {
840-
($bytes:ident, $i:ident) => ({
841-
b = $bytes.$i();
934+
b = next!(bytes);
842935
if !is_header_value_token(b) {
843-
break 'value;
936+
break 'value_line;
844937
}
845-
});
846-
($bytes:ident) => ({
847-
check!($bytes, _0);
848-
check!($bytes, _1);
849-
check!($bytes, _2);
850-
check!($bytes, _3);
851-
check!($bytes, _4);
852-
check!($bytes, _5);
853-
check!($bytes, _6);
854-
check!($bytes, _7);
855-
})
856-
}
857-
while let Some(mut bytes8) = bytes.next_8() {
858-
check!(bytes8);
859-
}
860-
loop {
861-
b = next!(bytes);
862-
if !is_header_value_token(b) {
863-
break 'value;
864938
}
865-
}
866-
}
867939

868-
//found_ctl
869-
let value_slice : &[u8] = if b == b'\r' {
870-
expect!(bytes.next() == b'\n' => Err(Error::HeaderValue));
871-
count += bytes.pos();
872-
// having just check that `\r\n` exists, it's safe to skip those 2 bytes
873-
unsafe {
874-
bytes.slice_skip(2)
875-
}
876-
} else if b == b'\n' {
877-
count += bytes.pos();
878-
// having just check that `\r\n` exists, it's safe to skip 1 byte
879-
unsafe {
880-
bytes.slice_skip(1)
940+
//found_ctl
941+
let skip = if b == b'\r' {
942+
expect!(bytes.next() == b'\n' => Err(Error::HeaderValue));
943+
2
944+
} else if b == b'\n' {
945+
1
946+
} else {
947+
return Err(Error::HeaderValue);
948+
};
949+
950+
if config.allow_obsolete_multiline_headers_in_responses {
951+
match bytes.peek() {
952+
None => {
953+
// Next byte may be a space, in which case that header
954+
// may be using line folding, so we need more data.
955+
return Ok(Status::Partial);
956+
}
957+
Some(b' ') | Some(b'\t') => {
958+
// The space will be consumed next iteration.
959+
continue 'value_lines;
960+
}
961+
_ => {
962+
// There is another byte after the end of the line,
963+
// but it's not a space, so it's probably another
964+
// header or the final line return. We are thus done
965+
// with this current header.
966+
},
967+
}
968+
}
969+
970+
count += bytes.pos();
971+
// having just checked that a newline exists, it's safe to skip it.
972+
unsafe {
973+
break 'value bytes.slice_skip(skip);
974+
}
881975
}
882-
} else {
883-
return Err(Error::HeaderValue);
884976
};
885977

886-
let header_value: &[u8];
887978
// trim trailing whitespace in the header
888-
if let Some(last_visible) = value_slice.iter().rposition(|b| *b != b' ' && *b != b'\t' ) {
979+
let header_value = if let Some(last_visible) = value_slice
980+
.iter()
981+
.rposition(|b| *b != b' ' && *b != b'\t' && *b != b'\r' && *b != b'\n')
982+
{
889983
// There is at least one non-whitespace character.
890-
header_value = &value_slice[0..last_visible+1];
984+
&value_slice[0..last_visible+1]
891985
} else {
892986
// There is no non-whitespace character. This can only happen when value_slice is
893987
// empty.
894-
header_value = value_slice;
895-
}
988+
value_slice
989+
};
896990

897991
*uninit_header = MaybeUninit::new(Header {
898992
name: header_name,
@@ -1402,6 +1496,122 @@ mod tests {
14021496
assert_eq!(result, Err(::Error::HeaderName));
14031497
}
14041498

1499+
static RESPONSE_WITH_OBSOLETE_LINE_FOLDING_AT_START: &'static [u8] =
1500+
b"HTTP/1.1 200 OK\r\nLine-Folded-Header: \r\n \r\n hello there\r\n\r\n";
1501+
1502+
#[test]
1503+
fn test_forbid_response_with_obsolete_line_folding_at_start() {
1504+
let mut headers = [EMPTY_HEADER; 1];
1505+
let mut response = Response::new(&mut headers[..]);
1506+
let result = response.parse(RESPONSE_WITH_OBSOLETE_LINE_FOLDING_AT_START);
1507+
1508+
assert_eq!(result, Err(::Error::HeaderName));
1509+
}
1510+
1511+
#[test]
1512+
fn test_allow_response_with_obsolete_line_folding_at_start() {
1513+
let mut headers = [EMPTY_HEADER; 1];
1514+
let mut response = Response::new(&mut headers[..]);
1515+
let result = ::ParserConfig::default()
1516+
.allow_obsolete_multiline_headers_in_responses(true)
1517+
.parse_response(&mut response, RESPONSE_WITH_OBSOLETE_LINE_FOLDING_AT_START);
1518+
1519+
assert_eq!(result, Ok(Status::Complete(RESPONSE_WITH_OBSOLETE_LINE_FOLDING_AT_START.len())));
1520+
assert_eq!(response.version.unwrap(), 1);
1521+
assert_eq!(response.code.unwrap(), 200);
1522+
assert_eq!(response.reason.unwrap(), "OK");
1523+
assert_eq!(response.headers.len(), 1);
1524+
assert_eq!(response.headers[0].name, "Line-Folded-Header");
1525+
assert_eq!(response.headers[0].value, &b"hello there"[..]);
1526+
}
1527+
1528+
static RESPONSE_WITH_OBSOLETE_LINE_FOLDING_AT_END: &'static [u8] =
1529+
b"HTTP/1.1 200 OK\r\nLine-Folded-Header: hello there\r\n \r\n \r\n\r\n";
1530+
1531+
#[test]
1532+
fn test_forbid_response_with_obsolete_line_folding_at_end() {
1533+
let mut headers = [EMPTY_HEADER; 1];
1534+
let mut response = Response::new(&mut headers[..]);
1535+
let result = response.parse(RESPONSE_WITH_OBSOLETE_LINE_FOLDING_AT_END);
1536+
1537+
assert_eq!(result, Err(::Error::HeaderName));
1538+
}
1539+
1540+
#[test]
1541+
fn test_allow_response_with_obsolete_line_folding_at_end() {
1542+
let mut headers = [EMPTY_HEADER; 1];
1543+
let mut response = Response::new(&mut headers[..]);
1544+
let result = ::ParserConfig::default()
1545+
.allow_obsolete_multiline_headers_in_responses(true)
1546+
.parse_response(&mut response, RESPONSE_WITH_OBSOLETE_LINE_FOLDING_AT_END);
1547+
1548+
assert_eq!(result, Ok(Status::Complete(RESPONSE_WITH_OBSOLETE_LINE_FOLDING_AT_END.len())));
1549+
assert_eq!(response.version.unwrap(), 1);
1550+
assert_eq!(response.code.unwrap(), 200);
1551+
assert_eq!(response.reason.unwrap(), "OK");
1552+
assert_eq!(response.headers.len(), 1);
1553+
assert_eq!(response.headers[0].name, "Line-Folded-Header");
1554+
assert_eq!(response.headers[0].value, &b"hello there"[..]);
1555+
}
1556+
1557+
static RESPONSE_WITH_OBSOLETE_LINE_FOLDING_IN_MIDDLE: &'static [u8] =
1558+
b"HTTP/1.1 200 OK\r\nLine-Folded-Header: hello \r\n \r\n there\r\n\r\n";
1559+
1560+
#[test]
1561+
fn test_forbid_response_with_obsolete_line_folding_in_middle() {
1562+
let mut headers = [EMPTY_HEADER; 1];
1563+
let mut response = Response::new(&mut headers[..]);
1564+
let result = response.parse(RESPONSE_WITH_OBSOLETE_LINE_FOLDING_IN_MIDDLE);
1565+
1566+
assert_eq!(result, Err(::Error::HeaderName));
1567+
}
1568+
1569+
#[test]
1570+
fn test_allow_response_with_obsolete_line_folding_in_middle() {
1571+
let mut headers = [EMPTY_HEADER; 1];
1572+
let mut response = Response::new(&mut headers[..]);
1573+
let result = ::ParserConfig::default()
1574+
.allow_obsolete_multiline_headers_in_responses(true)
1575+
.parse_response(&mut response, RESPONSE_WITH_OBSOLETE_LINE_FOLDING_IN_MIDDLE);
1576+
1577+
assert_eq!(result, Ok(Status::Complete(RESPONSE_WITH_OBSOLETE_LINE_FOLDING_IN_MIDDLE.len())));
1578+
assert_eq!(response.version.unwrap(), 1);
1579+
assert_eq!(response.code.unwrap(), 200);
1580+
assert_eq!(response.reason.unwrap(), "OK");
1581+
assert_eq!(response.headers.len(), 1);
1582+
assert_eq!(response.headers[0].name, "Line-Folded-Header");
1583+
assert_eq!(response.headers[0].value, &b"hello \r\n \r\n there"[..]);
1584+
}
1585+
1586+
static RESPONSE_WITH_OBSOLETE_LINE_FOLDING_IN_EMPTY_HEADER: &'static [u8] =
1587+
b"HTTP/1.1 200 OK\r\nLine-Folded-Header: \r\n \r\n \r\n\r\n";
1588+
1589+
#[test]
1590+
fn test_forbid_response_with_obsolete_line_folding_in_empty_header() {
1591+
let mut headers = [EMPTY_HEADER; 1];
1592+
let mut response = Response::new(&mut headers[..]);
1593+
let result = response.parse(RESPONSE_WITH_OBSOLETE_LINE_FOLDING_IN_EMPTY_HEADER);
1594+
1595+
assert_eq!(result, Err(::Error::HeaderName));
1596+
}
1597+
1598+
#[test]
1599+
fn test_allow_response_with_obsolete_line_folding_in_empty_header() {
1600+
let mut headers = [EMPTY_HEADER; 1];
1601+
let mut response = Response::new(&mut headers[..]);
1602+
let result = ::ParserConfig::default()
1603+
.allow_obsolete_multiline_headers_in_responses(true)
1604+
.parse_response(&mut response, RESPONSE_WITH_OBSOLETE_LINE_FOLDING_IN_EMPTY_HEADER);
1605+
1606+
assert_eq!(result, Ok(Status::Complete(RESPONSE_WITH_OBSOLETE_LINE_FOLDING_IN_EMPTY_HEADER.len())));
1607+
assert_eq!(response.version.unwrap(), 1);
1608+
assert_eq!(response.code.unwrap(), 200);
1609+
assert_eq!(response.reason.unwrap(), "OK");
1610+
assert_eq!(response.headers.len(), 1);
1611+
assert_eq!(response.headers[0].name, "Line-Folded-Header");
1612+
assert_eq!(response.headers[0].value, &b""[..]);
1613+
}
1614+
14051615
#[test]
14061616
fn test_chunk_size() {
14071617
assert_eq!(parse_chunk_size(b"0\r\n"), Ok(Status::Complete((3, 0))));

0 commit comments

Comments
 (0)