/* * SPDX-FileCopyrightText: 2020 Stalwart Labs LLC * * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-SEL */ use imap::op::list::matches_pattern; use imap_proto::ResponseType; use crate::utils::server::TestServer; use super::{AssertResult, ImapConnection, Type}; pub async fn test( mut imap: &mut ImapConnection, mut imap_check: &mut ImapConnection, test: &TestServer, ) { println!("Running mailbox tests..."); // Pattern matching tests mailbox_matches_pattern(); // Create third connection for testing let mut other_conn = test.account("jdoe@example.com").imap_client().await; // List folders imap.send("LIST \"\" \"*\"").await; imap.assert_read(Type::Tagged, ResponseType::Ok) .await .assert_folders([("INBOX", [""]), ("Deleted Items", [""])], true); // Create folders imap.send("CREATE \"Tofu\"").await; imap.assert_read(Type::Tagged, ResponseType::Ok).await; imap.send("CREATE \"Fruit\"").await; imap.assert_read(Type::Tagged, ResponseType::Ok).await; imap.send("CREATE \"Fruit/Apple\"").await; imap.assert_read(Type::Tagged, ResponseType::Ok).await; imap.send("CREATE \"Fruit/Apple/Green\"").await; imap.assert_read(Type::Tagged, ResponseType::Ok).await; imap.send("CREATE \"L&APg-bende opgaver\"").await; imap.assert_read(Type::Tagged, ResponseType::Ok).await; // Select folder from another connection other_conn.send("SELECT \"Tofu\"").await; other_conn.assert_read(Type::Tagged, ResponseType::Ok).await; other_conn.send("SELECT \"L&APg-bende opgaver\"").await; other_conn.assert_read(Type::Tagged, ResponseType::Ok).await; // Make sure folders are visible for imap in [&mut imap, &mut imap_check] { imap.send("LIST \"\" \"*\"").await; imap.assert_read(Type::Tagged, ResponseType::Ok) .await .assert_folders( [ ("INBOX", [""]), ("Deleted Items", [""]), ("Fruit", [""]), ("Fruit/Apple", [""]), ("Fruit/Apple/Green", [""]), ("Tofu", [""]), ("L&APg-bende opgaver", [""]), ], true, ); } imap.send("DELETE \"L&APg-bende opgaver\"").await; imap.assert_read(Type::Tagged, ResponseType::Ok).await; // Special use folders that already exist should not be allowed imap.send("CREATE \"Second trash\" (USE (\\Trash))").await; imap.assert_read(Type::Tagged, ResponseType::No).await; // Every command in a pipelined batch is answered, even after a failure imap.send_raw(concat!( "_p1 STATUS \"Tofu\" (MESSAGES)\r\n", "_p2 STATUS \"Does Not Exist\" (MESSAGES)\r\n", "_x STATUS \"Fruit/Apple\" (MESSAGES)\r\n" )) .await; imap.assert_read(Type::Tagged, ResponseType::Ok) .await .assert_contains("_p1 OK") .assert_contains("_p2 NO [NONEXISTENT]") .assert_contains("* STATUS \"Fruit/Apple\""); // Enable IMAP4rev2 imap.send("ENABLE IMAP4rev2").await; imap.assert_read(Type::Tagged, ResponseType::Ok).await; // Create and delete using IMAP4rev2 imap.send("CREATE \"L&APg-bende opgaver\"").await; imap.assert_read(Type::Tagged, ResponseType::Ok).await; imap.send("SELECT \"L&APg-bende opgaver\"").await; imap.assert_read(Type::Tagged, ResponseType::Ok).await; imap.send("UNSELECT \"L&APg-bende opgaver\"").await; imap.assert_read(Type::Tagged, ResponseType::Ok).await; imap.send("DELETE \"L&APg-bende opgaver\"").await; imap.assert_read(Type::Tagged, ResponseType::Ok).await; // Create missing parent folders imap.send("CREATE \"/Vegetable/Broccoli\" (USE (\\Important))") .await; imap.assert_read(Type::Tagged, ResponseType::Ok).await; imap.send("CREATE \" Cars/Electric /4 doors/ Red/\"").await; imap.assert_read(Type::Tagged, ResponseType::Ok).await; for imap in [&mut imap, &mut imap_check] { imap.send("LIST \"\" \"*\" RETURN (CHILDREN SPECIAL-USE)") .await; imap.assert_read(Type::Tagged, ResponseType::Ok) .await .assert_folders( [ ("INBOX", ["HasNoChildren", ""]), ("Deleted Items", ["HasNoChildren", "Trash"]), ("Cars/Electric/4 doors/Red", ["HasNoChildren", ""]), ("Cars/Electric/4 doors", ["HasChildren", ""]), ("Cars/Electric", ["HasChildren", ""]), ("Cars", ["HasChildren", ""]), ("Fruit", ["HasChildren", ""]), ("Fruit/Apple", ["HasChildren", ""]), ("Fruit/Apple/Green", ["HasNoChildren", ""]), ("Vegetable", ["HasChildren", ""]), ("Vegetable/Broccoli", ["HasNoChildren", "\\Important"]), ("Tofu", ["HasNoChildren", ""]), ], true, ); } // Rename folders imap.send("RENAME \"Fruit/Apple/Green\" \"Fruit/Apple/Red\"") .await; imap.assert_read(Type::Tagged, ResponseType::Ok).await; imap.send("RENAME \"Cars\" \"Vehicles\"").await; imap.assert_read(Type::Tagged, ResponseType::Ok).await; imap.send("RENAME \"Vegetable/Broccoli\" \"Veggies/Green/Broccoli\"") .await; imap.assert_read(Type::Tagged, ResponseType::Ok).await; imap.send("RENAME \"Tofu\" \"INBOX\"").await; imap.assert_read(Type::Tagged, ResponseType::No).await; imap.send("RENAME \"Tofu\" \"Inbox/Tofu\"").await; imap.assert_read(Type::Tagged, ResponseType::Ok).await; imap.send("RENAME \"Deleted Items\" \"Recycle Bin\"").await; imap.assert_read(Type::Tagged, ResponseType::Ok).await; for imap in [&mut imap, &mut imap_check] { imap.send("LIST \"\" \"*\" RETURN (CHILDREN SPECIAL-USE)") .await; imap.assert_read(Type::Tagged, ResponseType::Ok) .await .assert_folders( [ ("INBOX", ["HasChildren", ""]), ("INBOX/Tofu", ["HasNoChildren", ""]), ("Recycle Bin", ["HasNoChildren", "Trash"]), ("Vehicles/Electric/4 doors/Red", ["HasNoChildren", ""]), ("Vehicles/Electric/4 doors", ["HasChildren", ""]), ("Vehicles/Electric", ["HasChildren", ""]), ("Vehicles", ["HasChildren", ""]), ("Fruit", ["HasChildren", ""]), ("Fruit/Apple", ["HasChildren", ""]), ("Fruit/Apple/Red", ["HasNoChildren", ""]), ("Vegetable", ["HasNoChildren", ""]), ("Veggies", ["HasChildren", ""]), ("Veggies/Green", ["HasChildren", ""]), ("Veggies/Green/Broccoli", ["HasNoChildren", ""]), ], true, ); } // Delete folders imap.send("DELETE \"INBOX/Tofu\"").await; imap.assert_read(Type::Tagged, ResponseType::Ok).await; imap.send("DELETE \"Vegetable\"").await; imap.assert_read(Type::Tagged, ResponseType::Ok).await; imap.send("DELETE \"Vehicles\"").await; imap.assert_read(Type::Tagged, ResponseType::No).await; for imap in [&mut imap, &mut imap_check] { imap.send("LIST \"\" \"*\" RETURN (CHILDREN SPECIAL-USE)") .await; imap.assert_read(Type::Tagged, ResponseType::Ok) .await .assert_folders( [ ("INBOX", ["HasNoChildren", ""]), ("Recycle Bin", ["HasNoChildren", "Trash"]), ("Vehicles/Electric/4 doors/Red", ["HasNoChildren", ""]), ("Vehicles/Electric/4 doors", ["HasChildren", ""]), ("Vehicles/Electric", ["HasChildren", ""]), ("Vehicles", ["HasChildren", ""]), ("Fruit", ["HasChildren", ""]), ("Fruit/Apple", ["HasChildren", ""]), ("Fruit/Apple/Red", ["HasNoChildren", ""]), ("Veggies", ["HasChildren", ""]), ("Veggies/Green", ["HasChildren", ""]), ("Veggies/Green/Broccoli", ["HasNoChildren", ""]), ], true, ); } // Subscribe imap.send("SUBSCRIBE \"INBOX\"").await; imap.assert_read(Type::Tagged, ResponseType::Ok).await; imap.send("SUBSCRIBE \"Vehicles/Electric/4 doors/Red\"") .await; imap.assert_read(Type::Tagged, ResponseType::Ok).await; for imap in [&mut imap, &mut imap_check] { imap.send("LIST \"\" \"*\" RETURN (SUBSCRIBED SPECIAL-USE)") .await; imap.assert_read(Type::Tagged, ResponseType::Ok) .await .assert_folders( [ ("INBOX", ["Subscribed", ""]), ("Recycle Bin", ["", "Trash"]), ("Vehicles/Electric/4 doors/Red", ["Subscribed", ""]), ("Vehicles/Electric/4 doors", ["", ""]), ("Vehicles/Electric", ["", ""]), ("Vehicles", ["", ""]), ("Fruit", ["", ""]), ("Fruit/Apple", ["", ""]), ("Fruit/Apple/Red", ["", ""]), ("Veggies", ["", ""]), ("Veggies/Green", ["", ""]), ("Veggies/Green/Broccoli", ["", ""]), ], true, ); } // Filter by subscribed including children imap.send("LIST (SUBSCRIBED) \"\" \"*\" RETURN (CHILDREN)") .await; imap.assert_read(Type::Tagged, ResponseType::Ok) .await .assert_folders( [ ("INBOX", ["Subscribed", "HasNoChildren"]), ( "Vehicles/Electric/4 doors/Red", ["Subscribed", "HasNoChildren"], ), ], true, ); // Recursive match including children imap.send("LIST (SUBSCRIBED RECURSIVEMATCH) \"\" \"*\" RETURN (CHILDREN)") .await; imap.assert_read(Type::Tagged, ResponseType::Ok) .await .assert_folders( [ ("INBOX", ["Subscribed", "HasNoChildren"]), ( "Vehicles/Electric/4 doors/Red", ["Subscribed", "HasNoChildren"], ), ( "Vehicles/Electric/4 doors", ["\"CHILDINFO\" (\"SUBSCRIBED\")", "HasChildren"], ), ( "Vehicles/Electric", ["\"CHILDINFO\" (\"SUBSCRIBED\")", "HasChildren"], ), ( "Vehicles", ["\"CHILDINFO\" (\"SUBSCRIBED\")", "HasChildren"], ), ], true, ); // Imap4rev1 LSUB imap.send("LSUB \"\" \"*\"").await; imap.assert_read(Type::Tagged, ResponseType::Ok) .await .assert_folders( [("INBOX", [""]), ("Vehicles/Electric/4 doors/Red", [""])], true, ); // Unsubscribe imap.send("UNSUBSCRIBE \"Vehicles/Electric/4 doors/Red\"") .await; imap.assert_read(Type::Tagged, ResponseType::Ok).await; for imap in [&mut imap, &mut imap_check] { imap.send("LIST (SUBSCRIBED RECURSIVEMATCH) \"\" \"*\" RETURN (CHILDREN)") .await; imap.assert_read(Type::Tagged, ResponseType::Ok) .await .assert_folders([("INBOX", ["Subscribed", "HasNoChildren"])], true); } // LIST Filters imap.send("LIST \"\" \"%\"").await; imap.assert_read(Type::Tagged, ResponseType::Ok) .await .assert_folders( [ ("INBOX", [""]), ("Recycle Bin", [""]), ("Vehicles", [""]), ("Fruit", [""]), ("Veggies", [""]), ], true, ); imap.send("LIST \"\" \"*/Red\"").await; imap.assert_read(Type::Tagged, ResponseType::Ok) .await .assert_folders( [ ("Vehicles/Electric/4 doors/Red", [""]), ("Fruit/Apple/Red", [""]), ], true, ); imap.send("LIST \"\" \"Fruit/*\"").await; imap.assert_read(Type::Tagged, ResponseType::Ok) .await .assert_folders([("Fruit/Apple/Red", [""]), ("Fruit/Apple", [""])], true); imap.send("LIST \"\" \"Fruit/%\"").await; imap.assert_read(Type::Tagged, ResponseType::Ok) .await .assert_folders([("Fruit/Apple", [""])], true); // Restore Trash folder's original name imap.send("RENAME \"Recycle Bin\" \"Deleted Items\"").await; imap.assert_read(Type::Tagged, ResponseType::Ok).await; // Shared folder creation tests let mut imap_jane = test.account("jane.smith@example.com").imap_client().await; imap_jane .send("CREATE \"Shared Folders/support@example.com/INBOX/Test\"") .await; imap_jane.assert_read(Type::Tagged, ResponseType::Ok).await; imap_jane .send("CREATE \"Shared Folders/support@example.com/Test\"") .await; imap_jane.assert_read(Type::Tagged, ResponseType::Ok).await; imap_jane .send("CREATE \"Shared Folders/support@example.com/Test/TestSubfolder\"") .await; imap_jane.assert_read(Type::Tagged, ResponseType::Ok).await; imap_jane.send("LIST \"\" \"*\"").await; imap_jane .assert_read(Type::Tagged, ResponseType::Ok) .await .assert_folders( [ ("INBOX", [""]), ("Deleted Items", [""]), ("Drafts", [""]), ("Junk Mail", [""]), ("Sent Items", [""]), ("Shared Folders", [""]), ("Shared Folders/support@example.com", [""]), ("Shared Folders/support@example.com/Deleted Items", [""]), ("Shared Folders/support@example.com/Drafts", [""]), ("Shared Folders/support@example.com/INBOX", [""]), ("Shared Folders/support@example.com/INBOX/Test", [""]), ("Shared Folders/support@example.com/Junk Mail", [""]), ("Shared Folders/support@example.com/Sent Items", [""]), ("Shared Folders/support@example.com/Test", [""]), ( "Shared Folders/support@example.com/Test/TestSubfolder", [""], ), ], true, ); } fn mailbox_matches_pattern() { let mailboxes = [ "imaptest", "imaptest/test", "imaptest/test2", "imaptest/test3", "imaptest/test3/test4", "imaptest/test3/test4/test5", "foobar/test", "foobar/test/test", "foobar/test1/test1", ]; for (pattern, expected_match) in [ ( "imaptest/%", vec!["imaptest/test", "imaptest/test2", "imaptest/test3"], ), ("imaptest/%/%", vec!["imaptest/test3/test4"]), ( "imaptest/*", vec![ "imaptest/test", "imaptest/test2", "imaptest/test3", "imaptest/test3/test4", "imaptest/test3/test4/test5", ], ), ("imaptest/*test4", vec!["imaptest/test3/test4"]), ( "imaptest/*test*", vec![ "imaptest/test", "imaptest/test2", "imaptest/test3", "imaptest/test3/test4", "imaptest/test3/test4/test5", ], ), ("imaptest/%3/%", vec!["imaptest/test3/test4"]), ("imaptest/%3/%4", vec!["imaptest/test3/test4"]), ("imaptest/%t*4", vec!["imaptest/test3/test4"]), ("*st/%3/%4/%5", vec!["imaptest/test3/test4/test5"]), ( "*%*%*%", vec![ "imaptest", "imaptest/test", "imaptest/test2", "imaptest/test3", "imaptest/test3/test4", "imaptest/test3/test4/test5", "foobar/test", "foobar/test/test", "foobar/test1/test1", ], ), ("foobar*test", vec!["foobar/test", "foobar/test/test"]), ] { let patterns = vec![pattern.into()]; let mut matched_mailboxes = Vec::new(); for mailbox in mailboxes { if matches_pattern(&patterns, mailbox) { matched_mailboxes.push(mailbox); } } assert_eq!(matched_mailboxes, expected_match, "for pattern {}", pattern); } }