| //! Rules defined in [RFC 2822]. |
| //! |
| //! [RFC 2822]: https://datatracker.ietf.org/doc/html/rfc2822 |
| |
| use crate::parsing::combinator::rfc::rfc2234::wsp; |
| use crate::parsing::combinator::{ascii_char, one_or_more, zero_or_more}; |
| use crate::parsing::ParsedItem; |
| |
| /// Consume the `fws` rule. |
| // The full rule is equivalent to /\r\n[ \t]+|[ \t]+(?:\r\n[ \t]+)*/ |
| pub(crate) fn fws(mut input: &[u8]) -> Option<ParsedItem<'_, ()>> { |
| if let [b'\r', b'\n', rest @ ..] = input { |
| one_or_more(wsp)(rest) |
| } else { |
| input = one_or_more(wsp)(input)?.into_inner(); |
| while let [b'\r', b'\n', rest @ ..] = input { |
| input = one_or_more(wsp)(rest)?.into_inner(); |
| } |
| Some(ParsedItem(input, ())) |
| } |
| } |
| |
| /// Consume the `cfws` rule. |
| // The full rule is equivalent to any combination of `fws` and `comment` so long as it is not empty. |
| pub(crate) fn cfws(input: &[u8]) -> Option<ParsedItem<'_, ()>> { |
| one_or_more(|input| fws(input).or_else(|| comment(input)))(input) |
| } |
| |
| /// Consume the `comment` rule. |
| fn comment(mut input: &[u8]) -> Option<ParsedItem<'_, ()>> { |
| input = ascii_char::<b'('>(input)?.into_inner(); |
| input = zero_or_more(fws)(input).into_inner(); |
| while let Some(rest) = ccontent(input) { |
| input = rest.into_inner(); |
| input = zero_or_more(fws)(input).into_inner(); |
| } |
| input = ascii_char::<b')'>(input)?.into_inner(); |
| |
| Some(ParsedItem(input, ())) |
| } |
| |
| /// Consume the `ccontent` rule. |
| fn ccontent(input: &[u8]) -> Option<ParsedItem<'_, ()>> { |
| ctext(input) |
| .or_else(|| quoted_pair(input)) |
| .or_else(|| comment(input)) |
| } |
| |
| /// Consume the `ctext` rule. |
| #[allow(clippy::unnecessary_lazy_evaluations)] // rust-lang/rust-clippy#8522 |
| fn ctext(input: &[u8]) -> Option<ParsedItem<'_, ()>> { |
| no_ws_ctl(input).or_else(|| match input { |
| [33..=39 | 42..=91 | 93..=126, rest @ ..] => Some(ParsedItem(rest, ())), |
| _ => None, |
| }) |
| } |
| |
| /// Consume the `quoted_pair` rule. |
| fn quoted_pair(mut input: &[u8]) -> Option<ParsedItem<'_, ()>> { |
| input = ascii_char::<b'\\'>(input)?.into_inner(); |
| |
| let old_input_len = input.len(); |
| |
| input = text(input).into_inner(); |
| |
| // If nothing is parsed, this means we hit the `obs-text` rule and nothing matched. This is |
| // technically a success, but we should still check the `obs-qp` rule to ensure we consume |
| // everything possible. |
| if input.len() == old_input_len { |
| match input { |
| [0..=127, rest @ ..] => Some(ParsedItem(rest, ())), |
| _ => Some(ParsedItem(input, ())), |
| } |
| } else { |
| Some(ParsedItem(input, ())) |
| } |
| } |
| |
| /// Consume the `no_ws_ctl` rule. |
| const fn no_ws_ctl(input: &[u8]) -> Option<ParsedItem<'_, ()>> { |
| match input { |
| [1..=8 | 11..=12 | 14..=31 | 127, rest @ ..] => Some(ParsedItem(rest, ())), |
| _ => None, |
| } |
| } |
| |
| /// Consume the `text` rule. |
| fn text<'a>(input: &'a [u8]) -> ParsedItem<'a, ()> { |
| let new_text = |input: &'a [u8]| match input { |
| [1..=9 | 11..=12 | 14..=127, rest @ ..] => Some(ParsedItem(rest, ())), |
| _ => None, |
| }; |
| |
| let obs_char = |input: &'a [u8]| match input { |
| // This is technically allowed, but consuming this would mean the rest of the string is |
| // eagerly consumed without consideration for where the comment actually ends. |
| [b')', ..] => None, |
| [0..=9 | 11..=12 | 14..=127, rest @ ..] => Some(rest), |
| _ => None, |
| }; |
| |
| let obs_text = |mut input| { |
| input = zero_or_more(ascii_char::<b'\n'>)(input).into_inner(); |
| input = zero_or_more(ascii_char::<b'\r'>)(input).into_inner(); |
| while let Some(rest) = obs_char(input) { |
| input = rest; |
| input = zero_or_more(ascii_char::<b'\n'>)(input).into_inner(); |
| input = zero_or_more(ascii_char::<b'\r'>)(input).into_inner(); |
| } |
| |
| ParsedItem(input, ()) |
| }; |
| |
| new_text(input).unwrap_or_else(|| obs_text(input)) |
| } |