/* * SPDX-FileCopyrightText: 2020 Stalwart Labs LLC * * SPDX-License-Identifier: Apache-2.0 OR MIT */ use crate::exchange_ews::soap::write_escaped; use crate::exchange_ews::types::{DistinguishedFolderId, FolderId, ItemId}; #[derive(Debug, Clone, Copy)] pub enum FolderRef<'a> { Distinguished(DistinguishedFolderId), Concrete(&'a FolderId), } #[derive(Debug, Clone, Copy)] pub enum FolderShape { IdOnly, } impl FolderShape { fn as_str(self) -> &'static str { match self { FolderShape::IdOnly => "IdOnly", } } } #[derive(Debug, Clone, Copy)] pub enum Traversal { Shallow, Deep, } impl Traversal { fn as_str(self) -> &'static str { match self { Traversal::Shallow => "Shallow", Traversal::Deep => "Deep", } } } pub fn find_folder_body(parent: FolderRef<'_>, traversal: Traversal) -> String { let mut out = String::with_capacity(512); out.push_str(""); out.push_str("Default"); out.push_str(""); out.push_str(""); out.push_str(""); out.push_str(""); out.push_str(""); out.push_str(""); out.push_str(""); out.push_str(""); write_folder_ref(&mut out, parent); out.push_str(""); out } pub fn get_folder_body(folders: &[FolderRef<'_>], shape: FolderShape) -> String { let mut out = String::with_capacity(256 + folders.len() * 64); out.push_str(""); out.push_str(""); out.push_str(shape.as_str()); out.push_str(""); out.push_str(""); for f in folders { write_folder_ref(&mut out, *f); } out.push_str(""); out } pub fn find_item_body( parent: FolderRef<'_>, traversal: Traversal, offset: u32, page_size: u32, ) -> String { let mut out = String::with_capacity(512); out.push_str("IdOnly"); out.push_str(""); out.push_str(""); write_folder_ref(&mut out, parent); out.push_str(""); out } pub fn sync_folder_items_body(folder: &FolderId, sync_state: &str, max_changes: u32) -> String { let mut out = String::with_capacity(512); out.push_str(""); out.push_str("IdOnly"); out.push_str(""); write_folder_id_element(&mut out, folder); out.push_str(""); out.push_str(""); write_escaped(&mut out, sync_state); out.push_str(""); out.push_str(""); out.push_str(&max_changes.to_string()); out.push_str(""); out.push_str("NormalItems"); out.push_str(""); out } #[derive(Debug, Clone, Copy)] pub enum ItemShape { Message, CalendarItem, Contact, } pub fn get_item_body(shape: ItemShape, ids: &[ItemId]) -> String { let mut out = String::with_capacity(256 + ids.len() * 128); out.push_str(""); match shape { ItemShape::Message => { out.push_str("IdOnly"); out.push_str("true"); out.push_str("Best"); out.push_str(""); out.push_str(""); out.push_str(""); out.push_str(""); out.push_str(""); out.push_str(""); out.push_str(""); out.push_str(""); out.push_str(""); } ItemShape::CalendarItem => { out.push_str("IdOnly"); out.push_str("Best"); out.push_str(""); for f in CALENDAR_FIELDS { out.push_str(""); } out.push_str(""); } ItemShape::Contact => { out.push_str("AllProperties"); } } out.push_str(""); for id in ids { write_item_id_element(&mut out, id); } out.push_str(""); out } const CALENDAR_FIELDS: &[&str] = &[ "calendar:UID", "calendar:RecurrenceId", "calendar:Start", "calendar:End", "calendar:OriginalStart", "calendar:IsAllDayEvent", "calendar:LegacyFreeBusyStatus", "calendar:Location", "calendar:IsRecurring", "calendar:CalendarItemType", "calendar:Organizer", "calendar:RequiredAttendees", "calendar:OptionalAttendees", "calendar:Recurrence", "calendar:ModifiedOccurrences", "calendar:DeletedOccurrences", "calendar:StartTimeZone", "calendar:EndTimeZone", "calendar:Duration", "item:Subject", "item:Body", "item:Categories", "item:DateTimeCreated", "item:LastModifiedTime", "item:ParentFolderId", ]; pub fn get_attachment_body(ids: &[&str]) -> String { let mut out = String::with_capacity(256 + ids.len() * 64); out.push_str(""); out.push_str("true"); out.push_str("Best"); out.push_str(""); for id in ids { out.push_str(""); } out.push_str(""); out } pub fn write_folder_id_element(out: &mut String, folder: &FolderId) { out.push_str(""); } pub fn write_item_id_element(out: &mut String, id: &ItemId) { out.push_str(""); } fn write_folder_ref(out: &mut String, parent: FolderRef<'_>) { match parent { FolderRef::Distinguished(d) => { out.push_str(""); } FolderRef::Concrete(f) => write_folder_id_element(out, f), } } #[cfg(test)] mod tests { use super::*; #[test] fn find_folder_uses_deep_traversal_for_msgfolderroot() { let body = find_folder_body( FolderRef::Distinguished(DistinguishedFolderId::MsgFolderRoot), Traversal::Deep, ); assert!(body.contains("Traversal=\"Deep\"")); assert!(body.contains("")); assert!(body.contains("folder:FolderClass")); assert!(body.contains("folder:DisplayName")); assert!(body.contains("folder:ParentFolderId")); } #[test] fn find_folder_archive_uses_archiveroot() { let body = find_folder_body( FolderRef::Distinguished(DistinguishedFolderId::ArchiveRoot), Traversal::Deep, ); assert!(body.contains("")); } #[test] fn find_item_paginates_with_offset_and_page_size() { let folder = FolderId::new("FID", "FCK"); let body = find_item_body(FolderRef::Concrete(&folder), Traversal::Shallow, 200, 50); assert!(body.contains("Offset=\"200\"")); assert!(body.contains("MaxEntriesReturned=\"50\"")); assert!(body.contains("")); } #[test] fn get_item_message_shape_requests_mime() { let ids = vec![ItemId::new("I1", "CK1"), ItemId::new("I2", "")]; let body = get_item_body(ItemShape::Message, &ids); assert!(body.contains("true")); assert!(body.contains("")); assert!(body.contains("")); } #[test] fn get_item_calendar_shape_lists_calendar_fields() { let body = get_item_body(ItemShape::CalendarItem, &[ItemId::new("X", "")]); assert!(body.contains("calendar:Recurrence")); assert!(body.contains("calendar:ModifiedOccurrences")); assert!(body.contains("calendar:DeletedOccurrences")); assert!(body.contains("calendar:UID")); assert!(!body.contains("")); } #[test] fn get_item_contact_shape_requests_all_properties() { let body = get_item_body(ItemShape::Contact, &[ItemId::new("X", "")]); assert!(body.contains("AllProperties")); } #[test] fn sync_folder_items_body_carries_state_and_max() { let folder = FolderId::new("FID", "FCK"); let body = sync_folder_items_body(&folder, "STATE", 512); assert!(body.contains("")); assert!(body.contains("STATE")); assert!(body.contains("512")); assert!(body.contains("NormalItems")); } #[test] fn sync_folder_items_empty_state_round_trips() { let folder = FolderId::new("FID", ""); let body = sync_folder_items_body(&folder, "", 100); assert!(body.contains("")); } #[test] fn get_attachment_lists_ids() { let body = get_attachment_body(&["AID1", "AID2"]); assert!(body.contains("")); assert!(body.contains("")); assert!(body.contains("true")); } #[test] fn get_folder_emits_distinguished_ids() { let body = get_folder_body( &[ FolderRef::Distinguished(DistinguishedFolderId::Inbox), FolderRef::Distinguished(DistinguishedFolderId::SentItems), ], FolderShape::IdOnly, ); assert!(body.contains("")); assert!(body.contains("")); assert!(body.contains("IdOnly")); } }