melib: do some small parser refactoring

- Use HeaderName in parsers instead of raw byte strings.
- Use byte literal constants where appropriate instead of repeating
  &b"___"[..]

Signed-off-by: Manos Pitsidianakis <manos@pitsidianak.is>
pull/286/head
Manos Pitsidianakis 2023-08-24 11:32:21 +03:00
parent 66c21ab173
commit 946309c6f3
Signed by: Manos Pitsidianakis
GPG Key ID: 7729C7707F7E09D0
11 changed files with 218 additions and 163 deletions

View File

@ -1092,33 +1092,36 @@ impl Component for EnvelopeView {
ViewMode::Source(Source::Decoded) => {
let text = {
/* Decode each header value */
let mut ret = melib::email::parser::headers::headers(self.mail.bytes())
let mut ret = String::new();
match melib::email::parser::headers::headers(self.mail.bytes())
.map(|(_, v)| v)
.map_err(|err| err.into())
.and_then(|headers| {
Ok(headers
.into_iter()
.map(|(h, v)| {
melib::email::parser::encodings::phrase(v, true)
.map(|(_, v)| {
let mut h = h.to_vec();
h.push(b':');
h.push(b' ');
h.extend(v.into_iter());
h
})
.map_err(|err| err.into())
})
.collect::<Result<Vec<Vec<u8>>>>()?
.join(&b"\n"[..]))
})
.map(|v| String::from_utf8_lossy(&v).into_owned())
.unwrap_or_else(|err: Error| err.to_string());
{
Ok(headers) => {
for (h, v) in headers {
_ = match melib::email::parser::encodings::phrase(v, true) {
Ok((_, v)) => ret.write_fmt(format_args!(
"{h}: {}\n",
String::from_utf8_lossy(&v)
)),
Err(err) => ret.write_fmt(format_args!("{h}: {err}\n")),
};
}
}
Err(err) => {
_ = write!(&mut ret, "{err}");
}
}
if !ret.ends_with("\n\n") {
if ret.ends_with('\n') {
ret.pop();
}
ret.push_str("\n\n");
}
ret.push_str(&self.body_text);
if !ret.ends_with("\n\n") {
if ret.ends_with('\n') {
ret.pop();
}
ret.push_str("\n\n");
}
// ret.push_str(&self.attachment_tree);

View File

@ -104,7 +104,7 @@ pub mod mailto;
pub mod parser;
pub mod pgp;
use std::{borrow::Cow, convert::TryInto, ops::Deref};
use std::{borrow::Cow, ops::Deref};
pub use address::{Address, MessageID, References, StrBuild, StrBuilder};
pub use attachments::{Attachment, AttachmentBuilder};
@ -371,47 +371,55 @@ impl Envelope {
}
};
for (name, value) in headers {
let name: HeaderName = name.try_into()?;
if name == "to" {
let parse_result = parser::address::rfc2822address_list(value);
if let Ok((_, value)) = parse_result {
self.set_to(value);
};
} else if name == "cc" {
let parse_result = parser::address::rfc2822address_list(value);
if let Ok((_, value)) = parse_result {
self.set_cc(value);
};
} else if name == "bcc" {
let parse_result = parser::address::rfc2822address_list(value);
if let Ok((_, value)) = parse_result {
self.set_bcc(value.to_vec());
};
} else if name == "from" {
let parse_result = parser::address::rfc2822address_list(value);
if let Ok((_, value)) = parse_result {
self.set_from(value);
match name {
HeaderName::TO => {
let parse_result = parser::address::rfc2822address_list(value);
if let Ok((_, value)) = parse_result {
self.set_to(value);
};
}
} else if name == "subject" {
let parse_result = parser::encodings::phrase(value.trim(), false);
if let Ok((_, value)) = parse_result {
self.set_subject(value);
};
} else if name == "message-id" {
self.set_message_id(value);
} else if name == "references" {
self.set_references(value);
} else if name == "in-reply-to" {
self.set_in_reply_to(value);
} else if name == "date" {
let parse_result = parser::encodings::phrase(value, false);
if let Ok((_, value)) = parse_result {
self.set_date(value.as_slice());
} else {
self.set_date(value);
HeaderName::CC => {
let parse_result = parser::address::rfc2822address_list(value);
if let Ok((_, value)) = parse_result {
self.set_cc(value);
};
}
} else if name == "content-type" {
match parser::attachments::content_type(value) {
HeaderName::BCC => {
let parse_result = parser::address::rfc2822address_list(value);
if let Ok((_, value)) = parse_result {
self.set_bcc(value.to_vec());
};
}
HeaderName::FROM => {
let parse_result = parser::address::rfc2822address_list(value);
if let Ok((_, value)) = parse_result {
self.set_from(value);
}
}
HeaderName::SUBJECT => {
let parse_result = parser::encodings::phrase(value.trim(), false);
if let Ok((_, value)) = parse_result {
self.set_subject(value);
};
}
HeaderName::MESSAGE_ID => {
self.set_message_id(value);
}
HeaderName::REFERENCES => {
self.set_references(value);
}
HeaderName::IN_REPLY_TO => {
self.set_in_reply_to(value);
}
HeaderName::DATE => {
let parse_result = parser::encodings::phrase(value, false);
if let Ok((_, value)) = parse_result {
self.set_date(value.as_slice());
} else {
self.set_date(value);
}
}
HeaderName::CONTENT_TYPE => match parser::attachments::content_type(value) {
Ok((_, (ct, cst, ref params)))
if ct.eq_ignore_ascii_case(b"multipart")
&& cst.eq_ignore_ascii_case(b"mixed") =>
@ -437,7 +445,8 @@ impl Envelope {
}
}
_ => {}
}
},
_ => {}
}
self.other_headers.insert(
name,
@ -598,13 +607,13 @@ impl Envelope {
builder.build()
}
pub fn headers<'a>(&self, bytes: &'a [u8]) -> Result<Vec<(&'a str, &'a str)>> {
pub fn headers<'a>(&self, bytes: &'a [u8]) -> Result<Vec<(HeaderName, &'a str)>> {
let ret = parser::headers::headers(bytes)?.1;
let len = ret.len();
ret.into_iter()
.try_fold(Vec::with_capacity(len), |mut acc, (a, b)| {
Ok({
acc.push((std::str::from_utf8(a)?, std::str::from_utf8(b)?));
acc.push((a, std::str::from_utf8(b)?));
acc
})
})

View File

@ -31,7 +31,7 @@ use crate::{
address::StrBuilder,
attachment_types::*,
parser::{self, BytesExt},
Mail,
HeaderName, Mail,
},
BytesDisplay,
};
@ -108,11 +108,11 @@ impl AttachmentBuilder {
..Default::default()
};
for (name, value) in headers {
if name.eq_ignore_ascii_case(b"content-type") {
if name == HeaderName::CONTENT_TYPE {
builder.set_content_type_from_bytes(value);
} else if name.eq_ignore_ascii_case(b"content-transfer-encoding") {
} else if name == HeaderName::CONTENT_TRANSFER_ENCODING {
builder.set_content_transfer_encoding(ContentTransferEncoding::from(value));
} else if name.eq_ignore_ascii_case(b"content-disposition") {
} else if name == HeaderName::CONTENT_DISPOSITION {
builder.set_content_disposition(ContentDisposition::from(value));
}
}
@ -321,13 +321,13 @@ impl AttachmentBuilder {
length: body.len(),
};
for (name, value) in headers {
if name.eq_ignore_ascii_case(b"content-type") {
if name == HeaderName::CONTENT_TYPE {
builder.set_content_type_from_bytes(value);
} else if name.eq_ignore_ascii_case(b"content-transfer-encoding") {
} else if name == HeaderName::CONTENT_TRANSFER_ENCODING {
builder.set_content_transfer_encoding(ContentTransferEncoding::from(
value,
));
} else if name.eq_ignore_ascii_case(b"content-disposition") {
} else if name == HeaderName::CONTENT_DISPOSITION {
builder.set_content_disposition(ContentDisposition::from(value));
}
}
@ -547,10 +547,10 @@ impl Attachment {
Err(_err) => return false,
};
let headers = crate::email::parser::generic::HeaderIterator(headers)
.collect::<SmallVec<[(&[u8], &[u8]); 16]>>();
.collect::<SmallVec<[(HeaderName, &[u8]); 16]>>();
let disposition = headers
.iter()
.find(|(n, _)| n.eq_ignore_ascii_case(b"content-disposition"))
.find(|(n, _)| n == HeaderName::CONTENT_DISPOSITION)
.map(|(_, v)| ContentDisposition::from(*v))
.unwrap_or_default();
if disposition.kind.is_attachment() {
@ -558,7 +558,7 @@ impl Attachment {
}
if let Some(boundary) = headers
.iter()
.find(|(n, _)| n.eq_ignore_ascii_case(b"content-type"))
.find(|(n, _)| n == HeaderName::CONTENT_TYPE)
.and_then(|(_, v)| {
if let Ok((_, (ct, _cst, params))) =
parser::attachments::content_type(v)
@ -856,7 +856,7 @@ impl Attachment {
Err(_) => return ret,
};
for (name, value) in headers {
if name.eq_ignore_ascii_case(b"content-type") {
if name == HeaderName::CONTENT_TYPE {
if let Ok((_, (_, _, params))) = parser::attachments::content_type(value) {
ret = params;
}

View File

@ -92,8 +92,7 @@ impl FromStr for Draft {
let mut ret = Self::default();
for (k, v) in headers {
ret.headers
.insert(k.try_into()?, String::from_utf8(v.to_vec())?);
ret.headers.insert(k, String::from_utf8(v.to_vec())?);
}
let body = Envelope::new(EnvelopeHash::default()).body_bytes(s.as_bytes());
@ -107,7 +106,7 @@ impl Draft {
pub fn edit(envelope: &Envelope, bytes: &[u8]) -> Result<Self> {
let mut ret = Self::default();
for (k, v) in envelope.headers(bytes).unwrap_or_else(|_| Vec::new()) {
ret.headers.insert(k.try_into()?, v.into());
ret.headers.insert(k, v.into());
}
let body = envelope.body_bytes(bytes);

View File

@ -57,7 +57,7 @@ impl Eq for dyn HeaderKey + '_ {}
impl HeaderKey for HeaderName {
fn to_key(&self) -> &[u8] {
self.as_bytes()
self.as_lowercase_bytes()
}
}

View File

@ -44,6 +44,7 @@ bitflags! {
const Mail = Self::None.bits() << 1;
const NNTP = Self::Mail.bits() << 1;
const MIME = Self::NNTP.bits() << 1;
const Mbox = Self::MIME.bits() << 1;
}
}
@ -141,13 +142,21 @@ impl HeaderName {
/// The returned string will always be lower case. Use `Display` for a
/// properly formatted representation.
#[inline]
pub fn as_bytes(&self) -> &[u8] {
pub fn as_lowercase_bytes(&self) -> &[u8] {
match self.inner {
Repr::Standard(v) => v.as_str().as_bytes(),
Repr::Custom(ref v) => v.0.as_ref(),
}
}
/// Converts a header into a `Vec<u8>`.
///
/// The returned string will always be formatted.
#[inline]
pub fn into_bytes(self) -> Vec<u8> {
self.to_string().into_bytes()
}
pub fn from_bytes(src: &[u8]) -> Result<Self, InvalidHeaderName> {
if let Some(std) = StandardHeader::from_bytes(src.trim()) {
Ok(Self {
@ -413,9 +422,11 @@ const UPPERCASE_TOKENS: &[&str] = &[
"SIO", "SPF", "TLS", "VBR",
];
impl std::fmt::Display for Custom {
struct HeaderNameFmt<'a>(&'a str);
impl std::fmt::Display for HeaderNameFmt<'_> {
fn fmt(&self, fmt: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
let as_str = self.as_str();
let as_str = self.0;
let len = as_str.len();
let mut bytes_count = 0;
for chunk in as_str.split('-') {
@ -467,6 +478,12 @@ impl std::fmt::Display for Custom {
}
}
impl std::fmt::Display for Custom {
fn fmt(&self, fmt: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
write!(fmt, "{}", HeaderNameFmt(self.as_str()))
}
}
// an iterator which alternates between Some and None
struct AsciiIgnoreCaseCmp<'a, 'b> {
ord: Ordering,

View File

@ -370,12 +370,13 @@ standard_headers! {
(ResentMessageId, RESENT_MESSAGE_ID, "Resent-Message-ID", None, Protocol::Mail, Status::Standard, &[Standard::RFC5322]);
(ResentReplyTo, RESENT_REPLY_TO, "Resent-Reply-To", None, Protocol::Mail, Status::Obsoleted, &[Standard::RFC5322]);
(ResentSender, RESENT_SENDER, "Resent-Sender", None, Protocol::Mail, Status::Standard, &[Standard::RFC5322, Standard::RFC6854]);
(ResentTo, RESENT_TO, "Resent-To", None, Protocol::Mail, Status::Standard, &[Standard::RFC5322]);
(ResentTo, RESENT_TO, "Resent-To", None, Protocol::Mail, Status::Standard, &[Standard::RFC5322]);
(ReturnPath, RETURN_PATH, "Return-Path", None, Protocol::Mail, Status::Standard, &[Standard::RFC5322]);
(SeeAlso, SEE_ALSO, "See-Also", None, Protocol::NNTP, Status::Obsoleted, &[Standard::RFC1849, Standard::RFC5536]);
(Sender, SENDER, "Sender", None, Protocol::Mail | Protocol::NNTP, Status::Standard, &[Standard::RFC5322, Standard::RFC6854]);
(Sensitivity, SENSITIVITY, "Sensitivity", None, Protocol::Mail, Status::None, &[Standard::RFC4021]);
(Solicitation, SOLICITATION, "Solicitation", None, Protocol::Mail, Status::None, &[Standard::RFC3865]);
(Status, STATUS, "Status", None, Protocol::Mbox, Status::None, &[]);
(Summary, SUMMARY, "Summary", None, Protocol::NNTP, Status::Standard, &[Standard::RFC5536]);
(Supersedes, SUPERSEDES, "Supersedes", None, Protocol::Mail | Protocol::NNTP, Status::None, &[Standard::RFC5536, Standard::RFC2156]);
(TlsReportDomain, TLS_REPORT_DOMAIN, "TLS-Report-Domain", None, Protocol::Mail, Status::Standard, &[Standard::RFC8460]);
@ -402,9 +403,11 @@ standard_headers! {
(SioLabel, SIO_LABEL, "SIO-Label", None, Protocol::Mail, Status::None, &[Standard::RFC7444]);
(SioLabelHistory, SIO_LABEL_HISTORY, "SIO-Label-History", None, Protocol::Mail, Status::None, &[Standard::RFC7444]);
(XArchivedAt, X_ARCHIVED_AT, "X-Archived-At", Some("prov/x-archived-at"), Protocol::Mail | Protocol::NNTP, Status::Deprecated, &[Standard::RFC5064]);
(XKeywords, X_KEYWORDS, "X-Keywords", None, Protocol::Mbox, Status::None, &[]);
(XMittente, X_MITTENTE, "X-Mittente", None, Protocol::Mail, Status::None, &[Standard::RFC6109]);
(XRicevuta, X_RICEVUTA, "X-Ricevuta", None, Protocol::Mail, Status::None, &[Standard::RFC6109]);
(XRiferimentoMessageId, X_RIFERIMENTO_MESSAGE_ID, "X-Riferimento-Message-ID", None, Protocol::Mail, Status::None, &[Standard::RFC6109]);
(XStatus, X_STATUS, "X-Status", None, Protocol::Mbox, Status::None, &[]);
(XTiporicevuta, X_TIPORICEVUTA, "X-TipoRicevuta", None, Protocol::Mail, Status::None, &[Standard::RFC6109]);
(XTrasporto, X_TRASPORTO, "X-Trasporto", None, Protocol::Mail, Status::None, &[Standard::RFC6109]);
(XVerificasicurezza, X_VERIFICASICUREZZA, "X-VerificaSicurezza", None, Protocol::Mail, Status::None, &[Standard::RFC6109]);

View File

@ -309,7 +309,7 @@ impl<'a, P: for<'r> FnMut(&'r u8) -> bool> BytesIterExt for std::slice::Split<'a
}
//fn parser(input: I) -> IResult<I, O, E>;
pub fn mail(input: &[u8]) -> Result<(Vec<(&[u8], &[u8])>, &[u8])> {
pub fn mail(input: &[u8]) -> Result<(Vec<(HeaderName, &[u8])>, &[u8])> {
let (rest, result) = alt((
separated_pair(
headers::headers,
@ -1097,8 +1097,9 @@ pub mod generic {
pub struct HeaderIterator<'a>(pub &'a [u8]);
impl<'a> Iterator for HeaderIterator<'a> {
type Item = (&'a [u8], &'a [u8]);
fn next(&mut self) -> Option<(&'a [u8], &'a [u8])> {
type Item = (HeaderName, &'a [u8]);
fn next(&mut self) -> Option<(HeaderName, &'a [u8])> {
if self.0.is_empty() {
return None;
}
@ -1425,15 +1426,15 @@ pub mod headers {
//! Email headers.
use super::*;
pub fn headers(input: &[u8]) -> IResult<&[u8], Vec<(&[u8], &[u8])>> {
pub fn headers(input: &[u8]) -> IResult<&[u8], Vec<(HeaderName, &[u8])>> {
many1(header)(input)
}
pub fn header(input: &[u8]) -> IResult<&[u8], (&[u8], &[u8])> {
pub fn header(input: &[u8]) -> IResult<&[u8], (HeaderName, &[u8])> {
alt((header_without_val, header_with_val))(input)
}
pub fn header_without_val(input: &[u8]) -> IResult<&[u8], (&[u8], &[u8])> {
pub fn header_without_val(input: &[u8]) -> IResult<&[u8], (HeaderName, &[u8])> {
if input.is_empty() {
return Err(nom::Err::Error(
(input, "header_without_val(): input is empty").into(),
@ -1477,6 +1478,11 @@ pub mod headers {
(input, "header_without_val(): not enough input").into(),
));
}
let Ok(name) = HeaderName::try_from(name) else {
return Err(nom::Err::Error(
(input, "header_without_val(): invalid header name").into(),
));
};
if input[ptr] == b':' {
ptr += 1;
has_colon = true;
@ -1592,8 +1598,9 @@ pub mod headers {
))
}
/* Parse a single header as a tuple */
pub fn header_with_val(input: &[u8]) -> IResult<&[u8], (&[u8], &[u8])> {
/// Parse a single header as a ([`HeaderName`](crate::email::HeaderName),
/// [`&[u8]`]) tuple.
pub fn header_with_val(input: &[u8]) -> IResult<&[u8], (HeaderName, &[u8])> {
if input.is_empty() {
return Err(nom::Err::Error(
(input, "header_with_val(): empty input").into(),
@ -1660,6 +1667,11 @@ pub mod headers {
));
}
}
let Ok(name) = HeaderName::try_from(name) else {
return Err(nom::Err::Error(
(input, "header_with_val(): invalid header name").into(),
));
};
header_value(&input[ptr..]).map(|(rest, value)| (rest, (name, value)))
}
@ -1689,7 +1701,8 @@ pub mod attachments {
address::*,
attachment_types::{ContentDisposition, ContentDispositionKind},
};
pub fn attachment(input: &[u8]) -> IResult<&[u8], (std::vec::Vec<(&[u8], &[u8])>, &[u8])> {
pub fn attachment(input: &[u8]) -> IResult<&[u8], (std::vec::Vec<(HeaderName, &[u8])>, &[u8])> {
alt((
separated_pair(
many0(headers::header),

View File

@ -42,8 +42,11 @@ use crate::{
},
},
error::ResultIntoError,
utils::parsec::CRLF,
};
const UNTAGGED_PREFIX: &[u8] = b"* ";
bitflags! {
#[derive(Default, Serialize, Deserialize)]
pub struct RequiredResponses: u64 {
@ -75,10 +78,10 @@ bitflags! {
impl RequiredResponses {
pub fn check(&self, line: &[u8]) -> bool {
if !line.starts_with(b"* ") {
if !line.starts_with(UNTAGGED_PREFIX) {
return false;
}
let line = &line[b"* ".len()..];
let line = &line[UNTAGGED_PREFIX.len()..];
let mut ret = false;
if self.intersects(Self::CAPABILITY) {
ret |= line.starts_with(b"CAPABILITY");
@ -356,7 +359,7 @@ impl<'a> Iterator for ImapLineIterator<'a> {
let mut i = 0;
loop {
let cur_slice = &self.slice[i..];
if let Some(pos) = cur_slice.find(b"\r\n") {
if let Some(pos) = cur_slice.find(CRLF) {
/* Skip literal continuation line */
if cur_slice.get(pos.saturating_sub(1)) == Some(&b'}') {
i += pos + 2;
@ -424,7 +427,7 @@ pub fn list_mailbox_result(input: &[u8]) -> IResult<&[u8], ImapMailbox> {
let (input, separator) = delimited(tag(b"\""), take(1_u32), tag(b"\""))(input)?;
let (input, _) = take(1_u32)(input)?;
let (input, path) = mailbox_token(input)?;
let (input, _) = tag("\r\n")(input)?;
let (input, _) = tag(CRLF)(input)?;
Ok((
input,
({
@ -495,7 +498,7 @@ pub struct FetchResponse<'a> {
pub fn fetch_response(input: &[u8]) -> ImapParseResult<FetchResponse<'_>> {
macro_rules! should_start_with {
($input:expr, $tag:literal) => {
($input:expr, $tag:tt) => {
if !$input.starts_with($tag) {
return Err(Error::new(format!(
"Expected `{}` but got `{:.50}`",
@ -505,9 +508,9 @@ pub fn fetch_response(input: &[u8]) -> ImapParseResult<FetchResponse<'_>> {
}
};
}
should_start_with!(input, b"* ");
should_start_with!(input, UNTAGGED_PREFIX);
let mut i = b"* ".len();
let mut i = UNTAGGED_PREFIX.len();
macro_rules! bounds {
() => {
if i == input.len() {
@ -707,7 +710,7 @@ pub fn fetch_responses(mut input: &[u8]) -> ImapParseResult<Vec<FetchResponse<'_
let mut ret = Vec::new();
let mut alert: Option<Alert> = None;
while input.starts_with(b"* ") {
while input.starts_with(UNTAGGED_PREFIX) {
let next_response = fetch_response(input);
match next_response {
Ok((rest, el, el_alert)) => {
@ -784,8 +787,8 @@ pub fn capabilities(input: &[u8]) -> IResult<&[u8], Vec<&[u8]>> {
let (input, _) = take_until("CAPABILITY ")(input)?;
let (input, _) = tag("CAPABILITY ")(input)?;
let (input, ret) = separated_list1(tag(" "), is_not(" ]\r\n"))(input)?;
let (input, _) = take_until("\r\n")(input)?;
let (input, _) = tag("\r\n")(input)?;
let (input, _) = take_until(CRLF)(input)?;
let (input, _) = tag(CRLF)(input)?;
Ok((input, ret))
}
@ -860,14 +863,13 @@ pub enum UntaggedResponse<'s> {
pub fn untagged_responses(input: &[u8]) -> ImapParseResult<Option<UntaggedResponse<'_>>> {
let orig_input = input;
let (input, _) = tag::<_, &[u8], (&[u8], nom::error::ErrorKind)>(b"* ")(input)?;
let (input, _) = tag::<_, &[u8], (&[u8], nom::error::ErrorKind)>(UNTAGGED_PREFIX)(input)?;
let (input, num) = map_res::<_, _, _, (&[u8], nom::error::ErrorKind), _, _, _>(digit1, |s| {
ImapNum::from_str(unsafe { std::str::from_utf8_unchecked(s) })
})(input)?;
let (input, _) = tag::<_, &[u8], (&[u8], nom::error::ErrorKind)>(b" ")(input)?;
let (input, _tag) =
take_until::<_, &[u8], (&[u8], nom::error::ErrorKind)>(&b"\r\n"[..])(input)?;
let (input, _) = tag::<_, &[u8], (&[u8], nom::error::ErrorKind)>(b"\r\n")(input)?;
let (input, _tag) = take_until::<_, &[u8], (&[u8], nom::error::ErrorKind)>(CRLF)(input)?;
let (input, _) = tag::<_, &[u8], (&[u8], nom::error::ErrorKind)>(CRLF)(input)?;
debug!(
"Parse untagged response from {:?}",
String::from_utf8_lossy(orig_input)
@ -904,7 +906,7 @@ pub fn search_results<'a>(input: &'a [u8]) -> IResult<&'a [u8], Vec<ImapNum>> {
ImapNum::from_str(unsafe { std::str::from_utf8_unchecked(s) })
}),
)(input)?;
let (input, _) = tag("\r\n")(input)?;
let (input, _) = tag(CRLF)(input)?;
Ok((input, list))
},
|input: &'a [u8]| -> IResult<&'a [u8], Vec<ImapNum>> {
@ -918,8 +920,8 @@ pub fn search_results_raw<'a>(input: &'a [u8]) -> IResult<&'a [u8], &'a [u8]> {
alt((
|input: &'a [u8]| -> IResult<&'a [u8], &'a [u8]> {
let (input, _) = tag("* SEARCH ")(input)?;
let (input, list) = take_until("\r\n")(input)?;
let (input, _) = tag("\r\n")(input)?;
let (input, list) = take_until(CRLF)(input)?;
let (input, _) = tag(CRLF)(input)?;
Ok((input, list))
},
|input: &'a [u8]| -> IResult<&'a [u8], &'a [u8]> {
@ -971,13 +973,13 @@ pub fn select_response(input: &[u8]) -> Result<SelectResponse> {
if input.contains_subsequence(b"* OK") {
let mut ret = SelectResponse::default();
for l in input.split_rn() {
if l.starts_with(b"* ") && l.ends_with(b" EXISTS\r\n") {
if l.starts_with(UNTAGGED_PREFIX) && l.ends_with(b" EXISTS\r\n") {
ret.exists = ImapNum::from_str(&String::from_utf8_lossy(
&l[b"* ".len()..l.len() - b" EXISTS\r\n".len()],
&l[UNTAGGED_PREFIX.len()..l.len() - b" EXISTS\r\n".len()],
))?;
} else if l.starts_with(b"* ") && l.ends_with(b" RECENT\r\n") {
} else if l.starts_with(UNTAGGED_PREFIX) && l.ends_with(b" RECENT\r\n") {
ret.recent = ImapNum::from_str(&String::from_utf8_lossy(
&l[b"* ".len()..l.len() - b" RECENT\r\n".len()],
&l[UNTAGGED_PREFIX.len()..l.len() - b" RECENT\r\n".len()],
))?;
} else if l.starts_with(b"* FLAGS (") {
ret.flags = flags(&l[b"* FLAGS (".len()..l.len() - b")".len()]).map(|(_, v)| v)?;
@ -1110,28 +1112,29 @@ pub fn byte_flags(input: &[u8]) -> IResult<&[u8], (Flag, Vec<String>)> {
*/
pub fn envelope(input: &[u8]) -> IResult<&[u8], Envelope> {
const WS: &[u8] = b"\r\n\t ";
let (input, _) = tag("(")(input)?;
let (input, _) = opt(is_a("\r\n\t "))(input)?;
let (input, _) = opt(is_a(WS))(input)?;
let (input, date) = quoted_or_nil(input)?;
let (input, _) = opt(is_a("\r\n\t "))(input)?;
let (input, _) = opt(is_a(WS))(input)?;
let (input, subject) = quoted_or_nil(input)?;
let (input, _) = opt(is_a("\r\n\t "))(input)?;
let (input, _) = opt(is_a(WS))(input)?;
let (input, from) = envelope_addresses(input)?;
let (input, _) = opt(is_a("\r\n\t "))(input)?;
let (input, _) = opt(is_a(WS))(input)?;
let (input, _sender) = envelope_addresses(input)?;
let (input, _) = opt(is_a("\r\n\t "))(input)?;
let (input, _) = opt(is_a(WS))(input)?;
let (input, _reply_to) = envelope_addresses(input)?;
let (input, _) = opt(is_a("\r\n\t "))(input)?;
let (input, _) = opt(is_a(WS))(input)?;
let (input, to) = envelope_addresses(input)?;
let (input, _) = opt(is_a("\r\n\t "))(input)?;
let (input, _) = opt(is_a(WS))(input)?;
let (input, cc) = envelope_addresses(input)?;
let (input, _) = opt(is_a("\r\n\t "))(input)?;
let (input, _) = opt(is_a(WS))(input)?;
let (input, bcc) = envelope_addresses(input)?;
let (input, _) = opt(is_a("\r\n\t "))(input)?;
let (input, _) = opt(is_a(WS))(input)?;
let (input, in_reply_to) = quoted_or_nil(input)?;
let (input, _) = opt(is_a("\r\n\t "))(input)?;
let (input, _) = opt(is_a(WS))(input)?;
let (input, message_id) = quoted_or_nil(input)?;
let (input, _) = opt(is_a("\r\n\t "))(input)?;
let (input, _) = opt(is_a(WS))(input)?;
let (input, _) = tag(")")(input)?;
Ok((
input,
@ -1215,13 +1218,14 @@ pub fn envelope_addresses<'a>(
// Parse an address in the format of the ENVELOPE structure eg
// ("Terry Gray" NIL "gray" "cac.washington.edu")
pub fn envelope_address(input: &[u8]) -> IResult<&[u8], Address> {
const WS: &[u8] = b"\r\n\t ";
let (input, name) = alt((quoted, map(tag("NIL"), |_| Vec::new())))(input)?;
let (input, _) = is_a("\r\n\t ")(input)?;
let (input, _) = is_a(WS)(input)?;
let (input, _) = alt((quoted, map(tag("NIL"), |_| Vec::new())))(input)?;
let (input, _) = is_a("\r\n\t ")(input)?;
let (input, _) = is_a(WS)(input)?;
let (input, mailbox_name) = alt((quoted, map(tag("NIL"), |_| Vec::new())))(input)?;
let (input, host_name) = opt(preceded(
is_a("\r\n\t "),
is_a(WS),
alt((quoted, map(tag("NIL"), |_| Vec::new()))),
))(input)?;
Ok((
@ -1369,7 +1373,7 @@ fn eat_whitespace(mut input: &[u8]) -> IResult<&[u8], ()> {
while !input.is_empty() {
if input[0] == b' ' || input[0] == b'\n' || input[0] == b'\t' {
input = &input[1..];
} else if input.starts_with(b"\r\n") {
} else if input.starts_with(CRLF) {
input = &input[2..];
} else {
break;

View File

@ -20,7 +20,10 @@
*/
use super::*;
use crate::utils::datetime;
use crate::utils::{
datetime,
parsec::{CRLF, LF},
};
impl MboxFormat {
#[allow(clippy::too_many_arguments)]
@ -38,7 +41,7 @@ impl MboxFormat {
if tags.iter().any(|t| t.contains(' ')) {
return Err(Error::new("mbox tags/keywords can't contain spaces"));
}
let line_ending: &'static [u8] = if crlf { &b"\r\n"[..] } else { &b"\n"[..] };
let line_ending: &'static [u8] = if crlf { CRLF } else { LF };
if !is_empty {
writer.write_all(line_ending)?;
writer.write_all(line_ending)?;
@ -62,21 +65,21 @@ impl MboxFormat {
writer.write_all(line_ending)?;
let (mut headers, body) = parser::mail(input)?;
headers.retain(|(header_name, _)| {
!header_name.eq_ignore_ascii_case(b"Status")
&& !header_name.eq_ignore_ascii_case(b"X-Status")
&& !header_name.eq_ignore_ascii_case(b"X-Keywords")
&& !header_name.eq_ignore_ascii_case(b"Content-Length")
header_name != HeaderName::STATUS
&& header_name != HeaderName::X_STATUS
&& header_name != HeaderName::X_KEYWORDS
&& header_name != HeaderName::CONTENT_LENGTH
});
let write_header_val_fn = |writer: &mut dyn std::io::Write, bytes: &[u8]| {
let mut i = 0;
if crlf {
while i < bytes.len() {
if bytes[i..].starts_with(b"\r\n") {
writer.write_all(&[b'\r', b'\n'])?;
if bytes[i..].starts_with(CRLF) {
writer.write_all(CRLF)?;
i += 2;
continue;
} else if bytes[i] == b'\n' {
writer.write_all(&[b'\r', b'\n'])?;
writer.write_all(CRLF)?;
} else {
writer.write_all(&[bytes[i]])?;
}
@ -84,8 +87,8 @@ impl MboxFormat {
}
} else {
while i < bytes.len() {
if bytes[i..].starts_with(b"\r\n") {
writer.write_all(&[b'\n'])?;
if bytes[i..].starts_with(CRLF) {
writer.write_all(LF)?;
i += 2;
} else {
writer.write_all(&[bytes[i]])?;
@ -99,7 +102,7 @@ impl MboxFormat {
MboxMetadata::CClient => {
for (h, v) in {
if flags.is_seen() {
Some((&b"Status"[..], "R".into()))
Some((HeaderName::STATUS, "R".into()))
} else {
None
}
@ -113,7 +116,7 @@ impl MboxFormat {
None
} else {
Some((
&b"X-Status"[..],
HeaderName::X_STATUS,
format!(
"{flagged}{replied}{draft}{trashed}",
flagged = if flags.is_flagged() { "F" } else { "" },
@ -127,11 +130,10 @@ impl MboxFormat {
.chain(if tags.is_empty() {
None
} else {
Some((&b"X-Keywords"[..], tags.as_slice().join(" ")))
Some((HeaderName::X_KEYWORDS, tags.as_slice().join(" ")))
})
} {
writer.write_all(h)?;
writer.write_all(&b": "[..])?;
writer.write_fmt(format_args!("{}: ", h))?;
writer.write_all(v.as_bytes())?;
writer.write_all(line_ending)?;
}
@ -144,17 +146,19 @@ impl MboxFormat {
let mut len = body.len();
if crlf {
let stray_lfs = body.iter().filter(|b| **b == b'\n').count()
- body.windows(b"\r\n".len()).filter(|w| w == b"\r\n").count();
- body.windows(CRLF.len()).filter(|w| w == &CRLF).count();
len += stray_lfs;
} else {
let crlfs = body.windows(b"\r\n".len()).filter(|w| w == b"\r\n").count();
let crlfs = body.windows(CRLF.len()).filter(|w| w == &CRLF).count();
len -= crlfs;
}
len
};
match self {
Self::MboxO | Self::MboxRd => Err(Error::new("Unimplemented.")),
Self::MboxO | Self::MboxRd => {
Err(Error::new("Not implemented.").set_kind(ErrorKind::NotImplemented))
}
Self::MboxCl => {
let len = (body_len
+ body
@ -165,10 +169,9 @@ impl MboxFormat {
.to_string();
for (h, v) in headers
.into_iter()
.chain(Some((&b"Content-Length"[..], len.as_bytes())))
.chain(Some((HeaderName::CONTENT_LENGTH, len.as_bytes())))
{
writer.write_all(h)?;
writer.write_all(&b": "[..])?;
writer.write_fmt(format_args!("{}: ", h))?;
write_header_val_fn(writer, v)?;
writer.write_all(line_ending)?;
}
@ -181,14 +184,14 @@ impl MboxFormat {
let mut i = 0;
if crlf {
while i < body.len() {
if body[i..].starts_with(b"\r\n") {
writer.write_all(&[b'\r', b'\n'])?;
if body[i..].starts_with(CRLF) {
writer.write_all(CRLF)?;
if body[i..].starts_with(b"\r\nFrom ") {
writer.write_all(&[b'>'])?;
}
i += 2;
} else if body[i] == b'\n' {
writer.write_all(&[b'\r', b'\n'])?;
writer.write_all(CRLF)?;
if body[i..].starts_with(b"\nFrom ") {
writer.write_all(&[b'>'])?;
}
@ -200,8 +203,8 @@ impl MboxFormat {
}
} else {
while i < body.len() {
if body[i..].starts_with(b"\r\n") {
writer.write_all(&[b'\n'])?;
if body[i..].starts_with(CRLF) {
writer.write_all(LF)?;
if body[i..].starts_with(b"\r\nFrom ") {
writer.write_all(&[b'>'])?;
}
@ -221,10 +224,9 @@ impl MboxFormat {
let len = body_len.to_string();
for (h, v) in headers
.into_iter()
.chain(Some((&b"Content-Length"[..], len.as_bytes())))
.chain(Some((HeaderName::CONTENT_LENGTH, len.as_bytes())))
{
writer.write_all(h)?;
writer.write_all(&b": "[..])?;
writer.write_fmt(format_args!("{}: ", h))?;
write_header_val_fn(writer, v)?;
writer.write_all(line_ending)?;
}
@ -233,12 +235,12 @@ impl MboxFormat {
let mut i = 0;
if crlf {
while i < body.len() {
if body[i..].starts_with(b"\r\n") {
writer.write_all(&[b'\r', b'\n'])?;
if body[i..].starts_with(CRLF) {
writer.write_all(CRLF)?;
i += 2;
continue;
} else if body[i] == b'\n' {
writer.write_all(&[b'\r', b'\n'])?;
writer.write_all(CRLF)?;
} else {
writer.write_all(&[body[i]])?;
}
@ -246,8 +248,8 @@ impl MboxFormat {
}
} else {
while i < body.len() {
if body[i..].starts_with(b"\r\n") {
writer.write_all(&[b'\n'])?;
if body[i..].starts_with(CRLF) {
writer.write_all(LF)?;
i += 2;
} else {
writer.write_all(&[body[i]])?;

View File

@ -25,6 +25,11 @@ use std::borrow::Cow;
use crate::utils::datetime::{parse_timestamp_from_string, UnixTimestamp};
pub const CRLF: &[u8] = b"\r\n";
pub const LF: &[u8] = b"\n";
pub const NULL: &[u8] = b"\0";
pub const EMPTY: &[u8] = b"";
pub type Result<'a, Output> = std::result::Result<(&'a str, Output), &'a str>;
pub trait Parser<'a, Output> {