|
@@ -20,15 +20,19 @@
|
|
|
//
|
|
|
// SPDX-License-Identifier: EUPL-1.2 OR GPL-3.0-or-later
|
|
|
|
|
|
-use std::path::{Path, PathBuf};
|
|
|
+use std::{
|
|
|
+ path::{Path, PathBuf},
|
|
|
+ sync::Arc,
|
|
|
+};
|
|
|
|
|
|
use regex::Regex;
|
|
|
+use tempfile::TempDir;
|
|
|
|
|
|
use crate::{
|
|
|
- backends::FlagOp,
|
|
|
+ backends::prelude::*,
|
|
|
email::Flag,
|
|
|
error::Result,
|
|
|
- maildir::{move_to_cur, Configuration, MaildirPathTrait},
|
|
|
+ maildir::{move_to_cur, Configuration, MaildirMailbox, MaildirPathTrait, MaildirType},
|
|
|
};
|
|
|
|
|
|
fn set_flags(config: &Configuration, path: &Path, flag_ops: &[FlagOp]) -> Result<PathBuf> {
|
|
@@ -232,3 +236,312 @@ fn test_maildir_place_in_dir_regexp() {
|
|
|
"place_in_dir() should add missing `:2,` substring"
|
|
|
);
|
|
|
}
|
|
|
+
|
|
|
+fn new_maildir_backend(
|
|
|
+ temp_dir: &TempDir,
|
|
|
+ acc_name: &str,
|
|
|
+ event_consumer: BackendEventConsumer,
|
|
|
+ with_root_mailbox: bool,
|
|
|
+) -> Result<(PathBuf, AccountSettings, Box<MaildirType>)> {
|
|
|
+ let root_mailbox = temp_dir.path().join("INBOX");
|
|
|
+ {
|
|
|
+ std::fs::create_dir(&root_mailbox).expect("Could not create root mailbox directory.");
|
|
|
+ if with_root_mailbox {
|
|
|
+ for d in &["cur", "new", "tmp"] {
|
|
|
+ std::fs::create_dir(root_mailbox.join(d))
|
|
|
+ .expect("Could not create root mailbox directory contents.");
|
|
|
+ }
|
|
|
+ }
|
|
|
+ }
|
|
|
+ let subscribed_mailboxes = if with_root_mailbox {
|
|
|
+ vec!["INBOX".into()]
|
|
|
+ } else {
|
|
|
+ vec![]
|
|
|
+ };
|
|
|
+ let mailboxes = if with_root_mailbox {
|
|
|
+ vec![(
|
|
|
+ "INBOX".into(),
|
|
|
+ crate::conf::MailboxConf {
|
|
|
+ extra: indexmap::indexmap! {
|
|
|
+ "path".into() => root_mailbox.display().to_string(),
|
|
|
+ },
|
|
|
+ ..Default::default()
|
|
|
+ },
|
|
|
+ )]
|
|
|
+ .into_iter()
|
|
|
+ .collect()
|
|
|
+ } else {
|
|
|
+ indexmap::indexmap! {}
|
|
|
+ };
|
|
|
+ let extra = if with_root_mailbox {
|
|
|
+ indexmap::indexmap! {
|
|
|
+ "root_mailbox".into() => root_mailbox.display().to_string(),
|
|
|
+ }
|
|
|
+ } else {
|
|
|
+ indexmap::indexmap! {}
|
|
|
+ };
|
|
|
+
|
|
|
+ let account_conf = AccountSettings {
|
|
|
+ name: acc_name.to_string(),
|
|
|
+ root_mailbox: root_mailbox.display().to_string(),
|
|
|
+ format: "maildir".to_string(),
|
|
|
+ identity: "user@localhost".to_string(),
|
|
|
+ extra_identities: vec![],
|
|
|
+ read_only: false,
|
|
|
+ display_name: None,
|
|
|
+ order: Default::default(),
|
|
|
+ subscribed_mailboxes,
|
|
|
+ mailboxes,
|
|
|
+ manual_refresh: true,
|
|
|
+ extra,
|
|
|
+ };
|
|
|
+
|
|
|
+ let maildir = MaildirType::new(&account_conf, Default::default(), event_consumer)?;
|
|
|
+ Ok((root_mailbox, account_conf, maildir))
|
|
|
+}
|
|
|
+
|
|
|
+#[test]
|
|
|
+fn test_maildir_mailbox_paths() {
|
|
|
+ let temp_dir = TempDir::new().unwrap();
|
|
|
+ let backend_event_queue = Arc::new(std::sync::Mutex::new(
|
|
|
+ std::collections::VecDeque::with_capacity(16),
|
|
|
+ ));
|
|
|
+
|
|
|
+ let backend_event_consumer = {
|
|
|
+ let backend_event_queue = Arc::clone(&backend_event_queue);
|
|
|
+
|
|
|
+ BackendEventConsumer::new(Arc::new(move |ah, be| {
|
|
|
+ backend_event_queue.lock().unwrap().push_back((ah, be));
|
|
|
+ }))
|
|
|
+ };
|
|
|
+
|
|
|
+ // Perform tests on a maildir backend where the root mailbox, is not a valid
|
|
|
+ // maildir mailbox (e.g. has no cur,new,tmp sub directories.
|
|
|
+ {
|
|
|
+ let (root_mailbox, _settings, maildir) =
|
|
|
+ new_maildir_backend(&temp_dir, "maildir", backend_event_consumer.clone(), false)
|
|
|
+ .unwrap();
|
|
|
+ assert!(!maildir.config.is_root_a_mailbox);
|
|
|
+ // Assert that giving a file system path to MaildirBox::new is valid
|
|
|
+ let new_mailbox = MaildirMailbox::new(
|
|
|
+ root_mailbox.join("Archive").display().to_string(),
|
|
|
+ "Archive".into(),
|
|
|
+ None,
|
|
|
+ vec![],
|
|
|
+ true,
|
|
|
+ &maildir.config,
|
|
|
+ )
|
|
|
+ .unwrap();
|
|
|
+ assert_eq!(new_mailbox.name, "Archive");
|
|
|
+ assert_eq!(&new_mailbox.path, &Path::new("Archive"));
|
|
|
+ assert_eq!(&new_mailbox.fs_path, &root_mailbox.join("Archive"));
|
|
|
+ // Assert that giving a mailbox path to MaildirBox::new is valid
|
|
|
+ let new_mailbox = MaildirMailbox::new(
|
|
|
+ "INBOX/Archive".into(),
|
|
|
+ "Archive".into(),
|
|
|
+ None,
|
|
|
+ vec![],
|
|
|
+ true,
|
|
|
+ &maildir.config,
|
|
|
+ )
|
|
|
+ .unwrap();
|
|
|
+ assert_eq!(new_mailbox.name, "Archive");
|
|
|
+ assert_eq!(&new_mailbox.path, &Path::new("Archive"));
|
|
|
+ assert_eq!(&new_mailbox.fs_path, &root_mailbox.join("Archive"));
|
|
|
+ let mut backend = maildir as Box<dyn MailBackend>;
|
|
|
+ let ref_mailboxes = smol::block_on(backend.mailboxes().unwrap()).unwrap();
|
|
|
+ // Assert that backend has no mailboxes at all");
|
|
|
+ assert!(
|
|
|
+ ref_mailboxes.is_empty(),
|
|
|
+ "ref_mailboxes were not empty: {:?}",
|
|
|
+ ref_mailboxes
|
|
|
+ );
|
|
|
+ let (new_hash, ref_mailboxes) =
|
|
|
+ smol::block_on(backend.create_mailbox("Archive".into()).unwrap()).unwrap();
|
|
|
+ assert_eq!(ref_mailboxes[&new_hash].name(), "Archive");
|
|
|
+ assert_eq!(ref_mailboxes[&new_hash].path(), "Archive");
|
|
|
+ assert_eq!(
|
|
|
+ ref_mailboxes[&new_hash]
|
|
|
+ .as_any()
|
|
|
+ .downcast_ref::<MaildirMailbox>()
|
|
|
+ .unwrap()
|
|
|
+ .fs_path(),
|
|
|
+ &root_mailbox.join("Archive")
|
|
|
+ );
|
|
|
+ // Assert that even if we accidentally give a file system path to a maildir
|
|
|
+ // backend's create_mailbox() method, it still does the correct thing.
|
|
|
+ let (new_hash, ref_mailboxes) = smol::block_on(
|
|
|
+ backend
|
|
|
+ .create_mailbox(root_mailbox.join("Archive2").display().to_string())
|
|
|
+ .unwrap(),
|
|
|
+ )
|
|
|
+ .unwrap();
|
|
|
+ assert_eq!(ref_mailboxes[&new_hash].name(), "Archive2");
|
|
|
+ assert_eq!(ref_mailboxes[&new_hash].path(), "Archive2");
|
|
|
+ assert_eq!(
|
|
|
+ ref_mailboxes[&new_hash]
|
|
|
+ .as_any()
|
|
|
+ .downcast_ref::<MaildirMailbox>()
|
|
|
+ .unwrap()
|
|
|
+ .fs_path(),
|
|
|
+ &root_mailbox.join("Archive2")
|
|
|
+ );
|
|
|
+ let ref_mailboxes = smol::block_on(backend.mailboxes().unwrap()).unwrap();
|
|
|
+ // Assert that backend has all the created mailboxes so far
|
|
|
+ assert_eq!(
|
|
|
+ ref_mailboxes.len(),
|
|
|
+ 2,
|
|
|
+ "mailboxes() return value content not what expected: {:?}",
|
|
|
+ ref_mailboxes
|
|
|
+ );
|
|
|
+ // Assert that giving an absolute path returns an error
|
|
|
+ assert_eq!(
|
|
|
+ &smol::block_on(backend.create_mailbox("/Archive3".to_string()).unwrap())
|
|
|
+ .unwrap_err()
|
|
|
+ .to_string(),
|
|
|
+ "Path given (`/Archive3`) is absolute. Please provide a path relative to the \
|
|
|
+ account's root mailbox."
|
|
|
+ );
|
|
|
+ // Assert that attempting to create a mailbox outside of the root mailbox
|
|
|
+ // returns an error
|
|
|
+ assert_eq!(
|
|
|
+ &smol::block_on(
|
|
|
+ backend
|
|
|
+ .create_mailbox(temp_dir.path().join("Archive3").display().to_string())
|
|
|
+ .unwrap()
|
|
|
+ )
|
|
|
+ .unwrap_err()
|
|
|
+ .to_string(),
|
|
|
+ &format!(
|
|
|
+ "Path given, `{}`, is not included in the root mailbox path `{}`. A maildir \
|
|
|
+ backend cannot contain mailboxes outside of its root path.",
|
|
|
+ temp_dir.path().join("Archive3").display(),
|
|
|
+ root_mailbox.display(),
|
|
|
+ )
|
|
|
+ );
|
|
|
+
|
|
|
+ std::fs::remove_dir_all(root_mailbox).unwrap();
|
|
|
+ }
|
|
|
+
|
|
|
+ // Perform same tests on a maildir backend where the root mailbox is a valid
|
|
|
+ // maildir mailbox (e.g. has cur,new,tmp sub directories.
|
|
|
+ {
|
|
|
+ let (root_mailbox, _settings, maildir) =
|
|
|
+ new_maildir_backend(&temp_dir, "maildir", backend_event_consumer, true).unwrap();
|
|
|
+ assert!(maildir.config.is_root_a_mailbox);
|
|
|
+ // Assert that giving a file system path to MaildirBox::new is valid
|
|
|
+ let new_mailbox = MaildirMailbox::new(
|
|
|
+ root_mailbox.join("Archive").display().to_string(),
|
|
|
+ "Archive".into(),
|
|
|
+ None,
|
|
|
+ vec![],
|
|
|
+ true,
|
|
|
+ &maildir.config,
|
|
|
+ )
|
|
|
+ .unwrap();
|
|
|
+ assert_eq!(new_mailbox.name, "Archive");
|
|
|
+ assert_eq!(&new_mailbox.path, &Path::new("INBOX/Archive"));
|
|
|
+ assert_eq!(&new_mailbox.fs_path, &root_mailbox.join("Archive"));
|
|
|
+ // Assert that giving a mailbox path to MaildirBox::new is valid
|
|
|
+ let new_mailbox = MaildirMailbox::new(
|
|
|
+ "INBOX/Archive".into(),
|
|
|
+ "Archive".into(),
|
|
|
+ None,
|
|
|
+ vec![],
|
|
|
+ true,
|
|
|
+ &maildir.config,
|
|
|
+ )
|
|
|
+ .unwrap();
|
|
|
+ assert_eq!(new_mailbox.name, "Archive");
|
|
|
+ assert_eq!(&new_mailbox.path, &Path::new("INBOX/Archive"));
|
|
|
+ assert_eq!(&new_mailbox.fs_path, &root_mailbox.join("Archive"));
|
|
|
+ let mut backend = maildir as Box<dyn MailBackend>;
|
|
|
+ let ref_mailboxes = smol::block_on(backend.mailboxes().unwrap()).unwrap();
|
|
|
+ // Assert that backend has only INBOX as a mailbox
|
|
|
+ assert_eq!(
|
|
|
+ ref_mailboxes.len(),
|
|
|
+ 1,
|
|
|
+ "ref_mailboxes is not just INBOX: {:?}",
|
|
|
+ ref_mailboxes
|
|
|
+ );
|
|
|
+ // Assert that creating a mailbox without the root mailbox as a prefix does the
|
|
|
+ // correct thing.
|
|
|
+ let (new_hash, ref_mailboxes) =
|
|
|
+ smol::block_on(backend.create_mailbox("Archive".into()).unwrap()).unwrap();
|
|
|
+ assert_eq!(ref_mailboxes[&new_hash].name(), "Archive");
|
|
|
+ assert_eq!(ref_mailboxes[&new_hash].path(), "INBOX/Archive");
|
|
|
+ assert_eq!(
|
|
|
+ ref_mailboxes[&new_hash]
|
|
|
+ .as_any()
|
|
|
+ .downcast_ref::<MaildirMailbox>()
|
|
|
+ .unwrap()
|
|
|
+ .fs_path(),
|
|
|
+ &root_mailbox.join("Archive")
|
|
|
+ );
|
|
|
+ // Assert that creating a mailbox with the root mailbox as a prefix does the
|
|
|
+ // correct thing.
|
|
|
+ let (new_hash, ref_mailboxes) =
|
|
|
+ smol::block_on(backend.create_mailbox("INBOX/Archive2".into()).unwrap()).unwrap();
|
|
|
+ assert_eq!(ref_mailboxes[&new_hash].name(), "Archive2");
|
|
|
+ assert_eq!(ref_mailboxes[&new_hash].path(), "INBOX/Archive2");
|
|
|
+ assert_eq!(
|
|
|
+ ref_mailboxes[&new_hash]
|
|
|
+ .as_any()
|
|
|
+ .downcast_ref::<MaildirMailbox>()
|
|
|
+ .unwrap()
|
|
|
+ .fs_path(),
|
|
|
+ &root_mailbox.join("Archive2")
|
|
|
+ );
|
|
|
+ // Assert that even if we accidentally give a file system path to a maildir
|
|
|
+ // backend's create_mailbox() method, it still does the correct thing.
|
|
|
+ let (new_hash, ref_mailboxes) = smol::block_on(
|
|
|
+ backend
|
|
|
+ .create_mailbox(root_mailbox.join("Archive3").display().to_string())
|
|
|
+ .unwrap(),
|
|
|
+ )
|
|
|
+ .unwrap();
|
|
|
+ assert_eq!(ref_mailboxes[&new_hash].name(), "Archive3");
|
|
|
+ assert_eq!(ref_mailboxes[&new_hash].path(), "INBOX/Archive3");
|
|
|
+ assert_eq!(
|
|
|
+ ref_mailboxes[&new_hash]
|
|
|
+ .as_any()
|
|
|
+ .downcast_ref::<MaildirMailbox>()
|
|
|
+ .unwrap()
|
|
|
+ .fs_path(),
|
|
|
+ &root_mailbox.join("Archive3")
|
|
|
+ );
|
|
|
+ let ref_mailboxes = smol::block_on(backend.mailboxes().unwrap()).unwrap();
|
|
|
+ // Assert that backend has all the created mailboxes so far
|
|
|
+ assert_eq!(
|
|
|
+ ref_mailboxes.len(),
|
|
|
+ 4,
|
|
|
+ "mailboxes() return value content not what expected: {:?}",
|
|
|
+ ref_mailboxes
|
|
|
+ );
|
|
|
+ // Assert that giving an absolute path returns an error
|
|
|
+ assert_eq!(
|
|
|
+ &smol::block_on(backend.create_mailbox("/Archive4".to_string()).unwrap())
|
|
|
+ .unwrap_err()
|
|
|
+ .to_string(),
|
|
|
+ "Path given (`/Archive4`) is absolute. Please provide a path relative to the \
|
|
|
+ account's root mailbox."
|
|
|
+ );
|
|
|
+ // Assert that attempting to create a mailbox outside of the root mailbox
|
|
|
+ // returns an error
|
|
|
+ assert_eq!(
|
|
|
+ &smol::block_on(
|
|
|
+ backend
|
|
|
+ .create_mailbox(temp_dir.path().join("Archive4").display().to_string())
|
|
|
+ .unwrap()
|
|
|
+ )
|
|
|
+ .unwrap_err()
|
|
|
+ .to_string(),
|
|
|
+ &format!(
|
|
|
+ "Path given, `{}`, is not included in the root mailbox path `{}`. A maildir \
|
|
|
+ backend cannot contain mailboxes outside of its root path.",
|
|
|
+ temp_dir.path().join("Archive4").display(),
|
|
|
+ root_mailbox.display(),
|
|
|
+ )
|
|
|
+ );
|
|
|
+ }
|
|
|
+}
|