aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorQuentin <quentin@dufour.io>2024-01-02 22:44:29 +0000
committerQuentin <quentin@dufour.io>2024-01-02 22:44:29 +0000
commitb9a0c1e6eced036eb71e8221a4f236f72832fec2 (patch)
treec498a7a2a5833f2c6f27d4ba97894747f9d454c0
parent6ff3c6f71efd802da422a371e6168ae528fb2ddc (diff)
parentc9a33c080d39d4a2b269e3c8f166a708b6606da5 (diff)
downloadaerogramme-b9a0c1e6eced036eb71e8221a4f236f72832fec2.tar.gz
aerogramme-b9a0c1e6eced036eb71e8221a4f236f72832fec2.zip
Merge pull request 'Implement imap-flow' (#34) from refactor/imap-flow into main
Reviewed-on: https://git.deuxfleurs.fr/Deuxfleurs/aerogramme/pulls/34
-rw-r--r--Cargo.lock1367
-rw-r--r--Cargo.toml73
-rw-r--r--src/bayou.rs5
-rw-r--r--src/config.rs1
-rw-r--r--src/future_rest_admin_api.txt174
-rw-r--r--src/imap/command/anonymous.rs91
-rw-r--r--src/imap/command/anystate.rs52
-rw-r--r--src/imap/command/authenticated.rs400
-rw-r--r--src/imap/command/examined.rs142
-rw-r--r--src/imap/command/mod.rs17
-rw-r--r--src/imap/command/selected.rs161
-rw-r--r--src/imap/flow.rs24
-rw-r--r--src/imap/mailbox_view.rs493
-rw-r--r--src/imap/mod.rs223
-rw-r--r--src/imap/response.rs112
-rw-r--r--src/imap/session.rs224
-rw-r--r--src/login/demo_provider.rs51
-rw-r--r--src/login/mod.rs1
-rw-r--r--src/main.rs26
-rw-r--r--src/server.rs7
-rw-r--r--tests/imap_features.rs394
-rw-r--r--tests/instrumentation/README.md (renamed from tests/README.md)0
-rw-r--r--tests/instrumentation/docker-compose.yml (renamed from tests/docker-compose.yml)0
-rw-r--r--tests/instrumentation/docker/cyrus/Dockerfile (renamed from tests/docker/cyrus/Dockerfile)0
-rwxr-xr-xtests/instrumentation/docker/cyrus/entrypoint.sh (renamed from tests/docker/cyrus/entrypoint.sh)0
-rw-r--r--tests/instrumentation/docker/maddy/Dockerfile (renamed from tests/docker/maddy/Dockerfile)0
-rwxr-xr-xtests/instrumentation/docker/maddy/entrypoint.sh (renamed from tests/docker/maddy/entrypoint.sh)0
-rwxr-xr-xtests/instrumentation/inject_emails.sh (renamed from tests/inject_emails.sh)0
-rw-r--r--tests/instrumentation/rm-mail-parser-expected-struct.py (renamed from tests/rm-mail-parser-expected-struct.py)0
-rw-r--r--tests/instrumentation/send-to-imap.py (renamed from tests/send-to-imap.py)0
-rwxr-xr-xtests/instrumentation/unix2dos.py (renamed from tests/unix2dos.py)0
31 files changed, 2403 insertions, 1635 deletions
diff --git a/Cargo.lock b/Cargo.lock
index 7611ef8..a6a01b9 100644
--- a/Cargo.lock
+++ b/Cargo.lock
@@ -4,18 +4,18 @@ version = 3
[[package]]
name = "abnf-core"
-version = "0.4.1"
+version = "0.6.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "871a574ed52e84ec15e6266d57d477e3e5c396cd86f9b05f2cb629a2c5af2eec"
+checksum = "ec182d1f071b906a9f59269c89af101515a5cbe58f723eb6717e7fe7445c0dea"
dependencies = [
- "nom 6.1.2",
+ "nom 7.1.3",
]
[[package]]
name = "addr2line"
-version = "0.20.0"
+version = "0.21.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "f4fa78e18c64fce05e902adecd7a5eed15a5e0a3439f7b0e169f0252214865e3"
+checksum = "8a30b2e23b9e17a9f90641c7ab1549cd9b44f296d3ccbf309d2863cfe398a0cb"
dependencies = [
"gimli",
]
@@ -36,8 +36,7 @@ dependencies = [
"aws-config",
"aws-sdk-s3",
"backtrace",
- "base64 0.21.2",
- "boitalettres",
+ "base64 0.21.5",
"chrono",
"clap",
"duplexify",
@@ -47,6 +46,7 @@ dependencies = [
"hyper-rustls",
"im",
"imap-codec",
+ "imap-flow",
"itertools",
"k2v-client",
"lazy_static",
@@ -63,7 +63,6 @@ dependencies = [
"tokio",
"tokio-util",
"toml",
- "tower",
"tracing",
"tracing-subscriber",
"zstd",
@@ -95,9 +94,9 @@ dependencies = [
[[package]]
name = "anyhow"
-version = "1.0.72"
+version = "1.0.77"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "3b13c32d80ecc7ab747b80c3784bce54ee8a7a0cc4fbda9bf4cda2cf6fe90854"
+checksum = "c9d19de80eff169429ac1e9f48fffb163916b448a44e8e046186232046d9e1f9"
[[package]]
name = "argon2"
@@ -130,7 +129,7 @@ dependencies = [
"num-traits",
"rusticata-macros",
"thiserror",
- "time 0.3.23",
+ "time",
]
[[package]]
@@ -163,34 +162,34 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "81953c529336010edd6d8e358f886d9581267795c61b19475b71314bffa46d35"
dependencies = [
"concurrent-queue",
- "event-listener",
+ "event-listener 2.5.3",
"futures-core",
]
[[package]]
-name = "async-compat"
-version = "0.2.1"
+name = "async-channel"
+version = "2.1.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "9b48b4ff0c2026db683dea961cd8ea874737f56cffca86fa84415eaddc51c00d"
+checksum = "1ca33f4bc4ed1babef42cad36cc1f51fa88be00420404e5b1e80ab1b18f7678c"
dependencies = [
+ "concurrent-queue",
+ "event-listener 4.0.1",
+ "event-listener-strategy",
"futures-core",
- "futures-io",
- "once_cell",
- "pin-project-lite 0.2.10",
- "tokio",
+ "pin-project-lite 0.2.13",
]
[[package]]
name = "async-executor"
-version = "1.5.1"
+version = "1.8.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "6fa3dc5f2a8564f07759c008b9109dc0d39de92a88d5588b8a5036d286383afb"
+checksum = "17ae5ebefcc48e7452b4987947920dac9450be1110cadf34d1b8c116bdbaf97c"
dependencies = [
- "async-lock",
+ "async-lock 3.2.0",
"async-task",
"concurrent-queue",
- "fastrand 1.9.0",
- "futures-lite",
+ "fastrand 2.0.1",
+ "futures-lite 2.1.0",
"slab",
]
@@ -200,24 +199,24 @@ version = "1.6.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "279cf904654eeebfa37ac9bb1598880884924aab82e290aa65c9e77a0e142e06"
dependencies = [
- "async-lock",
+ "async-lock 2.8.0",
"autocfg",
"blocking",
- "futures-lite",
+ "futures-lite 1.13.0",
]
[[package]]
name = "async-global-executor"
-version = "2.3.1"
+version = "2.4.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "f1b6f5d7df27bd294849f8eec66ecfc63d11814df7a4f5d74168a2394467b776"
+checksum = "05b1b633a2115cd122d73b955eadd9916c18c8f510ec9cd1686404c60ad1c29c"
dependencies = [
- "async-channel",
+ "async-channel 2.1.1",
"async-executor",
- "async-io",
- "async-lock",
+ "async-io 2.2.2",
+ "async-lock 3.2.0",
"blocking",
- "futures-lite",
+ "futures-lite 2.1.0",
"once_cell",
]
@@ -227,57 +226,103 @@ version = "1.13.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0fc5b45d93ef0529756f812ca52e44c221b35341892d3dcc34132ac02f3dd2af"
dependencies = [
- "async-lock",
+ "async-lock 2.8.0",
"autocfg",
"cfg-if",
"concurrent-queue",
- "futures-lite",
+ "futures-lite 1.13.0",
"log",
"parking",
- "polling",
- "rustix",
+ "polling 2.8.0",
+ "rustix 0.37.27",
"slab",
- "socket2",
+ "socket2 0.4.10",
"waker-fn",
]
[[package]]
+name = "async-io"
+version = "2.2.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "6afaa937395a620e33dc6a742c593c01aced20aa376ffb0f628121198578ccc7"
+dependencies = [
+ "async-lock 3.2.0",
+ "cfg-if",
+ "concurrent-queue",
+ "futures-io",
+ "futures-lite 2.1.0",
+ "parking",
+ "polling 3.3.1",
+ "rustix 0.38.28",
+ "slab",
+ "tracing",
+ "windows-sys 0.52.0",
+]
+
+[[package]]
name = "async-lock"
-version = "2.7.0"
+version = "2.8.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "fa24f727524730b077666307f2734b4a1a1c57acb79193127dcc8914d5242dd7"
+checksum = "287272293e9d8c41773cec55e365490fe034813a2f172f502d6ddcf75b2f582b"
dependencies = [
- "event-listener",
+ "event-listener 2.5.3",
+]
+
+[[package]]
+name = "async-lock"
+version = "3.2.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "7125e42787d53db9dd54261812ef17e937c95a51e4d291373b670342fa44310c"
+dependencies = [
+ "event-listener 4.0.1",
+ "event-listener-strategy",
+ "pin-project-lite 0.2.13",
]
[[package]]
name = "async-net"
-version = "1.7.0"
+version = "1.8.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "4051e67316bc7eff608fe723df5d32ed639946adcd69e07df41fd42a7b411f1f"
+checksum = "0434b1ed18ce1cf5769b8ac540e33f01fa9471058b5e89da9e06f3c882a8c12f"
dependencies = [
- "async-io",
- "autocfg",
+ "async-io 1.13.0",
"blocking",
- "futures-lite",
+ "futures-lite 1.13.0",
]
[[package]]
name = "async-process"
-version = "1.7.0"
+version = "1.8.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "7a9d28b1d97e08915212e2e45310d47854eafa69600756fc735fb788f75199c9"
+checksum = "ea6438ba0a08d81529c69b36700fa2f95837bfe3e776ab39cde9c14d9149da88"
dependencies = [
- "async-io",
- "async-lock",
- "autocfg",
+ "async-io 1.13.0",
+ "async-lock 2.8.0",
+ "async-signal",
"blocking",
"cfg-if",
- "event-listener",
- "futures-lite",
- "rustix",
- "signal-hook",
- "windows-sys",
+ "event-listener 3.1.0",
+ "futures-lite 1.13.0",
+ "rustix 0.38.28",
+ "windows-sys 0.48.0",
+]
+
+[[package]]
+name = "async-signal"
+version = "0.2.5"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "9e47d90f65a225c4527103a8d747001fc56e375203592b25ad103e1ca13124c5"
+dependencies = [
+ "async-io 2.2.2",
+ "async-lock 2.8.0",
+ "atomic-waker",
+ "cfg-if",
+ "futures-core",
+ "futures-io",
+ "rustix 0.38.28",
+ "signal-hook-registry",
+ "slab",
+ "windows-sys 0.48.0",
]
[[package]]
@@ -286,70 +331,48 @@ version = "1.12.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "62565bb4402e926b29953c785397c6dc0391b7b446e45008b0049eb43cec6f5d"
dependencies = [
- "async-channel",
+ "async-channel 1.9.0",
"async-global-executor",
- "async-io",
- "async-lock",
+ "async-io 1.13.0",
+ "async-lock 2.8.0",
"crossbeam-utils",
"futures-channel",
"futures-core",
"futures-io",
- "futures-lite",
+ "futures-lite 1.13.0",
"gloo-timers",
"kv-log-macro",
"log",
"memchr",
"once_cell",
- "pin-project-lite 0.2.10",
+ "pin-project-lite 0.2.13",
"pin-utils",
"slab",
"wasm-bindgen-futures",
]
[[package]]
-name = "async-stream"
-version = "0.3.5"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "cd56dd203fef61ac097dd65721a419ddccb106b2d2b70ba60a6b529f03961a51"
-dependencies = [
- "async-stream-impl",
- "futures-core",
- "pin-project-lite 0.2.10",
-]
-
-[[package]]
-name = "async-stream-impl"
-version = "0.3.5"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "16e62a023e7c117e27523144c5d2459f4397fcc3cab0085af8e2224f643a0193"
-dependencies = [
- "proc-macro2",
- "quote",
- "syn 2.0.27",
-]
-
-[[package]]
name = "async-task"
-version = "4.4.0"
+version = "4.6.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "ecc7ab41815b3c653ccd2978ec3255c81349336702dfdf62ee6f7069b12a3aae"
+checksum = "e1d90cd0b264dfdd8eb5bad0a2c217c1f88fa96a8573f40e7b12de23fb468f46"
[[package]]
name = "async-trait"
-version = "0.1.72"
+version = "0.1.75"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "cc6dde6e4ed435a4c1ee4e73592f5ba9da2151af10076cc04858746af9352d09"
+checksum = "fdf6721fb0140e4f897002dd086c06f6c27775df19cfe1fccb21181a48fd2c98"
dependencies = [
"proc-macro2",
"quote",
- "syn 2.0.27",
+ "syn 2.0.43",
]
[[package]]
name = "atomic-waker"
-version = "1.1.1"
+version = "1.1.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "1181e1e0d1fce796a03db1ae795d67167da795f9cf4a39c37589e85ef57f26d3"
+checksum = "1505bd5d3d116872e7271a6d4e16d81d0c8570876c8de68093a09ac269d8aac0"
[[package]]
name = "atty"
@@ -427,7 +450,7 @@ dependencies = [
"http",
"hyper",
"ring 0.17.7",
- "time 0.3.23",
+ "time",
"tokio",
"tracing",
"zeroize",
@@ -457,7 +480,7 @@ dependencies = [
"bytes",
"http",
"http-body",
- "pin-project-lite 0.2.10",
+ "pin-project-lite 0.2.13",
"tracing",
]
@@ -485,9 +508,9 @@ dependencies = [
[[package]]
name = "aws-sdk-s3"
-version = "1.9.0"
+version = "1.11.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "27f89aed8bb54ff816b860cda5d6f010cf54b7b64a46bb66e3edd1063ac5f36e"
+checksum = "21392b29994de019a7059af5eab144ea49d572dd52863d8e10537267f59f998c"
dependencies = [
"aws-credential-types",
"aws-http",
@@ -515,9 +538,9 @@ dependencies = [
[[package]]
name = "aws-sdk-sso"
-version = "1.8.0"
+version = "1.9.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "7c8539671a6e0ca087464f784576b42796ed934a566b3c1263dcf2a88494d067"
+checksum = "da9d9a8ac4cdb8df39f9777fd41e15a9ae0d0b622b00909ae0322b4d2f9e6ac8"
dependencies = [
"aws-credential-types",
"aws-http",
@@ -538,9 +561,9 @@ dependencies = [
[[package]]
name = "aws-sdk-ssooidc"
-version = "1.8.0"
+version = "1.9.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "f96ac3757df21b29102edd909884ceb8d118b96b5e45546b8bb59b8a9a8eb85a"
+checksum = "56ba4a42aa91acecd5ca43b330b5c8eb7f8808d720b6a6f796a35faa302fc73d"
dependencies = [
"aws-credential-types",
"aws-http",
@@ -561,9 +584,9 @@ dependencies = [
[[package]]
name = "aws-sdk-sts"
-version = "1.8.0"
+version = "1.9.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "4fe238802a9ad92721c23fd0b1d52284ec9e3565d6d0a12243608833e2655abb"
+checksum = "8e3c7c3dcec7cccd24a13953eedf0f2964c2d728d22112744274cf0098ad2e35"
dependencies = [
"aws-credential-types",
"aws-http",
@@ -598,7 +621,7 @@ dependencies = [
"percent-encoding",
"regex",
"sha2",
- "time 0.3.23",
+ "time",
"tracing",
]
@@ -625,7 +648,7 @@ dependencies = [
"ring 0.17.7",
"sha2",
"subtle",
- "time 0.3.23",
+ "time",
"tracing",
"zeroize",
]
@@ -637,7 +660,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1e9f65000917e3aa94c259d67fe01fa9e4cd456187d026067d642436e6311a81"
dependencies = [
"futures-util",
- "pin-project-lite 0.2.10",
+ "pin-project-lite 0.2.13",
"tokio",
]
@@ -656,7 +679,7 @@ dependencies = [
"http",
"http-body",
"md-5",
- "pin-project-lite 0.2.10",
+ "pin-project-lite 0.2.13",
"sha1",
"sha2",
"tracing",
@@ -688,7 +711,7 @@ dependencies = [
"hyper",
"once_cell",
"percent-encoding",
- "pin-project-lite 0.2.10",
+ "pin-project-lite 0.2.13",
"pin-utils",
"tracing",
]
@@ -709,7 +732,7 @@ dependencies = [
"http-body",
"once_cell",
"percent-encoding",
- "pin-project-lite 0.2.10",
+ "pin-project-lite 0.2.13",
"pin-utils",
"tracing",
]
@@ -751,7 +774,7 @@ dependencies = [
"hyper",
"hyper-rustls",
"once_cell",
- "pin-project-lite 0.2.10",
+ "pin-project-lite 0.2.13",
"pin-utils",
"rustls 0.21.10",
"tokio",
@@ -768,7 +791,7 @@ dependencies = [
"aws-smithy-types 1.1.1",
"bytes",
"http",
- "pin-project-lite 0.2.10",
+ "pin-project-lite 0.2.13",
"tokio",
"tracing",
"zeroize",
@@ -784,7 +807,7 @@ dependencies = [
"itoa",
"num-integer",
"ryu",
- "time 0.3.23",
+ "time",
]
[[package]]
@@ -801,11 +824,11 @@ dependencies = [
"http-body",
"itoa",
"num-integer",
- "pin-project-lite 0.2.10",
+ "pin-project-lite 0.2.13",
"pin-utils",
"ryu",
"serde",
- "time 0.3.23",
+ "time",
"tokio",
"tokio-util",
]
@@ -836,9 +859,9 @@ dependencies = [
[[package]]
name = "backtrace"
-version = "0.3.68"
+version = "0.3.69"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "4319208da049c43661739c5fade2ba182f09d1dc2299b32298d3a31692b17e12"
+checksum = "2089b7e3f35b9dd2d0ed921ead4f6d318c27680d4a5bd167b3ee120edb105837"
dependencies = [
"addr2line",
"cc",
@@ -863,9 +886,9 @@ checksum = "9e1b586273c5702936fe7b7d6896644d8be71e6314cfe09d3167c95f712589e8"
[[package]]
name = "base64"
-version = "0.21.2"
+version = "0.21.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "604178f6c5c21f02dc555784810edfb88d34ac2c73b2eae109655649ee73ce3d"
+checksum = "35636a1494ede3b646cc98f74f8e62c773a38a659ebc777a2cf26b9b74171df9"
[[package]]
name = "base64-simd"
@@ -879,9 +902,9 @@ dependencies = [
[[package]]
name = "base64ct"
-version = "1.0.1"
+version = "1.6.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "8a32fd6af2b5827bce66c29053ba0e7c42b9dcab01835835058558c10851a46b"
+checksum = "8c3c1a368f70d6cf7302d78f8f7093da241fb8e8807c05cc9e51a125895a6d5b"
[[package]]
name = "bitflags"
@@ -936,56 +959,57 @@ dependencies = [
[[package]]
name = "blocking"
-version = "1.3.1"
+version = "1.5.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "77231a1c8f801696fc0123ec6150ce92cffb8e164a02afb9c8ddee0e9b65ad65"
+checksum = "6a37913e8dc4ddcc604f0c6d3bf2887c995153af3611de9e23c352b44c1b9118"
dependencies = [
- "async-channel",
- "async-lock",
+ "async-channel 2.1.1",
+ "async-lock 3.2.0",
"async-task",
- "atomic-waker",
- "fastrand 1.9.0",
- "futures-lite",
- "log",
+ "fastrand 2.0.1",
+ "futures-io",
+ "futures-lite 2.1.0",
+ "piper",
+ "tracing",
]
[[package]]
-name = "boitalettres"
-version = "0.1.0"
-source = "git+https://git.deuxfleurs.fr/quentin/boitalettres.git?branch=expose-mydatetime#874fa91186989ced7ebac270f18b64e47ee4f98a"
+name = "bounded-static"
+version = "0.5.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "b2325bd33fa7e3018e7e37f5b0591ba009124963b5a3f8b7cae6d0a8c1028ed4"
dependencies = [
- "async-compat",
- "async-stream",
- "bytes",
- "futures",
- "imap-codec",
- "miette",
- "pin-project",
- "thiserror",
- "tokio",
- "tokio-tower",
- "tower",
- "tracing",
- "tracing-futures",
+ "bounded-static-derive",
+]
+
+[[package]]
+name = "bounded-static-derive"
+version = "0.5.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "f10dd247355bf631d98d2753d87ae62c84c8dcb996ad9b24a4168e0aec29bd6b"
+dependencies = [
+ "proc-macro2",
+ "quote",
+ "syn 2.0.43",
]
[[package]]
name = "bumpalo"
-version = "3.13.0"
+version = "3.14.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "a3e2c3daef883ecc1b5d58c15adae93470a91d425f3532ba1695849656af3fc1"
+checksum = "7f30e7476521f6f8af1a1c4c0b8cc94f0bee37d91763d0ca2665f299b6cd8aec"
[[package]]
name = "byteorder"
-version = "1.4.3"
+version = "1.5.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "14c189c53d098945499cdfa7ecc63567cf3886b3332b312a5b4585d8d3a6a610"
+checksum = "1fd0f2584146f6f2ef48085050886acf353beff7305ebd1ae69500e27c67f64b"
[[package]]
name = "bytes"
-version = "1.4.0"
+version = "1.5.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "89b2fd2a0dcf38d7971e2194b6b6eebab45ae01067456a7fd93d5547a61b70be"
+checksum = "a2bd12c1caf447e69cd4528f47f94d203fd2582878ecb9e9465484c4148a8223"
[[package]]
name = "bytes-utils"
@@ -1015,17 +1039,16 @@ checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd"
[[package]]
name = "chrono"
-version = "0.4.26"
+version = "0.4.31"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "ec837a71355b28f6556dbd569b37b3f363091c0bd4b2e735674521b4c5fd9bc5"
+checksum = "7f2c685bad3eb3d45a01354cedb7d5faa66194d1d58ba6e267a8de788f79db38"
dependencies = [
"android-tzdata",
"iana-time-zone",
"js-sys",
"num-traits",
- "time 0.1.45",
"wasm-bindgen",
- "winapi",
+ "windows-targets 0.48.5",
]
[[package]]
@@ -1038,7 +1061,7 @@ dependencies = [
"bitflags 1.3.2",
"clap_derive",
"clap_lex",
- "indexmap",
+ "indexmap 1.9.3",
"once_cell",
"strsim",
"termcolor",
@@ -1069,9 +1092,9 @@ dependencies = [
[[package]]
name = "concurrent-queue"
-version = "2.2.0"
+version = "2.4.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "62ec6771ecfa0762d24683ee5a32ad78487a3d3afdc0fb8cae19d2c5deb50b7c"
+checksum = "d16048cd947b08fa32c24458a22f5dc5e835264f689f4f5653210c69fd107363"
dependencies = [
"crossbeam-utils",
]
@@ -1084,9 +1107,9 @@ checksum = "c2459377285ad874054d797f3ccebf984978aa39129f6eafde5cdc8315b612f8"
[[package]]
name = "core-foundation"
-version = "0.9.3"
+version = "0.9.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "194a7a9e6de53fa55116934067c844d9d749312f75c6f6d0980e8c252f8c2146"
+checksum = "91e195e091a93c46f7102ec7818a2aa394e1e1771c3ab4825963fa03e45afb8f"
dependencies = [
"core-foundation-sys",
"libc",
@@ -1094,15 +1117,15 @@ dependencies = [
[[package]]
name = "core-foundation-sys"
-version = "0.8.4"
+version = "0.8.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "e496a50fda8aacccc86d7529e2c1e0892dbd0f898a6b5645b5561b89c3210efa"
+checksum = "06ea2b9bc92be3c2baa9334a323ebca2d6f074ff852cd1d7b11064035cd3868f"
[[package]]
name = "cpufeatures"
-version = "0.2.9"
+version = "0.2.11"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "a17b76ff3a4162b0b27f354a0c87015ddad39d35f9c0c36607a3bdd175dde1f1"
+checksum = "ce420fe07aecd3e67c5f910618fe65e94158f6dcc0adf44e00d69ce2bdfe0fd0"
dependencies = [
"libc",
]
@@ -1126,68 +1149,10 @@ dependencies = [
]
[[package]]
-name = "crossbeam"
-version = "0.8.2"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "2801af0d36612ae591caa9568261fddce32ce6e08a7275ea334a06a4ad021a2c"
-dependencies = [
- "cfg-if",
- "crossbeam-channel",
- "crossbeam-deque",
- "crossbeam-epoch",
- "crossbeam-queue",
- "crossbeam-utils",
-]
-
-[[package]]
-name = "crossbeam-channel"
-version = "0.5.8"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "a33c2bf77f2df06183c3aa30d1e96c0695a313d4f9c453cc3762a6db39f99200"
-dependencies = [
- "cfg-if",
- "crossbeam-utils",
-]
-
-[[package]]
-name = "crossbeam-deque"
-version = "0.8.3"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "ce6fd6f855243022dcecf8702fef0c297d4338e226845fe067f6341ad9fa0cef"
-dependencies = [
- "cfg-if",
- "crossbeam-epoch",
- "crossbeam-utils",
-]
-
-[[package]]
-name = "crossbeam-epoch"
-version = "0.9.15"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "ae211234986c545741a7dc064309f67ee1e5ad243d0e48335adc0484d960bcc7"
-dependencies = [
- "autocfg",
- "cfg-if",
- "crossbeam-utils",
- "memoffset",
- "scopeguard",
-]
-
-[[package]]
-name = "crossbeam-queue"
-version = "0.3.8"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "d1cfb3ea8a53f37c40dea2c7bedcbd88bdfae54f5e2175d6ecaff1c988353add"
-dependencies = [
- "cfg-if",
- "crossbeam-utils",
-]
-
-[[package]]
name = "crossbeam-utils"
-version = "0.8.16"
+version = "0.8.18"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "5a22b2d63d4d1dc0b7f1b6b2747dd0088008a9be28b6ddf0b1e7d335e3037294"
+checksum = "c3a430a770ebd84726f584a90ee7f020d28db52c6d02138900f22341f866d39c"
dependencies = [
"cfg-if",
]
@@ -1226,9 +1191,9 @@ dependencies = [
[[package]]
name = "data-encoding"
-version = "2.4.0"
+version = "2.5.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "c2e66c9d817f1720209181c316d28635c050fa304f9c79e47a520882661b7308"
+checksum = "7e962a19be5cfc3f3bf6dd8f61eb50107f356ad6270fbb3ed41476571db78be5"
[[package]]
name = "der"
@@ -1255,6 +1220,15 @@ dependencies = [
]
[[package]]
+name = "deranged"
+version = "0.3.10"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "8eb30d70a07a3b04884d2677f06bec33509dc67ca60d92949e5535352d3191dc"
+dependencies = [
+ "powerfmt",
+]
+
+[[package]]
name = "derive_utils"
version = "0.11.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
@@ -1284,7 +1258,7 @@ checksum = "487585f4d0c6655fe74905e2504d8ad6908e4db67f744eb140876906c2f3175d"
dependencies = [
"proc-macro2",
"quote",
- "syn 2.0.27",
+ "syn 2.0.43",
]
[[package]]
@@ -1349,7 +1323,7 @@ name = "eml-codec"
version = "0.1.2"
source = "git+https://git.deuxfleurs.fr/Deuxfleurs/eml-codec.git?branch=main#a7bd3c475a58e42b86c163ec075ce01ddae7e60a"
dependencies = [
- "base64 0.21.2",
+ "base64 0.21.5",
"chrono",
"encoding_rs",
"nom 7.1.3",
@@ -1357,39 +1331,66 @@ dependencies = [
[[package]]
name = "encoding_rs"
-version = "0.8.32"
+version = "0.8.33"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "071a31f4ee85403370b58aca746f01041ede6f0da2730960ad001edc2b71b394"
+checksum = "7268b386296a025e474d5140678f75d6de9493ae55a5d709eeb9dd08149945e1"
dependencies = [
"cfg-if",
]
[[package]]
+name = "equivalent"
+version = "1.0.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "5443807d6dff69373d433ab9ef5378ad8df50ca6298caf15de6e52e24aaf54d5"
+
+[[package]]
name = "errno"
-version = "0.3.1"
+version = "0.3.8"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "4bcfec3a70f97c962c307b2d2c56e358cf1d00b558d74262b5f929ee8cc7e73a"
+checksum = "a258e46cdc063eb8519c00b9fc845fc47bcfca4130e2f08e88665ceda8474245"
dependencies = [
- "errno-dragonfly",
"libc",
- "windows-sys",
+ "windows-sys 0.52.0",
]
[[package]]
-name = "errno-dragonfly"
-version = "0.1.2"
+name = "event-listener"
+version = "2.5.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "0206175f82b8d6bf6652ff7d71a1e27fd2e4efde587fd368662814d6ec1d9ce0"
+
+[[package]]
+name = "event-listener"
+version = "3.1.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "aa68f1b12764fab894d2755d2518754e71b4fd80ecfb822714a1206c2aab39bf"
+checksum = "d93877bcde0eb80ca09131a08d23f0a5c18a620b01db137dba666d18cd9b30c2"
dependencies = [
- "cc",
- "libc",
+ "concurrent-queue",
+ "parking",
+ "pin-project-lite 0.2.13",
]
[[package]]
name = "event-listener"
-version = "2.5.3"
+version = "4.0.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "0206175f82b8d6bf6652ff7d71a1e27fd2e4efde587fd368662814d6ec1d9ce0"
+checksum = "84f2cdcf274580f2d63697192d744727b3198894b1bf02923643bf59e2c26712"
+dependencies = [
+ "concurrent-queue",
+ "parking",
+ "pin-project-lite 0.2.13",
+]
+
+[[package]]
+name = "event-listener-strategy"
+version = "0.4.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "958e4d70b6d5e81971bebec42271ec641e7ff4e170a6fa605f2b8a8b65cb97d3"
+dependencies = [
+ "event-listener 4.0.1",
+ "pin-project-lite 0.2.13",
+]
[[package]]
name = "fastrand"
@@ -1424,9 +1425,9 @@ checksum = "3f9eec918d3f24069decb9af1554cad7c880e2da24a9afd88aca000531ab82c1"
[[package]]
name = "form_urlencoded"
-version = "1.2.0"
+version = "1.2.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "a62bc1cf6f830c2ec14a513a9fb124d0a213a629668a4186f329db21fe045652"
+checksum = "e13624c2627564efccf4934284bdd98cbaa14e79b0b5a141218e507b3a823456"
dependencies = [
"percent-encoding",
]
@@ -1439,9 +1440,9 @@ checksum = "fed34cd105917e91daa4da6b3728c47b068749d6a62c59811f06ed2ac71d9da7"
[[package]]
name = "futures"
-version = "0.3.28"
+version = "0.3.30"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "23342abe12aba583913b2e62f22225ff9c950774065e4bfb61a19cd9770fec40"
+checksum = "645c6916888f6cb6350d2550b80fb63e734897a8498abe35cfb732b6487804b0"
dependencies = [
"futures-channel",
"futures-core",
@@ -1454,9 +1455,9 @@ dependencies = [
[[package]]
name = "futures-channel"
-version = "0.3.28"
+version = "0.3.30"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "955518d47e09b25bbebc7a18df10b81f0c766eaf4c4f1cccef2fca5f2a4fb5f2"
+checksum = "eac8f7d7865dcb88bd4373ab671c8cf4508703796caa2b1985a9ca867b3fcb78"
dependencies = [
"futures-core",
"futures-sink",
@@ -1464,15 +1465,15 @@ dependencies = [
[[package]]
name = "futures-core"
-version = "0.3.28"
+version = "0.3.30"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "4bca583b7e26f571124fe5b7561d49cb2868d79116cfa0eefce955557c6fee8c"
+checksum = "dfc6580bb841c5a68e9ef15c77ccc837b40a7504914d52e47b8b0e9bbda25a1d"
[[package]]
name = "futures-executor"
-version = "0.3.28"
+version = "0.3.30"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "ccecee823288125bd88b4d7f565c9e58e41858e47ab72e8ea2d64e93624386e0"
+checksum = "a576fc72ae164fca6b9db127eaa9a9dda0d61316034f33a0a0d4eda41f02b01d"
dependencies = [
"futures-core",
"futures-task",
@@ -1481,9 +1482,9 @@ dependencies = [
[[package]]
name = "futures-io"
-version = "0.3.28"
+version = "0.3.30"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "4fff74096e71ed47f8e023204cfd0aa1289cd54ae5430a9523be060cdb849964"
+checksum = "a44623e20b9681a318efdd71c299b6b222ed6f231972bfe2f224ebad6311f0c1"
[[package]]
name = "futures-lite"
@@ -1496,38 +1497,51 @@ dependencies = [
"futures-io",
"memchr",
"parking",
- "pin-project-lite 0.2.10",
+ "pin-project-lite 0.2.13",
"waker-fn",
]
[[package]]
+name = "futures-lite"
+version = "2.1.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "aeee267a1883f7ebef3700f262d2d54de95dfaf38189015a74fdc4e0c7ad8143"
+dependencies = [
+ "fastrand 2.0.1",
+ "futures-core",
+ "futures-io",
+ "parking",
+ "pin-project-lite 0.2.13",
+]
+
+[[package]]
name = "futures-macro"
-version = "0.3.28"
+version = "0.3.30"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "89ca545a94061b6365f2c7355b4b32bd20df3ff95f02da9329b34ccc3bd6ee72"
+checksum = "87750cf4b7a4c0625b1529e4c543c2182106e4dedc60a2a6455e00d212c489ac"
dependencies = [
"proc-macro2",
"quote",
- "syn 2.0.27",
+ "syn 2.0.43",
]
[[package]]
name = "futures-sink"
-version = "0.3.28"
+version = "0.3.30"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "f43be4fe21a13b9781a69afa4985b0f6ee0e1afab2c6f454a8cf30e2b2237b6e"
+checksum = "9fb8e00e87438d937621c1c6269e53f536c14d3fbd6a042bb24879e57d474fb5"
[[package]]
name = "futures-task"
-version = "0.3.28"
+version = "0.3.30"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "76d3d132be6c0e6aa1534069c705a74a5997a356c0dc2f86a47765e5617c5b65"
+checksum = "38d84fa142264698cdce1a9f9172cf383a0c82de1bddcf3092901442c4097004"
[[package]]
name = "futures-util"
-version = "0.3.28"
+version = "0.3.30"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "26b01e40b772d54cf6c6d721c1d1abd0647a0106a12ecaa1c186273392a69533"
+checksum = "3d6401deb83407ab3da39eba7e33987a73c3df0c82b4bb5813ee871c19c41d48"
dependencies = [
"futures-channel",
"futures-core",
@@ -1536,7 +1550,7 @@ dependencies = [
"futures-sink",
"futures-task",
"memchr",
- "pin-project-lite 0.2.10",
+ "pin-project-lite 0.2.13",
"pin-utils",
"slab",
]
@@ -1553,20 +1567,20 @@ dependencies = [
[[package]]
name = "getrandom"
-version = "0.2.10"
+version = "0.2.11"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "be4136b2a15dd319360be1c07d9933517ccf0be8f16bf62a3bee4f0d618df427"
+checksum = "fe9006bed769170c11f845cf00c7c1e9092aeb3f268e007c3e760ac68008070f"
dependencies = [
"cfg-if",
"libc",
- "wasi 0.11.0+wasi-snapshot-preview1",
+ "wasi",
]
[[package]]
name = "gimli"
-version = "0.27.3"
+version = "0.28.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "b6c80984affa11d98d1b88b66ac8853f143217b399d3c74116778ff8fdb4ed2e"
+checksum = "4271d37baee1b8c7e4b708028c57d816cf9d2434acb33a549475f78c181f6253"
[[package]]
name = "gloo-timers"
@@ -1593,9 +1607,9 @@ dependencies = [
[[package]]
name = "h2"
-version = "0.3.20"
+version = "0.3.22"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "97ec8491ebaf99c8eaa73058b045fe58073cd6be7f596ac993ced0b0a0c01049"
+checksum = "4d6250322ef6e60f93f9a2162799302cd6f68f79f6e5d85c8c16f14d1d958178"
dependencies = [
"bytes",
"fnv",
@@ -1603,7 +1617,7 @@ dependencies = [
"futures-sink",
"futures-util",
"http",
- "indexmap",
+ "indexmap 2.1.0",
"slab",
"tokio",
"tokio-util",
@@ -1617,14 +1631,10 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8a9ee70c43aaf417c914396645a0fa852624801b24ebb7ae78fe8272889ac888"
[[package]]
-name = "hdrhistogram"
-version = "7.5.2"
+name = "hashbrown"
+version = "0.14.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "7f19b9f54f7c7f55e31401bb647626ce0cf0f67b0004982ce815b3ee72a02aa8"
-dependencies = [
- "byteorder",
- "num-traits",
-]
+checksum = "290f1a1d9242c78d09ce40a5e87e7554ee637af1351968159f4952f028f75604"
[[package]]
name = "heck"
@@ -1643,9 +1653,9 @@ dependencies = [
[[package]]
name = "hermit-abi"
-version = "0.3.2"
+version = "0.3.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "443144c8cdadd93ebf52ddb4056d257f5b52c04d3c804e657d19eb73fc33668b"
+checksum = "d77f7ec81a6d05a3abb01ab6eb7590f6083d08449fe5a1c8b1e620283546ccb7"
[[package]]
name = "hex"
@@ -1664,9 +1674,9 @@ dependencies = [
[[package]]
name = "http"
-version = "0.2.9"
+version = "0.2.11"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "bd6effc99afb63425aff9b05836f029929e345a6148a14b7ecd5ab67af944482"
+checksum = "8947b1a6fad4393052c7ba1f4cd97bed3e953a95c79c92ad9b051a04611d9fbb"
dependencies = [
"bytes",
"fnv",
@@ -1675,13 +1685,13 @@ dependencies = [
[[package]]
name = "http-body"
-version = "0.4.5"
+version = "0.4.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "d5f38f16d184e36f2408a55281cd658ecbd3ca05cce6d6510a176eca393e26d1"
+checksum = "7ceab25649e9960c0311ea418d17bee82c0dcec1bd053b5f9a66e265a693bed2"
dependencies = [
"bytes",
"http",
- "pin-project-lite 0.2.10",
+ "pin-project-lite 0.2.13",
]
[[package]]
@@ -1692,15 +1702,15 @@ checksum = "d897f394bad6a705d5f4104762e116a75639e470d80901eed05a860a95cb1904"
[[package]]
name = "httpdate"
-version = "1.0.2"
+version = "1.0.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "c4a1e36c821dbe04574f602848a19f742f4fb3c98d40449f11bcad18d6b17421"
+checksum = "df3b46402a9d5adb4c86a0cf463f42e19994e3ee891101b1841f30a545cb49a9"
[[package]]
name = "hyper"
-version = "0.14.27"
+version = "0.14.28"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "ffb1cfd654a8219eaef89881fdb3bb3b1cdc5fa75ded05d6933b2b382e395468"
+checksum = "bf96e135eb83a2a8ddf766e426a841d8ddd7449d5f00d34ea02b41d2f19eef80"
dependencies = [
"bytes",
"futures-channel",
@@ -1712,8 +1722,8 @@ dependencies = [
"httparse",
"httpdate",
"itoa",
- "pin-project-lite 0.2.10",
- "socket2",
+ "pin-project-lite 0.2.13",
+ "socket2 0.5.5",
"tokio",
"tower-service",
"tracing",
@@ -1722,9 +1732,9 @@ dependencies = [
[[package]]
name = "hyper-rustls"
-version = "0.24.1"
+version = "0.24.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "8d78e1e73ec14cf7375674f74d7dde185c8206fd9dea6fb6295e8a98098aaa97"
+checksum = "ec3efd23720e2049821a693cbc7e65ea87c72f1c58ff2f9522ff332b1491e590"
dependencies = [
"futures-util",
"http",
@@ -1738,16 +1748,16 @@ dependencies = [
[[package]]
name = "iana-time-zone"
-version = "0.1.57"
+version = "0.1.58"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "2fad5b825842d2b38bd206f3e81d6957625fd7f0a361e345c30e01a0ae2dd613"
+checksum = "8326b86b6cff230b97d0d312a6c40a60726df3332e721f72a1b035f451663b20"
dependencies = [
"android_system_properties",
"core-foundation-sys",
"iana-time-zone-haiku",
"js-sys",
"wasm-bindgen",
- "windows",
+ "windows-core",
]
[[package]]
@@ -1772,9 +1782,9 @@ dependencies = [
[[package]]
name = "idna"
-version = "0.4.0"
+version = "0.5.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "7d20d6b07bfbc108882d88ed8e37d39636dcc260e15e30c45e6ba089610b917c"
+checksum = "634d9b1461af396cad843f47fdba5597a4f9e6ddd4bfb6ff5d85028c25cb12f6"
dependencies = [
"unicode-bidi",
"unicode-normalization",
@@ -1796,14 +1806,40 @@ dependencies = [
[[package]]
name = "imap-codec"
-version = "0.5.0"
-source = "git+https://github.com/superboum/imap-codec.git?branch=v0.5.x#09f18cff93b7e17e5b84ab1fbf06b8d13b8e9e2d"
+version = "1.0.0"
+source = "git+https://github.com/duesee/imap-codec?branch=v2#1f490146bb6197eee6032205e3aa7f297efd9b39"
dependencies = [
"abnf-core",
- "base64 0.13.1",
+ "base64 0.21.5",
+ "bounded-static",
"chrono",
- "nom 6.1.2",
- "rand",
+ "imap-types",
+ "log",
+ "nom 7.1.3",
+ "thiserror",
+]
+
+[[package]]
+name = "imap-flow"
+version = "0.1.0"
+source = "git+https://github.com/duesee/imap-flow.git?rev=e45ce7bb6ab6bda3c71a0c7b05e9b558a5902e90#e45ce7bb6ab6bda3c71a0c7b05e9b558a5902e90"
+dependencies = [
+ "bounded-static",
+ "bytes",
+ "imap-codec",
+ "thiserror",
+ "tokio",
+]
+
+[[package]]
+name = "imap-types"
+version = "1.0.0"
+source = "git+https://github.com/duesee/imap-codec?branch=v2#1f490146bb6197eee6032205e3aa7f297efd9b39"
+dependencies = [
+ "base64 0.21.5",
+ "bounded-static",
+ "chrono",
+ "thiserror",
]
[[package]]
@@ -1813,7 +1849,17 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "bd070e393353796e801d209ad339e89596eb4c8d430d18ede6a1cced8fafbd99"
dependencies = [
"autocfg",
- "hashbrown",
+ "hashbrown 0.12.3",
+]
+
+[[package]]
+name = "indexmap"
+version = "2.1.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "d530e1a18b1cb4c484e6e34556a0d948706958449fca0cab753d649f2bce3d1f"
+dependencies = [
+ "equivalent",
+ "hashbrown 0.14.3",
]
[[package]]
@@ -1831,9 +1877,9 @@ version = "1.0.11"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "eae7b9aee968036d54dce06cebaefd919e4472e753296daccd6d344e3e2df0c2"
dependencies = [
- "hermit-abi 0.3.2",
+ "hermit-abi 0.3.3",
"libc",
- "windows-sys",
+ "windows-sys 0.48.0",
]
[[package]]
@@ -1847,24 +1893,24 @@ dependencies = [
[[package]]
name = "itoa"
-version = "1.0.9"
+version = "1.0.10"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "af150ab688ff2122fcef229be89cb50dd66af9e01a4ff320cc137eecc9bacc38"
+checksum = "b1a46d1a171d865aa5f83f92695765caa047a9b4cbae2cbf37dbd613a793fd4c"
[[package]]
name = "jobserver"
-version = "0.1.26"
+version = "0.1.27"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "936cfd212a0155903bcbc060e316fb6cc7cbf2e1907329391ebadc1fe0ce77c2"
+checksum = "8c37f63953c4c63420ed5fd3d6d398c719489b9f872b9fa683262f8edd363c7d"
dependencies = [
"libc",
]
[[package]]
name = "js-sys"
-version = "0.3.64"
+version = "0.3.66"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "c5f195fe497f702db0f318b07fdd68edb16955aed830df8363d837542f8f935a"
+checksum = "cee9c64da59eae3b50095c18d3e74f8b73c0b86d2792824ff01bbce68ba229ca"
dependencies = [
"wasm-bindgen",
]
@@ -1875,7 +1921,7 @@ version = "0.0.4"
source = "git+https://git.deuxfleurs.fr/Deuxfleurs/garage.git?tag=v0.9.0#952c9570c494468643353ee1ae9052b510353665"
dependencies = [
"aws-sigv4 0.55.3",
- "base64 0.21.2",
+ "base64 0.21.5",
"hex",
"http",
"hyper",
@@ -1931,7 +1977,7 @@ dependencies = [
"nom 2.2.1",
"percent-encoding",
"ring 0.16.20",
- "rustls 0.20.8",
+ "rustls 0.20.9",
"rustls-native-certs",
"thiserror",
"tokio",
@@ -1980,20 +2026,16 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ef53942eb7bf7ff43a617b3e2c1c4a5ecf5944a7c1bc12d7ee39bbb15e5c1519"
[[package]]
-name = "lock_api"
-version = "0.4.10"
+name = "linux-raw-sys"
+version = "0.4.12"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "c1cc9717a20b1bb222f333e6a92fd32f7d8a18ddc5a3191a11af45dcbf4dcd16"
-dependencies = [
- "autocfg",
- "scopeguard",
-]
+checksum = "c4cd1a83af159aa67994778be9070f0ae1bd732942279cabb14f86f986a21456"
[[package]]
name = "log"
-version = "0.4.19"
+version = "0.4.20"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "b06a4cde4c0f271a446782e3eff8de789548ce57dbc8eca9292c27f4a42004b4"
+checksum = "b5e6163cb8c49088c2c36f57875e58ccd8c87c7427f7fbd50ea6710b2f3f2e8f"
dependencies = [
"value-bag",
]
@@ -2016,41 +2058,9 @@ dependencies = [
[[package]]
name = "memchr"
-version = "2.5.0"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "2dffe52ecf27772e601905b7522cb4ef790d2cc203488bbd0e2fe85fcb74566d"
-
-[[package]]
-name = "memoffset"
-version = "0.9.0"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "5a634b1c61a95585bd15607c6ab0c4e5b226e695ff2800ba0cdccddf208c406c"
-dependencies = [
- "autocfg",
-]
-
-[[package]]
-name = "miette"
-version = "5.10.0"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "59bb584eaeeab6bd0226ccf3509a69d7936d148cf3d036ad350abe35e8c6856e"
-dependencies = [
- "miette-derive",
- "once_cell",
- "thiserror",
- "unicode-width",
-]
-
-[[package]]
-name = "miette-derive"
-version = "5.10.0"
+version = "2.7.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "49e7bc1560b95a3c4a25d03de42fe76ca718ab92d1a22a55b9b4cf67b3ae635c"
-dependencies = [
- "proc-macro2",
- "quote",
- "syn 2.0.27",
-]
+checksum = "523dc4f511e55ab87b694dc30d0f820d60906ef06413f93d4d7a1385599cc149"
[[package]]
name = "minimal-lexical"
@@ -2069,13 +2079,13 @@ dependencies = [
[[package]]
name = "mio"
-version = "0.8.8"
+version = "0.8.10"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "927a765cd3fc26206e66b296465fa9d3e5ab003e651c1b3c060e7956d96b19d2"
+checksum = "8f3d0b296e374a4e6f3c7b0a1f5a51d748a0d34c85e7dc48fc3fa9a87657fe09"
dependencies = [
"libc",
- "wasi 0.11.0+wasi-snapshot-preview1",
- "windows-sys",
+ "wasi",
+ "windows-sys 0.48.0",
]
[[package]]
@@ -2130,9 +2140,9 @@ dependencies = [
[[package]]
name = "num-bigint"
-version = "0.4.3"
+version = "0.4.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "f93ab6289c7b344a8a9f60f88d80aa20032336fe78da341afc91c8a2341fc75f"
+checksum = "608e7659b5c3d7cba262d894801b9ec9d00de989e8a82bd4bef91d08da45cdc0"
dependencies = [
"autocfg",
"num-integer",
@@ -2151,9 +2161,9 @@ dependencies = [
[[package]]
name = "num-traits"
-version = "0.2.16"
+version = "0.2.17"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "f30b0abd723be7e2ffca1272140fac1a2f084c77ec3e123c192b66af1ee9e6c2"
+checksum = "39e3200413f237f41ab11ad6d161bc7239c84dcb631773ccd7de3dfe4b5c267c"
dependencies = [
"autocfg",
]
@@ -2164,15 +2174,15 @@ version = "1.16.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "4161fcb6d602d4d2081af7c3a45852d875a03dd337a6bfdd6e06407b61342a43"
dependencies = [
- "hermit-abi 0.3.2",
+ "hermit-abi 0.3.3",
"libc",
]
[[package]]
name = "object"
-version = "0.31.1"
+version = "0.32.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "8bda667d9f2b5051b8833f59f3bf748b28ef54f850f4fcb389a252aa383866d1"
+checksum = "a6a622008b6e321afc04970976f62ee297fdbaa6f95318ca343e3eebb9648441"
dependencies = [
"memchr",
]
@@ -2188,9 +2198,9 @@ dependencies = [
[[package]]
name = "once_cell"
-version = "1.18.0"
+version = "1.19.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "dd8b5dd2ae5ed71462c540258bedcb51965123ad7e7ccf4b9a8cafaa4a63576d"
+checksum = "3fdb12b2476b595f9358c5161aa467c2438859caa136dec86c26fdd2efe17b92"
[[package]]
name = "openssl-probe"
@@ -2200,9 +2210,9 @@ checksum = "ff011a302c396a5197692431fc1948019154afc178baf7d8e37367442a4601cf"
[[package]]
name = "os_str_bytes"
-version = "6.5.1"
+version = "6.6.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "4d5d9eb14b174ee9aa2ef96dc2b94637a2d4b6e7cb873c7e171f0c20c6cf3eac"
+checksum = "e2355d85b9a3786f481747ced0e0ff2ba35213a1f9bd406ed906554d7af805a1"
[[package]]
name = "outref"
@@ -2229,32 +2239,9 @@ dependencies = [
[[package]]
name = "parking"
-version = "2.1.0"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "14f2252c834a40ed9bb5422029649578e63aa341ac401f74e719dd1afda8394e"
-
-[[package]]
-name = "parking_lot"
-version = "0.12.1"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "3742b2c103b9f06bc9fff0a37ff4912935851bee6d36f3c02bcc755bcfec228f"
-dependencies = [
- "lock_api",
- "parking_lot_core",
-]
-
-[[package]]
-name = "parking_lot_core"
-version = "0.9.8"
+version = "2.2.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "93f00c865fe7cabf650081affecd3871070f26767e7b2070a3ffae14c654b447"
-dependencies = [
- "cfg-if",
- "libc",
- "redox_syscall",
- "smallvec",
- "windows-targets",
-]
+checksum = "bb813b8af86854136c6922af0598d719255ecb2179515e6e7730d468f05c9cae"
[[package]]
name = "password-hash"
@@ -2275,28 +2262,28 @@ checksum = "de3145af08024dea9fa9914f381a17b8fc6034dfb00f3a84013f7ff43f29ed4c"
[[package]]
name = "percent-encoding"
-version = "2.3.0"
+version = "2.3.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "9b2a4787296e9989611394c33f193f676704af1686e70b8f8033ab5ba9a35a94"
+checksum = "e3148f5046208a5d56bcfc03053e3ca6334e51da8dfb19b6cdc8b306fae3283e"
[[package]]
name = "pin-project"
-version = "1.1.2"
+version = "1.1.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "030ad2bc4db10a8944cb0d837f158bdfec4d4a4873ab701a95046770d11f8842"
+checksum = "fda4ed1c6c173e3fc7a83629421152e01d7b1f9b7f65fb301e490e8cfc656422"
dependencies = [
"pin-project-internal",
]
[[package]]
name = "pin-project-internal"
-version = "1.1.2"
+version = "1.1.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "ec2e072ecce94ec471b13398d5402c188e76ac03cf74dd1a975161b23a3f6d9c"
+checksum = "4359fd9c9171ec6e8c62926d6faaf553a8dc3f64e1507e76da7911b4f6a04405"
dependencies = [
"proc-macro2",
"quote",
- "syn 2.0.27",
+ "syn 2.0.43",
]
[[package]]
@@ -2307,9 +2294,9 @@ checksum = "257b64915a082f7811703966789728173279bdebb956b143dbcd23f6f970a777"
[[package]]
name = "pin-project-lite"
-version = "0.2.10"
+version = "0.2.13"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "4c40d25201921e5ff0c862a505c6557ea88568a4e3ace775ab55e93f2f4f9d57"
+checksum = "8afb450f006bf6385ca15ef45d71d2288452bc3683ce2e2cacc0d18e4be60b58"
[[package]]
name = "pin-utils"
@@ -2318,6 +2305,17 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8b870d8c151b6f2fb93e84a13146138f05d02ed11c7e7c54f8826aaaf7c9f184"
[[package]]
+name = "piper"
+version = "0.2.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "668d31b1c4eba19242f2088b2bf3316b82ca31082a8335764db4e083db7485d4"
+dependencies = [
+ "atomic-waker",
+ "fastrand 2.0.1",
+ "futures-io",
+]
+
+[[package]]
name = "pkcs8"
version = "0.9.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
@@ -2329,9 +2327,9 @@ dependencies = [
[[package]]
name = "pkg-config"
-version = "0.3.27"
+version = "0.3.28"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "26072860ba924cbfa98ea39c8c19b4dd6a4a25423dbdf219c1eca91aa0cf6964"
+checksum = "69d3587f8a9e599cc7ec2c00e331f71c4e69a5f9a4b8a6efd5b07466b9736f9a"
[[package]]
name = "polling"
@@ -2345,11 +2343,31 @@ dependencies = [
"concurrent-queue",
"libc",
"log",
- "pin-project-lite 0.2.10",
- "windows-sys",
+ "pin-project-lite 0.2.13",
+ "windows-sys 0.48.0",
]
[[package]]
+name = "polling"
+version = "3.3.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "cf63fa624ab313c11656b4cda960bfc46c410187ad493c41f6ba2d8c1e991c9e"
+dependencies = [
+ "cfg-if",
+ "concurrent-queue",
+ "pin-project-lite 0.2.13",
+ "rustix 0.38.28",
+ "tracing",
+ "windows-sys 0.52.0",
+]
+
+[[package]]
+name = "powerfmt"
+version = "0.2.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "439ee305def115ba05938db6eb1644ff94165c5ab5e9420d1c1bcedbba909391"
+
+[[package]]
name = "ppv-lite86"
version = "0.2.17"
source = "registry+https://github.com/rust-lang/crates.io-index"
@@ -2381,18 +2399,18 @@ dependencies = [
[[package]]
name = "proc-macro2"
-version = "1.0.66"
+version = "1.0.71"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "18fb31db3f9bddb2ea821cde30a9f70117e3f119938b5ee630b7403aa6e2ead9"
+checksum = "75cb1540fadbd5b8fbccc4dddad2734eba435053f725621c070711a14bb5f4b8"
dependencies = [
"unicode-ident",
]
[[package]]
name = "quote"
-version = "1.0.32"
+version = "1.0.33"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "50f3b39ccfb720540debaa0164757101c08ecb8d326b15358ce76a62c7e85965"
+checksum = "5267fca4496028628a95160fc423a33e8b2e6af8a5302579e322e4b520293cae"
dependencies = [
"proc-macro2",
]
@@ -2443,24 +2461,15 @@ dependencies = [
]
[[package]]
-name = "redox_syscall"
-version = "0.3.5"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "567664f262709473930a4bf9e51bf2ebf3348f2e748ccc50dea20646858f8f29"
-dependencies = [
- "bitflags 1.3.2",
-]
-
-[[package]]
name = "regex"
-version = "1.9.4"
+version = "1.10.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "12de2eff854e5fa4b1295edd650e227e9d8fb0c9e90b12e7f36d6a6811791a29"
+checksum = "380b951a9c5e80ddfd6136919eef32310721aa4aacd4889a8d39124b026ab343"
dependencies = [
"aho-corasick",
"memchr",
- "regex-automata 0.3.7",
- "regex-syntax 0.7.5",
+ "regex-automata 0.4.3",
+ "regex-syntax 0.8.2",
]
[[package]]
@@ -2474,13 +2483,13 @@ dependencies = [
[[package]]
name = "regex-automata"
-version = "0.3.7"
+version = "0.4.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "49530408a136e16e5b486e883fbb6ba058e8e4e8ae6621a77b048b314336e629"
+checksum = "5f804c7828047e88b2d32e2d7fe5a105da8ee3264f01902f796c8e067dc2483f"
dependencies = [
"aho-corasick",
"memchr",
- "regex-syntax 0.7.5",
+ "regex-syntax 0.8.2",
]
[[package]]
@@ -2497,9 +2506,9 @@ checksum = "f162c6dd7b008981e4d40210aca20b4bd0f9b60ca9271061b07f78537722f2e1"
[[package]]
name = "regex-syntax"
-version = "0.7.5"
+version = "0.8.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "dbb5fb1acd8a1a18b3dd5be62d25485eb770e05afb408a9627d14d451bae12da"
+checksum = "c08c74e62047bb2de4ff487b251e4a92e24f48745648451635cec7d591162d9f"
[[package]]
name = "rfc6979"
@@ -2538,7 +2547,7 @@ dependencies = [
"libc",
"spin 0.9.8",
"untrusted 0.9.0",
- "windows-sys",
+ "windows-sys 0.48.0",
]
[[package]]
@@ -2565,23 +2574,23 @@ dependencies = [
[[package]]
name = "rpassword"
-version = "7.2.0"
+version = "7.3.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "6678cf63ab3491898c0d021b493c94c9b221d91295294a2a5746eacbe5928322"
+checksum = "80472be3c897911d0137b2d2b9055faf6eeac5b14e324073d83bc17b191d7e3f"
dependencies = [
"libc",
"rtoolbox",
- "winapi",
+ "windows-sys 0.48.0",
]
[[package]]
name = "rtoolbox"
-version = "0.0.1"
+version = "0.0.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "034e22c514f5c0cb8a10ff341b9b048b5ceb21591f31c8f44c43b960f9b3524a"
+checksum = "c247d24e63230cdb56463ae328478bd5eac8b8faa8c69461a77e8e323afac90e"
dependencies = [
"libc",
- "winapi",
+ "windows-sys 0.48.0",
]
[[package]]
@@ -2610,23 +2619,36 @@ dependencies = [
[[package]]
name = "rustix"
-version = "0.37.23"
+version = "0.37.27"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "4d69718bf81c6127a49dc64e44a742e8bb9213c0ff8869a22c308f84c1d4ab06"
+checksum = "fea8ca367a3a01fe35e6943c400addf443c0f57670e6ec51196f71a4b8762dd2"
dependencies = [
"bitflags 1.3.2",
"errno",
"io-lifetimes",
"libc",
- "linux-raw-sys",
- "windows-sys",
+ "linux-raw-sys 0.3.8",
+ "windows-sys 0.48.0",
+]
+
+[[package]]
+name = "rustix"
+version = "0.38.28"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "72e572a5e8ca657d7366229cdde4bd14c4eb5499a9573d4d366fe1b599daa316"
+dependencies = [
+ "bitflags 2.4.1",
+ "errno",
+ "libc",
+ "linux-raw-sys 0.4.12",
+ "windows-sys 0.52.0",
]
[[package]]
name = "rustls"
-version = "0.20.8"
+version = "0.20.9"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "fff78fc74d175294f4e83b28343315ffcfb114b156f0185e9741cb5570f50e2f"
+checksum = "1b80e3dec595989ea8510028f30c408a4630db12c9cbb8de34203b89d6577e99"
dependencies = [
"log",
"ring 0.16.20",
@@ -2660,11 +2682,11 @@ dependencies = [
[[package]]
name = "rustls-pemfile"
-version = "1.0.3"
+version = "1.0.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "2d3987094b1d07b653b7dfdc3f70ce9a1da9c51ac18c1b06b662e4f9a0e9f4b2"
+checksum = "1c74cae0a4cf6ccbbf5f359f08efdf8ee7e1dc532573bf0db71968cb56b1448c"
dependencies = [
- "base64 0.21.2",
+ "base64 0.21.5",
]
[[package]]
@@ -2679,9 +2701,9 @@ dependencies = [
[[package]]
name = "ryu"
-version = "1.0.15"
+version = "1.0.16"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "1ad4cc8da4ef723ed60bced201181d83791ad433213d8c24efffda1eec85d741"
+checksum = "f98d2aa92eebf49b69786be48e4477826b256916e84a57ff2a4f21923b48eb4c"
[[package]]
name = "same-file"
@@ -2694,27 +2716,21 @@ dependencies = [
[[package]]
name = "schannel"
-version = "0.1.22"
+version = "0.1.23"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "0c3733bf4cf7ea0880754e19cb5a462007c4a8c1914bff372ccc95b464f1df88"
+checksum = "fbc91545643bcf3a0bbb6569265615222618bdf33ce4ffbbd13c4bbd4c093534"
dependencies = [
- "windows-sys",
+ "windows-sys 0.52.0",
]
[[package]]
-name = "scopeguard"
-version = "1.2.0"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "94143f37725109f92c262ed2cf5e59bce7498c01bcc1502d7b9afe439a4e9f49"
-
-[[package]]
name = "sct"
-version = "0.7.0"
+version = "0.7.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "d53dcdb7c9f8158937a7981b48accfd39a43af418591a5d008c7b22b5e1b7ca4"
+checksum = "da046153aa2352493d6cb7da4b6e5c0c057d8a1d0a9aa8560baffdd945acd414"
dependencies = [
- "ring 0.16.20",
- "untrusted 0.7.1",
+ "ring 0.17.7",
+ "untrusted 0.9.0",
]
[[package]]
@@ -2756,35 +2772,35 @@ dependencies = [
[[package]]
name = "semver"
-version = "1.0.18"
+version = "1.0.20"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "b0293b4b29daaf487284529cc2f5675b8e57c61f70167ba415a463651fd6a918"
+checksum = "836fa6a3e1e547f9a2c4040802ec865b5d85f4014efe00555d7090a3dcaa1090"
[[package]]
name = "serde"
-version = "1.0.171"
+version = "1.0.193"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "30e27d1e4fd7659406c492fd6cfaf2066ba8773de45ca75e855590f856dc34a9"
+checksum = "25dd9975e68d0cb5aa1120c288333fc98731bd1dd12f561e468ea4728c042b89"
dependencies = [
"serde_derive",
]
[[package]]
name = "serde_derive"
-version = "1.0.171"
+version = "1.0.193"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "389894603bd18c46fa56231694f8d827779c0951a667087194cf9de94ed24682"
+checksum = "43576ca501357b9b071ac53cdc7da8ef0cbd9493d8df094cd821777ea6e894d3"
dependencies = [
"proc-macro2",
"quote",
- "syn 2.0.27",
+ "syn 2.0.43",
]
[[package]]
name = "serde_json"
-version = "1.0.103"
+version = "1.0.108"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "d03b412469450d4404fe8499a268edd7f8b79fecb074b0d812ad64ca21f4031b"
+checksum = "3d1c7e3eac408d115102c4c24ad393e0821bb3a5df4d506a80f85f7a742a526b"
dependencies = [
"itoa",
"ryu",
@@ -2815,24 +2831,14 @@ dependencies = [
[[package]]
name = "sharded-slab"
-version = "0.1.4"
+version = "0.1.7"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "900fba806f70c630b0a382d0d825e17a0f19fcd059a2ade1ff237bcddf446b31"
+checksum = "f40ca3c46823713e0d4209592e8d6e826aa57e928f09752619fc696c499637f6"
dependencies = [
"lazy_static",
]
[[package]]
-name = "signal-hook"
-version = "0.3.17"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "8621587d4798caf8eb44879d42e56b9a93ea5dcd315a6487c357130095b62801"
-dependencies = [
- "libc",
- "signal-hook-registry",
-]
-
-[[package]]
name = "signal-hook-registry"
version = "1.4.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
@@ -2863,18 +2869,18 @@ dependencies = [
[[package]]
name = "slab"
-version = "0.4.8"
+version = "0.4.9"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "6528351c9bc8ab22353f9d776db39a20288e8d6c37ef8cfe3317cf875eecfc2d"
+checksum = "8f92a496fb766b417c996b9c5e57daf2f7ad3b0bebe1ccfca4856390e3d3bb67"
dependencies = [
"autocfg",
]
[[package]]
name = "smallvec"
-version = "1.11.0"
+version = "1.11.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "62bb4feee49fdd9f707ef802e22365a35de4b7b299de4763d44bfea899442ff9"
+checksum = "4dccd0940a2dcdf68d092b8cbab7dc0ad8fa938bf95787e1b916b0e3d0e8e970"
[[package]]
name = "smol"
@@ -2882,15 +2888,15 @@ version = "1.3.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "13f2b548cd8447f8de0fdf1c592929f70f4fc7039a05e47404b0d096ec6987a1"
dependencies = [
- "async-channel",
+ "async-channel 1.9.0",
"async-executor",
"async-fs",
- "async-io",
- "async-lock",
+ "async-io 1.13.0",
+ "async-lock 2.8.0",
"async-net",
"async-process",
"blocking",
- "futures-lite",
+ "futures-lite 1.13.0",
]
[[package]]
@@ -2933,15 +2939,25 @@ dependencies = [
[[package]]
name = "socket2"
-version = "0.4.9"
+version = "0.4.10"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "64a4a911eed85daf18834cfaa86a79b7d266ff93ff5ba14005426219480ed662"
+checksum = "9f7916fc008ca5542385b89a3d3ce689953c143e9304a9bf8beec1de48994c0d"
dependencies = [
"libc",
"winapi",
]
[[package]]
+name = "socket2"
+version = "0.5.5"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "7b5fac59a5cb5dd637972e5fca70daf0523c9067fcdc4842f053dae04a18f8e9"
+dependencies = [
+ "libc",
+ "windows-sys 0.48.0",
+]
+
+[[package]]
name = "sodiumoxide"
version = "0.2.7"
source = "registry+https://github.com/rust-lang/crates.io-index"
@@ -3006,9 +3022,9 @@ dependencies = [
[[package]]
name = "syn"
-version = "2.0.27"
+version = "2.0.43"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "b60f673f44a8255b9c8c657daf66a596d435f2da81a555b06dc644d080ba45e0"
+checksum = "ee659fb5f3d355364e1f3e5bc10fb82068efbf824a1e9d1c9504244a6469ad53"
dependencies = [
"proc-macro2",
"quote",
@@ -3035,9 +3051,9 @@ checksum = "55937e1799185b12863d447f42597ed69d9928686b8d88a1df17376a097d8369"
[[package]]
name = "termcolor"
-version = "1.2.0"
+version = "1.4.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "be55cf8942feac5c765c2c993422806843c9a9a45d4d5c407ad6dd2ea95eb9b6"
+checksum = "ff1bc3d3f05aff0403e8ac0d92ced918ec05b666a43f83297ccef5bea8a3d449"
dependencies = [
"winapi-util",
]
@@ -3050,22 +3066,22 @@ checksum = "222a222a5bfe1bba4a77b45ec488a741b3cb8872e5e499451fd7d0129c9c7c3d"
[[package]]
name = "thiserror"
-version = "1.0.44"
+version = "1.0.52"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "611040a08a0439f8248d1990b111c95baa9c704c805fa1f62104b39655fd7f90"
+checksum = "83a48fd946b02c0a526b2e9481c8e2a17755e47039164a86c4070446e3a4614d"
dependencies = [
"thiserror-impl",
]
[[package]]
name = "thiserror-impl"
-version = "1.0.44"
+version = "1.0.52"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "090198534930841fab3a5d1bb637cde49e339654e606195f8d9c76eeb081dc96"
+checksum = "e7fbe9b594d6568a6a1443250a7e67d80b74e1e96f6d1715e1e21cc1888291d3"
dependencies = [
"proc-macro2",
"quote",
- "syn 2.0.27",
+ "syn 2.0.43",
]
[[package]]
@@ -3080,22 +3096,13 @@ dependencies = [
[[package]]
name = "time"
-version = "0.1.45"
+version = "0.3.31"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "1b797afad3f312d1c66a56d11d0316f916356d11bd158fbc6ca6389ff6bf805a"
-dependencies = [
- "libc",
- "wasi 0.10.0+wasi-snapshot-preview1",
- "winapi",
-]
-
-[[package]]
-name = "time"
-version = "0.3.23"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "59e399c068f43a5d116fedaf73b203fa4f9c519f17e2b34f63221d3792f81446"
+checksum = "f657ba42c3f86e7680e53c8cd3af8abbe56b5491790b46e22e19c0d57463583e"
dependencies = [
+ "deranged",
"itoa",
+ "powerfmt",
"serde",
"time-core",
"time-macros",
@@ -3103,15 +3110,15 @@ dependencies = [
[[package]]
name = "time-core"
-version = "0.1.1"
+version = "0.1.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "7300fbefb4dadc1af235a9cef3737cea692a9d97e1b9cbcd4ebdae6f8868e6fb"
+checksum = "ef927ca75afb808a4d64dd374f00a2adf8d0fcff8e7b184af886c3c87ec4a3f3"
[[package]]
name = "time-macros"
-version = "0.2.10"
+version = "0.2.16"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "96ba15a897f3c86766b757e5ac7221554c6750054d74d5b28844fce5fb36a6c4"
+checksum = "26197e33420244aeb70c3e8c78376ca46571bc4e701e4791c2cd9f57dcb3a43f"
dependencies = [
"time-core",
]
@@ -3133,33 +3140,31 @@ checksum = "1f3ccbac311fea05f86f61904b462b55fb3df8837a366dfc601a0161d0532f20"
[[package]]
name = "tokio"
-version = "1.29.1"
+version = "1.35.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "532826ff75199d5833b9d2c5fe410f29235e25704ee5f0ef599fb51c21f4a4da"
+checksum = "c89b4efa943be685f629b149f53829423f8f5531ea21249408e8e2f8671ec104"
dependencies = [
- "autocfg",
"backtrace",
"bytes",
"libc",
"mio",
"num_cpus",
- "parking_lot",
- "pin-project-lite 0.2.10",
+ "pin-project-lite 0.2.13",
"signal-hook-registry",
- "socket2",
+ "socket2 0.5.5",
"tokio-macros",
- "windows-sys",
+ "windows-sys 0.48.0",
]
[[package]]
name = "tokio-macros"
-version = "2.1.0"
+version = "2.2.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "630bdcf245f78637c13ec01ffae6187cca34625e8c63150d424b59e55af2675e"
+checksum = "5b8a1e28f2deaa14e508979454cb3a223b10b938b45af148bc0986de36f1923b"
dependencies = [
"proc-macro2",
"quote",
- "syn 2.0.27",
+ "syn 2.0.43",
]
[[package]]
@@ -3168,7 +3173,7 @@ version = "0.23.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c43ee83903113e03984cb9e5cebe6c04a5116269e900e3ddba8f068a62adda59"
dependencies = [
- "rustls 0.20.8",
+ "rustls 0.20.9",
"tokio",
"webpki",
]
@@ -3190,38 +3195,21 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "397c988d37662c7dda6d2208364a706264bf3d6138b11d436cbac0ad38832842"
dependencies = [
"futures-core",
- "pin-project-lite 0.2.10",
- "tokio",
-]
-
-[[package]]
-name = "tokio-tower"
-version = "0.6.0"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "f4322b6e2ebfd3be4082c16df4341505ef333683158b55f22afaf3f61565d728"
-dependencies = [
- "crossbeam",
- "futures-core",
- "futures-sink",
- "futures-util",
- "pin-project",
+ "pin-project-lite 0.2.13",
"tokio",
- "tower",
- "tower-service",
- "tracing",
]
[[package]]
name = "tokio-util"
-version = "0.7.8"
+version = "0.7.10"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "806fe8c2c87eccc8b3267cbae29ed3ab2d0bd37fca70ab622e46aaa9375ddb7d"
+checksum = "5419f34732d9eb6ee4c3578b7989078579b7f039cbbb9ca2c4da015749371e15"
dependencies = [
"bytes",
"futures-core",
"futures-io",
"futures-sink",
- "pin-project-lite 0.2.10",
+ "pin-project-lite 0.2.13",
"tokio",
"tracing",
]
@@ -3236,33 +3224,6 @@ dependencies = [
]
[[package]]
-name = "tower"
-version = "0.4.13"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "b8fa9be0de6cf49e536ce1851f987bd21a43b771b09473c3549a6c853db37c1c"
-dependencies = [
- "futures-core",
- "futures-util",
- "hdrhistogram",
- "indexmap",
- "pin-project",
- "pin-project-lite 0.2.10",
- "rand",
- "slab",
- "tokio",
- "tokio-util",
- "tower-layer",
- "tower-service",
- "tracing",
-]
-
-[[package]]
-name = "tower-layer"
-version = "0.3.2"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "c20c8dbed6283a09604c3e69b4b7eeb54e298b8a600d4d5ecb5ad39de609f1d0"
-
-[[package]]
name = "tower-service"
version = "0.3.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
@@ -3270,64 +3231,52 @@ checksum = "b6bc1c9ce2b5135ac7f93c72918fc37feb872bdc6a5533a8b85eb4b86bfdae52"
[[package]]
name = "tracing"
-version = "0.1.37"
+version = "0.1.40"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "8ce8c33a8d48bd45d624a6e523445fd21ec13d3653cd51f681abf67418f54eb8"
+checksum = "c3523ab5a71916ccf420eebdf5521fcef02141234bbc0b8a49f2fdc4544364ef"
dependencies = [
- "cfg-if",
- "log",
- "pin-project-lite 0.2.10",
+ "pin-project-lite 0.2.13",
"tracing-attributes",
"tracing-core",
]
[[package]]
name = "tracing-attributes"
-version = "0.1.26"
+version = "0.1.27"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "5f4f31f56159e98206da9efd823404b79b6ef3143b4a7ab76e67b1751b25a4ab"
+checksum = "34704c8d6ebcbc939824180af020566b01a7c01f80641264eba0999f6c2b6be7"
dependencies = [
"proc-macro2",
"quote",
- "syn 2.0.27",
+ "syn 2.0.43",
]
[[package]]
name = "tracing-core"
-version = "0.1.31"
+version = "0.1.32"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "0955b8137a1df6f1a2e9a37d8a6656291ff0297c1a97c24e0d8425fe2312f79a"
+checksum = "c06d3da6113f116aaee68e4d601191614c9053067f9ab7f6edbcb161237daa54"
dependencies = [
"once_cell",
"valuable",
]
[[package]]
-name = "tracing-futures"
-version = "0.2.5"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "97d095ae15e245a057c8e8451bab9b3ee1e1f68e9ba2b4fbc18d0ac5237835f2"
-dependencies = [
- "pin-project",
- "tracing",
-]
-
-[[package]]
name = "tracing-log"
-version = "0.1.3"
+version = "0.2.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "78ddad33d2d10b1ed7eb9d1f518a5674713876e97e5bb9b7345a7984fbb4f922"
+checksum = "ee855f1f400bd0e5c02d150ae5de3840039a3f54b025156404e34c23c03f47c3"
dependencies = [
- "lazy_static",
"log",
+ "once_cell",
"tracing-core",
]
[[package]]
name = "tracing-subscriber"
-version = "0.3.17"
+version = "0.3.18"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "30a651bc37f915e81f087d86e62a18eec5f79550c7faff886f7090b4ea757c77"
+checksum = "ad0f048c97dbd9faa9b7df56362b8ebcaa52adb06b498c050d2f4e32f90a7a8b"
dependencies = [
"nu-ansi-term",
"sharded-slab",
@@ -3339,27 +3288,27 @@ dependencies = [
[[package]]
name = "try-lock"
-version = "0.2.4"
+version = "0.2.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "3528ecfd12c466c6f163363caf2d02a71161dd5e1cc6ae7b34207ea2d42d81ed"
+checksum = "e421abadd41a4225275504ea4d6566923418b7f05506fbc9c0fe86ba7396114b"
[[package]]
name = "typenum"
-version = "1.16.0"
+version = "1.17.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "497961ef93d974e23eb6f433eb5fe1b7930b659f06d12dec6fc44a8f554c0bba"
+checksum = "42ff0bf0c66b8238c6f3b578df37d0b7848e55df8577b3f74f92a69acceeb825"
[[package]]
name = "unicode-bidi"
-version = "0.3.13"
+version = "0.3.14"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "92888ba5573ff080736b3648696b70cafad7d250551175acbaa4e0385b3e1460"
+checksum = "6f2528f27a9eb2b21e69c95319b30bd0efd85d09c379741b0f78ea1d86be2416"
[[package]]
name = "unicode-ident"
-version = "1.0.11"
+version = "1.0.12"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "301abaae475aa91687eb82514b328ab47a211a533026cb25fc3e519b86adfc3c"
+checksum = "3354b9ac3fae1ff6755cb6db53683adb661634f67557942dea4facebec0fee4b"
[[package]]
name = "unicode-normalization"
@@ -3371,12 +3320,6 @@ dependencies = [
]
[[package]]
-name = "unicode-width"
-version = "0.1.10"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "c0edd1e5b14653f783770bce4a4dabb4a5108a5370a5f5d8cfe8710c361f6c8b"
-
-[[package]]
name = "unicode-xid"
version = "0.2.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
@@ -3396,12 +3339,12 @@ checksum = "8ecb6da28b8a351d773b68d5825ac39017e680750f980f3a1a85cd8dd28a47c1"
[[package]]
name = "url"
-version = "2.4.0"
+version = "2.5.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "50bff7831e19200a85b17131d085c25d7811bc4e186efdaf54bbd132994a88cb"
+checksum = "31e6302e3bb753d46e83516cae55ae196fc0c309407cf11ab35cc51a4c2a4633"
dependencies = [
"form_urlencoded",
- "idna 0.4.0",
+ "idna 0.5.0",
"percent-encoding",
]
@@ -3425,9 +3368,9 @@ checksum = "830b7e5d4d90034032940e4ace0d9a9a057e7a45cd94e6c007832e39edb82f6d"
[[package]]
name = "value-bag"
-version = "1.4.1"
+version = "1.4.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "d92ccd67fb88503048c01b59152a04effd0782d035a83a6d256ce6085f08f4a3"
+checksum = "4a72e1902dde2bd6441347de2b70b7f5d59bf157c6c62f0c44572607a1d55bbe"
[[package]]
name = "version_check"
@@ -3443,15 +3386,15 @@ checksum = "5c3082ca00d5a5ef149bb8b555a72ae84c9c59f7250f013ac822ac2e49b19c64"
[[package]]
name = "waker-fn"
-version = "1.1.0"
+version = "1.1.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "9d5b2c62b4012a3e1eca5a7e077d13b3bf498c4073e33ccd58626607748ceeca"
+checksum = "f3c4517f54858c779bbcbf228f4fca63d121bf85fbecb2dc578cdf4a39395690"
[[package]]
name = "walkdir"
-version = "2.3.3"
+version = "2.4.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "36df944cda56c7d8d8b7496af378e6b16de9284591917d307c9b4d313c44e698"
+checksum = "d71d857dc86794ca4c280d616f7da00d2dbfd8cd788846559a6813e6aa4b54ee"
dependencies = [
"same-file",
"winapi-util",
@@ -3468,21 +3411,15 @@ dependencies = [
[[package]]
name = "wasi"
-version = "0.10.0+wasi-snapshot-preview1"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "1a143597ca7c7793eff794def352d41792a93c481eb1042423ff7ff72ba2c31f"
-
-[[package]]
-name = "wasi"
version = "0.11.0+wasi-snapshot-preview1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9c8d87e72b64a3b4db28d11ce29237c246188f4f51057d65a7eab63b7987e423"
[[package]]
name = "wasm-bindgen"
-version = "0.2.87"
+version = "0.2.89"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "7706a72ab36d8cb1f80ffbf0e071533974a60d0a308d01a5d0375bf60499a342"
+checksum = "0ed0d4f68a3015cc185aff4db9506a015f4b96f95303897bfa23f846db54064e"
dependencies = [
"cfg-if",
"wasm-bindgen-macro",
@@ -3490,24 +3427,24 @@ dependencies = [
[[package]]
name = "wasm-bindgen-backend"
-version = "0.2.87"
+version = "0.2.89"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "5ef2b6d3c510e9625e5fe6f509ab07d66a760f0885d858736483c32ed7809abd"
+checksum = "1b56f625e64f3a1084ded111c4d5f477df9f8c92df113852fa5a374dbda78826"
dependencies = [
"bumpalo",
"log",
"once_cell",
"proc-macro2",
"quote",
- "syn 2.0.27",
+ "syn 2.0.43",
"wasm-bindgen-shared",
]
[[package]]
name = "wasm-bindgen-futures"
-version = "0.4.37"
+version = "0.4.39"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "c02dbc21516f9f1f04f187958890d7e6026df8d16540b7ad9492bc34a67cea03"
+checksum = "ac36a15a220124ac510204aec1c3e5db8a22ab06fd6706d881dc6149f8ed9a12"
dependencies = [
"cfg-if",
"js-sys",
@@ -3517,9 +3454,9 @@ dependencies = [
[[package]]
name = "wasm-bindgen-macro"
-version = "0.2.87"
+version = "0.2.89"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "dee495e55982a3bd48105a7b947fd2a9b4a8ae3010041b9e0faab3f9cd028f1d"
+checksum = "0162dbf37223cd2afce98f3d0785506dcb8d266223983e4b5b525859e6e182b2"
dependencies = [
"quote",
"wasm-bindgen-macro-support",
@@ -3527,28 +3464,28 @@ dependencies = [
[[package]]
name = "wasm-bindgen-macro-support"
-version = "0.2.87"
+version = "0.2.89"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "54681b18a46765f095758388f2d0cf16eb8d4169b639ab575a8f5693af210c7b"
+checksum = "f0eb82fcb7930ae6219a7ecfd55b217f5f0893484b7a13022ebb2b2bf20b5283"
dependencies = [
"proc-macro2",
"quote",
- "syn 2.0.27",
+ "syn 2.0.43",
"wasm-bindgen-backend",
"wasm-bindgen-shared",
]
[[package]]
name = "wasm-bindgen-shared"
-version = "0.2.87"
+version = "0.2.89"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "ca6ad05a4870b2bf5fe995117d3728437bd27d7cd5f06f13c17443ef369775a1"
+checksum = "7ab9b36309365056cd639da3134bf87fa8f3d86008abf99e612384a6eecd459f"
[[package]]
name = "web-sys"
-version = "0.3.64"
+version = "0.3.66"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "9b85cbef8c220a6abc02aefd892dfc0fc23afb1c6a426316ec33253a3877249b"
+checksum = "50c24a44ec86bb68fbecd1b3efed7e85ea5621b39b35ef2766b66cd984f8010f"
dependencies = [
"js-sys",
"wasm-bindgen",
@@ -3556,12 +3493,12 @@ dependencies = [
[[package]]
name = "webpki"
-version = "0.22.0"
+version = "0.22.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "f095d78192e208183081cc07bc5515ef55216397af48b873e5edcd72637fa1bd"
+checksum = "ed63aea5ce73d0ff405984102c42de94fc55a6b75765d621c65262469b3c9b53"
dependencies = [
- "ring 0.16.20",
- "untrusted 0.7.1",
+ "ring 0.17.7",
+ "untrusted 0.9.0",
]
[[package]]
@@ -3582,9 +3519,9 @@ checksum = "ac3b87c63620426dd9b991e5ce0329eff545bccbbb34f3be09ff6fb6ab51b7b6"
[[package]]
name = "winapi-util"
-version = "0.1.5"
+version = "0.1.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "70ec6ce85bb158151cae5e5c87f95a8e97d2c0c4b001223f33a334e3ce5de178"
+checksum = "f29e6f9198ba0d26b4c9f07dbe6f9ed633e1f3d5b8b414090084349e46a52596"
dependencies = [
"winapi",
]
@@ -3596,12 +3533,12 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f"
[[package]]
-name = "windows"
-version = "0.48.0"
+name = "windows-core"
+version = "0.51.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "e686886bc078bc1b0b600cac0147aadb815089b6e4da64016cbd754b6342700f"
+checksum = "f1f8cf84f35d2db49a46868f947758c7a1138116f7fac3bc844f43ade1292e64"
dependencies = [
- "windows-targets",
+ "windows-targets 0.48.5",
]
[[package]]
@@ -3610,65 +3547,131 @@ version = "0.48.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "677d2418bec65e3338edb076e806bc1ec15693c5d0104683f2efe857f61056a9"
dependencies = [
- "windows-targets",
+ "windows-targets 0.48.5",
+]
+
+[[package]]
+name = "windows-sys"
+version = "0.52.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "282be5f36a8ce781fad8c8ae18fa3f9beff57ec1b52cb3de0789201425d9a33d"
+dependencies = [
+ "windows-targets 0.52.0",
]
[[package]]
name = "windows-targets"
-version = "0.48.1"
+version = "0.48.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "05d4b17490f70499f20b9e791dcf6a299785ce8af4d709018206dc5b4953e95f"
+checksum = "9a2fa6e2155d7247be68c096456083145c183cbbbc2764150dda45a87197940c"
dependencies = [
- "windows_aarch64_gnullvm",
- "windows_aarch64_msvc",
- "windows_i686_gnu",
- "windows_i686_msvc",
- "windows_x86_64_gnu",
- "windows_x86_64_gnullvm",
- "windows_x86_64_msvc",
+ "windows_aarch64_gnullvm 0.48.5",
+ "windows_aarch64_msvc 0.48.5",
+ "windows_i686_gnu 0.48.5",
+ "windows_i686_msvc 0.48.5",
+ "windows_x86_64_gnu 0.48.5",
+ "windows_x86_64_gnullvm 0.48.5",
+ "windows_x86_64_msvc 0.48.5",
+]
+
+[[package]]
+name = "windows-targets"
+version = "0.52.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "8a18201040b24831fbb9e4eb208f8892e1f50a37feb53cc7ff887feb8f50e7cd"
+dependencies = [
+ "windows_aarch64_gnullvm 0.52.0",
+ "windows_aarch64_msvc 0.52.0",
+ "windows_i686_gnu 0.52.0",
+ "windows_i686_msvc 0.52.0",
+ "windows_x86_64_gnu 0.52.0",
+ "windows_x86_64_gnullvm 0.52.0",
+ "windows_x86_64_msvc 0.52.0",
]
[[package]]
name = "windows_aarch64_gnullvm"
-version = "0.48.0"
+version = "0.48.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "91ae572e1b79dba883e0d315474df7305d12f569b400fcf90581b06062f7e1bc"
+checksum = "2b38e32f0abccf9987a4e3079dfb67dcd799fb61361e53e2882c3cbaf0d905d8"
+
+[[package]]
+name = "windows_aarch64_gnullvm"
+version = "0.52.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "cb7764e35d4db8a7921e09562a0304bf2f93e0a51bfccee0bd0bb0b666b015ea"
[[package]]
name = "windows_aarch64_msvc"
-version = "0.48.0"
+version = "0.48.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "b2ef27e0d7bdfcfc7b868b317c1d32c641a6fe4629c171b8928c7b08d98d7cf3"
+checksum = "dc35310971f3b2dbbf3f0690a219f40e2d9afcf64f9ab7cc1be722937c26b4bc"
+
+[[package]]
+name = "windows_aarch64_msvc"
+version = "0.52.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "bbaa0368d4f1d2aaefc55b6fcfee13f41544ddf36801e793edbbfd7d7df075ef"
[[package]]
name = "windows_i686_gnu"
-version = "0.48.0"
+version = "0.48.5"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "a75915e7def60c94dcef72200b9a8e58e5091744960da64ec734a6c6e9b3743e"
+
+[[package]]
+name = "windows_i686_gnu"
+version = "0.52.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "622a1962a7db830d6fd0a69683c80a18fda201879f0f447f065a3b7467daa241"
+checksum = "a28637cb1fa3560a16915793afb20081aba2c92ee8af57b4d5f28e4b3e7df313"
[[package]]
name = "windows_i686_msvc"
-version = "0.48.0"
+version = "0.48.5"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "8f55c233f70c4b27f66c523580f78f1004e8b5a8b659e05a4eb49d4166cca406"
+
+[[package]]
+name = "windows_i686_msvc"
+version = "0.52.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "4542c6e364ce21bf45d69fdd2a8e455fa38d316158cfd43b3ac1c5b1b19f8e00"
+checksum = "ffe5e8e31046ce6230cc7215707b816e339ff4d4d67c65dffa206fd0f7aa7b9a"
[[package]]
name = "windows_x86_64_gnu"
-version = "0.48.0"
+version = "0.48.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "ca2b8a661f7628cbd23440e50b05d705db3686f894fc9580820623656af974b1"
+checksum = "53d40abd2583d23e4718fddf1ebec84dbff8381c07cae67ff7768bbf19c6718e"
+
+[[package]]
+name = "windows_x86_64_gnu"
+version = "0.52.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "3d6fa32db2bc4a2f5abeacf2b69f7992cd09dca97498da74a151a3132c26befd"
[[package]]
name = "windows_x86_64_gnullvm"
-version = "0.48.0"
+version = "0.48.5"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "0b7b52767868a23d5bab768e390dc5f5c55825b6d30b86c844ff2dc7414044cc"
+
+[[package]]
+name = "windows_x86_64_gnullvm"
+version = "0.52.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "7896dbc1f41e08872e9d5e8f8baa8fdd2677f29468c4e156210174edc7f7b953"
+checksum = "1a657e1e9d3f514745a572a6846d3c7aa7dbe1658c056ed9c3344c4109a6949e"
[[package]]
name = "windows_x86_64_msvc"
-version = "0.48.0"
+version = "0.48.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "1a515f5799fe4961cb532f983ce2b23082366b898e52ffbce459c86f67c8378a"
+checksum = "ed94fce61571a4006852b7389a063ab983c02eb1bb37b47f8272ce92d06d9538"
+
+[[package]]
+name = "windows_x86_64_msvc"
+version = "0.52.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "dff9641d1cd4be8d1a070daf9e3773c5f67e78b4d9d42263020c057706765c04"
[[package]]
name = "wyz"
@@ -3691,7 +3694,7 @@ dependencies = [
"oid-registry",
"rusticata-macros",
"thiserror",
- "time 0.3.23",
+ "time",
]
[[package]]
@@ -3702,9 +3705,9 @@ checksum = "66fee0b777b0f5ac1c69bb06d361268faafa61cd4682ae064a171c16c433e9e4"
[[package]]
name = "zeroize"
-version = "1.6.0"
+version = "1.7.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "2a0956f1ba7c7909bfb66c2e9e4124ab6f6482560f6628b5aaeba39207c9aad9"
+checksum = "525b4ec142c6b68a2d10f01f7bbf6755599ca3f81ea53b8431b7dd348f5fdb2d"
[[package]]
name = "zstd"
diff --git a/Cargo.toml b/Cargo.toml
index ce8cc8e..2a55524 100644
--- a/Cargo.toml
+++ b/Cargo.toml
@@ -7,48 +7,67 @@ license = "AGPL-3.0"
description = "Encrypted mail storage over Garage"
[dependencies]
-aws-config = { version = "1.1.1", features = ["behavior-version-latest"] }
-aws-sdk-s3 = "1.9.0"
-anyhow = "1.0.28"
-argon2 = "0.5"
-async-trait = "0.1"
+# async runtime
+tokio = { version = "1.18", default-features = false, features = ["rt", "rt-multi-thread", "io-util", "net", "time", "macros", "sync", "signal", "fs"] }
+tokio-util = { version = "0.7", features = [ "compat" ] }
+futures = "0.3"
+
+# debug
+log = "0.4"
backtrace = "0.3"
-base64 = "0.21"
-clap = { version = "3.1.18", features = ["derive", "env"] }
+tracing-subscriber = "0.3"
+tracing = "0.1"
+
+# language extensions
+lazy_static = "1.4"
duplexify = "1.1.0"
-eml-codec = { git = "https://git.deuxfleurs.fr/Deuxfleurs/eml-codec.git", branch = "main" }
-hex = "0.4"
-futures = "0.3"
im = "15"
+anyhow = "1.0.28"
+async-trait = "0.1"
itertools = "0.10"
-lazy_static = "1.4"
-ldap3 = { version = "0.10", default-features = false, features = ["tls-rustls"] }
-log = "0.4"
-hyper-rustls = { version = "0.24", features = ["http2"] }
+chrono = { version = "0.4", default-features = false, features = ["alloc"] }
+
+# process related
nix = { version = "0.27", features = ["signal"] }
+clap = { version = "3.1.18", features = ["derive", "env"] }
+
+# serialization
serde = "1.0.137"
-rand = "0.8.5"
rmp-serde = "0.15"
-rpassword = "7.0"
-sodiumoxide = "0.2"
-tokio = { version = "1.18", default-features = false, features = ["rt", "rt-multi-thread", "io-util", "net", "time", "macros", "sync", "signal", "fs"] }
-tokio-util = { version = "0.7", features = [ "compat" ] }
toml = "0.5"
+base64 = "0.21"
+hex = "0.4"
zstd = { version = "0.9", default-features = false }
-tracing-subscriber = "0.3"
-tracing = "0.1"
-tower = "0.4"
+# cryptography & security
+sodiumoxide = "0.2"
+argon2 = "0.5"
+rand = "0.8.5"
+hyper-rustls = { version = "0.24", features = ["http2"] }
+rpassword = "7.0"
-imap-codec = { git = "https://github.com/superboum/imap-codec.git", branch = "v0.5.x" }
-chrono = { version = "0.4", default-features = false, features = ["alloc"] }
+# 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" }
-boitalettres = { git = "https://git.deuxfleurs.fr/quentin/boitalettres.git", branch = "expose-mydatetime" }
+aws-config = { version = "1.1.1", features = ["behavior-version-latest"] }
+aws-sdk-s3 = "1.9.0"
+
+# email protocols
+eml-codec = { git = "https://git.deuxfleurs.fr/Deuxfleurs/eml-codec.git", branch = "main" }
smtp-message = { git = "http://github.com/Alexis211/kannader", branch = "feature/lmtp" }
smtp-server = { git = "http://github.com/Alexis211/kannader", branch = "feature/lmtp" }
-
-#k2v-client = { path = "../garage/src/k2v-client" }
+imap-codec = { version = "1.0.0", features = ["quirk_crlf_relaxed", "bounded-static"] }
+imap-flow = { git = "https://github.com/duesee/imap-flow.git", rev = "e45ce7bb6ab6bda3c71a0c7b05e9b558a5902e90" }
[dev-dependencies]
+[patch.crates-io]
+imap-types = { git = "https://github.com/duesee/imap-codec", branch = "v2" }
+imap-codec = { git = "https://github.com/duesee/imap-codec", branch = "v2" }
+
+[[test]]
+name = "imap_features"
+path = "tests/imap_features.rs"
+harness = false
diff --git a/src/bayou.rs b/src/bayou.rs
index 7253a30..c6a7ac0 100644
--- a/src/bayou.rs
+++ b/src/bayou.rs
@@ -450,10 +450,7 @@ impl K2vWatch {
) {
let mut row = match Weak::upgrade(&self_weak) {
Some(this) => this.target.clone(),
- None => {
- error!("can't start loop");
- return;
- }
+ None => return,
};
while let Some(this) = Weak::upgrade(&self_weak) {
diff --git a/src/config.rs b/src/config.rs
index 1438910..b9c1f09 100644
--- a/src/config.rs
+++ b/src/config.rs
@@ -26,6 +26,7 @@ pub struct ProviderConfig {
#[derive(Serialize, Deserialize, Debug, Clone)]
#[serde(tag = "user_driver")]
pub enum UserManagement {
+ Demo,
Static(LoginStaticConfig),
Ldap(LoginLdapConfig),
}
diff --git a/src/future_rest_admin_api.txt b/src/future_rest_admin_api.txt
deleted file mode 100644
index 19ece27..0000000
--- a/src/future_rest_admin_api.txt
+++ /dev/null
@@ -1,174 +0,0 @@
- Command::FirstLogin {
- creds,
- user_secrets,
- } => {
- let creds = make_storage_creds(creds);
- let user_secrets = make_user_secrets(user_secrets);
-
- println!("Please enter your password for key decryption.");
- println!("If you are using LDAP login, this must be your LDAP password.");
- println!("If you are using the static login provider, enter any password, and this will also become your password for local IMAP access.");
- let password = rpassword::prompt_password("Enter password: ")?;
- let password_confirm = rpassword::prompt_password("Confirm password: ")?;
- if password != password_confirm {
- bail!("Passwords don't match.");
- }
-
- CryptoKeys::init(&creds, &user_secrets, &password).await?;
-
- println!("");
- println!("Cryptographic key setup is complete.");
- println!("");
- println!("If you are using the static login provider, add the following section to your .toml configuration file:");
- println!("");
- dump_config(&password, &creds);
- }
- Command::InitializeLocalKeys { creds } => {
- let creds = make_storage_creds(creds);
-
- println!("Please enter a password for local IMAP access.");
- println!("This password is not used for key decryption, your keys will be printed below (do not lose them!)");
- println!(
- "If you plan on using LDAP login, stop right here and use `first-login` instead"
- );
- let password = rpassword::prompt_password("Enter password: ")?;
- let password_confirm = rpassword::prompt_password("Confirm password: ")?;
- if password != password_confirm {
- bail!("Passwords don't match.");
- }
-
- let master = gen_key();
- let (_, secret) = gen_keypair();
- let keys = CryptoKeys::init_without_password(&creds, &master, &secret).await?;
-
- println!("");
- println!("Cryptographic key setup is complete.");
- println!("");
- println!("Add the following section to your .toml configuration file:");
- println!("");
- dump_config(&password, &creds);
- dump_keys(&keys);
- }
- Command::AddPassword {
- creds,
- user_secrets,
- gen,
- } => {
- let creds = make_storage_creds(creds);
- let user_secrets = make_user_secrets(user_secrets);
-
- let existing_password =
- rpassword::prompt_password("Enter existing password to decrypt keys: ")?;
- let new_password = if gen {
- let password = base64::encode_config(
- &u128::to_be_bytes(thread_rng().gen())[..10],
- base64::URL_SAFE_NO_PAD,
- );
- println!("Your new password: {}", password);
- println!("Keep it safe!");
- password
- } else {
- let password = rpassword::prompt_password("Enter new password: ")?;
- let password_confirm = rpassword::prompt_password("Confirm new password: ")?;
- if password != password_confirm {
- bail!("Passwords don't match.");
- }
- password
- };
-
- let keys = CryptoKeys::open(&creds, &user_secrets, &existing_password).await?;
- keys.add_password(&creds, &user_secrets, &new_password)
- .await?;
- println!("");
- println!("New password added successfully.");
- }
- Command::DeletePassword {
- creds,
- user_secrets,
- allow_delete_all,
- } => {
- let creds = make_storage_creds(creds);
- let user_secrets = make_user_secrets(user_secrets);
-
- let existing_password = rpassword::prompt_password("Enter password to delete: ")?;
-
- let keys = match allow_delete_all {
- true => Some(CryptoKeys::open(&creds, &user_secrets, &existing_password).await?),
- false => None,
- };
-
- CryptoKeys::delete_password(&creds, &existing_password, allow_delete_all).await?;
-
- println!("");
- println!("Password was deleted successfully.");
-
- if let Some(keys) = keys {
- println!("As a reminder, here are your cryptographic keys:");
- dump_keys(&keys);
- }
- }
- Command::ShowKeys {
- creds,
- user_secrets,
- } => {
- let creds = make_storage_creds(creds);
- let user_secrets = make_user_secrets(user_secrets);
-
- let existing_password = rpassword::prompt_password("Enter key decryption password: ")?;
-
- let keys = CryptoKeys::open(&creds, &user_secrets, &existing_password).await?;
- dump_keys(&keys);
- }
- }
-
- Ok(())
-}
-
-fn make_storage_creds(c: StorageCredsArgs) -> StorageCredentials {
- let s3_region = Region {
- name: c.region.clone(),
- endpoint: c.s3_endpoint,
- };
- let k2v_region = Region {
- name: c.region,
- endpoint: c.k2v_endpoint,
- };
- StorageCredentials {
- k2v_region,
- s3_region,
- aws_access_key_id: c.aws_access_key_id,
- aws_secret_access_key: c.aws_secret_access_key,
- bucket: c.bucket,
- }
-}
-
-fn make_user_secrets(c: UserSecretsArgs) -> UserSecrets {
- UserSecrets {
- user_secret: c.user_secret,
- alternate_user_secrets: c
- .alternate_user_secrets
- .split(',')
- .map(|x| x.trim())
- .filter(|x| !x.is_empty())
- .map(|x| x.to_string())
- .collect(),
- }
-}
-
-fn dump_config(password: &str, creds: &StorageCredentials) {
- println!("[login_static.users.<username>]");
- println!(
- "password = \"{}\"",
- hash_password(password).expect("unable to hash password")
- );
- println!("aws_access_key_id = \"{}\"", creds.aws_access_key_id);
- println!(
- "aws_secret_access_key = \"{}\"",
- creds.aws_secret_access_key
- );
-}
-
-fn dump_keys(keys: &CryptoKeys) {
- println!("master_key = \"{}\"", base64::encode(&keys.master));
- println!("secret_key = \"{}\"", base64::encode(&keys.secret));
-}
diff --git a/src/imap/command/anonymous.rs b/src/imap/command/anonymous.rs
index d258bd3..fbd10e9 100644
--- a/src/imap/command/anonymous.rs
+++ b/src/imap/command/anonymous.rs
@@ -1,92 +1,77 @@
-use anyhow::{Error, Result};
-use boitalettres::proto::{res::body::Data as Body, Request, Response};
-use imap_codec::types::command::CommandBody;
-use imap_codec::types::core::AString;
-use imap_codec::types::response::{Capability, Data, Status};
+use anyhow::Result;
+use imap_codec::imap_types::command::{Command, CommandBody};
+use imap_codec::imap_types::core::AString;
+use imap_codec::imap_types::secret::Secret;
+use crate::imap::command::anystate;
use crate::imap::flow;
+use crate::imap::response::Response;
use crate::login::ArcLoginProvider;
use crate::mail::user::User;
//--- dispatching
pub struct AnonymousContext<'a> {
- pub req: &'a Request,
- pub login_provider: Option<&'a ArcLoginProvider>,
+ pub req: &'a Command<'static>,
+ pub login_provider: &'a ArcLoginProvider,
}
-pub async fn dispatch(ctx: AnonymousContext<'_>) -> Result<(Response, flow::Transition)> {
- match &ctx.req.command.body {
- CommandBody::Noop => Ok((Response::ok("Noop completed.")?, flow::Transition::None)),
- CommandBody::Capability => ctx.capability().await,
- CommandBody::Logout => ctx.logout().await,
+pub async fn dispatch(ctx: AnonymousContext<'_>) -> Result<(Response<'static>, flow::Transition)> {
+ match &ctx.req.body {
+ // Any State
+ CommandBody::Noop => anystate::noop_nothing(ctx.req.tag.clone()),
+ CommandBody::Capability => anystate::capability(ctx.req.tag.clone()),
+ CommandBody::Logout => anystate::logout(),
+
+ // Specific to anonymous context (3 commands)
CommandBody::Login { username, password } => ctx.login(username, password).await,
- _ => Ok((Response::no("Command unavailable")?, flow::Transition::None)),
+ CommandBody::Authenticate { .. } => {
+ anystate::not_implemented(ctx.req.tag.clone(), "authenticate")
+ }
+ //StartTLS is not implemented for now, we will probably go full TLS.
+
+ // Collect other commands
+ _ => anystate::wrong_state(ctx.req.tag.clone()),
}
}
//--- Command controllers, private
impl<'a> AnonymousContext<'a> {
- async fn capability(self) -> Result<(Response, flow::Transition)> {
- let capabilities = vec![Capability::Imap4Rev1, Capability::Idle];
- let res = Response::ok("Server capabilities")?.with_body(Data::Capability(capabilities));
- Ok((res, flow::Transition::None))
- }
-
async fn login(
self,
- username: &AString,
- password: &AString,
- ) -> Result<(Response, flow::Transition)> {
+ username: &AString<'a>,
+ password: &Secret<AString<'a>>,
+ ) -> Result<(Response<'static>, flow::Transition)> {
let (u, p) = (
- String::try_from(username.clone())?,
- String::try_from(password.clone())?,
+ std::str::from_utf8(username.as_ref())?,
+ std::str::from_utf8(password.declassify().as_ref())?,
);
tracing::info!(user = %u, "command.login");
- let login_provider = match &self.login_provider {
- Some(lp) => lp,
- None => {
- return Ok((
- Response::no("Login command not available (already logged in)")?,
- flow::Transition::None,
- ))
- }
- };
-
- let creds = match login_provider.login(&u, &p).await {
+ let creds = match self.login_provider.login(&u, &p).await {
Err(e) => {
tracing::debug!(error=%e, "authentication failed");
return Ok((
- Response::no("Authentication failed")?,
+ Response::build()
+ .to_req(self.req)
+ .message("Authentication failed")
+ .no()?,
flow::Transition::None,
));
}
Ok(c) => c,
};
- let user = User::new(u.clone(), creds).await?;
+ let user = User::new(u.to_string(), creds).await?;
tracing::info!(username=%u, "connected");
Ok((
- Response::ok("Completed")?,
+ Response::build()
+ .to_req(self.req)
+ .message("Completed")
+ .ok()?,
flow::Transition::Authenticate(user),
))
}
-
- // C: 10 logout
- // S: * BYE Logging out
- // S: 10 OK Logout completed.
- async fn logout(self) -> Result<(Response, flow::Transition)> {
- // @FIXME we should implement From<Vec<Status>> and From<Vec<ImapStatus>> in
- // boitalettres/src/proto/res/body.rs
- Ok((
- Response::ok("Logout completed")?.with_body(vec![Body::Status(
- Status::bye(None, "Logging out")
- .map_err(|e| Error::msg(e).context("Unable to generate IMAP status"))?,
- )]),
- flow::Transition::Logout,
- ))
- }
}
diff --git a/src/imap/command/anystate.rs b/src/imap/command/anystate.rs
new file mode 100644
index 0000000..42fe645
--- /dev/null
+++ b/src/imap/command/anystate.rs
@@ -0,0 +1,52 @@
+use anyhow::Result;
+use imap_codec::imap_types::core::{NonEmptyVec, Tag};
+use imap_codec::imap_types::response::{Capability, Data};
+
+use crate::imap::flow;
+use crate::imap::response::Response;
+
+pub(crate) fn capability(tag: Tag<'static>) -> Result<(Response<'static>, flow::Transition)> {
+ let capabilities: NonEmptyVec<Capability> =
+ (vec![Capability::Imap4Rev1, Capability::Idle]).try_into()?;
+ let res = Response::build()
+ .tag(tag)
+ .message("Server capabilities")
+ .data(Data::Capability(capabilities))
+ .ok()?;
+
+ Ok((res, flow::Transition::None))
+}
+
+pub(crate) fn noop_nothing(tag: Tag<'static>) -> Result<(Response<'static>, flow::Transition)> {
+ Ok((
+ Response::build().tag(tag).message("Noop completed.").ok()?,
+ flow::Transition::None,
+ ))
+}
+
+pub(crate) fn logout() -> Result<(Response<'static>, flow::Transition)> {
+ Ok((Response::bye()?, flow::Transition::Logout))
+}
+
+pub(crate) fn not_implemented<'a>(
+ tag: Tag<'a>,
+ what: &str,
+) -> Result<(Response<'a>, flow::Transition)> {
+ Ok((
+ Response::build()
+ .tag(tag)
+ .message(format!("Command not implemented {}", what))
+ .bad()?,
+ flow::Transition::None,
+ ))
+}
+
+pub(crate) fn wrong_state(tag: Tag<'static>) -> Result<(Response<'static>, flow::Transition)> {
+ Ok((
+ Response::build()
+ .tag(tag)
+ .message("Command not authorized in this state")
+ .bad()?,
+ flow::Transition::None,
+ ))
+}
diff --git a/src/imap/command/authenticated.rs b/src/imap/command/authenticated.rs
index 2deb723..1bb4c6d 100644
--- a/src/imap/command/authenticated.rs
+++ b/src/imap/command/authenticated.rs
@@ -2,37 +2,42 @@ use std::collections::BTreeMap;
use std::sync::Arc;
use anyhow::{anyhow, bail, Result};
-use boitalettres::proto::res::body::Data as Body;
-use boitalettres::proto::{Request, Response};
-use imap_codec::types::command::{CommandBody, StatusAttribute};
-use imap_codec::types::core::NonZeroBytes;
-use imap_codec::types::datetime::MyDateTime;
-use imap_codec::types::flag::{Flag, FlagNameAttribute};
-use imap_codec::types::mailbox::{ListMailbox, Mailbox as MailboxCodec};
-use imap_codec::types::response::{Code, Data, StatusAttributeValue};
-
-use crate::imap::command::anonymous;
+use imap_codec::imap_types::command::{Command, CommandBody};
+use imap_codec::imap_types::core::{Atom, Literal, QuotedChar};
+use imap_codec::imap_types::datetime::DateTime;
+use imap_codec::imap_types::flag::{Flag, FlagNameAttribute};
+use imap_codec::imap_types::mailbox::{ListMailbox, Mailbox as MailboxCodec};
+use imap_codec::imap_types::response::{Code, CodeOther, Data};
+use imap_codec::imap_types::status::{StatusDataItem, StatusDataItemName};
+
+use crate::imap::command::{anystate, MailboxName};
use crate::imap::flow;
use crate::imap::mailbox_view::MailboxView;
+use crate::imap::response::Response;
use crate::mail::mailbox::Mailbox;
use crate::mail::uidindex::*;
-use crate::mail::user::{User, INBOX, MAILBOX_HIERARCHY_DELIMITER};
+use crate::mail::user::{User, MAILBOX_HIERARCHY_DELIMITER as MBX_HIER_DELIM_RAW};
use crate::mail::IMF;
pub struct AuthenticatedContext<'a> {
- pub req: &'a Request,
+ pub req: &'a Command<'static>,
pub user: &'a Arc<User>,
}
-pub async fn dispatch(ctx: AuthenticatedContext<'_>) -> Result<(Response, flow::Transition)> {
- match &ctx.req.command.body {
+pub async fn dispatch<'a>(
+ ctx: AuthenticatedContext<'a>,
+) -> Result<(Response<'static>, flow::Transition)> {
+ match &ctx.req.body {
+ // Any state
+ CommandBody::Noop => anystate::noop_nothing(ctx.req.tag.clone()),
+ CommandBody::Capability => anystate::capability(ctx.req.tag.clone()),
+ CommandBody::Logout => anystate::logout(),
+
+ // Specific to this state (11 commands)
CommandBody::Create { mailbox } => ctx.create(mailbox).await,
CommandBody::Delete { mailbox } => ctx.delete(mailbox).await,
- CommandBody::Rename {
- mailbox,
- new_mailbox,
- } => ctx.rename(mailbox, new_mailbox).await,
+ CommandBody::Rename { from, to } => ctx.rename(from, to).await,
CommandBody::Lsub {
reference,
mailbox_wildcard,
@@ -43,8 +48,8 @@ pub async fn dispatch(ctx: AuthenticatedContext<'_>) -> Result<(Response, flow::
} => ctx.list(reference, mailbox_wildcard, false).await,
CommandBody::Status {
mailbox,
- attributes,
- } => ctx.status(mailbox, attributes).await,
+ item_names,
+ } => ctx.status(mailbox, item_names).await,
CommandBody::Subscribe { mailbox } => ctx.subscribe(mailbox).await,
CommandBody::Unsubscribe { mailbox } => ctx.unsubscribe(mailbox).await,
CommandBody::Select { mailbox } => ctx.select(mailbox).await,
@@ -55,90 +60,148 @@ pub async fn dispatch(ctx: AuthenticatedContext<'_>) -> Result<(Response, flow::
date,
message,
} => ctx.append(mailbox, flags, date, message).await,
- _ => {
- let ctx = anonymous::AnonymousContext {
- req: ctx.req,
- login_provider: None,
- };
- anonymous::dispatch(ctx).await
- }
+
+ // Collect other commands
+ _ => anystate::wrong_state(ctx.req.tag.clone()),
}
}
// --- PRIVATE ---
-
impl<'a> AuthenticatedContext<'a> {
- async fn create(self, mailbox: &MailboxCodec) -> Result<(Response, flow::Transition)> {
- let name = String::try_from(mailbox.clone())?;
-
- if name == INBOX {
- return Ok((
- Response::bad("Cannot create INBOX")?,
- flow::Transition::None,
- ));
- }
+ async fn create(
+ self,
+ mailbox: &MailboxCodec<'a>,
+ ) -> Result<(Response<'static>, flow::Transition)> {
+ let name = match mailbox {
+ MailboxCodec::Inbox => {
+ return Ok((
+ Response::build()
+ .to_req(self.req)
+ .message("Cannot create INBOX")
+ .bad()?,
+ flow::Transition::None,
+ ));
+ }
+ MailboxCodec::Other(aname) => std::str::from_utf8(aname.as_ref())?,
+ };
match self.user.create_mailbox(&name).await {
- Ok(()) => Ok((Response::ok("CREATE complete")?, flow::Transition::None)),
- Err(e) => Ok((Response::no(&e.to_string())?, flow::Transition::None)),
+ Ok(()) => Ok((
+ Response::build()
+ .to_req(self.req)
+ .message("CREATE complete")
+ .ok()?,
+ flow::Transition::None,
+ )),
+ Err(e) => Ok((
+ Response::build()
+ .to_req(self.req)
+ .message(&e.to_string())
+ .no()?,
+ flow::Transition::None,
+ )),
}
}
- async fn delete(self, mailbox: &MailboxCodec) -> Result<(Response, flow::Transition)> {
- let name = String::try_from(mailbox.clone())?;
+ async fn delete(
+ self,
+ mailbox: &MailboxCodec<'a>,
+ ) -> Result<(Response<'static>, flow::Transition)> {
+ let name: &str = MailboxName(mailbox).try_into()?;
match self.user.delete_mailbox(&name).await {
- Ok(()) => Ok((Response::ok("DELETE complete")?, flow::Transition::None)),
- Err(e) => Ok((Response::no(&e.to_string())?, flow::Transition::None)),
+ Ok(()) => Ok((
+ Response::build()
+ .to_req(self.req)
+ .message("DELETE complete")
+ .ok()?,
+ flow::Transition::None,
+ )),
+ Err(e) => Ok((
+ Response::build()
+ .to_req(self.req)
+ .message(e.to_string())
+ .no()?,
+ flow::Transition::None,
+ )),
}
}
async fn rename(
self,
- mailbox: &MailboxCodec,
- new_mailbox: &MailboxCodec,
- ) -> Result<(Response, flow::Transition)> {
- let name = String::try_from(mailbox.clone())?;
- let new_name = String::try_from(new_mailbox.clone())?;
+ from: &MailboxCodec<'a>,
+ to: &MailboxCodec<'a>,
+ ) -> Result<(Response<'static>, flow::Transition)> {
+ let name: &str = MailboxName(from).try_into()?;
+ let new_name: &str = MailboxName(to).try_into()?;
match self.user.rename_mailbox(&name, &new_name).await {
- Ok(()) => Ok((Response::ok("RENAME complete")?, flow::Transition::None)),
- Err(e) => Ok((Response::no(&e.to_string())?, flow::Transition::None)),
+ Ok(()) => Ok((
+ Response::build()
+ .to_req(self.req)
+ .message("RENAME complete")
+ .ok()?,
+ flow::Transition::None,
+ )),
+ Err(e) => Ok((
+ Response::build()
+ .to_req(self.req)
+ .message(e.to_string())
+ .no()?,
+ flow::Transition::None,
+ )),
}
}
async fn list(
self,
- reference: &MailboxCodec,
- mailbox_wildcard: &ListMailbox,
+ reference: &MailboxCodec<'a>,
+ mailbox_wildcard: &ListMailbox<'a>,
is_lsub: bool,
- ) -> Result<(Response, flow::Transition)> {
- let reference = String::try_from(reference.clone())?;
+ ) -> Result<(Response<'static>, flow::Transition)> {
+ let mbx_hier_delim: QuotedChar = QuotedChar::unvalidated(MBX_HIER_DELIM_RAW);
+
+ let reference: &str = MailboxName(reference).try_into()?;
if !reference.is_empty() {
return Ok((
- Response::bad("References not supported")?,
+ Response::build()
+ .to_req(self.req)
+ .message("References not supported")
+ .bad()?,
flow::Transition::None,
));
}
- let wildcard = String::try_from(mailbox_wildcard.clone())?;
+ // @FIXME would probably need a rewrite to better use the imap_codec library
+ let wildcard = match mailbox_wildcard {
+ ListMailbox::Token(v) => std::str::from_utf8(v.as_ref())?,
+ ListMailbox::String(v) => std::str::from_utf8(v.as_ref())?,
+ };
if wildcard.is_empty() {
if is_lsub {
return Ok((
- Response::ok("LSUB complete")?.with_body(vec![Data::Lsub {
- items: vec![],
- delimiter: Some(MAILBOX_HIERARCHY_DELIMITER),
- mailbox: "".try_into().unwrap(),
- }]),
+ Response::build()
+ .to_req(self.req)
+ .message("LSUB complete")
+ .data(Data::Lsub {
+ items: vec![],
+ delimiter: Some(mbx_hier_delim),
+ mailbox: "".try_into().unwrap(),
+ })
+ .ok()?,
flow::Transition::None,
));
} else {
return Ok((
- Response::ok("LIST complete")?.with_body(vec![Data::List {
- items: vec![],
- delimiter: Some(MAILBOX_HIERARCHY_DELIMITER),
- mailbox: "".try_into().unwrap(),
- }]),
+ Response::build()
+ .to_req(self.req)
+ .message("LIST complete")
+ .data(Data::List {
+ items: vec![],
+ delimiter: Some(mbx_hier_delim),
+ mailbox: "".try_into().unwrap(),
+ })
+ .ok()?,
flow::Transition::None,
));
}
@@ -147,7 +210,7 @@ impl<'a> AuthenticatedContext<'a> {
let mailboxes = self.user.list_mailboxes().await?;
let mut vmailboxes = BTreeMap::new();
for mb in mailboxes.iter() {
- for (i, _) in mb.match_indices(MAILBOX_HIERARCHY_DELIMITER) {
+ for (i, _) in mb.match_indices(MBX_HIER_DELIM_RAW) {
if i > 0 {
let smb = &mb[..i];
vmailboxes.entry(smb).or_insert(false);
@@ -163,22 +226,22 @@ impl<'a> AuthenticatedContext<'a> {
.to_string()
.try_into()
.map_err(|_| anyhow!("invalid mailbox name"))?;
- let mut items = vec![FlagNameAttribute::Extension(
- "Subscribed".try_into().unwrap(),
- )];
+ let mut items = vec![FlagNameAttribute::try_from(Atom::unvalidated(
+ "Subscribed",
+ ))?];
if !*is_real {
items.push(FlagNameAttribute::Noselect);
}
if is_lsub {
ret.push(Data::Lsub {
items,
- delimiter: Some(MAILBOX_HIERARCHY_DELIMITER),
+ delimiter: Some(mbx_hier_delim),
mailbox,
});
} else {
ret.push(Data::List {
items,
- delimiter: Some(MAILBOX_HIERARCHY_DELIMITER),
+ delimiter: Some(mbx_hier_delim),
mailbox,
});
}
@@ -190,79 +253,120 @@ impl<'a> AuthenticatedContext<'a> {
} else {
"LIST completed"
};
- Ok((Response::ok(msg)?.with_body(ret), flow::Transition::None))
+ Ok((
+ Response::build()
+ .to_req(self.req)
+ .message(msg)
+ .many_data(ret)
+ .ok()?,
+ flow::Transition::None,
+ ))
}
async fn status(
self,
- mailbox: &MailboxCodec,
- attributes: &[StatusAttribute],
- ) -> Result<(Response, flow::Transition)> {
- let name = String::try_from(mailbox.clone())?;
- let mb_opt = self.user.open_mailbox(&name).await?;
+ mailbox: &MailboxCodec<'static>,
+ attributes: &[StatusDataItemName],
+ ) -> Result<(Response<'static>, flow::Transition)> {
+ let name: &str = MailboxName(mailbox).try_into()?;
+ let mb_opt = self.user.open_mailbox(name).await?;
let mb = match mb_opt {
Some(mb) => mb,
None => {
return Ok((
- Response::no("Mailbox does not exist")?,
+ Response::build()
+ .to_req(self.req)
+ .message("Mailbox does not exist")
+ .no()?,
flow::Transition::None,
))
}
};
- let (view, _data) = MailboxView::new(mb).await?;
+ let view = MailboxView::new(mb).await;
let mut ret_attrs = vec![];
for attr in attributes.iter() {
ret_attrs.push(match attr {
- StatusAttribute::Messages => StatusAttributeValue::Messages(view.exists()?),
- StatusAttribute::Unseen => StatusAttributeValue::Unseen(view.unseen_count() as u32),
- StatusAttribute::Recent => StatusAttributeValue::Recent(view.recent()?),
- StatusAttribute::UidNext => StatusAttributeValue::UidNext(view.uidnext()),
- StatusAttribute::UidValidity => {
- StatusAttributeValue::UidValidity(view.uidvalidity())
+ StatusDataItemName::Messages => StatusDataItem::Messages(view.exists()?),
+ StatusDataItemName::Unseen => StatusDataItem::Unseen(view.unseen_count() as u32),
+ StatusDataItemName::Recent => StatusDataItem::Recent(view.recent()?),
+ StatusDataItemName::UidNext => StatusDataItem::UidNext(view.uidnext()),
+ StatusDataItemName::UidValidity => {
+ StatusDataItem::UidValidity(view.uidvalidity())
}
+ StatusDataItemName::Deleted => {
+ bail!("quota not implemented, can't return deleted elements waiting for EXPUNGE");
+ },
+ StatusDataItemName::DeletedStorage => {
+ bail!("quota not implemented, can't return freed storage after EXPUNGE will be run");
+ },
});
}
- let data = vec![Body::Data(Data::Status {
+ let data = Data::Status {
mailbox: mailbox.clone(),
- attributes: ret_attrs,
- })];
+ items: ret_attrs.into(),
+ };
Ok((
- Response::ok("STATUS completed")?.with_body(data),
+ Response::build()
+ .to_req(self.req)
+ .message("STATUS completed")
+ .data(data)
+ .ok()?,
flow::Transition::None,
))
}
- async fn subscribe(self, mailbox: &MailboxCodec) -> Result<(Response, flow::Transition)> {
- let name = String::try_from(mailbox.clone())?;
+ async fn subscribe(
+ self,
+ mailbox: &MailboxCodec<'a>,
+ ) -> Result<(Response<'static>, flow::Transition)> {
+ let name: &str = MailboxName(mailbox).try_into()?;
if self.user.has_mailbox(&name).await? {
- Ok((Response::ok("SUBSCRIBE complete")?, flow::Transition::None))
+ Ok((
+ Response::build()
+ .to_req(self.req)
+ .message("SUBSCRIBE complete")
+ .ok()?,
+ flow::Transition::None,
+ ))
} else {
Ok((
- Response::bad(&format!("Mailbox {} does not exist", name))?,
+ Response::build()
+ .to_req(self.req)
+ .message(format!("Mailbox {} does not exist", name))
+ .bad()?,
flow::Transition::None,
))
}
}
- async fn unsubscribe(self, mailbox: &MailboxCodec) -> Result<(Response, flow::Transition)> {
- let name = String::try_from(mailbox.clone())?;
+ async fn unsubscribe(
+ self,
+ mailbox: &MailboxCodec<'a>,
+ ) -> Result<(Response<'static>, flow::Transition)> {
+ let name: &str = MailboxName(mailbox).try_into()?;
if self.user.has_mailbox(&name).await? {
Ok((
- Response::bad(&format!(
- "Cannot unsubscribe from mailbox {}: not supported by Aerogramme",
- name
- ))?,
+ Response::build()
+ .to_req(self.req)
+ .message(format!(
+ "Cannot unsubscribe from mailbox {}: not supported by Aerogramme",
+ name
+ ))
+ .bad()?,
flow::Transition::None,
))
} else {
Ok((
- Response::bad(&format!("Mailbox {} does not exist", name))?,
+ Response::build()
+ .to_req(self.req)
+ .message(format!("Mailbox {} does not exist", name))
+ .no()?,
flow::Transition::None,
))
}
@@ -301,83 +405,113 @@ impl<'a> AuthenticatedContext<'a> {
* TRACE END ---
*/
- async fn select(self, mailbox: &MailboxCodec) -> Result<(Response, flow::Transition)> {
- let name = String::try_from(mailbox.clone())?;
+ async fn select(
+ self,
+ mailbox: &MailboxCodec<'a>,
+ ) -> Result<(Response<'static>, flow::Transition)> {
+ let name: &str = MailboxName(mailbox).try_into()?;
let mb_opt = self.user.open_mailbox(&name).await?;
let mb = match mb_opt {
Some(mb) => mb,
None => {
return Ok((
- Response::no("Mailbox does not exist")?,
+ Response::build()
+ .to_req(self.req)
+ .message("Mailbox does not exist")
+ .no()?,
flow::Transition::None,
))
}
};
tracing::info!(username=%self.user.username, mailbox=%name, "mailbox.selected");
- let (mb, data) = MailboxView::new(mb).await?;
+ let mb = MailboxView::new(mb).await;
+ let data = mb.summary()?;
Ok((
- Response::ok("Select completed")?
- .with_extra_code(Code::ReadWrite)
- .with_body(data),
+ Response::build()
+ .message("Select completed")
+ .to_req(self.req)
+ .code(Code::ReadWrite)
+ .set_body(data)
+ .ok()?,
flow::Transition::Select(mb),
))
}
- async fn examine(self, mailbox: &MailboxCodec) -> Result<(Response, flow::Transition)> {
- let name = String::try_from(mailbox.clone())?;
+ async fn examine(
+ self,
+ mailbox: &MailboxCodec<'a>,
+ ) -> Result<(Response<'static>, flow::Transition)> {
+ let name: &str = MailboxName(mailbox).try_into()?;
let mb_opt = self.user.open_mailbox(&name).await?;
let mb = match mb_opt {
Some(mb) => mb,
None => {
return Ok((
- Response::no("Mailbox does not exist")?,
+ Response::build()
+ .to_req(self.req)
+ .message("Mailbox does not exist")
+ .no()?,
flow::Transition::None,
))
}
};
tracing::info!(username=%self.user.username, mailbox=%name, "mailbox.examined");
- let (mb, data) = MailboxView::new(mb).await?;
+ let mb = MailboxView::new(mb).await;
+ let data = mb.summary()?;
Ok((
- Response::ok("Examine completed")?
- .with_extra_code(Code::ReadOnly)
- .with_body(data),
+ Response::build()
+ .to_req(self.req)
+ .message("Examine completed")
+ .code(Code::ReadOnly)
+ .set_body(data)
+ .ok()?,
flow::Transition::Examine(mb),
))
}
async fn append(
self,
- mailbox: &MailboxCodec,
- flags: &[Flag],
- date: &Option<MyDateTime>,
- message: &NonZeroBytes,
- ) -> Result<(Response, flow::Transition)> {
+ mailbox: &MailboxCodec<'a>,
+ flags: &[Flag<'a>],
+ date: &Option<DateTime>,
+ message: &Literal<'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)) => Ok((
- Response::ok("APPEND completed")?.with_extra_code(Code::Other(
- "APPENDUID".try_into().unwrap(),
- Some(format!("{} {}", uidvalidity, uid)),
- )),
+ Response::build()
+ .tag(append_tag)
+ .message("APPEND completed")
+ .code(Code::Other(CodeOther::unvalidated(
+ format!("APPENDUID {} {}", uidvalidity, uid).into_bytes(),
+ )))
+ .ok()?,
+ flow::Transition::None,
+ )),
+ Err(e) => Ok((
+ Response::build()
+ .tag(append_tag)
+ .message(e.to_string())
+ .no()?,
flow::Transition::None,
)),
- Err(e) => Ok((Response::no(&e.to_string())?, flow::Transition::None)),
}
}
pub(crate) async fn append_internal(
self,
- mailbox: &MailboxCodec,
- flags: &[Flag],
- date: &Option<MyDateTime>,
- message: &NonZeroBytes,
+ mailbox: &MailboxCodec<'a>,
+ flags: &[Flag<'a>],
+ date: &Option<DateTime>,
+ message: &Literal<'a>,
) -> Result<(Arc<Mailbox>, ImapUidvalidity, ImapUidvalidity)> {
- let name = String::try_from(mailbox.clone())?;
+ let name: &str = MailboxName(mailbox).try_into()?;
let mb_opt = self.user.open_mailbox(&name).await?;
let mb = match mb_opt {
@@ -389,8 +523,8 @@ impl<'a> AuthenticatedContext<'a> {
bail!("Cannot set date when appending message");
}
- let msg = IMF::try_from(message.as_slice())
- .map_err(|_| anyhow!("Could not parse e-mail message"))?;
+ let msg =
+ IMF::try_from(message.data()).map_err(|_| anyhow!("Could not parse e-mail message"))?;
let flags = flags.iter().map(|x| x.to_string()).collect::<Vec<_>>();
// TODO: filter allowed flags? ping @Quentin
@@ -422,7 +556,7 @@ fn matches_wildcard(wildcard: &str, name: &str) -> bool {
&& j > 0
&& matches[i - 1][j]
&& (wildcard[j - 1] == '*'
- || (wildcard[j - 1] == '%' && name[i - 1] != MAILBOX_HIERARCHY_DELIMITER)));
+ || (wildcard[j - 1] == '%' && name[i - 1] != MBX_HIER_DELIM_RAW)));
}
}
diff --git a/src/imap/command/examined.rs b/src/imap/command/examined.rs
index 1740b39..7de94f4 100644
--- a/src/imap/command/examined.rs
+++ b/src/imap/command/examined.rs
@@ -1,56 +1,60 @@
use std::sync::Arc;
use anyhow::Result;
-use boitalettres::proto::Request;
-use boitalettres::proto::Response;
-use imap_codec::types::command::{CommandBody, SearchKey};
-use imap_codec::types::core::{Charset, NonZeroBytes};
-use imap_codec::types::datetime::MyDateTime;
-use imap_codec::types::fetch_attributes::MacroOrFetchAttributes;
-use imap_codec::types::flag::Flag;
-use imap_codec::types::mailbox::Mailbox as MailboxCodec;
-use imap_codec::types::response::Code;
-use imap_codec::types::sequence::SequenceSet;
+use imap_codec::imap_types::command::{Command, CommandBody};
+use imap_codec::imap_types::core::Charset;
+use imap_codec::imap_types::fetch::MacroOrMessageDataItemNames;
+use imap_codec::imap_types::search::SearchKey;
+use imap_codec::imap_types::sequence::SequenceSet;
-use crate::imap::command::authenticated;
+use crate::imap::command::{anystate, authenticated};
use crate::imap::flow;
use crate::imap::mailbox_view::MailboxView;
+use crate::imap::response::Response;
use crate::mail::user::User;
pub struct ExaminedContext<'a> {
- pub req: &'a Request,
+ pub req: &'a Command<'static>,
pub user: &'a Arc<User>,
pub mailbox: &'a mut MailboxView,
}
-pub async fn dispatch(ctx: ExaminedContext<'_>) -> Result<(Response, flow::Transition)> {
- match &ctx.req.command.body {
- // CLOSE in examined state is not the same as in selected state
- // (in selected state it also does an EXPUNGE, here it doesn't)
+pub async fn dispatch(ctx: ExaminedContext<'_>) -> Result<(Response<'static>, flow::Transition)> {
+ match &ctx.req.body {
+ // Any State
+ // noop is specific to this state
+ CommandBody::Capability => anystate::capability(ctx.req.tag.clone()),
+ CommandBody::Logout => anystate::logout(),
+
+ // Specific to the EXAMINE state (specialization of the SELECTED state)
+ // ~3 commands -> close, fetch, search + NOOP
CommandBody::Close => ctx.close().await,
CommandBody::Fetch {
sequence_set,
- attributes,
+ macro_or_item_names,
uid,
- } => ctx.fetch(sequence_set, attributes, uid).await,
+ } => ctx.fetch(sequence_set, macro_or_item_names, uid).await,
CommandBody::Search {
charset,
criteria,
uid,
} => ctx.search(charset, criteria, uid).await,
- CommandBody::Noop => ctx.noop().await,
- CommandBody::Append {
- mailbox,
- flags,
- date,
- message,
- } => ctx.append(mailbox, flags, date, message).await,
+ CommandBody::Noop | CommandBody::Check => ctx.noop().await,
+ CommandBody::Expunge { .. } | CommandBody::Store { .. } => Ok((
+ Response::build()
+ .to_req(ctx.req)
+ .message("Forbidden command: can't write in read-only mode (EXAMINE)")
+ .bad()?,
+ flow::Transition::None,
+ )),
+
+ // In examined mode, we fallback to authenticated when needed
_ => {
- let ctx = authenticated::AuthenticatedContext {
+ authenticated::dispatch(authenticated::AuthenticatedContext {
req: ctx.req,
user: ctx.user,
- };
- authenticated::dispatch(ctx).await
+ })
+ .await
}
}
}
@@ -58,71 +62,69 @@ pub async fn dispatch(ctx: ExaminedContext<'_>) -> Result<(Response, flow::Trans
// --- PRIVATE ---
impl<'a> ExaminedContext<'a> {
- async fn close(self) -> Result<(Response, flow::Transition)> {
- Ok((Response::ok("CLOSE completed")?, flow::Transition::Unselect))
+ /// CLOSE in examined state is not the same as in selected state
+ /// (in selected state it also does an EXPUNGE, here it doesn't)
+ async fn close(self) -> Result<(Response<'static>, flow::Transition)> {
+ Ok((
+ Response::build()
+ .to_req(self.req)
+ .message("CLOSE completed")
+ .ok()?,
+ flow::Transition::Unselect,
+ ))
}
pub async fn fetch(
self,
sequence_set: &SequenceSet,
- attributes: &MacroOrFetchAttributes,
+ attributes: &'a MacroOrMessageDataItemNames<'static>,
uid: &bool,
- ) -> Result<(Response, flow::Transition)> {
+ ) -> Result<(Response<'static>, flow::Transition)> {
match self.mailbox.fetch(sequence_set, attributes, uid).await {
Ok(resp) => Ok((
- Response::ok("FETCH completed")?.with_body(resp),
+ Response::build()
+ .to_req(self.req)
+ .message("FETCH completed")
+ .set_body(resp)
+ .ok()?,
+ flow::Transition::None,
+ )),
+ Err(e) => Ok((
+ Response::build()
+ .to_req(self.req)
+ .message(e.to_string())
+ .no()?,
flow::Transition::None,
)),
- Err(e) => Ok((Response::no(&e.to_string())?, flow::Transition::None)),
}
}
pub async fn search(
self,
- _charset: &Option<Charset>,
- _criteria: &SearchKey,
+ _charset: &Option<Charset<'a>>,
+ _criteria: &SearchKey<'a>,
_uid: &bool,
- ) -> Result<(Response, flow::Transition)> {
- Ok((Response::bad("Not implemented")?, flow::Transition::None))
+ ) -> Result<(Response<'static>, flow::Transition)> {
+ Ok((
+ Response::build()
+ .to_req(self.req)
+ .message("Not implemented")
+ .bad()?,
+ flow::Transition::None,
+ ))
}
- pub async fn noop(self) -> Result<(Response, flow::Transition)> {
+ pub async fn noop(self) -> Result<(Response<'static>, flow::Transition)> {
self.mailbox.mailbox.force_sync().await?;
let updates = self.mailbox.update().await?;
Ok((
- Response::ok("NOOP completed.")?.with_body(updates),
+ Response::build()
+ .to_req(self.req)
+ .message("NOOP completed.")
+ .set_body(updates)
+ .ok()?,
flow::Transition::None,
))
}
-
- async fn append(
- self,
- mailbox: &MailboxCodec,
- flags: &[Flag],
- date: &Option<MyDateTime>,
- message: &NonZeroBytes,
- ) -> Result<(Response, flow::Transition)> {
- let ctx2 = authenticated::AuthenticatedContext {
- req: self.req,
- user: self.user,
- };
-
- match ctx2.append_internal(mailbox, flags, date, message).await {
- Ok((mb, uidvalidity, uid)) => {
- let resp = Response::ok("APPEND completed")?.with_extra_code(Code::Other(
- "APPENDUID".try_into().unwrap(),
- Some(format!("{} {}", uidvalidity, uid)),
- ));
-
- if Arc::ptr_eq(&mb, &self.mailbox.mailbox) {
- let data = self.mailbox.update().await?;
- Ok((resp.with_body(data), flow::Transition::None))
- } else {
- Ok((resp, flow::Transition::None))
- }
- }
- Err(e) => Ok((Response::no(&e.to_string())?, flow::Transition::None)),
- }
- }
}
diff --git a/src/imap/command/mod.rs b/src/imap/command/mod.rs
index 0b7e576..dc95746 100644
--- a/src/imap/command/mod.rs
+++ b/src/imap/command/mod.rs
@@ -1,4 +1,21 @@
pub mod anonymous;
+pub mod anystate;
pub mod authenticated;
pub mod examined;
pub mod selected;
+
+use crate::mail::user::INBOX;
+use imap_codec::imap_types::mailbox::Mailbox as MailboxCodec;
+
+/// Convert an IMAP mailbox name/identifier representation
+/// to an utf-8 string that is used internally in Aerogramme
+struct MailboxName<'a>(&'a MailboxCodec<'a>);
+impl<'a> TryInto<&'a str> for MailboxName<'a> {
+ type Error = std::str::Utf8Error;
+ fn try_into(self) -> Result<&'a str, Self::Error> {
+ match self.0 {
+ MailboxCodec::Inbox => Ok(INBOX),
+ MailboxCodec::Other(aname) => Ok(std::str::from_utf8(aname.as_ref())?),
+ }
+ }
+}
diff --git a/src/imap/command/selected.rs b/src/imap/command/selected.rs
index 90a00ee..220a952 100644
--- a/src/imap/command/selected.rs
+++ b/src/imap/command/selected.rs
@@ -1,31 +1,50 @@
use std::sync::Arc;
use anyhow::Result;
-use boitalettres::proto::Request;
-use boitalettres::proto::Response;
-use imap_codec::types::command::CommandBody;
-use imap_codec::types::flag::{Flag, StoreResponse, StoreType};
-use imap_codec::types::mailbox::Mailbox as MailboxCodec;
-use imap_codec::types::response::Code;
-use imap_codec::types::sequence::SequenceSet;
-
-use crate::imap::command::examined;
+use imap_codec::imap_types::command::{Command, CommandBody};
+use imap_codec::imap_types::core::Charset;
+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;
+use imap_codec::imap_types::response::{Code, CodeOther};
+use imap_codec::imap_types::search::SearchKey;
+use imap_codec::imap_types::sequence::SequenceSet;
+
+use crate::imap::command::{anystate, authenticated, MailboxName};
use crate::imap::flow;
use crate::imap::mailbox_view::MailboxView;
+use crate::imap::response::Response;
use crate::mail::user::User;
pub struct SelectedContext<'a> {
- pub req: &'a Request,
+ pub req: &'a Command<'static>,
pub user: &'a Arc<User>,
pub mailbox: &'a mut MailboxView,
}
-pub async fn dispatch(ctx: SelectedContext<'_>) -> Result<(Response, flow::Transition)> {
- match &ctx.req.command.body {
- // Only write commands here, read commands are handled in
- // `examined.rs`
+pub async fn dispatch<'a>(
+ ctx: SelectedContext<'a>,
+) -> Result<(Response<'static>, flow::Transition)> {
+ match &ctx.req.body {
+ // Any State
+ // noop is specific to this state
+ CommandBody::Capability => anystate::capability(ctx.req.tag.clone()),
+ CommandBody::Logout => anystate::logout(),
+
+ // Specific to this state (7 commands + NOOP)
CommandBody::Close => ctx.close().await,
+ CommandBody::Noop | CommandBody::Check => ctx.noop().await,
+ CommandBody::Fetch {
+ sequence_set,
+ macro_or_item_names,
+ uid,
+ } => ctx.fetch(sequence_set, macro_or_item_names, uid).await,
+ CommandBody::Search {
+ charset,
+ criteria,
+ uid,
+ } => ctx.search(charset, criteria, uid).await,
CommandBody::Expunge => ctx.expunge().await,
CommandBody::Store {
sequence_set,
@@ -39,13 +58,14 @@ pub async fn dispatch(ctx: SelectedContext<'_>) -> Result<(Response, flow::Trans
mailbox,
uid,
} => ctx.copy(sequence_set, mailbox, uid).await,
+
+ // In selected mode, we fallback to authenticated when needed
_ => {
- let ctx = examined::ExaminedContext {
+ authenticated::dispatch(authenticated::AuthenticatedContext {
req: ctx.req,
user: ctx.user,
- mailbox: ctx.mailbox,
- };
- examined::dispatch(ctx).await
+ })
+ .await
}
}
}
@@ -53,18 +73,81 @@ pub async fn dispatch(ctx: SelectedContext<'_>) -> Result<(Response, flow::Trans
// --- PRIVATE ---
impl<'a> SelectedContext<'a> {
- async fn close(self) -> Result<(Response, flow::Transition)> {
+ async fn close(self) -> Result<(Response<'static>, flow::Transition)> {
// We expunge messages,
// but we don't send the untagged EXPUNGE responses
+ let tag = self.req.tag.clone();
self.expunge().await?;
- Ok((Response::ok("CLOSE completed")?, flow::Transition::Unselect))
+ Ok((
+ Response::build().tag(tag).message("CLOSE completed").ok()?,
+ flow::Transition::Unselect,
+ ))
+ }
+
+ pub async fn fetch(
+ self,
+ sequence_set: &SequenceSet,
+ attributes: &'a MacroOrMessageDataItemNames<'static>,
+ uid: &bool,
+ ) -> Result<(Response<'static>, flow::Transition)> {
+ match self.mailbox.fetch(sequence_set, attributes, uid).await {
+ Ok(resp) => Ok((
+ Response::build()
+ .to_req(self.req)
+ .message("FETCH completed")
+ .set_body(resp)
+ .ok()?,
+ flow::Transition::None,
+ )),
+ Err(e) => Ok((
+ Response::build()
+ .to_req(self.req)
+ .message(e.to_string())
+ .no()?,
+ flow::Transition::None,
+ )),
+ }
+ }
+
+ pub async fn search(
+ self,
+ _charset: &Option<Charset<'a>>,
+ _criteria: &SearchKey<'a>,
+ _uid: &bool,
+ ) -> Result<(Response<'static>, flow::Transition)> {
+ Ok((
+ Response::build()
+ .to_req(self.req)
+ .message("Not implemented")
+ .bad()?,
+ flow::Transition::None,
+ ))
}
- async fn expunge(self) -> Result<(Response, flow::Transition)> {
+ pub async fn noop(self) -> Result<(Response<'static>, flow::Transition)> {
+ self.mailbox.mailbox.force_sync().await?;
+
+ let updates = self.mailbox.update().await?;
+ Ok((
+ Response::build()
+ .to_req(self.req)
+ .message("NOOP completed.")
+ .set_body(updates)
+ .ok()?,
+ flow::Transition::None,
+ ))
+ }
+
+ async fn expunge(self) -> Result<(Response<'static>, flow::Transition)> {
+ let tag = self.req.tag.clone();
let data = self.mailbox.expunge().await?;
Ok((
- Response::ok("EXPUNGE completed")?.with_body(data),
+ Response::build()
+ .tag(tag)
+ .message("EXPUNGE completed")
+ .set_body(data)
+ .ok()?,
flow::Transition::None,
))
}
@@ -74,16 +157,20 @@ impl<'a> SelectedContext<'a> {
sequence_set: &SequenceSet,
kind: &StoreType,
response: &StoreResponse,
- flags: &[Flag],
+ flags: &[Flag<'a>],
uid: &bool,
- ) -> Result<(Response, flow::Transition)> {
+ ) -> Result<(Response<'static>, flow::Transition)> {
let data = self
.mailbox
.store(sequence_set, kind, response, flags, uid)
.await?;
Ok((
- Response::ok("STORE completed")?.with_body(data),
+ Response::build()
+ .to_req(self.req)
+ .message("STORE completed")
+ .set_body(data)
+ .ok()?,
flow::Transition::None,
))
}
@@ -91,18 +178,21 @@ impl<'a> SelectedContext<'a> {
async fn copy(
self,
sequence_set: &SequenceSet,
- mailbox: &MailboxCodec,
+ mailbox: &MailboxCodec<'a>,
uid: &bool,
- ) -> Result<(Response, flow::Transition)> {
- let name = String::try_from(mailbox.clone())?;
+ ) -> Result<(Response<'static>, flow::Transition)> {
+ let name: &str = MailboxName(mailbox).try_into()?;
let mb_opt = self.user.open_mailbox(&name).await?;
let mb = match mb_opt {
Some(mb) => mb,
None => {
return Ok((
- Response::no("Destination mailbox does not exist")?
- .with_extra_code(Code::TryCreate),
+ Response::build()
+ .to_req(self.req)
+ .message("Destination mailbox does not exist")
+ .code(Code::TryCreate)
+ .no()?,
flow::Transition::None,
))
}
@@ -126,10 +216,13 @@ impl<'a> SelectedContext<'a> {
);
Ok((
- Response::ok("COPY completed")?.with_extra_code(Code::Other(
- "COPYUID".try_into().unwrap(),
- Some(copyuid_str),
- )),
+ Response::build()
+ .to_req(self.req)
+ .message("COPY completed")
+ .code(Code::Other(CodeOther::unvalidated(
+ format!("COPYUID {}", copyuid_str).into_bytes(),
+ )))
+ .ok()?,
flow::Transition::None,
))
}
diff --git a/src/imap/flow.rs b/src/imap/flow.rs
index eb94bb5..95810c1 100644
--- a/src/imap/flow.rs
+++ b/src/imap/flow.rs
@@ -37,23 +37,27 @@ pub enum Transition {
// See RFC3501 section 3.
// https://datatracker.ietf.org/doc/html/rfc3501#page-13
impl State {
- pub fn apply(self, tr: Transition) -> Result<Self, Error> {
- match (self, tr) {
- (s, Transition::None) => Ok(s),
- (State::NotAuthenticated, Transition::Authenticate(u)) => Ok(State::Authenticated(u)),
+ pub fn apply(&mut self, tr: Transition) -> Result<(), Error> {
+ let new_state = match (&self, tr) {
+ (_s, Transition::None) => return Ok(()),
+ (State::NotAuthenticated, Transition::Authenticate(u)) => State::Authenticated(u),
(
State::Authenticated(u) | State::Selected(u, _) | State::Examined(u, _),
Transition::Select(m),
- ) => Ok(State::Selected(u, m)),
+ ) => State::Selected(u.clone(), m),
(
State::Authenticated(u) | State::Selected(u, _) | State::Examined(u, _),
Transition::Examine(m),
- ) => Ok(State::Examined(u, m)),
+ ) => State::Examined(u.clone(), m),
(State::Selected(u, _) | State::Examined(u, _), Transition::Unselect) => {
- Ok(State::Authenticated(u))
+ State::Authenticated(u.clone())
}
- (_, Transition::Logout) => Ok(State::Logout),
- _ => Err(Error::ForbiddenTransition),
- }
+ (_, Transition::Logout) => State::Logout,
+ _ => return Err(Error::ForbiddenTransition),
+ };
+
+ *self = new_state;
+
+ Ok(())
}
}
diff --git a/src/imap/mailbox_view.rs b/src/imap/mailbox_view.rs
index 99069e2..7434512 100644
--- a/src/imap/mailbox_view.rs
+++ b/src/imap/mailbox_view.rs
@@ -4,22 +4,20 @@ use std::num::NonZeroU32;
use std::sync::Arc;
use anyhow::{anyhow, bail, Error, Result};
-use boitalettres::proto::res::body::Data as Body;
use chrono::{Offset, TimeZone, Utc};
use futures::stream::{FuturesOrdered, StreamExt};
-use imap_codec::types::address::Address;
-use imap_codec::types::body::{BasicFields, Body as FetchBody, BodyStructure, SpecificFields};
-use imap_codec::types::core::{AString, Atom, IString, NString};
-use imap_codec::types::datetime::MyDateTime;
-use imap_codec::types::envelope::Envelope;
-use imap_codec::types::fetch_attributes::{
- FetchAttribute, MacroOrFetchAttributes, Section as FetchSection,
+use imap_codec::imap_types::body::{BasicFields, Body as FetchBody, BodyStructure, SpecificFields};
+use imap_codec::imap_types::core::{AString, Atom, IString, NString, NonEmptyVec};
+use imap_codec::imap_types::datetime::DateTime;
+use imap_codec::imap_types::envelope::{Address, Envelope};
+use imap_codec::imap_types::fetch::{
+ MacroOrMessageDataItemNames, MessageDataItem, MessageDataItemName, Section as FetchSection,
};
-use imap_codec::types::flag::{Flag, StoreResponse, StoreType};
-use imap_codec::types::response::{Code, Data, MessageAttribute, Status};
-use imap_codec::types::sequence::{self, SequenceSet};
+use imap_codec::imap_types::flag::{Flag, FlagFetch, FlagPerm, StoreResponse, StoreType};
+use imap_codec::imap_types::response::{Code, Data, Status};
+use imap_codec::imap_types::sequence::{self, SequenceSet};
use eml_codec::{
header, imf, mime,
@@ -28,6 +26,7 @@ use eml_codec::{
};
use crate::cryptoblob::Key;
+use crate::imap::response::Body;
use crate::mail::mailbox::{MailMeta, Mailbox};
use crate::mail::uidindex::{ImapUid, ImapUidvalidity, UidIndex};
use crate::mail::unique_ident::UniqueIdent;
@@ -77,19 +76,31 @@ impl<'a> FetchedMail<'a> {
}
pub struct AttributesProxy {
- attrs: Vec<FetchAttribute>,
+ attrs: Vec<MessageDataItemName<'static>>,
}
impl AttributesProxy {
- fn new(attrs: &MacroOrFetchAttributes, is_uid_fetch: bool) -> Self {
+ fn new(attrs: &MacroOrMessageDataItemNames<'static>, is_uid_fetch: bool) -> Self {
// Expand macros
let mut fetch_attrs = match attrs {
- MacroOrFetchAttributes::Macro(m) => m.expand(),
- MacroOrFetchAttributes::FetchAttributes(a) => a.clone(),
+ MacroOrMessageDataItemNames::Macro(m) => {
+ use imap_codec::imap_types::fetch::Macro;
+ use MessageDataItemName::*;
+ match m {
+ Macro::All => vec![Flags, InternalDate, Rfc822Size, Envelope],
+ Macro::Fast => vec![Flags, InternalDate, Rfc822Size],
+ Macro::Full => vec![Flags, InternalDate, Rfc822Size, Envelope, Body],
+ _ => {
+ tracing::error!("unimplemented macro");
+ vec![]
+ }
+ }
+ }
+ MacroOrMessageDataItemNames::MessageDataItemNames(a) => a.clone(),
};
// Handle uids
- if is_uid_fetch && !fetch_attrs.contains(&FetchAttribute::Uid) {
- fetch_attrs.push(FetchAttribute::Uid);
+ if is_uid_fetch && !fetch_attrs.contains(&MessageDataItemName::Uid) {
+ fetch_attrs.push(MessageDataItemName::Uid);
}
Self { attrs: fetch_attrs }
@@ -99,11 +110,11 @@ impl AttributesProxy {
self.attrs.iter().any(|x| {
matches!(
x,
- FetchAttribute::Body
- | FetchAttribute::BodyExt { .. }
- | FetchAttribute::Rfc822
- | FetchAttribute::Rfc822Text
- | FetchAttribute::BodyStructure
+ MessageDataItemName::Body
+ | MessageDataItemName::BodyExt { .. }
+ | MessageDataItemName::Rfc822
+ | MessageDataItemName::Rfc822Text
+ | MessageDataItemName::BodyStructure
)
})
}
@@ -127,16 +138,20 @@ pub struct MailView<'a> {
meta: &'a MailMeta,
flags: &'a Vec<String>,
content: FetchedMail<'a>,
- add_seen: bool,
+}
+
+enum SeenFlag {
+ DoNothing,
+ MustAdd,
}
impl<'a> MailView<'a> {
- fn uid(&self) -> MessageAttribute {
- MessageAttribute::Uid(self.ids.uid)
+ fn uid(&self) -> MessageDataItem<'static> {
+ MessageDataItem::Uid(self.ids.uid.clone())
}
- fn flags(&self) -> MessageAttribute {
- MessageAttribute::Flags(
+ fn flags(&self) -> MessageDataItem<'static> {
+ MessageDataItem::Flags(
self.flags
.iter()
.filter_map(|f| string_to_flag(f))
@@ -144,12 +159,12 @@ impl<'a> MailView<'a> {
)
}
- fn rfc_822_size(&self) -> MessageAttribute {
- MessageAttribute::Rfc822Size(self.meta.rfc822_size as u32)
+ fn rfc_822_size(&self) -> MessageDataItem<'static> {
+ MessageDataItem::Rfc822Size(self.meta.rfc822_size as u32)
}
- fn rfc_822_header(&self) -> MessageAttribute {
- MessageAttribute::Rfc822Header(NString(
+ fn rfc_822_header(&self) -> MessageDataItem<'static> {
+ MessageDataItem::Rfc822Header(NString(
self.meta
.headers
.to_vec()
@@ -159,41 +174,42 @@ impl<'a> MailView<'a> {
))
}
- fn rfc_822_text(&self) -> Result<MessageAttribute> {
- Ok(MessageAttribute::Rfc822Text(NString(
+ fn rfc_822_text(&self) -> Result<MessageDataItem<'static>> {
+ Ok(MessageDataItem::Rfc822Text(NString(
self.content
.as_full()?
.raw_body
+ .to_vec()
.try_into()
.ok()
.map(IString::Literal),
)))
}
- fn rfc822(&self) -> Result<MessageAttribute> {
- Ok(MessageAttribute::Rfc822(NString(
+ fn rfc822(&self) -> Result<MessageDataItem<'static>> {
+ Ok(MessageDataItem::Rfc822(NString(
self.content
.as_full()?
- .raw_body
- .clone()
+ .raw_part
+ .to_vec()
.try_into()
.ok()
.map(IString::Literal),
)))
}
- fn envelope(&self) -> MessageAttribute {
- MessageAttribute::Envelope(message_envelope(self.content.imf()))
+ fn envelope(&self) -> MessageDataItem<'static> {
+ MessageDataItem::Envelope(message_envelope(self.content.imf().clone()))
}
- fn body(&self) -> Result<MessageAttribute> {
- Ok(MessageAttribute::Body(build_imap_email_struct(
+ fn body(&self) -> Result<MessageDataItem<'static>> {
+ Ok(MessageDataItem::Body(build_imap_email_struct(
self.content.as_full()?.child.as_ref(),
)?))
}
- fn body_structure(&self) -> Result<MessageAttribute> {
- Ok(MessageAttribute::Body(build_imap_email_struct(
+ fn body_structure(&self) -> Result<MessageDataItem<'static>> {
+ Ok(MessageDataItem::Body(build_imap_email_struct(
self.content.as_full()?.child.as_ref(),
)?))
}
@@ -202,12 +218,14 @@ impl<'a> MailView<'a> {
/// peek does not implicitly set the \Seen flag
/// eg. BODY[HEADER.FIELDS (DATE FROM)]
/// eg. BODY[]<0.2048>
- fn body_ext(
- &mut self,
- section: &Option<FetchSection>,
+ fn body_ext<'b>(
+ &self,
+ section: &Option<FetchSection<'b>>,
partial: &Option<(u32, NonZeroU32)>,
peek: &bool,
- ) -> Result<MessageAttribute> {
+ ) -> Result<(MessageDataItem<'b>, SeenFlag)> {
+ let mut seen = SeenFlag::DoNothing;
+
// Extract message section
let text = get_message_section(self.content.as_anypart()?, section)?;
@@ -215,7 +233,7 @@ impl<'a> MailView<'a> {
if !peek && !self.flags.iter().any(|x| *x == seen_flag) {
// Add \Seen flag
//self.mailbox.add_flags(uuid, &[seen_flag]).await?;
- self.add_seen = true;
+ seen = SeenFlag::MustAdd;
}
// Handle <<partial>> which cut the message bytes
@@ -223,49 +241,60 @@ impl<'a> MailView<'a> {
let data = NString(text.to_vec().try_into().ok().map(IString::Literal));
- return Ok(MessageAttribute::BodyExt {
- section: section.clone(),
- origin,
- data,
- });
+ return Ok((
+ MessageDataItem::BodyExt {
+ section: section.as_ref().map(|fs| fs.clone()),
+ origin,
+ data,
+ },
+ seen,
+ ));
}
- fn internal_date(&self) -> Result<MessageAttribute> {
+ fn internal_date(&self) -> Result<MessageDataItem<'static>> {
let dt = Utc
.fix()
.timestamp_opt(i64::try_from(self.meta.internaldate / 1000)?, 0)
.earliest()
.ok_or(anyhow!("Unable to parse internal date"))?;
- Ok(MessageAttribute::InternalDate(MyDateTime(dt)))
+ Ok(MessageDataItem::InternalDate(DateTime::unvalidated(dt)))
}
- fn filter(&mut self, ap: &AttributesProxy) -> Result<Body> {
+ fn filter<'b>(&self, ap: &AttributesProxy) -> Result<(Body<'static>, SeenFlag)> {
+ let mut seen = SeenFlag::DoNothing;
let res_attrs = ap
.attrs
.iter()
.map(|attr| match attr {
- FetchAttribute::Uid => Ok(self.uid()),
- FetchAttribute::Flags => Ok(self.flags()),
- FetchAttribute::Rfc822Size => Ok(self.rfc_822_size()),
- FetchAttribute::Rfc822Header => Ok(self.rfc_822_header()),
- FetchAttribute::Rfc822Text => self.rfc_822_text(),
- FetchAttribute::Rfc822 => self.rfc822(),
- FetchAttribute::Envelope => Ok(self.envelope()),
- FetchAttribute::Body => self.body(),
- FetchAttribute::BodyStructure => self.body_structure(),
- FetchAttribute::BodyExt {
+ MessageDataItemName::Uid => Ok(self.uid()),
+ MessageDataItemName::Flags => Ok(self.flags()),
+ MessageDataItemName::Rfc822Size => Ok(self.rfc_822_size()),
+ MessageDataItemName::Rfc822Header => Ok(self.rfc_822_header()),
+ MessageDataItemName::Rfc822Text => self.rfc_822_text(),
+ MessageDataItemName::Rfc822 => self.rfc822(),
+ MessageDataItemName::Envelope => Ok(self.envelope()),
+ MessageDataItemName::Body => self.body(),
+ MessageDataItemName::BodyStructure => self.body_structure(),
+ MessageDataItemName::BodyExt {
section,
partial,
peek,
- } => self.body_ext(section, partial, peek),
- FetchAttribute::InternalDate => self.internal_date(),
+ } => {
+ let (body, has_seen) = self.body_ext(section, partial, peek)?;
+ seen = has_seen;
+ Ok(body)
+ }
+ MessageDataItemName::InternalDate => self.internal_date(),
})
.collect::<Result<Vec<_>, _>>()?;
- Ok(Body::Data(Data::Fetch {
- seq_or_uid: self.ids.i,
- attributes: res_attrs,
- }))
+ Ok((
+ Body::Data(Data::Fetch {
+ seq: self.ids.i,
+ items: res_attrs.try_into()?,
+ }),
+ seen,
+ ))
}
}
@@ -376,7 +405,6 @@ impl<'a> MailSelectionBuilder<'a> {
meta,
flags,
content,
- add_seen: false,
})
.collect())
}
@@ -396,35 +424,26 @@ pub struct MailboxView {
impl MailboxView {
/// Creates a new IMAP view into a mailbox.
- /// Generates the necessary IMAP messages so that the client
- /// has a satisfactory summary of the current mailbox's state.
- /// These are the messages that are sent in response to a SELECT command.
- pub async fn new(mailbox: Arc<Mailbox>) -> Result<(Self, Vec<Body>)> {
+ pub async fn new(mailbox: Arc<Mailbox>) -> Self {
let state = mailbox.current_uid_index().await;
- let new_view = Self {
+ Self {
mailbox,
known_state: state,
- };
-
- let mut data = Vec::<Body>::new();
- data.push(new_view.exists_status()?);
- data.push(new_view.recent_status()?);
- data.extend(new_view.flags_status()?.into_iter());
- data.push(new_view.uidvalidity_status()?);
- data.push(new_view.uidnext_status()?);
-
- Ok((new_view, data))
+ }
}
+ /// Create an updated view, useful to make a diff
+ /// between what the client knows and new stuff
/// Produces a set of IMAP responses describing the change between
/// what the client knows and what is actually in the mailbox.
/// This does NOT trigger a sync, it bases itself on what is currently
/// loaded in RAM by Bayou.
- pub async fn update(&mut self) -> Result<Vec<Body>> {
- let new_view = MailboxView {
- mailbox: self.mailbox.clone(),
- known_state: self.mailbox.current_uid_index().await,
+ pub async fn update(&mut self) -> Result<Vec<Body<'static>>> {
+ let old_view: &mut Self = self;
+ let new_view = Self {
+ mailbox: old_view.mailbox.clone(),
+ known_state: old_view.mailbox.current_uid_index().await,
};
let mut data = Vec::<Body>::new();
@@ -446,7 +465,7 @@ impl MailboxView {
// - notify client of expunged mails
let mut n_expunge = 0;
- for (i, (_uid, uuid)) in self.known_state.idx_by_uid.iter().enumerate() {
+ for (i, (_uid, uuid)) in old_view.known_state.idx_by_uid.iter().enumerate() {
if !new_view.known_state.table.contains_key(uuid) {
data.push(Body::Data(Data::Expunge(
NonZeroU32::try_from((i + 1 - n_expunge) as u32).unwrap(),
@@ -456,49 +475,63 @@ impl MailboxView {
}
// - if new mails arrived, notify client of number of existing mails
- if new_view.known_state.table.len() != self.known_state.table.len() - n_expunge
- || new_view.known_state.uidvalidity != self.known_state.uidvalidity
+ if new_view.known_state.table.len() != old_view.known_state.table.len() - n_expunge
+ || new_view.known_state.uidvalidity != old_view.known_state.uidvalidity
{
data.push(new_view.exists_status()?);
}
- if new_view.known_state.uidvalidity != self.known_state.uidvalidity {
+ if new_view.known_state.uidvalidity != old_view.known_state.uidvalidity {
// TODO: do we want to push less/more info than this?
data.push(new_view.uidvalidity_status()?);
data.push(new_view.uidnext_status()?);
} else {
// - if flags changed for existing mails, tell client
for (i, (_uid, uuid)) in new_view.known_state.idx_by_uid.iter().enumerate() {
- let old_mail = self.known_state.table.get(uuid);
+ let old_mail = old_view.known_state.table.get(uuid);
let new_mail = new_view.known_state.table.get(uuid);
if old_mail.is_some() && old_mail != new_mail {
if let Some((uid, flags)) = new_mail {
data.push(Body::Data(Data::Fetch {
- seq_or_uid: NonZeroU32::try_from((i + 1) as u32).unwrap(),
- attributes: vec![
- MessageAttribute::Uid(*uid),
- MessageAttribute::Flags(
+ seq: NonZeroU32::try_from((i + 1) as u32).unwrap(),
+ items: vec![
+ MessageDataItem::Uid(*uid),
+ MessageDataItem::Flags(
flags.iter().filter_map(|f| string_to_flag(f)).collect(),
),
- ],
+ ]
+ .try_into()?,
}));
}
}
}
}
+ *old_view = new_view;
+ Ok(data)
+ }
+
+ /// Generates the necessary IMAP messages so that the client
+ /// has a satisfactory summary of the current mailbox's state.
+ /// These are the messages that are sent in response to a SELECT command.
+ pub fn summary(&self) -> Result<Vec<Body<'static>>> {
+ let mut data = Vec::<Body>::new();
+ data.push(self.exists_status()?);
+ data.push(self.recent_status()?);
+ data.extend(self.flags_status()?.into_iter());
+ data.push(self.uidvalidity_status()?);
+ data.push(self.uidnext_status()?);
- *self = new_view;
Ok(data)
}
- pub async fn store(
+ pub async fn store<'a>(
&mut self,
sequence_set: &SequenceSet,
kind: &StoreType,
_response: &StoreResponse,
- flags: &[Flag],
+ flags: &[Flag<'a>],
is_uid_store: &bool,
- ) -> Result<Vec<Body>> {
+ ) -> Result<Vec<Body<'static>>> {
self.mailbox.opportunistic_sync().await?;
let flags = flags.iter().map(|x| x.to_string()).collect::<Vec<_>>();
@@ -522,7 +555,7 @@ impl MailboxView {
self.update().await
}
- pub async fn expunge(&mut self) -> Result<Vec<Body>> {
+ pub async fn expunge(&mut self) -> Result<Vec<Body<'static>>> {
self.mailbox.opportunistic_sync().await?;
let deleted_flag = Flag::Deleted.to_string();
@@ -569,12 +602,12 @@ impl MailboxView {
/// Looks up state changes in the mailbox and produces a set of IMAP
/// responses describing the new state.
- pub async fn fetch(
+ pub async fn fetch<'b>(
&self,
sequence_set: &SequenceSet,
- attributes: &MacroOrFetchAttributes,
+ attributes: &'b MacroOrMessageDataItemNames<'static>,
is_uid_fetch: &bool,
- ) -> Result<Vec<Body>> {
+ ) -> Result<Vec<Body<'static>>> {
let ap = AttributesProxy::new(attributes, *is_uid_fetch);
// Prepare data
@@ -619,31 +652,37 @@ impl MailboxView {
selection.with_bodies(bodies.as_slice());
// Build mail selection views
- let mut views = selection.build()?;
+ let views = selection.build()?;
// Filter views to build the result
- let ret = views
- .iter_mut()
- .filter_map(|mv| mv.filter(&ap).ok())
+ // Also identify what must be put as seen
+ let filtered_view = views
+ .iter()
+ .filter_map(|mv| mv.filter(&ap).ok().map(|(body, seen)| (mv, body, seen)))
.collect::<Vec<_>>();
-
// Register seen flags
- let future_flags = views
+ let future_flags = filtered_view
.iter()
- .filter(|mv| mv.add_seen)
- .map(|mv| async move {
+ .filter(|(_mv, _body, seen)| matches!(seen, SeenFlag::MustAdd))
+ .map(|(mv, _body, _seen)| async move {
let seen_flag = Flag::Seen.to_string();
self.mailbox.add_flags(mv.ids.uuid, &[seen_flag]).await?;
Ok::<_, anyhow::Error>(())
})
.collect::<FuturesOrdered<_>>();
+
future_flags
.collect::<Vec<_>>()
.await
.into_iter()
.collect::<Result<_, _>>()?;
- Ok(ret)
+ let command_body = filtered_view
+ .into_iter()
+ .map(|(_mv, body, _seen)| body)
+ .collect::<Vec<_>>();
+
+ Ok(command_body)
}
// ----
@@ -717,7 +756,7 @@ impl MailboxView {
// ----
/// Produce an OK [UIDVALIDITY _] message corresponding to `known_state`
- fn uidvalidity_status(&self) -> Result<Body> {
+ fn uidvalidity_status(&self) -> Result<Body<'static>> {
let uid_validity = Status::ok(
None,
Some(Code::UidValidity(self.uidvalidity())),
@@ -732,7 +771,7 @@ impl MailboxView {
}
/// Produce an OK [UIDNEXT _] message corresponding to `known_state`
- fn uidnext_status(&self) -> Result<Body> {
+ fn uidnext_status(&self) -> Result<Body<'static>> {
let next_uid = Status::ok(
None,
Some(Code::UidNext(self.uidnext())),
@@ -748,7 +787,7 @@ impl MailboxView {
/// Produce an EXISTS message corresponding to the number of mails
/// in `known_state`
- fn exists_status(&self) -> Result<Body> {
+ fn exists_status(&self) -> Result<Body<'static>> {
Ok(Body::Data(Data::Exists(self.exists()?)))
}
@@ -758,7 +797,7 @@ impl MailboxView {
/// Produce a RECENT message corresponding to the number of
/// recent mails in `known_state`
- fn recent_status(&self) -> Result<Body> {
+ fn recent_status(&self) -> Result<Body<'static>> {
Ok(Body::Data(Data::Recent(self.recent()?)))
}
@@ -774,27 +813,48 @@ impl MailboxView {
/// Produce a FLAGS and a PERMANENTFLAGS message that indicates
/// the flags that are in `known_state` + default flags
- fn flags_status(&self) -> Result<Vec<Body>> {
- let mut flags: Vec<Flag> = self
+ fn flags_status(&self) -> Result<Vec<Body<'static>>> {
+ let mut body = vec![];
+
+ // 1. Collecting all the possible flags in the mailbox
+ // 1.a Fetch them from our index
+ let mut known_flags: Vec<Flag> = self
.known_state
.idx_by_flag
.flags()
- .filter_map(|f| string_to_flag(f))
+ .filter_map(|f| match string_to_flag(f) {
+ Some(FlagFetch::Flag(fl)) => Some(fl),
+ _ => None,
+ })
.collect();
+ // 1.b Merge it with our default flags list
for f in DEFAULT_FLAGS.iter() {
- if !flags.contains(f) {
- flags.push(f.clone());
+ if !known_flags.contains(f) {
+ known_flags.push(f.clone());
}
}
- let mut ret = vec![Body::Data(Data::Flags(flags.clone()))];
+ // 1.c Create the IMAP message
+ body.push(Body::Data(Data::Flags(known_flags.clone())));
- flags.push(Flag::Permanent);
- let permanent_flags =
- Status::ok(None, Some(Code::PermanentFlags(flags)), "Flags permitted")
- .map_err(Error::msg)?;
- ret.push(Body::Status(permanent_flags));
+ // 2. Returning flags that are persisted
+ // 2.a Always advertise our default flags
+ let mut permanent = DEFAULT_FLAGS
+ .iter()
+ .map(|f| FlagPerm::Flag(f.clone()))
+ .collect::<Vec<_>>();
+ // 2.b Say that we support any keyword flag
+ permanent.push(FlagPerm::Asterisk);
+ // 2.c Create the IMAP message
+ let permanent_flags = Status::ok(
+ None,
+ Some(Code::PermanentFlags(permanent)),
+ "Flags permitted",
+ )
+ .map_err(Error::msg)?;
+ body.push(Body::Status(permanent_flags));
- Ok(ret)
+ // Done!
+ Ok(body)
}
pub(crate) fn unseen_count(&self) -> usize {
@@ -809,21 +869,21 @@ impl MailboxView {
}
}
-fn string_to_flag(f: &str) -> Option<Flag> {
+fn string_to_flag(f: &str) -> Option<FlagFetch<'static>> {
match f.chars().next() {
Some('\\') => match f {
- "\\Seen" => Some(Flag::Seen),
- "\\Answered" => Some(Flag::Answered),
- "\\Flagged" => Some(Flag::Flagged),
- "\\Deleted" => Some(Flag::Deleted),
- "\\Draft" => Some(Flag::Draft),
- "\\Recent" => Some(Flag::Recent),
+ "\\Seen" => Some(FlagFetch::Flag(Flag::Seen)),
+ "\\Answered" => Some(FlagFetch::Flag(Flag::Answered)),
+ "\\Flagged" => Some(FlagFetch::Flag(Flag::Flagged)),
+ "\\Deleted" => Some(FlagFetch::Flag(Flag::Deleted)),
+ "\\Draft" => Some(FlagFetch::Flag(Flag::Draft)),
+ "\\Recent" => Some(FlagFetch::Recent),
_ => match Atom::try_from(f.strip_prefix('\\').unwrap().to_string()) {
Err(_) => {
tracing::error!(flag=%f, "Unable to encode flag as IMAP atom");
None
}
- Ok(a) => Some(Flag::Extension(a)),
+ Ok(a) => Some(FlagFetch::Flag(Flag::system(a))),
},
},
Some(_) => match Atom::try_from(f.to_string()) {
@@ -831,7 +891,7 @@ fn string_to_flag(f: &str) -> Option<Flag> {
tracing::error!(flag=%f, "Unable to encode flag as IMAP atom");
None
}
- Ok(a) => Some(Flag::Keyword(a)),
+ Ok(a) => Some(FlagFetch::Flag(Flag::keyword(a))),
},
None => None,
}
@@ -858,7 +918,7 @@ fn string_to_flag(f: &str) -> Option<Flag> {
//@FIXME return an error if the envelope is invalid instead of panicking
//@FIXME some fields must be defaulted if there are not set.
-fn message_envelope(msg: &imf::Imf) -> Envelope {
+fn message_envelope(msg: &imf::Imf) -> Envelope<'static> {
let from = msg.from.iter().map(convert_mbx).collect::<Vec<_>>();
Envelope {
@@ -900,7 +960,7 @@ fn message_envelope(msg: &imf::Imf) -> Envelope {
}
}
-fn convert_addresses(addrlist: &Vec<imf::address::AddressRef>) -> Vec<Address> {
+fn convert_addresses(addrlist: &Vec<imf::address::AddressRef>) -> Vec<Address<'static>> {
let mut acc = vec![];
for item in addrlist {
match item {
@@ -911,23 +971,23 @@ fn convert_addresses(addrlist: &Vec<imf::address::AddressRef>) -> Vec<Address> {
return acc;
}
-fn convert_mbx(addr: &imf::mailbox::MailboxRef) -> Address {
- Address::new(
- NString(
+fn convert_mbx(addr: &imf::mailbox::MailboxRef) -> Address<'static> {
+ Address {
+ name: NString(
addr.name
.as_ref()
.map(|x| IString::try_from(x.to_string()).unwrap()),
),
// SMTP at-domain-list (source route) seems obsolete since at least 1991
// https://www.mhonarc.org/archive/html/ietf-822/1991-06/msg00060.html
- NString(None),
- NString(Some(
+ adl: NString(None),
+ mailbox: NString(Some(
IString::try_from(addr.addrspec.local_part.to_string()).unwrap(),
)),
- NString(Some(
+ host: NString(Some(
IString::try_from(addr.addrspec.domain.to_string()).unwrap(),
)),
- )
+ }
}
/*
@@ -945,19 +1005,23 @@ b fetch 29878:29879 (BODY)
b OK Fetch completed (0.001 + 0.000 secs).
*/
-fn build_imap_email_struct<'a>(part: &AnyPart<'a>) -> Result<BodyStructure> {
+fn build_imap_email_struct<'a>(part: &AnyPart<'a>) -> Result<BodyStructure<'static>> {
match part {
AnyPart::Mult(x) => {
let itype = &x.mime.interpreted_type;
let subtype = IString::try_from(itype.subtype.to_string())
.unwrap_or(unchecked_istring("alternative"));
+ let inner_bodies = x
+ .children
+ .iter()
+ .filter_map(|inner| build_imap_email_struct(&inner).ok())
+ .collect::<Vec<_>>();
+ NonEmptyVec::validate(&inner_bodies)?;
+ let bodies = NonEmptyVec::unvalidated(inner_bodies);
+
Ok(BodyStructure::Multi {
- bodies: x
- .children
- .iter()
- .filter_map(|inner| build_imap_email_struct(&inner).ok())
- .collect(),
+ bodies,
subtype,
extension_data: None,
/*Some(MultipartExtensionData {
@@ -996,7 +1060,7 @@ fn build_imap_email_struct<'a>(part: &AnyPart<'a>) -> Result<BodyStructure> {
number_of_lines: nol(x.body),
},
},
- extension: None,
+ extension_data: None,
})
}
AnyPart::Bin(x) => {
@@ -1009,9 +1073,10 @@ fn build_imap_email_struct<'a>(part: &AnyPart<'a>) -> Result<BodyStructure> {
};
let ct = x.mime.fields.ctype.as_ref().unwrap_or(&default);
- let type_ = IString::try_from(String::from_utf8_lossy(ct.main).to_string()).or(Err(
- anyhow!("Unable to build IString from given Content-Type type given"),
- ))?;
+ let r#type =
+ IString::try_from(String::from_utf8_lossy(ct.main).to_string()).or(Err(
+ anyhow!("Unable to build IString from given Content-Type type given"),
+ ))?;
let subtype =
IString::try_from(String::from_utf8_lossy(ct.sub).to_string()).or(Err(anyhow!(
@@ -1021,9 +1086,9 @@ fn build_imap_email_struct<'a>(part: &AnyPart<'a>) -> Result<BodyStructure> {
Ok(BodyStructure::Single {
body: FetchBody {
basic,
- specific: SpecificFields::Basic { type_, subtype },
+ specific: SpecificFields::Basic { r#type, subtype },
},
- extension: None,
+ extension_data: None,
})
}
AnyPart::Msg(x) => {
@@ -1033,12 +1098,12 @@ fn build_imap_email_struct<'a>(part: &AnyPart<'a>) -> Result<BodyStructure> {
body: FetchBody {
basic,
specific: SpecificFields::Message {
- envelope: message_envelope(&x.imf),
+ envelope: Box::new(message_envelope(&x.imf)),
body_structure: Box::new(build_imap_email_struct(x.child.as_ref())?),
number_of_lines: nol(x.raw_part),
},
},
- extension: None,
+ extension_data: None,
})
}
}
@@ -1059,7 +1124,7 @@ fn unchecked_istring(s: &'static str) -> IString {
IString::try_from(s).expect("this value is expected to be a valid imap-codec::IString")
}
-fn basic_fields(m: &mime::NaiveMIME, sz: usize) -> Result<BasicFields> {
+fn basic_fields(m: &mime::NaiveMIME, sz: usize) -> Result<BasicFields<'static>> {
let parameter_list = m
.ctype
.as_ref()
@@ -1136,20 +1201,18 @@ fn get_message_section<'a>(
.ok_or(anyhow!("Part must be a message"))?;
match section {
Some(FetchSection::Text(None)) => Ok(msg.raw_body.into()),
- Some(FetchSection::Text(Some(part))) => {
- map_subpart(parsed, part.0.as_slice(), |part_msg| {
- Ok(part_msg
- .as_message()
- .ok_or(Error::msg(
- "Not a message/rfc822 part while expected by request (TEXT)",
- ))?
- .raw_body
- .into())
- })
- }
+ Some(FetchSection::Text(Some(part))) => map_subpart(parsed, part.0.as_ref(), |part_msg| {
+ Ok(part_msg
+ .as_message()
+ .ok_or(Error::msg(
+ "Not a message/rfc822 part while expected by request (TEXT)",
+ ))?
+ .raw_body
+ .into())
+ }),
Some(FetchSection::Header(part)) => map_subpart(
parsed,
- part.as_ref().map(|p| p.0.as_slice()).unwrap_or(&[]),
+ part.as_ref().map(|p| p.0.as_ref()).unwrap_or(&[]),
|part_msg| {
Ok(part_msg
.as_message()
@@ -1165,17 +1228,18 @@ fn get_message_section<'a>(
) => {
let invert = matches!(section, Some(FetchSection::HeaderFieldsNot(_, _)));
let fields = fields
+ .as_ref()
.iter()
.map(|x| match x {
- AString::Atom(a) => a.as_bytes(),
- AString::String(IString::Literal(l)) => l.as_slice(),
- AString::String(IString::Quoted(q)) => q.as_bytes(),
+ AString::Atom(a) => a.inner().as_bytes(),
+ AString::String(IString::Literal(l)) => l.as_ref(),
+ AString::String(IString::Quoted(q)) => q.inner().as_bytes(),
})
.collect::<Vec<_>>();
map_subpart(
parsed,
- part.as_ref().map(|p| p.0.as_slice()).unwrap_or(&[]),
+ part.as_ref().map(|p| p.0.as_ref()).unwrap_or(&[]),
|part_msg| {
let mut ret = vec![];
for f in &part_msg.mime().kv {
@@ -1195,7 +1259,7 @@ fn get_message_section<'a>(
},
)
}
- Some(FetchSection::Part(part)) => map_subpart(parsed, part.0.as_slice(), |part| {
+ Some(FetchSection::Part(part)) => map_subpart(parsed, part.0.as_ref(), |part| {
let bytes = match &part {
AnyPart::Txt(p) => p.body,
AnyPart::Bin(p) => p.body,
@@ -1204,7 +1268,7 @@ fn get_message_section<'a>(
};
Ok(bytes.to_vec().into())
}),
- Some(FetchSection::Mime(part)) => map_subpart(parsed, part.0.as_slice(), |part| {
+ Some(FetchSection::Mime(part)) => map_subpart(parsed, part.0.as_ref(), |part| {
let bytes = match &part {
AnyPart::Txt(p) => p.mime.fields.raw,
AnyPart::Bin(p) => p.mime.fields.raw,
@@ -1245,18 +1309,22 @@ mod tests {
use super::*;
use crate::cryptoblob;
use crate::mail::unique_ident;
- use imap_codec::codec::Encode;
- use imap_codec::types::fetch_attributes::Section;
+ use imap_codec::encode::Encoder;
+ use imap_codec::imap_types::fetch::Section;
+ use imap_codec::imap_types::response::Response;
+ use imap_codec::ResponseCodec;
use std::fs;
#[test]
fn mailview_body_ext() -> Result<()> {
let ap = AttributesProxy::new(
- &MacroOrFetchAttributes::FetchAttributes(vec![FetchAttribute::BodyExt {
- section: Some(Section::Header(None)),
- partial: None,
- peek: false,
- }]),
+ &MacroOrMessageDataItemNames::MessageDataItemNames(vec![
+ MessageDataItemName::BodyExt {
+ section: Some(Section::Header(None)),
+ partial: None,
+ peek: false,
+ },
+ ]),
false,
);
@@ -1276,27 +1344,26 @@ mod tests {
let rfc822 = b"Subject: hello\r\nFrom: a@a.a\r\nTo: b@b.b\r\nDate: Thu, 12 Oct 2023 08:45:28 +0000\r\n\r\nhello world";
let content = FetchedMail::new_from_message(eml_codec::parse_message(rfc822)?.1);
- let mut mv = MailView {
+ let mv = MailView {
ids: &ids,
content,
meta: &meta,
flags: &flags,
- add_seen: false,
};
- let res_body = mv.filter(&ap)?;
+ let (res_body, _seen) = mv.filter(&ap)?;
let fattr = match res_body {
Body::Data(Data::Fetch {
- seq_or_uid: _seq,
- attributes: attr,
+ seq: _seq,
+ items: attr,
}) => Ok(attr),
_ => Err(anyhow!("Not a fetch body")),
}?;
- assert_eq!(fattr.len(), 1);
+ assert_eq!(fattr.as_ref().len(), 1);
- let (sec, _orig, _data) = match &fattr[0] {
- MessageAttribute::BodyExt {
+ let (sec, _orig, _data) = match &fattr.as_ref()[0] {
+ MessageDataItem::BodyExt {
section,
origin,
data,
@@ -1345,22 +1412,24 @@ mod tests {
for pref in prefixes.iter() {
println!("{}", pref);
let txt = fs::read(format!("{}.eml", pref))?;
- let exp = fs::read(format!("{}.dovecot.body", pref))?;
+ let oracle = fs::read(format!("{}.dovecot.body", pref))?;
let message = eml_codec::parse_message(&txt).unwrap().1;
- let mut resp = Vec::new();
- MessageAttribute::Body(build_imap_email_struct(&message.child)?)
- .encode(&mut resp)
- .unwrap();
-
- let resp_str = String::from_utf8_lossy(&resp).to_lowercase();
+ let test_repr = Response::Data(Data::Fetch {
+ seq: NonZeroU32::new(1).unwrap(),
+ items: NonEmptyVec::from(MessageDataItem::Body(build_imap_email_struct(
+ &message.child,
+ )?)),
+ });
+ let test_bytes = ResponseCodec::new().encode(&test_repr).dump();
+ let test_str = String::from_utf8_lossy(&test_bytes).to_lowercase();
- let exp_no_parenthesis = &exp[1..exp.len() - 1];
- let exp_str = String::from_utf8_lossy(exp_no_parenthesis).to_lowercase();
+ let oracle_str =
+ format!("* 1 FETCH {}\r\n", String::from_utf8_lossy(&oracle)).to_lowercase();
- println!("aerogramme: {}\n\ndovecot: {}\n\n", resp_str, exp_str);
+ println!("aerogramme: {}\n\ndovecot: {}\n\n", test_str, oracle_str);
//println!("\n\n {} \n\n", String::from_utf8_lossy(&resp));
- assert_eq!(resp_str, exp_str);
+ assert_eq!(test_str, oracle_str);
}
Ok(())
diff --git a/src/imap/mod.rs b/src/imap/mod.rs
index f85bcc6..31eeaa8 100644
--- a/src/imap/mod.rs
+++ b/src/imap/mod.rs
@@ -1,105 +1,186 @@
mod command;
mod flow;
mod mailbox_view;
+mod response;
mod session;
-use std::task::{Context, Poll};
+use std::net::SocketAddr;
use anyhow::Result;
-use boitalettres::errors::Error as BalError;
-use boitalettres::proto::{Request, Response};
-use boitalettres::server::accept::addr::AddrIncoming;
-use boitalettres::server::accept::addr::AddrStream;
-use boitalettres::server::Server as ImapServer;
-use futures::future::BoxFuture;
-use futures::future::FutureExt;
+use futures::stream::{FuturesUnordered, StreamExt};
+
+use tokio::net::TcpListener;
use tokio::sync::watch;
-use tower::Service;
+
+use imap_codec::imap_types::response::Greeting;
+use imap_flow::server::{ServerFlow, ServerFlowEvent, ServerFlowOptions};
+use imap_flow::stream::AnyStream;
use crate::config::ImapConfig;
use crate::login::ArcLoginProvider;
/// Server is a thin wrapper to register our Services in BàL
-pub struct Server(ImapServer<AddrIncoming, Instance>);
-
-pub async fn new(config: ImapConfig, login: ArcLoginProvider) -> Result<Server> {
- //@FIXME add a configuration parameter
- let incoming = AddrIncoming::new(config.bind_addr).await?;
- tracing::info!("IMAP activated, will listen on {:#}", incoming.local_addr);
-
- let imap = ImapServer::new(incoming).serve(Instance::new(login.clone()));
- Ok(Server(imap))
-}
-
-impl Server {
- pub async fn run(self, mut must_exit: watch::Receiver<bool>) -> Result<()> {
- tracing::info!("IMAP started!");
- tokio::select! {
- s = self.0 => s?,
- _ = must_exit.changed() => tracing::info!("Stopped IMAP server"),
- }
-
- Ok(())
- }
+pub struct Server {
+ bind_addr: SocketAddr,
+ login_provider: ArcLoginProvider,
}
-//---
-
-/// Instance is the main Tokio Tower service that we register in BàL.
-/// It receives new connection demands and spawn a dedicated service.
-struct Instance {
+struct ClientContext {
+ stream: AnyStream,
+ addr: SocketAddr,
login_provider: ArcLoginProvider,
+ must_exit: watch::Receiver<bool>,
}
-impl Instance {
- pub fn new(login_provider: ArcLoginProvider) -> Self {
- Self { login_provider }
+pub fn new(config: ImapConfig, login: ArcLoginProvider) -> Server {
+ Server {
+ bind_addr: config.bind_addr,
+ login_provider: login,
}
}
-impl<'a> Service<&'a AddrStream> for Instance {
- type Response = Connection;
- type Error = anyhow::Error;
- type Future = BoxFuture<'static, Result<Self::Response>>;
+impl Server {
+ pub async fn run(self: Self, mut must_exit: watch::Receiver<bool>) -> Result<()> {
+ let tcp = TcpListener::bind(self.bind_addr).await?;
+ tracing::info!("IMAP server listening on {:#}", self.bind_addr);
+
+ let mut connections = FuturesUnordered::new();
+
+ while !*must_exit.borrow() {
+ let wait_conn_finished = async {
+ if connections.is_empty() {
+ futures::future::pending().await
+ } else {
+ connections.next().await
+ }
+ };
+ let (socket, remote_addr) = tokio::select! {
+ a = tcp.accept() => a?,
+ _ = wait_conn_finished => continue,
+ _ = must_exit.changed() => continue,
+ };
+ tracing::info!("IMAP: accepted connection from {}", remote_addr);
+
+ let client = ClientContext {
+ stream: AnyStream::new(socket),
+ addr: remote_addr.clone(),
+ login_provider: self.login_provider.clone(),
+ must_exit: must_exit.clone(),
+ };
+ let conn = tokio::spawn(client_wrapper(client));
+ connections.push(conn);
+ }
+ drop(tcp);
- fn poll_ready(&mut self, _cx: &mut Context<'_>) -> Poll<Result<(), Self::Error>> {
- Poll::Ready(Ok(()))
- }
+ tracing::info!("IMAP server shutting down, draining remaining connections...");
+ while connections.next().await.is_some() {}
- fn call(&mut self, addr: &'a AddrStream) -> Self::Future {
- tracing::info!(remote_addr = %addr.remote_addr, local_addr = %addr.local_addr, "accept");
- let lp = self.login_provider.clone();
- async { Ok(Connection::new(lp)) }.boxed()
+ Ok(())
}
}
-//---
-
-/// Connection is the per-connection Tokio Tower service we register in BàL.
-/// It handles a single TCP connection, and thus has a business logic.
-struct Connection {
- session: session::Manager,
-}
-
-impl Connection {
- pub fn new(login_provider: ArcLoginProvider) -> Self {
- Self {
- session: session::Manager::new(login_provider),
+async fn client_wrapper(ctx: ClientContext) {
+ let addr = ctx.addr.clone();
+ match client(ctx).await {
+ Ok(()) => {
+ tracing::info!("closing successful session for {:?}", addr);
+ }
+ Err(e) => {
+ tracing::error!("closing errored session for {:?}: {}", addr, e);
}
}
}
-impl Service<Request> for Connection {
- type Response = Response;
- type Error = BalError;
- type Future = BoxFuture<'static, Result<Self::Response, Self::Error>>;
+async fn client(mut ctx: ClientContext) -> Result<()> {
+ // Send greeting
+ let (mut server, _) = ServerFlow::send_greeting(
+ ctx.stream,
+ ServerFlowOptions::default(),
+ Greeting::ok(None, "Aerogramme").unwrap(),
+ )
+ .await?;
+
+ use crate::imap::response::{Body, Response as MyResponse};
+ use crate::imap::session::Instance;
+ use imap_codec::imap_types::command::Command;
+ use imap_codec::imap_types::response::{Response, Status};
+
+ use tokio::sync::mpsc;
+ let (cmd_tx, mut cmd_rx) = mpsc::channel::<Command<'static>>(10);
+ let (resp_tx, mut resp_rx) = mpsc::unbounded_channel::<MyResponse<'static>>();
+
+ let bckgrnd = tokio::spawn(async move {
+ let mut session = Instance::new(ctx.login_provider);
+ loop {
+ let cmd = match cmd_rx.recv().await {
+ None => break,
+ Some(cmd_recv) => cmd_recv,
+ };
+
+ let maybe_response = session.command(cmd).await;
+
+ match resp_tx.send(maybe_response) {
+ Err(_) => break,
+ Ok(_) => (),
+ };
+ }
+ tracing::info!("runner is quitting");
+ });
- fn poll_ready(&mut self, _cx: &mut Context<'_>) -> Poll<Result<(), Self::Error>> {
- Poll::Ready(Ok(()))
+ // Main loop
+ loop {
+ tokio::select! {
+ // Managing imap_flow stuff
+ srv_evt = server.progress() => match srv_evt? {
+ ServerFlowEvent::ResponseSent { handle: _handle, response } => {
+ match response {
+ Response::Status(Status::Bye(_)) => break,
+ _ => tracing::trace!("sent to {} content {:?}", ctx.addr, response),
+ }
+ },
+ ServerFlowEvent::CommandReceived { command } => {
+ match cmd_tx.try_send(command) {
+ Ok(_) => (),
+ Err(mpsc::error::TrySendError::Full(_)) => {
+ server.enqueue_status(Status::bye(None, "Too fast").unwrap());
+ tracing::error!("client {:?} is sending commands too fast, closing.", ctx.addr);
+ }
+ _ => {
+ server.enqueue_status(Status::bye(None, "Internal session exited").unwrap());
+ tracing::error!("session task exited for {:?}, quitting", ctx.addr);
+ }
+ }
+ },
+ },
+
+ // Managing response generated by Aerogramme
+ maybe_msg = resp_rx.recv() => {
+ let response = match maybe_msg {
+ None => {
+ server.enqueue_status(Status::bye(None, "Internal session exited").unwrap());
+ tracing::error!("session task exited for {:?}, quitting", ctx.addr);
+ continue
+ },
+ Some(r) => r,
+ };
+
+ for body_elem in response.body.into_iter() {
+ let _handle = match body_elem {
+ Body::Data(d) => server.enqueue_data(d),
+ Body::Status(s) => server.enqueue_status(s),
+ };
+ }
+ server.enqueue_status(response.completion);
+ },
+
+ // When receiving a CTRL+C
+ _ = ctx.must_exit.changed() => {
+ server.enqueue_status(Status::bye(None, "Server is being shutdown").unwrap());
+ },
+ };
}
- fn call(&mut self, req: Request) -> Self::Future {
- tracing::debug!("Got request: {:#?}", req.command);
- self.session.process(req)
- }
+ drop(cmd_tx);
+ bckgrnd.await?;
+ Ok(())
}
diff --git a/src/imap/response.rs b/src/imap/response.rs
new file mode 100644
index 0000000..d20e58e
--- /dev/null
+++ b/src/imap/response.rs
@@ -0,0 +1,112 @@
+use anyhow::Result;
+use imap_codec::imap_types::command::Command;
+use imap_codec::imap_types::core::Tag;
+use imap_codec::imap_types::response::{Code, Data, Status};
+
+pub enum Body<'a> {
+ Data(Data<'a>),
+ Status(Status<'a>),
+}
+
+pub struct ResponseBuilder<'a> {
+ tag: Option<Tag<'a>>,
+ code: Option<Code<'a>>,
+ text: String,
+ body: Vec<Body<'a>>,
+}
+
+impl<'a> ResponseBuilder<'a> {
+ pub fn to_req(mut self, cmd: &Command<'a>) -> Self {
+ self.tag = Some(cmd.tag.clone());
+ self
+ }
+ pub fn tag(mut self, tag: Tag<'a>) -> Self {
+ self.tag = Some(tag);
+ self
+ }
+
+ pub fn message(mut self, txt: impl Into<String>) -> Self {
+ self.text = txt.into();
+ self
+ }
+
+ pub fn code(mut self, code: Code<'a>) -> Self {
+ self.code = Some(code);
+ self
+ }
+
+ pub fn data(mut self, data: Data<'a>) -> Self {
+ self.body.push(Body::Data(data));
+ self
+ }
+
+ pub fn many_data(mut self, data: Vec<Data<'a>>) -> Self {
+ for d in data.into_iter() {
+ self = self.data(d);
+ }
+ self
+ }
+
+ #[allow(dead_code)]
+ pub fn info(mut self, status: Status<'a>) -> Self {
+ self.body.push(Body::Status(status));
+ self
+ }
+
+ #[allow(dead_code)]
+ pub fn many_info(mut self, status: Vec<Status<'a>>) -> Self {
+ for d in status.into_iter() {
+ self = self.info(d);
+ }
+ self
+ }
+
+ pub fn set_body(mut self, body: Vec<Body<'a>>) -> Self {
+ self.body = body;
+ self
+ }
+
+ pub fn ok(self) -> Result<Response<'a>> {
+ Ok(Response {
+ completion: Status::ok(self.tag, self.code, self.text)?,
+ body: self.body,
+ })
+ }
+
+ pub fn no(self) -> Result<Response<'a>> {
+ Ok(Response {
+ completion: Status::no(self.tag, self.code, self.text)?,
+ body: self.body,
+ })
+ }
+
+ pub fn bad(self) -> Result<Response<'a>> {
+ Ok(Response {
+ completion: Status::bad(self.tag, self.code, self.text)?,
+ body: self.body,
+ })
+ }
+}
+
+pub struct Response<'a> {
+ pub body: Vec<Body<'a>>,
+ pub completion: Status<'a>,
+}
+
+impl<'a> Response<'a> {
+ pub fn build() -> ResponseBuilder<'a> {
+ ResponseBuilder {
+ tag: None,
+ code: None,
+ text: "".to_string(),
+ body: vec![],
+ }
+ }
+
+ pub fn bye() -> Result<Response<'a>> {
+ Ok(Response {
+ completion: Status::bye(None, "bye")?,
+ body: vec![],
+ })
+ }
+}
diff --git a/src/imap/session.rs b/src/imap/session.rs
index 15141d3..5c67f8e 100644
--- a/src/imap/session.rs
+++ b/src/imap/session.rs
@@ -1,180 +1,86 @@
-use anyhow::Error;
-use boitalettres::errors::Error as BalError;
-use boitalettres::proto::{Request, Response};
-use futures::future::BoxFuture;
-use futures::future::FutureExt;
-
-use tokio::sync::mpsc::error::TrySendError;
-use tokio::sync::{mpsc, oneshot};
-
use crate::imap::command::{anonymous, authenticated, examined, selected};
use crate::imap::flow;
+use crate::imap::response::Response;
use crate::login::ArcLoginProvider;
-
-/* This constant configures backpressure in the system,
- * or more specifically, how many pipelined messages are allowed
- * before refusing them
- */
-const MAX_PIPELINED_COMMANDS: usize = 10;
-
-struct Message {
- req: Request,
- tx: oneshot::Sender<Result<Response, BalError>>,
-}
-
-//-----
-
-pub struct Manager {
- tx: mpsc::Sender<Message>,
-}
-
-impl Manager {
- pub fn new(login_provider: ArcLoginProvider) -> Self {
- let (tx, rx) = mpsc::channel(MAX_PIPELINED_COMMANDS);
- tokio::spawn(async move {
- let instance = Instance::new(login_provider, rx);
- instance.start().await;
- });
- Self { tx }
- }
-
- pub fn process(&self, req: Request) -> BoxFuture<'static, Result<Response, BalError>> {
- let (tx, rx) = oneshot::channel();
- let msg = Message { req, tx };
-
- // We use try_send on a bounded channel to protect the daemons from DoS.
- // Pipelining requests in IMAP are a special case: they should not occure often
- // and in a limited number (like 3 requests). Someone filling the channel
- // will probably be malicious so we "rate limit" them.
- match self.tx.try_send(msg) {
- Ok(()) => (),
- Err(TrySendError::Full(_)) => {
- return async { Response::bad("Too fast! Send less pipelined requests.") }.boxed()
- }
- Err(TrySendError::Closed(_)) => {
- return async { Err(BalError::Text("Terminated session".to_string())) }.boxed()
- }
- };
-
- // @FIXME add a timeout, handle a session that fails.
- async {
- match rx.await {
- Ok(r) => r,
- Err(e) => {
- tracing::warn!("Got error {:#?}", e);
- Response::bad("No response from the session handler")
- }
- }
- }
- .boxed()
- }
-}
+use imap_codec::imap_types::command::Command;
//-----
-
pub struct Instance {
- rx: mpsc::Receiver<Message>,
-
pub login_provider: ArcLoginProvider,
pub state: flow::State,
}
impl Instance {
- fn new(login_provider: ArcLoginProvider, rx: mpsc::Receiver<Message>) -> Self {
+ pub fn new(login_provider: ArcLoginProvider) -> Self {
Self {
login_provider,
- rx,
state: flow::State::NotAuthenticated,
}
}
- //@FIXME add a function that compute the runner's name from its local info
- // to ease debug
- // fn name(&self) -> String { }
-
- async fn start(mut self) {
- //@FIXME add more info about the runner
- tracing::debug!("starting runner");
-
- while let Some(msg) = self.rx.recv().await {
- // Command behavior is modulated by the state.
- // To prevent state error, we handle the same command in separate code paths.
- let ctrl = match &mut self.state {
- flow::State::NotAuthenticated => {
- let ctx = anonymous::AnonymousContext {
- req: &msg.req,
- login_provider: Some(&self.login_provider),
- };
- anonymous::dispatch(ctx).await
- }
- flow::State::Authenticated(ref user) => {
- let ctx = authenticated::AuthenticatedContext {
- req: &msg.req,
- user,
- };
- authenticated::dispatch(ctx).await
- }
- flow::State::Selected(ref user, ref mut mailbox) => {
- let ctx = selected::SelectedContext {
- req: &msg.req,
- user,
- mailbox,
- };
- selected::dispatch(ctx).await
- }
- flow::State::Examined(ref user, ref mut mailbox) => {
- let ctx = examined::ExaminedContext {
- req: &msg.req,
- user,
- mailbox,
- };
- examined::dispatch(ctx).await
- }
- flow::State::Logout => {
- Response::bad("No commands are allowed in the LOGOUT state.")
- .map(|r| (r, flow::Transition::None))
- .map_err(Error::msg)
- }
- };
-
- // Process result
- let res = match ctrl {
- Ok((res, tr)) => {
- //@FIXME remove unwrap
- self.state = match self.state.apply(tr) {
- Ok(new_state) => new_state,
- Err(e) => {
- tracing::error!("Invalid transition: {}, exiting", e);
- break;
- }
- };
-
- //@FIXME enrich here the command with some global status
-
- Ok(res)
- }
- // Cast from anyhow::Error to Bal::Error
- // @FIXME proper error handling would be great
- Err(e) => match e.downcast::<BalError>() {
- Ok(be) => Err(be),
- Err(e) => {
- tracing::warn!(error=%e, "internal.error");
- Response::bad("Internal error")
- }
- },
- };
-
- //@FIXME I think we should quit this thread on error and having our manager watch it,
- // and then abort the session as it is corrupted.
- msg.tx.send(res).unwrap_or_else(|e| {
- tracing::warn!("failed to send imap response to manager: {:#?}", e)
- });
-
- if let flow::State::Logout = &self.state {
- break;
+ pub async fn command(&mut self, cmd: Command<'static>) -> Response<'static> {
+ // Command behavior is modulated by the state.
+ // To prevent state error, we handle the same command in separate code paths.
+ let (resp, tr) = match &mut self.state {
+ flow::State::NotAuthenticated => {
+ let ctx = anonymous::AnonymousContext {
+ req: &cmd,
+ login_provider: &self.login_provider,
+ };
+ anonymous::dispatch(ctx).await
+ }
+ flow::State::Authenticated(ref user) => {
+ let ctx = authenticated::AuthenticatedContext { req: &cmd, user };
+ authenticated::dispatch(ctx).await
+ }
+ flow::State::Selected(ref user, ref mut mailbox) => {
+ let ctx = selected::SelectedContext {
+ req: &cmd,
+ user,
+ mailbox,
+ };
+ selected::dispatch(ctx).await
}
+ flow::State::Examined(ref user, ref mut mailbox) => {
+ let ctx = examined::ExaminedContext {
+ req: &cmd,
+ user,
+ mailbox,
+ };
+ examined::dispatch(ctx).await
+ }
+ flow::State::Logout => Response::build()
+ .tag(cmd.tag.clone())
+ .message("No commands are allowed in the LOGOUT state.")
+ .bad()
+ .map(|r| (r, flow::Transition::None)),
+ }
+ .unwrap_or_else(|err| {
+ tracing::error!("Command error {:?} occured while processing {:?}", err, cmd);
+ (
+ Response::build()
+ .to_req(&cmd)
+ .message("Internal error while processing command")
+ .bad()
+ .unwrap(),
+ flow::Transition::None,
+ )
+ });
+
+ if let Err(e) = self.state.apply(tr) {
+ tracing::error!(
+ "Transition error {:?} occured while processing on command {:?}",
+ e,
+ cmd
+ );
+ return Response::build()
+ .to_req(&cmd)
+ .message(
+ "Internal error, processing command triggered an illegal IMAP state transition",
+ )
+ .bad()
+ .unwrap();
}
- //@FIXME add more info about the runner
- tracing::debug!("exiting runner");
+ resp
}
}
diff --git a/src/login/demo_provider.rs b/src/login/demo_provider.rs
new file mode 100644
index 0000000..11c7d54
--- /dev/null
+++ b/src/login/demo_provider.rs
@@ -0,0 +1,51 @@
+use crate::login::*;
+use crate::storage::*;
+
+pub struct DemoLoginProvider {
+ keys: CryptoKeys,
+ in_memory_store: in_memory::MemDb,
+}
+
+impl DemoLoginProvider {
+ pub fn new() -> Self {
+ Self {
+ keys: CryptoKeys::init(),
+ in_memory_store: in_memory::MemDb::new(),
+ }
+ }
+}
+
+#[async_trait]
+impl LoginProvider for DemoLoginProvider {
+ async fn login(&self, username: &str, password: &str) -> Result<Credentials> {
+ tracing::debug!(user=%username, "login");
+
+ if username != "alice" {
+ bail!("user does not exist");
+ }
+
+ if password != "hunter2" {
+ bail!("wrong password");
+ }
+
+ let storage = self.in_memory_store.builder("alice").await;
+ let keys = self.keys.clone();
+
+ Ok(Credentials { storage, keys })
+ }
+
+ async fn public_login(&self, email: &str) -> Result<PublicCredentials> {
+ tracing::debug!(user=%email, "public_login");
+ if email != "alice@example.tld" {
+ bail!("invalid email address");
+ }
+
+ let storage = self.in_memory_store.builder("alice").await;
+ let public_key = self.keys.public.clone();
+
+ Ok(PublicCredentials {
+ storage,
+ public_key,
+ })
+ }
+}
diff --git a/src/login/mod.rs b/src/login/mod.rs
index 2926738..4a1dee1 100644
--- a/src/login/mod.rs
+++ b/src/login/mod.rs
@@ -1,3 +1,4 @@
+pub mod demo_provider;
pub mod ldap_provider;
pub mod static_provider;
diff --git a/src/main.rs b/src/main.rs
index 3221c2e..3baa8e2 100644
--- a/src/main.rs
+++ b/src/main.rs
@@ -29,7 +29,12 @@ struct Args {
#[clap(subcommand)]
command: Command,
+ /// A special mode dedicated to developers, NOT INTENDED FOR PRODUCTION
+ #[clap(long)]
+ dev: bool,
+
#[clap(short, long, env = "CONFIG_FILE", default_value = "aerogramme.toml")]
+ /// Path to the main Aerogramme configuration file
config_file: PathBuf,
}
@@ -158,7 +163,22 @@ async fn main() -> Result<()> {
tracing_subscriber::fmt::init();
let args = Args::parse();
- let any_config = read_config(args.config_file)?;
+ let any_config = if args.dev {
+ use std::net::*;
+ AnyConfig::Provider(ProviderConfig {
+ pid: None,
+ imap: ImapConfig {
+ bind_addr: SocketAddr::new(IpAddr::V6(Ipv6Addr::new(0, 0, 0, 0, 0, 0, 0, 1)), 1143),
+ },
+ lmtp: LmtpConfig {
+ bind_addr: SocketAddr::new(IpAddr::V6(Ipv6Addr::new(0, 0, 0, 0, 0, 0, 0, 1)), 1025),
+ hostname: "example.tld".to_string(),
+ },
+ users: UserManagement::Demo,
+ })
+ } else {
+ read_config(args.config_file)?
+ };
match (&args.command, any_config) {
(Command::Companion(subcommand), AnyConfig::Companion(config)) => match subcommand {
@@ -184,8 +204,8 @@ async fn main() -> Result<()> {
ProviderCommand::Account(cmd) => {
let user_file = match config.users {
UserManagement::Static(conf) => conf.user_list,
- UserManagement::Ldap(_) => {
- panic!("LDAP account management is not supported from Aerogramme.")
+ _ => {
+ panic!("Only static account management is supported from Aerogramme.")
}
};
account_management(&args.command, cmd, user_file)?;
diff --git a/src/server.rs b/src/server.rs
index 28e0b27..bd2fd5d 100644
--- a/src/server.rs
+++ b/src/server.rs
@@ -11,7 +11,7 @@ use crate::config::*;
use crate::imap;
use crate::lmtp::*;
use crate::login::ArcLoginProvider;
-use crate::login::{ldap_provider::*, static_provider::*};
+use crate::login::{demo_provider::*, ldap_provider::*, static_provider::*};
pub struct Server {
lmtp_server: Option<Arc<LmtpServer>>,
@@ -25,7 +25,7 @@ impl Server {
let login = Arc::new(StaticLoginProvider::new(config.users).await?);
let lmtp_server = None;
- let imap_server = Some(imap::new(config.imap, login.clone()).await?);
+ let imap_server = Some(imap::new(config.imap, login.clone()));
Ok(Self {
lmtp_server,
imap_server,
@@ -36,12 +36,13 @@ impl Server {
pub async fn from_provider_config(config: ProviderConfig) -> Result<Self> {
tracing::info!("Init as provider");
let login: ArcLoginProvider = match config.users {
+ UserManagement::Demo => Arc::new(DemoLoginProvider::new()),
UserManagement::Static(x) => Arc::new(StaticLoginProvider::new(x).await?),
UserManagement::Ldap(x) => Arc::new(LdapLoginProvider::new(x)?),
};
let lmtp_server = Some(LmtpServer::new(config.lmtp, login.clone()));
- let imap_server = Some(imap::new(config.imap, login.clone()).await?);
+ let imap_server = Some(imap::new(config.imap, login.clone()));
Ok(Self {
lmtp_server,
diff --git a/tests/imap_features.rs b/tests/imap_features.rs
new file mode 100644
index 0000000..9e8d587
--- /dev/null
+++ b/tests/imap_features.rs
@@ -0,0 +1,394 @@
+use anyhow::{bail, Context, Result};
+use std::io::{Read, Write};
+use std::net::{Shutdown, TcpStream};
+use std::process::Command;
+use std::{thread, time};
+
+static SMALL_DELAY: time::Duration = time::Duration::from_millis(200);
+static EMAIL1: &[u8] = b"Date: Sat, 8 Jul 2023 07:14:29 +0200\r
+From: Bob Robert <bob@example.tld>\r
+To: Alice Malice <alice@example.tld>\r
+CC: =?ISO-8859-1?Q?Andr=E9?= Pirard <PIRARD@vm1.ulg.ac.be>\r
+Subject: =?ISO-8859-1?B?SWYgeW91IGNhbiByZWFkIHRoaXMgeW8=?=\r
+ =?ISO-8859-2?B?dSB1bmRlcnN0YW5kIHRoZSBleGFtcGxlLg==?=\r
+X-Unknown: something something\r
+Bad entry\r
+ on multiple lines\r
+Message-ID: <NTAxNzA2AC47634Y366BAMTY4ODc5MzQyODY0ODY5@www.grrrndzero.org>\r
+MIME-Version: 1.0\r
+Content-Type: multipart/alternative;\r
+ boundary=\"b1_e376dc71bafc953c0b0fdeb9983a9956\"\r
+Content-Transfer-Encoding: 7bit\r
+\r
+This is a multi-part message in MIME format.\r
+\r
+--b1_e376dc71bafc953c0b0fdeb9983a9956\r
+Content-Type: text/plain; charset=utf-8\r
+Content-Transfer-Encoding: quoted-printable\r
+\r
+GZ\r
+OoOoO\r
+oOoOoOoOo\r
+oOoOoOoOoOoOoOoOo\r
+oOoOoOoOoOoOoOoOoOoOoOo\r
+oOoOoOoOoOoOoOoOoOoOoOoOoOoOo\r
+OoOoOoOoOoOoOoOoOoOoOoOoOoOoOoOoO\r
+\r
+--b1_e376dc71bafc953c0b0fdeb9983a9956\r
+Content-Type: text/html; charset=us-ascii\r
+\r
+<div style=\"text-align: center;\"><strong>GZ</strong><br />\r
+OoOoO<br />\r
+oOoOoOoOo<br />\r
+oOoOoOoOoOoOoOoOo<br />\r
+oOoOoOoOoOoOoOoOoOoOoOo<br />\r
+oOoOoOoOoOoOoOoOoOoOoOoOoOoOo<br />\r
+OoOoOoOoOoOoOoOoOoOoOoOoOoOoOoOoO<br />\r
+</div>\r
+\r
+--b1_e376dc71bafc953c0b0fdeb9983a9956--\r
+";
+
+static EMAIL2: &[u8] = b"From: alice@example.com\r
+To: alice@example.tld\r
+Subject: Test\r
+\r
+Hello world!\r
+";
+
+fn main() {
+ let mut daemon = Command::new(env!("CARGO_BIN_EXE_aerogramme"))
+ .arg("--dev")
+ .arg("provider")
+ .arg("daemon")
+ .spawn()
+ .expect("daemon should be started");
+
+ let mut max_retry = 20;
+ let mut imap_socket = loop {
+ max_retry -= 1;
+ match (TcpStream::connect("[::1]:1143"), max_retry) {
+ (Err(e), 0) => panic!("no more retry, last error is: {}", e),
+ (Err(e), _) => {
+ println!("unable to connect: {} ; will retry in 1 sec", e);
+ }
+ (Ok(v), _) => break v,
+ }
+ thread::sleep(SMALL_DELAY);
+ };
+
+ let mut lmtp_socket = TcpStream::connect("[::1]:1025").expect("lmtp socket must be connected");
+
+ println!("-- ready to test imap features --");
+ let result = generic_test(&mut imap_socket, &mut lmtp_socket);
+ println!("-- test teardown --");
+
+ imap_socket
+ .shutdown(Shutdown::Both)
+ .expect("closing imap socket at the end of the test");
+ lmtp_socket
+ .shutdown(Shutdown::Both)
+ .expect("closing lmtp socket at the end of the test");
+ daemon.kill().expect("daemon should be killed");
+
+ result.expect("all tests passed");
+}
+
+fn generic_test(imap_socket: &mut TcpStream, lmtp_socket: &mut TcpStream) -> Result<()> {
+ connect(imap_socket).context("server says hello")?;
+ capability(imap_socket).context("check server capabilities")?;
+ login(imap_socket).context("login test")?;
+ create_mailbox(imap_socket).context("created mailbox archive")?;
+ // UNSUBSCRIBE IS NOT IMPLEMENTED YET
+ //unsubscribe_mailbox(imap_socket).context("unsubscribe from archive")?;
+ select_inbox(imap_socket).context("select inbox")?;
+ check(imap_socket).context("check must run")?;
+ status_mailbox(imap_socket).context("status of archive from inbox")?;
+ lmtp_handshake(lmtp_socket).context("handshake lmtp done")?;
+ lmtp_deliver_email(lmtp_socket, EMAIL1).context("mail delivered successfully")?;
+ noop_exists(imap_socket).context("noop loop must detect a new email")?;
+ fetch_rfc822(imap_socket, EMAIL1).context("fetch rfc822 message")?;
+ copy_email(imap_socket).context("copy message to the archive mailbox")?;
+ append_email(imap_socket, EMAIL2).context("insert email in INBOX")?;
+ // SEARCH IS NOT IMPLEMENTED YET
+ //search(imap_socket).expect("search should return something");
+ add_flags_email(imap_socket).context("should add delete and important flags to the email")?;
+ expunge(imap_socket).context("expunge emails")?;
+ rename_mailbox(imap_socket).context("archive mailbox is renamed my-archives")?;
+ delete_mailbox(imap_socket).context("my-archives mailbox is deleted")?;
+ Ok(())
+}
+
+fn connect(imap: &mut TcpStream) -> Result<()> {
+ let mut buffer: [u8; 1500] = [0; 1500];
+
+ let read = read_lines(imap, &mut buffer, None)?;
+ assert_eq!(&read[..4], &b"* OK"[..]);
+
+ Ok(())
+}
+
+fn capability(imap: &mut TcpStream) -> Result<()> {
+ imap.write(&b"5 capability\r\n"[..])?;
+
+ let mut buffer: [u8; 1500] = [0; 1500];
+ let read = read_lines(imap, &mut buffer, Some(&b"5 OK"[..]))?;
+ let srv_msg = std::str::from_utf8(read)?;
+ assert!(srv_msg.contains("IMAP4REV1"));
+ assert!(srv_msg.contains("IDLE"));
+
+ Ok(())
+}
+
+fn login(imap: &mut TcpStream) -> Result<()> {
+ let mut buffer: [u8; 1500] = [0; 1500];
+
+ imap.write(&b"10 login alice hunter2\r\n"[..])?;
+
+ let read = read_lines(imap, &mut buffer, None)?;
+ assert_eq!(&read[..5], &b"10 OK"[..]);
+
+ Ok(())
+}
+
+fn create_mailbox(imap: &mut TcpStream) -> Result<()> {
+ let mut buffer: [u8; 1500] = [0; 1500];
+
+ imap.write(&b"15 create archive\r\n"[..])?;
+ let read = read_lines(imap, &mut buffer, None)?;
+ assert_eq!(&read[..12], &b"15 OK CREATE"[..]);
+
+ Ok(())
+}
+
+#[allow(dead_code)]
+fn unsubscribe_mailbox(imap: &mut TcpStream) -> Result<()> {
+ let mut buffer: [u8; 6000] = [0; 6000];
+
+ imap.write(&b"16 lsub \"\" *\r\n"[..])?;
+ let read = read_lines(imap, &mut buffer, Some(&b"16 OK LSUB"[..]))?;
+ let srv_msg = std::str::from_utf8(read)?;
+ assert!(srv_msg.contains(" INBOX\r\n"));
+ assert!(srv_msg.contains(" archive\r\n"));
+
+ imap.write(&b"17 unsubscribe archive\r\n"[..])?;
+ let read = read_lines(imap, &mut buffer, None)?;
+ assert_eq!(&read[..5], &b"17 OK"[..]);
+
+ imap.write(&b"18 lsub \"\" *\r\n"[..])?;
+ let read = read_lines(imap, &mut buffer, Some(&b"18 OK LSUB"[..]))?;
+ let srv_msg = std::str::from_utf8(read)?;
+ assert!(srv_msg.contains(" INBOX\r\n"));
+ assert!(!srv_msg.contains(" archive\r\n"));
+
+ Ok(())
+}
+
+fn select_inbox(imap: &mut TcpStream) -> Result<()> {
+ let mut buffer: [u8; 6000] = [0; 6000];
+
+ imap.write(&b"20 select inbox\r\n"[..])?;
+ let _read = read_lines(imap, &mut buffer, Some(&b"20 OK"[..]))?;
+
+ Ok(())
+}
+
+fn check(imap: &mut TcpStream) -> Result<()> {
+ let mut buffer: [u8; 1500] = [0; 1500];
+
+ imap.write(&b"21 check\r\n"[..])?;
+ let _read = read_lines(imap, &mut buffer, Some(&b"21 OK"[..]))?;
+
+ Ok(())
+}
+
+fn status_mailbox(imap: &mut TcpStream) -> Result<()> {
+ imap.write(&b"25 STATUS archive (UIDNEXT MESSAGES)\r\n"[..])?;
+ let mut buffer: [u8; 6000] = [0; 6000];
+ let _read = read_lines(imap, &mut buffer, Some(&b"25 OK"[..]))?;
+
+ Ok(())
+}
+
+fn lmtp_handshake(lmtp: &mut TcpStream) -> Result<()> {
+ let mut buffer: [u8; 1500] = [0; 1500];
+
+ let _read = read_lines(lmtp, &mut buffer, None)?;
+ assert_eq!(&buffer[..4], &b"220 "[..]);
+
+ lmtp.write(&b"LHLO example.tld\r\n"[..])?;
+ let _read = read_lines(lmtp, &mut buffer, Some(&b"250 "[..]))?;
+
+ Ok(())
+}
+
+fn lmtp_deliver_email(lmtp: &mut TcpStream, email: &[u8]) -> Result<()> {
+ let mut buffer: [u8; 1500] = [0; 1500];
+
+ lmtp.write(&b"MAIL FROM:<bob@example.tld>\r\n"[..])?;
+ let _read = read_lines(lmtp, &mut buffer, Some(&b"250 2.0.0"[..]))?;
+
+ lmtp.write(&b"RCPT TO:<alice@example.tld>\r\n"[..])?;
+ let _read = read_lines(lmtp, &mut buffer, Some(&b"250 2.1.5"[..]))?;
+
+ lmtp.write(&b"DATA\r\n"[..])?;
+ let _read = read_lines(lmtp, &mut buffer, Some(&b"354 "[..]))?;
+
+ lmtp.write(email)?;
+ lmtp.write(&b"\r\n.\r\n"[..])?;
+ let _read = read_lines(lmtp, &mut buffer, Some(&b"250 2.0.0"[..]))?;
+
+ Ok(())
+}
+
+fn noop_exists(imap: &mut TcpStream) -> Result<()> {
+ let mut buffer: [u8; 6000] = [0; 6000];
+
+ let mut max_retry = 20;
+ loop {
+ max_retry -= 1;
+ imap.write(&b"30 NOOP\r\n"[..])?;
+ let read = read_lines(imap, &mut buffer, Some(&b"30 OK NOOP"[..]))?;
+ let srv_msg = std::str::from_utf8(read)?;
+
+ match (max_retry, srv_msg.contains("* 1 EXISTS")) {
+ (_, true) => break,
+ (0, _) => bail!("no more retry"),
+ _ => (),
+ }
+
+ thread::sleep(SMALL_DELAY);
+ }
+
+ Ok(())
+}
+
+fn fetch_rfc822(imap: &mut TcpStream, ref_mail: &[u8]) -> Result<()> {
+ let mut buffer: [u8; 65535] = [0; 65535];
+ imap.write(&b"40 fetch 1 rfc822\r\n"[..])?;
+
+ let read = read_lines(imap, &mut buffer, Some(&b"40 OK FETCH"[..]))?;
+ let srv_msg = std::str::from_utf8(read)?;
+ let orig_email = std::str::from_utf8(ref_mail)?;
+ assert!(srv_msg.contains(orig_email));
+
+ Ok(())
+}
+
+fn copy_email(imap: &mut TcpStream) -> Result<()> {
+ let mut buffer: [u8; 65535] = [0; 65535];
+ imap.write(&b"45 copy 1 archive\r\n"[..])?;
+ let read = read_lines(imap, &mut buffer, None)?;
+ assert_eq!(&read[..5], &b"45 OK"[..]);
+
+ Ok(())
+}
+
+fn append_email(imap: &mut TcpStream, ref_mail: &[u8]) -> Result<()> {
+ let mut buffer: [u8; 6000] = [0; 6000];
+ assert_ne!(ref_mail.len(), 0);
+ let append_cmd = format!("47 append inbox (\\Seen) {{{}}}\r\n", ref_mail.len());
+ println!("append cmd: {}", append_cmd);
+ imap.write(append_cmd.as_bytes())?;
+
+ // wait for continuation
+ let read = read_lines(imap, &mut buffer, None)?;
+ assert_eq!(read[0], b'+');
+
+ // 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"[..]);
+
+ // noop to force a sync
+ imap.write(&b"48 NOOP\r\n"[..])?;
+ let _read = read_lines(imap, &mut buffer, Some(&b"48 OK NOOP"[..]))?;
+
+ // check it is stored successfully
+ imap.write(&b"49 fetch 2 rfc822.size\r\n"[..])?;
+ let read = read_lines(imap, &mut buffer, Some(&b"49 OK"[..]))?;
+ let expected = format!("* 2 FETCH (RFC822.SIZE {})", ref_mail.len());
+ let expbytes = expected.as_bytes();
+ assert_eq!(&read[..expbytes.len()], expbytes);
+
+ Ok(())
+}
+
+fn add_flags_email(imap: &mut TcpStream) -> Result<()> {
+ let mut buffer: [u8; 1500] = [0; 1500];
+ imap.write(&b"50 store 1 +FLAGS (\\Deleted \\Important)\r\n"[..])?;
+ let _read = read_lines(imap, &mut buffer, Some(&b"50 OK STORE"[..]))?;
+
+ Ok(())
+}
+
+#[allow(dead_code)]
+/// Not yet implemented
+fn search(imap: &mut TcpStream) -> Result<()> {
+ imap.write(&b"55 search text \"OoOoO\"\r\n"[..])?;
+ let mut buffer: [u8; 1500] = [0; 1500];
+ let _read = read_lines(imap, &mut buffer, Some(&b"55 OK SEARCH"[..]))?;
+ Ok(())
+}
+
+fn expunge(imap: &mut TcpStream) -> Result<()> {
+ imap.write(&b"60 expunge\r\n"[..])?;
+ let mut buffer: [u8; 1500] = [0; 1500];
+ let _read = read_lines(imap, &mut buffer, Some(&b"60 OK EXPUNGE"[..]))?;
+
+ Ok(())
+}
+
+fn rename_mailbox(imap: &mut TcpStream) -> Result<()> {
+ imap.write(&b"70 rename archive my-archives\r\n"[..])?;
+ let mut buffer: [u8; 1500] = [0; 1500];
+ let read = read_lines(imap, &mut buffer, None)?;
+ assert_eq!(&read[..5], &b"70 OK"[..]);
+
+ imap.write(&b"71 list \"\" *\r\n"[..])?;
+ let read = read_lines(imap, &mut buffer, Some(&b"71 OK LIST"[..]))?;
+ let srv_msg = std::str::from_utf8(read)?;
+ assert!(!srv_msg.contains(" archive\r\n"));
+ assert!(srv_msg.contains(" INBOX\r\n"));
+ assert!(srv_msg.contains(" my-archives\r\n"));
+
+ Ok(())
+}
+
+fn delete_mailbox(imap: &mut TcpStream) -> Result<()> {
+ imap.write(&b"80 delete my-archives\r\n"[..])?;
+ let mut buffer: [u8; 1500] = [0; 1500];
+ let read = read_lines(imap, &mut buffer, None)?;
+ assert_eq!(&read[..5], &b"80 OK"[..]);
+
+ imap.write(&b"81 list \"\" *\r\n"[..])?;
+ let read = read_lines(imap, &mut buffer, Some(&b"81 OK LIST"[..]))?;
+ let srv_msg = std::str::from_utf8(read)?;
+ assert!(!srv_msg.contains(" archive\r\n"));
+ assert!(!srv_msg.contains(" my-archives\r\n"));
+ assert!(srv_msg.contains(" INBOX\r\n"));
+
+ Ok(())
+}
+
+fn read_lines<'a, F: Read>(
+ reader: &mut F,
+ buffer: &'a mut [u8],
+ stop_marker: Option<&[u8]>,
+) -> Result<&'a [u8]> {
+ let mut nbytes = 0;
+ loop {
+ nbytes += reader.read(&mut buffer[nbytes..])?;
+ //println!("partial read: {}", std::str::from_utf8(&buffer[..nbytes])?);
+ let pre_condition = match stop_marker {
+ None => true,
+ Some(mark) => buffer[..nbytes].windows(mark.len()).any(|w| w == mark),
+ };
+ if pre_condition && &buffer[nbytes - 2..nbytes] == &b"\r\n"[..] {
+ break;
+ }
+ }
+ println!("read: {}", std::str::from_utf8(&buffer[..nbytes])?);
+ Ok(&buffer[..nbytes])
+}
diff --git a/tests/README.md b/tests/instrumentation/README.md
index b479cf4..b479cf4 100644
--- a/tests/README.md
+++ b/tests/instrumentation/README.md
diff --git a/tests/docker-compose.yml b/tests/instrumentation/docker-compose.yml
index 6a112bb..6a112bb 100644
--- a/tests/docker-compose.yml
+++ b/tests/instrumentation/docker-compose.yml
diff --git a/tests/docker/cyrus/Dockerfile b/tests/instrumentation/docker/cyrus/Dockerfile
index 67b4e11..67b4e11 100644
--- a/tests/docker/cyrus/Dockerfile
+++ b/tests/instrumentation/docker/cyrus/Dockerfile
diff --git a/tests/docker/cyrus/entrypoint.sh b/tests/instrumentation/docker/cyrus/entrypoint.sh
index c410e9d..c410e9d 100755
--- a/tests/docker/cyrus/entrypoint.sh
+++ b/tests/instrumentation/docker/cyrus/entrypoint.sh
diff --git a/tests/docker/maddy/Dockerfile b/tests/instrumentation/docker/maddy/Dockerfile
index 341e398..341e398 100644
--- a/tests/docker/maddy/Dockerfile
+++ b/tests/instrumentation/docker/maddy/Dockerfile
diff --git a/tests/docker/maddy/entrypoint.sh b/tests/instrumentation/docker/maddy/entrypoint.sh
index f875624..f875624 100755
--- a/tests/docker/maddy/entrypoint.sh
+++ b/tests/instrumentation/docker/maddy/entrypoint.sh
diff --git a/tests/inject_emails.sh b/tests/instrumentation/inject_emails.sh
index a021170..a021170 100755
--- a/tests/inject_emails.sh
+++ b/tests/instrumentation/inject_emails.sh
diff --git a/tests/rm-mail-parser-expected-struct.py b/tests/instrumentation/rm-mail-parser-expected-struct.py
index 3dbc56e..3dbc56e 100644
--- a/tests/rm-mail-parser-expected-struct.py
+++ b/tests/instrumentation/rm-mail-parser-expected-struct.py
diff --git a/tests/send-to-imap.py b/tests/instrumentation/send-to-imap.py
index df49076..df49076 100644
--- a/tests/send-to-imap.py
+++ b/tests/instrumentation/send-to-imap.py
diff --git a/tests/unix2dos.py b/tests/instrumentation/unix2dos.py
index 4e6c9cf..4e6c9cf 100755
--- a/tests/unix2dos.py
+++ b/tests/instrumentation/unix2dos.py