Skip to content

Commit

Permalink
Implement Condstore Status Codes
Browse files Browse the repository at this point in the history
  • Loading branch information
superboum committed Feb 7, 2024
1 parent 7c56f6f commit 2d59de0
Show file tree
Hide file tree
Showing 4 changed files with 96 additions and 1 deletion.
25 changes: 25 additions & 0 deletions imap-codec/src/codec/encode.rs
Original file line number Diff line number Diff line change
Expand Up @@ -79,6 +79,9 @@ use imap_types::{
};
use utils::{join_serializable, List1AttributeValueOrNil, List1OrNil};

#[cfg(feature = "ext_condstore_qresync")]
use std::num::NonZeroU64;

use crate::{AuthenticateDataCodec, CommandCodec, GreetingCodec, IdleDoneCodec, ResponseCodec};

/// Encoder.
Expand Down Expand Up @@ -1036,6 +1039,13 @@ impl EncodeIntoContext for NonZeroU32 {
}
}

#[cfg(feature = "ext_condstore_qresync")]
impl EncodeIntoContext for NonZeroU64 {
fn encode_ctx(&self, ctx: &mut EncodeContext) -> std::io::Result<()> {
write!(ctx, "{self}")
}
}

impl<'a> EncodeIntoContext for Capability<'a> {
fn encode_ctx(&self, ctx: &mut EncodeContext) -> std::io::Result<()> {
write!(ctx, "{}", self)
Expand Down Expand Up @@ -1171,6 +1181,21 @@ impl<'a> EncodeIntoContext for Code<'a> {
ctx.write_all(b"REFERRAL ")?;
ctx.write_all(url.as_bytes())
}

// RFC 4551
#[cfg(feature = "ext_condstore_qresync")]
Code::HighestModSeq(modseq) => {
ctx.write_all(b"HIGHESTMODSEQ ")?;
modseq.encode_ctx(ctx)
},
#[cfg(feature = "ext_condstore_qresync")]
Code::NoModSeq => ctx.write_all(b"NOMODSEQ"),
#[cfg(feature = "ext_condstore_qresync")]
Code::Modified(seq_or_uid_list) => {
ctx.write_all(b"MODIFIED ")?;
join_serializable(seq_or_uid_list.as_ref(), b",", ctx)
},

Code::CompressionActive => ctx.write_all(b"COMPRESSIONACTIVE"),
Code::OverQuota => ctx.write_all(b"OVERQUOTA"),
Code::TooBig => ctx.write_all(b"TOOBIG"),
Expand Down
12 changes: 12 additions & 0 deletions imap-codec/src/core.rs
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,9 @@ use nom::{
sequence::{delimited, terminated, tuple},
};

#[cfg(feature = "ext_condstore_qresync")]
use std::num::NonZeroU64;

use crate::decode::{IMAPErrorKind, IMAPParseError, IMAPResult};

// ----- number -----
Expand Down Expand Up @@ -65,6 +68,15 @@ pub(crate) fn nz_number(input: &[u8]) -> IMAPResult<&[u8], NonZeroU32> {
map_res(number, NonZeroU32::try_from)(input)
}

/// mod-sequence-value = 1*DIGIT
/// ;; Positive unsigned 64-bit integer
/// ;; (mod-sequence)
/// ;; (1 <= n < 18,446,744,073,709,551,615)
#[cfg(feature = "ext_condstore_qresync")]
pub(crate) fn mod_sequence_value(input: &[u8]) -> IMAPResult<&[u8], NonZeroU64> {
map_res(number64, NonZeroU64::try_from)(input)
}

// ----- string -----

/// `string = quoted / literal`
Expand Down
28 changes: 27 additions & 1 deletion imap-codec/src/response.rs
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,9 @@ use crate::{
mailbox::mailbox_data,
};

#[cfg(feature = "ext_condstore_qresync")]
use crate::core::mod_sequence_value;

// ----- greeting -----

/// `greeting = "*" SP (resp-cond-auth / resp-cond-bye) CRLF`
Expand Down Expand Up @@ -124,7 +127,10 @@ pub(crate) fn resp_text(input: &[u8]) -> IMAPResult<&[u8], (Option<Code>, Text)>
/// "UIDNEXT" SP nz-number /
/// "UIDVALIDITY" SP nz-number /
/// "UNSEEN" SP nz-number /
/// "COMPRESSIONACTIVE" ; RFC 4978
/// "COMPRESSIONACTIVE" ; RFC 4978 /
/// "HIGHESTMODSEQ" SP mod-sequence-value ; RFC4551 /
/// "NOMODSEQ" ; RFC4551 /
/// "MODIFIED" SP set ; RFC4551 /
/// atom [SP 1*<any TEXT-CHAR except "]">]`
///
/// Note: See errata id: 261
Expand Down Expand Up @@ -177,6 +183,18 @@ pub(crate) fn resp_text_code(input: &[u8]) -> IMAPResult<&[u8], Code> {
value(Code::CompressionActive, tag_no_case(b"COMPRESSIONACTIVE")),
value(Code::OverQuota, tag_no_case(b"OVERQUOTA")),
value(Code::TooBig, tag_no_case(b"TOOBIG")),
#[cfg(feature = "ext_condstore_qresync")]
map(
tuple((tag_no_case(b"HIGHESTMODSEQ"), sp, mod_sequence_value)),
|(_, _, modseq)| Code::HighestModSeq(modseq)
),
#[cfg(feature = "ext_condstore_qresync")]
value(Code::NoModSeq, tag_no_case(b"NOMODSEQ")),
#[cfg(feature = "ext_condstore_qresync")]
map(
tuple((tag_no_case(b"MODIFIED"), sp, separated_list1(tag(","), nz_number))),
|(_, _, set)| Code::Modified(Vec1::unvalidated(set)),
),
))(input)
}

Expand Down Expand Up @@ -701,4 +719,12 @@ mod tests {
assert!(resp_text(b"[IMAP4rev1] \r\n").is_ok());
}
}

#[cfg(feature = "ext_condstore_qresync")]
#[test]
fn test_condstore_qresync_codes() {
assert!(resp_text(b"[MODIFIED 7,9] Conditional STORE failed\r\n").is_ok());
assert!(resp_text(b"[NOMODSEQ] Sorry, this mailbox format doesn't support modsequences\r\n").is_ok());
assert!(resp_text(b"[HIGHESTMODSEQ 715194045007] Highest\r\n").is_ok());
}
}
32 changes: 32 additions & 0 deletions imap-types/src/response.rs
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,9 @@ use std::{
num::{NonZeroU32, TryFromIntError},
};

#[cfg(feature = "ext_condstore_qresync")]
use std::num::NonZeroU64;

#[cfg(feature = "arbitrary")]
use arbitrary::Arbitrary;
use base64::{engine::general_purpose::STANDARD as _base64, Engine};
Expand Down Expand Up @@ -832,6 +835,35 @@ pub enum Code<'a> {
/// Server got a non-synchronizing literal larger than 4096 bytes.
TooBig,


/// IMAP4 Extension for Conditional STORE Operation (RFC 4551)
/// A server supporting the persistent storage of mod-sequences for the mailbox
/// MUST send the OK untagged response including HIGHESTMODSEQ response
/// code with every successful SELECT or EXAMINE command
#[cfg(feature = "ext_condstore_qresync")]
#[cfg_attr(docsrs, doc(cfg("ext_condstore_qresync")))]
HighestModSeq(NonZeroU64),

/// IMAP4 Extension for Conditional STORE Operation (RFC 4551)
/// When the server finished performing the operation on all the
/// messages in the message set, it checks for a non-empty list of
/// messages that failed the UNCHANGESINCE test. If this list is
/// non-empty, the server MUST return in the tagged response a
/// MODIFIED response code. The MODIFIED response code includes the
/// message set (for STORE) or set of UIDs (for UID STORE) of all
/// messages that failed the UNCHANGESINCE test.
#[cfg(feature = "ext_condstore_qresync")]
#[cfg_attr(docsrs, doc(cfg("ext_condstore_qresync")))]
Modified(Vec1<NonZeroU32>),

/// IMAP4 Extension for Conditional STORE Operation (RFC 4551)
/// A server that doesn't support the persistent storage of mod-sequences
/// for the mailbox MUST send the OK untagged response including NOMODSEQ
/// response code with every successful SELECT or EXAMINE command.
#[cfg(feature = "ext_condstore_qresync")]
#[cfg_attr(docsrs, doc(cfg("ext_condstore_qresync")))]
NoModSeq,

/// Additional response codes defined by particular client or server
/// implementations SHOULD be prefixed with an "X" until they are
/// added to a revision of this protocol. Client implementations
Expand Down

0 comments on commit 2d59de0

Please sign in to comment.