format code

This commit is contained in:
Quentin 2023-07-23 16:37:47 +02:00
parent 6e2b29ec57
commit c97595c128
Signed by: quentin
GPG key ID: E9602264D639FF68
26 changed files with 610 additions and 439 deletions

View file

@ -1,14 +1,14 @@
use crate::text::misc_token::{unstructured, Unstructured};
use crate::text::whitespace::{foldable_line, obs_crlf};
use nom::{ use nom::{
IResult,
branch::alt, branch::alt,
bytes::complete::{tag_no_case, tag, take_while1}, bytes::complete::{tag, tag_no_case, take_while1},
character::complete::space0, character::complete::space0,
combinator::{map}, combinator::map,
multi::many0, multi::many0,
sequence::{pair, terminated, tuple}, sequence::{pair, terminated, tuple},
IResult,
}; };
use crate::text::whitespace::{foldable_line, obs_crlf};
use crate::text::misc_token::{Unstructured, unstructured};
#[derive(Debug, PartialEq)] #[derive(Debug, PartialEq)]
pub enum CompField<'a, T> { pub enum CompField<'a, T> {
@ -21,25 +21,37 @@ pub enum CompField<'a, T> {
pub struct CompFieldList<'a, T>(pub Vec<CompField<'a, T>>); pub struct CompFieldList<'a, T>(pub Vec<CompField<'a, T>>);
impl<'a, T> CompFieldList<'a, T> { impl<'a, T> CompFieldList<'a, T> {
pub fn known(self) -> Vec<T> { pub fn known(self) -> Vec<T> {
self.0.into_iter().map(|v| match v { self.0
CompField::Known(f) => Some(f), .into_iter()
_ => None, .map(|v| match v {
}).flatten().collect() CompField::Known(f) => Some(f),
_ => None,
})
.flatten()
.collect()
} }
} }
pub fn header<'a, T>(fx: impl Fn(&'a [u8]) -> IResult<&'a [u8], T> + Copy) pub fn header<'a, T>(
-> impl Fn(&'a [u8]) -> IResult<&'a [u8], CompFieldList<T>> fx: impl Fn(&'a [u8]) -> IResult<&'a [u8], T> + Copy,
{ ) -> impl Fn(&'a [u8]) -> IResult<&'a [u8], CompFieldList<T>> {
move |input| map(terminated(many0(alt(( move |input| {
map(fx, CompField::Known), map(
map(opt_field, |(k,v)| CompField::Unknown(k,v)), terminated(
map(foldable_line, CompField::Bad), many0(alt((
))), obs_crlf), CompFieldList)(input) map(fx, CompField::Known),
map(opt_field, |(k, v)| CompField::Unknown(k, v)),
map(foldable_line, CompField::Bad),
))),
obs_crlf,
),
CompFieldList,
)(input)
}
} }
/* /*
pub fn header_in_boundaries<'a, T>(bound: &'a [u8], fx: impl Fn(&'a [u8]) -> IResult<&[u8], T> + Copy) pub fn header_in_boundaries<'a, T>(bound: &'a [u8], fx: impl Fn(&'a [u8]) -> IResult<&[u8], T> + Copy)
-> impl Fn(&'a [u8]) -> IResult<&[u8], CompFieldList<T>> -> impl Fn(&'a [u8]) -> IResult<&[u8], CompFieldList<T>>
{ {
move |input| map(terminated(many0(preceded( move |input| map(terminated(many0(preceded(
@ -53,12 +65,7 @@ pub fn header_in_boundaries<'a, T>(bound: &'a [u8], fx: impl Fn(&'a [u8]) -> IRe
*/ */
pub fn field_name<'a>(name: &'static [u8]) -> impl Fn(&'a [u8]) -> IResult<&'a [u8], &'a [u8]> { pub fn field_name<'a>(name: &'static [u8]) -> impl Fn(&'a [u8]) -> IResult<&'a [u8], &'a [u8]> {
move |input| { move |input| terminated(tag_no_case(name), tuple((space0, tag(b":"), space0)))(input)
terminated(
tag_no_case(name),
tuple((space0, tag(b":"), space0)),
)(input)
}
} }
/// Optional field /// Optional field
@ -78,7 +85,7 @@ pub fn opt_field(input: &[u8]) -> IResult<&[u8], (&[u8], Unstructured)> {
tuple((space0, tag(b":"), space0)), tuple((space0, tag(b":"), space0)),
), ),
unstructured, unstructured,
), obs_crlf)(input) ),
obs_crlf,
)(input)
} }

View file

@ -1,9 +1,9 @@
mod error; mod error;
mod text;
mod header; mod header;
mod rfc5322;
mod mime; mod mime;
mod part; mod part;
mod rfc5322;
mod text;
pub fn email(input: &[u8]) -> Result<part::part::Message, error::EMLError> { pub fn email(input: &[u8]) -> Result<part::part::Message, error::EMLError> {
part::part::message(mime::mime::Message::default())(input) part::part::message(mime::mime::Message::default())(input)

View file

@ -74,7 +74,6 @@ impl<'a> From<&'a [u8]> for EmailCharset {
b"utf-8" | b"utf8" => EmailCharset::UTF_8, b"utf-8" | b"utf8" => EmailCharset::UTF_8,
_ => EmailCharset::Unknown, _ => EmailCharset::Unknown,
} }
} }
} }
@ -112,21 +111,16 @@ impl EmailCharset {
} }
pub fn as_encoding(&self) -> &'static Encoding { pub fn as_encoding(&self) -> &'static Encoding {
Encoding::for_label(self.as_str().as_bytes()) Encoding::for_label(self.as_str().as_bytes()).unwrap_or(encoding_rs::WINDOWS_1252)
.unwrap_or(encoding_rs::WINDOWS_1252)
} }
} }
#[cfg(test)] #[cfg(test)]
mod tests { mod tests {
use super::*; use super::*;
#[test] #[test]
fn test_charset() { fn test_charset() {
assert_eq!( assert_eq!(EmailCharset::from(&b"Us-Ascii"[..]).as_str(), "US-ASCII",);
EmailCharset::from(&b"Us-Ascii"[..]).as_str(),
"US-ASCII",
);
assert_eq!( assert_eq!(
EmailCharset::from(&b"Us-Ascii"[..]).as_encoding(), EmailCharset::from(&b"Us-Ascii"[..]).as_encoding(),

View file

@ -1,17 +1,17 @@
use nom::{ use nom::{
IResult,
branch::alt, branch::alt,
combinator::map, combinator::map,
sequence::{preceded, terminated}, sequence::{preceded, terminated},
IResult,
}; };
use crate::text::whitespace::obs_crlf;
use crate::text::misc_token::{Unstructured, unstructured};
use crate::rfc5322::identification::{MessageID, msg_id};
use crate::header::{field_name, CompFieldList}; use crate::header::{field_name, CompFieldList};
use crate::mime::r#type::{NaiveType, naive_type}; use crate::mime::mechanism::{mechanism, Mechanism};
use crate::mime::mechanism::{Mechanism, mechanism};
use crate::mime::mime::AnyMIME; use crate::mime::mime::AnyMIME;
use crate::mime::r#type::{naive_type, NaiveType};
use crate::rfc5322::identification::{msg_id, MessageID};
use crate::text::misc_token::{unstructured, Unstructured};
use crate::text::whitespace::obs_crlf;
#[derive(Debug, PartialEq)] #[derive(Debug, PartialEq)]
pub enum Content<'a> { pub enum Content<'a> {
@ -22,46 +22,68 @@ pub enum Content<'a> {
} }
impl<'a> Content<'a> { impl<'a> Content<'a> {
pub fn ctype(&'a self) -> Option<&'a NaiveType<'a>> { pub fn ctype(&'a self) -> Option<&'a NaiveType<'a>> {
match self { Content::Type(v) => Some(v), _ => None } match self {
Content::Type(v) => Some(v),
_ => None,
}
} }
pub fn transfer_encoding(&'a self) -> Option<&'a Mechanism<'a>> { pub fn transfer_encoding(&'a self) -> Option<&'a Mechanism<'a>> {
match self { Content::TransferEncoding(v) => Some(v), _ => None } match self {
Content::TransferEncoding(v) => Some(v),
_ => None,
}
} }
pub fn id(&'a self) -> Option<&'a MessageID<'a>> { pub fn id(&'a self) -> Option<&'a MessageID<'a>> {
match self { Content::ID(v) => Some(v), _ => None } match self {
} Content::ID(v) => Some(v),
_ => None,
}
}
pub fn description(&'a self) -> Option<&'a Unstructured<'a>> { pub fn description(&'a self) -> Option<&'a Unstructured<'a>> {
match self { Content::Description(v) => Some(v), _ => None } match self {
Content::Description(v) => Some(v),
_ => None,
}
} }
} }
impl<'a> CompFieldList<'a, Content<'a>> { impl<'a> CompFieldList<'a, Content<'a>> {
pub fn to_mime(self) -> AnyMIME<'a> { pub fn to_mime(self) -> AnyMIME<'a> {
self.known().into_iter().collect::<AnyMIME>() self.known().into_iter().collect::<AnyMIME>()
} }
} }
pub fn content(input: &[u8]) -> IResult<&[u8], Content> { pub fn content(input: &[u8]) -> IResult<&[u8], Content> {
terminated(alt(( terminated(
preceded(field_name(b"content-type"), map(naive_type, Content::Type)), alt((
preceded(field_name(b"content-transfer-encoding"), map(mechanism, Content::TransferEncoding)), preceded(field_name(b"content-type"), map(naive_type, Content::Type)),
preceded(field_name(b"content-id"), map(msg_id, Content::ID)), preceded(
preceded(field_name(b"content-description"), map(unstructured, Content::Description)), field_name(b"content-transfer-encoding"),
)), obs_crlf)(input) map(mechanism, Content::TransferEncoding),
),
preceded(field_name(b"content-id"), map(msg_id, Content::ID)),
preceded(
field_name(b"content-description"),
map(unstructured, Content::Description),
),
)),
obs_crlf,
)(input)
} }
#[cfg(test)] #[cfg(test)]
mod tests { mod tests {
use super::*; use super::*;
use crate::mime::r#type::*; use crate::header::{header, CompFieldList};
use crate::mime::charset::EmailCharset; use crate::mime::charset::EmailCharset;
use crate::mime::r#type::*;
use crate::text::misc_token::MIMEWord; use crate::text::misc_token::MIMEWord;
use crate::text::quoted::QuotedString; use crate::text::quoted::QuotedString;
use crate::header::{header, CompFieldList};
#[test] #[test]
fn test_content_type() { fn test_content_type() {
let (rest, content) = content(b"Content-Type: text/plain; charset=UTF-8; format=flowed\r\n").unwrap(); let (rest, content) =
content(b"Content-Type: text/plain; charset=UTF-8; format=flowed\r\n").unwrap();
assert_eq!(&b""[..], rest); assert_eq!(&b""[..], rest);
if let Content::Type(nt) = content { if let Content::Type(nt) = content {
@ -91,7 +113,8 @@ Content-Transfer-Encoding: 7bit
This is a multipart message. This is a multipart message.
"#.as_bytes(); "#
.as_bytes();
assert_eq!( assert_eq!(
map(header(content), CompFieldList::known)(fullmail), map(header(content), CompFieldList::known)(fullmail),
@ -101,12 +124,12 @@ This is a multipart message.
Content::Type(NaiveType { Content::Type(NaiveType {
main: &b"multipart"[..], main: &b"multipart"[..],
sub: &b"alternative"[..], sub: &b"alternative"[..],
params: vec![ params: vec![Parameter {
Parameter { name: &b"boundary"[..],
name: &b"boundary"[..], value: MIMEWord::Quoted(QuotedString(vec![
value: MIMEWord::Quoted(QuotedString(vec![&b"b1_e376dc71bafc953c0b0fdeb9983a9956"[..]])), &b"b1_e376dc71bafc953c0b0fdeb9983a9956"[..]
} ])),
] }]
}), }),
Content::TransferEncoding(Mechanism::_7Bit), Content::TransferEncoding(Mechanism::_7Bit),
], ],

View file

@ -1,12 +1,12 @@
use crate::text::whitespace::cfws;
use crate::text::words::mime_atom as token;
use nom::{ use nom::{
IResult,
branch::alt, branch::alt,
bytes::complete::tag_no_case, bytes::complete::tag_no_case,
combinator::{map, opt, value}, combinator::{map, opt, value},
sequence::delimited, sequence::delimited,
IResult,
}; };
use crate::text::whitespace::cfws;
use crate::text::words::mime_atom as token;
#[derive(Debug, Clone, PartialEq, Default)] #[derive(Debug, Clone, PartialEq, Default)]
pub enum Mechanism<'a> { pub enum Mechanism<'a> {
@ -25,7 +25,7 @@ pub fn mechanism(input: &[u8]) -> IResult<&[u8], Mechanism> {
alt(( alt((
delimited( delimited(
opt(cfws), opt(cfws),
alt(( alt((
value(_7Bit, tag_no_case("7bit")), value(_7Bit, tag_no_case("7bit")),
value(_8Bit, tag_no_case("8bit")), value(_8Bit, tag_no_case("8bit")),
value(Binary, tag_no_case("binary")), value(Binary, tag_no_case("binary")),
@ -38,31 +38,24 @@ pub fn mechanism(input: &[u8]) -> IResult<&[u8], Mechanism> {
))(input) ))(input)
} }
#[cfg(test)] #[cfg(test)]
mod tests { mod tests {
use super::*; use super::*;
#[test] #[test]
fn test_mechanism() { fn test_mechanism() {
assert_eq!( assert_eq!(mechanism(b"7bit"), Ok((&b""[..], Mechanism::_7Bit)),);
mechanism(b"7bit"),
Ok((&b""[..], Mechanism::_7Bit)),
);
assert_eq!( assert_eq!(
mechanism(b"(youhou) 8bit"), mechanism(b"(youhou) 8bit"),
Ok((&b""[..], Mechanism::_8Bit)), Ok((&b""[..], Mechanism::_8Bit)),
); );
assert_eq!( assert_eq!(
mechanism(b"(blip) bInArY (blip blip)"), mechanism(b"(blip) bInArY (blip blip)"),
Ok((&b""[..], Mechanism::Binary)), Ok((&b""[..], Mechanism::Binary)),
); );
assert_eq!( assert_eq!(mechanism(b" base64 "), Ok((&b""[..], Mechanism::Base64)),);
mechanism(b" base64 "),
Ok((&b""[..], Mechanism::Base64)),
);
assert_eq!( assert_eq!(
mechanism(b" Quoted-Printable "), mechanism(b" Quoted-Printable "),

View file

@ -1,9 +1,9 @@
use crate::mime::mechanism::Mechanism;
use crate::rfc5322::identification::MessageID;
use crate::text::misc_token::Unstructured;
use crate::mime::field::Content; use crate::mime::field::Content;
use crate::mime::r#type::{AnyType, self as ctype}; //Multipart, Message, Text, Binary}; use crate::mime::mechanism::Mechanism;
use crate::mime::r#type::{self as ctype, AnyType};
use crate::rfc5322::identification::MessageID;
use crate::text::misc_token::Unstructured; //Multipart, Message, Text, Binary};
#[derive(Debug, PartialEq, Clone)] #[derive(Debug, PartialEq, Clone)]
pub struct Multipart<'a>(pub ctype::Multipart, pub Generic<'a>); pub struct Multipart<'a>(pub ctype::Multipart, pub Generic<'a>);
@ -24,7 +24,6 @@ pub enum AnyMIME<'a> {
Bin(Binary<'a>), Bin(Binary<'a>),
} }
impl<'a> AnyMIME<'a> { impl<'a> AnyMIME<'a> {
pub fn from_pair(at: AnyType, gen: Generic<'a>) -> Self { pub fn from_pair(at: AnyType, gen: Generic<'a>) -> Self {
match at { match at {
@ -38,7 +37,7 @@ impl<'a> AnyMIME<'a> {
impl<'a> FromIterator<Content<'a>> for AnyMIME<'a> { impl<'a> FromIterator<Content<'a>> for AnyMIME<'a> {
fn from_iter<I: IntoIterator<Item = Content<'a>>>(it: I) -> Self { fn from_iter<I: IntoIterator<Item = Content<'a>>>(it: I) -> Self {
let (at, gen) = it.into_iter().fold( let (at, gen) = it.into_iter().fold(
(AnyType::default(), Generic::default()), (AnyType::default(), Generic::default()),
|(mut at, mut section), field| { |(mut at, mut section), field| {
match field { match field {
@ -48,9 +47,9 @@ impl<'a> FromIterator<Content<'a>> for AnyMIME<'a> {
Content::Description(v) => section.description = Some(v), Content::Description(v) => section.description = Some(v),
}; };
(at, section) (at, section)
} },
); );
Self::from_pair(at, gen) Self::from_pair(at, gen)
} }
} }
@ -61,4 +60,3 @@ pub struct Generic<'a> {
pub id: Option<MessageID<'a>>, pub id: Option<MessageID<'a>>,
pub description: Option<Unstructured<'a>>, pub description: Option<Unstructured<'a>>,
} }

View file

@ -1,5 +1,5 @@
pub mod charset; pub mod charset;
pub mod mechanism;
pub mod r#type;
pub mod field; pub mod field;
pub mod mechanism;
pub mod mime; pub mod mime;
pub mod r#type;

View file

@ -1,14 +1,14 @@
use nom::{ use nom::{
bytes::complete::tag, bytes::complete::tag,
combinator::map, combinator::map,
multi::many0, multi::many0,
sequence::{preceded, tuple}, sequence::{preceded, tuple},
IResult, IResult,
}; };
use crate::text::misc_token::{MIMEWord, mime_word};
use crate::text::words::{mime_atom};
use crate::mime::charset::EmailCharset; use crate::mime::charset::EmailCharset;
use crate::text::misc_token::{mime_word, MIMEWord};
use crate::text::words::mime_atom;
// --------- NAIVE TYPE // --------- NAIVE TYPE
#[derive(Debug, PartialEq)] #[derive(Debug, PartialEq)]
@ -18,7 +18,9 @@ pub struct NaiveType<'a> {
pub params: Vec<Parameter<'a>>, pub params: Vec<Parameter<'a>>,
} }
impl<'a> NaiveType<'a> { impl<'a> NaiveType<'a> {
pub fn to_type(&self) -> AnyType { self.into() } pub fn to_type(&self) -> AnyType {
self.into()
}
} }
pub fn naive_type(input: &[u8]) -> IResult<&[u8], NaiveType> { pub fn naive_type(input: &[u8]) -> IResult<&[u8], NaiveType> {
map( map(
@ -33,7 +35,10 @@ pub struct Parameter<'a> {
pub value: MIMEWord<'a>, pub value: MIMEWord<'a>,
} }
pub fn parameter(input: &[u8]) -> IResult<&[u8], Parameter> { pub fn parameter(input: &[u8]) -> IResult<&[u8], Parameter> {
map(tuple((mime_atom, tag(b"="), mime_word)), |(name, _, value)| Parameter { name, value })(input) map(
tuple((mime_atom, tag(b"="), mime_word)),
|(name, _, value)| Parameter { name, value },
)(input)
} }
pub fn parameter_list(input: &[u8]) -> IResult<&[u8], Vec<Parameter>> { pub fn parameter_list(input: &[u8]) -> IResult<&[u8], Vec<Parameter>> {
many0(preceded(tag(";"), parameter))(input) many0(preceded(tag(";"), parameter))(input)
@ -60,7 +65,9 @@ impl Default for AnyType {
impl<'a> From<&'a NaiveType<'a>> for AnyType { impl<'a> From<&'a NaiveType<'a>> for AnyType {
fn from(nt: &'a NaiveType<'a>) -> Self { fn from(nt: &'a NaiveType<'a>) -> Self {
match nt.main.to_ascii_lowercase().as_slice() { match nt.main.to_ascii_lowercase().as_slice() {
b"multipart" => Multipart::try_from(nt).map(Self::Multipart).unwrap_or(Self::default()), b"multipart" => Multipart::try_from(nt)
.map(Self::Multipart)
.unwrap_or(Self::default()),
b"message" => Self::Message(Message::from(nt)), b"message" => Self::Message(Message::from(nt)),
b"text" => Self::Text(Text::from(nt)), b"text" => Self::Text(Text::from(nt)),
_ => Self::Binary(Binary::default()), _ => Self::Binary(Binary::default()),
@ -77,7 +84,8 @@ impl<'a> TryFrom<&'a NaiveType<'a>> for Multipart {
type Error = (); type Error = ();
fn try_from(nt: &'a NaiveType<'a>) -> Result<Self, Self::Error> { fn try_from(nt: &'a NaiveType<'a>) -> Result<Self, Self::Error> {
nt.params.iter() nt.params
.iter()
.find(|x| x.name.to_ascii_lowercase().as_slice() == b"boundary") .find(|x| x.name.to_ascii_lowercase().as_slice() == b"boundary")
.map(|boundary| Multipart { .map(|boundary| Multipart {
subtype: MultipartSubtype::from(nt), subtype: MultipartSubtype::from(nt),
@ -137,7 +145,9 @@ impl<'a> From<&NaiveType<'a>> for Text {
fn from(nt: &NaiveType<'a>) -> Self { fn from(nt: &NaiveType<'a>) -> Self {
Self { Self {
subtype: TextSubtype::from(nt), subtype: TextSubtype::from(nt),
charset: nt.params.iter() charset: nt
.params
.iter()
.find(|x| x.name.to_ascii_lowercase().as_slice() == b"charset") .find(|x| x.name.to_ascii_lowercase().as_slice() == b"charset")
.map(|x| EmailCharset::from(x.value.to_string().as_bytes())) .map(|x| EmailCharset::from(x.value.to_string().as_bytes()))
.unwrap_or(EmailCharset::US_ASCII), .unwrap_or(EmailCharset::US_ASCII),
@ -175,17 +185,23 @@ mod tests {
fn test_parameter() { fn test_parameter() {
assert_eq!( assert_eq!(
parameter(b"charset=utf-8"), parameter(b"charset=utf-8"),
Ok((&b""[..], Parameter { Ok((
name: &b"charset"[..], &b""[..],
value: MIMEWord::Atom(&b"utf-8"[..]), Parameter {
})), name: &b"charset"[..],
value: MIMEWord::Atom(&b"utf-8"[..]),
}
)),
); );
assert_eq!( assert_eq!(
parameter(b"charset=\"utf-8\""), parameter(b"charset=\"utf-8\""),
Ok((&b""[..], Parameter { Ok((
name: &b"charset"[..], &b""[..],
value: MIMEWord::Quoted(QuotedString(vec![&b"utf-8"[..]])), Parameter {
})), name: &b"charset"[..],
value: MIMEWord::Quoted(QuotedString(vec![&b"utf-8"[..]])),
}
)),
); );
} }
@ -195,7 +211,7 @@ mod tests {
assert_eq!(rest, &b""[..]); assert_eq!(rest, &b""[..]);
assert_eq!( assert_eq!(
nt.to_type(), nt.to_type(),
AnyType::Text(Text { AnyType::Text(Text {
charset: EmailCharset::UTF_8, charset: EmailCharset::UTF_8,
subtype: TextSubtype::Plain, subtype: TextSubtype::Plain,
@ -203,7 +219,6 @@ mod tests {
); );
} }
#[test] #[test]
fn test_content_type_multipart() { fn test_content_type_multipart() {
let (rest, nt) = naive_type(b"multipart/mixed;\r\n\tboundary=\"--==_mimepart_64a3f2c69114f_2a13d020975fe\";\r\n\tcharset=UTF-8").unwrap(); let (rest, nt) = naive_type(b"multipart/mixed;\r\n\tboundary=\"--==_mimepart_64a3f2c69114f_2a13d020975fe\";\r\n\tcharset=UTF-8").unwrap();
@ -222,20 +237,20 @@ mod tests {
let (rest, nt) = naive_type(b"message/rfc822").unwrap(); let (rest, nt) = naive_type(b"message/rfc822").unwrap();
assert_eq!(rest, &[]); assert_eq!(rest, &[]);
assert_eq!( assert_eq!(nt.to_type(), AnyType::Message(Message::RFC822),);
nt.to_type(),
AnyType::Message(Message::RFC822),
);
} }
#[test] #[test]
fn test_parameter_ascii() { fn test_parameter_ascii() {
assert_eq!( assert_eq!(
parameter(b"charset = (simple) us-ascii (Plain text)"), parameter(b"charset = (simple) us-ascii (Plain text)"),
Ok((&b""[..], Parameter { Ok((
name: &b"charset"[..], &b""[..],
value: MIMEWord::Atom(&b"us-ascii"[..]), Parameter {
})) name: &b"charset"[..],
value: MIMEWord::Atom(&b"us-ascii"[..]),
}
))
); );
} }
} }

View file

@ -1,25 +1,29 @@
use nom::{ use nom::{
IResult,
branch::alt, branch::alt,
bytes::complete::{is_not}, bytes::complete::is_not,
multi::many0,
sequence::{pair},
combinator::{map, not, recognize}, combinator::{map, not, recognize},
multi::many0,
sequence::pair,
IResult,
}; };
use crate::mime;
use crate::mime::mime::{AnyMIME};
use crate::rfc5322::{self as imf};
use crate::text::boundary::{Delimiter, boundary};
use crate::text::whitespace::obs_crlf;
use crate::text::ascii::CRLF;
use crate::header::{header, CompFieldList}; use crate::header::{header, CompFieldList};
use crate::mime;
use crate::mime::mime::AnyMIME;
use crate::rfc5322::{self as imf};
use crate::text::ascii::CRLF;
use crate::text::boundary::{boundary, Delimiter};
use crate::text::whitespace::obs_crlf;
#[derive(Debug, PartialEq)] #[derive(Debug, PartialEq)]
pub struct Multipart<'a>(pub mime::mime::Multipart<'a>, pub Vec<AnyPart<'a>>); pub struct Multipart<'a>(pub mime::mime::Multipart<'a>, pub Vec<AnyPart<'a>>);
#[derive(Debug, PartialEq)] #[derive(Debug, PartialEq)]
pub struct Message<'a>(pub mime::mime::Message<'a>, pub imf::message::Message<'a>, pub Box<AnyPart<'a>>); pub struct Message<'a>(
pub mime::mime::Message<'a>,
pub imf::message::Message<'a>,
pub Box<AnyPart<'a>>,
);
#[derive(Debug, PartialEq)] #[derive(Debug, PartialEq)]
pub struct Text<'a>(pub mime::mime::Text<'a>, pub &'a [u8]); pub struct Text<'a>(pub mime::mime::Text<'a>, pub &'a [u8]);
@ -68,9 +72,18 @@ impl<'a> MixedField<'a> {
impl<'a> CompFieldList<'a, MixedField<'a>> { impl<'a> CompFieldList<'a, MixedField<'a>> {
pub fn sections(self) -> (mime::mime::AnyMIME<'a>, imf::message::Message<'a>) { pub fn sections(self) -> (mime::mime::AnyMIME<'a>, imf::message::Message<'a>) {
let k = self.known(); let k = self.known();
let (v1, v2): (Vec<MixedField>, Vec<MixedField>) = k.into_iter().partition(|v| v.mime().is_some()); let (v1, v2): (Vec<MixedField>, Vec<MixedField>) =
let mime = v1.into_iter().map(|v| v.to_mime()).flatten().collect::<mime::mime::AnyMIME>(); k.into_iter().partition(|v| v.mime().is_some());
let imf = v2.into_iter().map(|v| v.to_imf()).flatten().collect::<imf::message::Message>(); let mime = v1
.into_iter()
.map(|v| v.to_mime())
.flatten()
.collect::<mime::mime::AnyMIME>();
let imf = v2
.into_iter()
.map(|v| v.to_imf())
.flatten()
.collect::<imf::message::Message>();
(mime, imf) (mime, imf)
} }
} }
@ -81,7 +94,9 @@ pub fn mixed_field(input: &[u8]) -> IResult<&[u8], MixedField> {
))(input) ))(input)
} }
pub fn message<'a>(m: mime::mime::Message<'a>) -> impl Fn(&'a [u8]) -> IResult<&'a [u8], Message<'a>> { pub fn message<'a>(
m: mime::mime::Message<'a>,
) -> impl Fn(&'a [u8]) -> IResult<&'a [u8], Message<'a>> {
move |input: &[u8]| { move |input: &[u8]| {
let (input, fields) = header(mixed_field)(input)?; let (input, fields) = header(mixed_field)(input)?;
let (in_mime, imf) = fields.sections(); let (in_mime, imf) = fields.sections();
@ -92,7 +107,9 @@ pub fn message<'a>(m: mime::mime::Message<'a>) -> impl Fn(&'a [u8]) -> IResult<&
} }
} }
pub fn multipart<'a>(m: mime::mime::Multipart<'a>) -> impl Fn(&'a [u8]) -> IResult<&'a [u8], Multipart<'a>> { pub fn multipart<'a>(
m: mime::mime::Multipart<'a>,
) -> impl Fn(&'a [u8]) -> IResult<&'a [u8], Multipart<'a>> {
let m = m.clone(); let m = m.clone();
move |input| { move |input| {
@ -124,17 +141,16 @@ pub fn multipart<'a>(m: mime::mime::Multipart<'a>) -> impl Fn(&'a [u8]) -> IResu
pub fn to_anypart<'a>(m: AnyMIME<'a>, rpart: &'a [u8]) -> AnyPart<'a> { pub fn to_anypart<'a>(m: AnyMIME<'a>, rpart: &'a [u8]) -> AnyPart<'a> {
match m { match m {
AnyMIME::Mult(a) => map(multipart(a), AnyPart::Mult)(rpart) AnyMIME::Mult(a) => map(multipart(a), AnyPart::Mult)(rpart)
.map(|v| v.1) .map(|v| v.1)
.unwrap_or(AnyPart::Txt(Text(mime::mime::Text::default(), rpart))), .unwrap_or(AnyPart::Txt(Text(mime::mime::Text::default(), rpart))),
AnyMIME::Msg(a) => map(message(a), AnyPart::Msg)(rpart) AnyMIME::Msg(a) => map(message(a), AnyPart::Msg)(rpart)
.map(|v| v.1) .map(|v| v.1)
.unwrap_or(AnyPart::Txt(Text(mime::mime::Text::default(), rpart))), .unwrap_or(AnyPart::Txt(Text(mime::mime::Text::default(), rpart))),
AnyMIME::Txt(a) => AnyPart::Txt(Text(a, rpart)), AnyMIME::Txt(a) => AnyPart::Txt(Text(a, rpart)),
AnyMIME::Bin(a) => AnyPart::Bin(Binary(a, rpart)), AnyMIME::Bin(a) => AnyPart::Bin(Binary(a, rpart)),
} }
} }
pub fn part_raw<'a>(bound: &[u8]) -> impl Fn(&'a [u8]) -> IResult<&'a [u8], &'a [u8]> + '_ { pub fn part_raw<'a>(bound: &[u8]) -> impl Fn(&'a [u8]) -> IResult<&'a [u8], &'a [u8]> + '_ {
move |input| { move |input| {
recognize(many0(pair( recognize(many0(pair(
@ -147,14 +163,15 @@ pub fn part_raw<'a>(bound: &[u8]) -> impl Fn(&'a [u8]) -> IResult<&'a [u8], &'a
#[cfg(test)] #[cfg(test)]
mod tests { mod tests {
use super::*; use super::*;
use crate::text::encoding::{Base64Word, EncodedWord, QuotedChunk, QuotedWord};
use crate::text::misc_token::{Phrase, UnstrToken, Unstructured, Word};
use chrono::{FixedOffset, TimeZone}; use chrono::{FixedOffset, TimeZone};
use crate::text::misc_token::{Word, Phrase, Unstructured, UnstrToken};
use crate::text::encoding::{EncodedWord, QuotedChunk, Base64Word, QuotedWord};
#[test] #[test]
fn test_preamble() { fn test_preamble() {
assert_eq!( assert_eq!(
part_raw(b"hello")(b"blip part_raw(b"hello")(
b"blip
bloup bloup
blip blip
@ -164,7 +181,8 @@ bloup--
--hello --hello
Field: Body Field: Body
"), "
),
Ok(( Ok((
&b"\n--hello\nField: Body\n"[..], &b"\n--hello\nField: Body\n"[..],
&b"blip\nbloup\n\nblip\nbloup--\n--bim\n--bim--\n"[..], &b"blip\nbloup\n\nblip\nbloup--\n--bim\n--bim--\n"[..],
@ -292,7 +310,8 @@ OoOoOoOoOoOoOoOoOoOoOoOoOoOoOoOoO<br />
</div> </div>
--b1_e376dc71bafc953c0b0fdeb9983a9956-- --b1_e376dc71bafc953c0b0fdeb9983a9956--
"#.as_bytes(); "#
.as_bytes();
let base_mime = mime::mime::Message::default(); let base_mime = mime::mime::Message::default();
assert_eq!( assert_eq!(
@ -353,7 +372,7 @@ OoOoOoOoOoOoOoOoOoOoOoOoOoOoOoOoO<br />
UnstrToken::Encoded(EncodedWord::Base64(Base64Word{ UnstrToken::Encoded(EncodedWord::Base64(Base64Word{
enc: encoding_rs::WINDOWS_1252, enc: encoding_rs::WINDOWS_1252,
content: &b"SWYgeW91IGNhbiByZWFkIHRoaXMgeW8"[..], content: &b"SWYgeW91IGNhbiByZWFkIHRoaXMgeW8"[..],
})), })),
UnstrToken::Encoded(EncodedWord::Base64(Base64Word{ UnstrToken::Encoded(EncodedWord::Base64(Base64Word{
enc: encoding_rs::ISO_8859_2, enc: encoding_rs::ISO_8859_2,
content: &b"dSB1bmRlcnN0YW5kIHRoZSBleGFtcGxlLg"[..], content: &b"dSB1bmRlcnN0YW5kIHRoZSBleGFtcGxlLg"[..],
@ -387,7 +406,7 @@ OoOoOoOoOoOoOoOoOoOoOoOoOoOoOoOoO<br />
} }
), ),
&b"GZ\nOoOoO\noOoOoOoOo\noOoOoOoOoOoOoOoOo\noOoOoOoOoOoOoOoOoOoOoOo\noOoOoOoOoOoOoOoOoOoOoOoOoOoOo\nOoOoOoOoOoOoOoOoOoOoOoOoOoOoOoOoO\n"[..], &b"GZ\nOoOoO\noOoOoOoOo\noOoOoOoOoOoOoOoOo\noOoOoOoOoOoOoOoOoOoOoOo\noOoOoOoOoOoOoOoOoOoOoOoOoOoOo\nOoOoOoOoOoOoOoOoOoOoOoOoOoOoOoOoO\n"[..],
)), )),
AnyPart::Txt(Text( AnyPart::Txt(Text(
mime::mime::Text( mime::mime::Text(
mime::r#type::Text { mime::r#type::Text {
@ -405,7 +424,7 @@ oOoOoOoOoOoOoOoOoOoOoOoOoOoOo<br />
OoOoOoOoOoOoOoOoOoOoOoOoOoOoOoOoO<br /> OoOoOoOoOoOoOoOoOoOoOoOoOoOoOoOoO<br />
</div> </div>
"#[..], "#[..],
)), )),
], ],
))), ))),
), ),

View file

@ -141,21 +141,22 @@ pub fn address_list_cfws(input: &[u8]) -> IResult<&[u8], Vec<AddressRef>> {
} }
pub fn nullable_address_list(input: &[u8]) -> IResult<&[u8], Vec<AddressRef>> { pub fn nullable_address_list(input: &[u8]) -> IResult<&[u8], Vec<AddressRef>> {
map( map(opt(alt((address_list, address_list_cfws))), |v| {
opt(alt((address_list, address_list_cfws))), v.unwrap_or(vec![])
|v| v.unwrap_or(vec![]), })(input)
)(input)
} }
#[cfg(test)] #[cfg(test)]
mod tests { mod tests {
use super::*; use super::*;
use crate::text::misc_token::{Phrase, Word};
use crate::rfc5322::mailbox::{AddrSpec, Domain, LocalPart, LocalPartToken}; use crate::rfc5322::mailbox::{AddrSpec, Domain, LocalPart, LocalPartToken};
use crate::text::misc_token::{Phrase, Word};
#[test] #[test]
fn test_mailbox_list() { fn test_mailbox_list() {
match mailbox_list(r#"Pete(A nice \) chap) <pete(his account)@silly.test(his host)>"#.as_bytes()) { match mailbox_list(
r#"Pete(A nice \) chap) <pete(his account)@silly.test(his host)>"#.as_bytes(),
) {
Ok((rest, _)) => assert_eq!(&b""[..], rest), Ok((rest, _)) => assert_eq!(&b""[..], rest),
_ => panic!(), _ => panic!(),
}; };

View file

@ -145,7 +145,13 @@ fn strict_year(input: &[u8]) -> IResult<&[u8], i32> {
fws, fws,
map( map(
terminated(take_while_m_n(4, 9, |c| c >= 0x30 && c <= 0x39), digit0), terminated(take_while_m_n(4, 9, |c| c >= 0x30 && c <= 0x39), digit0),
|d: &[u8]| encoding_rs::UTF_8.decode_without_bom_handling(d).0.parse::<i32>().unwrap_or(0), |d: &[u8]| {
encoding_rs::UTF_8
.decode_without_bom_handling(d)
.0
.parse::<i32>()
.unwrap_or(0)
},
), ),
fws, fws,
)(input) )(input)
@ -225,8 +231,10 @@ fn strict_zone(input: &[u8]) -> IResult<&[u8], Option<FixedOffset>> {
take_while_m_n(2, 2, |c| c >= 0x30 && c <= 0x39), take_while_m_n(2, 2, |c| c >= 0x30 && c <= 0x39),
)), )),
|(_, op, dig_zone_hour, dig_zone_min)| { |(_, op, dig_zone_hour, dig_zone_min)| {
let zone_hour: i32 = ((dig_zone_hour[0] - 0x30) * 10 + (dig_zone_hour[1] - 0x30)) as i32 * HOUR; let zone_hour: i32 =
let zone_min: i32 = ((dig_zone_min[0] - 0x30) * 10 + (dig_zone_min[1] - 0x30)) as i32 * MIN; ((dig_zone_hour[0] - 0x30) * 10 + (dig_zone_hour[1] - 0x30)) as i32 * HOUR;
let zone_min: i32 =
((dig_zone_min[0] - 0x30) * 10 + (dig_zone_min[1] - 0x30)) as i32 * MIN;
match op { match op {
b"+" => FixedOffset::east_opt(zone_hour + zone_min), b"+" => FixedOffset::east_opt(zone_hour + zone_min),
b"-" => FixedOffset::west_opt(zone_hour + zone_min), b"-" => FixedOffset::west_opt(zone_hour + zone_min),
@ -298,7 +306,6 @@ fn obs_zone(input: &[u8]) -> IResult<&[u8], Option<FixedOffset>> {
value(FixedOffset::east_opt(11 * HOUR), tag_no_case(b"L")), value(FixedOffset::east_opt(11 * HOUR), tag_no_case(b"L")),
value(FixedOffset::east_opt(12 * HOUR), tag_no_case(b"M")), value(FixedOffset::east_opt(12 * HOUR), tag_no_case(b"M")),
)), )),
// Military Timezones West // Military Timezones West
alt(( alt((
value(FixedOffset::west_opt(1 * HOUR), tag_no_case(b"N")), value(FixedOffset::west_opt(1 * HOUR), tag_no_case(b"N")),
@ -314,7 +321,6 @@ fn obs_zone(input: &[u8]) -> IResult<&[u8], Option<FixedOffset>> {
value(FixedOffset::west_opt(11 * HOUR), tag_no_case(b"X")), value(FixedOffset::west_opt(11 * HOUR), tag_no_case(b"X")),
value(FixedOffset::west_opt(12 * HOUR), tag_no_case(b"Y")), value(FixedOffset::west_opt(12 * HOUR), tag_no_case(b"Y")),
)), )),
// Unknown timezone // Unknown timezone
value(FixedOffset::west_opt(0 * HOUR), alphanumeric1), value(FixedOffset::west_opt(0 * HOUR), alphanumeric1),
)), )),
@ -367,7 +373,8 @@ mod tests {
Feb Feb
1969 1969
23:32 23:32
-0330 (Newfoundland Time)"#.as_bytes() -0330 (Newfoundland Time)"#
.as_bytes()
), ),
Ok(( Ok((
&b""[..], &b""[..],

View file

@ -1,21 +1,21 @@
use chrono::{DateTime, FixedOffset}; use chrono::{DateTime, FixedOffset};
use nom::{ use nom::{
IResult,
branch::alt, branch::alt,
combinator::map, combinator::map,
sequence::{preceded, terminated}, sequence::{preceded, terminated},
IResult,
}; };
use crate::text::whitespace::{obs_crlf};
use crate::rfc5322::address::{AddressList, address_list, nullable_address_list, mailbox_list};
use crate::rfc5322::datetime::section as date;
use crate::rfc5322::mailbox::{MailboxRef, MailboxList, AddrSpec, mailbox};
use crate::rfc5322::identification::{MessageID, MessageIDList, msg_id, msg_list};
use crate::rfc5322::trace::{ReceivedLog, return_path, received_log};
use crate::rfc5322::mime::{Version, version};
use crate::rfc5322::message::Message;
use crate::header::{field_name, header}; use crate::header::{field_name, header};
use crate::text::misc_token::{Unstructured, PhraseList, unstructured, phrase_list}; use crate::rfc5322::address::{address_list, mailbox_list, nullable_address_list, AddressList};
use crate::rfc5322::datetime::section as date;
use crate::rfc5322::identification::{msg_id, msg_list, MessageID, MessageIDList};
use crate::rfc5322::mailbox::{mailbox, AddrSpec, MailboxList, MailboxRef};
use crate::rfc5322::message::Message;
use crate::rfc5322::mime::{version, Version};
use crate::rfc5322::trace::{received_log, return_path, ReceivedLog};
use crate::text::misc_token::{phrase_list, unstructured, PhraseList, Unstructured};
use crate::text::whitespace::obs_crlf;
#[derive(Debug, PartialEq)] #[derive(Debug, PartialEq)]
pub enum Field<'a> { pub enum Field<'a> {
@ -50,7 +50,6 @@ pub enum Field<'a> {
MIMEVersion(Version), MIMEVersion(Version),
} }
#[derive(Debug, PartialEq)] #[derive(Debug, PartialEq)]
pub struct FieldList<'a>(pub Vec<Field<'a>>); pub struct FieldList<'a>(pub Vec<Field<'a>>);
impl<'a> FieldList<'a> { impl<'a> FieldList<'a> {
@ -60,30 +59,33 @@ impl<'a> FieldList<'a> {
} }
pub fn field(input: &[u8]) -> IResult<&[u8], Field> { pub fn field(input: &[u8]) -> IResult<&[u8], Field> {
terminated(alt(( terminated(
preceded(field_name(b"date"), map(date, Field::Date)), alt((
preceded(field_name(b"date"), map(date, Field::Date)),
preceded(field_name(b"from"), map(mailbox_list, Field::From)), preceded(field_name(b"from"), map(mailbox_list, Field::From)),
preceded(field_name(b"sender"), map(mailbox, Field::Sender)), preceded(field_name(b"sender"), map(mailbox, Field::Sender)),
preceded(field_name(b"reply-to"), map(address_list, Field::ReplyTo)), preceded(field_name(b"reply-to"), map(address_list, Field::ReplyTo)),
preceded(field_name(b"to"), map(address_list, Field::To)),
preceded(field_name(b"to"), map(address_list, Field::To)), preceded(field_name(b"cc"), map(address_list, Field::Cc)),
preceded(field_name(b"cc"), map(address_list, Field::Cc)), preceded(field_name(b"bcc"), map(nullable_address_list, Field::Bcc)),
preceded(field_name(b"bcc"), map(nullable_address_list, Field::Bcc)), preceded(field_name(b"message-id"), map(msg_id, Field::MessageID)),
preceded(field_name(b"in-reply-to"), map(msg_list, Field::InReplyTo)),
preceded(field_name(b"message-id"), map(msg_id, Field::MessageID)), preceded(field_name(b"references"), map(msg_list, Field::References)),
preceded(field_name(b"in-reply-to"), map(msg_list, Field::InReplyTo)), preceded(field_name(b"subject"), map(unstructured, Field::Subject)),
preceded(field_name(b"references"), map(msg_list, Field::References)), preceded(field_name(b"comments"), map(unstructured, Field::Comments)),
preceded(field_name(b"keywords"), map(phrase_list, Field::Keywords)),
preceded(field_name(b"subject"), map(unstructured, Field::Subject)), preceded(
preceded(field_name(b"comments"), map(unstructured, Field::Comments)), field_name(b"return-path"),
preceded(field_name(b"keywords"), map(phrase_list, Field::Keywords)), map(return_path, Field::ReturnPath),
),
preceded(field_name(b"return-path"), map(return_path, Field::ReturnPath)), preceded(field_name(b"received"), map(received_log, Field::Received)),
preceded(field_name(b"received"), map(received_log, Field::Received)), preceded(
field_name(b"mime-version"),
preceded(field_name(b"mime-version"), map(version, Field::MIMEVersion)), map(version, Field::MIMEVersion),
)), obs_crlf)(input) ),
)),
obs_crlf,
)(input)
} }
pub fn message(input: &[u8]) -> IResult<&[u8], Message> { pub fn message(input: &[u8]) -> IResult<&[u8], Message> {
@ -93,10 +95,10 @@ pub fn message(input: &[u8]) -> IResult<&[u8], Message> {
#[cfg(test)] #[cfg(test)]
mod tests { mod tests {
use super::*; use super::*;
use chrono::{FixedOffset, TimeZone};
use crate::rfc5322::mailbox::*;
use crate::rfc5322::address::*; use crate::rfc5322::address::*;
use crate::rfc5322::mailbox::*;
use crate::text::misc_token::*; use crate::text::misc_token::*;
use chrono::{FixedOffset, TimeZone};
#[test] #[test]
fn test_header() { fn test_header() {

View file

@ -11,7 +11,6 @@ use crate::rfc5322::mailbox::is_dtext;
use crate::text::whitespace::cfws; use crate::text::whitespace::cfws;
use crate::text::words::dot_atom_text; use crate::text::words::dot_atom_text;
#[derive(Debug, PartialEq, Clone)] #[derive(Debug, PartialEq, Clone)]
pub struct MessageID<'a> { pub struct MessageID<'a> {
pub left: &'a [u8], pub left: &'a [u8],

View file

@ -7,10 +7,10 @@ use nom::{
IResult, IResult,
}; };
use crate::text::misc_token::{phrase, word, Word, Phrase};
use crate::text::whitespace::{cfws, fws, is_obs_no_ws_ctl};
use crate::text::words::{atom};
use crate::text::ascii; use crate::text::ascii;
use crate::text::misc_token::{phrase, word, Phrase, Word};
use crate::text::whitespace::{cfws, fws, is_obs_no_ws_ctl};
use crate::text::words::atom;
#[derive(Debug, PartialEq)] #[derive(Debug, PartialEq)]
pub struct AddrSpec<'a> { pub struct AddrSpec<'a> {
@ -19,7 +19,11 @@ pub struct AddrSpec<'a> {
} }
impl<'a> AddrSpec<'a> { impl<'a> AddrSpec<'a> {
pub fn to_string(&self) -> String { pub fn to_string(&self) -> String {
format!("{}@{}", self.local_part.to_string(), self.domain.to_string()) format!(
"{}@{}",
self.local_part.to_string(),
self.domain.to_string()
)
} }
} }
@ -33,7 +37,7 @@ impl<'a> MailboxRef<'a> {
pub fn to_string(&self) -> String { pub fn to_string(&self) -> String {
match &self.name { match &self.name {
Some(n) => format!("{} <{}>", n.to_string(), self.addrspec.to_string()), Some(n) => format!("{} <{}>", n.to_string(), self.addrspec.to_string()),
None => self.addrspec.to_string() None => self.addrspec.to_string(),
} }
} }
} }
@ -96,7 +100,8 @@ fn obs_domain_list(input: &[u8]) -> IResult<&[u8], Vec<Option<Domain>>> {
separated_list1( separated_list1(
tag(&[ascii::COMMA]), tag(&[ascii::COMMA]),
preceded(many0(cfws), opt(preceded(tag(&[ascii::AT]), obs_domain))), preceded(many0(cfws), opt(preceded(tag(&[ascii::AT]), obs_domain))),
))(input) ),
)(input)
} }
/// AddrSpec /// AddrSpec
@ -129,16 +134,13 @@ pub struct LocalPart<'a>(pub Vec<LocalPartToken<'a>>);
impl<'a> LocalPart<'a> { impl<'a> LocalPart<'a> {
pub fn to_string(&self) -> String { pub fn to_string(&self) -> String {
self.0.iter().fold( self.0.iter().fold(String::new(), |mut acc, token| {
String::new(), match token {
|mut acc, token| { LocalPartToken::Dot => acc.push('.'),
match token { LocalPartToken::Word(v) => acc.push_str(v.to_string().as_ref()),
LocalPartToken::Dot => acc.push('.'),
LocalPartToken::Word(v) => acc.push_str(v.to_string().as_ref()),
}
acc
} }
) acc
})
} }
} }
@ -157,7 +159,7 @@ impl<'a> LocalPart<'a> {
fn obs_local_part(input: &[u8]) -> IResult<&[u8], LocalPart> { fn obs_local_part(input: &[u8]) -> IResult<&[u8], LocalPart> {
map( map(
many0(alt(( many0(alt((
map(tag(&[ascii::PERIOD]), |_| LocalPartToken::Dot), map(tag(&[ascii::PERIOD]), |_| LocalPartToken::Dot),
map(word, |v| LocalPartToken::Word(v)), map(word, |v| LocalPartToken::Word(v)),
))), ))),
|v| LocalPart(v), |v| LocalPart(v),
@ -173,12 +175,30 @@ pub enum Domain<'a> {
impl<'a> Domain<'a> { impl<'a> Domain<'a> {
pub fn to_string(&self) -> String { pub fn to_string(&self) -> String {
match self { match self {
Domain::Atoms(v) => v.iter().map(|v| encoding_rs::UTF_8.decode_without_bom_handling(v).0.to_string()).collect::<Vec<String>>().join("."), Domain::Atoms(v) => v
.iter()
.map(|v| {
encoding_rs::UTF_8
.decode_without_bom_handling(v)
.0
.to_string()
})
.collect::<Vec<String>>()
.join("."),
Domain::Litteral(v) => { Domain::Litteral(v) => {
let inner = v.iter().map(|v| encoding_rs::UTF_8.decode_without_bom_handling(v).0.to_string()).collect::<Vec<String>>().join(" "); let inner = v
.iter()
.map(|v| {
encoding_rs::UTF_8
.decode_without_bom_handling(v)
.0
.to_string()
})
.collect::<Vec<String>>()
.join(" ");
format!("[{}]", inner) format!("[{}]", inner)
} }
} }
} }
} }
@ -211,10 +231,10 @@ fn domain_litteral(input: &[u8]) -> IResult<&[u8], Domain> {
} }
fn inner_domain_litteral(input: &[u8]) -> IResult<&[u8], Domain> { fn inner_domain_litteral(input: &[u8]) -> IResult<&[u8], Domain> {
map( map(
terminated(many0(preceded(opt(fws), take_while1(is_dtext))), opt(fws)), terminated(many0(preceded(opt(fws), take_while1(is_dtext))), opt(fws)),
|v| Domain::Litteral(v), |v| Domain::Litteral(v),
)(input) )(input)
} }
fn is_strict_dtext(c: u8) -> bool { fn is_strict_dtext(c: u8) -> bool {
@ -257,7 +277,10 @@ mod tests {
"jsmith@[192.168.2.1]".to_string(), "jsmith@[192.168.2.1]".to_string(),
); );
assert_eq!( assert_eq!(
addr_spec(b"jsmith@[IPv6:2001:db8::1]").unwrap().1.to_string(), addr_spec(b"jsmith@[IPv6:2001:db8::1]")
.unwrap()
.1
.to_string(),
"jsmith@[IPv6:2001:db8::1]".to_string(), "jsmith@[IPv6:2001:db8::1]".to_string(),
); );
@ -276,21 +299,29 @@ mod tests {
// ASCII Edge cases // ASCII Edge cases
assert_eq!( assert_eq!(
addr_spec(b"user+mailbox/department=shipping@example.com").unwrap().1.to_string(), addr_spec(b"user+mailbox/department=shipping@example.com")
.unwrap()
.1
.to_string(),
"user+mailbox/department=shipping@example.com".to_string(), "user+mailbox/department=shipping@example.com".to_string(),
); );
assert_eq!( assert_eq!(
addr_spec(b"!#$%&'*+-/=?^_`.{|}~@example.com").unwrap().1.to_string(), addr_spec(b"!#$%&'*+-/=?^_`.{|}~@example.com")
.unwrap()
.1
.to_string(),
"!#$%&'*+-/=?^_`.{|}~@example.com".to_string(), "!#$%&'*+-/=?^_`.{|}~@example.com".to_string(),
); );
assert_eq!( assert_eq!(
addr_spec(r#""Abc@def"@example.com"#.as_bytes()), addr_spec(r#""Abc@def"@example.com"#.as_bytes()),
Ok(( Ok((
&b""[..], &b""[..],
AddrSpec { AddrSpec {
local_part: LocalPart(vec![LocalPartToken::Word(Word::Quoted(QuotedString(vec![b"Abc@def"])))]), local_part: LocalPart(vec![LocalPartToken::Word(Word::Quoted(QuotedString(
vec![b"Abc@def"]
)))]),
domain: Domain::Atoms(vec![&b"example"[..], &b"com"[..]]), domain: Domain::Atoms(vec![&b"example"[..], &b"com"[..]]),
} }
)) ))
@ -300,7 +331,9 @@ mod tests {
Ok(( Ok((
&b""[..], &b""[..],
AddrSpec { AddrSpec {
local_part: LocalPart(vec![LocalPartToken::Word(Word::Quoted(QuotedString(vec![b"Fred", b" ", b"Bloggs"])))]), local_part: LocalPart(vec![LocalPartToken::Word(Word::Quoted(QuotedString(
vec![b"Fred", b" ", b"Bloggs"]
)))]),
domain: Domain::Atoms(vec![&b"example"[..], &b"com"[..]]), domain: Domain::Atoms(vec![&b"example"[..], &b"com"[..]]),
} }
)) ))
@ -310,7 +343,9 @@ mod tests {
Ok(( Ok((
&b""[..], &b""[..],
AddrSpec { AddrSpec {
local_part: LocalPart(vec![LocalPartToken::Word(Word::Quoted(QuotedString(vec![b"Joe.", &[ascii::BACKSLASH], b"Blow"])))]), local_part: LocalPart(vec![LocalPartToken::Word(Word::Quoted(QuotedString(
vec![b"Joe.", &[ascii::BACKSLASH], b"Blow"]
)))]),
domain: Domain::Atoms(vec![&b"example"[..], &b"com"[..]]), domain: Domain::Atoms(vec![&b"example"[..], &b"com"[..]]),
} }
)) ))
@ -324,7 +359,13 @@ mod tests {
Ok(( Ok((
&b""[..], &b""[..],
MailboxRef { MailboxRef {
name: Some(Phrase(vec![Word::Quoted(QuotedString(vec![&b"Joe"[..], &[ascii::SP], &b"Q."[..], &[ascii::SP], &b"Public"[..]]))])), name: Some(Phrase(vec![Word::Quoted(QuotedString(vec![
&b"Joe"[..],
&[ascii::SP],
&b"Q."[..],
&[ascii::SP],
&b"Public"[..]
]))])),
addrspec: AddrSpec { addrspec: AddrSpec {
local_part: LocalPart(vec![ local_part: LocalPart(vec![
LocalPartToken::Word(Word::Atom(&b"john"[..])), LocalPartToken::Word(Word::Atom(&b"john"[..])),
@ -344,7 +385,10 @@ mod tests {
Ok(( Ok((
&b""[..], &b""[..],
MailboxRef { MailboxRef {
name: Some(Phrase(vec![Word::Atom(&b"Mary"[..]), Word::Atom(&b"Smith"[..])])), name: Some(Phrase(vec![
Word::Atom(&b"Mary"[..]),
Word::Atom(&b"Smith"[..])
])),
addrspec: AddrSpec { addrspec: AddrSpec {
local_part: LocalPart(vec![LocalPartToken::Word(Word::Atom(&b"mary"[..]))]), local_part: LocalPart(vec![LocalPartToken::Word(Word::Atom(&b"mary"[..]))]),
domain: Domain::Atoms(vec![&b"x"[..], &b"test"[..]]), domain: Domain::Atoms(vec![&b"x"[..], &b"test"[..]]),
@ -410,7 +454,9 @@ mod tests {
&b"Box"[..] &b"Box"[..]
]))])), ]))])),
addrspec: AddrSpec { addrspec: AddrSpec {
local_part: LocalPart(vec![LocalPartToken::Word(Word::Atom(&b"sysservices"[..]))]), local_part: LocalPart(vec![LocalPartToken::Word(Word::Atom(
&b"sysservices"[..]
))]),
domain: Domain::Atoms(vec![&b"example"[..], &b"net"[..]]), domain: Domain::Atoms(vec![&b"example"[..], &b"net"[..]]),
} }
} }
@ -428,19 +474,27 @@ mod tests {
@33+4.com,,,, @33+4.com,,,,
,,,, ,,,,
(again) (again)
@example.com,@yep.com,@a,@b,,,@c"#.as_bytes() @example.com,@yep.com,@a,@b,,,@c"#
.as_bytes()
), ),
Ok(( Ok((
&b""[..], &b""[..],
vec![ vec![
None, None,
Some(Domain::Atoms(vec![&b"33+4"[..], &b"com"[..]])), Some(Domain::Atoms(vec![&b"33+4"[..], &b"com"[..]])),
None, None, None, None, None, None, None, None,
None,
None,
None,
None,
None,
None,
Some(Domain::Atoms(vec![&b"example"[..], &b"com"[..]])), Some(Domain::Atoms(vec![&b"example"[..], &b"com"[..]])),
Some(Domain::Atoms(vec![&b"yep"[..], &b"com"[..]])), Some(Domain::Atoms(vec![&b"yep"[..], &b"com"[..]])),
Some(Domain::Atoms(vec![&b"a"[..]])), Some(Domain::Atoms(vec![&b"a"[..]])),
Some(Domain::Atoms(vec![&b"b"[..]])), Some(Domain::Atoms(vec![&b"b"[..]])),
None, None, None,
None,
Some(Domain::Atoms(vec![&b"c"[..]])), Some(Domain::Atoms(vec![&b"c"[..]])),
] ]
)) ))
@ -455,7 +509,7 @@ mod tests {
&b""[..], &b""[..],
AddrSpec { AddrSpec {
local_part: LocalPart(vec![ local_part: LocalPart(vec![
LocalPartToken::Word(Word::Atom(&b"a"[..])), LocalPartToken::Word(Word::Atom(&b"a"[..])),
LocalPartToken::Dot, LocalPartToken::Dot,
LocalPartToken::Dot, LocalPartToken::Dot,
LocalPartToken::Word(Word::Atom(&b"howard"[..])), LocalPartToken::Word(Word::Atom(&b"howard"[..])),
@ -505,17 +559,18 @@ mod tests {
#[test] #[test]
fn test_enron4() { fn test_enron4() {
assert_eq!( assert_eq!(
mailbox(r#"<"mark_kopinski/intl/acim/americancentury"@americancentury.com@enron.com>"#.as_bytes()), mailbox(
r#"<"mark_kopinski/intl/acim/americancentury"@americancentury.com@enron.com>"#
.as_bytes()
),
Ok(( Ok((
&b""[..], &b""[..],
MailboxRef { MailboxRef {
name: None, name: None,
addrspec: AddrSpec { addrspec: AddrSpec {
local_part: LocalPart(vec![ local_part: LocalPart(vec![LocalPartToken::Word(Word::Quoted(
LocalPartToken::Word(Word::Quoted(QuotedString(vec![ QuotedString(vec![&b"mark_kopinski/intl/acim/americancentury"[..],])
&b"mark_kopinski/intl/acim/americancentury"[..], ))]),
])))
]),
domain: Domain::Atoms(vec![&b"americancentury"[..], &b"com"[..]]), domain: Domain::Atoms(vec![&b"americancentury"[..], &b"com"[..]]),
} }
} }

View file

@ -1,10 +1,10 @@
use crate::text::misc_token::{PhraseList, Unstructured}; use crate::rfc5322::address::AddressRef;
use crate::rfc5322::mime::Version;
use crate::rfc5322::mailbox::{AddrSpec, MailboxRef};
use crate::rfc5322::address::{AddressRef};
use crate::rfc5322::identification::{MessageID};
use crate::rfc5322::field::Field; use crate::rfc5322::field::Field;
use crate::rfc5322::identification::MessageID;
use crate::rfc5322::mailbox::{AddrSpec, MailboxRef};
use crate::rfc5322::mime::Version;
use crate::rfc5322::trace::ReceivedLog; use crate::rfc5322::trace::ReceivedLog;
use crate::text::misc_token::{PhraseList, Unstructured};
use chrono::{DateTime, FixedOffset}; use chrono::{DateTime, FixedOffset};
#[derive(Debug, PartialEq, Default)] #[derive(Debug, PartialEq, Default)]
@ -45,9 +45,8 @@ pub struct Message<'a> {
// it may result in missing data or silently overriden data. // it may result in missing data or silently overriden data.
impl<'a> FromIterator<Field<'a>> for Message<'a> { impl<'a> FromIterator<Field<'a>> for Message<'a> {
fn from_iter<I: IntoIterator<Item = Field<'a>>>(iter: I) -> Self { fn from_iter<I: IntoIterator<Item = Field<'a>>>(iter: I) -> Self {
iter.into_iter().fold( iter.into_iter()
Message::default(), .fold(Message::default(), |mut section, field| {
|mut section, field| {
match field { match field {
Field::Date(v) => section.date = v, Field::Date(v) => section.date = v,
Field::From(v) => section.from.extend(v), Field::From(v) => section.from.extend(v),
@ -67,7 +66,6 @@ impl<'a> FromIterator<Field<'a>> for Message<'a> {
Field::MIMEVersion(v) => section.mime_version = Some(v), Field::MIMEVersion(v) => section.mime_version = Some(v),
}; };
section section
} })
)
} }
} }

View file

@ -1,8 +1,8 @@
use nom::{ use nom::{
IResult,
sequence::tuple,
bytes::complete::{tag, take}, bytes::complete::{tag, take},
combinator::{map, opt, verify}, combinator::{map, opt, verify},
sequence::tuple,
IResult,
}; };
use crate::text::ascii; use crate::text::ascii;
@ -41,7 +41,10 @@ mod tests {
#[test] #[test]
fn test_version() { fn test_version() {
assert_eq!(version(b"1.0"), Ok((&b""[..], Version { major: 1, minor: 0 })),); assert_eq!(
version(b"1.0"),
Ok((&b""[..], Version { major: 1, minor: 0 })),
);
assert_eq!( assert_eq!(
version(b" 1.0 (produced by MetaSend Vx.x)"), version(b" 1.0 (produced by MetaSend Vx.x)"),

View file

@ -1,8 +1,8 @@
pub mod mailbox;
pub mod address; pub mod address;
pub mod datetime; pub mod datetime;
pub mod trace;
pub mod identification;
pub mod mime;
pub mod field; pub mod field;
pub mod identification;
pub mod mailbox;
pub mod message; pub mod message;
pub mod mime;
pub mod trace;

View file

@ -1,21 +1,21 @@
use chrono::{DateTime, FixedOffset};
use nom::{ use nom::{
branch::alt, branch::alt,
bytes::complete::{is_a, tag}, bytes::complete::{is_a, tag},
combinator::{map, opt, not}, combinator::{map, not, opt},
multi::many0, multi::many0,
sequence::{tuple, terminated}, sequence::{terminated, tuple},
IResult, IResult,
}; };
use chrono::{DateTime, FixedOffset};
use crate::rfc5322::{datetime, mailbox}; use crate::rfc5322::{datetime, mailbox};
use crate::text::{ascii, whitespace, misc_token }; use crate::text::{ascii, misc_token, whitespace};
#[derive(Debug, PartialEq)] #[derive(Debug, PartialEq)]
pub enum ReceivedLogToken<'a> { pub enum ReceivedLogToken<'a> {
Addr(mailbox::AddrSpec<'a>), Addr(mailbox::AddrSpec<'a>),
Domain(mailbox::Domain<'a>), Domain(mailbox::Domain<'a>),
Word(misc_token::Word<'a>) Word(misc_token::Word<'a>),
} }
#[derive(Debug, PartialEq)] #[derive(Debug, PartialEq)]
@ -37,12 +37,11 @@ impl<'a> TryFrom<&'a lazy::ReceivedLog<'a>> for ReceivedLog<'a> {
pub fn received_log(input: &[u8]) -> IResult<&[u8], ReceivedLog> { pub fn received_log(input: &[u8]) -> IResult<&[u8], ReceivedLog> {
map( map(
tuple(( tuple((many0(received_tokens), tag(";"), datetime::section)),
many0(received_tokens), |(tokens, _, dt)| ReceivedLog {
tag(";"), log: tokens,
datetime::section, date: dt,
)), },
|(tokens, _, dt)| ReceivedLog { log: tokens, date: dt } ,
)(input) )(input)
} }
@ -63,7 +62,10 @@ fn empty_path(input: &[u8]) -> IResult<&[u8], Option<mailbox::AddrSpec>> {
fn received_tokens(input: &[u8]) -> IResult<&[u8], ReceivedLogToken> { fn received_tokens(input: &[u8]) -> IResult<&[u8], ReceivedLogToken> {
alt(( alt((
terminated(map(misc_token::word, |x| ReceivedLogToken::Word(x)), not(is_a([ascii::PERIOD, ascii::AT]))), terminated(
map(misc_token::word, |x| ReceivedLogToken::Word(x)),
not(is_a([ascii::PERIOD, ascii::AT])),
),
map(mailbox::angle_addr, |x| ReceivedLogToken::Addr(x)), map(mailbox::angle_addr, |x| ReceivedLogToken::Addr(x)),
map(mailbox::addr_spec, |x| ReceivedLogToken::Addr(x)), map(mailbox::addr_spec, |x| ReceivedLogToken::Addr(x)),
map(mailbox::obs_domain, |x| ReceivedLogToken::Domain(x)), map(mailbox::obs_domain, |x| ReceivedLogToken::Domain(x)),
@ -73,8 +75,8 @@ fn received_tokens(input: &[u8]) -> IResult<&[u8], ReceivedLogToken> {
#[cfg(test)] #[cfg(test)]
mod tests { mod tests {
use super::*; use super::*;
use chrono::TimeZone;
use crate::rfc5322::trace::misc_token::Word; use crate::rfc5322::trace::misc_token::Word;
use chrono::TimeZone;
#[test] #[test]
fn test_received_body() { fn test_received_body() {
@ -82,17 +84,27 @@ mod tests {
by server with LMTP by server with LMTP
id xxxxxxxxx id xxxxxxxxx
(envelope-from <gitlab@example.com>) (envelope-from <gitlab@example.com>)
for <me@example.com>; Tue, 13 Jun 2023 19:01:08 +0000"#.as_bytes(); for <me@example.com>; Tue, 13 Jun 2023 19:01:08 +0000"#
.as_bytes();
assert_eq!( assert_eq!(
received_log(hdrs), received_log(hdrs),
Ok(( Ok((
&b""[..], &b""[..],
ReceivedLog { ReceivedLog {
date: Some(FixedOffset::east_opt(0).unwrap().with_ymd_and_hms(2023, 06, 13, 19, 1, 8).unwrap()), date: Some(
FixedOffset::east_opt(0)
.unwrap()
.with_ymd_and_hms(2023, 06, 13, 19, 1, 8)
.unwrap()
),
log: vec![ log: vec![
ReceivedLogToken::Word(Word::Atom(&b"from"[..])), ReceivedLogToken::Word(Word::Atom(&b"from"[..])),
ReceivedLogToken::Domain(mailbox::Domain::Atoms(vec![&b"smtp"[..], &b"example"[..], &b"com"[..]])), ReceivedLogToken::Domain(mailbox::Domain::Atoms(vec![
&b"smtp"[..],
&b"example"[..],
&b"com"[..]
])),
ReceivedLogToken::Word(Word::Atom(&b"by"[..])), ReceivedLogToken::Word(Word::Atom(&b"by"[..])),
ReceivedLogToken::Word(Word::Atom(&b"server"[..])), ReceivedLogToken::Word(Word::Atom(&b"server"[..])),
ReceivedLogToken::Word(Word::Atom(&b"with"[..])), ReceivedLogToken::Word(Word::Atom(&b"with"[..])),
@ -101,11 +113,13 @@ mod tests {
ReceivedLogToken::Word(Word::Atom(&b"xxxxxxxxx"[..])), ReceivedLogToken::Word(Word::Atom(&b"xxxxxxxxx"[..])),
ReceivedLogToken::Word(Word::Atom(&b"for"[..])), ReceivedLogToken::Word(Word::Atom(&b"for"[..])),
ReceivedLogToken::Addr(mailbox::AddrSpec { ReceivedLogToken::Addr(mailbox::AddrSpec {
local_part: mailbox::LocalPart(vec![mailbox::LocalPartToken::Word(Word::Atom(&b"me"[..]))]), local_part: mailbox::LocalPart(vec![mailbox::LocalPartToken::Word(
domain: mailbox::Domain::Atoms(vec![&b"example"[..], &b"com"[..]]), Word::Atom(&b"me"[..])
)]),
domain: mailbox::Domain::Atoms(vec![&b"example"[..], &b"com"[..]]),
}) })
], ],
} }
)) ))
); );
} }

View file

@ -4,7 +4,7 @@ pub const NULL: u8 = 0x00; // NULL
pub const SOH: u8 = 0x01; // START OF HEADER pub const SOH: u8 = 0x01; // START OF HEADER
pub const STX: u8 = 0x02; // START OF TEXT pub const STX: u8 = 0x02; // START OF TEXT
pub const ETX: u8 = 0x03; // END OF TEXT pub const ETX: u8 = 0x03; // END OF TEXT
pub const EOT: u8 = 0x04; // pub const EOT: u8 = 0x04; //
pub const ANQ: u8 = 0x05; pub const ANQ: u8 = 0x05;
pub const ACK: u8 = 0x06; pub const ACK: u8 = 0x06;
pub const BEL: u8 = 0x07; pub const BEL: u8 = 0x07;
@ -20,7 +20,7 @@ pub const DLE: u8 = 0x10;
pub const DC1: u8 = 0x11; pub const DC1: u8 = 0x11;
pub const DC2: u8 = 0x12; pub const DC2: u8 = 0x12;
pub const DC3: u8 = 0x13; pub const DC3: u8 = 0x13;
pub const DC4 : u8 = 0x14; pub const DC4: u8 = 0x14;
pub const NAK: u8 = 0x15; pub const NAK: u8 = 0x15;
pub const SYN: u8 = 0x16; pub const SYN: u8 = 0x16;
pub const ETB: u8 = 0x17; pub const ETB: u8 = 0x17;
@ -36,7 +36,7 @@ pub const DEL: u8 = 0x7F;
// -- GRAPHIC CHARACTERS // -- GRAPHIC CHARACTERS
pub const SP: u8 = 0x20; // space pub const SP: u8 = 0x20; // space
pub const EXCLAMATION: u8 = 0x21; // ! pub const EXCLAMATION: u8 = 0x21; // !
pub const DQUOTE: u8 = 0x22; // " pub const DQUOTE: u8 = 0x22; // "
pub const NUM: u8 = 0x23; // # pub const NUM: u8 = 0x23; // #
pub const DOLLAR: u8 = 0x24; // $ pub const DOLLAR: u8 = 0x24; // $

View file

@ -1,21 +1,22 @@
use nom::{ use nom::{bytes::complete::tag, combinator::opt, sequence::tuple, IResult};
IResult,
bytes::complete::tag,
sequence::tuple,
combinator::opt,
};
use crate::text::whitespace::obs_crlf; use crate::text::whitespace::obs_crlf;
#[derive(Debug, PartialEq)] #[derive(Debug, PartialEq)]
pub enum Delimiter { pub enum Delimiter {
Next, Next,
Last Last,
} }
pub fn boundary<'a>(boundary: &[u8]) -> impl Fn(&'a [u8]) -> IResult<&'a [u8], Delimiter> + '_ { pub fn boundary<'a>(boundary: &[u8]) -> impl Fn(&'a [u8]) -> IResult<&'a [u8], Delimiter> + '_ {
move |input: &[u8]| { move |input: &[u8]| {
let (rest, (_, _, _, last, _)) = tuple((opt(obs_crlf), tag(b"--"), tag(boundary), opt(tag(b"--")), opt(obs_crlf)))(input)?; let (rest, (_, _, _, last, _)) = tuple((
opt(obs_crlf),
tag(b"--"),
tag(boundary),
opt(tag(b"--")),
opt(obs_crlf),
))(input)?;
match last { match last {
Some(_) => Ok((rest, Delimiter::Last)), Some(_) => Ok((rest, Delimiter::Last)),
None => Ok((rest, Delimiter::Next)), None => Ok((rest, Delimiter::Next)),

View file

@ -1,19 +1,19 @@
use encoding_rs::Encoding; use encoding_rs::Encoding;
use base64::{engine::general_purpose, Engine as _};
use nom::{ use nom::{
IResult,
branch::alt, branch::alt,
bytes::complete::{tag, take, take_while1, take_while}, bytes::complete::{tag, take, take_while, take_while1},
character::complete::{one_of}, character::complete::one_of,
character::is_alphanumeric, character::is_alphanumeric,
combinator::map, combinator::map,
sequence::{preceded, terminated, tuple},
multi::many0, multi::many0,
sequence::{preceded, terminated, tuple},
IResult,
}; };
use base64::{Engine as _, engine::general_purpose};
use crate::text::words;
use crate::text::ascii; use crate::text::ascii;
use crate::text::words;
pub fn encoded_word(input: &[u8]) -> IResult<&[u8], EncodedWord> { pub fn encoded_word(input: &[u8]) -> IResult<&[u8], EncodedWord> {
alt((encoded_word_quoted, encoded_word_base64))(input) alt((encoded_word_quoted, encoded_word_base64))(input)
@ -21,35 +21,49 @@ pub fn encoded_word(input: &[u8]) -> IResult<&[u8], EncodedWord> {
pub fn encoded_word_quoted(input: &[u8]) -> IResult<&[u8], EncodedWord> { pub fn encoded_word_quoted(input: &[u8]) -> IResult<&[u8], EncodedWord> {
let (rest, (_, charset, _, _, _, txt, _)) = tuple(( let (rest, (_, charset, _, _, _, txt, _)) = tuple((
tag("=?"), words::mime_atom, tag("=?"),
tag("?"), one_of("Qq"), words::mime_atom,
tag("?"), ptext, tag("?"),
tag("?=")))(input)?; one_of("Qq"),
tag("?"),
ptext,
tag("?="),
))(input)?;
let renc = Encoding::for_label(charset).unwrap_or(encoding_rs::WINDOWS_1252); let renc = Encoding::for_label(charset).unwrap_or(encoding_rs::WINDOWS_1252);
let parsed = EncodedWord::Quoted(QuotedWord { enc: renc, chunks: txt }); let parsed = EncodedWord::Quoted(QuotedWord {
enc: renc,
chunks: txt,
});
Ok((rest, parsed)) Ok((rest, parsed))
} }
pub fn encoded_word_base64(input: &[u8]) -> IResult<&[u8], EncodedWord> { pub fn encoded_word_base64(input: &[u8]) -> IResult<&[u8], EncodedWord> {
let (rest, (_, charset, _, _, _, txt, _)) = tuple(( let (rest, (_, charset, _, _, _, txt, _)) = tuple((
tag("=?"), words::mime_atom, tag("=?"),
tag("?"), one_of("Bb"), words::mime_atom,
tag("?"), btext, tag("?"),
tag("?=")))(input)?; one_of("Bb"),
tag("?"),
btext,
tag("?="),
))(input)?;
let renc = Encoding::for_label(charset).unwrap_or(encoding_rs::WINDOWS_1252); let renc = Encoding::for_label(charset).unwrap_or(encoding_rs::WINDOWS_1252);
let parsed = EncodedWord::Base64(Base64Word { enc: renc, content: txt }); let parsed = EncodedWord::Base64(Base64Word {
enc: renc,
content: txt,
});
Ok((rest, parsed)) Ok((rest, parsed))
} }
#[derive(PartialEq,Debug, Clone)] #[derive(PartialEq, Debug, Clone)]
pub enum EncodedWord<'a> { pub enum EncodedWord<'a> {
Quoted(QuotedWord<'a>), Quoted(QuotedWord<'a>),
Base64(Base64Word<'a>), Base64(Base64Word<'a>),
} }
impl<'a> EncodedWord<'a> { impl<'a> EncodedWord<'a> {
pub fn to_string(&self) -> String { pub fn to_string(&self) -> String {
match self { match self {
EncodedWord::Quoted(v) => v.to_string(), EncodedWord::Quoted(v) => v.to_string(),
EncodedWord::Base64(v) => v.to_string(), EncodedWord::Base64(v) => v.to_string(),
@ -57,7 +71,7 @@ impl<'a> EncodedWord<'a> {
} }
} }
#[derive(PartialEq,Debug,Clone)] #[derive(PartialEq, Debug, Clone)]
pub struct Base64Word<'a> { pub struct Base64Word<'a> {
pub enc: &'static Encoding, pub enc: &'static Encoding,
pub content: &'a [u8], pub content: &'a [u8],
@ -72,7 +86,7 @@ impl<'a> Base64Word<'a> {
} }
} }
#[derive(PartialEq,Debug,Clone)] #[derive(PartialEq, Debug, Clone)]
pub struct QuotedWord<'a> { pub struct QuotedWord<'a> {
pub enc: &'static Encoding, pub enc: &'static Encoding,
pub chunks: Vec<QuotedChunk<'a>>, pub chunks: Vec<QuotedChunk<'a>>,
@ -80,27 +94,25 @@ pub struct QuotedWord<'a> {
impl<'a> QuotedWord<'a> { impl<'a> QuotedWord<'a> {
pub fn to_string(&self) -> String { pub fn to_string(&self) -> String {
self.chunks.iter().fold( self.chunks.iter().fold(String::new(), |mut acc, c| {
String::new(), match c {
|mut acc, c| { QuotedChunk::Safe(v) => {
match c { let (content, _) = encoding_rs::UTF_8.decode_without_bom_handling(v);
QuotedChunk::Safe(v) => { acc.push_str(content.as_ref());
let (content, _) = encoding_rs::UTF_8.decode_without_bom_handling(v); }
acc.push_str(content.as_ref()); QuotedChunk::Space => acc.push(' '),
} QuotedChunk::Encoded(v) => {
QuotedChunk::Space => acc.push(' '), let w = &[*v];
QuotedChunk::Encoded(v) => { let (d, _) = self.enc.decode_without_bom_handling(w);
let w = &[*v]; acc.push_str(d.as_ref());
let (d, _) = self.enc.decode_without_bom_handling(w); }
acc.push_str(d.as_ref()); };
}, acc
}; })
acc
})
} }
} }
#[derive(PartialEq,Debug,Clone)] #[derive(PartialEq, Debug, Clone)]
pub enum QuotedChunk<'a> { pub enum QuotedChunk<'a> {
Safe(&'a [u8]), Safe(&'a [u8]),
Encoded(u8), Encoded(u8),
@ -112,12 +124,10 @@ pub fn ptext(input: &[u8]) -> IResult<&[u8], Vec<QuotedChunk>> {
many0(alt((safe_char2, encoded_space, hex_octet)))(input) many0(alt((safe_char2, encoded_space, hex_octet)))(input)
} }
fn safe_char2(input: &[u8]) -> IResult<&[u8], QuotedChunk> { fn safe_char2(input: &[u8]) -> IResult<&[u8], QuotedChunk> {
map(take_while1(is_safe_char2), |v| QuotedChunk::Safe(v))(input) map(take_while1(is_safe_char2), |v| QuotedChunk::Safe(v))(input)
} }
/// RFC2047 section 4.2 /// RFC2047 section 4.2
/// 8-bit values which correspond to printable ASCII characters other /// 8-bit values which correspond to printable ASCII characters other
/// than "=", "?", and "_" (underscore), MAY be represented as those /// than "=", "?", and "_" (underscore), MAY be represented as those
@ -167,28 +177,33 @@ mod tests {
fn test_ptext() { fn test_ptext() {
assert_eq!( assert_eq!(
ptext(b"Accus=E9_de_r=E9ception_(affich=E9)"), ptext(b"Accus=E9_de_r=E9ception_(affich=E9)"),
Ok((&b""[..], vec![ Ok((
QuotedChunk::Safe(&b"Accus"[..]), &b""[..],
QuotedChunk::Encoded(0xe9), vec![
QuotedChunk::Space, QuotedChunk::Safe(&b"Accus"[..]),
QuotedChunk::Safe(&b"de"[..]), QuotedChunk::Encoded(0xe9),
QuotedChunk::Space, QuotedChunk::Space,
QuotedChunk::Safe(&b"r"[..]), QuotedChunk::Safe(&b"de"[..]),
QuotedChunk::Encoded(0xe9), QuotedChunk::Space,
QuotedChunk::Safe(&b"ception"[..]), QuotedChunk::Safe(&b"r"[..]),
QuotedChunk::Space, QuotedChunk::Encoded(0xe9),
QuotedChunk::Safe(&b"(affich"[..]), QuotedChunk::Safe(&b"ception"[..]),
QuotedChunk::Encoded(0xe9), QuotedChunk::Space,
QuotedChunk::Safe(&b")"[..]), QuotedChunk::Safe(&b"(affich"[..]),
])) QuotedChunk::Encoded(0xe9),
QuotedChunk::Safe(&b")"[..]),
]
))
); );
} }
#[test] #[test]
fn test_decode_word() { fn test_decode_word() {
assert_eq!( assert_eq!(
encoded_word(b"=?iso8859-1?Q?Accus=E9_de_r=E9ception_(affich=E9)?=").unwrap().1.to_string(), encoded_word(b"=?iso8859-1?Q?Accus=E9_de_r=E9ception_(affich=E9)?=")
.unwrap()
.1
.to_string(),
"Accusé de réception (affiché)".to_string(), "Accusé de réception (affiché)".to_string(),
); );
} }
@ -197,7 +212,10 @@ mod tests {
#[test] #[test]
fn test_decode_word_b64() { fn test_decode_word_b64() {
assert_eq!( assert_eq!(
encoded_word(b"=?ISO-8859-1?B?SWYgeW91IGNhbiByZWFkIHRoaXMgeW8=?=").unwrap().1.to_string(), encoded_word(b"=?ISO-8859-1?B?SWYgeW91IGNhbiByZWFkIHRoaXMgeW8=?=")
.unwrap()
.1
.to_string(),
"If you can read this yo".to_string(), "If you can read this yo".to_string(),
); );
} }

View file

@ -4,16 +4,16 @@ use nom::{
character::complete::space0, character::complete::space0,
combinator::{map, opt}, combinator::{map, opt},
multi::{many0, many1, separated_list1}, multi::{many0, many1, separated_list1},
sequence::{preceded}, sequence::preceded,
IResult, IResult,
}; };
use crate::text::{ use crate::text::{
quoted::{QuotedString, quoted_string},
whitespace::{fws, is_obs_no_ws_ctl},
words::{atom, mime_atom, is_vchar},
encoding::{self, encoded_word},
ascii, ascii,
encoding::{self, encoded_word},
quoted::{quoted_string, QuotedString},
whitespace::{fws, is_obs_no_ws_ctl},
words::{atom, is_vchar, mime_atom},
}; };
#[derive(Debug, PartialEq, Default)] #[derive(Debug, PartialEq, Default)]
@ -36,13 +36,16 @@ impl<'a> MIMEWord<'a> {
pub fn to_string(&self) -> String { pub fn to_string(&self) -> String {
match self { match self {
Self::Quoted(v) => v.to_string(), Self::Quoted(v) => v.to_string(),
Self::Atom(v) => encoding_rs::UTF_8.decode_without_bom_handling(v).0.to_string(), Self::Atom(v) => encoding_rs::UTF_8
.decode_without_bom_handling(v)
.0
.to_string(),
} }
} }
} }
pub fn mime_word(input: &[u8]) -> IResult<&[u8], MIMEWord> { pub fn mime_word(input: &[u8]) -> IResult<&[u8], MIMEWord> {
alt(( alt((
map(quoted_string, MIMEWord::Quoted), map(quoted_string, MIMEWord::Quoted),
map(mime_atom, MIMEWord::Atom), map(mime_atom, MIMEWord::Atom),
))(input) ))(input)
} }
@ -59,7 +62,10 @@ impl<'a> Word<'a> {
match self { match self {
Word::Quoted(v) => v.to_string(), Word::Quoted(v) => v.to_string(),
Word::Encoded(v) => v.to_string(), Word::Encoded(v) => v.to_string(),
Word::Atom(v) => encoding_rs::UTF_8.decode_without_bom_handling(v).0.to_string(), Word::Atom(v) => encoding_rs::UTF_8
.decode_without_bom_handling(v)
.0
.to_string(),
} }
} }
} }
@ -71,9 +77,9 @@ impl<'a> Word<'a> {
/// ``` /// ```
pub fn word(input: &[u8]) -> IResult<&[u8], Word> { pub fn word(input: &[u8]) -> IResult<&[u8], Word> {
alt(( alt((
map(quoted_string, |v| Word::Quoted(v)), map(quoted_string, |v| Word::Quoted(v)),
map(encoded_word, |v| Word::Encoded(v)), map(encoded_word, |v| Word::Encoded(v)),
map(atom, |v| Word::Atom(v)) map(atom, |v| Word::Atom(v)),
))(input) ))(input)
} }
@ -82,7 +88,11 @@ pub struct Phrase<'a>(pub Vec<Word<'a>>);
impl<'a> Phrase<'a> { impl<'a> Phrase<'a> {
pub fn to_string(&self) -> String { pub fn to_string(&self) -> String {
self.0.iter().map(|v| v.to_string()).collect::<Vec<String>>().join(" ") self.0
.iter()
.map(|v| v.to_string())
.collect::<Vec<String>>()
.join(" ")
} }
} }
@ -117,7 +127,10 @@ impl<'a> UnstrToken<'a> {
match self { match self {
UnstrToken::Init => "".into(), UnstrToken::Init => "".into(),
UnstrToken::Encoded(e) => e.to_string(), UnstrToken::Encoded(e) => e.to_string(),
UnstrToken::Plain(e) => encoding_rs::UTF_8.decode_without_bom_handling(e).0.into_owned(), UnstrToken::Plain(e) => encoding_rs::UTF_8
.decode_without_bom_handling(e)
.0
.into_owned(),
} }
} }
} }
@ -127,21 +140,26 @@ pub struct Unstructured<'a>(pub Vec<UnstrToken<'a>>);
impl<'a> Unstructured<'a> { impl<'a> Unstructured<'a> {
pub fn to_string(&self) -> String { pub fn to_string(&self) -> String {
self.0.iter().fold( self.0
(&UnstrToken::Init, String::new()), .iter()
|(prev_token, mut result), current_token| { .fold(
match (prev_token, current_token) { (&UnstrToken::Init, String::new()),
(UnstrToken::Init, v) => result.push_str(v.to_string().as_ref()), |(prev_token, mut result), current_token| {
(UnstrToken::Encoded(_), UnstrToken::Encoded(v)) => result.push_str(v.to_string().as_ref()), match (prev_token, current_token) {
(_, v) => { (UnstrToken::Init, v) => result.push_str(v.to_string().as_ref()),
result.push(' '); (UnstrToken::Encoded(_), UnstrToken::Encoded(v)) => {
result.push_str(v.to_string().as_ref()) result.push_str(v.to_string().as_ref())
}, }
}; (_, v) => {
result.push(' ');
result.push_str(v.to_string().as_ref())
}
};
(current_token, result) (current_token, result)
} },
).1 )
.1
} }
} }
@ -151,23 +169,25 @@ impl<'a> Unstructured<'a> {
/// unstructured = (*([FWS] VCHAR_SEQ) *WSP) / obs-unstruct /// unstructured = (*([FWS] VCHAR_SEQ) *WSP) / obs-unstruct
/// ``` /// ```
pub fn unstructured(input: &[u8]) -> IResult<&[u8], Unstructured> { pub fn unstructured(input: &[u8]) -> IResult<&[u8], Unstructured> {
let (input, r) = many0(preceded(opt(fws), alt(( let (input, r) = many0(preceded(
map(encoded_word, |v| UnstrToken::Encoded(v)), opt(fws),
map(take_while1(is_unstructured), |v| UnstrToken::Plain(v)), alt((
))))(input)?; map(encoded_word, |v| UnstrToken::Encoded(v)),
map(take_while1(is_unstructured), |v| UnstrToken::Plain(v)),
)),
))(input)?;
let (input, _) = space0(input)?; let (input, _) = space0(input)?;
Ok((input, Unstructured(r))) Ok((input, Unstructured(r)))
} }
#[cfg(test)] #[cfg(test)]
mod tests { mod tests {
use super::*; use super::*;
#[test] #[test]
fn test_phrase() { fn test_phrase() {
assert_eq!( assert_eq!(
phrase(b"hello world").unwrap().1.to_string(), phrase(b"hello world").unwrap().1.to_string(),
"hello world".to_string(), "hello world".to_string(),
); );
assert_eq!( assert_eq!(

View file

@ -1,7 +1,7 @@
pub mod ascii; pub mod ascii;
pub mod boundary;
pub mod encoding; pub mod encoding;
pub mod misc_token; pub mod misc_token;
pub mod quoted; pub mod quoted;
pub mod whitespace; pub mod whitespace;
pub mod words; pub mod words;
pub mod boundary;

View file

@ -1,14 +1,14 @@
use nom::{ use nom::{
branch::alt, branch::alt,
bytes::complete::{take_while1, take, tag}, bytes::complete::{tag, take, take_while1},
combinator::{opt}, combinator::opt,
multi::many0, multi::many0,
sequence::{pair, preceded}, sequence::{pair, preceded},
IResult, IResult,
}; };
use crate::text::whitespace::{cfws, fws, is_obs_no_ws_ctl};
use crate::text::ascii; use crate::text::ascii;
use crate::text::whitespace::{cfws, fws, is_obs_no_ws_ctl};
#[derive(Debug, PartialEq, Default)] #[derive(Debug, PartialEq, Default)]
pub struct QuotedString<'a>(pub Vec<&'a [u8]>); pub struct QuotedString<'a>(pub Vec<&'a [u8]>);
@ -22,14 +22,13 @@ impl<'a> QuotedString<'a> {
let enc = encoding_rs::UTF_8; let enc = encoding_rs::UTF_8;
let size = self.0.iter().fold(0, |acc, v| acc + v.len()); let size = self.0.iter().fold(0, |acc, v| acc + v.len());
self.0.iter().fold( self.0
String::with_capacity(size), .iter()
|mut acc, v| { .fold(String::with_capacity(size), |mut acc, v| {
let (content, _) = enc.decode_without_bom_handling(v); let (content, _) = enc.decode_without_bom_handling(v);
acc.push_str(content.as_ref()); acc.push_str(content.as_ref());
acc acc
}, })
)
} }
} }
@ -43,8 +42,6 @@ pub fn quoted_pair(input: &[u8]) -> IResult<&[u8], &[u8]> {
preceded(tag(&[ascii::BACKSLASH]), take(1usize))(input) preceded(tag(&[ascii::BACKSLASH]), take(1usize))(input)
} }
/// Allowed characters in quote /// Allowed characters in quote
/// ///
/// ```abnf /// ```abnf
@ -54,7 +51,9 @@ pub fn quoted_pair(input: &[u8]) -> IResult<&[u8], &[u8]> {
/// obs-qtext /// obs-qtext
/// ``` /// ```
fn is_restr_qtext(c: u8) -> bool { fn is_restr_qtext(c: u8) -> bool {
c == ascii::EXCLAMATION || (c >= ascii::NUM && c <= ascii::LEFT_BRACKET) || (c >= ascii::RIGHT_BRACKET && c <= ascii::TILDE) c == ascii::EXCLAMATION
|| (c >= ascii::NUM && c <= ascii::LEFT_BRACKET)
|| (c >= ascii::RIGHT_BRACKET && c <= ascii::TILDE)
} }
fn is_qtext(c: u8) -> bool { fn is_qtext(c: u8) -> bool {
@ -116,7 +115,10 @@ mod tests {
assert_eq!( assert_eq!(
quoted_string(b"\"hello\r\n world\""), quoted_string(b"\"hello\r\n world\""),
Ok((&b""[..], QuotedString(vec![b"hello", &[ascii::SP], b"world"]))), Ok((
&b""[..],
QuotedString(vec![b"hello", &[ascii::SP], b"world"])
)),
); );
} }

View file

@ -1,3 +1,6 @@
use crate::text::ascii;
use crate::text::encoding::encoded_word;
use crate::text::quoted::quoted_pair;
use nom::{ use nom::{
branch::alt, branch::alt,
bytes::complete::{is_not, tag, take_while1}, bytes::complete::{is_not, tag, take_while1},
@ -7,11 +10,8 @@ use nom::{
sequence::{pair, tuple}, sequence::{pair, tuple},
IResult, IResult,
}; };
use crate::text::encoding::encoded_word;
use crate::text::quoted::quoted_pair;
use crate::text::ascii;
/// Whitespace (space, new line, tab) content and /// Whitespace (space, new line, tab) content and
/// delimited content (eg. comment, line, sections, etc.) /// delimited content (eg. comment, line, sections, etc.)
/// Obsolete/Compatible CRLF /// Obsolete/Compatible CRLF
@ -37,10 +37,7 @@ pub fn line(input: &[u8]) -> IResult<&[u8], (&[u8], &[u8])> {
pub fn foldable_line(input: &[u8]) -> IResult<&[u8], &[u8]> { pub fn foldable_line(input: &[u8]) -> IResult<&[u8], &[u8]> {
recognize(tuple(( recognize(tuple((
is_not(ascii::CRLF), is_not(ascii::CRLF),
many0(pair( many0(pair(many1(pair(obs_crlf, space1)), is_not(ascii::CRLF))),
many1(pair(obs_crlf, space1)),
is_not(ascii::CRLF),
)),
obs_crlf, obs_crlf,
)))(input) )))(input)
} }
@ -101,7 +98,12 @@ pub fn comment(input: &[u8]) -> IResult<&[u8], ()> {
} }
pub fn ccontent(input: &[u8]) -> IResult<&[u8], &[u8]> { pub fn ccontent(input: &[u8]) -> IResult<&[u8], &[u8]> {
alt((ctext, recognize(quoted_pair), recognize(encoded_word), recognize(comment)))(input) alt((
ctext,
recognize(quoted_pair),
recognize(encoded_word),
recognize(comment),
))(input)
} }
pub fn ctext(input: &[u8]) -> IResult<&[u8], &[u8]> { pub fn ctext(input: &[u8]) -> IResult<&[u8], &[u8]> {
@ -137,7 +139,7 @@ pub fn is_restr_ctext(c: u8) -> bool {
/// ``` /// ```
pub fn is_obs_no_ws_ctl(c: u8) -> bool { pub fn is_obs_no_ws_ctl(c: u8) -> bool {
(c >= ascii::SOH && c <= ascii::BS) (c >= ascii::SOH && c <= ascii::BS)
|| c == ascii::VT || c == ascii::VT
|| c == ascii::FF || c == ascii::FF
|| (c >= ascii::SO && c <= ascii::US) || (c >= ascii::SO && c <= ascii::US)
|| c == ascii::DEL || c == ascii::DEL
@ -183,7 +185,7 @@ mod tests {
#[test] #[test]
fn test_cfws_encoded_word() { fn test_cfws_encoded_word() {
assert_eq!( assert_eq!(
cfws(b"(=?US-ASCII?Q?Keith_Moore?=)"), cfws(b"(=?US-ASCII?Q?Keith_Moore?=)"),
Ok((&b""[..], &b"(=?US-ASCII?Q?Keith_Moore?=)"[..])), Ok((&b""[..], &b"(=?US-ASCII?Q?Keith_Moore?=)"[..])),
); );

View file

@ -1,5 +1,5 @@
use crate::text::whitespace::cfws;
use crate::text::ascii; use crate::text::ascii;
use crate::text::whitespace::cfws;
use nom::{ use nom::{
bytes::complete::{tag, take_while1}, bytes::complete::{tag, take_while1},
character::is_alphanumeric, character::is_alphanumeric,
@ -17,24 +17,24 @@ pub fn is_vchar(c: u8) -> bool {
/// ///
/// forbidden: ()<>@,;:\"/[]?= /// forbidden: ()<>@,;:\"/[]?=
fn is_mime_atom_text(c: u8) -> bool { fn is_mime_atom_text(c: u8) -> bool {
is_alphanumeric(c) is_alphanumeric(c)
|| c == ascii::EXCLAMATION || c == ascii::EXCLAMATION
|| c == ascii::NUM || c == ascii::NUM
|| c == ascii::DOLLAR || c == ascii::DOLLAR
|| c == ascii::PERCENT || c == ascii::PERCENT
|| c == ascii::AMPERSAND || c == ascii::AMPERSAND
|| c == ascii::SQUOTE || c == ascii::SQUOTE
|| c == ascii::ASTERISK || c == ascii::ASTERISK
|| c == ascii::PLUS || c == ascii::PLUS
|| c == ascii::MINUS || c == ascii::MINUS
|| c == ascii::PERIOD || c == ascii::PERIOD
|| c == ascii::CARRET || c == ascii::CARRET
|| c == ascii::UNDERSCORE || c == ascii::UNDERSCORE
|| c == ascii::GRAVE || c == ascii::GRAVE
|| c == ascii::LEFT_CURLY || c == ascii::LEFT_CURLY
|| c == ascii::PIPE || c == ascii::PIPE
|| c == ascii::RIGHT_CURLY || c == ascii::RIGHT_CURLY
|| c == ascii::TILDE || c == ascii::TILDE
} }
/// MIME Token /// MIME Token
@ -49,25 +49,25 @@ pub fn mime_atom(input: &[u8]) -> IResult<&[u8], &[u8]> {
/// authorized: !#$%&'*+-/=?^_`{|}~ /// authorized: !#$%&'*+-/=?^_`{|}~
fn is_atext(c: u8) -> bool { fn is_atext(c: u8) -> bool {
is_alphanumeric(c) is_alphanumeric(c)
|| c == ascii::EXCLAMATION || c == ascii::EXCLAMATION
|| c == ascii::NUM || c == ascii::NUM
|| c == ascii::DOLLAR || c == ascii::DOLLAR
|| c == ascii::PERCENT || c == ascii::PERCENT
|| c == ascii::AMPERSAND || c == ascii::AMPERSAND
|| c == ascii::SQUOTE || c == ascii::SQUOTE
|| c == ascii::ASTERISK || c == ascii::ASTERISK
|| c == ascii::PLUS || c == ascii::PLUS
|| c == ascii::MINUS || c == ascii::MINUS
|| c == ascii::SLASH || c == ascii::SLASH
|| c == ascii::EQ || c == ascii::EQ
|| c == ascii::QUESTION || c == ascii::QUESTION
|| c == ascii::CARRET || c == ascii::CARRET
|| c == ascii::UNDERSCORE || c == ascii::UNDERSCORE
|| c == ascii::GRAVE || c == ascii::GRAVE
|| c == ascii::LEFT_CURLY || c == ascii::LEFT_CURLY
|| c == ascii::PIPE || c == ascii::PIPE
|| c == ascii::RIGHT_CURLY || c == ascii::RIGHT_CURLY
|| c == ascii::TILDE || c == ascii::TILDE
} }
/// Atom /// Atom