melib: add README.md and email module doco
parent
a1e3f269de
commit
e407b1e224
|
@ -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"
|
||||
|
|
|
@ -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" <some@address.com>
|
||||
To: "me" <myself@i.tld>
|
||||
Cc:
|
||||
Subject: =?utf-8?Q?gratuitously_encoded_subject?=
|
||||
Message-ID: <h2g7f.z0gy2pgaen5m@address.com>
|
||||
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(), "<h2g7f.z0gy2pgaen5m@address.com>");
|
||||
|
||||
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<Attachment> = body.attachments();
|
||||
assert_eq!(subattachments.len(), 3);
|
||||
assert_eq!(subattachments[2].content_type().name().unwrap(), "test_image.gif");
|
||||
```
|
|
@ -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" <some@address.com>
|
||||
* To: "me" <myself@i.tld>
|
||||
* Cc:
|
||||
* Subject: =?utf-8?Q?gratuitously_encoded_subject?=
|
||||
* Message-ID: <h2g7f.z0gy2pgaen5m@address.com>
|
||||
* 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(), "<h2g7f.z0gy2pgaen5m@address.com>");
|
||||
*
|
||||
* 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<Attachment> = 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;
|
||||
|
|
|
@ -19,6 +19,7 @@
|
|||
* along with meli. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
/*! Encoding/decoding of attachments */
|
||||
use crate::email::{
|
||||
address::StrBuilder,
|
||||
parser::{self, BytesExt},
|
||||
|
|
|
@ -19,6 +19,7 @@
|
|||
* along with meli. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
/*! Compose a `Draft`, with MIME and attachment support */
|
||||
use super::*;
|
||||
use crate::email::attachment_types::{
|
||||
Charset, ContentTransferEncoding, ContentType, MultipartType,
|
||||
|
|
|
@ -19,6 +19,7 @@
|
|||
* along with meli. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
/*! Wrapper type `HeaderName` for case-insensitive comparisons */
|
||||
use crate::error::MeliError;
|
||||
use indexmap::IndexMap;
|
||||
use smallvec::SmallVec;
|
||||
|
|
|
@ -19,6 +19,7 @@
|
|||
* along with meli. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
/*! Parsing of rfc2369/rfc2919 `List-*` headers */
|
||||
use super::parser;
|
||||
use super::Envelope;
|
||||
use smallvec::SmallVec;
|
||||
|
|
|
@ -19,6 +19,7 @@
|
|||
* along with meli. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
/*! Parsing of `mailto` addresses */
|
||||
use super::*;
|
||||
use std::convert::TryFrom;
|
||||
|
||||
|
|
|
@ -19,6 +19,7 @@
|
|||
* along with meli. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
/*! 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: <http://www.host.com/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;
|
||||
|
|
|
@ -19,6 +19,7 @@
|
|||
* along with meli. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
/*! 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 <CR><LF> 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<u8> {
|
||||
if input.is_empty() {
|
||||
return Vec::new();
|
||||
|
@ -125,8 +129,8 @@ pub fn verify_signature(a: &Attachment) -> Result<(Vec<u8>, &[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",
|
||||
)),
|
||||
}
|
||||
}
|
||||
|
|
Loading…
Reference in New Issue