move some common shit to common
This commit is contained in:
parent
7945051eac
commit
68f63b5a36
22 changed files with 84 additions and 50 deletions
11
Cargo.lock
generated
11
Cargo.lock
generated
|
@ -671,6 +671,7 @@ dependencies = [
|
||||||
"futures",
|
"futures",
|
||||||
"log",
|
"log",
|
||||||
"nom",
|
"nom",
|
||||||
|
"panorama-proto-common",
|
||||||
"stderrlog",
|
"stderrlog",
|
||||||
"tokio",
|
"tokio",
|
||||||
"tokio-rustls",
|
"tokio-rustls",
|
||||||
|
@ -678,6 +679,16 @@ dependencies = [
|
||||||
"webpki-roots",
|
"webpki-roots",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "panorama-proto-common"
|
||||||
|
version = "0.0.1"
|
||||||
|
dependencies = [
|
||||||
|
"anyhow",
|
||||||
|
"bytes",
|
||||||
|
"format-bytes",
|
||||||
|
"nom",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "panorama-smtp"
|
name = "panorama-smtp"
|
||||||
version = "0.0.1"
|
version = "0.0.1"
|
||||||
|
|
|
@ -3,6 +3,7 @@ members = [
|
||||||
"daemon",
|
"daemon",
|
||||||
"imap",
|
"imap",
|
||||||
"smtp",
|
"smtp",
|
||||||
|
"proto-common",
|
||||||
]
|
]
|
||||||
|
|
||||||
[profile.release]
|
[profile.release]
|
||||||
|
|
|
@ -36,3 +36,4 @@ tokio = { version = "1.9.0", features = ["full"] }
|
||||||
tokio-rustls = { version = "0.22.0", features = ["dangerous_configuration"] }
|
tokio-rustls = { version = "0.22.0", features = ["dangerous_configuration"] }
|
||||||
tokio-util = { version = "0.6.7", features = ["codec"] }
|
tokio-util = { version = "0.6.7", features = ["codec"] }
|
||||||
webpki-roots = "0.21.1"
|
webpki-roots = "0.21.1"
|
||||||
|
panorama-proto-common = { path = "../proto-common" }
|
|
@ -1,12 +1,10 @@
|
||||||
use tokio::io::{AsyncRead, AsyncWrite};
|
use tokio::io::{AsyncRead, AsyncWrite};
|
||||||
|
|
||||||
use anyhow::Result;
|
use anyhow::Result;
|
||||||
|
use panorama_proto_common::Bytes;
|
||||||
|
|
||||||
use crate::client::inner::Inner;
|
use crate::client::inner::Inner;
|
||||||
use crate::proto::{
|
use crate::proto::command::{Command, CommandLogin};
|
||||||
bytes::Bytes,
|
|
||||||
command::{Command, CommandLogin},
|
|
||||||
};
|
|
||||||
|
|
||||||
pub trait Client: AsyncRead + AsyncWrite + Unpin + Sync + Send + 'static {}
|
pub trait Client: AsyncRead + AsyncWrite + Unpin + Sync + Send + 'static {}
|
||||||
impl<C> Client for C where C: Send + Sync + Unpin + AsyncWrite + AsyncRead + 'static {}
|
impl<C> Client for C where C: Send + Sync + Unpin + AsyncWrite + AsyncRead + 'static {}
|
||||||
|
|
|
@ -6,11 +6,11 @@ use futures::{
|
||||||
future::{self, FutureExt},
|
future::{self, FutureExt},
|
||||||
stream::{Stream, StreamExt},
|
stream::{Stream, StreamExt},
|
||||||
};
|
};
|
||||||
|
use panorama_proto_common::Bytes;
|
||||||
use tokio::net::TcpStream;
|
use tokio::net::TcpStream;
|
||||||
use tokio_rustls::client::TlsStream;
|
use tokio_rustls::client::TlsStream;
|
||||||
|
|
||||||
use crate::proto::{
|
use crate::proto::{
|
||||||
bytes::Bytes,
|
|
||||||
command::{
|
command::{
|
||||||
Command, CommandFetch, CommandList, CommandSearch, CommandSelect, FetchItems,
|
Command, CommandFetch, CommandList, CommandSearch, CommandSelect, FetchItems,
|
||||||
SearchCriteria,
|
SearchCriteria,
|
||||||
|
|
|
@ -2,10 +2,10 @@ use std::io::{self};
|
||||||
|
|
||||||
use bytes::{BufMut, BytesMut};
|
use bytes::{BufMut, BytesMut};
|
||||||
use nom::Needed;
|
use nom::Needed;
|
||||||
|
use panorama_proto_common::Bytes;
|
||||||
use tokio_util::codec::{Decoder, Encoder};
|
use tokio_util::codec::{Decoder, Encoder};
|
||||||
|
|
||||||
use crate::proto::{
|
use crate::proto::{
|
||||||
bytes::Bytes,
|
|
||||||
command::Command,
|
command::Command,
|
||||||
convert_error::convert_error,
|
convert_error::convert_error,
|
||||||
response::{Response, Tag},
|
response::{Response, Tag},
|
||||||
|
|
|
@ -5,6 +5,7 @@ use futures::{
|
||||||
future::{self, FutureExt, TryFutureExt},
|
future::{self, FutureExt, TryFutureExt},
|
||||||
stream::StreamExt,
|
stream::StreamExt,
|
||||||
};
|
};
|
||||||
|
use panorama_proto_common::Bytes;
|
||||||
use tokio::{
|
use tokio::{
|
||||||
io::{split, AsyncRead, AsyncWrite, AsyncWriteExt, BufWriter, ReadHalf, WriteHalf},
|
io::{split, AsyncRead, AsyncWrite, AsyncWriteExt, BufWriter, ReadHalf, WriteHalf},
|
||||||
sync::{mpsc, oneshot},
|
sync::{mpsc, oneshot},
|
||||||
|
@ -14,7 +15,6 @@ use tokio_rustls::client::TlsStream;
|
||||||
use tokio_util::codec::FramedRead;
|
use tokio_util::codec::FramedRead;
|
||||||
|
|
||||||
use crate::proto::{
|
use crate::proto::{
|
||||||
bytes::Bytes,
|
|
||||||
command::Command,
|
command::Command,
|
||||||
response::{Condition, Response, Status, Tag},
|
response::{Condition, Response, Status, Tag},
|
||||||
rfc3501::capability as parse_capability,
|
rfc3501::capability as parse_capability,
|
||||||
|
|
|
@ -10,8 +10,8 @@ extern crate format_bytes;
|
||||||
extern crate futures;
|
extern crate futures;
|
||||||
#[macro_use]
|
#[macro_use]
|
||||||
extern crate log;
|
extern crate log;
|
||||||
// #[macro_use]
|
#[macro_use]
|
||||||
// extern crate bitflags;
|
extern crate panorama_proto_common;
|
||||||
|
|
||||||
pub mod client;
|
pub mod client;
|
||||||
// pub mod events;
|
// pub mod events;
|
||||||
|
|
|
@ -1,8 +1,9 @@
|
||||||
use std::io::{self, Write};
|
use std::io::{self, Write};
|
||||||
|
|
||||||
use format_bytes::DisplayBytes;
|
use format_bytes::DisplayBytes;
|
||||||
|
use panorama_proto_common::{quote_string as q, Bytes};
|
||||||
|
|
||||||
use crate::proto::{bytes::Bytes, formatter::quote_string as q};
|
use super::rfc3501::is_quoted_specials as isq;
|
||||||
|
|
||||||
#[derive(Clone, Debug)]
|
#[derive(Clone, Debug)]
|
||||||
pub enum Command {
|
pub enum Command {
|
||||||
|
@ -59,15 +60,25 @@ impl DisplayBytes for Command {
|
||||||
|
|
||||||
// command-nonauth
|
// command-nonauth
|
||||||
Command::Login(login) => {
|
Command::Login(login) => {
|
||||||
write_bytes!(w, b"LOGIN {} {}", q(&login.userid), q(&login.password))
|
write_bytes!(
|
||||||
|
w,
|
||||||
|
b"LOGIN {} {}",
|
||||||
|
q(&login.userid, isq),
|
||||||
|
q(&login.password, isq)
|
||||||
|
)
|
||||||
}
|
}
|
||||||
Command::Starttls => write_bytes!(w, b"STARTTLS"),
|
Command::Starttls => write_bytes!(w, b"STARTTLS"),
|
||||||
|
|
||||||
// command-auth
|
// command-auth
|
||||||
Command::List(list) => {
|
Command::List(list) => {
|
||||||
write_bytes!(w, b"LIST {} {}", q(&list.reference), q(&list.mailbox))
|
write_bytes!(
|
||||||
|
w,
|
||||||
|
b"LIST {} {}",
|
||||||
|
q(&list.reference, isq),
|
||||||
|
q(&list.mailbox, isq)
|
||||||
|
)
|
||||||
}
|
}
|
||||||
Command::Select(select) => write_bytes!(w, b"SELECT {}", q(&select.mailbox)),
|
Command::Select(select) => write_bytes!(w, b"SELECT {}", q(&select.mailbox, isq)),
|
||||||
|
|
||||||
#[cfg(feature = "rfc2177")]
|
#[cfg(feature = "rfc2177")]
|
||||||
Command::Idle => write_bytes!(w, b"IDLE"),
|
Command::Idle => write_bytes!(w, b"IDLE"),
|
||||||
|
|
|
@ -1,25 +1,9 @@
|
||||||
macro_rules! rule {
|
macro_rules! rule {
|
||||||
($vis:vis $name:ident : $ret:ty => $expr:expr) => {
|
($vis:vis $name:ident : $ret:ty => $expr:expr) => {
|
||||||
$vis fn $name (
|
$vis fn $name (
|
||||||
i: crate::proto::bytes::Bytes
|
i: panorama_proto_common::Bytes
|
||||||
) -> crate::proto::parsers::VResult<crate::proto::bytes::Bytes, $ret> {
|
) -> panorama_proto_common::VResult<panorama_proto_common::Bytes, $ret> {
|
||||||
$expr(i)
|
$expr(i)
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
// macro_rules! pred {
|
|
||||||
// ($($expr:tt)*) => { |c: u8| _pred!(expr { $($expr)* })(c) };
|
|
||||||
// }
|
|
||||||
|
|
||||||
// macro_rules! _pred {
|
|
||||||
// (expr {}) => {};
|
|
||||||
// (expr { $name:ident }) => { |b| $name(b) };
|
|
||||||
// (expr { ! $($expr:tt)* }) => { |b| !_pred!(expr { $($expr)* })(b) };
|
|
||||||
// (expr { ($($L:tt)*) && ($($R:tt)*) }) => {
|
|
||||||
// |b| { _pred!(expr { $($L)* })(b) && _pred!(expr { $($R)* })(b) }
|
|
||||||
// };
|
|
||||||
// (expr { ($($L:tt)*) || ($($R:tt)*) }) => {
|
|
||||||
// |b| { _pred!(expr { $($L)* })(b) || _pred!(expr { $($R)* })(b) }
|
|
||||||
// };
|
|
||||||
// }
|
|
||||||
|
|
|
@ -5,11 +5,7 @@
|
||||||
// utils
|
// utils
|
||||||
#[macro_use]
|
#[macro_use]
|
||||||
mod macros;
|
mod macros;
|
||||||
pub mod bytes;
|
|
||||||
#[macro_use]
|
|
||||||
pub mod parsers;
|
|
||||||
pub mod convert_error;
|
pub mod convert_error;
|
||||||
pub mod formatter;
|
|
||||||
|
|
||||||
// data types
|
// data types
|
||||||
pub mod command;
|
pub mod command;
|
||||||
|
|
|
@ -1,8 +1,7 @@
|
||||||
use std::ops::RangeInclusive;
|
use std::ops::RangeInclusive;
|
||||||
|
|
||||||
use chrono::{DateTime, FixedOffset};
|
use chrono::{DateTime, FixedOffset};
|
||||||
|
use panorama_proto_common::Bytes;
|
||||||
use super::bytes::Bytes;
|
|
||||||
|
|
||||||
pub type Atom = Bytes;
|
pub type Atom = Bytes;
|
||||||
|
|
||||||
|
|
|
@ -8,8 +8,7 @@ use nom::{
|
||||||
multi::many0,
|
multi::many0,
|
||||||
sequence::{pair, preceded},
|
sequence::{pair, preceded},
|
||||||
};
|
};
|
||||||
|
use panorama_proto_common::{byte, satisfy, skip};
|
||||||
use super::parsers::{byte, satisfy, skip};
|
|
||||||
|
|
||||||
rule!(pub ALPHA : u8 => satisfy(|c| (c >= b'a' && c <= b'z') || (c >= b'A' && c <= b'Z')));
|
rule!(pub ALPHA : u8 => satisfy(|c| (c >= b'a' && c <= b'z') || (c >= b'A' && c <= b'Z')));
|
||||||
|
|
||||||
|
|
|
@ -9,9 +9,10 @@ use nom::{
|
||||||
multi::{many0, many1},
|
multi::{many0, many1},
|
||||||
sequence::{delimited, pair, preceded, separated_pair, terminated, tuple},
|
sequence::{delimited, pair, preceded, separated_pair, terminated, tuple},
|
||||||
};
|
};
|
||||||
|
use panorama_proto_common::{
|
||||||
|
byte, never, parse_u32, satisfy, tagi, take, take_while1, Bytes, VResult,
|
||||||
|
};
|
||||||
|
|
||||||
use super::bytes::Bytes;
|
|
||||||
use super::parsers::{byte, never, parse_u32, satisfy, tagi, take, take_while1, VResult};
|
|
||||||
use super::response::{
|
use super::response::{
|
||||||
Address, Atom, Capability, Condition, Envelope, Flag, Mailbox, MailboxData, MailboxList,
|
Address, Atom, Capability, Condition, Envelope, Flag, Mailbox, MailboxData, MailboxList,
|
||||||
MailboxListFlag, MessageAttribute, Response, ResponseCode, ResponseText, Status, Tag,
|
MailboxListFlag, MessageAttribute, Response, ResponseCode, ResponseText, Status, Tag,
|
||||||
|
|
|
@ -1,4 +1,5 @@
|
||||||
use super::bytes::Bytes;
|
use panorama_proto_common::Bytes;
|
||||||
|
|
||||||
use super::rfc3501::*;
|
use super::rfc3501::*;
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
|
|
15
proto-common/Cargo.toml
Normal file
15
proto-common/Cargo.toml
Normal file
|
@ -0,0 +1,15 @@
|
||||||
|
[package]
|
||||||
|
name = "panorama-proto-common"
|
||||||
|
description = "Common code between protocol implementations"
|
||||||
|
version = "0.0.1"
|
||||||
|
edition = "2018"
|
||||||
|
authors = ["Michael Zhang <mail@mzhang.io>"]
|
||||||
|
license = "GPL-3.0-or-later"
|
||||||
|
repository = "https://git.mzhang.io/michael/panorama"
|
||||||
|
workspace = ".."
|
||||||
|
|
||||||
|
[dependencies]
|
||||||
|
anyhow = "1.0.42"
|
||||||
|
bytes = "1.0.1"
|
||||||
|
format-bytes = "0.2.2"
|
||||||
|
nom = "6.2.1"
|
|
@ -1,12 +1,10 @@
|
||||||
use super::rfc3501::is_quoted_specials;
|
pub fn quote_string(input: impl AsRef<[u8]>, should_escape: impl Fn(u8) -> bool) -> Vec<u8> {
|
||||||
|
|
||||||
pub fn quote_string(input: impl AsRef<[u8]>) -> Vec<u8> {
|
|
||||||
let input = input.as_ref();
|
let input = input.as_ref();
|
||||||
let mut ret = Vec::with_capacity(input.len() + 2);
|
let mut ret = Vec::with_capacity(input.len() + 2);
|
||||||
|
|
||||||
ret.push(b'\x22');
|
ret.push(b'\x22');
|
||||||
for c in input {
|
for c in input {
|
||||||
if is_quoted_specials(*c) {
|
if should_escape(*c) {
|
||||||
ret.push(b'\\');
|
ret.push(b'\\');
|
||||||
}
|
}
|
||||||
ret.push(*c);
|
ret.push(*c);
|
10
proto-common/src/lib.rs
Normal file
10
proto-common/src/lib.rs
Normal file
|
@ -0,0 +1,10 @@
|
||||||
|
#[macro_use]
|
||||||
|
extern crate anyhow;
|
||||||
|
|
||||||
|
mod bytes;
|
||||||
|
mod formatter;
|
||||||
|
mod parsers;
|
||||||
|
|
||||||
|
pub use crate::bytes::Bytes;
|
||||||
|
pub use crate::formatter::quote_string;
|
||||||
|
pub use crate::parsers::{byte, never, parse_u32, satisfy, skip, tagi, take, take_while1, VResult};
|
|
@ -5,8 +5,8 @@ use nom::{
|
||||||
};
|
};
|
||||||
|
|
||||||
use super::bytes::{ShitCompare, ShitNeededForParsing};
|
use super::bytes::{ShitCompare, ShitNeededForParsing};
|
||||||
use super::rfc2234::is_digit;
|
|
||||||
|
|
||||||
|
/// A specific form of `nom::IResult` that uses `nom::error::VerboseError`.
|
||||||
pub type VResult<I, O> = IResult<I, O, VerboseError<I>>;
|
pub type VResult<I, O> = IResult<I, O, VerboseError<I>>;
|
||||||
|
|
||||||
/// `sep_list!(t, d)` represents `t *(d t)` and automatically collapses it into
|
/// `sep_list!(t, d)` represents `t *(d t)` and automatically collapses it into
|
||||||
|
@ -16,6 +16,7 @@ pub type VResult<I, O> = IResult<I, O, VerboseError<I>>;
|
||||||
/// vec.
|
/// vec.
|
||||||
///
|
///
|
||||||
/// If `d` is not passed then it defaults to `SP`.
|
/// If `d` is not passed then it defaults to `SP`.
|
||||||
|
#[macro_export]
|
||||||
macro_rules! sep_list {
|
macro_rules! sep_list {
|
||||||
($t:expr) => {
|
($t:expr) => {
|
||||||
map(
|
map(
|
||||||
|
@ -55,27 +56,31 @@ macro_rules! sep_list {
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[macro_export]
|
||||||
macro_rules! opt_nil {
|
macro_rules! opt_nil {
|
||||||
($t:expr) => {
|
($t:expr) => {
|
||||||
alt((map($t, Some), map(crate::proto::rfc3501::nil, |_| None)))
|
alt((map($t, Some), map(crate::proto::rfc3501::nil, |_| None)))
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[macro_export]
|
||||||
macro_rules! paren {
|
macro_rules! paren {
|
||||||
($t:expr) => {
|
($t:expr) => {
|
||||||
delimited(byte(b'('), $t, byte(b')'))
|
delimited(byte(b'('), $t, byte(b')'))
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Parse from a [u8] into a u32 without first decoding it to UTF-8.
|
||||||
pub fn parse_u32(s: impl AsRef<[u8]>) -> Result<u32> {
|
pub fn parse_u32(s: impl AsRef<[u8]>) -> Result<u32> {
|
||||||
let mut total = 0u32;
|
let mut total = 0u32;
|
||||||
let s = s.as_ref();
|
let s = s.as_ref();
|
||||||
for digit in s.iter() {
|
for digit in s.iter() {
|
||||||
|
let digit = *digit;
|
||||||
total *= 10;
|
total *= 10;
|
||||||
if !is_digit(*digit) {
|
if !(digit >= b'0' && digit <= b'9') {
|
||||||
bail!("invalid digit {}", digit)
|
bail!("invalid digit {}", digit)
|
||||||
}
|
}
|
||||||
total += (*digit - b'\x30') as u32;
|
total += (digit - b'\x30') as u32;
|
||||||
}
|
}
|
||||||
Ok(total)
|
Ok(total)
|
||||||
}
|
}
|
4
smtp/src/proto/rfc5321.rs
Normal file
4
smtp/src/proto/rfc5321.rs
Normal file
|
@ -0,0 +1,4 @@
|
||||||
|
//! SMTP Specification
|
||||||
|
//! ---
|
||||||
|
//!
|
||||||
|
//! Grammar from <https://datatracker.ietf.org/doc/html/rfc5321>
|
Loading…
Reference in a new issue