diff --git a/imap-codec/src/codec/encode.rs b/imap-codec/src/codec/encode.rs index 1e045e82..c4f15ffc 100644 --- a/imap-codec/src/codec/encode.rs +++ b/imap-codec/src/codec/encode.rs @@ -45,7 +45,7 @@ //! C: Pa²²W0rD //! ``` -use std::{borrow::Borrow, io::Write, num::NonZeroU32}; +use std::{borrow::Borrow, io::Write, num::{NonZeroU32, NonZeroU64}}; use base64::{engine::general_purpose::STANDARD as base64, Engine}; use chrono::{DateTime as ChronoDateTime, FixedOffset}; @@ -980,6 +980,7 @@ impl<'a> EncodeIntoContext for MessageDataItemName<'a> { Self::Rfc822Size => ctx.write_all(b"RFC822.SIZE"), Self::Rfc822Text => ctx.write_all(b"RFC822.TEXT"), Self::Uid => ctx.write_all(b"UID"), + Self::ModSeq => ctx.write_all(b"MODSEQ"), } } } @@ -1044,6 +1045,12 @@ impl EncodeIntoContext for NonZeroU32 { } } +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) @@ -1295,6 +1302,7 @@ impl<'a> EncodeIntoContext for Data<'a> { root.encode_ctx(ctx)?; } } + #[cfg(feature = "ext_id")] Data::Id { parameters } => { ctx.write_all(b"* ID ")?; @@ -1448,6 +1456,11 @@ impl<'a> EncodeIntoContext for MessageDataItem<'a> { nstring.encode_ctx(ctx) } Self::Uid(uid) => write!(ctx, "UID {uid}"), + Self::ModSeq(modseq) => { + ctx.write_all(b"MODSEQ (")?; + modseq.encode_ctx(ctx)?; + ctx.write_all(b")") + } } } } diff --git a/imap-codec/src/core.rs b/imap-codec/src/core.rs index 90a810b7..664fae41 100644 --- a/imap-codec/src/core.rs +++ b/imap-codec/src/core.rs @@ -1,4 +1,4 @@ -use std::{borrow::Cow, num::NonZeroU32, str::from_utf8}; +use std::{borrow::Cow, num::{NonZeroU32, NonZeroU64}, str::from_utf8}; #[cfg(not(feature = "quirk_crlf_relaxed"))] use abnf_core::streaming::crlf; @@ -65,6 +65,10 @@ pub(crate) fn nz_number(input: &[u8]) -> IMAPResult<&[u8], NonZeroU32> { map_res(number, NonZeroU32::try_from)(input) } +pub(crate) fn nz_number64(input: &[u8]) -> IMAPResult<&[u8], NonZeroU64> { + map_res(number64, NonZeroU64::try_from)(input) +} + // ----- string ----- /// `string = quoted / literal` diff --git a/imap-codec/src/fetch.rs b/imap-codec/src/fetch.rs index ec536bce..f51c2cd9 100644 --- a/imap-codec/src/fetch.rs +++ b/imap-codec/src/fetch.rs @@ -1,4 +1,4 @@ -use std::num::NonZeroU32; +use std::num::{NonZeroU32}; use abnf_core::streaming::sp; use imap_types::{ @@ -15,7 +15,7 @@ use nom::{ use crate::{ body::body, - core::{astring, nstring, number, nz_number}, + core::{astring, nstring, number, nz_number64, nz_number}, datetime::date_time, decode::IMAPResult, envelope::envelope, @@ -83,6 +83,7 @@ pub(crate) fn fetch_att(input: &[u8]) -> IMAPResult<&[u8], MessageDataItemName> value(MessageDataItemName::Rfc822Size, tag_no_case(b"RFC822.SIZE")), value(MessageDataItemName::Rfc822Text, tag_no_case(b"RFC822.TEXT")), value(MessageDataItemName::Rfc822, tag_no_case(b"RFC822")), + value(MessageDataItemName::ModSeq, tag_no_case(b"MODSEQ")), ))(input) } @@ -175,6 +176,9 @@ pub(crate) fn msg_att_static(input: &[u8]) -> IMAPResult<&[u8], MessageDataItem> map(tuple((tag_no_case(b"UID"), sp, uniqueid)), |(_, _, uid)| { MessageDataItem::Uid(uid) }), + map(tuple((tag_no_case(b"MODSEQ "), delimited(tag("("), nz_number64, tag(")")))), |(_, modseq)| { + MessageDataItem::ModSeq(modseq) + }), ))(input) } diff --git a/imap-types/src/fetch.rs b/imap-types/src/fetch.rs index b20f6486..40f849c0 100644 --- a/imap-types/src/fetch.rs +++ b/imap-types/src/fetch.rs @@ -2,7 +2,7 @@ use std::{ fmt::{Display, Formatter}, - num::NonZeroU32, + num::{NonZeroU32, NonZeroU64}, }; #[cfg(feature = "arbitrary")] @@ -229,6 +229,9 @@ pub enum MessageDataItemName<'a> { /// UID /// ``` Uid, + + /// The ModSeq of CONDSTORE + ModSeq, } /// Message data item. @@ -356,6 +359,9 @@ pub enum MessageDataItem<'a> { /// UID /// ``` Uid(NonZeroU32), + + /// The ModSeq value described in CONDSTORE + ModSeq(NonZeroU64), } /// A part specifier is either a part number or one of the following: