refactor: Introduce imap-codec.
parent
6c6d9f4b4e
commit
330887c4f5
|
@ -2,6 +2,15 @@
|
|||
# It is not intended for manual editing.
|
||||
version = 3
|
||||
|
||||
[[package]]
|
||||
name = "abnf-core"
|
||||
version = "0.5.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "c44e09c43ae1c368fb91a03a566472d0087c26cf7e1b9e8e289c14ede681dd7d"
|
||||
dependencies = [
|
||||
"nom",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "adler"
|
||||
version = "1.0.2"
|
||||
|
@ -185,6 +194,12 @@ version = "0.13.0"
|
|||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "904dfeac50f3cdaba28fc6f57fdcddb75f49ed61346676a78c4ffe55877802fd"
|
||||
|
||||
[[package]]
|
||||
name = "base64"
|
||||
version = "0.21.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "604178f6c5c21f02dc555784810edfb88d34ac2c73b2eae109655649ee73ce3d"
|
||||
|
||||
[[package]]
|
||||
name = "bincode"
|
||||
version = "1.3.3"
|
||||
|
@ -869,6 +884,29 @@ dependencies = [
|
|||
"unicode-normalization",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "imap-codec"
|
||||
version = "1.0.0-dev"
|
||||
source = "git+https://github.com/duesee/imap-codec?rev=95acd5186dacc6c6892603954e3f28d18a9f1193#95acd5186dacc6c6892603954e3f28d18a9f1193"
|
||||
dependencies = [
|
||||
"abnf-core",
|
||||
"base64 0.21.2",
|
||||
"chrono",
|
||||
"imap-types",
|
||||
"nom",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "imap-types"
|
||||
version = "1.0.0-dev"
|
||||
source = "git+https://github.com/duesee/imap-codec?rev=95acd5186dacc6c6892603954e3f28d18a9f1193#95acd5186dacc6c6892603954e3f28d18a9f1193"
|
||||
dependencies = [
|
||||
"base64 0.21.2",
|
||||
"chrono",
|
||||
"subtle",
|
||||
"thiserror",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "indexmap"
|
||||
version = "1.9.1"
|
||||
|
@ -1087,7 +1125,7 @@ version = "0.6.1"
|
|||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "24d0411d6d3cf6baacae37461dc5b0a32b9c68ae99ddef61bcd88174b8da890a"
|
||||
dependencies = [
|
||||
"base64",
|
||||
"base64 0.13.0",
|
||||
"either",
|
||||
"log",
|
||||
"nom",
|
||||
|
@ -1167,7 +1205,7 @@ name = "melib"
|
|||
version = "0.7.2"
|
||||
dependencies = [
|
||||
"async-stream",
|
||||
"base64",
|
||||
"base64 0.13.0",
|
||||
"bincode",
|
||||
"bitflags",
|
||||
"data-encoding",
|
||||
|
@ -1175,6 +1213,7 @@ dependencies = [
|
|||
"encoding_rs",
|
||||
"flate2",
|
||||
"futures",
|
||||
"imap-codec",
|
||||
"indexmap",
|
||||
"isahc",
|
||||
"libc",
|
||||
|
@ -1712,7 +1751,7 @@ version = "1.0.1"
|
|||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "0864aeff53f8c05aa08d86e5ef839d3dfcf07aeba2db32f12db0ef716e87bd55"
|
||||
dependencies = [
|
||||
"base64",
|
||||
"base64 0.13.0",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
|
@ -1959,6 +1998,12 @@ dependencies = [
|
|||
"syn",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "subtle"
|
||||
version = "2.5.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "81cdd64d312baedb58e21336b31bc043b77e01cc99033ce76ef539f78e965ebc"
|
||||
|
||||
[[package]]
|
||||
name = "svg"
|
||||
version = "0.10.0"
|
||||
|
|
|
@ -53,6 +53,21 @@ uuid = { version = "^1", features = ["serde", "v4", "v5"] }
|
|||
xdg = "2.1.0"
|
||||
xdg-utils = "^0.4.0"
|
||||
|
||||
[dependencies.imap-codec]
|
||||
# TODO(#222): Replace `git` and `rev` with `version`.
|
||||
git = "https://github.com/duesee/imap-codec"
|
||||
rev = "95acd5186dacc6c6892603954e3f28d18a9f1193"
|
||||
features = [
|
||||
"ext_condstore_qresync",
|
||||
"ext_enable",
|
||||
"ext_idle",
|
||||
"ext_literal",
|
||||
"ext_move",
|
||||
"ext_sasl_ir",
|
||||
"ext_unselect"
|
||||
]
|
||||
optional = true
|
||||
|
||||
[dev-dependencies]
|
||||
mailin-embedded = { version = "0.7", features = ["rtls"] }
|
||||
stderrlog = "^0.5"
|
||||
|
@ -61,11 +76,11 @@ stderrlog = "^0.5"
|
|||
default = ["unicode_algorithms", "imap_backend", "maildir_backend", "mbox_backend", "vcard", "sqlite3", "smtp", "deflate_compression"]
|
||||
|
||||
debug-tracing = []
|
||||
deflate_compression = ["flate2", ]
|
||||
deflate_compression = ["flate2", "imap-codec/ext_compress"]
|
||||
gpgme = []
|
||||
http = ["isahc"]
|
||||
http-static = ["isahc", "isahc/static-curl"]
|
||||
imap_backend = ["tls"]
|
||||
imap_backend = ["imap-codec", "tls"]
|
||||
jmap_backend = ["http", "serde_json"]
|
||||
maildir_backend = ["notify"]
|
||||
mbox_backend = ["notify"]
|
||||
|
|
|
@ -49,6 +49,12 @@ use std::{
|
|||
};
|
||||
|
||||
use futures::{lock::Mutex as FutureMutex, stream::Stream};
|
||||
use imap_codec::{
|
||||
command::CommandBody,
|
||||
core::Literal,
|
||||
flag::{Flag as ImapCodecFlag, StoreResponse, StoreType},
|
||||
sequence::{SequenceSet, ONE},
|
||||
};
|
||||
|
||||
use crate::{
|
||||
backends::{
|
||||
|
@ -584,32 +590,18 @@ impl MailBackend for ImapType {
|
|||
.unwrap()
|
||||
.iter()
|
||||
.any(|cap| cap.eq_ignore_ascii_case(b"LITERAL+"));
|
||||
if has_literal_plus {
|
||||
conn.send_command(
|
||||
format!(
|
||||
"APPEND \"{}\" ({}) {{{}+}}",
|
||||
&path,
|
||||
flags_to_imap_list!(flags),
|
||||
bytes.len()
|
||||
)
|
||||
.as_bytes(),
|
||||
)
|
||||
.await?;
|
||||
let data = if has_literal_plus {
|
||||
Literal::try_from(bytes)?.into_non_sync()
|
||||
} else {
|
||||
conn.send_command(
|
||||
format!(
|
||||
"APPEND \"{}\" ({}) {{{}}}",
|
||||
&path,
|
||||
flags_to_imap_list!(flags),
|
||||
bytes.len()
|
||||
)
|
||||
.as_bytes(),
|
||||
)
|
||||
.await?;
|
||||
// wait for "+ Ready for literal data" reply
|
||||
conn.wait_for_continuation_request().await?;
|
||||
}
|
||||
conn.send_literal(&bytes).await?;
|
||||
Literal::try_from(bytes)?
|
||||
};
|
||||
conn.send_command(CommandBody::append(
|
||||
path,
|
||||
flags.derive_imap_codec_flags(),
|
||||
None,
|
||||
data,
|
||||
)?)
|
||||
.await?;
|
||||
conn.read_response(&mut response, RequiredResponses::empty())
|
||||
.await?;
|
||||
Ok(())
|
||||
|
@ -658,36 +650,24 @@ impl MailBackend for ImapType {
|
|||
conn.select_mailbox(source_mailbox_hash, &mut response, false)
|
||||
.await?;
|
||||
if has_move {
|
||||
let command = {
|
||||
let mut cmd = format!("UID MOVE {}", uids[0]);
|
||||
for uid in uids.iter().skip(1) {
|
||||
cmd = format!("{},{}", cmd, uid);
|
||||
}
|
||||
format!("{} \"{}\"", cmd, dest_path)
|
||||
};
|
||||
conn.send_command(command.as_bytes()).await?;
|
||||
conn.send_command(CommandBody::r#move(uids.as_slice(), dest_path, true)?)
|
||||
.await?;
|
||||
conn.read_response(&mut response, RequiredResponses::empty())
|
||||
.await?;
|
||||
} else {
|
||||
let command = {
|
||||
let mut cmd = format!("UID COPY {}", uids[0]);
|
||||
for uid in uids.iter().skip(1) {
|
||||
cmd = format!("{},{}", cmd, uid);
|
||||
}
|
||||
format!("{} \"{}\"", cmd, dest_path)
|
||||
};
|
||||
conn.send_command(command.as_bytes()).await?;
|
||||
conn.send_command(CommandBody::copy(uids.as_slice(), dest_path, true)?)
|
||||
.await?;
|
||||
conn.read_response(&mut response, RequiredResponses::empty())
|
||||
.await?;
|
||||
if move_ {
|
||||
let command = {
|
||||
let mut cmd = format!("UID STORE {}", uids[0]);
|
||||
for uid in uids.iter().skip(1) {
|
||||
cmd = format!("{},{}", cmd, uid);
|
||||
}
|
||||
format!("{} +FLAGS (\\Deleted)", cmd)
|
||||
};
|
||||
conn.send_command(command.as_bytes()).await?;
|
||||
conn.send_command(CommandBody::store(
|
||||
uids.as_slice(),
|
||||
StoreType::Add,
|
||||
StoreResponse::Answer,
|
||||
vec![ImapCodecFlag::Deleted],
|
||||
true,
|
||||
)?)
|
||||
.await?;
|
||||
conn.read_response(&mut response, RequiredResponses::empty())
|
||||
.await?;
|
||||
}
|
||||
|
@ -782,7 +762,7 @@ impl MailBackend for ImapType {
|
|||
cmd.push(')');
|
||||
cmd
|
||||
};
|
||||
conn.send_command(command.as_bytes()).await?;
|
||||
conn.send_command_raw(command.as_bytes()).await?;
|
||||
conn.read_response(&mut response, RequiredResponses::empty())
|
||||
.await?;
|
||||
if set_seen {
|
||||
|
@ -847,7 +827,7 @@ impl MailBackend for ImapType {
|
|||
cmd.push(')');
|
||||
cmd
|
||||
};
|
||||
conn.send_command(command.as_bytes()).await?;
|
||||
conn.send_command_raw(command.as_bytes()).await?;
|
||||
conn.read_response(&mut response, RequiredResponses::empty())
|
||||
.await?;
|
||||
if set_unseen {
|
||||
|
@ -878,7 +858,7 @@ impl MailBackend for ImapType {
|
|||
flag_future.await?;
|
||||
let mut response = Vec::with_capacity(8 * 1024);
|
||||
let mut conn = connection.lock().await;
|
||||
conn.send_command("EXPUNGE".as_bytes()).await?;
|
||||
conn.send_command(CommandBody::Expunge).await?;
|
||||
conn.read_response(&mut response, RequiredResponses::empty())
|
||||
.await?;
|
||||
debug!("EXPUNGE response: {}", &String::from_utf8_lossy(&response));
|
||||
|
@ -951,13 +931,13 @@ impl MailBackend for ImapType {
|
|||
conn_lck.unselect().await?;
|
||||
|
||||
conn_lck
|
||||
.send_command(format!("CREATE \"{}\"", path,).as_bytes())
|
||||
.send_command(CommandBody::create(path.as_str())?)
|
||||
.await?;
|
||||
conn_lck
|
||||
.read_response(&mut response, RequiredResponses::empty())
|
||||
.await?;
|
||||
conn_lck
|
||||
.send_command(format!("SUBSCRIBE \"{}\"", path,).as_bytes())
|
||||
.send_command(CommandBody::subscribe(path.as_str())?)
|
||||
.await?;
|
||||
conn_lck
|
||||
.read_response(&mut response, RequiredResponses::empty())
|
||||
|
@ -1013,7 +993,7 @@ impl MailBackend for ImapType {
|
|||
conn_lck.unselect().await?;
|
||||
if is_subscribed {
|
||||
conn_lck
|
||||
.send_command(format!("UNSUBSCRIBE \"{}\"", &imap_path).as_bytes())
|
||||
.send_command(CommandBody::unsubscribe(imap_path.as_str())?)
|
||||
.await?;
|
||||
conn_lck
|
||||
.read_response(&mut response, RequiredResponses::empty())
|
||||
|
@ -1021,7 +1001,7 @@ impl MailBackend for ImapType {
|
|||
}
|
||||
|
||||
conn_lck
|
||||
.send_command(debug!(format!("DELETE \"{}\"", &imap_path,)).as_bytes())
|
||||
.send_command(debug!(CommandBody::delete(imap_path.as_str())?))
|
||||
.await?;
|
||||
conn_lck
|
||||
.read_response(&mut response, RequiredResponses::empty())
|
||||
|
@ -1050,23 +1030,24 @@ impl MailBackend for ImapType {
|
|||
let uid_store = self.uid_store.clone();
|
||||
let connection = self.connection.clone();
|
||||
Ok(Box::pin(async move {
|
||||
let command: String;
|
||||
{
|
||||
let imap_path = {
|
||||
let mailboxes = uid_store.mailboxes.lock().await;
|
||||
if mailboxes[&mailbox_hash].is_subscribed() == new_val {
|
||||
return Ok(());
|
||||
}
|
||||
command = format!("SUBSCRIBE \"{}\"", mailboxes[&mailbox_hash].imap_path());
|
||||
}
|
||||
mailboxes[&mailbox_hash].imap_path().to_string()
|
||||
};
|
||||
|
||||
let mut response = Vec::with_capacity(8 * 1024);
|
||||
{
|
||||
let mut conn_lck = connection.lock().await;
|
||||
if new_val {
|
||||
conn_lck.send_command(command.as_bytes()).await?;
|
||||
conn_lck
|
||||
.send_command(CommandBody::subscribe(imap_path.as_str())?)
|
||||
.await?;
|
||||
} else {
|
||||
conn_lck
|
||||
.send_command(format!("UN{}", command).as_bytes())
|
||||
.send_command(CommandBody::unsubscribe(imap_path.as_str())?)
|
||||
.await?;
|
||||
}
|
||||
conn_lck
|
||||
|
@ -1125,7 +1106,9 @@ impl MailBackend for ImapType {
|
|||
}
|
||||
{
|
||||
let mut conn_lck = connection.lock().await;
|
||||
conn_lck.send_command(debug!(command).as_bytes()).await?;
|
||||
conn_lck
|
||||
.send_command_raw(debug!(command).as_bytes())
|
||||
.await?;
|
||||
conn_lck
|
||||
.read_response(&mut response, RequiredResponses::empty())
|
||||
.await?;
|
||||
|
@ -1191,8 +1174,10 @@ impl MailBackend for ImapType {
|
|||
let mut conn = connection.lock().await;
|
||||
conn.examine_mailbox(mailbox_hash, &mut response, false)
|
||||
.await?;
|
||||
conn.send_command(format!("UID SEARCH CHARSET UTF-8 {}", query_str.trim()).as_bytes())
|
||||
.await?;
|
||||
conn.send_command_raw(
|
||||
format!("UID SEARCH CHARSET UTF-8 {}", query_str.trim()).as_bytes(),
|
||||
)
|
||||
.await?;
|
||||
conn.read_response(&mut response, RequiredResponses::SEARCH)
|
||||
.await?;
|
||||
debug!(
|
||||
|
@ -1310,7 +1295,7 @@ impl ImapType {
|
|||
let mut res = Vec::with_capacity(8 * 1024);
|
||||
futures::executor::block_on(timeout(
|
||||
self.server_conf.timeout,
|
||||
conn.send_command(b"NOOP"),
|
||||
conn.send_command(CommandBody::Noop),
|
||||
))
|
||||
.unwrap()
|
||||
.unwrap();
|
||||
|
@ -1330,7 +1315,7 @@ impl ImapType {
|
|||
Ok(_) => {
|
||||
futures::executor::block_on(timeout(
|
||||
self.server_conf.timeout,
|
||||
conn.send_command(input.as_bytes()),
|
||||
conn.send_command_raw(input.as_bytes()),
|
||||
))
|
||||
.unwrap()
|
||||
.unwrap();
|
||||
|
@ -1373,7 +1358,7 @@ impl ImapType {
|
|||
.iter()
|
||||
.any(|cap| cap.eq_ignore_ascii_case(b"LIST-STATUS"));
|
||||
if has_list_status {
|
||||
conn.send_command(b"LIST \"\" \"*\" RETURN (STATUS (MESSAGES UNSEEN))")
|
||||
conn.send_command_raw(b"LIST \"\" \"*\" RETURN (STATUS (MESSAGES UNSEEN))")
|
||||
.await?;
|
||||
conn.read_response(
|
||||
&mut res,
|
||||
|
@ -1381,7 +1366,7 @@ impl ImapType {
|
|||
)
|
||||
.await?;
|
||||
} else {
|
||||
conn.send_command(b"LIST \"\" \"*\"").await?;
|
||||
conn.send_command(CommandBody::list("", "*")?).await?;
|
||||
conn.read_response(&mut res, RequiredResponses::LIST_REQUIRED)
|
||||
.await?;
|
||||
}
|
||||
|
@ -1433,7 +1418,7 @@ impl ImapType {
|
|||
}
|
||||
}
|
||||
mailboxes.retain(|_, v| !v.hash.is_null());
|
||||
conn.send_command(b"LSUB \"\" \"*\"").await?;
|
||||
conn.send_command(CommandBody::lsub("", "*")?).await?;
|
||||
conn.read_response(&mut res, RequiredResponses::LSUB_REQUIRED)
|
||||
.await?;
|
||||
debug!("LSUB reply: {}", String::from_utf8_lossy(&res));
|
||||
|
@ -1721,20 +1706,20 @@ async fn fetch_hlpr(state: &mut FetchState) -> Result<Vec<Envelope>> {
|
|||
.await?;
|
||||
if max_uid_left > 0 {
|
||||
debug!("{} max_uid_left= {}", mailbox_hash, max_uid_left);
|
||||
let command = if max_uid_left == 1 {
|
||||
"UID FETCH 1 (UID FLAGS ENVELOPE BODY.PEEK[HEADER.FIELDS (REFERENCES)] \
|
||||
BODYSTRUCTURE)"
|
||||
.to_string()
|
||||
let sequence_set = if max_uid_left == 1 {
|
||||
SequenceSet::from(ONE)
|
||||
} else {
|
||||
format!(
|
||||
"UID FETCH {}:{} (UID FLAGS ENVELOPE BODY.PEEK[HEADER.FIELDS \
|
||||
(REFERENCES)] BODYSTRUCTURE)",
|
||||
std::cmp::max(max_uid_left.saturating_sub(chunk_size), 1),
|
||||
max_uid_left
|
||||
)
|
||||
let min = std::cmp::max(max_uid_left.saturating_sub(chunk_size), 1);
|
||||
let max = max_uid_left;
|
||||
|
||||
SequenceSet::try_from(min..=max)?
|
||||
};
|
||||
debug!("sending {:?}", &command);
|
||||
conn.send_command(command.as_bytes()).await?;
|
||||
conn.send_command(CommandBody::Fetch {
|
||||
sequence_set,
|
||||
attributes: common_attributes(),
|
||||
uid: true,
|
||||
})
|
||||
.await?;
|
||||
conn.read_response(&mut response, RequiredResponses::FETCH_REQUIRED)
|
||||
.await
|
||||
.chain_err_summary(|| {
|
||||
|
|
|
@ -19,6 +19,13 @@
|
|||
* along with meli. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
use imap_codec::{
|
||||
fetch::{FetchAttribute, MacroOrFetchAttributes},
|
||||
search::SearchKey,
|
||||
sequence::SequenceSet,
|
||||
status::StatusAttribute,
|
||||
};
|
||||
|
||||
use super::*;
|
||||
|
||||
impl ImapConnection {
|
||||
|
@ -128,14 +135,11 @@ impl ImapConnection {
|
|||
cache_handle.update_mailbox(mailbox_hash, &select_response)?;
|
||||
|
||||
// 2. tag1 UID FETCH <lastseenuid+1>:* <descriptors>
|
||||
self.send_command(
|
||||
format!(
|
||||
"UID FETCH {}:* (UID FLAGS ENVELOPE BODY.PEEK[HEADER.FIELDS (REFERENCES)] \
|
||||
BODYSTRUCTURE)",
|
||||
max_uid + 1
|
||||
)
|
||||
.as_bytes(),
|
||||
)
|
||||
self.send_command(CommandBody::fetch(
|
||||
max_uid + 1..,
|
||||
common_attributes(),
|
||||
true,
|
||||
)?)
|
||||
.await?;
|
||||
self.read_response(&mut response, RequiredResponses::FETCH_REQUIRED)
|
||||
.await?;
|
||||
|
@ -229,12 +233,17 @@ impl ImapConnection {
|
|||
}
|
||||
mailbox_exists.lock().unwrap().insert_set(payload_hash_set);
|
||||
// 3. tag2 UID FETCH 1:<lastseenuid> FLAGS
|
||||
if max_uid == 0 {
|
||||
self.send_command("UID FETCH 1:* FLAGS".as_bytes()).await?;
|
||||
let sequence_set = if max_uid == 0 {
|
||||
SequenceSet::from(..)
|
||||
} else {
|
||||
self.send_command(format!("UID FETCH 1:{} FLAGS", max_uid).as_bytes())
|
||||
.await?;
|
||||
}
|
||||
SequenceSet::try_from(..=max_uid)?
|
||||
};
|
||||
self.send_command(CommandBody::Fetch {
|
||||
sequence_set,
|
||||
attributes: MacroOrFetchAttributes::FetchAttributes(vec![FetchAttribute::Flags]),
|
||||
uid: true,
|
||||
})
|
||||
.await?;
|
||||
self.read_response(&mut response, RequiredResponses::FETCH_REQUIRED)
|
||||
.await?;
|
||||
//1) update cached flags for old messages;
|
||||
|
@ -414,7 +423,7 @@ impl ImapConnection {
|
|||
// "SEARCH MODSEQ <cached-value>".
|
||||
|
||||
// 2. tag1 UID FETCH <lastseenuid+1>:* <descriptors>
|
||||
self.send_command(
|
||||
self.send_command_raw(
|
||||
format!(
|
||||
"UID FETCH {}:* (UID FLAGS ENVELOPE BODY.PEEK[HEADER.FIELDS (REFERENCES)] \
|
||||
BODYSTRUCTURE) (CHANGEDSINCE {})",
|
||||
|
@ -511,7 +520,7 @@ impl ImapConnection {
|
|||
mailbox_exists.lock().unwrap().insert_set(payload_hash_set);
|
||||
// 3. tag2 UID FETCH 1:<lastseenuid> FLAGS
|
||||
if cached_max_uid == 0 {
|
||||
self.send_command(
|
||||
self.send_command_raw(
|
||||
format!(
|
||||
"UID FETCH 1:* FLAGS (CHANGEDSINCE {})",
|
||||
cached_highestmodseq
|
||||
|
@ -520,7 +529,7 @@ impl ImapConnection {
|
|||
)
|
||||
.await?;
|
||||
} else {
|
||||
self.send_command(
|
||||
self.send_command_raw(
|
||||
format!(
|
||||
"UID FETCH 1:{} FLAGS (CHANGEDSINCE {})",
|
||||
cached_max_uid, cached_highestmodseq
|
||||
|
@ -575,7 +584,8 @@ impl ImapConnection {
|
|||
let mut valid_envs = BTreeSet::default();
|
||||
// This should be UID SEARCH 1:<maxuid> but it's difficult to compare to cached
|
||||
// UIDs at the point of calling this function
|
||||
self.send_command(b"UID SEARCH ALL").await?;
|
||||
self.send_command(CommandBody::search(None, SearchKey::All, true))
|
||||
.await?;
|
||||
self.read_response(&mut response, RequiredResponses::SEARCH)
|
||||
.await?;
|
||||
//1) update cached flags for old messages;
|
||||
|
@ -683,8 +693,11 @@ impl ImapConnection {
|
|||
.await?;
|
||||
if select_response.uidnext == 0 {
|
||||
/* UIDNEXT shouldn't be 0, since exists != 0 at this point */
|
||||
self.send_command(format!("STATUS \"{}\" (UIDNEXT)", mailbox_path).as_bytes())
|
||||
.await?;
|
||||
self.send_command(CommandBody::status(
|
||||
mailbox_path,
|
||||
[StatusAttribute::UidNext].as_slice(),
|
||||
)?)
|
||||
.await?;
|
||||
self.read_response(&mut response, RequiredResponses::STATUS)
|
||||
.await?;
|
||||
let (_, status) = protocol_parser::status_response(response.as_slice())?;
|
||||
|
|
|
@ -39,6 +39,20 @@ use std::{
|
|||
};
|
||||
|
||||
use futures::io::{AsyncReadExt, AsyncWriteExt};
|
||||
#[cfg(feature = "deflate_compression")]
|
||||
use imap_codec::extensions::compress::CompressionAlgorithm;
|
||||
use imap_codec::{
|
||||
auth::{AuthMechanism, AuthMechanismOther},
|
||||
codec::{Encode, Fragment},
|
||||
command::{Command, CommandBody},
|
||||
core::{AString, Atom, NonEmptyVec, Tag},
|
||||
extensions::enable::CapabilityEnable,
|
||||
mailbox::Mailbox,
|
||||
search::SearchKey,
|
||||
secret::Secret,
|
||||
sequence::SequenceSet,
|
||||
status::StatusAttribute,
|
||||
};
|
||||
use native_tls::TlsConnector;
|
||||
pub use smol::Async as AsyncWrapper;
|
||||
|
||||
|
@ -272,21 +286,15 @@ impl ImapStream {
|
|||
timeout: server_conf.timeout,
|
||||
};
|
||||
if let ImapProtocol::ManageSieve = server_conf.protocol {
|
||||
use data_encoding::BASE64;
|
||||
ret.read_response(&mut res).await?;
|
||||
ret.send_command(
|
||||
format!(
|
||||
"AUTHENTICATE \"PLAIN\" \"{}\"",
|
||||
BASE64.encode(
|
||||
format!(
|
||||
"\0{}\0{}",
|
||||
&server_conf.server_username, &server_conf.server_password
|
||||
)
|
||||
.as_bytes()
|
||||
)
|
||||
)
|
||||
.as_bytes(),
|
||||
)
|
||||
let credentials = format!(
|
||||
"\0{}\0{}",
|
||||
&server_conf.server_username, &server_conf.server_password
|
||||
);
|
||||
ret.send_command(CommandBody::authenticate(
|
||||
AuthMechanism::Plain,
|
||||
Some(credentials.as_bytes()),
|
||||
))
|
||||
.await?;
|
||||
ret.read_response(&mut res).await?;
|
||||
return Ok((Default::default(), ret));
|
||||
|
@ -298,7 +306,7 @@ impl ImapStream {
|
|||
message: "Negotiating server capabilities.".into(),
|
||||
},
|
||||
);
|
||||
ret.send_command(b"CAPABILITY").await?;
|
||||
ret.send_command(CommandBody::Capability).await?;
|
||||
ret.read_response(&mut res).await?;
|
||||
let capabilities: std::result::Result<Vec<&[u8]>, _> = res
|
||||
.split_rn()
|
||||
|
@ -364,30 +372,26 @@ impl ImapStream {
|
|||
.join(" ")
|
||||
)));
|
||||
}
|
||||
ret.send_command(
|
||||
format!("AUTHENTICATE XOAUTH2 {}", &server_conf.server_password).as_bytes(),
|
||||
)
|
||||
let xoauth2 = base64::decode(&server_conf.server_password)
|
||||
.map_err(|_| Error::new("Bad XOAUTH2 in config"))?;
|
||||
// TODO(#222): Improve this as soon as imap-codec supports XOAUTH2.
|
||||
ret.send_command(CommandBody::authenticate(
|
||||
AuthMechanism::Other(
|
||||
AuthMechanismOther::try_from(Atom::unchecked("XOAUTH2")).unwrap(),
|
||||
),
|
||||
Some(&xoauth2),
|
||||
))
|
||||
.await?;
|
||||
}
|
||||
_ => {
|
||||
ret.send_command(
|
||||
format!(
|
||||
r#"LOGIN "{}" {{{}}}"#,
|
||||
&server_conf
|
||||
.server_username
|
||||
.replace('\\', r#"\\"#)
|
||||
.replace('"', r#"\""#)
|
||||
.replace('{', r#"\{"#)
|
||||
.replace('}', r#"\}"#),
|
||||
&server_conf.server_password.as_bytes().len()
|
||||
)
|
||||
.as_bytes(),
|
||||
)
|
||||
let username = AString::try_from(server_conf.server_username.as_str())?;
|
||||
let password = AString::try_from(server_conf.server_password.as_str())?;
|
||||
|
||||
ret.send_command(CommandBody::Login {
|
||||
username,
|
||||
password: Secret::new(password),
|
||||
})
|
||||
.await?;
|
||||
// wait for "+ Ready for literal data" reply
|
||||
ret.wait_for_continuation_request().await?;
|
||||
ret.send_literal(server_conf.server_password.as_bytes())
|
||||
.await?;
|
||||
}
|
||||
}
|
||||
let tag_start = format!("M{} ", (ret.cmd_id - 1));
|
||||
|
@ -425,7 +429,7 @@ impl ImapStream {
|
|||
/* sending CAPABILITY after LOGIN automatically is an RFC recommendation, so
|
||||
* check for lazy servers */
|
||||
drop(capabilities);
|
||||
ret.send_command(b"CAPABILITY").await?;
|
||||
ret.send_command(CommandBody::Capability).await?;
|
||||
ret.read_response(&mut res).await.unwrap();
|
||||
let capabilities = protocol_parser::capabilities(&res)?.1;
|
||||
let capabilities = HashSet::from_iter(capabilities.into_iter().map(|s| s.to_vec()));
|
||||
|
@ -500,7 +504,40 @@ impl ImapStream {
|
|||
Ok(())
|
||||
}
|
||||
|
||||
pub async fn send_command(&mut self, command: &[u8]) -> Result<()> {
|
||||
pub async fn send_command(&mut self, body: CommandBody<'_>) -> Result<()> {
|
||||
timeout(self.timeout, async {
|
||||
let command = {
|
||||
let tag = Tag::unchecked(format!("M{}", self.cmd_id.to_string()));
|
||||
|
||||
Command { tag, body }
|
||||
};
|
||||
|
||||
for action in command.encode() {
|
||||
match action {
|
||||
Fragment::Line { data } => {
|
||||
self.stream.write_all(&data).await?;
|
||||
}
|
||||
Fragment::Literal { data, sync } => {
|
||||
// We only need to wait for a continuation request when we are about to
|
||||
// send a synchronizing literal, i.e., when not using LITERAL+.
|
||||
if sync {
|
||||
self.wait_for_continuation_request().await?;
|
||||
}
|
||||
self.stream.write_all(&data).await?;
|
||||
}
|
||||
}
|
||||
// Note: This is required for compression to work...
|
||||
self.stream.flush().await?;
|
||||
}
|
||||
|
||||
self.cmd_id += 1;
|
||||
|
||||
Ok(())
|
||||
})
|
||||
.await?
|
||||
}
|
||||
|
||||
pub async fn send_command_raw(&mut self, command: &[u8]) -> Result<()> {
|
||||
_ = timeout(
|
||||
self.timeout,
|
||||
try_await(async move {
|
||||
|
@ -589,7 +626,7 @@ impl ImapConnection {
|
|||
if self.stream.is_ok() {
|
||||
let mut ret = Vec::new();
|
||||
if let Err(err) = try_await(async {
|
||||
self.send_command(b"NOOP").await?;
|
||||
self.send_command(CommandBody::Noop).await?;
|
||||
self.read_response(&mut ret, RequiredResponses::empty())
|
||||
.await
|
||||
})
|
||||
|
@ -630,14 +667,27 @@ impl ImapConnection {
|
|||
/* Upgrade to Condstore */
|
||||
let mut ret = Vec::new();
|
||||
if capabilities.contains(&b"ENABLE"[..]) {
|
||||
self.send_command(b"ENABLE CONDSTORE").await?;
|
||||
self.send_command(CommandBody::Enable {
|
||||
capabilities: NonEmptyVec::from(
|
||||
CapabilityEnable::CondStore,
|
||||
),
|
||||
})
|
||||
.await?;
|
||||
self.read_response(&mut ret, RequiredResponses::empty())
|
||||
.await?;
|
||||
} else {
|
||||
self.send_command(
|
||||
b"STATUS INBOX (UIDNEXT UIDVALIDITY UNSEEN MESSAGES HIGHESTMODSEQ)",
|
||||
)
|
||||
.await?;
|
||||
self.send_command(CommandBody::Status {
|
||||
mailbox: Mailbox::Inbox,
|
||||
attributes: vec![
|
||||
StatusAttribute::UidNext,
|
||||
StatusAttribute::UidValidity,
|
||||
StatusAttribute::Unseen,
|
||||
StatusAttribute::Messages,
|
||||
StatusAttribute::HighestModSeq,
|
||||
]
|
||||
.into(),
|
||||
})
|
||||
.await?;
|
||||
self.read_response(&mut ret, RequiredResponses::empty())
|
||||
.await?;
|
||||
}
|
||||
|
@ -648,7 +698,8 @@ impl ImapConnection {
|
|||
#[cfg(feature = "deflate_compression")]
|
||||
if capabilities.contains(&b"COMPRESS=DEFLATE"[..]) && deflate {
|
||||
let mut ret = Vec::new();
|
||||
self.send_command(b"COMPRESS DEFLATE").await?;
|
||||
self.send_command(CommandBody::compress(CompressionAlgorithm::Deflate))
|
||||
.await?;
|
||||
self.read_response(&mut ret, RequiredResponses::empty())
|
||||
.await?;
|
||||
match ImapResponse::try_from(ret.as_slice())? {
|
||||
|
@ -798,7 +849,7 @@ impl ImapConnection {
|
|||
Ok(())
|
||||
}
|
||||
|
||||
pub async fn send_command(&mut self, command: &[u8]) -> Result<()> {
|
||||
pub async fn send_command(&mut self, command: CommandBody<'_>) -> Result<()> {
|
||||
if let Err(err) =
|
||||
try_await(async { self.stream.as_mut()?.send_command(command).await }).await
|
||||
{
|
||||
|
@ -813,6 +864,21 @@ impl ImapConnection {
|
|||
}
|
||||
}
|
||||
|
||||
pub async fn send_command_raw(&mut self, command: &[u8]) -> Result<()> {
|
||||
if let Err(err) =
|
||||
try_await(async { self.stream.as_mut()?.send_command_raw(command).await }).await
|
||||
{
|
||||
self.stream = Err(err.clone());
|
||||
if err.kind.is_network() {
|
||||
self.connect().await?;
|
||||
}
|
||||
Err(err)
|
||||
} else {
|
||||
*self.uid_store.is_online.lock().unwrap() = (SystemTime::now(), Ok(()));
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
pub async fn send_literal(&mut self, data: &[u8]) -> Result<()> {
|
||||
if let Err(err) = try_await(async { self.stream.as_mut()?.send_literal(data).await }).await
|
||||
{
|
||||
|
@ -863,7 +929,7 @@ impl ImapConnection {
|
|||
))
|
||||
.set_kind(crate::error::ErrorKind::Bug));
|
||||
}
|
||||
self.send_command(format!("SELECT \"{}\"", imap_path).as_bytes())
|
||||
self.send_command(CommandBody::select(imap_path.as_str())?)
|
||||
.await?;
|
||||
self.read_response(ret, RequiredResponses::SELECT_REQUIRED)
|
||||
.await?;
|
||||
|
@ -949,7 +1015,7 @@ impl ImapConnection {
|
|||
))
|
||||
.set_kind(crate::error::ErrorKind::Bug));
|
||||
}
|
||||
self.send_command(format!("EXAMINE \"{}\"", &imap_path).as_bytes())
|
||||
self.send_command(CommandBody::examine(imap_path.as_str())?)
|
||||
.await?;
|
||||
self.read_response(ret, RequiredResponses::EXAMINE_REQUIRED)
|
||||
.await?;
|
||||
|
@ -985,7 +1051,7 @@ impl ImapConnection {
|
|||
.iter()
|
||||
.any(|cap| cap.eq_ignore_ascii_case(b"UNSELECT"))
|
||||
{
|
||||
self.send_command(b"UNSELECT").await?;
|
||||
self.send_command(CommandBody::Unselect).await?;
|
||||
self.read_response(&mut response, RequiredResponses::empty())
|
||||
.await?;
|
||||
} else {
|
||||
|
@ -1000,8 +1066,7 @@ impl ImapConnection {
|
|||
nonexistent.push('p');
|
||||
}
|
||||
}
|
||||
self.send_command(format!("SELECT \"{}\"", nonexistent).as_bytes())
|
||||
.await?;
|
||||
self.send_command(CommandBody::select(nonexistent)?).await?;
|
||||
self.read_response(&mut response, RequiredResponses::NO_REQUIRED)
|
||||
.await?;
|
||||
}
|
||||
|
@ -1025,9 +1090,14 @@ impl ImapConnection {
|
|||
_select_response: &SelectResponse,
|
||||
) -> Result<()> {
|
||||
debug_assert!(low > 0);
|
||||
self.send_command(CommandBody::search(
|
||||
None,
|
||||
SearchKey::SequenceSet(SequenceSet::try_from(low..)?),
|
||||
true,
|
||||
))
|
||||
.await?;
|
||||
|
||||
let mut response = Vec::new();
|
||||
self.send_command(format!("UID SEARCH {}:*", low).as_bytes())
|
||||
.await?;
|
||||
self.read_response(&mut response, RequiredResponses::SEARCH)
|
||||
.await?;
|
||||
let mut msn_index_lck = self.uid_store.msn_index.lock().unwrap();
|
||||
|
|
|
@ -369,7 +369,7 @@ impl ManageSieveConnection {
|
|||
|
||||
pub async fn listscripts(&mut self) -> Result<Vec<(Vec<u8>, bool)>> {
|
||||
let mut ret = Vec::new();
|
||||
self.inner.send_command(b"Listscripts").await?;
|
||||
self.inner.send_command_raw(b"Listscripts").await?;
|
||||
self.inner
|
||||
.read_response(&mut ret, RequiredResponses::empty())
|
||||
.await?;
|
||||
|
|
|
@ -21,6 +21,8 @@
|
|||
|
||||
use std::sync::Arc;
|
||||
|
||||
use imap_codec::fetch::FetchAttribute;
|
||||
|
||||
use super::*;
|
||||
use crate::{backends::*, email::*, error::Error};
|
||||
|
||||
|
@ -68,8 +70,12 @@ impl BackendOp for ImapOp {
|
|||
conn.connect().await?;
|
||||
conn.examine_mailbox(mailbox_hash, &mut response, false)
|
||||
.await?;
|
||||
conn.send_command(format!("UID FETCH {} (FLAGS RFC822)", uid).as_bytes())
|
||||
.await?;
|
||||
conn.send_command(CommandBody::fetch(
|
||||
uid,
|
||||
vec![FetchAttribute::Flags, FetchAttribute::Rfc822],
|
||||
true,
|
||||
)?)
|
||||
.await?;
|
||||
conn.read_response(&mut response, RequiredResponses::FETCH_REQUIRED)
|
||||
.await?;
|
||||
}
|
||||
|
@ -127,7 +133,7 @@ impl BackendOp for ImapOp {
|
|||
conn.connect().await?;
|
||||
conn.examine_mailbox(mailbox_hash, &mut response, false)
|
||||
.await?;
|
||||
conn.send_command(format!("UID FETCH {} FLAGS", uid).as_bytes())
|
||||
conn.send_command(CommandBody::fetch(uid, vec![FetchAttribute::Flags], true)?)
|
||||
.await?;
|
||||
conn.read_response(&mut response, RequiredResponses::FETCH_REQUIRED)
|
||||
.await?;
|
||||
|
|
|
@ -763,40 +763,6 @@ pub fn uid_fetch_flags_response(input: &[u8]) -> IResult<&[u8], (UID, (Flag, Vec
|
|||
Ok((input, (uid_flags.0, uid_flags.1)))
|
||||
}
|
||||
|
||||
macro_rules! flags_to_imap_list {
|
||||
($flags:ident) => {{
|
||||
let mut ret = String::new();
|
||||
if !($flags & Flag::REPLIED).is_empty() {
|
||||
ret.push_str("\\Answered");
|
||||
}
|
||||
if !($flags & Flag::FLAGGED).is_empty() {
|
||||
if !ret.is_empty() {
|
||||
ret.push(' ');
|
||||
}
|
||||
ret.push_str("\\Flagged");
|
||||
}
|
||||
if !($flags & Flag::TRASHED).is_empty() {
|
||||
if !ret.is_empty() {
|
||||
ret.push(' ');
|
||||
}
|
||||
ret.push_str("\\Deleted");
|
||||
}
|
||||
if !($flags & Flag::SEEN).is_empty() {
|
||||
if !ret.is_empty() {
|
||||
ret.push(' ');
|
||||
}
|
||||
ret.push_str("\\Seen");
|
||||
}
|
||||
if !($flags & Flag::DRAFT).is_empty() {
|
||||
if !ret.is_empty() {
|
||||
ret.push(' ');
|
||||
}
|
||||
ret.push_str("\\Draft");
|
||||
}
|
||||
ret
|
||||
}};
|
||||
}
|
||||
|
||||
/* Input Example:
|
||||
* ==============
|
||||
*
|
||||
|
|
|
@ -19,7 +19,9 @@
|
|||
* along with meli. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
use std::convert::TryInto;
|
||||
use std::convert::{TryFrom, TryInto};
|
||||
|
||||
use imap_codec::{command::CommandBody, search::SearchKey, sequence::SequenceSet};
|
||||
|
||||
use super::{ImapConnection, MailboxSelection, UID};
|
||||
use crate::{
|
||||
|
@ -32,6 +34,7 @@ use crate::{
|
|||
RefreshEventKind::{self, *},
|
||||
TagHash,
|
||||
},
|
||||
email::common_attributes,
|
||||
error::*,
|
||||
};
|
||||
|
||||
|
@ -89,7 +92,12 @@ impl ImapConnection {
|
|||
n,
|
||||
self.uid_store.msn_index.lock().unwrap().get(&mailbox_hash)
|
||||
);
|
||||
self.send_command("UID SEARCH 1:*".as_bytes()).await?;
|
||||
self.send_command(CommandBody::search(
|
||||
None,
|
||||
SearchKey::SequenceSet(SequenceSet::from(..)),
|
||||
true,
|
||||
))
|
||||
.await?;
|
||||
self.read_response(&mut response, RequiredResponses::SEARCH)
|
||||
.await?;
|
||||
let results = super::protocol_parser::search_results(&response)?
|
||||
|
@ -199,7 +207,7 @@ impl ImapConnection {
|
|||
debug!("exists {}", n);
|
||||
try_fail!(
|
||||
mailbox_hash,
|
||||
self.send_command(format!("FETCH {} (UID FLAGS ENVELOPE BODY.PEEK[HEADER.FIELDS (REFERENCES)] BODYSTRUCTURE)", n).as_bytes()).await
|
||||
self.send_command(CommandBody::fetch(n, common_attributes(), false)?).await
|
||||
self.read_response(&mut response, RequiredResponses::FETCH_REQUIRED).await
|
||||
);
|
||||
let mut v = match super::protocol_parser::fetch_responses(&response) {
|
||||
|
@ -307,7 +315,7 @@ impl ImapConnection {
|
|||
UntaggedResponse::Recent(_) => {
|
||||
try_fail!(
|
||||
mailbox_hash,
|
||||
self.send_command(b"UID SEARCH RECENT").await
|
||||
self.send_command(CommandBody::search(None, SearchKey::Recent, true)).await
|
||||
self.read_response(&mut response, RequiredResponses::SEARCH).await
|
||||
);
|
||||
match super::protocol_parser::search_results_raw(&response)
|
||||
|
@ -334,7 +342,7 @@ impl ImapConnection {
|
|||
};
|
||||
try_fail!(
|
||||
mailbox_hash,
|
||||
self.send_command(command.as_bytes()).await
|
||||
self.send_command_raw(command.as_bytes()).await
|
||||
self.read_response(&mut response, RequiredResponses::FETCH_REQUIRED).await
|
||||
);
|
||||
let mut v = match super::protocol_parser::fetch_responses(&response) {
|
||||
|
@ -465,8 +473,12 @@ impl ImapConnection {
|
|||
} else {
|
||||
try_fail!(
|
||||
mailbox_hash,
|
||||
self.send_command(format!("UID SEARCH {}", msg_seq).as_bytes())
|
||||
.await,
|
||||
self.send_command(CommandBody::search(
|
||||
None,
|
||||
SearchKey::SequenceSet(SequenceSet::try_from(msg_seq)?),
|
||||
true
|
||||
))
|
||||
.await,
|
||||
self.read_response(&mut response, RequiredResponses::SEARCH)
|
||||
.await,
|
||||
);
|
||||
|
|
|
@ -20,6 +20,8 @@
|
|||
*/
|
||||
use std::sync::Arc;
|
||||
|
||||
use imap_codec::search::SearchKey;
|
||||
|
||||
use super::*;
|
||||
use crate::backends::SpecialUsageMailbox;
|
||||
|
||||
|
@ -120,7 +122,7 @@ pub async fn idle(kit: ImapWatchKit) -> Result<()> {
|
|||
}
|
||||
examine_updates(mailbox, &mut conn, &uid_store).await?;
|
||||
}
|
||||
conn.send_command(b"IDLE").await?;
|
||||
conn.send_command(CommandBody::Idle).await?;
|
||||
let mut blockn = ImapBlockingConnection::from(conn);
|
||||
let mut watch = std::time::Instant::now();
|
||||
/* duration interval to send heartbeat */
|
||||
|
@ -144,7 +146,7 @@ pub async fn idle(kit: ImapWatchKit) -> Result<()> {
|
|||
.conn
|
||||
.read_response(&mut response, RequiredResponses::empty())
|
||||
.await?;
|
||||
blockn.conn.send_command(b"IDLE").await?;
|
||||
blockn.conn.send_command(CommandBody::Idle).await?;
|
||||
let mut main_conn_lck = timeout(uid_store.timeout, main_conn.lock()).await?;
|
||||
main_conn_lck.connect().await?;
|
||||
continue;
|
||||
|
@ -192,7 +194,7 @@ pub async fn idle(kit: ImapWatchKit) -> Result<()> {
|
|||
}
|
||||
blockn.conn.process_untagged(l).await?;
|
||||
}
|
||||
blockn.conn.send_command(b"IDLE").await?;
|
||||
blockn.conn.send_command(CommandBody::Idle).await?;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -259,7 +261,7 @@ pub async fn examine_updates(
|
|||
.iter()
|
||||
.any(|cap| cap.eq_ignore_ascii_case(b"LIST-STATUS"));
|
||||
if has_list_status {
|
||||
conn.send_command(
|
||||
conn.send_command_raw(
|
||||
format!(
|
||||
"LIST \"{}\" \"\" RETURN (STATUS (MESSAGES UNSEEN))",
|
||||
mailbox.imap_path()
|
||||
|
@ -299,7 +301,8 @@ pub async fn examine_updates(
|
|||
}
|
||||
}
|
||||
} else {
|
||||
conn.send_command(b"SEARCH UNSEEN").await?;
|
||||
conn.send_command(CommandBody::search(None, SearchKey::Unseen, false))
|
||||
.await?;
|
||||
conn.read_response(&mut response, RequiredResponses::SEARCH)
|
||||
.await?;
|
||||
let unseen_count = protocol_parser::search_results(&response)?.1.len();
|
||||
|
@ -318,7 +321,8 @@ pub async fn examine_updates(
|
|||
|
||||
if select_response.recent > 0 {
|
||||
/* UID SEARCH RECENT */
|
||||
conn.send_command(b"UID SEARCH RECENT").await?;
|
||||
conn.send_command(CommandBody::search(None, SearchKey::Recent, true))
|
||||
.await?;
|
||||
conn.read_response(&mut response, RequiredResponses::SEARCH)
|
||||
.await?;
|
||||
let v = protocol_parser::search_results(response.as_slice()).map(|(_, v)| v)?;
|
||||
|
@ -329,30 +333,15 @@ pub async fn examine_updates(
|
|||
);
|
||||
return Ok(());
|
||||
}
|
||||
let mut cmd = "UID FETCH ".to_string();
|
||||
cmd.push_str(&v[0].to_string());
|
||||
if v.len() != 1 {
|
||||
for n in v.into_iter().skip(1) {
|
||||
cmd.push(',');
|
||||
cmd.push_str(&n.to_string());
|
||||
}
|
||||
}
|
||||
cmd.push_str(
|
||||
" (UID FLAGS ENVELOPE BODY.PEEK[HEADER.FIELDS (REFERENCES)] BODYSTRUCTURE)",
|
||||
);
|
||||
conn.send_command(cmd.as_bytes()).await?;
|
||||
conn.send_command(CommandBody::fetch(v.as_slice(), common_attributes(), true)?)
|
||||
.await?;
|
||||
conn.read_response(&mut response, RequiredResponses::FETCH_REQUIRED)
|
||||
.await?;
|
||||
} else if select_response.exists > mailbox.exists.lock().unwrap().len() {
|
||||
conn.send_command(
|
||||
format!(
|
||||
"FETCH {}:* (UID FLAGS ENVELOPE BODY.PEEK[HEADER.FIELDS (REFERENCES)] \
|
||||
BODYSTRUCTURE)",
|
||||
std::cmp::max(mailbox.exists.lock().unwrap().len(), 1)
|
||||
)
|
||||
.as_bytes(),
|
||||
)
|
||||
.await?;
|
||||
let min = std::cmp::max(mailbox.exists.lock().unwrap().len(), 1);
|
||||
|
||||
conn.send_command(CommandBody::fetch(min.., common_attributes(), false)?)
|
||||
.await?;
|
||||
conn.read_response(&mut response, RequiredResponses::FETCH_REQUIRED)
|
||||
.await?;
|
||||
} else {
|
||||
|
|
|
@ -112,6 +112,12 @@ pub use address::{Address, MessageID, References, StrBuild, StrBuilder};
|
|||
pub use attachments::{Attachment, AttachmentBuilder};
|
||||
pub use compose::{attachment_from_file, Draft};
|
||||
pub use headers::*;
|
||||
use imap_codec::{
|
||||
core::{AString, Atom, NonEmptyVec},
|
||||
fetch::{FetchAttribute, MacroOrFetchAttributes},
|
||||
flag::Flag as ImapCodecFlag,
|
||||
section::Section,
|
||||
};
|
||||
use smallvec::SmallVec;
|
||||
|
||||
use crate::{
|
||||
|
@ -122,6 +128,24 @@ use crate::{
|
|||
TagHash,
|
||||
};
|
||||
|
||||
// TODO(#222): Make this `const` as soon as it is possible.
|
||||
pub(crate) fn common_attributes() -> MacroOrFetchAttributes<'static> {
|
||||
MacroOrFetchAttributes::FetchAttributes(vec![
|
||||
FetchAttribute::Uid,
|
||||
FetchAttribute::Flags,
|
||||
FetchAttribute::Envelope,
|
||||
FetchAttribute::BodyExt {
|
||||
section: Some(Section::HeaderFields(
|
||||
None,
|
||||
NonEmptyVec::from(AString::from(Atom::unchecked("REFERENCES"))),
|
||||
)),
|
||||
partial: None,
|
||||
peek: true,
|
||||
},
|
||||
FetchAttribute::BodyStructure,
|
||||
])
|
||||
}
|
||||
|
||||
bitflags! {
|
||||
#[derive(Default, Serialize, Deserialize)]
|
||||
pub struct Flag: u8 {
|
||||
|
@ -165,6 +189,39 @@ impl Flag {
|
|||
flag_impl!(fn is_flagged, Flag::FLAGGED);
|
||||
}
|
||||
|
||||
#[cfg(feature = "imap_backend")]
|
||||
impl Flag {
|
||||
pub(crate) fn derive_imap_codec_flags(&self) -> Vec<ImapCodecFlag> {
|
||||
let mut flags = Vec::new();
|
||||
|
||||
if self.is_passed() {
|
||||
// This is from http://cr.yp.to/proto/maildir.html and not meaningful in IMAP.
|
||||
}
|
||||
|
||||
if self.is_replied() {
|
||||
flags.push(ImapCodecFlag::Answered);
|
||||
}
|
||||
|
||||
if self.is_seen() {
|
||||
flags.push(ImapCodecFlag::Seen);
|
||||
}
|
||||
|
||||
if self.is_trashed() {
|
||||
flags.push(ImapCodecFlag::Deleted);
|
||||
}
|
||||
|
||||
if self.is_draft() {
|
||||
flags.push(ImapCodecFlag::Draft);
|
||||
}
|
||||
|
||||
if self.is_flagged() {
|
||||
flags.push(ImapCodecFlag::Flagged);
|
||||
}
|
||||
|
||||
flags
|
||||
}
|
||||
}
|
||||
|
||||
///`Mail` holds both the envelope info of an email in its `envelope` field and
|
||||
/// the raw bytes that describe the email in `bytes`. Its body as an
|
||||
/// `melib::email::Attachment` can be parsed on demand
|
||||
|
|
|
@ -716,3 +716,96 @@ impl<'a> From<&'a Error> for Error {
|
|||
kind.clone()
|
||||
}
|
||||
}
|
||||
|
||||
// ----- imap-codec -----
|
||||
|
||||
use imap_codec::{
|
||||
command::{AppendError, CopyError, ListError},
|
||||
core::LiteralError,
|
||||
extensions::r#move::MoveError,
|
||||
sequence::SequenceSetError,
|
||||
};
|
||||
|
||||
impl From<LiteralError> for Error {
|
||||
#[inline]
|
||||
fn from(error: LiteralError) -> Error {
|
||||
Error {
|
||||
summary: error.to_string().into(),
|
||||
details: None,
|
||||
source: Some(Arc::new(error)),
|
||||
kind: ErrorKind::Configuration,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl From<SequenceSetError> for Error {
|
||||
#[inline]
|
||||
fn from(error: SequenceSetError) -> Error {
|
||||
Error {
|
||||
summary: error.to_string().into(),
|
||||
details: None,
|
||||
source: Some(Arc::new(error)),
|
||||
kind: ErrorKind::Bug,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<S, L> From<AppendError<S, L>> for Error
|
||||
where
|
||||
AppendError<S, L>: fmt::Debug + fmt::Display + Sync + Send + 'static,
|
||||
{
|
||||
#[inline]
|
||||
fn from(error: AppendError<S, L>) -> Error {
|
||||
Error {
|
||||
summary: error.to_string().into(),
|
||||
details: None,
|
||||
source: Some(Arc::new(error)),
|
||||
kind: ErrorKind::Bug,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<S, L> From<CopyError<S, L>> for Error
|
||||
where
|
||||
CopyError<S, L>: fmt::Debug + fmt::Display + Sync + Send + 'static,
|
||||
{
|
||||
#[inline]
|
||||
fn from(error: CopyError<S, L>) -> Error {
|
||||
Error {
|
||||
summary: error.to_string().into(),
|
||||
details: None,
|
||||
source: Some(Arc::new(error)),
|
||||
kind: ErrorKind::Bug,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<S, M> From<MoveError<S, M>> for Error
|
||||
where
|
||||
MoveError<S, M>: fmt::Debug + fmt::Display + Sync + Send + 'static,
|
||||
{
|
||||
#[inline]
|
||||
fn from(error: MoveError<S, M>) -> Error {
|
||||
Error {
|
||||
summary: error.to_string().into(),
|
||||
details: None,
|
||||
source: Some(Arc::new(error)),
|
||||
kind: ErrorKind::Bug,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<L1, L2> From<ListError<L1, L2>> for Error
|
||||
where
|
||||
ListError<L1, L2>: fmt::Debug + fmt::Display + Sync + Send + 'static,
|
||||
{
|
||||
#[inline]
|
||||
fn from(error: ListError<L1, L2>) -> Error {
|
||||
Error {
|
||||
summary: error.to_string().into(),
|
||||
details: None,
|
||||
source: Some(Arc::new(error)),
|
||||
kind: ErrorKind::Bug,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
Loading…
Reference in New Issue