/* * SPDX-FileCopyrightText: 2020 Stalwart Labs LLC * * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-SEL */ use crate::{ResponseCode, StatusResponse}; use super::{ImapResponse, ObjectId, Sequence, list::ListItem}; #[derive(Debug, Clone, PartialEq, Eq)] pub struct Arguments { pub tag: String, pub mailbox_name: String, pub condstore: bool, pub qresync: Option, pub objectid: Option, } #[derive(Debug, Clone, PartialEq, Eq)] pub struct QResync { pub uid_validity: u32, pub modseq: u64, pub known_uids: Option, pub seq_match: Option<(Sequence, Sequence)>, } #[derive(Debug, Clone, PartialEq, Eq)] pub struct HighestModSeq(u64); #[derive(Debug, Clone)] pub struct Response { pub mailbox: ListItem, pub total_messages: usize, pub recent_messages: usize, pub unseen_seq: u32, pub uid_validity: u32, pub uid_next: u32, pub is_rev2: bool, pub is_utf8: bool, pub closed_previous: bool, pub highest_modseq: Option, pub objectid: Option, } #[derive(Debug, Clone)] pub struct Exists { pub total_messages: usize, } impl ImapResponse for Response { fn serialize(self) -> Vec { let mut buf = Vec::with_capacity(100); if self.closed_previous { buf = StatusResponse::ok("Closed previous mailbox") .with_code(ResponseCode::Closed) .serialize(buf); } buf.extend_from_slice(b"* "); buf.extend_from_slice(self.total_messages.to_string().as_bytes()); if !self.is_rev2 && self.recent_messages > 0 { buf.extend_from_slice( b" EXISTS\r\n* FLAGS (\\Answered \\Flagged \\Deleted \\Seen \\Draft \\Recent)\r\n", ); } else { buf.extend_from_slice( b" EXISTS\r\n* FLAGS (\\Answered \\Flagged \\Deleted \\Seen \\Draft)\r\n", ); } if self.is_rev2 { self.mailbox .serialize(&mut buf, self.is_rev2, self.is_utf8, false); } else { buf.extend_from_slice(b"* "); buf.extend_from_slice(self.recent_messages.to_string().as_bytes()); buf.extend_from_slice(b" RECENT\r\n"); if self.unseen_seq > 0 { buf.extend_from_slice(b"* OK [UNSEEN "); buf.extend_from_slice(self.unseen_seq.to_string().as_bytes()); buf.extend_from_slice(b"] Unseen messages\r\n"); } } buf.extend_from_slice( b"* OK [PERMANENTFLAGS (\\Deleted \\Seen \\Answered \\Flagged \\Draft \\*)] All allowed\r\n", ); buf.extend_from_slice(b"* OK [UIDVALIDITY "); buf.extend_from_slice(self.uid_validity.to_string().as_bytes()); buf.extend_from_slice(b"] UIDs valid\r\n* OK [UIDNEXT "); buf.extend_from_slice(self.uid_next.to_string().as_bytes()); buf.extend_from_slice(b"] Next predicted UID\r\n"); if let Some(highest_modseq) = self.highest_modseq { highest_modseq.serialize(&mut buf); } if let Some(objectid) = &self.objectid { buf.extend_from_slice(b"* OK ["); objectid.serialize(&mut buf); buf.extend_from_slice(b"] Object identifiers\r\n"); } buf } } impl HighestModSeq { pub fn new(modseq: u64) -> Self { Self(modseq) } pub fn serialize(&self, buf: &mut Vec) { buf.extend_from_slice(b"* OK [HIGHESTMODSEQ "); buf.extend_from_slice(self.0.to_string().as_bytes()); buf.extend_from_slice(b"] Highest Modseq\r\n"); } pub fn into_bytes(self) -> Vec { let mut buf = Vec::with_capacity(40); self.serialize(&mut buf); buf } } impl Exists { pub fn serialize(&self, buf: &mut Vec) { buf.extend_from_slice(b"* "); buf.extend_from_slice(self.total_messages.to_string().as_bytes()); buf.extend_from_slice(b" EXISTS\r\n"); } pub fn into_bytes(self) -> Vec { let mut buf = Vec::with_capacity(15); self.serialize(&mut buf); buf } } #[cfg(test)] mod tests { use crate::protocol::{ImapResponse, ObjectId, list::ListItem}; use types::id::Id; use super::HighestModSeq; #[test] fn serialize_select() { let objectid = ObjectId { mailbox_id: Some(Id::from(1u32)), account_id: Some(Id::from(2u32)), ..Default::default() }; let mut objectid_line = b"* OK [".to_vec(); objectid.serialize(&mut objectid_line); objectid_line.extend_from_slice(b"] Object identifiers\r\n"); let objectid_line = String::from_utf8(objectid_line).unwrap(); for (mut response, _tag, expected_v2, expected_v1) in [ ( super::Response { mailbox: ListItem::new("INBOX"), total_messages: 172, recent_messages: 5, unseen_seq: 3, uid_validity: 3857529045, uid_next: 4392, closed_previous: false, is_rev2: true, is_utf8: true, highest_modseq: HighestModSeq::new(100).into(), objectid: Some(objectid.clone()), }, "A142", format!( concat!( "* 172 EXISTS\r\n", "* FLAGS (\\Answered \\Flagged \\Deleted \\Seen \\Draft)\r\n", "* LIST () \"/\" \"INBOX\"\r\n", "* OK [PERMANENTFLAGS (\\Deleted \\Seen \\Answered \\Flagged \\Draft \\*)] All allowed\r\n", "* OK [UIDVALIDITY 3857529045] UIDs valid\r\n", "* OK [UIDNEXT 4392] Next predicted UID\r\n", "* OK [HIGHESTMODSEQ 100] Highest Modseq\r\n", "{}" ), objectid_line ), format!( concat!( "* 172 EXISTS\r\n", "* FLAGS (\\Answered \\Flagged \\Deleted \\Seen \\Draft \\Recent)\r\n", "* 5 RECENT\r\n", "* OK [UNSEEN 3] Unseen messages\r\n", "* OK [PERMANENTFLAGS (\\Deleted \\Seen \\Answered \\Flagged \\Draft \\*)] All allowed\r\n", "* OK [UIDVALIDITY 3857529045] UIDs valid\r\n", "* OK [UIDNEXT 4392] Next predicted UID\r\n", "* OK [HIGHESTMODSEQ 100] Highest Modseq\r\n", "{}" ), objectid_line ), ), ( super::Response { mailbox: ListItem::new("~peter/mail/台北/日本語"), total_messages: 172, recent_messages: 5, unseen_seq: 3, uid_validity: 3857529045, uid_next: 4392, closed_previous: true, is_rev2: true, is_utf8: true, highest_modseq: None, objectid: Some(objectid.clone()), }, "A142", format!( concat!( "* OK [CLOSED] Closed previous mailbox\r\n", "* 172 EXISTS\r\n", "* FLAGS (\\Answered \\Flagged \\Deleted \\Seen \\Draft)\r\n", "* LIST () \"/\" \"~peter/mail/台北/日本語\" (\"OLDNAME\" ", "(\"~peter/mail/&U,BTFw-/&ZeVnLIqe-\"))\r\n", "* OK [PERMANENTFLAGS (\\Deleted \\Seen \\Answered \\Flagged \\Draft \\*)] All allowed\r\n", "* OK [UIDVALIDITY 3857529045] UIDs valid\r\n", "* OK [UIDNEXT 4392] Next predicted UID\r\n", "{}" ), objectid_line ), format!( concat!( "* OK [CLOSED] Closed previous mailbox\r\n", "* 172 EXISTS\r\n", "* FLAGS (\\Answered \\Flagged \\Deleted \\Seen \\Draft \\Recent)\r\n", "* 5 RECENT\r\n", "* OK [UNSEEN 3] Unseen messages\r\n", "* OK [PERMANENTFLAGS (\\Deleted \\Seen \\Answered \\Flagged \\Draft \\*)] All allowed\r\n", "* OK [UIDVALIDITY 3857529045] UIDs valid\r\n", "* OK [UIDNEXT 4392] Next predicted UID\r\n", "{}" ), objectid_line ), ), ] { let response_v2 = String::from_utf8(response.clone().serialize()).unwrap(); response.is_rev2 = false; let response_v1 = String::from_utf8(response.serialize()).unwrap(); assert_eq!(response_v2, expected_v2); assert_eq!(response_v1, expected_v1); } } }