Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

CONDSTORE #439

Closed
wants to merge 1 commit into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
24 changes: 24 additions & 0 deletions imap-codec/src/codec/encode.rs
Original file line number Diff line number Diff line change
Expand Up @@ -45,6 +45,8 @@
//! C: Pa²²W0rD
//! ```

#[cfg(feature = "ext_condstore_qresync")]
duesee marked this conversation as resolved.
Show resolved Hide resolved
use std::num::NonZeroU64;
use std::{borrow::Borrow, io::Write, num::NonZeroU32};

use base64::{engine::general_purpose::STANDARD as base64, Engine};
Expand Down Expand Up @@ -1036,6 +1038,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 +1180,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)
duesee marked this conversation as resolved.
Show resolved Hide resolved
}

Code::CompressionActive => ctx.write_all(b"COMPRESSIONACTIVE"),
Code::OverQuota => ctx.write_all(b"OVERQUOTA"),
Code::TooBig => ctx.write_all(b"TOOBIG"),
Expand Down
11 changes: 11 additions & 0 deletions imap-codec/src/core.rs
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
#[cfg(feature = "ext_condstore_qresync")]
use std::num::NonZeroU64;
use std::{borrow::Cow, num::NonZeroU32, str::from_utf8};

#[cfg(not(feature = "quirk_crlf_relaxed"))]
Expand Down Expand Up @@ -65,6 +67,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
34 changes: 33 additions & 1 deletion imap-codec/src/response.rs
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,8 @@ use nom::{
sequence::{delimited, preceded, terminated, tuple},
};

#[cfg(feature = "ext_condstore_qresync")]
use crate::core::mod_sequence_value;
#[cfg(feature = "ext_id")]
use crate::extensions::id::id_response;
use crate::{
Expand Down Expand Up @@ -124,7 +126,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 +182,22 @@ 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 +722,15 @@ 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());
}
}
30 changes: 30 additions & 0 deletions imap-types/src/response.rs
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
//! # 7. Server Responses

#[cfg(feature = "ext_condstore_qresync")]
use std::num::NonZeroU64;
use std::{
borrow::Cow,
fmt::{Debug, Display, Formatter},
Expand Down Expand Up @@ -832,6 +834,34 @@ 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
Loading