forked from meli/meli
1
Fork 0

Compare commits

...

4 Commits

Author SHA1 Message Date
Manos Pitsidianakis b2ff359eb7 melib/imap: add support for CHILDREN extesion RFC 3348 2022-09-03 23:59:29 +03:00
Manos Pitsidianakis 9fcf88b494 Wrap {AccountHash,MailboxHash} type aliases in New Types wrappers 2022-09-03 23:35:24 +03:00
Manos Pitsidianakis 808bdf75a1 melib/smtp: add BINARYMIME support to smtp client
Concerns #49

IMAP: Lemonade profile tracking issue
2022-09-03 18:17:17 +03:00
Manos Pitsidianakis ed16e29de1 melib/smtp: add 8BITMIME support to smtp client
Concerns #49

IMAP: Lemonade profile tracking issue
2022-09-03 18:02:58 +03:00
21 changed files with 213 additions and 184 deletions

View File

@ -257,6 +257,27 @@ impl Backends {
}
}
#[derive(
Debug, Serialize, Deserialize, Clone, PartialOrd, Ord, Default, Copy, Hash, PartialEq, Eq,
)]
pub struct AccountHash(pub u64);
#[derive(
Debug, Serialize, Deserialize, Clone, PartialOrd, Ord, Default, Copy, Hash, PartialEq, Eq,
)]
pub struct MailboxHash(pub u64);
impl std::fmt::Display for AccountHash {
fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
write!(f, "AccountHash({})", self.0)
}
}
impl std::fmt::Display for MailboxHash {
fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
write!(f, "MailboxHash({})", self.0)
}
}
#[derive(Debug, Clone)]
pub enum BackendEvent {
Notice {
@ -589,8 +610,6 @@ pub trait BackendMailbox: Debug {
fn count(&self) -> Result<(usize, usize)>;
}
pub type AccountHash = u64;
pub type MailboxHash = u64;
pub type Mailbox = Box<dyn BackendMailbox + Send + Sync>;
impl Clone for Mailbox {

View File

@ -66,6 +66,7 @@ pub type MessageSequenceNumber = ImapNum;
pub static SUPPORTED_CAPABILITIES: &[&str] = &[
"AUTH=OAUTH2",
"CHILDREN",
#[cfg(feature = "deflate_compression")]
"COMPRESS=DEFLATE",
"CONDSTORE",
@ -505,7 +506,7 @@ impl MailBackend for ImapType {
let account_hash = uid_store.account_hash;
main_conn_lck.add_refresh_event(RefreshEvent {
account_hash,
mailbox_hash: 0,
mailbox_hash: MailboxHash(0),
kind: RefreshEventKind::Failure(err.clone()),
});
return Err(err);
@ -938,7 +939,7 @@ impl MailBackend for ImapType {
}
let ret: Result<()> = ImapResponse::try_from(response.as_slice())?.into();
ret?;
let new_hash = get_path_hash!(path.as_str());
let new_hash = MailboxHash(get_path_hash!(path.as_str()));
uid_store.mailboxes.lock().await.clear();
Ok((new_hash, new_mailbox_fut?.await.map_err(|err| MeliError::new(format!("Mailbox create was succesful (returned `{}`) but listing mailboxes afterwards returned `{}`", String::from_utf8_lossy(&response), err)))?))
}))
@ -1075,7 +1076,7 @@ impl MailBackend for ImapType {
.read_response(&mut response, RequiredResponses::empty())
.await?;
}
let new_hash = get_path_hash!(new_path.as_str());
let new_hash = MailboxHash(get_path_hash!(new_path.as_str()));
let ret: Result<()> = ImapResponse::try_from(response.as_slice())?.into();
ret?;
uid_store.mailboxes.lock().await.clear();
@ -1322,7 +1323,7 @@ impl ImapType {
let account_hash = {
let mut hasher = DefaultHasher::new();
hasher.write(s.name.as_bytes());
hasher.finish()
AccountHash(hasher.finish())
};
let account_name = Arc::new(s.name().to_string());
let uid_store: Arc<UIDStore> = Arc::new(UIDStore {
@ -1475,7 +1476,7 @@ impl ImapType {
debug!("parse error for {:?}", l);
}
}
mailboxes.retain(|_, v| v.hash != 0);
mailboxes.retain(|_, v| v.hash.0 != 0);
conn.send_command(b"LSUB \"\" \"*\"").await?;
conn.read_response(&mut res, RequiredResponses::LSUB_REQUIRED)
.await?;

View File

@ -177,7 +177,7 @@ mod sqlite3_m {
.prepare("SELECT MAX(uid) FROM envelopes WHERE mailbox_hash = ?1;")?;
let mut ret: Vec<UID> = stmt
.query_map(sqlite3::params![mailbox_hash as i64], |row| {
.query_map(sqlite3::params![mailbox_hash.0 as i64], |row| {
Ok(row.get(0).map(|i: Sqlite3UID| i as UID)?)
})?
.collect::<std::result::Result<_, _>>()?;
@ -199,7 +199,7 @@ mod sqlite3_m {
"SELECT uidvalidity, flags, highestmodseq FROM mailbox WHERE mailbox_hash = ?1;",
)?;
let mut ret = stmt.query_map(sqlite3::params![mailbox_hash as i64], |row| {
let mut ret = stmt.query_map(sqlite3::params![mailbox_hash.0 as i64], |row| {
Ok((
row.get(0).map(|u: Sqlite3UID| u as UID)?,
row.get(1)?,
@ -265,7 +265,7 @@ mod sqlite3_m {
self.connection
.execute(
"DELETE FROM mailbox WHERE mailbox_hash = ?1",
sqlite3::params![mailbox_hash as i64],
sqlite3::params![mailbox_hash.0 as i64],
)
.chain_err_summary(|| {
format!(
@ -277,7 +277,7 @@ mod sqlite3_m {
if let Some(Ok(highestmodseq)) = select_response.highestmodseq {
self.connection.execute(
"INSERT OR IGNORE INTO mailbox (uidvalidity, flags, highestmodseq, mailbox_hash) VALUES (?1, ?2, ?3, ?4)",
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 as i64],
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.0 as i64],
)
.chain_err_summary(|| {
format!(
@ -292,7 +292,7 @@ mod sqlite3_m {
sqlite3::params![
select_response.uidvalidity as Sqlite3UID,
select_response.flags.1.iter().map(|s| s.as_str()).collect::<Vec<&str>>().join("\0").as_bytes(),
mailbox_hash as i64
mailbox_hash.0 as i64
],
)
.chain_err_summary(|| {
@ -328,7 +328,7 @@ mod sqlite3_m {
.join("\0")
.as_bytes(),
highestmodseq,
mailbox_hash as i64
mailbox_hash.0 as i64
],
)
.chain_err_summary(|| {
@ -350,7 +350,7 @@ mod sqlite3_m {
.collect::<Vec<&str>>()
.join("\0")
.as_bytes(),
mailbox_hash as i64
mailbox_hash.0 as i64
],
)
.chain_err_summary(|| {
@ -374,7 +374,7 @@ mod sqlite3_m {
)?;
let ret: Vec<(UID, Envelope, Option<ModSequence>)> = stmt
.query_map(sqlite3::params![mailbox_hash as i64], |row| {
.query_map(sqlite3::params![mailbox_hash.0 as i64], |row| {
Ok((
row.get(0).map(|i: Sqlite3UID| i as UID)?,
row.get(1)?,
@ -452,7 +452,7 @@ mod sqlite3_m {
max_uid = std::cmp::max(max_uid, *uid);
tx.execute(
"INSERT OR REPLACE INTO envelopes (hash, uid, mailbox_hash, modsequence, envelope) VALUES (?1, ?2, ?3, ?4, ?5)",
sqlite3::params![envelope.hash() as i64, *uid as Sqlite3UID, mailbox_hash as i64, modseq, &envelope],
sqlite3::params![envelope.hash() as i64, *uid as Sqlite3UID, mailbox_hash.0 as i64, modseq, &envelope],
).chain_err_summary(|| format!("Could not insert envelope {} {} in header_cache of account {}", envelope.message_id(), envelope.hash(), uid_store.account_name))?;
}
}
@ -486,7 +486,7 @@ mod sqlite3_m {
hash_index_lck.remove(&env_hash);
tx.execute(
"DELETE FROM envelopes WHERE mailbox_hash = ?1 AND uid = ?2;",
sqlite3::params![mailbox_hash as i64, *uid as Sqlite3UID],
sqlite3::params![mailbox_hash.0 as i64, *uid as Sqlite3UID],
)
.chain_err_summary(|| {
format!(
@ -502,7 +502,7 @@ mod sqlite3_m {
let mut ret: Vec<Envelope> = stmt
.query_map(
sqlite3::params![mailbox_hash as i64, *uid as Sqlite3UID],
sqlite3::params![mailbox_hash.0 as i64, *uid as Sqlite3UID],
|row| Ok(row.get(0)?),
)?
.collect::<std::result::Result<_, _>>()?;
@ -512,7 +512,7 @@ mod sqlite3_m {
env.labels_mut().extend(tags.iter().map(|t| tag_hash!(t)));
tx.execute(
"UPDATE envelopes SET envelope = ?1 WHERE mailbox_hash = ?2 AND uid = ?3;",
sqlite3::params![&env, mailbox_hash as i64, *uid as Sqlite3UID],
sqlite3::params![&env, mailbox_hash.0 as i64, *uid as Sqlite3UID],
)
.chain_err_summary(|| {
format!(
@ -556,7 +556,7 @@ mod sqlite3_m {
let x = stmt
.query_map(
sqlite3::params![mailbox_hash as i64, uid as Sqlite3UID],
sqlite3::params![mailbox_hash.0 as i64, uid as Sqlite3UID],
|row| {
Ok((
row.get(0).map(|u: Sqlite3UID| u as UID)?,
@ -575,7 +575,7 @@ mod sqlite3_m {
let x = stmt
.query_map(
sqlite3::params![mailbox_hash as i64, env_hash as i64],
sqlite3::params![mailbox_hash.0 as i64, env_hash as i64],
|row| {
Ok((
row.get(0).map(|u: Sqlite3UID| u as UID)?,
@ -612,7 +612,7 @@ mod sqlite3_m {
)?;
let x = stmt
.query_map(
sqlite3::params![mailbox_hash as i64, uid as Sqlite3UID],
sqlite3::params![mailbox_hash.0 as i64, uid as Sqlite3UID],
|row| Ok(row.get(0)?),
)?
.collect::<std::result::Result<_, _>>()?;
@ -624,7 +624,7 @@ mod sqlite3_m {
)?;
let x = stmt
.query_map(
sqlite3::params![mailbox_hash as i64, env_hash as i64],
sqlite3::params![mailbox_hash.0 as i64, env_hash as i64],
|row| Ok(row.get(0)?),
)?
.collect::<std::result::Result<_, _>>()?;

View File

@ -33,6 +33,7 @@ pub struct ImapMailbox {
pub path: String,
pub name: String,
pub parent: Option<MailboxHash>,
pub has_children: bool,
pub children: Vec<MailboxHash>,
pub separator: u8,
pub usage: Arc<RwLock<SpecialUsageMailbox>>,
@ -47,6 +48,12 @@ pub struct ImapMailbox {
}
impl ImapMailbox {
pub fn set_has_children(&mut self, has_children: bool) -> &mut Self {
self.has_children = has_children;
self
}
pub fn imap_path(&self) -> &str {
&self.imap_path
}
@ -88,6 +95,9 @@ impl BackendMailbox for ImapMailbox {
}
fn children(&self) -> &[MailboxHash] {
if !self.has_children {
return &[];
}
&self.children
}
@ -106,9 +116,11 @@ impl BackendMailbox for ImapMailbox {
fn permissions(&self) -> MailboxPermissions {
*self.permissions.lock().unwrap()
}
fn is_subscribed(&self) -> bool {
self.is_subscribed
}
fn set_is_subscribed(&mut self, new_val: bool) -> Result<()> {
self.is_subscribed = new_val;
Ok(())

View File

@ -449,6 +449,7 @@ pub fn list_mailbox_result(input: &[u8]) -> IResult<&[u8], ImapMailbox> {
({
let separator: u8 = separator[0];
let mut f = ImapMailbox::default();
f.has_children = true;
f.no_select = false;
f.is_subscribed = false;
@ -475,10 +476,14 @@ pub fn list_mailbox_result(input: &[u8]) -> IResult<&[u8], ImapMailbox> {
let _ = f.set_special_usage(SpecialUsageMailbox::Flagged);
} else if p.eq_ignore_ascii_case(b"\\Archive") {
let _ = f.set_special_usage(SpecialUsageMailbox::Archive);
} else if p.eq_ignore_ascii_case(b"\\HasChildren") {
f.has_children = true;
} else if p.eq_ignore_ascii_case(b"\\HasNoChildren") {
f.has_children = false;
}
}
f.imap_path = path.to_string();
f.hash = get_path_hash!(&f.imap_path);
f.hash = MailboxHash(get_path_hash!(&f.imap_path));
f.path = if separator == b'/' {
f.imap_path.clone()
} else {
@ -486,7 +491,7 @@ pub fn list_mailbox_result(input: &[u8]) -> IResult<&[u8], ImapMailbox> {
};
f.name = if let Some(pos) = f.imap_path.as_bytes().iter().rposition(|&c| c == separator)
{
f.parent = Some(get_path_hash!(&f.imap_path[..pos]));
f.parent = Some(MailboxHash(get_path_hash!(&f.imap_path[..pos])));
f.imap_path[pos + 1..].to_string()
} else {
f.imap_path.clone()
@ -1561,7 +1566,9 @@ pub struct StatusResponse {
pub fn status_response(input: &[u8]) -> IResult<&[u8], StatusResponse> {
let (input, _) = tag("* STATUS ")(input)?;
let (input, mailbox) = take_until(" (")(input)?;
let mailbox = mailbox_token(mailbox).map(|(_, m)| get_path_hash!(m)).ok();
let mailbox = mailbox_token(mailbox)
.map(|(_, m)| MailboxHash(get_path_hash!(m)))
.ok();
let (input, _) = tag(" (")(input)?;
let (input, result) = permutation((
opt(preceded(

View File

@ -138,8 +138,11 @@ impl MaildirMailbox {
settings: &AccountSettings,
) -> Result<Self> {
let pathbuf = PathBuf::from(&path);
let mut h = DefaultHasher::new();
pathbuf.hash(&mut h);
let hash = {
let mut h = DefaultHasher::new();
pathbuf.hash(&mut h);
MailboxHash(h.finish())
};
/* Check if mailbox path (Eg `INBOX/Lists/luddites`) is included in the subscribed
* mailboxes in user configuration */
@ -159,7 +162,7 @@ impl MaildirMailbox {
};
let ret = MaildirMailbox {
hash: h.finish(),
hash,
name: file_name,
path: fname.unwrap().to_path_buf(),
fs_path: pathbuf,

View File

@ -220,7 +220,7 @@ impl MailBackend for MaildirType {
let account_hash = {
let mut hasher = DefaultHasher::default();
hasher.write(self.name.as_bytes());
hasher.finish()
AccountHash(hasher.finish())
};
let sender = self.event_consumer.clone();
@ -327,7 +327,7 @@ impl MailBackend for MaildirType {
let account_hash = {
let mut hasher = DefaultHasher::default();
hasher.write(self.name.as_bytes());
hasher.finish()
AccountHash(hasher.finish())
};
let root_path = self.path.to_path_buf();
watcher.watch(&root_path, RecursiveMode::Recursive).unwrap();
@ -377,7 +377,7 @@ impl MailBackend for MaildirType {
}
};
}
let mailbox_hash = get_path_hash!(pathbuf);
let mailbox_hash = MailboxHash(get_path_hash!(pathbuf));
let file_name = pathbuf
.as_path()
.strip_prefix(&root_path)
@ -418,7 +418,7 @@ impl MailBackend for MaildirType {
/* Update */
DebouncedEvent::NoticeWrite(pathbuf) | DebouncedEvent::Write(pathbuf) => {
debug!("DebouncedEvent::Write(path = {:?}", &pathbuf);
let mailbox_hash = get_path_hash!(pathbuf);
let mailbox_hash = MailboxHash(get_path_hash!(pathbuf));
let mut hash_indexes_lock = hash_indexes.lock().unwrap();
let index_lock =
&mut hash_indexes_lock.entry(mailbox_hash).or_default();
@ -495,7 +495,7 @@ impl MailBackend for MaildirType {
/* Remove */
DebouncedEvent::NoticeRemove(pathbuf) | DebouncedEvent::Remove(pathbuf) => {
debug!("DebouncedEvent::Remove(path = {:?}", pathbuf);
let mailbox_hash = get_path_hash!(pathbuf);
let mailbox_hash = MailboxHash(get_path_hash!(pathbuf));
let mut hash_indexes_lock = hash_indexes.lock().unwrap();
let index_lock = hash_indexes_lock.entry(mailbox_hash).or_default();
let hash: EnvelopeHash = if let Some((k, _)) =
@ -549,9 +549,9 @@ impl MailBackend for MaildirType {
/* Envelope hasn't changed */
DebouncedEvent::Rename(src, dest) => {
debug!("DebouncedEvent::Rename(src = {:?}, dest = {:?})", src, dest);
let mailbox_hash = get_path_hash!(src);
let mailbox_hash = MailboxHash(get_path_hash!(src));
let dest_mailbox = {
let dest_mailbox = get_path_hash!(dest);
let dest_mailbox = MailboxHash(get_path_hash!(dest));
if dest_mailbox == mailbox_hash {
None
} else {
@ -787,7 +787,7 @@ impl MailBackend for MaildirType {
/* Maybe a re-read should be triggered here just to be safe.
(sender)(account_hash, BackendEvent::Refresh(RefreshEvent {
account_hash,
mailbox_hash: get_path_hash!(dest),
mailbox_hash: MailboxHash(get_path_hash!(dest)),
kind: Rescan,
}));
*/
@ -1011,7 +1011,7 @@ impl MailBackend for MaildirType {
.map(|item| *item.0)
});
let mailbox_hash = get_path_hash!(&path);
let mailbox_hash = MailboxHash(get_path_hash!(&path));
if let Some(parent) = parent {
self.mailboxes
.entry(parent)

View File

@ -981,7 +981,7 @@ impl MailBackend for MboxType {
let account_hash = {
let mut hasher = DefaultHasher::new();
hasher.write(self.account_name.as_bytes());
hasher.finish()
AccountHash(hasher.finish())
};
let mailboxes = self.mailboxes.clone();
let mailbox_index = self.mailbox_index.clone();
@ -1002,7 +1002,7 @@ impl MailBackend for MboxType {
Ok(event) => match event {
/* Update */
DebouncedEvent::NoticeWrite(pathbuf) | DebouncedEvent::Write(pathbuf) => {
let mailbox_hash = get_path_hash!(&pathbuf);
let mailbox_hash = MailboxHash(get_path_hash!(&pathbuf));
let file = match std::fs::OpenOptions::new()
.read(true)
.write(true)
@ -1064,7 +1064,7 @@ impl MailBackend for MboxType {
.values()
.any(|f| f.fs_path == pathbuf)
{
let mailbox_hash = get_path_hash!(&pathbuf);
let mailbox_hash = MailboxHash(get_path_hash!(&pathbuf));
(sender)(
account_hash,
BackendEvent::Refresh(RefreshEvent {
@ -1081,7 +1081,7 @@ impl MailBackend for MboxType {
}
DebouncedEvent::Rename(src, dest) => {
if mailboxes.lock().unwrap().values().any(|f| f.fs_path == src) {
let mailbox_hash = get_path_hash!(&src);
let mailbox_hash = MailboxHash(get_path_hash!(&src));
(sender)(
account_hash,
BackendEvent::Refresh(RefreshEvent {
@ -1265,7 +1265,7 @@ impl MboxType {
.file_name()
.map(|f| f.to_string_lossy().into())
.unwrap_or_default();
let hash = get_path_hash!(&ret.path);
let hash = MailboxHash(get_path_hash!(&ret.path));
let read_only = if let Ok(metadata) = std::fs::metadata(&ret.path) {
metadata.permissions().readonly()
@ -1303,7 +1303,7 @@ impl MboxType {
/* Look for other mailboxes */
for (k, f) in s.mailboxes.iter() {
if let Some(path_str) = f.extra.get("path") {
let hash = get_path_hash!(path_str);
let hash = MailboxHash(get_path_hash!(path_str));
let pathbuf: PathBuf = path_str.into();
if !pathbuf.exists() || pathbuf.is_dir() {
return Err(MeliError::new(format!(

View File

@ -573,12 +573,12 @@ impl NntpType {
let account_hash = {
let mut hasher = DefaultHasher::new();
hasher.write(s.name.as_bytes());
hasher.finish()
AccountHash(hasher.finish())
};
let account_name = Arc::new(s.name().to_string());
let mut mailboxes = HashMap::default();
for (k, _f) in s.mailboxes.iter() {
let mailbox_hash = get_path_hash!(&k);
let mailbox_hash = MailboxHash(get_path_hash!(&k));
mailboxes.insert(
mailbox_hash,
NntpMailbox {
@ -645,7 +645,7 @@ impl NntpType {
if s.len() != 3 {
continue;
}
let mailbox_hash = get_path_hash!(&s[0]);
let mailbox_hash = MailboxHash(get_path_hash!(&s[0]));
mailboxes_lck.entry(mailbox_hash).and_modify(|m| {
*m.high_watermark.lock().unwrap() = usize::from_str(s[1]).unwrap_or(0);
*m.low_watermark.lock().unwrap() = usize::from_str(s[2]).unwrap_or(0);

View File

@ -367,7 +367,7 @@ impl NotmuchDb {
let hash = {
let mut h = DefaultHasher::new();
k.hash(&mut h);
h.finish()
MailboxHash(h.finish())
};
mailboxes.insert(
hash,
@ -396,7 +396,7 @@ impl NotmuchDb {
let account_hash = {
let mut hasher = DefaultHasher::new();
hasher.write(s.name().as_bytes());
hasher.finish()
AccountHash(hasher.finish())
};
Ok(Box::new(NotmuchDb {
lib,
@ -533,7 +533,7 @@ impl MailBackend for NotmuchDb {
database: Arc<DbConnection>,
index: Arc<RwLock<HashMap<EnvelopeHash, CString>>>,
mailbox_index: Arc<RwLock<HashMap<EnvelopeHash, SmallVec<[MailboxHash; 16]>>>>,
mailboxes: Arc<RwLock<HashMap<u64, NotmuchMailbox>>>,
mailboxes: Arc<RwLock<HashMap<MailboxHash, NotmuchMailbox>>>,
tag_index: Arc<RwLock<BTreeMap<u64, String>>>,
iter: std::vec::IntoIter<CString>,
}

View File

@ -195,6 +195,8 @@ pub struct SmtpExtensionSupport {
pipelining: bool,
#[serde(default = "crate::conf::true_val")]
chunking: bool,
#[serde(default = "crate::conf::true_val")]
_8bitmime: bool,
//Essentially, the PRDR extension to SMTP allows (but does not require) an SMTP server to
//issue multiple responses after a message has been transferred, by mutual consent of the
//client and server. SMTP clients that support the PRDR extension then use the expanded
@ -202,7 +204,7 @@ pub struct SmtpExtensionSupport {
//envelope exchange.
#[serde(default = "crate::conf::true_val")]
prdr: bool,
#[serde(default = "crate::conf::false_val")]
#[serde(default = "crate::conf::true_val")]
binarymime: bool,
//Resources:
//- http://www.postfix.org/SMTPUTF8_README.html
@ -224,7 +226,8 @@ impl Default for SmtpExtensionSupport {
pipelining: true,
chunking: true,
prdr: true,
binarymime: false,
_8bitmime: true,
binarymime: true,
smtputf8: true,
auth: true,
dsn_notify: Some("FAILURE".into()),
@ -561,6 +564,7 @@ impl SmtpConnection {
self.server_conf.extensions.pipelining &= reply.lines.contains(&"PIPELINING");
self.server_conf.extensions.chunking &= reply.lines.contains(&"CHUNKING");
self.server_conf.extensions.prdr &= reply.lines.contains(&"PRDR");
self.server_conf.extensions._8bitmime &= reply.lines.contains(&"8BITMIME");
self.server_conf.extensions.binarymime &= reply.lines.contains(&"BINARYMIME");
self.server_conf.extensions.smtputf8 &= reply.lines.contains(&"SMTPUTF8");
if !reply.lines.contains(&"DSN") {
@ -637,6 +641,11 @@ impl SmtpConnection {
if self.server_conf.extensions.prdr {
current_command.push(b" PRDR");
}
if self.server_conf.extensions.binarymime {
current_command.push(b" BODY=BINARYMIME");
} else if self.server_conf.extensions._8bitmime {
current_command.push(b" BODY=8BITMIME");
}
self.send_command(&current_command).await?;
current_command.clear();
if !self.server_conf.extensions.pipelining {
@ -681,71 +690,81 @@ impl SmtpConnection {
//permitted on either side of the colon following FROM in the MAIL command or TO in the
//RCPT command. The syntax is exactly as given above.
//The third step in the procedure is the DATA command
//(or some alternative specified in a service extension).
//DATA <CRLF>
self.send_command(&[b"DATA"]).await?;
//Client SMTP implementations that employ pipelining MUST check ALL statuses associated
//with each command in a group. For example, if none of the RCPT TO recipient addresses
//were accepted the client must then check the response to the DATA command -- the client
//cannot assume that the DATA command will be rejected just because none of the RCPT TO
//commands worked. If the DATA command was properly 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 _any_error = false;
let mut ignore_mailfrom = true;
for expected_reply_code in pipelining_queue {
let reply = self.read_lines(&mut res, expected_reply_code).await?;
if !ignore_mailfrom {
_all_error &= reply.code.is_err();
_any_error |= reply.code.is_err();
if self.server_conf.extensions.binarymime {
let mail_length = format!("{}", mail.as_bytes().len());
self.send_command(&[b"BDAT", mail_length.as_bytes(), b"LAST"])
.await?;
self.stream
.write_all(mail.as_bytes())
.await
.chain_err_kind(crate::error::ErrorKind::Network)?;
} else {
//The third step in the procedure is the DATA command
//(or some alternative specified in a service extension).
//DATA <CRLF>
self.send_command(&[b"DATA"]).await?;
//Client SMTP implementations that employ pipelining MUST check ALL statuses associated
//with each command in a group. For example, if none of the RCPT TO recipient addresses
//were accepted the client must then check the response to the DATA command -- the client
//cannot assume that the DATA command will be rejected just because none of the RCPT TO
//commands worked. If the DATA command was properly 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 _any_error = false;
let mut ignore_mailfrom = true;
for expected_reply_code in pipelining_queue {
let reply = self.read_lines(&mut res, expected_reply_code).await?;
if !ignore_mailfrom {
_all_error &= reply.code.is_err();
_any_error |= reply.code.is_err();
}
ignore_mailfrom = false;
pipelining_results.push(reply.into());
}
ignore_mailfrom = false;
pipelining_results.push(reply.into());
}
//If accepted, the SMTP server returns a 354 Intermediate reply and considers all
//succeeding lines up to but not including the end of mail data indicator to be the
//message text. When the end of text is successfully received and stored, the
//SMTP-receiver sends a "250 OK" reply.
self.read_lines(&mut res, Some((ReplyCode::_354, &[])))
.await?;
//If accepted, the SMTP server returns a 354 Intermediate reply and considers all
//succeeding lines up to but not including the end of mail data indicator to be the
//message text. When the end of text is successfully received and stored, the
//SMTP-receiver sends a "250 OK" reply.
self.read_lines(&mut res, Some((ReplyCode::_354, &[])))
.await?;
//Before sending a line of mail text, the SMTP client checks the first 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() {
if line.starts_with('.') {
//Before sending a line of mail text, the SMTP client checks the first 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() {
if line.starts_with('.') {
self.stream
.write_all(b".")
.await
.chain_err_kind(crate::error::ErrorKind::Network)?;
}
self.stream
.write_all(b".")
.write_all(line.as_bytes())
.await
.chain_err_kind(crate::error::ErrorKind::Network)?;
self.stream
.write_all(b"\r\n")
.await
.chain_err_kind(crate::error::ErrorKind::Network)?;
}
self.stream
.write_all(line.as_bytes())
.await
.chain_err_kind(crate::error::ErrorKind::Network)?;
self.stream
.write_all(b"\r\n")
.await
.chain_err_kind(crate::error::ErrorKind::Network)?;
}
if !mail.ends_with('\n') {
if !mail.ends_with('\n') {
self.stream
.write_all(b".\r\n")
.await
.chain_err_kind(crate::error::ErrorKind::Network)?;
}
//The mail data are terminated by a line containing only a period, that is, the character
//sequence "<CRLF>.<CRLF>", where the first <CRLF> is 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
.chain_err_kind(crate::error::ErrorKind::Network)?;
}
//The mail data are terminated by a line containing only a period, that is, the character
//sequence "<CRLF>.<CRLF>", where the first <CRLF> is 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
.chain_err_kind(crate::error::ErrorKind::Network)?;
//The end of mail data indicator also confirms the mail transaction and tells the SMTP
//server to now process the stored recipients and mail data. If accepted, the SMTP
//server returns a "250 OK" reply.

View File

@ -151,7 +151,7 @@ impl Composer {
pager.set_show_scrollbar(true);
Composer {
reply_context: None,
account_hash: 0,
account_hash: AccountHash(0),
cursor: Cursor::Headers,
pager,
draft: Draft::default(),

View File

@ -613,6 +613,7 @@ pub struct Listing {
cursor_pos: (usize, MenuEntryCursor),
menu_cursor_pos: (usize, MenuEntryCursor),
menu_content: CellBuffer,
menu_content_dirty: bool,
menu_scrollbar_show_timer: crate::jobs::Timer,
show_menu_scrollbar: ShowMenuScrollbar,
startup_checks_rate: RateLimit,
@ -698,7 +699,8 @@ impl Component for Listing {
match self.component {
ListingComponent::Offline(_) => {}
_ => {
self.component = Offline(OfflineListing::new((account_hash, 0)));
self.component =
Offline(OfflineListing::new((account_hash, MailboxHash(0))));
}
}
}
@ -720,7 +722,8 @@ impl Component for Listing {
match self.component {
ListingComponent::Offline(_) => {}
_ => {
self.component = Offline(OfflineListing::new((account_hash, 0)));
self.component =
Offline(OfflineListing::new((account_hash, MailboxHash(0))));
}
}
}
@ -743,6 +746,7 @@ impl Component for Listing {
*account_settings!(context[account_hash].listing.sidebar_divider);
self.sidebar_divider_theme = conf::value(context, "mail.sidebar_divider");
self.menu_content = CellBuffer::new_with_context(0, 0, None, context);
self.menu_content_dirty = true;
self.set_dirty(true);
}
UIEvent::Timer(n) if *n == self.menu_scrollbar_show_timer.id() => {
@ -750,6 +754,7 @@ impl Component for Listing {
self.show_menu_scrollbar = ShowMenuScrollbar::False;
self.set_dirty(true);
self.menu_content.empty();
self.menu_content_dirty = true;
}
return true;
}
@ -775,41 +780,10 @@ impl Component for Listing {
if self.cursor_pos.0 == account_index {
self.change_account(context);
} else {
let previous_collapsed_mailboxes: BTreeSet<MailboxHash> = self.accounts
[account_index]
.entries
.iter()
.filter_map(|e| {
if e.collapsed {
Some(e.mailbox_hash)
} else {
None
}
})
.collect::<_>();
self.accounts[account_index].entries = context.accounts[&*account_hash]
.list_mailboxes()
.into_iter()
.filter(|mailbox_node| {
context.accounts[&*account_hash][&mailbox_node.hash]
.ref_mailbox
.is_subscribed()
})
.map(|f| MailboxMenuEntry {
depth: f.depth,
indentation: f.indentation,
has_sibling: f.has_sibling,
mailbox_hash: f.hash,
visible: true,
collapsed: if previous_collapsed_mailboxes.is_empty() {
context.accounts[&*account_hash][&f.hash].conf.collapsed
} else {
previous_collapsed_mailboxes.contains(&f.hash)
},
})
.collect::<_>();
self.update_menu_account(account_index, context);
self.set_dirty(true);
self.menu_content.empty();
self.menu_content_dirty = true;
context
.replies
.push_back(UIEvent::StatusEvent(StatusEvent::UpdateStatus(
@ -825,35 +799,8 @@ impl Component for Listing {
.get_index_of(account_hash)
.expect("Invalid account_hash in UIEventMailbox{Delete,Create}");
self.menu_content.empty();
let previous_collapsed_mailboxes: BTreeSet<MailboxHash> = self.accounts
[account_index]
.entries
.iter()
.filter_map(|e| {
if e.collapsed {
Some(e.mailbox_hash)
} else {
None
}
})
.collect::<_>();
self.accounts[account_index].entries = context.accounts[&*account_hash]
.list_mailboxes()
.into_iter()
.filter(|mailbox_node| {
context.accounts[&*account_hash][&mailbox_node.hash]
.ref_mailbox
.is_subscribed()
})
.map(|f| MailboxMenuEntry {
depth: f.depth,
indentation: f.indentation,
has_sibling: f.has_sibling,
mailbox_hash: f.hash,
visible: true,
collapsed: previous_collapsed_mailboxes.contains(&f.hash),
})
.collect::<_>();
self.menu_content_dirty = true;
self.update_menu_account(account_index, context);
let mut fallback = 0;
if let MenuEntryCursor::Mailbox(ref mut cur) = self.cursor_pos.1 {
*cur = std::cmp::min(
@ -900,6 +847,7 @@ impl Component for Listing {
self.component
.set_coordinates((account_hash, *mailbox_hash));
self.menu_content.empty();
self.menu_content_dirty = true;
self.set_dirty(true);
}
return true;
@ -1378,6 +1326,7 @@ impl Component for Listing {
target.collapsed = !(target.collapsed);
self.dirty = true;
self.menu_content.empty();
self.menu_content_dirty = true;
context
.replies
.push_back(UIEvent::StatusEvent(StatusEvent::ScrollUpdate(
@ -1520,6 +1469,7 @@ impl Component for Listing {
self.show_menu_scrollbar = ShowMenuScrollbar::True;
}
self.menu_content.empty();
self.menu_content_dirty = true;
self.set_dirty(true);
return true;
}
@ -1581,6 +1531,7 @@ impl Component for Listing {
self.show_menu_scrollbar = ShowMenuScrollbar::True;
}
self.menu_content.empty();
self.menu_content_dirty = true;
return true;
}
UIEvent::Input(ref k)
@ -1634,6 +1585,7 @@ impl Component for Listing {
self.show_menu_scrollbar = ShowMenuScrollbar::True;
}
self.menu_content.empty();
self.menu_content_dirty = true;
self.set_dirty(true);
return true;
@ -1660,6 +1612,7 @@ impl Component for Listing {
self.dirty = true;
/* clear menu to force redraw */
self.menu_content.empty();
self.menu_content_dirty = true;
context
.replies
.push_back(UIEvent::StatusEvent(StatusEvent::UpdateStatus(
@ -1810,13 +1763,14 @@ impl Listing {
.collect();
let first_account_hash = account_entries[0].hash;
let mut ret = Listing {
component: Offline(OfflineListing::new((first_account_hash, 0))),
component: Offline(OfflineListing::new((first_account_hash, MailboxHash(0)))),
accounts: account_entries,
status: None,
dirty: true,
cursor_pos: (0, MenuEntryCursor::Mailbox(0)),
menu_cursor_pos: (0, MenuEntryCursor::Mailbox(0)),
menu_content: CellBuffer::new_with_context(0, 0, None, context),
menu_content_dirty: true,
menu_scrollbar_show_timer: context.job_executor.clone().create_timer(
std::time::Duration::from_secs(0),
std::time::Duration::from_millis(1200),
@ -1841,6 +1795,11 @@ impl Listing {
}
fn draw_menu(&mut self, grid: &mut CellBuffer, area: Area, context: &mut Context) {
if self.menu_content_dirty {
for account_index in 0..self.accounts.len() {
self.update_menu_account(account_index, context);
}
}
clear_area(grid, area, self.theme_default);
let total_height: usize = 3 * (self.accounts.len())
+ self
@ -2283,9 +2242,8 @@ impl Listing {
}
}
fn change_account(&mut self, context: &mut Context) {
let account_hash = context.accounts[self.cursor_pos.0].hash();
let previous_collapsed_mailboxes: BTreeSet<MailboxHash> = self.accounts[self.cursor_pos.0]
fn update_menu_account(&mut self, account_index: usize, context: &mut Context) {
let previous_collapsed_mailboxes: BTreeSet<MailboxHash> = self.accounts[account_index]
.entries
.iter()
.filter_map(|e| {
@ -2296,11 +2254,11 @@ impl Listing {
}
})
.collect::<_>();
self.accounts[self.cursor_pos.0].entries = context.accounts[self.cursor_pos.0]
self.accounts[account_index].entries = context.accounts[account_index]
.list_mailboxes()
.into_iter()
.filter(|mailbox_node| {
context.accounts[self.cursor_pos.0][&mailbox_node.hash]
context.accounts[account_index][&mailbox_node.hash]
.ref_mailbox
.is_subscribed()
})
@ -2311,12 +2269,17 @@ impl Listing {
mailbox_hash: f.hash,
visible: true,
collapsed: if previous_collapsed_mailboxes.is_empty() {
context.accounts[self.cursor_pos.0][&f.hash].conf.collapsed
context.accounts[account_index][&f.hash].conf.collapsed
} else {
previous_collapsed_mailboxes.contains(&f.hash)
},
})
.collect::<_>();
}
fn change_account(&mut self, context: &mut Context) {
self.update_menu_account(self.cursor_pos.0, context);
let account_hash = self.accounts[self.cursor_pos.0].hash;
match self.cursor_pos.1 {
MenuEntryCursor::Mailbox(idx) => {
/* Account might have no mailboxes yet if it's offline */
@ -2334,7 +2297,7 @@ impl Listing {
self.component.set_style(*index_style);
} else {
/* Set to dummy */
self.component = Offline(OfflineListing::new((account_hash, 0)));
self.component = Offline(OfflineListing::new((account_hash, MailboxHash(0))));
}
self.status = None;
context

View File

@ -849,7 +849,7 @@ impl CompactListing {
pub const DESCRIPTION: &'static str = "compact listing";
pub fn new(coordinates: (AccountHash, MailboxHash)) -> Box<Self> {
Box::new(CompactListing {
cursor_pos: (coordinates.0, 1, 0),
cursor_pos: (coordinates.0, MailboxHash(0), 0),
new_cursor_pos: (coordinates.0, coordinates.1, 0),
length: 0,
sort: (Default::default(), Default::default()),

View File

@ -570,7 +570,7 @@ impl ConversationsListing {
pub fn new(coordinates: (AccountHash, MailboxHash)) -> Box<Self> {
Box::new(ConversationsListing {
cursor_pos: (coordinates.0, 1, 0),
cursor_pos: (coordinates.0, MailboxHash(0), 0),
new_cursor_pos: (coordinates.0, coordinates.1, 0),
length: 0,
sort: (Default::default(), Default::default()),

View File

@ -660,7 +660,7 @@ impl PlainListing {
const DESCRIPTION: &'static str = "plain listing";
pub fn new(coordinates: (AccountHash, MailboxHash)) -> Box<Self> {
Box::new(PlainListing {
cursor_pos: (0, 1, 0),
cursor_pos: (AccountHash(0), MailboxHash(0), 0),
new_cursor_pos: (coordinates.0, coordinates.1, 0),
length: 0,
sort: (Default::default(), Default::default()),

View File

@ -759,7 +759,7 @@ impl fmt::Display for ThreadListing {
impl ThreadListing {
pub fn new(coordinates: (AccountHash, MailboxHash)) -> Box<Self> {
Box::new(ThreadListing {
cursor_pos: (coordinates.0, 0, 0),
cursor_pos: (coordinates.0, MailboxHash(0), 0),
new_cursor_pos: (coordinates.0, coordinates.1, 0),
length: 0,
sort: (Default::default(), Default::default()),

View File

@ -1652,7 +1652,7 @@ impl Component for MailView {
}
fn process_event(&mut self, mut event: &mut UIEvent, context: &mut Context) -> bool {
if self.coordinates.0 == 0 || self.coordinates.1 == 0 {
if self.coordinates.0 .0 == 0 || self.coordinates.1 .0 == 0 {
return false;
}
let shortcuts = self.get_shortcuts(context);

View File

@ -1112,7 +1112,7 @@ impl Account {
}
pub fn load(&mut self, mailbox_hash: MailboxHash) -> result::Result<(), usize> {
if mailbox_hash == 0 {
if mailbox_hash.0 == 0 {
return Err(0);
}
match self.mailbox_entries[&mailbox_hash].status {

View File

@ -314,7 +314,12 @@ fn run_app(opt: Opt) -> Result<()> {
sender,
receiver.clone(),
)?;
state.register_component(Box::new(EnvelopeView::new(wrapper, None, None, 0)));
state.register_component(Box::new(EnvelopeView::new(
wrapper,
None,
None,
AccountHash(0),
)));
} else {
state = State::new(None, sender, receiver.clone())?;
#[cfg(feature = "svgscreenshot")]

View File

@ -258,7 +258,7 @@ impl State {
use std::hash::Hasher;
let mut hasher = DefaultHasher::new();
hasher.write(n.as_bytes());
hasher.finish()
AccountHash(hasher.finish())
};
Account::new(
account_hash,