#![cfg_attr(not(any(test, feature = "std")), no_std)]
#![deny(clippy::missing_safety_doc, clippy::undocumented_unsafe_blocks)]
#![cfg_attr(test, deny(warnings))]
use core::{fmt, result, str};
use crate::iter::Bytes;
mod iter;
#[macro_use]
mod macros;
mod simd;
#[inline]
fn is_method_token(b: u8) -> bool {
match b {
b'A'..=b'Z' => true,
_ => TOKEN_MAP[b as usize],
}
}
static URI_MAP: [bool; 256] = byte_map!(
b'!'..=0x7e | 0x80..=0xFF
);
#[inline]
pub(crate) fn is_uri_token(b: u8) -> bool {
URI_MAP[b as usize]
}
static TOKEN_MAP: [bool; 256] = byte_map!(
b'A'..=b'Z' | b'a'..=b'z' | b'0'..=b'9' |
b'!' | b'#' | b'$' | b'%' | b'&' | b'\'' | b'*' | b'+' |
b'-' | b'.' | b'^' | b'_' | b'`' | b'|' | b'~'
);
#[inline]
pub(crate) fn is_header_name_token(b: u8) -> bool {
TOKEN_MAP[b as usize]
}
static HEADER_VALUE_MAP: [bool; 256] = byte_map!(
b'\t' | b' '..=0x7e | 0x80..=0xFF
);
#[inline]
pub(crate) fn is_header_value_token(b: u8) -> bool {
HEADER_VALUE_MAP[b as usize]
}
#[derive(Copy, Clone, PartialEq, Eq, Debug)]
pub enum Error {
HeaderName,
HeaderValue,
NewLine,
Status,
Token,
TooManyHeaders,
Version,
}
impl Error {
#[inline]
fn description_str(&self) -> &'static str {
match *self {
Error::HeaderName => "invalid header name",
Error::HeaderValue => "invalid header value",
Error::NewLine => "invalid new line",
Error::Status => "invalid response status",
Error::Token => "invalid token",
Error::TooManyHeaders => "too many headers",
Error::Version => "invalid HTTP version",
}
}
}
impl fmt::Display for Error {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.write_str(self.description_str())
}
}
#[cfg(feature = "std")]
impl std::error::Error for Error {
fn description(&self) -> &str {
self.description_str()
}
}
#[derive(Debug, PartialEq, Eq)]
pub struct InvalidChunkSize;
impl fmt::Display for InvalidChunkSize {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.write_str("invalid chunk size")
}
}
pub type Result<T> = result::Result<Status<T>, Error>;
#[derive(Copy, Clone, Eq, PartialEq, Debug)]
pub enum Status<T> {
Complete(T),
Partial,
}
impl<T> Status<T> {
#[inline]
pub fn is_complete(&self) -> bool {
match *self {
Status::Complete(..) => true,
Status::Partial => false,
}
}
#[inline]
pub fn is_partial(&self) -> bool {
match *self {
Status::Complete(..) => false,
Status::Partial => true,
}
}
#[inline]
pub fn unwrap(self) -> T {
match self {
Status::Complete(t) => t,
Status::Partial => panic!("Tried to unwrap Status::Partial"),
}
}
}
#[derive(Clone, Debug, Default)]
pub struct ParserConfig {
allow_spaces_after_header_name_in_responses: bool,
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,
}
impl ParserConfig {
pub fn allow_spaces_after_header_name_in_responses(&mut self, value: bool) -> &mut Self {
self.allow_spaces_after_header_name_in_responses = value;
self
}
pub fn allow_multiple_spaces_in_request_line_delimiters(&mut self, value: bool) -> &mut Self {
self.allow_multiple_spaces_in_request_line_delimiters = value;
self
}
pub fn multiple_spaces_in_request_line_delimiters_are_allowed(&self) -> bool {
self.allow_multiple_spaces_in_request_line_delimiters
}
pub fn allow_multiple_spaces_in_response_status_delimiters(
&mut self,
value: bool,
) -> &mut Self {
self.allow_multiple_spaces_in_response_status_delimiters = value;
self
}
pub fn multiple_spaces_in_response_status_delimiters_are_allowed(&self) -> bool {
self.allow_multiple_spaces_in_response_status_delimiters
}
pub fn allow_obsolete_multiline_headers_in_responses(&mut self, value: bool) -> &mut Self {
self.allow_obsolete_multiline_headers_in_responses = value;
self
}
pub fn obsolete_multiline_headers_in_responses_are_allowed(&self) -> bool {
self.allow_obsolete_multiline_headers_in_responses
}
pub fn allow_space_before_first_header_name(&mut self, value: bool) -> &mut Self {
self.allow_space_before_first_header_name = value;
self
}
pub fn space_before_first_header_name_are_allowed(&self) -> bool {
self.allow_space_before_first_header_name
}
pub fn ignore_invalid_headers_in_responses(&mut self, value: bool) -> &mut Self {
self.ignore_invalid_headers_in_responses = value;
self
}
pub fn ignore_invalid_headers_in_requests(&mut self, value: bool) -> &mut Self {
self.ignore_invalid_headers_in_requests = value;
self
}
}
#[inline]
fn skip_empty_lines(bytes: &mut Bytes<'_>) -> Result<()> {
loop {
let b = bytes.peek();
match b {
Some(b'\r') => {
unsafe { bytes.bump() };
expect!(bytes.next() == b'\n' => Err(Error::NewLine));
}
Some(b'\n') => {
unsafe {
bytes.bump();
}
}
Some(..) => {
bytes.slice();
return Ok(Status::Complete(()));
}
None => return Ok(Status::Partial),
}
}
}
#[inline]
fn skip_spaces(bytes: &mut Bytes<'_>) -> Result<()> {
loop {
let b = bytes.peek();
match b {
Some(b' ') => {
unsafe { bytes.bump() };
}
Some(..) => {
bytes.slice();
return Ok(Status::Complete(()));
}
None => return Ok(Status::Partial),
}
}
}
#[inline]
pub fn parse_request<'b>(src: &'b [u8]) -> Result<(usize, &'b str, &'b str, u8)> {
let mut bytes = Bytes::new(src);
let method = complete!(parse_method(&mut bytes));
let path = complete!(parse_uri(&mut bytes));
let version = complete!(parse_version(&mut bytes));
newline!(bytes);
Ok(Status::Complete((bytes.slice_pos(), method, path, version)))
}
#[inline]
pub fn parse_response<'b>(buf: &'b [u8]) -> Result<(usize, u8, u16, &'b str)> {
let mut bytes = Bytes::new(buf);
complete!(skip_empty_lines(&mut bytes));
let version = complete!(parse_version(&mut bytes));
complete!(skip_empty_lines(&mut bytes));
space!(bytes or Error::Version);
complete!(skip_spaces(&mut bytes));
let code = complete!(parse_code(&mut bytes));
let reason = match next!(bytes) {
b' ' => {
complete!(skip_spaces(&mut bytes));
bytes.slice();
complete!(parse_reason(&mut bytes))
}
b'\r' => {
expect!(bytes.next() == b'\n' => Err(Error::Status));
bytes.slice();
""
}
b'\n' => {
bytes.slice();
""
}
_ => return Err(Error::Status),
};
Ok(Status::Complete((bytes.slice_pos(), version, code, reason)))
}
#[derive(Copy, Clone, Eq, PartialEq, Debug, Default)]
pub struct Header {
pub name_start: usize,
pub name_end: usize,
pub value_start: usize,
pub value_end: usize,
}
#[inline]
#[allow(missing_docs)]
pub fn parse_version<'a>(bytes: &mut Bytes<'a>) -> Result<u8> {
if let Some(eight) = bytes.peek_n::<[u8; 8]>(8) {
const H10: u64 = u64::from_ne_bytes(*b"HTTP/1.0");
const H11: u64 = u64::from_ne_bytes(*b"HTTP/1.1");
unsafe {
bytes.advance(8);
}
return match u64::from_ne_bytes(eight) {
H10 => Ok(Status::Complete(0)),
H11 => Ok(Status::Complete(1)),
_ => Err(Error::Version),
};
}
expect!(bytes.next() == b'H' => Err(Error::Version));
expect!(bytes.next() == b'T' => Err(Error::Version));
expect!(bytes.next() == b'T' => Err(Error::Version));
expect!(bytes.next() == b'P' => Err(Error::Version));
expect!(bytes.next() == b'/' => Err(Error::Version));
expect!(bytes.next() == b'1' => Err(Error::Version));
expect!(bytes.next() == b'.' => Err(Error::Version));
Ok(Status::Partial)
}
#[inline]
#[doc(hidden)]
#[allow(missing_docs)]
fn parse_method<'a>(mut bytes: &mut Bytes<'a>) -> Result<&'a str> {
complete!(skip_empty_lines(bytes));
const GET: [u8; 4] = *b"GET ";
const POST: [u8; 4] = *b"POST";
match bytes.peek_n::<[u8; 4]>(4) {
Some(GET) => {
let method = unsafe {
bytes.advance(4); str::from_utf8_unchecked(bytes.slice_skip(1)) };
complete!(skip_spaces(bytes));
Ok(Status::Complete(method))
}
Some(POST) if unsafe { bytes.peek_ahead(4) } == Some(b' ') => {
let method = unsafe {
bytes.advance(5); str::from_utf8_unchecked(bytes.slice_skip(1)) };
complete!(skip_spaces(bytes));
Ok(Status::Complete(method))
}
_ => parse_token(&mut bytes),
}
}
#[inline]
fn parse_reason<'a>(bytes: &mut Bytes<'a>) -> Result<&'a str> {
let mut seen_obs_text = false;
loop {
let b = next!(bytes);
if b == b'\r' {
expect!(bytes.next() == b'\n' => Err(Error::Status));
return Ok(Status::Complete(
unsafe {
let bytes = bytes.slice_skip(2);
if !seen_obs_text {
str::from_utf8_unchecked(bytes)
} else {
""
}
},
));
} else if b == b'\n' {
return Ok(Status::Complete(
unsafe {
let bytes = bytes.slice_skip(1);
if !seen_obs_text {
str::from_utf8_unchecked(bytes)
} else {
""
}
},
));
} else if !(b == 0x09 || b == b' ' || (0x21..=0x7E).contains(&b) || b >= 0x80) {
return Err(Error::Status);
} else if b >= 0x80 {
seen_obs_text = true;
}
}
}
#[inline]
fn parse_token<'a>(bytes: &mut Bytes<'a>) -> Result<&'a str> {
let b = next!(bytes);
if !is_method_token(b) {
return Err(Error::Token);
}
loop {
let b = next!(bytes);
if b == b' ' {
return Ok(Status::Complete(
unsafe { str::from_utf8_unchecked(bytes.slice_skip(1)) },
));
} else if !is_method_token(b) {
return Err(Error::Token);
}
}
}
#[inline]
#[allow(missing_docs)]
fn parse_uri<'a>(bytes: &mut Bytes<'a>) -> Result<&'a str> {
let start = bytes.pos();
simd::match_uri_vectored(bytes);
let end = bytes.pos();
if next!(bytes) == b' ' {
if end == start {
return Err(Error::Token);
}
match str::from_utf8(unsafe { bytes.slice_skip(1) }) {
Ok(uri) => {
complete!(skip_spaces(bytes));
Ok(Status::Complete(uri))
}
Err(_) => Err(Error::Token),
}
} else {
Err(Error::Token)
}
}
#[inline]
fn parse_code(bytes: &mut Bytes<'_>) -> Result<u16> {
let hundreds = expect!(bytes.next() == b'0'..=b'9' => Err(Error::Status));
let tens = expect!(bytes.next() == b'0'..=b'9' => Err(Error::Status));
let ones = expect!(bytes.next() == b'0'..=b'9' => Err(Error::Status));
Ok(Status::Complete(
(hundreds - b'0') as u16 * 100 + (tens - b'0') as u16 * 10 + (ones - b'0') as u16,
))
}
pub fn parse_header(src: &[u8]) -> Result<(usize, Option<Header>)> {
let mut bytes = Bytes::new(src);
parse_header_iter_uninit(&mut bytes, &HeaderParserConfig::default())
}
#[derive(Clone, Debug, Default)]
struct HeaderParserConfig {
allow_spaces_after_header_name: bool,
allow_obsolete_multiline_headers: bool,
}
fn parse_header_iter_uninit<'a>(
bytes: &mut Bytes<'a>,
config: &HeaderParserConfig,
) -> Result<(usize, Option<Header>)> {
let start = bytes.as_ref().as_ptr() as usize;
let mut header = Header::default();
macro_rules! maybe_continue_after_obsolete_line_folding {
($bytes:ident, $label:lifetime) => {
if config.allow_obsolete_multiline_headers {
match $bytes.peek() {
None => {
return Ok(Status::Partial);
}
Some(b' ') | Some(b'\t') => {
continue $label;
}
_ => {
},
}
}
}
}
loop {
macro_rules! handle_invalid_char {
($bytes:ident, $b:ident, $err:ident) => {
return Err(Error::$err);
};
}
let b = next!(bytes);
if b == b'\r' {
expect!(bytes.next() == b'\n' => Err(Error::NewLine));
let end = bytes.as_ref().as_ptr() as usize;
return Ok(Status::Complete((end - start, None)));
}
if b == b'\n' {
let end = bytes.as_ref().as_ptr() as usize;
return Ok(Status::Complete((end - start, None)));
}
if !is_header_name_token(b) {
handle_invalid_char!(bytes, b, HeaderName);
}
header.name_start = bytes.slice_pos() - 1;
#[allow(clippy::never_loop)]
'name: loop {
simd::match_header_name_vectored(bytes);
let mut b = next!(bytes);
header.name_end = bytes.slice_pos() - 1;
bytes.commit();
if b == b':' {
break 'name;
}
if config.allow_spaces_after_header_name {
while b == b' ' || b == b'\t' {
b = next!(bytes);
if b == b':' {
bytes.commit();
break 'name;
}
}
}
handle_invalid_char!(bytes, b, HeaderName);
}
let mut b;
header.value_start = bytes.slice_pos() + 1;
#[allow(clippy::never_loop)]
let value_slice = 'value: loop {
'whitespace_after_colon: loop {
b = next!(bytes);
if b == b' ' || b == b'\t' {
bytes.slice();
continue 'whitespace_after_colon;
}
if is_header_value_token(b) {
break 'whitespace_after_colon;
}
if b == b'\r' {
expect!(bytes.next() == b'\n' => Err(Error::HeaderValue));
} else if b != b'\n' {
handle_invalid_char!(bytes, b, HeaderValue);
}
maybe_continue_after_obsolete_line_folding!(bytes, 'whitespace_after_colon);
let whitespace_slice = bytes.slice();
break 'value &whitespace_slice[0..0];
}
'value_lines: loop {
simd::match_header_value_vectored(bytes);
let b = next!(bytes);
let skip = if b == b'\r' {
expect!(bytes.next() == b'\n' => Err(Error::HeaderValue));
2
} else if b == b'\n' {
1
} else {
handle_invalid_char!(bytes, b, HeaderValue);
};
maybe_continue_after_obsolete_line_folding!(bytes, 'value_lines);
unsafe {
break 'value bytes.slice_skip(skip);
}
}
};
header.value_end = bytes.slice_pos();
if let Some(last_visible) = value_slice
.iter()
.rposition(|b| *b != b' ' && *b != b'\t' && *b != b'\r' && *b != b'\n')
{
header.value_end = header.value_start + last_visible + 1;
}
return Ok(Status::Complete((bytes.slice_pos(), Some(header))));
}
}
pub fn parse_chunk_size(buf: &[u8]) -> result::Result<Status<(usize, u64)>, InvalidChunkSize> {
const RADIX: u64 = 16;
let mut bytes = Bytes::new(buf);
let mut size = 0;
let mut in_chunk_size = true;
let mut in_ext = false;
let mut count = 0;
loop {
let b = next!(bytes);
match b {
b'0'..=b'9' if in_chunk_size => {
if count > 15 {
return Err(InvalidChunkSize);
}
count += 1;
if cfg!(debug_assertions) && size > (u64::MAX / RADIX) {
return Err(InvalidChunkSize);
}
size *= RADIX;
size += (b - b'0') as u64;
}
b'a'..=b'f' | b'A'..=b'F' if in_chunk_size => {
if count > 15 {
return Err(InvalidChunkSize);
}
count += 1;
if cfg!(debug_assertions) && size > (u64::MAX / RADIX) {
return Err(InvalidChunkSize);
}
size *= RADIX;
size += ((b | 0x20) + 10 - b'a') as u64;
}
b'\r' => match next!(bytes) {
b'\n' => break,
_ => return Err(InvalidChunkSize),
},
b';' if !in_ext => {
in_ext = true;
in_chunk_size = false;
}
b'\t' | b' ' if !in_ext && !in_chunk_size => {}
b'\t' | b' ' if in_chunk_size => in_chunk_size = false,
_ if in_ext => {}
_ => return Err(InvalidChunkSize),
}
}
Ok(Status::Complete((bytes.slice_pos(), size)))
}
#[cfg(test)]
mod tests {
use super::{parse_chunk_size, Error, Request, Response, Status, EMPTY_HEADER};
const NUM_OF_HEADERS: usize = 4;
macro_rules! req {
($name:ident, $buf:expr, |$arg:ident| $body:expr) => {
req! {$name, $buf, Ok(Status::Complete($buf.len())), |$arg| $body }
};
($name:ident, $buf:expr, $len:expr, |$arg:ident| $body:expr) => {
#[test]
fn $name() {
let mut headers = [EMPTY_HEADER; NUM_OF_HEADERS];
let mut req = Request::new(&mut headers[..]);
let status = req.parse($buf.as_ref());
assert_eq!(status, $len);
closure(req);
fn closure($arg: Request) {
$body
}
}
};
}
req! {
test_request_simple,
b"GET / HTTP/1.1\r\n\r\n",
|req| {
assert_eq!(req.method.unwrap(), "GET");
assert_eq!(req.path.unwrap(), "/");
assert_eq!(req.version.unwrap(), 1);
assert_eq!(req.headers.len(), 0);
}
}
req! {
test_request_simple_with_query_params,
b"GET /thing?data=a HTTP/1.1\r\n\r\n",
|req| {
assert_eq!(req.method.unwrap(), "GET");
assert_eq!(req.path.unwrap(), "/thing?data=a");
assert_eq!(req.version.unwrap(), 1);
assert_eq!(req.headers.len(), 0);
}
}
req! {
test_request_simple_with_whatwg_query_params,
b"GET /thing?data=a^ HTTP/1.1\r\n\r\n",
|req| {
assert_eq!(req.method.unwrap(), "GET");
assert_eq!(req.path.unwrap(), "/thing?data=a^");
assert_eq!(req.version.unwrap(), 1);
assert_eq!(req.headers.len(), 0);
}
}
req! {
test_request_headers,
b"GET / HTTP/1.1\r\nHost: foo.com\r\nCookie: \r\n\r\n",
|req| {
assert_eq!(req.method.unwrap(), "GET");
assert_eq!(req.path.unwrap(), "/");
assert_eq!(req.version.unwrap(), 1);
assert_eq!(req.headers.len(), 2);
assert_eq!(req.headers[0].name, "Host");
assert_eq!(req.headers[0].value, b"foo.com");
assert_eq!(req.headers[1].name, "Cookie");
assert_eq!(req.headers[1].value, b"");
}
}
req! {
test_request_headers_optional_whitespace,
b"GET / HTTP/1.1\r\nHost: \tfoo.com\t \r\nCookie: \t \r\n\r\n",
|req| {
assert_eq!(req.method.unwrap(), "GET");
assert_eq!(req.path.unwrap(), "/");
assert_eq!(req.version.unwrap(), 1);
assert_eq!(req.headers.len(), 2);
assert_eq!(req.headers[0].name, "Host");
assert_eq!(req.headers[0].value, b"foo.com");
assert_eq!(req.headers[1].name, "Cookie");
assert_eq!(req.headers[1].value, b"");
}
}
req! {
test_request_header_value_htab_short,
b"GET / HTTP/1.1\r\nUser-Agent: some\tagent\r\n\r\n",
|req| {
assert_eq!(req.method.unwrap(), "GET");
assert_eq!(req.path.unwrap(), "/");
assert_eq!(req.version.unwrap(), 1);
assert_eq!(req.headers.len(), 1);
assert_eq!(req.headers[0].name, "User-Agent");
assert_eq!(req.headers[0].value, b"some\tagent");
}
}
req! {
test_request_header_value_htab_med,
b"GET / HTTP/1.1\r\nUser-Agent: 1234567890some\tagent\r\n\r\n",
|req| {
assert_eq!(req.method.unwrap(), "GET");
assert_eq!(req.path.unwrap(), "/");
assert_eq!(req.version.unwrap(), 1);
assert_eq!(req.headers.len(), 1);
assert_eq!(req.headers[0].name, "User-Agent");
assert_eq!(req.headers[0].value, b"1234567890some\tagent");
}
}
req! {
test_request_header_value_htab_long,
b"GET / HTTP/1.1\r\nUser-Agent: 1234567890some\t1234567890agent1234567890\r\n\r\n",
|req| {
assert_eq!(req.method.unwrap(), "GET");
assert_eq!(req.path.unwrap(), "/");
assert_eq!(req.version.unwrap(), 1);
assert_eq!(req.headers.len(), 1);
assert_eq!(req.headers[0].name, "User-Agent");
assert_eq!(req.headers[0].value, &b"1234567890some\t1234567890agent1234567890"[..]);
}
}
req! {
test_request_header_no_space_after_colon,
b"GET / HTTP/1.1\r\nUser-Agent:omg-no-space1234567890some1234567890agent1234567890\r\n\r\n",
|req| {
assert_eq!(req.method.unwrap(), "GET");
assert_eq!(req.path.unwrap(), "/");
assert_eq!(req.version.unwrap(), 1);
assert_eq!(req.headers.len(), 1);
assert_eq!(req.headers[0].name, "User-Agent");
assert_eq!(req.headers[0].value, &b"omg-no-space1234567890some1234567890agent1234567890"[..]);
}
}
req! {
test_request_headers_max,
b"GET / HTTP/1.1\r\nA: A\r\nB: B\r\nC: C\r\nD: D\r\n\r\n",
|req| {
assert_eq!(req.headers.len(), NUM_OF_HEADERS);
}
}
req! {
test_request_multibyte,
b"GET / HTTP/1.1\r\nHost: foo.com\r\nUser-Agent: \xe3\x81\xb2\xe3/1.0\r\n\r\n",
|req| {
assert_eq!(req.method.unwrap(), "GET");
assert_eq!(req.path.unwrap(), "/");
assert_eq!(req.version.unwrap(), 1);
assert_eq!(req.headers.len(), 2);
assert_eq!(req.headers[0].name, "Host");
assert_eq!(req.headers[0].value, b"foo.com");
assert_eq!(req.headers[1].name, "User-Agent");
assert_eq!(req.headers[1].value, b"\xe3\x81\xb2\xe3/1.0");
}
}
req! {
test_request_one_byte_method,
b"G", Ok(Status::Partial),
|_req| {}
}
req! {
test_request_partial_method,
b"GE", Ok(Status::Partial),
|_req| {}
}
req! {
test_request_method_no_delimiter,
b"GET", Ok(Status::Partial),
|_req| {}
}
req! {
test_request_method_only,
b"GET ", Ok(Status::Partial),
|_req| {}
}
req! {
test_request_partial,
b"GET / HTTP/1.1\r\n\r", Ok(Status::Partial),
|_req| {}
}
req! {
test_request_partial_version,
b"GET / HTTP/1.", Ok(Status::Partial),
|_req| {}
}
req! {
test_request_method_path_no_delimiter,
b"GET /", Ok(Status::Partial),
|_req| {}
}
req! {
test_request_method_path_only,
b"GET / ", Ok(Status::Partial),
|_req| {}
}
req! {
test_request_partial_parses_headers_as_much_as_it_can,
b"GET / HTTP/1.1\r\nHost: yolo\r\n",
Ok(crate::Status::Partial),
|req| {
assert_eq!(req.method.unwrap(), "GET");
assert_eq!(req.path.unwrap(), "/");
assert_eq!(req.version.unwrap(), 1);
assert_eq!(req.headers.len(), NUM_OF_HEADERS); assert_eq!(req.headers[0].name, "Host");
assert_eq!(req.headers[0].value, b"yolo");
}
}
req! {
test_request_newlines,
b"GET / HTTP/1.1\nHost: foo.bar\n\n",
|_r| {}
}
req! {
test_request_empty_lines_prefix,
b"\r\n\r\nGET / HTTP/1.1\r\n\r\n",
|req| {
assert_eq!(req.method.unwrap(), "GET");
assert_eq!(req.path.unwrap(), "/");
assert_eq!(req.version.unwrap(), 1);
assert_eq!(req.headers.len(), 0);
}
}
req! {
test_request_empty_lines_prefix_lf_only,
b"\n\nGET / HTTP/1.1\n\n",
|req| {
assert_eq!(req.method.unwrap(), "GET");
assert_eq!(req.path.unwrap(), "/");
assert_eq!(req.version.unwrap(), 1);
assert_eq!(req.headers.len(), 0);
}
}
req! {
test_request_path_backslash,
b"\n\nGET /\\?wayne\\=5 HTTP/1.1\n\n",
|req| {
assert_eq!(req.method.unwrap(), "GET");
assert_eq!(req.path.unwrap(), "/\\?wayne\\=5");
assert_eq!(req.version.unwrap(), 1);
assert_eq!(req.headers.len(), 0);
}
}
req! {
test_request_with_invalid_token_delimiter,
b"GET\n/ HTTP/1.1\r\nHost: foo.bar\r\n\r\n",
Err(crate::Error::Token),
|_r| {}
}
req! {
test_request_with_invalid_but_short_version,
b"GET / HTTP/1!",
Err(crate::Error::Version),
|_r| {}
}
req! {
test_request_with_empty_method,
b" / HTTP/1.1\r\n\r\n",
Err(crate::Error::Token),
|_r| {}
}
req! {
test_request_with_empty_path,
b"GET HTTP/1.1\r\n\r\n",
Err(crate::Error::Token),
|_r| {}
}
req! {
test_request_with_empty_method_and_path,
b" HTTP/1.1\r\n\r\n",
Err(crate::Error::Token),
|_r| {}
}
macro_rules! res {
($name:ident, $buf:expr, |$arg:ident| $body:expr) => {
res! {$name, $buf, Ok(Status::Complete($buf.len())), |$arg| $body }
};
($name:ident, $buf:expr, $len:expr, |$arg:ident| $body:expr) => {
#[test]
fn $name() {
let mut headers = [EMPTY_HEADER; NUM_OF_HEADERS];
let mut res = Response::new(&mut headers[..]);
let status = res.parse($buf.as_ref());
assert_eq!(status, $len);
closure(res);
fn closure($arg: Response) {
$body
}
}
};
}
res! {
test_response_simple,
b"HTTP/1.1 200 OK\r\n\r\n",
|res| {
assert_eq!(res.version.unwrap(), 1);
assert_eq!(res.code.unwrap(), 200);
assert_eq!(res.reason.unwrap(), "OK");
}
}
res! {
test_response_newlines,
b"HTTP/1.0 403 Forbidden\nServer: foo.bar\n\n",
|_r| {}
}
res! {
test_response_reason_missing,
b"HTTP/1.1 200 \r\n\r\n",
|res| {
assert_eq!(res.version.unwrap(), 1);
assert_eq!(res.code.unwrap(), 200);
assert_eq!(res.reason.unwrap(), "");
}
}
res! {
test_response_reason_missing_no_space,
b"HTTP/1.1 200\r\n\r\n",
|res| {
assert_eq!(res.version.unwrap(), 1);
assert_eq!(res.code.unwrap(), 200);
assert_eq!(res.reason.unwrap(), "");
}
}
res! {
test_response_reason_missing_no_space_with_headers,
b"HTTP/1.1 200\r\nFoo: bar\r\n\r\n",
|res| {
assert_eq!(res.version.unwrap(), 1);
assert_eq!(res.code.unwrap(), 200);
assert_eq!(res.reason.unwrap(), "");
assert_eq!(res.headers.len(), 1);
assert_eq!(res.headers[0].name, "Foo");
assert_eq!(res.headers[0].value, b"bar");
}
}
res! {
test_response_reason_with_space_and_tab,
b"HTTP/1.1 101 Switching Protocols\t\r\n\r\n",
|res| {
assert_eq!(res.version.unwrap(), 1);
assert_eq!(res.code.unwrap(), 101);
assert_eq!(res.reason.unwrap(), "Switching Protocols\t");
}
}
static RESPONSE_REASON_WITH_OBS_TEXT_BYTE: &[u8] = b"HTTP/1.1 200 X\xFFZ\r\n\r\n";
res! {
test_response_reason_with_obsolete_text_byte,
RESPONSE_REASON_WITH_OBS_TEXT_BYTE,
|res| {
assert_eq!(res.version.unwrap(), 1);
assert_eq!(res.code.unwrap(), 200);
assert_eq!(res.reason.unwrap(), "");
}
}
res! {
test_response_reason_with_nul_byte,
b"HTTP/1.1 200 \x00\r\n\r\n",
Err(crate::Error::Status),
|_res| {}
}
res! {
test_response_version_missing_space,
b"HTTP/1.1",
Ok(Status::Partial),
|_res| {}
}
res! {
test_response_code_missing_space,
b"HTTP/1.1 200",
Ok(Status::Partial),
|_res| {}
}
res! {
test_response_partial_parses_headers_as_much_as_it_can,
b"HTTP/1.1 200 OK\r\nServer: yolo\r\n",
Ok(crate::Status::Partial),
|res| {
assert_eq!(res.version.unwrap(), 1);
assert_eq!(res.code.unwrap(), 200);
assert_eq!(res.reason.unwrap(), "OK");
assert_eq!(res.headers.len(), NUM_OF_HEADERS); assert_eq!(res.headers[0].name, "Server");
assert_eq!(res.headers[0].value, b"yolo");
}
}
res! {
test_response_empty_lines_prefix_lf_only,
b"\n\nHTTP/1.1 200 OK\n\n",
|_res| {}
}
res! {
test_response_no_cr,
b"HTTP/1.0 200\nContent-type: text/html\n\n",
|res| {
assert_eq!(res.version.unwrap(), 0);
assert_eq!(res.code.unwrap(), 200);
assert_eq!(res.reason.unwrap(), "");
assert_eq!(res.headers.len(), 1);
assert_eq!(res.headers[0].name, "Content-type");
assert_eq!(res.headers[0].value, b"text/html");
}
}
#[test]
fn partial_permutations() {
let req_str = "GET / HTTP/1.1\r\n\r\n";
let mut headers = [EMPTY_HEADER; NUM_OF_HEADERS];
let mut req = Request::new(&mut headers[..]);
for i in 0..req_str.len() {
let status = req.parse(&req_str.as_bytes()[..i]);
assert_eq!(
status,
Ok(Status::Partial),
"partial request line should return partial. \
Portion which failed: '{seg}' (below {i})",
seg = &req_str[..i]
);
}
}
static RESPONSE_WITH_WHITESPACE_BETWEEN_HEADER_NAME_AND_COLON: &[u8] =
b"HTTP/1.1 200 OK\r\nAccess-Control-Allow-Credentials : true\r\nBread: baguette\r\n\r\n";
#[test]
fn test_forbid_response_with_whitespace_between_header_name_and_colon() {
let mut headers = [EMPTY_HEADER; 2];
let mut response = Response::new(&mut headers[..]);
let result = response.parse(RESPONSE_WITH_WHITESPACE_BETWEEN_HEADER_NAME_AND_COLON);
assert_eq!(result, Err(crate::Error::HeaderName));
}
#[test]
fn test_allow_response_with_whitespace_between_header_name_and_colon() {
let mut headers = [EMPTY_HEADER; 2];
let mut response = Response::new(&mut headers[..]);
let result = crate::ParserConfig::default()
.allow_spaces_after_header_name_in_responses(true)
.parse_response(
&mut response,
RESPONSE_WITH_WHITESPACE_BETWEEN_HEADER_NAME_AND_COLON,
);
assert_eq!(result, Ok(Status::Complete(77)));
assert_eq!(response.version.unwrap(), 1);
assert_eq!(response.code.unwrap(), 200);
assert_eq!(response.reason.unwrap(), "OK");
assert_eq!(response.headers.len(), 2);
assert_eq!(response.headers[0].name, "Access-Control-Allow-Credentials");
assert_eq!(response.headers[0].value, &b"true"[..]);
assert_eq!(response.headers[1].name, "Bread");
assert_eq!(response.headers[1].value, &b"baguette"[..]);
}
#[test]
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()
.ignore_invalid_headers_in_responses(true)
.parse_response(
&mut response,
RESPONSE_WITH_WHITESPACE_BETWEEN_HEADER_NAME_AND_COLON,
);
assert_eq!(result, Ok(Status::Complete(77)));
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, "Bread");
assert_eq!(response.headers[0].value, &b"baguette"[..]);
}
static REQUEST_WITH_WHITESPACE_BETWEEN_HEADER_NAME_AND_COLON: &[u8] =
b"GET / HTTP/1.1\r\nHost : localhost\r\n\r\n";
#[test]
fn test_forbid_request_with_whitespace_between_header_name_and_colon() {
let mut headers = [EMPTY_HEADER; 1];
let mut request = Request::new(&mut headers[..]);
let result = request.parse(REQUEST_WITH_WHITESPACE_BETWEEN_HEADER_NAME_AND_COLON);
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";
#[test]
fn test_forbid_response_with_obsolete_line_folding_at_start() {
let mut headers = [EMPTY_HEADER; 1];
let mut response = Response::new(&mut headers[..]);
let result = response.parse(RESPONSE_WITH_OBSOLETE_LINE_FOLDING_AT_START);
assert_eq!(result, Err(crate::Error::HeaderName));
}
#[test]
fn test_allow_response_with_obsolete_line_folding_at_start() {
let mut headers = [EMPTY_HEADER; 1];
let mut response = Response::new(&mut headers[..]);
let result = crate::ParserConfig::default()
.allow_obsolete_multiline_headers_in_responses(true)
.parse_response(&mut response, RESPONSE_WITH_OBSOLETE_LINE_FOLDING_AT_START);
assert_eq!(
result,
Ok(Status::Complete(
RESPONSE_WITH_OBSOLETE_LINE_FOLDING_AT_START.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, "Line-Folded-Header");
assert_eq!(response.headers[0].value, &b"hello there"[..]);
}
static RESPONSE_WITH_OBSOLETE_LINE_FOLDING_AT_END: &[u8] =
b"HTTP/1.1 200 OK\r\nLine-Folded-Header: hello there\r\n \r\n \r\n\r\n";
#[test]
fn test_forbid_response_with_obsolete_line_folding_at_end() {
let mut headers = [EMPTY_HEADER; 1];
let mut response = Response::new(&mut headers[..]);
let result = response.parse(RESPONSE_WITH_OBSOLETE_LINE_FOLDING_AT_END);
assert_eq!(result, Err(crate::Error::HeaderName));
}
#[test]
fn test_allow_response_with_obsolete_line_folding_at_end() {
let mut headers = [EMPTY_HEADER; 1];
let mut response = Response::new(&mut headers[..]);
let result = crate::ParserConfig::default()
.allow_obsolete_multiline_headers_in_responses(true)
.parse_response(&mut response, RESPONSE_WITH_OBSOLETE_LINE_FOLDING_AT_END);
assert_eq!(
result,
Ok(Status::Complete(
RESPONSE_WITH_OBSOLETE_LINE_FOLDING_AT_END.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, "Line-Folded-Header");
assert_eq!(response.headers[0].value, &b"hello there"[..]);
}
static RESPONSE_WITH_OBSOLETE_LINE_FOLDING_IN_MIDDLE: &[u8] =
b"HTTP/1.1 200 OK\r\nLine-Folded-Header: hello \r\n \r\n there\r\n\r\n";
#[test]
fn test_forbid_response_with_obsolete_line_folding_in_middle() {
let mut headers = [EMPTY_HEADER; 1];
let mut response = Response::new(&mut headers[..]);
let result = response.parse(RESPONSE_WITH_OBSOLETE_LINE_FOLDING_IN_MIDDLE);
assert_eq!(result, Err(crate::Error::HeaderName));
}
#[test]
fn test_allow_response_with_obsolete_line_folding_in_middle() {
let mut headers = [EMPTY_HEADER; 1];
let mut response = Response::new(&mut headers[..]);
let result = crate::ParserConfig::default()
.allow_obsolete_multiline_headers_in_responses(true)
.parse_response(&mut response, RESPONSE_WITH_OBSOLETE_LINE_FOLDING_IN_MIDDLE);
assert_eq!(
result,
Ok(Status::Complete(
RESPONSE_WITH_OBSOLETE_LINE_FOLDING_IN_MIDDLE.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, "Line-Folded-Header");
assert_eq!(response.headers[0].value, &b"hello \r\n \r\n there"[..]);
}
static RESPONSE_WITH_OBSOLETE_LINE_FOLDING_IN_EMPTY_HEADER: &[u8] =
b"HTTP/1.1 200 OK\r\nLine-Folded-Header: \r\n \r\n \r\n\r\n";
#[test]
fn test_forbid_response_with_obsolete_line_folding_in_empty_header() {
let mut headers = [EMPTY_HEADER; 1];
let mut response = Response::new(&mut headers[..]);
let result = response.parse(RESPONSE_WITH_OBSOLETE_LINE_FOLDING_IN_EMPTY_HEADER);
assert_eq!(result, Err(crate::Error::HeaderName));
}
#[test]
fn test_allow_response_with_obsolete_line_folding_in_empty_header() {
let mut headers = [EMPTY_HEADER; 1];
let mut response = Response::new(&mut headers[..]);
let result = crate::ParserConfig::default()
.allow_obsolete_multiline_headers_in_responses(true)
.parse_response(
&mut response,
RESPONSE_WITH_OBSOLETE_LINE_FOLDING_IN_EMPTY_HEADER,
);
assert_eq!(
result,
Ok(Status::Complete(
RESPONSE_WITH_OBSOLETE_LINE_FOLDING_IN_EMPTY_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, "Line-Folded-Header");
assert_eq!(response.headers[0].value, &b""[..]);
}
#[test]
fn test_chunk_size() {
assert_eq!(parse_chunk_size(b"0\r\n"), Ok(Status::Complete((3, 0))));
assert_eq!(
parse_chunk_size(b"12\r\nchunk"),
Ok(Status::Complete((4, 18)))
);
assert_eq!(
parse_chunk_size(b"3086d\r\n"),
Ok(Status::Complete((7, 198765)))
);
assert_eq!(
parse_chunk_size(b"3735AB1;foo bar*\r\n"),
Ok(Status::Complete((18, 57891505)))
);
assert_eq!(
parse_chunk_size(b"3735ab1 ; baz \r\n"),
Ok(Status::Complete((16, 57891505)))
);
assert_eq!(parse_chunk_size(b"77a65\r"), Ok(Status::Partial));
assert_eq!(parse_chunk_size(b"ab"), Ok(Status::Partial));
assert_eq!(
parse_chunk_size(b"567f8a\rfoo"),
Err(crate::InvalidChunkSize)
);
assert_eq!(
parse_chunk_size(b"567f8a\rfoo"),
Err(crate::InvalidChunkSize)
);
assert_eq!(
parse_chunk_size(b"567xf8a\r\n"),
Err(crate::InvalidChunkSize)
);
assert_eq!(
parse_chunk_size(b"ffffffffffffffff\r\n"),
Ok(Status::Complete((18, u64::MAX)))
);
assert_eq!(
parse_chunk_size(b"1ffffffffffffffff\r\n"),
Err(crate::InvalidChunkSize)
);
assert_eq!(
parse_chunk_size(b"Affffffffffffffff\r\n"),
Err(crate::InvalidChunkSize)
);
assert_eq!(
parse_chunk_size(b"fffffffffffffffff\r\n"),
Err(crate::InvalidChunkSize)
);
}
static RESPONSE_WITH_MULTIPLE_SPACE_DELIMITERS: &[u8] = b"HTTP/1.1 200 OK\r\n\r\n";
#[test]
fn test_forbid_response_with_multiple_space_delimiters() {
let mut headers = [EMPTY_HEADER; NUM_OF_HEADERS];
let mut response = Response::new(&mut headers[..]);
let result = response.parse(RESPONSE_WITH_MULTIPLE_SPACE_DELIMITERS);
assert_eq!(result, Err(crate::Error::Status));
}
#[test]
fn test_allow_response_with_multiple_space_delimiters() {
let mut headers = [EMPTY_HEADER; NUM_OF_HEADERS];
let mut response = Response::new(&mut headers[..]);
let result = crate::ParserConfig::default()
.allow_multiple_spaces_in_response_status_delimiters(true)
.parse_response(&mut response, RESPONSE_WITH_MULTIPLE_SPACE_DELIMITERS);
assert_eq!(
result,
Ok(Status::Complete(
RESPONSE_WITH_MULTIPLE_SPACE_DELIMITERS.len()
))
);
assert_eq!(response.version.unwrap(), 1);
assert_eq!(response.code.unwrap(), 200);
assert_eq!(response.reason.unwrap(), "OK");
assert_eq!(response.headers.len(), 0);
}
static RESPONSE_WITH_WEIRD_WHITESPACE_DELIMITERS: &[u8] = b"HTTP/1.1 200\rOK\r\n\r\n";
#[test]
fn test_forbid_response_with_weird_whitespace_delimiters() {
let mut headers = [EMPTY_HEADER; NUM_OF_HEADERS];
let mut response = Response::new(&mut headers[..]);
let result = response.parse(RESPONSE_WITH_WEIRD_WHITESPACE_DELIMITERS);
assert_eq!(result, Err(crate::Error::Status));
}
#[test]
fn test_still_forbid_response_with_weird_whitespace_delimiters() {
let mut headers = [EMPTY_HEADER; NUM_OF_HEADERS];
let mut response = Response::new(&mut headers[..]);
let result = crate::ParserConfig::default()
.allow_multiple_spaces_in_response_status_delimiters(true)
.parse_response(&mut response, RESPONSE_WITH_WEIRD_WHITESPACE_DELIMITERS);
assert_eq!(result, Err(crate::Error::Status));
}
static REQUEST_WITH_MULTIPLE_SPACE_DELIMITERS: &[u8] = b"GET / HTTP/1.1\r\n\r\n";
#[test]
fn test_forbid_request_with_multiple_space_delimiters() {
let mut headers = [EMPTY_HEADER; NUM_OF_HEADERS];
let mut request = Request::new(&mut headers[..]);
let result = request.parse(REQUEST_WITH_MULTIPLE_SPACE_DELIMITERS);
assert_eq!(result, Err(crate::Error::Token));
}
#[test]
fn test_allow_request_with_multiple_space_delimiters() {
let mut headers = [EMPTY_HEADER; NUM_OF_HEADERS];
let mut request = Request::new(&mut headers[..]);
let result = crate::ParserConfig::default()
.allow_multiple_spaces_in_request_line_delimiters(true)
.parse_request(&mut request, REQUEST_WITH_MULTIPLE_SPACE_DELIMITERS);
assert_eq!(
result,
Ok(Status::Complete(
REQUEST_WITH_MULTIPLE_SPACE_DELIMITERS.len()
))
);
assert_eq!(request.method.unwrap(), "GET");
assert_eq!(request.path.unwrap(), "/");
assert_eq!(request.version.unwrap(), 1);
assert_eq!(request.headers.len(), 0);
}
static REQUEST_WITH_WEIRD_WHITESPACE_DELIMITERS: &[u8] = b"GET\r/\rHTTP/1.1\r\n\r\n";
#[test]
fn test_forbid_request_with_weird_whitespace_delimiters() {
let mut headers = [EMPTY_HEADER; NUM_OF_HEADERS];
let mut request = Request::new(&mut headers[..]);
let result = request.parse(REQUEST_WITH_WEIRD_WHITESPACE_DELIMITERS);
assert_eq!(result, Err(crate::Error::Token));
}
#[test]
fn test_still_forbid_request_with_weird_whitespace_delimiters() {
let mut headers = [EMPTY_HEADER; NUM_OF_HEADERS];
let mut request = Request::new(&mut headers[..]);
let result = crate::ParserConfig::default()
.allow_multiple_spaces_in_request_line_delimiters(true)
.parse_request(&mut request, REQUEST_WITH_WEIRD_WHITESPACE_DELIMITERS);
assert_eq!(result, Err(crate::Error::Token));
}
static REQUEST_WITH_MULTIPLE_SPACES_AND_BAD_PATH: &[u8] = b"GET /foo ohno HTTP/1.1\r\n\r\n";
#[test]
fn test_request_with_multiple_spaces_and_bad_path() {
let mut headers = [EMPTY_HEADER; NUM_OF_HEADERS];
let mut request = Request::new(&mut headers[..]);
let result = crate::ParserConfig::default()
.allow_multiple_spaces_in_request_line_delimiters(true)
.parse_request(&mut request, REQUEST_WITH_MULTIPLE_SPACES_AND_BAD_PATH);
assert_eq!(result, Err(crate::Error::Version));
}
static REQUEST_WITH_DEL_IN_PATH: &[u8] = b"GET /foo\x7Fohno HTTP/1.1\r\n\r\n";
#[test]
fn test_request_with_del_in_path() {
let mut headers = [EMPTY_HEADER; NUM_OF_HEADERS];
let mut request = Request::new(&mut headers[..]);
let result = crate::ParserConfig::default()
.allow_multiple_spaces_in_request_line_delimiters(true)
.parse_request(&mut request, crate::tests::REQUEST_WITH_DEL_IN_PATH);
assert_eq!(result, Err(crate::Error::Token));
}
#[test]
#[cfg_attr(miri, ignore)] fn test_all_utf8_char_in_paths() {
for i in 128..256 {
for j in 128..256 {
let mut headers = [EMPTY_HEADER; NUM_OF_HEADERS];
let mut request = Request::new(&mut headers[..]);
let bytes = [i as u8, j as u8];
match core::str::from_utf8(&bytes) {
Ok(s) => {
let first_line = format!("GET /{} HTTP/1.1\r\n\r\n", s);
let result = crate::ParserConfig::default()
.allow_multiple_spaces_in_request_line_delimiters(true)
.parse_request(&mut request, first_line.as_bytes());
assert_eq!(
result,
Ok(Status::Complete(20)),
"failed for utf8 char i: {}, j: {}",
i,
j
);
}
Err(_) => {
let mut first_line = b"GET /".to_vec();
first_line.extend(&bytes);
first_line.extend(b" HTTP/1.1\r\n\r\n");
let result = crate::ParserConfig::default()
.allow_multiple_spaces_in_request_line_delimiters(true)
.parse_request(&mut request, first_line.as_slice());
assert_eq!(
result,
Err(crate::Error::Token),
"failed for utf8 char i: {}, j: {}",
i,
j
);
}
};
if i < 0xe0 {
continue;
}
for k in 128..256 {
let mut headers = [EMPTY_HEADER; NUM_OF_HEADERS];
let mut request = Request::new(&mut headers[..]);
let bytes = [i as u8, j as u8, k as u8];
match core::str::from_utf8(&bytes) {
Ok(s) => {
let first_line = format!("GET /{} HTTP/1.1\r\n\r\n", s);
let result = crate::ParserConfig::default()
.allow_multiple_spaces_in_request_line_delimiters(true)
.parse_request(&mut request, first_line.as_bytes());
assert_eq!(
result,
Ok(Status::Complete(21)),
"failed for utf8 char i: {}, j: {}, k: {}",
i,
j,
k
);
}
Err(_) => {
let mut first_line = b"GET /".to_vec();
first_line.extend(&bytes);
first_line.extend(b" HTTP/1.1\r\n\r\n");
let result = crate::ParserConfig::default()
.allow_multiple_spaces_in_request_line_delimiters(true)
.parse_request(&mut request, first_line.as_slice());
assert_eq!(
result,
Err(crate::Error::Token),
"failed for utf8 char i: {}, j: {}, k: {}",
i,
j,
k
);
}
};
if i < 0xf0 {
continue;
}
for l in 128..256 {
let mut headers = [EMPTY_HEADER; NUM_OF_HEADERS];
let mut request = Request::new(&mut headers[..]);
let bytes = [i as u8, j as u8, k as u8, l as u8];
match core::str::from_utf8(&bytes) {
Ok(s) => {
let first_line = format!("GET /{} HTTP/1.1\r\n\r\n", s);
let result = crate::ParserConfig::default()
.allow_multiple_spaces_in_request_line_delimiters(true)
.parse_request(&mut request, first_line.as_bytes());
assert_eq!(
result,
Ok(Status::Complete(22)),
"failed for utf8 char i: {}, j: {}, k: {}, l: {}",
i,
j,
k,
l
);
}
Err(_) => {
let mut first_line = b"GET /".to_vec();
first_line.extend(&bytes);
first_line.extend(b" HTTP/1.1\r\n\r\n");
let result = crate::ParserConfig::default()
.allow_multiple_spaces_in_request_line_delimiters(true)
.parse_request(&mut request, first_line.as_slice());
assert_eq!(
result,
Err(crate::Error::Token),
"failed for utf8 char i: {}, j: {}, k: {}, l: {}",
i,
j,
k,
l
);
}
};
}
}
}
}
}
static RESPONSE_WITH_SPACES_IN_CODE: &[u8] = b"HTTP/1.1 99 200 OK\r\n\r\n";
#[test]
fn test_response_with_spaces_in_code() {
let mut headers = [EMPTY_HEADER; NUM_OF_HEADERS];
let mut response = Response::new(&mut headers[..]);
let result = crate::ParserConfig::default()
.allow_multiple_spaces_in_response_status_delimiters(true)
.parse_response(&mut response, RESPONSE_WITH_SPACES_IN_CODE);
assert_eq!(result, Err(crate::Error::Status));
}
#[test]
fn test_response_with_empty_header_name() {
const RESPONSE: &[u8] = b"HTTP/1.1 200 OK\r\n: hello\r\nBread: baguette\r\n\r\n";
let mut headers = [EMPTY_HEADER; 2];
let mut response = Response::new(&mut headers[..]);
let result = crate::ParserConfig::default()
.allow_spaces_after_header_name_in_responses(true)
.parse_response(&mut response, RESPONSE);
assert_eq!(result, Err(crate::Error::HeaderName));
let result = crate::ParserConfig::default()
.ignore_invalid_headers_in_responses(true)
.parse_response(&mut response, RESPONSE);
assert_eq!(result, Ok(Status::Complete(45)));
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, "Bread");
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] =
b"GET / HTTP/1.1\r\nAccess-Control-Allow-Credentials : 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()
.allow_spaces_after_header_name_in_responses(true)
.parse_request(&mut request, REQUEST);
assert_eq!(result, Err(crate::Error::HeaderName));
let result = crate::ParserConfig::default()
.ignore_invalid_headers_in_responses(true)
.parse_request(&mut request, REQUEST);
assert_eq!(result, Err(crate::Error::HeaderName));
}
#[test]
fn test_response_with_invalid_char_between_header_name_and_colon() {
const RESPONSE: &[u8] =
b"HTTP/1.1 200 OK\r\nAccess-Control-Allow-Credentials\xFF : true\r\nBread: baguette\r\n\r\n";
let mut headers = [EMPTY_HEADER; 2];
let mut response = Response::new(&mut headers[..]);
let result = crate::ParserConfig::default()
.allow_spaces_after_header_name_in_responses(true)
.parse_response(&mut response, RESPONSE);
assert_eq!(result, Err(crate::Error::HeaderName));
let result = crate::ParserConfig::default()
.ignore_invalid_headers_in_responses(true)
.parse_response(&mut response, RESPONSE);
assert_eq!(result, Ok(Status::Complete(79)));
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, "Bread");
assert_eq!(response.headers[0].value, &b"baguette"[..]);
}
#[test]
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";
let mut headers = [EMPTY_HEADER; 2];
let mut response = Response::new(&mut headers[..]);
let result = crate::ParserConfig::default().parse_response(&mut response, RESPONSE);
assert_eq!(result, Err(crate::Error::HeaderName));
let result = crate::ParserConfig::default()
.ignore_invalid_headers_in_responses(true)
.parse_response(&mut response, RESPONSE);
assert_eq!(result, Ok(Status::Complete(70)));
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, "Bread");
assert_eq!(response.headers[0].value, &b"baguette"[..]);
}
#[test]
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";
let mut headers = [EMPTY_HEADER; 2];
let mut response = Response::new(&mut headers[..]);
let result = crate::ParserConfig::default()
.allow_obsolete_multiline_headers_in_responses(true)
.allow_spaces_after_header_name_in_responses(true)
.parse_response(&mut response, RESPONSE);
assert_eq!(result, Err(crate::Error::HeaderName));
let result = crate::ParserConfig::default()
.ignore_invalid_headers_in_responses(true)
.parse_response(&mut response, RESPONSE);
assert_eq!(result, Ok(Status::Complete(81)));
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, "Bread");
assert_eq!(response.headers[0].value, &b"baguette"[..]);
}
#[test]
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";
let mut headers = [EMPTY_HEADER; 2];
let mut response = Response::new(&mut headers[..]);
let result = crate::ParserConfig::default().parse_response(&mut response, RESPONSE);
assert_eq!(result, Err(crate::Error::HeaderName));
let result = crate::ParserConfig::default()
.ignore_invalid_headers_in_responses(true)
.parse_response(&mut response, RESPONSE);
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] =
b"HTTP/1.1 200 OK\r\nAccess-Control-Allow-Cred\rentials: hello\r\nBread: baguette\r\n\r\n";
let mut headers = [EMPTY_HEADER; 2];
let mut response = Response::new(&mut headers[..]);
let result = crate::ParserConfig::default().parse_response(&mut response, RESPONSE);
assert_eq!(result, Err(crate::Error::HeaderName));
let result = crate::ParserConfig::default()
.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]
fn test_header_with_nul_in_whitespace_before_colon() {
const RESPONSE: &[u8] =
b"HTTP/1.1 200 OK\r\nAccess-Control-Allow-Credentials \0: hello\r\nBread: baguette\r\n\r\n";
let mut headers = [EMPTY_HEADER; 2];
let mut response = Response::new(&mut headers[..]);
let result = crate::ParserConfig::default()
.allow_spaces_after_header_name_in_responses(true)
.parse_response(&mut response, RESPONSE);
assert_eq!(result, Err(crate::Error::HeaderName));
let result = crate::ParserConfig::default()
.allow_spaces_after_header_name_in_responses(true)
.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]
fn test_header_with_nul_in_value() {
const RESPONSE: &[u8] =
b"HTTP/1.1 200 OK\r\nAccess-Control-Allow-Credentials: hell\0o\r\nBread: baguette\r\n\r\n";
let mut headers = [EMPTY_HEADER; 2];
let mut response = Response::new(&mut headers[..]);
let result = crate::ParserConfig::default().parse_response(&mut response, RESPONSE);
assert_eq!(result, Err(crate::Error::HeaderValue));
let result = crate::ParserConfig::default()
.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]
fn test_header_with_invalid_char_in_value() {
const RESPONSE: &[u8] =
b"HTTP/1.1 200 OK\r\nAccess-Control-Allow-Credentials: hell\x01o\r\nBread: baguette\r\n\r\n";
let mut headers = [EMPTY_HEADER; 2];
let mut response = Response::new(&mut headers[..]);
let result = crate::ParserConfig::default().parse_response(&mut response, RESPONSE);
assert_eq!(result, Err(crate::Error::HeaderValue));
let result = crate::ParserConfig::default()
.ignore_invalid_headers_in_responses(true)
.parse_response(&mut response, RESPONSE);
assert_eq!(result, Ok(Status::Complete(78)));
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, "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]
fn test_header_with_invalid_char_in_value_with_folding() {
const RESPONSE: &[u8] =
b"HTTP/1.1 200 OK\r\nAccess-Control-Allow-Credentials: hell\x01o \n world!\r\nBread: baguette\r\n\r\n";
let mut headers = [EMPTY_HEADER; 2];
let mut response = Response::new(&mut headers[..]);
let result = crate::ParserConfig::default().parse_response(&mut response, RESPONSE);
assert_eq!(result, Err(crate::Error::HeaderValue));
let result = crate::ParserConfig::default()
.ignore_invalid_headers_in_responses(true)
.parse_response(&mut response, RESPONSE);
assert_eq!(result, Ok(Status::Complete(88)));
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, "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]
fn test_method_within_buffer() {
const REQUEST: &[u8] = b"GET / HTTP/1.1\r\n\r\n";
let mut headers = [EMPTY_HEADER; 0];
let mut request = Request::new(&mut headers[..]);
crate::ParserConfig::default()
.parse_request(&mut request, REQUEST)
.unwrap();
let buf_end = unsafe { REQUEST.as_ptr().add(REQUEST.len()) };
let method = request.method.unwrap();
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"[..]);
}
#[test]
fn test_no_space_after_colon() {
let mut headers = [EMPTY_HEADER; 1];
let mut response = Response::new(&mut headers[..]);
let result = crate::ParserConfig::default()
.parse_response(&mut response, b"HTTP/1.1 200 OK\r\nfoo:bar\r\n\r\n");
assert_eq!(result, Ok(Status::Complete(28)));
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, "foo");
assert_eq!(response.headers[0].value, &b"bar"[..]);
}
#[test]
fn test_request_with_leading_space() {
let mut headers = [EMPTY_HEADER; 1];
let mut request = Request::new(&mut headers[..]);
let result = crate::ParserConfig::default()
.parse_request(&mut request, b" GET / HTTP/1.1\r\nfoo:bar\r\n\r\n");
assert_eq!(result, Err(Error::Token));
}
#[test]
fn test_request_with_invalid_method() {
let mut headers = [EMPTY_HEADER; 1];
let mut request = Request::new(&mut headers[..]);
let result = crate::ParserConfig::default()
.parse_request(&mut request, b"P()ST / HTTP/1.1\r\nfoo:bar\r\n\r\n");
assert_eq!(result, Err(Error::Token));
}
#[test]
fn test_utf8_in_path_ok() {
let mut headers = [EMPTY_HEADER; 1];
let mut request = Request::new(&mut headers[..]);
let result = crate::ParserConfig::default().parse_request(
&mut request,
b"GET /test?post=I\xE2\x80\x99msorryIforkedyou HTTP/1.1\r\nHost: example.org\r\n\r\n",
);
assert_eq!(result, Ok(Status::Complete(67)));
assert_eq!(request.version.unwrap(), 1);
assert_eq!(request.method.unwrap(), "GET");
assert_eq!(request.path.unwrap(), "/test?post=I’msorryIforkedyou");
assert_eq!(request.headers.len(), 1);
assert_eq!(request.headers[0].name, "Host");
assert_eq!(request.headers[0].value, &b"example.org"[..]);
}
#[test]
fn test_bad_utf8_in_path() {
let mut headers = [EMPTY_HEADER; 1];
let mut request = Request::new(&mut headers[..]);
let result = crate::ParserConfig::default().parse_request(
&mut request,
b"GET /test?post=I\xE2msorryIforkedyou HTTP/1.1\r\nHost: example.org\r\n\r\n",
);
assert_eq!(result, Err(crate::Error::Token));
}
}