response code
This commit is contained in:
parent
a22a2ee35c
commit
3cc65d5ce7
5 changed files with 250 additions and 30 deletions
|
@ -8,6 +8,7 @@ pub mod command;
|
|||
pub mod response;
|
||||
|
||||
// parsers
|
||||
#[macro_use]
|
||||
pub mod parsers;
|
||||
pub mod rfc2234;
|
||||
pub mod rfc3501;
|
||||
|
|
|
@ -1,12 +1,34 @@
|
|||
use std::ops::RangeFrom;
|
||||
|
||||
use nom::{
|
||||
error::{ErrorKind, ParseError},
|
||||
error::{Error, ErrorKind, ParseError},
|
||||
Err, IResult, InputIter, Needed, Parser, Slice,
|
||||
};
|
||||
|
||||
pub fn skip<E, F, I: Clone, O>(mut f: F) -> impl FnMut(I) -> IResult<I, (), E>
|
||||
macro_rules! sep_list {
|
||||
($t:expr) => {
|
||||
map(pair($t, many0(preceded(crate::proto::rfc2234::SP, $t))),
|
||||
|(hd, mut tl)| { tl.insert(0, hd); tl })
|
||||
};
|
||||
($t:expr, $d:expr) => {
|
||||
map(pair($t, many0(preceded($d, $t))),
|
||||
|(hd, mut tl)| { tl.insert(0, hd); tl })
|
||||
};
|
||||
(? $t:expr) => {
|
||||
map(opt(pair($t, many0(preceded(crate::proto::rfc2234::SP, $t)))),
|
||||
|opt| opt.map(|(hd, mut tl)| { tl.insert(0, hd); tl }).unwrap_or_else(Vec::new))
|
||||
};
|
||||
(? $t:expr, $d:expr) => {
|
||||
map(opt(pair($t, many0(preceded($d, $t)))),
|
||||
|opt| opt.map(|(hd, mut tl)| { tl.insert(0, hd); tl }).unwrap_or_else(Vec::new))
|
||||
};
|
||||
}
|
||||
|
||||
pub fn never<I, O>(i: I) -> IResult<I, O> { Err(Err::Error(Error::new(i, ErrorKind::Not))) }
|
||||
|
||||
pub fn skip<E, F, I, O>(mut f: F) -> impl FnMut(I) -> IResult<I, (), E>
|
||||
where
|
||||
I: Clone,
|
||||
F: Parser<I, O, E>,
|
||||
{
|
||||
move |i: I| match f.parse(i.clone()) {
|
||||
|
|
|
@ -1,17 +1,43 @@
|
|||
use std::borrow::Cow;
|
||||
|
||||
pub type Atom<'a> = Cow<'a, [u8]>;
|
||||
pub type CowU8<'a> = Cow<'a, [u8]>;
|
||||
|
||||
#[derive(Clone, Debug)]
|
||||
pub struct Tag(pub String);
|
||||
pub struct Tag<'a>(pub CowU8<'a>);
|
||||
|
||||
#[derive(Debug)]
|
||||
#[non_exhaustive]
|
||||
pub enum Response<'a> {
|
||||
Capabilities(Vec<Capability<'a>>),
|
||||
Continue(ResponseText<'a>),
|
||||
Condition(Condition<'a>),
|
||||
Done(ResponseDone<'a>),
|
||||
MailboxData(MailboxData<'a>),
|
||||
Fetch(u32, Vec<MessageAttribute<'a>>),
|
||||
Expunge(u32),
|
||||
Fatal(Condition<'a>),
|
||||
Tagged(Tag<'a>, Condition<'a>),
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct ResponseText<'a> {
|
||||
pub code: Option<ResponseCode<'a>>,
|
||||
pub info: CowU8<'a>,
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
pub enum MessageAttribute<'a> {
|
||||
Flags(Vec<Flag<'a>>),
|
||||
Envelope(Envelope),
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct Envelope {}
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct ResponseDone<'a> {
|
||||
pub tag: Tag,
|
||||
pub tag: Tag<'a>,
|
||||
pub status: Status,
|
||||
pub code: Option<ResponseCode<'a>>,
|
||||
pub info: Option<Cow<'a, str>>,
|
||||
|
@ -21,7 +47,7 @@ pub struct ResponseDone<'a> {
|
|||
pub struct Condition<'a> {
|
||||
pub status: Status,
|
||||
pub code: Option<ResponseCode<'a>>,
|
||||
pub text: String,
|
||||
pub info: CowU8<'a>,
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
|
@ -43,6 +69,46 @@ pub enum ResponseCode<'a> {
|
|||
#[derive(Debug)]
|
||||
pub enum Capability<'a> {
|
||||
Imap4rev1,
|
||||
Auth(Cow<'a, [u8]>),
|
||||
Atom(Cow<'a, [u8]>),
|
||||
Auth(Atom<'a>),
|
||||
Atom(Atom<'a>),
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
pub enum MailboxData<'a> {
|
||||
Flags(Vec<Flag<'a>>),
|
||||
List(MailboxList<'a>),
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
pub enum Mailbox<'a> {
|
||||
Inbox,
|
||||
Name(CowU8<'a>),
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
pub enum Flag<'a> {
|
||||
Answered,
|
||||
Flagged,
|
||||
Deleted,
|
||||
Seen,
|
||||
Draft,
|
||||
Recent,
|
||||
Keyword(Atom<'a>),
|
||||
Extension(Atom<'a>),
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct MailboxList<'a> {
|
||||
pub flags: Vec<MailboxListFlag<'a>>,
|
||||
pub delimiter: Option<u8>,
|
||||
pub mailbox: Mailbox<'a>,
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
pub enum MailboxListFlag<'a> {
|
||||
NoInferiors,
|
||||
NoSelect,
|
||||
Marked,
|
||||
Unmarked,
|
||||
Extension(Atom<'a>),
|
||||
}
|
||||
|
|
|
@ -19,7 +19,8 @@ rule!(pub CRLF : (u8, u8) => pair(CR, LF));
|
|||
pub fn is_ctl(c: u8) -> bool { c <= b'\x1f' || c == b'\x7f' }
|
||||
rule!(pub CTL : u8 => satisfy(is_ctl));
|
||||
|
||||
rule!(pub DIGIT : u8 => satisfy(|c| c >= b'\x30' && c <= b'\x39'));
|
||||
pub fn is_digit(c: u8) -> bool { c >= b'\x30' && c <= b'\x39' }
|
||||
rule!(pub DIGIT : u8 => satisfy(is_digit));
|
||||
|
||||
pub(crate) fn is_dquote(c: u8) -> bool { c == b'\x22' }
|
||||
rule!(pub DQUOTE : u8 => satisfy(is_dquote));
|
||||
|
|
|
@ -6,27 +6,28 @@ use nom::{
|
|||
branch::alt,
|
||||
bytes::streaming::{tag_no_case, take, take_while1},
|
||||
character::streaming::char,
|
||||
combinator::{map, map_res},
|
||||
multi::{many0, many1},
|
||||
sequence::{delimited, pair, preceded, separated_pair, terminated},
|
||||
combinator::{map, verify, map_res, opt},
|
||||
multi::{many0},
|
||||
sequence::{delimited, pair, tuple, preceded, separated_pair, terminated},
|
||||
IResult,
|
||||
};
|
||||
|
||||
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};
|
||||
use super::parsers::{byte, never, satisfy};
|
||||
use super::response::{
|
||||
Atom, Capability, Condition, CowU8, Flag, Mailbox, MailboxData, MailboxList, MailboxListFlag,
|
||||
Response, ResponseCode, Status, Tag, MessageAttribute, Envelope, ResponseText,
|
||||
};
|
||||
use super::rfc2234::{is_char, is_cr, is_ctl, is_digit, is_dquote, is_lf, is_sp, CRLF, DQUOTE, SP};
|
||||
|
||||
rule!(pub astring : Vec<u8> => alt((many1(ASTRING_CHAR), string)));
|
||||
rule!(pub astring : CowU8 => alt((map(take_while1(is_astring_char), Cow::from), string)));
|
||||
|
||||
pub(crate) fn is_astring_char(c: u8) -> bool { is_atom_char(c) || is_resp_specials(c) }
|
||||
rule!(pub ASTRING_CHAR : u8 => alt((ATOM_CHAR, resp_specials)));
|
||||
|
||||
rule!(pub atom : Vec<u8> => many1(ATOM_CHAR));
|
||||
rule!(pub atom : CowU8 => map(take_while1(is_atom_char), Cow::from));
|
||||
|
||||
// TODO: somehow incorporate CHAR in here?
|
||||
// technically ATOM_CHAR is defined as <any CHAR except atom-specials>
|
||||
// but "except"-style rules don't really make sense except for character sets
|
||||
// and some other niche cases so probably doesn't warrant a separate combinator
|
||||
rule!(pub ATOM_CHAR : u8 => satisfy(pred!((is_char) && (!is_atom_specials))));
|
||||
pub(crate) fn is_atom_char(c: u8) -> bool { is_char(c) && !is_atom_specials(c) }
|
||||
rule!(pub ATOM_CHAR : u8 => satisfy(is_atom_char));
|
||||
|
||||
pub(crate) fn is_atom_specials(c: u8) -> bool {
|
||||
c == b'('
|
||||
|
@ -40,7 +41,7 @@ pub(crate) fn is_atom_specials(c: u8) -> bool {
|
|||
}
|
||||
rule!(pub atom_specials : u8 => satisfy(is_atom_specials));
|
||||
|
||||
rule!(pub auth_type : Vec<u8> => atom);
|
||||
rule!(pub auth_type : Atom => atom);
|
||||
|
||||
rule!(pub capability : Capability => alt((
|
||||
map(preceded(tag_no_case("AUTH="), auth_type), |s| Capability::Auth(Cow::from(s))),
|
||||
|
@ -55,6 +56,28 @@ rule!(pub capability_data : Vec<Capability> => preceded(tag_no_case("CAPABILITY"
|
|||
), |(mut a, b)| { a.extend(b); a })
|
||||
}));
|
||||
|
||||
rule!(pub continue_req : Response => delimited(pair(byte(b'+'), SP),
|
||||
// TODO: handle base64 case?
|
||||
map(resp_text, Response::Continue),
|
||||
CRLF));
|
||||
|
||||
rule!(pub envelope : Envelope => map(byte(b'('), |_| Envelope {}));
|
||||
|
||||
rule!(pub flag : Flag => alt((
|
||||
map(tag_no_case("\\Answered"), |_| Flag::Answered),
|
||||
map(tag_no_case("\\Flagged"), |_| Flag::Flagged),
|
||||
map(tag_no_case("\\Deleted"), |_| Flag::Deleted),
|
||||
map(tag_no_case("\\Seen"), |_| Flag::Seen),
|
||||
map(tag_no_case("\\Draft"), |_| Flag::Draft),
|
||||
map(flag_extension, Flag::Extension),
|
||||
)));
|
||||
|
||||
rule!(pub flag_extension : Atom => preceded(byte(b'\\'), atom));
|
||||
|
||||
rule!(pub flag_fetch : Flag => alt((flag, map(tag_no_case("\\Recent"), |_| Flag::Recent))));
|
||||
|
||||
rule!(pub flag_list : Vec<Flag> => delimited(byte(b'('), sep_list!(?flag), byte(b')')));
|
||||
|
||||
pub(crate) fn is_list_wildcards(c: u8) -> bool { c == b'%' || c == b'*' }
|
||||
rule!(pub list_wildcards : u8 => satisfy(is_list_wildcards));
|
||||
|
||||
|
@ -63,32 +86,96 @@ rule!(pub list_wildcards : u8 => satisfy(is_list_wildcards));
|
|||
// 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>> {
|
||||
pub fn literal(i: &[u8]) -> IResult<&[u8], CowU8> {
|
||||
let mut length_of = terminated(delimited(char('{'), number, char('}')), CRLF);
|
||||
let (i, length) = length_of(i)?;
|
||||
println!("length is: {:?}", (i, length));
|
||||
map(take(length), |s: &[u8]| s.to_vec())(i)
|
||||
map(take(length), Cow::from)(i)
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_literal() {
|
||||
assert_eq!(
|
||||
literal(b"{13}\r\nHello, world!").unwrap().1,
|
||||
literal(b"{13}\r\nHello, world!").unwrap().1.as_ref(),
|
||||
b"Hello, world!"
|
||||
);
|
||||
}
|
||||
|
||||
rule!(pub mailbox : Mailbox => alt((
|
||||
map(tag_no_case("INBOX"), |_| Mailbox::Inbox),
|
||||
map(astring, Mailbox::Name),
|
||||
)));
|
||||
|
||||
rule!(pub mailbox_data : MailboxData => alt((
|
||||
map(preceded(pair(tag_no_case("FLAGS"), SP), flag_list), MailboxData::Flags),
|
||||
map(preceded(pair(tag_no_case("LIST"), SP), mailbox_list), MailboxData::List),
|
||||
)));
|
||||
|
||||
rule!(pub mailbox_list : MailboxList => map(separated_pair(
|
||||
delimited(byte(b'('), map(opt(mbx_list_flags), |opt| opt.unwrap_or_else(Vec::new)), byte(b')')),
|
||||
SP, separated_pair(
|
||||
alt((
|
||||
map(delimited(DQUOTE, QUOTED_CHAR, DQUOTE), Some),
|
||||
map(nil, |_| None),
|
||||
)),
|
||||
SP, mailbox,
|
||||
),
|
||||
), |(flags, (delimiter, mailbox))| MailboxList { flags, delimiter, mailbox }));
|
||||
|
||||
rule!(pub mbx_list_flags : Vec<MailboxListFlag> => alt((
|
||||
map(tuple((
|
||||
many0(terminated(mbx_list_oflag, SP)),
|
||||
mbx_list_sflag,
|
||||
many0(preceded(SP, mbx_list_oflag)),
|
||||
)), |(mut a, b, c)| { a.push(b); a.extend(c); a }),
|
||||
sep_list!(mbx_list_oflag),
|
||||
)));
|
||||
|
||||
rule!(pub mbx_list_oflag : MailboxListFlag => alt((
|
||||
map(tag_no_case("\\Noinferiors"), |_| MailboxListFlag::NoInferiors),
|
||||
map(flag_extension, MailboxListFlag::Extension),
|
||||
)));
|
||||
|
||||
rule!(pub mbx_list_sflag : MailboxListFlag => alt((
|
||||
map(tag_no_case("\\NoSelect"), |_| MailboxListFlag::NoSelect),
|
||||
map(tag_no_case("\\Marked"), |_| MailboxListFlag::Marked),
|
||||
map(tag_no_case("\\Unmarked"), |_| MailboxListFlag::Unmarked),
|
||||
)));
|
||||
|
||||
rule!(pub message_data : Response => alt((
|
||||
map(terminated(nz_number, pair(SP, tag_no_case("EXPUNGE"))), Response::Expunge),
|
||||
map(separated_pair(nz_number, SP, preceded(pair(tag_no_case("FETCH"), SP), msg_att)),
|
||||
|(n, attrs)| Response::Fetch(n, attrs)),
|
||||
)));
|
||||
|
||||
rule!(pub msg_att : Vec<MessageAttribute> => delimited(byte(b'('),
|
||||
sep_list!(alt((msg_att_dynamic, msg_att_static))),
|
||||
byte(b')')));
|
||||
|
||||
rule!(pub msg_att_dynamic : MessageAttribute => alt((
|
||||
map(preceded(pair(tag_no_case("FLAGS"), SP),
|
||||
delimited(byte(b'('), sep_list!(?flag_fetch), byte(b')'))), MessageAttribute::Flags),
|
||||
never,
|
||||
)));
|
||||
|
||||
rule!(pub msg_att_static : MessageAttribute => alt((
|
||||
map(preceded(pair(tag_no_case("ENVELOPE"), SP), envelope), MessageAttribute::Envelope),
|
||||
map(preceded(pair(tag_no_case("ENVELOPE"), SP), envelope), MessageAttribute::Envelope),
|
||||
)));
|
||||
|
||||
rule!(pub nil : &[u8] => tag_no_case("NIL"));
|
||||
|
||||
rule!(pub nstring : Option<Vec<u8>> => alt((map(string, Some), map(nil, |_| None))));
|
||||
rule!(pub nstring : Option<CowU8> => alt((map(string, Some), map(nil, |_| None))));
|
||||
|
||||
pub(crate) fn number(i: &[u8]) -> IResult<&[u8], u32> {
|
||||
map_res(map_res(many1(DIGIT), String::from_utf8), |s| {
|
||||
map_res(map_res(take_while1(is_digit), std::str::from_utf8), |s| {
|
||||
s.parse::<u32>()
|
||||
})(i)
|
||||
}
|
||||
|
||||
rule!(pub quoted : Vec<u8> => delimited(DQUOTE, many0(QUOTED_CHAR), DQUOTE));
|
||||
rule!(pub nz_number : u32 => verify(number, |n| *n != 0));
|
||||
|
||||
rule!(pub quoted : CowU8 => delimited(DQUOTE, map(take_while1(is_quoted_char), Cow::from), DQUOTE));
|
||||
|
||||
fn is_quoted_char(c: u8) -> bool { is_char(c) && !is_quoted_specials(c) }
|
||||
rule!(pub QUOTED_CHAR : u8 => alt((satisfy(is_quoted_char), preceded(byte(b'\\'), quoted_specials))));
|
||||
|
@ -96,17 +183,60 @@ rule!(pub QUOTED_CHAR : u8 => alt((satisfy(is_quoted_char), preceded(byte(b'\\')
|
|||
pub(crate) fn is_quoted_specials(c: u8) -> bool { is_dquote(c) || c == b'\\' }
|
||||
rule!(pub quoted_specials : u8 => satisfy(is_quoted_specials));
|
||||
|
||||
// TODO: technically, this is supposed to be
|
||||
rule!(pub response : Response => alt((continue_req, response_data, response_done)));
|
||||
|
||||
rule!(pub response_data : Response => delimited(pair(byte(b'*'), SP), alt((
|
||||
map(resp_cond_state, Response::Condition),
|
||||
map(resp_cond_bye, Response::Condition),
|
||||
map(mailbox_data, Response::MailboxData),
|
||||
message_data,
|
||||
map(capability_data, Response::Capabilities),
|
||||
)), CRLF));
|
||||
|
||||
rule!(pub response_done : Response => alt((response_tagged, response_fatal)));
|
||||
|
||||
rule!(pub response_fatal : Response => delimited(pair(byte(b'*'), SP),
|
||||
map(resp_cond_bye, Response::Fatal), CRLF));
|
||||
|
||||
rule!(pub response_tagged : Response => map(terminated(separated_pair(tag, SP, resp_cond_state), CRLF),
|
||||
|(tag, cond)| Response::Tagged(tag, cond)));
|
||||
|
||||
rule!(pub resp_cond_bye : Condition => preceded(pair(tag_no_case("BYE"), SP),
|
||||
map(resp_text, |ResponseText { code, info }| Condition { status: Status::Bye, code, info })));
|
||||
|
||||
rule!(pub resp_cond_state : Condition => map(
|
||||
separated_pair(
|
||||
alt((
|
||||
map(tag_no_case("OK"), |_| Status::Ok),
|
||||
map(tag_no_case("NO"), |_| Status::No),
|
||||
map(tag_no_case("BAD"), |_| Status::Bad),
|
||||
)),
|
||||
SP,
|
||||
resp_text,
|
||||
),
|
||||
|(status, ResponseText { code, info })| Condition { status, code, info }
|
||||
));
|
||||
|
||||
pub(crate) fn is_resp_specials(c: u8) -> bool { c == b']' }
|
||||
rule!(pub resp_specials : u8 => satisfy(is_resp_specials));
|
||||
|
||||
rule!(pub resp_text : ResponseText => map(pair(
|
||||
opt(terminated(delimited(byte(b'['), resp_text_code, byte(b']')), SP)),
|
||||
text,
|
||||
), |(code, info)| ResponseText { code, info }));
|
||||
|
||||
rule!(pub resp_text_code : ResponseCode => alt((
|
||||
map(tag_no_case("ALERT"), |_| ResponseCode::Alert),
|
||||
map(capability_data, ResponseCode::Capabilities),
|
||||
)));
|
||||
|
||||
rule!(pub string : Vec<u8> => alt((quoted, literal)));
|
||||
rule!(pub string : CowU8 => alt((quoted, literal)));
|
||||
|
||||
rule!(pub text : &[u8] => take_while1(is_text_char));
|
||||
pub(crate) fn is_tag_char(c: u8) -> bool { is_astring_char(c) && c != b'+' }
|
||||
rule!(pub tag : Tag => map(take_while1(is_tag_char), |s: &[u8]| Tag(s.into())));
|
||||
|
||||
rule!(pub text : CowU8 => map(take_while1(is_text_char), Cow::from));
|
||||
|
||||
pub(crate) fn is_text_char(c: u8) -> bool { is_char(c) && !is_cr(c) && !is_lf(c) }
|
||||
rule!(pub TEXT_CHAR : u8 => satisfy(is_text_char));
|
||||
|
|
Loading…
Reference in a new issue