Compare commits
5 Commits
master
...
duesee/exp
Author | SHA1 | Date |
---|---|---|
Damian Poddebniak | 456c21cc03 | |
Damian Poddebniak | 6c576b2606 | |
Damian Poddebniak | 9c1cdc5696 | |
Damian Poddebniak | af8fe0b6d8 | |
Damian Poddebniak | 613de70a93 |
|
@ -2,6 +2,15 @@
|
||||||
# It is not intended for manual editing.
|
# It is not intended for manual editing.
|
||||||
version = 3
|
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]]
|
[[package]]
|
||||||
name = "adler"
|
name = "adler"
|
||||||
version = "1.0.2"
|
version = "1.0.2"
|
||||||
|
@ -185,6 +194,12 @@ version = "0.13.0"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "904dfeac50f3cdaba28fc6f57fdcddb75f49ed61346676a78c4ffe55877802fd"
|
checksum = "904dfeac50f3cdaba28fc6f57fdcddb75f49ed61346676a78c4ffe55877802fd"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "base64"
|
||||||
|
version = "0.21.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "a4a4ddaa51a5bc52a6948f74c06d20aaaddb71924eab79b8c97a8c556e942d6a"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "bincode"
|
name = "bincode"
|
||||||
version = "1.3.3"
|
version = "1.3.3"
|
||||||
|
@ -869,6 +884,27 @@ dependencies = [
|
||||||
"unicode-normalization",
|
"unicode-normalization",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "imap-codec"
|
||||||
|
version = "0.9.0-dev"
|
||||||
|
dependencies = [
|
||||||
|
"abnf-core",
|
||||||
|
"base64 0.21.0",
|
||||||
|
"chrono",
|
||||||
|
"imap-types",
|
||||||
|
"nom",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "imap-types"
|
||||||
|
version = "0.9.0-dev"
|
||||||
|
dependencies = [
|
||||||
|
"base64 0.21.0",
|
||||||
|
"chrono",
|
||||||
|
"subtle",
|
||||||
|
"thiserror",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "indexmap"
|
name = "indexmap"
|
||||||
version = "1.9.1"
|
version = "1.9.1"
|
||||||
|
@ -1087,7 +1123,7 @@ version = "0.6.1"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "24d0411d6d3cf6baacae37461dc5b0a32b9c68ae99ddef61bcd88174b8da890a"
|
checksum = "24d0411d6d3cf6baacae37461dc5b0a32b9c68ae99ddef61bcd88174b8da890a"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"base64",
|
"base64 0.13.0",
|
||||||
"either",
|
"either",
|
||||||
"log",
|
"log",
|
||||||
"nom",
|
"nom",
|
||||||
|
@ -1167,7 +1203,7 @@ name = "melib"
|
||||||
version = "0.7.2"
|
version = "0.7.2"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"async-stream",
|
"async-stream",
|
||||||
"base64",
|
"base64 0.13.0",
|
||||||
"bincode",
|
"bincode",
|
||||||
"bitflags",
|
"bitflags",
|
||||||
"data-encoding",
|
"data-encoding",
|
||||||
|
@ -1175,6 +1211,7 @@ dependencies = [
|
||||||
"encoding_rs",
|
"encoding_rs",
|
||||||
"flate2",
|
"flate2",
|
||||||
"futures",
|
"futures",
|
||||||
|
"imap-codec",
|
||||||
"indexmap",
|
"indexmap",
|
||||||
"isahc",
|
"isahc",
|
||||||
"libc",
|
"libc",
|
||||||
|
@ -1712,7 +1749,7 @@ version = "1.0.1"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "0864aeff53f8c05aa08d86e5ef839d3dfcf07aeba2db32f12db0ef716e87bd55"
|
checksum = "0864aeff53f8c05aa08d86e5ef839d3dfcf07aeba2db32f12db0ef716e87bd55"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"base64",
|
"base64 0.13.0",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
|
@ -1959,6 +1996,12 @@ dependencies = [
|
||||||
"syn",
|
"syn",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "subtle"
|
||||||
|
version = "2.5.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "81cdd64d312baedb58e21336b31bc043b77e01cc99033ce76ef539f78e965ebc"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "svg"
|
name = "svg"
|
||||||
version = "0.10.0"
|
version = "0.10.0"
|
||||||
|
|
|
@ -47,6 +47,8 @@ serde_derive = "1.0.71"
|
||||||
serde_json = { version = "1.0", optional = true, features = ["raw_value",] }
|
serde_json = { version = "1.0", optional = true, features = ["raw_value",] }
|
||||||
smallvec = { version = "^1.5.0", features = ["serde", ] }
|
smallvec = { version = "^1.5.0", features = ["serde", ] }
|
||||||
smol = "1.0.0"
|
smol = "1.0.0"
|
||||||
|
# TODO(duesee): Use latest version from crates.io.
|
||||||
|
imap-codec = { path = "../../imap-codec", optional = true, features = ["ext_enable", "ext_literal", "ext_sasl_ir", "ext_unselect", "ext_condstore_qresync"] }
|
||||||
|
|
||||||
unicode-segmentation = { version = "1.2.1", default-features = false, optional = true }
|
unicode-segmentation = { version = "1.2.1", default-features = false, optional = true }
|
||||||
uuid = { version = "^1", features = ["serde", "v4", "v5"] }
|
uuid = { version = "^1", features = ["serde", "v4", "v5"] }
|
||||||
|
@ -61,11 +63,11 @@ stderrlog = "^0.5"
|
||||||
default = ["unicode_algorithms", "imap_backend", "maildir_backend", "mbox_backend", "vcard", "sqlite3", "smtp", "deflate_compression"]
|
default = ["unicode_algorithms", "imap_backend", "maildir_backend", "mbox_backend", "vcard", "sqlite3", "smtp", "deflate_compression"]
|
||||||
|
|
||||||
debug-tracing = []
|
debug-tracing = []
|
||||||
deflate_compression = ["flate2", ]
|
deflate_compression = ["flate2", "imap-codec/ext_compress"]
|
||||||
gpgme = []
|
gpgme = []
|
||||||
http = ["isahc"]
|
http = ["isahc"]
|
||||||
http-static = ["isahc", "isahc/static-curl"]
|
http-static = ["isahc", "isahc/static-curl"]
|
||||||
imap_backend = ["tls"]
|
imap_backend = ["tls", "imap-codec"]
|
||||||
jmap_backend = ["http", "serde_json"]
|
jmap_backend = ["http", "serde_json"]
|
||||||
maildir_backend = ["notify"]
|
maildir_backend = ["notify"]
|
||||||
mbox_backend = ["notify"]
|
mbox_backend = ["notify"]
|
||||||
|
|
|
@ -40,6 +40,7 @@ use std::{
|
||||||
collections::{hash_map::DefaultHasher, BTreeSet, HashMap, HashSet},
|
collections::{hash_map::DefaultHasher, BTreeSet, HashMap, HashSet},
|
||||||
convert::TryFrom,
|
convert::TryFrom,
|
||||||
hash::Hasher,
|
hash::Hasher,
|
||||||
|
num::NonZeroU32,
|
||||||
pin::Pin,
|
pin::Pin,
|
||||||
str::FromStr,
|
str::FromStr,
|
||||||
sync::{Arc, Mutex},
|
sync::{Arc, Mutex},
|
||||||
|
@ -47,6 +48,11 @@ use std::{
|
||||||
};
|
};
|
||||||
|
|
||||||
use futures::{lock::Mutex as FutureMutex, stream::Stream};
|
use futures::{lock::Mutex as FutureMutex, stream::Stream};
|
||||||
|
use imap_codec::{
|
||||||
|
command::{fetch::FetchAttribute, CommandBody, ListMailbox as ImapCodecListMailbox},
|
||||||
|
core::{AString, NonEmptyVec},
|
||||||
|
message::{Mailbox as ImapCodecMailbox, Section},
|
||||||
|
};
|
||||||
|
|
||||||
use crate::{
|
use crate::{
|
||||||
backends::{
|
backends::{
|
||||||
|
@ -876,7 +882,7 @@ impl MailBackend for ImapType {
|
||||||
flag_future.await?;
|
flag_future.await?;
|
||||||
let mut response = Vec::with_capacity(8 * 1024);
|
let mut response = Vec::with_capacity(8 * 1024);
|
||||||
let mut conn = connection.lock().await;
|
let mut conn = connection.lock().await;
|
||||||
conn.send_command("EXPUNGE".as_bytes()).await?;
|
conn.send_command_imap_codec(CommandBody::Expunge).await?;
|
||||||
conn.read_response(&mut response, RequiredResponses::empty())
|
conn.read_response(&mut response, RequiredResponses::empty())
|
||||||
.await?;
|
.await?;
|
||||||
debug!("EXPUNGE response: {}", &String::from_utf8_lossy(&response));
|
debug!("EXPUNGE response: {}", &String::from_utf8_lossy(&response));
|
||||||
|
@ -1003,6 +1009,9 @@ impl MailBackend for ImapType {
|
||||||
)));
|
)));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
let mailbox = ImapCodecMailbox::try_from(imap_path.as_str()).unwrap();
|
||||||
|
|
||||||
let mut response = Vec::with_capacity(8 * 1024);
|
let mut response = Vec::with_capacity(8 * 1024);
|
||||||
{
|
{
|
||||||
let mut conn_lck = connection.lock().await;
|
let mut conn_lck = connection.lock().await;
|
||||||
|
@ -1011,7 +1020,9 @@ impl MailBackend for ImapType {
|
||||||
conn_lck.unselect().await?;
|
conn_lck.unselect().await?;
|
||||||
if is_subscribed {
|
if is_subscribed {
|
||||||
conn_lck
|
conn_lck
|
||||||
.send_command(format!("UNSUBSCRIBE \"{}\"", &imap_path).as_bytes())
|
.send_command_imap_codec(CommandBody::Unsubscribe {
|
||||||
|
mailbox: mailbox.clone(),
|
||||||
|
})
|
||||||
.await?;
|
.await?;
|
||||||
conn_lck
|
conn_lck
|
||||||
.read_response(&mut response, RequiredResponses::empty())
|
.read_response(&mut response, RequiredResponses::empty())
|
||||||
|
@ -1019,7 +1030,7 @@ impl MailBackend for ImapType {
|
||||||
}
|
}
|
||||||
|
|
||||||
conn_lck
|
conn_lck
|
||||||
.send_command(debug!(format!("DELETE \"{}\"", &imap_path,)).as_bytes())
|
.send_command_imap_codec(debug!(CommandBody::Delete { mailbox }))
|
||||||
.await?;
|
.await?;
|
||||||
conn_lck
|
conn_lck
|
||||||
.read_response(&mut response, RequiredResponses::empty())
|
.read_response(&mut response, RequiredResponses::empty())
|
||||||
|
@ -1048,23 +1059,24 @@ impl MailBackend for ImapType {
|
||||||
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 {
|
||||||
let command: String;
|
let mailboxes = uid_store.mailboxes.lock().await;
|
||||||
{
|
if mailboxes[&mailbox_hash].is_subscribed() == new_val {
|
||||||
let mailboxes = uid_store.mailboxes.lock().await;
|
return Ok(());
|
||||||
if mailboxes[&mailbox_hash].is_subscribed() == new_val {
|
|
||||||
return Ok(());
|
|
||||||
}
|
|
||||||
command = format!("SUBSCRIBE \"{}\"", mailboxes[&mailbox_hash].imap_path());
|
|
||||||
}
|
}
|
||||||
|
|
||||||
let mut response = Vec::with_capacity(8 * 1024);
|
let mut response = Vec::with_capacity(8 * 1024);
|
||||||
{
|
{
|
||||||
|
let mailbox =
|
||||||
|
ImapCodecMailbox::try_from(mailboxes[&mailbox_hash].imap_path()).unwrap();
|
||||||
|
|
||||||
let mut conn_lck = connection.lock().await;
|
let mut conn_lck = connection.lock().await;
|
||||||
if new_val {
|
if new_val {
|
||||||
conn_lck.send_command(command.as_bytes()).await?;
|
conn_lck
|
||||||
|
.send_command_imap_codec(CommandBody::Subscribe { mailbox })
|
||||||
|
.await?;
|
||||||
} else {
|
} else {
|
||||||
conn_lck
|
conn_lck
|
||||||
.send_command(format!("UN{}", command).as_bytes())
|
.send_command_imap_codec(CommandBody::Unsubscribe { mailbox })
|
||||||
.await?;
|
.await?;
|
||||||
}
|
}
|
||||||
conn_lck
|
conn_lck
|
||||||
|
@ -1395,7 +1407,7 @@ impl ImapType {
|
||||||
let mut res = Vec::with_capacity(8 * 1024);
|
let mut res = Vec::with_capacity(8 * 1024);
|
||||||
futures::executor::block_on(timeout(
|
futures::executor::block_on(timeout(
|
||||||
self.server_conf.timeout,
|
self.server_conf.timeout,
|
||||||
conn.send_command(b"NOOP"),
|
conn.send_command_imap_codec(CommandBody::Noop),
|
||||||
))
|
))
|
||||||
.unwrap()
|
.unwrap()
|
||||||
.unwrap();
|
.unwrap();
|
||||||
|
@ -1458,6 +1470,7 @@ impl ImapType {
|
||||||
.iter()
|
.iter()
|
||||||
.any(|cap| cap.eq_ignore_ascii_case(b"LIST-STATUS"));
|
.any(|cap| cap.eq_ignore_ascii_case(b"LIST-STATUS"));
|
||||||
if has_list_status {
|
if has_list_status {
|
||||||
|
// TODO(imap-codec): LIST-STATUS not supported.
|
||||||
conn.send_command(b"LIST \"\" \"*\" RETURN (STATUS (MESSAGES UNSEEN))")
|
conn.send_command(b"LIST \"\" \"*\" RETURN (STATUS (MESSAGES UNSEEN))")
|
||||||
.await?;
|
.await?;
|
||||||
conn.read_response(
|
conn.read_response(
|
||||||
|
@ -1466,7 +1479,11 @@ impl ImapType {
|
||||||
)
|
)
|
||||||
.await?;
|
.await?;
|
||||||
} else {
|
} else {
|
||||||
conn.send_command(b"LIST \"\" \"*\"").await?;
|
conn.send_command_imap_codec(CommandBody::List {
|
||||||
|
reference: ImapCodecMailbox::try_from("").unwrap(),
|
||||||
|
mailbox_wildcard: ImapCodecListMailbox::try_from("*").unwrap(),
|
||||||
|
})
|
||||||
|
.await?;
|
||||||
conn.read_response(&mut res, RequiredResponses::LIST_REQUIRED)
|
conn.read_response(&mut res, RequiredResponses::LIST_REQUIRED)
|
||||||
.await?;
|
.await?;
|
||||||
}
|
}
|
||||||
|
@ -1806,20 +1823,40 @@ async fn fetch_hlpr(state: &mut FetchState) -> Result<Vec<Envelope>> {
|
||||||
.await?;
|
.await?;
|
||||||
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 attributes = vec![
|
||||||
|
FetchAttribute::Uid,
|
||||||
|
FetchAttribute::Flags,
|
||||||
|
FetchAttribute::Envelope,
|
||||||
|
FetchAttribute::BodyExt {
|
||||||
|
section: Some(Section::HeaderFields(
|
||||||
|
None,
|
||||||
|
NonEmptyVec::from(AString::try_from("REFERENCES").unwrap()),
|
||||||
|
)),
|
||||||
|
partial: None,
|
||||||
|
peek: true,
|
||||||
|
},
|
||||||
|
FetchAttribute::BodyStructure,
|
||||||
|
];
|
||||||
|
|
||||||
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)] \
|
CommandBody::fetch(1, attributes, true).unwrap()
|
||||||
BODYSTRUCTURE)"
|
|
||||||
.to_string()
|
|
||||||
} else {
|
} else {
|
||||||
format!(
|
let from = NonZeroU32::try_from(
|
||||||
"UID FETCH {}:{} (UID FLAGS ENVELOPE BODY.PEEK[HEADER.FIELDS \
|
u32::try_from(std::cmp::max(
|
||||||
(REFERENCES)] BODYSTRUCTURE)",
|
max_uid_left.saturating_sub(chunk_size),
|
||||||
std::cmp::max(max_uid_left.saturating_sub(chunk_size), 1),
|
1,
|
||||||
max_uid_left
|
))
|
||||||
|
.unwrap(),
|
||||||
)
|
)
|
||||||
|
.unwrap();
|
||||||
|
let to =
|
||||||
|
NonZeroU32::try_from(u32::try_from(max_uid_left).unwrap()).unwrap();
|
||||||
|
|
||||||
|
CommandBody::fetch(format!("{}:{}", from, to), attributes, true).unwrap()
|
||||||
};
|
};
|
||||||
debug!("sending {:?}", &command);
|
debug!("sending {:?}", &command);
|
||||||
conn.send_command(command.as_bytes()).await?;
|
conn.send_command_imap_codec(command).await?;
|
||||||
conn.read_response(&mut response, RequiredResponses::FETCH_REQUIRED)
|
conn.read_response(&mut response, RequiredResponses::FETCH_REQUIRED)
|
||||||
.await
|
.await
|
||||||
.chain_err_summary(|| {
|
.chain_err_summary(|| {
|
||||||
|
|
|
@ -33,12 +33,24 @@ use std::{
|
||||||
convert::TryFrom,
|
convert::TryFrom,
|
||||||
future::Future,
|
future::Future,
|
||||||
iter::FromIterator,
|
iter::FromIterator,
|
||||||
|
num::NonZeroU32,
|
||||||
pin::Pin,
|
pin::Pin,
|
||||||
sync::Arc,
|
sync::Arc,
|
||||||
time::{Duration, Instant, SystemTime},
|
time::{Duration, Instant, SystemTime},
|
||||||
};
|
};
|
||||||
|
|
||||||
use futures::io::{AsyncReadExt, AsyncWriteExt};
|
use futures::io::{AsyncReadExt, AsyncWriteExt};
|
||||||
|
use imap_codec::{
|
||||||
|
codec::{Action, Encode},
|
||||||
|
command::{
|
||||||
|
search::SearchKey, status::StatusAttribute, Command, CommandBody, SeqOrUid, Sequence,
|
||||||
|
SequenceSet,
|
||||||
|
},
|
||||||
|
core::{AString, NonEmptyVec},
|
||||||
|
extensions::{compress::CompressionAlgorithm, enable::CapabilityEnable},
|
||||||
|
message::{AuthMechanism, Mailbox, Tag},
|
||||||
|
secret::Secret,
|
||||||
|
};
|
||||||
use native_tls::TlsConnector;
|
use native_tls::TlsConnector;
|
||||||
pub use smol::Async as AsyncWrapper;
|
pub use smol::Async as AsyncWrapper;
|
||||||
|
|
||||||
|
@ -272,23 +284,19 @@ impl ImapStream {
|
||||||
timeout: server_conf.timeout,
|
timeout: server_conf.timeout,
|
||||||
};
|
};
|
||||||
if let ImapProtocol::ManageSieve = server_conf.protocol {
|
if let ImapProtocol::ManageSieve = server_conf.protocol {
|
||||||
use data_encoding::BASE64;
|
|
||||||
ret.read_response(&mut res).await?;
|
ret.read_response(&mut res).await?;
|
||||||
ret.send_command(
|
|
||||||
format!(
|
let credentials = format!(
|
||||||
"AUTHENTICATE \"PLAIN\" \"{}\"",
|
"\0{}\0{}",
|
||||||
BASE64.encode(
|
&server_conf.server_username, &server_conf.server_password
|
||||||
format!(
|
);
|
||||||
"\0{}\0{}",
|
|
||||||
&server_conf.server_username, &server_conf.server_password
|
ret.send_command_imap_codec(CommandBody::authenticate(
|
||||||
)
|
AuthMechanism::Plain,
|
||||||
.as_bytes()
|
Some(credentials.as_bytes()),
|
||||||
)
|
))
|
||||||
)
|
|
||||||
.as_bytes(),
|
|
||||||
)
|
|
||||||
.await?;
|
.await?;
|
||||||
ret.read_response(&mut res).await?;
|
|
||||||
return Ok((Default::default(), ret));
|
return Ok((Default::default(), ret));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -298,7 +306,7 @@ impl ImapStream {
|
||||||
message: "Negotiating server capabilities.".into(),
|
message: "Negotiating server capabilities.".into(),
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
ret.send_command(b"CAPABILITY").await?;
|
ret.send_command_imap_codec(CommandBody::Capability).await?;
|
||||||
ret.read_response(&mut res).await?;
|
ret.read_response(&mut res).await?;
|
||||||
let capabilities: std::result::Result<Vec<&[u8]>, _> = res
|
let capabilities: std::result::Result<Vec<&[u8]>, _> = res
|
||||||
.split_rn()
|
.split_rn()
|
||||||
|
@ -370,24 +378,14 @@ impl ImapStream {
|
||||||
.await?;
|
.await?;
|
||||||
}
|
}
|
||||||
_ => {
|
_ => {
|
||||||
ret.send_command(
|
let username = AString::try_from(server_conf.server_username.as_str())?;
|
||||||
format!(
|
let password = AString::try_from(server_conf.server_password.as_str())?;
|
||||||
r#"LOGIN "{}" {{{}}}"#,
|
|
||||||
&server_conf
|
ret.send_command_imap_codec(CommandBody::Login {
|
||||||
.server_username
|
username,
|
||||||
.replace('\\', r#"\\"#)
|
password: Secret::new(password),
|
||||||
.replace('"', r#"\""#)
|
})
|
||||||
.replace('{', r#"\{"#)
|
|
||||||
.replace('}', r#"\}"#),
|
|
||||||
&server_conf.server_password.as_bytes().len()
|
|
||||||
)
|
|
||||||
.as_bytes(),
|
|
||||||
)
|
|
||||||
.await?;
|
.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));
|
let tag_start = format!("M{} ", (ret.cmd_id - 1));
|
||||||
|
@ -425,7 +423,7 @@ impl ImapStream {
|
||||||
/* sending CAPABILITY after LOGIN automatically is an RFC recommendation, so
|
/* sending CAPABILITY after LOGIN automatically is an RFC recommendation, so
|
||||||
* check for lazy servers */
|
* check for lazy servers */
|
||||||
drop(capabilities);
|
drop(capabilities);
|
||||||
ret.send_command(b"CAPABILITY").await?;
|
ret.send_command_imap_codec(CommandBody::Capability).await?;
|
||||||
ret.read_response(&mut res).await.unwrap();
|
ret.read_response(&mut res).await.unwrap();
|
||||||
let capabilities = protocol_parser::capabilities(&res)?.1;
|
let capabilities = protocol_parser::capabilities(&res)?.1;
|
||||||
let capabilities = HashSet::from_iter(capabilities.into_iter().map(|s| s.to_vec()));
|
let capabilities = HashSet::from_iter(capabilities.into_iter().map(|s| s.to_vec()));
|
||||||
|
@ -500,6 +498,55 @@ impl ImapStream {
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// TODO(duesee): Rename after `send_command` was removed.
|
||||||
|
pub async fn send_command_imap_codec(&mut self, command_body: CommandBody<'_>) -> Result<()> {
|
||||||
|
let command = Command {
|
||||||
|
// We know that this tag is valid.
|
||||||
|
tag: Tag::unchecked(format!("M{}", self.cmd_id.to_string())),
|
||||||
|
body: command_body,
|
||||||
|
};
|
||||||
|
|
||||||
|
_ = timeout(
|
||||||
|
self.timeout,
|
||||||
|
try_await(async move {
|
||||||
|
match self.protocol {
|
||||||
|
ImapProtocol::IMAP { .. } => {
|
||||||
|
self.cmd_id += 1;
|
||||||
|
}
|
||||||
|
ImapProtocol::ManageSieve => {}
|
||||||
|
}
|
||||||
|
|
||||||
|
for action in command.encode() {
|
||||||
|
match action {
|
||||||
|
Action::Send { data } => {
|
||||||
|
self.stream.write_all(&data).await?;
|
||||||
|
}
|
||||||
|
Action::RecvContinuationRequest => {
|
||||||
|
self.wait_for_continuation_request().await?;
|
||||||
|
}
|
||||||
|
Action::Unknown => {
|
||||||
|
return Err("Unexpected message flow".into());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
match self.protocol {
|
||||||
|
ImapProtocol::IMAP { .. } => {
|
||||||
|
// We do not need to worry about logging sensitive values here because
|
||||||
|
// imap-codec redacts it. The only way to obtain sensitive data (without
|
||||||
|
// calling `expose_secret`) is through `Encode::encode{,_detached}`.
|
||||||
|
debug!("sent: {:?}", command);
|
||||||
|
}
|
||||||
|
ImapProtocol::ManageSieve => {}
|
||||||
|
}
|
||||||
|
Ok(())
|
||||||
|
}),
|
||||||
|
)
|
||||||
|
.await?;
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
// TODO(duesee): Replace this with `send_command_imap_codec`.
|
||||||
pub async fn send_command(&mut self, command: &[u8]) -> Result<()> {
|
pub async fn send_command(&mut self, command: &[u8]) -> Result<()> {
|
||||||
_ = timeout(
|
_ = timeout(
|
||||||
self.timeout,
|
self.timeout,
|
||||||
|
@ -589,7 +636,7 @@ impl ImapConnection {
|
||||||
if self.stream.is_ok() {
|
if self.stream.is_ok() {
|
||||||
let mut ret = Vec::new();
|
let mut ret = Vec::new();
|
||||||
if let Err(err) = try_await(async {
|
if let Err(err) = try_await(async {
|
||||||
self.send_command(b"NOOP").await?;
|
self.send_command_imap_codec(CommandBody::Noop).await?;
|
||||||
self.read_response(&mut ret, RequiredResponses::empty())
|
self.read_response(&mut ret, RequiredResponses::empty())
|
||||||
.await
|
.await
|
||||||
})
|
})
|
||||||
|
@ -630,14 +677,26 @@ impl ImapConnection {
|
||||||
/* Upgrade to Condstore */
|
/* Upgrade to Condstore */
|
||||||
let mut ret = Vec::new();
|
let mut ret = Vec::new();
|
||||||
if capabilities.contains(&b"ENABLE"[..]) {
|
if capabilities.contains(&b"ENABLE"[..]) {
|
||||||
self.send_command(b"ENABLE CONDSTORE").await?;
|
self.send_command_imap_codec(CommandBody::Enable {
|
||||||
|
capabilities: NonEmptyVec::from(
|
||||||
|
CapabilityEnable::CondStore,
|
||||||
|
),
|
||||||
|
})
|
||||||
|
.await?;
|
||||||
self.read_response(&mut ret, RequiredResponses::empty())
|
self.read_response(&mut ret, RequiredResponses::empty())
|
||||||
.await?;
|
.await?;
|
||||||
} else {
|
} else {
|
||||||
self.send_command(
|
self.send_command_imap_codec(CommandBody::Status {
|
||||||
b"STATUS INBOX (UIDNEXT UIDVALIDITY UNSEEN MESSAGES HIGHESTMODSEQ)",
|
mailbox: Mailbox::Inbox,
|
||||||
)
|
attributes: vec![
|
||||||
.await?;
|
StatusAttribute::UidNext,
|
||||||
|
StatusAttribute::UidValidity,
|
||||||
|
StatusAttribute::Unseen,
|
||||||
|
StatusAttribute::Messages,
|
||||||
|
StatusAttribute::HighestModSeq,
|
||||||
|
],
|
||||||
|
})
|
||||||
|
.await?;
|
||||||
self.read_response(&mut ret, RequiredResponses::empty())
|
self.read_response(&mut ret, RequiredResponses::empty())
|
||||||
.await?;
|
.await?;
|
||||||
}
|
}
|
||||||
|
@ -648,7 +707,10 @@ impl ImapConnection {
|
||||||
#[cfg(feature = "deflate_compression")]
|
#[cfg(feature = "deflate_compression")]
|
||||||
if capabilities.contains(&b"COMPRESS=DEFLATE"[..]) && deflate {
|
if capabilities.contains(&b"COMPRESS=DEFLATE"[..]) && deflate {
|
||||||
let mut ret = Vec::new();
|
let mut ret = Vec::new();
|
||||||
self.send_command(b"COMPRESS DEFLATE").await?;
|
self.send_command_imap_codec(CommandBody::compress(
|
||||||
|
CompressionAlgorithm::Deflate,
|
||||||
|
))
|
||||||
|
.await?;
|
||||||
self.read_response(&mut ret, RequiredResponses::empty())
|
self.read_response(&mut ret, RequiredResponses::empty())
|
||||||
.await?;
|
.await?;
|
||||||
match ImapResponse::try_from(ret.as_slice())? {
|
match ImapResponse::try_from(ret.as_slice())? {
|
||||||
|
@ -798,6 +860,21 @@ impl ImapConnection {
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub async fn send_command_imap_codec(&mut self, command: CommandBody<'_>) -> Result<()> {
|
||||||
|
if let Err(err) =
|
||||||
|
try_await(async { self.stream.as_mut()?.send_command_imap_codec(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_command(&mut self, command: &[u8]) -> Result<()> {
|
pub async fn send_command(&mut self, command: &[u8]) -> Result<()> {
|
||||||
if let Err(err) =
|
if let Err(err) =
|
||||||
try_await(async { self.stream.as_mut()?.send_command(command).await }).await
|
try_await(async { self.stream.as_mut()?.send_command(command).await }).await
|
||||||
|
@ -863,7 +940,7 @@ impl ImapConnection {
|
||||||
))
|
))
|
||||||
.set_kind(crate::error::ErrorKind::Bug));
|
.set_kind(crate::error::ErrorKind::Bug));
|
||||||
}
|
}
|
||||||
self.send_command(format!("SELECT \"{}\"", imap_path).as_bytes())
|
self.send_command_imap_codec(CommandBody::select(imap_path.as_str()).unwrap())
|
||||||
.await?;
|
.await?;
|
||||||
self.read_response(ret, RequiredResponses::SELECT_REQUIRED)
|
self.read_response(ret, RequiredResponses::SELECT_REQUIRED)
|
||||||
.await?;
|
.await?;
|
||||||
|
@ -949,7 +1026,7 @@ impl ImapConnection {
|
||||||
))
|
))
|
||||||
.set_kind(crate::error::ErrorKind::Bug));
|
.set_kind(crate::error::ErrorKind::Bug));
|
||||||
}
|
}
|
||||||
self.send_command(format!("EXAMINE \"{}\"", &imap_path).as_bytes())
|
self.send_command_imap_codec(CommandBody::examine(imap_path.as_str()).unwrap())
|
||||||
.await?;
|
.await?;
|
||||||
self.read_response(ret, RequiredResponses::EXAMINE_REQUIRED)
|
self.read_response(ret, RequiredResponses::EXAMINE_REQUIRED)
|
||||||
.await?;
|
.await?;
|
||||||
|
@ -985,7 +1062,7 @@ impl ImapConnection {
|
||||||
.iter()
|
.iter()
|
||||||
.any(|cap| cap.eq_ignore_ascii_case(b"UNSELECT"))
|
.any(|cap| cap.eq_ignore_ascii_case(b"UNSELECT"))
|
||||||
{
|
{
|
||||||
self.send_command(b"UNSELECT").await?;
|
self.send_command_imap_codec(CommandBody::Unselect).await?;
|
||||||
self.read_response(&mut response, RequiredResponses::empty())
|
self.read_response(&mut response, RequiredResponses::empty())
|
||||||
.await?;
|
.await?;
|
||||||
} else {
|
} else {
|
||||||
|
@ -1000,7 +1077,7 @@ impl ImapConnection {
|
||||||
nonexistent.push('p');
|
nonexistent.push('p');
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
self.send_command(format!("SELECT \"{}\"", nonexistent).as_bytes())
|
self.send_command_imap_codec(CommandBody::select(nonexistent).unwrap())
|
||||||
.await?;
|
.await?;
|
||||||
self.read_response(&mut response, RequiredResponses::NO_REQUIRED)
|
self.read_response(&mut response, RequiredResponses::NO_REQUIRED)
|
||||||
.await?;
|
.await?;
|
||||||
|
@ -1026,8 +1103,15 @@ impl ImapConnection {
|
||||||
) -> Result<()> {
|
) -> Result<()> {
|
||||||
debug_assert!(low > 0);
|
debug_assert!(low > 0);
|
||||||
let mut response = Vec::new();
|
let mut response = Vec::new();
|
||||||
self.send_command(format!("UID SEARCH {}:*", low).as_bytes())
|
self.send_command_imap_codec(CommandBody::search(
|
||||||
.await?;
|
None,
|
||||||
|
SearchKey::Uid(SequenceSet::from(Sequence::Range(
|
||||||
|
SeqOrUid::Value(NonZeroU32::try_from(u32::try_from(low).unwrap()).unwrap()),
|
||||||
|
SeqOrUid::Asterisk,
|
||||||
|
))),
|
||||||
|
true,
|
||||||
|
))
|
||||||
|
.await?;
|
||||||
self.read_response(&mut response, RequiredResponses::SEARCH)
|
self.read_response(&mut response, RequiredResponses::SEARCH)
|
||||||
.await?;
|
.await?;
|
||||||
let mut msn_index_lck = self.uid_store.msn_index.lock().unwrap();
|
let mut msn_index_lck = self.uid_store.msn_index.lock().unwrap();
|
||||||
|
|
|
@ -21,6 +21,10 @@
|
||||||
|
|
||||||
use std::{convert::TryFrom, str::FromStr};
|
use std::{convert::TryFrom, str::FromStr};
|
||||||
|
|
||||||
|
use imap_codec::{
|
||||||
|
codec::Decode,
|
||||||
|
response::{data::FetchAttributeValue, Data, Response},
|
||||||
|
};
|
||||||
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},
|
||||||
|
@ -541,220 +545,91 @@ pub struct FetchResponse<'a> {
|
||||||
pub message_sequence_number: MessageSequenceNumber,
|
pub message_sequence_number: MessageSequenceNumber,
|
||||||
pub modseq: Option<ModSequence>,
|
pub modseq: Option<ModSequence>,
|
||||||
pub flags: Option<(Flag, Vec<String>)>,
|
pub flags: Option<(Flag, Vec<String>)>,
|
||||||
pub body: Option<&'a [u8]>,
|
pub body: Option<Cow<'a, [u8]>>,
|
||||||
pub references: Option<&'a [u8]>,
|
pub references: Option<&'a [u8]>,
|
||||||
pub envelope: Option<Envelope>,
|
pub envelope: Option<Envelope>,
|
||||||
pub raw_fetch_value: &'a [u8],
|
pub raw_fetch_value: &'a [u8],
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn fetch_response(input: &[u8]) -> ImapParseResult<FetchResponse<'_>> {
|
pub fn fetch_response(input: &[u8]) -> ImapParseResult<FetchResponse<'_>> {
|
||||||
macro_rules! should_start_with {
|
match Response::decode(input) {
|
||||||
($input:expr, $tag:literal) => {
|
Ok((remainder, response)) => {
|
||||||
if !$input.starts_with($tag) {
|
match response {
|
||||||
return Err(Error::new(format!(
|
Response::Data(Data::Fetch {
|
||||||
"Expected `{}` but got `{:.50}`",
|
seq_or_uid,
|
||||||
String::from_utf8_lossy($tag),
|
attributes,
|
||||||
String::from_utf8_lossy(&$input)
|
}) => {
|
||||||
)));
|
let mut ret = FetchResponse {
|
||||||
}
|
uid: None,
|
||||||
};
|
message_sequence_number: seq_or_uid.get() as usize, // TODO: Can't be both.
|
||||||
}
|
modseq: None, // TODO: HIGHESTMODSEQ?
|
||||||
should_start_with!(input, b"* ");
|
flags: None,
|
||||||
|
body: None,
|
||||||
|
references: None,
|
||||||
|
envelope: None,
|
||||||
|
raw_fetch_value: &[], // TODO
|
||||||
|
};
|
||||||
|
|
||||||
let mut i = b"* ".len();
|
for attribute in attributes.into_iter() {
|
||||||
macro_rules! bounds {
|
// TODO: What should be present and what not?
|
||||||
() => {
|
match attribute {
|
||||||
if i == input.len() {
|
FetchAttributeValue::Uid(uid) => ret.uid = Some(uid.get() as usize),
|
||||||
return Err(Error::new(format!(
|
FetchAttributeValue::Flags(flags) => {
|
||||||
"Expected more input. Got: `{:.50}`",
|
ret.flags = Some((
|
||||||
String::from_utf8_lossy(&input)
|
// TODO
|
||||||
)));
|
flags
|
||||||
}
|
.into_iter()
|
||||||
};
|
.map(|flag| crate::Flag::from(flag))
|
||||||
(break) => {
|
.collect(),
|
||||||
if i == input.len() {
|
// TODO
|
||||||
break;
|
vec![],
|
||||||
}
|
))
|
||||||
};
|
}
|
||||||
}
|
FetchAttributeValue::BodyExt {
|
||||||
|
section: None,
|
||||||
macro_rules! eat_whitespace {
|
origin: None,
|
||||||
() => {
|
data,
|
||||||
while (input[i] as char).is_whitespace() {
|
} => {
|
||||||
i += 1;
|
ret.body = data.into_option();
|
||||||
bounds!();
|
}
|
||||||
}
|
//TODO: Make query of REFERENCES more ergonomic.
|
||||||
};
|
FetchAttributeValue::BodyExt {
|
||||||
(break) => {
|
section: Some(Section::HeaderFields(None, fields)),
|
||||||
while (input[i] as char).is_whitespace() {
|
origin: None,
|
||||||
i += 1;
|
data,
|
||||||
bounds!(break);
|
} if fields
|
||||||
}
|
.into_iter()
|
||||||
};
|
.map(|i| i.as_ref())
|
||||||
}
|
.collect::<Vec<_>>()
|
||||||
|
.into_iter()
|
||||||
let mut ret = FetchResponse {
|
.collect::<Vec<_>>()
|
||||||
uid: None,
|
.contains(b"REFERENCES") =>
|
||||||
message_sequence_number: 0,
|
{
|
||||||
modseq: None,
|
// TODO
|
||||||
flags: None,
|
println!("YAY!")
|
||||||
body: None,
|
}
|
||||||
references: None,
|
FetchAttributeValue::Envelope(envelope) => {
|
||||||
envelope: None,
|
// TODO
|
||||||
raw_fetch_value: &[],
|
}
|
||||||
};
|
FetchAttributeValue::BodyStructure(body_structure) => {
|
||||||
|
// TODO
|
||||||
while input[i].is_ascii_digit() {
|
}
|
||||||
let b: u8 = input[i] - 0x30;
|
unexpected => {
|
||||||
ret.message_sequence_number *= 10;
|
// Ignore unexpected attributes.
|
||||||
ret.message_sequence_number += b as MessageSequenceNumber;
|
panic!("Unexpected attribute `{:?}`", unexpected);
|
||||||
i += 1;
|
}
|
||||||
bounds!();
|
}
|
||||||
}
|
|
||||||
|
|
||||||
eat_whitespace!();
|
|
||||||
should_start_with!(&input[i..], b"FETCH (");
|
|
||||||
i += b"FETCH (".len();
|
|
||||||
let mut has_attachments = false;
|
|
||||||
while i < input.len() {
|
|
||||||
eat_whitespace!(break);
|
|
||||||
bounds!(break);
|
|
||||||
|
|
||||||
if input[i..].starts_with(b"UID ") {
|
|
||||||
i += b"UID ".len();
|
|
||||||
if let Ok((rest, uid)) =
|
|
||||||
take_while::<_, &[u8], (&[u8], nom::error::ErrorKind)>(is_digit)(&input[i..])
|
|
||||||
{
|
|
||||||
i += input.len() - i - rest.len();
|
|
||||||
ret.uid =
|
|
||||||
Some(UID::from_str(unsafe { std::str::from_utf8_unchecked(uid) }).unwrap());
|
|
||||||
} else {
|
|
||||||
return debug!(Err(Error::new(format!(
|
|
||||||
"Unexpected input while parsing UID FETCH response. Got: `{:.40}`",
|
|
||||||
String::from_utf8_lossy(input)
|
|
||||||
))));
|
|
||||||
}
|
|
||||||
} else if input[i..].starts_with(b"FLAGS (") {
|
|
||||||
i += b"FLAGS (".len();
|
|
||||||
if let Ok((rest, flags)) = flags(&input[i..]) {
|
|
||||||
ret.flags = Some(flags);
|
|
||||||
i += (input.len() - i - rest.len()) + 1;
|
|
||||||
} else {
|
|
||||||
return debug!(Err(Error::new(format!(
|
|
||||||
"Unexpected input while parsing UID FETCH response. Could not parse FLAGS: \
|
|
||||||
{:.40}.",
|
|
||||||
String::from_utf8_lossy(&input[i..])
|
|
||||||
))));
|
|
||||||
}
|
|
||||||
} else if input[i..].starts_with(b"MODSEQ (") {
|
|
||||||
i += b"MODSEQ (".len();
|
|
||||||
if let Ok((rest, modseq)) =
|
|
||||||
take_while::<_, &[u8], (&[u8], nom::error::ErrorKind)>(is_digit)(&input[i..])
|
|
||||||
{
|
|
||||||
i += (input.len() - i - rest.len()) + 1;
|
|
||||||
ret.modseq = u64::from_str(to_str!(modseq))
|
|
||||||
.ok()
|
|
||||||
.and_then(std::num::NonZeroU64::new)
|
|
||||||
.map(ModSequence);
|
|
||||||
} else {
|
|
||||||
return debug!(Err(Error::new(format!(
|
|
||||||
"Unexpected input while parsing MODSEQ in UID FETCH response. Got: `{:.40}`",
|
|
||||||
String::from_utf8_lossy(input)
|
|
||||||
))));
|
|
||||||
}
|
|
||||||
} else if input[i..].starts_with(b"RFC822 {") {
|
|
||||||
i += b"RFC822 ".len();
|
|
||||||
if let Ok((rest, body)) =
|
|
||||||
length_data::<_, _, (&[u8], nom::error::ErrorKind), _>(delimited(
|
|
||||||
tag("{"),
|
|
||||||
map_res(digit1, |s| {
|
|
||||||
usize::from_str(unsafe { std::str::from_utf8_unchecked(s) })
|
|
||||||
}),
|
|
||||||
tag("}\r\n"),
|
|
||||||
))(&input[i..])
|
|
||||||
{
|
|
||||||
ret.body = Some(body);
|
|
||||||
i += input.len() - i - rest.len();
|
|
||||||
} else {
|
|
||||||
return debug!(Err(Error::new(format!(
|
|
||||||
"Unexpected input while parsing UID FETCH response. Could not parse RFC822: \
|
|
||||||
{:.40}",
|
|
||||||
String::from_utf8_lossy(&input[i..])
|
|
||||||
))));
|
|
||||||
}
|
|
||||||
} else if input[i..].starts_with(b"ENVELOPE (") {
|
|
||||||
i += b"ENVELOPE ".len();
|
|
||||||
if let Ok((rest, envelope)) = envelope(&input[i..]) {
|
|
||||||
ret.envelope = Some(envelope);
|
|
||||||
i += input.len() - i - rest.len();
|
|
||||||
} else {
|
|
||||||
return debug!(Err(Error::new(format!(
|
|
||||||
"Unexpected input while parsing UID FETCH response. Could not parse ENVELOPE: \
|
|
||||||
{:.40}",
|
|
||||||
String::from_utf8_lossy(&input[i..])
|
|
||||||
))));
|
|
||||||
}
|
|
||||||
} else if input[i..].starts_with(b"BODYSTRUCTURE ") {
|
|
||||||
i += b"BODYSTRUCTURE ".len();
|
|
||||||
|
|
||||||
let (rest, _has_attachments) = bodystructure_has_attachments(&input[i..])?;
|
|
||||||
has_attachments = _has_attachments;
|
|
||||||
i += input[i..].len() - rest.len();
|
|
||||||
} else if input[i..].starts_with(b"BODY[HEADER.FIELDS (REFERENCES)] ") {
|
|
||||||
i += b"BODY[HEADER.FIELDS (REFERENCES)] ".len();
|
|
||||||
if let Ok((rest, mut references)) = astring_token(&input[i..]) {
|
|
||||||
if !references.trim().is_empty() {
|
|
||||||
if let Ok((_, (_, v))) = crate::email::parser::headers::header(references) {
|
|
||||||
references = v;
|
|
||||||
}
|
}
|
||||||
ret.references = Some(references);
|
|
||||||
|
Ok((remainder, ret, None))
|
||||||
}
|
}
|
||||||
i += input.len() - i - rest.len();
|
_ => todo!(),
|
||||||
} else {
|
|
||||||
return debug!(Err(Error::new(format!(
|
|
||||||
"Unexpected input while parsing UID FETCH response. Could not parse \
|
|
||||||
BODY[HEADER.FIELDS (REFERENCES)]: {:.40}",
|
|
||||||
String::from_utf8_lossy(&input[i..])
|
|
||||||
))));
|
|
||||||
}
|
}
|
||||||
} else if input[i..].starts_with(b"BODY[HEADER.FIELDS (\"REFERENCES\")] ") {
|
}
|
||||||
i += b"BODY[HEADER.FIELDS (\"REFERENCES\")] ".len();
|
Err(error) => {
|
||||||
if let Ok((rest, mut references)) = astring_token(&input[i..]) {
|
todo!()
|
||||||
if !references.trim().is_empty() {
|
|
||||||
if let Ok((_, (_, v))) = crate::email::parser::headers::header(references) {
|
|
||||||
references = v;
|
|
||||||
}
|
|
||||||
ret.references = Some(references);
|
|
||||||
}
|
|
||||||
i += input.len() - i - rest.len();
|
|
||||||
} else {
|
|
||||||
return debug!(Err(Error::new(format!(
|
|
||||||
"Unexpected input while parsing UID FETCH response. Could not parse \
|
|
||||||
BODY[HEADER.FIELDS (\"REFERENCES\"): {:.40}",
|
|
||||||
String::from_utf8_lossy(&input[i..])
|
|
||||||
))));
|
|
||||||
}
|
|
||||||
} else if input[i..].starts_with(b")\r\n") {
|
|
||||||
i += b")\r\n".len();
|
|
||||||
break;
|
|
||||||
} else {
|
|
||||||
debug!(
|
|
||||||
"Got unexpected token while parsing UID FETCH response:\n`{}`\n",
|
|
||||||
String::from_utf8_lossy(input)
|
|
||||||
);
|
|
||||||
return debug!(Err(Error::new(format!(
|
|
||||||
"Got unexpected token while parsing UID FETCH response: `{:.40}`",
|
|
||||||
String::from_utf8_lossy(&input[i..])
|
|
||||||
))));
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
ret.raw_fetch_value = &input[..i];
|
|
||||||
|
|
||||||
if let Some(env) = ret.envelope.as_mut() {
|
|
||||||
env.set_has_attachments(has_attachments);
|
|
||||||
}
|
|
||||||
|
|
||||||
Ok((&input[i..], ret, None))
|
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn fetch_responses(mut input: &[u8]) -> ImapParseResult<Vec<FetchResponse<'_>>> {
|
pub fn fetch_responses(mut input: &[u8]) -> ImapParseResult<Vec<FetchResponse<'_>>> {
|
||||||
|
@ -1028,7 +903,13 @@ fn test_imap_untagged_responses() {
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn test_imap_fetch_response() {
|
fn test_imap_fetch_response() {
|
||||||
let input: &[u8] = b"* 198 FETCH (UID 7608 FLAGS (\\Seen) ENVELOPE (\"Fri, 24 Jun 2011 10:09:10 +0000\" \"xxxx/xxxx\" ((\"xx@xx.com\" NIL \"xx\" \"xx.com\")) NIL NIL ((\"xx@xx\" NIL \"xx\" \"xx.com\")) ((\"'xx, xx'\" NIL \"xx.xx\" \"xx.com\") (\"xx.xx@xx.com\" NIL \"xx.xx\" \"xx.com\") (\"'xx'\" NIL \"xx.xx\" \"xx.com\") (\"'xx xx'\" NIL \"xx.xx\" \"xx.com\") (\"xx.xx@xx.com\" NIL \"xx.xx\" \"xx.com\")) NIL NIL \"<xx@xx.com>\") BODY[HEADER.FIELDS (REFERENCES)] {2}\r\n\r\nBODYSTRUCTURE ((\"text\" \"html\" (\"charset\" \"us-ascii\") \"<xx@xx>\" NIL \"7BIT\" 17236 232 NIL NIL NIL NIL)(\"image\" \"jpeg\" (\"name\" \"image001.jpg\") \"<image001.jpg@xx.xx>\" \"image001.jpg\" \"base64\" 1918 NIL (\"inline\" (\"filename\" \"image001.jpg\" \"size\" \"1650\" \"creation-date\" \"Sun, 09 Aug 2015 20:56:04 GMT\" \"modification-date\" \"Sun, 14 Aug 2022 22:11:45 GMT\")) NIL NIL) \"related\" (\"boundary\" \"xx--xx\" \"type\" \"text/html\") NIL \"en-US\"))\r\n";
|
#[rustfmt::skip]
|
||||||
|
let input: &[u8] = b"* 198 FETCH (UID 7608 FLAGS (\\Seen) ENVELOPE (\"Fri, 24 Jun 2011 10:09:10 +0000\" \"xxxx/xxxx\" ((\"xx@xx.com\" NIL \"xx\" \"xx.com\")) NIL NIL ((\"xx@xx\" NIL \"xx\" \"xx.com\")) ((\"'xx, xx'\" NIL \"xx.xx\" \"xx.com\")(\"xx.xx@xx.com\" NIL \"xx.xx\" \"xx.com\")(\"'xx'\" NIL \"xx.xx\" \"xx.com\")(\"'xx xx'\" NIL \"xx.xx\" \"xx.com\")(\"xx.xx@xx.com\" NIL \"xx.xx\" \"xx.com\")) NIL NIL \"<xx@xx.com>\") BODY[HEADER.FIELDS (REFERENCES)] {2}\r\n\r\n BODYSTRUCTURE ((\"text\" \"html\" (\"charset\" \"us-ascii\") \"<xx@xx>\" NIL \"7BIT\" 17236 232 NIL NIL NIL NIL)(\"image\" \"jpeg\" (\"name\" \"image001.jpg\") \"<image001.jpg@xx.xx>\" \"image001.jpg\" \"base64\" 1918 NIL (\"inline\" (\"filename\" \"image001.jpg\" \"size\" \"1650\" \"creation-date\" \"Sun, 09 Aug 2015 20:56:04 GMT\" \"modification-date\" \"Sun, 14 Aug 2022 22:11:45 GMT\")) NIL NIL) \"related\" (\"boundary\" \"xx--xx\" \"type\" \"text/html\") NIL \"en-US\"))\r\n";
|
||||||
|
#[rustfmt::skip]
|
||||||
|
// ----------------------------------- ------------- --------------------------------------- --- --- ----------------------------------- -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- --- --- ---------------
|
||||||
|
// date subject from | | to cc bcc irt message-id
|
||||||
|
// | reply-to
|
||||||
|
// sender
|
||||||
|
|
||||||
let mut address = SmallVec::new();
|
let mut address = SmallVec::new();
|
||||||
address.push(Address::new(None, "xx@xx.com".to_string()));
|
address.push(Address::new(None, "xx@xx.com".to_string()));
|
||||||
|
@ -1048,7 +929,7 @@ fn test_imap_fetch_response() {
|
||||||
flags: Some((Flag::SEEN, vec![])),
|
flags: Some((Flag::SEEN, vec![])),
|
||||||
modseq: None,
|
modseq: None,
|
||||||
body: None,
|
body: None,
|
||||||
references: None,
|
references: None, // TODO: Some?
|
||||||
envelope: Some(env),
|
envelope: Some(env),
|
||||||
raw_fetch_value: input,
|
raw_fetch_value: input,
|
||||||
},
|
},
|
||||||
|
|
|
@ -112,6 +112,7 @@ 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::*;
|
||||||
|
use imap_codec::message::{Flag as ImapCodecFlag, FlagFetch as ImapCodecFlagFetch};
|
||||||
pub use mailto::*;
|
pub use mailto::*;
|
||||||
use smallvec::SmallVec;
|
use smallvec::SmallVec;
|
||||||
|
|
||||||
|
@ -149,6 +150,21 @@ impl PartialEq<&str> for Flag {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
impl From<ImapCodecFlagFetch<'_>> for Flag {
|
||||||
|
fn from(value: ImapCodecFlagFetch) -> Self {
|
||||||
|
match value {
|
||||||
|
ImapCodecFlagFetch::Flag(flag) => match flag {
|
||||||
|
ImapCodecFlag::Seen => Flag::SEEN,
|
||||||
|
ImapCodecFlag::Draft => Flag::DRAFT,
|
||||||
|
ImapCodecFlag::Flagged => Flag::FLAGGED,
|
||||||
|
// TODO
|
||||||
|
_ => Flag::empty(),
|
||||||
|
},
|
||||||
|
ImapCodecFlagFetch::Recent => Flag::empty(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
macro_rules! flag_impl {
|
macro_rules! flag_impl {
|
||||||
(fn $name:ident, $val:expr) => {
|
(fn $name:ident, $val:expr) => {
|
||||||
pub const fn $name(&self) -> bool {
|
pub const fn $name(&self) -> bool {
|
||||||
|
@ -216,16 +232,20 @@ crate::declare_u64_hash!(EnvelopeHash);
|
||||||
/// bytes into an `Attachment` object.
|
/// bytes into an `Attachment` object.
|
||||||
#[derive(Clone, Serialize, Deserialize)]
|
#[derive(Clone, Serialize, Deserialize)]
|
||||||
pub struct Envelope {
|
pub struct Envelope {
|
||||||
pub hash: EnvelopeHash,
|
// ----- IMAP4rev1 -----
|
||||||
pub date: String,
|
pub date: String,
|
||||||
pub timestamp: UnixTimestamp,
|
pub subject: Option<String>,
|
||||||
pub from: SmallVec<[Address; 1]>,
|
pub from: SmallVec<[Address; 1]>,
|
||||||
|
// TODO: sender
|
||||||
|
// TODO: reply_to
|
||||||
pub to: SmallVec<[Address; 1]>,
|
pub to: SmallVec<[Address; 1]>,
|
||||||
pub cc: SmallVec<[Address; 1]>,
|
pub cc: SmallVec<[Address; 1]>,
|
||||||
pub bcc: Vec<Address>,
|
pub bcc: Vec<Address>,
|
||||||
pub subject: Option<String>,
|
|
||||||
pub message_id: MessageID,
|
|
||||||
pub in_reply_to: Option<MessageID>,
|
pub in_reply_to: Option<MessageID>,
|
||||||
|
pub message_id: MessageID,
|
||||||
|
// ----- Other -----
|
||||||
|
pub hash: EnvelopeHash,
|
||||||
|
pub timestamp: UnixTimestamp,
|
||||||
pub references: Option<References>,
|
pub references: Option<References>,
|
||||||
pub other_headers: HeaderMap,
|
pub other_headers: HeaderMap,
|
||||||
pub thread: ThreadNodeHash,
|
pub thread: ThreadNodeHash,
|
||||||
|
|
|
@ -615,6 +615,7 @@ 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();
|
||||||
|
#[rustfmt::skip]
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
s,
|
s,
|
||||||
"<!--\nDate: Sun, 16 Jun 2013 17:56:45 +0200\nFrom: \nTo: \nCc: \nBcc: \nSubject: \
|
"<!--\nDate: Sun, 16 Jun 2013 17:56:45 +0200\nFrom: \nTo: \nCc: \nBcc: \nSubject: \
|
||||||
|
|
|
@ -704,3 +704,17 @@ impl<'a> From<&'a Error> for Error {
|
||||||
kind.clone()
|
kind.clone()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// ----- imap-codec -----
|
||||||
|
|
||||||
|
impl From<imap_codec::core::LiteralError> for Error {
|
||||||
|
#[inline]
|
||||||
|
fn from(error: imap_codec::core::LiteralError) -> Error {
|
||||||
|
Error{
|
||||||
|
summary: error.to_string().into(),
|
||||||
|
details: None,
|
||||||
|
source: Some(Arc::new(error)),
|
||||||
|
kind: ErrorKind::Configuration,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
Loading…
Reference in New Issue