Osu
This commit is contained in:
parent
f5d3a89641
commit
921e4b02d1
11 changed files with 425 additions and 17 deletions
121
Cargo.lock
generated
121
Cargo.lock
generated
|
@ -105,9 +105,9 @@ checksum = "40e38929add23cdf8a366df9b0e088953150724bcbe5fc330b0d8eb3b328eec8"
|
|||
|
||||
[[package]]
|
||||
name = "bumpalo"
|
||||
version = "3.6.0"
|
||||
version = "3.6.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "099e596ef14349721d9016f6b80dd3419ea1bf289ab9b44df8e4dfd3a005d5d9"
|
||||
checksum = "63396b8a4b9de3f4fdfb320ab6080762242f66a8ef174c49d8e19b674db4cdbe"
|
||||
|
||||
[[package]]
|
||||
name = "byteorder"
|
||||
|
@ -123,9 +123,9 @@ checksum = "b700ce4376041dcd0a327fd0097c41095743c4c8af8887265942faf1100bd040"
|
|||
|
||||
[[package]]
|
||||
name = "cc"
|
||||
version = "1.0.66"
|
||||
version = "1.0.67"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "4c0496836a84f8d0495758516b8621a622beb77c0fed418570e50764093ced48"
|
||||
checksum = "e3c69b077ad434294d3ce9f1f6143a2a4b89a8a2d54ef813d85003a4fd1137fd"
|
||||
|
||||
[[package]]
|
||||
name = "cfg-if"
|
||||
|
@ -155,7 +155,7 @@ dependencies = [
|
|||
"ansi_term 0.11.0",
|
||||
"atty",
|
||||
"bitflags",
|
||||
"strsim",
|
||||
"strsim 0.8.0",
|
||||
"textwrap",
|
||||
"unicode-width",
|
||||
"vec_map",
|
||||
|
@ -223,6 +223,66 @@ dependencies = [
|
|||
"winapi",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "darling"
|
||||
version = "0.10.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "0d706e75d87e35569db781a9b5e2416cff1236a47ed380831f959382ccd5f858"
|
||||
dependencies = [
|
||||
"darling_core",
|
||||
"darling_macro",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "darling_core"
|
||||
version = "0.10.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "f0c960ae2da4de88a91b2d920c2a7233b400bc33cb28453a2987822d8392519b"
|
||||
dependencies = [
|
||||
"fnv",
|
||||
"ident_case",
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"strsim 0.9.3",
|
||||
"syn",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "darling_macro"
|
||||
version = "0.10.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "d9b5a2f4ac4969822c62224815d069952656cadc7084fdca9751e6d959189b72"
|
||||
dependencies = [
|
||||
"darling_core",
|
||||
"quote",
|
||||
"syn",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "derive_builder"
|
||||
version = "0.9.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "a2658621297f2cf68762a6f7dc0bb7e1ff2cfd6583daef8ee0fed6f7ec468ec0"
|
||||
dependencies = [
|
||||
"darling",
|
||||
"derive_builder_core",
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "derive_builder_core"
|
||||
version = "0.9.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "2791ea3e372c8495c0bc2033991d76b512cd799d07491fbd6890124db9458bef"
|
||||
dependencies = [
|
||||
"darling",
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "fast_chemail"
|
||||
version = "0.9.6"
|
||||
|
@ -232,6 +292,12 @@ dependencies = [
|
|||
"ascii_utils",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "fnv"
|
||||
version = "1.0.7"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "3f9eec918d3f24069decb9af1554cad7c880e2da24a9afd88aca000531ab82c1"
|
||||
|
||||
[[package]]
|
||||
name = "foreign-types"
|
||||
version = "0.3.2"
|
||||
|
@ -409,6 +475,12 @@ dependencies = [
|
|||
"winutil",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "ident_case"
|
||||
version = "1.0.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "b9e0384b61958566e926dc50660321d12159025e767c18e043daf26b70104c39"
|
||||
|
||||
[[package]]
|
||||
name = "inotify"
|
||||
version = "0.9.2"
|
||||
|
@ -520,9 +592,9 @@ checksum = "0ee1c47aaa256ecabcaea351eae4a9b01ef39ed810004e298d2511ed284b1525"
|
|||
|
||||
[[package]]
|
||||
name = "mio"
|
||||
version = "0.7.7"
|
||||
version = "0.7.8"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "e50ae3f04d169fcc9bde0b547d1c205219b7157e07ded9c5aff03e0637cb3ed7"
|
||||
checksum = "dc250d6848c90d719ea2ce34546fb5df7af1d3fd189d10bf7bad80bfcebecd95"
|
||||
dependencies = [
|
||||
"libc",
|
||||
"log",
|
||||
|
@ -693,10 +765,23 @@ dependencies = [
|
|||
name = "panorama-imap"
|
||||
version = "0.0.1"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"assert_matches",
|
||||
"derive_builder",
|
||||
"futures",
|
||||
"nom 6.1.2",
|
||||
"panorama-strings",
|
||||
"parking_lot",
|
||||
"tokio",
|
||||
"tokio-rustls",
|
||||
"tracing",
|
||||
"webpki-roots",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "panorama-strings"
|
||||
version = "0.1.0"
|
||||
|
||||
[[package]]
|
||||
name = "parking_lot"
|
||||
version = "0.11.1"
|
||||
|
@ -1111,6 +1196,12 @@ version = "0.8.0"
|
|||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "8ea5119cdb4c55b55d432abb513a0429384878c15dde60cc77b1c99de1a95a6a"
|
||||
|
||||
[[package]]
|
||||
name = "strsim"
|
||||
version = "0.9.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "6446ced80d6c486436db5c078dde11a9f73d42b57fb273121e160b84f63d894c"
|
||||
|
||||
[[package]]
|
||||
name = "structopt"
|
||||
version = "0.3.21"
|
||||
|
@ -1276,9 +1367,9 @@ dependencies = [
|
|||
|
||||
[[package]]
|
||||
name = "tracing"
|
||||
version = "0.1.23"
|
||||
version = "0.1.24"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "f7d40a22fd029e33300d8d89a5cc8ffce18bb7c587662f54629e94c9de5487f3"
|
||||
checksum = "f77d3842f76ca899ff2dbcf231c5c65813dea431301d6eb686279c15c4464f12"
|
||||
dependencies = [
|
||||
"cfg-if",
|
||||
"pin-project-lite",
|
||||
|
@ -1299,9 +1390,9 @@ dependencies = [
|
|||
|
||||
[[package]]
|
||||
name = "tracing-attributes"
|
||||
version = "0.1.12"
|
||||
version = "0.1.13"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "43f080ea7e4107844ef4766459426fa2d5c1ada2e47edba05dc7fa99d9629f47"
|
||||
checksum = "a8a9bd1db7706f2373a190b0d067146caa39350c486f3d455b0e33b431f94c07"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
|
@ -1319,9 +1410,9 @@ dependencies = [
|
|||
|
||||
[[package]]
|
||||
name = "tracing-log"
|
||||
version = "0.1.1"
|
||||
version = "0.1.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "5e0f8c7178e13481ff6765bd169b33e8d554c5d2bbede5e32c356194be02b9b9"
|
||||
checksum = "a6923477a48e41c1951f1999ef8bb5a3023eb723ceadafe78ffb65dc366761e3"
|
||||
dependencies = [
|
||||
"lazy_static",
|
||||
"log",
|
||||
|
@ -1340,9 +1431,9 @@ dependencies = [
|
|||
|
||||
[[package]]
|
||||
name = "tracing-subscriber"
|
||||
version = "0.2.15"
|
||||
version = "0.2.16"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "a1fa8f0c8f4c594e4fc9debc1990deab13238077271ba84dd853d54902ee3401"
|
||||
checksum = "8ab8966ac3ca27126141f7999361cc97dd6fb4b71da04c02044fa9045d98bb96"
|
||||
dependencies = [
|
||||
"ansi_term 0.12.1",
|
||||
"chrono",
|
||||
|
|
|
@ -9,7 +9,7 @@ readme = "README.md"
|
|||
license = "GPL-3.0-or-later"
|
||||
|
||||
[workspace]
|
||||
members = ["imap"]
|
||||
members = ["imap", "strings"]
|
||||
|
||||
[dependencies]
|
||||
# log = "0.4.14"
|
||||
|
|
|
@ -12,7 +12,16 @@ edition = "2018"
|
|||
maintenance = { status = "passively-maintained" }
|
||||
|
||||
[dependencies]
|
||||
anyhow = "1.0.38"
|
||||
tokio = { version = "1.1.1", features = ["full"] }
|
||||
futures = "0.3.12"
|
||||
nom = { version = "6.1.2", default-features = false, features = ["std"] }
|
||||
derive_builder = "0.9.0"
|
||||
tokio-rustls = "0.22.0"
|
||||
webpki-roots = "0.21.0"
|
||||
panorama-strings = { path = "../strings", version = "0" }
|
||||
parking_lot = "0.11.1"
|
||||
tracing = "0.1.23"
|
||||
|
||||
[dev-dependencies]
|
||||
assert_matches = "1.3"
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
use imap_proto::Response;
|
||||
use panorama_imap::Response;
|
||||
use std::io::Write;
|
||||
|
||||
fn main() -> std::io::Result<()> {
|
||||
|
|
105
imap/src/client/inner.rs
Normal file
105
imap/src/client/inner.rs
Normal file
|
@ -0,0 +1,105 @@
|
|||
use std::collections::HashMap;
|
||||
use std::pin::Pin;
|
||||
use std::sync::Arc;
|
||||
use std::task::{Context, Poll};
|
||||
|
||||
use anyhow::Result;
|
||||
use futures::future::{Future, FutureExt};
|
||||
use panorama_strings::{StringEntry, StringStore};
|
||||
use parking_lot::RwLock;
|
||||
use tokio::{
|
||||
io::{
|
||||
self, AsyncBufRead, AsyncBufReadExt, AsyncRead, AsyncWrite, AsyncWriteExt, BufReader,
|
||||
WriteHalf,
|
||||
},
|
||||
task::JoinHandle,
|
||||
};
|
||||
|
||||
use crate::command::Command;
|
||||
|
||||
pub type BoxedFunc = Box<dyn Fn()>;
|
||||
|
||||
/// The private Client struct, that is shared by all of the exported structs in the state machine.
|
||||
pub struct Client<C> {
|
||||
conn: WriteHalf<C>,
|
||||
symbols: StringStore,
|
||||
|
||||
id: usize,
|
||||
handlers: Arc<RwLock<HashMap<usize, bool>>>,
|
||||
|
||||
/// Cached capabilities that shouldn't change between
|
||||
caps: Vec<StringEntry>,
|
||||
handle: JoinHandle<Result<()>>,
|
||||
}
|
||||
|
||||
impl<C> Client<C>
|
||||
where
|
||||
C: AsyncRead + AsyncWrite + Unpin + Send + 'static,
|
||||
{
|
||||
/// Creates a new client that wraps a connection
|
||||
pub fn new(conn: C) -> Self {
|
||||
let (read_half, write_half) = io::split(conn);
|
||||
let listen_fut = tokio::spawn(listen(read_half));
|
||||
|
||||
Client {
|
||||
conn: write_half,
|
||||
symbols: StringStore::new(256),
|
||||
id: 0,
|
||||
handlers: Arc::new(RwLock::new(HashMap::new())),
|
||||
caps: Vec::new(),
|
||||
handle: listen_fut,
|
||||
}
|
||||
}
|
||||
|
||||
/// Sends a command to the server and returns a handle to retrieve the result
|
||||
pub async fn execute(&mut self, cmd: Command) -> Result<()> {
|
||||
let id = self.id;
|
||||
self.id += 1;
|
||||
|
||||
{
|
||||
let mut handlers = self.handlers.write();
|
||||
handlers.insert(id, false);
|
||||
}
|
||||
|
||||
let cmd_str = cmd.to_string();
|
||||
self.conn.write_all(cmd_str.as_bytes()).await?;
|
||||
|
||||
ExecHandle(self, id).await;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Executes the CAPABILITY command
|
||||
pub async fn supports(&mut self) {
|
||||
let cmd = Command::Capability;
|
||||
let result = self.execute(cmd).await;
|
||||
debug!("poggers {:?}", result);
|
||||
}
|
||||
}
|
||||
|
||||
pub struct ExecHandle<'a, C>(&'a Client<C>, usize);
|
||||
|
||||
impl<'a, C> Future for ExecHandle<'a, C> {
|
||||
type Output = ();
|
||||
fn poll(self: Pin<&mut Self>, _: &mut Context) -> Poll<Self::Output> {
|
||||
let state = {
|
||||
let handlers = self.0.handlers.read();
|
||||
handlers.get(&self.1).cloned()
|
||||
};
|
||||
|
||||
// TODO: handle the None case here
|
||||
let state = state.unwrap();
|
||||
|
||||
match state {
|
||||
true => Poll::Ready(()),
|
||||
false => Poll::Pending,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
async fn listen(conn: impl AsyncRead + Unpin) -> Result<()> {
|
||||
let mut reader = BufReader::new(conn);
|
||||
loop {
|
||||
let mut next_line = String::new();
|
||||
reader.read_line(&mut next_line).await?;
|
||||
}
|
||||
}
|
86
imap/src/client/mod.rs
Normal file
86
imap/src/client/mod.rs
Normal file
|
@ -0,0 +1,86 @@
|
|||
//! IMAP Client
|
||||
//! ===
|
||||
//!
|
||||
//! The IMAP client in this module is implemented as a state machine in the type system: methods
|
||||
//! that are not supported in a particular state (ex. fetch in an unauthenticated state) cannot be
|
||||
//! expressed in the type system entirely.
|
||||
|
||||
mod inner;
|
||||
|
||||
use std::sync::Arc;
|
||||
|
||||
use anyhow::Result;
|
||||
use tokio::{
|
||||
io::{self, AsyncRead, AsyncWrite, ReadHalf, WriteHalf},
|
||||
net::TcpStream,
|
||||
};
|
||||
use tokio_rustls::{client::TlsStream, rustls::ClientConfig, webpki::DNSNameRef, TlsConnector};
|
||||
|
||||
use self::inner::Client;
|
||||
|
||||
/// An IMAP client that hasn't been connected yet.
|
||||
#[derive(Builder, Clone, Debug)]
|
||||
pub struct ClientNotConnected {
|
||||
/// The hostname of the IMAP server. If using TLS, must be an address
|
||||
hostname: String,
|
||||
|
||||
/// The port of the IMAP server.
|
||||
port: u16,
|
||||
|
||||
/// Whether or not the client is using an encrypted stream.
|
||||
///
|
||||
/// To upgrade the connection later, use the upgrade method.
|
||||
tls: bool,
|
||||
}
|
||||
|
||||
impl ClientNotConnected {
|
||||
pub async fn connect(self) -> Result<ClientUnauthenticated> {
|
||||
let hostname = self.hostname.as_ref();
|
||||
let port = self.port;
|
||||
let conn = TcpStream::connect((hostname, port)).await?;
|
||||
|
||||
if self.tls {
|
||||
let mut tls_config = ClientConfig::new();
|
||||
tls_config
|
||||
.root_store
|
||||
.add_server_trust_anchors(&webpki_roots::TLS_SERVER_ROOTS);
|
||||
let tls_config = TlsConnector::from(Arc::new(tls_config));
|
||||
let dnsname = DNSNameRef::try_from_ascii_str(hostname).unwrap();
|
||||
let conn = tls_config.connect(dnsname, conn).await?;
|
||||
|
||||
let inner = Client::new(conn);
|
||||
return Ok(ClientUnauthenticated::Encrypted(
|
||||
ClientEncryptedUnauthenticated { inner },
|
||||
));
|
||||
}
|
||||
|
||||
let inner = Client::new(conn);
|
||||
return Ok(ClientUnauthenticated::Unencrypted(
|
||||
ClientUnencryptedUnauthenticated { inner },
|
||||
));
|
||||
}
|
||||
}
|
||||
|
||||
pub enum ClientUnauthenticated {
|
||||
Encrypted(ClientEncryptedUnauthenticated),
|
||||
Unencrypted(ClientUnencryptedUnauthenticated),
|
||||
}
|
||||
|
||||
impl ClientUnauthenticated {}
|
||||
|
||||
pub struct ClientUnencryptedUnauthenticated {
|
||||
/// Connection to the remote server
|
||||
inner: Client<TcpStream>,
|
||||
}
|
||||
|
||||
impl ClientUnencryptedUnauthenticated {
|
||||
pub async fn upgrade(&self) {}
|
||||
}
|
||||
|
||||
/// An IMAP client that isn't authenticated.
|
||||
pub struct ClientEncryptedUnauthenticated {
|
||||
/// Connection to the remote server
|
||||
inner: Client<TlsStream<TcpStream>>,
|
||||
}
|
||||
|
||||
impl ClientEncryptedUnauthenticated {}
|
14
imap/src/command/mod.rs
Normal file
14
imap/src/command/mod.rs
Normal file
|
@ -0,0 +1,14 @@
|
|||
use std::fmt;
|
||||
|
||||
/// Commands, without the tag part.
|
||||
pub enum Command {
|
||||
Capability,
|
||||
}
|
||||
|
||||
impl fmt::Display for Command {
|
||||
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
|
||||
match self {
|
||||
Command::Capability => write!(f, "CAPABILITY"),
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,5 +1,13 @@
|
|||
#[macro_use]
|
||||
extern crate derive_builder;
|
||||
#[macro_use]
|
||||
extern crate tracing;
|
||||
|
||||
pub mod builders;
|
||||
pub mod client;
|
||||
pub mod command;
|
||||
pub mod parser;
|
||||
pub mod response;
|
||||
pub mod types;
|
||||
|
||||
pub use crate::parser::ParseResult;
|
||||
|
|
1
imap/src/response/mod.rs
Normal file
1
imap/src/response/mod.rs
Normal file
|
@ -0,0 +1 @@
|
|||
pub struct Response {}
|
7
strings/Cargo.toml
Normal file
7
strings/Cargo.toml
Normal file
|
@ -0,0 +1,7 @@
|
|||
[package]
|
||||
name = "panorama-strings"
|
||||
version = "0.1.0"
|
||||
authors = ["Michael Zhang <mail@mzhang.io>"]
|
||||
edition = "2018"
|
||||
|
||||
[dependencies]
|
87
strings/src/lib.rs
Normal file
87
strings/src/lib.rs
Normal file
|
@ -0,0 +1,87 @@
|
|||
use std::collections::HashMap;
|
||||
use std::hash::Hash;
|
||||
use std::marker::PhantomData;
|
||||
use std::ops::Deref;
|
||||
|
||||
pub type StringStore = Store<&'static str>;
|
||||
pub type StringEntry = Entry<&'static str>;
|
||||
|
||||
pub struct Store<T> {
|
||||
capacity: usize,
|
||||
store: Vec<Entry<T>>,
|
||||
index: HashMap<T, usize>,
|
||||
head: usize,
|
||||
tail: usize,
|
||||
}
|
||||
|
||||
impl<T: Hash + Eq> Store<T> {
|
||||
pub fn new(capacity: usize) -> Self {
|
||||
Store {
|
||||
capacity,
|
||||
store: Vec::with_capacity(capacity),
|
||||
index: HashMap::new(),
|
||||
head: 0,
|
||||
tail: 0,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn insert(&mut self, val: T) {
|
||||
if self.index.contains_key(&val) {
|
||||
return;
|
||||
}
|
||||
|
||||
let entry = Entry {
|
||||
val,
|
||||
prev: 0,
|
||||
next: 0,
|
||||
};
|
||||
|
||||
let new_head = if self.store.len() == self.store.capacity() {
|
||||
let idx = self.pop_back();
|
||||
self.store[idx] = entry;
|
||||
idx
|
||||
} else {
|
||||
self.store.push(entry);
|
||||
self.store.len() - 1
|
||||
};
|
||||
|
||||
self.push_front(new_head);
|
||||
}
|
||||
|
||||
fn pop_back(&mut self) -> usize {
|
||||
let old_tail = self.tail;
|
||||
let new_tail = self.store[old_tail].prev;
|
||||
self.tail = new_tail;
|
||||
old_tail
|
||||
}
|
||||
|
||||
fn push_front(&mut self, idx: usize) {
|
||||
if self.store.len() == 1 {
|
||||
self.tail = idx;
|
||||
} else {
|
||||
self.store[self.head].prev = idx;
|
||||
self.store[idx].next = idx;
|
||||
}
|
||||
self.head = idx;
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Copy, Clone)]
|
||||
pub struct Entry<T> {
|
||||
val: T,
|
||||
prev: usize,
|
||||
next: usize,
|
||||
}
|
||||
|
||||
impl<T: Copy + Clone> Deref for Entry<T> {
|
||||
type Target = T;
|
||||
fn deref(&self) -> &Self::Target {
|
||||
&self.val
|
||||
}
|
||||
}
|
||||
|
||||
impl<T> AsRef<T> for Entry<T> {
|
||||
fn as_ref(&self) -> &T {
|
||||
&self.val
|
||||
}
|
||||
}
|
Loading…
Reference in a new issue