diff --git a/Cargo.lock b/Cargo.lock index 70ddf9c62..8ee11e505 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -12,6 +12,15 @@ version = "0.3.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a4c527152e37cf757a3f78aae5a06fbeefdb07ccc535c980a3208ee3060dd544" +[[package]] +name = "arrayvec" +version = "0.4.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cd9fd44efafa8690358b7408d253adf110036b88f55672a933f01d616ad9b1b9" +dependencies = [ + "nodrop", +] + [[package]] name = "arrayvec" version = "0.5.1" @@ -59,7 +68,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d8fb2d74254a3a0b5cac33ac9f8ed0e44aa50378d9dbb2e5d83bd21ed1dc2c8a" dependencies = [ "arrayref", - "arrayvec", + "arrayvec 0.5.1", "constant_time_eq", ] @@ -639,6 +648,19 @@ version = "1.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b294d6fa9ee409a054354afc4352b0b9ef7ca222c69b8812cbea9e7d2bf3783f" +[[package]] +name = "lexical-core" +version = "0.6.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d7043aa5c05dd34fb73b47acb8c3708eac428de4545ea3682ed2f11293ebd890" +dependencies = [ + "arrayvec 0.4.12", + "cfg-if", + "rustc_version", + "ryu", + "static_assertions", +] + [[package]] name = "libc" version = "0.2.71" @@ -750,7 +772,7 @@ dependencies = [ "linkify", "melib", "nix", - "nom", + "nom 3.2.1", "notify", "notify-rust", "pcre2", @@ -785,7 +807,7 @@ dependencies = [ "memmap", "native-tls", "nix", - "nom", + "nom 5.1.1", "notify", "notify-rust", "reqwest", @@ -937,6 +959,12 @@ dependencies = [ "void", ] +[[package]] +name = "nodrop" +version = "0.1.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "72ef4a56884ca558e5ddb05a1d1e7e1bfd9a68d9ed024c21704cc98872dae1bb" + [[package]] name = "nom" version = "3.2.1" @@ -946,6 +974,17 @@ dependencies = [ "memchr 1.0.2", ] +[[package]] +name = "nom" +version = "5.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0b471253da97532da4b61552249c521e01e736071f71c1a4f7ebbfbf0a06aad6" +dependencies = [ + "lexical-core", + "memchr 2.3.3", + "version_check", +] + [[package]] name = "notify" version = "4.0.15" @@ -1339,6 +1378,15 @@ dependencies = [ "crossbeam-utils", ] +[[package]] +name = "rustc_version" +version = "0.2.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "138e3e0acb6c9fb258b19b67cb8abd63c00679d2851805ea151465464fe9030a" +dependencies = [ + "semver", +] + [[package]] name = "ryu" version = "1.0.4" @@ -1393,6 +1441,21 @@ dependencies = [ "libc", ] +[[package]] +name = "semver" +version = "0.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1d7eb9ef2c18661902cc47e535f9bc51b78acd254da71d375c2f6720d9a40403" +dependencies = [ + "semver-parser", +] + +[[package]] +name = "semver-parser" +version = "0.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "388a1df253eca08550bef6c72392cfe7c30914bf41df5269b68cbd6ff8f570a3" + [[package]] name = "serde" version = "1.0.110" @@ -1480,6 +1543,12 @@ dependencies = [ "serde", ] +[[package]] +name = "static_assertions" +version = "0.3.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7f3eb36b47e512f8f1c9e3d10c2c1965bc992bd9cdb024fa581e2194501c83d3" + [[package]] name = "syn" version = "1.0.27" diff --git a/melib/Cargo.toml b/melib/Cargo.toml index 9f238fd5f..fa4f77cd4 100644 --- a/melib/Cargo.toml +++ b/melib/Cargo.toml @@ -23,7 +23,8 @@ crossbeam = "0.7.2" data-encoding = "2.1.1" encoding = "0.2.33" memmap = { version = "0.5.2", optional = true } -nom = "3.2.0" +nom = { version = "5.1.1" } + notify = { version = "4.0.1", optional = true } notify-rust = { version = "^3", optional = true } termion = "1.5.1" diff --git a/melib/src/backends/imap.rs b/melib/src/backends/imap.rs index 46fd2ecdb..d08a6832c 100644 --- a/melib/src/backends/imap.rs +++ b/melib/src/backends/imap.rs @@ -533,11 +533,11 @@ impl MailBackend for ImapType { Ok(()) } - fn as_any(&self) -> &dyn ::std::any::Any { + fn as_any(&self) -> &dyn::std::any::Any { self } - fn as_any_mut(&mut self) -> &mut dyn ::std::any::Any { + fn as_any_mut(&mut self) -> &mut dyn::std::any::Any { self } @@ -967,7 +967,7 @@ impl ImapType { lines.next_back(); for l in lines.map(|l| l.trim()) { if let Ok(mut mailbox) = - protocol_parser::list_mailbox_result(l.as_bytes()).to_full_result() + protocol_parser::list_mailbox_result(l.as_bytes()).map(|(_, v)| v) { if let Some(parent) = mailbox.parent { if mailboxes.contains_key(&parent) { @@ -1007,7 +1007,7 @@ impl ImapType { lines.next_back(); for l in lines.map(|l| l.trim()) { if let Ok(subscription) = - protocol_parser::list_mailbox_result(l.as_bytes()).to_full_result() + protocol_parser::list_mailbox_result(l.as_bytes()).map(|(_, v)| v) { if let Some(f) = mailboxes.get_mut(&subscription.hash()) { if subscription.no_select { diff --git a/melib/src/backends/imap/connection.rs b/melib/src/backends/imap/connection.rs index 2fc7cc6d9..51236d27a 100644 --- a/melib/src/backends/imap/connection.rs +++ b/melib/src/backends/imap/connection.rs @@ -195,8 +195,8 @@ impl ImapStream { .ok_or_else(|| MeliError::new("")) .and_then(|res| { protocol_parser::capabilities(res.as_bytes()) - .to_full_result() .map_err(|_| MeliError::new("")) + .map(|(_, v)| v) }); if capabilities.is_err() { @@ -241,8 +241,7 @@ impl ImapStream { for l in res.split_rn() { if l.starts_with("* CAPABILITY") { capabilities = protocol_parser::capabilities(l.as_bytes()) - .to_full_result() - .map(|capabilities| { + .map(|(_, capabilities)| { HashSet::from_iter(capabilities.into_iter().map(|s: &[u8]| s.to_vec())) }) .ok(); @@ -269,7 +268,7 @@ impl ImapStream { drop(capabilities); ret.send_command(b"CAPABILITY")?; ret.read_response(&mut res).unwrap(); - let capabilities = protocol_parser::capabilities(res.as_bytes()).to_full_result()?; + let capabilities = protocol_parser::capabilities(res.as_bytes())?.1; let capabilities = HashSet::from_iter(capabilities.into_iter().map(|s| s.to_vec())); Ok((capabilities, ret)) } else { diff --git a/melib/src/backends/imap/managesieve.rs b/melib/src/backends/imap/managesieve.rs index 0f731fc24..470d98d93 100644 --- a/melib/src/backends/imap/managesieve.rs +++ b/melib/src/backends/imap/managesieve.rs @@ -23,48 +23,53 @@ use super::{ImapConnection, ImapProtocol, ImapServerConf, UIDStore}; use crate::conf::AccountSettings; use crate::error::{MeliError, Result}; use crate::get_conf_val; -use nom::IResult; +use nom::{ + branch::alt, bytes::complete::tag, combinator::map, error::ErrorKind, + multi::separated_nonempty_list, sequence::separated_pair, IResult, +}; use std::str::FromStr; use std::sync::{Arc, Mutex}; use std::time::Instant; -named!( - pub managesieve_capabilities>, - do_parse!( - ret: separated_nonempty_list_complete!(tag!(b"\r\n"), alt_complete!(separated_pair!(quoted_raw, tag!(b" "), quoted_raw) | map!(quoted_raw, |q| (q, &b""[..])))) - >> opt!(tag!("\r\n")) - >> ({ ret }) - ) - ); +pub fn managesieve_capabilities(input: &[u8]) -> Result> { + let (_, ret) = separated_nonempty_list( + tag(b"\r\n"), + alt(( + separated_pair(quoted_raw, tag(b" "), quoted_raw), + map(quoted_raw, |q| (q, &b""[..])), + )), + )(input)?; + Ok(ret) +} #[test] fn test_managesieve_capabilities() { - assert_eq!(managesieve_capabilities(b"\"IMPLEMENTATION\" \"Dovecot Pigeonhole\"\r\n\"SIEVE\" \"fileinto reject envelope encoded-character vacation subaddress comparator-i;ascii-numeric relational regex imap4flags copy include variables body enotify environment mailbox date index ihave duplicate mime foreverypart extracttext\"\r\n\"NOTIFY\" \"mailto\"\r\n\"SASL\" \"PLAIN\"\r\n\"STARTTLS\"\r\n\"VERSION\" \"1.0\"\r\n").to_full_result(), Ok(vec![ -(&b"IMPLEMENTATION"[..],&b"Dovecot Pigeonhole"[..]), -(&b"SIEVE"[..],&b"fileinto reject envelope encoded-character vacation subaddress comparator-i;ascii-numeric relational regex imap4flags copy include variables body enotify environment mailbox date index ihave duplicate mime foreverypart extracttext"[..]), -(&b"NOTIFY"[..],&b"mailto"[..]), -(&b"SASL"[..],&b"PLAIN"[..]), -(&b"STARTTLS"[..], &b""[..]), -(&b"VERSION"[..],&b"1.0"[..])]) + assert_eq!(managesieve_capabilities(b"\"IMPLEMENTATION\" \"Dovecot Pigeonhole\"\r\n\"SIEVE\" \"fileinto reject envelope encoded-character vacation subaddress comparator-i;ascii-numeric relational regex imap4flags copy include variables body enotify environment mailbox date index ihave duplicate mime foreverypart extracttext\"\r\n\"NOTIFY\" \"mailto\"\r\n\"SASL\" \"PLAIN\"\r\n\"STARTTLS\"\r\n\"VERSION\" \"1.0\"\r\n").unwrap(), vec![ + (&b"IMPLEMENTATION"[..],&b"Dovecot Pigeonhole"[..]), + (&b"SIEVE"[..],&b"fileinto reject envelope encoded-character vacation subaddress comparator-i;ascii-numeric relational regex imap4flags copy include variables body enotify environment mailbox date index ihave duplicate mime foreverypart extracttext"[..]), + (&b"NOTIFY"[..],&b"mailto"[..]), + (&b"SASL"[..],&b"PLAIN"[..]), + (&b"STARTTLS"[..], &b""[..]), + (&b"VERSION"[..],&b"1.0"[..])] -); + ); } // Return a byte sequence surrounded by "s and decoded if necessary pub fn quoted_raw(input: &[u8]) -> IResult<&[u8], &[u8]> { if input.is_empty() || input[0] != b'"' { - return IResult::Error(nom::ErrorKind::Custom(0)); + return Err(nom::Err::Error((input, ErrorKind::Tag))); } let mut i = 1; while i < input.len() { if input[i] == b'\"' && input[i - 1] != b'\\' { - return IResult::Done(&input[i + 1..], &input[1..i]); + return Ok((&input[i + 1..], &input[1..i])); } i += 1; } - return IResult::Error(nom::ErrorKind::Custom(0)); + Err(nom::Err::Error((input, ErrorKind::Tag))) } pub trait ManageSieve { diff --git a/melib/src/backends/imap/operations.rs b/melib/src/backends/imap/operations.rs index 165537cb0..2d61a659f 100644 --- a/melib/src/backends/imap/operations.rs +++ b/melib/src/backends/imap/operations.rs @@ -142,7 +142,7 @@ impl BackendOp for ImapOp { response.lines().collect::>().len() ); match protocol_parser::uid_fetch_flags_response(response.as_bytes()) - .to_full_result() + .map(|(_, v)| v) .map_err(MeliError::from) { Ok(v) => { @@ -181,7 +181,7 @@ impl BackendOp for ImapOp { conn.read_response(&mut response, RequiredResponses::STORE_REQUIRED)?; debug!(&response); match protocol_parser::uid_fetch_flags_response(response.as_bytes()) - .to_full_result() + .map(|(_, v)| v) .map_err(MeliError::from) { Ok(v) => { @@ -215,7 +215,7 @@ impl BackendOp for ImapOp { )?; conn.read_response(&mut response, RequiredResponses::STORE_REQUIRED)?; protocol_parser::uid_fetch_flags_response(response.as_bytes()) - .to_full_result() + .map(|(_, v)| v) .map_err(MeliError::from)?; let hash = tag_hash!(tag); if value { diff --git a/melib/src/backends/imap/protocol_parser.rs b/melib/src/backends/imap/protocol_parser.rs index 5a656fa8e..a4a406428 100644 --- a/melib/src/backends/imap/protocol_parser.rs +++ b/melib/src/backends/imap/protocol_parser.rs @@ -22,7 +22,16 @@ use super::*; use crate::email::parser::BytesExt; use crate::get_path_hash; -use nom::{digit, is_digit, rest, IResult}; +use nom::{ + branch::{alt, permutation}, + bytes::complete::{is_a, is_not, tag, take, take_until, take_while}, + character::complete::digit1, + character::is_digit, + combinator::{map, map_res, opt, rest}, + multi::{length_data, many0, many1, separated_list, separated_nonempty_list}, + sequence::{delimited, preceded}, + IResult, +}; use std::str::FromStr; bitflags! { @@ -277,7 +286,7 @@ macro_rules! to_str ( ($v:expr) => (unsafe{ std::str::from_utf8_unchecked($v) }) ); -macro_rules! dbg_dmp ( +/*macro_rules! dbg_dmp ( ($i: expr, $submac:ident!( $($args:tt)* )) => ( { let l = line!(); @@ -299,13 +308,59 @@ macro_rules! dbg_dmp ( dbg_dmp!($i, call!($f)); ); ); +*/ /* * LIST (\HasNoChildren) "." INBOX.Sent * LIST (\HasChildren) "." INBOX */ -named!( - pub list_mailbox_result, + +pub fn list_mailbox_result(input: &[u8]) -> IResult<&[u8], ImapMailbox> { + let (input, _) = alt((tag("* LIST ("), tag("* LSUB (")))(input.ltrim())?; + let (input, properties) = take_until(&b")"[0..])(input)?; + let (input, _) = tag(b") ")(input)?; + let (input, separator) = delimited(tag(b"\""), take(1_u32), tag(b"\""))(input)?; + let (input, _) = take(1_u32)(input)?; + let (input, path) = alt((delimited(tag("\""), is_not("\""), tag("\"")), rest))(input)?; + Ok(( + input, + ({ + let separator: u8 = separator[0]; + let mut f = ImapMailbox::default(); + f.no_select = false; + f.is_subscribed = false; + for p in properties.split(|&b| b == b' ') { + use crate::backends::SpecialUsageMailbox; + if p.eq_ignore_ascii_case(b"\\NoSelect") { + f.no_select = true; + } else if p.eq_ignore_ascii_case(b"\\Sent") { + let _ = f.set_special_usage(SpecialUsageMailbox::Sent); + } else if p.eq_ignore_ascii_case(b"\\Junk") { + let _ = f.set_special_usage(SpecialUsageMailbox::Trash); + } else if p.eq_ignore_ascii_case(b"\\Drafts") { + let _ = f.set_special_usage(SpecialUsageMailbox::Drafts); + } + } + f.imap_path = String::from_utf8_lossy(path).into(); + f.hash = get_path_hash!(&f.imap_path); + f.path = if separator == b'/' { + f.imap_path.clone() + } else { + f.imap_path.replace(separator as char, "/") + }; + f.name = if let Some(pos) = f.imap_path.as_bytes().iter().rposition(|&c| c == separator) + { + f.parent = Some(get_path_hash!(&f.imap_path[..pos])); + f.imap_path[pos + 1..].to_string() + } else { + f.imap_path.clone() + }; + f.separator = separator; + + debug!(f) + }), + )) + /* do_parse!( ws!(alt_complete!(tag!("* LIST (") | tag!("* LSUB ("))) >> properties: take_until!(&b")"[0..]) @@ -348,42 +403,74 @@ named!( debug!(f) }) ) -); + */ + +} -named!( - my_flags, - do_parse!( - flags: separated_list!(tag!(" "), preceded!(tag!("\\"), is_not!(")"))) - >> ({ - let mut ret = Flag::default(); - for f in flags { - match f { - b"Answered" => { - ret.set(Flag::REPLIED, true); - } - b"Flagged" => { - ret.set(Flag::FLAGGED, true); - } - b"Deleted" => { - ret.set(Flag::TRASHED, true); - } - b"Seen" => { - ret.set(Flag::SEEN, true); - } - b"Draft" => { - ret.set(Flag::DRAFT, true); - } - f => { - debug!("unknown Flag token value: {}", unsafe { - std::str::from_utf8_unchecked(f) - }); +pub fn my_flags(input: &[u8]) -> IResult<&[u8], Flag> { + let (input, flags) = separated_list(tag(" "), preceded(tag("\\"), is_not(")")))(input)?; + let mut ret = Flag::default(); + for f in flags { + match f { + b"Answered" => { + ret.set(Flag::REPLIED, true); + } + b"Flagged" => { + ret.set(Flag::FLAGGED, true); + } + b"Deleted" => { + ret.set(Flag::TRASHED, true); + } + b"Seen" => { + ret.set(Flag::SEEN, true); + } + b"Draft" => { + ret.set(Flag::DRAFT, true); + } + f => { + debug!("unknown Flag token value: {}", unsafe { + std::str::from_utf8_unchecked(f) + }); + } + } + } + Ok((input, ret)) + + /* + my_flags, + do_parse!( + flags: separated_list!(tag!(" "), preceded!(tag!("\\"), is_not!(")"))) + >> ({ + let mut ret = Flag::default(); + for f in flags { + match f { + b"Answered" => { + ret.set(Flag::REPLIED, true); + } + b"Flagged" => { + ret.set(Flag::FLAGGED, true); + } + b"Deleted" => { + ret.set(Flag::TRASHED, true); + } + b"Seen" => { + ret.set(Flag::SEEN, true); + } + b"Draft" => { + ret.set(Flag::DRAFT, true); + } + f => { + debug!("unknown Flag token value: {}", unsafe { + std::str::from_utf8_unchecked(f) + }); + } } } - } - ret - }) - ) -); + ret + }) + ) + */ +} #[derive(Debug)] pub struct UidFetchResponse<'a> { @@ -459,7 +546,10 @@ pub fn uid_fetch_response(input: &str) -> ImapParseResult> if input[i..].starts_with("UID ") { i += "UID ".len(); - if let IResult::Done(rest, uid) = take_while!(input[i..].as_bytes(), call!(is_digit)) { + if let Ok((rest, uid)) = take_while::<_, &[u8], (&[u8], nom::error::ErrorKind)>( + is_digit, + )(input[i..].as_bytes()) + { i += input.len() - i - rest.len(); ret.uid = usize::from_str(unsafe { std::str::from_utf8_unchecked(uid) }).unwrap(); } else { @@ -470,7 +560,7 @@ pub fn uid_fetch_response(input: &str) -> ImapParseResult> } } else if input[i..].starts_with("FLAGS (") { i += "FLAGS (".len(); - if let IResult::Done(rest, flags) = flags(&input[i..]) { + if let Ok((rest, flags)) = flags(&input[i..]) { ret.flags = Some(flags); i += (input.len() - i - rest.len()) + 1; } else { @@ -481,16 +571,15 @@ pub fn uid_fetch_response(input: &str) -> ImapParseResult> } } else if input[i..].starts_with("RFC822 {") { i += "RFC822 ".len(); - if let IResult::Done(rest, body) = length_bytes!( - input[i..].as_bytes(), - delimited!( - tag!("{"), - map_res!(digit, |s| { + if let Ok((rest, body)) = + length_data::<_, _, (&[u8], nom::error::ErrorKind), _>(delimited( + tag("{"), + map_res(digit1, |s| { usize::from_str(unsafe { std::str::from_utf8_unchecked(s) }) }), - tag!("}\r\n") - ) - ) { + tag("}\r\n"), + ))(input[i..].as_bytes()) + { ret.body = Some(body); i += input.len() - i - rest.len(); } else { @@ -501,7 +590,7 @@ pub fn uid_fetch_response(input: &str) -> ImapParseResult> } } else if input[i..].starts_with("ENVELOPE (") { i += "ENVELOPE ".len(); - if let IResult::Done(rest, envelope) = envelope(input[i..].as_bytes()) { + if let Ok((rest, envelope)) = envelope(input[i..].as_bytes()) { ret.envelope = Some(envelope); i += input.len() - i - rest.len(); } else { @@ -596,34 +685,64 @@ pub fn uid_fetch_responses(mut input: &str) -> ImapParseResult)>, &[u8])>>, - many0!( - do_parse!( - tag!("* ") - >> take_while!(call!(is_digit)) - >> tag!(" FETCH (") - >> result: permutation!(preceded!(ws!(tag!("UID ")), map_res!(digit, |s| { usize::from_str(unsafe { std::str::from_utf8_unchecked(s) }) })), opt!(preceded!(ws!(tag!("FLAGS ")), delimited!(tag!("("), byte_flags, tag!(")")))), - ws!(length_bytes!(delimited!(tag!("{"), map_res!(digit, |s| { usize::from_str(unsafe { std::str::from_utf8_unchecked(s) }) }), tag!("}\r\n"))))) - >> tag!(")\r\n") - >> ((result.0, result.1, result.2)) - ) - ) -); +pub fn uid_fetch_response_( + input: &[u8], +) -> IResult<&[u8], Vec<(usize, Option<(Flag, Vec)>, &[u8])>> { + many0( + |input| -> IResult<&[u8], (usize, Option<(Flag, Vec)>, &[u8])> { + let (input, _) = tag("* ")(input)?; + let (input, _) = take_while(is_digit)(input)?; + let (input, result) = permutation(( + preceded( + tag("UID "), + map_res(digit1, |s| { + usize::from_str(unsafe { std::str::from_utf8_unchecked(s) }) + }), + ), + opt(preceded( + tag("FLAGS "), + delimited(tag("("), byte_flags, tag(")")), + )), + length_data(delimited( + tag("{"), + map_res(digit1, |s| { + usize::from_str(unsafe { std::str::from_utf8_unchecked(s) }) + }), + tag("}\r\n"), + )), + ))(input.ltrim())?; + let (input, _) = tag(")\r\n")(input)?; + Ok((input, (result.0, result.1, result.2))) + }, + )(input) +} -named!( - pub uid_fetch_flags_response))>>, - many0!( - do_parse!( - tag!("* ") - >> take_while!(call!(is_digit)) - >> tag!(" FETCH (") - >> uid_flags: permutation!(preceded!(ws!(tag!("UID ")), map_res!(digit, |s| { usize::from_str(unsafe { std::str::from_utf8_unchecked(s) }) })), preceded!(ws!(tag!("FLAGS ")), delimited!(tag!("("), byte_flags, tag!(")")))) - >> tag!(")\r\n") - >> ((uid_flags.0, uid_flags.1)) +pub fn uid_fetch_flags_response(input: &[u8]) -> IResult<&[u8], Vec<(usize, (Flag, Vec))>> { + many0(|input| -> IResult<&[u8], (usize, (Flag, Vec))> { + let (input, _) = tag("* ")(input)?; + let (input, _) = take_while(is_digit)(input)?; + let (input, _) = tag(" FETCH ( ")(input)?; + let (input, uid_flags) = permutation(( + preceded( + tag("UID "), + map_res(digit1, |s| { + usize::from_str(unsafe { std::str::from_utf8_unchecked(s) }) + }), + ), + preceded(tag("FLAGS "), delimited(tag("("), byte_flags, tag(")"))), + ))(input.ltrim())?; + let (input, _) = tag(")\r\n")(input)?; + Ok((input, (uid_flags.0, uid_flags.1))) + })(input) + + /* + >> uid_flags: permutation!(preceded!(ws!(tag!("UID ")), map_res!(digit1, |s| { usize::from_str(unsafe { std::str::from_utf8_unchecked(s) }) })), preceded!(ws!(tag!("FLAGS ")), delimited!(tag!("("), byte_flags, tag!(")")))) + >> tag!(")\r\n") + >> ((uid_flags.0, uid_flags.1)) + ) ) - ) -); + */ +} macro_rules! flags_to_imap_list { ($flags:ident) => {{ @@ -667,8 +786,15 @@ macro_rules! flags_to_imap_list { * "* CAPABILITY IMAP4rev1 LITERAL+ SASL-IR LOGIN-REFERRALS ID ENABLE IDLE AUTH=PLAIN\r\n" */ -named!( - pub capabilities>, +pub fn capabilities(input: &[u8]) -> IResult<&[u8], Vec<&[u8]>> { + let (input, _) = take_until("CAPABILITY ")(input)?; + let (input, _) = tag("CAPABILITY ")(input)?; + let (input, ret) = separated_nonempty_list(tag(" "), is_not(" ]\r\n"))(input)?; + let (input, _) = take_until("\r\n")(input)?; + let (input, _) = tag("\r\n")(input)?; + Ok((input, ret)) + /* + pub capabilities<>, do_parse!( take_until!("CAPABILITY ") >> tag!("CAPABILITY ") @@ -677,7 +803,8 @@ named!( >> tag!("\r\n") >> ({ ret }) ) -); + */ +} /// This enum represents the server's untagged responses detailed in `7. Server Responses` of RFC 3501 INTERNET MESSAGE ACCESS PROTOCOL - VERSION 4rev1 pub enum UntaggedResponse { @@ -746,62 +873,130 @@ pub enum UntaggedResponse { }, } -named!( - pub untagged_responses>, - do_parse!( - tag!("* ") - >> num: map_res!(digit, |s| { usize::from_str(unsafe { std::str::from_utf8_unchecked(s) }) }) - >> tag!(" ") - >> tag: take_until!("\r\n") - >> tag!("\r\n") - >> ({ - use UntaggedResponse::*; - match tag { - b"EXPUNGE" => Some(Expunge(num)), - b"EXISTS" => Some(Exists(num)), - b"RECENT" => Some(Recent(num)), - _ if tag.starts_with(b"FETCH ") => flags( -unsafe { std::str::from_utf8_unchecked(&tag[b"FETCH (FLAGS (".len()..]) }).map(|flags| Fetch(num, flags)).to_full_result().map_err(|err| debug!("untagged_response malformed fetch: {}", unsafe { std::str::from_utf8_unchecked(tag) })).ok(), - _ => { - debug!("unknown untagged_response: {}", unsafe { std::str::from_utf8_unchecked(tag) }); - None - } +pub fn untagged_responses(input: &[u8]) -> IResult<&[u8], Option> { + let (input, _) = tag("* ")(input)?; + let (input, num) = map_res(digit1, |s| { + usize::from_str(unsafe { std::str::from_utf8_unchecked(s) }) + })(input)?; + let (input, _) = tag(" ")(input)?; + let (input, _tag) = take_until("\r\n")(input)?; + let (input, _) = tag("\r\n")(input)?; + Ok((input, { + use UntaggedResponse::*; + match _tag { + b"EXPUNGE" => Some(Expunge(num)), + b"EXISTS" => Some(Exists(num)), + b"RECENT" => Some(Recent(num)), + _ if _tag.starts_with(b"FETCH ") => { + flags(unsafe { std::str::from_utf8_unchecked(&_tag[b"FETCH (FLAGS (".len()..]) }) + .map(|(_, flags)| Fetch(num, flags)) + .map_err(|err| { + debug!( + "untagged_response malformed fetch: {} {}", + unsafe { std::str::from_utf8_unchecked(_tag) }, + err + ) + }) + .ok() } - }) - ) -); + _ => { + debug!("unknown untagged_response: {}", unsafe { + std::str::from_utf8_unchecked(_tag) + }); + None + } + } + })) + /* + pub untagged_responses<>, + do_parse!( + tag!("* ") + >> num: map_res!(digit1, |s| { usize::from_str(unsafe { std::str::from_utf8_unchecked(s) }) }) + >> tag!(" ") + >> tag: take_until!("\r\n") + >> tag!("\r\n") + >> ({ + use UntaggedResponse::*; + match tag { + b"EXPUNGE" => Some(Expunge(num)), + b"EXISTS" => Some(Exists(num)), + b"RECENT" => Some(Recent(num)), + _ if tag.starts_with(b"FETCH ") => flags( + unsafe { std::str::from_utf8_unchecked(&tag[b"FETCH (FLAGS (".len()..]) }).map(|flags| Fetch(num, flags)).map_err(|err| debug!("untagged_response malformed fetch: {}", unsafe { std::str::from_utf8_unchecked(tag) })).ok(), + _ => { + debug!("unknown untagged_response: {}", unsafe { std::str::from_utf8_unchecked(tag) }); + None + } + } + }) + ) + */ +} -named!( - pub search_results>, +pub fn search_results<'a>(input: &'a [u8]) -> IResult<&'a [u8], Vec> { + alt(( + |input: &'a [u8]| -> IResult<&'a [u8], Vec> { + let (input, _) = tag("* SEARCH ")(input)?; + let (input, list) = separated_nonempty_list( + tag(b" "), + map_res(is_not(" \r\n"), |s: &[u8]| { + usize::from_str(unsafe { std::str::from_utf8_unchecked(s) }) + }), + )(input)?; + let (input, _) = tag("\r\n")(input)?; + Ok((input, list)) + }, + |input: &'a [u8]| -> IResult<&'a [u8], Vec> { + let (input, _) = tag("* SEARCH\r\n")(input)?; + Ok((input, vec![])) + }, + ))(input) + /* alt_complete!(do_parse!( tag!("* SEARCH ") >> list: separated_nonempty_list_complete!(tag!(b" "), map_res!(is_not!(" \r\n"), |s: &[u8]| { usize::from_str(unsafe { std::str::from_utf8_unchecked(s) }) })) >> tag!("\r\n") >> ({ list })) | do_parse!(tag!("* SEARCH\r\n") >> ({ Vec::new() }))) -); + */ +} -named!( - pub search_results_raw<&[u8]>, - alt_complete!(do_parse!( tag!("* SEARCH ") - >> list: take_until!("\r\n") - >> tag!("\r\n") - >> ({ list })) | - do_parse!(tag!("* SEARCH\r\n") >> ({ &b""[0..] }))) -); +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)?; + Ok((input, list)) + }, + |input: &'a [u8]| -> IResult<&'a [u8], &'a [u8]> { + let (input, _) = tag("* SEARCH\r\n")(input)?; + Ok((input, &b""[0..])) + }, + ))(input) + /* + pub search_results_raw<&[u8]>, + alt_complete!(do_parse!( tag!("* SEARCH ") + >> list: take_until!("\r\n") + >> tag!("\r\n") + >> ({ list })) | + do_parse!(tag!("* SEARCH\r\n") >> ({ &b""[0..] }))) + ); + */ +} #[test] fn test_imap_search() { - assert_eq!(search_results(b"* SEARCH\r\n").to_full_result(), Ok(vec![])); + assert_eq!(search_results(b"* SEARCH\r\n").map(|(_, v)| v), Ok(vec![])); assert_eq!( - search_results(b"* SEARCH 1\r\n").to_full_result(), + search_results(b"* SEARCH 1\r\n").map(|(_, v)| v), Ok(vec![1]) ); assert_eq!( - search_results(b"* SEARCH 1 2 3 4\r\n").to_full_result(), + search_results(b"* SEARCH 1 2 3 4\r\n").map(|(_, v)| v), Ok(vec![1, 2, 3, 4]) ); assert_eq!( - search_results_raw(b"* SEARCH 1 2 3 4\r\n").to_full_result(), + search_results_raw(b"* SEARCH 1 2 3 4\r\n").map(|(_, v)| v), Ok(&b"1 2 3 4"[..]) ); } @@ -851,7 +1046,7 @@ pub fn select_response(input: &str) -> Result { } else if l.starts_with("* ") && l.ends_with(" RECENT") { ret.recent = usize::from_str(&l["* ".len()..l.len() - " RECENT".len()])?; } else if l.starts_with("* FLAGS (") { - ret.flags = flags(&l["* FLAGS (".len()..l.len() - ")".len()]).to_full_result()?; + ret.flags = flags(&l["* FLAGS (".len()..l.len() - ")".len()]).map(|(_, v)| v)?; } else if l.starts_with("* OK [UNSEEN ") { ret.unseen = usize::from_str(&l["* OK [UNSEEN ".len()..l.find(']').unwrap()])?; } else if l.starts_with("* OK [UIDVALIDITY ") { @@ -862,7 +1057,7 @@ pub fn select_response(input: &str) -> Result { } else if l.starts_with("* OK [PERMANENTFLAGS (") { ret.permanentflags = flags(&l["* OK [PERMANENTFLAGS (".len()..l.find(')').unwrap()]) - .to_full_result()?; + .map(|(_, v)| v)?; ret.can_create_flags = l.contains("\\*"); } else if l.contains("OK [READ-WRITE]") { ret.read_only = false; @@ -919,15 +1114,16 @@ pub fn flags(input: &str) -> IResult<&str, (Flag, Vec)> { input = &input[match_end..]; input = input.trim_start(); } - IResult::Done(input, (ret, keywords)) + Ok((input, (ret, keywords))) } pub fn byte_flags(input: &[u8]) -> IResult<&[u8], (Flag, Vec)> { let i = unsafe { std::str::from_utf8_unchecked(input) }; match flags(i) { - IResult::Done(rest, ret) => IResult::Done(rest.as_bytes(), ret), - IResult::Error(e) => IResult::Error(e), - IResult::Incomplete(e) => IResult::Incomplete(e), + Ok((rest, ret)) => Ok((rest.as_bytes(), ret)), + Err(nom::Err::Error((_, err))) => Err(nom::Err::Error((input, err))), + Err(nom::Err::Failure((_, err))) => Err(nom::Err::Error((input, err))), + Err(nom::Err::Incomplete(_)) => Err(nom::Err::Error((input, nom::error::ErrorKind::Tag))), } } @@ -957,8 +1153,71 @@ pub fn byte_flags(input: &[u8]) -> IResult<&[u8], (Flag, Vec)> { * "") */ -named!( - pub envelope, +pub fn envelope(input: &[u8]) -> IResult<&[u8], Envelope> { + let (input, _) = tag("(")(input)?; + let (input, _) = opt(is_a("\r\n\t "))(input)?; + let (input, date) = quoted_or_nil(input)?; + let (input, _) = opt(is_a("\r\n\t "))(input)?; + let (input, subject) = quoted_or_nil(input)?; + let (input, _) = opt(is_a("\r\n\t "))(input)?; + let (input, from) = envelope_addresses(input)?; + let (input, _) = opt(is_a("\r\n\t "))(input)?; + let (input, _sender) = envelope_addresses(input)?; + let (input, _) = opt(is_a("\r\n\t "))(input)?; + let (input, _reply_to) = envelope_addresses(input)?; + let (input, _) = opt(is_a("\r\n\t "))(input)?; + let (input, to) = envelope_addresses(input)?; + let (input, _) = opt(is_a("\r\n\t "))(input)?; + let (input, cc) = envelope_addresses(input)?; + let (input, _) = opt(is_a("\r\n\t "))(input)?; + let (input, bcc) = envelope_addresses(input)?; + let (input, _) = opt(is_a("\r\n\t "))(input)?; + let (input, in_reply_to) = quoted_or_nil(input)?; + let (input, _) = opt(is_a("\r\n\t "))(input)?; + let (input, message_id) = quoted_or_nil(input)?; + let (input, _) = opt(is_a("\r\n\t "))(input)?; + let (input, _) = tag(")")(input)?; + Ok(( + input, + ({ + let mut env = Envelope::new(0); + if let Some(date) = date { + env.set_date(&date); + if let Ok(d) = crate::email::parser::generic::date(env.date_as_str().as_bytes()) { + env.set_datetime(d); + } + } + + if let Some(subject) = subject { + env.set_subject(subject.to_vec()); + } + + if let Some(from) = from { + env.set_from(from); + } + if let Some(to) = to { + env.set_to(to); + } + + if let Some(cc) = cc { + env.set_cc(cc); + } + + if let Some(bcc) = bcc { + env.set_bcc(bcc); + } + if let Some(in_reply_to) = in_reply_to { + env.set_in_reply_to(&in_reply_to); + env.push_references(&in_reply_to); + } + + if let Some(message_id) = message_id { + env.set_message_id(&message_id); + } + env + }), + )) + /* do_parse!( tag!("(") >> opt!(is_a!("\r\n\t ")) @@ -987,7 +1246,7 @@ named!( let mut env = Envelope::new(0); if let Some(date) = date { env.set_date(&date); - if let Ok(d) = crate::email::parser::date(env.date_as_str().as_bytes()) { + if let Ok(d) = crate::email::parser::generic::date(env.date_as_str().as_bytes()) { env.set_datetime(d); } } @@ -1020,7 +1279,8 @@ named!( } env }) -)); + */ +} /* Helper to build StrBuilder for Address structs */ macro_rules! str_builder { @@ -1033,94 +1293,179 @@ macro_rules! str_builder { } // Parse a list of addresses in the format of the ENVELOPE structure -named!(pub envelope_addresses>>, - alt_complete!(map!(tag!("NIL"), |_| None) | - do_parse!( - tag!("(") - >> envelopes: many1!(delimited!(ws!(tag!("(")), envelope_address, tag!(")"))) - >> tag!(")") - >> ({ - Some(envelopes) - }) - ) -)); +pub fn envelope_addresses<'a>(input: &'a [u8]) -> IResult<&'a [u8], Option>> { + alt(( + map(tag("NIL"), |_| None), + |input: &'a [u8]| -> IResult<&'a [u8], Option>> { + let (input, _) = tag("(")(input)?; + let (input, envelopes) = + many1(delimited(tag("("), envelope_address, tag(")")))(input.ltrim())?; + let (input, _) = tag(")")(input)?; + Ok((input, Some(envelopes))) + }, + ))(input) + /* + alt_complete!(map!(tag!("NIL"), |_| None) | + do_parse!( + tag!("(") + >> envelopes: many1!(delimited!(ws!(tag!("(")), envelope_address, tag!(")"))) + >> tag!(")") + >> ({ + Some(envelopes) + }) + ) + )); + */ +} // Parse an address in the format of the ENVELOPE structure eg // ("Terry Gray" NIL "gray" "cac.washington.edu") -named!( - pub envelope_address
, - do_parse!( - name: alt_complete!(quoted | map!(tag!("NIL"), |_| Vec::new())) - >> is_a!("\r\n\t ") - >> alt_complete!(quoted| map!(tag!("NIL"), |_| Vec::new())) - >> is_a!("\r\n\t ") - >> mailbox_name: dbg_dmp!(alt_complete!(quoted | map!(tag!("NIL"), |_| Vec::new()))) - >> is_a!("\r\n\t ") - >> host_name: alt_complete!(quoted | map!(tag!("NIL"), |_| Vec::new())) - >> ({ +pub fn envelope_address(input: &[u8]) -> IResult<&[u8], Address> { + let (input, name) = alt((quoted, map(tag("NIL"), |_| Vec::new())))(input)?; + let (input, _) = is_a("\r\n\t ")(input)?; + let (input, _) = alt((quoted, map(tag("NIL"), |_| Vec::new())))(input)?; + let (input, _) = is_a("\r\n\t ")(input)?; + let (input, mailbox_name) = alt((quoted, map(tag("NIL"), |_| Vec::new())))(input)?; + let (input, _) = is_a("\r\n\t ")(input)?; + let (input, host_name) = alt((quoted, map(tag("NIL"), |_| Vec::new())))(input)?; + Ok(( + input, Address::Mailbox(MailboxAddress { - raw: format!("{}{}<{}@{}>", to_str!(&name), if name.is_empty() { "" } else { " " }, to_str!(&mailbox_name), to_str!(&host_name)).into_bytes(), + raw: format!( + "{}{}<{}@{}>", + to_str!(&name), + if name.is_empty() { "" } else { " " }, + to_str!(&mailbox_name), + to_str!(&host_name) + ) + .into_bytes(), display_name: str_builder!(0, name.len()), - address_spec: str_builder!(if name.is_empty() { 1 } else { name.len() + 2 }, mailbox_name.len() + host_name.len() + 1), + address_spec: str_builder!( + if name.is_empty() { 1 } else { name.len() + 2 }, + mailbox_name.len() + host_name.len() + 1 + ), + }), + )) + /* + do_parse!( + name: alt_complete!(quoted | map!(tag!("NIL"), |_| Vec::new())) + >> is_a!("\r\n\t ") + >> alt_complete!(quoted| map!(tag!("NIL"), |_| Vec::new())) + >> is_a!("\r\n\t ") + >> mailbox_name: dbg_dmp!(alt_complete!(quoted | map!(tag!("NIL"), |_| Vec::new()))) + >> is_a!("\r\n\t ") + >> host_name: alt_complete!(quoted | map!(tag!("NIL"), |_| Vec::new())) + >> ({ + Address::Mailbox(MailboxAddress { + raw: format!("{}{}<{}@{}>", to_str!(&name), if name.is_empty() { "" } else { " " }, to_str!(&mailbox_name), to_str!(&host_name)).into_bytes(), + display_name: str_builder!(0, name.len()), + address_spec: str_builder!(if name.is_empty() { 1 } else { name.len() + 2 }, mailbox_name.len() + host_name.len() + 1), + }) }) - }) -)); + )); + */ +} // Read a literal ie a byte sequence prefixed with a tag containing its length delimited in {}s -named!(pub literal<&[u8]>,length_bytes!(delimited!(tag!("{"), map_res!(digit, |s| { usize::from_str(unsafe { std::str::from_utf8_unchecked(s) }) }), tag!("}\r\n")))); +pub fn literal(input: &[u8]) -> IResult<&[u8], &[u8]> { + length_data(delimited( + tag("{"), + map_res(digit1, |s| { + usize::from_str(unsafe { std::str::from_utf8_unchecked(s) }) + }), + tag("}\r\n"), + ))(input) +} // Return a byte sequence surrounded by "s and decoded if necessary pub fn quoted(input: &[u8]) -> IResult<&[u8], Vec> { - if let IResult::Done(r, o) = literal(input) { - return match crate::email::parser::phrase(o, false) { - IResult::Done(_, out) => IResult::Done(r, out), + if let Ok((r, o)) = literal(input) { + return match crate::email::parser::encodings::phrase(o, false) { + Ok((_, out)) => Ok((r, out)), e => e, }; } if input.is_empty() || input[0] != b'"' { - return IResult::Error(nom::ErrorKind::Custom(0)); + return Err(nom::Err::Error((input, nom::error::ErrorKind::Tag))); } let mut i = 1; while i < input.len() { if input[i] == b'\"' && input[i - 1] != b'\\' { - return match crate::email::parser::phrase(&input[1..i], false) { - IResult::Done(_, out) => IResult::Done(&input[i + 1..], out), + return match crate::email::parser::encodings::phrase(&input[1..i], false) { + Ok((_, out)) => Ok((&input[i + 1..], out)), e => e, }; } i += 1; } - return IResult::Error(nom::ErrorKind::Custom(0)); + return Err(nom::Err::Error((input, nom::error::ErrorKind::Tag))); } -named!( - pub quoted_or_nil>>, +pub fn quoted_or_nil(input: &[u8]) -> IResult<&[u8], Option>> { + alt((map(tag("NIL"), |_| None), map(quoted, |v| Some(v))))(input.ltrim()) + /* alt_complete!(map!(ws!(tag!("NIL")), |_| None) | map!(quoted, |v| Some(v)))); + */ +} -named!( - pub uid_fetch_envelopes_response)>, Envelope)>>, - many0!( - do_parse!( - tag!("* ") - >> take_while!(call!(is_digit)) - >> tag!(" FETCH (") - >> uid_flags: permutation!(preceded!(ws!(tag!("UID ")), map_res!(digit, |s| { usize::from_str(unsafe { std::str::from_utf8_unchecked(s) }) })), opt!(preceded!(ws!(tag!("FLAGS ")), delimited!(tag!("("), byte_flags, tag!(")"))))) - >> tag!(" ENVELOPE ") - >> env: ws!(envelope) - >> tag!("BODYSTRUCTURE ") - >> bodystructure: take_until!(")\r\n") - >> tag!(")\r\n") - >> ({ +pub fn uid_fetch_envelopes_response( + input: &[u8], +) -> IResult<&[u8], Vec<(usize, Option<(Flag, Vec)>, Envelope)>> { + many0( + |input: &[u8]| -> IResult<&[u8], (usize, Option<(Flag, Vec)>, Envelope)> { + let (input, _) = tag("* ")(input)?; + let (input, _) = take_while(is_digit)(input)?; + let (input, _) = tag(" FETCH (")(input)?; + let (input, uid_flags) = permutation(( + preceded( + tag("UID "), + map_res(digit1, |s| { + usize::from_str(unsafe { std::str::from_utf8_unchecked(s) }) + }), + ), + opt(preceded( + tag("FLAGS "), + delimited(tag("("), byte_flags, tag(")")), + )), + ))(input.ltrim())?; + let (input, _) = tag(" ENVELOPE ")(input)?; + let (input, env) = envelope(input.ltrim())?; + let (input, _) = tag("BODYSTRUCTURE ")(input)?; + let (input, bodystructure) = take_until(")\r\n")(input)?; + let (input, _) = tag(")\r\n")(input)?; + Ok((input, { let mut env = env; let has_attachments = bodystructure_has_attachments(bodystructure); env.set_has_attachments(has_attachments); (uid_flags.0, uid_flags.1, env) - }) + })) + }, + )(input) + /* + many0!( + do_parse!( + tag!("* ") + >> take_while!(call!(is_digit)) + >> tag!(" FETCH (") + >> uid_flags: permutation!(preceded!(ws!(tag!("UID ")), map_res!(digit1, |s| { usize::from_str(unsafe { std::str::from_utf8_unchecked(s) }) })), opt!(preceded!(ws!(tag!("FLAGS ")), delimited!(tag!("("), byte_flags, tag!(")"))))) + >> tag!(" ENVELOPE ") + >> env: ws!(envelope) + >> tag!("BODYSTRUCTURE ") + >> bodystructure: take_until!(")\r\n") + >> tag!(")\r\n") + >> ({ + let mut env = env; + let has_attachments = bodystructure_has_attachments(bodystructure); + env.set_has_attachments(has_attachments); + (uid_flags.0, uid_flags.1, env) + }) + ) ) - ) -); + ); + */ +} pub fn bodystructure_has_attachments(input: &[u8]) -> bool { input.rfind(b" \"mixed\" ").is_some() || input.rfind(b" \"MIXED\" ").is_some() diff --git a/melib/src/backends/imap/untagged.rs b/melib/src/backends/imap/untagged.rs index eaa8a1ef7..30c5dad20 100644 --- a/melib/src/backends/imap/untagged.rs +++ b/melib/src/backends/imap/untagged.rs @@ -62,7 +62,7 @@ impl ImapConnection { let mut response = String::with_capacity(8 * 1024); let untagged_response = - match super::protocol_parser::untagged_responses(line.as_bytes()).to_full_result() { + match super::protocol_parser::untagged_responses(line.as_bytes()).map(|(_, v)| v) { Ok(None) | Err(_) => { return Ok(false); } @@ -162,7 +162,7 @@ impl ImapConnection { self.read_response(&mut response, RequiredResponses::SEARCH) ); match super::protocol_parser::search_results_raw(response.as_bytes()) - .to_full_result() + .map(|(_, v)| v) .map_err(MeliError::from) { Ok(&[]) => { @@ -267,7 +267,7 @@ impl ImapConnection { match super::protocol_parser::search_results( response.split_rn().next().unwrap_or("").as_bytes(), ) - .to_full_result() + .map(|(_, v)| v) { Ok(mut v) => { if let Some(uid) = v.pop() { diff --git a/melib/src/backends/imap/watch.rs b/melib/src/backends/imap/watch.rs index f6a4dec45..374097137 100644 --- a/melib/src/backends/imap/watch.rs +++ b/melib/src/backends/imap/watch.rs @@ -271,7 +271,7 @@ pub fn idle(kit: ImapWatchKit) -> Result<()> { } *uid_store.is_online.lock().unwrap() = (Instant::now(), Ok(())); match protocol_parser::untagged_responses(line.as_slice()) - .to_full_result() + .map(|(_, v)| v) .map_err(MeliError::from) { Ok(Some(Recent(r))) => { @@ -292,7 +292,7 @@ pub fn idle(kit: ImapWatchKit) -> Result<()> { conn.read_response(&mut response, RequiredResponses::SEARCH) ); match protocol_parser::search_results_raw(response.as_bytes()) - .to_full_result() + .map(|(_, v)| v) .map_err(MeliError::from) { Ok(&[]) => { @@ -529,7 +529,7 @@ pub fn idle(kit: ImapWatchKit) -> Result<()> { conn.read_response(&mut response, RequiredResponses::SEARCH) ); match search_results(response.split_rn().next().unwrap_or("").as_bytes()) - .to_full_result() + .map(|(_, v)| v) { Ok(mut v) => { if let Some(uid) = v.pop() { @@ -646,7 +646,7 @@ pub fn examine_updates( conn.read_response(&mut response, RequiredResponses::SEARCH) ); match protocol_parser::search_results_raw(response.as_bytes()) - .to_full_result() + .map(|(_, v)| v) .map_err(MeliError::from) { Ok(&[]) => { diff --git a/melib/src/backends/jmap/objects/email.rs b/melib/src/backends/jmap/objects/email.rs index ab5bbd173..52c0b12ac 100644 --- a/melib/src/backends/jmap/objects/email.rs +++ b/melib/src/backends/jmap/objects/email.rs @@ -231,7 +231,7 @@ impl std::fmt::Display for EmailAddress { impl std::convert::From for crate::Envelope { fn from(mut t: EmailObject) -> crate::Envelope { let mut env = crate::Envelope::new(0); - if let Ok(d) = crate::email::parser::date(env.date_as_str().as_bytes()) { + if let Ok(d) = crate::email::parser::generic::date(env.date_as_str().as_bytes()) { env.set_datetime(d); } if let Some(ref mut sent_at) = t.sent_at { @@ -249,9 +249,9 @@ impl std::convert::From for crate::Envelope { env.push_references(in_reply_to[0].as_bytes()); } if let Some(v) = t.headers.get("References") { - let parse_result = crate::email::parser::references(v.as_bytes()); - if parse_result.is_done() { - for v in parse_result.to_full_result().unwrap() { + let parse_result = crate::email::parser::address::references(v.as_bytes()); + if let Ok((_, v)) = parse_result { + for v in v { env.push_references(v); } } @@ -259,7 +259,7 @@ impl std::convert::From for crate::Envelope { } if let Some(v) = t.headers.get("Date") { env.set_date(v.as_bytes()); - if let Ok(d) = crate::email::parser::date(v.as_bytes()) { + if let Ok(d) = crate::email::parser::generic::date(v.as_bytes()) { env.set_datetime(d); } } diff --git a/melib/src/backends/mbox.rs b/melib/src/backends/mbox.rs index 659e3d485..312373ff0 100644 --- a/melib/src/backends/mbox.rs +++ b/melib/src/backends/mbox.rs @@ -38,7 +38,7 @@ use crate::get_path_hash; use crate::shellexpand::ShellExpandTrait; use libc; use memmap::{Mmap, Protection}; -use nom::{IResult, Needed}; +use nom::{self, error::ErrorKind, IResult}; extern crate notify; use self::notify::{watcher, DebouncedEvent, RecursiveMode, Watcher}; use std::collections::hash_map::{DefaultHasher, HashMap}; @@ -213,7 +213,7 @@ impl BackendOp for MboxOp { return flags; }; - if let Ok(headers) = parser::headers_raw(contents.as_slice()).to_full_result() { + if let Ok((_, headers)) = parser::headers::headers_raw(contents.as_slice()) { if let Some(start) = headers.find(b"Status:") { if let Some(end) = headers[start..].find(b"\n") { let start = start + b"Status:".len(); @@ -275,7 +275,7 @@ pub fn mbox_parse( file_offset: usize, ) -> IResult<&[u8], Vec> { if input.is_empty() { - return IResult::Incomplete(Needed::Unknown); + return Err(nom::Err::Error((input, ErrorKind::Tag))); } let mut input = input; let mut offset = 0; @@ -381,7 +381,7 @@ pub fn mbox_parse( break; } } - return IResult::Done(&[], envelopes); + return Ok((&[], envelopes)); } /// Mbox backend @@ -430,8 +430,8 @@ impl MailBackend for MboxType { }; let payload = mbox_parse(index, contents.as_slice(), 0) - .to_full_result() - .map_err(|e| MeliError::from(e)); + .map_err(|e| MeliError::from(e)) + .map(|(_, v)| v); { let mut mailbox_lock = mailboxes.lock().unwrap(); mailbox_lock @@ -516,13 +516,11 @@ impl MailBackend for MboxType { if contents .starts_with(mailbox_lock[&mailbox_hash].content.as_slice()) { - if let Ok(envelopes) = mbox_parse( + if let Ok((_, envelopes)) = mbox_parse( index.clone(), &contents[mailbox_lock[&mailbox_hash].content.len()..], mailbox_lock[&mailbox_hash].content.len(), - ) - .to_full_result() - { + ) { for env in envelopes { sender.send(RefreshEvent { account_hash, @@ -618,7 +616,7 @@ impl MailBackend for MboxType { Err(MeliError::new("Unimplemented.")) } - fn as_any(&self) -> &dyn ::std::any::Any { + fn as_any(&self) -> &dyn::std::any::Any { self } } diff --git a/melib/src/email.rs b/melib/src/email.rs index 3ba96ddf5..05dc7a256 100644 --- a/melib/src/email.rs +++ b/melib/src/email.rs @@ -33,6 +33,7 @@ mod attachment_types; pub mod attachments; pub use crate::attachments::*; mod address; +//pub mod parser; pub mod parser; use crate::parser::BytesExt; pub use address::*; @@ -243,7 +244,7 @@ impl Envelope { bytes = &bytes[offset + 1..]; } } - let (headers, body) = match parser::mail(bytes).to_full_result() { + let (headers, body) = match parser::mail(bytes) { Ok(v) => v, Err(e) => { debug!("error in parsing mail\n{:?}\n", e); @@ -257,51 +258,50 @@ impl Envelope { self.other_headers.insert( String::from_utf8(name.to_vec()) .unwrap_or_else(|err| String::from_utf8_lossy(&err.into_bytes()).into()), - parser::phrase(value, false) - .to_full_result() - .map(|value| { + parser::encodings::phrase(value, false) + .map(|(_, value)| { String::from_utf8(value) .unwrap_or_else(|err| String::from_utf8_lossy(&err.into_bytes()).into()) }) .unwrap_or_else(|_| String::from_utf8_lossy(value).into()), ); if name.eq_ignore_ascii_case(b"to") { - let parse_result = parser::rfc2822address_list(value); - if parse_result.is_done() { - let value = parse_result.to_full_result().unwrap(); + let parse_result = parser::address::rfc2822address_list(value); + if parse_result.is_ok() { + let value = parse_result.unwrap().1; self.set_to(value); }; } else if name.eq_ignore_ascii_case(b"cc") { - let parse_result = parser::rfc2822address_list(value); - if parse_result.is_done() { - let value = parse_result.to_full_result().unwrap(); + let parse_result = parser::address::rfc2822address_list(value); + if parse_result.is_ok() { + let value = parse_result.unwrap().1; self.set_cc(value); }; } else if name.eq_ignore_ascii_case(b"bcc") { - let parse_result = parser::rfc2822address_list(value); - if parse_result.is_done() { - let value = parse_result.to_full_result().unwrap(); + let parse_result = parser::address::rfc2822address_list(value); + if parse_result.is_ok() { + let value = parse_result.unwrap().1; self.set_bcc(value); }; } else if name.eq_ignore_ascii_case(b"from") { - let parse_result = parser::rfc2822address_list(value); - if parse_result.is_done() { - let value = parse_result.to_full_result().unwrap(); + let parse_result = parser::address::rfc2822address_list(value); + if parse_result.is_ok() { + let value = parse_result.unwrap().1; self.set_from(value); } } else if name.eq_ignore_ascii_case(b"subject") { - let parse_result = parser::phrase(value.trim(), false); - if parse_result.is_done() { - let value = parse_result.to_full_result().unwrap(); + let parse_result = parser::encodings::phrase(value.trim(), false); + if parse_result.is_ok() { + let value = parse_result.unwrap().1; self.set_subject(value); }; } else if name.eq_ignore_ascii_case(b"message-id") { self.set_message_id(value); } else if name.eq_ignore_ascii_case(b"references") { { - let parse_result = parser::references(value); - if parse_result.is_done() { - for v in parse_result.to_full_result().unwrap() { + let parse_result = parser::address::references(value); + if parse_result.is_ok() { + for v in parse_result.unwrap().1 { self.push_references(v); } } @@ -311,16 +311,16 @@ impl Envelope { self.set_in_reply_to(value); in_reply_to = Some(value); } else if name.eq_ignore_ascii_case(b"date") { - let parse_result = parser::phrase(value, false); - if parse_result.is_done() { - let value = parse_result.to_full_result().unwrap(); + let parse_result = parser::encodings::phrase(value, false); + if parse_result.is_ok() { + let value = parse_result.unwrap().1; self.set_date(value.as_slice()); } else { self.set_date(value); } } else if name.eq_ignore_ascii_case(b"content-type") { - match parser::content_type(value).to_full_result() { - Ok((ct, cst, ref params)) + 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") => { @@ -352,7 +352,7 @@ impl Envelope { if let Some(ref mut x) = in_reply_to { self.push_references(x); } - if let Ok(d) = parser::date(&self.date.as_bytes()) { + if let Ok(d) = parser::generic::date(&self.date.as_bytes()) { self.set_datetime(d); } if self.message_id.raw().is_empty() { @@ -425,7 +425,7 @@ impl Envelope { /// Requests bytes from backend and thus can fail pub fn headers<'a>(&self, bytes: &'a [u8]) -> Result> { - let ret = parser::headers(bytes).to_full_result()?; + let ret = parser::headers::headers(bytes)?.1; let len = ret.len(); ret.into_iter() .try_fold(Vec::with_capacity(len), |mut acc, (a, b)| { @@ -491,8 +491,8 @@ impl Envelope { self.to = new_val; } pub fn set_in_reply_to(&mut self, new_val: &[u8]) { - let slice = match parser::message_id(new_val).to_full_result() { - Ok(v) => v, + let slice = match parser::address::message_id(new_val) { + Ok(v) => v.1, Err(_) => { self.in_reply_to = None; return; @@ -515,8 +515,8 @@ impl Envelope { self.subject = Some(new_val); } pub fn set_message_id(&mut self, new_val: &[u8]) { - match parser::message_id(new_val).to_full_result() { - Ok(slice) => { + match parser::address::message_id(new_val) { + Ok((_, slice)) => { self.message_id = MessageID::new(new_val, slice); } Err(_) => { @@ -525,8 +525,8 @@ impl Envelope { } } pub fn push_references(&mut self, new_val: &[u8]) { - let slice = match parser::message_id(new_val).to_full_result() { - Ok(v) => v, + let slice = match parser::address::message_id(new_val) { + Ok(v) => v.1, Err(e) => { debug!(e); return; diff --git a/melib/src/email/address.rs b/melib/src/email/address.rs index 44ca48572..05f488cc5 100644 --- a/melib/src/email/address.rs +++ b/melib/src/email/address.rs @@ -218,7 +218,7 @@ impl StrBuild for MessageID { #[test] fn test_strbuilder() { let m_id = b"<20170825132332.6734-1@el13635@mail.ntua.gr>"; - let (_, slice) = parser::message_id(m_id).unwrap(); + let (_, slice) = parser::address::message_id(m_id).unwrap(); assert_eq!( MessageID::new(m_id, slice), MessageID( diff --git a/melib/src/email/attachments.rs b/melib/src/email/attachments.rs index 2b8278212..ea85ca1ed 100644 --- a/melib/src/email/attachments.rs +++ b/melib/src/email/attachments.rs @@ -39,8 +39,8 @@ pub struct AttachmentBuilder { impl AttachmentBuilder { pub fn new(content: &[u8]) -> Self { - let (headers, body) = match parser::attachment(content).to_full_result() { - Ok(v) => v, + let (headers, body) = match parser::attachments::attachment(content) { + Ok((_, v)) => v, Err(_) => { debug!("error in parsing attachment"); debug!("\n-------------------------------"); @@ -121,8 +121,8 @@ impl AttachmentBuilder { } pub fn set_content_type_from_bytes(&mut self, value: &[u8]) -> &mut Self { - match parser::content_type(value).to_full_result() { - Ok((ct, cst, params)) => { + match parser::attachments::content_type(value) { + Ok((_, (ct, cst, params))) => { if ct.eq_ignore_ascii_case(b"multipart") { let mut boundary = None; for (n, v) in params { @@ -185,10 +185,9 @@ impl AttachmentBuilder { let mut name: Option = None; for (n, v) in params { if n.eq_ignore_ascii_case(b"name") { - if let Ok(v) = crate::email::parser::phrase(v.trim(), false) - .to_full_result() + if let Ok(v) = crate::email::parser::encodings::phrase(v.trim(), false) .as_ref() - .and_then(|r| Ok(String::from_utf8_lossy(r).to_string())) + .and_then(|(_, r)| Ok(String::from_utf8_lossy(r).to_string())) { name = Some(v); } else { @@ -229,13 +228,13 @@ impl AttachmentBuilder { return Vec::new(); } - match parser::parts(raw, boundary).to_full_result() { - Ok(attachments) => { + match parser::attachments::parts(raw, boundary) { + Ok((_, attachments)) => { let mut vec = Vec::with_capacity(attachments.len()); for a in attachments { let mut builder = AttachmentBuilder::default(); - let (headers, body) = match parser::attachment(&a).to_full_result() { - Ok(v) => v, + let (headers, body) = match parser::attachments::attachment(&a) { + Ok((_, v)) => v, Err(_) => { debug!("error in parsing attachment"); debug!("\n-------------------------------"); @@ -420,8 +419,8 @@ impl Attachment { match self.content_type { ContentType::Multipart { ref boundary, .. } => { - match parser::multipart_parts(self.body(), boundary).to_full_result() { - Ok(v) => v, + match parser::attachments::multipart_parts(self.body(), boundary) { + Ok((_, v)) => v, Err(e) => { debug!("error in parsing attachment"); debug!("\n-------------------------------"); @@ -444,10 +443,12 @@ impl Attachment { } // FIXME: check if any part is multipart/mixed as well - match parser::multipart_parts(bytes, boundary).to_full_result() { - Ok(parts) => { + match parser::attachments::multipart_parts(bytes, boundary) { + Ok((_, parts)) => { for p in parts { - for (n, v) in crate::email::parser::HeaderIterator(p.display_bytes(bytes)) { + for (n, v) in + crate::email::parser::generic::HeaderIterator(p.display_bytes(bytes)) + { if !n.eq_ignore_ascii_case(b"content-type") && !v.starts_with(b"text/") { return true; } @@ -655,14 +656,14 @@ impl Attachment { pub fn parameters(&self) -> Vec<(&[u8], &[u8])> { let mut ret = Vec::new(); - let (headers, _) = match parser::attachment(&self.raw).to_full_result() { - Ok(v) => v, + let (headers, _) = match parser::attachments::attachment(&self.raw) { + Ok((_, v)) => v, Err(_) => return ret, }; for (name, value) in headers { if name.eq_ignore_ascii_case(b"content-type") { - match parser::content_type(value).to_full_result() { - Ok((_, _, params)) => { + match parser::attachments::content_type(value) { + Ok((_, (_, _, params))) => { ret = params; } _ => {} @@ -751,16 +752,18 @@ fn decode_helper<'a>(a: &'a Attachment, filter: &mut Option>) -> Vec< Ok(v) => v, _ => a.body().to_vec(), }, - ContentTransferEncoding::QuotedPrintable => parser::quoted_printable_bytes(a.body()) - .to_full_result() - .unwrap(), + ContentTransferEncoding::QuotedPrintable => { + parser::encodings::quoted_printable_bytes(a.body()) + .unwrap() + .1 + } ContentTransferEncoding::_7Bit | ContentTransferEncoding::_8Bit | ContentTransferEncoding::Other { .. } => a.body().to_vec(), }; let mut ret = if a.content_type.is_text() { - if let Ok(v) = parser::decode_charset(&bytes, charset) { + if let Ok(v) = parser::encodings::decode_charset(&bytes, charset) { v.into_bytes() } else { a.body().to_vec() diff --git a/melib/src/email/compose.rs b/melib/src/email/compose.rs index e386f76d4..d452fd296 100644 --- a/melib/src/email/compose.rs +++ b/melib/src/email/compose.rs @@ -88,7 +88,7 @@ impl str::FromStr for Draft { return Err(MeliError::new("Empty input in Draft::from_str")); } - let (headers, _) = parser::mail(s.as_bytes()).to_full_result()?; + let (headers, _) = parser::mail(s.as_bytes())?; let mut ret = Draft::default(); for (k, v) in headers { @@ -107,9 +107,7 @@ impl str::FromStr for Draft { } } if ret.headers.contains_key("From") && !ret.headers.contains_key("Message-ID") { - if let super::parser::IResult::Done(_, addr) = - super::parser::mailbox(ret.headers["From"].as_bytes()) - { + if let Ok((_, addr)) = super::parser::address::mailbox(ret.headers["From"].as_bytes()) { if let Some(fqdn) = addr.get_fqdn() { if ret .headers @@ -256,8 +254,7 @@ impl Draft { let mut ret = String::new(); if self.headers.contains_key("From") && !self.headers.contains_key("Message-ID") { - if let super::parser::IResult::Done(_, addr) = - super::parser::mailbox(self.headers["From"].as_bytes()) + if let Ok((_, addr)) = super::parser::address::mailbox(self.headers["From"].as_bytes()) { if let Some(fqdn) = addr.get_fqdn() { if self diff --git a/melib/src/email/compose/mime.rs b/melib/src/email/compose/mime.rs index 1d2b9d5f8..381d6090d 100644 --- a/melib/src/email/compose/mime.rs +++ b/melib/src/email/compose/mime.rs @@ -162,9 +162,9 @@ fn test_encode_header() { ); assert_eq!( &std::str::from_utf8( - &crate::email::parser::phrase(encode_header(&words).as_bytes(), false) - .to_full_result() + &crate::email::parser::encodings::phrase(encode_header(&words).as_bytes(), false) .unwrap() + .1 ) .unwrap(), &words, @@ -175,9 +175,9 @@ fn test_encode_header() { assert_eq!( r#"[internal] Νέος Οδηγός Συγγραφής"#, std::str::from_utf8( - &crate::email::parser::phrase(encode_header(&words_enc).as_bytes(), false) - .to_full_result() + &crate::email::parser::encodings::phrase(encode_header(&words_enc).as_bytes(), false) .unwrap() + .1 ) .unwrap(), ); @@ -186,9 +186,9 @@ fn test_encode_header() { assert_eq!( "[Advcomparch] Συμπεριφορά σε flush λόγω misprediction κατά την εκτέλεση store", std::str::from_utf8( - &crate::email::parser::phrase(encode_header(&words_enc).as_bytes(), false) - .to_full_result() + &crate::email::parser::encodings::phrase(encode_header(&words_enc).as_bytes(), false) .unwrap() + .1 ) .unwrap(), ); diff --git a/melib/src/email/list_management.rs b/melib/src/email/list_management.rs index 18e848282..e9517d944 100644 --- a/melib/src/email/list_management.rs +++ b/melib/src/email/list_management.rs @@ -48,8 +48,8 @@ impl<'a> From<&'a [u8]> for ListAction<'a> { impl<'a> ListAction<'a> { pub fn parse_options_list(input: &'a [u8]) -> Option; 4]>> { - parser::angle_bracket_delimeted_list(input) - .map(|mut vec| { + parser::generic::angle_bracket_delimeted_list(input) + .map(|(_, mut vec)| { /* Prefer email options first, since this _is_ a mail client after all and it's * more automated */ vec.sort_unstable_by(|a, b| { @@ -64,7 +64,6 @@ impl<'a> ListAction<'a> { .map(|elem| ListAction::from(elem)) .collect::; 4]>>() }) - .to_full_result() .ok() } } diff --git a/melib/src/email/mailto.rs b/melib/src/email/mailto.rs index 890fbcc87..d9ff4bc72 100644 --- a/melib/src/email/mailto.rs +++ b/melib/src/email/mailto.rs @@ -54,7 +54,7 @@ impl TryFrom<&[u8]> for Mailto { type Error = String; fn try_from(value: &[u8]) -> std::result::Result { - let parse_res = super::parser::mailto(value).to_full_result(); + let parse_res = super::parser::generic::mailto(value).map(|(_, v)| v); if parse_res.is_ok() { Ok(parse_res.unwrap()) } else { @@ -74,8 +74,8 @@ mod tests { #[test] fn test_mailto() { - let test_address = super::parser::address(b"info@example.com") - .to_full_result() + let test_address = super::parser::address::address(b"info@example.com") + .map(|(_, v)| v) .unwrap(); let mailto = Mailto::try_from(&b"mailto:info@example.com?subject=email%20subject"[0..]) .expect("Could not parse mailto link."); diff --git a/melib/src/email/parser.rs b/melib/src/email/parser.rs index b0380cfb7..e6520f00d 100644 --- a/melib/src/email/parser.rs +++ b/melib/src/email/parser.rs @@ -1,7 +1,7 @@ /* * meli - parser module * - * Copyright 2017 Manos Pitsidianakis + * Copyright 2017 - 2020 Manos Pitsidianakis * * This file is part of meli. * @@ -18,14 +18,19 @@ * You should have received a copy of the GNU General Public License * along with meli. If not, see . */ -use super::*; -use data_encoding::BASE64_MIME; -use encoding::{DecoderTrap, Encoding}; -use nom::{is_hex_digit, le_u8}; -pub(super) use nom::{ErrorKind, IResult, Needed}; -use encoding::all::*; -use std; +use crate::error::{MeliError, Result, ResultIntoMeliError}; +use nom::{ + branch::alt, + bytes::complete::{is_a, is_not, tag, take_until, take_while}, + character::is_hex_digit, + combinator::peek, + error::ErrorKind, + multi::{many0, many1, separated_list, separated_nonempty_list}, + number::complete::le_u8, + sequence::{delimited, preceded, separated_pair, terminated}, + IResult, +}; macro_rules! is_ctl_or_space { ($var:ident) => { @@ -121,969 +126,1038 @@ impl<'a, P: for<'r> FnMut(&'r u8) -> bool> BytesIterExt for std::slice::Split<'a } } -fn quoted_printable_byte(input: &[u8]) -> IResult<&[u8], u8> { - if input.len() < 3 { - IResult::Incomplete(Needed::Size(1)) - } else if input[0] == b'=' && is_hex_digit(input[1]) && is_hex_digit(input[2]) { - let a = if input[1] < b':' { - input[1] - 48 - } else if input[1] < b'[' { - input[1] - 55 +//fn parser(input: I) -> IResult; +pub fn mail(input: &[u8]) -> Result<(Vec<(&[u8], &[u8])>, &[u8])> { + let (rest, result) = separated_pair( + headers::headers, + alt((tag(b"\n"), tag(b"\r\n"))), + take_while(|_| true), + )(input) + .chain_err_summary(|| "Could not parse mail")?; + + if !rest.is_empty() { + return Err(MeliError::new("Got leftover bytes after parsing mail")); + } + + Ok(result) +} + +pub mod generic { + use super::*; + pub fn angle_bracket_delimeted_list(input: &[u8]) -> IResult<&[u8], Vec<&[u8]>> { + separated_nonempty_list(is_a(","), delimited(tag("<"), take_until(">"), tag(">")))( + input.rtrim(), + ) + // separated_nonempty_list!(complete!(is_a!(",")), ws!(complete!(complete!(delimited!(tag!("<"), take_until1!(">"), tag!(">"))))))); + } + + pub fn date(input: &[u8]) -> Result { + let (_, mut parsed_result) = encodings::phrase(&eat_comments(input), false)?; + if let Some(pos) = parsed_result.find(b"-0000") { + parsed_result[pos] = b'+'; + } + + crate::datetime::rfc822_to_timestamp(parsed_result.trim()) + } + + fn eat_comments(input: &[u8]) -> Vec { + let mut in_comment = false; + input + .iter() + .fold(Vec::with_capacity(input.len()), |mut acc, x| { + if *x == b'(' && !in_comment { + in_comment = true; + acc + } else if *x == b')' && in_comment { + in_comment = false; + acc + } else if in_comment { + acc + } else { + acc.push(*x); + acc + } + }) + } + use crate::email::address::Address; + use crate::email::mailto::Mailto; + pub fn mailto(mut input: &[u8]) -> IResult<&[u8], Mailto> { + if !input.starts_with(b"mailto:") { + return Err(nom::Err::Error((input, ErrorKind::Tag))); + } + + input = &input[b"mailto:".len()..]; + + let end = input.iter().position(|e| *e == b'?').unwrap_or(input.len()); + let address: Address; + + if let Ok((_, addr)) = crate::email::parser::address::address(&input[..end]) { + address = addr; + input = if input[end..].is_empty() { + &input[end..] + } else { + &input[end + 1..] + }; } else { - input[1] - 87 - }; - let b = if input[2] < b':' { - input[2] - 48 - } else if input[2] < b'[' { - input[2] - 55 - } else { - input[2] - 87 - }; - IResult::Done(&input[3..], a * 16 + b) - } else if input.starts_with(b"\r\n") { - IResult::Done(&input[2..], b'\n') - } else { - IResult::Error(error_code!(ErrorKind::Custom(43))) + return Err(nom::Err::Error((input, ErrorKind::Tag))); + } + + let mut subject = None; + let mut cc = None; + let mut bcc = None; + let mut body = None; + while !input.is_empty() { + let tag = if let Some(tag_pos) = input.iter().position(|e| *e == b'=') { + let ret = &input[0..tag_pos]; + input = &input[tag_pos + 1..]; + ret + } else { + return Err(nom::Err::Error((input, ErrorKind::Tag))); + }; + + let value_end = input.iter().position(|e| *e == b'&').unwrap_or(input.len()); + + let value = String::from_utf8_lossy(&input[..value_end]).to_string(); + match tag { + b"subject" if subject.is_none() => { + subject = Some(value); + } + b"cc" if cc.is_none() => { + cc = Some(value); + } + b"bcc" if bcc.is_none() => { + bcc = Some(value); + } + b"body" if body.is_none() => { + /* FIXME: + * Parse escaped characters properly. + */ + body = Some(value.replace("%20", " ").replace("%0A", "\n")); + } + _ => { + return Err(nom::Err::Error((input, ErrorKind::Tag))); + } + } + if input[value_end..].is_empty() { + break; + } + input = &input[value_end + 1..]; + } + Ok(( + input, + Mailto { + address, + subject, + cc, + bcc, + body, + }, + )) + } + + 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])> { + if self.0.is_empty() { + return None; + } + + match super::headers::header(self.0) { + Ok((rest, value)) => { + self.0 = rest; + Some(value) + } + _ => { + self.0 = &[]; + None + } + } + } } } -// Parser definition +pub mod headers { + use super::*; -/* A header can span multiple lines, eg: - * - * Received: from -------------------- (-------------------------) - * by --------------------- (--------------------- [------------------]) (-----------------------) - * with ESMTP id ------------ for <------------------->; - * Tue, 5 Jan 2016 21:30:44 +0100 (CET) - */ - -fn header_value(input: &[u8]) -> IResult<&[u8], &[u8]> { - let input_len = input.len(); - for (i, x) in input.iter().enumerate() { - if *x == b'\n' - && (((i + 1) < input_len && input[i + 1] != b' ' && input[i + 1] != b'\t') - || i + 1 == input_len) - { - return IResult::Done(&input[(i + 1)..], &input[0..i]); - } else if input[i..].starts_with(b"\r\n") - && (((i + 2) < input_len && input[i + 2] != b' ' && input[i + 2] != b'\t') - || i + 2 == input_len) - { - return IResult::Done(&input[(i + 2)..], &input[0..i]); - } - } - IResult::Incomplete(Needed::Unknown) -} - -/* Parse a single header as a tuple */ -fn header_with_val(input: &[u8]) -> IResult<&[u8], (&[u8], &[u8])> { - if input.is_empty() { - return IResult::Incomplete(Needed::Unknown); - } else if input.starts_with(b"\n") || input.starts_with(b"\r\n") { - return IResult::Error(error_code!(ErrorKind::Custom(43))); - } - let mut ptr = 0; - let mut name: &[u8] = &input[0..0]; - /* field-name = 1* */ - for (i, x) in input.iter().enumerate() { - if *x == b':' { - name = &input[0..i]; - ptr = i + 1; - break; - } else if is_ctl_or_space!(*x) { - return IResult::Error(error_code!(ErrorKind::Custom(43))); - } - } - if name.is_empty() { - return IResult::Error(error_code!(ErrorKind::Custom(43))); - } - if ptr >= input.len() { - return IResult::Error(error_code!(ErrorKind::Custom(43))); + pub fn headers(input: &[u8]) -> IResult<&[u8], Vec<(&[u8], &[u8])>> { + many1(header)(input) } - if input[ptr] == b'\n' { - ptr += 1; - if ptr >= input.len() { - return IResult::Error(error_code!(ErrorKind::Custom(43))); - } - } else if input[ptr..].starts_with(b"\r\n") { - ptr += 2; - if ptr > input.len() { - return IResult::Error(error_code!(ErrorKind::Custom(43))); - } + pub fn header(input: &[u8]) -> IResult<&[u8], (&[u8], &[u8])> { + alt((header_without_val, header_with_val))(input) } - if ptr >= input.len() { - return IResult::Error(error_code!(ErrorKind::Custom(43))); - } - while input[ptr] == b' ' || input[ptr] == b'\t' { - ptr += 1; - if ptr >= input.len() { - return IResult::Error(error_code!(ErrorKind::Custom(43))); - } - } - match header_value(&input[ptr..]) { - IResult::Done(rest, value) => IResult::Done(rest, (name, value)), - IResult::Incomplete(needed) => IResult::Incomplete(needed), - IResult::Error(code) => IResult::Error(code), - } -} -fn header_without_val(input: &[u8]) -> IResult<&[u8], (&[u8], &[u8])> { - if input.is_empty() { - return IResult::Incomplete(Needed::Unknown); - } else if input.starts_with(b"\n") || input.starts_with(b"\r\n") { - return IResult::Error(error_code!(ErrorKind::Custom(43))); - } - let mut ptr = 0; - let mut name: &[u8] = &input[0..0]; - let mut has_colon = false; - /* field-name = 1* */ - for (i, x) in input.iter().enumerate() { - if input[i..].starts_with(b"\r\n") { - name = &input[0..i]; - ptr = i + 2; - break; - } else if *x == b':' || *x == b'\n' { - name = &input[0..i]; + pub fn header_without_val(input: &[u8]) -> IResult<&[u8], (&[u8], &[u8])> { + if input.is_empty() { + return Err(nom::Err::Error((input, ErrorKind::Tag))); + } else if input.starts_with(b"\n") || input.starts_with(b"\r\n") { + return Err(nom::Err::Error((input, ErrorKind::Tag))); + } + let mut ptr = 0; + let mut name: &[u8] = &input[0..0]; + let mut has_colon = false; + /* field-name = 1* */ + for (i, x) in input.iter().enumerate() { + if input[i..].starts_with(b"\r\n") { + name = &input[0..i]; + ptr = i + 2; + break; + } else if *x == b':' || *x == b'\n' { + name = &input[0..i]; + has_colon = true; + ptr = i; + break; + } else if is_ctl_or_space!(*x) { + return Err(nom::Err::Error((input, ErrorKind::Tag))); + } + } + if name.is_empty() || input.len() <= ptr { + return Err(nom::Err::Error((input, ErrorKind::Tag))); + } + if input[ptr] == b':' { + ptr += 1; has_colon = true; - ptr = i; - break; - } else if is_ctl_or_space!(*x) { - return IResult::Error(error_code!(ErrorKind::Custom(43))); + if ptr >= input.len() { + return Err(nom::Err::Error((input, ErrorKind::Tag))); + } } - } - if name.is_empty() || input.len() <= ptr { - return IResult::Error(error_code!(ErrorKind::Custom(43))); - } - if input[ptr] == b':' { - ptr += 1; - has_colon = true; - if ptr >= input.len() { - return IResult::Incomplete(Needed::Unknown); - } - } - if !has_colon { - return IResult::Incomplete(Needed::Unknown); - } + if !has_colon { + return Err(nom::Err::Error((input, ErrorKind::Tag))); + } - while input[ptr] == b' ' { - ptr += 1; - if ptr >= input.len() { - return IResult::Incomplete(Needed::Unknown); + while input[ptr] == b' ' { + ptr += 1; + if ptr >= input.len() { + return Err(nom::Err::Error((input, ErrorKind::Tag))); + } } - } - if input[ptr..].starts_with(b"\n") { - ptr += 1; - if ptr >= input.len() { - return IResult::Incomplete(Needed::Unknown); - } - if input.len() > ptr && input[ptr] != b' ' && input[ptr] != b'\t' { - IResult::Done(&input[ptr..], (name, b"")) + if input[ptr..].starts_with(b"\n") { + ptr += 1; + if ptr >= input.len() { + return Err(nom::Err::Error((input, ErrorKind::Tag))); + } + if input.len() > ptr && input[ptr] != b' ' && input[ptr] != b'\t' { + Ok((&input[ptr..], (name, b""))) + } else { + Err(nom::Err::Error((input, ErrorKind::Tag))) + } + } else if input[ptr..].starts_with(b"\r\n") { + ptr += 2; + if ptr > input.len() { + return Err(nom::Err::Error((input, ErrorKind::Tag))); + } + if input.len() > ptr && input[ptr] != b' ' && input[ptr] != b'\t' { + Ok((&input[ptr..], (name, b""))) + } else { + Err(nom::Err::Error((input, ErrorKind::Tag))) + } } else { - IResult::Error(error_code!(ErrorKind::Custom(43))) - } - } else if input[ptr..].starts_with(b"\r\n") { - ptr += 2; - if ptr > input.len() { - return IResult::Incomplete(Needed::Unknown); - } - if input.len() > ptr && input[ptr] != b' ' && input[ptr] != b'\t' { - IResult::Done(&input[ptr..], (name, b"")) - } else { - IResult::Error(error_code!(ErrorKind::Custom(43))) - } - } else { - IResult::Error(error_code!(ErrorKind::Custom(43))) - } -} - -named!( - header<(&[u8], &[u8])>, - alt_complete!(call!(header_without_val) | call!(header_with_val)) -); -/* Parse all headers -> Vec<(&str, Vec<&str>)> */ -named!(pub headers>, - many1!(complete!(header))); - -pub fn headers_raw(input: &[u8]) -> IResult<&[u8], &[u8]> { - if input.is_empty() { - return IResult::Incomplete(Needed::Unknown); - } - for i in 0..input.len() { - if input[i..].starts_with(b"\n\n") { - return IResult::Done(&input[(i + 1)..], &input[0..=i]); - } else if input[i..].starts_with(b"\r\n\r\n") { - return IResult::Done(&input[(i + 2)..], &input[0..=i]); + Err(nom::Err::Error((input, ErrorKind::Tag))) } } - IResult::Error(error_code!(ErrorKind::Custom(43))) -} -named!(pub body_raw<&[u8]>, - do_parse!( - alt_complete!(take_until1!("\n\n") | take_until1!("\r\n\r\n")) >> - body: take_while!(call!(|_| true)) >> - ( { body } ))); - -named!(pub mail<(std::vec::Vec<(&[u8], &[u8])>, &[u8])>, - separated_pair!(headers, alt_complete!(tag!(b"\n") | tag!(b"\r\n")), take_while!(call!(|_| true)))); - -named!(pub attachment<(std::vec::Vec<(&[u8], &[u8])>, &[u8])>, - do_parse!( - pair: separated_pair!(many0!(complete!(header)), alt_complete!(tag!(b"\n") | tag!(b"\r\n")), take_while!(call!(|_| true))) >> - ( { pair } ))); - -/* Header parsers */ - -/* Encoded words - *"=?charset?encoding?encoded text?=". - */ -fn encoded_word(input: &[u8]) -> IResult<&[u8], Vec> { - if input.is_empty() { - return IResult::Done(&[], Vec::with_capacity(0)); - } - if input.len() < 5 { - return IResult::Incomplete(Needed::Unknown); - } else if input[0] != b'=' || input[1] != b'?' { - return IResult::Error(error_code!(ErrorKind::Custom(43))); - } - /* find end of Charset tag: - * =?charset?encoding?encoded text?= - * ---------^ + /* A header can span multiple lines, eg: + * + * Received: from -------------------- (-------------------------) + * by --------------------- (--------------------- [------------------]) (-----------------------) + * with ESMTP id ------------ for <------------------->; + * Tue, 5 Jan 2016 21:30:44 +0100 (CET) */ - let mut tag_end_idx = None; - for (idx, b) in input[2..].iter().enumerate() { - if *b == b'?' { - tag_end_idx = Some(idx + 2); - break; + + pub fn header_value(input: &[u8]) -> IResult<&[u8], &[u8]> { + let input_len = input.len(); + for (i, x) in input.iter().enumerate() { + if *x == b'\n' + && (((i + 1) < input_len && input[i + 1] != b' ' && input[i + 1] != b'\t') + || i + 1 == input_len) + { + return Ok((&input[(i + 1)..], &input[0..i])); + } else if input[i..].starts_with(b"\r\n") + && (((i + 2) < input_len && input[i + 2] != b' ' && input[i + 2] != b'\t') + || i + 2 == input_len) + { + return Ok((&input[(i + 2)..], &input[0..i])); + } } + Err(nom::Err::Error((input, ErrorKind::Tag))) } - if tag_end_idx.is_none() { - return IResult::Error(error_code!(ErrorKind::Custom(42))); - } - let tag_end_idx = tag_end_idx.unwrap(); - if tag_end_idx + 2 >= input.len() || input[2 + tag_end_idx] != b'?' { - return IResult::Error(error_code!(ErrorKind::Custom(43))); - } - /* See if input ends with "?=" and get ending index - * =?charset?encoding?encoded text?= - * -------------------------------^ - */ - let mut encoded_end_idx = None; - for i in (3 + tag_end_idx)..input.len() { - if input[i] == b'?' && i + 1 < input.len() && input[i + 1] == b'=' { - encoded_end_idx = Some(i); - break; + /* Parse a single header as a tuple */ + pub fn header_with_val(input: &[u8]) -> IResult<&[u8], (&[u8], &[u8])> { + if input.is_empty() { + return Err(nom::Err::Error((input, ErrorKind::Tag))); + } else if input.starts_with(b"\n") || input.starts_with(b"\r\n") { + return Err(nom::Err::Error((input, ErrorKind::Tag))); } - } - if encoded_end_idx.is_none() { - return IResult::Error(error_code!(ErrorKind::Custom(44))); - } - let encoded_end_idx = encoded_end_idx.unwrap(); - let encoded_text = &input[3 + tag_end_idx..encoded_end_idx]; - - let s: Vec = match input[tag_end_idx + 1] { - b'b' | b'B' => match BASE64_MIME.decode(encoded_text) { - Ok(v) => v, - Err(_) => encoded_text.to_vec(), - }, - b'q' | b'Q' => match quoted_printable_bytes_header(encoded_text) { - IResult::Done(b"", s) => s, - _ => return IResult::Error(error_code!(ErrorKind::Custom(45))), - }, - _ => return IResult::Error(error_code!(ErrorKind::Custom(46))), - }; - - let charset = Charset::from(&input[2..tag_end_idx]); - - if let Charset::UTF8 = charset { - IResult::Done(&input[encoded_end_idx + 2..], s) - } else { - match decode_charset(&s, charset) { - Ok(v) => IResult::Done(&input[encoded_end_idx + 2..], v.into_bytes()), - _ => IResult::Error(error_code!(ErrorKind::Custom(43))), + let mut ptr = 0; + let mut name: &[u8] = &input[0..0]; + /* field-name = 1* */ + for (i, x) in input.iter().enumerate() { + if *x == b':' { + name = &input[0..i]; + ptr = i + 1; + break; + } else if is_ctl_or_space!(*x) { + return Err(nom::Err::Error((input, ErrorKind::Tag))); + } } + if name.is_empty() { + return Err(nom::Err::Error((input, ErrorKind::Tag))); + } + if ptr >= input.len() { + return Err(nom::Err::Error((input, ErrorKind::Tag))); + } + + if input[ptr] == b'\n' { + ptr += 1; + if ptr >= input.len() { + return Err(nom::Err::Error((input, ErrorKind::Tag))); + } + } else if input[ptr..].starts_with(b"\r\n") { + ptr += 2; + if ptr > input.len() { + return Err(nom::Err::Error((input, ErrorKind::Tag))); + } + } + if ptr >= input.len() { + return Err(nom::Err::Error((input, ErrorKind::Tag))); + } + while input[ptr] == b' ' || input[ptr] == b'\t' { + ptr += 1; + if ptr >= input.len() { + return Err(nom::Err::Error((input, ErrorKind::Tag))); + } + } + header_value(&input[ptr..]).map(|(rest, value)| (rest, (name, value))) + } + + pub fn headers_raw(input: &[u8]) -> IResult<&[u8], &[u8]> { + if input.is_empty() { + return Err(nom::Err::Error((input, ErrorKind::Tag))); + } + for i in 0..input.len() { + if input[i..].starts_with(b"\n\n") { + return Ok((&input[(i + 1)..], &input[0..=i])); + } else if input[i..].starts_with(b"\r\n\r\n") { + return Ok((&input[(i + 2)..], &input[0..=i])); + } + } + Err(nom::Err::Error((input, ErrorKind::Tag))) } } -pub fn decode_charset(s: &[u8], charset: Charset) -> Result { - match charset { - Charset::UTF8 | Charset::Ascii => Ok(String::from_utf8_lossy(s).to_string()), - Charset::ISO8859_1 => Ok(ISO_8859_1.decode(s, DecoderTrap::Strict)?), - Charset::ISO8859_2 => Ok(ISO_8859_2.decode(s, DecoderTrap::Strict)?), - Charset::ISO8859_7 => Ok(ISO_8859_7.decode(s, DecoderTrap::Strict)?), - Charset::ISO8859_15 => Ok(ISO_8859_15.decode(s, DecoderTrap::Strict)?), - Charset::GBK => Ok(GBK.decode(s, DecoderTrap::Strict)?), - Charset::Windows1250 => Ok(WINDOWS_1250.decode(s, DecoderTrap::Strict)?), - Charset::Windows1251 => Ok(WINDOWS_1251.decode(s, DecoderTrap::Strict)?), - Charset::Windows1252 => Ok(WINDOWS_1252.decode(s, DecoderTrap::Strict)?), - Charset::Windows1253 => Ok(WINDOWS_1253.decode(s, DecoderTrap::Strict)?), - // Unimplemented: - Charset::GB2312 => Ok(String::from_utf8_lossy(s).to_string()), - Charset::UTF16 => Ok(String::from_utf8_lossy(s).to_string()), - Charset::BIG5 => Ok(String::from_utf8_lossy(s).to_string()), - Charset::ISO2022JP => Ok(String::from_utf8_lossy(s).to_string()), +pub mod attachments { + use super::*; + use crate::email::address::*; + pub fn attachment(input: &[u8]) -> IResult<&[u8], (std::vec::Vec<(&[u8], &[u8])>, &[u8])> { + separated_pair( + many0(headers::header), + alt((tag(b"\n"), tag(b"\r\n"))), + take_while(|_| true), + )(input) } -} -fn quoted_printable_soft_break(input: &[u8]) -> IResult<&[u8], &[u8]> { - if input.len() < 2 { - IResult::Incomplete(Needed::Size(1)) - } else if input[0] == b'=' && input[1] == b'\n' { - IResult::Done(&input[2..], &input[0..2]) // `=\n` is an escaped space character. - } else if input.len() > 3 && input.starts_with(b"=\r\n") { - IResult::Done(&input[3..], &input[0..3]) // `=\r\n` is an escaped space character. - } else { - IResult::Error(error_code!(ErrorKind::Custom(43))) - } -} + pub fn multipart_parts<'a>( + input: &'a [u8], + boundary: &[u8], + ) -> IResult<&'a [u8], Vec> { + let mut ret: Vec<_> = Vec::new(); + let mut input = input; + let mut offset = 0; + loop { + let b_start = if let Some(v) = input.find(boundary) { + v + } else { + return Err(nom::Err::Error((input, ErrorKind::Tag))); + }; -named!( - qp_underscore_header, - do_parse!(tag!(b"_") >> ({ 0x20 })) -); - -// With MIME, headers in quoted printable format can contain underscores that represent spaces. -// In non-header context, an underscore is just a plain underscore. -named!( - pub quoted_printable_bytes_header>, - many0!(alt_complete!( - quoted_printable_byte | qp_underscore_header | le_u8 - )) -); - -// For atoms in Header values. -named!( - pub quoted_printable_bytes>, - many0!(alt_complete!( - preceded!(quoted_printable_soft_break, quoted_printable_byte) | - preceded!(quoted_printable_soft_break, le_u8) | quoted_printable_byte | le_u8 - )) -); - -fn display_addr(input: &[u8]) -> IResult<&[u8], Address> { - if input.is_empty() || input.len() < 3 { - IResult::Incomplete(Needed::Size(1)) - } else if !is_whitespace!(input[0]) { - let mut display_name = StrBuilder { - offset: 0, - length: 0, - }; - let mut flag = false; - for (i, b) in input[0..].iter().enumerate() { - if *b == b'<' { - display_name.length = i.saturating_sub(1); // if i != 0 { i - 1 } else { 0 }; - flag = true; + if b_start < 2 { + return Err(nom::Err::Error((input, ErrorKind::Tag))); + } + offset += b_start - 2; + input = &input[b_start - 2..]; + if &input[0..2] == b"--" { + offset += 2 + boundary.len(); + input = &input[2 + boundary.len()..]; + if input[0] == b'\n' { + offset += 1; + input = &input[1..]; + } else if input[0..].starts_with(b"\r\n") { + offset += 2; + input = &input[2..]; + } else { + continue; + } break; } } - if !flag { - let (rest, output) = match phrase(input, false) { - IResult::Done(rest, raw) => (rest, raw), - _ => return IResult::Error(error_code!(ErrorKind::Custom(43))), - }; - if output.contains(&b'<') { - match display_addr(&output) { - IResult::Done(_, address) => return IResult::Done(rest, address), - _ => return IResult::Error(error_code!(ErrorKind::Custom(43))), - } + + loop { + if input.len() < boundary.len() + 4 { + return Err(nom::Err::Error((input, ErrorKind::Tag))); } - return IResult::Error(error_code!(ErrorKind::Custom(43))); - } - let mut end = input.len(); - let mut at_flag = false; - let mut flag = false; - for (i, b) in input[display_name.length + 2..].iter().enumerate() { - match *b { - b'@' => at_flag = true, - b'>' => { - end = i; - flag = true; + if let Some(end) = input.find(boundary) { + if &input[end - 2..end] != b"--" { + return Err(nom::Err::Error((input, ErrorKind::Tag))); + } + ret.push(StrBuilder { + offset, + length: end - 2, + }); + offset += end + boundary.len(); + input = &input[end + boundary.len()..]; + if input.len() < 2 || input[0] != b'\n' || &input[0..2] == b"--" { break; } - _ => {} + if input[0] == b'\n' { + offset += 1; + input = &input[1..]; + } else if input[0..].starts_with(b"\r\n") { + offset += 2; + input = &input[2..]; + } + } else { + ret.push(StrBuilder { + offset, + length: input.len(), + }); + break; } } - if at_flag && flag { - match phrase(&input[0..end + display_name.length + 3], false) { - IResult::Error(e) => IResult::Error(e), - IResult::Incomplete(i) => IResult::Incomplete(i), - IResult::Done(_, raw) => { - let display_name_end = raw.find(b"<").unwrap(); - display_name.length = { raw[0..display_name_end].trim().len() }; - let address_spec = if display_name_end == 0 { - StrBuilder { - offset: 1, - length: end + 1, - } - } else { - StrBuilder { - offset: display_name_end + 1, - length: end, - } - }; + Ok((input, ret)) + } - if display_name.display(&raw).as_bytes().is_quoted() { - display_name.offset += 1; - display_name.length -= 2; + fn parts_f(boundary: &[u8]) -> impl Fn(&[u8]) -> IResult<&[u8], Vec<&[u8]>> + '_ { + move |input: &[u8]| -> IResult<&[u8], Vec<&[u8]>> { + let mut ret: Vec<&[u8]> = Vec::new(); + let mut input = input; + loop { + let b_start = if let Some(v) = input.find(boundary) { + v + } else { + return Err(nom::Err::Error((input, ErrorKind::Tag))); + }; + + if b_start < 2 { + return Err(nom::Err::Error((input, ErrorKind::Tag))); + } + input = &input[b_start - 2..]; + if &input[0..2] == b"--" { + input = &input[2 + boundary.len()..]; + if input[0] == b'\n' { + input = &input[1..]; + } else if input[0..].starts_with(b"\r\n") { + input = &input[2..]; + } else { + continue; } - - let rest_start = if input.len() > end + display_name.length + 2 { - end + display_name.length + 3 - } else { - end + display_name.length + 2 - }; - - IResult::Done( - input.get(rest_start..).unwrap_or_default(), - Address::Mailbox(MailboxAddress { - raw, - display_name, - address_spec, - }), - ) + break; } } - } else { - IResult::Error(error_code!(ErrorKind::Custom(43))) - } - } else { - IResult::Error(error_code!(ErrorKind::Custom(43))) - } -} - -fn addr_spec(input: &[u8]) -> IResult<&[u8], Address> { - if input.is_empty() || input.len() < 3 { - IResult::Incomplete(Needed::Size(1)) - } else if !is_whitespace!(input[0]) { - let mut end = input[1..].len(); - let mut flag = false; - for (i, b) in input[1..].iter().enumerate() { - if *b == b'@' { - flag = true; - } - if is_whitespace!(*b) { - end = i; - break; - } - } - if flag { - IResult::Done( - &input[end..], - Address::Mailbox(MailboxAddress { - raw: input[0..=end].into(), - display_name: StrBuilder { - offset: 0, - length: 0, - }, - address_spec: StrBuilder { - offset: 0, - length: input[0..=end].len(), - }, - }), - ) - } else { - IResult::Error(error_code!(ErrorKind::Custom(43))) - } - } else { - IResult::Error(error_code!(ErrorKind::Custom(42))) - } -} - -named!( - pub mailbox
, - ws!(alt_complete!(display_addr | addr_spec)) -); -named!(mailbox_list>, many0!(mailbox)); - -/* - * group of recipients eg. undisclosed-recipients; - */ -fn group(input: &[u8]) -> IResult<&[u8], Address> { - let mut flag = false; - let mut dlength = 0; - for (i, b) in input.iter().enumerate() { - if *b == b':' { - flag = true; - dlength = i; - break; - } - } - if !flag { - return IResult::Error(error_code!(ErrorKind::Custom(43))); - } - - match mailbox_list(&input[dlength..]) { - IResult::Error(e) => IResult::Error(e), - IResult::Done(rest, vec) => { - let size: usize = - (rest.as_ptr() as usize).wrapping_sub((&input[0..] as &[u8]).as_ptr() as usize); - IResult::Done( - rest, - Address::Group(GroupAddress { - raw: input[0..size].into(), - display_name: StrBuilder { - offset: 0, - length: dlength, - }, - mailbox_list: vec, - }), - ) - } - IResult::Incomplete(i) => IResult::Incomplete(i), - } -} - -named!(pub address
, ws!(alt_complete!(mailbox | group))); - -named!(pub rfc2822address_list>, ws!( separated_list!(is_a!(","), address))); - -named!(pub address_list, ws!(do_parse!( - list: alt_complete!( encoded_word_list | ascii_token) >> - ( { - let list: Vec<&[u8]> = list.split(|c| *c == b',').collect(); - let string_len = list.iter().fold(0, |mut acc, x| { acc+=x.trim().len(); acc }) + list.len() - 1; - let list_len = list.len(); - let mut i = 0; - list.iter().fold(String::with_capacity(string_len), - |acc, x| { - let mut acc = acc + &String::from_utf8_lossy(x.replace(b"\n", b"").replace(b"\r", b"").replace(b"\t", b" ").trim()); - if i != list_len - 1 { - acc.push_str(" "); - i+=1; + loop { + if input.len() < boundary.len() + 4 { + return Err(nom::Err::Error((input, ErrorKind::Tag))); + } + if let Some(end) = input.find(boundary) { + if &input[end - 2..end] != b"--" { + return Err(nom::Err::Error((input, ErrorKind::Tag))); + } + ret.push(&input[0..end - 2]); + input = &input[end + boundary.len()..]; + if input.len() < 2 + || (input[0] != b'\n' && &input[0..2] != b"\r\n") + || &input[0..2] == b"--" + { + break; + } + if input[0] == b'\n' { + input = &input[1..]; + } else if input[0..].starts_with(b"\r\n") { + input = &input[2..]; + } + } else { + ret.push(input); + break; } - acc - }) - } ) - - ))); - -fn eat_comments(input: &[u8]) -> Vec { - let mut in_comment = false; - input - .iter() - .fold(Vec::with_capacity(input.len()), |mut acc, x| { - if *x == b'(' && !in_comment { - in_comment = true; - acc - } else if *x == b')' && in_comment { - in_comment = false; - acc - } else if in_comment { - acc - } else { - acc.push(*x); - acc } - }) -} - -/* - * Date should tokenize input and convert the tokens, - * right now we expect input will have no extra spaces in between tokens - * - * We should use a custom parser here*/ -pub fn date(input: &[u8]) -> Result { - let mut parsed_result = phrase(&eat_comments(input), false).to_full_result()?; - if let Some(pos) = parsed_result.find(b"-0000") { - parsed_result[pos] = b'+'; - } - - crate::datetime::rfc822_to_timestamp(parsed_result.trim()) -} - -named!(pub message_id<&[u8]>, - 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() { - IResult::Incomplete(Needed::Size(1)) - } else if input_length == 2 || input[0] != b'<' { - IResult::Error(error_code!(ErrorKind::Custom(43))) - } else { - for (i, &x) in input.iter().take(input_length).enumerate().skip(1) { - if x == b'>' { - return IResult::Done(&input[i + 1..], &input[0..=i]); - } - } - IResult::Incomplete(Needed::Unknown) - } -} - -named!(pub references>, separated_list!(complete!(is_a!(" \n\t\r")), message_id_peek)); - -pub fn multipart_parts<'a>(input: &'a [u8], boundary: &[u8]) -> IResult<&'a [u8], Vec> { - let mut ret: Vec<_> = Vec::new(); - let mut input = input; - let mut offset = 0; - loop { - let b_start = if let Some(v) = input.find(boundary) { - v - } else { - return IResult::Error(error_code!(ErrorKind::Custom(39))); - }; - - if b_start < 2 { - return IResult::Error(error_code!(ErrorKind::Custom(40))); - } - offset += b_start - 2; - input = &input[b_start - 2..]; - if &input[0..2] == b"--" { - offset += 2 + boundary.len(); - input = &input[2 + boundary.len()..]; - if input[0] == b'\n' { - offset += 1; - input = &input[1..]; - } else if input[0..].starts_with(b"\r\n") { - offset += 2; - input = &input[2..]; - } else { - continue; - } - break; + Ok((input, ret)) } } - loop { - if input.len() < boundary.len() + 4 { - return IResult::Error(error_code!(ErrorKind::Custom(41))); - } - if let Some(end) = input.find(boundary) { - if &input[end - 2..end] != b"--" { - return IResult::Error(error_code!(ErrorKind::Custom(42))); - } - ret.push(StrBuilder { - offset, - length: end - 2, - }); - offset += end + boundary.len(); - input = &input[end + boundary.len()..]; - if input.len() < 2 || input[0] != b'\n' || &input[0..2] == b"--" { - break; - } - if input[0] == b'\n' { - offset += 1; - input = &input[1..]; - } else if input[0..].starts_with(b"\r\n") { - offset += 2; - input = &input[2..]; - } - } else { - ret.push(StrBuilder { - offset, - length: input.len(), - }); - break; - } - } - IResult::Done(input, ret) -} - -fn parts_f<'a>(input: &'a [u8], boundary: &[u8]) -> IResult<&'a [u8], Vec<&'a [u8]>> { - let mut ret: Vec<&[u8]> = Vec::new(); - let mut input = input; - loop { - let b_start = if let Some(v) = input.find(boundary) { - v - } else { - return IResult::Error(error_code!(ErrorKind::Custom(39))); - }; - - if b_start < 2 { - return IResult::Error(error_code!(ErrorKind::Custom(40))); - } - input = &input[b_start - 2..]; - if &input[0..2] == b"--" { - input = &input[2 + boundary.len()..]; - if input[0] == b'\n' { - input = &input[1..]; - } else if input[0..].starts_with(b"\r\n") { - input = &input[2..]; - } else { - continue; - } - break; - } - } - loop { - if input.len() < boundary.len() + 4 { - return IResult::Error(error_code!(ErrorKind::Custom(41))); - } - if let Some(end) = input.find(boundary) { - if &input[end - 2..end] != b"--" { - return IResult::Error(error_code!(ErrorKind::Custom(42))); - } - ret.push(&input[0..end - 2]); - input = &input[end + boundary.len()..]; - if input.len() < 2 - || (input[0] != b'\n' && &input[0..2] != b"\r\n") - || &input[0..2] == b"--" - { - break; - } - if input[0] == b'\n' { - input = &input[1..]; - } else if input[0..].starts_with(b"\r\n") { - input = &input[2..]; - } - } else { - ret.push(input); - break; - } - } - IResult::Done(input, ret) -} - -named_args!(pub parts<'a>(boundary: &'a [u8]) < Vec<&'this_is_probably_unique_i_hope_please [u8]> >, + pub fn parts<'a>(input: &'a [u8], boundary: &[u8]) -> IResult<&'a [u8], Vec<&'a [u8]>> { + alt(( + parts_f(boundary), + |input: &'a [u8]| -> IResult<&'a [u8], Vec<&'a [u8]>> { + let (input, _) = take_until(&b"--"[..])(input)?; + let (input, _) = take_until(boundary)(input)?; + Ok((input, Vec::<&[u8]>::new())) + }, + ))(input) + /* alt_complete!(call!(parts_f, boundary) | do_parse!( take_until_and_consume!(&b"--"[..]) >> take_until_and_consume!(boundary) >> ( { Vec::<&[u8]>::new() } )) )); - -/* Caution: values should be passed through phrase() */ -named!( - content_type_parameter<(&[u8], &[u8])>, - do_parse!( - tag!(";") - >> name: terminated!(ws!(take_until!("=")), tag!("=")) - >> value: - ws!(alt_complete!( - delimited!(tag!("\""), take_until!("\""), tag!("\"")) | is_not!(";") - )) - >> ({ (name, value) }) - ) -); - -named!(pub content_type< (&[u8], &[u8], Vec<(&[u8], &[u8])>) >, - do_parse!( - _type: take_until!("/") >> - tag!("/") >> - _subtype: is_not!(";") >> - parameters: many0!(complete!(content_type_parameter)) >> - ( { - (_type, _subtype, parameters) - } ) - )); - -named!(pub space, eat_separator!(&b" \t\r\n"[..])); -named!( - encoded_word_list>, - ws!(do_parse!( - list: separated_nonempty_list!(call!(space), encoded_word) - >> ({ - let list_len = list.iter().fold(0, |mut acc, x| { - acc += x.len(); - acc - }); - list.iter() - .fold(Vec::with_capacity(list_len), |mut acc, x| { - acc.append(&mut x.clone()); - acc - }) - }) - )) -); -named!( - ascii_token>, - do_parse!( - word: alt_complete!( - terminated!( - take_until1!(" =?"), - peek!(preceded!(tag!(b" "), call!(encoded_word))) - ) | take_while!(call!(|_| true)) - ) >> ({ word.into() }) - ) -); - -pub fn phrase(input: &[u8], multiline: /* preserve newlines */ bool) -> IResult<&[u8], Vec> { - if input.is_empty() { - return IResult::Done(&[], Vec::with_capacity(0)); + */ } - let mut input = input.ltrim(); - let mut acc: Vec = Vec::new(); - let mut ptr = 0; + /* Caution: values should be passed through phrase() */ + pub fn content_type_parameter(input: &[u8]) -> IResult<&[u8], (&[u8], &[u8])> { + let (input, _) = tag(";")(input)?; + let (input, name) = terminated(take_until("="), tag("="))(input.ltrim())?; + let (input, value) = alt(( + delimited(tag("\""), take_until("\""), tag("\"")), + is_not(";"), + ))(input.ltrim())?; - while ptr < input.len() { - let mut flag = false; - // Check if word is encoded. - while let IResult::Done(rest, v) = encoded_word(&input[ptr..]) { - flag = true; - input = rest; - ptr = 0; - acc.extend(v); + Ok((input, (name, value))) + } - // consume whitespace - while ptr < input.len() && (is_whitespace!(input[ptr])) { - ptr += 1; - } + pub fn content_type(input: &[u8]) -> IResult<&[u8], (&[u8], &[u8], Vec<(&[u8], &[u8])>)> { + let (input, _type) = take_until("/")(input)?; + let (input, _) = tag("/")(input)?; + let (input, _subtype) = is_not(";")(input)?; + let (input, parameters) = many0(content_type_parameter)(input)?; + Ok((input, (_type, _subtype, parameters))) + /* + do_parse!( + _type: take_until!("/") >> + tag!("/") >> + _subtype: is_not!(";") >> + parameters: many0!(complete!(content_type_parameter)) >> + ( { + (_type, _subtype, parameters) + } ) + )); + */ + } +} - if ptr >= input.len() { +pub mod encodings { + use super::*; + use crate::email::attachment_types::Charset; + use data_encoding::BASE64_MIME; + use encoding::all::*; + use encoding::{DecoderTrap, Encoding}; + pub fn quoted_printable_byte(input: &[u8]) -> IResult<&[u8], u8> { + if input.len() < 3 { + Err(nom::Err::Error((input, ErrorKind::Tag))) + } else if input[0] == b'=' && is_hex_digit(input[1]) && is_hex_digit(input[2]) { + let a = if input[1] < b':' { + input[1] - 48 + } else if input[1] < b'[' { + input[1] - 55 + } else { + input[1] - 87 + }; + let b = if input[2] < b':' { + input[2] - 48 + } else if input[2] < b'[' { + input[2] - 55 + } else { + input[2] - 87 + }; + Ok((&input[3..], a * 16 + b)) + } else if input.starts_with(b"\r\n") { + Ok((&input[2..], b'\n')) + } else { + Err(nom::Err::Error((input, ErrorKind::Tag))) + } + } + + /* Encoded words + *"=?charset?encoding?encoded text?=". + */ + fn encoded_word(input: &[u8]) -> IResult<&[u8], Vec> { + if input.is_empty() { + return Ok((&[], Vec::with_capacity(0))); + } + if input.len() < 5 { + return Err(nom::Err::Error((input, ErrorKind::Tag))); + } else if input[0] != b'=' || input[1] != b'?' { + return Err(nom::Err::Error((input, ErrorKind::Tag))); + } + /* find end of Charset tag: + * =?charset?encoding?encoded text?= + * ---------^ + */ + let mut tag_end_idx = None; + for (idx, b) in input[2..].iter().enumerate() { + if *b == b'?' { + tag_end_idx = Some(idx + 2); break; } } - if flag && ptr < input.len() && ptr != 0 { - acc.push(b' '); + if tag_end_idx.is_none() { + return Err(nom::Err::Error((input, ErrorKind::Tag))); } - let end = input[ptr..].find(b"=?"); + let tag_end_idx = tag_end_idx.unwrap(); - let end = end.unwrap_or_else(|| input.len() - ptr) + ptr; - let ascii_s = ptr; - let mut ascii_e = 0; - - while ptr < end && !(is_whitespace!(input[ptr])) { - ptr += 1; + if tag_end_idx + 2 >= input.len() || input[2 + tag_end_idx] != b'?' { + return Err(nom::Err::Error((input, ErrorKind::Tag))); } - if !multiline { - ascii_e = ptr; + /* See if input ends with "?=" and get ending index + * =?charset?encoding?encoded text?= + * -------------------------------^ + */ + let mut encoded_end_idx = None; + for i in (3 + tag_end_idx)..input.len() { + if input[i] == b'?' && i + 1 < input.len() && input[i + 1] == b'=' { + encoded_end_idx = Some(i); + break; + } } - - while ptr < input.len() && (is_whitespace!(input[ptr])) { - ptr += 1; - } - if multiline { - ascii_e = ptr; - } - if ptr >= input.len() { - acc.extend( - ascii_token(&input[ascii_s..ascii_e]) - .to_full_result() - .unwrap(), - ); - break; - } - if ascii_s >= ascii_e { - /* We have the start of an encoded word but not the end, so parse it as ascii */ - ascii_e = input[ascii_s..] - .find(b" ") - .unwrap_or_else(|| ascii_s + input[ascii_s..].len()); - ptr = ascii_e; - } - if ascii_s >= ascii_e { - return IResult::Error(error_code!(ErrorKind::Custom(43))); + if encoded_end_idx.is_none() { + return Err(nom::Err::Error((input, ErrorKind::Tag))); } + let encoded_end_idx = encoded_end_idx.unwrap(); + let encoded_text = &input[3 + tag_end_idx..encoded_end_idx]; - acc.extend( - ascii_token(&input[ascii_s..ascii_e]) - .to_full_result() - .unwrap(), - ); - if ptr != ascii_e { - acc.push(b' '); - } - } - IResult::Done(&input[ptr..], acc) -} - -named!(pub angle_bracket_delimeted_list>, separated_nonempty_list!(complete!(is_a!(",")), ws!(complete!(complete!(delimited!(tag!("<"), take_until1!(">"), tag!(">"))))))); - -pub fn mailto(mut input: &[u8]) -> IResult<&[u8], Mailto> { - if !input.starts_with(b"mailto:") { - return IResult::Error(error_code!(ErrorKind::Custom(43))); - } - - input = &input[b"mailto:".len()..]; - - let end = input.iter().position(|e| *e == b'?').unwrap_or(input.len()); - let address: Address; - - if let IResult::Done(_, addr) = crate::email::parser::address(&input[..end]) { - address = addr; - input = if input[end..].is_empty() { - &input[end..] - } else { - &input[end + 1..] - }; - } else { - return IResult::Error(error_code!(ErrorKind::Custom(43))); - } - - let mut subject = None; - let mut cc = None; - let mut bcc = None; - let mut body = None; - while !input.is_empty() { - let tag = if let Some(tag_pos) = input.iter().position(|e| *e == b'=') { - let ret = &input[0..tag_pos]; - input = &input[tag_pos + 1..]; - ret - } else { - return IResult::Error(error_code!(ErrorKind::Custom(43))); + let s: Vec = match input[tag_end_idx + 1] { + b'b' | b'B' => match BASE64_MIME.decode(encoded_text) { + Ok(v) => v, + Err(_) => encoded_text.to_vec(), + }, + b'q' | b'Q' => match quoted_printable_bytes_header(encoded_text) { + Ok((b"", s)) => s, + _ => return Err(nom::Err::Error((input, ErrorKind::Tag))), + }, + _ => return Err(nom::Err::Error((input, ErrorKind::Tag))), }; - let value_end = input.iter().position(|e| *e == b'&').unwrap_or(input.len()); + let charset = Charset::from(&input[2..tag_end_idx]); - let value = String::from_utf8_lossy(&input[..value_end]).to_string(); - match tag { - b"subject" if subject.is_none() => { - subject = Some(value); - } - b"cc" if cc.is_none() => { - cc = Some(value); - } - b"bcc" if bcc.is_none() => { - bcc = Some(value); - } - b"body" if body.is_none() => { - /* FIXME: - * Parse escaped characters properly. - */ - body = Some(value.replace("%20", " ").replace("%0A", "\n")); - } - _ => { - return IResult::Error(error_code!(ErrorKind::Custom(43))); + if let Charset::UTF8 = charset { + Ok((&input[encoded_end_idx + 2..], s)) + } else { + match decode_charset(&s, charset) { + Ok(v) => Ok((&input[encoded_end_idx + 2..], v.into_bytes())), + _ => Err(nom::Err::Error((input, ErrorKind::Tag))), } } - if input[value_end..].is_empty() { - break; - } - input = &input[value_end + 1..]; } - IResult::Done( - input, - Mailto { - address, - subject, - cc, - bcc, - body, - }, - ) + + pub fn decode_charset(s: &[u8], charset: Charset) -> Result { + match charset { + Charset::UTF8 | Charset::Ascii => Ok(String::from_utf8_lossy(s).to_string()), + Charset::ISO8859_1 => Ok(ISO_8859_1.decode(s, DecoderTrap::Strict)?), + Charset::ISO8859_2 => Ok(ISO_8859_2.decode(s, DecoderTrap::Strict)?), + Charset::ISO8859_7 => Ok(ISO_8859_7.decode(s, DecoderTrap::Strict)?), + Charset::ISO8859_15 => Ok(ISO_8859_15.decode(s, DecoderTrap::Strict)?), + Charset::GBK => Ok(GBK.decode(s, DecoderTrap::Strict)?), + Charset::Windows1250 => Ok(WINDOWS_1250.decode(s, DecoderTrap::Strict)?), + Charset::Windows1251 => Ok(WINDOWS_1251.decode(s, DecoderTrap::Strict)?), + Charset::Windows1252 => Ok(WINDOWS_1252.decode(s, DecoderTrap::Strict)?), + Charset::Windows1253 => Ok(WINDOWS_1253.decode(s, DecoderTrap::Strict)?), + // Unimplemented: + Charset::GB2312 => Ok(String::from_utf8_lossy(s).to_string()), + Charset::UTF16 => Ok(String::from_utf8_lossy(s).to_string()), + Charset::BIG5 => Ok(String::from_utf8_lossy(s).to_string()), + Charset::ISO2022JP => Ok(String::from_utf8_lossy(s).to_string()), + } + } + + fn quoted_printable_soft_break(input: &[u8]) -> IResult<&[u8], &[u8]> { + if input.len() < 2 { + return Err(nom::Err::Error((input, ErrorKind::Tag))); + } else if input[0] == b'=' && input[1] == b'\n' { + Ok((&input[2..], &input[0..2])) // `=\n` is an escaped space character. + } else if input.len() > 3 && input.starts_with(b"=\r\n") { + Ok((&input[3..], &input[0..3])) // `=\r\n` is an escaped space character. + } else { + Err(nom::Err::Error((input, ErrorKind::Tag))) + } + } + + pub fn qp_underscore_header(input: &[u8]) -> IResult<&[u8], u8> { + let (rest, _) = tag(b"_")(input)?; + Ok((rest, 0x20)) + } + + // With MIME, headers in quoted printable format can contain underscores that represent spaces. + // In non-header context, an underscore is just a plain underscore. + pub fn quoted_printable_bytes_header(input: &[u8]) -> IResult<&[u8], Vec> { + many0(alt((quoted_printable_byte, qp_underscore_header, le_u8)))(input) + } + + // For atoms in Header values. + pub fn quoted_printable_bytes(input: &[u8]) -> IResult<&[u8], Vec> { + many0(alt(( + preceded(quoted_printable_soft_break, quoted_printable_byte), + preceded(quoted_printable_soft_break, le_u8), + quoted_printable_byte, + le_u8, + )))(input) + } + + pub fn space(input: &[u8]) -> IResult<&[u8], ()> { + let (rest, _) = + take_while(|c: u8| c == b' ' || c == b'\t' || c == b'\r' || c == b'\n')(input)?; + Ok((rest, ())) + //eat_separator!()); + } + + pub fn encoded_word_list(input: &[u8]) -> IResult<&[u8], Vec> { + let (input, list) = separated_nonempty_list(space, encoded_word)(input)?; + let list_len = list.iter().fold(0, |mut acc, x| { + acc += x.len(); + acc + }); + Ok(( + input, + list.iter() + .fold(Vec::with_capacity(list_len), |mut acc, x| { + acc.append(&mut x.clone()); + acc + }), + )) + } + + pub fn ascii_token(input: &[u8]) -> IResult<&[u8], Vec> { + // TODO take_until used to be take_until1, check if this works + let (input, word) = alt(( + terminated(take_until(" =?"), peek(preceded(tag(b" "), encoded_word))), + take_while(|_| true), + ))(input)?; + Ok((input, word.to_vec())) + /* + do_parse!( + word: alt_complete!( + terminated!( + take_until1!(" =?"), + peek!(preceded!(tag!(b" "), call!(encoded_word))) + ) | take_while!(call!(|_| true)) + ) >> ({ word.into() }) + ) + */ + } + + pub fn phrase( + input: &[u8], + multiline: /* preserve newlines */ bool, + ) -> IResult<&[u8], Vec> { + if input.is_empty() { + return Ok((&[], Vec::with_capacity(0))); + } + + let mut input = input.ltrim(); + let mut acc: Vec = Vec::new(); + let mut ptr = 0; + + while ptr < input.len() { + let mut flag = false; + // Check if word is encoded. + while let Ok((rest, v)) = encoded_word(&input[ptr..]) { + flag = true; + input = rest; + ptr = 0; + acc.extend(v); + + // consume whitespace + while ptr < input.len() && (is_whitespace!(input[ptr])) { + ptr += 1; + } + + if ptr >= input.len() { + break; + } + } + if flag && ptr < input.len() && ptr != 0 { + acc.push(b' '); + } + let end = input[ptr..].find(b"=?"); + + let end = end.unwrap_or_else(|| input.len() - ptr) + ptr; + let ascii_s = ptr; + let mut ascii_e = 0; + + while ptr < end && !(is_whitespace!(input[ptr])) { + ptr += 1; + } + if !multiline { + ascii_e = ptr; + } + + while ptr < input.len() && (is_whitespace!(input[ptr])) { + ptr += 1; + } + if multiline { + ascii_e = ptr; + } + if ptr >= input.len() { + acc.extend(ascii_token(&input[ascii_s..ascii_e])?.1); + break; + } + if ascii_s >= ascii_e { + /* We have the start of an encoded word but not the end, so parse it as ascii */ + ascii_e = input[ascii_s..] + .find(b" ") + .unwrap_or_else(|| ascii_s + input[ascii_s..].len()); + ptr = ascii_e; + } + if ascii_s >= ascii_e { + return Err(nom::Err::Error((input, ErrorKind::Tag))); + } + + acc.extend(ascii_token(&input[ascii_s..ascii_e])?.1); + if ptr != ascii_e { + acc.push(b' '); + } + } + Ok((&input[ptr..], acc)) + } } -//alt_complete!(call!(header_without_val) | call!(header_with_val)) - -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])> { - if self.0.is_empty() { - return None; - } - - match header(self.0) { - IResult::Done(rest, value) => { - self.0 = rest; - Some(value) +pub mod address { + use super::*; + use crate::email::address::*; + pub fn display_addr(input: &[u8]) -> IResult<&[u8], Address> { + if input.is_empty() || input.len() < 3 { + return Err(nom::Err::Error((input, ErrorKind::Tag))); + } else if !is_whitespace!(input[0]) { + let mut display_name = StrBuilder { + offset: 0, + length: 0, + }; + let mut flag = false; + for (i, b) in input[0..].iter().enumerate() { + if *b == b'<' { + display_name.length = i.saturating_sub(1); // if i != 0 { i - 1 } else { 0 }; + flag = true; + break; + } } - _ => { - self.0 = &[]; - None + if !flag { + let (rest, output) = match super::encodings::phrase(input, false) { + Ok(v) => v, + _ => return Err(nom::Err::Error((input, ErrorKind::Tag))), + }; + if output.contains(&b'<') { + let (_, address) = match display_addr(&output) { + Ok(v) => v, + _ => return Err(nom::Err::Error((input, ErrorKind::Tag))), + }; + return Ok((rest, address)); + } + return Err(nom::Err::Error((input, ErrorKind::Tag))); + } + let mut end = input.len(); + let mut at_flag = false; + let mut flag = false; + for (i, b) in input[display_name.length + 2..].iter().enumerate() { + match *b { + b'@' => at_flag = true, + b'>' => { + end = i; + flag = true; + break; + } + _ => {} + } + } + if at_flag && flag { + let (_, raw) = + super::encodings::phrase(&input[0..end + display_name.length + 3], false)?; + let display_name_end = raw.find(b"<").unwrap(); + display_name.length = raw[0..display_name_end].trim().len(); + let address_spec = if display_name_end == 0 { + StrBuilder { + offset: 1, + length: end + 1, + } + } else { + StrBuilder { + offset: display_name_end + 1, + length: end, + } + }; + + if display_name.display(&raw).as_bytes().is_quoted() { + display_name.offset += 1; + display_name.length -= 2; + } + + let rest_start = if input.len() > end + display_name.length + 2 { + end + display_name.length + 3 + } else { + end + display_name.length + 2 + }; + + Ok(( + input.get(rest_start..).unwrap_or_default(), + Address::Mailbox(MailboxAddress { + raw, + display_name, + address_spec, + }), + )) + } else { + Err(nom::Err::Error((input, ErrorKind::Tag))) + } + } else { + Err(nom::Err::Error((input, ErrorKind::Tag))) + } + } + + fn addr_spec(input: &[u8]) -> IResult<&[u8], Address> { + if input.is_empty() || input.len() < 3 { + return Err(nom::Err::Error((input, ErrorKind::Tag))); + } else if !is_whitespace!(input[0]) { + let mut end = input[1..].len(); + let mut flag = false; + for (i, b) in input[1..].iter().enumerate() { + if *b == b'@' { + flag = true; + } + if is_whitespace!(*b) { + end = i; + break; + } + } + if flag { + Ok(( + &input[end..], + Address::Mailbox(MailboxAddress { + raw: input[0..=end].into(), + display_name: StrBuilder { + offset: 0, + length: 0, + }, + address_spec: StrBuilder { + offset: 0, + length: input[0..=end].len(), + }, + }), + )) + } else { + Err(nom::Err::Error((input, ErrorKind::Tag))) + } + } else { + Err(nom::Err::Error((input, ErrorKind::Tag))) + } + } + + pub fn mailbox(input: &[u8]) -> IResult<&[u8], Address> { + alt((display_addr, addr_spec))(input) + //ws!(alt_complete!(display_addr | addr_spec)) + } + + pub fn mailbox_list(input: &[u8]) -> IResult<&[u8], Vec
> { + many0(mailbox)(input) + // many0!(mailbox)); + } + + /* + * group of recipients eg. undisclosed-recipients; + */ + fn group(input: &[u8]) -> IResult<&[u8], Address> { + let mut flag = false; + let mut dlength = 0; + for (i, b) in input.iter().enumerate() { + if *b == b':' { + flag = true; + dlength = i; + break; } } + if !flag { + return Err(nom::Err::Error((input, ErrorKind::Tag))); + } + + let (rest, vec) = mailbox_list(&input[dlength..])?; + let size: usize = + (rest.as_ptr() as usize).wrapping_sub((&input[0..] as &[u8]).as_ptr() as usize); + Ok(( + rest, + Address::Group(GroupAddress { + raw: input[0..size].into(), + display_name: StrBuilder { + offset: 0, + length: dlength, + }, + mailbox_list: vec, + }), + )) + } + + pub fn address(input: &[u8]) -> IResult<&[u8], Address> { + alt((mailbox, group))(input.ltrim()) + // ws!(alt_complete!(mailbox | group)) + } + + pub fn rfc2822address_list(input: &[u8]) -> IResult<&[u8], Vec
> { + separated_list(is_a(","), address)(input.ltrim()) + // ws!( separated_list!(is_a!(","), address)) + } + + pub fn address_list(input: &[u8]) -> IResult<&[u8], String> { + let (input, list) = alt(( + super::encodings::encoded_word_list, + super::encodings::ascii_token, + ))(input)?; + let list: Vec<&[u8]> = list.split(|c| *c == b',').collect(); + let string_len = list.iter().fold(0, |mut acc, x| { + acc += x.trim().len(); + acc + }) + list.len() + - 1; + let list_len = list.len(); + let mut i = 0; + Ok(( + input, + list.iter() + .fold(String::with_capacity(string_len), |acc, x| { + let mut acc = acc + + &String::from_utf8_lossy( + x.replace(b"\n", b"") + .replace(b"\r", b"") + .replace(b"\t", b" ") + .trim(), + ); + if i != list_len - 1 { + acc.push_str(" "); + i += 1; + } + acc + }), + )) + } + + 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() { + return Err(nom::Err::Error((input, ErrorKind::Tag))); + } else if input_length == 2 || input[0] != b'<' { + return Err(nom::Err::Error((input, ErrorKind::Tag))); + } else { + for (i, &x) in input.iter().take(input_length).enumerate().skip(1) { + if x == b'>' { + return Ok((&input[i + 1..], &input[0..=i])); + } + } + return Err(nom::Err::Error((input, ErrorKind::Tag))); + } + } + + 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)); } } #[cfg(test)] mod tests { - - use super::*; + use super::{address::*, encodings::*, generic::*, *}; + use crate::email::address::*; use crate::make_address; #[test] @@ -1091,33 +1165,33 @@ mod tests { let words = b"=?iso-8859-7?B?W215Y291cnNlcy5udHVhLmdyIC0gyvXs4fTp6t4g6uHpIMri4e306ere?= =?iso-8859-7?B?INb18+nq3l0gzd3hIMHt4erv3+358+c6IMzF0c/TIMHQz9TFy8XTzMHU?= =?iso-8859-7?B?2c0gwiDUzC4gysHNLiDFzsXUwdPH0yAyMDE3LTE4OiDTx8zFydnTxw==?="; - assert_eq!("[mycourses.ntua.gr - Κυματική και Κβαντική Φυσική] Νέα Ανακοίνωση: ΜΕΡΟΣ ΑΠΟΤΕΛΕΣΜΑΤΩΝ Β ΤΜ. ΚΑΝ. ΕΞΕΤΑΣΗΣ 2017-18: ΣΗΜΕΙΩΣΗ" , std::str::from_utf8(&phrase(words.trim(), false).to_full_result().unwrap()).unwrap()); + assert_eq!("[mycourses.ntua.gr - Κυματική και Κβαντική Φυσική] Νέα Ανακοίνωση: ΜΕΡΟΣ ΑΠΟΤΕΛΕΣΜΑΤΩΝ Β ΤΜ. ΚΑΝ. ΕΞΕΤΑΣΗΣ 2017-18: ΣΗΜΕΙΩΣΗ" , std::str::from_utf8(&phrase(words.trim(), false).unwrap().1).unwrap()); let words = b"=?UTF-8?Q?=CE=A0=CF=81=CF=8C=CF=83=CE=B8=CE=B5?= =?UTF-8?Q?=CF=84=CE=B7_=CE=B5=CE=BE=CE=B5=CF=84?= =?UTF-8?Q?=CE=B1=CF=83=CF=84=CE=B9=CE=BA=CE=AE?="; assert_eq!( "Πρόσθετη εξεταστική", - std::str::from_utf8(&phrase(words.trim(), false).to_full_result().unwrap()).unwrap() + std::str::from_utf8(&phrase(words.trim(), false).unwrap().1).unwrap() ); let words = b"[Advcomparch] =?utf-8?b?zqPPhc68z4DOtc+BzrnPhs6/z4HOrCDPg861IGZs?=\n\t=?utf-8?b?dXNoIM67z4zOs8+JIG1pc3ByZWRpY3Rpb24gzrrOsc+Ezqwgz4TOt869?=\n\t=?utf-8?b?IM61zrrPhM6tzrvOtc+Dzrcgc3RvcmU=?="; assert_eq!( "[Advcomparch] Συμπεριφορά σε flush λόγω misprediction κατά την εκτέλεση store", - std::str::from_utf8(&phrase(words.trim(), false).to_full_result().unwrap()).unwrap() + std::str::from_utf8(&phrase(words.trim(), false).unwrap().1).unwrap() ); let words = b"Re: [Advcomparch] =?utf-8?b?zqPPhc68z4DOtc+BzrnPhs6/z4HOrCDPg861IGZs?= =?utf-8?b?dXNoIM67z4zOs8+JIG1pc3ByZWRpY3Rpb24gzrrOsc+Ezqwgz4TOt869?= =?utf-8?b?IM61zrrPhM6tzrvOtc+Dzrcgc3RvcmU=?="; assert_eq!( "Re: [Advcomparch] Συμπεριφορά σε flush λόγω misprediction κατά την εκτέλεση store", - std::str::from_utf8(&phrase(words.trim(), false).to_full_result().unwrap()).unwrap() + std::str::from_utf8(&phrase(words.trim(), false).unwrap().1).unwrap() ); let words = b"sdf"; assert_eq!( "sdf", - std::str::from_utf8(&phrase(words, false).to_full_result().unwrap()).unwrap() + std::str::from_utf8(&phrase(words, false).unwrap().1).unwrap() ); let words = b"=?iso-8859-7?b?U2VnIGZhdWx0IPP05+0g5er03evl8+cg9O/1?= =?iso-8859-7?q?_example_ru_n_=5Fsniper?="; assert_eq!( "Seg fault στην εκτέλεση του example ru n _sniper", - std::str::from_utf8(&phrase(words, false).to_full_result().unwrap()).unwrap() + std::str::from_utf8(&phrase(words, false).unwrap().1).unwrap() ); let words = b"Re: [Advcomparch] =?iso-8859-7?b?U2VnIGZhdWx0IPP05+0g5er03evl8+cg9O/1?= @@ -1125,31 +1199,28 @@ mod tests { assert_eq!( "Re: [Advcomparch] Seg fault στην εκτέλεση του example ru n _sniper", - std::str::from_utf8(&phrase(words, false).to_full_result().unwrap()).unwrap() + std::str::from_utf8(&phrase(words, false).unwrap().1).unwrap() ); let words = r#"[internal] =?UTF-8?B?zp3Orc6/z4Igzp/OtM63zrPPjM+CIM6jz4XOs86zz4E=?= =?UTF-8?B?zrHPhs6uz4I=?="#; assert_eq!( "[internal] Νέος Οδηγός Συγγραφής", - std::str::from_utf8(&phrase(words.as_bytes(), false).to_full_result().unwrap()) - .unwrap() + std::str::from_utf8(&phrase(words.as_bytes(), false).unwrap().1).unwrap() ); let words = r#"=?UTF-8?Q?Re=3a_Climate_crisis_reality_check_=e2=80=93=c2=a0EcoHust?= =?UTF-8?Q?ler?="#; assert_eq!( "Re: Climate crisis reality check –\u{a0}EcoHustler", - std::str::from_utf8(&phrase(words.as_bytes(), false).to_full_result().unwrap()) - .unwrap() + std::str::from_utf8(&phrase(words.as_bytes(), false).unwrap().1).unwrap() ); let words = r#"Re: Climate crisis reality check =?windows-1250?B?lqBFY29IdXN0?= =?windows-1250?B?bGVy?="#; assert_eq!( "Re: Climate crisis reality check –\u{a0}EcoHustler", - std::str::from_utf8(&phrase(words.as_bytes(), false).to_full_result().unwrap()) - .unwrap() + std::str::from_utf8(&phrase(words.as_bytes(), false).unwrap().1).unwrap() ); } @@ -1196,11 +1267,11 @@ mod tests { let mut buffer: Vec = Vec::new(); let _ = std::fs::File::open("").unwrap().read_to_end(&mut buffer); let boundary = b"b1_4382d284f0c601a737bb32aaeda53160"; - let (_, body) = match mail(&buffer).to_full_result() { + let (_, body) = match mail(&buffer) { Ok(v) => v, Err(_) => panic!(), }; - let attachments = parts(body, boundary).to_full_result().unwrap(); + let attachments = parts(body, boundary).unwrap().1; assert_eq!(attachments.len(), 4); let v: Vec<&str> = attachments .iter() @@ -1275,9 +1346,8 @@ border=3D=220=22> "#; assert_eq!( quoted_printable_bytes(input.as_bytes()) - .to_full_result() .as_ref() - .map(|b| unsafe { std::str::from_utf8_unchecked(b) }), + .map(|(_, b)| unsafe { std::str::from_utf8_unchecked(b) }), Ok(r#" diff --git a/melib/src/error.rs b/melib/src/error.rs index b1bcc2326..af460b145 100644 --- a/melib/src/error.rs +++ b/melib/src/error.rs @@ -32,8 +32,6 @@ use std::str; use std::string; use std::sync::Arc; -use nom; - pub type Result = result::Result; #[derive(Debug, Clone)] @@ -139,13 +137,6 @@ impl From for MeliError { } } -impl From for MeliError { - #[inline] - fn from(kind: nom::IError) -> MeliError { - MeliError::new(format!("{:?}", kind)) - } -} - impl<'a> From> for MeliError { #[inline] fn from(kind: Cow<'_, str>) -> MeliError { @@ -261,3 +252,19 @@ impl From for MeliError { MeliError::new(kind) } } + +impl From> for MeliError { + #[inline] + fn from(kind: nom::Err<(&[u8], nom::error::ErrorKind)>) -> MeliError { + MeliError::new("Parsing error") + .set_source(Some(Arc::new(MeliError::new(format!("{}", kind))))) + } +} + +impl From> for MeliError { + #[inline] + fn from(kind: nom::Err<(&str, nom::error::ErrorKind)>) -> MeliError { + MeliError::new("Parsing error") + .set_source(Some(Arc::new(MeliError::new(format!("{}", kind))))) + } +} diff --git a/melib/src/lib.rs b/melib/src/lib.rs index 0ebd73374..ddb48a570 100644 --- a/melib/src/lib.rs +++ b/melib/src/lib.rs @@ -122,10 +122,9 @@ pub mod sqlite3; #[macro_use] extern crate serde_derive; /* parser */ -#[macro_use] -extern crate nom; extern crate data_encoding; extern crate encoding; +pub use nom; #[macro_use] extern crate bitflags; diff --git a/src/components/mail/compose.rs b/src/components/mail/compose.rs index 43146c6e0..d113e3c4b 100644 --- a/src/components/mail/compose.rs +++ b/src/components/mail/compose.rs @@ -197,9 +197,8 @@ impl Composer { if let Some(actions) = list_management::ListActions::detect(&parent_message) { if let Some(post) = actions.post { if let list_management::ListAction::Email(list_post_addr) = post[0] { - if let Ok(list_address) = melib::email::parser::mailto(list_post_addr) - .to_full_result() - .map(|m| m.address) + if let Ok(list_address) = melib::email::parser::generic::mailto(list_post_addr) + .map(|(_, m)| m.address) { let list_address_string = list_address.to_string(); ret.mode = ViewMode::SelectRecipients(UIDialog::new( diff --git a/src/components/mail/view.rs b/src/components/mail/view.rs index 037749cda..e2dbbedec 100644 --- a/src/components/mail/view.rs +++ b/src/components/mail/view.rs @@ -701,17 +701,16 @@ impl Component for MailView { let mut ret = op .as_bytes() .and_then(|b| { - melib::email::parser::headers(b) - .to_full_result() + melib::email::parser::headers::headers(b) + .map(|(_, v)| v) .map_err(|err| err.into()) }) .and_then(|headers| { Ok(headers .into_iter() .map(|(h, v)| { - melib::email::parser::phrase(v, true) - .to_full_result() - .map(|v| { + melib::email::parser::encodings::phrase(v, true) + .map(|(_, v)| { let mut h = h.to_vec(); h.push(b':'); h.push(b' '); @@ -1106,8 +1105,8 @@ impl Component for MailView { let attachment_type = u.mime_type(); let binary = query_default_app(&attachment_type); let mut name_opt = name.as_ref().and_then(|n| { - melib::email::parser::phrase(n.as_bytes(), false) - .to_full_result() + melib::email::parser::encodings::phrase(n.as_bytes(), false) + .map(|(_, v)| v) .ok() .and_then(|n| String::from_utf8(n).ok()) }); diff --git a/src/plugins/backend.rs b/src/plugins/backend.rs index 98e979097..b55fab765 100644 --- a/src/plugins/backend.rs +++ b/src/plugins/backend.rs @@ -117,38 +117,43 @@ impl MailBackend for PluginBackend { }| { let mut env = melib::Envelope::new(hash); env.set_date(date.as_bytes()); - if let Ok(d) = melib::email::parser::date(date.as_bytes()) { + if let Ok(d) = + melib::email::parser::generic::date(date.as_bytes()) + { env.set_datetime(d); } env.set_message_id(message_id.as_bytes()); let parse_result = - melib::email::parser::rfc2822address_list( + melib::email::parser::address::rfc2822address_list( from.as_bytes(), ); - if parse_result.is_done() { - let value = parse_result.to_full_result().unwrap(); + if parse_result.is_ok() { + let value = parse_result.unwrap().1; env.set_from(value); } let parse_result = - melib::email::parser::rfc2822address_list( + melib::email::parser::address::rfc2822address_list( to.as_bytes(), ); - if parse_result.is_done() { - let value = parse_result.to_full_result().unwrap(); + if parse_result.is_ok() { + let value = parse_result.unwrap().1; env.set_to(value); } - let parse_result = - melib::email::parser::phrase(subject.as_bytes(), false); - if parse_result.is_done() { - let value = parse_result.to_full_result().unwrap(); + let parse_result = melib::email::parser::encodings::phrase( + subject.as_bytes(), + false, + ); + if parse_result.is_ok() { + let value = parse_result.unwrap().1; env.set_subject(value); } if !references.is_empty() { - let parse_result = melib::email::parser::references( - references.as_bytes(), - ); - if parse_result.is_done() { - for v in parse_result.to_full_result().unwrap() { + let parse_result = + melib::email::parser::address::references( + references.as_bytes(), + ); + if parse_result.is_ok() { + for v in parse_result.unwrap().1 { env.push_references(v); } }