Skip to content

Commit

Permalink
Allow MODIFIERS in SELECT/EXAMINE/STORE
Browse files Browse the repository at this point in the history
  • Loading branch information
superboum committed Jan 10, 2024
1 parent ceed169 commit 88bdca2
Show file tree
Hide file tree
Showing 6 changed files with 114 additions and 17 deletions.
2 changes: 2 additions & 0 deletions imap-codec/src/codec.rs
Original file line number Diff line number Diff line change
Expand Up @@ -112,6 +112,7 @@ mod tests {
"a",
CommandBody::Select {
mailbox: Mailbox::Inbox,
parameters: None,
},
)
.unwrap(),
Expand All @@ -123,6 +124,7 @@ mod tests {
"a",
CommandBody::Select {
mailbox: Mailbox::Inbox,
parameters: None,
},
)
.unwrap(),
Expand Down
2 changes: 2 additions & 0 deletions imap-codec/src/codec/decode.rs
Original file line number Diff line number Diff line change
Expand Up @@ -434,6 +434,7 @@ mod tests {
"a",
CommandBody::Select {
mailbox: Mailbox::Inbox,
parameters: None,
},
)
.unwrap(),
Expand All @@ -447,6 +448,7 @@ mod tests {
"a",
CommandBody::Select {
mailbox: Mailbox::Inbox,
parameters: None,
},
)
.unwrap(),
Expand Down
55 changes: 50 additions & 5 deletions imap-codec/src/codec/encode.rs
Original file line number Diff line number Diff line change
Expand Up @@ -55,7 +55,7 @@ use imap_types::{
BasicFields, Body, BodyExtension, BodyStructure, Disposition, Language, Location,
MultiPartExtensionData, SinglePartExtensionData, SpecificFields,
},
command::{Command, CommandBody},
command::{Command, CommandBody, StoreModifier},
core::{
AString, Atom, AtomExt, Charset, IString, Literal, LiteralMode, NString, Quoted,
QuotedChar, Tag, Text,
Expand Down Expand Up @@ -325,16 +325,34 @@ impl<'a> EncodeIntoContext for CommandBody<'a> {
ctx.write_all(b" ")?;
password.declassify().encode_ctx(ctx)
}
CommandBody::Select { mailbox } => {
CommandBody::Select { mailbox, parameters } => {
ctx.write_all(b"SELECT")?;
ctx.write_all(b" ")?;
mailbox.encode_ctx(ctx)
mailbox.encode_ctx(ctx)?;
if let Some(p) = parameters {
ctx.write_all(b" (")?;
for atom in p.as_ref().iter() {
atom.encode_ctx(ctx)?;
}
ctx.write_all(b")")?;
}

Ok(())
}
CommandBody::Unselect => ctx.write_all(b"UNSELECT"),
CommandBody::Examine { mailbox } => {
CommandBody::Examine { mailbox, parameters } => {
ctx.write_all(b"EXAMINE")?;
ctx.write_all(b" ")?;
mailbox.encode_ctx(ctx)
mailbox.encode_ctx(ctx)?;
if let Some(p) = parameters {
ctx.write_all(b" (")?;
for atom in p.as_ref().iter() {
atom.encode_ctx(ctx)?;
}
ctx.write_all(b")")?;
}

Ok(())
}
CommandBody::Create { mailbox } => {
ctx.write_all(b"CREATE")?;
Expand Down Expand Up @@ -463,6 +481,7 @@ impl<'a> EncodeIntoContext for CommandBody<'a> {
kind,
response,
flags,
modifiers,
uid,
} => {
if *uid {
Expand All @@ -474,6 +493,32 @@ impl<'a> EncodeIntoContext for CommandBody<'a> {
sequence_set.encode_ctx(ctx)?;
ctx.write_all(b" ")?;

if !modifiers.is_empty() {
ctx.write_all(b" (")?;
let mut mod_iter = modifiers.iter().peekable();
while let Some((k, x)) = mod_iter.next() {
k.encode_ctx(ctx)?;
ctx.write_all(b" ")?;
match x {
StoreModifier::Value(num) => {
num.encode_ctx(ctx)?;
},
StoreModifier::SequenceSet(seq) => {
seq.encode_ctx(ctx)?;
},
StoreModifier::Arbitrary(val) => {
ctx.write_all(b"(")?;
val.encode_ctx(ctx)?;
ctx.write_all(b")")?;
}
}
if mod_iter.peek().is_some() {
ctx.write_all(b" ")?;
}
}
ctx.write_all(b") ")?;
}

match kind {
StoreType::Add => ctx.write_all(b"+")?,
StoreType::Remove => ctx.write_all(b"-")?,
Expand Down
45 changes: 34 additions & 11 deletions imap-codec/src/command.rs
Original file line number Diff line number Diff line change
Expand Up @@ -7,8 +7,8 @@ use abnf_core::streaming::crlf_relaxed as crlf;
use abnf_core::streaming::sp;
use imap_types::{
auth::AuthMechanism,
command::{Command, CommandBody},
core::AString,
command::{Command, CommandBody, StoreModifier},
core::{AString, NonEmptyVec},
fetch::{Macro, MacroOrMessageDataItemNames},
flag::{Flag, StoreResponse, StoreType},
secret::Secret,
Expand All @@ -25,7 +25,7 @@ use nom::{
use crate::extensions::id::id;
use crate::{
auth::auth_type,
core::{astring, base64, literal, tag_imap},
core::{astring, atom, base64, literal, number, tag_imap},
datetime::date_time,
decode::{IMAPErrorKind, IMAPResult},
extensions::{
Expand Down Expand Up @@ -190,11 +190,17 @@ pub(crate) fn delete(input: &[u8]) -> IMAPResult<&[u8], CommandBody> {

/// `examine = "EXAMINE" SP mailbox`
pub(crate) fn examine(input: &[u8]) -> IMAPResult<&[u8], CommandBody> {
let mut parser = tuple((tag_no_case(b"EXAMINE"), sp, mailbox));
let mut parser = tuple((
tag_no_case(b"EXAMINE"),
sp,
mailbox,
opt(tuple((sp, delimited(tag(b"("), separated_list1(sp, atom), tag(b")"))))),
));

let (remaining, (_, _, mailbox)) = parser(input)?;
let (remaining, (_, _, mailbox, maybe_params)) = parser(input)?;
let final_params = maybe_params.map(|(_, elems)| NonEmptyVec::unvalidated(elems));

Ok((remaining, CommandBody::Examine { mailbox }))
Ok((remaining, CommandBody::Examine { mailbox, parameters: final_params }))
}

/// `list = "LIST" SP mailbox SP list-mailbox`
Expand Down Expand Up @@ -246,11 +252,17 @@ pub(crate) fn rename(input: &[u8]) -> IMAPResult<&[u8], CommandBody> {

/// `select = "SELECT" SP mailbox`
pub(crate) fn select(input: &[u8]) -> IMAPResult<&[u8], CommandBody> {
let mut parser = tuple((tag_no_case(b"SELECT"), sp, mailbox));
let mut parser = tuple((
tag_no_case(b"SELECT"),
sp,
mailbox,
opt(tuple((sp, delimited(tag(b"("), separated_list1(sp, atom), tag(b")"))))),
));

let (remaining, (_, _, mailbox)) = parser(input)?;
let (remaining, (_, _, mailbox, maybe_params)) = parser(input)?;
let final_params = maybe_params.map(|(_, elems)| NonEmptyVec::unvalidated(elems));

Ok((remaining, CommandBody::Select { mailbox }))
Ok((remaining, CommandBody::Select { mailbox, parameters: final_params }))
}

/// `status = "STATUS" SP mailbox SP "(" status-att *(SP status-att) ")"`
Expand Down Expand Up @@ -467,9 +479,19 @@ pub(crate) fn fetch(input: &[u8]) -> IMAPResult<&[u8], CommandBody> {

/// `store = "STORE" SP sequence-set SP store-att-flags`
pub(crate) fn store(input: &[u8]) -> IMAPResult<&[u8], CommandBody> {
let mut parser = tuple((tag_no_case(b"STORE"), sp, sequence_set, sp, store_att_flags));
let modifier_val_parser = alt((
map(number, |num| StoreModifier::Value(num)),
map(sequence_set, |sq| StoreModifier::SequenceSet(sq)),
map(astring, |astr| StoreModifier::Arbitrary(astr)),
));
let modifiers_parser = opt(delimited(
tag(b"("),
separated_list1(sp, map(tuple((atom, sp, modifier_val_parser)), |(k, _, v)| (k, v))),
tag(b") ")));
let mut parser = tuple((tag_no_case(b"STORE"), sp, sequence_set, sp, modifiers_parser, store_att_flags));

let (remaining, (_, _, sequence_set, _, (kind, response, flags))) = parser(input)?;
let (remaining, (_, _, sequence_set, _, maybe_modifiers, (kind, response, flags))) = parser(input)?;
let modifiers = maybe_modifiers.unwrap_or(vec![]);

Ok((
remaining,
Expand All @@ -478,6 +500,7 @@ pub(crate) fn store(input: &[u8]) -> IMAPResult<&[u8], CommandBody> {
kind,
response,
flags,
modifiers,
uid: false,
},
))
Expand Down
1 change: 1 addition & 0 deletions imap-codec/tests/trace.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1150,6 +1150,7 @@ Content-Type: TEXT/PLAIN; CHARSET=US-ASCII\r
StoreType::Add,
StoreResponse::Answer,
vec![Flag::Deleted],
vec![],
false,
)
.unwrap(),
Expand Down
26 changes: 25 additions & 1 deletion imap-types/src/command.rs
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@ use crate::core::{IString, NString};
use crate::{
auth::AuthMechanism,
command::error::{AppendError, CopyError, ListError, LoginError, RenameError},
core::{AString, Charset, Literal, NonEmptyVec, Tag},
core::{Atom, AString, Charset, Literal, NonEmptyVec, Tag},
datetime::DateTime,
extensions::{compress::CompressionAlgorithm, enable::CapabilityEnable, quota::QuotaSet},
fetch::MacroOrMessageDataItemNames,
Expand Down Expand Up @@ -386,6 +386,8 @@ pub enum CommandBody<'a> {
Select {
/// Mailbox.
mailbox: Mailbox<'a>,
/// Optional parameters according to RFC466 section 2.1
parameters: Option<NonEmptyVec<Atom<'a>>>,
},

/// Unselect a mailbox.
Expand Down Expand Up @@ -415,6 +417,8 @@ pub enum CommandBody<'a> {
Examine {
/// Mailbox.
mailbox: Mailbox<'a>,
/// Optional parameters according to RFC466 section 2.1
parameters: Option<NonEmptyVec<Atom<'a>>>,
},

/// ### 6.3.3. CREATE Command
Expand Down Expand Up @@ -1110,6 +1114,8 @@ pub enum CommandBody<'a> {
response: StoreResponse,
/// Flags.
flags: Vec<Flag<'a>>, // FIXME(misuse): must not accept "\*" or "\Recent"
/// Modifiers.
modifiers: Vec<(Atom<'a>, StoreModifier<'a>)>,
/// Use UID variant.
uid: bool,
},
Expand Down Expand Up @@ -1427,6 +1433,7 @@ impl<'a> CommandBody<'a> {
{
Ok(CommandBody::Select {
mailbox: mailbox.try_into()?,
parameters: None,
})
}

Expand All @@ -1437,6 +1444,7 @@ impl<'a> CommandBody<'a> {
{
Ok(CommandBody::Examine {
mailbox: mailbox.try_into()?,
parameters: None,
})
}

Expand Down Expand Up @@ -1585,6 +1593,7 @@ impl<'a> CommandBody<'a> {
kind: StoreType,
response: StoreResponse,
flags: Vec<Flag<'a>>,
modifiers: Vec<(Atom<'a>, StoreModifier<'a>)>,
uid: bool,
) -> Result<Self, S::Error>
where
Expand All @@ -1597,6 +1606,7 @@ impl<'a> CommandBody<'a> {
kind,
response,
flags,
modifiers,
uid,
})
}
Expand Down Expand Up @@ -1659,6 +1669,15 @@ impl<'a> CommandBody<'a> {
}
}
}
#[cfg_attr(feature = "arbitrary", derive(Arbitrary))]
#[cfg_attr(feature = "bounded-static", derive(ToStatic))]
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
#[derive(Debug, Clone, PartialEq, Eq, Hash)]
pub enum StoreModifier<'a> {
Value(u32),
SequenceSet(SequenceSet),
Arbitrary(AString<'a>),
}

/// Error-related types.
pub mod error {
Expand Down Expand Up @@ -1870,6 +1889,7 @@ mod tests {
StoreType::Remove,
StoreResponse::Answer,
vec![Flag::Seen, Flag::Draft],
vec![],
false,
)
.unwrap(),
Expand All @@ -1878,6 +1898,7 @@ mod tests {
StoreType::Add,
StoreResponse::Answer,
vec![Flag::Keyword("TEST".try_into().unwrap())],
vec![],
true,
)
.unwrap(),
Expand Down Expand Up @@ -1917,13 +1938,15 @@ mod tests {
(
CommandBody::Select {
mailbox: Mailbox::Inbox,
parameters: None,
},
"SELECT",
),
(CommandBody::Unselect, "UNSELECT"),
(
CommandBody::Examine {
mailbox: Mailbox::Inbox,
parameters: None,
},
"EXAMINE",
),
Expand Down Expand Up @@ -2013,6 +2036,7 @@ mod tests {
flags: vec![],
response: StoreResponse::Silent,
kind: StoreType::Add,
modifiers: vec![],
uid: true,
},
"STORE",
Expand Down

0 comments on commit 88bdca2

Please sign in to comment.