diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml
index caf4d0d3c..1bb6b4af7 100644
--- a/.github/workflows/ci.yml
+++ b/.github/workflows/ci.yml
@@ -98,6 +98,8 @@ jobs:
uses: swatinem/rust-cache@v2
- name: Tests
+ env:
+ RUST_BACKTRACE: 1
run: cargo test --workspace
- name: Test cargo vendor
diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md
index 5bb9ea6f6..0285b2697 100644
--- a/CONTRIBUTING.md
+++ b/CONTRIBUTING.md
@@ -76,6 +76,29 @@ If you have multiple changes in one PR, create multiple conventional commits, an
[Conventional Commits]: https://www.conventionalcommits.org/
[git-cliff]: https://git-cliff.org/
+### Errors
+
+Delta Chat core mostly uses [`anyhow`](https://docs.rs/anyhow/) errors.
+When using [`Context`](https://docs.rs/anyhow/latest/anyhow/trait.Context.html),
+capitalize it but do not add a full stop as the contexts will be separated by `:`.
+For example:
+```
+.with_context(|| format!("Unable to trash message {msg_id}"))
+```
+
+### Logging
+
+For logging, use `info!`, `warn!` and `error!` macros.
+Log messages should be capitalized and have a full stop in the end. For example:
+```
+info!(context, "Ignoring addition of {added_addr:?} to {chat_id}.");
+```
+
+Format anyhow errors with `{:#}` to print all the contexts like this:
+```
+error!(context, "Failed to set selfavatar timestamp: {err:#}.");
+```
+
### Reviewing
Once a PR has an approval and passes CI, it can be merged.
diff --git a/Cargo.lock b/Cargo.lock
index 08a25afa4..b7493ce1d 100644
--- a/Cargo.lock
+++ b/Cargo.lock
@@ -17,9 +17,9 @@ dependencies = [
[[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",
]
@@ -79,9 +79,9 @@ dependencies = [
[[package]]
name = "allocator-api2"
-version = "0.2.15"
+version = "0.2.16"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "56fc6cf8dc8c4158eed8649f9b8b0ea1518eb62b544fe9490d66fa0b349eafe9"
+checksum = "0942ffc6dcaadf03badf6e6a2d0228460359d5e34b57ccdc720b7382dfbd5ec5"
[[package]]
name = "android-tzdata"
@@ -121,9 +121,9 @@ checksum = "3a30da5c5f2d5e72842e00bcb57657162cdabef0931f40e2deb9b4140440cecd"
[[package]]
name = "anyhow"
-version = "1.0.71"
+version = "1.0.75"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "9c7d0618f0e0b7e8ff11427422b64564d5fb0be1940354bfe2e0529b18a9d9b8"
+checksum = "a4668cab20f66d8d020e1fbc0ebe47217433c1b6c8f2040faf858554e394ace6"
dependencies = [
"backtrace",
]
@@ -159,7 +159,7 @@ dependencies = [
"num-traits",
"rusticata-macros",
"thiserror",
- "time 0.3.22",
+ "time 0.3.24",
]
[[package]]
@@ -187,9 +187,9 @@ dependencies = [
[[package]]
name = "async-channel"
-version = "1.8.0"
+version = "1.9.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "cf46fee83e5ccffc220104713af3292ff9bc7c64c7de289f66dae8e38d826833"
+checksum = "81953c529336010edd6d8e358f886d9581267795c61b19475b71314bffa46d35"
dependencies = [
"concurrent-queue",
"event-listener",
@@ -216,7 +216,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "936c1b580be4373b48c9c687e0c79285441664398354df28d0860087cac0c069"
dependencies = [
"async-channel",
- "base64 0.21.2",
+ "base64 0.21.3",
"bytes",
"chrono",
"futures",
@@ -271,13 +271,13 @@ dependencies = [
[[package]]
name = "async-trait"
-version = "0.1.70"
+version = "0.1.72"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "79fa67157abdfd688a259b6648808757db9347af834624f27ec646da976aee5d"
+checksum = "cc6dde6e4ed435a4c1ee4e73592f5ba9da2151af10076cc04858746af9352d09"
dependencies = [
"proc-macro2",
"quote",
- "syn 2.0.23",
+ "syn 2.0.29",
]
[[package]]
@@ -308,7 +308,7 @@ checksum = "3b829e4e32b91e643de6eafe82b1d90675f5874230191a4ffbc1b336dec4d6bf"
dependencies = [
"async-trait",
"axum-core",
- "base64 0.21.2",
+ "base64 0.21.3",
"bitflags 1.3.2",
"bytes",
"futures-util",
@@ -354,9 +354,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",
@@ -399,9 +399,9 @@ checksum = "9e1b586273c5702936fe7b7d6896644d8be71e6314cfe09d3167c95f712589e8"
[[package]]
name = "base64"
-version = "0.21.2"
+version = "0.21.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "604178f6c5c21f02dc555784810edfb88d34ac2c73b2eae109655649ee73ce3d"
+checksum = "414dcefbc63d77c526a76b3afcf6fbb9b5e2791c19c3aa2297733208750c6e53"
[[package]]
name = "base64ct"
@@ -438,9 +438,9 @@ checksum = "630be753d4e58660abd17930c71b647fe46c27ea6b63cc59e1e3851406972e42"
[[package]]
name = "blake3"
-version = "1.4.0"
+version = "1.4.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "729b71f35bd3fa1a4c86b85d32c8b9069ea7fe14f7a53cfabb65f62d4265b888"
+checksum = "199c42ab6972d92c9f8995f086273d25c42fc0f7b2a1fcefba465c1352d25ba5"
dependencies = [
"arrayref",
"arrayvec",
@@ -489,9 +489,9 @@ dependencies = [
[[package]]
name = "brotli"
-version = "3.3.4"
+version = "3.4.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "a1a0b1dbcc8ae29329621f8d4f0d835787c1c38bb1401979b49d13b0b305ff68"
+checksum = "516074a47ef4bce09577a3b379392300159ce5b1ba2e501ff1c819950066100f"
dependencies = [
"alloc-no-stdlib",
"alloc-stdlib",
@@ -500,9 +500,9 @@ dependencies = [
[[package]]
name = "brotli-decompressor"
-version = "2.3.4"
+version = "2.5.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "4b6561fd3f895a11e8f72af2cb7d22e08366bebc2b6b57f7744c4bda27034744"
+checksum = "da74e2b81409b1b743f8f0c62cc6254afefb8b8e50bbfe3735550f7aeefa3448"
dependencies = [
"alloc-no-stdlib",
"alloc-stdlib",
@@ -510,9 +510,9 @@ dependencies = [
[[package]]
name = "bstr"
-version = "1.5.0"
+version = "1.6.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "a246e68bb43f6cd9db24bea052a53e40405417c5fb372e3d1a8a7f770a564ef5"
+checksum = "6798148dccfbff0fae41c7574d2fa8f1ef3492fba0face179de5d8d447d67b05"
dependencies = [
"memchr",
"serde",
@@ -564,18 +564,18 @@ dependencies = [
[[package]]
name = "camino"
-version = "1.1.4"
+version = "1.1.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "c530edf18f37068ac2d977409ed5cd50d53d73bc653c7647b48eb78976ac9ae2"
+checksum = "c59e92b5a388f549b863a7bea62612c09f24c8393560709a54558a9abdfb3b9c"
dependencies = [
"serde",
]
[[package]]
name = "cargo-platform"
-version = "0.1.2"
+version = "0.1.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "cbdb825da8a5df079a43676dbe042702f1707b1109f713a01420fbb4cc71fa27"
+checksum = "2cfa25e60aea747ec7e1124f238816749faa93759c6ff5b31f1ccdda137f4479"
dependencies = [
"serde",
]
@@ -610,9 +610,12 @@ dependencies = [
[[package]]
name = "cc"
-version = "1.0.79"
+version = "1.0.80"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "50d30906286121d95be3d479533b458f87493b30a4b5f79a607db8f5d11aa91f"
+checksum = "51f1226cd9da55587234753d1245dd5b132343ea240f26b6a9003d68706141ba"
+dependencies = [
+ "libc",
+]
[[package]]
name = "cfb-mode"
@@ -641,9 +644,9 @@ dependencies = [
[[package]]
name = "chrono"
-version = "0.4.26"
+version = "0.4.28"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "ec837a71355b28f6556dbd569b37b3f363091c0bd4b2e735674521b4c5fd9bc5"
+checksum = "95ed24df0632f708f5f6d8082675bef2596f7084dee3dd55f632290bf35bfe0f"
dependencies = [
"android-tzdata",
"iana-time-zone",
@@ -651,7 +654,7 @@ dependencies = [
"num-traits",
"time 0.1.45",
"wasm-bindgen",
- "winapi",
+ "windows-targets 0.48.1",
]
[[package]]
@@ -693,18 +696,18 @@ dependencies = [
[[package]]
name = "clap"
-version = "4.3.10"
+version = "4.3.19"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "384e169cc618c613d5e3ca6404dda77a8685a63e08660dcc64abaf7da7cb0c7a"
+checksum = "5fd304a20bff958a57f04c4e96a2e7594cc4490a0e809cbd48bb6437edaa452d"
dependencies = [
"clap_builder",
]
[[package]]
name = "clap_builder"
-version = "4.3.10"
+version = "4.3.19"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "ef137bbe35aab78bdb468ccfba75a5f4d8321ae011d34063770780545176af2d"
+checksum = "01c6a3f08f1fe5662a35cfe393aec09c4df95f60ee93b7556505260f75eee9e1"
dependencies = [
"anstyle",
"clap_lex",
@@ -750,9 +753,9 @@ dependencies = [
[[package]]
name = "const-oid"
-version = "0.9.3"
+version = "0.9.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "6340df57935414636969091153f35f68d9f00bbc8fb4a9c6054706c213e6c6bc"
+checksum = "795bc6e66a8e340f075fcf6227e417a2dc976b92b91f3cdc778bb858778b6747"
[[package]]
name = "const_format"
@@ -776,9 +779,9 @@ dependencies = [
[[package]]
name = "constant_time_eq"
-version = "0.2.6"
+version = "0.3.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "21a53c0a4d288377e7415b53dcfc3c04da5cdc2cc95c8d5ac178b58f0b861ad6"
+checksum = "f7144d30dcf0fafbce74250a3963025d8d52177934239851c917d29f1df280c2"
[[package]]
name = "convert_case"
@@ -810,9 +813,9 @@ checksum = "e496a50fda8aacccc86d7529e2c1e0892dbd0f898a6b5645b5561b89c3210efa"
[[package]]
name = "cpufeatures"
-version = "0.2.8"
+version = "0.2.9"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "03e69e28e9f7f77debdedbaafa2866e1de9ba56df55a8bd7cfc724c25a09987c"
+checksum = "a17b76ff3a4162b0b27f354a0c87015ddad39d35f9c0c36607a3bdd175dde1f1"
dependencies = [
"libc",
]
@@ -947,16 +950,6 @@ dependencies = [
"typenum",
]
-[[package]]
-name = "ctor"
-version = "0.1.26"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "6d2301688392eb071b0bf1a37be05c469d3cc4dbbd95df672fe28ab021e6a096"
-dependencies = [
- "quote",
- "syn 1.0.109",
-]
-
[[package]]
name = "curve25519-dalek"
version = "3.2.0"
@@ -995,7 +988,7 @@ checksum = "83fdaf97f4804dcebfa5862639bc9ce4121e82140bec2a987ac5140294865b5b"
dependencies = [
"proc-macro2",
"quote",
- "syn 2.0.23",
+ "syn 2.0.29",
]
[[package]]
@@ -1103,7 +1096,7 @@ dependencies = [
"async-smtp",
"async_zip",
"backtrace",
- "base64 0.21.2",
+ "base64 0.21.3",
"brotli",
"chrono",
"criterion",
@@ -1112,6 +1105,7 @@ dependencies = [
"encoded-words",
"escaper",
"fast-socks5",
+ "fd-lock",
"format-flowed",
"futures",
"futures-lite",
@@ -1126,7 +1120,7 @@ dependencies = [
"log",
"mailparse",
"mime",
- "num-derive 0.4.0",
+ "num-derive",
"num-traits",
"num_cpus",
"once_cell",
@@ -1148,7 +1142,7 @@ dependencies = [
"serde",
"serde_json",
"sha-1",
- "sha2 0.10.7",
+ "sha2 0.10.8",
"smallvec",
"strum",
"strum_macros",
@@ -1174,7 +1168,7 @@ dependencies = [
"anyhow",
"async-channel",
"axum",
- "base64 0.21.2",
+ "base64 0.21.3",
"deltachat",
"env_logger",
"futures",
@@ -1228,7 +1222,7 @@ name = "deltachat_derive"
version = "2.0.0"
dependencies = [
"quote",
- "syn 2.0.23",
+ "syn 2.0.29",
]
[[package]]
@@ -1298,6 +1292,12 @@ dependencies = [
"syn 1.0.109",
]
+[[package]]
+name = "deranged"
+version = "0.3.6"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "8810e7e2cf385b1e9b50d68264908ec367ba642c96d02edfe61c39e88e2a3c01"
+
[[package]]
name = "derive_builder"
version = "0.12.0"
@@ -1428,7 +1428,7 @@ checksum = "487585f4d0c6655fe74905e2504d8ad6908e4db67f744eb140876906c2f3175d"
dependencies = [
"proc-macro2",
"quote",
- "syn 2.0.23",
+ "syn 2.0.29",
]
[[package]]
@@ -1456,9 +1456,9 @@ dependencies = [
[[package]]
name = "dyn-clone"
-version = "1.0.11"
+version = "1.0.12"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "68b0cf012f1230e43cd00ebb729c6bb58707ecfa8ad08b52ef3a4ccd2697fc30"
+checksum = "304e6508efa593091e97a9abbc10f90aa7ca635b6d2784feff3c89d41dd12272"
[[package]]
name = "ecdsa"
@@ -1474,9 +1474,9 @@ dependencies = [
[[package]]
name = "ecdsa"
-version = "0.16.7"
+version = "0.16.8"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "0997c976637b606099b9985693efa3581e84e41f5c11ba5255f88711058ad428"
+checksum = "a4b1e0c257a9e9f25f90ff76d7a68360ed497ee519c8e428d1825ef0000799d4"
dependencies = [
"der 0.7.7",
"digest 0.10.7",
@@ -1530,7 +1530,7 @@ dependencies = [
"curve25519-dalek 4.0.0-rc.3",
"ed25519 2.2.1",
"serde",
- "sha2 0.10.7",
+ "sha2 0.10.8",
"zeroize",
]
@@ -1548,9 +1548,9 @@ dependencies = [
[[package]]
name = "either"
-version = "1.8.1"
+version = "1.9.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "7fcaabb2fef8c910e7f4c7ce9f67a1283a1715879a7c230ca9d6d1ae31f16d91"
+checksum = "a26ae43d7bcc3b814de94796a5e736d4029efb0ee900c12e2d54c993ad1a1e07"
[[package]]
name = "elliptic-curve"
@@ -1587,7 +1587,7 @@ dependencies = [
"pem-rfc7468 0.7.0",
"pkcs8 0.10.2",
"rand_core 0.6.4",
- "sec1 0.7.2",
+ "sec1 0.7.3",
"subtle",
"zeroize",
]
@@ -1715,7 +1715,7 @@ dependencies = [
"heck",
"proc-macro2",
"quote",
- "syn 2.0.23",
+ "syn 2.0.29",
]
[[package]]
@@ -1728,7 +1728,7 @@ dependencies = [
"num-traits",
"proc-macro2",
"quote",
- "syn 2.0.23",
+ "syn 2.0.29",
]
[[package]]
@@ -1746,15 +1746,15 @@ dependencies = [
[[package]]
name = "equivalent"
-version = "1.0.0"
+version = "1.0.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "88bffebc5d80432c9b140ee17875ff173a8ab62faad5b257da912bd2f6c1c0a1"
+checksum = "5443807d6dff69373d433ab9ef5378ad8df50ca6298caf15de6e52e24aaf54d5"
[[package]]
name = "errno"
-version = "0.3.1"
+version = "0.3.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "4bcfec3a70f97c962c307b2d2c56e358cf1d00b558d74262b5f929ee8cc7e73a"
+checksum = "6b30f669a7961ef1631673d2766cc92f52d64f7ef354d4fe0ddfd30ed52f0f4f"
dependencies = [
"errno-dragonfly",
"libc",
@@ -1839,6 +1839,12 @@ dependencies = [
"instant",
]
+[[package]]
+name = "fastrand"
+version = "2.0.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "6999dc1837253364c2ebb0704ba97994bd874e8f195d665c50b7548f6ea92764"
+
[[package]]
name = "fd-lock"
version = "3.0.13"
@@ -1846,7 +1852,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ef033ed5e9bad94e55838ca0ca906db0e043f517adda0c8b79c7a8c66c93c1b5"
dependencies = [
"cfg-if",
- "rustix 0.38.2",
+ "rustix",
"windows-sys 0.48.0",
]
@@ -2008,7 +2014,7 @@ version = "1.13.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "49a9d51ce47660b1e808d3c990b4709f2f415d928835a17dfd16991515c46bce"
dependencies = [
- "fastrand",
+ "fastrand 1.9.0",
"futures-core",
"futures-io",
"memchr",
@@ -2025,7 +2031,7 @@ checksum = "89ca545a94061b6365f2c7355b4b32bd20df3ff95f02da9329b34ccc3bd6ee72"
dependencies = [
"proc-macro2",
"quote",
- "syn 2.0.23",
+ "syn 2.0.29",
]
[[package]]
@@ -2105,9 +2111,9 @@ dependencies = [
[[package]]
name = "gimli"
-version = "0.27.3"
+version = "0.28.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "b6c80984affa11d98d1b88b66ac8853f143217b399d3c74116778ff8fdb4ed2e"
+checksum = "6fb8d784f27acf97159b40fc4db5ecd8aa23b9ad5ef69cdd136d3bc80665f0c0"
[[package]]
name = "group"
@@ -2432,9 +2438,9 @@ dependencies = [
[[package]]
name = "image"
-version = "0.24.6"
+version = "0.24.7"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "527909aa81e20ac3a44803521443a765550f09b5130c2c2fa1ea59c2f8f50a3a"
+checksum = "6f3dfdbdd72063086ff443e297b61695500514b1e41095b6fb9a5ab48a70a711"
dependencies = [
"bytemuck",
"byteorder",
@@ -2493,17 +2499,6 @@ dependencies = [
"cfg-if",
]
-[[package]]
-name = "io-lifetimes"
-version = "1.0.11"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "eae7b9aee968036d54dce06cebaefd919e4472e753296daccd6d344e3e2df0c2"
-dependencies = [
- "hermit-abi",
- "libc",
- "windows-sys 0.48.0",
-]
-
[[package]]
name = "ipconfig"
version = "0.3.2"
@@ -2513,7 +2508,7 @@ dependencies = [
"socket2 0.5.3",
"widestring",
"windows-sys 0.48.0",
- "winreg 0.50.0",
+ "winreg",
]
[[package]]
@@ -2530,7 +2525,7 @@ checksum = "e4fb9858c8cd3dd924a5da5bc511363845a9bcfdfac066bb2ef8454eb6111546"
dependencies = [
"abao",
"anyhow",
- "base64 0.21.2",
+ "base64 0.21.3",
"blake3",
"bytes",
"default-net",
@@ -2568,12 +2563,12 @@ dependencies = [
[[package]]
name = "is-terminal"
-version = "0.4.8"
+version = "0.4.9"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "24fddda5af7e54bf7da53067d6e802dbcc381d0a8eef629df528e3ebf68755cb"
+checksum = "cb0889898416213fab133e1d33a0e5858a48177452750691bde3666d0fdbaf8b"
dependencies = [
"hermit-abi",
- "rustix 0.38.2",
+ "rustix",
"windows-sys 0.48.0",
]
@@ -2588,9 +2583,9 @@ dependencies = [
[[package]]
name = "itoa"
-version = "1.0.8"
+version = "1.0.9"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "62b02a5381cc465bd3041d84623d0fa3b66738b52b8e2fc3bab8ad63ab032f4a"
+checksum = "af150ab688ff2122fcef229be89cb50dd66af9e01a4ff320cc137eecc9bacc38"
[[package]]
name = "jpeg-decoder"
@@ -2690,15 +2685,9 @@ checksum = "0717cef1bc8b636c6e1c1bbdefc09e6322da8a9321966e8928ef80d20f7f770f"
[[package]]
name = "linux-raw-sys"
-version = "0.3.8"
+version = "0.4.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "ef53942eb7bf7ff43a617b3e2c1c4a5ecf5944a7c1bc12d7ee39bbb15e5c1519"
-
-[[package]]
-name = "linux-raw-sys"
-version = "0.4.3"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "09fc20d2ca12cb9f044c93e3bd6d32d523e6e2ec3db4f7b2939cd99026ecd3f0"
+checksum = "57bcfdad1b858c2db7c38303a6d2ad4dfaf5eb53dfeb0910128b2c26d6158503"
[[package]]
name = "lock_api"
@@ -2712,9 +2701,9 @@ dependencies = [
[[package]]
name = "log"
-version = "0.4.19"
+version = "0.4.20"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "b06a4cde4c0f271a446782e3eff8de789548ce57dbc8eca9292c27f4a42004b4"
+checksum = "b5e6163cb8c49088c2c36f57875e58ccd8c87c7427f7fbd50ea6710b2f3f2e8f"
[[package]]
name = "lru-cache"
@@ -2748,14 +2737,14 @@ version = "0.1.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8263075bb86c5a1b1427b5ae862e8889656f126e9f77c484496e8b47cf5c5558"
dependencies = [
- "regex-automata",
+ "regex-automata 0.1.10",
]
[[package]]
name = "matchit"
-version = "0.7.0"
+version = "0.7.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "b87248edafb776e59e6ee64a79086f65890d3510f2c656c000bf2a7e8a0aea40"
+checksum = "67827e6ea8ee8a7c4a72227ef4fc08957040acffdb5f122733b24fa12daff41b"
[[package]]
name = "md-5"
@@ -2774,9 +2763,9 @@ checksum = "df39d232f5c40b0891c10216992c2f250c054105cb1e56f0fc9032db6203ecc1"
[[package]]
name = "memchr"
-version = "2.5.0"
+version = "2.6.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "2dffe52ecf27772e601905b7522cb4ef790d2cc203488bbd0e2fe85fcb74566d"
+checksum = "8f232d6ef707e1956a43342693d2a31e72989554d58299d7a88738cc95b0d35c"
[[package]]
name = "memoffset"
@@ -2981,17 +2970,6 @@ dependencies = [
"zeroize",
]
-[[package]]
-name = "num-derive"
-version = "0.3.3"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "876a53fff98e03a936a674b29568b0e605f06b29372c2489ff4de23f1949743d"
-dependencies = [
- "proc-macro2",
- "quote",
- "syn 1.0.109",
-]
-
[[package]]
name = "num-derive"
version = "0.4.0"
@@ -3000,7 +2978,7 @@ checksum = "9e6a0fd4f737c707bd9086cc16c925f294943eb62eb71499e9fd4cf71f8b9f4e"
dependencies = [
"proc-macro2",
"quote",
- "syn 2.0.23",
+ "syn 2.0.29",
]
[[package]]
@@ -3037,9 +3015,9 @@ dependencies = [
[[package]]
name = "num-traits"
-version = "0.2.15"
+version = "0.2.16"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "578ede34cf02f8924ab9447f50c28075b4d3e5b269972345e7e0372b38c6cdcd"
+checksum = "f30b0abd723be7e2ffca1272140fac1a2f084c77ec3e123c192b66af1ee9e6c2"
dependencies = [
"autocfg",
"libm",
@@ -3057,9 +3035,9 @@ dependencies = [
[[package]]
name = "object"
-version = "0.31.1"
+version = "0.32.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "8bda667d9f2b5051b8833f59f3bf748b28ef54f850f4fcb389a252aa383866d1"
+checksum = "77ac5bbd07aea88c60a577a1ce218075ffd59208b2d7ca97adf9bfc5aeb21ebe"
dependencies = [
"memchr",
]
@@ -3114,7 +3092,7 @@ checksum = "a948666b637a0f465e8564c73e89d4dde00d72d4d473cc972f390fc3dcee7d9c"
dependencies = [
"proc-macro2",
"quote",
- "syn 2.0.23",
+ "syn 2.0.29",
]
[[package]]
@@ -3162,15 +3140,6 @@ dependencies = [
"winapi",
]
-[[package]]
-name = "output_vt100"
-version = "0.1.3"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "628223faebab4e3e40667ee0b2336d34a5b960ff60ea743ddfdbcf7770bcfb66"
-dependencies = [
- "winapi",
-]
-
[[package]]
name = "overload"
version = "0.1.1"
@@ -3185,7 +3154,7 @@ checksum = "51f44edd08f51e2ade572f141051021c5af22677e42b7dd28a88155151c33594"
dependencies = [
"ecdsa 0.14.8",
"elliptic-curve 0.12.3",
- "sha2 0.10.7",
+ "sha2 0.10.8",
]
[[package]]
@@ -3194,10 +3163,10 @@ version = "0.13.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c9863ad85fa8f4460f9c48cb909d38a0d689dba1f6f6988a5e3e0d31071bcd4b"
dependencies = [
- "ecdsa 0.16.7",
+ "ecdsa 0.16.8",
"elliptic-curve 0.13.5",
"primeorder",
- "sha2 0.10.7",
+ "sha2 0.10.8",
]
[[package]]
@@ -3208,7 +3177,7 @@ checksum = "dfc8c5bf642dde52bb9e87c0ecd8ca5a76faac2eeed98dedb7c717997e1080aa"
dependencies = [
"ecdsa 0.14.8",
"elliptic-curve 0.12.3",
- "sha2 0.10.7",
+ "sha2 0.10.8",
]
[[package]]
@@ -3217,10 +3186,10 @@ version = "0.13.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "70786f51bcc69f6a4c0360e063a4cac5419ef7c5cd5b3c99ad70f3be5ba79209"
dependencies = [
- "ecdsa 0.16.7",
+ "ecdsa 0.16.8",
"elliptic-curve 0.13.5",
"primeorder",
- "sha2 0.10.7",
+ "sha2 0.10.8",
]
[[package]]
@@ -3254,9 +3223,9 @@ dependencies = [
[[package]]
name = "paste"
-version = "1.0.13"
+version = "1.0.14"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "b4b27ab7be369122c218afc2079489cdcb4b517c0a3fc386ff11e1fedfcc2b35"
+checksum = "de3145af08024dea9fa9914f381a17b8fc6034dfb00f3a84013f7ff43f29ed4c"
[[package]]
name = "pem"
@@ -3293,12 +3262,12 @@ checksum = "9b2a4787296e9989611394c33f193f676704af1686e70b8f8033ab5ba9a35a94"
[[package]]
name = "pgp"
-version = "0.10.1"
+version = "0.10.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "37a79d6411154d1a9908e7a2c4bac60a5742f6125823c2c30780c7039aef02f0"
+checksum = "27e1f8e085bfa9b85763fe3ddaacbe90a09cd847b3833129153a6cb063bbe132"
dependencies = [
"aes",
- "base64 0.21.2",
+ "base64 0.21.3",
"bitfield",
"block-padding",
"blowfish",
@@ -3311,6 +3280,7 @@ dependencies = [
"chrono",
"cipher",
"crc24",
+ "curve25519-dalek 4.0.0-rc.3",
"derive_builder",
"des",
"digest 0.10.7",
@@ -3324,15 +3294,15 @@ dependencies = [
"md-5",
"nom",
"num-bigint-dig",
- "num-derive 0.3.3",
+ "num-derive",
"num-traits",
"p256 0.13.2",
"p384 0.13.0",
"rand 0.8.5",
"ripemd",
- "rsa 0.9.0-pre.2",
+ "rsa 0.9.2",
"sha1",
- "sha2 0.10.7",
+ "sha2 0.10.8",
"sha3",
"signature 2.1.0",
"smallvec",
@@ -3359,7 +3329,7 @@ checksum = "ec2e072ecce94ec471b13398d5402c188e76ac03cf74dd1a975161b23a3f6d9c"
dependencies = [
"proc-macro2",
"quote",
- "syn 2.0.23",
+ "syn 2.0.29",
]
[[package]]
@@ -3472,15 +3442,15 @@ dependencies = [
[[package]]
name = "portable-atomic"
-version = "1.3.3"
+version = "1.4.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "767eb9f07d4a5ebcb39bbf2d452058a93c011373abf6832e24194a1c3f004794"
+checksum = "f32154ba0af3a075eefa1eda8bb414ee928f62303a54ea85b8d6638ff1a6ee9e"
[[package]]
name = "postcard"
-version = "1.0.4"
+version = "1.0.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "cfa512cd0d087cc9f99ad30a1bf64795b67871edbead083ffc3a4dfafa59aa00"
+checksum = "c9ee729232311d3cd113749948b689627618133b1c5012b77342c1950b25eaeb"
dependencies = [
"cobs",
"const_format",
@@ -3507,13 +3477,11 @@ checksum = "5b40af805b3121feab8a3c29f04d8ad262fa8e0561883e7653e024ae4479e6de"
[[package]]
name = "pretty_assertions"
-version = "1.3.0"
+version = "1.4.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "a25e9bcb20aa780fd0bb16b72403a9064d6b3f22f026946029acb941a50af755"
+checksum = "af7cee1a6c8a5b9208b3cb1061f10c0cb689087b3d8ce85fb9d2dd7a29b6ba66"
dependencies = [
- "ctor",
"diff",
- "output_vt100",
"yansi",
]
@@ -3562,27 +3530,26 @@ dependencies = [
[[package]]
name = "proc-macro2"
-version = "1.0.63"
+version = "1.0.66"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "7b368fba921b0dce7e60f5e04ec15e565b3303972b42bcfde1d0713b881959eb"
+checksum = "18fb31db3f9bddb2ea821cde30a9f70117e3f119938b5ee630b7403aa6e2ead9"
dependencies = [
"unicode-ident",
]
[[package]]
name = "proptest"
-version = "1.2.0"
+version = "1.3.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "4e35c06b98bf36aba164cc17cb25f7e232f5c4aeea73baa14b8a9f0d92dbfa65"
+checksum = "7c003ac8c77cb07bb74f5f198bce836a689bcd5a42574612bf14d17bfd08c20e"
dependencies = [
- "bitflags 1.3.2",
- "byteorder",
+ "bitflags 2.3.3",
"lazy_static",
"num-traits",
"rand 0.8.5",
"rand_chacha 0.3.1",
"rand_xorshift",
- "regex-syntax 0.6.29",
+ "regex-syntax 0.7.5",
"unarray",
]
@@ -3618,9 +3585,9 @@ checksum = "a1d01941d82fa2ab50be1e79e6714289dd7cde78eba4c074bc5a4374f650dfe0"
[[package]]
name = "quick-xml"
-version = "0.29.0"
+version = "0.30.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "81b9228215d82c7b61490fec1de287136b5de6f5700f6e58ea9ad61a7964ca51"
+checksum = "eff6510e86862b57b210fd8cbe8ed3f0d7d600b9c2863cd4549a2e033c66e956"
dependencies = [
"memchr",
]
@@ -3675,9 +3642,9 @@ dependencies = [
[[package]]
name = "quote"
-version = "1.0.29"
+version = "1.0.33"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "573015e8ab27661678357f27dc26460738fd2b6c86e46f386fde94cb5d913105"
+checksum = "5267fca4496028628a95160fc423a33e8b2e6af8a5302579e322e4b520293cae"
dependencies = [
"proc-macro2",
]
@@ -3812,7 +3779,7 @@ checksum = "ffbe84efe2f38dea12e9bfc1f65377fdf03e53a18cb3b995faedf7934c7e785b"
dependencies = [
"pem",
"ring",
- "time 0.3.22",
+ "time 0.3.24",
"yasna",
]
@@ -3847,13 +3814,14 @@ dependencies = [
[[package]]
name = "regex"
-version = "1.8.4"
+version = "1.9.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "d0ab3ca65655bb1e41f2a8c8cd662eb4fb035e67c3f78da1d61dffe89d07300f"
+checksum = "ebee201405406dbf528b8b672104ae6d6d63e6d118cb10e4d51abbc7b58044ff"
dependencies = [
"aho-corasick",
"memchr",
- "regex-syntax 0.7.2",
+ "regex-automata 0.3.9",
+ "regex-syntax 0.7.5",
]
[[package]]
@@ -3865,6 +3833,17 @@ dependencies = [
"regex-syntax 0.6.29",
]
+[[package]]
+name = "regex-automata"
+version = "0.3.9"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "59b23e92ee4318893fa3fe3e6fb365258efbfe6ac6ab30f090cdcbb7aa37efa9"
+dependencies = [
+ "aho-corasick",
+ "memchr",
+ "regex-syntax 0.7.5",
+]
+
[[package]]
name = "regex-syntax"
version = "0.6.29"
@@ -3873,17 +3852,17 @@ checksum = "f162c6dd7b008981e4d40210aca20b4bd0f9b60ca9271061b07f78537722f2e1"
[[package]]
name = "regex-syntax"
-version = "0.7.2"
+version = "0.7.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "436b050e76ed2903236f032a59761c1eb99e1b0aead2c257922771dab1fc8c78"
+checksum = "dbb5fb1acd8a1a18b3dd5be62d25485eb770e05afb408a9627d14d451bae12da"
[[package]]
name = "reqwest"
-version = "0.11.18"
+version = "0.11.20"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "cde824a14b7c14f85caff81225f411faacc04a2013f41670f41443742b1c1c55"
+checksum = "3e9ad3fe7488d7e34558a2033d45a0c90b72d97b4f80705666fea71472e2e6a1"
dependencies = [
- "base64 0.21.2",
+ "base64 0.21.3",
"bytes",
"encoding_rs",
"futures-core",
@@ -3911,7 +3890,7 @@ dependencies = [
"wasm-bindgen",
"wasm-bindgen-futures",
"web-sys",
- "winreg 0.10.1",
+ "winreg",
]
[[package]]
@@ -3992,9 +3971,9 @@ dependencies = [
[[package]]
name = "rsa"
-version = "0.9.0-pre.2"
+version = "0.9.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "65db0998ad35adcaca498b7358992e088ee16cc783fe6fb899da203e113a63e5"
+checksum = "6ab43bb47d23c1a631b4b680199a45255dce26fa9ab2fa902581f624ff13e6a8"
dependencies = [
"byteorder",
"const-oid",
@@ -4007,6 +3986,7 @@ dependencies = [
"pkcs8 0.10.2",
"rand_core 0.6.4",
"signature 2.1.0",
+ "spki 0.7.2",
"subtle",
"zeroize",
]
@@ -4063,28 +4043,14 @@ dependencies = [
[[package]]
name = "rustix"
-version = "0.37.22"
+version = "0.38.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "8818fa822adcc98b18fedbb3632a6a33213c070556b5aa7c4c8cc21cff565c4c"
-dependencies = [
- "bitflags 1.3.2",
- "errno",
- "io-lifetimes",
- "libc",
- "linux-raw-sys 0.3.8",
- "windows-sys 0.48.0",
-]
-
-[[package]]
-name = "rustix"
-version = "0.38.2"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "aabcb0461ebd01d6b79945797c27f8529082226cb630a9865a71870ff63532a4"
+checksum = "0a962918ea88d644592894bc6dc55acc6c0956488adcebbfb6e273506b7fd6e5"
dependencies = [
"bitflags 2.3.3",
"errno",
"libc",
- "linux-raw-sys 0.4.3",
+ "linux-raw-sys",
"windows-sys 0.48.0",
]
@@ -4117,14 +4083,14 @@ version = "1.0.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "2d3987094b1d07b653b7dfdc3f70ce9a1da9c51ac18c1b06b662e4f9a0e9f4b2"
dependencies = [
- "base64 0.21.2",
+ "base64 0.21.3",
]
[[package]]
name = "rustversion"
-version = "1.0.13"
+version = "1.0.14"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "dc31bd9b61a32c31f9650d18add92aa83a49ba979c143eefd27fe7177b05bd5f"
+checksum = "7ffc183a10b4478d04cbbbfc96d0873219d962dd5accaff2ffbd4ceb7df837f4"
[[package]]
name = "rustyline"
@@ -4151,9 +4117,9 @@ dependencies = [
[[package]]
name = "ryu"
-version = "1.0.14"
+version = "1.0.15"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "fe232bdf6be8c8de797b22184ee71118d63780ea42ac85b61d1baa6d3b782ae9"
+checksum = "1ad4cc8da4ef723ed60bced201181d83791ad433213d8c24efffda1eec85d741"
[[package]]
name = "safemem"
@@ -4172,9 +4138,9 @@ dependencies = [
[[package]]
name = "sanitize-filename"
-version = "0.4.0"
+version = "0.5.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "08c502bdb638f1396509467cb0580ef3b29aa2a45c5d43e5d84928241280296c"
+checksum = "2ed72fbaf78e6f2d41744923916966c4fbe3d7c74e3037a8ee482f1115572603"
dependencies = [
"lazy_static",
"regex",
@@ -4191,9 +4157,9 @@ dependencies = [
[[package]]
name = "schemars"
-version = "0.8.12"
+version = "0.8.13"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "02c613288622e5f0c3fdc5dbd4db1c5fbe752746b1d1a56a0630b78fd00de44f"
+checksum = "763f8cd0d4c71ed8389c90cb8100cba87e763bd01a8e614d4f0af97bcd50a161"
dependencies = [
"dyn-clone",
"schemars_derive",
@@ -4203,9 +4169,9 @@ dependencies = [
[[package]]
name = "schemars_derive"
-version = "0.8.12"
+version = "0.8.13"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "109da1e6b197438deb6db99952990c7f959572794b80ff93707d55a232545e7c"
+checksum = "ec0f696e21e10fa546b7ffb1c9672c6de8fbc7a81acf59524386d8639bf12737"
dependencies = [
"proc-macro2",
"quote",
@@ -4215,9 +4181,9 @@ dependencies = [
[[package]]
name = "scopeguard"
-version = "1.1.0"
+version = "1.2.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "d29ab0c6d3fc0ee92fe66e2d99f700eab17a8d57d1c1d3b748380fb20baa78cd"
+checksum = "94143f37725109f92c262ed2cf5e59bce7498c01bcc1502d7b9afe439a4e9f49"
[[package]]
name = "sct"
@@ -4245,9 +4211,9 @@ dependencies = [
[[package]]
name = "sec1"
-version = "0.7.2"
+version = "0.7.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "f0aec48e813d6b90b15f0b8948af3c63483992dee44c03e9930b3eebdabe046e"
+checksum = "d3e97a565f76233a6003f9f5c54be1d9c5bdfa3eccfb189469f11ec4901c47dc"
dependencies = [
"base16ct 0.2.0",
"der 0.7.7",
@@ -4259,9 +4225,9 @@ dependencies = [
[[package]]
name = "security-framework"
-version = "2.9.1"
+version = "2.9.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "1fc758eb7bffce5b308734e9b0c1468893cae9ff70ebf13e7090be8dcbcc83a8"
+checksum = "05b64fb303737d99b81884b2c63433e9ae28abebe5eb5045dcdd175dc2ecf4de"
dependencies = [
"bitflags 1.3.2",
"core-foundation",
@@ -4272,9 +4238,9 @@ dependencies = [
[[package]]
name = "security-framework-sys"
-version = "2.9.0"
+version = "2.9.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "f51d0c0d83bec45f16480d0ce0058397a69e48fcdc52d1dc8855fb68acbd31a7"
+checksum = "e932934257d3b408ed8f30db49d85ea163bfe74961f017f405b025af298f0c7a"
dependencies = [
"core-foundation-sys",
"libc",
@@ -4288,18 +4254,18 @@ checksum = "4c309e515543e67811222dbc9e3dd7e1056279b782e1dacffe4242b718734fb6"
[[package]]
name = "semver"
-version = "1.0.17"
+version = "1.0.18"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "bebd363326d05ec3e2f532ab7660680f3b02130d780c299bca73469d521bc0ed"
+checksum = "b0293b4b29daaf487284529cc2f5675b8e57c61f70167ba415a463651fd6a918"
dependencies = [
"serde",
]
[[package]]
name = "serde"
-version = "1.0.166"
+version = "1.0.188"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "d01b7404f9d441d3ad40e6a636a7782c377d2abdbe4fa2440e2edcc2f4f10db8"
+checksum = "cf9e0fcba69a370eed61bcf2b728575f726b50b55cba78064753d708ddc7549e"
dependencies = [
"serde_derive",
]
@@ -4315,22 +4281,22 @@ dependencies = [
[[package]]
name = "serde_bytes"
-version = "0.11.10"
+version = "0.11.12"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "f3c5113243e4a3a1c96587342d067f3e6b0f50790b6cf40d2868eb647a3eef0e"
+checksum = "ab33ec92f677585af6d88c65593ae2375adde54efdbf16d597f2cbc7a6d368ff"
dependencies = [
"serde",
]
[[package]]
name = "serde_derive"
-version = "1.0.166"
+version = "1.0.188"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "5dd83d6dde2b6b2d466e14d9d1acce8816dedee94f735eac6395808b3483c6d6"
+checksum = "4eca7ac642d82aa35b60049a6eccb4be6be75e599bd2e9adb5f875a737654af2"
dependencies = [
"proc-macro2",
"quote",
- "syn 2.0.23",
+ "syn 2.0.29",
]
[[package]]
@@ -4346,9 +4312,9 @@ dependencies = [
[[package]]
name = "serde_json"
-version = "1.0.100"
+version = "1.0.105"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "0f1e14e89be7aa4c4b78bdbdc9eb5bf8517829a600ae8eaa39a6e1d960b5185c"
+checksum = "693151e1ac27563d6dbcec9dee9fbd5da8539b20fa14ad3752b2e6d363ace360"
dependencies = [
"itoa",
"ryu",
@@ -4357,9 +4323,9 @@ dependencies = [
[[package]]
name = "serde_path_to_error"
-version = "0.1.12"
+version = "0.1.14"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "0b1b6471d7496b051e03f1958802a73f88b947866f5146f329e47e36554f4e55"
+checksum = "4beec8bce849d58d06238cb50db2e1c417cfeafa4c63f692b15c82b7c80f8335"
dependencies = [
"itoa",
"serde",
@@ -4423,9 +4389,9 @@ dependencies = [
[[package]]
name = "sha2"
-version = "0.10.7"
+version = "0.10.8"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "479fb9d862239e610720565ca91403019f2f00410f1864c5aa7479b950a76ed8"
+checksum = "793db75ad2bcafc3ffa7c68b215fee268f537982cd901d132f89c6343f3a3dc8"
dependencies = [
"cfg-if",
"cpufeatures",
@@ -4482,9 +4448,9 @@ dependencies = [
[[package]]
name = "simd-adler32"
-version = "0.3.5"
+version = "0.3.7"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "238abfbb77c1915110ad968465608b68e869e0772622c9656714e73e5a1a522f"
+checksum = "d66dc143e6b11c1eddc06d5c423cfc97062865baf299914ab64caa38182078fe"
[[package]]
name = "slab"
@@ -4497,9 +4463,9 @@ dependencies = [
[[package]]
name = "smallvec"
-version = "1.10.0"
+version = "1.11.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "a507befe795404456341dfab10cef66ead4c041f62b8b11bbb92bffe5d0953e0"
+checksum = "942b4a808e05215192e39f4ab80813e599068285906cc91aa64f923db842bd5a"
[[package]]
name = "smawk"
@@ -4570,7 +4536,7 @@ checksum = "19cfdc32e0199062113edf41f344fbf784b8205a94600233c84eb838f45191e1"
dependencies = [
"base64ct",
"pem-rfc7468 0.6.0",
- "sha2 0.10.7",
+ "sha2 0.10.8",
]
[[package]]
@@ -4585,7 +4551,7 @@ dependencies = [
"rand_core 0.6.4",
"rsa 0.7.2",
"sec1 0.3.0",
- "sha2 0.10.7",
+ "sha2 0.10.8",
"signature 1.6.4",
"ssh-encoding",
"zeroize",
@@ -4637,7 +4603,7 @@ dependencies = [
"proc-macro2",
"quote",
"rustversion",
- "syn 2.0.23",
+ "syn 2.0.29",
]
[[package]]
@@ -4659,9 +4625,9 @@ dependencies = [
[[package]]
name = "syn"
-version = "2.0.23"
+version = "2.0.29"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "59fb7d6d8281a51045d62b8eb3a7d1ce347b76f312af50cd3dc0af39c87c1737"
+checksum = "c324c494eba9d92503e6f1ef2e6df781e78f6a7705a0202d9801b198807d518a"
dependencies = [
"proc-macro2",
"quote",
@@ -4723,21 +4689,20 @@ dependencies = [
[[package]]
name = "tagger"
-version = "4.3.4"
+version = "4.3.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "6aaa6f5d645d1dae4cd0286e9f8bf15b75a31656348e5e106eb1a940abd34b63"
+checksum = "094c9f64d6de9a8506b1e49b63a29333b37ed9e821ee04be694d431b3264c3c5"
[[package]]
name = "tempfile"
-version = "3.6.0"
+version = "3.8.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "31c0432476357e58790aaa47a8efb0c5138f137343f3b5f23bd36a27e3b0a6d6"
+checksum = "cb94d2f3cc536af71caac6b6fcebf65860b347e7ce0cc9ebe8f70d3e521054ef"
dependencies = [
- "autocfg",
"cfg-if",
- "fastrand",
+ "fastrand 2.0.0",
"redox_syscall 0.3.5",
- "rustix 0.37.22",
+ "rustix",
"windows-sys 0.48.0",
]
@@ -4777,22 +4742,22 @@ dependencies = [
[[package]]
name = "thiserror"
-version = "1.0.41"
+version = "1.0.49"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "c16a64ba9387ef3fdae4f9c1a7f07a0997fce91985c0336f1ddc1822b3b37802"
+checksum = "1177e8c6d7ede7afde3585fd2513e611227efd6481bd78d2e82ba1ce16557ed4"
dependencies = [
"thiserror-impl",
]
[[package]]
name = "thiserror-impl"
-version = "1.0.41"
+version = "1.0.49"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "d14928354b01c4d6a4f0e549069adef399a284e7995c7ccca94e8a07a5346c59"
+checksum = "10712f02019e9288794769fba95cd6847df9874d49d871d062172f9dd41bc4cc"
dependencies = [
"proc-macro2",
"quote",
- "syn 2.0.23",
+ "syn 2.0.29",
]
[[package]]
@@ -4818,10 +4783,11 @@ dependencies = [
[[package]]
name = "time"
-version = "0.3.22"
+version = "0.3.24"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "ea9e1b3cf1243ae005d9e74085d4d542f3125458f3a81af210d901dcd7411efd"
+checksum = "b79eabcd964882a646b3584543ccabeae7869e9ac32a46f6f22b7a5bd405308b"
dependencies = [
+ "deranged",
"itoa",
"serde",
"time-core",
@@ -4836,9 +4802,9 @@ checksum = "7300fbefb4dadc1af235a9cef3737cea692a9d97e1b9cbcd4ebdae6f8868e6fb"
[[package]]
name = "time-macros"
-version = "0.2.9"
+version = "0.2.11"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "372950940a5f07bf38dbe211d7283c9e6d7327df53794992d293e534c733d09b"
+checksum = "eb71511c991639bb078fd5bf97757e03914361c48100d52878b8e52b46fb92cd"
dependencies = [
"time-core",
]
@@ -4905,7 +4871,7 @@ checksum = "630bdcf245f78637c13ec01ffae6187cca34625e8c63150d424b59e55af2675e"
dependencies = [
"proc-macro2",
"quote",
- "syn 2.0.23",
+ "syn 2.0.29",
]
[[package]]
@@ -4973,9 +4939,9 @@ dependencies = [
[[package]]
name = "tokio-util"
-version = "0.7.8"
+version = "0.7.9"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "806fe8c2c87eccc8b3267cbae29ed3ab2d0bd37fca70ab622e46aaa9375ddb7d"
+checksum = "1d68074620f57a0b21594d9735eb2e98ab38b17f80d3fcb189fca266771ca60d"
dependencies = [
"bytes",
"futures-core",
@@ -4987,9 +4953,9 @@ dependencies = [
[[package]]
name = "toml"
-version = "0.7.5"
+version = "0.7.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "1ebafdf5ad1220cb59e7d17cf4d2c72015297b75b19a10472f99b89225089240"
+checksum = "c17e963a819c331dcacd7ab957d80bc2b9a9c1e71c804826d2f283dd65306542"
dependencies = [
"serde",
"serde_spanned",
@@ -5008,9 +4974,9 @@ dependencies = [
[[package]]
name = "toml_edit"
-version = "0.19.11"
+version = "0.19.14"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "266f016b7f039eec8a1a80dfe6156b633d208b9fccca5e4db1d6775b0c4e34a7"
+checksum = "f8123f27e969974a3dfba720fdb560be359f57b44302d280ba72e76a74480e8a"
dependencies = [
"indexmap 2.0.0",
"serde",
@@ -5068,7 +5034,7 @@ checksum = "5f4f31f56159e98206da9efd823404b79b6ef3143b4a7ab76e67b1751b25a4ab"
dependencies = [
"proc-macro2",
"quote",
- "syn 2.0.23",
+ "syn 2.0.29",
]
[[package]]
@@ -5162,9 +5128,9 @@ checksum = "497961ef93d974e23eb6f433eb5fe1b7930b659f06d12dec6fc44a8f554c0bba"
[[package]]
name = "typescript-type-def"
-version = "0.5.6"
+version = "0.5.8"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "4e6b74ffbd5684d318252bb7182051df8c4ecc098b542f63fddf792e7f42aa02"
+checksum = "356e00027bd9ef773605a353070dc87684b25561a59087ea3ee3dd5fe8854e83"
dependencies = [
"serde_json",
"typescript-type-def-derive",
@@ -5172,9 +5138,9 @@ dependencies = [
[[package]]
name = "typescript-type-def-derive"
-version = "0.5.6"
+version = "0.5.8"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "b10a4f5dd87c279f90beef31edb7055bfd1ceb66e73148de107a5c9005e9f864"
+checksum = "c4e696c28431595138cc53892104528152cbcf26653ae0aa655e4eaede5b9f69"
dependencies = [
"darling 0.13.4",
"ident_case",
@@ -5198,9 +5164,9 @@ checksum = "92888ba5573ff080736b3648696b70cafad7d250551175acbaa4e0385b3e1460"
[[package]]
name = "unicode-ident"
-version = "1.0.10"
+version = "1.0.11"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "22049a19f4a68748a168c0fc439f9516686aa045927ff767eca0a85101fb6e73"
+checksum = "301abaae475aa91687eb82514b328ab47a211a533026cb25fc3e519b86adfc3c"
[[package]]
name = "unicode-linebreak"
@@ -5243,9 +5209,9 @@ checksum = "a156c684c91ea7d62626509bce3cb4e1d9ed5c4d978f7b4352658f96a4c26b4a"
[[package]]
name = "url"
-version = "2.4.0"
+version = "2.4.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "50bff7831e19200a85b17131d085c25d7811bc4e186efdaf54bbd132994a88cb"
+checksum = "143b538f18257fac9cad154828a57c6bf5157e1aa604d4816b5995bf6de87ae5"
dependencies = [
"form_urlencoded",
"idna",
@@ -5266,9 +5232,9 @@ checksum = "711b9620af191e0cdc7468a8d14e709c3dcdb115b36f838e601583af800a370a"
[[package]]
name = "uuid"
-version = "1.4.0"
+version = "1.4.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "d023da39d1fde5a8a3fe1f3e01ca9632ada0a63e9797de55a879d6e2236277be"
+checksum = "79daa5ed5740825c40b389c5e50312b9c86df53fccd33f281df655642b43869d"
dependencies = [
"getrandom 0.2.10",
"serde",
@@ -5356,7 +5322,7 @@ dependencies = [
"once_cell",
"proc-macro2",
"quote",
- "syn 2.0.23",
+ "syn 2.0.29",
"wasm-bindgen-shared",
]
@@ -5390,7 +5356,7 @@ checksum = "54681b18a46765f095758388f2d0cf16eb8d4169b639ab575a8f5693af210c7b"
dependencies = [
"proc-macro2",
"quote",
- "syn 2.0.23",
+ "syn 2.0.29",
"wasm-bindgen-backend",
"wasm-bindgen-shared",
]
@@ -5660,22 +5626,13 @@ checksum = "1a515f5799fe4961cb532f983ce2b23082366b898e52ffbce459c86f67c8378a"
[[package]]
name = "winnow"
-version = "0.4.7"
+version = "0.5.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "ca0ace3845f0d96209f0375e6d367e3eb87eb65d27d445bdc9f1843a26f39448"
+checksum = "f46aab759304e4d7b2075a9aecba26228bb073ee8c50db796b2c72c676b5d807"
dependencies = [
"memchr",
]
-[[package]]
-name = "winreg"
-version = "0.10.1"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "80d0f4e272c85def139476380b12f9ac60926689dd2e01d4923222f40580869d"
-dependencies = [
- "winapi",
-]
-
[[package]]
name = "winreg"
version = "0.50.0"
@@ -5688,12 +5645,13 @@ dependencies = [
[[package]]
name = "x25519-dalek"
-version = "2.0.0-pre.1"
+version = "2.0.0-rc.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "e5da623d8af10a62342bcbbb230e33e58a63255a58012f8653c578e54bab48df"
+checksum = "ec7fae07da688e17059d5886712c933bb0520f15eff2e09cfa18e30968f4e63a"
dependencies = [
- "curve25519-dalek 3.2.0",
+ "curve25519-dalek 4.0.0-rc.3",
"rand_core 0.6.4",
+ "serde",
"zeroize",
]
@@ -5712,7 +5670,7 @@ dependencies = [
"oid-registry",
"rusticata-macros",
"thiserror",
- "time 0.3.22",
+ "time 0.3.24",
]
[[package]]
@@ -5736,14 +5694,14 @@ version = "0.5.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e17bb3549cc1321ae1296b9cdc2698e2b6cb1992adfa19a8c72e5b7a738f44cd"
dependencies = [
- "time 0.3.22",
+ "time 0.3.24",
]
[[package]]
name = "yerpc"
-version = "0.5.1"
+version = "0.5.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "30fc983d32883ecb563227a2dcdcbe8567decd9c533b5ecca7e3099e2f7d4c96"
+checksum = "75b5547af776328f66a5476ea3b7c0789e6fed164eb32d1a2122cfb39ffa505d"
dependencies = [
"anyhow",
"async-channel",
@@ -5764,9 +5722,9 @@ dependencies = [
[[package]]
name = "yerpc_derive"
-version = "0.5.0"
+version = "0.5.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "6d6b8ce490e8719fe84d7d80ad4d58572b2ea9d7a83d156f6afd6ab3ad5cfb94"
+checksum = "f321bb5f728fb066af06c5a994e4375f1f8b054ee6d650766f0bd68dfa4faefe"
dependencies = [
"convert_case 0.5.0",
"darling 0.14.4",
@@ -5792,7 +5750,7 @@ checksum = "9731702e2f0617ad526794ae28fbc6f6ca8849b5ba729666c2a5bc4b6ddee2cd"
dependencies = [
"proc-macro2",
"quote",
- "syn 2.0.23",
+ "syn 2.0.29",
]
[[package]]
@@ -5812,5 +5770,5 @@ checksum = "ce36e65b0d2999d2aafac989fb249189a141aee1f53c612c1f37d72631959f69"
dependencies = [
"proc-macro2",
"quote",
- "syn 2.0.23",
+ "syn 2.0.29",
]
diff --git a/Cargo.toml b/Cargo.toml
index 5863cc9e9..e71720265 100644
--- a/Cargo.toml
+++ b/Cargo.toml
@@ -43,18 +43,19 @@ async-smtp = { version = "0.9", default-features = false, features = ["runtime-t
async_zip = { version = "0.0.12", default-features = false, features = ["deflate", "fs"] }
backtrace = "0.3"
base64 = "0.21"
-brotli = { version = "3.3", default-features=false, features = ["std"] }
+brotli = { version = "3.4", default-features=false, features = ["std"] }
chrono = { version = "0.4", default-features=false, features = ["clock", "std"] }
email = { git = "https://github.com/deltachat/rust-email", branch = "master" }
encoded-words = { git = "https://github.com/async-email/encoded-words", branch = "master" }
escaper = "0.1"
fast-socks5 = "0.8"
+fd-lock = "3.0.11"
futures = "0.3"
futures-lite = "1.13.0"
hex = "0.4.0"
hickory-resolver = "0.24"
humansize = "2"
-image = { version = "0.24.6", default-features=false, features = ["gif", "jpeg", "ico", "png", "pnm", "webp", "bmp"] }
+image = { version = "0.24.7", default-features=false, features = ["gif", "jpeg", "ico", "png", "pnm", "webp", "bmp"] }
iroh = { version = "0.4.1", default-features = false }
kamadak-exif = "0.5"
lettre_email = { git = "https://github.com/deltachat/lettre", branch = "master" }
@@ -70,13 +71,13 @@ parking_lot = "0.12"
pgp = { version = "0.10", default-features = false }
pretty_env_logger = { version = "0.5", optional = true }
qrcodegen = "1.7.0"
-quick-xml = "0.29"
+quick-xml = "0.30"
rand = "0.8"
-regex = "1.8"
-reqwest = { version = "0.11.18", features = ["json"] }
+regex = "1.9"
+reqwest = { version = "0.11.20", features = ["json"] }
rusqlite = { version = "0.29", features = ["sqlcipher"] }
rust-hsluv = "0.1"
-sanitize-filename = "0.4"
+sanitize-filename = "0.5"
serde_json = "1.0"
serde = { version = "1.0", features = ["derive"] }
sha-1 = "0.10"
@@ -91,7 +92,7 @@ tokio = { version = "1", features = ["fs", "rt-multi-thread", "macros"] }
tokio-io-timeout = "1.2.0"
tokio-stream = { version = "0.1.14", features = ["fs"] }
tokio-tar = { version = "0.3" } # TODO: integrate tokio into async-tar
-tokio-util = "0.7.8"
+tokio-util = "0.7.9"
toml = "0.7"
url = "2"
uuid = { version = "1", features = ["serde", "v4"] }
@@ -119,11 +120,6 @@ members = [
"format-flowed",
]
-[[example]]
-name = "simple"
-path = "examples/simple.rs"
-
-
[[bench]]
name = "create_account"
harness = false
diff --git a/README.md b/README.md
index 9dec8d468..96ff5b485 100644
--- a/README.md
+++ b/README.md
@@ -1,8 +1,16 @@
-# Delta Chat Rust
+
+
+
-> Deltachat-core written in Rust
+
+
+
+
+
-[](https://github.com/deltachat/deltachat-core-rust/actions/workflows/ci.yml)
+
+The core library for Delta Chat, written in Rust
+
## Installing Rust and Cargo
diff --git a/benches/create_account.rs b/benches/create_account.rs
index 5e1ae8561..c487004ac 100644
--- a/benches/create_account.rs
+++ b/benches/create_account.rs
@@ -8,7 +8,8 @@ async fn create_accounts(n: u32) {
let dir = tempdir().unwrap();
let p: PathBuf = dir.path().join("accounts");
- let mut accounts = Accounts::new(p.clone()).await.unwrap();
+ let writable = true;
+ let mut accounts = Accounts::new(p.clone(), writable).await.unwrap();
for expected_id in 2..n {
let id = accounts.add_account().await.unwrap();
diff --git a/cliff.toml b/cliff.toml
index 86a2303e5..98a959c28 100644
--- a/cliff.toml
+++ b/cliff.toml
@@ -54,7 +54,7 @@ header = """
# Changelog\n
"""
# template for the changelog body
-# https://tera.netlify.app/docs/#introduction
+# https://keats.github.io/tera/docs/#templates
body = """
{% if version %}\
## [{{ version | trim_start_matches(pat="v") }}] - {{ timestamp | date(format="%Y-%m-%d") }}
diff --git a/deltachat-ffi/Doxyfile b/deltachat-ffi/Doxyfile
index 03365989d..5c60f15e3 100644
--- a/deltachat-ffi/Doxyfile
+++ b/deltachat-ffi/Doxyfile
@@ -846,7 +846,7 @@ EXCLUDE_PATTERNS =
# exclude all test directories use the pattern */test/*
######################################################
-EXCLUDE_SYMBOLS = dc_aheader_t dc_apeerstate_t dc_e2ee_helper_t dc_imap_t dc_job*_t dc_key_t dc_keyring_t dc_loginparam_t dc_mime*_t
+EXCLUDE_SYMBOLS = dc_aheader_t dc_apeerstate_t dc_e2ee_helper_t dc_imap_t dc_job*_t dc_key_t dc_loginparam_t dc_mime*_t
EXCLUDE_SYMBOLS += dc_saxparser_t dc_simplify_t dc_smtp_t dc_sqlite3_t dc_strbuilder_t dc_param_t dc_hash_t dc_hashelem_t
EXCLUDE_SYMBOLS += _dc_* jsmn*
######################################################
diff --git a/deltachat-ffi/deltachat.h b/deltachat-ffi/deltachat.h
index a1c45214f..ab4d746d4 100644
--- a/deltachat-ffi/deltachat.h
+++ b/deltachat-ffi/deltachat.h
@@ -511,6 +511,13 @@ char* dc_get_blobdir (const dc_context_t* context);
* - `gossip_period` = How often to gossip Autocrypt keys in chats with multiple recipients, in
* seconds. 2 days by default.
* This is not supposed to be changed by UIs and only used for testing.
+ * - `verified_one_on_one_chats` = Feature flag for verified 1:1 chats; the UI should set it
+ * to 1 if it supports verified 1:1 chats.
+ * Regardless of this setting, `dc_chat_is_protected()` returns true while the key is verified,
+ * and when the key changes, an info message is posted into the chat.
+ * 0=Nothing else happens when the key changes.
+ * 1=After the key changed, `dc_chat_can_send()` returns false and `dc_chat_is_protection_broken()` returns true
+ * until `dc_accept_chat()` is called.
* - `ui.*` = All keys prefixed by `ui.` can be used by the user-interfaces for system-specific purposes.
* The prefix should be followed by the system and maybe subsystem,
* e.g. `ui.desktop.foo`, `ui.desktop.linux.bar`, `ui.android.foo`, `ui.dc40.bar`, `ui.bot.simplebot.baz`.
@@ -1130,7 +1137,7 @@ dc_reactions_t* dc_get_msg_reactions (dc_context_t *context, int msg_id);
*
* In JS land, that would be mapped to something as:
* ```
- * success = window.webxdc.sendUpdate('{"action":"move","src":"A3","dest":"B4"}', 'move A3 B4');
+ * success = window.webxdc.sendUpdate('{payload: {"action":"move","src":"A3","dest":"B4"}}', 'move A3 B4');
* ```
* `context` and `msg_id` are not needed in JS as those are unique within a webxdc instance.
* See dc_get_webxdc_status_updates() for the receiving counterpart.
@@ -1512,24 +1519,6 @@ dc_array_t* dc_get_chat_media (dc_context_t* context, uint32_t ch
uint32_t dc_get_next_media (dc_context_t* context, uint32_t msg_id, int dir, int msg_type, int msg_type2, int msg_type3);
-/**
- * Enable or disable protection against active attacks.
- * To enable protection, it is needed that all members are verified;
- * if this condition is met, end-to-end-encryption is always enabled
- * and only the verified keys are used.
- *
- * Sends out #DC_EVENT_CHAT_MODIFIED on changes
- * and #DC_EVENT_MSGS_CHANGED if a status message was sent.
- *
- * @memberof dc_context_t
- * @param context The context object as returned from dc_context_new().
- * @param chat_id The ID of the chat to change the protection for.
- * @param protect 1=protect chat, 0=unprotect chat
- * @return 1=success, 0=error, e.g. some members may be unverified
- */
-int dc_set_chat_protection (dc_context_t* context, uint32_t chat_id, int protect);
-
-
/**
* Set chat visibility to pinned, archived or normal.
*
@@ -2956,12 +2945,15 @@ int dc_receive_backup (dc_context_t* context, const char* qr);
* @param dir The directory to create the context-databases in.
* If the directory does not exist,
* dc_accounts_new() will try to create it.
+ * @param writable Whether the returned account manager is writable, i.e. calling these functions on
+ * it is possible: dc_accounts_add_account(), dc_accounts_add_closed_account(),
+ * dc_accounts_migrate_account(), dc_accounts_remove_account(), dc_accounts_select_account().
* @return An account manager object.
* The object must be passed to the other account manager functions
* and must be freed using dc_accounts_unref() after usage.
* On errors, NULL is returned.
*/
-dc_accounts_t* dc_accounts_new (const char* os_name, const char* dir);
+dc_accounts_t* dc_accounts_new (const char* dir, int writable);
/**
@@ -3742,7 +3734,6 @@ int dc_chat_can_send (const dc_chat_t* chat);
* Check if a chat is protected.
* Protected chats contain only verified members and encryption is always enabled.
* Protected chats are created using dc_create_group_chat() by setting the 'protect' parameter to 1.
- * The status can be changed using dc_set_chat_protection().
*
* @memberof dc_chat_t
* @param chat The chat object.
@@ -3751,6 +3742,26 @@ int dc_chat_can_send (const dc_chat_t* chat);
int dc_chat_is_protected (const dc_chat_t* chat);
+/**
+ * Checks if the chat was protected, and then an incoming message broke this protection.
+ *
+ * This function is only useful if the UI enabled the `verified_one_on_one_chats` feature flag,
+ * otherwise it will return false for all chats.
+ *
+ * 1:1 chats are automatically set as protected when a contact is verified.
+ * When a message comes in that is not encrypted / signed correctly,
+ * the chat is automatically set as unprotected again.
+ * dc_chat_is_protection_broken() will return true until dc_accept_chat() is called.
+ *
+ * The UI should let the user confirm that this is OK with a message like
+ * `Bob sent a message from another device. Tap to learn more` and then call dc_accept_chat().
+ * @memberof dc_chat_t
+ * @param chat The chat object.
+ * @return 1=chat protection broken, 0=otherwise.
+ */
+int dc_chat_is_protection_broken (const dc_chat_t* chat);
+
+
/**
* Check if locations are sent to the chat
* at the time the object was created using dc_get_chat().
@@ -4345,7 +4356,7 @@ int dc_msg_is_forwarded (const dc_msg_t* msg);
* Check if the message is an informational message, created by the
* device or by another users. Such messages are not "typed" by the user but
* created due to other actions,
- * e.g. dc_set_chat_name(), dc_set_chat_profile_image(), dc_set_chat_protection()
+ * e.g. dc_set_chat_name(), dc_set_chat_profile_image(),
* or dc_add_contact_to_chat().
*
* These messages are typically shown in the center of the chat view,
@@ -5039,7 +5050,12 @@ int dc_contact_is_verified (dc_contact_t* contact);
/**
* Return the address that verified a contact
*
- * The UI may use this in addition to a checkmark showing the verification status
+ * The UI may use this in addition to a checkmark showing the verification status.
+ * In case of verification chains,
+ * the last contact in the chain is shown.
+ * This is because of privacy reasons, but also as it would not help the user
+ * to see a unknown name here - where one can mostly always ask the shown name
+ * as it is directly known.
*
* @memberof dc_contact_t
* @param contact The contact object.
@@ -6780,15 +6796,6 @@ void dc_event_unref(dc_event_t* event);
/// Used in error strings.
#define DC_STR_ERROR_NO_NETWORK 87
-/// "Chat protection enabled."
-///
-
-/// @deprecated Deprecated, replaced by DC_STR_MSG_YOU_ENABLED_PROTECTION and DC_STR_MSG_PROTECTION_ENABLED_BY.
-#define DC_STR_PROTECTION_ENABLED 88
-
-/// @deprecated Deprecated, replaced by DC_STR_MSG_YOU_DISABLED_PROTECTION and DC_STR_MSG_PROTECTION_DISABLED_BY.
-#define DC_STR_PROTECTION_DISABLED 89
-
/// "Reply"
///
/// Used in summaries.
@@ -7233,26 +7240,6 @@ void dc_event_unref(dc_event_t* event);
/// `%2$s` will be replaced by name and address of the contact.
#define DC_STR_EPHEMERAL_TIMER_WEEKS_BY_OTHER 157
-/// "You enabled chat protection."
-///
-/// Used in status messages.
-#define DC_STR_PROTECTION_ENABLED_BY_YOU 158
-
-/// "Chat protection enabled by %1$s."
-///
-/// `%1$s` will be replaced by name and address of the contact.
-///
-/// Used in status messages.
-#define DC_STR_PROTECTION_ENABLED_BY_OTHER 159
-
-/// "You disabled chat protection."
-#define DC_STR_PROTECTION_DISABLED_BY_YOU 160
-
-/// "Chat protection disabled by %1$s."
-///
-/// `%1$s` will be replaced by name and address of the contact.
-#define DC_STR_PROTECTION_DISABLED_BY_OTHER 161
-
/// "Scan to set up second device for %1$s"
///
/// `%1$s` will be replaced by name and address of the account.
@@ -7263,6 +7250,16 @@ void dc_event_unref(dc_event_t* event);
/// Used as a device message after a successful backup transfer.
#define DC_STR_BACKUP_TRANSFER_MSG_BODY 163
+/// "Messages are guaranteed to be end-to-end encrypted from now on."
+///
+/// Used in info messages.
+#define DC_STR_CHAT_PROTECTION_ENABLED 170
+
+/// "%1$s sent a message from another device."
+///
+/// Used in info messages.
+#define DC_STR_CHAT_PROTECTION_DISABLED 171
+
/**
* @}
*/
diff --git a/deltachat-ffi/src/lib.rs b/deltachat-ffi/src/lib.rs
index 86d142355..025c3f7ce 100644
--- a/deltachat-ffi/src/lib.rs
+++ b/deltachat-ffi/src/lib.rs
@@ -29,7 +29,7 @@ use deltachat::contact::{Contact, ContactId, Origin};
use deltachat::context::Context;
use deltachat::ephemeral::Timer as EphemeralTimer;
use deltachat::imex::BackupProvider;
-use deltachat::key::{DcKey, DcSecretKey};
+use deltachat::key::preconfigure_keypair;
use deltachat::message::MsgId;
use deltachat::net::read_url_blob;
use deltachat::qr_code_generator::{generate_backup_qr, get_securejoin_qr_svg};
@@ -813,21 +813,12 @@ pub unsafe extern "C" fn dc_preconfigure_keypair(
return 0;
}
let ctx = &*context;
- block_on(async move {
- let addr = tools::EmailAddress::new(&to_string_lossy(addr))?;
- let secret = key::SignedSecretKey::from_asc(&to_string_lossy(secret_data))?.0;
- let public = secret.split_public_key()?;
- let keypair = key::KeyPair {
- addr,
- public,
- secret,
- };
- key::store_self_keypair(ctx, &keypair, key::KeyPairUse::Default).await?;
- Ok::<_, anyhow::Error>(1)
- })
- .context("Failed to save keypair")
- .log_err(ctx)
- .unwrap_or(0)
+ let addr = to_string_lossy(addr);
+ let secret_data = to_string_lossy(secret_data);
+ block_on(preconfigure_keypair(ctx, &addr, &secret_data))
+ .context("Failed to save keypair")
+ .log_err(ctx)
+ .is_ok() as libc::c_int
}
#[no_mangle]
@@ -1472,32 +1463,6 @@ pub unsafe extern "C" fn dc_get_next_media(
})
}
-#[no_mangle]
-pub unsafe extern "C" fn dc_set_chat_protection(
- context: *mut dc_context_t,
- chat_id: u32,
- protect: libc::c_int,
-) -> libc::c_int {
- if context.is_null() {
- eprintln!("ignoring careless call to dc_set_chat_protection()");
- return 0;
- }
- let ctx = &*context;
- let protect = if let Some(s) = ProtectionStatus::from_i32(protect) {
- s
- } else {
- warn!(ctx, "bad protect-value for dc_set_chat_protection()");
- return 0;
- };
-
- block_on(async move {
- match ChatId::new(chat_id).set_protection(ctx, protect).await {
- Ok(()) => 1,
- Err(_) => 0,
- }
- })
-}
-
#[no_mangle]
pub unsafe extern "C" fn dc_set_chat_visibility(
context: *mut dc_context_t,
@@ -3132,6 +3097,16 @@ pub unsafe extern "C" fn dc_chat_is_protected(chat: *mut dc_chat_t) -> libc::c_i
ffi_chat.chat.is_protected() as libc::c_int
}
+#[no_mangle]
+pub unsafe extern "C" fn dc_chat_is_protection_broken(chat: *mut dc_chat_t) -> libc::c_int {
+ if chat.is_null() {
+ eprintln!("ignoring careless call to dc_chat_is_protection_broken()");
+ return 0;
+ }
+ let ffi_chat = &*chat;
+ ffi_chat.chat.is_protection_broken() as libc::c_int
+}
+
#[no_mangle]
pub unsafe extern "C" fn dc_chat_is_sending_locations(chat: *mut dc_chat_t) -> libc::c_int {
if chat.is_null() {
@@ -4750,17 +4725,17 @@ pub type dc_accounts_t = AccountsWrapper;
#[no_mangle]
pub unsafe extern "C" fn dc_accounts_new(
- _os_name: *const libc::c_char,
- dbfile: *const libc::c_char,
+ dir: *const libc::c_char,
+ writable: libc::c_int,
) -> *mut dc_accounts_t {
setup_panic!();
- if dbfile.is_null() {
+ if dir.is_null() {
eprintln!("ignoring careless call to dc_accounts_new()");
return ptr::null_mut();
}
- let accs = block_on(Accounts::new(as_path(dbfile).into()));
+ let accs = block_on(Accounts::new(as_path(dir).into(), writable != 0));
match accs {
Ok(accs) => Box::into_raw(Box::new(AccountsWrapper::new(accs))),
diff --git a/deltachat-jsonrpc/Cargo.toml b/deltachat-jsonrpc/Cargo.toml
index 2080f0dfa..16b7b2827 100644
--- a/deltachat-jsonrpc/Cargo.toml
+++ b/deltachat-jsonrpc/Cargo.toml
@@ -15,22 +15,22 @@ required-features = ["webserver"]
anyhow = "1"
deltachat = { path = ".." }
num-traits = "0.2"
-schemars = "0.8.11"
+schemars = "0.8.13"
serde = { version = "1.0", features = ["derive"] }
-tempfile = "3.6.0"
+tempfile = "3.8.0"
log = "0.4"
async-channel = { version = "1.8.0" }
futures = { version = "0.3.28" }
-serde_json = "1.0.99"
-yerpc = { version = "0.5.1", features = ["anyhow_expose", "openrpc"] }
-typescript-type-def = { version = "0.5.5", features = ["json_value"] }
+serde_json = "1.0.105"
+yerpc = { version = "0.5.2", features = ["anyhow_expose", "openrpc"] }
+typescript-type-def = { version = "0.5.8", features = ["json_value"] }
tokio = { version = "1.33.0" }
-sanitize-filename = "0.4"
+sanitize-filename = "0.5"
walkdir = "2.3.3"
base64 = "0.21"
# optional dependencies
-axum = { version = "0.6.18", optional = true, features = ["ws"] }
+axum = { version = "0.6.20", optional = true, features = ["ws"] }
env_logger = { version = "0.10.0", optional = true }
[dev-dependencies]
diff --git a/deltachat-jsonrpc/src/api/mod.rs b/deltachat-jsonrpc/src/api/mod.rs
index 580634cf5..b67c8e595 100644
--- a/deltachat-jsonrpc/src/api/mod.rs
+++ b/deltachat-jsonrpc/src/api/mod.rs
@@ -4,30 +4,30 @@ use std::{collections::HashMap, str::FromStr};
use anyhow::{anyhow, bail, ensure, Context, Result};
pub use deltachat::accounts::Accounts;
-use deltachat::message::get_msg_read_receipts;
-use deltachat::qr::Qr;
-use deltachat::{
- chat::{
- self, add_contact_to_chat, forward_msgs, get_chat_media, get_chat_msgs, get_chat_msgs_ex,
- marknoticed_chat, remove_contact_from_chat, Chat, ChatId, ChatItem, MessageListOptions,
- ProtectionStatus,
- },
- chatlist::Chatlist,
- config::Config,
- constants::DC_MSG_ID_DAYMARKER,
- contact::{may_be_valid_addr, Contact, ContactId, Origin},
- context::get_info,
- ephemeral::Timer,
- imex, location,
- message::{self, delete_msgs, markseen_msgs, Message, MessageState, MsgId, Viewtype},
- provider::get_provider_info,
- qr,
- qr_code_generator::{generate_backup_qr, get_securejoin_qr_svg},
- reaction::{get_msg_reactions, send_reaction},
- securejoin,
- stock_str::StockMessage,
- webxdc::StatusUpdateSerial,
+use deltachat::chat::{
+ self, add_contact_to_chat, forward_msgs, get_chat_media, get_chat_msgs, get_chat_msgs_ex,
+ marknoticed_chat, remove_contact_from_chat, Chat, ChatId, ChatItem, MessageListOptions,
+ ProtectionStatus,
};
+use deltachat::chatlist::Chatlist;
+use deltachat::config::Config;
+use deltachat::constants::DC_MSG_ID_DAYMARKER;
+use deltachat::contact::{may_be_valid_addr, Contact, ContactId, Origin};
+use deltachat::context::get_info;
+use deltachat::ephemeral::Timer;
+use deltachat::imex;
+use deltachat::location;
+use deltachat::message::get_msg_read_receipts;
+use deltachat::message::{
+ self, delete_msgs, markseen_msgs, Message, MessageState, MsgId, Viewtype,
+};
+use deltachat::provider::get_provider_info;
+use deltachat::qr::{self, Qr};
+use deltachat::qr_code_generator::{generate_backup_qr, get_securejoin_qr_svg};
+use deltachat::reaction::{get_msg_reactions, send_reaction};
+use deltachat::securejoin;
+use deltachat::stock_str::StockMessage;
+use deltachat::webxdc::StatusUpdateSerial;
use sanitize_filename::is_sanitized;
use tokio::fs;
use tokio::sync::{watch, Mutex, RwLock};
@@ -142,11 +142,7 @@ impl CommandApi {
}
}
-#[rpc(
- all_positional,
- ts_outdir = "typescript/generated",
- openrpc_outdir = "openrpc"
-)]
+#[rpc(all_positional, ts_outdir = "typescript/generated")]
impl CommandApi {
/// Test function.
async fn sleep(&self, delay: f64) {
@@ -157,12 +153,12 @@ impl CommandApi {
// Misc top level functions
// ---------------------------------------------
- /// Check if an email address is valid.
+ /// Checks if an email address is valid.
async fn check_email_validity(&self, email: String) -> bool {
may_be_valid_addr(&email)
}
- /// Get general system info.
+ /// Returns general system info.
async fn get_system_info(&self) -> BTreeMap<&'static str, String> {
get_info()
}
@@ -223,11 +219,13 @@ impl CommandApi {
Ok(accounts)
}
+ /// Starts background tasks for all accounts.
async fn start_io_for_all_accounts(&self) -> Result<()> {
self.accounts.read().await.start_io().await;
Ok(())
}
+ /// Stops background tasks for all accounts.
async fn stop_io_for_all_accounts(&self) -> Result<()> {
self.accounts.read().await.stop_io().await;
Ok(())
@@ -237,14 +235,16 @@ impl CommandApi {
// Methods that work on individual accounts
// ---------------------------------------------
- async fn start_io(&self, id: u32) -> Result<()> {
- let ctx = self.get_context(id).await?;
+ /// Starts background tasks for a single account.
+ async fn start_io(&self, account_id: u32) -> Result<()> {
+ let ctx = self.get_context(account_id).await?;
ctx.start_io().await;
Ok(())
}
- async fn stop_io(&self, id: u32) -> Result<()> {
- let ctx = self.get_context(id).await?;
+ /// Stops background tasks for a single account.
+ async fn stop_io(&self, account_id: u32) -> Result<()> {
+ let ctx = self.get_context(account_id).await?;
ctx.stop_io().await;
Ok(())
}
@@ -311,11 +311,13 @@ impl CommandApi {
ctx.get_info().await
}
+ /// Sets the given configuration key.
async fn set_config(&self, account_id: u32, key: String, value: Option) -> Result<()> {
let ctx = self.get_context(account_id).await?;
set_config(&ctx, &key, value.as_deref()).await
}
+ /// Updates a batch of configuration values.
async fn batch_set_config(
&self,
account_id: u32,
@@ -347,6 +349,7 @@ impl CommandApi {
Ok(qr_object)
}
+ /// Returns configuration value for the given key.
async fn get_config(&self, account_id: u32, key: String) -> Result> {
let ctx = self.get_context(account_id).await?;
get_config(&ctx, &key).await
@@ -1741,6 +1744,9 @@ impl CommandApi {
let mut msg = Message::new(Viewtype::Sticker);
msg.set_file(&sticker_path, None);
+ // JSON-rpc does not need heuristics to turn [Viewtype::Sticker] into [Viewtype::Image]
+ msg.force_sticker();
+
let message_id = deltachat::chat::send_msg(&ctx, ChatId::new(chat_id), &mut msg).await?;
Ok(message_id.to_u32())
}
@@ -1874,7 +1880,7 @@ impl CommandApi {
.context("path conversion to string failed")
}
- /// save a sticker to a collection/folder in the account's sticker folder
+ /// Saves a sticker to a collection/folder in the account's sticker folder.
async fn misc_save_sticker(
&self,
account_id: u32,
diff --git a/deltachat-jsonrpc/src/api/types/account.rs b/deltachat-jsonrpc/src/api/types/account.rs
index d27a53723..b2909109d 100644
--- a/deltachat-jsonrpc/src/api/types/account.rs
+++ b/deltachat-jsonrpc/src/api/types/account.rs
@@ -7,7 +7,7 @@ use typescript_type_def::TypeDef;
use super::color_int_to_hex_string;
#[derive(Serialize, TypeDef, schemars::JsonSchema)]
-#[serde(tag = "type")]
+#[serde(tag = "kind")]
pub enum Account {
#[serde(rename_all = "camelCase")]
Configured {
diff --git a/deltachat-jsonrpc/src/api/types/chat.rs b/deltachat-jsonrpc/src/api/types/chat.rs
index c8d7d867f..e2146c8c1 100644
--- a/deltachat-jsonrpc/src/api/types/chat.rs
+++ b/deltachat-jsonrpc/src/api/types/chat.rs
@@ -167,10 +167,11 @@ impl BasicChat {
}
#[derive(Clone, Serialize, Deserialize, TypeDef, schemars::JsonSchema)]
+#[serde(tag = "kind")]
pub enum MuteDuration {
NotMuted,
Forever,
- Until(i64),
+ Until { duration: i64 },
}
impl MuteDuration {
@@ -178,13 +179,13 @@ impl MuteDuration {
match self {
MuteDuration::NotMuted => Ok(chat::MuteDuration::NotMuted),
MuteDuration::Forever => Ok(chat::MuteDuration::Forever),
- MuteDuration::Until(n) => {
- if n <= 0 {
+ MuteDuration::Until { duration } => {
+ if duration <= 0 {
bail!("failed to read mute duration")
}
Ok(SystemTime::now()
- .checked_add(Duration::from_secs(n as u64))
+ .checked_add(Duration::from_secs(duration as u64))
.map_or(chat::MuteDuration::Forever, chat::MuteDuration::Until))
}
}
diff --git a/deltachat-jsonrpc/src/api/types/chat_list.rs b/deltachat-jsonrpc/src/api/types/chat_list.rs
index fac1fb039..b47f7a728 100644
--- a/deltachat-jsonrpc/src/api/types/chat_list.rs
+++ b/deltachat-jsonrpc/src/api/types/chat_list.rs
@@ -15,7 +15,7 @@ use super::color_int_to_hex_string;
use super::message::MessageViewtype;
#[derive(Serialize, TypeDef, schemars::JsonSchema)]
-#[serde(tag = "type")]
+#[serde(tag = "kind")]
pub enum ChatListItemFetchResult {
#[serde(rename_all = "camelCase")]
ChatListItem {
diff --git a/deltachat-jsonrpc/src/api/types/events.rs b/deltachat-jsonrpc/src/api/types/events.rs
index 0cfb02a84..dd03358bc 100644
--- a/deltachat-jsonrpc/src/api/types/events.rs
+++ b/deltachat-jsonrpc/src/api/types/events.rs
@@ -22,7 +22,7 @@ impl From for Event {
}
#[derive(Serialize, TypeDef, schemars::JsonSchema)]
-#[serde(tag = "type")]
+#[serde(tag = "kind")]
pub enum EventType {
/// The library-user may write an informational string to the log.
///
diff --git a/deltachat-jsonrpc/src/api/types/message.rs b/deltachat-jsonrpc/src/api/types/message.rs
index 5132e5231..4bd691e3a 100644
--- a/deltachat-jsonrpc/src/api/types/message.rs
+++ b/deltachat-jsonrpc/src/api/types/message.rs
@@ -19,7 +19,7 @@ use super::reactions::JSONRPCReactions;
use super::webxdc::WebxdcMessageInfo;
#[derive(Serialize, TypeDef, schemars::JsonSchema)]
-#[serde(rename_all = "camelCase", tag = "variant")]
+#[serde(rename_all = "camelCase", tag = "kind")]
pub enum MessageLoadResult {
Message(MessageObject),
LoadingError { error: String },
diff --git a/deltachat-jsonrpc/src/api/types/qr.rs b/deltachat-jsonrpc/src/api/types/qr.rs
index d53ebeab5..0f6d79c8c 100644
--- a/deltachat-jsonrpc/src/api/types/qr.rs
+++ b/deltachat-jsonrpc/src/api/types/qr.rs
@@ -4,7 +4,7 @@ use typescript_type_def::TypeDef;
#[derive(Serialize, TypeDef, schemars::JsonSchema)]
#[serde(rename = "Qr", rename_all = "camelCase")]
-#[serde(tag = "type")]
+#[serde(tag = "kind")]
pub enum QrObject {
AskVerifyContact {
contact_id: u32,
diff --git a/deltachat-jsonrpc/src/lib.rs b/deltachat-jsonrpc/src/lib.rs
index 16592bd88..10ec39ea4 100644
--- a/deltachat-jsonrpc/src/lib.rs
+++ b/deltachat-jsonrpc/src/lib.rs
@@ -13,7 +13,8 @@ mod tests {
#[tokio::test(flavor = "multi_thread")]
async fn basic_json_rpc_functionality() -> anyhow::Result<()> {
let tmp_dir = TempDir::new().unwrap().path().into();
- let accounts = Accounts::new(tmp_dir).await?;
+ let writable = true;
+ let accounts = Accounts::new(tmp_dir, writable).await?;
let api = CommandApi::new(accounts);
let (sender, mut receiver) = unbounded::();
@@ -54,7 +55,8 @@ mod tests {
#[tokio::test(flavor = "multi_thread")]
async fn test_batch_set_config() -> anyhow::Result<()> {
let tmp_dir = TempDir::new().unwrap().path().into();
- let accounts = Accounts::new(tmp_dir).await?;
+ let writable = true;
+ let accounts = Accounts::new(tmp_dir, writable).await?;
let api = CommandApi::new(accounts);
let (sender, mut receiver) = unbounded::();
diff --git a/deltachat-jsonrpc/src/webserver.rs b/deltachat-jsonrpc/src/webserver.rs
index df8f92135..f4b6f38af 100644
--- a/deltachat-jsonrpc/src/webserver.rs
+++ b/deltachat-jsonrpc/src/webserver.rs
@@ -19,7 +19,8 @@ async fn main() -> Result<(), std::io::Error> {
.map(|port| port.parse::().expect("DC_PORT must be a number"))
.unwrap_or(DEFAULT_PORT);
log::info!("Starting with accounts directory `{path}`.");
- let accounts = Accounts::new(PathBuf::from(&path)).await.unwrap();
+ let writable = true;
+ let accounts = Accounts::new(PathBuf::from(&path), writable).await.unwrap();
let state = CommandApi::new(accounts);
let app = Router::new()
diff --git a/deltachat-jsonrpc/typescript/example/example.ts b/deltachat-jsonrpc/typescript/example/example.ts
index d7b038e0c..e45bc18cc 100644
--- a/deltachat-jsonrpc/typescript/example/example.ts
+++ b/deltachat-jsonrpc/typescript/example/example.ts
@@ -35,7 +35,7 @@ async function run() {
const accounts = await client.rpc.getAllAccounts();
console.log("accounts loaded", accounts);
for (const account of accounts) {
- if (account.type === "Configured") {
+ if (account.kind === "Configured") {
write(
$head,
`
@@ -57,7 +57,7 @@ async function run() {
clear($main);
const selectedAccount = SELECTED_ACCOUNT;
const info = await client.rpc.getAccountInfo(selectedAccount);
- if (info.type !== "Configured") {
+ if (info.kind !== "Configured") {
return write($main, "Account is not configured");
}
write($main, `${info.addr!} `);
@@ -81,8 +81,7 @@ async function run() {
messageIds
);
for (const [_messageId, message] of Object.entries(messages)) {
- if (message.variant === "message")
- write($main, `${message.text}
`);
+ if (message.kind === "message") write($main, `${message.text}
`);
else write($main, `loading error: ${message.error}
`);
}
}
@@ -93,9 +92,9 @@ async function run() {
$side,
`
- [${event.type} on account ${accountId}]
+ [${event.kind} on account ${accountId}]
f1: ${JSON.stringify(
- Object.assign({}, event, { type: undefined })
+ Object.assign({}, event, { kind: undefined })
)}
`
);
diff --git a/deltachat-jsonrpc/typescript/src/client.ts b/deltachat-jsonrpc/typescript/src/client.ts
index 1a51b5e04..83cc2f7e7 100644
--- a/deltachat-jsonrpc/typescript/src/client.ts
+++ b/deltachat-jsonrpc/typescript/src/client.ts
@@ -6,22 +6,22 @@ import { WebsocketTransport, BaseTransport, Request } from "yerpc";
import { TinyEmitter } from "@deltachat/tiny-emitter";
type Events = { ALL: (accountId: number, event: EventType) => void } & {
- [Property in EventType["type"]]: (
+ [Property in EventType["kind"]]: (
accountId: number,
- event: Extract
+ event: Extract
) => void;
};
type ContextEvents = { ALL: (event: EventType) => void } & {
- [Property in EventType["type"]]: (
- event: Extract
+ [Property in EventType["kind"]]: (
+ event: Extract
) => void;
};
export type DcEvent = EventType;
-export type DcEventType = Extract<
+export type DcEventType = Extract<
EventType,
- { type: T }
+ { kind: T }
>;
export class BaseDeltaChat<
@@ -46,12 +46,12 @@ export class BaseDeltaChat<
while (true) {
const event = await this.rpc.getNextEvent();
//@ts-ignore
- this.emit(event.event.type, event.contextId, event.event);
+ this.emit(event.event.kind, event.contextId, event.event);
this.emit("ALL", event.contextId, event.event);
if (this.contextEmitters[event.contextId]) {
this.contextEmitters[event.contextId].emit(
- event.event.type,
+ event.event.kind,
//@ts-ignore
event.event as any
);
diff --git a/deltachat-jsonrpc/typescript/test/online.ts b/deltachat-jsonrpc/typescript/test/online.ts
index c9fa4077a..c85376b09 100644
--- a/deltachat-jsonrpc/typescript/test/online.ts
+++ b/deltachat-jsonrpc/typescript/test/online.ts
@@ -29,8 +29,8 @@ describe("online tests", function () {
serverHandle = await startServer();
dc = new DeltaChat(serverHandle.stdin, serverHandle.stdout, true);
- dc.on("ALL", (contextId, { type }) => {
- if (type !== "Info") console.log(contextId, type);
+ dc.on("ALL", (contextId, { kind }) => {
+ if (kind !== "Info") console.log(contextId, kind);
});
account1 = await createTempUser(process.env.DCC_NEW_TMP_EMAIL);
@@ -177,12 +177,12 @@ describe("online tests", function () {
});
});
-async function waitForEvent(
+async function waitForEvent(
dc: DeltaChat,
eventType: T,
accountId: number,
timeout: number = EVENT_TIMEOUT
-): Promise> {
+): Promise> {
return new Promise((resolve, reject) => {
const rejectTimeout = setTimeout(
() => reject(new Error("Timeout reached before event came in")),
diff --git a/deltachat-repl/Cargo.toml b/deltachat-repl/Cargo.toml
index 2a2d1cd2e..dd5cb0a67 100644
--- a/deltachat-repl/Cargo.toml
+++ b/deltachat-repl/Cargo.toml
@@ -9,7 +9,7 @@ ansi_term = "0.12.1"
anyhow = "1"
deltachat = { path = "..", features = ["internals"]}
dirs = "5"
-log = "0.4.19"
+log = "0.4.20"
pretty_env_logger = "0.5"
rusqlite = "0.29"
rustyline = "12"
diff --git a/deltachat-repl/src/cmdline.rs b/deltachat-repl/src/cmdline.rs
index 9011813c5..40fa149fa 100644
--- a/deltachat-repl/src/cmdline.rs
+++ b/deltachat-repl/src/cmdline.rs
@@ -18,6 +18,7 @@ use deltachat::imex::*;
use deltachat::location;
use deltachat::log::LogExt;
use deltachat::message::{self, Message, MessageState, MsgId, Viewtype};
+use deltachat::mimeparser::SystemMessage;
use deltachat::peerstate::*;
use deltachat::qr::*;
use deltachat::reaction::send_reaction;
@@ -210,7 +211,17 @@ async fn log_msg(context: &Context, prefix: impl AsRef, msg: &Message) {
} else {
"[FRESH]"
},
- if msg.is_info() { "[INFO]" } else { "" },
+ if msg.is_info() {
+ if msg.get_info_type() == SystemMessage::ChatProtectionEnabled {
+ "[INFO π‘οΈ]"
+ } else if msg.get_info_type() == SystemMessage::ChatProtectionDisabled {
+ "[INFO π‘οΈβ]"
+ } else {
+ "[INFO]"
+ }
+ } else {
+ ""
+ },
if msg.get_viewtype() == Viewtype::VideochatInvitation {
format!(
"[VIDEOCHAT-INVITATION: {}, type={}]",
@@ -395,8 +406,6 @@ pub async fn cmdline(context: Context, line: &str, chat_id: &mut ChatId) -> Resu
unpin \n\
mute []\n\
unmute \n\
- protect \n\
- unprotect \n\
delchat \n\
accept \n\
decline \n\
@@ -1071,20 +1080,6 @@ pub async fn cmdline(context: Context, line: &str, chat_id: &mut ChatId) -> Resu
};
chat::set_muted(&context, chat_id, duration).await?;
}
- "protect" | "unprotect" => {
- ensure!(!arg1.is_empty(), "Argument missing.");
- let chat_id = ChatId::new(arg1.parse()?);
- chat_id
- .set_protection(
- &context,
- match arg0 {
- "protect" => ProtectionStatus::Protected,
- "unprotect" => ProtectionStatus::Unprotected,
- _ => unreachable!("arg0={:?}", arg0),
- },
- )
- .await?;
- }
"delchat" => {
ensure!(!arg1.is_empty(), "Argument missing.");
let chat_id = ChatId::new(arg1.parse()?);
diff --git a/deltachat-rpc-client/examples/echobot_advanced.py b/deltachat-rpc-client/examples/echobot_advanced.py
index 0c315ac5d..221f69dda 100644
--- a/deltachat-rpc-client/examples/echobot_advanced.py
+++ b/deltachat-rpc-client/examples/echobot_advanced.py
@@ -14,9 +14,9 @@ hooks = events.HookCollection()
@hooks.on(events.RawEvent)
def log_event(event):
- if event.type == EventType.INFO:
+ if event.kind == EventType.INFO:
logging.info(event.msg)
- elif event.type == EventType.WARNING:
+ elif event.kind == EventType.WARNING:
logging.warning(event.msg)
diff --git a/deltachat-rpc-client/src/deltachat_rpc_client/chat.py b/deltachat-rpc-client/src/deltachat_rpc_client/chat.py
index 68e310811..20fc11b36 100644
--- a/deltachat-rpc-client/src/deltachat_rpc_client/chat.py
+++ b/deltachat-rpc-client/src/deltachat_rpc_client/chat.py
@@ -54,14 +54,14 @@ class Chat:
"""
if duration is not None:
assert duration > 0, "Invalid duration"
- dur: Union[str, dict] = {"Until": duration}
+ dur: dict = {"kind": "Until", "duration": duration}
else:
- dur = "Forever"
+ dur = {"kind": "Forever"}
self._rpc.set_chat_mute_duration(self.account.id, self.id, dur)
def unmute(self) -> None:
"""Unmute this chat."""
- self._rpc.set_chat_mute_duration(self.account.id, self.id, "NotMuted")
+ self._rpc.set_chat_mute_duration(self.account.id, self.id, {"kind": "NotMuted"})
def pin(self) -> None:
"""Pin this chat."""
diff --git a/deltachat-rpc-client/src/deltachat_rpc_client/client.py b/deltachat-rpc-client/src/deltachat_rpc_client/client.py
index e27c35a1f..31b628ffa 100644
--- a/deltachat-rpc-client/src/deltachat_rpc_client/client.py
+++ b/deltachat-rpc-client/src/deltachat_rpc_client/client.py
@@ -104,10 +104,10 @@ class Client:
self._process_messages() # Process old messages.
while True:
event = self.account.wait_for_event()
- event["type"] = EventType(event.type)
+ event["kind"] = EventType(event.kind)
event["account"] = self.account
self._on_event(event)
- if event.type == EventType.INCOMING_MSG:
+ if event.kind == EventType.INCOMING_MSG:
self._process_messages()
stop = func(event)
diff --git a/deltachat-rpc-client/src/deltachat_rpc_client/events.py b/deltachat-rpc-client/src/deltachat_rpc_client/events.py
index f83c06e83..b90b6e045 100644
--- a/deltachat-rpc-client/src/deltachat_rpc_client/events.py
+++ b/deltachat-rpc-client/src/deltachat_rpc_client/events.py
@@ -79,7 +79,7 @@ class RawEvent(EventFilter):
return False
def filter(self, event: "AttrDict") -> bool:
- if self.types and event.type not in self.types:
+ if self.types and event.kind not in self.types:
return False
return self._call_func(event)
diff --git a/deltachat-rpc-client/src/deltachat_rpc_client/pytestplugin.py b/deltachat-rpc-client/src/deltachat_rpc_client/pytestplugin.py
index 9fb1422e7..4d569d3ea 100644
--- a/deltachat-rpc-client/src/deltachat_rpc_client/pytestplugin.py
+++ b/deltachat-rpc-client/src/deltachat_rpc_client/pytestplugin.py
@@ -54,7 +54,7 @@ class ACFactory:
account.start_io()
while True:
event = account.wait_for_event()
- if event.type == EventType.IMAP_INBOX_IDLE:
+ if event.kind == EventType.IMAP_INBOX_IDLE:
break
return account
@@ -95,7 +95,7 @@ class ACFactory:
group=group,
)
- return to_client.run_until(lambda e: e.type == EventType.INCOMING_MSG)
+ return to_client.run_until(lambda e: e.kind == EventType.INCOMING_MSG)
@pytest.fixture()
diff --git a/deltachat-rpc-client/tests/test_something.py b/deltachat-rpc-client/tests/test_something.py
index b667d5c00..f91f9844e 100644
--- a/deltachat-rpc-client/tests/test_something.py
+++ b/deltachat-rpc-client/tests/test_something.py
@@ -1,4 +1,6 @@
import concurrent.futures
+import json
+import subprocess
from unittest.mock import MagicMock
import pytest
@@ -42,7 +44,7 @@ def test_acfactory(acfactory) -> None:
account = acfactory.new_configured_account()
while True:
event = account.wait_for_event()
- if event.type == EventType.CONFIGURE_PROGRESS:
+ if event.kind == EventType.CONFIGURE_PROGRESS:
assert event.progress != 0 # Progress 0 indicates error.
if event.progress == 1000: # Success
break
@@ -71,7 +73,7 @@ def test_account(acfactory) -> None:
while True:
event = bob.wait_for_event()
- if event.type == EventType.INCOMING_MSG:
+ if event.kind == EventType.INCOMING_MSG:
chat_id = event.chat_id
msg_id = event.msg_id
break
@@ -140,7 +142,7 @@ def test_chat(acfactory) -> None:
while True:
event = bob.wait_for_event()
- if event.type == EventType.INCOMING_MSG:
+ if event.kind == EventType.INCOMING_MSG:
chat_id = event.chat_id
msg_id = event.msg_id
break
@@ -224,7 +226,7 @@ def test_message(acfactory) -> None:
while True:
event = bob.wait_for_event()
- if event.type == EventType.INCOMING_MSG:
+ if event.kind == EventType.INCOMING_MSG:
chat_id = event.chat_id
msg_id = event.msg_id
break
@@ -263,7 +265,7 @@ def test_is_bot(acfactory) -> None:
while True:
event = bob.wait_for_event()
- if event.type == EventType.INCOMING_MSG:
+ if event.kind == EventType.INCOMING_MSG:
msg_id = event.msg_id
message = bob.get_message_by_id(msg_id)
snapshot = message.get_snapshot()
@@ -346,3 +348,13 @@ def test_import_export(acfactory, tmp_path) -> None:
files = list(tmp_path.glob("*.tar"))
alice2 = acfactory.get_unconfigured_account()
alice2.import_backup(files[0])
+
+ assert alice2.manager.get_system_info()
+
+
+def test_openrpc_command_line() -> None:
+ """Test that "deltachat-rpc-server --openrpc" command returns an OpenRPC specification."""
+ out = subprocess.run(["deltachat-rpc-server", "--openrpc"], capture_output=True, check=True).stdout
+ openrpc = json.loads(out)
+ assert "openrpc" in openrpc
+ assert "methods" in openrpc
diff --git a/deltachat-rpc-client/tests/test_webxdc.py b/deltachat-rpc-client/tests/test_webxdc.py
index cb62566e4..8f3d8edcb 100644
--- a/deltachat-rpc-client/tests/test_webxdc.py
+++ b/deltachat-rpc-client/tests/test_webxdc.py
@@ -11,7 +11,7 @@ def test_webxdc(acfactory) -> None:
while True:
event = bob.wait_for_event()
- if event.type == EventType.INCOMING_MSG:
+ if event.kind == EventType.INCOMING_MSG:
bob_chat_alice = bob.get_chat_by_id(event.chat_id)
message = bob.get_message_by_id(event.msg_id)
break
diff --git a/deltachat-rpc-server/Cargo.toml b/deltachat-rpc-server/Cargo.toml
index b30e67b83..6315c5b89 100644
--- a/deltachat-rpc-server/Cargo.toml
+++ b/deltachat-rpc-server/Cargo.toml
@@ -17,11 +17,11 @@ anyhow = "1"
env_logger = { version = "0.10.0" }
futures-lite = "1.13.0"
log = "0.4"
-serde_json = "1.0.99"
+serde_json = "1.0.105"
serde = { version = "1.0", features = ["derive"] }
tokio = { version = "1.33.0", features = ["io-std"] }
-tokio-util = "0.7.8"
-yerpc = { version = "0.5.1", features = ["anyhow_expose"] }
+tokio-util = "0.7.9"
+yerpc = { version = "0.5.2", features = ["anyhow_expose", "openrpc"] }
[features]
default = ["vendored"]
diff --git a/deltachat-rpc-server/README.md b/deltachat-rpc-server/README.md
index 2027072a5..be8c0c562 100644
--- a/deltachat-rpc-server/README.md
+++ b/deltachat-rpc-server/README.md
@@ -32,3 +32,6 @@ languages other than Rust, for example:
1. Python: https://github.com/deltachat/deltachat-core-rust/tree/master/deltachat-rpc-client/
2. Go: https://github.com/deltachat/deltachat-rpc-client-go/
+
+Run `deltachat-rpc-server --version` to check the version of the server.
+Run `deltachat-rpc-server --openrpc` to get [OpenRPC](https://open-rpc.org/) specification of the provided JSON-RPC API.
diff --git a/deltachat-rpc-server/src/main.rs b/deltachat-rpc-server/src/main.rs
index e3d0b0b20..4be58760b 100644
--- a/deltachat-rpc-server/src/main.rs
+++ b/deltachat-rpc-server/src/main.rs
@@ -10,6 +10,7 @@ use deltachat::constants::DC_VERSION_STR;
use deltachat_jsonrpc::api::{Accounts, CommandApi};
use futures_lite::stream::StreamExt;
use tokio::io::{self, AsyncBufReadExt, BufReader};
+use yerpc::RpcServer as _;
#[cfg(target_family = "unix")]
use tokio::signal::unix as signal_unix;
@@ -39,6 +40,12 @@ async fn main_impl() -> Result<()> {
}
eprintln!("{}", &*DC_VERSION_STR);
return Ok(());
+ } else if first_arg.to_str() == Some("--openrpc") {
+ if let Some(arg) = args.next() {
+ return Err(anyhow!("Unrecognized argument {:?}", arg));
+ }
+ println!("{}", CommandApi::openrpc_specification()?);
+ return Ok(());
} else {
return Err(anyhow!("Unrecognized option {:?}", first_arg));
}
@@ -56,7 +63,8 @@ async fn main_impl() -> Result<()> {
let path = std::env::var("DC_ACCOUNTS_PATH").unwrap_or_else(|_| "accounts".to_string());
log::info!("Starting with accounts directory `{}`.", path);
- let accounts = Accounts::new(PathBuf::from(&path)).await?;
+ let writable = true;
+ let accounts = Accounts::new(PathBuf::from(&path), writable).await?;
log::info!("Creating JSON-RPC API.");
let accounts = Arc::new(RwLock::new(accounts));
diff --git a/deny.toml b/deny.toml
index 2231b94c1..22d06bab4 100644
--- a/deny.toml
+++ b/deny.toml
@@ -24,11 +24,10 @@ skip = [
{ name = "digest", version = "<0.10" },
{ name = "ed25519-dalek", version = "1.0.1" },
{ name = "ed25519", version = "1.5.3" },
+ { name = "fastrand", version = "1.9.0" },
{ name = "getrandom", version = "<0.2" },
{ name = "hashbrown", version = "<0.14.0" },
{ name = "indexmap", version = "<2.0.0" },
- { name = "linux-raw-sys", version = "0.3.8" },
- { name = "num-derive", version = "0.3.3" },
{ name = "pem-rfc7468", version = "0.6.0" },
{ name = "pkcs8", version = "0.9.0" },
{ name = "quick-error", version = "<2.0" },
@@ -36,8 +35,8 @@ skip = [
{ name = "rand_core", version = "<0.6" },
{ name = "rand", version = "<0.8" },
{ name = "redox_syscall", version = "0.2.16" },
+ { name = "regex-automata", version = "0.1.10" },
{ name = "regex-syntax", version = "0.6.29" },
- { name = "rustix", version = "0.37.21" },
{ name = "sec1", version = "0.3.0" },
{ name = "sha2", version = "<0.10" },
{ name = "signature", version = "1.6.4" },
@@ -53,11 +52,10 @@ skip = [
{ name = "windows_i686_msvc", version = "<0.48" },
{ name = "windows-sys", version = "<0.48" },
{ name = "windows-targets", version = "<0.48" },
- { name = "windows_x86_64_gnullvm", version = "<0.48" },
{ name = "windows", version = "0.32.0" },
+ { name = "windows_x86_64_gnullvm", version = "<0.48" },
{ name = "windows_x86_64_gnu", version = "<0.48" },
{ name = "windows_x86_64_msvc", version = "<0.48" },
- { name = "winreg", version = "0.10.1" },
]
diff --git a/examples/simple.rs b/examples/simple.rs
deleted file mode 100644
index cca50bb27..000000000
--- a/examples/simple.rs
+++ /dev/null
@@ -1,100 +0,0 @@
-use deltachat::chat::{self, ChatId};
-use deltachat::chatlist::*;
-use deltachat::config;
-use deltachat::contact::*;
-use deltachat::context::*;
-use deltachat::message::Message;
-use deltachat::stock_str::StockStrings;
-use deltachat::{EventType, Events};
-use tempfile::tempdir;
-
-fn cb(event: EventType) {
- match event {
- EventType::ConfigureProgress { progress, .. } => {
- log::info!("progress: {}", progress);
- }
- EventType::Info(msg) => {
- log::info!("{}", msg);
- }
- EventType::Warning(msg) => {
- log::warn!("{}", msg);
- }
- EventType::Error(msg) => {
- log::error!("{}", msg);
- }
- event => {
- log::info!("{:?}", event);
- }
- }
-}
-
-/// Run with `RUST_LOG=simple=info cargo run --release --example simple -- email pw`.
-#[tokio::main]
-async fn main() {
- pretty_env_logger::try_init_timed().ok();
-
- let dir = tempdir().unwrap();
- let dbfile = dir.path().join("db.sqlite");
- log::info!("creating database {:?}", dbfile);
- let ctx = Context::new(&dbfile, 0, Events::new(), StockStrings::new())
- .await
- .expect("Failed to create context");
- let info = ctx.get_info().await;
- log::info!("info: {:#?}", info);
-
- let events = ctx.get_event_emitter();
- let events_spawn = tokio::task::spawn(async move {
- while let Some(event) = events.recv().await {
- cb(event.typ);
- }
- });
-
- log::info!("configuring");
- let args = std::env::args().collect::>();
- assert_eq!(args.len(), 3, "requires email password");
- let email = args[1].clone();
- let pw = args[2].clone();
- ctx.set_config(config::Config::Addr, Some(&email))
- .await
- .unwrap();
- ctx.set_config(config::Config::MailPw, Some(&pw))
- .await
- .unwrap();
-
- ctx.configure().await.unwrap();
-
- log::info!("------ RUN ------");
- ctx.start_io().await;
- log::info!("--- SENDING A MESSAGE ---");
-
- let contact_id = Contact::create(&ctx, "dignifiedquire", "dignifiedquire@gmail.com")
- .await
- .unwrap();
- let chat_id = ChatId::create_for_contact(&ctx, contact_id).await.unwrap();
-
- for i in 0..1 {
- log::info!("sending message {}", i);
- chat::send_text_msg(&ctx, chat_id, format!("Hi, here is my {i}nth message!"))
- .await
- .unwrap();
- }
-
- // wait for the message to be sent out
- tokio::time::sleep(std::time::Duration::from_secs(1)).await;
-
- log::info!("fetching chats..");
- let chats = Chatlist::try_load(&ctx, 0, None, None).await.unwrap();
-
- for i in 0..chats.len() {
- let msg = Message::load_from_db(&ctx, chats.get_msg_id(i).unwrap().unwrap())
- .await
- .unwrap();
- log::info!("[{}] msg: {:?}", i, msg);
- }
-
- log::info!("stopping");
- ctx.stop_io().await;
- log::info!("closing");
- drop(ctx);
- events_spawn.await.unwrap();
-}
diff --git a/fuzz/Cargo.lock b/fuzz/Cargo.lock
index 097b7ac9b..feb204237 100644
--- a/fuzz/Cargo.lock
+++ b/fuzz/Cargo.lock
@@ -177,13 +177,13 @@ dependencies = [
[[package]]
name = "async-imap"
-version = "0.9.0"
+version = "0.9.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "da93622739d458dd9a6abc1abf0e38e81965a5824a3b37f9500437c82a8bb572"
+checksum = "b538b767cbf9c162a6c5795d4b932bd2c20ba10b5a91a94d2b2b6886c1dce6a8"
dependencies = [
"async-channel",
"base64 0.21.0",
- "byte-pool",
+ "bytes",
"chrono",
"futures",
"imap-proto",
@@ -337,9 +337,9 @@ checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a"
[[package]]
name = "bitflags"
-version = "2.0.2"
+version = "2.4.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "487f1e0fcbe47deb8b0574e646def1c903389d95241dd1bbcc6ce4a715dfc0c1"
+checksum = "b4682ae6287fcf752ecaabbfcc7b6f9b72aa33933dc23a554d853aea8eea8635"
[[package]]
name = "blake3"
@@ -529,16 +529,6 @@ version = "3.11.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "572f695136211188308f16ad2ca5c851a712c464060ae6974944458eb83880ba"
-[[package]]
-name = "byte-pool"
-version = "0.2.4"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "c2f1b21189f50b5625efa6227cf45e9d4cfdc2e73582df2b879e9689e78a7158"
-dependencies = [
- "crossbeam-queue",
- "stable_deref_trait",
-]
-
[[package]]
name = "bytemuck"
version = "1.12.3"
@@ -741,16 +731,6 @@ dependencies = [
"cfg-if",
]
-[[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.14"
@@ -926,7 +906,7 @@ dependencies = [
[[package]]
name = "deltachat"
-version = "1.117.0"
+version = "1.123.0"
dependencies = [
"anyhow",
"async-channel",
@@ -943,6 +923,7 @@ dependencies = [
"encoded-words",
"escaper",
"fast-socks5",
+ "fd-lock",
"format-flowed",
"futures",
"futures-lite",
@@ -955,7 +936,7 @@ dependencies = [
"libc",
"mailparse 0.14.0",
"mime",
- "num-derive",
+ "num-derive 0.4.0",
"num-traits",
"num_cpus",
"once_cell",
@@ -1429,14 +1410,14 @@ checksum = "b5320ae4c3782150d900b79807611a59a99fc9a1d61d686faafc24b93fc8d7ca"
[[package]]
name = "enum-as-inner"
-version = "0.5.1"
+version = "0.6.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "c9720bba047d567ffc8a3cba48bf19126600e249ab7f128e9233e6376976a116"
+checksum = "5ffccbb6966c05b32ef8fbac435df276c4ae4d3dc55a8cd0eb9745e6c12f546a"
dependencies = [
"heck",
"proc-macro2",
"quote",
- "syn 1.0.107",
+ "syn 2.0.15",
]
[[package]]
@@ -1464,6 +1445,17 @@ dependencies = [
"winapi",
]
+[[package]]
+name = "errno"
+version = "0.3.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "136526188508e25c6fef639d7927dfb3e0e3084488bf202267829cf7fc23dbdd"
+dependencies = [
+ "errno-dragonfly",
+ "libc",
+ "windows-sys 0.48.0",
+]
+
[[package]]
name = "errno-dragonfly"
version = "0.1.2"
@@ -1532,6 +1524,17 @@ dependencies = [
"instant",
]
+[[package]]
+name = "fd-lock"
+version = "3.0.13"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "ef033ed5e9bad94e55838ca0ca906db0e043f517adda0c8b79c7a8c66c93c1b5"
+dependencies = [
+ "cfg-if",
+ "rustix 0.38.14",
+ "windows-sys 0.48.0",
+]
+
[[package]]
name = "ff"
version = "0.12.1"
@@ -1616,9 +1619,9 @@ checksum = "00b0228411908ca8685dba7fc2cdd70ec9990a6e753e89b6ac91a84c40fbaf4b"
[[package]]
name = "form_urlencoded"
-version = "1.1.0"
+version = "1.2.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "a9c384f161156f5260c24a097c56119f9be8c798586aecc13afbcbe7b7e26bf8"
+checksum = "a62bc1cf6f830c2ec14a513a9fb124d0a213a629668a4186f329db21fe045652"
dependencies = [
"percent-encoding",
]
@@ -1843,18 +1846,15 @@ dependencies = [
[[package]]
name = "heck"
-version = "0.4.0"
+version = "0.4.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "2540771e65fc8cb83cd6e8a237f70c319bd5c29f78ed1084ba5d50eeac86f7f9"
+checksum = "95505c38b4572b2d910cecb0281560f54b440a19336cbbcb27bf6ce6adc6f5a8"
[[package]]
name = "hermit-abi"
-version = "0.2.6"
+version = "0.3.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "ee512640fe35acbfb4bb779db6f0d80704c2cacfa2e39b601ef3e3f47d1ae4c7"
-dependencies = [
- "libc",
-]
+checksum = "d77f7ec81a6d05a3abb01ab6eb7590f6083d08449fe5a1c8b1e620283546ccb7"
[[package]]
name = "hex"
@@ -1951,7 +1951,7 @@ dependencies = [
"httpdate",
"itoa",
"pin-project-lite",
- "socket2",
+ "socket2 0.4.7",
"tokio",
"tower-service",
"tracing",
@@ -2012,20 +2012,9 @@ checksum = "b9e0384b61958566e926dc50660321d12159025e767c18e043daf26b70104c39"
[[package]]
name = "idna"
-version = "0.2.3"
+version = "0.4.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "418a0a6fab821475f634efe3ccc45c013f742efe03d853e8d3355d5cb850ecf8"
-dependencies = [
- "matches",
- "unicode-bidi",
- "unicode-normalization",
-]
-
-[[package]]
-name = "idna"
-version = "0.3.0"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "e14ddfc70884202db2244c223200c204c2bda1bc6e0998d11b5e024d657209e6"
+checksum = "7d20d6b07bfbc108882d88ed8e37d39636dcc260e15e30c45e6ba089610b917c"
dependencies = [
"unicode-bidi",
"unicode-normalization",
@@ -2033,9 +2022,9 @@ dependencies = [
[[package]]
name = "image"
-version = "0.24.6"
+version = "0.24.7"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "527909aa81e20ac3a44803521443a765550f09b5130c2c2fa1ea59c2f8f50a3a"
+checksum = "6f3dfdbdd72063086ff443e297b61695500514b1e41095b6fb9a5ab48a70a711"
dependencies = [
"bytemuck",
"byteorder",
@@ -2100,10 +2089,10 @@ version = "0.3.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "bd302af1b90f2463a98fa5ad469fc212c8e3175a41c3068601bfa2727591c5be"
dependencies = [
- "socket2",
+ "socket2 0.4.7",
"widestring",
"winapi",
- "winreg",
+ "winreg 0.10.1",
]
[[package]]
@@ -2230,9 +2219,9 @@ dependencies = [
[[package]]
name = "libc"
-version = "0.2.139"
+version = "0.2.148"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "201de327520df007757c1f0adce6e827fe8562fbc28bfd9c15571c66ca1f5f79"
+checksum = "9cdc71e17332e86d2e1d38c1f99edcb6288ee11b815fb1a4b049eaa2114d369b"
[[package]]
name = "libm"
@@ -2279,6 +2268,12 @@ version = "0.1.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f051f77a7c8e6957c0696eac88f26b0117e54f52d3fc682ab19397a8812846a4"
+[[package]]
+name = "linux-raw-sys"
+version = "0.4.7"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "1a9bad9f94746442c783ca431b22403b519cd7fbeed0533fdd6328b2f2212128"
+
[[package]]
name = "lock_api"
version = "0.4.9"
@@ -2341,15 +2336,9 @@ version = "0.1.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8263075bb86c5a1b1427b5ae862e8889656f126e9f77c484496e8b47cf5c5558"
dependencies = [
- "regex-automata",
+ "regex-automata 0.1.10",
]
-[[package]]
-name = "matches"
-version = "0.1.9"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "a3e378b66a060d48947b590737b30a1be76706c8dd7b8ba0f2fe3989c68a853f"
-
[[package]]
name = "md-5"
version = "0.10.5"
@@ -2367,9 +2356,9 @@ checksum = "df39d232f5c40b0891c10216992c2f250c054105cb1e56f0fc9032db6203ecc1"
[[package]]
name = "memchr"
-version = "2.5.0"
+version = "2.6.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "2dffe52ecf27772e601905b7522cb4ef790d2cc203488bbd0e2fe85fcb74566d"
+checksum = "8f232d6ef707e1956a43342693d2a31e72989554d58299d7a88738cc95b0d35c"
[[package]]
name = "mime"
@@ -2394,14 +2383,13 @@ dependencies = [
[[package]]
name = "mio"
-version = "0.8.5"
+version = "0.8.8"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "e5d732bc30207a6423068df043e3d02e0735b155ad7ce1a6f76fe2baa5b158de"
+checksum = "927a765cd3fc26206e66b296465fa9d3e5ab003e651c1b3c060e7956d96b19d2"
dependencies = [
"libc",
- "log",
"wasi 0.11.0+wasi-snapshot-preview1",
- "windows-sys 0.42.0",
+ "windows-sys 0.48.0",
]
[[package]]
@@ -2555,6 +2543,17 @@ dependencies = [
"syn 1.0.107",
]
+[[package]]
+name = "num-derive"
+version = "0.4.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "9e6a0fd4f737c707bd9086cc16c925f294943eb62eb71499e9fd4cf71f8b9f4e"
+dependencies = [
+ "proc-macro2",
+ "quote",
+ "syn 2.0.15",
+]
+
[[package]]
name = "num-integer"
version = "0.1.45"
@@ -2599,9 +2598,9 @@ dependencies = [
[[package]]
name = "num_cpus"
-version = "1.15.0"
+version = "1.16.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "0fac9e2da13b5eb447a6ce3d392f23a29d8694bff781bf03a16cd9ac8697593b"
+checksum = "4161fcb6d602d4d2081af7c3a45852d875a03dd337a6bfdd6e06407b61342a43"
dependencies = [
"hermit-abi",
"libc",
@@ -2854,7 +2853,7 @@ dependencies = [
"md-5",
"nom",
"num-bigint-dig",
- "num-derive",
+ "num-derive 0.3.3",
"num-traits",
"p256 0.13.1",
"p384 0.13.0",
@@ -2894,9 +2893,9 @@ dependencies = [
[[package]]
name = "pin-project-lite"
-version = "0.2.9"
+version = "0.2.13"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "e0a7ae3ac2f1173085d398531c705756c94a4c56843785df85a60c1a0afac116"
+checksum = "8afb450f006bf6385ca15ef45d71d2288452bc3683ce2e2cacc0d18e4be60b58"
[[package]]
name = "pin-utils"
@@ -3087,9 +3086,9 @@ checksum = "a1d01941d82fa2ab50be1e79e6714289dd7cde78eba4c074bc5a4374f650dfe0"
[[package]]
name = "quick-xml"
-version = "0.28.2"
+version = "0.30.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "0ce5e73202a820a31f8a0ee32ada5e21029c81fd9e3ebf668a40832e4219d9d1"
+checksum = "eff6510e86862b57b210fd8cbe8ed3f0d7d600b9c2863cd4549a2e033c66e956"
dependencies = [
"memchr",
]
@@ -3114,9 +3113,9 @@ dependencies = [
[[package]]
name = "quinn-proto"
-version = "0.9.2"
+version = "0.9.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "72ef4ced82a24bb281af338b9e8f94429b6eca01b4e66d899f40031f074e74c9"
+checksum = "c956be1b23f4261676aed05a0046e204e8a6836e50203902683a718af0797989"
dependencies = [
"bytes",
"rand 0.8.5",
@@ -3139,7 +3138,7 @@ checksum = "641538578b21f5e5c8ea733b736895576d0fe329bb883b937db6f4d163dbaaf4"
dependencies = [
"libc",
"quinn-proto",
- "socket2",
+ "socket2 0.4.7",
"tracing",
"windows-sys 0.42.0",
]
@@ -3268,13 +3267,14 @@ dependencies = [
[[package]]
name = "regex"
-version = "1.8.4"
+version = "1.9.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "d0ab3ca65655bb1e41f2a8c8cd662eb4fb035e67c3f78da1d61dffe89d07300f"
+checksum = "697061221ea1b4a94a624f67d0ae2bfe4e22b8a17b6a192afb11046542cc8c47"
dependencies = [
"aho-corasick",
"memchr",
- "regex-syntax 0.7.2",
+ "regex-automata 0.3.8",
+ "regex-syntax 0.7.5",
]
[[package]]
@@ -3286,6 +3286,17 @@ dependencies = [
"regex-syntax 0.6.28",
]
+[[package]]
+name = "regex-automata"
+version = "0.3.8"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "c2f401f4955220693b56f8ec66ee9c78abffd8d1c4f23dc41a23839eb88f0795"
+dependencies = [
+ "aho-corasick",
+ "memchr",
+ "regex-syntax 0.7.5",
+]
+
[[package]]
name = "regex-syntax"
version = "0.6.28"
@@ -3294,15 +3305,15 @@ checksum = "456c603be3e8d448b072f410900c09faf164fbce2d480456f50eea6e25f9c848"
[[package]]
name = "regex-syntax"
-version = "0.7.2"
+version = "0.7.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "436b050e76ed2903236f032a59761c1eb99e1b0aead2c257922771dab1fc8c78"
+checksum = "dbb5fb1acd8a1a18b3dd5be62d25485eb770e05afb408a9627d14d451bae12da"
[[package]]
name = "reqwest"
-version = "0.11.18"
+version = "0.11.20"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "cde824a14b7c14f85caff81225f411faacc04a2013f41670f41443742b1c1c55"
+checksum = "3e9ad3fe7488d7e34558a2033d45a0c90b72d97b4f80705666fea71472e2e6a1"
dependencies = [
"base64 0.21.0",
"bytes",
@@ -3332,7 +3343,7 @@ dependencies = [
"wasm-bindgen",
"wasm-bindgen-futures",
"web-sys",
- "winreg",
+ "winreg 0.50.0",
]
[[package]]
@@ -3438,7 +3449,7 @@ version = "0.29.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "549b9d036d571d42e6e85d1c1425e2ac83491075078ca9a15be021c56b1641f2"
dependencies = [
- "bitflags 2.0.2",
+ "bitflags 2.4.0",
"fallible-iterator",
"fallible-streaming-iterator",
"hashlink",
@@ -3489,13 +3500,26 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d4fdebc4b395b7fbb9ab11e462e20ed9051e7b16e42d24042c776eca0ac81b03"
dependencies = [
"bitflags 1.3.2",
- "errno",
+ "errno 0.2.8",
"io-lifetimes",
"libc",
- "linux-raw-sys",
+ "linux-raw-sys 0.1.4",
"windows-sys 0.42.0",
]
+[[package]]
+name = "rustix"
+version = "0.38.14"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "747c788e9ce8e92b12cd485c49ddf90723550b654b32508f979b71a7b1ecda4f"
+dependencies = [
+ "bitflags 2.4.0",
+ "errno 0.3.3",
+ "libc",
+ "linux-raw-sys 0.4.7",
+ "windows-sys 0.48.0",
+]
+
[[package]]
name = "rustls"
version = "0.20.8"
@@ -3557,9 +3581,9 @@ dependencies = [
[[package]]
name = "sanitize-filename"
-version = "0.4.0"
+version = "0.5.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "08c502bdb638f1396509467cb0580ef3b29aa2a45c5d43e5d84928241280296c"
+checksum = "2ed72fbaf78e6f2d41744923916966c4fbe3d7c74e3037a8ee482f1115572603"
dependencies = [
"lazy_static",
"regex",
@@ -3855,6 +3879,16 @@ dependencies = [
"winapi",
]
+[[package]]
+name = "socket2"
+version = "0.5.4"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "4031e820eb552adee9295814c0ced9e5cf38ddf1e8b7d566d6de8e2538ea989e"
+dependencies = [
+ "libc",
+ "windows-sys 0.48.0",
+]
+
[[package]]
name = "spin"
version = "0.5.2"
@@ -3919,12 +3953,6 @@ dependencies = [
"zeroize",
]
-[[package]]
-name = "stable_deref_trait"
-version = "1.2.0"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "a8f112729512f8e442d81f95a8a7ddf2b7c6b8a1a6f509a95864142b30cab2d3"
-
[[package]]
name = "stop-token"
version = "0.7.0"
@@ -3945,21 +3973,21 @@ checksum = "73473c0e59e6d5812c5dfe2a064a6444949f089e20eec9a2e5506596494e4623"
[[package]]
name = "strum"
-version = "0.24.1"
+version = "0.25.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "063e6045c0e62079840579a7e47a355ae92f60eb74daaf156fb1e84ba164e63f"
+checksum = "290d54ea6f91c969195bdbcd7442c8c2a2ba87da8bf60a7ee86a235d4bc1e125"
[[package]]
name = "strum_macros"
-version = "0.24.3"
+version = "0.25.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "1e385be0d24f186b4ce2f9982191e7101bb737312ad61c1f2f984f34bcf85d59"
+checksum = "ad8d03b598d3d0fff69bf533ee3ef19b8eeb342729596df84bcc7e1f96ec4059"
dependencies = [
"heck",
"proc-macro2",
"quote",
"rustversion",
- "syn 1.0.107",
+ "syn 2.0.15",
]
[[package]]
@@ -4038,7 +4066,7 @@ dependencies = [
"cfg-if",
"fastrand",
"redox_syscall",
- "rustix",
+ "rustix 0.36.7",
"windows-sys 0.42.0",
]
@@ -4147,22 +4175,21 @@ checksum = "cda74da7e1a664f795bb1f8a87ec406fb89a02522cf6e50620d016add6dbbf5c"
[[package]]
name = "tokio"
-version = "1.25.0"
+version = "1.32.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "c8e00990ebabbe4c14c08aca901caed183ecd5c09562a12c824bb53d3c3fd3af"
+checksum = "17ed6077ed6cd6c74735e21f37eb16dc3935f96878b1fe961074089cc80893f9"
dependencies = [
- "autocfg",
+ "backtrace",
"bytes",
"libc",
- "memchr",
"mio",
"num_cpus",
"parking_lot",
"pin-project-lite",
"signal-hook-registry",
- "socket2",
+ "socket2 0.5.4",
"tokio-macros",
- "windows-sys 0.42.0",
+ "windows-sys 0.48.0",
]
[[package]]
@@ -4177,13 +4204,13 @@ dependencies = [
[[package]]
name = "tokio-macros"
-version = "1.8.2"
+version = "2.1.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "d266c00fde287f55d3f1c3e96c500c362a2b8c695076ec180f27918820bc6df8"
+checksum = "630bdcf245f78637c13ec01ffae6187cca34625e8c63150d424b59e55af2675e"
dependencies = [
"proc-macro2",
"quote",
- "syn 1.0.107",
+ "syn 2.0.15",
]
[[package]]
@@ -4239,9 +4266,9 @@ dependencies = [
[[package]]
name = "tokio-util"
-version = "0.7.8"
+version = "0.7.9"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "806fe8c2c87eccc8b3267cbae29ed3ab2d0bd37fca70ab622e46aaa9375ddb7d"
+checksum = "1d68074620f57a0b21594d9735eb2e98ab38b17f80d3fcb189fca266771ca60d"
dependencies = [
"bytes",
"futures-core",
@@ -4365,9 +4392,9 @@ dependencies = [
[[package]]
name = "trust-dns-proto"
-version = "0.22.0"
+version = "0.23.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "4f7f83d1e4a0e4358ac54c5c3681e5d7da5efc5a7a632c90bb6d6669ddd9bc26"
+checksum = "0dc775440033cb114085f6f2437682b194fa7546466024b1037e82a48a052a69"
dependencies = [
"async-trait",
"cfg-if",
@@ -4376,9 +4403,9 @@ dependencies = [
"futures-channel",
"futures-io",
"futures-util",
- "idna 0.2.3",
+ "idna",
"ipnet",
- "lazy_static",
+ "once_cell",
"rand 0.8.5",
"smallvec",
"thiserror",
@@ -4390,16 +4417,17 @@ dependencies = [
[[package]]
name = "trust-dns-resolver"
-version = "0.22.0"
+version = "0.23.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "aff21aa4dcefb0a1afbfac26deb0adc93888c7d295fb63ab273ef276ba2b7cfe"
+checksum = "2dff7aed33ef3e8bf2c9966fccdfed93f93d46f432282ea875cd66faabc6ef2f"
dependencies = [
"cfg-if",
"futures-util",
"ipconfig",
- "lazy_static",
"lru-cache",
+ "once_cell",
"parking_lot",
+ "rand 0.8.5",
"resolv-conf",
"smallvec",
"thiserror",
@@ -4431,9 +4459,9 @@ checksum = "497961ef93d974e23eb6f433eb5fe1b7930b659f06d12dec6fc44a8f554c0bba"
[[package]]
name = "unicode-bidi"
-version = "0.3.8"
+version = "0.3.13"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "099b7128301d285f79ddd55b9a83d5e6b9e97c92e0ea0daebee7263e932de992"
+checksum = "92888ba5573ff080736b3648696b70cafad7d250551175acbaa4e0385b3e1460"
[[package]]
name = "unicode-ident"
@@ -4480,12 +4508,12 @@ checksum = "a156c684c91ea7d62626509bce3cb4e1d9ed5c4d978f7b4352658f96a4c26b4a"
[[package]]
name = "url"
-version = "2.3.1"
+version = "2.4.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "0d68c799ae75762b8c3fe375feb6600ef5602c883c5d21eb51c09f22b83c4643"
+checksum = "143b538f18257fac9cad154828a57c6bf5157e1aa604d4816b5995bf6de87ae5"
dependencies = [
"form_urlencoded",
- "idna 0.3.0",
+ "idna",
"percent-encoding",
]
@@ -4648,9 +4676,9 @@ dependencies = [
[[package]]
name = "webpki"
-version = "0.22.0"
+version = "0.22.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "f095d78192e208183081cc07bc5515ef55216397af48b873e5edcd72637fa1bd"
+checksum = "07ecc0cd7cac091bf682ec5efa18b1cff79d617b84181f38b3951dbe135f607f"
dependencies = [
"ring",
"untrusted",
@@ -4731,21 +4759,51 @@ version = "0.42.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5a3e1820f08b8513f676f7ab6c1f99ff312fb97b553d30ff4dd86f9f15728aa7"
dependencies = [
- "windows_aarch64_gnullvm",
+ "windows_aarch64_gnullvm 0.42.0",
"windows_aarch64_msvc 0.42.0",
"windows_i686_gnu 0.42.0",
"windows_i686_msvc 0.42.0",
"windows_x86_64_gnu 0.42.0",
- "windows_x86_64_gnullvm",
+ "windows_x86_64_gnullvm 0.42.0",
"windows_x86_64_msvc 0.42.0",
]
+[[package]]
+name = "windows-sys"
+version = "0.48.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "677d2418bec65e3338edb076e806bc1ec15693c5d0104683f2efe857f61056a9"
+dependencies = [
+ "windows-targets",
+]
+
+[[package]]
+name = "windows-targets"
+version = "0.48.5"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "9a2fa6e2155d7247be68c096456083145c183cbbbc2764150dda45a87197940c"
+dependencies = [
+ "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_aarch64_gnullvm"
version = "0.42.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "41d2aa71f6f0cbe00ae5167d90ef3cfe66527d6f613ca78ac8024c3ccab9a19e"
+[[package]]
+name = "windows_aarch64_gnullvm"
+version = "0.48.5"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "2b38e32f0abccf9987a4e3079dfb67dcd799fb61361e53e2882c3cbaf0d905d8"
+
[[package]]
name = "windows_aarch64_msvc"
version = "0.32.0"
@@ -4764,6 +4822,12 @@ version = "0.42.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "dd0f252f5a35cac83d6311b2e795981f5ee6e67eb1f9a7f64eb4500fbc4dcdb4"
+[[package]]
+name = "windows_aarch64_msvc"
+version = "0.48.5"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "dc35310971f3b2dbbf3f0690a219f40e2d9afcf64f9ab7cc1be722937c26b4bc"
+
[[package]]
name = "windows_i686_gnu"
version = "0.32.0"
@@ -4782,6 +4846,12 @@ version = "0.42.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "fbeae19f6716841636c28d695375df17562ca208b2b7d0dc47635a50ae6c5de7"
+[[package]]
+name = "windows_i686_gnu"
+version = "0.48.5"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "a75915e7def60c94dcef72200b9a8e58e5091744960da64ec734a6c6e9b3743e"
+
[[package]]
name = "windows_i686_msvc"
version = "0.32.0"
@@ -4800,6 +4870,12 @@ version = "0.42.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "84c12f65daa39dd2babe6e442988fc329d6243fdce47d7d2d155b8d874862246"
+[[package]]
+name = "windows_i686_msvc"
+version = "0.48.5"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "8f55c233f70c4b27f66c523580f78f1004e8b5a8b659e05a4eb49d4166cca406"
+
[[package]]
name = "windows_x86_64_gnu"
version = "0.32.0"
@@ -4818,12 +4894,24 @@ version = "0.42.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "bf7b1b21b5362cbc318f686150e5bcea75ecedc74dd157d874d754a2ca44b0ed"
+[[package]]
+name = "windows_x86_64_gnu"
+version = "0.48.5"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "53d40abd2583d23e4718fddf1ebec84dbff8381c07cae67ff7768bbf19c6718e"
+
[[package]]
name = "windows_x86_64_gnullvm"
version = "0.42.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "09d525d2ba30eeb3297665bd434a54297e4170c7f1a44cad4ef58095b4cd2028"
+[[package]]
+name = "windows_x86_64_gnullvm"
+version = "0.48.5"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "0b7b52767868a23d5bab768e390dc5f5c55825b6d30b86c844ff2dc7414044cc"
+
[[package]]
name = "windows_x86_64_msvc"
version = "0.32.0"
@@ -4842,6 +4930,12 @@ version = "0.42.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f40009d85759725a34da6d89a94e63d7bdc50a862acf0dbc7c8e488f1edcb6f5"
+[[package]]
+name = "windows_x86_64_msvc"
+version = "0.48.5"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "ed94fce61571a4006852b7389a063ab983c02eb1bb37b47f8272ce92d06d9538"
+
[[package]]
name = "winreg"
version = "0.10.1"
@@ -4851,6 +4945,16 @@ dependencies = [
"winapi",
]
+[[package]]
+name = "winreg"
+version = "0.50.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "524e57b2c537c0f9b1e69f1965311ec12182b4122e45035b1508cd24d2adadb1"
+dependencies = [
+ "cfg-if",
+ "windows-sys 0.48.0",
+]
+
[[package]]
name = "x25519-dalek"
version = "2.0.0-pre.1"
diff --git a/node/constants.js b/node/constants.js
index dcbe52daf..b9c990c77 100644
--- a/node/constants.js
+++ b/node/constants.js
@@ -159,6 +159,8 @@ module.exports = {
DC_STR_BROADCAST_LIST: 115,
DC_STR_CANNOT_LOGIN: 60,
DC_STR_CANTDECRYPT_MSG_BODY: 29,
+ DC_STR_CHAT_PROTECTION_DISABLED: 171,
+ DC_STR_CHAT_PROTECTION_ENABLED: 170,
DC_STR_CONFIGURATION_FAILED: 84,
DC_STR_CONNECTED: 107,
DC_STR_CONNTECTING: 108,
@@ -244,12 +246,6 @@ module.exports = {
DC_STR_OUTGOING_MESSAGES: 104,
DC_STR_PARTIAL_DOWNLOAD_MSG_BODY: 99,
DC_STR_PART_OF_TOTAL_USED: 116,
- DC_STR_PROTECTION_DISABLED: 89,
- DC_STR_PROTECTION_DISABLED_BY_OTHER: 161,
- DC_STR_PROTECTION_DISABLED_BY_YOU: 160,
- DC_STR_PROTECTION_ENABLED: 88,
- DC_STR_PROTECTION_ENABLED_BY_OTHER: 159,
- DC_STR_PROTECTION_ENABLED_BY_YOU: 158,
DC_STR_QUOTA_EXCEEDING_MSG_BODY: 98,
DC_STR_READRCPT: 31,
DC_STR_READRCPT_MAILBODY: 32,
diff --git a/node/lib/constants.ts b/node/lib/constants.ts
index f97e44426..6cc08f144 100644
--- a/node/lib/constants.ts
+++ b/node/lib/constants.ts
@@ -159,6 +159,8 @@ export enum C {
DC_STR_BROADCAST_LIST = 115,
DC_STR_CANNOT_LOGIN = 60,
DC_STR_CANTDECRYPT_MSG_BODY = 29,
+ DC_STR_CHAT_PROTECTION_DISABLED = 171,
+ DC_STR_CHAT_PROTECTION_ENABLED = 170,
DC_STR_CONFIGURATION_FAILED = 84,
DC_STR_CONNECTED = 107,
DC_STR_CONNTECTING = 108,
@@ -244,12 +246,6 @@ export enum C {
DC_STR_OUTGOING_MESSAGES = 104,
DC_STR_PARTIAL_DOWNLOAD_MSG_BODY = 99,
DC_STR_PART_OF_TOTAL_USED = 116,
- DC_STR_PROTECTION_DISABLED = 89,
- DC_STR_PROTECTION_DISABLED_BY_OTHER = 161,
- DC_STR_PROTECTION_DISABLED_BY_YOU = 160,
- DC_STR_PROTECTION_ENABLED = 88,
- DC_STR_PROTECTION_ENABLED_BY_OTHER = 159,
- DC_STR_PROTECTION_ENABLED_BY_YOU = 158,
DC_STR_QUOTA_EXCEEDING_MSG_BODY = 98,
DC_STR_READRCPT = 31,
DC_STR_READRCPT_MAILBODY = 32,
diff --git a/node/lib/context.ts b/node/lib/context.ts
index a9cf9c687..b292e9e35 100644
--- a/node/lib/context.ts
+++ b/node/lib/context.ts
@@ -699,23 +699,6 @@ export class Context extends EventEmitter {
)
}
- /**
- *
- * @param chatId
- * @param protect
- * @returns success boolean
- */
- setChatProtection(chatId: number, protect: boolean) {
- debug(`setChatProtection ${chatId} ${protect}`)
- return Boolean(
- binding.dcn_set_chat_protection(
- this.dcn_context,
- Number(chatId),
- protect ? 1 : 0
- )
- )
- }
-
getChatEphemeralTimer(chatId: number): number {
debug(`getChatEphemeralTimer ${chatId}`)
return binding.dcn_get_chat_ephemeral_timer(
diff --git a/node/lib/deltachat.ts b/node/lib/deltachat.ts
index 2526b9378..dbf002e46 100644
--- a/node/lib/deltachat.ts
+++ b/node/lib/deltachat.ts
@@ -21,12 +21,15 @@ export class AccountManager extends EventEmitter {
accountDir: string
jsonRpcStarted = false
- constructor(cwd: string, os = 'deltachat-node') {
+ constructor(cwd: string, writable = true) {
super()
debug('DeltaChat constructor')
this.accountDir = cwd
- this.dcn_accounts = binding.dcn_accounts_new(os, this.accountDir)
+ this.dcn_accounts = binding.dcn_accounts_new(
+ this.accountDir,
+ writable ? 1 : 0
+ )
}
getAllAccountIds() {
diff --git a/node/src/module.c b/node/src/module.c
index 3d3045017..5c675020e 100644
--- a/node/src/module.c
+++ b/node/src/module.c
@@ -1399,18 +1399,6 @@ NAPI_METHOD(dcn_set_chat_name) {
NAPI_RETURN_INT32(result);
}
-NAPI_METHOD(dcn_set_chat_protection) {
- NAPI_ARGV(3);
- NAPI_DCN_CONTEXT();
- NAPI_ARGV_UINT32(chat_id, 1);
- NAPI_ARGV_INT32(protect, 1);
-
- int result = dc_set_chat_protection(dcn_context->dc_context,
- chat_id,
- protect);
- NAPI_RETURN_INT32(result);
-}
-
NAPI_METHOD(dcn_get_chat_ephemeral_timer) {
NAPI_ARGV(2);
NAPI_DCN_CONTEXT();
@@ -2915,8 +2903,8 @@ NAPI_METHOD(dcn_msg_get_webxdc_blob){
NAPI_METHOD(dcn_accounts_new) {
NAPI_ARGV(2);
- NAPI_ARGV_UTF8_MALLOC(os_name, 0);
- NAPI_ARGV_UTF8_MALLOC(dir, 1);
+ NAPI_ARGV_UTF8_MALLOC(dir, 0);
+ NAPI_ARGV_INT32(writable, 1);
TRACE("calling..");
dcn_accounts_t* dcn_accounts = calloc(1, sizeof(dcn_accounts_t));
@@ -2925,7 +2913,7 @@ NAPI_METHOD(dcn_accounts_new) {
}
- dcn_accounts->dc_accounts = dc_accounts_new(os_name, dir);
+ dcn_accounts->dc_accounts = dc_accounts_new(dir, writable);
napi_value result;
NAPI_STATUS_THROWS(napi_create_external(env, dcn_accounts,
@@ -3491,7 +3479,6 @@ NAPI_INIT() {
NAPI_EXPORT_FUNCTION(dcn_send_msg);
NAPI_EXPORT_FUNCTION(dcn_send_videochat_invitation);
NAPI_EXPORT_FUNCTION(dcn_set_chat_name);
- NAPI_EXPORT_FUNCTION(dcn_set_chat_protection);
NAPI_EXPORT_FUNCTION(dcn_get_chat_ephemeral_timer);
NAPI_EXPORT_FUNCTION(dcn_set_chat_ephemeral_timer);
NAPI_EXPORT_FUNCTION(dcn_set_chat_profile_image);
diff --git a/python/tests/test_0_complex_or_slow.py b/python/tests/test_0_complex_or_slow.py
index 857e1645d..75c554a6d 100644
--- a/python/tests/test_0_complex_or_slow.py
+++ b/python/tests/test_0_complex_or_slow.py
@@ -137,6 +137,7 @@ def test_qr_verified_group_and_chatting(acfactory, lp):
lp.sec("ac2: read member added message")
msg = ac2._evtracker.wait_next_incoming_message()
assert msg.is_encrypted()
+ assert msg.is_system_message()
assert "added" in msg.text.lower()
lp.sec("ac1: send message")
@@ -182,8 +183,9 @@ def test_qr_verified_group_and_chatting(acfactory, lp):
lp.sec("ac2: send message and let ac3 read it")
chat2.send_text("hi")
- # Skip system message about added member
- ac3._evtracker.wait_next_incoming_message()
+ # System message about the added member.
+ msg = ac3._evtracker.wait_next_incoming_message()
+ assert msg.is_system_message()
msg = ac3._evtracker.wait_next_incoming_message()
assert msg.text == "hi"
assert msg.is_encrypted()
@@ -524,7 +526,8 @@ def test_see_new_verified_member_after_going_online(acfactory, tmp_path, lp):
lp.sec("ac2: sending message")
# Message can be sent only after a receipt of "vg-member-added" message. Just wait for
# "Member Me () added by ." message.
- ac2._evtracker.wait_next_incoming_message()
+ msg_in = ac2._evtracker.wait_next_incoming_message()
+ assert msg_in.is_system_message()
msg_out = chat2.send_text("hello")
lp.sec("ac1: receiving message")
@@ -633,7 +636,7 @@ def test_verified_group_vs_delete_server_after(acfactory, tmp_path, lp):
ac2_offl.start_io()
msg_in = ac2_offl._evtracker.wait_next_incoming_message()
assert not msg_in.is_system_message()
- assert msg_in.text.startswith("[Sender of this message is not verified:")
+ assert msg_in.text.startswith("[The message was sent with non-verified encryption")
ac2_offl_ac1_contact = msg_in.get_sender_contact()
assert ac2_offl_ac1_contact.addr == ac1.get_config("addr")
assert not ac2_offl_ac1_contact.is_verified()
diff --git a/python/tests/test_1_online.py b/python/tests/test_1_online.py
index bf5914a77..431463952 100644
--- a/python/tests/test_1_online.py
+++ b/python/tests/test_1_online.py
@@ -9,7 +9,7 @@ import pytest
from imap_tools import AND, U
import deltachat as dc
-from deltachat import account_hookimpl, Message
+from deltachat import account_hookimpl, Message, Chat
from deltachat.tracker import ImexTracker
@@ -1467,13 +1467,18 @@ def test_reaction_to_partially_fetched_msg(acfactory, lp, tmp_path):
path = tmp_path / "large"
path.write_bytes(os.urandom(download_limit + 1))
msgs.append(chat.send_file(str(path)))
-
- lp.sec("sending a reaction to the large message from ac1 to ac2")
- react_str = "\N{THUMBS UP SIGN}"
- msgs.append(msgs[-1].send_reaction(react_str))
-
for m in msgs:
ac1._evtracker.wait_msg_delivered(m)
+
+ lp.sec("sending a reaction to the large message from ac1 to ac2")
+ # TODO: Find the reason of an occasional message reordering on the server (so that the reaction
+ # has a lower UID than the previous message). W/a is to sleep for some time to let the reaction
+ # have a later INTERNALDATE.
+ time.sleep(1.1)
+ react_str = "\N{THUMBS UP SIGN}"
+ msgs.append(msgs[-1].send_reaction(react_str))
+ ac1._evtracker.wait_msg_delivered(msgs[-1])
+
ac2.start_io()
lp.sec("wait for ac2 to receive a reaction")
@@ -1505,7 +1510,7 @@ def test_reactions_for_a_reordering_move(acfactory, lp):
ac1._evtracker.wait_msg_delivered(msg1)
# It's is sad, but messages must differ in their INTERNALDATEs to be processed in the correct
# order by DC, and most (if not all) mail servers provide only seconds precision.
- time.sleep(2)
+ time.sleep(1.1)
react_str = "\N{THUMBS UP SIGN}"
ac1._evtracker.wait_msg_delivered(msg1.send_reaction(react_str))
@@ -1664,8 +1669,12 @@ def test_qr_setup_contact(acfactory, lp):
ac1._evtracker.wait_securejoin_inviter_progress(1000)
-def test_qr_join_chat(acfactory, lp):
+@pytest.mark.parametrize("verified_one_on_one_chats", [0, 1])
+def test_qr_join_chat(acfactory, lp, verified_one_on_one_chats):
ac1, ac2 = acfactory.get_online_accounts(2)
+ ac1.set_config("verified_one_on_one_chats", verified_one_on_one_chats)
+ ac2.set_config("verified_one_on_one_chats", verified_one_on_one_chats)
+
lp.sec("ac1: create QR code and let ac2 scan it, starting the securejoin")
chat = ac1.create_group_chat("hello")
qr = chat.get_join_qr()
@@ -1678,6 +1687,17 @@ def test_qr_join_chat(acfactory, lp):
ac2._evtracker.get_matching("DC_EVENT_IMAP_MESSAGE_DELETED")
ac1._evtracker.wait_securejoin_inviter_progress(1000)
+ msg = ac2._evtracker.wait_next_incoming_message()
+ assert msg.text == "Member Me ({}) added by {}.".format(ac2.get_config("addr"), ac1.get_config("addr"))
+
+ # ac1 reloads the chat.
+ chat = Chat(chat.account, chat.id)
+ assert not chat.is_protected()
+
+ # ac2 reloads the chat.
+ ch = Chat(ch.account, ch.id)
+ assert not ch.is_protected()
+
def test_qr_new_group_unblocked(acfactory, lp):
"""Regression test for a bug intoduced in core v1.113.0.
diff --git a/python/tests/test_4_lowlevel.py b/python/tests/test_4_lowlevel.py
index 1695f943d..23ef3dbe3 100644
--- a/python/tests/test_4_lowlevel.py
+++ b/python/tests/test_4_lowlevel.py
@@ -222,8 +222,9 @@ def test_logged_ac_process_ffi_failure(acfactory):
def test_jsonrpc_blocking_call(tmp_path):
accounts_fname = tmp_path / "accounts"
+ writable = True
accounts = ffi.gc(
- lib.dc_accounts_new(ffi.NULL, str(accounts_fname).encode("ascii")),
+ lib.dc_accounts_new(str(accounts_fname).encode("ascii"), writable),
lib.dc_accounts_unref,
)
jsonrpc = ffi.gc(lib.dc_jsonrpc_init(accounts), lib.dc_jsonrpc_unref)
diff --git a/python/tox.ini b/python/tox.ini
index a68b4e729..ed6069dec 100644
--- a/python/tox.ini
+++ b/python/tox.ini
@@ -62,7 +62,8 @@ commands =
[testenv:doc]
changedir=doc
deps =
- sphinx
+# Pinned due to incompatibility of breathe with sphinx 7.2:
+ sphinx<=7.1.2
breathe
commands =
sphinx-build -Q -w toxdoc-warnings.log -b html . _build/html
diff --git a/scripts/coredeps/Dockerfile b/scripts/coredeps/Dockerfile
index a6ceed532..55b90d9f6 100644
--- a/scripts/coredeps/Dockerfile
+++ b/scripts/coredeps/Dockerfile
@@ -6,3 +6,4 @@ FROM $BASEIMAGE
RUN pipx install tox
COPY install-rust.sh /scripts/
RUN /scripts/install-rust.sh
+RUN if command -v yum; then yum install -y perl-IPC-Cmd; fi
diff --git a/src/accounts.rs b/src/accounts.rs
index 77770049f..7cbe1cea4 100644
--- a/src/accounts.rs
+++ b/src/accounts.rs
@@ -7,6 +7,9 @@ use anyhow::{ensure, Context as _, Result};
use serde::{Deserialize, Serialize};
use tokio::fs;
use tokio::io::AsyncWriteExt;
+use tokio::sync::oneshot;
+use tokio::task::JoinHandle;
+use tokio::time::{sleep, Duration};
use uuid::Uuid;
use crate::context::Context;
@@ -33,16 +36,16 @@ pub struct Accounts {
impl Accounts {
/// Loads or creates an accounts folder at the given `dir`.
- pub async fn new(dir: PathBuf) -> Result {
- if !dir.exists() {
+ pub async fn new(dir: PathBuf, writable: bool) -> Result {
+ if writable && !dir.exists() {
Accounts::create(&dir).await?;
}
- Accounts::open(dir).await
+ Accounts::open(dir, writable).await
}
/// Creates a new default structure.
- pub async fn create(dir: &Path) -> Result<()> {
+ async fn create(dir: &Path) -> Result<()> {
fs::create_dir_all(dir)
.await
.context("failed to create folder")?;
@@ -54,13 +57,13 @@ impl Accounts {
/// Opens an existing accounts structure. Will error if the folder doesn't exist,
/// no account exists and no config exists.
- pub async fn open(dir: PathBuf) -> Result {
+ async fn open(dir: PathBuf, writable: bool) -> Result {
ensure!(dir.exists(), "directory does not exist");
let config_file = dir.join(CONFIG_NAME);
ensure!(config_file.exists(), "{:?} does not exist", config_file);
- let config = Config::from_file(config_file)
+ let config = Config::from_file(config_file, writable)
.await
.context("failed to load accounts config")?;
let events = Events::new();
@@ -298,14 +301,20 @@ impl Accounts {
/// Configuration file name.
const CONFIG_NAME: &str = "accounts.toml";
+/// Lockfile name.
+const LOCKFILE_NAME: &str = "accounts.lock";
+
/// Database file name.
const DB_NAME: &str = "dc.db";
/// Account manager configuration file.
-#[derive(Debug, Clone, PartialEq)]
+#[derive(Debug)]
struct Config {
file: PathBuf,
inner: InnerConfig,
+ // We lock the lockfile in the Config constructors to protect also from having multiple Config
+ // objects for the same config file.
+ lock_task: Option>>,
}
/// Account manager configuration file contents.
@@ -319,17 +328,74 @@ struct InnerConfig {
pub accounts: Vec,
}
+impl Drop for Config {
+ fn drop(&mut self) {
+ if let Some(lock_task) = self.lock_task.take() {
+ lock_task.abort();
+ }
+ }
+}
+
impl Config {
- /// Creates a new configuration file in the given account manager directory.
- pub async fn new(dir: &Path) -> Result {
+ /// Creates a new Config for `file`, but doesn't open/sync it.
+ async fn new_nosync(file: PathBuf, lock: bool) -> Result {
+ let dir = file.parent().context("Cannot get config file directory")?;
let inner = InnerConfig {
accounts: Vec::new(),
selected_account: 0,
next_id: 1,
};
- let file = dir.join(CONFIG_NAME);
- let mut cfg = Self { file, inner };
+ if !lock {
+ let cfg = Self {
+ file,
+ inner,
+ lock_task: None,
+ };
+ return Ok(cfg);
+ }
+ let lockfile = dir.join(LOCKFILE_NAME);
+ let mut lock = fd_lock::RwLock::new(fs::File::create(lockfile).await?);
+ let (locked_tx, locked_rx) = oneshot::channel();
+ let lock_task: JoinHandle> = tokio::spawn(async move {
+ let mut timeout = Duration::from_millis(100);
+ let _guard = loop {
+ match lock.try_write() {
+ Ok(guard) => break Ok(guard),
+ Err(err) => {
+ if timeout.as_millis() > 1600 {
+ break Err(err);
+ }
+ // We need to wait for the previous lock_task to be aborted thus unlocking
+ // the lockfile. We don't open configs for writing often outside of the
+ // tests, so this adds delays to the tests, but otherwise ok.
+ sleep(timeout).await;
+ if err.kind() == std::io::ErrorKind::WouldBlock {
+ timeout *= 2;
+ }
+ }
+ }
+ }?;
+ locked_tx
+ .send(())
+ .ok()
+ .context("Cannot notify about lockfile locking")?;
+ let (_tx, rx) = oneshot::channel();
+ rx.await?;
+ Ok(())
+ });
+ let cfg = Self {
+ file,
+ inner,
+ lock_task: Some(lock_task),
+ };
+ locked_rx.await?;
+ Ok(cfg)
+ }
+ /// Creates a new configuration file in the given account manager directory.
+ pub async fn new(dir: &Path) -> Result {
+ let lock = true;
+ let mut cfg = Self::new_nosync(dir.join(CONFIG_NAME), lock).await?;
cfg.sync().await?;
Ok(cfg)
@@ -339,6 +405,11 @@ impl Config {
/// Takes a mutable reference because the saved file is a part of the `Config` state. This
/// protects from parallel calls resulting to a wrong file contents.
async fn sync(&mut self) -> Result<()> {
+ ensure!(!self
+ .lock_task
+ .as_ref()
+ .context("Config is read-only")?
+ .is_finished());
let tmp_path = self.file.with_extension("toml.tmp");
let mut file = fs::File::create(&tmp_path)
.await
@@ -357,24 +428,28 @@ impl Config {
}
/// Read a configuration from the given file into memory.
- pub async fn from_file(file: PathBuf) -> Result {
- let dir = file.parent().context("can't get config file directory")?;
- let bytes = fs::read(&file).await.context("failed to read file")?;
+ pub async fn from_file(file: PathBuf, writable: bool) -> Result {
+ let dir = file
+ .parent()
+ .context("Cannot get config file directory")?
+ .to_path_buf();
+ let mut config = Self::new_nosync(file, writable).await?;
+ let bytes = fs::read(&config.file)
+ .await
+ .context("Failed to read file")?;
let s = std::str::from_utf8(&bytes)?;
- let mut inner: InnerConfig = toml::from_str(s).context("failed to parse config")?;
+ config.inner = toml::from_str(s).context("Failed to parse config")?;
// Previous versions of the core stored absolute paths in account config.
// Convert them to relative paths.
let mut modified = false;
- for account in &mut inner.accounts {
- if let Ok(new_dir) = account.dir.strip_prefix(dir) {
+ for account in &mut config.inner.accounts {
+ if let Ok(new_dir) = account.dir.strip_prefix(&dir) {
account.dir = new_dir.to_path_buf();
modified = true;
}
}
-
- let mut config = Self { file, inner };
- if modified {
+ if modified && writable {
config.sync().await?;
}
@@ -518,26 +593,44 @@ mod tests {
let p: PathBuf = dir.path().join("accounts1");
{
- let mut accounts = Accounts::new(p.clone()).await.unwrap();
+ let writable = true;
+ let mut accounts = Accounts::new(p.clone(), writable).await.unwrap();
accounts.add_account().await.unwrap();
assert_eq!(accounts.accounts.len(), 1);
assert_eq!(accounts.config.get_selected_account(), 1);
}
- {
- let accounts = Accounts::open(p).await.unwrap();
+ for writable in [true, false] {
+ let accounts = Accounts::new(p.clone(), writable).await.unwrap();
assert_eq!(accounts.accounts.len(), 1);
assert_eq!(accounts.config.get_selected_account(), 1);
}
}
+ #[tokio::test(flavor = "multi_thread", worker_threads = 2)]
+ async fn test_account_new_open_conflict() {
+ let dir = tempfile::tempdir().unwrap();
+ let p: PathBuf = dir.path().join("accounts");
+ let writable = true;
+ let _accounts = Accounts::new(p.clone(), writable).await.unwrap();
+
+ let writable = true;
+ assert!(Accounts::new(p.clone(), writable).await.is_err());
+
+ let writable = false;
+ let accounts = Accounts::new(p, writable).await.unwrap();
+ assert_eq!(accounts.accounts.len(), 0);
+ assert_eq!(accounts.config.get_selected_account(), 0);
+ }
+
#[tokio::test(flavor = "multi_thread", worker_threads = 2)]
async fn test_account_new_add_remove() {
let dir = tempfile::tempdir().unwrap();
let p: PathBuf = dir.path().join("accounts");
- let mut accounts = Accounts::new(p.clone()).await.unwrap();
+ let writable = true;
+ let mut accounts = Accounts::new(p.clone(), writable).await.unwrap();
assert_eq!(accounts.accounts.len(), 0);
assert_eq!(accounts.config.get_selected_account(), 0);
@@ -564,7 +657,8 @@ mod tests {
let dir = tempfile::tempdir()?;
let p: PathBuf = dir.path().join("accounts");
- let mut accounts = Accounts::new(p.clone()).await?;
+ let writable = true;
+ let mut accounts = Accounts::new(p.clone(), writable).await?;
assert!(accounts.get_selected_account().is_none());
assert_eq!(accounts.config.get_selected_account(), 0);
@@ -585,7 +679,8 @@ mod tests {
let dir = tempfile::tempdir().unwrap();
let p: PathBuf = dir.path().join("accounts");
- let mut accounts = Accounts::new(p.clone()).await.unwrap();
+ let writable = true;
+ let mut accounts = Accounts::new(p.clone(), writable).await.unwrap();
assert_eq!(accounts.accounts.len(), 0);
assert_eq!(accounts.config.get_selected_account(), 0);
@@ -622,7 +717,8 @@ mod tests {
let dir = tempfile::tempdir().unwrap();
let p: PathBuf = dir.path().join("accounts");
- let mut accounts = Accounts::new(p.clone()).await.unwrap();
+ let writable = true;
+ let mut accounts = Accounts::new(p.clone(), writable).await.unwrap();
for expected_id in 1..10 {
let id = accounts.add_account().await.unwrap();
@@ -642,7 +738,8 @@ mod tests {
let dummy_accounts = 10;
let (id0, id1, id2) = {
- let mut accounts = Accounts::new(p.clone()).await?;
+ let writable = true;
+ let mut accounts = Accounts::new(p.clone(), writable).await?;
accounts.add_account().await?;
let ids = accounts.get_all();
assert_eq!(ids.len(), 1);
@@ -677,7 +774,8 @@ mod tests {
assert!(id2 > id1 + dummy_accounts);
let (id0_reopened, id1_reopened, id2_reopened) = {
- let accounts = Accounts::new(p.clone()).await?;
+ let writable = false;
+ let accounts = Accounts::new(p.clone(), writable).await?;
let ctx = accounts.get_selected_account().unwrap();
assert_eq!(
ctx.get_config(crate::config::Config::Addr).await?,
@@ -722,7 +820,8 @@ mod tests {
let dir = tempfile::tempdir().unwrap();
let p: PathBuf = dir.path().join("accounts");
- let accounts = Accounts::new(p.clone()).await?;
+ let writable = true;
+ let accounts = Accounts::new(p.clone(), writable).await?;
// Make sure there are no accounts.
assert_eq!(accounts.accounts.len(), 0);
@@ -748,7 +847,8 @@ mod tests {
let dir = tempfile::tempdir().context("failed to create tempdir")?;
let p: PathBuf = dir.path().join("accounts");
- let mut accounts = Accounts::new(p.clone())
+ let writable = true;
+ let mut accounts = Accounts::new(p.clone(), writable)
.await
.context("failed to create accounts manager")?;
@@ -768,7 +868,8 @@ mod tests {
assert!(passphrase_set_success);
drop(accounts);
- let accounts = Accounts::new(p.clone())
+ let writable = false;
+ let accounts = Accounts::new(p.clone(), writable)
.await
.context("failed to create second accounts manager")?;
let account = accounts
@@ -792,7 +893,8 @@ mod tests {
let dir = tempfile::tempdir().unwrap();
let p: PathBuf = dir.path().join("accounts");
- let mut accounts = Accounts::new(p.clone()).await?;
+ let writable = true;
+ let mut accounts = Accounts::new(p.clone(), writable).await?;
accounts.add_account().await?;
accounts.add_account().await?;
diff --git a/src/chat.rs b/src/chat.rs
index 378a19ba4..b6d593822 100644
--- a/src/chat.rs
+++ b/src/chat.rs
@@ -27,6 +27,7 @@ use crate::download::DownloadState;
use crate::ephemeral::Timer as EphemeralTimer;
use crate::events::EventType;
use crate::html::new_html_mimepart;
+use crate::location;
use crate::message::{self, Message, MessageState, MsgId, Viewtype};
use crate::mimefactory::MimeFactory;
use crate::mimeparser::SystemMessage;
@@ -35,6 +36,7 @@ use crate::peerstate::{Peerstate, PeerstateVerifiedStatus};
use crate::receive_imf::ReceivedMsg;
use crate::scheduler::InterruptInfo;
use crate::smtp::send_msg_to_smtp;
+use crate::sql;
use crate::stock_str;
use crate::tools::{
buf_compress, create_id, create_outgoing_rfc724_mid, create_smeared_timestamp,
@@ -42,7 +44,6 @@ use crate::tools::{
strip_rtlo_characters, time, IsNoneOrEmpty,
};
use crate::webxdc::WEBXDC_SUFFIX;
-use crate::{location, sql};
/// An chat item, such as a message or a marker.
#[derive(Debug, Copy, Clone, PartialEq, Eq)]
@@ -88,6 +89,14 @@ pub enum ProtectionStatus {
///
/// All members of the chat must be verified.
Protected = 1,
+
+ /// The chat was protected, but now a new message came in
+ /// which was not encrypted / signed correctly.
+ /// The user has to confirm that this is OK.
+ ///
+ /// We only do this in 1:1 chats; in group chats, the chat just
+ /// stays protected.
+ ProtectionBroken = 3, // `2` was never used as a value.
}
/// The reason why messages cannot be sent to the chat.
@@ -104,6 +113,10 @@ pub(crate) enum CantSendReason {
/// The chat is a contact request, it needs to be accepted before sending a message.
ContactRequest,
+ /// The chat was protected, but now a new message came in
+ /// which was not encrypted / signed correctly.
+ ProtectionBroken,
+
/// Mailing list without known List-Post header.
ReadOnlyMailingList,
@@ -120,6 +133,10 @@ impl fmt::Display for CantSendReason {
f,
"contact request chat should be accepted before sending messages"
),
+ Self::ProtectionBroken => write!(
+ f,
+ "accept that the encryption isn't verified anymore before sending messages"
+ ),
Self::ReadOnlyMailingList => {
write!(f, "mailing list does not have a know post address")
}
@@ -272,6 +289,7 @@ impl ChatId {
param: Option,
) -> Result {
let grpname = strip_rtlo_characters(grpname);
+ let smeared_time = create_smeared_timestamp(context);
let row_id =
context.sql.insert(
"INSERT INTO chats (type, name, grpid, blocked, created_timestamp, protected, param) VALUES(?, ?, ?, ?, ?, ?, ?);",
@@ -280,13 +298,20 @@ impl ChatId {
&grpname,
grpid,
create_blocked,
- create_smeared_timestamp(context),
+ smeared_time,
create_protected,
param.unwrap_or_default(),
),
).await?;
let chat_id = ChatId::new(u32::try_from(row_id)?);
+
+ if create_protected == ProtectionStatus::Protected {
+ chat_id
+ .add_protection_msg(context, ProtectionStatus::Protected, None, smeared_time)
+ .await?;
+ }
+
info!(
context,
"Created group/mailinglist '{}' grpid={} as {}, blocked={}.",
@@ -334,7 +359,7 @@ impl ChatId {
let chat = Chat::load_from_db(context, self).await?;
match chat.typ {
- Chattype::Undefined | Chattype::Broadcast => {
+ Chattype::Broadcast => {
bail!("Can't block chat of type {:?}", chat.typ)
}
Chattype::Single => {
@@ -375,7 +400,16 @@ impl ChatId {
let chat = Chat::load_from_db(context, self).await?;
match chat.typ {
- Chattype::Undefined => bail!("Can't accept chat of undefined chattype"),
+ Chattype::Single
+ if chat.blocked == Blocked::Not
+ && chat.protected == ProtectionStatus::ProtectionBroken =>
+ {
+ // The protection was broken, then the user clicked 'Accept'/'OK',
+ // so, now we want to set the status to Unprotected again:
+ chat.id
+ .inner_set_protection(context, ProtectionStatus::Unprotected)
+ .await?;
+ }
Chattype::Single | Chattype::Group | Chattype::Broadcast => {
// User has "created a chat" with all these contacts.
//
@@ -402,20 +436,19 @@ impl ChatId {
/// Sets protection without sending a message.
///
- /// Used when a message arrives indicating that someone else has
- /// changed the protection value for a chat.
+ /// Returns whether the protection status was actually modified.
pub(crate) async fn inner_set_protection(
self,
context: &Context,
protect: ProtectionStatus,
- ) -> Result<()> {
- ensure!(!self.is_special(), "Invalid chat-id.");
+ ) -> Result {
+ ensure!(!self.is_special(), "Invalid chat-id {self}.");
let chat = Chat::load_from_db(context, self).await?;
if protect == chat.protected {
info!(context, "Protection status unchanged for {}.", self);
- return Ok(());
+ return Ok(false);
}
match protect {
@@ -430,9 +463,8 @@ impl ChatId {
}
}
Chattype::Mailinglist => bail!("Cannot protect mailing lists"),
- Chattype::Undefined => bail!("Undefined group type"),
},
- ProtectionStatus::Unprotected => {}
+ ProtectionStatus::Unprotected | ProtectionStatus::ProtectionBroken => {}
};
context
@@ -445,68 +477,58 @@ impl ChatId {
// make sure, the receivers will get all keys
self.reset_gossiped_timestamp(context).await?;
- Ok(())
+ Ok(true)
}
- /// Send protected status message to the chat.
+ /// Adds an info message to the chat, telling the user that the protection status changed.
///
- /// This sends the message with the protected status change to the chat,
- /// notifying the user on this device as well as the other users in the chat.
+ /// Params:
///
- /// If `promote` is false this means, the message must not be sent out
- /// and only a local info message should be added to the chat.
- /// This is used when protection is enabled implicitly or when a chat is not yet promoted.
+ /// * `contact_id`: In a 1:1 chat, pass the chat partner's contact id.
+ /// * `timestamp_sort` is used as the timestamp of the added message
+ /// and should be the timestamp of the change happening.
pub(crate) async fn add_protection_msg(
self,
context: &Context,
protect: ProtectionStatus,
- promote: bool,
- from_id: ContactId,
+ contact_id: Option,
+ timestamp_sort: i64,
) -> Result<()> {
- let text = context.stock_protection_msg(protect, from_id).await;
+ let text = context.stock_protection_msg(protect, contact_id).await;
let cmd = match protect {
ProtectionStatus::Protected => SystemMessage::ChatProtectionEnabled,
ProtectionStatus::Unprotected => SystemMessage::ChatProtectionDisabled,
+ ProtectionStatus::ProtectionBroken => SystemMessage::ChatProtectionDisabled,
};
-
- if promote {
- let mut msg = Message {
- viewtype: Viewtype::Text,
- text,
- ..Default::default()
- };
- msg.param.set_cmd(cmd);
- send_msg(context, self, &mut msg).await?;
- } else {
- add_info_msg_with_cmd(
- context,
- self,
- &text,
- cmd,
- create_smeared_timestamp(context),
- None,
- None,
- None,
- )
- .await?;
- }
+ add_info_msg_with_cmd(context, self, &text, cmd, timestamp_sort, None, None, None).await?;
Ok(())
}
/// Sets protection and sends or adds a message.
- pub async fn set_protection(self, context: &Context, protect: ProtectionStatus) -> Result<()> {
- ensure!(!self.is_special(), "set protection: invalid chat-id.");
-
- let chat = Chat::load_from_db(context, self).await?;
-
- if let Err(e) = self.inner_set_protection(context, protect).await {
- error!(context, "Cannot set protection: {e:#}."); // make error user-visible
- return Err(e);
+ ///
+ /// `timestamp_sort` is used as the timestamp of the added message
+ /// and should be the timestamp of the change happening.
+ pub(crate) async fn set_protection(
+ self,
+ context: &Context,
+ protect: ProtectionStatus,
+ timestamp_sort: i64,
+ contact_id: Option,
+ ) -> Result<()> {
+ match self.inner_set_protection(context, protect).await {
+ Ok(protection_status_modified) => {
+ if protection_status_modified {
+ self.add_protection_msg(context, protect, contact_id, timestamp_sort)
+ .await?;
+ }
+ Ok(())
+ }
+ Err(e) => {
+ error!(context, "Cannot set protection: {e:#}."); // make error user-visible
+ Err(e)
+ }
}
-
- self.add_protection_msg(context, protect, chat.is_promoted(), ContactId::SELF)
- .await
}
/// Archives or unarchives a chat.
@@ -743,14 +765,6 @@ impl ChatId {
}
}
- let chat = Chat::load_from_db(context, self).await?;
- if let Some(cant_send_reason) = chat.why_cant_send(context).await? {
- bail!(
- "Can't set a draft because chat is not writeable: {}",
- cant_send_reason
- );
- }
-
// set back draft information to allow identifying the draft later on -
// no matter if message object is reused or reloaded from db
msg.state = MessageState::OutDraft;
@@ -1260,7 +1274,7 @@ pub struct Chat {
pub grpid: String,
/// Whether the chat is blocked, unblocked or a contact request.
- pub(crate) blocked: Blocked,
+ pub blocked: Blocked,
/// Additional chat parameters stored in the database.
pub param: Params,
@@ -1272,7 +1286,7 @@ pub struct Chat {
pub mute_duration: MuteDuration,
/// If the chat is protected (verified).
- protected: ProtectionStatus,
+ pub(crate) protected: ProtectionStatus,
}
impl Chat {
@@ -1367,6 +1381,8 @@ impl Chat {
Some(DeviceChat)
} else if self.is_contact_request() {
Some(ContactRequest)
+ } else if self.is_protection_broken() {
+ Some(ProtectionBroken)
} else if self.is_mailing_list() && self.get_mailinglist_addr().is_none_or_empty() {
Some(ReadOnlyMailingList)
} else if !self.is_self_in_chat(context).await? {
@@ -1391,7 +1407,6 @@ impl Chat {
match self.typ {
Chattype::Single | Chattype::Broadcast | Chattype::Mailinglist => Ok(true),
Chattype::Group => is_contact_in_chat(context, self.id, ContactId::SELF).await,
- Chattype::Undefined => Ok(false),
}
}
@@ -1530,6 +1545,27 @@ impl Chat {
self.protected == ProtectionStatus::Protected
}
+ /// Returns true if the chat was protected, and then an incoming message broke this protection.
+ ///
+ /// This function is only useful if the UI enabled the `verified_one_on_one_chats` feature flag,
+ /// otherwise it will return false for all chats.
+ ///
+ /// 1:1 chats are automatically set as protected when a contact is verified.
+ /// When a message comes in that is not encrypted / signed correctly,
+ /// the chat is automatically set as unprotected again.
+ /// `is_protection_broken()` will return true until `chat_id.accept()` is called.
+ ///
+ /// The UI should let the user confirm that this is OK with a message like
+ /// `Bob sent a message from another device. Tap to learn more`
+ /// and then call `chat_id.accept()`.
+ pub fn is_protection_broken(&self) -> bool {
+ match self.protected {
+ ProtectionStatus::Protected => false,
+ ProtectionStatus::Unprotected => false,
+ ProtectionStatus::ProtectionBroken => true,
+ }
+ }
+
/// Returns true if location streaming is enabled in the chat.
pub fn is_sending_locations(&self) -> bool {
self.is_sending_locations
@@ -1560,15 +1596,6 @@ impl Chat {
let mut to_id = 0;
let mut location_id = 0;
- if let Some(reason) = self.why_cant_send(context).await? {
- if self.typ == Chattype::Group && reason == CantSendReason::NotAMember {
- context.emit_event(EventType::ErrorSelfNotInGroup(
- "Cannot send message; self not in group.".into(),
- ));
- }
- bail!("Cannot send message to {}: {}", self.id, reason);
- }
-
let from = context.get_primary_self_addr().await?;
let new_rfc724_mid = {
let grpid = match self.typ {
@@ -2089,19 +2116,30 @@ impl ChatIdBlocked {
_ => (),
}
+ let peerstate = Peerstate::from_addr(context, contact.get_addr()).await?;
+ let protected = peerstate.map_or(false, |p| {
+ p.is_using_verified_key() && p.prefer_encrypt == EncryptPreference::Mutual
+ });
+ let smeared_time = create_smeared_timestamp(context);
+
let chat_id = context
.sql
.transaction(move |transaction| {
transaction.execute(
"INSERT INTO chats
- (type, name, param, blocked, created_timestamp)
- VALUES(?, ?, ?, ?, ?)",
+ (type, name, param, blocked, created_timestamp, protected)
+ VALUES(?, ?, ?, ?, ?, ?)",
(
Chattype::Single,
chat_name,
params.to_string(),
create_blocked as u8,
- create_smeared_timestamp(context),
+ smeared_time,
+ if protected {
+ ProtectionStatus::Protected
+ } else {
+ ProtectionStatus::Unprotected
+ },
),
)?;
let chat_id = ChatId::new(
@@ -2122,6 +2160,17 @@ impl ChatIdBlocked {
})
.await?;
+ if protected {
+ chat_id
+ .add_protection_msg(
+ context,
+ ProtectionStatus::Protected,
+ Some(contact_id),
+ smeared_time,
+ )
+ .await?;
+ }
+
match contact_id {
ContactId::SELF => update_saved_messages_icon(context).await?,
ContactId::DEVICE => update_device_icon(context).await?,
@@ -2159,9 +2208,12 @@ async fn prepare_msg_blob(context: &Context, msg: &mut Message) -> Result<()> {
.with_context(|| format!("attachment missing for message of type #{}", msg.viewtype))?;
let mut maybe_sticker = msg.viewtype == Viewtype::Sticker;
- if msg.viewtype == Viewtype::Image || maybe_sticker {
+ if msg.viewtype == Viewtype::Image
+ || maybe_sticker && !msg.param.exists(Param::ForceSticker)
+ {
blob.recode_to_image_size(context, &mut maybe_sticker)
.await?;
+
if !maybe_sticker {
msg.viewtype = Viewtype::Image;
}
@@ -2235,7 +2287,13 @@ async fn prepare_msg_common(
// Check if the chat can be sent to.
if let Some(reason) = chat.why_cant_send(context).await? {
- bail!("cannot send to {}: {}", chat_id, reason);
+ if reason == CantSendReason::ProtectionBroken
+ && msg.param.get_cmd() == SystemMessage::SecurejoinMessage
+ {
+ // Send out the message, the securejoin message is supposed to repair the verification
+ } else {
+ bail!("cannot send to {chat_id}: {reason}");
+ }
}
// check current MessageState for drafts (to keep msg_id) ...
@@ -2459,14 +2517,13 @@ pub(crate) async fn create_send_msg_job(
msg.chat_id.set_gossiped_timestamp(context, time()).await?;
}
- if 0 != rendered_msg.last_added_location_id {
+ if let Some(last_added_location_id) = rendered_msg.last_added_location_id {
if let Err(err) = location::set_kml_sent_timestamp(context, msg.chat_id, time()).await {
error!(context, "Failed to set kml sent_timestamp: {err:#}.");
}
if !msg.hidden {
if let Err(err) =
- location::set_msg_location_id(context, msg.id, rendered_msg.last_added_location_id)
- .await
+ location::set_msg_location_id(context, msg.id, last_added_location_id).await
{
error!(context, "Failed to set msg_location_id: {err:#}.");
}
@@ -2979,18 +3036,14 @@ pub async fn create_group_chat(
let grpid = create_id();
+ let timestamp = create_smeared_timestamp(context);
let row_id = context
.sql
.insert(
"INSERT INTO chats
(type, name, grpid, param, created_timestamp)
VALUES(?, ?, ?, \'U=1\', ?);",
- (
- Chattype::Group,
- chat_name,
- grpid,
- create_smeared_timestamp(context),
- ),
+ (Chattype::Group, chat_name, grpid, timestamp),
)
.await?;
@@ -3002,9 +3055,9 @@ pub async fn create_group_chat(
context.emit_msgs_changed_without_ids();
if protect == ProtectionStatus::Protected {
- // this part is to stay compatible to verified groups,
- // in some future, we will drop the "protect"-flag from create_group_chat()
- chat_id.inner_set_protection(context, protect).await?;
+ chat_id
+ .set_protection(context, protect, timestamp, None)
+ .await?;
}
Ok(chat_id)
@@ -5260,72 +5313,6 @@ mod tests {
Ok(())
}
- #[tokio::test(flavor = "multi_thread", worker_threads = 2)]
- async fn test_set_protection() -> Result<()> {
- let t = TestContext::new_alice().await;
- t.set_config_bool(Config::BccSelf, false).await?;
- let chat_id = create_group_chat(&t, ProtectionStatus::Unprotected, "foo").await?;
- let chat = Chat::load_from_db(&t, chat_id).await?;
- assert!(!chat.is_protected());
- assert!(chat.is_unpromoted());
-
- // enable protection on unpromoted chat, the info-message is added via add_info_msg()
- chat_id
- .set_protection(&t, ProtectionStatus::Protected)
- .await?;
-
- let chat = Chat::load_from_db(&t, chat_id).await?;
- assert!(chat.is_protected());
- assert!(chat.is_unpromoted());
-
- let msgs = get_chat_msgs(&t, chat_id).await?;
- assert_eq!(msgs.len(), 1);
-
- let msg = t.get_last_msg_in(chat_id).await;
- assert!(msg.is_info());
- assert_eq!(msg.get_info_type(), SystemMessage::ChatProtectionEnabled);
- assert_eq!(msg.get_state(), MessageState::InNoticed);
-
- // disable protection again, still unpromoted
- chat_id
- .set_protection(&t, ProtectionStatus::Unprotected)
- .await?;
-
- let chat = Chat::load_from_db(&t, chat_id).await?;
- assert!(!chat.is_protected());
- assert!(chat.is_unpromoted());
-
- let msg = t.get_last_msg_in(chat_id).await;
- assert!(msg.is_info());
- assert_eq!(msg.get_info_type(), SystemMessage::ChatProtectionDisabled);
- assert_eq!(msg.get_state(), MessageState::InNoticed);
-
- // send a message, this switches to promoted state
- send_text_msg(&t, chat_id, "hi!".to_string()).await?;
-
- let chat = Chat::load_from_db(&t, chat_id).await?;
- assert!(!chat.is_protected());
- assert!(!chat.is_unpromoted());
-
- let msgs = get_chat_msgs(&t, chat_id).await?;
- assert_eq!(msgs.len(), 3);
-
- // enable protection on promoted chat, the info-message is sent via send_msg() this time
- chat_id
- .set_protection(&t, ProtectionStatus::Protected)
- .await?;
- let chat = Chat::load_from_db(&t, chat_id).await?;
- assert!(chat.is_protected());
- assert!(!chat.is_unpromoted());
-
- let msg = t.get_last_msg_in(chat_id).await;
- assert!(msg.is_info());
- assert_eq!(msg.get_info_type(), SystemMessage::ChatProtectionEnabled);
- assert_eq!(msg.get_state(), MessageState::OutDelivered); // as bcc-self is disabled and there is nobody else in the chat
-
- Ok(())
- }
-
#[tokio::test(flavor = "multi_thread", worker_threads = 2)]
async fn test_lookup_by_contact_id() {
let ctx = TestContext::new_alice().await;
@@ -5695,6 +5682,51 @@ mod tests {
.await
}
+ #[tokio::test(flavor = "multi_thread", worker_threads = 2)]
+ async fn test_sticker_jpeg_force() {
+ let alice = TestContext::new_alice().await;
+ let bob = TestContext::new_bob().await;
+ let alice_chat = alice.create_chat(&bob).await;
+
+ let file = alice.get_blobdir().join("sticker.jpg");
+ tokio::fs::write(
+ &file,
+ include_bytes!("../test-data/image/avatar1000x1000.jpg"),
+ )
+ .await
+ .unwrap();
+
+ // Images without force_sticker should be turned into [Viewtype::Image]
+ let mut msg = Message::new(Viewtype::Sticker);
+ msg.set_file(file.to_str().unwrap(), None);
+ let sent_msg = alice.send_msg(alice_chat.id, &mut msg).await;
+ let msg = bob.recv_msg(&sent_msg).await;
+ assert_eq!(msg.get_viewtype(), Viewtype::Image);
+
+ // Images with `force_sticker = true` should keep [Viewtype::Sticker]
+ let mut msg = Message::new(Viewtype::Sticker);
+ msg.set_file(file.to_str().unwrap(), None);
+ msg.force_sticker();
+ let sent_msg = alice.send_msg(alice_chat.id, &mut msg).await;
+ let msg = bob.recv_msg(&sent_msg).await;
+ assert_eq!(msg.get_viewtype(), Viewtype::Sticker);
+
+ // Images with `force_sticker = true` should keep [Viewtype::Sticker]
+ // even on drafted messages
+ let mut msg = Message::new(Viewtype::Sticker);
+ msg.set_file(file.to_str().unwrap(), None);
+ msg.force_sticker();
+ alice_chat
+ .id
+ .set_draft(&alice, Some(&mut msg))
+ .await
+ .unwrap();
+ let mut msg = alice_chat.id.get_draft(&alice).await.unwrap().unwrap();
+ let sent_msg = alice.send_msg(alice_chat.id, &mut msg).await;
+ let msg = bob.recv_msg(&sent_msg).await;
+ assert_eq!(msg.get_viewtype(), Viewtype::Sticker);
+ }
+
#[tokio::test(flavor = "multi_thread", worker_threads = 2)]
async fn test_sticker_gif() -> Result<()> {
test_sticker(
diff --git a/src/chatlist.rs b/src/chatlist.rs
index 70b07ba73..883ca780c 100644
--- a/src/chatlist.rs
+++ b/src/chatlist.rs
@@ -406,7 +406,7 @@ impl Chatlist {
.context("loading contact failed")?;
(Some(lastmsg), Some(lastcontact))
}
- Chattype::Single | Chattype::Undefined => (Some(lastmsg), None),
+ Chattype::Single => (Some(lastmsg), None),
}
}
} else {
diff --git a/src/config.rs b/src/config.rs
index 8611778fa..1ffba283e 100644
--- a/src/config.rs
+++ b/src/config.rs
@@ -324,6 +324,16 @@ pub enum Config {
/// This is not supposed to be changed by UIs and only used for testing.
#[strum(props(default = "172800"))]
GossipPeriod,
+
+ /// Feature flag for verified 1:1 chats; the UI should set it
+ /// to 1 if it supports verified 1:1 chats.
+ /// Regardless of this setting, `chat.is_protected()` returns true while the key is verified,
+ /// and when the key changes, an info message is posted into the chat.
+ /// 0=Nothing else happens when the key changes.
+ /// 1=After the key changed, `can_send()` returns false and `is_protection_broken()` returns true
+ /// until `chat_id.accept()` is called.
+ #[strum(props(default = "0"))]
+ VerifiedOneOnOneChats,
}
impl Context {
diff --git a/src/constants.rs b/src/constants.rs
index 957a09ef3..165de5391 100644
--- a/src/constants.rs
+++ b/src/constants.rs
@@ -125,7 +125,6 @@ pub const DC_CHAT_ID_LAST_SPECIAL: ChatId = ChatId::new(9);
/// Chat type.
#[derive(
Debug,
- Default,
Display,
Clone,
Copy,
@@ -141,10 +140,6 @@ pub const DC_CHAT_ID_LAST_SPECIAL: ChatId = ChatId::new(9);
)]
#[repr(u32)]
pub enum Chattype {
- /// Undefined chat type.
- #[default]
- Undefined = 0,
-
/// 1:1 chat.
Single = 100,
@@ -223,8 +218,6 @@ mod tests {
#[test]
fn test_chattype_values() {
// values may be written to disk and must not change
- assert_eq!(Chattype::Undefined, Chattype::default());
- assert_eq!(Chattype::Undefined, Chattype::from_i32(0).unwrap());
assert_eq!(Chattype::Single, Chattype::from_i32(100).unwrap());
assert_eq!(Chattype::Group, Chattype::from_i32(120).unwrap());
assert_eq!(Chattype::Mailinglist, Chattype::from_i32(140).unwrap());
diff --git a/src/contact.rs b/src/contact.rs
index 4a4ff3540..fed85e053 100644
--- a/src/contact.rs
+++ b/src/contact.rs
@@ -25,7 +25,7 @@ use crate::config::Config;
use crate::constants::{Blocked, Chattype, DC_GCL_ADD_SELF, DC_GCL_VERIFIED_ONLY};
use crate::context::Context;
use crate::events::EventType;
-use crate::key::{DcKey, SignedPublicKey};
+use crate::key::{load_self_public_key, DcKey};
use crate::login_param::LoginParam;
use crate::message::MessageState;
use crate::mimeparser::AvatarAction;
@@ -1032,7 +1032,7 @@ impl Contact {
let finger_prints = stock_str::finger_prints(context).await;
ret += &format!("{stock_message}.\n{finger_prints}:");
- let fingerprint_self = SignedPublicKey::load_self(context)
+ let fingerprint_self = load_self_public_key(context)
.await?
.fingerprint()
.to_string();
@@ -1234,31 +1234,15 @@ impl Contact {
/// and if the key has not changed since this verification.
///
/// The UI may draw a checkbox or something like that beside verified contacts.
- ///
pub async fn is_verified(&self, context: &Context) -> Result {
- self.is_verified_ex(context, None).await
- }
-
- /// Same as `Contact::is_verified` but allows speeding up things
- /// by adding the peerstate belonging to the contact.
- /// If you do not have the peerstate available, it is loaded automatically.
- pub async fn is_verified_ex(
- &self,
- context: &Context,
- peerstate: Option<&Peerstate>,
- ) -> Result {
// We're always sort of secured-verified as we could verify the key on this device any time with the key
// on this device
if self.id == ContactId::SELF {
return Ok(VerifiedStatus::BidirectVerified);
}
- if let Some(peerstate) = peerstate {
- if peerstate.verified_key.is_some() {
- return Ok(VerifiedStatus::BidirectVerified);
- }
- } else if let Some(peerstate) = Peerstate::from_addr(context, &self.addr).await? {
- if peerstate.verified_key.is_some() {
+ if let Some(peerstate) = Peerstate::from_addr(context, &self.addr).await? {
+ if peerstate.is_using_verified_key() {
return Ok(VerifiedStatus::BidirectVerified);
}
}
diff --git a/src/context.rs b/src/context.rs
index 3fd97a2ad..a0f78f267 100644
--- a/src/context.rs
+++ b/src/context.rs
@@ -19,7 +19,7 @@ use crate::constants::DC_VERSION_STR;
use crate::contact::Contact;
use crate::debug_logging::DebugLogging;
use crate::events::{Event, EventEmitter, EventType, Events};
-use crate::key::{DcKey, SignedPublicKey};
+use crate::key::{load_self_public_key, DcKey as _};
use crate::login_param::LoginParam;
use crate::message::{self, MessageState, MsgId};
use crate::quota::QuotaInfo;
@@ -593,7 +593,7 @@ impl Context {
.sql
.count("SELECT COUNT(*) FROM acpeerstates;", ())
.await?;
- let fingerprint_str = match SignedPublicKey::load_self(self).await {
+ let fingerprint_str = match load_self_public_key(self).await {
Ok(key) => key.fingerprint().hex(),
Err(err) => format!(""),
};
@@ -772,6 +772,12 @@ impl Context {
"gossip_period",
self.get_config_int(Config::GossipPeriod).await?.to_string(),
);
+ res.insert(
+ "verified_one_on_one_chats",
+ self.get_config_bool(Config::VerifiedOneOnOneChats)
+ .await?
+ .to_string(),
+ );
let elapsed = self.creation_time.elapsed();
res.insert("uptime", duration_to_str(elapsed.unwrap_or_default()));
diff --git a/src/debug_logging.rs b/src/debug_logging.rs
index 7b343a35f..d9a67473e 100644
--- a/src/debug_logging.rs
+++ b/src/debug_logging.rs
@@ -1,14 +1,12 @@
//! Forward log messages to logging webxdc
-use crate::{
- chat::ChatId,
- config::Config,
- context::Context,
- message::{Message, MsgId, Viewtype},
- param::Param,
- tools::time,
- webxdc::StatusUpdateItem,
- EventType,
-};
+use crate::chat::ChatId;
+use crate::config::Config;
+use crate::context::Context;
+use crate::events::EventType;
+use crate::message::{Message, MsgId, Viewtype};
+use crate::param::Param;
+use crate::tools::time;
+use crate::webxdc::StatusUpdateItem;
use async_channel::{self as channel, Receiver, Sender};
use serde_json::json;
use std::path::PathBuf;
diff --git a/src/decrypt.rs b/src/decrypt.rs
index ce5624ea5..24ab873f0 100644
--- a/src/decrypt.rs
+++ b/src/decrypt.rs
@@ -13,7 +13,6 @@ use crate::contact::addr_cmp;
use crate::context::Context;
use crate::headerdef::{HeaderDef, HeaderDefMap};
use crate::key::{DcKey, Fingerprint, SignedPublicKey, SignedSecretKey};
-use crate::keyring::Keyring;
use crate::peerstate::Peerstate;
use crate::pgp;
@@ -26,8 +25,8 @@ use crate::pgp;
pub fn try_decrypt(
context: &Context,
mail: &ParsedMail<'_>,
- private_keyring: &Keyring,
- public_keyring_for_validate: &Keyring,
+ private_keyring: &[SignedSecretKey],
+ public_keyring_for_validate: &[SignedPublicKey],
) -> Result, HashSet)>> {
let encrypted_data_part = match {
let mime = get_autocrypt_mime(mail);
@@ -227,8 +226,8 @@ fn get_autocrypt_mime<'a, 'b>(mail: &'a ParsedMail<'b>) -> Option<&'a ParsedMail
/// Returns Ok(None) if nothing encrypted was found.
fn decrypt_part(
mail: &ParsedMail<'_>,
- private_keyring: &Keyring,
- public_keyring_for_validate: &Keyring,
+ private_keyring: &[SignedSecretKey],
+ public_keyring_for_validate: &[SignedPublicKey],
) -> Result, HashSet)>> {
let data = mail.get_body_raw()?;
@@ -263,7 +262,7 @@ fn has_decrypted_pgp_armor(input: &[u8]) -> bool {
/// Returns None if the message is not Multipart/Signed or doesn't contain necessary parts.
pub(crate) fn validate_detached_signature<'a, 'b>(
mail: &'a ParsedMail<'b>,
- public_keyring_for_validate: &Keyring,
+ public_keyring_for_validate: &[SignedPublicKey],
) -> Option<(&'a ParsedMail<'b>, HashSet)> {
if mail.ctype.mimetype != "multipart/signed" {
return None;
@@ -283,13 +282,13 @@ pub(crate) fn validate_detached_signature<'a, 'b>(
}
}
-pub(crate) fn keyring_from_peerstate(peerstate: Option<&Peerstate>) -> Keyring {
- let mut public_keyring_for_validate: Keyring = Keyring::new();
+pub(crate) fn keyring_from_peerstate(peerstate: Option<&Peerstate>) -> Vec {
+ let mut public_keyring_for_validate = Vec::new();
if let Some(peerstate) = peerstate {
if let Some(key) = &peerstate.public_key {
- public_keyring_for_validate.add(key.clone());
+ public_keyring_for_validate.push(key.clone());
} else if let Some(key) = &peerstate.gossip_key {
- public_keyring_for_validate.add(key.clone());
+ public_keyring_for_validate.push(key.clone());
}
}
public_keyring_for_validate
diff --git a/src/e2ee.rs b/src/e2ee.rs
index b870f89fe..6a966b488 100644
--- a/src/e2ee.rs
+++ b/src/e2ee.rs
@@ -6,8 +6,7 @@ use num_traits::FromPrimitive;
use crate::aheader::{Aheader, EncryptPreference};
use crate::config::Config;
use crate::context::Context;
-use crate::key::{DcKey, SignedPublicKey, SignedSecretKey};
-use crate::keyring::Keyring;
+use crate::key::{load_self_public_key, load_self_secret_key, SignedPublicKey};
use crate::peerstate::{Peerstate, PeerstateVerifiedStatus};
use crate::pgp;
@@ -24,7 +23,7 @@ impl EncryptHelper {
EncryptPreference::from_i32(context.get_config_int(Config::E2eeEnabled).await?)
.unwrap_or_default();
let addr = context.get_primary_self_addr().await?;
- let public_key = SignedPublicKey::load_self(context).await?;
+ let public_key = load_self_public_key(context).await?;
Ok(EncryptHelper {
prefer_encrypt,
@@ -104,7 +103,7 @@ impl EncryptHelper {
mail_to_encrypt: lettre_email::PartBuilder,
peerstates: Vec<(Option, &str)>,
) -> Result {
- let mut keyring: Keyring = Keyring::new();
+ let mut keyring: Vec = Vec::new();
for (peerstate, addr) in peerstates
.into_iter()
@@ -113,10 +112,10 @@ impl EncryptHelper {
let key = peerstate
.take_key(min_verified)
.with_context(|| format!("proper enc-key for {addr} missing, cannot encrypt"))?;
- keyring.add(key);
+ keyring.push(key);
}
- keyring.add(self.public_key.clone());
- let sign_key = SignedSecretKey::load_self(context).await?;
+ keyring.push(self.public_key.clone());
+ let sign_key = load_self_secret_key(context).await?;
let raw_message = mail_to_encrypt.build().as_string().into_bytes();
@@ -132,7 +131,7 @@ impl EncryptHelper {
context: &Context,
mail: lettre_email::PartBuilder,
) -> Result<(lettre_email::MimeMessage, String)> {
- let sign_key = SignedSecretKey::load_self(context).await?;
+ let sign_key = load_self_secret_key(context).await?;
let mime_message = mail.build();
let signature = pgp::pk_calc_signature(mime_message.as_string().as_bytes(), &sign_key)?;
Ok((mime_message, signature))
@@ -145,20 +144,17 @@ impl EncryptHelper {
/// sent but in a few locations there are no such guarantees,
/// e.g. when exporting keys, and calling this function ensures a
/// private key will be present.
-///
-/// If this succeeds you are also guaranteed that the
-/// [Config::ConfiguredAddr] is configured, this address is returned.
// TODO, remove this once deltachat::key::Key no longer exists.
-pub async fn ensure_secret_key_exists(context: &Context) -> Result {
- let self_addr = context.get_primary_self_addr().await?;
- SignedPublicKey::load_self(context).await?;
- Ok(self_addr)
+pub async fn ensure_secret_key_exists(context: &Context) -> Result<()> {
+ load_self_public_key(context).await?;
+ Ok(())
}
#[cfg(test)]
mod tests {
use super::*;
use crate::chat;
+ use crate::key::DcKey;
use crate::message::{Message, Viewtype};
use crate::param::Param;
use crate::test_utils::{bob_keypair, TestContext};
@@ -169,10 +165,7 @@ mod tests {
#[tokio::test(flavor = "multi_thread", worker_threads = 2)]
async fn test_prexisting() {
let t = TestContext::new_alice().await;
- assert_eq!(
- ensure_secret_key_exists(&t).await.unwrap(),
- "alice@example.org"
- );
+ assert!(ensure_secret_key_exists(&t).await.is_ok());
}
#[tokio::test(flavor = "multi_thread", worker_threads = 2)]
diff --git a/src/ephemeral.rs b/src/ephemeral.rs
index 48cf0801b..694c1d41c 100644
--- a/src/ephemeral.rs
+++ b/src/ephemeral.rs
@@ -545,7 +545,7 @@ async fn next_expiration_timestamp(context: &Context) -> Option {
ephemeral_timestamp
.into_iter()
- .chain(delete_device_after_timestamp.into_iter())
+ .chain(delete_device_after_timestamp)
.min()
}
@@ -986,7 +986,7 @@ mod tests {
t.send_text(self_chat.id, "Saved message, which we delete manually")
.await;
let msg = t.get_last_msg_in(self_chat.id).await;
- msg.id.delete_from_db(&t).await?;
+ msg.id.trash(&t).await?;
check_msg_is_deleted(&t, &self_chat, msg.id).await;
self_chat
@@ -1003,7 +1003,7 @@ mod tests {
.await
.unwrap();
- // Set DeleteDeviceAfter to 1800s. Thend send a saved message which will
+ // Set DeleteDeviceAfter to 1800s. Then send a saved message which will
// still be deleted after 3600s because DeleteDeviceAfter doesn't apply to saved messages.
t.set_config(Config::DeleteDeviceAfter, Some("1800"))
.await?;
@@ -1260,8 +1260,8 @@ mod tests {
);
let msg = alice.get_last_msg().await;
- // Message is deleted from the database when its timer expires.
- msg.id.delete_from_db(&alice).await?;
+ // Message is deleted when its timer expires.
+ msg.id.trash(&alice).await?;
// Message with Message-ID , referencing and
// , is received. The message is not in the
diff --git a/src/html.rs b/src/html.rs
index 7124c0a1a..5af221b70 100644
--- a/src/html.rs
+++ b/src/html.rs
@@ -17,12 +17,12 @@ use lettre_email::mime::{self, Mime};
use lettre_email::PartBuilder;
use mailparse::ParsedContentType;
+use crate::context::Context;
use crate::headerdef::{HeaderDef, HeaderDefMap};
-use crate::message::{Message, MsgId};
+use crate::message::{self, Message, MsgId};
use crate::mimeparser::parse_message_id;
use crate::param::Param::SendHtml;
use crate::plaintext::PlainText;
-use crate::{context::Context, message};
impl Message {
/// Check if the message can be retrieved as HTML.
diff --git a/src/imex.rs b/src/imex.rs
index d137c8b20..236f5e6fc 100644
--- a/src/imex.rs
+++ b/src/imex.rs
@@ -19,7 +19,9 @@ use crate::contact::ContactId;
use crate::context::Context;
use crate::e2ee;
use crate::events::EventType;
-use crate::key::{self, DcKey, DcSecretKey, SignedPublicKey, SignedSecretKey};
+use crate::key::{
+ self, load_self_secret_key, DcKey, DcSecretKey, SignedPublicKey, SignedSecretKey,
+};
use crate::log::LogExt;
use crate::message::{Message, MsgId, Viewtype};
use crate::mimeparser::SystemMessage;
@@ -185,7 +187,7 @@ pub async fn render_setup_file(context: &Context, passphrase: &str) -> Result None,
true => Some(("Autocrypt-Prefer-Encrypt", "mutual")),
@@ -785,6 +787,11 @@ async fn export_database(context: &Context, dest: &Path, passphrase: String) ->
let res = conn
.query_row("SELECT sqlcipher_export('backup')", [], |_row| Ok(()))
.context("failed to export to attached backup database");
+ conn.execute(
+ "UPDATE backup.config SET value='0' WHERE keyname='verified_one_on_one_chats';",
+ [],
+ )
+ .ok(); // If verified_one_on_one_chats was not set, this errors, which we ignore
conn.execute("DETACH DATABASE backup", [])
.context("failed to detach backup database")?;
res?;
@@ -915,55 +922,73 @@ mod tests {
#[tokio::test(flavor = "multi_thread", worker_threads = 2)]
async fn test_export_and_import_backup() -> Result<()> {
- let backup_dir = tempfile::tempdir().unwrap();
+ for set_verified_oneonone_chats in [true, false] {
+ let backup_dir = tempfile::tempdir().unwrap();
- let context1 = TestContext::new_alice().await;
- assert!(context1.is_configured().await?);
+ let context1 = TestContext::new_alice().await;
+ assert!(context1.is_configured().await?);
+ if set_verified_oneonone_chats {
+ context1
+ .set_config_bool(Config::VerifiedOneOnOneChats, true)
+ .await?;
+ }
- let context2 = TestContext::new().await;
- assert!(!context2.is_configured().await?);
- assert!(has_backup(&context2, backup_dir.path()).await.is_err());
+ let context2 = TestContext::new().await;
+ assert!(!context2.is_configured().await?);
+ assert!(has_backup(&context2, backup_dir.path()).await.is_err());
- // export from context1
- assert!(
- imex(&context1, ImexMode::ExportBackup, backup_dir.path(), None)
- .await
- .is_ok()
- );
- let _event = context1
- .evtracker
- .get_matching(|evt| matches!(evt, EventType::ImexProgress(1000)))
- .await;
+ // export from context1
+ assert!(
+ imex(&context1, ImexMode::ExportBackup, backup_dir.path(), None)
+ .await
+ .is_ok()
+ );
+ let _event = context1
+ .evtracker
+ .get_matching(|evt| matches!(evt, EventType::ImexProgress(1000)))
+ .await;
- // import to context2
- let backup = has_backup(&context2, backup_dir.path()).await?;
+ // import to context2
+ let backup = has_backup(&context2, backup_dir.path()).await?;
- // Import of unencrypted backup with incorrect "foobar" backup passphrase fails.
- assert!(imex(
- &context2,
- ImexMode::ImportBackup,
- backup.as_ref(),
- Some("foobar".to_string())
- )
- .await
- .is_err());
+ // Import of unencrypted backup with incorrect "foobar" backup passphrase fails.
+ assert!(imex(
+ &context2,
+ ImexMode::ImportBackup,
+ backup.as_ref(),
+ Some("foobar".to_string())
+ )
+ .await
+ .is_err());
- assert!(
- imex(&context2, ImexMode::ImportBackup, backup.as_ref(), None)
- .await
- .is_ok()
- );
- let _event = context2
- .evtracker
- .get_matching(|evt| matches!(evt, EventType::ImexProgress(1000)))
- .await;
-
- assert!(context2.is_configured().await?);
- assert_eq!(
- context2.get_config(Config::Addr).await?,
- Some("alice@example.org".to_string())
- );
+ assert!(
+ imex(&context2, ImexMode::ImportBackup, backup.as_ref(), None)
+ .await
+ .is_ok()
+ );
+ let _event = context2
+ .evtracker
+ .get_matching(|evt| matches!(evt, EventType::ImexProgress(1000)))
+ .await;
+ assert!(context2.is_configured().await?);
+ assert_eq!(
+ context2.get_config(Config::Addr).await?,
+ Some("alice@example.org".to_string())
+ );
+ assert_eq!(
+ context2
+ .get_config_bool(Config::VerifiedOneOnOneChats)
+ .await?,
+ false
+ );
+ assert_eq!(
+ context1
+ .get_config_bool(Config::VerifiedOneOnOneChats)
+ .await?,
+ set_verified_oneonone_chats
+ );
+ }
Ok(())
}
diff --git a/src/key.rs b/src/key.rs
index 32deec2e3..230f00f8c 100644
--- a/src/key.rs
+++ b/src/key.rs
@@ -3,11 +3,9 @@
use std::collections::BTreeMap;
use std::fmt;
use std::io::Cursor;
-use std::pin::Pin;
use anyhow::{ensure, Context as _, Result};
use base64::Engine as _;
-use futures::Future;
use num_traits::FromPrimitive;
use pgp::composed::Deserializable;
pub use pgp::composed::{SignedPublicKey, SignedSecretKey};
@@ -18,8 +16,7 @@ use tokio::runtime::Handle;
use crate::config::Config;
use crate::constants::KeyGenType;
use crate::context::Context;
-// Re-export key types
-pub use crate::pgp::KeyPair;
+use crate::pgp::KeyPair;
use crate::tools::{time, EmailAddress};
/// Convenience trait for working with keys.
@@ -27,7 +24,7 @@ use crate::tools::{time, EmailAddress};
/// This trait is implemented for rPGP's [SignedPublicKey] and
/// [SignedSecretKey] types and makes working with them a little
/// easier in the deltachat world.
-pub trait DcKey: Serialize + Deserializable + KeyTrait + Clone {
+pub(crate) trait DcKey: Serialize + Deserializable + KeyTrait + Clone {
/// Create a key from some bytes.
fn from_slice(bytes: &[u8]) -> Result {
Ok(::from_bytes(Cursor::new(bytes))?)
@@ -50,11 +47,6 @@ pub trait DcKey: Serialize + Deserializable + KeyTrait + Clone {
Self::from_armor_single(Cursor::new(bytes)).context("rPGP error")
}
- /// Load the users' default key from the database.
- fn load_self<'a>(
- context: &'a Context,
- ) -> Pin> + 'a + Send>>;
-
/// Serialise the key as bytes.
fn to_bytes(&self) -> Vec {
// Not using Serialize::to_bytes() to make clear *why* it is
@@ -85,38 +77,55 @@ pub trait DcKey: Serialize + Deserializable + KeyTrait + Clone {
}
}
-impl DcKey for SignedPublicKey {
- fn load_self<'a>(
- context: &'a Context,
- ) -> Pin> + 'a + Send>> {
- Box::pin(async move {
- let addr = context.get_primary_self_addr().await?;
- match context
- .sql
- .query_row_optional(
- r#"
- SELECT public_key
- FROM keypairs
- WHERE addr=?
- AND is_default=1;
- "#,
- (addr,),
- |row| {
- let bytes: Vec = row.get(0)?;
- Ok(bytes)
- },
- )
- .await?
- {
- Some(bytes) => Self::from_slice(&bytes),
- None => {
- let keypair = generate_keypair(context).await?;
- Ok(keypair.public)
- }
- }
- })
+pub(crate) async fn load_self_public_key(context: &Context) -> Result {
+ match context
+ .sql
+ .query_row_optional(
+ r#"SELECT public_key
+ FROM keypairs
+ WHERE addr=(SELECT value FROM config WHERE keyname="configured_addr")
+ AND is_default=1"#,
+ (),
+ |row| {
+ let bytes: Vec = row.get(0)?;
+ Ok(bytes)
+ },
+ )
+ .await?
+ {
+ Some(bytes) => SignedPublicKey::from_slice(&bytes),
+ None => {
+ let keypair = generate_keypair(context).await?;
+ Ok(keypair.public)
+ }
}
+}
+pub(crate) async fn load_self_secret_key(context: &Context) -> Result {
+ match context
+ .sql
+ .query_row_optional(
+ r#"SELECT private_key
+ FROM keypairs
+ WHERE addr=(SELECT value FROM config WHERE keyname="configured_addr")
+ AND is_default=1"#,
+ (),
+ |row| {
+ let bytes: Vec = row.get(0)?;
+ Ok(bytes)
+ },
+ )
+ .await?
+ {
+ Some(bytes) => SignedSecretKey::from_slice(&bytes),
+ None => {
+ let keypair = generate_keypair(context).await?;
+ Ok(keypair.secret)
+ }
+ }
+}
+
+impl DcKey for SignedPublicKey {
fn to_asc(&self, header: Option<(&str, &str)>) -> String {
// Not using .to_armored_string() to make clear *why* it is
// safe to ignore this error.
@@ -135,36 +144,6 @@ impl DcKey for SignedPublicKey {
}
impl DcKey for SignedSecretKey {
- fn load_self<'a>(
- context: &'a Context,
- ) -> Pin> + 'a + Send>> {
- Box::pin(async move {
- match context
- .sql
- .query_row_optional(
- r#"
- SELECT private_key
- FROM keypairs
- WHERE addr=(SELECT value FROM config WHERE keyname="configured_addr")
- AND is_default=1;
- "#,
- (),
- |row| {
- let bytes: Vec = row.get(0)?;
- Ok(bytes)
- },
- )
- .await?
- {
- Some(bytes) => Self::from_slice(&bytes),
- None => {
- let keypair = generate_keypair(context).await?;
- Ok(keypair.secret)
- }
- }
- })
- }
-
fn to_asc(&self, header: Option<(&str, &str)>) -> String {
// Not using .to_armored_string() to make clear *why* it is
// safe to do these unwraps.
@@ -185,7 +164,7 @@ impl DcKey for SignedSecretKey {
/// Deltachat extension trait for secret keys.
///
/// Provides some convenience wrappers only applicable to [SignedSecretKey].
-pub trait DcSecretKey {
+pub(crate) trait DcSecretKey {
/// Create a public key from a private one.
fn split_public_key(&self) -> Result;
}
@@ -328,6 +307,24 @@ pub async fn store_self_keypair(
Ok(())
}
+/// Saves a keypair as the default keys.
+///
+/// This API is used for testing purposes
+/// to avoid generating the key in tests.
+/// Use import/export APIs instead.
+pub async fn preconfigure_keypair(context: &Context, addr: &str, secret_data: &str) -> Result<()> {
+ let addr = EmailAddress::new(addr)?;
+ let secret = SignedSecretKey::from_asc(secret_data)?.0;
+ let public = secret.split_public_key()?;
+ let keypair = KeyPair {
+ addr,
+ public,
+ secret,
+ };
+ store_self_keypair(context, &keypair, KeyPairUse::Default).await?;
+ Ok(())
+}
+
/// A key fingerprint
#[derive(Clone, Eq, PartialEq, Hash, serde::Serialize, serde::Deserialize)]
pub struct Fingerprint(Vec);
@@ -522,9 +519,9 @@ i8pcjGO+IZffvyZJVRWfVooBJmWWbPB1pueo3tx8w3+fcuzpxz+RLFKaPyqXO+dD
async fn test_load_self_existing() {
let alice = alice_keypair();
let t = TestContext::new_alice().await;
- let pubkey = SignedPublicKey::load_self(&t).await.unwrap();
+ let pubkey = load_self_public_key(&t).await.unwrap();
assert_eq!(alice.public, pubkey);
- let seckey = SignedSecretKey::load_self(&t).await.unwrap();
+ let seckey = load_self_secret_key(&t).await.unwrap();
assert_eq!(alice.secret, seckey);
}
@@ -534,7 +531,7 @@ i8pcjGO+IZffvyZJVRWfVooBJmWWbPB1pueo3tx8w3+fcuzpxz+RLFKaPyqXO+dD
t.set_config(Config::ConfiguredAddr, Some("alice@example.org"))
.await
.unwrap();
- let key = SignedPublicKey::load_self(&t).await;
+ let key = load_self_public_key(&t).await;
assert!(key.is_ok());
}
@@ -544,7 +541,7 @@ i8pcjGO+IZffvyZJVRWfVooBJmWWbPB1pueo3tx8w3+fcuzpxz+RLFKaPyqXO+dD
t.set_config(Config::ConfiguredAddr, Some("alice@example.org"))
.await
.unwrap();
- let key = SignedSecretKey::load_self(&t).await;
+ let key = load_self_secret_key(&t).await;
assert!(key.is_ok());
}
@@ -561,7 +558,7 @@ i8pcjGO+IZffvyZJVRWfVooBJmWWbPB1pueo3tx8w3+fcuzpxz+RLFKaPyqXO+dD
thread::spawn(move || {
tokio::runtime::Runtime::new()
.unwrap()
- .block_on(SignedPublicKey::load_self(&ctx))
+ .block_on(load_self_public_key(&ctx))
})
};
let thr1 = {
@@ -569,7 +566,7 @@ i8pcjGO+IZffvyZJVRWfVooBJmWWbPB1pueo3tx8w3+fcuzpxz+RLFKaPyqXO+dD
thread::spawn(move || {
tokio::runtime::Runtime::new()
.unwrap()
- .block_on(SignedPublicKey::load_self(&ctx))
+ .block_on(load_self_public_key(&ctx))
})
};
let res0 = thr0.join().unwrap();
diff --git a/src/keyring.rs b/src/keyring.rs
deleted file mode 100644
index e354d2348..000000000
--- a/src/keyring.rs
+++ /dev/null
@@ -1,87 +0,0 @@
-//! Keyring to perform rpgp operations with.
-
-use anyhow::Result;
-
-use crate::context::Context;
-use crate::key::DcKey;
-
-/// An in-memory keyring.
-///
-/// Instances are usually constructed just for the rpgp operation and
-/// short-lived.
-#[derive(Clone, Debug, Default)]
-pub struct Keyring
-where
- T: DcKey,
-{
- keys: Vec,
-}
-
-impl Keyring
-where
- T: DcKey,
-{
- /// New empty keyring.
- pub fn new() -> Keyring {
- Keyring { keys: Vec::new() }
- }
-
- /// Create a new keyring with the the user's secret key loaded.
- pub async fn new_self(context: &Context) -> Result> {
- let mut keyring: Keyring = Keyring::new();
- keyring.load_self(context).await?;
- Ok(keyring)
- }
-
- /// Load the user's key into the keyring.
- pub async fn load_self(&mut self, context: &Context) -> Result<()> {
- self.add(T::load_self(context).await?);
- Ok(())
- }
-
- /// Add a key to the keyring.
- pub fn add(&mut self, key: T) {
- self.keys.push(key);
- }
-
- pub fn is_empty(&self) -> bool {
- self.keys.is_empty()
- }
-
- /// A vector with reference to all the keys in the keyring.
- pub fn keys(&self) -> &[T] {
- &self.keys
- }
-}
-
-#[cfg(test)]
-mod tests {
- use super::*;
- use crate::key::{SignedPublicKey, SignedSecretKey};
- use crate::test_utils::{alice_keypair, TestContext};
-
- #[test]
- fn test_keyring_add_keys() {
- let alice = alice_keypair();
- let mut pub_ring: Keyring = Keyring::new();
- pub_ring.add(alice.public.clone());
- assert_eq!(pub_ring.keys(), [alice.public]);
-
- let mut sec_ring: Keyring = Keyring::new();
- sec_ring.add(alice.secret.clone());
- assert_eq!(sec_ring.keys(), [alice.secret]);
- }
-
- #[tokio::test(flavor = "multi_thread", worker_threads = 2)]
- async fn test_keyring_load_self() {
- // new_self() implies load_self()
- let t = TestContext::new_alice().await;
- let alice = alice_keypair();
-
- let pub_ring: Keyring = Keyring::new_self(&t).await.unwrap();
- assert_eq!(pub_ring.keys(), [alice.public]);
-
- let sec_ring: Keyring = Keyring::new_self(&t).await.unwrap();
- assert_eq!(sec_ring.keys(), [alice.secret]);
- }
-}
diff --git a/src/lib.rs b/src/lib.rs
index c0b00724e..20f15972c 100644
--- a/src/lib.rs
+++ b/src/lib.rs
@@ -8,7 +8,6 @@
missing_debug_implementations,
missing_docs,
clippy::all,
- clippy::indexing_slicing,
clippy::wildcard_imports,
clippy::needless_borrow,
clippy::cast_lossless,
@@ -17,6 +16,7 @@
clippy::explicit_into_iter_loop,
clippy::cloned_instead_of_copied
)]
+#![cfg_attr(not(test), warn(clippy::indexing_slicing))]
#![allow(
clippy::match_bool,
clippy::mixed_read_write_in_expression,
@@ -66,7 +66,6 @@ pub mod ephemeral;
mod imap;
pub mod imex;
pub mod key;
-mod keyring;
pub mod location;
mod login_param;
pub mod message;
diff --git a/src/location.rs b/src/location.rs
index 9084e752f..455997813 100644
--- a/src/location.rs
+++ b/src/location.rs
@@ -733,7 +733,7 @@ async fn maybe_send_locations(context: &Context) -> Result> {
next_event = next_event
.into_iter()
- .chain(u64::try_from(locations_send_until - now).into_iter())
+ .chain(u64::try_from(locations_send_until - now))
.min();
if has_locations {
@@ -757,7 +757,7 @@ async fn maybe_send_locations(context: &Context) -> Result > {
);
next_event = next_event
.into_iter()
- .chain(u64::try_from(locations_last_sent + 61 - now).into_iter())
+ .chain(u64::try_from(locations_last_sent + 61 - now))
.min();
}
} else {
diff --git a/src/login_param.rs b/src/login_param.rs
index e01e43a0f..25511c419 100644
--- a/src/login_param.rs
+++ b/src/login_param.rs
@@ -5,9 +5,10 @@ use std::fmt;
use anyhow::{ensure, Result};
use crate::constants::{DC_LP_AUTH_FLAGS, DC_LP_AUTH_NORMAL, DC_LP_AUTH_OAUTH2};
+use crate::context::Context;
+use crate::provider::Socket;
use crate::provider::{get_provider_by_id, Provider};
use crate::socks::Socks5Config;
-use crate::{context::Context, provider::Socket};
#[derive(Copy, Clone, Debug, Display, FromPrimitive, ToPrimitive, PartialEq, Eq)]
#[repr(u32)]
diff --git a/src/message.rs b/src/message.rs
index 8d5ec6fea..915c19af3 100644
--- a/src/message.rs
+++ b/src/message.rs
@@ -114,24 +114,16 @@ WHERE id=?;
}
/// Deletes a message, corresponding MDNs and unsent SMTP messages from the database.
- pub async fn delete_from_db(self, context: &Context) -> Result<()> {
- // We don't use transactions yet, so remove MDNs first to make
- // sure they are not left while the message is deleted.
+ pub(crate) async fn delete_from_db(self, context: &Context) -> Result<()> {
context
.sql
- .execute("DELETE FROM smtp WHERE msg_id=?", (self,))
- .await?;
- context
- .sql
- .execute("DELETE FROM msgs_mdns WHERE msg_id=?;", (self,))
- .await?;
- context
- .sql
- .execute("DELETE FROM msgs_status_updates WHERE msg_id=?;", (self,))
- .await?;
- context
- .sql
- .execute("DELETE FROM msgs WHERE id=?;", (self,))
+ .transaction(move |transaction| {
+ transaction.execute("DELETE FROM smtp WHERE msg_id=?", (self,))?;
+ transaction.execute("DELETE FROM msgs_mdns WHERE msg_id=?", (self,))?;
+ transaction.execute("DELETE FROM msgs_status_updates WHERE msg_id=?", (self,))?;
+ transaction.execute("DELETE FROM msgs WHERE id=?", (self,))?;
+ Ok(())
+ })
.await?;
Ok(())
}
@@ -672,6 +664,12 @@ impl Message {
self.viewtype
}
+ /// Forces the message to **keep** [Viewtype::Sticker]
+ /// e.g the message will not be converted to a [Viewtype::Image].
+ pub fn force_sticker(&mut self) {
+ self.param.set_int(Param::ForceSticker, 1);
+ }
+
/// Returns the state of the message.
pub fn get_state(&self) -> MessageState {
self.state
@@ -772,7 +770,7 @@ impl Message {
Chattype::Group | Chattype::Broadcast | Chattype::Mailinglist => {
Some(Contact::get_by_id(context, self.from_id).await?)
}
- Chattype::Single | Chattype::Undefined => None,
+ Chattype::Single => None,
}
} else {
None
diff --git a/src/mimefactory.rs b/src/mimefactory.rs
index 1a016f00a..f54b2fa38 100644
--- a/src/mimefactory.rs
+++ b/src/mimefactory.rs
@@ -66,7 +66,7 @@ pub struct MimeFactory<'a> {
in_reply_to: String,
references: String,
req_mdn: bool,
- last_added_location_id: u32,
+ last_added_location_id: Option,
/// If the created mime-structure contains sync-items,
/// the IDs of these items are listed here.
@@ -85,7 +85,7 @@ pub struct RenderedEmail {
// pub envelope: Envelope,
pub is_encrypted: bool,
pub is_gossiped: bool,
- pub last_added_location_id: u32,
+ pub last_added_location_id: Option,
/// A comma-separated string of sync-IDs that are used by the rendered email
/// and must be deleted once the message is actually queued for sending
@@ -223,7 +223,7 @@ impl<'a> MimeFactory<'a> {
in_reply_to,
references,
req_mdn,
- last_added_location_id: 0,
+ last_added_location_id: None,
sync_ids_to_delete: None,
attach_selfavatar,
};
@@ -264,7 +264,7 @@ impl<'a> MimeFactory<'a> {
in_reply_to: String::default(),
references: String::default(),
req_mdn: false,
- last_added_location_id: 0,
+ last_added_location_id: None,
sync_ids_to_delete: None,
attach_selfavatar: false,
};
@@ -894,7 +894,7 @@ impl<'a> MimeFactory<'a> {
.body(kml_content);
if !self.msg.param.exists(Param::SetLatitude) {
// otherwise, the independent location is already filed
- self.last_added_location_id = last_added_location_id;
+ self.last_added_location_id = Some(last_added_location_id);
}
Ok(Some(part))
}
@@ -914,7 +914,16 @@ impl<'a> MimeFactory<'a> {
let mut placeholdertext = None;
let mut meta_part = None;
- if chat.is_protected() {
+ let send_verified_headers = match chat.typ {
+ // In single chats, the protection status isn't necessarily the same for both sides,
+ // so we don't send the Chat-Verified header:
+ Chattype::Single => false,
+ Chattype::Group => true,
+ // Mailinglists and broadcast lists can actually never be verified:
+ Chattype::Mailinglist => false,
+ Chattype::Broadcast => false,
+ };
+ if chat.is_protected() && send_verified_headers {
headers
.protected
.push(Header::new("Chat-Verified".to_string(), "1".to_string()));
@@ -1975,7 +1984,7 @@ mod tests {
let incoming_msg = get_chat_msg(&t, new_msg.chat_id, 0, 2).await;
if delete_original_msg {
- incoming_msg.id.delete_from_db(&t).await.unwrap();
+ incoming_msg.id.trash(&t).await.unwrap();
}
if message_arrives_inbetween {
diff --git a/src/mimeparser.rs b/src/mimeparser.rs
index cda1b00cd..3e09cac14 100644
--- a/src/mimeparser.rs
+++ b/src/mimeparser.rs
@@ -27,8 +27,7 @@ use crate::decrypt::{
use crate::dehtml::dehtml;
use crate::events::EventType;
use crate::headerdef::{HeaderDef, HeaderDefMap};
-use crate::key::{DcKey, Fingerprint, SignedPublicKey, SignedSecretKey};
-use crate::keyring::Keyring;
+use crate::key::{load_self_secret_key, DcKey, Fingerprint, SignedPublicKey};
use crate::message::{
self, set_msg_failed, update_msg_state, Message, MessageState, MsgId, Viewtype,
};
@@ -265,9 +264,10 @@ impl MimeMessage {
headers.remove("chat-verified");
let from = from.context("No from in message")?;
- let private_keyring: Keyring = Keyring::new_self(context)
+ let private_keyring = vec![load_self_secret_key(context)
.await
- .context("failed to get own keyring")?;
+ .context("Failed to get own key")?];
+
let mut decryption_info =
prepare_decryption(context, &mail, &from.addr, message_time).await?;
@@ -2188,7 +2188,7 @@ async fn ndn_maybe_add_info_msg(
// If we get an NDN for the mailing list, just issue a warning.
warn!(context, "ignoring NDN for mailing list.");
}
- Chattype::Single | Chattype::Undefined => {}
+ Chattype::Single => {}
}
Ok(())
}
diff --git a/src/param.rs b/src/param.rs
index d0f3bb1aa..a9f5c1a42 100644
--- a/src/param.rs
+++ b/src/param.rs
@@ -187,6 +187,9 @@ pub enum Param {
/// For Webxdc Message Instances: timestamp of summary update.
WebxdcSummaryTimestamp = b'Q',
+
+ /// For messages: Whether [crate::message::Viewtype::Sticker] should be forced.
+ ForceSticker = b'X',
}
/// An object for handling key=value parameter lists.
diff --git a/src/peerstate.rs b/src/peerstate.rs
index e41752541..6a2a83380 100644
--- a/src/peerstate.rs
+++ b/src/peerstate.rs
@@ -392,6 +392,31 @@ impl Peerstate {
}
}
+ /// Returns a reference to the contact's public key fingerprint.
+ ///
+ /// Similar to [`Self::peek_key`], but returns the fingerprint instead of the key.
+ fn peek_key_fingerprint(&self, min_verified: PeerstateVerifiedStatus) -> Option<&Fingerprint> {
+ match min_verified {
+ PeerstateVerifiedStatus::BidirectVerified => self.verified_key_fingerprint.as_ref(),
+ PeerstateVerifiedStatus::Unverified => self
+ .public_key_fingerprint
+ .as_ref()
+ .or(self.gossip_key_fingerprint.as_ref()),
+ }
+ }
+
+ /// Returns true if the key used for opportunistic encryption in the 1:1 chat
+ /// is the same as the verified key.
+ ///
+ /// Note that verified groups always use the verified key no matter if the
+ /// opportunistic key matches or not.
+ pub(crate) fn is_using_verified_key(&self) -> bool {
+ let verified = self.peek_key_fingerprint(PeerstateVerifiedStatus::BidirectVerified);
+
+ verified.is_some()
+ && verified == self.peek_key_fingerprint(PeerstateVerifiedStatus::Unverified)
+ }
+
/// Set this peerstate to verified
/// Make sure to call `self.save_to_db` to save these changes
/// Params:
diff --git a/src/pgp.rs b/src/pgp.rs
index d4a5a072b..c6989bbb5 100644
--- a/src/pgp.rs
+++ b/src/pgp.rs
@@ -20,7 +20,6 @@ use tokio::runtime::Handle;
use crate::constants::KeyGenType;
use crate::key::{DcKey, Fingerprint};
-use crate::keyring::Keyring;
use crate::tools::EmailAddress;
#[allow(missing_docs)]
@@ -237,7 +236,7 @@ fn select_pk_for_encryption(key: &SignedPublicKey) -> Option,
+ public_keys_for_encryption: Vec,
private_key_for_signing: Option,
) -> Result {
let lit_msg = Message::new_literal_bytes("", plain);
@@ -245,7 +244,6 @@ pub async fn pk_encrypt(
Handle::current()
.spawn_blocking(move || {
let pkeys: Vec = public_keys_for_encryption
- .keys()
.iter()
.filter_map(select_pk_for_encryption)
.collect();
@@ -298,15 +296,15 @@ pub fn pk_calc_signature(
#[allow(clippy::implicit_hasher)]
pub fn pk_decrypt(
ctext: Vec,
- private_keys_for_decryption: &Keyring,
- public_keys_for_validation: &Keyring,
+ private_keys_for_decryption: &[SignedSecretKey],
+ public_keys_for_validation: &[SignedPublicKey],
) -> Result<(Vec, HashSet)> {
let mut ret_signature_fingerprints: HashSet = Default::default();
let cursor = Cursor::new(ctext);
let (msg, _) = Message::from_armor_single(cursor)?;
- let skeys: Vec<&SignedSecretKey> = private_keys_for_decryption.keys().iter().collect();
+ let skeys: Vec<&SignedSecretKey> = private_keys_for_decryption.iter().collect();
let (decryptor, _) = msg.decrypt(|| "".into(), &skeys[..])?;
let msgs = decryptor.collect::>>()?;
@@ -321,20 +319,13 @@ pub fn pk_decrypt(
None => bail!("The decrypted message is empty"),
};
- if !public_keys_for_validation.is_empty() {
- let pkeys = public_keys_for_validation.keys();
-
- let mut fingerprints: Vec = Vec::new();
- if let signed_msg @ pgp::composed::Message::Signed { .. } = msg {
- for pkey in pkeys {
- if signed_msg.verify(&pkey.primary_key).is_ok() {
- let fp = DcKey::fingerprint(pkey);
- fingerprints.push(fp);
- }
+ if let signed_msg @ pgp::composed::Message::Signed { .. } = msg {
+ for pkey in public_keys_for_validation {
+ if signed_msg.verify(&pkey.primary_key).is_ok() {
+ let fp = DcKey::fingerprint(pkey);
+ ret_signature_fingerprints.insert(fp);
}
}
-
- ret_signature_fingerprints.extend(fingerprints);
}
Ok((content, ret_signature_fingerprints))
} else {
@@ -346,12 +337,11 @@ pub fn pk_decrypt(
pub fn pk_validate(
content: &[u8],
signature: &[u8],
- public_keys_for_validation: &Keyring,
+ public_keys_for_validation: &[SignedPublicKey],
) -> Result> {
let mut ret: HashSet = Default::default();
let standalone_signature = StandaloneSignature::from_armor_single(Cursor::new(signature))?.0;
- let pkeys = public_keys_for_validation.keys();
// Remove trailing CRLF before the delimiter.
// According to RFC 3156 it is considered to be part of the MIME delimiter for the purpose of
@@ -360,7 +350,7 @@ pub fn pk_validate(
.get(..content.len().saturating_sub(2))
.context("index is out of range")?;
- for pkey in pkeys {
+ for pkey in public_keys_for_validation {
if standalone_signature.verify(pkey, content).is_ok() {
let fp = DcKey::fingerprint(pkey);
ret.insert(fp);
@@ -495,9 +485,7 @@ mod tests {
async fn ctext_signed() -> &'static String {
CTEXT_SIGNED
.get_or_init(|| async {
- let mut keyring = Keyring::new();
- keyring.add(KEYS.alice_public.clone());
- keyring.add(KEYS.bob_public.clone());
+ let keyring = vec![KEYS.alice_public.clone(), KEYS.bob_public.clone()];
pk_encrypt(CLEARTEXT, keyring, Some(KEYS.alice_secret.clone()))
.await
@@ -510,9 +498,7 @@ mod tests {
async fn ctext_unsigned() -> &'static String {
CTEXT_UNSIGNED
.get_or_init(|| async {
- let mut keyring = Keyring::new();
- keyring.add(KEYS.alice_public.clone());
- keyring.add(KEYS.bob_public.clone());
+ let keyring = vec![KEYS.alice_public.clone(), KEYS.bob_public.clone()];
pk_encrypt(CLEARTEXT, keyring, None).await.unwrap()
})
.await
@@ -537,10 +523,8 @@ mod tests {
#[tokio::test(flavor = "multi_thread", worker_threads = 2)]
async fn test_decrypt_singed() {
// Check decrypting as Alice
- let mut decrypt_keyring: Keyring = Keyring::new();
- decrypt_keyring.add(KEYS.alice_secret.clone());
- let mut sig_check_keyring: Keyring = Keyring::new();
- sig_check_keyring.add(KEYS.alice_public.clone());
+ let decrypt_keyring = vec![KEYS.alice_secret.clone()];
+ let sig_check_keyring = vec![KEYS.alice_public.clone()];
let (plain, valid_signatures) = pk_decrypt(
ctext_signed().await.as_bytes().to_vec(),
&decrypt_keyring,
@@ -551,10 +535,8 @@ mod tests {
assert_eq!(valid_signatures.len(), 1);
// Check decrypting as Bob
- let mut decrypt_keyring = Keyring::new();
- decrypt_keyring.add(KEYS.bob_secret.clone());
- let mut sig_check_keyring = Keyring::new();
- sig_check_keyring.add(KEYS.alice_public.clone());
+ let decrypt_keyring = vec![KEYS.bob_secret.clone()];
+ let sig_check_keyring = vec![KEYS.alice_public.clone()];
let (plain, valid_signatures) = pk_decrypt(
ctext_signed().await.as_bytes().to_vec(),
&decrypt_keyring,
@@ -567,15 +549,9 @@ mod tests {
#[tokio::test(flavor = "multi_thread", worker_threads = 2)]
async fn test_decrypt_no_sig_check() {
- let mut keyring = Keyring::new();
- keyring.add(KEYS.alice_secret.clone());
- let empty_keyring = Keyring::new();
- let (plain, valid_signatures) = pk_decrypt(
- ctext_signed().await.as_bytes().to_vec(),
- &keyring,
- &empty_keyring,
- )
- .unwrap();
+ let keyring = vec![KEYS.alice_secret.clone()];
+ let (plain, valid_signatures) =
+ pk_decrypt(ctext_signed().await.as_bytes().to_vec(), &keyring, &[]).unwrap();
assert_eq!(plain, CLEARTEXT);
assert_eq!(valid_signatures.len(), 0);
}
@@ -583,10 +559,8 @@ mod tests {
#[tokio::test(flavor = "multi_thread", worker_threads = 2)]
async fn test_decrypt_signed_no_key() {
// The validation does not have the public key of the signer.
- let mut decrypt_keyring = Keyring::new();
- decrypt_keyring.add(KEYS.bob_secret.clone());
- let mut sig_check_keyring = Keyring::new();
- sig_check_keyring.add(KEYS.bob_public.clone());
+ let decrypt_keyring = vec![KEYS.bob_secret.clone()];
+ let sig_check_keyring = vec![KEYS.bob_public.clone()];
let (plain, valid_signatures) = pk_decrypt(
ctext_signed().await.as_bytes().to_vec(),
&decrypt_keyring,
@@ -599,13 +573,11 @@ mod tests {
#[tokio::test(flavor = "multi_thread", worker_threads = 2)]
async fn test_decrypt_unsigned() {
- let mut decrypt_keyring = Keyring::new();
- decrypt_keyring.add(KEYS.bob_secret.clone());
- let sig_check_keyring = Keyring::new();
+ let decrypt_keyring = vec![KEYS.bob_secret.clone()];
let (plain, valid_signatures) = pk_decrypt(
ctext_unsigned().await.as_bytes().to_vec(),
&decrypt_keyring,
- &sig_check_keyring,
+ &[],
)
.unwrap();
assert_eq!(plain, CLEARTEXT);
diff --git a/src/qr.rs b/src/qr.rs
index e11b74f56..7d89d3e93 100644
--- a/src/qr.rs
+++ b/src/qr.rs
@@ -17,11 +17,12 @@ use crate::contact::{
addr_normalize, may_be_valid_addr, Contact, ContactAddress, ContactId, Origin,
};
use crate::context::Context;
+use crate::events::EventType;
use crate::key::Fingerprint;
use crate::message::Message;
use crate::peerstate::Peerstate;
use crate::socks::Socks5Config;
-use crate::{token, EventType};
+use crate::token;
const OPENPGP4FPR_SCHEME: &str = "OPENPGP4FPR:"; // yes: uppercase
const DCACCOUNT_SCHEME: &str = "DCACCOUNT:";
diff --git a/src/qr_code_generator.rs b/src/qr_code_generator.rs
index 4fb9a2deb..85c3ecba6 100644
--- a/src/qr_code_generator.rs
+++ b/src/qr_code_generator.rs
@@ -4,17 +4,15 @@ use anyhow::Result;
use base64::Engine as _;
use qrcodegen::{QrCode, QrCodeEcc};
-use crate::{
- blob::BlobObject,
- chat::{Chat, ChatId},
- color::color_int_to_hex_string,
- config::Config,
- contact::{Contact, ContactId},
- context::Context,
- qr::{self, Qr},
- securejoin,
- stock_str::{self, backup_transfer_qr},
-};
+use crate::blob::BlobObject;
+use crate::chat::{Chat, ChatId};
+use crate::color::color_int_to_hex_string;
+use crate::config::Config;
+use crate::contact::{Contact, ContactId};
+use crate::context::Context;
+use crate::qr::{self, Qr};
+use crate::securejoin;
+use crate::stock_str::{self, backup_transfer_qr};
/// Returns SVG of the QR code to join the group or verify contact.
///
diff --git a/src/receive_imf.rs b/src/receive_imf.rs
index f254459e7..1ca7323c9 100644
--- a/src/receive_imf.rs
+++ b/src/receive_imf.rs
@@ -4,7 +4,7 @@ use std::cmp::min;
use std::collections::HashSet;
use std::convert::TryFrom;
-use anyhow::{bail, ensure, Context as _, Result};
+use anyhow::{Context as _, Result};
use mailparse::{parse_mail, SingleInfo};
use num_traits::FromPrimitive;
use once_cell::sync::Lazy;
@@ -14,7 +14,7 @@ use crate::chat::{self, Chat, ChatId, ChatIdBlocked, ProtectionStatus};
use crate::config::Config;
use crate::constants::{Blocked, Chattype, ShowEmails, DC_CHAT_ID_TRASH};
use crate::contact::{
- may_be_valid_addr, normalize_name, Contact, ContactAddress, ContactId, Origin, VerifiedStatus,
+ may_be_valid_addr, normalize_name, Contact, ContactAddress, ContactId, Origin,
};
use crate::context::Context;
use crate::debug_logging::maybe_set_logging_xdc_inner;
@@ -534,6 +534,10 @@ async fn add_parts(
securejoin_seen = true;
}
}
+ // Peerstate could be updated by handling the Securejoin handshake.
+ let contact = Contact::get_by_id(context, from_id).await?;
+ mime_parser.decryption_info.peerstate =
+ Peerstate::from_addr(context, contact.get_addr()).await?;
} else {
securejoin_seen = false;
}
@@ -624,12 +628,17 @@ async fn add_parts(
// In lookup_chat_by_reply() and create_or_lookup_group(), it can happen that the message is put into a chat
// but the From-address is not a member of this chat.
- if let Some(chat_id) = chat_id {
- if !chat::is_contact_in_chat(context, chat_id, from_id).await? {
- let chat = Chat::load_from_db(context, chat_id).await?;
+ if let Some(group_chat_id) = chat_id {
+ if !chat::is_contact_in_chat(context, group_chat_id, from_id).await? {
+ let chat = Chat::load_from_db(context, group_chat_id).await?;
if chat.is_protected() {
- let s = stock_str::unknown_sender_for_chat(context).await;
- mime_parser.repl_msg_by_error(&s);
+ if chat.typ == Chattype::Single {
+ // Just assign the message to the 1:1 chat with the actual sender instead.
+ chat_id = None;
+ } else {
+ let s = stock_str::unknown_sender_for_chat(context).await;
+ mime_parser.repl_msg_by_error(&s);
+ }
} else {
// In non-protected chats, just mark the sender as overridden. Therefore, the UI will prepend `~`
// to the sender's name, indicating to the user that he/she is not part of the group.
@@ -645,7 +654,7 @@ async fn add_parts(
context,
mime_parser,
sent_timestamp,
- chat_id,
+ group_chat_id,
from_id,
to_ids,
)
@@ -726,6 +735,51 @@ async fn add_parts(
);
}
}
+
+ // Check if the message was sent with verified encryption and set the protection of
+ // the 1:1 chat accordingly.
+ let chat = match is_partial_download.is_none()
+ && mime_parser.get_header(HeaderDef::SecureJoin).is_none()
+ && !is_mdn
+ {
+ true => Some(Chat::load_from_db(context, chat_id).await?)
+ .filter(|chat| chat.typ == Chattype::Single),
+ false => None,
+ };
+ if let Some(chat) = chat {
+ let mut new_protection = match has_verified_encryption(
+ context,
+ mime_parser,
+ from_id,
+ to_ids,
+ Chattype::Single,
+ )
+ .await?
+ {
+ VerifiedEncryption::Verified => ProtectionStatus::Protected,
+ VerifiedEncryption::NotVerified(_) => ProtectionStatus::Unprotected,
+ };
+
+ if chat.protected != ProtectionStatus::Unprotected
+ && new_protection == ProtectionStatus::Unprotected
+ // `chat.protected` must be maintained regardless of the `Config::VerifiedOneOnOneChats`.
+ // That's why the config is checked here, and not above.
+ && context.get_config_bool(Config::VerifiedOneOnOneChats).await?
+ {
+ new_protection = ProtectionStatus::ProtectionBroken;
+ }
+ if chat.protected != new_protection {
+ // The message itself will be sorted under the device message since the device
+ // message is `MessageState::InNoticed`, which means that all following
+ // messages are sorted under it.
+ let sort_timestamp =
+ calc_sort_timestamp(context, sent_timestamp, chat_id, true, incoming)
+ .await?;
+ chat_id
+ .set_protection(context, new_protection, sort_timestamp, Some(from_id))
+ .await?;
+ }
+ }
}
}
@@ -914,7 +968,8 @@ async fn add_parts(
};
let in_fresh = state == MessageState::InFresh;
- let sort_timestamp = calc_sort_timestamp(context, sent_timestamp, chat_id, in_fresh).await?;
+ let sort_timestamp =
+ calc_sort_timestamp(context, sent_timestamp, chat_id, false, incoming).await?;
// Apply ephemeral timer changes to the chat.
//
@@ -993,42 +1048,14 @@ async fn add_parts(
// if a chat is protected and the message is fully downloaded, check additional properties
if !chat_id.is_special() && is_partial_download.is_none() {
let chat = Chat::load_from_db(context, chat_id).await?;
- let new_status = match mime_parser.is_system_message {
- SystemMessage::ChatProtectionEnabled => Some(ProtectionStatus::Protected),
- SystemMessage::ChatProtectionDisabled => Some(ProtectionStatus::Unprotected),
- _ => None,
- };
- if chat.is_protected() || new_status.is_some() {
- if let Err(err) = check_verified_properties(context, mime_parser, from_id, to_ids).await
+ if chat.is_protected() {
+ if let VerifiedEncryption::NotVerified(err) =
+ has_verified_encryption(context, mime_parser, from_id, to_ids, chat.typ).await?
{
warn!(context, "Verification problem: {err:#}.");
let s = format!("{err}. See 'Info' for more details");
mime_parser.repl_msg_by_error(&s);
- } else {
- // change chat protection only when verification check passes
- if let Some(new_status) = new_status {
- if chat_id
- .update_timestamp(
- context,
- Param::ProtectionSettingsTimestamp,
- sent_timestamp,
- )
- .await?
- {
- if let Err(e) = chat_id.inner_set_protection(context, new_status).await {
- chat::add_info_msg(
- context,
- chat_id,
- &format!("Cannot set protection: {e}"),
- sort_timestamp,
- )
- .await?;
- // do not return an error as this would result in retrying the message
- }
- }
- better_msg = Some(context.stock_protection_msg(new_status, from_id).await);
- }
}
}
}
@@ -1268,7 +1295,7 @@ RETURNING id
if let Some(replace_msg_id) = replace_msg_id {
// "Replace" placeholder with a message that has no parts.
- replace_msg_id.delete_from_db(context).await?;
+ replace_msg_id.trash(context).await?;
}
chat_id.unarchive_if_not_muted(context, state).await?;
@@ -1375,25 +1402,43 @@ async fn calc_sort_timestamp(
context: &Context,
message_timestamp: i64,
chat_id: ChatId,
- is_fresh_msg: bool,
+ always_sort_to_bottom: bool,
+ incoming: bool,
) -> Result {
let mut sort_timestamp = message_timestamp;
- // get newest non fresh message for this chat
- // update sort_timestamp if less than that
- if is_fresh_msg {
- let last_msg_time: Option = context
+ let last_msg_time: Option = if always_sort_to_bottom {
+ // get newest message for this chat
+ context
+ .sql
+ .query_get_value(
+ "SELECT MAX(timestamp) FROM msgs WHERE chat_id=?",
+ (chat_id,),
+ )
+ .await?
+ } else if incoming {
+ // get newest non fresh message for this chat.
+
+ // If a user hasn't been online for some time, the Inbox is fetched first and then the
+ // Sentbox. In order for Inbox and Sent messages to be allowed to mingle, outgoing messages
+ // are purely sorted by their sent timestamp. NB: The Inbox must be fetched first otherwise
+ // Inbox messages would be always below old Sentbox messages. We could take in the query
+ // below only incoming messages, but then new incoming messages would mingle with just sent
+ // outgoing ones and apear somewhere in the middle of the chat.
+ context
.sql
.query_get_value(
"SELECT MAX(timestamp) FROM msgs WHERE chat_id=? AND state>?",
(chat_id, MessageState::InFresh),
)
- .await?;
+ .await?
+ } else {
+ None
+ };
- if let Some(last_msg_time) = last_msg_time {
- if last_msg_time > sort_timestamp {
- sort_timestamp = last_msg_time;
- }
+ if let Some(last_msg_time) = last_msg_time {
+ if last_msg_time > sort_timestamp {
+ sort_timestamp = last_msg_time;
}
}
@@ -1546,7 +1591,9 @@ async fn create_or_lookup_group(
}
let create_protected = if mime_parser.get_header(HeaderDef::ChatVerified).is_some() {
- if let Err(err) = check_verified_properties(context, mime_parser, from_id, to_ids).await {
+ if let VerifiedEncryption::NotVerified(err) =
+ has_verified_encryption(context, mime_parser, from_id, to_ids, Chattype::Group).await?
+ {
warn!(context, "Verification problem: {err:#}.");
let s = format!("{err}. See 'Info' for more details");
mime_parser.repl_msg_by_error(&s);
@@ -1705,6 +1752,22 @@ async fn apply_group_changes(
allow_member_list_changes
};
+ if mime_parser.get_header(HeaderDef::ChatVerified).is_some() {
+ if let VerifiedEncryption::NotVerified(err) =
+ has_verified_encryption(context, mime_parser, from_id, to_ids, chat.typ).await?
+ {
+ warn!(context, "Verification problem: {err:#}.");
+ let s = format!("{err}. See 'Info' for more details");
+ mime_parser.repl_msg_by_error(&s);
+ }
+
+ if !chat.is_protected() {
+ chat_id
+ .inner_set_protection(context, ProtectionStatus::Protected)
+ .await?;
+ }
+ }
+
if let Some(removed_addr) = mime_parser.get_header(HeaderDef::ChatGroupMemberRemoved) {
removed_id = Contact::lookup_id_by_addr(context, removed_addr, Origin::Unknown).await?;
@@ -1785,20 +1848,6 @@ async fn apply_group_changes(
}
}
- if mime_parser.get_header(HeaderDef::ChatVerified).is_some() {
- if let Err(err) = check_verified_properties(context, mime_parser, from_id, to_ids).await {
- warn!(context, "Verification problem: {err:#}.");
- let s = format!("{err}. See 'Info' for more details");
- mime_parser.repl_msg_by_error(&s);
- }
-
- if !chat.is_protected() {
- chat_id
- .inner_set_protection(context, ProtectionStatus::Protected)
- .await?;
- }
- }
-
if allow_member_list_changes {
let mut new_members = HashSet::from_iter(to_ids.iter().copied());
new_members.insert(ContactId::SELF);
@@ -2191,49 +2240,55 @@ async fn create_adhoc_group(
Ok(Some(new_chat_id))
}
-async fn check_verified_properties(
+enum VerifiedEncryption {
+ Verified,
+ NotVerified(String), // The string contains the reason why it's not verified
+}
+
+/// Checks whether the message is allowed to appear in a protected chat.
+///
+/// This means that it is encrypted, signed with a verified key,
+/// and if it's a group, all the recipients are verified.
+async fn has_verified_encryption(
context: &Context,
mimeparser: &MimeMessage,
from_id: ContactId,
to_ids: &[ContactId],
-) -> Result<()> {
- let contact = Contact::get_by_id(context, from_id).await?;
+ chat_type: Chattype,
+) -> Result {
+ use VerifiedEncryption::*;
- ensure!(mimeparser.was_encrypted(), "This message is not encrypted");
-
- if mimeparser.get_header(HeaderDef::ChatVerified).is_none() {
- // we do not fail here currently, this would exclude (a) non-deltas
- // and (b) deltas with different protection views across multiple devices.
- // for group creation or protection enabled/disabled, however, Chat-Verified is respected.
- warn!(
- context,
- "{} did not mark message as protected.",
- contact.get_addr()
- );
+ if from_id == ContactId::SELF && chat_type == Chattype::Single {
+ // For outgoing emails in the 1:1 chat, we have an exception that
+ // they are allowed to be unencrypted:
+ // 1. They can't be an attack (they are outgoing, not incoming)
+ // 2. Probably the unencryptedness is just a temporary state, after all
+ // the user obviously still uses DC
+ // -> Showing info messages everytime would be a lot of noise
+ // 3. The info messages that are shown to the user ("Your chat partner
+ // likely reinstalled DC" or similar) would be wrong.
+ return Ok(Verified);
}
+ if !mimeparser.was_encrypted() {
+ return Ok(NotVerified("This message is not encrypted".to_string()));
+ };
+
// ensure, the contact is verified
// and the message is signed with a verified key of the sender.
// this check is skipped for SELF as there is no proper SELF-peerstate
// and results in group-splits otherwise.
if from_id != ContactId::SELF {
- let peerstate = Peerstate::from_addr(context, contact.get_addr()).await?;
+ let Some(peerstate) = &mimeparser.decryption_info.peerstate else {
+ return Ok(NotVerified(
+ "No peerstate, the contact isn't verified".to_string(),
+ ));
+ };
- if peerstate.is_none()
- || contact.is_verified_ex(context, peerstate.as_ref()).await?
- != VerifiedStatus::BidirectVerified
- {
- bail!(
- "Sender of this message is not verified: {}",
- contact.get_addr()
- );
- }
-
- if let Some(peerstate) = peerstate {
- ensure!(
- peerstate.has_verified_key(&mimeparser.signatures),
- "The message was sent with non-verified encryption"
- );
+ if !peerstate.has_verified_key(&mimeparser.signatures) {
+ return Ok(NotVerified(
+ "The message was sent with non-verified encryption".to_string(),
+ ));
}
}
@@ -2245,7 +2300,7 @@ async fn check_verified_properties(
.collect::>();
if to_ids.is_empty() {
- return Ok(());
+ return Ok(Verified);
}
let rows = context
@@ -2269,10 +2324,12 @@ async fn check_verified_properties(
)
.await?;
+ let contact = Contact::get_by_id(context, from_id).await?;
+
for (to_addr, mut is_verified) in rows {
info!(
context,
- "check_verified_properties: {:?} self={:?}.",
+ "has_verified_encryption: {:?} self={:?}.",
to_addr,
context.is_self_addr(&to_addr).await
);
@@ -2306,13 +2363,13 @@ async fn check_verified_properties(
}
}
if !is_verified {
- bail!(
+ return Ok(NotVerified(format!(
"{} is not a member of this protected chat",
- to_addr.to_string()
- );
+ to_addr
+ )));
}
}
- Ok(())
+ Ok(Verified)
}
/// Returns the last message referenced from `References` header if it is in the database.
diff --git a/src/receive_imf/tests.rs b/src/receive_imf/tests.rs
index 25de7a7d9..1b1476313 100644
--- a/src/receive_imf/tests.rs
+++ b/src/receive_imf/tests.rs
@@ -2945,7 +2945,7 @@ async fn test_outgoing_private_reply_multidevice() -> Result<()> {
let received = alice2.get_last_msg().await;
// That's a regression test for https://github.com/deltachat/deltachat-core-rust/issues/2949:
- assert_eq!(received.chat_id, alice2.get_chat(&bob).await.unwrap().id);
+ assert_eq!(received.chat_id, alice2.get_chat(&bob).await.id);
let alice2_bob_contact = alice2.add_or_lookup_contact(&bob).await;
assert_eq!(received.from_id, ContactId::SELF);
diff --git a/src/scheduler.rs b/src/scheduler.rs
index 59b805e5d..a30df79bb 100644
--- a/src/scheduler.rs
+++ b/src/scheduler.rs
@@ -905,7 +905,7 @@ impl Scheduler {
// Actually shutdown tasks.
let timeout_duration = std::time::Duration::from_secs(30);
- for b in once(self.inbox).chain(self.oboxes.into_iter()) {
+ for b in once(self.inbox).chain(self.oboxes) {
tokio::time::timeout(timeout_duration, b.handle)
.await
.log_err(context)
diff --git a/src/securejoin.rs b/src/securejoin.rs
index 5ffa387ed..df045ad4c 100644
--- a/src/securejoin.rs
+++ b/src/securejoin.rs
@@ -6,15 +6,15 @@ use anyhow::{bail, Context as _, Error, Result};
use percent_encoding::{utf8_percent_encode, AsciiSet, NON_ALPHANUMERIC};
use crate::aheader::EncryptPreference;
-use crate::chat::{self, Chat, ChatId, ChatIdBlocked};
+use crate::chat::{self, Chat, ChatId, ChatIdBlocked, ProtectionStatus};
use crate::config::Config;
-use crate::constants::Blocked;
+use crate::constants::{Blocked, Chattype};
use crate::contact::{Contact, ContactId, Origin, VerifiedStatus};
use crate::context::Context;
use crate::e2ee::ensure_secret_key_exists;
use crate::events::EventType;
use crate::headerdef::HeaderDef;
-use crate::key::{DcKey, Fingerprint, SignedPublicKey};
+use crate::key::{load_self_public_key, DcKey, Fingerprint};
use crate::message::{Message, Viewtype};
use crate::mimeparser::{MimeMessage, SystemMessage};
use crate::param::Param;
@@ -130,7 +130,7 @@ pub async fn get_securejoin_qr(context: &Context, group: Option) -> Resu
}
async fn get_self_fingerprint(context: &Context) -> Option {
- match SignedPublicKey::load_self(context).await {
+ match load_self_public_key(context).await {
Ok(key) => Some(key.fingerprint()),
Err(_) => {
warn!(context, "get_self_fingerprint(): failed to load key");
@@ -701,6 +701,22 @@ async fn secure_connection_established(
let contact = Contact::get_by_id(context, contact_id).await?;
let msg = stock_str::contact_verified(context, &contact).await;
chat::add_info_msg(context, chat_id, &msg, time()).await?;
+ if context
+ .get_config_bool(Config::VerifiedOneOnOneChats)
+ .await?
+ {
+ let chat = Chat::load_from_db(context, chat_id).await?;
+ if chat.typ == Chattype::Single {
+ chat_id
+ .set_protection(
+ context,
+ ProtectionStatus::Protected,
+ time(),
+ Some(contact_id),
+ )
+ .await?;
+ }
+ }
context.emit_event(EventType::ChatModified(chat_id));
Ok(())
}
@@ -778,11 +794,12 @@ mod tests {
use crate::chat;
use crate::chat::ProtectionStatus;
use crate::chatlist::Chatlist;
- use crate::constants::Chattype;
use crate::contact::ContactAddress;
use crate::contact::VerifiedStatus;
use crate::peerstate::Peerstate;
use crate::receive_imf::receive_imf;
+ use crate::stock_str::chat_protection_enabled;
+ use crate::test_utils::get_chat_msg;
use crate::test_utils::{TestContext, TestContextManager};
use crate::tools::EmailAddress;
@@ -791,6 +808,14 @@ mod tests {
let mut tcm = TestContextManager::new();
let alice = tcm.alice().await;
let bob = tcm.bob().await;
+ alice
+ .set_config(Config::VerifiedOneOnOneChats, Some("1"))
+ .await
+ .unwrap();
+ bob.set_config(Config::VerifiedOneOnOneChats, Some("1"))
+ .await
+ .unwrap();
+
assert_eq!(
Chatlist::try_load(&alice, 0, None, None)
.await
@@ -874,10 +899,7 @@ mod tests {
"vc-request-with-auth"
);
assert!(msg.get_header(HeaderDef::SecureJoinAuth).is_some());
- let bob_fp = SignedPublicKey::load_self(&bob.ctx)
- .await
- .unwrap()
- .fingerprint();
+ let bob_fp = load_self_public_key(&bob.ctx).await.unwrap().fingerprint();
assert_eq!(
*msg.get_header(HeaderDef::SecureJoinFingerprint).unwrap(),
bob_fp.hex()
@@ -921,7 +943,7 @@ mod tests {
// Check Alice got the verified message in her 1:1 chat.
{
let chat = alice.create_chat(&bob).await;
- let msg_id = chat::get_chat_msgs(&alice.ctx, chat.get_id())
+ let msg_ids: Vec<_> = chat::get_chat_msgs(&alice.ctx, chat.get_id())
.await
.unwrap()
.into_iter()
@@ -929,11 +951,17 @@ mod tests {
chat::ChatItem::Message { msg_id } => Some(msg_id),
_ => None,
})
- .max()
- .expect("No messages in Alice's 1:1 chat");
- let msg = Message::load_from_db(&alice.ctx, msg_id).await.unwrap();
- assert!(msg.is_info());
- assert!(msg.get_text().contains("bob@example.net verified"));
+ .collect();
+ assert_eq!(msg_ids.len(), 2);
+
+ let msg0 = Message::load_from_db(&alice.ctx, msg_ids[0]).await.unwrap();
+ assert!(msg0.is_info());
+ assert!(msg0.get_text().contains("bob@example.net verified"));
+
+ let msg1 = Message::load_from_db(&alice.ctx, msg_ids[1]).await.unwrap();
+ assert!(msg1.is_info());
+ let expected_text = chat_protection_enabled(&alice).await;
+ assert_eq!(msg1.get_text(), expected_text);
}
// Check Alice sent the right message to Bob.
@@ -969,7 +997,7 @@ mod tests {
// Check Bob got the verified message in his 1:1 chat.
{
let chat = bob.create_chat(&alice).await;
- let msg_id = chat::get_chat_msgs(&bob.ctx, chat.get_id())
+ let msg_ids: Vec<_> = chat::get_chat_msgs(&bob.ctx, chat.get_id())
.await
.unwrap()
.into_iter()
@@ -977,11 +1005,16 @@ mod tests {
chat::ChatItem::Message { msg_id } => Some(msg_id),
_ => None,
})
- .max()
- .expect("No messages in Bob's 1:1 chat");
- let msg = Message::load_from_db(&bob.ctx, msg_id).await.unwrap();
- assert!(msg.is_info());
- assert!(msg.get_text().contains("alice@example.org verified"));
+ .collect();
+
+ let msg0 = Message::load_from_db(&bob.ctx, msg_ids[0]).await.unwrap();
+ assert!(msg0.is_info());
+ assert!(msg0.get_text().contains("alice@example.org verified"));
+
+ let msg1 = Message::load_from_db(&bob.ctx, msg_ids[1]).await.unwrap();
+ assert!(msg1.is_info());
+ let expected_text = chat_protection_enabled(&bob).await;
+ assert_eq!(msg1.get_text(), expected_text);
}
// Check Bob sent the final message
@@ -1008,7 +1041,7 @@ mod tests {
let bob = tcm.bob().await;
// Ensure Bob knows Alice_FP
- let alice_pubkey = SignedPublicKey::load_self(&alice.ctx).await?;
+ let alice_pubkey = load_self_public_key(&alice.ctx).await?;
let peerstate = Peerstate {
addr: "alice@example.org".into(),
last_seen: 10,
@@ -1062,7 +1095,7 @@ mod tests {
"vc-request-with-auth"
);
assert!(msg.get_header(HeaderDef::SecureJoinAuth).is_some());
- let bob_fp = SignedPublicKey::load_self(&bob.ctx).await?.fingerprint();
+ let bob_fp = load_self_public_key(&bob.ctx).await?.fingerprint();
assert_eq!(
*msg.get_header(HeaderDef::SecureJoinFingerprint).unwrap(),
bob_fp.hex()
@@ -1233,7 +1266,7 @@ mod tests {
"vg-request-with-auth"
);
assert!(msg.get_header(HeaderDef::SecureJoinAuth).is_some());
- let bob_fp = SignedPublicKey::load_self(&bob.ctx).await?.fingerprint();
+ let bob_fp = load_self_public_key(&bob.ctx).await?.fingerprint();
assert_eq!(
*msg.get_header(HeaderDef::SecureJoinFingerprint).unwrap(),
bob_fp.hex()
@@ -1269,26 +1302,17 @@ mod tests {
// Now Alice's chat with Bob should still be hidden, the verified message should
// appear in the group chat.
- let chat = alice
- .get_chat(&bob)
- .await
- .expect("Alice has no 1:1 chat with bob");
+ let chat = alice.get_chat(&bob).await;
assert_eq!(
chat.blocked,
Blocked::Yes,
"Alice's 1:1 chat with Bob is not hidden"
);
- let msg_id = chat::get_chat_msgs(&alice.ctx, alice_chatid)
- .await
- .unwrap()
- .into_iter()
- .filter_map(|item| match item {
- chat::ChatItem::Message { msg_id } => Some(msg_id),
- _ => None,
- })
- .min()
- .expect("No messages in Alice's group chat");
- let msg = Message::load_from_db(&alice.ctx, msg_id).await.unwrap();
+ // There should be 3 messages in the chat:
+ // - The ChatProtectionEnabled message
+ // - bob@example.net verified
+ // - You added member bob@example.net
+ let msg = get_chat_msg(&alice, alice_chatid, 1, 3).await;
assert!(msg.is_info());
assert!(msg.get_text().contains("bob@example.net verified"));
}
@@ -1313,10 +1337,7 @@ mod tests {
contact_alice.is_verified(&bob.ctx).await?,
VerifiedStatus::BidirectVerified
);
- let chat = bob
- .get_chat(&alice)
- .await
- .expect("Bob has no 1:1 chat with Alice");
+ let chat = bob.get_chat(&alice).await;
assert_eq!(
chat.blocked,
Blocked::Yes,
diff --git a/src/securejoin/bob.rs b/src/securejoin/bob.rs
index 674fa94f7..73903e2ce 100644
--- a/src/securejoin/bob.rs
+++ b/src/securejoin/bob.rs
@@ -9,6 +9,7 @@ use super::bobstate::{BobHandshakeStage, BobState};
use super::qrinvite::QrInvite;
use super::HandshakeMessage;
use crate::chat::{is_contact_in_chat, ChatId, ProtectionStatus};
+use crate::config::Config;
use crate::constants::{Blocked, Chattype};
use crate::contact::Contact;
use crate::context::Context;
@@ -222,6 +223,22 @@ impl BobState {
let msg = stock_str::contact_verified(context, &contact).await;
let chat_id = self.joining_chat_id(context).await?;
chat::add_info_msg(context, chat_id, &msg, time()).await?;
+
+ if context
+ .get_config_bool(Config::VerifiedOneOnOneChats)
+ .await?
+ && chat_id == self.alice_chat()
+ {
+ chat_id
+ .set_protection(
+ context,
+ ProtectionStatus::Protected,
+ time(),
+ Some(contact.id),
+ )
+ .await?;
+ }
+
context.emit_event(EventType::ChatModified(chat_id));
Ok(())
}
diff --git a/src/securejoin/bobstate.rs b/src/securejoin/bobstate.rs
index 2c48c54f2..b46b45bed 100644
--- a/src/securejoin/bobstate.rs
+++ b/src/securejoin/bobstate.rs
@@ -17,7 +17,7 @@ use crate::contact::{Contact, Origin};
use crate::context::Context;
use crate::events::EventType;
use crate::headerdef::HeaderDef;
-use crate::key::{DcKey, SignedPublicKey};
+use crate::key::{load_self_public_key, DcKey};
use crate::message::{Message, Viewtype};
use crate::mimeparser::{MimeMessage, SystemMessage};
use crate::param::Param;
@@ -448,7 +448,7 @@ async fn send_handshake_message(
};
// Sends our own fingerprint in the Secure-Join-Fingerprint header.
- let bob_fp = SignedPublicKey::load_self(context).await?.fingerprint();
+ let bob_fp = load_self_public_key(context).await?.fingerprint();
msg.param.set(Param::Arg3, bob_fp.hex());
// Sends the grpid in the Secure-Join-Group header.
diff --git a/src/smtp.rs b/src/smtp.rs
index f2998b8cc..c30f4626e 100644
--- a/src/smtp.rs
+++ b/src/smtp.rs
@@ -12,6 +12,7 @@ use tokio::task;
use crate::config::Config;
use crate::contact::{Contact, ContactId};
+use crate::context::Context;
use crate::events::EventType;
use crate::login_param::{CertificateChecks, LoginParam, ServerLoginParam};
use crate::message::Message;
@@ -22,9 +23,9 @@ use crate::net::session::SessionBufStream;
use crate::net::tls::wrap_tls;
use crate::oauth2::get_oauth2_access_token;
use crate::provider::Socket;
+use crate::scheduler::connectivity::ConnectivityStore;
use crate::socks::Socks5Config;
use crate::sql;
-use crate::{context::Context, scheduler::connectivity::ConnectivityStore};
/// SMTP write and read timeout.
const SMTP_TIMEOUT: Duration = Duration::from_secs(30);
diff --git a/src/sql/migrations.rs b/src/sql/migrations.rs
index 8f3b24509..6e69311c5 100644
--- a/src/sql/migrations.rs
+++ b/src/sql/migrations.rs
@@ -730,6 +730,7 @@ CREATE INDEX smtp_messageid ON imap(rfc724_mid);
)
.await?;
}
+
if dbversion < 102 {
sql.execute_migration(
"CREATE TABLE download (
diff --git a/src/stock_str.rs b/src/stock_str.rs
index 0dc305107..888c474c4 100644
--- a/src/stock_str.rs
+++ b/src/stock_str.rs
@@ -393,18 +393,6 @@ pub enum StockMessage {
#[strum(props(fallback = "Message deletion timer is set to %1$s weeks by %2$s."))]
MsgEphemeralTimerWeeksBy = 157,
- #[strum(props(fallback = "You enabled chat protection."))]
- YouEnabledProtection = 158,
-
- #[strum(props(fallback = "Chat protection enabled by %1$s."))]
- ProtectionEnabledBy = 159,
-
- #[strum(props(fallback = "You disabled chat protection."))]
- YouDisabledProtection = 160,
-
- #[strum(props(fallback = "Chat protection disabled by %1$s."))]
- ProtectionDisabledBy = 161,
-
#[strum(props(fallback = "Scan to set up second device for %1$s"))]
BackupTransferQr = 162,
@@ -419,6 +407,12 @@ pub enum StockMessage {
#[strum(props(fallback = "I left the group."))]
MsgILeftGroup = 166,
+
+ #[strum(props(fallback = "Messages are guaranteed to be end-to-end encrypted from now on."))]
+ ChatProtectionEnabled = 170,
+
+ #[strum(props(fallback = "%1$s sent a message from another device."))]
+ ChatProtectionDisabled = 171,
}
impl StockMessage {
@@ -515,13 +509,21 @@ trait StockStringMods: AsRef + Sized {
}
impl ContactId {
- /// Get contact name for stock string.
- async fn get_stock_name(self, context: &Context) -> String {
+ /// Get contact name and address for stock string, e.g. `Bob (bob@example.net)`
+ async fn get_stock_name_n_addr(self, context: &Context) -> String {
Contact::get_by_id(context, self)
.await
.map(|contact| contact.get_name_n_addr())
.unwrap_or_else(|_| self.to_string())
}
+
+ /// Get contact name, e.g. `Bob`, or `bob@exmple.net` if no name is set.
+ async fn get_stock_name(self, context: &Context) -> String {
+ Contact::get_by_id(context, self)
+ .await
+ .map(|contact| contact.get_display_name().to_string())
+ .unwrap_or_else(|_| self.to_string())
+ }
}
impl StockStringMods for String {}
@@ -583,7 +585,7 @@ pub(crate) async fn msg_grp_name(
.await
.replace1(from_group)
.replace2(to_group)
- .replace3(&by_contact.get_stock_name(context).await)
+ .replace3(&by_contact.get_stock_name_n_addr(context).await)
}
}
@@ -593,7 +595,7 @@ pub(crate) async fn msg_grp_img_changed(context: &Context, by_contact: ContactId
} else {
translated(context, StockMessage::MsgGrpImgChangedBy)
.await
- .replace1(&by_contact.get_stock_name(context).await)
+ .replace1(&by_contact.get_stock_name_n_addr(context).await)
}
}
@@ -640,7 +642,7 @@ pub(crate) async fn msg_add_member_local(
translated(context, StockMessage::MsgAddMemberBy)
.await
.replace1(whom)
- .replace2(&by_contact.get_stock_name(context).await)
+ .replace2(&by_contact.get_stock_name_n_addr(context).await)
}
}
@@ -687,7 +689,7 @@ pub(crate) async fn msg_del_member_local(
translated(context, StockMessage::MsgDelMemberBy)
.await
.replace1(whom)
- .replace2(&by_contact.get_stock_name(context).await)
+ .replace2(&by_contact.get_stock_name_n_addr(context).await)
}
}
@@ -703,7 +705,7 @@ pub(crate) async fn msg_group_left_local(context: &Context, by_contact: ContactI
} else {
translated(context, StockMessage::MsgGroupLeftBy)
.await
- .replace1(&by_contact.get_stock_name(context).await)
+ .replace1(&by_contact.get_stock_name_n_addr(context).await)
}
}
@@ -756,7 +758,7 @@ pub(crate) async fn msg_grp_img_deleted(context: &Context, by_contact: ContactId
} else {
translated(context, StockMessage::MsgGrpImgDeletedBy)
.await
- .replace1(&by_contact.get_stock_name(context).await)
+ .replace1(&by_contact.get_stock_name_n_addr(context).await)
}
}
@@ -782,13 +784,9 @@ pub(crate) async fn secure_join_started(
/// Stock string: `%1$s replied, waiting for being added to the groupβ¦`.
pub(crate) async fn secure_join_replies(context: &Context, contact_id: ContactId) -> String {
- if let Ok(contact) = Contact::get_by_id(context, contact_id).await {
- translated(context, StockMessage::SecureJoinReplies)
- .await
- .replace1(contact.get_display_name())
- } else {
- format!("secure_join_replies: unknown contact {contact_id}")
- }
+ translated(context, StockMessage::SecureJoinReplies)
+ .await
+ .replace1(&contact_id.get_stock_name(context).await)
}
/// Stock string: `Scan to chat with %1$s`.
@@ -881,7 +879,7 @@ pub(crate) async fn msg_location_enabled_by(context: &Context, contact: ContactI
} else {
translated(context, StockMessage::MsgLocationEnabledBy)
.await
- .replace1(&contact.get_stock_name(context).await)
+ .replace1(&contact.get_stock_name_n_addr(context).await)
}
}
@@ -950,7 +948,7 @@ pub(crate) async fn msg_ephemeral_timer_disabled(
} else {
translated(context, StockMessage::MsgEphemeralTimerDisabledBy)
.await
- .replace1(&by_contact.get_stock_name(context).await)
+ .replace1(&by_contact.get_stock_name_n_addr(context).await)
}
}
@@ -968,7 +966,7 @@ pub(crate) async fn msg_ephemeral_timer_enabled(
translated(context, StockMessage::MsgEphemeralTimerEnabledBy)
.await
.replace1(timer)
- .replace2(&by_contact.get_stock_name(context).await)
+ .replace2(&by_contact.get_stock_name_n_addr(context).await)
}
}
@@ -979,7 +977,7 @@ pub(crate) async fn msg_ephemeral_timer_minute(context: &Context, by_contact: Co
} else {
translated(context, StockMessage::MsgEphemeralTimerMinuteBy)
.await
- .replace1(&by_contact.get_stock_name(context).await)
+ .replace1(&by_contact.get_stock_name_n_addr(context).await)
}
}
@@ -990,7 +988,7 @@ pub(crate) async fn msg_ephemeral_timer_hour(context: &Context, by_contact: Cont
} else {
translated(context, StockMessage::MsgEphemeralTimerHourBy)
.await
- .replace1(&by_contact.get_stock_name(context).await)
+ .replace1(&by_contact.get_stock_name_n_addr(context).await)
}
}
@@ -1001,7 +999,7 @@ pub(crate) async fn msg_ephemeral_timer_day(context: &Context, by_contact: Conta
} else {
translated(context, StockMessage::MsgEphemeralTimerDayBy)
.await
- .replace1(&by_contact.get_stock_name(context).await)
+ .replace1(&by_contact.get_stock_name_n_addr(context).await)
}
}
@@ -1012,7 +1010,7 @@ pub(crate) async fn msg_ephemeral_timer_week(context: &Context, by_contact: Cont
} else {
translated(context, StockMessage::MsgEphemeralTimerWeekBy)
.await
- .replace1(&by_contact.get_stock_name(context).await)
+ .replace1(&by_contact.get_stock_name_n_addr(context).await)
}
}
@@ -1053,26 +1051,16 @@ pub(crate) async fn error_no_network(context: &Context) -> String {
translated(context, StockMessage::ErrorNoNetwork).await
}
-/// Stock string: `Chat protection enabled.`.
-pub(crate) async fn protection_enabled(context: &Context, by_contact: ContactId) -> String {
- if by_contact == ContactId::SELF {
- translated(context, StockMessage::YouEnabledProtection).await
- } else {
- translated(context, StockMessage::ProtectionEnabledBy)
- .await
- .replace1(&by_contact.get_stock_name(context).await)
- }
+/// Stock string: `Messages are guaranteed to be end-to-end encrypted from now on.`
+pub(crate) async fn chat_protection_enabled(context: &Context) -> String {
+ translated(context, StockMessage::ChatProtectionEnabled).await
}
-/// Stock string: `Chat protection disabled.`.
-pub(crate) async fn protection_disabled(context: &Context, by_contact: ContactId) -> String {
- if by_contact == ContactId::SELF {
- translated(context, StockMessage::YouDisabledProtection).await
- } else {
- translated(context, StockMessage::ProtectionDisabledBy)
- .await
- .replace1(&by_contact.get_stock_name(context).await)
- }
+/// Stock string: `%1$s sent a message from another device.`
+pub(crate) async fn chat_protection_disabled(context: &Context, contact_id: ContactId) -> String {
+ translated(context, StockMessage::ChatProtectionDisabled)
+ .await
+ .replace1(&contact_id.get_stock_name(context).await)
}
/// Stock string: `Reply`.
@@ -1104,7 +1092,7 @@ pub(crate) async fn msg_ephemeral_timer_minutes(
translated(context, StockMessage::MsgEphemeralTimerMinutesBy)
.await
.replace1(minutes)
- .replace2(&by_contact.get_stock_name(context).await)
+ .replace2(&by_contact.get_stock_name_n_addr(context).await)
}
}
@@ -1122,7 +1110,7 @@ pub(crate) async fn msg_ephemeral_timer_hours(
translated(context, StockMessage::MsgEphemeralTimerHoursBy)
.await
.replace1(hours)
- .replace2(&by_contact.get_stock_name(context).await)
+ .replace2(&by_contact.get_stock_name_n_addr(context).await)
}
}
@@ -1140,7 +1128,7 @@ pub(crate) async fn msg_ephemeral_timer_days(
translated(context, StockMessage::MsgEphemeralTimerDaysBy)
.await
.replace1(days)
- .replace2(&by_contact.get_stock_name(context).await)
+ .replace2(&by_contact.get_stock_name_n_addr(context).await)
}
}
@@ -1158,7 +1146,7 @@ pub(crate) async fn msg_ephemeral_timer_weeks(
translated(context, StockMessage::MsgEphemeralTimerWeeksBy)
.await
.replace1(weeks)
- .replace2(&by_contact.get_stock_name(context).await)
+ .replace2(&by_contact.get_stock_name_n_addr(context).await)
}
}
@@ -1332,11 +1320,19 @@ impl Context {
pub(crate) async fn stock_protection_msg(
&self,
protect: ProtectionStatus,
- from_id: ContactId,
+ contact_id: Option,
) -> String {
match protect {
- ProtectionStatus::Unprotected => protection_enabled(self, from_id).await,
- ProtectionStatus::Protected => protection_disabled(self, from_id).await,
+ ProtectionStatus::Unprotected | ProtectionStatus::ProtectionBroken => {
+ if let Some(contact_id) = contact_id {
+ chat_protection_disabled(self, contact_id).await
+ } else {
+ // In a group chat, it's not possible to downgrade verification.
+ // In a 1:1 chat, the `contact_id` always has to be provided.
+ "[Error] No contact_id given".to_string()
+ }
+ }
+ ProtectionStatus::Protected => chat_protection_enabled(self).await,
}
}
diff --git a/src/summary.rs b/src/summary.rs
index 731f4edf0..dfeab5f59 100644
--- a/src/summary.rs
+++ b/src/summary.rs
@@ -82,7 +82,7 @@ impl Summary {
.map(SummaryPrefix::Username)
}
}
- Chattype::Single | Chattype::Undefined => None,
+ Chattype::Single => None,
}
};
diff --git a/src/test_utils.rs b/src/test_utils.rs
index 596c0cba9..fe02cfa1f 100644
--- a/src/test_utils.rs
+++ b/src/test_utils.rs
@@ -27,15 +27,19 @@ use crate::chat::{
};
use crate::chatlist::Chatlist;
use crate::config::Config;
-use crate::constants::Chattype;
+use crate::constants::{Blocked, Chattype};
use crate::constants::{DC_GCL_NO_SPECIALS, DC_MSG_ID_DAYMARKER};
use crate::contact::{Contact, ContactAddress, ContactId, Modifier, Origin};
use crate::context::Context;
+use crate::e2ee::EncryptHelper;
use crate::events::{Event, EventType, Events};
-use crate::key::{self, DcKey, KeyPair, KeyPairUse};
+use crate::key::{self, DcKey, KeyPairUse};
use crate::message::{update_msg_state, Message, MessageState, MsgId, Viewtype};
-use crate::mimeparser::MimeMessage;
+use crate::mimeparser::{MimeMessage, SystemMessage};
+use crate::peerstate::Peerstate;
+use crate::pgp::KeyPair;
use crate::receive_imf::receive_imf;
+use crate::securejoin::{get_securejoin_qr, join_securejoin};
use crate::stock_str::StockStrings;
use crate::tools::EmailAddress;
@@ -115,6 +119,10 @@ impl TestContextManager {
msg: &str,
) -> Message {
let received_msg = self.send_recv(from, to, msg).await;
+ assert_eq!(
+ received_msg.chat_blocked, Blocked::Request,
+ "`send_recv_accept()` is meant to be used for chat requests. Use `send_recv()` if the chat is already accepted."
+ );
received_msg.chat_id.accept(to).await.unwrap();
received_msg
}
@@ -158,6 +166,27 @@ impl TestContextManager {
new_addr
);
}
+
+ pub async fn execute_securejoin(&self, scanner: &TestContext, scanned: &TestContext) {
+ self.section(&format!(
+ "{} scans {}'s QR code",
+ scanner.name(),
+ scanned.name()
+ ));
+
+ let qr = get_securejoin_qr(&scanned.ctx, None).await.unwrap();
+ join_securejoin(&scanner.ctx, &qr).await.unwrap();
+
+ loop {
+ if let Some(sent) = scanner.pop_sent_msg_opt(Duration::ZERO).await {
+ scanned.recv_msg(&sent).await;
+ } else if let Some(sent) = scanned.pop_sent_msg_opt(Duration::ZERO).await {
+ scanner.recv_msg(&sent).await;
+ } else {
+ break;
+ }
+ }
+ }
}
#[derive(Debug, Clone, Default)]
@@ -562,19 +591,21 @@ impl TestContext {
Contact::get_by_id(&self.ctx, contact_id).await.unwrap()
}
- /// Returns 1:1 [`Chat`] with another account, if it exists.
+ /// Returns 1:1 [`Chat`] with another account. Panics if it doesn't exist.
///
/// This first creates a contact using the configured details on the other account, then
- /// creates a 1:1 chat with this contact.
- pub async fn get_chat(&self, other: &TestContext) -> Option {
+ /// gets the 1:1 chat with this contact.
+ pub async fn get_chat(&self, other: &TestContext) -> Chat {
let contact = self.add_or_lookup_contact(other).await;
- match ChatId::lookup_by_contact(&self.ctx, contact.id)
+ let chat_id = ChatId::lookup_by_contact(&self.ctx, contact.id)
.await
.unwrap()
- {
- Some(id) => Some(Chat::load_from_db(&self.ctx, id).await.unwrap()),
- None => None,
- }
+ .expect(
+ "There is no chat with this contact. \
+ Hint: Use create_chat() instead of get_chat() if this is expected.",
+ );
+
+ Chat::load_from_db(&self.ctx, chat_id).await.unwrap()
}
/// Creates or returns an existing 1:1 [`Chat`] with another account.
@@ -633,7 +664,6 @@ impl TestContext {
res
}
- #[allow(unused)]
pub async fn golden_test_chat(&self, chat_id: ChatId, filename: &str) {
let filename = Path::new("test-data/golden/").join(filename);
@@ -642,7 +672,7 @@ impl TestContext {
// We're using `unwrap_or_default()` here so that if the file doesn't exist,
// it can be created using `write` below.
let expected = fs::read(&filename).await.unwrap_or_default();
- let expected = String::from_utf8(expected).unwrap();
+ let expected = String::from_utf8(expected).unwrap().replace("\r\n", "\n");
if (std::env::var("UPDATE_GOLDEN_TESTS") == Ok("1".to_string())) && actual != expected {
fs::write(&filename, &actual)
.await
@@ -660,8 +690,6 @@ impl TestContext {
/// You can use this to debug your test by printing the entire chat conversation.
// This code is mainly the same as `log_msglist` in `cmdline.rs`, so one day, we could
// merge them to a public function in the `deltachat` crate.
- #[allow(dead_code)]
- #[allow(clippy::indexing_slicing)]
async fn display_chat(&self, chat_id: ChatId) -> String {
let mut res = String::new();
@@ -886,7 +914,7 @@ pub fn alice_keypair() -> KeyPair {
let secret = key::SignedSecretKey::from_asc(include_str!("../test-data/key/alice-secret.asc"))
.unwrap()
.0;
- key::KeyPair {
+ KeyPair {
addr,
public,
secret,
@@ -904,7 +932,7 @@ pub fn bob_keypair() -> KeyPair {
let secret = key::SignedSecretKey::from_asc(include_str!("../test-data/key/bob-secret.asc"))
.unwrap()
.0;
- key::KeyPair {
+ KeyPair {
addr,
public,
secret,
@@ -914,7 +942,7 @@ pub fn bob_keypair() -> KeyPair {
/// Load a pre-generated keypair for fiona@example.net from disk.
///
/// Like [alice_keypair] but a different key and identity.
-pub fn fiona_keypair() -> key::KeyPair {
+pub fn fiona_keypair() -> KeyPair {
let addr = EmailAddress::new("fiona@example.net").unwrap();
let public = key::SignedPublicKey::from_asc(include_str!("../test-data/key/fiona-public.asc"))
.unwrap()
@@ -922,7 +950,7 @@ pub fn fiona_keypair() -> key::KeyPair {
let secret = key::SignedSecretKey::from_asc(include_str!("../test-data/key/fiona-secret.asc"))
.unwrap()
.0;
- key::KeyPair {
+ KeyPair {
addr,
public,
secret,
@@ -1014,6 +1042,26 @@ fn print_logevent(logevent: &LogEvent) {
}
}
+/// Saves the other account's public key as verified.
+pub(crate) async fn mark_as_verified(this: &TestContext, other: &TestContext) {
+ let mut peerstate = Peerstate::from_header(
+ &EncryptHelper::new(other).await.unwrap().get_aheader(),
+ // We have to give 0 as the time, not the current time:
+ // The time is going to be saved in peerstate.last_seen.
+ // The code in `peerstate.rs` then compares `if message_time > self.last_seen`,
+ // and many similar checks in peerstate.rs, and doesn't allow changes otherwise.
+ // Giving the current time would mean that message_time == peerstate.last_seen,
+ // so changes would not be allowed.
+ // This might lead to flaky tests.
+ 0,
+ );
+
+ peerstate.verified_key = peerstate.public_key.clone();
+ peerstate.verified_key_fingerprint = peerstate.public_key_fingerprint.clone();
+
+ peerstate.save_to_db(&this.sql).await.unwrap();
+}
+
/// Pretty-print an event to stdout
///
/// Done during tests this is captured by `cargo test` and associated with the test itself.
@@ -1120,7 +1168,17 @@ async fn write_msg(context: &Context, prefix: &str, msg: &Message, buf: &mut Str
} else {
"[FRESH]"
},
- if msg.is_info() { "[INFO]" } else { "" },
+ if msg.is_info() {
+ if msg.get_info_type() == SystemMessage::ChatProtectionEnabled {
+ "[INFO π‘οΈ]"
+ } else if msg.get_info_type() == SystemMessage::ChatProtectionDisabled {
+ "[INFO π‘οΈβ]"
+ } else {
+ "[INFO]"
+ }
+ } else {
+ ""
+ },
if msg.get_viewtype() == Viewtype::VideochatInvitation {
format!(
"[VIDEOCHAT-INVITATION: {}, type={}]",
diff --git a/src/tests.rs b/src/tests.rs
index 9067f1962..3b417c0d4 100644
--- a/src/tests.rs
+++ b/src/tests.rs
@@ -1 +1,2 @@
mod aeap;
+mod verified_chats;
diff --git a/src/tests/aeap.rs b/src/tests/aeap.rs
index 0c43e8c17..db7cedd5f 100644
--- a/src/tests/aeap.rs
+++ b/src/tests/aeap.rs
@@ -8,10 +8,10 @@ use crate::contact;
use crate::contact::Contact;
use crate::contact::ContactId;
use crate::message::Message;
-use crate::peerstate;
use crate::peerstate::Peerstate;
use crate::receive_imf::receive_imf;
use crate::stock_str;
+use crate::test_utils::mark_as_verified;
use crate::test_utils::TestContext;
use crate::test_utils::TestContextManager;
@@ -134,11 +134,11 @@ async fn check_aeap_transition(
let fiona = tcm.fiona().await;
tcm.send_recv_accept(&fiona, &bob, "Hi").await;
- tcm.send_recv_accept(&bob, &fiona, "Hi back").await;
+ tcm.send_recv(&bob, &fiona, "Hi back").await;
}
tcm.send_recv_accept(&alice, &bob, "Hi").await;
- tcm.send_recv_accept(&bob, &alice, "Hi back").await;
+ tcm.send_recv(&bob, &alice, "Hi back").await;
if verified {
mark_as_verified(&alice, &bob).await;
@@ -327,19 +327,6 @@ async fn check_no_transition_done(groups: &[ChatId], old_alice_addr: &str, bob:
}
}
-async fn mark_as_verified(this: &TestContext, other: &TestContext) {
- let other_addr = other.get_primary_self_addr().await.unwrap();
- let mut peerstate = peerstate::Peerstate::from_addr(this, &other_addr)
- .await
- .unwrap()
- .unwrap();
-
- peerstate.verified_key = peerstate.public_key.clone();
- peerstate.verified_key_fingerprint = peerstate.public_key_fingerprint.clone();
-
- peerstate.save_to_db(&this.sql).await.unwrap();
-}
-
async fn get_last_info_msg(t: &TestContext, chat_id: ChatId) -> Option {
let msgs = chat::get_chat_msgs_ex(
&t.ctx,
@@ -368,7 +355,7 @@ async fn test_aeap_replay_attack() -> Result<()> {
let bob = tcm.bob().await;
tcm.send_recv_accept(&alice, &bob, "Hi").await;
- tcm.send_recv_accept(&bob, &alice, "Hi back").await;
+ tcm.send_recv(&bob, &alice, "Hi back").await;
let group =
chat::create_group_chat(&bob, chat::ProtectionStatus::Unprotected, "Group 0").await?;
diff --git a/src/tests/verified_chats.rs b/src/tests/verified_chats.rs
new file mode 100644
index 000000000..413338929
--- /dev/null
+++ b/src/tests/verified_chats.rs
@@ -0,0 +1,770 @@
+use anyhow::Result;
+use pretty_assertions::assert_eq;
+
+use crate::chat::{Chat, ProtectionStatus};
+use crate::chatlist::Chatlist;
+use crate::config::Config;
+use crate::constants::DC_GCL_FOR_FORWARDING;
+use crate::contact::VerifiedStatus;
+use crate::contact::{Contact, Origin};
+use crate::message::{Message, Viewtype};
+use crate::mimefactory::MimeFactory;
+use crate::mimeparser::SystemMessage;
+use crate::receive_imf::receive_imf;
+use crate::stock_str;
+use crate::test_utils::{get_chat_msg, mark_as_verified, TestContext, TestContextManager};
+use crate::{e2ee, message};
+
+#[tokio::test(flavor = "multi_thread", worker_threads = 2)]
+async fn test_verified_oneonone_chat_broken_by_classical() {
+ check_verified_oneonone_chat(true).await;
+}
+
+#[tokio::test(flavor = "multi_thread", worker_threads = 2)]
+async fn test_verified_oneonone_chat_broken_by_device_change() {
+ check_verified_oneonone_chat(false).await;
+}
+
+async fn check_verified_oneonone_chat(broken_by_classical_email: bool) {
+ let mut tcm = TestContextManager::new();
+ let alice = tcm.alice().await;
+ let bob = tcm.bob().await;
+ enable_verified_oneonone_chats(&[&alice, &bob]).await;
+
+ tcm.execute_securejoin(&alice, &bob).await;
+
+ assert_verified(&alice, &bob, ProtectionStatus::Protected).await;
+ assert_verified(&bob, &alice, ProtectionStatus::Protected).await;
+
+ if broken_by_classical_email {
+ tcm.section("Bob uses a classical MUA to send a message to Alice");
+ receive_imf(
+ &alice,
+ b"Subject: Re: Message from alice\r\n\
+ From: \r\n\
+ To: \r\n\
+ Date: Mon, 12 Dec 2022 14:33:39 +0000\r\n\
+ Message-ID: \r\n\
+ \r\n\
+ Heyho!\r\n",
+ false,
+ )
+ .await
+ .unwrap()
+ .unwrap();
+ } else {
+ tcm.section("Bob sets up another Delta Chat device");
+ let bob2 = TestContext::new().await;
+ enable_verified_oneonone_chats(&[&bob2]).await;
+ bob2.set_name("bob2");
+ bob2.configure_addr("bob@example.net").await;
+
+ tcm.send_recv(&bob2, &alice, "Using another device now")
+ .await;
+ }
+
+ // Bob's contact is still verified, but the chat isn't marked as protected anymore
+ assert_verified(&alice, &bob, ProtectionStatus::ProtectionBroken).await;
+
+ tcm.section("Bob sends another message from DC");
+ tcm.send_recv(&bob, &alice, "Using DC again").await;
+
+ let contact = alice.add_or_lookup_contact(&bob).await;
+ assert_eq!(
+ contact.is_verified(&alice.ctx).await.unwrap(),
+ VerifiedStatus::BidirectVerified
+ );
+
+ // Bob's chat is marked as verified again
+ assert_verified(&alice, &bob, ProtectionStatus::Protected).await;
+}
+
+#[tokio::test(flavor = "multi_thread", worker_threads = 2)]
+async fn test_create_verified_oneonone_chat() -> Result<()> {
+ let mut tcm = TestContextManager::new();
+ let alice = tcm.alice().await;
+ let bob = tcm.bob().await;
+ let fiona = tcm.fiona().await;
+ enable_verified_oneonone_chats(&[&alice, &bob, &fiona]).await;
+
+ tcm.execute_securejoin(&alice, &bob).await;
+ tcm.execute_securejoin(&bob, &fiona).await;
+ assert_verified(&alice, &bob, ProtectionStatus::Protected).await;
+ assert_verified(&bob, &alice, ProtectionStatus::Protected).await;
+ assert_verified(&bob, &fiona, ProtectionStatus::Protected).await;
+ assert_verified(&fiona, &bob, ProtectionStatus::Protected).await;
+
+ let group_id = bob
+ .create_group_with_members(
+ ProtectionStatus::Protected,
+ "Group with everyone",
+ &[&alice, &fiona],
+ )
+ .await;
+ assert_eq!(
+ get_chat_msg(&bob, group_id, 0, 1).await.get_info_type(),
+ SystemMessage::ChatProtectionEnabled
+ );
+
+ {
+ let sent = bob.send_text(group_id, "Heyho").await;
+ alice.recv_msg(&sent).await;
+
+ let msg = fiona.recv_msg(&sent).await;
+ assert_eq!(
+ get_chat_msg(&fiona, msg.chat_id, 0, 2)
+ .await
+ .get_info_type(),
+ SystemMessage::ChatProtectionEnabled
+ );
+ }
+
+ // Alice and Fiona should now be verified because of gossip
+ let alice_fiona_contact = alice.add_or_lookup_contact(&fiona).await;
+ assert_eq!(
+ alice_fiona_contact.is_verified(&alice).await.unwrap(),
+ VerifiedStatus::BidirectVerified
+ );
+
+ // As soon as Alice creates a chat with Fiona, it should directly be protected
+ {
+ let chat = alice.create_chat(&fiona).await;
+ assert!(chat.is_protected());
+
+ let msg = alice.get_last_msg().await;
+ let expected_text = stock_str::chat_protection_enabled(&alice).await;
+ assert_eq!(msg.text, expected_text);
+ }
+
+ // Fiona should also see the chat as protected
+ {
+ let rcvd = tcm.send_recv(&alice, &fiona, "Hi Fiona").await;
+ let alice_fiona_id = rcvd.chat_id;
+ let chat = Chat::load_from_db(&fiona, alice_fiona_id).await?;
+ assert!(chat.is_protected());
+
+ let msg0 = get_chat_msg(&fiona, chat.id, 0, 2).await;
+ let expected_text = stock_str::chat_protection_enabled(&fiona).await;
+ assert_eq!(msg0.text, expected_text);
+ }
+
+ tcm.section("Fiona reinstalls DC");
+ drop(fiona);
+
+ let fiona_new = tcm.unconfigured().await;
+ enable_verified_oneonone_chats(&[&fiona_new]).await;
+ fiona_new.configure_addr("fiona@example.net").await;
+ e2ee::ensure_secret_key_exists(&fiona_new).await?;
+
+ tcm.send_recv(&fiona_new, &alice, "I have a new device")
+ .await;
+
+ // The chat should be and stay unprotected
+ {
+ let chat = alice.get_chat(&fiona_new).await;
+ assert!(!chat.is_protected());
+ assert!(chat.is_protection_broken());
+
+ // After recreating the chat, it should still be unprotected
+ chat.id.delete(&alice).await?;
+
+ let chat = alice.create_chat(&fiona_new).await;
+ assert!(!chat.is_protected());
+ assert!(!chat.is_protection_broken());
+ }
+
+ Ok(())
+}
+
+#[tokio::test(flavor = "multi_thread", worker_threads = 2)]
+async fn test_create_unverified_oneonone_chat() -> Result<()> {
+ let mut tcm = TestContextManager::new();
+ let alice = tcm.alice().await;
+ let bob = tcm.bob().await;
+ enable_verified_oneonone_chats(&[&alice, &bob]).await;
+
+ // A chat with an unknown contact should be created unprotected
+ let chat = alice.create_chat(&bob).await;
+ assert!(!chat.is_protected());
+ assert!(!chat.is_protection_broken());
+
+ receive_imf(
+ &alice,
+ b"From: Bob \n\
+ To: alice@example.org\n\
+ Message-ID: <1234-2@example.org>\n\
+ \n\
+ hello\n",
+ false,
+ )
+ .await?;
+
+ chat.id.delete(&alice).await.unwrap();
+ // Now Bob is a known contact, new chats should still be created unprotected
+ let chat = alice.create_chat(&bob).await;
+ assert!(!chat.is_protected());
+ assert!(!chat.is_protection_broken());
+
+ tcm.send_recv(&bob, &alice, "hi").await;
+ chat.id.delete(&alice).await.unwrap();
+ // Now we have a public key, new chats should still be created unprotected
+ let chat = alice.create_chat(&bob).await;
+ assert!(!chat.is_protected());
+ assert!(!chat.is_protection_broken());
+
+ Ok(())
+}
+
+#[tokio::test(flavor = "multi_thread", worker_threads = 2)]
+async fn test_degrade_verified_oneonone_chat() -> Result<()> {
+ let mut tcm = TestContextManager::new();
+ let alice = tcm.alice().await;
+ let bob = tcm.bob().await;
+ enable_verified_oneonone_chats(&[&alice, &bob]).await;
+
+ mark_as_verified(&alice, &bob).await;
+
+ let alice_chat = alice.create_chat(&bob).await;
+ assert!(alice_chat.is_protected());
+
+ receive_imf(
+ &alice,
+ b"From: Bob \n\
+ To: alice@example.org\n\
+ Message-ID: <1234-2@example.org>\n\
+ \n\
+ hello\n",
+ false,
+ )
+ .await?;
+
+ let contact_id = Contact::lookup_id_by_addr(&alice, "bob@example.net", Origin::Hidden)
+ .await?
+ .unwrap();
+
+ let msg0 = get_chat_msg(&alice, alice_chat.id, 0, 3).await;
+ let enabled = stock_str::chat_protection_enabled(&alice).await;
+ assert_eq!(msg0.text, enabled);
+ assert_eq!(msg0.param.get_cmd(), SystemMessage::ChatProtectionEnabled);
+
+ let msg1 = get_chat_msg(&alice, alice_chat.id, 1, 3).await;
+ let disabled = stock_str::chat_protection_disabled(&alice, contact_id).await;
+ assert_eq!(msg1.text, disabled);
+ assert_eq!(msg1.param.get_cmd(), SystemMessage::ChatProtectionDisabled);
+
+ let msg2 = get_chat_msg(&alice, alice_chat.id, 2, 3).await;
+ assert_eq!(msg2.text, "hello".to_string());
+ assert!(!msg2.is_system_message());
+
+ Ok(())
+}
+
+#[tokio::test(flavor = "multi_thread", worker_threads = 2)]
+async fn test_verified_oneonone_chat_enable_disable() -> Result<()> {
+ let mut tcm = TestContextManager::new();
+ let alice = tcm.alice().await;
+ let bob = tcm.bob().await;
+ enable_verified_oneonone_chats(&[&alice, &bob]).await;
+
+ // Alice & Bob verify each other
+ mark_as_verified(&alice, &bob).await;
+ mark_as_verified(&bob, &alice).await;
+
+ let chat = alice.create_chat(&bob).await;
+ assert!(chat.is_protected());
+
+ for alice_accepts_breakage in [true, false] {
+ // Bob uses Thunderbird to send a message
+ receive_imf(
+ &alice,
+ format!(
+ "From: Bob \n\
+ To: alice@example.org\n\
+ Message-ID: <1234-2{alice_accepts_breakage}@example.org>\n\
+ \n\
+ Message from Thunderbird\n"
+ )
+ .as_bytes(),
+ false,
+ )
+ .await?;
+
+ let chat = alice.get_chat(&bob).await;
+ assert!(!chat.is_protected());
+ assert!(chat.is_protection_broken());
+
+ if alice_accepts_breakage {
+ tcm.section("Alice clicks 'Accept' on the input-bar-dialog");
+ chat.id.accept(&alice).await?;
+ let chat = alice.get_chat(&bob).await;
+ assert!(!chat.is_protected());
+ assert!(!chat.is_protection_broken());
+ }
+
+ // Bob sends a message from DC again
+ tcm.send_recv(&bob, &alice, "Hello from DC").await;
+ let chat = alice.get_chat(&bob).await;
+ assert!(chat.is_protected());
+ assert!(!chat.is_protection_broken());
+ }
+
+ alice
+ .golden_test_chat(chat.id, "test_verified_oneonone_chat_enable_disable")
+ .await;
+
+ Ok(())
+}
+
+/// Messages with old timestamps are difficult for verified chats:
+/// - They must not be sorted over a protection-changed info message.
+/// That's what `test_old_message_2` tests
+/// - If they change the protection, then they must not be sorted over existing other messages,
+/// because then the protection-changed info message would also be above these existing messages.
+/// That's what `test_old_message_3` tests.
+///
+/// `test_old_message_1` tests the case where both the old and the new message
+/// change verification
+#[tokio::test(flavor = "multi_thread", worker_threads = 2)]
+async fn test_old_message_1() -> Result<()> {
+ let mut tcm = TestContextManager::new();
+ let alice = tcm.alice().await;
+ let bob = tcm.bob().await;
+ enable_verified_oneonone_chats(&[&alice, &bob]).await;
+
+ mark_as_verified(&alice, &bob).await;
+
+ let chat = alice.create_chat(&bob).await; // This creates a protection-changed info message
+ assert!(chat.is_protected());
+
+ // This creates protection-changed info message #2;
+ // even though the date is old, info message and email must be sorted below the original info message.
+ receive_imf(
+ &alice,
+ b"From: Bob \n\
+ To: alice@example.org\n\
+ Message-ID: <1234-2-3@example.org>\n\
+ Date: Sat, 07 Dec 2019 19:00:27 +0000\n\
+ \n\
+ Message from Thunderbird\n",
+ true,
+ )
+ .await?;
+
+ alice.golden_test_chat(chat.id, "test_old_message_1").await;
+
+ Ok(())
+}
+
+#[tokio::test(flavor = "multi_thread", worker_threads = 2)]
+async fn test_old_message_2() -> Result<()> {
+ let mut tcm = TestContextManager::new();
+ let alice = tcm.alice().await;
+ let bob = tcm.bob().await;
+ enable_verified_oneonone_chats(&[&alice, &bob]).await;
+
+ mark_as_verified(&alice, &bob).await;
+
+ // This creates protection-changed info message #1:
+ let chat = alice.create_chat(&bob).await;
+ assert!(chat.is_protected());
+ let protection_msg = alice.get_last_msg().await;
+ assert_eq!(
+ protection_msg.param.get_cmd(),
+ SystemMessage::ChatProtectionEnabled
+ );
+
+ // This creates protection-changed info message #2.
+ let first_email = receive_imf(
+ &alice,
+ b"From: Bob \n\
+ To: alice@example.org\n\
+ Message-ID: <1234-2-3@example.org>\n\
+ Date: Sun, 08 Dec 2019 19:00:27 +0000\n\
+ \n\
+ Somewhat old message\n",
+ false,
+ )
+ .await?
+ .unwrap();
+
+ // Both messages will get the same timestamp as the protection-changed
+ // message, so this one will be sorted under the previous one
+ // even though it has an older timestamp.
+ let second_email = receive_imf(
+ &alice,
+ b"From: Bob \n\
+ To: alice@example.org\n\
+ Message-ID: <2319-2-3@example.org>\n\
+ Date: Sat, 07 Dec 2019 19:00:27 +0000\n\
+ \n\
+ Even older message, that must NOT be shown before the info message\n",
+ true,
+ )
+ .await?
+ .unwrap();
+
+ assert_eq!(first_email.sort_timestamp, second_email.sort_timestamp);
+ assert_eq!(first_email.sort_timestamp, protection_msg.timestamp_sort);
+
+ alice.golden_test_chat(chat.id, "test_old_message_2").await;
+
+ Ok(())
+}
+
+#[tokio::test(flavor = "multi_thread", worker_threads = 2)]
+async fn test_old_message_3() -> Result<()> {
+ let mut tcm = TestContextManager::new();
+ let alice = tcm.alice().await;
+ let bob = tcm.bob().await;
+ enable_verified_oneonone_chats(&[&alice, &bob]).await;
+
+ mark_as_verified(&alice, &bob).await;
+ mark_as_verified(&bob, &alice).await;
+
+ tcm.send_recv_accept(&bob, &alice, "Heyho from my verified device!")
+ .await;
+
+ // This unverified message must not be sorted over the message sent in the previous line:
+ receive_imf(
+ &alice,
+ b"From: Bob \n\
+ To: alice@example.org\n\
+ Message-ID: <1234-2-3@example.org>\n\
+ Date: Sat, 07 Dec 2019 19:00:27 +0000\n\
+ \n\
+ Old, unverified message\n",
+ true,
+ )
+ .await?;
+
+ alice
+ .golden_test_chat(alice.get_chat(&bob).await.id, "test_old_message_3")
+ .await;
+
+ Ok(())
+}
+
+/// Alice is offline for some time.
+/// When she comes online, first her inbox is synced and then her sentbox.
+/// This test tests that the messages are still in the right order.
+#[tokio::test(flavor = "multi_thread", worker_threads = 2)]
+async fn test_old_message_4() -> Result<()> {
+ let alice = TestContext::new_alice().await;
+ let msg_incoming = receive_imf(
+ &alice,
+ b"From: Bob \n\
+ To: alice@example.org\n\
+ Message-ID: <1234-2-3@example.org>\n\
+ Date: Sun, 08 Dec 2019 19:00:27 +0000\n\
+ \n\
+ Thanks, Alice!\n",
+ true,
+ )
+ .await?
+ .unwrap();
+
+ let msg_sent = receive_imf(
+ &alice,
+ b"From: alice@example.org\n\
+ To: Bob \n\
+ Message-ID: <1234-2-4@example.org>\n\
+ Date: Sat, 07 Dec 2019 19:00:27 +0000\n\
+ \n\
+ Happy birthday, Bob!\n",
+ true,
+ )
+ .await?
+ .unwrap();
+
+ // The "Happy birthday" message should be shown first, and then the "Thanks" message
+ assert!(msg_sent.sort_timestamp < msg_incoming.sort_timestamp);
+
+ Ok(())
+}
+
+/// Alice is offline for some time.
+/// When they come online, first their sentbox is synced and then their inbox.
+/// This test tests that the messages are still in the right order.
+#[tokio::test(flavor = "multi_thread", worker_threads = 2)]
+async fn test_old_message_5() -> Result<()> {
+ let alice = TestContext::new_alice().await;
+ let msg_sent = receive_imf(
+ &alice,
+ b"From: alice@example.org\n\
+ To: Bob \n\
+ Message-ID: <1234-2-4@example.org>\n\
+ Date: Sat, 07 Dec 2019 19:00:27 +0000\n\
+ \n\
+ Happy birthday, Bob!\n",
+ true,
+ )
+ .await?
+ .unwrap();
+
+ let msg_incoming = receive_imf(
+ &alice,
+ b"From: Bob \n\
+ To: alice@example.org\n\
+ Message-ID: <1234-2-3@example.org>\n\
+ Date: Sun, 07 Dec 2019 19:00:26 +0000\n\
+ \n\
+ Happy birthday to me, Alice!\n",
+ false,
+ )
+ .await?
+ .unwrap();
+
+ assert!(msg_sent.sort_timestamp == msg_incoming.sort_timestamp);
+ alice
+ .golden_test_chat(msg_sent.chat_id, "test_old_message_5")
+ .await;
+
+ Ok(())
+}
+
+#[tokio::test(flavor = "multi_thread", worker_threads = 2)]
+async fn test_mdn_doesnt_disable_verification() -> Result<()> {
+ let mut tcm = TestContextManager::new();
+ let alice = tcm.alice().await;
+ let bob = tcm.bob().await;
+ enable_verified_oneonone_chats(&[&alice, &bob]).await;
+ bob.set_config_bool(Config::MdnsEnabled, true).await?;
+
+ // Alice & Bob verify each other
+ mark_as_verified(&alice, &bob).await;
+ mark_as_verified(&bob, &alice).await;
+
+ let rcvd = tcm.send_recv_accept(&alice, &bob, "Heyho").await;
+ message::markseen_msgs(&bob, vec![rcvd.id]).await?;
+
+ let mimefactory = MimeFactory::from_mdn(&bob, &rcvd, vec![]).await?;
+ let rendered_msg = mimefactory.render(&bob).await?;
+ let body = rendered_msg.message;
+ receive_imf(&alice, body.as_bytes(), false).await.unwrap();
+
+ assert_verified(&alice, &bob, ProtectionStatus::Protected).await;
+
+ Ok(())
+}
+
+#[tokio::test(flavor = "multi_thread", worker_threads = 2)]
+async fn test_outgoing_mua_msg() -> Result<()> {
+ let mut tcm = TestContextManager::new();
+ let alice = tcm.alice().await;
+ let bob = tcm.bob().await;
+ enable_verified_oneonone_chats(&[&alice, &bob]).await;
+
+ mark_as_verified(&alice, &bob).await;
+ mark_as_verified(&bob, &alice).await;
+
+ tcm.send_recv_accept(&bob, &alice, "Heyho from DC").await;
+ assert_verified(&alice, &bob, ProtectionStatus::Protected).await;
+
+ let sent = receive_imf(
+ &alice,
+ b"From: alice@example.org\n\
+ To: bob@example.net\n\
+ \n\
+ One classical MUA message",
+ false,
+ )
+ .await?
+ .unwrap();
+ tcm.send_recv(&alice, &bob, "Sending with DC again").await;
+
+ alice
+ .golden_test_chat(sent.chat_id, "test_outgoing_mua_msg")
+ .await;
+
+ Ok(())
+}
+
+/// If Bob answers unencrypted from another address with a classical MUA,
+/// the message is under some circumstances still assigned to the original
+/// chat (see lookup_chat_by_reply()); this is meant to make aliases
+/// work nicely.
+/// However, if the original chat is verified, the unencrypted message
+/// must NOT be assigned to it (it would be replaced by an error
+/// message in the verified chat, so, this would just be a usability issue,
+/// not a security issue).
+#[tokio::test(flavor = "multi_thread", worker_threads = 2)]
+async fn test_reply() -> Result<()> {
+ for verified in [false, true] {
+ let mut tcm = TestContextManager::new();
+ let alice = tcm.alice().await;
+ let bob = tcm.bob().await;
+ enable_verified_oneonone_chats(&[&alice, &bob]).await;
+
+ if verified {
+ mark_as_verified(&alice, &bob).await;
+ mark_as_verified(&bob, &alice).await;
+ }
+
+ tcm.send_recv_accept(&bob, &alice, "Heyho from DC").await;
+ let encrypted_msg = tcm.send_recv(&alice, &bob, "Heyho back").await;
+
+ let unencrypted_msg = receive_imf(
+ &alice,
+ format!(
+ "From: bob@someotherdomain.org\n\
+ To: some-alias-forwarding-to-alice@example.org\n\
+ In-Reply-To: {}\n\
+ \n\
+ Weird reply",
+ encrypted_msg.rfc724_mid
+ )
+ .as_bytes(),
+ false,
+ )
+ .await?
+ .unwrap();
+
+ let unencrypted_msg = Message::load_from_db(&alice, unencrypted_msg.msg_ids[0]).await?;
+ assert_eq!(unencrypted_msg.text, "Weird reply");
+
+ if verified {
+ assert_ne!(unencrypted_msg.chat_id, encrypted_msg.chat_id);
+ } else {
+ assert_eq!(unencrypted_msg.chat_id, encrypted_msg.chat_id);
+ }
+ }
+
+ Ok(())
+}
+
+/// Regression test for the following bug:
+///
+/// - Scan your chat partner's QR Code
+/// - They change devices
+/// - They send you a message
+/// - Without accepting the encryption downgrade, scan your chat partner's QR Code again
+///
+/// -> The re-verification fails.
+#[tokio::test(flavor = "multi_thread", worker_threads = 2)]
+async fn test_break_protection_then_verify_again() -> Result<()> {
+ let mut tcm = TestContextManager::new();
+ let alice = tcm.alice().await;
+ let bob = tcm.bob().await;
+ enable_verified_oneonone_chats(&[&alice, &bob]).await;
+
+ // Cave: Bob can't write a message to Alice here.
+ // If he did, alice would increase his peerstate's last_seen timestamp.
+ // Then, after Bob reinstalls DC, alice's `if message_time > last_seen*`
+ // checks would return false (there are many checks of this form in peerstate.rs).
+ // Therefore, during the securejoin, Alice wouldn't accept the new key
+ // and reject the securejoin.
+
+ mark_as_verified(&alice, &bob).await;
+ mark_as_verified(&bob, &alice).await;
+
+ alice.create_chat(&bob).await;
+ assert_verified(&alice, &bob, ProtectionStatus::Protected).await;
+ let chats = Chatlist::try_load(&alice, DC_GCL_FOR_FORWARDING, None, None).await?;
+ assert!(chats.len() == 1);
+
+ tcm.section("Bob reinstalls DC");
+ drop(bob);
+ let bob_new = tcm.unconfigured().await;
+ enable_verified_oneonone_chats(&[&bob_new]).await;
+ bob_new.configure_addr("bob@example.net").await;
+ e2ee::ensure_secret_key_exists(&bob_new).await?;
+
+ tcm.send_recv(&bob_new, &alice, "I have a new device").await;
+
+ let contact = alice.add_or_lookup_contact(&bob_new).await;
+ assert_eq!(
+ contact.is_verified(&alice).await.unwrap(),
+ // Bob sent a message with a new key, so he most likely doesn't have
+ // the old key anymore. This means that Alice's device should show
+ // him as unverified:
+ VerifiedStatus::Unverified
+ );
+ let chat = alice.get_chat(&bob_new).await;
+ assert_eq!(chat.is_protected(), false);
+ assert_eq!(chat.is_protection_broken(), true);
+ let chats = Chatlist::try_load(&alice, DC_GCL_FOR_FORWARDING, None, None).await?;
+ assert!(chats.len() == 1);
+
+ {
+ let alice_bob_chat = alice.get_chat(&bob_new).await;
+ assert!(!alice_bob_chat.can_send(&alice).await?);
+
+ // Alice's UI should still be able to save a draft, which Alice started to type right when she got Bob's message:
+ let mut msg = Message::new(Viewtype::Text);
+ msg.set_text("Draftttt".to_string());
+ alice_bob_chat.id.set_draft(&alice, Some(&mut msg)).await?;
+ assert_eq!(
+ alice_bob_chat.id.get_draft(&alice).await?.unwrap().text,
+ "Draftttt"
+ );
+ }
+
+ tcm.execute_securejoin(&alice, &bob_new).await;
+ assert_verified(&alice, &bob_new, ProtectionStatus::Protected).await;
+
+ Ok(())
+}
+
+/// Regression test:
+/// - Verify a contact
+/// - The contact stops using DC and sends a message from a classical MUA instead
+/// - Delete the 1:1 chat
+/// - Create a 1:1 chat
+/// - Check that the created chat is not marked as protected
+#[tokio::test(flavor = "multi_thread", worker_threads = 2)]
+async fn test_create_oneonone_chat_with_former_verified_contact() -> Result<()> {
+ let mut tcm = TestContextManager::new();
+ let alice = tcm.alice().await;
+ let bob = tcm.bob().await;
+ enable_verified_oneonone_chats(&[&alice]).await;
+
+ mark_as_verified(&alice, &bob).await;
+
+ receive_imf(
+ &alice,
+ b"Subject: Message from bob\r\n\
+ From: \r\n\
+ To: \r\n\
+ Date: Mon, 12 Dec 2022 14:33:39 +0000\r\n\
+ Message-ID: \r\n\
+ \r\n\
+ Heyho!\r\n",
+ false,
+ )
+ .await
+ .unwrap()
+ .unwrap();
+
+ alice.create_chat(&bob).await;
+
+ assert_verified(&alice, &bob, ProtectionStatus::Unprotected).await;
+
+ Ok(())
+}
+
+// ============== Helper Functions ==============
+
+async fn assert_verified(this: &TestContext, other: &TestContext, protected: ProtectionStatus) {
+ let contact = this.add_or_lookup_contact(other).await;
+ assert_eq!(
+ contact.is_verified(this).await.unwrap(),
+ VerifiedStatus::BidirectVerified
+ );
+
+ let chat = this.get_chat(other).await;
+ let (expect_protected, expect_broken) = match protected {
+ ProtectionStatus::Unprotected => (false, false),
+ ProtectionStatus::Protected => (true, false),
+ ProtectionStatus::ProtectionBroken => (false, true),
+ };
+ assert_eq!(chat.is_protected(), expect_protected);
+ assert_eq!(chat.is_protection_broken(), expect_broken);
+}
+
+async fn enable_verified_oneonone_chats(test_contexts: &[&TestContext]) {
+ for t in test_contexts {
+ t.set_config_bool(Config::VerifiedOneOnOneChats, true)
+ .await
+ .unwrap()
+ }
+}
diff --git a/src/webxdc.rs b/src/webxdc.rs
index 3341446e4..107ae1680 100644
--- a/src/webxdc.rs
+++ b/src/webxdc.rs
@@ -26,11 +26,12 @@ use serde::{Deserialize, Serialize};
use serde_json::Value;
use tokio::io::AsyncReadExt;
-use crate::chat::Chat;
+use crate::chat::{self, Chat};
use crate::constants::Chattype;
use crate::contact::ContactId;
use crate::context::Context;
use crate::download::DownloadState;
+use crate::events::EventType;
use crate::message::{Message, MessageState, MsgId, Viewtype};
use crate::mimefactory::wrapped_base64_encode;
use crate::mimeparser::SystemMessage;
@@ -39,7 +40,6 @@ use crate::param::Params;
use crate::scheduler::InterruptInfo;
use crate::tools::strip_rtlo_characters;
use crate::tools::{create_smeared_timestamp, get_abs_path};
-use crate::{chat, EventType};
/// The current API version.
/// If `min_api` in manifest.toml is set to a larger value,
diff --git a/test-data/golden/test_old_message_1 b/test-data/golden/test_old_message_1
new file mode 100644
index 000000000..61e54d298
--- /dev/null
+++ b/test-data/golden/test_old_message_1
@@ -0,0 +1,6 @@
+Single#Chat#10: Bob [bob@example.net]
+--------------------------------------------------------------------------------
+Msg#10: info (Contact#Contact#Info): Messages are guaranteed to be end-to-end encrypted from now on. [NOTICED][INFO π‘οΈ]
+Msg#11: info (Contact#Contact#Info): Bob sent a message from another device. [NOTICED][INFO π‘οΈβ]
+Msg#12: (Contact#Contact#10): Message from Thunderbird [SEEN]
+--------------------------------------------------------------------------------
diff --git a/test-data/golden/test_old_message_2 b/test-data/golden/test_old_message_2
new file mode 100644
index 000000000..d7e7712ab
--- /dev/null
+++ b/test-data/golden/test_old_message_2
@@ -0,0 +1,7 @@
+Single#Chat#10: Bob [bob@example.net]
+--------------------------------------------------------------------------------
+Msg#10: info (Contact#Contact#Info): Messages are guaranteed to be end-to-end encrypted from now on. [NOTICED][INFO π‘οΈ]
+Msg#11: info (Contact#Contact#Info): Bob sent a message from another device. [NOTICED][INFO π‘οΈβ]
+Msg#12: (Contact#Contact#10): Somewhat old message [FRESH]
+Msg#13: (Contact#Contact#10): Even older message, that must NOT be shown before the info message [SEEN]
+--------------------------------------------------------------------------------
diff --git a/test-data/golden/test_old_message_3 b/test-data/golden/test_old_message_3
new file mode 100644
index 000000000..30d5ed041
--- /dev/null
+++ b/test-data/golden/test_old_message_3
@@ -0,0 +1,7 @@
+Single#Chat#10: Bob [bob@example.net]
+--------------------------------------------------------------------------------
+Msg#10: info (Contact#Contact#Info): Messages are guaranteed to be end-to-end encrypted from now on. [NOTICED][INFO π‘οΈ]
+Msg#11π: (Contact#Contact#10): Heyho from my verified device! [FRESH]
+Msg#12: info (Contact#Contact#Info): Bob sent a message from another device. [NOTICED][INFO π‘οΈβ]
+Msg#13: (Contact#Contact#10): Old, unverified message [SEEN]
+--------------------------------------------------------------------------------
diff --git a/test-data/golden/test_old_message_5 b/test-data/golden/test_old_message_5
new file mode 100644
index 000000000..75d3c0af0
--- /dev/null
+++ b/test-data/golden/test_old_message_5
@@ -0,0 +1,5 @@
+Single#Chat#10: Bob [bob@example.net]
+--------------------------------------------------------------------------------
+Msg#10: Me (Contact#Contact#Self): Happy birthday, Bob! β
+Msg#11: (Contact#Contact#10): Happy birthday to me, Alice! [FRESH]
+--------------------------------------------------------------------------------
diff --git a/test-data/golden/test_outgoing_mua_msg b/test-data/golden/test_outgoing_mua_msg
new file mode 100644
index 000000000..549f84175
--- /dev/null
+++ b/test-data/golden/test_outgoing_mua_msg
@@ -0,0 +1,7 @@
+Single#Chat#10: bob@example.net [bob@example.net] π‘οΈ
+--------------------------------------------------------------------------------
+Msg#10: info (Contact#Contact#Info): Messages are guaranteed to be end-to-end encrypted from now on. [NOTICED][INFO π‘οΈ]
+Msg#11π: (Contact#Contact#10): Heyho from DC [FRESH]
+Msg#12: Me (Contact#Contact#Self): One classical MUA message β
+Msg#13π: Me (Contact#Contact#Self): Sending with DC again β
+--------------------------------------------------------------------------------
diff --git a/test-data/golden/test_verified_oneonone_chat_enable_disable b/test-data/golden/test_verified_oneonone_chat_enable_disable
new file mode 100644
index 000000000..4da4a67d0
--- /dev/null
+++ b/test-data/golden/test_verified_oneonone_chat_enable_disable
@@ -0,0 +1,12 @@
+Single#Chat#10: Bob [bob@example.net] π‘οΈ
+--------------------------------------------------------------------------------
+Msg#10: info (Contact#Contact#Info): Messages are guaranteed to be end-to-end encrypted from now on. [NOTICED][INFO π‘οΈ]
+Msg#11: info (Contact#Contact#Info): Bob sent a message from another device. [NOTICED][INFO π‘οΈβ]
+Msg#12: (Contact#Contact#10): Message from Thunderbird [FRESH]
+Msg#13: info (Contact#Contact#Info): Messages are guaranteed to be end-to-end encrypted from now on. [NOTICED][INFO π‘οΈ]
+Msg#14π: (Contact#Contact#10): Hello from DC [FRESH]
+Msg#15: info (Contact#Contact#Info): Bob sent a message from another device. [NOTICED][INFO π‘οΈβ]
+Msg#16: (Contact#Contact#10): Message from Thunderbird [FRESH]
+Msg#17: info (Contact#Contact#Info): Messages are guaranteed to be end-to-end encrypted from now on. [NOTICED][INFO π‘οΈ]
+Msg#18π: (Contact#Contact#10): Hello from DC [FRESH]
+--------------------------------------------------------------------------------