summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authormdecimus <mauro@stalw.art>2024-09-19 19:39:35 +0200
committermdecimus <mauro@stalw.art>2024-09-19 19:39:35 +0200
commita67f308645ad0873850efc832d72b7575b40f48e (patch)
tree63547f6925e0cb43b2aa3592e7a283d9ae6d0f65
parente9d12aea44930d5136a67fc9536d55d31d9a1ddc (diff)
Branding + fixes
-rw-r--r--Cargo.lock322
-rw-r--r--crates/cli/Cargo.toml4
-rw-r--r--crates/cli/src/main.rs2
-rw-r--r--crates/common/Cargo.toml5
-rw-r--r--crates/common/src/config/mod.rs1
-rw-r--r--crates/common/src/config/smtp/resolver.rs9
-rw-r--r--crates/common/src/enterprise/config.rs1
-rw-r--r--crates/common/src/enterprise/mod.rs100
-rw-r--r--crates/common/src/expr/tokenizer.rs4
-rw-r--r--crates/common/src/lib.rs7
-rw-r--r--crates/common/src/listener/acme/order.rs10
-rw-r--r--crates/common/src/manager/webadmin.rs19
-rw-r--r--crates/common/src/scripts/functions/unicode.rs4
-rw-r--r--crates/common/src/scripts/plugins/bayes.rs10
-rw-r--r--crates/common/src/scripts/plugins/pyzor.rs47
-rw-r--r--crates/common/src/scripts/plugins/text.rs23
-rw-r--r--crates/directory/Cargo.toml2
-rw-r--r--crates/directory/src/backend/internal/manage.rs85
-rw-r--r--crates/directory/src/backend/internal/mod.rs1
-rw-r--r--crates/imap/Cargo.toml2
-rw-r--r--crates/jmap/Cargo.toml4
-rw-r--r--crates/jmap/src/api/autoconfig.rs18
-rw-r--r--crates/jmap/src/api/http.rs69
-rw-r--r--crates/jmap/src/api/management/dns.rs6
-rw-r--r--crates/jmap/src/api/management/principal.rs10
-rw-r--r--crates/jmap/src/api/management/stores.rs6
-rw-r--r--crates/jmap/src/vacation/set.rs3
-rw-r--r--crates/main/Cargo.toml2
-rw-r--r--crates/managesieve/Cargo.toml2
-rw-r--r--crates/nlp/Cargo.toml4
-rw-r--r--crates/nlp/src/bayes/tokenize.rs20
-rw-r--r--crates/nlp/src/lib.rs11
-rw-r--r--crates/nlp/src/tokenizers/mod.rs2
-rw-r--r--crates/nlp/src/tokenizers/types.rs43
-rw-r--r--crates/pop3/Cargo.toml2
-rw-r--r--crates/smtp/Cargo.toml4
-rw-r--r--crates/smtp/src/inbound/data.rs2
-rw-r--r--crates/store/Cargo.toml2
-rw-r--r--crates/store/src/backend/mysql/main.rs8
-rw-r--r--crates/store/src/backend/mysql/read.rs6
-rw-r--r--crates/store/src/backend/mysql/write.rs24
-rw-r--r--crates/trc/Cargo.toml4
-rw-r--r--crates/utils/Cargo.toml4
-rw-r--r--crates/utils/src/lib.rs1
-rw-r--r--tests/Cargo.toml2
-rw-r--r--tests/src/jmap/enterprise.rs2
-rw-r--r--tests/src/jmap/permissions.rs6
-rw-r--r--tests/src/jmap/webhooks.rs5
-rw-r--r--tests/src/smtp/inbound/antispam.rs4
-rw-r--r--tests/src/smtp/inbound/milter.rs5
-rw-r--r--tests/src/smtp/outbound/dane.rs2
51 files changed, 514 insertions, 427 deletions
diff --git a/Cargo.lock b/Cargo.lock
index c43db489..5276c5fc 100644
--- a/Cargo.lock
+++ b/Cargo.lock
@@ -4,20 +4,14 @@ version = 3
[[package]]
name = "addr2line"
-version = "0.22.0"
+version = "0.24.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "6e4503c46a5c0c7844e948c9a4d6acd9f50cccb4de1c48eb9e291ea17470c678"
+checksum = "f5fb1d8e4442bd405fdfd1dacb42792696b0cf9cb15882e5d097b742a676d375"
dependencies = [
"gimli",
]
[[package]]
-name = "adler"
-version = "1.0.2"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "f26201604c87b1e01bd3d98f8d5d9a8fcbb815e8cedb41ffccbeb4bf593a35fe"
-
-[[package]]
name = "adler2"
version = "2.0.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
@@ -180,9 +174,9 @@ dependencies = [
[[package]]
name = "anyhow"
-version = "1.0.86"
+version = "1.0.89"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "b3d1d046238990b9cf5bcde22a3fb3584ee5cf65fb2765f454ed428c7a0063da"
+checksum = "86fdf8605db99b54d3cd748a44c6d04df638eb5dafb219b135d0149bd0db01f6"
[[package]]
name = "arbitrary"
@@ -213,9 +207,9 @@ dependencies = [
[[package]]
name = "arrayref"
-version = "0.3.8"
+version = "0.3.9"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "9d151e35f61089500b617991b791fc8bfd237ae50cd5950803758a179b41e67a"
+checksum = "76a2e8124351fda1ef8aaaa3bbd7ebbcb486bbcd4225aca0aa0d84bb2db8fecb"
[[package]]
name = "arrayvec"
@@ -385,7 +379,7 @@ dependencies = [
"serde",
"serde_json",
"url",
- "webpki-roots 0.26.5",
+ "webpki-roots 0.26.6",
]
[[package]]
@@ -469,17 +463,17 @@ dependencies = [
[[package]]
name = "backtrace"
-version = "0.3.73"
+version = "0.3.74"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "5cc23269a4f8976d0a4d2e7109211a419fe30e8d88d677cd60b6bc79c5732e0a"
+checksum = "8d82cb332cdfaed17ae235a638438ac4d4839913cc2af585c3c6746e8f8bee1a"
dependencies = [
"addr2line",
- "cc",
"cfg-if",
"libc",
- "miniz_oxide 0.7.4",
+ "miniz_oxide",
"object",
"rustc-demangle",
+ "windows-targets 0.52.6",
]
[[package]]
@@ -788,9 +782,9 @@ dependencies = [
[[package]]
name = "bytemuck"
-version = "1.17.1"
+version = "1.18.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "773d90827bc3feecfb67fab12e24de0749aad83c74b9504ecde46237b5cd24e2"
+checksum = "94bbb0ad554ad961ddc5da507a12a29b14e4ae5bda06b19f575a3e6079d2e2ae"
[[package]]
name = "byteorder"
@@ -800,9 +794,9 @@ checksum = "1fd0f2584146f6f2ef48085050886acf353beff7305ebd1ae69500e27c67f64b"
[[package]]
name = "bytes"
-version = "1.7.1"
+version = "1.7.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "8318a53db07bb3f8dca91a600466bdb3f2eaadeedfdbcf02e1accbad9271ba50"
+checksum = "428d9aa8fbc0670b7b8d6030a7fadd0f86151cae55e4dbbece15f3780a3dfaf3"
[[package]]
name = "bzip2"
@@ -855,9 +849,9 @@ dependencies = [
[[package]]
name = "cc"
-version = "1.1.15"
+version = "1.1.21"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "57b6a275aa2903740dc87da01c62040406b8812552e97129a63ea8850a17c6e6"
+checksum = "07b1695e2c7e8fc85310cde85aeaab7e3097f593c91d209d3f9df76c928100f0"
dependencies = [
"jobserver",
"libc",
@@ -962,9 +956,9 @@ dependencies = [
[[package]]
name = "clap"
-version = "4.5.16"
+version = "4.5.17"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "ed6719fffa43d0d87e5fd8caeab59be1554fb028cd30edc88fc4369b17971019"
+checksum = "3e5a21b8495e732f1b3c364c9949b201ca7bae518c502c80256c96ad79eaf6ac"
dependencies = [
"clap_builder",
"clap_derive",
@@ -972,9 +966,9 @@ dependencies = [
[[package]]
name = "clap_builder"
-version = "4.5.15"
+version = "4.5.17"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "216aec2b177652e3846684cbfe25c9964d18ec45234f0f5da5157b207ed1aab6"
+checksum = "8cf2dd12af7a047ad9d6da2b6b249759a22a7abc0f474c1dae1777afa4b21a73"
dependencies = [
"anstream",
"anstyle",
@@ -1042,7 +1036,7 @@ dependencies = [
[[package]]
name = "common"
-version = "0.9.4"
+version = "0.10.0"
dependencies = [
"ahash 0.8.11",
"arc-swap",
@@ -1075,12 +1069,13 @@ dependencies = [
"privdrop",
"prometheus",
"proxy-header",
+ "psl",
"pwhash",
"rcgen 0.12.1",
"regex",
"reqwest 0.12.7",
"ring 0.17.8",
- "rustls 0.23.12",
+ "rustls 0.23.13",
"rustls-pemfile 2.1.3",
"rustls-pki-types",
"serde",
@@ -1176,9 +1171,9 @@ checksum = "773648b94d0e5d620f64f280777445740e61fe701025087ec8b57f45c791888b"
[[package]]
name = "cpufeatures"
-version = "0.2.13"
+version = "0.2.14"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "51e852e6dc9a5bed1fae92dd2375037bf2b768725bf3be87811edee3249d09ad"
+checksum = "608697df725056feaccfa42cffdaeeec3fccc4ffc38358ecd19b243e716a78e0"
dependencies = [
"libc",
]
@@ -1437,9 +1432,9 @@ dependencies = [
[[package]]
name = "dashmap"
-version = "6.0.1"
+version = "6.1.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "804c8821570c3f8b70230c2ba75ffa5c0f9a4189b9a432b6656c536712acae28"
+checksum = "5041cc499144891f3790297212f32a74fb938e5136a14943f338ef9e0ae276cf"
dependencies = [
"cfg-if",
"crossbeam-utils",
@@ -1650,7 +1645,7 @@ dependencies = [
[[package]]
name = "directory"
-version = "0.9.4"
+version = "0.10.0"
dependencies = [
"ahash 0.8.11",
"argon2",
@@ -1670,7 +1665,7 @@ dependencies = [
"proc_macros",
"pwhash",
"regex",
- "rustls 0.23.12",
+ "rustls 0.23.13",
"rustls-pki-types",
"scrypt",
"serde",
@@ -1932,11 +1927,11 @@ checksum = "c34f04666d835ff5d62e058c3995147c06f42fe86ff053337632bca83e42702d"
[[package]]
name = "enum-as-inner"
-version = "0.6.0"
+version = "0.6.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "5ffccbb6966c05b32ef8fbac435df276c4ae4d3dc55a8cd0eb9745e6c12f546a"
+checksum = "a1e6a265c649f3f5979b601d26f1d05ada116434c87741c9493cb56218f76cbc"
dependencies = [
- "heck 0.4.1",
+ "heck 0.5.0",
"proc-macro2",
"quote",
"syn 2.0.77",
@@ -2038,7 +2033,7 @@ checksum = "324a1be68054ef05ad64b861cc9eaf1d623d2d8cb25b4bf2cb9cdd902b4bf253"
dependencies = [
"crc32fast",
"libz-sys",
- "miniz_oxide 0.8.0",
+ "miniz_oxide",
]
[[package]]
@@ -2354,9 +2349,9 @@ dependencies = [
[[package]]
name = "gimli"
-version = "0.29.0"
+version = "0.31.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "40ecd4077b5ae9fd2e9e169b102c6c330d0605168eb0e8bf79952b256dbefffd"
+checksum = "32085ea23f3234fc7846555e85283ba4de91e21016dc0455a16286d87a292d64"
[[package]]
name = "glob"
@@ -2739,20 +2734,20 @@ dependencies = [
[[package]]
name = "hyper-rustls"
-version = "0.27.2"
+version = "0.27.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "5ee4be2c948921a1a5320b629c4193916ed787a7f7f293fd3f7f5a6c9de74155"
+checksum = "08afdbb5c31130e3034af566421053ab03787c640246a446327f550d11bcb333"
dependencies = [
"futures-util",
"http 1.1.0",
"hyper 1.4.1",
"hyper-util",
- "rustls 0.23.12",
+ "rustls 0.23.13",
"rustls-pki-types",
"tokio",
"tokio-rustls 0.26.0",
"tower-service",
- "webpki-roots 0.26.5",
+ "webpki-roots 0.26.6",
]
[[package]]
@@ -2770,9 +2765,9 @@ dependencies = [
[[package]]
name = "hyper-util"
-version = "0.1.7"
+version = "0.1.8"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "cde7055719c54e36e95e8719f95883f22072a48ede39db7fc17a4e1d5281e9b9"
+checksum = "da62f120a8a37763efb0cf8fdf264b884c7b8b9ac8660b900c8661030c00e6ba"
dependencies = [
"bytes",
"futures-channel",
@@ -2790,9 +2785,9 @@ dependencies = [
[[package]]
name = "iana-time-zone"
-version = "0.1.60"
+version = "0.1.61"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "e7ffbb5a1b541ea2561f8c41c087286cc091e21e556a4f09a8f6cbf17b69b141"
+checksum = "235e081f3925a06703c2d0117ea8b91f042756fd6e7a6e5d901e8ca1a996b220"
dependencies = [
"android_system_properties",
"core-foundation-sys",
@@ -2984,7 +2979,7 @@ checksum = "edcd27d72f2f071c64249075f42e205ff93c9a4c5f6c6da53e79ed9f9832c285"
[[package]]
name = "imap"
-version = "0.9.4"
+version = "0.10.0"
dependencies = [
"ahash 0.8.11",
"common",
@@ -2999,7 +2994,7 @@ dependencies = [
"nlp",
"parking_lot",
"rand",
- "rustls 0.23.12",
+ "rustls 0.23.13",
"rustls-pemfile 2.1.3",
"store",
"tokio",
@@ -3096,9 +3091,9 @@ dependencies = [
[[package]]
name = "ipnet"
-version = "2.9.0"
+version = "2.10.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "8f518f335dce6725a761382244631d86cf0ccb2863413590b31338feb467f9c3"
+checksum = "187674a687eed5fe42285b40c6291f9a01517d415fad1c3cbc6a9f778af7fcd4"
[[package]]
name = "is-terminal"
@@ -3196,7 +3191,7 @@ dependencies = [
[[package]]
name = "jmap"
-version = "0.9.4"
+version = "0.10.0"
dependencies = [
"aes",
"aes-gcm",
@@ -3549,9 +3544,9 @@ dependencies = [
[[package]]
name = "lz4-sys"
-version = "1.10.0"
+version = "1.11.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "109de74d5d2353660401699a4174a4ff23fcc649caf553df71933c7fb45ad868"
+checksum = "fcb44a01837a858d47e5a630d2ccf304c8efcc4b83b8f9f75b7a9ee4fcc6e57d"
dependencies = [
"cc",
"libc",
@@ -3575,9 +3570,9 @@ dependencies = [
[[package]]
name = "mail-auth"
-version = "0.4.3"
+version = "0.5.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "9bd9d657de66a3d5ac360c3eab8c9f5cac2565f2b97cc032d5de4c900ef470de"
+checksum = "aaee4c38f4df428c6732f3d5472a013fa248d2772f48c8932295b32c683a23c4"
dependencies = [
"ahash 0.8.11",
"flate2",
@@ -3586,7 +3581,7 @@ dependencies = [
"mail-builder",
"mail-parser",
"parking_lot",
- "quick-xml 0.32.0",
+ "quick-xml 0.36.1",
"rand",
"ring 0.17.8",
"rsa",
@@ -3607,9 +3602,9 @@ dependencies = [
[[package]]
name = "mail-parser"
-version = "0.9.3"
+version = "0.9.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "ed5a1335c3a964788c90cb42ae04a34b5f2628e89566949ce3bd4ada695c0bcd"
+checksum = "93c3b9e5d8b17faf573330bbc43b37d6e918c0a3bf8a88e7d0a220ebc84af9fc"
dependencies = [
"encoding_rs",
"serde",
@@ -3624,17 +3619,17 @@ dependencies = [
"base64 0.22.1",
"gethostname",
"md5",
- "rustls 0.23.12",
+ "rustls 0.23.13",
"rustls-pki-types",
"smtp-proto",
"tokio",
"tokio-rustls 0.26.0",
- "webpki-roots 0.26.5",
+ "webpki-roots 0.26.6",
]
[[package]]
name = "mail-server"
-version = "0.9.4"
+version = "0.10.0"
dependencies = [
"common",
"directory",
@@ -3653,7 +3648,7 @@ dependencies = [
[[package]]
name = "managesieve"
-version = "0.9.4"
+version = "0.10.0"
dependencies = [
"ahash 0.8.11",
"bincode",
@@ -3667,7 +3662,7 @@ dependencies = [
"mail-send",
"md5",
"parking_lot",
- "rustls 0.23.12",
+ "rustls 0.23.13",
"rustls-pemfile 2.1.3",
"sieve-rs",
"store",
@@ -3788,15 +3783,6 @@ checksum = "68354c5c6bd36d73ff3feceb05efa59b6acb7626617f4962be322a825e61f79a"
[[package]]
name = "miniz_oxide"
-version = "0.7.4"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "b8a240ddb74feaf34a79a7add65a741f3167852fba007066dcac1ca548d89c08"
-dependencies = [
- "adler",
-]
-
-[[package]]
-name = "miniz_oxide"
version = "0.8.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e2d80299ef12ff69b16a84bb182e3b9df68b5a91574d3d4fa6e41b65deec4df1"
@@ -3880,7 +3866,7 @@ dependencies = [
"twox-hash",
"url",
"webpki",
- "webpki-roots 0.26.5",
+ "webpki-roots 0.26.6",
]
[[package]]
@@ -3952,7 +3938,7 @@ dependencies = [
[[package]]
name = "nlp"
-version = "0.9.4"
+version = "0.10.0"
dependencies = [
"ahash 0.8.11",
"bincode",
@@ -3962,12 +3948,12 @@ dependencies = [
"nohash",
"parking_lot",
"phf",
+ "psl",
"rust-stemmers",
"serde",
"siphasher 1.0.1",
"tinysegmenter",
"tokio",
- "utils",
"whatlang",
"xxhash-rust",
]
@@ -4300,7 +4286,7 @@ checksum = "1e401f977ab385c9e4e3ab30627d6f26d00e2c73eef317493c4ec6d468726cf8"
dependencies = [
"cfg-if",
"libc",
- "redox_syscall 0.5.3",
+ "redox_syscall",
"smallvec",
"windows-targets 0.52.6",
]
@@ -4503,7 +4489,7 @@ dependencies = [
[[package]]
name = "pop3"
-version = "0.9.4"
+version = "0.10.0"
dependencies = [
"common",
"directory",
@@ -4512,7 +4498,7 @@ dependencies = [
"jmap_proto",
"mail-parser",
"mail-send",
- "rustls 0.23.12",
+ "rustls 0.23.13",
"store",
"tokio",
"tokio-rustls 0.26.0",
@@ -4546,9 +4532,9 @@ dependencies = [
[[package]]
name = "postgres-types"
-version = "0.2.7"
+version = "0.2.8"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "02048d9e032fb3cc3413bbf7b83a15d84a5d419778e2628751896d856498eee9"
+checksum = "f66ea23a2d0e5734297357705193335e0a957696f34bed2f2faefacb2fec336f"
dependencies = [
"bytes",
"fallible-iterator 0.2.0",
@@ -4718,6 +4704,21 @@ dependencies = [
]
[[package]]
+name = "psl"
+version = "2.1.55"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "ce9398ad066421139b2e3afe16ea46772ffda30bd9ba57554dc035df5e26edc8"
+dependencies = [
+ "psl-types",
+]
+
+[[package]]
+name = "psl-types"
+version = "2.0.11"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "33cb294fe86a74cbcf50d4445b37da762029549ebeea341421c7c70370f86cac"
+
+[[package]]
name = "ptr_meta"
version = "0.1.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
@@ -4779,16 +4780,16 @@ dependencies = [
[[package]]
name = "quinn"
-version = "0.11.3"
+version = "0.11.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "b22d8e7369034b9a7132bc2008cac12f2013c8132b45e0554e6e20e2617f2156"
+checksum = "8c7c5fdde3cdae7203427dc4f0a68fe0ed09833edc525a03456b153b79828684"
dependencies = [
"bytes",
"pin-project-lite",
"quinn-proto",
"quinn-udp",
"rustc-hash 2.0.0",
- "rustls 0.23.12",
+ "rustls 0.23.13",
"socket2",
"thiserror",
"tokio",
@@ -4797,15 +4798,15 @@ dependencies = [
[[package]]
name = "quinn-proto"
-version = "0.11.6"
+version = "0.11.8"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "ba92fb39ec7ad06ca2582c0ca834dfeadcaf06ddfc8e635c80aa7e1c05315fdd"
+checksum = "fadfaed2cd7f389d0161bb73eeb07b7b78f8691047a6f3e73caaeae55310a4a6"
dependencies = [
"bytes",
"rand",
"ring 0.17.8",
"rustc-hash 2.0.0",
- "rustls 0.23.12",
+ "rustls 0.23.13",
"slab",
"thiserror",
"tinyvec",
@@ -4814,15 +4815,15 @@ dependencies = [
[[package]]
name = "quinn-udp"
-version = "0.5.4"
+version = "0.5.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "8bffec3605b73c6f1754535084a85229fa8a30f86014e6c81aeec4abb68b0285"
+checksum = "4fe68c2e9e1a1234e218683dbdf9f9dfcb094113c5ac2b938dfcb9bab4c4140b"
dependencies = [
"libc",
"once_cell",
"socket2",
"tracing",
- "windows-sys 0.52.0",
+ "windows-sys 0.59.0",
]
[[package]]
@@ -5011,7 +5012,7 @@ dependencies = [
"percent-encoding",
"pin-project-lite",
"rand",
- "rustls 0.23.12",
+ "rustls 0.23.13",
"rustls-native-certs 0.7.3",
"rustls-pemfile 2.1.3",
"rustls-pki-types",
@@ -5022,23 +5023,14 @@ dependencies = [
"tokio-rustls 0.26.0",
"tokio-util",
"url",
- "webpki-roots 0.26.5",
-]
-
-[[package]]
-name = "redox_syscall"
-version = "0.4.1"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "4722d768eff46b75989dd134e5c353f0d6296e5aaa3132e776cbdb56be7731aa"
-dependencies = [
- "bitflags 1.3.2",
+ "webpki-roots 0.26.6",
]
[[package]]
name = "redox_syscall"
-version = "0.5.3"
+version = "0.5.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "2a908a6e00f1fdd0dfd9c0eb08ce85126f6d8bbda50017e74bc4a4b7d4a926a4"
+checksum = "0884ad60e090bf1345b93da0a5de8923c93884cd03f40dfcfddd3b4bee661853"
dependencies = [
"bitflags 2.6.0",
]
@@ -5152,7 +5144,7 @@ dependencies = [
"http-body 1.0.1",
"http-body-util",
"hyper 1.4.1",
- "hyper-rustls 0.27.2",
+ "hyper-rustls 0.27.3",
"hyper-util",
"ipnet",
"js-sys",
@@ -5163,7 +5155,7 @@ dependencies = [
"percent-encoding",
"pin-project-lite",
"quinn",
- "rustls 0.23.12",
+ "rustls 0.23.13",
"rustls-pemfile 2.1.3",
"rustls-pki-types",
"serde",
@@ -5177,7 +5169,7 @@ dependencies = [
"wasm-bindgen",
"wasm-bindgen-futures",
"web-sys",
- "webpki-roots 0.26.5",
+ "webpki-roots 0.26.6",
"windows-registry",
]
@@ -5480,9 +5472,9 @@ dependencies = [
[[package]]
name = "rustix"
-version = "0.38.35"
+version = "0.38.37"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "a85d50532239da68e9addb745ba38ff4612a242c1c7ceea689c4bc7c2f43c36f"
+checksum = "8acb788b847c24f28525660c4d7758620a7210875711f79e7f663cc152726811"
dependencies = [
"bitflags 2.6.0",
"errno",
@@ -5512,21 +5504,21 @@ dependencies = [
"log",
"ring 0.17.8",
"rustls-pki-types",
- "rustls-webpki 0.102.7",
+ "rustls-webpki 0.102.8",
"subtle",
"zeroize",
]
[[package]]
name = "rustls"
-version = "0.23.12"
+version = "0.23.13"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "c58f8c84392efc0a126acce10fa59ff7b3d2ac06ab451a33f2741989b806b044"
+checksum = "f2dabaac7466917e566adb06783a81ca48944c6898a1b08b9374106dd671f4c8"
dependencies = [
"once_cell",
"ring 0.17.8",
"rustls-pki-types",
- "rustls-webpki 0.102.7",
+ "rustls-webpki 0.102.8",
"subtle",
"zeroize",
]
@@ -5593,9 +5585,9 @@ dependencies = [
[[package]]
name = "rustls-webpki"
-version = "0.102.7"
+version = "0.102.8"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "84678086bd54edf2b415183ed7a94d0efb049f1b646a33e22a36f3794be6ae56"
+checksum = "64ca1bc8749bd4cf37b5ce386cc146580777b4e8572c7b97baf22c83f444bee9"
dependencies = [
"ring 0.17.8",
"rustls-pki-types",
@@ -5640,20 +5632,20 @@ checksum = "ece8e78b2f38ec51c51f5d475df0a7187ba5111b2a28bdc761ee05b075d40a71"
[[package]]
name = "scc"
-version = "2.1.16"
+version = "2.1.17"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "aeb7ac86243095b70a7920639507b71d51a63390d1ba26c4f60a552fbb914a37"
+checksum = "0c947adb109a8afce5fc9c7bf951f87f146e9147b3a6a58413105628fb1d1e66"
dependencies = [
"sdd",
]
[[package]]
name = "schannel"
-version = "0.1.23"
+version = "0.1.24"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "fbc91545643bcf3a0bbb6569265615222618bdf33ce4ffbbd13c4bbd4c093534"
+checksum = "e9aaafd5a2b6e3d657ff009d82fbd630b6bd54dd4eb06f21693925cdf80f9b8b"
dependencies = [
- "windows-sys 0.52.0",
+ "windows-sys 0.59.0",
]
[[package]]
@@ -5695,9 +5687,9 @@ dependencies = [
[[package]]
name = "sdd"
-version = "3.0.2"
+version = "3.0.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "0495e4577c672de8254beb68d01a9b62d0e8a13c099edecdbedccce3223cd29f"
+checksum = "60a7b59a5d9b0099720b417b6325d91a52cbf5b3dcb5041d864be53eefa58abc"
[[package]]
name = "seahash"
@@ -5821,9 +5813,9 @@ dependencies = [
[[package]]
name = "serde"
-version = "1.0.209"
+version = "1.0.210"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "99fce0ffe7310761ca6bf9faf5115afbc19688edd00171d81b1bb1b116c63e09"
+checksum = "c8e3592472072e6e22e0a54d5904d9febf8508f65fb8552499a1abc7d1078c3a"
dependencies = [
"serde_derive",
]
@@ -5839,9 +5831,9 @@ dependencies = [
[[package]]
name = "serde_derive"
-version = "1.0.209"
+version = "1.0.210"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "a5831b979fd7b5439637af1752d535ff49f4860c0f341d1baeb6faf0f4242170"
+checksum = "243902eda00fad750862fc144cea25caca5e20d615af0a81bee94ca738f1df1f"
dependencies = [
"proc-macro2",
"quote",
@@ -5850,9 +5842,9 @@ dependencies = [
[[package]]
name = "serde_json"
-version = "1.0.127"
+version = "1.0.128"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "8043c06d9f82bd7271361ed64f415fe5e12a77fdb52e573e7f06a516dea329ad"
+checksum = "6ff5456707a1de34e7e37f2a6fd3d3f808c318259cbd01ab6377795054b483d8"
dependencies = [
"itoa",
"memchr",
@@ -6065,7 +6057,7 @@ checksum = "3c5e1a9a646d36c3599cd173a41282daf47c44583ad367b8e6837255952e5c67"
[[package]]
name = "smtp"
-version = "0.9.4"
+version = "0.10.0"
dependencies = [
"ahash 0.8.11",
"bincode",
@@ -6091,7 +6083,7 @@ dependencies = [
"rayon",
"regex",
"reqwest 0.12.7",
- "rustls 0.23.12",
+ "rustls 0.23.13",
"rustls-pemfile 2.1.3",
"rustls-pki-types",
"serde",
@@ -6105,7 +6097,7 @@ dependencies = [
"tokio-rustls 0.26.0",
"trc",
"utils",
- "webpki-roots 0.26.5",
+ "webpki-roots 0.26.6",
"x509-parser 0.16.0",
]
@@ -6181,7 +6173,7 @@ checksum = "a8f112729512f8e442d81f95a8a7ddf2b7c6b8a1a6f509a95864142b30cab2d3"
[[package]]
name = "stalwart-cli"
-version = "0.9.4"
+version = "0.10.0"
dependencies = [
"clap",
"console",
@@ -6212,7 +6204,7 @@ checksum = "a2eb9349b6444b326872e140eb1cf5e7c522154d69e7a0ffb0fb81c06b37543f"
[[package]]
name = "store"
-version = "0.9.4"
+version = "0.10.0"
dependencies = [
"ahash 0.8.11",
"arc-swap",
@@ -6244,7 +6236,7 @@ dependencies = [
"rocksdb",
"rusqlite",
"rust-s3",
- "rustls 0.23.12",
+ "rustls 0.23.13",
"rustls-pki-types",
"serde",
"serde_json",
@@ -6462,7 +6454,7 @@ dependencies = [
"rayon",
"reqwest 0.12.7",
"ring 0.17.8",
- "rustls 0.23.12",
+ "rustls 0.23.13",
"rustls-pemfile 2.1.3",
"rustls-pki-types",
"serde",
@@ -6604,9 +6596,9 @@ dependencies = [
[[package]]
name = "tokio-postgres"
-version = "0.7.11"
+version = "0.7.12"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "03adcf0147e203b6032c0b2d30be1415ba03bc348901f3ff1cc0df6a733e60c3"
+checksum = "3b5d3742945bc7d7f210693b0c58ae542c6fd47b17adbbda0885f3dcb34a6bdb"
dependencies = [
"async-trait",
"byteorder",
@@ -6655,16 +6647,16 @@ version = "0.26.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0c7bc40d0e5a97695bb96e27995cd3a08538541b0a846f65bba7a359f36700d4"
dependencies = [
- "rustls 0.23.12",
+ "rustls 0.23.13",
"rustls-pki-types",
"tokio",
]
[[package]]
name = "tokio-stream"
-version = "0.1.15"
+version = "0.1.16"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "267ac89e0bec6e691e5813911606935d77c476ff49024f98abcea3e7b15e37af"
+checksum = "4f4e6ce100d0eb49a2734f8c0812bcd324cf357d21810932c5df6b96ef2b86f1"
dependencies = [
"futures-core",
"pin-project-lite",
@@ -6684,7 +6676,7 @@ dependencies = [
"tokio",
"tokio-rustls 0.25.0",
"tungstenite 0.21.0",
- "webpki-roots 0.26.5",
+ "webpki-roots 0.26.6",
]
[[package]]
@@ -6701,9 +6693,9 @@ dependencies = [
[[package]]
name = "tokio-util"
-version = "0.7.11"
+version = "0.7.12"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "9cf6b47b3771c49ac75ad09a6162f53ad4b8088b76ac60e8ec1455b31a189fe1"
+checksum = "61e7c3654c13bcd040d4a03abee2c75b1d14a37b423cf5a813ceae1cc903ec6a"
dependencies = [
"bytes",
"futures-core",
@@ -6720,9 +6712,9 @@ checksum = "0dd7358ecb8fc2f8d014bf86f6f638ce72ba252a2c3a2572f2a795f1d23efb41"
[[package]]
name = "toml_edit"
-version = "0.22.20"
+version = "0.22.21"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "583c44c02ad26b0c3f3066fe629275e50627026c51ac2e595cca4c230ce1ce1d"
+checksum = "3b072cee73c449a636ffd6f32bd8de3a9f7119139aff882f44943ce2986dc5cf"
dependencies = [
"indexmap 2.5.0",
"toml_datetime",
@@ -6839,7 +6831,7 @@ dependencies = [
[[package]]
name = "trc"
-version = "0.9.4"
+version = "0.10.0"
dependencies = [
"ahash 0.8.11",
"base64 0.22.1",
@@ -6970,15 +6962,15 @@ checksum = "08f95100a766bf4f8f28f90d77e0a5461bbdb219042e7679bebe79004fed8d75"
[[package]]
name = "unicode-ident"
-version = "1.0.12"
+version = "1.0.13"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "3354b9ac3fae1ff6755cb6db53683adb661634f67557942dea4facebec0fee4b"
+checksum = "e91b56cd4cadaeb79bbf1a5645f6b4f8dc5bde8834ad5894a8db35fda9efa1fe"
[[package]]
name = "unicode-normalization"
-version = "0.1.23"
+version = "0.1.24"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "a56d1686db2308d901306f92a263857ef59ea39678a5458e7cb17f01415101f5"
+checksum = "5033c97c4262335cded6d6fc3e5c18ab755e1a3dc96376350f3d8e9f009ad956"
dependencies = [
"tinyvec",
]
@@ -6997,9 +6989,9 @@ checksum = "ad8d71f5726e5f285a935e9fe8edfd53f0491eb6e9a5774097fdabee7cd8c9cd"
[[package]]
name = "unicode-security"
-version = "0.1.1"
+version = "0.1.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "ee9e13753df674873f3c4693b240ae5c03245ddc157dfccf7c26db9329af3a11"
+checksum = "2e4ddba1535dd35ed8b61c52166b7155d7f4e4b8847cec6f48e71dc66d8b5e50"
dependencies = [
"unicode-normalization",
"unicode-script",
@@ -7013,9 +7005,9 @@ checksum = "0336d538f7abc86d282a4189614dfaa90810dfc2c6f6427eaf88e16311dd225d"
[[package]]
name = "unicode-xid"
-version = "0.2.5"
+version = "0.2.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "229730647fbc343e3a80e463c1db7f78f3855d3f3739bee0dda773c9a037c90a"
+checksum = "ebc1c04c71510c7f702b52b7c350734c9ff1295c464a03335b00bb84fc54f853"
[[package]]
name = "universal-hash"
@@ -7082,7 +7074,7 @@ checksum = "06abde3611657adf66d383f00b093d7faecc7fa57071cce2578660c9f1010821"
[[package]]
name = "utils"
-version = "0.9.4"
+version = "0.10.0"
dependencies = [
"ahash 0.8.11",
"base64 0.22.1",
@@ -7103,7 +7095,7 @@ dependencies = [
"regex",
"reqwest 0.12.7",
"ring 0.17.8",
- "rustls 0.23.12",
+ "rustls 0.23.13",
"rustls-pemfile 2.1.3",
"rustls-pki-types",
"serde",
@@ -7112,7 +7104,7 @@ dependencies = [
"tokio",
"tokio-rustls 0.26.0",
"trc",
- "webpki-roots 0.26.5",
+ "webpki-roots 0.26.6",
"x509-parser 0.16.0",
]
@@ -7282,9 +7274,9 @@ checksum = "5f20c57d8d7db6d3b86154206ae5d8fba62dd39573114de97c2cb0578251f8e1"
[[package]]
name = "webpki-roots"
-version = "0.26.5"
+version = "0.26.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "0bd24728e5af82c6c4ec1b66ac4844bdf8156257fccda846ec58b42cd0cdbe6a"
+checksum = "841c67bff177718f1d4dfefde8d8f0e78f9b6589319ba88312f567fc5841a958"
dependencies = [
"rustls-pki-types",
]
@@ -7313,11 +7305,11 @@ dependencies = [
[[package]]
name = "whoami"
-version = "1.5.1"
+version = "1.5.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "a44ab49fad634e88f55bf8f9bb3abd2f27d7204172a112c7c9987e01c1c94ea9"
+checksum = "372d5b87f58ec45c384ba03563b03544dc5fadc3983e434b286913f5b4a9bb6d"
dependencies = [
- "redox_syscall 0.4.1",
+ "redox_syscall",
"wasite",
"web-sys",
]
@@ -7643,9 +7635,9 @@ dependencies = [
[[package]]
name = "xml-rs"
-version = "0.8.21"
+version = "0.8.22"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "539a77ee7c0de333dcc6da69b177380a0b81e0dacfa4f7344c465a36871ee601"
+checksum = "af4e2e2f7cba5a093896c1e150fbfe177d1883e7448200efb81d40b9d339ef26"
[[package]]
name = "xxhash-rust"
diff --git a/crates/cli/Cargo.toml b/crates/cli/Cargo.toml
index 23269160..c599a0ec 100644
--- a/crates/cli/Cargo.toml
+++ b/crates/cli/Cargo.toml
@@ -5,7 +5,7 @@ authors = ["Stalwart Labs Ltd. <hello@stalw.art>"]
license = "AGPL-3.0-only OR LicenseRef-SEL"
repository = "https://github.com/stalwartlabs/cli"
homepage = "https://github.com/stalwartlabs/cli"
-version = "0.9.4"
+version = "0.10.0"
edition = "2021"
readme = "README.md"
resolver = "2"
@@ -29,4 +29,4 @@ human-size = "0.4.2"
futures = "0.3.28"
pwhash = "1.0.0"
rand = "0.8.5"
-mail-auth = { version = "0.4" }
+mail-auth = { version = "0.5" }
diff --git a/crates/cli/src/main.rs b/crates/cli/src/main.rs
index 8da26622..3e94714e 100644
--- a/crates/cli/src/main.rs
+++ b/crates/cli/src/main.rs
@@ -88,7 +88,7 @@ async fn oauth(url: &str) -> Credentials {
.danger_accept_invalid_certs(is_localhost(url))
.build()
.unwrap_or_default()
- .get(&format!("{}/.well-known/oauth-authorization-server", url))
+ .get(format!("{}/.well-known/oauth-authorization-server", url))
.send()
.await
.unwrap_result("send OAuth GET request")
diff --git a/crates/common/Cargo.toml b/crates/common/Cargo.toml
index 515991ce..253424eb 100644
--- a/crates/common/Cargo.toml
+++ b/crates/common/Cargo.toml
@@ -1,6 +1,6 @@
[package]
name = "common"
-version = "0.9.4"
+version = "0.10.0"
edition = "2021"
resolver = "2"
@@ -14,7 +14,7 @@ jmap_proto = { path = "../jmap-proto" }
sieve-rs = { version = "0.5" }
mail-parser = { version = "0.9", features = ["full_encoding", "ludicrous_mode"] }
mail-builder = { version = "0.3", features = ["ludicrous_mode"] }
-mail-auth = { version = "0.4" }
+mail-auth = { version = "0.5" }
mail-send = { version = "0.4", default-features = false, features = ["cram-md5", "ring", "tls12"] }
smtp-proto = { version = "0.1", features = ["serde_support"] }
dns-update = { version = "0.1" }
@@ -58,6 +58,7 @@ hostname = "0.4.0"
zip = "2.1"
pwhash = "1.0.0"
xxhash-rust = { version = "0.8.5", features = ["xxh3"] }
+psl = "2"
[target.'cfg(unix)'.dependencies]
privdrop = "0.5.3"
diff --git a/crates/common/src/config/mod.rs b/crates/common/src/config/mod.rs
index 538e35f1..edd5ed29 100644
--- a/crates/common/src/config/mod.rs
+++ b/crates/common/src/config/mod.rs
@@ -175,6 +175,7 @@ impl Core {
32,
),
permissions_version: Default::default(),
+ logos: Default::default(),
},
storage: Storage {
data,
diff --git a/crates/common/src/config/smtp/resolver.rs b/crates/common/src/config/smtp/resolver.rs
index f28f2a7c..ffe43328 100644
--- a/crates/common/src/config/smtp/resolver.rs
+++ b/crates/common/src/config/smtp/resolver.rs
@@ -22,10 +22,7 @@ use mail_auth::{
Resolver,
};
use parking_lot::Mutex;
-use utils::{
- config::{utils::ParseValue, Config},
- suffixlist::PublicSuffix,
-};
+use utils::config::{utils::ParseValue, Config};
use crate::Core;
@@ -33,7 +30,6 @@ pub struct Resolvers {
pub dns: Resolver,
pub dnssec: DnssecResolver,
pub cache: DnsRecordCache,
- pub psl: PublicSuffix,
}
#[derive(Clone)]
@@ -237,7 +233,6 @@ impl Resolvers {
.unwrap_or(1024),
),
},
- psl: PublicSuffix::parse(config, "resolver.public-suffix").await,
}
}
}
@@ -356,7 +351,6 @@ impl Default for Resolvers {
tlsa: LruCache::with_capacity(1024),
mta_sts: LruCache::with_capacity(1024),
},
- psl: PublicSuffix::default(),
}
}
}
@@ -402,7 +396,6 @@ impl Clone for Resolvers {
dns: self.dns.clone(),
dnssec: self.dnssec.clone(),
cache: self.cache.clone(),
- psl: self.psl.clone(),
}
}
}
diff --git a/crates/common/src/enterprise/config.rs b/crates/common/src/enterprise/config.rs
index a822c792..b076ab65 100644
--- a/crates/common/src/enterprise/config.rs
+++ b/crates/common/src/enterprise/config.rs
@@ -117,6 +117,7 @@ impl Enterprise {
.property_or_default::<Option<Duration>>("storage.undelete.retention", "false")
.unwrap_or_default()
.map(|retention| Undelete { retention }),
+ logo_url: config.value("enterprise.logo-url").map(|s| s.to_string()),
trace_store,
metrics_store,
metrics_alerts: parse_metric_alerts(config),
diff --git a/crates/common/src/enterprise/mod.rs b/crates/common/src/enterprise/mod.rs
index 0f0ac995..e4a2230e 100644
--- a/crates/common/src/enterprise/mod.rs
+++ b/crates/common/src/enterprise/mod.rs
@@ -15,17 +15,22 @@ pub mod undelete;
use std::time::Duration;
+use directory::{
+ backend::internal::{lookup::DirectoryStore, PrincipalField},
+ QueryBy, Type,
+};
use license::LicenseKey;
use mail_parser::DateTime;
use store::Store;
-use trc::{EventType, MetricType};
+use trc::{AddContext, EventType, MetricType};
use utils::config::cron::SimpleCron;
-use crate::{expr::Expression, Core};
+use crate::{expr::Expression, manager::webadmin::Resource, Core};
#[derive(Clone)]
pub struct Enterprise {
pub license: LicenseKey,
+ pub logo_url: Option<String>,
pub undelete: Option<Undelete>,
pub trace_store: Option<TraceStore>,
pub metrics_store: Option<MetricStore>,
@@ -114,4 +119,95 @@ impl Core {
);
}
}
+
+ pub async fn logo_resource(&self, domain: &str) -> trc::Result<Option<Resource<Vec<u8>>>> {
+ if self.is_enterprise_edition() {
+ let domain = psl::domain_str(domain).unwrap_or(domain);
+ let logo = { self.security.logos.lock().get(domain).cloned() };
+
+ if let Some(logo) = logo {
+ Ok(logo)
+ } else {
+ // Try fetching the logo for the domain
+ let logo_url = if let Some(mut principal) = self
+ .storage
+ .data
+ .query(QueryBy::Name(domain), false)
+ .await
+ .caused_by(trc::location!())?
+ .filter(|p| p.typ() == Type::Domain)
+ {
+ if let Some(logo) = principal
+ .take_str(PrincipalField::Picture)
+ .filter(|l| l.starts_with("http"))
+ {
+ logo.into()
+ } else if let Some(tenant_id) = principal.get_int(PrincipalField::Tenant) {
+ if let Some(logo) = self
+ .storage
+ .data
+ .query(QueryBy::Id(tenant_id as u32), false)
+ .await
+ .caused_by(trc::location!())?
+ .and_then(|mut p| p.take_str(PrincipalField::Picture))
+ .filter(|l| l.starts_with("http"))
+ {
+ logo.into()
+ } else {
+ self.default_logo_url()
+ }
+ } else {
+ self.default_logo_url()
+ }
+ } else {
+ self.default_logo_url()
+ };
+
+ let mut logo = None;
+ if let Some(logo_url) = logo_url {
+ let response = reqwest::get(&logo_url).await.map_err(|err| {
+ trc::ResourceEvent::DownloadExternal
+ .into_err()
+ .details("Failed to download logo")
+ .reason(err)
+ })?;
+
+ let content_type = response
+ .headers()
+ .get(reqwest::header::CONTENT_TYPE)
+ .and_then(|ct| ct.to_str().ok())
+ .unwrap_or("image/svg+xml")
+ .to_string();
+
+ let contents = response
+ .bytes()
+ .await
+ .map_err(|err| {
+ trc::ResourceEvent::DownloadExternal
+ .into_err()
+ .details("Failed to download logo")
+ .reason(err)
+ })?
+ .to_vec();
+
+ logo = Resource::new(content_type, contents).into();
+ }
+
+ self.security
+ .logos
+ .lock()
+ .insert(domain.to_string(), logo.clone());
+
+ Ok(logo)
+ }
+ } else {
+ Ok(None)
+ }
+ }
+
+ fn default_logo_url(&self) -> Option<String> {
+ self.enterprise
+ .as_ref()
+ .and_then(|e| e.logo_url.as_ref().map(|l| l.to_string()))
+ }
}
diff --git a/crates/common/src/expr/tokenizer.rs b/crates/common/src/expr/tokenizer.rs
index e3366c51..7e7a6e0a 100644
--- a/crates/common/src/expr/tokenizer.rs
+++ b/crates/common/src/expr/tokenizer.rs
@@ -91,14 +91,14 @@ impl<'x> Tokenizer<'x> {
_ => {
let (prev_token, ch) = if ch == b'(' && self.buf.eq(b"matches") {
// Parse regular expressions
- let stop_ch = self.find_char(&[b'\"', b'\''])?;
+ let stop_ch = self.find_char(b"\"'")?;
let regex_str = self.parse_string(stop_ch)?;
let regex = Regex::new(&regex_str).map_err(|e| {
format!("Invalid regular expression {:?}: {}", regex_str, e)
})?;
self.has_alpha = false;
self.buf.clear();
- self.find_char(&[b','])?;
+ self.find_char(b",")?;
(Token::Regex(regex).into(), b'(')
} else if !self.buf.is_empty() {
self.is_start = false;
diff --git a/crates/common/src/lib.rs b/crates/common/src/lib.rs
index a5c6df41..d602a1ca 100644
--- a/crates/common/src/lib.rs
+++ b/crates/common/src/lib.rs
@@ -10,6 +10,7 @@ use std::{
sync::{atomic::AtomicU8, Arc},
};
+use ahash::AHashMap;
use arc_swap::ArcSwap;
use auth::{roles::RolePermissions, AccessToken};
use config::{
@@ -35,6 +36,8 @@ use listener::{
};
use mail_send::Credentials;
+use manager::webadmin::Resource;
+use parking_lot::Mutex;
use sieve::Sieve;
use store::{
write::{QueueClass, ValueClass},
@@ -58,6 +61,8 @@ pub mod manager;
pub mod scripts;
pub mod telemetry;
+pub use psl;
+
pub static USER_AGENT: &str = concat!("Stalwart/", env!("CARGO_PKG_VERSION"),);
pub static DAEMON_NAME: &str = concat!("Stalwart Mail Server v", env!("CARGO_PKG_VERSION"),);
@@ -83,6 +88,7 @@ pub struct Core {
//TODO: temporary hack until OIDC is implemented
#[derive(Default)]
pub struct Security {
+ pub logos: Mutex<AHashMap<String, Option<Resource<Vec<u8>>>>>,
pub access_tokens: TtlDashMap<u32, Arc<AccessToken>>,
pub permissions: ADashMap<u32, Arc<RolePermissions>>,
pub permissions_version: AtomicU8,
@@ -405,6 +411,7 @@ impl Clone for Security {
self.permissions_version
.load(std::sync::atomic::Ordering::Relaxed),
),
+ logos: Mutex::new(self.logos.lock().clone()),
}
}
}
diff --git a/crates/common/src/listener/acme/order.rs b/crates/common/src/listener/acme/order.rs
index 83e8e7ac..983198ea 100644
--- a/crates/common/src/listener/acme/order.rs
+++ b/crates/common/src/listener/acme/order.rs
@@ -9,7 +9,6 @@ use rustls::sign::CertifiedKey;
use rustls_pki_types::{CertificateDer, PrivateKeyDer, PrivatePkcs8KeyDer};
use std::sync::Arc;
use std::time::{Duration, Instant};
-use utils::suffixlist::DomainPart;
use x509_parser::parse_x509_certificate;
use crate::listener::acme::directory::Identifier;
@@ -241,11 +240,10 @@ impl Core {
let domain = domain.strip_prefix("*.").unwrap_or(&domain);
let name = format!("_acme-challenge.{}", domain);
let origin = origin
- .clone()
- .or_else(|| {
- self.smtp.resolvers.psl.domain_part(domain, DomainPart::Sld)
- })
- .unwrap_or_else(|| domain.to_string());
+ .as_deref()
+ .or_else(|| psl::domain_str(domain))
+ .unwrap_or(domain)
+ .to_string();
// First try deleting the record
if let Err(err) = updater.delete(&name, &origin).await {
diff --git a/crates/common/src/manager/webadmin.rs b/crates/common/src/manager/webadmin.rs
index 3bad6b0e..cf52a266 100644
--- a/crates/common/src/manager/webadmin.rs
+++ b/crates/common/src/manager/webadmin.rs
@@ -5,6 +5,7 @@
*/
use std::{
+ borrow::Cow,
io::{self, Cursor, Read},
path::PathBuf,
};
@@ -22,12 +23,21 @@ pub struct WebAdminManager {
routes: ArcSwap<AHashMap<String, Resource<PathBuf>>>,
}
-#[derive(Default)]
+#[derive(Default, Clone)]
pub struct Resource<T> {
- pub content_type: &'static str,
+ pub content_type: Cow<'static, str>,
pub contents: T,
}
+impl<T> Resource<T> {
+ pub fn new(content_type: impl Into<Cow<'static, str>>, contents: T) -> Self {
+ Self {
+ content_type: content_type.into(),
+ contents,
+ }
+ }
+}
+
impl WebAdminManager {
pub fn new() -> Self {
Self {
@@ -42,7 +52,7 @@ impl WebAdminManager {
tokio::fs::read(&resource.contents)
.await
.map(|contents| Resource {
- content_type: resource.content_type,
+ content_type: resource.content_type.clone(),
contents,
})
.map_err(|err| {
@@ -114,7 +124,8 @@ impl WebAdminManager {
"svg" => "image/svg+xml",
"ico" => "image/x-icon",
_ => "application/octet-stream",
- },
+ }
+ .into(),
contents: path,
};
diff --git a/crates/common/src/scripts/functions/unicode.rs b/crates/common/src/scripts/functions/unicode.rs
index d11d09f8..852e3074 100644
--- a/crates/common/src/scripts/functions/unicode.rs
+++ b/crates/common/src/scripts/functions/unicode.rs
@@ -9,10 +9,10 @@ use unicode_security::MixedScript;
pub fn fn_is_ascii<'x>(_: &'x Context<'x>, v: Vec<Variable>) -> Variable {
match &v[0] {
- Variable::String(s) => s.chars().all(|c| c.is_ascii()),
+ Variable::String(s) => s.is_ascii(),
Variable::Integer(_) | Variable::Float(_) => true,
Variable::Array(a) => a.iter().all(|v| match v {
- Variable::String(s) => s.chars().all(|c| c.is_ascii()),
+ Variable::String(s) => s.is_ascii(),
_ => true,
}),
}
diff --git a/crates/common/src/scripts/plugins/bayes.rs b/crates/common/src/scripts/plugins/bayes.rs
index 67eefeb7..5250cac9 100644
--- a/crates/common/src/scripts/plugins/bayes.rs
+++ b/crates/common/src/scripts/plugins/bayes.rs
@@ -63,10 +63,7 @@ async fn train(ctx: PluginContext<'_>, is_train: bool) -> trc::Result<Variable>
// Train the model
let mut model = BayesModel::default();
model.train(
- OsbTokenizer::new(
- BayesTokenizer::new(text.as_ref(), &ctx.core.smtp.resolvers.psl),
- 5,
- ),
+ OsbTokenizer::new(BayesTokenizer::new(text.as_ref()), 5),
is_spam,
);
if model.weights.is_empty() {
@@ -187,10 +184,7 @@ pub async fn exec_classify(ctx: PluginContext<'_>) -> trc::Result<Variable> {
// Classify the text
let mut tokens = Vec::new();
- for token in OsbTokenizer::<_, TokenHash>::new(
- BayesTokenizer::new(text.as_ref(), &ctx.core.smtp.resolvers.psl),
- 5,
- ) {
+ for token in OsbTokenizer::<_, TokenHash>::new(BayesTokenizer::new(text.as_ref()), 5) {
let weights = bayes_cache.get_or_update(token.inner, store).await?;
tokens.push(OsbToken {
inner: weights,
diff --git a/crates/common/src/scripts/plugins/pyzor.rs b/crates/common/src/scripts/plugins/pyzor.rs
index 5202a0de..f5642418 100644
--- a/crates/common/src/scripts/plugins/pyzor.rs
+++ b/crates/common/src/scripts/plugins/pyzor.rs
@@ -18,7 +18,6 @@ use mail_parser::{decoders::html::add_html_token, Message, PartType};
use nlp::tokenizers::types::{TokenType, TypesTokenizer};
use sha1::{Digest, Sha1};
use tokio::net::UdpSocket;
-use utils::suffixlist::PublicSuffix;
const MIN_LINE_LENGTH: usize = 8;
const ATOMIC_NUM_LINES: usize = 4;
@@ -47,9 +46,7 @@ pub async fn exec(ctx: PluginContext<'_>) -> trc::Result<Variable> {
}
// Hash message
- let request = ctx
- .message
- .pyzor_check_message(&ctx.core.smtp.resolvers.psl);
+ let request = ctx.message.pyzor_check_message();
#[cfg(feature = "test_mode")]
{
@@ -161,15 +158,15 @@ async fn pyzor_send_message(
}
trait PyzorDigest<W: Write> {
- fn pyzor_digest(&self, writer: W, psl: &PublicSuffix) -> W;
+ fn pyzor_digest(&self, writer: W) -> W;
}
pub trait PyzorCheck {
- fn pyzor_check_message(&self, psl: &PublicSuffix) -> String;
+ fn pyzor_check_message(&self) -> String;
}
impl<'x, W: Write> PyzorDigest<W> for Message<'x> {
- fn pyzor_digest(&self, writer: W, psl: &PublicSuffix) -> W {
+ fn pyzor_digest(&self, writer: W) -> W {
let parts = self
.parts
.iter()
@@ -180,33 +177,27 @@ impl<'x, W: Write> PyzorDigest<W> for Message<'x> {
})
.collect::<Vec<Cow<str>>>();
- pyzor_digest(writer, parts.iter().flat_map(|text| text.lines()), psl)
+ pyzor_digest(writer, parts.iter().flat_map(|text| text.lines()))
}
}
impl<'x> PyzorCheck for Message<'x> {
- fn pyzor_check_message(&self, psl: &PublicSuffix) -> String {
+ fn pyzor_check_message(&self) -> String {
let time = SystemTime::now()
.duration_since(SystemTime::UNIX_EPOCH)
.map_or(0, |d| d.as_secs());
pyzor_create_message(
self,
- psl,
time,
(time & 0xFFFF) as u16 ^ ((time >> 16) & 0xFFFF) as u16,
)
}
}
-fn pyzor_create_message(
- message: &Message<'_>,
- psl: &PublicSuffix,
- time: u64,
- thread: u16,
-) -> String {
+fn pyzor_create_message(message: &Message<'_>, time: u64, thread: u16) -> String {
// Hash message
- let hash = message.pyzor_digest(Sha1::new(), psl).finalize();
+ let hash = message.pyzor_digest(Sha1::new()).finalize();
// Hash key
let mut hash_key = Sha1::new();
hash_key.update("anonymous:".as_bytes());
@@ -223,13 +214,13 @@ fn pyzor_create_message(
// Sign
let mut sig = Sha1::new();
sig.update(msg_hash);
- sig.update(&format!(":{time}:{hash_key:x}"));
+ sig.update(format!(":{time}:{hash_key:x}"));
let sig = sig.finalize();
format!("{message}\nSig: {sig:x}\n")
}
-fn pyzor_digest<'x, I, W>(mut writer: W, lines: I, psl: &PublicSuffix) -> W
+fn pyzor_digest<'x, I, W>(mut writer: W, lines: I) -> W
where
I: Iterator<Item = &'x str>,
W: Write,
@@ -254,7 +245,7 @@ where
}
};
- for token in TypesTokenizer::new(line, psl) {
+ for token in TypesTokenizer::new(line) {
match token.word {
TokenType::Alphabetic(_)
| TokenType::Alphanumeric(_)
@@ -448,7 +439,6 @@ mod test {
use mail_parser::MessageParser;
use sha1::Digest;
use sha1::Sha1;
- use utils::suffixlist::PublicSuffix;
use super::pyzor_create_message;
use super::pyzor_send_message;
@@ -485,11 +475,8 @@ mod test {
#[test]
fn message_pyzor() {
- let mut psl = PublicSuffix::default();
- psl.suffixes.insert("com".to_string());
let message = pyzor_create_message(
&MessageParser::new().parse(HTML_TEXT_STYLE_SCRIPT).unwrap(),
- &psl,
1697468672,
49005,
);
@@ -510,9 +497,6 @@ mod test {
#[test]
fn digest_pyzor() {
- let mut psl = PublicSuffix::default();
- psl.suffixes.insert("com".to_string());
-
// HTML stripping
assert_eq!(html_to_text(HTML_RAW), HTML_RAW_STRIPED);
@@ -531,7 +515,6 @@ mod test {
String::from_utf8(pyzor_digest(
Vec::new(),
format!("Test {strip_me} Test2").lines(),
- &psl
))
.unwrap(),
"TestTest2"
@@ -543,7 +526,6 @@ mod test {
String::from_utf8(pyzor_digest(
Vec::new(),
concat!("This line is included\n", "not this\n", "This also").lines(),
- &psl
))
.unwrap(),
"ThislineisincludedThisalso"
@@ -554,7 +536,6 @@ mod test {
String::from_utf8(pyzor_digest(
Vec::new(),
"All this message\nShould be included\nIn the digest".lines(),
- &psl
))
.unwrap(),
"AllthismessageShouldbeincludedInthedigest"
@@ -570,7 +551,7 @@ mod test {
expected += format!("Line{i}testtesttest").as_str();
}
assert_eq!(
- String::from_utf8(pyzor_digest(Vec::new(), text.lines(), &psl)).unwrap(),
+ String::from_utf8(pyzor_digest(Vec::new(), text.lines(),)).unwrap(),
expected
);
@@ -602,7 +583,7 @@ mod test {
MessageParser::new()
.parse(input)
.unwrap()
- .pyzor_digest(Vec::new(), &psl)
+ .pyzor_digest(Vec::new(),)
)
.unwrap(),
expected,
@@ -617,7 +598,7 @@ mod test {
MessageParser::new()
.parse(HTML_TEXT_STYLE_SCRIPT)
.unwrap()
- .pyzor_digest(Sha1::new(), &psl)
+ .pyzor_digest(Sha1::new(),)
.finalize()
),
"b2c27325a034c581df0c9ef37e4a0d63208a3e7e",
diff --git a/crates/common/src/scripts/plugins/text.rs b/crates/common/src/scripts/plugins/text.rs
index b4ad9761..922c89b6 100644
--- a/crates/common/src/scripts/plugins/text.rs
+++ b/crates/common/src/scripts/plugins/text.rs
@@ -6,7 +6,6 @@
use nlp::tokenizers::types::{TokenType, TypesTokenizer};
use sieve::{runtime::Variable, FunctionMap};
-use utils::suffixlist::DomainPart;
use crate::scripts::functions::{html::html_to_tokens, text::tokenize_words, ApplyString};
@@ -33,7 +32,7 @@ pub fn exec_tokenize(ctx: PluginContext<'_>) -> trc::Result<Variable> {
Ok(match v.remove(0) {
v @ (Variable::String(_) | Variable::Array(_)) => {
- TypesTokenizer::new(v.to_string().as_ref(), &ctx.core.smtp.resolvers.psl)
+ TypesTokenizer::new(v.to_string().as_ref())
.tokenize_numbers(false)
.tokenize_urls(urls)
.tokenize_urls_without_scheme(urls_without_scheme)
@@ -53,6 +52,12 @@ pub fn exec_tokenize(ctx: PluginContext<'_>) -> trc::Result<Variable> {
})
}
+enum DomainPart {
+ Sld,
+ Tld,
+ Host,
+}
+
pub fn exec_domain_part(ctx: PluginContext<'_>) -> trc::Result<Variable> {
let v = ctx.arguments;
let part = match v[1].to_string().as_ref() {
@@ -63,12 +68,12 @@ pub fn exec_domain_part(ctx: PluginContext<'_>) -> trc::Result<Variable> {
};
Ok(v[0].transform(|domain| {
- ctx.core
- .smtp
- .resolvers
- .psl
- .domain_part(domain, part)
- .map(Variable::from)
- .unwrap_or_default()
+ match part {
+ DomainPart::Sld => psl::domain_str(domain),
+ DomainPart::Tld => domain.rsplit_once('.').map(|(_, tld)| tld),
+ DomainPart::Host => domain.split_once('.').map(|(host, _)| host),
+ }
+ .map(Variable::from)
+ .unwrap_or_default()
}))
}
diff --git a/crates/directory/Cargo.toml b/crates/directory/Cargo.toml
index ac23f369..222114ee 100644
--- a/crates/directory/Cargo.toml
+++ b/crates/directory/Cargo.toml
@@ -1,6 +1,6 @@
[package]
name = "directory"
-version = "0.9.4"
+version = "0.10.0"
edition = "2021"
resolver = "2"
diff --git a/crates/directory/src/backend/internal/manage.rs b/crates/directory/src/backend/internal/manage.rs
index 89d4c0f8..abc97fa9 100644
--- a/crates/directory/src/backend/internal/manage.rs
+++ b/crates/directory/src/backend/internal/manage.rs
@@ -405,7 +405,7 @@ impl ManageDirectory for Store {
principal_id: MaybeDynamicId::Static(member.id),
member_of: MaybeDynamicId::Dynamic(0),
}),
- vec![member.typ as u8],
+ vec![principal.typ as u8],
);
batch.set(
ValueClass::Directory(DirectoryClass::Members {
@@ -460,7 +460,15 @@ impl ManageDirectory for Store {
.list_principals(
None,
principal.id().into(),
- &[],
+ &[
+ Type::Individual,
+ Type::Group,
+ Type::Role,
+ Type::List,
+ Type::Resource,
+ Type::Other,
+ Type::Location,
+ ],
&[PrincipalField::Name],
0,
0,
@@ -742,25 +750,25 @@ impl ManageDirectory for Store {
));
}
- match principal.inner.tenant() {
- Some(old_tenant_id) if old_tenant_id != tenant_info.id => {
- // Update quota
- if let Some(used_quota) = used_quota {
- batch
- .add(DirectoryClass::UsedQuota(old_tenant_id), -used_quota)
- .add(DirectoryClass::UsedQuota(tenant_info.id), used_quota);
- }
+ if principal.inner.tenant() == Some(tenant_info.id) {
+ continue;
+ }
- principal.inner.set(PrincipalField::Tenant, tenant_info.id);
- pinfo_name = PrincipalInfo::new(
- principal_id,
- principal.inner.typ,
- tenant_info.id.into(),
- )
- .serialize();
+ // Update quota
+ if let Some(used_quota) = used_quota {
+ if let Some(old_tenant_id) = principal.inner.tenant() {
+ batch.add(DirectoryClass::UsedQuota(old_tenant_id), -used_quota);
}
- _ => continue,
+ batch.add(DirectoryClass::UsedQuota(tenant_info.id), used_quota);
}
+
+ principal.inner.set(PrincipalField::Tenant, tenant_info.id);
+ pinfo_name = PrincipalInfo::new(
+ principal_id,
+ principal.inner.typ,
+ tenant_info.id.into(),
+ )
+ .serialize();
} else if let Some(tenant_id) = principal.inner.tenant() {
// Update quota
if let Some(used_quota) = used_quota {
@@ -826,15 +834,13 @@ impl ManageDirectory for Store {
}
(
PrincipalAction::Set,
- PrincipalField::Description,
- PrincipalValue::String(description),
+ PrincipalField::Description | PrincipalField::Picture,
+ PrincipalValue::String(value),
) => {
- if !description.is_empty() {
- principal
- .inner
- .set(PrincipalField::Description, description);
+ if !value.is_empty() {
+ principal.inner.set(change.field, value);
} else {
- principal.inner.remove(PrincipalField::Description);
+ principal.inner.remove(change.field);
}
}
(PrincipalAction::Set, PrincipalField::Quota, PrincipalValue::Integer(quota))
@@ -1118,7 +1124,7 @@ impl ManageDirectory for Store {
principal_id: MaybeDynamicId::Static(member_info.id),
member_of: MaybeDynamicId::Static(principal_id),
}),
- vec![member_info.typ as u8],
+ vec![principal.inner.typ as u8],
);
batch.set(
ValueClass::Directory(DirectoryClass::Members {
@@ -1180,7 +1186,7 @@ impl ManageDirectory for Store {
principal_id: MaybeDynamicId::Static(member_info.id),
member_of: MaybeDynamicId::Static(principal_id),
}),
- vec![member_info.typ as u8],
+ vec![principal.inner.typ as u8],
);
batch.set(
ValueClass::Directory(DirectoryClass::Members {
@@ -1561,25 +1567,24 @@ impl ManageDirectory for Store {
let to_key = ValueKey::from(ValueClass::Directory(DirectoryClass::EmailToId(
vec![u8::MAX; 10],
)));
- let mut results = Vec::new();
let domain_name = principal.name();
+ let mut total: u64 = 0;
self.iterate(
IterateParams::new(from_key, to_key).no_values(),
|key, _| {
- let email = std::str::from_utf8(key.get(1..).unwrap_or_default())
- .unwrap_or_default();
- if email
+ if std::str::from_utf8(key.get(1..).unwrap_or_default())
+ .unwrap_or_default()
.rsplit_once('@')
.map_or(false, |(_, domain)| domain == domain_name)
{
- results.push(email.to_string());
+ total += 1;
}
Ok(true)
},
)
.await
.caused_by(trc::location!())?;
- principal.set(PrincipalField::Members, results);
+ principal.set(PrincipalField::Members, total);
}
Type::Tenant => {
let from_key =
@@ -1587,25 +1592,23 @@ impl ManageDirectory for Store {
let to_key = ValueKey::from(ValueClass::Directory(DirectoryClass::NameToId(
vec![u8::MAX; 10],
)));
- let mut results = Vec::new();
- self.iterate(IterateParams::new(from_key, to_key), |key, value| {
+ let mut total: u64 = 0;
+
+ self.iterate(IterateParams::new(from_key, to_key), |_, value| {
let pinfo =
PrincipalInfo::deserialize(value).caused_by(trc::location!())?;
if pinfo.typ == Type::Individual
&& pinfo.has_tenant_access(Some(principal.id))
{
- results.push(
- std::str::from_utf8(key.get(1..).unwrap_or_default())
- .unwrap_or_default()
- .to_string(),
- );
+ total += 1;
}
Ok(true)
})
.await
.caused_by(trc::location!())?;
- principal.set(PrincipalField::Members, results);
+
+ principal.set(PrincipalField::Members, total);
}
_ => {}
}
diff --git a/crates/directory/src/backend/internal/mod.rs b/crates/directory/src/backend/internal/mod.rs
index b8ad3b7b..cc59fcb9 100644
--- a/crates/directory/src/backend/internal/mod.rs
+++ b/crates/directory/src/backend/internal/mod.rs
@@ -95,6 +95,7 @@ impl PrincipalInfo {
pub fn has_tenant_access(&self, tenant_id: Option<u32>) -> bool {
tenant_id.map_or(true, |tenant_id| {
self.tenant.map_or(false, |t| tenant_id == t)
+ || (self.typ == Type::Tenant && self.id == tenant_id)
})
}
}
diff --git a/crates/imap/Cargo.toml b/crates/imap/Cargo.toml
index a408942b..a77cb2fb 100644
--- a/crates/imap/Cargo.toml
+++ b/crates/imap/Cargo.toml
@@ -1,6 +1,6 @@
[package]
name = "imap"
-version = "0.9.4"
+version = "0.10.0"
edition = "2021"
resolver = "2"
diff --git a/crates/jmap/Cargo.toml b/crates/jmap/Cargo.toml
index e796e10a..5fcd188b 100644
--- a/crates/jmap/Cargo.toml
+++ b/crates/jmap/Cargo.toml
@@ -1,6 +1,6 @@
[package]
name = "jmap"
-version = "0.9.4"
+version = "0.10.0"
edition = "2021"
resolver = "2"
@@ -17,7 +17,7 @@ smtp-proto = { version = "0.1" }
mail-parser = { version = "0.9", features = ["full_encoding", "serde_support", "ludicrous_mode"] }
mail-builder = { version = "0.3", features = ["ludicrous_mode"] }
mail-send = { version = "0.4", default-features = false, features = ["cram-md5", "ring", "tls12"] }
-mail-auth = { version = "0.4", features = ["generate"] }
+mail-auth = { version = "0.5", features = ["generate"] }
sieve-rs = { version = "0.5" }
serde = { version = "1.0", features = ["derive"]}
serde_json = "1.0"
diff --git a/crates/jmap/src/api/autoconfig.rs b/crates/jmap/src/api/autoconfig.rs
index 11d29547..296dd5aa 100644
--- a/crates/jmap/src/api/autoconfig.rs
+++ b/crates/jmap/src/api/autoconfig.rs
@@ -67,11 +67,10 @@ impl JMAP {
);
config.push_str("</clientConfig>\n");
- Ok(Resource {
- content_type: "application/xml; charset=utf-8",
- contents: config.into_bytes(),
- }
- .into_http_response())
+ Ok(
+ Resource::new("application/xml; charset=utf-8", config.into_bytes())
+ .into_http_response(),
+ )
}
pub async fn handle_autodiscover_request(
@@ -147,11 +146,10 @@ impl JMAP {
let _ = writeln!(&mut config, "\t</Response>");
let _ = writeln!(&mut config, "</Autodiscover>");
- Ok(Resource {
- content_type: "application/xml; charset=utf-8",
- contents: config.into_bytes(),
- }
- .into_http_response())
+ Ok(
+ Resource::new("application/xml; charset=utf-8", config.into_bytes())
+ .into_http_response(),
+ )
}
async fn autoconfig_parameters<'x>(
diff --git a/crates/jmap/src/api/http.rs b/crates/jmap/src/api/http.rs
index 16ad3c39..86943ffe 100644
--- a/crates/jmap/src/api/http.rs
+++ b/crates/jmap/src/api/http.rs
@@ -223,22 +223,16 @@ impl JMAP {
.key_get::<String>(format!("acme:{token}").into_bytes())
.await?
{
- Some(proof) => Ok(Resource {
- content_type: "text/plain",
- contents: proof.into_bytes(),
- }
- .into_http_response()),
+ Some(proof) => Ok(Resource::new("text/plain", proof.into_bytes())
+ .into_http_response()),
None => Err(trc::ResourceEvent::NotFound.into_err()),
};
}
}
("mta-sts.txt", &Method::GET) => {
if let Some(policy) = self.core.build_mta_sts_policy() {
- return Ok(Resource {
- content_type: "text/plain",
- contents: policy.to_string().into_bytes(),
- }
- .into_http_response());
+ return Ok(Resource::new("text/plain", policy.to_string().into_bytes())
+ .into_http_response());
} else {
return Err(trc::ResourceEvent::NotFound.into_err());
}
@@ -357,11 +351,10 @@ impl JMAP {
}
}
"robots.txt" => {
- return Ok(Resource {
- content_type: "text/plain",
- contents: b"User-agent: *\nDisallow: /\n".to_vec(),
- }
- .into_http_response());
+ return Ok(
+ Resource::new("text/plain", b"User-agent: *\nDisallow: /\n".to_vec())
+ .into_http_response(),
+ );
}
"healthz" => match path.next().unwrap_or_default() {
"live" => {
@@ -394,10 +387,10 @@ impl JMAP {
}
}
- return Ok(Resource {
- content_type: "text/plain; version=0.0.4",
- contents: self.core.export_prometheus_metrics().await?.into_bytes(),
- }
+ return Ok(Resource::new(
+ "text/plain; version=0.0.4",
+ self.core.export_prometheus_metrics().await?.into_bytes(),
+ )
.into_http_response());
}
}
@@ -406,6 +399,42 @@ impl JMAP {
}
_ => (),
},
+ #[cfg(feature = "enterprise")]
+ "logo.svg" if self.core.is_enterprise_edition() => {
+ // SPDX-SnippetBegin
+ // SPDX-FileCopyrightText: 2020 Stalwart Labs Ltd <hello@stalw.art>
+ // SPDX-License-Identifier: LicenseRef-SEL
+
+ match self
+ .core
+ .logo_resource(
+ req.headers()
+ .get(header::HOST)
+ .and_then(|h| h.to_str().ok())
+ .map(|h| h.rsplit_once(':').map_or(h, |(h, _)| h))
+ .unwrap_or_default(),
+ )
+ .await
+ {
+ Ok(Some(resource)) => {
+ return Ok(resource.into_http_response());
+ }
+ Ok(None) => (),
+ Err(err) => {
+ trc::error!(err.span_id(session.session_id));
+ }
+ }
+
+ let resource = self.inner.webadmin.get("logo.svg").await?;
+
+ return if !resource.is_empty() {
+ Ok(resource.into_http_response())
+ } else {
+ Err(trc::ResourceEvent::NotFound.into_err())
+ };
+
+ // SPDX-SnippetEnd
+ }
_ => {
let path = req.uri().path();
let resource = self
@@ -895,7 +924,7 @@ impl ToRequestError for trc::Error {
},
trc::EventType::Auth(cause) => match cause {
trc::AuthEvent::MissingTotp => {
- RequestError::blank(403, "TOTP code required", cause.message())
+ RequestError::blank(402, "TOTP code required", cause.message())
}
trc::AuthEvent::TooManyAttempts => RequestError::too_many_auth_attempts(),
_ => RequestError::unauthorized(),
diff --git a/crates/jmap/src/api/management/dns.rs b/crates/jmap/src/api/management/dns.rs
index d2577007..65e56bb1 100644
--- a/crates/jmap/src/api/management/dns.rs
+++ b/crates/jmap/src/api/management/dns.rs
@@ -115,10 +115,10 @@ impl JMAP {
}
for signature_id in signature_ids {
if let (Some(algo), Some(pk), Some(selector)) = (
- keys.value(&format!("{signature_id}.algorithm"))
+ keys.value(format!("{signature_id}.algorithm"))
.and_then(|algo| algo.parse::<Algorithm>().ok()),
- keys.value(&format!("{signature_id}.private-key")),
- keys.value(&format!("{signature_id}.selector")),
+ keys.value(format!("{signature_id}.private-key")),
+ keys.value(format!("{signature_id}.selector")),
) {
match obtain_dkim_public_key(algo, pk) {
Ok(public) => {
diff --git a/crates/jmap/src/api/management/principal.rs b/crates/jmap/src/api/management/principal.rs
index f3944a81..f661e970 100644
--- a/crates/jmap/src/api/management/principal.rs
+++ b/crates/jmap/src/api/management/principal.rs
@@ -108,6 +108,7 @@ impl JMAP {
let filter = params.get("filter");
let page: usize = params.parse("page").unwrap_or(0);
let limit: usize = params.parse("limit").unwrap_or(0);
+ let count = params.get("count").is_some();
// Parse types
let mut types = Vec::new();
@@ -181,13 +182,17 @@ impl JMAP {
return Err(manage::enterprise());
}
- let principals = self
+ let mut principals = self
.core
.storage
.data
.list_principals(filter, tenant, &types, &fields, page, limit)
.await?;
+ if count {
+ principals.items.clear();
+ }
+
Ok(JsonResponse::new(json!({
"data": principals,
}))
@@ -356,9 +361,6 @@ impl JMAP {
} else {
expire_token = true;
}
- if change.field == PrincipalField::Roles {
- needs_assert = true;
- }
}
}
}
diff --git a/crates/jmap/src/api/management/stores.rs b/crates/jmap/src/api/management/stores.rs
index f4c95ada..78148a93 100644
--- a/crates/jmap/src/api/management/stores.rs
+++ b/crates/jmap/src/api/management/stores.rs
@@ -69,11 +69,7 @@ impl JMAP {
.to_vec()
};
- Ok(Resource {
- content_type: "application/octet-stream",
- contents,
- }
- .into_http_response())
+ Ok(Resource::new("application/octet-stream", contents).into_http_response())
}
(Some("purge"), Some("blob"), _, &Method::GET) => {
// Validate the access token
diff --git a/crates/jmap/src/vacation/set.rs b/crates/jmap/src/vacation/set.rs
index b0143c16..604e24f0 100644
--- a/crates/jmap/src/vacation/set.rs
+++ b/crates/jmap/src/vacation/set.rs
@@ -200,10 +200,9 @@ impl JMAP {
Property::Value,
)
.await?
- .map(|value| {
+ .inspect(|value| {
was_active = value.inner.properties.get(&Property::IsActive)
== Some(&Value::Bool(true));
- value
})
.ok_or_else(|| {
trc::StoreEvent::NotFound
diff --git a/crates/main/Cargo.toml b/crates/main/Cargo.toml
index d9f93111..a2473ec9 100644
--- a/crates/main/Cargo.toml
+++ b/crates/main/Cargo.toml
@@ -7,7 +7,7 @@ homepage = "https://stalw.art"
keywords = ["imap", "jmap", "smtp", "email", "mail", "server"]
categories = ["email"]
license = "AGPL-3.0-only OR LicenseRef-SEL"
-version = "0.9.4"
+version = "0.10.0"
edition = "2021"
resolver = "2"
diff --git a/crates/managesieve/Cargo.toml b/crates/managesieve/Cargo.toml
index 5795fa85..88100fc8 100644
--- a/crates/managesieve/Cargo.toml
+++ b/crates/managesieve/Cargo.toml
@@ -1,6 +1,6 @@
[package]
name = "managesieve"
-version = "0.9.4"
+version = "0.10.0"
edition = "2021"
resolver = "2"
diff --git a/crates/nlp/Cargo.toml b/crates/nlp/Cargo.toml
index 5541f535..07a39939 100644
--- a/crates/nlp/Cargo.toml
+++ b/crates/nlp/Cargo.toml
@@ -1,11 +1,10 @@
[package]
name = "nlp"
-version = "0.9.4"
+version = "0.10.0"
edition = "2021"
resolver = "2"
[dependencies]
-utils = { path = "../utils" }
xxhash-rust = { version = "0.8.5", features = ["xxh3"] }
farmhash = "1.1.5"
siphasher = "1.0"
@@ -20,6 +19,7 @@ jieba-rs = "0.7" # Chinese stemmer
phf = { version = "0.11", features = ["macros"] }
lru-cache = "0.1.2"
parking_lot = "0.12.1"
+psl = "2"
[features]
test_mode = []
diff --git a/crates/nlp/src/bayes/tokenize.rs b/crates/nlp/src/bayes/tokenize.rs
index 2f3ae323..fd59ac90 100644
--- a/crates/nlp/src/bayes/tokenize.rs
+++ b/crates/nlp/src/bayes/tokenize.rs
@@ -6,8 +6,6 @@
use std::borrow::Cow;
-use utils::suffixlist::PublicSuffix;
-
use crate::{
language::{
detect::{LanguageDetector, MIN_LANGUAGE_SCORE},
@@ -21,9 +19,9 @@ use crate::{
},
};
-pub struct BayesTokenizer<'x, 'y> {
+pub struct BayesTokenizer<'x> {
text: &'x str,
- tokenizer: TypesTokenizer<'x, 'y>,
+ tokenizer: TypesTokenizer<'x>,
stemmer: Stemmer,
stop_words: Option<&'static phf::Set<&'static str>>,
tokens: Vec<Cow<'x, str>>,
@@ -36,8 +34,8 @@ enum Stemmer {
None,
}
-impl<'x, 'y> BayesTokenizer<'x, 'y> {
- pub fn new(text: &'x str, suffixes: &'y PublicSuffix) -> Self {
+impl<'x> BayesTokenizer<'x> {
+ pub fn new(text: &'x str) -> Self {
// Detect language
let (mut language, score) =
LanguageDetector::detect_single(text).unwrap_or((Language::English, 1.0));
@@ -47,7 +45,7 @@ impl<'x, 'y> BayesTokenizer<'x, 'y> {
Self {
text,
- tokenizer: TypesTokenizer::new(text, suffixes),
+ tokenizer: TypesTokenizer::new(text),
stemmer: match language {
Language::Mandarin => Stemmer::Mandarin,
Language::Japanese => Stemmer::Japanese,
@@ -61,7 +59,7 @@ impl<'x, 'y> BayesTokenizer<'x, 'y> {
}
}
-impl<'x, 'y> Iterator for BayesTokenizer<'x, 'y> {
+impl<'x> Iterator for BayesTokenizer<'x> {
type Item = Cow<'x, str>;
fn next(&mut self) -> Option<Self::Item> {
@@ -1149,8 +1147,6 @@ pub static SYMBOLS: phf::Set<char> = phf::phf_set! {
mod tests {
use std::borrow::Cow;
- use utils::suffixlist::PublicSuffix;
-
use crate::bayes::tokenize::BayesTokenizer;
#[test]
@@ -1236,10 +1232,8 @@ mod tests {
("시작이 반이다", vec!["시작이", "반이다"]),
];
- let suffixes = PublicSuffix::default();
-
for (input, expect) in inputs.iter() {
- let input = BayesTokenizer::new(input, &suffixes).collect::<Vec<_>>();
+ let input = BayesTokenizer::new(input).collect::<Vec<_>>();
let expect = expect.iter().copied().map(Cow::from).collect::<Vec<_>>();
assert_eq!(input, expect,);
diff --git a/crates/nlp/src/lib.rs b/crates/nlp/src/lib.rs
index a5c174e8..68a40005 100644
--- a/crates/nlp/src/lib.rs
+++ b/crates/nlp/src/lib.rs
@@ -6,8 +6,6 @@ pub mod tokenizers;
mod test {
use std::fs;
- use utils::suffixlist::PublicSuffix;
-
use crate::{
bayes::{tokenize::BayesTokenizer, BayesClassifier, BayesModel},
tokenizers::osb::{OsbToken, OsbTokenizer},
@@ -19,16 +17,12 @@ mod test {
let db =
fs::read_to_string("/Users/me/code/mail-server/_ignore/spam_or_not_spam.csv").unwrap();
let mut bayes = BayesModel::default();
- let suffixes = PublicSuffix::default();
for line in db.lines() {
let (text, is_spam) = line.rsplit_once(',').unwrap();
let is_spam = is_spam == "1";
- bayes.train(
- OsbTokenizer::new(BayesTokenizer::new(text, &suffixes), 5),
- is_spam,
- );
+ bayes.train(OsbTokenizer::new(BayesTokenizer::new(text), 5), is_spam);
}
println!("Ham: {} Spam: {}", bayes.ham_learns, bayes.spam_learns,);
fs::write(
@@ -46,7 +40,6 @@ mod test {
)
.unwrap();
let bayes = BayesClassifier::new();
- let suffixes = PublicSuffix::default();
for text in [
"i am attaching to this email a presentation to integrate the spreadsheet into our server",
@@ -58,7 +51,7 @@ mod test {
"{:?} -> {}",
text,
bayes
- .classify(OsbTokenizer::new(BayesTokenizer::new(text, &suffixes), 5).filter_map(|x| model.weights.get(&x.inner).map(|w| {
+ .classify(OsbTokenizer::new(BayesTokenizer::new(text), 5).filter_map(|x| model.weights.get(&x.inner).map(|w| {
OsbToken {
idx: x.idx,
inner: *w,
diff --git a/crates/nlp/src/tokenizers/mod.rs b/crates/nlp/src/tokenizers/mod.rs
index bbd5f050..eb449b4c 100644
--- a/crates/nlp/src/tokenizers/mod.rs
+++ b/crates/nlp/src/tokenizers/mod.rs
@@ -37,7 +37,7 @@ impl<'x> InnerToken<'x> for Cow<'x, str> {
}
fn is_alphabetic_8bit(&self) -> bool {
- !self.chars().all(|c| c.is_ascii())
+ !self.is_ascii()
}
fn unwrap_alphabetic(self) -> Cow<'x, str> {
diff --git a/crates/nlp/src/tokenizers/types.rs b/crates/nlp/src/tokenizers/types.rs
index f69fe0ab..cb12035e 100644
--- a/crates/nlp/src/tokenizers/types.rs
+++ b/crates/nlp/src/tokenizers/types.rs
@@ -6,13 +6,10 @@
use std::str::CharIndices;
-use utils::suffixlist::PublicSuffix;
-
use super::Token;
-pub struct TypesTokenizer<'x, 'y> {
+pub struct TypesTokenizer<'x> {
text: &'x str,
- suffixes: &'y PublicSuffix,
iter: CharIndices<'x>,
tokens: Vec<Token<TokenType<&'x str>>>,
peek_pos: usize,
@@ -45,7 +42,7 @@ pub enum TokenType<T> {
impl Copy for Token<TokenType<&'_ str>> {}
-impl<'x, 'y> Iterator for TypesTokenizer<'x, 'y> {
+impl<'x> Iterator for TypesTokenizer<'x> {
type Item = Token<TokenType<&'x str>>;
fn next(&mut self) -> Option<Self::Item> {
@@ -58,7 +55,7 @@ impl<'x, 'y> Iterator for TypesTokenizer<'x, 'y> {
&& matches!(
token.word,
TokenType::Alphabetic(t) | TokenType::Alphanumeric(t)
- if t.len() <= 8 && t.chars().all(|c| c.is_ascii()))
+ if t.len() <= 8 && t.is_ascii())
&& self.try_skip_url_scheme()
{
if let Some(url) = self.try_parse_url(token.into()) {
@@ -111,15 +108,14 @@ impl<'x, 'y> Iterator for TypesTokenizer<'x, 'y> {
}
}
-impl<'x, 'y> TypesTokenizer<'x, 'y> {
- pub fn new(text: &'x str, suffixes: &'y PublicSuffix) -> Self {
+impl<'x> TypesTokenizer<'x> {
+ pub fn new(text: &'x str) -> Self {
Self {
text,
iter: text.char_indices(),
tokens: Vec::new(),
eof: false,
peek_pos: 0,
- suffixes,
last_ch_is_space: false,
last_token_is_dot: false,
tokenize_urls: true,
@@ -327,8 +323,13 @@ impl<'x, 'y> TypesTokenizer<'x, 'y> {
while let Some(token) = self.peek() {
match token.word {
TokenType::Alphabetic(text) | TokenType::Alphanumeric(text) => {
- last_label_is_tld =
- text.len() >= 2 && self.suffixes.contains(&text.to_ascii_lowercase());
+ last_label_is_tld = text.len() >= 2
+ && psl::Psl::find(
+ &psl::List,
+ [text.to_ascii_lowercase().as_bytes()].into_iter(),
+ )
+ .typ
+ .is_some();
text_count += 1;
}
TokenType::Integer(text) => {
@@ -552,8 +553,13 @@ impl<'x, 'y> TypesTokenizer<'x, 'y> {
.map(|(from, to)| (from, to, true));
}
TokenType::Alphabetic(text) | TokenType::Alphanumeric(text) if text.len() <= 63 => {
- last_label_is_tld =
- text.len() >= 2 && self.suffixes.contains(&text.to_ascii_lowercase());
+ last_label_is_tld = text.len() >= 2
+ && psl::Psl::find(
+ &psl::List,
+ [text.to_ascii_lowercase().as_bytes()].into_iter(),
+ )
+ .typ
+ .is_some();
has_alpha = true;
last_ch = 0;
}
@@ -691,7 +697,7 @@ impl<'x, 'y> TypesTokenizer<'x, 'y> {
(TokenType::Punctuation('/'), State::Slash2) => return true,
(TokenType::Punctuation('+'), State::None) => State::PlusAlpha,
(TokenType::Alphabetic(t) | TokenType::Alphanumeric(t), State::PlusAlpha)
- if t.chars().all(|c| c.is_ascii()) =>
+ if t.is_ascii() =>
{
State::Colon
}
@@ -748,17 +754,10 @@ impl<T> TokenType<T> {
#[cfg(test)]
mod test {
- use utils::suffixlist::PublicSuffix;
-
use super::{TokenType, TypesTokenizer};
#[test]
fn type_tokenizer() {
- let mut suffixes = PublicSuffix::default();
- suffixes.suffixes.insert("com".to_string());
- suffixes.suffixes.insert("co".to_string());
- suffixes.suffixes.insert("org".to_string());
-
// Credits: test suite from linkify crate
for (text, expected) in [
("", vec![]),
@@ -2862,7 +2861,7 @@ mod test {
],
),
] {
- let result = TypesTokenizer::new(text, &suffixes)
+ let result = TypesTokenizer::new(text)
.map(|t| t.word)
.collect::<Vec<_>>();
diff --git a/crates/pop3/Cargo.toml b/crates/pop3/Cargo.toml
index 36bdfa3d..e9fb62bf 100644
--- a/crates/pop3/Cargo.toml
+++ b/crates/pop3/Cargo.toml
@@ -1,6 +1,6 @@
[package]
name = "pop3"
-version = "0.9.4"
+version = "0.10.0"
edition = "2021"
resolver = "2"
diff --git a/crates/smtp/Cargo.toml b/crates/smtp/Cargo.toml
index 77dfbeec..355a22d2 100644
--- a/crates/smtp/Cargo.toml
+++ b/crates/smtp/Cargo.toml
@@ -7,7 +7,7 @@ homepage = "https://stalw.art/smtp"
keywords = ["smtp", "email", "mail", "server"]
categories = ["email"]
license = "AGPL-3.0-only OR LicenseRef-SEL"
-version = "0.9.4"
+version = "0.10.0"
edition = "2021"
resolver = "2"
@@ -18,7 +18,7 @@ nlp = { path = "../nlp" }
directory = { path = "../directory" }
common = { path = "../common" }
trc = { path = "../trc" }
-mail-auth = { version = "0.4" }
+mail-auth = { version = "0.5" }
mail-send = { version = "0.4", default-features = false, features = ["cram-md5", "ring", "tls12"] }
mail-parser = { version = "0.9", features = ["full_encoding", "ludicrous_mode"] }
mail-builder = { version = "0.3", features = ["ludicrous_mode"] }
diff --git a/crates/smtp/src/inbound/data.rs b/crates/smtp/src/inbound/data.rs
index 4f089bde..822fc734 100644
--- a/crates/smtp/src/inbound/data.rs
+++ b/crates/smtp/src/inbound/data.rs
@@ -14,6 +14,7 @@ use std::{
use common::{
config::smtp::{auth::VerifyStrategy, session::Stage},
listener::SessionStream,
+ psl,
scripts::ScriptModification,
};
use mail_auth::{
@@ -249,6 +250,7 @@ impl<T: SessionStream> Session<T> {
&self.data.helo_domain
},
spf_output,
+ |domain| psl::domain_str(domain).unwrap_or(domain),
)
.await;
diff --git a/crates/store/Cargo.toml b/crates/store/Cargo.toml
index 1eef9a96..3572e774 100644
--- a/crates/store/Cargo.toml
+++ b/crates/store/Cargo.toml
@@ -1,6 +1,6 @@
[package]
name = "store"
-version = "0.9.4"
+version = "0.10.0"
edition = "2021"
resolver = "2"
diff --git a/crates/store/src/backend/mysql/main.rs b/crates/store/src/backend/mysql/main.rs
index 2d9e7f0a..c28a271b 100644
--- a/crates/store/src/backend/mysql/main.rs
+++ b/crates/store/src/backend/mysql/main.rs
@@ -110,7 +110,7 @@ impl MysqlStore {
SUBSPACE_TELEMETRY_INDEX,
] {
let table = char::from(table);
- conn.query_drop(&format!(
+ conn.query_drop(format!(
"CREATE TABLE IF NOT EXISTS {table} (
k TINYBLOB,
v MEDIUMBLOB NOT NULL,
@@ -121,7 +121,7 @@ impl MysqlStore {
.map_err(into_error)?;
}
- conn.query_drop(&format!(
+ conn.query_drop(format!(
"CREATE TABLE IF NOT EXISTS {} (
k TINYBLOB,
v LONGBLOB NOT NULL,
@@ -139,7 +139,7 @@ impl MysqlStore {
SUBSPACE_BITMAP_TEXT,
] {
let table = char::from(table);
- conn.query_drop(&format!(
+ conn.query_drop(format!(
"CREATE TABLE IF NOT EXISTS {table} (
k BLOB,
PRIMARY KEY (k(400))
@@ -150,7 +150,7 @@ impl MysqlStore {
}
for table in [SUBSPACE_COUNTER, SUBSPACE_QUOTA] {
- conn.query_drop(&format!(
+ conn.query_drop(format!(
"CREATE TABLE IF NOT EXISTS {} (
k TINYBLOB,
v BIGINT NOT NULL DEFAULT 0,
diff --git a/crates/store/src/backend/mysql/read.rs b/crates/store/src/backend/mysql/read.rs
index 24a929f8..0b1f573d 100644
--- a/crates/store/src/backend/mysql/read.rs
+++ b/crates/store/src/backend/mysql/read.rs
@@ -22,7 +22,7 @@ impl MysqlStore {
{
let mut conn = self.conn_pool.get_conn().await.map_err(into_error)?;
let s = conn
- .prep(&format!(
+ .prep(format!(
"SELECT v FROM {} WHERE k = ?",
char::from(key.subspace())
))
@@ -54,7 +54,7 @@ impl MysqlStore {
let mut bm = RoaringBitmap::new();
let s = conn
- .prep(&format!("SELECT k FROM {table} WHERE k >= ? AND k <= ?"))
+ .prep(format!("SELECT k FROM {table} WHERE k >= ? AND k <= ?"))
.await
.map_err(into_error)?;
let mut rows = conn
@@ -147,7 +147,7 @@ impl MysqlStore {
let key = key.serialize(0);
let mut conn = self.conn_pool.get_conn().await.map_err(into_error)?;
let s = conn
- .prep(&format!("SELECT v FROM {table} WHERE k = ?"))
+ .prep(format!("SELECT v FROM {table} WHERE k = ?"))
.await
.map_err(into_error)?;
match conn.exec_first::<i64, _, _>(&s, (key,)).await {
diff --git a/crates/store/src/backend/mysql/write.rs b/crates/store/src/backend/mysql/write.rs
index 7d570ae1..31c6e40f 100644
--- a/crates/store/src/backend/mysql/write.rs
+++ b/crates/store/src/backend/mysql/write.rs
@@ -108,10 +108,10 @@ impl MysqlStore {
let exists = asserted_values.get(&key);
let s = if let Some(exists) = exists {
if *exists {
- trx.prep(&format!("UPDATE {} SET v = :v WHERE k = :k", table))
+ trx.prep(format!("UPDATE {} SET v = :v WHERE k = :k", table))
.await?
} else {
- trx.prep(&format!(
+ trx.prep(format!(
"INSERT INTO {} (k, v) VALUES (:k, :v)",
table
))
@@ -120,7 +120,7 @@ impl MysqlStore {
} else {
trx
.prep(
- &format!("INSERT INTO {} (k, v) VALUES (:k, :v) ON DUPLICATE KEY UPDATE v = VALUES(v)", table),
+ format!("INSERT INTO {} (k, v) VALUES (:k, :v) ON DUPLICATE KEY UPDATE v = VALUES(v)", table),
)
.await?
};
@@ -149,7 +149,7 @@ impl MysqlStore {
ValueOp::AtomicAdd(by) => {
if *by >= 0 {
let s = trx
- .prep(&format!(
+ .prep(format!(
concat!(
"INSERT INTO {} (k, v) VALUES (?, ?) ",
"ON DUPLICATE KEY UPDATE v = v + VALUES(v)"
@@ -160,14 +160,14 @@ impl MysqlStore {
trx.exec_drop(&s, (key, by)).await?;
} else {
let s = trx
- .prep(&format!("UPDATE {table} SET v = v + ? WHERE k = ?"))
+ .prep(format!("UPDATE {table} SET v = v + ? WHERE k = ?"))
.await?;
trx.exec_drop(&s, (by, key)).await?;
}
}
ValueOp::AddAndGet(by) => {
let s = trx
- .prep(&format!(
+ .prep(format!(
concat!(
"INSERT INTO {} (k, v) VALUES (:k, LAST_INSERT_ID(:v)) ",
"ON DUPLICATE KEY UPDATE v = LAST_INSERT_ID(v + :v)"
@@ -190,7 +190,7 @@ impl MysqlStore {
}
ValueOp::Clear => {
let s = trx
- .prep(&format!("DELETE FROM {} WHERE k = ?", table))
+ .prep(format!("DELETE FROM {} WHERE k = ?", table))
.await?;
trx.exec_drop(&s, (key,)).await?;
}
@@ -256,11 +256,11 @@ impl MysqlStore {
if is_document_id {
trx.prep("INSERT INTO b (k) VALUES (?)").await?
} else {
- trx.prep(&format!("INSERT IGNORE INTO {} (k) VALUES (?)", table))
+ trx.prep(format!("INSERT IGNORE INTO {} (k) VALUES (?)", table))
.await?
}
} else {
- trx.prep(&format!("DELETE FROM {} WHERE k = ?", table))
+ trx.prep(format!("DELETE FROM {} WHERE k = ?", table))
.await?
};
@@ -301,7 +301,7 @@ impl MysqlStore {
let table = char::from(class.subspace(collection));
let s = trx
- .prep(&format!("SELECT v FROM {} WHERE k = ? FOR UPDATE", table))
+ .prep(format!("SELECT v FROM {} WHERE k = ? FOR UPDATE", table))
.await?;
let (exists, matches) = trx
.exec_first::<Vec<u8>, _, _>(&s, (&key,))
@@ -324,7 +324,7 @@ impl MysqlStore {
let mut conn = self.conn_pool.get_conn().await.map_err(into_error)?;
for subspace in [SUBSPACE_QUOTA, SUBSPACE_COUNTER] {
let s = conn
- .prep(&format!("DELETE FROM {} WHERE v = 0", char::from(subspace),))
+ .prep(format!("DELETE FROM {} WHERE v = 0", char::from(subspace),))
.await
.map_err(into_error)?;
conn.exec_drop(&s, ()).await.map_err(into_error)?;
@@ -337,7 +337,7 @@ impl MysqlStore {
let mut conn = self.conn_pool.get_conn().await.map_err(into_error)?;
let s = conn
- .prep(&format!(
+ .prep(format!(
"DELETE FROM {} WHERE k >= ? AND k < ?",
char::from(from.subspace()),
))
diff --git a/crates/trc/Cargo.toml b/crates/trc/Cargo.toml
index 5bf0dc45..65e073b1 100644
--- a/crates/trc/Cargo.toml
+++ b/crates/trc/Cargo.toml
@@ -1,12 +1,12 @@
[package]
name = "trc"
-version = "0.9.4"
+version = "0.10.0"
edition = "2021"
resolver = "2"
[dependencies]
event_macro = { path = "./event-macro" }
-mail-auth = { version = "0.4" }
+mail-auth = { version = "0.5" }
mail-parser = { version = "0.9", features = ["full_encoding", "ludicrous_mode"] }
base64 = "0.22.1"
serde = "1.0"
diff --git a/crates/utils/Cargo.toml b/crates/utils/Cargo.toml
index 5344433a..b5ad2d52 100644
--- a/crates/utils/Cargo.toml
+++ b/crates/utils/Cargo.toml
@@ -1,6 +1,6 @@
[package]
name = "utils"
-version = "0.9.4"
+version = "0.10.0"
edition = "2021"
resolver = "2"
@@ -12,7 +12,7 @@ rustls-pki-types = { version = "1" }
tokio = { version = "1.23", features = ["net", "macros"] }
tokio-rustls = { version = "0.26", default-features = false, features = ["ring", "tls12"] }
serde = { version = "1.0", features = ["derive"]}
-mail-auth = { version = "0.4" }
+mail-auth = { version = "0.5" }
smtp-proto = { version = "0.1" }
mail-send = { version = "0.4", default-features = false, features = ["cram-md5", "ring", "tls12"] }
dashmap = "6.0"
diff --git a/crates/utils/src/lib.rs b/crates/utils/src/lib.rs
index 3aef7ba0..4e92531a 100644
--- a/crates/utils/src/lib.rs
+++ b/crates/utils/src/lib.rs
@@ -12,7 +12,6 @@ pub mod glob;
pub mod lru_cache;
pub mod map;
pub mod snowflake;
-pub mod suffixlist;
pub mod url_params;
use rustls::{
diff --git a/tests/Cargo.toml b/tests/Cargo.toml
index 51d2d81b..07a578f5 100644
--- a/tests/Cargo.toml
+++ b/tests/Cargo.toml
@@ -31,7 +31,7 @@ trc = { path = "../crates/trc" }
managesieve = { path = "../crates/managesieve", features = ["test_mode"] }
smtp-proto = { version = "0.1" }
mail-send = { version = "0.4", default-features = false, features = ["cram-md5", "ring", "tls12"] }
-mail-auth = { version = "0.4", features = ["test"] }
+mail-auth = { version = "0.5", features = ["test"] }
sieve-rs = { version = "0.5" }
utils = { path = "../crates/utils", features = ["test_mode"] }
jmap-client = { version = "0.3", features = ["websockets", "debug", "async"] }
diff --git a/tests/src/jmap/enterprise.rs b/tests/src/jmap/enterprise.rs
index fc6fc491..ddf8d809 100644
--- a/tests/src/jmap/enterprise.rs
+++ b/tests/src/jmap/enterprise.rs
@@ -104,6 +104,7 @@ pub async fn test(params: &mut JMAPTest) {
}
.into(),
metrics_alerts: parse_metric_alerts(&mut config),
+ logo_url: None,
}
.into();
config.assert_no_errors();
@@ -160,6 +161,7 @@ impl EnterpriseCore for Core {
trace_store: None,
metrics_store: None,
metrics_alerts: vec![],
+ logo_url: None,
}
.into();
self
diff --git a/tests/src/jmap/permissions.rs b/tests/src/jmap/permissions.rs
index 236fc153..37eb1e64 100644
--- a/tests/src/jmap/permissions.rs
+++ b/tests/src/jmap/permissions.rs
@@ -13,8 +13,6 @@ use directory::{
backend::internal::{PrincipalField, PrincipalUpdate, PrincipalValue},
Permission, Principal, Type,
};
-use hyper::header::TE;
-use rayon::vec;
use utils::BlobHash;
use crate::jmap::assert_is_empty;
@@ -360,12 +358,12 @@ pub async fn test(params: &JMAPTest) {
)
.await
.unwrap()
- .expect_error("notFound");
+ .expect_request_error("Forbidden");
tenant_api
.get::<()>("/api/principal/foobar")
.await
.unwrap()
- .expect_error("notFound");
+ .expect_request_error("Forbidden");
tenant_api
.get::<()>("/api/principal?type=tenant")
.await
diff --git a/tests/src/jmap/webhooks.rs b/tests/src/jmap/webhooks.rs
index 8119bb29..3988cdfe 100644
--- a/tests/src/jmap/webhooks.rs
+++ b/tests/src/jmap/webhooks.rs
@@ -131,10 +131,7 @@ pub fn spawn_mock_webhook_endpoint() -> Arc<MockWebhookEndpoint> {
endpoint.events.lock().extend(request.events);
Ok::<_, hyper::Error>(
- Resource {
- content_type: "application/json",
- contents: "[]".to_string().into_bytes(),
- }
+ Resource::new("application/json", "[]".to_string().into_bytes())
.into_http_response().build(),
)
} else {
diff --git a/tests/src/smtp/inbound/antispam.rs b/tests/src/smtp/inbound/antispam.rs
index c61b33c5..1c3e5eb9 100644
--- a/tests/src/smtp/inbound/antispam.rs
+++ b/tests/src/smtp/inbound/antispam.rs
@@ -78,8 +78,8 @@ path = "{PATH}/test_antispam.db"
#url = "redis://127.0.0.1"
[lookup]
-"spam-free" = {"gmail.com", "googlemail.com", "yahoomail.com", "*.freemail.org"}
-"spam-disposable" = {"guerrillamail.com", "*.disposable.org"}
+"spam-free" = {"gmail.com", "googlemail.com", "yahoomail.com", "*freemail.org"}
+"spam-disposable" = {"guerrillamail.com", "*disposable.org"}
"spam-redirect" = {"bit.ly", "redirect.io", "redirect.me", "redirect.org", "redirect.com", "redirect.net", "t.ly", "tinyurl.com"}
"spam-dmarc" = {"dmarc-allow.org"}
"spam-spdk" = {"spf-dkim-allow.org"}
diff --git a/tests/src/smtp/inbound/milter.rs b/tests/src/smtp/inbound/milter.rs
index 1be5f193..ccbac2f5 100644
--- a/tests/src/smtp/inbound/milter.rs
+++ b/tests/src/smtp/inbound/milter.rs
@@ -809,10 +809,7 @@ pub fn spawn_mock_mta_hook_server() -> watch::Sender<bool> {
let response = handle_mta_hook(request, tests);
Ok::<_, hyper::Error>(
- Resource {
- content_type: "application/json",
- contents: serde_json::to_string(&response).unwrap().into_bytes(),
- }
+ Resource::new("application/json", serde_json::to_string(&response).unwrap().into_bytes())
.into_http_response().build(),
)
}
diff --git a/tests/src/smtp/outbound/dane.rs b/tests/src/smtp/outbound/dane.rs
index 42e4a717..80c5242a 100644
--- a/tests/src/smtp/outbound/dane.rs
+++ b/tests/src/smtp/outbound/dane.rs
@@ -35,7 +35,6 @@ use mail_auth::{
Resolver, MX,
};
use rustls_pki_types::CertificateDer;
-use utils::suffixlist::PublicSuffix;
use crate::smtp::{
inbound::{TestMessage, TestQueueEvent, TestReportingEvent},
@@ -260,7 +259,6 @@ async fn dane_test() {
tlsa: LruCache::with_capacity(10),
mta_sts: LruCache::with_capacity(10),
},
- psl: PublicSuffix::default(),
};
let r = SMTP {
core: core.into(),