From 45df4d0d9b577fecee798d672695fe24ff57fb1b Mon Sep 17 00:00:00 2001 From: mo khan Date: Tue, 15 Jul 2025 16:37:08 -0600 Subject: feat: migrate from Cedar to SpiceDB authorization system This is a major architectural change that replaces the Cedar policy-based authorization system with SpiceDB's relation-based authorization. Key changes: - Migrate from Rust to Go implementation - Replace Cedar policies with SpiceDB schema and relationships - Switch from envoy `ext_authz` with Cedar to SpiceDB permission checks - Update build system and dependencies for Go ecosystem - Maintain Envoy integration for external authorization This change enables more flexible permission modeling through SpiceDB's Google Zanzibar inspired relation-based system, supporting complex hierarchical permissions that were difficult to express in Cedar. Breaking change: Existing Cedar policies and Rust-based configuration will no longer work and need to be migrated to SpiceDB schema. --- vendor/httparse/src/lib.rs | 2798 -------------------------------------------- 1 file changed, 2798 deletions(-) delete mode 100644 vendor/httparse/src/lib.rs (limited to 'vendor/httparse/src/lib.rs') diff --git a/vendor/httparse/src/lib.rs b/vendor/httparse/src/lib.rs deleted file mode 100644 index 06bc4df5..00000000 --- a/vendor/httparse/src/lib.rs +++ /dev/null @@ -1,2798 +0,0 @@ -#![cfg_attr(not(feature = "std"), no_std)] -#![deny( - missing_docs, - clippy::missing_safety_doc, - clippy::undocumented_unsafe_blocks -)] -#![cfg_attr(test, deny(warnings))] - -//! # httparse -//! -//! A push library for parsing HTTP/1.x requests and responses. -//! -//! The focus is on speed and safety. Unsafe code is used to keep parsing fast, -//! but unsafety is contained in a submodule, with invariants enforced. The -//! parsing internals use an `Iterator` instead of direct indexing, while -//! skipping bounds checks. -//! -//! With Rust 1.27.0 or later, support for SIMD is enabled automatically. -//! If building an executable to be run on multiple platforms, and thus -//! not passing `target_feature` or `target_cpu` flags to the compiler, -//! runtime detection can still detect SSE4.2 or AVX2 support to provide -//! massive wins. -//! -//! If compiling for a specific target, remembering to include -//! `-C target_cpu=native` allows the detection to become compile time checks, -//! making it *even* faster. - -use core::{fmt, mem, result, str}; -use core::mem::MaybeUninit; - -use crate::iter::Bytes; - -mod iter; -#[macro_use] mod macros; -mod simd; - -#[doc(hidden)] -// Expose some internal functions so we can bench them individually -// WARNING: Exported for internal benchmarks, not fit for public consumption -pub mod _benchable { - pub use super::parse_uri; - pub use super::parse_version; - pub use super::parse_method; - pub use super::iter::Bytes; -} - -/// Determines if byte is a method token char. -/// -/// > ```notrust -/// > token = 1*tchar -/// > -/// > tchar = "!" / "#" / "$" / "%" / "&" / "'" / "*" -/// > / "+" / "-" / "." / "^" / "_" / "`" / "|" / "~" -/// > / DIGIT / ALPHA -/// > ; any VCHAR, except delimiters -/// > ``` -#[inline] -fn is_method_token(b: u8) -> bool { - match b { - // For the majority case, this can be faster than the table lookup. - b'A'..=b'Z' => true, - _ => TOKEN_MAP[b as usize], - } -} - -// char codes to accept URI string. -// i.e. b'!' <= char and char != 127 -// TODO: Make a stricter checking for URI string? -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] -} - -/// An error in parsing. -#[derive(Copy, Clone, PartialEq, Eq, Debug)] -pub enum Error { - /// Invalid byte in header name. - HeaderName, - /// Invalid byte in header value. - HeaderValue, - /// Invalid byte in new line. - NewLine, - /// Invalid byte in Response status. - Status, - /// Invalid byte where token is required. - Token, - /// Parsed more headers than provided buffer can contain. - TooManyHeaders, - /// Invalid byte in HTTP version. - 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() - } -} - -/// An error in parsing a chunk size. -// Note: Move this into the error enum once v2.0 is released. -#[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") - } -} - -/// A Result of any parsing action. -/// -/// If the input is invalid, an `Error` will be returned. Note that incomplete -/// data is not considered invalid, and so will not return an error, but rather -/// a `Ok(Status::Partial)`. -pub type Result = result::Result, Error>; - -/// The result of a successful parse pass. -/// -/// `Complete` is used when the buffer contained the complete value. -/// `Partial` is used when parsing did not reach the end of the expected value, -/// but no invalid data was found. -#[derive(Copy, Clone, Eq, PartialEq, Debug)] -pub enum Status { - /// The completed result. - Complete(T), - /// A partial result. - Partial -} - -impl Status { - /// Convenience method to check if status is complete. - #[inline] - pub fn is_complete(&self) -> bool { - match *self { - Status::Complete(..) => true, - Status::Partial => false - } - } - - /// Convenience method to check if status is partial. - #[inline] - pub fn is_partial(&self) -> bool { - match *self { - Status::Complete(..) => false, - Status::Partial => true - } - } - - /// Convenience method to unwrap a Complete value. Panics if the status is - /// `Partial`. - #[inline] - pub fn unwrap(self) -> T { - match self { - Status::Complete(t) => t, - Status::Partial => panic!("Tried to unwrap Status::Partial") - } - } -} - -/// Parser configuration. -#[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 { - /// Sets whether spaces and tabs should be allowed after header names in responses. - 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 - } - - /// Sets whether multiple spaces are allowed as delimiters in request lines. - /// - /// # Background - /// - /// The [latest version of the HTTP/1.1 spec][spec] allows implementations to parse multiple - /// whitespace characters in place of the `SP` delimiters in the request line, including: - /// - /// > SP, HTAB, VT (%x0B), FF (%x0C), or bare CR - /// - /// This option relaxes the parser to allow for multiple spaces, but does *not* allow the - /// request line to contain the other mentioned whitespace characters. - /// - /// [spec]: https://httpwg.org/http-core/draft-ietf-httpbis-messaging-latest.html#rfc.section.3.p.3 - 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 - } - - /// Whether multiple spaces are allowed as delimiters in request lines. - pub fn multiple_spaces_in_request_line_delimiters_are_allowed(&self) -> bool { - self.allow_multiple_spaces_in_request_line_delimiters - } - - /// Sets whether multiple spaces are allowed as delimiters in response status lines. - /// - /// # Background - /// - /// The [latest version of the HTTP/1.1 spec][spec] allows implementations to parse multiple - /// whitespace characters in place of the `SP` delimiters in the response status line, - /// including: - /// - /// > SP, HTAB, VT (%x0B), FF (%x0C), or bare CR - /// - /// This option relaxes the parser to allow for multiple spaces, but does *not* allow the status - /// line to contain the other mentioned whitespace characters. - /// - /// [spec]: https://httpwg.org/http-core/draft-ietf-httpbis-messaging-latest.html#rfc.section.4.p.3 - 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 - } - - /// Whether multiple spaces are allowed as delimiters in response status lines. - pub fn multiple_spaces_in_response_status_delimiters_are_allowed(&self) -> bool { - self.allow_multiple_spaces_in_response_status_delimiters - } - - /// Sets whether obsolete multiline headers should be allowed. - /// - /// This is an obsolete part of HTTP/1. Use at your own risk. If you are - /// building an HTTP library, the newlines (`\r` and `\n`) should be - /// replaced by spaces before handing the header value to the user. - /// - /// # Example - /// - /// ```rust - /// let buf = b"HTTP/1.1 200 OK\r\nFolded-Header: hello\r\n there \r\n\r\n"; - /// let mut headers = [httparse::EMPTY_HEADER; 16]; - /// let mut response = httparse::Response::new(&mut headers); - /// - /// let res = httparse::ParserConfig::default() - /// .allow_obsolete_multiline_headers_in_responses(true) - /// .parse_response(&mut response, buf); - /// - /// assert_eq!(res, Ok(httparse::Status::Complete(buf.len()))); - /// - /// assert_eq!(response.headers.len(), 1); - /// assert_eq!(response.headers[0].name, "Folded-Header"); - /// assert_eq!(response.headers[0].value, b"hello\r\n there"); - /// ``` - pub fn allow_obsolete_multiline_headers_in_responses( - &mut self, - value: bool, - ) -> &mut Self { - self.allow_obsolete_multiline_headers_in_responses = value; - self - } - - /// Whether obsolete multiline headers should be allowed. - pub fn obsolete_multiline_headers_in_responses_are_allowed(&self) -> bool { - 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, - request: &mut Request<'_, 'buf>, - buf: &'buf [u8], - ) -> Result { - request.parse_with_config(buf, self) - } - - /// Parses a request with the given config and buffer for headers - pub fn parse_request_with_uninit_headers<'headers, 'buf>( - &self, - request: &mut Request<'headers, 'buf>, - buf: &'buf [u8], - headers: &'headers mut [MaybeUninit>], - ) -> Result { - request.parse_with_config_and_uninit_headers(buf, self, headers) - } - - /// Sets whether invalid header lines should be silently ignored in responses. - /// - /// This mimicks the behaviour of major browsers. You probably don't want this. - /// You should only want this if you are implementing a proxy whose main - /// purpose is to sit in front of browsers whose users access arbitrary content - /// which may be malformed, and they expect everything that works without - /// the proxy to keep working with the proxy. - /// - /// This option will prevent `ParserConfig::parse_response` from returning - /// an error encountered when parsing a header, except if the error was caused - /// by the character NUL (ASCII code 0), as Chrome specifically always reject - /// those, or if the error was caused by a lone character `\r`, as Firefox and - /// Chrome behave differently in that case. - /// - /// The ignorable errors are: - /// * empty header names; - /// * characters that are not allowed in header names, except for `\0` and `\r`; - /// * when `allow_spaces_after_header_name_in_responses` is not enabled, - /// spaces and tabs between the header name and the colon; - /// * missing colon between header name and value; - /// * when `allow_obsolete_multiline_headers_in_responses` is not enabled, - /// headers using obsolete line folding. - /// * characters that are not allowed in header values except for `\0` and `\r`. - /// - /// If an ignorable error is encountered, the parser tries to find the next - /// line in the input to resume parsing the rest of the headers. As lines - /// contributing to a header using obsolete line folding always start - /// with whitespace, those will be ignored too. An error will be emitted - /// nonetheless if it finds `\0` or a lone `\r` while looking for the - /// next line. - pub fn ignore_invalid_headers_in_responses( - &mut self, - value: bool, - ) -> &mut Self { - self.ignore_invalid_headers_in_responses = value; - 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, - response: &mut Response<'_, 'buf>, - buf: &'buf [u8], - ) -> Result { - response.parse_with_config(buf, self) - } - - /// Parses a response with the given config and buffer for headers - pub fn parse_response_with_uninit_headers<'headers, 'buf>( - &self, - response: &mut Response<'headers, 'buf>, - buf: &'buf [u8], - headers: &'headers mut [MaybeUninit>], - ) -> Result { - response.parse_with_config_and_uninit_headers(buf, self, headers) - } -} - -/// A parsed Request. -/// -/// The optional values will be `None` if a parse was not complete, and did not -/// parse the associated property. This allows you to inspect the parts that -/// could be parsed, before reading more, in case you wish to exit early. -/// -/// # Example -/// -/// ```no_run -/// let buf = b"GET /404 HTTP/1.1\r\nHost:"; -/// let mut headers = [httparse::EMPTY_HEADER; 16]; -/// let mut req = httparse::Request::new(&mut headers); -/// let res = req.parse(buf).unwrap(); -/// if res.is_partial() { -/// match req.path { -/// Some(ref path) => { -/// // check router for path. -/// // /404 doesn't exist? we could stop parsing -/// }, -/// None => { -/// // must read more and parse again -/// } -/// } -/// } -/// ``` -#[derive(Debug, Eq, PartialEq)] -pub struct Request<'headers, 'buf> { - /// The request method, such as `GET`. - pub method: Option<&'buf str>, - /// The request path, such as `/about-us`. - pub path: Option<&'buf str>, - /// The request minor version, such as `1` for `HTTP/1.1`. - pub version: Option, - /// The request headers. - pub headers: &'headers mut [Header<'buf>] -} - -impl<'h, 'b> Request<'h, 'b> { - /// Creates a new Request, using a slice of headers you allocate. - #[inline] - pub fn new(headers: &'h mut [Header<'b>]) -> Request<'h, 'b> { - Request { - method: None, - path: None, - version: None, - headers, - } - } - - fn parse_with_config_and_uninit_headers( - &mut self, - buf: &'b [u8], - config: &ParserConfig, - mut headers: &'h mut [MaybeUninit>], - ) -> Result { - let orig_len = buf.len(); - let mut bytes = Bytes::new(buf); - complete!(skip_empty_lines(&mut bytes)); - let method = complete!(parse_method(&mut bytes)); - self.method = Some(method); - if config.allow_multiple_spaces_in_request_line_delimiters { - complete!(skip_spaces(&mut bytes)); - } - self.path = Some(complete!(parse_uri(&mut bytes))); - if config.allow_multiple_spaces_in_request_line_delimiters { - complete!(skip_spaces(&mut bytes)); - } - self.version = Some(complete!(parse_version(&mut bytes))); - newline!(bytes); - - let len = orig_len - bytes.len(); - let headers_len = complete!(parse_headers_iter_uninit( - &mut headers, - &mut bytes, - &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 - }, - )); - /* SAFETY: see `parse_headers_iter_uninit` guarantees */ - self.headers = unsafe { assume_init_slice(headers) }; - - Ok(Status::Complete(len + headers_len)) - } - - /// Try to parse a buffer of bytes into the Request, - /// except use an uninitialized slice of `Header`s. - /// - /// For more information, see `parse` - pub fn parse_with_uninit_headers( - &mut self, - buf: &'b [u8], - headers: &'h mut [MaybeUninit>], - ) -> Result { - self.parse_with_config_and_uninit_headers(buf, &Default::default(), headers) - } - - fn parse_with_config(&mut self, buf: &'b [u8], config: &ParserConfig) -> Result { - let headers = mem::take(&mut self.headers); - - /* SAFETY: see `parse_headers_iter_uninit` guarantees */ - unsafe { - let headers: *mut [Header<'_>] = headers; - let headers = headers as *mut [MaybeUninit>]; - match self.parse_with_config_and_uninit_headers(buf, config, &mut *headers) { - Ok(Status::Complete(idx)) => Ok(Status::Complete(idx)), - other => { - // put the original headers back - self.headers = &mut *(headers as *mut [Header<'_>]); - other - }, - } - } - } - - /// Try to parse a buffer of bytes into the Request. - /// - /// Returns byte offset in `buf` to start of HTTP body. - pub fn parse(&mut self, buf: &'b [u8]) -> Result { - self.parse_with_config(buf, &Default::default()) - } -} - -#[inline] -fn skip_empty_lines(bytes: &mut Bytes<'_>) -> Result<()> { - loop { - let b = bytes.peek(); - match b { - Some(b'\r') => { - // SAFETY: peeked and found `\r`, so it's safe to bump 1 pos - unsafe { bytes.bump() }; - expect!(bytes.next() == b'\n' => Err(Error::NewLine)); - } - Some(b'\n') => { - // SAFETY: peeked and found `\n`, so it's safe to bump 1 pos - 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' ') => { - // SAFETY: peeked and found ` `, so it's safe to bump 1 pos - unsafe { bytes.bump() }; - } - Some(..) => { - bytes.slice(); - return Ok(Status::Complete(())); - } - None => return Ok(Status::Partial), - } - } -} - -/// A parsed Response. -/// -/// See `Request` docs for explanation of optional values. -#[derive(Debug, Eq, PartialEq)] -pub struct Response<'headers, 'buf> { - /// The response minor version, such as `1` for `HTTP/1.1`. - pub version: Option, - /// The response code, such as `200`. - pub code: Option, - /// The response reason-phrase, such as `OK`. - /// - /// Contains an empty string if the reason-phrase was missing or contained invalid characters. - pub reason: Option<&'buf str>, - /// The response headers. - pub headers: &'headers mut [Header<'buf>] -} - -impl<'h, 'b> Response<'h, 'b> { - /// Creates a new `Response` using a slice of `Header`s you have allocated. - #[inline] - pub fn new(headers: &'h mut [Header<'b>]) -> Response<'h, 'b> { - Response { - version: None, - code: None, - reason: None, - headers, - } - } - - /// Try to parse a buffer of bytes into this `Response`. - pub fn parse(&mut self, buf: &'b [u8]) -> Result { - self.parse_with_config(buf, &ParserConfig::default()) - } - - fn parse_with_config(&mut self, buf: &'b [u8], config: &ParserConfig) -> Result { - let headers = mem::take(&mut self.headers); - - // SAFETY: see guarantees of [`parse_headers_iter_uninit`], which leaves no uninitialized - // headers around. On failure, the original headers are restored. - unsafe { - let headers: *mut [Header<'_>] = headers; - let headers = headers as *mut [MaybeUninit>]; - match self.parse_with_config_and_uninit_headers(buf, config, &mut *headers) { - Ok(Status::Complete(idx)) => Ok(Status::Complete(idx)), - other => { - // put the original headers back - self.headers = &mut *(headers as *mut [Header<'_>]); - other - }, - } - } - } - - fn parse_with_config_and_uninit_headers( - &mut self, - buf: &'b [u8], - config: &ParserConfig, - mut headers: &'h mut [MaybeUninit>], - ) -> Result { - let orig_len = buf.len(); - let mut bytes = Bytes::new(buf); - - complete!(skip_empty_lines(&mut bytes)); - self.version = Some(complete!(parse_version(&mut bytes))); - space!(bytes or Error::Version); - if config.allow_multiple_spaces_in_response_status_delimiters { - complete!(skip_spaces(&mut bytes)); - } - self.code = Some(complete!(parse_code(&mut bytes))); - - // RFC7230 says there must be 'SP' and then reason-phrase, but admits - // its only for legacy reasons. With the reason-phrase completely - // optional (and preferred to be omitted) in HTTP2, we'll just - // handle any response that doesn't include a reason-phrase, because - // it's more lenient, and we don't care anyways. - // - // So, a SP means parse a reason-phrase. - // A newline means go to headers. - // Anything else we'll say is a malformed status. - match next!(bytes) { - b' ' => { - if config.allow_multiple_spaces_in_response_status_delimiters { - complete!(skip_spaces(&mut bytes)); - } - bytes.slice(); - self.reason = Some(complete!(parse_reason(&mut bytes))); - }, - b'\r' => { - expect!(bytes.next() == b'\n' => Err(Error::Status)); - bytes.slice(); - self.reason = Some(""); - }, - b'\n' => { - bytes.slice(); - self.reason = Some(""); - } - _ => return Err(Error::Status), - } - - - let len = orig_len - bytes.len(); - let headers_len = complete!(parse_headers_iter_uninit( - &mut headers, - &mut bytes, - &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 - } - )); - /* SAFETY: see `parse_headers_iter_uninit` guarantees */ - self.headers = unsafe { assume_init_slice(headers) }; - Ok(Status::Complete(len + headers_len)) - } -} - -/// Represents a parsed header. -#[derive(Copy, Clone, Eq, PartialEq)] -pub struct Header<'a> { - /// The name portion of a header. - /// - /// A header name must be valid ASCII-US, so it's safe to store as a `&str`. - pub name: &'a str, - /// The value portion of a header. - /// - /// While headers **should** be ASCII-US, the specification allows for - /// values that may not be, and so the value is stored as bytes. - pub value: &'a [u8], -} - -impl fmt::Debug for Header<'_> { - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - let mut f = f.debug_struct("Header"); - f.field("name", &self.name); - if let Ok(value) = str::from_utf8(self.value) { - f.field("value", &value); - } else { - f.field("value", &self.value); - } - f.finish() - } -} - -/// An empty header, useful for constructing a `Header` array to pass in for -/// parsing. -/// -/// # Example -/// -/// ``` -/// let headers = [httparse::EMPTY_HEADER; 64]; -/// ``` -pub const EMPTY_HEADER: Header<'static> = Header { name: "", value: b"" }; - -#[inline] -#[doc(hidden)] -#[allow(missing_docs)] -// WARNING: Exported for internal benchmarks, not fit for public consumption -pub fn parse_version(bytes: &mut Bytes) -> Result { - if let Some(eight) = bytes.peek_n::<[u8; 8]>(8) { - // NOTE: should be const once MSRV >= 1.44 - let h10: u64 = u64::from_ne_bytes(*b"HTTP/1.0"); - let h11: u64 = u64::from_ne_bytes(*b"HTTP/1.1"); - // SAFETY: peek_n(8) before ensure within bounds - unsafe { - bytes.advance(8); - } - let block = u64::from_ne_bytes(eight); - // NOTE: should be match once h10 & h11 are consts - return if block == h10 { - Ok(Status::Complete(0)) - } else if block == h11 { - Ok(Status::Complete(1)) - } else { - Err(Error::Version) - }; - } - - // else (but not in `else` because of borrow checker) - - // If there aren't at least 8 bytes, we still want to detect early - // if this is a valid version or not. If it is, we'll return Partial. - 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)] -// WARNING: Exported for internal benchmarks, not fit for public consumption -pub fn parse_method<'a>(bytes: &mut Bytes<'a>) -> Result<&'a str> { - const GET: [u8; 4] = *b"GET "; - const POST: [u8; 4] = *b"POST"; - match bytes.peek_n::<[u8; 4]>(4) { - Some(GET) => { - // SAFETY: we matched "GET " which has 4 bytes and is ASCII - let method = unsafe { - bytes.advance(4); // advance cursor past "GET " - str::from_utf8_unchecked(bytes.slice_skip(1)) // "GET" without space - }; - Ok(Status::Complete(method)) - } - // SAFETY: - // If `bytes.peek_n...` returns a Some([u8; 4]), - // then we are assured that `bytes` contains at least 4 bytes. - // Thus `bytes.len() >= 4`, - // and it is safe to peek at byte 4 with `bytes.peek_ahead(4)`. - Some(POST) if unsafe { bytes.peek_ahead(4) } == Some(b' ') => { - // SAFETY: we matched "POST " which has 5 bytes - let method = unsafe { - bytes.advance(5); // advance cursor past "POST " - str::from_utf8_unchecked(bytes.slice_skip(1)) // "POST" without space - }; - Ok(Status::Complete(method)) - } - _ => parse_token(bytes), - } -} - -/// From [RFC 7230](https://tools.ietf.org/html/rfc7230): -/// -/// > ```notrust -/// > reason-phrase = *( HTAB / SP / VCHAR / obs-text ) -/// > HTAB = %x09 ; horizontal tab -/// > VCHAR = %x21-7E ; visible (printing) characters -/// > obs-text = %x80-FF -/// > ``` -/// -/// > A.2. Changes from RFC 2616 -/// > -/// > Non-US-ASCII content in header fields and the reason phrase -/// > has been obsoleted and made opaque (the TEXT rule was removed). -#[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( - // SAFETY: (1) calling bytes.slice_skip(2) is safe, because at least two next! calls - // advance the bytes iterator. - // (2) calling from_utf8_unchecked is safe, because the bytes returned by slice_skip - // were validated to be allowed US-ASCII chars by the other arms of the if/else or - // otherwise `seen_obs_text` is true and an empty string is returned instead. - unsafe { - let bytes = bytes.slice_skip(2); - if !seen_obs_text { - // all bytes up till `i` must have been HTAB / SP / VCHAR - str::from_utf8_unchecked(bytes) - } else { - // obs-text characters were found, so return the fallback empty string - "" - } - }, - )); - } else if b == b'\n' { - return Ok(Status::Complete( - // SAFETY: (1) calling bytes.slice_skip(1) is safe, because at least one next! call - // advance the bytes iterator. - // (2) see (2) of safety comment above. - unsafe { - let bytes = bytes.slice_skip(1); - if !seen_obs_text { - // all bytes up till `i` must have been HTAB / SP / VCHAR - str::from_utf8_unchecked(bytes) - } else { - // obs-text characters were found, so return the fallback empty string - "" - } - }, - )); - } 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) { - // First char must be a token char, it can't be a space which would indicate an empty token. - return Err(Error::Token); - } - - loop { - let b = next!(bytes); - if b == b' ' { - return Ok(Status::Complete( - // SAFETY: all bytes up till `i` must have been `is_method_token` and therefore also utf-8. - unsafe { str::from_utf8_unchecked(bytes.slice_skip(1)) }, - )); - } else if !is_method_token(b) { - return Err(Error::Token); - } - } -} - -#[inline] -#[doc(hidden)] -#[allow(missing_docs)] -// WARNING: Exported for internal benchmarks, not fit for public consumption -pub 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' ' { - // URI must have at least one char - if end == start { - return Err(Error::Token); - } - - // SAFETY: all bytes up till `i` must have been `is_token` and therefore also utf-8. - match str::from_utf8(unsafe { bytes.slice_skip(1) }) { - Ok(uri) => Ok(Status::Complete(uri)), - Err(_) => Err(Error::Token), - } - } else { - Err(Error::Token) - } -} - -#[inline] -fn parse_code(bytes: &mut Bytes<'_>) -> Result { - 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)) -} - -/// Parse a buffer of bytes as headers. -/// -/// The return value, if complete and successful, includes the index of the -/// buffer that parsing stopped at, and a sliced reference to the parsed -/// headers. The length of the slice will be equal to the number of properly -/// parsed headers. -/// -/// # Example -/// -/// ``` -/// let buf = b"Host: foo.bar\nAccept: */*\n\nblah blah"; -/// let mut headers = [httparse::EMPTY_HEADER; 4]; -/// assert_eq!(httparse::parse_headers(buf, &mut headers), -/// Ok(httparse::Status::Complete((27, &[ -/// httparse::Header { name: "Host", value: b"foo.bar" }, -/// httparse::Header { name: "Accept", value: b"*/*" } -/// ][..])))); -/// ``` -pub fn parse_headers<'b: 'h, 'h>( - src: &'b [u8], - 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, &HeaderParserConfig::default())); - Ok(Status::Complete((pos, dst))) -} - -#[inline] -fn parse_headers_iter<'a>( - headers: &mut &mut [Header<'a>], - bytes: &mut Bytes<'a>, - config: &HeaderParserConfig, -) -> Result { - parse_headers_iter_uninit( - /* SAFETY: see `parse_headers_iter_uninit` guarantees */ - unsafe { deinit_slice_mut(headers) }, - bytes, - config, - ) -} - -unsafe fn deinit_slice_mut<'a, 'b, T>(s: &'a mut &'b mut [T]) -> &'a mut &'b mut [MaybeUninit] { - let s: *mut &mut [T] = s; - let s = s as *mut &mut [MaybeUninit]; - &mut *s -} -unsafe fn assume_init_slice(s: &mut [MaybeUninit]) -> &mut [T] { - let s: *mut [MaybeUninit] = s; - let s = s as *mut [T]; - &mut *s -} - -#[derive(Clone, Debug, Default)] -struct HeaderParserConfig { - allow_spaces_after_header_name: bool, - allow_obsolete_multiline_headers: bool, - allow_space_before_first_header_name: bool, - ignore_invalid_headers: bool, -} - -/* Function which parsers headers into uninitialized buffer. - * - * Guarantees that it doesn't write garbage, so casting - * &mut &mut [Header] -> &mut &mut [MaybeUninit
] - * is safe here. - * - * Also it promises `headers` get shrunk to number of initialized headers, - * so casting the other way around after calling this function is safe - */ -fn parse_headers_iter_uninit<'a>( - headers: &mut &mut [MaybeUninit>], - bytes: &mut Bytes<'a>, - config: &HeaderParserConfig -) -> Result { - - /* Flow of this function is pretty complex, especially with macros, - * so this struct makes sure we shrink `headers` to only parsed ones. - * Comparing to previous code, this only may introduce some additional - * instructions in case of early return */ - struct ShrinkOnDrop<'r1, 'r2, 'a> { - headers: &'r1 mut &'r2 mut [MaybeUninit>], - num_headers: usize, - } - - impl Drop for ShrinkOnDrop<'_, '_, '_> { - fn drop(&mut self) { - let headers = mem::take(self.headers); - - /* SAFETY: num_headers is the number of initialized headers */ - let headers = unsafe { headers.get_unchecked_mut(..self.num_headers) }; - - *self.headers = headers; - } - } - - let mut autoshrink = ShrinkOnDrop { - headers, - num_headers: 0, - }; - // Track starting pointer to calculate the number of bytes parsed. - let start = bytes.as_ref().as_ptr() as usize; - let mut result = Err(Error::TooManyHeaders); - - let mut iter = autoshrink.headers.iter_mut(); - - macro_rules! maybe_continue_after_obsolete_line_folding { - ($bytes:ident, $label:lifetime) => { - if config.allow_obsolete_multiline_headers { - match $bytes.peek() { - None => { - // Next byte may be a space, in which case that header - // is using obsolete line folding, so we may have more - // whitespace to skip after colon. - return Ok(Status::Partial); - } - Some(b' ') | Some(b'\t') => { - // The space will be consumed next iteration. - continue $label; - } - _ => { - // There is another byte after the end of the line, - // but it's not whitespace, so it's probably another - // header or the final line return. This header is thus - // empty. - }, - } - } - } - } - - 'headers: loop { - // Return the error `$err` if `ignore_invalid_headers_in_responses` - // is false, otherwise find the end of the current line and resume - // parsing on the next one. - macro_rules! handle_invalid_char { - ($bytes:ident, $b:ident, $err:ident) => { - if !config.ignore_invalid_headers { - return Err(Error::$err); - } - - let mut b = $b; - - loop { - if b == b'\r' { - expect!(bytes.next() == b'\n' => Err(Error::$err)); - break; - } - if b == b'\n' { - break; - } - if b == b'\0' { - return Err(Error::$err); - } - b = next!($bytes); - } - - $bytes.slice(); - - continue 'headers; - }; - } - - // a newline here means the head is over! - 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; - result = Ok(Status::Complete(end - start)); - break; - } - if b == b'\n' { - let end = bytes.as_ref().as_ptr() as usize; - result = Ok(Status::Complete(end - start)); - break; - } - if !is_header_name_token(b) { - 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)] - // parse header name until colon - let header_name: &str = 'name: loop { - simd::match_header_name_vectored(bytes); - let mut b = next!(bytes); - - // SAFETY: previously bumped by 1 with next! -> always safe. - let bslice = unsafe { bytes.slice_skip(1) }; - // SAFETY: previous call to match_header_name_vectored ensured all bytes are valid - // header name chars, and as such also valid utf-8. - let name = unsafe { str::from_utf8_unchecked(bslice) }; - - if b == b':' { - break 'name name; - } - - if config.allow_spaces_after_header_name { - while b == b' ' || b == b'\t' { - b = next!(bytes); - - if b == b':' { - bytes.slice(); - break 'name name; - } - } - } - - handle_invalid_char!(bytes, b, HeaderName); - }; - - let mut b; - - #[allow(clippy::never_loop)] - let value_slice = 'value: loop { - // eat white space between colon and value - '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(); - - // This produces an empty slice that points to the beginning - // of the whitespace. - break 'value &whitespace_slice[0..0]; - } - - 'value_lines: loop { - // parse value till EOL - - simd::match_header_value_vectored(bytes); - let b = next!(bytes); - - //found_ctl - 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); - - // SAFETY: having just checked that a newline exists, it's safe to skip it. - unsafe { - break 'value bytes.slice_skip(skip); - } - } - }; - - let uninit_header = match iter.next() { - Some(header) => header, - None => break 'headers - }; - - // trim trailing whitespace in the header - let header_value = if let Some(last_visible) = value_slice - .iter() - .rposition(|b| *b != b' ' && *b != b'\t' && *b != b'\r' && *b != b'\n') - { - // There is at least one non-whitespace character. - &value_slice[0..last_visible+1] - } else { - // There is no non-whitespace character. This can only happen when value_slice is - // empty. - value_slice - }; - - *uninit_header = MaybeUninit::new(Header { - name: header_name, - value: header_value, - }); - autoshrink.num_headers += 1; - } - - result -} - -/// Parse a buffer of bytes as a chunk size. -/// -/// The return value, if complete and successful, includes the index of the -/// buffer that parsing stopped at, and the size of the following chunk. -/// -/// # Example -/// -/// ``` -/// let buf = b"4\r\nRust\r\n0\r\n\r\n"; -/// assert_eq!(httparse::parse_chunk_size(buf), -/// Ok(httparse::Status::Complete((3, 4)))); -/// ``` -pub fn parse_chunk_size(buf: &[u8]) - -> result::Result, 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) { - // actually unreachable!(), because count stops the loop at 15 digits before - // we can reach u64::MAX / RADIX == 0xfffffffffffffff, which requires 15 hex - // digits. This stops mirai reporting a false alarm regarding the `size *= - // RADIX` multiplication below. - return Err(InvalidChunkSize); - } - size *= RADIX; - size += (b - b'0') as u64; - }, - 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 + 10 - b'a') as u64; - } - 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 + 10 - b'A') as u64; - } - b'\r' => { - match next!(bytes) { - b'\n' => break, - _ => return Err(InvalidChunkSize), - } - } - // If we weren't in the extension yet, the ";" signals its start - b';' if !in_ext => { - in_ext = true; - in_chunk_size = false; - } - // "Linear white space" is ignored between the chunk size and the - // extension separator token (";") due to the "implied *LWS rule". - b'\t' | b' ' if !in_ext && !in_chunk_size => {} - // LWS can follow the chunk size, but no more digits can come - b'\t' | b' ' if in_chunk_size => in_chunk_size = false, - // We allow any arbitrary octet once we are in the extension, since - // they all get ignored anyway. According to the HTTP spec, valid - // extensions would have a more strict syntax: - // (token ["=" (token | quoted-string)]) - // but we gain nothing by rejecting an otherwise valid chunk size. - _ if in_ext => {} - // Finally, if we aren't in the extension and we're reading any - // other octet, the chunk size line is invalid! - _ => return Err(InvalidChunkSize), - } - } - Ok(Status::Complete((bytes.pos(), size))) -} - -#[cfg(test)] -mod tests { - use super::{Error, Request, Response, Status, EMPTY_HEADER, parse_chunk_size}; - - 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 the scalar parsing - 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 the sse42 parsing - 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 the avx2 parsing - 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 the avx2 parsing - 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"); - } - } - - // A single byte which is part of a method is not invalid - req! { - test_request_one_byte_method, - b"G", Ok(Status::Partial), - |_req| {} - } - - // A subset of a method is a partial method, not invalid - req! { - test_request_partial_method, - b"GE", Ok(Status::Partial), - |_req| {} - } - - // A method, without the delimiting space, is a partial request - req! { - test_request_method_no_delimiter, - b"GET", Ok(Status::Partial), - |_req| {} - } - - // Regression test: assert that a partial read with just the method and - // space results in a partial, rather than a token error from uri parsing. - 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); // doesn't slice since not Complete - 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); - // Empty string fallback in case of obs-text - 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); // doesn't slice since not Complete - 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"); - } - } - - /// Check all subset permutations of a partial request line with no headers - #[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[..i].as_bytes()); - 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); - } - - /// This is technically allowed by the spec, but we only support multiple spaces as an option, - /// not stray `\r`s. - 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); - } - - /// This is technically allowed by the spec, but we only support multiple spaces as an option, - /// not stray `\r`s. - 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)); - } - - // This test ensure there is an error when there is a DEL character in the path - // since we allow all char from 0x21 code except DEL, this test ensure that DEL - // is not allowed in the path - 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)] // Miri is too slow for this test - fn test_all_utf8_char_in_paths() { - // two code points - 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); - }, - }; - - // three code points starting from 0xe0 - 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); - }, - }; - - // four code points starting from 0xf0 - 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(); - - // SAFETY: will not wrap - let buf_end = unsafe { REQUEST.as_ptr().add(REQUEST.len()) }; - // Check that the method str is within the buffer - 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)); - } -} -- cgit v1.2.3