melib/email/parser: Add rfc5322 compliant parser for MessageID
parent
5d107db8b8
commit
d14f26569e
|
@ -1253,7 +1253,7 @@ pub fn envelope(input: &[u8]) -> IResult<&[u8], Envelope> {
|
|||
}
|
||||
if let Some(in_reply_to) = in_reply_to {
|
||||
env.set_in_reply_to(&in_reply_to);
|
||||
env.push_references(&in_reply_to);
|
||||
env.push_references(env.in_reply_to().unwrap().clone());
|
||||
}
|
||||
|
||||
if let Some(message_id) = message_id {
|
||||
|
|
|
@ -246,10 +246,10 @@ impl std::convert::From<EmailObject> for crate::Envelope {
|
|||
}
|
||||
if let Some(ref in_reply_to) = t.in_reply_to {
|
||||
env.set_in_reply_to(in_reply_to[0].as_bytes());
|
||||
env.push_references(in_reply_to[0].as_bytes());
|
||||
env.push_references(env.in_reply_to().unwrap().clone());
|
||||
}
|
||||
if let Some(v) = t.headers.get("References") {
|
||||
let parse_result = crate::email::parser::address::references(v.as_bytes());
|
||||
let parse_result = crate::email::parser::address::msg_id_list(v.as_bytes());
|
||||
if let Ok((_, v)) = parse_result {
|
||||
for v in v {
|
||||
env.push_references(v);
|
||||
|
|
|
@ -144,7 +144,7 @@ pub fn over_article(input: &str) -> IResult<&str, (UID, Envelope)> {
|
|||
if let Some(references) = references {
|
||||
{
|
||||
if let Ok((_, r)) =
|
||||
crate::email::parser::address::references(references.as_bytes())
|
||||
crate::email::parser::address::msg_id_list(references.as_bytes())
|
||||
{
|
||||
for v in r {
|
||||
env.push_references(v);
|
||||
|
|
|
@ -227,8 +227,6 @@ impl Envelope {
|
|||
return Err(MeliError::new(error_msg));
|
||||
}
|
||||
};
|
||||
let mut in_reply_to = None;
|
||||
|
||||
for (name, value) in headers {
|
||||
let name: HeaderName = name.try_into()?;
|
||||
if name == "to" {
|
||||
|
@ -260,7 +258,7 @@ impl Envelope {
|
|||
self.set_message_id(value);
|
||||
} else if name == "references" {
|
||||
{
|
||||
let parse_result = parser::address::references(value);
|
||||
let parse_result = parser::address::msg_id_list(value);
|
||||
if let Ok((_, value)) = parse_result {
|
||||
for v in value {
|
||||
self.push_references(v);
|
||||
|
@ -270,7 +268,6 @@ impl Envelope {
|
|||
self.set_references(value);
|
||||
} else if name == "in-reply-to" {
|
||||
self.set_in_reply_to(value);
|
||||
in_reply_to = Some(value);
|
||||
} else if name == "date" {
|
||||
let parse_result = parser::encodings::phrase(value, false);
|
||||
if let Ok((_, value)) = parse_result {
|
||||
|
@ -318,7 +315,7 @@ impl Envelope {
|
|||
*
|
||||
* if self.message_id.is_none() ...
|
||||
*/
|
||||
if let Some(ref mut x) = in_reply_to {
|
||||
if let Some(x) = self.in_reply_to.clone() {
|
||||
self.push_references(x);
|
||||
}
|
||||
if let Ok(d) = parser::generic::date(&self.date.as_bytes()) {
|
||||
|
@ -520,15 +517,16 @@ impl Envelope {
|
|||
self.to = new_val;
|
||||
}
|
||||
pub fn set_in_reply_to(&mut self, new_val: &[u8]) {
|
||||
// FIXME msg_id_list
|
||||
let new_val = new_val.trim();
|
||||
let slice = match parser::address::message_id(new_val) {
|
||||
let val = match parser::address::msg_id(new_val) {
|
||||
Ok(v) => v.1,
|
||||
Err(_) => {
|
||||
self.in_reply_to = None;
|
||||
self.in_reply_to = Some(MessageID::new(new_val, new_val));
|
||||
return;
|
||||
}
|
||||
};
|
||||
self.in_reply_to = Some(MessageID::new(new_val, slice));
|
||||
self.in_reply_to = Some(val);
|
||||
}
|
||||
pub fn set_subject(&mut self, new_val: Vec<u8>) {
|
||||
let mut new_val = String::from_utf8(new_val)
|
||||
|
@ -546,25 +544,16 @@ impl Envelope {
|
|||
}
|
||||
pub fn set_message_id(&mut self, new_val: &[u8]) {
|
||||
let new_val = new_val.trim();
|
||||
match parser::address::message_id(new_val) {
|
||||
Ok((_, slice)) => {
|
||||
self.message_id = MessageID::new(new_val, slice);
|
||||
match parser::address::msg_id(new_val) {
|
||||
Ok((_, val)) => {
|
||||
self.message_id = val;
|
||||
}
|
||||
Err(_) => {
|
||||
self.message_id = MessageID::new(new_val, new_val);
|
||||
}
|
||||
}
|
||||
}
|
||||
pub fn push_references(&mut self, new_val: &[u8]) {
|
||||
let new_val = new_val.trim();
|
||||
let slice = match parser::address::message_id(new_val) {
|
||||
Ok(v) => v.1,
|
||||
Err(e) => {
|
||||
debug!(e);
|
||||
return;
|
||||
}
|
||||
};
|
||||
let new_ref = MessageID::new(new_val, slice);
|
||||
pub fn push_references(&mut self, new_ref: MessageID) {
|
||||
match self.references {
|
||||
Some(ref mut s) => {
|
||||
if s.refs.contains(&new_ref) {
|
||||
|
|
|
@ -318,15 +318,15 @@ impl StrBuild for MessageID {
|
|||
|
||||
#[test]
|
||||
fn test_strbuilder() {
|
||||
let m_id = b"<20170825132332.6734-1@el13635@mail.ntua.gr>";
|
||||
let (_, slice) = parser::address::message_id(m_id).unwrap();
|
||||
let m_id = b"<20170825132332.6734-1@mail.ntua.gr>";
|
||||
let (_, val) = parser::address::msg_id(m_id).unwrap();
|
||||
assert_eq!(
|
||||
MessageID::new(m_id, slice),
|
||||
val,
|
||||
MessageID(
|
||||
m_id.to_vec(),
|
||||
StrBuilder {
|
||||
offset: 1,
|
||||
length: 43,
|
||||
length: 35,
|
||||
}
|
||||
)
|
||||
);
|
||||
|
|
|
@ -27,7 +27,7 @@ use nom::{
|
|||
combinator::peek,
|
||||
combinator::{map, opt},
|
||||
error::{context, ErrorKind},
|
||||
multi::{many0, many1, separated_list, separated_nonempty_list},
|
||||
multi::{many0, many1, separated_nonempty_list},
|
||||
number::complete::le_u8,
|
||||
sequence::{delimited, pair, preceded, separated_pair, terminated},
|
||||
};
|
||||
|
@ -618,9 +618,225 @@ pub mod generic {
|
|||
let s = "this is\n\ta folded name";
|
||||
assert_eq!(
|
||||
&unstructured(s.as_bytes()).unwrap(),
|
||||
"this is a\tfolded name",
|
||||
"this is\ta folded name",
|
||||
);
|
||||
}
|
||||
|
||||
///`atom = [CFWS] 1*atext [CFWS]`
|
||||
pub fn atom(input: &[u8]) -> IResult<&[u8], Cow<'_, [u8]>> {
|
||||
let (input, opt_space) = opt(cfws)(input)?;
|
||||
let mut i = 0;
|
||||
while i < input.len() {
|
||||
//&& !input[i].is_ascii_whitespace() {
|
||||
match input[i] {
|
||||
b'(' | b')' | b'<' | b'>' | b'[' | b']' | b':' | b';' | b'@' | b'\\' | b','
|
||||
| b'.' | b'\r' | b'\n' | b'"' => break,
|
||||
_ => {}
|
||||
}
|
||||
i += 1;
|
||||
}
|
||||
if i == 0 {
|
||||
return Err(nom::Err::Error(
|
||||
(input, "atom(): starts with whitespace or empty").into(),
|
||||
));
|
||||
}
|
||||
while i + 1 > 0 {
|
||||
if input[i - 1] == b' ' || input[i - 1] == b'\t' {
|
||||
i -= 1;
|
||||
} else {
|
||||
break;
|
||||
}
|
||||
}
|
||||
let (rest, opt_space2) = opt(cfws)(&input[i..])?;
|
||||
let ret = if opt_space.is_some() || opt_space2.is_some() {
|
||||
let mut ret = Vec::with_capacity(i + 2);
|
||||
if let Some(opt_space) = opt_space {
|
||||
ret.extend_from_slice(&opt_space);
|
||||
}
|
||||
ret.extend_from_slice(&input[..i]);
|
||||
if let Some(opt_space) = opt_space2 {
|
||||
ret.extend_from_slice(&opt_space);
|
||||
}
|
||||
Cow::Owned(ret)
|
||||
} else {
|
||||
Cow::Borrowed(&input[..i])
|
||||
};
|
||||
Ok((rest, ret))
|
||||
}
|
||||
|
||||
///`quoted-string = [CFWS] DQUOTE *([FWS] qcontent) [FWS] DQUOTE [CFWS]`
|
||||
pub fn quoted_string(input: &[u8]) -> IResult<&[u8], Cow<'_, [u8]>> {
|
||||
let (input, opt_space) = opt(cfws)(input)?;
|
||||
if !input.starts_with(b"\"") {
|
||||
return Err(nom::Err::Error(
|
||||
(input, "quoted_string(): doesn't start with DQUOTE").into(),
|
||||
));
|
||||
}
|
||||
let input = &input[1..];
|
||||
let mut i = 0;
|
||||
while i < input.len() && input[i] != b'"' {
|
||||
if opt_space.is_some() || (input[i..].starts_with(b"\\") && i + 1 < input.len()) {
|
||||
let mut ret = if let Some(opt_space) = opt_space {
|
||||
let mut r = Vec::with_capacity(2 * i);
|
||||
r.extend_from_slice(&opt_space);
|
||||
r
|
||||
} else {
|
||||
Vec::with_capacity(2 * i)
|
||||
};
|
||||
ret.extend_from_slice(&input[..i]);
|
||||
i += 1;
|
||||
ret.push(input[i]);
|
||||
i += 1;
|
||||
while i < input.len() && input[i] != b'"' {
|
||||
if input[i..].starts_with(b"\\") && i + 1 < input.len() {
|
||||
i += 1;
|
||||
}
|
||||
ret.push(input[i]);
|
||||
i += 1;
|
||||
}
|
||||
if i < input.len() {
|
||||
// skip DQUOTE
|
||||
i += 1;
|
||||
} else {
|
||||
return Err(nom::Err::Error(
|
||||
(input, "quoted_string(): unclosed DQUOTE").into(),
|
||||
));
|
||||
}
|
||||
|
||||
let (rest, opt_sp) = opt(cfws)(&input[i..])?;
|
||||
if let Some(opt_sp) = opt_sp {
|
||||
ret.extend_from_slice(&opt_sp);
|
||||
}
|
||||
let ret = Cow::Owned(ret);
|
||||
return Ok((rest, ret));
|
||||
}
|
||||
i += 1;
|
||||
}
|
||||
let ret = Cow::Borrowed(&input[..i]);
|
||||
if i < input.len() {
|
||||
// skip DQUOTE
|
||||
i += 1;
|
||||
} else {
|
||||
return Err(nom::Err::Error(
|
||||
(input, "quoted_string(): unclosed DQUOTE").into(),
|
||||
));
|
||||
}
|
||||
|
||||
let (rest, opt_sp) = opt(cfws)(&input[i..])?;
|
||||
if let Some(opt_sp) = opt_sp {
|
||||
let mut ret = ret.to_vec();
|
||||
ret.extend_from_slice(&opt_sp);
|
||||
Ok((rest, Cow::Owned(ret)))
|
||||
} else {
|
||||
Ok((rest, ret))
|
||||
}
|
||||
}
|
||||
|
||||
///`word = atom / quoted-string`
|
||||
pub fn word(input: &[u8]) -> IResult<&[u8], Cow<'_, [u8]>> {
|
||||
alt((quoted_string, atom))(input)
|
||||
}
|
||||
|
||||
///`phrase = 1*word / obs-phrase`
|
||||
pub fn phrase2(input: &[u8]) -> IResult<&[u8], Vec<u8>> {
|
||||
let (rest, words) = many1(word)(input)?;
|
||||
let len = words.iter().map(|v| v.len()).sum::<usize>();
|
||||
let mut ret = words
|
||||
.into_iter()
|
||||
.fold(Vec::with_capacity(len), |mut acc, el| {
|
||||
acc.extend_from_slice(&el);
|
||||
acc
|
||||
});
|
||||
let right_wsp_padding = ret.len() - ret.rtrim().len();
|
||||
for _ in 0..right_wsp_padding {
|
||||
ret.pop();
|
||||
}
|
||||
Ok((rest, ret))
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_phrase() {
|
||||
let s = b"\"Jeffrey \\\"fejj\\\" Stedfast\""; // <fejj@helixcode.com>"
|
||||
assert_eq!(to_str!(&phrase2(s).unwrap().1), "Jeffrey \"fejj\" Stedfast");
|
||||
}
|
||||
|
||||
///dot-atom-text = 1*atext *("." 1*atext)
|
||||
pub fn dot_atom_text(mut input: &[u8]) -> IResult<&[u8], Cow<'_, [u8]>> {
|
||||
let mut ret = vec![];
|
||||
let mut at_least_one = false;
|
||||
while let Ok((_input, atext_r)) = atext(input) {
|
||||
at_least_one = true;
|
||||
ret.push(atext_r);
|
||||
input = _input;
|
||||
}
|
||||
if !at_least_one {
|
||||
return Err(nom::Err::Error(
|
||||
(input, "dot_atom(): starts with at least one atext").into(),
|
||||
));
|
||||
}
|
||||
|
||||
loop {
|
||||
if !input.starts_with(b".") {
|
||||
break;
|
||||
}
|
||||
ret.push(b'.');
|
||||
input = &input[1..];
|
||||
let mut at_least_one = false;
|
||||
while let Ok((_input, atext_r)) = atext(input) {
|
||||
at_least_one = true;
|
||||
ret.push(atext_r);
|
||||
input = _input;
|
||||
}
|
||||
if !at_least_one {
|
||||
return Err(nom::Err::Error(
|
||||
(input, "dot_atom(): DOT followed with at least one atext").into(),
|
||||
));
|
||||
}
|
||||
}
|
||||
Ok((input, ret.into()))
|
||||
}
|
||||
|
||||
///`atext = ALPHA / DIGIT / ; Printable US-ASCII "!" / "#" / ; characters not including "$" / "%" / ; specials. Used for atoms. "&" / "'" / "*" / "+" / "-" / "/" / "=" / "?" / "^" / "_" / "`" / "{" / "|" / "}" / "~"`
|
||||
pub fn atext(input: &[u8]) -> IResult<&[u8], u8> {
|
||||
if input.is_empty() {
|
||||
return Err(nom::Err::Error((input, "atext(): empty input").into()));
|
||||
}
|
||||
if input[0].is_ascii_alphanumeric()
|
||||
|| [
|
||||
b'!', b'#', b'$', b'%', b'&', b'\'', b'*', b'+', b'-', b'/', b'=', b'?', b'^',
|
||||
b'_', b'`', b'{', b'|', b'}', b'~',
|
||||
]
|
||||
.contains(&input[0])
|
||||
{
|
||||
Ok((&input[1..], input[0]))
|
||||
} else {
|
||||
return Err(nom::Err::Error((input, "atext(): invalid byte").into()));
|
||||
}
|
||||
}
|
||||
|
||||
///dot-atom = [CFWS] dot-atom-text [CFWS]
|
||||
pub fn dot_atom(input: &[u8]) -> IResult<&[u8], Cow<'_, [u8]>> {
|
||||
let (input, _) = opt(cfws)(input)?;
|
||||
let (input, ret) = dot_atom_text(input)?;
|
||||
let (input, _) = opt(cfws)(input)?;
|
||||
Ok((input, ret.into()))
|
||||
}
|
||||
|
||||
///```text
|
||||
///dtext = %d33-90 / ; Printable US-ASCII
|
||||
/// %d94-126 / ; characters not including
|
||||
/// obs-dtext ; "[", "]", or "\"
|
||||
///```
|
||||
pub fn dtext(input: &[u8]) -> IResult<&[u8], u8> {
|
||||
if input.is_empty() {
|
||||
return Err(nom::Err::Error((input, "dtext(): empty input").into()));
|
||||
}
|
||||
if (input[0] >= 33 && input[0] <= 90) || (input[0] > 94 && input[0] < 126) {
|
||||
Ok((&input[1..], input[0]))
|
||||
} else {
|
||||
Err(nom::Err::Error((input, "dtext(): out of range").into()))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub mod headers {
|
||||
|
@ -1453,7 +1669,9 @@ pub mod encodings {
|
|||
pub mod address {
|
||||
use super::*;
|
||||
use crate::email::address::*;
|
||||
use crate::email::parser::generic::cfws;
|
||||
use crate::email::parser::generic::{
|
||||
atom, cfws, dot_atom, dot_atom_text, dtext, phrase2, quoted_string,
|
||||
};
|
||||
pub fn display_addr(input: &[u8]) -> IResult<&[u8], Address> {
|
||||
if input.is_empty() || input.len() < 3 {
|
||||
Err(nom::Err::Error((input, "display_addr(): EOF").into()))
|
||||
|
@ -1565,202 +1783,9 @@ pub mod address {
|
|||
let (input, _) = opt(cfws)(input)?;
|
||||
Ok((input, addr_spec))
|
||||
}
|
||||
///`atom = [CFWS] 1*atext [CFWS]`
|
||||
fn atom(input: &[u8]) -> IResult<&[u8], Cow<'_, [u8]>> {
|
||||
let (input, opt_space) = opt(cfws)(input)?;
|
||||
let mut i = 0;
|
||||
while i < input.len() {
|
||||
//&& !input[i].is_ascii_whitespace() {
|
||||
match input[i] {
|
||||
b'(' | b')' | b'<' | b'>' | b'[' | b']' | b':' | b';' | b'@' | b'\\' | b','
|
||||
| b'.' | b'\r' | b'\n' | b'"' => break,
|
||||
_ => {}
|
||||
}
|
||||
i += 1;
|
||||
}
|
||||
if i == 0 {
|
||||
return Err(nom::Err::Error(
|
||||
(input, "atom(): starts with whitespace or empty").into(),
|
||||
));
|
||||
}
|
||||
while i + 1 > 0 {
|
||||
if input[i - 1] == b' ' || input[i - 1] == b'\t' {
|
||||
i -= 1;
|
||||
} else {
|
||||
break;
|
||||
}
|
||||
}
|
||||
let (rest, opt_space2) = opt(cfws)(&input[i..])?;
|
||||
let ret = if opt_space.is_some() || opt_space2.is_some() {
|
||||
let mut ret = Vec::with_capacity(i + 2);
|
||||
if let Some(opt_space) = opt_space {
|
||||
ret.extend_from_slice(&opt_space);
|
||||
}
|
||||
ret.extend_from_slice(&input[..i]);
|
||||
if let Some(opt_space) = opt_space2 {
|
||||
ret.extend_from_slice(&opt_space);
|
||||
}
|
||||
Cow::Owned(ret)
|
||||
} else {
|
||||
Cow::Borrowed(&input[..i])
|
||||
};
|
||||
Ok((rest, ret))
|
||||
}
|
||||
|
||||
///`quoted-string = [CFWS] DQUOTE *([FWS] qcontent) [FWS] DQUOTE [CFWS]`
|
||||
fn quoted_string(input: &[u8]) -> IResult<&[u8], Cow<'_, [u8]>> {
|
||||
let (input, opt_space) = opt(cfws)(input)?;
|
||||
if !input.starts_with(b"\"") {
|
||||
return Err(nom::Err::Error(
|
||||
(input, "quoted_string(): doesn't start with DQUOTE").into(),
|
||||
));
|
||||
}
|
||||
let input = &input[1..];
|
||||
let mut i = 0;
|
||||
while i < input.len() && input[i] != b'"' {
|
||||
if opt_space.is_some() || (input[i..].starts_with(b"\\") && i + 1 < input.len()) {
|
||||
let mut ret = if let Some(opt_space) = opt_space {
|
||||
let mut r = Vec::with_capacity(2 * i);
|
||||
r.extend_from_slice(&opt_space);
|
||||
r
|
||||
} else {
|
||||
Vec::with_capacity(2 * i)
|
||||
};
|
||||
ret.extend_from_slice(&input[..i]);
|
||||
i += 1;
|
||||
ret.push(input[i]);
|
||||
i += 1;
|
||||
while i < input.len() && input[i] != b'"' {
|
||||
if input[i..].starts_with(b"\\") && i + 1 < input.len() {
|
||||
i += 1;
|
||||
}
|
||||
ret.push(input[i]);
|
||||
i += 1;
|
||||
}
|
||||
if i < input.len() {
|
||||
// skip DQUOTE
|
||||
i += 1;
|
||||
} else {
|
||||
return Err(nom::Err::Error(
|
||||
(input, "quoted_string(): unclosed DQUOTE").into(),
|
||||
));
|
||||
}
|
||||
|
||||
let (rest, opt_sp) = opt(cfws)(&input[i..])?;
|
||||
if let Some(opt_sp) = opt_sp {
|
||||
ret.extend_from_slice(&opt_sp);
|
||||
}
|
||||
let ret = Cow::Owned(ret);
|
||||
return Ok((rest, ret));
|
||||
}
|
||||
i += 1;
|
||||
}
|
||||
let ret = Cow::Borrowed(&input[..i]);
|
||||
if i < input.len() {
|
||||
// skip DQUOTE
|
||||
i += 1;
|
||||
} else {
|
||||
return Err(nom::Err::Error(
|
||||
(input, "quoted_string(): unclosed DQUOTE").into(),
|
||||
));
|
||||
}
|
||||
|
||||
let (rest, opt_sp) = opt(cfws)(&input[i..])?;
|
||||
if let Some(opt_sp) = opt_sp {
|
||||
let mut ret = ret.to_vec();
|
||||
ret.extend_from_slice(&opt_sp);
|
||||
Ok((rest, Cow::Owned(ret)))
|
||||
} else {
|
||||
Ok((rest, ret))
|
||||
}
|
||||
}
|
||||
|
||||
///`word = atom / quoted-string`
|
||||
fn word(input: &[u8]) -> IResult<&[u8], Cow<'_, [u8]>> {
|
||||
alt((quoted_string, atom))(input)
|
||||
}
|
||||
|
||||
///`phrase = 1*word / obs-phrase`
|
||||
fn phrase(input: &[u8]) -> IResult<&[u8], Vec<u8>> {
|
||||
let (rest, words) = many1(word)(input)?;
|
||||
let len = words.iter().map(|v| v.len()).sum::<usize>();
|
||||
let mut ret = words
|
||||
.into_iter()
|
||||
.fold(Vec::with_capacity(len), |mut acc, el| {
|
||||
acc.extend_from_slice(&el);
|
||||
acc
|
||||
});
|
||||
let right_wsp_padding = ret.len() - ret.rtrim().len();
|
||||
for _ in 0..right_wsp_padding {
|
||||
ret.pop();
|
||||
}
|
||||
Ok((rest, ret))
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_phrase() {
|
||||
let s = b"\"Jeffrey \\\"fejj\\\" Stedfast\""; // <fejj@helixcode.com>"
|
||||
assert_eq!(to_str!(&phrase(s).unwrap().1), "Jeffrey \"fejj\" Stedfast");
|
||||
}
|
||||
|
||||
///`addr-spec = local-part "@" domain`
|
||||
pub fn addr_spec(input: &[u8]) -> IResult<&[u8], Address> {
|
||||
///`atext = ALPHA / DIGIT / ; Printable US-ASCII "!" / "#" / ; characters not including "$" / "%" / ; specials. Used for atoms. "&" / "'" / "*" / "+" / "-" / "/" / "=" / "?" / "^" / "_" / "`" / "{" / "|" / "}" / "~"`
|
||||
fn atext(input: &[u8]) -> IResult<&[u8], u8> {
|
||||
if input.is_empty() {
|
||||
return Err(nom::Err::Error((input, "atext(): empty input").into()));
|
||||
}
|
||||
if input[0].is_ascii_alphanumeric()
|
||||
|| [
|
||||
b'!', b'#', b'$', b'%', b'&', b'\'', b'*', b'+', b'-', b'/', b'=', b'?', b'^',
|
||||
b'_', b'`', b'{', b'|', b'}', b'~',
|
||||
]
|
||||
.contains(&input[0])
|
||||
{
|
||||
Ok((&input[1..], input[0]))
|
||||
} else {
|
||||
return Err(nom::Err::Error((input, "atext(): invalid byte").into()));
|
||||
}
|
||||
}
|
||||
///dot-atom-text = 1*atext *("." 1*atext)
|
||||
///dot-atom = [CFWS] dot-atom-text [CFWS]
|
||||
fn dot_atom(input: &[u8]) -> IResult<&[u8], Cow<'_, [u8]>> {
|
||||
let (mut input, _) = opt(cfws)(input)?;
|
||||
let mut ret = vec![];
|
||||
let mut at_least_one = false;
|
||||
while let Ok((_input, atext_r)) = atext(input) {
|
||||
at_least_one = true;
|
||||
ret.push(atext_r);
|
||||
input = _input;
|
||||
}
|
||||
if !at_least_one {
|
||||
return Err(nom::Err::Error(
|
||||
(input, "dot_atom(): starts with at least one atext").into(),
|
||||
));
|
||||
}
|
||||
|
||||
loop {
|
||||
if !input.starts_with(b".") {
|
||||
break;
|
||||
}
|
||||
ret.push(b'.');
|
||||
input = &input[1..];
|
||||
let mut at_least_one = false;
|
||||
while let Ok((_input, atext_r)) = atext(input) {
|
||||
at_least_one = true;
|
||||
ret.push(atext_r);
|
||||
input = _input;
|
||||
}
|
||||
if !at_least_one {
|
||||
return Err(nom::Err::Error(
|
||||
(input, "dot_atom(): DOT followed with at least one atext").into(),
|
||||
));
|
||||
}
|
||||
}
|
||||
let (input, _) = opt(cfws)(input)?;
|
||||
Ok((input, ret.into()))
|
||||
}
|
||||
|
||||
///`obs-domain = atom *("." atom)`
|
||||
fn obs_domain(input: &[u8]) -> IResult<&[u8], Cow<'_, [u8]>> {
|
||||
let (mut input, atom_) = context("obs_domain", atom)(input)?;
|
||||
|
@ -1819,21 +1844,6 @@ pub mod address {
|
|||
Ok((input, ret_s.into()))
|
||||
}
|
||||
|
||||
///```text
|
||||
///dtext = %d33-90 / ; Printable US-ASCII
|
||||
/// %d94-126 / ; characters not including
|
||||
/// obs-dtext ; "[", "]", or "\"
|
||||
///```
|
||||
fn dtext(input: &[u8]) -> IResult<&[u8], u8> {
|
||||
if input.is_empty() {
|
||||
return Err(nom::Err::Error((input, "dtext(): empty input").into()));
|
||||
}
|
||||
if (input[0] >= 33 && input[0] <= 90) || (input[0] > 94 && input[0] < 126) {
|
||||
Ok((&input[1..], input[0]))
|
||||
} else {
|
||||
Err(nom::Err::Error((input, "dtext(): out of range").into()))
|
||||
}
|
||||
}
|
||||
let (input, local_part) = context("addr_spec()", local_part)(input)?;
|
||||
let (input, _) = context("addr_spec()", tag("@"))(input)?;
|
||||
let (input, domain) = context("addr_spec()", domain)(input)?;
|
||||
|
@ -1849,7 +1859,7 @@ pub mod address {
|
|||
|
||||
///`display-name = phrase`
|
||||
pub fn display_name(input: &[u8]) -> IResult<&[u8], Vec<u8>> {
|
||||
let (rest, ret) = phrase(input)?;
|
||||
let (rest, ret) = phrase2(input)?;
|
||||
if let Ok((_, ret)) = crate::email::parser::encodings::phrase(&ret, true) {
|
||||
Ok((rest, ret))
|
||||
} else {
|
||||
|
@ -1965,36 +1975,44 @@ pub mod address {
|
|||
))
|
||||
}
|
||||
|
||||
pub fn message_id(input: &[u8]) -> IResult<&[u8], &[u8]> {
|
||||
delimited(tag("<"), take_until(">"), tag(">"))(input.ltrim())
|
||||
//complete!(delimited!(ws!(tag!("<")), take_until1!(">"), tag!(">")))
|
||||
}
|
||||
|
||||
fn message_id_peek(input: &[u8]) -> IResult<&[u8], &[u8]> {
|
||||
let input_length = input.len();
|
||||
if input.is_empty() {
|
||||
Err(nom::Err::Error(
|
||||
(input, "message_id_peek(): found EOF").into(),
|
||||
))
|
||||
} else if input_length == 2 || input[0] != b'<' {
|
||||
Err(nom::Err::Error(
|
||||
(input, "message_id_peek(): expected '<'").into(),
|
||||
))
|
||||
} else {
|
||||
for (i, &x) in input.iter().take(input_length).enumerate().skip(1) {
|
||||
if x == b'>' {
|
||||
return Ok((&input[i + 1..], &input[0..=i]));
|
||||
}
|
||||
}
|
||||
Err(nom::Err::Error(
|
||||
(input, "message_id_peek(): expected closing '>'").into(),
|
||||
))
|
||||
///`msg-id = [CFWS] "<" id-left "@" id-right ">" [CFWS]`
|
||||
pub fn msg_id(input: &[u8]) -> IResult<&[u8], MessageID> {
|
||||
///`no-fold-literal = "[" *dtext "]"`
|
||||
pub fn no_fold_literal(input: &[u8]) -> IResult<&[u8], Cow<'_, [u8]>> {
|
||||
let orig_input = input;
|
||||
let (input, _) = tag("[")(input)?;
|
||||
let (input, ret) = many0(dtext)(input)?;
|
||||
let (input, _) = tag("]")(input)?;
|
||||
Ok((input, Cow::Borrowed(&orig_input[0..ret.len() + 1])))
|
||||
}
|
||||
|
||||
///`id-left = dot-atom-text / obs-id-left`
|
||||
pub fn id_left(input: &[u8]) -> IResult<&[u8], Cow<'_, [u8]>> {
|
||||
dot_atom_text(input)
|
||||
}
|
||||
///`id-right = dot-atom-text / no-fold-literal / obs-id-right`
|
||||
pub fn id_right(input: &[u8]) -> IResult<&[u8], Cow<'_, [u8]>> {
|
||||
alt((dot_atom_text, no_fold_literal))(input)
|
||||
}
|
||||
let (input, _) = opt(cfws)(input)?;
|
||||
let orig_input = input;
|
||||
let (input, _) = tag("<")(input)?;
|
||||
let (input, id_left_) = id_left(input)?;
|
||||
let (input, _) = tag("@")(input)?;
|
||||
let (input, id_right_) = id_right(input)?;
|
||||
let (input, _) = tag(">")(input)?;
|
||||
let (input, _) = opt(cfws)(input)?;
|
||||
Ok((
|
||||
input,
|
||||
MessageID::new(
|
||||
&orig_input[..3 + id_left_.len() + id_right_.len()],
|
||||
&orig_input[1..2 + id_left_.len() + id_right_.len()],
|
||||
),
|
||||
))
|
||||
}
|
||||
|
||||
pub fn references(input: &[u8]) -> IResult<&[u8], Vec<&[u8]>> {
|
||||
separated_list(is_a(" \n\t\r"), message_id_peek)(input)
|
||||
// separated_list!(complete!(is_a!(" \n\t\r")), message_id_peek));
|
||||
pub fn msg_id_list(input: &[u8]) -> IResult<&[u8], Vec<MessageID>> {
|
||||
many0(msg_id)(input)
|
||||
}
|
||||
|
||||
use smallvec::SmallVec;
|
||||
|
@ -2463,4 +2481,28 @@ border=3D=220=22>
|
|||
</tr>"#)
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_msg_id() {
|
||||
let s = "Message-ID: <1234@local.machine.example>\r\n";
|
||||
let (rest, (_header_name, value)) = headers::header(s.as_bytes()).unwrap();
|
||||
assert!(rest.is_empty());
|
||||
let a = msg_id(value).unwrap().1;
|
||||
assert_eq!(a.val(), b"<1234@local.machine.example>");
|
||||
let s = "Message-ID: <testabcd.1234@silly.test>\r\n";
|
||||
let (rest, (_header_name, value)) = headers::header(s.as_bytes()).unwrap();
|
||||
assert!(rest.is_empty());
|
||||
let b = msg_id(value).unwrap().1;
|
||||
assert_eq!(b.val(), b"<testabcd.1234@silly.test>");
|
||||
let s = "References: <1234@local.machine.example>\r\n";
|
||||
let (rest, (_header_name, value)) = headers::header(s.as_bytes()).unwrap();
|
||||
assert!(rest.is_empty());
|
||||
assert_eq!(&msg_id_list(value).unwrap().1, &[a.clone()]);
|
||||
let s = "References: <1234@local.machine.example> <3456@example.net>\r\n";
|
||||
let (rest, (_header_name, value)) = headers::header(s.as_bytes()).unwrap();
|
||||
assert!(rest.is_empty());
|
||||
let s = b"<3456@example.net>";
|
||||
let c = msg_id(s).unwrap().1;
|
||||
assert_eq!(&msg_id_list(value).unwrap().1, &[a, c]);
|
||||
}
|
||||
}
|
||||
|
|
Loading…
Reference in New Issue