switch &[u8] parsers to using Bytes wrapper
This commit is contained in:
parent
5a387f7063
commit
9d31a13882
19 changed files with 913 additions and 359 deletions
88
Cargo.lock
generated
88
Cargo.lock
generated
|
@ -79,6 +79,19 @@ version = "1.0.0"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd"
|
checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "chrono"
|
||||||
|
version = "0.4.19"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "670ad68c9088c2a963aaa298cb369688cf3f9465ce5e2d4ca10e6e0098a1ce73"
|
||||||
|
dependencies = [
|
||||||
|
"libc",
|
||||||
|
"num-integer",
|
||||||
|
"num-traits",
|
||||||
|
"time",
|
||||||
|
"winapi",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "darling"
|
name = "darling"
|
||||||
version = "0.12.4"
|
version = "0.12.4"
|
||||||
|
@ -277,9 +290,9 @@ dependencies = [
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "js-sys"
|
name = "js-sys"
|
||||||
version = "0.3.51"
|
version = "0.3.52"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "83bdfbace3a0e81a4253f73b49e960b053e396a11012cbd49b9b74d6a2b67062"
|
checksum = "ce791b7ca6638aae45be056e068fc756d871eb3b3b10b8efa62d1c9cec616752"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"wasm-bindgen",
|
"wasm-bindgen",
|
||||||
]
|
]
|
||||||
|
@ -377,6 +390,25 @@ dependencies = [
|
||||||
"winapi",
|
"winapi",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "num-integer"
|
||||||
|
version = "0.1.44"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "d2cc698a63b549a70bc047073d2949cce27cd1c7b0a4a862d08a8031bc2801db"
|
||||||
|
dependencies = [
|
||||||
|
"autocfg",
|
||||||
|
"num-traits",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "num-traits"
|
||||||
|
version = "0.2.14"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "9a64b1ec5cda2586e284722486d802acf1f7dbdc623e2bfc57e65ca1cd099290"
|
||||||
|
dependencies = [
|
||||||
|
"autocfg",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "num_cpus"
|
name = "num_cpus"
|
||||||
version = "1.13.0"
|
version = "1.13.0"
|
||||||
|
@ -401,6 +433,7 @@ dependencies = [
|
||||||
"async-trait",
|
"async-trait",
|
||||||
"bitflags",
|
"bitflags",
|
||||||
"bytes",
|
"bytes",
|
||||||
|
"chrono",
|
||||||
"derive_builder",
|
"derive_builder",
|
||||||
"futures",
|
"futures",
|
||||||
"log",
|
"log",
|
||||||
|
@ -486,9 +519,9 @@ checksum = "941ba9d78d8e2f7ce474c015eea4d9c6d25b6a3327f9832ee29a4de27f91bbb8"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "redox_syscall"
|
name = "redox_syscall"
|
||||||
version = "0.2.9"
|
version = "0.2.10"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "5ab49abadf3f9e1c4bc499e8845e152ad87d2ad2d30371841171169e9d75feee"
|
checksum = "8383f39639269cde97d255a32bdb68c047337295414940c68bdd30c2e13203ff"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"bitflags",
|
"bitflags",
|
||||||
]
|
]
|
||||||
|
@ -554,9 +587,9 @@ dependencies = [
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "slab"
|
name = "slab"
|
||||||
version = "0.4.3"
|
version = "0.4.4"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "f173ac3d1a7e3b28003f40de0b5ce7fe2710f9b9dc3fc38664cebee46b3b6527"
|
checksum = "c307a32c1c5c437f38c7fd45d753050587732ba8628319fbdf12a7e289ccc590"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "smallvec"
|
name = "smallvec"
|
||||||
|
@ -599,6 +632,17 @@ 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 = "55937e1799185b12863d447f42597ed69d9928686b8d88a1df17376a097d8369"
|
checksum = "55937e1799185b12863d447f42597ed69d9928686b8d88a1df17376a097d8369"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "time"
|
||||||
|
version = "0.1.44"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "6db9e6914ab8b1ae1c260a4ae7a49b6c5611b40328a735b21862567685e73255"
|
||||||
|
dependencies = [
|
||||||
|
"libc",
|
||||||
|
"wasi",
|
||||||
|
"winapi",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "tokio"
|
name = "tokio"
|
||||||
version = "1.9.0"
|
version = "1.9.0"
|
||||||
|
@ -674,10 +718,16 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "5fecdca9a5291cc2b8dcf7dc02453fee791a280f3743cb0905f8822ae463b3fe"
|
checksum = "5fecdca9a5291cc2b8dcf7dc02453fee791a280f3743cb0905f8822ae463b3fe"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "wasm-bindgen"
|
name = "wasi"
|
||||||
version = "0.2.74"
|
version = "0.10.0+wasi-snapshot-preview1"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "d54ee1d4ed486f78874278e63e4069fc1ab9f6a18ca492076ffb90c5eb2997fd"
|
checksum = "1a143597ca7c7793eff794def352d41792a93c481eb1042423ff7ff72ba2c31f"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "wasm-bindgen"
|
||||||
|
version = "0.2.75"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "b608ecc8f4198fe8680e2ed18eccab5f0cd4caaf3d83516fa5fb2e927fda2586"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"cfg-if",
|
"cfg-if",
|
||||||
"wasm-bindgen-macro",
|
"wasm-bindgen-macro",
|
||||||
|
@ -685,9 +735,9 @@ dependencies = [
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "wasm-bindgen-backend"
|
name = "wasm-bindgen-backend"
|
||||||
version = "0.2.74"
|
version = "0.2.75"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "3b33f6a0694ccfea53d94db8b2ed1c3a8a4c86dd936b13b9f0a15ec4a451b900"
|
checksum = "580aa3a91a63d23aac5b6b267e2d13cb4f363e31dce6c352fca4752ae12e479f"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"bumpalo",
|
"bumpalo",
|
||||||
"lazy_static",
|
"lazy_static",
|
||||||
|
@ -700,9 +750,9 @@ dependencies = [
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "wasm-bindgen-macro"
|
name = "wasm-bindgen-macro"
|
||||||
version = "0.2.74"
|
version = "0.2.75"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "088169ca61430fe1e58b8096c24975251700e7b1f6fd91cc9d59b04fb9b18bd4"
|
checksum = "171ebf0ed9e1458810dfcb31f2e766ad6b3a89dbda42d8901f2b268277e5f09c"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"quote",
|
"quote",
|
||||||
"wasm-bindgen-macro-support",
|
"wasm-bindgen-macro-support",
|
||||||
|
@ -710,9 +760,9 @@ dependencies = [
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "wasm-bindgen-macro-support"
|
name = "wasm-bindgen-macro-support"
|
||||||
version = "0.2.74"
|
version = "0.2.75"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "be2241542ff3d9f241f5e2cb6dd09b37efe786df8851c54957683a49f0987a97"
|
checksum = "6c2657dd393f03aa2a659c25c6ae18a13a4048cebd220e147933ea837efc589f"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"proc-macro2",
|
"proc-macro2",
|
||||||
"quote",
|
"quote",
|
||||||
|
@ -723,15 +773,15 @@ dependencies = [
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "wasm-bindgen-shared"
|
name = "wasm-bindgen-shared"
|
||||||
version = "0.2.74"
|
version = "0.2.75"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "d7cff876b8f18eed75a66cf49b65e7f967cb354a7aa16003fb55dbfd25b44b4f"
|
checksum = "2e0c4a743a309662d45f4ede961d7afa4ba4131a59a639f29b0069c3798bbcc2"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "web-sys"
|
name = "web-sys"
|
||||||
version = "0.3.51"
|
version = "0.3.52"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "e828417b379f3df7111d3a2a9e5753706cae29c41f7c4029ee9fd77f3e09e582"
|
checksum = "01c70a82d842c9979078c772d4a1344685045f1a5628f677c2b2eab4dd7d2696"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"js-sys",
|
"js-sys",
|
||||||
"wasm-bindgen",
|
"wasm-bindgen",
|
||||||
|
|
|
@ -20,11 +20,15 @@ path = "bin/greenmail_test.rs"
|
||||||
default = ["rfc2177-idle"]
|
default = ["rfc2177-idle"]
|
||||||
rfc2177-idle = []
|
rfc2177-idle = []
|
||||||
|
|
||||||
|
[profile.release]
|
||||||
|
lto = true
|
||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
anyhow = "1.0.42"
|
anyhow = "1.0.42"
|
||||||
async-trait = "0.1.51"
|
async-trait = "0.1.51"
|
||||||
bitflags = "1.2.1"
|
bitflags = "1.2.1"
|
||||||
bytes = "1.0.1"
|
bytes = "1.0.1"
|
||||||
|
chrono = "0.4.19"
|
||||||
derive_builder = "0.10.2"
|
derive_builder = "0.10.2"
|
||||||
futures = "0.3.16"
|
futures = "0.3.16"
|
||||||
log = "0.4.14"
|
log = "0.4.14"
|
||||||
|
|
|
@ -1,14 +1,14 @@
|
||||||
use anyhow::Result;
|
use anyhow::Result;
|
||||||
use panorama_imap::client::ConfigBuilder;
|
// use panorama_imap::client::ConfigBuilder;
|
||||||
|
|
||||||
#[tokio::main]
|
#[tokio::main]
|
||||||
async fn main() -> Result<()> {
|
async fn main() -> Result<()> {
|
||||||
let _client = ConfigBuilder::default()
|
// let _client = ConfigBuilder::default()
|
||||||
.hostname(String::from("localhost"))
|
// .hostname(String::from("localhost"))
|
||||||
.port(3993)
|
// .port(3993)
|
||||||
.tls(true)
|
// .tls(true)
|
||||||
.connect()
|
// .connect()
|
||||||
.await?;
|
// .await?;
|
||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,59 +0,0 @@
|
||||||
use anyhow::Result;
|
|
||||||
|
|
||||||
use crate::command::Command;
|
|
||||||
use crate::response::{Response, ResponseDone, Status};
|
|
||||||
|
|
||||||
use super::{ClientAuthenticated, ClientUnauthenticated};
|
|
||||||
|
|
||||||
#[async_trait]
|
|
||||||
pub trait Auth {
|
|
||||||
/// Performs authentication, consuming the client
|
|
||||||
// TODO: return the unauthed client if failed?
|
|
||||||
async fn perform_auth(self, client: ClientUnauthenticated) -> Result<ClientAuthenticated>;
|
|
||||||
|
|
||||||
/// Converts the wrappers around the client once the authentication has happened. Should only
|
|
||||||
/// be called by the `perform_auth` function.
|
|
||||||
fn convert_client(client: ClientUnauthenticated) -> ClientAuthenticated {
|
|
||||||
match client {
|
|
||||||
ClientUnauthenticated::Encrypted(e) => ClientAuthenticated::Encrypted(e),
|
|
||||||
ClientUnauthenticated::Unencrypted(e) => ClientAuthenticated::Unencrypted(e),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub struct Plain {
|
|
||||||
pub username: String,
|
|
||||||
pub password: String,
|
|
||||||
}
|
|
||||||
|
|
||||||
#[async_trait]
|
|
||||||
impl Auth for Plain {
|
|
||||||
async fn perform_auth(self, mut client: ClientUnauthenticated) -> Result<ClientAuthenticated> {
|
|
||||||
let command = Command::Login {
|
|
||||||
username: self.username,
|
|
||||||
password: self.password,
|
|
||||||
};
|
|
||||||
|
|
||||||
let result = client.execute(command).await?;
|
|
||||||
let done = result.done().await?;
|
|
||||||
|
|
||||||
assert!(done.is_some());
|
|
||||||
let done = done.unwrap();
|
|
||||||
|
|
||||||
if done.status != Status::Ok {
|
|
||||||
bail!("unable to login: {:?}", done);
|
|
||||||
}
|
|
||||||
|
|
||||||
// if !matches!(
|
|
||||||
// result,
|
|
||||||
// Response::Done(ResponseDone {
|
|
||||||
// status: Status::Ok,
|
|
||||||
// ..
|
|
||||||
// })
|
|
||||||
// ) {
|
|
||||||
// bail!("unable to login: {:?}", result);
|
|
||||||
// }
|
|
||||||
|
|
||||||
Ok(<Self as Auth>::convert_client(client))
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,5 +1,6 @@
|
||||||
use std::borrow::Cow;
|
use std::pin::Pin;
|
||||||
use std::sync::Arc;
|
use std::sync::Arc;
|
||||||
|
use std::task::{Context, Poll};
|
||||||
|
|
||||||
use anyhow::Result;
|
use anyhow::Result;
|
||||||
use futures::{
|
use futures::{
|
||||||
|
@ -11,39 +12,45 @@ use tokio_rustls::{
|
||||||
client::TlsStream, rustls::ClientConfig as RustlsConfig, webpki::DNSNameRef, TlsConnector,
|
client::TlsStream, rustls::ClientConfig as RustlsConfig, webpki::DNSNameRef, TlsConnector,
|
||||||
};
|
};
|
||||||
|
|
||||||
pub use super::inner::{Client, ResponseStream};
|
use crate::proto::{
|
||||||
|
command::{
|
||||||
|
Command, CommandFetch, CommandList, CommandSearch, CommandSelect, FetchItems,
|
||||||
|
SearchCriteria,
|
||||||
|
},
|
||||||
|
response::{
|
||||||
|
Condition, Flag, Mailbox, MailboxData, MailboxList, MessageAttribute, Response,
|
||||||
|
ResponseCode, Status,
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
/// Struct used to start building the config for a client.
|
use super::inner::Inner;
|
||||||
///
|
use super::response_stream::ResponseStream;
|
||||||
/// Call [`.build`][1] to _build_ the config, then run [`.open`][2] to actually start opening
|
|
||||||
/// the connection to the server.
|
|
||||||
///
|
|
||||||
/// [1]: self::ClientConfigBuilder::build
|
|
||||||
/// [2]: self::ClientConfig::open
|
|
||||||
pub type ClientBuilder = ClientConfigBuilder;
|
|
||||||
|
|
||||||
/// An IMAP client that hasn't been connected yet.
|
/// An IMAP client that hasn't been connected yet.
|
||||||
#[derive(Builder, Clone, Debug)]
|
#[derive(Builder, Clone, Debug)]
|
||||||
pub struct ClientConfig {
|
#[builder(build_fn(private))]
|
||||||
|
pub struct Config {
|
||||||
/// The hostname of the IMAP server. If using TLS, must be an address
|
/// The hostname of the IMAP server. If using TLS, must be an address
|
||||||
hostname: String,
|
pub hostname: String,
|
||||||
|
|
||||||
/// The port of the IMAP server.
|
/// The port of the IMAP server.
|
||||||
port: u16,
|
pub port: u16,
|
||||||
|
|
||||||
/// Whether or not the client is using an encrypted stream.
|
/// Whether or not the client is using an encrypted stream.
|
||||||
///
|
///
|
||||||
/// To upgrade the connection later, use the upgrade method.
|
/// To upgrade the connection later, use the upgrade method.
|
||||||
tls: bool,
|
pub tls: bool,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl ClientConfig {
|
impl ConfigBuilder {
|
||||||
pub async fn open(self) -> Result<ClientUnauthenticated> {
|
pub async fn open(self) -> Result<ClientUnauthenticated> {
|
||||||
let hostname = self.hostname.as_ref();
|
let config = self.build()?;
|
||||||
let port = self.port;
|
|
||||||
|
let hostname = config.hostname.as_ref();
|
||||||
|
let port = config.port;
|
||||||
let conn = TcpStream::connect((hostname, port)).await?;
|
let conn = TcpStream::connect((hostname, port)).await?;
|
||||||
|
|
||||||
if self.tls {
|
if config.tls {
|
||||||
let mut tls_config = RustlsConfig::new();
|
let mut tls_config = RustlsConfig::new();
|
||||||
tls_config
|
tls_config
|
||||||
.root_store
|
.root_store
|
||||||
|
@ -52,11 +59,11 @@ impl ClientConfig {
|
||||||
let dnsname = DNSNameRef::try_from_ascii_str(hostname).unwrap();
|
let dnsname = DNSNameRef::try_from_ascii_str(hostname).unwrap();
|
||||||
let conn = tls_config.connect(dnsname, conn).await?;
|
let conn = tls_config.connect(dnsname, conn).await?;
|
||||||
|
|
||||||
let mut inner = Client::new(conn, self);
|
let mut inner = Inner::new(conn, config).await?;
|
||||||
inner.wait_for_greeting().await?;
|
inner.wait_for_greeting().await?;
|
||||||
return Ok(ClientUnauthenticated::Encrypted(inner));
|
return Ok(ClientUnauthenticated::Encrypted(inner));
|
||||||
} else {
|
} else {
|
||||||
let mut inner = Client::new(conn, self);
|
let mut inner = Inner::new(conn, config).await?;
|
||||||
inner.wait_for_greeting().await?;
|
inner.wait_for_greeting().await?;
|
||||||
return Ok(ClientUnauthenticated::Unencrypted(inner));
|
return Ok(ClientUnauthenticated::Unencrypted(inner));
|
||||||
}
|
}
|
||||||
|
@ -64,8 +71,8 @@ impl ClientConfig {
|
||||||
}
|
}
|
||||||
|
|
||||||
pub enum ClientUnauthenticated {
|
pub enum ClientUnauthenticated {
|
||||||
Encrypted(Client<TlsStream<TcpStream>>),
|
Encrypted(Inner<TlsStream<TcpStream>>),
|
||||||
Unencrypted(Client<TcpStream>),
|
Unencrypted(Inner<TcpStream>),
|
||||||
}
|
}
|
||||||
|
|
||||||
impl ClientUnauthenticated {
|
impl ClientUnauthenticated {
|
||||||
|
@ -79,36 +86,18 @@ impl ClientUnauthenticated {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Exposing low-level execute
|
client_expose!(async execute<'a>(cmd: Command<'a>) -> Result<ResponseStream>);
|
||||||
async fn execute(&mut self, cmd: Command) -> Result<ResponseStream> {
|
client_expose!(async has_capability(cap: impl AsRef<str>) -> Result<bool>);
|
||||||
match self {
|
|
||||||
ClientUnauthenticated::Encrypted(e) => e.execute(cmd).await,
|
|
||||||
ClientUnauthenticated::Unencrypted(e) => e.execute(cmd).await,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Checks if the server that the client is talking to has support for the given capability.
|
|
||||||
pub async fn has_capability(&mut self, cap: impl AsRef<str>) -> Result<bool> {
|
|
||||||
match self {
|
|
||||||
ClientUnauthenticated::Encrypted(e) => e.has_capability(cap).await,
|
|
||||||
ClientUnauthenticated::Unencrypted(e) => e.has_capability(cap).await,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
pub enum ClientAuthenticated {
|
pub enum ClientAuthenticated {
|
||||||
Encrypted(Client<TlsStream<TcpStream>>),
|
Encrypted(Inner<TlsStream<TcpStream>>),
|
||||||
Unencrypted(Client<TcpStream>),
|
Unencrypted(Inner<TcpStream>),
|
||||||
}
|
}
|
||||||
|
|
||||||
impl ClientAuthenticated {
|
impl ClientAuthenticated {
|
||||||
/// Exposing low-level execute
|
client_expose!(async execute<'a>(cmd: Command<'a>) -> Result<ResponseStream>);
|
||||||
async fn execute(&mut self, cmd: Command) -> Result<ResponseStream> {
|
client_expose!(async has_capability(cap: impl AsRef<str>) -> Result<bool>);
|
||||||
match self {
|
|
||||||
ClientAuthenticated::Encrypted(e) => e.execute(cmd).await,
|
|
||||||
ClientAuthenticated::Unencrypted(e) => e.execute(cmd).await,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fn sender(&self) -> mpsc::UnboundedSender<String> {
|
fn sender(&self) -> mpsc::UnboundedSender<String> {
|
||||||
match self {
|
match self {
|
||||||
|
@ -117,28 +106,20 @@ impl ClientAuthenticated {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Checks if the server that the client is talking to has support for the given capability.
|
|
||||||
pub async fn has_capability(&mut self, cap: impl AsRef<str>) -> Result<bool> {
|
|
||||||
match self {
|
|
||||||
ClientAuthenticated::Encrypted(e) => e.has_capability(cap).await,
|
|
||||||
ClientAuthenticated::Unencrypted(e) => e.has_capability(cap).await,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Runs the LIST command
|
/// Runs the LIST command
|
||||||
pub async fn list(&mut self) -> Result<Vec<String>> {
|
pub async fn list(&mut self) -> Result<Vec<Mailbox>> {
|
||||||
let cmd = Command::List {
|
let cmd = Command::List(CommandList {
|
||||||
reference: "".to_owned(),
|
reference: "",
|
||||||
mailbox: "*".to_owned(),
|
mailbox: "*",
|
||||||
};
|
});
|
||||||
|
|
||||||
let res = self.execute(cmd).await?;
|
let res = self.execute(cmd).await?;
|
||||||
let (_, data) = res.wait().await?;
|
let (_, data) = res.wait().await?;
|
||||||
|
|
||||||
let mut folders = Vec::new();
|
let mut folders = Vec::new();
|
||||||
for resp in data {
|
for resp in data {
|
||||||
if let Response::MailboxData(MailboxData::List { name, .. }) = resp {
|
if let Response::MailboxData(MailboxData::List(MailboxList { mailbox, .. })) = resp {
|
||||||
folders.push(name.to_owned());
|
folders.push(mailbox);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -147,9 +128,9 @@ impl ClientAuthenticated {
|
||||||
|
|
||||||
/// Runs the SELECT command
|
/// Runs the SELECT command
|
||||||
pub async fn select(&mut self, mailbox: impl AsRef<str>) -> Result<SelectResponse> {
|
pub async fn select(&mut self, mailbox: impl AsRef<str>) -> Result<SelectResponse> {
|
||||||
let cmd = Command::Select {
|
let cmd = Command::Select(CommandSelect {
|
||||||
mailbox: mailbox.as_ref().to_owned(),
|
mailbox: mailbox.as_ref(),
|
||||||
};
|
});
|
||||||
|
|
||||||
let stream = self.execute(cmd).await?;
|
let stream = self.execute(cmd).await?;
|
||||||
let (_, data) = stream.wait().await?;
|
let (_, data) = stream.wait().await?;
|
||||||
|
@ -160,11 +141,14 @@ impl ClientAuthenticated {
|
||||||
Response::MailboxData(MailboxData::Flags(flags)) => select.flags = flags,
|
Response::MailboxData(MailboxData::Flags(flags)) => select.flags = flags,
|
||||||
Response::MailboxData(MailboxData::Exists(exists)) => select.exists = Some(exists),
|
Response::MailboxData(MailboxData::Exists(exists)) => select.exists = Some(exists),
|
||||||
Response::MailboxData(MailboxData::Recent(recent)) => select.recent = Some(recent),
|
Response::MailboxData(MailboxData::Recent(recent)) => select.recent = Some(recent),
|
||||||
Response::Data(ResponseData {
|
Response::Tagged(
|
||||||
status: Status::Ok,
|
_,
|
||||||
code: Some(code),
|
Condition {
|
||||||
..
|
status: Status::Ok,
|
||||||
}) => match code {
|
code: Some(code),
|
||||||
|
..
|
||||||
|
},
|
||||||
|
) => match code {
|
||||||
ResponseCode::Unseen(value) => select.unseen = Some(value),
|
ResponseCode::Unseen(value) => select.unseen = Some(value),
|
||||||
ResponseCode::UidNext(value) => select.uid_next = Some(value),
|
ResponseCode::UidNext(value) => select.uid_next = Some(value),
|
||||||
ResponseCode::UidValidity(value) => select.uid_validity = Some(value),
|
ResponseCode::UidValidity(value) => select.uid_validity = Some(value),
|
||||||
|
@ -179,9 +163,9 @@ impl ClientAuthenticated {
|
||||||
|
|
||||||
/// Runs the SEARCH command
|
/// Runs the SEARCH command
|
||||||
pub async fn uid_search(&mut self) -> Result<Vec<u32>> {
|
pub async fn uid_search(&mut self) -> Result<Vec<u32>> {
|
||||||
let cmd = Command::UidSearch {
|
let cmd = Command::UidSearch(CommandSearch {
|
||||||
criteria: SearchCriteria::All,
|
criteria: SearchCriteria::All,
|
||||||
};
|
});
|
||||||
let stream = self.execute(cmd).await?;
|
let stream = self.execute(cmd).await?;
|
||||||
let (_, data) = stream.wait().await?;
|
let (_, data) = stream.wait().await?;
|
||||||
for resp in data {
|
for resp in data {
|
||||||
|
@ -197,12 +181,12 @@ impl ClientAuthenticated {
|
||||||
&mut self,
|
&mut self,
|
||||||
uids: &[u32],
|
uids: &[u32],
|
||||||
items: FetchItems,
|
items: FetchItems,
|
||||||
) -> Result<impl Stream<Item = (u32, Vec<AttributeValue>)>> {
|
) -> Result<impl Stream<Item = (u32, Vec<MessageAttribute>)>> {
|
||||||
let cmd = Command::Fetch {
|
let cmd = Command::Fetch(CommandFetch {
|
||||||
uids: uids.to_vec(),
|
ids: uids.to_vec(),
|
||||||
items,
|
items,
|
||||||
};
|
});
|
||||||
debug!("fetch: {}", cmd);
|
debug!("fetch: {:?}", cmd);
|
||||||
let stream = self.execute(cmd).await?;
|
let stream = self.execute(cmd).await?;
|
||||||
// let (done, data) = stream.wait().await?;
|
// let (done, data) = stream.wait().await?;
|
||||||
Ok(stream.filter_map(|resp| match resp {
|
Ok(stream.filter_map(|resp| match resp {
|
||||||
|
@ -217,12 +201,12 @@ impl ClientAuthenticated {
|
||||||
&mut self,
|
&mut self,
|
||||||
uids: &[u32],
|
uids: &[u32],
|
||||||
items: FetchItems,
|
items: FetchItems,
|
||||||
) -> Result<impl Stream<Item = (u32, Vec<AttributeValue>)>> {
|
) -> Result<impl Stream<Item = (u32, Vec<MessageAttribute>)>> {
|
||||||
let cmd = Command::UidFetch {
|
let cmd = Command::UidFetch(CommandFetch {
|
||||||
uids: uids.to_vec(),
|
ids: uids.to_vec(),
|
||||||
items,
|
items,
|
||||||
};
|
});
|
||||||
debug!("uid fetch: {}", cmd);
|
debug!("uid fetch: {:?}", cmd);
|
||||||
let stream = self.execute(cmd).await?;
|
let stream = self.execute(cmd).await?;
|
||||||
// let (done, data) = stream.wait().await?;
|
// let (done, data) = stream.wait().await?;
|
||||||
Ok(stream.filter_map(|resp| match resp {
|
Ok(stream.filter_map(|resp| match resp {
|
||||||
|
@ -244,8 +228,8 @@ impl ClientAuthenticated {
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug, Default)]
|
#[derive(Debug, Default)]
|
||||||
pub struct SelectResponse<'a> {
|
pub struct SelectResponse {
|
||||||
pub flags: Vec<Cow<'a, str>>,
|
pub flags: Vec<Flag>,
|
||||||
pub exists: Option<u32>,
|
pub exists: Option<u32>,
|
||||||
pub recent: Option<u32>,
|
pub recent: Option<u32>,
|
||||||
pub uid_next: Option<u32>,
|
pub uid_next: Option<u32>,
|
||||||
|
@ -255,8 +239,8 @@ pub struct SelectResponse<'a> {
|
||||||
|
|
||||||
/// A token that represents an idling connection.
|
/// A token that represents an idling connection.
|
||||||
///
|
///
|
||||||
/// Dropping this token indicates that the idling should be completed, and the DONE command will be
|
/// Dropping this token indicates that the idling should be completed, and the
|
||||||
/// sent to the server as a result.
|
/// DONE command will be sent to the server as a result.
|
||||||
#[cfg(feature = "rfc2177-idle")]
|
#[cfg(feature = "rfc2177-idle")]
|
||||||
#[cfg_attr(docsrs, doc(cfg(feature = "rfc2177-idle")))]
|
#[cfg_attr(docsrs, doc(cfg(feature = "rfc2177-idle")))]
|
||||||
pub struct IdleToken {
|
pub struct IdleToken {
|
|
@ -1,63 +1,127 @@
|
||||||
|
use std::sync::atomic::AtomicU32;
|
||||||
|
|
||||||
use anyhow::Result;
|
use anyhow::Result;
|
||||||
use futures::{future::FutureExt, stream::StreamExt};
|
use futures::{
|
||||||
|
future::{FutureExt, TryFutureExt},
|
||||||
|
stream::StreamExt,
|
||||||
|
};
|
||||||
use tokio::{
|
use tokio::{
|
||||||
io::{split, AsyncRead, AsyncWrite, ReadHalf, WriteHalf},
|
io::{split, AsyncRead, AsyncWrite, AsyncWriteExt, ReadHalf, WriteHalf},
|
||||||
sync::oneshot,
|
sync::{mpsc, oneshot},
|
||||||
task::JoinHandle,
|
task::JoinHandle,
|
||||||
};
|
};
|
||||||
use tokio_rustls::client::TlsStream;
|
use tokio_rustls::client::TlsStream;
|
||||||
use tokio_util::codec::FramedRead;
|
use tokio_util::codec::FramedRead;
|
||||||
|
|
||||||
use crate::codec::ImapCodec;
|
use crate::codec::ImapCodec;
|
||||||
use crate::proto::command::Command;
|
use crate::proto::{
|
||||||
|
bytes::Bytes, command::Command, response::Response, rfc3501::capability as parse_capability,
|
||||||
|
};
|
||||||
|
|
||||||
|
use super::client::Config;
|
||||||
|
use super::response_stream::ResponseStream;
|
||||||
use super::upgrade::upgrade;
|
use super::upgrade::upgrade;
|
||||||
|
|
||||||
type ExitSender = oneshot::Sender<()>;
|
type ExitSender = oneshot::Sender<()>;
|
||||||
type ExitListener = oneshot::Receiver<()>;
|
type ExitListener = oneshot::Receiver<()>;
|
||||||
|
type GreetingSender = oneshot::Sender<()>;
|
||||||
|
type GreetingWaiter = oneshot::Receiver<()>;
|
||||||
|
|
||||||
pub struct Inner<C> {
|
pub struct Inner<C> {
|
||||||
|
config: Config,
|
||||||
|
tag_number: AtomicU32,
|
||||||
|
|
||||||
read_exit: ExitSender,
|
read_exit: ExitSender,
|
||||||
read_handle: JoinHandle<ReadHalf<C>>,
|
read_handle: JoinHandle<ReadHalf<C>>,
|
||||||
|
|
||||||
write_half: WriteHalf<C>,
|
write_exit: ExitSender,
|
||||||
|
pub(crate) write_tx: mpsc::UnboundedSender<String>,
|
||||||
|
write_handle: JoinHandle<WriteHalf<C>>,
|
||||||
|
|
||||||
|
greeting_rx: Option<GreetingWaiter>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<C> Inner<C>
|
impl<C> Inner<C>
|
||||||
where
|
where
|
||||||
C: AsyncRead + AsyncWrite + Unpin + Send + 'static,
|
C: AsyncRead + AsyncWrite + Unpin + Send + 'static,
|
||||||
{
|
{
|
||||||
pub async fn open(c: C) -> Result<Self> {
|
pub async fn new(c: C, config: Config) -> Result<Self> {
|
||||||
// break the stream of bytes into a reader and a writer
|
// break the stream of bytes into a reader and a writer
|
||||||
// the read_half represents the server->client connection
|
// the read_half represents the server->client connection
|
||||||
// the write_half represents the client->server connection
|
// the write_half represents the client->server connection
|
||||||
let (read_half, write_half) = split(c);
|
let (read_half, write_half) = split(c);
|
||||||
|
|
||||||
|
// this channel is used to inform clients when we receive the
|
||||||
|
// initial greeting from the server
|
||||||
|
let (greeting_tx, greeting_rx) = oneshot::channel();
|
||||||
|
|
||||||
// spawn the server->client loop
|
// spawn the server->client loop
|
||||||
let (read_exit, exit_rx) = oneshot::channel();
|
let (read_exit, exit_rx) = oneshot::channel();
|
||||||
let read_handle = tokio::spawn(read_loop(read_half, exit_rx));
|
let read_handle = tokio::spawn(read_loop(read_half, exit_rx));
|
||||||
|
|
||||||
|
// spawn the client->server loop
|
||||||
|
let (write_exit, exit_rx) = oneshot::channel();
|
||||||
|
// TODO: maybe an arbitrary/configurable limit here would be better?
|
||||||
|
let (write_tx, write_rx) = mpsc::unbounded_channel();
|
||||||
|
let write_handle = tokio::spawn(write_loop(write_half, exit_rx, write_rx));
|
||||||
|
|
||||||
|
let tag_number = AtomicU32::new(0);
|
||||||
Ok(Inner {
|
Ok(Inner {
|
||||||
|
config,
|
||||||
|
tag_number,
|
||||||
read_exit,
|
read_exit,
|
||||||
read_handle,
|
read_handle,
|
||||||
write_half,
|
write_exit,
|
||||||
|
write_tx,
|
||||||
|
write_handle,
|
||||||
|
greeting_rx: Some(greeting_rx),
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
pub async fn execute<'a>(&mut self, _command: Command<'a>) {}
|
pub async fn execute<'a>(&mut self, _command: Command<'a>) -> Result<ResponseStream> { todo!() }
|
||||||
|
|
||||||
|
pub async fn has_capability(&mut self, cap: impl AsRef<str>) -> Result<bool> {
|
||||||
|
// TODO: cache capabilities if needed?
|
||||||
|
let cap = cap.as_ref().to_owned();
|
||||||
|
let (_, cap) = parse_capability(Bytes::from(cap))?;
|
||||||
|
|
||||||
|
let resp = self.execute(Command::Capability).await?;
|
||||||
|
let (_, data) = resp.wait().await?;
|
||||||
|
|
||||||
|
for resp in data {
|
||||||
|
if let Response::Capabilities(caps) = resp {
|
||||||
|
return Ok(caps.contains(&cap));
|
||||||
|
}
|
||||||
|
// debug!("cap: {:?}", resp);
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(false)
|
||||||
|
}
|
||||||
|
|
||||||
pub async fn upgrade(self) -> Result<Inner<TlsStream<C>>> {
|
pub async fn upgrade(self) -> Result<Inner<TlsStream<C>>> {
|
||||||
// TODO: check that this capability exists??
|
// TODO: check that this capability exists??
|
||||||
|
// TODO: issue the STARTTLS command to the server
|
||||||
|
|
||||||
// issue exit to the read loop and retrieve the read half
|
// issue exit to the read loop and retrieve the read half
|
||||||
let _ = self.read_exit.send(());
|
let _ = self.read_exit.send(());
|
||||||
let read_half = self.read_handle.await?;
|
let read_half = self.read_handle.await?;
|
||||||
let write_half = self.write_half;
|
|
||||||
|
// issue exit to the write loop and retrieve the write half
|
||||||
|
let _ = self.write_exit.send(());
|
||||||
|
let write_half = self.write_handle.await?;
|
||||||
|
|
||||||
// put the read half and write half back together
|
// put the read half and write half back together
|
||||||
let stream = read_half.unsplit(write_half);
|
let stream = read_half.unsplit(write_half);
|
||||||
let tls_stream = upgrade(stream, "hellosu").await?;
|
let tls_stream = upgrade(stream, &self.config.hostname).await?;
|
||||||
|
|
||||||
Inner::open(tls_stream).await
|
Inner::new(tls_stream, self.config).await
|
||||||
|
}
|
||||||
|
|
||||||
|
pub async fn wait_for_greeting(&mut self) -> Result<()> {
|
||||||
|
if let Some(greeting_rx) = self.greeting_rx.take() {
|
||||||
|
greeting_rx.await?;
|
||||||
|
}
|
||||||
|
Ok(())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -89,3 +153,32 @@ where
|
||||||
|
|
||||||
framed.into_inner()
|
framed.into_inner()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async fn write_loop<C>(
|
||||||
|
mut stream: WriteHalf<C>,
|
||||||
|
exit_rx: ExitListener,
|
||||||
|
mut write_rx: mpsc::UnboundedReceiver<String>,
|
||||||
|
) -> WriteHalf<C>
|
||||||
|
where
|
||||||
|
C: AsyncWrite,
|
||||||
|
{
|
||||||
|
let mut exit_rx = exit_rx.map_err(|_| ()).shared();
|
||||||
|
loop {
|
||||||
|
let write_fut = write_rx.recv().fuse();
|
||||||
|
pin_mut!(write_fut);
|
||||||
|
|
||||||
|
select! {
|
||||||
|
line = write_fut => {
|
||||||
|
if let Some(line) = line {
|
||||||
|
// TODO: handle errors here
|
||||||
|
stream.write_all(line.as_bytes()).await;
|
||||||
|
stream.flush().await;
|
||||||
|
trace!("C>>>S: {:?}", line);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
_ = exit_rx => break,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
stream
|
||||||
|
}
|
||||||
|
|
17
imap/src/client/macros.rs
Normal file
17
imap/src/client/macros.rs
Normal file
|
@ -0,0 +1,17 @@
|
||||||
|
macro_rules! client_expose {
|
||||||
|
(
|
||||||
|
async
|
||||||
|
$fn_name:ident
|
||||||
|
$(< $($lifetime:lifetime)* >)?
|
||||||
|
( $($arg_name:ident : $ty:ty),* $(,)? )
|
||||||
|
$(-> $ret:ty)?
|
||||||
|
) => {
|
||||||
|
#[allow(dead_code)]
|
||||||
|
async fn $fn_name $(< $($lifetime)* >)? (&mut self, $($arg_name : $ty,)*) $(-> $ret)? {
|
||||||
|
match self {
|
||||||
|
Self::Encrypted(inner) => inner.$fn_name($($arg_name,)*).await,
|
||||||
|
Self::Unencrypted(inner) => inner.$fn_name($($arg_name,)*).await,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
|
@ -1,20 +1,8 @@
|
||||||
|
#[macro_use]
|
||||||
|
mod macros;
|
||||||
|
|
||||||
pub mod auth;
|
pub mod auth;
|
||||||
|
pub mod client;
|
||||||
pub mod inner;
|
pub mod inner;
|
||||||
|
pub mod response_stream;
|
||||||
pub mod upgrade;
|
pub mod upgrade;
|
||||||
|
|
||||||
use anyhow::Result;
|
|
||||||
|
|
||||||
#[derive(Clone, Debug, Builder)]
|
|
||||||
#[builder(build_fn(skip))]
|
|
||||||
pub struct Config {
|
|
||||||
// (required for TLS)
|
|
||||||
hostname: String,
|
|
||||||
port: u16,
|
|
||||||
tls: bool,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl ConfigBuilder {
|
|
||||||
pub async fn connect(&self) -> Result<Client> { todo!() }
|
|
||||||
}
|
|
||||||
|
|
||||||
pub struct Client;
|
|
||||||
|
|
48
imap/src/client/response_stream.rs
Normal file
48
imap/src/client/response_stream.rs
Normal file
|
@ -0,0 +1,48 @@
|
||||||
|
use std::pin::Pin;
|
||||||
|
use std::task::{Context, Poll};
|
||||||
|
|
||||||
|
use anyhow::Result;
|
||||||
|
use futures::stream::Stream;
|
||||||
|
use tokio::sync::mpsc;
|
||||||
|
|
||||||
|
use crate::proto::response::{Response, ResponseDone};
|
||||||
|
|
||||||
|
/// A series of responses that follow an
|
||||||
|
pub struct ResponseStream {
|
||||||
|
pub(crate) inner: mpsc::UnboundedReceiver<Response>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl ResponseStream {
|
||||||
|
/// Retrieves just the DONE item in the stream, discarding the rest
|
||||||
|
pub async fn done(mut self) -> Result<Option<ResponseDone>> {
|
||||||
|
while let Some(resp) = self.inner.recv().await {
|
||||||
|
if let Response::Done(done) = resp {
|
||||||
|
return Ok(Some(done));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Ok(None)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Waits for the entire stream to finish, returning the DONE status and the
|
||||||
|
/// stream
|
||||||
|
pub async fn wait(mut self) -> Result<(Option<ResponseDone>, Vec<Response>)> {
|
||||||
|
let mut done = None;
|
||||||
|
let mut vec = Vec::new();
|
||||||
|
while let Some(resp) = self.inner.recv().await {
|
||||||
|
if let Response::Done(d) = resp {
|
||||||
|
done = Some(d);
|
||||||
|
break;
|
||||||
|
} else {
|
||||||
|
vec.push(resp);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Ok((done, vec))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Stream for ResponseStream {
|
||||||
|
type Item = Response;
|
||||||
|
fn poll_next(mut self: Pin<&mut Self>, cx: &mut Context) -> Poll<Option<Self::Item>> {
|
||||||
|
self.inner.poll_recv(cx)
|
||||||
|
}
|
||||||
|
}
|
|
@ -1,13 +1,9 @@
|
||||||
use std::io;
|
use std::io;
|
||||||
|
|
||||||
use bytes::{Bytes, BytesMut};
|
use bytes::BytesMut;
|
||||||
|
|
||||||
use tokio_util::codec::{Decoder, Encoder};
|
use tokio_util::codec::{Decoder, Encoder};
|
||||||
|
|
||||||
use crate::proto::{
|
use crate::proto::{command::Command, response::Response};
|
||||||
command::Command,
|
|
||||||
response::{Response, ResponseDone, Tag},
|
|
||||||
};
|
|
||||||
|
|
||||||
#[derive(Default)]
|
#[derive(Default)]
|
||||||
pub struct ImapCodec {
|
pub struct ImapCodec {
|
||||||
|
@ -15,7 +11,7 @@ pub struct ImapCodec {
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<'a> Decoder for ImapCodec {
|
impl<'a> Decoder for ImapCodec {
|
||||||
type Item = OwnedResponse;
|
type Item = Response;
|
||||||
type Error = io::Error;
|
type Error = io::Error;
|
||||||
fn decode(&mut self, buf: &mut BytesMut) -> Result<Option<Self::Item>, io::Error> {
|
fn decode(&mut self, buf: &mut BytesMut) -> Result<Option<Self::Item>, io::Error> {
|
||||||
if self.decode_need_message_bytes > buf.len() {
|
if self.decode_need_message_bytes > buf.len() {
|
||||||
|
@ -64,29 +60,3 @@ impl<'a> Encoder<&'a Command<'a>> for ImapCodec {
|
||||||
// Ok(())
|
// Ok(())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug)]
|
|
||||||
pub struct OwnedResponse {
|
|
||||||
raw: Bytes,
|
|
||||||
// This reference is really scoped to the lifetime of the `raw`
|
|
||||||
// member, but unfortunately Rust does not allow that yet. It
|
|
||||||
// is transmuted to `'static` by the `Decoder`, instead, and
|
|
||||||
// references returned to callers of `ResponseData` are limited
|
|
||||||
// to the lifetime of the `ResponseData` struct.
|
|
||||||
//
|
|
||||||
// `raw` is never mutated during the lifetime of `ResponseData`,
|
|
||||||
// and `Response` does not not implement any specific drop glue.
|
|
||||||
response: Response<'static>,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl OwnedResponse {
|
|
||||||
pub fn _request_id(&self) -> Option<&Tag> {
|
|
||||||
match self.response {
|
|
||||||
Response::Done(ResponseDone { ref tag, .. }) => Some(tag),
|
|
||||||
_ => None,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[allow(clippy::needless_lifetimes)]
|
|
||||||
pub fn _parsed<'a>(&'a self) -> &'a Response<'a> { &self.response }
|
|
||||||
}
|
|
||||||
|
|
|
@ -1,8 +1,8 @@
|
||||||
// #[macro_use]
|
#[macro_use]
|
||||||
extern crate anyhow;
|
extern crate anyhow;
|
||||||
#[macro_use]
|
#[macro_use]
|
||||||
extern crate async_trait;
|
extern crate async_trait;
|
||||||
// #[macro_use]
|
#[macro_use]
|
||||||
extern crate log;
|
extern crate log;
|
||||||
#[macro_use]
|
#[macro_use]
|
||||||
extern crate futures;
|
extern crate futures;
|
||||||
|
|
230
imap/src/proto/bytes.rs
Normal file
230
imap/src/proto/bytes.rs
Normal file
|
@ -0,0 +1,230 @@
|
||||||
|
use std::ops::{Deref, RangeBounds};
|
||||||
|
|
||||||
|
use nom::{
|
||||||
|
error::{ErrorKind, ParseError},
|
||||||
|
CompareResult, Err, IResult, InputLength, Needed,
|
||||||
|
};
|
||||||
|
|
||||||
|
/// Glue code between nom and Bytes so they work together
|
||||||
|
#[derive(Clone, Debug, Default, PartialEq, Eq)]
|
||||||
|
pub struct Bytes(bytes::Bytes);
|
||||||
|
|
||||||
|
impl Bytes {
|
||||||
|
pub fn len(&self) -> usize { self.0.len() }
|
||||||
|
}
|
||||||
|
|
||||||
|
impl From<bytes::Bytes> for Bytes {
|
||||||
|
fn from(b: bytes::Bytes) -> Self { Bytes(b) }
|
||||||
|
}
|
||||||
|
|
||||||
|
impl From<&'static [u8]> for Bytes {
|
||||||
|
fn from(slice: &'static [u8]) -> Self { Bytes(bytes::Bytes::from(slice)) }
|
||||||
|
}
|
||||||
|
|
||||||
|
impl From<String> for Bytes {
|
||||||
|
fn from(slice: String) -> Self { Bytes(bytes::Bytes::from(slice)) }
|
||||||
|
}
|
||||||
|
|
||||||
|
pub trait ShitNeededForParsing: Sized {
|
||||||
|
type Item;
|
||||||
|
type Sliced;
|
||||||
|
fn slice<R: RangeBounds<usize>>(&self, range: R) -> Self::Sliced;
|
||||||
|
|
||||||
|
fn first(&self) -> Option<Self::Item>;
|
||||||
|
fn slice_index(&self, count: usize) -> Result<usize, Needed>;
|
||||||
|
|
||||||
|
// InputTake
|
||||||
|
fn take(&self, count: usize) -> Self;
|
||||||
|
fn take_split(&self, count: usize) -> (Self, Self);
|
||||||
|
|
||||||
|
// InputTakeAtPosition
|
||||||
|
fn split_at_position<P, E: ParseError<Self>>(&self, predicate: P) -> IResult<Self, Self, E>
|
||||||
|
where
|
||||||
|
P: Fn(Self::Item) -> bool;
|
||||||
|
fn split_at_position1<P, E: ParseError<Self>>(
|
||||||
|
&self,
|
||||||
|
predicate: P,
|
||||||
|
e: ErrorKind,
|
||||||
|
) -> IResult<Self, Self, E>
|
||||||
|
where
|
||||||
|
P: Fn(Self::Item) -> bool;
|
||||||
|
fn split_at_position_complete<P, E: ParseError<Self>>(
|
||||||
|
&self,
|
||||||
|
predicate: P,
|
||||||
|
) -> IResult<Self, Self, E>
|
||||||
|
where
|
||||||
|
P: Fn(Self::Item) -> bool;
|
||||||
|
fn split_at_position1_complete<P, E: ParseError<Self>>(
|
||||||
|
&self,
|
||||||
|
predicate: P,
|
||||||
|
e: ErrorKind,
|
||||||
|
) -> IResult<Self, Self, E>
|
||||||
|
where
|
||||||
|
P: Fn(Self::Item) -> bool;
|
||||||
|
}
|
||||||
|
|
||||||
|
impl ShitNeededForParsing for Bytes {
|
||||||
|
type Item = u8;
|
||||||
|
|
||||||
|
type Sliced = Bytes;
|
||||||
|
fn slice<R: RangeBounds<usize>>(&self, range: R) -> Self::Sliced { Self(self.0.slice(range)) }
|
||||||
|
|
||||||
|
fn first(&self) -> Option<Self::Item> { self.0.first().copied() }
|
||||||
|
fn slice_index(&self, count: usize) -> Result<usize, Needed> {
|
||||||
|
if self.len() >= count {
|
||||||
|
Ok(count)
|
||||||
|
} else {
|
||||||
|
Err(Needed::new(count - self.len()))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// InputTake
|
||||||
|
fn take(&self, count: usize) -> Self { self.slice(..count) }
|
||||||
|
fn take_split(&self, count: usize) -> (Self, Self) {
|
||||||
|
let mut prefix = self.clone();
|
||||||
|
let suffix = Self(prefix.0.split_off(count));
|
||||||
|
(suffix, prefix)
|
||||||
|
}
|
||||||
|
|
||||||
|
// InputTakeAtPosition
|
||||||
|
fn split_at_position<P, E: ParseError<Self>>(&self, predicate: P) -> IResult<Self, Self, E>
|
||||||
|
where
|
||||||
|
P: Fn(Self::Item) -> bool,
|
||||||
|
{
|
||||||
|
match (0..self.len()).find(|b| predicate(self[*b])) {
|
||||||
|
Some(i) => Ok((self.slice(i..), self.slice(..i))),
|
||||||
|
None => Err(Err::Incomplete(Needed::new(1))),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn split_at_position1<P, E: ParseError<Self>>(
|
||||||
|
&self,
|
||||||
|
predicate: P,
|
||||||
|
e: ErrorKind,
|
||||||
|
) -> IResult<Self, Self, E>
|
||||||
|
where
|
||||||
|
P: Fn(Self::Item) -> bool,
|
||||||
|
{
|
||||||
|
match (0..self.len()).find(|b| predicate(self[*b])) {
|
||||||
|
Some(0) => Err(Err::Error(E::from_error_kind(self.clone(), e))),
|
||||||
|
Some(i) => Ok((self.slice(i..), self.slice(..i))),
|
||||||
|
None => Err(Err::Incomplete(Needed::new(1))),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn split_at_position_complete<P, E: ParseError<Self>>(
|
||||||
|
&self,
|
||||||
|
predicate: P,
|
||||||
|
) -> IResult<Self, Self, E>
|
||||||
|
where
|
||||||
|
P: Fn(Self::Item) -> bool,
|
||||||
|
{
|
||||||
|
match (0..self.len()).find(|b| predicate(self[*b])) {
|
||||||
|
Some(i) => Ok((self.slice(i..), self.slice(..i))),
|
||||||
|
None => Ok(self.take_split(self.input_len())),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn split_at_position1_complete<P, E: ParseError<Self>>(
|
||||||
|
&self,
|
||||||
|
predicate: P,
|
||||||
|
e: ErrorKind,
|
||||||
|
) -> IResult<Self, Self, E>
|
||||||
|
where
|
||||||
|
P: Fn(Self::Item) -> bool,
|
||||||
|
{
|
||||||
|
match (0..self.len()).find(|b| predicate(self[*b])) {
|
||||||
|
Some(0) => Err(Err::Error(E::from_error_kind(self.clone(), e))),
|
||||||
|
Some(i) => Ok((self.slice(i..), self.slice(..i))),
|
||||||
|
None => {
|
||||||
|
if self.is_empty() {
|
||||||
|
Err(Err::Error(E::from_error_kind(self.clone(), e)))
|
||||||
|
} else {
|
||||||
|
Ok(self.take_split(self.input_len()))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub trait ShitCompare<T> {
|
||||||
|
fn compare(&self, t: T) -> CompareResult;
|
||||||
|
fn compare_no_case(&self, t: T) -> CompareResult;
|
||||||
|
}
|
||||||
|
|
||||||
|
impl ShitCompare<&[u8]> for Bytes {
|
||||||
|
fn compare(&self, other: &[u8]) -> CompareResult {
|
||||||
|
match self.iter().zip(other.iter()).any(|(a, b)| a != b) {
|
||||||
|
true => CompareResult::Error,
|
||||||
|
false if self.len() < other.len() => CompareResult::Incomplete,
|
||||||
|
false => CompareResult::Ok,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
fn compare_no_case(&self, other: &[u8]) -> CompareResult {
|
||||||
|
match self
|
||||||
|
.iter()
|
||||||
|
.zip(other.iter())
|
||||||
|
.any(|(a, b)| (a & 0x20) != (b & 0x20))
|
||||||
|
{
|
||||||
|
true => CompareResult::Error,
|
||||||
|
false if self.len() < other.len() => CompareResult::Incomplete,
|
||||||
|
false => CompareResult::Ok,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
macro_rules! array_impls {
|
||||||
|
($($N:expr)+) => { $(
|
||||||
|
impl ShitCompare<[u8; $N]> for Bytes {
|
||||||
|
#[inline(always)]
|
||||||
|
fn compare(&self, t: [u8; $N]) -> CompareResult {
|
||||||
|
self.compare(&t[..])
|
||||||
|
}
|
||||||
|
|
||||||
|
#[inline(always)]
|
||||||
|
fn compare_no_case(&self, t: [u8;$N]) -> CompareResult {
|
||||||
|
self.compare_no_case(&t[..])
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl ShitCompare<&[u8; $N]> for Bytes {
|
||||||
|
#[inline(always)]
|
||||||
|
fn compare(&self, t: &[u8; $N]) -> CompareResult {
|
||||||
|
self.compare(&t[..])
|
||||||
|
}
|
||||||
|
|
||||||
|
#[inline(always)]
|
||||||
|
fn compare_no_case(&self, t: &[u8;$N]) -> CompareResult {
|
||||||
|
self.compare_no_case(&t[..])
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl From<&'static [u8; $N]> for Bytes {
|
||||||
|
fn from(slice: &'static [u8; $N]) -> Self { Bytes(bytes::Bytes::from(&slice[..])) }
|
||||||
|
}
|
||||||
|
)* }
|
||||||
|
}
|
||||||
|
|
||||||
|
array_impls! {
|
||||||
|
0 1 2 3 4 5 6 7 8 9
|
||||||
|
10 11 12 13 14 15 16 17 18 19
|
||||||
|
20 21 22 23 24 25 26 27 28 29
|
||||||
|
30 31 32
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Bytes {
|
||||||
|
pub fn inner(self) -> bytes::Bytes { self.0 }
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Deref for Bytes {
|
||||||
|
type Target = [u8];
|
||||||
|
fn deref(&self) -> &Self::Target { &*self.0 }
|
||||||
|
}
|
||||||
|
|
||||||
|
impl AsRef<[u8]> for Bytes {
|
||||||
|
fn as_ref(&self) -> &[u8] { &*self.0 }
|
||||||
|
}
|
||||||
|
|
||||||
|
impl InputLength for Bytes {
|
||||||
|
fn input_len(&self) -> usize { self.0.len() }
|
||||||
|
}
|
|
@ -1,3 +1,4 @@
|
||||||
|
#[derive(Debug)]
|
||||||
pub enum Command<'a> {
|
pub enum Command<'a> {
|
||||||
// Any state
|
// Any state
|
||||||
Capability,
|
Capability,
|
||||||
|
@ -10,14 +11,14 @@ pub enum Command<'a> {
|
||||||
Authenticate,
|
Authenticate,
|
||||||
|
|
||||||
// Authenticated
|
// Authenticated
|
||||||
Select,
|
Select(CommandSelect<'a>),
|
||||||
Examine,
|
Examine,
|
||||||
Create,
|
Create,
|
||||||
Delete,
|
Delete,
|
||||||
Rename,
|
Rename,
|
||||||
Subscribe,
|
Subscribe,
|
||||||
Unsubscribe,
|
Unsubscribe,
|
||||||
List,
|
List(CommandList<'a>),
|
||||||
Lsub,
|
Lsub,
|
||||||
Status,
|
Status,
|
||||||
Append,
|
Append,
|
||||||
|
@ -27,17 +28,64 @@ pub enum Command<'a> {
|
||||||
Close,
|
Close,
|
||||||
Expunge,
|
Expunge,
|
||||||
Search,
|
Search,
|
||||||
Fetch,
|
|
||||||
Store,
|
|
||||||
Copy,
|
Copy,
|
||||||
Uid,
|
Fetch(CommandFetch),
|
||||||
|
Store,
|
||||||
|
UidCopy,
|
||||||
|
UidFetch(CommandFetch),
|
||||||
|
UidStore,
|
||||||
|
UidSearch(CommandSearch),
|
||||||
|
|
||||||
// Extensions
|
// Extensions
|
||||||
#[cfg(feature = "rfc2177-idle")]
|
#[cfg(feature = "rfc2177-idle")]
|
||||||
Idle,
|
Idle,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[derive(Debug)]
|
||||||
|
pub struct CommandFetch {
|
||||||
|
pub ids: Vec<u32>,
|
||||||
|
pub items: FetchItems,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug)]
|
||||||
|
pub struct CommandList<'a> {
|
||||||
|
pub reference: &'a str,
|
||||||
|
pub mailbox: &'a str,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug)]
|
||||||
pub struct CommandLogin<'a> {
|
pub struct CommandLogin<'a> {
|
||||||
pub username: &'a str,
|
pub username: &'a str,
|
||||||
pub password: &'a str,
|
pub password: &'a str,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[derive(Debug)]
|
||||||
|
pub struct CommandSearch {
|
||||||
|
pub criteria: SearchCriteria,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug)]
|
||||||
|
pub struct CommandSelect<'a> {
|
||||||
|
pub mailbox: &'a str,
|
||||||
|
}
|
||||||
|
|
||||||
|
//
|
||||||
|
|
||||||
|
#[derive(Clone, Debug)]
|
||||||
|
pub enum FetchItems {
|
||||||
|
All,
|
||||||
|
Fast,
|
||||||
|
Full,
|
||||||
|
BodyPeek,
|
||||||
|
Items(Vec<FetchAttr>),
|
||||||
|
/// item set that panorama uses, TODO: remove when FetchItems has a builder
|
||||||
|
PanoramaAll,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Clone, Debug)]
|
||||||
|
pub enum FetchAttr {}
|
||||||
|
|
||||||
|
#[derive(Clone, Debug)]
|
||||||
|
pub enum SearchCriteria {
|
||||||
|
All,
|
||||||
|
}
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
macro_rules! rule {
|
macro_rules! rule {
|
||||||
($vis:vis $name:ident : $ret:ty => $expr:expr) => {
|
($vis:vis $name:ident : $ret:ty => $expr:expr) => {
|
||||||
$vis fn $name(i: &[u8]) -> nom::IResult<&[u8], $ret> {
|
$vis fn $name(i: crate::proto::bytes::Bytes) -> nom::IResult<crate::proto::bytes::Bytes, $ret> {
|
||||||
$expr(i)
|
$expr(i)
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
|
@ -1,15 +1,17 @@
|
||||||
#![allow(non_snake_case, dead_code)]
|
#![allow(non_snake_case, dead_code)]
|
||||||
|
|
||||||
|
// utils
|
||||||
#[macro_use]
|
#[macro_use]
|
||||||
mod macros;
|
mod macros;
|
||||||
|
pub mod bytes;
|
||||||
|
#[macro_use]
|
||||||
|
pub mod parsers;
|
||||||
|
|
||||||
// data types
|
// data types
|
||||||
pub mod command;
|
pub mod command;
|
||||||
pub mod response;
|
pub mod response;
|
||||||
|
|
||||||
// parsers
|
// parsers
|
||||||
#[macro_use]
|
|
||||||
pub mod parsers;
|
|
||||||
pub mod rfc2234;
|
pub mod rfc2234;
|
||||||
pub mod rfc3501;
|
pub mod rfc3501;
|
||||||
|
|
||||||
|
|
|
@ -1,13 +1,17 @@
|
||||||
use std::ops::RangeFrom;
|
use anyhow::Result;
|
||||||
|
|
||||||
use nom::{
|
use nom::{
|
||||||
error::{Error, ErrorKind, ParseError},
|
error::{Error, ErrorKind, ParseError},
|
||||||
Err, IResult, InputIter, Needed, Parser, Slice,
|
CompareResult, Err, IResult, InputLength, Needed, Parser, ToUsize,
|
||||||
};
|
};
|
||||||
|
|
||||||
/// `sep_list!(t, d)` represents `t *(d t)` and automatically collapses it into `Vec<T>`.
|
use super::bytes::{ShitCompare, ShitNeededForParsing};
|
||||||
|
use super::rfc2234::is_digit;
|
||||||
|
|
||||||
|
/// `sep_list!(t, d)` represents `t *(d t)` and automatically collapses it into
|
||||||
|
/// `Vec<T>`.
|
||||||
///
|
///
|
||||||
/// Also `sep_list!(?t, d)` represents `[t *(d t)]` and will default to an empty vec.
|
/// Also `sep_list!(?t, d)` represents `[t *(d t)]` and will default to an empty
|
||||||
|
/// vec.
|
||||||
///
|
///
|
||||||
/// If `d` is not passed then it defaults to `SP`.
|
/// If `d` is not passed then it defaults to `SP`.
|
||||||
macro_rules! sep_list {
|
macro_rules! sep_list {
|
||||||
|
@ -49,6 +53,31 @@ macro_rules! sep_list {
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
macro_rules! opt_nil {
|
||||||
|
($t:expr) => {
|
||||||
|
alt((map($t, Some), map(crate::proto::rfc3501::nil, |_| None)))
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
macro_rules! paren {
|
||||||
|
($t:expr) => {
|
||||||
|
delimited(byte(b'('), $t, byte(b')'))
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn parse_u32(s: impl AsRef<[u8]>) -> Result<u32> {
|
||||||
|
let mut total = 0u32;
|
||||||
|
let s = s.as_ref();
|
||||||
|
for digit in s.iter().rev() {
|
||||||
|
total *= 10;
|
||||||
|
if !is_digit(*digit) {
|
||||||
|
bail!("invalid digit {}", digit)
|
||||||
|
}
|
||||||
|
total += (*digit - b'\x30') as u32;
|
||||||
|
}
|
||||||
|
Ok(total)
|
||||||
|
}
|
||||||
|
|
||||||
/// Always fails, used as a no-op.
|
/// Always fails, used as a no-op.
|
||||||
pub fn never<I, O>(i: I) -> IResult<I, O> { Err(Err::Error(Error::new(i, ErrorKind::Not))) }
|
pub fn never<I, O>(i: I) -> IResult<I, O> { Err(Err::Error(Error::new(i, ErrorKind::Not))) }
|
||||||
|
|
||||||
|
@ -64,15 +93,64 @@ where
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Same as nom satisfy, but operates on `&[u8]` instead of `&str`.
|
/// Same as nom's streaming take, but operates on Bytes
|
||||||
|
pub fn take<C, I, E>(count: C) -> impl Fn(I) -> IResult<I, I, E>
|
||||||
|
where
|
||||||
|
I: ShitNeededForParsing + InputLength,
|
||||||
|
E: ParseError<I>,
|
||||||
|
C: ToUsize,
|
||||||
|
{
|
||||||
|
let c = count.to_usize();
|
||||||
|
move |i: I| match i.slice_index(c) {
|
||||||
|
Err(i) => Err(Err::Incomplete(i)),
|
||||||
|
Ok(index) => Ok(i.take_split(index)),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Same as nom's streaming take_while1, but operates on Bytes
|
||||||
|
pub fn take_while1<F, I, E, T>(cond: F) -> impl Fn(I) -> IResult<I, I, E>
|
||||||
|
where
|
||||||
|
I: ShitNeededForParsing<Item = T>,
|
||||||
|
E: ParseError<I>,
|
||||||
|
F: Fn(T) -> bool,
|
||||||
|
{
|
||||||
|
move |i: I| {
|
||||||
|
let e: ErrorKind = ErrorKind::TakeWhile1;
|
||||||
|
i.split_at_position1(|c| !cond(c), e)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Tag (case-)Insensitive: Same as nom's tag_no_case, but operates on Bytes
|
||||||
|
pub fn tagi<T, I, E>(tag: T) -> impl Fn(I) -> IResult<I, I, E>
|
||||||
|
where
|
||||||
|
I: ShitNeededForParsing<Sliced = I> + InputLength + ShitCompare<T>,
|
||||||
|
T: InputLength + Clone,
|
||||||
|
E: ParseError<I>,
|
||||||
|
{
|
||||||
|
let tag_len = tag.input_len();
|
||||||
|
move |i: I| match i.compare_no_case(tag.clone()) {
|
||||||
|
CompareResult::Ok => {
|
||||||
|
let fst = i.slice(0..tag_len);
|
||||||
|
let snd = i.slice(tag_len..);
|
||||||
|
Ok((snd, fst))
|
||||||
|
}
|
||||||
|
CompareResult::Incomplete => Err(Err::Incomplete(Needed::new(tag_len - i.input_len()))),
|
||||||
|
CompareResult::Error => {
|
||||||
|
let e: ErrorKind = ErrorKind::Tag;
|
||||||
|
Err(Err::Error(E::from_error_kind(i, e)))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Same as nom's satisfy, but operates on `Bytes` instead of `&str`.
|
||||||
pub fn satisfy<F, I, E, T>(f: F) -> impl Fn(I) -> IResult<I, T, E>
|
pub fn satisfy<F, I, E, T>(f: F) -> impl Fn(I) -> IResult<I, T, E>
|
||||||
where
|
where
|
||||||
I: Slice<RangeFrom<usize>> + InputIter<Item = T>,
|
I: ShitNeededForParsing<Item = T, Sliced = I>,
|
||||||
F: Fn(T) -> bool,
|
F: Fn(T) -> bool,
|
||||||
E: ParseError<I>,
|
E: ParseError<I>,
|
||||||
T: Copy,
|
T: Copy,
|
||||||
{
|
{
|
||||||
move |i: I| match i.iter_elements().next().map(|t| (f(t), t)) {
|
move |i: I| match i.first().map(|t| (f(t), t)) {
|
||||||
Some((true, ft)) => Ok((i.slice(1..), ft)),
|
Some((true, ft)) => Ok((i.slice(1..), ft)),
|
||||||
Some((false, _)) => Err(Err::Error(E::from_error_kind(i, ErrorKind::Satisfy))),
|
Some((false, _)) => Err(Err::Error(E::from_error_kind(i, ErrorKind::Satisfy))),
|
||||||
None => Err(Err::Incomplete(Needed::Unknown)),
|
None => Err(Err::Incomplete(Needed::Unknown)),
|
||||||
|
@ -82,7 +160,7 @@ where
|
||||||
/// Match a single byte exactly.
|
/// Match a single byte exactly.
|
||||||
pub fn byte<I, E: ParseError<I>>(b: u8) -> impl Fn(I) -> IResult<I, u8, E>
|
pub fn byte<I, E: ParseError<I>>(b: u8) -> impl Fn(I) -> IResult<I, u8, E>
|
||||||
where
|
where
|
||||||
I: Slice<RangeFrom<usize>> + InputIter<Item = u8>,
|
I: ShitNeededForParsing<Item = u8, Sliced = I>,
|
||||||
{
|
{
|
||||||
satisfy(move |c| c == b)
|
satisfy(move |c| c == b)
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,53 +1,84 @@
|
||||||
use std::borrow::Cow;
|
use std::ops::RangeInclusive;
|
||||||
|
|
||||||
pub type Atom<'a> = Cow<'a, [u8]>;
|
use chrono::{DateTime, FixedOffset};
|
||||||
pub type CowU8<'a> = Cow<'a, [u8]>;
|
|
||||||
|
use super::bytes::Bytes;
|
||||||
|
|
||||||
|
pub type Atom = Bytes;
|
||||||
|
|
||||||
#[derive(Clone, Debug)]
|
#[derive(Clone, Debug)]
|
||||||
pub struct Tag<'a>(pub CowU8<'a>);
|
pub struct Tag(pub Bytes);
|
||||||
|
|
||||||
#[derive(Debug)]
|
#[derive(Debug)]
|
||||||
#[non_exhaustive]
|
#[non_exhaustive]
|
||||||
pub enum Response<'a> {
|
pub enum Response {
|
||||||
Capabilities(Vec<Capability<'a>>),
|
Capabilities(Vec<Capability>),
|
||||||
Continue(ResponseText<'a>),
|
Continue(ResponseText),
|
||||||
Condition(Condition<'a>),
|
Condition(Condition),
|
||||||
Done(ResponseDone<'a>),
|
Done(ResponseDone),
|
||||||
MailboxData(MailboxData<'a>),
|
MailboxData(MailboxData),
|
||||||
Fetch(u32, Vec<MessageAttribute<'a>>),
|
Fetch(u32, Vec<MessageAttribute>),
|
||||||
Expunge(u32),
|
Expunge(u32),
|
||||||
Fatal(Condition<'a>),
|
Fatal(Condition),
|
||||||
Tagged(Tag<'a>, Condition<'a>),
|
Tagged(Tag, Condition),
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug)]
|
#[derive(Debug)]
|
||||||
pub struct ResponseText<'a> {
|
pub struct ResponseText {
|
||||||
pub code: Option<ResponseCode<'a>>,
|
pub code: Option<ResponseCode>,
|
||||||
pub info: CowU8<'a>,
|
pub info: Bytes,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug)]
|
#[derive(Debug)]
|
||||||
pub enum MessageAttribute<'a> {
|
pub enum MessageAttribute {
|
||||||
Flags(Vec<Flag<'a>>),
|
BodySection,
|
||||||
|
BodyStructure,
|
||||||
Envelope(Envelope),
|
Envelope(Envelope),
|
||||||
|
Flags(Vec<Flag>),
|
||||||
|
InternalDate(DateTime<FixedOffset>),
|
||||||
|
ModSeq(u64), // RFC 4551, section 3.3.2
|
||||||
|
Rfc822(Option<String>),
|
||||||
|
Rfc822Header(Option<String>),
|
||||||
|
Rfc822Size(u32),
|
||||||
|
Rfc822Text(Option<String>),
|
||||||
|
Uid(u32),
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug)]
|
#[derive(Debug)]
|
||||||
pub struct Envelope {}
|
pub struct Envelope {
|
||||||
|
pub date: Option<Bytes>,
|
||||||
#[derive(Debug)]
|
pub subject: Option<Bytes>,
|
||||||
pub struct ResponseDone<'a> {
|
pub from: Option<Vec<Address>>,
|
||||||
pub tag: Tag<'a>,
|
pub sender: Option<Vec<Address>>,
|
||||||
pub status: Status,
|
pub reply_to: Option<Vec<Address>>,
|
||||||
pub code: Option<ResponseCode<'a>>,
|
pub to: Option<Vec<Address>>,
|
||||||
pub info: Option<Cow<'a, str>>,
|
pub cc: Option<Vec<Address>>,
|
||||||
|
pub bcc: Option<Vec<Address>>,
|
||||||
|
pub in_reply_to: Option<Bytes>,
|
||||||
|
pub message_id: Option<Bytes>,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug)]
|
#[derive(Debug)]
|
||||||
pub struct Condition<'a> {
|
pub struct Address {
|
||||||
|
pub name: Option<Bytes>,
|
||||||
|
pub adl: Option<Bytes>,
|
||||||
|
pub mailbox: Option<Bytes>,
|
||||||
|
pub host: Option<Bytes>,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug)]
|
||||||
|
pub struct ResponseDone {
|
||||||
|
pub tag: Tag,
|
||||||
pub status: Status,
|
pub status: Status,
|
||||||
pub code: Option<ResponseCode<'a>>,
|
pub code: Option<ResponseCode>,
|
||||||
pub info: CowU8<'a>,
|
pub info: Option<Bytes>,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug)]
|
||||||
|
pub struct Condition {
|
||||||
|
pub status: Status,
|
||||||
|
pub code: Option<ResponseCode>,
|
||||||
|
pub info: Bytes,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug)]
|
#[derive(Debug)]
|
||||||
|
@ -61,54 +92,79 @@ pub enum Status {
|
||||||
|
|
||||||
#[derive(Debug)]
|
#[derive(Debug)]
|
||||||
#[non_exhaustive]
|
#[non_exhaustive]
|
||||||
pub enum ResponseCode<'a> {
|
pub enum ResponseCode {
|
||||||
Alert,
|
Alert,
|
||||||
Capabilities(Vec<Capability<'a>>),
|
BadCharset(Option<Vec<Bytes>>),
|
||||||
|
Capabilities(Vec<Capability>),
|
||||||
|
HighestModSeq(u64), // RFC 4551, section 3.1.1
|
||||||
|
Parse,
|
||||||
|
PermanentFlags(Vec<Bytes>),
|
||||||
|
ReadOnly,
|
||||||
|
ReadWrite,
|
||||||
|
TryCreate,
|
||||||
|
UidNext(u32),
|
||||||
|
UidValidity(u32),
|
||||||
|
Unseen(u32),
|
||||||
|
AppendUid(u32, Vec<UidSetMember>),
|
||||||
|
CopyUid(u32, Vec<UidSetMember>, Vec<UidSetMember>),
|
||||||
|
UidNotSticky,
|
||||||
|
Other(Bytes, Option<Bytes>),
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug)]
|
#[derive(Debug)]
|
||||||
pub enum Capability<'a> {
|
pub enum UidSetMember {
|
||||||
|
UidRange(RangeInclusive<u32>),
|
||||||
|
Uid(u32),
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, PartialEq, Eq)]
|
||||||
|
pub enum Capability {
|
||||||
Imap4rev1,
|
Imap4rev1,
|
||||||
Auth(Atom<'a>),
|
Auth(Atom),
|
||||||
Atom(Atom<'a>),
|
Atom(Atom),
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug)]
|
#[derive(Debug)]
|
||||||
pub enum MailboxData<'a> {
|
pub enum MailboxData {
|
||||||
Flags(Vec<Flag<'a>>),
|
Flags(Vec<Flag>),
|
||||||
List(MailboxList<'a>),
|
List(MailboxList),
|
||||||
|
Lsub,
|
||||||
|
Search(Vec<u32>),
|
||||||
|
Status,
|
||||||
|
Exists(u32),
|
||||||
|
Recent(u32),
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug)]
|
#[derive(Debug)]
|
||||||
pub enum Mailbox<'a> {
|
pub enum Mailbox {
|
||||||
Inbox,
|
Inbox,
|
||||||
Name(CowU8<'a>),
|
Name(Bytes),
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug)]
|
#[derive(Debug)]
|
||||||
pub enum Flag<'a> {
|
pub enum Flag {
|
||||||
Answered,
|
Answered,
|
||||||
Flagged,
|
Flagged,
|
||||||
Deleted,
|
Deleted,
|
||||||
Seen,
|
Seen,
|
||||||
Draft,
|
Draft,
|
||||||
Recent,
|
Recent,
|
||||||
Keyword(Atom<'a>),
|
Keyword(Atom),
|
||||||
Extension(Atom<'a>),
|
Extension(Atom),
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug)]
|
#[derive(Debug)]
|
||||||
pub struct MailboxList<'a> {
|
pub struct MailboxList {
|
||||||
pub flags: Vec<MailboxListFlag<'a>>,
|
pub flags: Vec<MailboxListFlag>,
|
||||||
pub delimiter: Option<u8>,
|
pub delimiter: Option<u8>,
|
||||||
pub mailbox: Mailbox<'a>,
|
pub mailbox: Mailbox,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug)]
|
#[derive(Debug)]
|
||||||
pub enum MailboxListFlag<'a> {
|
pub enum MailboxListFlag {
|
||||||
NoInferiors,
|
NoInferiors,
|
||||||
NoSelect,
|
NoSelect,
|
||||||
Marked,
|
Marked,
|
||||||
Unmarked,
|
Unmarked,
|
||||||
Extension(Atom<'a>),
|
Extension(Atom),
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
//! Grammar from <https://tools.ietf.org/html/rfc2234#section-6.1>
|
//! Grammar from <https://tools.ietf.org/html/rfc2234#section-6.1>
|
||||||
|
|
||||||
use nom::{branch::alt, character::streaming::anychar, multi::many0, sequence::pair};
|
use nom::{branch::alt, multi::many0, sequence::pair};
|
||||||
|
|
||||||
use super::parsers::{byte, satisfy, skip};
|
use super::parsers::{byte, satisfy, skip};
|
||||||
|
|
||||||
|
@ -16,10 +16,10 @@ rule!(pub CR : u8 => satisfy(is_cr));
|
||||||
|
|
||||||
rule!(pub CRLF : (u8, u8) => pair(CR, LF));
|
rule!(pub CRLF : (u8, u8) => pair(CR, LF));
|
||||||
|
|
||||||
pub fn is_ctl(c: u8) -> bool { c <= b'\x1f' || c == b'\x7f' }
|
pub(crate) fn is_ctl(c: u8) -> bool { c <= b'\x1f' || c == b'\x7f' }
|
||||||
rule!(pub CTL : u8 => satisfy(is_ctl));
|
rule!(pub CTL : u8 => satisfy(is_ctl));
|
||||||
|
|
||||||
pub fn is_digit(c: u8) -> bool { c >= b'\x30' && c <= b'\x39' }
|
pub(crate) fn is_digit(c: u8) -> bool { c >= b'\x30' && c <= b'\x39' }
|
||||||
rule!(pub DIGIT : u8 => satisfy(is_digit));
|
rule!(pub DIGIT : u8 => satisfy(is_digit));
|
||||||
|
|
||||||
pub(crate) fn is_dquote(c: u8) -> bool { c == b'\x22' }
|
pub(crate) fn is_dquote(c: u8) -> bool { c == b'\x22' }
|
||||||
|
@ -34,7 +34,7 @@ rule!(pub LF : u8 => satisfy(is_lf));
|
||||||
|
|
||||||
rule!(pub LWSP : () => skip(many0(alt((skip(WSP), skip(pair(CRLF, WSP)))))));
|
rule!(pub LWSP : () => skip(many0(alt((skip(WSP), skip(pair(CRLF, WSP)))))));
|
||||||
|
|
||||||
rule!(pub OCTET : char => anychar);
|
// rule!(pub OCTET : char => anychar);
|
||||||
|
|
||||||
pub(crate) fn is_sp(c: u8) -> bool { c == b'\x20' }
|
pub(crate) fn is_sp(c: u8) -> bool { c == b'\x20' }
|
||||||
rule!(pub SP : u8 => satisfy(is_sp));
|
rule!(pub SP : u8 => satisfy(is_sp));
|
||||||
|
|
|
@ -1,30 +1,42 @@
|
||||||
//! Grammar from <https://datatracker.ietf.org/doc/html/rfc3501#section-9>
|
//! Grammar from <https://datatracker.ietf.org/doc/html/rfc3501#section-9>
|
||||||
|
|
||||||
use std::borrow::Cow;
|
|
||||||
|
|
||||||
use nom::{
|
use nom::{
|
||||||
branch::alt,
|
branch::alt,
|
||||||
bytes::streaming::{tag_no_case, take, take_while1},
|
|
||||||
character::streaming::char,
|
|
||||||
combinator::{map, map_res, opt, verify},
|
combinator::{map, map_res, opt, verify},
|
||||||
multi::many0,
|
multi::{many0, many1},
|
||||||
sequence::{delimited, pair, preceded, separated_pair, terminated, tuple},
|
sequence::{delimited, pair, preceded, separated_pair, terminated, tuple},
|
||||||
IResult,
|
IResult,
|
||||||
};
|
};
|
||||||
|
|
||||||
use super::parsers::{byte, never, satisfy};
|
use super::bytes::Bytes;
|
||||||
|
use super::parsers::{byte, never, parse_u32, satisfy, tagi, take, take_while1};
|
||||||
use super::response::{
|
use super::response::{
|
||||||
Atom, Capability, Condition, CowU8, Envelope, Flag, Mailbox, MailboxData, MailboxList,
|
Address, Atom, Capability, Condition, Envelope, Flag, Mailbox, MailboxData, MailboxList,
|
||||||
MailboxListFlag, MessageAttribute, Response, ResponseCode, ResponseText, Status, Tag,
|
MailboxListFlag, MessageAttribute, Response, ResponseCode, ResponseText, Status, Tag,
|
||||||
};
|
};
|
||||||
use super::rfc2234::{is_char, is_cr, is_ctl, is_digit, is_dquote, is_lf, is_sp, CRLF, DQUOTE, SP};
|
use super::rfc2234::{is_char, is_cr, is_ctl, is_digit, is_dquote, is_lf, is_sp, CRLF, DQUOTE, SP};
|
||||||
|
|
||||||
rule!(pub astring : CowU8 => alt((map(take_while1(is_astring_char), Cow::from), string)));
|
rule!(pub address : Address => map(paren!(tuple((
|
||||||
|
terminated(addr_name, SP),
|
||||||
|
terminated(addr_adl, SP),
|
||||||
|
terminated(addr_mailbox, SP),
|
||||||
|
addr_host
|
||||||
|
))), |(name, adl, mailbox, host)| Address { name, adl, mailbox, host }));
|
||||||
|
|
||||||
|
rule!(pub addr_adl : Option<Bytes> => nstring);
|
||||||
|
|
||||||
|
rule!(pub addr_host : Option<Bytes> => nstring);
|
||||||
|
|
||||||
|
rule!(pub addr_mailbox : Option<Bytes> => nstring);
|
||||||
|
|
||||||
|
rule!(pub addr_name : Option<Bytes> => nstring);
|
||||||
|
|
||||||
|
rule!(pub astring : Bytes => alt((take_while1(is_astring_char), string)));
|
||||||
|
|
||||||
pub(crate) fn is_astring_char(c: u8) -> bool { is_atom_char(c) || is_resp_specials(c) }
|
pub(crate) fn is_astring_char(c: u8) -> bool { is_atom_char(c) || is_resp_specials(c) }
|
||||||
rule!(pub ASTRING_CHAR : u8 => alt((ATOM_CHAR, resp_specials)));
|
rule!(pub ASTRING_CHAR : u8 => alt((ATOM_CHAR, resp_specials)));
|
||||||
|
|
||||||
rule!(pub atom : CowU8 => map(take_while1(is_atom_char), Cow::from));
|
rule!(pub atom : Bytes => take_while1(is_atom_char));
|
||||||
|
|
||||||
pub(crate) fn is_atom_char(c: u8) -> bool { is_char(c) && !is_atom_specials(c) }
|
pub(crate) fn is_atom_char(c: u8) -> bool { is_char(c) && !is_atom_specials(c) }
|
||||||
rule!(pub ATOM_CHAR : u8 => satisfy(is_atom_char));
|
rule!(pub ATOM_CHAR : u8 => satisfy(is_atom_char));
|
||||||
|
@ -44,39 +56,73 @@ rule!(pub atom_specials : u8 => satisfy(is_atom_specials));
|
||||||
rule!(pub auth_type : Atom => atom);
|
rule!(pub auth_type : Atom => atom);
|
||||||
|
|
||||||
rule!(pub capability : Capability => alt((
|
rule!(pub capability : Capability => alt((
|
||||||
map(preceded(tag_no_case("AUTH="), auth_type), |s| Capability::Auth(Cow::from(s))),
|
map(preceded(tagi(b"AUTH="), auth_type), Capability::Auth),
|
||||||
map(atom, |s| Capability::Atom(Cow::from(s))),
|
map(atom, Capability::Atom),
|
||||||
)));
|
)));
|
||||||
|
|
||||||
rule!(pub capability_data : Vec<Capability> => preceded(tag_no_case("CAPABILITY"), {
|
rule!(pub capability_data : Vec<Capability> => preceded(tagi(b"CAPABILITY"), {
|
||||||
map(separated_pair(
|
map(separated_pair(
|
||||||
many0(preceded(SP, capability)),
|
many0(preceded(SP, capability)),
|
||||||
pair(SP, tag_no_case("IMAP4rev1")),
|
pair(SP, tagi(b"IMAP4rev1")),
|
||||||
many0(preceded(SP, capability))
|
many0(preceded(SP, capability))
|
||||||
), |(mut a, b)| { a.extend(b); a })
|
), |(mut a, b)| { a.extend(b); a })
|
||||||
}));
|
}));
|
||||||
|
|
||||||
|
pub(crate) fn is_char8(c: u8) -> bool { c != b'\0' }
|
||||||
|
|
||||||
rule!(pub continue_req : Response => delimited(pair(byte(b'+'), SP),
|
rule!(pub continue_req : Response => delimited(pair(byte(b'+'), SP),
|
||||||
// TODO: handle base64 case?
|
// TODO: handle base64 case?
|
||||||
map(resp_text, Response::Continue),
|
map(resp_text, Response::Continue),
|
||||||
CRLF));
|
CRLF));
|
||||||
|
|
||||||
rule!(pub envelope : Envelope => map(byte(b'('), |_| Envelope {}));
|
rule!(pub envelope : Envelope => map(paren!(tuple((
|
||||||
|
terminated(env_date, SP),
|
||||||
|
terminated(env_subject, SP),
|
||||||
|
terminated(env_from, SP),
|
||||||
|
terminated(env_sender, SP),
|
||||||
|
terminated(env_reply_to, SP),
|
||||||
|
terminated(env_to, SP),
|
||||||
|
terminated(env_cc, SP),
|
||||||
|
terminated(env_bcc, SP),
|
||||||
|
terminated(env_in_reply_to, SP),
|
||||||
|
env_message_id,
|
||||||
|
))), |(date, subject, from, sender, reply_to, to, cc, bcc, in_reply_to, message_id)|
|
||||||
|
Envelope { date, subject, from, sender, reply_to, to, cc, bcc, in_reply_to, message_id }));
|
||||||
|
|
||||||
|
rule!(pub env_bcc : Option<Vec<Address>> => opt_nil!(paren!(many1(address))));
|
||||||
|
|
||||||
|
rule!(pub env_cc : Option<Vec<Address>> => opt_nil!(paren!(many1(address))));
|
||||||
|
|
||||||
|
rule!(pub env_date : Option<Bytes> => nstring);
|
||||||
|
|
||||||
|
rule!(pub env_from : Option<Vec<Address>> => opt_nil!(paren!(many1(address))));
|
||||||
|
|
||||||
|
rule!(pub env_in_reply_to : Option<Bytes> => nstring);
|
||||||
|
|
||||||
|
rule!(pub env_message_id : Option<Bytes> => nstring);
|
||||||
|
|
||||||
|
rule!(pub env_reply_to : Option<Vec<Address>> => opt_nil!(paren!(many1(address))));
|
||||||
|
|
||||||
|
rule!(pub env_sender : Option<Vec<Address>> => opt_nil!(paren!(many1(address))));
|
||||||
|
|
||||||
|
rule!(pub env_subject : Option<Bytes> => nstring);
|
||||||
|
|
||||||
|
rule!(pub env_to : Option<Vec<Address>> => opt_nil!(paren!(many1(address))));
|
||||||
|
|
||||||
rule!(pub flag : Flag => alt((
|
rule!(pub flag : Flag => alt((
|
||||||
map(tag_no_case("\\Answered"), |_| Flag::Answered),
|
map(tagi(b"\\Answered"), |_| Flag::Answered),
|
||||||
map(tag_no_case("\\Flagged"), |_| Flag::Flagged),
|
map(tagi(b"\\Flagged"), |_| Flag::Flagged),
|
||||||
map(tag_no_case("\\Deleted"), |_| Flag::Deleted),
|
map(tagi(b"\\Deleted"), |_| Flag::Deleted),
|
||||||
map(tag_no_case("\\Seen"), |_| Flag::Seen),
|
map(tagi(b"\\Seen"), |_| Flag::Seen),
|
||||||
map(tag_no_case("\\Draft"), |_| Flag::Draft),
|
map(tagi(b"\\Draft"), |_| Flag::Draft),
|
||||||
map(flag_extension, Flag::Extension),
|
map(flag_extension, Flag::Extension),
|
||||||
)));
|
)));
|
||||||
|
|
||||||
rule!(pub flag_extension : Atom => preceded(byte(b'\\'), atom));
|
rule!(pub flag_extension : Atom => preceded(byte(b'\\'), atom));
|
||||||
|
|
||||||
rule!(pub flag_fetch : Flag => alt((flag, map(tag_no_case("\\Recent"), |_| Flag::Recent))));
|
rule!(pub flag_fetch : Flag => alt((flag, map(tagi(b"\\Recent"), |_| Flag::Recent))));
|
||||||
|
|
||||||
rule!(pub flag_list : Vec<Flag> => delimited(byte(b'('), sep_list!(?flag), byte(b')')));
|
rule!(pub flag_list : Vec<Flag> => paren!(sep_list!(?flag)));
|
||||||
|
|
||||||
pub(crate) fn is_list_wildcards(c: u8) -> bool { c == b'%' || c == b'*' }
|
pub(crate) fn is_list_wildcards(c: u8) -> bool { c == b'%' || c == b'*' }
|
||||||
rule!(pub list_wildcards : u8 => satisfy(is_list_wildcards));
|
rule!(pub list_wildcards : u8 => satisfy(is_list_wildcards));
|
||||||
|
@ -86,33 +132,36 @@ rule!(pub list_wildcards : u8 => satisfy(is_list_wildcards));
|
||||||
// TODO: Future work, could possibly initialize writing to file if the length is
|
// TODO: Future work, could possibly initialize writing to file if the length is
|
||||||
// determined to exceed a certain threshold so we don't have insane amounts of
|
// determined to exceed a certain threshold so we don't have insane amounts of
|
||||||
// data in memory
|
// data in memory
|
||||||
pub fn literal(i: &[u8]) -> IResult<&[u8], CowU8> {
|
pub fn literal(i: Bytes) -> IResult<Bytes, Bytes> {
|
||||||
let mut length_of = terminated(delimited(char('{'), number, char('}')), CRLF);
|
let mut length_of = terminated(delimited(byte(b'{'), number, byte(b'}')), CRLF);
|
||||||
let (i, length) = length_of(i)?;
|
let (i, length) = length_of(i)?;
|
||||||
println!("length is: {:?}", (i, length));
|
println!("length is: {:?}", length);
|
||||||
map(take(length), Cow::from)(i)
|
map(take(length), Bytes::from)(i)
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn test_literal() {
|
fn test_literal() {
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
literal(b"{13}\r\nHello, world!").unwrap().1.as_ref(),
|
literal(Bytes::from(b"{13}\r\nHello, world!"))
|
||||||
|
.unwrap()
|
||||||
|
.1
|
||||||
|
.as_ref(),
|
||||||
b"Hello, world!"
|
b"Hello, world!"
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
rule!(pub mailbox : Mailbox => alt((
|
rule!(pub mailbox : Mailbox => alt((
|
||||||
map(tag_no_case("INBOX"), |_| Mailbox::Inbox),
|
map(tagi(b"INBOX"), |_| Mailbox::Inbox),
|
||||||
map(astring, Mailbox::Name),
|
map(astring, Mailbox::Name),
|
||||||
)));
|
)));
|
||||||
|
|
||||||
rule!(pub mailbox_data : MailboxData => alt((
|
rule!(pub mailbox_data : MailboxData => alt((
|
||||||
map(preceded(pair(tag_no_case("FLAGS"), SP), flag_list), MailboxData::Flags),
|
map(preceded(pair(tagi(b"FLAGS"), SP), flag_list), MailboxData::Flags),
|
||||||
map(preceded(pair(tag_no_case("LIST"), SP), mailbox_list), MailboxData::List),
|
map(preceded(pair(tagi(b"LIST"), SP), mailbox_list), MailboxData::List),
|
||||||
)));
|
)));
|
||||||
|
|
||||||
rule!(pub mailbox_list : MailboxList => map(separated_pair(
|
rule!(pub mailbox_list : MailboxList => map(separated_pair(
|
||||||
delimited(byte(b'('), map(opt(mbx_list_flags), |opt| opt.unwrap_or_else(Vec::new)), byte(b')')),
|
paren!(map(opt(mbx_list_flags), |opt| opt.unwrap_or_else(Vec::new))),
|
||||||
SP, separated_pair(
|
SP, separated_pair(
|
||||||
alt((
|
alt((
|
||||||
map(delimited(DQUOTE, QUOTED_CHAR, DQUOTE), Some),
|
map(delimited(DQUOTE, QUOTED_CHAR, DQUOTE), Some),
|
||||||
|
@ -132,50 +181,46 @@ rule!(pub mbx_list_flags : Vec<MailboxListFlag> => alt((
|
||||||
)));
|
)));
|
||||||
|
|
||||||
rule!(pub mbx_list_oflag : MailboxListFlag => alt((
|
rule!(pub mbx_list_oflag : MailboxListFlag => alt((
|
||||||
map(tag_no_case("\\Noinferiors"), |_| MailboxListFlag::NoInferiors),
|
map(tagi(b"\\Noinferiors"), |_| MailboxListFlag::NoInferiors),
|
||||||
map(flag_extension, MailboxListFlag::Extension),
|
map(flag_extension, MailboxListFlag::Extension),
|
||||||
)));
|
)));
|
||||||
|
|
||||||
rule!(pub mbx_list_sflag : MailboxListFlag => alt((
|
rule!(pub mbx_list_sflag : MailboxListFlag => alt((
|
||||||
map(tag_no_case("\\NoSelect"), |_| MailboxListFlag::NoSelect),
|
map(tagi(b"\\NoSelect"), |_| MailboxListFlag::NoSelect),
|
||||||
map(tag_no_case("\\Marked"), |_| MailboxListFlag::Marked),
|
map(tagi(b"\\Marked"), |_| MailboxListFlag::Marked),
|
||||||
map(tag_no_case("\\Unmarked"), |_| MailboxListFlag::Unmarked),
|
map(tagi(b"\\Unmarked"), |_| MailboxListFlag::Unmarked),
|
||||||
)));
|
)));
|
||||||
|
|
||||||
rule!(pub message_data : Response => alt((
|
rule!(pub message_data : Response => alt((
|
||||||
map(terminated(nz_number, pair(SP, tag_no_case("EXPUNGE"))), Response::Expunge),
|
map(terminated(nz_number, pair(SP, tagi(b"EXPUNGE"))), Response::Expunge),
|
||||||
map(separated_pair(nz_number, SP, preceded(pair(tag_no_case("FETCH"), SP), msg_att)),
|
map(separated_pair(nz_number, SP, preceded(pair(tagi(b"FETCH"), SP), msg_att)),
|
||||||
|(n, attrs)| Response::Fetch(n, attrs)),
|
|(n, attrs)| Response::Fetch(n, attrs)),
|
||||||
)));
|
)));
|
||||||
|
|
||||||
rule!(pub msg_att : Vec<MessageAttribute> => delimited(byte(b'('),
|
rule!(pub msg_att : Vec<MessageAttribute> => paren!(sep_list!(alt((msg_att_dynamic, msg_att_static)))));
|
||||||
sep_list!(alt((msg_att_dynamic, msg_att_static))),
|
|
||||||
byte(b')')));
|
|
||||||
|
|
||||||
rule!(pub msg_att_dynamic : MessageAttribute => alt((
|
rule!(pub msg_att_dynamic : MessageAttribute => alt((
|
||||||
map(preceded(pair(tag_no_case("FLAGS"), SP),
|
map(preceded(pair(tagi(b"FLAGS"), SP),
|
||||||
delimited(byte(b'('), sep_list!(?flag_fetch), byte(b')'))), MessageAttribute::Flags),
|
paren!(sep_list!(?flag_fetch))), MessageAttribute::Flags),
|
||||||
never,
|
never,
|
||||||
)));
|
)));
|
||||||
|
|
||||||
rule!(pub msg_att_static : MessageAttribute => alt((
|
rule!(pub msg_att_static : MessageAttribute => alt((
|
||||||
map(preceded(pair(tag_no_case("ENVELOPE"), SP), envelope), MessageAttribute::Envelope),
|
map(preceded(pair(tagi(b"ENVELOPE"), SP), envelope), MessageAttribute::Envelope),
|
||||||
map(preceded(pair(tag_no_case("ENVELOPE"), SP), envelope), MessageAttribute::Envelope),
|
map(preceded(pair(tagi(b"ENVELOPE"), SP), envelope), MessageAttribute::Envelope),
|
||||||
)));
|
)));
|
||||||
|
|
||||||
rule!(pub nil : &[u8] => tag_no_case("NIL"));
|
rule!(pub nil : Bytes => tagi(b"NIL"));
|
||||||
|
|
||||||
rule!(pub nstring : Option<CowU8> => alt((map(string, Some), map(nil, |_| None))));
|
rule!(pub nstring : Option<Bytes> => opt_nil!(string));
|
||||||
|
|
||||||
pub(crate) fn number(i: &[u8]) -> IResult<&[u8], u32> {
|
pub(crate) fn number(i: Bytes) -> IResult<Bytes, u32> {
|
||||||
map_res(map_res(take_while1(is_digit), std::str::from_utf8), |s| {
|
map_res(take_while1(is_digit), parse_u32)(i)
|
||||||
s.parse::<u32>()
|
|
||||||
})(i)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
rule!(pub nz_number : u32 => verify(number, |n| *n != 0));
|
rule!(pub nz_number : u32 => verify(number, |n| *n != 0));
|
||||||
|
|
||||||
rule!(pub quoted : CowU8 => delimited(DQUOTE, map(take_while1(is_quoted_char), Cow::from), DQUOTE));
|
rule!(pub quoted : Bytes => delimited(DQUOTE, take_while1(is_quoted_char), DQUOTE));
|
||||||
|
|
||||||
fn is_quoted_char(c: u8) -> bool { is_char(c) && !is_quoted_specials(c) }
|
fn is_quoted_char(c: u8) -> bool { is_char(c) && !is_quoted_specials(c) }
|
||||||
rule!(pub QUOTED_CHAR : u8 => alt((satisfy(is_quoted_char), preceded(byte(b'\\'), quoted_specials))));
|
rule!(pub QUOTED_CHAR : u8 => alt((satisfy(is_quoted_char), preceded(byte(b'\\'), quoted_specials))));
|
||||||
|
@ -202,15 +247,15 @@ rule!(pub response_fatal : Response => delimited(pair(byte(b'*'), SP),
|
||||||
rule!(pub response_tagged : Response => map(terminated(separated_pair(tag, SP, resp_cond_state), CRLF),
|
rule!(pub response_tagged : Response => map(terminated(separated_pair(tag, SP, resp_cond_state), CRLF),
|
||||||
|(tag, cond)| Response::Tagged(tag, cond)));
|
|(tag, cond)| Response::Tagged(tag, cond)));
|
||||||
|
|
||||||
rule!(pub resp_cond_bye : Condition => preceded(pair(tag_no_case("BYE"), SP),
|
rule!(pub resp_cond_bye : Condition => preceded(pair(tagi(b"BYE"), SP),
|
||||||
map(resp_text, |ResponseText { code, info }| Condition { status: Status::Bye, code, info })));
|
map(resp_text, |ResponseText { code, info }| Condition { status: Status::Bye, code, info })));
|
||||||
|
|
||||||
rule!(pub resp_cond_state : Condition => map(
|
rule!(pub resp_cond_state : Condition => map(
|
||||||
separated_pair(
|
separated_pair(
|
||||||
alt((
|
alt((
|
||||||
map(tag_no_case("OK"), |_| Status::Ok),
|
map(tagi(b"OK"), |_| Status::Ok),
|
||||||
map(tag_no_case("NO"), |_| Status::No),
|
map(tagi(b"NO"), |_| Status::No),
|
||||||
map(tag_no_case("BAD"), |_| Status::Bad),
|
map(tagi(b"BAD"), |_| Status::Bad),
|
||||||
)),
|
)),
|
||||||
SP,
|
SP,
|
||||||
resp_text,
|
resp_text,
|
||||||
|
@ -227,16 +272,16 @@ rule!(pub resp_text : ResponseText => map(pair(
|
||||||
), |(code, info)| ResponseText { code, info }));
|
), |(code, info)| ResponseText { code, info }));
|
||||||
|
|
||||||
rule!(pub resp_text_code : ResponseCode => alt((
|
rule!(pub resp_text_code : ResponseCode => alt((
|
||||||
map(tag_no_case("ALERT"), |_| ResponseCode::Alert),
|
map(tagi(b"ALERT"), |_| ResponseCode::Alert),
|
||||||
map(capability_data, ResponseCode::Capabilities),
|
map(capability_data, ResponseCode::Capabilities),
|
||||||
)));
|
)));
|
||||||
|
|
||||||
rule!(pub string : CowU8 => alt((quoted, literal)));
|
rule!(pub string : Bytes => alt((quoted, literal)));
|
||||||
|
|
||||||
pub(crate) fn is_tag_char(c: u8) -> bool { is_astring_char(c) && c != b'+' }
|
pub(crate) fn is_tag_char(c: u8) -> bool { is_astring_char(c) && c != b'+' }
|
||||||
rule!(pub tag : Tag => map(take_while1(is_tag_char), |s: &[u8]| Tag(s.into())));
|
rule!(pub tag : Tag => map(take_while1(is_tag_char), Tag));
|
||||||
|
|
||||||
rule!(pub text : CowU8 => map(take_while1(is_text_char), Cow::from));
|
rule!(pub text : Bytes => map(take_while1(is_text_char), Bytes::from));
|
||||||
|
|
||||||
pub(crate) fn is_text_char(c: u8) -> bool { is_char(c) && !is_cr(c) && !is_lf(c) }
|
pub(crate) fn is_text_char(c: u8) -> bool { is_char(c) && !is_cr(c) && !is_lf(c) }
|
||||||
rule!(pub TEXT_CHAR : u8 => satisfy(is_text_char));
|
rule!(pub TEXT_CHAR : u8 => satisfy(is_text_char));
|
||||||
|
|
Loading…
Reference in a new issue