//! # 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, result, str}; use core::mem::{self, MaybeUninit};
/// An error in parsing a chunk size. // Note: Move this into the error enum once v2.0 is released. #[derive(Debug, PartialEq, Eq)] pubstruct InvalidChunkSize;
/// 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)`. pubtype Result<T> = result::Result<Status<T>, 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)] pubenum Status<T> { /// The completed result.
Complete(T), /// A partial result.
Partial
}
impl<T> Status<T> { /// Convenience method to check if status is complete. #[inline] pubfn is_complete(&self) -> bool { match *self {
Status::Complete(..) => true,
Status::Partial => false
}
}
/// Convenience method to check if status is partial. #[inline] pubfn 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] pubfn unwrap(self) -> T { matchself {
Status::Complete(t) => t,
Status::Partial => panic!("Tried to unwrap Status::Partial")
}
}
}
impl ParserConfig { /// Sets whether spaces and tabs should be allowed after header names in responses. pubfn allow_spaces_after_header_name_in_responses(
&mutself,
value: bool,
) -> &mutSelf { 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 pubfn allow_multiple_spaces_in_request_line_delimiters(&mutself, value: bool) -> &mutSelf { self.allow_multiple_spaces_in_request_line_delimiters = value; self
}
/// Whether multiple spaces are allowed as delimiters in request lines. pubfn 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 pubfn allow_multiple_spaces_in_response_status_delimiters(&mutself, value: bool) -> &mutSelf { self.allow_multiple_spaces_in_response_status_delimiters = value; self
}
/// Whether multiple spaces are allowed as delimiters in response status lines. pubfn 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"); /// ``` pubfn allow_obsolete_multiline_headers_in_responses(
&mutself,
value: bool,
) -> &mutSelf { self.allow_obsolete_multiline_headers_in_responses = value; self
}
/// Whether obsolete multiline headers should be allowed. pubfn obsolete_multiline_headers_in_responses_are_allowed(&self) -> bool { self.allow_obsolete_multiline_headers_in_responses
}
/// Parses a request with the given config. pubfn parse_request<'headers, 'buf>(
&self,
request: &mut Request<'headers, 'buf>,
buf: &'buf [u8],
) -> Result<usize> {
request.parse_with_config(buf, self)
}
/// Parses a request with the given config and buffer for headers pubfn parse_request_with_uninit_headers<'headers, 'buf>(
&self,
request: &mut Request<'headers, 'buf>,
buf: &'buf [u8],
headers: &'headers mut [MaybeUninit<Header<'buf>>],
) -> Result<usize> {
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. pubfn ignore_invalid_headers_in_responses(
&mutself,
value: bool,
) -> &mutSelf { self.ignore_invalid_headers_in_responses = value; self
}
/// Parses a response with the given config. pubfn parse_response<'headers, 'buf>(
&self,
response: &mut Response<'headers, 'buf>,
buf: &'buf [u8],
) -> Result<usize> {
response.parse_with_config(buf, self)
}
/// Parses a response with the given config and buffer for headers pubfn parse_response_with_uninit_headers<'headers, 'buf>(
&self,
response: &mut Response<'headers, 'buf>,
buf: &'buf [u8],
headers: &'headers mut [MaybeUninit<Header<'buf>>],
) -> Result<usize> {
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)] pubstruct 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<u8>, /// 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] pubfn new(headers: &'h mut [Header<'b>]) -> Request<'h, 'b> {
Request {
method: None,
path: None,
version: None,
headers,
}
}
let len = orig_len - bytes.len(); let headers_len = complete!(parse_headers_iter_uninit(
&mut headers,
&mut bytes,
&ParserConfig::default(),
)); /* 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` pubfn parse_with_uninit_headers(
&mutself,
buf: &'b [u8],
headers: &'h mut [MaybeUninit<Header<'b>>],
) -> Result<usize> { self.parse_with_config_and_uninit_headers(buf, &Default::default(), headers)
}
/* SAFETY: see `parse_headers_iter_uninit` guarantees */ unsafe { let headers: *mut [Header<'_>] = headers; let headers = headers as *mut [MaybeUninit<Header<'_>>]; matchself.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. pubfn parse(&mutself, buf: &'b [u8]) -> Result<usize> { 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') => { // there's `\r`, so it's safe to bump 1 pos unsafe { bytes.bump() };
expect!(bytes.next() == b'\n' => Err(Error::NewLine));
},
Some(b'\n') => { // there's `\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' ') => { // there's ` `, 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)] pubstruct Response<'headers, 'buf> { /// The response minor version, such as `1` for `HTTP/1.1`. pub version: Option<u8>, /// The response code, such as `200`. pub code: Option<u16>, /// 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] pubfn 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`. pubfn parse(&mutself, buf: &'b [u8]) -> Result<usize> { self.parse_with_config(buf, &ParserConfig::default())
}
unsafe { let headers: *mut [Header<'_>] = headers; let headers = headers as *mut [MaybeUninit<Header<'_>>]; matchself.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
},
}
}
}
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,
config
)); /* 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)] pubstruct 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],
}
// 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)
}
/// 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> { letmut 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 { // 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 ""
}
}));
} elseif b == b'\n' { return Ok(Status::Complete(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 ""
}
}));
} elseif !(b == 0x09 || b == b' ' || (0x21..=0x7E).contains(&b) || b >= 0x80) { return Err(Error::Status);
} elseif b >= 0x80 {
seen_obs_text = true;
}
}
}
#[inline] fn parse_token<'a>(bytes: &mut Bytes<'a>) -> Result<&'a str> { let b = next!(bytes); if !is_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(unsafe { // all bytes up till `i` must have been `is_token`.
str::from_utf8_unchecked(bytes.slice_skip(1))
}));
} elseif !is_token(b) { return Err(Error::Token);
}
}
}
#[inline] fn parse_uri<'a>(bytes: &mut Bytes<'a>) -> Result<&'a str> { let b = next!(bytes); if !is_uri_token(b) { // First char must be a URI char, it can't be a space which would indicate an empty path. return Err(Error::Token);
}
simd::match_uri_vectored(bytes);
loop { let b = next!(bytes); if b == b' ' { return Ok(Status::Complete(unsafe { // all bytes up till `i` must have been `is_token`.
str::from_utf8_unchecked(bytes.slice_skip(1))
}));
} elseif !is_uri_token(b) { return Err(Error::Token);
}
}
}
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"*/*" } /// ][..])))); /// ``` pubfn parse_headers<'b: 'h, 'h>(
src: &'b [u8], mut dst: &'h mut [Header<'b>],
) -> Result<(usize, &'h [Header<'b>])> { letmut iter = Bytes::new(src); let pos = complete!(parse_headers_iter(&mut dst, &mut iter, &ParserConfig::default()));
Ok(Status::Complete((pos, dst)))
}
/* Flow of this function is pretty complex, especially with macros, *sothisstructmakessureweshrink`headers`toonlyparsedones. *Comparingtopreviouscode,thisonlymayintroducesomeadditional
* instructions in case of early return */ struct ShrinkOnDrop<'r1, 'r2, 'a> {
headers: &'r1 mut &'r2 mut [MaybeUninit<Header<'a>>],
num_headers: usize,
}
impl<'r1, 'r2, 'a> Drop for ShrinkOnDrop<'r1, 'r2, 'a> { fn drop(&mutself) { let headers = mem::replace(self.headers, &mut []);
/* SAFETY: num_headers is the number of initialized headers */ let headers = unsafe { headers.get_unchecked_mut(..self.num_headers) };
macro_rules! maybe_continue_after_obsolete_line_folding {
($bytes:ident, $label:lifetime) => { if config.allow_obsolete_multiline_headers_in_responses { 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_in_responses { return Err(Error::$err);
}
letmut 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);
}
count += $bytes.pos();
$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));
result = Ok(Status::Complete(count + bytes.pos())); break;
} if b == b'\n' {
result = Ok(Status::Complete(count + bytes.pos())); break;
} if !is_header_name_token(b) {
handle_invalid_char!(bytes, b, HeaderName);
}
// parse header name until colon let header_name: &str = 'name: loop { letmut b = next!(bytes);
if is_header_name_token(b) { continue'name;
}
count += bytes.pos(); let name = unsafe {
str::from_utf8_unchecked(bytes.slice_skip(1))
};
if b == b':' { break'name name;
}
if config.allow_spaces_after_header_name_in_responses { while b == b' ' || b == b'\t' {
b = next!(bytes);
if b == b':' {
count += bytes.pos();
bytes.slice(); break'name name;
}
}
}
handle_invalid_char!(bytes, b, HeaderName);
};
letmut b;
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' {
count += bytes.pos();
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));
} elseif b != b'\n' {
handle_invalid_char!(bytes, b, HeaderValue);
}
count += bytes.pos(); // 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 = iflet 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
};
/// 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)))); /// ``` pubfn parse_chunk_size(buf: &[u8])
-> result::Result<Status<(usize, u64)>, InvalidChunkSize> { const RADIX: u64 = 16; letmut bytes = Bytes::new(buf); letmut size = 0; letmut in_chunk_size = true; letmut in_ext = false; letmut 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;
size *= RADIX;
size += (b - b'0') as u64;
},
b'a' ..= b'f'if in_chunk_size => { if count > 15 { return Err(InvalidChunkSize);
}
count += 1;
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;
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 { usesuper::{Request, Response, Status, EMPTY_HEADER, parse_chunk_size};
/// 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() { letmut headers = [EMPTY_HEADER; NUM_OF_HEADERS]; letmut response = Response::new(&mut headers[..]); let result = response.parse(RESPONSE_WITH_WEIRD_WHITESPACE_DELIMITERS);
/// 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() { letmut headers = [EMPTY_HEADER; NUM_OF_HEADERS]; letmut request = Request::new(&mut headers[..]); let result = request.parse(REQUEST_WITH_WEIRD_WHITESPACE_DELIMITERS);
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)));
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()
.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);
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)));
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)));
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));
}
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)));
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)));
Die Informationen auf dieser Webseite wurden
nach bestem Wissen sorgfältig zusammengestellt. Es wird jedoch weder Vollständigkeit, noch Richtigkeit,
noch Qualität der bereit gestellten Informationen zugesichert.
Bemerkung:
Die farbliche Syntaxdarstellung und die Messung sind noch experimentell.