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(())
}