mirror of
https://github.com/stalwartlabs/mail-server.git
synced 2025-11-08 12:51:07 +08:00
v0.3.5
This commit is contained in:
parent
c8c32f3198
commit
016a5bde18
13 changed files with 701 additions and 342 deletions
11
CHANGELOG.md
11
CHANGELOG.md
|
|
@ -2,6 +2,17 @@
|
|||
|
||||
All notable changes to this project will be documented in this file. This project adheres to [Semantic Versioning](http://semver.org/).
|
||||
|
||||
## [0.3.5] - 2023-08-18
|
||||
|
||||
## Added
|
||||
- TCP listener option `nodelay`.
|
||||
|
||||
### Changed
|
||||
|
||||
### Fixed
|
||||
- SMTP: Allow disabling `STARTTLS`.
|
||||
- JMAP: Support for `OPTIONS` HTTP method.
|
||||
|
||||
## [0.3.4] - 2023-08-09
|
||||
|
||||
## Added
|
||||
|
|
|
|||
216
Cargo.lock
generated
216
Cargo.lock
generated
|
|
@ -93,9 +93,9 @@ dependencies = [
|
|||
|
||||
[[package]]
|
||||
name = "aho-corasick"
|
||||
version = "1.0.2"
|
||||
version = "1.0.4"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "43f6cb1bf222025340178f382c426f13757b2960e89779dfcb319c32542a5a41"
|
||||
checksum = "6748e8def348ed4d14996fa801f4122cd763fff530258cdc03f64b25f89d3a5a"
|
||||
dependencies = [
|
||||
"memchr",
|
||||
]
|
||||
|
|
@ -179,9 +179,9 @@ dependencies = [
|
|||
|
||||
[[package]]
|
||||
name = "anyhow"
|
||||
version = "1.0.72"
|
||||
version = "1.0.75"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "3b13c32d80ecc7ab747b80c3784bce54ee8a7a0cc4fbda9bf4cda2cf6fe90854"
|
||||
checksum = "a4668cab20f66d8d020e1fbc0ebe47217433c1b6c8f2040faf858554e394ace6"
|
||||
|
||||
[[package]]
|
||||
name = "argon2"
|
||||
|
|
@ -254,7 +254,7 @@ checksum = "0e97ce7de6cf12de5d7226c73f5ba9811622f4db3a5b91b55c53e987e5f91cba"
|
|||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn 2.0.28",
|
||||
"syn 2.0.29",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
|
|
@ -276,18 +276,18 @@ checksum = "16e62a023e7c117e27523144c5d2459f4397fcc3cab0085af8e2224f643a0193"
|
|||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn 2.0.28",
|
||||
"syn 2.0.29",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "async-trait"
|
||||
version = "0.1.72"
|
||||
version = "0.1.73"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "cc6dde6e4ed435a4c1ee4e73592f5ba9da2151af10076cc04858746af9352d09"
|
||||
checksum = "bc00ceb34980c03614e35a3a4e218276a0a824e911d07651cd0d858a51e8c0f0"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn 2.0.28",
|
||||
"syn 2.0.29",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
|
|
@ -498,7 +498,7 @@ dependencies = [
|
|||
"regex",
|
||||
"rustc-hash",
|
||||
"shlex",
|
||||
"syn 2.0.28",
|
||||
"syn 2.0.29",
|
||||
"which",
|
||||
]
|
||||
|
||||
|
|
@ -531,9 +531,9 @@ checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a"
|
|||
|
||||
[[package]]
|
||||
name = "bitflags"
|
||||
version = "2.3.3"
|
||||
version = "2.4.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "630be753d4e58660abd17930c71b647fe46c27ea6b63cc59e1e3851406972e42"
|
||||
checksum = "b4682ae6287fcf752ecaabbfcc7b6f9b72aa33933dc23a554d853aea8eea8635"
|
||||
dependencies = [
|
||||
"serde",
|
||||
]
|
||||
|
|
@ -824,9 +824,9 @@ dependencies = [
|
|||
|
||||
[[package]]
|
||||
name = "clap"
|
||||
version = "4.3.21"
|
||||
version = "4.3.22"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "c27cdf28c0f604ba3f512b0c9a409f8de8513e4816705deb0498b627e7c3a3fd"
|
||||
checksum = "b417ae4361bca3f5de378294fc7472d3c4ed86a5ef9f49e93ae722f432aae8d2"
|
||||
dependencies = [
|
||||
"clap_builder",
|
||||
"clap_derive",
|
||||
|
|
@ -835,9 +835,9 @@ dependencies = [
|
|||
|
||||
[[package]]
|
||||
name = "clap_builder"
|
||||
version = "4.3.21"
|
||||
version = "4.3.22"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "08a9f1ab5e9f01a9b81f202e8562eb9a10de70abf9eaeac1be465c28b75aa4aa"
|
||||
checksum = "9c90dc0f0e42c64bff177ca9d7be6fcc9ddb0f26a6e062174a61c84dd6c644d4"
|
||||
dependencies = [
|
||||
"anstream",
|
||||
"anstyle",
|
||||
|
|
@ -854,7 +854,7 @@ dependencies = [
|
|||
"heck",
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn 2.0.28",
|
||||
"syn 2.0.29",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
|
|
@ -1079,9 +1079,9 @@ dependencies = [
|
|||
|
||||
[[package]]
|
||||
name = "curve25519-dalek"
|
||||
version = "4.0.0-rc.3"
|
||||
version = "4.0.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "436ace70fc06e06f7f689d2624dc4e2f0ea666efb5aa704215f7249ae6e047a7"
|
||||
checksum = "f711ade317dd348950a9910f81c5947e3d8907ebd2b83f76203ff1807e6a2bc2"
|
||||
dependencies = [
|
||||
"cfg-if",
|
||||
"cpufeatures",
|
||||
|
|
@ -1102,7 +1102,7 @@ checksum = "83fdaf97f4804dcebfa5862639bc9ce4121e82140bec2a987ac5140294865b5b"
|
|||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn 2.0.28",
|
||||
"syn 2.0.29",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
|
|
@ -1347,7 +1347,7 @@ checksum = "487585f4d0c6655fe74905e2504d8ad6908e4db67f744eb140876906c2f3175d"
|
|||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn 2.0.28",
|
||||
"syn 2.0.29",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
|
|
@ -1402,9 +1402,9 @@ dependencies = [
|
|||
|
||||
[[package]]
|
||||
name = "ed25519"
|
||||
version = "2.2.1"
|
||||
version = "2.2.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "5fb04eee5d9d907f29e80ee6b0e78f7e2c82342c63e3580d8c4f69d9d5aad963"
|
||||
checksum = "60f6d271ca33075c88028be6f04d502853d63a5ece419d269c15315d4fc1cf1d"
|
||||
dependencies = [
|
||||
"pkcs8",
|
||||
"signature",
|
||||
|
|
@ -1412,9 +1412,9 @@ dependencies = [
|
|||
|
||||
[[package]]
|
||||
name = "ed25519-dalek"
|
||||
version = "2.0.0-rc.3"
|
||||
version = "2.0.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "faa8e9049d5d72bfc12acbc05914731b5322f79b5e2f195e9f2d705fca22ab4c"
|
||||
checksum = "7277392b266383ef8396db7fdeb1e77b6c52fed775f5df15bb24f35b72156980"
|
||||
dependencies = [
|
||||
"curve25519-dalek",
|
||||
"ed25519",
|
||||
|
|
@ -1606,9 +1606,9 @@ checksum = "0ce7134b9999ecaf8bcd65542e436736ef32ddca1b3e06094cb6ec5755203b80"
|
|||
|
||||
[[package]]
|
||||
name = "flate2"
|
||||
version = "1.0.26"
|
||||
version = "1.0.27"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "3b9429470923de8e8cbd4d2dc513535400b4b3fef0319fb5c4e1f520a7bef743"
|
||||
checksum = "c6c98ee8095e9d1dcbf2fcc6d95acccb90d1c81db1e44725c6a984b1dbdfb010"
|
||||
dependencies = [
|
||||
"crc32fast",
|
||||
"libz-sys",
|
||||
|
|
@ -1711,7 +1711,7 @@ checksum = "83c8d52fe8b46ab822b4decdcc0d6d85aeedfc98f0d52ba2bd4aec4a97807516"
|
|||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn 2.0.28",
|
||||
"syn 2.0.29",
|
||||
"try_map",
|
||||
]
|
||||
|
||||
|
|
@ -1797,7 +1797,7 @@ checksum = "89ca545a94061b6365f2c7355b4b32bd20df3ff95f02da9329b34ccc3bd6ee72"
|
|||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn 2.0.28",
|
||||
"syn 2.0.29",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
|
|
@ -1857,7 +1857,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
|
|||
checksum = "0176e0459c2e4a1fe232f984bca6890e681076abb9934f6cea7c326f3fc47818"
|
||||
dependencies = [
|
||||
"libc",
|
||||
"windows-targets 0.48.1",
|
||||
"windows-targets 0.48.4",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
|
|
@ -2073,9 +2073,9 @@ checksum = "d897f394bad6a705d5f4104762e116a75639e470d80901eed05a860a95cb1904"
|
|||
|
||||
[[package]]
|
||||
name = "httpdate"
|
||||
version = "1.0.2"
|
||||
version = "1.0.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "c4a1e36c821dbe04574f602848a19f742f4fb3c98d40449f11bcad18d6b17421"
|
||||
checksum = "df3b46402a9d5adb4c86a0cf463f42e19994e3ee891101b1841f30a545cb49a9"
|
||||
|
||||
[[package]]
|
||||
name = "human-size"
|
||||
|
|
@ -2233,7 +2233,7 @@ dependencies = [
|
|||
|
||||
[[package]]
|
||||
name = "imap"
|
||||
version = "0.3.4"
|
||||
version = "0.3.5"
|
||||
dependencies = [
|
||||
"ahash 0.8.3",
|
||||
"dashmap",
|
||||
|
|
@ -2398,7 +2398,7 @@ dependencies = [
|
|||
|
||||
[[package]]
|
||||
name = "jmap"
|
||||
version = "0.3.4"
|
||||
version = "0.3.5"
|
||||
dependencies = [
|
||||
"aes",
|
||||
"aes-gcm",
|
||||
|
|
@ -2660,9 +2660,9 @@ dependencies = [
|
|||
|
||||
[[package]]
|
||||
name = "log"
|
||||
version = "0.4.19"
|
||||
version = "0.4.20"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "b06a4cde4c0f271a446782e3eff8de789548ce57dbc8eca9292c27f4a42004b4"
|
||||
checksum = "b5e6163cb8c49088c2c36f57875e58ccd8c87c7427f7fbd50ea6710b2f3f2e8f"
|
||||
|
||||
[[package]]
|
||||
name = "lru-cache"
|
||||
|
|
@ -2723,7 +2723,7 @@ dependencies = [
|
|||
[[package]]
|
||||
name = "mail-send"
|
||||
version = "0.4.0"
|
||||
source = "git+https://github.com/stalwartlabs/mail-send#d5ac9b328308fd95709cb8ee1c3ce37716e210ef"
|
||||
source = "git+https://github.com/stalwartlabs/mail-send#ffa60e3f653d0f4057b7c97d103751a80adc4c12"
|
||||
dependencies = [
|
||||
"base64 0.20.0",
|
||||
"gethostname",
|
||||
|
|
@ -2732,12 +2732,12 @@ dependencies = [
|
|||
"smtp-proto",
|
||||
"tokio",
|
||||
"tokio-rustls 0.24.1",
|
||||
"webpki-roots 0.23.1",
|
||||
"webpki-roots 0.25.2",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "mail-server"
|
||||
version = "0.3.4"
|
||||
version = "0.3.5"
|
||||
dependencies = [
|
||||
"directory",
|
||||
"imap",
|
||||
|
|
@ -2999,7 +2999,7 @@ checksum = "9e6a0fd4f737c707bd9086cc16c925f294943eb62eb71499e9fd4cf71f8b9f4e"
|
|||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn 2.0.28",
|
||||
"syn 2.0.29",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
|
|
@ -3102,7 +3102,7 @@ checksum = "a948666b637a0f465e8564c73e89d4dde00d72d4d473cc972f390fc3dcee7d9c"
|
|||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn 2.0.28",
|
||||
"syn 2.0.29",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
|
|
@ -3297,7 +3297,7 @@ dependencies = [
|
|||
"libc",
|
||||
"redox_syscall 0.3.5",
|
||||
"smallvec",
|
||||
"windows-targets 0.48.1",
|
||||
"windows-targets 0.48.4",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
|
|
@ -3475,7 +3475,7 @@ dependencies = [
|
|||
"phf_shared",
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn 2.0.28",
|
||||
"syn 2.0.29",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
|
|
@ -3504,7 +3504,7 @@ checksum = "4359fd9c9171ec6e8c62926d6faaf553a8dc3f64e1507e76da7911b4f6a04405"
|
|||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn 2.0.28",
|
||||
"syn 2.0.29",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
|
|
@ -3593,7 +3593,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
|
|||
checksum = "6c64d9ba0963cdcea2e1b2230fbae2bab30eb25a174be395c41e764bfb65dd62"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"syn 2.0.28",
|
||||
"syn 2.0.29",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
|
|
@ -3734,9 +3734,9 @@ dependencies = [
|
|||
|
||||
[[package]]
|
||||
name = "quote"
|
||||
version = "1.0.32"
|
||||
version = "1.0.33"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "50f3b39ccfb720540debaa0164757101c08ecb8d326b15358ce76a62c7e85965"
|
||||
checksum = "5267fca4496028628a95160fc423a33e8b2e6af8a5302579e322e4b520293cae"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
]
|
||||
|
|
@ -4101,7 +4101,7 @@ version = "0.29.0"
|
|||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "549b9d036d571d42e6e85d1c1425e2ac83491075078ca9a15be021c56b1641f2"
|
||||
dependencies = [
|
||||
"bitflags 2.3.3",
|
||||
"bitflags 2.4.0",
|
||||
"fallible-iterator",
|
||||
"fallible-streaming-iterator",
|
||||
"hashlink",
|
||||
|
|
@ -4193,11 +4193,11 @@ dependencies = [
|
|||
|
||||
[[package]]
|
||||
name = "rustix"
|
||||
version = "0.38.7"
|
||||
version = "0.38.8"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "172891ebdceb05aa0005f533a6cbfca599ddd7d966f6f5d4d9b2e70478e70399"
|
||||
checksum = "19ed4fa021d81c8392ce04db050a3da9a60299050b7ae1cf482d862b54a7218f"
|
||||
dependencies = [
|
||||
"bitflags 2.3.3",
|
||||
"bitflags 2.4.0",
|
||||
"errno",
|
||||
"libc",
|
||||
"linux-raw-sys",
|
||||
|
|
@ -4411,14 +4411,14 @@ checksum = "aafe972d60b0b9bee71a91b92fee2d4fb3c9d7e8f6b179aa99f27203d99a4816"
|
|||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn 2.0.28",
|
||||
"syn 2.0.29",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "serde_json"
|
||||
version = "1.0.104"
|
||||
version = "1.0.105"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "076066c5f1078eac5b722a31827a8832fe108bed65dfa75e233c89f8206e976c"
|
||||
checksum = "693151e1ac27563d6dbcec9dee9fbd5da8539b20fa14ad3752b2e6d363ace360"
|
||||
dependencies = [
|
||||
"itoa",
|
||||
"ryu",
|
||||
|
|
@ -4459,7 +4459,7 @@ checksum = "91d129178576168c589c9ec973feedf7d3126c01ac2bf08795109aa35b69fb8f"
|
|||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn 2.0.28",
|
||||
"syn 2.0.29",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
|
|
@ -4597,7 +4597,7 @@ checksum = "62bb4feee49fdd9f707ef802e22365a35de4b7b299de4763d44bfea899442ff9"
|
|||
|
||||
[[package]]
|
||||
name = "smtp"
|
||||
version = "0.3.4"
|
||||
version = "0.3.5"
|
||||
dependencies = [
|
||||
"ahash 0.8.3",
|
||||
"blake3",
|
||||
|
|
@ -4823,7 +4823,7 @@ checksum = "8ca69bf415b93b60b80dc8fda3cb4ef52b2336614d8da2de5456cc942a110482"
|
|||
dependencies = [
|
||||
"atoi",
|
||||
"base64 0.21.2",
|
||||
"bitflags 2.3.3",
|
||||
"bitflags 2.4.0",
|
||||
"byteorder",
|
||||
"bytes",
|
||||
"crc",
|
||||
|
|
@ -4865,7 +4865,7 @@ checksum = "a0db2df1b8731c3651e204629dd55e52adbae0462fa1bdcbed56a2302c18181e"
|
|||
dependencies = [
|
||||
"atoi",
|
||||
"base64 0.21.2",
|
||||
"bitflags 2.3.3",
|
||||
"bitflags 2.4.0",
|
||||
"byteorder",
|
||||
"crc",
|
||||
"dotenvy",
|
||||
|
|
@ -4920,7 +4920,7 @@ dependencies = [
|
|||
|
||||
[[package]]
|
||||
name = "stalwart-cli"
|
||||
version = "0.3.4"
|
||||
version = "0.3.5"
|
||||
dependencies = [
|
||||
"clap",
|
||||
"console",
|
||||
|
|
@ -4942,7 +4942,7 @@ dependencies = [
|
|||
|
||||
[[package]]
|
||||
name = "stalwart-install"
|
||||
version = "0.3.4"
|
||||
version = "0.3.5"
|
||||
dependencies = [
|
||||
"base64 0.21.2",
|
||||
"clap",
|
||||
|
|
@ -5035,9 +5035,9 @@ dependencies = [
|
|||
|
||||
[[package]]
|
||||
name = "syn"
|
||||
version = "2.0.28"
|
||||
version = "2.0.29"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "04361975b3f5e348b2189d8dc55bc942f278b2d482a6a0365de5bdd62d351567"
|
||||
checksum = "c324c494eba9d92503e6f1ef2e6df781e78f6a7705a0202d9801b198807d518a"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
|
|
@ -5153,22 +5153,22 @@ dependencies = [
|
|||
|
||||
[[package]]
|
||||
name = "thiserror"
|
||||
version = "1.0.44"
|
||||
version = "1.0.47"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "611040a08a0439f8248d1990b111c95baa9c704c805fa1f62104b39655fd7f90"
|
||||
checksum = "97a802ec30afc17eee47b2855fc72e0c4cd62be9b4efe6591edde0ec5bd68d8f"
|
||||
dependencies = [
|
||||
"thiserror-impl",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "thiserror-impl"
|
||||
version = "1.0.44"
|
||||
version = "1.0.47"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "090198534930841fab3a5d1bb637cde49e339654e606195f8d9c76eeb081dc96"
|
||||
checksum = "6bb623b56e39ab7dcd4b1b98bb6c8f8d907ed255b18de254088016b27a8ee19b"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn 2.0.28",
|
||||
"syn 2.0.29",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
|
|
@ -5247,9 +5247,9 @@ checksum = "1f3ccbac311fea05f86f61904b462b55fb3df8837a366dfc601a0161d0532f20"
|
|||
|
||||
[[package]]
|
||||
name = "tokio"
|
||||
version = "1.30.0"
|
||||
version = "1.32.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "2d3ce25f50619af8b0aec2eb23deebe84249e19e2ddd393a6e16e3300a6dadfd"
|
||||
checksum = "17ed6077ed6cd6c74735e21f37eb16dc3935f96878b1fe961074089cc80893f9"
|
||||
dependencies = [
|
||||
"backtrace",
|
||||
"bytes",
|
||||
|
|
@ -5282,7 +5282,7 @@ checksum = "630bdcf245f78637c13ec01ffae6187cca34625e8c63150d424b59e55af2675e"
|
|||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn 2.0.28",
|
||||
"syn 2.0.29",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
|
|
@ -5467,7 +5467,7 @@ checksum = "5f4f31f56159e98206da9efd823404b79b6ef3143b4a7ab76e67b1751b25a4ab"
|
|||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn 2.0.28",
|
||||
"syn 2.0.29",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
|
|
@ -5748,7 +5748,7 @@ checksum = "711b9620af191e0cdc7468a8d14e709c3dcdb115b36f838e601583af800a370a"
|
|||
|
||||
[[package]]
|
||||
name = "utils"
|
||||
version = "0.3.4"
|
||||
version = "0.3.5"
|
||||
dependencies = [
|
||||
"ahash 0.8.3",
|
||||
"dashmap",
|
||||
|
|
@ -5839,7 +5839,7 @@ dependencies = [
|
|||
"once_cell",
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn 2.0.28",
|
||||
"syn 2.0.29",
|
||||
"wasm-bindgen-shared",
|
||||
]
|
||||
|
||||
|
|
@ -5873,7 +5873,7 @@ checksum = "54681b18a46765f095758388f2d0cf16eb8d4169b639ab575a8f5693af210c7b"
|
|||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn 2.0.28",
|
||||
"syn 2.0.29",
|
||||
"wasm-bindgen-backend",
|
||||
"wasm-bindgen-shared",
|
||||
]
|
||||
|
|
@ -5945,10 +5945,16 @@ dependencies = [
|
|||
]
|
||||
|
||||
[[package]]
|
||||
name = "whatlang"
|
||||
version = "0.16.2"
|
||||
name = "webpki-roots"
|
||||
version = "0.25.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "9c531a2dc4c462b833788be2c07eef4e621d0e9edbd55bf280cc164c1c1aa043"
|
||||
checksum = "14247bb57be4f377dfb94c72830b8ce8fc6beac03cf4bf7b9732eadd414123fc"
|
||||
|
||||
[[package]]
|
||||
name = "whatlang"
|
||||
version = "0.16.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "dcdcd0195a5b871e50926da8e881277f36a4621b3220d85092e7b91cc85f6bd9"
|
||||
dependencies = [
|
||||
"hashbrown 0.12.3",
|
||||
"once_cell",
|
||||
|
|
@ -6005,7 +6011,7 @@ version = "0.48.0"
|
|||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "e686886bc078bc1b0b600cac0147aadb815089b6e4da64016cbd754b6342700f"
|
||||
dependencies = [
|
||||
"windows-targets 0.48.1",
|
||||
"windows-targets 0.48.4",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
|
|
@ -6023,7 +6029,7 @@ version = "0.48.0"
|
|||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "677d2418bec65e3338edb076e806bc1ec15693c5d0104683f2efe857f61056a9"
|
||||
dependencies = [
|
||||
"windows-targets 0.48.1",
|
||||
"windows-targets 0.48.4",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
|
|
@ -6043,17 +6049,17 @@ dependencies = [
|
|||
|
||||
[[package]]
|
||||
name = "windows-targets"
|
||||
version = "0.48.1"
|
||||
version = "0.48.4"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "05d4b17490f70499f20b9e791dcf6a299785ce8af4d709018206dc5b4953e95f"
|
||||
checksum = "d92ecb8ae0317859f509f17b19adc74b0763b0fa3b085dea8ed01085c8dac222"
|
||||
dependencies = [
|
||||
"windows_aarch64_gnullvm 0.48.0",
|
||||
"windows_aarch64_msvc 0.48.0",
|
||||
"windows_i686_gnu 0.48.0",
|
||||
"windows_i686_msvc 0.48.0",
|
||||
"windows_x86_64_gnu 0.48.0",
|
||||
"windows_x86_64_gnullvm 0.48.0",
|
||||
"windows_x86_64_msvc 0.48.0",
|
||||
"windows_aarch64_gnullvm 0.48.4",
|
||||
"windows_aarch64_msvc 0.48.4",
|
||||
"windows_i686_gnu 0.48.4",
|
||||
"windows_i686_msvc 0.48.4",
|
||||
"windows_x86_64_gnu 0.48.4",
|
||||
"windows_x86_64_gnullvm 0.48.4",
|
||||
"windows_x86_64_msvc 0.48.4",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
|
|
@ -6064,9 +6070,9 @@ checksum = "597a5118570b68bc08d8d59125332c54f1ba9d9adeedeef5b99b02ba2b0698f8"
|
|||
|
||||
[[package]]
|
||||
name = "windows_aarch64_gnullvm"
|
||||
version = "0.48.0"
|
||||
version = "0.48.4"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "91ae572e1b79dba883e0d315474df7305d12f569b400fcf90581b06062f7e1bc"
|
||||
checksum = "d14b0ee96970be7108701212f097ce67ca772fd84cb0ffbc86d26a94e77ba929"
|
||||
|
||||
[[package]]
|
||||
name = "windows_aarch64_msvc"
|
||||
|
|
@ -6076,9 +6082,9 @@ checksum = "e08e8864a60f06ef0d0ff4ba04124db8b0fb3be5776a5cd47641e942e58c4d43"
|
|||
|
||||
[[package]]
|
||||
name = "windows_aarch64_msvc"
|
||||
version = "0.48.0"
|
||||
version = "0.48.4"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "b2ef27e0d7bdfcfc7b868b317c1d32c641a6fe4629c171b8928c7b08d98d7cf3"
|
||||
checksum = "1332277d49f440c8fc6014941e320ee47ededfcce10cb272728470f56cc092c9"
|
||||
|
||||
[[package]]
|
||||
name = "windows_i686_gnu"
|
||||
|
|
@ -6088,9 +6094,9 @@ checksum = "c61d927d8da41da96a81f029489353e68739737d3beca43145c8afec9a31a84f"
|
|||
|
||||
[[package]]
|
||||
name = "windows_i686_gnu"
|
||||
version = "0.48.0"
|
||||
version = "0.48.4"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "622a1962a7db830d6fd0a69683c80a18fda201879f0f447f065a3b7467daa241"
|
||||
checksum = "d992130ac399d56f02c20564e9975ac5ba08cb25cb832849bbc0d736a101abe5"
|
||||
|
||||
[[package]]
|
||||
name = "windows_i686_msvc"
|
||||
|
|
@ -6100,9 +6106,9 @@ checksum = "44d840b6ec649f480a41c8d80f9c65108b92d89345dd94027bfe06ac444d1060"
|
|||
|
||||
[[package]]
|
||||
name = "windows_i686_msvc"
|
||||
version = "0.48.0"
|
||||
version = "0.48.4"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "4542c6e364ce21bf45d69fdd2a8e455fa38d316158cfd43b3ac1c5b1b19f8e00"
|
||||
checksum = "962e96d0fa4b4773c63977977ea6564f463fb10e34a6e07360428b53ae7a3f71"
|
||||
|
||||
[[package]]
|
||||
name = "windows_x86_64_gnu"
|
||||
|
|
@ -6112,9 +6118,9 @@ checksum = "8de912b8b8feb55c064867cf047dda097f92d51efad5b491dfb98f6bbb70cb36"
|
|||
|
||||
[[package]]
|
||||
name = "windows_x86_64_gnu"
|
||||
version = "0.48.0"
|
||||
version = "0.48.4"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "ca2b8a661f7628cbd23440e50b05d705db3686f894fc9580820623656af974b1"
|
||||
checksum = "30652a53018a48a9735fbc2986ff0446c37bc8bed0d3f98a0ed4d04cdb80027e"
|
||||
|
||||
[[package]]
|
||||
name = "windows_x86_64_gnullvm"
|
||||
|
|
@ -6124,9 +6130,9 @@ checksum = "26d41b46a36d453748aedef1486d5c7a85db22e56aff34643984ea85514e94a3"
|
|||
|
||||
[[package]]
|
||||
name = "windows_x86_64_gnullvm"
|
||||
version = "0.48.0"
|
||||
version = "0.48.4"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "7896dbc1f41e08872e9d5e8f8baa8fdd2677f29468c4e156210174edc7f7b953"
|
||||
checksum = "b5bb3f0331abfe1a95af56067f1e64b3791b55b5373b03869560b6025de809bf"
|
||||
|
||||
[[package]]
|
||||
name = "windows_x86_64_msvc"
|
||||
|
|
@ -6136,9 +6142,9 @@ checksum = "9aec5da331524158c6d1a4ac0ab1541149c0b9505fde06423b02f5ef0106b9f0"
|
|||
|
||||
[[package]]
|
||||
name = "windows_x86_64_msvc"
|
||||
version = "0.48.0"
|
||||
version = "0.48.4"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "1a515f5799fe4961cb532f983ce2b23082366b898e52ffbce459c86f67c8378a"
|
||||
checksum = "bd1df36d9fd0bbe4849461de9b969f765170f4e0f90497d580a235d515722b10"
|
||||
|
||||
[[package]]
|
||||
name = "winreg"
|
||||
|
|
@ -6170,9 +6176,9 @@ dependencies = [
|
|||
|
||||
[[package]]
|
||||
name = "x25519-dalek"
|
||||
version = "2.0.0-rc.3"
|
||||
version = "2.0.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "ec7fae07da688e17059d5886712c933bb0520f15eff2e09cfa18e30968f4e63a"
|
||||
checksum = "fb66477291e7e8d2b0ff1bcb900bf29489a9692816d79874bea351e7a8b6de96"
|
||||
dependencies = [
|
||||
"curve25519-dalek",
|
||||
"rand_core",
|
||||
|
|
@ -6235,7 +6241,7 @@ checksum = "ce36e65b0d2999d2aafac989fb249189a141aee1f53c612c1f37d72631959f69"
|
|||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn 2.0.28",
|
||||
"syn 2.0.29",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
|
|
|
|||
|
|
@ -1,6 +1,12 @@
|
|||
use std::{collections::HashMap, fmt::Display};
|
||||
use std::{collections::HashMap, fmt::Display, iter::Peekable, str::Chars};
|
||||
|
||||
use super::Token;
|
||||
use super::{Comparator, Logical, Operation, Token};
|
||||
|
||||
// Parse a meta expression into a list of tokens that can be easily
|
||||
// converted into a Sieve test.
|
||||
// The parser is not very robust but works on all SpamAssassin meta expressions.
|
||||
// It might be a good idea in the future to instead build a parse tree and
|
||||
// then convert that into a Sieve expression.
|
||||
|
||||
#[derive(Debug, Clone, Default)]
|
||||
pub struct MetaExpression {
|
||||
|
|
@ -11,7 +17,7 @@ pub struct MetaExpression {
|
|||
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct TokenDepth {
|
||||
token: Token,
|
||||
pub token: Token,
|
||||
depth: u32,
|
||||
prefix: Vec<Token>,
|
||||
}
|
||||
|
|
@ -40,15 +46,9 @@ impl MetaExpression {
|
|||
if !buf.is_empty() {
|
||||
let token = Token::from(buf);
|
||||
buf = String::new();
|
||||
if !seen_comp
|
||||
&& matches!(
|
||||
iter.clone()
|
||||
.find(|t| { ['&', '|', '>', '<', '='].contains(t) }),
|
||||
None | Some('&' | '|')
|
||||
)
|
||||
{
|
||||
if !seen_comp && !meta.has_comparator(iter.clone()) {
|
||||
meta.push(token);
|
||||
meta.push(Token::Gt);
|
||||
meta.push(Token::Comparator(Comparator::Gt));
|
||||
meta.push(Token::Number(0));
|
||||
seen_comp = true;
|
||||
} else {
|
||||
|
|
@ -60,7 +60,7 @@ impl MetaExpression {
|
|||
'&' => {
|
||||
seen_comp = false;
|
||||
if matches!(iter.next(), Some('&')) {
|
||||
meta.push(Token::And);
|
||||
meta.push(Token::Logical(Logical::And));
|
||||
} else {
|
||||
eprintln!("Warning: Single & in meta expression {expr}",);
|
||||
}
|
||||
|
|
@ -68,24 +68,24 @@ impl MetaExpression {
|
|||
'|' => {
|
||||
seen_comp = false;
|
||||
if matches!(iter.next(), Some('|')) {
|
||||
meta.push(Token::Or);
|
||||
meta.push(Token::Logical(Logical::Or));
|
||||
} else {
|
||||
eprintln!("Warning: Single | in meta expression {expr}",);
|
||||
}
|
||||
}
|
||||
'!' => {
|
||||
seen_comp = false;
|
||||
meta.push(Token::Not)
|
||||
meta.push(Token::Logical(Logical::Not))
|
||||
}
|
||||
'=' => {
|
||||
seen_comp = true;
|
||||
meta.push(match iter.next() {
|
||||
Some('=') => Token::Eq,
|
||||
Some('>') => Token::Ge,
|
||||
Some('<') => Token::Le,
|
||||
Some('=') => Token::Comparator(Comparator::Eq),
|
||||
Some('>') => Token::Comparator(Comparator::Ge),
|
||||
Some('<') => Token::Comparator(Comparator::Le),
|
||||
_ => {
|
||||
eprintln!("Warning: Single = in meta expression {expr}",);
|
||||
Token::Eq
|
||||
Token::Comparator(Comparator::Eq)
|
||||
}
|
||||
});
|
||||
}
|
||||
|
|
@ -94,9 +94,9 @@ impl MetaExpression {
|
|||
meta.push(match iter.peek() {
|
||||
Some('=') => {
|
||||
iter.next();
|
||||
Token::Ge
|
||||
Token::Comparator(Comparator::Ge)
|
||||
}
|
||||
_ => Token::Gt,
|
||||
_ => Token::Comparator(Comparator::Gt),
|
||||
})
|
||||
}
|
||||
'<' => {
|
||||
|
|
@ -104,9 +104,9 @@ impl MetaExpression {
|
|||
meta.push(match iter.peek() {
|
||||
Some('=') => {
|
||||
iter.next();
|
||||
Token::Le
|
||||
Token::Comparator(Comparator::Le)
|
||||
}
|
||||
_ => Token::Lt,
|
||||
_ => Token::Comparator(Comparator::Lt),
|
||||
})
|
||||
}
|
||||
'(' => meta.push(Token::OpenParen),
|
||||
|
|
@ -119,9 +119,9 @@ impl MetaExpression {
|
|||
|
||||
meta.push(Token::CloseParen)
|
||||
}
|
||||
'+' => meta.push(Token::Add),
|
||||
'*' => meta.push(Token::Multiply),
|
||||
'/' => meta.push(Token::Divide),
|
||||
'+' => meta.push(Token::Operation(Operation::Add)),
|
||||
'*' => meta.push(Token::Operation(Operation::Multiply)),
|
||||
'/' => meta.push(Token::Operation(Operation::Divide)),
|
||||
' ' => {}
|
||||
_ => {
|
||||
eprintln!("Warning: Invalid character {ch} in meta expression {expr}");
|
||||
|
|
@ -139,7 +139,7 @@ impl MetaExpression {
|
|||
if !buf.is_empty() {
|
||||
meta.push(Token::from(buf));
|
||||
if !seen_comp {
|
||||
meta.push(Token::Gt);
|
||||
meta.push(Token::Comparator(Comparator::Gt));
|
||||
meta.push(Token::Number(0));
|
||||
}
|
||||
}
|
||||
|
|
@ -148,7 +148,7 @@ impl MetaExpression {
|
|||
meta
|
||||
}
|
||||
|
||||
fn push(&mut self, token: Token) {
|
||||
fn push(&mut self, mut token: Token) {
|
||||
let pos = self.tokens.len();
|
||||
let depth_range = self
|
||||
.depth_range
|
||||
|
|
@ -182,35 +182,60 @@ impl MetaExpression {
|
|||
self.depth = self.depth.saturating_sub(1);
|
||||
depth = self.depth;
|
||||
}
|
||||
Token::Or | Token::And => {
|
||||
let start_prefix = &mut self.tokens[depth_range.start].prefix;
|
||||
if !start_prefix.contains(&Token::And) && !start_prefix.contains(&Token::Or) {
|
||||
start_prefix.insert(0, token.clone());
|
||||
}
|
||||
depth_range.logic_end = true;
|
||||
if let Some((pos, is_static)) = depth_range.expr_end.take() {
|
||||
self.tokens[pos + 2]
|
||||
.prefix
|
||||
.push(Token::BeginExpression(is_static));
|
||||
prefix.push(Token::EndExpression(is_static));
|
||||
Token::Logical(op) => {
|
||||
if self
|
||||
.tokens
|
||||
.iter()
|
||||
.any(|t| matches!(t.token, Token::Comparator(_)) && t.depth < depth)
|
||||
{
|
||||
token = Token::Operation(match op {
|
||||
Logical::And => Operation::And,
|
||||
Logical::Or => Operation::Or,
|
||||
Logical::Not => Operation::Not,
|
||||
});
|
||||
if let Some((pos, true)) = depth_range.expr_end {
|
||||
depth_range.expr_end = Some((pos, false));
|
||||
}
|
||||
} else if matches!(op, Logical::Or | Logical::And) {
|
||||
let start_prefix = &mut self.tokens[depth_range.start].prefix;
|
||||
if !start_prefix.contains(&Token::Logical(Logical::And))
|
||||
&& !start_prefix.contains(&Token::Logical(Logical::Or))
|
||||
{
|
||||
start_prefix.insert(0, token.clone());
|
||||
}
|
||||
depth_range.logic_end = true;
|
||||
if let Some((pos, is_static)) = depth_range.expr_end.take() {
|
||||
self.tokens[pos + 2]
|
||||
.prefix
|
||||
.push(Token::BeginExpression(is_static));
|
||||
prefix.push(Token::EndExpression(is_static));
|
||||
}
|
||||
}
|
||||
}
|
||||
Token::Lt | Token::Gt | Token::Eq | Token::Le | Token::Ge => {
|
||||
Token::Comparator(_) => {
|
||||
let mut is_static = true;
|
||||
let mut start_pos = usize::MAX;
|
||||
for (pos, token) in self.tokens.iter().enumerate().rev() {
|
||||
for (pos, token) in self.tokens.iter_mut().enumerate().rev() {
|
||||
if token.depth >= depth {
|
||||
start_pos = pos;
|
||||
match &token.token {
|
||||
Token::And | Token::Or | Token::Not => {
|
||||
start_pos += 1;
|
||||
break;
|
||||
Token::Logical(op) => {
|
||||
if token.depth == depth {
|
||||
start_pos += 1;
|
||||
break;
|
||||
} else {
|
||||
is_static = false;
|
||||
token.token = Token::Operation(match op {
|
||||
Logical::And => Operation::And,
|
||||
Logical::Or => Operation::Or,
|
||||
Logical::Not => Operation::Not,
|
||||
});
|
||||
token.prefix.clear();
|
||||
}
|
||||
}
|
||||
Token::OpenParen
|
||||
| Token::CloseParen
|
||||
| Token::Add
|
||||
| Token::Multiply
|
||||
| Token::Divide
|
||||
| Token::Operation(_)
|
||||
| Token::Tag(_) => {
|
||||
is_static = false;
|
||||
}
|
||||
|
|
@ -231,7 +256,7 @@ impl MetaExpression {
|
|||
depth_range.expr_end = Some((pos, true));
|
||||
}
|
||||
}
|
||||
Token::Tag(_) | Token::Add | Token::Multiply | Token::Divide => {
|
||||
Token::Tag(_) | Token::Operation(_) => {
|
||||
if let Some((pos, true)) = depth_range.expr_end {
|
||||
depth_range.expr_end = Some((pos, false));
|
||||
}
|
||||
|
|
@ -266,6 +291,47 @@ impl MetaExpression {
|
|||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn has_comparator(&self, iter: Peekable<Chars<'_>>) -> bool {
|
||||
let mut d = self.depth;
|
||||
let mut comp_depth = None;
|
||||
let mut logic_depth = None;
|
||||
|
||||
for (pos, ch) in iter.enumerate() {
|
||||
match ch {
|
||||
'(' => {
|
||||
d += 1;
|
||||
}
|
||||
')' => {
|
||||
d = d.saturating_sub(1);
|
||||
}
|
||||
'>' | '<' | '=' => {
|
||||
comp_depth = Some((pos, d));
|
||||
break;
|
||||
}
|
||||
'&' | '|' => {
|
||||
if d <= self.depth {
|
||||
logic_depth = Some((pos, d));
|
||||
}
|
||||
}
|
||||
_ => (),
|
||||
}
|
||||
}
|
||||
|
||||
println!("comp_depth: {comp_depth:?} {logic_depth:?}");
|
||||
|
||||
match (comp_depth, logic_depth) {
|
||||
(Some((comp_pos, comp_depth)), Some((logic_pos, logic_depth))) => {
|
||||
match comp_depth.cmp(&logic_depth) {
|
||||
std::cmp::Ordering::Less => true,
|
||||
std::cmp::Ordering::Equal => comp_pos < logic_pos,
|
||||
_ => false,
|
||||
}
|
||||
}
|
||||
(Some(_), None) => true,
|
||||
_ => false,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl From<String> for Token {
|
||||
|
|
@ -288,8 +354,12 @@ impl Display for MetaExpression {
|
|||
}
|
||||
|
||||
match &token.token {
|
||||
Token::And | Token::Or => f.write_str(", "),
|
||||
Token::Gt | Token::Lt | Token::Eq | Token::Ge | Token::Le => f.write_str(" "),
|
||||
Token::Logical(Logical::And) | Token::Logical(Logical::Or) => f.write_str(", "),
|
||||
Token::Comparator(Comparator::Gt)
|
||||
| Token::Comparator(Comparator::Lt)
|
||||
| Token::Comparator(Comparator::Eq)
|
||||
| Token::Comparator(Comparator::Ge)
|
||||
| Token::Comparator(Comparator::Le) => f.write_str(" "),
|
||||
_ => token.token.fmt(f),
|
||||
}?;
|
||||
}
|
||||
|
|
@ -303,27 +373,30 @@ impl Display for Token {
|
|||
match self {
|
||||
Token::Tag(t) => t.fmt(f),
|
||||
Token::Number(n) => n.fmt(f),
|
||||
Token::And => f.write_str("allof("),
|
||||
Token::Or => f.write_str("anyof("),
|
||||
Token::Not => f.write_str("not "),
|
||||
Token::Lt | Token::Eq | Token::Ge | Token::Le | Token::Gt => {
|
||||
f.write_str("string :")?;
|
||||
match self {
|
||||
Token::Eq => f.write_str("eq")?,
|
||||
Token::Gt => f.write_str("gt")?,
|
||||
Token::Lt => f.write_str("lt")?,
|
||||
Token::Ge => f.write_str("ge")?,
|
||||
Token::Le => f.write_str("gt")?,
|
||||
Token::Logical(Logical::And) => f.write_str("allof("),
|
||||
Token::Logical(Logical::Or) => f.write_str("anyof("),
|
||||
Token::Logical(Logical::Not) => f.write_str("not "),
|
||||
Token::Comparator(comp) => {
|
||||
f.write_str("string :value \"")?;
|
||||
match comp {
|
||||
Comparator::Eq => f.write_str("eq")?,
|
||||
Comparator::Gt => f.write_str("gt")?,
|
||||
Comparator::Lt => f.write_str("lt")?,
|
||||
Comparator::Ge => f.write_str("ge")?,
|
||||
Comparator::Le => f.write_str("gt")?,
|
||||
_ => unreachable!(),
|
||||
}
|
||||
f.write_str(" ")
|
||||
f.write_str("\" :comparator \"i;ascii-numeric\" ")
|
||||
}
|
||||
|
||||
Token::OpenParen => f.write_str("("),
|
||||
Token::CloseParen => f.write_str(")"),
|
||||
Token::Add => f.write_str(" + "),
|
||||
Token::Multiply => f.write_str(" * "),
|
||||
Token::Divide => f.write_str(" / "),
|
||||
Token::Operation(Operation::Add) => f.write_str(" + "),
|
||||
Token::Operation(Operation::Multiply) => f.write_str(" * "),
|
||||
Token::Operation(Operation::Divide) => f.write_str(" / "),
|
||||
Token::Operation(Operation::And) => f.write_str(" & "),
|
||||
Token::Operation(Operation::Or) => f.write_str(" | "),
|
||||
Token::Operation(Operation::Not) => f.write_str("!"),
|
||||
Token::BeginExpression(is_static) => {
|
||||
if *is_static {
|
||||
f.write_str("\"")
|
||||
|
|
@ -363,17 +436,19 @@ mod test {
|
|||
("__ML2 || __ML4", ""),
|
||||
("(__AT_HOTMAIL_MSGID && (!__FROM_HOTMAIL_COM && !__FROM_MSN_COM && !__FROM_YAHOO_COM))", ""),
|
||||
("(0)", ""),
|
||||
("RAZOR2_CHECK + DCC_CHECK + PYZOR_CHECK > 1", ""),*/
|
||||
("RAZOR2_CHECK + DCC_CHECK + PYZOR_CHECK > 1", ""),
|
||||
("(SUBJECT_IN_BLOCKLIST)", ""),
|
||||
("__HAS_MSGID && !(__SANE_MSGID || __MSGID_COMMENT)", ""),
|
||||
("!__CTYPE_HTML && __X_MAILER_APPLEMAIL && (__MSGID_APPLEMAIL || __MIME_VERSION_APPLEMAIL)", ""),
|
||||
("((__AUTO_GEN_MS||__AUTO_GEN_3||__AUTO_GEN_4) && !__XM_VBULLETIN && !__X_CRON_ENV)", ""),
|
||||
("((__AUTO_GEN_MS||__AUTO_GEN_3||__AUTO_GEN_4) && !__XM_VBULLETIN && !__X_CRON_ENV)", ""),*/
|
||||
("(__WEBMAIL_ACCT + __MAILBOX_FULL + (__TVD_PH_SUBJ_META || __TVD_PH_BODY_META) > 3)", ""),
|
||||
|
||||
] {
|
||||
let meta = MetaExpression::from_meta(expr);
|
||||
//println!("{:#?}", meta.tokens);
|
||||
let result = meta.to_string();
|
||||
|
||||
//println!("{}", expected);
|
||||
println!("{expr}");
|
||||
println!("{}", result);
|
||||
|
||||
/*assert_eq!(
|
||||
|
|
|
|||
|
|
@ -1,10 +1,12 @@
|
|||
use std::collections::HashMap;
|
||||
|
||||
use self::meta::MetaExpression;
|
||||
|
||||
pub mod meta;
|
||||
pub mod spamassassin;
|
||||
pub mod utils;
|
||||
|
||||
#[derive(Debug, Default)]
|
||||
#[derive(Debug, Default, Clone)]
|
||||
struct Rule {
|
||||
name: String,
|
||||
t: RuleType,
|
||||
|
|
@ -12,13 +14,16 @@ struct Rule {
|
|||
description: HashMap<String, String>,
|
||||
priority: i32,
|
||||
flags: Vec<TestFlag>,
|
||||
forward_score_pos: f64,
|
||||
forward_score_neg: f64,
|
||||
}
|
||||
|
||||
#[derive(Debug, Default)]
|
||||
#[derive(Debug, Default, Clone)]
|
||||
enum RuleType {
|
||||
Header {
|
||||
matches: HeaderMatches,
|
||||
header: Header,
|
||||
part: Vec<HeaderPart>,
|
||||
if_unset: Option<String>,
|
||||
pattern: String,
|
||||
},
|
||||
|
|
@ -37,7 +42,7 @@ enum RuleType {
|
|||
params: Vec<String>,
|
||||
},
|
||||
Meta {
|
||||
tokens: Vec<Token>,
|
||||
expr: MetaExpression,
|
||||
},
|
||||
|
||||
#[default]
|
||||
|
|
@ -56,7 +61,7 @@ impl RuleType {
|
|||
}
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
#[derive(Debug, PartialEq, Eq, Clone)]
|
||||
enum TestFlag {
|
||||
Net,
|
||||
Nice,
|
||||
|
|
@ -74,7 +79,7 @@ enum TestFlag {
|
|||
DnsBlockRule(String),
|
||||
}
|
||||
|
||||
#[derive(Debug, Default)]
|
||||
#[derive(Debug, Default, PartialEq, Eq, Clone)]
|
||||
enum Header {
|
||||
#[default]
|
||||
All,
|
||||
|
|
@ -82,13 +87,10 @@ enum Header {
|
|||
AllExternal,
|
||||
EnvelopeFrom,
|
||||
ToCc,
|
||||
Name {
|
||||
name: String,
|
||||
part: Vec<HeaderPart>,
|
||||
},
|
||||
Name(String),
|
||||
}
|
||||
|
||||
#[derive(Debug, Default)]
|
||||
#[derive(Debug, Default, Clone)]
|
||||
enum HeaderMatches {
|
||||
#[default]
|
||||
Matches,
|
||||
|
|
@ -96,7 +98,7 @@ enum HeaderMatches {
|
|||
Exists,
|
||||
}
|
||||
|
||||
#[derive(Debug, Default)]
|
||||
#[derive(Debug, Default, PartialEq, Eq, Clone)]
|
||||
enum HeaderPart {
|
||||
Name,
|
||||
Addr,
|
||||
|
|
@ -108,23 +110,42 @@ enum HeaderPart {
|
|||
pub enum Token {
|
||||
Tag(String),
|
||||
Number(u32),
|
||||
Logical(Logical),
|
||||
Comparator(Comparator),
|
||||
Operation(Operation),
|
||||
|
||||
OpenParen,
|
||||
CloseParen,
|
||||
|
||||
// Sieve specific
|
||||
BeginExpression(bool),
|
||||
EndExpression(bool),
|
||||
}
|
||||
|
||||
#[derive(Debug, PartialEq, Eq, Clone)]
|
||||
pub enum Logical {
|
||||
And,
|
||||
Or,
|
||||
Not,
|
||||
}
|
||||
|
||||
#[derive(Debug, PartialEq, Eq, Clone)]
|
||||
pub enum Comparator {
|
||||
Gt,
|
||||
Lt,
|
||||
Eq,
|
||||
Ge,
|
||||
Le,
|
||||
OpenParen,
|
||||
CloseParen,
|
||||
}
|
||||
|
||||
#[derive(Debug, PartialEq, Eq, Clone)]
|
||||
pub enum Operation {
|
||||
Add,
|
||||
Multiply,
|
||||
Divide,
|
||||
|
||||
// Sieve specific
|
||||
BeginExpression(bool),
|
||||
EndExpression(bool),
|
||||
And,
|
||||
Or,
|
||||
Not,
|
||||
}
|
||||
|
||||
impl Rule {
|
||||
|
|
@ -143,12 +164,39 @@ impl Rule {
|
|||
|
||||
impl Ord for Rule {
|
||||
fn cmp(&self, other: &Self) -> std::cmp::Ordering {
|
||||
match self.priority.cmp(&other.priority) {
|
||||
std::cmp::Ordering::Equal => match self.score().partial_cmp(&other.score()).unwrap() {
|
||||
std::cmp::Ordering::Equal => other.name.cmp(&self.name),
|
||||
let this_score = self.score();
|
||||
let other_score = other.score();
|
||||
|
||||
let this_is_negative = this_score < 0.0;
|
||||
let other_is_negative = other_score < 0.0;
|
||||
|
||||
if this_is_negative != other_is_negative {
|
||||
if this_is_negative {
|
||||
std::cmp::Ordering::Less
|
||||
} else {
|
||||
std::cmp::Ordering::Greater
|
||||
}
|
||||
} else {
|
||||
let this_priority = if this_score != 0.0 {
|
||||
self.priority
|
||||
} else {
|
||||
9000
|
||||
};
|
||||
let other_priority = if other_score != 0.0 {
|
||||
other.priority
|
||||
} else {
|
||||
9000
|
||||
};
|
||||
|
||||
match this_priority.cmp(&other_priority) {
|
||||
std::cmp::Ordering::Equal => {
|
||||
match other_score.abs().partial_cmp(&this_score.abs()).unwrap() {
|
||||
std::cmp::Ordering::Equal => other.name.cmp(&self.name),
|
||||
x => x,
|
||||
}
|
||||
}
|
||||
x => x,
|
||||
},
|
||||
x => x,
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,8 +1,7 @@
|
|||
use std::{
|
||||
collections::{BTreeMap, BTreeSet, HashMap, HashSet},
|
||||
default,
|
||||
fmt::format,
|
||||
fs,
|
||||
collections::{BTreeMap, HashMap, HashSet},
|
||||
fmt::{Display, Write},
|
||||
fs::{self},
|
||||
path::PathBuf,
|
||||
};
|
||||
|
||||
|
|
@ -12,7 +11,9 @@ use super::{
|
|||
Header, HeaderMatches, HeaderPart, Rule, RuleType, TestFlag, Token, UnwrapResult,
|
||||
};
|
||||
|
||||
static SUPPORTED_PLUGINS: [&str; 37] = [
|
||||
const VERSION: f64 = 4.000000;
|
||||
|
||||
static IF_TRUE: [&str; 57] = [
|
||||
"Mail::SpamAssassin::Plugin::DKIM",
|
||||
"Mail::SpamAssassin::Plugin::SPF",
|
||||
"Mail::SpamAssassin::Plugin::ASN",
|
||||
|
|
@ -49,9 +50,31 @@ static SUPPORTED_PLUGINS: [&str; 37] = [
|
|||
"Mail::SpamAssassin::Plugin::VBounce",
|
||||
"Mail::SpamAssassin::Plugin::WLBLEval",
|
||||
"Mail::SpamAssassin::Plugin::WelcomeListSubject",
|
||||
"Mail::SpamAssassin::Plugin::WhiteListSubject",
|
||||
"Mail::SpamAssassin::Conf::feature_bayes_stopwords",
|
||||
"Mail::SpamAssassin::Conf::feature_bug6558_free",
|
||||
"Mail::SpamAssassin::Conf::feature_capture_rules",
|
||||
"Mail::SpamAssassin::Conf::feature_dns_local_ports_permit_avoid",
|
||||
"Mail::SpamAssassin::Conf::feature_originating_ip_headers",
|
||||
"Mail::SpamAssassin::Conf::feature_registryboundaries",
|
||||
"Mail::SpamAssassin::Conf::feature_welcomelist_blocklist",
|
||||
"Mail::SpamAssassin::Conf::feature_yesno_takes_args",
|
||||
"Mail::SpamAssassin::Conf::perl_min_version_5010000",
|
||||
"Mail::SpamAssassin::Plugin::BodyEval::has_check_body_length",
|
||||
"Mail::SpamAssassin::Plugin::DKIM::has_arc",
|
||||
"Mail::SpamAssassin::Plugin::DecodeShortURLs::has_get",
|
||||
"Mail::SpamAssassin::Plugin::DecodeShortURLs::has_short_url_redir",
|
||||
"Mail::SpamAssassin::Plugin::MIMEEval::has_check_abundant_unicode_ratio",
|
||||
"Mail::SpamAssassin::Plugin::MIMEEval::has_check_for_ascii_text_illegal",
|
||||
"Mail::SpamAssassin::Plugin::SPF::has_check_for_spf_errors",
|
||||
"Mail::SpamAssassin::Plugin::URIDNSBL::has_tflags_domains_only",
|
||||
"Mail::SpamAssassin::Plugin::URIDNSBL::has_uridnsbl_for_a",
|
||||
"Mail::SpamAssassin::Plugin::ASN::has_check_asn",
|
||||
"Mail::SpamAssassin::Conf::compat_welcomelist_blocklist",
|
||||
"Mail::SpamAssassin::Conf::feature_dns_block_rule",
|
||||
];
|
||||
|
||||
static IF_FALSE: [&str; 1] = ["Mail::SpamAssassin::Plugin::WhiteListSubject"];
|
||||
|
||||
static SUPPORTED_FUNCTIONS: [&str; 162] = [
|
||||
"check_abundant_unicode_ratio",
|
||||
"check_access_database",
|
||||
|
|
@ -217,59 +240,6 @@ static SUPPORTED_FUNCTIONS: [&str; 162] = [
|
|||
"tvd_vertical_words",
|
||||
];
|
||||
|
||||
static IF_TRUE: [&str; 25] = [
|
||||
"!(!plugin(Mail::SpamAssassin::Plugin::DKIM))",
|
||||
"(version >= 3.003000)",
|
||||
"(version >= 3.004000)",
|
||||
"(version >= 3.004001)",
|
||||
"(version >= 3.004002)",
|
||||
"(version >= 3.004003)",
|
||||
"(version >= 4.000000)",
|
||||
"can(Mail::SpamAssassin::Conf::feature_bayes_stopwords)",
|
||||
"can(Mail::SpamAssassin::Conf::feature_bug6558_free)",
|
||||
"can(Mail::SpamAssassin::Conf::feature_capture_rules)",
|
||||
"can(Mail::SpamAssassin::Conf::feature_dns_local_ports_permit_avoid)",
|
||||
"can(Mail::SpamAssassin::Conf::feature_originating_ip_headers)",
|
||||
"can(Mail::SpamAssassin::Conf::feature_registryboundaries)",
|
||||
"can(Mail::SpamAssassin::Conf::feature_welcomelist_blocklist)",
|
||||
"can(Mail::SpamAssassin::Conf::feature_yesno_takes_args)",
|
||||
"can(Mail::SpamAssassin::Conf::perl_min_version_5010000)",
|
||||
"can(Mail::SpamAssassin::Plugin::BodyEval::has_check_body_length)",
|
||||
"can(Mail::SpamAssassin::Plugin::DKIM::has_arc)",
|
||||
"can(Mail::SpamAssassin::Plugin::DecodeShortURLs::has_get)",
|
||||
"can(Mail::SpamAssassin::Plugin::DecodeShortURLs::has_short_url_redir)",
|
||||
"can(Mail::SpamAssassin::Plugin::MIMEEval::has_check_abundant_unicode_ratio)",
|
||||
"can(Mail::SpamAssassin::Plugin::MIMEEval::has_check_for_ascii_text_illegal)",
|
||||
"can(Mail::SpamAssassin::Plugin::SPF::has_check_for_spf_errors)",
|
||||
"can(Mail::SpamAssassin::Plugin::URIDNSBL::has_tflags_domains_only)",
|
||||
"can(Mail::SpamAssassin::Plugin::URIDNSBL::has_uridnsbl_for_a)",
|
||||
];
|
||||
|
||||
static IF_FALSE: [&str; 22] = [
|
||||
"(version < 4.000000)",
|
||||
"!((version >= 3.003000))",
|
||||
"!((version >= 3.004000))",
|
||||
"can(Mail::SpamAssassin::Conf::feature_dns_block_rule)",
|
||||
"!plugin(Mail::SpamAssassin::Plugin::BodyEval)",
|
||||
"!plugin(Mail::SpamAssassin::Plugin::DKIM)",
|
||||
"!plugin(Mail::SpamAssassin::Plugin::FreeMail)",
|
||||
"!plugin(Mail::SpamAssassin::Plugin::HTMLEval)",
|
||||
"!plugin(Mail::SpamAssassin::Plugin::HeaderEval)",
|
||||
"!plugin(Mail::SpamAssassin::Plugin::ImageInfo)",
|
||||
"!plugin(Mail::SpamAssassin::Plugin::MIMEEval)",
|
||||
"!plugin(Mail::SpamAssassin::Plugin::MIMEHeader)",
|
||||
"!plugin(Mail::SpamAssassin::Plugin::ReplaceTags)",
|
||||
"!plugin(Mail::SpamAssassin::Plugin::SPF)",
|
||||
"!plugin(Mail::SpamAssassin::Plugin::WLBLEval)",
|
||||
"!plugin(Mail::SpamAssassin::Plugin::WelcomeListSubject)",
|
||||
"!(can(Mail::SpamAssassin::Conf::feature_bug6558_free))",
|
||||
"!(can(Mail::SpamAssassin::Plugin::ASN::has_check_asn))",
|
||||
"!(can(Mail::SpamAssassin::Plugin::BodyEval::has_check_body_length))",
|
||||
"!can(Mail::SpamAssassin::Conf::compat_welcomelist_blocklist)",
|
||||
"!can(Mail::SpamAssassin::Conf::feature_welcomelist_blocklist)",
|
||||
"!can(Mail::SpamAssassin::Plugin::DecodeShortURLs::has_short_url_redir)",
|
||||
];
|
||||
|
||||
pub fn import_spamassassin(path: PathBuf, extension: String, do_warn: bool, validate_regex: bool) {
|
||||
let mut paths: Vec<_> = fs::read_dir(&path)
|
||||
.unwrap_result("read directory")
|
||||
|
|
@ -285,7 +255,7 @@ pub fn import_spamassassin(path: PathBuf, extension: String, do_warn: bool, vali
|
|||
let mut replace_rules: HashSet<String> = HashSet::new();
|
||||
let mut tags: HashMap<String, String> = HashMap::new();
|
||||
|
||||
let mut unsupported_plugins: BTreeMap<String, HashMap<PathBuf, Vec<String>>> = BTreeMap::new();
|
||||
let mut unsupported_ifs: BTreeMap<String, HashMap<PathBuf, Vec<String>>> = BTreeMap::new();
|
||||
let mut unsupported_commands: BTreeMap<String, HashMap<PathBuf, Vec<String>>> = BTreeMap::new();
|
||||
|
||||
for path in paths {
|
||||
|
|
@ -345,7 +315,7 @@ pub fn import_spamassassin(path: PathBuf, extension: String, do_warn: bool, vali
|
|||
last_ch = ch;
|
||||
}
|
||||
|
||||
let (cmd, params) = line
|
||||
let (cmd, mut params) = line
|
||||
.split_once(' ')
|
||||
.map(|(k, v)| (k.trim(), v.trim()))
|
||||
.unwrap_or((line.as_str().trim(), ""));
|
||||
|
|
@ -358,10 +328,10 @@ pub fn import_spamassassin(path: PathBuf, extension: String, do_warn: bool, vali
|
|||
match cmd {
|
||||
"ifplugin" => {
|
||||
is_supported_stack.push(is_supported_block);
|
||||
is_supported_block = SUPPORTED_PLUGINS.contains(¶ms);
|
||||
is_supported_block = IF_TRUE.contains(¶ms);
|
||||
|
||||
if !is_supported_block {
|
||||
unsupported_plugins
|
||||
if !is_supported_block && !IF_FALSE.contains(¶ms) {
|
||||
unsupported_ifs
|
||||
.entry(params.to_string())
|
||||
.or_default()
|
||||
.entry(path.clone())
|
||||
|
|
@ -370,14 +340,79 @@ pub fn import_spamassassin(path: PathBuf, extension: String, do_warn: bool, vali
|
|||
}
|
||||
}
|
||||
"if" => {
|
||||
is_supported_stack.push(is_supported_block);
|
||||
is_supported_block = IF_TRUE.contains(¶ms);
|
||||
if !is_supported_block && !IF_FALSE.contains(¶ms) {
|
||||
eprintln!(
|
||||
"Warning: Unknown if condition on {}, line {}",
|
||||
path.display(),
|
||||
line_num
|
||||
);
|
||||
let _params = params;
|
||||
let mut is_not = false;
|
||||
loop {
|
||||
let mut has_changes = false;
|
||||
if let Some(expr) = params.strip_prefix('!') {
|
||||
is_not = !is_not;
|
||||
params = expr.trim();
|
||||
has_changes = true;
|
||||
}
|
||||
if let Some(expr) =
|
||||
params.strip_prefix('(').and_then(|v| v.strip_suffix(')'))
|
||||
{
|
||||
params = expr.trim();
|
||||
has_changes = true;
|
||||
}
|
||||
if let Some(expr) = params
|
||||
.strip_prefix("can(")
|
||||
.or_else(|| params.strip_prefix("plugin("))
|
||||
.and_then(|v| v.strip_suffix(')'))
|
||||
{
|
||||
params = expr.trim();
|
||||
has_changes = true;
|
||||
}
|
||||
if !has_changes {
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if let Some(version) = params.strip_prefix("version ") {
|
||||
is_supported_stack.push(is_supported_block);
|
||||
let (op, version) = version.trim().split_once(' ').unwrap_or(("", version));
|
||||
let version = version
|
||||
.parse::<f64>()
|
||||
.unwrap_result("Failed to parse version");
|
||||
match op {
|
||||
"<" => {
|
||||
is_supported_block = (VERSION < version) ^ is_not;
|
||||
}
|
||||
"<=" => {
|
||||
is_supported_block = (VERSION <= version) ^ is_not;
|
||||
}
|
||||
">" => {
|
||||
is_supported_block = (VERSION > version) ^ is_not;
|
||||
}
|
||||
">=" => {
|
||||
is_supported_block = (VERSION >= version) ^ is_not;
|
||||
}
|
||||
"==" => {
|
||||
is_supported_block = (VERSION == version) ^ is_not;
|
||||
}
|
||||
"!=" => {
|
||||
is_supported_block = (VERSION != version) ^ is_not;
|
||||
}
|
||||
_ => {
|
||||
eprintln!(
|
||||
"Warning: Invalid version operator on {}, line {}",
|
||||
path.display(),
|
||||
line_num
|
||||
);
|
||||
}
|
||||
}
|
||||
} else {
|
||||
is_supported_stack.push(is_supported_block);
|
||||
is_supported_block = IF_TRUE.contains(¶ms);
|
||||
if !is_supported_block && !IF_FALSE.contains(¶ms) {
|
||||
unsupported_ifs
|
||||
.entry(params.to_string())
|
||||
.or_default()
|
||||
.entry(path.clone())
|
||||
.or_default()
|
||||
.push(line_num.to_string());
|
||||
}
|
||||
is_supported_block ^= is_not;
|
||||
}
|
||||
}
|
||||
"endif" => {
|
||||
|
|
@ -410,7 +445,7 @@ pub fn import_spamassassin(path: PathBuf, extension: String, do_warn: bool, vali
|
|||
if let Some((name, value)) =
|
||||
params.split_once(' ').map(|(k, v)| (k.trim(), v.trim()))
|
||||
{
|
||||
let mut rule = rules.entry(name.to_string()).or_default();
|
||||
let rule = rules.entry(name.to_string()).or_default();
|
||||
|
||||
if let Some(function) = value.strip_prefix("eval:") {
|
||||
if let Some((fnc_name, params_)) = function
|
||||
|
|
@ -478,17 +513,33 @@ pub fn import_spamassassin(path: PathBuf, extension: String, do_warn: bool, vali
|
|||
if let Some(exists) = value.strip_prefix("exists:") {
|
||||
rule.t = RuleType::Header {
|
||||
matches: HeaderMatches::Exists,
|
||||
header: Header::Name {
|
||||
name: exists.to_string(),
|
||||
part: vec![],
|
||||
},
|
||||
header: Header::Name(exists.to_string()),
|
||||
if_unset: None,
|
||||
pattern: String::new(),
|
||||
part: vec![],
|
||||
};
|
||||
} else if let Some((header, (op, mut pattern))) = value
|
||||
.split_once(' ')
|
||||
.and_then(|(k, v)| (k.trim(), v.trim().split_once(' ')?).into())
|
||||
{
|
||||
let (header, part) = header.split_once(':').unwrap_or((header, ""));
|
||||
let part = part.split(':').filter_map(|part| {
|
||||
match part.trim() {
|
||||
"name" => {Some(HeaderPart::Name)}
|
||||
"addr" => {Some(HeaderPart::Addr)}
|
||||
"raw" => {Some(HeaderPart::Raw)}
|
||||
"" => None,
|
||||
_ => {
|
||||
eprintln!(
|
||||
"Warning: Invalid header part {part:?} on {}, line {}",
|
||||
path.display(),
|
||||
line_num
|
||||
);
|
||||
None
|
||||
}
|
||||
}
|
||||
|
||||
}).collect::<Vec<_>>();
|
||||
rule.t = RuleType::Header {
|
||||
matches: match op {
|
||||
"=~" => HeaderMatches::Matches,
|
||||
|
|
@ -502,38 +553,13 @@ pub fn import_spamassassin(path: PathBuf, extension: String, do_warn: bool, vali
|
|||
continue;
|
||||
}
|
||||
},
|
||||
header: if let Some((header, part)) = header.split_once(':') {
|
||||
Header::Name {
|
||||
name: header.to_string(),
|
||||
part: part.split(':').filter_map(|part| {
|
||||
match part {
|
||||
"name" => {Some(HeaderPart::Name)}
|
||||
"addr" => {Some(HeaderPart::Addr)}
|
||||
"raw" => {Some(HeaderPart::Raw)}
|
||||
_ => {
|
||||
eprintln!(
|
||||
"Warning: Invalid header part {part:?} on {}, line {}",
|
||||
path.display(),
|
||||
line_num
|
||||
);
|
||||
None
|
||||
}
|
||||
}
|
||||
|
||||
}).collect::<Vec<_>>()
|
||||
}
|
||||
} else {
|
||||
match header {
|
||||
"ALL" => Header::All,
|
||||
"MESSAGEID" => Header::MessageId,
|
||||
"ALL-EXTERNAL" => Header::AllExternal,
|
||||
"EnvelopeFrom" => Header::EnvelopeFrom,
|
||||
"ToCc" => Header::ToCc,
|
||||
_ => Header::Name {
|
||||
name: header.to_string(),
|
||||
part: vec![],
|
||||
},
|
||||
}
|
||||
header: match header {
|
||||
"ALL" => Header::All,
|
||||
"MESSAGEID" => Header::MessageId,
|
||||
"ALL-EXTERNAL" => Header::AllExternal,
|
||||
"EnvelopeFrom" => Header::EnvelopeFrom,
|
||||
"ToCc" => Header::ToCc,
|
||||
_ => Header::Name(header.to_string()),
|
||||
},
|
||||
if_unset: pattern.rsplit_once("[if-unset:").and_then(
|
||||
|(new_pattern, if_unset)| {
|
||||
|
|
@ -553,6 +579,7 @@ pub fn import_spamassassin(path: PathBuf, extension: String, do_warn: bool, vali
|
|||
},
|
||||
),
|
||||
pattern: fix_broken_regex(pattern).to_string(),
|
||||
part,
|
||||
};
|
||||
} else {
|
||||
eprintln!(
|
||||
|
|
@ -620,7 +647,7 @@ pub fn import_spamassassin(path: PathBuf, extension: String, do_warn: bool, vali
|
|||
}
|
||||
"meta" => {
|
||||
if let Some((test_name, expression)) = params.split_once(' ') {
|
||||
let tokens = MetaExpression::from_meta(expression);
|
||||
let expr = MetaExpression::from_meta(expression);
|
||||
/*if tokens.tokens.contains(&Token::Divide) {
|
||||
println!(
|
||||
"->: {expression}\n{:?}\n<-: {}",
|
||||
|
|
@ -632,10 +659,8 @@ pub fn import_spamassassin(path: PathBuf, extension: String, do_warn: bool, vali
|
|||
String::from(tokens.clone())
|
||||
);
|
||||
std::process::exit(1);
|
||||
}
|
||||
rules.entry(test_name.to_string()).or_default().t = RuleType::Meta {
|
||||
tokens: tokens.tokens,
|
||||
};*/
|
||||
}*/
|
||||
rules.entry(test_name.to_string()).or_default().t = RuleType::Meta { expr };
|
||||
} else {
|
||||
eprintln!(
|
||||
"Warning: Invalid meta command on {}, line {}",
|
||||
|
|
@ -993,53 +1018,51 @@ pub fn import_spamassassin(path: PathBuf, extension: String, do_warn: bool, vali
|
|||
}
|
||||
})
|
||||
.collect::<Vec<_>>();
|
||||
rules.sort_unstable_by(|a, b| b.cmp(a));
|
||||
rules.sort_unstable();
|
||||
|
||||
let no_meta: Vec<Token> = vec![];
|
||||
let no_meta = MetaExpression::default();
|
||||
let mut meta = &no_meta;
|
||||
|
||||
let mut tests_done = HashSet::new();
|
||||
let mut tests_linked = HashSet::new();
|
||||
let mut rules_iter = rules.iter();
|
||||
let mut rules_stack = Vec::new();
|
||||
let mut rules_sorted = Vec::with_capacity(rules.len());
|
||||
|
||||
// Sort rules by meta
|
||||
loop {
|
||||
while let Some(rule) = rules_iter.next() {
|
||||
let in_meta = !meta.tokens.is_empty();
|
||||
if tests_done.contains(&rule.name)
|
||||
|| (!meta.is_empty()
|
||||
|| (in_meta
|
||||
&& !meta
|
||||
.tokens
|
||||
.iter()
|
||||
.any(|t| matches!(t, Token::Tag(n) if n == &rule.name)))
|
||||
.any(|t| matches!(&t.token, Token::Tag(n) if n == &rule.name)))
|
||||
{
|
||||
continue;
|
||||
}
|
||||
tests_done.insert(&rule.name);
|
||||
if in_meta {
|
||||
tests_linked.insert(&rule.name);
|
||||
}
|
||||
|
||||
match &rule.t {
|
||||
RuleType::Meta { tokens } => {
|
||||
meta = tokens;
|
||||
rules_stack.push((meta, rules_iter));
|
||||
RuleType::Meta { expr } if rule.score() != 0.0 => {
|
||||
rules_stack.push((meta, rule, rules_iter));
|
||||
rules_iter = rules.iter();
|
||||
meta = expr;
|
||||
}
|
||||
_ => {
|
||||
rules_sorted.push(rule);
|
||||
//write!(&mut script, "{rule}").unwrap();
|
||||
}
|
||||
RuleType::Header {
|
||||
matches,
|
||||
header,
|
||||
if_unset,
|
||||
pattern,
|
||||
} => todo!(),
|
||||
RuleType::Body { pattern, raw } => todo!(),
|
||||
RuleType::Full { pattern } => todo!(),
|
||||
RuleType::Uri { pattern } => todo!(),
|
||||
RuleType::Eval { function, params } => todo!(),
|
||||
RuleType::None => (),
|
||||
}
|
||||
|
||||
tests_done.insert(&rule.name);
|
||||
}
|
||||
|
||||
if let Some((prev_meta, prev_rules_iter)) = rules_stack.pop() {
|
||||
for token in meta {
|
||||
//TODO
|
||||
}
|
||||
|
||||
if let Some((prev_meta, prev_rule, prev_rules_iter)) = rules_stack.pop() {
|
||||
rules_sorted.push(prev_rule);
|
||||
//write!(&mut script, "{prev_rule}").unwrap();
|
||||
rules_iter = prev_rules_iter;
|
||||
meta = prev_meta;
|
||||
} else {
|
||||
|
|
@ -1047,9 +1070,48 @@ pub fn import_spamassassin(path: PathBuf, extension: String, do_warn: bool, vali
|
|||
}
|
||||
}
|
||||
|
||||
// Generate script
|
||||
let mut script = String::new();
|
||||
let mut rules_iter = rules_sorted.iter();
|
||||
|
||||
while let Some(&rule) = rules_iter.next() {
|
||||
if rule.score() == 0.0 && !tests_linked.contains(&rule.name) {
|
||||
if do_warn {
|
||||
eprintln!("Warning: Test {} is never linked to.", rule.name);
|
||||
}
|
||||
continue;
|
||||
}
|
||||
|
||||
// Calculate forward scores
|
||||
let (score_pos, score_neg) =
|
||||
rules_iter
|
||||
.clone()
|
||||
.fold((0.0, 0.0), |(acc_pos, acc_neg), rule| {
|
||||
let score = rule.score();
|
||||
if score > 0.0 {
|
||||
(acc_pos + score, acc_neg)
|
||||
} else if score < 0.0 {
|
||||
(acc_pos, acc_neg + score)
|
||||
} else {
|
||||
(acc_pos, acc_neg)
|
||||
}
|
||||
});
|
||||
let mut rule = rule.clone();
|
||||
rule.forward_score_neg = score_neg;
|
||||
rule.forward_score_pos = score_pos;
|
||||
|
||||
write!(&mut script, "{rule}").unwrap();
|
||||
}
|
||||
|
||||
fs::write(
|
||||
"/Users/me/code/mail-server/_ignore/script.sieve",
|
||||
script.as_bytes(),
|
||||
)
|
||||
.unwrap();
|
||||
|
||||
for (message, unsupported) in [
|
||||
("commands", unsupported_commands),
|
||||
("plugins", unsupported_plugins),
|
||||
("plugins", unsupported_ifs),
|
||||
] {
|
||||
if !unsupported.is_empty() {
|
||||
eprintln!("Unsupported {}:", message);
|
||||
|
|
@ -1071,3 +1133,158 @@ pub fn import_spamassassin(path: PathBuf, extension: String, do_warn: bool, vali
|
|||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Display for Rule {
|
||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||
// Add comment
|
||||
self.description
|
||||
.get("en")
|
||||
.map(|v| {
|
||||
writeln!(f, "# {v} (rank {})", self.priority).unwrap();
|
||||
})
|
||||
.unwrap_or_else(|| writeln!(f, "# {} (rank {})", self.name, self.priority).unwrap());
|
||||
|
||||
match &self.t {
|
||||
RuleType::Header {
|
||||
matches,
|
||||
header: header @ (Header::All | Header::AllExternal),
|
||||
if_unset,
|
||||
pattern,
|
||||
part,
|
||||
} => {
|
||||
write!(
|
||||
f,
|
||||
"if vnd.stalwart.eval(\"match_all_headers\", \"{}\", {:?})",
|
||||
if header == &Header::All {
|
||||
"all"
|
||||
} else {
|
||||
"all-external"
|
||||
},
|
||||
pattern
|
||||
)?;
|
||||
}
|
||||
RuleType::Header {
|
||||
matches,
|
||||
header,
|
||||
if_unset,
|
||||
pattern,
|
||||
part,
|
||||
} => {
|
||||
f.write_str("if ")?;
|
||||
let cmd = if matches!(header, Header::EnvelopeFrom) {
|
||||
"envelope"
|
||||
} else if part.contains(&HeaderPart::Addr) || part.contains(&HeaderPart::Name) {
|
||||
"address"
|
||||
} else {
|
||||
"header"
|
||||
};
|
||||
match matches {
|
||||
HeaderMatches::Matches => write!(f, "{cmd} :regex ")?,
|
||||
HeaderMatches::NotMatches => write!(f, "not {cmd} :regex ")?,
|
||||
HeaderMatches::Exists => write!(f, "{cmd} :contains ")?,
|
||||
}
|
||||
for part in part {
|
||||
match part {
|
||||
HeaderPart::Name => f.write_str(":name ")?,
|
||||
HeaderPart::Addr => f.write_str(":all ")?,
|
||||
HeaderPart::Raw => f.write_str(":raw ")?,
|
||||
}
|
||||
}
|
||||
match header {
|
||||
Header::MessageId => f.write_str("[\"Message-Id\",\"Resent-Message-Id\",\"X-Message-Id\",\"X-Original-Message-ID\"]")?,
|
||||
Header::ToCc => f.write_str("[\"To\",\"Cc\"]")?,
|
||||
Header::Name (name) => write!(f, "{:?}", name)?,
|
||||
Header::EnvelopeFrom => f.write_str("\"from\"")?,
|
||||
Header::All |
|
||||
Header::AllExternal => unreachable!(),
|
||||
}
|
||||
|
||||
write!(f, " {:?}", pattern)?;
|
||||
}
|
||||
RuleType::Body { pattern, raw } => {
|
||||
if *raw {
|
||||
write!(f, "if body :raw :regex {pattern:?}")?;
|
||||
} else if !self.flags.contains(&TestFlag::NoSubject) {
|
||||
write!(f, "if body :subject :regex {pattern:?}")?;
|
||||
} else {
|
||||
write!(f, "if body :regex {pattern:?}")?;
|
||||
}
|
||||
}
|
||||
RuleType::Full { pattern } => {
|
||||
write!(f, "if vnd.stalwart.eval(\"match_full\", {:?})", pattern)?;
|
||||
}
|
||||
RuleType::Uri { pattern } => {
|
||||
write!(f, "if vnd.stalwart.eval(\"match_uri\", {:?})", pattern)?;
|
||||
}
|
||||
RuleType::Eval { function, params } => {
|
||||
write!(f, "if vnd.stalwart.eval({function:?}")?;
|
||||
for param in params {
|
||||
write!(f, ", {param:?}")?;
|
||||
}
|
||||
f.write_str(")")?;
|
||||
}
|
||||
RuleType::Meta { expr } => {
|
||||
expr.fmt(f)?;
|
||||
}
|
||||
RuleType::None => {
|
||||
f.write_str("if false")?;
|
||||
}
|
||||
}
|
||||
|
||||
f.write_str(" {\n\tset \"")?;
|
||||
f.write_str(&self.name)?;
|
||||
f.write_str("\" \"1\";\n")?;
|
||||
let score = self.score();
|
||||
|
||||
if score != 0.0 {
|
||||
f.write_str("\tset \"score\" \"${score")?;
|
||||
if score > 0.0 {
|
||||
f.write_str(" + ")?;
|
||||
score.fmt(f)?;
|
||||
} else {
|
||||
f.write_str(" - ")?;
|
||||
(-score).fmt(f)?;
|
||||
}
|
||||
f.write_str("}\";\n\t")?;
|
||||
|
||||
if score > 0.0 {
|
||||
if self.forward_score_neg != 0.0 {
|
||||
write!(
|
||||
f,
|
||||
concat!(
|
||||
"if allof(string :value \"ge\" :comparator ",
|
||||
"\"i;ascii-numeric\" \"${{score}}\" \"${{spam_score}}\", ",
|
||||
"string :value \"ge\" :comparator ",
|
||||
"\"i;ascii-numeric\" \"${{score - {:.4}}}\" \"${{spam_score}}\")"
|
||||
),
|
||||
-self.forward_score_neg
|
||||
)?;
|
||||
} else {
|
||||
f.write_str(concat!(
|
||||
"if string :value \"ge\" :comparator ",
|
||||
"\"i;ascii-numeric\" \"${score}\" \"${spam_score}\""
|
||||
))?;
|
||||
}
|
||||
} else if self.forward_score_pos != 0.0 {
|
||||
write!(
|
||||
f,
|
||||
concat!(
|
||||
"if allof(string :value \"lt\" :comparator ",
|
||||
"\"i;ascii-numeric\" \"${{score}}\" \"${{spam_score}}\", ",
|
||||
"string :value \"lt\" :comparator ",
|
||||
"\"i;ascii-numeric\" \"${{score + {:.4}}}\" \"${{spam_score}}\")"
|
||||
),
|
||||
self.forward_score_pos
|
||||
)?;
|
||||
} else {
|
||||
f.write_str(concat!(
|
||||
"if string :value \"lt\" :comparator ",
|
||||
"\"i;ascii-numeric\" \"${score}\" \"${spam_score}\""
|
||||
))?;
|
||||
}
|
||||
f.write_str(" {\n\t\treturn;\n\t}\n")?;
|
||||
}
|
||||
|
||||
f.write_str("}\n\n")
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -5,7 +5,7 @@ authors = ["Stalwart Labs Ltd. <hello@stalw.art>"]
|
|||
license = "AGPL-3.0-only"
|
||||
repository = "https://github.com/stalwartlabs/cli"
|
||||
homepage = "https://github.com/stalwartlabs/cli"
|
||||
version = "0.3.4"
|
||||
version = "0.3.5"
|
||||
edition = "2021"
|
||||
readme = "README.md"
|
||||
resolver = "2"
|
||||
|
|
|
|||
|
|
@ -1,6 +1,6 @@
|
|||
[package]
|
||||
name = "imap"
|
||||
version = "0.3.4"
|
||||
version = "0.3.5"
|
||||
edition = "2021"
|
||||
resolver = "2"
|
||||
|
||||
|
|
|
|||
|
|
@ -5,7 +5,7 @@ authors = ["Stalwart Labs Ltd. <hello@stalw.art>"]
|
|||
license = "AGPL-3.0-only"
|
||||
repository = "https://github.com/stalwartlabs/mail-server"
|
||||
homepage = "https://github.com/stalwartlabs/mail-server"
|
||||
version = "0.3.4"
|
||||
version = "0.3.5"
|
||||
edition = "2021"
|
||||
readme = "README.md"
|
||||
resolver = "2"
|
||||
|
|
|
|||
|
|
@ -1,6 +1,6 @@
|
|||
[package]
|
||||
name = "jmap"
|
||||
version = "0.3.4"
|
||||
version = "0.3.5"
|
||||
edition = "2021"
|
||||
resolver = "2"
|
||||
|
||||
|
|
|
|||
|
|
@ -7,7 +7,7 @@ homepage = "https://stalw.art"
|
|||
keywords = ["imap", "jmap", "smtp", "email", "mail", "server"]
|
||||
categories = ["email"]
|
||||
license = "AGPL-3.0-only"
|
||||
version = "0.3.4"
|
||||
version = "0.3.5"
|
||||
edition = "2021"
|
||||
resolver = "2"
|
||||
|
||||
|
|
|
|||
|
|
@ -7,7 +7,7 @@ homepage = "https://stalw.art/smtp"
|
|||
keywords = ["smtp", "email", "mail", "server"]
|
||||
categories = ["email"]
|
||||
license = "AGPL-3.0-only"
|
||||
version = "0.3.4"
|
||||
version = "0.3.5"
|
||||
edition = "2021"
|
||||
resolver = "2"
|
||||
|
||||
|
|
|
|||
|
|
@ -1,6 +1,6 @@
|
|||
[package]
|
||||
name = "utils"
|
||||
version = "0.3.4"
|
||||
version = "0.3.5"
|
||||
edition = "2021"
|
||||
resolver = "2"
|
||||
|
||||
|
|
|
|||
|
|
@ -37,7 +37,9 @@ timeout = "30s"
|
|||
directory = "__DIRECTORY__"
|
||||
|
||||
[jmap.http]
|
||||
#headers = ["Access-Control-Allow-Origin: *", "Access-Control-Allow-Methods: POST, GET"]
|
||||
#headers = ["Access-Control-Allow-Origin: *",
|
||||
# "Access-Control-Allow-Methods: POST, GET, HEAD, OPTIONS",
|
||||
# "Access-Control-Allow-Headers: *"]
|
||||
|
||||
[jmap.encryption]
|
||||
enable = true
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue