Compare commits
35 Commits
Author | SHA1 | Date |
---|---|---|
Quentin | 0dcf69f180 | |
Quentin | d92ae5220c | |
Quentin | 1ea3de3099 | |
Quentin | 0b122582e8 | |
Quentin | ab03c7a160 | |
Quentin | 2a084df300 | |
Quentin | 02a8537556 | |
Quentin | a579382042 | |
Quentin | 38a8c7de2a | |
Quentin | 9b26e251e3 | |
Quentin | 2adf73dd8e | |
Quentin | 3f204b102a | |
Quentin | 4d501b6947 | |
Quentin | de5717a020 | |
Quentin | 64b474f682 | |
Quentin | 28b1f4f14d | |
Quentin | 4aa31ba8b5 | |
Quentin | 0b20d726bb | |
Quentin | 0bb7cdf696 | |
Quentin | d50b1dc178 | |
Quentin | 9377ca3ef4 | |
Quentin | 25e716a17f | |
Quentin | e778bebfd3 | |
Quentin | ede836fc80 | |
Quentin | 3dfe914fda | |
Quentin | 9954cea30f | |
Quentin | 3b675ac357 | |
Quentin | 0e3cfe536f | |
Quentin | 599480c3d3 | |
Quentin | 59f4bdf9d0 | |
Quentin | 678c5bacc6 | |
Quentin | 22f0eb901a | |
Quentin | c27919a757 | |
Quentin | 1d6344363a | |
Quentin | 93c0aa4b3a |
|
@ -28,13 +28,15 @@ checksum = "f26201604c87b1e01bd3d98f8d5d9a8fcbb815e8cedb41ffccbeb4bf593a35fe"
|
|||
|
||||
[[package]]
|
||||
name = "aerogramme"
|
||||
version = "0.2.0"
|
||||
version = "0.2.2"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"argon2",
|
||||
"async-trait",
|
||||
"aws-config",
|
||||
"aws-sdk-s3",
|
||||
"aws-smithy-runtime",
|
||||
"aws-smithy-runtime-api",
|
||||
"backtrace",
|
||||
"base64 0.21.7",
|
||||
"chrono",
|
||||
|
@ -44,7 +46,8 @@ dependencies = [
|
|||
"eml-codec",
|
||||
"futures",
|
||||
"hex",
|
||||
"hyper-rustls",
|
||||
"hyper-rustls 0.26.0",
|
||||
"hyper-util",
|
||||
"im",
|
||||
"imap-codec",
|
||||
"imap-flow",
|
||||
|
@ -455,28 +458,27 @@ checksum = "d468802bab17cbc0cc575e9b053f41e72aa36bfa6b7f55e3529ffa43161b97fa"
|
|||
|
||||
[[package]]
|
||||
name = "aws-config"
|
||||
version = "1.1.2"
|
||||
version = "1.1.6"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "7e64b72d4bdbb41a73d27709c65a25b6e4bfc8321bf70fa3a8b19ce7d4eb81b0"
|
||||
checksum = "3182c19847238b50b62ae0383a6dbfc14514e552eb5e307e1ea83ccf5840b8a6"
|
||||
dependencies = [
|
||||
"aws-credential-types",
|
||||
"aws-http",
|
||||
"aws-runtime",
|
||||
"aws-sdk-sso",
|
||||
"aws-sdk-ssooidc",
|
||||
"aws-sdk-sts",
|
||||
"aws-smithy-async",
|
||||
"aws-smithy-http 0.60.3",
|
||||
"aws-smithy-http",
|
||||
"aws-smithy-json",
|
||||
"aws-smithy-runtime",
|
||||
"aws-smithy-runtime-api",
|
||||
"aws-smithy-types 1.1.3",
|
||||
"aws-smithy-types",
|
||||
"aws-types",
|
||||
"bytes",
|
||||
"fastrand 2.0.1",
|
||||
"hex",
|
||||
"http",
|
||||
"hyper",
|
||||
"http 0.2.11",
|
||||
"hyper 0.14.28",
|
||||
"ring 0.17.7",
|
||||
"time",
|
||||
"tokio",
|
||||
|
@ -486,77 +488,84 @@ dependencies = [
|
|||
|
||||
[[package]]
|
||||
name = "aws-credential-types"
|
||||
version = "1.1.2"
|
||||
version = "1.1.6"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "4a7cb3510b95492bd9014b60e2e3bee3e48bc516e220316f8e6b60df18b47331"
|
||||
checksum = "e5635d8707f265c773282a22abe1ecd4fbe96a8eb2f0f14c0796f8016f11a41a"
|
||||
dependencies = [
|
||||
"aws-smithy-async",
|
||||
"aws-smithy-runtime-api",
|
||||
"aws-smithy-types 1.1.3",
|
||||
"aws-smithy-types",
|
||||
"zeroize",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "aws-http"
|
||||
version = "0.60.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "a95d41abe4e941399fdb4bc2f54713eac3c839d98151875948bb24e66ab658f2"
|
||||
dependencies = [
|
||||
"aws-smithy-runtime-api",
|
||||
"aws-smithy-types 1.1.3",
|
||||
"aws-types",
|
||||
"bytes",
|
||||
"http",
|
||||
"http-body",
|
||||
"pin-project-lite 0.2.13",
|
||||
"tracing",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "aws-runtime"
|
||||
version = "1.1.2"
|
||||
version = "1.1.6"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "233cca219c6705d525ace011d6f9bc51aaf32fce5b4c41661d2d7ff22d9b4d49"
|
||||
checksum = "6f82b9ae2adfd9d6582440d0eeb394c07f74d21b4c0cc72bdb73735c9e1a9c0e"
|
||||
dependencies = [
|
||||
"aws-credential-types",
|
||||
"aws-http",
|
||||
"aws-sigv4 1.1.2",
|
||||
"aws-sigv4",
|
||||
"aws-smithy-async",
|
||||
"aws-smithy-eventstream",
|
||||
"aws-smithy-http 0.60.3",
|
||||
"aws-smithy-http",
|
||||
"aws-smithy-runtime-api",
|
||||
"aws-smithy-types 1.1.3",
|
||||
"aws-smithy-types",
|
||||
"aws-types",
|
||||
"bytes",
|
||||
"fastrand 2.0.1",
|
||||
"http",
|
||||
"http 0.2.11",
|
||||
"http-body 0.4.6",
|
||||
"percent-encoding",
|
||||
"pin-project-lite 0.2.13",
|
||||
"tracing",
|
||||
"uuid",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "aws-sdk-s3"
|
||||
version = "1.12.0"
|
||||
name = "aws-sdk-config"
|
||||
version = "1.15.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "634fbe5b6591ee2e281cd2ba8641e9bd752dbf5bf338924d6ad4bd5a3304fe31"
|
||||
checksum = "0cb71960e3e197c3f512f3bf0f47f444acd708db59733416107ec2ff161ff5c4"
|
||||
dependencies = [
|
||||
"aws-credential-types",
|
||||
"aws-http",
|
||||
"aws-runtime",
|
||||
"aws-sigv4 1.1.2",
|
||||
"aws-smithy-async",
|
||||
"aws-smithy-checksums",
|
||||
"aws-smithy-eventstream",
|
||||
"aws-smithy-http 0.60.3",
|
||||
"aws-smithy-http",
|
||||
"aws-smithy-json",
|
||||
"aws-smithy-runtime",
|
||||
"aws-smithy-runtime-api",
|
||||
"aws-smithy-types 1.1.3",
|
||||
"aws-smithy-types",
|
||||
"aws-types",
|
||||
"bytes",
|
||||
"http 0.2.11",
|
||||
"once_cell",
|
||||
"regex-lite",
|
||||
"tracing",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "aws-sdk-s3"
|
||||
version = "1.16.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "5076637347e7d0218e61facae853110682ae58efabd2f4e2a9e530c203d5fa7b"
|
||||
dependencies = [
|
||||
"aws-credential-types",
|
||||
"aws-runtime",
|
||||
"aws-sigv4",
|
||||
"aws-smithy-async",
|
||||
"aws-smithy-checksums",
|
||||
"aws-smithy-eventstream",
|
||||
"aws-smithy-http",
|
||||
"aws-smithy-json",
|
||||
"aws-smithy-runtime",
|
||||
"aws-smithy-runtime-api",
|
||||
"aws-smithy-types",
|
||||
"aws-smithy-xml",
|
||||
"aws-types",
|
||||
"bytes",
|
||||
"http",
|
||||
"http-body",
|
||||
"http 0.2.11",
|
||||
"http-body 0.4.6",
|
||||
"once_cell",
|
||||
"percent-encoding",
|
||||
"regex-lite",
|
||||
|
@ -566,22 +575,21 @@ dependencies = [
|
|||
|
||||
[[package]]
|
||||
name = "aws-sdk-sso"
|
||||
version = "1.10.0"
|
||||
version = "1.14.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "ee41005e0f3a19ae749c7953d9e1f1ef8d2183f76f64966e346fa41c1ba0ed44"
|
||||
checksum = "ca7e8097448832fcd22faf6bb227e97d76b40e354509d1307653a885811c7151"
|
||||
dependencies = [
|
||||
"aws-credential-types",
|
||||
"aws-http",
|
||||
"aws-runtime",
|
||||
"aws-smithy-async",
|
||||
"aws-smithy-http 0.60.3",
|
||||
"aws-smithy-http",
|
||||
"aws-smithy-json",
|
||||
"aws-smithy-runtime",
|
||||
"aws-smithy-runtime-api",
|
||||
"aws-smithy-types 1.1.3",
|
||||
"aws-smithy-types",
|
||||
"aws-types",
|
||||
"bytes",
|
||||
"http",
|
||||
"http 0.2.11",
|
||||
"once_cell",
|
||||
"regex-lite",
|
||||
"tracing",
|
||||
|
@ -589,22 +597,21 @@ dependencies = [
|
|||
|
||||
[[package]]
|
||||
name = "aws-sdk-ssooidc"
|
||||
version = "1.10.0"
|
||||
version = "1.14.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "fa08168f8a27505e7b90f922c32a489feb1f2133878981a15138bebc849ac09c"
|
||||
checksum = "a75073590e23d63044606771afae309fada8eb10ded54a1ce4598347221d3fef"
|
||||
dependencies = [
|
||||
"aws-credential-types",
|
||||
"aws-http",
|
||||
"aws-runtime",
|
||||
"aws-smithy-async",
|
||||
"aws-smithy-http 0.60.3",
|
||||
"aws-smithy-http",
|
||||
"aws-smithy-json",
|
||||
"aws-smithy-runtime",
|
||||
"aws-smithy-runtime-api",
|
||||
"aws-smithy-types 1.1.3",
|
||||
"aws-smithy-types",
|
||||
"aws-types",
|
||||
"bytes",
|
||||
"http",
|
||||
"http 0.2.11",
|
||||
"once_cell",
|
||||
"regex-lite",
|
||||
"tracing",
|
||||
|
@ -612,23 +619,22 @@ dependencies = [
|
|||
|
||||
[[package]]
|
||||
name = "aws-sdk-sts"
|
||||
version = "1.10.0"
|
||||
version = "1.14.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "29102eff04d50ef70f11a48823db33e33c6cc5f027bfb6ff4864efbd5f1f66f3"
|
||||
checksum = "650e4aaae41547151dea4d8142f7ffcc8ab8ba76d5dccc8933936ef2102c3356"
|
||||
dependencies = [
|
||||
"aws-credential-types",
|
||||
"aws-http",
|
||||
"aws-runtime",
|
||||
"aws-smithy-async",
|
||||
"aws-smithy-http 0.60.3",
|
||||
"aws-smithy-http",
|
||||
"aws-smithy-json",
|
||||
"aws-smithy-query",
|
||||
"aws-smithy-runtime",
|
||||
"aws-smithy-runtime-api",
|
||||
"aws-smithy-types 1.1.3",
|
||||
"aws-smithy-types",
|
||||
"aws-smithy-xml",
|
||||
"aws-types",
|
||||
"http",
|
||||
"http 0.2.11",
|
||||
"once_cell",
|
||||
"regex-lite",
|
||||
"tracing",
|
||||
|
@ -636,40 +642,22 @@ dependencies = [
|
|||
|
||||
[[package]]
|
||||
name = "aws-sigv4"
|
||||
version = "0.55.3"
|
||||
version = "1.1.6"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "9d2ce6f507be68e968a33485ced670111d1cbad161ddbbab1e313c03d37d8f4c"
|
||||
dependencies = [
|
||||
"aws-smithy-http 0.55.3",
|
||||
"form_urlencoded",
|
||||
"hex",
|
||||
"hmac",
|
||||
"http",
|
||||
"once_cell",
|
||||
"percent-encoding",
|
||||
"regex",
|
||||
"sha2",
|
||||
"time",
|
||||
"tracing",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "aws-sigv4"
|
||||
version = "1.1.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "b92384b39aedb258aa734fe0e7b2ffcd13f33e68227251a72cd2635e0acc8f1a"
|
||||
checksum = "404c64a104188ac70dd1684718765cb5559795458e446480e41984e68e57d888"
|
||||
dependencies = [
|
||||
"aws-credential-types",
|
||||
"aws-smithy-eventstream",
|
||||
"aws-smithy-http 0.60.3",
|
||||
"aws-smithy-http",
|
||||
"aws-smithy-runtime-api",
|
||||
"aws-smithy-types 1.1.3",
|
||||
"aws-smithy-types",
|
||||
"bytes",
|
||||
"crypto-bigint 0.5.5",
|
||||
"form_urlencoded",
|
||||
"hex",
|
||||
"hmac",
|
||||
"http",
|
||||
"http 0.2.11",
|
||||
"http 1.0.0",
|
||||
"once_cell",
|
||||
"p256",
|
||||
"percent-encoding",
|
||||
|
@ -683,9 +671,9 @@ dependencies = [
|
|||
|
||||
[[package]]
|
||||
name = "aws-smithy-async"
|
||||
version = "1.1.3"
|
||||
version = "1.1.7"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "2eac0bb78e9e2765699999a02d7bfb4e6ad8f13e0962ebb9f5202b1d8cd76006"
|
||||
checksum = "fcf7f09a27286d84315dfb9346208abb3b0973a692454ae6d0bc8d803fcce3b4"
|
||||
dependencies = [
|
||||
"futures-util",
|
||||
"pin-project-lite 0.2.13",
|
||||
|
@ -694,18 +682,18 @@ dependencies = [
|
|||
|
||||
[[package]]
|
||||
name = "aws-smithy-checksums"
|
||||
version = "0.60.3"
|
||||
version = "0.60.6"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "535a2d5f1e459bc7709580a77152c8d493982db083236c2b1d1c51dc6217e8a3"
|
||||
checksum = "0fd4b66f2a8e7c84d7e97bda2666273d41d2a2e25302605bcf906b7b2661ae5e"
|
||||
dependencies = [
|
||||
"aws-smithy-http 0.60.3",
|
||||
"aws-smithy-types 1.1.3",
|
||||
"aws-smithy-http",
|
||||
"aws-smithy-types",
|
||||
"bytes",
|
||||
"crc32c",
|
||||
"crc32fast",
|
||||
"hex",
|
||||
"http",
|
||||
"http-body",
|
||||
"http 0.2.11",
|
||||
"http-body 0.4.6",
|
||||
"md-5",
|
||||
"pin-project-lite 0.2.13",
|
||||
"sha1",
|
||||
|
@ -715,49 +703,29 @@ dependencies = [
|
|||
|
||||
[[package]]
|
||||
name = "aws-smithy-eventstream"
|
||||
version = "0.60.3"
|
||||
version = "0.60.4"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "682371561562d08ab437766903c6bc28f4f95d7ab2ecfb389bda7849dd98aefe"
|
||||
checksum = "e6363078f927f612b970edf9d1903ef5cef9a64d1e8423525ebb1f0a1633c858"
|
||||
dependencies = [
|
||||
"aws-smithy-types 1.1.3",
|
||||
"aws-smithy-types",
|
||||
"bytes",
|
||||
"crc32fast",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "aws-smithy-http"
|
||||
version = "0.55.3"
|
||||
version = "0.60.6"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "2b3b693869133551f135e1f2c77cb0b8277d9e3e17feaf2213f735857c4f0d28"
|
||||
dependencies = [
|
||||
"aws-smithy-types 0.55.3",
|
||||
"bytes",
|
||||
"bytes-utils",
|
||||
"futures-core",
|
||||
"http",
|
||||
"http-body",
|
||||
"hyper",
|
||||
"once_cell",
|
||||
"percent-encoding",
|
||||
"pin-project-lite 0.2.13",
|
||||
"pin-utils",
|
||||
"tracing",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "aws-smithy-http"
|
||||
version = "0.60.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "365ca49744b2bda2f1e2dc03b856da3fa5a28ca5b0a41e41d7ff5305a8fae190"
|
||||
checksum = "b6ca214a6a26f1b7ebd63aa8d4f5e2194095643023f9608edf99a58247b9d80d"
|
||||
dependencies = [
|
||||
"aws-smithy-eventstream",
|
||||
"aws-smithy-runtime-api",
|
||||
"aws-smithy-types 1.1.3",
|
||||
"aws-smithy-types",
|
||||
"bytes",
|
||||
"bytes-utils",
|
||||
"futures-core",
|
||||
"http",
|
||||
"http-body",
|
||||
"http 0.2.11",
|
||||
"http-body 0.4.6",
|
||||
"once_cell",
|
||||
"percent-encoding",
|
||||
"pin-project-lite 0.2.13",
|
||||
|
@ -767,40 +735,40 @@ dependencies = [
|
|||
|
||||
[[package]]
|
||||
name = "aws-smithy-json"
|
||||
version = "0.60.3"
|
||||
version = "0.60.6"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "733ccdb727ac63370836aa3b3c483d75ad2ef7bc6507db3efe1d01e8d2e50367"
|
||||
checksum = "1af80ecf3057fb25fe38d1687e94c4601a7817c6a1e87c1b0635f7ecb644ace5"
|
||||
dependencies = [
|
||||
"aws-smithy-types 1.1.3",
|
||||
"aws-smithy-types",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "aws-smithy-query"
|
||||
version = "0.60.3"
|
||||
version = "0.60.6"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "aff02ae2ee7968bbce2983ffb5ce529d24f4848532300f398347bde8c2196974"
|
||||
checksum = "eb27084f72ea5fc20033efe180618677ff4a2f474b53d84695cfe310a6526cbc"
|
||||
dependencies = [
|
||||
"aws-smithy-types 1.1.3",
|
||||
"aws-smithy-types",
|
||||
"urlencoding",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "aws-smithy-runtime"
|
||||
version = "1.1.3"
|
||||
version = "1.1.7"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "6ab9cb6fee50680af8ceaa293ae79eba32095ca117161cb323f9ee30dd87d139"
|
||||
checksum = "fbb5fca54a532a36ff927fbd7407a7c8eb9c3b4faf72792ba2965ea2cad8ed55"
|
||||
dependencies = [
|
||||
"aws-smithy-async",
|
||||
"aws-smithy-http 0.60.3",
|
||||
"aws-smithy-http",
|
||||
"aws-smithy-runtime-api",
|
||||
"aws-smithy-types 1.1.3",
|
||||
"aws-smithy-types",
|
||||
"bytes",
|
||||
"fastrand 2.0.1",
|
||||
"h2",
|
||||
"http",
|
||||
"http-body",
|
||||
"hyper",
|
||||
"hyper-rustls",
|
||||
"h2 0.3.24",
|
||||
"http 0.2.11",
|
||||
"http-body 0.4.6",
|
||||
"hyper 0.14.28",
|
||||
"hyper-rustls 0.24.2",
|
||||
"once_cell",
|
||||
"pin-project-lite 0.2.13",
|
||||
"pin-utils",
|
||||
|
@ -811,14 +779,15 @@ dependencies = [
|
|||
|
||||
[[package]]
|
||||
name = "aws-smithy-runtime-api"
|
||||
version = "1.1.3"
|
||||
version = "1.1.7"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "02ca2da7619517310bfead6d18abcdde90f1439224d887d608503cfacff46dff"
|
||||
checksum = "22389cb6f7cac64f266fb9f137745a9349ced7b47e0d2ba503e9e40ede4f7060"
|
||||
dependencies = [
|
||||
"aws-smithy-async",
|
||||
"aws-smithy-types 1.1.3",
|
||||
"aws-smithy-types",
|
||||
"bytes",
|
||||
"http",
|
||||
"http 0.2.11",
|
||||
"http 1.0.0",
|
||||
"pin-project-lite 0.2.13",
|
||||
"tokio",
|
||||
"tracing",
|
||||
|
@ -827,29 +796,16 @@ dependencies = [
|
|||
|
||||
[[package]]
|
||||
name = "aws-smithy-types"
|
||||
version = "0.55.3"
|
||||
version = "1.1.7"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "16a3d0bf4f324f4ef9793b86a1701d9700fbcdbd12a846da45eed104c634c6e8"
|
||||
dependencies = [
|
||||
"base64-simd",
|
||||
"itoa",
|
||||
"num-integer",
|
||||
"ryu",
|
||||
"time",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "aws-smithy-types"
|
||||
version = "1.1.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "5d4bb944488536cd2fef43212d829bc7e9a8bfc4afa079d21170441e7be8d2d0"
|
||||
checksum = "f081da5481210523d44ffd83d9f0740320050054006c719eae0232d411f024d3"
|
||||
dependencies = [
|
||||
"base64-simd",
|
||||
"bytes",
|
||||
"bytes-utils",
|
||||
"futures-core",
|
||||
"http",
|
||||
"http-body",
|
||||
"http 0.2.11",
|
||||
"http-body 0.4.6",
|
||||
"itoa",
|
||||
"num-integer",
|
||||
"pin-project-lite 0.2.13",
|
||||
|
@ -863,24 +819,24 @@ dependencies = [
|
|||
|
||||
[[package]]
|
||||
name = "aws-smithy-xml"
|
||||
version = "0.60.3"
|
||||
version = "0.60.6"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "ef796feaf894d7fd03869235237aeffe73ed1b29a3927cceeee2eecadf876eba"
|
||||
checksum = "0fccd8f595d0ca839f9f2548e66b99514a85f92feb4c01cf2868d93eb4888a42"
|
||||
dependencies = [
|
||||
"xmlparser",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "aws-types"
|
||||
version = "1.1.2"
|
||||
version = "1.1.6"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "8549aa62c5b7db5c57ab915200ee214b4f5d8f19b29a4a8fa0b3ad3bca1380e3"
|
||||
checksum = "8fbb5d48aae496f628e7aa2e41991dd4074f606d9e3ade1ce1059f293d40f9a2"
|
||||
dependencies = [
|
||||
"aws-credential-types",
|
||||
"aws-smithy-async",
|
||||
"aws-smithy-runtime-api",
|
||||
"aws-smithy-types 1.1.3",
|
||||
"http",
|
||||
"aws-smithy-types",
|
||||
"http 0.2.11",
|
||||
"rustc_version",
|
||||
"tracing",
|
||||
]
|
||||
|
@ -896,9 +852,9 @@ dependencies = [
|
|||
"bitflags 1.3.2",
|
||||
"bytes",
|
||||
"futures-util",
|
||||
"http",
|
||||
"http-body",
|
||||
"hyper",
|
||||
"http 0.2.11",
|
||||
"http-body 0.4.6",
|
||||
"hyper 0.14.28",
|
||||
"itoa",
|
||||
"matchit",
|
||||
"memchr",
|
||||
|
@ -922,8 +878,8 @@ dependencies = [
|
|||
"async-trait",
|
||||
"bytes",
|
||||
"futures-util",
|
||||
"http",
|
||||
"http-body",
|
||||
"http 0.2.11",
|
||||
"http-body 0.4.6",
|
||||
"mime",
|
||||
"rustversion",
|
||||
"tower-layer",
|
||||
|
@ -1242,9 +1198,9 @@ dependencies = [
|
|||
|
||||
[[package]]
|
||||
name = "crc32c"
|
||||
version = "0.6.4"
|
||||
version = "0.6.5"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "d8f48d60e5b4d2c53d5c2b1d8a58c849a70ae5e5509b08a48d047e3b65714a74"
|
||||
checksum = "89254598aa9b9fa608de44b3ae54c810f0f06d755e24c50177f1f8f31ff50ce2"
|
||||
dependencies = [
|
||||
"rustc_version",
|
||||
]
|
||||
|
@ -1437,7 +1393,8 @@ dependencies = [
|
|||
[[package]]
|
||||
name = "eml-codec"
|
||||
version = "0.1.2"
|
||||
source = "git+https://git.deuxfleurs.fr/Deuxfleurs/eml-codec.git?branch=main#a7bd3c475a58e42b86c163ec075ce01ddae7e60a"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "d4499124d87abce26a57ef96ece800fa8babc38fbedd81c607c340ae83d46d2e"
|
||||
dependencies = [
|
||||
"base64 0.21.7",
|
||||
"chrono",
|
||||
|
@ -1742,7 +1699,26 @@ dependencies = [
|
|||
"futures-core",
|
||||
"futures-sink",
|
||||
"futures-util",
|
||||
"http",
|
||||
"http 0.2.11",
|
||||
"indexmap 2.1.0",
|
||||
"slab",
|
||||
"tokio",
|
||||
"tokio-util",
|
||||
"tracing",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "h2"
|
||||
version = "0.4.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "31d030e59af851932b72ceebadf4a2b5986dba4c3b99dd2493f8273a0f151943"
|
||||
dependencies = [
|
||||
"bytes",
|
||||
"fnv",
|
||||
"futures-core",
|
||||
"futures-sink",
|
||||
"futures-util",
|
||||
"http 1.0.0",
|
||||
"indexmap 2.1.0",
|
||||
"slab",
|
||||
"tokio",
|
||||
|
@ -1822,6 +1798,17 @@ dependencies = [
|
|||
"itoa",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "http"
|
||||
version = "1.0.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "b32afd38673a8016f7c9ae69e5af41a58f81b1d31689040f2f1959594ce194ea"
|
||||
dependencies = [
|
||||
"bytes",
|
||||
"fnv",
|
||||
"itoa",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "http-body"
|
||||
version = "0.4.6"
|
||||
|
@ -1829,7 +1816,30 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
|
|||
checksum = "7ceab25649e9960c0311ea418d17bee82c0dcec1bd053b5f9a66e265a693bed2"
|
||||
dependencies = [
|
||||
"bytes",
|
||||
"http",
|
||||
"http 0.2.11",
|
||||
"pin-project-lite 0.2.13",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "http-body"
|
||||
version = "1.0.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "1cac85db508abc24a2e48553ba12a996e87244a0395ce011e62b37158745d643"
|
||||
dependencies = [
|
||||
"bytes",
|
||||
"http 1.0.0",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "http-body-util"
|
||||
version = "0.1.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "41cb79eb393015dadd30fc252023adb0b2400a0caee0fa2a077e6e21a551e840"
|
||||
dependencies = [
|
||||
"bytes",
|
||||
"futures-util",
|
||||
"http 1.0.0",
|
||||
"http-body 1.0.0",
|
||||
"pin-project-lite 0.2.13",
|
||||
]
|
||||
|
||||
|
@ -1861,9 +1871,9 @@ dependencies = [
|
|||
"futures-channel",
|
||||
"futures-core",
|
||||
"futures-util",
|
||||
"h2",
|
||||
"http",
|
||||
"http-body",
|
||||
"h2 0.3.24",
|
||||
"http 0.2.11",
|
||||
"http-body 0.4.6",
|
||||
"httparse",
|
||||
"httpdate",
|
||||
"itoa",
|
||||
|
@ -1875,6 +1885,27 @@ dependencies = [
|
|||
"want",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "hyper"
|
||||
version = "1.2.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "186548d73ac615b32a73aafe38fb4f56c0d340e110e5a200bcadbaf2e199263a"
|
||||
dependencies = [
|
||||
"bytes",
|
||||
"futures-channel",
|
||||
"futures-util",
|
||||
"h2 0.4.2",
|
||||
"http 1.0.0",
|
||||
"http-body 1.0.0",
|
||||
"httparse",
|
||||
"httpdate",
|
||||
"itoa",
|
||||
"pin-project-lite 0.2.13",
|
||||
"smallvec",
|
||||
"tokio",
|
||||
"want",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "hyper-rustls"
|
||||
version = "0.24.2"
|
||||
|
@ -1882,27 +1913,66 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
|
|||
checksum = "ec3efd23720e2049821a693cbc7e65ea87c72f1c58ff2f9522ff332b1491e590"
|
||||
dependencies = [
|
||||
"futures-util",
|
||||
"http",
|
||||
"hyper",
|
||||
"http 0.2.11",
|
||||
"hyper 0.14.28",
|
||||
"log",
|
||||
"rustls 0.21.10",
|
||||
"rustls-native-certs",
|
||||
"rustls-native-certs 0.6.3",
|
||||
"tokio",
|
||||
"tokio-rustls 0.24.1",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "hyper-rustls"
|
||||
version = "0.26.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "a0bea761b46ae2b24eb4aef630d8d1c398157b6fc29e6350ecf090a0b70c952c"
|
||||
dependencies = [
|
||||
"futures-util",
|
||||
"http 1.0.0",
|
||||
"hyper 1.2.0",
|
||||
"hyper-util",
|
||||
"log",
|
||||
"rustls 0.22.2",
|
||||
"rustls-native-certs 0.7.0",
|
||||
"rustls-pki-types",
|
||||
"tokio",
|
||||
"tokio-rustls 0.25.0",
|
||||
"tower-service",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "hyper-timeout"
|
||||
version = "0.4.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "bbb958482e8c7be4bc3cf272a766a2b0bf1a6755e7a6ae777f017a31d11b13b1"
|
||||
dependencies = [
|
||||
"hyper",
|
||||
"hyper 0.14.28",
|
||||
"pin-project-lite 0.2.13",
|
||||
"tokio",
|
||||
"tokio-io-timeout",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "hyper-util"
|
||||
version = "0.1.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "ca38ef113da30126bbff9cd1705f9273e15d45498615d138b0c20279ac7a76aa"
|
||||
dependencies = [
|
||||
"bytes",
|
||||
"futures-channel",
|
||||
"futures-util",
|
||||
"http 1.0.0",
|
||||
"http-body 1.0.0",
|
||||
"hyper 1.2.0",
|
||||
"pin-project-lite 0.2.13",
|
||||
"socket2 0.5.5",
|
||||
"tokio",
|
||||
"tower",
|
||||
"tower-service",
|
||||
"tracing",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "iana-time-zone"
|
||||
version = "0.1.59"
|
||||
|
@ -1964,7 +2034,7 @@ dependencies = [
|
|||
[[package]]
|
||||
name = "imap-codec"
|
||||
version = "2.0.0"
|
||||
source = "git+https://github.com/superboum/imap-codec?branch=custom/aerogramme#0adcc244282c64cc7874ffa9cd22e4a451ee19f8"
|
||||
source = "git+https://github.com/superboum/imap-codec?branch=custom/aerogramme#d8a5afc03fb771232e94c73af6a05e79dc80bbed"
|
||||
dependencies = [
|
||||
"abnf-core",
|
||||
"base64 0.21.7",
|
||||
|
@ -1979,19 +2049,21 @@ dependencies = [
|
|||
[[package]]
|
||||
name = "imap-flow"
|
||||
version = "0.1.0"
|
||||
source = "git+https://github.com/superboum/imap-flow.git?branch=custom/aerogramme#60ff9e082ccfcd10a042b616d8038a578fa0c8ff"
|
||||
source = "git+https://github.com/duesee/imap-flow.git?branch=main#68c1da5d1c56dbe543d9736de9683259d1d28191"
|
||||
dependencies = [
|
||||
"bounded-static",
|
||||
"bytes",
|
||||
"imap-codec",
|
||||
"imap-types",
|
||||
"thiserror",
|
||||
"tokio",
|
||||
"tracing",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "imap-types"
|
||||
version = "2.0.0"
|
||||
source = "git+https://github.com/superboum/imap-codec?branch=custom/aerogramme#0adcc244282c64cc7874ffa9cd22e4a451ee19f8"
|
||||
source = "git+https://github.com/superboum/imap-codec?branch=custom/aerogramme#d8a5afc03fb771232e94c73af6a05e79dc80bbed"
|
||||
dependencies = [
|
||||
"base64 0.21.7",
|
||||
"bounded-static",
|
||||
|
@ -2075,14 +2147,17 @@ dependencies = [
|
|||
[[package]]
|
||||
name = "k2v-client"
|
||||
version = "0.0.4"
|
||||
source = "git+https://git.deuxfleurs.fr/Deuxfleurs/garage.git?tag=v0.9.0#952c9570c494468643353ee1ae9052b510353665"
|
||||
source = "git+https://git.deuxfleurs.fr/Deuxfleurs/garage.git?branch=k2v/shared_http_client#8b35a946d9f6b31b26b9783acbfab984316051f4"
|
||||
dependencies = [
|
||||
"aws-sigv4 0.55.3",
|
||||
"aws-sdk-config",
|
||||
"aws-sigv4",
|
||||
"base64 0.21.7",
|
||||
"hex",
|
||||
"http",
|
||||
"hyper",
|
||||
"hyper-rustls",
|
||||
"http 1.0.0",
|
||||
"http-body-util",
|
||||
"hyper 1.2.0",
|
||||
"hyper-rustls 0.26.0",
|
||||
"hyper-util",
|
||||
"log",
|
||||
"percent-encoding",
|
||||
"serde",
|
||||
|
@ -2135,7 +2210,7 @@ dependencies = [
|
|||
"percent-encoding",
|
||||
"ring 0.16.20",
|
||||
"rustls 0.20.9",
|
||||
"rustls-native-certs",
|
||||
"rustls-native-certs 0.6.3",
|
||||
"thiserror",
|
||||
"tokio",
|
||||
"tokio-rustls 0.23.4",
|
||||
|
@ -2904,6 +2979,19 @@ dependencies = [
|
|||
"security-framework",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "rustls-native-certs"
|
||||
version = "0.7.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "8f1fb85efa936c42c6d5fc28d2629bb51e4b2f4b8a5211e297d599cc5a093792"
|
||||
dependencies = [
|
||||
"openssl-probe",
|
||||
"rustls-pemfile 2.0.0",
|
||||
"rustls-pki-types",
|
||||
"schannel",
|
||||
"security-framework",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "rustls-pemfile"
|
||||
version = "1.0.4"
|
||||
|
@ -3519,10 +3607,10 @@ dependencies = [
|
|||
"axum",
|
||||
"base64 0.21.7",
|
||||
"bytes",
|
||||
"h2",
|
||||
"http",
|
||||
"http-body",
|
||||
"hyper",
|
||||
"h2 0.3.24",
|
||||
"http 0.2.11",
|
||||
"http-body 0.4.6",
|
||||
"hyper 0.14.28",
|
||||
"hyper-timeout",
|
||||
"percent-encoding",
|
||||
"pin-project",
|
||||
|
@ -3573,6 +3661,7 @@ version = "0.1.40"
|
|||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "c3523ab5a71916ccf420eebdf5521fcef02141234bbc0b8a49f2fdc4544364ef"
|
||||
dependencies = [
|
||||
"log",
|
||||
"pin-project-lite 0.2.13",
|
||||
"tracing-attributes",
|
||||
"tracing-core",
|
||||
|
|
17
Cargo.toml
17
Cargo.toml
|
@ -1,6 +1,6 @@
|
|||
[package]
|
||||
name = "aerogramme"
|
||||
version = "0.2.0"
|
||||
version = "0.2.2"
|
||||
authors = ["Alex Auvolat <alex@adnab.me>", "Quentin Dufour <quentin@dufour.io>"]
|
||||
edition = "2021"
|
||||
license = "EUPL-1.2"
|
||||
|
@ -48,23 +48,26 @@ rand = "0.8.5"
|
|||
rustls = "0.22"
|
||||
rustls-pemfile = "2.0"
|
||||
tokio-rustls = "0.25"
|
||||
hyper-rustls = { version = "0.24", features = ["http2"] }
|
||||
hyper-rustls = { version = "0.26", features = ["http2"] }
|
||||
hyper-util = { version = "0.1", features = ["full"] }
|
||||
rpassword = "7.0"
|
||||
|
||||
# login
|
||||
ldap3 = { version = "0.10", default-features = false, features = ["tls-rustls"] }
|
||||
|
||||
# storage
|
||||
k2v-client = { git = "https://git.deuxfleurs.fr/Deuxfleurs/garage.git", tag = "v0.9.0" }
|
||||
aws-config = { version = "1.1.1", features = ["behavior-version-latest"] }
|
||||
aws-sdk-s3 = "1.9.0"
|
||||
k2v-client = { git = "https://git.deuxfleurs.fr/Deuxfleurs/garage.git", branch = "k2v/shared_http_client" }
|
||||
aws-config = { version = "1", features = ["behavior-version-latest"] }
|
||||
aws-sdk-s3 = "1"
|
||||
aws-smithy-runtime = "1"
|
||||
aws-smithy-runtime-api = "1"
|
||||
|
||||
# email protocols
|
||||
eml-codec = { git = "https://git.deuxfleurs.fr/Deuxfleurs/eml-codec.git", branch = "main" }
|
||||
eml-codec = "0.1.2"
|
||||
smtp-message = { git = "http://github.com/Alexis211/kannader", branch = "feature/lmtp" }
|
||||
smtp-server = { git = "http://github.com/Alexis211/kannader", branch = "feature/lmtp" }
|
||||
imap-codec = { version = "2.0.0", features = ["bounded-static", "ext_condstore_qresync"] }
|
||||
imap-flow = { git = "https://github.com/superboum/imap-flow.git", branch = "custom/aerogramme" }
|
||||
imap-flow = { git = "https://github.com/duesee/imap-flow.git", branch = "main" }
|
||||
thiserror = "1.0.56"
|
||||
|
||||
[dev-dependencies]
|
||||
|
|
|
@ -1,10 +1,30 @@
|
|||
{
|
||||
"nodes": {
|
||||
"albatros": {
|
||||
"inputs": {
|
||||
"flake-utils": "flake-utils",
|
||||
"nixpkgs": "nixpkgs"
|
||||
},
|
||||
"locked": {
|
||||
"lastModified": 1684830446,
|
||||
"narHash": "sha256-jyYwYYNKSe40Y9OirIkeFTvTvqNj0NErh4TNBJmujw4=",
|
||||
"ref": "main",
|
||||
"rev": "fb80c5d6734044ca7718989a3b36503b9463f1b2",
|
||||
"revCount": 81,
|
||||
"type": "git",
|
||||
"url": "https://git.deuxfleurs.fr/Deuxfleurs/albatros.git"
|
||||
},
|
||||
"original": {
|
||||
"ref": "main",
|
||||
"type": "git",
|
||||
"url": "https://git.deuxfleurs.fr/Deuxfleurs/albatros.git"
|
||||
}
|
||||
},
|
||||
"cargo2nix": {
|
||||
"inputs": {
|
||||
"flake-compat": "flake-compat",
|
||||
"flake-utils": "flake-utils",
|
||||
"nixpkgs": "nixpkgs",
|
||||
"flake-utils": "flake-utils_2",
|
||||
"nixpkgs": "nixpkgs_2",
|
||||
"rust-overlay": "rust-overlay"
|
||||
},
|
||||
"locked": {
|
||||
|
@ -24,20 +44,19 @@
|
|||
},
|
||||
"fenix": {
|
||||
"inputs": {
|
||||
"nixpkgs": "nixpkgs_2",
|
||||
"nixpkgs": "nixpkgs_3",
|
||||
"rust-analyzer-src": "rust-analyzer-src"
|
||||
},
|
||||
"locked": {
|
||||
"lastModified": 1688484237,
|
||||
"narHash": "sha256-qFUn2taHGe203wm7Oio4UGFz1sAiq+kitRexY3sQ1CA=",
|
||||
"lastModified": 1708669354,
|
||||
"narHash": "sha256-eGhZLjF59aF9bYdSOleT1BD94qvo1NgMio4vMKBzxgY=",
|
||||
"owner": "nix-community",
|
||||
"repo": "fenix",
|
||||
"rev": "626a9e0a84010728b335f14d3982e11b99af7dc6",
|
||||
"rev": "a0f0f781683e4e93b61beaf1dfee4dd34cf3a092",
|
||||
"type": "github"
|
||||
},
|
||||
"original": {
|
||||
"owner": "nix-community",
|
||||
"ref": "monthly",
|
||||
"repo": "fenix",
|
||||
"type": "github"
|
||||
}
|
||||
|
@ -59,6 +78,24 @@
|
|||
}
|
||||
},
|
||||
"flake-utils": {
|
||||
"inputs": {
|
||||
"systems": "systems"
|
||||
},
|
||||
"locked": {
|
||||
"lastModified": 1681202837,
|
||||
"narHash": "sha256-H+Rh19JDwRtpVPAWp64F+rlEtxUWBAQW28eAi3SRSzg=",
|
||||
"owner": "numtide",
|
||||
"repo": "flake-utils",
|
||||
"rev": "cfacdce06f30d2b68473a46042957675eebb3401",
|
||||
"type": "github"
|
||||
},
|
||||
"original": {
|
||||
"owner": "numtide",
|
||||
"repo": "flake-utils",
|
||||
"type": "github"
|
||||
}
|
||||
},
|
||||
"flake-utils_2": {
|
||||
"locked": {
|
||||
"lastModified": 1667395993,
|
||||
"narHash": "sha256-nuEHfE/LcWyuSWnS8t12N1wc105Qtau+/OdUAjtQ0rA=",
|
||||
|
@ -73,16 +110,16 @@
|
|||
"type": "github"
|
||||
}
|
||||
},
|
||||
"flake-utils_2": {
|
||||
"flake-utils_3": {
|
||||
"inputs": {
|
||||
"systems": "systems"
|
||||
"systems": "systems_2"
|
||||
},
|
||||
"locked": {
|
||||
"lastModified": 1689068808,
|
||||
"narHash": "sha256-6ixXo3wt24N/melDWjq70UuHQLxGV8jZvooRanIHXw0=",
|
||||
"lastModified": 1705309234,
|
||||
"narHash": "sha256-uNRRNRKmJyCRC/8y1RqBkqWBLM034y4qN7EprSdmgyA=",
|
||||
"owner": "numtide",
|
||||
"repo": "flake-utils",
|
||||
"rev": "919d646de7be200f3bf08cb76ae1f09402b6f9b4",
|
||||
"rev": "1ef2e671c3b0c19053962c07dbda38332dcebf26",
|
||||
"type": "github"
|
||||
},
|
||||
"original": {
|
||||
|
@ -92,6 +129,21 @@
|
|||
}
|
||||
},
|
||||
"nixpkgs": {
|
||||
"locked": {
|
||||
"lastModified": 1678964307,
|
||||
"narHash": "sha256-POV15raLJzwns6U84W4aWNSeSJRXTz7xWQW6IcrWQns=",
|
||||
"owner": "NixOS",
|
||||
"repo": "nixpkgs",
|
||||
"rev": "fd4f7832961053e6095af8de8d6a57b5ad402f19",
|
||||
"type": "github"
|
||||
},
|
||||
"original": {
|
||||
"owner": "NixOS",
|
||||
"repo": "nixpkgs",
|
||||
"type": "github"
|
||||
}
|
||||
},
|
||||
"nixpkgs_2": {
|
||||
"locked": {
|
||||
"lastModified": 1672580127,
|
||||
"narHash": "sha256-3lW3xZslREhJogoOkjeZtlBtvFMyxHku7I/9IVehhT8=",
|
||||
|
@ -107,13 +159,13 @@
|
|||
"type": "github"
|
||||
}
|
||||
},
|
||||
"nixpkgs_2": {
|
||||
"nixpkgs_3": {
|
||||
"locked": {
|
||||
"lastModified": 1688231357,
|
||||
"narHash": "sha256-ZOn16X5jZ6X5ror58gOJAxPfFLAQhZJ6nOUeS4tfFwo=",
|
||||
"lastModified": 1706550542,
|
||||
"narHash": "sha256-UcsnCG6wx++23yeER4Hg18CXWbgNpqNXcHIo5/1Y+hc=",
|
||||
"owner": "nixos",
|
||||
"repo": "nixpkgs",
|
||||
"rev": "645ff62e09d294a30de823cb568e9c6d68e92606",
|
||||
"rev": "97b17f32362e475016f942bbdfda4a4a72a8a652",
|
||||
"type": "github"
|
||||
},
|
||||
"original": {
|
||||
|
@ -123,13 +175,13 @@
|
|||
"type": "github"
|
||||
}
|
||||
},
|
||||
"nixpkgs_3": {
|
||||
"nixpkgs_4": {
|
||||
"locked": {
|
||||
"lastModified": 1690294827,
|
||||
"narHash": "sha256-JV53dEaMM566e+6R4Wj58jBAkFg7HaZr3SsXZ9hdh40=",
|
||||
"lastModified": 1708673722,
|
||||
"narHash": "sha256-FPbPhA727wuVkmR21Va6scRjAmj4pk3U8blteaXB/Hg=",
|
||||
"owner": "NixOS",
|
||||
"repo": "nixpkgs",
|
||||
"rev": "7ce0abe77d2ace6d6fc43ff7077019e62a77e741",
|
||||
"rev": "92cf4feb2b9091466a82b27e4bb045cbccc2ba09",
|
||||
"type": "github"
|
||||
},
|
||||
"original": {
|
||||
|
@ -141,20 +193,21 @@
|
|||
},
|
||||
"root": {
|
||||
"inputs": {
|
||||
"albatros": "albatros",
|
||||
"cargo2nix": "cargo2nix",
|
||||
"fenix": "fenix",
|
||||
"flake-utils": "flake-utils_2",
|
||||
"nixpkgs": "nixpkgs_3"
|
||||
"flake-utils": "flake-utils_3",
|
||||
"nixpkgs": "nixpkgs_4"
|
||||
}
|
||||
},
|
||||
"rust-analyzer-src": {
|
||||
"flake": false,
|
||||
"locked": {
|
||||
"lastModified": 1688410727,
|
||||
"narHash": "sha256-TqKZO9D64UDBCMY2sUP2ebAKP0oY7S9enrHfZaDiqBQ=",
|
||||
"lastModified": 1706735270,
|
||||
"narHash": "sha256-IJk+UitcJsxzMQWm9pa1ZbJBriQ4ginXOlPyVq+Cu40=",
|
||||
"owner": "rust-lang",
|
||||
"repo": "rust-analyzer",
|
||||
"rev": "45272efec5fcb8bc46e303d6ced8bd2ba095a667",
|
||||
"rev": "42cb1a2bd79af321b0cc503d2960b73f34e2f92b",
|
||||
"type": "github"
|
||||
},
|
||||
"original": {
|
||||
|
@ -203,6 +256,21 @@
|
|||
"repo": "default",
|
||||
"type": "github"
|
||||
}
|
||||
},
|
||||
"systems_2": {
|
||||
"locked": {
|
||||
"lastModified": 1681028828,
|
||||
"narHash": "sha256-Vy1rq5AaRuLzOxct8nz4T6wlgyUR7zLU309k9mBC768=",
|
||||
"owner": "nix-systems",
|
||||
"repo": "default",
|
||||
"rev": "da67096a3b9bf56a91d16901293e51ba5b49a27e",
|
||||
"type": "github"
|
||||
},
|
||||
"original": {
|
||||
"owner": "nix-systems",
|
||||
"repo": "default",
|
||||
"type": "github"
|
||||
}
|
||||
}
|
||||
},
|
||||
"root": "root",
|
||||
|
|
127
flake.nix
127
flake.nix
|
@ -14,12 +14,14 @@
|
|||
};
|
||||
|
||||
# use rust project builds
|
||||
fenix.url = "github:nix-community/fenix/monthly";
|
||||
fenix.url = "github:nix-community/fenix";
|
||||
|
||||
# import alba releasing tool
|
||||
albatros.url = "git+https://git.deuxfleurs.fr/Deuxfleurs/albatros.git?ref=main";
|
||||
};
|
||||
|
||||
outputs = { self, nixpkgs, cargo2nix, flake-utils, fenix }:
|
||||
flake-utils.lib.eachSystem [
|
||||
"x86_64-linux"
|
||||
outputs = { self, nixpkgs, cargo2nix, flake-utils, fenix, albatros }:
|
||||
let platformArtifacts = flake-utils.lib.eachSystem [
|
||||
"x86_64-unknown-linux-musl"
|
||||
"aarch64-unknown-linux-musl"
|
||||
"armv6l-unknown-linux-musleabihf"
|
||||
|
@ -52,21 +54,6 @@
|
|||
];
|
||||
};
|
||||
|
||||
pkgVanilla = import nixpkgs { system = "x86_64-linux"; };
|
||||
|
||||
shell = pkgVanilla.mkShell {
|
||||
buildInputs = [
|
||||
cargo2nix.packages.x86_64-linux.default
|
||||
fenix.packages.x86_64-linux.minimal.toolchain
|
||||
fenix.packages.x86_64-linux.rust-analyzer
|
||||
];
|
||||
shellHook = ''
|
||||
echo "AEROGRAME DEVELOPMENT SHELL ${fenix.packages.x86_64-linux.minimal.rustc}"
|
||||
export RUST_SRC_PATH="${fenix.packages.x86_64-linux.latest.rust-src}/lib/rustlib/src/rust/library"
|
||||
export RUST_ANALYZER_INTERNALS_DO_NOT_USE='this is unstable'
|
||||
'';
|
||||
};
|
||||
|
||||
rustTarget = if targetHost == "armv6l-unknown-linux-musleabihf" then "arm-unknown-linux-musleabihf" else targetHost;
|
||||
|
||||
# release builds
|
||||
|
@ -74,8 +61,8 @@
|
|||
packageFun = import ./Cargo.nix;
|
||||
target = rustTarget;
|
||||
release = true;
|
||||
rustcLinkFlags = [ "--cfg" "tokio_unstable" ];
|
||||
rustcBuildFlags = [ "--cfg" "tokio_unstable" ];
|
||||
#rustcLinkFlags = [ "--cfg" "tokio_unstable" ];
|
||||
#rustcBuildFlags = [ "--cfg" "tokio_unstable" ];
|
||||
rustToolchain = with fenix.packages.x86_64-linux; combine [
|
||||
minimal.cargo
|
||||
minimal.rustc
|
||||
|
@ -125,14 +112,29 @@
|
|||
];
|
||||
});
|
||||
|
||||
|
||||
crate = (rustRelease.workspace.aerogramme {});
|
||||
|
||||
# binary extract
|
||||
bin = pkgs.stdenv.mkDerivation {
|
||||
pname = "aerogramme-bin";
|
||||
version = "0.1.0";
|
||||
pname = "${crate.name}-bin";
|
||||
version = crate.version;
|
||||
dontUnpack = true;
|
||||
dontBuild = true;
|
||||
installPhase = ''
|
||||
cp ${(rustRelease.workspace.aerogramme {}).bin}/bin/aerogramme $out
|
||||
cp ${crate.bin}/bin/aerogramme $out
|
||||
'';
|
||||
};
|
||||
|
||||
# fhs extract
|
||||
fhs = pkgs.stdenv.mkDerivation {
|
||||
pname = "${crate.name}-fhs";
|
||||
version = crate.version;
|
||||
dontUnpack = true;
|
||||
dontBuild = true;
|
||||
installPhase = ''
|
||||
mkdir -p $out/bin
|
||||
cp ${crate.bin}/bin/aerogramme $out/bin/
|
||||
'';
|
||||
};
|
||||
|
||||
|
@ -151,16 +153,81 @@
|
|||
container = pkgs.dockerTools.buildImage {
|
||||
name = "dxflrs/aerogramme";
|
||||
architecture = (builtins.getAttr targetHost archMap).GOARCH;
|
||||
copyToRoot = fhs;
|
||||
config = {
|
||||
Cmd = [ "${bin}" "server" ];
|
||||
Env = [ "PATH=/bin" ];
|
||||
Cmd = [ "aerogramme" "--dev" "provider" "daemon" ];
|
||||
};
|
||||
};
|
||||
|
||||
in {
|
||||
devShells.default = shell;
|
||||
packages.debug = (rustDebug.workspace.aerogramme {}).bin;
|
||||
packages.aerogramme = bin;
|
||||
packages.container = container;
|
||||
packages.default = self.packages.${targetHost}.aerogramme;
|
||||
meta = {
|
||||
version = crate.version;
|
||||
};
|
||||
packages = {
|
||||
inherit fhs container;
|
||||
debug = (rustDebug.workspace.aerogramme {}).bin;
|
||||
aerogramme = bin;
|
||||
default = self.packages.${targetHost}.aerogramme;
|
||||
};
|
||||
});
|
||||
|
||||
###
|
||||
#
|
||||
# RELEASE STUFF
|
||||
#
|
||||
###
|
||||
gpkgs = import nixpkgs {
|
||||
system = "x86_64-linux"; # hardcoded as we will cross compile
|
||||
};
|
||||
alba = albatros.packages.x86_64-linux.alba;
|
||||
|
||||
# Shell
|
||||
shell = gpkgs.mkShell {
|
||||
buildInputs = [
|
||||
cargo2nix.packages.x86_64-linux.default
|
||||
fenix.packages.x86_64-linux.minimal.toolchain
|
||||
fenix.packages.x86_64-linux.rust-analyzer
|
||||
];
|
||||
shellHook = ''
|
||||
echo "AEROGRAME DEVELOPMENT SHELL ${fenix.packages.x86_64-linux.minimal.rustc}"
|
||||
export RUST_SRC_PATH="${fenix.packages.x86_64-linux.latest.rust-src}/lib/rustlib/src/rust/library"
|
||||
export RUST_ANALYZER_INTERNALS_DO_NOT_USE='this is unstable'
|
||||
'';
|
||||
};
|
||||
|
||||
# Used only to fetch the "version"
|
||||
version = platformArtifacts.meta.x86_64-unknown-linux-musl.version;
|
||||
|
||||
build = gpkgs.writeScriptBin "aerogramme-build" ''
|
||||
set -euxo pipefail
|
||||
|
||||
# static
|
||||
nix build --print-build-logs .#packages.x86_64-unknown-linux-musl.aerogramme -o static/linux/amd64/aerogramme
|
||||
nix build --print-build-logs .#packages.aarch64-unknown-linux-musl.aerogramme -o static/linux/arm64/aerogramme
|
||||
nix build --print-build-logs .#packages.armv6l-unknown-linux-musleabihf.aerogramme -o static/linux/arm/aerogramme
|
||||
|
||||
# containers
|
||||
nix build --print-build-logs .#packages.x86_64-unknown-linux-musl.container -o docker/linux.amd64.tar.gz
|
||||
nix build --print-build-logs .#packages.aarch64-unknown-linux-musl.container -o docker/linux.arm64.tar.gz
|
||||
nix build --print-build-logs .#packages.armv6l-unknown-linux-musleabihf.container -o docker/linux.arm.tar.gz
|
||||
'';
|
||||
|
||||
push = gpkgs.writeScriptBin "aerogramme-publish" ''
|
||||
set -euxo pipefail
|
||||
|
||||
${alba} static push -t aerogramme:${version} static/ 's3://download.deuxfleurs.org?endpoint=garage.deuxfleurs.fr&s3ForcePathStyle=true®ion=garage' 1>&2
|
||||
${alba} container push -t aerogramme:${version} docker/ 's3://registry.deuxfleurs.org?endpoint=garage.deuxfleurs.fr&s3ForcePathStyle=true®ion=garage' 1>&2
|
||||
${alba} container push -t aerogramme:${version} docker/ "docker://docker.io/dxflrs/aerogramme:${version}" 1>&2
|
||||
'';
|
||||
|
||||
in
|
||||
{
|
||||
devShells.x86_64-linux.default = shell;
|
||||
packages = {
|
||||
x86_64-linux = {
|
||||
inherit build push;
|
||||
};
|
||||
} // platformArtifacts.packages;
|
||||
};
|
||||
}
|
||||
|
|
434
src/auth.rs
434
src/auth.rs
|
@ -1,6 +1,6 @@
|
|||
use std::net::SocketAddr;
|
||||
|
||||
use anyhow::{Result, anyhow, bail};
|
||||
use anyhow::{anyhow, bail, Result};
|
||||
use futures::stream::{FuturesUnordered, StreamExt};
|
||||
use tokio::io::BufStream;
|
||||
use tokio::io::{AsyncBufReadExt, AsyncWriteExt};
|
||||
|
@ -25,9 +25,9 @@ use crate::login::ArcLoginProvider;
|
|||
/// C: VERSION 1 2
|
||||
/// C: CPID 1
|
||||
///
|
||||
/// C: AUTH 2 PLAIN service=smtp
|
||||
/// S: CONT 2
|
||||
/// C: CONT 2 base64stringFollowingRFC4616==
|
||||
/// C: AUTH 2 PLAIN service=smtp
|
||||
/// S: CONT 2
|
||||
/// C: CONT 2 base64stringFollowingRFC4616==
|
||||
/// S: OK 2 user=alice@example.tld
|
||||
///
|
||||
/// C: AUTH 42 LOGIN service=smtp
|
||||
|
@ -41,7 +41,7 @@ use crate::login::ArcLoginProvider;
|
|||
/// ## RFC References
|
||||
///
|
||||
/// PLAIN SASL - https://datatracker.ietf.org/doc/html/rfc4616
|
||||
///
|
||||
///
|
||||
///
|
||||
/// ## Dovecot References
|
||||
///
|
||||
|
@ -54,22 +54,20 @@ pub struct AuthServer {
|
|||
bind_addr: SocketAddr,
|
||||
}
|
||||
|
||||
|
||||
impl AuthServer {
|
||||
pub fn new(
|
||||
config: AuthConfig,
|
||||
login_provider: ArcLoginProvider,
|
||||
) -> Self {
|
||||
pub fn new(config: AuthConfig, login_provider: ArcLoginProvider) -> Self {
|
||||
Self {
|
||||
bind_addr: config.bind_addr,
|
||||
login_provider,
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
pub async fn run(self: Self, mut must_exit: watch::Receiver<bool>) -> Result<()> {
|
||||
let tcp = TcpListener::bind(self.bind_addr).await?;
|
||||
tracing::info!("SASL Authentication Protocol listening on {:#}", self.bind_addr);
|
||||
tracing::info!(
|
||||
"SASL Authentication Protocol listening on {:#}",
|
||||
self.bind_addr
|
||||
);
|
||||
|
||||
let mut connections = FuturesUnordered::new();
|
||||
|
||||
|
@ -89,8 +87,9 @@ impl AuthServer {
|
|||
};
|
||||
|
||||
tracing::info!("AUTH: accepted connection from {}", remote_addr);
|
||||
let conn = tokio::spawn(NetLoop::new(socket, self.login_provider.clone(), must_exit.clone()).run_error());
|
||||
|
||||
let conn = tokio::spawn(
|
||||
NetLoop::new(socket, self.login_provider.clone(), must_exit.clone()).run_error(),
|
||||
);
|
||||
|
||||
connections.push(conn);
|
||||
}
|
||||
|
@ -106,7 +105,7 @@ impl AuthServer {
|
|||
struct NetLoop {
|
||||
login: ArcLoginProvider,
|
||||
stream: BufStream<TcpStream>,
|
||||
stop: watch::Receiver<bool>,
|
||||
stop: watch::Receiver<bool>,
|
||||
state: State,
|
||||
read_buf: Vec<u8>,
|
||||
write_buf: BytesMut,
|
||||
|
@ -197,82 +196,114 @@ enum State {
|
|||
Init,
|
||||
HandshakePart(Version),
|
||||
HandshakeDone,
|
||||
AuthPlainProgress {
|
||||
id: u64,
|
||||
},
|
||||
AuthDone {
|
||||
id: u64,
|
||||
res: AuthRes
|
||||
},
|
||||
AuthPlainProgress { id: u64 },
|
||||
AuthDone { id: u64, res: AuthRes },
|
||||
}
|
||||
|
||||
const SERVER_MAJOR: u64 = 1;
|
||||
const SERVER_MINOR: u64 = 2;
|
||||
const EMPTY_AUTHZ: &[u8] = &[];
|
||||
impl State {
|
||||
async fn progress(&mut self, cmd: ClientCommand, login: &ArcLoginProvider) {
|
||||
async fn try_auth_plain<'a>(&self, data: &'a [u8], login: &ArcLoginProvider) -> AuthRes {
|
||||
// Check that we can extract user's login+pass
|
||||
let (ubin, pbin) = match auth_plain(&data) {
|
||||
Ok(([], (authz, user, pass))) if authz == user || authz == EMPTY_AUTHZ => (user, pass),
|
||||
Ok(_) => {
|
||||
tracing::error!("Impersonating user is not supported");
|
||||
return AuthRes::Failed(None, None);
|
||||
}
|
||||
Err(e) => {
|
||||
tracing::error!(err=?e, "Could not parse the SASL PLAIN data chunk");
|
||||
return AuthRes::Failed(None, None);
|
||||
}
|
||||
};
|
||||
|
||||
// Try to convert it to UTF-8
|
||||
let (user, password) = match (std::str::from_utf8(ubin), std::str::from_utf8(pbin)) {
|
||||
(Ok(u), Ok(p)) => (u, p),
|
||||
_ => {
|
||||
tracing::error!("Username or password contain invalid UTF-8 characters");
|
||||
return AuthRes::Failed(None, None);
|
||||
}
|
||||
};
|
||||
|
||||
// Try to connect user
|
||||
match login.login(user, password).await {
|
||||
Ok(_) => AuthRes::Success(user.to_string()),
|
||||
Err(e) => {
|
||||
tracing::warn!(err=?e, "login failed");
|
||||
AuthRes::Failed(Some(user.to_string()), None)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
async fn progress(&mut self, cmd: ClientCommand, login: &ArcLoginProvider) {
|
||||
let new_state = 'state: {
|
||||
match (std::mem::replace(self, State::Error), cmd) {
|
||||
(Self::Init, ClientCommand::Version(v)) => Self::HandshakePart(v),
|
||||
(Self::HandshakePart(version), ClientCommand::Cpid(_cpid)) => {
|
||||
if version.major != SERVER_MAJOR {
|
||||
tracing::error!(client_major=version.major, server_major=SERVER_MAJOR, "Unsupported client major version");
|
||||
break 'state Self::Error
|
||||
}
|
||||
|
||||
Self::HandshakeDone
|
||||
},
|
||||
(Self::HandshakeDone { .. }, ClientCommand::Auth { id, mech, .. }) |
|
||||
(Self::AuthDone { .. }, ClientCommand::Auth { id, mech, ..}) => {
|
||||
if mech != Mechanism::Plain {
|
||||
tracing::error!(mechanism=?mech, "Unsupported Authentication Mechanism");
|
||||
break 'state Self::AuthDone { id, res: AuthRes::Failed(None, None) }
|
||||
tracing::error!(
|
||||
client_major = version.major,
|
||||
server_major = SERVER_MAJOR,
|
||||
"Unsupported client major version"
|
||||
);
|
||||
break 'state Self::Error;
|
||||
}
|
||||
|
||||
Self::AuthPlainProgress { id }
|
||||
},
|
||||
Self::HandshakeDone
|
||||
}
|
||||
(
|
||||
Self::HandshakeDone { .. },
|
||||
ClientCommand::Auth {
|
||||
id, mech, options, ..
|
||||
},
|
||||
)
|
||||
| (
|
||||
Self::AuthDone { .. },
|
||||
ClientCommand::Auth {
|
||||
id, mech, options, ..
|
||||
},
|
||||
) => {
|
||||
if mech != Mechanism::Plain {
|
||||
tracing::error!(mechanism=?mech, "Unsupported Authentication Mechanism");
|
||||
break 'state Self::AuthDone {
|
||||
id,
|
||||
res: AuthRes::Failed(None, None),
|
||||
};
|
||||
}
|
||||
|
||||
match options.last() {
|
||||
Some(AuthOption::Resp(data)) => Self::AuthDone {
|
||||
id,
|
||||
res: self.try_auth_plain(&data, login).await,
|
||||
},
|
||||
_ => Self::AuthPlainProgress { id },
|
||||
}
|
||||
}
|
||||
(Self::AuthPlainProgress { id }, ClientCommand::Cont { id: cid, data }) => {
|
||||
// Check that ID matches
|
||||
if cid != id {
|
||||
tracing::error!(auth_id=id, cont_id=cid, "CONT id does not match AUTH id");
|
||||
break 'state Self::AuthDone { id, res: AuthRes::Failed(None, None) }
|
||||
tracing::error!(
|
||||
auth_id = id,
|
||||
cont_id = cid,
|
||||
"CONT id does not match AUTH id"
|
||||
);
|
||||
break 'state Self::AuthDone {
|
||||
id,
|
||||
res: AuthRes::Failed(None, None),
|
||||
};
|
||||
}
|
||||
|
||||
// Check that we can extract user's login+pass
|
||||
let (ubin, pbin) = match auth_plain(&data) {
|
||||
Ok(([], ([], user, pass))) => (user, pass),
|
||||
Ok(_) => {
|
||||
tracing::error!("Impersonating user is not supported");
|
||||
break 'state Self::AuthDone { id, res: AuthRes::Failed(None, None) }
|
||||
}
|
||||
Err(e) => {
|
||||
tracing::error!(err=?e, "Could not parse the SASL PLAIN data chunk");
|
||||
break 'state Self::AuthDone { id, res: AuthRes::Failed(None, None) }
|
||||
},
|
||||
};
|
||||
|
||||
// Try to convert it to UTF-8
|
||||
let (user, password) = match (std::str::from_utf8(ubin), std::str::from_utf8(pbin)) {
|
||||
(Ok(u), Ok(p)) => (u, p),
|
||||
_ => {
|
||||
tracing::error!("Username or password contain invalid UTF-8 characters");
|
||||
break 'state Self::AuthDone { id, res: AuthRes::Failed(None, None) }
|
||||
}
|
||||
};
|
||||
|
||||
// Try to connect user
|
||||
match login.login(user, password).await {
|
||||
Ok(_) => Self::AuthDone { id, res: AuthRes::Success(user.to_string())},
|
||||
Err(e) => {
|
||||
tracing::warn!(err=?e, "login failed");
|
||||
Self::AuthDone { id, res: AuthRes::Failed(Some(user.to_string()), None) }
|
||||
}
|
||||
Self::AuthDone {
|
||||
id,
|
||||
res: self.try_auth_plain(&data, login).await,
|
||||
}
|
||||
},
|
||||
}
|
||||
_ => {
|
||||
tracing::error!("This command is not valid in this context");
|
||||
Self::Error
|
||||
},
|
||||
}
|
||||
}
|
||||
};
|
||||
tracing::debug!(state=?new_state, "Made progress");
|
||||
|
@ -284,7 +315,10 @@ impl State {
|
|||
|
||||
match self {
|
||||
Self::HandshakeDone { .. } => {
|
||||
srv_cmd.push(ServerCommand::Version(Version { major: SERVER_MAJOR, minor: SERVER_MINOR }));
|
||||
srv_cmd.push(ServerCommand::Version(Version {
|
||||
major: SERVER_MAJOR,
|
||||
minor: SERVER_MINOR,
|
||||
}));
|
||||
|
||||
srv_cmd.push(ServerCommand::Mech {
|
||||
kind: Mechanism::Plain,
|
||||
|
@ -299,16 +333,34 @@ impl State {
|
|||
srv_cmd.push(ServerCommand::Cookie(cookie));
|
||||
|
||||
srv_cmd.push(ServerCommand::Done);
|
||||
},
|
||||
}
|
||||
Self::AuthPlainProgress { id } => {
|
||||
srv_cmd.push(ServerCommand::Cont { id: *id, data: None });
|
||||
},
|
||||
Self::AuthDone { id, res: AuthRes::Success(user) } => {
|
||||
srv_cmd.push(ServerCommand::Ok { id: *id, user_id: Some(user.to_string()), extra_parameters: vec![]});
|
||||
},
|
||||
Self::AuthDone { id, res: AuthRes::Failed(maybe_user, maybe_failcode) } => {
|
||||
srv_cmd.push(ServerCommand::Fail { id: *id, user_id: maybe_user.clone(), code: maybe_failcode.clone(), extra_parameters: vec![]});
|
||||
},
|
||||
srv_cmd.push(ServerCommand::Cont {
|
||||
id: *id,
|
||||
data: None,
|
||||
});
|
||||
}
|
||||
Self::AuthDone {
|
||||
id,
|
||||
res: AuthRes::Success(user),
|
||||
} => {
|
||||
srv_cmd.push(ServerCommand::Ok {
|
||||
id: *id,
|
||||
user_id: Some(user.to_string()),
|
||||
extra_parameters: vec![],
|
||||
});
|
||||
}
|
||||
Self::AuthDone {
|
||||
id,
|
||||
res: AuthRes::Failed(maybe_user, maybe_failcode),
|
||||
} => {
|
||||
srv_cmd.push(ServerCommand::Fail {
|
||||
id: *id,
|
||||
user_id: maybe_user.clone(),
|
||||
code: maybe_failcode.clone(),
|
||||
extra_parameters: vec![],
|
||||
});
|
||||
}
|
||||
_ => (),
|
||||
};
|
||||
|
||||
|
@ -316,7 +368,6 @@ impl State {
|
|||
}
|
||||
}
|
||||
|
||||
|
||||
// -----------------------------------------------------------------
|
||||
//
|
||||
// DOVECOT AUTH TYPES
|
||||
|
@ -329,7 +380,6 @@ enum Mechanism {
|
|||
Login,
|
||||
}
|
||||
|
||||
|
||||
#[derive(Clone, Debug)]
|
||||
enum AuthOption {
|
||||
/// Unique session ID. Mainly used for logging.
|
||||
|
@ -343,9 +393,9 @@ enum AuthOption {
|
|||
/// Remote client port
|
||||
RemotePort(u16),
|
||||
/// When Dovecot proxy is used, the real_rip/real_port are the proxy’s IP/port and real_lip/real_lport are the backend’s IP/port where the proxy was connected to.
|
||||
RealRemoteIp(String),
|
||||
RealLocalIp(String),
|
||||
RealLocalPort(u16),
|
||||
RealRemoteIp(String),
|
||||
RealLocalIp(String),
|
||||
RealLocalPort(u16),
|
||||
RealRemotePort(u16),
|
||||
/// TLS SNI name
|
||||
LocalName(String),
|
||||
|
@ -380,8 +430,8 @@ enum AuthOption {
|
|||
/// An unknown key
|
||||
UnknownPair(String, Vec<u8>),
|
||||
UnknownBool(Vec<u8>),
|
||||
/// Initial response for authentication mechanism.
|
||||
/// NOTE: This must be the last parameter. Everything after it is ignored.
|
||||
/// Initial response for authentication mechanism.
|
||||
/// NOTE: This must be the last parameter. Everything after it is ignored.
|
||||
/// This is to avoid accidental security holes if user-given data is directly put to base64 string without filtering out tabs.
|
||||
/// @FIXME: I don't understand this parameter
|
||||
Resp(Vec<u8>),
|
||||
|
@ -409,14 +459,13 @@ enum ClientCommand {
|
|||
service: String,
|
||||
/// All the optional parameters
|
||||
options: Vec<AuthOption>,
|
||||
|
||||
},
|
||||
Cont {
|
||||
/// The <id> must match the <id> of the AUTH command.
|
||||
id: u64,
|
||||
/// Data that will be serialized to / deserialized from base64
|
||||
data: Vec<u8>,
|
||||
}
|
||||
},
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
|
@ -464,8 +513,8 @@ enum ServerCommand {
|
|||
parameters: Vec<MechanismParameters>,
|
||||
},
|
||||
/// COOKIE returns connection-specific 128 bit cookie in hex. It must be given to REQUEST command. (Protocol v1.1+ / Dovecot v2.0+)
|
||||
Cookie([u8;16]),
|
||||
/// DONE finishes the handshake from server.
|
||||
Cookie([u8; 16]),
|
||||
/// DONE finishes the handshake from server.
|
||||
Done,
|
||||
|
||||
Fail {
|
||||
|
@ -478,7 +527,7 @@ enum ServerCommand {
|
|||
id: u64,
|
||||
data: Option<Vec<u8>>,
|
||||
},
|
||||
/// FAIL and OK may contain multiple unspecified parameters which authentication client may handle specially.
|
||||
/// FAIL and OK may contain multiple unspecified parameters which authentication client may handle specially.
|
||||
/// The only one specified here is user=<userid> parameter, which should always be sent if the userid is known.
|
||||
Ok {
|
||||
id: u64,
|
||||
|
@ -493,26 +542,20 @@ enum ServerCommand {
|
|||
//
|
||||
// ------------------------------------------------------------------
|
||||
|
||||
use nom::{
|
||||
IResult,
|
||||
branch::alt,
|
||||
error::{ErrorKind, Error},
|
||||
character::complete::{tab, u64, u16},
|
||||
bytes::complete::{is_not, tag, tag_no_case, take, take_while, take_while1},
|
||||
multi::{many1, separated_list0},
|
||||
combinator::{map, opt, recognize, value, rest},
|
||||
sequence::{pair, preceded, tuple},
|
||||
};
|
||||
use base64::Engine;
|
||||
use nom::{
|
||||
branch::alt,
|
||||
bytes::complete::{is_not, tag, tag_no_case, take, take_while, take_while1},
|
||||
character::complete::{tab, u16, u64},
|
||||
combinator::{map, opt, recognize, rest, value},
|
||||
error::{Error, ErrorKind},
|
||||
multi::{many1, separated_list0},
|
||||
sequence::{pair, preceded, tuple},
|
||||
IResult,
|
||||
};
|
||||
|
||||
fn version_command<'a>(input: &'a [u8]) -> IResult<&'a [u8], ClientCommand> {
|
||||
let mut parser = tuple((
|
||||
tag_no_case(b"VERSION"),
|
||||
tab,
|
||||
u64,
|
||||
tab,
|
||||
u64
|
||||
));
|
||||
let mut parser = tuple((tag_no_case(b"VERSION"), tab, u64, tab, u64));
|
||||
|
||||
let (input, (_, _, major, _, minor)) = parser(input)?;
|
||||
Ok((input, ClientCommand::Version(Version { major, minor })))
|
||||
|
@ -521,7 +564,7 @@ fn version_command<'a>(input: &'a [u8]) -> IResult<&'a [u8], ClientCommand> {
|
|||
fn cpid_command<'a>(input: &'a [u8]) -> IResult<&'a [u8], ClientCommand> {
|
||||
preceded(
|
||||
pair(tag_no_case(b"CPID"), tab),
|
||||
map(u64, |v| ClientCommand::Cpid(v))
|
||||
map(u64, |v| ClientCommand::Cpid(v)),
|
||||
)(input)
|
||||
}
|
||||
|
||||
|
@ -541,10 +584,7 @@ fn is_esc<'a>(input: &'a [u8]) -> IResult<&'a [u8], &[u8]> {
|
|||
}
|
||||
|
||||
fn parameter<'a>(input: &'a [u8]) -> IResult<&'a [u8], &[u8]> {
|
||||
recognize(many1(alt((
|
||||
take_while1(is_not_tab_or_esc_or_lf),
|
||||
is_esc
|
||||
))))(input)
|
||||
recognize(many1(alt((take_while1(is_not_tab_or_esc_or_lf), is_esc))))(input)
|
||||
}
|
||||
|
||||
fn parameter_str(input: &[u8]) -> IResult<&[u8], String> {
|
||||
|
@ -568,10 +608,7 @@ fn parameter_name(input: &[u8]) -> IResult<&[u8], String> {
|
|||
}
|
||||
|
||||
fn service<'a>(input: &'a [u8]) -> IResult<&'a [u8], String> {
|
||||
preceded(
|
||||
tag_no_case("service="),
|
||||
parameter_str
|
||||
)(input)
|
||||
preceded(tag_no_case("service="), parameter_str)(input)
|
||||
}
|
||||
|
||||
fn auth_option<'a>(input: &'a [u8]) -> IResult<&'a [u8], AuthOption> {
|
||||
|
@ -583,31 +620,74 @@ fn auth_option<'a>(input: &'a [u8]) -> IResult<&'a [u8], AuthOption> {
|
|||
value(ClientId, tag_no_case(b"client_id")),
|
||||
value(NoLogin, tag_no_case(b"nologin")),
|
||||
map(preceded(tag_no_case(b"session="), u64), |id| Session(id)),
|
||||
map(preceded(tag_no_case(b"lip="), parameter_str), |ip| LocalIp(ip)),
|
||||
map(preceded(tag_no_case(b"rip="), parameter_str), |ip| RemoteIp(ip)),
|
||||
map(preceded(tag_no_case(b"lport="), u16), |port| LocalPort(port)),
|
||||
map(preceded(tag_no_case(b"rport="), u16), |port| RemotePort(port)),
|
||||
map(preceded(tag_no_case(b"real_rip="), parameter_str), |ip| RealRemoteIp(ip)),
|
||||
map(preceded(tag_no_case(b"real_lip="), parameter_str), |ip| RealLocalIp(ip)),
|
||||
map(preceded(tag_no_case(b"real_lport="), u16), |port| RealLocalPort(port)),
|
||||
map(preceded(tag_no_case(b"real_rport="), u16), |port| RealRemotePort(port)),
|
||||
map(preceded(tag_no_case(b"lip="), parameter_str), |ip| {
|
||||
LocalIp(ip)
|
||||
}),
|
||||
map(preceded(tag_no_case(b"rip="), parameter_str), |ip| {
|
||||
RemoteIp(ip)
|
||||
}),
|
||||
map(preceded(tag_no_case(b"lport="), u16), |port| {
|
||||
LocalPort(port)
|
||||
}),
|
||||
map(preceded(tag_no_case(b"rport="), u16), |port| {
|
||||
RemotePort(port)
|
||||
}),
|
||||
map(preceded(tag_no_case(b"real_rip="), parameter_str), |ip| {
|
||||
RealRemoteIp(ip)
|
||||
}),
|
||||
map(preceded(tag_no_case(b"real_lip="), parameter_str), |ip| {
|
||||
RealLocalIp(ip)
|
||||
}),
|
||||
map(preceded(tag_no_case(b"real_lport="), u16), |port| {
|
||||
RealLocalPort(port)
|
||||
}),
|
||||
map(preceded(tag_no_case(b"real_rport="), u16), |port| {
|
||||
RealRemotePort(port)
|
||||
}),
|
||||
)),
|
||||
alt((
|
||||
map(preceded(tag_no_case(b"local_name="), parameter_str), |name| LocalName(name)),
|
||||
map(preceded(tag_no_case(b"forward_views="), parameter), |views| ForwardViews(views.into())),
|
||||
map(preceded(tag_no_case(b"secured="), parameter_str), |info| Secured(Some(info))),
|
||||
map(
|
||||
preceded(tag_no_case(b"local_name="), parameter_str),
|
||||
|name| LocalName(name),
|
||||
),
|
||||
map(
|
||||
preceded(tag_no_case(b"forward_views="), parameter),
|
||||
|views| ForwardViews(views.into()),
|
||||
),
|
||||
map(preceded(tag_no_case(b"secured="), parameter_str), |info| {
|
||||
Secured(Some(info))
|
||||
}),
|
||||
value(Secured(None), tag_no_case(b"secured")),
|
||||
value(CertUsername, tag_no_case(b"cert_username")),
|
||||
map(preceded(tag_no_case(b"transport="), parameter_str), |ts| Transport(ts)),
|
||||
map(preceded(tag_no_case(b"tls_cipher="), parameter_str), |cipher| TlsCipher(cipher)),
|
||||
map(preceded(tag_no_case(b"tls_cipher_bits="), parameter_str), |bits| TlsCipherBits(bits)),
|
||||
map(preceded(tag_no_case(b"tls_pfs="), parameter_str), |pfs| TlsPfs(pfs)),
|
||||
map(preceded(tag_no_case(b"tls_protocol="), parameter_str), |proto| TlsProtocol(proto)),
|
||||
map(preceded(tag_no_case(b"valid-client-cert="), parameter_str), |cert| ValidClientCert(cert)),
|
||||
map(preceded(tag_no_case(b"transport="), parameter_str), |ts| {
|
||||
Transport(ts)
|
||||
}),
|
||||
map(
|
||||
preceded(tag_no_case(b"tls_cipher="), parameter_str),
|
||||
|cipher| TlsCipher(cipher),
|
||||
),
|
||||
map(
|
||||
preceded(tag_no_case(b"tls_cipher_bits="), parameter_str),
|
||||
|bits| TlsCipherBits(bits),
|
||||
),
|
||||
map(preceded(tag_no_case(b"tls_pfs="), parameter_str), |pfs| {
|
||||
TlsPfs(pfs)
|
||||
}),
|
||||
map(
|
||||
preceded(tag_no_case(b"tls_protocol="), parameter_str),
|
||||
|proto| TlsProtocol(proto),
|
||||
),
|
||||
map(
|
||||
preceded(tag_no_case(b"valid-client-cert="), parameter_str),
|
||||
|cert| ValidClientCert(cert),
|
||||
),
|
||||
)),
|
||||
alt((
|
||||
map(preceded(tag_no_case(b"resp="), base64), |data| Resp(data)),
|
||||
map(tuple((parameter_name, tag(b"="), parameter)), |(n, _, v)| UnknownPair(n, v.into())),
|
||||
map(
|
||||
tuple((parameter_name, tag(b"="), parameter)),
|
||||
|(n, _, v)| UnknownPair(n, v.into()),
|
||||
),
|
||||
map(parameter, |v| UnknownBool(v.into())),
|
||||
)),
|
||||
))(input)
|
||||
|
@ -622,13 +702,20 @@ fn auth_command<'a>(input: &'a [u8]) -> IResult<&'a [u8], ClientCommand> {
|
|||
mechanism,
|
||||
tab,
|
||||
service,
|
||||
map(
|
||||
opt(preceded(tab, separated_list0(tab, auth_option))),
|
||||
|o| o.unwrap_or(vec![])
|
||||
),
|
||||
map(opt(preceded(tab, separated_list0(tab, auth_option))), |o| {
|
||||
o.unwrap_or(vec![])
|
||||
}),
|
||||
));
|
||||
let (input, (_, _, id, _, mech, _, service, options)) = parser(input)?;
|
||||
Ok((input, ClientCommand::Auth { id, mech, service, options }))
|
||||
Ok((
|
||||
input,
|
||||
ClientCommand::Auth {
|
||||
id,
|
||||
mech,
|
||||
service,
|
||||
options,
|
||||
},
|
||||
))
|
||||
}
|
||||
|
||||
fn is_base64_core(c: u8) -> bool {
|
||||
|
@ -644,10 +731,7 @@ fn is_base64_pad(c: u8) -> bool {
|
|||
}
|
||||
|
||||
fn base64(input: &[u8]) -> IResult<&[u8], Vec<u8>> {
|
||||
let (input, (b64, _)) = tuple((
|
||||
take_while1(is_base64_core),
|
||||
take_while(is_base64_pad),
|
||||
))(input)?;
|
||||
let (input, (b64, _)) = tuple((take_while1(is_base64_core), take_while(is_base64_pad)))(input)?;
|
||||
|
||||
let data = base64::engine::general_purpose::STANDARD_NO_PAD
|
||||
.decode(b64)
|
||||
|
@ -657,26 +741,15 @@ fn base64(input: &[u8]) -> IResult<&[u8], Vec<u8>> {
|
|||
}
|
||||
|
||||
/// @FIXME Dovecot does not say if base64 content must be padded or not
|
||||
fn cont_command<'a>(input: &'a [u8]) -> IResult<&'a [u8], ClientCommand> {
|
||||
let mut parser = tuple((
|
||||
tag_no_case(b"CONT"),
|
||||
tab,
|
||||
u64,
|
||||
tab,
|
||||
base64
|
||||
));
|
||||
fn cont_command<'a>(input: &'a [u8]) -> IResult<&'a [u8], ClientCommand> {
|
||||
let mut parser = tuple((tag_no_case(b"CONT"), tab, u64, tab, base64));
|
||||
|
||||
let (input, (_, _, id, _, data)) = parser(input)?;
|
||||
Ok((input, ClientCommand::Cont { id, data }))
|
||||
}
|
||||
|
||||
fn client_command<'a>(input: &'a [u8]) -> IResult<&'a [u8], ClientCommand> {
|
||||
alt((
|
||||
version_command,
|
||||
cpid_command,
|
||||
auth_command,
|
||||
cont_command,
|
||||
))(input)
|
||||
alt((version_command, cpid_command, auth_command, cont_command))(input)
|
||||
}
|
||||
|
||||
/*
|
||||
|
@ -698,7 +771,13 @@ fn not_null(c: u8) -> bool {
|
|||
// impersonated user, login, password
|
||||
fn auth_plain<'a>(input: &'a [u8]) -> IResult<&'a [u8], (&'a [u8], &'a [u8], &'a [u8])> {
|
||||
map(
|
||||
tuple((take_while(not_null), take(1usize), take_while(not_null), take(1usize), rest)),
|
||||
tuple((
|
||||
take_while(not_null),
|
||||
take(1usize),
|
||||
take_while(not_null),
|
||||
take(1usize),
|
||||
rest,
|
||||
)),
|
||||
|(imp, _, user, _, pass)| (imp, user, pass),
|
||||
)(input)
|
||||
}
|
||||
|
@ -746,7 +825,6 @@ impl Encode for MechanismParameters {
|
|||
}
|
||||
}
|
||||
|
||||
|
||||
impl Encode for FailCode {
|
||||
fn encode(&self, out: &mut BytesMut) -> Result<()> {
|
||||
match self {
|
||||
|
@ -762,33 +840,32 @@ impl Encode for FailCode {
|
|||
impl Encode for ServerCommand {
|
||||
fn encode(&self, out: &mut BytesMut) -> Result<()> {
|
||||
match self {
|
||||
Self::Version (Version { major, minor }) => {
|
||||
Self::Version(Version { major, minor }) => {
|
||||
out.put(&b"VERSION"[..]);
|
||||
tab_enc(out);
|
||||
out.put(major.to_string().as_bytes());
|
||||
tab_enc(out);
|
||||
out.put(minor.to_string().as_bytes());
|
||||
lf_enc(out);
|
||||
},
|
||||
}
|
||||
Self::Spid(pid) => {
|
||||
out.put(&b"SPID"[..]);
|
||||
tab_enc(out);
|
||||
out.put(pid.to_string().as_bytes());
|
||||
lf_enc(out);
|
||||
},
|
||||
}
|
||||
Self::Cuid(pid) => {
|
||||
out.put(&b"CUID"[..]);
|
||||
tab_enc(out);
|
||||
out.put(pid.to_string().as_bytes());
|
||||
lf_enc(out);
|
||||
},
|
||||
}
|
||||
Self::Cookie(cval) => {
|
||||
out.put(&b"COOKIE"[..]);
|
||||
tab_enc(out);
|
||||
out.put(hex::encode(cval).as_bytes());
|
||||
out.put(hex::encode(cval).as_bytes());
|
||||
lf_enc(out);
|
||||
|
||||
},
|
||||
}
|
||||
Self::Mech { kind, parameters } => {
|
||||
out.put(&b"MECH"[..]);
|
||||
tab_enc(out);
|
||||
|
@ -798,11 +875,11 @@ impl Encode for ServerCommand {
|
|||
p.encode(out)?;
|
||||
}
|
||||
lf_enc(out);
|
||||
},
|
||||
}
|
||||
Self::Done => {
|
||||
out.put(&b"DONE"[..]);
|
||||
lf_enc(out);
|
||||
},
|
||||
}
|
||||
Self::Cont { id, data } => {
|
||||
out.put(&b"CONT"[..]);
|
||||
tab_enc(out);
|
||||
|
@ -813,8 +890,12 @@ impl Encode for ServerCommand {
|
|||
out.put(b64.as_bytes());
|
||||
}
|
||||
lf_enc(out);
|
||||
},
|
||||
Self::Ok { id, user_id, extra_parameters } => {
|
||||
}
|
||||
Self::Ok {
|
||||
id,
|
||||
user_id,
|
||||
extra_parameters,
|
||||
} => {
|
||||
out.put(&b"OK"[..]);
|
||||
tab_enc(out);
|
||||
out.put(id.to_string().as_bytes());
|
||||
|
@ -828,8 +909,13 @@ impl Encode for ServerCommand {
|
|||
out.put(&p[..]);
|
||||
}
|
||||
lf_enc(out);
|
||||
},
|
||||
Self::Fail {id, user_id, code, extra_parameters } => {
|
||||
}
|
||||
Self::Fail {
|
||||
id,
|
||||
user_id,
|
||||
code,
|
||||
extra_parameters,
|
||||
} => {
|
||||
out.put(&b"FAIL"[..]);
|
||||
tab_enc(out);
|
||||
out.put(id.to_string().as_bytes());
|
||||
|
@ -848,7 +934,7 @@ impl Encode for ServerCommand {
|
|||
out.put(&p[..]);
|
||||
}
|
||||
lf_enc(out);
|
||||
},
|
||||
}
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
use imap_codec::imap_types::command::{FetchModifier, SelectExamineModifier, StoreModifier};
|
||||
use imap_codec::imap_types::core::NonEmptyVec;
|
||||
use imap_codec::imap_types::core::Vec1;
|
||||
use imap_codec::imap_types::extensions::enable::{CapabilityEnable, Utf8Kind};
|
||||
use imap_codec::imap_types::response::Capability;
|
||||
use std::collections::HashSet;
|
||||
|
@ -49,7 +49,7 @@ impl Default for ServerCapability {
|
|||
}
|
||||
|
||||
impl ServerCapability {
|
||||
pub fn to_vec(&self) -> NonEmptyVec<Capability<'static>> {
|
||||
pub fn to_vec(&self) -> Vec1<Capability<'static>> {
|
||||
self.0
|
||||
.iter()
|
||||
.map(|v| v.clone())
|
||||
|
|
|
@ -6,7 +6,7 @@ use anyhow::{anyhow, bail, Result};
|
|||
use imap_codec::imap_types::command::{
|
||||
Command, CommandBody, ListReturnItem, SelectExamineModifier,
|
||||
};
|
||||
use imap_codec::imap_types::core::{Atom, Literal, NonEmptyVec, QuotedChar};
|
||||
use imap_codec::imap_types::core::{Atom, Literal, QuotedChar, Vec1};
|
||||
use imap_codec::imap_types::datetime::DateTime;
|
||||
use imap_codec::imap_types::extensions::enable::CapabilityEnable;
|
||||
use imap_codec::imap_types::flag::{Flag, FlagNameAttribute};
|
||||
|
@ -17,10 +17,10 @@ use imap_codec::imap_types::status::{StatusDataItem, StatusDataItemName};
|
|||
use crate::imap::capability::{ClientCapability, ServerCapability};
|
||||
use crate::imap::command::{anystate, MailboxName};
|
||||
use crate::imap::flow;
|
||||
use crate::imap::mailbox_view::MailboxView;
|
||||
use crate::imap::mailbox_view::{MailboxView, UpdateParameters};
|
||||
use crate::imap::response::Response;
|
||||
use crate::imap::Body;
|
||||
|
||||
use crate::mail::mailbox::Mailbox;
|
||||
use crate::mail::uidindex::*;
|
||||
use crate::mail::user::{User, MAILBOX_HIERARCHY_DELIMITER as MBX_HIER_DELIM_RAW};
|
||||
use crate::mail::IMF;
|
||||
|
@ -549,6 +549,8 @@ impl<'a> AuthenticatedContext<'a> {
|
|||
))
|
||||
}
|
||||
|
||||
//@FIXME we should write a specific version for the "selected" state
|
||||
//that returns some unsollicited responses
|
||||
async fn append(
|
||||
self,
|
||||
mailbox: &MailboxCodec<'a>,
|
||||
|
@ -558,7 +560,7 @@ impl<'a> AuthenticatedContext<'a> {
|
|||
) -> Result<(Response<'static>, flow::Transition)> {
|
||||
let append_tag = self.req.tag.clone();
|
||||
match self.append_internal(mailbox, flags, date, message).await {
|
||||
Ok((_mb, uidvalidity, uid, _modseq)) => Ok((
|
||||
Ok((_mb_view, uidvalidity, uid, _modseq)) => Ok((
|
||||
Response::build()
|
||||
.tag(append_tag)
|
||||
.message("APPEND completed")
|
||||
|
@ -580,7 +582,7 @@ impl<'a> AuthenticatedContext<'a> {
|
|||
|
||||
fn enable(
|
||||
self,
|
||||
cap_enable: &NonEmptyVec<CapabilityEnable<'static>>,
|
||||
cap_enable: &Vec1<CapabilityEnable<'static>>,
|
||||
) -> Result<(Response<'static>, flow::Transition)> {
|
||||
let mut response_builder = Response::build().to_req(self.req);
|
||||
let capabilities = self.client_capabilities.try_enable(cap_enable.as_ref());
|
||||
|
@ -593,13 +595,14 @@ impl<'a> AuthenticatedContext<'a> {
|
|||
))
|
||||
}
|
||||
|
||||
//@FIXME should be refactored and integrated to the mailbox view
|
||||
pub(crate) async fn append_internal(
|
||||
self,
|
||||
mailbox: &MailboxCodec<'a>,
|
||||
flags: &[Flag<'a>],
|
||||
date: &Option<DateTime>,
|
||||
message: &Literal<'a>,
|
||||
) -> Result<(Arc<Mailbox>, ImapUidvalidity, ImapUid, ModSeq)> {
|
||||
) -> Result<(MailboxView, ImapUidvalidity, ImapUid, ModSeq)> {
|
||||
let name: &str = MailboxName(mailbox).try_into()?;
|
||||
|
||||
let mb_opt = self.user.open_mailbox(&name).await?;
|
||||
|
@ -607,6 +610,7 @@ impl<'a> AuthenticatedContext<'a> {
|
|||
Some(mb) => mb,
|
||||
None => bail!("Mailbox does not exist"),
|
||||
};
|
||||
let mut view = MailboxView::new(mb, self.client_capabilities.condstore.is_enabled()).await;
|
||||
|
||||
if date.is_some() {
|
||||
tracing::warn!("Cannot set date when appending message");
|
||||
|
@ -617,9 +621,11 @@ impl<'a> AuthenticatedContext<'a> {
|
|||
let flags = flags.iter().map(|x| x.to_string()).collect::<Vec<_>>();
|
||||
// TODO: filter allowed flags? ping @Quentin
|
||||
|
||||
let (uidvalidity, uid, modseq) = mb.append(msg, None, &flags[..]).await?;
|
||||
let (uidvalidity, uid, modseq) =
|
||||
view.internal.mailbox.append(msg, None, &flags[..]).await?;
|
||||
//let unsollicited = view.update(UpdateParameters::default()).await?;
|
||||
|
||||
Ok((mb, uidvalidity, uid, modseq))
|
||||
Ok((view, uidvalidity, uid, modseq))
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -3,7 +3,7 @@ use std::sync::Arc;
|
|||
|
||||
use anyhow::Result;
|
||||
use imap_codec::imap_types::command::{Command, CommandBody, FetchModifier, StoreModifier};
|
||||
use imap_codec::imap_types::core::Charset;
|
||||
use imap_codec::imap_types::core::{Charset, Vec1};
|
||||
use imap_codec::imap_types::fetch::MacroOrMessageDataItemNames;
|
||||
use imap_codec::imap_types::flag::{Flag, StoreResponse, StoreType};
|
||||
use imap_codec::imap_types::mailbox::Mailbox as MailboxCodec;
|
||||
|
@ -54,11 +54,15 @@ pub async fn dispatch<'a>(
|
|||
ctx.fetch(sequence_set, macro_or_item_names, modifiers, uid)
|
||||
.await
|
||||
}
|
||||
//@FIXME SearchKey::And is a legacy hack, should be refactored
|
||||
CommandBody::Search {
|
||||
charset,
|
||||
criteria,
|
||||
uid,
|
||||
} => ctx.search(charset, criteria, uid).await,
|
||||
} => {
|
||||
ctx.search(charset, &SearchKey::And(criteria.clone()), uid)
|
||||
.await
|
||||
}
|
||||
CommandBody::Expunge {
|
||||
// UIDPLUS (rfc4315)
|
||||
uid_sequence_set,
|
||||
|
@ -88,15 +92,6 @@ pub async fn dispatch<'a>(
|
|||
// UNSELECT extension (rfc3691)
|
||||
CommandBody::Unselect => ctx.unselect().await,
|
||||
|
||||
// IDLE extension (rfc2177)
|
||||
CommandBody::Idle => Ok((
|
||||
Response::build()
|
||||
.to_req(ctx.req)
|
||||
.message("DUMMY command due to anti-pattern in the code")
|
||||
.ok()?,
|
||||
flow::Transition::Idle(ctx.req.tag.clone(), tokio::sync::Notify::new()),
|
||||
)),
|
||||
|
||||
// In selected mode, we fallback to authenticated when needed
|
||||
_ => {
|
||||
authenticated::dispatch(authenticated::AuthenticatedContext {
|
||||
|
|
|
@ -1,11 +1,12 @@
|
|||
use std::error::Error as StdError;
|
||||
use std::fmt;
|
||||
use std::sync::Arc;
|
||||
|
||||
use imap_codec::imap_types::core::Tag;
|
||||
use tokio::sync::Notify;
|
||||
|
||||
use crate::imap::mailbox_view::MailboxView;
|
||||
use crate::mail::user::User;
|
||||
use imap_codec::imap_types::core::Tag;
|
||||
|
||||
#[derive(Debug)]
|
||||
pub enum Error {
|
||||
|
@ -31,6 +32,14 @@ pub enum State {
|
|||
),
|
||||
Logout,
|
||||
}
|
||||
impl State {
|
||||
pub fn notify(&self) -> Option<Arc<Notify>> {
|
||||
match self {
|
||||
Self::Idle(_, _, _, _, anotif) => Some(anotif.clone()),
|
||||
_ => None,
|
||||
}
|
||||
}
|
||||
}
|
||||
impl fmt::Display for State {
|
||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||
use State::*;
|
||||
|
|
|
@ -4,9 +4,9 @@ use std::sync::Arc;
|
|||
|
||||
use anyhow::{anyhow, Error, Result};
|
||||
|
||||
use futures::stream::{FuturesOrdered, StreamExt};
|
||||
use futures::stream::{StreamExt, TryStreamExt};
|
||||
|
||||
use imap_codec::imap_types::core::Charset;
|
||||
use imap_codec::imap_types::core::{Charset, Vec1};
|
||||
use imap_codec::imap_types::fetch::MessageDataItem;
|
||||
use imap_codec::imap_types::flag::{Flag, FlagFetch, FlagPerm, StoreResponse, StoreType};
|
||||
use imap_codec::imap_types::response::{Code, CodeOther, Data, Status};
|
||||
|
@ -362,46 +362,36 @@ impl MailboxView {
|
|||
.iter()
|
||||
.map(|midx| midx.uuid)
|
||||
.collect::<Vec<_>>();
|
||||
let query_result = self.internal.query(&uuids, query_scope).fetch().await?;
|
||||
|
||||
// [3/6] Derive an IMAP-specific view from the results, apply the filters
|
||||
let views = query_result
|
||||
.iter()
|
||||
.zip(mail_idx_list.into_iter())
|
||||
.map(|(qr, midx)| MailView::new(qr, midx))
|
||||
.collect::<Result<Vec<_>, _>>()?;
|
||||
let query = self.internal.query(&uuids, query_scope);
|
||||
//let query_result = self.internal.query(&uuids, query_scope).fetch().await?;
|
||||
|
||||
// [4/6] Apply the IMAP transformation, bubble up any error
|
||||
// We get 2 results:
|
||||
// - The one we send to the client
|
||||
// - The \Seen flags we must set internally
|
||||
let (flag_mgmt, imap_ret): (Vec<_>, Vec<_>) = views
|
||||
.iter()
|
||||
.map(|mv| mv.filter(&ap).map(|(body, seen)| ((mv, seen), body)))
|
||||
.collect::<Result<Vec<_>, _>>()?
|
||||
.into_iter()
|
||||
.unzip();
|
||||
|
||||
// [5/6] Register the \Seen flags
|
||||
flag_mgmt
|
||||
.iter()
|
||||
.filter(|(_mv, seen)| matches!(seen, SeenFlag::MustAdd))
|
||||
.map(|(mv, _seen)| async move {
|
||||
let seen_flag = Flag::Seen.to_string();
|
||||
self.internal
|
||||
.mailbox
|
||||
.add_flags(*mv.query_result.uuid(), &[seen_flag])
|
||||
.await?;
|
||||
Ok::<_, anyhow::Error>(())
|
||||
let query_stream = query
|
||||
.fetch()
|
||||
.zip(futures::stream::iter(mail_idx_list))
|
||||
// [3/6] Derive an IMAP-specific view from the results, apply the filters
|
||||
.map(|(maybe_qr, midx)| match maybe_qr {
|
||||
Ok(qr) => Ok((MailView::new(&qr, midx)?.filter(&ap)?, midx)),
|
||||
Err(e) => Err(e),
|
||||
})
|
||||
.collect::<FuturesOrdered<_>>()
|
||||
.collect::<Vec<_>>()
|
||||
.await
|
||||
.into_iter()
|
||||
.collect::<Result<_, _>>()?;
|
||||
// [4/6] Apply the IMAP transformation
|
||||
.then(|maybe_ret| async move {
|
||||
let ((body, seen), midx) = maybe_ret?;
|
||||
|
||||
// [5/6] Register the \Seen flags
|
||||
if matches!(seen, SeenFlag::MustAdd) {
|
||||
let seen_flag = Flag::Seen.to_string();
|
||||
self.internal
|
||||
.mailbox
|
||||
.add_flags(midx.uuid, &[seen_flag])
|
||||
.await?;
|
||||
}
|
||||
|
||||
Ok::<_, anyhow::Error>(body)
|
||||
});
|
||||
|
||||
// [6/6] Build the final result that will be sent to the client.
|
||||
Ok(imap_ret)
|
||||
query_stream.try_collect().await
|
||||
}
|
||||
|
||||
/// A naive search implementation...
|
||||
|
@ -423,39 +413,54 @@ impl MailboxView {
|
|||
// 3. Filter the selection based on the ID / UID / Flags
|
||||
let (kept_idx, to_fetch) = crit.filter_on_idx(&selection);
|
||||
|
||||
// 4. Fetch additional info about the emails
|
||||
// 4.a Fetch additional info about the emails
|
||||
let query_scope = crit.query_scope();
|
||||
let uuids = to_fetch.iter().map(|midx| midx.uuid).collect::<Vec<_>>();
|
||||
let query_result = self.internal.query(&uuids, query_scope).fetch().await?;
|
||||
let query = self.internal.query(&uuids, query_scope);
|
||||
|
||||
// 5. If needed, filter the selection based on the body
|
||||
let kept_query = crit.filter_on_query(&to_fetch, &query_result)?;
|
||||
// 4.b We don't want to keep all data in memory, so we do the computing in a stream
|
||||
let query_stream = query
|
||||
.fetch()
|
||||
.zip(futures::stream::iter(&to_fetch))
|
||||
// 5.a Build a mailview with the body, might fail with an error
|
||||
// 5.b If needed, filter the selection based on the body, but keep the errors
|
||||
// 6. Drop the query+mailbox, keep only the mail index
|
||||
// Here we release a lot of memory, this is the most important part ^^
|
||||
.filter_map(|(maybe_qr, midx)| {
|
||||
let r = match maybe_qr {
|
||||
Ok(qr) => match MailView::new(&qr, midx).map(|mv| crit.is_keep_on_query(&mv)) {
|
||||
Ok(true) => Some(Ok(*midx)),
|
||||
Ok(_) => None,
|
||||
Err(e) => Some(Err(e)),
|
||||
},
|
||||
Err(e) => Some(Err(e)),
|
||||
};
|
||||
futures::future::ready(r)
|
||||
});
|
||||
|
||||
// 6. Format the result according to the client's taste:
|
||||
// either return UID or ID.
|
||||
let final_selection = kept_idx.iter().chain(kept_query.iter());
|
||||
let selection_fmt = match uid {
|
||||
true => final_selection.map(|in_idx| in_idx.uid).collect(),
|
||||
_ => final_selection.map(|in_idx| in_idx.i).collect(),
|
||||
};
|
||||
// 7. Chain both streams (part resolved from index, part resolved from metadata+body)
|
||||
let main_stream = futures::stream::iter(kept_idx)
|
||||
.map(Ok)
|
||||
.chain(query_stream)
|
||||
.map_ok(|idx| match uid {
|
||||
true => (idx.uid, idx.modseq),
|
||||
_ => (idx.i, idx.modseq),
|
||||
});
|
||||
|
||||
// 7. Add the modseq entry if needed
|
||||
let is_modseq = crit.is_modseq();
|
||||
let maybe_modseq = match is_modseq {
|
||||
true => {
|
||||
let final_selection = kept_idx.iter().chain(kept_query.iter());
|
||||
final_selection
|
||||
.map(|in_idx| in_idx.modseq)
|
||||
.max()
|
||||
.map(|r| NonZeroU64::try_from(r))
|
||||
.transpose()?
|
||||
}
|
||||
// 8. Do the actual computation
|
||||
let internal_result: Vec<_> = main_stream.try_collect().await?;
|
||||
let (selection, modseqs): (Vec<_>, Vec<_>) = internal_result.into_iter().unzip();
|
||||
|
||||
// 9. Aggregate the maximum modseq value
|
||||
let maybe_modseq = match crit.is_modseq() {
|
||||
true => modseqs.into_iter().max(),
|
||||
_ => None,
|
||||
};
|
||||
|
||||
// 10. Return the final result
|
||||
Ok((
|
||||
vec![Body::Data(Data::Search(selection_fmt, maybe_modseq))],
|
||||
is_modseq,
|
||||
vec![Body::Data(Data::Search(selection, maybe_modseq))],
|
||||
maybe_modseq.is_some(),
|
||||
))
|
||||
}
|
||||
|
||||
|
@ -626,7 +631,7 @@ impl MailboxView {
|
|||
mod tests {
|
||||
use super::*;
|
||||
use imap_codec::encode::Encoder;
|
||||
use imap_codec::imap_types::core::NonEmptyVec;
|
||||
use imap_codec::imap_types::core::Vec1;
|
||||
use imap_codec::imap_types::fetch::Section;
|
||||
use imap_codec::imap_types::fetch::{MacroOrMessageDataItemNames, MessageDataItemName};
|
||||
use imap_codec::imap_types::response::Response;
|
||||
|
@ -746,7 +751,7 @@ mod tests {
|
|||
|
||||
let test_repr = Response::Data(Data::Fetch {
|
||||
seq: NonZeroU32::new(1).unwrap(),
|
||||
items: NonEmptyVec::from(MessageDataItem::Body(mime_view::bodystructure(
|
||||
items: Vec1::from(MessageDataItem::Body(mime_view::bodystructure(
|
||||
&message.child,
|
||||
false,
|
||||
)?)),
|
||||
|
|
|
@ -8,7 +8,7 @@ use imap_codec::imap_types::body::{
|
|||
BasicFields, Body as FetchBody, BodyStructure, MultiPartExtensionData, SinglePartExtensionData,
|
||||
SpecificFields,
|
||||
};
|
||||
use imap_codec::imap_types::core::{AString, IString, NString, NonEmptyVec};
|
||||
use imap_codec::imap_types::core::{AString, IString, NString, Vec1};
|
||||
use imap_codec::imap_types::fetch::{Part as FetchPart, Section as FetchSection};
|
||||
|
||||
use eml_codec::{
|
||||
|
@ -141,8 +141,8 @@ impl<'a> NodeMime<'a> {
|
|||
enum SubsettedSection<'a> {
|
||||
Part,
|
||||
Header,
|
||||
HeaderFields(&'a NonEmptyVec<AString<'a>>),
|
||||
HeaderFieldsNot(&'a NonEmptyVec<AString<'a>>),
|
||||
HeaderFields(&'a Vec1<AString<'a>>),
|
||||
HeaderFieldsNot(&'a Vec1<AString<'a>>),
|
||||
Text,
|
||||
Mime,
|
||||
}
|
||||
|
@ -238,7 +238,7 @@ impl<'a> SelectedMime<'a> {
|
|||
/// case-insensitive but otherwise exact.
|
||||
fn header_fields(
|
||||
&self,
|
||||
fields: &'a NonEmptyVec<AString<'a>>,
|
||||
fields: &'a Vec1<AString<'a>>,
|
||||
invert: bool,
|
||||
) -> Result<ExtractedFull<'a>> {
|
||||
// Build a lowercase ascii hashset with the fields to fetch
|
||||
|
@ -398,8 +398,8 @@ impl<'a> NodeMult<'a> {
|
|||
.filter_map(|inner| NodeMime(&inner).structure(is_ext).ok())
|
||||
.collect::<Vec<_>>();
|
||||
|
||||
NonEmptyVec::validate(&inner_bodies)?;
|
||||
let bodies = NonEmptyVec::unvalidated(inner_bodies);
|
||||
Vec1::validate(&inner_bodies)?;
|
||||
let bodies = Vec1::unvalidated(inner_bodies);
|
||||
|
||||
Ok(BodyStructure::Multi {
|
||||
bodies,
|
||||
|
|
225
src/imap/mod.rs
225
src/imap/mod.rs
|
@ -15,7 +15,7 @@ mod session;
|
|||
|
||||
use std::net::SocketAddr;
|
||||
|
||||
use anyhow::{bail, Result};
|
||||
use anyhow::{anyhow, bail, Context, Result};
|
||||
use futures::stream::{FuturesUnordered, StreamExt};
|
||||
|
||||
use tokio::net::TcpListener;
|
||||
|
@ -26,8 +26,8 @@ use imap_codec::imap_types::response::{Code, CommandContinuationRequest, Respons
|
|||
use imap_codec::imap_types::{core::Text, response::Greeting};
|
||||
use imap_flow::server::{ServerFlow, ServerFlowEvent, ServerFlowOptions};
|
||||
use imap_flow::stream::AnyStream;
|
||||
use tokio_rustls::TlsAcceptor;
|
||||
use rustls_pemfile::{certs, private_key};
|
||||
use tokio_rustls::TlsAcceptor;
|
||||
|
||||
use crate::config::{ImapConfig, ImapUnsecureConfig};
|
||||
use crate::imap::capability::ServerCapability;
|
||||
|
@ -53,8 +53,14 @@ struct ClientContext {
|
|||
}
|
||||
|
||||
pub fn new(config: ImapConfig, login: ArcLoginProvider) -> Result<Server> {
|
||||
let loaded_certs = certs(&mut std::io::BufReader::new(std::fs::File::open(config.certs)?)).collect::<Result<Vec<_>, _>>()?;
|
||||
let loaded_key = private_key(&mut std::io::BufReader::new(std::fs::File::open(config.key)?))?.unwrap();
|
||||
let loaded_certs = certs(&mut std::io::BufReader::new(std::fs::File::open(
|
||||
config.certs,
|
||||
)?))
|
||||
.collect::<Result<Vec<_>, _>>()?;
|
||||
let loaded_key = private_key(&mut std::io::BufReader::new(std::fs::File::open(
|
||||
config.key,
|
||||
)?))?
|
||||
.unwrap();
|
||||
|
||||
let tls_config = rustls::ServerConfig::builder()
|
||||
.with_no_client_auth()
|
||||
|
@ -109,7 +115,7 @@ impl Server {
|
|||
}
|
||||
};
|
||||
AnyStream::new(stream)
|
||||
},
|
||||
}
|
||||
None => AnyStream::new(socket),
|
||||
};
|
||||
|
||||
|
@ -135,11 +141,8 @@ use std::sync::Arc;
|
|||
use tokio::sync::mpsc::*;
|
||||
use tokio::sync::Notify;
|
||||
use tokio_util::bytes::BytesMut;
|
||||
enum LoopMode {
|
||||
Quit,
|
||||
Interactive,
|
||||
Idle(BytesMut, Arc<Notify>),
|
||||
}
|
||||
|
||||
const PIPELINABLE_COMMANDS: usize = 64;
|
||||
|
||||
// @FIXME a full refactor of this part of the code will be needed sooner or later
|
||||
struct NetLoop {
|
||||
|
@ -153,7 +156,7 @@ impl NetLoop {
|
|||
async fn handler(ctx: ClientContext, sock: AnyStream) {
|
||||
let addr = ctx.addr.clone();
|
||||
|
||||
let nl = match Self::new(ctx, sock).await {
|
||||
let mut nl = match Self::new(ctx, sock).await {
|
||||
Ok(nl) => {
|
||||
tracing::debug!(addr=?addr, "netloop successfully initialized");
|
||||
nl
|
||||
|
@ -175,15 +178,15 @@ impl NetLoop {
|
|||
}
|
||||
|
||||
async fn new(ctx: ClientContext, sock: AnyStream) -> Result<Self> {
|
||||
let mut opts = ServerFlowOptions::default();
|
||||
opts.crlf_relaxed = false;
|
||||
opts.literal_accept_text = Text::unvalidated("OK");
|
||||
opts.literal_reject_text = Text::unvalidated("Literal rejected");
|
||||
|
||||
// Send greeting
|
||||
let (server, _) = ServerFlow::send_greeting(
|
||||
sock,
|
||||
ServerFlowOptions {
|
||||
crlf_relaxed: false,
|
||||
literal_accept_text: Text::unvalidated("OK"),
|
||||
literal_reject_text: Text::unvalidated("Literal rejected"),
|
||||
..ServerFlowOptions::default()
|
||||
},
|
||||
opts,
|
||||
Greeting::ok(
|
||||
Some(Code::Capability(ctx.server_capabilities.to_vec())),
|
||||
"Aerogramme",
|
||||
|
@ -193,7 +196,7 @@ impl NetLoop {
|
|||
.await?;
|
||||
|
||||
// Start a mailbox session in background
|
||||
let (cmd_tx, cmd_rx) = mpsc::channel::<Request>(3);
|
||||
let (cmd_tx, cmd_rx) = mpsc::channel::<Request>(PIPELINABLE_COMMANDS);
|
||||
let (resp_tx, resp_rx) = mpsc::unbounded_channel::<ResponseOrIdle>();
|
||||
tokio::spawn(Self::session(ctx.clone(), cmd_rx, resp_tx));
|
||||
|
||||
|
@ -231,83 +234,115 @@ impl NetLoop {
|
|||
tracing::info!("runner is quitting");
|
||||
}
|
||||
|
||||
async fn core(mut self) -> Result<()> {
|
||||
let mut mode = LoopMode::Interactive;
|
||||
async fn core(&mut self) -> Result<()> {
|
||||
let mut maybe_idle: Option<Arc<Notify>> = None;
|
||||
loop {
|
||||
mode = match mode {
|
||||
LoopMode::Interactive => self.interactive_mode().await?,
|
||||
LoopMode::Idle(buff, stop) => self.idle_mode(buff, stop).await?,
|
||||
LoopMode::Quit => break,
|
||||
}
|
||||
tokio::select! {
|
||||
// Managing imap_flow stuff
|
||||
srv_evt = self.server.progress() => match srv_evt? {
|
||||
ServerFlowEvent::ResponseSent { handle: _handle, response } => {
|
||||
match response {
|
||||
Response::Status(Status::Bye(_)) => return Ok(()),
|
||||
_ => tracing::trace!("sent to {} content {:?}", self.ctx.addr, response),
|
||||
}
|
||||
},
|
||||
ServerFlowEvent::CommandReceived { command } => {
|
||||
match self.cmd_tx.try_send(Request::ImapCommand(command)) {
|
||||
Ok(_) => (),
|
||||
Err(mpsc::error::TrySendError::Full(_)) => {
|
||||
self.server.enqueue_status(Status::bye(None, "Too fast").unwrap());
|
||||
tracing::error!("client {:?} is sending commands too fast, closing.", self.ctx.addr);
|
||||
}
|
||||
_ => {
|
||||
self.server.enqueue_status(Status::bye(None, "Internal session exited").unwrap());
|
||||
tracing::error!("session task exited for {:?}, quitting", self.ctx.addr);
|
||||
}
|
||||
}
|
||||
},
|
||||
ServerFlowEvent::IdleCommandReceived { tag } => {
|
||||
match self.cmd_tx.try_send(Request::IdleStart(tag)) {
|
||||
Ok(_) => (),
|
||||
Err(mpsc::error::TrySendError::Full(_)) => {
|
||||
self.server.enqueue_status(Status::bye(None, "Too fast").unwrap());
|
||||
tracing::error!("client {:?} is sending commands too fast, closing.", self.ctx.addr);
|
||||
}
|
||||
_ => {
|
||||
self.server.enqueue_status(Status::bye(None, "Internal session exited").unwrap());
|
||||
tracing::error!("session task exited for {:?}, quitting", self.ctx.addr);
|
||||
}
|
||||
}
|
||||
}
|
||||
ServerFlowEvent::IdleDoneReceived => {
|
||||
tracing::trace!("client sent DONE and want to stop IDLE");
|
||||
maybe_idle.ok_or(anyhow!("Received IDLE done but not idling currently"))?.notify_one();
|
||||
maybe_idle = None;
|
||||
}
|
||||
flow => {
|
||||
self.server.enqueue_status(Status::bye(None, "Unsupported server flow event").unwrap());
|
||||
tracing::error!("session task exited for {:?} due to unsupported flow {:?}", self.ctx.addr, flow);
|
||||
}
|
||||
},
|
||||
|
||||
// Managing response generated by Aerogramme
|
||||
maybe_msg = self.resp_rx.recv() => match maybe_msg {
|
||||
Some(ResponseOrIdle::Response(response)) => {
|
||||
tracing::trace!("Interactive, server has a response for the client");
|
||||
for body_elem in response.body.into_iter() {
|
||||
let _handle = match body_elem {
|
||||
Body::Data(d) => self.server.enqueue_data(d),
|
||||
Body::Status(s) => self.server.enqueue_status(s),
|
||||
};
|
||||
}
|
||||
self.server.enqueue_status(response.completion);
|
||||
},
|
||||
Some(ResponseOrIdle::IdleAccept(stop)) => {
|
||||
tracing::trace!("Interactive, server agreed to switch in idle mode");
|
||||
let cr = CommandContinuationRequest::basic(None, "Idling")?;
|
||||
self.server.idle_accept(cr).or(Err(anyhow!("refused continuation for idle accept")))?;
|
||||
self.cmd_tx.try_send(Request::IdlePoll)?;
|
||||
if maybe_idle.is_some() {
|
||||
bail!("Can't start IDLE if already idling");
|
||||
}
|
||||
maybe_idle = Some(stop);
|
||||
},
|
||||
Some(ResponseOrIdle::IdleEvent(elems)) => {
|
||||
tracing::trace!("server imap session has some change to communicate to the client");
|
||||
for body_elem in elems.into_iter() {
|
||||
let _handle = match body_elem {
|
||||
Body::Data(d) => self.server.enqueue_data(d),
|
||||
Body::Status(s) => self.server.enqueue_status(s),
|
||||
};
|
||||
}
|
||||
self.cmd_tx.try_send(Request::IdlePoll)?;
|
||||
},
|
||||
Some(ResponseOrIdle::IdleReject(response)) => {
|
||||
tracing::trace!("inform client that session rejected idle");
|
||||
self.server
|
||||
.idle_reject(response.completion)
|
||||
.or(Err(anyhow!("wrong reject command")))?;
|
||||
},
|
||||
None => {
|
||||
self.server.enqueue_status(Status::bye(None, "Internal session exited").unwrap());
|
||||
tracing::error!("session task exited for {:?}, quitting", self.ctx.addr);
|
||||
},
|
||||
Some(_) => unreachable!(),
|
||||
|
||||
},
|
||||
|
||||
// When receiving a CTRL+C
|
||||
_ = self.ctx.must_exit.changed() => {
|
||||
tracing::trace!("Interactive, CTRL+C, exiting");
|
||||
self.server.enqueue_status(Status::bye(None, "Server is being shutdown").unwrap());
|
||||
},
|
||||
};
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
async fn interactive_mode(&mut self) -> Result<LoopMode> {
|
||||
tokio::select! {
|
||||
// Managing imap_flow stuff
|
||||
srv_evt = self.server.progress() => match srv_evt? {
|
||||
ServerFlowEvent::ResponseSent { handle: _handle, response } => {
|
||||
match response {
|
||||
Response::Status(Status::Bye(_)) => return Ok(LoopMode::Quit),
|
||||
_ => tracing::trace!("sent to {} content {:?}", self.ctx.addr, response),
|
||||
}
|
||||
},
|
||||
ServerFlowEvent::CommandReceived { command } => {
|
||||
match self.cmd_tx.try_send(Request::ImapCommand(command)) {
|
||||
Ok(_) => (),
|
||||
Err(mpsc::error::TrySendError::Full(_)) => {
|
||||
self.server.enqueue_status(Status::bye(None, "Too fast").unwrap());
|
||||
tracing::error!("client {:?} is sending commands too fast, closing.", self.ctx.addr);
|
||||
}
|
||||
_ => {
|
||||
self.server.enqueue_status(Status::bye(None, "Internal session exited").unwrap());
|
||||
tracing::error!("session task exited for {:?}, quitting", self.ctx.addr);
|
||||
}
|
||||
}
|
||||
},
|
||||
flow => {
|
||||
self.server.enqueue_status(Status::bye(None, "Unsupported server flow event").unwrap());
|
||||
tracing::error!("session task exited for {:?} due to unsupported flow {:?}", self.ctx.addr, flow);
|
||||
}
|
||||
},
|
||||
|
||||
// Managing response generated by Aerogramme
|
||||
maybe_msg = self.resp_rx.recv() => match maybe_msg {
|
||||
Some(ResponseOrIdle::Response(response)) => {
|
||||
for body_elem in response.body.into_iter() {
|
||||
let _handle = match body_elem {
|
||||
Body::Data(d) => self.server.enqueue_data(d),
|
||||
Body::Status(s) => self.server.enqueue_status(s),
|
||||
};
|
||||
}
|
||||
self.server.enqueue_status(response.completion);
|
||||
},
|
||||
Some(ResponseOrIdle::StartIdle(stop)) => {
|
||||
let cr = CommandContinuationRequest::basic(None, "Idling")?;
|
||||
self.server.enqueue_continuation(cr);
|
||||
self.cmd_tx.try_send(Request::Idle)?;
|
||||
return Ok(LoopMode::Idle(BytesMut::new(), stop))
|
||||
},
|
||||
None => {
|
||||
self.server.enqueue_status(Status::bye(None, "Internal session exited").unwrap());
|
||||
tracing::error!("session task exited for {:?}, quitting", self.ctx.addr);
|
||||
},
|
||||
Some(_) => unreachable!(),
|
||||
|
||||
},
|
||||
|
||||
// When receiving a CTRL+C
|
||||
_ = self.ctx.must_exit.changed() => {
|
||||
self.server.enqueue_status(Status::bye(None, "Server is being shutdown").unwrap());
|
||||
},
|
||||
};
|
||||
Ok(LoopMode::Interactive)
|
||||
}
|
||||
|
||||
/*
|
||||
async fn idle_mode(&mut self, mut buff: BytesMut, stop: Arc<Notify>) -> Result<LoopMode> {
|
||||
// Flush send
|
||||
loop {
|
||||
tracing::trace!("flush server send");
|
||||
match self.server.progress_send().await? {
|
||||
Some(..) => continue,
|
||||
None => break,
|
||||
|
@ -319,6 +354,7 @@ impl NetLoop {
|
|||
maybe_msg = self.resp_rx.recv() => match maybe_msg {
|
||||
// Session decided idle is terminated
|
||||
Some(ResponseOrIdle::Response(response)) => {
|
||||
tracing::trace!("server imap session said idle is done, sending response done, switching to interactive");
|
||||
for body_elem in response.body.into_iter() {
|
||||
let _handle = match body_elem {
|
||||
Body::Data(d) => self.server.enqueue_data(d),
|
||||
|
@ -330,6 +366,7 @@ impl NetLoop {
|
|||
},
|
||||
// Session has some information for user
|
||||
Some(ResponseOrIdle::IdleEvent(elems)) => {
|
||||
tracing::trace!("server imap session has some change to communicate to the client");
|
||||
for body_elem in elems.into_iter() {
|
||||
let _handle = match body_elem {
|
||||
Body::Data(d) => self.server.enqueue_data(d),
|
||||
|
@ -352,16 +389,21 @@ impl NetLoop {
|
|||
},
|
||||
|
||||
// User is trying to interact with us
|
||||
_read_client_bytes = self.server.stream.read(&mut buff) => {
|
||||
read_client_result = self.server.stream.read(&mut buff) => {
|
||||
let _bytes_read = read_client_result?;
|
||||
use imap_codec::decode::Decoder;
|
||||
let codec = imap_codec::IdleDoneCodec::new();
|
||||
tracing::trace!("client sent some data for the server IMAP session");
|
||||
match codec.decode(&buff) {
|
||||
Ok(([], imap_codec::imap_types::extensions::idle::IdleDone)) => {
|
||||
// Session will be informed that it must stop idle
|
||||
// It will generate the "done" message and change the loop mode
|
||||
tracing::trace!("client sent DONE and want to stop IDLE");
|
||||
stop.notify_one()
|
||||
},
|
||||
Err(_) => (),
|
||||
Err(_) => {
|
||||
tracing::trace!("Unable to decode DONE, maybe not enough data were sent?");
|
||||
},
|
||||
_ => bail!("Client sent data after terminating the continuation without waiting for the server. This is an unsupported behavior and bug in Aerogramme, quitting."),
|
||||
};
|
||||
|
||||
|
@ -370,9 +412,10 @@ impl NetLoop {
|
|||
|
||||
// When receiving a CTRL+C
|
||||
_ = self.ctx.must_exit.changed() => {
|
||||
tracing::trace!("CTRL+C sent, aborting IDLE for this session");
|
||||
self.server.enqueue_status(Status::bye(None, "Server is being shutdown").unwrap());
|
||||
return Ok(LoopMode::Interactive)
|
||||
},
|
||||
};
|
||||
}
|
||||
}*/
|
||||
}
|
||||
|
|
|
@ -1,7 +1,9 @@
|
|||
use imap_codec::imap_types::command::Command;
|
||||
use imap_codec::imap_types::core::Tag;
|
||||
|
||||
#[derive(Debug)]
|
||||
pub enum Request {
|
||||
ImapCommand(Command<'static>),
|
||||
Idle,
|
||||
IdleStart(Tag<'static>),
|
||||
IdlePoll,
|
||||
}
|
||||
|
|
|
@ -118,6 +118,7 @@ impl<'a> Response<'a> {
|
|||
#[derive(Debug)]
|
||||
pub enum ResponseOrIdle {
|
||||
Response(Response<'static>),
|
||||
StartIdle(Arc<Notify>),
|
||||
IdleAccept(Arc<Notify>),
|
||||
IdleReject(Response<'static>),
|
||||
IdleEvent(Vec<Body<'static>>),
|
||||
}
|
||||
|
|
|
@ -1,13 +1,12 @@
|
|||
use std::num::{NonZeroU32, NonZeroU64};
|
||||
|
||||
use anyhow::Result;
|
||||
use imap_codec::imap_types::core::NonEmptyVec;
|
||||
use imap_codec::imap_types::core::Vec1;
|
||||
use imap_codec::imap_types::search::{MetadataItemSearch, SearchKey};
|
||||
use imap_codec::imap_types::sequence::{SeqOrUid, Sequence, SequenceSet};
|
||||
|
||||
use crate::imap::index::MailIndex;
|
||||
use crate::imap::mail_view::MailView;
|
||||
use crate::mail::query::{QueryResult, QueryScope};
|
||||
use crate::mail::query::QueryScope;
|
||||
|
||||
pub enum SeqType {
|
||||
Undefined,
|
||||
|
@ -49,7 +48,7 @@ impl<'a> Criteria<'a> {
|
|||
let mut new_vec = base.0.into_inner();
|
||||
new_vec.extend_from_slice(ext.0.as_ref());
|
||||
let seq = SequenceSet(
|
||||
NonEmptyVec::try_from(new_vec)
|
||||
Vec1::try_from(new_vec)
|
||||
.expect("merging non empty vec lead to non empty vec"),
|
||||
);
|
||||
(seq, x)
|
||||
|
@ -145,22 +144,6 @@ impl<'a> Criteria<'a> {
|
|||
(to_keep, to_fetch)
|
||||
}
|
||||
|
||||
pub fn filter_on_query<'b>(
|
||||
&self,
|
||||
midx_list: &[&'b MailIndex<'b>],
|
||||
query_result: &'b Vec<QueryResult>,
|
||||
) -> Result<Vec<&'b MailIndex<'b>>> {
|
||||
Ok(midx_list
|
||||
.iter()
|
||||
.zip(query_result.iter())
|
||||
.map(|(midx, qr)| MailView::new(qr, midx))
|
||||
.collect::<Result<Vec<_>, _>>()?
|
||||
.into_iter()
|
||||
.filter(|mail_view| self.is_keep_on_query(mail_view))
|
||||
.map(|mail_view| mail_view.in_idx)
|
||||
.collect())
|
||||
}
|
||||
|
||||
// ----
|
||||
|
||||
/// Here we are doing a partial filtering: we do not have access
|
||||
|
@ -213,7 +196,7 @@ impl<'a> Criteria<'a> {
|
|||
/// the email, as body(x) might be false. So we need to check it. But as seqid(x) is true,
|
||||
/// we could simplify the request to just body(x) and truncate the first OR. Today, we are
|
||||
/// not doing that, and thus we reevaluate everything.
|
||||
fn is_keep_on_query(&self, mail_view: &MailView) -> bool {
|
||||
pub fn is_keep_on_query(&self, mail_view: &MailView) -> bool {
|
||||
use SearchKey::*;
|
||||
match self.0 {
|
||||
// Combinator logic
|
||||
|
|
|
@ -4,8 +4,8 @@ use crate::imap::flow;
|
|||
use crate::imap::request::Request;
|
||||
use crate::imap::response::{Response, ResponseOrIdle};
|
||||
use crate::login::ArcLoginProvider;
|
||||
use anyhow::{anyhow, bail, Result};
|
||||
use imap_codec::imap_types::command::Command;
|
||||
use anyhow::{anyhow, bail, Context, Result};
|
||||
use imap_codec::imap_types::{command::Command, core::Tag};
|
||||
|
||||
//-----
|
||||
pub struct Instance {
|
||||
|
@ -27,13 +27,48 @@ impl Instance {
|
|||
|
||||
pub async fn request(&mut self, req: Request) -> ResponseOrIdle {
|
||||
match req {
|
||||
Request::Idle => self.idle().await,
|
||||
Request::IdleStart(tag) => self.idle_init(tag),
|
||||
Request::IdlePoll => self.idle_poll().await,
|
||||
Request::ImapCommand(cmd) => self.command(cmd).await,
|
||||
}
|
||||
}
|
||||
|
||||
pub async fn idle(&mut self) -> ResponseOrIdle {
|
||||
match self.idle_happy().await {
|
||||
pub fn idle_init(&mut self, tag: Tag<'static>) -> ResponseOrIdle {
|
||||
// Build transition
|
||||
//@FIXME the notifier should be hidden inside the state and thus not part of the transition!
|
||||
let transition = flow::Transition::Idle(tag.clone(), tokio::sync::Notify::new());
|
||||
|
||||
// Try to apply the transition and get the stop notifier
|
||||
let maybe_stop = self
|
||||
.state
|
||||
.apply(transition)
|
||||
.context("IDLE transition failed")
|
||||
.and_then(|_| {
|
||||
self.state
|
||||
.notify()
|
||||
.ok_or(anyhow!("IDLE state has no Notify object"))
|
||||
});
|
||||
|
||||
// Build an appropriate response
|
||||
match maybe_stop {
|
||||
Ok(stop) => ResponseOrIdle::IdleAccept(stop),
|
||||
Err(e) => {
|
||||
tracing::error!(err=?e, "unable to init idle due to a transition error");
|
||||
//ResponseOrIdle::IdleReject(tag)
|
||||
let no = Response::build()
|
||||
.tag(tag)
|
||||
.message(
|
||||
"Internal error, processing command triggered an illegal IMAP state transition",
|
||||
)
|
||||
.no()
|
||||
.unwrap();
|
||||
ResponseOrIdle::IdleReject(no)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub async fn idle_poll(&mut self) -> ResponseOrIdle {
|
||||
match self.idle_poll_happy().await {
|
||||
Ok(r) => r,
|
||||
Err(e) => {
|
||||
tracing::error!(err=?e, "something bad happened in idle");
|
||||
|
@ -42,7 +77,7 @@ impl Instance {
|
|||
}
|
||||
}
|
||||
|
||||
pub async fn idle_happy(&mut self) -> Result<ResponseOrIdle> {
|
||||
pub async fn idle_poll_happy(&mut self) -> Result<ResponseOrIdle> {
|
||||
let (mbx, tag, stop) = match &mut self.state {
|
||||
flow::State::Idle(_, ref mut mbx, _, tag, stop) => (mbx, tag.clone(), stop.clone()),
|
||||
_ => bail!("Invalid session state, can't idle"),
|
||||
|
@ -128,10 +163,11 @@ impl Instance {
|
|||
.bad()
|
||||
.unwrap());
|
||||
}
|
||||
ResponseOrIdle::Response(resp)
|
||||
|
||||
match &self.state {
|
||||
/*match &self.state {
|
||||
flow::State::Idle(_, _, _, _, n) => ResponseOrIdle::StartIdle(n.clone()),
|
||||
_ => ResponseOrIdle::Response(resp),
|
||||
}
|
||||
}*/
|
||||
}
|
||||
}
|
||||
|
|
|
@ -16,7 +16,7 @@ use tokio::select;
|
|||
use tokio::sync::watch;
|
||||
use tokio_util::compat::*;
|
||||
|
||||
use smtp_message::{Email, EscapedDataReader, Reply, ReplyCode};
|
||||
use smtp_message::{DataUnescaper, Email, EscapedDataReader, Reply, ReplyCode};
|
||||
use smtp_server::{reply, Config, ConnectionMetadata, Decision, MailMetadata};
|
||||
|
||||
use crate::config::*;
|
||||
|
@ -181,6 +181,12 @@ impl Config for LmtpServer {
|
|||
return err_response_stream(meta, format!("io error: {}", e));
|
||||
}
|
||||
reader.complete();
|
||||
let raw_size = text.len();
|
||||
|
||||
// Unescape email, shrink it also to remove last dot
|
||||
let unesc_res = DataUnescaper::new(true).unescape(&mut text);
|
||||
text.truncate(unesc_res.written);
|
||||
tracing::debug!(prev_sz = raw_size, new_sz = text.len(), "unescaped");
|
||||
|
||||
let encrypted_message = match EncryptedMessage::new(text) {
|
||||
Ok(x) => Arc::new(x),
|
||||
|
|
|
@ -21,6 +21,7 @@ pub struct LdapLoginProvider {
|
|||
|
||||
storage_specific: StorageSpecific,
|
||||
in_memory_store: storage::in_memory::MemDb,
|
||||
garage_store: storage::garage::GarageRoot,
|
||||
}
|
||||
|
||||
enum BucketSource {
|
||||
|
@ -60,6 +61,9 @@ impl LdapLoginProvider {
|
|||
let specific = match config.storage {
|
||||
LdapStorage::InMemory => StorageSpecific::InMemory,
|
||||
LdapStorage::Garage(grgconf) => {
|
||||
attrs_to_retrieve.push(grgconf.aws_access_key_id_attr.clone());
|
||||
attrs_to_retrieve.push(grgconf.aws_secret_access_key_attr.clone());
|
||||
|
||||
let bucket_source =
|
||||
match (grgconf.default_bucket.clone(), grgconf.bucket_attr.clone()) {
|
||||
(Some(b), None) => BucketSource::Constant(b),
|
||||
|
@ -88,7 +92,11 @@ impl LdapLoginProvider {
|
|||
mail_attr: config.mail_attr,
|
||||
crypto_root_attr: config.crypto_root_attr,
|
||||
storage_specific: specific,
|
||||
//@FIXME should be created outside of the login provider
|
||||
//Login provider should return only a cryptoroot + a storage URI
|
||||
//storage URI that should be resolved outside...
|
||||
in_memory_store: storage::in_memory::MemDb::new(),
|
||||
garage_store: storage::garage::GarageRoot::new()?,
|
||||
})
|
||||
}
|
||||
|
||||
|
@ -111,7 +119,7 @@ impl LdapLoginProvider {
|
|||
BucketSource::Attr(a) => get_attr(user, &a)?,
|
||||
};
|
||||
|
||||
storage::garage::GarageBuilder::new(storage::garage::GarageConf {
|
||||
self.garage_store.user(storage::garage::GarageConf {
|
||||
region: from_config.aws_region.clone(),
|
||||
s3_endpoint: from_config.s3_endpoint.clone(),
|
||||
k2v_endpoint: from_config.k2v_endpoint.clone(),
|
||||
|
|
|
@ -25,6 +25,7 @@ pub struct UserDatabase {
|
|||
pub struct StaticLoginProvider {
|
||||
user_db: watch::Receiver<UserDatabase>,
|
||||
in_memory_store: storage::in_memory::MemDb,
|
||||
garage_store: storage::garage::GarageRoot,
|
||||
}
|
||||
|
||||
pub async fn update_user_list(config: PathBuf, up: watch::Sender<UserDatabase>) -> Result<()> {
|
||||
|
@ -84,6 +85,7 @@ impl StaticLoginProvider {
|
|||
Ok(Self {
|
||||
user_db: rx,
|
||||
in_memory_store: storage::in_memory::MemDb::new(),
|
||||
garage_store: storage::garage::GarageRoot::new()?,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
@ -109,7 +111,7 @@ impl LoginProvider for StaticLoginProvider {
|
|||
let storage: storage::Builder = match &user.config.storage {
|
||||
StaticStorage::InMemory => self.in_memory_store.builder(username).await,
|
||||
StaticStorage::Garage(grgconf) => {
|
||||
storage::garage::GarageBuilder::new(storage::garage::GarageConf {
|
||||
self.garage_store.user(storage::garage::GarageConf {
|
||||
region: grgconf.aws_region.clone(),
|
||||
k2v_endpoint: grgconf.k2v_endpoint.clone(),
|
||||
s3_endpoint: grgconf.s3_endpoint.clone(),
|
||||
|
@ -140,7 +142,7 @@ impl LoginProvider for StaticLoginProvider {
|
|||
let storage: storage::Builder = match &user.config.storage {
|
||||
StaticStorage::InMemory => self.in_memory_store.builder(&user.username).await,
|
||||
StaticStorage::Garage(grgconf) => {
|
||||
storage::garage::GarageBuilder::new(storage::garage::GarageConf {
|
||||
self.garage_store.user(storage::garage::GarageConf {
|
||||
region: grgconf.aws_region.clone(),
|
||||
k2v_endpoint: grgconf.k2v_endpoint.clone(),
|
||||
s3_endpoint: grgconf.s3_endpoint.clone(),
|
||||
|
|
|
@ -498,7 +498,7 @@ fn dump(uid_index: &Bayou<UidIndex>) {
|
|||
|
||||
/// The metadata of a message that is stored in K2V
|
||||
/// at pk = mail/<mailbox uuid>, sk = <message uuid>
|
||||
#[derive(Debug, Serialize, Deserialize)]
|
||||
#[derive(Debug, Clone, Serialize, Deserialize)]
|
||||
pub struct MailMeta {
|
||||
/// INTERNALDATE field (milliseconds since epoch)
|
||||
pub internaldate: u64,
|
||||
|
|
|
@ -2,7 +2,8 @@ use super::mailbox::MailMeta;
|
|||
use super::snapshot::FrozenMailbox;
|
||||
use super::unique_ident::UniqueIdent;
|
||||
use anyhow::Result;
|
||||
use futures::stream::{FuturesOrdered, StreamExt};
|
||||
use futures::future::FutureExt;
|
||||
use futures::stream::{BoxStream, Stream, StreamExt};
|
||||
|
||||
/// Query is in charge of fetching efficiently
|
||||
/// requested data for a list of emails
|
||||
|
@ -28,64 +29,62 @@ impl QueryScope {
|
|||
}
|
||||
}
|
||||
|
||||
//type QueryResultStream = Box<dyn Stream<Item = Result<QueryResult>>>;
|
||||
|
||||
impl<'a, 'b> Query<'a, 'b> {
|
||||
pub async fn fetch(&self) -> Result<Vec<QueryResult>> {
|
||||
pub fn fetch(&self) -> BoxStream<Result<QueryResult>> {
|
||||
match self.scope {
|
||||
QueryScope::Index => Ok(self
|
||||
.emails
|
||||
.iter()
|
||||
.map(|&uuid| QueryResult::IndexResult { uuid })
|
||||
.collect()),
|
||||
QueryScope::Partial => self.partial().await,
|
||||
QueryScope::Full => self.full().await,
|
||||
QueryScope::Index => Box::pin(
|
||||
futures::stream::iter(self.emails)
|
||||
.map(|&uuid| Ok(QueryResult::IndexResult { uuid })),
|
||||
),
|
||||
QueryScope::Partial => Box::pin(self.partial()),
|
||||
QueryScope::Full => Box::pin(self.full()),
|
||||
}
|
||||
}
|
||||
|
||||
// --- functions below are private *for reasons*
|
||||
fn partial<'d>(&'d self) -> impl Stream<Item = Result<QueryResult>> + 'd + Send {
|
||||
async move {
|
||||
let maybe_meta_list: Result<Vec<MailMeta>> =
|
||||
self.frozen.mailbox.fetch_meta(self.emails).await;
|
||||
let list_res = maybe_meta_list
|
||||
.map(|meta_list| {
|
||||
meta_list
|
||||
.into_iter()
|
||||
.zip(self.emails)
|
||||
.map(|(metadata, &uuid)| Ok(QueryResult::PartialResult { uuid, metadata }))
|
||||
.collect()
|
||||
})
|
||||
.unwrap_or_else(|e| vec![Err(e)]);
|
||||
|
||||
async fn partial(&self) -> Result<Vec<QueryResult>> {
|
||||
let meta = self.frozen.mailbox.fetch_meta(self.emails).await?;
|
||||
let result = meta
|
||||
.into_iter()
|
||||
.zip(self.emails.iter())
|
||||
.map(|(metadata, &uuid)| QueryResult::PartialResult { uuid, metadata })
|
||||
.collect::<Vec<_>>();
|
||||
|
||||
Ok(result)
|
||||
futures::stream::iter(list_res)
|
||||
}
|
||||
.flatten_stream()
|
||||
}
|
||||
|
||||
/// @FIXME WARNING: THIS CAN ALLOCATE A LOT OF MEMORY
|
||||
/// AND GENERATE SO MUCH NETWORK TRAFFIC.
|
||||
/// THIS FUNCTION SHOULD BE REWRITTEN, FOR EXAMPLE WITH
|
||||
/// SOMETHING LIKE AN ITERATOR
|
||||
async fn full(&self) -> Result<Vec<QueryResult>> {
|
||||
let meta_list = self.partial().await?;
|
||||
meta_list
|
||||
.into_iter()
|
||||
.map(|meta| async move {
|
||||
let content = self
|
||||
.frozen
|
||||
.mailbox
|
||||
.fetch_full(
|
||||
*meta.uuid(),
|
||||
&meta
|
||||
.metadata()
|
||||
.expect("meta to be PartialResult")
|
||||
.message_key,
|
||||
)
|
||||
.await?;
|
||||
fn full<'d>(&'d self) -> impl Stream<Item = Result<QueryResult>> + 'd + Send {
|
||||
self.partial().then(move |maybe_meta| async move {
|
||||
let meta = maybe_meta?;
|
||||
|
||||
Ok(meta.into_full(content).expect("meta to be PartialResult"))
|
||||
})
|
||||
.collect::<FuturesOrdered<_>>()
|
||||
.collect::<Vec<_>>()
|
||||
.await
|
||||
.into_iter()
|
||||
.collect::<Result<Vec<_>, _>>()
|
||||
let content = self
|
||||
.frozen
|
||||
.mailbox
|
||||
.fetch_full(
|
||||
*meta.uuid(),
|
||||
&meta
|
||||
.metadata()
|
||||
.expect("meta to be PartialResult")
|
||||
.message_key,
|
||||
)
|
||||
.await?;
|
||||
|
||||
Ok(meta.into_full(content).expect("meta to be PartialResult"))
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
#[derive(Debug, Clone)]
|
||||
pub enum QueryResult {
|
||||
IndexResult {
|
||||
uuid: UniqueIdent,
|
||||
|
|
12
src/main.rs
12
src/main.rs
|
@ -34,7 +34,12 @@ struct Args {
|
|||
#[clap(long)]
|
||||
dev: bool,
|
||||
|
||||
#[clap(short, long, env = "AEROGRAMME_CONFIG", default_value = "aerogramme.toml")]
|
||||
#[clap(
|
||||
short,
|
||||
long,
|
||||
env = "AEROGRAMME_CONFIG",
|
||||
default_value = "aerogramme.toml"
|
||||
)]
|
||||
/// Path to the main Aerogramme configuration file
|
||||
config_file: PathBuf,
|
||||
}
|
||||
|
@ -187,7 +192,10 @@ async fn main() -> Result<()> {
|
|||
hostname: "example.tld".to_string(),
|
||||
}),
|
||||
auth: Some(AuthConfig {
|
||||
bind_addr: SocketAddr::new(IpAddr::V6(Ipv6Addr::new(0, 0, 0, 0, 0, 0, 0, 1)), 12345),
|
||||
bind_addr: SocketAddr::new(
|
||||
IpAddr::V6(Ipv6Addr::new(0, 0, 0, 0, 0, 0, 0, 1)),
|
||||
12345,
|
||||
),
|
||||
}),
|
||||
users: UserManagement::Demo,
|
||||
})
|
||||
|
|
|
@ -7,9 +7,9 @@ use futures::try_join;
|
|||
use log::*;
|
||||
use tokio::sync::watch;
|
||||
|
||||
use crate::auth;
|
||||
use crate::config::*;
|
||||
use crate::imap;
|
||||
use crate::auth;
|
||||
use crate::lmtp::*;
|
||||
use crate::login::ArcLoginProvider;
|
||||
use crate::login::{demo_provider::*, ldap_provider::*, static_provider::*};
|
||||
|
@ -47,9 +47,16 @@ impl Server {
|
|||
};
|
||||
|
||||
let lmtp_server = config.lmtp.map(|lmtp| LmtpServer::new(lmtp, login.clone()));
|
||||
let imap_unsecure_server = config.imap_unsecure.map(|imap| imap::new_unsecure(imap, login.clone()));
|
||||
let imap_server = config.imap.map(|imap| imap::new(imap, login.clone())).transpose()?;
|
||||
let auth_server = config.auth.map(|auth| auth::AuthServer::new(auth, login.clone()));
|
||||
let imap_unsecure_server = config
|
||||
.imap_unsecure
|
||||
.map(|imap| imap::new_unsecure(imap, login.clone()));
|
||||
let imap_server = config
|
||||
.imap
|
||||
.map(|imap| imap::new(imap, login.clone()))
|
||||
.transpose()?;
|
||||
let auth_server = config
|
||||
.auth
|
||||
.map(|auth| auth::AuthServer::new(auth, login.clone()));
|
||||
|
||||
Ok(Self {
|
||||
lmtp_server,
|
||||
|
|
|
@ -1,7 +1,45 @@
|
|||
use crate::storage::*;
|
||||
use aws_sdk_s3::{self as s3, error::SdkError, operation::get_object::GetObjectError};
|
||||
use aws_smithy_runtime::client::http::hyper_014::HyperClientBuilder;
|
||||
use aws_smithy_runtime_api::client::http::SharedHttpClient;
|
||||
use hyper_rustls::HttpsConnector;
|
||||
use hyper_util::client::legacy::{connect::HttpConnector, Client as HttpClient};
|
||||
use hyper_util::rt::TokioExecutor;
|
||||
use serde::Serialize;
|
||||
|
||||
use crate::storage::*;
|
||||
|
||||
pub struct GarageRoot {
|
||||
k2v_http: HttpClient<HttpsConnector<HttpConnector>, k2v_client::Body>,
|
||||
aws_http: SharedHttpClient,
|
||||
}
|
||||
|
||||
impl GarageRoot {
|
||||
pub fn new() -> anyhow::Result<Self> {
|
||||
let connector = hyper_rustls::HttpsConnectorBuilder::new()
|
||||
.with_native_roots()?
|
||||
.https_or_http()
|
||||
.enable_http1()
|
||||
.enable_http2()
|
||||
.build();
|
||||
let k2v_http = HttpClient::builder(TokioExecutor::new()).build(connector);
|
||||
let aws_http = HyperClientBuilder::new().build_https();
|
||||
Ok(Self { k2v_http, aws_http })
|
||||
}
|
||||
|
||||
pub fn user(&self, conf: GarageConf) -> anyhow::Result<Arc<GarageUser>> {
|
||||
let mut unicity: Vec<u8> = vec![];
|
||||
unicity.extend_from_slice(file!().as_bytes());
|
||||
unicity.append(&mut rmp_serde::to_vec(&conf)?);
|
||||
|
||||
Ok(Arc::new(GarageUser {
|
||||
conf,
|
||||
aws_http: self.aws_http.clone(),
|
||||
k2v_http: self.k2v_http.clone(),
|
||||
unicity,
|
||||
}))
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, Serialize)]
|
||||
pub struct GarageConf {
|
||||
pub region: String,
|
||||
|
@ -12,23 +50,19 @@ pub struct GarageConf {
|
|||
pub bucket: String,
|
||||
}
|
||||
|
||||
//@FIXME we should get rid of this builder
|
||||
//and allocate a S3 + K2V client only once per user
|
||||
//(and using a shared HTTP client)
|
||||
#[derive(Clone, Debug)]
|
||||
pub struct GarageBuilder {
|
||||
pub struct GarageUser {
|
||||
conf: GarageConf,
|
||||
aws_http: SharedHttpClient,
|
||||
k2v_http: HttpClient<HttpsConnector<HttpConnector>, k2v_client::Body>,
|
||||
unicity: Vec<u8>,
|
||||
}
|
||||
|
||||
impl GarageBuilder {
|
||||
pub fn new(conf: GarageConf) -> anyhow::Result<Arc<Self>> {
|
||||
let mut unicity: Vec<u8> = vec![];
|
||||
unicity.extend_from_slice(file!().as_bytes());
|
||||
unicity.append(&mut rmp_serde::to_vec(&conf)?);
|
||||
Ok(Arc::new(Self { conf, unicity }))
|
||||
}
|
||||
}
|
||||
|
||||
#[async_trait]
|
||||
impl IBuilder for GarageBuilder {
|
||||
impl IBuilder for GarageUser {
|
||||
async fn build(&self) -> Result<Store, StorageError> {
|
||||
let s3_creds = s3::config::Credentials::new(
|
||||
self.conf.aws_access_key_id.clone(),
|
||||
|
@ -41,6 +75,7 @@ impl IBuilder for GarageBuilder {
|
|||
let sdk_config = aws_config::from_env()
|
||||
.region(aws_config::Region::new(self.conf.region.clone()))
|
||||
.credentials_provider(s3_creds)
|
||||
.http_client(self.aws_http.clone())
|
||||
.endpoint_url(self.conf.s3_endpoint.clone())
|
||||
.load()
|
||||
.await;
|
||||
|
@ -60,13 +95,14 @@ impl IBuilder for GarageBuilder {
|
|||
user_agent: None,
|
||||
};
|
||||
|
||||
let k2v_client = match k2v_client::K2vClient::new(k2v_config) {
|
||||
Err(e) => {
|
||||
tracing::error!("unable to build k2v client: {}", e);
|
||||
return Err(StorageError::Internal);
|
||||
}
|
||||
Ok(v) => v,
|
||||
};
|
||||
let k2v_client =
|
||||
match k2v_client::K2vClient::new_with_client(k2v_config, self.k2v_http.clone()) {
|
||||
Err(e) => {
|
||||
tracing::error!("unable to build k2v client: {}", e);
|
||||
return Err(StorageError::Internal);
|
||||
}
|
||||
Ok(v) => v,
|
||||
};
|
||||
|
||||
Ok(Box::new(GarageStore {
|
||||
bucket: self.conf.bucket.clone(),
|
||||
|
@ -105,6 +141,7 @@ fn causal_to_row_val(row_ref: RowRef, causal_value: k2v_client::CausalValue) ->
|
|||
#[async_trait]
|
||||
impl IStore for GarageStore {
|
||||
async fn row_fetch<'a>(&self, select: &Selector<'a>) -> Result<Vec<RowVal>, StorageError> {
|
||||
tracing::trace!(select=%select, command="row_fetch");
|
||||
let (pk_list, batch_op) = match select {
|
||||
Selector::Range {
|
||||
shard,
|
||||
|
@ -196,21 +233,26 @@ impl IStore for GarageStore {
|
|||
}
|
||||
Ok(v) => v,
|
||||
};
|
||||
//println!("fetch res -> {:?}", all_raw_res);
|
||||
|
||||
let row_vals = all_raw_res
|
||||
.into_iter()
|
||||
.fold(vec![], |mut acc, v| {
|
||||
acc.extend(v.items);
|
||||
acc
|
||||
})
|
||||
.into_iter()
|
||||
.zip(pk_list.into_iter())
|
||||
.map(|((sk, cv), pk)| causal_to_row_val(RowRef::new(&pk, &sk), cv))
|
||||
.collect::<Vec<_>>();
|
||||
let row_vals =
|
||||
all_raw_res
|
||||
.into_iter()
|
||||
.zip(pk_list.into_iter())
|
||||
.fold(vec![], |mut acc, (page, pk)| {
|
||||
page.items
|
||||
.into_iter()
|
||||
.map(|(sk, cv)| causal_to_row_val(RowRef::new(&pk, &sk), cv))
|
||||
.for_each(|rr| acc.push(rr));
|
||||
|
||||
acc
|
||||
});
|
||||
tracing::debug!(fetch_count = row_vals.len(), command = "row_fetch");
|
||||
|
||||
Ok(row_vals)
|
||||
}
|
||||
async fn row_rm<'a>(&self, select: &Selector<'a>) -> Result<(), StorageError> {
|
||||
tracing::trace!(select=%select, command="row_rm");
|
||||
let del_op = match select {
|
||||
Selector::Range {
|
||||
shard,
|
||||
|
@ -280,6 +322,7 @@ impl IStore for GarageStore {
|
|||
}
|
||||
|
||||
async fn row_insert(&self, values: Vec<RowVal>) -> Result<(), StorageError> {
|
||||
tracing::trace!(entries=%values.iter().map(|v| v.row_ref.to_string()).collect::<Vec<_>>().join(","), command="row_insert");
|
||||
let batch_ops = values
|
||||
.iter()
|
||||
.map(|v| k2v_client::BatchInsertOp {
|
||||
|
@ -307,6 +350,7 @@ impl IStore for GarageStore {
|
|||
}
|
||||
}
|
||||
async fn row_poll(&self, value: &RowRef) -> Result<RowVal, StorageError> {
|
||||
tracing::trace!(entry=%value, command="row_poll");
|
||||
loop {
|
||||
if let Some(ct) = &value.causality {
|
||||
match self
|
||||
|
@ -343,6 +387,7 @@ impl IStore for GarageStore {
|
|||
}
|
||||
|
||||
async fn blob_fetch(&self, blob_ref: &BlobRef) -> Result<BlobVal, StorageError> {
|
||||
tracing::trace!(entry=%blob_ref, command="blob_fetch");
|
||||
let maybe_out = self
|
||||
.s3
|
||||
.get_object()
|
||||
|
@ -382,6 +427,7 @@ impl IStore for GarageStore {
|
|||
Ok(bv)
|
||||
}
|
||||
async fn blob_insert(&self, blob_val: BlobVal) -> Result<(), StorageError> {
|
||||
tracing::trace!(entry=%blob_val.blob_ref, command="blob_insert");
|
||||
let streamable_value = s3::primitives::ByteStream::from(blob_val.value);
|
||||
|
||||
let maybe_send = self
|
||||
|
@ -406,6 +452,7 @@ impl IStore for GarageStore {
|
|||
}
|
||||
}
|
||||
async fn blob_copy(&self, src: &BlobRef, dst: &BlobRef) -> Result<(), StorageError> {
|
||||
tracing::trace!(src=%src, dst=%dst, command="blob_copy");
|
||||
let maybe_copy = self
|
||||
.s3
|
||||
.copy_object()
|
||||
|
@ -433,6 +480,7 @@ impl IStore for GarageStore {
|
|||
}
|
||||
}
|
||||
async fn blob_list(&self, prefix: &str) -> Result<Vec<BlobRef>, StorageError> {
|
||||
tracing::trace!(prefix = prefix, command = "blob_list");
|
||||
let maybe_list = self
|
||||
.s3
|
||||
.list_objects_v2()
|
||||
|
@ -462,6 +510,7 @@ impl IStore for GarageStore {
|
|||
}
|
||||
}
|
||||
async fn blob_rm(&self, blob_ref: &BlobRef) -> Result<(), StorageError> {
|
||||
tracing::trace!(entry=%blob_ref, command="blob_rm");
|
||||
let maybe_delete = self
|
||||
.s3
|
||||
.delete_object()
|
||||
|
|
|
@ -225,7 +225,7 @@ fn rfc4551_imapext_condstore() {
|
|||
FetchKind::Rfc822Size,
|
||||
FetchMod::ChangedSince(2),
|
||||
)?;
|
||||
assert!(fetch_res.contains("* 1 FETCH (RFC822.SIZE 84 MODSEQ (3))"));
|
||||
assert!(fetch_res.contains("* 1 FETCH (RFC822.SIZE 81 MODSEQ (3))"));
|
||||
assert!(!fetch_res.contains("* 2 FETCH"));
|
||||
assert_eq!(store_res.lines().count(), 2);
|
||||
|
||||
|
|
|
@ -384,8 +384,7 @@ pub fn append(imap: &mut TcpStream, content: Email) -> Result<String> {
|
|||
// write our stuff
|
||||
imap.write(ref_mail)?;
|
||||
imap.write(&b"\r\n"[..])?;
|
||||
let read = read_lines(imap, &mut buffer, None)?;
|
||||
assert_eq!(&read[..5], &b"47 OK"[..]);
|
||||
let read = read_lines(imap, &mut buffer, Some(&b"47 OK"[..]))?;
|
||||
let srv_msg = std::str::from_utf8(read)?;
|
||||
|
||||
Ok(srv_msg.to_string())
|
||||
|
|
|
@ -0,0 +1 @@
|
|||
*.zstd filter=lfs diff=lfs merge=lfs -text
|
|
@ -0,0 +1 @@
|
|||
*.mbox
|
Binary file not shown.
|
@ -0,0 +1,240 @@
|
|||
Append { mailbox: Other(MailboxOther(Atom(AtomExt("Sent")))), flags: [Seen], date: None, message: REDACTED } }
|
||||
Append { mailbox: Other(MailboxOther(String(Quoted(Quoted("Drafts"))))), flags: [Seen, Draft], date: None, message: REDACTED } }
|
||||
Append { mailbox: Other(MailboxOther(String(Quoted(Quoted("Sent"))))), flags: [Seen], date: None, message: REDACTED } }
|
||||
Append { mailbox: Other(MailboxOther(String(Quoted(Quoted("Sent"))))), flags: [Seen], date: Some(2024-02-14T14:12:35+01:00), message: REDACTED } }
|
||||
Capability
|
||||
Check
|
||||
Close
|
||||
Create { mailbox: Other(MailboxOther(Atom(AtomExt("Mailspring")))) } }
|
||||
Create { mailbox: Other(MailboxOther(String(Quoted(Quoted("Dataset"))))) } }
|
||||
Create { mailbox: Other(MailboxOther(String(Quoted(Quoted("Mailspring.Snoozed"))))) } }
|
||||
Enable { capabilities: [CondStore]+ } }
|
||||
Enable { capabilities: [Utf8(Accept)]+ } }
|
||||
Examine { mailbox: Inbox, modifiers: [] } }
|
||||
Expunge { uid_sequence_set: None } }
|
||||
Fetch { sequence_set: SequenceSet([Range(Asterisk, Value(5))]+), modifiers: [], macro_or_item_names: MessageDataItemNames([Uid, Envelope, BodyExt { section: Some(HeaderFields(None, [Atom(AtomExt("References"))]+)), partial: None, peek: true }]), uid: false } }
|
||||
Fetch { sequence_set: SequenceSet([Range(Value(12), Value(13))]+), modifiers: [], macro_or_item_names: MessageDataItemNames([InternalDate, Uid, Rfc822Size, Flags, ModSeq, BodyExt { section: Some(Header(None)), partial: None, peek: true }]), uid: true } }
|
||||
Fetch { sequence_set: SequenceSet([Range(Value(1), Asterisk)]+), modifiers: [ChangedSince(22)], macro_or_item_names: MessageDataItemNames([Uid, Flags]), uid: true } }
|
||||
Fetch { sequence_set: SequenceSet([Range(Value(1), Asterisk)]+), modifiers: [ChangedSince(24)], macro_or_item_names: MessageDataItemNames([Uid, Flags]), uid: true } }
|
||||
Fetch { sequence_set: SequenceSet([Range(Value(1), Asterisk)]+), modifiers: [ChangedSince(5)], macro_or_item_names: MessageDataItemNames([Uid, Flags]), uid: true } }
|
||||
Fetch { sequence_set: SequenceSet([Range(Value(1), Asterisk)]+), modifiers: [ChangedSince(638435220681800000)], macro_or_item_names: MessageDataItemNames([Uid, Flags]), uid: true } }
|
||||
Fetch { sequence_set: SequenceSet([Range(Value(1), Asterisk)]+), modifiers: [ChangedSince(638435229950250000)], macro_or_item_names: MessageDataItemNames([Uid, Flags]), uid: true } }
|
||||
Fetch { sequence_set: SequenceSet([Range(Value(1), Asterisk)]+), modifiers: [ChangedSince(638435249520030000)], macro_or_item_names: MessageDataItemNames([Uid, Flags]), uid: true } }
|
||||
Fetch { sequence_set: SequenceSet([Range(Value(1), Asterisk)]+), modifiers: [ChangedSince(6)], macro_or_item_names: MessageDataItemNames([Uid, Flags]), uid: true } }
|
||||
Fetch { sequence_set: SequenceSet([Range(Value(1), Asterisk)]+), modifiers: [ChangedSince(7)], macro_or_item_names: MessageDataItemNames([Uid, Flags, Envelope, InternalDate, BodyExt { section: Some(HeaderFields(None, [Atom(AtomExt("References"))]+)), partial: None, peek: true }]), uid: true } }
|
||||
Fetch { sequence_set: SequenceSet([Range(Value(1), Asterisk)]+), modifiers: [ChangedSince(8)], macro_or_item_names: MessageDataItemNames([Uid, Flags]), uid: true } }
|
||||
Fetch { sequence_set: SequenceSet([Range(Value(1), Asterisk)]+), modifiers: [], macro_or_item_names: MessageDataItemNames([Flags]), uid: true } }
|
||||
Fetch { sequence_set: SequenceSet([Range(Value(1), Asterisk)]+), modifiers: [], macro_or_item_names: MessageDataItemNames([Uid, Flags]), uid: true } }
|
||||
Fetch { sequence_set: SequenceSet([Range(Value(1), Value(1))]+), modifiers: [], macro_or_item_names: MessageDataItemNames([Uid, Flags, InternalDate, Rfc822Size, BodyExt { section: Some(HeaderFields(None, [Atom(AtomExt("DATE")), Atom(AtomExt("FROM")), Atom(AtomExt("SENDER")), Atom(AtomExt("SUBJECT")), Atom(AtomExt("TO")), Atom(AtomExt("CC")), Atom(AtomExt("MESSAGE-ID")), Atom(AtomExt("REFERENCES")), Atom(AtomExt("CONTENT-TYPE")), Atom(AtomExt("CONTENT-DESCRIPTION")), Atom(AtomExt("IN-REPLY-TO")), Atom(AtomExt("REPLY-TO")), Atom(AtomExt("LINES")), Atom(AtomExt("LIST-POST")), Atom(AtomExt("X-LABEL"))]+)), partial: None, peek: true }]), uid: false } }
|
||||
Fetch { sequence_set: SequenceSet([Range(Value(1), Value(20))]+), modifiers: [], macro_or_item_names: MessageDataItemNames([Uid, Flags, Envelope, InternalDate, BodyExt { section: Some(HeaderFields(None, [Atom(AtomExt("References"))]+)), partial: None, peek: true }]), uid: true } }
|
||||
Fetch { sequence_set: SequenceSet([Range(Value(1), Value(20))]+), modifiers: [], macro_or_item_names: MessageDataItemNames([Uid, Flags]), uid: true } }
|
||||
Fetch { sequence_set: SequenceSet([Range(Value(1), Value(23))]+), modifiers: [], macro_or_item_names: MessageDataItemNames([Uid, Flags]), uid: true } }
|
||||
Fetch { sequence_set: SequenceSet([Range(Value(1), Value(2))]+), modifiers: [], macro_or_item_names: MessageDataItemNames([Flags, Uid]), uid: false } }
|
||||
Fetch { sequence_set: SequenceSet([Range(Value(1), Value(2))]+), modifiers: [], macro_or_item_names: MessageDataItemNames([InternalDate, Uid, Flags, Rfc822Size, BodyExt { section: Some(HeaderFields(None, [Atom(AtomExt("DATE")), Atom(AtomExt("FROM")), Atom(AtomExt("SUBJECT")), Atom(AtomExt("CONTENT-TYPE")), Atom(AtomExt("X-MS-TNEF-Correlator")), Atom(AtomExt("CONTENT-CLASS")), Atom(AtomExt("IMPORTANCE")), Atom(AtomExt("PRIORITY")), Atom(AtomExt("X-PRIORITY")), Atom(AtomExt("THREAD-TOPIC")), Atom(AtomExt("REPLY-TO"))]+)), partial: None, peek: true }, BodyStructure]), uid: false } }
|
||||
Fetch { sequence_set: SequenceSet([Range(Value(1), Value(2))]+), modifiers: [], macro_or_item_names: MessageDataItemNames([Uid, Flags, InternalDate, Rfc822Size, BodyExt { section: Some(HeaderFields(None, [Atom(AtomExt("DATE")), Atom(AtomExt("FROM")), Atom(AtomExt("SENDER")), Atom(AtomExt("SUBJECT")), Atom(AtomExt("TO")), Atom(AtomExt("CC")), Atom(AtomExt("MESSAGE-ID")), Atom(AtomExt("REFERENCES")), Atom(AtomExt("CONTENT-TYPE")), Atom(AtomExt("CONTENT-DESCRIPTION")), Atom(AtomExt("IN-REPLY-TO")), Atom(AtomExt("REPLY-TO")), Atom(AtomExt("LINES")), Atom(AtomExt("LIST-POST")), Atom(AtomExt("X-LABEL"))]+)), partial: None, peek: true }]), uid: false } }
|
||||
Fetch { sequence_set: SequenceSet([Range(Value(1), Value(2))]+), modifiers: [], macro_or_item_names: MessageDataItemNames([Uid, Rfc822Size, Flags, BodyExt { section: Some(HeaderFields(None, [Atom(AtomExt("From")), Atom(AtomExt("To")), Atom(AtomExt("Cc")), Atom(AtomExt("Bcc")), Atom(AtomExt("Subject")), Atom(AtomExt("Date")), Atom(AtomExt("Message-ID")), Atom(AtomExt("Priority")), Atom(AtomExt("X-Priority")), Atom(AtomExt("References")), Atom(AtomExt("Newsgroups")), Atom(AtomExt("In-Reply-To")), Atom(AtomExt("Content-Type")), Atom(AtomExt("Reply-To"))]+)), partial: None, peek: true }]), uid: true } }
|
||||
Fetch { sequence_set: SequenceSet([Range(Value(1), Value(2))]+), modifiers: [], macro_or_item_names: MessageDataItemNames([Uid]), uid: false } }
|
||||
Fetch { sequence_set: SequenceSet([Range(Value(1), Value(2)), Range(Value(11), Value(13)), Range(Value(18), Value(19)), Range(Value(22), Value(26))]+), modifiers: [ChangedSince(68)], macro_or_item_names: MessageDataItemNames([Flags]), uid: true } }
|
||||
Fetch { sequence_set: SequenceSet([Range(Value(1), Value(2)), Range(Value(11), Value(13)), Range(Value(18), Value(19)), Range(Value(22), Value(26))]+), modifiers: [ChangedSince(69)], macro_or_item_names: MessageDataItemNames([Flags]), uid: true } }
|
||||
Fetch { sequence_set: SequenceSet([Range(Value(1), Value(2)), Range(Value(11), Value(13)), Range(Value(18), Value(19)), Range(Value(22), Value(26)), Range(Value(33), Value(34))]+), modifiers: [ChangedSince(102)], macro_or_item_names: MessageDataItemNames([Flags]), uid: true } }
|
||||
Fetch { sequence_set: SequenceSet([Range(Value(1), Value(2)), Range(Value(11), Value(13)), Range(Value(18), Value(19)), Range(Value(22), Value(26)), Range(Value(33), Value(34))]+), modifiers: [ChangedSince(189)], macro_or_item_names: MessageDataItemNames([Flags]), uid: true } }
|
||||
Fetch { sequence_set: SequenceSet([Range(Value(1), Value(2)), Range(Value(11), Value(13)), Range(Value(18), Value(19)), Range(Value(22), Value(26)), Range(Value(33), Value(34))]+), modifiers: [ChangedSince(81)], macro_or_item_names: MessageDataItemNames([Flags]), uid: true } }
|
||||
Fetch { sequence_set: SequenceSet([Range(Value(1), Value(2)), Range(Value(11), Value(13)), Range(Value(18), Value(19)), Range(Value(22), Value(26)), Range(Value(33), Value(34)), Range(Value(60), Value(62))]+), modifiers: [ChangedSince(165)], macro_or_item_names: MessageDataItemNames([Flags]), uid: true } }
|
||||
Fetch { sequence_set: SequenceSet([Range(Value(1), Value(2)), Range(Value(11), Value(13)), Range(Value(18), Value(19)), Range(Value(22), Value(26)), Range(Value(33), Value(34)), Range(Value(60), Value(62))]+), modifiers: [ChangedSince(166)], macro_or_item_names: MessageDataItemNames([Flags]), uid: true } }
|
||||
Fetch { sequence_set: SequenceSet([Range(Value(1), Value(2)), Range(Value(11), Value(13)), Range(Value(18), Value(19)), Range(Value(22), Value(26)), Range(Value(33), Value(34)), Range(Value(60), Value(62))]+), modifiers: [ChangedSince(168)], macro_or_item_names: MessageDataItemNames([Flags]), uid: true } }
|
||||
Fetch { sequence_set: SequenceSet([Range(Value(1), Value(3))]+), modifiers: [], macro_or_item_names: MessageDataItemNames([Flags, Uid]), uid: false } }
|
||||
Fetch { sequence_set: SequenceSet([Range(Value(1), Value(3))]+), modifiers: [], macro_or_item_names: MessageDataItemNames([Uid, Flags]), uid: false } }
|
||||
Fetch { sequence_set: SequenceSet([Range(Value(1), Value(4))]+), modifiers: [], macro_or_item_names: MessageDataItemNames([InternalDate, Uid, Flags, Rfc822Size, BodyExt { section: Some(HeaderFields(None, [Atom(AtomExt("DATE")), Atom(AtomExt("FROM")), Atom(AtomExt("SUBJECT")), Atom(AtomExt("CONTENT-TYPE")), Atom(AtomExt("X-MS-TNEF-Correlator")), Atom(AtomExt("CONTENT-CLASS")), Atom(AtomExt("IMPORTANCE")), Atom(AtomExt("PRIORITY")), Atom(AtomExt("X-PRIORITY")), Atom(AtomExt("THREAD-TOPIC")), Atom(AtomExt("REPLY-TO"))]+)), partial: None, peek: true }, BodyStructure]), uid: false } }
|
||||
Fetch { sequence_set: SequenceSet([Range(Value(1), Value(4))]+), modifiers: [], macro_or_item_names: MessageDataItemNames([Uid, Flags, Envelope, InternalDate, BodyExt { section: Some(HeaderFields(None, [Atom(AtomExt("References"))]+)), partial: None, peek: true }]), uid: true } }
|
||||
Fetch { sequence_set: SequenceSet([Range(Value(1), Value(4))]+), modifiers: [], macro_or_item_names: MessageDataItemNames([Uid, Flags]), uid: true } }
|
||||
Fetch { sequence_set: SequenceSet([Range(Value(1), Value(6))]+), modifiers: [], macro_or_item_names: MessageDataItemNames([Uid, Flags, Envelope, InternalDate, BodyExt { section: Some(HeaderFields(None, [Atom(AtomExt("References"))]+)), partial: None, peek: true }]), uid: true } }
|
||||
Fetch { sequence_set: SequenceSet([Range(Value(1), Value(7))]+), modifiers: [ChangedSince(9)], macro_or_item_names: MessageDataItemNames([Flags]), uid: true } }
|
||||
Fetch { sequence_set: SequenceSet([Range(Value(1), Value(7))]+), modifiers: [], macro_or_item_names: MessageDataItemNames([Uid, Flags]), uid: false } }
|
||||
Fetch { sequence_set: SequenceSet([Range(Value(1), Value(7))]+), modifiers: [], macro_or_item_names: MessageDataItemNames([Uid, Flags]), uid: true } }
|
||||
Fetch { sequence_set: SequenceSet([Range(Value(1), Value(7))]+), modifiers: [], macro_or_item_names: MessageDataItemNames([Uid, Rfc822Size, BodyExt { section: None, partial: None, peek: true }]), uid: true } }
|
||||
Fetch { sequence_set: SequenceSet([Range(Value(20), Value(23))]+), modifiers: [], macro_or_item_names: MessageDataItemNames([Uid, Flags, Envelope, InternalDate, BodyExt { section: Some(HeaderFields(None, [Atom(AtomExt("References"))]+)), partial: None, peek: true }]), uid: true } }
|
||||
Fetch { sequence_set: SequenceSet([Range(Value(27), Asterisk)]+), modifiers: [], macro_or_item_names: MessageDataItemNames([Flags]), uid: true } }
|
||||
Fetch { sequence_set: SequenceSet([Range(Value(2), Asterisk)]+), modifiers: [], macro_or_item_names: MessageDataItemNames([Flags]), uid: true } }
|
||||
Fetch { sequence_set: SequenceSet([Range(Value(30), Asterisk)]+), modifiers: [], macro_or_item_names: MessageDataItemNames([Flags]), uid: true } }
|
||||
Fetch { sequence_set: SequenceSet([Range(Value(3), Asterisk)]+), modifiers: [], macro_or_item_names: MessageDataItemNames([Flags]), uid: true } }
|
||||
Fetch { sequence_set: SequenceSet([Range(Value(3), Asterisk)]+), modifiers: [], macro_or_item_names: MessageDataItemNames([Uid, Flags]), uid: true } }
|
||||
Fetch { sequence_set: SequenceSet([Range(Value(4), Asterisk)]+), modifiers: [], macro_or_item_names: MessageDataItemNames([Flags]), uid: true } }
|
||||
Fetch { sequence_set: SequenceSet([Range(Value(5), Asterisk)]+), modifiers: [], macro_or_item_names: MessageDataItemNames([Flags]), uid: true } }
|
||||
Fetch { sequence_set: SequenceSet([Range(Value(5), Value(7))]+), modifiers: [], macro_or_item_names: MessageDataItemNames([InternalDate, Uid, Flags, Rfc822Size, BodyExt { section: Some(HeaderFields(None, [Atom(AtomExt("DATE")), Atom(AtomExt("FROM")), Atom(AtomExt("SUBJECT")), Atom(AtomExt("CONTENT-TYPE")), Atom(AtomExt("X-MS-TNEF-Correlator")), Atom(AtomExt("CONTENT-CLASS")), Atom(AtomExt("IMPORTANCE")), Atom(AtomExt("PRIORITY")), Atom(AtomExt("X-PRIORITY")), Atom(AtomExt("THREAD-TOPIC")), Atom(AtomExt("REPLY-TO"))]+)), partial: None, peek: true }, BodyStructure]), uid: false } }
|
||||
Fetch { sequence_set: SequenceSet([Range(Value(5), Value(7))]+), modifiers: [], macro_or_item_names: MessageDataItemNames([Uid, Rfc822Size, Flags, BodyExt { section: Some(HeaderFields(None, [Atom(AtomExt("From")), Atom(AtomExt("To")), Atom(AtomExt("Cc")), Atom(AtomExt("Bcc")), Atom(AtomExt("Subject")), Atom(AtomExt("Date")), Atom(AtomExt("Message-ID")), Atom(AtomExt("Priority")), Atom(AtomExt("X-Priority")), Atom(AtomExt("References")), Atom(AtomExt("Newsgroups")), Atom(AtomExt("In-Reply-To")), Atom(AtomExt("Content-Type")), Atom(AtomExt("Reply-To"))]+)), partial: None, peek: true }]), uid: true } }
|
||||
Fetch { sequence_set: SequenceSet([Range(Value(60), Value(62))]+), modifiers: [], macro_or_item_names: MessageDataItemNames([InternalDate, Uid, Rfc822Size, Flags, ModSeq, BodyExt { section: Some(Header(None)), partial: None, peek: true }]), uid: true } }
|
||||
Fetch { sequence_set: SequenceSet([Range(Value(60), Value(62))]+), modifiers: [], macro_or_item_names: MessageDataItemNames([Uid, Rfc822Size, BodyExt { section: None, partial: None, peek: true }]), uid: true } }
|
||||
Fetch { sequence_set: SequenceSet([Range(Value(63), Asterisk)]+), modifiers: [], macro_or_item_names: MessageDataItemNames([Flags]), uid: true } }
|
||||
Fetch { sequence_set: SequenceSet([Range(Value(6), Asterisk)]+), modifiers: [], macro_or_item_names: MessageDataItemNames([Flags]), uid: true } }
|
||||
Fetch { sequence_set: SequenceSet([Range(Value(6), Value(7))]+), modifiers: [], macro_or_item_names: MessageDataItemNames([Uid, Flags, Envelope, InternalDate, BodyExt { section: Some(HeaderFields(None, [Atom(AtomExt("References"))]+)), partial: None, peek: true }]), uid: true } }
|
||||
Fetch { sequence_set: SequenceSet([Range(Value(6), Value(7))]+), modifiers: [], macro_or_item_names: MessageDataItemNames([Uid, Flags, InternalDate, Envelope, BodyStructure, Rfc822Size, BodyExt { section: Some(HeaderFields(None, [Atom(AtomExt("REFERENCES")), Atom(AtomExt("THREAD-TOPIC")), Atom(AtomExt("FROM")), Atom(AtomExt("SENDER")), Atom(AtomExt("REPLY-TO"))]+)), partial: None, peek: true }]), uid: true } }
|
||||
Fetch { sequence_set: SequenceSet([Range(Value(6), Value(7))]+), modifiers: [], macro_or_item_names: MessageDataItemNames([Uid, Rfc822Size, Flags, BodyExt { section: Some(HeaderFields(None, [Atom(AtomExt("From")), Atom(AtomExt("To")), Atom(AtomExt("Cc")), Atom(AtomExt("Bcc")), Atom(AtomExt("Subject")), Atom(AtomExt("Date")), Atom(AtomExt("Message-ID")), Atom(AtomExt("Priority")), Atom(AtomExt("X-Priority")), Atom(AtomExt("References")), Atom(AtomExt("Newsgroups")), Atom(AtomExt("In-Reply-To")), Atom(AtomExt("Content-Type")), Atom(AtomExt("Reply-To"))]+)), partial: None, peek: true }]), uid: true } }
|
||||
Fetch { sequence_set: SequenceSet([Range(Value(86), Asterisk)]+), modifiers: [], macro_or_item_names: MessageDataItemNames([Flags]), uid: true } }
|
||||
Fetch { sequence_set: SequenceSet([Range(Value(8), Asterisk)]+), modifiers: [], macro_or_item_names: MessageDataItemNames([Flags]), uid: true } }
|
||||
Fetch { sequence_set: SequenceSet([Range(Value(9), Asterisk)]+), modifiers: [], macro_or_item_names: MessageDataItemNames([Flags]), uid: true } }
|
||||
Fetch { sequence_set: SequenceSet([Single(Value(13))]+), modifiers: [], macro_or_item_names: MessageDataItemNames([BodyExt { section: Some(Header(None)), partial: None, peek: true }, BodyExt { section: Some(Text(None)), partial: None, peek: true }]), uid: true } }
|
||||
Fetch { sequence_set: SequenceSet([Single(Value(1))]+), modifiers: [], macro_or_item_names: MessageDataItemNames([BodyExt { section: None, partial: None, peek: true }]), uid: true } }
|
||||
Fetch { sequence_set: SequenceSet([Single(Value(1))]+), modifiers: [], macro_or_item_names: MessageDataItemNames([Flags, Uid]), uid: false } }
|
||||
Fetch { sequence_set: SequenceSet([Single(Value(1))]+), modifiers: [], macro_or_item_names: MessageDataItemNames([Rfc822Header, BodyExt { section: Some(Part(Part([1]+))), partial: None, peek: true }]), uid: true } }
|
||||
Fetch { sequence_set: SequenceSet([Single(Value(1))]+), modifiers: [], macro_or_item_names: MessageDataItemNames([Uid, Flags]), uid: true } }
|
||||
Fetch { sequence_set: SequenceSet([Single(Value(1))]+), modifiers: [], macro_or_item_names: MessageDataItemNames([Uid, Rfc822Size, BodyExt { section: None, partial: None, peek: false }]), uid: true } }
|
||||
Fetch { sequence_set: SequenceSet([Single(Value(1))]+), modifiers: [], macro_or_item_names: MessageDataItemNames([Uid, Rfc822Size, BodyExt { section: None, partial: None, peek: true }]), uid: true } }
|
||||
Fetch { sequence_set: SequenceSet([Single(Value(1))]+), modifiers: [], macro_or_item_names: MessageDataItemNames([Uid, Rfc822Size, Flags, BodyExt { section: Some(HeaderFields(None, [Atom(AtomExt("From")), Atom(AtomExt("To")), Atom(AtomExt("Cc")), Atom(AtomExt("Bcc")), Atom(AtomExt("Resent-Message-ID")), Atom(AtomExt("Subject")), Atom(AtomExt("Date")), Atom(AtomExt("Message-ID")), Atom(AtomExt("Priority")), Atom(AtomExt("X-Priority")), Atom(AtomExt("References")), Atom(AtomExt("Newsgroups")), Atom(AtomExt("In-Reply-To")), Atom(AtomExt("Content-Type")), Atom(AtomExt("Reply-To")), Atom(AtomExt("List-Unsubscribe")), Atom(AtomExt("Received")), Atom(AtomExt("Delivery-Date"))]+)), partial: None, peek: true }]), uid: true } }
|
||||
Fetch { sequence_set: SequenceSet([Single(Value(1))]+), modifiers: [], macro_or_item_names: MessageDataItemNames([Uid]), uid: true } }
|
||||
Fetch { sequence_set: SequenceSet([Single(Value(1)), Single(Value(2))]+), modifiers: [], macro_or_item_names: MessageDataItemNames([BodyExt { section: Some(HeaderFields(None, [Atom(AtomExt("bcc")), Atom(AtomExt("cc")), Atom(AtomExt("date")), Atom(AtomExt("from")), Atom(AtomExt("reply-to")), Atom(AtomExt("sender")), Atom(AtomExt("subject")), Atom(AtomExt("to"))]+)), partial: None, peek: true }]), uid: true } }
|
||||
Fetch { sequence_set: SequenceSet([Single(Value(1)), Single(Value(2))]+), modifiers: [], macro_or_item_names: MessageDataItemNames([BodyExt { section: Some(HeaderFields(None, [Atom(AtomExt("in-reply-to")), Atom(AtomExt("message-id")), Atom(AtomExt("references"))]+)), partial: None, peek: true }]), uid: true } }
|
||||
Fetch { sequence_set: SequenceSet([Single(Value(1)), Single(Value(2))]+), modifiers: [], macro_or_item_names: MessageDataItemNames([BodyExt { section: Some(Text(None)), partial: None, peek: true }]), uid: true } }
|
||||
Fetch { sequence_set: SequenceSet([Single(Value(1)), Single(Value(2))]+), modifiers: [], macro_or_item_names: MessageDataItemNames([InternalDate, Rfc822Size, Flags]), uid: true } }
|
||||
Fetch { sequence_set: SequenceSet([Single(Value(1)), Single(Value(2))]+), modifiers: [], macro_or_item_names: MessageDataItemNames([Rfc822Header]), uid: true } }
|
||||
Fetch { sequence_set: SequenceSet([Single(Value(22))]+), modifiers: [], macro_or_item_names: MessageDataItemNames([BodyExt { section: None, partial: None, peek: true }]), uid: true } }
|
||||
Fetch { sequence_set: SequenceSet([Single(Value(24))]+), modifiers: [], macro_or_item_names: MessageDataItemNames([Uid, BodyExt { section: Some(Text(None)), partial: None, peek: true }]), uid: true } }
|
||||
Fetch { sequence_set: SequenceSet([Single(Value(24))]+), modifiers: [], macro_or_item_names: MessageDataItemNames([Uid, Flags, InternalDate, Envelope, BodyStructure, Rfc822Size, BodyExt { section: Some(HeaderFields(None, [Atom(AtomExt("REFERENCES")), Atom(AtomExt("THREAD-TOPIC")), Atom(AtomExt("FROM")), Atom(AtomExt("SENDER")), Atom(AtomExt("REPLY-TO"))]+)), partial: None, peek: true }]), uid: true } }
|
||||
Fetch { sequence_set: SequenceSet([Single(Value(24))]+), modifiers: [], macro_or_item_names: MessageDataItemNames([Uid, Rfc822Size, BodyExt { section: None, partial: None, peek: true }]), uid: true } }
|
||||
Fetch { sequence_set: SequenceSet([Single(Value(24))]+), modifiers: [], macro_or_item_names: MessageDataItemNames([Uid, Rfc822Size, Flags, BodyExt { section: Some(HeaderFields(None, [Atom(AtomExt("From")), Atom(AtomExt("To")), Atom(AtomExt("Cc")), Atom(AtomExt("Bcc")), Atom(AtomExt("Subject")), Atom(AtomExt("Date")), Atom(AtomExt("Message-ID")), Atom(AtomExt("Priority")), Atom(AtomExt("X-Priority")), Atom(AtomExt("References")), Atom(AtomExt("Newsgroups")), Atom(AtomExt("In-Reply-To")), Atom(AtomExt("Content-Type")), Atom(AtomExt("Reply-To"))]+)), partial: None, peek: true }]), uid: true } }
|
||||
Fetch { sequence_set: SequenceSet([Single(Value(24)), Single(Value(1))]+), modifiers: [], macro_or_item_names: MessageDataItemNames([Uid, Rfc822Size, Flags, BodyExt { section: Some(HeaderFields(None, [Atom(AtomExt("From")), Atom(AtomExt("To")), Atom(AtomExt("Cc")), Atom(AtomExt("Bcc")), Atom(AtomExt("Resent-Message-ID")), Atom(AtomExt("Subject")), Atom(AtomExt("Date")), Atom(AtomExt("Message-ID")), Atom(AtomExt("Priority")), Atom(AtomExt("X-Priority")), Atom(AtomExt("References")), Atom(AtomExt("Newsgroups")), Atom(AtomExt("In-Reply-To")), Atom(AtomExt("Content-Type")), Atom(AtomExt("Reply-To")), Atom(AtomExt("List-Unsubscribe")), Atom(AtomExt("Received")), Atom(AtomExt("Delivery-Date"))]+)), partial: None, peek: true }]), uid: true } }
|
||||
Fetch { sequence_set: SequenceSet([Single(Value(29))]+), modifiers: [], macro_or_item_names: MessageDataItemNames([Uid, Rfc822Size, BodyExt { section: None, partial: None, peek: true }]), uid: true } }
|
||||
Fetch { sequence_set: SequenceSet([Single(Value(29))]+), modifiers: [], macro_or_item_names: MessageDataItemNames([Uid, Rfc822Size, Flags, BodyExt { section: Some(HeaderFields(None, [Atom(AtomExt("From")), Atom(AtomExt("To")), Atom(AtomExt("Cc")), Atom(AtomExt("Bcc")), Atom(AtomExt("Subject")), Atom(AtomExt("Date")), Atom(AtomExt("Message-ID")), Atom(AtomExt("Priority")), Atom(AtomExt("X-Priority")), Atom(AtomExt("References")), Atom(AtomExt("Newsgroups")), Atom(AtomExt("In-Reply-To")), Atom(AtomExt("Content-Type")), Atom(AtomExt("Reply-To"))]+)), partial: None, peek: true }]), uid: true } }
|
||||
Fetch { sequence_set: SequenceSet([Single(Value(2))]+), modifiers: [], macro_or_item_names: MessageDataItemNames([BodyExt { section: None, partial: None, peek: true }]), uid: true } }
|
||||
Fetch { sequence_set: SequenceSet([Single(Value(2))]+), modifiers: [], macro_or_item_names: MessageDataItemNames([BodyExt { section: Some(Text(None)), partial: None, peek: true }]), uid: false } }
|
||||
Fetch { sequence_set: SequenceSet([Single(Value(2))]+), modifiers: [], macro_or_item_names: MessageDataItemNames([Envelope, InternalDate, Rfc822Size, Flags, BodyStructure, Uid, BodyExt { section: Some(Header(None)), partial: None, peek: true }, Rfc822Size, InternalDate]), uid: false } }
|
||||
Fetch { sequence_set: SequenceSet([Single(Value(2))]+), modifiers: [], macro_or_item_names: MessageDataItemNames([Rfc822Header, BodyExt { section: Some(Part(Part([1]+))), partial: None, peek: true }]), uid: true } }
|
||||
Fetch { sequence_set: SequenceSet([Single(Value(2))]+), modifiers: [], macro_or_item_names: MessageDataItemNames([Rfc822Header, BodyExt { section: Some(Part(Part([2]+))), partial: None, peek: true }]), uid: true } }
|
||||
Fetch { sequence_set: SequenceSet([Single(Value(2))]+), modifiers: [], macro_or_item_names: MessageDataItemNames([Uid, Rfc822Size, BodyExt { section: None, partial: None, peek: true }]), uid: true } }
|
||||
Fetch { sequence_set: SequenceSet([Single(Value(2)), Single(Value(8))]+), modifiers: [], macro_or_item_names: MessageDataItemNames([Uid, Rfc822Size, Flags, BodyExt { section: Some(HeaderFields(None, [Atom(AtomExt("From")), Atom(AtomExt("To")), Atom(AtomExt("Cc")), Atom(AtomExt("Bcc")), Atom(AtomExt("Subject")), Atom(AtomExt("Date")), Atom(AtomExt("Message-ID")), Atom(AtomExt("Priority")), Atom(AtomExt("X-Priority")), Atom(AtomExt("References")), Atom(AtomExt("Newsgroups")), Atom(AtomExt("In-Reply-To")), Atom(AtomExt("Content-Type")), Atom(AtomExt("Reply-To"))]+)), partial: None, peek: true }]), uid: true } }
|
||||
Fetch { sequence_set: SequenceSet([Single(Value(3))]+), modifiers: [], macro_or_item_names: MessageDataItemNames([BodyExt { section: None, partial: None, peek: true }]), uid: true } }
|
||||
Fetch { sequence_set: SequenceSet([Single(Value(3))]+), modifiers: [], macro_or_item_names: MessageDataItemNames([InternalDate, Uid, Flags, Rfc822Size, BodyExt { section: Some(HeaderFields(None, [Atom(AtomExt("DATE")), Atom(AtomExt("FROM")), Atom(AtomExt("SUBJECT")), Atom(AtomExt("CONTENT-TYPE")), Atom(AtomExt("X-MS-TNEF-Correlator")), Atom(AtomExt("CONTENT-CLASS")), Atom(AtomExt("IMPORTANCE")), Atom(AtomExt("PRIORITY")), Atom(AtomExt("X-PRIORITY")), Atom(AtomExt("THREAD-TOPIC")), Atom(AtomExt("REPLY-TO"))]+)), partial: None, peek: true }, BodyStructure]), uid: false } }
|
||||
Fetch { sequence_set: SequenceSet([Single(Value(3))]+), modifiers: [], macro_or_item_names: MessageDataItemNames([Rfc822Header, BodyExt { section: Some(Part(Part([1]+))), partial: None, peek: true }]), uid: true } }
|
||||
Fetch { sequence_set: SequenceSet([Single(Value(3))]+), modifiers: [], macro_or_item_names: MessageDataItemNames([Rfc822Header, BodyExt { section: Some(Part(Part([2]+))), partial: Some((0, 10240)), peek: true }]), uid: true } }
|
||||
Fetch { sequence_set: SequenceSet([Single(Value(3))]+), modifiers: [], macro_or_item_names: MessageDataItemNames([Uid, BodyStructure, InternalDate, Rfc822Size]), uid: true } }
|
||||
Fetch { sequence_set: SequenceSet([Single(Value(3))]+), modifiers: [], macro_or_item_names: MessageDataItemNames([Uid, Flags, InternalDate, Envelope, BodyStructure, Rfc822Size, BodyExt { section: Some(HeaderFields(None, [Atom(AtomExt("REFERENCES")), Atom(AtomExt("THREAD-TOPIC")), Atom(AtomExt("FROM")), Atom(AtomExt("SENDER")), Atom(AtomExt("REPLY-TO")), Atom(AtomExt("AUTO-SUBMITTED")), Atom(AtomExt("BOUNCES-TO")), Atom(AtomExt("LIST-ARCHIVE")), Atom(AtomExt("LIST-HELP")), Atom(AtomExt("LIST-ID")), Atom(AtomExt("LIST-OWNER")), Atom(AtomExt("LIST-POST")), Atom(AtomExt("LIST-SUBSCRIBE")), Atom(AtomExt("LIST-UNSUBSCRIBE")), Atom(AtomExt("PRECEDENCE")), Atom(AtomExt("RESENT-FROM")), Atom(AtomExt("RETURN-PATH"))]+)), partial: None, peek: true }, BodyExt { section: Some(Text(None)), partial: None, peek: true }]), uid: true } }
|
||||
Fetch { sequence_set: SequenceSet([Single(Value(3))]+), modifiers: [], macro_or_item_names: MessageDataItemNames([Uid, Rfc822Size, BodyExt { section: None, partial: None, peek: true }]), uid: true } }
|
||||
Fetch { sequence_set: SequenceSet([Single(Value(3))]+), modifiers: [], macro_or_item_names: MessageDataItemNames([Uid, Rfc822Size, Flags, BodyExt { section: Some(HeaderFields(None, [Atom(AtomExt("From")), Atom(AtomExt("To")), Atom(AtomExt("Cc")), Atom(AtomExt("Bcc")), Atom(AtomExt("Subject")), Atom(AtomExt("Date")), Atom(AtomExt("Message-ID")), Atom(AtomExt("Priority")), Atom(AtomExt("X-Priority")), Atom(AtomExt("References")), Atom(AtomExt("Newsgroups")), Atom(AtomExt("In-Reply-To")), Atom(AtomExt("Content-Type")), Atom(AtomExt("Reply-To"))]+)), partial: None, peek: true }]), uid: true } }
|
||||
Fetch { sequence_set: SequenceSet([Single(Value(3)), Single(Value(2)), Single(Value(1))]+), modifiers: [], macro_or_item_names: MessageDataItemNames([Uid, Rfc822Size, Flags, BodyExt { section: Some(HeaderFields(None, [Atom(AtomExt("From")), Atom(AtomExt("To")), Atom(AtomExt("Cc")), Atom(AtomExt("Bcc")), Atom(AtomExt("Resent-Message-ID")), Atom(AtomExt("Subject")), Atom(AtomExt("Date")), Atom(AtomExt("Message-ID")), Atom(AtomExt("Priority")), Atom(AtomExt("X-Priority")), Atom(AtomExt("References")), Atom(AtomExt("Newsgroups")), Atom(AtomExt("In-Reply-To")), Atom(AtomExt("Content-Type")), Atom(AtomExt("Reply-To")), Atom(AtomExt("List-Unsubscribe")), Atom(AtomExt("Received")), Atom(AtomExt("Delivery-Date"))]+)), partial: None, peek: true }]), uid: true } }
|
||||
Fetch { sequence_set: SequenceSet([Single(Value(4))]+), modifiers: [], macro_or_item_names: MessageDataItemNames([BodyExt { section: None, partial: None, peek: true }]), uid: true } }
|
||||
Fetch { sequence_set: SequenceSet([Single(Value(4))]+), modifiers: [], macro_or_item_names: MessageDataItemNames([Rfc822Header, BodyExt { section: Some(Part(Part([2, 1]+))), partial: None, peek: true }, BodyExt { section: Some(Part(Part([2, 2]+))), partial: None, peek: true }]), uid: true } }
|
||||
Fetch { sequence_set: SequenceSet([Single(Value(4))]+), modifiers: [], macro_or_item_names: MessageDataItemNames([Uid]), uid: true } }
|
||||
Fetch { sequence_set: SequenceSet([Single(Value(4)), Single(Value(3)), Single(Value(2)), Single(Value(1))]+), modifiers: [], macro_or_item_names: MessageDataItemNames([Uid, Flags]), uid: true } }
|
||||
Fetch { sequence_set: SequenceSet([Single(Value(5))]+), modifiers: [], macro_or_item_names: MessageDataItemNames([BodyExt { section: None, partial: None, peek: true }]), uid: true } }
|
||||
Fetch { sequence_set: SequenceSet([Single(Value(5))]+), modifiers: [], macro_or_item_names: MessageDataItemNames([Rfc822Header, BodyExt { section: Some(Part(Part([1, 2]+))), partial: None, peek: true }]), uid: true } }
|
||||
Fetch { sequence_set: SequenceSet([Single(Value(5))]+), modifiers: [], macro_or_item_names: MessageDataItemNames([Uid, Rfc822Size, BodyExt { section: None, partial: None, peek: true }]), uid: true } }
|
||||
Fetch { sequence_set: SequenceSet([Single(Value(5))]+), modifiers: [], macro_or_item_names: MessageDataItemNames([Uid, Rfc822Size, Flags, BodyExt { section: Some(HeaderFields(None, [Atom(AtomExt("From")), Atom(AtomExt("To")), Atom(AtomExt("Cc")), Atom(AtomExt("Bcc")), Atom(AtomExt("Subject")), Atom(AtomExt("Date")), Atom(AtomExt("Message-ID")), Atom(AtomExt("Priority")), Atom(AtomExt("X-Priority")), Atom(AtomExt("References")), Atom(AtomExt("Newsgroups")), Atom(AtomExt("In-Reply-To")), Atom(AtomExt("Content-Type")), Atom(AtomExt("Reply-To"))]+)), partial: None, peek: true }]), uid: true } }
|
||||
Fetch { sequence_set: SequenceSet([Single(Value(60))]+), modifiers: [], macro_or_item_names: MessageDataItemNames([BodyExt { section: Some(Part(Part([1, 2]+))), partial: Some((0, 2227)), peek: true }]), uid: true } }
|
||||
Fetch { sequence_set: SequenceSet([Single(Value(60))]+), modifiers: [], macro_or_item_names: MessageDataItemNames([BodyExt { section: Some(Part(Part([2]+))), partial: None, peek: true }]), uid: true } }
|
||||
Fetch { sequence_set: SequenceSet([Single(Value(60))]+), modifiers: [], macro_or_item_names: MessageDataItemNames([BodyStructure, BodyExt { section: Some(Header(None)), partial: None, peek: true }]), uid: true } }
|
||||
Fetch { sequence_set: SequenceSet([Single(Value(60))]+), modifiers: [], macro_or_item_names: MessageDataItemNames([Uid, Rfc822Size, BodyExt { section: None, partial: None, peek: true }]), uid: true } }
|
||||
Fetch { sequence_set: SequenceSet([Single(Value(61))]+), modifiers: [], macro_or_item_names: MessageDataItemNames([BodyExt { section: Some(Header(None)), partial: None, peek: true }, BodyExt { section: Some(Text(None)), partial: None, peek: true }]), uid: true } }
|
||||
Fetch { sequence_set: SequenceSet([Single(Value(62))]+), modifiers: [], macro_or_item_names: MessageDataItemNames([BodyExt { section: Some(Part(Part([1, 2]+))), partial: Some((0, 711)), peek: true }]), uid: true } }
|
||||
Fetch { sequence_set: SequenceSet([Single(Value(62))]+), modifiers: [], macro_or_item_names: MessageDataItemNames([BodyExt { section: Some(Part(Part([2]+))), partial: None, peek: true }]), uid: true } }
|
||||
Fetch { sequence_set: SequenceSet([Single(Value(62))]+), modifiers: [], macro_or_item_names: MessageDataItemNames([BodyStructure, BodyExt { section: Some(Header(None)), partial: None, peek: true }]), uid: true } }
|
||||
Fetch { sequence_set: SequenceSet([Single(Value(62))]+), modifiers: [], macro_or_item_names: MessageDataItemNames([Uid, Rfc822Size, BodyExt { section: None, partial: None, peek: true }]), uid: true } }
|
||||
Fetch { sequence_set: SequenceSet([Single(Value(6))]+), modifiers: [], macro_or_item_names: MessageDataItemNames([Rfc822Header, BodyExt { section: Some(Part(Part([2]+))), partial: None, peek: true }]), uid: true } }
|
||||
Fetch { sequence_set: SequenceSet([Single(Value(6))]+), modifiers: [], macro_or_item_names: MessageDataItemNames([Uid, BodyExt { section: Some(Part(Part([2]+))), partial: None, peek: true }]), uid: true } }
|
||||
Fetch { sequence_set: SequenceSet([Single(Value(6))]+), modifiers: [], macro_or_item_names: MessageDataItemNames([Uid, Flags, Envelope, BodyExt { section: Some(HeaderFields(None, [Atom(AtomExt("References"))]+)), partial: None, peek: true }]), uid: true } }
|
||||
Fetch { sequence_set: SequenceSet([Single(Value(6))]+), modifiers: [], macro_or_item_names: MessageDataItemNames([Uid, Rfc822Size, BodyExt { section: None, partial: None, peek: true }]), uid: true } }
|
||||
Fetch { sequence_set: SequenceSet([Single(Value(7))]+), modifiers: [], macro_or_item_names: MessageDataItemNames([BodyExt { section: Some(Header(None)), partial: None, peek: true }, BodyExt { section: Some(Text(None)), partial: None, peek: true }]), uid: true } }
|
||||
Fetch { sequence_set: SequenceSet([Single(Value(7))]+), modifiers: [], macro_or_item_names: MessageDataItemNames([InternalDate, Uid, Rfc822Size, Flags, ModSeq, BodyExt { section: Some(Header(None)), partial: None, peek: true }]), uid: true } }
|
||||
Fetch { sequence_set: SequenceSet([Single(Value(7))]+), modifiers: [], macro_or_item_names: MessageDataItemNames([Rfc822Header, BodyExt { section: Some(Part(Part([1, 2]+))), partial: None, peek: true }, BodyExt { section: Some(Part(Part([2]+))), partial: None, peek: true }]), uid: true } }
|
||||
Fetch { sequence_set: SequenceSet([Single(Value(7))]+), modifiers: [], macro_or_item_names: MessageDataItemNames([Uid, BodyExt { section: Some(Part(Part([1, 2]+))), partial: None, peek: true }]), uid: true } }
|
||||
Fetch { sequence_set: SequenceSet([Single(Value(7))]+), modifiers: [], macro_or_item_names: MessageDataItemNames([Uid, Rfc822Size, BodyExt { section: None, partial: None, peek: true }]), uid: true } }
|
||||
Fetch { sequence_set: SequenceSet([Single(Value(7)), Single(Value(6)), Single(Value(5)), Single(Value(4)), Single(Value(3)), Single(Value(2)), Single(Value(1))]+), modifiers: [], macro_or_item_names: MessageDataItemNames([Uid, Rfc822Size, Flags, BodyExt { section: Some(HeaderFields(None, [Atom(AtomExt("From")), Atom(AtomExt("To")), Atom(AtomExt("Cc")), Atom(AtomExt("Bcc")), Atom(AtomExt("Resent-Message-ID")), Atom(AtomExt("Subject")), Atom(AtomExt("Date")), Atom(AtomExt("Message-ID")), Atom(AtomExt("Priority")), Atom(AtomExt("X-Priority")), Atom(AtomExt("References")), Atom(AtomExt("Newsgroups")), Atom(AtomExt("In-Reply-To")), Atom(AtomExt("Content-Type")), Atom(AtomExt("Reply-To")), Atom(AtomExt("List-Unsubscribe")), Atom(AtomExt("Received")), Atom(AtomExt("Delivery-Date"))]+)), partial: None, peek: true }]), uid: true } }
|
||||
Fetch { sequence_set: SequenceSet([Single(Value(83))]+), modifiers: [], macro_or_item_names: MessageDataItemNames([BodyExt { section: Some(Header(None)), partial: None, peek: true }, BodyExt { section: Some(Text(None)), partial: None, peek: true }]), uid: true } }
|
||||
Fetch { sequence_set: SequenceSet([Single(Value(83))]+), modifiers: [], macro_or_item_names: MessageDataItemNames([InternalDate, Uid, Rfc822Size, Flags, ModSeq, BodyExt { section: Some(Header(None)), partial: None, peek: true }]), uid: true } }
|
||||
Fetch { sequence_set: SequenceSet([Single(Value(85))]+), modifiers: [], macro_or_item_names: MessageDataItemNames([InternalDate, Uid, Rfc822Size, Flags, ModSeq, BodyExt { section: Some(Header(None)), partial: None, peek: true }]), uid: true } }
|
||||
Idle
|
||||
List { reference: Other(MailboxOther(String(Quoted(Quoted("Archive."))))), mailbox_wildcard: Token(ListCharString("*")), return: [] } }
|
||||
List { reference: Other(MailboxOther(String(Quoted(Quoted("INBOX."))))), mailbox_wildcard: Token(ListCharString("*")), return: [] } }
|
||||
List { reference: Other(MailboxOther(String(Quoted(Quoted(""))))), mailbox_wildcard: String(Quoted(Quoted("Dataset"))), return: [] } }
|
||||
List { reference: Other(MailboxOther(String(Quoted(Quoted(""))))), mailbox_wildcard: String(Quoted(Quoted("INBOX"))), return: [] } }
|
||||
List { reference: Other(MailboxOther(String(Quoted(Quoted(""))))), mailbox_wildcard: String(Quoted(Quoted(""))), return: [] } }
|
||||
List { reference: Other(MailboxOther(String(Quoted(Quoted(""))))), mailbox_wildcard: String(Quoted(Quoted("*"))), return: [] } }
|
||||
List { reference: Other(MailboxOther(String(Quoted(Quoted(""))))), mailbox_wildcard: String(Quoted(Quoted("*"))), return: [Status([Unseen])] } }
|
||||
List { reference: Other(MailboxOther(String(Quoted(Quoted(""))))), mailbox_wildcard: Token(ListCharString("INBOX")), return: [] } }
|
||||
List { reference: Other(MailboxOther(String(Quoted(Quoted(""))))), mailbox_wildcard: Token(ListCharString("%")), return: [] } }
|
||||
List { reference: Other(MailboxOther(String(Quoted(Quoted("Mailspring."))))), mailbox_wildcard: Token(ListCharString("*")), return: [] } }
|
||||
List { reference: Other(MailboxOther(String(Quoted(Quoted("Sent."))))), mailbox_wildcard: Token(ListCharString("*")), return: [] } }
|
||||
List { reference: Other(MailboxOther(String(Quoted(Quoted("Trash."))))), mailbox_wildcard: Token(ListCharString("*")), return: [] } }
|
||||
Login { username: Atom(AtomExt("REDACTED")), password: /* REDACTED */ } }
|
||||
Login { username: String(Quoted(Quoted("REDACTED"))), password: /* REDACTED */ } }
|
||||
Login { username: String(Quoted(Quoted("REDACTED@saint-ex.deuxfleurs.org"))), password: /* REDACTED */ } }
|
||||
Logout
|
||||
Lsub { reference: Other(MailboxOther(String(Quoted(Quoted(""))))), mailbox_wildcard: String(Quoted(Quoted("*"))) } }
|
||||
Move { sequence_set: SequenceSet([Range(Value(20), Value(21))]+), mailbox: Other(MailboxOther(String(Quoted(Quoted("Dataset"))))), uid: true } }
|
||||
Move { sequence_set: SequenceSet([Single(Value(29))]+), mailbox: Other(MailboxOther(String(Quoted(Quoted("Dataset"))))), uid: true } }
|
||||
Noop
|
||||
Search { charset: None, criteria: And([Header(Atom(AtomExt("Message-ID")), Atom(AtomExt("<Mailbird-8d3b03f5-7737-479e-b8d7-a3cae013e72f@saint-ex.deuxfleurs.org>"))), Undeleted]+), uid: true } }
|
||||
Search { charset: None, criteria: And([SequenceSet(SequenceSet([Range(Value(1), Asterisk)]+)), Deleted]+), uid: true } }
|
||||
Search { charset: None, criteria: And([SequenceSet(SequenceSet([Range(Value(1), Asterisk)]+)), Not(Deleted)]+), uid: true } }
|
||||
Search { charset: None, criteria: And([SequenceSet(SequenceSet([Range(Value(1), Asterisk)]+)), Since(2023-11-16)]+), uid: true } }
|
||||
Search { charset: None, criteria: And([SequenceSet(SequenceSet([Range(Value(1), Asterisk)]+)), Unseen]+), uid: true } }
|
||||
Search { charset: None, criteria: And([SequenceSet(SequenceSet([Range(Value(1), Value(1))]+)), Not(Deleted)]+), uid: true } }
|
||||
Search { charset: None, criteria: And([SequenceSet(SequenceSet([Range(Value(1), Value(4))]+)), Not(Deleted)]+), uid: true } }
|
||||
Search { charset: None, criteria: And([Since(2024-02-07), All]+), uid: false } }
|
||||
Search { charset: None, criteria: And([Since(2024-02-08), All]+), uid: false } }
|
||||
Search { charset: None, criteria: And([Since(2024-02-09), All]+), uid: false } }
|
||||
Search { charset: None, criteria: And([Undeleted, Since(2023-11-17)]+), uid: false } }
|
||||
Search { charset: None, criteria: And([Undeleted, Since(2024-02-13)]+), uid: false } }
|
||||
Search { charset: None, criteria: Before(2024-02-09), uid: true } }
|
||||
Search { charset: None, criteria: Since(2024-01-31), uid: true } }
|
||||
Select { mailbox: Inbox, modifiers: [] } }
|
||||
Select { mailbox: Inbox, modifiers: [Condstore] } }
|
||||
Select { mailbox: Other(MailboxOther(Atom(AtomExt("Archive")))), modifiers: [] } }
|
||||
Select { mailbox: Other(MailboxOther(Atom(AtomExt("Drafts")))), modifiers: [] } }
|
||||
Select { mailbox: Other(MailboxOther(Atom(AtomExt("Mailspring")))), modifiers: [] } }
|
||||
Select { mailbox: Other(MailboxOther(Atom(AtomExt("Sent")))), modifiers: [] } }
|
||||
Select { mailbox: Other(MailboxOther(Atom(AtomExt("Test.Coucou")))), modifiers: [] } }
|
||||
Select { mailbox: Other(MailboxOther(Atom(AtomExt("Test")))), modifiers: [] } }
|
||||
Select { mailbox: Other(MailboxOther(Atom(AtomExt("Trash")))), modifiers: [] } }
|
||||
Select { mailbox: Other(MailboxOther(String(Quoted(Quoted("Archive"))))), modifiers: [] } }
|
||||
Select { mailbox: Other(MailboxOther(String(Quoted(Quoted("Dataset"))))), modifiers: [] } }
|
||||
Select { mailbox: Other(MailboxOther(String(Quoted(Quoted("Drafts"))))), modifiers: [] } }
|
||||
Select { mailbox: Other(MailboxOther(String(Quoted(Quoted("INBOX.Pourriel"))))), modifiers: [] } }
|
||||
Select { mailbox: Other(MailboxOther(String(Quoted(Quoted("Mailspring"))))), modifiers: [] } }
|
||||
Select { mailbox: Other(MailboxOther(String(Quoted(Quoted("Mailspring.Snoozed"))))), modifiers: [] } }
|
||||
Select { mailbox: Other(MailboxOther(String(Quoted(Quoted("Sent"))))), modifiers: [] } }
|
||||
Select { mailbox: Other(MailboxOther(String(Quoted(Quoted("Sent"))))), modifiers: [Condstore] } }
|
||||
Select { mailbox: Other(MailboxOther(String(Quoted(Quoted("Trash"))))), modifiers: [] } }
|
||||
Status { mailbox: Inbox, item_names: [Messages, Recent, UidNext, UidValidity, Unseen] } }
|
||||
Status { mailbox: Inbox, item_names: [Unseen, Messages, Recent, UidNext, UidValidity, HighestModSeq] } }
|
||||
Status { mailbox: Other(MailboxOther(Atom(AtomExt("Archive")))), item_names: [Unseen, Messages, Recent, UidNext, UidValidity, HighestModSeq] } }
|
||||
Status { mailbox: Other(MailboxOther(Atom(AtomExt("Drafts")))), item_names: [Unseen, Messages, Recent, UidNext, UidValidity, HighestModSeq] } }
|
||||
Status { mailbox: Other(MailboxOther(Atom(AtomExt("Mailspring")))), item_names: [Unseen, Messages, Recent, UidNext, UidValidity, HighestModSeq] } }
|
||||
Status { mailbox: Other(MailboxOther(Atom(AtomExt("Sent")))), item_names: [Unseen, Messages, Recent, UidNext, UidValidity, HighestModSeq] } }
|
||||
Status { mailbox: Other(MailboxOther(Atom(AtomExt("Trash")))), item_names: [Unseen, Messages, Recent, UidNext, UidValidity, HighestModSeq] } }
|
||||
Status { mailbox: Other(MailboxOther(String(Quoted(Quoted("Archive"))))), item_names: [UidNext, Messages, Unseen, Recent] } }
|
||||
Status { mailbox: Other(MailboxOther(String(Quoted(Quoted("Archive"))))), item_names: [UidNext, UidValidity, Unseen, Recent] } }
|
||||
Status { mailbox: Other(MailboxOther(String(Quoted(Quoted("Drafts"))))), item_names: [Messages] } }
|
||||
Status { mailbox: Other(MailboxOther(String(Quoted(Quoted("Drafts"))))), item_names: [UidNext, Messages, Unseen, Recent] } }
|
||||
Status { mailbox: Other(MailboxOther(String(Quoted(Quoted("Drafts"))))), item_names: [UidNext, UidValidity, Unseen, Recent] } }
|
||||
Status { mailbox: Other(MailboxOther(String(Quoted(Quoted("INBOX.Pourriel"))))), item_names: [UidNext, Messages, Unseen, Recent] } }
|
||||
Status { mailbox: Other(MailboxOther(String(Quoted(Quoted("INBOX.Pourriel"))))), item_names: [Unseen, Messages, Recent, UidNext, UidValidity, HighestModSeq] } }
|
||||
Status { mailbox: Other(MailboxOther(String(Quoted(Quoted("Mailspring"))))), item_names: [UidNext, Messages, Unseen, Recent] } }
|
||||
Status { mailbox: Other(MailboxOther(String(Quoted(Quoted("Mailspring.Snoozed"))))), item_names: [UidNext, Messages, Unseen, Recent] } }
|
||||
Status { mailbox: Other(MailboxOther(String(Quoted(Quoted("Mailspring.Snoozed"))))), item_names: [Unseen, Messages, Recent, UidNext, UidValidity, HighestModSeq] } }
|
||||
Status { mailbox: Other(MailboxOther(String(Quoted(Quoted("Sent"))))), item_names: [UidNext, Messages, Unseen, Recent] } }
|
||||
Status { mailbox: Other(MailboxOther(String(Quoted(Quoted("Sent"))))), item_names: [UidNext, UidValidity, Unseen, Recent] } }
|
||||
Status { mailbox: Other(MailboxOther(String(Quoted(Quoted("Test.Coucou"))))), item_names: [UidNext, UidValidity, Unseen, Recent] } }
|
||||
Status { mailbox: Other(MailboxOther(String(Quoted(Quoted("Test"))))), item_names: [UidNext, UidValidity, Unseen, Recent] } }
|
||||
Status { mailbox: Other(MailboxOther(String(Quoted(Quoted("Trash"))))), item_names: [UidNext, UidValidity, Unseen, Recent] } }
|
||||
Store { sequence_set: SequenceSet([Range(Value(60), Value(62))]+), kind: Add, response: Answer, flags: [Deleted], modifiers: [], uid: true } }
|
||||
Store { sequence_set: SequenceSet([Range(Value(60), Value(62))]+), kind: Add, response: Answer, flags: [Deleted, Seen], modifiers: [], uid: true } }
|
||||
Store { sequence_set: SequenceSet([Range(Value(60), Value(62))]+), kind: Add, response: Answer, flags: [Seen, Deleted], modifiers: [], uid: true } }
|
||||
Store { sequence_set: SequenceSet([Single(Value(1))]+), kind: Add, response: Answer, flags: [Seen], modifiers: [], uid: true } }
|
||||
Store { sequence_set: SequenceSet([Single(Value(24))]+), kind: Add, response: Answer, flags: [Seen, Deleted], modifiers: [], uid: true } }
|
||||
Store { sequence_set: SequenceSet([Single(Value(24))]+), kind: Add, response: Answer, flags: [Seen], modifiers: [], uid: true } }
|
||||
Store { sequence_set: SequenceSet([Single(Value(26))]+), kind: Add, response: Answer, flags: [Answered], modifiers: [], uid: true } }
|
||||
Store { sequence_set: SequenceSet([Single(Value(29))]+), kind: Add, response: Answer, flags: [Keyword(Atom("NonJunk"))], modifiers: [], uid: true } }
|
||||
Store { sequence_set: SequenceSet([Single(Value(29))]+), kind: Add, response: Answer, flags: [Seen], modifiers: [], uid: true } }
|
||||
Store { sequence_set: SequenceSet([Single(Value(2))]+), kind: Add, response: Answer, flags: [Seen], modifiers: [], uid: false } }
|
||||
Store { sequence_set: SequenceSet([Single(Value(2))]+), kind: Add, response: Silent, flags: [Deleted], modifiers: [], uid: true } }
|
||||
Store { sequence_set: SequenceSet([Single(Value(3))]+), kind: Add, response: Answer, flags: [Seen], modifiers: [], uid: true } }
|
||||
Store { sequence_set: SequenceSet([Single(Value(3))]+), kind: Add, response: Silent, flags: [Seen], modifiers: [], uid: true } }
|
||||
Store { sequence_set: SequenceSet([Single(Value(5))]+), kind: Add, response: Answer, flags: [Keyword(Atom("NonJunk"))], modifiers: [], uid: true } }
|
||||
Store { sequence_set: SequenceSet([Single(Value(60))]+), kind: Add, response: Answer, flags: [Seen, Deleted], modifiers: [], uid: true } }
|
||||
Store { sequence_set: SequenceSet([Single(Value(60))]+), kind: Add, response: Silent, flags: [Seen], modifiers: [], uid: true } }
|
||||
Store { sequence_set: SequenceSet([Single(Value(61))]+), kind: Add, response: Answer, flags: [Seen, Deleted], modifiers: [], uid: true } }
|
||||
Store { sequence_set: SequenceSet([Single(Value(61))]+), kind: Add, response: Silent, flags: [Seen], modifiers: [], uid: true } }
|
||||
Store { sequence_set: SequenceSet([Single(Value(62))]+), kind: Add, response: Silent, flags: [Seen], modifiers: [], uid: true } }
|
||||
Subscribe { mailbox: Other(MailboxOther(Atom(AtomExt("Mailspring")))) } }
|
||||
Subscribe { mailbox: Other(MailboxOther(String(Quoted(Quoted("Dataset"))))) } }
|
||||
Subscribe { mailbox: Other(MailboxOther(String(Quoted(Quoted("Mailspring.Snoozed"))))) } }
|
||||
Unselect
|
|
@ -0,0 +1,45 @@
|
|||
count,command,aggregation
|
||||
6,Append,Raw
|
||||
33,Capability,Raw
|
||||
96,Check,Raw
|
||||
6,Close,Raw
|
||||
3,Create,Raw
|
||||
9,Enable,Raw
|
||||
26,Examine,Raw
|
||||
1,Expunge,Raw
|
||||
1187,Fetch,Raw
|
||||
248,Idle,Raw
|
||||
132,List,Raw
|
||||
244,Login,Raw
|
||||
169,Logout,Raw
|
||||
14,Lsub,Raw
|
||||
2,Move,Raw
|
||||
295,Noop,Raw
|
||||
658,Search,Raw
|
||||
746,Select,Raw
|
||||
203,Status,Raw
|
||||
23,Store,Raw
|
||||
3,Subscribe,Raw
|
||||
515,Unselect,Raw
|
||||
6,Append,Unique
|
||||
1,Capability,Unique
|
||||
1,Check,Unique
|
||||
1,Close,Unique
|
||||
3,Create,Unique
|
||||
2,Enable,Unique
|
||||
1,Examine,Unique
|
||||
1,Expunge,Unique
|
||||
128,Fetch,Unique
|
||||
1,Idle,Unique
|
||||
12,List,Unique
|
||||
9,Login,Unique
|
||||
1,Logout,Unique
|
||||
1,Lsub,Unique
|
||||
2,Move,Unique
|
||||
1,Noop,Unique
|
||||
14,Search,Unique
|
||||
18,Select,Unique
|
||||
22,Status,Unique
|
||||
19,Store,Unique
|
||||
3,Subscribe,Unique
|
||||
1,Unselect,Unique
|
|
File diff suppressed because it is too large
Load Diff
|
@ -0,0 +1,14 @@
|
|||
library(tidyverse)
|
||||
library(lubridate)
|
||||
read_csv("imap_commands_summary.csv") -> cmd
|
||||
|
||||
ggplot(cmd, aes(x=command, y=count)) +
|
||||
geom_bar(stat = "identity")+
|
||||
theme_classic() +
|
||||
facet_wrap(~aggregation, ncol=1, scales = "free")
|
||||
|
||||
read_csv("mailbox_email_sizes.csv") -> mbx
|
||||
ggplot(mbx, aes(x=size, colour=mailbox)) +
|
||||
stat_ecdf(pad=FALSE,geom = "step") +
|
||||
scale_x_log10()+ theme_classic()
|
||||
|
Loading…
Reference in New Issue