Browse Source

melib: cleanup commit

Cleanup melib module exports, add some document tests, change some
documentation.
memfd
parent
commit
c6c0da7fcb
WARNING! Although there is a key with this ID in the database it does not verify this commit! This commit is SUSPICIOUS. GPG Key ID: 73627C2F690DF710
22 changed files with 318 additions and 217 deletions
  1. +6
    -4
      melib/src/addressbook.rs
  2. +1
    -0
      melib/src/backends/imap/protocol_parser.rs
  3. +1
    -0
      melib/src/backends/jmap/objects/email.rs
  4. +2
    -0
      melib/src/conf.rs
  5. +2
    -0
      melib/src/connections.rs
  6. +19
    -2
      melib/src/datetime.rs
  7. +90
    -65
      melib/src/email.rs
  8. +62
    -14
      melib/src/email/address.rs
  9. +8
    -6
      melib/src/email/attachments.rs
  10. +4
    -1
      melib/src/email/compose.rs
  11. +71
    -53
      melib/src/email/parser.rs
  12. +4
    -1
      melib/src/email/signatures.rs
  13. +22
    -30
      melib/src/lib.rs
  14. +2
    -0
      melib/src/parsec.rs
  15. +1
    -1
      src/bin.rs
  16. +1
    -0
      src/components/mail.rs
  17. +1
    -0
      src/components/mail/compose.rs
  18. +0
    -1
      src/components/mail/status.rs
  19. +1
    -1
      src/components/mail/view.rs
  20. +11
    -19
      src/components/mail/view/envelope.rs
  21. +1
    -1
      tests/generating_email.rs
  22. +8
    -18
      tools/src/smtp_conn.rs

+ 6
- 4
melib/src/addressbook.rs View File

@@ -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) {


+ 1
- 0
melib/src/backends/imap/protocol_parser.rs View File

@@ -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;


+ 1
- 0
melib/src/backends/jmap/objects/email.rs View File

@@ -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;


+ 2
- 0
melib/src/conf.rs View File

@@ -18,6 +18,8 @@
* You should have received a copy of the GNU General Public License
* 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 serde::{Deserialize, Deserializer, Serialize, Serializer};
use std::collections::HashMap;


+ 2
- 0
melib/src/connections.rs View File

@@ -18,6 +18,8 @@
* You should have received a copy of the GNU General Public License
* 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")]
use flate2::{read::DeflateDecoder, write::DeflateEncoder, Compression};



+ 19
- 2
melib/src/datetime.rs View File

@@ -19,11 +19,28 @@
* 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::ffi::{CStr, CString};

use crate::error::Result;

pub type UnixTimestamp = u64;

use libc::{timeval, timezone};


+ 90
- 65
melib/src/email.rs View File

@@ -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<u8>,
pub struct Mail {
pub envelope: Envelope,
pub bytes: Vec<u8>,
}

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<u8>) -> Result<Self> {
Ok(EnvelopeWrapper {
envelope: Envelope::from_bytes(&buffer, None)?,
buffer,
impl Mail {
pub fn new(bytes: Vec<u8>) -> Result<Self> {
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<Address>,
subject: Option<String>,
message_id: MessageID,
in_reply_to: Option<MessageID>,
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<Address>,
pub subject: Option<String>,
pub message_id: MessageID,
pub in_reply_to: Option<MessageID>,
pub references: Option<References>,
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<Vec<(&'a str, &'a str)>> {
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<Cow<str>> {
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<str> {
String::from_utf8_lossy(self.message_id.val())
}

pub fn message_id_raw(&self) -> Cow<str> {
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<Address>) {
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<u8>) {
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<Ordering> {
fn partial_cmp(&self, other: &Envelope) -> Option<std::cmp::Ordering> {
Some(self.cmp(other))
}
}


+ 62
- 14
melib/src/email/address.rs View File

@@ -19,6 +19,7 @@
* 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 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 <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)]
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 <address@domain.tld> "Name Name2" <address@domain.tld>
/// β””β”€β”¬β”˜ β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”€β”˜ β””β”€β”€β”€β”€β”€β”¬β”€β”€β”˜ β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”€β”˜
/// 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::<HashSet<_>>()
@@ -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<MessageID>,
}

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


+ 8
- 6
melib/src/email/attachments.rs View File

@@ -18,15 +18,17 @@
* You should have received a copy of the GNU General Public License
* along with meli. If not, see <http://www.gnu.org/licenses/>.
*/
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: {} - {} - {}",


+ 4
- 1
melib/src/email/compose.rs View File

@@ -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;


+ 71
- 53
melib/src/email/parser.rs View File

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

///`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<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(),
));
}
///`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()))
}
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)
}
///`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`
fn domain(input: &[u8]) -> IResult<&[u8], Cow<'_, [u8]>> {
alt((dot_atom, domain_literal, obs_domain))(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]`
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);
///`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(b']');
Ok((input, ret_s.into()))
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> {
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<u8>> {
let (rest, ret) = phrase2(input)?;


+ 4
- 1
melib/src/email/signatures.rs View File

@@ -19,8 +19,11 @@
* 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::{
attachment_types::{ContentType, MultipartType},
attachments::Attachment,
};
use crate::{MeliError, Result};

/// rfc3156


+ 22
- 30
melib/src/lib.rs View File

@@ -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 {


+ 2
- 0
melib/src/parsec.rs View File

@@ -19,6 +19,8 @@
* 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 trait Parser<'a, Output> {


+ 1
- 1
src/bin.rs View File

@@ -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()),


+ 1
- 0
src/components/mail.rs View File

@@ -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;


+ 1
- 0
src/components/mail/compose.rs View File

@@ -20,6 +20,7 @@
*/

use super::*;
use melib::email::attachment_types::{ContentType, MultipartType};
use melib::list_management;
use melib::Draft;



+ 0
- 1
src/components/mail/status.rs View File

@@ -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(


+ 1
- 1
src/components/mail/view.rs View File

@@ -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


+ 11
- 19
src/components/mail/view/envelope.rs View File

@@ -51,7 +51,7 @@ pub struct EnvelopeView {
subview: Option<Box<dyn Component>>,
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<Pager>,
subview: Option<Box<dyn Component>>,
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<Link> = finder.links(&t).collect();
if let Some(u) = links.get(lidx) {
u.as_str().to_string()


+ 1
- 1
tests/generating_email.rs View File

@@ -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;
}
_ => {}


+ 8
- 18
tools/src/smtp_conn.rs View File

@@ -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 <el13635@mail.ntua.gr>
Message-Id: <E1hSjnr-0003fN-RL2@postretch>
From: Xxxxx <username@example.com>
Message-Id: <E1hSjnr-0003fN-RL2@example>
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(())
}

Loading…
Cancel
Save