From 6ca561fff5ef6972b7e61ceb9613f69f0a522cec Mon Sep 17 00:00:00 2001 From: Maurus Decimus <11444311+mdecimus@users.noreply.github.com> Date: Sat, 27 Jun 2026 12:00:12 +0200 Subject: [PATCH] Fix: Strict `RFC822.SIZE` == `BODY[]` length check discards good mail --- CHANGELOG.md | 9 +++++++++ Cargo.lock | 2 +- Cargo.toml | 2 +- src/dav/discover.rs | 2 +- src/db/roles.rs | 4 +++- src/sync/export/tree.rs | 3 +-- src/sync/import_imap/coordinator.rs | 11 ++--------- tests/mock_imap.rs | 8 +++++--- tests/mock_sync.rs | 10 ++++++++-- 9 files changed, 31 insertions(+), 20 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 89258f7..5bd1c26 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,6 +2,15 @@ All notable changes to this project will be documented in this file. This project adheres to [Semantic Versioning](http://semver.org/). +## [1.0.5] - 2026-06-27 + +### Added + +### Changed + +### Fixed +- Strict `RFC822.SIZE` == `BODY[]` length check discards good mail. + ## [1.0.4] - 2026-06-21 ### Added diff --git a/Cargo.lock b/Cargo.lock index 4f6a7c2..196b56b 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -2892,7 +2892,7 @@ dependencies = [ [[package]] name = "vandelay" -version = "1.0.4" +version = "1.0.5" dependencies = [ "base64", "blake3", diff --git a/Cargo.toml b/Cargo.toml index 504780f..56e984f 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,7 +1,7 @@ [package] name = "vandelay" description = "JMAP account migration utility" -version = "1.0.4" +version = "1.0.5" authors = ["Stalwart Labs LLC "] license = "Apache-2.0 OR MIT" repository = "https://github.com/stalwartlabs/vandelay" diff --git a/src/dav/discover.rs b/src/dav/discover.rs index d87f851..5425e2d 100644 --- a/src/dav/discover.rs +++ b/src/dav/discover.rs @@ -222,7 +222,7 @@ fn try_via_principal( return Ok(None); } Err(JmapError::RetriesExhausted(_)) | Err(JmapError::Malformed(_)) => { - return Ok(None) + return Ok(None); } Err(e) => return Err(DiscoveryError::Transport(e)), }; diff --git a/src/db/roles.rs b/src/db/roles.rs index c9a48bc..45de76d 100644 --- a/src/db/roles.rs +++ b/src/db/roles.rs @@ -75,7 +75,9 @@ mod tests { let conn = mem(); let id = insert(&conn, "Sent", Some("sent")); assert_eq!( - unique_role(&conn, Some("sent"), Some(id)).unwrap().as_deref(), + unique_role(&conn, Some("sent"), Some(id)) + .unwrap() + .as_deref(), Some("sent") ); } diff --git a/src/sync/export/tree.rs b/src/sync/export/tree.rs index 3b1b645..96ea498 100644 --- a/src/sync/export/tree.rs +++ b/src/sync/export/tree.rs @@ -160,8 +160,7 @@ pub fn reconcile( .unwrap_or(0); let mut uploader = Uploader::new(net, &ctx.conn); - let mut taken_roles: HashSet = - targets.iter().filter_map(|t| t.role.clone()).collect(); + let mut taken_roles: HashSet = targets.iter().filter_map(|t| t.role.clone()).collect(); let interleave = ty == ObjectType::FileNode; for d in 0..=max_depth { let level: Vec<&LocalNode> = to_create diff --git a/src/sync/import_imap/coordinator.rs b/src/sync/import_imap/coordinator.rs index 6159f0f..f44e077 100644 --- a/src/sync/import_imap/coordinator.rs +++ b/src/sync/import_imap/coordinator.rs @@ -582,12 +582,7 @@ fn upsert_mailboxes( tx.execute( "INSERT INTO mailboxes (name, parent_id, role, sort_order, is_subscribed) VALUES (?1, ?2, ?3, 0, ?4)", - params![ - folder.leaf, - parent_local, - role, - folder.subscribed as i64 - ], + params![folder.leaf, parent_local, role, folder.subscribed as i64], )?; let new_id = tx.last_insert_rowid(); db::imap_ids::insert_mailbox(&tx, source_id, &folder.name, new_id)?; @@ -927,12 +922,10 @@ fn insert_single_message( logger, LEVEL_DEFAULT, &format!( - "folder {folder:?} uid {uid}: BODY[] {} bytes vs RFC822.SIZE {declared_size}, skipping", + "folder {folder:?} uid {uid}: BODY[] {} bytes vs RFC822.SIZE {declared_size}, importing the fetched literal", body.len() ), ); - counts.skipped += 1; - return Ok(()); } let translation = translate_flags(&attrs.flags, include_deleted); if translation.has_deleted_flag && !include_deleted { diff --git a/tests/mock_imap.rs b/tests/mock_imap.rs index 21dc1b9..e45576d 100644 --- a/tests/mock_imap.rs +++ b/tests/mock_imap.rs @@ -707,7 +707,7 @@ fn coordinator_falls_back_to_uid_fetch_when_search_all_bad() { } #[test] -fn coordinator_skips_message_on_size_mismatch() { +fn coordinator_imports_message_despite_size_mismatch() { let control = control_script_one_folder(100, 2, &[1]); let worker: Script = Box::new(|conn: &mut MockConn| -> std::io::Result<()> { auth_preamble(conn, "IMAP4rev2 LITERAL+ AUTH=PLAIN")?; @@ -734,8 +734,10 @@ fn coordinator_skips_message_on_size_mismatch() { .iter() .find(|(k, _)| *k == "email") .unwrap(); - assert_eq!(email.1.skipped, 1); - assert_eq!(email.1.created, 0); + assert_eq!(email.1.skipped, 0); + assert_eq!(email.1.created, 1); + let dbc = Connection::open(&archive).unwrap(); + assert_eq!(count(&dbc, "emails"), 1); } fn single_inbox_scripts(uidvalidity: u32, uidnext: u32, body: &'static [u8]) -> Vec