added some high level client stuff
This commit is contained in:
parent
bd2e670a70
commit
4ddcaf19c1
14 changed files with 158 additions and 48 deletions
13
Cargo.lock
generated
13
Cargo.lock
generated
|
@ -14,6 +14,17 @@ version = "0.5.2"
|
|||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "23b62fc65de8e4e7f52534fb52b0f3ed04746ae267519eef2a83941e8085068b"
|
||||
|
||||
[[package]]
|
||||
name = "async-trait"
|
||||
version = "0.1.51"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "44318e776df68115a881de9a8fd1b9e53368d7a4a5ce4cc48517da3393233a5e"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "autocfg"
|
||||
version = "1.0.1"
|
||||
|
@ -260,6 +271,8 @@ name = "imap"
|
|||
version = "0.1.0"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"async-trait",
|
||||
"bitflags",
|
||||
"bytes",
|
||||
"derive_builder",
|
||||
"futures",
|
||||
|
|
|
@ -8,6 +8,8 @@ rfc2177-idle = []
|
|||
|
||||
[dependencies]
|
||||
anyhow = "1.0.42"
|
||||
async-trait = "0.1.51"
|
||||
bitflags = "1.2.1"
|
||||
bytes = "1.0.1"
|
||||
derive_builder = "0.10.2"
|
||||
futures = "0.3.16"
|
||||
|
|
8
imap/src/client/auth.rs
Normal file
8
imap/src/client/auth.rs
Normal file
|
@ -0,0 +1,8 @@
|
|||
pub trait AuthMethod {}
|
||||
|
||||
pub struct Login {
|
||||
pub username: String,
|
||||
pub password: String,
|
||||
}
|
||||
|
||||
impl AuthMethod for Login {}
|
61
imap/src/client/inner.rs
Normal file
61
imap/src/client/inner.rs
Normal file
|
@ -0,0 +1,61 @@
|
|||
use anyhow::Result;
|
||||
use futures::future::FutureExt;
|
||||
use tokio::{
|
||||
io::{split, AsyncRead, AsyncWrite, ReadHalf},
|
||||
sync::oneshot,
|
||||
task::JoinHandle,
|
||||
};
|
||||
use tokio_util::codec::FramedRead;
|
||||
|
||||
use crate::codec::ImapCodec;
|
||||
|
||||
type ExitSender = oneshot::Sender<()>;
|
||||
type ExitListener = oneshot::Receiver<()>;
|
||||
|
||||
pub struct Inner<C> {
|
||||
read_exit: ExitSender,
|
||||
read_handle: JoinHandle<ReadHalf<C>>,
|
||||
}
|
||||
|
||||
impl<C> Inner<C>
|
||||
where
|
||||
C: AsyncRead + AsyncWrite + Send + 'static,
|
||||
{
|
||||
pub async fn open(c: C) -> Result<Self> {
|
||||
// break the stream of bytes into a reader and a writer
|
||||
// the read_half represents the server->client connection
|
||||
// the write_half represents the client->server connection
|
||||
let (read_half, write_half) = split(c);
|
||||
|
||||
// spawn the server->client loop
|
||||
let (read_exit, exit_rx) = oneshot::channel();
|
||||
let read_handle = tokio::spawn(read_loop(read_half, exit_rx));
|
||||
|
||||
Ok(Inner {
|
||||
read_exit,
|
||||
read_handle,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
// exit is a channel that will notify this loop when some external
|
||||
// even requires this loop to stop (for example, TLS upgrade).
|
||||
//
|
||||
// when the loop exits, the read half of the stream will be returned
|
||||
async fn read_loop<C>(stream: ReadHalf<C>, exit: ExitListener) -> ReadHalf<C>
|
||||
where
|
||||
C: AsyncRead,
|
||||
{
|
||||
// set up framed communication
|
||||
let codec = ImapCodec::default();
|
||||
let framed = FramedRead::new(stream, codec);
|
||||
|
||||
let exit = exit.fuse();
|
||||
pin_mut!(exit);
|
||||
loop {
|
||||
select! {
|
||||
_ = exit => break,
|
||||
}
|
||||
}
|
||||
todo!()
|
||||
}
|
3
imap/src/client/mod.rs
Normal file
3
imap/src/client/mod.rs
Normal file
|
@ -0,0 +1,3 @@
|
|||
pub mod auth;
|
||||
pub mod inner;
|
||||
pub mod upgrade;
|
4
imap/src/client/upgrade.rs
Normal file
4
imap/src/client/upgrade.rs
Normal file
|
@ -0,0 +1,4 @@
|
|||
use anyhow::Result;
|
||||
use tokio_rustls::TlsStream;
|
||||
|
||||
pub fn upgrade<C>(c: C) -> Result<TlsStream<C>> { todo!() }
|
|
@ -1,11 +1,13 @@
|
|||
use std::io;
|
||||
use std::mem;
|
||||
|
||||
use bytes::{BufMut, Bytes, BytesMut};
|
||||
use nom::{self, Needed};
|
||||
|
||||
use tokio_util::codec::{Decoder, Encoder};
|
||||
|
||||
use imap_proto::types::{Request, RequestId, Response};
|
||||
use crate::proto::{
|
||||
command::Command,
|
||||
response::{Response, ResponseDone, Tag},
|
||||
};
|
||||
|
||||
#[derive(Default)]
|
||||
pub struct ImapCodec {
|
||||
|
@ -20,44 +22,46 @@ impl<'a> Decoder for ImapCodec {
|
|||
return Ok(None);
|
||||
}
|
||||
|
||||
let (response, rsp_len) = match imap_proto::Response::from_bytes(buf) {
|
||||
Ok((remaining, response)) => {
|
||||
// This SHOULD be acceptable/safe: BytesMut storage memory is
|
||||
// allocated on the heap and should not move. It will not be
|
||||
// freed as long as we keep a reference alive, which we do
|
||||
// by retaining a reference to the split buffer, below.
|
||||
let response = unsafe { mem::transmute(response) };
|
||||
(response, buf.len() - remaining.len())
|
||||
}
|
||||
Err(nom::Err::Incomplete(Needed::Size(min))) => {
|
||||
self.decode_need_message_bytes = min.get();
|
||||
return Ok(None);
|
||||
}
|
||||
Err(nom::Err::Incomplete(_)) => {
|
||||
return Ok(None);
|
||||
}
|
||||
Err(nom::Err::Error(nom::error::Error { code, .. }))
|
||||
| Err(nom::Err::Failure(nom::error::Error { code, .. })) => {
|
||||
return Err(io::Error::new(
|
||||
io::ErrorKind::Other,
|
||||
format!("{:?} during parsing of {:?}", code, buf),
|
||||
));
|
||||
}
|
||||
};
|
||||
let raw = buf.split_to(rsp_len).freeze();
|
||||
self.decode_need_message_bytes = 0;
|
||||
Ok(Some(ResponseData { raw, response }))
|
||||
todo!()
|
||||
// let (response, rsp_len) = match Response::from_bytes(buf) {
|
||||
// Ok((remaining, response)) => {
|
||||
// // This SHOULD be acceptable/safe: BytesMut storage memory is
|
||||
// // allocated on the heap and should not move. It will not be
|
||||
// // freed as long as we keep a reference alive, which we do
|
||||
// // by retaining a reference to the split buffer, below.
|
||||
// let response = unsafe { mem::transmute(response) };
|
||||
// (response, buf.len() - remaining.len())
|
||||
// }
|
||||
// Err(nom::Err::Incomplete(Needed::Size(min))) => {
|
||||
// self.decode_need_message_bytes = min.get();
|
||||
// return Ok(None);
|
||||
// }
|
||||
// Err(nom::Err::Incomplete(_)) => {
|
||||
// return Ok(None);
|
||||
// }
|
||||
// Err(nom::Err::Error(nom::error::Error { code, .. }))
|
||||
// | Err(nom::Err::Failure(nom::error::Error { code, .. })) => {
|
||||
// return Err(io::Error::new(
|
||||
// io::ErrorKind::Other,
|
||||
// format!("{:?} during parsing of {:?}", code, buf),
|
||||
// ));
|
||||
// }
|
||||
// };
|
||||
// let raw = buf.split_to(rsp_len).freeze();
|
||||
// self.decode_need_message_bytes = 0;
|
||||
// Ok(Some(ResponseData { raw, response }))
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a> Encoder<&'a Request<'a>> for ImapCodec {
|
||||
impl<'a> Encoder<&'a Command<'a>> for ImapCodec {
|
||||
type Error = io::Error;
|
||||
fn encode(&mut self, msg: &Request, dst: &mut BytesMut) -> Result<(), io::Error> {
|
||||
dst.put(&*msg.0);
|
||||
dst.put_u8(b' ');
|
||||
dst.put_slice(&*msg.1);
|
||||
dst.put_slice(b"\r\n");
|
||||
Ok(())
|
||||
fn encode(&mut self, msg: &Command, dst: &mut BytesMut) -> Result<(), io::Error> {
|
||||
todo!()
|
||||
// dst.put(&*msg.0);
|
||||
// dst.put_u8(b' ');
|
||||
// dst.put_slice(&*msg.1);
|
||||
// dst.put_slice(b"\r\n");
|
||||
// Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -76,15 +80,13 @@ pub struct ResponseData {
|
|||
}
|
||||
|
||||
impl ResponseData {
|
||||
pub fn request_id(&self) -> Option<&RequestId> {
|
||||
pub fn request_id(&self) -> Option<&Tag> {
|
||||
match self.response {
|
||||
Response::Done { ref tag, .. } => Some(tag),
|
||||
Response::Done(ResponseDone { ref tag, .. }) => Some(tag),
|
||||
_ => None,
|
||||
}
|
||||
}
|
||||
|
||||
#[allow(clippy::needless_lifetimes)]
|
||||
pub fn parsed<'a>(&'a self) -> &'a Response<'a> {
|
||||
&self.response
|
||||
}
|
||||
pub fn parsed<'a>(&'a self) -> &'a Response<'a> { &self.response }
|
||||
}
|
||||
|
|
5
imap/src/events.rs
Normal file
5
imap/src/events.rs
Normal file
|
@ -0,0 +1,5 @@
|
|||
bitflags! {
|
||||
pub struct EventMask: u32 {
|
||||
const NONE = 0;
|
||||
}
|
||||
}
|
|
@ -1,16 +1,19 @@
|
|||
#[macro_use]
|
||||
extern crate anyhow;
|
||||
#[macro_use]
|
||||
extern crate async_trait;
|
||||
#[macro_use]
|
||||
extern crate log;
|
||||
#[macro_use]
|
||||
extern crate futures;
|
||||
#[macro_use]
|
||||
extern crate derive_builder;
|
||||
#[macro_use]
|
||||
extern crate nom;
|
||||
extern crate bitflags;
|
||||
|
||||
// mod auth;
|
||||
pub mod client;
|
||||
// mod codec;
|
||||
mod codec;
|
||||
mod events;
|
||||
// mod inner;
|
||||
pub mod proto;
|
||||
|
|
3
imap/src/proto/command.rs
Normal file
3
imap/src/proto/command.rs
Normal file
|
@ -0,0 +1,3 @@
|
|||
pub enum Command<'a> {
|
||||
Todo(&'a ()),
|
||||
}
|
|
@ -4,6 +4,7 @@
|
|||
mod macros;
|
||||
|
||||
// data types
|
||||
pub mod command;
|
||||
pub mod response;
|
||||
|
||||
// parsers
|
||||
|
|
|
@ -1,12 +1,14 @@
|
|||
use std::borrow::Cow;
|
||||
|
||||
#[derive(Clone)]
|
||||
#[derive(Clone, Debug)]
|
||||
pub struct Tag(pub String);
|
||||
|
||||
#[derive(Debug)]
|
||||
pub enum Response<'a> {
|
||||
Done(ResponseDone<'a>),
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct ResponseDone<'a> {
|
||||
pub tag: Tag,
|
||||
pub status: Status,
|
||||
|
@ -14,6 +16,7 @@ pub struct ResponseDone<'a> {
|
|||
pub info: Option<Cow<'a, str>>,
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
pub enum Status {
|
||||
Ok,
|
||||
No,
|
||||
|
@ -22,11 +25,13 @@ pub enum Status {
|
|||
Bye,
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
pub enum ResponseCode<'a> {
|
||||
Alert,
|
||||
Capabilities(Vec<Capability<'a>>),
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
pub enum Capability<'a> {
|
||||
Imap4rev1,
|
||||
Auth(Cow<'a, [u8]>),
|
||||
|
|
|
@ -4,7 +4,7 @@ use std::borrow::Cow;
|
|||
|
||||
use nom::{
|
||||
branch::alt,
|
||||
bytes::streaming::{tag_no_case, take_while1, take},
|
||||
bytes::streaming::{tag_no_case, take, take_while1},
|
||||
character::streaming::char,
|
||||
combinator::{map, map_res},
|
||||
multi::{many0, many1},
|
||||
|
@ -12,7 +12,7 @@ use nom::{
|
|||
IResult,
|
||||
};
|
||||
|
||||
use super::parsers::{satisfy, byte};
|
||||
use super::parsers::{byte, satisfy};
|
||||
use super::response::{Capability, ResponseCode};
|
||||
use super::rfc2234::{is_char, is_cr, is_ctl, is_dquote, is_lf, is_sp, CRLF, DIGIT, DQUOTE, SP};
|
||||
|
||||
|
|
Loading…
Reference in a new issue