diff --git a/src/lib.rs b/src/lib.rs index 7153de5..572e295 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -239,21 +239,66 @@ impl Status { } /// Parser configuration. -#[derive(Clone, Debug, Default)] +#[derive(Clone)] 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, ignore_invalid_headers_in_responses: bool, + + version_parser: fn(&mut Bytes) -> Result, +} + +impl fmt::Debug for ParserConfig { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + f.debug_struct("ParserConfig") + .field("allow_spaces_after_header_name_in_responses", &self.allow_spaces_after_header_name_in_responses) + .field("allow_obsolete_multiline_headers_in_responses", &self.allow_obsolete_multiline_headers_in_responses) + .field("allow_multiple_spaces_in_request_line_delimiters", &self.allow_multiple_spaces_in_request_line_delimiters) + .field("allow_multiple_spaces_in_response_status_delimiters", &self.ignore_invalid_headers_in_responses) + .field("ignore_invalid_headers_in_responses", &self.ignore_invalid_headers_in_responses) + .finish() + } +} + +impl Default for ParserConfig { + fn default() -> Self { + Self::const_default() + } } impl ParserConfig { + /// Returns default configuration. Same as `Default::default()` but can be used in const + /// context. + pub const fn const_default() -> Self { + Self { + allow_spaces_after_header_name_in_responses: false, + allow_obsolete_multiline_headers_in_responses: false, + allow_multiple_spaces_in_request_line_delimiters: false, + allow_multiple_spaces_in_response_status_delimiters: false, + ignore_invalid_headers_in_responses: false, + version_parser: HTTP_VERSION_PARSER, + } + } + + /// Sets first-line parsing to SIP + pub const fn set_sip_protocol_parser(mut self) -> Self { + self.version_parser = SIP_VERSION_PARSER; + self + } + + /// Set first-line parsing to HTTP + pub const fn set_http_protocol_parser(mut self) -> Self { + self.version_parser = HTTP_VERSION_PARSER; + self + } + /// Sets whether spaces and tabs should be allowed after header names in responses. - pub fn allow_spaces_after_header_name_in_responses( - &mut self, + pub const fn allow_spaces_after_header_name_in_responses( + mut self, value: bool, - ) -> &mut Self { + ) -> Self { self.allow_spaces_after_header_name_in_responses = value; self } @@ -271,13 +316,13 @@ impl ParserConfig { /// 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 { + pub const fn allow_multiple_spaces_in_request_line_delimiters(mut self, value: bool) -> 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 { + pub const fn multiple_spaces_in_request_line_delimiters_are_allowed(&self) -> bool { self.allow_multiple_spaces_in_request_line_delimiters } @@ -295,13 +340,13 @@ impl ParserConfig { /// 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 { + pub const fn allow_multiple_spaces_in_response_status_delimiters(mut self, value: bool) -> 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 { + pub const fn multiple_spaces_in_response_status_delimiters_are_allowed(&self) -> bool { self.allow_multiple_spaces_in_response_status_delimiters } @@ -328,16 +373,16 @@ impl ParserConfig { /// 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, + pub const fn allow_obsolete_multiline_headers_in_responses( + mut self, value: bool, - ) -> &mut Self { + ) -> 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 { + pub const fn obsolete_multiline_headers_in_responses_are_allowed(&self) -> bool { self.allow_obsolete_multiline_headers_in_responses } @@ -390,10 +435,10 @@ impl ParserConfig { /// 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, + pub const fn ignore_invalid_headers_in_responses( + mut self, value: bool, - ) -> &mut Self { + ) -> Self { self.ignore_invalid_headers_in_responses = value; self } @@ -501,14 +546,14 @@ impl<'h, 'b> Request<'h, 'b> { if config.allow_multiple_spaces_in_request_line_delimiters { complete!(skip_spaces(&mut bytes)); } - self.version = Some(complete!(parse_version(&mut bytes))); + self.version = Some(complete!((config.version_parser)(&mut bytes))); newline!(bytes); let len = orig_len - bytes.len(); let headers_len = complete!(parse_headers_iter_uninit( &mut headers, &mut bytes, - &ParserConfig::default(), + &ParserConfig::const_default(), )); /* SAFETY: see `parse_headers_iter_uninit` guarantees */ self.headers = unsafe { assume_init_slice(headers) }; @@ -626,7 +671,7 @@ impl<'h, 'b> Response<'h, 'b> { /// 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()) + self.parse_with_config(buf, &ParserConfig::const_default()) } fn parse_with_config(&mut self, buf: &'b [u8], config: &ParserConfig) -> Result { @@ -656,7 +701,7 @@ impl<'h, 'b> Response<'h, 'b> { let mut bytes = Bytes::new(buf); complete!(skip_empty_lines(&mut bytes)); - self.version = Some(complete!(parse_version(&mut bytes))); + self.version = Some(complete!((config.version_parser)(&mut bytes))); space!(bytes or Error::Version); if config.allow_multiple_spaces_in_response_status_delimiters { complete!(skip_spaces(&mut bytes)); @@ -742,8 +787,10 @@ impl<'a> fmt::Debug for Header<'a> { /// ``` pub const EMPTY_HEADER: Header<'static> = Header { name: "", value: b"" }; +const HTTP_VERSION_PARSER: fn(&mut Bytes) -> Result = parse_version_http; + #[inline] -fn parse_version(bytes: &mut Bytes<'_>) -> Result { +fn parse_version_http(bytes: &mut Bytes) -> Result { if let Some(eight) = bytes.peek_n::<[u8; 8]>(8) { unsafe { bytes.advance(8); } return match &eight { @@ -767,6 +814,33 @@ fn parse_version(bytes: &mut Bytes<'_>) -> Result { Ok(Status::Partial) } +const SIP_VERSION_PARSER: fn(&mut Bytes) -> Result = parse_version_sip; + +#[inline] +fn parse_version_sip(bytes: &mut Bytes) -> Result { + const PEEK_BY: usize = 7; + + if let Some(seven) = bytes.peek_n::<[u8; PEEK_BY]>(PEEK_BY) { + unsafe { bytes.advance(PEEK_BY); } + return match &seven { + b"SIP/2.0" => Ok(Status::Complete(0)), + _ => 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'S' => Err(Error::Version)); + expect!(bytes.next() == b'I' => Err(Error::Version)); + expect!(bytes.next() == b'P' => Err(Error::Version)); + expect!(bytes.next() == b'/' => Err(Error::Version)); + expect!(bytes.next() == b'2' => Err(Error::Version)); + expect!(bytes.next() == b'.' => Err(Error::Version)); + Ok(Status::Partial) +} + /// From [RFC 7230](https://tools.ietf.org/html/rfc7230): /// /// > ```notrust @@ -2233,4 +2307,35 @@ mod tests { assert_eq!(response.headers[0].name, "Bread"); assert_eq!(response.headers[0].value, &b"baguette"[..]); } + + #[test] + fn test_config_in_const_ctx() { + const CONFIG: super::ParserConfig = super::ParserConfig::const_default() + .allow_spaces_after_header_name_in_responses(true) + .allow_multiple_spaces_in_request_line_delimiters(true); + + assert!(CONFIG.allow_spaces_after_header_name_in_responses); + assert!(CONFIG.allow_multiple_spaces_in_request_line_delimiters); + } + + #[test] + fn test_sip_resp() { + const RESPONSE: &[u8] = + b"SIP/2.0 200 OK\r\nVia: SIP/2.0/UDP example.com;branch=FFFFFFFFFFFFFF;received=10.10.10.10\r\n\r\n"; + + let mut headers = [EMPTY_HEADER; 1]; + let mut response = Response::new(&mut headers[..]); + + let result = crate::ParserConfig::const_default() + .set_sip_protocol_parser() + .parse_response(&mut response, RESPONSE); + assert_eq!(result, Ok(Status::Complete(91))); + + assert_eq!(response.version.unwrap(), 0); + assert_eq!(response.code.unwrap(), 200); + assert_eq!(response.reason.unwrap(), "OK"); + assert_eq!(response.headers.len(), 1); + assert_eq!(response.headers[0].name, "Via"); + assert_eq!(response.headers[0].value, b"SIP/2.0/UDP example.com;branch=FFFFFFFFFFFFFF;received=10.10.10.10"); + } }