diff --git a/melib/src/addressbook.rs b/melib/src/addressbook.rs index 3ce2fefe..6020eba4 100644 --- a/melib/src/addressbook.rs +++ b/melib/src/addressbook.rs @@ -97,10 +97,13 @@ impl AddressBook { } pub fn with_account(s: &crate::conf::AccountSettings) -> AddressBook { - let mut ret = AddressBook::new(s.name.clone()); - + #[cfg(not(feature = "vcard"))] + { + AddressBook::new(s.name.clone()) + } #[cfg(feature = "vcard")] { + let mut ret = AddressBook::new(s.name.clone()); if let Some(vcard_path) = s.vcard_folder() { if let Ok(cards) = vcard::load_cards(&std::path::Path::new(vcard_path)) { for c in cards { @@ -108,9 +111,8 @@ impl AddressBook { } } } + ret } - - ret } pub fn add_card(&mut self, card: Card) { diff --git a/melib/src/backends/imap/protocol_parser.rs b/melib/src/backends/imap/protocol_parser.rs index 1f3ab0a9..792eb5c4 100644 --- a/melib/src/backends/imap/protocol_parser.rs +++ b/melib/src/backends/imap/protocol_parser.rs @@ -20,6 +20,7 @@ */ use super::*; +use crate::email::address::{Address, MailboxAddress}; use crate::email::parser::{BytesExt, IResult}; use crate::error::ResultIntoMeliError; use crate::get_path_hash; diff --git a/melib/src/backends/jmap/objects/email.rs b/melib/src/backends/jmap/objects/email.rs index 5a464c44..da9bcf68 100644 --- a/melib/src/backends/jmap/objects/email.rs +++ b/melib/src/backends/jmap/objects/email.rs @@ -21,6 +21,7 @@ use super::*; use crate::backends::jmap::rfc8620::bool_false; +use crate::email::address::{Address, MailboxAddress}; use core::marker::PhantomData; use serde::de::{Deserialize, Deserializer}; use serde_json::Value; diff --git a/melib/src/conf.rs b/melib/src/conf.rs index 66ec1823..a78cbc14 100644 --- a/melib/src/conf.rs +++ b/melib/src/conf.rs @@ -18,6 +18,8 @@ * You should have received a copy of the GNU General Public License * along with meli. If not, see . */ + +//! Basic mail account configuration to use with [`backends`](./backends/index.html) use crate::backends::SpecialUsageMailbox; use serde::{Deserialize, Deserializer, Serialize, Serializer}; use std::collections::HashMap; diff --git a/melib/src/connections.rs b/melib/src/connections.rs index 8fdebe69..1d0a1cc3 100644 --- a/melib/src/connections.rs +++ b/melib/src/connections.rs @@ -18,6 +18,8 @@ * You should have received a copy of the GNU General Public License * along with meli. If not, see . */ + +//! Connections layers (TCP/fd/TLS/Deflate) to use with remote backends. #[cfg(feature = "deflate_compression")] use flate2::{read::DeflateDecoder, write::DeflateEncoder, Compression}; diff --git a/melib/src/datetime.rs b/melib/src/datetime.rs index aa267455..dd2e7e83 100644 --- a/melib/src/datetime.rs +++ b/melib/src/datetime.rs @@ -19,11 +19,28 @@ * along with meli. If not, see . */ +//! Functions for dealing with date strings and UNIX Epoch timestamps. +//! +//! # Examples +//! +//! ```rust +//! # use melib::datetime::*; +//! // Get current UNIX Epoch timestamp. +//! let now: UnixTimestamp = now(); +//! +//! // Parse date from string +//! let date_val = "Wed, 8 Jan 2020 10:44:03 -0800"; +//! let timestamp = rfc822_to_timestamp(date_val).unwrap(); +//! assert_eq!(timestamp, 1578509043); +//! +//! // Convert timestamp back to string +//! let s = timestamp_to_string(timestamp, Some("%Y-%m-%d")); +//! assert_eq!(s, "2020-01-08"); +//! ``` +use crate::error::Result; use std::convert::TryInto; use std::ffi::{CStr, CString}; -use crate::error::Result; - pub type UnixTimestamp = u64; use libc::{timeval, timezone}; diff --git a/melib/src/email.rs b/melib/src/email.rs index e950350e..fa03ee54 100644 --- a/melib/src/email.rs +++ b/melib/src/email.rs @@ -22,38 +22,33 @@ /*! * Email parsing, handling, sending etc. */ -use std::convert::TryInto; -mod compose; -pub use self::compose::*; - -pub mod list_management; -mod mailto; -pub use mailto::*; -mod attachment_types; +pub mod address; +pub mod attachment_types; pub mod attachments; -pub use crate::attachments::*; -mod address; -//pub mod parser; +pub mod compose; +pub mod headers; +pub mod list_management; +pub mod mailto; pub mod parser; -use crate::parser::BytesExt; -pub use address::*; -mod headers; pub mod signatures; + +pub use address::{Address, MessageID, References, StrBuild, StrBuilder}; +pub use attachments::{Attachment, AttachmentBuilder}; +pub use compose::{attachment_from_file, Draft}; pub use headers::*; +pub use mailto::*; use crate::datetime::UnixTimestamp; use crate::error::{MeliError, Result}; +use crate::parser::BytesExt; use crate::thread::ThreadNodeHash; use smallvec::SmallVec; use std::borrow::Cow; -use std::cmp::Ordering; use std::collections::hash_map::DefaultHasher; -use std::fmt; +use std::convert::TryInto; use std::hash::Hasher; -use std::option::Option; -use std::str; -use std::string::String; +use std::ops::Deref; bitflags! { #[derive(Default, Serialize, Deserialize)] @@ -81,15 +76,16 @@ impl PartialEq<&str> for Flag { } } +///`Mail` holds both the envelope info of an email in its `envelope` field and the raw bytes that +///describe the email in `bytes`. Its body as an `melib::email::Attachment` can be parsed on demand +///with the `melib::email::Mail::body` method. #[derive(Debug, Clone, Default)] -pub struct EnvelopeWrapper { - envelope: Envelope, - buffer: Vec, +pub struct Mail { + pub envelope: Envelope, + pub bytes: Vec, } -use std::ops::Deref; - -impl Deref for EnvelopeWrapper { +impl Deref for Mail { type Target = Envelope; fn deref(&self) -> &Envelope { @@ -97,56 +93,57 @@ impl Deref for EnvelopeWrapper { } } -impl EnvelopeWrapper { - pub fn new(buffer: Vec) -> Result { - Ok(EnvelopeWrapper { - envelope: Envelope::from_bytes(&buffer, None)?, - buffer, +impl Mail { + pub fn new(bytes: Vec) -> Result { + Ok(Mail { + envelope: Envelope::from_bytes(&bytes, None)?, + bytes, }) } pub fn envelope(&self) -> &Envelope { &self.envelope } - pub fn buffer(&self) -> &[u8] { - &self.buffer + + pub fn bytes(&self) -> &[u8] { + &self.bytes + } + + pub fn body(&self) -> Attachment { + self.envelope.body_bytes(&self.bytes) } } pub type EnvelopeHash = u64; -/// `Envelope` represents all the data of an email we need to know. +/// `Envelope` represents all the header and structure data of an email we need to know. /// -/// Attachments (the email's body) is parsed on demand with `body`. +/// Attachments (the email's body) is parsed on demand with `body` method. /// -/// Access to the underlying email object in the account's backend (for example the file or the -/// entry in an IMAP server) is given through `operation_token`. For more information see -/// `BackendOp`. +///To access the email attachments, you need to parse them from the raw email bytes into an +///`Attachment` object. #[derive(Clone, Serialize, Deserialize)] pub struct Envelope { - date: String, - from: SmallVec<[Address; 1]>, - to: SmallVec<[Address; 1]>, - cc: SmallVec<[Address; 1]>, - bcc: Vec
, - subject: Option, - message_id: MessageID, - in_reply_to: Option, + pub hash: EnvelopeHash, + pub date: String, + pub timestamp: UnixTimestamp, + pub from: SmallVec<[Address; 1]>, + pub to: SmallVec<[Address; 1]>, + pub cc: SmallVec<[Address; 1]>, + pub bcc: Vec
, + pub subject: Option, + pub message_id: MessageID, + pub in_reply_to: Option, pub references: Option, - other_headers: HeaderMap, - - timestamp: UnixTimestamp, - thread: ThreadNodeHash, - - hash: EnvelopeHash, - - flags: Flag, - has_attachments: bool, - labels: SmallVec<[u64; 8]>, + pub other_headers: HeaderMap, + pub thread: ThreadNodeHash, + pub flags: Flag, + pub has_attachments: bool, + pub labels: SmallVec<[u64; 8]>, } -impl fmt::Debug for Envelope { - fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { +impl core::fmt::Debug for Envelope { + fn fmt(&self, f: &mut core::fmt::Formatter) -> core::fmt::Result { f.debug_struct("Envelope") .field("Subject", &self.subject()) .field("Date", &self.date) @@ -169,7 +166,9 @@ impl Default for Envelope { impl Envelope { pub fn new(hash: EnvelopeHash) -> Self { Envelope { + hash, date: String::new(), + timestamp: 0, from: SmallVec::new(), to: SmallVec::new(), cc: SmallVec::new(), @@ -179,12 +178,7 @@ impl Envelope { in_reply_to: None, references: None, other_headers: Default::default(), - - timestamp: 0, - thread: ThreadNodeHash::null(), - - hash, has_attachments: false, flags: Flag::default(), labels: SmallVec::new(), @@ -212,6 +206,7 @@ impl Envelope { pub fn hash(&self) -> EnvelopeHash { self.hash } + pub fn populate_headers(&mut self, mut bytes: &[u8]) -> Result<()> { if bytes.starts_with(b"From ") { /* Attempt to recover if message includes the mbox From label as first line */ @@ -352,9 +347,11 @@ impl Envelope { pub fn date_as_str(&self) -> &str { &self.date } + pub fn from(&self) -> &[Address] { self.from.as_slice() } + pub fn field_bcc_to_string(&self) -> String { if self.bcc.is_empty() { self.other_headers @@ -372,6 +369,7 @@ impl Envelope { }) } } + pub fn field_cc_to_string(&self) -> String { if self.cc.is_empty() { self.other_headers @@ -389,6 +387,7 @@ impl Envelope { }) } } + pub fn field_from_to_string(&self) -> String { if self.from.is_empty() { self.other_headers @@ -406,9 +405,11 @@ impl Envelope { }) } } + pub fn to(&self) -> &[Address] { self.to.as_slice() } + pub fn field_to_to_string(&self) -> String { if self.to.is_empty() { self.other_headers @@ -429,6 +430,7 @@ impl Envelope { }) } } + pub fn field_references_to_string(&self) -> String { let refs = self.references(); if refs.is_empty() { @@ -455,7 +457,6 @@ impl Envelope { builder.build() } - /// Requests bytes from backend and thus can fail pub fn headers<'a>(&self, bytes: &'a [u8]) -> Result> { let ret = parser::headers::headers(bytes)?.1; let len = ret.len(); @@ -486,36 +487,46 @@ impl Envelope { .as_ref() .map(|m| String::from_utf8_lossy(m.val())) } + pub fn in_reply_to_raw(&self) -> Option> { self.in_reply_to .as_ref() .map(|m| String::from_utf8_lossy(m.raw())) } + pub fn message_id(&self) -> &MessageID { &self.message_id } + pub fn message_id_display(&self) -> Cow { String::from_utf8_lossy(self.message_id.val()) } + pub fn message_id_raw(&self) -> Cow { String::from_utf8_lossy(self.message_id.raw()) } + pub fn set_date(&mut self, new_val: &[u8]) { let new_val = new_val.trim(); self.date = String::from_utf8_lossy(new_val).into_owned(); } + pub fn set_bcc(&mut self, new_val: Vec
) { self.bcc = new_val; } + pub fn set_cc(&mut self, new_val: SmallVec<[Address; 1]>) { self.cc = new_val; } + pub fn set_from(&mut self, new_val: SmallVec<[Address; 1]>) { self.from = new_val; } + pub fn set_to(&mut self, new_val: SmallVec<[Address; 1]>) { self.to = new_val; } + pub fn set_in_reply_to(&mut self, new_val: &[u8]) { // FIXME msg_id_list let new_val = new_val.trim(); @@ -528,6 +539,7 @@ impl Envelope { }; self.in_reply_to = Some(val); } + pub fn set_subject(&mut self, new_val: Vec) { let mut new_val = String::from_utf8(new_val) .unwrap_or_else(|err| String::from_utf8_lossy(&err.into_bytes()).into()); @@ -542,6 +554,7 @@ impl Envelope { self.subject = Some(new_val); } + pub fn set_message_id(&mut self, new_val: &[u8]) { let new_val = new_val.trim(); match parser::address::msg_id(new_val) { @@ -553,6 +566,7 @@ impl Envelope { } } } + pub fn push_references(&mut self, new_ref: MessageID) { match self.references { Some(ref mut s) => { @@ -578,6 +592,7 @@ impl Envelope { } } } + pub fn set_references(&mut self, new_val: &[u8]) { let new_val = new_val.trim(); match self.references { @@ -592,6 +607,7 @@ impl Envelope { } } } + pub fn references(&self) -> SmallVec<[&MessageID; 8]> { match self.references { Some(ref s) => s.refs.iter().fold(SmallVec::new(), |mut acc, x| { @@ -613,27 +629,35 @@ impl Envelope { pub fn thread(&self) -> ThreadNodeHash { self.thread } + pub fn set_thread(&mut self, new_val: ThreadNodeHash) { self.thread = new_val; } + pub fn set_datetime(&mut self, new_val: UnixTimestamp) { self.timestamp = new_val; } + pub fn set_flag(&mut self, f: Flag, value: bool) { self.flags.set(f, value); } + pub fn set_flags(&mut self, f: Flag) { self.flags = f; } + pub fn flags(&self) -> Flag { self.flags } + pub fn set_seen(&mut self) { self.set_flag(Flag::SEEN, true) } + pub fn set_unseen(&mut self) { self.set_flag(Flag::SEEN, false) } + pub fn is_seen(&self) -> bool { self.flags.contains(Flag::SEEN) } @@ -656,14 +680,15 @@ impl Envelope { } impl Eq for Envelope {} + impl Ord for Envelope { - fn cmp(&self, other: &Envelope) -> Ordering { + fn cmp(&self, other: &Envelope) -> std::cmp::Ordering { self.datetime().cmp(&other.datetime()) } } impl PartialOrd for Envelope { - fn partial_cmp(&self, other: &Envelope) -> Option { + fn partial_cmp(&self, other: &Envelope) -> Option { Some(self.cmp(other)) } } diff --git a/melib/src/email/address.rs b/melib/src/email/address.rs index 8cfc24ea..2c41b67c 100644 --- a/melib/src/email/address.rs +++ b/melib/src/email/address.rs @@ -19,6 +19,7 @@ * along with meli. If not, see . */ +//! Email addresses. Parsing functions are in [melib::email::parser::address](../parser/address/index.html). use super::*; use std::collections::HashSet; use std::convert::TryFrom; @@ -60,6 +61,35 @@ pub struct MailboxAddress { pub address_spec: StrBuilder, } +impl Eq for MailboxAddress {} + +impl PartialEq for MailboxAddress { + fn eq(&self, other: &MailboxAddress) -> bool { + self.address_spec.display_bytes(&self.raw) == other.address_spec.display_bytes(&other.raw) + } +} + +/// An email address. +/// +/// Conforms to [RFC5322 - Internet Message Format](https://tools.ietf.org/html/rfc5322). +/// +/// # Creating an `Address` +/// You can directly create an address with `Address::new`, +/// +/// ```rust +/// # use melib::email::Address; +/// let addr = Address::new(Some("Jörg Doe".to_string()), "joerg@example.com".to_string()); +/// assert_eq!(addr.to_string().as_str(), "Jörg Doe "); +/// ``` +/// +/// or parse it from a raw value: +/// +/// ```rust +/// let (rest_bytes, addr) = melib::email::parser::address::address("=?utf-8?q?J=C3=B6rg_Doe?= ".as_bytes()).unwrap(); +/// assert!(rest_bytes.is_empty()); +/// assert_eq!(addr.get_display_name(), "Jörg Doe"); +/// assert_eq!(addr.get_email(), "joerg@example.com"); +/// ``` #[derive(Clone, Serialize, Deserialize)] pub enum Address { Mailbox(MailboxAddress), @@ -121,6 +151,22 @@ impl Address { Address::Group(g) => g.raw.as_slice(), } } + + /// Get the display name of this address. + /// + /// If it's a group, it's the name of the group. Otherwise it's the `display_name` part of + /// the mailbox: + /// + /// + /// ```text + /// raw raw + /// ┌──────────┴────────────┐ ┌──────────┴────────────────────┐ + /// Name "Name Name2" + /// └─┬┘ └──────────┬─────┘ └─────┬──┘ └──────────┬─────┘ + /// display_name │ display_name │ + /// │ │ + /// address_spec address_spec + ///``` pub fn get_display_name(&self) -> String { match self { Address::Mailbox(m) => m.display_name.display(&m.raw), @@ -128,6 +174,7 @@ impl Address { } } + /// Get the address spec part of this address. A group returns an empty `String`. pub fn get_email(&self) -> String { match self { Address::Mailbox(m) => m.address_spec.display(&m.raw), @@ -176,15 +223,14 @@ impl Address { } impl Eq for Address {} + impl PartialEq for Address { fn eq(&self, other: &Address) -> bool { match (self, other) { (Address::Mailbox(_), Address::Group(_)) | (Address::Group(_), Address::Mailbox(_)) => { false } - (Address::Mailbox(s), Address::Mailbox(o)) => { - s.address_spec.display_bytes(&s.raw) == o.address_spec.display_bytes(&o.raw) - } + (Address::Mailbox(s), Address::Mailbox(o)) => s == o, (Address::Group(s), Address::Group(o)) => { s.display_name.display_bytes(&s.raw) == o.display_name.display_bytes(&o.raw) && s.mailbox_list.iter().collect::>() @@ -210,8 +256,8 @@ impl Hash for Address { } } -impl fmt::Display for Address { - fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { +impl core::fmt::Display for Address { + fn fmt(&self, f: &mut core::fmt::Formatter) -> core::fmt::Result { match self { Address::Mailbox(m) if m.display_name.length > 0 => write!( f, @@ -234,8 +280,8 @@ impl fmt::Display for Address { } } -impl fmt::Debug for Address { - fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { +impl core::fmt::Debug for Address { + fn fmt(&self, f: &mut core::fmt::Formatter) -> core::fmt::Result { match self { Address::Mailbox(m) => f .debug_struct("Address::Mailbox") @@ -332,10 +378,12 @@ fn test_strbuilder() { ); } -impl fmt::Display for MessageID { - fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { +impl core::fmt::Display for MessageID { + fn fmt(&self, f: &mut core::fmt::Formatter) -> core::fmt::Result { if self.val().is_ascii() { - write!(f, "{}", unsafe { str::from_utf8_unchecked(self.val()) }) + write!(f, "{}", unsafe { + std::str::from_utf8_unchecked(self.val()) + }) } else { write!(f, "{}", String::from_utf8_lossy(self.val())) } @@ -347,8 +395,8 @@ impl PartialEq for MessageID { self.raw() == other.raw() } } -impl fmt::Debug for MessageID { - fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { +impl core::fmt::Debug for MessageID { + fn fmt(&self, f: &mut core::fmt::Formatter) -> core::fmt::Result { write!(f, "{}", String::from_utf8(self.raw().to_vec()).unwrap()) } } @@ -359,8 +407,8 @@ pub struct References { pub refs: Vec, } -impl fmt::Debug for References { - fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { +impl core::fmt::Debug for References { + fn fmt(&self, f: &mut core::fmt::Formatter) -> core::fmt::Result { write!(f, "{:#?}", self.refs) } } diff --git a/melib/src/email/attachments.rs b/melib/src/email/attachments.rs index 95a604ee..bb5466a9 100644 --- a/melib/src/email/attachments.rs +++ b/melib/src/email/attachments.rs @@ -18,15 +18,17 @@ * You should have received a copy of the GNU General Public License * along with meli. If not, see . */ -use crate::email::address::StrBuilder; -use crate::email::parser; -use crate::email::parser::BytesExt; -use crate::email::EnvelopeWrapper; + +use crate::email::{ + address::StrBuilder, + parser::{self, BytesExt}, + Mail, +}; use core::fmt; use core::str; use data_encoding::BASE64_MIME; -pub use crate::email::attachment_types::*; +use crate::email::attachment_types::*; #[derive(Default, Debug, Clone, Serialize, Deserialize, PartialEq, Eq)] pub struct AttachmentBuilder { @@ -360,7 +362,7 @@ impl fmt::Display for Attachment { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { match self.content_type { ContentType::MessageRfc822 => { - match EnvelopeWrapper::new(self.body.display_bytes(&self.raw).to_vec()) { + match Mail::new(self.body.display_bytes(&self.raw).to_vec()) { Ok(wrapper) => write!( f, "message/rfc822: {} - {} - {}", diff --git a/melib/src/email/compose.rs b/melib/src/email/compose.rs index a664e6b1..921449f4 100644 --- a/melib/src/email/compose.rs +++ b/melib/src/email/compose.rs @@ -20,7 +20,10 @@ */ use super::*; -use crate::email::attachments::AttachmentBuilder; +use crate::email::attachment_types::{ + Charset, ContentTransferEncoding, ContentType, MultipartType, +}; +use crate::email::attachments::{decode, decode_rec, AttachmentBuilder}; use crate::shellexpand::ShellExpandTrait; use data_encoding::BASE64_MIME; use std::ffi::OsStr; diff --git a/melib/src/email/parser.rs b/melib/src/email/parser.rs index ee25e82b..089493ad 100644 --- a/melib/src/email/parser.rs +++ b/melib/src/email/parser.rs @@ -1667,6 +1667,13 @@ pub mod encodings { } pub mod address { + //! Parsing of address values and address-related headers. + //! + //! Implemented RFCs: + //! + //! - [RFC5322 "Internet Message Format"](https://tools.ietf.org/html/rfc5322) + //! - [RFC6532 "Internationalized Email Headers"](https://tools.ietf.org/html/rfc6532) + //! - [RFC2047 "MIME Part Three: Message Header Extensions for Non-ASCII Text"](https://tools.ietf.org/html/rfc2047) use super::*; use crate::email::address::*; use crate::email::parser::generic::{ @@ -1775,7 +1782,7 @@ pub mod address { } ///`angle-addr = [CFWS] "<" addr-spec ">" [CFWS] / obs-angle-addr` - fn angle_addr(input: &[u8]) -> IResult<&[u8], Address> { + pub fn angle_addr(input: &[u8]) -> IResult<&[u8], Address> { let (input, _) = opt(cfws)(input)?; let (input, _) = tag("<")(input)?; let (input, addr_spec) = addr_spec(input)?; @@ -1784,66 +1791,66 @@ pub mod address { Ok((input, addr_spec)) } + ///`obs-domain = atom *("." atom)` + pub fn obs_domain(input: &[u8]) -> IResult<&[u8], Cow<'_, [u8]>> { + let (mut input, atom_) = context("obs_domain", atom)(input)?; + let mut ret: Vec = atom_.into(); + loop { + if !input.starts_with(b".") { + break; + } + ret.push(b'.'); + input = &input[1..]; + if let Ok((_input, atom_)) = context("obs_domain", atom)(input) { + ret.extend_from_slice(&atom_); + input = _input; + } else { + return Err(nom::Err::Error( + (input, "obs_domain(): expected after DOT").into(), + )); + } + } + Ok((input, ret.into())) + } + + ///`local-part = dot-atom / quoted-string / obs-local-part` + pub fn local_part(input: &[u8]) -> IResult<&[u8], Cow<'_, [u8]>> { + alt((dot_atom, quoted_string))(input) + } + + ///`domain = dot-atom / domain-literal / obs-domain` + pub fn domain(input: &[u8]) -> IResult<&[u8], Cow<'_, [u8]>> { + alt((dot_atom, domain_literal, obs_domain))(input) + } + + ///`domain-literal = [CFWS] "[" *([FWS] dtext) [FWS] "]" [CFWS]` + pub fn domain_literal(input: &[u8]) -> IResult<&[u8], Cow<'_, [u8]>> { + use crate::email::parser::generic::fws; + let (input, first_opt_space) = context("domain_literal()", opt(cfws))(input)?; + let (input, _) = context("domain_literal()", tag("["))(input)?; + let (input, dtexts) = many0(pair(opt(fws), dtext))(input)?; + let (input, end_fws): (_, Option<_>) = context("domain_literal()", opt(fws))(input)?; + let (input, _) = context("domain_literal()", tag("]"))(input)?; + let (input, _) = context("domain_literal()", opt(cfws))(input)?; + let mut ret_s = vec![b'[']; + if let Some(first_opt_space) = first_opt_space { + ret_s.extend_from_slice(&first_opt_space); + } + for (fws_opt, dtext) in dtexts { + if let Some(fws_opt) = fws_opt { + ret_s.extend_from_slice(&fws_opt); + } + ret_s.push(dtext); + } + if let Some(end_fws) = end_fws { + ret_s.extend_from_slice(&end_fws); + } + ret_s.push(b']'); + Ok((input, ret_s.into())) + } + ///`addr-spec = local-part "@" domain` pub fn addr_spec(input: &[u8]) -> IResult<&[u8], Address> { - ///`obs-domain = atom *("." atom)` - fn obs_domain(input: &[u8]) -> IResult<&[u8], Cow<'_, [u8]>> { - let (mut input, atom_) = context("obs_domain", atom)(input)?; - let mut ret: Vec = atom_.into(); - loop { - if !input.starts_with(b".") { - break; - } - ret.push(b'.'); - input = &input[1..]; - if let Ok((_input, atom_)) = context("obs_domain", atom)(input) { - ret.extend_from_slice(&atom_); - input = _input; - } else { - return Err(nom::Err::Error( - (input, "obs_domain(): expected after DOT").into(), - )); - } - } - Ok((input, ret.into())) - } - - ///`local-part = dot-atom / quoted-string / obs-local-part` - fn local_part(input: &[u8]) -> IResult<&[u8], Cow<'_, [u8]>> { - alt((dot_atom, quoted_string))(input) - } - - ///`domain = dot-atom / domain-literal / obs-domain` - fn domain(input: &[u8]) -> IResult<&[u8], Cow<'_, [u8]>> { - alt((dot_atom, domain_literal, obs_domain))(input) - } - - ///`domain-literal = [CFWS] "[" *([FWS] dtext) [FWS] "]" [CFWS]` - fn domain_literal(input: &[u8]) -> IResult<&[u8], Cow<'_, [u8]>> { - use crate::email::parser::generic::fws; - let (input, first_opt_space) = context("domain_literal()", opt(cfws))(input)?; - let (input, _) = context("domain_literal()", tag("["))(input)?; - let (input, dtexts) = many0(pair(opt(fws), dtext))(input)?; - let (input, end_fws): (_, Option<_>) = context("domain_literal()", opt(fws))(input)?; - let (input, _) = context("domain_literal()", tag("]"))(input)?; - let (input, _) = context("domain_literal()", opt(cfws))(input)?; - let mut ret_s = vec![b'[']; - if let Some(first_opt_space) = first_opt_space { - ret_s.extend_from_slice(&first_opt_space); - } - for (fws_opt, dtext) in dtexts { - if let Some(fws_opt) = fws_opt { - ret_s.extend_from_slice(&fws_opt); - } - ret_s.push(dtext); - } - if let Some(end_fws) = end_fws { - ret_s.extend_from_slice(&end_fws); - } - ret_s.push(b']'); - Ok((input, ret_s.into())) - } - let (input, local_part) = context("addr_spec()", local_part)(input)?; let (input, _) = context("addr_spec()", tag("@"))(input)?; let (input, domain) = context("addr_spec()", domain)(input)?; @@ -1857,6 +1864,17 @@ pub mod address { )) } + ///Returns the raw `local_part` and `domain` parts. + /// + ///`addr-spec = local-part "@" domain` + pub fn addr_spec_raw(input: &[u8]) -> IResult<&[u8], (Cow<'_, [u8]>, Cow<'_, [u8]>)> { + let (input, local_part) = context("addr_spec()", local_part)(input)?; + let (input, _) = context("addr_spec()", tag("@"))(input)?; + let (input, domain) = context("addr_spec()", domain)(input)?; + + Ok((input, (local_part, domain))) + } + ///`display-name = phrase` pub fn display_name(input: &[u8]) -> IResult<&[u8], Vec> { let (rest, ret) = phrase2(input)?; diff --git a/melib/src/email/signatures.rs b/melib/src/email/signatures.rs index cf6ce4ac..e53b19f8 100644 --- a/melib/src/email/signatures.rs +++ b/melib/src/email/signatures.rs @@ -19,8 +19,11 @@ * along with meli. If not, see . */ -use crate::email::attachments::{Attachment, ContentType, MultipartType}; use crate::email::parser::BytesExt; +use crate::email::{ + attachment_types::{ContentType, MultipartType}, + attachments::Attachment, +}; use crate::{MeliError, Result}; /// rfc3156 diff --git a/melib/src/lib.rs b/melib/src/lib.rs index b2f1b59a..dafe33e9 100644 --- a/melib/src/lib.rs +++ b/melib/src/lib.rs @@ -20,21 +20,19 @@ */ //! A crate that performs mail client operations such as -//! - Hold an `Envelope` with methods convenient for mail client use. (see module `email`) -//! - Abstract through mail storages through the `MailBackend` trait, and handle -//! read/writes/updates through it. (see module `melib::backends`) -//! - Decode attachments (see module `melib::email::attachments`) -//! - Create new mail (see `email::Draft`) -//! - Send mail with an SMTP client (see module `smtp`) -//! - Manage an `addressbook` i.e. have contacts (see module `addressbook`) -//! - Build thread structures out of a list of mail via their `In-Reply-To` and `References` header -//! values (see module `thread`) +//! - Hold an [`Envelope`](./email/struct.Envelope.html) with methods convenient for mail client use. (see module [`email`](./email/index.html)) +//! - Abstract through mail storages through the [`MailBackend`](./backends/trait.MailBackend.html) trait, and handle read/writes/updates through it. (see module [`backends`](./backends/index.html)) +//! - Decode attachments (see module [`email::attachments`](./email/attachments/index.html)) +//! - Create new mail (see [`email::Draft`](./email/compose/struct.Draft.html)) +//! - Send mail with an SMTP client (see module [`smtp`](./smtp/index.html)) +//! - Manage an `addressbook` i.e. have contacts (see module [`addressbook`](./addressbook/index.html)) +//! - Build thread structures out of a list of mail via their `In-Reply-To` and `References` header values (see module [`thread`](./thread/index.html)) //! //! Other exports are -//! - Basic mail account configuration to use with `backends` (see module `conf`) -//! - Parser combinators (see module `parsec`) +//! - Basic mail account configuration to use with [`backends`](./backends/index.html) (see module [`conf`](./conf/index.html)) +//! - Parser combinators (see module [`parsec`](./parsec/index.html)) //! - A `ShellExpandTrait` to expand paths like a shell. -//! - A `debug` macro that works like `std::dbg` but for multiple threads. (see `dbg` module) +//! - A `debug` macro that works like `std::dbg` but for multiple threads. (see [`debug` macro](./macro.debug.html)) #[macro_use] pub mod dbg { @@ -104,14 +102,19 @@ pub use self::logging::LoggingLevel::*; pub use self::logging::*; pub mod addressbook; +pub use addressbook::*; pub mod backends; +pub use backends::*; mod collection; +pub use collection::*; pub mod conf; +pub use conf::*; pub mod email; +pub use email::*; pub mod error; +pub use crate::error::*; pub mod thread; -pub use crate::email::*; -pub use crate::thread::*; +pub use thread::*; pub mod connections; pub mod parsec; pub mod search; @@ -126,26 +129,15 @@ extern crate serde_derive; /* parser */ extern crate data_encoding; extern crate encoding; -pub use nom; +pub extern crate nom; #[macro_use] extern crate bitflags; +pub extern crate futures; pub extern crate indexmap; -extern crate uuid; -pub use smallvec; - -pub use futures; -pub use smol; - -pub use crate::backends::{ - BackendEvent, BackendEventConsumer, Backends, RefreshEvent, SpecialUsageMailbox, -}; -pub use crate::collection::*; -pub use crate::conf::*; -pub use crate::email::{Envelope, EnvelopeHash, Flag}; -pub use crate::error::{IntoMeliError, MeliError, Result, ResultIntoMeliError}; - -pub use crate::addressbook::*; +pub extern crate smallvec; +pub extern crate smol; +pub extern crate uuid; pub use shellexpand::ShellExpandTrait; pub mod shellexpand { diff --git a/melib/src/parsec.rs b/melib/src/parsec.rs index 9ac717b8..53d4d331 100644 --- a/melib/src/parsec.rs +++ b/melib/src/parsec.rs @@ -19,6 +19,8 @@ * along with meli. If not, see . */ +//! Parser combinators. + pub type Result<'a, Output> = std::result::Result<(&'a str, Output), &'a str>; pub trait Parser<'a, Output> { diff --git a/src/bin.rs b/src/bin.rs index d22e4513..e26ef9ed 100644 --- a/src/bin.rs +++ b/src/bin.rs @@ -309,7 +309,7 @@ fn run_app(opt: Opt) -> Result<()> { if let Some(SubCommand::View { path }) = opt.subcommand { let bytes = std::fs::read(&path) .chain_err_summary(|| format!("Could not read from `{}`", path.display()))?; - let wrapper = EnvelopeWrapper::new(bytes) + let wrapper = Mail::new(bytes) .chain_err_summary(|| format!("Could not parse `{}`", path.display()))?; state = State::new( Some(Settings::without_accounts().unwrap_or_default()), diff --git a/src/components/mail.rs b/src/components/mail.rs index 44642330..0aa19489 100644 --- a/src/components/mail.rs +++ b/src/components/mail.rs @@ -23,6 +23,7 @@ */ use super::*; use melib::backends::{AccountHash, Mailbox, MailboxHash}; +use melib::email::{attachment_types::*, attachments::*}; use melib::thread::ThreadNodeHash; pub mod listing; diff --git a/src/components/mail/compose.rs b/src/components/mail/compose.rs index 85b2ab45..84f79b7c 100644 --- a/src/components/mail/compose.rs +++ b/src/components/mail/compose.rs @@ -20,6 +20,7 @@ */ use super::*; +use melib::email::attachment_types::{ContentType, MultipartType}; use melib::list_management; use melib::Draft; diff --git a/src/components/mail/status.rs b/src/components/mail/status.rs index 4a427bc6..eb047eb0 100644 --- a/src/components/mail/status.rs +++ b/src/components/mail/status.rs @@ -481,7 +481,6 @@ impl Component for AccountStatus { None, ); - use melib::backends::MailBackendExtensionStatus; let (width, height) = self.content.size(); let (x, y) = match status { MailBackendExtensionStatus::Unsupported { comment: _ } => write_string_to_grid( diff --git a/src/components/mail/view.rs b/src/components/mail/view.rs index 3a04452a..dfc55c6e 100644 --- a/src/components/mail/view.rs +++ b/src/components/mail/view.rs @@ -481,7 +481,7 @@ impl MailView { } } else { match u.content_type() { - ContentType::MessageRfc822 => match EnvelopeWrapper::new(u.body().to_vec()) { + ContentType::MessageRfc822 => match Mail::new(u.body().to_vec()) { Ok(wrapper) => { context .replies diff --git a/src/components/mail/view/envelope.rs b/src/components/mail/view/envelope.rs index d33e7775..d50ef4ac 100644 --- a/src/components/mail/view/envelope.rs +++ b/src/components/mail/view/envelope.rs @@ -51,7 +51,7 @@ pub struct EnvelopeView { subview: Option>, dirty: bool, mode: ViewMode, - wrapper: EnvelopeWrapper, + mail: Mail, account_hash: AccountHash, cmd_buf: String, @@ -66,7 +66,7 @@ impl fmt::Display for EnvelopeView { impl EnvelopeView { pub fn new( - wrapper: EnvelopeWrapper, + mail: Mail, pager: Option, subview: Option>, account_hash: AccountHash, @@ -76,7 +76,7 @@ impl EnvelopeView { subview, dirty: true, mode: ViewMode::Normal, - wrapper, + mail, account_hash, cmd_buf: String::with_capacity(4), id: ComponentId::new_v4(), @@ -225,15 +225,13 @@ impl Component for EnvelopeView { let bottom_right = bottom_right!(area); let y: usize = { - let envelope: &Envelope = &self.wrapper; - if self.mode == ViewMode::Raw { clear_area(grid, area, crate::conf::value(context, "theme_default")); context.dirty_areas.push_back(area); get_y(upper_left).saturating_sub(1) } else { let (x, y) = write_string_to_grid( - &format!("Date: {}", envelope.date_as_str()), + &format!("Date: {}", self.mail.date_as_str()), grid, Color::Byte(33), Color::Default, @@ -247,7 +245,7 @@ impl Component for EnvelopeView { grid[(x, y)].set_fg(Color::Default); } let (x, y) = write_string_to_grid( - &format!("From: {}", envelope.field_from_to_string()), + &format!("From: {}", self.mail.field_from_to_string()), grid, Color::Byte(33), Color::Default, @@ -261,7 +259,7 @@ impl Component for EnvelopeView { grid[(x, y)].set_fg(Color::Default); } let (x, y) = write_string_to_grid( - &format!("To: {}", envelope.field_to_to_string()), + &format!("To: {}", self.mail.field_to_to_string()), grid, Color::Byte(33), Color::Default, @@ -275,7 +273,7 @@ impl Component for EnvelopeView { grid[(x, y)].set_fg(Color::Default); } let (x, y) = write_string_to_grid( - &format!("Subject: {}", envelope.subject()), + &format!("Subject: {}", self.mail.subject()), grid, Color::Byte(33), Color::Default, @@ -289,7 +287,7 @@ impl Component for EnvelopeView { grid[(x, y)].set_fg(Color::Default); } let (x, y) = write_string_to_grid( - &format!("Message-ID: <{}>", envelope.message_id_raw()), + &format!("Message-ID: <{}>", self.mail.message_id_raw()), grid, Color::Byte(33), Color::Default, @@ -315,7 +313,7 @@ impl Component for EnvelopeView { }; if self.dirty { - let body = self.wrapper.body_bytes(self.wrapper.buffer()); + let body = self.mail.body(); match self.mode { ViewMode::Attachment(aidx) if body.attachments()[aidx].is_html() => { let attachment = &body.attachments()[aidx]; @@ -409,12 +407,7 @@ impl Component for EnvelopeView { .push_back(UIEvent::StatusEvent(StatusEvent::BufClear)); { - let envelope: &Envelope = self.wrapper.envelope(); - if let Some(u) = envelope - .body_bytes(self.wrapper.buffer()) - .attachments() - .get(lidx) - { + if let Some(u) = self.mail.body().attachments().get(lidx) { match u.content_type() { ContentType::MessageRfc822 => { self.mode = ViewMode::Subview; @@ -518,9 +511,8 @@ impl Component for EnvelopeView { .replies .push_back(UIEvent::StatusEvent(StatusEvent::BufClear)); let url = { - let envelope: &Envelope = self.wrapper.envelope(); let finder = LinkFinder::new(); - let t = envelope.body_bytes(self.wrapper.buffer()).text(); + let t = self.mail.body().text(); let links: Vec = finder.links(&t).collect(); if let Some(u) = links.get(lidx) { u.as_str().to_string() diff --git a/tests/generating_email.rs b/tests/generating_email.rs index 04b94a9d..e44a2d0c 100644 --- a/tests/generating_email.rs +++ b/tests/generating_email.rs @@ -9,7 +9,7 @@ fn build_draft() { .expect("Could not open test_image.gif."); if let Ok(mime_type) = query_mime_info("./tests/test_image.gif") { match attachment.content_type { - melib::email::ContentType::Other { ref mut tag, .. } => { + melib::email::attachment_types::ContentType::Other { ref mut tag, .. } => { *tag = mime_type; } _ => {} diff --git a/tools/src/smtp_conn.rs b/tools/src/smtp_conn.rs index d7195372..a8665f82 100644 --- a/tools/src/smtp_conn.rs +++ b/tools/src/smtp_conn.rs @@ -5,28 +5,17 @@ use melib::smol; use melib::smtp::*; use melib::Result; -/// Opens an interactive shell on an IMAP server. Suggested use is with rlwrap(1) -/// -/// # Example invocation: -/// ```sh -/// ./imap_conn server_hostname server_username server_password server_port"); -/// ``` -/// -/// `danger_accept_invalid_certs` is turned on by default, so no certificate validation is performed. - fn main() -> Result<()> { let conf = SmtpServerConf { - hostname: "smtp1.ntua.gr".into(), + hostname: "smtp1.example.com".into(), port: 587, security: SmtpSecurity::StartTLS { danger_accept_invalid_certs: false, }, extensions: SmtpExtensionSupport::default(), auth: SmtpAuth::Auto { - username: "el13635".into(), - password: Password::CommandEval( - "gpg2 --no-tty -q -d ~/.passwords/msmtp/ntua.gpg".into(), - ), + username: "username".into(), + password: Password::CommandEval("gpg2 --no-tty -q -d ~/.passwords/password.gpg".into()), require_auth: true, }, envelope_from: String::new(), @@ -37,14 +26,15 @@ fn main() -> Result<()> { let mut conn = futures::executor::block_on(SmtpConnection::new_connection(conf)).unwrap(); futures::executor::block_on(conn.mail_transaction( - r##"To: pr.birch@gmail.com + r##"To: username@example.com Auto-Submitted: auto-generated Subject: Fwd: *** SMTP TEST #2 information *** -From: Manos -Message-Id: +From: Xxxxx +Message-Id: Date: Mon, 13 Jul 2020 15:02:15 +0300 -postretch : May 20 18:02:00 : epilys : user NOT in sudoers ; TTY=pts/13 ; PWD=/tmp/db-project ; USER=postgres ; COMMAND=/usr/bin/dropdb Prescriptions-R-X"##, +machine : May 20 18:02:00 : user : user NOT in sudoers ; TTY=pts/13 ; PWD=/tmp/db-project ; USER=postgres ; COMMAND=/usr/bin/dropdb Prescriptions-R-X"##, +None )).unwrap(); Ok(()) }