forked from meli/meli
1
Fork 0

Clippy fixes

duesee/experiment/use_imap_codec
Manos Pitsidianakis 2023-04-30 19:39:41 +03:00
parent 3a02b6fb80
commit b1a7188771
Signed by: Manos Pitsidianakis
GPG Key ID: 7729C7707F7E09D0
136 changed files with 4642 additions and 3388 deletions

View File

@ -37,14 +37,9 @@ fn main() {
]); ]);
#[cfg(feature = "cli-docs")] #[cfg(feature = "cli-docs")]
{ {
use flate2::Compression; use flate2::{Compression, GzBuilder};
use flate2::GzBuilder;
const MANDOC_OPTS: &[&str] = &["-T", "utf8", "-I", "os=Generated by mandoc(1)"]; const MANDOC_OPTS: &[&str] = &["-T", "utf8", "-I", "os=Generated by mandoc(1)"];
use std::env; use std::{env, fs::File, io::prelude::*, path::Path, process::Command};
use std::fs::File;
use std::io::prelude::*;
use std::path::Path;
use std::process::Command;
let out_dir = env::var("OUT_DIR").unwrap(); let out_dir = env::var("OUT_DIR").unwrap();
let mut out_dir_path = Path::new(&out_dir).to_path_buf(); let mut out_dir_path = Path::new(&out_dir).to_path_buf();
@ -57,7 +52,8 @@ fn main() {
.output() .output()
.or_else(|_| Command::new("man").arg("-l").arg(filepath).output()) .or_else(|_| Command::new("man").arg("-l").arg(filepath).output())
.expect( .expect(
"could not execute `mandoc` or `man`. If the binaries are not available in the PATH, disable `cli-docs` feature to be able to continue compilation.", "could not execute `mandoc` or `man`. If the binaries are not available in \
the PATH, disable `cli-docs` feature to be able to continue compilation.",
); );
let file = File::create(&out_dir_path).unwrap_or_else(|err| { let file = File::create(&out_dir_path).unwrap_or_else(|err| {

View File

@ -19,9 +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 std::fs::File; use std::{
use std::io::prelude::*; fs::File,
use std::process::{Command, Stdio}; io::prelude::*,
process::{Command, Stdio},
};
use quote::{format_ident, quote}; use quote::{format_ident, quote};
@ -29,7 +31,8 @@ use quote::{format_ident, quote};
pub fn override_derive(filenames: &[(&str, &str)]) { pub fn override_derive(filenames: &[(&str, &str)]) {
let mut output_file = let mut output_file =
File::create("src/conf/overrides.rs").expect("Unable to open output file"); File::create("src/conf/overrides.rs").expect("Unable to open output file");
let mut output_string = r##"/* let mut output_string = r##"// @generated
/*
* meli - conf/overrides.rs * meli - conf/overrides.rs
* *
* Copyright 2020 Manos Pitsidianakis * Copyright 2020 Manos Pitsidianakis
@ -60,7 +63,7 @@ use super::*;
'file_loop: for (filename, ident) in filenames { 'file_loop: for (filename, ident) in filenames {
println!("cargo:rerun-if-changed={}", filename); println!("cargo:rerun-if-changed={}", filename);
let mut file = File::open(&filename) let mut file = File::open(filename)
.unwrap_or_else(|err| panic!("Unable to open file `{}` {}", filename, err)); .unwrap_or_else(|err| panic!("Unable to open file `{}` {}", filename, err));
let mut src = String::new(); let mut src = String::new();

View File

@ -29,11 +29,12 @@ fn main() -> Result<(), std::io::Error> {
println!("cargo:rerun-if-changed=build.rs"); println!("cargo:rerun-if-changed=build.rs");
println!("cargo:rerun-if-changed={}", MOD_PATH); println!("cargo:rerun-if-changed={}", MOD_PATH);
/* Line break tables */ /* Line break tables */
use std::fs::File; use std::{
use std::io::prelude::*; fs::File,
use std::io::BufReader; io::{prelude::*, BufReader},
use std::path::Path; path::Path,
use std::process::{Command, Stdio}; process::{Command, Stdio},
};
const LINE_BREAK_TABLE_URL: &str = const LINE_BREAK_TABLE_URL: &str =
"http://www.unicode.org/Public/UCD/latest/ucd/LineBreak.txt"; "http://www.unicode.org/Public/UCD/latest/ucd/LineBreak.txt";
/* Grapheme width tables */ /* Grapheme width tables */
@ -52,7 +53,7 @@ fn main() -> Result<(), std::io::Error> {
std::process::exit(0); std::process::exit(0);
} }
let mut child = Command::new("curl") let mut child = Command::new("curl")
.args(&["-o", "-", LINE_BREAK_TABLE_URL]) .args(["-o", "-", LINE_BREAK_TABLE_URL])
.stdout(Stdio::piped()) .stdout(Stdio::piped())
.stdin(Stdio::null()) .stdin(Stdio::null())
.stderr(Stdio::inherit()) .stderr(Stdio::inherit())
@ -69,7 +70,8 @@ fn main() -> Result<(), std::io::Error> {
let tokens: &str = line.split_whitespace().next().unwrap(); let tokens: &str = line.split_whitespace().next().unwrap();
let semicolon_idx: usize = tokens.chars().position(|c| c == ';').unwrap(); let semicolon_idx: usize = tokens.chars().position(|c| c == ';').unwrap();
/* LineBreak.txt list is ascii encoded so we can assume each char takes one byte: */ /* LineBreak.txt list is ascii encoded so we can assume each char takes one
* byte: */
let chars_str: &str = &tokens[..semicolon_idx]; let chars_str: &str = &tokens[..semicolon_idx];
let mut codepoint_iter = chars_str.split(".."); let mut codepoint_iter = chars_str.split("..");
@ -87,21 +89,21 @@ fn main() -> Result<(), std::io::Error> {
child.wait()?; child.wait()?;
let child = Command::new("curl") let child = Command::new("curl")
.args(&["-o", "-", UNICODE_DATA_URL]) .args(["-o", "-", UNICODE_DATA_URL])
.stdout(Stdio::piped()) .stdout(Stdio::piped())
.output()?; .output()?;
let unicode_data = String::from_utf8_lossy(&child.stdout); let unicode_data = String::from_utf8_lossy(&child.stdout);
let child = Command::new("curl") let child = Command::new("curl")
.args(&["-o", "-", EAW_URL]) .args(["-o", "-", EAW_URL])
.stdout(Stdio::piped()) .stdout(Stdio::piped())
.output()?; .output()?;
let eaw_data = String::from_utf8_lossy(&child.stdout); let eaw_data = String::from_utf8_lossy(&child.stdout);
let child = Command::new("curl") let child = Command::new("curl")
.args(&["-o", "-", EMOJI_DATA_URL]) .args(["-o", "-", EMOJI_DATA_URL])
.stdout(Stdio::piped()) .stdout(Stdio::piped())
.output()?; .output()?;
@ -198,13 +200,13 @@ fn main() -> Result<(), std::io::Error> {
} }
// Apply the following special cases: // Apply the following special cases:
// - The unassigned code points in the following blocks default to "W": // - The unassigned code points in the following blocks default to "W":
// CJK Unified Ideographs Extension A: U+3400..U+4DBF // - CJK Unified Ideographs Extension A: U+3400..U+4DBF
// CJK Unified Ideographs: U+4E00..U+9FFF // - CJK Unified Ideographs: U+4E00..U+9FFF
// CJK Compatibility Ideographs: U+F900..U+FAFF // - CJK Compatibility Ideographs: U+F900..U+FAFF
// - All undesignated code points in Planes 2 and 3, whether inside or // - All undesignated code points in Planes 2 and 3, whether inside or outside
// outside of allocated blocks, default to "W": // of allocated blocks, default to "W":
// Plane 2: U+20000..U+2FFFD // - Plane 2: U+20000..U+2FFFD
// Plane 3: U+30000..U+3FFFD // - Plane 3: U+30000..U+3FFFD
const WIDE_RANGES: [(usize, usize); 5] = [ const WIDE_RANGES: [(usize, usize); 5] = [
(0x3400, 0x4DBF), (0x3400, 0x4DBF),
(0x4E00, 0x9FFF), (0x4E00, 0x9FFF),
@ -245,12 +247,12 @@ fn main() -> Result<(), std::io::Error> {
} }
use std::str::FromStr; use std::str::FromStr;
let mut v = comment.trim().split_whitespace().next().unwrap(); let mut v = comment.split_whitespace().next().unwrap();
if v.starts_with('E') { if v.starts_with('E') {
v = &v[1..]; v = &v[1..];
} }
if v.as_bytes() if v.as_bytes()
.get(0) .first()
.map(|c| !c.is_ascii_digit()) .map(|c| !c.is_ascii_digit())
.unwrap_or(true) .unwrap_or(true)
{ {
@ -325,7 +327,7 @@ fn main() -> Result<(), std::io::Error> {
} }
} }
let mut file = File::create(&mod_path)?; let mut file = File::create(mod_path)?;
file.write_all( file.write_all(
br#"/* br#"/*
* meli - text_processing crate. * meli - text_processing crate.

View File

@ -24,12 +24,14 @@ pub mod vcard;
pub mod mutt; pub mod mutt;
use crate::datetime::{self, UnixTimestamp}; use std::{collections::HashMap, ops::Deref};
use crate::parsec::Parser;
use std::collections::HashMap;
use uuid::Uuid; use uuid::Uuid;
use std::ops::Deref; use crate::{
datetime::{self, UnixTimestamp},
parsec::Parser,
};
#[derive(Hash, Debug, PartialEq, Eq, Clone, Copy, Deserialize, Serialize)] #[derive(Hash, Debug, PartialEq, Eq, Clone, Copy, Deserialize, Serialize)]
#[serde(from = "String")] #[serde(from = "String")]
@ -85,7 +87,8 @@ pub struct Card {
last_edited: UnixTimestamp, last_edited: UnixTimestamp,
extra_properties: HashMap<String, String>, extra_properties: HashMap<String, String>,
/// If true, we can't make any changes because we do not manage this resource. /// If true, we can't make any changes because we do not manage this
/// resource.
external_resource: bool, external_resource: bool,
} }

View File

@ -20,11 +20,11 @@
*/ */
//! # Mutt contact formats //! # Mutt contact formats
//!
use std::collections::VecDeque;
use super::*; use super::*;
use crate::parsec::{is_not, map_res, match_literal_anycase, prefix, Parser}; use crate::parsec::{is_not, map_res, match_literal_anycase, prefix, Parser};
use std::collections::VecDeque;
//alias <nickname> [ <long name> ] <address> //alias <nickname> [ <long name> ] <address>
// From mutt doc: // From mutt doc:

View File

@ -27,11 +27,13 @@
//! - Version 4 [RFC 6350: vCard Format Specification](https://datatracker.ietf.org/doc/rfc6350/) //! - Version 4 [RFC 6350: vCard Format Specification](https://datatracker.ietf.org/doc/rfc6350/)
//! - Parameter escaping [RFC 6868 Parameter Value Encoding in iCalendar and vCard](https://datatracker.ietf.org/doc/rfc6868/) //! - Parameter escaping [RFC 6868 Parameter Value Encoding in iCalendar and vCard](https://datatracker.ietf.org/doc/rfc6868/)
use std::{collections::HashMap, convert::TryInto};
use super::*; use super::*;
use crate::error::{Error, Result}; use crate::{
use crate::parsec::{match_literal_anycase, one_or_more, peek, prefix, take_until, Parser}; error::{Error, Result},
use std::collections::HashMap; parsec::{match_literal_anycase, one_or_more, peek, prefix, take_until, Parser},
use std::convert::TryInto; };
/* Supported vcard versions */ /* Supported vcard versions */
pub trait VCardVersion: core::fmt::Debug {} pub trait VCardVersion: core::fmt::Debug {}
@ -86,7 +88,11 @@ impl CardDeserializer {
input = if (!input.starts_with(HEADER_CRLF) || !input.ends_with(FOOTER_CRLF)) input = if (!input.starts_with(HEADER_CRLF) || !input.ends_with(FOOTER_CRLF))
&& (!input.starts_with(HEADER_LF) || !input.ends_with(FOOTER_LF)) && (!input.starts_with(HEADER_LF) || !input.ends_with(FOOTER_LF))
{ {
return Err(Error::new(format!("Error while parsing vcard: input does not start or end with correct header and footer. input is:\n{:?}", input))); return Err(Error::new(format!(
"Error while parsing vcard: input does not start or end with correct header and \
footer. input is:\n{:?}",
input
)));
} else if input.starts_with(HEADER_CRLF) { } else if input.starts_with(HEADER_CRLF) {
&input[HEADER_CRLF.len()..input.len() - FOOTER_CRLF.len()] &input[HEADER_CRLF.len()..input.len() - FOOTER_CRLF.len()]
} else { } else {

View File

@ -36,35 +36,41 @@ pub mod jmap;
pub mod maildir; pub mod maildir;
#[cfg(feature = "mbox_backend")] #[cfg(feature = "mbox_backend")]
pub mod mbox; pub mod mbox;
use std::{
any::Any,
borrow::Cow,
collections::{BTreeSet, HashMap},
fmt,
fmt::Debug,
future::Future,
ops::Deref,
pin::Pin,
sync::{Arc, RwLock},
};
use futures::stream::Stream;
#[cfg(feature = "imap_backend")] #[cfg(feature = "imap_backend")]
pub use self::imap::ImapType; pub use self::imap::ImapType;
#[cfg(feature = "imap_backend")]
pub use self::nntp::NntpType;
use crate::conf::AccountSettings;
use crate::error::{Error, ErrorKind, Result};
#[cfg(feature = "maildir_backend")] #[cfg(feature = "maildir_backend")]
use self::maildir::MaildirType; use self::maildir::MaildirType;
#[cfg(feature = "mbox_backend")] #[cfg(feature = "mbox_backend")]
use self::mbox::MboxType; use self::mbox::MboxType;
#[cfg(feature = "imap_backend")]
pub use self::nntp::NntpType;
use super::email::{Envelope, EnvelopeHash, Flag}; use super::email::{Envelope, EnvelopeHash, Flag};
use futures::stream::Stream; use crate::{
use std::any::Any; conf::AccountSettings,
use std::borrow::Cow; error::{Error, ErrorKind, Result},
use std::collections::BTreeSet; };
use std::collections::HashMap;
use std::fmt;
use std::fmt::Debug;
use std::future::Future;
use std::ops::Deref;
use std::pin::Pin;
use std::sync::{Arc, RwLock};
#[macro_export] #[macro_export]
macro_rules! get_path_hash { macro_rules! get_path_hash {
($path:expr) => {{ ($path:expr) => {{
use std::collections::hash_map::DefaultHasher; use std::{
use std::hash::{Hash, Hasher}; collections::hash_map::DefaultHasher,
hash::{Hash, Hasher},
};
let mut hasher = DefaultHasher::new(); let mut hasher = DefaultHasher::new();
$path.hash(&mut hasher); $path.hash(&mut hasher);
hasher.finish() hasher.finish()
@ -97,10 +103,13 @@ impl Default for Backends {
} }
#[cfg(feature = "notmuch_backend")] #[cfg(feature = "notmuch_backend")]
pub const NOTMUCH_ERROR_MSG: &str = pub const NOTMUCH_ERROR_MSG: &str = "libnotmuch5 was not found in your system. Make sure it is \
"libnotmuch5 was not found in your system. Make sure it is installed and in the library paths. For a custom file path, use `library_file_path` setting in your notmuch account.\n"; installed and in the library paths. For a custom file path, \
use `library_file_path` setting in your notmuch account.\n";
#[cfg(not(feature = "notmuch_backend"))] #[cfg(not(feature = "notmuch_backend"))]
pub const NOTMUCH_ERROR_MSG: &str = "this version of meli is not compiled with notmuch support. Use an appropriate version and make sure libnotmuch5 is installed and in the library paths.\n"; pub const NOTMUCH_ERROR_MSG: &str = "this version of meli is not compiled with notmuch support. \
Use an appropriate version and make sure libnotmuch5 is \
installed and in the library paths.\n";
#[cfg(not(feature = "notmuch_backend"))] #[cfg(not(feature = "notmuch_backend"))]
pub const NOTMUCH_ERROR_DETAILS: &str = ""; pub const NOTMUCH_ERROR_DETAILS: &str = "";
@ -432,13 +441,14 @@ pub trait MailBackend: ::std::fmt::Debug + Send + Sync {
} }
} }
/// A `BackendOp` manages common operations for the various mail backends. They only live for the /// A `BackendOp` manages common operations for the various mail backends. They
/// duration of the operation. They are generated by the `operation` method of `Mailbackend` trait. /// only live for the duration of the operation. They are generated by the
/// `operation` method of `Mailbackend` trait.
/// ///
/// # Motivation /// # Motivation
/// ///
/// We need a way to do various operations on individual mails regardless of what backend they come /// We need a way to do various operations on individual mails regardless of
/// from (eg local or imap). /// what backend they come from (eg local or imap).
/// ///
/// # Creation /// # Creation
/// ```ignore /// ```ignore
@ -474,8 +484,8 @@ pub trait BackendOp: ::std::fmt::Debug + ::std::marker::Send {
/// Wrapper for BackendOps that are to be set read-only. /// Wrapper for BackendOps that are to be set read-only.
/// ///
/// Warning: Backend implementations may still cause side-effects (for example IMAP can set the /// Warning: Backend implementations may still cause side-effects (for example
/// Seen flag when fetching an envelope) /// IMAP can set the Seen flag when fetching an envelope)
#[derive(Debug)] #[derive(Debug)]
pub struct ReadOnlyOp { pub struct ReadOnlyOp {
op: Box<dyn BackendOp>, op: Box<dyn BackendOp>,

View File

@ -36,26 +36,29 @@ use cache::{ImapCacheReset, ModSequence};
pub mod managesieve; pub mod managesieve;
mod untagged; mod untagged;
use crate::backends::{ use std::{
RefreshEventKind::{self, *}, collections::{hash_map::DefaultHasher, BTreeSet, HashMap, HashSet},
*, convert::TryFrom,
hash::Hasher,
pin::Pin,
str::FromStr,
sync::{Arc, Mutex},
time::{Duration, SystemTime},
}; };
use crate::collection::Collection; use futures::{lock::Mutex as FutureMutex, stream::Stream};
use crate::conf::AccountSettings;
use crate::connections::timeout; use crate::{
use crate::email::{parser::BytesExt, *}; backends::{
use crate::error::{Error, Result, ResultIntoError}; RefreshEventKind::{self, *},
use futures::lock::Mutex as FutureMutex; *,
use futures::stream::Stream; },
use std::collections::hash_map::DefaultHasher; collection::Collection,
use std::collections::{BTreeSet, HashMap, HashSet}; conf::AccountSettings,
use std::convert::TryFrom; connections::timeout,
use std::hash::Hasher; email::{parser::BytesExt, *},
use std::pin::Pin; error::{Error, Result, ResultIntoError},
use std::str::FromStr; };
use std::sync::{Arc, Mutex};
use std::time::{Duration, SystemTime};
pub type ImapNum = usize; pub type ImapNum = usize;
pub type UID = ImapNum; pub type UID = ImapNum;
@ -340,7 +343,8 @@ impl MailBackend for ImapType {
cache_handle, cache_handle,
}; };
/* do this in a closure to prevent recursion limit error in async_stream macro */ /* do this in a closure to prevent recursion limit error in async_stream
* macro */
let prepare_cl = |f: &ImapMailbox| { let prepare_cl = |f: &ImapMailbox| {
f.set_warm(true); f.set_warm(true);
if let Ok(mut exists) = f.exists.lock() { if let Ok(mut exists) = f.exists.lock() {
@ -526,15 +530,15 @@ impl MailBackend for ImapType {
} }
fn operation(&self, hash: EnvelopeHash) -> Result<Box<dyn BackendOp>> { fn operation(&self, hash: EnvelopeHash) -> Result<Box<dyn BackendOp>> {
let (uid, mailbox_hash) = if let Some(v) = let (uid, mailbox_hash) =
self.uid_store.hash_index.lock().unwrap().get(&hash) if let Some(v) = self.uid_store.hash_index.lock().unwrap().get(&hash) {
{ *v
*v } else {
} else { return Err(Error::new(
return Err(Error::new( "Message not found in local cache, it might have been deleted before you \
"Message not found in local cache, it might have been deleted before you requested it." requested it.",
)); ));
}; };
Ok(Box::new(ImapOp::new( Ok(Box::new(ImapOp::new(
uid, uid,
mailbox_hash, mailbox_hash,
@ -749,8 +753,20 @@ impl MailBackend for ImapType {
cmd.push_str("\\Draft "); cmd.push_str("\\Draft ");
} }
Ok(_) => { Ok(_) => {
crate::log(format!("Application error: more than one flag bit set in set_flags: {:?}", flags), crate::ERROR); crate::log(
return Err(Error::new(format!("Application error: more than one flag bit set in set_flags: {:?}", flags)).set_kind(crate::ErrorKind::Bug)); format!(
"Application error: more than one flag bit set in \
set_flags: {:?}",
flags
),
crate::ERROR,
);
return Err(Error::new(format!(
"Application error: more than one flag bit set in set_flags: \
{:?}",
flags
))
.set_kind(crate::ErrorKind::Bug));
} }
Err(tag) => { Err(tag) => {
let hash = TagHash::from_bytes(tag.as_bytes()); let hash = TagHash::from_bytes(tag.as_bytes());
@ -812,13 +828,17 @@ impl MailBackend for ImapType {
Ok(_) => { Ok(_) => {
crate::log( crate::log(
format!( format!(
"Application error: more than one flag bit set in set_flags: {:?}", flags "Application error: more than one flag bit set in \
), set_flags: {:?}",
flags
),
crate::ERROR, crate::ERROR,
); );
return Err(Error::new(format!( return Err(Error::new(format!(
"Application error: more than one flag bit set in set_flags: {:?}", flags "Application error: more than one flag bit set in set_flags: \
))); {:?}",
flags
)));
} }
Err(tag) => { Err(tag) => {
cmd.push_str(tag); cmd.push_str(tag);
@ -892,16 +912,17 @@ impl MailBackend for ImapType {
Ok(Box::pin(async move { Ok(Box::pin(async move {
/* Must transform path to something the IMAP server will accept /* Must transform path to something the IMAP server will accept
* *
* Each root mailbox has a hierarchy delimeter reported by the LIST entry. All paths * Each root mailbox has a hierarchy delimeter reported by the LIST entry.
* must use this delimeter to indicate children of this mailbox. * All paths must use this delimeter to indicate children of this
* mailbox.
* *
* A new root mailbox should have the default delimeter, which can be found out by issuing * A new root mailbox should have the default delimeter, which can be found
* an empty LIST command as described in RFC3501: * out by issuing an empty LIST command as described in RFC3501:
* C: A101 LIST "" "" * C: A101 LIST "" ""
* S: * LIST (\Noselect) "/" "" * S: * LIST (\Noselect) "/" ""
* *
* The default delimiter for us is '/' just like UNIX paths. I apologise if this * The default delimiter for us is '/' just like UNIX paths. I apologise if
* decision is unpleasant for you. * this decision is unpleasant for you.
*/ */
{ {
@ -924,8 +945,8 @@ impl MailBackend for ImapType {
} }
} }
/* FIXME Do not try to CREATE a sub-mailbox in a mailbox that has the \Noinferiors /* FIXME Do not try to CREATE a sub-mailbox in a mailbox
* flag set. */ * that has the \Noinferiors flag set. */
} }
let mut response = Vec::with_capacity(8 * 1024); let mut response = Vec::with_capacity(8 * 1024);
@ -950,7 +971,17 @@ impl MailBackend for ImapType {
ret?; ret?;
let new_hash = MailboxHash::from_bytes(path.as_str().as_bytes()); let new_hash = MailboxHash::from_bytes(path.as_str().as_bytes());
uid_store.mailboxes.lock().await.clear(); uid_store.mailboxes.lock().await.clear();
Ok((new_hash, new_mailbox_fut?.await.map_err(|err| Error::new(format!("Mailbox create was succesful (returned `{}`) but listing mailboxes afterwards returned `{}`", String::from_utf8_lossy(&response), err)))?)) Ok((
new_hash,
new_mailbox_fut?.await.map_err(|err| {
Error::new(format!(
"Mailbox create was succesful (returned `{}`) but listing mailboxes \
afterwards returned `{}`",
String::from_utf8_lossy(&response),
err
))
})?,
))
})) }))
} }
@ -970,7 +1001,12 @@ impl MailBackend for ImapType {
imap_path = mailboxes[&mailbox_hash].imap_path().to_string(); imap_path = mailboxes[&mailbox_hash].imap_path().to_string();
let permissions = mailboxes[&mailbox_hash].permissions(); let permissions = mailboxes[&mailbox_hash].permissions();
if !permissions.delete_mailbox { if !permissions.delete_mailbox {
return Err(Error::new(format!("You do not have permission to delete `{}`. Set permissions for this mailbox are {}", mailboxes[&mailbox_hash].name(), permissions))); return Err(Error::new(format!(
"You do not have permission to delete `{}`. Set permissions for this \
mailbox are {}",
mailboxes[&mailbox_hash].name(),
permissions
)));
} }
} }
let mut response = Vec::with_capacity(8 * 1024); let mut response = Vec::with_capacity(8 * 1024);
@ -998,7 +1034,15 @@ impl MailBackend for ImapType {
let ret: Result<()> = ImapResponse::try_from(response.as_slice())?.into(); let ret: Result<()> = ImapResponse::try_from(response.as_slice())?.into();
ret?; ret?;
uid_store.mailboxes.lock().await.clear(); uid_store.mailboxes.lock().await.clear();
new_mailbox_fut?.await.map_err(|err| format!("Mailbox delete was succesful (returned `{}`) but listing mailboxes afterwards returned `{}`", String::from_utf8_lossy(&response), err).into()) new_mailbox_fut?.await.map_err(|err| {
format!(
"Mailbox delete was succesful (returned `{}`) but listing mailboxes \
afterwards returned `{}`",
String::from_utf8_lossy(&response),
err
)
.into()
})
})) }))
} }
@ -1064,7 +1108,12 @@ impl MailBackend for ImapType {
let mailboxes = uid_store.mailboxes.lock().await; let mailboxes = uid_store.mailboxes.lock().await;
let permissions = mailboxes[&mailbox_hash].permissions(); let permissions = mailboxes[&mailbox_hash].permissions();
if !permissions.delete_mailbox { if !permissions.delete_mailbox {
return Err(Error::new(format!("You do not have permission to rename mailbox `{}` (rename is equivalent to delete + create). Set permissions for this mailbox are {}", mailboxes[&mailbox_hash].name(), permissions))); return Err(Error::new(format!(
"You do not have permission to rename mailbox `{}` (rename is equivalent \
to delete + create). Set permissions for this mailbox are {}",
mailboxes[&mailbox_hash].name(),
permissions
)));
} }
if mailboxes[&mailbox_hash].separator != b'/' { if mailboxes[&mailbox_hash].separator != b'/' {
new_path = new_path.replace( new_path = new_path.replace(
@ -1089,7 +1138,14 @@ impl MailBackend for ImapType {
let ret: Result<()> = ImapResponse::try_from(response.as_slice())?.into(); let ret: Result<()> = ImapResponse::try_from(response.as_slice())?.into();
ret?; ret?;
uid_store.mailboxes.lock().await.clear(); uid_store.mailboxes.lock().await.clear();
new_mailbox_fut?.await.map_err(|err| format!("Mailbox rename was succesful (returned `{}`) but listing mailboxes afterwards returned `{}`", String::from_utf8_lossy(&response), err))?; new_mailbox_fut?.await.map_err(|err| {
format!(
"Mailbox rename was succesful (returned `{}`) but listing mailboxes \
afterwards returned `{}`",
String::from_utf8_lossy(&response),
err
)
})?;
Ok(BackendMailbox::clone( Ok(BackendMailbox::clone(
&uid_store.mailboxes.lock().await[&new_hash], &uid_store.mailboxes.lock().await[&new_hash],
)) ))
@ -1107,7 +1163,12 @@ impl MailBackend for ImapType {
let mailboxes = uid_store.mailboxes.lock().await; let mailboxes = uid_store.mailboxes.lock().await;
let permissions = mailboxes[&mailbox_hash].permissions(); let permissions = mailboxes[&mailbox_hash].permissions();
if !permissions.change_permissions { if !permissions.change_permissions {
return Err(Error::new(format!("You do not have permission to change permissions for mailbox `{}`. Set permissions for this mailbox are {}", mailboxes[&mailbox_hash].name(), permissions))); return Err(Error::new(format!(
"You do not have permission to change permissions for mailbox `{}`. Set \
permissions for this mailbox are {}",
mailboxes[&mailbox_hash].name(),
permissions
)));
} }
Err(Error::new("Unimplemented.")) Err(Error::new("Unimplemented."))
@ -1262,7 +1323,8 @@ impl ImapType {
if use_oauth2 && !s.extra.contains_key("server_password_command") { if use_oauth2 && !s.extra.contains_key("server_password_command") {
return Err(Error::new(format!( return Err(Error::new(format!(
"({}) `use_oauth2` use requires `server_password_command` set with a command that returns an OAUTH2 token. Consult documentation for guidance.", "({}) `use_oauth2` use requires `server_password_command` set with a command that \
returns an OAUTH2 token. Consult documentation for guidance.",
s.name, s.name,
))); )));
} }
@ -1524,14 +1586,16 @@ impl ImapType {
if !s.extra.contains_key("server_password_command") { if !s.extra.contains_key("server_password_command") {
if use_oauth2 { if use_oauth2 {
return Err(Error::new(format!( return Err(Error::new(format!(
"({}) `use_oauth2` use requires `server_password_command` set with a command that returns an OAUTH2 token. Consult documentation for guidance.", "({}) `use_oauth2` use requires `server_password_command` set with a command \
that returns an OAUTH2 token. Consult documentation for guidance.",
s.name, s.name,
))); )));
} }
get_conf_val!(s["server_password"])?; get_conf_val!(s["server_password"])?;
} else if s.extra.contains_key("server_password") { } else if s.extra.contains_key("server_password") {
return Err(Error::new(format!( return Err(Error::new(format!(
"Configuration error ({}): both server_password and server_password_command are set, cannot choose", "Configuration error ({}): both server_password and server_password_command are \
set, cannot choose",
s.name.as_str(), s.name.as_str(),
))); )));
} }
@ -1541,7 +1605,8 @@ impl ImapType {
let use_starttls = get_conf_val!(s["use_starttls"], false)?; let use_starttls = get_conf_val!(s["use_starttls"], false)?;
if !use_tls && use_starttls { if !use_tls && use_starttls {
return Err(Error::new(format!( return Err(Error::new(format!(
"Configuration error ({}): incompatible use_tls and use_starttls values: use_tls = false, use_starttls = true", "Configuration error ({}): incompatible use_tls and use_starttls values: use_tls \
= false, use_starttls = true",
s.name.as_str(), s.name.as_str(),
))); )));
} }
@ -1565,7 +1630,8 @@ impl ImapType {
#[cfg(not(feature = "deflate_compression"))] #[cfg(not(feature = "deflate_compression"))]
if s.extra.contains_key("use_deflate") { if s.extra.contains_key("use_deflate") {
return Err(Error::new(format!( return Err(Error::new(format!(
"Configuration error ({}): setting `use_deflate` is set but this version of meli isn't compiled with DEFLATE support.", "Configuration error ({}): setting `use_deflate` is set but this version of meli \
isn't compiled with DEFLATE support.",
s.name.as_str(), s.name.as_str(),
))); )));
} }
@ -1578,8 +1644,10 @@ impl ImapType {
let diff = extra_keys.difference(&keys).collect::<Vec<&&str>>(); let diff = extra_keys.difference(&keys).collect::<Vec<&&str>>();
if !diff.is_empty() { if !diff.is_empty() {
return Err(Error::new(format!( return Err(Error::new(format!(
"Configuration error ({}): the following flags are set but are not recognized: {:?}.", "Configuration error ({}): the following flags are set but are not recognized: \
s.name.as_str(), diff {:?}.",
s.name.as_str(),
diff
))); )));
} }
Ok(()) Ok(())
@ -1658,7 +1726,14 @@ async fn fetch_hlpr(state: &mut FetchState) -> Result<Vec<Envelope>> {
/* Try resetting the database */ /* Try resetting the database */
if let Some(ref mut cache_handle) = state.cache_handle { if let Some(ref mut cache_handle) = state.cache_handle {
if let Err(err) = cache_handle.reset() { if let Err(err) = cache_handle.reset() {
crate::log(format!("IMAP cache error: could not reset cache for {}. Reason: {}", state.uid_store.account_name, err), crate::ERROR); crate::log(
format!(
"IMAP cache error: could not reset cache for {}. Reason: \
{}",
state.uid_store.account_name, err
),
crate::ERROR,
);
} }
} }
state.stage = FetchStage::InitialFresh; state.stage = FetchStage::InitialFresh;
@ -1743,11 +1818,14 @@ async fn fetch_hlpr(state: &mut FetchState) -> Result<Vec<Envelope>> {
if max_uid_left > 0 { if max_uid_left > 0 {
debug!("{} max_uid_left= {}", mailbox_hash, max_uid_left); debug!("{} max_uid_left= {}", mailbox_hash, max_uid_left);
let command = if max_uid_left == 1 { let command = if max_uid_left == 1 {
"UID FETCH 1 (UID FLAGS ENVELOPE BODY.PEEK[HEADER.FIELDS (REFERENCES)] BODYSTRUCTURE)".to_string() "UID FETCH 1 (UID FLAGS ENVELOPE BODY.PEEK[HEADER.FIELDS (REFERENCES)] \
BODYSTRUCTURE)"
.to_string()
} else { } else {
format!( format!(
"UID FETCH {}:{} (UID FLAGS ENVELOPE BODY.PEEK[HEADER.FIELDS (REFERENCES)] BODYSTRUCTURE)", "UID FETCH {}:{} (UID FLAGS ENVELOPE BODY.PEEK[HEADER.FIELDS \
std::cmp::max(max_uid_left.saturating_sub(chunk_size), 1), (REFERENCES)] BODYSTRUCTURE)",
std::cmp::max(max_uid_left.saturating_sub(chunk_size), 1),
max_uid_left max_uid_left
) )
}; };

View File

@ -21,12 +21,13 @@
use super::*; use super::*;
mod sync; mod sync;
use std::convert::TryFrom;
use crate::{ use crate::{
backends::MailboxHash, backends::MailboxHash,
email::{Envelope, EnvelopeHash}, email::{Envelope, EnvelopeHash},
error::*, error::*,
}; };
use std::convert::TryFrom;
#[derive(Debug, PartialEq, Hash, Eq, Ord, PartialOrd, Copy, Clone)] #[derive(Debug, PartialEq, Hash, Eq, Ord, PartialOrd, Copy, Clone)]
pub struct ModSequence(pub std::num::NonZeroU64); pub struct ModSequence(pub std::num::NonZeroU64);
@ -105,10 +106,11 @@ pub use sqlite3_m::*;
#[cfg(feature = "sqlite3")] #[cfg(feature = "sqlite3")]
mod sqlite3_m { mod sqlite3_m {
use super::*; use super::*;
use crate::sqlite3::rusqlite::types::{ use crate::sqlite3::{
FromSql, FromSqlError, FromSqlResult, ToSql, ToSqlOutput, self,
rusqlite::types::{FromSql, FromSqlError, FromSqlResult, ToSql, ToSqlOutput},
DatabaseDescription,
}; };
use crate::sqlite3::{self, DatabaseDescription};
type Sqlite3UID = i32; type Sqlite3UID = i32;
@ -287,23 +289,45 @@ mod sqlite3_m {
})?; })?;
if let Some(Ok(highestmodseq)) = select_response.highestmodseq { if let Some(Ok(highestmodseq)) = select_response.highestmodseq {
self.connection.execute( self.connection
"INSERT OR IGNORE INTO mailbox (uidvalidity, flags, highestmodseq, mailbox_hash) VALUES (?1, ?2, ?3, ?4)", .execute(
sqlite3::params![select_response.uidvalidity as Sqlite3UID, select_response.flags.1.iter().map(|s| s.as_str()).collect::<Vec<&str>>().join("\0").as_bytes(), highestmodseq, mailbox_hash], "INSERT OR IGNORE INTO mailbox (uidvalidity, flags, highestmodseq, \
) mailbox_hash) VALUES (?1, ?2, ?3, ?4)",
.chain_err_summary(|| { sqlite3::params![
format!( select_response.uidvalidity as Sqlite3UID,
"Could not insert uidvalidity {} in header_cache of account {}", select_response
select_response.uidvalidity, self.uid_store.account_name .flags
) .1
})?; .iter()
.map(|s| s.as_str())
.collect::<Vec<&str>>()
.join("\0")
.as_bytes(),
highestmodseq,
mailbox_hash
],
)
.chain_err_summary(|| {
format!(
"Could not insert uidvalidity {} in header_cache of account {}",
select_response.uidvalidity, self.uid_store.account_name
)
})?;
} else { } else {
self.connection self.connection
.execute( .execute(
"INSERT OR IGNORE INTO mailbox (uidvalidity, flags, mailbox_hash) VALUES (?1, ?2, ?3)", "INSERT OR IGNORE INTO mailbox (uidvalidity, flags, mailbox_hash) VALUES \
(?1, ?2, ?3)",
sqlite3::params![ sqlite3::params![
select_response.uidvalidity as Sqlite3UID, select_response.uidvalidity as Sqlite3UID,
select_response.flags.1.iter().map(|s| s.as_str()).collect::<Vec<&str>>().join("\0").as_bytes(), select_response
.flags
.1
.iter()
.map(|s| s.as_str())
.collect::<Vec<&str>>()
.join("\0")
.as_bytes(),
mailbox_hash mailbox_hash
], ],
) )
@ -463,9 +487,24 @@ mod sqlite3_m {
{ {
max_uid = std::cmp::max(max_uid, *uid); max_uid = std::cmp::max(max_uid, *uid);
tx.execute( tx.execute(
"INSERT OR REPLACE INTO envelopes (hash, uid, mailbox_hash, modsequence, envelope) VALUES (?1, ?2, ?3, ?4, ?5)", "INSERT OR REPLACE INTO envelopes (hash, uid, mailbox_hash, modsequence, \
sqlite3::params![envelope.hash(), *uid as Sqlite3UID, mailbox_hash, modseq, &envelope], envelope) VALUES (?1, ?2, ?3, ?4, ?5)",
).chain_err_summary(|| format!("Could not insert envelope {} {} in header_cache of account {}", envelope.message_id(), envelope.hash(), uid_store.account_name))?; sqlite3::params![
envelope.hash(),
*uid as Sqlite3UID,
mailbox_hash,
modseq,
&envelope
],
)
.chain_err_summary(|| {
format!(
"Could not insert envelope {} {} in header_cache of account {}",
envelope.message_id(),
envelope.hash(),
uid_store.account_name
)
})?;
} }
} }
tx.commit()?; tx.commit()?;
@ -523,15 +562,17 @@ mod sqlite3_m {
env.tags_mut() env.tags_mut()
.extend(tags.iter().map(|t| TagHash::from_bytes(t.as_bytes()))); .extend(tags.iter().map(|t| TagHash::from_bytes(t.as_bytes())));
tx.execute( tx.execute(
"UPDATE envelopes SET envelope = ?1 WHERE mailbox_hash = ?2 AND uid = ?3;", "UPDATE envelopes SET envelope = ?1 WHERE mailbox_hash = ?2 AND \
sqlite3::params![&env, mailbox_hash, *uid as Sqlite3UID], uid = ?3;",
) sqlite3::params![&env, mailbox_hash, *uid as Sqlite3UID],
.chain_err_summary(|| { )
format!( .chain_err_summary(|| {
"Could not update envelope {} uid {} from mailbox {} account {}", format!(
env_hash, *uid, mailbox_hash, uid_store.account_name "Could not update envelope {} uid {} from mailbox {} account \
) {}",
})?; env_hash, *uid, mailbox_hash, uid_store.account_name
)
})?;
uid_store uid_store
.envelopes .envelopes
.lock() .lock()
@ -563,8 +604,9 @@ mod sqlite3_m {
let mut ret: Vec<(UID, Envelope, Option<ModSequence>)> = match identifier { let mut ret: Vec<(UID, Envelope, Option<ModSequence>)> = match identifier {
Ok(uid) => { Ok(uid) => {
let mut stmt = self.connection.prepare( let mut stmt = self.connection.prepare(
"SELECT uid, envelope, modsequence FROM envelopes WHERE mailbox_hash = ?1 AND uid = ?2;", "SELECT uid, envelope, modsequence FROM envelopes WHERE mailbox_hash = ?1 \
)?; AND uid = ?2;",
)?;
let x = stmt let x = stmt
.query_map(sqlite3::params![mailbox_hash, uid as Sqlite3UID], |row| { .query_map(sqlite3::params![mailbox_hash, uid as Sqlite3UID], |row| {
@ -579,8 +621,9 @@ mod sqlite3_m {
} }
Err(env_hash) => { Err(env_hash) => {
let mut stmt = self.connection.prepare( let mut stmt = self.connection.prepare(
"SELECT uid, envelope, modsequence FROM envelopes WHERE mailbox_hash = ?1 AND hash = ?2;", "SELECT uid, envelope, modsequence FROM envelopes WHERE mailbox_hash = ?1 \
)?; AND hash = ?2;",
)?;
let x = stmt let x = stmt
.query_map(sqlite3::params![mailbox_hash, env_hash], |row| { .query_map(sqlite3::params![mailbox_hash, env_hash], |row| {

View File

@ -130,7 +130,8 @@ impl ImapConnection {
// 2. tag1 UID FETCH <lastseenuid+1>:* <descriptors> // 2. tag1 UID FETCH <lastseenuid+1>:* <descriptors>
self.send_command( self.send_command(
format!( format!(
"UID FETCH {}:* (UID FLAGS ENVELOPE BODY.PEEK[HEADER.FIELDS (REFERENCES)] BODYSTRUCTURE)", "UID FETCH {}:* (UID FLAGS ENVELOPE BODY.PEEK[HEADER.FIELDS (REFERENCES)] \
BODYSTRUCTURE)",
max_uid + 1 max_uid + 1
) )
.as_bytes(), .as_bytes(),
@ -375,9 +376,9 @@ impl ImapConnection {
// client MUST // client MUST
// * empty the local cache of that mailbox; // * empty the local cache of that mailbox;
// * "forget" the cached HIGHESTMODSEQ value for the mailbox; // * "forget" the cached HIGHESTMODSEQ value for the mailbox;
// * remove any pending "actions" that refer to UIDs in that // * remove any pending "actions" that refer to UIDs in that mailbox (note
// mailbox (note that this doesn't affect actions performed on // that this doesn't affect actions performed on client-generated fake UIDs;
// client-generated fake UIDs; see Section 5); and // see Section 5); and
// * skip steps 1b and 2-II; // * skip steps 1b and 2-II;
cache_handle.clear(mailbox_hash, &select_response)?; cache_handle.clear(mailbox_hash, &select_response)?;
return Ok(None); return Ok(None);
@ -398,9 +399,9 @@ impl ImapConnection {
let new_highestmodseq = select_response.highestmodseq.unwrap().unwrap(); let new_highestmodseq = select_response.highestmodseq.unwrap().unwrap();
let mut refresh_events = vec![]; let mut refresh_events = vec![];
// 1b) Check the mailbox HIGHESTMODSEQ. // 1b) Check the mailbox HIGHESTMODSEQ.
// If the cached value is the same as the one returned by the server, skip fetching // If the cached value is the same as the one returned by the server, skip
// message flags on step 2-II, i.e., the client only has to find out which messages got // fetching message flags on step 2-II, i.e., the client only has to
// expunged. // find out which messages got expunged.
if cached_highestmodseq != new_highestmodseq { if cached_highestmodseq != new_highestmodseq {
/* Cache is synced, only figure out which messages got expunged */ /* Cache is synced, only figure out which messages got expunged */
@ -415,7 +416,8 @@ impl ImapConnection {
// 2. tag1 UID FETCH <lastseenuid+1>:* <descriptors> // 2. tag1 UID FETCH <lastseenuid+1>:* <descriptors>
self.send_command( self.send_command(
format!( format!(
"UID FETCH {}:* (UID FLAGS ENVELOPE BODY.PEEK[HEADER.FIELDS (REFERENCES)] BODYSTRUCTURE) (CHANGEDSINCE {})", "UID FETCH {}:* (UID FLAGS ENVELOPE BODY.PEEK[HEADER.FIELDS (REFERENCES)] \
BODYSTRUCTURE) (CHANGEDSINCE {})",
cached_max_uid + 1, cached_max_uid + 1,
cached_highestmodseq, cached_highestmodseq,
) )
@ -571,8 +573,8 @@ impl ImapConnection {
.insert(mailbox_hash, Ok(new_highestmodseq)); .insert(mailbox_hash, Ok(new_highestmodseq));
} }
let mut valid_envs = BTreeSet::default(); let mut valid_envs = BTreeSet::default();
// This should be UID SEARCH 1:<maxuid> but it's difficult to compare to cached UIDs at the // This should be UID SEARCH 1:<maxuid> but it's difficult to compare to cached
// point of calling this function // UIDs at the point of calling this function
self.send_command(b"UID SEARCH ALL").await?; self.send_command(b"UID SEARCH ALL").await?;
self.read_response(&mut response, RequiredResponses::SEARCH) self.read_response(&mut response, RequiredResponses::SEARCH)
.await?; .await?;
@ -614,7 +616,8 @@ impl ImapConnection {
Ok(Some(payload.into_iter().map(|(_, env)| env).collect())) Ok(Some(payload.into_iter().map(|(_, env)| env).collect()))
} }
//rfc7162_Quick Flag Changes Resynchronization (CONDSTORE)_and Quick Mailbox Resynchronization (QRESYNC) //rfc7162_Quick Flag Changes Resynchronization (CONDSTORE)_and Quick Mailbox
// Resynchronization (QRESYNC)
pub async fn resync_condstoreqresync( pub async fn resync_condstoreqresync(
&mut self, &mut self,
_cache_handle: Box<dyn ImapCache>, _cache_handle: Box<dyn ImapCache>,
@ -634,8 +637,8 @@ impl ImapConnection {
) )
}; };
/* first SELECT the mailbox to get READ/WRITE permissions (because EXAMINE only /* first SELECT the mailbox to get READ/WRITE permissions (because EXAMINE
* returns READ-ONLY for both cases) */ * only returns READ-ONLY for both cases) */
let mut select_response = self let mut select_response = self
.select_mailbox(mailbox_hash, &mut response, true) .select_mailbox(mailbox_hash, &mut response, true)
.await? .await?

View File

@ -20,33 +20,38 @@
*/ */
use super::protocol_parser::{ImapLineSplit, ImapResponse, RequiredResponses, SelectResponse}; use super::protocol_parser::{ImapLineSplit, ImapResponse, RequiredResponses, SelectResponse};
use crate::backends::{MailboxHash, RefreshEvent}; use crate::{
use crate::connections::{lookup_ipv4, timeout, Connection}; backends::{MailboxHash, RefreshEvent},
use crate::email::parser::BytesExt; connections::{lookup_ipv4, timeout, Connection},
use crate::error::*; email::parser::BytesExt,
error::*,
};
extern crate native_tls; extern crate native_tls;
use std::{
collections::HashSet,
convert::TryFrom,
future::Future,
iter::FromIterator,
pin::Pin,
sync::Arc,
time::{Duration, Instant, SystemTime},
};
use futures::io::{AsyncReadExt, AsyncWriteExt}; use futures::io::{AsyncReadExt, AsyncWriteExt};
use native_tls::TlsConnector; use native_tls::TlsConnector;
pub use smol::Async as AsyncWrapper; pub use smol::Async as AsyncWrapper;
use std::collections::HashSet;
use std::convert::TryFrom;
use std::future::Future;
use std::iter::FromIterator;
use std::pin::Pin;
use std::sync::Arc;
use std::time::{Duration, Instant, SystemTime};
const IMAP_PROTOCOL_TIMEOUT: Duration = Duration::from_secs(60 * 28); const IMAP_PROTOCOL_TIMEOUT: Duration = Duration::from_secs(60 * 28);
use super::protocol_parser; use super::{protocol_parser, Capabilities, ImapServerConf, UIDStore};
use super::{Capabilities, ImapServerConf, UIDStore};
#[derive(Debug, Clone, Copy)] #[derive(Debug, Clone, Copy)]
pub enum SyncPolicy { pub enum SyncPolicy {
None, None,
///rfc4549 `Synch Ops for Disconnected IMAP4 Clients` <https://tools.ietf.org/html/rfc4549> ///rfc4549 `Synch Ops for Disconnected IMAP4 Clients` <https://tools.ietf.org/html/rfc4549>
Basic, Basic,
///rfc7162 `IMAP Extensions: Quick Flag Changes Resynchronization (CONDSTORE) and Quick Mailbox Resynchronization (QRESYNC)` ///rfc7162 `IMAP Extensions: Quick Flag Changes Resynchronization
/// (CONDSTORE) and Quick Mailbox Resynchronization (QRESYNC)`
Condstore, Condstore,
CondstoreQresync, CondstoreQresync,
} }
@ -144,13 +149,14 @@ impl ImapStream {
if let Some(timeout) = server_conf.timeout { if let Some(timeout) = server_conf.timeout {
TcpStream::connect_timeout(&addr, timeout)? TcpStream::connect_timeout(&addr, timeout)?
} else { } else {
TcpStream::connect(&addr)? TcpStream::connect(addr)?
}, },
))?; ))?;
if server_conf.use_starttls { if server_conf.use_starttls {
let err_fn = || { let err_fn = || {
if server_conf.server_port == 993 { if server_conf.server_port == 993 {
"STARTTLS failed. Server port is set to 993, which normally uses TLS. Maybe try disabling use_starttls." "STARTTLS failed. Server port is set to 993, which normally uses TLS. \
Maybe try disabling use_starttls."
} else { } else {
"STARTTLS failed. Is the connection already encrypted?" "STARTTLS failed. Is the connection already encrypted?"
} }
@ -246,7 +252,7 @@ impl ImapStream {
if let Some(timeout) = server_conf.timeout { if let Some(timeout) = server_conf.timeout {
TcpStream::connect_timeout(&addr, timeout)? TcpStream::connect_timeout(&addr, timeout)?
} else { } else {
TcpStream::connect(&addr)? TcpStream::connect(addr)?
}, },
))? ))?
}; };
@ -350,10 +356,14 @@ impl ImapStream {
.any(|cap| cap.eq_ignore_ascii_case(b"AUTH=XOAUTH2")) .any(|cap| cap.eq_ignore_ascii_case(b"AUTH=XOAUTH2"))
{ {
return Err(Error::new(format!( return Err(Error::new(format!(
"Could not connect to {}: OAUTH2 is enabled but server did not return AUTH=XOAUTH2 capability. Returned capabilities were: {}", "Could not connect to {}: OAUTH2 is enabled but server did not return \
&server_conf.server_hostname, AUTH=XOAUTH2 capability. Returned capabilities were: {}",
capabilities.iter().map(|capability| &server_conf.server_hostname,
String::from_utf8_lossy(capability).to_string()).collect::<Vec<String>>().join(" ") capabilities
.iter()
.map(|capability| String::from_utf8_lossy(capability).to_string())
.collect::<Vec<String>>()
.join(" ")
))); )));
} }
ret.send_command( ret.send_command(
@ -414,8 +424,8 @@ impl ImapStream {
} }
if capabilities.is_none() { if capabilities.is_none() {
/* sending CAPABILITY after LOGIN automatically is an RFC recommendation, so check /* sending CAPABILITY after LOGIN automatically is an RFC recommendation, so
* for lazy servers */ * check for lazy servers */
drop(capabilities); drop(capabilities);
ret.send_command(b"CAPABILITY").await?; ret.send_command(b"CAPABILITY").await?;
ret.read_response(&mut res).await.unwrap(); ret.read_response(&mut res).await.unwrap();
@ -648,7 +658,14 @@ impl ImapConnection {
| ImapResponse::Bad(code) | ImapResponse::Bad(code)
| ImapResponse::Preauth(code) | ImapResponse::Preauth(code)
| ImapResponse::Bye(code) => { | ImapResponse::Bye(code) => {
crate::log(format!("Could not use COMPRESS=DEFLATE in account `{}`: server replied with `{}`", self.uid_store.account_name, code), crate::LoggingLevel::WARN); crate::log(
format!(
"Could not use COMPRESS=DEFLATE in account `{}`: server \
replied with `{}`",
self.uid_store.account_name, code
),
crate::LoggingLevel::WARN,
);
} }
ImapResponse::Ok(_) => { ImapResponse::Ok(_) => {
let ImapStream { let ImapStream {
@ -750,7 +767,7 @@ impl ImapConnection {
&required_responses &required_responses
);*/ );*/
for l in response.split_rn() { for l in response.split_rn() {
/*debug!("check line: {}", &l);*/ /* debug!("check line: {}", &l); */
if required_responses.check(l) || !self.process_untagged(l).await? { if required_responses.check(l) || !self.process_untagged(l).await? {
ret.extend_from_slice(l); ret.extend_from_slice(l);
} }

View File

@ -19,13 +19,16 @@
* along with meli. If not, see <http://www.gnu.org/licenses/>. * along with meli. If not, see <http://www.gnu.org/licenses/>.
*/ */
use super::protocol_parser::SelectResponse;
use crate::backends::{
BackendMailbox, LazyCountSet, Mailbox, MailboxHash, MailboxPermissions, SpecialUsageMailbox,
};
use crate::error::*;
use std::sync::{Arc, Mutex, RwLock}; use std::sync::{Arc, Mutex, RwLock};
use super::protocol_parser::SelectResponse;
use crate::{
backends::{
BackendMailbox, LazyCountSet, Mailbox, MailboxHash, MailboxPermissions, SpecialUsageMailbox,
},
error::*,
};
#[derive(Debug, Default, Clone)] #[derive(Debug, Default, Clone)]
pub struct ImapMailbox { pub struct ImapMailbox {
pub hash: MailboxHash, pub hash: MailboxHash,
@ -51,7 +54,8 @@ impl ImapMailbox {
&self.imap_path &self.imap_path
} }
/// Establish that mailbox contents have been fetched at least once during this execution /// Establish that mailbox contents have been fetched at least once during
/// this execution
#[inline(always)] #[inline(always)]
pub fn set_warm(&self, new_value: bool) { pub fn set_warm(&self, new_value: bool) {
*self.warm.lock().unwrap() = new_value; *self.warm.lock().unwrap() = new_value;

View File

@ -19,19 +19,25 @@
* along with meli. If not, see <http://www.gnu.org/licenses/>. * along with meli. If not, see <http://www.gnu.org/licenses/>.
*/ */
use super::{ImapConnection, ImapProtocol, ImapServerConf, UIDStore}; use std::{
use crate::conf::AccountSettings; str::FromStr,
use crate::email::parser::IResult; sync::{Arc, Mutex},
use crate::error::{Error, Result}; time::SystemTime,
use crate::get_conf_val; };
use crate::imap::RequiredResponses;
use nom::{ use nom::{
branch::alt, bytes::complete::tag, combinator::map, multi::separated_list1, branch::alt, bytes::complete::tag, combinator::map, multi::separated_list1,
sequence::separated_pair, sequence::separated_pair,
}; };
use std::str::FromStr;
use std::sync::{Arc, Mutex}; use super::{ImapConnection, ImapProtocol, ImapServerConf, UIDStore};
use std::time::SystemTime; use crate::{
conf::AccountSettings,
email::parser::IResult,
error::{Error, Result},
get_conf_val,
imap::RequiredResponses,
};
pub struct ManageSieveConnection { pub struct ManageSieveConnection {
pub inner: ImapConnection, pub inner: ImapConnection,
@ -61,12 +67,17 @@ pub enum ManageSieveResponse<'a> {
} }
mod parser { mod parser {
use nom::{
bytes::complete::tag,
character::complete::crlf,
combinator::{iterator, map, opt},
};
pub use nom::{
bytes::complete::{is_not, tag_no_case},
sequence::{delimited, pair, preceded, terminated},
};
use super::*; use super::*;
use nom::bytes::complete::tag;
pub use nom::bytes::complete::{is_not, tag_no_case};
use nom::character::complete::crlf;
use nom::combinator::{iterator, map, opt};
pub use nom::sequence::{delimited, pair, preceded, terminated};
pub fn sieve_name(input: &[u8]) -> IResult<&[u8], &[u8]> { pub fn sieve_name(input: &[u8]) -> IResult<&[u8], &[u8]> {
crate::backends::imap::protocol_parser::string_token(input) crate::backends::imap::protocol_parser::string_token(input)

View File

@ -19,13 +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 super::*;
use crate::backends::*;
use crate::email::*;
use crate::error::Error;
use std::sync::Arc; use std::sync::Arc;
use super::*;
use crate::{backends::*, email::*, error::Error};
/// `BackendOp` implementor for Imap /// `BackendOp` implementor for Imap
#[derive(Debug, Clone)] #[derive(Debug, Clone)]
pub struct ImapOp { pub struct ImapOp {

View File

@ -19,24 +19,28 @@
* along with meli. If not, see <http://www.gnu.org/licenses/>. * along with meli. If not, see <http://www.gnu.org/licenses/>.
*/ */
use super::*; use std::{convert::TryFrom, str::FromStr};
use crate::email::address::{Address, MailboxAddress};
use crate::email::parser::{
generic::{byte_in_range, byte_in_slice},
BytesExt, IResult,
};
use crate::error::ResultIntoError;
use nom::{ use nom::{
branch::{alt, permutation}, branch::{alt, permutation},
bytes::complete::{is_a, is_not, tag, take, take_until, take_while}, bytes::complete::{is_a, is_not, tag, take, take_until, take_while},
character::complete::digit1, character::{complete::digit1, is_digit},
character::is_digit,
combinator::{map, map_res, opt}, combinator::{map, map_res, opt},
multi::{fold_many1, length_data, many0, many1, separated_list1}, multi::{fold_many1, length_data, many0, many1, separated_list1},
sequence::{delimited, preceded}, sequence::{delimited, preceded},
}; };
use std::convert::TryFrom;
use std::str::FromStr; use super::*;
use crate::{
email::{
address::{Address, MailboxAddress},
parser::{
generic::{byte_in_range, byte_in_slice},
BytesExt, IResult,
},
},
error::ResultIntoError,
};
bitflags! { bitflags! {
#[derive(Default, Serialize, Deserialize)] #[derive(Default, Serialize, Deserialize)]
@ -137,7 +141,7 @@ fn test_imap_required_responses() {
let response = let response =
&b"* 1040 FETCH (UID 1064 FLAGS ())\r\nM15 OK Fetch completed (0.001 + 0.299 secs).\r\n"[..]; &b"* 1040 FETCH (UID 1064 FLAGS ())\r\nM15 OK Fetch completed (0.001 + 0.299 secs).\r\n"[..];
for l in response.split_rn() { for l in response.split_rn() {
/*debug!("check line: {}", &l);*/ /* debug!("check line: {}", &l); */
if required_responses.check(l) { if required_responses.check(l) {
ret.extend_from_slice(l); ret.extend_from_slice(l);
} }
@ -159,35 +163,59 @@ pub struct ImapLineIterator<'a> {
#[derive(Debug, PartialEq)] #[derive(Debug, PartialEq)]
pub enum ResponseCode { pub enum ResponseCode {
///The human-readable text contains a special alert that MUST be presented to the user in a fashion that calls the user's attention to the message. ///The human-readable text contains a special alert that MUST be presented
/// to the user in a fashion that calls the user's attention to the message.
Alert(String), Alert(String),
///Optionally followed by a parenthesized list of charsets. A SEARCH failed because the given charset is not supported by this implementation. If the optional list of charsets is given, this lists the charsets that are supported by this implementation. ///Optionally followed by a parenthesized list of charsets. A SEARCH
/// failed because the given charset is not supported by this
/// implementation. If the optional list of charsets is given, this lists
/// the charsets that are supported by this implementation.
Badcharset(Option<String>), Badcharset(Option<String>),
/// Followed by a list of capabilities. This can appear in the initial OK or PREAUTH response to transmit an initial capabilities list. This makes it unnecessary for a client to send a separate CAPABILITY command if it recognizes this response. /// Followed by a list of capabilities. This can appear in the initial OK
/// or PREAUTH response to transmit an initial capabilities list. This
/// makes it unnecessary for a client to send a separate CAPABILITY command
/// if it recognizes this response.
Capability, Capability,
/// The human-readable text represents an error in parsing the [RFC-2822] header or [MIME-IMB] headers of a message in the mailbox. /// The human-readable text represents an error in parsing the [RFC-2822]
/// header or [MIME-IMB] headers of a message in the mailbox.
Parse(String), Parse(String),
/// Followed by a parenthesized list of flags, indicates which of the known flags the client can change permanently. Any flags that are in the FLAGS untagged response, but not the PERMANENTFLAGS list, can not be set permanently. If the client attempts to STORE a flag that is not in the PERMANENTFLAGS list, the server will either ignore the change or store the state change for the remainder of the current session only. The PERMANENTFLAGS list can also include the special flag \*, which indicates that it is possible to create new keywords by attempting to store those flags in the mailbox. /// Followed by a parenthesized list of flags, indicates which of the known
/// flags the client can change permanently. Any flags that are in the
/// FLAGS untagged response, but not the PERMANENTFLAGS list, can not be set
/// permanently. If the client attempts to STORE a flag that is not in the
/// PERMANENTFLAGS list, the server will either ignore the change or store
/// the state change for the remainder of the current session only. The
/// PERMANENTFLAGS list can also include the special flag \*, which
/// indicates that it is possible to create new keywords by attempting to
/// store those flags in the mailbox.
Permanentflags(String), Permanentflags(String),
/// The mailbox is selected read-only, or its access while selected has changed from read-write to read-only. /// The mailbox is selected read-only, or its access while selected has
/// changed from read-write to read-only.
ReadOnly, ReadOnly,
/// The mailbox is selected read-write, or its access while selected has changed from read-only to read-write. /// The mailbox is selected read-write, or its access while selected has
/// changed from read-only to read-write.
ReadWrite, ReadWrite,
/// An APPEND or COPY attempt is failing because the target mailbox does not exist (as opposed to some other reason). This is a hint to the client that the operation can succeed if the mailbox is first created by the CREATE command. /// An APPEND or COPY attempt is failing because the target mailbox does not
/// exist (as opposed to some other reason). This is a hint to the client
/// that the operation can succeed if the mailbox is first created by the
/// CREATE command.
Trycreate, Trycreate,
/// Followed by a decimal number, indicates the next unique identifier value. Refer to section 2.3.1.1 for more information. /// Followed by a decimal number, indicates the next unique identifier
/// value. Refer to section 2.3.1.1 for more information.
Uidnext(UID), Uidnext(UID),
/// Followed by a decimal number, indicates the unique identifier validity value. Refer to section 2.3.1.1 for more information. /// Followed by a decimal number, indicates the unique identifier validity
/// value. Refer to section 2.3.1.1 for more information.
Uidvalidity(UID), Uidvalidity(UID),
/// Followed by a decimal number, indicates the number of the first message without the \Seen flag set. /// Followed by a decimal number, indicates the number of the first message
/// without the \Seen flag set.
Unseen(ImapNum), Unseen(ImapNum),
} }
@ -195,15 +223,23 @@ impl std::fmt::Display for ResponseCode {
fn fmt(&self, fmt: &mut std::fmt::Formatter) -> std::fmt::Result { fn fmt(&self, fmt: &mut std::fmt::Formatter) -> std::fmt::Result {
use ResponseCode::*; use ResponseCode::*;
match self { match self {
Alert(s)=> write!(fmt, "ALERT: {}", s), Alert(s) => write!(fmt, "ALERT: {}", s),
Badcharset(None)=> write!(fmt, "Given charset is not supported by this server."), Badcharset(None) => write!(fmt, "Given charset is not supported by this server."),
Badcharset(Some(s))=> write!(fmt, "Given charset is not supported by this server. Supported ones are: {}", s), Badcharset(Some(s)) => write!(
fmt,
"Given charset is not supported by this server. Supported ones are: {}",
s
),
Capability => write!(fmt, "Capability response"), Capability => write!(fmt, "Capability response"),
Parse(s) => write!(fmt, "Server error in parsing message headers: {}", s), Parse(s) => write!(fmt, "Server error in parsing message headers: {}", s),
Permanentflags(s) => write!(fmt, "Mailbox supports these flags: {}", s), Permanentflags(s) => write!(fmt, "Mailbox supports these flags: {}", s),
ReadOnly=> write!(fmt, "This mailbox is selected read-only."), ReadOnly => write!(fmt, "This mailbox is selected read-only."),
ReadWrite => write!(fmt, "This mailbox is selected with read-write permissions."), ReadWrite => write!(fmt, "This mailbox is selected with read-write permissions."),
Trycreate => write!(fmt, "Failed to operate on the target mailbox because it doesn't exist. Try creating it first."), Trycreate => write!(
fmt,
"Failed to operate on the target mailbox because it doesn't exist. Try creating \
it first."
),
Uidnext(uid) => write!(fmt, "Next UID value is {}", uid), Uidnext(uid) => write!(fmt, "Next UID value is {}", uid),
Uidvalidity(uid) => write!(fmt, "Next UIDVALIDITY value is {}", uid), Uidvalidity(uid) => write!(fmt, "Next UIDVALIDITY value is {}", uid),
Unseen(uid) => write!(fmt, "First message without the \\Seen flag is {}", uid), Unseen(uid) => write!(fmt, "First message without the \\Seen flag is {}", uid),
@ -265,7 +301,8 @@ impl TryFrom<&'_ [u8]> for ImapResponse {
)) ))
})? + 1..] })? + 1..]
.trim(); .trim();
// M12 NO [CANNOT] Invalid mailbox name: Name must not have \'/\' characters (0.000 + 0.098 + 0.097 secs).\r\n // M12 NO [CANNOT] Invalid mailbox name: Name must not have \'/\' characters
// (0.000 + 0.098 + 0.097 secs).\r\n
if val.ends_with(b" secs).") { if val.ends_with(b" secs).") {
val = &val[..val.rfind(b"(").ok_or_else(|| { val = &val[..val.rfind(b"(").ok_or_else(|| {
Error::new(format!( Error::new(format!(
@ -432,8 +469,8 @@ fn test_imap_line_iterator() {
*/ */
/* /*
* LIST (\HasNoChildren) "." INBOX.Sent * LIST (\HasNoChildren) "." INBOX.Sent
* LIST (\HasChildren) "." INBOX * LIST (\HasChildren) "." INBOX
*/ */
pub fn list_mailbox_result(input: &[u8]) -> IResult<&[u8], ImapMailbox> { pub fn list_mailbox_result(input: &[u8]) -> IResult<&[u8], ImapMailbox> {
@ -604,7 +641,8 @@ pub fn fetch_response(input: &[u8]) -> ImapParseResult<FetchResponse<'_>> {
i += (input.len() - i - rest.len()) + 1; i += (input.len() - i - rest.len()) + 1;
} else { } else {
return debug!(Err(Error::new(format!( return debug!(Err(Error::new(format!(
"Unexpected input while parsing UID FETCH response. Could not parse FLAGS: {:.40}.", "Unexpected input while parsing UID FETCH response. Could not parse FLAGS: \
{:.40}.",
String::from_utf8_lossy(&input[i..]) String::from_utf8_lossy(&input[i..])
)))); ))));
} }
@ -639,7 +677,8 @@ pub fn fetch_response(input: &[u8]) -> ImapParseResult<FetchResponse<'_>> {
i += input.len() - i - rest.len(); i += input.len() - i - rest.len();
} else { } else {
return debug!(Err(Error::new(format!( return debug!(Err(Error::new(format!(
"Unexpected input while parsing UID FETCH response. Could not parse RFC822: {:.40}", "Unexpected input while parsing UID FETCH response. Could not parse RFC822: \
{:.40}",
String::from_utf8_lossy(&input[i..]) String::from_utf8_lossy(&input[i..])
)))); ))));
} }
@ -650,7 +689,8 @@ pub fn fetch_response(input: &[u8]) -> ImapParseResult<FetchResponse<'_>> {
i += input.len() - i - rest.len(); i += input.len() - i - rest.len();
} else { } else {
return debug!(Err(Error::new(format!( return debug!(Err(Error::new(format!(
"Unexpected input while parsing UID FETCH response. Could not parse ENVELOPE: {:.40}", "Unexpected input while parsing UID FETCH response. Could not parse ENVELOPE: \
{:.40}",
String::from_utf8_lossy(&input[i..]) String::from_utf8_lossy(&input[i..])
)))); ))));
} }
@ -672,7 +712,8 @@ pub fn fetch_response(input: &[u8]) -> ImapParseResult<FetchResponse<'_>> {
i += input.len() - i - rest.len(); i += input.len() - i - rest.len();
} else { } else {
return debug!(Err(Error::new(format!( return debug!(Err(Error::new(format!(
"Unexpected input while parsing UID FETCH response. Could not parse BODY[HEADER.FIELDS (REFERENCES)]: {:.40}", "Unexpected input while parsing UID FETCH response. Could not parse \
BODY[HEADER.FIELDS (REFERENCES)]: {:.40}",
String::from_utf8_lossy(&input[i..]) String::from_utf8_lossy(&input[i..])
)))); ))));
} }
@ -688,7 +729,8 @@ pub fn fetch_response(input: &[u8]) -> ImapParseResult<FetchResponse<'_>> {
i += input.len() - i - rest.len(); i += input.len() - i - rest.len();
} else { } else {
return debug!(Err(Error::new(format!( return debug!(Err(Error::new(format!(
"Unexpected input while parsing UID FETCH response. Could not parse BODY[HEADER.FIELDS (\"REFERENCES\"): {:.40}", "Unexpected input while parsing UID FETCH response. Could not parse \
BODY[HEADER.FIELDS (\"REFERENCES\"): {:.40}",
String::from_utf8_lossy(&input[i..]) String::from_utf8_lossy(&input[i..])
)))); ))));
} }
@ -815,9 +857,15 @@ macro_rules! flags_to_imap_list {
/* Input Example: /* Input Example:
* ============== * ==============
* *
* "M0 OK [CAPABILITY IMAP4rev1 LITERAL+ SASL-IR LOGIN-REFERRALS ID ENABLE IDLE SORT SORT=DISPLAY THREAD=REFERENCES THREAD=REFS THREAD=ORDEREDSUBJECT MULTIAPPEND URL-PARTIAL CATENATE UNSELECT CHILDREN NAMESPACE UIDPLUS LIST-EXTENDED I18NLEVEL=1 CONDSTORE QRESYNC ESEARCH ESORT SEARCHRES WITHIN CONTEXT=SEARCH LIST-STATUS BINARY MOVE SPECIAL-USE] Logged in\r\n" * "M0 OK [CAPABILITY IMAP4rev1 LITERAL+ SASL-IR LOGIN-REFERRALS ID ENABLE
* "* CAPABILITY IMAP4rev1 UNSELECT IDLE NAMESPACE QUOTA ID XLIST CHILDREN X-GM-EXT-1 XYZZY SASL-IR AUTH=XOAUTH2 AUTH=PLAIN AUTH=PLAIN-CLIENT TOKEN AUTH=OAUTHBEARER AUTH=XOAUTH\r\n" * IDLE SORT SORT=DISPLAY THREAD=REFERENCES THREAD=REFS THREAD=ORDEREDSUBJECT
* "* CAPABILITY IMAP4rev1 LITERAL+ SASL-IR LOGIN-REFERRALS ID ENABLE IDLE AUTH=PLAIN\r\n" * MULTIAPPEND URL-PARTIAL CATENATE UNSELECT CHILDREN NAMESPACE UIDPLUS
* LIST-EXTENDED I18NLEVEL=1 CONDSTORE QRESYNC ESEARCH ESORT SEARCHRES WITHIN
* CONTEXT=SEARCH LIST-STATUS BINARY MOVE SPECIAL-USE] Logged in\r\n"
* "* CAPABILITY IMAP4rev1 UNSELECT IDLE NAMESPACE QUOTA ID XLIST CHILDREN
* X-GM-EXT-1 XYZZY SASL-IR AUTH=XOAUTH2 AUTH=PLAIN AUTH=PLAIN-CLIENT TOKEN
* AUTH=OAUTHBEARER AUTH=XOAUTH\r\n" "* CAPABILITY IMAP4rev1 LITERAL+
* SASL-IR LOGIN-REFERRALS ID ENABLE IDLE AUTH=PLAIN\r\n"
*/ */
pub fn capabilities(input: &[u8]) -> IResult<&[u8], Vec<&[u8]>> { pub fn capabilities(input: &[u8]) -> IResult<&[u8], Vec<&[u8]>> {
@ -829,7 +877,8 @@ pub fn capabilities(input: &[u8]) -> IResult<&[u8], Vec<&[u8]>> {
Ok((input, ret)) Ok((input, ret))
} }
/// This enum represents the server's untagged responses detailed in `7. Server Responses` of RFC 3501 INTERNET MESSAGE ACCESS PROTOCOL - VERSION 4rev1 /// This enum represents the server's untagged responses detailed in `7. Server
/// Responses` of RFC 3501 INTERNET MESSAGE ACCESS PROTOCOL - VERSION 4rev1
#[derive(Debug, PartialEq)] #[derive(Debug, PartialEq)]
pub enum UntaggedResponse<'s> { pub enum UntaggedResponse<'s> {
/// ```text /// ```text
@ -1090,7 +1139,8 @@ pub struct SelectResponse {
/* /*
* *
* * FLAGS (\Answered \Flagged \Deleted \Seen \Draft) * * FLAGS (\Answered \Flagged \Deleted \Seen \Draft)
* * OK [PERMANENTFLAGS (\Answered \Flagged \Deleted \Seen \Draft \*)] Flags permitted. * * OK [PERMANENTFLAGS (\Answered \Flagged \Deleted \Seen \Draft \*)] Flags
* permitted.
* * 45 EXISTS * * 45 EXISTS
* * 0 RECENT * * 0 RECENT
* * OK [UNSEEN 16] First unseen. * * OK [UNSEEN 16] First unseen.
@ -1283,30 +1333,30 @@ pub fn byte_flags(input: &[u8]) -> IResult<&[u8], (Flag, Vec<String>)> {
} }
/* /*
* The fields of the envelope structure are in the following * The fields of the envelope structure are in the following
* order: date, subject, from, sender, reply-to, to, cc, bcc, * order: date, subject, from, sender, reply-to, to, cc, bcc,
* in-reply-to, and message-id. The date, subject, in-reply-to, * in-reply-to, and message-id. The date, subject, in-reply-to,
* and message-id fields are strings. The from, sender, reply-to, * and message-id fields are strings. The from, sender, reply-to,
* to, cc, and bcc fields are parenthesized lists of address * to, cc, and bcc fields are parenthesized lists of address
* structures. * structures.
* An address structure is a parenthesized list that describes an * An address structure is a parenthesized list that describes an
* electronic mail address. The fields of an address structure * electronic mail address. The fields of an address structure
* are in the following order: personal name, [SMTP] * are in the following order: personal name, [SMTP]
* at-domain-list (source route), mailbox name, and host name. * at-domain-list (source route), mailbox name, and host name.
*/ */
/* /*
* * 12 FETCH (FLAGS (\Seen) INTERNALDATE "17-Jul-1996 02:44:25 -0700" * * 12 FETCH (FLAGS (\Seen) INTERNALDATE "17-Jul-1996 02:44:25 -0700"
* RFC822.SIZE 4286 ENVELOPE ("Wed, 17 Jul 1996 02:23:25 -0700 (PDT)" * RFC822.SIZE 4286 ENVELOPE ("Wed, 17 Jul 1996 02:23:25 -0700 (PDT)"
* "IMAP4rev1 WG mtg summary and minutes" * "IMAP4rev1 WG mtg summary and minutes"
* (("Terry Gray" NIL "gray" "cac.washington.edu")) * (("Terry Gray" NIL "gray" "cac.washington.edu"))
* (("Terry Gray" NIL "gray" "cac.washington.edu")) * (("Terry Gray" NIL "gray" "cac.washington.edu"))
* (("Terry Gray" NIL "gray" "cac.washington.edu")) * (("Terry Gray" NIL "gray" "cac.washington.edu"))
* ((NIL NIL "imap" "cac.washington.edu")) * ((NIL NIL "imap" "cac.washington.edu"))
* ((NIL NIL "minutes" "CNRI.Reston.VA.US") * ((NIL NIL "minutes" "CNRI.Reston.VA.US")
* ("John Klensin" NIL "KLENSIN" "MIT.EDU")) NIL NIL * ("John Klensin" NIL "KLENSIN" "MIT.EDU")) NIL NIL
* "<B27397-0100000@cac.washington.edu>") * "<B27397-0100000@cac.washington.edu>")
*/ */
pub fn envelope(input: &[u8]) -> IResult<&[u8], Envelope> { pub fn envelope(input: &[u8]) -> IResult<&[u8], Envelope> {
let (input, _) = tag("(")(input)?; let (input, _) = tag("(")(input)?;
@ -1466,7 +1516,8 @@ pub fn envelope_address(input: &[u8]) -> IResult<&[u8], Address> {
)) ))
} }
// Read a literal ie a byte sequence prefixed with a tag containing its length delimited in {}s // Read a literal ie a byte sequence prefixed with a tag containing its length
// delimited in {}s
pub fn literal(input: &[u8]) -> IResult<&[u8], &[u8]> { pub fn literal(input: &[u8]) -> IResult<&[u8], &[u8]> {
length_data(delimited( length_data(delimited(
tag("{"), tag("{"),
@ -1694,7 +1745,8 @@ pub fn string_token(input: &[u8]) -> IResult<&[u8], &[u8]> {
// ASTRING-CHAR = ATOM-CHAR / resp-specials // ASTRING-CHAR = ATOM-CHAR / resp-specials
// atom = 1*ATOM-CHAR // atom = 1*ATOM-CHAR
// ATOM-CHAR = <any CHAR except atom-specials> // ATOM-CHAR = <any CHAR except atom-specials>
// atom-specials = "(" / ")" / "{" / SP / CTL / list-wildcards / quoted-specials / resp-specials // atom-specials = "(" / ")" / "{" / SP / CTL / list-wildcards / quoted-specials
// / resp-specials
fn astring_char(input: &[u8]) -> IResult<&[u8], &[u8]> { fn astring_char(input: &[u8]) -> IResult<&[u8], &[u8]> {
let (rest, chars) = many1(atom_char)(input)?; let (rest, chars) = many1(atom_char)(input)?;
Ok((rest, &input[0..chars.len()])) Ok((rest, &input[0..chars.len()]))

View File

@ -19,19 +19,22 @@
* along with meli. If not, see <http://www.gnu.org/licenses/>. * along with meli. If not, see <http://www.gnu.org/licenses/>.
*/ */
use super::{ImapConnection, MailboxSelection, UID};
use crate::backends::imap::protocol_parser::{
generate_envelope_hash, FetchResponse, ImapLineSplit, RequiredResponses, UntaggedResponse,
};
use crate::backends::BackendMailbox;
use crate::backends::{
RefreshEvent,
RefreshEventKind::{self, *},
TagHash,
};
use crate::error::*;
use std::convert::TryInto; use std::convert::TryInto;
use super::{ImapConnection, MailboxSelection, UID};
use crate::{
backends::{
imap::protocol_parser::{
generate_envelope_hash, FetchResponse, ImapLineSplit, RequiredResponses,
UntaggedResponse,
},
BackendMailbox, RefreshEvent,
RefreshEventKind::{self, *},
TagHash,
},
error::*,
};
impl ImapConnection { impl ImapConnection {
pub async fn process_untagged(&mut self, line: &[u8]) -> Result<bool> { pub async fn process_untagged(&mut self, line: &[u8]) -> Result<bool> {
macro_rules! try_fail { macro_rules! try_fail {
@ -323,7 +326,11 @@ impl ImapConnection {
accum.push(','); accum.push(',');
accum.push_str(to_str!(ms).trim()); accum.push_str(to_str!(ms).trim());
} }
format!("UID FETCH {} (UID FLAGS ENVELOPE BODY.PEEK[HEADER.FIELDS (REFERENCES)] BODYSTRUCTURE)", accum) format!(
"UID FETCH {} (UID FLAGS ENVELOPE BODY.PEEK[HEADER.FIELDS \
(REFERENCES)] BODYSTRUCTURE)",
accum
)
}; };
try_fail!( try_fail!(
mailbox_hash, mailbox_hash,

View File

@ -18,9 +18,10 @@
* 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 std::sync::Arc;
use super::*; use super::*;
use crate::backends::SpecialUsageMailbox; use crate::backends::SpecialUsageMailbox;
use std::sync::Arc;
/// Arguments for IMAP watching functions /// Arguments for IMAP watching functions
pub struct ImapWatchKit { pub struct ImapWatchKit {
@ -52,8 +53,8 @@ pub async fn poll_with_examine(kit: ImapWatchKit) -> Result<()> {
pub async fn idle(kit: ImapWatchKit) -> Result<()> { pub async fn idle(kit: ImapWatchKit) -> Result<()> {
debug!("IDLE"); debug!("IDLE");
/* IDLE only watches the connection's selected mailbox. We will IDLE on INBOX and every ~5 /* IDLE only watches the connection's selected mailbox. We will IDLE on INBOX
* minutes wake up and poll the others */ * and every ~5 minutes wake up and poll the others */
let ImapWatchKit { let ImapWatchKit {
mut conn, mut conn,
main_conn, main_conn,
@ -70,7 +71,10 @@ pub async fn idle(kit: ImapWatchKit) -> Result<()> {
{ {
Some(mailbox) => mailbox, Some(mailbox) => mailbox,
None => { None => {
return Err(Error::new("INBOX mailbox not found in local mailbox index. meli may have not parsed the IMAP mailboxes correctly")); return Err(Error::new(
"INBOX mailbox not found in local mailbox index. meli may have not parsed the \
IMAP mailboxes correctly",
));
} }
}; };
let mailbox_hash = mailbox.hash(); let mailbox_hash = mailbox.hash();
@ -342,7 +346,8 @@ pub async fn examine_updates(
} else if select_response.exists > mailbox.exists.lock().unwrap().len() { } else if select_response.exists > mailbox.exists.lock().unwrap().len() {
conn.send_command( conn.send_command(
format!( format!(
"FETCH {}:* (UID FLAGS ENVELOPE BODY.PEEK[HEADER.FIELDS (REFERENCES)] BODYSTRUCTURE)", "FETCH {}:* (UID FLAGS ENVELOPE BODY.PEEK[HEADER.FIELDS (REFERENCES)] \
BODYSTRUCTURE)",
std::cmp::max(mailbox.exists.lock().unwrap().len(), 1) std::cmp::max(mailbox.exists.lock().unwrap().len(), 1)
) )
.as_bytes(), .as_bytes(),

View File

@ -19,21 +19,26 @@
* along with meli. If not, see <http://www.gnu.org/licenses/>. * along with meli. If not, see <http://www.gnu.org/licenses/>.
*/ */
use crate::backends::*; use std::{
use crate::conf::AccountSettings; collections::{HashMap, HashSet},
use crate::connections::timeout; convert::TryFrom,
use crate::email::*; str::FromStr,
use crate::error::{Error, Result}; sync::{Arc, Mutex, RwLock},
use crate::Collection; time::{Duration, Instant},
};
use futures::lock::Mutex as FutureMutex; use futures::lock::Mutex as FutureMutex;
use isahc::config::RedirectPolicy; use isahc::{config::RedirectPolicy, AsyncReadResponseExt, HttpClient};
use isahc::{AsyncReadResponseExt, HttpClient};
use serde_json::Value; use serde_json::Value;
use std::collections::{HashMap, HashSet};
use std::convert::TryFrom; use crate::{
use std::str::FromStr; backends::*,
use std::sync::{Arc, Mutex, RwLock}; conf::AccountSettings,
use std::time::{Duration, Instant}; connections::timeout,
email::*,
error::{Error, Result},
Collection,
};
#[macro_export] #[macro_export]
macro_rules! _impl { macro_rules! _impl {
@ -131,7 +136,9 @@ impl JmapServerConf {
^ s.extra.contains_key("server_password")) ^ s.extra.contains_key("server_password"))
{ {
return Err(Error::new(format!( return Err(Error::new(format!(
"({}) `use_token` use requires either the `server_password_command` set with a command that returns an Bearer token of your account, or `server_password` with the API Bearer token as a string. Consult documentation for guidance.", "({}) `use_token` use requires either the `server_password_command` set with a \
command that returns an Bearer token of your account, or `server_password` with \
the API Bearer token as a string. Consult documentation for guidance.",
s.name, s.name,
))); )));
} }
@ -416,7 +423,13 @@ impl MailBackend for JmapType {
let upload_response: UploadResponse = match serde_json::from_str(&res_text) { let upload_response: UploadResponse = match serde_json::from_str(&res_text) {
Err(err) => { Err(err) => {
let err = Error::new(format!("BUG: Could not deserialize {} server JSON response properly, please report this!\nReply from server: {}", &conn.server_conf.server_url, &res_text)).set_source(Some(Arc::new(err))).set_kind(ErrorKind::Bug); let err = Error::new(format!(
"BUG: Could not deserialize {} server JSON response properly, please \
report this!\nReply from server: {}",
&conn.server_conf.server_url, &res_text
))
.set_source(Some(Arc::new(err)))
.set_kind(ErrorKind::Bug);
*conn.store.online_status.lock().await = (Instant::now(), Err(err.clone())); *conn.store.online_status.lock().await = (Instant::now(), Err(err.clone()));
return Err(err); return Err(err);
} }
@ -447,7 +460,13 @@ impl MailBackend for JmapType {
let mut v: MethodResponse = match serde_json::from_str(&res_text) { let mut v: MethodResponse = match serde_json::from_str(&res_text) {
Err(err) => { Err(err) => {
let err = Error::new(format!("BUG: Could not deserialize {} server JSON response properly, please report this!\nReply from server: {}", &conn.server_conf.server_url, &res_text)).set_source(Some(Arc::new(err))).set_kind(ErrorKind::Bug); let err = Error::new(format!(
"BUG: Could not deserialize {} server JSON response properly, please \
report this!\nReply from server: {}",
&conn.server_conf.server_url, &res_text
))
.set_source(Some(Arc::new(err)))
.set_kind(ErrorKind::Bug);
*conn.store.online_status.lock().await = (Instant::now(), Err(err.clone())); *conn.store.online_status.lock().await = (Instant::now(), Err(err.clone()));
return Err(err); return Err(err);
} }
@ -528,7 +547,13 @@ impl MailBackend for JmapType {
let res_text = res.text().await?; let res_text = res.text().await?;
let mut v: MethodResponse = match serde_json::from_str(&res_text) { let mut v: MethodResponse = match serde_json::from_str(&res_text) {
Err(err) => { Err(err) => {
let err = Error::new(format!("BUG: Could not deserialize {} server JSON response properly, please report this!\nReply from server: {}", &conn.server_conf.server_url, &res_text)).set_source(Some(Arc::new(err))).set_kind(ErrorKind::Bug); let err = Error::new(format!(
"BUG: Could not deserialize {} server JSON response properly, please \
report this!\nReply from server: {}",
&conn.server_conf.server_url, &res_text
))
.set_source(Some(Arc::new(err)))
.set_kind(ErrorKind::Bug);
*conn.store.online_status.lock().await = (Instant::now(), Err(err.clone())); *conn.store.online_status.lock().await = (Instant::now(), Err(err.clone()));
return Err(err); return Err(err);
} }
@ -664,7 +689,13 @@ impl MailBackend for JmapType {
let mut v: MethodResponse = match serde_json::from_str(&res_text) { let mut v: MethodResponse = match serde_json::from_str(&res_text) {
Err(err) => { Err(err) => {
let err = Error::new(format!("BUG: Could not deserialize {} server JSON response properly, please report this!\nReply from server: {}", &conn.server_conf.server_url, &res_text)).set_source(Some(Arc::new(err))).set_kind(ErrorKind::Bug); let err = Error::new(format!(
"BUG: Could not deserialize {} server JSON response properly, please \
report this!\nReply from server: {}",
&conn.server_conf.server_url, &res_text
))
.set_source(Some(Arc::new(err)))
.set_kind(ErrorKind::Bug);
*conn.store.online_status.lock().await = (Instant::now(), Err(err.clone())); *conn.store.online_status.lock().await = (Instant::now(), Err(err.clone()));
return Err(err); return Err(err);
} }
@ -771,12 +802,22 @@ impl MailBackend for JmapType {
let res_text = res.text().await?; let res_text = res.text().await?;
/* /*
*{"methodResponses":[["Email/set",{"notUpdated":null,"notDestroyed":null,"oldState":"86","newState":"87","accountId":"u148940c7","updated":{"M045926eed54b11423918f392":{"id":"M045926eed54b11423918f392"}},"created":null,"destroyed":null,"notCreated":null},"m3"]],"sessionState":"cyrus-0;p-5;vfs-0"} *{"methodResponses":[["Email/set",{"notUpdated":null,"notDestroyed":null,"
* oldState":"86","newState":"87","accountId":"u148940c7","updated":{"
* M045926eed54b11423918f392":{"id":"M045926eed54b11423918f392"}},"created":
* null,"destroyed":null,"notCreated":null},"m3"]],"sessionState":"cyrus-0;
* p-5;vfs-0"}
*/ */
//debug!("res_text = {}", &res_text); //debug!("res_text = {}", &res_text);
let mut v: MethodResponse = match serde_json::from_str(&res_text) { let mut v: MethodResponse = match serde_json::from_str(&res_text) {
Err(err) => { Err(err) => {
let err = Error::new(format!("BUG: Could not deserialize {} server JSON response properly, please report this!\nReply from server: {}", &conn.server_conf.server_url, &res_text)).set_source(Some(Arc::new(err))).set_kind(ErrorKind::Bug); let err = Error::new(format!(
"BUG: Could not deserialize {} server JSON response properly, please \
report this!\nReply from server: {}",
&conn.server_conf.server_url, &res_text
))
.set_source(Some(Arc::new(err)))
.set_kind(ErrorKind::Bug);
*conn.store.online_status.lock().await = (Instant::now(), Err(err.clone())); *conn.store.online_status.lock().await = (Instant::now(), Err(err.clone()));
return Err(err); return Err(err);
} }

View File

@ -19,10 +19,12 @@
* along with meli. If not, see <http://www.gnu.org/licenses/>. * along with meli. If not, see <http://www.gnu.org/licenses/>.
*/ */
use super::*;
use isahc::config::Configurable;
use std::sync::MutexGuard; use std::sync::MutexGuard;
use isahc::config::Configurable;
use super::*;
#[derive(Debug)] #[derive(Debug)]
pub struct JmapConnection { pub struct JmapConnection {
pub session: Arc<Mutex<JmapSession>>, pub session: Arc<Mutex<JmapSession>>,
@ -73,11 +75,22 @@ impl JmapConnection {
let mut jmap_session_resource_url = self.server_conf.server_url.to_string(); let mut jmap_session_resource_url = self.server_conf.server_url.to_string();
jmap_session_resource_url.push_str("/.well-known/jmap"); jmap_session_resource_url.push_str("/.well-known/jmap");
let mut req = self.client.get_async(&jmap_session_resource_url).await.map_err(|err| { let mut req = self
let err = Error::new(format!("Could not connect to JMAP server endpoint for {}. Is your server url setting correct? (i.e. \"jmap.mailserver.org\") (Note: only session resource discovery via /.well-known/jmap is supported. DNS SRV records are not suppported.)\nError connecting to server: {}", &self.server_conf.server_url, &err)).set_source(Some(Arc::new(err))); .client
.get_async(&jmap_session_resource_url)
.await
.map_err(|err| {
let err = Error::new(format!(
"Could not connect to JMAP server endpoint for {}. Is your server url setting \
correct? (i.e. \"jmap.mailserver.org\") (Note: only session resource \
discovery via /.well-known/jmap is supported. DNS SRV records are not \
suppported.)\nError connecting to server: {}",
&self.server_conf.server_url, &err
))
.set_source(Some(Arc::new(err)));
//*self.store.online_status.lock().await = (Instant::now(), Err(err.clone())); //*self.store.online_status.lock().await = (Instant::now(), Err(err.clone()));
err err
})?; })?;
if !req.status().is_success() { if !req.status().is_success() {
let kind: crate::error::NetworkErrorKind = req.status().into(); let kind: crate::error::NetworkErrorKind = req.status().into();
@ -95,7 +108,14 @@ impl JmapConnection {
let session: JmapSession = match serde_json::from_str(&res_text) { let session: JmapSession = match serde_json::from_str(&res_text) {
Err(err) => { Err(err) => {
let err = Error::new(format!("Could not connect to JMAP server endpoint for {}. Is your server url setting correct? (i.e. \"jmap.mailserver.org\") (Note: only session resource discovery via /.well-known/jmap is supported. DNS SRV records are not suppported.)\nReply from server: {}", &self.server_conf.server_url, &res_text)).set_source(Some(Arc::new(err))); let err = Error::new(format!(
"Could not connect to JMAP server endpoint for {}. Is your server url setting \
correct? (i.e. \"jmap.mailserver.org\") (Note: only session resource \
discovery via /.well-known/jmap is supported. DNS SRV records are not \
suppported.)\nReply from server: {}",
&self.server_conf.server_url, &res_text
))
.set_source(Some(Arc::new(err)));
*self.store.online_status.lock().await = (Instant::now(), Err(err.clone())); *self.store.online_status.lock().await = (Instant::now(), Err(err.clone()));
return Err(err); return Err(err);
} }
@ -105,7 +125,17 @@ impl JmapConnection {
.capabilities .capabilities
.contains_key("urn:ietf:params:jmap:core") .contains_key("urn:ietf:params:jmap:core")
{ {
let err = Error::new(format!("Server {} did not return JMAP Core capability (urn:ietf:params:jmap:core). Returned capabilities were: {}", &self.server_conf.server_url, session.capabilities.keys().map(String::as_str).collect::<Vec<&str>>().join(", "))); let err = Error::new(format!(
"Server {} did not return JMAP Core capability (urn:ietf:params:jmap:core). \
Returned capabilities were: {}",
&self.server_conf.server_url,
session
.capabilities
.keys()
.map(String::as_str)
.collect::<Vec<&str>>()
.join(", ")
));
*self.store.online_status.lock().await = (Instant::now(), Err(err.clone())); *self.store.online_status.lock().await = (Instant::now(), Err(err.clone()));
return Err(err); return Err(err);
} }
@ -113,7 +143,17 @@ impl JmapConnection {
.capabilities .capabilities
.contains_key("urn:ietf:params:jmap:mail") .contains_key("urn:ietf:params:jmap:mail")
{ {
let err = Error::new(format!("Server {} does not support JMAP Mail capability (urn:ietf:params:jmap:mail). Returned capabilities were: {}", &self.server_conf.server_url, session.capabilities.keys().map(String::as_str).collect::<Vec<&str>>().join(", "))); let err = Error::new(format!(
"Server {} does not support JMAP Mail capability (urn:ietf:params:jmap:mail). \
Returned capabilities were: {}",
&self.server_conf.server_url,
session
.capabilities
.keys()
.map(String::as_str)
.collect::<Vec<&str>>()
.join(", ")
));
*self.store.online_status.lock().await = (Instant::now(), Err(err.clone())); *self.store.online_status.lock().await = (Instant::now(), Err(err.clone()));
return Err(err); return Err(err);
} }
@ -207,7 +247,13 @@ impl JmapConnection {
debug!(&res_text); debug!(&res_text);
let mut v: MethodResponse = match serde_json::from_str(&res_text) { let mut v: MethodResponse = match serde_json::from_str(&res_text) {
Err(err) => { Err(err) => {
let err = Error::new(format!("BUG: Could not deserialize {} server JSON response properly, please report this!\nReply from server: {}", &self.server_conf.server_url, &res_text)).set_source(Some(Arc::new(err))).set_kind(ErrorKind::Bug); let err = Error::new(format!(
"BUG: Could not deserialize {} server JSON response properly, please \
report this!\nReply from server: {}",
&self.server_conf.server_url, &res_text
))
.set_source(Some(Arc::new(err)))
.set_kind(ErrorKind::Bug);
*self.store.online_status.lock().await = (Instant::now(), Err(err.clone())); *self.store.online_status.lock().await = (Instant::now(), Err(err.clone()));
return Err(err); return Err(err);
} }

View File

@ -19,9 +19,10 @@
* along with meli. If not, see <http://www.gnu.org/licenses/>. * along with meli. If not, see <http://www.gnu.org/licenses/>.
*/ */
use std::sync::{Arc, Mutex, RwLock};
use super::*; use super::*;
use crate::backends::{LazyCountSet, MailboxPermissions, SpecialUsageMailbox}; use crate::backends::{LazyCountSet, MailboxPermissions, SpecialUsageMailbox};
use std::sync::{Arc, Mutex, RwLock};
#[derive(Debug, Clone)] #[derive(Debug, Clone)]
pub struct JmapMailbox { pub struct JmapMailbox {

View File

@ -19,15 +19,18 @@
* along with meli. If not, see <http://www.gnu.org/licenses/>. * along with meli. If not, see <http://www.gnu.org/licenses/>.
*/ */
use super::*;
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_json::value::RawValue;
use serde_json::Value;
use std::collections::HashMap; use std::collections::HashMap;
use serde::de::{Deserialize, Deserializer};
use serde_json::{value::RawValue, Value};
use super::*;
use crate::{
backends::jmap::rfc8620::bool_false,
email::address::{Address, MailboxAddress},
};
mod import; mod import;
pub use import::*; pub use import::*;
@ -83,14 +86,13 @@ impl Id<EmailObject> {
// first character changed from "\" in IMAP to "$" in JMAP and have // first character changed from "\" in IMAP to "$" in JMAP and have
// particular semantic meaning: // particular semantic meaning:
// //
// * "$draft": The Email is a draft the user is composing. // * "$draft": The Email is a draft the user is composing.
// //
// * "$seen": The Email has been read. // * "$seen": The Email has been read.
// //
// * "$flagged": The Email has been flagged for urgent/special // * "$flagged": The Email has been flagged for urgent/special attention.
// attention.
// //
// * "$answered": The Email has been replied to. // * "$answered": The Email has been replied to.
// //
// The IMAP "\Recent" keyword is not exposed via JMAP. The IMAP // The IMAP "\Recent" keyword is not exposed via JMAP. The IMAP
// "\Deleted" keyword is also not present: IMAP uses a delete+expunge // "\Deleted" keyword is also not present: IMAP uses a delete+expunge
@ -115,19 +117,19 @@ impl Id<EmailObject> {
// keywords in common use. New keywords may be established here in // keywords in common use. New keywords may be established here in
// the future. In particular, note: // the future. In particular, note:
// //
// * "$forwarded": The Email has been forwarded. // * "$forwarded": The Email has been forwarded.
// //
// * "$phishing": The Email is highly likely to be phishing. // * "$phishing": The Email is highly likely to be phishing. Clients SHOULD
// Clients SHOULD warn users to take care when viewing this Email // warn users to take care when viewing this Email and disable links and
// and disable links and attachments. // attachments.
// //
// * "$junk": The Email is definitely spam. Clients SHOULD set this // * "$junk": The Email is definitely spam. Clients SHOULD set this flag
// flag when users report spam to help train automated spam- // when users report spam to help train automated spam- detection
// detection systems. // systems.
// //
// * "$notjunk": The Email is definitely not spam. Clients SHOULD // * "$notjunk": The Email is definitely not spam. Clients SHOULD set this
// set this flag when users indicate an Email is legitimate, to // flag when users indicate an Email is legitimate, to help train
// help train automated spam-detection systems. // automated spam-detection systems.
// //
// o size: "UnsignedInt" (immutable; server-set) // o size: "UnsignedInt" (immutable; server-set)
// //
@ -586,8 +588,10 @@ impl From<crate::search::Query> for Filter<EmailFilterCondition, EmailObject> {
fn from(val: crate::search::Query) -> Self { fn from(val: crate::search::Query) -> Self {
let mut ret = Filter::Condition(EmailFilterCondition::new().into()); let mut ret = Filter::Condition(EmailFilterCondition::new().into());
fn rec(q: &crate::search::Query, f: &mut Filter<EmailFilterCondition, EmailObject>) { fn rec(q: &crate::search::Query, f: &mut Filter<EmailFilterCondition, EmailObject>) {
use crate::datetime::{timestamp_to_string, RFC3339_FMT}; use crate::{
use crate::search::Query::*; datetime::{timestamp_to_string, RFC3339_FMT},
search::Query::*,
};
match q { match q {
Subject(t) => { Subject(t) => {
*f = Filter::Condition(EmailFilterCondition::new().subject(t.clone()).into()); *f = Filter::Condition(EmailFilterCondition::new().subject(t.clone()).into());
@ -849,8 +853,16 @@ pub struct EmailQueryChangesResponse {
impl std::convert::TryFrom<&RawValue> for EmailQueryChangesResponse { impl std::convert::TryFrom<&RawValue> for EmailQueryChangesResponse {
type Error = crate::error::Error; type Error = crate::error::Error;
fn try_from(t: &RawValue) -> Result<EmailQueryChangesResponse> { fn try_from(t: &RawValue) -> Result<EmailQueryChangesResponse> {
let res: (String, EmailQueryChangesResponse, String) = let res: (String, EmailQueryChangesResponse, String) = serde_json::from_str(t.get())
serde_json::from_str(t.get()).map_err(|err| crate::error::Error::new(format!("BUG: Could not deserialize server JSON response properly, please report this!\nReply from server: {}", &t)).set_source(Some(Arc::new(err))).set_kind(ErrorKind::Bug))?; .map_err(|err| {
crate::error::Error::new(format!(
"BUG: Could not deserialize server JSON response properly, please report \
this!\nReply from server: {}",
&t
))
.set_source(Some(Arc::new(err)))
.set_kind(ErrorKind::Bug)
})?;
assert_eq!(&res.0, "Email/queryChanges"); assert_eq!(&res.0, "Email/queryChanges");
Ok(res.1) Ok(res.1)
} }

View File

@ -19,9 +19,10 @@
* along with meli. If not, see <http://www.gnu.org/licenses/>. * along with meli. If not, see <http://www.gnu.org/licenses/>.
*/ */
use super::*;
use serde_json::value::RawValue; use serde_json::value::RawValue;
use super::*;
/// #`import` /// #`import`
/// ///
/// Objects of type `Foo` are imported via a call to `Foo/import`. /// Objects of type `Foo` are imported via a call to `Foo/import`.
@ -31,7 +32,6 @@ use serde_json::value::RawValue;
/// - `account_id`: "Id" /// - `account_id`: "Id"
/// ///
/// The id of the account to use. /// The id of the account to use.
///
#[derive(Deserialize, Serialize, Debug)] #[derive(Deserialize, Serialize, Debug)]
#[serde(rename_all = "camelCase")] #[serde(rename_all = "camelCase")]
pub struct ImportCall { pub struct ImportCall {
@ -81,10 +81,9 @@ impl ImportCall {
} }
_impl!( _impl!(
/// - accountId: "Id" /// - accountId: "Id"
/// ///
/// The id of the account to use. /// The id of the account to use.
///
account_id: Id<Account> account_id: Id<Account>
); );
_impl!(if_in_state: Option<State<EmailObject>>); _impl!(if_in_state: Option<State<EmailObject>>);
@ -123,9 +122,10 @@ pub enum ImportError {
AlreadyExists { AlreadyExists {
description: Option<String>, description: Option<String>,
/// An "existingId" property of type "Id" MUST be included on /// An "existingId" property of type "Id" MUST be included on
///the SetError object with the id of the existing Email. If duplicates ///the SetError object with the id of the existing Email. If
///are allowed, the newly created Email object MUST have a separate id /// duplicates are allowed, the newly created Email object MUST
///and independent mutable properties to the existing object. /// have a separate id and independent mutable properties to the
/// existing object.
existing_id: Id<EmailObject>, existing_id: Id<EmailObject>,
}, },
///If the "blobId", "mailboxIds", or "keywords" properties are invalid ///If the "blobId", "mailboxIds", or "keywords" properties are invalid
@ -146,7 +146,8 @@ pub enum ImportError {
///different to the "blobId" on the EmailImport object. Alternatively, ///different to the "blobId" on the EmailImport object. Alternatively,
///the server MAY reject the import with an "invalidEmail" SetError. ///the server MAY reject the import with an "invalidEmail" SetError.
InvalidEmail { description: Option<String> }, InvalidEmail { description: Option<String> },
///An "ifInState" argument was supplied, and it does not match the current state. ///An "ifInState" argument was supplied, and it does not match the current
/// state.
StateMismatch, StateMismatch,
} }
@ -185,7 +186,15 @@ impl std::convert::TryFrom<&RawValue> for ImportResponse {
type Error = crate::error::Error; type Error = crate::error::Error;
fn try_from(t: &RawValue) -> Result<ImportResponse> { fn try_from(t: &RawValue) -> Result<ImportResponse> {
let res: (String, ImportResponse, String) = let res: (String, ImportResponse, String) =
serde_json::from_str(t.get()).map_err(|err| crate::error::Error::new(format!("BUG: Could not deserialize server JSON response properly, please report this!\nReply from server: {}", &t)).set_source(Some(Arc::new(err))).set_kind(ErrorKind::Bug))?; serde_json::from_str(t.get()).map_err(|err| {
crate::error::Error::new(format!(
"BUG: Could not deserialize server JSON response properly, please report \
this!\nReply from server: {}",
&t
))
.set_source(Some(Arc::new(err)))
.set_kind(ErrorKind::Bug)
})?;
assert_eq!(&res.0, &ImportCall::NAME); assert_eq!(&res.0, &ImportCall::NAME);
Ok(res.1) Ok(res.1)
} }

View File

@ -19,9 +19,10 @@
* along with meli. If not, see <http://www.gnu.org/licenses/>. * along with meli. If not, see <http://www.gnu.org/licenses/>.
*/ */
use super::*;
use std::sync::Arc; use std::sync::Arc;
use super::*;
/// `BackendOp` implementor for Imap /// `BackendOp` implementor for Imap
#[derive(Debug, Clone)] #[derive(Debug, Clone)]
pub struct JmapOp { pub struct JmapOp {

View File

@ -19,11 +19,12 @@
* along with meli. If not, see <http://www.gnu.org/licenses/>. * along with meli. If not, see <http://www.gnu.org/licenses/>.
*/ */
use super::mailbox::JmapMailbox; use std::convert::{TryFrom, TryInto};
use super::*;
use serde::Serialize; use serde::Serialize;
use serde_json::{json, Value}; use serde_json::{json, Value};
use std::convert::{TryFrom, TryInto};
use super::{mailbox::JmapMailbox, *};
pub type UtcDate = String; pub type UtcDate = String;
@ -97,7 +98,13 @@ pub async fn get_mailboxes(conn: &JmapConnection) -> Result<HashMap<MailboxHash,
let res_text = res.text().await?; let res_text = res.text().await?;
let mut v: MethodResponse = match serde_json::from_str(&res_text) { let mut v: MethodResponse = match serde_json::from_str(&res_text) {
Err(err) => { Err(err) => {
let err = Error::new(format!("BUG: Could not deserialize {} server JSON response properly, please report this!\nReply from server: {}", &conn.server_conf.server_url, &res_text)).set_source(Some(Arc::new(err))).set_kind(ErrorKind::Bug); let err = Error::new(format!(
"BUG: Could not deserialize {} server JSON response properly, please report \
this!\nReply from server: {}",
&conn.server_conf.server_url, &res_text
))
.set_source(Some(Arc::new(err)))
.set_kind(ErrorKind::Bug);
*conn.store.online_status.lock().await = (Instant::now(), Err(err.clone())); *conn.store.online_status.lock().await = (Instant::now(), Err(err.clone()));
return Err(err); return Err(err);
} }
@ -108,8 +115,9 @@ pub async fn get_mailboxes(conn: &JmapConnection) -> Result<HashMap<MailboxHash,
let GetResponse::<MailboxObject> { let GetResponse::<MailboxObject> {
list, account_id, .. list, account_id, ..
} = m; } = m;
// Is account set as `personal`? (`isPersonal` property). Then, even if `isSubscribed` is false // Is account set as `personal`? (`isPersonal` property). Then, even if
// on a mailbox, it should be regarded as subscribed. // `isSubscribed` is false on a mailbox, it should be regarded as
// subscribed.
let is_personal: bool = { let is_personal: bool = {
let session = conn.session_guard(); let session = conn.session_guard();
session session
@ -204,7 +212,13 @@ pub async fn get_message_list(
let res_text = res.text().await?; let res_text = res.text().await?;
let mut v: MethodResponse = match serde_json::from_str(&res_text) { let mut v: MethodResponse = match serde_json::from_str(&res_text) {
Err(err) => { Err(err) => {
let err = Error::new(format!("BUG: Could not deserialize {} server JSON response properly, please report this!\nReply from server: {}", &conn.server_conf.server_url, &res_text)).set_source(Some(Arc::new(err))).set_kind(ErrorKind::Bug); let err = Error::new(format!(
"BUG: Could not deserialize {} server JSON response properly, please report \
this!\nReply from server: {}",
&conn.server_conf.server_url, &res_text
))
.set_source(Some(Arc::new(err)))
.set_kind(ErrorKind::Bug);
*conn.store.online_status.lock().await = (Instant::now(), Err(err.clone())); *conn.store.online_status.lock().await = (Instant::now(), Err(err.clone()));
return Err(err); return Err(err);
} }
@ -284,7 +298,13 @@ pub async fn fetch(
let mut v: MethodResponse = match serde_json::from_str(&res_text) { let mut v: MethodResponse = match serde_json::from_str(&res_text) {
Err(err) => { Err(err) => {
let err = Error::new(format!("BUG: Could not deserialize {} server JSON response properly, please report this!\nReply from server: {}", &conn.server_conf.server_url, &res_text)).set_source(Some(Arc::new(err))).set_kind(ErrorKind::Bug); let err = Error::new(format!(
"BUG: Could not deserialize {} server JSON response properly, please report \
this!\nReply from server: {}",
&conn.server_conf.server_url, &res_text
))
.set_source(Some(Arc::new(err)))
.set_kind(ErrorKind::Bug);
*conn.store.online_status.lock().await = (Instant::now(), Err(err.clone())); *conn.store.online_status.lock().await = (Instant::now(), Err(err.clone()));
return Err(err); return Err(err);
} }

View File

@ -19,23 +19,30 @@
* 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::parser::BytesExt;
use core::marker::PhantomData; use core::marker::PhantomData;
use serde::de::DeserializeOwned; use std::{
use serde::ser::{Serialize, SerializeStruct, Serializer}; hash::{Hash, Hasher},
sync::Arc,
};
use serde::{
de::DeserializeOwned,
ser::{Serialize, SerializeStruct, Serializer},
};
use serde_json::{value::RawValue, Value}; use serde_json::{value::RawValue, Value};
use std::hash::{Hash, Hasher};
use std::sync::Arc; use crate::email::parser::BytesExt;
mod filters; mod filters;
pub use filters::*; pub use filters::*;
mod comparator; mod comparator;
pub use comparator::*; pub use comparator::*;
mod argument; mod argument;
use std::collections::HashMap;
pub use argument::*; pub use argument::*;
use super::protocol::Method; use super::protocol::Method;
use std::collections::HashMap;
pub trait Object { pub trait Object {
const NAME: &'static str; const NAME: &'static str;
} }
@ -275,7 +282,6 @@ impl Object for BlobObject {
/// - `account_id`: "Id" /// - `account_id`: "Id"
/// ///
/// The id of the account to use. /// The id of the account to use.
///
#[derive(Deserialize, Debug)] #[derive(Deserialize, Debug)]
#[serde(rename_all = "camelCase")] #[serde(rename_all = "camelCase")]
pub struct Get<OBJ: Object> pub struct Get<OBJ: Object>
@ -305,31 +311,30 @@ where
} }
} }
_impl!( _impl!(
/// - accountId: "Id" /// - accountId: "Id"
/// ///
/// The id of the account to use. /// The id of the account to use.
///
account_id: Id<Account> account_id: Id<Account>
); );
_impl!( _impl!(
/// - ids: `Option<JmapArgument<Vec<String>>>` /// - ids: `Option<JmapArgument<Vec<String>>>`
///
/// The ids of the Foo objects to return. If `None`, then *all* records
/// of the data type are returned, if this is supported for that data
/// type and the number of records does not exceed the
/// "max_objects_in_get" limit.
/// ///
/// The ids of the Foo objects to return. If `None`, then *all*
/// records of the data type are returned, if this is
/// supported for that data type and the number of records
/// does not exceed the "max_objects_in_get" limit.
ids: Option<JmapArgument<Vec<Id<OBJ>>>> ids: Option<JmapArgument<Vec<Id<OBJ>>>>
); );
_impl!( _impl!(
/// - properties: Option<Vec<String>> /// - properties: Option<Vec<String>>
/// ///
/// If supplied, only the properties listed in the array are returned /// If supplied, only the properties listed in the array are
/// for each `Foo` object. If `None`, all properties of the object are /// returned for each `Foo` object. If `None`, all
/// returned. The `id` property of the object is *always* returned, /// properties of the object are returned. The `id`
/// even if not explicitly requested. If an invalid property is /// property of the object is *always* returned, even if
/// requested, the call WILL be rejected with an "invalid_arguments" /// not explicitly requested. If an invalid property is
/// error. /// requested, the call WILL be rejected with an
/// "invalid_arguments" error.
properties: Option<Vec<String>> properties: Option<Vec<String>>
); );
} }
@ -414,7 +419,15 @@ impl<OBJ: Object + DeserializeOwned> std::convert::TryFrom<&RawValue> for GetRes
type Error = crate::error::Error; type Error = crate::error::Error;
fn try_from(t: &RawValue) -> Result<GetResponse<OBJ>, crate::error::Error> { fn try_from(t: &RawValue) -> Result<GetResponse<OBJ>, crate::error::Error> {
let res: (String, GetResponse<OBJ>, String) = let res: (String, GetResponse<OBJ>, String) =
serde_json::from_str(t.get()).map_err(|err| crate::error::Error::new(format!("BUG: Could not deserialize server JSON response properly, please report this!\nReply from server: {}", &t)).set_source(Some(Arc::new(err))).set_kind(crate::error::ErrorKind::Bug))?; serde_json::from_str(t.get()).map_err(|err| {
crate::error::Error::new(format!(
"BUG: Could not deserialize server JSON response properly, please report \
this!\nReply from server: {}",
&t
))
.set_source(Some(Arc::new(err)))
.set_kind(crate::error::ErrorKind::Bug)
})?;
assert_eq!(&res.0, &format!("{}/get", OBJ::NAME)); assert_eq!(&res.0, &format!("{}/get", OBJ::NAME));
Ok(res.1) Ok(res.1)
} }
@ -519,7 +532,15 @@ impl<OBJ: Object + DeserializeOwned> std::convert::TryFrom<&RawValue> for QueryR
type Error = crate::error::Error; type Error = crate::error::Error;
fn try_from(t: &RawValue) -> Result<QueryResponse<OBJ>, crate::error::Error> { fn try_from(t: &RawValue) -> Result<QueryResponse<OBJ>, crate::error::Error> {
let res: (String, QueryResponse<OBJ>, String) = let res: (String, QueryResponse<OBJ>, String) =
serde_json::from_str(t.get()).map_err(|err| crate::error::Error::new(format!("BUG: Could not deserialize server JSON response properly, please report this!\nReply from server: {}", &t)).set_source(Some(Arc::new(err))).set_kind(crate::error::ErrorKind::Bug))?; serde_json::from_str(t.get()).map_err(|err| {
crate::error::Error::new(format!(
"BUG: Could not deserialize server JSON response properly, please report \
this!\nReply from server: {}",
&t
))
.set_source(Some(Arc::new(err)))
.set_kind(crate::error::ErrorKind::Bug)
})?;
assert_eq!(&res.0, &format!("{}/query", OBJ::NAME)); assert_eq!(&res.0, &format!("{}/query", OBJ::NAME));
Ok(res.1) Ok(res.1)
} }
@ -543,8 +564,8 @@ impl<M: Method<OBJ>, OBJ: Object> ResultField<M, OBJ> {
} }
} }
// error[E0723]: trait bounds other than `Sized` on const fn parameters are unstable // error[E0723]: trait bounds other than `Sized` on const fn parameters are
// --> melib/src/backends/jmap/rfc8620.rs:626:6 // unstable --> melib/src/backends/jmap/rfc8620.rs:626:6
// | // |
// 626 | impl<M: Method<OBJ>, OBJ: Object> ResultField<M, OBJ> { // 626 | impl<M: Method<OBJ>, OBJ: Object> ResultField<M, OBJ> {
// | ^ // | ^
@ -562,8 +583,9 @@ impl<M: Method<OBJ>, OBJ: Object> ResultField<M, OBJ> {
/// #`changes` /// #`changes`
/// ///
/// The "Foo/changes" method allows a client to efficiently update the state of its Foo cache /// The "Foo/changes" method allows a client to efficiently update the state
/// to match the new state on the server. It takes the following arguments: /// of its Foo cache to match the new state on the server. It takes the
/// following arguments:
/// ///
/// - accountId: "Id" The id of the account to use. /// - accountId: "Id" The id of the account to use.
/// - sinceState: "String" /// - sinceState: "String"
@ -579,7 +601,6 @@ impl<M: Method<OBJ>, OBJ: Object> ResultField<M, OBJ> {
/// to return. If supplied by the client, the value MUST be a /// to return. If supplied by the client, the value MUST be a
/// positive integer greater than 0. If a value outside of this range /// positive integer greater than 0. If a value outside of this range
/// is given, the server MUST re /// is given, the server MUST re
///
#[derive(Deserialize, Serialize, Debug)] #[derive(Deserialize, Serialize, Debug)]
#[serde(rename_all = "camelCase")] #[serde(rename_all = "camelCase")]
/* ch-ch-ch-ch-ch-Changes */ /* ch-ch-ch-ch-ch-Changes */
@ -608,10 +629,9 @@ where
} }
} }
_impl!( _impl!(
/// - accountId: "Id" /// - accountId: "Id"
/// ///
/// The id of the account to use. /// The id of the account to use.
///
account_id: Id<Account> account_id: Id<Account>
); );
_impl!( _impl!(
@ -620,8 +640,6 @@ where
/// returned as the "state" argument in the "Foo/get" response. The /// returned as the "state" argument in the "Foo/get" response. The
/// server will return the changes that have occurred since this /// server will return the changes that have occurred since this
/// state. /// state.
///
///
since_state: State<OBJ> since_state: State<OBJ>
); );
_impl!( _impl!(
@ -630,8 +648,8 @@ where
/// MAY choose to return fewer than this value but MUST NOT return /// MAY choose to return fewer than this value but MUST NOT return
/// more. If not given by the client, the server may choose how many /// more. If not given by the client, the server may choose how many
/// to return. If supplied by the client, the value MUST be a /// to return. If supplied by the client, the value MUST be a
/// positive integer greater than 0. If a value outside of this range /// positive integer greater than 0. If a value outside of this
/// is given, the server MUST re /// range is given, the server MUST re
max_changes: Option<u64> max_changes: Option<u64>
); );
} }
@ -654,7 +672,15 @@ impl<OBJ: Object + DeserializeOwned> std::convert::TryFrom<&RawValue> for Change
type Error = crate::error::Error; type Error = crate::error::Error;
fn try_from(t: &RawValue) -> Result<ChangesResponse<OBJ>, crate::error::Error> { fn try_from(t: &RawValue) -> Result<ChangesResponse<OBJ>, crate::error::Error> {
let res: (String, ChangesResponse<OBJ>, String) = let res: (String, ChangesResponse<OBJ>, String) =
serde_json::from_str(t.get()).map_err(|err| crate::error::Error::new(format!("BUG: Could not deserialize server JSON response properly, please report this!\nReply from server: {}", &t)).set_source(Some(Arc::new(err))).set_kind(crate::error::ErrorKind::Bug))?; serde_json::from_str(t.get()).map_err(|err| {
crate::error::Error::new(format!(
"BUG: Could not deserialize server JSON response properly, please report \
this!\nReply from server: {}",
&t
))
.set_source(Some(Arc::new(err)))
.set_kind(crate::error::ErrorKind::Bug)
})?;
assert_eq!(&res.0, &format!("{}/changes", OBJ::NAME)); assert_eq!(&res.0, &format!("{}/changes", OBJ::NAME));
Ok(res.1) Ok(res.1)
} }
@ -707,7 +733,6 @@ where
/// ///
/// The client MUST omit any properties that may only be set by the /// The client MUST omit any properties that may only be set by the
/// server (for example, the "id" property on most object types). /// server (for example, the "id" property on most object types).
///
pub create: Option<HashMap<Id<OBJ>, OBJ>>, pub create: Option<HashMap<Id<OBJ>, OBJ>>,
///o update: "Id[PatchObject]|null" ///o update: "Id[PatchObject]|null"
/// ///
@ -722,26 +747,26 @@ where
/// All paths MUST also conform to the following restrictions; if /// All paths MUST also conform to the following restrictions; if
/// there is any violation, the update MUST be rejected with an /// there is any violation, the update MUST be rejected with an
/// "invalidPatch" error: /// "invalidPatch" error:
/// * The pointer MUST NOT reference inside an array (i.e., you MUST /// * The pointer MUST NOT reference inside an array (i.e., you MUST NOT
/// NOT insert/delete from an array; the array MUST be replaced in /// insert/delete from an array; the array MUST be replaced in its
/// its entirety instead). /// entirety instead).
/// ///
/// * All parts prior to the last (i.e., the value after the final /// * All parts prior to the last (i.e., the value after the final slash)
/// slash) MUST already exist on the object being patched. /// MUST already exist on the object being patched.
/// ///
/// * There MUST NOT be two patches in the PatchObject where the /// * There MUST NOT be two patches in the PatchObject where the pointer
/// pointer of one is the prefix of the pointer of the other, e.g., /// of one is the prefix of the pointer of the other, e.g.,
/// "alerts/1/offset" and "alerts". /// "alerts/1/offset" and "alerts".
/// ///
/// The value associated with each pointer determines how to apply /// The value associated with each pointer determines how to apply
/// that patch: /// that patch:
/// ///
/// * If null, set to the default value if specified for this /// * If null, set to the default value if specified for this property;
/// property; otherwise, remove the property from the patched /// otherwise, remove the property from the patched object. If the key
/// object. If the key is not present in the parent, this a no-op. /// is not present in the parent, this a no-op.
/// ///
/// * Anything else: The value to set for this property (this may be /// * Anything else: The value to set for this property (this may be a
/// a replacement or addition to the object being patched). /// replacement or addition to the object being patched).
/// ///
/// Any server-set properties MAY be included in the patch if their /// Any server-set properties MAY be included in the patch if their
/// value is identical to the current server value (before applying /// value is identical to the current server value (before applying
@ -853,7 +878,15 @@ impl<OBJ: Object + DeserializeOwned> std::convert::TryFrom<&RawValue> for SetRes
type Error = crate::error::Error; type Error = crate::error::Error;
fn try_from(t: &RawValue) -> Result<SetResponse<OBJ>, crate::error::Error> { fn try_from(t: &RawValue) -> Result<SetResponse<OBJ>, crate::error::Error> {
let res: (String, SetResponse<OBJ>, String) = let res: (String, SetResponse<OBJ>, String) =
serde_json::from_str(t.get()).map_err(|err| crate::error::Error::new(format!("BUG: Could not deserialize server JSON response properly, please report this!\nReply from server: {}", &t)).set_source(Some(Arc::new(err))).set_kind(crate::error::ErrorKind::Bug))?; serde_json::from_str(t.get()).map_err(|err| {
crate::error::Error::new(format!(
"BUG: Could not deserialize server JSON response properly, please report \
this!\nReply from server: {}",
&t
))
.set_source(Some(Arc::new(err)))
.set_kind(crate::error::ErrorKind::Bug)
})?;
assert_eq!(&res.0, &format!("{}/set", OBJ::NAME)); assert_eq!(&res.0, &format!("{}/set", OBJ::NAME));
Ok(res.1) Ok(res.1)
} }
@ -863,31 +896,41 @@ impl<OBJ: Object + DeserializeOwned> std::convert::TryFrom<&RawValue> for SetRes
#[serde(rename_all = "camelCase")] #[serde(rename_all = "camelCase")]
#[serde(tag = "type", content = "description")] #[serde(tag = "type", content = "description")]
pub enum SetError { pub enum SetError {
///(create; update; destroy). The create/update/destroy would violate an ACL or other permissions policy. ///(create; update; destroy). The create/update/destroy would violate an
/// ACL or other permissions policy.
Forbidden(Option<String>), Forbidden(Option<String>),
///(create; update). The create would exceed a server- defined limit on the number or total size of objects of this type. ///(create; update). The create would exceed a server- defined limit on
/// the number or total size of objects of this type.
OverQuota(Option<String>), OverQuota(Option<String>),
///(create; update). The create/update would result in an object that exceeds a server-defined limit for the maximum size of a single object of this type. ///(create; update). The create/update would result in an object that
/// exceeds a server-defined limit for the maximum size of a single object
/// of this type.
TooLarge(Option<String>), TooLarge(Option<String>),
///(create). Too many objects of this type have been created recently, and a server-defined rate limit has been reached. It may work if tried again later. ///(create). Too many objects of this type have been created recently, and
/// a server-defined rate limit has been reached. It may work if tried
/// again later.
RateLimit(Option<String>), RateLimit(Option<String>),
///(update; destroy). The id given to update/destroy cannot be found. ///(update; destroy). The id given to update/destroy cannot be found.
NotFound(Option<String>), NotFound(Option<String>),
///(update). The PatchObject given to update the record was not a valid patch (see the patch description). ///(update). The PatchObject given to update the record was not a valid
/// patch (see the patch description).
InvalidPatch(Option<String>), InvalidPatch(Option<String>),
///(update). The client requested that an object be both updated and destroyed in the same /set request, and the server has decided to therefore ignore the update. ///(update). The client requested that an object be both updated and
/// destroyed in the same /set request, and the server has decided to
/// therefore ignore the update.
WillDestroy(Option<String>), WillDestroy(Option<String>),
///(create; update). The record given is invalid in some way. ///(create; update). The record given is invalid in some way.
InvalidProperties { InvalidProperties {
description: Option<String>, description: Option<String>,
properties: Vec<String>, properties: Vec<String>,
}, },
///(create; destroy). This is a singleton type, so you cannot create another one or destroy the existing one. ///(create; destroy). This is a singleton type, so you cannot create
/// another one or destroy the existing one.
Singleton(Option<String>), Singleton(Option<String>),
RequestTooLarge(Option<String>), RequestTooLarge(Option<String>),
StateMismatch(Option<String>), StateMismatch(Option<String>),
@ -1001,8 +1044,9 @@ pub struct UploadResponse {
pub account_id: Id<Account>, pub account_id: Id<Account>,
///o blobId: "Id" ///o blobId: "Id"
/// ///
///The id representing the binary data uploaded. The data for this id is immutable. ///The id representing the binary data uploaded. The data for this id is
///The id *only* refers to the binary data, not any metadata. /// immutable. The id *only* refers to the binary data, not any
/// metadata.
pub blob_id: Id<BlobObject>, pub blob_id: Id<BlobObject>,
///o type: "String" ///o type: "String"
/// ///
@ -1098,11 +1142,15 @@ where
pub struct QueryChangesResponse<OBJ: Object> { pub struct QueryChangesResponse<OBJ: Object> {
/// The id of the account used for the call. /// The id of the account used for the call.
pub account_id: Id<Account>, pub account_id: Id<Account>,
/// This is the "sinceQueryState" argument echoed back; that is, the state from which the server is returning changes. /// This is the "sinceQueryState" argument echoed back; that is, the state
/// from which the server is returning changes.
pub old_query_state: String, pub old_query_state: String,
///This is the state the query will be in after applying the set of changes to the old state. ///This is the state the query will be in after applying the set of changes
/// to the old state.
pub new_query_state: String, pub new_query_state: String,
/// The total number of Foos in the results (given the "filter"). This argument MUST be omitted if the "calculateTotal" request argument is not true. /// The total number of Foos in the results (given the "filter"). This
/// argument MUST be omitted if the "calculateTotal" request argument is not
/// true.
#[serde(default)] #[serde(default)]
pub total: Option<usize>, pub total: Option<usize>,
///The "id" for every Foo that was in the query results in the old ///The "id" for every Foo that was in the query results in the old
@ -1139,9 +1187,9 @@ pub struct QueryChangesResponse<OBJ: Object> {
///An *AddedItem* object has the following properties: ///An *AddedItem* object has the following properties:
///* id: "Id" /// * id: "Id"
///* index: "UnsignedInt" /// * index: "UnsignedInt"
///The result of this is that if the client has a cached sparse array of ///The result of this is that if the client has a cached sparse array of
///Foo ids corresponding to the results in the old state, then: ///Foo ids corresponding to the results in the old state, then:

View File

@ -19,9 +19,10 @@
* along with meli. If not, see <http://www.gnu.org/licenses/>. * along with meli. If not, see <http://www.gnu.org/licenses/>.
*/ */
use crate::backends::jmap::protocol::Method; use crate::backends::jmap::{
use crate::backends::jmap::rfc8620::Object; protocol::Method,
use crate::backends::jmap::rfc8620::ResultField; rfc8620::{Object, ResultField},
};
#[derive(Deserialize, Serialize, Debug)] #[derive(Deserialize, Serialize, Debug)]
#[serde(rename_all = "camelCase")] #[serde(rename_all = "camelCase")]

View File

@ -24,19 +24,24 @@ mod backend;
pub use self::backend::*; pub use self::backend::*;
mod stream; mod stream;
use std::{
collections::hash_map::DefaultHasher,
fs,
hash::{Hash, Hasher},
io::{BufReader, Read},
path::{Path, PathBuf},
sync::{Arc, Mutex},
};
use futures::stream::Stream;
pub use stream::*; pub use stream::*;
use crate::backends::*; use crate::{
use crate::email::Flag; backends::*,
use crate::error::{Error, Result}; email::Flag,
use crate::shellexpand::ShellExpandTrait; error::{Error, Result},
use futures::stream::Stream; shellexpand::ShellExpandTrait,
use std::collections::hash_map::DefaultHasher; };
use std::fs;
use std::hash::{Hash, Hasher};
use std::io::{BufReader, Read};
use std::path::{Path, PathBuf};
use std::sync::{Arc, Mutex};
/// `BackendOp` implementor for Maildir /// `BackendOp` implementor for Maildir
#[derive(Debug)] #[derive(Debug)]
@ -96,7 +101,7 @@ impl<'a> BackendOp for MaildirOp {
let file = std::fs::OpenOptions::new() let file = std::fs::OpenOptions::new()
.read(true) .read(true)
.write(false) .write(false)
.open(&self.path()?)?; .open(self.path()?)?;
let mut buf_reader = BufReader::new(file); let mut buf_reader = BufReader::new(file);
let mut contents = Vec::new(); let mut contents = Vec::new();
buf_reader.read_to_end(&mut contents)?; buf_reader.read_to_end(&mut contents)?;
@ -141,8 +146,8 @@ impl MaildirMailbox {
let mut h = DefaultHasher::new(); let mut h = DefaultHasher::new();
pathbuf.hash(&mut h); pathbuf.hash(&mut h);
/* Check if mailbox path (Eg `INBOX/Lists/luddites`) is included in the subscribed /* Check if mailbox path (Eg `INBOX/Lists/luddites`) is included in the
* mailboxes in user configuration */ * subscribed mailboxes in user configuration */
let fname = pathbuf let fname = pathbuf
.strip_prefix( .strip_prefix(
PathBuf::from(&settings.root_mailbox) PathBuf::from(&settings.root_mailbox)
@ -279,7 +284,11 @@ impl MaildirPathTrait for Path {
'S' => flag |= Flag::SEEN, 'S' => flag |= Flag::SEEN,
'T' => flag |= Flag::TRASHED, 'T' => flag |= Flag::TRASHED,
_ => { _ => {
debug!("DEBUG: in MaildirPathTrait::flags(), encountered unknown flag marker {:?}, path is {}", f, path); debug!(
"DEBUG: in MaildirPathTrait::flags(), encountered unknown flag marker \
{:?}, path is {}",
f, path
);
} }
} }
} }

View File

@ -21,32 +21,36 @@
//! # Maildir Backend //! # Maildir Backend
//! //!
//! This module implements a maildir backend according to the maildir specification. //! This module implements a maildir backend according to the maildir
//! <https://cr.yp.to/proto/maildir.html> //! specification. <https://cr.yp.to/proto/maildir.html>
use super::{MaildirMailbox, MaildirOp, MaildirPathTrait};
use crate::backends::{RefreshEventKind::*, *};
use crate::conf::AccountSettings;
use crate::email::{Envelope, EnvelopeHash, Flag};
use crate::error::{Error, ErrorKind, Result};
use crate::shellexpand::ShellExpandTrait;
use crate::Collection;
use futures::prelude::Stream; use futures::prelude::Stream;
extern crate notify; use super::{MaildirMailbox, MaildirOp, MaildirPathTrait};
use self::notify::{watcher, DebouncedEvent, RecursiveMode, Watcher}; use crate::{
use std::time::Duration; backends::{RefreshEventKind::*, *},
conf::AccountSettings,
email::{Envelope, EnvelopeHash, Flag},
error::{Error, ErrorKind, Result},
shellexpand::ShellExpandTrait,
Collection,
};
use std::collections::{hash_map::DefaultHasher, HashMap, HashSet}; extern crate notify;
use std::ffi::OsStr; use std::{
use std::fs; collections::{hash_map::DefaultHasher, HashMap, HashSet},
use std::hash::{Hash, Hasher}; ffi::OsStr,
use std::io::{self, Read, Write}; fs,
use std::ops::{Deref, DerefMut}; hash::{Hash, Hasher},
use std::os::unix::fs::PermissionsExt; io::{self, Read, Write},
use std::path::{Component, Path, PathBuf}; ops::{Deref, DerefMut},
use std::sync::mpsc::channel; os::unix::fs::PermissionsExt,
use std::sync::{Arc, Mutex}; path::{Component, Path, PathBuf},
sync::{mpsc::channel, Arc, Mutex},
time::Duration,
};
use self::notify::{watcher, DebouncedEvent, RecursiveMode, Watcher};
#[derive(Clone, Debug, PartialEq)] #[derive(Clone, Debug, PartialEq)]
pub(super) enum PathMod { pub(super) enum PathMod {
@ -669,7 +673,10 @@ impl MailBackend for MaildirType {
e.modified = Some(PathMod::Hash(new_hash)); e.modified = Some(PathMod::Hash(new_hash));
e.removed = false; e.removed = false;
}); });
debug!("contains_old_key, key was marked as removed (by external source)"); debug!(
"contains_old_key, key was marked as removed (by external \
source)"
);
} else { } else {
debug!("not contains_new_key"); debug!("not contains_new_key");
} }
@ -893,7 +900,7 @@ impl MailBackend for MaildirType {
Some(PathMod::Path(new_name.clone())); Some(PathMod::Path(new_name.clone()));
debug!("renaming {:?} to {:?}", path, new_name); debug!("renaming {:?} to {:?}", path, new_name);
fs::rename(&path, &new_name)?; fs::rename(path, &new_name)?;
debug!("success in rename"); debug!("success in rename");
} }
Ok(()) Ok(())
@ -996,12 +1003,16 @@ impl MailBackend for MaildirType {
let mut path = self.path.clone(); let mut path = self.path.clone();
path.push(&new_path); path.push(&new_path);
if !path.starts_with(&self.path) { if !path.starts_with(&self.path) {
return Err(Error::new(format!("Path given (`{}`) is absolute. Please provide a path relative to the account's root mailbox.", &new_path))); return Err(Error::new(format!(
"Path given (`{}`) is absolute. Please provide a path relative to the account's \
root mailbox.",
&new_path
)));
} }
std::fs::create_dir(&path)?; std::fs::create_dir(&path)?;
/* create_dir does not create intermediate directories (like `mkdir -p`), so the parent must be a valid /* create_dir does not create intermediate directories (like `mkdir -p`), so
* mailbox at this point. */ * the parent must be a valid mailbox at this point. */
let parent = path.parent().and_then(|p| { let parent = path.parent().and_then(|p| {
self.mailboxes self.mailboxes
@ -1143,8 +1154,9 @@ impl MaildirType {
children.push(f.hash); children.push(f.hash);
mailboxes.insert(f.hash, f); mailboxes.insert(f.hash, f);
} else { } else {
/* If directory is invalid (i.e. has no {cur,new,tmp} subfolders), /* If directory is invalid (i.e. has no {cur,new,tmp}
* accept it ONLY if it contains subdirs of any depth that are * subfolders), accept it ONLY if
* it contains subdirs of any depth that are
* valid maildir paths * valid maildir paths
*/ */
let subdirs = recurse_mailboxes(mailboxes, settings, &path)?; let subdirs = recurse_mailboxes(mailboxes, settings, &path)?;
@ -1379,7 +1391,7 @@ fn add_path_to_index(
map.len() map.len()
); );
} }
let mut reader = io::BufReader::new(fs::File::open(&path)?); let mut reader = io::BufReader::new(fs::File::open(path)?);
buf.clear(); buf.clear();
reader.read_to_end(buf)?; reader.read_to_end(buf)?;
let mut env = Envelope::from_bytes(buf.as_slice(), Some(path.flags()))?; let mut env = Envelope::from_bytes(buf.as_slice(), Some(path.flags()))?;

View File

@ -19,17 +19,22 @@
* along with meli. If not, see <http://www.gnu.org/licenses/>. * along with meli. If not, see <http://www.gnu.org/licenses/>.
*/ */
use core::{future::Future, pin::Pin};
use std::{
io::{self, Read},
os::unix::fs::PermissionsExt,
path::PathBuf,
result,
sync::{Arc, Mutex},
};
use futures::{
stream::{FuturesUnordered, StreamExt},
task::{Context, Poll},
};
use super::*; use super::*;
use crate::backends::maildir::backend::move_to_cur; use crate::backends::maildir::backend::move_to_cur;
use core::future::Future;
use core::pin::Pin;
use futures::stream::{FuturesUnordered, StreamExt};
use futures::task::{Context, Poll};
use std::io::{self, Read};
use std::os::unix::fs::PermissionsExt;
use std::path::PathBuf;
use std::result;
use std::sync::{Arc, Mutex};
pub struct MaildirStream { pub struct MaildirStream {
payloads: Pin< payloads: Pin<
@ -66,7 +71,7 @@ impl MaildirStream {
files files
.chunks(chunk_size) .chunks(chunk_size)
.map(|chunk| { .map(|chunk| {
let cache_dir = xdg::BaseDirectories::with_profile("meli", &name).unwrap(); let cache_dir = xdg::BaseDirectories::with_profile("meli", name).unwrap();
Box::pin(Self::chunk( Box::pin(Self::chunk(
SmallVec::from(chunk), SmallVec::from(chunk),
cache_dir, cache_dir,

View File

@ -31,36 +31,44 @@
//! //!
//! `mbox` describes a family of incompatible legacy formats. //! `mbox` describes a family of incompatible legacy formats.
//! //!
//! "All of the 'mbox' formats store all of the messages in the mailbox in a single file. Delivery appends new messages to the end of the file." [^0] //! "All of the 'mbox' formats store all of the messages in the mailbox in a
//! single file. Delivery appends new messages to the end of the file." [^0]
//! //!
//! "Each message is preceded by a From_ line and followed by a blank line. A From_ line is a line that begins with the five characters 'F', 'r', 'o', 'm', and ' '." [^0] //! "Each message is preceded by a From_ line and followed by a blank line. A
//! From_ line is a line that begins with the five characters 'F', 'r', 'o',
//! 'm', and ' '." [^0]
//! //!
//! ## `From ` / postmark line //! ## `From ` / postmark line
//! //!
//! "An mbox is a text file containing an arbitrary number of e-mail messages. Each message //! "An mbox is a text file containing an arbitrary number of e-mail messages.
//! consists of a postmark, followed by an e-mail message formatted according to RFC822, RFC2822. //! Each message consists of a postmark, followed by an e-mail message formatted
//! The file format is line-oriented. Lines are separated by line feed characters (ASCII 10). //! according to RFC822, RFC2822. The file format is line-oriented. Lines are
//! separated by line feed characters (ASCII 10).
//! //!
//! "A postmark line consists of the four characters 'From', followed by a space character, //! "A postmark line consists of the four characters 'From', followed by a space
//! followed by the message's envelope sender address, followed by whitespace, and followed by a //! character, followed by the message's envelope sender address, followed by
//! time stamp. This line is often called From_ line. //! whitespace, and followed by a time stamp. This line is often called From_
//! line.
//! //!
//! "The sender address is expected to be addr-spec as defined in RFC2822 3.4.1. The date is expected //! "The sender address is expected to be addr-spec as defined in RFC2822 3.4.1.
//! to be date-time as output by asctime(3). For compatibility reasons with legacy software, //! The date is expected to be date-time as output by asctime(3). For
//! two-digit years greater than or equal to 70 should be interpreted as the years 1970+, while //! compatibility reasons with legacy software, two-digit years greater than or
//! two-digit years less than 70 should be interpreted as the years 2000-2069. Software reading //! equal to 70 should be interpreted as the years 1970+, while two-digit years
//! files in this format should also be prepared to accept non-numeric timezone information such as //! less than 70 should be interpreted as the years 2000-2069. Software reading
//! 'CET DST' for Central European Time, daylight saving time. //! files in this format should also be prepared to accept non-numeric timezone
//! information such as 'CET DST' for Central European Time, daylight saving
//! time.
//! //!
//! "Example: //! "Example:
//! //!
//!```text //!```text
//!From example@example.com Fri Jun 23 02:56:55 2000 //! From example@example.com Fri Jun 23 02:56:55 2000
//!``` //! ```
//! //!
//! "In order to avoid misinterpretation of lines in message bodies which begin with the four //! "In order to avoid misinterpretation of lines in message bodies which begin
//! characters 'From', followed by a space character, the mail delivery agent must quote //! with the four characters 'From', followed by a space character, the mail
//! any occurrence of 'From ' at the start of a body line." [^2] //! delivery agent must quote any occurrence of 'From ' at the start of a body
//! line." [^2]
//! //!
//! ## Metadata //! ## Metadata
//! //!
@ -77,7 +85,8 @@
//! # use std::collections::HashMap; //! # use std::collections::HashMap;
//! # use std::sync::{Arc, Mutex}; //! # use std::sync::{Arc, Mutex};
//! let file_contents = vec![]; // Replace with actual mbox file contents //! let file_contents = vec![]; // Replace with actual mbox file contents
//! let index: Arc<Mutex<HashMap<EnvelopeHash, (Offset, Length)>>> = Arc::new(Mutex::new(HashMap::default())); //! let index: Arc<Mutex<HashMap<EnvelopeHash, (Offset, Length)>>> =
//! Arc::new(Mutex::new(HashMap::default()));
//! let mut message_iter = MessageIterator { //! let mut message_iter = MessageIterator {
//! index: index.clone(), //! index: index.clone(),
//! input: &file_contents.as_slice(), //! input: &file_contents.as_slice(),
@ -100,9 +109,9 @@
//! format.append( //! format.append(
//! &mut file, //! &mut file,
//! mbox_1, //! mbox_1,
//! None, // Envelope From //! None, // Envelope From
//! Some(melib::datetime::now()), // Delivered date //! Some(melib::datetime::now()), // Delivered date
//! Default::default(), // Flags and tags //! Default::default(), // Flags and tags
//! MboxMetadata::None, //! MboxMetadata::None,
//! true, //! true,
//! false, //! false,
@ -121,29 +130,37 @@
//! # Ok::<(), melib::Error>(()) //! # Ok::<(), melib::Error>(())
//! ``` //! ```
use crate::backends::*; use nom::{
use crate::collection::Collection; self,
use crate::conf::AccountSettings; bytes::complete::tag,
use crate::email::parser::BytesExt; character::complete::digit1,
use crate::email::*; combinator::map_res,
use crate::error::{Error, ErrorKind, Result}; error::{Error as NomError, ErrorKind as NomErrorKind},
use crate::get_path_hash; IResult,
use crate::shellexpand::ShellExpandTrait; };
use nom::bytes::complete::tag;
use nom::character::complete::digit1; use crate::{
use nom::combinator::map_res; backends::*,
use nom::{self, error::Error as NomError, error::ErrorKind as NomErrorKind, IResult}; collection::Collection,
conf::AccountSettings,
email::{parser::BytesExt, *},
error::{Error, ErrorKind, Result},
get_path_hash,
shellexpand::ShellExpandTrait,
};
extern crate notify; extern crate notify;
use std::{
collections::hash_map::HashMap,
fs::File,
io::{BufReader, Read},
os::unix::io::AsRawFd,
path::{Path, PathBuf},
str::FromStr,
sync::{mpsc::channel, Arc, Mutex, RwLock},
};
use self::notify::{watcher, DebouncedEvent, RecursiveMode, Watcher}; use self::notify::{watcher, DebouncedEvent, RecursiveMode, Watcher};
use std::collections::hash_map::HashMap;
use std::fs::File;
use std::io::{BufReader, Read};
use std::os::unix::io::AsRawFd;
use std::path::{Path, PathBuf};
use std::str::FromStr;
use std::sync::mpsc::channel;
use std::sync::{Arc, Mutex, RwLock};
pub mod write; pub mod write;
@ -163,7 +180,8 @@ fn get_rw_lock_blocking(f: &File, path: &Path) -> Result<()> {
l_start: 0, l_start: 0,
l_len: 0, /* "Specifying 0 for l_len has the special meaning: lock all bytes starting at the location l_len: 0, /* "Specifying 0 for l_len has the special meaning: lock all bytes starting at the location
specified by l_whence and l_start through to the end of file, no matter how large the file grows." */ specified by l_whence and l_start through to the end of file, no matter how large the file grows." */
l_pid: 0, /* "By contrast with traditional record locks, the l_pid field of that structure must be set to zero when using the commands described below." */ l_pid: 0, /* "By contrast with traditional record locks, the l_pid field of that
* structure must be set to zero when using the commands described below." */
#[cfg(target_os = "freebsd")] #[cfg(target_os = "freebsd")]
l_sysid: 0, l_sysid: 0,
}; };
@ -368,9 +386,12 @@ impl BackendOp for MboxOp {
#[derive(Debug, Clone, Copy)] #[derive(Debug, Clone, Copy)]
pub enum MboxMetadata { pub enum MboxMetadata {
/// Dovecot uses C-Client (ie. UW-IMAP, Pine) compatible headers in mbox messages to store me /// Dovecot uses C-Client (ie. UW-IMAP, Pine) compatible headers in mbox
/// - X-IMAPbase: Contains UIDVALIDITY, last used UID and list of used keywords /// messages to store me
/// - X-IMAP: Same as X-IMAPbase but also specifies that the message is a “pseudo message” /// - X-IMAPbase: Contains UIDVALIDITY, last used UID and list of used
/// keywords
/// - X-IMAP: Same as X-IMAPbase but also specifies that the message is a
/// “pseudo message”
/// - X-UID: Messages allocated UID /// - X-UID: Messages allocated UID
/// - Status: R (Seen) and O (non-Recent) flags /// - Status: R (Seen) and O (non-Recent) flags
/// - X-Status: A (Answered), F (Flagged), T (Draft) and D (Deleted) flags /// - X-Status: A (Answered), F (Flagged), T (Draft) and D (Deleted) flags
@ -380,8 +401,8 @@ pub enum MboxMetadata {
None, None,
} }
/// Choose between "mboxo", "mboxrd", "mboxcl", "mboxcl2". For new mailboxes, prefer "mboxcl2" /// Choose between "mboxo", "mboxrd", "mboxcl", "mboxcl2". For new mailboxes,
/// which does not alter the mail body. /// prefer "mboxcl2" which does not alter the mail body.
#[derive(Debug, Clone, Copy)] #[derive(Debug, Clone, Copy)]
pub enum MboxFormat { pub enum MboxFormat {
MboxO, MboxO,
@ -1406,7 +1427,8 @@ impl MboxType {
); );
} else { } else {
return Err(Error::new(format!( return Err(Error::new(format!(
"mbox mailbox configuration entry \"{}\" should have a \"path\" value set pointing to an mbox file.", "mbox mailbox configuration entry \"{}\" should have a \"path\" value set \
pointing to an mbox file.",
k k
))); )));
} }

View File

@ -21,14 +21,14 @@
//! # NNTP backend / client //! # NNTP backend / client
//! //!
//! Implements an NNTP client as specified by [RFC 3977: Network News Transfer Protocol //! Implements an NNTP client as specified by [RFC 3977: Network News Transfer
//! (NNTP)](https://datatracker.ietf.org/doc/html/rfc3977). Also implements [RFC 6048: Network News //! Protocol (NNTP)](https://datatracker.ietf.org/doc/html/rfc3977). Also implements [RFC 6048: Network News
//! Transfer Protocol (NNTP) Additions to LIST //! Transfer Protocol (NNTP) Additions to LIST
//! Command](https://datatracker.ietf.org/doc/html/rfc6048). //! Command](https://datatracker.ietf.org/doc/html/rfc6048).
use crate::get_conf_val;
use crate::get_path_hash;
use smallvec::SmallVec; use smallvec::SmallVec;
use crate::{get_conf_val, get_path_hash};
#[macro_use] #[macro_use]
mod protocol_parser; mod protocol_parser;
pub use protocol_parser::*; pub use protocol_parser::*;
@ -37,21 +37,26 @@ pub use mailbox::*;
mod operations; mod operations;
pub use operations::*; pub use operations::*;
mod connection; mod connection;
pub use connection::*; use std::{
collections::{hash_map::DefaultHasher, BTreeSet, HashMap, HashSet},
hash::Hasher,
pin::Pin,
str::FromStr,
sync::{Arc, Mutex},
time::{Duration, Instant},
};
use crate::conf::AccountSettings; pub use connection::*;
use crate::connections::timeout; use futures::{lock::Mutex as FutureMutex, stream::Stream};
use crate::email::*;
use crate::error::{Error, Result, ResultIntoError}; use crate::{
use crate::{backends::*, Collection}; backends::*,
use futures::lock::Mutex as FutureMutex; conf::AccountSettings,
use futures::stream::Stream; connections::timeout,
use std::collections::{hash_map::DefaultHasher, BTreeSet, HashMap, HashSet}; email::*,
use std::hash::Hasher; error::{Error, Result, ResultIntoError},
use std::pin::Pin; Collection,
use std::str::FromStr; };
use std::sync::{Arc, Mutex};
use std::time::{Duration, Instant};
pub type UID = usize; pub type UID = usize;
macro_rules! get_conf_val { macro_rules! get_conf_val {
@ -253,9 +258,21 @@ impl MailBackend for NntpType {
let uid_store = self.uid_store.clone(); let uid_store = self.uid_store.clone();
let connection = self.connection.clone(); let connection = self.connection.clone();
Ok(Box::pin(async move { Ok(Box::pin(async move {
/* To get updates, either issue NEWNEWS if it's supported by the server, and fallback /* To get updates, either issue NEWNEWS if it's supported by the server, and
* to OVER otherwise */ * fallback to OVER otherwise */
let mbox: NntpMailbox = uid_store.mailboxes.lock().await.get(&mailbox_hash).map(std::clone::Clone::clone).ok_or_else(|| Error::new(format!("Mailbox with hash {} not found in NNTP connection, this could possibly be a bug or it was deleted.", mailbox_hash)))?; let mbox: NntpMailbox = uid_store
.mailboxes
.lock()
.await
.get(&mailbox_hash)
.map(std::clone::Clone::clone)
.ok_or_else(|| {
Error::new(format!(
"Mailbox with hash {} not found in NNTP connection, this could possibly \
be a bug or it was deleted.",
mailbox_hash
))
})?;
let latest_article: Option<crate::UnixTimestamp> = *mbox.latest_article.lock().unwrap(); let latest_article: Option<crate::UnixTimestamp> = *mbox.latest_article.lock().unwrap();
let (over_msgid_support, newnews_support): (bool, bool) = { let (over_msgid_support, newnews_support): (bool, bool) = {
let caps = uid_store.capabilities.lock().unwrap(); let caps = uid_store.capabilities.lock().unwrap();
@ -374,15 +391,15 @@ impl MailBackend for NntpType {
} }
fn operation(&self, env_hash: EnvelopeHash) -> Result<Box<dyn BackendOp>> { fn operation(&self, env_hash: EnvelopeHash) -> Result<Box<dyn BackendOp>> {
let (uid, mailbox_hash) = if let Some(v) = let (uid, mailbox_hash) =
self.uid_store.hash_index.lock().unwrap().get(&env_hash) if let Some(v) = self.uid_store.hash_index.lock().unwrap().get(&env_hash) {
{ *v
*v } else {
} else { return Err(Error::new(
return Err(Error::new( "Message not found in local cache, it might have been deleted before you \
"Message not found in local cache, it might have been deleted before you requested it." requested it.",
)); ));
}; };
Ok(Box::new(NntpOp::new( Ok(Box::new(NntpOp::new(
uid, uid,
mailbox_hash, mailbox_hash,
@ -671,34 +688,35 @@ impl NntpType {
pub fn validate_config(s: &mut AccountSettings) -> Result<()> { pub fn validate_config(s: &mut AccountSettings) -> Result<()> {
let mut keys: HashSet<&'static str> = Default::default(); let mut keys: HashSet<&'static str> = Default::default();
macro_rules! get_conf_val { macro_rules! get_conf_val {
($s:ident[$var:literal]) => {{ ($s:ident[$var:literal]) => {{
keys.insert($var); keys.insert($var);
$s.extra.remove($var).ok_or_else(|| { $s.extra.remove($var).ok_or_else(|| {
Error::new(format!(
"Configuration error ({}): NNTP connection requires the field `{}` set",
$s.name.as_str(),
$var
))
})
}};
($s:ident[$var:literal], $default:expr) => {{
keys.insert($var);
$s.extra
.remove($var)
.map(|v| {
<_>::from_str(&v).map_err(|e| {
Error::new(format!( Error::new(format!(
"Configuration error ({}) NNTP: Invalid value for field `{}`: {}\n{}", "Configuration error ({}): NNTP connection requires the field `{}` set",
$s.name.as_str(), $s.name.as_str(),
$var, $var
v,
e
)) ))
}) })
}) }};
.unwrap_or_else(|| Ok($default)) ($s:ident[$var:literal], $default:expr) => {{
}}; keys.insert($var);
} $s.extra
.remove($var)
.map(|v| {
<_>::from_str(&v).map_err(|e| {
Error::new(format!(
"Configuration error ({}) NNTP: Invalid value for field `{}`: \
{}\n{}",
$s.name.as_str(),
$var,
v,
e
))
})
})
.unwrap_or_else(|| Ok($default))
}};
}
get_conf_val!(s["require_auth"], false)?; get_conf_val!(s["require_auth"], false)?;
get_conf_val!(s["server_hostname"])?; get_conf_val!(s["server_hostname"])?;
get_conf_val!(s["server_username"], String::new())?; get_conf_val!(s["server_username"], String::new())?;
@ -706,7 +724,8 @@ impl NntpType {
get_conf_val!(s["server_password"], String::new())?; get_conf_val!(s["server_password"], String::new())?;
} else if s.extra.contains_key("server_password") { } else if s.extra.contains_key("server_password") {
return Err(Error::new(format!( return Err(Error::new(format!(
"Configuration error ({}): both server_password and server_password_command are set, cannot choose", "Configuration error ({}): both server_password and server_password_command are \
set, cannot choose",
s.name.as_str(), s.name.as_str(),
))); )));
} }
@ -716,7 +735,8 @@ impl NntpType {
let use_starttls = get_conf_val!(s["use_starttls"], server_port != 563)?; let use_starttls = get_conf_val!(s["use_starttls"], server_port != 563)?;
if !use_tls && use_starttls { if !use_tls && use_starttls {
return Err(Error::new(format!( return Err(Error::new(format!(
"Configuration error ({}): incompatible use_tls and use_starttls values: use_tls = false, use_starttls = true", "Configuration error ({}): incompatible use_tls and use_starttls values: use_tls \
= false, use_starttls = true",
s.name.as_str(), s.name.as_str(),
))); )));
} }
@ -725,7 +745,8 @@ impl NntpType {
#[cfg(not(feature = "deflate_compression"))] #[cfg(not(feature = "deflate_compression"))]
if s.extra.contains_key("use_deflate") { if s.extra.contains_key("use_deflate") {
return Err(Error::new(format!( return Err(Error::new(format!(
"Configuration error ({}): setting `use_deflate` is set but this version of meli isn't compiled with DEFLATE support.", "Configuration error ({}): setting `use_deflate` is set but this version of meli \
isn't compiled with DEFLATE support.",
s.name.as_str(), s.name.as_str(),
))); )));
} }
@ -738,8 +759,10 @@ impl NntpType {
let diff = extra_keys.difference(&keys).collect::<Vec<&&str>>(); let diff = extra_keys.difference(&keys).collect::<Vec<&&str>>();
if !diff.is_empty() { if !diff.is_empty() {
return Err(Error::new(format!( return Err(Error::new(format!(
"Configuration error ({}) NNTP: the following flags are set but are not recognized: {:?}.", "Configuration error ({}) NNTP: the following flags are set but are not \
s.name.as_str(), diff recognized: {:?}.",
s.name.as_str(),
diff
))); )));
} }
Ok(()) Ok(())

View File

@ -19,19 +19,18 @@
* along with meli. If not, see <http://www.gnu.org/licenses/>. * along with meli. If not, see <http://www.gnu.org/licenses/>.
*/ */
use crate::backends::{BackendMailbox, MailboxHash}; use crate::{
use crate::connections::{lookup_ipv4, Connection}; backends::{BackendMailbox, MailboxHash},
use crate::email::parser::BytesExt; connections::{lookup_ipv4, Connection},
use crate::error::*; email::parser::BytesExt,
error::*,
};
extern crate native_tls; extern crate native_tls;
use std::{collections::HashSet, future::Future, pin::Pin, sync::Arc, time::Instant};
use futures::io::{AsyncReadExt, AsyncWriteExt}; use futures::io::{AsyncReadExt, AsyncWriteExt};
use native_tls::TlsConnector; use native_tls::TlsConnector;
pub use smol::Async as AsyncWrapper; pub use smol::Async as AsyncWrapper;
use std::collections::HashSet;
use std::future::Future;
use std::pin::Pin;
use std::sync::Arc;
use std::time::Instant;
use super::{Capabilities, NntpServerConf, UIDStore}; use super::{Capabilities, NntpServerConf, UIDStore};

View File

@ -19,13 +19,16 @@
* along with meli. If not, see <http://www.gnu.org/licenses/>. * along with meli. If not, see <http://www.gnu.org/licenses/>.
*/ */
use crate::backends::{
BackendMailbox, LazyCountSet, Mailbox, MailboxHash, MailboxPermissions, SpecialUsageMailbox,
};
use crate::error::*;
use crate::UnixTimestamp;
use std::sync::{Arc, Mutex}; use std::sync::{Arc, Mutex};
use crate::{
backends::{
BackendMailbox, LazyCountSet, Mailbox, MailboxHash, MailboxPermissions, SpecialUsageMailbox,
},
error::*,
UnixTimestamp,
};
#[derive(Debug, Default, Clone)] #[derive(Debug, Default, Clone)]
pub struct NntpMailbox { pub struct NntpMailbox {
pub(super) hash: MailboxHash, pub(super) hash: MailboxHash,

View File

@ -19,13 +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 super::*;
use crate::backends::*;
use crate::email::*;
use crate::error::Error;
use std::sync::Arc; use std::sync::Arc;
use super::*;
use crate::{backends::*, email::*, error::Error};
/// `BackendOp` implementor for Nntp /// `BackendOp` implementor for Nntp
#[derive(Debug, Clone)] #[derive(Debug, Clone)]
pub struct NntpOp { pub struct NntpOp {

View File

@ -19,13 +19,15 @@
* along with meli. If not, see <http://www.gnu.org/licenses/>. * along with meli. If not, see <http://www.gnu.org/licenses/>.
*/ */
use super::*; use std::str::FromStr;
use crate::email::parser::IResult;
use nom::{ use nom::{
bytes::complete::{is_not, tag}, bytes::complete::{is_not, tag},
combinator::opt, combinator::opt,
}; };
use std::str::FromStr;
use super::*;
use crate::email::parser::IResult;
pub struct NntpLineIterator<'a> { pub struct NntpLineIterator<'a> {
slice: &'a str, slice: &'a str,

View File

@ -19,18 +19,25 @@
* along with meli. If not, see <http://www.gnu.org/licenses/>. * along with meli. If not, see <http://www.gnu.org/licenses/>.
*/ */
use crate::conf::AccountSettings; use std::{
use crate::email::{Envelope, EnvelopeHash, Flag}; collections::{hash_map::HashMap, BTreeMap},
use crate::error::{Error, Result}; ffi::{CStr, CString, OsStr},
use crate::shellexpand::ShellExpandTrait; io::Read,
use crate::{backends::*, Collection}; os::unix::ffi::OsStrExt,
path::{Path, PathBuf},
sync::{Arc, Mutex, RwLock},
};
use smallvec::SmallVec; use smallvec::SmallVec;
use std::collections::{hash_map::HashMap, BTreeMap};
use std::ffi::{CStr, CString, OsStr}; use crate::{
use std::io::Read; backends::*,
use std::os::unix::ffi::OsStrExt; conf::AccountSettings,
use std::path::{Path, PathBuf}; email::{Envelope, EnvelopeHash, Flag},
use std::sync::{Arc, Mutex, RwLock}; error::{Error, Result},
shellexpand::ShellExpandTrait,
Collection,
};
macro_rules! call { macro_rules! call {
($lib:expr, $func:ty) => {{ ($lib:expr, $func:ty) => {{
@ -316,9 +323,14 @@ impl NotmuchDb {
Ok(l) => l, Ok(l) => l,
Err(err) => { Err(err) => {
if custom_dlpath { if custom_dlpath {
return Err(Error::new(format!("Notmuch `library_file_path` setting value `{}` for account {} does not exist or is a directory or not a valid library file.",dlpath, s.name())) return Err(Error::new(format!(
.set_kind(ErrorKind::Configuration) "Notmuch `library_file_path` setting value `{}` for account {} does \
.set_source(Some(Arc::new(err)))); not exist or is a directory or not a valid library file.",
dlpath,
s.name()
))
.set_kind(ErrorKind::Configuration)
.set_source(Some(Arc::new(err))));
} else { } else {
return Err(Error::new("Could not load libnotmuch!") return Err(Error::new("Could not load libnotmuch!")
.set_details(super::NOTMUCH_ERROR_DETAILS) .set_details(super::NOTMUCH_ERROR_DETAILS)
@ -347,10 +359,12 @@ impl NotmuchDb {
path.push(".notmuch"); path.push(".notmuch");
if !path.exists() || !path.is_dir() { if !path.exists() || !path.is_dir() {
return Err(Error::new(format!( return Err(Error::new(format!(
"Notmuch `root_mailbox` {} for account {} does not contain a `.notmuch` subdirectory.", "Notmuch `root_mailbox` {} for account {} does not contain a `.notmuch` \
subdirectory.",
s.root_mailbox.as_str(), s.root_mailbox.as_str(),
s.name() s.name()
)).set_kind(ErrorKind::Configuration)); ))
.set_kind(ErrorKind::Configuration));
} }
path.pop(); path.pop();
@ -378,7 +392,8 @@ impl NotmuchDb {
); );
} else { } else {
return Err(Error::new(format!( return Err(Error::new(format!(
"notmuch mailbox configuration entry `{}` for account {} should have a `query` value set.", "notmuch mailbox configuration entry `{}` for account {} should have a \
`query` value set.",
k, k,
s.name(), s.name(),
)) ))
@ -399,7 +414,8 @@ impl NotmuchDb {
mailboxes.entry(hash).or_default().parent = Some(parent_hash); mailboxes.entry(hash).or_default().parent = Some(parent_hash);
} else { } else {
return Err(Error::new(format!( return Err(Error::new(format!(
"Mailbox configuration for `{}` defines its parent mailbox as `{}` but no mailbox exists with this exact name.", "Mailbox configuration for `{}` defines its parent mailbox as `{}` but no \
mailbox exists with this exact name.",
mailboxes[&hash].name(), mailboxes[&hash].name(),
parent parent
)) ))
@ -445,10 +461,12 @@ impl NotmuchDb {
path.push(".notmuch"); path.push(".notmuch");
if !path.exists() || !path.is_dir() { if !path.exists() || !path.is_dir() {
return Err(Error::new(format!( return Err(Error::new(format!(
"Notmuch `root_mailbox` {} for account {} does not contain a `.notmuch` subdirectory.", "Notmuch `root_mailbox` {} for account {} does not contain a `.notmuch` \
subdirectory.",
s.root_mailbox.as_str(), s.root_mailbox.as_str(),
s.name() s.name()
)).set_kind(ErrorKind::Configuration)); ))
.set_kind(ErrorKind::Configuration));
} }
path.pop(); path.pop();
@ -456,19 +474,21 @@ impl NotmuchDb {
if let Some(lib_path) = s.extra.remove("library_file_path") { if let Some(lib_path) = s.extra.remove("library_file_path") {
if !Path::new(&lib_path).exists() || Path::new(&lib_path).is_dir() { if !Path::new(&lib_path).exists() || Path::new(&lib_path).is_dir() {
return Err(Error::new(format!( return Err(Error::new(format!(
"Notmuch `library_file_path` setting value `{}` for account {} does not exist or is a directory.", "Notmuch `library_file_path` setting value `{}` for account {} does not exist \
&lib_path, or is a directory.",
s.name() &lib_path,
)).set_kind(ErrorKind::Configuration)); s.name()
))
.set_kind(ErrorKind::Configuration));
} }
} }
let mut parents: Vec<(String, String)> = Vec::with_capacity(s.mailboxes.len()); let mut parents: Vec<(String, String)> = Vec::with_capacity(s.mailboxes.len());
for (k, f) in s.mailboxes.iter_mut() { for (k, f) in s.mailboxes.iter_mut() {
if f.extra.remove("query").is_none() { if f.extra.remove("query").is_none() {
return Err(Error::new(format!( return Err(Error::new(format!(
"notmuch mailbox configuration entry `{}` for account {} should have a `query` value set.", "notmuch mailbox configuration entry `{}` for account {} should have a \
k, `query` value set.",
account_name, k, account_name,
)) ))
.set_kind(ErrorKind::Configuration)); .set_kind(ErrorKind::Configuration));
} }
@ -480,9 +500,9 @@ impl NotmuchDb {
for (mbox, parent) in parents.iter() { for (mbox, parent) in parents.iter() {
if !s.mailboxes.contains_key(parent) { if !s.mailboxes.contains_key(parent) {
return Err(Error::new(format!( return Err(Error::new(format!(
"Mailbox configuration for `{}` defines its parent mailbox as `{}` but no mailbox exists with this exact name.", "Mailbox configuration for `{}` defines its parent mailbox as `{}` but no \
mbox, mailbox exists with this exact name.",
parent mbox, parent
)) ))
.set_kind(ErrorKind::Configuration)); .set_kind(ErrorKind::Configuration));
} }

View File

@ -244,7 +244,6 @@ pub type notmuch_database_open_verbose = unsafe extern "C" fn(
) -> notmuch_status_t; ) -> notmuch_status_t;
/// Retrieve last status string for given database. /// Retrieve last status string for given database.
///
pub type notmuch_database_status_string = pub type notmuch_database_status_string =
unsafe extern "C" fn(notmuch: *const notmuch_database_t) -> *const ::std::os::raw::c_char; unsafe extern "C" fn(notmuch: *const notmuch_database_t) -> *const ::std::os::raw::c_char;
@ -509,7 +508,6 @@ extern "C" {
/// @deprecated Deprecated as of libnotmuch 5.1 (notmuch 0.26). Please /// @deprecated Deprecated as of libnotmuch 5.1 (notmuch 0.26). Please
/// use notmuch_database_index_file instead. /// use notmuch_database_index_file instead.
/// ``` /// ```
///
pub fn notmuch_database_add_message( pub fn notmuch_database_add_message(
database: *mut notmuch_database_t, database: *mut notmuch_database_t,
filename: *const ::std::os::raw::c_char, filename: *const ::std::os::raw::c_char,
@ -751,7 +749,7 @@ pub type notmuch_query_add_tag_exclude = unsafe extern "C" fn(
/// } /// }
/// ///
/// notmuch_query_destroy (query); /// notmuch_query_destroy (query);
///``` /// ```
/// ///
/// Note: If you are finished with a thread before its containing /// Note: If you are finished with a thread before its containing
/// query, you can call notmuch_thread_destroy to clean up some memory /// query, you can call notmuch_thread_destroy to clean up some memory
@ -779,7 +777,6 @@ pub type notmuch_query_search_threads = unsafe extern "C" fn(
/// @deprecated Deprecated as of libnotmuch 5 (notmuch 0.25). Please /// @deprecated Deprecated as of libnotmuch 5 (notmuch 0.25). Please
/// ``` /// ```
/// use notmuch_query_search_threads instead. /// use notmuch_query_search_threads instead.
///
pub type notmuch_query_search_threads_st = unsafe extern "C" fn( pub type notmuch_query_search_threads_st = unsafe extern "C" fn(
query: *mut notmuch_query_t, query: *mut notmuch_query_t,
out: *mut *mut notmuch_threads_t, out: *mut *mut notmuch_threads_t,
@ -809,7 +806,7 @@ pub type notmuch_query_search_threads_st = unsafe extern "C" fn(
/// } /// }
/// ///
/// notmuch_query_destroy (query); /// notmuch_query_destroy (query);
///``` /// ```
/// ///
/// Note: If you are finished with a message before its containing /// Note: If you are finished with a message before its containing
/// query, you can call notmuch_message_destroy to clean up some memory /// query, you can call notmuch_message_destroy to clean up some memory
@ -839,7 +836,6 @@ pub type notmuch_query_search_messages = unsafe extern "C" fn(
/// @deprecated Deprecated as of libnotmuch 5 (notmuch 0.25). Please use /// @deprecated Deprecated as of libnotmuch 5 (notmuch 0.25). Please use
/// ``` /// ```
/// notmuch_query_search_messages instead. /// notmuch_query_search_messages instead.
///
pub type notmuch_query_search_messages_st = unsafe extern "C" fn( pub type notmuch_query_search_messages_st = unsafe extern "C" fn(
query: *mut notmuch_query_t, query: *mut notmuch_query_t,
out: *mut *mut notmuch_messages_t, out: *mut *mut notmuch_messages_t,
@ -1091,7 +1087,7 @@ pub type notmuch_thread_get_newest_date =
/// } /// }
/// ///
/// notmuch_thread_destroy (thread); /// notmuch_thread_destroy (thread);
///``` /// ```
/// ///
/// Note that there's no explicit destructor needed for the /// Note that there's no explicit destructor needed for the
/// notmuch_tags_t object. (For consistency, we do provide a /// notmuch_tags_t object. (For consistency, we do provide a
@ -1250,7 +1246,8 @@ pub type notmuch_message_get_filename =
pub type notmuch_message_get_filenames = pub type notmuch_message_get_filenames =
unsafe extern "C" fn(message: *mut notmuch_message_t) -> *mut notmuch_filenames_t; unsafe extern "C" fn(message: *mut notmuch_message_t) -> *mut notmuch_filenames_t;
/// Re-index the e-mail corresponding to 'message' using the supplied index options /// Re-index the e-mail corresponding to 'message' using the supplied index
/// options
/// ///
/// Returns the status of the re-index operation. (see the return /// Returns the status of the re-index operation. (see the return
/// codes documented in notmuch_database_index_file) /// codes documented in notmuch_database_index_file)
@ -1333,7 +1330,7 @@ pub type notmuch_message_get_header = unsafe extern "C" fn(
/// } /// }
/// ///
/// notmuch_message_destroy (message); /// notmuch_message_destroy (message);
///``` /// ```
/// ///
/// Note that there's no explicit destructor needed for the /// Note that there's no explicit destructor needed for the
/// notmuch_tags_t object. (For consistency, we do provide a /// notmuch_tags_t object. (For consistency, we do provide a
@ -1423,7 +1420,6 @@ pub type notmuch_message_maildir_flags_to_tags =
/// return TRUE if any filename of 'message' has maildir flag 'flag', /// return TRUE if any filename of 'message' has maildir flag 'flag',
/// FALSE otherwise. /// FALSE otherwise.
///
pub type notmuch_message_has_maildir_flag = unsafe extern "C" fn( pub type notmuch_message_has_maildir_flag = unsafe extern "C" fn(
message: *mut notmuch_message_t, message: *mut notmuch_message_t,
flag: ::std::os::raw::c_char, flag: ::std::os::raw::c_char,
@ -1673,7 +1669,7 @@ extern "C" {
/// } /// }
/// ///
/// notmuch_message_properties_destroy (list); /// notmuch_message_properties_destroy (list);
///``` /// ```
/// ///
/// Note that there's no explicit destructor needed for the /// Note that there's no explicit destructor needed for the
/// notmuch_message_properties_t object. (For consistency, we do /// notmuch_message_properties_t object. (For consistency, we do
@ -1689,7 +1685,8 @@ extern "C" {
exact: notmuch_bool_t, exact: notmuch_bool_t,
) -> *mut notmuch_message_properties_t; ) -> *mut notmuch_message_properties_t;
} }
/// Return the number of properties named "key" belonging to the specific message. /// Return the number of properties named "key" belonging to the specific
/// message.
/// ///
/// ```text /// ```text
/// @param[in] message The message to examine /// @param[in] message The message to examine
@ -1970,7 +1967,8 @@ pub type notmuch_database_get_config_list = unsafe extern "C" fn(
out: *mut *mut notmuch_config_list_t, out: *mut *mut notmuch_config_list_t,
) -> notmuch_status_t; ) -> notmuch_status_t;
/// Is 'config_list' iterator valid (i.e. _key, _value, _move_to_next can be called). /// Is 'config_list' iterator valid (i.e. _key, _value, _move_to_next can be
/// called).
/// ///
/// ```text /// ```text
/// @since libnotmuch 4.4 (notmuch 0.23) /// @since libnotmuch 4.4 (notmuch 0.23)

View File

@ -3,23 +3,23 @@
* *
* Copyright (c) 2021 Ilya Medvedev * Copyright (c) 2021 Ilya Medvedev
* *
* Permission is hereby granted, free of charge, to any person obtaining a copy * Permission is hereby granted, free of charge, to any person obtaining a
* of this software and associated documentation files (the "Software"), to deal * copy of this software and associated documentation files (the "Software"),
* in the Software without restriction, including without limitation the rights * to deal in the Software without restriction, including without limitation
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell * the rights to use, copy, modify, merge, publish, distribute, sublicense,
* copies of the Software, and to permit persons to whom the Software is * and/or sell copies of the Software, and to permit persons to whom the
* furnished to do so, subject to the following conditions: * Software is furnished to do so, subject to the following conditions:
* *
* The above copyright notice and this permission notice shall be included in all * The above copyright notice and this permission notice shall be included in
* copies or substantial portions of the Software. * all copies or substantial portions of the Software.
* *
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
* SOFTWARE. * DEALINGS IN THE SOFTWARE.
*/ */
/* Code from <https://github.com/iam-medvedev/rust-utf7-imap> */ /* Code from <https://github.com/iam-medvedev/rust-utf7-imap> */
@ -111,7 +111,7 @@ fn encode_modified_utf7(text: &str) -> String {
/// <https://datatracker.ietf.org/doc/html/rfc3501#section-5.1.3> /// <https://datatracker.ietf.org/doc/html/rfc3501#section-5.1.3>
pub fn decode_utf7_imap(text: &str) -> String { pub fn decode_utf7_imap(text: &str) -> String {
let pattern = Regex::new(r"&([^-]*)-").unwrap(); let pattern = Regex::new(r"&([^-]*)-").unwrap();
pattern.replace_all(&text, expand).to_string() pattern.replace_all(text, expand).to_string()
} }
fn expand(cap: &Captures) -> String { fn expand(cap: &Captures) -> String {

View File

@ -19,13 +19,16 @@
* along with meli. If not, see <http://www.gnu.org/licenses/>. * along with meli. If not, see <http://www.gnu.org/licenses/>.
*/ */
use std::{
collections::{BTreeMap, HashMap, HashSet},
ops::{Deref, DerefMut},
sync::{Arc, RwLock, RwLockReadGuard, RwLockWriteGuard},
};
use smallvec::SmallVec;
use super::*; use super::*;
use crate::backends::{MailboxHash, TagHash}; use crate::backends::{MailboxHash, TagHash};
use smallvec::SmallVec;
use std::ops::{Deref, DerefMut};
use std::sync::{Arc, RwLock, RwLockReadGuard, RwLockWriteGuard};
use std::collections::{BTreeMap, HashMap, HashSet};
pub type EnvelopeRef<'g> = RwRef<'g, EnvelopeHash, Envelope>; pub type EnvelopeRef<'g> = RwRef<'g, EnvelopeHash, Envelope>;
pub type EnvelopeRefMut<'g> = RwRefMut<'g, EnvelopeHash, Envelope>; pub type EnvelopeRefMut<'g> = RwRefMut<'g, EnvelopeHash, Envelope>;

View File

@ -19,13 +19,18 @@
* 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) //! Basic mail account configuration to use with
use crate::backends::SpecialUsageMailbox; //! [`backends`](./backends/index.html)
use crate::error::{Error, Result};
pub use crate::{SortField, SortOrder};
use serde::{Deserialize, Deserializer, Serialize, Serializer};
use std::collections::HashMap; use std::collections::HashMap;
use serde::{Deserialize, Deserializer, Serialize, Serializer};
use crate::{
backends::SpecialUsageMailbox,
error::{Error, Result},
};
pub use crate::{SortField, SortOrder};
#[derive(Debug, Serialize, Default, Clone)] #[derive(Debug, Serialize, Default, Clone)]
pub struct AccountSettings { pub struct AccountSettings {
pub name: String, pub name: String,
@ -87,7 +92,7 @@ impl AccountSettings {
pub fn server_password(&self) -> Result<String> { pub fn server_password(&self) -> Result<String> {
if let Some(cmd) = self.extra.get("server_password_command") { if let Some(cmd) = self.extra.get("server_password_command") {
let output = std::process::Command::new("sh") let output = std::process::Command::new("sh")
.args(&["-c", cmd]) .args(["-c", cmd])
.stdin(std::process::Stdio::piped()) .stdin(std::process::Stdio::piped())
.stdout(std::process::Stdio::piped()) .stdout(std::process::Stdio::piped())
.stderr(std::process::Stdio::piped()) .stderr(std::process::Stdio::piped())
@ -107,7 +112,10 @@ impl AccountSettings {
} else if let Some(pass) = self.extra.get("server_password") { } else if let Some(pass) = self.extra.get("server_password") {
Ok(pass.to_owned()) Ok(pass.to_owned())
} else { } else {
Err(Error::new(format!("Configuration error: connection requires either server_password or server_password_command"))) Err(Error::new(format!(
"Configuration error: connection requires either server_password or \
server_password_command"
)))
} }
} }
} }

View File

@ -20,6 +20,8 @@
*/ */
//! Connections layers (TCP/fd/TLS/Deflate) to use with remote backends. //! Connections layers (TCP/fd/TLS/Deflate) to use with remote backends.
use std::{os::unix::io::AsRawFd, time::Duration};
#[cfg(feature = "deflate_compression")] #[cfg(feature = "deflate_compression")]
use flate2::{read::DeflateDecoder, write::DeflateEncoder, Compression}; use flate2::{read::DeflateDecoder, write::DeflateEncoder, Compression};
#[cfg(any(target_os = "openbsd", target_os = "netbsd", target_os = "haiku"))] #[cfg(any(target_os = "openbsd", target_os = "netbsd", target_os = "haiku"))]
@ -35,8 +37,6 @@ use libc::TCP_KEEPALIVE as KEEPALIVE_OPTION;
)))] )))]
use libc::TCP_KEEPIDLE as KEEPALIVE_OPTION; use libc::TCP_KEEPIDLE as KEEPALIVE_OPTION;
use libc::{self, c_int, c_void}; use libc::{self, c_int, c_void};
use std::os::unix::io::AsRawFd;
use std::time::Duration;
#[derive(Debug)] #[derive(Debug)]
pub enum Connection { pub enum Connection {

View File

@ -37,11 +37,14 @@
//! let s = timestamp_to_string(timestamp, Some("%Y-%m-%d"), true); //! let s = timestamp_to_string(timestamp, Some("%Y-%m-%d"), true);
//! assert_eq!(s, "2020-01-08"); //! assert_eq!(s, "2020-01-08");
//! ``` //! ```
use std::{
borrow::Cow,
convert::TryInto,
ffi::{CStr, CString},
os::raw::c_int,
};
use crate::error::{Result, ResultIntoError}; use crate::error::{Result, ResultIntoError};
use std::borrow::Cow;
use std::convert::TryInto;
use std::ffi::{CStr, CString};
use std::os::raw::c_int;
pub type UnixTimestamp = u64; pub type UnixTimestamp = u64;
pub const RFC3339_FMT_WITH_TIME: &str = "%Y-%m-%dT%H:%M:%S\0"; pub const RFC3339_FMT_WITH_TIME: &str = "%Y-%m-%dT%H:%M:%S\0";
@ -122,7 +125,8 @@ impl Drop for Locale {
} }
} }
// How to unit test this? Test machine is not guaranteed to have non-english locales. // How to unit test this? Test machine is not guaranteed to have non-english
// locales.
impl Locale { impl Locale {
#[cfg(not(target_os = "netbsd"))] #[cfg(not(target_os = "netbsd"))]
fn new( fn new(

View File

@ -24,8 +24,9 @@
* *
* # Parsing bytes into an `Envelope` * # Parsing bytes into an `Envelope`
* *
* An [`Envelope`](Envelope) represents the information you can get from an email's headers and body * An [`Envelope`](Envelope) represents the information you can get from an
* structure. Addresses in `To`, `From` fields etc are parsed into [`Address`](crate::email::Address) types. * email's headers and body structure. Addresses in `To`, `From` fields etc
* are parsed into [`Address`](crate::email::Address) types.
* *
* ``` * ```
* use melib::{Attachment, Envelope}; * use melib::{Attachment, Envelope};
@ -75,7 +76,10 @@
* *
* let envelope = Envelope::from_bytes(raw_mail.as_bytes(), None).expect("Could not parse mail"); * let envelope = Envelope::from_bytes(raw_mail.as_bytes(), None).expect("Could not parse mail");
* assert_eq!(envelope.subject().as_ref(), "gratuitously encoded subject"); * assert_eq!(envelope.subject().as_ref(), "gratuitously encoded subject");
* assert_eq!(envelope.message_id_display().as_ref(), "<h2g7f.z0gy2pgaen5m@example.com>"); * assert_eq!(
* envelope.message_id_display().as_ref(),
* "<h2g7f.z0gy2pgaen5m@example.com>"
* );
* *
* let body = envelope.body_bytes(raw_mail.as_bytes()); * let body = envelope.body_bytes(raw_mail.as_bytes());
* assert_eq!(body.content_type().to_string().as_str(), "multipart/mixed"); * assert_eq!(body.content_type().to_string().as_str(), "multipart/mixed");
@ -85,7 +89,10 @@
* *
* let subattachments: Vec<Attachment> = body.attachments(); * let subattachments: Vec<Attachment> = body.attachments();
* assert_eq!(subattachments.len(), 3); * assert_eq!(subattachments.len(), 3);
* assert_eq!(subattachments[2].content_type().name().unwrap(), "test_image.gif"); * assert_eq!(
* subattachments[2].content_type().name().unwrap(),
* "test_image.gif"
* );
* ``` * ```
*/ */
@ -99,22 +106,22 @@ pub mod mailto;
pub mod parser; pub mod parser;
pub mod pgp; pub mod pgp;
use std::{borrow::Cow, convert::TryInto, ops::Deref};
pub use address::{Address, MessageID, References, StrBuild, StrBuilder}; pub use address::{Address, MessageID, References, StrBuild, StrBuilder};
pub use attachments::{Attachment, AttachmentBuilder}; pub use attachments::{Attachment, AttachmentBuilder};
pub use compose::{attachment_from_file, Draft}; pub use compose::{attachment_from_file, Draft};
pub use headers::*; pub use headers::*;
pub use mailto::*; pub use mailto::*;
use crate::datetime::UnixTimestamp;
use crate::error::{Error, Result};
use crate::parser::BytesExt;
use crate::thread::ThreadNodeHash;
use crate::TagHash;
use smallvec::SmallVec; use smallvec::SmallVec;
use std::borrow::Cow;
use std::convert::TryInto; use crate::{
use std::ops::Deref; datetime::UnixTimestamp,
error::{Error, Result},
parser::BytesExt,
thread::ThreadNodeHash,
TagHash,
};
bitflags! { bitflags! {
#[derive(Default, Serialize, Deserialize)] #[derive(Default, Serialize, Deserialize)]
@ -159,9 +166,10 @@ impl Flag {
flag_impl!(fn is_flagged, Flag::FLAGGED); flag_impl!(fn is_flagged, Flag::FLAGGED);
} }
///`Mail` holds both the envelope info of an email in its `envelope` field and the raw bytes that ///`Mail` holds both the envelope info of an email in its `envelope` field and
///describe the email in `bytes`. Its body as an `melib::email::Attachment` can be parsed on demand /// the raw bytes that describe the email in `bytes`. Its body as an
///with the `melib::email::Mail::body` method. /// `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 Mail { pub struct Mail {
pub envelope: Envelope, pub envelope: Envelope,
@ -199,12 +207,13 @@ impl Mail {
crate::declare_u64_hash!(EnvelopeHash); crate::declare_u64_hash!(EnvelopeHash);
/// `Envelope` represents all the header and structure 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` method. /// Attachments (the email's body) is parsed on demand with `body` method.
/// ///
///To access the email attachments, you need to parse them from the raw email bytes into an ///To access the email attachments, you need to parse them from the raw email
///`Attachment` object. /// bytes into an `Attachment` object.
#[derive(Clone, Serialize, Deserialize)] #[derive(Clone, Serialize, Deserialize)]
pub struct Envelope { pub struct Envelope {
pub hash: EnvelopeHash, pub hash: EnvelopeHash,
@ -364,7 +373,11 @@ impl Envelope {
self.has_attachments = self.has_attachments =
Attachment::check_if_has_attachments_quick(body, boundary); Attachment::check_if_has_attachments_quick(body, boundary);
} else { } else {
debug!("{:?} has no boundary field set in multipart/mixed content-type field.", &self); debug!(
"{:?} has no boundary field set in multipart/mixed content-type \
field.",
&self
);
} }
} }
_ => {} _ => {}

View File

@ -19,11 +19,15 @@
* 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). //! Email addresses. Parsing functions are in
//! [melib::email::parser::address](../parser/address/index.html).
use std::{
collections::HashSet,
convert::TryFrom,
hash::{Hash, Hasher},
};
use super::*; use super::*;
use std::collections::HashSet;
use std::convert::TryFrom;
use std::hash::{Hash, Hasher};
#[derive(Clone, Debug, Serialize, Deserialize)] #[derive(Clone, Debug, Serialize, Deserialize)]
pub struct GroupAddress { pub struct GroupAddress {
@ -53,7 +57,7 @@ pub struct GroupAddress {
* > display_name * > display_name
* > * >
* > address_spec * > address_spec
*``` * ```
*/ */
pub struct MailboxAddress { pub struct MailboxAddress {
pub raw: Vec<u8>, pub raw: Vec<u8>,
@ -78,14 +82,20 @@ impl PartialEq for MailboxAddress {
/// ///
/// ```rust /// ```rust
/// # use melib::email::Address; /// # use melib::email::Address;
/// let addr = Address::new(Some("Jörg Doe".to_string()), "joerg@example.com".to_string()); /// 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>"); /// assert_eq!(addr.to_string().as_str(), "Jörg Doe <joerg@example.com>");
/// ``` /// ```
/// ///
/// or parse it from a raw value: /// or parse it from a raw value:
/// ///
/// ```rust /// ```rust
/// let (rest_bytes, addr) = melib::email::parser::address::address("=?utf-8?q?J=C3=B6rg_Doe?= <joerg@example.com>".as_bytes()).unwrap(); /// 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!(rest_bytes.is_empty());
/// assert_eq!(addr.get_display_name(), Some("Jörg Doe".to_string())); /// assert_eq!(addr.get_display_name(), Some("Jörg Doe".to_string()));
/// assert_eq!(addr.get_email(), "joerg@example.com".to_string()); /// assert_eq!(addr.get_email(), "joerg@example.com".to_string());
@ -154,8 +164,8 @@ impl Address {
/// Get the display name of this address. /// 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 /// If it's a group, it's the name of the group. Otherwise it's the
/// the mailbox: /// `display_name` part of the mailbox:
/// ///
/// ///
/// ```text /// ```text
@ -166,7 +176,7 @@ impl Address {
/// display_name │ display_name │ /// display_name │ display_name │
/// │ │ /// │ │
/// address_spec address_spec /// address_spec address_spec
///``` /// ```
pub fn get_display_name(&self) -> Option<String> { pub fn get_display_name(&self) -> Option<String> {
let ret = match self { let ret = match self {
Address::Mailbox(m) => m.display_name.display(&m.raw), Address::Mailbox(m) => m.display_name.display(&m.raw),
@ -179,7 +189,8 @@ impl Address {
} }
} }
/// Get the address spec part of this address. A group returns an empty `String`. /// 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),
@ -238,8 +249,8 @@ impl Address {
/// Get subaddress out of an address (e.g. `ken+subaddress@example.org`). /// Get subaddress out of an address (e.g. `ken+subaddress@example.org`).
/// ///
/// Subaddresses are commonly text following a "+" character in an email address's local part /// Subaddresses are commonly text following a "+" character in an email
/// . They are defined in [RFC5233 `Sieve Email Filtering: Subaddress Extension`](https://tools.ietf.org/html/rfc5233.html) /// address's local part . They are defined in [RFC5233 `Sieve Email Filtering: Subaddress Extension`](https://tools.ietf.org/html/rfc5233.html)
/// ///
/// # Examples /// # Examples
/// ///

View File

@ -18,11 +18,15 @@
* 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::attachments::{Attachment, AttachmentBuilder}; use std::{
use crate::email::parser::BytesExt; fmt::{Display, Formatter, Result as FmtResult},
str,
};
use std::fmt::{Display, Formatter, Result as FmtResult}; use crate::email::{
use std::str; attachments::{Attachment, AttachmentBuilder},
parser::BytesExt,
};
#[derive(Clone, Copy, Debug, PartialEq, Eq, Serialize, Deserialize)] #[derive(Clone, Copy, Debug, PartialEq, Eq, Serialize, Deserialize)]
pub enum Charset { pub enum Charset {
@ -421,9 +425,10 @@ impl ContentType {
boundary.push_str(&random_boundary); boundary.push_str(&random_boundary);
/* rfc134 /* rfc134
* "The only mandatory parameter for the multipart Content-Type is the boundary parameter, * "The only mandatory parameter for the multipart Content-Type is the
* which consists of 1 to 70 characters from a set of characters known to be very robust * boundary parameter, which consists of 1 to 70 characters from a
* through email gateways, and NOT ending with white space"*/ * set of characters known to be very robust through email gateways,
* and NOT ending with white space" */
boundary.truncate(70); boundary.truncate(70);
boundary boundary
} }

View File

@ -20,17 +20,17 @@
*/ */
/*! Encoding/decoding of attachments */ /*! Encoding/decoding of attachments */
use crate::email::{ use core::{fmt, str};
address::StrBuilder,
parser::{self, BytesExt},
Mail,
};
use core::fmt;
use core::str;
use data_encoding::BASE64_MIME; use data_encoding::BASE64_MIME;
use smallvec::SmallVec; use smallvec::SmallVec;
use crate::email::attachment_types::*; use crate::email::{
address::StrBuilder,
attachment_types::*,
parser::{self, BytesExt},
Mail,
};
pub type Filter<'a> = Box<dyn FnMut(&Attachment, &mut Vec<u8>) + 'a>; pub type Filter<'a> = Box<dyn FnMut(&Attachment, &mut Vec<u8>) + 'a>;
@ -117,8 +117,9 @@ impl AttachmentBuilder {
self self
} }
/// Set body to the entire raw contents, use this if raw contains only data and no headers /// Set body to the entire raw contents, use this if raw contains only data
/// If raw contains data and headers pass it through AttachmentBuilder::new(). /// and no headers If raw contains data and headers pass it through
/// AttachmentBuilder::new().
pub fn set_body_to_raw(&mut self) -> &mut Self { pub fn set_body_to_raw(&mut self) -> &mut Self {
self.body = StrBuilder { self.body = StrBuilder {
offset: 0, offset: 0,
@ -515,8 +516,8 @@ impl Attachment {
} }
} }
/* Call on the body of a multipart/mixed Envelope to check if there are attachments without /* Call on the body of a multipart/mixed Envelope to check if there are
* completely parsing them */ * attachments without completely parsing them */
pub fn check_if_has_attachments_quick(bytes: &[u8], boundary: &[u8]) -> bool { pub fn check_if_has_attachments_quick(bytes: &[u8], boundary: &[u8]) -> bool {
if bytes.is_empty() { if bytes.is_empty() {
return false; return false;

View File

@ -20,19 +20,25 @@
*/ */
/*! Compose a `Draft`, with MIME and attachment support */ /*! Compose a `Draft`, with MIME and attachment support */
use super::*; use std::{
use crate::email::attachment_types::{ ffi::OsStr,
Charset, ContentTransferEncoding, ContentType, MultipartType, io::Read,
path::{Path, PathBuf},
str::FromStr,
}; };
use crate::email::attachments::AttachmentBuilder;
use crate::shellexpand::ShellExpandTrait;
use data_encoding::BASE64_MIME; use data_encoding::BASE64_MIME;
use std::ffi::OsStr;
use std::io::Read;
use std::path::{Path, PathBuf};
use std::str::FromStr;
use xdg_utils::query_mime_info; use xdg_utils::query_mime_info;
use super::*;
use crate::{
email::{
attachment_types::{Charset, ContentTransferEncoding, ContentType, MultipartType},
attachments::AttachmentBuilder,
},
shellexpand::ShellExpandTrait,
};
pub mod mime; pub mod mime;
pub mod random; pub mod random;
@ -370,7 +376,10 @@ fn build_multipart(
} }
ret.push_str("\r\n\r\n"); ret.push_str("\r\n\r\n");
/* rfc1341 */ /* rfc1341 */
ret.push_str("This is a MIME formatted message with attachments. Use a MIME-compliant client to view it properly.\r\n"); ret.push_str(
"This is a MIME formatted message with attachments. Use a MIME-compliant client to view \
it properly.\r\n",
);
for sub in parts { for sub in parts {
ret.push_str("--"); ret.push_str("--");
ret.push_str(&boundary); ret.push_str(&boundary);
@ -484,9 +493,10 @@ fn print_attachment(ret: &mut String, a: AttachmentBuilder) {
#[cfg(test)] #[cfg(test)]
mod tests { mod tests {
use super::*;
use std::str::FromStr; use std::str::FromStr;
use super::*;
#[test] #[test]
fn test_new_draft() { fn test_new_draft() {
let mut default = Draft::default(); let mut default = Draft::default();
@ -508,21 +518,33 @@ mod tests {
let original = default.clone(); let original = default.clone();
let s = default.to_edit_string(); let s = default.to_edit_string();
assert_eq!(s, "<!--\nDate: Sun, 16 Jun 2013 17:56:45 +0200\nFrom: \nTo: \nCc: \nBcc: \nSubject: test_update()\n-->\n\nαδφαφσαφασ"); assert_eq!(
s,
"<!--\nDate: Sun, 16 Jun 2013 17:56:45 +0200\nFrom: \nTo: \nCc: \nBcc: \nSubject: \
test_update()\n-->\n\nαδφαφσαφασ"
);
assert!(!default.update(&s).unwrap()); assert!(!default.update(&s).unwrap());
assert_eq!(&original, &default); assert_eq!(&original, &default);
default.set_wrap_header_preamble(Some(("".to_string(), "".to_string()))); default.set_wrap_header_preamble(Some(("".to_string(), "".to_string())));
let original = default.clone(); let original = default.clone();
let s = default.to_edit_string(); let s = default.to_edit_string();
assert_eq!(s, "Date: Sun, 16 Jun 2013 17:56:45 +0200\nFrom: \nTo: \nCc: \nBcc: \nSubject: test_update()\n\nαδφαφσαφασ"); assert_eq!(
s,
"Date: Sun, 16 Jun 2013 17:56:45 +0200\nFrom: \nTo: \nCc: \nBcc: \nSubject: \
test_update()\n\nαδφαφσαφασ"
);
assert!(!default.update(&s).unwrap()); assert!(!default.update(&s).unwrap());
assert_eq!(&original, &default); assert_eq!(&original, &default);
default.set_wrap_header_preamble(None); default.set_wrap_header_preamble(None);
let original = default.clone(); let original = default.clone();
let s = default.to_edit_string(); let s = default.to_edit_string();
assert_eq!(s, "Date: Sun, 16 Jun 2013 17:56:45 +0200\nFrom: \nTo: \nCc: \nBcc: \nSubject: test_update()\n\nαδφαφσαφασ"); assert_eq!(
s,
"Date: Sun, 16 Jun 2013 17:56:45 +0200\nFrom: \nTo: \nCc: \nBcc: \nSubject: \
test_update()\n\nαδφαφσαφασ"
);
assert!(!default.update(&s).unwrap()); assert!(!default.update(&s).unwrap());
assert_eq!(&original, &default); assert_eq!(&original, &default);
@ -532,7 +554,11 @@ mod tests {
))); )));
let original = default.clone(); let original = default.clone();
let s = default.to_edit_string(); let s = default.to_edit_string();
assert_eq!(s, "{-\n\n\n===========\nDate: Sun, 16 Jun 2013 17:56:45 +0200\nFrom: \nTo: \nCc: \nBcc: \nSubject: test_update()\n</mixed>\n\nαδφαφσαφασ"); assert_eq!(
s,
"{-\n\n\n===========\nDate: Sun, 16 Jun 2013 17:56:45 +0200\nFrom: \nTo: \nCc: \nBcc: \
\nSubject: test_update()\n</mixed>\n\nαδφαφσαφασ"
);
assert!(!default.update(&s).unwrap()); assert!(!default.update(&s).unwrap());
assert_eq!(&original, &default); assert_eq!(&original, &default);
@ -543,7 +569,11 @@ mod tests {
.set_wrap_header_preamble(Some(("<!--".to_string(), "-->".to_string()))); .set_wrap_header_preamble(Some(("<!--".to_string(), "-->".to_string())));
let original = default.clone(); let original = default.clone();
let s = default.to_edit_string(); let s = default.to_edit_string();
assert_eq!(s, "<!--\nDate: Sun, 16 Jun 2013 17:56:45 +0200\nFrom: \nTo: \nCc: \nBcc: \nSubject: test_update()\n-->\n\nhellohello<!--\n<!--\n<--hellohello\nhellohello-->\n-->\n-->hello\n"); assert_eq!(
s,
"<!--\nDate: Sun, 16 Jun 2013 17:56:45 +0200\nFrom: \nTo: \nCc: \nBcc: \nSubject: \
test_update()\n-->\n\nhellohello<!--\n<!--\n<--hellohello\nhellohello-->\n-->\n-->hello\n"
);
assert!(!default.update(&s).unwrap()); assert!(!default.update(&s).unwrap());
assert_eq!(&original, &default); assert_eq!(&original, &default);
} }
@ -572,7 +602,8 @@ mod tests {
*/ */
} }
/// Reads file from given path, and returns an 'application/octet-stream' AttachmentBuilder object /// Reads file from given path, and returns an 'application/octet-stream'
/// AttachmentBuilder object
pub fn attachment_from_file<I>(path: &I) -> Result<AttachmentBuilder> pub fn attachment_from_file<I>(path: &I) -> Result<AttachmentBuilder>
where where
I: AsRef<OsStr>, I: AsRef<OsStr>,

View File

@ -20,7 +20,6 @@
*/ */
use super::*; use super::*;
#[cfg(feature = "unicode_algorithms")] #[cfg(feature = "unicode_algorithms")]
use crate::text_processing::grapheme_clusters::TextProcessing; use crate::text_processing::grapheme_clusters::TextProcessing;
@ -61,9 +60,9 @@ pub fn encode_header(value: &str) -> String {
is_current_window_ascii = false; is_current_window_ascii = false;
} }
/* RFC2047 recommends: /* RFC2047 recommends:
* 'While there is no limit to the length of a multiple-line header field, each line of * 'While there is no limit to the length of a multiple-line header field, each
* a header field that contains one or more 'encoded-word's is limited to 76 * line of a header field that contains one or more
* characters.' * 'encoded-word's is limited to 76 characters.'
* This is a rough compliance. * This is a rough compliance.
*/ */
(false, false) if (((4 * (idx - current_window_start) / 3) + 3) & !3) > 33 => { (false, false) if (((4 * (idx - current_window_start) / 3) + 3) & !3) > 33 => {
@ -84,8 +83,8 @@ pub fn encode_header(value: &str) -> String {
} }
#[cfg(not(feature = "unicode_algorithms"))] #[cfg(not(feature = "unicode_algorithms"))]
{ {
/* TODO: test this. If it works as fine as the one above, there's no need to keep the above /* TODO: test this. If it works as fine as the one above, there's no need to
* implementation.*/ * keep the above implementation. */
for (i, g) in value.char_indices() { for (i, g) in value.char_indices() {
match (g.is_ascii(), is_current_window_ascii) { match (g.is_ascii(), is_current_window_ascii) {
(true, true) => { (true, true) => {
@ -116,9 +115,9 @@ pub fn encode_header(value: &str) -> String {
is_current_window_ascii = false; is_current_window_ascii = false;
} }
/* RFC2047 recommends: /* RFC2047 recommends:
* 'While there is no limit to the length of a multiple-line header field, each line of * 'While there is no limit to the length of a multiple-line header field, each
* a header field that contains one or more 'encoded-word's is limited to 76 * line of a header field that contains one or more
* characters.' * 'encoded-word's is limited to 76 characters.'
* This is a rough compliance. * This is a rough compliance.
*/ */
(false, false) (false, false)
@ -139,8 +138,8 @@ pub fn encode_header(value: &str) -> String {
} }
} }
} }
/* If the last part of the header value is encoded, it won't be pushed inside the previous for /* If the last part of the header value is encoded, it won't be pushed inside
* block */ * the previous for block */
if !is_current_window_ascii { if !is_current_window_ascii {
ret.push_str(&format!( ret.push_str(&format!(
"=?UTF-8?B?{}?=", "=?UTF-8?B?{}?=",
@ -156,35 +155,39 @@ fn test_encode_header() {
let words = "compilers/2020a σε Rust"; let words = "compilers/2020a σε Rust";
assert_eq!( assert_eq!(
"compilers/2020a =?UTF-8?B?z4POtSA=?=Rust", "compilers/2020a =?UTF-8?B?z4POtSA=?=Rust",
&encode_header(&words), &encode_header(words),
); );
assert_eq!( assert_eq!(
&std::str::from_utf8( &std::str::from_utf8(
&crate::email::parser::encodings::phrase(encode_header(&words).as_bytes(), false) &crate::email::parser::encodings::phrase(encode_header(words).as_bytes(), false)
.unwrap() .unwrap()
.1 .1
) )
.unwrap(), .unwrap(),
&words, &words,
); );
let words = "[internal] =?UTF-8?B?zp3Orc6/z4Igzp/OtM63zrPPjM+CIM6jz4U=?= =?UTF-8?B?zrPOs8+BzrHPhs6uz4I=?="; let words = "[internal] =?UTF-8?B?zp3Orc6/z4Igzp/OtM63zrPPjM+CIM6jz4U=?= \
=?UTF-8?B?zrPOs8+BzrHPhs6uz4I=?=";
let words_enc = r#"[internal] Νέος Οδηγός Συγγραφής"#; let words_enc = r#"[internal] Νέος Οδηγός Συγγραφής"#;
assert_eq!(words, &encode_header(&words_enc),); assert_eq!(words, &encode_header(words_enc),);
assert_eq!( assert_eq!(
r#"[internal] Νέος Οδηγός Συγγραφής"#, r#"[internal] Νέος Οδηγός Συγγραφής"#,
std::str::from_utf8( std::str::from_utf8(
&crate::email::parser::encodings::phrase(encode_header(&words_enc).as_bytes(), false) &crate::email::parser::encodings::phrase(encode_header(words_enc).as_bytes(), false)
.unwrap() .unwrap()
.1 .1
) )
.unwrap(), .unwrap(),
); );
//let words = "[Advcomparch] =?utf-8?b?zqPPhc68z4DOtc+BzrnPhs6/z4HOrCDPg861IGZs?=\n\t=?utf-8?b?dXNoIM67z4zOs8+JIG1pc3ByZWRpY3Rpb24gzrrOsc+Ezqwgz4TOt869?=\n\t=?utf-8?b?IM61zrrPhM6tzrvOtc+Dzrcgc3RvcmU=?="; //let words = "[Advcomparch]
// =?utf-8?b?zqPPhc68z4DOtc+BzrnPhs6/z4HOrCDPg861IGZs?=\n\t=?utf-8?b?
// dXNoIM67z4zOs8+JIG1pc3ByZWRpY3Rpb24gzrrOsc+Ezqwgz4TOt869?=\n\t=?utf-8?b?
// IM61zrrPhM6tzrvOtc+Dzrcgc3RvcmU=?=";
let words_enc = "[Advcomparch] Συμπεριφορά σε flush λόγω misprediction κατά την εκτέλεση store"; let words_enc = "[Advcomparch] Συμπεριφορά σε flush λόγω misprediction κατά την εκτέλεση store";
assert_eq!( assert_eq!(
"[Advcomparch] Συμπεριφορά σε flush λόγω misprediction κατά την εκτέλεση store", "[Advcomparch] Συμπεριφορά σε flush λόγω misprediction κατά την εκτέλεση store",
std::str::from_utf8( std::str::from_utf8(
&crate::email::parser::encodings::phrase(encode_header(&words_enc).as_bytes(), false) &crate::email::parser::encodings::phrase(encode_header(words_enc).as_bytes(), false)
.unwrap() .unwrap()
.1 .1
) )

View File

@ -19,10 +19,7 @@
* along with meli. If not, see <http://www.gnu.org/licenses/>. * along with meli. If not, see <http://www.gnu.org/licenses/>.
*/ */
use std::char; use std::{char, fs::File, io::prelude::*, time::SystemTime};
use std::fs::File;
use std::io::prelude::*;
use std::time::SystemTime;
fn random_u64() -> u64 { fn random_u64() -> u64 {
let mut f = File::open("/dev/urandom").unwrap(); let mut f = File::open("/dev/urandom").unwrap();

View File

@ -20,20 +20,25 @@
*/ */
/*! Wrapper type `HeaderName` for case-insensitive comparisons */ /*! Wrapper type `HeaderName` for case-insensitive comparisons */
use crate::error::Error; use std::{
borrow::Borrow,
cmp::{Eq, PartialEq},
convert::TryFrom,
fmt,
hash::{Hash, Hasher},
ops::{Deref, DerefMut},
};
use indexmap::IndexMap; use indexmap::IndexMap;
use smallvec::SmallVec; use smallvec::SmallVec;
use std::borrow::Borrow;
use std::cmp::{Eq, PartialEq}; use crate::error::Error;
use std::convert::TryFrom;
use std::fmt;
use std::hash::{Hash, Hasher};
use std::ops::{Deref, DerefMut};
#[derive(Clone, Copy, Serialize, Deserialize)] #[derive(Clone, Copy, Serialize, Deserialize)]
pub struct HeaderNameType<S>(S); pub struct HeaderNameType<S>(S);
///Case insensitive wrapper for a header name. As of `RFC5322` it's guaranteened to be ASCII. /// Case insensitive wrapper for a header name. As of `RFC5322` it's
/// guaranteed to be ASCII.
pub type HeaderName = HeaderNameType<SmallVec<[u8; 32]>>; pub type HeaderName = HeaderNameType<SmallVec<[u8; 32]>>;
impl HeaderName { impl HeaderName {
@ -148,7 +153,7 @@ impl<'a> Borrow<dyn HeaderKey + 'a> for HeaderName {
impl<S: AsRef<[u8]>> HeaderNameType<S> { impl<S: AsRef<[u8]>> HeaderNameType<S> {
pub fn as_str(&self) -> &str { pub fn as_str(&self) -> &str {
//HeadersType are ascii so valid utf8 // HeadersType are ascii so valid utf8
unsafe { std::str::from_utf8_unchecked(self.0.as_ref()) } unsafe { std::str::from_utf8_unchecked(self.0.as_ref()) }
} }

View File

@ -20,11 +20,12 @@
*/ */
/*! Parsing of rfc2369/rfc2919 `List-*` headers */ /*! Parsing of rfc2369/rfc2919 `List-*` headers */
use super::parser;
use super::Envelope;
use smallvec::SmallVec;
use std::convert::From; use std::convert::From;
use smallvec::SmallVec;
use super::{parser, Envelope};
#[derive(Debug, PartialEq, Eq, Clone, Copy)] #[derive(Debug, PartialEq, Eq, Clone, Copy)]
pub enum ListAction<'a> { pub enum ListAction<'a> {
Url(&'a [u8]), Url(&'a [u8]),
@ -43,8 +44,8 @@ impl<'a> From<&'a [u8]> for ListAction<'a> {
} else if value.starts_with(b"NO") { } else if value.starts_with(b"NO") {
ListAction::No ListAction::No
} else { } else {
/* Otherwise treat it as url. There's no foolproof way to check if this is valid, so /* Otherwise treat it as url. There's no foolproof way to check if this is
* postpone it until we try an HTTP request. * valid, so postpone it until we try an HTTP request.
*/ */
ListAction::Url(value) ListAction::Url(value)
} }
@ -55,8 +56,8 @@ impl<'a> ListAction<'a> {
pub fn parse_options_list(input: &'a [u8]) -> Option<SmallVec<[ListAction<'a>; 4]>> { pub fn parse_options_list(input: &'a [u8]) -> Option<SmallVec<[ListAction<'a>; 4]>> {
parser::mailing_lists::rfc_2369_list_headers_action_list(input) parser::mailing_lists::rfc_2369_list_headers_action_list(input)
.map(|(_, mut vec)| { .map(|(_, mut vec)| {
/* Prefer email options first, since this _is_ a mail client after all and it's /* Prefer email options first, since this _is_ a mail client after all and
* more automated */ * it's more automated */
vec.sort_unstable_by(|a, b| { vec.sort_unstable_by(|a, b| {
match (a.starts_with(b"mailto:"), b.starts_with(b"mailto:")) { match (a.starts_with(b"mailto:"), b.starts_with(b"mailto:")) {
(true, false) => std::cmp::Ordering::Less, (true, false) => std::cmp::Ordering::Less,

View File

@ -20,9 +20,10 @@
*/ */
/*! Parsing of `mailto` addresses */ /*! Parsing of `mailto` addresses */
use super::*;
use std::convert::TryFrom; use std::convert::TryFrom;
use super::*;
#[derive(Debug, Clone)] #[derive(Debug, Clone)]
pub struct Mailto { pub struct Mailto {
pub address: Address, pub address: Address,

View File

@ -20,20 +20,21 @@
*/ */
/*! Parsers for email. See submodules */ /*! Parsers for email. See submodules */
use crate::error::{Error, Result, ResultIntoError}; use std::borrow::Cow;
use nom::{ use nom::{
branch::alt, branch::alt,
bytes::complete::{is_a, is_not, tag, take, take_until, take_while, take_while1}, bytes::complete::{is_a, is_not, tag, take, take_until, take_while, take_while1},
character::{is_alphabetic, is_digit, is_hex_digit}, character::{is_alphabetic, is_digit, is_hex_digit},
combinator::peek, combinator::{map, opt, peek},
combinator::{map, opt},
error::{context, ErrorKind}, error::{context, ErrorKind},
multi::{many0, many1, separated_list1}, multi::{many0, many1, separated_list1},
number::complete::le_u8, number::complete::le_u8,
sequence::{delimited, pair, preceded, separated_pair, terminated}, sequence::{delimited, pair, preceded, separated_pair, terminated},
}; };
use smallvec::SmallVec; use smallvec::SmallVec;
use std::borrow::Cow;
use crate::error::{Error, Result, ResultIntoError};
macro_rules! to_str { macro_rules! to_str {
($l:expr) => {{ ($l:expr) => {{
@ -318,8 +319,7 @@ pub fn mail(input: &[u8]) -> Result<(Vec<(&[u8], &[u8])>, &[u8])> {
pub mod dates { pub mod dates {
/*! Date values in headers */ /*! Date values in headers */
use super::generic::*; use super::{generic::*, *};
use super::*;
use crate::datetime::UnixTimestamp; use crate::datetime::UnixTimestamp;
fn take_n_digits(n: usize) -> impl Fn(&[u8]) -> IResult<&[u8], &[u8]> { fn take_n_digits(n: usize) -> impl Fn(&[u8]) -> IResult<&[u8], &[u8]> {
@ -451,15 +451,15 @@ pub mod dates {
///e.g Wed Sep 9 00:27:54 2020 ///e.g Wed Sep 9 00:27:54 2020
///```text ///```text
///day-of-week month day time year /// day-of-week month day time year
///date-time = [ day-of-week "," ] date time [CFWS] /// date-time = [ day-of-week "," ] date time [CFWS]
///date = day month year /// date = day month year
///time = time-of-day zone /// time = time-of-day zone
///time-of-day = hour ":" minute [ ":" second ] /// time-of-day = hour ":" minute [ ":" second ]
///hour = 2DIGIT / obs-hour /// hour = 2DIGIT / obs-hour
///minute = 2DIGIT / obs-minute /// minute = 2DIGIT / obs-minute
///second = 2DIGIT / obs-second /// second = 2DIGIT / obs-second
///``` /// ```
pub fn mbox_date_time(input: &[u8]) -> IResult<&[u8], UnixTimestamp> { pub fn mbox_date_time(input: &[u8]) -> IResult<&[u8], UnixTimestamp> {
let orig_input = input; let orig_input = input;
let mut accum: SmallVec<[u8; 32]> = SmallVec::new(); let mut accum: SmallVec<[u8; 32]> = SmallVec::new();
@ -656,7 +656,8 @@ pub mod generic {
let (rest, _) = utf8_tail(rest)?; let (rest, _) = utf8_tail(rest)?;
Ok((rest, &input[0..2])) Ok((rest, &input[0..2]))
} }
/// UTF8-3 = %xE0 %xA0-BF UTF8-tail / %xE1-EC 2( UTF8-tail ) / %xED %x80-9F UTF8-tail / %xEE-EF 2( UTF8-tail ) /// UTF8-3 = %xE0 %xA0-BF UTF8-tail / %xE1-EC 2( UTF8-tail ) / %xED
/// %x80-9F UTF8-tail / %xEE-EF 2( UTF8-tail )
fn utf8_3<'a>(input: &'a [u8]) -> IResult<&'a [u8], &'a [u8]> { fn utf8_3<'a>(input: &'a [u8]) -> IResult<&'a [u8], &'a [u8]> {
alt(( alt((
|input: &'a [u8]| -> IResult<&'a [u8], &'a [u8]> { |input: &'a [u8]| -> IResult<&'a [u8], &'a [u8]> {
@ -685,7 +686,8 @@ pub mod generic {
}, },
))(input) ))(input)
} }
/// UTF8-4 = %xF0 %x90-BF 2( UTF8-tail ) / %xF1-F3 3( UTF8-tail ) / %xF4 %x80-8F 2( UTF8-tail ) /// UTF8-4 = %xF0 %x90-BF 2( UTF8-tail ) / %xF1-F3 3( UTF8-tail ) /
/// %xF4 %x80-8F 2( UTF8-tail )
fn utf8_4<'a>(input: &'a [u8]) -> IResult<&'a [u8], &'a [u8]> { fn utf8_4<'a>(input: &'a [u8]) -> IResult<&'a [u8], &'a [u8]> {
alt(( alt((
|input: &'a [u8]| -> IResult<&'a [u8], &'a [u8]> { |input: &'a [u8]| -> IResult<&'a [u8], &'a [u8]> {
@ -741,11 +743,11 @@ pub mod generic {
} }
///```text ///```text
///ctext = %d33-39 / ; Printable US-ASCII /// ctext = %d33-39 / ; Printable US-ASCII
/// %d42-91 / ; characters not including /// %d42-91 / ; characters not including
/// %d93-126 / ; "(", ")", or "\" /// %d93-126 / ; "(", ")", or "\"
/// obs-ctext /// obs-ctext
///``` /// ```
fn ctext(input: &[u8]) -> IResult<&[u8], ()> { fn ctext(input: &[u8]) -> IResult<&[u8], ()> {
alt(( alt((
map( map(
@ -761,13 +763,13 @@ pub mod generic {
} }
///```text ///```text
///ctext = %d33-39 / ; Printable US-ASCII /// ctext = %d33-39 / ; Printable US-ASCII
/// %d42-91 / ; characters not including /// %d42-91 / ; characters not including
/// %d93-126 / ; "(", ")", or "\" /// %d93-126 / ; "(", ")", or "\"
/// obs-ctext /// obs-ctext
///ccontent = ctext / quoted-pair / comment /// ccontent = ctext / quoted-pair / comment
///comment = "(" *([FWS] ccontent) [FWS] ")" /// comment = "(" *([FWS] ccontent) [FWS] ")"
///``` /// ```
pub fn comment(input: &[u8]) -> IResult<&[u8], ()> { pub fn comment(input: &[u8]) -> IResult<&[u8], ()> {
if !input.starts_with(b"(") { if !input.starts_with(b"(") {
return Err(nom::Err::Error( return Err(nom::Err::Error(
@ -911,8 +913,7 @@ pub mod generic {
} }
} }
use crate::email::address::Address; use crate::email::{address::Address, mailto::Mailto};
use crate::email::mailto::Mailto;
pub fn mailto(mut input: &[u8]) -> IResult<&[u8], Mailto> { pub fn mailto(mut input: &[u8]) -> IResult<&[u8], Mailto> {
if !input.starts_with(b"mailto:") { if !input.starts_with(b"mailto:") {
return Err(nom::Err::Error( return Err(nom::Err::Error(
@ -1081,7 +1082,8 @@ pub mod generic {
Ok((rest, ret)) Ok((rest, ret))
} }
///`quoted-string = [CFWS] DQUOTE *([FWS] qcontent) [FWS] DQUOTE [CFWS]` ///`quoted-string = [CFWS] DQUOTE *([FWS] qcontent) [FWS] DQUOTE
/// [CFWS]`
pub fn quoted_string(input: &[u8]) -> IResult<&[u8], Cow<'_, [u8]>> { pub fn quoted_string(input: &[u8]) -> IResult<&[u8], Cow<'_, [u8]>> {
let (input, opt_space) = opt(cfws)(input)?; let (input, opt_space) = opt(cfws)(input)?;
if !input.starts_with(b"\"") { if !input.starts_with(b"\"") {
@ -1213,7 +1215,10 @@ pub mod generic {
Ok((input, ret.into())) Ok((input, ret.into()))
} }
///`atext = ALPHA / DIGIT / ; Printable US-ASCII "!" / "#" / ; characters not including "$" / "%" / ; specials. Used for atoms. "&" / "'" / "*" / "+" / "-" / "/" / "=" / "?" / "^" / "_" / "`" / "{" / "|" / "}" / "~"` ///`atext = ALPHA / DIGIT / ; Printable US-ASCII "!" / "#" /
/// ; characters not including "$" / "%" / ; specials. Used for
/// atoms. "&" / "'" / "*" / "+" / "-" / "/" / "=" / "?" / "^" / "_" / "`"
/// / "{" / "|" / "}" / "~"`
pub fn atext_ascii(input: &[u8]) -> IResult<&[u8], Cow<'_, [u8]>> { pub fn atext_ascii(input: &[u8]) -> IResult<&[u8], Cow<'_, [u8]>> {
if input.is_empty() { if input.is_empty() {
return Err(nom::Err::Error((input, "atext(): empty input").into())); return Err(nom::Err::Error((input, "atext(): empty input").into()));
@ -1244,10 +1249,10 @@ pub mod generic {
} }
///```text ///```text
///dtext = %d33-90 / ; Printable US-ASCII /// dtext = %d33-90 / ; Printable US-ASCII
/// %d94-126 / ; characters not including /// %d94-126 / ; characters not including
/// obs-dtext ; "[", "]", or "\" /// obs-dtext ; "[", "]", or "\"
///``` /// ```
pub fn dtext(input: &[u8]) -> IResult<&[u8], u8> { pub fn dtext(input: &[u8]) -> IResult<&[u8], u8> {
alt((byte_in_range(33, 90), byte_in_range(94, 125)))(input) alt((byte_in_range(33, 90), byte_in_range(94, 125)))(input)
} }
@ -1259,11 +1264,13 @@ pub mod mailing_lists {
//! Implemented RFCs: //! Implemented RFCs:
//! //!
//! - [RFC2369 "The Use of URLs as Meta-Syntax for Core Mail List Commands and their Transport through Message Header Fields"](https://tools.ietf.org/html/rfc2369) //! - [RFC2369 "The Use of URLs as Meta-Syntax for Core Mail List Commands and their Transport through Message Header Fields"](https://tools.ietf.org/html/rfc2369)
use super::*;
use generic::cfws; use generic::cfws;
///Parse the value of headers defined in RFC2369 "The Use of URLs as Meta-Syntax for Core use super::*;
///Mail List Commands and their Transport through Message Header Fields"
///Parse the value of headers defined in RFC2369 "The Use of URLs as
/// Meta-Syntax for Core Mail List Commands and their Transport through
/// Message Header Fields"
pub fn rfc_2369_list_headers_action_list(input: &[u8]) -> IResult<&[u8], Vec<&[u8]>> { pub fn rfc_2369_list_headers_action_list(input: &[u8]) -> IResult<&[u8], Vec<&[u8]>> {
let (input, _) = opt(cfws)(input)?; let (input, _) = opt(cfws)(input)?;
let (input, ret) = alt(( let (input, ret) = alt((
@ -1458,9 +1465,9 @@ pub mod headers {
/* A header can span multiple lines, eg: /* A header can span multiple lines, eg:
* *
* Received: from -------------------- (-------------------------) * Received: from -------------------- (-------------------------)
* by --------------------- (--------------------- [------------------]) (-----------------------) * by --------------------- (--------------------- [------------------])
* with ESMTP id ------------ for <------------------->; * (-----------------------) with ESMTP id ------------ for
* Tue, 5 Jan 2016 21:30:44 +0100 (CET) * <------------------->; Tue, 5 Jan 2016 21:30:44 +0100 (CET)
*/ */
pub fn header_value(input: &[u8]) -> IResult<&[u8], &[u8]> { pub fn header_value(input: &[u8]) -> IResult<&[u8], &[u8]> {
@ -1580,8 +1587,10 @@ pub mod headers {
pub mod attachments { pub mod attachments {
/*! Email attachments */ /*! Email attachments */
use super::*; use super::*;
use crate::email::address::*; use crate::email::{
use crate::email::attachment_types::{ContentDisposition, ContentDispositionKind}; address::*,
attachment_types::{ContentDisposition, ContentDispositionKind},
};
pub fn attachment(input: &[u8]) -> IResult<&[u8], (std::vec::Vec<(&[u8], &[u8])>, &[u8])> { pub fn attachment(input: &[u8]) -> IResult<&[u8], (std::vec::Vec<(&[u8], &[u8])>, &[u8])> {
alt(( alt((
separated_pair( separated_pair(
@ -1807,7 +1816,8 @@ pub mod attachments {
pub fn content_disposition(input: &[u8]) -> IResult<&[u8], ContentDisposition> { pub fn content_disposition(input: &[u8]) -> IResult<&[u8], ContentDisposition> {
let (input, kind) = alt((take_until(";"), take_while(|_| true)))(input.trim())?; let (input, kind) = alt((take_until(";"), take_while(|_| true)))(input.trim())?;
let mut ret = ContentDisposition { let mut ret = ContentDisposition {
/* RFC2183 Content-Disposition: "Unrecognized disposition types should be treated as `attachment'." */ /* RFC2183 Content-Disposition: "Unrecognized disposition types should be treated as
* `attachment'." */
kind: if kind.trim().eq_ignore_ascii_case(b"inline") { kind: if kind.trim().eq_ignore_ascii_case(b"inline") {
ContentDispositionKind::Inline ContentDispositionKind::Inline
} else { } else {
@ -1846,11 +1856,11 @@ pub mod attachments {
pub mod encodings { pub mod encodings {
/*! Email encodings (quoted printable, MIME) */ /*! Email encodings (quoted printable, MIME) */
use data_encoding::BASE64_MIME;
use encoding::{all::*, DecoderTrap, Encoding};
use super::*; use super::*;
use crate::email::attachment_types::Charset; use crate::email::attachment_types::Charset;
use data_encoding::BASE64_MIME;
use encoding::all::*;
use encoding::{DecoderTrap, Encoding};
pub fn quoted_printable_byte(input: &[u8]) -> IResult<&[u8], u8> { pub fn quoted_printable_byte(input: &[u8]) -> IResult<&[u8], u8> {
if input.len() < 3 { if input.len() < 3 {
Err(nom::Err::Error( Err(nom::Err::Error(
@ -2023,7 +2033,8 @@ pub mod encodings {
if input.starts_with(b"=\n") { if input.starts_with(b"=\n") {
Ok((&input[2..], input[1])) // `=\n` is an escaped space character. Ok((&input[2..], input[1])) // `=\n` is an escaped space character.
} else if input.starts_with(b"=\r\n") { } else if input.starts_with(b"=\r\n") {
Ok((&input[3..], input[2])) // `=\r\n` is an escaped space character. Ok((&input[3..], input[2])) // `=\r\n` is an escaped space
// character.
} else { } else {
Err(nom::Err::Error( Err(nom::Err::Error(
(input, "quoted_printable_soft_break(): invalid input").into(), (input, "quoted_printable_soft_break(): invalid input").into(),
@ -2036,8 +2047,9 @@ pub mod encodings {
Ok((rest, 0x20)) Ok((rest, 0x20))
} }
// With MIME, headers in quoted printable format can contain underscores that represent spaces. // With MIME, headers in quoted printable format can contain underscores that
// In non-header context, an underscore is just a plain underscore. // represent spaces. In non-header context, an underscore is just a plain
// underscore.
pub fn quoted_printable_bytes_header(input: &[u8]) -> IResult<&[u8], Vec<u8>> { pub fn quoted_printable_bytes_header(input: &[u8]) -> IResult<&[u8], Vec<u8>> {
many0(alt((quoted_printable_byte, qp_underscore_header, le_u8)))(input) many0(alt((quoted_printable_byte, qp_underscore_header, le_u8)))(input)
} }
@ -2173,9 +2185,9 @@ pub mod address {
//! - [RFC6532 "Internationalized Email Headers"](https://tools.ietf.org/html/rfc6532) //! - [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) //! - [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::{
use crate::email::parser::generic::{ address::*,
atom, cfws, dot_atom, dot_atom_text, dtext, phrase2, quoted_string, parser::generic::{atom, cfws, dot_atom, dot_atom_text, dtext, phrase2, quoted_string},
}; };
pub fn display_addr(input: &[u8]) -> IResult<&[u8], Address> { pub fn display_addr(input: &[u8]) -> IResult<&[u8], Address> {
if input.is_empty() || input.len() < 3 { if input.is_empty() || input.len() < 3 {
@ -2447,8 +2459,8 @@ pub mod address {
} }
///```text ///```text
///address = mailbox / group /// address = mailbox / group
///``` /// ```
pub fn address(input: &[u8]) -> IResult<&[u8], Address> { pub fn address(input: &[u8]) -> IResult<&[u8], Address> {
alt((mailbox, group))(input) alt((mailbox, group))(input)
} }
@ -2599,15 +2611,18 @@ pub mod address {
#[cfg(test)] #[cfg(test)]
mod tests { mod tests {
use super::{address::*, encodings::*, *}; use super::{address::*, encodings::*, *};
use crate::email::address::*; use crate::{email::address::*, make_address};
use crate::make_address;
#[test] #[test]
fn test_phrase() { fn test_phrase() {
let words = b"=?iso-8859-7?B?W215Y291cnNlcy5udHVhLmdyIC0gyvXs4fTp6t4g6uHpIMri4e306ere?= let words = b"=?iso-8859-7?B?W215Y291cnNlcy5udHVhLmdyIC0gyvXs4fTp6t4g6uHpIMri4e306ere?=
=?iso-8859-7?B?INb18+nq3l0gzd3hIMHt4erv3+358+c6IMzF0c/TIMHQz9TFy8XTzMHU?= =?iso-8859-7?B?INb18+nq3l0gzd3hIMHt4erv3+358+c6IMzF0c/TIMHQz9TFy8XTzMHU?=
=?iso-8859-7?B?2c0gwiDUzC4gysHNLiDFzsXUwdPH0yAyMDE3LTE4OiDTx8zFydnTxw==?="; =?iso-8859-7?B?2c0gwiDUzC4gysHNLiDFzsXUwdPH0yAyMDE3LTE4OiDTx8zFydnTxw==?=";
assert_eq!("[mycourses.ntua.gr - Κυματική και Κβαντική Φυσική] Νέα Ανακοίνωση: ΜΕΡΟΣ ΑΠΟΤΕΛΕΣΜΑΤΩΝ Β ΤΜ. ΚΑΝ. ΕΞΕΤΑΣΗΣ 2017-18: ΣΗΜΕΙΩΣΗ" , std::str::from_utf8(&phrase(words.trim(), false).unwrap().1).unwrap()); assert_eq!(
"[mycourses.ntua.gr - Κυματική και Κβαντική Φυσική] Νέα Ανακοίνωση: ΜΕΡΟΣ \
ΑΠΟΤΕΛΕΣΜΑΤΩΝ Β ΤΜ. ΚΑΝ. ΕΞΕΤΑΣΗΣ 2017-18: ΣΗΜΕΙΩΣΗ",
std::str::from_utf8(&phrase(words.trim(), false).unwrap().1).unwrap()
);
let words = b"=?UTF-8?Q?=CE=A0=CF=81=CF=8C=CF=83=CE=B8=CE=B5?= =?UTF-8?Q?=CF=84=CE=B7_=CE=B5=CE=BE=CE=B5=CF=84?= =?UTF-8?Q?=CE=B1=CF=83=CF=84=CE=B9=CE=BA=CE=AE?="; let words = b"=?UTF-8?Q?=CE=A0=CF=81=CF=8C=CF=83=CE=B8=CE=B5?= =?UTF-8?Q?=CF=84=CE=B7_=CE=B5=CE=BE=CE=B5=CF=84?= =?UTF-8?Q?=CE=B1=CF=83=CF=84=CE=B9=CE=BA=CE=AE?=";
assert_eq!( assert_eq!(
"Πρόσθετη εξεταστική", "Πρόσθετη εξεταστική",
@ -2929,12 +2944,16 @@ mod tests {
"=?iso-8859-1?q?Fran=E7ois?= Pons <fpons@mandrakesoft.com>" "=?iso-8859-1?q?Fran=E7ois?= Pons <fpons@mandrakesoft.com>"
); );
assert_parse!( assert_parse!(
"هل تتكلم اللغة الإنجليزية /العربية؟", "do.you.speak@arabic.com", "هل تتكلم اللغة الإنجليزية /العربية؟",
"=?utf-8?b?2YfZhCDYqtiq2YPZhNmFINin2YTZhNi62Kkg2KfZhNil2YbYrNmE2YrYstmK2Kk=?=\n =?utf-8?b?IC/Yp9mE2LnYsdio2YrYqdif?= <do.you.speak@arabic.com>" "do.you.speak@arabic.com",
"=?utf-8?b?2YfZhCDYqtiq2YPZhNmFINin2YTZhNi62Kkg2KfZhNil2YbYrNmE2YrYstmK2Kk=?=\n \
=?utf-8?b?IC/Yp9mE2LnYsdio2YrYqdif?= <do.you.speak@arabic.com>"
); );
assert_parse!( assert_parse!(
"狂ったこの世で狂うなら気は確かだ。", "famous@quotes.ja", "狂ったこの世で狂うなら気は確かだ。",
"=?utf-8?b?54uC44Gj44Gf44GT44Gu5LiW44Gn54uC44GG44Gq44KJ5rCX44Gv56K644GL44Gg?=\n =?utf-8?b?44CC?= <famous@quotes.ja>" "famous@quotes.ja",
"=?utf-8?b?54uC44Gj44Gf44GT44Gu5LiW44Gn54uC44GG44Gq44KJ5rCX44Gv56K644GL44Gg?=\n \
=?utf-8?b?44CC?= <famous@quotes.ja>"
); );
assert_eq!( assert_eq!(
Address::new_group( Address::new_group(

View File

@ -20,11 +20,13 @@
*/ */
/*! Verification of OpenPGP signatures */ /*! Verification of OpenPGP signatures */
use crate::email::{ use crate::{
attachment_types::{ContentType, MultipartType}, email::{
attachments::Attachment, attachment_types::{ContentType, MultipartType},
attachments::Attachment,
},
Error, Result,
}; };
use crate::{Error, Result};
/// Convert raw attachment to the form needed for signature verification ([rfc3156](https://tools.ietf.org/html/rfc3156)) /// Convert raw attachment to the form needed for signature verification ([rfc3156](https://tools.ietf.org/html/rfc3156))
/// ///

View File

@ -23,13 +23,7 @@
* An error object for `melib` * An error object for `melib`
*/ */
use std::borrow::Cow; use std::{borrow::Cow, fmt, io, result, str, string, sync::Arc};
use std::fmt;
use std::io;
use std::result;
use std::str;
use std::string;
use std::sync::Arc;
pub type Result<T> = result::Result<T, Error>; pub type Result<T> = result::Result<T, Error>;

View File

@ -195,8 +195,7 @@ pub struct gpgme_data {
} }
pub type gpgme_data_t = *mut gpgme_data; pub type gpgme_data_t = *mut gpgme_data;
pub type gpgme_error_t = gpg_error_t; pub type gpgme_error_t = gpg_error_t;
pub use self::gpg_err_code_t as gpgme_err_code_t; pub use self::{gpg_err_code_t as gpgme_err_code_t, gpg_err_source_t as gpgme_err_source_t};
pub use self::gpg_err_source_t as gpgme_err_source_t;
pub type gpgme_strerror = extern "C" fn(err: gpgme_error_t) -> *const ::std::os::raw::c_char; pub type gpgme_strerror = extern "C" fn(err: gpgme_error_t) -> *const ::std::os::raw::c_char;
pub type gpgme_strerror_r = unsafe extern "C" fn( pub type gpgme_strerror_r = unsafe extern "C" fn(
err: gpg_error_t, err: gpg_error_t,
@ -5326,14 +5325,12 @@ pub type gpgme_op_assuan_transact = extern "C" fn(
pub type GpgmeCtx = gpgme_ctx_t; pub type GpgmeCtx = gpgme_ctx_t;
pub type GpgmeData = gpgme_data_t; pub type GpgmeData = gpgme_data_t;
pub type GpgmeError = gpgme_error_t; pub type GpgmeError = gpgme_error_t;
pub use self::gpgme_attr_t as GpgmeAttr; pub use self::{
pub use self::gpgme_data_encoding_t as GpgmeDataEncoding; gpgme_attr_t as GpgmeAttr, gpgme_data_encoding_t as GpgmeDataEncoding,
pub use self::gpgme_hash_algo_t as GpgmeHashAlgo; gpgme_hash_algo_t as GpgmeHashAlgo, gpgme_protocol_t as GpgmeProtocol,
pub use self::gpgme_protocol_t as GpgmeProtocol; gpgme_pubkey_algo_t as GpgmePubKeyAlgo, gpgme_sig_mode_t as GpgmeSigMode,
pub use self::gpgme_pubkey_algo_t as GpgmePubKeyAlgo; gpgme_sig_stat_t as GpgmeSigStat, gpgme_validity_t as GpgmeValidity,
pub use self::gpgme_sig_mode_t as GpgmeSigMode; };
pub use self::gpgme_sig_stat_t as GpgmeSigStat;
pub use self::gpgme_validity_t as GpgmeValidity;
pub type GpgmeEngineInfo = gpgme_engine_info_t; pub type GpgmeEngineInfo = gpgme_engine_info_t;
pub type GpgmeSubkey = gpgme_subkey_t; pub type GpgmeSubkey = gpgme_subkey_t;
pub type GpgmeKeySig = gpgme_key_sig_t; pub type GpgmeKeySig = gpgme_key_sig_t;

View File

@ -19,9 +19,10 @@
* along with meli. If not, see <http://www.gnu.org/licenses/>. * along with meli. If not, see <http://www.gnu.org/licenses/>.
*/ */
use super::*;
use std::io::{self, Read, Seek, Write}; use std::io::{self, Read, Seek, Write};
use super::*;
#[repr(C)] #[repr(C)]
struct TagData { struct TagData {
idx: usize, idx: usize,

View File

@ -19,26 +19,34 @@
* 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::{ use std::{
pgp::{DecryptionMetadata, Recipient}, borrow::Cow,
Address, collections::HashMap,
ffi::{CStr, CString, OsStr},
future::Future,
io::Seek,
os::unix::{
ffi::OsStrExt,
io::{AsRawFd, RawFd},
},
path::Path,
sync::{Arc, Mutex},
}; };
use crate::error::{Error, ErrorKind, IntoError, Result, ResultIntoError};
use futures::FutureExt; use futures::FutureExt;
use serde::{ use serde::{
de::{self, Deserialize}, de::{self, Deserialize},
Deserializer, Serialize, Serializer, Deserializer, Serialize, Serializer,
}; };
use smol::Async; use smol::Async;
use std::borrow::Cow;
use std::collections::HashMap; use crate::{
use std::ffi::{CStr, CString, OsStr}; email::{
use std::future::Future; pgp::{DecryptionMetadata, Recipient},
use std::io::Seek; Address,
use std::os::unix::ffi::OsStrExt; },
use std::os::unix::io::{AsRawFd, RawFd}; error::{Error, ErrorKind, IntoError, Result, ResultIntoError},
use std::path::Path; };
use std::sync::{Arc, Mutex};
macro_rules! call { macro_rules! call {
($lib:expr, $func:ty) => {{ ($lib:expr, $func:ty) => {{
@ -247,10 +255,10 @@ impl Context {
let mut io_cbs = gpgme_io_cbs { let mut io_cbs = gpgme_io_cbs {
add: Some(io::gpgme_register_io_cb), add: Some(io::gpgme_register_io_cb),
add_priv: Arc::into_raw(add_priv_data) as *mut ::std::os::raw::c_void, //add_priv: *mut ::std::os::raw::c_void, add_priv: Arc::into_raw(add_priv_data) as *mut ::std::os::raw::c_void, /* add_priv: *mut ::std::os::raw::c_void, */
remove: Some(io::gpgme_remove_io_cb), remove: Some(io::gpgme_remove_io_cb),
event: Some(io::gpgme_event_io_cb), event: Some(io::gpgme_event_io_cb),
event_priv: Arc::into_raw(event_priv_data) as *mut ::std::os::raw::c_void, //pub event_priv: *mut ::std::os::raw::c_void, event_priv: Arc::into_raw(event_priv_data) as *mut ::std::os::raw::c_void, /* pub event_priv: *mut ::std::os::raw::c_void, */
}; };
unsafe { unsafe {
@ -1345,7 +1353,8 @@ impl Drop for Key {
// futures::executor::block_on(ctx.keylist().unwrap()).unwrap() // futures::executor::block_on(ctx.keylist().unwrap()).unwrap()
// ); // );
// let cipher = ctx.new_data_file("/tmp/msg.asc").unwrap(); // let cipher = ctx.new_data_file("/tmp/msg.asc").unwrap();
// let plain = futures::executor::block_on(ctx.decrypt(cipher).unwrap()).unwrap(); // let plain =
// futures::executor::block_on(ctx.decrypt(cipher).unwrap()).unwrap();
// println!( // println!(
// "buf: {}", // "buf: {}",
// String::from_utf8_lossy(&plain.into_bytes().unwrap()) // String::from_utf8_lossy(&plain.into_bytes().unwrap())

View File

@ -20,19 +20,29 @@
*/ */
//! A crate that performs mail client operations such as //! A crate that performs mail client operations such as
//! - Hold an [`Envelope`](./email/struct.Envelope.html) with methods convenient for mail client use. (see module [`email`](./email/index.html)) //! - Hold an [`Envelope`](./email/struct.Envelope.html) with methods convenient
//! - 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)) //! for mail client use. (see module [`email`](./email/index.html))
//! - Decode attachments (see module [`email::attachments`](./email/attachments/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)) //! - Create new mail (see [`email::Draft`](./email/compose/struct.Draft.html))
//! - Send mail with an SMTP client (see module [`smtp`](./smtp/index.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)) //! - Manage an `addressbook` i.e. have contacts (see module
//! - Build thread structures out of a list of mail via their `In-Reply-To` and `References` header values (see module [`thread`](./thread/index.html)) //! [`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 //! Other exports are
//! - Basic mail account configuration to use with [`backends`](./backends/index.html) (see module [`conf`](./conf/index.html)) //! - 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)) //! - 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 [`debug` macro](./macro.debug.html)) //! - 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 {
@ -102,8 +112,7 @@ pub use datetime::UnixTimestamp;
#[macro_use] #[macro_use]
mod logging; mod logging;
pub use self::logging::LoggingLevel::*; pub use self::logging::{LoggingLevel::*, *};
pub use self::logging::*;
pub mod addressbook; pub mod addressbook;
pub use addressbook::*; pub use addressbook::*;
@ -180,12 +189,15 @@ impl core::fmt::Display for Bytes {
pub use shellexpand::ShellExpandTrait; pub use shellexpand::ShellExpandTrait;
pub mod shellexpand { pub mod shellexpand {
use smallvec::SmallVec;
use std::ffi::OsStr;
use std::os::unix::ffi::OsStrExt;
#[cfg(not(any(target_os = "netbsd", target_os = "macos")))] #[cfg(not(any(target_os = "netbsd", target_os = "macos")))]
use std::os::unix::io::AsRawFd; use std::os::unix::io::AsRawFd;
use std::path::{Path, PathBuf}; use std::{
ffi::OsStr,
os::unix::ffi::OsStrExt,
path::{Path, PathBuf},
};
use smallvec::SmallVec;
pub trait ShellExpandTrait { pub trait ShellExpandTrait {
fn expand(&self) -> PathBuf; fn expand(&self) -> PathBuf;
@ -233,23 +245,20 @@ pub mod shellexpand {
let (prefix, _match) = if self.as_os_str().as_bytes().ends_with(b"/.") { let (prefix, _match) = if self.as_os_str().as_bytes().ends_with(b"/.") {
(self.components().as_path(), OsStr::from_bytes(b".")) (self.components().as_path(), OsStr::from_bytes(b"."))
} else if self.exists() && (!force || self.as_os_str().as_bytes().ends_with(b"/")) {
return SmallVec::new();
} else { } else {
if self.exists() && (!force || self.as_os_str().as_bytes().ends_with(b"/")) { let last_component = self
// println!("{} {:?}", self.display(), self.components().last()); .components()
return SmallVec::new(); .last()
.map(|c| c.as_os_str())
.unwrap_or_else(|| OsStr::from_bytes(b""));
let prefix = if let Some(p) = self.parent() {
p
} else { } else {
let last_component = self return SmallVec::new();
.components() };
.last() (prefix, last_component)
.map(|c| c.as_os_str())
.unwrap_or_else(|| OsStr::from_bytes(b""));
let prefix = if let Some(p) = self.parent() {
p
} else {
return SmallVec::new();
};
(prefix, last_component)
}
}; };
let dir = match ::nix::dir::Dir::openat( let dir = match ::nix::dir::Dir::openat(
@ -322,10 +331,13 @@ pub mod shellexpand {
pos += dir[0].d_reclen as usize; pos += dir[0].d_reclen as usize;
} }
// https://github.com/romkatv/gitstatus/blob/caf44f7aaf33d0f46e6749e50595323c277e0908/src/dir.cc // https://github.com/romkatv/gitstatus/blob/caf44f7aaf33d0f46e6749e50595323c277e0908/src/dir.cc
// "It's tempting to bail here if n + sizeof(linux_dirent64) + 512 <= n. After all, there // "It's tempting to bail here if n + sizeof(linux_dirent64) +
// was enough space for another entry but SYS_getdents64 didn't write it, so this must be // 512 <= n. After all, there was enough space
// the end of the directory listing, right? Unfortunately, no. SYS_getdents64 is finicky. // for another entry but SYS_getdents64 didn't write it, so this
// It sometimes writes a partial list of entries even if the full list would fit." // must be the end of the directory listing,
// right? Unfortunately, no. SYS_getdents64 is finicky.
// It sometimes writes a partial list of entries even if the
// full list would fit."
} }
entries entries
} }
@ -420,8 +432,7 @@ macro_rules! declare_u64_hash {
impl $type_name { impl $type_name {
#[inline(always)] #[inline(always)]
pub fn from_bytes(bytes: &[u8]) -> Self { pub fn from_bytes(bytes: &[u8]) -> Self {
use std::collections::hash_map::DefaultHasher; use std::{collections::hash_map::DefaultHasher, hash::Hasher};
use std::hash::Hasher;
let mut h = DefaultHasher::new(); let mut h = DefaultHasher::new();
h.write(bytes); h.write(bytes);
Self(h.finish()) Self(h.finish())

View File

@ -19,11 +19,14 @@
* along with meli. If not, see <http://www.gnu.org/licenses/>. * along with meli. If not, see <http://www.gnu.org/licenses/>.
*/ */
use std::{
fs::OpenOptions,
io::{BufWriter, Write},
path::PathBuf,
sync::{Arc, Mutex},
};
use crate::shellexpand::ShellExpandTrait; use crate::shellexpand::ShellExpandTrait;
use std::fs::OpenOptions;
use std::io::{BufWriter, Write};
use std::path::PathBuf;
use std::sync::{Arc, Mutex};
#[derive(Copy, Clone, PartialEq, PartialOrd, Hash, Debug, Serialize, Deserialize)] #[derive(Copy, Clone, PartialEq, PartialOrd, Hash, Debug, Serialize, Deserialize)]
pub enum LoggingLevel { pub enum LoggingLevel {

View File

@ -442,30 +442,17 @@ pub fn is_not<'a>(slice: &'static [u8]) -> impl Parser<'a, &'a str> {
/// ///
/// let parser = |input| { /// let parser = |input| {
/// alt([ /// alt([
/// delimited( /// delimited(match_literal("{"), quoted_slice(), match_literal("}")),
/// match_literal("{"), /// delimited(match_literal("["), quoted_slice(), match_literal("]")),
/// quoted_slice(), /// ])
/// match_literal("}"), /// .parse(input)
/// ),
/// delimited(
/// match_literal("["),
/// quoted_slice(),
/// match_literal("]"),
/// ),
/// ]).parse(input)
/// }; /// };
/// ///
/// let input1: &str = "{\"quoted\"}"; /// let input1: &str = "{\"quoted\"}";
/// let input2: &str = "[\"quoted\"]"; /// let input2: &str = "[\"quoted\"]";
/// assert_eq!( /// assert_eq!(Ok(("", "quoted")), parser.parse(input1));
/// Ok(("", "quoted")),
/// parser.parse(input1)
/// );
/// ///
/// assert_eq!( /// assert_eq!(Ok(("", "quoted")), parser.parse(input2));
/// Ok(("", "quoted")),
/// parser.parse(input2)
/// );
/// ``` /// ```
pub fn alt<'a, P, A, const N: usize>(parsers: [P; N]) -> impl Parser<'a, A> pub fn alt<'a, P, A, const N: usize>(parsers: [P; N]) -> impl Parser<'a, A>
where where
@ -591,20 +578,17 @@ pub fn take<'a>(count: usize) -> impl Parser<'a, &'a str> {
///```rust ///```rust
/// # use std::str::FromStr; /// # use std::str::FromStr;
/// # use melib::parsec::{Parser, delimited, match_literal, map_res, is_a, take_literal}; /// # use melib::parsec::{Parser, delimited, match_literal, map_res, is_a, take_literal};
/// let lit: &str = "{31}\r\nThere is no script by that name\r\n"; /// let lit: &str = "{31}\r\nThere is no script by that name\r\n";
/// assert_eq!( /// assert_eq!(
/// take_literal(delimited( /// take_literal(delimited(
/// match_literal("{"), /// match_literal("{"),
/// map_res(is_a(b"0123456789"), |s| usize::from_str(s)), /// map_res(is_a(b"0123456789"), |s| usize::from_str(s)),
/// match_literal("}\r\n"), /// match_literal("}\r\n"),
/// )) /// ))
/// .parse(lit), /// .parse(lit),
/// Ok(( /// Ok(("\r\n", "There is no script by that name",))
/// "\r\n", /// );
/// "There is no script by that name", /// ```
/// ))
/// );
///```
pub fn take_literal<'a, P>(parser: P) -> impl Parser<'a, &'a str> pub fn take_literal<'a, P>(parser: P) -> impl Parser<'a, &'a str>
where where
P: Parser<'a, usize>, P: Parser<'a, usize>,
@ -617,9 +601,10 @@ where
#[cfg(test)] #[cfg(test)]
mod test { mod test {
use super::*;
use std::collections::HashMap; use std::collections::HashMap;
use super::*;
#[test] #[test]
fn test_parsec() { fn test_parsec() {
#[derive(Debug, PartialEq)] #[derive(Debug, PartialEq)]
@ -639,16 +624,16 @@ mod test {
either( either(
either( either(
either( either(
map(parse_bool(), |b| JsonValue::JsonBool(b)), map(parse_bool(), JsonValue::JsonBool),
map(parse_null(), |()| JsonValue::JsonNull), map(parse_null(), |()| JsonValue::JsonNull),
), ),
map(parse_array(), |vec| JsonValue::JsonArray(vec)), map(parse_array(), JsonValue::JsonArray),
), ),
map(parse_object(), |obj| JsonValue::JsonObject(obj)), map(parse_object(), JsonValue::JsonObject),
), ),
map(parse_number(), |n| JsonValue::JsonNumber(n)), map(parse_number(), JsonValue::JsonNumber),
), ),
map(quoted_string(), |s| JsonValue::JsonString(s)), map(quoted_string(), JsonValue::JsonString),
) )
.parse(input) .parse(input)
} }

View File

@ -19,14 +19,13 @@
* along with meli. If not, see <http://www.gnu.org/licenses/>. * along with meli. If not, see <http://www.gnu.org/licenses/>.
*/ */
use crate::parsec::*; use std::{borrow::Cow, convert::TryFrom};
use crate::UnixTimestamp;
use std::borrow::Cow;
use std::convert::TryFrom;
pub use query_parser::query; pub use query_parser::query;
use Query::*; use Query::*;
use crate::{parsec::*, UnixTimestamp};
#[derive(Debug, PartialEq, Clone, Serialize)] #[derive(Debug, PartialEq, Clone, Serialize)]
pub enum Query { pub enum Query {
Before(UnixTimestamp), Before(UnixTimestamp),
@ -233,9 +232,10 @@ pub mod query_parser {
/// ///
/// # Invocation /// # Invocation
/// ``` /// ```
/// use melib::search::query; /// use melib::{
/// use melib::search::Query; /// parsec::Parser,
/// use melib::parsec::Parser; /// search::{query, Query},
/// };
/// ///
/// let input = "test"; /// let input = "test";
/// let query = query().parse(input); /// let query = query().parse(input);

View File

@ -153,7 +153,8 @@ pub enum ZoneRule {
///time zone in offset format "+hhmm" or "-hhmm". An ///time zone in offset format "+hhmm" or "-hhmm". An
///offset of 0 (Zulu) always has a positive sign. ///offset of 0 (Zulu) always has a positive sign.
Zone, Zone,
/// "weekday" => the day of the week expressed as an integer between "0" and "6". "0" is Sunday, "1" is Monday, etc. /// "weekday" => the day of the week expressed as an integer between "0"
/// and "6". "0" is Sunday, "1" is Monday, etc.
Weekday, Weekday,
} }
@ -370,9 +371,9 @@ pub mod parser {
), ),
|(num_s, quant)| { |(num_s, quant)| {
Ok(match (num_s.parse::<u64>(), quant.to_ascii_lowercase()) { Ok(match (num_s.parse::<u64>(), quant.to_ascii_lowercase()) {
(Ok(num), 'k') => num * 1000, (Ok(num), 'k') => num * 1_000,
(Ok(num), 'm') => num * 1000_000, (Ok(num), 'm') => num * 1_000_000,
(Ok(num), 'g') => num * 1000_000_000, (Ok(num), 'g') => num * 1_000_000_000,
_ => return Err(num_s), _ => return Err(num_s),
}) })
}, },
@ -483,7 +484,8 @@ pub mod parser {
} }
} }
// address [COMPARATOR] [ADDRESS-PART] [MATCH-TYPE] <header-list: string-list> <key-list: string-list> // address [COMPARATOR] [ADDRESS-PART] [MATCH-TYPE] <header-list: string-list>
// <key-list: string-list>
pub fn parse_sieve_address<'a>() -> impl Parser<'a, ConditionRule> { pub fn parse_sieve_address<'a>() -> impl Parser<'a, ConditionRule> {
move |input| { move |input| {
map( map(
@ -677,19 +679,12 @@ pub mod parser {
#[cfg(test)] #[cfg(test)]
mod test { mod test {
use super::parser::*; use super::{
parser::*, ActionCommand::*, AddressOperator::*, CharacterOperator::*, ConditionRule::*,
ControlCommand::*, IntegerOperator::*, MatchOperator::*, Rule::*, RuleBlock,
};
use crate::parsec::Parser; use crate::parsec::Parser;
use super::ActionCommand::*;
use super::AddressOperator::*;
use super::CharacterOperator::*;
use super::ConditionRule::*;
use super::ControlCommand::*;
use super::IntegerOperator::*;
use super::MatchOperator::*;
use super::Rule::*;
use super::RuleBlock;
#[test] #[test]
fn test_sieve_parse_strings() { fn test_sieve_parse_strings() {
assert_eq!( assert_eq!(
@ -705,9 +700,10 @@ mod test {
#[test] #[test]
fn test_sieve_parse_conditionals() { fn test_sieve_parse_conditionals() {
/* Operators that start with : like :matches are unordered and optional, since they have /* Operators that start with : like :matches are unordered and optional,
* defaults. But that means we must handle any order correctly, which is tricky if we use * since they have defaults. But that means we must handle any order
* an optional parser; for an optional parser both None and Some(_) are valid values. * correctly, which is tricky if we use an optional parser; for an
* optional parser both None and Some(_) are valid values.
*/ */
/* Permutations of two */ /* Permutations of two */

View File

@ -24,8 +24,8 @@
//! SMTP client support //! SMTP client support
//! //!
//! This module implements a client for the SMTP protocol as specified by [RFC 5321 Simple Mail //! This module implements a client for the SMTP protocol as specified by [RFC
//! Transfer Protocol](https://www.rfc-editor.org/rfc/rfc5321). //! 5321 Simple Mail Transfer Protocol](https://www.rfc-editor.org/rfc/rfc5321).
//! //!
//! The connection and methods are `async` and uses the `smol` runtime. //! The connection and methods are `async` and uses the `smol` runtime.
//!# Example //!# Example
@ -72,18 +72,18 @@
//! Ok(()) //! Ok(())
//! ``` //! ```
use crate::connections::{lookup_ipv4, Connection}; use std::{borrow::Cow, convert::TryFrom, net::TcpStream, process::Command};
use crate::email::{parser::BytesExt, Address, Envelope};
use crate::error::{Error, Result, ResultIntoError};
use futures::io::{AsyncReadExt, AsyncWriteExt}; use futures::io::{AsyncReadExt, AsyncWriteExt};
use native_tls::TlsConnector; use native_tls::TlsConnector;
use smallvec::SmallVec; use smallvec::SmallVec;
use smol::unblock; use smol::{unblock, Async as AsyncWrapper};
use smol::Async as AsyncWrapper;
use std::borrow::Cow; use crate::{
use std::convert::TryFrom; connections::{lookup_ipv4, Connection},
use std::net::TcpStream; email::{parser::BytesExt, Address, Envelope},
use std::process::Command; error::{Error, Result, ResultIntoError},
};
/// Kind of server security (StartTLS/TLS/None) the client should attempt /// Kind of server security (StartTLS/TLS/None) the client should attempt
#[derive(Debug, Copy, PartialEq, Eq, Clone, Serialize, Deserialize)] #[derive(Debug, Copy, PartialEq, Eq, Clone, Serialize, Deserialize)]
@ -191,11 +191,12 @@ pub struct SmtpExtensionSupport {
/// [RFC 6152: SMTP Service Extension for 8-bit MIME Transport](https://www.rfc-editor.org/rfc/rfc6152) /// [RFC 6152: SMTP Service Extension for 8-bit MIME Transport](https://www.rfc-editor.org/rfc/rfc6152)
#[serde(default = "crate::conf::true_val")] #[serde(default = "crate::conf::true_val")]
_8bitmime: bool, _8bitmime: bool,
/// Essentially, the PRDR extension to SMTP allows (but does not require) an SMTP server to /// Essentially, the PRDR extension to SMTP allows (but does not require) an
/// issue multiple responses after a message has been transferred, by mutual consent of the /// SMTP server to issue multiple responses after a message has been
/// client and server. SMTP clients that support the PRDR extension then use the expanded /// transferred, by mutual consent of the client and server. SMTP
/// responses as supplemental data to the responses that were received during the earlier /// clients that support the PRDR extension then use the expanded
/// envelope exchange. /// responses as supplemental data to the responses that were received
/// during the earlier envelope exchange.
#[serde(default = "crate::conf::true_val")] #[serde(default = "crate::conf::true_val")]
prdr: bool, prdr: bool,
#[serde(default = "crate::conf::true_val")] #[serde(default = "crate::conf::true_val")]
@ -284,7 +285,10 @@ impl SmtpConnection {
danger_accept_invalid_certs, danger_accept_invalid_certs,
}; };
} else { } else {
return Err(Error::new("Please specify what SMTP security transport to use explicitly instead of `auto`.")); return Err(Error::new(
"Please specify what SMTP security transport to use explicitly \
instead of `auto`.",
));
} }
} }
socket.write_all(b"EHLO meli.delivery\r\n").await?; socket.write_all(b"EHLO meli.delivery\r\n").await?;
@ -386,9 +390,11 @@ impl SmtpConnection {
.any(|l| l.starts_with("AUTH")) .any(|l| l.starts_with("AUTH"))
{ {
return Err(Error::new(format!( return Err(Error::new(format!(
"SMTP Server doesn't advertise Authentication support. Server response was: {:?}", "SMTP Server doesn't advertise Authentication support. Server response was: \
pre_auth_extensions_reply {:?}",
)).set_kind(crate::error::ErrorKind::Authentication)); pre_auth_extensions_reply
))
.set_kind(crate::error::ErrorKind::Authentication));
} }
no_auth_needed = no_auth_needed =
ret.server_conf.auth == SmtpAuth::None || !ret.server_conf.auth.require_auth(); ret.server_conf.auth == SmtpAuth::None || !ret.server_conf.auth.require_auth();
@ -430,7 +436,7 @@ impl SmtpConnection {
let mut output = unblock(move || { let mut output = unblock(move || {
Command::new("sh") Command::new("sh")
.args(&["-c", &_command]) .args(["-c", &_command])
.stdin(std::process::Stdio::piped()) .stdin(std::process::Stdio::piped())
.stdout(std::process::Stdio::piped()) .stdout(std::process::Stdio::piped())
.stderr(std::process::Stdio::piped()) .stderr(std::process::Stdio::piped())
@ -493,7 +499,7 @@ impl SmtpConnection {
let _token_command = token_command.clone(); let _token_command = token_command.clone();
let mut output = unblock(move || { let mut output = unblock(move || {
Command::new("sh") Command::new("sh")
.args(&["-c", &_token_command]) .args(["-c", &_token_command])
.stdin(std::process::Stdio::piped()) .stdin(std::process::Stdio::piped())
.stdout(std::process::Stdio::piped()) .stdout(std::process::Stdio::piped())
.stderr(std::process::Stdio::piped()) .stderr(std::process::Stdio::piped())
@ -538,7 +544,7 @@ impl SmtpConnection {
self.server_conf.envelope_from = envelope_from; self.server_conf.envelope_from = envelope_from;
} }
fn set_extension_support(&mut self, reply: Reply<'_>) { fn set_extension_support(&mut self, reply: Reply) {
debug_assert_eq!(reply.code, ReplyCode::_250); debug_assert_eq!(reply.code, ReplyCode::_250);
self.server_conf.extensions.pipelining &= reply.lines.contains(&"PIPELINING"); self.server_conf.extensions.pipelining &= reply.lines.contains(&"PIPELINING");
self.server_conf.extensions.chunking &= reply.lines.contains(&"CHUNKING"); self.server_conf.extensions.chunking &= reply.lines.contains(&"CHUNKING");
@ -595,7 +601,10 @@ impl SmtpConnection {
.chain_err_summary(|| "SMTP submission was aborted")?; .chain_err_summary(|| "SMTP submission was aborted")?;
let tos = tos.unwrap_or_else(|| envelope.to()); let tos = tos.unwrap_or_else(|| envelope.to());
if tos.is_empty() && envelope.cc().is_empty() && envelope.bcc().is_empty() { if tos.is_empty() && envelope.cc().is_empty() && envelope.bcc().is_empty() {
return Err(Error::new("SMTP submission was aborted because there was no e-mail address found in the To: header field. Consider adding recipients.")); return Err(Error::new(
"SMTP submission was aborted because there was no e-mail address found in the To: \
header field. Consider adding recipients.",
));
} }
let mut current_command: SmallVec<[&[u8]; 16]> = SmallVec::new(); let mut current_command: SmallVec<[&[u8]; 16]> = SmallVec::new();
//first step in the procedure is the MAIL command. //first step in the procedure is the MAIL command.
@ -605,9 +614,17 @@ impl SmtpConnection {
current_command.push(envelope_from.trim().as_bytes()); current_command.push(envelope_from.trim().as_bytes());
} else { } else {
if envelope.from().is_empty() { if envelope.from().is_empty() {
return Err(Error::new("SMTP submission was aborted because there was no e-mail address found in the From: header field. Consider adding a valid value or setting `envelope_from` in SMTP client settings")); return Err(Error::new(
"SMTP submission was aborted because there was no e-mail address found in the \
From: header field. Consider adding a valid value or setting `envelope_from` \
in SMTP client settings",
));
} else if envelope.from().len() != 1 { } else if envelope.from().len() != 1 {
return Err(Error::new("SMTP submission was aborted because there was more than one e-mail address found in the From: header field. Consider setting `envelope_from` in SMTP client settings")); return Err(Error::new(
"SMTP submission was aborted because there was more than one e-mail address \
found in the From: header field. Consider setting `envelope_from` in SMTP \
client settings",
));
} }
current_command.push(envelope.from()[0].address_spec_raw().trim()); current_command.push(envelope.from()[0].address_spec_raw().trim());
} }
@ -628,12 +645,13 @@ impl SmtpConnection {
} else { } else {
pipelining_queue.push(Some((ReplyCode::_250, &[]))); pipelining_queue.push(Some((ReplyCode::_250, &[])));
} }
//The second step in the procedure is the RCPT command. This step of the procedure can //The second step in the procedure is the RCPT command. This step of the
//be repeated any number of times. If accepted, the SMTP server returns a "250 OK" // procedure can be repeated any number of times. If accepted, the SMTP
//reply. If the mailbox specification is not acceptable for some reason, the server MUST // server returns a "250 OK" reply. If the mailbox specification is not
//return a reply indicating whether the failure is permanent (i.e., will occur again if // acceptable for some reason, the server MUST return a reply indicating
//the client tries to send the same address again) or temporary (i.e., the address might // whether the failure is permanent (i.e., will occur again if
//be accepted if the client tries again later). // the client tries to send the same address again) or temporary (i.e., the
// address might be accepted if the client tries again later).
for addr in tos for addr in tos
.iter() .iter()
.chain(envelope.cc().iter()) .chain(envelope.cc().iter())
@ -651,7 +669,8 @@ impl SmtpConnection {
self.send_command(&current_command).await?; self.send_command(&current_command).await?;
//RCPT TO:<forward-path> [ SP <rcpt-parameters> ] <CRLF> //RCPT TO:<forward-path> [ SP <rcpt-parameters> ] <CRLF>
//If accepted, the SMTP server returns a "250 OK" reply and stores the forward-path. //If accepted, the SMTP server returns a "250 OK" reply and stores the
// forward-path.
if !self.server_conf.extensions.pipelining { if !self.server_conf.extensions.pipelining {
self.read_lines(&mut res, Some((ReplyCode::_250, &[]))) self.read_lines(&mut res, Some((ReplyCode::_250, &[])))
.await?; .await?;
@ -660,9 +679,10 @@ impl SmtpConnection {
} }
} }
//Since it has been a common source of errors, it is worth noting that spaces are not //Since it has been a common source of errors, it is worth noting that spaces
//permitted on either side of the colon following FROM in the MAIL command or TO in the // are not permitted on either side of the colon following FROM in the
//RCPT command. The syntax is exactly as given above. // MAIL command or TO in the RCPT command. The syntax is exactly as
// given above.
if self.server_conf.extensions.binarymime { if self.server_conf.extensions.binarymime {
let mail_length = format!("{}", mail.as_bytes().len()); let mail_length = format!("{}", mail.as_bytes().len());
@ -674,13 +694,14 @@ impl SmtpConnection {
//(or some alternative specified in a service extension). //(or some alternative specified in a service extension).
//DATA <CRLF> //DATA <CRLF>
self.send_command(&[b"DATA"]).await?; self.send_command(&[b"DATA"]).await?;
//Client SMTP implementations that employ pipelining MUST check ALL statuses associated //Client SMTP implementations that employ pipelining MUST check ALL statuses
//with each command in a group. For example, if none of the RCPT TO recipient addresses // associated with each command in a group. For example, if none of
//were accepted the client must then check the response to the DATA command -- the client // the RCPT TO recipient addresses were accepted the client must
//cannot assume that the DATA command will be rejected just because none of the RCPT TO // then check the response to the DATA command -- the client
//commands worked. If the DATA command was properly rejected the client SMTP can just // cannot assume that the DATA command will be rejected just because none of the
//issue RSET, but if the DATA command was accepted the client SMTP should send a single // RCPT TO commands worked. If the DATA command was properly
//dot. // rejected the client SMTP can just issue RSET, but if the DATA
// command was accepted the client SMTP should send a single dot.
let mut _all_error = self.server_conf.extensions.pipelining; let mut _all_error = self.server_conf.extensions.pipelining;
let mut _any_error = false; let mut _any_error = false;
let mut ignore_mailfrom = true; let mut ignore_mailfrom = true;
@ -694,15 +715,17 @@ impl SmtpConnection {
pipelining_results.push(reply.into()); pipelining_results.push(reply.into());
} }
//If accepted, the SMTP server returns a 354 Intermediate reply and considers all //If accepted, the SMTP server returns a 354 Intermediate reply and considers
//succeeding lines up to but not including the end of mail data indicator to be the // all succeeding lines up to but not including the end of mail data
//message text. When the end of text is successfully received and stored, the // indicator to be the message text. When the end of text is
//SMTP-receiver sends a "250 OK" reply. // successfully received and stored, the SMTP-receiver sends a "250
// OK" reply.
self.read_lines(&mut res, Some((ReplyCode::_354, &[]))) self.read_lines(&mut res, Some((ReplyCode::_354, &[])))
.await?; .await?;
//Before sending a line of mail text, the SMTP client checks the first character of the //Before sending a line of mail text, the SMTP client checks the first
//line.If it is a period, one additional period is inserted at the beginning of the line. // character of the line.If it is a period, one additional period is
// inserted at the beginning of the line.
for line in mail.lines() { for line in mail.lines() {
if line.starts_with('.') { if line.starts_with('.') {
self.stream.write_all(b".").await?; self.stream.write_all(b".").await?;
@ -715,15 +738,16 @@ impl SmtpConnection {
self.stream.write_all(b".\r\n").await?; self.stream.write_all(b".\r\n").await?;
} }
//The mail data are terminated by a line containing only a period, that is, the character //The mail data are terminated by a line containing only a period, that is, the
//sequence "<CRLF>.<CRLF>", where the first <CRLF> is actually the terminator of the // character sequence "<CRLF>.<CRLF>", where the first <CRLF> is
//previous line (see Section 4.5.2). This is the end of mail data indication. // actually the terminator of the previous line (see Section 4.5.2).
// This is the end of mail data indication.
self.stream.write_all(b".\r\n").await?; self.stream.write_all(b".\r\n").await?;
} }
//The end of mail data indicator also confirms the mail transaction and tells the SMTP //The end of mail data indicator also confirms the mail transaction and tells
//server to now process the stored recipients and mail data. If accepted, the SMTP // the SMTP server to now process the stored recipients and mail data.
//server returns a "250 OK" reply. // If accepted, the SMTP server returns a "250 OK" reply.
let reply_code = self let reply_code = self
.read_lines( .read_lines(
&mut res, &mut res,
@ -760,7 +784,9 @@ pub type ExpectedReplyCode = Option<(ReplyCode, &'static [ReplyCode])>;
pub enum ReplyCode { pub enum ReplyCode {
/// System status, or system help reply /// System status, or system help reply
_211, _211,
/// Help message (Information on how to use the receiver or the meaning of a particular non-standard command; this reply is useful only to the human user) /// Help message (Information on how to use the receiver or the meaning of a
/// particular non-standard command; this reply is useful only to the human
/// user)
_214, _214,
/// <domain> Service ready /// <domain> Service ready
_220, _220,
@ -772,7 +798,8 @@ pub enum ReplyCode {
_250, _250,
/// User not local; will forward to <forward-path> (See Section 3.4) /// User not local; will forward to <forward-path> (See Section 3.4)
_251, _251,
/// Cannot VRFY user, but will accept message and attempt delivery (See Section 3.5.3) /// Cannot VRFY user, but will accept message and attempt delivery (See
/// Section 3.5.3)
_252, _252,
/// rfc4954 AUTH continuation request /// rfc4954 AUTH continuation request
_334, _334,
@ -780,9 +807,11 @@ pub enum ReplyCode {
_353, _353,
/// Start mail input; end with <CRLF>.<CRLF> /// Start mail input; end with <CRLF>.<CRLF>
_354, _354,
/// <domain> Service not available, closing transmission channel (This may be a reply to any command if the service knows it must shut down) /// <domain> Service not available, closing transmission channel (This may
/// be a reply to any command if the service knows it must shut down)
_421, _421,
/// Requested mail action not taken: mailbox unavailable (e.g., mailbox busy or temporarily blocked for policy reasons) /// Requested mail action not taken: mailbox unavailable (e.g., mailbox busy
/// or temporarily blocked for policy reasons)
_450, _450,
/// Requested action aborted: local error in processing /// Requested action aborted: local error in processing
_451, _451,
@ -790,7 +819,8 @@ pub enum ReplyCode {
_452, _452,
/// Server unable to accommodate parameters /// Server unable to accommodate parameters
_455, _455,
/// Syntax error, command unrecognized (This may include errors such as command line too long) /// Syntax error, command unrecognized (This may include errors such as
/// command line too long)
_500, _500,
/// Syntax error in parameters or arguments /// Syntax error in parameters or arguments
_501, _501,
@ -802,15 +832,18 @@ pub enum ReplyCode {
_504, _504,
/// Authentication failed /// Authentication failed
_535, _535,
/// Requested action not taken: mailbox unavailable (e.g., mailbox not found, no access, or command rejected for policy reasons) /// Requested action not taken: mailbox unavailable (e.g., mailbox not
/// found, no access, or command rejected for policy reasons)
_550, _550,
/// User not local; please try <forward-path> (See Section 3.4) /// User not local; please try <forward-path> (See Section 3.4)
_551, _551,
/// Requested mail action aborted: exceeded storage allocation /// Requested mail action aborted: exceeded storage allocation
_552, _552,
/// Requested action not taken: mailbox name not allowed (e.g., mailbox syntax incorrect) /// Requested action not taken: mailbox name not allowed (e.g., mailbox
/// syntax incorrect)
_553, _553,
/// Transaction failed (Or, in the case of a connection-opening response, "No SMTP service here") /// Transaction failed (Or, in the case of a connection-opening response,
/// "No SMTP service here")
_554, _554,
/// MAIL FROM/RCPT TO parameters not recognized or not implemented /// MAIL FROM/RCPT TO parameters not recognized or not implemented
_555, _555,
@ -844,10 +877,16 @@ impl ReplyCode {
_503 => "Bad sequence of commands", _503 => "Bad sequence of commands",
_504 => "Command parameter not implemented", _504 => "Command parameter not implemented",
_535 => "Authentication failed", _535 => "Authentication failed",
_550 => "Requested action not taken: mailbox unavailable (e.g., mailbox not found, no access, or command rejected for policy reasons)", _550 => {
"Requested action not taken: mailbox unavailable (e.g., mailbox not found, no \
access, or command rejected for policy reasons)"
}
_551 => "User not local", _551 => "User not local",
_552 => "Requested mail action aborted: exceeded storage allocation", _552 => "Requested mail action aborted: exceeded storage allocation",
_553 => "Requested action not taken: mailbox name not allowed (e.g., mailbox syntax incorrect)", _553 => {
"Requested action not taken: mailbox name not allowed (e.g., mailbox syntax \
incorrect)"
}
_554 => "Transaction failed", _554 => "Transaction failed",
_555 => "MAIL FROM/RCPT TO parameters not recognized or not implemented", _555 => "MAIL FROM/RCPT TO parameters not recognized or not implemented",
_530 => "Must issue a STARTTLS command first", _530 => "Must issue a STARTTLS command first",
@ -927,19 +966,19 @@ pub struct Reply<'s> {
pub lines: SmallVec<[&'s str; 16]>, pub lines: SmallVec<[&'s str; 16]>,
} }
impl<'s> Into<Result<ReplyCode>> for Reply<'s> { impl<'s> From<Reply<'s>> for Result<ReplyCode> {
fn into(self: Reply<'s>) -> Result<ReplyCode> { fn from(val: Reply<'s>) -> Self {
if self.code.is_err() { if val.code.is_err() {
Err(Error::new(self.lines.join("\n")).set_summary(self.code.as_str())) Err(Error::new(val.lines.join("\n")).set_summary(val.code.as_str()))
} else { } else {
Ok(self.code) Ok(val.code)
} }
} }
} }
impl<'s> Reply<'s> { impl<'s> Reply<'s> {
/// `s` must be raw SMTP output i.e each line must start with 3 digit reply code, a space /// `s` must be raw SMTP output i.e each line must start with 3 digit reply
/// or '-' and end with '\r\n' /// code, a space or '-' and end with '\r\n'
pub fn new(s: &'s str, code: ReplyCode) -> Self { pub fn new(s: &'s str, code: ReplyCode) -> Self {
let lines: SmallVec<_> = s.lines().map(|l| &l[4..l.len()]).collect(); let lines: SmallVec<_> = s.lines().map(|l| &l[4..l.len()]).collect();
Reply { lines, code } Reply { lines, code }
@ -959,7 +998,9 @@ async fn read_lines<'r>(
let mut returned_code: Option<ReplyCode> = None; let mut returned_code: Option<ReplyCode> = None;
'read_loop: loop { 'read_loop: loop {
while let Some(pos) = ret[last_line_idx..].find("\r\n") { while let Some(pos) = ret[last_line_idx..].find("\r\n") {
// "Formally, a reply is defined to be the sequence: a three-digit code, <SP>, one line of text, and <CRLF>, or a multiline reply (as defined in the same section)." // "Formally, a reply is defined to be the sequence: a three-digit code, <SP>,
// one line of text, and <CRLF>, or a multiline reply (as defined in the same
// section)."
if ret[last_line_idx..].len() < 4 if ret[last_line_idx..].len() < 4
|| !ret[last_line_idx..] || !ret[last_line_idx..]
.chars() .chars()
@ -1022,12 +1063,15 @@ async fn read_lines<'r>(
#[cfg(test)] #[cfg(test)]
mod test { mod test {
use super::*; use std::net::IpAddr; //, Ipv4Addr, Ipv6Addr};
use std::{
sync::{Arc, Mutex},
thread,
};
use mailin_embedded::{Handler, Response, Server, SslConfig}; use mailin_embedded::{Handler, Response, Server, SslConfig};
use std::net::IpAddr; //, Ipv4Addr, Ipv6Addr};
use std::sync::{Arc, Mutex}; use super::*;
use std::thread;
const ADDRESS: &str = "127.0.0.1:8825"; const ADDRESS: &str = "127.0.0.1:8825";
#[derive(Debug, Clone)] #[derive(Debug, Clone)]
@ -1229,7 +1273,7 @@ mod test {
futures::executor::block_on(SmtpConnection::new_connection(smtp_server_conf)).unwrap(); futures::executor::block_on(SmtpConnection::new_connection(smtp_server_conf)).unwrap();
futures::executor::block_on(connection.mail_transaction( futures::executor::block_on(connection.mail_transaction(
input_str, input_str,
/*tos*/ /* tos */
Some(&[ Some(&[
Address::try_from("foo-chat@example.com").unwrap(), Address::try_from("foo-chat@example.com").unwrap(),
Address::try_from("webmaster@example.com").unwrap(), Address::try_from("webmaster@example.com").unwrap(),

View File

@ -19,10 +19,12 @@
* along with meli. If not, see <http://www.gnu.org/licenses/>. * along with meli. If not, see <http://www.gnu.org/licenses/>.
*/ */
use crate::{error::*, logging::log, Envelope}; use std::path::PathBuf;
use rusqlite::types::{FromSql, FromSqlError, FromSqlResult, ToSql, ToSqlOutput}; use rusqlite::types::{FromSql, FromSqlError, FromSqlResult, ToSql, ToSqlOutput};
pub use rusqlite::{self, params, Connection}; pub use rusqlite::{self, params, Connection};
use std::path::PathBuf;
use crate::{error::*, logging::log, Envelope};
#[derive(Copy, Clone, Debug)] #[derive(Copy, Clone, Debug)]
pub struct DatabaseDescription { pub struct DatabaseDescription {
@ -90,8 +92,9 @@ pub fn open_or_create_db(
); );
if second_try { if second_try {
return Err(Error::new(format!( return Err(Error::new(format!(
"Database version mismatch, is {} but expected {}. Could not recreate database.", "Database version mismatch, is {} but expected {}. Could not recreate \
version, description.version database.",
version, description.version
))); )));
} }
reset_db(description, identifier)?; reset_db(description, identifier)?;
@ -100,7 +103,7 @@ pub fn open_or_create_db(
} }
if version == 0 { if version == 0 {
conn.pragma_update(None, "user_version", &description.version)?; conn.pragma_update(None, "user_version", description.version)?;
} }
if let Some(s) = description.init_script { if let Some(s) = description.init_script {
conn.execute_batch(s) conn.execute_batch(s)

File diff suppressed because it is too large Load Diff

View File

@ -20,17 +20,18 @@
*/ */
extern crate unicode_segmentation; extern crate unicode_segmentation;
use self::unicode_segmentation::UnicodeSegmentation; use core::{cmp::Ordering, iter::Peekable, str::FromStr};
use super::grapheme_clusters::TextProcessing;
use super::tables::LINE_BREAK_RULES;
use super::types::LineBreakClass;
use super::types::Reflow;
use core::cmp::Ordering;
use core::iter::Peekable;
use core::str::FromStr;
use std::collections::VecDeque; use std::collections::VecDeque;
use LineBreakClass::*; use LineBreakClass::*;
use self::unicode_segmentation::UnicodeSegmentation;
use super::{
grapheme_clusters::TextProcessing,
tables::LINE_BREAK_RULES,
types::{LineBreakClass, Reflow},
};
#[derive(Debug, PartialEq, Copy, Clone)] #[derive(Debug, PartialEq, Copy, Clone)]
pub enum LineBreakCandidate { pub enum LineBreakCandidate {
MandatoryBreak, MandatoryBreak,
@ -138,14 +139,32 @@ impl EvenAfterSpaces for str {
/// Returns positions where breaks can happen /// Returns positions where breaks can happen
/// Examples: /// Examples:
/// ``` /// ```
/// use melib::text_processing::{self, LineBreakCandidate::{self, *}}; /// use melib::text_processing::{
/// use melib::text_processing::line_break::LineBreakCandidateIter; /// self,
/// line_break::LineBreakCandidateIter,
/// LineBreakCandidate::{self, *},
/// };
/// ///
/// assert!(LineBreakCandidateIter::new("").collect::<Vec<(usize, LineBreakCandidate)>>().is_empty()); /// assert!(LineBreakCandidateIter::new("")
/// assert_eq!(&[(7, BreakAllowed), (12, MandatoryBreak)], /// .collect::<Vec<(usize, LineBreakCandidate)>>()
/// LineBreakCandidateIter::new("Sample Text.").collect::<Vec<(usize, LineBreakCandidate)>>().as_slice()); /// .is_empty());
/// assert_eq!(&[(3, MandatoryBreak), (7, MandatoryBreak), (10, BreakAllowed), (17, MandatoryBreak)], /// assert_eq!(
/// LineBreakCandidateIter::new("Sa\nmp\r\nle T(e)xt.").collect::<Vec<(usize, LineBreakCandidate)>>().as_slice()); /// &[(7, BreakAllowed), (12, MandatoryBreak)],
/// LineBreakCandidateIter::new("Sample Text.")
/// .collect::<Vec<(usize, LineBreakCandidate)>>()
/// .as_slice()
/// );
/// assert_eq!(
/// &[
/// (3, MandatoryBreak),
/// (7, MandatoryBreak),
/// (10, BreakAllowed),
/// (17, MandatoryBreak)
/// ],
/// LineBreakCandidateIter::new("Sa\nmp\r\nle T(e)xt.")
/// .collect::<Vec<(usize, LineBreakCandidate)>>()
/// .as_slice()
/// );
/// ``` /// ```
impl<'a> Iterator for LineBreakCandidateIter<'a> { impl<'a> Iterator for LineBreakCandidateIter<'a> {
type Item = (usize, LineBreakCandidate); type Item = (usize, LineBreakCandidate);
@ -190,13 +209,13 @@ impl<'a> Iterator for LineBreakCandidateIter<'a> {
*reg_ind_streak = 0; *reg_ind_streak = 0;
} }
/* LB1 Assign a line breaking class to each code point of the input. Resolve AI, CB, CJ, /* LB1 Assign a line breaking class to each code point of the input. Resolve
* SA, SG, and XX into other line breaking classes depending on criteria outside the scope * AI, CB, CJ, SA, SG, and XX into other line breaking classes
* of this algorithm. * depending on criteria outside the scope of this algorithm.
* *
* In the absence of such criteria all characters with a specific combination of original * In the absence of such criteria all characters with a specific combination
* class and General_Category property value are resolved as follows: * of original class and General_Category property value are
* Resolved Original General_Category * resolved as follows: Resolved Original General_Category
* AL AI, SG, XX Any * AL AI, SG, XX Any
* CM SA Only Mn or Mc * CM SA Only Mn or Mc
* AL SA Any except Mn and Mc * AL SA Any except Mn and Mc
@ -245,7 +264,8 @@ impl<'a> Iterator for LineBreakCandidateIter<'a> {
continue; continue;
} }
WJ => { WJ => {
/*: LB11 Do not break before or after Word joiner and related characters.*/ /* : LB11 Do not break before or after Word joiner and related
* characters. */
*pos += grapheme.len(); *pos += grapheme.len();
continue; continue;
} }
@ -266,8 +286,8 @@ impl<'a> Iterator for LineBreakCandidateIter<'a> {
} }
match class { match class {
ZW => { ZW => {
// LB8 Break before any character following a zero-width space, even if one or more // LB8 Break before any character following a zero-width space, even if one or
// spaces intervene // more spaces intervene
// ZW SP* ÷ // ZW SP* ÷
*pos += grapheme.len(); *pos += grapheme.len();
while next_grapheme_class!((next_char is SP)) { while next_grapheme_class!((next_char is SP)) {
@ -286,9 +306,9 @@ impl<'a> Iterator for LineBreakCandidateIter<'a> {
} }
CM => { CM => {
// LB9 Do not break a combining character sequence; treat it as if it has the line // LB9 Do not break a combining character sequence; treat it as if it has the
// breaking class of the base character in all of the following rules. Treat ZWJ as // line breaking class of the base character in all of the
// if it were CM. // following rules. Treat ZWJ as if it were CM.
// Treat X (CM | ZWJ)* as if it were X. // Treat X (CM | ZWJ)* as if it were X.
// where X is any line break class except BK, CR, LF, NL, SP, or ZW. // where X is any line break class except BK, CR, LF, NL, SP, or ZW.
@ -296,7 +316,7 @@ impl<'a> Iterator for LineBreakCandidateIter<'a> {
continue; continue;
} }
WJ => { WJ => {
/*: LB11 Do not break before or after Word joiner and related characters.*/ /* : LB11 Do not break before or after Word joiner and related characters. */
*pos += grapheme.len(); *pos += grapheme.len();
/* Get next grapheme */ /* Get next grapheme */
if next_grapheme_class!(iter, grapheme).is_some() { if next_grapheme_class!(iter, grapheme).is_some() {
@ -305,7 +325,8 @@ impl<'a> Iterator for LineBreakCandidateIter<'a> {
continue; continue;
} }
GL => { GL => {
/*LB12 Non-breaking characters: LB12 Do not break after NBSP and related characters.*/ /* LB12 Non-breaking characters: LB12 Do not break after NBSP and related
* characters. */
*pos += grapheme.len(); *pos += grapheme.len();
continue; continue;
} }
@ -315,8 +336,8 @@ impl<'a> Iterator for LineBreakCandidateIter<'a> {
let next_class = get_class!(next_grapheme); let next_class = get_class!(next_grapheme);
match next_class { match next_class {
GL if ![SP, BA, HY].contains(&class) => { GL if ![SP, BA, HY].contains(&class) => {
/* LB12a Do not break before NBSP and related characters, except after spaces and /* LB12a Do not break before NBSP and related characters, except after
* hyphens. [^SP BA HY] × GL * spaces and hyphens. [^SP BA HY] × GL
* Also LB12 Do not break after NBSP and related characters */ * Also LB12 Do not break after NBSP and related characters */
*pos += grapheme.len(); *pos += grapheme.len();
continue; continue;
@ -384,8 +405,8 @@ impl<'a> Iterator for LineBreakCandidateIter<'a> {
if !text[idx + grapheme.len()..].even_after_spaces().is_empty() if !text[idx + grapheme.len()..].even_after_spaces().is_empty()
&& get_class!(text[idx + grapheme.len()..].even_after_spaces()) == NS => && get_class!(text[idx + grapheme.len()..].even_after_spaces()) == NS =>
{ {
/* LB16 Do not break between closing punctuation and a nonstarter (lb=NS), even with /* LB16 Do not break between closing punctuation and a nonstarter (lb=NS),
* intervening spaces. * even with intervening spaces.
* (CL | CP) SP* × NS */ * (CL | CP) SP* × NS */
*pos += grapheme.len(); *pos += grapheme.len();
while Some(SP) == next_grapheme_class!(iter, grapheme) { while Some(SP) == next_grapheme_class!(iter, grapheme) {
@ -397,7 +418,7 @@ impl<'a> Iterator for LineBreakCandidateIter<'a> {
&& get_class!(text[idx + grapheme.len()..].even_after_spaces()) == B2 => && get_class!(text[idx + grapheme.len()..].even_after_spaces()) == B2 =>
{ {
/* LB17 Do not break within ‘——’, even with intervening spaces. /* LB17 Do not break within ‘——’, even with intervening spaces.
* B2 SP* × B2*/ * B2 SP* × B2 */
*pos += grapheme.len(); *pos += grapheme.len();
continue; continue;
} }
@ -434,8 +455,9 @@ impl<'a> Iterator for LineBreakCandidateIter<'a> {
set_last_break!(*last_break, ret); set_last_break!(*last_break, ret);
return Some((ret, BreakAllowed)); return Some((ret, BreakAllowed));
} }
/* LB21 Do not break before hyphen-minus, other hyphens, fixed-width spaces, small /* LB21 Do not break before hyphen-minus, other hyphens, fixed-width spaces,
* kana, and other non-starters, or after acute accents. × BA, × HY, × NS, BB × */ * small kana, and other non-starters, or after acute accents. ×
* BA, × HY, × NS, BB × */
BB if !*break_now => { BB if !*break_now => {
*pos += grapheme.len(); *pos += grapheme.len();
continue; continue;
@ -447,8 +469,9 @@ impl<'a> Iterator for LineBreakCandidateIter<'a> {
let next_class = get_class!(next_grapheme); let next_class = get_class!(next_grapheme);
match next_class { match next_class {
BA | HY | NS => { BA | HY | NS => {
/* LB21 Do not break before hyphen-minus, other hyphens, fixed-width spaces, small /* LB21 Do not break before hyphen-minus, other hyphens, fixed-width
* kana, and other non-starters, or after acute accents. × BA, × HY, × NS, BB × */ * spaces, small kana, and other non-starters, or
* after acute accents. × BA, × HY, × NS, BB × */
*pos += grapheme.len(); *pos += grapheme.len();
//*pos += next_grapheme.len(); //*pos += next_grapheme.len();
continue; continue;
@ -485,7 +508,7 @@ impl<'a> Iterator for LineBreakCandidateIter<'a> {
self.iter.next(); self.iter.next();
continue; continue;
} }
/* EX × IN */ /* EX × IN */
EX if next_grapheme_class!((next_char is IN)) => { EX if next_grapheme_class!((next_char is IN)) => {
let (idx, next_grapheme) = next_char.unwrap(); let (idx, next_grapheme) = next_char.unwrap();
*pos = idx + next_grapheme.len(); *pos = idx + next_grapheme.len();
@ -497,21 +520,21 @@ impl<'a> Iterator for LineBreakCandidateIter<'a> {
*pos += grapheme.len(); *pos += grapheme.len();
continue; continue;
} }
/* (ID | EB | EM) × IN */ /* (ID | EB | EM) × IN */
ID | EB | EM if next_grapheme_class!((next_char is IN)) => { ID | EB | EM if next_grapheme_class!((next_char is IN)) => {
let (idx, next_grapheme) = next_char.unwrap(); let (idx, next_grapheme) = next_char.unwrap();
*pos = idx + next_grapheme.len(); *pos = idx + next_grapheme.len();
self.iter.next(); self.iter.next();
continue; continue;
} }
/* IN × IN */ /* IN × IN */
IN if next_grapheme_class!((next_char is IN)) => { IN if next_grapheme_class!((next_char is IN)) => {
let (idx, next_grapheme) = next_char.unwrap(); let (idx, next_grapheme) = next_char.unwrap();
*pos = idx + next_grapheme.len(); *pos = idx + next_grapheme.len();
self.iter.next(); self.iter.next();
continue; continue;
} }
/* NU × IN */ /* NU × IN */
NU if next_grapheme_class!((next_char is IN)) => { NU if next_grapheme_class!((next_char is IN)) => {
let (idx, next_grapheme) = next_char.unwrap(); let (idx, next_grapheme) = next_char.unwrap();
*pos = idx + next_grapheme.len(); *pos = idx + next_grapheme.len();
@ -533,8 +556,8 @@ impl<'a> Iterator for LineBreakCandidateIter<'a> {
self.iter.next(); self.iter.next();
continue; continue;
} }
/* LB23a Do not break between numeric prefixes and ideographs, or between ideographs /* LB23a Do not break between numeric prefixes and ideographs, or between
* and numeric postfixes. * ideographs and numeric postfixes.
* PR × (ID | EB | EM) */ * PR × (ID | EB | EM) */
PR if next_grapheme_class!((next_char is ID, EB, EM)) => { PR if next_grapheme_class!((next_char is ID, EB, EM)) => {
let (idx, next_grapheme) = next_char.unwrap(); let (idx, next_grapheme) = next_char.unwrap();
@ -558,7 +581,7 @@ impl<'a> Iterator for LineBreakCandidateIter<'a> {
self.iter.next(); self.iter.next();
continue; continue;
} }
/*(AL | HL) × (PR | PO) */ /* (AL | HL) × (PR | PO) */
AL | HL if next_grapheme_class!((next_char is PR, PO)) => { AL | HL if next_grapheme_class!((next_char is PR, PO)) => {
let (idx, next_grapheme) = next_char.unwrap(); let (idx, next_grapheme) = next_char.unwrap();
*pos = idx + next_grapheme.len(); *pos = idx + next_grapheme.len();
@ -749,9 +772,9 @@ impl<'a> Iterator for LineBreakCandidateIter<'a> {
continue; continue;
} }
RI => { RI => {
/* LB30a Break between two regional indicator symbols if and only if there are an /* LB30a Break between two regional indicator symbols if and only if there
* even number of regional indicators preceding the position of the break. * are an even number of regional indicators preceding
* sot (RI RI)* RI × RI * the position of the break. sot (RI RI)* RI × RI
* [^RI] (RI RI)* RI × RI */ * [^RI] (RI RI)* RI × RI */
*reg_ind_streak += 1; *reg_ind_streak += 1;
*pos += grapheme.len(); *pos += grapheme.len();
@ -852,8 +875,7 @@ mod tests {
pub use alg::linear; pub use alg::linear;
mod alg { mod alg {
use super::super::grapheme_clusters::TextProcessing; use super::super::{grapheme_clusters::TextProcessing, *};
use super::super::*;
fn cost(i: usize, j: usize, width: usize, minima: &[usize], offsets: &[usize]) -> usize { fn cost(i: usize, j: usize, width: usize, minima: &[usize], offsets: &[usize]) -> usize {
let w = offsets[j] + j - offsets[i] - i - 1; let w = offsets[j] + j - offsets[i] - i - 1;
if w > width { if w > width {
@ -1060,7 +1082,7 @@ pub fn split_lines_reflow(text: &str, reflow: Reflow, width: Option<usize>) -> V
} else if prev_quote_depth == quote_depth { } else if prev_quote_depth == quote_depth {
/* This becomes part of the paragraph we're in */ /* This becomes part of the paragraph we're in */
} else { } else {
/*Malformed line, different quote depths can't be in the same paragraph. */ /* Malformed line, different quote depths can't be in the same paragraph. */
let paragraph = &text[paragraph_start..prev_index]; let paragraph = &text[paragraph_start..prev_index];
reflow_helper(&mut ret, paragraph, prev_quote_depth, in_paragraph, width); reflow_helper(&mut ret, paragraph, prev_quote_depth, in_paragraph, width);
@ -1071,7 +1093,7 @@ pub fn split_lines_reflow(text: &str, reflow: Reflow, width: Option<usize>) -> V
let paragraph = &text[paragraph_start..*i]; let paragraph = &text[paragraph_start..*i];
reflow_helper(&mut ret, paragraph, quote_depth, in_paragraph, width); reflow_helper(&mut ret, paragraph, quote_depth, in_paragraph, width);
} else { } else {
/*Malformed line, different quote depths can't be in the same paragraph. */ /* Malformed line, different quote depths can't be in the same paragraph. */
let paragraph = &text[paragraph_start..prev_index]; let paragraph = &text[paragraph_start..prev_index];
reflow_helper(&mut ret, paragraph, prev_quote_depth, in_paragraph, width); reflow_helper(&mut ret, paragraph, prev_quote_depth, in_paragraph, width);
let paragraph = &text[prev_index..*i]; let paragraph = &text[prev_index..*i];
@ -1248,12 +1270,13 @@ easy to take MORE than nothing.'"#;
} }
mod segment_tree { mod segment_tree {
/*! Simple segment tree implementation for maximum in range queries. This is useful if given an /*! Simple segment tree implementation for maximum in range queries. This
* array of numbers you want to get the maximum value inside an interval quickly. * is useful if given an array of numbers you want to get the
* maximum value inside an interval quickly.
*/ */
use std::{convert::TryFrom, iter::FromIterator};
use smallvec::SmallVec; use smallvec::SmallVec;
use std::convert::TryFrom;
use std::iter::FromIterator;
#[derive(Default, Debug, Clone)] #[derive(Default, Debug, Clone)]
pub(super) struct SegmentTree { pub(super) struct SegmentTree {
@ -1329,8 +1352,9 @@ mod segment_tree {
} }
} }
/// A lazy stateful iterator for line breaking text. Useful for very long text where you don't want /// A lazy stateful iterator for line breaking text. Useful for very long text
/// to linebreak it completely before user requests specific lines. /// where you don't want to linebreak it completely before user requests
/// specific lines.
#[derive(Debug, Clone)] #[derive(Debug, Clone)]
pub struct LineBreakText { pub struct LineBreakText {
text: String, text: String,
@ -1511,7 +1535,8 @@ impl Iterator for LineBreakText {
} else if prev_quote_depth == quote_depth { } else if prev_quote_depth == quote_depth {
/* This becomes part of the paragraph we're in */ /* This becomes part of the paragraph we're in */
} else { } else {
/*Malformed line, different quote depths can't be in the same paragraph. */ /* Malformed line, different quote depths can't be in the same
* paragraph. */
let paragraph_s = &self.text[paragraph_start..prev_index]; let paragraph_s = &self.text[paragraph_start..prev_index];
reflow_helper2( reflow_helper2(
&mut paragraph, &mut paragraph,
@ -1534,7 +1559,8 @@ impl Iterator for LineBreakText {
self.width, self.width,
); );
} else { } else {
/*Malformed line, different quote depths can't be in the same paragraph. */ /* Malformed line, different quote depths can't be in the same
* paragraph. */
let paragraph_s = &self.text[paragraph_start..prev_index]; let paragraph_s = &self.text[paragraph_start..prev_index];
reflow_helper2( reflow_helper2(
&mut paragraph, &mut paragraph,

View File

@ -19,10 +19,10 @@
* along with meli. If not, see <http://www.gnu.org/licenses/>. * along with meli. If not, see <http://www.gnu.org/licenses/>.
*/ */
use super::TextProcessing;
use smallvec::SmallVec; use smallvec::SmallVec;
use super::TextProcessing;
pub trait KMP { pub trait KMP {
fn kmp_search(&self, pattern: &str) -> SmallVec<[usize; 256]>; fn kmp_search(&self, pattern: &str) -> SmallVec<[usize; 256]>;
fn kmp_table(graphemes: &[&str]) -> SmallVec<[i32; 256]> { fn kmp_table(graphemes: &[&str]) -> SmallVec<[i32; 256]> {

View File

@ -123,15 +123,10 @@ impl From<&str> for LineBreakClass {
} }
} }
#[derive(PartialEq, Eq, Debug, Copy, Clone)] #[derive(PartialEq, Eq, Debug, Copy, Clone, Default)]
pub enum Reflow { pub enum Reflow {
No, No,
All, All,
#[default]
FormatFlowed, FormatFlowed,
} }
impl Default for Reflow {
fn default() -> Self {
Reflow::FormatFlowed
}
}

View File

@ -48,7 +48,10 @@ pub struct CodePointsIterator<'a> {
} }
/* /*
* UTF-8 uses a system of binary prefixes, in which the high bits of each byte mark whether its a single byte, the beginning of a multi-byte sequence, or a continuation byte; the remaining bits, concatenated, give the code point index. This table shows how it works: * UTF-8 uses a system of binary prefixes, in which the high bits of each
* byte mark whether its a single byte, the beginning of a multi-byte
* sequence, or a continuation byte; the remaining bits, concatenated, give
* the code point index. This table shows how it works:
* *
* UTF-8 (binary) Code point (binary) Range * UTF-8 (binary) Code point (binary) Range
* 0xxxxxxx xxxxxxx U+0000U+007F * 0xxxxxxx xxxxxxx U+0000U+007F

View File

@ -25,36 +25,38 @@
* This module implements Jamie Zawinski's [threading algorithm](https://www.jwz.org/doc/threading.html). Quoted comments (/* " .. " */) are * This module implements Jamie Zawinski's [threading algorithm](https://www.jwz.org/doc/threading.html). Quoted comments (/* " .. " */) are
* taken almost verbatim from the algorithm. * taken almost verbatim from the algorithm.
* *
* The entry point of this module is the `Threads` struct and its `new` method. It contains * The entry point of this module is the `Threads` struct and its `new`
* `ThreadNodes` which are the nodes in the thread trees that might have messages associated with * method. It contains `ThreadNodes` which are the nodes in the thread trees
* them. The root nodes (first messages in each thread) are stored in `root_set` and `tree` * that might have messages associated with them. The root nodes (first
* vectors. `Threads` has inner mutability since we need to sort without the user having mutable * messages in each thread) are stored in `root_set` and `tree`
* ownership. * vectors. `Threads` has inner mutability since we need to sort without the
* user having mutable ownership.
*/ */
use crate::datetime::UnixTimestamp; use crate::{
use crate::email::address::StrBuild; datetime::UnixTimestamp,
use crate::email::parser::BytesExt; email::{address::StrBuild, parser::BytesExt, *},
use crate::email::*; };
mod iterators; mod iterators;
use std::{
cmp::Ordering,
collections::{HashMap, HashSet},
fmt,
iter::FromIterator,
ops::Index,
result::Result as StdResult,
str::FromStr,
string::ToString,
sync::{Arc, RwLock},
};
pub use iterators::*; pub use iterators::*;
use smallvec::SmallVec;
use uuid::Uuid;
#[cfg(feature = "unicode_algorithms")] #[cfg(feature = "unicode_algorithms")]
use crate::text_processing::grapheme_clusters::*; use crate::text_processing::grapheme_clusters::*;
use uuid::Uuid;
use std::cmp::Ordering;
use std::collections::{HashMap, HashSet};
use std::fmt;
use std::iter::FromIterator;
use std::ops::Index;
use std::result::Result as StdResult;
use std::str::FromStr;
use std::string::ToString;
use std::sync::{Arc, RwLock};
use smallvec::SmallVec;
type Envelopes = Arc<RwLock<HashMap<EnvelopeHash, Envelope>>>; type Envelopes = Arc<RwLock<HashMap<EnvelopeHash, Envelope>>>;
@ -120,24 +122,35 @@ macro_rules! make {
let old_group_hash = $threads.find_group($threads.thread_nodes[&$c].group); let old_group_hash = $threads.find_group($threads.thread_nodes[&$c].group);
let parent_group_hash = $threads.find_group($threads.thread_nodes[&$p].group); let parent_group_hash = $threads.find_group($threads.thread_nodes[&$p].group);
if old_group_hash != parent_group_hash { if old_group_hash != parent_group_hash {
if let Some(old_env_hashes) = $threads.thread_to_envelope.get(&old_group_hash).cloned() { if let Some(old_env_hashes) = $threads.thread_to_envelope.get(&old_group_hash).cloned()
{
for &env_hash in &old_env_hashes { for &env_hash in &old_env_hashes {
*$threads.envelope_to_thread.entry(env_hash).or_default() = parent_group_hash; *$threads.envelope_to_thread.entry(env_hash).or_default() = parent_group_hash;
} }
$threads.thread_to_envelope.entry(parent_group_hash).or_default().extend(old_env_hashes.into_iter()); $threads
.thread_to_envelope
.entry(parent_group_hash)
.or_default()
.extend(old_env_hashes.into_iter());
} }
let prev_parent = remove_from_parent!(&mut $threads.thread_nodes, $c); let prev_parent = remove_from_parent!(&mut $threads.thread_nodes, $c);
if !($threads.thread_nodes[&$p]).children.contains(&$c) { if !($threads.thread_nodes[&$p]).children.contains(&$c) {
/* Pruned nodes keep their children in case they show up in a later merge, so do not panic /* Pruned nodes keep their children in case they show up in a later merge, so
* if children exists */ * do not panic if children exists */
$threads.thread_nodes.entry($p).and_modify(|e| e.children.push($c)); $threads
.thread_nodes
.entry($p)
.and_modify(|e| e.children.push($c));
} }
$threads.thread_nodes.entry($c).and_modify(|e| { $threads.thread_nodes.entry($c).and_modify(|e| {
e.parent = Some($p); e.parent = Some($p);
}); });
let old_group = std::mem::replace($threads.groups.entry(old_group_hash).or_default(), ThreadGroup::Node { let old_group = std::mem::replace(
parent: Arc::new(RwLock::new(parent_group_hash)), $threads.groups.entry(old_group_hash).or_default(),
}); ThreadGroup::Node {
parent: Arc::new(RwLock::new(parent_group_hash)),
},
);
$threads.thread_nodes.entry($c).and_modify(|e| { $threads.thread_nodes.entry($c).and_modify(|e| {
e.group = parent_group_hash; e.group = parent_group_hash;
}); });
@ -147,21 +160,24 @@ macro_rules! make {
{ {
let parent_group = $threads.thread_ref_mut(parent_group_hash); let parent_group = $threads.thread_ref_mut(parent_group_hash);
match (parent_group, old_group) { match (parent_group, old_group) {
(Thread { (
ref mut date, Thread {
ref mut len, ref mut date,
ref mut unseen, ref mut len,
ref mut snoozed, ref mut unseen,
ref mut attachments, ref mut snoozed,
.. ref mut attachments,
}, ThreadGroup::Root(Thread { ..
date: old_date, },
len: old_len, ThreadGroup::Root(Thread {
unseen: old_unseen, date: old_date,
snoozed: old_snoozed, len: old_len,
attachments: old_attachments, unseen: old_unseen,
.. snoozed: old_snoozed,
})) => { attachments: old_attachments,
..
}),
) => {
*date = std::cmp::max(old_date, *date); *date = std::cmp::max(old_date, *date);
*len += old_len; *len += old_len;
*unseen += old_unseen; *unseen += old_unseen;
@ -169,13 +185,13 @@ macro_rules! make {
*snoozed |= old_snoozed; *snoozed |= old_snoozed;
} }
_ => unreachable!(), _ => unreachable!(),
} }
} }
prev_parent prev_parent
} else { } else {
None None
} }
}}; }};
} }
/// Strip common prefixes from subjects /// Strip common prefixes from subjects
@ -185,9 +201,15 @@ macro_rules! make {
/// use melib::thread::SubjectPrefix; /// use melib::thread::SubjectPrefix;
/// ///
/// let mut subject = "Re: RE: Res: Re: Res: Subject"; /// let mut subject = "Re: RE: Res: Re: Res: Subject";
/// assert_eq!(subject.strip_prefixes_from_list(<&str>::USUAL_PREFIXES, None), &"Subject"); /// assert_eq!(
/// subject.strip_prefixes_from_list(<&str>::USUAL_PREFIXES, None),
/// &"Subject"
/// );
/// let mut subject = "Re: RE: Res: Re: Res: Subject"; /// let mut subject = "Re: RE: Res: Re: Res: Subject";
/// assert_eq!(subject.strip_prefixes_from_list(<&str>::USUAL_PREFIXES, Some(1)), &"RE: Res: Re: Res: Subject"); /// assert_eq!(
/// subject.strip_prefixes_from_list(<&str>::USUAL_PREFIXES, Some(1)),
/// &"RE: Res: Re: Res: Subject"
/// );
/// ``` /// ```
pub trait SubjectPrefix { pub trait SubjectPrefix {
const USUAL_PREFIXES: &'static [&'static str] = &[ const USUAL_PREFIXES: &'static [&'static str] = &[
@ -199,7 +221,7 @@ pub trait SubjectPrefix {
"Fw:", "Fw:",
/* taken from /* taken from
* https://en.wikipedia.org/wiki/List_of_email_subject_abbreviations#Abbreviations_in_other_languages * https://en.wikipedia.org/wiki/List_of_email_subject_abbreviations#Abbreviations_in_other_languages
* */ */
"回复:", "回复:",
"回覆:", "回覆:",
// Dutch (Antwoord) // Dutch (Antwoord)
@ -919,8 +941,9 @@ impl Threads {
.unwrap() .unwrap()
.set_thread(thread_hash); .set_thread(thread_hash);
/* If thread node currently has a message from a foreign mailbox and env_hash is /* If thread node currently has a message from a foreign mailbox and env_hash
* from current mailbox we want to update it, otherwise return */ * is from current mailbox we want to update it, otherwise
* return */
if node.other_mailbox || other_mailbox { if node.other_mailbox || other_mailbox {
return false; return false;
} }

View File

@ -19,12 +19,14 @@
* along with meli. If not, see <http://www.gnu.org/licenses/>. * along with meli. If not, see <http://www.gnu.org/licenses/>.
*/ */
use super::{ThreadNode, ThreadNodeHash};
use smallvec::SmallVec;
use std::collections::HashMap; use std::collections::HashMap;
/* `ThreadsIterator` returns messages according to the sorted order. For example, for the following use smallvec::SmallVec;
* threads:
use super::{ThreadNode, ThreadNodeHash};
/* `ThreadsIterator` returns messages according to the sorted order. For
* example, for the following threads:
* *
* ``` * ```
* A_ * A_
@ -82,8 +84,8 @@ impl<'a> Iterator for ThreadsGroupIterator<'a> {
} }
} }
} }
/* `ThreadIterator` returns messages of a specific thread according to the sorted order. For example, for the following /* `ThreadIterator` returns messages of a specific thread according to the
* thread: * sorted order. For example, for the following thread:
* *
* ``` * ```
* A_ * A_

View File

@ -1 +1,7 @@
edition = "2018" edition = "2018"
format_generated_files = false
format_code_in_doc_comments = true
format_strings = true
imports_granularity = "Crate"
group_imports = "StdExternalCrate"
wrap_comments = true

View File

@ -67,7 +67,9 @@ pub enum SubCommand {
PrintLoadedThemes, PrintLoadedThemes,
/// edit configuration files in `$EDITOR`/`$VISUAL`. /// edit configuration files in `$EDITOR`/`$VISUAL`.
EditConfig, EditConfig,
/// create a sample configuration file with available configuration options. If PATH is not specified, meli will try to create it in $XDG_CONFIG_HOME/meli/config.toml /// create a sample configuration file with available configuration options.
/// If PATH is not specified, meli will try to create it in
/// $XDG_CONFIG_HOME/meli/config.toml
#[structopt(display_order = 1)] #[structopt(display_order = 1)]
CreateConfig { CreateConfig {
#[structopt(value_name = "NEW_CONFIG_PATH", parse(from_os_str))] #[structopt(value_name = "NEW_CONFIG_PATH", parse(from_os_str))]

View File

@ -20,35 +20,42 @@
*/ */
/*! A parser module for user commands passed through Command mode. /*! A parser module for user commands passed through Command mode.
*/ */
use crate::melib::parser::BytesExt;
use melib::nom::{
self,
branch::alt,
bytes::complete::{is_a, is_not, tag, take_until},
character::complete::{digit1, not_line_ending},
combinator::{map, map_res},
error::Error as NomError,
multi::separated_list1,
sequence::{pair, preceded, separated_pair},
IResult,
};
pub use melib::thread::{SortField, SortOrder}; pub use melib::thread::{SortField, SortOrder};
use melib::Error; use melib::{
nom::{
self,
branch::alt,
bytes::complete::{is_a, is_not, tag, take_until},
character::complete::{digit1, not_line_ending},
combinator::{map, map_res},
error::Error as NomError,
multi::separated_list1,
sequence::{pair, preceded, separated_pair},
IResult,
},
Error,
};
use crate::melib::parser::BytesExt;
pub mod actions; pub mod actions;
use actions::MailboxOperation;
use std::collections::HashSet; use std::collections::HashSet;
use actions::MailboxOperation;
pub mod history; pub mod history;
pub use crate::actions::AccountAction::{self, *};
pub use crate::actions::Action::{self, *};
pub use crate::actions::ComposeAction::{self, *};
pub use crate::actions::ListingAction::{self, *};
pub use crate::actions::MailingListAction::{self, *};
pub use crate::actions::TabAction::{self, *};
pub use crate::actions::TagAction::{self, *};
pub use crate::actions::ViewAction::{self, *};
use std::str::FromStr; use std::str::FromStr;
pub use crate::actions::{
AccountAction::{self, *},
Action::{self, *},
ComposeAction::{self, *},
ListingAction::{self, *},
MailingListAction::{self, *},
TabAction::{self, *},
TagAction::{self, *},
ViewAction::{self, *},
};
/// Helper macro to convert an array of tokens into a TokenStream /// Helper macro to convert an array of tokens into a TokenStream
macro_rules! to_stream { macro_rules! to_stream {
($token: expr) => { ($token: expr) => {
@ -63,7 +70,8 @@ macro_rules! to_stream {
}; };
} }
/// Macro to create a const table with every command part that can be auto-completed and its description /// Macro to create a const table with every command part that can be
/// auto-completed and its description
macro_rules! define_commands { macro_rules! define_commands {
( [$({ tags: [$( $tags:literal),*], desc: $desc:literal, tokens: $tokens:expr, parser: ($parser:item)}),*]) => { ( [$({ tags: [$( $tags:literal),*], desc: $desc:literal, tokens: $tokens:expr, parser: ($parser:item)}),*]) => {
pub const COMMAND_COMPLETION: &[(&str, &str, TokenStream)] = &[$($( ($tags, $desc, TokenStream { tokens: $tokens } ) ),*),* ]; pub const COMMAND_COMPLETION: &[(&str, &str, TokenStream)] = &[$($( ($tags, $desc, TokenStream { tokens: $tokens } ) ),*),* ];
@ -142,7 +150,8 @@ impl TokenStream {
| t @ QuotedStringValue | t @ QuotedStringValue
| t @ AlphanumericStringValue => { | t @ AlphanumericStringValue => {
let _t = t; let _t = t;
//sugg.insert(format!("{}{:?}", if s.is_empty() { " " } else { "" }, t)); //sugg.insert(format!("{}{:?}", if s.is_empty() { " " }
// else { "" }, t));
} }
} }
tokens.push((*s, *t.inner())); tokens.push((*s, *t.inner()));
@ -209,7 +218,8 @@ impl TokenStream {
} }
} }
/// `Token` wrapper that defines how many times a token is expected to be repeated /// `Token` wrapper that defines how many times a token is expected to be
/// repeated
#[derive(Debug, Copy, Clone)] #[derive(Debug, Copy, Clone)]
pub enum TokenAdicity { pub enum TokenAdicity {
ZeroOrOne(Token), ZeroOrOne(Token),

View File

@ -23,10 +23,12 @@
* User actions that need to be handled by the UI * User actions that need to be handled by the UI
*/ */
use crate::components::Component; use std::path::PathBuf;
pub use melib::thread::{SortField, SortOrder}; pub use melib::thread::{SortField, SortOrder};
use melib::uuid::Uuid; use melib::uuid::Uuid;
use std::path::PathBuf;
use crate::components::Component;
#[derive(Debug)] #[derive(Debug)]
pub enum TagAction { pub enum TagAction {

View File

@ -19,9 +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 std::fs::OpenOptions; use std::{
use std::io::{Read, Write}; fs::OpenOptions,
use std::sync::{Arc, Mutex}; io::{Read, Write},
sync::{Arc, Mutex},
};
thread_local!(static CMD_HISTORY_FILE: Arc<Mutex<std::fs::File>> = Arc::new(Mutex::new({ thread_local!(static CMD_HISTORY_FILE: Arc<Mutex<std::fs::File>> = Arc::new(Mutex::new({
let data_dir = xdg::BaseDirectories::with_prefix("meli").unwrap(); let data_dir = xdg::BaseDirectories::with_prefix("meli").unwrap();

View File

@ -21,13 +21,16 @@
/*! Components visual and logical separations of application interfaces. /*! Components visual and logical separations of application interfaces.
* *
* They can draw on the terminal and receive events, but also do other stuff as well. (For example, see the `notifications` module.) * They can draw on the terminal and receive events, but also do other stuff
* as well. (For example, see the `notifications` module.)
* See the `Component` Trait for more details. * See the `Component` Trait for more details.
*/ */
use super::*; use super::*;
use crate::melib::text_processing::{TextProcessing, Truncate}; use crate::{
use crate::terminal::boundaries::*; melib::text_processing::{TextProcessing, Truncate},
terminal::boundaries::*,
};
pub mod mail; pub mod mail;
pub use crate::mail::*; pub use crate::mail::*;
@ -46,8 +49,10 @@ pub use self::mailbox_management::*;
#[cfg(feature = "svgscreenshot")] #[cfg(feature = "svgscreenshot")]
pub mod svg; pub mod svg;
use std::fmt; use std::{
use std::fmt::{Debug, Display}; fmt,
fmt::{Debug, Display},
};
use indexmap::IndexMap; use indexmap::IndexMap;
use uuid::Uuid; use uuid::Uuid;
@ -86,8 +91,9 @@ pub enum ScrollUpdate {
} }
/// Types implementing this Trait can draw on the terminal and receive events. /// Types implementing this Trait can draw on the terminal and receive events.
/// If a type wants to skip drawing if it has not changed anything, it can hold some flag in its /// If a type wants to skip drawing if it has not changed anything, it can hold
/// fields (eg self.dirty = false) and act upon that in their `draw` implementation. /// some flag in its fields (eg self.dirty = false) and act upon that in their
/// `draw` implementation.
pub trait Component: Display + Debug + Send + Sync { pub trait Component: Display + Debug + Send + Sync {
fn draw(&mut self, grid: &mut CellBuffer, area: Area, context: &mut Context); fn draw(&mut self, grid: &mut CellBuffer, area: Area, context: &mut Context);
fn process_event(&mut self, event: &mut UIEvent, context: &mut Context) -> bool; fn process_event(&mut self, event: &mut UIEvent, context: &mut Context) -> bool;

View File

@ -19,9 +19,10 @@
* along with meli. If not, see <http://www.gnu.org/licenses/>. * along with meli. If not, see <http://www.gnu.org/licenses/>.
*/ */
use super::*;
use std::collections::HashMap; use std::collections::HashMap;
use super::*;
mod contact_list; mod contact_list;
pub use self::contact_list::*; pub use self::contact_list::*;

View File

@ -18,12 +18,12 @@
* 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 std::cmp;
use melib::{backends::AccountHash, CardId};
use super::*; use super::*;
use crate::melib::text_processing::TextProcessing; use crate::melib::text_processing::TextProcessing;
use melib::backends::AccountHash;
use melib::CardId;
use std::cmp;
#[derive(Debug, PartialEq, Eq)] #[derive(Debug, PartialEq, Eq)]
enum ViewMode { enum ViewMode {
@ -69,7 +69,7 @@ pub struct ContactList {
impl fmt::Display for ContactList { impl fmt::Display for ContactList {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
write!(f, "{}", "contact list") write!(f, "contact list")
} }
} }
@ -436,8 +436,8 @@ impl ContactList {
))); )));
} }
/* If cursor position has changed, remove the highlight from the previous position and /* If cursor position has changed, remove the highlight from the previous
* apply it in the new one. */ * position and apply it in the new one. */
if self.cursor_pos != self.new_cursor_pos && prev_page_no == page_no { if self.cursor_pos != self.new_cursor_pos && prev_page_no == page_no {
let old_cursor_pos = self.cursor_pos; let old_cursor_pos = self.cursor_pos;
self.cursor_pos = self.new_cursor_pos; self.cursor_pos = self.new_cursor_pos;
@ -464,7 +464,7 @@ impl ContactList {
let width = width!(area); let width = width!(area);
self.data_columns.widths = Default::default(); self.data_columns.widths = Default::default();
self.data_columns.widths[0] = self.data_columns.columns[0].size().0; /* name */ self.data_columns.widths[0] = self.data_columns.columns[0].size().0; /* name */
self.data_columns.widths[1] = self.data_columns.columns[1].size().0; /* email*/ self.data_columns.widths[1] = self.data_columns.columns[1].size().0; /* email */
self.data_columns.widths[2] = self.data_columns.columns[2].size().0; /* url */ self.data_columns.widths[2] = self.data_columns.columns[2].size().0; /* url */
self.data_columns.widths[3] = self.data_columns.columns[3].size().0; /* source */ self.data_columns.widths[3] = self.data_columns.columns[3].size().0; /* source */
@ -783,7 +783,7 @@ impl Component for ContactList {
.push_back(UIEvent::StatusEvent(StatusEvent::BufClear)); .push_back(UIEvent::StatusEvent(StatusEvent::BufClear));
return true; return true;
} }
UIEvent::Input(Key::Char(c)) if ('0'..='9').contains(&c) => { UIEvent::Input(Key::Char(c)) if c.is_ascii_digit() => {
self.cmd_buf.push(c); self.cmd_buf.push(c);
context context
.replies .replies

View File

@ -21,10 +21,13 @@
/*! Entities that handle Mail specific functions. /*! Entities that handle Mail specific functions.
*/ */
use melib::{
backends::{AccountHash, Mailbox, MailboxHash},
email::{attachment_types::*, attachments::*},
thread::ThreadNodeHash,
};
use super::*; use super::*;
use melib::backends::{AccountHash, Mailbox, MailboxHash};
use melib::email::{attachment_types::*, attachments::*};
use melib::thread::ThreadNodeHash;
pub mod listing; pub mod listing;
pub use crate::listing::*; pub use crate::listing::*;

View File

@ -19,21 +19,23 @@
* along with meli. If not, see <http://www.gnu.org/licenses/>. * along with meli. If not, see <http://www.gnu.org/licenses/>.
*/ */
use super::*; use std::{
use melib::email::attachment_types::{ContentType, MultipartType}; convert::TryInto,
use melib::list_management; future::Future,
use melib::Draft; pin::Pin,
process::{Command, Stdio},
sync::{Arc, Mutex},
};
use crate::conf::accounts::JobRequest;
use crate::jobs::JoinHandle;
use crate::terminal::embed::EmbedTerminal;
use indexmap::IndexSet; use indexmap::IndexSet;
use melib::{
email::attachment_types::{ContentType, MultipartType},
list_management, Draft,
};
use nix::sys::wait::WaitStatus; use nix::sys::wait::WaitStatus;
use std::convert::TryInto;
use std::future::Future; use super::*;
use std::pin::Pin; use crate::{conf::accounts::JobRequest, jobs::JoinHandle, terminal::embed::EmbedTerminal};
use std::process::{Command, Stdio};
use std::sync::{Arc, Mutex};
#[cfg(feature = "gpgme")] #[cfg(feature = "gpgme")]
mod gpg; mod gpg;
@ -373,8 +375,9 @@ impl Composer {
let mut ret = Composer::reply_to(coordinates, reply_body, context, false); let mut ret = Composer::reply_to(coordinates, reply_body, context, false);
let account = &context.accounts[&coordinates.0]; let account = &context.accounts[&coordinates.0];
let parent_message = account.collection.get_env(coordinates.2); let parent_message = account.collection.get_env(coordinates.2);
/* If message is from a mailing list and we detect a List-Post header, ask user if they /* If message is from a mailing list and we detect a List-Post header, ask
* want to reply to the mailing list or the submitter of the message */ * user if they want to reply to the mailing list or the submitter of
* the message */
if let Some(actions) = list_management::ListActions::detect(&parent_message) { if let Some(actions) = list_management::ListActions::detect(&parent_message) {
if let Some(post) = actions.post { if let Some(post) = actions.post {
if let list_management::ListAction::Email(list_post_addr) = post[0] { if let list_management::ListAction::Email(list_post_addr) = post[0] {
@ -1104,20 +1107,27 @@ impl Component for Composer {
))); )));
self.mode = ViewMode::WaitingForSendResult( self.mode = ViewMode::WaitingForSendResult(
UIDialog::new( UIDialog::new(
"Waiting for confirmation.. The tab will close automatically on successful submission.", "Waiting for confirmation.. The tab will close automatically \
on successful submission.",
vec![ vec![
('c', "force close tab".to_string()), ('c', "force close tab".to_string()),
('n', "close this message and return to edit mode".to_string()), (
'n',
"close this message and return to edit mode"
.to_string(),
),
], ],
true, true,
Some(Box::new(move |id: ComponentId, results: &[char]| { Some(Box::new(move |id: ComponentId, results: &[char]| {
Some(UIEvent::FinishedUIDialog( Some(UIEvent::FinishedUIDialog(
id, id,
Box::new(results.first().cloned().unwrap_or('c')), Box::new(results.first().cloned().unwrap_or('c')),
)) ))
})), })),
context, context,
), handle); ),
handle,
);
} }
Err(err) => { Err(err) => {
context.replies.push_back(UIEvent::Notification( context.replies.push_back(UIEvent::Notification(
@ -1680,10 +1690,12 @@ impl Component for Composer {
match std::env::var("EDITOR") { match std::env::var("EDITOR") {
Err(err) => { Err(err) => {
context.replies.push_back(UIEvent::Notification( context.replies.push_back(UIEvent::Notification(
Some(err.to_string()), Some(err.to_string()),
"$EDITOR is not set. You can change an envvar's value with setenv or set composing.editor_command setting in your configuration.".to_string(), "$EDITOR is not set. You can change an envvar's value with setenv \
Some(NotificationType::Error(melib::error::ErrorKind::None)), or set composing.editor_command setting in your configuration."
)); .to_string(),
Some(NotificationType::Error(melib::error::ErrorKind::None)),
));
return true; return true;
} }
Ok(v) => v, Ok(v) => v,
@ -1744,7 +1756,7 @@ impl Component for Composer {
DEBUG, DEBUG,
); );
match Command::new("sh") match Command::new("sh")
.args(&["-c", &editor_command]) .args(["-c", &editor_command])
.stdin(Stdio::inherit()) .stdin(Stdio::inherit())
.stdout(Stdio::inherit()) .stdout(Stdio::inherit())
.spawn() .spawn()
@ -1798,7 +1810,7 @@ impl Component for Composer {
} }
let f = create_temp_file(&[], None, None, true); let f = create_temp_file(&[], None, None, true);
match Command::new("sh") match Command::new("sh")
.args(&["-c", command]) .args(["-c", command])
.stdin(Stdio::null()) .stdin(Stdio::null())
.stdout(Stdio::from(f.file())) .stdout(Stdio::from(f.file()))
.spawn() .spawn()
@ -1886,7 +1898,7 @@ impl Component for Composer {
DEBUG, DEBUG,
); );
match Command::new("sh") match Command::new("sh")
.args(&["-c", command]) .args(["-c", command])
.stdin(Stdio::inherit()) .stdin(Stdio::inherit())
.stdout(Stdio::inherit()) .stdout(Stdio::inherit())
.stderr(Stdio::piped()) .stderr(Stdio::piped())

View File

@ -21,18 +21,13 @@
use super::*; use super::*;
#[derive(Debug, Copy, Clone, PartialEq, Eq)] #[derive(Debug, Copy, Clone, PartialEq, Eq, Default)]
pub enum EditAttachmentCursor { pub enum EditAttachmentCursor {
AttachmentNo(usize), AttachmentNo(usize),
#[default]
Buttons, Buttons,
} }
impl Default for EditAttachmentCursor {
fn default() -> Self {
EditAttachmentCursor::Buttons
}
}
#[derive(Debug)] #[derive(Debug)]
pub enum EditAttachmentMode { pub enum EditAttachmentMode {
Overview, Overview,

View File

@ -19,27 +19,32 @@
* along with meli. If not, see <http://www.gnu.org/licenses/>. * along with meli. If not, see <http://www.gnu.org/licenses/>.
*/ */
use super::*; use std::{
use crate::conf::accounts::JobRequest; collections::{BTreeSet, HashMap, HashSet},
use crate::types::segment_tree::SegmentTree; convert::TryFrom,
ops::{Deref, DerefMut},
};
use melib::backends::EnvelopeHashBatch; use melib::backends::EnvelopeHashBatch;
use smallvec::SmallVec; use smallvec::SmallVec;
use std::collections::{BTreeSet, HashMap, HashSet};
use std::convert::TryFrom;
use std::ops::{Deref, DerefMut};
// TODO: emoji_text_presentation_selector should be printed along with the chars before it but not use super::*;
// as a separate Cell use crate::{conf::accounts::JobRequest, types::segment_tree::SegmentTree};
// TODO: emoji_text_presentation_selector should be printed along with the chars
// before it but not as a separate Cell
//macro_rules! emoji_text_presentation_selector { //macro_rules! emoji_text_presentation_selector {
// () => { // () => {
// "\u{FE0E}" // "\u{FE0E}"
// }; // };
//} //}
// //
//pub const DEFAULT_ATTACHMENT_FLAG: &str = concat!("📎", emoji_text_presentation_selector!()); //pub const DEFAULT_ATTACHMENT_FLAG: &str = concat!("📎",
//pub const DEFAULT_SELECTED_FLAG: &str = concat!("☑️", emoji_text_presentation_selector!()); // emoji_text_presentation_selector!()); pub const DEFAULT_SELECTED_FLAG: &str =
//pub const DEFAULT_UNSEEN_FLAG: &str = concat!("●", emoji_text_presentation_selector!()); // concat!("☑️", emoji_text_presentation_selector!());
//pub const DEFAULT_SNOOZED_FLAG: &str = concat!("💤", emoji_text_presentation_selector!()); // pub const DEFAULT_UNSEEN_FLAG: &str = concat!("●",
// emoji_text_presentation_selector!()); pub const DEFAULT_SNOOZED_FLAG: &str =
// concat!("💤", emoji_text_presentation_selector!());
pub const DEFAULT_ATTACHMENT_FLAG: &str = "📎"; pub const DEFAULT_ATTACHMENT_FLAG: &str = "📎";
pub const DEFAULT_SELECTED_FLAG: &str = "☑️"; pub const DEFAULT_SELECTED_FLAG: &str = "☑️";
@ -225,20 +230,15 @@ pub enum Focus {
EntryFullscreen, EntryFullscreen,
} }
#[derive(Debug, Copy, PartialEq, Eq, Clone)] #[derive(Debug, Copy, PartialEq, Eq, Clone, Default)]
pub enum Modifier { pub enum Modifier {
#[default]
SymmetricDifference, SymmetricDifference,
Union, Union,
Difference, Difference,
Intersection, Intersection,
} }
impl Default for Modifier {
fn default() -> Self {
Modifier::SymmetricDifference
}
}
#[derive(Debug, Default)] #[derive(Debug, Default)]
/// Save theme colors to avoid looking them up again and again from settings /// Save theme colors to avoid looking them up again and again from settings
pub struct ColorCache { pub struct ColorCache {
@ -601,10 +601,9 @@ pub trait MailListingTrait: ListingTrait {
} }
} }
ListingAction::ExportMbox(format, ref path) => { ListingAction::ExportMbox(format, ref path) => {
use std::{future::Future, io::Write, pin::Pin};
use futures::future::try_join_all; use futures::future::try_join_all;
use std::future::Future;
use std::io::Write;
use std::pin::Pin;
let futures: Result<Vec<_>> = envs_to_set let futures: Result<Vec<_>> = envs_to_set
.iter() .iter()
@ -725,7 +724,8 @@ pub trait MailListingTrait: ListingTrait {
) { ) {
} }
/// Use `force` when there have been changes in the mailbox or account lists in `context` /// Use `force` when there have been changes in the mailbox or account lists
/// in `context`
fn refresh_mailbox(&mut self, context: &mut Context, force: bool); fn refresh_mailbox(&mut self, context: &mut Context, force: bool);
} }
@ -1357,7 +1357,7 @@ impl Component for Listing {
.mailbox_by_path(mailbox_path) .mailbox_by_path(mailbox_path)
.and_then(|mailbox_hash| { .and_then(|mailbox_hash| {
Ok(( Ok((
std::fs::read(&file_path).chain_err_summary(|| { std::fs::read(file_path).chain_err_summary(|| {
format!("Could not read {}", file_path.display()) format!("Could not read {}", file_path.display())
})?, })?,
mailbox_hash, mailbox_hash,
@ -1716,7 +1716,8 @@ impl Component for Listing {
*account_cursor += 1; *account_cursor += 1;
*entry_cursor = MenuEntryCursor::Status; *entry_cursor = MenuEntryCursor::Status;
} }
/* If current account has no mailboxes and there is no next account, return true */ /* If current account has no mailboxes and there is no next
* account, return true */
(_, MenuEntryCursor::Status) => { (_, MenuEntryCursor::Status) => {
return true; return true;
} }
@ -1929,7 +1930,7 @@ impl Component for Listing {
.push_back(UIEvent::StatusEvent(StatusEvent::BufClear)); .push_back(UIEvent::StatusEvent(StatusEvent::BufClear));
return true; return true;
} }
UIEvent::Input(Key::Char(c)) if ('0'..='9').contains(&c) => { UIEvent::Input(Key::Char(c)) if c.is_ascii_digit() => {
self.cmd_buf.push(c); self.cmd_buf.push(c);
self.component.set_modifier_active(true); self.component.set_modifier_active(true);
context context
@ -2309,8 +2310,8 @@ impl Listing {
let mut branches = String::with_capacity(16); let mut branches = String::with_capacity(16);
// What depth to skip if a mailbox is toggled to collapse // What depth to skip if a mailbox is toggled to collapse
// The value should be the collapsed mailbox's indentation, so that its children are not // The value should be the collapsed mailbox's indentation, so that its children
// visible. // are not visible.
let mut skip: Option<usize> = None; let mut skip: Option<usize> = None;
let mut skipped_counter: usize = 0; let mut skipped_counter: usize = 0;
'grid_loop: for y in get_y(upper_left) + 1..get_y(bottom_right) { 'grid_loop: for y in get_y(upper_left) + 1..get_y(bottom_right) {
@ -2381,8 +2382,8 @@ impl Listing {
) )
}; };
/* Calculate how many columns the mailbox index tags should occupy with right alignment, /* Calculate how many columns the mailbox index tags should occupy with right
* eg. * alignment, eg.
* 1 * 1
* 2 * 2
* ... * ...

View File

@ -19,14 +19,12 @@
* along with meli. If not, see <http://www.gnu.org/licenses/>. * along with meli. If not, see <http://www.gnu.org/licenses/>.
*/ */
use super::*; use std::{cmp, collections::BTreeMap, convert::TryInto, iter::FromIterator};
use crate::components::PageMovement;
use crate::jobs::JoinHandle;
use indexmap::IndexSet; use indexmap::IndexSet;
use std::cmp;
use std::collections::BTreeMap; use super::*;
use std::convert::TryInto; use crate::{components::PageMovement, jobs::JoinHandle};
use std::iter::FromIterator;
macro_rules! digits_of_num { macro_rules! digits_of_num {
($num:expr) => {{ ($num:expr) => {{
@ -158,8 +156,8 @@ macro_rules! row_attr {
}}; }};
} }
/// A list of all mail (`Envelope`s) in a `Mailbox`. On `\n` it opens the `Envelope` content in a /// A list of all mail (`Envelope`s) in a `Mailbox`. On `\n` it opens the
/// `ThreadView`. /// `Envelope` content in a `ThreadView`.
#[derive(Debug)] #[derive(Debug)]
pub struct CompactListing { pub struct CompactListing {
/// (x, y, z): x is accounts, y is mailboxes, z is index inside a mailbox. /// (x, y, z): x is accounts, y is mailboxes, z is index inside a mailbox.
@ -239,8 +237,8 @@ impl MailListingTrait for CompactListing {
SmallVec::from_iter(iter) SmallVec::from_iter(iter)
} }
/// Fill the `self.data_columns` `CellBuffers` with the contents of the account mailbox the user has /// Fill the `self.data_columns` `CellBuffers` with the contents of the
/// chosen. /// account mailbox the user has chosen.
fn refresh_mailbox(&mut self, context: &mut Context, force: bool) { fn refresh_mailbox(&mut self, context: &mut Context, force: bool) {
self.set_dirty(true); self.set_dirty(true);
self.rows.clear(); self.rows.clear();
@ -565,7 +563,7 @@ impl ListingTrait for CompactListing {
fn set_coordinates(&mut self, coordinates: (AccountHash, MailboxHash)) { fn set_coordinates(&mut self, coordinates: (AccountHash, MailboxHash)) {
self.new_cursor_pos = (coordinates.0, coordinates.1, 0); self.new_cursor_pos = (coordinates.0, coordinates.1, 0);
self.focus = Focus::None; self.focus = Focus::None;
self.view = Box::new(ThreadView::default()); self.view = Box::<ThreadView>::default();
self.filtered_selection.clear(); self.filtered_selection.clear();
self.filtered_order.clear(); self.filtered_order.clear();
self.filter_term.clear(); self.filter_term.clear();
@ -692,8 +690,8 @@ impl ListingTrait for CompactListing {
let end_idx = cmp::min(self.length.saturating_sub(1), top_idx + rows - 1); let end_idx = cmp::min(self.length.saturating_sub(1), top_idx + rows - 1);
self.draw_rows(context, top_idx, end_idx); self.draw_rows(context, top_idx, end_idx);
/* If cursor position has changed, remove the highlight from the previous position and /* If cursor position has changed, remove the highlight from the previous
* apply it in the new one. */ * position and apply it in the new one. */
if self.cursor_pos.2 != self.new_cursor_pos.2 && prev_page_no == page_no { if self.cursor_pos.2 != self.new_cursor_pos.2 && prev_page_no == page_no {
let old_cursor_pos = self.cursor_pos; let old_cursor_pos = self.cursor_pos;
self.cursor_pos = self.new_cursor_pos; self.cursor_pos = self.new_cursor_pos;
@ -840,9 +838,10 @@ impl ListingTrait for CompactListing {
self.view self.view
.process_event(&mut UIEvent::VisibilityChange(false), context); .process_event(&mut UIEvent::VisibilityChange(false), context);
self.dirty = true; self.dirty = true;
/* If self.rows.row_updates is not empty and we exit a thread, the row_update events /* If self.rows.row_updates is not empty and we exit a thread, the row_update
* will be performed but the list will not be drawn. So force a draw in any case. * events will be performed but the list will not be drawn.
* */ * So force a draw in any case.
*/
self.force_draw = true; self.force_draw = true;
} }
Focus::Entry => { Focus::Entry => {
@ -889,7 +888,7 @@ impl CompactListing {
rows: RowsState::default(), rows: RowsState::default(),
dirty: true, dirty: true,
force_draw: true, force_draw: true,
view: Box::new(ThreadView::default()), view: Box::<ThreadView>::default(),
color_cache: ColorCache::default(), color_cache: ColorCache::default(),
movement: None, movement: None,
modifier_active: false, modifier_active: false,
@ -1064,8 +1063,8 @@ impl CompactListing {
let account = &context.accounts[&self.cursor_pos.0]; let account = &context.accounts[&self.cursor_pos.0];
if !account.contains_key(env_hash) { if !account.contains_key(env_hash) {
/* The envelope has been renamed or removed, so wait for the appropriate event to /* The envelope has been renamed or removed, so wait for the appropriate
* arrive */ * event to arrive */
return; return;
} }
let tags_lck = account.collection.tag_index.read().unwrap(); let tags_lck = account.collection.tag_index.read().unwrap();

View File

@ -19,12 +19,12 @@
* along with meli. If not, see <http://www.gnu.org/licenses/>. * along with meli. If not, see <http://www.gnu.org/licenses/>.
*/ */
use super::*; use std::{collections::BTreeMap, iter::FromIterator};
use crate::components::PageMovement;
use crate::jobs::JoinHandle;
use indexmap::IndexSet; use indexmap::IndexSet;
use std::collections::BTreeMap;
use std::iter::FromIterator; use super::*;
use crate::{components::PageMovement, jobs::JoinHandle};
macro_rules! row_attr { macro_rules! row_attr {
($field:ident, $color_cache:expr, $unseen:expr, $highlighted:expr, $selected:expr $(,)*) => {{ ($field:ident, $color_cache:expr, $unseen:expr, $highlighted:expr, $selected:expr $(,)*) => {{
@ -91,8 +91,8 @@ macro_rules! row_attr {
}}; }};
} }
/// A list of all mail (`Envelope`s) in a `Mailbox`. On `\n` it opens the `Envelope` content in a /// A list of all mail (`Envelope`s) in a `Mailbox`. On `\n` it opens the
/// `ThreadView`. /// `Envelope` content in a `ThreadView`.
#[derive(Debug)] #[derive(Debug)]
pub struct ConversationsListing { pub struct ConversationsListing {
/// (x, y, z): x is accounts, y is mailboxes, z is index inside a mailbox. /// (x, y, z): x is accounts, y is mailboxes, z is index inside a mailbox.
@ -455,8 +455,8 @@ impl ListingTrait for ConversationsListing {
let top_idx = page_no * rows; let top_idx = page_no * rows;
/* If cursor position has changed, remove the highlight from the previous position and /* If cursor position has changed, remove the highlight from the previous
* apply it in the new one. */ * position and apply it in the new one. */
if self.cursor_pos.2 != self.new_cursor_pos.2 && prev_page_no == page_no { if self.cursor_pos.2 != self.new_cursor_pos.2 && prev_page_no == page_no {
let old_cursor_pos = self.cursor_pos; let old_cursor_pos = self.cursor_pos;
self.cursor_pos = self.new_cursor_pos; self.cursor_pos = self.new_cursor_pos;
@ -582,9 +582,10 @@ impl ListingTrait for ConversationsListing {
self.view self.view
.process_event(&mut UIEvent::VisibilityChange(false), context); .process_event(&mut UIEvent::VisibilityChange(false), context);
self.dirty = true; self.dirty = true;
/* If self.rows.row_updates is not empty and we exit a thread, the row_update events /* If self.rows.row_updates is not empty and we exit a thread, the row_update
* will be performed but the list will not be drawn. So force a draw in any case. * events will be performed but the list will not be drawn.
* */ * So force a draw in any case.
*/
self.force_draw = true; self.force_draw = true;
} }
Focus::Entry => { Focus::Entry => {
@ -1181,7 +1182,7 @@ impl Component for ConversationsListing {
if !self.rows.row_updates.is_empty() { if !self.rows.row_updates.is_empty() {
/* certain rows need to be updated (eg an unseen message was just set seen) /* certain rows need to be updated (eg an unseen message was just set seen)
* */ */
while let Some(row) = self.rows.row_updates.pop() { while let Some(row) = self.rows.row_updates.pop() {
self.update_line(context, row); self.update_line(context, row);
let row: usize = self.rows.env_order[&row]; let row: usize = self.rows.env_order[&row];
@ -1379,7 +1380,8 @@ impl Component for ConversationsListing {
// FIXME subsort // FIXME subsort
//if !self.filtered_selection.is_empty() { //if !self.filtered_selection.is_empty() {
// let threads = &account.collection.threads[&self.cursor_pos.1]; // let threads = &account.collection.threads[&self.cursor_pos.1];
// threads.vec_inner_sort_by(&mut self.filtered_selection, self.sort, &account.collection); // threads.vec_inner_sort_by(&mut self.filtered_selection, self.sort,
// &account.collection);
//} else { //} else {
// self.refresh_mailbox(context, false); // self.refresh_mailbox(context, false);
//} //}

View File

@ -19,9 +19,10 @@
* along with meli. If not, see <http://www.gnu.org/licenses/>. * along with meli. If not, see <http://www.gnu.org/licenses/>.
*/ */
use std::borrow::Cow;
use super::*; use super::*;
use crate::components::PageMovement; use crate::components::PageMovement;
use std::borrow::Cow;
#[derive(Debug)] #[derive(Debug)]
pub struct OfflineListing { pub struct OfflineListing {

View File

@ -19,12 +19,10 @@
* along with meli. If not, see <http://www.gnu.org/licenses/>. * along with meli. If not, see <http://www.gnu.org/licenses/>.
*/ */
use super::EntryStrings; use std::{cmp, iter::FromIterator};
use super::*;
use crate::components::PageMovement; use super::{EntryStrings, *};
use crate::jobs::JoinHandle; use crate::{components::PageMovement, jobs::JoinHandle};
use std::cmp;
use std::iter::FromIterator;
macro_rules! address_list { macro_rules! address_list {
(($name:expr) as comma_sep_list) => {{ (($name:expr) as comma_sep_list) => {{
@ -118,8 +116,8 @@ macro_rules! row_attr {
}}; }};
} }
/// A list of all mail (`Envelope`s) in a `Mailbox`. On `\n` it opens the `Envelope` content in a /// A list of all mail (`Envelope`s) in a `Mailbox`. On `\n` it opens the
/// `MailView`. /// `Envelope` content in a `MailView`.
#[derive(Debug)] #[derive(Debug)]
pub struct PlainListing { pub struct PlainListing {
/// (x, y, z): x is accounts, y is mailboxes, z is index inside a mailbox. /// (x, y, z): x is accounts, y is mailboxes, z is index inside a mailbox.
@ -182,8 +180,8 @@ impl MailListingTrait for PlainListing {
) )
} }
/// Fill the `self.data_columns` `CellBuffers` with the contents of the account mailbox the user has /// Fill the `self.data_columns` `CellBuffers` with the contents of the
/// chosen. /// account mailbox the user has chosen.
fn refresh_mailbox(&mut self, context: &mut Context, force: bool) { fn refresh_mailbox(&mut self, context: &mut Context, force: bool) {
self.set_dirty(true); self.set_dirty(true);
let old_cursor_pos = self.cursor_pos; let old_cursor_pos = self.cursor_pos;
@ -423,8 +421,8 @@ impl ListingTrait for PlainListing {
let top_idx = page_no * rows; let top_idx = page_no * rows;
/* If cursor position has changed, remove the highlight from the previous position and /* If cursor position has changed, remove the highlight from the previous
* apply it in the new one. */ * position and apply it in the new one. */
if self.cursor_pos.2 != self.new_cursor_pos.2 && prev_page_no == page_no { if self.cursor_pos.2 != self.new_cursor_pos.2 && prev_page_no == page_no {
let old_cursor_pos = self.cursor_pos; let old_cursor_pos = self.cursor_pos;
self.cursor_pos = self.new_cursor_pos; self.cursor_pos = self.new_cursor_pos;
@ -571,9 +569,10 @@ impl ListingTrait for PlainListing {
self.view self.view
.process_event(&mut UIEvent::VisibilityChange(false), context); .process_event(&mut UIEvent::VisibilityChange(false), context);
self.dirty = true; self.dirty = true;
/* If self.rows.row_updates is not empty and we exit a thread, the row_update events /* If self.rows.row_updates is not empty and we exit a thread, the row_update
* will be performed but the list will not be drawn. So force a draw in any case. * events will be performed but the list will not be drawn.
* */ * So force a draw in any case.
*/
self.force_draw = true; self.force_draw = true;
} }
Focus::Entry => { Focus::Entry => {
@ -973,8 +972,8 @@ impl PlainListing {
let account = &context.accounts[&self.cursor_pos.0]; let account = &context.accounts[&self.cursor_pos.0];
if !account.contains_key(env_hash) { if !account.contains_key(env_hash) {
/* The envelope has been renamed or removed, so wait for the appropriate event to /* The envelope has been renamed or removed, so wait for the appropriate
* arrive */ * event to arrive */
return; return;
} }
let envelope: EnvelopeRef = account.collection.get_env(env_hash); let envelope: EnvelopeRef = account.collection.get_env(env_hash);
@ -1425,7 +1424,8 @@ impl Component for PlainListing {
self.subsort = (*field, *order); self.subsort = (*field, *order);
//if !self.filtered_selection.is_empty() { //if !self.filtered_selection.is_empty() {
// let threads = &account.collection.threads[&self.cursor_pos.1]; // let threads = &account.collection.threads[&self.cursor_pos.1];
// threads.vec_inner_sort_by(&mut self.filtered_selection, self.sort, &account.collection); // threads.vec_inner_sort_by(&mut self.filtered_selection, self.sort,
// &account.collection);
//} else { //} else {
// self.refresh_mailbox(contex, falset); // self.refresh_mailbox(contex, falset);
//} //}

View File

@ -19,12 +19,10 @@
* along with meli. If not, see <http://www.gnu.org/licenses/>. * along with meli. If not, see <http://www.gnu.org/licenses/>.
*/ */
use std::{cmp, convert::TryInto, fmt::Write, iter::FromIterator};
use super::*; use super::*;
use crate::components::PageMovement; use crate::components::PageMovement;
use std::cmp;
use std::convert::TryInto;
use std::fmt::Write;
use std::iter::FromIterator;
macro_rules! row_attr { macro_rules! row_attr {
($color_cache:expr, $even: expr, $unseen:expr, $highlighted:expr, $selected:expr $(,)*) => {{ ($color_cache:expr, $even: expr, $unseen:expr, $highlighted:expr, $selected:expr $(,)*) => {{
@ -102,8 +100,8 @@ macro_rules! row_attr {
}}; }};
} }
/// A list of all mail (`Envelope`s) in a `Mailbox`. On `\n` it opens the `Envelope` content in a /// A list of all mail (`Envelope`s) in a `Mailbox`. On `\n` it opens the
/// `MailView`. /// `Envelope` content in a `MailView`.
#[derive(Debug)] #[derive(Debug)]
pub struct ThreadListing { pub struct ThreadListing {
/// (x, y, z): x is accounts, y is mailboxes, z is index inside a mailbox. /// (x, y, z): x is accounts, y is mailboxes, z is index inside a mailbox.
@ -167,8 +165,8 @@ impl MailListingTrait for ThreadListing {
) )
} }
/// Fill the `self.content` `CellBuffer` with the contents of the account mailbox the user has /// Fill the `self.content` `CellBuffer` with the contents of the account
/// chosen. /// mailbox the user has chosen.
fn refresh_mailbox(&mut self, context: &mut Context, _force: bool) { fn refresh_mailbox(&mut self, context: &mut Context, _force: bool) {
self.set_dirty(true); self.set_dirty(true);
if !(self.cursor_pos.0 == self.new_cursor_pos.0 if !(self.cursor_pos.0 == self.new_cursor_pos.0
@ -485,8 +483,8 @@ impl ListingTrait for ThreadListing {
let top_idx = page_no * rows; let top_idx = page_no * rows;
/* If cursor position has changed, remove the highlight from the previous position and /* If cursor position has changed, remove the highlight from the previous
* apply it in the new one. */ * position and apply it in the new one. */
if self.cursor_pos.2 != self.new_cursor_pos.2 && prev_page_no == page_no { if self.cursor_pos.2 != self.new_cursor_pos.2 && prev_page_no == page_no {
let old_cursor_pos = self.cursor_pos; let old_cursor_pos = self.cursor_pos;
self.cursor_pos = self.new_cursor_pos; self.cursor_pos = self.new_cursor_pos;
@ -634,9 +632,10 @@ impl ListingTrait for ThreadListing {
Focus::None => { Focus::None => {
self.view = None; self.view = None;
self.dirty = true; self.dirty = true;
/* If self.rows.row_updates is not empty and we exit a thread, the row_update events /* If self.rows.row_updates is not empty and we exit a thread, the row_update
* will be performed but the list will not be drawn. So force a draw in any case. * events will be performed but the list will not be drawn.
* */ * So force a draw in any case.
*/
self.force_draw = true; self.force_draw = true;
} }
Focus::Entry => { Focus::Entry => {
@ -982,8 +981,8 @@ impl ThreadListing {
let account = &context.accounts[&self.cursor_pos.0]; let account = &context.accounts[&self.cursor_pos.0];
if !account.contains_key(env_hash) { if !account.contains_key(env_hash) {
/* The envelope has been renamed or removed, so wait for the appropriate event to /* The envelope has been renamed or removed, so wait for the appropriate
* arrive */ * event to arrive */
return; return;
} }
let envelope: EnvelopeRef = account.collection.get_env(env_hash); let envelope: EnvelopeRef = account.collection.get_env(env_hash);

View File

@ -22,15 +22,17 @@
//FIXME //FIXME
#![allow(unused_imports, unused_variables)] #![allow(unused_imports, unused_variables)]
use melib::email::{ use std::{future::Future, pin::Pin};
attachment_types::{ContentDisposition, ContentType, MultipartType},
pgp as melib_pgp, Attachment, AttachmentBuilder, use melib::{
email::{
attachment_types::{ContentDisposition, ContentType, MultipartType},
pgp as melib_pgp, Attachment, AttachmentBuilder,
},
error::*,
gpgme::*,
parser::BytesExt,
}; };
use melib::error::*;
use melib::gpgme::*;
use melib::parser::BytesExt;
use std::future::Future;
use std::pin::Pin;
pub async fn decrypt(raw: Vec<u8>) -> Result<(melib_pgp::DecryptionMetadata, Vec<u8>)> { pub async fn decrypt(raw: Vec<u8>) -> Result<(melib_pgp::DecryptionMetadata, Vec<u8>)> {
Err("libgpgme functions are temporarily disabled due to an unsolved bug <https://git.meli.delivery/meli/meli/issues/176>.".into()) Err("libgpgme functions are temporarily disabled due to an unsolved bug <https://git.meli.delivery/meli/meli/issues/176>.".into())

View File

@ -33,7 +33,7 @@ pub struct AccountStatus {
impl fmt::Display for AccountStatus { impl fmt::Display for AccountStatus {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
write!(f, "{}", "status") write!(f, "status")
} }
} }
@ -368,8 +368,8 @@ impl Component for AccountStatus {
} }
} }
/* self.content may have been resized with write_string_to_grid() calls above since it has /* self.content may have been resized with write_string_to_grid() calls above
* growable set */ * since it has growable set */
let (width, height) = self.content.size(); let (width, height) = self.content.size();
let (cols, rows) = (width!(area), height!(area)); let (cols, rows) = (width!(area), height!(area));
self.cursor = ( self.cursor = (

View File

@ -19,20 +19,23 @@
* along with meli. If not, see <http://www.gnu.org/licenses/>. * along with meli. If not, see <http://www.gnu.org/licenses/>.
*/ */
use super::*; use std::{
use crate::conf::accounts::JobRequest; collections::HashSet,
use crate::jobs::{JobId, JoinHandle}; convert::TryFrom,
use melib::email::attachment_types::ContentType; fmt::Write as _,
use melib::list_management; io::Write,
use melib::parser::BytesExt; os::unix::fs::PermissionsExt,
use smallvec::SmallVec; process::{Command, Stdio},
use std::collections::HashSet; };
use std::fmt::Write as _;
use std::io::Write;
use std::convert::TryFrom; use melib::{email::attachment_types::ContentType, list_management, parser::BytesExt};
use std::os::unix::fs::PermissionsExt; use smallvec::SmallVec;
use std::process::{Command, Stdio};
use super::*;
use crate::{
conf::accounts::JobRequest,
jobs::{JobId, JoinHandle},
};
mod html; mod html;
pub use self::html::*; pub use self::html::*;
@ -40,11 +43,11 @@ mod thread;
pub use self::thread::*; pub use self::thread::*;
mod envelope; mod envelope;
pub use self::envelope::*;
use linkify::LinkFinder; use linkify::LinkFinder;
use xdg_utils::query_default_app; use xdg_utils::query_default_app;
pub use self::envelope::*;
#[derive(Debug, Default)] #[derive(Debug, Default)]
enum ForceCharset { enum ForceCharset {
#[default] #[default]
@ -68,8 +71,9 @@ enum Source {
Raw, Raw,
} }
#[derive(PartialEq, Debug)] #[derive(PartialEq, Debug, Default)]
enum ViewMode { enum ViewMode {
#[default]
Normal, Normal,
Url, Url,
Attachment(usize), Attachment(usize),
@ -79,12 +83,6 @@ enum ViewMode {
ContactSelector(Box<UIDialog<Card>>), ContactSelector(Box<UIDialog<Card>>),
} }
impl Default for ViewMode {
fn default() -> Self {
ViewMode::Normal
}
}
impl ViewMode { impl ViewMode {
/* /*
fn is_ansi(&self) -> bool { fn is_ansi(&self) -> bool {
@ -158,8 +156,8 @@ pub enum AttachmentDisplay {
}, },
} }
/// Contains an Envelope view, with sticky headers, a pager for the body, and subviews for more /// Contains an Envelope view, with sticky headers, a pager for the body, and
/// menus /// subviews for more menus
#[derive(Debug, Default)] #[derive(Debug, Default)]
pub struct MailView { pub struct MailView {
coordinates: (AccountHash, MailboxHash, EnvelopeHash), coordinates: (AccountHash, MailboxHash, EnvelopeHash),
@ -322,7 +320,7 @@ impl Clone for MailView {
impl fmt::Display for MailView { impl fmt::Display for MailView {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
write!(f, "{}", "view mail") write!(f, "view mail")
} }
} }
@ -739,7 +737,7 @@ impl MailView {
branches, branches,
paths, paths,
cur_path, cur_path,
iter.peek() != None, iter.peek().is_some(),
s, s,
); );
if Some(i) == default_alternative { if Some(i) == default_alternative {
@ -795,7 +793,7 @@ impl MailView {
.map(|s| s.as_str()) .map(|s| s.as_str())
.unwrap_or("w3m -I utf-8 -T text/html"); .unwrap_or("w3m -I utf-8 -T text/html");
let command_obj = Command::new("sh") let command_obj = Command::new("sh")
.args(&["-c", filter_invocation]) .args(["-c", filter_invocation])
.stdin(Stdio::piped()) .stdin(Stdio::piped())
.stdout(Stdio::piped()) .stdout(Stdio::piped())
.spawn(); .spawn();
@ -810,9 +808,10 @@ impl MailView {
Some(NotificationType::Error(melib::ErrorKind::External)), Some(NotificationType::Error(melib::ErrorKind::External)),
)); ));
let comment = Some(format!( let comment = Some(format!(
"Failed to start html filter process: `{}`. Press `v` to open in web browser. \n\n", "Failed to start html filter process: `{}`. Press `v` to open in web \
filter_invocation browser. \n\n",
)); filter_invocation
));
let text = String::from_utf8_lossy(&bytes).to_string(); let text = String::from_utf8_lossy(&bytes).to_string();
acc.push(AttachmentDisplay::InlineText { acc.push(AttachmentDisplay::InlineText {
inner: Box::new(a.clone()), inner: Box::new(a.clone()),
@ -975,9 +974,12 @@ impl MailView {
#[cfg(not(feature = "gpgme"))] #[cfg(not(feature = "gpgme"))]
{ {
acc.push(AttachmentDisplay::EncryptedFailed { acc.push(AttachmentDisplay::EncryptedFailed {
inner: Box::new(a.clone()), inner: Box::new(a.clone()),
error: Error::new("Cannot decrypt: meli must be compiled with libgpgme support."), error: Error::new(
}); "Cannot decrypt: meli must be compiled with libgpgme \
support.",
),
});
} }
#[cfg(feature = "gpgme")] #[cfg(feature = "gpgme")]
{ {
@ -1185,8 +1187,8 @@ impl Component for MailView {
let y: usize = { let y: usize = {
let account = &context.accounts[&self.coordinates.0]; let account = &context.accounts[&self.coordinates.0];
if !account.contains_key(self.coordinates.2) { if !account.contains_key(self.coordinates.2) {
/* The envelope has been renamed or removed, so wait for the appropriate event to /* The envelope has been renamed or removed, so wait for the appropriate
* arrive */ * event to arrive */
return; return;
} }
let envelope: EnvelopeRef = account.collection.get_env(self.coordinates.2); let envelope: EnvelopeRef = account.collection.get_env(self.coordinates.2);
@ -1866,7 +1868,8 @@ impl Component for MailView {
} if handle.job_id == *job_id => { } if handle.job_id == *job_id => {
match handle.chan.try_recv() { match handle.chan.try_recv() {
Err(_) => { /* Job was canceled */ } Err(_) => { /* Job was canceled */ }
Ok(None) => { /* something happened, perhaps a worker thread panicked */ Ok(None) => { /* something happened, perhaps a worker
* thread panicked */
} }
Ok(Some(Ok(bytes))) => { Ok(Some(Ok(bytes))) => {
MailViewState::load_bytes(self, bytes, context); MailViewState::load_bytes(self, bytes, context);
@ -1895,7 +1898,9 @@ impl Component for MailView {
self.initialised = false; self.initialised = false;
match handle.chan.try_recv() { match handle.chan.try_recv() {
Err(_) => { /* Job was canceled */ } Err(_) => { /* Job was canceled */ }
Ok(None) => { /* something happened, perhaps a worker thread panicked */ Ok(None) => { /* something happened,
* perhaps a worker thread
* panicked */
} }
Ok(Some(Ok(()))) => { Ok(Some(Ok(()))) => {
*d = AttachmentDisplay::SignedVerified { *d = AttachmentDisplay::SignedVerified {
@ -1930,7 +1935,9 @@ impl Component for MailView {
self.initialised = false; self.initialised = false;
match handle.chan.try_recv() { match handle.chan.try_recv() {
Err(_) => { /* Job was canceled */ } Err(_) => { /* Job was canceled */ }
Ok(None) => { /* something happened, perhaps a worker thread panicked */ Ok(None) => { /* something happened,
* perhaps a worker thread
* panicked */
} }
Ok(Some(Ok((metadata, decrypted_bytes)))) => { Ok(Some(Ok((metadata, decrypted_bytes)))) => {
let plaintext = Box::new( let plaintext = Box::new(
@ -2108,35 +2115,36 @@ impl Component for MailView {
on_finish: Some(CallbackFn(Box::new(move |context: &mut Context| { on_finish: Some(CallbackFn(Box::new(move |context: &mut Context| {
match receiver.try_recv() { match receiver.try_recv() {
Err(_) => { /* Job was canceled */ } Err(_) => { /* Job was canceled */ }
Ok(None) => { /* something happened, perhaps a worker thread panicked */ Ok(None) => { /* something happened, perhaps a worker
* thread panicked */
} }
Ok(Some(result)) => { Ok(Some(result)) => {
match result.and_then(|bytes| { match result.and_then(|bytes| {
Composer::edit(account_hash, env_hash, &bytes, context) Composer::edit(account_hash, env_hash, &bytes, context)
}) { }) {
Ok(composer) => { Ok(composer) => {
context.replies.push_back(UIEvent::Action(Tab(New(Some( context.replies.push_back(UIEvent::Action(Tab(New(
Box::new(composer), Some(Box::new(composer)),
))))); ))));
} }
Err(err) => { Err(err) => {
let err_string = format!( let err_string = format!(
"Failed to open envelope {}: {}", "Failed to open envelope {}: {}",
context.accounts[&account_hash] context.accounts[&account_hash]
.collection .collection
.envelopes .envelopes
.read() .read()
.unwrap() .unwrap()
.get(&env_hash) .get(&env_hash)
.map(|env| env.message_id_display()) .map(|env| env.message_id_display())
.unwrap_or_else(|| "Not found".into()), .unwrap_or_else(|| "Not found".into()),
err err
); );
log(&err_string, ERROR); log(&err_string, ERROR);
context.replies.push_back(UIEvent::Notification( context.replies.push_back(UIEvent::Notification(
Some("Failed to open e-mail".to_string()), Some("Failed to open e-mail".to_string()),
err_string, err_string,
Some(NotificationType::Error(err.kind)), Some(NotificationType::Error(err.kind)),
)); ));
} }
} }
@ -2176,7 +2184,7 @@ impl Component for MailView {
.push_back(UIEvent::StatusEvent(StatusEvent::BufClear)); .push_back(UIEvent::StatusEvent(StatusEvent::BufClear));
return true; return true;
} }
UIEvent::Input(Key::Char(c)) if ('0'..='9').contains(&c) => { UIEvent::Input(Key::Char(c)) if c.is_ascii_digit() => {
self.cmd_buf.push(c); self.cmd_buf.push(c);
context context
.replies .replies
@ -2316,7 +2324,7 @@ impl Component for MailView {
false, false,
); );
match Command::new("sh") match Command::new("sh")
.args(&["-c", &exec_cmd]) .args(["-c", &exec_cmd])
.stdin(Stdio::piped()) .stdin(Stdio::piped())
.stdout(Stdio::piped()) .stdout(Stdio::piped())
.spawn() .spawn()
@ -2336,19 +2344,22 @@ impl Component for MailView {
} }
} else { } else {
context.replies.push_back(UIEvent::StatusEvent( context.replies.push_back(UIEvent::StatusEvent(
StatusEvent::DisplayMessage(if let Some(filename) = filename.as_ref() { StatusEvent::DisplayMessage(
format!( if let Some(filename) = filename.as_ref() {
"Couldn't find a default application for file {} (type {})", format!(
filename, "Couldn't find a default application for \
attachment_type file {} (type {})",
) filename, attachment_type
} else { )
format!( } else {
"Couldn't find a default application for type {}", format!(
attachment_type "Couldn't find a default application for \
) type {}",
}), attachment_type
)); )
},
),
));
} }
} }
ContentType::OctetStream { ContentType::OctetStream {
@ -2357,9 +2368,10 @@ impl Component for MailView {
} => { } => {
context.replies.push_back(UIEvent::StatusEvent( context.replies.push_back(UIEvent::StatusEvent(
StatusEvent::DisplayMessage(format!( StatusEvent::DisplayMessage(format!(
"Failed to open {}. application/octet-stream isn't supported yet", "Failed to open {}. application/octet-stream isn't \
name.as_ref().map(|n| n.as_str()).unwrap_or("file") supported yet",
)), name.as_ref().map(|n| n.as_str()).unwrap_or("file")
)),
)); ));
} }
} }
@ -2479,8 +2491,8 @@ impl Component for MailView {
// Save entire message as eml // Save entire message as eml
let account = &context.accounts[&self.coordinates.0]; let account = &context.accounts[&self.coordinates.0];
if !account.contains_key(self.coordinates.2) { if !account.contains_key(self.coordinates.2) {
/* The envelope has been renamed or removed, so wait for the appropriate event to /* The envelope has been renamed or removed, so wait for the appropriate
* arrive */ * event to arrive */
return true; return true;
} }
let bytes = if let MailViewState::Loaded { ref bytes, .. } = self.state { let bytes = if let MailViewState::Loaded { ref bytes, .. } = self.state {
@ -2532,8 +2544,8 @@ impl Component for MailView {
{ {
let account = &context.accounts[&self.coordinates.0]; let account = &context.accounts[&self.coordinates.0];
if !account.contains_key(self.coordinates.2) { if !account.contains_key(self.coordinates.2) {
/* The envelope has been renamed or removed, so wait for the appropriate event to /* The envelope has been renamed or removed, so wait for the appropriate
* arrive */ * event to arrive */
return true; return true;
} }
} }
@ -2626,8 +2638,8 @@ impl Component for MailView {
UIEvent::Action(MailingListAction(ref e)) => { UIEvent::Action(MailingListAction(ref e)) => {
let account = &context.accounts[&self.coordinates.0]; let account = &context.accounts[&self.coordinates.0];
if !account.contains_key(self.coordinates.2) { if !account.contains_key(self.coordinates.2) {
/* The envelope has been renamed or removed, so wait for the appropriate event to /* The envelope has been renamed or removed, so wait for the appropriate
* arrive */ * event to arrive */
return true; return true;
} }
let envelope: EnvelopeRef = account.collection.get_env(self.coordinates.2); let envelope: EnvelopeRef = account.collection.get_env(self.coordinates.2);
@ -2661,7 +2673,7 @@ impl Component for MailView {
return true; return true;
} }
MailingListAction::ListUnsubscribe if actions.unsubscribe.is_some() => { MailingListAction::ListUnsubscribe if actions.unsubscribe.is_some() => {
/* autosend or open unsubscribe option*/ /* autosend or open unsubscribe option */
let unsubscribe = actions.unsubscribe.as_ref().unwrap(); let unsubscribe = actions.unsubscribe.as_ref().unwrap();
for option in unsubscribe.iter() { for option in unsubscribe.iter() {
/* TODO: Ask for confirmation before proceding with an action */ /* TODO: Ask for confirmation before proceding with an action */

View File

@ -19,12 +19,13 @@
* along with meli. If not, see <http://www.gnu.org/licenses/>. * along with meli. If not, see <http://www.gnu.org/licenses/>.
*/ */
use super::*;
use linkify::{Link, LinkFinder};
use std::process::{Command, Stdio}; use std::process::{Command, Stdio};
use linkify::{Link, LinkFinder};
use xdg_utils::query_default_app; use xdg_utils::query_default_app;
use super::*;
#[derive(PartialEq, Eq, Debug)] #[derive(PartialEq, Eq, Debug)]
enum ViewMode { enum ViewMode {
Normal, Normal,
@ -40,8 +41,8 @@ impl ViewMode {
} }
} }
/// Contains an Envelope view, with sticky headers, a pager for the body, and subviews for more /// Contains an Envelope view, with sticky headers, a pager for the body, and
/// menus /// subviews for more menus
#[derive(Debug)] #[derive(Debug)]
pub struct EnvelopeView { pub struct EnvelopeView {
pager: Option<Pager>, pager: Option<Pager>,
@ -91,7 +92,7 @@ impl EnvelopeView {
let settings = &context.settings; let settings = &context.settings;
if let Some(filter_invocation) = settings.pager.html_filter.as_ref() { if let Some(filter_invocation) = settings.pager.html_filter.as_ref() {
let command_obj = Command::new("sh") let command_obj = Command::new("sh")
.args(&["-c", filter_invocation]) .args(["-c", filter_invocation])
.stdin(Stdio::piped()) .stdin(Stdio::piped())
.stdout(Stdio::piped()) .stdout(Stdio::piped())
.spawn(); .spawn();
@ -114,9 +115,10 @@ impl EnvelopeView {
.write_all(v) .write_all(v)
.expect("Failed to write to stdin"); .expect("Failed to write to stdin");
*v = format!( *v = format!(
"Text piped through `{}`. Press `v` to open in web browser. \n\n", "Text piped through `{}`. Press `v` to open in web browser. \
filter_invocation \n\n",
) filter_invocation
)
.into_bytes(); .into_bytes();
v.extend(html_filter.wait_with_output().unwrap().stdout); v.extend(html_filter.wait_with_output().unwrap().stdout);
} }
@ -372,7 +374,7 @@ impl Component for EnvelopeView {
.push_back(UIEvent::StatusEvent(StatusEvent::BufClear)); .push_back(UIEvent::StatusEvent(StatusEvent::BufClear));
return true; return true;
} }
UIEvent::Input(Key::Char(c)) if ('0'..='9').contains(&c) => { UIEvent::Input(Key::Char(c)) if c.is_ascii_digit() => {
self.cmd_buf.push(c); self.cmd_buf.push(c);
return true; return true;
} }
@ -449,7 +451,7 @@ impl Component for EnvelopeView {
false, false,
); );
match Command::new("sh") match Command::new("sh")
.args(&["-c", &exec_cmd]) .args(["-c", &exec_cmd])
.stdin(Stdio::piped()) .stdin(Stdio::piped())
.stdout(Stdio::piped()) .stdout(Stdio::piped())
.spawn() .spawn()
@ -469,18 +471,20 @@ impl Component for EnvelopeView {
} }
} else { } else {
context.replies.push_back(UIEvent::StatusEvent( context.replies.push_back(UIEvent::StatusEvent(
StatusEvent::DisplayMessage(if let Some(filename) = filename.as_ref() { StatusEvent::DisplayMessage(
if let Some(filename) = filename.as_ref() {
format!( format!(
"Couldn't find a default application for file {} (type {})", "Couldn't find a default application for file {} \
filename, (type {})",
attachment_type filename, attachment_type
) )
} else { } else {
format!( format!(
"Couldn't find a default application for type {}", "Couldn't find a default application for type {}",
attachment_type attachment_type
) )
}), },
),
)); ));
return true; return true;
} }

View File

@ -19,9 +19,12 @@
* along with meli. If not, see <http://www.gnu.org/licenses/>. * along with meli. If not, see <http://www.gnu.org/licenses/>.
*/ */
use std::{
io::Write,
process::{Command, Stdio},
};
use super::*; use super::*;
use std::io::Write;
use std::process::{Command, Stdio};
#[derive(Debug)] #[derive(Debug)]
pub struct HtmlView { pub struct HtmlView {
@ -40,7 +43,7 @@ impl HtmlView {
let mut display_text = if let Some(filter_invocation) = settings.pager.html_filter.as_ref() let mut display_text = if let Some(filter_invocation) = settings.pager.html_filter.as_ref()
{ {
let command_obj = Command::new("sh") let command_obj = Command::new("sh")
.args(&["-c", filter_invocation]) .args(["-c", filter_invocation])
.stdin(Stdio::piped()) .stdin(Stdio::piped())
.stdout(Stdio::piped()) .stdout(Stdio::piped())
.spawn(); .spawn();
@ -74,7 +77,7 @@ impl HtmlView {
} }
} }
} else if let Ok(mut html_filter) = Command::new("w3m") } else if let Ok(mut html_filter) = Command::new("w3m")
.args(&["-I", "utf-8", "-T", "text/html"]) .args(["-I", "utf-8", "-T", "text/html"])
.stdin(Stdio::piped()) .stdin(Stdio::piped())
.stdout(Stdio::piped()) .stdout(Stdio::piped())
.spawn() .spawn()
@ -154,7 +157,7 @@ impl Component for HtmlView {
let exec_cmd = let exec_cmd =
super::desktop_exec_to_command(&command, p.path.display().to_string(), false); super::desktop_exec_to_command(&command, p.path.display().to_string(), false);
match Command::new("sh") match Command::new("sh")
.args(&["-c", &exec_cmd]) .args(["-c", &exec_cmd])
.stdin(Stdio::piped()) .stdin(Stdio::piped())
.stdout(Stdio::piped()) .stdout(Stdio::piped())
.spawn() .spawn()

View File

@ -19,9 +19,10 @@
* along with meli. If not, see <http://www.gnu.org/licenses/>. * along with meli. If not, see <http://www.gnu.org/licenses/>.
*/ */
use std::cmp;
use super::*; use super::*;
use crate::components::PageMovement; use crate::components::PageMovement;
use std::cmp;
#[derive(Debug, Clone)] #[derive(Debug, Clone)]
struct ThreadEntry { struct ThreadEntry {
@ -62,8 +63,8 @@ pub struct ThreadView {
impl ThreadView { impl ThreadView {
/* /*
* coordinates: (account index, mailbox_hash, root set thread_node index) * coordinates: (account index, mailbox_hash, root set thread_node index)
* expanded_hash: optional position of expanded entry when we render the threadview. Default * expanded_hash: optional position of expanded entry when we render the
* expanded message is the last one. * threadview. Default expanded message is the last one.
* context: current context * context: current context
*/ */
pub fn new( pub fn new(
@ -491,8 +492,8 @@ impl ThreadView {
let page_no = (self.new_cursor_pos).wrapping_div(rows); let page_no = (self.new_cursor_pos).wrapping_div(rows);
let top_idx = page_no * rows; let top_idx = page_no * rows;
/* This closure (written for code clarity, should be inlined by the compiler) returns the /* This closure (written for code clarity, should be inlined by the compiler)
* **line** of an entry in the ThreadView grid. */ * returns the **line** of an entry in the ThreadView grid. */
let get_entry_area = |idx: usize, entries: &[ThreadEntry]| { let get_entry_area = |idx: usize, entries: &[ThreadEntry]| {
let entries = &entries; let entries = &entries;
let visual_indentation = entries[idx].index.0 * 4; let visual_indentation = entries[idx].index.0 * 4;
@ -530,8 +531,8 @@ impl ThreadView {
), ),
); );
} }
/* If cursor position has changed, remove the highlight from the previous position and /* If cursor position has changed, remove the highlight from the previous
* apply it in the new one. */ * position and apply it in the new one. */
self.cursor_pos = self.new_cursor_pos; self.cursor_pos = self.new_cursor_pos;
if self.cursor_pos + 1 > visibles.len() { if self.cursor_pos + 1 > visibles.len() {
self.cursor_pos = visibles.len().saturating_sub(1); self.cursor_pos = visibles.len().saturating_sub(1);
@ -588,8 +589,8 @@ impl ThreadView {
} else { } else {
let old_cursor_pos = self.cursor_pos; let old_cursor_pos = self.cursor_pos;
self.cursor_pos = self.new_cursor_pos; self.cursor_pos = self.new_cursor_pos;
/* If cursor position has changed, remove the highlight from the previous position and /* If cursor position has changed, remove the highlight from the previous
* apply it in the new one. */ * position and apply it in the new one. */
let visibles: Vec<&usize> = let visibles: Vec<&usize> =
self.visible_entries.iter().flat_map(|v| v.iter()).collect(); self.visible_entries.iter().flat_map(|v| v.iter()).collect();
for &idx in &[old_cursor_pos, self.cursor_pos] { for &idx in &[old_cursor_pos, self.cursor_pos] {
@ -941,7 +942,8 @@ impl ThreadView {
} }
} }
/// Current position in self.entries (not in drawn entries which might exclude nonvisible ones) /// Current position in self.entries (not in drawn entries which might
/// exclude nonvisible ones)
fn current_pos(&self) -> usize { fn current_pos(&self) -> usize {
let visibles: Vec<&usize> = self.visible_entries.iter().flat_map(|v| v.iter()).collect(); let visibles: Vec<&usize> = self.visible_entries.iter().flat_map(|v| v.iter()).collect();
*visibles[self.new_cursor_pos] *visibles[self.new_cursor_pos]

View File

@ -18,12 +18,12 @@
* 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 super::*; use std::cmp;
use crate::conf::accounts::MailboxEntry;
use crate::melib::text_processing::TextProcessing;
use melib::backends::AccountHash; use melib::backends::AccountHash;
use std::cmp; use super::*;
use crate::{conf::accounts::MailboxEntry, melib::text_processing::TextProcessing};
#[derive(Debug, Clone, Copy, PartialEq, Eq)] #[derive(Debug, Clone, Copy, PartialEq, Eq)]
enum MailboxAction { enum MailboxAction {
@ -63,7 +63,7 @@ pub struct MailboxManager {
impl fmt::Display for MailboxManager { impl fmt::Display for MailboxManager {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
write!(f, "{}", "mailboxes") write!(f, "mailboxes")
} }
} }
@ -96,7 +96,7 @@ impl MailboxManager {
self.length = account.mailbox_entries.len(); self.length = account.mailbox_entries.len();
self.entries = account.mailbox_entries.clone(); self.entries = account.mailbox_entries.clone();
self.entries self.entries
.sort_by(|_, a, _, b| a.ref_mailbox.path().cmp(&b.ref_mailbox.path())); .sort_by(|_, a, _, b| a.ref_mailbox.path().cmp(b.ref_mailbox.path()));
self.set_dirty(true); self.set_dirty(true);
let mut min_width = ( let mut min_width = (
@ -268,8 +268,8 @@ impl MailboxManager {
))); )));
} }
/* If cursor position has changed, remove the highlight from the previous position and /* If cursor position has changed, remove the highlight from the previous
* apply it in the new one. */ * position and apply it in the new one. */
if self.cursor_pos != self.new_cursor_pos && prev_page_no == page_no { if self.cursor_pos != self.new_cursor_pos && prev_page_no == page_no {
let old_cursor_pos = self.cursor_pos; let old_cursor_pos = self.cursor_pos;
self.cursor_pos = self.new_cursor_pos; self.cursor_pos = self.new_cursor_pos;
@ -362,11 +362,13 @@ impl Component for MailboxManager {
MailboxAction::Move | MailboxAction::Rename => { MailboxAction::Move | MailboxAction::Rename => {
context.replies.push_back(UIEvent::CmdInput(Key::Paste( context.replies.push_back(UIEvent::CmdInput(Key::Paste(
format!( format!(
"rename-mailbox \"{account_name}\" \"{mailbox_path_src}\" ", "rename-mailbox \"{account_name}\" \
account_name = context.accounts[&self.account_hash].name(), \"{mailbox_path_src}\" ",
mailbox_path_src = account_name =
self.entries[self.cursor_pos].ref_mailbox.path() context.accounts[&self.account_hash].name(),
), mailbox_path_src =
self.entries[self.cursor_pos].ref_mailbox.path()
),
))); )));
context context
.replies .replies

View File

@ -24,11 +24,11 @@ Notification handling components.
*/ */
use std::process::{Command, Stdio}; use std::process::{Command, Stdio};
use super::*;
#[cfg(all(target_os = "linux", feature = "dbus-notifications"))] #[cfg(all(target_os = "linux", feature = "dbus-notifications"))]
pub use dbus::*; pub use dbus::*;
use super::*;
#[cfg(all(target_os = "linux", feature = "dbus-notifications"))] #[cfg(all(target_os = "linux", feature = "dbus-notifications"))]
mod dbus { mod dbus {
use super::*; use super::*;
@ -230,7 +230,20 @@ impl Component for NotificationCommand {
} else { } else {
#[cfg(target_os = "macos")] #[cfg(target_os = "macos")]
{ {
let applescript = format!("display notification \"{message}\" with title \"{title}\" subtitle \"{subtitle}\"", message = body.replace('"', "'"), title = title.as_ref().map(String::as_str).unwrap_or("meli").replace('"', "'"), subtitle = kind.map(|k| k.to_string()).unwrap_or_default().replace('"', "'")); let applescript = format!(
"display notification \"{message}\" with title \"{title}\" subtitle \
\"{subtitle}\"",
message = body.replace('"', "'"),
title = title
.as_ref()
.map(String::as_str)
.unwrap_or("meli")
.replace('"', "'"),
subtitle = kind
.map(|k| k.to_string())
.unwrap_or_default()
.replace('"', "'")
);
match Command::new("osascript") match Command::new("osascript")
.arg("-e") .arg("-e")
.arg(applescript) .arg(applescript)
@ -279,7 +292,7 @@ impl Component for NotificationCommand {
fn update_xbiff(path: &str) -> Result<()> { fn update_xbiff(path: &str) -> Result<()> {
let mut file = std::fs::OpenOptions::new() let mut file = std::fs::OpenOptions::new()
.append(true) /* writes will append to a file instead of overwriting previous contents */ .append(true) /* writes will append to a file instead of overwriting previous contents */
.create(true) /* a new file will be created if the file does not yet already exist.*/ .create(true) /* a new file will be created if the file does not yet already exist. */
.open(path)?; .open(path)?;
if file.metadata()?.len() > 128 { if file.metadata()?.len() > 128 {
file.set_len(0)?; file.set_len(0)?;

View File

@ -18,9 +18,9 @@
* 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 std::{collections::BTreeMap, io::Write};
use super::*; use super::*;
use std::collections::BTreeMap;
use std::io::Write;
#[derive(Debug)] #[derive(Debug)]
pub struct SVGScreenshotFilter { pub struct SVGScreenshotFilter {
@ -48,22 +48,29 @@ impl Component for SVGScreenshotFilter {
} }
self.save_screenshot = false; self.save_screenshot = false;
let grid: &CellBuffer = _grid; let grid: &CellBuffer = _grid;
use svg_crate::node::element::{Definitions, Group, Rectangle, Style, Text, Use}; use svg_crate::{
use svg_crate::node::Text as TextNode; node::{
use svg_crate::Document; element::{Definitions, Group, Rectangle, Style, Text, Use},
Text as TextNode,
},
Document,
};
let (width, height) = grid.size(); let (width, height) = grid.size();
/* /*
* Format frame as follows: * Format frame as follows:
* - The entire background is a big rectangle. * - The entire background is a big rectangle.
* - Every text piece with unified foreground color is a text element inserted into the * - Every text piece with unified foreground color is a text element
* inserted into the
* `definitions` field of the svg, and then `use`ed as a reference * `definitions` field of the svg, and then `use`ed as a reference
* - Every background piece (a slice of unified background color) is a rectangle element * - Every background piece (a slice of unified background color) is a
* rectangle element
* inserted along with the `use` elements * inserted along with the `use` elements
* *
* Each row is arbritarily set at 17px high, and each character cell is 8 pixels wide. * Each row is arbritarily set at 17px high, and each character cell is 8
* Rectangle cells each have one extra pixel (so 18px * 9px) in their dimensions in order * pixels wide. Rectangle cells each have one extra pixel (so 18px *
* to cover the spacing between cells. * 9px) in their dimensions in order to cover the spacing between
* cells.
*/ */
let mut definitions = Definitions::new(); let mut definitions = Definitions::new();
let mut rows_group = Group::new(); let mut rows_group = Group::new();
@ -79,9 +86,11 @@ impl Component for SVGScreenshotFilter {
/* Each row is a <g> group element, consisting of text elements */ /* Each row is a <g> group element, consisting of text elements */
let mut row_group = Group::new().set("id", format!("{:x}", row_idx + 1)); let mut row_group = Group::new().set("id", format!("{:x}", row_idx + 1));
/* Keep track of colors and attributes. /* Keep track of colors and attributes.
* - Whenever the foreground color changes, emit a text element with the accumulated * - Whenever the foreground color changes, emit a text element with the
* accumulated
* text in the specific foreground color. * text in the specific foreground color.
* - Whenever the backgrund color changes, emit a rectangle element filled with the * - Whenever the backgrund color changes, emit a rectangle element filled
* with the
* specific background color. * specific background color.
*/ */
let mut cur_fg = Color::Default; let mut cur_fg = Color::Default;
@ -151,7 +160,7 @@ impl Component for SVGScreenshotFilter {
.add(TextNode::new(&escaped_text)) .add(TextNode::new(&escaped_text))
.set("x", prev_x_fg * 8) .set("x", prev_x_fg * 8)
.set("textLength", text_length * 8); .set("textLength", text_length * 8);
/*.set("fgname", format!("{:?}", cur_fg));*/ /* .set("fgname", format!("{:?}", cur_fg)); */
if cur_attrs.intersects(Attr::BOLD) { if cur_attrs.intersects(Attr::BOLD) {
text_el = text_el.set("font-weight", "bold"); text_el = text_el.set("font-weight", "bold");
} }
@ -275,7 +284,7 @@ impl Component for SVGScreenshotFilter {
.add(TextNode::new(&escaped_text)) .add(TextNode::new(&escaped_text))
.set("x", prev_x_fg * 8) .set("x", prev_x_fg * 8)
.set("textLength", text_length * 8); .set("textLength", text_length * 8);
/*.set("fgname", format!("{:?}", cur_fg));*/ /* .set("fgname", format!("{:?}", cur_fg)); */
if cur_attrs.intersects(Attr::BOLD) { if cur_attrs.intersects(Attr::BOLD) {
text_el = text_el.set("font-weight", "bold"); text_el = text_el.set("font-weight", "bold");
} }
@ -441,260 +450,260 @@ impl Component for SVGScreenshotFilter {
const CSS_STYLE: &str = r#"#t{font-family:'DejaVu Sans Mono',monospace;font-style:normal;font-size:14px;} text {dominant-baseline: text-before-edge; white-space: pre;} .f{fill:#e5e5e5;} .b{fill:#000;} .c0 {fill:#000;} .c1 {fill:#cd0000;} .c2 {fill:#00cd00;} .c3 {fill:#cdcd00;} .c4 {fill:#00e;} .c5 {fill:#cd00cd;} .c6 {fill:#00cdcd;} .c7 {fill:#e5e5e5;} .c8 {fill:#7f7f7f;} .c9 {fill:#f00;} .c10 {fill:#0f0;} .c11 {fill:#ff0;} .c12 {fill:#5c5cff;} .c13 {fill:#f0f;} .c14 {fill:#0ff;} .c15 {fill:#fff;}"#; const CSS_STYLE: &str = r#"#t{font-family:'DejaVu Sans Mono',monospace;font-style:normal;font-size:14px;} text {dominant-baseline: text-before-edge; white-space: pre;} .f{fill:#e5e5e5;} .b{fill:#000;} .c0 {fill:#000;} .c1 {fill:#cd0000;} .c2 {fill:#00cd00;} .c3 {fill:#cdcd00;} .c4 {fill:#00e;} .c5 {fill:#cd00cd;} .c6 {fill:#00cdcd;} .c7 {fill:#e5e5e5;} .c8 {fill:#7f7f7f;} .c9 {fill:#f00;} .c10 {fill:#0f0;} .c11 {fill:#ff0;} .c12 {fill:#5c5cff;} .c13 {fill:#f0f;} .c14 {fill:#0ff;} .c15 {fill:#fff;}"#;
const XTERM_COLORS: &[(u8, u8, u8)] = &[ const XTERM_COLORS: &[(u8, u8, u8)] = &[
/*0*/ (0, 0, 0), /* 0 */ (0, 0, 0),
/*1*/ (128, 0, 0), /* 1 */ (128, 0, 0),
/*2*/ (0, 128, 0), /* 2 */ (0, 128, 0),
/*3*/ (128, 128, 0), /* 3 */ (128, 128, 0),
/*4*/ (0, 0, 128), /* 4 */ (0, 0, 128),
/*5*/ (128, 0, 128), /* 5 */ (128, 0, 128),
/*6*/ (0, 128, 128), /* 6 */ (0, 128, 128),
/*7*/ (192, 192, 192), /* 7 */ (192, 192, 192),
/*8*/ (128, 128, 128), /* 8 */ (128, 128, 128),
/*9*/ (255, 0, 0), /* 9 */ (255, 0, 0),
/*10*/ (0, 255, 0), /* 10 */ (0, 255, 0),
/*11*/ (255, 255, 0), /* 11 */ (255, 255, 0),
/*12*/ (0, 0, 255), /* 12 */ (0, 0, 255),
/*13*/ (255, 0, 255), /* 13 */ (255, 0, 255),
/*14*/ (0, 255, 255), /* 14 */ (0, 255, 255),
/*15*/ (255, 255, 255), /* 15 */ (255, 255, 255),
/*16*/ (0, 0, 0), /* 16 */ (0, 0, 0),
/*17*/ (0, 0, 95), /* 17 */ (0, 0, 95),
/*18*/ (0, 0, 135), /* 18 */ (0, 0, 135),
/*19*/ (0, 0, 175), /* 19 */ (0, 0, 175),
/*20*/ (0, 0, 215), /* 20 */ (0, 0, 215),
/*21*/ (0, 0, 255), /* 21 */ (0, 0, 255),
/*22*/ (0, 95, 0), /* 22 */ (0, 95, 0),
/*23*/ (0, 95, 95), /* 23 */ (0, 95, 95),
/*24*/ (0, 95, 135), /* 24 */ (0, 95, 135),
/*25*/ (0, 95, 175), /* 25 */ (0, 95, 175),
/*26*/ (0, 95, 215), /* 26 */ (0, 95, 215),
/*27*/ (0, 95, 255), /* 27 */ (0, 95, 255),
/*28*/ (0, 135, 0), /* 28 */ (0, 135, 0),
/*29*/ (0, 135, 95), /* 29 */ (0, 135, 95),
/*30*/ (0, 135, 135), /* 30 */ (0, 135, 135),
/*31*/ (0, 135, 175), /* 31 */ (0, 135, 175),
/*32*/ (0, 135, 215), /* 32 */ (0, 135, 215),
/*33*/ (0, 135, 255), /* 33 */ (0, 135, 255),
/*34*/ (0, 175, 0), /* 34 */ (0, 175, 0),
/*35*/ (0, 175, 95), /* 35 */ (0, 175, 95),
/*36*/ (0, 175, 135), /* 36 */ (0, 175, 135),
/*37*/ (0, 175, 175), /* 37 */ (0, 175, 175),
/*38*/ (0, 175, 215), /* 38 */ (0, 175, 215),
/*39*/ (0, 175, 255), /* 39 */ (0, 175, 255),
/*40*/ (0, 215, 0), /* 40 */ (0, 215, 0),
/*41*/ (0, 215, 95), /* 41 */ (0, 215, 95),
/*42*/ (0, 215, 135), /* 42 */ (0, 215, 135),
/*43*/ (0, 215, 175), /* 43 */ (0, 215, 175),
/*44*/ (0, 215, 215), /* 44 */ (0, 215, 215),
/*45*/ (0, 215, 255), /* 45 */ (0, 215, 255),
/*46*/ (0, 255, 0), /* 46 */ (0, 255, 0),
/*47*/ (0, 255, 95), /* 47 */ (0, 255, 95),
/*48*/ (0, 255, 135), /* 48 */ (0, 255, 135),
/*49*/ (0, 255, 175), /* 49 */ (0, 255, 175),
/*50*/ (0, 255, 215), /* 50 */ (0, 255, 215),
/*51*/ (0, 255, 255), /* 51 */ (0, 255, 255),
/*52*/ (95, 0, 0), /* 52 */ (95, 0, 0),
/*53*/ (95, 0, 95), /* 53 */ (95, 0, 95),
/*54*/ (95, 0, 135), /* 54 */ (95, 0, 135),
/*55*/ (95, 0, 175), /* 55 */ (95, 0, 175),
/*56*/ (95, 0, 215), /* 56 */ (95, 0, 215),
/*57*/ (95, 0, 255), /* 57 */ (95, 0, 255),
/*58*/ (95, 95, 0), /* 58 */ (95, 95, 0),
/*59*/ (95, 95, 95), /* 59 */ (95, 95, 95),
/*60*/ (95, 95, 135), /* 60 */ (95, 95, 135),
/*61*/ (95, 95, 175), /* 61 */ (95, 95, 175),
/*62*/ (95, 95, 215), /* 62 */ (95, 95, 215),
/*63*/ (95, 95, 255), /* 63 */ (95, 95, 255),
/*64*/ (95, 135, 0), /* 64 */ (95, 135, 0),
/*65*/ (95, 135, 95), /* 65 */ (95, 135, 95),
/*66*/ (95, 135, 135), /* 66 */ (95, 135, 135),
/*67*/ (95, 135, 175), /* 67 */ (95, 135, 175),
/*68*/ (95, 135, 215), /* 68 */ (95, 135, 215),
/*69*/ (95, 135, 255), /* 69 */ (95, 135, 255),
/*70*/ (95, 175, 0), /* 70 */ (95, 175, 0),
/*71*/ (95, 175, 95), /* 71 */ (95, 175, 95),
/*72*/ (95, 175, 135), /* 72 */ (95, 175, 135),
/*73*/ (95, 175, 175), /* 73 */ (95, 175, 175),
/*74*/ (95, 175, 215), /* 74 */ (95, 175, 215),
/*75*/ (95, 175, 255), /* 75 */ (95, 175, 255),
/*76*/ (95, 215, 0), /* 76 */ (95, 215, 0),
/*77*/ (95, 215, 95), /* 77 */ (95, 215, 95),
/*78*/ (95, 215, 135), /* 78 */ (95, 215, 135),
/*79*/ (95, 215, 175), /* 79 */ (95, 215, 175),
/*80*/ (95, 215, 215), /* 80 */ (95, 215, 215),
/*81*/ (95, 215, 255), /* 81 */ (95, 215, 255),
/*82*/ (95, 255, 0), /* 82 */ (95, 255, 0),
/*83*/ (95, 255, 95), /* 83 */ (95, 255, 95),
/*84*/ (95, 255, 135), /* 84 */ (95, 255, 135),
/*85*/ (95, 255, 175), /* 85 */ (95, 255, 175),
/*86*/ (95, 255, 215), /* 86 */ (95, 255, 215),
/*87*/ (95, 255, 255), /* 87 */ (95, 255, 255),
/*88*/ (135, 0, 0), /* 88 */ (135, 0, 0),
/*89*/ (135, 0, 95), /* 89 */ (135, 0, 95),
/*90*/ (135, 0, 135), /* 90 */ (135, 0, 135),
/*91*/ (135, 0, 175), /* 91 */ (135, 0, 175),
/*92*/ (135, 0, 215), /* 92 */ (135, 0, 215),
/*93*/ (135, 0, 255), /* 93 */ (135, 0, 255),
/*94*/ (135, 95, 0), /* 94 */ (135, 95, 0),
/*95*/ (135, 95, 95), /* 95 */ (135, 95, 95),
/*96*/ (135, 95, 135), /* 96 */ (135, 95, 135),
/*97*/ (135, 95, 175), /* 97 */ (135, 95, 175),
/*98*/ (135, 95, 215), /* 98 */ (135, 95, 215),
/*99*/ (135, 95, 255), /* 99 */ (135, 95, 255),
/*100*/ (135, 135, 0), /* 100 */ (135, 135, 0),
/*101*/ (135, 135, 95), /* 101 */ (135, 135, 95),
/*102*/ (135, 135, 135), /* 102 */ (135, 135, 135),
/*103*/ (135, 135, 175), /* 103 */ (135, 135, 175),
/*104*/ (135, 135, 215), /* 104 */ (135, 135, 215),
/*105*/ (135, 135, 255), /* 105 */ (135, 135, 255),
/*106*/ (135, 175, 0), /* 106 */ (135, 175, 0),
/*107*/ (135, 175, 95), /* 107 */ (135, 175, 95),
/*108*/ (135, 175, 135), /* 108 */ (135, 175, 135),
/*109*/ (135, 175, 175), /* 109 */ (135, 175, 175),
/*110*/ (135, 175, 215), /* 110 */ (135, 175, 215),
/*111*/ (135, 175, 255), /* 111 */ (135, 175, 255),
/*112*/ (135, 215, 0), /* 112 */ (135, 215, 0),
/*113*/ (135, 215, 95), /* 113 */ (135, 215, 95),
/*114*/ (135, 215, 135), /* 114 */ (135, 215, 135),
/*115*/ (135, 215, 175), /* 115 */ (135, 215, 175),
/*116*/ (135, 215, 215), /* 116 */ (135, 215, 215),
/*117*/ (135, 215, 255), /* 117 */ (135, 215, 255),
/*118*/ (135, 255, 0), /* 118 */ (135, 255, 0),
/*119*/ (135, 255, 95), /* 119 */ (135, 255, 95),
/*120*/ (135, 255, 135), /* 120 */ (135, 255, 135),
/*121*/ (135, 255, 175), /* 121 */ (135, 255, 175),
/*122*/ (135, 255, 215), /* 122 */ (135, 255, 215),
/*123*/ (135, 255, 255), /* 123 */ (135, 255, 255),
/*124*/ (175, 0, 0), /* 124 */ (175, 0, 0),
/*125*/ (175, 0, 95), /* 125 */ (175, 0, 95),
/*126*/ (175, 0, 135), /* 126 */ (175, 0, 135),
/*127*/ (175, 0, 175), /* 127 */ (175, 0, 175),
/*128*/ (175, 0, 215), /* 128 */ (175, 0, 215),
/*129*/ (175, 0, 255), /* 129 */ (175, 0, 255),
/*130*/ (175, 95, 0), /* 130 */ (175, 95, 0),
/*131*/ (175, 95, 95), /* 131 */ (175, 95, 95),
/*132*/ (175, 95, 135), /* 132 */ (175, 95, 135),
/*133*/ (175, 95, 175), /* 133 */ (175, 95, 175),
/*134*/ (175, 95, 215), /* 134 */ (175, 95, 215),
/*135*/ (175, 95, 255), /* 135 */ (175, 95, 255),
/*136*/ (175, 135, 0), /* 136 */ (175, 135, 0),
/*137*/ (175, 135, 95), /* 137 */ (175, 135, 95),
/*138*/ (175, 135, 135), /* 138 */ (175, 135, 135),
/*139*/ (175, 135, 175), /* 139 */ (175, 135, 175),
/*140*/ (175, 135, 215), /* 140 */ (175, 135, 215),
/*141*/ (175, 135, 255), /* 141 */ (175, 135, 255),
/*142*/ (175, 175, 0), /* 142 */ (175, 175, 0),
/*143*/ (175, 175, 95), /* 143 */ (175, 175, 95),
/*144*/ (175, 175, 135), /* 144 */ (175, 175, 135),
/*145*/ (175, 175, 175), /* 145 */ (175, 175, 175),
/*146*/ (175, 175, 215), /* 146 */ (175, 175, 215),
/*147*/ (175, 175, 255), /* 147 */ (175, 175, 255),
/*148*/ (175, 215, 0), /* 148 */ (175, 215, 0),
/*149*/ (175, 215, 95), /* 149 */ (175, 215, 95),
/*150*/ (175, 215, 135), /* 150 */ (175, 215, 135),
/*151*/ (175, 215, 175), /* 151 */ (175, 215, 175),
/*152*/ (175, 215, 215), /* 152 */ (175, 215, 215),
/*153*/ (175, 215, 255), /* 153 */ (175, 215, 255),
/*154*/ (175, 255, 0), /* 154 */ (175, 255, 0),
/*155*/ (175, 255, 95), /* 155 */ (175, 255, 95),
/*156*/ (175, 255, 135), /* 156 */ (175, 255, 135),
/*157*/ (175, 255, 175), /* 157 */ (175, 255, 175),
/*158*/ (175, 255, 215), /* 158 */ (175, 255, 215),
/*159*/ (175, 255, 255), /* 159 */ (175, 255, 255),
/*160*/ (215, 0, 0), /* 160 */ (215, 0, 0),
/*161*/ (215, 0, 95), /* 161 */ (215, 0, 95),
/*162*/ (215, 0, 135), /* 162 */ (215, 0, 135),
/*163*/ (215, 0, 175), /* 163 */ (215, 0, 175),
/*164*/ (215, 0, 215), /* 164 */ (215, 0, 215),
/*165*/ (215, 0, 255), /* 165 */ (215, 0, 255),
/*166*/ (215, 95, 0), /* 166 */ (215, 95, 0),
/*167*/ (215, 95, 95), /* 167 */ (215, 95, 95),
/*168*/ (215, 95, 135), /* 168 */ (215, 95, 135),
/*169*/ (215, 95, 175), /* 169 */ (215, 95, 175),
/*170*/ (215, 95, 215), /* 170 */ (215, 95, 215),
/*171*/ (215, 95, 255), /* 171 */ (215, 95, 255),
/*172*/ (215, 135, 0), /* 172 */ (215, 135, 0),
/*173*/ (215, 135, 95), /* 173 */ (215, 135, 95),
/*174*/ (215, 135, 135), /* 174 */ (215, 135, 135),
/*175*/ (215, 135, 175), /* 175 */ (215, 135, 175),
/*176*/ (215, 135, 215), /* 176 */ (215, 135, 215),
/*177*/ (215, 135, 255), /* 177 */ (215, 135, 255),
/*178*/ (215, 175, 0), /* 178 */ (215, 175, 0),
/*179*/ (215, 175, 95), /* 179 */ (215, 175, 95),
/*180*/ (215, 175, 135), /* 180 */ (215, 175, 135),
/*181*/ (215, 175, 175), /* 181 */ (215, 175, 175),
/*182*/ (215, 175, 215), /* 182 */ (215, 175, 215),
/*183*/ (215, 175, 255), /* 183 */ (215, 175, 255),
/*184*/ (215, 215, 0), /* 184 */ (215, 215, 0),
/*185*/ (215, 215, 95), /* 185 */ (215, 215, 95),
/*186*/ (215, 215, 135), /* 186 */ (215, 215, 135),
/*187*/ (215, 215, 175), /* 187 */ (215, 215, 175),
/*188*/ (215, 215, 215), /* 188 */ (215, 215, 215),
/*189*/ (215, 215, 255), /* 189 */ (215, 215, 255),
/*190*/ (215, 255, 0), /* 190 */ (215, 255, 0),
/*191*/ (215, 255, 95), /* 191 */ (215, 255, 95),
/*192*/ (215, 255, 135), /* 192 */ (215, 255, 135),
/*193*/ (215, 255, 175), /* 193 */ (215, 255, 175),
/*194*/ (215, 255, 215), /* 194 */ (215, 255, 215),
/*195*/ (215, 255, 255), /* 195 */ (215, 255, 255),
/*196*/ (255, 0, 0), /* 196 */ (255, 0, 0),
/*197*/ (255, 0, 95), /* 197 */ (255, 0, 95),
/*198*/ (255, 0, 135), /* 198 */ (255, 0, 135),
/*199*/ (255, 0, 175), /* 199 */ (255, 0, 175),
/*200*/ (255, 0, 215), /* 200 */ (255, 0, 215),
/*201*/ (255, 0, 255), /* 201 */ (255, 0, 255),
/*202*/ (255, 95, 0), /* 202 */ (255, 95, 0),
/*203*/ (255, 95, 95), /* 203 */ (255, 95, 95),
/*204*/ (255, 95, 135), /* 204 */ (255, 95, 135),
/*205*/ (255, 95, 175), /* 205 */ (255, 95, 175),
/*206*/ (255, 95, 215), /* 206 */ (255, 95, 215),
/*207*/ (255, 95, 255), /* 207 */ (255, 95, 255),
/*208*/ (255, 135, 0), /* 208 */ (255, 135, 0),
/*209*/ (255, 135, 95), /* 209 */ (255, 135, 95),
/*210*/ (255, 135, 135), /* 210 */ (255, 135, 135),
/*211*/ (255, 135, 175), /* 211 */ (255, 135, 175),
/*212*/ (255, 135, 215), /* 212 */ (255, 135, 215),
/*213*/ (255, 135, 255), /* 213 */ (255, 135, 255),
/*214*/ (255, 175, 0), /* 214 */ (255, 175, 0),
/*215*/ (255, 175, 95), /* 215 */ (255, 175, 95),
/*216*/ (255, 175, 135), /* 216 */ (255, 175, 135),
/*217*/ (255, 175, 175), /* 217 */ (255, 175, 175),
/*218*/ (255, 175, 215), /* 218 */ (255, 175, 215),
/*219*/ (255, 175, 255), /* 219 */ (255, 175, 255),
/*220*/ (255, 215, 0), /* 220 */ (255, 215, 0),
/*221*/ (255, 215, 95), /* 221 */ (255, 215, 95),
/*222*/ (255, 215, 135), /* 222 */ (255, 215, 135),
/*223*/ (255, 215, 175), /* 223 */ (255, 215, 175),
/*224*/ (255, 215, 215), /* 224 */ (255, 215, 215),
/*225*/ (255, 215, 255), /* 225 */ (255, 215, 255),
/*226*/ (255, 255, 0), /* 226 */ (255, 255, 0),
/*227*/ (255, 255, 95), /* 227 */ (255, 255, 95),
/*228*/ (255, 255, 135), /* 228 */ (255, 255, 135),
/*229*/ (255, 255, 175), /* 229 */ (255, 255, 175),
/*230*/ (255, 255, 215), /* 230 */ (255, 255, 215),
/*231*/ (255, 255, 255), /* 231 */ (255, 255, 255),
/*232*/ (8, 8, 8), /* 232 */ (8, 8, 8),
/*233*/ (18, 18, 18), /* 233 */ (18, 18, 18),
/*234*/ (28, 28, 28), /* 234 */ (28, 28, 28),
/*235*/ (38, 38, 38), /* 235 */ (38, 38, 38),
/*236*/ (48, 48, 48), /* 236 */ (48, 48, 48),
/*237*/ (58, 58, 58), /* 237 */ (58, 58, 58),
/*238*/ (68, 68, 68), /* 238 */ (68, 68, 68),
/*239*/ (78, 78, 78), /* 239 */ (78, 78, 78),
/*240*/ (88, 88, 88), /* 240 */ (88, 88, 88),
/*241*/ (98, 98, 98), /* 241 */ (98, 98, 98),
/*242*/ (108, 108, 108), /* 242 */ (108, 108, 108),
/*243*/ (118, 118, 118), /* 243 */ (118, 118, 118),
/*244*/ (128, 128, 128), /* 244 */ (128, 128, 128),
/*245*/ (138, 138, 138), /* 245 */ (138, 138, 138),
/*246*/ (148, 148, 148), /* 246 */ (148, 148, 148),
/*247*/ (158, 158, 158), /* 247 */ (158, 158, 158),
/*248*/ (168, 168, 168), /* 248 */ (168, 168, 168),
/*249*/ (178, 178, 178), /* 249 */ (178, 178, 178),
/*250*/ (188, 188, 188), /* 250 */ (188, 188, 188),
/*251*/ (198, 198, 198), /* 251 */ (198, 198, 198),
/*252*/ (208, 208, 208), /* 252 */ (208, 208, 208),
/*253*/ (218, 218, 218), /* 253 */ (218, 218, 218),
/*254*/ (228, 228, 228), /* 254 */ (228, 228, 228),
/*255*/ (238, 238, 238), /* 255 */ (238, 238, 238),
]; ];

View File

@ -21,9 +21,10 @@
/*! Various useful components that can be used in a generic fashion. /*! Various useful components that can be used in a generic fashion.
*/ */
use super::*;
use text_processing::Reflow; use text_processing::Reflow;
use super::*;
mod pager; mod pager;
pub use self::pager::*; pub use self::pager::*;
@ -37,11 +38,11 @@ mod dialogs;
pub use self::dialogs::*; pub use self::dialogs::*;
mod tables; mod tables;
pub use self::tables::*;
use crate::jobs::JobId;
use std::collections::HashSet; use std::collections::HashSet;
pub use self::tables::*;
use crate::jobs::JobId;
#[derive(Default, Debug, Clone)] #[derive(Default, Debug, Clone)]
pub struct SearchPattern { pub struct SearchPattern {
pattern: String, pattern: String,
@ -943,12 +944,13 @@ impl Component for Tabbed {
)); ));
} }
/* If children are dirty but self isn't and the shortcuts panel is visible, it will get /* If children are dirty but self isn't and the shortcuts panel is visible,
* overwritten. */ * it will get overwritten. */
let must_redraw_shortcuts: bool = self.show_shortcuts && !self.dirty && self.is_dirty(); let must_redraw_shortcuts: bool = self.show_shortcuts && !self.dirty && self.is_dirty();
/* children should be drawn after the shortcuts/help panel lest they overwrite the panel on /* children should be drawn after the shortcuts/help panel lest they
* the grid. the drawing order is determined by the dirty_areas queue which is LIFO */ * overwrite the panel on the grid. the drawing order is determined
* by the dirty_areas queue which is LIFO */
if self.children.len() > 1 { if self.children.len() > 1 {
self.draw_tabs( self.draw_tabs(
grid, grid,

View File

@ -40,9 +40,10 @@ enum SelectorCursor {
/// Shows a little window with options for user to select. /// Shows a little window with options for user to select.
/// ///
/// Instantiate with Selector::new(). Set single_only to true if user should only choose one of the /// Instantiate with Selector::new(). Set single_only to true if user should
/// options. After passing input events to this component, check Selector::is_done to see if the /// only choose one of the options. After passing input events to this
/// user has finalised their choices. Collect the choices by consuming the Selector with /// component, check Selector::is_done to see if the user has finalised their
/// choices. Collect the choices by consuming the Selector with
/// Selector::collect() /// Selector::collect()
pub struct Selector<T: 'static + PartialEq + Debug + Clone + Sync + Send, F: 'static + Sync + Send> pub struct Selector<T: 'static + PartialEq + Debug + Clone + Sync + Send, F: 'static + Sync + Send>
{ {

Some files were not shown because too many files have changed in this diff Show More