Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add option to allow space before first header name #147

Merged
Merged
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
89 changes: 88 additions & 1 deletion src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -259,6 +259,7 @@ pub struct ParserConfig {
allow_obsolete_multiline_headers_in_responses: bool,
allow_multiple_spaces_in_request_line_delimiters: bool,
allow_multiple_spaces_in_response_status_delimiters: bool,
allow_space_before_first_header_name: bool,
ignore_invalid_headers_in_responses: bool,
ignore_invalid_headers_in_requests: bool,
}
Expand Down Expand Up @@ -356,6 +357,39 @@ impl ParserConfig {
self.allow_obsolete_multiline_headers_in_responses
}

/// Sets whether white space before the first header is allowed
///
/// This is not allowed by spec but some browsers ignore it. So this an option for
/// compatibility.
/// See https://github.com/curl/curl/issues/11605 for reference
/// # Example
///
/// ```rust
/// let buf = b"HTTP/1.1 200 OK\r\n Space-Before-Header: hello there\r\n\r\n";
/// let mut headers = [httparse::EMPTY_HEADER; 1];
/// let mut response = httparse::Response::new(&mut headers[..]);
/// let result = httparse::ParserConfig::default()
/// .allow_space_before_first_header_name(true)
/// .parse_response(&mut response, buf);

/// assert_eq!(result, Ok(httparse::Status::Complete(buf.len())));
/// assert_eq!(response.version.unwrap(), 1);
/// assert_eq!(response.code.unwrap(), 200);
/// assert_eq!(response.reason.unwrap(), "OK");
/// assert_eq!(response.headers.len(), 1);
/// assert_eq!(response.headers[0].name, "Space-Before-Header");
/// assert_eq!(response.headers[0].value, &b"hello there"[..]);
/// ```
pub fn allow_space_before_first_header_name(&mut self, value: bool) -> &mut Self {
self.allow_space_before_first_header_name = value;
self
}

/// Whether white space before first header is allowed or not
pub fn space_before_first_header_name_are_allowed(&self) -> bool {
self.allow_space_before_first_header_name
}

/// Parses a request with the given config.
pub fn parse_request<'buf>(
&self,
Expand Down Expand Up @@ -519,6 +553,7 @@ impl<'h, 'b> Request<'h, 'b> {
&HeaderParserConfig {
allow_spaces_after_header_name: false,
allow_obsolete_multiline_headers: false,
allow_space_before_first_header_name: config.allow_space_before_first_header_name,
ignore_invalid_headers: config.ignore_invalid_headers_in_requests
},
));
Expand Down Expand Up @@ -716,6 +751,7 @@ impl<'h, 'b> Response<'h, 'b> {
&HeaderParserConfig {
allow_spaces_after_header_name: config.allow_spaces_after_header_name_in_responses,
allow_obsolete_multiline_headers: config.allow_obsolete_multiline_headers_in_responses,
allow_space_before_first_header_name: config.allow_space_before_first_header_name,
ignore_invalid_headers: config.ignore_invalid_headers_in_responses
}
));
Expand Down Expand Up @@ -1001,6 +1037,7 @@ unsafe fn assume_init_slice<T>(s: &mut [MaybeUninit<T>]) -> &mut [T] {
struct HeaderParserConfig {
allow_spaces_after_header_name: bool,
allow_obsolete_multiline_headers: bool,
allow_space_before_first_header_name: bool,
ignore_invalid_headers: bool,
}

Expand Down Expand Up @@ -1120,7 +1157,23 @@ fn parse_headers_iter_uninit<'a>(
break;
}
if !is_header_name_token(b) {
handle_invalid_char!(bytes, b, HeaderName);
if config.allow_space_before_first_header_name
&& autoshrink.num_headers == 0
&& (b == b' ' || b == b'\t')
{
//advance past white space and then try parsing header again
while let Some(peek) = bytes.peek() {
if peek == b' ' || peek == b'\t' {
next!(bytes);
} else {
break;
}
}
bytes.slice();
continue 'headers;
} else {
handle_invalid_char!(bytes, b, HeaderName);
}
}

#[allow(clippy::never_loop)]
Expand Down Expand Up @@ -2498,4 +2551,38 @@ mod tests {
assert!(REQUEST.as_ptr() <= method.as_ptr());
assert!(method.as_ptr() <= buf_end);
}

static RESPONSE_WITH_SPACE_BEFORE_FIRST_HEADER: &[u8] =
b"HTTP/1.1 200 OK\r\n Space-Before-Header: hello there\r\n\r\n";

#[test]
fn test_forbid_response_with_space_before_first_header() {
let mut headers = [EMPTY_HEADER; 1];
let mut response = Response::new(&mut headers[..]);
let result = response.parse(RESPONSE_WITH_SPACE_BEFORE_FIRST_HEADER);

assert_eq!(result, Err(crate::Error::HeaderName));
}

#[test]
fn test_allow_response_response_with_space_before_first_header() {
let mut headers = [EMPTY_HEADER; 1];
let mut response = Response::new(&mut headers[..]);
let result = crate::ParserConfig::default()
.allow_space_before_first_header_name(true)
.parse_response(&mut response, RESPONSE_WITH_SPACE_BEFORE_FIRST_HEADER);

assert_eq!(
result,
Ok(Status::Complete(
RESPONSE_WITH_SPACE_BEFORE_FIRST_HEADER.len()
))
);
assert_eq!(response.version.unwrap(), 1);
assert_eq!(response.code.unwrap(), 200);
assert_eq!(response.reason.unwrap(), "OK");
assert_eq!(response.headers.len(), 1);
assert_eq!(response.headers[0].name, "Space-Before-Header");
assert_eq!(response.headers[0].value, &b"hello there"[..]);
}
}
Loading