Skip to content

Commit d01ddf0

Browse files
authored
Add option to allow space before first header name (#147)
1 parent a3c10fd commit d01ddf0

File tree

1 file changed

+88
-1
lines changed

1 file changed

+88
-1
lines changed

src/lib.rs

+88-1
Original file line numberDiff line numberDiff line change
@@ -259,6 +259,7 @@ pub struct ParserConfig {
259259
allow_obsolete_multiline_headers_in_responses: bool,
260260
allow_multiple_spaces_in_request_line_delimiters: bool,
261261
allow_multiple_spaces_in_response_status_delimiters: bool,
262+
allow_space_before_first_header_name: bool,
262263
ignore_invalid_headers_in_responses: bool,
263264
ignore_invalid_headers_in_requests: bool,
264265
}
@@ -356,6 +357,39 @@ impl ParserConfig {
356357
self.allow_obsolete_multiline_headers_in_responses
357358
}
358359

360+
/// Sets whether white space before the first header is allowed
361+
///
362+
/// This is not allowed by spec but some browsers ignore it. So this an option for
363+
/// compatibility.
364+
/// See https://github.com/curl/curl/issues/11605 for reference
365+
/// # Example
366+
///
367+
/// ```rust
368+
/// let buf = b"HTTP/1.1 200 OK\r\n Space-Before-Header: hello there\r\n\r\n";
369+
/// let mut headers = [httparse::EMPTY_HEADER; 1];
370+
/// let mut response = httparse::Response::new(&mut headers[..]);
371+
/// let result = httparse::ParserConfig::default()
372+
/// .allow_space_before_first_header_name(true)
373+
/// .parse_response(&mut response, buf);
374+
375+
/// assert_eq!(result, Ok(httparse::Status::Complete(buf.len())));
376+
/// assert_eq!(response.version.unwrap(), 1);
377+
/// assert_eq!(response.code.unwrap(), 200);
378+
/// assert_eq!(response.reason.unwrap(), "OK");
379+
/// assert_eq!(response.headers.len(), 1);
380+
/// assert_eq!(response.headers[0].name, "Space-Before-Header");
381+
/// assert_eq!(response.headers[0].value, &b"hello there"[..]);
382+
/// ```
383+
pub fn allow_space_before_first_header_name(&mut self, value: bool) -> &mut Self {
384+
self.allow_space_before_first_header_name = value;
385+
self
386+
}
387+
388+
/// Whether white space before first header is allowed or not
389+
pub fn space_before_first_header_name_are_allowed(&self) -> bool {
390+
self.allow_space_before_first_header_name
391+
}
392+
359393
/// Parses a request with the given config.
360394
pub fn parse_request<'buf>(
361395
&self,
@@ -519,6 +553,7 @@ impl<'h, 'b> Request<'h, 'b> {
519553
&HeaderParserConfig {
520554
allow_spaces_after_header_name: false,
521555
allow_obsolete_multiline_headers: false,
556+
allow_space_before_first_header_name: config.allow_space_before_first_header_name,
522557
ignore_invalid_headers: config.ignore_invalid_headers_in_requests
523558
},
524559
));
@@ -716,6 +751,7 @@ impl<'h, 'b> Response<'h, 'b> {
716751
&HeaderParserConfig {
717752
allow_spaces_after_header_name: config.allow_spaces_after_header_name_in_responses,
718753
allow_obsolete_multiline_headers: config.allow_obsolete_multiline_headers_in_responses,
754+
allow_space_before_first_header_name: config.allow_space_before_first_header_name,
719755
ignore_invalid_headers: config.ignore_invalid_headers_in_responses
720756
}
721757
));
@@ -1001,6 +1037,7 @@ unsafe fn assume_init_slice<T>(s: &mut [MaybeUninit<T>]) -> &mut [T] {
10011037
struct HeaderParserConfig {
10021038
allow_spaces_after_header_name: bool,
10031039
allow_obsolete_multiline_headers: bool,
1040+
allow_space_before_first_header_name: bool,
10041041
ignore_invalid_headers: bool,
10051042
}
10061043

@@ -1120,7 +1157,23 @@ fn parse_headers_iter_uninit<'a>(
11201157
break;
11211158
}
11221159
if !is_header_name_token(b) {
1123-
handle_invalid_char!(bytes, b, HeaderName);
1160+
if config.allow_space_before_first_header_name
1161+
&& autoshrink.num_headers == 0
1162+
&& (b == b' ' || b == b'\t')
1163+
{
1164+
//advance past white space and then try parsing header again
1165+
while let Some(peek) = bytes.peek() {
1166+
if peek == b' ' || peek == b'\t' {
1167+
next!(bytes);
1168+
} else {
1169+
break;
1170+
}
1171+
}
1172+
bytes.slice();
1173+
continue 'headers;
1174+
} else {
1175+
handle_invalid_char!(bytes, b, HeaderName);
1176+
}
11241177
}
11251178

11261179
#[allow(clippy::never_loop)]
@@ -2498,4 +2551,38 @@ mod tests {
24982551
assert!(REQUEST.as_ptr() <= method.as_ptr());
24992552
assert!(method.as_ptr() <= buf_end);
25002553
}
2554+
2555+
static RESPONSE_WITH_SPACE_BEFORE_FIRST_HEADER: &[u8] =
2556+
b"HTTP/1.1 200 OK\r\n Space-Before-Header: hello there\r\n\r\n";
2557+
2558+
#[test]
2559+
fn test_forbid_response_with_space_before_first_header() {
2560+
let mut headers = [EMPTY_HEADER; 1];
2561+
let mut response = Response::new(&mut headers[..]);
2562+
let result = response.parse(RESPONSE_WITH_SPACE_BEFORE_FIRST_HEADER);
2563+
2564+
assert_eq!(result, Err(crate::Error::HeaderName));
2565+
}
2566+
2567+
#[test]
2568+
fn test_allow_response_response_with_space_before_first_header() {
2569+
let mut headers = [EMPTY_HEADER; 1];
2570+
let mut response = Response::new(&mut headers[..]);
2571+
let result = crate::ParserConfig::default()
2572+
.allow_space_before_first_header_name(true)
2573+
.parse_response(&mut response, RESPONSE_WITH_SPACE_BEFORE_FIRST_HEADER);
2574+
2575+
assert_eq!(
2576+
result,
2577+
Ok(Status::Complete(
2578+
RESPONSE_WITH_SPACE_BEFORE_FIRST_HEADER.len()
2579+
))
2580+
);
2581+
assert_eq!(response.version.unwrap(), 1);
2582+
assert_eq!(response.code.unwrap(), 200);
2583+
assert_eq!(response.reason.unwrap(), "OK");
2584+
assert_eq!(response.headers.len(), 1);
2585+
assert_eq!(response.headers[0].name, "Space-Before-Header");
2586+
assert_eq!(response.headers[0].value, &b"hello there"[..]);
2587+
}
25012588
}

0 commit comments

Comments
 (0)