From e407b1e22489b8898b4b672e2aab89a3ca28a401 Mon Sep 17 00:00:00 2001 From: Manos Pitsidianakis Date: Thu, 24 Sep 2020 16:51:51 +0300 Subject: [PATCH] melib: add README.md and email module doco --- melib/Cargo.toml | 7 +-- melib/README.md | 83 ++++++++++++++++++++++++++++++ melib/src/email.rs | 69 ++++++++++++++++++++++++- melib/src/email/attachments.rs | 1 + melib/src/email/compose.rs | 1 + melib/src/email/headers.rs | 1 + melib/src/email/list_management.rs | 1 + melib/src/email/mailto.rs | 1 + melib/src/email/parser.rs | 6 +++ melib/src/email/signatures.rs | 14 +++-- 10 files changed, 175 insertions(+), 9 deletions(-) create mode 100644 melib/README.md diff --git a/melib/Cargo.toml b/melib/Cargo.toml index 2213a74aa..0da14196e 100644 --- a/melib/Cargo.toml +++ b/melib/Cargo.toml @@ -8,10 +8,11 @@ build = "build.rs" homepage = "https://meli.delivery" repository = "https://git.meli.delivery/meli/meli.git" -description = "backend mail client library" -keywords = ["mail", "mua", "maildir", "imap"] -categories = [ "email"] +description = "mail library" +keywords = ["mail", "mua", "maildir", "imap", "jmap"] +categories = [ "email", "parser-implementations"] license = "GPL-3.0-or-later" +readme = "README.md" [lib] name = "melib" diff --git a/melib/README.md b/melib/README.md new file mode 100644 index 000000000..b8d82caba --- /dev/null +++ b/melib/README.md @@ -0,0 +1,83 @@ +# melib + +[![GitHub license](https://img.shields.io/github/license/meli/meli)](https://github.com/meli/meli/blob/master/COPYING) [![Crates.io](https://img.shields.io/crates/v/melib)](https://crates.io/crates/melib) [![docs.rs](https://docs.rs/melib/badge.svg)](https://docs.rs/melib) + +Library for handling mail. + +## optional features + +| feature flag | dependencies | notes | +| ---------------------- | ----------------------------------- | ------------------------ | +| `imap_backend` | `native-tls` | | +| `deflate_compression` | `flate2` | for use with IMAP | +| `jmap_backend` | `isahc`, `native-tls`, `serde_json` | | +| `maildir_backend` | `notify`, `memmap` | | +| `mbox_backend` | `notify`, `memmap` | | +| `smtp` | `native-tls`, `base64` | async SMTP communication | + +## Example: Parsing bytes into an `Envelope` + +An `Envelope` represents the information you can get from an email's headers +and body structure. Addresses in `To`, `From` fields etc are parsed into +`Address` types. + +```rust +use melib::{Attachment, Envelope}; + +let raw_mail = r#"From: "some name" +To: "me" +Cc: +Subject: =?utf-8?Q?gratuitously_encoded_subject?= +Message-ID: +MIME-Version: 1.0 +Content-Type: multipart/mixed; charset="utf-8"; + boundary="bzz_bzz__bzz__" + +This is a MIME formatted message with attachments. Use a MIME-compliant client to view it properly. +--bzz_bzz__bzz__ + +hello world. +--bzz_bzz__bzz__ +Content-Type: image/gif; name="test_image.gif"; charset="utf-8" +Content-Disposition: attachment +Content-Transfer-Encoding: base64 + +R0lGODdhKAAXAOfZAAABzAADzQAEzgQFtBEAxAAGxBcAxwALvRcFwAAPwBcLugATuQEUuxoNuxYQ +sxwOvAYVvBsStSAVtx8YsRUcuhwhth4iuCQsyDAwuDc1vTc3uDg4uT85rkc9ukJBvENCvURGukdF +wUVKt0hLuUxPvVZSvFlYu1hbt2BZuFxdul5joGhqlnNuf3FvlnBvwXJyt3Jxw3N0oXx1gH12gV99 +z317f3N7spFxwHp5wH99gYB+goF/g25+26tziIOBhWqD3oiBjICAuudkjIN+zHeC2n6Bzc1vh4eF +iYaBw8F0kImHi4KFxYyHmIWIvI2Lj4uIvYaJyY+IuJGMi5iJl4qKxZSMmIuLxpONnpGPk42NvI2M +1LKGl46OvZePm5ORlZiQnJqSnpaUmLyJnJuTn5iVmZyUoJGVyZ2VoZSVw5iXoZmWrO18rJiUyp6W +opuYnKaVnZ+Xo5yZncaMoaCYpJiaqo+Z2Z2annuf5qGZpa2WoJybpZmayZ2Z0KCZypydrZ6dp6Cd +oZ6a0aGay5ucy5+eqKGeouWMgp+b0qKbzKCfqdqPnp2ezaGgqqOgpKafqrScpp+gz6ajqKujr62j +qayksKmmq62lsaiosqqorOyWnaqqtKeqzLGptaurta2rr7Kqtq+ssLOrt6+uuLGusuqhfbWtubCv +ubKvs7GwurOwtPSazbevu+ali7SxtbiwvOykjLOyvLWytuCmqOankrSzvbazuLmyvrW0vre0uba1 +wLi1ury0wLm2u721wbe3wbq3vMC2vLi4wr+3w7m5w8C4xLi6yry6vsG5xbu7xcC6zMK6xry8xry+ +u8O7x729x8C9wb++yMG+wsO+vMK/w8a+y8e/zMnBzcXH18nL2/////////////////////////// +//////////////////////////////////////////////////////////////////////////// +/////////////////////////////////////////////////////ywAAAAAKAAXAAAI/gBP4Cjh +IYMLEh0w4EgBgsMLEyFGFBEB5cOFABgzatS4AVssZAOsLOHCxooVMzCyoNmzaBOkJlS0VEDyZMjG +mxk3XOMF60CDBgsoPABK9KcDCRImPCiQYAECAgQCRMU4VSrGCjFarBgUSJCgQ10FBTrkNRCfPnz4 +dA3UNa1btnDZqgU7Ntqzu3ej2X2mFy9eaHuhNRtMGJrhwYYN930G2K7eaNIY34U2mfJkwpgzI9Yr +GBqwR2KSvAlMOXHnw5pTNzPdLNoWIWtU9XjGjDEYS8LAlFm1SrVvzIKj5TH0KpORSZOryPgCZgqL +Ob+jG0YVRBErUrOiiGJ8KxgtYsh27xWL/tswnTtEbsiRVYdJNMHk4yOGhswGjR88UKjQ9Ey+/8TL +XKKGGn7Akph/8XX2WDTTcAYfguVt9hhrEPqmzIOJ3VUheb48WJiHG6amC4i+WVJKKCimqGIoYxyj +WWK8kKjaJ9bA18sxvXjYhourmbbMMrjI+OIn1QymDCVXANGFK4S1gQw0PxozzC+33FLLKUJq9gk1 +gyWDhyNwrMLkYGUEM4wvuLRiCiieXIJJJVlmJskcZ9TZRht1lnFGGmTMkMoonVQSSSOFAGJHHI0w +ouiijDaaCCGQRgrpH3q4QYYXWDihxBE+7KCDDjnUIEVAADs= +--bzz_bzz__bzz__--"#; + +let envelope = Envelope::from_bytes(raw_mail.as_bytes(), None).expect("Could not parse mail"); +assert_eq!(envelope.subject().as_ref(), "gratuitously encoded subject"); +assert_eq!(envelope.message_id_display().as_ref(), ""); + +let body = envelope.body_bytes(raw_mail.as_bytes()); +assert_eq!(body.content_type().to_string().as_str(), "multipart/mixed"); + +let body_text = body.text(); +assert_eq!(body_text.as_str(), "hello world."); + +let subattachments: Vec = body.attachments(); +assert_eq!(subattachments.len(), 3); +assert_eq!(subattachments[2].content_type().name().unwrap(), "test_image.gif"); +``` diff --git a/melib/src/email.rs b/melib/src/email.rs index 89cc1375f..6e31cdb05 100644 --- a/melib/src/email.rs +++ b/melib/src/email.rs @@ -20,8 +20,75 @@ */ /*! - * Email parsing, handling, sending etc. + * Email parsing and composing. + * + * # Parsing bytes into an `Envelope` + * + * An [`Envelope`](Envelope) represents the information you can get from an email's headers and body + * structure. Addresses in `To`, `From` fields etc are parsed into [`Address`](email::address::Address) types. + * + * ``` + * use melib::{Attachment, Envelope}; + * + * let raw_mail = r#"From: "some name" + * To: "me" + * Cc: + * Subject: =?utf-8?Q?gratuitously_encoded_subject?= + * Message-ID: + * MIME-Version: 1.0 + * Content-Type: multipart/mixed; charset="utf-8"; + * boundary="bzz_bzz__bzz__" + * + * This is a MIME formatted message with attachments. Use a MIME-compliant client to view it properly. + * --bzz_bzz__bzz__ + * + * hello world. + * --bzz_bzz__bzz__ + * Content-Type: image/gif; name="test_image.gif"; charset="utf-8" + * Content-Disposition: attachment + * Content-Transfer-Encoding: base64 + * + * R0lGODdhKAAXAOfZAAABzAADzQAEzgQFtBEAxAAGxBcAxwALvRcFwAAPwBcLugATuQEUuxoNuxYQ + * sxwOvAYVvBsStSAVtx8YsRUcuhwhth4iuCQsyDAwuDc1vTc3uDg4uT85rkc9ukJBvENCvURGukdF + * wUVKt0hLuUxPvVZSvFlYu1hbt2BZuFxdul5joGhqlnNuf3FvlnBvwXJyt3Jxw3N0oXx1gH12gV99 + * z317f3N7spFxwHp5wH99gYB+goF/g25+26tziIOBhWqD3oiBjICAuudkjIN+zHeC2n6Bzc1vh4eF + * iYaBw8F0kImHi4KFxYyHmIWIvI2Lj4uIvYaJyY+IuJGMi5iJl4qKxZSMmIuLxpONnpGPk42NvI2M + * 1LKGl46OvZePm5ORlZiQnJqSnpaUmLyJnJuTn5iVmZyUoJGVyZ2VoZSVw5iXoZmWrO18rJiUyp6W + * opuYnKaVnZ+Xo5yZncaMoaCYpJiaqo+Z2Z2annuf5qGZpa2WoJybpZmayZ2Z0KCZypydrZ6dp6Cd + * oZ6a0aGay5ucy5+eqKGeouWMgp+b0qKbzKCfqdqPnp2ezaGgqqOgpKafqrScpp+gz6ajqKujr62j + * qayksKmmq62lsaiosqqorOyWnaqqtKeqzLGptaurta2rr7Kqtq+ssLOrt6+uuLGusuqhfbWtubCv + * ubKvs7GwurOwtPSazbevu+ali7SxtbiwvOykjLOyvLWytuCmqOankrSzvbazuLmyvrW0vre0uba1 + * wLi1ury0wLm2u721wbe3wbq3vMC2vLi4wr+3w7m5w8C4xLi6yry6vsG5xbu7xcC6zMK6xry8xry+ + * u8O7x729x8C9wb++yMG+wsO+vMK/w8a+y8e/zMnBzcXH18nL2/////////////////////////// + * //////////////////////////////////////////////////////////////////////////// + * /////////////////////////////////////////////////////ywAAAAAKAAXAAAI/gBP4Cjh + * IYMLEh0w4EgBgsMLEyFGFBEB5cOFABgzatS4AVssZAOsLOHCxooVMzCyoNmzaBOkJlS0VEDyZMjG + * mxk3XOMF60CDBgsoPABK9KcDCRImPCiQYAECAgQCRMU4VSrGCjFarBgUSJCgQ10FBTrkNRCfPnz4 + * dA3UNa1btnDZqgU7Ntqzu3ej2X2mFy9eaHuhNRtMGJrhwYYN930G2K7eaNIY34U2mfJkwpgzI9Yr + * GBqwR2KSvAlMOXHnw5pTNzPdLNoWIWtU9XjGjDEYS8LAlFm1SrVvzIKj5TH0KpORSZOryPgCZgqL + * Ob+jG0YVRBErUrOiiGJ8KxgtYsh27xWL/tswnTtEbsiRVYdJNMHk4yOGhswGjR88UKjQ9Ey+/8TL + * XKKGGn7Akph/8XX2WDTTcAYfguVt9hhrEPqmzIOJ3VUheb48WJiHG6amC4i+WVJKKCimqGIoYxyj + * WWK8kKjaJ9bA18sxvXjYhourmbbMMrjI+OIn1QymDCVXANGFK4S1gQw0PxozzC+33FLLKUJq9gk1 + * gyWDhyNwrMLkYGUEM4wvuLRiCiieXIJJJVlmJskcZ9TZRht1lnFGGmTMkMoonVQSSSOFAGJHHI0w + * ouiijDaaCCGQRgrpH3q4QYYXWDihxBE+7KCDDjnUIEVAADs= + * --bzz_bzz__bzz__--"#; + * + * let envelope = Envelope::from_bytes(raw_mail.as_bytes(), None).expect("Could not parse mail"); + * assert_eq!(envelope.subject().as_ref(), "gratuitously encoded subject"); + * assert_eq!(envelope.message_id_display().as_ref(), ""); + * + * let body = envelope.body_bytes(raw_mail.as_bytes()); + * assert_eq!(body.content_type().to_string().as_str(), "multipart/mixed"); + * + * let body_text = body.text(); + * assert_eq!(body_text.as_str(), "hello world."); + * + * let subattachments: Vec = body.attachments(); + * assert_eq!(subattachments.len(), 3); + * assert_eq!(subattachments[2].content_type().name().unwrap(), "test_image.gif"); + * ``` */ + pub mod address; pub mod attachment_types; pub mod attachments; diff --git a/melib/src/email/attachments.rs b/melib/src/email/attachments.rs index a5a1a7c80..ccffe7a02 100644 --- a/melib/src/email/attachments.rs +++ b/melib/src/email/attachments.rs @@ -19,6 +19,7 @@ * along with meli. If not, see . */ +/*! Encoding/decoding of attachments */ use crate::email::{ address::StrBuilder, parser::{self, BytesExt}, diff --git a/melib/src/email/compose.rs b/melib/src/email/compose.rs index 59c5eacda..1a7ad9059 100644 --- a/melib/src/email/compose.rs +++ b/melib/src/email/compose.rs @@ -19,6 +19,7 @@ * along with meli. If not, see . */ +/*! Compose a `Draft`, with MIME and attachment support */ use super::*; use crate::email::attachment_types::{ Charset, ContentTransferEncoding, ContentType, MultipartType, diff --git a/melib/src/email/headers.rs b/melib/src/email/headers.rs index 4dbe98997..1568fe4fd 100644 --- a/melib/src/email/headers.rs +++ b/melib/src/email/headers.rs @@ -19,6 +19,7 @@ * along with meli. If not, see . */ +/*! Wrapper type `HeaderName` for case-insensitive comparisons */ use crate::error::MeliError; use indexmap::IndexMap; use smallvec::SmallVec; diff --git a/melib/src/email/list_management.rs b/melib/src/email/list_management.rs index 64a8d248f..aad769fd8 100644 --- a/melib/src/email/list_management.rs +++ b/melib/src/email/list_management.rs @@ -19,6 +19,7 @@ * along with meli. If not, see . */ +/*! Parsing of rfc2369/rfc2919 `List-*` headers */ use super::parser; use super::Envelope; use smallvec::SmallVec; diff --git a/melib/src/email/mailto.rs b/melib/src/email/mailto.rs index af36cac61..a5ecaa156 100644 --- a/melib/src/email/mailto.rs +++ b/melib/src/email/mailto.rs @@ -19,6 +19,7 @@ * along with meli. If not, see . */ +/*! Parsing of `mailto` addresses */ use super::*; use std::convert::TryFrom; diff --git a/melib/src/email/parser.rs b/melib/src/email/parser.rs index 08e15cf4d..2d5c26258 100644 --- a/melib/src/email/parser.rs +++ b/melib/src/email/parser.rs @@ -19,6 +19,7 @@ * along with meli. If not, see . */ +/*! Parsers for email. See submodules */ use crate::error::{MeliError, Result, ResultIntoMeliError}; use nom::{ branch::alt, @@ -281,6 +282,7 @@ pub fn mail(input: &[u8]) -> Result<(Vec<(&[u8], &[u8])>, &[u8])> { } pub mod dates { + /*! Date values in headers */ use super::generic::*; use super::*; use crate::datetime::UnixTimestamp; @@ -505,6 +507,7 @@ pub mod dates { } pub mod generic { + /*! Generally useful parser combinators */ use super::*; #[inline(always)] pub fn byte_in_slice<'a>(slice: &'static [u8]) -> impl Fn(&'a [u8]) -> IResult<&'a [u8], u8> { @@ -1205,6 +1208,7 @@ List-Archive: (Web Archive) } pub mod headers { + /*! Email headers */ use super::*; pub fn headers(input: &[u8]) -> IResult<&[u8], Vec<(&[u8], &[u8])>> { @@ -1465,6 +1469,7 @@ pub mod headers { } pub mod attachments { + /*! Email attachments */ use super::*; use crate::email::address::*; use crate::email::attachment_types::{ContentDisposition, ContentDispositionKind}; @@ -1731,6 +1736,7 @@ pub mod attachments { } pub mod encodings { + /*! Email encodings (quoted printable, MIME) */ use super::*; use crate::email::attachment_types::Charset; use data_encoding::BASE64_MIME; diff --git a/melib/src/email/signatures.rs b/melib/src/email/signatures.rs index e53b19f83..121434a56 100644 --- a/melib/src/email/signatures.rs +++ b/melib/src/email/signatures.rs @@ -19,6 +19,7 @@ * along with meli. If not, see . */ +/*! Verification of OpenPGP signatures */ use crate::email::parser::BytesExt; use crate::email::{ attachment_types::{ContentType, MultipartType}, @@ -26,7 +27,10 @@ use crate::email::{ }; use crate::{MeliError, Result}; -/// rfc3156 +/// Convert raw attachment to the form needed for signature verification ([rfc3156](https://tools.ietf.org/html/rfc3156)) +/// +/// ## rfc3156 +/// ```text /// Upon receipt of a signed message, an application MUST: /// /// (1) Convert line endings to the canonical sequence before @@ -35,7 +39,7 @@ use crate::{MeliError, Result}; /// (2) Pass both the signed data and its associated content headers /// along with the OpenPGP signature to the signature verification /// service. -/// +/// ``` pub fn convert_attachment_to_rfc_spec(input: &[u8]) -> Vec { if input.is_empty() { return Vec::new(); @@ -125,8 +129,8 @@ pub fn verify_signature(a: &Attachment) -> Result<(Vec, &[u8])> { }; Ok((signed_part, signature)) } - _ => { - unreachable!("Should not give non-signed attachments to this function"); - } + _ => Err(MeliError::new( + "Should not give non-signed attachments to this function", + )), } }