2023-07-18 23:25:10 +02:00
|
|
|
use nom::{
|
|
|
|
branch::alt,
|
2023-07-20 09:41:10 +02:00
|
|
|
bytes::complete::{tag, take_while1},
|
2023-07-18 23:25:10 +02:00
|
|
|
character::complete::space0,
|
2023-07-19 10:41:51 +02:00
|
|
|
combinator::{map, opt},
|
2023-07-20 09:41:10 +02:00
|
|
|
multi::{many0, many1, separated_list1},
|
2023-07-23 16:37:47 +02:00
|
|
|
sequence::preceded,
|
2023-07-18 23:25:10 +02:00
|
|
|
IResult,
|
|
|
|
};
|
2023-07-24 12:37:30 +02:00
|
|
|
use std::fmt;
|
2023-07-18 23:25:10 +02:00
|
|
|
|
|
|
|
use crate::text::{
|
|
|
|
ascii,
|
2023-07-23 16:37:47 +02:00
|
|
|
encoding::{self, encoded_word},
|
|
|
|
quoted::{quoted_string, QuotedString},
|
|
|
|
whitespace::{fws, is_obs_no_ws_ctl},
|
|
|
|
words::{atom, is_vchar, mime_atom},
|
2023-07-18 23:25:10 +02:00
|
|
|
};
|
|
|
|
|
|
|
|
#[derive(Debug, PartialEq, Default)]
|
2023-07-20 09:41:10 +02:00
|
|
|
pub struct PhraseList<'a>(pub Vec<Phrase<'a>>);
|
|
|
|
pub fn phrase_list(input: &[u8]) -> IResult<&[u8], PhraseList> {
|
|
|
|
map(separated_list1(tag(","), phrase), PhraseList)(input)
|
2023-07-19 22:27:59 +02:00
|
|
|
}
|
2023-07-18 23:25:10 +02:00
|
|
|
|
2023-07-24 22:08:13 +02:00
|
|
|
#[derive(Debug, PartialEq, Clone)]
|
2023-07-21 18:31:56 +02:00
|
|
|
pub enum MIMEWord<'a> {
|
|
|
|
Quoted(QuotedString<'a>),
|
|
|
|
Atom(&'a [u8]),
|
|
|
|
}
|
2023-07-22 11:53:26 +02:00
|
|
|
impl Default for MIMEWord<'static> {
|
|
|
|
fn default() -> Self {
|
|
|
|
Self::Atom(&[])
|
|
|
|
}
|
|
|
|
}
|
2023-07-21 18:31:56 +02:00
|
|
|
impl<'a> MIMEWord<'a> {
|
|
|
|
pub fn to_string(&self) -> String {
|
|
|
|
match self {
|
2023-07-22 09:47:20 +02:00
|
|
|
Self::Quoted(v) => v.to_string(),
|
2023-07-23 16:37:47 +02:00
|
|
|
Self::Atom(v) => encoding_rs::UTF_8
|
|
|
|
.decode_without_bom_handling(v)
|
|
|
|
.0
|
|
|
|
.to_string(),
|
2023-07-21 18:31:56 +02:00
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
pub fn mime_word(input: &[u8]) -> IResult<&[u8], MIMEWord> {
|
|
|
|
alt((
|
2023-07-23 16:37:47 +02:00
|
|
|
map(quoted_string, MIMEWord::Quoted),
|
2023-07-21 18:31:56 +02:00
|
|
|
map(mime_atom, MIMEWord::Atom),
|
|
|
|
))(input)
|
|
|
|
}
|
|
|
|
|
2023-07-23 18:35:13 +02:00
|
|
|
#[derive(PartialEq)]
|
2023-07-18 23:25:10 +02:00
|
|
|
pub enum Word<'a> {
|
2023-07-19 15:28:17 +02:00
|
|
|
Quoted(QuotedString<'a>),
|
2023-07-18 23:25:10 +02:00
|
|
|
Encoded(encoding::EncodedWord<'a>),
|
|
|
|
Atom(&'a [u8]),
|
|
|
|
}
|
2023-07-19 12:09:23 +02:00
|
|
|
|
2023-07-23 17:14:16 +02:00
|
|
|
impl<'a> ToString for Word<'a> {
|
|
|
|
fn to_string(&self) -> String {
|
2023-07-18 23:25:10 +02:00
|
|
|
match self {
|
|
|
|
Word::Quoted(v) => v.to_string(),
|
|
|
|
Word::Encoded(v) => v.to_string(),
|
2023-07-23 16:37:47 +02:00
|
|
|
Word::Atom(v) => encoding_rs::UTF_8
|
|
|
|
.decode_without_bom_handling(v)
|
|
|
|
.0
|
|
|
|
.to_string(),
|
2023-07-18 23:25:10 +02:00
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
2023-07-23 18:35:13 +02:00
|
|
|
impl<'a> fmt::Debug for Word<'a> {
|
2023-07-23 17:14:16 +02:00
|
|
|
fn fmt(&self, fmt: &mut fmt::Formatter<'_>) -> fmt::Result {
|
2023-07-24 12:37:30 +02:00
|
|
|
fmt.debug_tuple("Word")
|
|
|
|
.field(&format_args!("\"{}\"", self.to_string()))
|
|
|
|
.finish()
|
2023-07-23 17:14:16 +02:00
|
|
|
}
|
2023-07-23 18:35:13 +02:00
|
|
|
}
|
2023-07-18 23:25:10 +02:00
|
|
|
|
|
|
|
/// Word
|
|
|
|
///
|
|
|
|
/// ```abnf
|
|
|
|
/// word = atom / quoted-string
|
|
|
|
/// ```
|
|
|
|
pub fn word(input: &[u8]) -> IResult<&[u8], Word> {
|
|
|
|
alt((
|
2023-07-24 09:24:38 +02:00
|
|
|
map(quoted_string, Word::Quoted),
|
|
|
|
map(encoded_word, Word::Encoded),
|
|
|
|
map(atom, Word::Atom),
|
2023-07-18 23:25:10 +02:00
|
|
|
))(input)
|
|
|
|
}
|
|
|
|
|
2023-07-23 18:35:13 +02:00
|
|
|
#[derive(PartialEq)]
|
2023-07-18 23:25:10 +02:00
|
|
|
pub struct Phrase<'a>(pub Vec<Word<'a>>);
|
2023-07-19 12:09:23 +02:00
|
|
|
|
2023-07-23 17:14:16 +02:00
|
|
|
impl<'a> ToString for Phrase<'a> {
|
|
|
|
fn to_string(&self) -> String {
|
2023-07-23 16:37:47 +02:00
|
|
|
self.0
|
|
|
|
.iter()
|
|
|
|
.map(|v| v.to_string())
|
|
|
|
.collect::<Vec<String>>()
|
|
|
|
.join(" ")
|
2023-07-18 23:25:10 +02:00
|
|
|
}
|
|
|
|
}
|
2023-07-23 18:35:13 +02:00
|
|
|
impl<'a> fmt::Debug for Phrase<'a> {
|
2023-07-23 17:14:16 +02:00
|
|
|
fn fmt(&self, fmt: &mut fmt::Formatter<'_>) -> fmt::Result {
|
2023-07-24 12:37:30 +02:00
|
|
|
fmt.debug_tuple("Phrase")
|
|
|
|
.field(&format_args!("\"{}\"", self.to_string()))
|
|
|
|
.finish()
|
2023-07-23 17:14:16 +02:00
|
|
|
}
|
2023-07-23 18:35:13 +02:00
|
|
|
}
|
2023-07-18 23:25:10 +02:00
|
|
|
|
|
|
|
/// Phrase
|
|
|
|
///
|
|
|
|
/// ```abnf
|
|
|
|
/// phrase = 1*word / obs-phrase
|
|
|
|
/// ```
|
|
|
|
pub fn phrase(input: &[u8]) -> IResult<&[u8], Phrase> {
|
2023-07-24 09:24:38 +02:00
|
|
|
let (input, phrase) = map(many1(word), Phrase)(input)?;
|
2023-07-18 23:25:10 +02:00
|
|
|
Ok((input, phrase))
|
|
|
|
}
|
|
|
|
|
|
|
|
/// Compatible unstructured input
|
|
|
|
///
|
|
|
|
/// ```abnf
|
|
|
|
/// obs-utext = %d0 / obs-NO-WS-CTL / VCHAR
|
|
|
|
/// ```
|
|
|
|
fn is_unstructured(c: u8) -> bool {
|
|
|
|
is_vchar(c) || is_obs_no_ws_ctl(c) || c == ascii::NULL
|
|
|
|
}
|
|
|
|
|
2023-07-23 12:24:46 +02:00
|
|
|
#[derive(Debug, PartialEq, Clone)]
|
2023-07-19 10:41:51 +02:00
|
|
|
pub enum UnstrToken<'a> {
|
2023-07-18 23:25:10 +02:00
|
|
|
Init,
|
|
|
|
Encoded(encoding::EncodedWord<'a>),
|
|
|
|
Plain(&'a [u8]),
|
|
|
|
}
|
2023-07-19 12:09:23 +02:00
|
|
|
|
2023-07-23 17:14:16 +02:00
|
|
|
impl<'a> ToString for UnstrToken<'a> {
|
|
|
|
fn to_string(&self) -> String {
|
2023-07-18 23:25:10 +02:00
|
|
|
match self {
|
|
|
|
UnstrToken::Init => "".into(),
|
|
|
|
UnstrToken::Encoded(e) => e.to_string(),
|
2023-07-23 16:37:47 +02:00
|
|
|
UnstrToken::Plain(e) => encoding_rs::UTF_8
|
|
|
|
.decode_without_bom_handling(e)
|
|
|
|
.0
|
|
|
|
.into_owned(),
|
2023-07-18 23:25:10 +02:00
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2023-07-23 17:14:16 +02:00
|
|
|
#[derive(PartialEq, Clone)]
|
2023-07-18 23:25:10 +02:00
|
|
|
pub struct Unstructured<'a>(pub Vec<UnstrToken<'a>>);
|
2023-07-19 12:09:23 +02:00
|
|
|
|
2023-07-23 17:14:16 +02:00
|
|
|
impl<'a> ToString for Unstructured<'a> {
|
|
|
|
fn to_string(&self) -> String {
|
2023-07-23 16:37:47 +02:00
|
|
|
self.0
|
|
|
|
.iter()
|
|
|
|
.fold(
|
|
|
|
(&UnstrToken::Init, String::new()),
|
|
|
|
|(prev_token, mut result), current_token| {
|
|
|
|
match (prev_token, current_token) {
|
|
|
|
(UnstrToken::Init, v) => result.push_str(v.to_string().as_ref()),
|
|
|
|
(UnstrToken::Encoded(_), UnstrToken::Encoded(v)) => {
|
|
|
|
result.push_str(v.to_string().as_ref())
|
|
|
|
}
|
|
|
|
(_, v) => {
|
|
|
|
result.push(' ');
|
|
|
|
result.push_str(v.to_string().as_ref())
|
|
|
|
}
|
|
|
|
};
|
|
|
|
|
|
|
|
(current_token, result)
|
|
|
|
},
|
|
|
|
)
|
|
|
|
.1
|
2023-07-18 23:25:10 +02:00
|
|
|
}
|
|
|
|
}
|
2023-07-23 17:14:16 +02:00
|
|
|
impl<'a> fmt::Debug for Unstructured<'a> {
|
|
|
|
fn fmt(&self, fmt: &mut fmt::Formatter<'_>) -> fmt::Result {
|
2023-07-24 12:37:30 +02:00
|
|
|
fmt.debug_tuple("Unstructured")
|
|
|
|
.field(&format_args!("\"{}\"", self.to_string()))
|
|
|
|
.finish()
|
2023-07-23 17:14:16 +02:00
|
|
|
}
|
|
|
|
}
|
2023-07-18 23:25:10 +02:00
|
|
|
|
|
|
|
/// Unstructured header field body
|
|
|
|
///
|
|
|
|
/// ```abnf
|
|
|
|
/// unstructured = (*([FWS] VCHAR_SEQ) *WSP) / obs-unstruct
|
|
|
|
/// ```
|
|
|
|
pub fn unstructured(input: &[u8]) -> IResult<&[u8], Unstructured> {
|
2023-07-23 16:37:47 +02:00
|
|
|
let (input, r) = many0(preceded(
|
|
|
|
opt(fws),
|
|
|
|
alt((
|
2023-07-24 09:24:38 +02:00
|
|
|
map(encoded_word, UnstrToken::Encoded),
|
|
|
|
map(take_while1(is_unstructured), UnstrToken::Plain),
|
2023-07-23 16:37:47 +02:00
|
|
|
)),
|
|
|
|
))(input)?;
|
2023-07-18 23:25:10 +02:00
|
|
|
|
|
|
|
let (input, _) = space0(input)?;
|
|
|
|
Ok((input, Unstructured(r)))
|
|
|
|
}
|
|
|
|
|
|
|
|
#[cfg(test)]
|
|
|
|
mod tests {
|
|
|
|
use super::*;
|
|
|
|
#[test]
|
|
|
|
fn test_phrase() {
|
|
|
|
assert_eq!(
|
2023-07-23 16:37:47 +02:00
|
|
|
phrase(b"hello world").unwrap().1.to_string(),
|
2023-07-19 11:03:40 +02:00
|
|
|
"hello world".to_string(),
|
2023-07-18 23:25:10 +02:00
|
|
|
);
|
|
|
|
assert_eq!(
|
2023-07-19 11:03:40 +02:00
|
|
|
phrase(b"salut \"le\" monde").unwrap().1.to_string(),
|
|
|
|
"salut le monde".to_string(),
|
2023-07-18 23:25:10 +02:00
|
|
|
);
|
2023-07-19 11:03:40 +02:00
|
|
|
|
|
|
|
let (rest, parsed) = phrase(b"fin\r\n du\r\nmonde").unwrap();
|
|
|
|
assert_eq!(rest, &b"\r\nmonde"[..]);
|
|
|
|
assert_eq!(parsed.to_string(), "fin du".to_string());
|
2023-07-18 23:25:10 +02:00
|
|
|
}
|
|
|
|
}
|