Merge branch 'rewrite-parsers'
This commit is contained in:
commit
2b97aca995
27 changed files with 827 additions and 244 deletions
5
.github/workflows/doc.yml
vendored
5
.github/workflows/doc.yml
vendored
|
@ -1,4 +1,7 @@
|
||||||
on: [push]
|
on:
|
||||||
|
push:
|
||||||
|
branches:
|
||||||
|
- master
|
||||||
|
|
||||||
name: CI
|
name: CI
|
||||||
|
|
||||||
|
|
189
Cargo.lock
generated
189
Cargo.lock
generated
|
@ -77,15 +77,24 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "cf1de2fe8c75bc145a2f577add951f8134889b4795d47466a54a5c846d691693"
|
checksum = "cf1de2fe8c75bc145a2f577add951f8134889b4795d47466a54a5c846d691693"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "bitvec"
|
name = "block-buffer"
|
||||||
version = "0.19.4"
|
version = "0.7.3"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "a7ba35e9565969edb811639dbebfe34edc0368e472c5018474c8eb2543397f81"
|
checksum = "c0940dc441f31689269e10ac70eb1002a3a1d3ad1390e030043662eb7fe4688b"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"funty",
|
"block-padding",
|
||||||
"radium",
|
"byte-tools",
|
||||||
"tap",
|
"byteorder",
|
||||||
"wyz",
|
"generic-array",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "block-padding"
|
||||||
|
version = "0.1.5"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "fa79dedbb091f449f1f39e53edf88d5dbe95f895dae6135a8d7b881fb5af73f5"
|
||||||
|
dependencies = [
|
||||||
|
"byte-tools",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
|
@ -100,6 +109,12 @@ version = "3.6.1"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "63396b8a4b9de3f4fdfb320ab6080762242f66a8ef174c49d8e19b674db4cdbe"
|
checksum = "63396b8a4b9de3f4fdfb320ab6080762242f66a8ef174c49d8e19b674db4cdbe"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "byte-tools"
|
||||||
|
version = "0.3.1"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "e3b5ca7a04898ad4bcd41c90c5285445ff5b791899bb1b0abdd2a2aa791211d7"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "byteorder"
|
name = "byteorder"
|
||||||
version = "1.4.2"
|
version = "1.4.2"
|
||||||
|
@ -264,6 +279,21 @@ dependencies = [
|
||||||
"syn",
|
"syn",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "digest"
|
||||||
|
version = "0.8.1"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "f3d0c8c8752312f9713efd397ff63acb9f85585afbf179282e720e7704954dd5"
|
||||||
|
dependencies = [
|
||||||
|
"generic-array",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "fake-simd"
|
||||||
|
version = "0.1.2"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "e88a8acf291dafb59c2d96e8f59828f3838bb1a70398823ade51a84de6a6deed"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "fast_chemail"
|
name = "fast_chemail"
|
||||||
version = "0.9.6"
|
version = "0.9.6"
|
||||||
|
@ -326,12 +356,6 @@ dependencies = [
|
||||||
"syn",
|
"syn",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "funty"
|
|
||||||
version = "1.1.0"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "fed34cd105917e91daa4da6b3728c47b068749d6a62c59811f06ed2ac71d9da7"
|
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "futures"
|
name = "futures"
|
||||||
version = "0.3.12"
|
version = "0.3.12"
|
||||||
|
@ -427,6 +451,15 @@ dependencies = [
|
||||||
"slab",
|
"slab",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "generic-array"
|
||||||
|
version = "0.12.3"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "c68f0274ae0e023facc3c97b2e00f076be70e254bc851d972503b328db79b2ec"
|
||||||
|
dependencies = [
|
||||||
|
"typenum",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "getrandom"
|
name = "getrandom"
|
||||||
version = "0.2.2"
|
version = "0.2.2"
|
||||||
|
@ -536,7 +569,7 @@ dependencies = [
|
||||||
"hostname",
|
"hostname",
|
||||||
"log",
|
"log",
|
||||||
"native-tls",
|
"native-tls",
|
||||||
"nom 4.2.3",
|
"nom",
|
||||||
"serde",
|
"serde",
|
||||||
"serde_derive",
|
"serde_derive",
|
||||||
"serde_json",
|
"serde_json",
|
||||||
|
@ -566,6 +599,12 @@ dependencies = [
|
||||||
"cfg-if",
|
"cfg-if",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "maplit"
|
||||||
|
version = "1.0.2"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "3e2e65a1a2e43cfcb47a895c4c8b10d1f4a61097f9f254f183aee60cad9c651d"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "memchr"
|
name = "memchr"
|
||||||
version = "2.3.4"
|
version = "2.3.4"
|
||||||
|
@ -623,18 +662,6 @@ dependencies = [
|
||||||
"version_check 0.1.5",
|
"version_check 0.1.5",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "nom"
|
|
||||||
version = "6.1.2"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "e7413f999671bd4745a7b624bd370a569fb6bc574b23c83a3c5ed2e453f3d5e2"
|
|
||||||
dependencies = [
|
|
||||||
"bitvec",
|
|
||||||
"funty",
|
|
||||||
"memchr",
|
|
||||||
"version_check 0.9.2",
|
|
||||||
]
|
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "ntapi"
|
name = "ntapi"
|
||||||
version = "0.3.6"
|
version = "0.3.6"
|
||||||
|
@ -679,6 +706,12 @@ version = "1.5.2"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "13bd41f508810a131401606d54ac32a467c97172d74ba7662562ebba5ad07fa0"
|
checksum = "13bd41f508810a131401606d54ac32a467c97172d74ba7662562ebba5ad07fa0"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "opaque-debug"
|
||||||
|
version = "0.2.3"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "2839e79665f131bdb5782e51f2c6c9599c133c6098982a54c794358bf432529c"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "openssl"
|
name = "openssl"
|
||||||
version = "0.10.32"
|
version = "0.10.32"
|
||||||
|
@ -712,15 +745,6 @@ dependencies = [
|
||||||
"vcpkg",
|
"vcpkg",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "owning_ref"
|
|
||||||
version = "0.4.1"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "6ff55baddef9e4ad00f88b6c743a2a8062d4c6ade126c2a528644b8e444d52ce"
|
|
||||||
dependencies = [
|
|
||||||
"stable_deref_trait",
|
|
||||||
]
|
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "panorama"
|
name = "panorama"
|
||||||
version = "0.0.1"
|
version = "0.0.1"
|
||||||
|
@ -760,9 +784,9 @@ dependencies = [
|
||||||
"derive_builder",
|
"derive_builder",
|
||||||
"futures",
|
"futures",
|
||||||
"log",
|
"log",
|
||||||
"nom 6.1.2",
|
|
||||||
"owning_ref",
|
|
||||||
"parking_lot",
|
"parking_lot",
|
||||||
|
"pest",
|
||||||
|
"pest_derive",
|
||||||
"tokio",
|
"tokio",
|
||||||
"tokio-rustls",
|
"tokio-rustls",
|
||||||
"webpki-roots",
|
"webpki-roots",
|
||||||
|
@ -793,6 +817,49 @@ dependencies = [
|
||||||
"winapi",
|
"winapi",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "pest"
|
||||||
|
version = "2.1.3"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "10f4872ae94d7b90ae48754df22fd42ad52ce740b8f370b03da4835417403e53"
|
||||||
|
dependencies = [
|
||||||
|
"ucd-trie",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "pest_derive"
|
||||||
|
version = "2.1.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "833d1ae558dc601e9a60366421196a8d94bc0ac980476d0b67e1d0988d72b2d0"
|
||||||
|
dependencies = [
|
||||||
|
"pest",
|
||||||
|
"pest_generator",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "pest_generator"
|
||||||
|
version = "2.1.3"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "99b8db626e31e5b81787b9783425769681b347011cc59471e33ea46d2ea0cf55"
|
||||||
|
dependencies = [
|
||||||
|
"pest",
|
||||||
|
"pest_meta",
|
||||||
|
"proc-macro2",
|
||||||
|
"quote",
|
||||||
|
"syn",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "pest_meta"
|
||||||
|
version = "2.1.3"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "54be6e404f5317079812fc8f9f5279de376d8856929e21c184ecf6bbd692a11d"
|
||||||
|
dependencies = [
|
||||||
|
"maplit",
|
||||||
|
"pest",
|
||||||
|
"sha-1",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "pin-project"
|
name = "pin-project"
|
||||||
version = "1.0.5"
|
version = "1.0.5"
|
||||||
|
@ -891,12 +958,6 @@ dependencies = [
|
||||||
"proc-macro2",
|
"proc-macro2",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "radium"
|
|
||||||
version = "0.5.3"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "941ba9d78d8e2f7ce474c015eea4d9c6d25b6a3327f9832ee29a4de27f91bbb8"
|
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "rand"
|
name = "rand"
|
||||||
version = "0.8.3"
|
version = "0.8.3"
|
||||||
|
@ -1093,6 +1154,18 @@ dependencies = [
|
||||||
"serde",
|
"serde",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "sha-1"
|
||||||
|
version = "0.8.2"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "f7d94d0bede923b3cea61f3f1ff57ff8cdfd77b400fb8f9998949e0cf04163df"
|
||||||
|
dependencies = [
|
||||||
|
"block-buffer",
|
||||||
|
"digest",
|
||||||
|
"fake-simd",
|
||||||
|
"opaque-debug",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "signal-hook"
|
name = "signal-hook"
|
||||||
version = "0.1.17"
|
version = "0.1.17"
|
||||||
|
@ -1142,12 +1215,6 @@ version = "0.5.2"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "6e63cff320ae2c57904679ba7cb63280a3dc4613885beafb148ee7bf9aa9042d"
|
checksum = "6e63cff320ae2c57904679ba7cb63280a3dc4613885beafb148ee7bf9aa9042d"
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "stable_deref_trait"
|
|
||||||
version = "1.2.0"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "a8f112729512f8e442d81f95a8a7ddf2b7c6b8a1a6f509a95864142b30cab2d3"
|
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "strsim"
|
name = "strsim"
|
||||||
version = "0.8.0"
|
version = "0.8.0"
|
||||||
|
@ -1195,12 +1262,6 @@ dependencies = [
|
||||||
"unicode-xid",
|
"unicode-xid",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "tap"
|
|
||||||
version = "1.0.1"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "55937e1799185b12863d447f42597ed69d9928686b8d88a1df17376a097d8369"
|
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "tempfile"
|
name = "tempfile"
|
||||||
version = "3.2.0"
|
version = "3.2.0"
|
||||||
|
@ -1314,6 +1375,18 @@ dependencies = [
|
||||||
"serde",
|
"serde",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "typenum"
|
||||||
|
version = "1.12.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "373c8a200f9e67a0c95e62a4f52fbf80c23b4381c05a17845531982fa99e6b33"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "ucd-trie"
|
||||||
|
version = "0.1.3"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "56dee185309b50d1f11bfedef0fe6d036842e3fb77413abef29f8f8d1c5d4c1c"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "unicode-segmentation"
|
name = "unicode-segmentation"
|
||||||
version = "1.7.1"
|
version = "1.7.1"
|
||||||
|
@ -1482,12 +1555,6 @@ dependencies = [
|
||||||
"winapi",
|
"winapi",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "wyz"
|
|
||||||
version = "0.2.0"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "85e60b0d1b5f99db2556934e21937020776a5d31520bf169e851ac44e6420214"
|
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "xdg"
|
name = "xdg"
|
||||||
version = "2.2.0"
|
version = "2.2.0"
|
||||||
|
|
|
@ -16,6 +16,7 @@ Goals:
|
||||||
Stretch goals:
|
Stretch goals:
|
||||||
- Unified "feed" that any app can submit to.
|
- Unified "feed" that any app can submit to.
|
||||||
- Submit notifications to gotify-shaped notification servers.
|
- Submit notifications to gotify-shaped notification servers.
|
||||||
|
- JMAP implementation.
|
||||||
- RSS aggregator.
|
- RSS aggregator.
|
||||||
- IRC client??
|
- IRC client??
|
||||||
|
|
||||||
|
|
|
@ -1,2 +1,2 @@
|
||||||
src/builders
|
src/builders
|
||||||
src/parser
|
src/oldparser
|
||||||
|
|
|
@ -16,9 +16,11 @@ anyhow = "1.0.38"
|
||||||
derive_builder = "0.9.0"
|
derive_builder = "0.9.0"
|
||||||
futures = "0.3.12"
|
futures = "0.3.12"
|
||||||
log = "0.4.14"
|
log = "0.4.14"
|
||||||
nom = { version = "6.1.2", default-features = false, features = ["std"] }
|
# nom = { version = "6.1.2", default-features = false, features = ["std"] }
|
||||||
owning_ref = "0.4.1"
|
# owning_ref = "0.4.1"
|
||||||
parking_lot = "0.11.1"
|
parking_lot = "0.11.1"
|
||||||
|
pest = "2.1.3"
|
||||||
|
pest_derive = "2.1.0"
|
||||||
tokio = { version = "1.1.1", features = ["full"] }
|
tokio = { version = "1.1.1", features = ["full"] }
|
||||||
tokio-rustls = "0.22.0"
|
tokio-rustls = "0.22.0"
|
||||||
webpki-roots = "0.21.0"
|
webpki-roots = "0.21.0"
|
||||||
|
|
|
@ -1,17 +1,17 @@
|
||||||
use std::collections::HashMap;
|
use std::collections::{HashMap, HashSet, VecDeque};
|
||||||
|
use std::mem;
|
||||||
use std::pin::Pin;
|
use std::pin::Pin;
|
||||||
use std::sync::Arc;
|
use std::sync::Arc;
|
||||||
use std::task::{Context, Poll, Waker};
|
use std::task::{Context, Poll, Waker};
|
||||||
|
|
||||||
use anyhow::{Context as AnyhowContext, Result};
|
use anyhow::{Context as AnyhowContext, Result};
|
||||||
use futures::future::{self, Either, Future, FutureExt};
|
use futures::future::{self, Either, Future, FutureExt};
|
||||||
use parking_lot::{Mutex, RwLock};
|
use parking_lot::{RwLock, RwLockWriteGuard};
|
||||||
use tokio::{
|
use tokio::{
|
||||||
io::{
|
io::{
|
||||||
self, AsyncBufRead, AsyncBufReadExt, AsyncRead, AsyncWrite, AsyncWriteExt, BufReader,
|
self, AsyncBufReadExt, AsyncRead, AsyncWrite, AsyncWriteExt, BufReader, ReadHalf, WriteHalf,
|
||||||
ReadHalf, WriteHalf,
|
|
||||||
},
|
},
|
||||||
sync::{mpsc, oneshot},
|
sync::mpsc,
|
||||||
task::JoinHandle,
|
task::JoinHandle,
|
||||||
};
|
};
|
||||||
use tokio_rustls::{
|
use tokio_rustls::{
|
||||||
|
@ -19,14 +19,15 @@ use tokio_rustls::{
|
||||||
};
|
};
|
||||||
|
|
||||||
use crate::command::Command;
|
use crate::command::Command;
|
||||||
use crate::response::{Capability, Response, ResponseCode};
|
use crate::parser::{parse_capability, parse_response};
|
||||||
use crate::types::Status;
|
use crate::response::{Capability, Response, ResponseCode, Status};
|
||||||
|
// use crate::types::{Capability as Capability_, Status};
|
||||||
|
|
||||||
use super::ClientConfig;
|
use super::ClientConfig;
|
||||||
|
|
||||||
pub type CapsLock = Arc<RwLock<Option<Vec<Capability>>>>;
|
pub type CapsLock = Arc<RwLock<Option<HashSet<Capability>>>>;
|
||||||
pub type ResultMap = Arc<RwLock<HashMap<usize, (Option<Response>, Option<Waker>)>>>;
|
pub type ResultMap = Arc<RwLock<VecDeque<(usize, Option<Response>, Vec<Response>, Option<Waker>)>>>;
|
||||||
pub type GreetingState = Arc<RwLock<(bool, Option<Waker>)>>;
|
pub type GreetingState = Arc<RwLock<(Option<Response>, Option<Waker>)>>;
|
||||||
pub const TAG_PREFIX: &str = "panorama";
|
pub const TAG_PREFIX: &str = "panorama";
|
||||||
|
|
||||||
/// The lower-level Client struct, that is shared by all of the exported structs in the state machine.
|
/// The lower-level Client struct, that is shared by all of the exported structs in the state machine.
|
||||||
|
@ -57,9 +58,9 @@ where
|
||||||
/// Creates a new client that wraps a connection
|
/// Creates a new client that wraps a connection
|
||||||
pub fn new(conn: C, config: ClientConfig) -> Self {
|
pub fn new(conn: C, config: ClientConfig) -> Self {
|
||||||
let (read_half, write_half) = io::split(conn);
|
let (read_half, write_half) = io::split(conn);
|
||||||
let results = Arc::new(RwLock::new(HashMap::new()));
|
let results = Arc::new(RwLock::new(VecDeque::new()));
|
||||||
let (exit_tx, exit_rx) = mpsc::channel(1);
|
let (exit_tx, exit_rx) = mpsc::channel(1);
|
||||||
let greeting = Arc::new(RwLock::new((false, None)));
|
let greeting = Arc::new(RwLock::new((None, None)));
|
||||||
let caps: CapsLock = Arc::new(RwLock::new(None));
|
let caps: CapsLock = Arc::new(RwLock::new(None));
|
||||||
|
|
||||||
let listener_handle = tokio::spawn(listen(
|
let listener_handle = tokio::spawn(listen(
|
||||||
|
@ -89,13 +90,13 @@ where
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Sends a command to the server and returns a handle to retrieve the result
|
/// Sends a command to the server and returns a handle to retrieve the result
|
||||||
pub async fn execute(&mut self, cmd: Command) -> Result<Response> {
|
pub async fn execute(&mut self, cmd: Command) -> Result<(Response, Vec<Response>)> {
|
||||||
debug!("executing command {:?}", cmd);
|
debug!("executing command {:?}", cmd);
|
||||||
let id = self.id;
|
let id = self.id;
|
||||||
self.id += 1;
|
self.id += 1;
|
||||||
{
|
{
|
||||||
let mut handlers = self.results.write();
|
let mut handlers = self.results.write();
|
||||||
handlers.insert(id, (None, None));
|
handlers.push_back((id, None, vec![], None));
|
||||||
}
|
}
|
||||||
|
|
||||||
let cmd_str = format!("{}{} {}\r\n", TAG_PREFIX, id, cmd);
|
let cmd_str = format!("{}{} {}\r\n", TAG_PREFIX, id, cmd);
|
||||||
|
@ -104,23 +105,39 @@ where
|
||||||
self.conn.flush().await?;
|
self.conn.flush().await?;
|
||||||
debug!("[{}] written.", id);
|
debug!("[{}] written.", id);
|
||||||
|
|
||||||
ExecWaiter(self, id).await;
|
let resp = ExecWaiter(self, id, false).await;
|
||||||
let resp = {
|
// let resp = {
|
||||||
let mut handlers = self.results.write();
|
// let mut handlers = self.results.write();
|
||||||
handlers.remove(&id).unwrap().0.unwrap()
|
// handlers.remove(&id).unwrap().0.unwrap()
|
||||||
};
|
// };
|
||||||
Ok(resp)
|
Ok(resp)
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Executes the CAPABILITY command
|
/// Executes the CAPABILITY command
|
||||||
pub async fn capabilities(&mut self) -> Result<()> {
|
pub async fn capabilities(&mut self, force: bool) -> Result<()> {
|
||||||
|
{
|
||||||
|
let caps = &*self.caps.read();
|
||||||
|
if caps.is_some() && !force {
|
||||||
|
return Ok(());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
let cmd = Command::Capability;
|
let cmd = Command::Capability;
|
||||||
debug!("sending: {:?} {:?}", cmd, cmd.to_string());
|
debug!("sending: {:?} {:?}", cmd, cmd.to_string());
|
||||||
let result = self
|
let (result, intermediate) = self
|
||||||
.execute(cmd)
|
.execute(cmd)
|
||||||
.await
|
.await
|
||||||
.context("error executing CAPABILITY command")?;
|
.context("error executing CAPABILITY command")?;
|
||||||
debug!("cap resp: {:?}", result);
|
debug!("cap resp: {:?}", result);
|
||||||
|
|
||||||
|
if let Some(Response::Capabilities(new_caps)) = intermediate
|
||||||
|
.iter()
|
||||||
|
.find(|resp| matches!(resp, Response::Capabilities(_)))
|
||||||
|
{
|
||||||
|
let caps = &mut *self.caps.write();
|
||||||
|
*caps = Some(new_caps.iter().cloned().collect());
|
||||||
|
}
|
||||||
|
|
||||||
// if let Response::Capabilities(caps) = resp {
|
// if let Response::Capabilities(caps) = resp {
|
||||||
// debug!("capabilities: {:?}", caps);
|
// debug!("capabilities: {:?}", caps);
|
||||||
// }
|
// }
|
||||||
|
@ -130,6 +147,10 @@ where
|
||||||
/// Attempts to upgrade this connection using STARTTLS
|
/// Attempts to upgrade this connection using STARTTLS
|
||||||
pub async fn upgrade(mut self) -> Result<Client<TlsStream<C>>> {
|
pub async fn upgrade(mut self) -> Result<Client<TlsStream<C>>> {
|
||||||
// TODO: make sure STARTTLS is in the capability list
|
// TODO: make sure STARTTLS is in the capability list
|
||||||
|
if !self.has_capability("STARTTLS").await? {
|
||||||
|
bail!("server doesn't support this capability");
|
||||||
|
}
|
||||||
|
|
||||||
// first, send the STARTTLS command
|
// first, send the STARTTLS command
|
||||||
let resp = self.execute(Command::Starttls).await?;
|
let resp = self.execute(Command::Starttls).await?;
|
||||||
debug!("server response to starttls: {:?}", resp);
|
debug!("server response to starttls: {:?}", resp);
|
||||||
|
@ -154,12 +175,28 @@ where
|
||||||
|
|
||||||
Ok(Client::new(stream, self.config))
|
Ok(Client::new(stream, self.config))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Check if this client has a particular capability
|
||||||
|
pub async fn has_capability(&mut self, cap: impl AsRef<str>) -> Result<bool> {
|
||||||
|
let cap = cap.as_ref().to_owned();
|
||||||
|
debug!("checking for the capability: {:?}", cap);
|
||||||
|
let cap = parse_capability(cap)?;
|
||||||
|
|
||||||
|
self.capabilities(false).await?;
|
||||||
|
let caps = &*self.caps.read();
|
||||||
|
// TODO: refresh caps
|
||||||
|
|
||||||
|
let caps = caps.as_ref().unwrap();
|
||||||
|
let result = caps.contains(&cap);
|
||||||
|
debug!("cap result: {:?}", result);
|
||||||
|
Ok(result)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub struct GreetingWaiter(GreetingState);
|
pub struct GreetingWaiter(GreetingState);
|
||||||
|
|
||||||
impl Future for GreetingWaiter {
|
impl Future for GreetingWaiter {
|
||||||
type Output = ();
|
type Output = Response;
|
||||||
fn poll(self: Pin<&mut Self>, cx: &mut Context) -> Poll<Self::Output> {
|
fn poll(self: Pin<&mut Self>, cx: &mut Context) -> Poll<Self::Output> {
|
||||||
let (state, waker) = &mut *self.0.write();
|
let (state, waker) = &mut *self.0.write();
|
||||||
debug!("g {:?}", state);
|
debug!("g {:?}", state);
|
||||||
|
@ -167,32 +204,43 @@ impl Future for GreetingWaiter {
|
||||||
*waker = Some(cx.waker().clone());
|
*waker = Some(cx.waker().clone());
|
||||||
}
|
}
|
||||||
|
|
||||||
match state {
|
match state.take() {
|
||||||
true => Poll::Ready(()),
|
Some(v) => Poll::Ready(v),
|
||||||
false => Poll::Pending,
|
None => Poll::Pending,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub struct ExecWaiter<'a, C>(&'a Client<C>, usize);
|
pub struct ExecWaiter<'a, C>(&'a Client<C>, usize, bool);
|
||||||
|
|
||||||
impl<'a, C> Future for ExecWaiter<'a, C> {
|
impl<'a, C> Future for ExecWaiter<'a, C> {
|
||||||
type Output = ();
|
type Output = (Response, Vec<Response>);
|
||||||
fn poll(self: Pin<&mut Self>, cx: &mut Context) -> Poll<Self::Output> {
|
fn poll(mut self: Pin<&mut Self>, cx: &mut Context) -> Poll<Self::Output> {
|
||||||
let mut handlers = self.0.results.write();
|
// add the waker
|
||||||
let state = handlers.get_mut(&self.1);
|
let mut results = self.0.results.write();
|
||||||
|
if !self.2 {
|
||||||
// TODO: handle the None case here
|
if let Some((_, _, _, waker_ref)) =
|
||||||
debug!("f[{}] {:?}", self.1, state);
|
results.iter_mut().find(|(id, _, _, _)| *id == self.1)
|
||||||
let (result, waker) = state.unwrap();
|
{
|
||||||
|
let waker = cx.waker().clone();
|
||||||
match result {
|
*waker_ref = Some(waker);
|
||||||
Some(_) => Poll::Ready(()),
|
self.2 = true;
|
||||||
None => {
|
|
||||||
*waker = Some(cx.waker().clone());
|
|
||||||
Poll::Pending
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// if this struct exists then there's definitely at least one entry
|
||||||
|
let (id, last_response, _, _) = &results[0];
|
||||||
|
if *id != self.1 || last_response.is_none() {
|
||||||
|
return Poll::Pending;
|
||||||
|
}
|
||||||
|
|
||||||
|
let (_, last_response, intermediate_responses, _) = results.pop_front().unwrap();
|
||||||
|
mem::drop(results);
|
||||||
|
|
||||||
|
Poll::Ready((
|
||||||
|
last_response.expect("already checked"),
|
||||||
|
intermediate_responses,
|
||||||
|
))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -226,26 +274,19 @@ where
|
||||||
}
|
}
|
||||||
|
|
||||||
debug!("got a new line {:?}", next_line);
|
debug!("got a new line {:?}", next_line);
|
||||||
let (_, resp) = match crate::parser::parse_response(next_line.as_bytes()) {
|
let resp = parse_response(next_line)?;
|
||||||
Ok(v) => v,
|
|
||||||
Err(err) => {
|
|
||||||
debug!("shiet: {:?}", err);
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
if let Some(greeting) = greeting.take() {
|
if let Some(greeting) = greeting.take() {
|
||||||
let (greeting, waker) = &mut *greeting.write();
|
let (greeting, waker) = &mut *greeting.write();
|
||||||
debug!("received greeting!");
|
debug!("received greeting!");
|
||||||
*greeting = true;
|
*greeting = Some(resp.clone());
|
||||||
if let Some(waker) = waker.take() {
|
if let Some(waker) = waker.take() {
|
||||||
waker.wake();
|
waker.wake();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
let resp = Response::from(resp);
|
|
||||||
debug!("resp: {:?}", resp);
|
|
||||||
match &resp {
|
match &resp {
|
||||||
|
// capabilities list
|
||||||
Response::Capabilities(new_caps)
|
Response::Capabilities(new_caps)
|
||||||
| Response::Data {
|
| Response::Data {
|
||||||
status: Status::Ok,
|
status: Status::Ok,
|
||||||
|
@ -253,17 +294,24 @@ where
|
||||||
..
|
..
|
||||||
} => {
|
} => {
|
||||||
let caps = &mut *caps.write();
|
let caps = &mut *caps.write();
|
||||||
*caps = Some(new_caps.clone());
|
*caps = Some(new_caps.iter().cloned().collect());
|
||||||
debug!("new caps: {:?}", caps);
|
debug!("new caps: {:?}", caps);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// bye
|
||||||
|
Response::Data {
|
||||||
|
status: Status::Bye,
|
||||||
|
..
|
||||||
|
} => {
|
||||||
|
bail!("disconnected");
|
||||||
|
}
|
||||||
|
|
||||||
Response::Done { tag, .. } => {
|
Response::Done { tag, .. } => {
|
||||||
let tag_str = &tag.0;
|
if tag.starts_with(TAG_PREFIX) {
|
||||||
if tag_str.starts_with(TAG_PREFIX) {
|
let id = tag.trim_start_matches(TAG_PREFIX).parse::<usize>()?;
|
||||||
let id = tag_str.trim_start_matches(TAG_PREFIX).parse::<usize>()?;
|
|
||||||
let mut results = results.write();
|
let mut results = results.write();
|
||||||
if let Some((c, waker)) = results.get_mut(&id) {
|
if let Some((_, opt, _, waker)) = results.iter_mut().next() {
|
||||||
*c = Some(resp);
|
*opt = Some(resp);
|
||||||
if let Some(waker) = waker.take() {
|
if let Some(waker) = waker.take() {
|
||||||
waker.wake();
|
waker.wake();
|
||||||
}
|
}
|
||||||
|
@ -271,7 +319,7 @@ where
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
_ => todo!("unhandled response: {:?}", resp),
|
_ => {}
|
||||||
}
|
}
|
||||||
|
|
||||||
// debug!("parsed as: {:?}", resp);
|
// debug!("parsed as: {:?}", resp);
|
||||||
|
|
|
@ -43,6 +43,9 @@ use tokio_rustls::{
|
||||||
client::TlsStream, rustls::ClientConfig as RustlsConfig, webpki::DNSNameRef, TlsConnector,
|
client::TlsStream, rustls::ClientConfig as RustlsConfig, webpki::DNSNameRef, TlsConnector,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
use crate::command::Command;
|
||||||
|
use crate::response::Response;
|
||||||
|
|
||||||
pub use self::inner::Client;
|
pub use self::inner::Client;
|
||||||
|
|
||||||
/// Struct used to start building the config for a client.
|
/// Struct used to start building the config for a client.
|
||||||
|
@ -118,12 +121,20 @@ impl ClientUnauthenticated {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub async fn capabilities(&mut self) -> Result<()> {
|
/// TODO: Exposing low-level execute , shoudl remove later
|
||||||
|
pub async fn execute(&mut self, cmd: Command) -> Result<(Response, Vec<Response>)> {
|
||||||
match self {
|
match self {
|
||||||
ClientUnauthenticated::Encrypted(e) => e.inner.capabilities().await?,
|
ClientUnauthenticated::Encrypted(e) => e.inner.execute(cmd).await,
|
||||||
ClientUnauthenticated::Unencrypted(e) => e.inner.capabilities().await?,
|
ClientUnauthenticated::Unencrypted(e) => e.inner.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.inner.has_capability(cap).await,
|
||||||
|
ClientUnauthenticated::Unencrypted(e) => e.inner.has_capability(cap).await,
|
||||||
}
|
}
|
||||||
Ok(())
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -5,6 +5,7 @@ use std::fmt;
|
||||||
pub enum Command {
|
pub enum Command {
|
||||||
Capability,
|
Capability,
|
||||||
Starttls,
|
Starttls,
|
||||||
|
Login { username: String, password: String },
|
||||||
}
|
}
|
||||||
|
|
||||||
impl fmt::Display for Command {
|
impl fmt::Display for Command {
|
||||||
|
@ -12,6 +13,7 @@ impl fmt::Display for Command {
|
||||||
match self {
|
match self {
|
||||||
Command::Capability => write!(f, "CAPABILITY"),
|
Command::Capability => write!(f, "CAPABILITY"),
|
||||||
Command::Starttls => write!(f, "STARTTLS"),
|
Command::Starttls => write!(f, "STARTTLS"),
|
||||||
|
Command::Login { username, password } => write!(f, "LOGIN {} {}", username, password),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -6,13 +6,17 @@ extern crate derive_builder;
|
||||||
extern crate futures;
|
extern crate futures;
|
||||||
#[macro_use]
|
#[macro_use]
|
||||||
extern crate log;
|
extern crate log;
|
||||||
|
#[macro_use]
|
||||||
|
extern crate pest_derive;
|
||||||
|
|
||||||
pub mod builders;
|
|
||||||
pub mod client;
|
pub mod client;
|
||||||
pub mod command;
|
pub mod command;
|
||||||
pub mod parser;
|
pub mod parser;
|
||||||
pub mod response;
|
pub mod response;
|
||||||
pub mod types;
|
|
||||||
|
|
||||||
pub use crate::parser::ParseResult;
|
// pub mod builders;
|
||||||
pub use crate::types::*;
|
// pub mod oldparser;
|
||||||
|
// pub mod types;
|
||||||
|
|
||||||
|
// pub use crate::oldparser::ParseResult;
|
||||||
|
// pub use crate::types::*;
|
||||||
|
|
25
imap/src/oldparser/mod.rs
Normal file
25
imap/src/oldparser/mod.rs
Normal file
|
@ -0,0 +1,25 @@
|
||||||
|
use crate::types::Response;
|
||||||
|
use nom::{branch::alt, IResult};
|
||||||
|
|
||||||
|
pub mod core;
|
||||||
|
|
||||||
|
pub mod bodystructure;
|
||||||
|
pub mod rfc3501;
|
||||||
|
pub mod rfc4315;
|
||||||
|
pub mod rfc4551;
|
||||||
|
pub mod rfc5161;
|
||||||
|
pub mod rfc5464;
|
||||||
|
pub mod rfc7162;
|
||||||
|
|
||||||
|
#[cfg(test)]
|
||||||
|
mod tests;
|
||||||
|
|
||||||
|
pub fn parse_response(msg: &[u8]) -> ParseResult {
|
||||||
|
alt((
|
||||||
|
rfc3501::continue_req,
|
||||||
|
rfc3501::response_data,
|
||||||
|
rfc3501::response_tagged,
|
||||||
|
))(msg)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub type ParseResult<'a> = IResult<&'a [u8], Response<'a>>;
|
|
@ -8,7 +8,7 @@ use nom::{
|
||||||
IResult,
|
IResult,
|
||||||
};
|
};
|
||||||
|
|
||||||
use crate::{parser::core::*, types::*};
|
use crate::{oldparser::core::*, types::*};
|
||||||
|
|
||||||
pub fn section_part(i: &[u8]) -> IResult<&[u8], Vec<u32>> {
|
pub fn section_part(i: &[u8]) -> IResult<&[u8], Vec<u32>> {
|
||||||
let (i, (part, mut rest)) = tuple((number, many0(preceded(char('.'), number))))(i)?;
|
let (i, (part, mut rest)) = tuple((number, many0(preceded(char('.'), number))))(i)?;
|
|
@ -9,7 +9,7 @@ use nom::{
|
||||||
};
|
};
|
||||||
|
|
||||||
use crate::{
|
use crate::{
|
||||||
parser::{core::*, rfc3501::envelope},
|
oldparser::{core::*, rfc3501::envelope},
|
||||||
types::*,
|
types::*,
|
||||||
};
|
};
|
||||||
|
|
|
@ -17,7 +17,7 @@ use nom::{
|
||||||
};
|
};
|
||||||
|
|
||||||
use crate::{
|
use crate::{
|
||||||
parser::{
|
oldparser::{
|
||||||
core::*, rfc3501::body::*, rfc3501::body_structure::*, rfc4315, rfc4551, rfc5161, rfc5464,
|
core::*, rfc3501::body::*, rfc3501::body_structure::*, rfc4315, rfc4551, rfc5161, rfc5464,
|
||||||
rfc7162,
|
rfc7162,
|
||||||
},
|
},
|
||||||
|
@ -181,7 +181,7 @@ fn resp_text_code(i: &[u8]) -> IResult<&[u8], ResponseCode> {
|
||||||
)(i)
|
)(i)
|
||||||
}
|
}
|
||||||
|
|
||||||
fn capability(i: &[u8]) -> IResult<&[u8], Capability> {
|
pub fn capability(i: &[u8]) -> IResult<&[u8], Capability> {
|
||||||
alt((
|
alt((
|
||||||
map(tag_no_case(b"IMAP4rev1"), |_| Capability::Imap4rev1),
|
map(tag_no_case(b"IMAP4rev1"), |_| Capability::Imap4rev1),
|
||||||
map(preceded(tag_no_case(b"AUTH="), atom), Capability::Auth),
|
map(preceded(tag_no_case(b"AUTH="), atom), Capability::Auth),
|
|
@ -13,7 +13,7 @@ use nom::{
|
||||||
IResult,
|
IResult,
|
||||||
};
|
};
|
||||||
|
|
||||||
use crate::parser::core::number;
|
use crate::oldparser::core::number;
|
||||||
use crate::types::*;
|
use crate::types::*;
|
||||||
|
|
||||||
/// Extends resp-text-code as follows:
|
/// Extends resp-text-code as follows:
|
|
@ -8,7 +8,7 @@
|
||||||
use nom::{bytes::streaming::tag_no_case, sequence::tuple, IResult};
|
use nom::{bytes::streaming::tag_no_case, sequence::tuple, IResult};
|
||||||
|
|
||||||
use crate::{
|
use crate::{
|
||||||
parser::core::{number_64, paren_delimited},
|
oldparser::core::{number_64, paren_delimited},
|
||||||
types::*,
|
types::*,
|
||||||
};
|
};
|
||||||
|
|
|
@ -13,7 +13,7 @@ use nom::{
|
||||||
IResult,
|
IResult,
|
||||||
};
|
};
|
||||||
|
|
||||||
use crate::parser::core::atom;
|
use crate::oldparser::core::atom;
|
||||||
use crate::types::*;
|
use crate::types::*;
|
||||||
|
|
||||||
// The ENABLED response lists capabilities that were enabled in response
|
// The ENABLED response lists capabilities that were enabled in response
|
||||||
|
@ -23,7 +23,7 @@ pub(crate) fn resp_enabled(i: &[u8]) -> IResult<&[u8], Response> {
|
||||||
map(enabled_data, Response::Capabilities)(i)
|
map(enabled_data, Response::Capabilities)(i)
|
||||||
}
|
}
|
||||||
|
|
||||||
fn enabled_data(i: &[u8]) -> IResult<&[u8], Vec<Capability>> {
|
pub fn enabled_data(i: &[u8]) -> IResult<&[u8], Vec<Capability>> {
|
||||||
let (i, (_, capabilities)) = tuple((
|
let (i, (_, capabilities)) = tuple((
|
||||||
tag_no_case("ENABLED"),
|
tag_no_case("ENABLED"),
|
||||||
many0(preceded(char(' '), capability)),
|
many0(preceded(char(' '), capability)),
|
||||||
|
@ -31,6 +31,6 @@ fn enabled_data(i: &[u8]) -> IResult<&[u8], Vec<Capability>> {
|
||||||
Ok((i, capabilities))
|
Ok((i, capabilities))
|
||||||
}
|
}
|
||||||
|
|
||||||
fn capability(i: &[u8]) -> IResult<&[u8], Capability> {
|
pub fn capability(i: &[u8]) -> IResult<&[u8], Capability> {
|
||||||
map(atom, Capability::Atom)(i)
|
map(atom, Capability::Atom)(i)
|
||||||
}
|
}
|
|
@ -13,7 +13,7 @@ use nom::{
|
||||||
IResult,
|
IResult,
|
||||||
};
|
};
|
||||||
|
|
||||||
use crate::{parser::core::*, types::*};
|
use crate::{oldparser::core::*, types::*};
|
||||||
|
|
||||||
fn is_entry_component_char(c: u8) -> bool {
|
fn is_entry_component_char(c: u8) -> bool {
|
||||||
c < 0x80 && c > 0x19 && c != b'*' && c != b'%' && c != b'/'
|
c < 0x80 && c > 0x19 && c != b'*' && c != b'%' && c != b'/'
|
|
@ -10,7 +10,7 @@ use nom::{
|
||||||
IResult,
|
IResult,
|
||||||
};
|
};
|
||||||
|
|
||||||
use crate::parser::core::sequence_set;
|
use crate::oldparser::core::sequence_set;
|
||||||
use crate::types::*;
|
use crate::types::*;
|
||||||
|
|
||||||
// The VANISHED response reports that the specified UIDs have been
|
// The VANISHED response reports that the specified UIDs have been
|
|
@ -1,25 +1,318 @@
|
||||||
use crate::types::Response;
|
use std::fmt::Debug;
|
||||||
use nom::{branch::alt, IResult};
|
use std::str::FromStr;
|
||||||
|
|
||||||
pub mod core;
|
use pest::{
|
||||||
|
error::Error,
|
||||||
|
iterators::{Pair, Pairs},
|
||||||
|
Parser,
|
||||||
|
};
|
||||||
|
|
||||||
pub mod bodystructure;
|
use crate::response::*;
|
||||||
pub mod rfc3501;
|
|
||||||
pub mod rfc4315;
|
|
||||||
pub mod rfc4551;
|
|
||||||
pub mod rfc5161;
|
|
||||||
pub mod rfc5464;
|
|
||||||
pub mod rfc7162;
|
|
||||||
|
|
||||||
#[cfg(test)]
|
#[derive(Parser)]
|
||||||
mod tests;
|
#[grammar = "parser/rfc3501.pest"]
|
||||||
|
struct Rfc3501;
|
||||||
|
|
||||||
pub fn parse_response(msg: &[u8]) -> ParseResult {
|
pub fn parse_capability(s: impl AsRef<str>) -> Result<Capability, Error<Rule>> {
|
||||||
alt((
|
let mut pairs = Rfc3501::parse(Rule::capability, s.as_ref())?;
|
||||||
rfc3501::continue_req,
|
let pair = pairs.next().unwrap();
|
||||||
rfc3501::response_data,
|
Ok(build_capability(pair))
|
||||||
rfc3501::response_tagged,
|
|
||||||
))(msg)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
pub type ParseResult<'a> = IResult<&'a [u8], Response<'a>>;
|
pub fn parse_response(s: impl AsRef<str>) -> Result<Response, Error<Rule>> {
|
||||||
|
let mut pairs = Rfc3501::parse(Rule::response, s.as_ref())?;
|
||||||
|
let pair = pairs.next().unwrap();
|
||||||
|
Ok(build_response(pair))
|
||||||
|
}
|
||||||
|
|
||||||
|
fn build_response(pair: Pair<Rule>) -> Response {
|
||||||
|
if !matches!(pair.as_rule(), Rule::response) {
|
||||||
|
unreachable!("{:#?}", pair);
|
||||||
|
}
|
||||||
|
|
||||||
|
let mut pairs = pair.into_inner();
|
||||||
|
let pair = pairs.next().unwrap();
|
||||||
|
match pair.as_rule() {
|
||||||
|
Rule::response_done => {
|
||||||
|
let mut pairs = pair.into_inner();
|
||||||
|
let pair = pairs.next().unwrap();
|
||||||
|
match pair.as_rule() {
|
||||||
|
Rule::response_tagged => {
|
||||||
|
let mut pairs = pair.into_inner();
|
||||||
|
let pair = pairs.next().unwrap();
|
||||||
|
let tag = pair.as_str().to_owned();
|
||||||
|
|
||||||
|
let pair = pairs.next().unwrap();
|
||||||
|
let (status, code, information) = build_resp_cond_state(pair);
|
||||||
|
Response::Done {
|
||||||
|
tag,
|
||||||
|
status,
|
||||||
|
code,
|
||||||
|
information,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
_ => unreachable!("{:#?}", pair),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Rule::response_data => {
|
||||||
|
let mut pairs = pair.into_inner();
|
||||||
|
let pair = pairs.next().unwrap();
|
||||||
|
match pair.as_rule() {
|
||||||
|
Rule::resp_cond_state => {
|
||||||
|
let (status, code, information) = build_resp_cond_state(pair);
|
||||||
|
Response::Data {
|
||||||
|
status,
|
||||||
|
code,
|
||||||
|
information,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Rule::mailbox_data => Response::MailboxData(build_mailbox_data(pair)),
|
||||||
|
Rule::capability_data => Response::Capabilities(build_capabilities(pair)),
|
||||||
|
_ => unreachable!("{:#?}", pair),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
_ => unreachable!("{:#?}", pair),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn build_resp_cond_state(pair: Pair<Rule>) -> (Status, Option<ResponseCode>, Option<String>) {
|
||||||
|
if !matches!(pair.as_rule(), Rule::resp_cond_state) {
|
||||||
|
unreachable!("{:#?}", pair);
|
||||||
|
}
|
||||||
|
|
||||||
|
let mut pairs = pair.into_inner();
|
||||||
|
let pair = pairs.next().unwrap();
|
||||||
|
let status = build_status(pair);
|
||||||
|
let mut code = None;
|
||||||
|
let mut information = None;
|
||||||
|
|
||||||
|
let pair = pairs.next().unwrap();
|
||||||
|
let pairs = pair.into_inner();
|
||||||
|
for pair in pairs {
|
||||||
|
match pair.as_rule() {
|
||||||
|
Rule::resp_text_code => code = build_resp_code(pair),
|
||||||
|
Rule::text => information = Some(pair.as_str().to_owned()),
|
||||||
|
_ => unreachable!("{:#?}", pair),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
(status, code, information)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn build_resp_code(pair: Pair<Rule>) -> Option<ResponseCode> {
|
||||||
|
if !matches!(pair.as_rule(), Rule::resp_text_code) {
|
||||||
|
unreachable!("{:#?}", pair);
|
||||||
|
}
|
||||||
|
|
||||||
|
// panic!("pair: {:#?}", pair);
|
||||||
|
debug!("pair: {:#?}", pair);
|
||||||
|
|
||||||
|
let mut pairs = pair.into_inner();
|
||||||
|
let pair = pairs.next()?;
|
||||||
|
Some(match pair.as_rule() {
|
||||||
|
Rule::capability_data => ResponseCode::Capabilities(build_capabilities(pair)),
|
||||||
|
Rule::resp_text_code_readwrite => ResponseCode::ReadWrite,
|
||||||
|
Rule::resp_text_code_uidvalidity => ResponseCode::UidValidity(build_number(pair)),
|
||||||
|
Rule::resp_text_code_unseen => ResponseCode::Unseen(build_number(pair)),
|
||||||
|
_ => unreachable!("{:#?}", pair),
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
fn build_capability(pair: Pair<Rule>) -> Capability {
|
||||||
|
if !matches!(pair.as_rule(), Rule::capability) {
|
||||||
|
unreachable!("{:#?}", pair);
|
||||||
|
}
|
||||||
|
|
||||||
|
let mut pairs = pair.into_inner();
|
||||||
|
let pair = pairs.next().unwrap();
|
||||||
|
match pair.as_rule() {
|
||||||
|
Rule::auth_type => Capability::Auth(pair.as_str().to_uppercase().to_owned()),
|
||||||
|
Rule::atom => match pair.as_str() {
|
||||||
|
"IMAP4rev1" => Capability::Imap4rev1,
|
||||||
|
s => Capability::Atom(s.to_uppercase().to_owned()),
|
||||||
|
},
|
||||||
|
_ => unreachable!("{:?}", pair),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn build_capabilities(pair: Pair<Rule>) -> Vec<Capability> {
|
||||||
|
if !matches!(pair.as_rule(), Rule::capability_data) {
|
||||||
|
unreachable!("{:#?}", pair);
|
||||||
|
}
|
||||||
|
|
||||||
|
pair.into_inner().map(build_capability).collect()
|
||||||
|
}
|
||||||
|
|
||||||
|
fn build_status(pair: Pair<Rule>) -> Status {
|
||||||
|
match pair.as_rule() {
|
||||||
|
Rule::resp_status => match pair.as_str().to_uppercase().as_str() {
|
||||||
|
"OK" => Status::Ok,
|
||||||
|
"NO" => Status::No,
|
||||||
|
"BAD" => Status::Bad,
|
||||||
|
s => unreachable!("invalid status {:?}", s),
|
||||||
|
},
|
||||||
|
_ => unreachable!("{:?}", pair),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn build_flag_list(pair: Pair<Rule>) -> Vec<Flag> {
|
||||||
|
if !matches!(pair.as_rule(), Rule::flag_list) {
|
||||||
|
unreachable!("{:#?}", pair);
|
||||||
|
}
|
||||||
|
|
||||||
|
pair.into_inner().map(build_flag).collect()
|
||||||
|
}
|
||||||
|
|
||||||
|
fn build_flag(pair: Pair<Rule>) -> Flag {
|
||||||
|
if !matches!(pair.as_rule(), Rule::flag) {
|
||||||
|
unreachable!("{:#?}", pair);
|
||||||
|
}
|
||||||
|
|
||||||
|
match pair.as_str() {
|
||||||
|
"\\Answered" => Flag::Answered,
|
||||||
|
"\\Flagged" => Flag::Flagged,
|
||||||
|
"\\Deleted" => Flag::Deleted,
|
||||||
|
"\\Seen" => Flag::Seen,
|
||||||
|
"\\Draft" => Flag::Draft,
|
||||||
|
s if s.starts_with("\\") => Flag::Ext(s.to_owned()),
|
||||||
|
_ => unreachable!("{:#?}", pair.as_str()),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn build_mailbox_data(pair: Pair<Rule>) -> MailboxData {
|
||||||
|
if !matches!(pair.as_rule(), Rule::mailbox_data) {
|
||||||
|
unreachable!("{:#?}", pair);
|
||||||
|
}
|
||||||
|
|
||||||
|
let mut pairs = pair.into_inner();
|
||||||
|
let pair = pairs.next().unwrap();
|
||||||
|
match pair.as_rule() {
|
||||||
|
Rule::mailbox_data_exists => MailboxData::Exists(build_number(pair)),
|
||||||
|
Rule::mailbox_data_flags => {
|
||||||
|
let mut pairs = pair.into_inner();
|
||||||
|
let pair = pairs.next().unwrap();
|
||||||
|
let flags = build_flag_list(pair);
|
||||||
|
MailboxData::Flags(flags)
|
||||||
|
}
|
||||||
|
Rule::mailbox_data_recent => MailboxData::Recent(build_number(pair)),
|
||||||
|
_ => unreachable!("{:#?}", pair),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn build_number<T>(pair: Pair<Rule>) -> T
|
||||||
|
where
|
||||||
|
T: FromStr,
|
||||||
|
T::Err: Debug,
|
||||||
|
{
|
||||||
|
let mut pairs = pair.into_inner();
|
||||||
|
let pair = pairs.next().unwrap();
|
||||||
|
pair.as_str().parse::<T>().unwrap()
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(test)]
|
||||||
|
mod tests {
|
||||||
|
use super::*;
|
||||||
|
use crate::response::*;
|
||||||
|
use pest::Parser;
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
#[rustfmt::skip]
|
||||||
|
fn test_capability() {
|
||||||
|
assert_eq!(parse_capability("IMAP4rev1"), Ok(Capability::Imap4rev1));
|
||||||
|
assert_eq!(parse_capability("LOGINDISABLED"), Ok(Capability::Atom("LOGINDISABLED".to_owned())));
|
||||||
|
assert_eq!(parse_capability("AUTH=PLAIN"), Ok(Capability::Auth("PLAIN".to_owned())));
|
||||||
|
assert_eq!(parse_capability("auth=plain"), Ok(Capability::Auth("PLAIN".to_owned())));
|
||||||
|
|
||||||
|
assert!(parse_capability("(OSU)").is_err());
|
||||||
|
assert!(parse_capability("\x01HELLO").is_err());
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
#[rustfmt::skip]
|
||||||
|
fn test_nil() {
|
||||||
|
assert!(Rfc3501::parse(Rule::nil, "NIL").is_ok());
|
||||||
|
assert!(Rfc3501::parse(Rule::nil, "anything else").is_err());
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_section_8() {
|
||||||
|
// this little exchange is from section 8 of rfc3501
|
||||||
|
// https://tools.ietf.org/html/rfc3501#section-8
|
||||||
|
|
||||||
|
assert_eq!(
|
||||||
|
parse_response("* OK IMAP4rev1 Service Ready\r\n"),
|
||||||
|
Ok(Response::Data {
|
||||||
|
status: Status::Ok,
|
||||||
|
code: None,
|
||||||
|
information: Some("IMAP4rev1 Service Ready".to_owned()),
|
||||||
|
})
|
||||||
|
);
|
||||||
|
|
||||||
|
assert_eq!(
|
||||||
|
parse_response("a001 OK LOGIN completed\r\n"),
|
||||||
|
Ok(Response::Done {
|
||||||
|
tag: "a001".to_owned(),
|
||||||
|
status: Status::Ok,
|
||||||
|
code: None,
|
||||||
|
information: Some("LOGIN completed".to_owned()),
|
||||||
|
})
|
||||||
|
);
|
||||||
|
|
||||||
|
assert_eq!(
|
||||||
|
parse_response("* 18 EXISTS\r\n"),
|
||||||
|
Ok(Response::MailboxData(MailboxData::Exists(18)))
|
||||||
|
);
|
||||||
|
|
||||||
|
assert_eq!(
|
||||||
|
parse_response("* FLAGS (\\Answered \\Flagged \\Deleted \\Seen \\Draft)\r\n"),
|
||||||
|
Ok(Response::MailboxData(MailboxData::Flags(vec![
|
||||||
|
Flag::Answered,
|
||||||
|
Flag::Flagged,
|
||||||
|
Flag::Deleted,
|
||||||
|
Flag::Seen,
|
||||||
|
Flag::Draft,
|
||||||
|
])))
|
||||||
|
);
|
||||||
|
|
||||||
|
assert_eq!(
|
||||||
|
parse_response("* 2 RECENT\r\n"),
|
||||||
|
Ok(Response::MailboxData(MailboxData::Recent(2)))
|
||||||
|
);
|
||||||
|
|
||||||
|
assert_eq!(
|
||||||
|
parse_response("* OK [UNSEEN 17] Message 17 is the first unseen message\r\n"),
|
||||||
|
Ok(Response::Data {
|
||||||
|
status: Status::Ok,
|
||||||
|
code: Some(ResponseCode::Unseen(17)),
|
||||||
|
information: Some("Message 17 is the first unseen message".to_owned()),
|
||||||
|
})
|
||||||
|
);
|
||||||
|
|
||||||
|
assert_eq!(
|
||||||
|
parse_response("* OK [UIDVALIDITY 3857529045] UIDs valid\r\n"),
|
||||||
|
Ok(Response::Data {
|
||||||
|
status: Status::Ok,
|
||||||
|
code: Some(ResponseCode::UidValidity(3857529045)),
|
||||||
|
information: Some("UIDs valid".to_owned()),
|
||||||
|
})
|
||||||
|
);
|
||||||
|
|
||||||
|
assert_eq!(
|
||||||
|
parse_response("a002 OK [READ-WRITE] SELECT completed\r\n"),
|
||||||
|
Ok(Response::Done {
|
||||||
|
tag: "a002".to_owned(),
|
||||||
|
status: Status::Ok,
|
||||||
|
code: Some(ResponseCode::ReadWrite),
|
||||||
|
information: Some("SELECT completed".to_owned()),
|
||||||
|
})
|
||||||
|
);
|
||||||
|
|
||||||
|
// assert_eq!(
|
||||||
|
// parse_response(concat!(
|
||||||
|
// r#"* 12 FETCH (FLAGS (\Seen) INTERNALDATE "17-Jul-1996 02:44:25 -0700" RFC822.SIZE 4286 ENVELOPE ("Wed, 17 Jul 1996 02:23:25 -0700 (PDT)" "IMAP4rev1 WG mtg summary and minutes" (("Terry Gray" NIL "gray" "cac.washington.edu")) (("Terry Gray" NIL "gray" "cac.washington.edu")) (("Terry Gray" NIL "gray" "cac.washington.edu")) ((NIL NIL "imap" "cac.washington.edu")) ((NIL NIL "minutes" "CNRI.Reston.VA.US") ("John Klensin" NIL "KLENSIN" "MIT.EDU")) NIL NIL "<B27397-0100000@cac.washington.edu>") BODY ("TEXT" "PLAIN" ("CHARSET" "US-ASCII") NIL NIL "7BIT" 3028 92))"#,
|
||||||
|
// "\r\n",
|
||||||
|
// )),
|
||||||
|
// Ok(Response::Fetch(12, vec![]))
|
||||||
|
// );
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
131
imap/src/parser/rfc3501.pest
Normal file
131
imap/src/parser/rfc3501.pest
Normal file
|
@ -0,0 +1,131 @@
|
||||||
|
// formal syntax from https://tools.ietf.org/html/rfc3501#section-9
|
||||||
|
addr_adl = { nstring }
|
||||||
|
addr_host = { nstring }
|
||||||
|
addr_mailbox = { nstring }
|
||||||
|
addr_name = { nstring }
|
||||||
|
address = { "(" ~ addr_name ~ sp ~ addr_adl ~ sp ~ addr_mailbox ~ sp ~ addr_host ~ ")" }
|
||||||
|
astring = @{ astring_char{1,} | string }
|
||||||
|
astring_char = @{ atom_char | resp_specials }
|
||||||
|
atom = @{ atom_char{1,} }
|
||||||
|
atom_char = @{ !atom_specials ~ char }
|
||||||
|
atom_specials = @{ "(" | ")" | "{" | sp | ctl | list_wildcards | quoted_specials | resp_specials }
|
||||||
|
auth_type = { atom }
|
||||||
|
base64 = @{ (base64_char{4})* ~ base64_terminal }
|
||||||
|
base64_char = @{ alpha | digit | "+" | "/" }
|
||||||
|
base64_terminal = @{ (base64_char{2} ~ "==") | (base64_char{3} ~ "=") }
|
||||||
|
body = { "(" ~ (body_type_1part | body_type_mpart) ~ ")" }
|
||||||
|
body_ext_1part = { body_fld_md5 ~ (sp ~ body_fld_dsp ~ (sp ~ body_fld_lang ~ (sp ~ body_fld_loc ~ (sp ~ body_extension)*)?)?)? }
|
||||||
|
body_ext_mpart = { body_fld_param ~ (sp ~ body_fld_dsp ~ (sp ~ body_fld_lang ~ (sp ~ body_fld_loc ~ (sp ~ body_extension)*)?)?)? }
|
||||||
|
body_extension = { nstring | number | "(" ~ body_extension ~ (sp ~ body_extension)* ~ ")" }
|
||||||
|
body_fields = { body_fld_param ~ sp ~ body_fld_id ~ sp ~ body_fld_desc ~ sp ~ body_fld_enc ~ sp ~ body_fld_octets }
|
||||||
|
body_fld_desc = { nstring }
|
||||||
|
body_fld_dsp = { "(" ~ string ~ sp ~ body_fld_param ~ ")" | nil }
|
||||||
|
body_fld_enc = { (dquote ~ (^"7BIT" | ^"8BIT" | ^"BINARY" | ^"BASE64" | ^"QUOTED-PRINTABLE") ~ dquote) | string }
|
||||||
|
body_fld_id = { nstring }
|
||||||
|
body_fld_lang = { nstring | "(" ~ string ~ (sp ~ string)* ~ ")" }
|
||||||
|
body_fld_lines = { number }
|
||||||
|
body_fld_loc = { nstring }
|
||||||
|
body_fld_md5 = { nstring }
|
||||||
|
body_fld_octets = { number }
|
||||||
|
body_fld_param = { "(" ~ string ~ sp ~ string ~ (sp ~ string ~ sp ~ string)* ~ ")" | nil}
|
||||||
|
body_type_1part = { (body_type_basic | body_type_msg | body_type_text) ~ (sp ~ body_ext_1part)? }
|
||||||
|
body_type_basic = { media_basic ~ sp ~ body_fields }
|
||||||
|
body_type_mpart = { body{1,} ~ sp ~ media_subtype ~ (sp ~ body_ext_mpart)? }
|
||||||
|
body_type_msg = { media_message ~ sp ~ body_fields ~ sp ~ envelope ~ sp ~ body ~ sp ~ body_fld_lines }
|
||||||
|
body_type_text = { media_text ~ sp ~ body_fields ~ sp ~ body_fld_lines }
|
||||||
|
capability = ${ ^"AUTH=" ~ auth_type | atom }
|
||||||
|
capability_data = { ^"CAPABILITY" ~ (sp ~ ("IMAP4rev1" ~ capability))* ~ sp ~ "IMAP4rev1" ~ (sp ~ capability)* }
|
||||||
|
char8 = @{ '\x01'..'\xff' }
|
||||||
|
continue_req = { "+" ~ sp ~ (resp_text | base64) ~ crlf }
|
||||||
|
date_day_fixed = { (sp ~ digit) | digit{2} }
|
||||||
|
date_month = { "Jan" | "Feb" | "Mar" | "Apr" | "May" | "Jun" | "Jul" | "Aug" | "Sep" | "Oct" | "Nov" | "Dec" }
|
||||||
|
date_time = { dquote ~ date_day_fixed ~ "-" ~ date_month ~ "-" ~ date_year ~ sp ~ time ~ sp ~ zone ~ dquote }
|
||||||
|
date_year = { digit{4} }
|
||||||
|
digit_nz = @{ '\x31'..'\x39' }
|
||||||
|
env_bcc = { "(" ~ address{1,} ~ ")" | nil }
|
||||||
|
env_cc = { "(" ~ address{1,} ~ ")" | nil }
|
||||||
|
env_date = { nstring }
|
||||||
|
env_from = { "(" ~ address{1,} ~ ")" | nil }
|
||||||
|
env_in_reply_to = { nstring }
|
||||||
|
env_message_id = { nstring }
|
||||||
|
env_reply_to = { "(" ~ address{1,} ~ ")" | nil }
|
||||||
|
env_sender = { "(" ~ address{1,} ~ ")" | nil }
|
||||||
|
env_subject = { nstring }
|
||||||
|
env_to = { "(" ~ address{1,} ~ ")" | nil }
|
||||||
|
envelope = { "(" ~ env_date ~ sp ~ env_subject ~ sp ~ env_from ~ sp ~ env_sender ~ sp ~ env_reply_to ~ sp ~ env_to ~ sp ~ env_cc ~ sp ~ env_bcc ~ sp ~ env_in_reply_to ~ sp ~ env_message_id ~ ")" }
|
||||||
|
flag = { "\\Answered" | "\\Flagged" | "\\Deleted" | "\\Seen" | "\\Draft" | flag_keyword | flag_extension }
|
||||||
|
flag_extension = @{ "\\" ~ atom }
|
||||||
|
flag_fetch = { flag | "\\Recent" }
|
||||||
|
flag_keyword = @{ atom }
|
||||||
|
flag_list = { "(" ~ (flag ~ (sp ~ flag)*)? ~ ")" }
|
||||||
|
flag_perm = { flag | "\\*" }
|
||||||
|
header_fld_name = { astring }
|
||||||
|
header_list = { "(" ~ header_fld_name ~ (sp ~ header_fld_name)* ~ ")" }
|
||||||
|
list_wildcards = @{ "%" | "*" }
|
||||||
|
literal = @{ "{" ~ number ~ "}" ~ crlf ~ char8* }
|
||||||
|
mailbox = { ^"INBOX" | astring }
|
||||||
|
mailbox_data = { mailbox_data_flags | (^"LIST" ~ sp ~ mailbox_list) | (^"LSUB" ~ sp ~ mailbox_list) | (^"SEARCH" ~ (sp ~ nz_number)*) | (^"STATUS" ~ sp ~ mailbox ~ sp ~ ^"(" ~ status_att_list? ~ ^")") | mailbox_data_exists | mailbox_data_recent }
|
||||||
|
mailbox_data_exists = { number ~ sp ~ ^"EXISTS" }
|
||||||
|
mailbox_data_flags = { ^"FLAGS" ~ sp ~ flag_list }
|
||||||
|
mailbox_data_recent = { number ~ sp ~ ^"RECENT" }
|
||||||
|
mailbox_list = { "(" ~ mbx_list_flags* ~ ")" ~ sp ~ (dquote ~ quoted_char ~ dquote | nil) ~ sp ~ mailbox }
|
||||||
|
mbx_list_flags = { (mbx_list_oflag ~ sp)* ~ mbx_list_sflag ~ (sp ~ mbx_list_oflag)* | mbx_list_oflag ~ (sp ~ mbx_list_oflag)* }
|
||||||
|
mbx_list_oflag = { "\\NoInferiors" | flag_extension }
|
||||||
|
mbx_list_sflag = { "\\NoSelect" | "\\Marked" | "\\Unmarked" }
|
||||||
|
media_basic = { ((dquote ~ ("APPLICATION" | "AUDIO" | "IMAGE" | "MESSAGE" | "VIDEO") ~ dquote) | string) ~ sp ~ media_subtype }
|
||||||
|
media_message = { dquote ~ "MESSAGE" ~ dquote ~ sp ~ dquote ~ "RFC822" ~ dquote }
|
||||||
|
media_subtype = { string }
|
||||||
|
media_text = { dquote ~ "TEXT" ~ dquote ~ sp ~ media_subtype }
|
||||||
|
message_data = { nz_number ~ sp ~ (^"EXPUNGE" | (^"FETCH" ~ sp ~ msg_att)) }
|
||||||
|
msg_att = { "(" ~ (msg_att_dynamic | msg_att_static) ~ (sp ~ (msg_att_dynamic | msg_att_static))* ~ ")" }
|
||||||
|
msg_att_dynamic = { ^"FLAGS" ~ sp ~ "(" ~ (flag_fetch ~ (sp ~ flag_fetch)*)? ~ ")" }
|
||||||
|
msg_att_static = { (^"ENVELOPE" ~ sp ~ envelope) | (^"INTERNALDATE" ~ sp ~ date_time) | (^"RFC822" ~ (^".HEADER" | ^".TEXT") ~ sp ~ nstring) | (^"RFC822.SIZE" ~ sp ~ number) | (^"BODY" ~ ^"STRUCTURE"? ~ sp ~ body) | (^"BODY" ~ section ~ ("<" ~ number ~ ">")? ~ sp ~ nstring) | (^"UID" ~ sp ~ uniqueid) }
|
||||||
|
nil = { ^"NIL" }
|
||||||
|
nstring = { string | nil }
|
||||||
|
number = @{ digit{1,} }
|
||||||
|
nz_number = @{ digit_nz ~ digit* }
|
||||||
|
quoted = @{ dquote ~ quoted_char* ~ dquote }
|
||||||
|
quoted_char = @{ (!quoted_specials ~ char) | ("\\" ~ quoted_specials) }
|
||||||
|
quoted_specials = @{ dquote | "\\" }
|
||||||
|
resp_cond_bye = { ^"BYE" ~ sp ~ resp_text }
|
||||||
|
resp_cond_state = { resp_status ~ sp ~ resp_text }
|
||||||
|
resp_specials = @{ "]" }
|
||||||
|
resp_status = { (^"OK" | ^"NO" | ^"BAD") }
|
||||||
|
resp_text = { ("[" ~ resp_text_code ~ "]" ~ sp)? ~ text }
|
||||||
|
resp_text_code = { ^"ALERT" | (^"BADCHARSET" ~ (sp ~ "(" ~ astring ~ (sp ~ astring)* ~ ")")?) | capability_data | ^"PARSE" | (^"PERMANENTFLAGS" ~ sp ~ "(" ~ (flag_perm ~ (sp ~ flag_perm)*)? ~ ")") | ^"READ-ONLY" | resp_text_code_readwrite | ^"TRYCREATE" | (^"UIDNEXT" ~ sp ~ nz_number) | resp_text_code_uidvalidity | resp_text_code_unseen | (atom ~ (sp ~ resp_text_code_atom)?) }
|
||||||
|
resp_text_code_atom = @{ (!"]" ~ text_char){1,} }
|
||||||
|
resp_text_code_readwrite = { ^"READ-WRITE" }
|
||||||
|
resp_text_code_uidvalidity = { ^"UIDVALIDITY" ~ sp ~ nz_number }
|
||||||
|
resp_text_code_unseen = { ^"UNSEEN" ~ sp ~ nz_number }
|
||||||
|
response = { continue_req | response_data | response_done }
|
||||||
|
response_data = { "*" ~ sp ~ (resp_cond_state | resp_cond_bye | mailbox_data | message_data | capability_data) ~ crlf }
|
||||||
|
response_done = { response_tagged | response_fatal }
|
||||||
|
response_fatal = { "*" ~ sp ~ resp_cond_bye ~ crlf }
|
||||||
|
response_tagged = { tag ~ sp ~ resp_cond_state ~ crlf }
|
||||||
|
section = { "[" ~ section_spec? ~ "]" }
|
||||||
|
section_msgtext = { ^"HEADER" | (^"HEADER.FIELDS" ~ ^".NOT"? ~ sp ~ header_list) | ^"TEXT" }
|
||||||
|
section_part = { nz_number ~ ("." ~ nz_number)* }
|
||||||
|
section_spec = { section_msgtext | (section_part ~ ("." ~ section_text)?) }
|
||||||
|
section_text = { section_msgtext | "MIME" }
|
||||||
|
status_att = { ^"MESSAGES" | ^"RECENT" | ^"UIDNEXT" | ^"UIDVALIDITY" | ^"UNSEEN" }
|
||||||
|
status_att_list = { status_att ~ sp ~ number ~ (sp ~ status_att ~ sp ~ number)* }
|
||||||
|
string = @{ quoted | literal }
|
||||||
|
tag = @{ tag_char{1,} }
|
||||||
|
tag_char = @{ !"+" ~ astring_char }
|
||||||
|
text = @{ text_char{1,} }
|
||||||
|
text_char = @{ !cr ~ !lf ~ char }
|
||||||
|
time = { digit{2} ~ ":" ~ digit{2} ~ ":" ~ digit{2} }
|
||||||
|
uniqueid = { nz_number }
|
||||||
|
zone = { ("+" | "-") ~ digit{4} }
|
||||||
|
|
||||||
|
// core rules from https://tools.ietf.org/html/rfc2234#section-6.1
|
||||||
|
alpha = @{ '\x41'..'\x5a' | '\x61'..'\x7a' }
|
||||||
|
char = @{ '\x01'..'\x7f' }
|
||||||
|
cr = @{ "\x0d" }
|
||||||
|
crlf = _{ cr ~ lf }
|
||||||
|
ctl = @{ '\x00'..'\x1f' | "\x7f" }
|
||||||
|
digit = @{ '\x30'..'\x39' }
|
||||||
|
dquote = @{ "\"" }
|
||||||
|
lf = @{ "\x0a" }
|
||||||
|
sp = _{ " " }
|
||||||
|
|
|
@ -1,11 +1,6 @@
|
||||||
use std::ops::RangeInclusive;
|
use std::ops::RangeInclusive;
|
||||||
|
|
||||||
use crate::types::{
|
#[derive(Clone, Debug, PartialEq, Eq)]
|
||||||
AttributeValue as AttributeValue_, Capability as Capability_, MailboxDatum as MailboxDatum_,
|
|
||||||
RequestId, Response as Response_, ResponseCode as ResponseCode_, State, Status,
|
|
||||||
};
|
|
||||||
|
|
||||||
#[derive(Clone, Debug)]
|
|
||||||
pub enum Response {
|
pub enum Response {
|
||||||
Capabilities(Vec<Capability>),
|
Capabilities(Vec<Capability>),
|
||||||
Continue {
|
Continue {
|
||||||
|
@ -13,7 +8,7 @@ pub enum Response {
|
||||||
information: Option<String>,
|
information: Option<String>,
|
||||||
},
|
},
|
||||||
Done {
|
Done {
|
||||||
tag: RequestId,
|
tag: String,
|
||||||
status: Status,
|
status: Status,
|
||||||
code: Option<ResponseCode>,
|
code: Option<ResponseCode>,
|
||||||
information: Option<String>,
|
information: Option<String>,
|
||||||
|
@ -29,66 +24,17 @@ pub enum Response {
|
||||||
uids: Vec<RangeInclusive<u32>>,
|
uids: Vec<RangeInclusive<u32>>,
|
||||||
},
|
},
|
||||||
Fetch(u32, Vec<AttributeValue>),
|
Fetch(u32, Vec<AttributeValue>),
|
||||||
MailboxData(MailboxDatum),
|
MailboxData(MailboxData),
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<'a> From<Response_<'a>> for Response {
|
#[derive(Clone, Debug, Hash, PartialEq, Eq)]
|
||||||
fn from(b: Response_) -> Self {
|
|
||||||
use Response_::*;
|
|
||||||
match b {
|
|
||||||
Capabilities(caps) => {
|
|
||||||
Response::Capabilities(caps.into_iter().map(Capability::from).collect())
|
|
||||||
}
|
|
||||||
Continue { code, information } => Response::Continue {
|
|
||||||
code: code.map(ResponseCode::from),
|
|
||||||
information: information.map(str::to_owned),
|
|
||||||
},
|
|
||||||
Done {
|
|
||||||
tag,
|
|
||||||
status,
|
|
||||||
code,
|
|
||||||
information,
|
|
||||||
} => Response::Done {
|
|
||||||
tag,
|
|
||||||
status,
|
|
||||||
code: code.map(ResponseCode::from),
|
|
||||||
information: information.map(str::to_owned),
|
|
||||||
},
|
|
||||||
Data {
|
|
||||||
status,
|
|
||||||
code,
|
|
||||||
information,
|
|
||||||
} => Response::Data {
|
|
||||||
status,
|
|
||||||
code: code.map(ResponseCode::from),
|
|
||||||
information: information.map(str::to_owned),
|
|
||||||
},
|
|
||||||
Expunge(n) => Response::Expunge(n),
|
|
||||||
Vanished { earlier, uids } => Response::Vanished { earlier, uids },
|
|
||||||
_ => todo!("nyi: {:?}", b),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Clone, Debug)]
|
|
||||||
pub enum Capability {
|
pub enum Capability {
|
||||||
Imap4rev1,
|
Imap4rev1,
|
||||||
Auth(String),
|
Auth(String),
|
||||||
Atom(String),
|
Atom(String),
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<'a> From<Capability_<'a>> for Capability {
|
#[derive(Clone, Debug, PartialEq, Eq)]
|
||||||
fn from(b: Capability_) -> Self {
|
|
||||||
use Capability_::*;
|
|
||||||
match b {
|
|
||||||
Imap4rev1 => Capability::Imap4rev1,
|
|
||||||
Auth(s) => Capability::Auth(s.to_owned()),
|
|
||||||
Atom(s) => Capability::Atom(s.to_owned()),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Clone, Debug)]
|
|
||||||
pub enum ResponseCode {
|
pub enum ResponseCode {
|
||||||
Alert,
|
Alert,
|
||||||
BadCharset(Option<Vec<String>>),
|
BadCharset(Option<Vec<String>>),
|
||||||
|
@ -107,32 +53,72 @@ pub enum ResponseCode {
|
||||||
UidNotSticky,
|
UidNotSticky,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<'a> From<ResponseCode_<'a>> for ResponseCode {
|
#[derive(Clone, Debug, PartialEq, Eq)]
|
||||||
fn from(b: ResponseCode_) -> Self {
|
|
||||||
use ResponseCode_::*;
|
|
||||||
match b {
|
|
||||||
Alert => ResponseCode::Alert,
|
|
||||||
BadCharset(s) => {
|
|
||||||
ResponseCode::BadCharset(s.map(|v| v.into_iter().map(str::to_owned).collect()))
|
|
||||||
}
|
|
||||||
Capabilities(v) => {
|
|
||||||
ResponseCode::Capabilities(v.into_iter().map(Capability::from).collect())
|
|
||||||
}
|
|
||||||
HighestModSeq(n) => ResponseCode::HighestModSeq(n),
|
|
||||||
Parse => ResponseCode::Parse,
|
|
||||||
_ => todo!("nyi: {:?}", b),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Clone, Debug)]
|
|
||||||
pub enum UidSetMember {
|
pub enum UidSetMember {
|
||||||
UidRange(RangeInclusive<u32>),
|
UidRange(RangeInclusive<u32>),
|
||||||
Uid(u32),
|
Uid(u32),
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Clone, Debug)]
|
#[derive(Clone, Debug, PartialEq, Eq)]
|
||||||
pub enum AttributeValue {}
|
pub enum AttributeValue {}
|
||||||
|
|
||||||
#[derive(Clone, Debug)]
|
#[derive(Clone, Debug, PartialEq, Eq)]
|
||||||
pub enum MailboxDatum {}
|
pub enum MailboxData {
|
||||||
|
Exists(u32),
|
||||||
|
Flags(Vec<Flag>),
|
||||||
|
List {
|
||||||
|
flags: Vec<String>,
|
||||||
|
delimiter: Option<String>,
|
||||||
|
name: String,
|
||||||
|
},
|
||||||
|
Search(Vec<u32>),
|
||||||
|
Status {
|
||||||
|
mailbox: String,
|
||||||
|
status: Vec<StatusAttribute>,
|
||||||
|
},
|
||||||
|
Recent(u32),
|
||||||
|
MetadataSolicited {
|
||||||
|
mailbox: String,
|
||||||
|
values: Vec<Metadata>,
|
||||||
|
},
|
||||||
|
MetadataUnsolicited {
|
||||||
|
mailbox: String,
|
||||||
|
values: Vec<String>,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Eq, PartialEq, Clone)]
|
||||||
|
pub enum Flag {
|
||||||
|
Answered,
|
||||||
|
Flagged,
|
||||||
|
Deleted,
|
||||||
|
Seen,
|
||||||
|
Draft,
|
||||||
|
Ext(String),
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Eq, PartialEq, Clone)]
|
||||||
|
pub struct Metadata {
|
||||||
|
pub entry: String,
|
||||||
|
pub value: Option<String>,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Clone, Debug, Eq, PartialEq)]
|
||||||
|
#[non_exhaustive]
|
||||||
|
pub enum StatusAttribute {
|
||||||
|
HighestModSeq(u64), // RFC 4551
|
||||||
|
Messages(u32),
|
||||||
|
Recent(u32),
|
||||||
|
UidNext(u32),
|
||||||
|
UidValidity(u32),
|
||||||
|
Unseen(u32),
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Clone, Debug, Eq, PartialEq)]
|
||||||
|
pub enum Status {
|
||||||
|
Ok,
|
||||||
|
No,
|
||||||
|
Bad,
|
||||||
|
PreAuth,
|
||||||
|
Bye,
|
||||||
|
}
|
||||||
|
|
|
@ -40,7 +40,7 @@ pub enum Response<'a> {
|
||||||
|
|
||||||
impl<'a> Response<'a> {
|
impl<'a> Response<'a> {
|
||||||
pub fn from_bytes(buf: &'a [u8]) -> crate::ParseResult {
|
pub fn from_bytes(buf: &'a [u8]) -> crate::ParseResult {
|
||||||
crate::parser::parse_response(buf)
|
crate::oldparser::parse_response(buf)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -9,7 +9,7 @@ use panorama_imap::{
|
||||||
use tokio::{sync::mpsc::UnboundedReceiver, task::JoinHandle};
|
use tokio::{sync::mpsc::UnboundedReceiver, task::JoinHandle};
|
||||||
use tokio_stream::wrappers::WatchStream;
|
use tokio_stream::wrappers::WatchStream;
|
||||||
|
|
||||||
use crate::config::{Config, ConfigWatcher, MailAccountConfig, TlsMethod};
|
use crate::config::{Config, ConfigWatcher, ImapAuth, MailAccountConfig, TlsMethod};
|
||||||
|
|
||||||
/// Command sent to the mail thread by something else (i.e. UI)
|
/// Command sent to the mail thread by something else (i.e. UI)
|
||||||
pub enum MailCommand {
|
pub enum MailCommand {
|
||||||
|
@ -89,7 +89,13 @@ async fn imap_main(acct: MailAccountConfig) -> Result<()> {
|
||||||
|
|
||||||
debug!("preparing to auth");
|
debug!("preparing to auth");
|
||||||
// check if the authentication method is supported
|
// check if the authentication method is supported
|
||||||
unauth.capabilities().await?;
|
let authed = match acct.imap.auth {
|
||||||
|
ImapAuth::Plain { username, password } => {
|
||||||
|
let ok = unauth.has_capability("AUTH=PLAIN").await?;
|
||||||
|
let res = unauth.execute(ImapCommand::Login { username, password }).await?;
|
||||||
|
debug!("res: {:?}", res);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
// debug!("sending CAPABILITY");
|
// debug!("sending CAPABILITY");
|
||||||
// let result = unauth.capabilities().await?;
|
// let result = unauth.capabilities().await?;
|
||||||
|
|
|
@ -28,6 +28,10 @@ pub struct Rect(u16, u16, u16, u16);
|
||||||
|
|
||||||
/// UI entrypoint.
|
/// UI entrypoint.
|
||||||
pub async fn run_ui(mut w: impl Write + Debug, exit: ExitSender) -> Result<()> {
|
pub async fn run_ui(mut w: impl Write + Debug, exit: ExitSender) -> Result<()> {
|
||||||
|
loop {
|
||||||
|
tokio::time::sleep(Duration::from_secs(4000)).await;
|
||||||
|
}
|
||||||
|
|
||||||
execute!(w, cursor::Hide, terminal::EnterAlternateScreen)?;
|
execute!(w, cursor::Hide, terminal::EnterAlternateScreen)?;
|
||||||
terminal::enable_raw_mode()?;
|
terminal::enable_raw_mode()?;
|
||||||
|
|
||||||
|
|
Loading…
Reference in a new issue