From 8f6ac2d114fe61bab7d108379251f095e1ba660e Mon Sep 17 00:00:00 2001 From: mdecimus Date: Mon, 27 Nov 2023 10:06:48 +0100 Subject: [PATCH] Improved storage of IMAP UIDs + Bump to hyper 1.0.1 --- Cargo.lock | 1074 ++++++++--------- crates/imap/Cargo.toml | 1 + crates/imap/src/core/client.rs | 4 +- crates/imap/src/core/mailbox.rs | 43 +- crates/imap/src/core/message.rs | 563 ++++----- crates/imap/src/core/mod.rs | 6 +- crates/imap/src/core/writer.rs | 16 +- crates/imap/src/lib.rs | 5 - crates/imap/src/op/acl.rs | 116 +- crates/imap/src/op/append.rs | 17 +- crates/imap/src/op/copy_move.rs | 64 +- crates/imap/src/op/create.rs | 15 +- crates/imap/src/op/delete.rs | 12 +- crates/imap/src/op/expunge.rs | 148 +-- crates/imap/src/op/fetch.rs | 2 +- crates/imap/src/op/list.rs | 12 - crates/imap/src/op/rename.rs | 16 +- crates/imap/src/op/search.rs | 30 +- crates/imap/src/op/select.rs | 2 +- crates/imap/src/op/status.rs | 206 ++-- crates/imap/src/op/store.rs | 269 +++-- crates/imap/src/op/subscribe.rs | 10 +- crates/jmap/Cargo.toml | 8 +- crates/jmap/src/blob/get.rs | 9 +- crates/jmap/src/email/copy.rs | 13 +- crates/jmap/src/email/get.rs | 9 +- crates/jmap/src/email/index.rs | 6 +- crates/jmap/src/email/ingest.rs | 7 +- crates/jmap/src/email/metadata.rs | 8 +- crates/jmap/src/email/set.rs | 37 +- crates/jmap/src/mailbox/mod.rs | 65 + crates/jmap/src/mailbox/set.rs | 39 +- crates/smtp/Cargo.toml | 8 +- crates/smtp/src/queue/quota.rs | 2 +- .../src/backend/foundationdb/id_assign.rs | 10 +- crates/store/src/backend/mod.rs | 2 + crates/store/src/fts/term_index.rs | 1000 --------------- resources/config/imap/settings.toml | 4 - tests/Cargo.toml | 6 +- tests/resources/imap/014.imap | 211 ++++ tests/resources/imap/014.txt | 31 + tests/src/imap/append.rs | 4 - tests/src/imap/copy_move.rs | 10 +- tests/src/imap/mailbox.rs | 21 +- tests/src/imap/mod.rs | 26 +- tests/src/imap/thread.rs | 14 +- tests/src/jmap/push_subscription.rs | 3 +- tests/src/jmap/stress_test.rs | 8 +- tests/src/smtp/management/mod.rs | 2 +- tests/src/smtp/management/queue.rs | 2 +- 50 files changed, 1632 insertions(+), 2564 deletions(-) delete mode 100644 crates/store/src/fts/term_index.rs create mode 100644 tests/resources/imap/014.imap create mode 100644 tests/resources/imap/014.txt diff --git a/Cargo.lock b/Cargo.lock index 5ef3c586..5fd6d148 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -36,6 +36,7 @@ dependencies = [ "cfg-if", "cipher 0.4.4", "cpufeatures", + "zeroize", ] [[package]] @@ -69,26 +70,27 @@ dependencies = [ [[package]] name = "ahash" -version = "0.7.6" +version = "0.7.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fcb51a0695d8f838b1ee009b3fbf66bda078cd64590202a864a8f3e8c4315c47" +checksum = "5a824f2aa7e75a0c98c5a504fceb80649e9c35265d44525b5f94de4771a395cd" dependencies = [ - "getrandom 0.2.10", + "getrandom", "once_cell", "version_check", ] [[package]] name = "ahash" -version = "0.8.3" +version = "0.8.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2c99f64d1e06488f620f932677e24bc6e2897582980441ae90a671415bd7ec2f" +checksum = "91429305e9f0a25f6205c5b8e0d2db09e0708a7a6df0f42212bb56c32c8ac97a" dependencies = [ "cfg-if", - "getrandom 0.2.10", + "getrandom", "once_cell", "serde", "version_check", + "zerocopy", ] [[package]] @@ -255,7 +257,7 @@ checksum = "5fd55a5ba1179988837d24ab4c7cc8ed6efdeff578ede0416b4225a5fca35bd0" dependencies = [ "proc-macro2", "quote", - "syn 2.0.38", + "syn 2.0.39", ] [[package]] @@ -277,7 +279,7 @@ checksum = "16e62a023e7c117e27523144c5d2459f4397fcc3cab0085af8e2224f643a0193" dependencies = [ "proc-macro2", "quote", - "syn 2.0.38", + "syn 2.0.39", ] [[package]] @@ -288,7 +290,7 @@ checksum = "a66537f1bb974b254c98ed142ff995236e81b9d0fe4db0575f46612cb15eb0f9" dependencies = [ "proc-macro2", "quote", - "syn 2.0.38", + "syn 2.0.39", ] [[package]] @@ -306,7 +308,7 @@ version = "0.22.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1fcf00bc6d5abb29b5f97e3c61a90b6d3caa12f3faf897d4a3e3607c050a35a7" dependencies = [ - "http", + "http 0.2.11", "log", "rustls 0.20.9", "serde", @@ -359,7 +361,7 @@ dependencies = [ "bitflags 1.3.2", "bytes", "futures-util", - "http", + "http 0.2.11", "http-body 0.4.5", "hyper 0.14.27", "itoa", @@ -385,7 +387,7 @@ dependencies = [ "async-trait", "bytes", "futures-util", - "http", + "http 0.2.11", "http-body 0.4.5", "mime", "rustversion", @@ -428,9 +430,9 @@ checksum = "0ea22880d78093b0cbe17c89f64a7d457941e65759157ec6cb31a31d652b05e5" [[package]] name = "base64" -version = "0.21.4" +version = "0.21.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9ba43ea6f343b788c8764558649e08df62f86c6ef251fdaeb1ffd010a9ae50a2" +checksum = "35636a1494ede3b646cc98f74f8e62c773a38a659ebc777a2cf26b9b74171df9" [[package]] name = "base64ct" @@ -499,7 +501,7 @@ dependencies = [ "regex", "rustc-hash", "shlex", - "syn 2.0.38", + "syn 2.0.39", "which", ] @@ -636,9 +638,9 @@ dependencies = [ [[package]] name = "buffered-reader" -version = "1.2.0" +version = "1.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "66d3bea5bcc3ecc38fe5388e6bc35e6fe7bd665eb3ae9a44283e15b91ad3867d" +checksum = "2b9b0a25eb06e83579bc985d836e1e3b957a7201301b48538764d2b2e78090d4" dependencies = [ "lazy_static", "libc", @@ -689,6 +691,16 @@ dependencies = [ "pkg-config", ] +[[package]] +name = "camellia" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3264e2574e9ef2b53ce6f536dea83a69ac0bc600b762d1523ff83fe07230ce30" +dependencies = [ + "byteorder", + "cipher 0.4.4", +] + [[package]] name = "cast5" version = "0.11.1" @@ -793,6 +805,7 @@ checksum = "773f3b9af64447d2ce9850330c473515014aa235e6a783b02db81ff39e4a3dad" dependencies = [ "crypto-common", "inout", + "zeroize", ] [[package]] @@ -808,9 +821,9 @@ dependencies = [ [[package]] name = "clap" -version = "4.4.6" +version = "4.4.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d04704f56c2cde07f43e8e2c154b43f216dc5c92fc98ada720177362f953b956" +checksum = "2275f18819641850fa26c89acc84d465c1bf91ce57bc2748b28c420473352f64" dependencies = [ "clap_builder", "clap_derive", @@ -818,9 +831,9 @@ dependencies = [ [[package]] name = "clap_builder" -version = "4.4.6" +version = "4.4.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0e231faeaca65ebd1ea3c737966bf858971cd38c3849107aa3ea7de90a804e45" +checksum = "07cdf1b148b25c1e1f7a42225e30a0d99a615cd4637eae7365548dd4529b95bc" dependencies = [ "anstream", "anstyle", @@ -830,21 +843,21 @@ dependencies = [ [[package]] name = "clap_derive" -version = "4.4.2" +version = "4.4.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0862016ff20d69b84ef8247369fabf5c008a7417002411897d40ee1f4532b873" +checksum = "cf9804afaaf59a91e75b022a30fb7229a7901f60c755489cc61c9b423b836442" dependencies = [ "heck", "proc-macro2", "quote", - "syn 2.0.38", + "syn 2.0.39", ] [[package]] name = "clap_lex" -version = "0.5.1" +version = "0.6.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cd7cc57abe963c6d3b9d8be5b06ba7c8957a930305ca90304f24ef040aa6f961" +checksum = "702fc72eb24e5a1e48ce58027a675bc24edd52096d5397d4aea7c6dd9eca0bd1" [[package]] name = "cmac" @@ -918,9 +931,9 @@ checksum = "e496a50fda8aacccc86d7529e2c1e0892dbd0f898a6b5645b5561b89c3210efa" [[package]] name = "cpufeatures" -version = "0.2.10" +version = "0.2.11" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3fbc60abd742b35f2492f808e1abbb83d45f72db402e14c55057edc9c7b1e9e4" +checksum = "ce420fe07aecd3e67c5f910618fe65e94158f6dcc0adf44e00d69ce2bdfe0fd0" dependencies = [ "libc", ] @@ -936,9 +949,9 @@ dependencies = [ [[package]] name = "crc-catalog" -version = "2.2.0" +version = "2.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9cace84e55f07e7301bae1c519df89cdad8cc3cd868413d3fdbdeca9ff3db484" +checksum = "19d374276b40fb8bbdee95aef7c7fa6b5316ec764510eb64b8dd0e2ed0d7e7f5" [[package]] name = "crc32fast" @@ -1010,12 +1023,12 @@ checksum = "7a81dae078cea95a014a339291cec439d2f232ebe854a9d672b796c6afafa9b7" [[package]] name = "crypto-bigint" -version = "0.5.3" +version = "0.5.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "740fe28e594155f10cfc383984cbefd529d7396050557148f79cb0f621204124" +checksum = "0dc92fb57ca44df6db8059111ab3af99a63d5d0f8375d9972e319a379c6bab76" dependencies = [ "generic-array", - "rand_core 0.6.4", + "rand_core", "subtle", "zeroize", ] @@ -1027,7 +1040,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1bfb12502f3fc46cca1bb51ac28df9d618d813cdc3d2f25b9fe775a34af26bb3" dependencies = [ "generic-array", - "rand_core 0.6.4", + "rand_core", "typenum", ] @@ -1073,28 +1086,30 @@ dependencies = [ [[package]] name = "curve25519-dalek" -version = "3.2.0" +version = "4.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0b9fdf9972b2bd6af2d913799d9ebc165ea4d2e65878e329d9c6b372c4491b61" +checksum = "e89b8c6a2e4b1f45971ad09761aafb85514a84744b67a95e32c3cc1352d1f65c" dependencies = [ - "byteorder", - "digest 0.9.0", - "rand_core 0.5.1", + "cfg-if", + "cpufeatures", + "curve25519-dalek-derive", + "digest 0.10.7", + "fiat-crypto", + "platforms", + "rustc_version", "subtle", "zeroize", ] [[package]] -name = "curve25519-dalek-ng" -version = "4.1.1" +name = "curve25519-dalek-derive" +version = "0.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1c359b7249347e46fb28804470d071c921156ad62b3eef5d34e2ba867533dec8" +checksum = "f46882e17999c6cc590af592290432be3bce0428cb0d5f8b6715e4dc7b383eb3" dependencies = [ - "byteorder", - "digest 0.9.0", - "rand_core 0.6.4", - "subtle-ng", - "zeroize", + "proc-macro2", + "quote", + "syn 2.0.39", ] [[package]] @@ -1104,7 +1119,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "978747c1d849a7d2ee5e8adc0159961c48fb7e5db2f06af6723b80123bb53856" dependencies = [ "cfg-if", - "hashbrown 0.14.2", + "hashbrown 0.14.3", "lock_api", "once_cell", "parking_lot_core", @@ -1112,9 +1127,9 @@ dependencies = [ [[package]] name = "data-encoding" -version = "2.4.0" +version = "2.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c2e66c9d817f1720209181c316d28635c050fa304f9c79e47a520882661b7308" +checksum = "7e962a19be5cfc3f3bf6dd8f61eb50107f356ad6270fbb3ed41476571db78be5" [[package]] name = "dbl" @@ -1219,7 +1234,7 @@ dependencies = [ name = "directory" version = "0.1.0" dependencies = [ - "ahash 0.8.3", + "ahash 0.8.6", "argon2", "async-trait", "bb8", @@ -1237,7 +1252,7 @@ dependencies = [ "pwhash", "regex", "reqwest", - "rustls 0.21.7", + "rustls 0.21.9", "scrypt", "sha1", "sha2 0.10.8", @@ -1299,7 +1314,7 @@ checksum = "487585f4d0c6655fe74905e2504d8ad6908e4db67f744eb140876906c2f3175d" dependencies = [ "proc-macro2", "quote", - "syn 2.0.38", + "syn 2.0.39", ] [[package]] @@ -1321,10 +1336,26 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1aaf95b3e5c8f23aa320147307562d361db0ae0d51242340f558153b4eb2439b" [[package]] -name = "dyn-clone" -version = "1.0.14" +name = "dsa" +version = "0.6.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "23d2f3407d9a573d666de4b5bdf10569d73ca9478087346697dcbae6244bfbcd" +checksum = "d88afbb2443ba68ef8593de497e830b2e276434e1408f85cd760b1107b44ead0" +dependencies = [ + "digest 0.10.7", + "num-bigint-dig", + "num-traits", + "pkcs8", + "rfc6979", + "sha2 0.10.8", + "signature", + "zeroize", +] + +[[package]] +name = "dyn-clone" +version = "1.0.16" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "545b22097d44f8a9581187cdf93de7a71e4722bf51200cfaba810865b49a495d" [[package]] name = "eax" @@ -1350,15 +1381,15 @@ dependencies = [ [[package]] name = "ecdsa" -version = "0.16.8" +version = "0.16.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a4b1e0c257a9e9f25f90ff76d7a68360ed497ee519c8e428d1825ef0000799d4" +checksum = "ee27f32b5c5292967d2d4a9d7f1e0b0aed2c15daded5a60300e4abb9d8020bca" dependencies = [ "der", "digest 0.10.7", "elliptic-curve", "rfc6979", - "signature 2.1.0", + "signature", "spki", ] @@ -1382,23 +1413,26 @@ dependencies = [ [[package]] name = "ed25519" -version = "1.5.3" +version = "2.2.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "91cff35c70bba8a626e3185d8cd48cc11b5437e1a5bcd15b9b5fa3c64b6dfee7" +checksum = "115531babc129696a58c64a4fef0a8bf9e9698629fb97e9e40767d235cfbcd53" dependencies = [ - "signature 1.6.4", + "pkcs8", + "signature", ] [[package]] name = "ed25519-dalek" -version = "1.0.1" +version = "2.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c762bae6dcaf24c4c84667b8579785430908723d5c889f469d76a41d59cc7a9d" +checksum = "1f628eaec48bfd21b865dc2950cfa014450c01d2fa2b69a86c2fd5844ec523c0" dependencies = [ "curve25519-dalek", "ed25519", - "rand 0.7.3", - "sha2 0.9.9", + "rand_core", + "serde", + "sha2 0.10.8", + "subtle", "zeroize", ] @@ -1413,9 +1447,9 @@ dependencies = [ [[package]] name = "elliptic-curve" -version = "0.13.6" +version = "0.13.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d97ca172ae9dc9f9b779a6e3a65d308f2af74e5b8c921299075bdb4a0370e914" +checksum = "b5e6043086bf7973472e0c7dff2142ea0b680d30e18d9cc40f267efbf222bd47" dependencies = [ "base16ct", "crypto-bigint", @@ -1426,7 +1460,7 @@ dependencies = [ "hkdf", "pem-rfc7468", "pkcs8", - "rand_core 0.6.4", + "rand_core", "sec1", "subtle", "zeroize", @@ -1471,7 +1505,7 @@ dependencies = [ "heck", "proc-macro2", "quote", - "syn 2.0.38", + "syn 2.0.39", ] [[package]] @@ -1482,9 +1516,9 @@ checksum = "5443807d6dff69373d433ab9ef5378ad8df50ca6298caf15de6e52e24aaf54d5" [[package]] name = "errno" -version = "0.3.5" +version = "0.3.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ac3e13f66a2f95e32a39eaa81f6b95d42878ca0e1db0c7543723dfe12557e860" +checksum = "f258a7194e7f7c2a7837a8913aeab7fd8c383457034fa20ce4dd3dcb813e8eb8" dependencies = [ "libc", "windows-sys 0.48.0", @@ -1553,10 +1587,16 @@ version = "0.13.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ded41244b729663b1e574f1b4fb731469f69f79c17667b5d776b16cda0479449" dependencies = [ - "rand_core 0.6.4", + "rand_core", "subtle", ] +[[package]] +name = "fiat-crypto" +version = "0.2.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "27573eac26f4dd11e2b1916c3fe1baa56407c83c71a773a8ba17ec0bca03b6b7" + [[package]] name = "filetime" version = "0.2.22" @@ -1626,12 +1666,12 @@ checksum = "00b0228411908ca8685dba7fc2cdd70ec9990a6e753e89b6ac91a84c40fbaf4b" [[package]] name = "form-data" -version = "0.4.2" +version = "0.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0322eefcd026820bbb430b427f0e9b22fc3952f163df23b74a3c5639bae32f0f" +checksum = "c52dc5f41ce1da428247d7bf7697ee3d6ec09d5704dfd4797f4b94e74ef6972f" dependencies = [ "bytes", - "http", + "http 1.0.0", "httparse", "memchr", "mime", @@ -1642,9 +1682,9 @@ dependencies = [ [[package]] name = "form_urlencoded" -version = "1.2.0" +version = "1.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a62bc1cf6f830c2ec14a513a9fb124d0a213a629668a4186f329db21fe045652" +checksum = "e13624c2627564efccf4934284bdd98cbaa14e79b0b5a141218e507b3a823456" dependencies = [ "percent-encoding", ] @@ -1662,7 +1702,7 @@ dependencies = [ "foundationdb-sys", "futures", "memchr", - "rand 0.8.5", + "rand", "serde", "serde_bytes", "serde_json", @@ -1687,7 +1727,7 @@ checksum = "83c8d52fe8b46ab822b4decdcc0d6d85aeedfc98f0d52ba2bd4aec4a97807516" dependencies = [ "proc-macro2", "quote", - "syn 2.0.38", + "syn 2.0.39", "try_map", ] @@ -1708,9 +1748,9 @@ checksum = "e6d5a32815ae3f33302d95fdcb2ce17862f8c65363dcfd29360480ba1001fc9c" [[package]] name = "futures" -version = "0.3.28" +version = "0.3.29" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "23342abe12aba583913b2e62f22225ff9c950774065e4bfb61a19cd9770fec40" +checksum = "da0290714b38af9b4a7b094b8a37086d1b4e61f2df9122c3cad2577669145335" dependencies = [ "futures-channel", "futures-core", @@ -1723,9 +1763,9 @@ dependencies = [ [[package]] name = "futures-channel" -version = "0.3.28" +version = "0.3.29" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "955518d47e09b25bbebc7a18df10b81f0c766eaf4c4f1cccef2fca5f2a4fb5f2" +checksum = "ff4dd66668b557604244583e3e1e1eada8c5c2e96a6d0d6653ede395b78bbacb" dependencies = [ "futures-core", "futures-sink", @@ -1733,15 +1773,15 @@ dependencies = [ [[package]] name = "futures-core" -version = "0.3.28" +version = "0.3.29" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4bca583b7e26f571124fe5b7561d49cb2868d79116cfa0eefce955557c6fee8c" +checksum = "eb1d22c66e66d9d72e1758f0bd7d4fd0bee04cad842ee34587d68c07e45d088c" [[package]] name = "futures-executor" -version = "0.3.28" +version = "0.3.29" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ccecee823288125bd88b4d7f565c9e58e41858e47ab72e8ea2d64e93624386e0" +checksum = "0f4fb8693db0cf099eadcca0efe2a5a22e4550f98ed16aba6c48700da29597bc" dependencies = [ "futures-core", "futures-task", @@ -1761,38 +1801,38 @@ dependencies = [ [[package]] name = "futures-io" -version = "0.3.28" +version = "0.3.29" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4fff74096e71ed47f8e023204cfd0aa1289cd54ae5430a9523be060cdb849964" +checksum = "8bf34a163b5c4c52d0478a4d757da8fb65cabef42ba90515efee0f6f9fa45aaa" [[package]] name = "futures-macro" -version = "0.3.28" +version = "0.3.29" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "89ca545a94061b6365f2c7355b4b32bd20df3ff95f02da9329b34ccc3bd6ee72" +checksum = "53b153fd91e4b0147f4aced87be237c98248656bb01050b96bf3ee89220a8ddb" dependencies = [ "proc-macro2", "quote", - "syn 2.0.38", + "syn 2.0.39", ] [[package]] name = "futures-sink" -version = "0.3.28" +version = "0.3.29" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f43be4fe21a13b9781a69afa4985b0f6ee0e1afab2c6f454a8cf30e2b2237b6e" +checksum = "e36d3378ee38c2a36ad710c5d30c2911d752cb941c00c72dbabfb786a7970817" [[package]] name = "futures-task" -version = "0.3.28" +version = "0.3.29" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "76d3d132be6c0e6aa1534069c705a74a5997a356c0dc2f86a47765e5617c5b65" +checksum = "efd193069b0ddadc69c46389b740bbccdd97203899b48d09c5f7969591d6bae2" [[package]] name = "futures-util" -version = "0.3.28" +version = "0.3.29" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "26b01e40b772d54cf6c6d721c1d1abd0647a0106a12ecaa1c186273392a69533" +checksum = "a19526d624e703a3179b3d322efec918b6246ea0fa51d41124525f00f1cc8104" dependencies = [ "futures-channel", "futures-core", @@ -1838,27 +1878,14 @@ dependencies = [ [[package]] name = "getrandom" -version = "0.1.16" +version = "0.2.11" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8fc3cb4d91f53b50155bdcfd23f6a4c39ae1969c2ae85982b135750cccaf5fce" +checksum = "fe9006bed769170c11f845cf00c7c1e9092aeb3f268e007c3e760ac68008070f" dependencies = [ "cfg-if", "js-sys", "libc", - "wasi 0.9.0+wasi-snapshot-preview1", - "wasm-bindgen", -] - -[[package]] -name = "getrandom" -version = "0.2.10" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "be4136b2a15dd319360be1c07d9933517ccf0be8f16bf62a3bee4f0d618df427" -dependencies = [ - "cfg-if", - "js-sys", - "libc", - "wasi 0.11.0+wasi-snapshot-preview1", + "wasi", "wasm-bindgen", ] @@ -1874,9 +1901,9 @@ dependencies = [ [[package]] name = "gimli" -version = "0.28.0" +version = "0.28.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6fb8d784f27acf97159b40fc4db5ecd8aa23b9ad5ef69cdd136d3bc80665f0c0" +checksum = "4271d37baee1b8c7e4b708028c57d816cf9d2434acb33a549475f78c181f6253" [[package]] name = "glob" @@ -1891,23 +1918,42 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f0f9ef7462f7c099f518d754361858f86d8a07af53ba9af0fe635bbccb151a63" dependencies = [ "ff", - "rand_core 0.6.4", + "rand_core", "subtle", ] [[package]] name = "h2" -version = "0.3.21" +version = "0.3.22" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "91fc23aa11be92976ef4729127f1a74adf36d8436f7816b185d18df956790833" +checksum = "4d6250322ef6e60f93f9a2162799302cd6f68f79f6e5d85c8c16f14d1d958178" dependencies = [ "bytes", "fnv", "futures-core", "futures-sink", "futures-util", - "http", - "indexmap 1.9.3", + "http 0.2.11", + "indexmap 2.1.0", + "slab", + "tokio", + "tokio-util", + "tracing", +] + +[[package]] +name = "h2" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e1d308f63daf4181410c242d34c11f928dcb3aa105852019e043c9d1f4e4368a" +dependencies = [ + "bytes", + "fnv", + "futures-core", + "futures-sink", + "futures-util", + "http 1.0.0", + "indexmap 2.1.0", "slab", "tokio", "tokio-util", @@ -1920,16 +1966,16 @@ version = "0.12.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8a9ee70c43aaf417c914396645a0fa852624801b24ebb7ae78fe8272889ac888" dependencies = [ - "ahash 0.7.6", + "ahash 0.7.7", ] [[package]] name = "hashbrown" -version = "0.14.2" +version = "0.14.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f93e7192158dbcda357bdec5fb5788eebf8bbac027f3f33e719d29135ae84156" +checksum = "290f1a1d9242c78d09ce40a5e87e7554ee637af1351968159f4952f028f75604" dependencies = [ - "ahash 0.8.3", + "ahash 0.8.6", "allocator-api2", ] @@ -1939,7 +1985,7 @@ version = "0.8.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e8094feaf31ff591f651a2664fb9cfd92bba7a60ce3197265e9482ebe753c8f7" dependencies = [ - "hashbrown 0.14.2", + "hashbrown 0.14.3", ] [[package]] @@ -1979,9 +2025,9 @@ dependencies = [ "idna 0.4.0", "ipnet", "once_cell", - "rand 0.8.5", + "rand", "ring 0.16.20", - "rustls 0.21.7", + "rustls 0.21.9", "rustls-pemfile", "thiserror", "tinyvec", @@ -2004,9 +2050,9 @@ dependencies = [ "lru-cache", "once_cell", "parking_lot", - "rand 0.8.5", + "rand", "resolv-conf", - "rustls 0.21.7", + "rustls 0.21.9", "smallvec", "thiserror", "tokio", @@ -2064,9 +2110,20 @@ dependencies = [ [[package]] name = "http" -version = "0.2.9" +version = "0.2.11" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bd6effc99afb63425aff9b05836f029929e345a6148a14b7ecd5ab67af944482" +checksum = "8947b1a6fad4393052c7ba1f4cd97bed3e953a95c79c92ad9b051a04611d9fbb" +dependencies = [ + "bytes", + "fnv", + "itoa", +] + +[[package]] +name = "http" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b32afd38673a8016f7c9ae69e5af41a58f81b1d31689040f2f1959594ce194ea" dependencies = [ "bytes", "fnv", @@ -2080,30 +2137,30 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d5f38f16d184e36f2408a55281cd658ecbd3ca05cce6d6510a176eca393e26d1" dependencies = [ "bytes", - "http", + "http 0.2.11", "pin-project-lite", ] [[package]] name = "http-body" -version = "1.0.0-rc.2" +version = "1.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "951dfc2e32ac02d67c90c0d65bd27009a635dc9b381a2cc7d284ab01e3a0150d" +checksum = "1cac85db508abc24a2e48553ba12a996e87244a0395ce011e62b37158745d643" dependencies = [ "bytes", - "http", + "http 1.0.0", ] [[package]] name = "http-body-util" -version = "0.1.0-rc.3" +version = "0.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "08ef12f041acdd397010e5fb6433270c147d3b8b2d0a840cd7fff8e531dca5c8" +checksum = "41cb79eb393015dadd30fc252023adb0b2400a0caee0fa2a077e6e21a551e840" dependencies = [ "bytes", "futures-util", - "http", - "http-body 1.0.0-rc.2", + "http 1.0.0", + "http-body 1.0.0", "pin-project-lite", ] @@ -2135,8 +2192,8 @@ dependencies = [ "futures-channel", "futures-core", "futures-util", - "h2", - "http", + "h2 0.3.22", + "http 0.2.11", "http-body 0.4.5", "httparse", "httpdate", @@ -2151,35 +2208,33 @@ dependencies = [ [[package]] name = "hyper" -version = "1.0.0-rc.4" +version = "1.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d280a71f348bcc670fc55b02b63c53a04ac0bf2daff2980795aeaf53edae10e6" +checksum = "403f9214f3e703236b221f1a9cd88ec8b4adfa5296de01ab96216361f4692f56" dependencies = [ "bytes", "futures-channel", "futures-util", - "h2", - "http", - "http-body 1.0.0-rc.2", + "h2 0.4.0", + "http 1.0.0", + "http-body 1.0.0", "httparse", "httpdate", "itoa", "pin-project-lite", "tokio", - "tracing", - "want", ] [[package]] name = "hyper-rustls" -version = "0.24.1" +version = "0.24.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8d78e1e73ec14cf7375674f74d7dde185c8206fd9dea6fb6295e8a98098aaa97" +checksum = "ec3efd23720e2049821a693cbc7e65ea87c72f1c58ff2f9522ff332b1491e590" dependencies = [ "futures-util", - "http", + "http 0.2.11", "hyper 0.14.27", - "rustls 0.21.7", + "rustls 0.21.9", "tokio", "tokio-rustls", ] @@ -2198,16 +2253,16 @@ dependencies = [ [[package]] name = "hyper-util" -version = "0.0.0" -source = "git+https://github.com/hyperium/hyper-util#1ed4c2ccdb23f576eb7024555f08b376b9d5c9eb" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9ca339002caeb0d159cc6e023dff48e199f081e42fa039895c7c6f38b37f2e9d" dependencies = [ "bytes", "futures-channel", "futures-util", - "http", - "http-body 1.0.0-rc.2", - "hyper 1.0.0-rc.4", - "once_cell", + "http 1.0.0", + "http-body 1.0.0", + "hyper 1.0.1", "pin-project-lite", "socket2 0.5.5", "tokio", @@ -2250,9 +2305,9 @@ dependencies = [ [[package]] name = "idna" -version = "0.3.0" +version = "0.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e14ddfc70884202db2244c223200c204c2bda1bc6e0998d11b5e024d657209e6" +checksum = "7d20d6b07bfbc108882d88ed8e37d39636dcc260e15e30c45e6ba089610b917c" dependencies = [ "unicode-bidi", "unicode-normalization", @@ -2260,9 +2315,9 @@ dependencies = [ [[package]] name = "idna" -version = "0.4.0" +version = "0.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7d20d6b07bfbc108882d88ed8e37d39636dcc260e15e30c45e6ba089610b917c" +checksum = "634d9b1461af396cad843f47fdba5597a4f9e6ddd4bfb6ff5d85028c25cb12f6" dependencies = [ "unicode-bidi", "unicode-normalization", @@ -2278,7 +2333,7 @@ checksum = "029d73f573d8e8d63e6d5020011d3255b28c3ba85d6cf870a07184ed23de9284" name = "imap" version = "0.4.2" dependencies = [ - "ahash 0.8.3", + "ahash 0.8.6", "dashmap", "directory", "imap_proto", @@ -2289,7 +2344,8 @@ dependencies = [ "md5", "nlp", "parking_lot", - "rustls 0.21.7", + "rand", + "rustls 0.21.9", "rustls-pemfile", "store", "tokio", @@ -2302,7 +2358,7 @@ dependencies = [ name = "imap_proto" version = "0.1.0" dependencies = [ - "ahash 0.8.3", + "ahash 0.8.6", "chrono", "jmap_proto", "mail-parser", @@ -2322,12 +2378,12 @@ dependencies = [ [[package]] name = "indexmap" -version = "2.0.2" +version = "2.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8adf3ddd720272c6ea8bf59463c04e0f93d0bbf7c5439b691bca2987e0270897" +checksum = "d530e1a18b1cb4c484e6e34556a0d948706958449fca0cab753d649f2bce3d1f" dependencies = [ "equivalent", - "hashbrown 0.14.2", + "hashbrown 0.14.3", ] [[package]] @@ -2385,9 +2441,9 @@ dependencies = [ [[package]] name = "ipnet" -version = "2.8.0" +version = "2.9.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "28b29a3cd74f0f4598934efe3aeba42bae0eb4680554128851ebbecb02af14e6" +checksum = "8f518f335dce6725a761382244631d86cf0ccb2863413590b31338feb467f9c3" [[package]] name = "is-terminal" @@ -2452,7 +2508,7 @@ checksum = "93f0c1347cd3ac8d7c6e3a2dc33ac496d365cf09fc0831aa61111e1a6738983e" dependencies = [ "cedarwood", "fxhash", - "hashbrown 0.14.2", + "hashbrown 0.14.3", "lazy_static", "phf", "phf_codegen", @@ -2468,7 +2524,7 @@ dependencies = [ "aes-gcm-siv", "async-stream", "async-trait", - "base64 0.21.4", + "base64 0.21.5", "bincode", "cbc", "chrono", @@ -2480,7 +2536,7 @@ dependencies = [ "futures-util", "hkdf", "http-body-util", - "hyper 1.0.0-rc.4", + "hyper 1.0.1", "hyper-util", "jmap_proto", "lz4_flex", @@ -2490,7 +2546,7 @@ dependencies = [ "mime", "nlp", "p256", - "rand 0.8.5", + "rand", "rasn", "rasn-cms", "rasn-pkix", @@ -2507,18 +2563,18 @@ dependencies = [ "sqlx", "store", "tokio", - "tokio-tungstenite 0.20.1", + "tokio-tungstenite", "tracing", - "tungstenite 0.20.1", + "tungstenite", "utils", ] [[package]] name = "jmap-client" version = "0.3.0" -source = "git+https://github.com/stalwartlabs/jmap-client#5b6595e770c75c3a5b2ae2e90ee18e35d6c6b5cf" +source = "git+https://github.com/stalwartlabs/jmap-client#1e47fe8d08e10fc953c26555a24f2197cb7264e2" dependencies = [ - "ahash 0.8.3", + "ahash 0.8.6", "async-stream", "base64 0.13.1", "chrono", @@ -2526,18 +2582,18 @@ dependencies = [ "maybe-async", "parking_lot", "reqwest", - "rustls 0.21.7", + "rustls 0.21.9", "serde", "serde_json", "tokio", - "tokio-tungstenite 0.19.0", + "tokio-tungstenite", ] [[package]] name = "jmap_proto" version = "0.1.0" dependencies = [ - "ahash 0.8.3", + "ahash 0.8.6", "fast-float", "mail-parser", "serde", @@ -2559,9 +2615,9 @@ dependencies = [ [[package]] name = "js-sys" -version = "0.3.64" +version = "0.3.65" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c5f195fe497f702db0f318b07fdd68edb16955aed830df8363d837542f8f935a" +checksum = "54c0c35952f67de54bb584e9fd912b3023117cbafc0a77d8f3dee1fb5f572fe8" dependencies = [ "wasm-bindgen", ] @@ -2588,9 +2644,9 @@ dependencies = [ [[package]] name = "lalrpop" -version = "0.19.12" +version = "0.20.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0a1cbf952127589f2851ab2046af368fd20645491bb4b376f04b7f94d7a9837b" +checksum = "da4081d44f4611b66c6dd725e6de3169f9f63905421e8626fcb86b6a898998b8" dependencies = [ "ascii-canvas", "bit-set", @@ -2601,7 +2657,7 @@ dependencies = [ "lalrpop-util", "petgraph", "regex", - "regex-syntax 0.6.29", + "regex-syntax 0.7.5", "string_cache", "term", "tiny-keccak", @@ -2610,9 +2666,9 @@ dependencies = [ [[package]] name = "lalrpop-util" -version = "0.19.12" +version = "0.20.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d3c48237b9604c5a4702de6b824e02006c3214327564636aef27c1028a8fa0ed" +checksum = "3f35c735096c0293d313e8f2a641627472b83d01b937177fe76e5e2708d31e0d" [[package]] name = "lazy_static" @@ -2655,7 +2711,7 @@ dependencies = [ "nom", "percent-encoding", "ring 0.16.20", - "rustls 0.21.7", + "rustls 0.21.9", "rustls-native-certs", "thiserror", "tokio", @@ -2668,9 +2724,9 @@ dependencies = [ [[package]] name = "libc" -version = "0.2.149" +version = "0.2.150" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a08173bc88b7955d1b3145aa561539096c421ac8debde8cbc3612ec635fee29b" +checksum = "89d92a4743f9a61002fae18374ed11e7973f530cb3a3255fb354818118b2203c" [[package]] name = "libloading" @@ -2688,6 +2744,17 @@ version = "0.2.8" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "4ec2a862134d2a7d32d7983ddcdd1c4923530833c9f2ea1a44fc5fa473989058" +[[package]] +name = "libredox" +version = "0.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "85c833ca1e66078851dba29046874e38f08b2c883700aa29a03ddd3b23814ee8" +dependencies = [ + "bitflags 2.4.1", + "libc", + "redox_syscall 0.4.1", +] + [[package]] name = "librocksdb-sys" version = "0.10.0+7.9.2" @@ -2734,9 +2801,9 @@ checksum = "0717cef1bc8b636c6e1c1bbdefc09e6322da8a9321966e8928ef80d20f7f770f" [[package]] name = "linux-raw-sys" -version = "0.4.10" +version = "0.4.11" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "da2479e8c062e40bf0066ffa0bc823de0a9368974af99c9f6df941d2c231e03f" +checksum = "969488b55f8ac402214f3f5fd243ebb7206cf82de60d3172994707a4bcc2b829" [[package]] name = "lock_api" @@ -2787,7 +2854,7 @@ name = "mail-auth" version = "0.3.6" source = "git+https://github.com/stalwartlabs/mail-auth#8a1b216adc60b95192645033aed6d20fa89821cd" dependencies = [ - "ahash 0.8.3", + "ahash 0.8.6", "flate2", "hickory-resolver", "lru-cache", @@ -2812,8 +2879,8 @@ dependencies = [ [[package]] name = "mail-parser" -version = "0.9.1" -source = "git+https://github.com/stalwartlabs/mail-parser#b6c080ff45c0de95703a4fadb6a41dab49cd1566" +version = "0.9.2" +source = "git+https://github.com/stalwartlabs/mail-parser#65533d3faf6f147fa288fa30eae0bb4df80b2948" dependencies = [ "encoding_rs", "serde", @@ -2827,11 +2894,11 @@ dependencies = [ "base64 0.20.0", "gethostname", "md5", - "rustls 0.21.7", + "rustls 0.21.9", "smtp-proto", "tokio", "tokio-rustls", - "webpki-roots 0.25.2", + "webpki-roots 0.25.3", ] [[package]] @@ -2855,7 +2922,7 @@ dependencies = [ name = "managesieve" version = "0.4.2" dependencies = [ - "ahash 0.8.3", + "ahash 0.8.6", "bincode", "directory", "imap", @@ -2866,7 +2933,7 @@ dependencies = [ "mail-send", "md5", "parking_lot", - "rustls 0.21.7", + "rustls 0.21.9", "rustls-pemfile", "sieve-rs", "store", @@ -3004,12 +3071,12 @@ dependencies = [ [[package]] name = "mio" -version = "0.8.8" +version = "0.8.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "927a765cd3fc26206e66b296465fa9d3e5ab003e651c1b3c060e7956d96b19d2" +checksum = "3dce281c5e46beae905d4de1870d8b1509a9142b62eedf18b443b011ca8343d0" dependencies = [ "libc", - "wasi 0.11.0+wasi-snapshot-preview1", + "wasi", "windows-sys 0.48.0", ] @@ -3036,7 +3103,7 @@ dependencies = [ name = "nlp" version = "0.4.2" dependencies = [ - "ahash 0.8.3", + "ahash 0.8.6", "bincode", "farmhash", "jieba-rs", @@ -3104,7 +3171,7 @@ dependencies = [ "num-integer", "num-iter", "num-traits", - "rand 0.8.5", + "rand", "smallvec", "zeroize", ] @@ -3188,9 +3255,9 @@ checksum = "624a8340c38c1b80fd549087862da4ba43e08858af025b236e509b6649fc13d5" [[package]] name = "openssl" -version = "0.10.57" +version = "0.10.60" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bac25ee399abb46215765b1cb35bc0212377e58a061560d8b29b024fd0430e7c" +checksum = "79a4c6c3a2b158f7f8f2a2fc5a969fa3a068df6fc9dbb4a43845436e3af7c800" dependencies = [ "bitflags 2.4.1", "cfg-if", @@ -3209,7 +3276,7 @@ checksum = "a948666b637a0f465e8564c73e89d4dde00d72d4d473cc972f390fc3dcee7d9c" dependencies = [ "proc-macro2", "quote", - "syn 2.0.38", + "syn 2.0.39", ] [[package]] @@ -3220,18 +3287,18 @@ checksum = "ff011a302c396a5197692431fc1948019154afc178baf7d8e37367442a4601cf" [[package]] name = "openssl-src" -version = "300.1.5+3.1.3" +version = "300.1.6+3.1.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "559068e4c12950d7dcaa1857a61725c0d38d4fc03ff8e070ab31a75d6e316491" +checksum = "439fac53e092cd7442a3660c85dde4643ab3b5bd39040912388dcdabf6b88085" dependencies = [ "cc", ] [[package]] name = "openssl-sys" -version = "0.9.93" +version = "0.9.96" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "db4d56a4c0478783083cfafcc42493dd4a981d41669da64b4572a2a089b51b1d" +checksum = "3812c071ba60da8b5677cc12bcb1d42989a65553772897a7e0355545a819838f" dependencies = [ "cc", "libc", @@ -3258,7 +3325,7 @@ checksum = "c7594ec0e11d8e33faf03530a4c49af7064ebba81c1480e01be67d90b356508b" dependencies = [ "async-trait", "bytes", - "http", + "http 0.2.11", "opentelemetry_api", "reqwest", ] @@ -3271,7 +3338,7 @@ checksum = "7e5e5a5c4135864099f3faafbe939eb4d7f9b80ebf68a8448da961b32a7c1275" dependencies = [ "async-trait", "futures-core", - "http", + "http 0.2.11", "opentelemetry-http", "opentelemetry-proto", "opentelemetry-semantic-conventions", @@ -3336,7 +3403,7 @@ dependencies = [ "opentelemetry_api", "ordered-float", "percent-encoding", - "rand 0.8.5", + "rand", "regex", "serde_json", "thiserror", @@ -3411,7 +3478,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7676374caaee8a325c9e7a2ae557f216c5563a171d6997b0ef8a65af35147700" dependencies = [ "base64ct", - "rand_core 0.6.4", + "rand_core", "subtle", ] @@ -3422,7 +3489,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "346f04948ba92c43e8469c1ee6736c7563d71012b17d40745260fe106aac2166" dependencies = [ "base64ct", - "rand_core 0.6.4", + "rand_core", "subtle", ] @@ -3473,9 +3540,9 @@ dependencies = [ [[package]] name = "percent-encoding" -version = "2.3.0" +version = "2.3.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9b2a4787296e9989611394c33f193f676704af1686e70b8f8033ab5ba9a35a94" +checksum = "e3148f5046208a5d56bcfc03053e3ca6334e51da8dfb19b6cdc8b306fae3283e" [[package]] name = "petgraph" @@ -3484,7 +3551,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e1d3afd2628e69da2be385eb6f2fd57c8ac7977ceeff6dc166ff1657b0e386a9" dependencies = [ "fixedbitset", - "indexmap 2.0.2", + "indexmap 2.1.0", ] [[package]] @@ -3514,7 +3581,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "48e4cc64c2ad9ebe670cb8fd69dd50ae301650392e81c05f9bfcb2d5bdbc24b0" dependencies = [ "phf_shared 0.11.2", - "rand 0.8.5", + "rand", ] [[package]] @@ -3527,7 +3594,7 @@ dependencies = [ "phf_shared 0.11.2", "proc-macro2", "quote", - "syn 2.0.38", + "syn 2.0.39", ] [[package]] @@ -3565,7 +3632,7 @@ checksum = "4359fd9c9171ec6e8c62926d6faaf553a8dc3f64e1507e76da7911b4f6a04405" dependencies = [ "proc-macro2", "quote", - "syn 2.0.38", + "syn 2.0.39", ] [[package]] @@ -3607,6 +3674,12 @@ version = "0.3.27" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "26072860ba924cbfa98ea39c8c19b4dd6a4a25423dbdf219c1eca91aa0cf6964" +[[package]] +name = "platforms" +version = "3.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "14e6ab3f592e6fb464fc9712d8d6e6912de6473954635fd76a589d832cffcbb0" + [[package]] name = "polyval" version = "0.6.1" @@ -3621,9 +3694,9 @@ dependencies = [ [[package]] name = "portable-atomic" -version = "1.4.3" +version = "1.5.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "31114a898e107c51bb1609ffaf55a0e011cf6a4d7f1170d0015a165082c0338b" +checksum = "3bccab0e7fd7cc19f820a1c8c91720af652d0c88dc9664dd72aef2614f04af3b" [[package]] name = "powerfmt" @@ -3650,7 +3723,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ae005bd773ab59b4725093fd7df83fd7892f7d8eafb48dbd7de6e024e4215f9d" dependencies = [ "proc-macro2", - "syn 2.0.38", + "syn 2.0.39", ] [[package]] @@ -3669,9 +3742,9 @@ dependencies = [ [[package]] name = "primeorder" -version = "0.13.2" +version = "0.13.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3c2fcef82c0ec6eefcc179b978446c399b3cdf73c392c35604e399eee6df1ee3" +checksum = "353e1ca18966c16d9deb1c69278edbc5f194139612772bd9537af60ac231e1e6" dependencies = [ "elliptic-curve", ] @@ -3688,9 +3761,9 @@ dependencies = [ [[package]] name = "proc-macro2" -version = "1.0.69" +version = "1.0.70" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "134c189feb4956b20f6f547d2cf727d4c0fe06722b20a0eec87ed445a97f92da" +checksum = "39278fbbf5fb4f646ce651690877f89d1c5811a3d4acb27700c1cb3cdb78fd3b" dependencies = [ "unicode-ident", ] @@ -3728,8 +3801,8 @@ dependencies = [ "byteorder", "hmac 0.10.1", "md-5 0.9.1", - "rand 0.8.5", - "sha-1 0.9.8", + "rand", + "sha-1", "sha2 0.9.9", ] @@ -3784,19 +3857,6 @@ version = "0.7.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "dc33ff2d4973d518d823d61aa239014831e521c75da58e3df4840d3f47749d09" -[[package]] -name = "rand" -version = "0.7.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6a6b1679d49b24bbfe0c803429aa1874472f50d9b363131f0e89fc356b544d03" -dependencies = [ - "getrandom 0.1.16", - "libc", - "rand_chacha 0.2.2", - "rand_core 0.5.1", - "rand_hc", -] - [[package]] name = "rand" version = "0.8.5" @@ -3804,18 +3864,8 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "34af8d1a0e25924bc5b7c43c079c942339d8f0a8b57c39049bef581b46327404" dependencies = [ "libc", - "rand_chacha 0.3.1", - "rand_core 0.6.4", -] - -[[package]] -name = "rand_chacha" -version = "0.2.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f4c8ed856279c9737206bf725bf36935d8666ead7aa69b52be55af369d193402" -dependencies = [ - "ppv-lite86", - "rand_core 0.5.1", + "rand_chacha", + "rand_core", ] [[package]] @@ -3825,16 +3875,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e6c10a63a0fa32252be49d21e7709d4d4baf8d231c2dbce1eaa8141b9b127d88" dependencies = [ "ppv-lite86", - "rand_core 0.6.4", -] - -[[package]] -name = "rand_core" -version = "0.5.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "90bde5296fc891b0cef12a6d03ddccc162ce7b2aff54160af9338f8d40df6d19" -dependencies = [ - "getrandom 0.1.16", + "rand_core", ] [[package]] @@ -3843,23 +3884,14 @@ version = "0.6.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ec0be4795e2f6a28069bec0b5ff3e2ac9bafc99e6a9a7dc3547996c5c816922c" dependencies = [ - "getrandom 0.2.10", -] - -[[package]] -name = "rand_hc" -version = "0.2.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ca3129af7b92a17112d59ad498c6f81eaf463253766b90396d39ea7a39d6613c" -dependencies = [ - "rand_core 0.5.1", + "getrandom", ] [[package]] name = "rasn" -version = "0.10.5" +version = "0.10.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0ef6075807dc2ea24ddbfaf1546bf4b956ac1f8338eae4dffa35d69846c8b691" +checksum = "76def3a472085e40dae01e4eeb106f2ed70f8c7dabe041faed1668a00ee143ef" dependencies = [ "arrayvec", "bitvec", @@ -3879,9 +3911,9 @@ dependencies = [ [[package]] name = "rasn-cms" -version = "0.10.5" +version = "0.10.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8d8d8656a9e88bca3a726fc7c365e4c8a4455d1f95ecd58f7817cfc642674cbc" +checksum = "09d45b1b6c0bfb41d1023246a3853a82d158d7e6690698ad4acac009f2f09380" dependencies = [ "rasn", "rasn-pkix", @@ -3889,9 +3921,9 @@ dependencies = [ [[package]] name = "rasn-derive" -version = "0.10.5" +version = "0.10.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "eda946c91626f958f853e47616cdfdd1fcd9c36a65a2388eb9a5d80dd644efd3" +checksum = "48d1fd02e16232e942b5e7ce305b447c550d09a9146255a3e8a2cf62a0e2ac2d" dependencies = [ "either", "itertools 0.10.5", @@ -3904,9 +3936,9 @@ dependencies = [ [[package]] name = "rasn-pkix" -version = "0.10.5" +version = "0.10.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6c91ef69d71656ca94e119989fd2056679823852d2544f94cd96bd6912a49a06" +checksum = "6c825c4183e8f4db9bd772206d7d0fa10f3df35b6ed5bcb8f6cb50fd1166f8e9" dependencies = [ "rasn", ] @@ -3931,15 +3963,6 @@ dependencies = [ "crossbeam-utils", ] -[[package]] -name = "redox_syscall" -version = "0.2.16" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fb5a58c1855b4b6819d59012155603f0b22ad30cad752600aadfcb695265519a" -dependencies = [ - "bitflags 1.3.2", -] - [[package]] name = "redox_syscall" version = "0.3.5" @@ -3960,12 +3983,12 @@ dependencies = [ [[package]] name = "redox_users" -version = "0.4.3" +version = "0.4.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b033d837a7cf162d7993aded9304e30a83213c648b6e389db233191f891e5c2b" +checksum = "a18479200779601e498ada4e8c1e1f50e3ee19deb0259c25825a98b5603b2cb4" dependencies = [ - "getrandom 0.2.10", - "redox_syscall 0.2.16", + "getrandom", + "libredox", "thiserror", ] @@ -4007,6 +4030,12 @@ version = "0.6.29" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f162c6dd7b008981e4d40210aca20b4bd0f9b60ca9271061b07f78537722f2e1" +[[package]] +name = "regex-syntax" +version = "0.7.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dbb5fb1acd8a1a18b3dd5be62d25485eb770e05afb408a9627d14d451bae12da" + [[package]] name = "regex-syntax" version = "0.8.2" @@ -4019,13 +4048,13 @@ version = "0.11.22" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "046cd98826c46c2ac8ddecae268eb5c2e58628688a5fc7a2643704a73faba95b" dependencies = [ - "base64 0.21.4", + "base64 0.21.5", "bytes", "encoding_rs", "futures-core", "futures-util", - "h2", - "http", + "h2 0.3.22", + "http 0.2.11", "http-body 0.4.5", "hyper 0.14.27", "hyper-rustls", @@ -4037,7 +4066,7 @@ dependencies = [ "once_cell", "percent-encoding", "pin-project-lite", - "rustls 0.21.7", + "rustls 0.21.9", "rustls-pemfile", "serde", "serde_json", @@ -4052,7 +4081,7 @@ dependencies = [ "wasm-bindgen-futures", "wasm-streams", "web-sys", - "webpki-roots 0.25.2", + "webpki-roots 0.25.3", "winreg", ] @@ -4104,7 +4133,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "fb0205304757e5d899b9c2e448b867ffd03ae7f988002e47cd24954391394d0b" dependencies = [ "cc", - "getrandom 0.2.10", + "getrandom", "libc", "spin 0.9.8", "untrusted 0.9.0", @@ -4143,32 +4172,30 @@ dependencies = [ [[package]] name = "rpassword" -version = "7.2.0" +version = "7.3.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6678cf63ab3491898c0d021b493c94c9b221d91295294a2a5746eacbe5928322" +checksum = "80472be3c897911d0137b2d2b9055faf6eeac5b14e324073d83bc17b191d7e3f" dependencies = [ "libc", "rtoolbox", - "winapi", + "windows-sys 0.48.0", ] [[package]] name = "rsa" -version = "0.9.2" +version = "0.9.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6ab43bb47d23c1a631b4b680199a45255dce26fa9ab2fa902581f624ff13e6a8" +checksum = "6a3211b01eea83d80687da9eef70e39d65144a3894866a5153a2723e425a157f" dependencies = [ - "byteorder", "const-oid", "digest 0.10.7", "num-bigint-dig", "num-integer", - "num-iter", "num-traits", "pkcs1", "pkcs8", - "rand_core 0.6.4", - "signature 2.1.0", + "rand_core", + "signature", "spki", "subtle", "zeroize", @@ -4176,12 +4203,12 @@ dependencies = [ [[package]] name = "rtoolbox" -version = "0.0.1" +version = "0.0.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "034e22c514f5c0cb8a10ff341b9b048b5ceb21591f31c8f44c43b960f9b3524a" +checksum = "c247d24e63230cdb56463ae328478bd5eac8b8faa8c69461a77e8e323afac90e" dependencies = [ "libc", - "winapi", + "windows-sys 0.48.0", ] [[package]] @@ -4223,7 +4250,7 @@ dependencies = [ "futures", "hex", "hmac 0.12.1", - "http", + "http 0.2.11", "log", "maybe-async", "md5", @@ -4262,6 +4289,15 @@ version = "1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "08d43f7aa6b08d49f382cde6a7982047c3426db949b1424bc4b7ec9ae12c6ce2" +[[package]] +name = "rustc_version" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bfa0f585226d2e68097d4f95d113b15b83a82e819ab25717ec0590d9584ef366" +dependencies = [ + "semver", +] + [[package]] name = "rusticata-macros" version = "4.1.0" @@ -4273,9 +4309,9 @@ dependencies = [ [[package]] name = "rustix" -version = "0.38.20" +version = "0.38.25" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "67ce50cb2e16c2903e30d1cbccfd8387a74b9d4c938b6a4c5ec6cc7556f7a8a0" +checksum = "dc99bc2d4f1fed22595588a013687477aedf3cdcfb26558c559edb67b4d9b22e" dependencies = [ "bitflags 2.4.1", "errno", @@ -4298,13 +4334,13 @@ dependencies = [ [[package]] name = "rustls" -version = "0.21.7" +version = "0.21.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cd8d6c9f025a446bc4d18ad9632e69aec8f287aa84499ee335599fabd20c3fd8" +checksum = "629648aced5775d558af50b2b4c7b02983a04b312126d45eeead26e7caa498b9" dependencies = [ "log", - "ring 0.16.20", - "rustls-webpki 0.101.6", + "ring 0.17.5", + "rustls-webpki", "sct", ] @@ -4322,31 +4358,21 @@ dependencies = [ [[package]] name = "rustls-pemfile" -version = "1.0.3" +version = "1.0.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2d3987094b1d07b653b7dfdc3f70ce9a1da9c51ac18c1b06b662e4f9a0e9f4b2" +checksum = "1c74cae0a4cf6ccbbf5f359f08efdf8ee7e1dc532573bf0db71968cb56b1448c" dependencies = [ - "base64 0.21.4", + "base64 0.21.5", ] [[package]] name = "rustls-webpki" -version = "0.100.3" +version = "0.101.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5f6a5fc258f1c1276dfe3016516945546e2d5383911efc0fc4f1cdc5df3a4ae3" +checksum = "8b6275d1ee7a1cd780b64aca7726599a1dbc893b1e64144529e55c3c2f745765" dependencies = [ - "ring 0.16.20", - "untrusted 0.7.1", -] - -[[package]] -name = "rustls-webpki" -version = "0.101.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3c7d5dece342910d9ba34d259310cae3e0154b873b35408b787b59bce53d34fe" -dependencies = [ - "ring 0.16.20", - "untrusted 0.7.1", + "ring 0.17.5", + "untrusted 0.9.0", ] [[package]] @@ -4408,12 +4434,12 @@ dependencies = [ [[package]] name = "sct" -version = "0.7.0" +version = "0.7.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d53dcdb7c9f8158937a7981b48accfd39a43af418591a5d008c7b22b5e1b7ca4" +checksum = "da046153aa2352493d6cb7da4b6e5c0c057d8a1d0a9aa8560baffdd945acd414" dependencies = [ - "ring 0.16.20", - "untrusted 0.7.1", + "ring 0.17.5", + "untrusted 0.9.0", ] [[package]] @@ -4454,33 +4480,41 @@ dependencies = [ ] [[package]] -name = "sequoia-openpgp" -version = "1.16.1" +name = "semver" +version = "1.0.20" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a16854c0f6297de6db4df195e28324dfbc2429802f0e48cd04007db8e3049709" +checksum = "836fa6a3e1e547f9a2c4040802ec865b5d85f4014efe00555d7090a3dcaa1090" + +[[package]] +name = "sequoia-openpgp" +version = "1.17.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2ea026cf8a70d331c742e3ad7e68fd405d0743ff86630fb4334a1bf8d0e194c7" dependencies = [ "aes", + "aes-gcm", "anyhow", - "base64 0.21.4", + "base64 0.21.5", "block-padding", "blowfish 0.9.1", "buffered-reader", + "camellia", "cast5", "cfb-mode", "chrono", "cipher 0.4.4", "des", "digest 0.10.7", + "dsa", "dyn-clone", "eax", "ecb", "ecdsa", "ed25519", "ed25519-dalek", - "generic-array", - "getrandom 0.2.10", + "getrandom", "idea", - "idna 0.3.0", + "idna 0.4.0", "lalrpop", "lalrpop-util", "lazy_static", @@ -4490,28 +4524,26 @@ dependencies = [ "num-bigint-dig", "once_cell", "p256", - "rand 0.7.3", - "rand 0.8.5", - "rand_core 0.6.4", + "rand", + "rand_core", "regex", - "regex-syntax 0.6.29", + "regex-syntax 0.8.2", "ripemd", "rsa", - "sha-1 0.10.1", "sha1collisiondetection", "sha2 0.10.8", "thiserror", "twofish", "typenum", - "x25519-dalek-ng", + "x25519-dalek", "xxhash-rust", ] [[package]] name = "serde" -version = "1.0.189" +version = "1.0.193" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8e422a44e74ad4001bdc8eede9a4570ab52f71190e9c076d14369f38b9200537" +checksum = "25dd9975e68d0cb5aa1120c288333fc98731bd1dd12f561e468ea4728c042b89" dependencies = [ "serde_derive", ] @@ -4527,20 +4559,20 @@ dependencies = [ [[package]] name = "serde_derive" -version = "1.0.189" +version = "1.0.193" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1e48d1f918009ce3145511378cf68d613e3b3d9137d67272562080d68a2b32d5" +checksum = "43576ca501357b9b071ac53cdc7da8ef0cbd9493d8df094cd821777ea6e894d3" dependencies = [ "proc-macro2", "quote", - "syn 2.0.38", + "syn 2.0.39", ] [[package]] name = "serde_json" -version = "1.0.107" +version = "1.0.108" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6b420ce6e3d8bd882e9b243c6eed35dbc9a6110c9769e74b584e0d68d1f20c65" +checksum = "3d1c7e3eac408d115102c4c24ad393e0821bb3a5df4d506a80f85f7a742a526b" dependencies = [ "itoa", "ryu", @@ -4581,7 +4613,7 @@ checksum = "91d129178576168c589c9ec973feedf7d3126c01ac2bf08795109aa35b69fb8f" dependencies = [ "proc-macro2", "quote", - "syn 2.0.38", + "syn 2.0.39", ] [[package]] @@ -4597,17 +4629,6 @@ dependencies = [ "opaque-debug", ] -[[package]] -name = "sha-1" -version = "0.10.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f5058ada175748e33390e40e872bd0fe59a19f265d0158daa551c5a88a76009c" -dependencies = [ - "cfg-if", - "cpufeatures", - "digest 0.10.7", -] - [[package]] name = "sha1" version = "0.10.6" @@ -4621,11 +4642,12 @@ dependencies = [ [[package]] name = "sha1collisiondetection" -version = "0.2.7" +version = "0.3.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b20793cf8330b2c7da4c438116660fed24e380bcb8a1bcfff2581b5593a0b38e" +checksum = "31c0b86a052106b16741199985c9ec2bf501f619f70c48fa479b44b093ad9a68" dependencies = [ - "digest 0.9.0", + "const-oid", + "digest 0.10.7", "generic-array", ] @@ -4679,7 +4701,7 @@ name = "sieve-rs" version = "0.3.1" source = "git+https://github.com/stalwartlabs/sieve#b7b62b3b33d05b7c1e50ea5123a35ef461c66b8d" dependencies = [ - "ahash 0.8.3", + "ahash 0.8.6", "bincode", "fancy-regex", "mail-builder", @@ -4699,18 +4721,12 @@ dependencies = [ [[package]] name = "signature" -version = "1.6.4" +version = "2.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "74233d3b3b2f6d4b006dc19dee745e73e2a6bfb6f93607cd3b02bd5b00797d7c" - -[[package]] -name = "signature" -version = "2.1.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5e1788eed21689f9cf370582dfc467ef36ed9c707f073528ddafa8d83e3b8500" +checksum = "77549399552de45a898a580c1b41d445bf730df867cc44e6c0233bbc4b8329de" dependencies = [ "digest 0.10.7", - "rand_core 0.6.4", + "rand_core", ] [[package]] @@ -4736,24 +4752,24 @@ dependencies = [ [[package]] name = "smallvec" -version = "1.11.1" +version = "1.11.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "942b4a808e05215192e39f4ab80813e599068285906cc91aa64f923db842bd5a" +checksum = "4dccd0940a2dcdf68d092b8cbab7dc0ad8fa938bf95787e1b916b0e3d0e8e970" [[package]] name = "smtp" version = "0.4.2" dependencies = [ - "ahash 0.8.3", + "ahash 0.8.6", "blake3", "dashmap", "decancer", "directory", "form_urlencoded", "http-body-util", - "hyper 1.0.0-rc.4", + "hyper 1.0.1", "hyper-util", - "idna 0.4.0", + "idna 0.5.0", "imagesize", "infer", "lazy_static", @@ -4766,11 +4782,11 @@ dependencies = [ "nlp", "num_cpus", "parking_lot", - "rand 0.8.5", + "rand", "rayon", "regex", "reqwest", - "rustls 0.21.7", + "rustls 0.21.9", "rustls-pemfile", "serde", "serde_json", @@ -4784,7 +4800,7 @@ dependencies = [ "tracing", "unicode-security", "utils", - "webpki-roots 0.25.2", + "webpki-roots 0.25.3", "whatlang", "x509-parser", ] @@ -4892,7 +4908,7 @@ version = "0.7.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8d6753e460c998bbd4cd8c6f0ed9a64346fcca0723d6e75e52fdc351c5d2169d" dependencies = [ - "ahash 0.8.3", + "ahash 0.8.6", "atoi", "byteorder", "bytes", @@ -4908,13 +4924,13 @@ dependencies = [ "futures-util", "hashlink", "hex", - "indexmap 2.0.2", + "indexmap 2.1.0", "log", "memchr", "once_cell", "paste", "percent-encoding", - "rustls 0.21.7", + "rustls 0.21.9", "rustls-pemfile", "serde", "serde_json", @@ -4975,7 +4991,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "864b869fdf56263f4c95c45483191ea0af340f9f3e3e7b4d57a61c7c87a970db" dependencies = [ "atoi", - "base64 0.21.4", + "base64 0.21.5", "bitflags 2.4.1", "byteorder", "bytes", @@ -4997,7 +5013,7 @@ dependencies = [ "memchr", "once_cell", "percent-encoding", - "rand 0.8.5", + "rand", "rsa", "serde", "sha1", @@ -5017,7 +5033,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "eb7ae0e6a97fb3ba33b23ac2671a5ce6e3cabe003f451abd5a56e7951d975624" dependencies = [ "atoi", - "base64 0.21.4", + "base64 0.21.5", "bitflags 2.4.1", "byteorder", "crc", @@ -5036,7 +5052,7 @@ dependencies = [ "md-5 0.10.6", "memchr", "once_cell", - "rand 0.8.5", + "rand", "serde", "serde_json", "sha1", @@ -5097,7 +5113,7 @@ dependencies = [ name = "stalwart-install" version = "0.4.2" dependencies = [ - "base64 0.21.4", + "base64 0.21.5", "clap", "dialoguer", "flate2", @@ -5105,7 +5121,7 @@ dependencies = [ "libc", "openssl", "pwhash", - "rand 0.8.5", + "rand", "reqwest", "rpassword", "rusqlite", @@ -5123,7 +5139,7 @@ checksum = "a2eb9349b6444b326872e140eb1cf5e7c522154d69e7a0ffb0fb81c06b37543f" name = "store" version = "0.1.0" dependencies = [ - "ahash 0.8.3", + "ahash 0.8.6", "async-trait", "bitpacking", "blake3", @@ -5137,7 +5153,7 @@ dependencies = [ "num_cpus", "parking_lot", "r2d2", - "rand 0.8.5", + "rand", "rayon", "roaring", "rocksdb", @@ -5187,12 +5203,6 @@ version = "2.4.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6bdef32e8150c2a081110b42772ffe7d7c9032b606bc226c8260fd97e0976601" -[[package]] -name = "subtle-ng" -version = "2.5.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "734676eb262c623cec13c3155096e08d1f8f29adce39ba17948b18dad1e54142" - [[package]] name = "syn" version = "1.0.109" @@ -5206,9 +5216,9 @@ dependencies = [ [[package]] name = "syn" -version = "2.0.38" +version = "2.0.39" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e96b79aaa137db8f61e26363a0c9b47d8b4ec75da28b7d1d614c2303e232408b" +checksum = "23e78b90f2fcf45d3e842032ce32e3f2d1545ba6636271dcbf24fa306d87be7a" dependencies = [ "proc-macro2", "quote", @@ -5273,13 +5283,13 @@ dependencies = [ [[package]] name = "tempfile" -version = "3.8.0" +version = "3.8.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cb94d2f3cc536af71caac6b6fcebf65860b347e7ce0cc9ebe8f70d3e521054ef" +checksum = "7ef1adac450ad7f4b3c28589471ade84f25f731a7a0fe30d71dfa9f60fd808e5" dependencies = [ "cfg-if", "fastrand", - "redox_syscall 0.3.5", + "redox_syscall 0.4.1", "rustix", "windows-sys 0.48.0", ] @@ -5299,9 +5309,9 @@ dependencies = [ name = "tests" version = "0.1.0" dependencies = [ - "ahash 0.8.3", + "ahash 0.8.6", "async-trait", - "base64 0.21.4", + "base64 0.21.5", "bytes", "chrono", "csv", @@ -5311,7 +5321,7 @@ dependencies = [ "flate2", "futures", "http-body-util", - "hyper 1.0.0-rc.4", + "hyper 1.0.1", "hyper-util", "imap", "imap_proto", @@ -5327,7 +5337,7 @@ dependencies = [ "num_cpus", "rayon", "reqwest", - "rustls 0.21.7", + "rustls 0.21.9", "rustls-pemfile", "serde", "serde_json", @@ -5361,7 +5371,7 @@ checksum = "266b2e40bc00e5a6c09c3584011e08b06f123c00362c92b975ba9843aaaa14b8" dependencies = [ "proc-macro2", "quote", - "syn 2.0.38", + "syn 2.0.39", ] [[package]] @@ -5439,9 +5449,9 @@ checksum = "1f3ccbac311fea05f86f61904b462b55fb3df8837a366dfc601a0161d0532f20" [[package]] name = "tokio" -version = "1.33.0" +version = "1.34.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4f38200e3ef7995e5ef13baec2f432a6da0aa9ac495b2c0e8f3b7eec2c92d653" +checksum = "d0c014766411e834f7af5b8f4cf46257aab4036ca95e9d2c144a10f59ad6f5b9" dependencies = [ "backtrace", "bytes", @@ -5468,13 +5478,13 @@ dependencies = [ [[package]] name = "tokio-macros" -version = "2.1.0" +version = "2.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "630bdcf245f78637c13ec01ffae6187cca34625e8c63150d424b59e55af2675e" +checksum = "5b8a1e28f2deaa14e508979454cb3a223b10b938b45af148bc0986de36f1923b" dependencies = [ "proc-macro2", "quote", - "syn 2.0.38", + "syn 2.0.39", ] [[package]] @@ -5483,7 +5493,7 @@ version = "0.24.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c28327cf380ac148141087fbfb9de9d7bd4e84ab5d2c28fbc911d753de8a7081" dependencies = [ - "rustls 0.21.7", + "rustls 0.21.9", "tokio", ] @@ -5498,21 +5508,6 @@ dependencies = [ "tokio", ] -[[package]] -name = "tokio-tungstenite" -version = "0.19.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ec509ac96e9a0c43427c74f003127d953a265737636129424288d27cb5c4b12c" -dependencies = [ - "futures-util", - "log", - "rustls 0.21.7", - "tokio", - "tokio-rustls", - "tungstenite 0.19.0", - "webpki-roots 0.23.1", -] - [[package]] name = "tokio-tungstenite" version = "0.20.1" @@ -5521,15 +5516,18 @@ checksum = "212d5dcb2a1ce06d81107c3d0ffa3121fe974b73f068c8282cb1c32328113b6c" dependencies = [ "futures-util", "log", + "rustls 0.21.9", "tokio", - "tungstenite 0.20.1", + "tokio-rustls", + "tungstenite", + "webpki-roots 0.25.3", ] [[package]] name = "tokio-util" -version = "0.7.9" +version = "0.7.10" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1d68074620f57a0b21594d9735eb2e98ab38b17f80d3fcb189fca266771ca60d" +checksum = "5419f34732d9eb6ee4c3578b7989078579b7f039cbbb9ca2c4da015749371e15" dependencies = [ "bytes", "futures-core", @@ -5547,12 +5545,12 @@ checksum = "3082666a3a6433f7f511c7192923fa1fe07c69332d3c6a2e6bb040b569199d5a" dependencies = [ "async-trait", "axum", - "base64 0.21.4", + "base64 0.21.5", "bytes", "futures-core", "futures-util", - "h2", - "http", + "h2 0.3.22", + "http 0.2.11", "http-body 0.4.5", "hyper 0.14.27", "hyper-timeout", @@ -5578,7 +5576,7 @@ dependencies = [ "indexmap 1.9.3", "pin-project", "pin-project-lite", - "rand 0.8.5", + "rand", "slab", "tokio", "tokio-util", @@ -5613,11 +5611,12 @@ dependencies = [ [[package]] name = "tracing-appender" -version = "0.2.2" +version = "0.2.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "09d48f71a791638519505cefafe162606f706c25592e4bde4d97600c0195312e" +checksum = "3566e8ce28cc0a3fe42519fc80e6b4c943cc4c8cef275620eb8dac2d3d4e06cf" dependencies = [ "crossbeam-channel", + "thiserror", "time", "tracing-subscriber", ] @@ -5630,7 +5629,7 @@ checksum = "34704c8d6ebcbc939824180af020566b01a7c01f80641264eba0999f6c2b6be7" dependencies = [ "proc-macro2", "quote", - "syn 2.0.38", + "syn 2.0.39", ] [[package]] @@ -5656,12 +5655,23 @@ dependencies = [ [[package]] name = "tracing-log" -version = "0.1.3" +version = "0.1.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "78ddad33d2d10b1ed7eb9d1f518a5674713876e97e5bb9b7345a7984fbb4f922" +checksum = "f751112709b4e791d8ce53e32c4ed2d353565a795ce84da2285393f41557bdf2" dependencies = [ - "lazy_static", "log", + "once_cell", + "tracing-core", +] + +[[package]] +name = "tracing-log" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ee855f1f400bd0e5c02d150ae5de3840039a3f54b025156404e34c23c03f47c3" +dependencies = [ + "log", + "once_cell", "tracing-core", ] @@ -5677,15 +5687,15 @@ dependencies = [ "smallvec", "tracing", "tracing-core", - "tracing-log", + "tracing-log 0.1.4", "tracing-subscriber", ] [[package]] name = "tracing-subscriber" -version = "0.3.17" +version = "0.3.18" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "30a651bc37f915e81f087d86e62a18eec5f79550c7faff886f7090b4ea757c77" +checksum = "ad0f048c97dbd9faa9b7df56362b8ebcaa52adb06b498c050d2f4e32f90a7a8b" dependencies = [ "matchers", "nu-ansi-term", @@ -5696,7 +5706,7 @@ dependencies = [ "thread_local", "tracing", "tracing-core", - "tracing-log", + "tracing-log 0.2.0", ] [[package]] @@ -5711,27 +5721,6 @@ version = "0.3.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "fb1626d07cb5c1bb2cf17d94c0be4852e8a7c02b041acec9a8c5bdda99f9d580" -[[package]] -name = "tungstenite" -version = "0.19.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "15fba1a6d6bb030745759a9a2a588bfe8490fc8b4751a277db3a0be1c9ebbf67" -dependencies = [ - "byteorder", - "bytes", - "data-encoding", - "http", - "httparse", - "log", - "rand 0.8.5", - "rustls 0.21.7", - "sha1", - "thiserror", - "url", - "utf-8", - "webpki", -] - [[package]] name = "tungstenite" version = "0.20.1" @@ -5741,10 +5730,11 @@ dependencies = [ "byteorder", "bytes", "data-encoding", - "http", + "http 0.2.11", "httparse", "log", - "rand 0.8.5", + "rand", + "rustls 0.21.9", "sha1", "thiserror", "url", @@ -5885,12 +5875,12 @@ checksum = "8ecb6da28b8a351d773b68d5825ac39017e680750f980f3a1a85cd8dd28a47c1" [[package]] name = "url" -version = "2.4.1" +version = "2.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "143b538f18257fac9cad154828a57c6bf5157e1aa604d4816b5995bf6de87ae5" +checksum = "31e6302e3bb753d46e83516cae55ae196fc0c309407cf11ab35cc51a4c2a4633" dependencies = [ "form_urlencoded", - "idna 0.4.0", + "idna 0.5.0", "percent-encoding", ] @@ -5916,7 +5906,7 @@ checksum = "711b9620af191e0cdc7468a8d14e709c3dcdb115b36f838e601583af800a370a" name = "utils" version = "0.4.2" dependencies = [ - "ahash 0.8.3", + "ahash 0.8.6", "chrono", "dashmap", "mail-auth", @@ -5925,8 +5915,8 @@ dependencies = [ "opentelemetry-otlp", "opentelemetry-semantic-conventions", "privdrop", - "rand 0.8.5", - "rustls 0.21.7", + "rand", + "rustls 0.21.9", "rustls-pemfile", "serde", "smtp-proto", @@ -5941,11 +5931,11 @@ dependencies = [ [[package]] name = "uuid" -version = "1.5.0" +version = "1.6.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "88ad59a7560b41a70d191093a945f0b87bc1deeda46fb237479708a1d6b6cdfc" +checksum = "5e395fcf16a7a3d8127ec99782007af141946b4795001f876d54fb0d55978560" dependencies = [ - "getrandom 0.2.10", + "getrandom", ] [[package]] @@ -5975,12 +5965,6 @@ dependencies = [ "try-lock", ] -[[package]] -name = "wasi" -version = "0.9.0+wasi-snapshot-preview1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cccddf32554fecc6acb585f82a32a72e28b48f8c4c1883ddfeeeaa96f7d8e519" - [[package]] name = "wasi" version = "0.11.0+wasi-snapshot-preview1" @@ -5989,9 +5973,9 @@ checksum = "9c8d87e72b64a3b4db28d11ce29237c246188f4f51057d65a7eab63b7987e423" [[package]] name = "wasm-bindgen" -version = "0.2.87" +version = "0.2.88" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7706a72ab36d8cb1f80ffbf0e071533974a60d0a308d01a5d0375bf60499a342" +checksum = "7daec296f25a1bae309c0cd5c29c4b260e510e6d813c286b19eaadf409d40fce" dependencies = [ "cfg-if", "wasm-bindgen-macro", @@ -5999,24 +5983,24 @@ dependencies = [ [[package]] name = "wasm-bindgen-backend" -version = "0.2.87" +version = "0.2.88" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5ef2b6d3c510e9625e5fe6f509ab07d66a760f0885d858736483c32ed7809abd" +checksum = "e397f4664c0e4e428e8313a469aaa58310d302159845980fd23b0f22a847f217" dependencies = [ "bumpalo", "log", "once_cell", "proc-macro2", "quote", - "syn 2.0.38", + "syn 2.0.39", "wasm-bindgen-shared", ] [[package]] name = "wasm-bindgen-futures" -version = "0.4.37" +version = "0.4.38" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c02dbc21516f9f1f04f187958890d7e6026df8d16540b7ad9492bc34a67cea03" +checksum = "9afec9963e3d0994cac82455b2b3502b81a7f40f9a0d32181f7528d9f4b43e02" dependencies = [ "cfg-if", "js-sys", @@ -6026,9 +6010,9 @@ dependencies = [ [[package]] name = "wasm-bindgen-macro" -version = "0.2.87" +version = "0.2.88" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dee495e55982a3bd48105a7b947fd2a9b4a8ae3010041b9e0faab3f9cd028f1d" +checksum = "5961017b3b08ad5f3fe39f1e79877f8ee7c23c5e5fd5eb80de95abc41f1f16b2" dependencies = [ "quote", "wasm-bindgen-macro-support", @@ -6036,22 +6020,22 @@ dependencies = [ [[package]] name = "wasm-bindgen-macro-support" -version = "0.2.87" +version = "0.2.88" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "54681b18a46765f095758388f2d0cf16eb8d4169b639ab575a8f5693af210c7b" +checksum = "c5353b8dab669f5e10f5bd76df26a9360c748f054f862ff5f3f8aae0c7fb3907" dependencies = [ "proc-macro2", "quote", - "syn 2.0.38", + "syn 2.0.39", "wasm-bindgen-backend", "wasm-bindgen-shared", ] [[package]] name = "wasm-bindgen-shared" -version = "0.2.87" +version = "0.2.88" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ca6ad05a4870b2bf5fe995117d3728437bd27d7cd5f06f13c17443ef369775a1" +checksum = "0d046c5d029ba91a1ed14da14dca44b68bf2f124cfbaf741c54151fdb3e0750b" [[package]] name = "wasm-streams" @@ -6068,9 +6052,9 @@ dependencies = [ [[package]] name = "web-sys" -version = "0.3.64" +version = "0.3.65" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9b85cbef8c220a6abc02aefd892dfc0fc23afb1c6a426316ec33253a3877249b" +checksum = "5db499c5f66323272151db0e666cd34f78617522fb0c1604d31a27c50c206a85" dependencies = [ "js-sys", "wasm-bindgen", @@ -6095,29 +6079,20 @@ dependencies = [ "webpki", ] -[[package]] -name = "webpki-roots" -version = "0.23.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b03058f88386e5ff5310d9111d53f48b17d732b401aeb83a8d5190f2ac459338" -dependencies = [ - "rustls-webpki 0.100.3", -] - [[package]] name = "webpki-roots" version = "0.24.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b291546d5d9d1eab74f069c77749f2cb8504a12caa20f0f2de93ddbf6f411888" dependencies = [ - "rustls-webpki 0.101.6", + "rustls-webpki", ] [[package]] name = "webpki-roots" -version = "0.25.2" +version = "0.25.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "14247bb57be4f377dfb94c72830b8ce8fc6beac03cf4bf7b9732eadd414123fc" +checksum = "1778a42e8b3b90bff8d0f5032bf22250792889a5cdc752aa0020c84abe3aaf10" [[package]] name = "whatlang" @@ -6336,14 +6311,13 @@ dependencies = [ ] [[package]] -name = "x25519-dalek-ng" -version = "1.1.1" +name = "x25519-dalek" +version = "2.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bf7074de8999662970c3c4c8f7f30925028dd8f4ca31ad4c055efa9cdf2ec326" +checksum = "fb66477291e7e8d2b0ff1bcb900bf29489a9692816d79874bea351e7a8b6de96" dependencies = [ - "curve25519-dalek-ng", - "rand 0.8.5", - "rand_core 0.6.4", + "curve25519-dalek", + "rand_core", "zeroize", ] @@ -6386,10 +6360,30 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9828b178da53440fa9c766a3d2f73f7cf5d0ac1fe3980c1e5018d899fd19e07b" [[package]] -name = "zeroize" -version = "1.6.0" +name = "zerocopy" +version = "0.7.26" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2a0956f1ba7c7909bfb66c2e9e4124ab6f6482560f6628b5aaeba39207c9aad9" +checksum = "e97e415490559a91254a2979b4829267a57d2fcd741a98eee8b722fb57289aa0" +dependencies = [ + "zerocopy-derive", +] + +[[package]] +name = "zerocopy-derive" +version = "0.7.26" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dd7e48ccf166952882ca8bd778a43502c64f33bf94c12ebe2a7f08e5a0f6689f" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.39", +] + +[[package]] +name = "zeroize" +version = "1.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "525b4ec142c6b68a2d10f01f7bbf6755599ca3f81ea53b8431b7dd348f5fdb2d" dependencies = [ "zeroize_derive", ] @@ -6402,7 +6396,7 @@ checksum = "ce36e65b0d2999d2aafac989fb249189a141aee1f53c612c1f37d72631959f69" dependencies = [ "proc-macro2", "quote", - "syn 2.0.38", + "syn 2.0.39", ] [[package]] diff --git a/crates/imap/Cargo.toml b/crates/imap/Cargo.toml index 2c1aedf9..d8fa824c 100644 --- a/crates/imap/Cargo.toml +++ b/crates/imap/Cargo.toml @@ -23,6 +23,7 @@ tracing = "0.1" ahash = { version = "0.8" } md5 = "0.7.0" dashmap = "5.4" +rand = "0.8.5" [features] test_mode = [] diff --git a/crates/imap/src/core/client.rs b/crates/imap/src/core/client.rs index f4720ec0..f5e43e46 100644 --- a/crates/imap/src/core/client.rs +++ b/crates/imap/src/core/client.rs @@ -36,10 +36,10 @@ use super::{SelectedMailbox, Session, SessionData, State, IMAP}; impl Session { pub async fn ingest(&mut self, bytes: &[u8]) -> crate::Result { - /*for line in String::from_utf8_lossy(bytes).split("\r\n") { + for line in String::from_utf8_lossy(bytes).split("\r\n") { //let c = println!("<- {:?}", &line[..std::cmp::min(line.len(), 100)]); let c = println!("{}", line); - }*/ + } tracing::trace!(parent: &self.span, event = "read", diff --git a/crates/imap/src/core/mailbox.rs b/crates/imap/src/core/mailbox.rs index ebeb1b80..a20ae26e 100644 --- a/crates/imap/src/core/mailbox.rs +++ b/crates/imap/src/core/mailbox.rs @@ -13,7 +13,7 @@ use jmap_proto::{ use parking_lot::Mutex; use store::query::log::{Change, Query}; use tokio::io::AsyncRead; -use utils::{listener::limiter::InFlight, map::mutex_map::MutexMap}; +use utils::listener::limiter::InFlight; use super::{Account, Mailbox, MailboxId, MailboxSync, Session, SessionData}; @@ -31,7 +31,6 @@ impl SessionData { span: session.span.clone(), mailboxes: Mutex::new(vec![]), state: access_token.state().into(), - mailbox_locks: MutexMap::with_capacity(5), in_flight, }; @@ -495,37 +494,25 @@ impl SessionData { } pub fn get_mailbox_by_name(&self, mailbox_name: &str) -> Option { - if !self.is_all_mailbox(mailbox_name) { - let is_inbox = mailbox_name.eq_ignore_ascii_case("inbox"); - for account in self.mailboxes.lock().iter() { - if account - .prefix - .as_ref() - .map_or(true, |p| mailbox_name.starts_with(p)) - { - for (mailbox_name_, mailbox_id_) in account.mailbox_names.iter() { - if mailbox_name_ == mailbox_name || (is_inbox && *mailbox_id_ == INBOX_ID) { - return MailboxId { - account_id: account.account_id, - mailbox_id: Some(*mailbox_id_), - } - .into(); + let is_inbox = mailbox_name.eq_ignore_ascii_case("inbox"); + for account in self.mailboxes.lock().iter() { + if account + .prefix + .as_ref() + .map_or(true, |p| mailbox_name.starts_with(p)) + { + for (mailbox_name_, mailbox_id_) in account.mailbox_names.iter() { + if mailbox_name_ == mailbox_name || (is_inbox && *mailbox_id_ == INBOX_ID) { + return MailboxId { + account_id: account.account_id, + mailbox_id: *mailbox_id_, } + .into(); } } } - None - } else { - MailboxId { - account_id: self.account_id, - mailbox_id: None, - } - .into() } - } - - pub fn is_all_mailbox(&self, mailbox_name: &str) -> bool { - self.imap.name_all == mailbox_name + None } pub async fn check_mailbox_acl( diff --git a/crates/imap/src/core/message.rs b/crates/imap/src/core/message.rs index 11638919..8ee4ec25 100644 --- a/crates/imap/src/core/message.rs +++ b/crates/imap/src/core/message.rs @@ -21,257 +21,162 @@ * for more details. */ -use std::hash::{BuildHasher, Hash, Hasher}; +use std::collections::BTreeMap; -use ahash::{AHashMap, AHashSet, AHasher, RandomState}; +use ahash::AHashMap; use imap_proto::{ protocol::{expunge, select::Exists, Sequence}, StatusResponse, }; -use jmap_proto::types::{collection::Collection, property::Property}; -use store::{ - roaring::RoaringBitmap, - write::{assert::HashedValue, now, BatchBuilder, ToBitmaps, F_VALUE}, - Deserialize, Serialize, +use jmap::mailbox::UidMailbox; +use jmap_proto::{ + object::Object, + types::{collection::Collection, property::Property, value::Value}, }; -use utils::codec::leb128::{Leb128Iterator, Leb128Vec}; +use store::write::{assert::HashedValue, BatchBuilder, F_VALUE}; use crate::core::ImapId; use super::{MailboxId, MailboxState, NextMailboxState, SelectedMailbox, SessionData}; -const MAX_RETRIES: usize = 10; - -#[derive(Debug)] -struct UidMap { - uid_next: u32, - uid_validity: u32, - hash: u64, - items: Vec, -} - -#[derive(Debug)] -struct Uid { - uid: u32, - id: u32, - received: u32, -} - -struct UidMapBuilder { - message_ids: RoaringBitmap, - hasher: AHasher, - id_list: Vec<(u32, u32)>, -} +pub(crate) const MAX_RETRIES: usize = 10; impl SessionData { pub async fn fetch_messages(&self, mailbox: &MailboxId) -> crate::op::Result { - let mut try_count = 0; + // Obtain message ids + let message_ids = self + .jmap + .get_tag( + mailbox.account_id, + Collection::Email, + Property::MailboxIds, + mailbox.mailbox_id, + ) + .await? + .unwrap_or_default(); - loop { - // Acquire lock on the mailbox - let _guard = self.mailbox_locks.lock_hash(mailbox).await; + // Obtain mailbox data + let uid_validity = self + .jmap + .get_property::>( + mailbox.account_id, + Collection::Mailbox, + mailbox.mailbox_id, + &Property::Value, + ) + .await? + .and_then(|obj| obj.get(&Property::Cid).as_uint()) + .ok_or_else(|| { + tracing::debug!(event = "error", + context = "store", + account_id = mailbox.account_id, + collection = ?Collection::Mailbox, + mailbox_id = mailbox.mailbox_id, + "Failed to obtain uid validity"); + StatusResponse::no("Mailbox unavailable.") + })? as u32; - // Deserialize mailbox data - let uid_map = self - .jmap - .get_property::>( - mailbox.account_id, - Collection::Mailbox, - mailbox.mailbox_id.unwrap_or(u32::MAX), - Property::EmailIds, - ) - .await?; - - // Obtain current state - let modseq = self - .jmap - .store - .get_last_change_id(mailbox.account_id, Collection::Email) - .await - .map_err(|err| { - tracing::error!(event = "error", - context = "store", - account_id = mailbox.account_id, - collection = ?Collection::Email, - error = ?err, - "Failed to obtain state"); - StatusResponse::database_failure() - })?; - - // Obtain message ids - let message_ids = if let Some(mailbox_id) = mailbox.mailbox_id { - self.jmap - .get_tag( - mailbox.account_id, - Collection::Email, - Property::MailboxIds, - mailbox_id, - ) - .await? - .unwrap_or_default() - } else { - self.jmap - .get_document_ids(mailbox.account_id, Collection::Email) - .await? - .unwrap_or_default() - }; - - // Obtain message data - let (id_list, id_list_hash) = if !message_ids.is_empty() { - let mut uid_builder = UidMapBuilder { - id_list: Vec::with_capacity(message_ids.len() as usize), - message_ids, - hasher: RandomState::with_seeds( - 0xaf1f2242106c64b3, - 0x60ca4cfb4b3ed0ce, - 0xc7dbc0bb615e82b3, - 0x520ad065378daf88, - ) - .build_hasher(), - }; - - self.jmap - .store - .sort_index( - mailbox.account_id, - Collection::Email, - Property::ReceivedAt, - true, - |bytes, message_id| { - if uid_builder.message_ids.remove(message_id) { - let received = (u64::deserialize(bytes)? & u32::MAX as u64) as u32; - uid_builder.id_list.push((message_id, received)); - message_id.hash(&mut uid_builder.hasher); - received.hash(&mut uid_builder.hasher); - Ok(!uid_builder.message_ids.is_empty()) - } else { - Ok(true) - } - }, - ) - .await - .map_err(|err| { - tracing::error!(event = "error", + // Obtain current state + let modseq = self + .jmap + .store + .get_last_change_id(mailbox.account_id, Collection::Email) + .await + .map_err(|err| { + tracing::error!(event = "error", context = "store", account_id = mailbox.account_id, collection = ?Collection::Email, error = ?err, - "Failed to obtain message data"); - StatusResponse::database_failure() - })?; - (uid_builder.id_list, uid_builder.hasher.finish()) - } else { - (Vec::new(), 0) - }; + "Failed to obtain state"); + StatusResponse::database_failure() + })?; - // Build mailboxdata - if let Some(mut uid_map) = uid_map { - if uid_map.inner.hash != id_list_hash { - let mut id_list_map = id_list.iter().cloned().collect::>(); - let mut items = Vec::with_capacity(uid_map.inner.items.len()); + // Retrieve message ids + let mut assigned = BTreeMap::new(); + let mut unassigned = Vec::new(); - for item in uid_map.inner.items { - if id_list_map.remove(&(item.id, item.received)) { - items.push(item); - } - } - - for (id, received) in id_list_map { - items.push(Uid { - uid: uid_map.inner.uid_next, - id, - received, - }); - - uid_map.inner.uid_next += 1; - } - - uid_map.inner.items = items; - uid_map.inner.hash = id_list_hash; - - // Save updated uid map - let mut batch = BatchBuilder::new(); - batch - .with_account_id(mailbox.account_id) - .with_collection(Collection::Mailbox) - .update_document(mailbox.mailbox_id.unwrap_or(u32::MAX)) - .assert_value(Property::EmailIds, &uid_map) - .value(Property::EmailIds, &uid_map.inner, F_VALUE); - - match self.jmap.store.write(batch.build()).await { - Ok(_) => (), - Err(store::Error::AssertValueFailed) if try_count < MAX_RETRIES => { - try_count += 1; - continue; - } - Err(err) => { - tracing::error!(event = "error", - context = "store", - account_id = mailbox.account_id, - collection = ?Collection::Mailbox, - error = ?err, - "Failed to update uid map"); - return Err(StatusResponse::database_failure()); + // Obtain all message ids + for (uid_mailbox, message_id) in self + .jmap + .get_properties::>>( + mailbox.account_id, + Collection::Email, + message_ids.iter(), + Property::MailboxIds, + ) + .await? + .into_iter() + .zip(message_ids.into_iter()) + { + // Make sure the message is still in this mailbox + if let Some(uid_mailbox) = uid_mailbox { + if let Some(item) = uid_mailbox + .inner + .iter() + .find(|item| item.mailbox_id == mailbox.mailbox_id) + { + if item.uid > 0 { + if assigned.insert(item.uid, message_id).is_some() { + tracing::warn!(event = "error", + context = "store", + account_id = mailbox.account_id, + collection = ?Collection::Mailbox, + mailbox_id = mailbox.mailbox_id, + message_id = message_id, + "Duplicate UID"); } + } else { + unassigned.push((message_id, uid_mailbox)); } } + } + } - let uid_map = uid_map.inner; - let mut id_to_imap = AHashMap::with_capacity(uid_map.items.len()); - let mut uid_to_id = AHashMap::with_capacity(uid_map.items.len()); - let mut uid_max = 0; + // Obtain UID next and assign UIDs + let mut try_count = 0; + let mut uid_next = 1; + let mut uid_other = 0; - for (seqnum, item) in uid_map.items.into_iter().enumerate() { - id_to_imap.insert( - item.id, - ImapId { - uid: item.uid, - seqnum: (seqnum + 1) as u32, - }, - ); - uid_to_id.insert(item.uid, item.id); - uid_max = item.uid; - } + // Shuffle unassigned + /*if unassigned.len() > 1 { + let mut rng = rand::thread_rng(); + unassigned.shuffle(&mut rng); + }*/ - return Ok(MailboxState { - uid_next: uid_map.uid_next, - uid_validity: uid_map.uid_validity, - total_messages: id_to_imap.len(), - id_to_imap, - uid_to_id, - uid_max, - modseq, - next_state: None, - }); - } else { - let uid_next = (id_list.len() + 1) as u32; - let uid_validity = now() as u32 ^ mailbox.mailbox_id.unwrap_or(0); - let mut id_to_imap = AHashMap::with_capacity(id_list.len()); - let mut uid_to_id = AHashMap::with_capacity(id_list.len()); - let mut uids = Vec::with_capacity(id_list.len()); - let mut uid_map = UidMap { - uid_next, - uid_validity, - hash: id_list_hash, - items: Vec::with_capacity(id_list.len()), - }; + loop { + let last_uid = self + .jmap + .get_property::( + mailbox.account_id, + Collection::Mailbox, + mailbox.mailbox_id, + Property::EmailIds, + ) + .await?; - for (uid, (id, received)) in id_list.into_iter().enumerate() { - let uid = (uid + 1) as u32; - id_to_imap.insert(id, ImapId { uid, seqnum: uid }); - uid_to_id.insert(uid, id); - uids.push(uid); - uid_map.items.push(Uid { uid, id, received }); - } - - // Store uid map + if !unassigned.is_empty() { + // Increment UID next let mut batch = BatchBuilder::new(); batch .with_account_id(mailbox.account_id) .with_collection(Collection::Mailbox) - .update_document(mailbox.mailbox_id.unwrap_or(u32::MAX)) - .assert_value(Property::EmailIds, ()) - .value(Property::EmailIds, &uid_map, F_VALUE); + .update_document(mailbox.mailbox_id); + + if let Some(last_uid) = last_uid { + batch.assert_value(Property::EmailIds, last_uid).value( + Property::EmailIds, + last_uid + unassigned.len() as u32, + F_VALUE, + ); + uid_next = last_uid + 1; + } else { + batch.assert_value(Property::EmailIds, ()).value( + Property::EmailIds, + unassigned.len() as u32, + F_VALUE, + ); + } match self.jmap.store.write(batch.build()).await { Ok(_) => (), @@ -281,27 +186,139 @@ impl SessionData { } Err(err) => { tracing::error!(event = "error", - context = "store", - account_id = mailbox.account_id, - collection = ?Collection::Mailbox, - error = ?err, - "Failed to store new uid map"); + context = "store", + account_id = mailbox.account_id, + collection = ?Collection::Mailbox, + mailbox_id = mailbox.mailbox_id, + error = ?err, + "Failed to update UID next"); return Err(StatusResponse::database_failure()); } } - return Ok(MailboxState { - uid_next, - uid_validity, - total_messages: uids.len(), - id_to_imap, - uid_to_id, - uid_max: uid_next.saturating_sub(1), - modseq, - next_state: None, - }); + // Assign UIDs + for (message_id, mut uid_mailbox) in unassigned { + let uid = uid_next; + uid_next += 1; + try_count = 0; + + loop { + if let Some(item) = uid_mailbox + .inner + .iter_mut() + .find(|item| item.mailbox_id == mailbox.mailbox_id) + { + if item.uid == 0 { + item.uid = uid; + + // Increment UID next + let mut batch = BatchBuilder::new(); + batch + .with_account_id(mailbox.account_id) + .with_collection(Collection::Email) + .update_document(message_id) + .assert_value(Property::MailboxIds, &uid_mailbox) + .value(Property::MailboxIds, uid_mailbox.inner, F_VALUE); + + match self.jmap.store.write(batch.build()).await { + Ok(_) => { + if assigned.insert(uid, message_id).is_some() { + tracing::warn!(event = "error", + context = "store", + account_id = mailbox.account_id, + collection = ?Collection::Mailbox, + mailbox_id = mailbox.mailbox_id, + message_id = message_id, + "Duplicate UID"); + } + } + Err(store::Error::AssertValueFailed) + if try_count < MAX_RETRIES => + { + // Another process modified the mailbox ids + if let Some(modified_uid_mailbox) = self + .jmap + .get_property::>>( + mailbox.account_id, + Collection::Email, + message_id, + Property::MailboxIds, + ) + .await? + { + uid_mailbox = modified_uid_mailbox; + try_count += 1; + continue; + } + } + Err(err) => { + tracing::error!(event = "error", + context = "store", + account_id = mailbox.account_id, + collection = ?Collection::Email, + mailbox_id = message_id, + error = ?err, + "Failed to store UID"); + return Err(StatusResponse::database_failure()); + } + } + } else { + // Another thread has already assigned a UID + if item.uid > uid_other { + // Keep track of highest UID assigned by another thread + uid_other = item.uid; + } + + if assigned.insert(item.uid, message_id).is_some() { + tracing::warn!(event = "error", + context = "store", + account_id = mailbox.account_id, + collection = ?Collection::Mailbox, + mailbox_id = mailbox.mailbox_id, + message_id = message_id, + "Duplicate UID assigned by another thread"); + } + } + } + + break; + } + } + } else { + uid_next = last_uid.unwrap_or(0) + 1; } + break; } + + // Other processes might have assigned a higher UID + if uid_next <= uid_other { + uid_next = uid_other + 1; + } + + let mut id_to_imap = AHashMap::with_capacity(assigned.len()); + let mut uid_to_id = AHashMap::with_capacity(assigned.len()); + + for (seqnum, (uid, message_id)) in assigned.into_iter().enumerate() { + id_to_imap.insert( + message_id, + ImapId { + uid, + seqnum: seqnum as u32 + 1, + }, + ); + uid_to_id.insert(uid, message_id); + } + + Ok(MailboxState { + uid_next, + uid_validity, + total_messages: id_to_imap.len(), + id_to_imap, + uid_to_id, + uid_max: uid_next.saturating_sub(1), + modseq, + next_state: None, + }) } pub async fn synchronize_messages( @@ -484,83 +501,3 @@ impl SelectedMailbox { deleted_ids } } - -impl Serialize for &UidMap { - fn serialize(self) -> Vec { - let mut buf = Vec::with_capacity((self.items.len() + 2) * std::mem::size_of::()); - buf.push_leb128(self.items.len()); - buf.push_leb128(self.uid_next); - buf.extend_from_slice(self.uid_validity.to_le_bytes().as_ref()); - buf.extend_from_slice(self.hash.to_le_bytes().as_ref()); - - let mut last_uid = 0; - for item in &self.items { - if last_uid + 1 != item.uid { - buf.push(0); - buf.push_leb128(item.uid); - } - - buf.push_leb128(item.id + 1); - buf.extend_from_slice(item.received.to_le_bytes().as_ref()); - last_uid = item.uid; - } - - buf - } -} - -impl UidMap { - fn deserialize_(bytes: &[u8]) -> Option { - let mut buf_u32 = [0u8; std::mem::size_of::()]; - let mut buf_u64 = [0u8; std::mem::size_of::()]; - - let mut bytes = bytes.iter(); - let items_len: usize = bytes.next_leb128()?; - let uid_next: u32 = bytes.next_leb128()?; - buf_u32 - .iter_mut() - .try_for_each(|b| bytes.next().map(|v| *b = *v))?; - buf_u64 - .iter_mut() - .try_for_each(|b| bytes.next().map(|v| *b = *v))?; - let mut uid_map = UidMap { - uid_next, - uid_validity: u32::from_le_bytes(buf_u32), - hash: u64::from_le_bytes(buf_u64), - items: Vec::with_capacity(items_len), - }; - let mut next_uid: u32 = 1; - for _ in 0..items_len { - let mut id: u32 = bytes.next_leb128()?; - if id == 0 { - next_uid = bytes.next_leb128()?; - id = bytes.next_leb128()?; - } - buf_u32 - .iter_mut() - .try_for_each(|b| bytes.next().map(|v| *b = *v))?; - uid_map.items.push(Uid { - uid: next_uid, - id: id - 1, - received: u32::from_le_bytes(buf_u32), - }); - next_uid += 1; - } - - uid_map.into() - } -} - -impl Deserialize for UidMap { - fn deserialize(bytes: &[u8]) -> store::Result { - Self::deserialize_(bytes).ok_or(store::Error::InternalError( - "Failed to deserialize uid map".to_string(), - )) - } -} - -impl ToBitmaps for &UidMap { - fn to_bitmaps(&self, _: &mut Vec, _: u8, _: bool) { - unreachable!() - } -} diff --git a/crates/imap/src/core/mod.rs b/crates/imap/src/core/mod.rs index 01fd1622..6a5718f4 100644 --- a/crates/imap/src/core/mod.rs +++ b/crates/imap/src/core/mod.rs @@ -49,7 +49,6 @@ use tokio::{ use utils::{ config::Rate, listener::{limiter::InFlight, ServerInstance}, - map::mutex_map::MutexMap, }; pub mod client; @@ -74,8 +73,6 @@ pub struct IMAP { pub max_request_size: usize, pub max_auth_failures: u32, pub name_shared: String, - pub name_all: String, - pub name_all_enable: bool, pub allow_plain_auth: bool, pub enable_uidplus: bool, @@ -114,7 +111,6 @@ pub struct SessionData { pub imap: Arc, pub span: tracing::Span, pub mailboxes: parking_lot::Mutex>, - pub mailbox_locks: MutexMap<()>, pub writer: mpsc::Sender, pub state: AtomicU32, pub in_flight: InFlight, @@ -154,7 +150,7 @@ pub struct SelectedMailbox { #[derive(Debug, PartialEq, Eq, Hash)] pub struct MailboxId { pub account_id: u32, - pub mailbox_id: Option, + pub mailbox_id: u32, } #[derive(Debug)] diff --git a/crates/imap/src/core/writer.rs b/crates/imap/src/core/writer.rs index 86ed287b..e45316c5 100644 --- a/crates/imap/src/core/writer.rs +++ b/crates/imap/src/core/writer.rs @@ -58,7 +58,7 @@ pub fn spawn_writer(mut stream: Event, span: tracing::Span) -> mpsc::Sender { @@ -101,7 +101,7 @@ pub fn spawn_writer(mut stream: Event, span: tracing::Span) -> mpsc::Sender { @@ -132,10 +132,10 @@ impl Session { pub async fn write_bytes(&self, bytes: impl Into>) -> crate::OpResult { let bytes = bytes.into(); - /*let c = println!( - "-> {:?}", + let c = println!( + "{:?}", String::from_utf8_lossy(&bytes[..std::cmp::min(bytes.len(), 100)]) - );*/ + ); if let Err(err) = self.writer.send(Event::Bytes(bytes)).await { debug!("Failed to send bytes: {}", err); @@ -149,10 +149,10 @@ impl Session { impl SessionData { pub async fn write_bytes(&self, bytes: impl Into>) -> bool { let bytes = bytes.into(); - /*let c = println!( - "-> {:?}", + let c = println!( + "{:?}", String::from_utf8_lossy(&bytes[..std::cmp::min(bytes.len(), 100)]) - );*/ + ); if let Err(err) = self.writer.send(Event::Bytes(bytes)).await { debug!("Failed to send bytes: {}", err); diff --git a/crates/imap/src/lib.rs b/crates/imap/src/lib.rs index 9da16038..cafde2f3 100644 --- a/crates/imap/src/lib.rs +++ b/crates/imap/src/lib.rs @@ -47,11 +47,6 @@ impl IMAP { .value("imap.folders.name.shared") .unwrap_or("Shared Folders") .to_string(), - name_all_enable: config.property_or_static("imap.folders.all-messages", "true")?, - name_all: config - .value("imap.folders.name.all") - .unwrap_or("All Mail") - .to_string(), timeout_auth: config.property_or_static("imap.timeout.authenticated", "30m")?, timeout_unauth: config.property_or_static("imap.timeout.anonymous", "1m")?, timeout_idle: config.property_or_static("imap.timeout.idle", "30m")?, diff --git a/crates/imap/src/op/acl.rs b/crates/imap/src/op/acl.rs index 2b7d04ee..3a8b6074 100644 --- a/crates/imap/src/op/acl.rs +++ b/crates/imap/src/op/acl.rs @@ -58,7 +58,7 @@ impl Session { tokio::spawn(async move { match data.get_acl_mailbox(&arguments, true).await { - Ok(Some((_, values, _))) => { + Ok((_, values, _)) => { let mut permissions = Vec::new(); if let Some(acls) = values .inner @@ -137,21 +137,6 @@ impl Session { ) .await; } - Ok(None) => { - // Response for "All Mail" folder - data.write_bytes( - StatusResponse::completed(Command::GetAcl) - .with_tag(arguments.tag) - .serialize( - GetAclResponse { - mailbox_name: arguments.mailbox_name, - permissions: vec![], - } - .into_bytes(is_rev2), - ), - ) - .await; - } Err(response) => { data.write_bytes(response.with_tag(arguments.tag).into_bytes()) .await; @@ -172,7 +157,7 @@ impl Session { tokio::spawn(async move { match data.get_acl_mailbox(&arguments, false).await { - Ok(Some((mailbox, values, access_token))) => { + Ok((mailbox, values, access_token)) => { data.write_bytes( StatusResponse::completed(Command::MyRights) .with_tag(arguments.tag) @@ -227,30 +212,6 @@ impl Session { ) .await; } - Ok(None) => { - // Response for All mail folder - data.write_bytes( - StatusResponse::completed(Command::MyRights) - .with_tag(arguments.tag) - .serialize( - MyRightsResponse { - mailbox_name: arguments.mailbox_name, - rights: vec![ - Rights::Read, - Rights::Lookup, - Rights::Insert, - Rights::DeleteMessages, - Rights::Expunge, - Rights::Seen, - Rights::Write, - Rights::Post, - ], - } - .into_bytes(is_rev2), - ), - ) - .await; - } Err(response) => { data.write_bytes(response.with_tag(arguments.tag).into_bytes()) .await; @@ -272,18 +233,7 @@ impl Session { tokio::spawn(async move { // Validate mailbox let (mailbox, values, _) = match data.get_acl_mailbox(&arguments, true).await { - Ok(Some(result)) => result, - Ok(None) => { - data.write_bytes( - StatusResponse::no( - "ACL operations are not permitted on this mailbox.", - ) - .with_tag(arguments.tag) - .into_bytes(), - ) - .await; - return; - } + Ok(result) => result, Err(response) => { data.write_bytes(response.with_tag(arguments.tag).into_bytes()) .await; @@ -413,7 +363,7 @@ impl Session { } // Write changes - let mailbox_id = mailbox.mailbox_id.unwrap(); + let mailbox_id = mailbox.mailbox_id; let mut batch = BatchBuilder::new(); batch .with_account_id(mailbox.account_id) @@ -525,41 +475,37 @@ impl SessionData { &self, arguments: &Arguments, validate: bool, - ) -> crate::op::Result>, Arc)>> { + ) -> crate::op::Result<(MailboxId, HashedValue>, Arc)> { if let Some(mailbox) = self.get_mailbox_by_name(&arguments.mailbox_name) { - if let Some(mailbox_id) = mailbox.mailbox_id { - match ( - self.jmap - .get_property::>>( - mailbox.account_id, - Collection::Mailbox, - mailbox_id, - Property::Value, + match ( + self.jmap + .get_property::>>( + mailbox.account_id, + Collection::Mailbox, + mailbox.mailbox_id, + Property::Value, + ) + .await, + self.get_access_token().await, + ) { + (Ok(Some(values)), Ok(access_token)) => { + if !validate + || access_token.is_member(mailbox.account_id) + || values + .inner + .effective_acl(&access_token) + .contains(Acl::Administer) + { + Ok((mailbox, values, access_token)) + } else { + Err(StatusResponse::no( + "You do not have enough permissions to perform this operation.", ) - .await, - self.get_access_token().await, - ) { - (Ok(Some(values)), Ok(access_token)) => { - if !validate - || access_token.is_member(mailbox.account_id) - || values - .inner - .effective_acl(&access_token) - .contains(Acl::Administer) - { - Ok(Some((mailbox, values, access_token))) - } else { - Err(StatusResponse::no( - "You do not have enough permissions to perform this operation.", - ) - .with_code(ResponseCode::NoPerm)) - } + .with_code(ResponseCode::NoPerm)) } - (Ok(None), _) => Err(StatusResponse::no("Mailbox no longer exists.")), - _ => Err(StatusResponse::database_failure()), } - } else { - Ok(None) + (Ok(None), _) => Err(StatusResponse::no("Mailbox no longer exists.")), + _ => Err(StatusResponse::database_failure()), } } else { Err(StatusResponse::no("Mailbox does not exist.")) diff --git a/crates/imap/src/op/append.rs b/crates/imap/src/op/append.rs index 4faabb98..1c856818 100644 --- a/crates/imap/src/op/append.rs +++ b/crates/imap/src/op/append.rs @@ -50,20 +50,7 @@ impl Session { // Obtain mailbox let mailbox = if let Some(mailbox) = data.get_mailbox_by_name(&arguments.mailbox_name) { - if mailbox.mailbox_id.is_some() { - mailbox - } else { - return self - .write_bytes( - StatusResponse::no( - "Appending messages to this mailbox is not allowed.", - ) - .with_tag(arguments.tag) - .with_code(ResponseCode::Cannot) - .into_bytes(), - ) - .await; - } + mailbox } else { return self .write_bytes( @@ -106,7 +93,7 @@ impl SessionData { ) -> crate::op::Result { // Verify ACLs let account_id = mailbox.account_id; - let mailbox_id = mailbox.mailbox_id.unwrap(); + let mailbox_id = mailbox.mailbox_id; if !self .check_mailbox_acl(account_id, mailbox_id, Acl::AddItems) .await diff --git a/crates/imap/src/op/copy_move.rs b/crates/imap/src/op/copy_move.rs index c6188db0..499ae2d3 100644 --- a/crates/imap/src/op/copy_move.rs +++ b/crates/imap/src/op/copy_move.rs @@ -28,7 +28,7 @@ use imap_proto::{ StatusResponse, }; -use jmap::email::set::TagManager; +use jmap::{email::set::TagManager, mailbox::UidMailbox}; use jmap_proto::{ error::{method::MethodError, set::SetErrorType}, types::{ @@ -64,20 +64,7 @@ impl Session { // Make sure the mailbox exists. let dest_mailbox = if let Some(mailbox) = data.get_mailbox_by_name(&arguments.mailbox_name) { - if mailbox.mailbox_id.is_some() { - mailbox - } else { - return data - .write_bytes( - StatusResponse::no( - "Appending messages to this mailbox is not allowed.", - ) - .with_tag(arguments.tag) - .with_code(ResponseCode::Cannot) - .into_bytes(), - ) - .await; - } + mailbox } else { return data .write_bytes( @@ -105,20 +92,6 @@ impl Session { .await; } - // Messages cannot me moved out of all folders. - if is_move && src_mailbox.id.mailbox_id.is_none() { - return data - .write_bytes( - StatusResponse::no( - "Moving messages out of this mailbox is not allowed.", - ) - .with_tag(arguments.tag) - .with_code(ResponseCode::Cannot) - .into_bytes(), - ) - .await; - } - if let Err(err) = data .copy_move( arguments, @@ -175,7 +148,7 @@ impl SessionData { && !self .check_mailbox_acl( src_mailbox.id.account_id, - src_mailbox.id.mailbox_id.unwrap_or_default(), + src_mailbox.id.mailbox_id, Acl::RemoveItems, ) .await @@ -188,7 +161,7 @@ impl SessionData { } // Verify that the user can append messages to the destination mailbox. - let dest_mailbox_id = dest_mailbox.mailbox_id.unwrap(); + let dest_mailbox_id = dest_mailbox.mailbox_id; if !self .check_mailbox_acl(dest_mailbox.account_id, dest_mailbox_id, Acl::AddItems) .await @@ -211,6 +184,7 @@ impl SessionData { if src_mailbox.id.account_id == dest_mailbox.account_id { // Mailboxes are in the same account let account_id = src_mailbox.id.account_id; + let dest_mailbox_id = UidMailbox::from(dest_mailbox_id); for (id, imap_id) in ids { // Obtain mailbox tags let (mut mailboxes, thread_id) = if let Some(result) = self @@ -223,10 +197,9 @@ impl SessionData { continue; }; // Make sure the message still belongs to this mailbox - if src_mailbox - .id - .mailbox_id - .map_or(false, |id| !mailboxes.current().contains(&id)) + if !mailboxes + .current() + .contains(&UidMailbox::from(src_mailbox.id.mailbox_id)) || mailboxes.current().contains(&dest_mailbox_id) { tracing::debug!( @@ -240,7 +213,7 @@ impl SessionData { // Add destination folder mailboxes.update(dest_mailbox_id, true); if is_move { - mailboxes.update(src_mailbox.id.mailbox_id.unwrap(), false); + mailboxes.update(UidMailbox::from(src_mailbox.id.mailbox_id), false); } // Write changes @@ -260,12 +233,10 @@ impl SessionData { match self.jmap.write_batch(batch).await { Ok(_) => { changelog.log_update(Collection::Email, Id::from_parts(thread_id, id)); - changelog.log_child_update(Collection::Mailbox, dest_mailbox_id); + changelog.log_child_update(Collection::Mailbox, dest_mailbox_id.mailbox_id); if is_move { - changelog.log_child_update( - Collection::Mailbox, - src_mailbox.id.mailbox_id.unwrap(), - ); + changelog + .log_child_update(Collection::Mailbox, src_mailbox.id.mailbox_id); did_move = true; } copied_ids.push((imap_id, id)); @@ -339,7 +310,7 @@ impl SessionData { }; // Make sure the message is still in the mailbox - let src_mailbox_id = src_mailbox.id.mailbox_id.unwrap(); + let src_mailbox_id = UidMailbox::from(src_mailbox.id.mailbox_id); if !mailboxes.current().contains(&src_mailbox_id) { continue; } else if mailboxes.current().len() == 1 { @@ -378,7 +349,10 @@ impl SessionData { Ok(_) => { changelog .log_update(Collection::Email, Id::from_parts(thread_id, id)); - changelog.log_child_update(Collection::Mailbox, src_mailbox_id); + changelog.log_child_update( + Collection::Mailbox, + src_mailbox_id.mailbox_id, + ); did_move = true; } Err(MethodError::ServerUnavailable) => { @@ -492,11 +466,11 @@ impl SessionData { &self, account_id: u32, id: u32, - ) -> Result, u32)>, MethodError> { + ) -> Result, u32)>, MethodError> { // Obtain mailbox tags if let (Some(mailboxes), Some(thread_id)) = ( self.jmap - .get_property::>>( + .get_property::>>( account_id, Collection::Email, id, diff --git a/crates/imap/src/op/create.rs b/crates/imap/src/op/create.rs index 460694fd..50e7f2ba 100644 --- a/crates/imap/src/op/create.rs +++ b/crates/imap/src/op/create.rs @@ -108,9 +108,13 @@ impl SessionData { return StatusResponse::database_failure().with_tag(arguments.tag); } }; - let mut mailbox = Object::with_capacity(3) + let mut mailbox = Object::with_capacity(4) .with_property(Property::Name, path_item) - .with_property(Property::ParentId, Value::Id(Id::from(parent_id))); + .with_property(Property::ParentId, Value::Id(Id::from(parent_id))) + .with_property( + Property::Cid, + Value::UnsignedInt(rand::random::() as u64), + ); if pos == params.path.len() - 1 { if let Some(mailbox_role) = arguments.mailbox_role { mailbox.set(Property::Role, mailbox_role); @@ -268,12 +272,7 @@ impl SessionData { let (account_id, path) = { let mailboxes = self.mailboxes.lock(); let first_path_item = path.first().unwrap(); - let account = if first_path_item == &self.imap.name_all { - return Err(StatusResponse::no( - "Mailboxes cannot be created under virtual folders.", - ) - .with_code(ResponseCode::Cannot)); - } else if first_path_item == &self.imap.name_shared { + let account = if first_path_item == &self.imap.name_shared { // Shared Folders// if path.len() < 3 { return Err(StatusResponse::no( diff --git a/crates/imap/src/op/delete.rs b/crates/imap/src/op/delete.rs index 8f95d606..b8b44c4a 100644 --- a/crates/imap/src/op/delete.rs +++ b/crates/imap/src/op/delete.rs @@ -21,9 +21,7 @@ * for more details. */ -use imap_proto::{ - protocol::delete::Arguments, receiver::Request, Command, ResponseCode, StatusResponse, -}; +use imap_proto::{protocol::delete::Arguments, receiver::Request, Command, StatusResponse}; use jmap_proto::types::{state::StateChange, type_state::DataType}; use store::write::log::ChangeLogBuilder; use tokio::io::AsyncRead; @@ -66,13 +64,7 @@ impl SessionData { // Validate mailbox let (account_id, mailbox_id) = if let Some(mailbox) = self.get_mailbox_by_name(&arguments.mailbox_name) { - if let Some(mailbox_id) = mailbox.mailbox_id { - (mailbox.account_id, mailbox_id) - } else { - return StatusResponse::no("Folder cannot be deleted.") - .with_code(ResponseCode::Cannot) - .with_tag(arguments.tag); - } + (mailbox.account_id, mailbox.mailbox_id) } else { return StatusResponse::no("Mailbox does not exist.").with_tag(arguments.tag); }; diff --git a/crates/imap/src/op/expunge.rs b/crates/imap/src/op/expunge.rs index fe545320..32ebf1a9 100644 --- a/crates/imap/src/op/expunge.rs +++ b/crates/imap/src/op/expunge.rs @@ -30,7 +30,7 @@ use imap_proto::{ Command, ResponseCode, StatusResponse, }; -use jmap::email::set::TagManager; +use jmap::{email::set::TagManager, mailbox::UidMailbox}; use jmap_proto::{ error::method::MethodError, types::{ @@ -55,7 +55,7 @@ impl Session { match data .check_mailbox_acl( mailbox.id.account_id, - mailbox.id.mailbox_id.unwrap_or_default(), + mailbox.id.mailbox_id, Acl::RemoveItems, ) .await @@ -132,28 +132,18 @@ impl SessionData { ) -> crate::op::Result<()> { // Obtain message ids let account_id = mailbox.id.account_id; - let deleted_ids = if let Some(mailbox_id) = mailbox.id.mailbox_id { - self.jmap - .get_tag( - account_id, - Collection::Email, - Property::MailboxIds, - mailbox_id, - ) - .await? - .unwrap_or_default() - & self - .jmap - .get_tag( - account_id, - Collection::Email, - Property::Keywords, - Keyword::Deleted, - ) - .await? - .unwrap_or_default() - } else { - self.jmap + let deleted_ids = self + .jmap + .get_tag( + account_id, + Collection::Email, + Property::MailboxIds, + mailbox.id.mailbox_id, + ) + .await? + .unwrap_or_default() + & self + .jmap .get_tag( account_id, Collection::Email, @@ -161,8 +151,7 @@ impl SessionData { Keyword::Deleted, ) .await? - .unwrap_or_default() - }; + .unwrap_or_default(); // Delete ids let mut changelog = ChangeLogBuilder::new(); @@ -174,63 +163,58 @@ impl SessionData { continue; } - if let Some(mailbox_id) = mailbox.id.mailbox_id { - // If the message is present in multiple mailboxes, untag it from this mailbox. - let (mut mailboxes, thread_id) = - if let Some(result) = self.get_mailbox_tags(account_id, id).await? { - result - } else { - continue; - }; - if !mailboxes.current().contains(&mailbox_id) { - continue; - } else if mailboxes.current().len() > 1 { - // Remove deleted flag - let mut keywords = if let Some(keywords) = self - .jmap - .get_property::>>( - account_id, - Collection::Email, - id, - Property::Keywords, - ) - .await? - { - TagManager::new(keywords) - } else { - continue; - }; - - // Untag message from this mailbox and remove Deleted flag - mailboxes.update(mailbox_id, false); - keywords.update(Keyword::Deleted, false); - - // Write changes - let mut batch = BatchBuilder::new(); - batch - .with_account_id(account_id) - .with_collection(Collection::Email) - .update_document(id); - mailboxes.update_batch(&mut batch, Property::MailboxIds); - keywords.update_batch(&mut batch, Property::Keywords); - if changelog.change_id == u64::MAX { - changelog.change_id = self.jmap.assign_change_id(account_id).await? - } - batch.value(Property::Cid, changelog.change_id, F_VALUE); - match self.jmap.write_batch(batch).await { - Ok(_) => { - changelog.log_update(Collection::Email, Id::from_parts(thread_id, id)); - changelog.log_child_update(Collection::Mailbox, mailbox_id); - } - Err(MethodError::ServerUnavailable) => {} - Err(_) => { - return Err(StatusResponse::database_failure()); - } - } + // If the message is present in multiple mailboxes, untag it from this mailbox. + let mailbox_id = mailbox.id.mailbox_id; + let (mut mailboxes, thread_id) = + if let Some(result) = self.get_mailbox_tags(account_id, id).await? { + result } else { - // Delete message from all mailboxes - if let Ok(changes) = self.jmap.email_delete(account_id, id).await? { - changelog.merge(changes); + continue; + }; + let mailbox_id = UidMailbox::from(mailbox_id); + if !mailboxes.current().contains(&mailbox_id) { + continue; + } else if mailboxes.current().len() > 1 { + // Remove deleted flag + let mut keywords = if let Some(keywords) = self + .jmap + .get_property::>>( + account_id, + Collection::Email, + id, + Property::Keywords, + ) + .await? + { + TagManager::new(keywords) + } else { + continue; + }; + + // Untag message from this mailbox and remove Deleted flag + mailboxes.update(mailbox_id, false); + keywords.update(Keyword::Deleted, false); + + // Write changes + let mut batch = BatchBuilder::new(); + batch + .with_account_id(account_id) + .with_collection(Collection::Email) + .update_document(id); + mailboxes.update_batch(&mut batch, Property::MailboxIds); + keywords.update_batch(&mut batch, Property::Keywords); + if changelog.change_id == u64::MAX { + changelog.change_id = self.jmap.assign_change_id(account_id).await? + } + batch.value(Property::Cid, changelog.change_id, F_VALUE); + match self.jmap.write_batch(batch).await { + Ok(_) => { + changelog.log_update(Collection::Email, Id::from_parts(thread_id, id)); + changelog.log_child_update(Collection::Mailbox, mailbox_id.mailbox_id); + } + Err(MethodError::ServerUnavailable) => {} + Err(_) => { + return Err(StatusResponse::database_failure()); } } } else { diff --git a/crates/imap/src/op/fetch.rs b/crates/imap/src/op/fetch.rs index 17c82264..8ab163c6 100644 --- a/crates/imap/src/op/fetch.rs +++ b/crates/imap/src/op/fetch.rs @@ -245,7 +245,7 @@ impl SessionData { && !self .check_mailbox_acl( mailbox.id.account_id, - mailbox.id.mailbox_id.unwrap_or_default(), + mailbox.id.mailbox_id, Acl::ModifyItems, ) .await diff --git a/crates/imap/src/op/list.rs b/crates/imap/src/op/list.rs index bc1f1c75..cdcb0198 100644 --- a/crates/imap/src/op/list.rs +++ b/crates/imap/src/op/list.rs @@ -174,18 +174,6 @@ impl SessionData { let mut list_items = Vec::with_capacity(10); - // Add "All Mail" folder - if self.imap.name_all_enable - && !filter_subscribed - && matches_pattern(&patterns, &self.imap.name_all) - { - list_items.push(ListItem { - mailbox_name: self.imap.name_all.clone(), - attributes: vec![Attribute::All, Attribute::NoInferiors], - tags: vec![], - }); - } - // Add mailboxes let mut added_shared_folder = false; for account in self.mailboxes.lock().iter() { diff --git a/crates/imap/src/op/rename.rs b/crates/imap/src/op/rename.rs index fea2dd30..17ff5b00 100644 --- a/crates/imap/src/op/rename.rs +++ b/crates/imap/src/op/rename.rs @@ -165,9 +165,13 @@ impl SessionData { }; batch.create_document(mailbox_id).custom( ObjectIndexBuilder::new(SCHEMA).with_changes( - Object::with_capacity(2) + Object::with_capacity(3) .with_property(Property::Name, path_item) - .with_property(Property::ParentId, Value::Id(Id::from(parent_id))), + .with_property(Property::ParentId, Value::Id(Id::from(parent_id))) + .with_property( + Property::Cid, + Value::UnsignedInt(rand::random::() as u64), + ), ), ); changes.log_insert(Collection::Mailbox, mailbox_id); @@ -178,9 +182,13 @@ impl SessionData { ObjectIndexBuilder::new(SCHEMA) .with_current(mailbox) .with_changes( - Object::with_capacity(2) + Object::with_capacity(3) .with_property(Property::Name, new_mailbox_name) - .with_property(Property::ParentId, Value::Id(Id::from(parent_id))), + .with_property(Property::ParentId, Value::Id(Id::from(parent_id))) + .with_property( + Property::Cid, + Value::UnsignedInt(rand::random::() as u64), + ), ), ); changes.log_update(Collection::Mailbox, mailbox_id); diff --git a/crates/imap/src/op/search.rs b/crates/imap/src/op/search.rs index 270536d2..dac52910 100644 --- a/crates/imap/src/op/search.rs +++ b/crates/imap/src/op/search.rs @@ -254,25 +254,17 @@ impl SessionData { ) -> Result<(ResultSet, bool), StatusResponse> { // Obtain message ids let mut filters = Vec::with_capacity(imap_filter.len() + 1); - let message_ids = if let Some(mailbox_id) = mailbox.id.mailbox_id { - let ids = self - .jmap - .get_tag( - mailbox.id.account_id, - Collection::Email, - Property::MailboxIds, - mailbox_id, - ) - .await? - .unwrap_or_default(); - filters.push(query::Filter::is_in_set(ids.clone())); - ids - } else { - self.jmap - .get_document_ids(mailbox.id.account_id, Collection::Email) - .await? - .unwrap_or_default() - }; + let message_ids = self + .jmap + .get_tag( + mailbox.id.account_id, + Collection::Email, + Property::MailboxIds, + mailbox.id.mailbox_id, + ) + .await? + .unwrap_or_default(); + filters.push(query::Filter::is_in_set(message_ids.clone())); // Convert query let mut include_highest_modseq = false; diff --git a/crates/imap/src/op/select.rs b/crates/imap/src/op/select.rs index 8805c70d..00dd139c 100644 --- a/crates/imap/src/op/select.rs +++ b/crates/imap/src/op/select.rs @@ -124,7 +124,7 @@ impl Session { highest_modseq, mailbox_id: Id::from_parts( mailbox.id.account_id, - mailbox.id.mailbox_id.unwrap_or(u32::MAX), + mailbox.id.mailbox_id, ) .to_string(), }; diff --git a/crates/imap/src/op/status.rs b/crates/imap/src/op/status.rs index 0095a422..37ea70d9 100644 --- a/crates/imap/src/op/status.rs +++ b/crates/imap/src/op/status.rs @@ -133,7 +133,7 @@ impl SessionData { if account.account_id == mailbox.account_id { let mailbox_state = account .mailbox_state - .entry(mailbox.mailbox_id.as_ref().cloned().unwrap_or(u32::MAX)) + .entry(mailbox.mailbox_id) .or_insert_with(Mailbox::default); for item in items { match item { @@ -191,11 +191,8 @@ impl SessionData { items_response.push(( *item, StatusItemType::String( - Id::from_parts( - mailbox.account_id, - mailbox.mailbox_id.unwrap_or(u32::MAX), - ) - .to_string(), + Id::from_parts(mailbox.account_id, mailbox.mailbox_id) + .to_string(), ), )); } @@ -217,140 +214,83 @@ impl SessionData { None }; - if let Some(mailbox_id) = mailbox.mailbox_id { - let mailbox_message_ids = self - .jmap - .get_tag( - mailbox.account_id, - Collection::Email, - Property::MailboxIds, - mailbox_id, - ) - .await? - .map(Arc::new); - let message_ids = self - .jmap - .get_document_ids(mailbox.account_id, Collection::Email) - .await?; + let mailbox_message_ids = self + .jmap + .get_tag( + mailbox.account_id, + Collection::Email, + Property::MailboxIds, + mailbox.mailbox_id, + ) + .await? + .map(Arc::new); + let message_ids = self + .jmap + .get_document_ids(mailbox.account_id, Collection::Email) + .await?; - for item in items_update { - let result = match item { - Status::Messages => { - mailbox_message_ids.as_ref().map(|v| v.len()).unwrap_or(0) - } - Status::UidNext => mailbox_state.as_ref().unwrap().uid_next as u64, - Status::UidValidity => mailbox_state.as_ref().unwrap().uid_validity as u64, - Status::Unseen => { - if let (Some(message_ids), Some(mailbox_message_ids)) = - (&message_ids, &mailbox_message_ids) + for item in items_update { + let result = match item { + Status::Messages => mailbox_message_ids.as_ref().map(|v| v.len()).unwrap_or(0), + Status::UidNext => mailbox_state.as_ref().unwrap().uid_next as u64, + Status::UidValidity => mailbox_state.as_ref().unwrap().uid_validity as u64, + Status::Unseen => { + if let (Some(message_ids), Some(mailbox_message_ids)) = + (&message_ids, &mailbox_message_ids) + { + if let Some(mut seen) = self + .jmap + .get_tag( + mailbox.account_id, + Collection::Email, + Property::Keywords, + Keyword::Seen, + ) + .await? { - if let Some(mut seen) = self - .jmap - .get_tag( - mailbox.account_id, - Collection::Email, - Property::Keywords, - Keyword::Seen, - ) - .await? - { - seen ^= message_ids; - seen &= mailbox_message_ids.as_ref(); - seen.len() - } else { - mailbox_message_ids.len() - } - } else { - 0 - } - } - Status::Deleted => { - if let (Some(mailbox_message_ids), Some(mut deleted)) = ( - &mailbox_message_ids, - self.jmap - .get_tag( - mailbox.account_id, - Collection::Email, - Property::Keywords, - Keyword::Deleted, - ) - .await?, - ) { - deleted &= mailbox_message_ids.as_ref(); - deleted.len() - } else { - 0 - } - } - Status::Size => { - if let Some(mailbox_message_ids) = &mailbox_message_ids { - self.calculate_mailbox_size(mailbox.account_id, mailbox_message_ids) - .await? as u64 - } else { - 0 - } - } - Status::HighestModSeq | Status::MailboxId | Status::Recent => { - unreachable!() - } - }; - - items_response.push((item, StatusItemType::Number(result))); - values_update.push((item, result as u32)); - } - } else { - let message_ids = Arc::new( - self.jmap - .get_document_ids(mailbox.account_id, Collection::Email) - .await? - .unwrap_or_default(), - ); - for item in items_update { - let result = match item { - Status::Messages => message_ids.len(), - Status::UidNext => mailbox_state.as_ref().unwrap().uid_next as u64, - Status::UidValidity => mailbox_state.as_ref().unwrap().uid_validity as u64, - Status::Unseen => self - .jmap - .get_tag( - mailbox.account_id, - Collection::Email, - Property::Keywords, - Keyword::Seen, - ) - .await? - .map(|mut seen| { - seen ^= message_ids.as_ref(); + seen ^= message_ids; + seen &= mailbox_message_ids.as_ref(); seen.len() - }) - .unwrap_or_else(|| message_ids.len()), - Status::Deleted => self - .jmap - .get_tag( - mailbox.account_id, - Collection::Email, - Property::Keywords, - Keyword::Deleted, - ) - .await? - .map(|v| v.len()) - .unwrap_or(0), - Status::Size => { - if !message_ids.is_empty() { - self.calculate_mailbox_size(mailbox.account_id, &message_ids) - .await? as u64 } else { - 0 + mailbox_message_ids.len() } + } else { + 0 } - Status::HighestModSeq | Status::MailboxId | Status::Recent => { - unreachable!() + } + Status::Deleted => { + if let (Some(mailbox_message_ids), Some(mut deleted)) = ( + &mailbox_message_ids, + self.jmap + .get_tag( + mailbox.account_id, + Collection::Email, + Property::Keywords, + Keyword::Deleted, + ) + .await?, + ) { + deleted &= mailbox_message_ids.as_ref(); + deleted.len() + } else { + 0 } - }; + } + Status::Size => { + if let Some(mailbox_message_ids) = &mailbox_message_ids { + self.calculate_mailbox_size(mailbox.account_id, mailbox_message_ids) + .await? as u64 + } else { + 0 + } + } + Status::HighestModSeq | Status::MailboxId | Status::Recent => { + unreachable!() + } + }; - items_response.push((item, StatusItemType::Number(result))); - values_update.push((item, result as u32)); - } + items_response.push((item, StatusItemType::Number(result))); + values_update.push((item, result as u32)); } // Update cache @@ -358,7 +298,7 @@ impl SessionData { if account.account_id == mailbox.account_id { let mailbox_state = account .mailbox_state - .entry(mailbox.mailbox_id.as_ref().cloned().unwrap_or(u32::MAX)) + .entry(mailbox.mailbox_id) .or_insert_with(Mailbox::default); for (item, value) in values_update { diff --git a/crates/imap/src/op/store.rs b/crates/imap/src/op/store.rs index f2d5e0e3..a7569a14 100644 --- a/crates/imap/src/op/store.rs +++ b/crates/imap/src/op/store.rs @@ -33,7 +33,7 @@ use imap_proto::{ receiver::Request, Command, ResponseCode, ResponseType, StatusResponse, }; -use jmap::email::set::TagManager; +use jmap::{email::set::TagManager, mailbox::UidMailbox}; use jmap_proto::{ error::method::MethodError, types::{ @@ -47,7 +47,7 @@ use store::{ }; use tokio::io::AsyncRead; -use crate::core::{SelectedMailbox, Session, SessionData}; +use crate::core::{message::MAX_RETRIES, SelectedMailbox, Session, SessionData}; use super::FromModSeq; @@ -112,7 +112,7 @@ impl SessionData { if !self .check_mailbox_acl( mailbox.id.account_id, - mailbox.id.mailbox_id.unwrap_or_default(), + mailbox.id.mailbox_id, Acl::ModifyItems, ) .await @@ -197,142 +197,153 @@ impl SessionData { .collect::>(); let mut changelog = ChangeLogBuilder::new(); let mut changed_mailboxes = AHashSet::new(); - for (id, imap_id) in ids { - // Obtain current keywords - let (mut keywords, thread_id) = if let (Some(keywords), Some(thread_id)) = ( - self.jmap - .get_property::>>( - account_id, - Collection::Email, - id, - Property::Keywords, - ) - .await - .map_err(|_| { - StatusResponse::database_failure().with_tag(response.tag.as_ref().unwrap()) - })?, - self.jmap - .get_property::(account_id, Collection::Email, id, Property::ThreadId) - .await - .map_err(|_| { - StatusResponse::database_failure().with_tag(response.tag.as_ref().unwrap()) - })?, - ) { - (TagManager::new(keywords), thread_id) - } else { - continue; - }; - - // Apply changes - match arguments.operation { - Operation::Set => { - keywords.set(set_keywords.clone()); - } - Operation::Add => { - for keyword in &set_keywords { - keywords.update(keyword.clone(), true); - } - } - Operation::Clear => { - for keyword in &set_keywords { - keywords.update(keyword.clone(), false); - } - } - } - - if keywords.has_changes() { - // Convert keywords to flags - let seen_changed = keywords - .changed_tags() - .any(|keyword| keyword == &Keyword::Seen); - let flags = if !arguments.is_silent { - keywords - .current() - .iter() - .cloned() - .map(Flag::from) - .collect::>() - } else { - vec![] - }; - - // Write changes - let mut batch = BatchBuilder::new(); - batch - .with_account_id(account_id) - .with_collection(Collection::Email) - .update_document(id); - keywords.update_batch(&mut batch, Property::Keywords); - if changelog.change_id == u64::MAX { - changelog.change_id = - self.jmap.assign_change_id(account_id).await.map_err(|_| { + 'outer: for (id, imap_id) in ids { + let mut try_count = 0; + loop { + // Obtain current keywords + let (mut keywords, thread_id) = if let (Some(keywords), Some(thread_id)) = ( + self.jmap + .get_property::>>( + account_id, + Collection::Email, + id, + Property::Keywords, + ) + .await + .map_err(|_| { StatusResponse::database_failure() .with_tag(response.tag.as_ref().unwrap()) - })? + })?, + self.jmap + .get_property::(account_id, Collection::Email, id, Property::ThreadId) + .await + .map_err(|_| { + StatusResponse::database_failure() + .with_tag(response.tag.as_ref().unwrap()) + })?, + ) { + (TagManager::new(keywords), thread_id) + } else { + continue 'outer; + }; + + // Apply changes + match arguments.operation { + Operation::Set => { + keywords.set(set_keywords.clone()); + } + Operation::Add => { + for keyword in &set_keywords { + keywords.update(keyword.clone(), true); + } + } + Operation::Clear => { + for keyword in &set_keywords { + keywords.update(keyword.clone(), false); + } + } } - batch.value(Property::Cid, changelog.change_id, F_VALUE); - match self.jmap.write_batch(batch).await { - Ok(_) => { - // Set all current mailboxes as changed if the Seen tag changed - if seen_changed { - if let Some(mailboxes) = self - .jmap - .get_property::>( - account_id, - Collection::Email, - id, - Property::MailboxIds, - ) - .await - .map_err(|_| { - StatusResponse::database_failure() - .with_tag(response.tag.as_ref().unwrap()) - })? - { - for mailbox_id in mailboxes { - changed_mailboxes.insert(mailbox_id); + + if keywords.has_changes() { + // Convert keywords to flags + let seen_changed = keywords + .changed_tags() + .any(|keyword| keyword == &Keyword::Seen); + let flags = if !arguments.is_silent { + keywords + .current() + .iter() + .cloned() + .map(Flag::from) + .collect::>() + } else { + vec![] + }; + + // Write changes + let mut batch = BatchBuilder::new(); + batch + .with_account_id(account_id) + .with_collection(Collection::Email) + .update_document(id); + keywords.update_batch(&mut batch, Property::Keywords); + if changelog.change_id == u64::MAX { + changelog.change_id = + self.jmap.assign_change_id(account_id).await.map_err(|_| { + StatusResponse::database_failure() + .with_tag(response.tag.as_ref().unwrap()) + })? + } + batch.value(Property::Cid, changelog.change_id, F_VALUE); + match self.jmap.write_batch(batch).await { + Ok(_) => { + // Set all current mailboxes as changed if the Seen tag changed + if seen_changed { + if let Some(mailboxes) = self + .jmap + .get_property::>( + account_id, + Collection::Email, + id, + Property::MailboxIds, + ) + .await + .map_err(|_| { + StatusResponse::database_failure() + .with_tag(response.tag.as_ref().unwrap()) + })? + { + for mailbox_id in mailboxes { + changed_mailboxes.insert(mailbox_id.mailbox_id); + } } } - } - changelog.log_update(Collection::Email, Id::from_parts(thread_id, id)); + changelog.log_update(Collection::Email, Id::from_parts(thread_id, id)); - // Add item to response - let modseq = changelog.change_id + 1; - if !arguments.is_silent { - let mut data_items = vec![DataItem::Flags { flags }]; - if is_uid { - data_items.push(DataItem::Uid { uid: imap_id.uid }); + // Add item to response + let modseq = changelog.change_id + 1; + if !arguments.is_silent { + let mut data_items = vec![DataItem::Flags { flags }]; + if is_uid { + data_items.push(DataItem::Uid { uid: imap_id.uid }); + } + if is_condstore { + data_items.push(DataItem::ModSeq { modseq }); + } + items.items.push(FetchItem { + id: imap_id.seqnum, + items: data_items, + }); + } else if is_condstore { + items.items.push(FetchItem { + id: imap_id.seqnum, + items: if is_uid { + vec![ + DataItem::ModSeq { modseq }, + DataItem::Uid { uid: imap_id.uid }, + ] + } else { + vec![DataItem::ModSeq { modseq }] + }, + }); } - if is_condstore { - data_items.push(DataItem::ModSeq { modseq }); - } - items.items.push(FetchItem { - id: imap_id.seqnum, - items: data_items, - }); - } else if is_condstore { - items.items.push(FetchItem { - id: imap_id.seqnum, - items: if is_uid { - vec![ - DataItem::ModSeq { modseq }, - DataItem::Uid { uid: imap_id.uid }, - ] - } else { - vec![DataItem::ModSeq { modseq }] - }, - }); } - } - Err(MethodError::ServerUnavailable) => { - response.rtype = ResponseType::No; - response.message = "Some messages could not be updated.".into(); - } - Err(_) => { - return Err(StatusResponse::database_failure() - .with_tag(response.tag.as_ref().unwrap())); + Err(MethodError::ServerUnavailable) => { + if try_count < MAX_RETRIES { + try_count += 1; + continue; + } else { + response.rtype = ResponseType::No; + response.message = "Some messages could not be updated.".into(); + } + } + Err(_) => { + return Err(StatusResponse::database_failure() + .with_tag(response.tag.as_ref().unwrap())); + } } } + break; } } diff --git a/crates/imap/src/op/subscribe.rs b/crates/imap/src/op/subscribe.rs index c383865e..a7a44be5 100644 --- a/crates/imap/src/op/subscribe.rs +++ b/crates/imap/src/op/subscribe.rs @@ -74,15 +74,7 @@ impl SessionData { // Validate mailbox let (account_id, mailbox_id) = match self.get_mailbox_by_name(&mailbox_name) { - Some(mailbox) => { - if let Some(mailbox_id) = mailbox.mailbox_id { - (mailbox.account_id, mailbox_id) - } else { - return StatusResponse::no("Subscribing to this mailbox is not supported.") - .with_tag(tag) - .with_code(ResponseCode::Cannot); - } - } + Some(mailbox) => (mailbox.account_id, mailbox.mailbox_id), None => { return StatusResponse::no("Mailbox does not exist.") .with_tag(tag) diff --git a/crates/jmap/Cargo.toml b/crates/jmap/Cargo.toml index adb88759..75a3b144 100644 --- a/crates/jmap/Cargo.toml +++ b/crates/jmap/Cargo.toml @@ -18,16 +18,16 @@ mail-send = { git = "https://github.com/stalwartlabs/mail-send", default-feature sieve-rs = { git = "https://github.com/stalwartlabs/sieve" } serde = { version = "1.0", features = ["derive"]} serde_json = "1.0" -hyper = { version = "1.0.0-rc.4", features = ["server", "http1", "http2"] } -hyper-util = { git = "https://github.com/hyperium/hyper-util" } -http-body-util = "0.1.0-rc.3" +hyper = { version = "1.0.1", features = ["server", "http1", "http2"] } +hyper-util = { version = "0.1.1", features = ["tokio"] } +http-body-util = "0.1.0" form_urlencoded = "1.1.0" tracing = "0.1" tokio = { version = "1.23", features = ["rt"] } aes-gcm = "0.10.1" aes-gcm-siv = "0.11.1" bincode = "1.3.3" -form-data = { version = "0.4.2", features = ["sync"], default-features = false } +form-data = { version = "0.5.0", features = ["sync"], default-features = false } mime = "0.3.17" sqlx = { version = "0.7", features = [ "runtime-tokio-rustls", "postgres", "mysql", "sqlite" ] } futures-util = "0.3.28" diff --git a/crates/jmap/src/blob/get.rs b/crates/jmap/src/blob/get.rs index 95106d76..06b72ae0 100644 --- a/crates/jmap/src/blob/get.rs +++ b/crates/jmap/src/blob/get.rs @@ -43,7 +43,7 @@ use sha2::{Sha256, Sha512}; use store::BlobClass; use utils::map::vec_map::VecMap; -use crate::{auth::AccessToken, JMAP}; +use crate::{auth::AccessToken, mailbox::UidMailbox, JMAP}; impl JMAP { pub async fn blob_get( @@ -242,7 +242,7 @@ impl JMAP { } if include_mailbox { if let Some(mailboxes) = self - .get_property::>( + .get_property::>( req_account_id, Collection::Email, *document_id, @@ -252,7 +252,10 @@ impl JMAP { { matched_ids.append( DataType::Mailbox, - mailboxes.into_iter().map(Id::from).collect::>(), + mailboxes + .into_iter() + .map(|m| Id::from(m.mailbox_id)) + .collect::>(), ); } } diff --git a/crates/jmap/src/email/copy.rs b/crates/jmap/src/email/copy.rs index de34b465..57a488e9 100644 --- a/crates/jmap/src/email/copy.rs +++ b/crates/jmap/src/email/copy.rs @@ -53,7 +53,9 @@ use store::{ }; use utils::map::vec_map::VecMap; -use crate::{auth::AccessToken, services::housekeeper::Event, Bincode, NamedKey, JMAP}; +use crate::{ + auth::AccessToken, mailbox::UidMailbox, services::housekeeper::Event, Bincode, NamedKey, JMAP, +}; use super::{ index::{EmailIndexBuilder, TrimTextValue, MAX_SORT_FIELD_LENGTH}, @@ -402,7 +404,14 @@ impl JMAP { .with_collection(Collection::Email) .create_document(message_id) .value(Property::ThreadId, thread_id, F_VALUE | F_BITMAP) - .value(Property::MailboxIds, mailboxes, F_VALUE | F_BITMAP) + .value( + Property::MailboxIds, + mailboxes + .into_iter() + .map(UidMailbox::from) + .collect::>(), + F_VALUE | F_BITMAP, + ) .value(Property::Keywords, keywords, F_VALUE | F_BITMAP) .value(Property::Cid, changes.change_id, F_VALUE) .set( diff --git a/crates/jmap/src/email/get.rs b/crates/jmap/src/email/get.rs index c47e6b9d..fe05fe05 100644 --- a/crates/jmap/src/email/get.rs +++ b/crates/jmap/src/email/get.rs @@ -39,7 +39,7 @@ use jmap_proto::{ use mail_parser::HeaderName; use store::BlobClass; -use crate::{auth::AccessToken, email::headers::HeaderToValue, Bincode, JMAP}; +use crate::{auth::AccessToken, email::headers::HeaderToValue, mailbox::UidMailbox, Bincode, JMAP}; use super::{ body::{ToBodyPart, TruncateBody}, @@ -222,7 +222,7 @@ impl JMAP { } Property::MailboxIds => { if let Some(mailboxes) = self - .get_property::>( + .get_property::>( account_id, Collection::Email, id.document_id(), @@ -232,7 +232,10 @@ impl JMAP { .map(|ids| { let mut obj = Object::with_capacity(ids.len()); for id in ids { - obj.append(Property::_T(Id::from(id).to_string()), true); + obj.append( + Property::_T(Id::from(id.mailbox_id).to_string()), + true, + ); } Value::Object(obj) }) diff --git a/crates/jmap/src/email/index.rs b/crates/jmap/src/email/index.rs index 8dac003c..09320b65 100644 --- a/crates/jmap/src/email/index.rs +++ b/crates/jmap/src/email/index.rs @@ -38,7 +38,7 @@ use store::{ BlobHash, }; -use crate::{Bincode, NamedKey}; +use crate::{mailbox::UidMailbox, Bincode, NamedKey}; use super::metadata::MessageMetadata; @@ -60,7 +60,7 @@ pub(super) trait IndexMessage { message: Message, blob_hash: BlobHash, keywords: Vec, - mailbox_ids: Vec, + mailbox_ids: Vec, received_at: u64, ) -> &mut Self; @@ -77,7 +77,7 @@ impl IndexMessage for BatchBuilder { message: Message, blob_hash: BlobHash, keywords: Vec, - mailbox_ids: Vec, + mailbox_ids: Vec, received_at: u64, ) -> &mut Self { // Index keywords diff --git a/crates/jmap/src/email/ingest.rs b/crates/jmap/src/email/ingest.rs index 9fda9e88..07b43850 100644 --- a/crates/jmap/src/email/ingest.rs +++ b/crates/jmap/src/email/ingest.rs @@ -47,6 +47,7 @@ use utils::map::vec_map::VecMap; use crate::{ email::index::{IndexMessage, MAX_ID_LENGTH}, + mailbox::UidMailbox, services::housekeeper::Event, IngestError, NamedKey, JMAP, }; @@ -305,7 +306,11 @@ impl JMAP { message, blob_id.hash.clone(), params.keywords, - params.mailbox_ids, + params + .mailbox_ids + .into_iter() + .map(UidMailbox::from) + .collect(), params.received_at.unwrap_or_else(now), ) .value(Property::Cid, change_id, F_VALUE) diff --git a/crates/jmap/src/email/metadata.rs b/crates/jmap/src/email/metadata.rs index 21b5f39a..e51eec1e 100644 --- a/crates/jmap/src/email/metadata.rs +++ b/crates/jmap/src/email/metadata.rs @@ -34,7 +34,7 @@ use mail_parser::{ use serde::{Deserialize, Serialize}; use store::BlobHash; -#[derive(Serialize, Deserialize)] +#[derive(Debug, Serialize, Deserialize)] pub struct MessageMetadata<'x> { pub contents: MessageMetadataContents<'x>, pub blob_hash: BlobHash, @@ -44,7 +44,7 @@ pub struct MessageMetadata<'x> { pub has_attachments: bool, } -#[derive(Serialize, Deserialize)] +#[derive(Debug, Serialize, Deserialize)] pub struct MessageMetadataContents<'x> { pub html_body: Vec, pub text_body: Vec, @@ -52,7 +52,7 @@ pub struct MessageMetadataContents<'x> { pub parts: Vec>, } -#[derive(Serialize, Deserialize)] +#[derive(Debug, Serialize, Deserialize)] pub struct MessageMetadataPart<'x> { pub headers: Vec>, pub is_encoding_problem: bool, @@ -64,7 +64,7 @@ pub struct MessageMetadataPart<'x> { pub offset_end: usize, } -#[derive(Serialize, Deserialize)] +#[derive(Debug, Serialize, Deserialize)] pub enum MetadataPartType<'x> { Text, Html, diff --git a/crates/jmap/src/email/set.rs b/crates/jmap/src/email/set.rs index b0a53637..7d42962a 100644 --- a/crates/jmap/src/email/set.rs +++ b/crates/jmap/src/email/set.rs @@ -60,7 +60,8 @@ use store::{ }; use crate::{ - auth::AccessToken, services::housekeeper::Event, Bincode, IngestError, NamedKey, JMAP, + auth::AccessToken, mailbox::UidMailbox, services::housekeeper::Event, Bincode, IngestError, + NamedKey, JMAP, }; use super::{ @@ -771,7 +772,7 @@ impl JMAP { // Obtain current keywords and mailboxes let document_id = id.document_id(); let (mut mailboxes, mut keywords) = if let (Some(mailboxes), Some(keywords)) = ( - self.get_property::>>( + self.get_property::>>( account_id, Collection::Email, document_id, @@ -810,7 +811,9 @@ impl JMAP { (Property::MailboxIds, MaybePatchValue::Value(Value::List(ids))) => { mailboxes.set( ids.into_iter() - .filter_map(|id| id.try_unwrap_id()?.document_id().into()) + .filter_map(|id| { + UidMailbox::from(id.try_unwrap_id()?.document_id()).into() + }) .collect(), ); } @@ -818,7 +821,7 @@ impl JMAP { let mut patch = patch.into_iter(); if let Some(id) = patch.next().unwrap().try_unwrap_id() { mailboxes.update( - id.document_id(), + UidMailbox::from(id.document_id()), patch.next().unwrap().try_unwrap_bool().unwrap_or_default(), ); } @@ -879,7 +882,7 @@ impl JMAP { .any(|keyword| keyword == &Keyword::Seen) { for mailbox_id in mailboxes.current() { - changed_mailboxes.insert(*mailbox_id); + changed_mailboxes.insert(mailbox_id.mailbox_id); } } @@ -908,16 +911,17 @@ impl JMAP { // Make sure all new mailboxIds are valid for mailbox_id in mailboxes.added() { - if mailbox_ids.contains(*mailbox_id) { + if mailbox_ids.contains(mailbox_id.mailbox_id) { // Verify permissions on shared accounts - if !matches!(&can_add_mailbox_ids, Some(ids) if !ids.contains(*mailbox_id)) + if !matches!(&can_add_mailbox_ids, Some(ids) if !ids.contains(mailbox_id.mailbox_id)) { - changed_mailboxes.insert(*mailbox_id); + changed_mailboxes.insert(mailbox_id.mailbox_id); } else { response.not_updated.append( id, SetError::forbidden().with_description(format!( - "You are not allowed to add messages to mailbox {mailbox_id}." + "You are not allowed to add messages to mailbox {}.", + mailbox_id.mailbox_id )), ); continue 'update; @@ -928,7 +932,8 @@ impl JMAP { SetError::invalid_properties() .with_property(Property::MailboxIds) .with_description(format!( - "mailboxId {mailbox_id} does not exist." + "mailboxId {} does not exist.", + mailbox_id.mailbox_id )), ); continue 'update; @@ -938,13 +943,15 @@ impl JMAP { // Add all removed mailboxes to change list for mailbox_id in mailboxes.removed() { // Verify permissions on shared accounts - if !matches!(&can_delete_mailbox_ids, Some(ids) if !ids.contains(*mailbox_id)) { - changed_mailboxes.insert(*mailbox_id); + if !matches!(&can_delete_mailbox_ids, Some(ids) if !ids.contains(mailbox_id.mailbox_id)) + { + changed_mailboxes.insert(mailbox_id.mailbox_id); } else { response.not_updated.append( id, SetError::forbidden().with_description(format!( - "You are not allowed to delete messages from mailbox {mailbox_id}." + "You are not allowed to delete messages from mailbox {}.", + mailbox_id.mailbox_id )), ); continue 'update; @@ -1071,7 +1078,7 @@ impl JMAP { // Remove mailboxes let mailboxes = if let Some(mailboxes) = self - .get_property::>>( + .get_property::>>( account_id, Collection::Email, document_id, @@ -1091,7 +1098,7 @@ impl JMAP { return Ok(Err(SetError::not_found())); }; for mailbox_id in &mailboxes.inner { - changes.log_child_update(Collection::Mailbox, *mailbox_id); + changes.log_child_update(Collection::Mailbox, mailbox_id.mailbox_id); } batch.assert_value(Property::MailboxIds, &mailboxes).value( Property::MailboxIds, diff --git a/crates/jmap/src/mailbox/mod.rs b/crates/jmap/src/mailbox/mod.rs index 87aefefe..62ecd39f 100644 --- a/crates/jmap/src/mailbox/mod.rs +++ b/crates/jmap/src/mailbox/mod.rs @@ -21,9 +21,74 @@ * for more details. */ +use std::slice::Iter; + +use store::{ + write::{BitmapClass, DeserializeFrom, Operation, SerializeInto, TagValue, ToBitmaps}, + Serialize, U32_LEN, +}; +use utils::codec::leb128::{Leb128Iterator, Leb128Vec}; + pub mod get; pub mod query; pub mod set; pub const INBOX_ID: u32 = 0; pub const TRASH_ID: u32 = 1; +pub const JUNK_ID: u32 = 1; + +#[derive(Debug, Clone, Copy)] +pub struct UidMailbox { + pub mailbox_id: u32, + pub uid: u32, +} + +impl PartialEq for UidMailbox { + fn eq(&self, other: &Self) -> bool { + self.mailbox_id == other.mailbox_id + } +} + +impl Eq for UidMailbox {} + +impl ToBitmaps for UidMailbox { + fn to_bitmaps(&self, ops: &mut Vec, field: u8, set: bool) { + ops.push(Operation::Bitmap { + class: BitmapClass::Tag { + field, + value: TagValue::Id(self.mailbox_id), + }, + set, + }); + } +} + +impl SerializeInto for UidMailbox { + fn serialize_into(&self, buf: &mut Vec) { + buf.push_leb128(self.mailbox_id); + buf.push_leb128(self.uid); + } +} + +impl DeserializeFrom for UidMailbox { + fn deserialize_from(bytes: &mut Iter<'_, u8>) -> Option { + Some(UidMailbox { + mailbox_id: bytes.next_leb128()?, + uid: bytes.next_leb128()?, + }) + } +} + +impl Serialize for UidMailbox { + fn serialize(self) -> Vec { + let mut buf = Vec::with_capacity(U32_LEN * 2); + self.serialize_into(&mut buf); + buf + } +} + +impl From for UidMailbox { + fn from(mailbox_id: u32) -> Self { + UidMailbox { mailbox_id, uid: 0 } + } +} diff --git a/crates/jmap/src/mailbox/set.rs b/crates/jmap/src/mailbox/set.rs index 9ab4412e..c6112686 100644 --- a/crates/jmap/src/mailbox/set.rs +++ b/crates/jmap/src/mailbox/set.rs @@ -54,7 +54,7 @@ use crate::{ JMAP, }; -use super::{INBOX_ID, TRASH_ID}; +use super::{UidMailbox, INBOX_ID, JUNK_ID, TRASH_ID}; struct SetContext<'x> { account_id: u32, @@ -269,9 +269,9 @@ impl JMAP { remove_emails: bool, ) -> Result, MethodError> { // Internal folders cannot be deleted - if (document_id == INBOX_ID || document_id == TRASH_ID) && !access_token.is_super_user() { + if [INBOX_ID, TRASH_ID, JUNK_ID].contains(&document_id) && !access_token.is_super_user() { return Ok(Err(SetError::forbidden().with_description( - "You are not allowed to delete Inbox or Trash folders.", + "You are not allowed to delete Inbox, Junk or Trash folders.", ))); } @@ -310,7 +310,7 @@ impl JMAP { for message_id in message_ids { // Obtain mailboxIds if let Some(mailbox_ids) = self - .get_property::>>( + .get_property::>>( account_id, Collection::Email, message_id, @@ -318,7 +318,10 @@ impl JMAP { ) .await? .and_then(|mut ids| { - let idx = ids.inner.iter().position(|&id| id == document_id)?; + let idx = ids + .inner + .iter() + .position(|&id| id.mailbox_id == document_id)?; ids.inner.swap_remove(idx); Some(ids) }) @@ -636,6 +639,14 @@ impl JMAP { changes.append(Property::ParentId, Value::Id(0u64.into())); } + // Generate IMAP UID validity + if update.is_none() { + changes.append( + Property::Cid, + Value::UnsignedInt(rand::random::() as u64), + ); + } + // Verify that the mailbox role is unique. if let Value::Text(mailbox_role) = changes.get(&Property::Role) { if update @@ -768,19 +779,23 @@ impl JMAP { for (name, role) in [ ("Inbox", "inbox"), ("Deleted Items", "trash"), + ("Junk Mail", "junk"), ("Drafts", "drafts"), ("Sent Items", "sent"), - ("Junk Mail", "junk"), ] { let mailbox_id = self .assign_document_id(account_id, Collection::Mailbox) .await?; batch.create_document(mailbox_id).custom( ObjectIndexBuilder::new(SCHEMA).with_changes( - Object::with_capacity(3) + Object::with_capacity(4) .with_property(Property::Name, name) .with_property(Property::Role, role) - .with_property(Property::ParentId, Value::Id(0u64.into())), + .with_property(Property::ParentId, Value::Id(0u64.into())) + .with_property( + Property::Cid, + Value::UnsignedInt(rand::random::() as u64), + ), ), ); mailbox_ids.insert(mailbox_id); @@ -840,9 +855,13 @@ impl JMAP { .await?; batch.create_document(document_id).custom( ObjectIndexBuilder::new(SCHEMA).with_changes( - Object::with_capacity(2) + Object::with_capacity(3) .with_property(Property::Name, name) - .with_property(Property::ParentId, Value::Id(Id::from(next_parent_id))), + .with_property(Property::ParentId, Value::Id(Id::from(next_parent_id))) + .with_property( + Property::Cid, + Value::UnsignedInt(rand::random::() as u64), + ), ), ); changes.log_insert(Collection::Mailbox, document_id); diff --git a/crates/smtp/Cargo.toml b/crates/smtp/Cargo.toml index 62a4fa46..b0c543d1 100644 --- a/crates/smtp/Cargo.toml +++ b/crates/smtp/Cargo.toml @@ -27,9 +27,9 @@ rustls-pemfile = "1.0" tokio = { version = "1.23", features = ["full"] } tokio-rustls = { version = "0.24.0"} webpki-roots = { version = "0.25"} -hyper = { version = "1.0.0-rc.4", features = ["server", "http1", "http2"] } -hyper-util = { git = "https://github.com/hyperium/hyper-util" } -http-body-util = "0.1.0-rc.3" +hyper = { version = "1.0.1", features = ["server", "http1", "http2"] } +hyper-util = { version = "0.1.1", features = ["tokio"] } +http-body-util = "0.1.0" form_urlencoded = "1.1.0" sha1 = "0.10" sha2 = "0.10.6" @@ -51,7 +51,7 @@ num_cpus = "1.15.0" lazy_static = "1.4" whatlang = "0.16" imagesize = "0.12" -idna = "0.4" +idna = "0.5" decancer = "1.6.1" unicode-security = "0.1.0" infer = "0.15.0" diff --git a/crates/smtp/src/queue/quota.rs b/crates/smtp/src/queue/quota.rs index 377a9c14..f39ec097 100644 --- a/crates/smtp/src/queue/quota.rs +++ b/crates/smtp/src/queue/quota.rs @@ -137,7 +137,7 @@ impl Message { &domain.status, Status::Completed(_) | Status::PermanentFailure(_) ) { - quota_ids.push(((pos + 1) << 32) as u64); + quota_ids.push(((pos + 1) as u64) << 32); } } for (pos, rcpt) in self.recipients.iter().enumerate() { diff --git a/crates/store/src/backend/foundationdb/id_assign.rs b/crates/store/src/backend/foundationdb/id_assign.rs index 8e469efd..5f873b98 100644 --- a/crates/store/src/backend/foundationdb/id_assign.rs +++ b/crates/store/src/backend/foundationdb/id_assign.rs @@ -21,7 +21,10 @@ * for more details. */ -use crate::{write::key::DeserializeBigEndian, Deserialize, Key, Serialize, U32_LEN}; +use crate::{ + backend::ID_ASSIGNMENT_EXPIRY, write::key::DeserializeBigEndian, Deserialize, Key, Serialize, + U32_LEN, +}; use ahash::AHashSet; use foundationdb::{options::StreamingMode, FdbError, KeySelector, RangeOption}; use futures::StreamExt; @@ -82,9 +85,8 @@ impl FdbStore { #[cfg(not(feature = "test_mode"))] let expired_timestamp = now() - ID_ASSIGNMENT_EXPIRY; #[cfg(feature = "test_mode")] - let expired_timestamp = now() - - crate::backend::ID_ASSIGNMENT_EXPIRY - .load(std::sync::atomic::Ordering::Relaxed); + let expired_timestamp = + now() - ID_ASSIGNMENT_EXPIRY.load(std::sync::atomic::Ordering::Relaxed); while let Some(values) = values.next().await { for value in values? { let key = value.key(); diff --git a/crates/store/src/backend/mod.rs b/crates/store/src/backend/mod.rs index 9cd8cf94..60202d0d 100644 --- a/crates/store/src/backend/mod.rs +++ b/crates/store/src/backend/mod.rs @@ -36,6 +36,8 @@ pub const MAX_TOKEN_MASK: usize = MAX_TOKEN_LENGTH - 1; #[cfg(feature = "test_mode")] pub static ID_ASSIGNMENT_EXPIRY: std::sync::atomic::AtomicU64 = std::sync::atomic::AtomicU64::new(60 * 60); // seconds +#[cfg(not(feature = "test_mode"))] +pub const ID_ASSIGNMENT_EXPIRY: u64 = 60 * 60; // seconds impl From for crate::Error { fn from(err: std::io::Error) -> Self { diff --git a/crates/store/src/fts/term_index.rs b/crates/store/src/fts/term_index.rs deleted file mode 100644 index 3ec6e7e8..00000000 --- a/crates/store/src/fts/term_index.rs +++ /dev/null @@ -1,1000 +0,0 @@ -/* - * Copyright (c) 2023, Stalwart Labs Ltd. - * - * This file is part of Stalwart Mail Server. - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU Affero General Public License as - * published by the Free Software Foundation, either version 3 of - * the License, or (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU Affero General Public License for more details. - * in the LICENSE file at the top-level directory of this distribution. - * You should have received a copy of the GNU Affero General Public License - * along with this program. If not, see . - * - * You can be released from the requirements of the AGPLv3 license by - * purchasing a commercial license. Please contact licensing@stalw.art - * for more details. -*/ - -use std::{borrow::Cow, convert::TryInto}; - -use crate::{Deserialize, Serialize, U32_LEN, U64_LEN}; - -use ahash::{AHashMap, AHashSet}; -use bitpacking::{BitPacker, BitPacker1x, BitPacker4x, BitPacker8x}; -use nlp::{language::stemmer::StemmedToken, tokenizers::Token}; -use utils::codec::leb128::{Leb128Reader, Leb128Vec}; - -#[derive(Debug)] -pub enum Error { - DataCorruption, - Leb128DecodeError, - BitpackDecodeError, - InvalidArgument, -} - -pub type TermId = u32; -pub type Result = std::result::Result; - -const LENGTH_SIZE: usize = U32_LEN; - -#[derive(Debug, PartialEq, Eq)] -pub struct Term { - pub id: TermId, - pub id_stemmed: TermId, - pub offset: u32, - pub len: u8, -} - -#[derive(Debug)] -pub struct TermGroup { - pub field_id: u8, - pub part_id: u32, - pub terms: Vec, -} - -#[derive(Debug)] -pub struct TermIndexBuilderItem { - field: u8, - part_id: u32, - terms: Vec, -} - -#[derive(Debug)] -pub struct TermIndexBuilder { - terms: AHashMap, - items: Vec, -} - -#[derive(Debug)] -pub struct TermIndexItem { - pub field_id: u8, - pub part_id: u32, - pub terms_len: usize, - pub terms: Vec, -} - -#[derive(Debug, Default)] -pub struct TermIndex { - pub token_map: AHashMap, - pub items: Vec, -} - -#[derive(Debug, PartialEq, Eq)] -pub struct MatchTerm { - pub id: TermId, - pub id_stemmed: TermId, -} - -#[derive(Clone, Copy)] -struct TermIndexPacker { - bitpacker_1: BitPacker1x, - bitpacker_4: BitPacker4x, - bitpacker_8: BitPacker8x, - block_len: usize, -} - -impl TermIndexPacker { - pub fn with_block_len(block_len: usize) -> Self { - TermIndexPacker { - bitpacker_1: BitPacker1x::new(), - bitpacker_4: BitPacker4x::new(), - bitpacker_8: BitPacker8x::new(), - block_len, - } - } - - pub fn block_len(&mut self, num: usize) { - self.block_len = num; - } -} - -impl BitPacker for TermIndexPacker { - const BLOCK_LEN: usize = 0; - - fn new() -> Self { - TermIndexPacker { - bitpacker_1: BitPacker1x::new(), - bitpacker_4: BitPacker4x::new(), - bitpacker_8: BitPacker8x::new(), - block_len: 1, - } - } - - fn compress(&self, decompressed: &[u32], compressed: &mut [u8], num_bits: u8) -> usize { - match self.block_len { - BitPacker8x::BLOCK_LEN => self - .bitpacker_8 - .compress(decompressed, compressed, num_bits), - BitPacker4x::BLOCK_LEN => self - .bitpacker_4 - .compress(decompressed, compressed, num_bits), - _ => self - .bitpacker_1 - .compress(decompressed, compressed, num_bits), - } - } - - fn compress_sorted( - &self, - initial: u32, - decompressed: &[u32], - compressed: &mut [u8], - num_bits: u8, - ) -> usize { - match self.block_len { - BitPacker8x::BLOCK_LEN => { - self.bitpacker_8 - .compress_sorted(initial, decompressed, compressed, num_bits) - } - BitPacker4x::BLOCK_LEN => { - self.bitpacker_4 - .compress_sorted(initial, decompressed, compressed, num_bits) - } - _ => self - .bitpacker_1 - .compress_sorted(initial, decompressed, compressed, num_bits), - } - } - - fn decompress(&self, compressed: &[u8], decompressed: &mut [u32], num_bits: u8) -> usize { - match self.block_len { - BitPacker8x::BLOCK_LEN => { - self.bitpacker_8 - .decompress(compressed, decompressed, num_bits) - } - BitPacker4x::BLOCK_LEN => { - self.bitpacker_4 - .decompress(compressed, decompressed, num_bits) - } - _ => self - .bitpacker_1 - .decompress(compressed, decompressed, num_bits), - } - } - - fn decompress_sorted( - &self, - initial: u32, - compressed: &[u8], - decompressed: &mut [u32], - num_bits: u8, - ) -> usize { - match self.block_len { - BitPacker8x::BLOCK_LEN => { - self.bitpacker_8 - .decompress_sorted(initial, compressed, decompressed, num_bits) - } - BitPacker4x::BLOCK_LEN => { - self.bitpacker_4 - .decompress_sorted(initial, compressed, decompressed, num_bits) - } - _ => self - .bitpacker_1 - .decompress_sorted(initial, compressed, decompressed, num_bits), - } - } - - fn num_bits(&self, decompressed: &[u32]) -> u8 { - match self.block_len { - BitPacker8x::BLOCK_LEN => self.bitpacker_8.num_bits(decompressed), - BitPacker4x::BLOCK_LEN => self.bitpacker_4.num_bits(decompressed), - _ => self.bitpacker_1.num_bits(decompressed), - } - } - - fn num_bits_sorted(&self, initial: u32, decompressed: &[u32]) -> u8 { - match self.block_len { - BitPacker8x::BLOCK_LEN => self.bitpacker_8.num_bits_sorted(initial, decompressed), - BitPacker4x::BLOCK_LEN => self.bitpacker_4.num_bits_sorted(initial, decompressed), - _ => self.bitpacker_1.num_bits_sorted(initial, decompressed), - } - } -} - -#[allow(clippy::new_without_default)] -impl TermIndexBuilder { - pub fn new() -> TermIndexBuilder { - TermIndexBuilder { - items: Vec::new(), - terms: AHashMap::default(), - } - } - - pub fn add_token(&mut self, token: Token>) -> Term { - let id = self.terms.len() as u32; - let id = self - .terms - .entry(token.word.into_owned()) - .or_insert_with(|| id); - Term { - id: *id, - id_stemmed: *id, - offset: token.from as u32, - len: (token.to - token.from) as u8, - } - } - - pub fn add_stemmed_token(&mut self, token: StemmedToken) -> Term { - let id = self.terms.len() as u32; - let id = *self - .terms - .entry(token.word.into_owned()) - .or_insert_with(|| id); - let id_stemmed = if let Some(stemmed_word) = token.stemmed_word { - let id_stemmed = self.terms.len() as u32; - *self - .terms - .entry(stemmed_word.into_owned()) - .or_insert_with(|| id_stemmed) - } else { - id - }; - Term { - id, - id_stemmed, - offset: token.from as u32, - len: (token.to - token.from) as u8, - } - } - - pub fn add_terms(&mut self, field: u8, part_id: u32, terms: Vec) { - self.items.push(TermIndexBuilderItem { - field, - part_id, - terms, - }); - } - - pub fn is_empty(&self) -> bool { - self.items.is_empty() - } -} - -impl Serialize for TermIndexBuilder { - fn serialize(self) -> Vec { - // Add tokens - if self.terms.is_empty() { - return Vec::new(); - } - let mut terms = vec![""; self.terms.len()]; - let mut terms_len = 0; - for (word, id) in &self.terms { - terms[*id as usize] = word; - terms_len += word.len() + 1; - } - - // Serialize tokens - let mut bytes = - Vec::with_capacity(terms_len + ((self.items.len() / self.terms.len()) * U64_LEN * 2)); - bytes.push_leb128(self.terms.len()); - for terms in terms { - bytes.extend_from_slice(terms.as_bytes()); - bytes.push(0); - } - - // Write terms - let mut bitpacker = TermIndexPacker::new(); - let mut compressed = vec![0u8; 4 * BitPacker8x::BLOCK_LEN]; - - for term_index in &self.items { - let mut ids = Vec::with_capacity(term_index.terms.len() * 2); - let mut offsets = Vec::with_capacity(term_index.terms.len()); - let mut lengths = Vec::with_capacity(term_index.terms.len()); - - let header_pos = bytes.len(); - bytes.extend_from_slice(&[0u8; LENGTH_SIZE]); - bytes.push(term_index.field); - bytes.push_leb128(term_index.part_id); - bytes.push_leb128(term_index.terms.len()); - - let terms_pos = bytes.len(); - - for term in &term_index.terms { - ids.push(term.id); - ids.push(term.id_stemmed); - offsets.push(term.offset); - lengths.push(term.len); - } - - for (chunk, is_sorted) in [(ids, false), (offsets, true)] { - let mut pos = 0; - let len = chunk.len(); - let mut initial_value = 0; - - while pos < len { - let block_len = match len - pos { - 0..=31 => 0, - 32..=127 => BitPacker1x::BLOCK_LEN, - 128..=255 => BitPacker4x::BLOCK_LEN, - _ => BitPacker8x::BLOCK_LEN, - }; - - if block_len > 0 { - let chunk = &chunk[pos..pos + block_len]; - bitpacker.block_len(block_len); - if is_sorted { - let num_bits: u8 = bitpacker.num_bits_sorted(initial_value, chunk); - let compressed_len = bitpacker.compress_sorted( - initial_value, - chunk, - &mut compressed[..], - num_bits, - ); - bytes.push(num_bits); - bytes.extend_from_slice(&compressed[..compressed_len]); - initial_value = chunk[chunk.len() - 1]; - } else { - let num_bits: u8 = bitpacker.num_bits(chunk); - let compressed_len = - bitpacker.compress(chunk, &mut compressed[..], num_bits); - bytes.push(num_bits); - bytes.extend_from_slice(&compressed[..compressed_len]); - } - - pos += block_len; - } else { - for val in &chunk[pos..] { - bytes.push_leb128(*val); - } - pos = len; - } - } - } - bytes.append(&mut lengths); - - let len = (bytes.len() - terms_pos) as u32; - bytes[header_pos..header_pos + LENGTH_SIZE].copy_from_slice(&len.to_le_bytes()); - } - - bytes - } -} - -impl Deserialize for TermIndex { - fn deserialize(bytes: &[u8]) -> crate::Result { - TermIndex::from_bytes(bytes).ok_or_else(|| { - crate::Error::InternalError("Failed to deserialize term index".to_string()) - }) - } -} - -impl TermIndex { - pub fn get_match_term(&self, word: &str, stemmed_word: Option<&str>) -> MatchTerm { - let id = self.token_map.get(word).copied().unwrap_or(u32::MAX); - let id_stemmed = stemmed_word - .and_then(|word| self.token_map.get(word)) - .copied() - .unwrap_or(id); - - MatchTerm { id, id_stemmed } - } - - fn skip_items(&self, bytes: &[u8], mut remaining_items: usize) -> Result { - let mut pos = 0; - while remaining_items > 0 { - let block_len = match remaining_items { - 0..=31 => 0, - 32..=127 => BitPacker1x::BLOCK_LEN, - 128..=255 => BitPacker4x::BLOCK_LEN, - _ => BitPacker8x::BLOCK_LEN, - }; - - if block_len > 0 { - pos += - ((*bytes.get(pos).ok_or(Error::DataCorruption)? as usize) * block_len / 8) + 1; - remaining_items -= block_len; - } else { - while remaining_items > 0 { - let bytes_read = bytes - .get(pos..) - .ok_or(Error::DataCorruption)? - .skip_leb128() - .ok_or(Error::Leb128DecodeError)?; - - pos += bytes_read; - remaining_items -= 1; - } - } - } - Ok(pos) - } - - fn uncompress_chunk( - bytes: &[u8], - remaining_items: usize, - initial_value: Option, - ) -> Result<(usize, Vec)> { - let block_len = match remaining_items { - 0..=31 => 0, - 32..=127 => BitPacker1x::BLOCK_LEN, - 128..=255 => BitPacker4x::BLOCK_LEN, - _ => BitPacker8x::BLOCK_LEN, - }; - - if block_len > 0 { - let bitpacker = TermIndexPacker::with_block_len(block_len); - let num_bits = *bytes.first().ok_or(Error::DataCorruption)?; - let bytes_read = ((num_bits as usize) * block_len / 8) + 1; - let mut decompressed = vec![0u32; block_len]; - - if let Some(initial_value) = initial_value { - bitpacker.decompress_sorted( - initial_value, - bytes.get(1..bytes_read).ok_or(Error::DataCorruption)?, - &mut decompressed[..], - num_bits, - ); - } else { - bitpacker.decompress( - bytes.get(1..bytes_read).ok_or(Error::DataCorruption)?, - &mut decompressed[..], - num_bits, - ); - } - - Ok((bytes_read, decompressed)) - } else { - let mut decompressed = Vec::with_capacity(remaining_items); - let mut pos = 0; - while decompressed.len() < remaining_items { - let (val, bytes_read) = bytes - .get(pos..) - .ok_or(Error::DataCorruption)? - .read_leb128() - .ok_or(Error::Leb128DecodeError)?; - decompressed.push(val); - pos += bytes_read; - } - Ok((pos, decompressed)) - } - } - - pub fn match_terms( - &self, - match_terms: &[MatchTerm], - match_field: Option, - match_phrase: bool, - match_many: bool, - include_offsets: bool, - ) -> Result>> { - let mut result = Vec::new(); - - // Safety check to avoid overflowing the bit mask - if !match_phrase && !(1..=64).contains(&match_terms.len()) { - return Err(Error::InvalidArgument); - } - - // Term matching is done using a bit mask, where each bit represents a word. - // Each time a word is matched, the corresponding bit is cleared. - // When all bits are cleared, all matching terms are added to the result list. - let words_mask: u64 = u64::MAX >> (64 - match_terms.len()); - let mut matched_mask = words_mask; - - for item in &self.items { - if match_field.map_or(false, |match_field| match_field != item.field_id) { - continue; - } - - let mut terms = Vec::new(); - let mut partial_match = Vec::new(); - - let mut term_pos = 0; - let mut byte_pos = 0; - - 'term_loop: while term_pos < item.terms_len { - let (bytes_read, chunk) = TermIndex::uncompress_chunk( - item.terms.get(byte_pos..).ok_or(Error::DataCorruption)?, - (item.terms_len * 2) - (term_pos * 2), - None, - )?; - - byte_pos += bytes_read; - - for encoded_term in chunk.chunks_exact(2) { - let term_id = encoded_term[0]; - let term_id_stemmed = encoded_term[1]; - - if match_phrase { - let match_pos = partial_match.len(); - if match_terms[match_pos].id == term_id { - partial_match.push(Term { - id: term_id, - id_stemmed: term_id_stemmed, - offset: term_pos as u32, - len: 0, - }); - if partial_match.len() == match_terms.len() { - terms.append(&mut partial_match); - if !match_many { - break 'term_loop; - } - } - } else if match_pos > 0 { - partial_match.clear(); - } - } else { - 'match_loop: for (match_pos, match_term) in match_terms.iter().enumerate() { - if match_term.id == term_id - || match_term.id == term_id_stemmed - || ((match_term.id_stemmed != match_term.id) - && (match_term.id_stemmed == term_id - || match_term.id_stemmed == term_id_stemmed)) - { - partial_match.push(Term { - id: term_id, - id_stemmed: term_id_stemmed, - offset: term_pos as u32, - len: 0, - }); - - // Clear the bit corresponding to the matched term - matched_mask &= !(1 << match_pos); - break 'match_loop; - } - } - - if !match_many && matched_mask == 0 { - break 'term_loop; - } - } - term_pos += 1; - } - } - - if !match_phrase && !partial_match.is_empty() { - terms.append(&mut partial_match); - } - - if !terms.is_empty() { - if include_offsets { - // Skip any term ids that were not uncompressed - if term_pos < item.terms_len { - byte_pos += self.skip_items( - item.terms.get(byte_pos..).ok_or(Error::DataCorruption)?, - (item.terms_len - term_pos) * 2, - )?; - } - - // Uncompress offsets - let mut term_it = terms.iter_mut(); - let mut term = term_it.next().unwrap(); - let mut initial_value = 0; - term_pos = 0; - - 'outer: while term_pos < item.terms_len { - let (bytes_read, chunk) = TermIndex::uncompress_chunk( - item.terms.get(byte_pos..).ok_or(Error::DataCorruption)?, - item.terms_len - term_pos, - Some(initial_value), - )?; - - initial_value = chunk[chunk.len() - 1]; - byte_pos += bytes_read; - - for offset in chunk.into_iter() { - if term.offset == term_pos as u32 { - term.len = *item - .terms - .get(item.terms.len() - item.terms_len + term.offset as usize) - .ok_or(Error::DataCorruption)?; - term.offset = offset; - if let Some(next_term) = term_it.next() { - term = next_term; - } else { - break 'outer; - } - } - term_pos += 1; - } - } - } - - result.push(TermGroup { - field_id: item.field_id, - part_id: item.part_id, - terms, - }); - - if !match_many { - break; - } - } - } - - Ok(if !result.is_empty() { - Some(result) - } else { - None - }) - } - - fn from_bytes(bytes: &[u8]) -> Option { - let (num_tokens, mut pos) = bytes.read_leb128()?; - let mut token_map = AHashMap::with_capacity(num_tokens as usize); - for term_id in 0..num_tokens { - let nil_pos = bytes.get(pos..)?.iter().position(|b| b == &0)?; - token_map.insert( - String::from_utf8(bytes.get(pos..pos + nil_pos)?.to_vec()).ok()?, - term_id, - ); - pos += nil_pos + 1; - } - - let mut term_index = TermIndex { - items: Vec::new(), - token_map, - }; - - while pos < bytes.len() { - let item_len = - u32::from_le_bytes(bytes.get(pos..pos + LENGTH_SIZE)?.try_into().ok()?) as usize; - pos += LENGTH_SIZE; - - let field = bytes.get(pos)?; - pos += 1; - - let (part_id, bytes_read) = bytes.get(pos..)?.read_leb128()?; - pos += bytes_read; - - let (terms_len, bytes_read) = bytes.get(pos..)?.read_leb128()?; - pos += bytes_read; - - term_index.items.push(TermIndexItem { - field_id: *field, - part_id, - terms_len, - terms: bytes.get(pos..pos + item_len)?.to_vec(), - }); - - pos += item_len; - } - - Some(term_index) - } -} - -#[derive(Default)] -pub struct Terms { - pub field_id: u8, - pub exact_terms: AHashSet, - pub stemmed_terms: AHashSet, -} - -pub struct TokenIndex { - pub tokens: Vec, - pub terms: Vec, -} - -impl Deserialize for TokenIndex { - fn deserialize(bytes: &[u8]) -> crate::Result { - Self::from_bytes(bytes).ok_or_else(|| { - crate::Error::InternalError("Failed to deserialize token index.".to_string()) - }) - } -} - -impl TokenIndex { - fn from_bytes(bytes: &[u8]) -> Option { - if !bytes.is_empty() { - let (num_tokens, mut pos) = bytes.read_leb128::()?; - let mut tokens = Vec::with_capacity(num_tokens as usize); - for _ in 0..num_tokens { - let nil_pos = bytes.get(pos..)?.iter().position(|b| b == &0)?; - tokens.push(String::from_utf8(bytes.get(pos..pos + nil_pos)?.to_vec()).ok()?); - pos += nil_pos + 1; - } - - let mut terms = Vec::new(); - while pos < bytes.len() { - let item_len = - u32::from_le_bytes(bytes.get(pos..pos + LENGTH_SIZE)?.try_into().ok()?) - as usize; - pos += LENGTH_SIZE; - - let mut field_terms = Terms { - field_id: *bytes.get(pos)?, - exact_terms: AHashSet::default(), - stemmed_terms: AHashSet::default(), - }; - pos += 1; - - let bytes_read = bytes.get(pos..)?.skip_leb128()?; - pos += bytes_read; - - let (terms_len, bytes_read) = bytes.get(pos..)?.read_leb128::()?; - pos += bytes_read; - - let mut term_pos = 0; - let mut byte_pos = pos; - - while term_pos < terms_len { - let (bytes_read, chunk) = TermIndex::uncompress_chunk( - bytes.get(byte_pos..)?, - (terms_len - term_pos) * 2, - None, - ) - .ok()?; - - byte_pos += bytes_read; - - for encoded_term in chunk.chunks_exact(2) { - let term_id = encoded_term[0]; - let term_id_stemmed = encoded_term[1]; - - field_terms.exact_terms.insert(term_id); - if term_id != term_id_stemmed { - field_terms.stemmed_terms.insert(term_id_stemmed); - } - term_pos += 1; - } - } - - terms.push(field_terms); - pos += item_len; - } - - Some(TokenIndex { tokens, terms }) - } else { - Some(TokenIndex { - tokens: Vec::new(), - terms: Vec::new(), - }) - } - } -} - -#[cfg(test)] -mod tests { - - use ahash::AHashMap; - use nlp::language::{stemmer::Stemmer, Language}; - - use crate::{ - fts::term_index::{TermIndexBuilder, TokenIndex}, - Deserialize, Serialize, - }; - - use super::TermIndex; - - #[test] - #[allow(clippy::bind_instead_of_map)] - fn term_index() { - const SUBJECT: u8 = 1; - const BODY: u8 = 2; - const ATTACHMENT: u8 = 3; - - let parts = [ - ( - r#"I felt happy because I saw the others were happy - and because I knew I should feel happy, but I wasn’t - really happy."#, - SUBJECT, - ), - ( - r#"But good morning! Good morning to ye and thou! I’d - say to all my patients, because I was the worse of the - hypocrites, of all the hypocrites, the cruel and phony - hypocrites, I was the very worst."#, - BODY, - ), - ( - r#"So I said yes to Thomas Clinton and later thought - that I had said yes to God and later still realized I - had said yes only to Thomas Clinton."#, - BODY, - ), - ( - r#"Even if they are djinns, I will get djinns that - can outdjinn them."#, - BODY, - ), - ( - r#"Hatred was spreading everywhere, blood was being - spilled everywhere, wars were breaking out - everywhere."#, - BODY, - ), - ( - r#"Almost nothing was more annoying than having - our wasted time wasted on something not worth - wasting it on."#, - BODY, - ), - ( - r#"The depressed person was in terrible and unceasing - emotional pain, and the impossibility of sharing or - articulating this pain was itself a component of the - pain and a contributing factor in its essential horror."#, - BODY, - ), - ( - r#"Paranoids are not paranoid because they’re paranoid, - but because they keep putting themselves, darn idiots, - deliberately into paranoid situations."#, - BODY, - ), - ( - r#"Because the world is a place of silence, the sky at - night when the birds have gone is a vast silent place."#, - BODY, - ), - ( - r#"There are some things that are so unforgivable that - they make other things easily forgivable."#, - BODY, - ), - ( - r#"I had known loneliness before, and emptiness upon the - moor, but I had never been a NOTHING, a nothing floating - on a nothing, known by nothing, lonelier and colder than - the space between the stars."#, - ATTACHMENT, - ), - ( - r#"You’re an insomniac, you tell yourself: there are - profound truths revealed only to the insomniac by night - like those phosphorescent minerals veined and glimmering - in the dark but coarse and ordinary otherwise; you have - to examine such minerals in the absence of light to - discover their beauty, you tell yourself."#, - ATTACHMENT, - ), - ( - r#"Every person had a star, every star had a friend, - and for every person carrying a star there was someone - else who reflected it, and everyone carried this reflection - like a secret confidante in the heart."#, - ATTACHMENT, - ), - ( - r#"As my grandfather went, arm over arm, his heart making - sour little shudders against his ribs, he kept listening - for a sound, the sound of the tiger, the sound of anything - but his own feet and lungs."#, - ATTACHMENT, - ), - (r#"love loving lovingly loved lovely"#, ATTACHMENT), - ]; - - let mut builder = TermIndexBuilder::new(); - let mut stemmed_word_ids = AHashMap::default(); - - // Build the term index - for (part_id, (text, field_id)) in parts.iter().enumerate() { - let mut terms = Vec::new(); - for token in Stemmer::new(text, Language::English, 40) { - let stemmed_word = if token.stemmed_word.is_some() { - token.stemmed_word.clone() - } else { - None - }; - let term = builder.add_stemmed_token(token); - if let Some(stemmed_word) = stemmed_word { - stemmed_word_ids.insert(term.id_stemmed, stemmed_word.into_owned()); - } - terms.push(term); - } - builder.add_terms(*field_id, part_id as u32, terms); - } - - let compressed_term_index = builder.serialize(); - let term_index = TermIndex::deserialize(&compressed_term_index[..]).unwrap(); - - assert_eq!( - 15, - TokenIndex::deserialize(&compressed_term_index[..]) - .unwrap() - .terms - .len() - ); - - for (words, field_id, match_phrase, match_count) in [ - (vec!["thomas", "clinton"], None, true, 4), - (vec!["was", "the", "worse"], None, true, 3), - (vec!["carri"], None, false, 2), - (vec!["nothing", "floating"], None, true, 2), - (vec!["floating", "nothing"], None, false, 6), - (vec!["floating", "nothing"], None, true, 0), - (vec!["noth", "floating"], None, true, 0), - (vec!["noth", "floating"], None, false, 6), - (vec!["realli", "happi"], None, false, 5), - (vec!["really", "happy"], None, true, 2), - (vec!["should", "feel", "happy", "but"], None, true, 4), - ( - vec!["love", "loving", "lovingly", "loved", "lovely"], - Some(ATTACHMENT), - true, - 5, - ), - (vec!["love"], Some(ATTACHMENT), false, 5), - (vec!["but"], None, false, 6), - (vec!["but"], None, true, 6), - ] { - let mut match_terms = Vec::new(); - for word in &words { - let stemmed_token = Stemmer::new(word, Language::English, 40) - .next() - .and_then(|w| w.stemmed_word); - match_terms.push( - term_index.get_match_term(word, stemmed_token.as_ref().map(|w| w.as_ref())), - ); - } - - let result = term_index - .match_terms(&match_terms, field_id, match_phrase, true, true) - .unwrap() - .unwrap_or_default(); - - let mut result_len = 0; - for r in &result { - result_len += r.terms.len(); - } - - if result_len != match_count { - for term_group in result { - let part = &parts[term_group.part_id as usize].0; - println!("-> Part id {}", term_group.part_id); - for term in term_group.terms { - println!( - "[{}] ", - &part[term.offset as usize..term.offset as usize + term.len as usize] - ); - } - } - panic!( - "Expected {}, got {} for words {:?}, match phrase {:?}.", - match_count, result_len, words, match_phrase - ); - } - - for term_group in &result { - 'outer: for term in &term_group.terms { - let text_word = parts[term_group.part_id as usize].0 - [term.offset as usize..term.offset as usize + term.len as usize] - .to_lowercase(); - let token_stemmed_word = if term.id_stemmed != term.id { - stemmed_word_ids.get(&term.id_stemmed) - } else { - None - }; - - for word in words.iter() { - if word == &text_word - || !match_phrase - && word == token_stemmed_word.unwrap_or(&"".to_string()) - { - continue 'outer; - } - } - panic!("({:?}, {}) != {:?}", words, match_phrase, result); - } - } - } - } -} diff --git a/resources/config/imap/settings.toml b/resources/config/imap/settings.toml index 5dbb2883..9f5d85c4 100644 --- a/resources/config/imap/settings.toml +++ b/resources/config/imap/settings.toml @@ -9,12 +9,8 @@ max-size = 52428800 max-failures = 3 allow-plain-text = false -[imap.folders] -all-messages = true - [imap.folders.name] shared = "Shared Folders" -all = "All Mail" [imap.timeout] authenticated = "30m" diff --git a/tests/Cargo.toml b/tests/Cargo.toml index 1708c05c..66d9d364 100644 --- a/tests/Cargo.toml +++ b/tests/Cargo.toml @@ -41,9 +41,9 @@ reqwest = { version = "0.11", default-features = false, features = ["rustls-tls- bytes = "1.4.0" futures = "0.3" ece = "2.2" -hyper = { version = "1.0.0-rc.4", features = ["server", "http1", "http2"] } -hyper-util = { git = "https://github.com/hyperium/hyper-util" } -http-body-util = "0.1.0-rc.3" +hyper = { version = "1.0.1", features = ["server", "http1", "http2"] } +hyper-util = { version = "0.1.1", features = ["tokio"] } +http-body-util = "0.1.0" base64 = "0.21" dashmap = "5.4" ahash = { version = "0.8" } diff --git a/tests/resources/imap/014.imap b/tests/resources/imap/014.imap new file mode 100644 index 00000000..679a697d --- /dev/null +++ b/tests/resources/imap/014.imap @@ -0,0 +1,211 @@ +BODY ( + ( + "text" "x-myown" ( + "charset" "us-ascii" + ) NIL NIL "quoted-printable" 79 15 + ) "mixed" +) + +BODYSTRUCTURE ( + ( + "text" "x-myown" ( + "charset" "us-ascii" + ) NIL NIL "quoted-printable" 79 15 "22839bb2efefde05dda98625a9ed8875" NIL NIL NIL + ) "mixed" ( + "boundary" "foo bar" + ) NIL NIL NIL +) + +BODY[] {404} +From user@domain Fri Feb 22 17:06:23 2008 +From: user@domain.org +Date: Sat, 24 Mar 2007 23:00:00 +0200 +Mime-Version: 1.0 +Content-Type: multipart/mixed; boundary="foo + bar" + +Root MIME prologue + +--foo bar +Content-Type: text/x-myown; charset=us-ascii +Content-Transfer-Encoding: quoted-printable + +hello +bar= + +foo = +bar +foo = +=62 +foo = +bar +foo = +=62 +foo +bar= + +foo_bar + +--foo bar-- +Root MIME epilogue + +BINARY[] {16} +[binary content] +BINARY.SIZE[] 404 +---------------------------------- +BODY[HEADER] {173} +From user@domain Fri Feb 22 17:06:23 2008 +From: user@domain.org +Date: Sat, 24 Mar 2007 23:00:00 +0200 +Mime-Version: 1.0 +Content-Type: multipart/mixed; boundary="foo + bar" + + +---------------------------------- +BODY[TEXT] {231} +Root MIME prologue + +--foo bar +Content-Type: text/x-myown; charset=us-ascii +Content-Transfer-Encoding: quoted-printable + +hello +bar= + +foo = +bar +foo = +=62 +foo = +bar +foo = +=62 +foo +bar= + +foo_bar + +--foo bar-- +Root MIME epilogue + +---------------------------------- +BODY[MIME] {53} +Content-Type: multipart/mixed; boundary="foo + bar" + + +---------------------------------- +BODY[1] {79} +hello +bar= + +foo = +bar +foo = +=62 +foo = +bar +foo = +=62 +foo +bar= + +foo_bar + +BINARY[1] {56} +hello +bar +foo bar +foo b +foo bar +foo b +foo +bar +foo_bar + +BINARY.SIZE[1] 56 +---------------------------------- +BODY[1.HEADER] {90} +Content-Type: text/x-myown; charset=us-ascii +Content-Transfer-Encoding: quoted-printable + + +---------------------------------- +BODY[1.TEXT] {79} +hello +bar= + +foo = +bar +foo = +=62 +foo = +bar +foo = +=62 +foo +bar= + +foo_bar + +---------------------------------- +BODY[1.MIME] {91} +Content-Type: text/x-myown; charset=us-ascii +Content-Transfer-Encoding: quoted-printable + + +---------------------------------- +BODY[1.1] {79} +hello +bar= + +foo = +bar +foo = +=62 +foo = +bar +foo = +=62 +foo +bar= + +foo_bar + +BINARY[1.1] {56} +hello +bar +foo bar +foo b +foo bar +foo b +foo +bar +foo_bar + +BINARY.SIZE[1.1] 56 +---------------------------------- +BODY[HEADER.FIELDS (FROM TO)] {24} +From: user@domain.org + + +---------------------------------- +BODY[HEADER.FIELDS (FROM TO)]<10> {14} +@domain.org + + +---------------------------------- +BODY[HEADER.FIELDS.NOT (SUBJECT CC)] {174} +From user@domain Fri Feb 22 17:06:23 2008 +From: user@domain.org +Date: Sat, 24 Mar 2007 23:00:00 +0200 +MIME-Version: 1.0 +Content-Type: multipart/mixed; boundary="foo + bar" + + +---------------------------------- +BODY[HEADER.FIELDS.NOT (SUBJECT CC)]<10> {25} +domain Fri Feb 22 17:06: +---------------------------------- diff --git a/tests/resources/imap/014.txt b/tests/resources/imap/014.txt new file mode 100644 index 00000000..52954d58 --- /dev/null +++ b/tests/resources/imap/014.txt @@ -0,0 +1,31 @@ +From user@domain Fri Feb 22 17:06:23 2008 +From: user@domain.org +Date: Sat, 24 Mar 2007 23:00:00 +0200 +Mime-Version: 1.0 +Content-Type: multipart/mixed; boundary="foo + bar" + +Root MIME prologue + +--foo bar +Content-Type: text/x-myown; charset=us-ascii +Content-Transfer-Encoding: quoted-printable + +hello +bar= + +foo = +bar +foo = +=62 +foo = +bar +foo = +=62 +foo +bar= + +foo_bar + +--foo bar-- +Root MIME epilogue diff --git a/tests/src/imap/append.rs b/tests/src/imap/append.rs index ede7bed8..02ebe9e5 100644 --- a/tests/src/imap/append.rs +++ b/tests/src/imap/append.rs @@ -31,10 +31,6 @@ use super::{resources_dir, AssertResult, IMAPTest, ImapConnection, Type}; pub async fn test(imap: &mut ImapConnection, _imap_check: &mut ImapConnection, handle: &IMAPTest) { // Invalid APPEND commands - imap.send("APPEND \"All Mail\" {1+}\r\na").await; - imap.assert_read(Type::Tagged, ResponseType::No) - .await - .assert_response_code("CANNOT"); imap.send("APPEND \"Does not exist\" {1+}\r\na").await; imap.assert_read(Type::Tagged, ResponseType::No) .await diff --git a/tests/src/imap/copy_move.rs b/tests/src/imap/copy_move.rs index b599b666..055e0161 100644 --- a/tests/src/imap/copy_move.rs +++ b/tests/src/imap/copy_move.rs @@ -31,24 +31,18 @@ pub async fn test(imap: &mut ImapConnection, _imap_check: &mut ImapConnection) { .await; imap.assert_read(Type::Tagged, ResponseType::Ok) .await - .assert_contains("\"INBOX\" (UIDNEXT 11 MESSAGES 10 UNSEEN 10 SIZE 12193)") - .assert_contains("\"All Mail\" (UIDNEXT 11 MESSAGES 10 UNSEEN 10 SIZE 12193)"); + .assert_contains("\"INBOX\" (UIDNEXT 11 MESSAGES 10 UNSEEN 10 SIZE 12193)"); // Select INBOX imap.send("SELECT INBOX").await; imap.assert_read(Type::Tagged, ResponseType::Ok).await; - // Copying to "All Mail" or the same mailbox should fail + // Copying to the same mailbox should fail imap.send("COPY 1:* INBOX").await; imap.assert_read(Type::Tagged, ResponseType::No) .await .assert_response_code("CANNOT"); - imap.send("COPY 1:* \"All Mail\"").await; - imap.assert_read(Type::Tagged, ResponseType::No) - .await - .assert_response_code("CANNOT"); - // Copying to a non-existent mailbox should fail imap.send("COPY 1:* \"/dev/null\"").await; imap.assert_read(Type::Tagged, ResponseType::No) diff --git a/tests/src/imap/mailbox.rs b/tests/src/imap/mailbox.rs index 989c4a07..d902a110 100644 --- a/tests/src/imap/mailbox.rs +++ b/tests/src/imap/mailbox.rs @@ -38,14 +38,7 @@ pub async fn test(mut imap: &mut ImapConnection, mut imap_check: &mut ImapConnec imap.send("LIST \"\" \"*\"").await; imap.assert_read(Type::Tagged, ResponseType::Ok) .await - .assert_folders( - [ - ("All Mail", ["NoInferiors"]), - ("INBOX", [""]), - ("Deleted Items", [""]), - ], - true, - ); + .assert_folders([("INBOX", [""]), ("Deleted Items", [""])], true); // Create folders imap.send("CREATE \"Tofu\"").await; @@ -68,7 +61,6 @@ pub async fn test(mut imap: &mut ImapConnection, mut imap_check: &mut ImapConnec .await .assert_folders( [ - ("All Mail", ["NoInferiors"]), ("INBOX", [""]), ("Deleted Items", [""]), ("Fruit", [""]), @@ -80,10 +72,6 @@ pub async fn test(mut imap: &mut ImapConnection, mut imap_check: &mut ImapConnec ); } - // Folders under All Mail should not be allowed - imap.send("CREATE \"All Mail/Untitled\"").await; - imap.assert_read(Type::Tagged, ResponseType::No).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; @@ -108,7 +96,6 @@ pub async fn test(mut imap: &mut ImapConnection, mut imap_check: &mut ImapConnec .await .assert_folders( [ - ("All Mail", ["NoInferiors", "All"]), ("INBOX", ["HasNoChildren", ""]), ("Deleted Items", ["HasNoChildren", "Trash"]), ("Cars/Electric/4 doors/Red", ["HasNoChildren", ""]), @@ -148,7 +135,6 @@ pub async fn test(mut imap: &mut ImapConnection, mut imap_check: &mut ImapConnec .await .assert_folders( [ - ("All Mail", ["NoInferiors", "All"]), ("INBOX", ["HasChildren", ""]), ("INBOX/Tofu", ["HasNoChildren", ""]), ("Recycle Bin", ["HasNoChildren", "Trash"]), @@ -173,8 +159,6 @@ pub async fn test(mut imap: &mut ImapConnection, mut imap_check: &mut ImapConnec imap.assert_read(Type::Tagged, ResponseType::Ok).await; imap.send("DELETE \"Vegetable\"").await; imap.assert_read(Type::Tagged, ResponseType::Ok).await; - imap.send("DELETE \"All Mail\"").await; - imap.assert_read(Type::Tagged, ResponseType::No).await; imap.send("DELETE \"Vehicles\"").await; imap.assert_read(Type::Tagged, ResponseType::No).await; for imap in [&mut imap, &mut imap_check] { @@ -184,7 +168,6 @@ pub async fn test(mut imap: &mut ImapConnection, mut imap_check: &mut ImapConnec .await .assert_folders( [ - ("All Mail", ["NoInferiors", "All"]), ("INBOX", ["HasNoChildren", ""]), ("Recycle Bin", ["HasNoChildren", "Trash"]), ("Vehicles/Electric/4 doors/Red", ["HasNoChildren", ""]), @@ -215,7 +198,6 @@ pub async fn test(mut imap: &mut ImapConnection, mut imap_check: &mut ImapConnec .await .assert_folders( [ - ("All Mail", ["NoInferiors", "All"]), ("INBOX", ["Subscribed", ""]), ("Recycle Bin", ["", "Trash"]), ("Vehicles/Electric/4 doors/Red", ["Subscribed", ""]), @@ -304,7 +286,6 @@ pub async fn test(mut imap: &mut ImapConnection, mut imap_check: &mut ImapConnec .await .assert_folders( [ - ("All Mail", [""]), ("INBOX", [""]), ("Recycle Bin", [""]), ("Vehicles", [""]), diff --git a/tests/src/imap/mod.rs b/tests/src/imap/mod.rs index 55cc21d2..0bff5d0b 100644 --- a/tests/src/imap/mod.rs +++ b/tests/src/imap/mod.rs @@ -38,6 +38,7 @@ pub mod thread; use std::{path::PathBuf, sync::Arc, time::Duration}; use ::managesieve::core::ManageSieveSessionManager; +use ahash::AHashSet; use directory::config::ConfigDirectory; use imap::core::{ImapSessionManager, IMAP}; use imap_proto::ResponseType; @@ -494,7 +495,7 @@ pub trait AssertResult: Sized { fn assert_response_code(self, code: &str) -> Self; fn assert_contains(self, text: &str) -> Self; - fn assert_count(self, text: &str, occurences: usize) -> Self; + fn assert_count(self, text: &str, occurrences: usize) -> Self; fn assert_equals(self, text: &str) -> Self; fn into_response_code(self) -> String; fn into_highest_modseq(self) -> String; @@ -555,12 +556,12 @@ impl AssertResult for Vec { panic!("Expected response to contain {:?}, got {:?}", text, self); } - fn assert_count(self, text: &str, occurences: usize) -> Self { + fn assert_count(self, text: &str, occurrences: usize) -> Self { assert_eq!( self.iter().filter(|l| l.contains(text)).count(), - occurences, + occurrences, "Expected {} occurrences of {:?}, found {}.", - occurences, + occurrences, text, self.iter().filter(|l| l.contains(text)).count() ); @@ -653,6 +654,23 @@ impl AssertResult for Vec { } } +pub fn expand_uid_list(list: &str) -> AHashSet { + let mut items = AHashSet::new(); + for uid in list.split(',') { + if let Some((start, end)) = uid.split_once(':') { + let start = start.parse::().unwrap(); + let end = end.parse::().unwrap(); + for uid in start..=end { + items.insert(uid); + } + } else { + items.insert(uid.parse::().unwrap()); + } + } + + items +} + fn resources_dir() -> PathBuf { let mut resources = PathBuf::from(env!("CARGO_MANIFEST_DIR")); resources.push("resources"); diff --git a/tests/src/imap/thread.rs b/tests/src/imap/thread.rs index 167dedf1..ca6ff2d0 100644 --- a/tests/src/imap/thread.rs +++ b/tests/src/imap/thread.rs @@ -23,7 +23,7 @@ use imap_proto::ResponseType; -use crate::imap::AssertResult; +use crate::imap::{expand_uid_list, AssertResult}; use super::{append::build_messages, ImapConnection, Type}; @@ -47,10 +47,14 @@ pub async fn test(imap: &mut ImapConnection, _imap_check: &mut ImapConnection) { } else { imap.send_untagged(message).await; assert_eq!( - imap.assert_read(Type::Tagged, ResponseType::Ok) - .await - .into_append_uid(), - format!("1:{}", messages.len()), + expand_uid_list( + &imap + .assert_read(Type::Tagged, ResponseType::Ok) + .await + .into_append_uid() + ) + .len(), + messages.len(), ); } } diff --git a/tests/src/jmap/push_subscription.rs b/tests/src/jmap/push_subscription.rs index 04260786..94002e54 100644 --- a/tests/src/jmap/push_subscription.rs +++ b/tests/src/jmap/push_subscription.rs @@ -31,7 +31,7 @@ use std::{ use base64::{engine::general_purpose, Engine}; use ece::EcKeyComponents; -use hyper::{body, server::conn::http1, service::service_fn, StatusCode}; +use hyper::{body, header::CONTENT_ENCODING, server::conn::http1, service::service_fn, StatusCode}; use hyper_util::rt::TokioIo; use jmap::{ api::{ @@ -44,7 +44,6 @@ use jmap::{ }; use jmap_client::{client::Client, mailbox::Role, push_subscription::Keys}; use jmap_proto::types::{id::Id, type_state::DataType}; -use reqwest::header::CONTENT_ENCODING; use store::ahash::AHashSet; use tokio::{net::TcpStream, sync::mpsc}; diff --git a/tests/src/jmap/stress_test.rs b/tests/src/jmap/stress_test.rs index ac1dffdf..92a9bb51 100644 --- a/tests/src/jmap/stress_test.rs +++ b/tests/src/jmap/stress_test.rs @@ -25,7 +25,7 @@ use std::{sync::Arc, time::Duration}; use crate::jmap::mailbox::destroy_all_mailboxes; use futures::future::join_all; -use jmap::JMAP; +use jmap::{mailbox::UidMailbox, JMAP}; use jmap_client::{ client::Client, core::set::{SetErrorType, SetObject}, @@ -221,7 +221,7 @@ async fn email_tests(server: Arc, client: Arc) { for email_id in &email_ids_in_mailbox { if let Some(mailbox_tags) = server - .get_property::>( + .get_property::>( TEST_USER_ID, Collection::Email, email_id, @@ -237,10 +237,10 @@ async fn email_tests(server: Arc, client: Arc) { ); } let mailbox_tag = mailbox_tags[0]; - if mailbox_tag != mailbox_id { + if mailbox_tag.mailbox_id != mailbox_id { panic!( concat!( - "Email ORM has an unexpected mailbox tag {}! Id {} in ", + "Email ORM has an unexpected mailbox tag {:?}! Id {} in ", "mailbox {} with messages {:?}" ), mailbox_tag, email_id, mailbox_id, email_ids_in_mailbox, diff --git a/tests/src/smtp/management/mod.rs b/tests/src/smtp/management/mod.rs index a194cf55..1cd9cee9 100644 --- a/tests/src/smtp/management/mod.rs +++ b/tests/src/smtp/management/mod.rs @@ -23,7 +23,7 @@ use std::time::Duration; -use hyper::header::AUTHORIZATION; +use reqwest::header::AUTHORIZATION; use serde::{de::DeserializeOwned, Deserialize}; pub mod queue; diff --git a/tests/src/smtp/management/queue.rs b/tests/src/smtp/management/queue.rs index a0abba5c..cfb268a2 100644 --- a/tests/src/smtp/management/queue.rs +++ b/tests/src/smtp/management/queue.rs @@ -28,9 +28,9 @@ use std::{ use ahash::{AHashMap, HashMap, HashSet}; use directory::config::ConfigDirectory; -use hyper::{header::AUTHORIZATION, StatusCode}; use mail_auth::MX; use mail_parser::DateTime; +use reqwest::{header::AUTHORIZATION, StatusCode}; use utils::config::{Config, ServerProtocol}; use crate::smtp::{