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

Ignoring invalid headers for requests #145

Merged
merged 6 commits into from
Aug 8, 2023
Merged
Show file tree
Hide file tree
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
235 changes: 223 additions & 12 deletions src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -260,6 +260,7 @@ pub struct ParserConfig {
allow_multiple_spaces_in_request_line_delimiters: bool,
allow_multiple_spaces_in_response_status_delimiters: bool,
ignore_invalid_headers_in_responses: bool,
ignore_invalid_headers_in_requests: bool,
}

impl ParserConfig {
Expand Down Expand Up @@ -412,6 +413,15 @@ impl ParserConfig {
self
}

/// Sets whether invalid header lines should be silently ignored in requests.
pub fn ignore_invalid_headers_in_requests(
&mut self,
value: bool,
) -> &mut Self {
self.ignore_invalid_headers_in_requests = value;
self
}

/// Parses a response with the given config.
pub fn parse_response<'buf>(
&self,
Expand Down Expand Up @@ -506,7 +516,11 @@ impl<'h, 'b> Request<'h, 'b> {
let headers_len = complete!(parse_headers_iter_uninit(
&mut headers,
&mut bytes,
&ParserConfig::default(),
&HeaderParserConfig {
allow_spaces_after_header_name: false,
allow_obsolete_multiline_headers: false,
ignore_invalid_headers: config.ignore_invalid_headers_in_requests
},
));
/* SAFETY: see `parse_headers_iter_uninit` guarantees */
self.headers = unsafe { assume_init_slice(headers) };
Expand Down Expand Up @@ -699,7 +713,11 @@ impl<'h, 'b> Response<'h, 'b> {
let headers_len = complete!(parse_headers_iter_uninit(
&mut headers,
&mut bytes,
config
&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,
ignore_invalid_headers: config.ignore_invalid_headers_in_responses
}
));
/* SAFETY: see `parse_headers_iter_uninit` guarantees */
self.headers = unsafe { assume_init_slice(headers) };
Expand Down Expand Up @@ -950,15 +968,15 @@ pub fn parse_headers<'b: 'h, 'h>(
mut dst: &'h mut [Header<'b>],
) -> Result<(usize, &'h [Header<'b>])> {
let mut iter = Bytes::new(src);
let pos = complete!(parse_headers_iter(&mut dst, &mut iter, &ParserConfig::default()));
let pos = complete!(parse_headers_iter(&mut dst, &mut iter, &HeaderParserConfig::default()));
Ok(Status::Complete((pos, dst)))
}

#[inline]
fn parse_headers_iter<'a>(
headers: &mut &mut [Header<'a>],
bytes: &mut Bytes<'a>,
config: &ParserConfig,
config: &HeaderParserConfig,
) -> Result<usize> {
parse_headers_iter_uninit(
/* SAFETY: see `parse_headers_iter_uninit` guarantees */
Expand All @@ -979,6 +997,13 @@ unsafe fn assume_init_slice<T>(s: &mut [MaybeUninit<T>]) -> &mut [T] {
&mut *s
}

#[derive(Clone, Debug, Default)]
struct HeaderParserConfig {
allow_spaces_after_header_name: bool,
allow_obsolete_multiline_headers: bool,
ignore_invalid_headers: bool,
}

/* Function which parsers headers into uninitialized buffer.
*
* Guarantees that it doesn't write garbage, so casting
Expand All @@ -991,7 +1016,7 @@ unsafe fn assume_init_slice<T>(s: &mut [MaybeUninit<T>]) -> &mut [T] {
fn parse_headers_iter_uninit<'a>(
headers: &mut &mut [MaybeUninit<Header<'a>>],
bytes: &mut Bytes<'a>,
config: &ParserConfig,
config: &HeaderParserConfig
) -> Result<usize> {

/* Flow of this function is pretty complex, especially with macros,
Expand Down Expand Up @@ -1026,7 +1051,7 @@ fn parse_headers_iter_uninit<'a>(

macro_rules! maybe_continue_after_obsolete_line_folding {
($bytes:ident, $label:lifetime) => {
if config.allow_obsolete_multiline_headers_in_responses {
if config.allow_obsolete_multiline_headers {
match $bytes.peek() {
None => {
// Next byte may be a space, in which case that header
Expand Down Expand Up @@ -1055,7 +1080,7 @@ fn parse_headers_iter_uninit<'a>(
// parsing on the next one.
macro_rules! handle_invalid_char {
($bytes:ident, $b:ident, $err:ident) => {
if !config.ignore_invalid_headers_in_responses {
if !config.ignore_invalid_headers {
return Err(Error::$err);
}

Expand Down Expand Up @@ -1114,7 +1139,7 @@ fn parse_headers_iter_uninit<'a>(
break 'name name;
}

if config.allow_spaces_after_header_name_in_responses {
if config.allow_spaces_after_header_name {
while b == b' ' || b == b'\t' {
b = next!(bytes);

Expand Down Expand Up @@ -1733,7 +1758,7 @@ mod tests {
}

#[test]
fn test_ignore_header_line_with_whitespaces_after_header_name() {
fn test_ignore_header_line_with_whitespaces_after_header_name_in_response() {
let mut headers = [EMPTY_HEADER; 2];
let mut response = Response::new(&mut headers[..]);
let result = crate::ParserConfig::default()
Expand Down Expand Up @@ -1761,6 +1786,17 @@ mod tests {
assert_eq!(result, Err(crate::Error::HeaderName));
}

#[test]
fn test_ignore_header_line_with_whitespaces_after_header_name_in_request() {
let mut headers = [EMPTY_HEADER; 2];
let mut request = Request::new(&mut headers[..]);
let result = crate::ParserConfig::default()
.ignore_invalid_headers_in_requests(true)
.parse_request(&mut request, REQUEST_WITH_WHITESPACE_BETWEEN_HEADER_NAME_AND_COLON);

assert_eq!(result, Ok(Status::Complete(36)));
}

static RESPONSE_WITH_OBSOLETE_LINE_FOLDING_AT_START: &[u8] =
b"HTTP/1.1 200 OK\r\nLine-Folded-Header: \r\n \r\n hello there\r\n\r\n";

Expand Down Expand Up @@ -2047,6 +2083,24 @@ mod tests {
assert_eq!(response.headers[0].value, &b"baguette"[..]);
}

#[test]
fn test_request_with_empty_header_name() {
const RESPONSE: &[u8] =
b"GET / HTTP/1.1\r\n: hello\r\nBread: baguette\r\n\r\n";

let mut headers = [EMPTY_HEADER; 2];
let mut request = Request::new(&mut headers[..]);

let result = crate::ParserConfig::default()
.parse_request(&mut request, RESPONSE);
assert_eq!(result, Err(crate::Error::HeaderName));

let result = crate::ParserConfig::default()
.ignore_invalid_headers_in_requests(true)
.parse_request(&mut request, RESPONSE);
assert_eq!(result, Ok(Status::Complete(44)));
}

#[test]
fn test_request_with_whitespace_between_header_name_and_colon() {
const REQUEST: &[u8] =
Expand Down Expand Up @@ -2094,7 +2148,25 @@ mod tests {
}

#[test]
fn test_ignore_header_line_with_missing_colon() {
fn test_request_with_invalid_char_between_header_name_and_colon() {
const REQUEST: &[u8] =
b"GET / HTTP/1.1\r\nAccess-Control-Allow-Credentials\xFF : true\r\nBread: baguette\r\n\r\n";

let mut headers = [EMPTY_HEADER; 2];
let mut request = Request::new(&mut headers[..]);

let result = crate::ParserConfig::default()
.parse_request(&mut request, REQUEST);
assert_eq!(result, Err(crate::Error::HeaderName));

let result = crate::ParserConfig::default()
.ignore_invalid_headers_in_requests(true)
.parse_request(&mut request, REQUEST);
assert_eq!(result, Ok(Status::Complete(78)));
}

#[test]
fn test_ignore_header_line_with_missing_colon_in_response() {
const RESPONSE: &[u8] =
b"HTTP/1.1 200 OK\r\nAccess-Control-Allow-Credentials\r\nBread: baguette\r\n\r\n";

Expand All @@ -2119,7 +2191,25 @@ mod tests {
}

#[test]
fn test_header_with_missing_colon_with_folding() {
fn test_ignore_header_line_with_missing_colon_in_request() {
const REQUEST: &[u8] =
b"GET / HTTP/1.1\r\nAccess-Control-Allow-Credentials\r\nBread: baguette\r\n\r\n";

let mut headers = [EMPTY_HEADER; 2];
let mut request = Request::new(&mut headers[..]);

let result = crate::ParserConfig::default()
.parse_request(&mut request, REQUEST);
assert_eq!(result, Err(crate::Error::HeaderName));

let result = crate::ParserConfig::default()
.ignore_invalid_headers_in_requests(true)
.parse_request(&mut request, REQUEST);
assert_eq!(result, Ok(Status::Complete(69)));
}

#[test]
fn test_response_header_with_missing_colon_with_folding() {
const RESPONSE: &[u8] =
b"HTTP/1.1 200 OK\r\nAccess-Control-Allow-Credentials \r\n hello\r\nBread: baguette\r\n\r\n";

Expand All @@ -2146,7 +2236,25 @@ mod tests {
}

#[test]
fn test_header_with_nul_in_header_name() {
fn test_request_header_with_missing_colon_with_folding() {
const REQUEST: &[u8] =
b"GET / HTTP/1.1\r\nAccess-Control-Allow-Credentials \r\n hello\r\nBread: baguette\r\n\r\n";

let mut headers = [EMPTY_HEADER; 2];
let mut request = Request::new(&mut headers[..]);

let result = crate::ParserConfig::default()
.parse_request(&mut request, REQUEST);
assert_eq!(result, Err(crate::Error::HeaderName));

let result = crate::ParserConfig::default()
.ignore_invalid_headers_in_requests(true)
.parse_request(&mut request, REQUEST);
assert_eq!(result, Ok(Status::Complete(80)));
}

#[test]
fn test_response_header_with_nul_in_header_name() {
const RESPONSE: &[u8] =
b"HTTP/1.1 200 OK\r\nAccess-Control-Allow-Cred\0entials: hello\r\nBread: baguette\r\n\r\n";

Expand All @@ -2163,6 +2271,24 @@ mod tests {
assert_eq!(result, Err(crate::Error::HeaderName));
}

#[test]
fn test_request_header_with_nul_in_header_name() {
const REQUEST: &[u8] =
b"GET / HTTP/1.1\r\nAccess-Control-Allow-Cred\0entials: hello\r\nBread: baguette\r\n\r\n";

let mut headers = [EMPTY_HEADER; 2];
let mut request = Request::new(&mut headers[..]);

let result = crate::ParserConfig::default()
.parse_request(&mut request, REQUEST);
assert_eq!(result, Err(crate::Error::HeaderName));

let result = crate::ParserConfig::default()
.ignore_invalid_headers_in_requests(true)
.parse_request(&mut request, REQUEST);
assert_eq!(result, Err(crate::Error::HeaderName));
}

#[test]
fn test_header_with_cr_in_header_name() {
const RESPONSE: &[u8] =
Expand All @@ -2179,6 +2305,21 @@ mod tests {
.ignore_invalid_headers_in_responses(true)
.parse_response(&mut response, RESPONSE);
assert_eq!(result, Err(crate::Error::HeaderName));

const REQUEST: &[u8] =
b"GET / HTTP/1.1\r\nAccess-Control-Allow-Cred\rentials: hello\r\nBread: baguette\r\n\r\n";

let mut headers = [EMPTY_HEADER; 2];
let mut request = Request::new(&mut headers[..]);

let result = crate::ParserConfig::default()
.parse_request(&mut request, REQUEST);
assert_eq!(result, Err(crate::Error::HeaderName));

let result = crate::ParserConfig::default()
.ignore_invalid_headers_in_requests(true)
.parse_request(&mut request, REQUEST);
assert_eq!(result, Err(crate::Error::HeaderName));
}

#[test]
Expand All @@ -2199,6 +2340,17 @@ mod tests {
.ignore_invalid_headers_in_responses(true)
.parse_response(&mut response, RESPONSE);
assert_eq!(result, Err(crate::Error::HeaderName));

const REQUEST: &[u8] =
b"GET / HTTP/1.1\r\nAccess-Control-Allow-Credentials \0: hello\r\nBread: baguette\r\n\r\n";

let mut headers = [EMPTY_HEADER; 2];
let mut request = Request::new(&mut headers[..]);

let result = crate::ParserConfig::default()
.ignore_invalid_headers_in_requests(true)
.parse_request(&mut request, REQUEST);
assert_eq!(result, Err(crate::Error::HeaderName));
}

#[test]
Expand All @@ -2217,6 +2369,21 @@ mod tests {
.ignore_invalid_headers_in_responses(true)
.parse_response(&mut response, RESPONSE);
assert_eq!(result, Err(crate::Error::HeaderValue));

const REQUEST: &[u8] =
b"GET / HTTP/1.1\r\nAccess-Control-Allow-Credentials: hell\0o\r\nBread: baguette\r\n\r\n";

let mut headers = [EMPTY_HEADER; 2];
let mut request = Request::new(&mut headers[..]);

let result = crate::ParserConfig::default()
.parse_request(&mut request, REQUEST);
assert_eq!(result, Err(crate::Error::HeaderValue));

let result = crate::ParserConfig::default()
.ignore_invalid_headers_in_requests(true)
.parse_request(&mut request, REQUEST);
assert_eq!(result, Err(crate::Error::HeaderValue));
}

#[test]
Expand All @@ -2242,6 +2409,28 @@ mod tests {
assert_eq!(response.headers.len(), 1);
assert_eq!(response.headers[0].name, "Bread");
assert_eq!(response.headers[0].value, &b"baguette"[..]);

const REQUEST: &[u8] =
b"GET / HTTP/1.1\r\nAccess-Control-Allow-Credentials: hell\x01o\r\nBread: baguette\r\n\r\n";

let mut headers = [EMPTY_HEADER; 2];
let mut request = Request::new(&mut headers[..]);

let result = crate::ParserConfig::default()
.parse_request(&mut request, REQUEST);
assert_eq!(result, Err(crate::Error::HeaderValue));

let result = crate::ParserConfig::default()
.ignore_invalid_headers_in_requests(true)
.parse_request(&mut request, REQUEST);
assert_eq!(result, Ok(Status::Complete(77)));

assert_eq!(request.version.unwrap(), 1);
assert_eq!(request.method.unwrap(), "GET");
assert_eq!(request.path.unwrap(), "/");
assert_eq!(request.headers.len(), 1);
assert_eq!(request.headers[0].name, "Bread");
assert_eq!(request.headers[0].value, &b"baguette"[..]);
}

#[test]
Expand All @@ -2267,6 +2456,28 @@ mod tests {
assert_eq!(response.headers.len(), 1);
assert_eq!(response.headers[0].name, "Bread");
assert_eq!(response.headers[0].value, &b"baguette"[..]);

const REQUEST: &[u8] =
b"GET / HTTP/1.1\r\nAccess-Control-Allow-Credentials: hell\x01o \n world!\r\nBread: baguette\r\n\r\n";

let mut headers = [EMPTY_HEADER; 2];
let mut request = Request::new(&mut headers[..]);

let result = crate::ParserConfig::default()
.parse_request(&mut request, REQUEST);
assert_eq!(result, Err(crate::Error::HeaderValue));

let result = crate::ParserConfig::default()
.ignore_invalid_headers_in_requests(true)
.parse_request(&mut request, REQUEST);
assert_eq!(result, Ok(Status::Complete(87)));

assert_eq!(request.version.unwrap(), 1);
assert_eq!(request.method.unwrap(), "GET");
assert_eq!(request.path.unwrap(), "/");
assert_eq!(request.headers.len(), 1);
assert_eq!(request.headers[0].name, "Bread");
assert_eq!(request.headers[0].value, &b"baguette"[..]);
}

#[test]
Expand Down
Loading
Loading