mirror of
https://github.com/chatmail/core.git
synced 2026-04-06 15:42:10 +03:00
Compare commits
19 Commits
v1.126.1
...
adb/fix-so
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
b26dccd174 | ||
|
|
3a63628f1f | ||
|
|
3705616cd9 | ||
|
|
b8fcb660ad | ||
|
|
5673294623 | ||
|
|
7062bb0502 | ||
|
|
659cffe0cc | ||
|
|
a1663a98e0 | ||
|
|
3de1dbc9e4 | ||
|
|
6d37e8601e | ||
|
|
d762753103 | ||
|
|
a020d5ccce | ||
|
|
1e28ea9bb0 | ||
|
|
17f2d33731 | ||
|
|
976797d4cf | ||
|
|
31e3169433 | ||
|
|
d2b15cb629 | ||
|
|
9cd000c4f2 | ||
|
|
243c035b03 |
16
.github/workflows/ci.yml
vendored
16
.github/workflows/ci.yml
vendored
@@ -24,7 +24,7 @@ jobs:
|
||||
name: Lint Rust
|
||||
runs-on: ubuntu-latest
|
||||
env:
|
||||
RUSTUP_TOOLCHAIN: 1.70.0
|
||||
RUSTUP_TOOLCHAIN: 1.71.0
|
||||
steps:
|
||||
- uses: actions/checkout@v3
|
||||
- name: Install rustfmt and clippy
|
||||
@@ -80,19 +80,15 @@ jobs:
|
||||
matrix:
|
||||
include:
|
||||
- os: ubuntu-latest
|
||||
rust: 1.68.2
|
||||
rust: 1.71.0
|
||||
- os: windows-latest
|
||||
rust: 1.68.2
|
||||
rust: 1.71.0
|
||||
- os: macos-latest
|
||||
rust: 1.68.2
|
||||
rust: 1.71.0
|
||||
|
||||
# Minimum Supported Rust Version = 1.65.0
|
||||
#
|
||||
# Minimum Supported Python Version = 3.7
|
||||
# This is the minimum version for which manylinux Python wheels are
|
||||
# built.
|
||||
# Minimum Supported Rust Version = 1.66.0
|
||||
- os: ubuntu-latest
|
||||
rust: 1.65.0
|
||||
rust: 1.66.0
|
||||
runs-on: ${{ matrix.os }}
|
||||
steps:
|
||||
- uses: actions/checkout@v3
|
||||
|
||||
@@ -1,5 +1,11 @@
|
||||
# Changelog
|
||||
|
||||
## Unreleased
|
||||
|
||||
### Fixes
|
||||
|
||||
- deltachat-rpc-client: fix bug in client.py and add missing `EventType.MSG_DELETED`
|
||||
|
||||
## [1.118.0] - 2023-07-07
|
||||
|
||||
### API-Changes
|
||||
|
||||
@@ -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.
|
||||
|
||||
287
Cargo.lock
generated
287
Cargo.lock
generated
@@ -89,9 +89,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"
|
||||
@@ -131,9 +131,9 @@ checksum = "3a30da5c5f2d5e72842e00bcb57657162cdabef0931f40e2deb9b4140440cecd"
|
||||
|
||||
[[package]]
|
||||
name = "anyhow"
|
||||
version = "1.0.71"
|
||||
version = "1.0.72"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "9c7d0618f0e0b7e8ff11427422b64564d5fb0be1940354bfe2e0529b18a9d9b8"
|
||||
checksum = "3b13c32d80ecc7ab747b80c3784bce54ee8a7a0cc4fbda9bf4cda2cf6fe90854"
|
||||
dependencies = [
|
||||
"backtrace",
|
||||
]
|
||||
@@ -169,7 +169,7 @@ dependencies = [
|
||||
"num-traits",
|
||||
"rusticata-macros",
|
||||
"thiserror",
|
||||
"time 0.3.22",
|
||||
"time 0.3.23",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -197,9 +197,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",
|
||||
@@ -281,13 +281,13 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "async-trait"
|
||||
version = "0.1.70"
|
||||
version = "0.1.71"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "79fa67157abdfd688a259b6648808757db9347af834624f27ec646da976aee5d"
|
||||
checksum = "a564d521dd56509c4c47480d00b80ee55f7e385ae48db5744c67ad50c92d2ebf"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn 2.0.23",
|
||||
"syn 2.0.26",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -312,9 +312,9 @@ checksum = "d468802bab17cbc0cc575e9b053f41e72aa36bfa6b7f55e3529ffa43161b97fa"
|
||||
|
||||
[[package]]
|
||||
name = "axum"
|
||||
version = "0.6.18"
|
||||
version = "0.6.19"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "f8175979259124331c1d7bf6586ee7e0da434155e4b2d48ec2c8386281d8df39"
|
||||
checksum = "a6a1de45611fdb535bfde7b7de4fd54f4fd2b17b1737c0a59b69bf9b92074b8c"
|
||||
dependencies = [
|
||||
"async-trait",
|
||||
"axum-core",
|
||||
@@ -448,9 +448,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",
|
||||
@@ -520,9 +520,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",
|
||||
@@ -584,18 +584,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",
|
||||
]
|
||||
@@ -713,18 +713,18 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "clap"
|
||||
version = "4.3.10"
|
||||
version = "4.3.16"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "384e169cc618c613d5e3ca6404dda77a8685a63e08660dcc64abaf7da7cb0c7a"
|
||||
checksum = "74bb1b4028935821b2d6b439bba2e970bdcf740832732437ead910c632e30d7d"
|
||||
dependencies = [
|
||||
"clap_builder",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "clap_builder"
|
||||
version = "4.3.10"
|
||||
version = "4.3.16"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "ef137bbe35aab78bdb468ccfba75a5f4d8321ae011d34063770780545176af2d"
|
||||
checksum = "5ae467cbb0111869b765e13882a1dbbd6cb52f58203d8b80c44f667d4dd19843"
|
||||
dependencies = [
|
||||
"anstyle",
|
||||
"clap_lex",
|
||||
@@ -770,9 +770,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"
|
||||
@@ -796,9 +796,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"
|
||||
@@ -830,9 +830,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",
|
||||
]
|
||||
@@ -977,16 +977,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"
|
||||
@@ -1025,7 +1015,7 @@ checksum = "83fdaf97f4804dcebfa5862639bc9ce4121e82140bec2a987ac5140294865b5b"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn 2.0.23",
|
||||
"syn 2.0.26",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -1258,7 +1248,7 @@ name = "deltachat_derive"
|
||||
version = "2.0.0"
|
||||
dependencies = [
|
||||
"quote",
|
||||
"syn 2.0.23",
|
||||
"syn 2.0.26",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -1458,7 +1448,7 @@ checksum = "487585f4d0c6655fe74905e2504d8ad6908e4db67f744eb140876906c2f3175d"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn 2.0.23",
|
||||
"syn 2.0.26",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -1486,9 +1476,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"
|
||||
@@ -1617,7 +1607,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",
|
||||
]
|
||||
@@ -1758,7 +1748,7 @@ dependencies = [
|
||||
"num-traits",
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn 2.0.23",
|
||||
"syn 2.0.26",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -1776,9 +1766,9 @@ 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"
|
||||
@@ -1876,7 +1866,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "ef033ed5e9bad94e55838ca0ca906db0e043f517adda0c8b79c7a8c66c93c1b5"
|
||||
dependencies = [
|
||||
"cfg-if",
|
||||
"rustix 0.38.2",
|
||||
"rustix 0.38.4",
|
||||
"windows-sys 0.48.0",
|
||||
]
|
||||
|
||||
@@ -2055,7 +2045,7 @@ checksum = "89ca545a94061b6365f2c7355b4b32bd20df3ff95f02da9329b34ccc3bd6ee72"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn 2.0.23",
|
||||
"syn 2.0.26",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -2567,12 +2557,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 0.38.4",
|
||||
"windows-sys 0.48.0",
|
||||
]
|
||||
|
||||
@@ -2587,9 +2577,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"
|
||||
@@ -3005,7 +2995,7 @@ checksum = "9e6a0fd4f737c707bd9086cc16c925f294943eb62eb71499e9fd4cf71f8b9f4e"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn 2.0.23",
|
||||
"syn 2.0.26",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -3119,7 +3109,7 @@ checksum = "a948666b637a0f465e8564c73e89d4dde00d72d4d473cc972f390fc3dcee7d9c"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn 2.0.23",
|
||||
"syn 2.0.26",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -3167,15 +3157,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"
|
||||
@@ -3259,9 +3240,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"
|
||||
@@ -3364,7 +3345,7 @@ checksum = "ec2e072ecce94ec471b13398d5402c188e76ac03cf74dd1a975161b23a3f6d9c"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn 2.0.23",
|
||||
"syn 2.0.26",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -3477,15 +3458,15 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "portable-atomic"
|
||||
version = "1.3.3"
|
||||
version = "1.4.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "767eb9f07d4a5ebcb39bbf2d452058a93c011373abf6832e24194a1c3f004794"
|
||||
checksum = "edc55135a600d700580e406b4de0d59cb9ad25e344a3a091a97ded2622ec4ec6"
|
||||
|
||||
[[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",
|
||||
@@ -3512,13 +3493,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",
|
||||
]
|
||||
|
||||
@@ -3567,9 +3546,9 @@ 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",
|
||||
]
|
||||
@@ -3680,9 +3659,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "quote"
|
||||
version = "1.0.29"
|
||||
version = "1.0.31"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "573015e8ab27661678357f27dc26460738fd2b6c86e46f386fde94cb5d913105"
|
||||
checksum = "5fe8a65d69dd0808184ebb5f836ab526bb259db23c657efa38711b1072ee47f0"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
]
|
||||
@@ -3817,7 +3796,7 @@ checksum = "ffbe84efe2f38dea12e9bfc1f65377fdf03e53a18cb3b995faedf7934c7e785b"
|
||||
dependencies = [
|
||||
"pem",
|
||||
"ring",
|
||||
"time 0.3.22",
|
||||
"time 0.3.23",
|
||||
"yasna",
|
||||
]
|
||||
|
||||
@@ -3858,7 +3837,7 @@ checksum = "d0ab3ca65655bb1e41f2a8c8cd662eb4fb035e67c3f78da1d61dffe89d07300f"
|
||||
dependencies = [
|
||||
"aho-corasick",
|
||||
"memchr",
|
||||
"regex-syntax 0.7.2",
|
||||
"regex-syntax 0.7.4",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -3878,9 +3857,9 @@ checksum = "f162c6dd7b008981e4d40210aca20b4bd0f9b60ca9271061b07f78537722f2e1"
|
||||
|
||||
[[package]]
|
||||
name = "regex-syntax"
|
||||
version = "0.7.2"
|
||||
version = "0.7.4"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "436b050e76ed2903236f032a59761c1eb99e1b0aead2c257922771dab1fc8c78"
|
||||
checksum = "e5ea92a5b6195c6ef2a0295ea818b312502c6fc94dde986c5553242e18fd4ce2"
|
||||
|
||||
[[package]]
|
||||
name = "reqwest"
|
||||
@@ -4068,9 +4047,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "rustix"
|
||||
version = "0.37.22"
|
||||
version = "0.37.23"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "8818fa822adcc98b18fedbb3632a6a33213c070556b5aa7c4c8cc21cff565c4c"
|
||||
checksum = "4d69718bf81c6127a49dc64e44a742e8bb9213c0ff8869a22c308f84c1d4ab06"
|
||||
dependencies = [
|
||||
"bitflags 1.3.2",
|
||||
"errno",
|
||||
@@ -4082,9 +4061,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "rustix"
|
||||
version = "0.38.2"
|
||||
version = "0.38.4"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "aabcb0461ebd01d6b79945797c27f8529082226cb630a9865a71870ff63532a4"
|
||||
checksum = "0a962918ea88d644592894bc6dc55acc6c0956488adcebbfb6e273506b7fd6e5"
|
||||
dependencies = [
|
||||
"bitflags 2.3.3",
|
||||
"errno",
|
||||
@@ -4127,9 +4106,9 @@ dependencies = [
|
||||
|
||||
[[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"
|
||||
@@ -4156,9 +4135,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"
|
||||
@@ -4220,9 +4199,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"
|
||||
@@ -4250,9 +4229,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",
|
||||
@@ -4293,18 +4272,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.171"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "d01b7404f9d441d3ad40e6a636a7782c377d2abdbe4fa2440e2edcc2f4f10db8"
|
||||
checksum = "30e27d1e4fd7659406c492fd6cfaf2066ba8773de45ca75e855590f856dc34a9"
|
||||
dependencies = [
|
||||
"serde_derive",
|
||||
]
|
||||
@@ -4320,22 +4299,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.171"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "5dd83d6dde2b6b2d466e14d9d1acce8816dedee94f735eac6395808b3483c6d6"
|
||||
checksum = "389894603bd18c46fa56231694f8d827779c0951a667087194cf9de94ed24682"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn 2.0.23",
|
||||
"syn 2.0.26",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -4351,9 +4330,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "serde_json"
|
||||
version = "1.0.100"
|
||||
version = "1.0.103"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "0f1e14e89be7aa4c4b78bdbdc9eb5bf8517829a600ae8eaa39a6e1d960b5185c"
|
||||
checksum = "d03b412469450d4404fe8499a268edd7f8b79fecb074b0d812ad64ca21f4031b"
|
||||
dependencies = [
|
||||
"itoa",
|
||||
"ryu",
|
||||
@@ -4362,9 +4341,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",
|
||||
@@ -4502,9 +4481,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "smallvec"
|
||||
version = "1.10.0"
|
||||
version = "1.11.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "a507befe795404456341dfab10cef66ead4c041f62b8b11bbb92bffe5d0953e0"
|
||||
checksum = "62bb4feee49fdd9f707ef802e22365a35de4b7b299de4763d44bfea899442ff9"
|
||||
|
||||
[[package]]
|
||||
name = "smawk"
|
||||
@@ -4648,7 +4627,7 @@ dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"rustversion",
|
||||
"syn 2.0.23",
|
||||
"syn 2.0.26",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -4670,9 +4649,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "syn"
|
||||
version = "2.0.23"
|
||||
version = "2.0.26"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "59fb7d6d8281a51045d62b8eb3a7d1ce347b76f312af50cd3dc0af39c87c1737"
|
||||
checksum = "45c3457aacde3c65315de5031ec191ce46604304d2446e803d71ade03308d970"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
@@ -4734,9 +4713,9 @@ 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"
|
||||
@@ -4748,7 +4727,7 @@ dependencies = [
|
||||
"cfg-if",
|
||||
"fastrand",
|
||||
"redox_syscall 0.3.5",
|
||||
"rustix 0.37.22",
|
||||
"rustix 0.37.23",
|
||||
"windows-sys 0.48.0",
|
||||
]
|
||||
|
||||
@@ -4788,22 +4767,22 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "thiserror"
|
||||
version = "1.0.41"
|
||||
version = "1.0.43"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "c16a64ba9387ef3fdae4f9c1a7f07a0997fce91985c0336f1ddc1822b3b37802"
|
||||
checksum = "a35fc5b8971143ca348fa6df4f024d4d55264f3468c71ad1c2f365b0a4d58c42"
|
||||
dependencies = [
|
||||
"thiserror-impl",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "thiserror-impl"
|
||||
version = "1.0.41"
|
||||
version = "1.0.43"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "d14928354b01c4d6a4f0e549069adef399a284e7995c7ccca94e8a07a5346c59"
|
||||
checksum = "463fe12d7993d3b327787537ce8dd4dfa058de32fc2b195ef3cde03dc4771e8f"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn 2.0.23",
|
||||
"syn 2.0.26",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -4829,9 +4808,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "time"
|
||||
version = "0.3.22"
|
||||
version = "0.3.23"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "ea9e1b3cf1243ae005d9e74085d4d542f3125458f3a81af210d901dcd7411efd"
|
||||
checksum = "59e399c068f43a5d116fedaf73b203fa4f9c519f17e2b34f63221d3792f81446"
|
||||
dependencies = [
|
||||
"itoa",
|
||||
"serde",
|
||||
@@ -4847,9 +4826,9 @@ checksum = "7300fbefb4dadc1af235a9cef3737cea692a9d97e1b9cbcd4ebdae6f8868e6fb"
|
||||
|
||||
[[package]]
|
||||
name = "time-macros"
|
||||
version = "0.2.9"
|
||||
version = "0.2.10"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "372950940a5f07bf38dbe211d7283c9e6d7327df53794992d293e534c733d09b"
|
||||
checksum = "96ba15a897f3c86766b757e5ac7221554c6750054d74d5b28844fce5fb36a6c4"
|
||||
dependencies = [
|
||||
"time-core",
|
||||
]
|
||||
@@ -4917,7 +4896,7 @@ checksum = "630bdcf245f78637c13ec01ffae6187cca34625e8c63150d424b59e55af2675e"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn 2.0.23",
|
||||
"syn 2.0.26",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -4973,9 +4952,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "tokio-tungstenite"
|
||||
version = "0.18.0"
|
||||
version = "0.19.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "54319c93411147bced34cb5609a80e0a8e44c5999c93903a81cd866630ec0bfd"
|
||||
checksum = "ec509ac96e9a0c43427c74f003127d953a265737636129424288d27cb5c4b12c"
|
||||
dependencies = [
|
||||
"futures-util",
|
||||
"log",
|
||||
@@ -4999,9 +4978,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",
|
||||
@@ -5020,9 +4999,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",
|
||||
@@ -5080,7 +5059,7 @@ checksum = "5f4f31f56159e98206da9efd823404b79b6ef3143b4a7ab76e67b1751b25a4ab"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn 2.0.23",
|
||||
"syn 2.0.26",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -5185,13 +5164,13 @@ checksum = "3528ecfd12c466c6f163363caf2d02a71161dd5e1cc6ae7b34207ea2d42d81ed"
|
||||
|
||||
[[package]]
|
||||
name = "tungstenite"
|
||||
version = "0.18.0"
|
||||
version = "0.19.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "30ee6ab729cd4cf0fd55218530c4522ed30b7b6081752839b68fcec8d0960788"
|
||||
checksum = "15fba1a6d6bb030745759a9a2a588bfe8490fc8b4751a277db3a0be1c9ebbf67"
|
||||
dependencies = [
|
||||
"base64 0.13.1",
|
||||
"byteorder",
|
||||
"bytes",
|
||||
"data-encoding",
|
||||
"http",
|
||||
"httparse",
|
||||
"log",
|
||||
@@ -5219,9 +5198,9 @@ checksum = "497961ef93d974e23eb6f433eb5fe1b7930b659f06d12dec6fc44a8f554c0bba"
|
||||
|
||||
[[package]]
|
||||
name = "typescript-type-def"
|
||||
version = "0.5.6"
|
||||
version = "0.5.7"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "4e6b74ffbd5684d318252bb7182051df8c4ecc098b542f63fddf792e7f42aa02"
|
||||
checksum = "3c4be05eb0171e18da471e545cfff85d80b8118e5aa2d50c2d152265d7435997"
|
||||
dependencies = [
|
||||
"serde_json",
|
||||
"typescript-type-def-derive",
|
||||
@@ -5229,9 +5208,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "typescript-type-def-derive"
|
||||
version = "0.5.6"
|
||||
version = "0.5.7"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "b10a4f5dd87c279f90beef31edb7055bfd1ceb66e73148de107a5c9005e9f864"
|
||||
checksum = "71aeb2295da6c2bfe665493332b4e4281d113fab3a0786e9651dcc85708095a4"
|
||||
dependencies = [
|
||||
"darling 0.13.4",
|
||||
"ident_case",
|
||||
@@ -5255,9 +5234,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"
|
||||
@@ -5327,9 +5306,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",
|
||||
@@ -5417,7 +5396,7 @@ dependencies = [
|
||||
"once_cell",
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn 2.0.23",
|
||||
"syn 2.0.26",
|
||||
"wasm-bindgen-shared",
|
||||
]
|
||||
|
||||
@@ -5451,7 +5430,7 @@ checksum = "54681b18a46765f095758388f2d0cf16eb8d4169b639ab575a8f5693af210c7b"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn 2.0.23",
|
||||
"syn 2.0.26",
|
||||
"wasm-bindgen-backend",
|
||||
"wasm-bindgen-shared",
|
||||
]
|
||||
@@ -5721,9 +5700,9 @@ checksum = "1a515f5799fe4961cb532f983ce2b23082366b898e52ffbce459c86f67c8378a"
|
||||
|
||||
[[package]]
|
||||
name = "winnow"
|
||||
version = "0.4.7"
|
||||
version = "0.5.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "ca0ace3845f0d96209f0375e6d367e3eb87eb65d27d445bdc9f1843a26f39448"
|
||||
checksum = "81fac9742fd1ad1bd9643b991319f72dd031016d44b77039a26977eb667141e7"
|
||||
dependencies = [
|
||||
"memchr",
|
||||
]
|
||||
@@ -5773,7 +5752,7 @@ dependencies = [
|
||||
"oid-registry",
|
||||
"rusticata-macros",
|
||||
"thiserror",
|
||||
"time 0.3.22",
|
||||
"time 0.3.23",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -5797,7 +5776,7 @@ version = "0.5.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "e17bb3549cc1321ae1296b9cdc2698e2b6cb1992adfa19a8c72e5b7a738f44cd"
|
||||
dependencies = [
|
||||
"time 0.3.22",
|
||||
"time 0.3.23",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -5853,5 +5832,5 @@ checksum = "ce36e65b0d2999d2aafac989fb249189a141aee1f53c612c1f37d72631959f69"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn 2.0.23",
|
||||
"syn 2.0.26",
|
||||
]
|
||||
|
||||
@@ -3,7 +3,7 @@ name = "deltachat"
|
||||
version = "1.118.0"
|
||||
edition = "2021"
|
||||
license = "MPL-2.0"
|
||||
rust-version = "1.65"
|
||||
rust-version = "1.66"
|
||||
|
||||
[profile.dev]
|
||||
debug = 0
|
||||
@@ -118,11 +118,6 @@ members = [
|
||||
"format-flowed",
|
||||
]
|
||||
|
||||
[[example]]
|
||||
name = "simple"
|
||||
path = "examples/simple.rs"
|
||||
|
||||
|
||||
[[bench]]
|
||||
name = "create_account"
|
||||
harness = false
|
||||
|
||||
@@ -485,6 +485,13 @@ char* dc_get_blobdir (const dc_context_t* context);
|
||||
* to not mess up with non-delivery-reports or read-receipts.
|
||||
* 0=no limit (default).
|
||||
* Changes affect future messages only.
|
||||
* - `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`.
|
||||
@@ -1470,24 +1477,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.
|
||||
*
|
||||
@@ -3712,7 +3701,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.
|
||||
@@ -3721,6 +3709,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().
|
||||
@@ -4315,7 +4323,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,
|
||||
@@ -5009,7 +5017,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.
|
||||
@@ -6749,15 +6762,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.
|
||||
@@ -7202,26 +7206,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.
|
||||
@@ -7232,6 +7216,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
|
||||
|
||||
/**
|
||||
* @}
|
||||
*/
|
||||
|
||||
@@ -1429,32 +1429,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,
|
||||
@@ -3084,6 +3058,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() {
|
||||
|
||||
@@ -1709,6 +1709,20 @@ impl CommandApi {
|
||||
forward_msgs(&ctx, &message_ids, ChatId::new(chat_id)).await
|
||||
}
|
||||
|
||||
/// Resend messages and make information available for newly added chat members.
|
||||
/// Resending sends out the original message, however, recipients and webxdc-status may differ.
|
||||
/// Clients that already have the original message can still ignore the resent message as
|
||||
/// they have tracked the state by dedicated updates.
|
||||
///
|
||||
/// Some messages cannot be resent, eg. info-messages, drafts, already pending messages or messages that are not sent by SELF.
|
||||
///
|
||||
/// message_ids all message IDs that should be resend. All messages must belong to the same chat.
|
||||
async fn resend_messages(&self, account_id: u32, message_ids: Vec<u32>) -> Result<()> {
|
||||
let ctx = self.get_context(account_id).await?;
|
||||
let message_ids: Vec<MsgId> = message_ids.into_iter().map(MsgId::new).collect();
|
||||
chat::resend_msgs(&ctx, &message_ids).await
|
||||
}
|
||||
|
||||
async fn send_sticker(
|
||||
&self,
|
||||
account_id: u32,
|
||||
|
||||
@@ -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<str>, 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 <chat-id>\n\
|
||||
mute <chat-id> [<seconds>]\n\
|
||||
unmute <chat-id>\n\
|
||||
protect <chat-id>\n\
|
||||
unprotect <chat-id>\n\
|
||||
delchat <chat-id>\n\
|
||||
accept <chat-id>\n\
|
||||
decline <chat-id>\n\
|
||||
@@ -1056,20 +1065,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 <chat-id> 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 <chat-id> missing.");
|
||||
let chat_id = ChatId::new(arg1.parse()?);
|
||||
|
||||
@@ -155,7 +155,7 @@ class Client:
|
||||
|
||||
async def _on_new_msg(self, snapshot: AttrDict) -> None:
|
||||
event = AttrDict(command="", payload="", message_snapshot=snapshot)
|
||||
if not snapshot.is_info and snapshot.text.startswith(COMMAND_PREFIX):
|
||||
if not snapshot.is_info and (snapshot.text or "").startswith(COMMAND_PREFIX):
|
||||
await self._parse_command(event)
|
||||
await self._on_event(event, NewMessage)
|
||||
|
||||
|
||||
@@ -45,6 +45,7 @@ class EventType(str, Enum):
|
||||
MSG_DELIVERED = "MsgDelivered"
|
||||
MSG_FAILED = "MsgFailed"
|
||||
MSG_READ = "MsgRead"
|
||||
MSG_DELETED = "MsgDeleted"
|
||||
CHAT_MODIFIED = "ChatModified"
|
||||
CHAT_EPHEMERAL_TIMER_MODIFIED = "ChatEphemeralTimerModified"
|
||||
CONTACTS_CHANGED = "ContactsChanged"
|
||||
|
||||
@@ -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::<Vec<String>>();
|
||||
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();
|
||||
}
|
||||
@@ -158,6 +158,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,
|
||||
@@ -243,12 +245,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,
|
||||
|
||||
@@ -158,6 +158,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,
|
||||
@@ -243,12 +245,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,
|
||||
|
||||
@@ -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(
|
||||
|
||||
@@ -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();
|
||||
@@ -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);
|
||||
|
||||
@@ -9,7 +9,7 @@ set -e
|
||||
unset RUSTFLAGS
|
||||
|
||||
# Pin Rust version to avoid uncontrolled changes in the compiler and linker flags.
|
||||
export RUSTUP_TOOLCHAIN=1.70.0
|
||||
export RUSTUP_TOOLCHAIN=1.71.0
|
||||
|
||||
ZIG_VERSION=0.11.0-dev.2213+515e1c93e
|
||||
|
||||
|
||||
@@ -8,7 +8,7 @@ set -e
|
||||
unset RUSTFLAGS
|
||||
|
||||
# Pin Rust version to avoid uncontrolled changes in the compiler and linker flags.
|
||||
export RUSTUP_TOOLCHAIN=1.70.0
|
||||
export RUSTUP_TOOLCHAIN=1.71.0
|
||||
|
||||
ZIG_VERSION=0.11.0-dev.2213+515e1c93e
|
||||
|
||||
|
||||
282
src/chat.rs
282
src/chat.rs
@@ -86,6 +86,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.
|
||||
@@ -102,6 +110,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,
|
||||
|
||||
@@ -118,6 +130,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")
|
||||
}
|
||||
@@ -270,6 +286,7 @@ impl ChatId {
|
||||
param: Option<String>,
|
||||
) -> Result<Self> {
|
||||
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(?, ?, ?, ?, ?, ?, ?);",
|
||||
@@ -278,13 +295,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={}.",
|
||||
@@ -374,6 +398,13 @@ impl ChatId {
|
||||
|
||||
match chat.typ {
|
||||
Chattype::Undefined => bail!("Can't accept chat of undefined chattype"),
|
||||
Chattype::Single if chat.protected == ProtectionStatus::ProtectionBroken => {
|
||||
// The chat was in the 'Request' state because the protection was broken.
|
||||
// The user clicked 'Accept', 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.
|
||||
//
|
||||
@@ -400,20 +431,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<bool> {
|
||||
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,7 +460,7 @@ impl ChatId {
|
||||
Chattype::Mailinglist => bail!("Cannot protect mailing lists"),
|
||||
Chattype::Undefined => bail!("Undefined group type"),
|
||||
},
|
||||
ProtectionStatus::Unprotected => {}
|
||||
ProtectionStatus::Unprotected | ProtectionStatus::ProtectionBroken => {}
|
||||
};
|
||||
|
||||
context
|
||||
@@ -443,68 +473,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<ContactId>,
|
||||
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<ContactId>,
|
||||
) -> 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.
|
||||
@@ -741,14 +761,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;
|
||||
@@ -1141,7 +1153,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,
|
||||
@@ -1153,7 +1165,7 @@ pub struct Chat {
|
||||
pub mute_duration: MuteDuration,
|
||||
|
||||
/// If the chat is protected (verified).
|
||||
protected: ProtectionStatus,
|
||||
pub(crate) protected: ProtectionStatus,
|
||||
}
|
||||
|
||||
impl Chat {
|
||||
@@ -1247,6 +1259,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.param.get(Param::ListPost).is_none_or_empty() {
|
||||
Some(ReadOnlyMailingList)
|
||||
} else if !self.is_self_in_chat(context).await? {
|
||||
@@ -1410,6 +1424,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
|
||||
@@ -1440,15 +1475,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 {
|
||||
@@ -1964,19 +1990,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(
|
||||
@@ -1997,6 +2034,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?,
|
||||
@@ -2100,7 +2148,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) ...
|
||||
@@ -2202,7 +2256,7 @@ pub async fn send_msg_sync(context: &Context, chat_id: ChatId, msg: &mut Message
|
||||
}
|
||||
|
||||
async fn send_msg_inner(context: &Context, chat_id: ChatId, msg: &mut Message) -> Result<MsgId> {
|
||||
// protect all system messages againts RTLO attacks
|
||||
// protect all system messages against RTLO attacks
|
||||
if msg.is_system_message() {
|
||||
msg.text = strip_rtlo_characters(&msg.text);
|
||||
}
|
||||
@@ -2850,18 +2904,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?;
|
||||
|
||||
@@ -2873,9 +2923,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)
|
||||
@@ -5131,72 +5181,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;
|
||||
|
||||
@@ -311,6 +311,16 @@ pub enum Config {
|
||||
|
||||
/// Last message processed by the bot.
|
||||
LastMsgId,
|
||||
|
||||
/// 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 {
|
||||
|
||||
@@ -131,7 +131,7 @@ async fn on_configure_completed(
|
||||
) -> Result<()> {
|
||||
if let Some(provider) = param.provider {
|
||||
if let Some(config_defaults) = &provider.config_defaults {
|
||||
for def in config_defaults.iter() {
|
||||
for def in config_defaults {
|
||||
if !context.config_exists(def.key).await? {
|
||||
info!(context, "apply config_defaults {}={}", def.key, def.value);
|
||||
context.set_config(def.key, Some(def.value)).await?;
|
||||
@@ -656,7 +656,7 @@ async fn try_smtp_one_param(
|
||||
})
|
||||
} else {
|
||||
info!(context, "success: {}", inf);
|
||||
smtp.disconnect().await;
|
||||
smtp.disconnect();
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
@@ -234,7 +234,7 @@ fn parse_serverparams(in_emailaddr: &str, xml_raw: &str) -> Result<Vec<ServerPar
|
||||
let res = moz_ac
|
||||
.incoming_servers
|
||||
.into_iter()
|
||||
.chain(moz_ac.outgoing_servers.into_iter())
|
||||
.chain(moz_ac.outgoing_servers)
|
||||
.filter_map(|server| {
|
||||
let protocol = match server.typ.as_ref() {
|
||||
"imap" => Some(Protocol::Imap),
|
||||
|
||||
@@ -755,6 +755,12 @@ impl Context {
|
||||
"last_msg_id",
|
||||
self.get_config_int(Config::LastMsgId).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()));
|
||||
|
||||
@@ -545,7 +545,7 @@ async fn next_expiration_timestamp(context: &Context) -> Option<i64> {
|
||||
|
||||
ephemeral_timestamp
|
||||
.into_iter()
|
||||
.chain(delete_device_after_timestamp.into_iter())
|
||||
.chain(delete_device_after_timestamp)
|
||||
.min()
|
||||
}
|
||||
|
||||
|
||||
16
src/html.rs
16
src/html.rs
@@ -291,7 +291,7 @@ mod tests {
|
||||
let parser = HtmlMsgParser::from_bytes(&t.ctx, raw).await.unwrap();
|
||||
assert_eq!(
|
||||
parser.html,
|
||||
r##"<!DOCTYPE html>
|
||||
r#"<!DOCTYPE html>
|
||||
<html><head>
|
||||
<meta http-equiv="Content-Type" content="text/html; charset=utf-8" />
|
||||
<meta name="color-scheme" content="light dark" />
|
||||
@@ -299,7 +299,7 @@ mod tests {
|
||||
This message does not have Content-Type nor Subject.<br/>
|
||||
<br/>
|
||||
</body></html>
|
||||
"##
|
||||
"#
|
||||
);
|
||||
}
|
||||
|
||||
@@ -310,7 +310,7 @@ This message does not have Content-Type nor Subject.<br/>
|
||||
let parser = HtmlMsgParser::from_bytes(&t.ctx, raw).await.unwrap();
|
||||
assert_eq!(
|
||||
parser.html,
|
||||
r##"<!DOCTYPE html>
|
||||
r#"<!DOCTYPE html>
|
||||
<html><head>
|
||||
<meta http-equiv="Content-Type" content="text/html; charset=utf-8" />
|
||||
<meta name="color-scheme" content="light dark" />
|
||||
@@ -318,7 +318,7 @@ This message does not have Content-Type nor Subject.<br/>
|
||||
message with a non-UTF-8 encoding: äöüßÄÖÜ<br/>
|
||||
<br/>
|
||||
</body></html>
|
||||
"##
|
||||
"#
|
||||
);
|
||||
}
|
||||
|
||||
@@ -330,7 +330,7 @@ message with a non-UTF-8 encoding: äöüßÄÖÜ<br/>
|
||||
assert!(parser.plain.unwrap().flowed);
|
||||
assert_eq!(
|
||||
parser.html,
|
||||
r##"<!DOCTYPE html>
|
||||
r#"<!DOCTYPE html>
|
||||
<html><head>
|
||||
<meta http-equiv="Content-Type" content="text/html; charset=utf-8" />
|
||||
<meta name="color-scheme" content="light dark" />
|
||||
@@ -341,7 +341,7 @@ This line does not end with a space<br/>
|
||||
and will be wrapped as usual.<br/>
|
||||
<br/>
|
||||
</body></html>
|
||||
"##
|
||||
"#
|
||||
);
|
||||
}
|
||||
|
||||
@@ -352,7 +352,7 @@ and will be wrapped as usual.<br/>
|
||||
let parser = HtmlMsgParser::from_bytes(&t.ctx, raw).await.unwrap();
|
||||
assert_eq!(
|
||||
parser.html,
|
||||
r##"<!DOCTYPE html>
|
||||
r#"<!DOCTYPE html>
|
||||
<html><head>
|
||||
<meta http-equiv="Content-Type" content="text/html; charset=utf-8" />
|
||||
<meta name="color-scheme" content="light dark" />
|
||||
@@ -363,7 +363,7 @@ test some special html-characters as < > and & but also " and &#x
|
||||
<br/>
|
||||
<br/>
|
||||
</body></html>
|
||||
"##
|
||||
"#
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -735,7 +735,7 @@ async fn maybe_send_locations(context: &Context) -> Result<Option<u64>> {
|
||||
|
||||
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 {
|
||||
@@ -759,7 +759,7 @@ async fn maybe_send_locations(context: &Context) -> Result<Option<u64>> {
|
||||
);
|
||||
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 {
|
||||
|
||||
@@ -896,7 +896,17 @@ impl<'a> MimeFactory<'a> {
|
||||
let mut placeholdertext = None;
|
||||
let mut meta_part = None;
|
||||
|
||||
if chat.is_protected() {
|
||||
let send_verified_headers = match chat.typ {
|
||||
Chattype::Undefined => bail!("Undefined chat type"),
|
||||
// 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()));
|
||||
|
||||
@@ -1892,7 +1892,7 @@ fn get_mime_type(mail: &mailparse::ParsedMail<'_>) -> Result<(Mime, Viewtype)> {
|
||||
} else {
|
||||
// Enacapsulated messages, see <https://www.w3.org/Protocols/rfc1341/7_3_Message.html>
|
||||
// Also used as part "message/disposition-notification" of "multipart/report", which, however, will
|
||||
// be handled separatedly.
|
||||
// be handled separately.
|
||||
// I've not seen any messages using this, so we do not attach these parts (maybe they're used to attach replies,
|
||||
// which are unwanted at all).
|
||||
// For now, we skip these parts at all; if desired, we could return DcMimeType::File/DC_MSG_File
|
||||
@@ -3090,7 +3090,7 @@ CWt6wx7fiLp0qS9RrX75g6Gqw7nfCs6EcBERcIPt7DTe8VStJwf3LWqVwxl4gQl46yhfoqwEO+I=
|
||||
#[tokio::test(flavor = "multi_thread", worker_threads = 2)]
|
||||
async fn parse_outlook_html_embedded_image() {
|
||||
let context = TestContext::new_alice().await;
|
||||
let raw = br##"From: Anonymous <anonymous@example.org>
|
||||
let raw = br#"From: Anonymous <anonymous@example.org>
|
||||
To: Anonymous <anonymous@example.org>
|
||||
Subject: Delta Chat is great stuff!
|
||||
Date: Tue, 5 May 2020 01:23:45 +0000
|
||||
@@ -3145,7 +3145,7 @@ K+TuvC7qOah0WLFhcsXWn2+dDV1bXuAeC769TkqkpHhdXfUHnVgK3Pv7u3rVPT5AMeFUGxRB2dP4
|
||||
CWt6wx7fiLp0qS9RrX75g6Gqw7nfCs6EcBERcIPt7DTe8VStJwf3LWqVwxl4gQl46yhfoqwEO+I=
|
||||
|
||||
------=_NextPart_000_0003_01D622B3.CA753E60--
|
||||
"##;
|
||||
"#;
|
||||
|
||||
let message = MimeMessage::from_bytes(&context.ctx, &raw[..], None)
|
||||
.await
|
||||
@@ -3520,7 +3520,7 @@ On 2020-10-25, Bob wrote:
|
||||
|
||||
// A message with a long Message-ID.
|
||||
// Long message-IDs are generated by Mailjet.
|
||||
let raw = br###"Date: Thu, 28 Jan 2021 00:26:57 +0000
|
||||
let raw = br"Date: Thu, 28 Jan 2021 00:26:57 +0000
|
||||
Chat-Version: 1.0\n\
|
||||
Message-ID: <ABCDEFGH.1234567_AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA@mailjet.com>
|
||||
To: Bob <bob@example.org>
|
||||
@@ -3528,11 +3528,11 @@ From: Alice <alice@example.org>
|
||||
Subject: ...
|
||||
|
||||
Some quote.
|
||||
"###;
|
||||
";
|
||||
receive_imf(&t, raw, false).await?;
|
||||
|
||||
// Delta Chat generates In-Reply-To with a starting tab when Message-ID is too long.
|
||||
let raw = br###"In-Reply-To:
|
||||
let raw = br"In-Reply-To:
|
||||
<ABCDEFGH.1234567_AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA@mailjet.com>
|
||||
Date: Thu, 28 Jan 2021 00:26:57 +0000
|
||||
Chat-Version: 1.0\n\
|
||||
@@ -3544,7 +3544,7 @@ Subject: ...
|
||||
> Some quote.
|
||||
|
||||
Some reply
|
||||
"###;
|
||||
";
|
||||
|
||||
receive_imf(&t, raw, false).await?;
|
||||
|
||||
@@ -3562,7 +3562,7 @@ Some reply
|
||||
let alice = TestContext::new_alice().await;
|
||||
let bob = TestContext::new_bob().await;
|
||||
|
||||
let raw = br###"Date: Thu, 28 Jan 2021 00:26:57 +0000
|
||||
let raw = br"Date: Thu, 28 Jan 2021 00:26:57 +0000
|
||||
Chat-Version: 1.0\n\
|
||||
Message-ID: <foobarbaz@example.org>
|
||||
To: Bob <bob@example.org>
|
||||
@@ -3571,7 +3571,7 @@ Subject: subject
|
||||
Chat-Disposition-Notification-To: alice@example.org
|
||||
|
||||
Message.
|
||||
"###;
|
||||
";
|
||||
|
||||
// Bob receives message.
|
||||
receive_imf(&bob, raw, false).await?;
|
||||
|
||||
@@ -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:
|
||||
|
||||
@@ -26,10 +26,10 @@ impl PlainText {
|
||||
/// The function handles quotes, links, fixed and floating text paragraphs.
|
||||
pub fn to_html(&self) -> String {
|
||||
static LINKIFY_MAIL_RE: Lazy<regex::Regex> =
|
||||
Lazy::new(|| regex::Regex::new(r#"\b([\w.\-+]+@[\w.\-]+)\b"#).unwrap());
|
||||
Lazy::new(|| regex::Regex::new(r"\b([\w.\-+]+@[\w.\-]+)\b").unwrap());
|
||||
|
||||
static LINKIFY_URL_RE: Lazy<regex::Regex> = Lazy::new(|| {
|
||||
regex::Regex::new(r#"\b((http|https|ftp|ftps):[\w.,:;$/@!?&%\-~=#+]+)"#).unwrap()
|
||||
regex::Regex::new(r"\b((http|https|ftp|ftps):[\w.,:;$/@!?&%\-~=#+]+)").unwrap()
|
||||
});
|
||||
|
||||
let lines = split_lines(&self.text);
|
||||
@@ -127,7 +127,7 @@ http://link-at-start-of-line.org
|
||||
.to_html();
|
||||
assert_eq!(
|
||||
html,
|
||||
r##"<!DOCTYPE html>
|
||||
r#"<!DOCTYPE html>
|
||||
<html><head>
|
||||
<meta http-equiv="Content-Type" content="text/html; charset=utf-8" />
|
||||
<meta name="color-scheme" content="light dark" />
|
||||
@@ -138,7 +138,7 @@ line with <a href="https://link-mid-of-line.org">https://link-mid-of-line.org</a
|
||||
<a href="http://link-at-start-of-line.org">http://link-at-start-of-line.org</a><br/>
|
||||
<br/>
|
||||
</body></html>
|
||||
"##
|
||||
"#
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
@@ -223,7 +223,7 @@ pub async fn get_provider_by_mx(context: &Context, domain: &str) -> Option<&'sta
|
||||
}
|
||||
|
||||
if let Ok(mx_domains) = resolver.mx_lookup(fqdn).await {
|
||||
for (provider_domain, provider) in PROVIDER_DATA.iter() {
|
||||
for (provider_domain, provider) in &*PROVIDER_DATA {
|
||||
if provider.id != "gmail" {
|
||||
// MX lookup is limited to Gmail for security reasons
|
||||
continue;
|
||||
|
||||
@@ -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;
|
||||
@@ -594,12 +594,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.
|
||||
@@ -615,7 +620,7 @@ async fn add_parts(
|
||||
context,
|
||||
mime_parser,
|
||||
sent_timestamp,
|
||||
chat_id,
|
||||
group_chat_id,
|
||||
from_id,
|
||||
to_ids,
|
||||
)
|
||||
@@ -718,6 +723,44 @@ async fn add_parts(
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
// The next block checks if the message was sent with verified encryption
|
||||
// and sets the protection of the 1:1 chat accordingly.
|
||||
if is_partial_download.is_none()
|
||||
&& mime_parser.get_header(HeaderDef::SecureJoin).is_none()
|
||||
&& !is_mdn
|
||||
{
|
||||
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,
|
||||
};
|
||||
|
||||
let chat = Chat::load_from_db(context, chat_id).await?;
|
||||
|
||||
if chat.protected != new_protection {
|
||||
if new_protection == ProtectionStatus::Unprotected
|
||||
&& context
|
||||
.get_config_bool(Config::VerifiedOneOnOneChats)
|
||||
.await?
|
||||
{
|
||||
new_protection = ProtectionStatus::ProtectionBroken;
|
||||
}
|
||||
|
||||
let sort_timestamp =
|
||||
calc_sort_timestamp(context, sent_timestamp, chat_id, true).await?;
|
||||
chat_id
|
||||
.set_protection(context, new_protection, sort_timestamp, Some(from_id))
|
||||
.await?;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -984,42 +1027,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);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1527,7 +1542,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);
|
||||
@@ -1775,20 +1792,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?;
|
||||
}
|
||||
}
|
||||
|
||||
// Recreate the member list.
|
||||
if recreate_member_list {
|
||||
if !chat::is_contact_in_chat(context, chat_id, from_id).await? {
|
||||
@@ -2125,49 +2128,53 @@ 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<VerifiedEncryption> {
|
||||
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(),
|
||||
));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2179,7 +2186,7 @@ async fn check_verified_properties(
|
||||
.collect::<Vec<ContactId>>();
|
||||
|
||||
if to_ids.is_empty() {
|
||||
return Ok(());
|
||||
return Ok(Verified);
|
||||
}
|
||||
|
||||
let rows = context
|
||||
@@ -2203,10 +2210,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
|
||||
);
|
||||
@@ -2240,13 +2249,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.
|
||||
@@ -2345,7 +2354,7 @@ async fn add_or_lookup_contacts_by_address_list(
|
||||
origin: Origin,
|
||||
) -> Result<Vec<ContactId>> {
|
||||
let mut contact_ids = HashSet::new();
|
||||
for info in address_list.iter() {
|
||||
for info in address_list {
|
||||
let addr = &info.addr;
|
||||
if !may_be_valid_addr(addr) {
|
||||
continue;
|
||||
|
||||
@@ -864,7 +864,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)
|
||||
|
||||
@@ -6,7 +6,7 @@ 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::contact::{Contact, ContactId, Origin, VerifiedStatus};
|
||||
@@ -701,6 +701,14 @@ 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?;
|
||||
chat_id
|
||||
.set_protection(
|
||||
context,
|
||||
ProtectionStatus::Protected,
|
||||
time(),
|
||||
Some(contact_id),
|
||||
)
|
||||
.await?;
|
||||
context.emit_event(EventType::ChatModified(chat_id));
|
||||
Ok(())
|
||||
}
|
||||
@@ -783,6 +791,8 @@ mod tests {
|
||||
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;
|
||||
|
||||
@@ -921,7 +931,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 +939,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 +985,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 +993,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
|
||||
@@ -1278,17 +1299,11 @@ mod tests {
|
||||
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"));
|
||||
}
|
||||
|
||||
@@ -222,6 +222,14 @@ 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?;
|
||||
chat_id
|
||||
.set_protection(
|
||||
context,
|
||||
ProtectionStatus::Protected,
|
||||
time(),
|
||||
Some(contact.id),
|
||||
)
|
||||
.await?;
|
||||
context.emit_event(EventType::ChatModified(chat_id));
|
||||
Ok(())
|
||||
}
|
||||
|
||||
10
src/smtp.rs
10
src/smtp.rs
@@ -55,7 +55,7 @@ impl Smtp {
|
||||
}
|
||||
|
||||
/// Disconnect the SMTP transport and drop it entirely.
|
||||
pub async fn disconnect(&mut self) {
|
||||
pub fn disconnect(&mut self) {
|
||||
if let Some(mut transport) = self.transport.take() {
|
||||
// Closing connection with a QUIT command may take some time, especially if it's a
|
||||
// stale connection and an attempt to send the command times out. Send a command in a
|
||||
@@ -88,7 +88,7 @@ impl Smtp {
|
||||
pub async fn connect_configured(&mut self, context: &Context) -> Result<()> {
|
||||
if self.has_maybe_stale_connection() {
|
||||
info!(context, "Closing stale connection");
|
||||
self.disconnect().await;
|
||||
self.disconnect();
|
||||
}
|
||||
|
||||
if self.is_connected() {
|
||||
@@ -465,13 +465,13 @@ pub(crate) async fn smtp_send(
|
||||
|
||||
// this clears last_success info
|
||||
info!(context, "Failed to send message over SMTP, disconnecting");
|
||||
smtp.disconnect().await;
|
||||
smtp.disconnect();
|
||||
|
||||
res
|
||||
}
|
||||
Err(crate::smtp::send::Error::Envelope(err)) => {
|
||||
// Local error, job is invalid, do not retry.
|
||||
smtp.disconnect().await;
|
||||
smtp.disconnect();
|
||||
warn!(context, "SMTP job is invalid: {}", err);
|
||||
SendResult::Failure(err)
|
||||
}
|
||||
@@ -483,7 +483,7 @@ pub(crate) async fn smtp_send(
|
||||
}
|
||||
Err(crate::smtp::send::Error::Other(err)) => {
|
||||
// Local error, job is invalid, do not retry.
|
||||
smtp.disconnect().await;
|
||||
smtp.disconnect();
|
||||
warn!(context, "unable to load job: {}", err);
|
||||
SendResult::Failure(err)
|
||||
}
|
||||
|
||||
114
src/stock_str.rs
114
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<str> + 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<ContactId>,
|
||||
) -> 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,
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -31,11 +31,14 @@ use crate::constants::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::message::{update_msg_state, Message, MessageState, MsgId, Viewtype};
|
||||
use crate::mimeparser::MimeMessage;
|
||||
use crate::mimeparser::{MimeMessage, SystemMessage};
|
||||
use crate::peerstate::Peerstate;
|
||||
use crate::receive_imf::receive_imf;
|
||||
use crate::securejoin::{get_securejoin_qr, join_securejoin};
|
||||
use crate::stock_str::StockStrings;
|
||||
use crate::tools::EmailAddress;
|
||||
|
||||
@@ -108,9 +111,15 @@ impl TestContextManager {
|
||||
/// - Let one TestContext send a message
|
||||
/// - Let the other TestContext receive it and accept the chat
|
||||
/// - Assert that the message arrived
|
||||
pub async fn send_recv_accept(&self, from: &TestContext, to: &TestContext, msg: &str) {
|
||||
pub async fn send_recv_accept(
|
||||
&self,
|
||||
from: &TestContext,
|
||||
to: &TestContext,
|
||||
msg: &str,
|
||||
) -> Message {
|
||||
let received_msg = self.send_recv(from, to, msg).await;
|
||||
received_msg.chat_id.accept(to).await.unwrap();
|
||||
received_msg
|
||||
}
|
||||
|
||||
/// - Let one TestContext send a message
|
||||
@@ -152,6 +161,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)]
|
||||
@@ -636,7 +666,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
|
||||
@@ -1008,6 +1038,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.
|
||||
@@ -1114,7 +1164,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={}]",
|
||||
|
||||
@@ -1 +1,2 @@
|
||||
mod aeap;
|
||||
mod verified_chats;
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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<Message> {
|
||||
let msgs = chat::get_chat_msgs_ex(
|
||||
&t.ctx,
|
||||
|
||||
547
src/tests/verified_chats.rs
Normal file
547
src/tests/verified_chats.rs
Normal file
@@ -0,0 +1,547 @@
|
||||
use anyhow::Result;
|
||||
use pretty_assertions::assert_eq;
|
||||
|
||||
use crate::chat::{Chat, ProtectionStatus};
|
||||
use crate::config::Config;
|
||||
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: <bob@example.net>\r\n\
|
||||
To: <alice@example.org>\r\n\
|
||||
Date: Mon, 12 Dec 2022 14:33:39 +0000\r\n\
|
||||
Message-ID: <abcd@example.net>\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.unwrap();
|
||||
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 <bob@example.net>\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 <bob@example.net>\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 <bob@example.net>\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.unwrap();
|
||||
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.unwrap();
|
||||
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.unwrap();
|
||||
assert!(chat.is_protected());
|
||||
assert!(!chat.is_protection_broken());
|
||||
}
|
||||
|
||||
alice
|
||||
.golden_test_chat(chat.id, "test_verified_oneonone_chat_enable_disable")
|
||||
.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_accept(&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_accept(&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;
|
||||
|
||||
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;
|
||||
assert_verified(&alice, &bob_new, ProtectionStatus::ProtectionBroken).await;
|
||||
|
||||
{
|
||||
let alice_bob_chat = alice.get_chat(&bob_new).await.unwrap();
|
||||
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: <bob@example.net>\r\n\
|
||||
To: <alice@example.org>\r\n\
|
||||
Date: Mon, 12 Dec 2022 14:33:39 +0000\r\n\
|
||||
Message-ID: <abcd@example.net>\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.unwrap();
|
||||
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()
|
||||
}
|
||||
}
|
||||
@@ -702,7 +702,7 @@ pub(crate) fn buf_decompress(buf: &[u8]) -> Result<Vec<u8>> {
|
||||
}
|
||||
|
||||
const RTLO_CHARACTERS: [char; 5] = ['\u{202A}', '\u{202B}', '\u{202C}', '\u{202D}', '\u{202E}'];
|
||||
/// This method strips all occurances of the RTLO Unicode character.
|
||||
/// This method strips all occurrences of the RTLO Unicode character.
|
||||
/// [Why is this needed](https://github.com/deltachat/deltachat-core-rust/issues/3479)?
|
||||
pub(crate) fn strip_rtlo_characters(input_str: &str) -> String {
|
||||
input_str.replace(|char| RTLO_CHARACTERS.contains(&char), "")
|
||||
|
||||
7
test-data/golden/test_outgoing_mua_msg
Normal file
7
test-data/golden/test_outgoing_mua_msg
Normal file
@@ -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 √
|
||||
--------------------------------------------------------------------------------
|
||||
12
test-data/golden/test_verified_oneonone_chat_enable_disable
Normal file
12
test-data/golden/test_verified_oneonone_chat_enable_disable
Normal file
@@ -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]
|
||||
--------------------------------------------------------------------------------
|
||||
Reference in New Issue
Block a user