melib: cleanup commit
Cleanup melib module exports, add some document tests, change some documentation.master
parent
d14f26569e
commit
c6c0da7fcb
|
@ -97,10 +97,13 @@ impl AddressBook {
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn with_account(s: &crate::conf::AccountSettings) -> 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")]
|
#[cfg(feature = "vcard")]
|
||||||
{
|
{
|
||||||
|
let mut ret = AddressBook::new(s.name.clone());
|
||||||
if let Some(vcard_path) = s.vcard_folder() {
|
if let Some(vcard_path) = s.vcard_folder() {
|
||||||
if let Ok(cards) = vcard::load_cards(&std::path::Path::new(vcard_path)) {
|
if let Ok(cards) = vcard::load_cards(&std::path::Path::new(vcard_path)) {
|
||||||
for c in cards {
|
for c in cards {
|
||||||
|
@ -108,9 +111,8 @@ impl AddressBook {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
ret
|
||||||
}
|
}
|
||||||
|
|
||||||
ret
|
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn add_card(&mut self, card: Card) {
|
pub fn add_card(&mut self, card: Card) {
|
||||||
|
|
|
@ -20,6 +20,7 @@
|
||||||
*/
|
*/
|
||||||
|
|
||||||
use super::*;
|
use super::*;
|
||||||
|
use crate::email::address::{Address, MailboxAddress};
|
||||||
use crate::email::parser::{BytesExt, IResult};
|
use crate::email::parser::{BytesExt, IResult};
|
||||||
use crate::error::ResultIntoMeliError;
|
use crate::error::ResultIntoMeliError;
|
||||||
use crate::get_path_hash;
|
use crate::get_path_hash;
|
||||||
|
|
|
@ -21,6 +21,7 @@
|
||||||
|
|
||||||
use super::*;
|
use super::*;
|
||||||
use crate::backends::jmap::rfc8620::bool_false;
|
use crate::backends::jmap::rfc8620::bool_false;
|
||||||
|
use crate::email::address::{Address, MailboxAddress};
|
||||||
use core::marker::PhantomData;
|
use core::marker::PhantomData;
|
||||||
use serde::de::{Deserialize, Deserializer};
|
use serde::de::{Deserialize, Deserializer};
|
||||||
use serde_json::Value;
|
use serde_json::Value;
|
||||||
|
|
|
@ -18,6 +18,8 @@
|
||||||
* You should have received a copy of the GNU General Public License
|
* You should have received a copy of the GNU General Public License
|
||||||
* along with meli. If not, see <http://www.gnu.org/licenses/>.
|
* along with meli. If not, see <http://www.gnu.org/licenses/>.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
|
//! Basic mail account configuration to use with [`backends`](./backends/index.html)
|
||||||
use crate::backends::SpecialUsageMailbox;
|
use crate::backends::SpecialUsageMailbox;
|
||||||
use serde::{Deserialize, Deserializer, Serialize, Serializer};
|
use serde::{Deserialize, Deserializer, Serialize, Serializer};
|
||||||
use std::collections::HashMap;
|
use std::collections::HashMap;
|
||||||
|
|
|
@ -18,6 +18,8 @@
|
||||||
* You should have received a copy of the GNU General Public License
|
* You should have received a copy of the GNU General Public License
|
||||||
* along with meli. If not, see <http://www.gnu.org/licenses/>.
|
* along with meli. If not, see <http://www.gnu.org/licenses/>.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
|
//! Connections layers (TCP/fd/TLS/Deflate) to use with remote backends.
|
||||||
#[cfg(feature = "deflate_compression")]
|
#[cfg(feature = "deflate_compression")]
|
||||||
use flate2::{read::DeflateDecoder, write::DeflateEncoder, Compression};
|
use flate2::{read::DeflateDecoder, write::DeflateEncoder, Compression};
|
||||||
|
|
||||||
|
|
|
@ -19,11 +19,28 @@
|
||||||
* along with meli. If not, see <http://www.gnu.org/licenses/>.
|
* along with meli. If not, see <http://www.gnu.org/licenses/>.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
|
//! 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::convert::TryInto;
|
||||||
use std::ffi::{CStr, CString};
|
use std::ffi::{CStr, CString};
|
||||||
|
|
||||||
use crate::error::Result;
|
|
||||||
|
|
||||||
pub type UnixTimestamp = u64;
|
pub type UnixTimestamp = u64;
|
||||||
|
|
||||||
use libc::{timeval, timezone};
|
use libc::{timeval, timezone};
|
||||||
|
|
|
@ -22,38 +22,33 @@
|
||||||
/*!
|
/*!
|
||||||
* Email parsing, handling, sending etc.
|
* Email parsing, handling, sending etc.
|
||||||
*/
|
*/
|
||||||
use std::convert::TryInto;
|
pub mod address;
|
||||||
mod compose;
|
pub mod attachment_types;
|
||||||
pub use self::compose::*;
|
|
||||||
|
|
||||||
pub mod list_management;
|
|
||||||
mod mailto;
|
|
||||||
pub use mailto::*;
|
|
||||||
mod attachment_types;
|
|
||||||
pub mod attachments;
|
pub mod attachments;
|
||||||
pub use crate::attachments::*;
|
pub mod compose;
|
||||||
mod address;
|
pub mod headers;
|
||||||
//pub mod parser;
|
pub mod list_management;
|
||||||
|
pub mod mailto;
|
||||||
pub mod parser;
|
pub mod parser;
|
||||||
use crate::parser::BytesExt;
|
|
||||||
pub use address::*;
|
|
||||||
mod headers;
|
|
||||||
pub mod signatures;
|
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 headers::*;
|
||||||
|
pub use mailto::*;
|
||||||
|
|
||||||
use crate::datetime::UnixTimestamp;
|
use crate::datetime::UnixTimestamp;
|
||||||
use crate::error::{MeliError, Result};
|
use crate::error::{MeliError, Result};
|
||||||
|
use crate::parser::BytesExt;
|
||||||
use crate::thread::ThreadNodeHash;
|
use crate::thread::ThreadNodeHash;
|
||||||
|
|
||||||
use smallvec::SmallVec;
|
use smallvec::SmallVec;
|
||||||
use std::borrow::Cow;
|
use std::borrow::Cow;
|
||||||
use std::cmp::Ordering;
|
|
||||||
use std::collections::hash_map::DefaultHasher;
|
use std::collections::hash_map::DefaultHasher;
|
||||||
use std::fmt;
|
use std::convert::TryInto;
|
||||||
use std::hash::Hasher;
|
use std::hash::Hasher;
|
||||||
use std::option::Option;
|
use std::ops::Deref;
|
||||||
use std::str;
|
|
||||||
use std::string::String;
|
|
||||||
|
|
||||||
bitflags! {
|
bitflags! {
|
||||||
#[derive(Default, Serialize, Deserialize)]
|
#[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)]
|
#[derive(Debug, Clone, Default)]
|
||||||
pub struct EnvelopeWrapper {
|
pub struct Mail {
|
||||||
envelope: Envelope,
|
pub envelope: Envelope,
|
||||||
buffer: Vec<u8>,
|
pub bytes: Vec<u8>,
|
||||||
}
|
}
|
||||||
|
|
||||||
use std::ops::Deref;
|
impl Deref for Mail {
|
||||||
|
|
||||||
impl Deref for EnvelopeWrapper {
|
|
||||||
type Target = Envelope;
|
type Target = Envelope;
|
||||||
|
|
||||||
fn deref(&self) -> &Envelope {
|
fn deref(&self) -> &Envelope {
|
||||||
|
@ -97,56 +93,57 @@ impl Deref for EnvelopeWrapper {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl EnvelopeWrapper {
|
impl Mail {
|
||||||
pub fn new(buffer: Vec<u8>) -> Result<Self> {
|
pub fn new(bytes: Vec<u8>) -> Result<Self> {
|
||||||
Ok(EnvelopeWrapper {
|
Ok(Mail {
|
||||||
envelope: Envelope::from_bytes(&buffer, None)?,
|
envelope: Envelope::from_bytes(&bytes, None)?,
|
||||||
buffer,
|
bytes,
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn envelope(&self) -> &Envelope {
|
pub fn envelope(&self) -> &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;
|
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
|
///To access the email attachments, you need to parse them from the raw email bytes into an
|
||||||
/// entry in an IMAP server) is given through `operation_token`. For more information see
|
///`Attachment` object.
|
||||||
/// `BackendOp`.
|
|
||||||
#[derive(Clone, Serialize, Deserialize)]
|
#[derive(Clone, Serialize, Deserialize)]
|
||||||
pub struct Envelope {
|
pub struct Envelope {
|
||||||
date: String,
|
pub hash: EnvelopeHash,
|
||||||
from: SmallVec<[Address; 1]>,
|
pub date: String,
|
||||||
to: SmallVec<[Address; 1]>,
|
pub timestamp: UnixTimestamp,
|
||||||
cc: SmallVec<[Address; 1]>,
|
pub from: SmallVec<[Address; 1]>,
|
||||||
bcc: Vec<Address>,
|
pub to: SmallVec<[Address; 1]>,
|
||||||
subject: Option<String>,
|
pub cc: SmallVec<[Address; 1]>,
|
||||||
message_id: MessageID,
|
pub bcc: Vec<Address>,
|
||||||
in_reply_to: Option<MessageID>,
|
pub subject: Option<String>,
|
||||||
|
pub message_id: MessageID,
|
||||||
|
pub in_reply_to: Option<MessageID>,
|
||||||
pub references: Option<References>,
|
pub references: Option<References>,
|
||||||
other_headers: HeaderMap,
|
pub other_headers: HeaderMap,
|
||||||
|
pub thread: ThreadNodeHash,
|
||||||
timestamp: UnixTimestamp,
|
pub flags: Flag,
|
||||||
thread: ThreadNodeHash,
|
pub has_attachments: bool,
|
||||||
|
pub labels: SmallVec<[u64; 8]>,
|
||||||
hash: EnvelopeHash,
|
|
||||||
|
|
||||||
flags: Flag,
|
|
||||||
has_attachments: bool,
|
|
||||||
labels: SmallVec<[u64; 8]>,
|
|
||||||
}
|
}
|
||||||
|
|
||||||
impl fmt::Debug for Envelope {
|
impl core::fmt::Debug for Envelope {
|
||||||
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
|
fn fmt(&self, f: &mut core::fmt::Formatter) -> core::fmt::Result {
|
||||||
f.debug_struct("Envelope")
|
f.debug_struct("Envelope")
|
||||||
.field("Subject", &self.subject())
|
.field("Subject", &self.subject())
|
||||||
.field("Date", &self.date)
|
.field("Date", &self.date)
|
||||||
|
@ -169,7 +166,9 @@ impl Default for Envelope {
|
||||||
impl Envelope {
|
impl Envelope {
|
||||||
pub fn new(hash: EnvelopeHash) -> Self {
|
pub fn new(hash: EnvelopeHash) -> Self {
|
||||||
Envelope {
|
Envelope {
|
||||||
|
hash,
|
||||||
date: String::new(),
|
date: String::new(),
|
||||||
|
timestamp: 0,
|
||||||
from: SmallVec::new(),
|
from: SmallVec::new(),
|
||||||
to: SmallVec::new(),
|
to: SmallVec::new(),
|
||||||
cc: SmallVec::new(),
|
cc: SmallVec::new(),
|
||||||
|
@ -179,12 +178,7 @@ impl Envelope {
|
||||||
in_reply_to: None,
|
in_reply_to: None,
|
||||||
references: None,
|
references: None,
|
||||||
other_headers: Default::default(),
|
other_headers: Default::default(),
|
||||||
|
|
||||||
timestamp: 0,
|
|
||||||
|
|
||||||
thread: ThreadNodeHash::null(),
|
thread: ThreadNodeHash::null(),
|
||||||
|
|
||||||
hash,
|
|
||||||
has_attachments: false,
|
has_attachments: false,
|
||||||
flags: Flag::default(),
|
flags: Flag::default(),
|
||||||
labels: SmallVec::new(),
|
labels: SmallVec::new(),
|
||||||
|
@ -212,6 +206,7 @@ impl Envelope {
|
||||||
pub fn hash(&self) -> EnvelopeHash {
|
pub fn hash(&self) -> EnvelopeHash {
|
||||||
self.hash
|
self.hash
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn populate_headers(&mut self, mut bytes: &[u8]) -> Result<()> {
|
pub fn populate_headers(&mut self, mut bytes: &[u8]) -> Result<()> {
|
||||||
if bytes.starts_with(b"From ") {
|
if bytes.starts_with(b"From ") {
|
||||||
/* Attempt to recover if message includes the mbox From label as first line */
|
/* 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 {
|
pub fn date_as_str(&self) -> &str {
|
||||||
&self.date
|
&self.date
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn from(&self) -> &[Address] {
|
pub fn from(&self) -> &[Address] {
|
||||||
self.from.as_slice()
|
self.from.as_slice()
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn field_bcc_to_string(&self) -> String {
|
pub fn field_bcc_to_string(&self) -> String {
|
||||||
if self.bcc.is_empty() {
|
if self.bcc.is_empty() {
|
||||||
self.other_headers
|
self.other_headers
|
||||||
|
@ -372,6 +369,7 @@ impl Envelope {
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn field_cc_to_string(&self) -> String {
|
pub fn field_cc_to_string(&self) -> String {
|
||||||
if self.cc.is_empty() {
|
if self.cc.is_empty() {
|
||||||
self.other_headers
|
self.other_headers
|
||||||
|
@ -389,6 +387,7 @@ impl Envelope {
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn field_from_to_string(&self) -> String {
|
pub fn field_from_to_string(&self) -> String {
|
||||||
if self.from.is_empty() {
|
if self.from.is_empty() {
|
||||||
self.other_headers
|
self.other_headers
|
||||||
|
@ -406,9 +405,11 @@ impl Envelope {
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn to(&self) -> &[Address] {
|
pub fn to(&self) -> &[Address] {
|
||||||
self.to.as_slice()
|
self.to.as_slice()
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn field_to_to_string(&self) -> String {
|
pub fn field_to_to_string(&self) -> String {
|
||||||
if self.to.is_empty() {
|
if self.to.is_empty() {
|
||||||
self.other_headers
|
self.other_headers
|
||||||
|
@ -429,6 +430,7 @@ impl Envelope {
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn field_references_to_string(&self) -> String {
|
pub fn field_references_to_string(&self) -> String {
|
||||||
let refs = self.references();
|
let refs = self.references();
|
||||||
if refs.is_empty() {
|
if refs.is_empty() {
|
||||||
|
@ -455,7 +457,6 @@ impl Envelope {
|
||||||
builder.build()
|
builder.build()
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Requests bytes from backend and thus can fail
|
|
||||||
pub fn headers<'a>(&self, bytes: &'a [u8]) -> Result<Vec<(&'a str, &'a str)>> {
|
pub fn headers<'a>(&self, bytes: &'a [u8]) -> Result<Vec<(&'a str, &'a str)>> {
|
||||||
let ret = parser::headers::headers(bytes)?.1;
|
let ret = parser::headers::headers(bytes)?.1;
|
||||||
let len = ret.len();
|
let len = ret.len();
|
||||||
|
@ -486,36 +487,46 @@ impl Envelope {
|
||||||
.as_ref()
|
.as_ref()
|
||||||
.map(|m| String::from_utf8_lossy(m.val()))
|
.map(|m| String::from_utf8_lossy(m.val()))
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn in_reply_to_raw(&self) -> Option<Cow<str>> {
|
pub fn in_reply_to_raw(&self) -> Option<Cow<str>> {
|
||||||
self.in_reply_to
|
self.in_reply_to
|
||||||
.as_ref()
|
.as_ref()
|
||||||
.map(|m| String::from_utf8_lossy(m.raw()))
|
.map(|m| String::from_utf8_lossy(m.raw()))
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn message_id(&self) -> &MessageID {
|
pub fn message_id(&self) -> &MessageID {
|
||||||
&self.message_id
|
&self.message_id
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn message_id_display(&self) -> Cow<str> {
|
pub fn message_id_display(&self) -> Cow<str> {
|
||||||
String::from_utf8_lossy(self.message_id.val())
|
String::from_utf8_lossy(self.message_id.val())
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn message_id_raw(&self) -> Cow<str> {
|
pub fn message_id_raw(&self) -> Cow<str> {
|
||||||
String::from_utf8_lossy(self.message_id.raw())
|
String::from_utf8_lossy(self.message_id.raw())
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn set_date(&mut self, new_val: &[u8]) {
|
pub fn set_date(&mut self, new_val: &[u8]) {
|
||||||
let new_val = new_val.trim();
|
let new_val = new_val.trim();
|
||||||
self.date = String::from_utf8_lossy(new_val).into_owned();
|
self.date = String::from_utf8_lossy(new_val).into_owned();
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn set_bcc(&mut self, new_val: Vec<Address>) {
|
pub fn set_bcc(&mut self, new_val: Vec<Address>) {
|
||||||
self.bcc = new_val;
|
self.bcc = new_val;
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn set_cc(&mut self, new_val: SmallVec<[Address; 1]>) {
|
pub fn set_cc(&mut self, new_val: SmallVec<[Address; 1]>) {
|
||||||
self.cc = new_val;
|
self.cc = new_val;
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn set_from(&mut self, new_val: SmallVec<[Address; 1]>) {
|
pub fn set_from(&mut self, new_val: SmallVec<[Address; 1]>) {
|
||||||
self.from = new_val;
|
self.from = new_val;
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn set_to(&mut self, new_val: SmallVec<[Address; 1]>) {
|
pub fn set_to(&mut self, new_val: SmallVec<[Address; 1]>) {
|
||||||
self.to = new_val;
|
self.to = new_val;
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn set_in_reply_to(&mut self, new_val: &[u8]) {
|
pub fn set_in_reply_to(&mut self, new_val: &[u8]) {
|
||||||
// FIXME msg_id_list
|
// FIXME msg_id_list
|
||||||
let new_val = new_val.trim();
|
let new_val = new_val.trim();
|
||||||
|
@ -528,6 +539,7 @@ impl Envelope {
|
||||||
};
|
};
|
||||||
self.in_reply_to = Some(val);
|
self.in_reply_to = Some(val);
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn set_subject(&mut self, new_val: Vec<u8>) {
|
pub fn set_subject(&mut self, new_val: Vec<u8>) {
|
||||||
let mut new_val = String::from_utf8(new_val)
|
let mut new_val = String::from_utf8(new_val)
|
||||||
.unwrap_or_else(|err| String::from_utf8_lossy(&err.into_bytes()).into());
|
.unwrap_or_else(|err| String::from_utf8_lossy(&err.into_bytes()).into());
|
||||||
|
@ -542,6 +554,7 @@ impl Envelope {
|
||||||
|
|
||||||
self.subject = Some(new_val);
|
self.subject = Some(new_val);
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn set_message_id(&mut self, new_val: &[u8]) {
|
pub fn set_message_id(&mut self, new_val: &[u8]) {
|
||||||
let new_val = new_val.trim();
|
let new_val = new_val.trim();
|
||||||
match parser::address::msg_id(new_val) {
|
match parser::address::msg_id(new_val) {
|
||||||
|
@ -553,6 +566,7 @@ impl Envelope {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn push_references(&mut self, new_ref: MessageID) {
|
pub fn push_references(&mut self, new_ref: MessageID) {
|
||||||
match self.references {
|
match self.references {
|
||||||
Some(ref mut s) => {
|
Some(ref mut s) => {
|
||||||
|
@ -578,6 +592,7 @@ impl Envelope {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn set_references(&mut self, new_val: &[u8]) {
|
pub fn set_references(&mut self, new_val: &[u8]) {
|
||||||
let new_val = new_val.trim();
|
let new_val = new_val.trim();
|
||||||
match self.references {
|
match self.references {
|
||||||
|
@ -592,6 +607,7 @@ impl Envelope {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn references(&self) -> SmallVec<[&MessageID; 8]> {
|
pub fn references(&self) -> SmallVec<[&MessageID; 8]> {
|
||||||
match self.references {
|
match self.references {
|
||||||
Some(ref s) => s.refs.iter().fold(SmallVec::new(), |mut acc, x| {
|
Some(ref s) => s.refs.iter().fold(SmallVec::new(), |mut acc, x| {
|
||||||
|
@ -613,27 +629,35 @@ impl Envelope {
|
||||||
pub fn thread(&self) -> ThreadNodeHash {
|
pub fn thread(&self) -> ThreadNodeHash {
|
||||||
self.thread
|
self.thread
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn set_thread(&mut self, new_val: ThreadNodeHash) {
|
pub fn set_thread(&mut self, new_val: ThreadNodeHash) {
|
||||||
self.thread = new_val;
|
self.thread = new_val;
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn set_datetime(&mut self, new_val: UnixTimestamp) {
|
pub fn set_datetime(&mut self, new_val: UnixTimestamp) {
|
||||||
self.timestamp = new_val;
|
self.timestamp = new_val;
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn set_flag(&mut self, f: Flag, value: bool) {
|
pub fn set_flag(&mut self, f: Flag, value: bool) {
|
||||||
self.flags.set(f, value);
|
self.flags.set(f, value);
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn set_flags(&mut self, f: Flag) {
|
pub fn set_flags(&mut self, f: Flag) {
|
||||||
self.flags = f;
|
self.flags = f;
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn flags(&self) -> Flag {
|
pub fn flags(&self) -> Flag {
|
||||||
self.flags
|
self.flags
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn set_seen(&mut self) {
|
pub fn set_seen(&mut self) {
|
||||||
self.set_flag(Flag::SEEN, true)
|
self.set_flag(Flag::SEEN, true)
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn set_unseen(&mut self) {
|
pub fn set_unseen(&mut self) {
|
||||||
self.set_flag(Flag::SEEN, false)
|
self.set_flag(Flag::SEEN, false)
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn is_seen(&self) -> bool {
|
pub fn is_seen(&self) -> bool {
|
||||||
self.flags.contains(Flag::SEEN)
|
self.flags.contains(Flag::SEEN)
|
||||||
}
|
}
|
||||||
|
@ -656,14 +680,15 @@ impl Envelope {
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Eq for Envelope {}
|
impl Eq for Envelope {}
|
||||||
|
|
||||||
impl Ord 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())
|
self.datetime().cmp(&other.datetime())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl PartialOrd for Envelope {
|
impl PartialOrd for Envelope {
|
||||||
fn partial_cmp(&self, other: &Envelope) -> Option<Ordering> {
|
fn partial_cmp(&self, other: &Envelope) -> Option<std::cmp::Ordering> {
|
||||||
Some(self.cmp(other))
|
Some(self.cmp(other))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -19,6 +19,7 @@
|
||||||
* along with meli. If not, see <http://www.gnu.org/licenses/>.
|
* along with meli. If not, see <http://www.gnu.org/licenses/>.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
|
//! Email addresses. Parsing functions are in [melib::email::parser::address](../parser/address/index.html).
|
||||||
use super::*;
|
use super::*;
|
||||||
use std::collections::HashSet;
|
use std::collections::HashSet;
|
||||||
use std::convert::TryFrom;
|
use std::convert::TryFrom;
|
||||||
|
@ -60,6 +61,35 @@ pub struct MailboxAddress {
|
||||||
pub address_spec: StrBuilder,
|
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 <joerg@example.com>");
|
||||||
|
/// ```
|
||||||
|
///
|
||||||
|
/// or parse it from a raw value:
|
||||||
|
///
|
||||||
|
/// ```rust
|
||||||
|
/// let (rest_bytes, addr) = melib::email::parser::address::address("=?utf-8?q?J=C3=B6rg_Doe?= <joerg@example.com>".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)]
|
#[derive(Clone, Serialize, Deserialize)]
|
||||||
pub enum Address {
|
pub enum Address {
|
||||||
Mailbox(MailboxAddress),
|
Mailbox(MailboxAddress),
|
||||||
|
@ -121,6 +151,22 @@ impl Address {
|
||||||
Address::Group(g) => g.raw.as_slice(),
|
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 <address@domain.tld> "Name Name2" <address@domain.tld>
|
||||||
|
/// └─┬┘ └──────────┬─────┘ └─────┬──┘ └──────────┬─────┘
|
||||||
|
/// display_name │ display_name │
|
||||||
|
/// │ │
|
||||||
|
/// address_spec address_spec
|
||||||
|
///```
|
||||||
pub fn get_display_name(&self) -> String {
|
pub fn get_display_name(&self) -> String {
|
||||||
match self {
|
match self {
|
||||||
Address::Mailbox(m) => m.display_name.display(&m.raw),
|
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 {
|
pub fn get_email(&self) -> String {
|
||||||
match self {
|
match self {
|
||||||
Address::Mailbox(m) => m.address_spec.display(&m.raw),
|
Address::Mailbox(m) => m.address_spec.display(&m.raw),
|
||||||
|
@ -176,15 +223,14 @@ impl Address {
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Eq for Address {}
|
impl Eq for Address {}
|
||||||
|
|
||||||
impl PartialEq for Address {
|
impl PartialEq for Address {
|
||||||
fn eq(&self, other: &Address) -> bool {
|
fn eq(&self, other: &Address) -> bool {
|
||||||
match (self, other) {
|
match (self, other) {
|
||||||
(Address::Mailbox(_), Address::Group(_)) | (Address::Group(_), Address::Mailbox(_)) => {
|
(Address::Mailbox(_), Address::Group(_)) | (Address::Group(_), Address::Mailbox(_)) => {
|
||||||
false
|
false
|
||||||
}
|
}
|
||||||
(Address::Mailbox(s), Address::Mailbox(o)) => {
|
(Address::Mailbox(s), Address::Mailbox(o)) => s == o,
|
||||||
s.address_spec.display_bytes(&s.raw) == o.address_spec.display_bytes(&o.raw)
|
|
||||||
}
|
|
||||||
(Address::Group(s), Address::Group(o)) => {
|
(Address::Group(s), Address::Group(o)) => {
|
||||||
s.display_name.display_bytes(&s.raw) == o.display_name.display_bytes(&o.raw)
|
s.display_name.display_bytes(&s.raw) == o.display_name.display_bytes(&o.raw)
|
||||||
&& s.mailbox_list.iter().collect::<HashSet<_>>()
|
&& s.mailbox_list.iter().collect::<HashSet<_>>()
|
||||||
|
@ -210,8 +256,8 @@ impl Hash for Address {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl fmt::Display for Address {
|
impl core::fmt::Display for Address {
|
||||||
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
|
fn fmt(&self, f: &mut core::fmt::Formatter) -> core::fmt::Result {
|
||||||
match self {
|
match self {
|
||||||
Address::Mailbox(m) if m.display_name.length > 0 => write!(
|
Address::Mailbox(m) if m.display_name.length > 0 => write!(
|
||||||
f,
|
f,
|
||||||
|
@ -234,8 +280,8 @@ impl fmt::Display for Address {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl fmt::Debug for Address {
|
impl core::fmt::Debug for Address {
|
||||||
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
|
fn fmt(&self, f: &mut core::fmt::Formatter) -> core::fmt::Result {
|
||||||
match self {
|
match self {
|
||||||
Address::Mailbox(m) => f
|
Address::Mailbox(m) => f
|
||||||
.debug_struct("Address::Mailbox")
|
.debug_struct("Address::Mailbox")
|
||||||
|
@ -332,10 +378,12 @@ fn test_strbuilder() {
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
impl fmt::Display for MessageID {
|
impl core::fmt::Display for MessageID {
|
||||||
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
|
fn fmt(&self, f: &mut core::fmt::Formatter) -> core::fmt::Result {
|
||||||
if self.val().is_ascii() {
|
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 {
|
} else {
|
||||||
write!(f, "{}", String::from_utf8_lossy(self.val()))
|
write!(f, "{}", String::from_utf8_lossy(self.val()))
|
||||||
}
|
}
|
||||||
|
@ -347,8 +395,8 @@ impl PartialEq for MessageID {
|
||||||
self.raw() == other.raw()
|
self.raw() == other.raw()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
impl fmt::Debug for MessageID {
|
impl core::fmt::Debug for MessageID {
|
||||||
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
|
fn fmt(&self, f: &mut core::fmt::Formatter) -> core::fmt::Result {
|
||||||
write!(f, "{}", String::from_utf8(self.raw().to_vec()).unwrap())
|
write!(f, "{}", String::from_utf8(self.raw().to_vec()).unwrap())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -359,8 +407,8 @@ pub struct References {
|
||||||
pub refs: Vec<MessageID>,
|
pub refs: Vec<MessageID>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl fmt::Debug for References {
|
impl core::fmt::Debug for References {
|
||||||
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
|
fn fmt(&self, f: &mut core::fmt::Formatter) -> core::fmt::Result {
|
||||||
write!(f, "{:#?}", self.refs)
|
write!(f, "{:#?}", self.refs)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -18,15 +18,17 @@
|
||||||
* You should have received a copy of the GNU General Public License
|
* You should have received a copy of the GNU General Public License
|
||||||
* along with meli. If not, see <http://www.gnu.org/licenses/>.
|
* along with meli. If not, see <http://www.gnu.org/licenses/>.
|
||||||
*/
|
*/
|
||||||
use crate::email::address::StrBuilder;
|
|
||||||
use crate::email::parser;
|
use crate::email::{
|
||||||
use crate::email::parser::BytesExt;
|
address::StrBuilder,
|
||||||
use crate::email::EnvelopeWrapper;
|
parser::{self, BytesExt},
|
||||||
|
Mail,
|
||||||
|
};
|
||||||
use core::fmt;
|
use core::fmt;
|
||||||
use core::str;
|
use core::str;
|
||||||
use data_encoding::BASE64_MIME;
|
use data_encoding::BASE64_MIME;
|
||||||
|
|
||||||
pub use crate::email::attachment_types::*;
|
use crate::email::attachment_types::*;
|
||||||
|
|
||||||
#[derive(Default, Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
|
#[derive(Default, Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
|
||||||
pub struct AttachmentBuilder {
|
pub struct AttachmentBuilder {
|
||||||
|
@ -360,7 +362,7 @@ impl fmt::Display for Attachment {
|
||||||
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
|
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
|
||||||
match self.content_type {
|
match self.content_type {
|
||||||
ContentType::MessageRfc822 => {
|
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!(
|
Ok(wrapper) => write!(
|
||||||
f,
|
f,
|
||||||
"message/rfc822: {} - {} - {}",
|
"message/rfc822: {} - {} - {}",
|
||||||
|
|
|
@ -20,7 +20,10 @@
|
||||||
*/
|
*/
|
||||||
|
|
||||||
use super::*;
|
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 crate::shellexpand::ShellExpandTrait;
|
||||||
use data_encoding::BASE64_MIME;
|
use data_encoding::BASE64_MIME;
|
||||||
use std::ffi::OsStr;
|
use std::ffi::OsStr;
|
||||||
|
|
|
@ -1667,6 +1667,13 @@ pub mod encodings {
|
||||||
}
|
}
|
||||||
|
|
||||||
pub mod address {
|
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 super::*;
|
||||||
use crate::email::address::*;
|
use crate::email::address::*;
|
||||||
use crate::email::parser::generic::{
|
use crate::email::parser::generic::{
|
||||||
|
@ -1775,7 +1782,7 @@ pub mod address {
|
||||||
}
|
}
|
||||||
|
|
||||||
///`angle-addr = [CFWS] "<" addr-spec ">" [CFWS] / obs-angle-addr`
|
///`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, _) = opt(cfws)(input)?;
|
||||||
let (input, _) = tag("<")(input)?;
|
let (input, _) = tag("<")(input)?;
|
||||||
let (input, addr_spec) = addr_spec(input)?;
|
let (input, addr_spec) = addr_spec(input)?;
|
||||||
|
@ -1784,66 +1791,66 @@ pub mod address {
|
||||||
Ok((input, addr_spec))
|
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<u8> = 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 <atom> 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`
|
///`addr-spec = local-part "@" domain`
|
||||||
pub fn addr_spec(input: &[u8]) -> IResult<&[u8], Address> {
|
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<u8> = 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 <atom> 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, local_part) = context("addr_spec()", local_part)(input)?;
|
||||||
let (input, _) = context("addr_spec()", tag("@"))(input)?;
|
let (input, _) = context("addr_spec()", tag("@"))(input)?;
|
||||||
let (input, domain) = context("addr_spec()", domain)(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`
|
///`display-name = phrase`
|
||||||
pub fn display_name(input: &[u8]) -> IResult<&[u8], Vec<u8>> {
|
pub fn display_name(input: &[u8]) -> IResult<&[u8], Vec<u8>> {
|
||||||
let (rest, ret) = phrase2(input)?;
|
let (rest, ret) = phrase2(input)?;
|
||||||
|
|
|
@ -19,8 +19,11 @@
|
||||||
* along with meli. If not, see <http://www.gnu.org/licenses/>.
|
* along with meli. If not, see <http://www.gnu.org/licenses/>.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
use crate::email::attachments::{Attachment, ContentType, MultipartType};
|
|
||||||
use crate::email::parser::BytesExt;
|
use crate::email::parser::BytesExt;
|
||||||
|
use crate::email::{
|
||||||
|
attachment_types::{ContentType, MultipartType},
|
||||||
|
attachments::Attachment,
|
||||||
|
};
|
||||||
use crate::{MeliError, Result};
|
use crate::{MeliError, Result};
|
||||||
|
|
||||||
/// rfc3156
|
/// rfc3156
|
||||||
|
|
|
@ -20,21 +20,19 @@
|
||||||
*/
|
*/
|
||||||
|
|
||||||
//! A crate that performs mail client operations such as
|
//! A crate that performs mail client operations such as
|
||||||
//! - Hold an `Envelope` with methods convenient for mail client use. (see module `email`)
|
//! - 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` trait, and handle
|
//! - 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))
|
||||||
//! read/writes/updates through it. (see module `melib::backends`)
|
//! - Decode attachments (see module [`email::attachments`](./email/attachments/index.html))
|
||||||
//! - Decode attachments (see module `melib::email::attachments`)
|
//! - Create new mail (see [`email::Draft`](./email/compose/struct.Draft.html))
|
||||||
//! - Create new mail (see `email::Draft`)
|
//! - Send mail with an SMTP client (see module [`smtp`](./smtp/index.html))
|
||||||
//! - Send mail with an SMTP client (see module `smtp`)
|
//! - Manage an `addressbook` i.e. have contacts (see module [`addressbook`](./addressbook/index.html))
|
||||||
//! - 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`](./thread/index.html))
|
||||||
//! - Build thread structures out of a list of mail via their `In-Reply-To` and `References` header
|
|
||||||
//! values (see module `thread`)
|
|
||||||
//!
|
//!
|
||||||
//! Other exports are
|
//! Other exports are
|
||||||
//! - Basic mail account configuration to use with `backends` (see module `conf`)
|
//! - Basic mail account configuration to use with [`backends`](./backends/index.html) (see module [`conf`](./conf/index.html))
|
||||||
//! - Parser combinators (see module `parsec`)
|
//! - Parser combinators (see module [`parsec`](./parsec/index.html))
|
||||||
//! - A `ShellExpandTrait` to expand paths like a shell.
|
//! - 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]
|
#[macro_use]
|
||||||
pub mod dbg {
|
pub mod dbg {
|
||||||
|
|
||||||
|
@ -104,14 +102,19 @@ pub use self::logging::LoggingLevel::*;
|
||||||
pub use self::logging::*;
|
pub use self::logging::*;
|
||||||
|
|
||||||
pub mod addressbook;
|
pub mod addressbook;
|
||||||
|
pub use addressbook::*;
|
||||||
pub mod backends;
|
pub mod backends;
|
||||||
|
pub use backends::*;
|
||||||
mod collection;
|
mod collection;
|
||||||
|
pub use collection::*;
|
||||||
pub mod conf;
|
pub mod conf;
|
||||||
|
pub use conf::*;
|
||||||
pub mod email;
|
pub mod email;
|
||||||
|
pub use email::*;
|
||||||
pub mod error;
|
pub mod error;
|
||||||
|
pub use crate::error::*;
|
||||||
pub mod thread;
|
pub mod thread;
|
||||||
pub use crate::email::*;
|
pub use thread::*;
|
||||||
pub use crate::thread::*;
|
|
||||||
pub mod connections;
|
pub mod connections;
|
||||||
pub mod parsec;
|
pub mod parsec;
|
||||||
pub mod search;
|
pub mod search;
|
||||||
|
@ -126,26 +129,15 @@ extern crate serde_derive;
|
||||||
/* parser */
|
/* parser */
|
||||||
extern crate data_encoding;
|
extern crate data_encoding;
|
||||||
extern crate encoding;
|
extern crate encoding;
|
||||||
pub use nom;
|
pub extern crate nom;
|
||||||
|
|
||||||
#[macro_use]
|
#[macro_use]
|
||||||
extern crate bitflags;
|
extern crate bitflags;
|
||||||
|
pub extern crate futures;
|
||||||
pub extern crate indexmap;
|
pub extern crate indexmap;
|
||||||
extern crate uuid;
|
pub extern crate smallvec;
|
||||||
pub use smallvec;
|
pub extern crate smol;
|
||||||
|
pub extern crate uuid;
|
||||||
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 use shellexpand::ShellExpandTrait;
|
pub use shellexpand::ShellExpandTrait;
|
||||||
pub mod shellexpand {
|
pub mod shellexpand {
|
||||||
|
|
|
@ -19,6 +19,8 @@
|
||||||
* along with meli. If not, see <http://www.gnu.org/licenses/>.
|
* along with meli. If not, see <http://www.gnu.org/licenses/>.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
|
//! Parser combinators.
|
||||||
|
|
||||||
pub type Result<'a, Output> = std::result::Result<(&'a str, Output), &'a str>;
|
pub type Result<'a, Output> = std::result::Result<(&'a str, Output), &'a str>;
|
||||||
|
|
||||||
pub trait Parser<'a, Output> {
|
pub trait Parser<'a, Output> {
|
||||||
|
|
|
@ -309,7 +309,7 @@ fn run_app(opt: Opt) -> Result<()> {
|
||||||
if let Some(SubCommand::View { path }) = opt.subcommand {
|
if let Some(SubCommand::View { path }) = opt.subcommand {
|
||||||
let bytes = std::fs::read(&path)
|
let bytes = std::fs::read(&path)
|
||||||
.chain_err_summary(|| format!("Could not read from `{}`", path.display()))?;
|
.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()))?;
|
.chain_err_summary(|| format!("Could not parse `{}`", path.display()))?;
|
||||||
state = State::new(
|
state = State::new(
|
||||||
Some(Settings::without_accounts().unwrap_or_default()),
|
Some(Settings::without_accounts().unwrap_or_default()),
|
||||||
|
|
|
@ -23,6 +23,7 @@
|
||||||
*/
|
*/
|
||||||
use super::*;
|
use super::*;
|
||||||
use melib::backends::{AccountHash, Mailbox, MailboxHash};
|
use melib::backends::{AccountHash, Mailbox, MailboxHash};
|
||||||
|
use melib::email::{attachment_types::*, attachments::*};
|
||||||
use melib::thread::ThreadNodeHash;
|
use melib::thread::ThreadNodeHash;
|
||||||
|
|
||||||
pub mod listing;
|
pub mod listing;
|
||||||
|
|
|
@ -20,6 +20,7 @@
|
||||||
*/
|
*/
|
||||||
|
|
||||||
use super::*;
|
use super::*;
|
||||||
|
use melib::email::attachment_types::{ContentType, MultipartType};
|
||||||
use melib::list_management;
|
use melib::list_management;
|
||||||
use melib::Draft;
|
use melib::Draft;
|
||||||
|
|
||||||
|
|
|
@ -481,7 +481,6 @@ impl Component for AccountStatus {
|
||||||
None,
|
None,
|
||||||
);
|
);
|
||||||
|
|
||||||
use melib::backends::MailBackendExtensionStatus;
|
|
||||||
let (width, height) = self.content.size();
|
let (width, height) = self.content.size();
|
||||||
let (x, y) = match status {
|
let (x, y) = match status {
|
||||||
MailBackendExtensionStatus::Unsupported { comment: _ } => write_string_to_grid(
|
MailBackendExtensionStatus::Unsupported { comment: _ } => write_string_to_grid(
|
||||||
|
|
|
@ -481,7 +481,7 @@ impl MailView {
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
match u.content_type() {
|
match u.content_type() {
|
||||||
ContentType::MessageRfc822 => match EnvelopeWrapper::new(u.body().to_vec()) {
|
ContentType::MessageRfc822 => match Mail::new(u.body().to_vec()) {
|
||||||
Ok(wrapper) => {
|
Ok(wrapper) => {
|
||||||
context
|
context
|
||||||
.replies
|
.replies
|
||||||
|
|
|
@ -51,7 +51,7 @@ pub struct EnvelopeView {
|
||||||
subview: Option<Box<dyn Component>>,
|
subview: Option<Box<dyn Component>>,
|
||||||
dirty: bool,
|
dirty: bool,
|
||||||
mode: ViewMode,
|
mode: ViewMode,
|
||||||
wrapper: EnvelopeWrapper,
|
mail: Mail,
|
||||||
|
|
||||||
account_hash: AccountHash,
|
account_hash: AccountHash,
|
||||||
cmd_buf: String,
|
cmd_buf: String,
|
||||||
|
@ -66,7 +66,7 @@ impl fmt::Display for EnvelopeView {
|
||||||
|
|
||||||
impl EnvelopeView {
|
impl EnvelopeView {
|
||||||
pub fn new(
|
pub fn new(
|
||||||
wrapper: EnvelopeWrapper,
|
mail: Mail,
|
||||||
pager: Option<Pager>,
|
pager: Option<Pager>,
|
||||||
subview: Option<Box<dyn Component>>,
|
subview: Option<Box<dyn Component>>,
|
||||||
account_hash: AccountHash,
|
account_hash: AccountHash,
|
||||||
|
@ -76,7 +76,7 @@ impl EnvelopeView {
|
||||||
subview,
|
subview,
|
||||||
dirty: true,
|
dirty: true,
|
||||||
mode: ViewMode::Normal,
|
mode: ViewMode::Normal,
|
||||||
wrapper,
|
mail,
|
||||||
account_hash,
|
account_hash,
|
||||||
cmd_buf: String::with_capacity(4),
|
cmd_buf: String::with_capacity(4),
|
||||||
id: ComponentId::new_v4(),
|
id: ComponentId::new_v4(),
|
||||||
|
@ -225,15 +225,13 @@ impl Component for EnvelopeView {
|
||||||
let bottom_right = bottom_right!(area);
|
let bottom_right = bottom_right!(area);
|
||||||
|
|
||||||
let y: usize = {
|
let y: usize = {
|
||||||
let envelope: &Envelope = &self.wrapper;
|
|
||||||
|
|
||||||
if self.mode == ViewMode::Raw {
|
if self.mode == ViewMode::Raw {
|
||||||
clear_area(grid, area, crate::conf::value(context, "theme_default"));
|
clear_area(grid, area, crate::conf::value(context, "theme_default"));
|
||||||
context.dirty_areas.push_back(area);
|
context.dirty_areas.push_back(area);
|
||||||
get_y(upper_left).saturating_sub(1)
|
get_y(upper_left).saturating_sub(1)
|
||||||
} else {
|
} else {
|
||||||
let (x, y) = write_string_to_grid(
|
let (x, y) = write_string_to_grid(
|
||||||
&format!("Date: {}", envelope.date_as_str()),
|
&format!("Date: {}", self.mail.date_as_str()),
|
||||||
grid,
|
grid,
|
||||||
Color::Byte(33),
|
Color::Byte(33),
|
||||||
Color::Default,
|
Color::Default,
|
||||||
|
@ -247,7 +245,7 @@ impl Component for EnvelopeView {
|
||||||
grid[(x, y)].set_fg(Color::Default);
|
grid[(x, y)].set_fg(Color::Default);
|
||||||
}
|
}
|
||||||
let (x, y) = write_string_to_grid(
|
let (x, y) = write_string_to_grid(
|
||||||
&format!("From: {}", envelope.field_from_to_string()),
|
&format!("From: {}", self.mail.field_from_to_string()),
|
||||||
grid,
|
grid,
|
||||||
Color::Byte(33),
|
Color::Byte(33),
|
||||||
Color::Default,
|
Color::Default,
|
||||||
|
@ -261,7 +259,7 @@ impl Component for EnvelopeView {
|
||||||
grid[(x, y)].set_fg(Color::Default);
|
grid[(x, y)].set_fg(Color::Default);
|
||||||
}
|
}
|
||||||
let (x, y) = write_string_to_grid(
|
let (x, y) = write_string_to_grid(
|
||||||
&format!("To: {}", envelope.field_to_to_string()),
|
&format!("To: {}", self.mail.field_to_to_string()),
|
||||||
grid,
|
grid,
|
||||||
Color::Byte(33),
|
Color::Byte(33),
|
||||||
Color::Default,
|
Color::Default,
|
||||||
|
@ -275,7 +273,7 @@ impl Component for EnvelopeView {
|
||||||
grid[(x, y)].set_fg(Color::Default);
|
grid[(x, y)].set_fg(Color::Default);
|
||||||
}
|
}
|
||||||
let (x, y) = write_string_to_grid(
|
let (x, y) = write_string_to_grid(
|
||||||
&format!("Subject: {}", envelope.subject()),
|
&format!("Subject: {}", self.mail.subject()),
|
||||||
grid,
|
grid,
|
||||||
Color::Byte(33),
|
Color::Byte(33),
|
||||||
Color::Default,
|
Color::Default,
|
||||||
|
@ -289,7 +287,7 @@ impl Component for EnvelopeView {
|
||||||
grid[(x, y)].set_fg(Color::Default);
|
grid[(x, y)].set_fg(Color::Default);
|
||||||
}
|
}
|
||||||
let (x, y) = write_string_to_grid(
|
let (x, y) = write_string_to_grid(
|
||||||
&format!("Message-ID: <{}>", envelope.message_id_raw()),
|
&format!("Message-ID: <{}>", self.mail.message_id_raw()),
|
||||||
grid,
|
grid,
|
||||||
Color::Byte(33),
|
Color::Byte(33),
|
||||||
Color::Default,
|
Color::Default,
|
||||||
|
@ -315,7 +313,7 @@ impl Component for EnvelopeView {
|
||||||
};
|
};
|
||||||
|
|
||||||
if self.dirty {
|
if self.dirty {
|
||||||
let body = self.wrapper.body_bytes(self.wrapper.buffer());
|
let body = self.mail.body();
|
||||||
match self.mode {
|
match self.mode {
|
||||||
ViewMode::Attachment(aidx) if body.attachments()[aidx].is_html() => {
|
ViewMode::Attachment(aidx) if body.attachments()[aidx].is_html() => {
|
||||||
let attachment = &body.attachments()[aidx];
|
let attachment = &body.attachments()[aidx];
|
||||||
|
@ -409,12 +407,7 @@ impl Component for EnvelopeView {
|
||||||
.push_back(UIEvent::StatusEvent(StatusEvent::BufClear));
|
.push_back(UIEvent::StatusEvent(StatusEvent::BufClear));
|
||||||
|
|
||||||
{
|
{
|
||||||
let envelope: &Envelope = self.wrapper.envelope();
|
if let Some(u) = self.mail.body().attachments().get(lidx) {
|
||||||
if let Some(u) = envelope
|
|
||||||
.body_bytes(self.wrapper.buffer())
|
|
||||||
.attachments()
|
|
||||||
.get(lidx)
|
|
||||||
{
|
|
||||||
match u.content_type() {
|
match u.content_type() {
|
||||||
ContentType::MessageRfc822 => {
|
ContentType::MessageRfc822 => {
|
||||||
self.mode = ViewMode::Subview;
|
self.mode = ViewMode::Subview;
|
||||||
|
@ -518,9 +511,8 @@ impl Component for EnvelopeView {
|
||||||
.replies
|
.replies
|
||||||
.push_back(UIEvent::StatusEvent(StatusEvent::BufClear));
|
.push_back(UIEvent::StatusEvent(StatusEvent::BufClear));
|
||||||
let url = {
|
let url = {
|
||||||
let envelope: &Envelope = self.wrapper.envelope();
|
|
||||||
let finder = LinkFinder::new();
|
let finder = LinkFinder::new();
|
||||||
let t = envelope.body_bytes(self.wrapper.buffer()).text();
|
let t = self.mail.body().text();
|
||||||
let links: Vec<Link> = finder.links(&t).collect();
|
let links: Vec<Link> = finder.links(&t).collect();
|
||||||
if let Some(u) = links.get(lidx) {
|
if let Some(u) = links.get(lidx) {
|
||||||
u.as_str().to_string()
|
u.as_str().to_string()
|
||||||
|
|
|
@ -9,7 +9,7 @@ fn build_draft() {
|
||||||
.expect("Could not open test_image.gif.");
|
.expect("Could not open test_image.gif.");
|
||||||
if let Ok(mime_type) = query_mime_info("./tests/test_image.gif") {
|
if let Ok(mime_type) = query_mime_info("./tests/test_image.gif") {
|
||||||
match attachment.content_type {
|
match attachment.content_type {
|
||||||
melib::email::ContentType::Other { ref mut tag, .. } => {
|
melib::email::attachment_types::ContentType::Other { ref mut tag, .. } => {
|
||||||
*tag = mime_type;
|
*tag = mime_type;
|
||||||
}
|
}
|
||||||
_ => {}
|
_ => {}
|
||||||
|
|
|
@ -5,28 +5,17 @@ use melib::smol;
|
||||||
use melib::smtp::*;
|
use melib::smtp::*;
|
||||||
use melib::Result;
|
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<()> {
|
fn main() -> Result<()> {
|
||||||
let conf = SmtpServerConf {
|
let conf = SmtpServerConf {
|
||||||
hostname: "smtp1.ntua.gr".into(),
|
hostname: "smtp1.example.com".into(),
|
||||||
port: 587,
|
port: 587,
|
||||||
security: SmtpSecurity::StartTLS {
|
security: SmtpSecurity::StartTLS {
|
||||||
danger_accept_invalid_certs: false,
|
danger_accept_invalid_certs: false,
|
||||||
},
|
},
|
||||||
extensions: SmtpExtensionSupport::default(),
|
extensions: SmtpExtensionSupport::default(),
|
||||||
auth: SmtpAuth::Auto {
|
auth: SmtpAuth::Auto {
|
||||||
username: "el13635".into(),
|
username: "username".into(),
|
||||||
password: Password::CommandEval(
|
password: Password::CommandEval("gpg2 --no-tty -q -d ~/.passwords/password.gpg".into()),
|
||||||
"gpg2 --no-tty -q -d ~/.passwords/msmtp/ntua.gpg".into(),
|
|
||||||
),
|
|
||||||
require_auth: true,
|
require_auth: true,
|
||||||
},
|
},
|
||||||
envelope_from: String::new(),
|
envelope_from: String::new(),
|
||||||
|
@ -37,14 +26,15 @@ fn main() -> Result<()> {
|
||||||
|
|
||||||
let mut conn = futures::executor::block_on(SmtpConnection::new_connection(conf)).unwrap();
|
let mut conn = futures::executor::block_on(SmtpConnection::new_connection(conf)).unwrap();
|
||||||
futures::executor::block_on(conn.mail_transaction(
|
futures::executor::block_on(conn.mail_transaction(
|
||||||
r##"To: pr.birch@gmail.com
|
r##"To: username@example.com
|
||||||
Auto-Submitted: auto-generated
|
Auto-Submitted: auto-generated
|
||||||
Subject: Fwd: *** SMTP TEST #2 information ***
|
Subject: Fwd: *** SMTP TEST #2 information ***
|
||||||
From: Manos <el13635@mail.ntua.gr>
|
From: Xxxxx <username@example.com>
|
||||||
Message-Id: <E1hSjnr-0003fN-RL2@postretch>
|
Message-Id: <E1hSjnr-0003fN-RL2@example>
|
||||||
Date: Mon, 13 Jul 2020 15:02:15 +0300
|
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();
|
)).unwrap();
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in New Issue