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"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "23b62fc65de8e4e7f52534fb52b0f3ed04746ae267519eef2a83941e8085068b"
|
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]]
|
[[package]]
|
||||||
name = "autocfg"
|
name = "autocfg"
|
||||||
version = "1.0.1"
|
version = "1.0.1"
|
||||||
|
@ -260,6 +271,8 @@ name = "imap"
|
||||||
version = "0.1.0"
|
version = "0.1.0"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"anyhow",
|
"anyhow",
|
||||||
|
"async-trait",
|
||||||
|
"bitflags",
|
||||||
"bytes",
|
"bytes",
|
||||||
"derive_builder",
|
"derive_builder",
|
||||||
"futures",
|
"futures",
|
||||||
|
|
|
@ -8,6 +8,8 @@ rfc2177-idle = []
|
||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
anyhow = "1.0.42"
|
anyhow = "1.0.42"
|
||||||
|
async-trait = "0.1.51"
|
||||||
|
bitflags = "1.2.1"
|
||||||
bytes = "1.0.1"
|
bytes = "1.0.1"
|
||||||
derive_builder = "0.10.2"
|
derive_builder = "0.10.2"
|
||||||
futures = "0.3.16"
|
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::io;
|
||||||
use std::mem;
|
|
||||||
|
|
||||||
use bytes::{BufMut, Bytes, BytesMut};
|
use bytes::{BufMut, Bytes, BytesMut};
|
||||||
use nom::{self, Needed};
|
|
||||||
use tokio_util::codec::{Decoder, Encoder};
|
use tokio_util::codec::{Decoder, Encoder};
|
||||||
|
|
||||||
use imap_proto::types::{Request, RequestId, Response};
|
use crate::proto::{
|
||||||
|
command::Command,
|
||||||
|
response::{Response, ResponseDone, Tag},
|
||||||
|
};
|
||||||
|
|
||||||
#[derive(Default)]
|
#[derive(Default)]
|
||||||
pub struct ImapCodec {
|
pub struct ImapCodec {
|
||||||
|
@ -19,45 +21,47 @@ impl<'a> Decoder for ImapCodec {
|
||||||
if self.decode_need_message_bytes > buf.len() {
|
if self.decode_need_message_bytes > buf.len() {
|
||||||
return Ok(None);
|
return Ok(None);
|
||||||
}
|
}
|
||||||
|
|
||||||
let (response, rsp_len) = match imap_proto::Response::from_bytes(buf) {
|
todo!()
|
||||||
Ok((remaining, response)) => {
|
// let (response, rsp_len) = match Response::from_bytes(buf) {
|
||||||
// This SHOULD be acceptable/safe: BytesMut storage memory is
|
// Ok((remaining, response)) => {
|
||||||
// allocated on the heap and should not move. It will not be
|
// // This SHOULD be acceptable/safe: BytesMut storage memory is
|
||||||
// freed as long as we keep a reference alive, which we do
|
// // allocated on the heap and should not move. It will not be
|
||||||
// by retaining a reference to the split buffer, below.
|
// // freed as long as we keep a reference alive, which we do
|
||||||
let response = unsafe { mem::transmute(response) };
|
// // by retaining a reference to the split buffer, below.
|
||||||
(response, buf.len() - remaining.len())
|
// 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();
|
// Err(nom::Err::Incomplete(Needed::Size(min))) => {
|
||||||
return Ok(None);
|
// self.decode_need_message_bytes = min.get();
|
||||||
}
|
// return Ok(None);
|
||||||
Err(nom::Err::Incomplete(_)) => {
|
// }
|
||||||
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, .. })) => {
|
// Err(nom::Err::Error(nom::error::Error { code, .. }))
|
||||||
return Err(io::Error::new(
|
// | Err(nom::Err::Failure(nom::error::Error { code, .. })) => {
|
||||||
io::ErrorKind::Other,
|
// return Err(io::Error::new(
|
||||||
format!("{:?} during parsing of {:?}", code, buf),
|
// io::ErrorKind::Other,
|
||||||
));
|
// format!("{:?} during parsing of {:?}", code, buf),
|
||||||
}
|
// ));
|
||||||
};
|
// }
|
||||||
let raw = buf.split_to(rsp_len).freeze();
|
// };
|
||||||
self.decode_need_message_bytes = 0;
|
// let raw = buf.split_to(rsp_len).freeze();
|
||||||
Ok(Some(ResponseData { raw, response }))
|
// 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;
|
type Error = io::Error;
|
||||||
fn encode(&mut self, msg: &Request, dst: &mut BytesMut) -> Result<(), io::Error> {
|
fn encode(&mut self, msg: &Command, dst: &mut BytesMut) -> Result<(), io::Error> {
|
||||||
dst.put(&*msg.0);
|
todo!()
|
||||||
dst.put_u8(b' ');
|
// dst.put(&*msg.0);
|
||||||
dst.put_slice(&*msg.1);
|
// dst.put_u8(b' ');
|
||||||
dst.put_slice(b"\r\n");
|
// dst.put_slice(&*msg.1);
|
||||||
Ok(())
|
// dst.put_slice(b"\r\n");
|
||||||
|
// Ok(())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -76,15 +80,13 @@ pub struct ResponseData {
|
||||||
}
|
}
|
||||||
|
|
||||||
impl ResponseData {
|
impl ResponseData {
|
||||||
pub fn request_id(&self) -> Option<&RequestId> {
|
pub fn request_id(&self) -> Option<&Tag> {
|
||||||
match self.response {
|
match self.response {
|
||||||
Response::Done { ref tag, .. } => Some(tag),
|
Response::Done(ResponseDone { ref tag, .. }) => Some(tag),
|
||||||
_ => None,
|
_ => None,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[allow(clippy::needless_lifetimes)]
|
#[allow(clippy::needless_lifetimes)]
|
||||||
pub fn parsed<'a>(&'a self) -> &'a Response<'a> {
|
pub fn parsed<'a>(&'a self) -> &'a Response<'a> { &self.response }
|
||||||
&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]
|
#[macro_use]
|
||||||
extern crate anyhow;
|
extern crate anyhow;
|
||||||
#[macro_use]
|
#[macro_use]
|
||||||
|
extern crate async_trait;
|
||||||
|
#[macro_use]
|
||||||
extern crate log;
|
extern crate log;
|
||||||
#[macro_use]
|
#[macro_use]
|
||||||
extern crate futures;
|
extern crate futures;
|
||||||
#[macro_use]
|
#[macro_use]
|
||||||
extern crate derive_builder;
|
extern crate derive_builder;
|
||||||
#[macro_use]
|
#[macro_use]
|
||||||
extern crate nom;
|
extern crate bitflags;
|
||||||
|
|
||||||
// mod auth;
|
// mod auth;
|
||||||
pub mod client;
|
pub mod client;
|
||||||
// mod codec;
|
mod codec;
|
||||||
|
mod events;
|
||||||
// mod inner;
|
// mod inner;
|
||||||
pub mod proto;
|
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;
|
mod macros;
|
||||||
|
|
||||||
// data types
|
// data types
|
||||||
|
pub mod command;
|
||||||
pub mod response;
|
pub mod response;
|
||||||
|
|
||||||
// parsers
|
// parsers
|
||||||
|
|
|
@ -1,12 +1,14 @@
|
||||||
use std::borrow::Cow;
|
use std::borrow::Cow;
|
||||||
|
|
||||||
#[derive(Clone)]
|
#[derive(Clone, Debug)]
|
||||||
pub struct Tag(pub String);
|
pub struct Tag(pub String);
|
||||||
|
|
||||||
|
#[derive(Debug)]
|
||||||
pub enum Response<'a> {
|
pub enum Response<'a> {
|
||||||
Done(ResponseDone<'a>),
|
Done(ResponseDone<'a>),
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[derive(Debug)]
|
||||||
pub struct ResponseDone<'a> {
|
pub struct ResponseDone<'a> {
|
||||||
pub tag: Tag,
|
pub tag: Tag,
|
||||||
pub status: Status,
|
pub status: Status,
|
||||||
|
@ -14,6 +16,7 @@ pub struct ResponseDone<'a> {
|
||||||
pub info: Option<Cow<'a, str>>,
|
pub info: Option<Cow<'a, str>>,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[derive(Debug)]
|
||||||
pub enum Status {
|
pub enum Status {
|
||||||
Ok,
|
Ok,
|
||||||
No,
|
No,
|
||||||
|
@ -22,11 +25,13 @@ pub enum Status {
|
||||||
Bye,
|
Bye,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[derive(Debug)]
|
||||||
pub enum ResponseCode<'a> {
|
pub enum ResponseCode<'a> {
|
||||||
Alert,
|
Alert,
|
||||||
Capabilities(Vec<Capability<'a>>),
|
Capabilities(Vec<Capability<'a>>),
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[derive(Debug)]
|
||||||
pub enum Capability<'a> {
|
pub enum Capability<'a> {
|
||||||
Imap4rev1,
|
Imap4rev1,
|
||||||
Auth(Cow<'a, [u8]>),
|
Auth(Cow<'a, [u8]>),
|
||||||
|
|
|
@ -4,7 +4,7 @@ use std::borrow::Cow;
|
||||||
|
|
||||||
use nom::{
|
use nom::{
|
||||||
branch::alt,
|
branch::alt,
|
||||||
bytes::streaming::{tag_no_case, take_while1, take},
|
bytes::streaming::{tag_no_case, take, take_while1},
|
||||||
character::streaming::char,
|
character::streaming::char,
|
||||||
combinator::{map, map_res},
|
combinator::{map, map_res},
|
||||||
multi::{many0, many1},
|
multi::{many0, many1},
|
||||||
|
@ -12,7 +12,7 @@ use nom::{
|
||||||
IResult,
|
IResult,
|
||||||
};
|
};
|
||||||
|
|
||||||
use super::parsers::{satisfy, byte};
|
use super::parsers::{byte, satisfy};
|
||||||
use super::response::{Capability, ResponseCode};
|
use super::response::{Capability, ResponseCode};
|
||||||
use super::rfc2234::{is_char, is_cr, is_ctl, is_dquote, is_lf, is_sp, CRLF, DIGIT, DQUOTE, SP};
|
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