add some test-related things

This commit is contained in:
Michael Zhang 2021-08-08 01:41:07 -05:00
parent 4ddcaf19c1
commit c1e770e050
Signed by: michael
GPG key ID: BDA47A31A3C8EE6B
10 changed files with 149 additions and 11 deletions

13
docker-compose.yml Normal file
View file

@ -0,0 +1,13 @@
version: "3"
services:
mail:
image: greenmail/standalone
ports:
- "3025:3025" # SMTP
- "3143:3143" # IMAP
- "3465:3465" # SMTPS
- "3993:3993" # IMAPS
- "3080:8080" # Web
environment:
- "GREENMAIL_OPTS=-Dgreenmail.setup.test.all -Dgreenmail.users=user:pass -Dgreenmail.verbose"

View file

@ -3,7 +3,12 @@ name = "imap"
version = "0.1.0"
edition = "2018"
[[bin]]
name = "greenmail-test"
path = "bin/greenmail_test.rs"
[features]
default = ["rfc2177-idle"]
rfc2177-idle = []
[dependencies]

View file

@ -0,0 +1,4 @@
#[tokio::main]
async fn main() {
}

View file

@ -1,8 +1,33 @@
pub trait AuthMethod {}
use tokio::io::{AsyncRead, AsyncWrite};
use crate::proto::command::{Command, CommandLogin};
use crate::client::inner::Inner;
pub trait Client: AsyncRead + AsyncWrite + Unpin + Sync + Send + 'static {}
#[async_trait]
pub trait AuthMethod {
async fn perform_auth<C>(&self, inner: &mut Inner<C>)
where
C: Client;
}
pub struct Login {
pub username: String,
pub password: String,
}
impl AuthMethod for Login {}
#[async_trait]
impl AuthMethod for Login {
async fn perform_auth<C>(&self, inner: &mut Inner<C>)
where
C: Client,
{
let command = Command::Login(CommandLogin {
username: &self.username,
password: &self.password,
});
let result = inner.execute(command).await;
}
}

View file

@ -1,13 +1,17 @@
use anyhow::Result;
use futures::future::FutureExt;
use tokio::{
io::{split, AsyncRead, AsyncWrite, ReadHalf},
io::{split, AsyncRead, AsyncWrite, ReadHalf, WriteHalf},
sync::oneshot,
task::JoinHandle,
};
use tokio_rustls::client::TlsStream;
use tokio_util::codec::FramedRead;
use crate::codec::ImapCodec;
use crate::proto::command::Command;
use super::upgrade::upgrade;
type ExitSender = oneshot::Sender<()>;
type ExitListener = oneshot::Receiver<()>;
@ -15,11 +19,13 @@ type ExitListener = oneshot::Receiver<()>;
pub struct Inner<C> {
read_exit: ExitSender,
read_handle: JoinHandle<ReadHalf<C>>,
write_half: WriteHalf<C>,
}
impl<C> Inner<C>
where
C: AsyncRead + AsyncWrite + Send + 'static,
C: AsyncRead + AsyncWrite + Unpin + Send + 'static,
{
pub async fn open(c: C) -> Result<Self> {
// break the stream of bytes into a reader and a writer
@ -34,8 +40,25 @@ where
Ok(Inner {
read_exit,
read_handle,
write_half,
})
}
pub async fn execute<'a>(&mut self, command: Command<'a>) {}
pub async fn upgrade(self) -> Result<Inner<TlsStream<C>>> {
// TODO: check that this capability exists??
// issue exit to the read loop and retrieve the read half
let _ = self.read_exit.send(());
let read_half = self.read_handle.await?;
let write_half = self.write_half;
// put the read half and write half back together
let stream = read_half.unsplit(write_half);
let tls_stream = upgrade(stream, "hellosu").await?;
Inner::open(tls_stream).await
}
}
// exit is a channel that will notify this loop when some external
@ -57,5 +80,6 @@ where
_ = exit => break,
}
}
todo!()
framed.into_inner()
}

View file

@ -1,4 +1,24 @@
use anyhow::Result;
use tokio_rustls::TlsStream;
use std::sync::Arc;
pub fn upgrade<C>(c: C) -> Result<TlsStream<C>> { todo!() }
use anyhow::Result;
use tokio::io::{AsyncRead, AsyncWrite};
use tokio_rustls::{
client::TlsStream, rustls::ClientConfig as RustlsConfig, webpki::DNSNameRef, TlsConnector,
};
pub async fn upgrade<C>(c: C, hostname: impl AsRef<str>) -> Result<TlsStream<C>>
where
C: AsyncRead + AsyncWrite + Unpin,
{
let server_name = hostname.as_ref();
let mut tls_config = RustlsConfig::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(server_name).unwrap();
let stream = tls_config.connect(dnsname, c).await?;
Ok(stream)
}

View file

@ -1,3 +1,43 @@
pub enum Command<'a> {
Todo(&'a ()),
// Any state
Capability,
Noop,
Logout,
// Not authenticated
Login(CommandLogin<'a>),
Starttls,
Authenticate,
// Authenticated
Select,
Examine,
Create,
Delete,
Rename,
Subscribe,
Unsubscribe,
List,
Lsub,
Status,
Append,
// Selected
Check,
Close,
Expunge,
Search,
Fetch,
Store,
Copy,
Uid,
// Extensions
#[cfg(feature = "rfc2177-idle")]
Idle,
}
pub struct CommandLogin<'a> {
pub username: &'a str,
pub password: &'a str,
}

View file

@ -11,3 +11,6 @@ pub mod response;
pub mod parsers;
pub mod rfc2234;
pub mod rfc3501;
#[cfg(feature = "rfc2177-idle")]
pub mod rfc2177;

View file

@ -0,0 +1 @@
//! Grammar from https://datatracker.ietf.org/doc/html/rfc2177#section-4

View file

@ -58,8 +58,11 @@ rule!(pub capability_data : Vec<Capability> => preceded(tag_no_case("CAPABILITY"
pub(crate) fn is_list_wildcards(c: u8) -> bool { c == b'%' || c == b'*' }
rule!(pub list_wildcards : u8 => satisfy(is_list_wildcards));
/// literal = "{" number "}" CRLF *CHAR8
/// ; Number represents the number of CHAR8s
// literal = "{" number "}" CRLF *CHAR8
// ; Number represents the number of CHAR8s
// TODO: Future work, could possibly initialize writing to file if the length is
// determined to exceed a certain threshold so we don't have insane amounts of
// data in memory
pub fn literal(i: &[u8]) -> IResult<&[u8], Vec<u8>> {
let mut length_of = terminated(delimited(char('{'), number, char('}')), CRLF);
let (i, length) = length_of(i)?;