mirror of
https://github.com/chatmail/core.git
synced 2026-10-17 18:33:01 +03:00
Compare commits
38
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
7436368b50 | ||
|
|
c9fdfc32f2 | ||
|
|
0ce2f35909 | ||
|
|
5096a43831 | ||
|
|
2db390b25a | ||
|
|
028b0211c9 | ||
|
|
3a03c9993c | ||
|
|
6c536f3a9b | ||
|
|
4b24b6a848 | ||
|
|
5f254a929f | ||
|
|
8df1a01ace | ||
|
|
27b5ffb34f | ||
|
|
80af012962 | ||
|
|
615c80bef4 | ||
|
|
f5f4026dbb | ||
|
|
b431206ede | ||
|
|
c4878e9b49 | ||
|
|
aa452971a6 | ||
|
|
2d798f7cfe | ||
|
|
08bb0484eb | ||
|
|
b0b7337f5a | ||
|
|
93241a4beb | ||
|
|
4f1bf1f13c | ||
|
|
2d0b7b5bd8 | ||
|
|
8fe3ce5cab | ||
|
|
59a0f1d94f | ||
|
|
5175dc3450 | ||
|
|
9a22ccd058 | ||
|
|
c06ed49a2a | ||
|
|
2e51a5a454 | ||
|
|
75cc353528 | ||
|
|
3977580426 | ||
|
|
3a1370e174 | ||
|
|
c218c05b96 | ||
|
|
db247d9f9a | ||
|
|
78b7715ea6 | ||
|
|
ba76944d75 | ||
|
|
4a1a2122f0 |
@@ -1,5 +1,38 @@
|
||||
# Changelog
|
||||
|
||||
## [2.10.0] - 2025-08-04
|
||||
|
||||
### Features / Changes
|
||||
|
||||
- Also lookup key contacts in lookup_id_by_addr() ([#7073](https://github.com/chatmail/core/pull/7073)).
|
||||
|
||||
### Miscellaneous Tasks
|
||||
|
||||
- cargo: Bump serde_json from 1.0.140 to 1.0.142.
|
||||
- cargo: Bump bolero from 0.13.3 to 0.13.4.
|
||||
- cargo: Bump async-channel from 2.3.1 to 2.5.0.
|
||||
- cargo: Bump hyper-util from 0.1.14 to 0.1.16.
|
||||
- cargo: Bump criterion from 0.6.0 to 0.7.0.
|
||||
- cargo: Bump strum from 0.27.1 to 0.27.2.
|
||||
- cargo: Bump strum_macros from 0.27.1 to 0.27.2.
|
||||
- Upgrade async-imap to 0.11.1.
|
||||
|
||||
## [2.9.0] - 2025-07-31
|
||||
|
||||
### Features / Changes
|
||||
|
||||
- repl: Add import-vcard and make-vcard commands ([#7048](https://github.com/chatmail/core/pull/7048)).
|
||||
|
||||
### Fixes
|
||||
|
||||
- Display correct timer value for ephemeral timer changes.
|
||||
- Get_chat_msgs_ex(): Report local midnight in ChatItem::DayMarker.
|
||||
|
||||
### Refactor
|
||||
|
||||
- Rename add_or_lookup_key_contacts_by_address_list() to add_or_lookup_key_contacts().
|
||||
- Don't call add_or_lookup_key_contacts() in advance.
|
||||
|
||||
## [2.8.0] - 2025-07-28
|
||||
|
||||
### Features / Changes
|
||||
@@ -6561,3 +6594,5 @@ https://github.com/chatmail/core/pulls?q=is%3Apr+is%3Aclosed
|
||||
[2.6.0]: https://github.com/chatmail/core/compare/v2.5.0..v2.6.0
|
||||
[2.7.0]: https://github.com/chatmail/core/compare/v2.6.0..v2.7.0
|
||||
[2.8.0]: https://github.com/chatmail/core/compare/v2.7.0..v2.8.0
|
||||
[2.9.0]: https://github.com/chatmail/core/compare/v2.8.0..v2.9.0
|
||||
[2.10.0]: https://github.com/chatmail/core/compare/v2.9.0..v2.10.0
|
||||
|
||||
Generated
+74
-62
@@ -229,9 +229,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "async-channel"
|
||||
version = "2.3.1"
|
||||
version = "2.5.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "89b47800b0be77592da0afd425cc03468052844aff33b84e33cc696f64e77b6a"
|
||||
checksum = "924ed96dd52d1b75e9c1a3e6275715fd320f5f9439fb5a4a11fa51f4221158d2"
|
||||
dependencies = [
|
||||
"concurrent-queue",
|
||||
"event-listener-strategy",
|
||||
@@ -268,11 +268,11 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "async-imap"
|
||||
version = "0.11.0"
|
||||
version = "0.11.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "8e9f9a9c94a403cf46aa2b4cecbceefc6e4284441ebbeca79b80f3bab4394458"
|
||||
checksum = "8da885da5980f3934831e6370445c0e0e44ef251d7792308b39e908915a41d09"
|
||||
dependencies = [
|
||||
"async-channel 2.3.1",
|
||||
"async-channel 2.5.0",
|
||||
"async-compression",
|
||||
"base64",
|
||||
"bytes",
|
||||
@@ -535,9 +535,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "bolero"
|
||||
version = "0.13.3"
|
||||
version = "0.13.4"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "4e913ed74716cd68dc5be41c702327b1cc4ffc8f0b55945ae46fb015777007eb"
|
||||
checksum = "0ff44d278fc0062c95327087ed96b3d256906d1d8f579e534a3de8d6b386913a"
|
||||
dependencies = [
|
||||
"bolero-afl",
|
||||
"bolero-engine",
|
||||
@@ -561,9 +561,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "bolero-engine"
|
||||
version = "0.13.3"
|
||||
version = "0.13.4"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "05cae8c41807b046bb7005f52fa60c8f67787c1bf272242f0b84224853e04ceb"
|
||||
checksum = "dca199170a7c92c669c1019f9219a316b66bcdcfa4b36cac5a460a4c1a851aba"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"bolero-generator",
|
||||
@@ -575,9 +575,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "bolero-generator"
|
||||
version = "0.13.4"
|
||||
version = "0.13.5"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "8e3ac7405f187921256faa515fa05ae02521103582a9d938410cefabe3a9a172"
|
||||
checksum = "98a5782f2650f80d533f58ec339c6dce4cc5428f9c2755894f98156f52af81f2"
|
||||
dependencies = [
|
||||
"bolero-generator-derive",
|
||||
"either",
|
||||
@@ -588,14 +588,14 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "bolero-generator-derive"
|
||||
version = "0.13.3"
|
||||
version = "0.13.4"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "9c56c2f8c1c0707d678bebb36168cfd523c45927bb8d9cb7567d3578fa428cbd"
|
||||
checksum = "9a21a3b022507b9edd2050caf370d945e398c1a7c8455531220fa3968c45d29e"
|
||||
dependencies = [
|
||||
"proc-macro-crate 2.0.0",
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn 1.0.109",
|
||||
"syn 2.0.104",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -1023,16 +1023,16 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "criterion"
|
||||
version = "0.6.0"
|
||||
version = "0.7.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "3bf7af66b0989381bd0be551bd7cc91912a655a58c6918420c9527b1fd8b4679"
|
||||
checksum = "e1c047a62b0cc3e145fa84415a3191f628e980b194c2755aa12300a4e6cbd928"
|
||||
dependencies = [
|
||||
"anes",
|
||||
"cast",
|
||||
"ciborium",
|
||||
"clap",
|
||||
"criterion-plot",
|
||||
"itertools 0.13.0",
|
||||
"itertools",
|
||||
"num-traits",
|
||||
"oorandom",
|
||||
"plotters",
|
||||
@@ -1047,12 +1047,12 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "criterion-plot"
|
||||
version = "0.5.0"
|
||||
version = "0.6.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "6b50826342786a51a89e2da3a28f1c32b06e387201bc2d19791f622c673706b1"
|
||||
checksum = "9b1bcc0dc7dfae599d84ad0b1a55f80cde8af3725da8313b528da95ef783e338"
|
||||
dependencies = [
|
||||
"cast",
|
||||
"itertools 0.10.5",
|
||||
"itertools",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -1285,11 +1285,11 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "deltachat"
|
||||
version = "2.8.0"
|
||||
version = "2.10.0"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"async-broadcast",
|
||||
"async-channel 2.3.1",
|
||||
"async-channel 2.5.0",
|
||||
"async-imap",
|
||||
"async-native-tls",
|
||||
"async-smtp",
|
||||
@@ -1353,8 +1353,8 @@ dependencies = [
|
||||
"sha2",
|
||||
"shadowsocks",
|
||||
"smallvec",
|
||||
"strum 0.27.1",
|
||||
"strum_macros 0.27.1",
|
||||
"strum 0.27.2",
|
||||
"strum_macros 0.27.2",
|
||||
"tagger",
|
||||
"tempfile",
|
||||
"testdir",
|
||||
@@ -1395,10 +1395,10 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "deltachat-jsonrpc"
|
||||
version = "2.8.0"
|
||||
version = "2.10.0"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"async-channel 2.3.1",
|
||||
"async-channel 2.5.0",
|
||||
"base64",
|
||||
"deltachat",
|
||||
"deltachat-contact-tools",
|
||||
@@ -1417,7 +1417,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "deltachat-repl"
|
||||
version = "2.8.0"
|
||||
version = "2.10.0"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"deltachat",
|
||||
@@ -1433,7 +1433,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "deltachat-rpc-server"
|
||||
version = "2.8.0"
|
||||
version = "2.10.0"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"deltachat",
|
||||
@@ -1462,7 +1462,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "deltachat_ffi"
|
||||
version = "2.8.0"
|
||||
version = "2.10.0"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"deltachat",
|
||||
@@ -1923,9 +1923,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "event-listener-strategy"
|
||||
version = "0.5.3"
|
||||
version = "0.5.4"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "3c3e4e0dd3673c1139bf041f3008816d9cf2946bbfac2945c09e523b8d7b05b2"
|
||||
checksum = "8be9f3dfaaffdae2972880079a491a1a8bb7cbed0b8dd7a347f668b4150a3b93"
|
||||
dependencies = [
|
||||
"event-listener 5.4.0",
|
||||
"pin-project-lite",
|
||||
@@ -2557,9 +2557,9 @@ checksum = "df3b46402a9d5adb4c86a0cf463f42e19994e3ee891101b1841f30a545cb49a9"
|
||||
|
||||
[[package]]
|
||||
name = "human-panic"
|
||||
version = "2.0.2"
|
||||
version = "2.0.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "80b84a66a325082740043a6c28bbea400c129eac0d3a27673a1de971e44bf1f7"
|
||||
checksum = "ac63a746b187e95d51fe16850eb04d1cfef203f6af98e6c405a6f262ad3df00a"
|
||||
dependencies = [
|
||||
"backtrace",
|
||||
"os_info",
|
||||
@@ -2619,9 +2619,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "hyper-util"
|
||||
version = "0.1.14"
|
||||
version = "0.1.16"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "dc2fdfdbff08affe55bb779f33b053aa1fe5dd5b54c257343c17edfa55711bdb"
|
||||
checksum = "8d9b05277c7e8da2c93a568989bb6207bef0112e8d17df7a6eda4a3cf143bc5e"
|
||||
dependencies = [
|
||||
"bytes",
|
||||
"futures-channel",
|
||||
@@ -3019,7 +3019,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "3ca43045ceb44b913369f417d56323fb1628ebf482ab4c1e9360e81f1b58cbc2"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"async-channel 2.3.1",
|
||||
"async-channel 2.5.0",
|
||||
"bytes",
|
||||
"derive_more 1.0.0",
|
||||
"ed25519-dalek",
|
||||
@@ -3169,15 +3169,6 @@ dependencies = [
|
||||
"z32",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "itertools"
|
||||
version = "0.10.5"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "b0fd2260e829bddf4cb6ea802289de2f86d6a7a690192fbe91b3f46e0f2c8473"
|
||||
dependencies = [
|
||||
"either",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "itertools"
|
||||
version = "0.13.0"
|
||||
@@ -5393,9 +5384,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "serde_json"
|
||||
version = "1.0.140"
|
||||
version = "1.0.142"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "20068b6e96dc6c9bd23e01df8827e6c7e1f2fddd43c21810382803c136b99373"
|
||||
checksum = "030fedb782600dcbd6f02d479bf0d817ac3bb40d644745b769d6a96bc3afc5a7"
|
||||
dependencies = [
|
||||
"itoa",
|
||||
"memchr",
|
||||
@@ -5405,9 +5396,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "serde_spanned"
|
||||
version = "0.6.9"
|
||||
version = "1.0.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "bf41e0cfaf7226dca15e8197172c295a782857fcb97fad1808a166870dee75a3"
|
||||
checksum = "40734c41988f7306bb04f0ecf60ec0f3f1caa34290e4e8ea471dcd3346483b83"
|
||||
dependencies = [
|
||||
"serde",
|
||||
]
|
||||
@@ -5726,9 +5717,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "strum"
|
||||
version = "0.27.1"
|
||||
version = "0.27.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "f64def088c51c9510a8579e3c5d67c65349dcf755e5479ad3d010aa6454e2c32"
|
||||
checksum = "af23d6f6c1a224baef9d3f61e287d2761385a5b88fdab4eb4c6f11aeb54c4bcf"
|
||||
|
||||
[[package]]
|
||||
name = "strum_macros"
|
||||
@@ -5745,14 +5736,13 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "strum_macros"
|
||||
version = "0.27.1"
|
||||
version = "0.27.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "c77a8c5abcaf0f9ce05d62342b7d298c346515365c36b673df4ebe3ced01fde8"
|
||||
checksum = "7695ce3845ea4b33927c055a39dc438a45b059f7c1b3d91d38d10355fb8cbca7"
|
||||
dependencies = [
|
||||
"heck 0.5.0",
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"rustversion",
|
||||
"syn 2.0.104",
|
||||
]
|
||||
|
||||
@@ -6186,14 +6176,17 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "toml"
|
||||
version = "0.8.23"
|
||||
version = "0.9.4"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "dc1beb996b9d83529a9e75c17a1686767d148d70663143c7854d8b4a09ced362"
|
||||
checksum = "41ae868b5a0f67631c14589f7e250c1ea2c574ee5ba21c6c8dd4b1485705a5a1"
|
||||
dependencies = [
|
||||
"indexmap",
|
||||
"serde",
|
||||
"serde_spanned",
|
||||
"toml_datetime",
|
||||
"toml_edit 0.22.27",
|
||||
"toml_datetime 0.7.0",
|
||||
"toml_parser",
|
||||
"toml_writer",
|
||||
"winnow 0.7.11",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -6201,6 +6194,12 @@ name = "toml_datetime"
|
||||
version = "0.6.11"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "22cddaf88f4fbc13c51aebbf5f8eceb5c7c5a9da2ac40a13519eb5b0a0e8f11c"
|
||||
|
||||
[[package]]
|
||||
name = "toml_datetime"
|
||||
version = "0.7.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "bade1c3e902f58d73d3f294cd7f20391c1cb2fbcb643b73566bc773971df91e3"
|
||||
dependencies = [
|
||||
"serde",
|
||||
]
|
||||
@@ -6212,7 +6211,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "70f427fce4d84c72b5b732388bf4a9f4531b53f74e2887e3ecb2481f68f66d81"
|
||||
dependencies = [
|
||||
"indexmap",
|
||||
"toml_datetime",
|
||||
"toml_datetime 0.6.11",
|
||||
"winnow 0.5.40",
|
||||
]
|
||||
|
||||
@@ -6223,19 +6222,32 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "41fe8c660ae4257887cf66394862d21dbca4a6ddd26f04a3560410406a2f819a"
|
||||
dependencies = [
|
||||
"indexmap",
|
||||
"serde",
|
||||
"serde_spanned",
|
||||
"toml_datetime",
|
||||
"toml_datetime 0.6.11",
|
||||
"toml_write",
|
||||
"winnow 0.7.11",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "toml_parser"
|
||||
version = "1.0.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "97200572db069e74c512a14117b296ba0a80a30123fbbb5aa1f4a348f639ca30"
|
||||
dependencies = [
|
||||
"winnow 0.7.11",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "toml_write"
|
||||
version = "0.1.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "5d99f8c9a7727884afe522e9bd5edbfc91a3312b36a77b5fb8926e4c31a41801"
|
||||
|
||||
[[package]]
|
||||
name = "toml_writer"
|
||||
version = "1.0.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "fcc842091f2def52017664b53082ecbbeb5c7731092bad69d2c63050401dfd64"
|
||||
|
||||
[[package]]
|
||||
name = "tower"
|
||||
version = "0.5.2"
|
||||
|
||||
+6
-6
@@ -1,6 +1,6 @@
|
||||
[package]
|
||||
name = "deltachat"
|
||||
version = "2.8.0"
|
||||
version = "2.10.0"
|
||||
edition = "2024"
|
||||
license = "MPL-2.0"
|
||||
rust-version = "1.85"
|
||||
@@ -44,7 +44,7 @@ ratelimit = { path = "./deltachat-ratelimit" }
|
||||
anyhow = { workspace = true }
|
||||
async-broadcast = "0.7.2"
|
||||
async-channel = { workspace = true }
|
||||
async-imap = { version = "0.11.0", default-features = false, features = ["runtime-tokio", "compress"] }
|
||||
async-imap = { version = "0.11.1", default-features = false, features = ["runtime-tokio", "compress"] }
|
||||
async-native-tls = { version = "0.5", default-features = false, features = ["runtime-tokio"] }
|
||||
async-smtp = { version = "0.10.2", default-features = false, features = ["runtime-tokio"] }
|
||||
async_zip = { version = "0.0.17", default-features = false, features = ["deflate", "tokio-fs"] }
|
||||
@@ -63,7 +63,7 @@ hickory-resolver = "0.25.2"
|
||||
http-body-util = "0.1.3"
|
||||
humansize = "2"
|
||||
hyper = "1"
|
||||
hyper-util = "0.1.14"
|
||||
hyper-util = "0.1.16"
|
||||
image = { version = "0.25.6", default-features=false, features = ["gif", "jpeg", "ico", "png", "pnm", "webp", "bmp"] }
|
||||
iroh-gossip = { version = "0.35", default-features = false, features = ["net"] }
|
||||
iroh = { version = "0.35", default-features = false }
|
||||
@@ -107,7 +107,7 @@ tokio-stream = { version = "0.1.17", features = ["fs"] }
|
||||
tokio-tar = { version = "0.3" } # TODO: integrate tokio into async-tar
|
||||
tokio-util = { workspace = true }
|
||||
tokio = { workspace = true, features = ["fs", "rt-multi-thread", "macros"] }
|
||||
toml = "0.8"
|
||||
toml = "0.9"
|
||||
tracing = "0.1.41"
|
||||
url = "2"
|
||||
uuid = { version = "1", features = ["serde", "v4"] }
|
||||
@@ -116,7 +116,7 @@ blake3 = "1.8.2"
|
||||
|
||||
[dev-dependencies]
|
||||
anyhow = { workspace = true, features = ["backtrace"] } # Enable `backtrace` feature in tests.
|
||||
criterion = { version = "0.6.0", features = ["async_tokio"] }
|
||||
criterion = { version = "0.7.0", features = ["async_tokio"] }
|
||||
futures-lite = { workspace = true }
|
||||
log = { workspace = true }
|
||||
nu-ansi-term = { workspace = true }
|
||||
@@ -175,7 +175,7 @@ harness = false
|
||||
|
||||
[workspace.dependencies]
|
||||
anyhow = "1"
|
||||
async-channel = "2.3.1"
|
||||
async-channel = "2.5.0"
|
||||
base64 = "0.22"
|
||||
chrono = { version = "0.4.41", default-features = false }
|
||||
deltachat-contact-tools = { path = "deltachat-contact-tools" }
|
||||
|
||||
@@ -80,18 +80,26 @@ Connect to your mail server (if already configured):
|
||||
> connect
|
||||
```
|
||||
|
||||
Create a contact:
|
||||
Export your public key to a vCard file:
|
||||
|
||||
```
|
||||
> make-vcard my.vcard 1
|
||||
```
|
||||
|
||||
Create contacts by address or vCard file:
|
||||
|
||||
```
|
||||
> addcontact yourfriends@email.org
|
||||
> import-vcard key-contact.vcard
|
||||
```
|
||||
|
||||
List contacts:
|
||||
|
||||
```
|
||||
> listcontacts
|
||||
Contact#Contact#11: key-contact@email.org <key-contact@email.org>
|
||||
Contact#Contact#Self: Me √ <your@email.org>
|
||||
1 key contacts.
|
||||
2 key contacts.
|
||||
Contact#Contact#10: yourfriends@email.org <yourfriends@email.org>
|
||||
1 address contacts.
|
||||
```
|
||||
|
||||
|
Before Width: | Height: | Size: 2.2 KiB After Width: | Height: | Size: 2.2 KiB |
|
Before Width: | Height: | Size: 1.8 KiB After Width: | Height: | Size: 1.8 KiB |
@@ -1,6 +1,6 @@
|
||||
[package]
|
||||
name = "deltachat_ffi"
|
||||
version = "2.8.0"
|
||||
version = "2.10.0"
|
||||
description = "Deltachat FFI"
|
||||
edition = "2018"
|
||||
readme = "README.md"
|
||||
|
||||
@@ -1332,12 +1332,14 @@ dc_msg_t* dc_get_draft (dc_context_t* context, uint32_t ch
|
||||
* Optionally, some special markers added to the ID array may help to
|
||||
* implement virtual lists.
|
||||
*
|
||||
* To get the concrete time of the message, use dc_array_get_timestamp().
|
||||
*
|
||||
* @memberof dc_context_t
|
||||
* @param context The context object as returned from dc_context_new().
|
||||
* @param chat_id The chat ID of which the messages IDs should be queried.
|
||||
* @param flags If set to DC_GCM_ADDDAYMARKER, the marker DC_MSG_ID_DAYMARKER will
|
||||
* be added before each day (regarding the local timezone). Set this to 0 if you do not want this behaviour.
|
||||
* To get the concrete time of the marker, use dc_array_get_timestamp().
|
||||
* The day marker timestamp is the midnight one for the corresponding (following) day in the local timezone.
|
||||
* If set to DC_GCM_INFO_ONLY, only system messages will be returned, can be combined with DC_GCM_ADDDAYMARKER.
|
||||
* @param marker1before Deprecated, set this to 0.
|
||||
* @return Array of message IDs, must be dc_array_unref()'d when no longer used.
|
||||
@@ -2087,9 +2089,19 @@ int dc_may_be_valid_addr (const char* addr);
|
||||
|
||||
|
||||
/**
|
||||
* Check if an e-mail address belongs to a known and unblocked contact.
|
||||
* Looks up a known and unblocked contact with a given e-mail address.
|
||||
* To get a list of all known and unblocked contacts, use dc_get_contacts().
|
||||
*
|
||||
* **POTENTIAL SECURITY ISSUE**: If there are multiple contacts with this address
|
||||
* (e.g. an address-contact and a key-contact),
|
||||
* this looks up the most recently seen contact,
|
||||
* i.e. which contact is returned depends on which contact last sent a message.
|
||||
* If the user just clicked on a mailto: link, then this is the best thing you can do.
|
||||
* But **DO NOT** internally represent contacts by their email address
|
||||
* and do not use this function to look them up;
|
||||
* otherwise this function will sometimes look up the wrong contact.
|
||||
* Instead, you should internally represent contacts by their ids.
|
||||
*
|
||||
* To validate an e-mail address independently of the contact database
|
||||
* use dc_may_be_valid_addr().
|
||||
*
|
||||
@@ -2111,6 +2123,13 @@ uint32_t dc_lookup_contact_id_by_addr (dc_context_t* context, const char*
|
||||
* To add a number of contacts, see dc_add_address_book() which is much faster for adding
|
||||
* a bunch of addresses.
|
||||
*
|
||||
* This will always create or look up an address-contact,
|
||||
* i.e. a contact identified by an email address,
|
||||
* with all messages sent to and from this contact being unencrypted.
|
||||
* If the user just clicked on an email address,
|
||||
* you should first check `lookup_contact_id_by_addr`,
|
||||
* and only if there is no contact yet, call this function here.
|
||||
*
|
||||
* May result in a #DC_EVENT_CONTACTS_CHANGED event.
|
||||
*
|
||||
* @memberof dc_context_t
|
||||
@@ -7579,6 +7598,18 @@ 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 set message deletion timer to 1 year."
|
||||
///
|
||||
/// Used in status messages.
|
||||
#define DC_STR_EPHEMERAL_TIMER_1_YEAR_BY_YOU 158
|
||||
|
||||
/// "Message deletion timer is set to 1 year by %1$s."
|
||||
///
|
||||
/// `%1$s` will be replaced by name and address of the contact.
|
||||
///
|
||||
/// Used in status messages.
|
||||
#define DC_STR_EPHEMERAL_TIMER_1_YEAR_BY_OTHER 159
|
||||
|
||||
/// "Scan to set up second device for %1$s"
|
||||
///
|
||||
/// `%1$s` will be replaced by name and address of the account.
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
[package]
|
||||
name = "deltachat-jsonrpc"
|
||||
version = "2.8.0"
|
||||
version = "2.10.0"
|
||||
description = "DeltaChat JSON-RPC API"
|
||||
edition = "2021"
|
||||
license = "MPL-2.0"
|
||||
|
||||
@@ -1227,8 +1227,10 @@ impl CommandApi {
|
||||
}
|
||||
|
||||
/// Returns all messages of a particular chat.
|
||||
/// If `add_daymarker` is `true`, it will return them as
|
||||
/// `DC_MSG_ID_DAYMARKER`, e.g. [1234, 1237, 9, 1239].
|
||||
///
|
||||
/// * `add_daymarker` - If `true`, add day markers as `DC_MSG_ID_DAYMARKER` to the result,
|
||||
/// e.g. [1234, 1237, 9, 1239]. The day marker timestamp is the midnight one for the
|
||||
/// corresponding (following) day in the local timezone.
|
||||
async fn get_message_ids(
|
||||
&self,
|
||||
account_id: u32,
|
||||
@@ -1471,7 +1473,14 @@ impl CommandApi {
|
||||
|
||||
/// Add a single contact as a result of an explicit user action.
|
||||
///
|
||||
/// Returns contact id of the created or existing contact
|
||||
/// This will always create or look up an address-contact,
|
||||
/// i.e. a contact identified by an email address,
|
||||
/// with all messages sent to and from this contact being unencrypted.
|
||||
/// If the user just clicked on an email address,
|
||||
/// you should first check [`Self::lookup_contact_id_by_addr`]/`lookupContactIdByAddr.`,
|
||||
/// and only if there is no contact yet, call this function here.
|
||||
///
|
||||
/// Returns contact id of the created or existing contact.
|
||||
async fn create_contact(
|
||||
&self,
|
||||
account_id: u32,
|
||||
@@ -1621,9 +1630,19 @@ impl CommandApi {
|
||||
Contact::get_encrinfo(&ctx, ContactId::new(contact_id)).await
|
||||
}
|
||||
|
||||
/// Check if an e-mail address belongs to a known and unblocked contact.
|
||||
/// Looks up a known and unblocked contact with a given e-mail address.
|
||||
/// To get a list of all known and unblocked contacts, use contacts_get_contacts().
|
||||
///
|
||||
/// **POTENTIAL SECURITY ISSUE**: If there are multiple contacts with this address
|
||||
/// (e.g. an address-contact and a key-contact),
|
||||
/// this looks up the most recently seen contact,
|
||||
/// i.e. which contact is returned depends on which contact last sent a message.
|
||||
/// If the user just clicked on a mailto: link, then this is the best thing you can do.
|
||||
/// But **DO NOT** internally represent contacts by their email address
|
||||
/// and do not use this function to look them up;
|
||||
/// otherwise this function will sometimes look up the wrong contact.
|
||||
/// Instead, you should internally represent contacts by their ids.
|
||||
///
|
||||
/// To validate an e-mail address independently of the contact database
|
||||
/// use check_email_validity().
|
||||
async fn lookup_contact_id_by_addr(
|
||||
|
||||
@@ -54,5 +54,5 @@
|
||||
},
|
||||
"type": "module",
|
||||
"types": "dist/deltachat.d.ts",
|
||||
"version": "2.8.0"
|
||||
"version": "2.10.0"
|
||||
}
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
[package]
|
||||
name = "deltachat-repl"
|
||||
version = "2.8.0"
|
||||
version = "2.10.0"
|
||||
license = "MPL-2.0"
|
||||
edition = "2021"
|
||||
repository = "https://github.com/chatmail/core"
|
||||
|
||||
@@ -403,6 +403,8 @@ pub async fn cmdline(context: Context, line: &str, chat_id: &mut ChatId) -> Resu
|
||||
block <contact-id>\n\
|
||||
unblock <contact-id>\n\
|
||||
listblocked\n\
|
||||
import-vcard <file>\n\
|
||||
make-vcard <file> <contact-id> [contact-id ...]\n\
|
||||
======================================Misc.==\n\
|
||||
getqr [<chat-id>]\n\
|
||||
getqrsvg [<chat-id>]\n\
|
||||
@@ -1218,6 +1220,24 @@ pub async fn cmdline(context: Context, line: &str, chat_id: &mut ChatId) -> Resu
|
||||
log_contactlist(&context, &contacts).await?;
|
||||
println!("{} blocked contacts.", contacts.len());
|
||||
}
|
||||
"import-vcard" => {
|
||||
ensure!(!arg1.is_empty(), "Argument <file> missing.");
|
||||
let vcard_content = fs::read_to_string(&arg1.to_string()).await?;
|
||||
let contacts = import_vcard(&context, &vcard_content).await?;
|
||||
println!("vCard contacts imported:");
|
||||
log_contactlist(&context, &contacts).await?;
|
||||
}
|
||||
"make-vcard" => {
|
||||
ensure!(!arg1.is_empty(), "Argument <file> missing.");
|
||||
ensure!(!arg2.is_empty(), "Argument <contact-id> missing.");
|
||||
let mut contact_ids = vec![];
|
||||
for x in arg2.split_whitespace() {
|
||||
contact_ids.push(ContactId::new(x.parse()?))
|
||||
}
|
||||
let vcard_content = make_vcard(&context, &contact_ids).await?;
|
||||
fs::write(&arg1.to_string(), vcard_content).await?;
|
||||
println!("vCard written to: {arg1}");
|
||||
}
|
||||
"checkqr" => {
|
||||
ensure!(!arg1.is_empty(), "Argument <qr-content> missing.");
|
||||
let qr = check_qr(&context, arg1).await?;
|
||||
|
||||
@@ -232,7 +232,7 @@ const MESSAGE_COMMANDS: [&str; 10] = [
|
||||
"delmsg",
|
||||
"react",
|
||||
];
|
||||
const CONTACT_COMMANDS: [&str; 7] = [
|
||||
const CONTACT_COMMANDS: [&str; 9] = [
|
||||
"listcontacts",
|
||||
"addcontact",
|
||||
"contactinfo",
|
||||
@@ -240,6 +240,8 @@ const CONTACT_COMMANDS: [&str; 7] = [
|
||||
"block",
|
||||
"unblock",
|
||||
"listblocked",
|
||||
"import-vcard",
|
||||
"make-vcard",
|
||||
];
|
||||
const MISC_COMMANDS: [&str; 14] = [
|
||||
"getqr",
|
||||
|
||||
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
|
||||
|
||||
[project]
|
||||
name = "deltachat-rpc-client"
|
||||
version = "2.8.0"
|
||||
version = "2.10.0"
|
||||
description = "Python client for Delta Chat core JSON-RPC interface"
|
||||
classifiers = [
|
||||
"Development Status :: 5 - Production/Stable",
|
||||
|
||||
@@ -185,7 +185,21 @@ class Account:
|
||||
return Contact(self, contact_id)
|
||||
|
||||
def get_contact_by_addr(self, address: str) -> Optional[Contact]:
|
||||
"""Check if an e-mail address belongs to a known and unblocked contact."""
|
||||
"""Looks up a known and unblocked contact with a given e-mail address.
|
||||
To get a list of all known and unblocked contacts, use contacts_get_contacts().
|
||||
|
||||
**POTENTIAL SECURITY ISSUE**: If there are multiple contacts with this address
|
||||
(e.g. an address-contact and a key-contact),
|
||||
this looks up the most recently seen contact,
|
||||
i.e. which contact is returned depends on which contact last sent a message.
|
||||
If the user just clicked on a mailto: link, then this is the best thing you can do.
|
||||
But **DO NOT** internally represent contacts by their email address
|
||||
and do not use this function to look them up;
|
||||
otherwise this function will sometimes look up the wrong contact.
|
||||
Instead, you should internally represent contacts by their ids.
|
||||
|
||||
To validate an e-mail address independently of the contact database
|
||||
use check_email_validity()."""
|
||||
contact_id = self._rpc.lookup_contact_id_by_addr(self.id, address)
|
||||
return contact_id and Contact(self, contact_id)
|
||||
|
||||
|
||||
@@ -171,7 +171,10 @@ def test_account(acfactory) -> None:
|
||||
assert alice.get_size()
|
||||
assert alice.is_configured()
|
||||
assert not alice.get_avatar()
|
||||
assert alice.get_contact_by_addr(bob_addr) is None # There is no address-contact, only key-contact
|
||||
# get_contact_by_addr() can lookup a key contact by address:
|
||||
bob_contact = alice.get_contact_by_addr(bob_addr).get_snapshot()
|
||||
assert bob_contact.display_name == "Bob"
|
||||
assert bob_contact.is_key_contact
|
||||
assert alice.get_contacts()
|
||||
assert alice.get_contacts(snapshot=True)
|
||||
assert alice.self_contact
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
[package]
|
||||
name = "deltachat-rpc-server"
|
||||
version = "2.8.0"
|
||||
version = "2.10.0"
|
||||
description = "DeltaChat JSON-RPC server"
|
||||
edition = "2021"
|
||||
readme = "README.md"
|
||||
|
||||
@@ -15,5 +15,5 @@
|
||||
},
|
||||
"type": "module",
|
||||
"types": "index.d.ts",
|
||||
"version": "2.8.0"
|
||||
"version": "2.10.0"
|
||||
}
|
||||
|
||||
@@ -48,6 +48,7 @@ skip = [
|
||||
{ name = "syn", version = "1.0.109" },
|
||||
{ name = "thiserror-impl", version = "1.0.69" },
|
||||
{ name = "thiserror", version = "1.0.69" },
|
||||
{ name = "toml_datetime", version = "0.6.11" },
|
||||
{ name = "wasi", version = "0.11.0+wasi-snapshot-preview1" },
|
||||
{ name = "windows" },
|
||||
{ name = "windows_aarch64_gnullvm" },
|
||||
|
||||
+1
-1
@@ -6,7 +6,7 @@ edition = "2021"
|
||||
license = "MPL-2.0"
|
||||
|
||||
[dev-dependencies]
|
||||
bolero = "0.13.3"
|
||||
bolero = "0.13.4"
|
||||
|
||||
[dependencies]
|
||||
mailparse = { workspace = true }
|
||||
|
||||
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
|
||||
|
||||
[project]
|
||||
name = "deltachat"
|
||||
version = "2.8.0"
|
||||
version = "2.10.0"
|
||||
description = "Python bindings for the Delta Chat Core library using CFFI against the Rust-implemented libdeltachat"
|
||||
readme = "README.rst"
|
||||
requires-python = ">=3.8"
|
||||
|
||||
@@ -330,7 +330,21 @@ class Account:
|
||||
return bool(lib.dc_delete_contact(self._dc_context, contact_id))
|
||||
|
||||
def get_contact_by_addr(self, email: str) -> Optional[Contact]:
|
||||
"""get a contact for the email address or None if it's blocked or doesn't exist."""
|
||||
"""Looks up a known and unblocked contact with a given e-mail address.
|
||||
To get a list of all known and unblocked contacts, use contacts_get_contacts().
|
||||
|
||||
**POTENTIAL SECURITY ISSUE**: If there are multiple contacts with this address
|
||||
(e.g. an address-contact and a key-contact),
|
||||
this looks up the most recently seen contact,
|
||||
i.e. which contact is returned depends on which contact last sent a message.
|
||||
If the user just clicked on a mailto: link, then this is the best thing you can do.
|
||||
But **DO NOT** internally represent contacts by their email address
|
||||
and do not use this function to look them up;
|
||||
otherwise this function will sometimes look up the wrong contact.
|
||||
Instead, you should internally represent contacts by their ids.
|
||||
|
||||
To validate an e-mail address independently of the contact database
|
||||
use check_email_validity()."""
|
||||
_, addr = parseaddr(email)
|
||||
addr = as_dc_charpointer(addr)
|
||||
contact_id = lib.dc_lookup_contact_id_by_addr(self._dc_context, addr)
|
||||
|
||||
+1
-1
@@ -1 +1 @@
|
||||
2025-07-28
|
||||
2025-08-04
|
||||
+28
-16
@@ -1771,6 +1771,12 @@ impl Chat {
|
||||
return Ok(Some(get_device_icon(context).await?));
|
||||
} else if self.is_self_talk() {
|
||||
return Ok(Some(get_saved_messages_icon(context).await?));
|
||||
} else if !self.is_encrypted(context).await? {
|
||||
// This is an unencrypted chat, show a special avatar that marks it as such.
|
||||
return Ok(Some(get_abs_path(
|
||||
context,
|
||||
Path::new(&get_unencrypted_icon(context).await?),
|
||||
)));
|
||||
} else if self.typ == Chattype::Single {
|
||||
// For 1:1 chats, we always use the same avatar as for the contact
|
||||
// This is before the `self.is_encrypted()` check, because that function
|
||||
@@ -1780,12 +1786,6 @@ impl Chat {
|
||||
let contact = Contact::get_by_id(context, *contact_id).await?;
|
||||
return contact.get_profile_image(context).await;
|
||||
}
|
||||
} else if !self.is_encrypted(context).await? {
|
||||
// This is an address-contact chat, show a special avatar that marks it as such
|
||||
return Ok(Some(get_abs_path(
|
||||
context,
|
||||
Path::new(&get_address_contact_icon(context).await?),
|
||||
)));
|
||||
} else if let Some(image_rel) = self.param.get(Param::ProfileImage) {
|
||||
// Load the group avatar, or the device-chat / saved-messages icon
|
||||
if !image_rel.is_empty() {
|
||||
@@ -2490,11 +2490,13 @@ pub(crate) async fn get_archive_icon(context: &Context) -> Result<PathBuf> {
|
||||
.await
|
||||
}
|
||||
|
||||
pub(crate) async fn get_address_contact_icon(context: &Context) -> Result<PathBuf> {
|
||||
/// Returns path to the icon
|
||||
/// indicating unencrypted chats and address-contacts.
|
||||
pub(crate) async fn get_unencrypted_icon(context: &Context) -> Result<PathBuf> {
|
||||
get_asset_icon(
|
||||
context,
|
||||
"icon-address-contact",
|
||||
include_bytes!("../assets/icon-address-contact.png"),
|
||||
"icon-unencrypted",
|
||||
include_bytes!("../assets/icon-unencrypted.png"),
|
||||
)
|
||||
.await
|
||||
}
|
||||
@@ -3323,10 +3325,11 @@ pub async fn get_chat_msgs_ex(
|
||||
for (ts, curr_id) in sorted_rows {
|
||||
if add_daymarker {
|
||||
let curr_local_timestamp = ts + cnv_to_local;
|
||||
let curr_day = curr_local_timestamp / 86400;
|
||||
let secs_in_day = 86400;
|
||||
let curr_day = curr_local_timestamp / secs_in_day;
|
||||
if curr_day != last_day {
|
||||
ret.push(ChatItem::DayMarker {
|
||||
timestamp: curr_day * 86400, // Convert day back to Unix timestamp
|
||||
timestamp: curr_day * secs_in_day - cnv_to_local,
|
||||
});
|
||||
last_day = curr_day;
|
||||
}
|
||||
@@ -4480,15 +4483,24 @@ pub(crate) async fn save_copy_in_self_talk(
|
||||
bail!("message already saved.");
|
||||
}
|
||||
|
||||
let copy_fields = "from_id, to_id, timestamp_sent, timestamp_rcvd, type, txt, \
|
||||
mime_modified, mime_headers, mime_compressed, mime_in_reply_to, subject, msgrmsg";
|
||||
let copy_fields = "from_id, to_id, timestamp_rcvd, type, txt,
|
||||
mime_modified, mime_headers, mime_compressed, mime_in_reply_to, subject, msgrmsg";
|
||||
let row_id = context
|
||||
.sql
|
||||
.insert(
|
||||
&format!(
|
||||
"INSERT INTO msgs ({copy_fields}, chat_id, rfc724_mid, state, timestamp, param, starred) \
|
||||
SELECT {copy_fields}, ?, ?, ?, ?, ?, ? \
|
||||
FROM msgs WHERE id=?;"
|
||||
"INSERT INTO msgs ({copy_fields},
|
||||
timestamp_sent,
|
||||
chat_id, rfc724_mid, state, timestamp, param, starred)
|
||||
SELECT {copy_fields},
|
||||
-- Outgoing messages on originating device
|
||||
-- have timestamp_sent == 0.
|
||||
-- We copy sort timestamp instead
|
||||
-- so UIs display the same timestamp
|
||||
-- for saved and original message.
|
||||
IIF(timestamp_sent == 0, timestamp, timestamp_sent),
|
||||
?, ?, ?, ?, ?, ?
|
||||
FROM msgs WHERE id=?;"
|
||||
),
|
||||
(
|
||||
dest_chat_id,
|
||||
|
||||
@@ -2280,14 +2280,19 @@ async fn test_only_minimal_data_are_forwarded() -> Result<()> {
|
||||
|
||||
#[tokio::test(flavor = "multi_thread", worker_threads = 2)]
|
||||
async fn test_save_msgs() -> Result<()> {
|
||||
let alice = TestContext::new_alice().await;
|
||||
let bob = TestContext::new_bob().await;
|
||||
let mut tcm = TestContextManager::new();
|
||||
let alice = tcm.alice().await;
|
||||
let bob = tcm.bob().await;
|
||||
let alice_chat = alice.create_chat(&bob).await;
|
||||
|
||||
let sent = alice.send_text(alice_chat.get_id(), "hi, bob").await;
|
||||
let sent_msg = Message::load_from_db(&alice, sent.sender_msg_id).await?;
|
||||
assert!(sent_msg.get_saved_msg_id(&alice).await?.is_none());
|
||||
assert!(sent_msg.get_original_msg_id(&alice).await?.is_none());
|
||||
let sent_timestamp = sent_msg.get_timestamp();
|
||||
assert!(sent_timestamp > 0);
|
||||
|
||||
SystemTime::shift(Duration::from_secs(60));
|
||||
|
||||
let self_chat = alice.get_self_chat().await;
|
||||
save_msgs(&alice, &[sent.sender_msg_id]).await?;
|
||||
@@ -2305,6 +2310,8 @@ async fn test_save_msgs() -> Result<()> {
|
||||
assert_eq!(saved_msg.get_from_id(), ContactId::SELF);
|
||||
assert_eq!(saved_msg.get_state(), MessageState::OutDelivered);
|
||||
assert_ne!(saved_msg.rfc724_mid(), sent_msg.rfc724_mid());
|
||||
let saved_timestamp = saved_msg.get_timestamp();
|
||||
assert_eq!(saved_timestamp, sent_timestamp);
|
||||
|
||||
let sent_msg = Message::load_from_db(&alice, sent.sender_msg_id).await?;
|
||||
assert_eq!(
|
||||
|
||||
+31
-5
@@ -755,7 +755,19 @@ impl Contact {
|
||||
self.is_bot
|
||||
}
|
||||
|
||||
/// Check if an e-mail address belongs to a known and unblocked contact.
|
||||
/// Looks up a known and unblocked contact with a given e-mail address.
|
||||
/// To get a list of all known and unblocked contacts, use contacts_get_contacts().
|
||||
///
|
||||
///
|
||||
/// **POTENTIAL SECURITY ISSUE**: If there are multiple contacts with this address
|
||||
/// (e.g. an address-contact and a key-contact),
|
||||
/// this looks up the most recently seen contact,
|
||||
/// i.e. which contact is returned depends on which contact last sent a message.
|
||||
/// If the user just clicked on a mailto: link, then this is the best thing you can do.
|
||||
/// But **DO NOT** internally represent contacts by their email address
|
||||
/// and do not use this function to look them up;
|
||||
/// otherwise this function will sometimes look up the wrong contact.
|
||||
/// Instead, you should internally represent contacts by their ids.
|
||||
///
|
||||
/// Known and unblocked contacts will be returned by `get_contacts()`.
|
||||
///
|
||||
@@ -795,14 +807,28 @@ impl Contact {
|
||||
.query_get_value(
|
||||
"SELECT id FROM contacts
|
||||
WHERE addr=?1 COLLATE NOCASE
|
||||
AND fingerprint='' -- Do not lookup key-contacts
|
||||
AND id>?2 AND origin>=?3 AND (? OR blocked=?)",
|
||||
AND id>?2 AND origin>=?3 AND (? OR blocked=?)
|
||||
ORDER BY
|
||||
(
|
||||
SELECT COUNT(*) FROM chats c
|
||||
INNER JOIN chats_contacts cc
|
||||
ON c.id=cc.chat_id
|
||||
WHERE c.type=?
|
||||
AND c.id>?
|
||||
AND c.blocked=?
|
||||
AND cc.contact_id=contacts.id
|
||||
) DESC,
|
||||
last_seen DESC, fingerprint DESC
|
||||
LIMIT 1",
|
||||
(
|
||||
&addr_normalized,
|
||||
ContactId::LAST_SPECIAL,
|
||||
min_origin as u32,
|
||||
blocked.is_none(),
|
||||
blocked.unwrap_or_default(),
|
||||
blocked.unwrap_or(Blocked::Not),
|
||||
Chattype::Single,
|
||||
constants::DC_CHAT_ID_LAST_SPECIAL,
|
||||
blocked.unwrap_or(Blocked::Not),
|
||||
),
|
||||
)
|
||||
.await?;
|
||||
@@ -1538,7 +1564,7 @@ impl Contact {
|
||||
return Ok(Some(chat::get_device_icon(context).await?));
|
||||
}
|
||||
if show_fallback_icon && !self.id.is_special() && !self.is_key_contact() {
|
||||
return Ok(Some(chat::get_address_contact_icon(context).await?));
|
||||
return Ok(Some(chat::get_unencrypted_icon(context).await?));
|
||||
}
|
||||
if let Some(image_rel) = self.param.get(Param::ProfileImage) {
|
||||
if !image_rel.is_empty() {
|
||||
|
||||
@@ -1035,6 +1035,50 @@ async fn test_was_seen_recently_event() -> Result<()> {
|
||||
Ok(())
|
||||
}
|
||||
|
||||
async fn test_lookup_id_by_addr_recent_ex(accept_unencrypted_chat: bool) -> Result<()> {
|
||||
let mut tcm = TestContextManager::new();
|
||||
let bob = &tcm.bob().await;
|
||||
|
||||
let raw = include_bytes!("../../test-data/message/thunderbird_with_autocrypt.eml");
|
||||
assert!(std::str::from_utf8(raw)?.contains("Date: Thu, 24 Nov 2022 20:05:57 +0100"));
|
||||
let received_msg = receive_imf(bob, raw, false).await?.unwrap();
|
||||
received_msg.chat_id.accept(bob).await?;
|
||||
|
||||
let raw = r#"From: Alice <alice@example.org>
|
||||
To: bob@example.net
|
||||
Message-ID: message$TIME@example.org
|
||||
Content-Type: text/plain; charset=utf-8; format=flowed; delsp=no
|
||||
Date: Thu, 24 Nov 2022 $TIME +0100
|
||||
|
||||
Hi"#
|
||||
.to_string();
|
||||
for (time, is_key_contact) in [("20:05:57", true), ("20:05:58", !accept_unencrypted_chat)] {
|
||||
let raw = raw.replace("$TIME", time);
|
||||
let received_msg = receive_imf(bob, raw.as_bytes(), false).await?.unwrap();
|
||||
if accept_unencrypted_chat {
|
||||
received_msg.chat_id.accept(bob).await?;
|
||||
}
|
||||
let contact_id = Contact::lookup_id_by_addr(bob, "alice@example.org", Origin::Unknown)
|
||||
.await?
|
||||
.unwrap();
|
||||
let contact = Contact::get_by_id(bob, contact_id).await?;
|
||||
assert_eq!(contact.is_key_contact(), is_key_contact);
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[tokio::test(flavor = "multi_thread", worker_threads = 2)]
|
||||
async fn test_lookup_id_by_addr_recent() -> Result<()> {
|
||||
let accept_unencrypted_chat = true;
|
||||
test_lookup_id_by_addr_recent_ex(accept_unencrypted_chat).await
|
||||
}
|
||||
|
||||
#[tokio::test(flavor = "multi_thread", worker_threads = 2)]
|
||||
async fn test_lookup_id_by_addr_recent_accepted() -> Result<()> {
|
||||
let accept_unencrypted_chat = false;
|
||||
test_lookup_id_by_addr_recent_ex(accept_unencrypted_chat).await
|
||||
}
|
||||
|
||||
#[tokio::test(flavor = "multi_thread", worker_threads = 2)]
|
||||
async fn test_verified_by_none() -> Result<()> {
|
||||
let mut tcm = TestContextManager::new();
|
||||
@@ -1072,6 +1116,7 @@ async fn test_sync_create() -> Result<()> {
|
||||
.unwrap();
|
||||
let a1b_contact = Contact::get_by_id(alice1, a1b_contact_id).await?;
|
||||
assert_eq!(a1b_contact.name, "Bob");
|
||||
assert_eq!(a1b_contact.is_key_contact(), false);
|
||||
|
||||
Contact::create(alice0, "Bob Renamed", "bob@example.net").await?;
|
||||
test_utils::sync(alice0, alice1).await;
|
||||
@@ -1081,6 +1126,7 @@ async fn test_sync_create() -> Result<()> {
|
||||
assert_eq!(id, a1b_contact_id);
|
||||
let a1b_contact = Contact::get_by_id(alice1, a1b_contact_id).await?;
|
||||
assert_eq!(a1b_contact.name, "Bob Renamed");
|
||||
assert_eq!(a1b_contact.is_key_contact(), false);
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
@@ -277,6 +277,7 @@ pub(crate) async fn stock_ephemeral_timer_changed(
|
||||
.await
|
||||
}
|
||||
604_800 => stock_str::msg_ephemeral_timer_week(context, from_id).await,
|
||||
31_536_000..=31_708_800 => stock_str::msg_ephemeral_timer_year(context, from_id).await,
|
||||
_ => {
|
||||
stock_str::msg_ephemeral_timer_weeks(
|
||||
context,
|
||||
|
||||
@@ -128,31 +128,33 @@ async fn test_stock_ephemeral_messages() {
|
||||
/// Test enabling and disabling ephemeral timer remotely.
|
||||
#[tokio::test(flavor = "multi_thread", worker_threads = 2)]
|
||||
async fn test_ephemeral_enable_disable() -> Result<()> {
|
||||
let alice = TestContext::new_alice().await;
|
||||
let bob = TestContext::new_bob().await;
|
||||
let mut tcm = TestContextManager::new();
|
||||
let alice = &tcm.alice().await;
|
||||
let bob = &tcm.bob().await;
|
||||
|
||||
let chat_alice = alice.create_chat(&bob).await.id;
|
||||
let chat_bob = bob.create_chat(&alice).await.id;
|
||||
let chat_alice = alice.create_chat(bob).await.id;
|
||||
let chat_bob = bob.create_chat(alice).await.id;
|
||||
|
||||
chat_alice
|
||||
.set_ephemeral_timer(&alice.ctx, Timer::Enabled { duration: 60 })
|
||||
.set_ephemeral_timer(alice, Timer::Enabled { duration: 60 })
|
||||
.await?;
|
||||
let sent = alice.pop_sent_msg().await;
|
||||
bob.recv_msg(&sent).await;
|
||||
let bob_received_message = bob.recv_msg(&sent).await;
|
||||
assert_eq!(
|
||||
chat_bob.get_ephemeral_timer(&bob.ctx).await?,
|
||||
bob_received_message.text,
|
||||
"Message deletion timer is set to 1 minute by alice@example.org."
|
||||
);
|
||||
assert_eq!(
|
||||
chat_bob.get_ephemeral_timer(bob).await?,
|
||||
Timer::Enabled { duration: 60 }
|
||||
);
|
||||
|
||||
chat_alice
|
||||
.set_ephemeral_timer(&alice.ctx, Timer::Disabled)
|
||||
.set_ephemeral_timer(alice, Timer::Disabled)
|
||||
.await?;
|
||||
let sent = alice.pop_sent_msg().await;
|
||||
bob.recv_msg(&sent).await;
|
||||
assert_eq!(
|
||||
chat_bob.get_ephemeral_timer(&bob.ctx).await?,
|
||||
Timer::Disabled
|
||||
);
|
||||
assert_eq!(chat_bob.get_ephemeral_timer(bob).await?, Timer::Disabled);
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
@@ -250,7 +250,7 @@ impl BackupProvider {
|
||||
Err(format_err!("Backup provider dropped"))
|
||||
}
|
||||
).await {
|
||||
warn!(context, "Error while handling backup connection: {err:#}.");
|
||||
error!(context, "Error while handling backup connection: {err:#}.");
|
||||
context.emit_event(EventType::ImexProgress(0));
|
||||
break;
|
||||
} else {
|
||||
@@ -367,7 +367,8 @@ pub async fn get_backup(context: &Context, qr: Qr) -> Result<()> {
|
||||
Err(format_err!("Backup reception cancelled"))
|
||||
})
|
||||
.await;
|
||||
if res.is_err() {
|
||||
if let Err(ref res) = res {
|
||||
error!(context, "{:#}", res);
|
||||
context.emit_event(EventType::ImexProgress(0));
|
||||
}
|
||||
context.free_ongoing().await;
|
||||
|
||||
+40
-3
@@ -2,6 +2,7 @@
|
||||
|
||||
use std::collections::BTreeSet;
|
||||
use std::collections::HashSet;
|
||||
use std::collections::VecDeque;
|
||||
use std::path::{Path, PathBuf};
|
||||
use std::str;
|
||||
|
||||
@@ -1713,6 +1714,35 @@ pub(crate) async fn delete_msg_locally(context: &Context, msg: &Message) -> Resu
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub(crate) async fn get_webxdc_info_messages(
|
||||
context: &Context,
|
||||
msg: &Message,
|
||||
) -> Result<Vec<MsgId>> {
|
||||
let msg_ids = context
|
||||
.sql
|
||||
.query_map(
|
||||
r#"SELECT id, param
|
||||
FROM msgs
|
||||
WHERE chat_id=?1 AND hidden=0 AND mime_in_reply_to = ?2
|
||||
"#,
|
||||
(msg.chat_id, &msg.rfc724_mid),
|
||||
|row| {
|
||||
let info_msg_id: MsgId = row.get(0)?;
|
||||
let last_param: Params = row.get::<_, String>(1)?.parse().unwrap_or_default();
|
||||
Ok((info_msg_id, last_param))
|
||||
},
|
||||
|row| {
|
||||
Ok(row
|
||||
.filter_map(Result::ok)
|
||||
.filter(|(_, param)| param.get_cmd() == SystemMessage::WebxdcInfoMessage)
|
||||
.map(|(msg_id, _)| msg_id)
|
||||
.collect::<Vec<_>>())
|
||||
},
|
||||
)
|
||||
.await?;
|
||||
Ok(msg_ids)
|
||||
}
|
||||
|
||||
/// Do final events and jobs after batch deletion using calls to delete_msg_locally().
|
||||
/// To avoid additional database queries, collecting data is up to the caller.
|
||||
pub(crate) async fn delete_msgs_locally_done(
|
||||
@@ -1751,8 +1781,10 @@ pub async fn delete_msgs_ex(
|
||||
let mut modified_chat_ids = HashSet::new();
|
||||
let mut deleted_rfc724_mid = Vec::new();
|
||||
let mut res = Ok(());
|
||||
let mut msg_ids_queue = VecDeque::from_iter(msg_ids.iter().copied());
|
||||
let mut msg_ids = Vec::from(msg_ids);
|
||||
|
||||
for &msg_id in msg_ids {
|
||||
while let Some(msg_id) = msg_ids_queue.pop_front() {
|
||||
let msg = Message::load_from_db(context, msg_id).await?;
|
||||
ensure!(
|
||||
!delete_for_all || msg.from_id == ContactId::SELF,
|
||||
@@ -1763,6 +1795,11 @@ pub async fn delete_msgs_ex(
|
||||
"Cannot request deletion of unencrypted message for others"
|
||||
);
|
||||
|
||||
if msg.viewtype == Viewtype::Webxdc {
|
||||
let info_msgs = get_webxdc_info_messages(context, &msg).await?;
|
||||
msg_ids_queue.extend(&info_msgs);
|
||||
msg_ids.extend(info_msgs);
|
||||
}
|
||||
modified_chat_ids.insert(msg.chat_id);
|
||||
deleted_rfc724_mid.push(msg.rfc724_mid.clone());
|
||||
|
||||
@@ -1808,11 +1845,11 @@ pub async fn delete_msgs_ex(
|
||||
.await?;
|
||||
}
|
||||
|
||||
for &msg_id in msg_ids {
|
||||
for &msg_id in &msg_ids {
|
||||
let msg = Message::load_from_db(context, msg_id).await?;
|
||||
delete_msg_locally(context, &msg).await?;
|
||||
}
|
||||
delete_msgs_locally_done(context, msg_ids, modified_chat_ids).await?;
|
||||
delete_msgs_locally_done(context, &msg_ids, modified_chat_ids).await?;
|
||||
|
||||
// Interrupt Inbox loop to start message deletion, run housekeeping and call send_sync_msg().
|
||||
context.scheduler.interrupt_inbox().await;
|
||||
|
||||
@@ -808,3 +808,22 @@ async fn test_sanitize_filename_message() -> Result<()> {
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Tests that empty file can be sent and received.
|
||||
#[tokio::test(flavor = "multi_thread", worker_threads = 2)]
|
||||
async fn test_send_empty_file() -> Result<()> {
|
||||
let mut tcm = TestContextManager::new();
|
||||
let alice = &tcm.alice().await;
|
||||
let bob = &tcm.bob().await;
|
||||
|
||||
let alice_chat = alice.create_chat(bob).await;
|
||||
let mut msg = Message::new(Viewtype::File);
|
||||
msg.set_file_from_bytes(alice, "myfile", b"", None)?;
|
||||
chat::send_msg(alice, alice_chat.id, &mut msg).await?;
|
||||
let sent = alice.pop_sent_msg().await;
|
||||
|
||||
let bob_received_msg = bob.recv_msg(&sent).await;
|
||||
assert_eq!(bob_received_msg.get_filename().unwrap(), "myfile");
|
||||
assert_eq!(bob_received_msg.get_viewtype(), Viewtype::File);
|
||||
Ok(())
|
||||
}
|
||||
|
||||
@@ -683,6 +683,7 @@ async fn test_selfavatar_unencrypted_signed() {
|
||||
.unwrap()
|
||||
.unwrap();
|
||||
let alice_contact = Contact::get_by_id(&bob.ctx, alice_id).await.unwrap();
|
||||
assert_eq!(alice_contact.is_key_contact(), false);
|
||||
assert!(
|
||||
alice_contact
|
||||
.get_profile_image(&bob.ctx)
|
||||
|
||||
@@ -1322,10 +1322,6 @@ impl MimeMessage {
|
||||
filename: &str,
|
||||
is_related: bool,
|
||||
) -> Result<()> {
|
||||
if decoded_data.is_empty() {
|
||||
return Ok(());
|
||||
}
|
||||
|
||||
// Process attached PGP keys.
|
||||
if mime_type.type_() == mime::APPLICATION
|
||||
&& mime_type.subtype().as_str() == "pgp-keys"
|
||||
|
||||
+3
-3
@@ -244,7 +244,7 @@ async fn fetch_url(context: &Context, original_url: &str) -> Result<Response> {
|
||||
.clone();
|
||||
|
||||
let req = hyper::Request::builder()
|
||||
.uri(parsed_url.path())
|
||||
.uri(parsed_url)
|
||||
.header(hyper::header::HOST, authority.as_str())
|
||||
.body(http_body_util::Empty::<Bytes>::new())?;
|
||||
let response = sender.send_request(req).await?;
|
||||
@@ -378,7 +378,7 @@ pub(crate) async fn post_string(context: &Context, url: &str, body: String) -> R
|
||||
.context("URL has no authority")?
|
||||
.clone();
|
||||
|
||||
let request = hyper::Request::post(parsed_url.path())
|
||||
let request = hyper::Request::post(parsed_url)
|
||||
.header(hyper::header::HOST, authority.as_str())
|
||||
.body(body)?;
|
||||
let response = sender.send_request(request).await?;
|
||||
@@ -408,7 +408,7 @@ pub(crate) async fn post_form<T: Serialize + ?Sized>(
|
||||
.authority()
|
||||
.context("URL has no authority")?
|
||||
.clone();
|
||||
let request = hyper::Request::post(parsed_url.path())
|
||||
let request = hyper::Request::post(parsed_url)
|
||||
.header(hyper::header::HOST, authority.as_str())
|
||||
.header("content-type", "application/x-www-form-urlencoded")
|
||||
.body(encoded_body)?;
|
||||
|
||||
+67
-21
@@ -18,7 +18,7 @@ use crate::chat::{
|
||||
self, Chat, ChatId, ChatIdBlocked, ProtectionStatus, remove_from_chat_contacts_table,
|
||||
};
|
||||
use crate::config::Config;
|
||||
use crate::constants::{Blocked, Chattype, DC_CHAT_ID_TRASH, EDITED_PREFIX, ShowEmails};
|
||||
use crate::constants::{self, Blocked, Chattype, DC_CHAT_ID_TRASH, EDITED_PREFIX, ShowEmails};
|
||||
use crate::contact::{Contact, ContactId, Origin, mark_contact_id_as_verified};
|
||||
use crate::context::Context;
|
||||
use crate::debug_logging::maybe_set_logging_xdc_inner;
|
||||
@@ -33,7 +33,8 @@ use crate::log::LogExt;
|
||||
use crate::log::{info, warn};
|
||||
use crate::logged_debug_assert;
|
||||
use crate::message::{
|
||||
self, Message, MessageState, MessengerMessage, MsgId, Viewtype, rfc724_mid_exists,
|
||||
self, Message, MessageState, MessengerMessage, MsgId, Viewtype, get_webxdc_info_messages,
|
||||
rfc724_mid_exists,
|
||||
};
|
||||
use crate::mimeparser::{AvatarAction, MimeMessage, SystemMessage, parse_message_ids};
|
||||
use crate::param::{Param, Params};
|
||||
@@ -297,18 +298,16 @@ async fn get_to_and_past_contact_ids(
|
||||
past_member_fingerprints = &[];
|
||||
}
|
||||
|
||||
let pgp_to_ids = add_or_lookup_key_contacts_by_address_list(
|
||||
context,
|
||||
&mime_parser.recipients,
|
||||
&mime_parser.gossiped_keys,
|
||||
to_member_fingerprints,
|
||||
Origin::Hidden,
|
||||
)
|
||||
.await?;
|
||||
|
||||
match chat_assignment {
|
||||
ChatAssignment::GroupChat { .. } => {
|
||||
to_ids = pgp_to_ids;
|
||||
to_ids = add_or_lookup_key_contacts(
|
||||
context,
|
||||
&mime_parser.recipients,
|
||||
&mime_parser.gossiped_keys,
|
||||
to_member_fingerprints,
|
||||
Origin::Hidden,
|
||||
)
|
||||
.await?;
|
||||
|
||||
if let Some(chat_id) = chat_id {
|
||||
past_ids = lookup_key_contacts_by_address_list(
|
||||
@@ -319,7 +318,7 @@ async fn get_to_and_past_contact_ids(
|
||||
)
|
||||
.await?;
|
||||
} else {
|
||||
past_ids = add_or_lookup_key_contacts_by_address_list(
|
||||
past_ids = add_or_lookup_key_contacts(
|
||||
context,
|
||||
&mime_parser.past_members,
|
||||
&mime_parser.gossiped_keys,
|
||||
@@ -336,7 +335,14 @@ async fn get_to_and_past_contact_ids(
|
||||
ChatAssignment::ExistingChat { chat_id, .. } => {
|
||||
let chat = Chat::load_from_db(context, *chat_id).await?;
|
||||
if chat.is_encrypted(context).await? {
|
||||
to_ids = pgp_to_ids;
|
||||
to_ids = add_or_lookup_key_contacts(
|
||||
context,
|
||||
&mime_parser.recipients,
|
||||
&mime_parser.gossiped_keys,
|
||||
to_member_fingerprints,
|
||||
Origin::Hidden,
|
||||
)
|
||||
.await?;
|
||||
past_ids = lookup_key_contacts_by_address_list(
|
||||
context,
|
||||
&mime_parser.past_members,
|
||||
@@ -388,6 +394,14 @@ async fn get_to_and_past_contact_ids(
|
||||
.await?;
|
||||
}
|
||||
ChatAssignment::OneOneChat => {
|
||||
let pgp_to_ids = add_or_lookup_key_contacts(
|
||||
context,
|
||||
&mime_parser.recipients,
|
||||
&mime_parser.gossiped_keys,
|
||||
to_member_fingerprints,
|
||||
Origin::Hidden,
|
||||
)
|
||||
.await?;
|
||||
if pgp_to_ids
|
||||
.first()
|
||||
.is_some_and(|contact_id| contact_id.is_some())
|
||||
@@ -429,7 +443,7 @@ async fn get_to_and_past_contact_ids(
|
||||
context,
|
||||
&mime_parser.recipients,
|
||||
to_member_fingerprints,
|
||||
chat_id,
|
||||
None,
|
||||
)
|
||||
.await?
|
||||
}
|
||||
@@ -1835,6 +1849,8 @@ async fn add_parts(
|
||||
{
|
||||
Some(stock_str::msg_location_enabled_by(context, from_id).await)
|
||||
} else if mime_parser.is_system_message == SystemMessage::EphemeralTimerChanged {
|
||||
let better_msg = stock_ephemeral_timer_changed(context, ephemeral_timer, from_id).await;
|
||||
|
||||
// Do not delete the system message itself.
|
||||
//
|
||||
// This prevents confusion when timer is changed
|
||||
@@ -1843,7 +1859,7 @@ async fn add_parts(
|
||||
// week is left.
|
||||
ephemeral_timer = EphemeralTimer::Disabled;
|
||||
|
||||
Some(stock_ephemeral_timer_changed(context, ephemeral_timer, from_id).await)
|
||||
Some(better_msg)
|
||||
} else {
|
||||
None
|
||||
};
|
||||
@@ -2280,12 +2296,18 @@ async fn handle_edit_delete(
|
||||
let mut modified_chat_ids = HashSet::new();
|
||||
let mut msg_ids = Vec::new();
|
||||
|
||||
let mut deleted_info_msgs = vec![];
|
||||
let rfc724_mid_vec: Vec<&str> = rfc724_mid_list.split_whitespace().collect();
|
||||
for rfc724_mid in rfc724_mid_vec {
|
||||
if let Some((msg_id, _)) =
|
||||
message::rfc724_mid_exists(context, rfc724_mid).await?
|
||||
{
|
||||
if let Some(msg) = Message::load_from_db_optional(context, msg_id).await? {
|
||||
if msg.viewtype == Viewtype::Webxdc {
|
||||
let info_msgs = get_webxdc_info_messages(context, &msg).await?;
|
||||
deleted_info_msgs.extend(&info_msgs);
|
||||
}
|
||||
|
||||
if msg.from_id == from_id {
|
||||
message::delete_msg_locally(context, &msg).await?;
|
||||
msg_ids.push(msg.id);
|
||||
@@ -2300,6 +2322,15 @@ async fn handle_edit_delete(
|
||||
warn!(context, "Delete message: {rfc724_mid:?} not found.");
|
||||
}
|
||||
}
|
||||
if !deleted_info_msgs.is_empty() {
|
||||
message::delete_msgs(context, &deleted_info_msgs).await?;
|
||||
for msg_id in deleted_info_msgs {
|
||||
if let Some(msg) = Message::load_from_db_optional(context, msg_id).await? {
|
||||
message::delete_msg_locally(context, &msg).await?;
|
||||
msg_ids.push(msg.id);
|
||||
}
|
||||
}
|
||||
}
|
||||
message::delete_msgs_locally_done(context, &msg_ids, modified_chat_ids).await?;
|
||||
} else {
|
||||
warn!(context, "Delete message: Not encrypted.");
|
||||
@@ -3727,7 +3758,7 @@ async fn add_or_lookup_contacts_by_address_list(
|
||||
}
|
||||
|
||||
/// Looks up contact IDs from the database given the list of recipients.
|
||||
async fn add_or_lookup_key_contacts_by_address_list(
|
||||
async fn add_or_lookup_key_contacts(
|
||||
context: &Context,
|
||||
address_list: &[SingleInfo],
|
||||
gossiped_keys: &HashMap<String, SignedPublicKey>,
|
||||
@@ -3778,7 +3809,7 @@ async fn add_or_lookup_key_contacts_by_address_list(
|
||||
/// Looks up a key-contact by email address.
|
||||
///
|
||||
/// If provided, `chat_id` must be an encrypted chat ID that has key-contacts inside.
|
||||
/// Otherwise the function searches in all contacts, returning the recently seen one.
|
||||
/// Otherwise the function searches in all contacts, preferring accepted and most recently seen ones.
|
||||
async fn lookup_key_contact_by_address(
|
||||
context: &Context,
|
||||
addr: &str,
|
||||
@@ -3821,11 +3852,26 @@ async fn lookup_key_contact_by_address(
|
||||
.sql
|
||||
.query_row_optional(
|
||||
"SELECT id FROM contacts
|
||||
WHERE contacts.addr=?1
|
||||
WHERE addr=?
|
||||
AND fingerprint<>''
|
||||
ORDER BY last_seen DESC, id DESC
|
||||
ORDER BY
|
||||
(
|
||||
SELECT COUNT(*) FROM chats c
|
||||
INNER JOIN chats_contacts cc
|
||||
ON c.id=cc.chat_id
|
||||
WHERE c.type=?
|
||||
AND c.id>?
|
||||
AND c.blocked=?
|
||||
AND cc.contact_id=contacts.id
|
||||
) DESC,
|
||||
last_seen DESC, id DESC
|
||||
",
|
||||
(addr,),
|
||||
(
|
||||
addr,
|
||||
Chattype::Single,
|
||||
constants::DC_CHAT_ID_LAST_SPECIAL,
|
||||
Blocked::Not,
|
||||
),
|
||||
|row| {
|
||||
let contact_id: ContactId = row.get(0)?;
|
||||
Ok(contact_id)
|
||||
|
||||
@@ -991,6 +991,7 @@ async fn test_other_device_writes_to_mailinglist() -> Result<()> {
|
||||
.await?
|
||||
.unwrap();
|
||||
let list_post_contact = Contact::get_by_id(&t, list_post_contact_id).await?;
|
||||
assert_eq!(list_post_contact.is_key_contact(), false);
|
||||
assert_eq!(
|
||||
list_post_contact.param.get(Param::ListId).unwrap(),
|
||||
"delta.codespeak.net"
|
||||
@@ -1453,6 +1454,7 @@ async fn test_apply_mailinglist_changes_assigned_by_reply() {
|
||||
.unwrap()
|
||||
.unwrap();
|
||||
let contact = Contact::get_by_id(&t, contact_id).await.unwrap();
|
||||
assert_eq!(contact.is_key_contact(), false);
|
||||
assert_eq!(
|
||||
contact.param.get(Param::ListId).unwrap(),
|
||||
"deltachat-core-rust.deltachat.github.com"
|
||||
@@ -3682,10 +3684,13 @@ async fn test_mua_user_adds_recipient_to_single_chat() -> Result<()> {
|
||||
chat::get_chat_contacts(&alice, group_chat.id).await?.len(),
|
||||
4
|
||||
);
|
||||
let fiona = Contact::lookup_id_by_addr(&alice, "fiona@example.net", Origin::IncomingTo)
|
||||
.await?
|
||||
.unwrap();
|
||||
assert!(chat::is_contact_in_chat(&alice, group_chat.id, fiona).await?);
|
||||
let fiona_contact_id =
|
||||
Contact::lookup_id_by_addr(&alice, "fiona@example.net", Origin::IncomingTo)
|
||||
.await?
|
||||
.unwrap();
|
||||
assert!(chat::is_contact_in_chat(&alice, group_chat.id, fiona_contact_id).await?);
|
||||
let fiona_contact = Contact::get_by_id(&alice, fiona_contact_id).await?;
|
||||
assert_eq!(fiona_contact.is_key_contact(), false);
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
+5
-1
@@ -414,7 +414,11 @@ pub(crate) async fn send_msg_to_smtp(
|
||||
.await?;
|
||||
}
|
||||
SendResult::Failure(ref err) => {
|
||||
if err.to_string().contains("Invalid unencrypted mail") {
|
||||
if err
|
||||
.to_string()
|
||||
.to_lowercase()
|
||||
.contains("invalid unencrypted mail")
|
||||
{
|
||||
let res = context
|
||||
.sql
|
||||
.query_row_optional(
|
||||
|
||||
+36
-1
@@ -13,6 +13,7 @@ use crate::config::Config;
|
||||
use crate::constants::DC_CHAT_ID_TRASH;
|
||||
use crate::context::Context;
|
||||
use crate::debug_logging::set_debug_logging_xdc;
|
||||
use crate::ensure_and_debug_assert;
|
||||
use crate::ephemeral::start_ephemeral_timers;
|
||||
use crate::imex::BLOBS_BACKUP_NAME;
|
||||
use crate::location::delete_orphaned_poi_locations;
|
||||
@@ -175,10 +176,12 @@ impl Sql {
|
||||
.await
|
||||
}
|
||||
|
||||
const N_DB_CONNECTIONS: usize = 3;
|
||||
|
||||
/// Creates a new connection pool.
|
||||
fn new_pool(dbfile: &Path, passphrase: String) -> Result<Pool> {
|
||||
let mut connections = Vec::new();
|
||||
for _ in 0..3 {
|
||||
for _ in 0..Self::N_DB_CONNECTIONS {
|
||||
let connection = new_connection(dbfile, &passphrase)?;
|
||||
connections.push(connection);
|
||||
}
|
||||
@@ -637,6 +640,31 @@ impl Sql {
|
||||
pub fn config_cache(&self) -> &RwLock<HashMap<String, Option<String>>> {
|
||||
&self.config_cache
|
||||
}
|
||||
|
||||
/// Runs a checkpoint operation in TRUNCATE mode, so the WAL file is truncated to 0 bytes.
|
||||
pub(crate) async fn wal_checkpoint(&self) -> Result<()> {
|
||||
let lock = self.pool.read().await;
|
||||
let pool = lock.as_ref().context("No SQL connection pool")?;
|
||||
let mut conns = Vec::new();
|
||||
let query_only = true;
|
||||
// Kick out readers to avoid blocking/SQLITE_BUSY.
|
||||
for _ in 0..(Self::N_DB_CONNECTIONS - 1) {
|
||||
conns.push(pool.get(query_only).await?);
|
||||
}
|
||||
let conn = pool.get(query_only).await?;
|
||||
tokio::task::block_in_place(move || {
|
||||
// Execute some transaction causing the WAL file to be opened so that the
|
||||
// `wal_checkpoint()` can proceed, otherwise it fails when called the first time, see
|
||||
// https://sqlite.org/forum/forumpost/7512d76a05268fc8.
|
||||
conn.query_row("PRAGMA table_list", [], |_row| Ok(()))?;
|
||||
let blocked = conn.query_row("PRAGMA wal_checkpoint(TRUNCATE)", [], |row| {
|
||||
let blocked: i64 = row.get(0)?;
|
||||
Ok(blocked)
|
||||
})?;
|
||||
ensure_and_debug_assert!(blocked == 0,);
|
||||
Ok(())
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
/// Creates a new SQLite connection.
|
||||
@@ -760,6 +788,13 @@ pub async fn housekeeping(context: &Context) -> Result<()> {
|
||||
if let Err(err) = incremental_vacuum(context).await {
|
||||
warn!(context, "Failed to run incremental vacuum: {err:#}.");
|
||||
}
|
||||
// Work around possible checkpoint starvations (there were cases reported when a WAL file is
|
||||
// bigger than 200M) and also make sure we truncate the WAL periodically. Auto-checkponting does
|
||||
// not normally truncate the WAL (unless the `journal_size_limit` pragma is set), see
|
||||
// https://www.sqlite.org/wal.html.
|
||||
if let Err(err) = context.sql.wal_checkpoint().await {
|
||||
warn!(context, "wal_checkpoint() failed: {err:#}.");
|
||||
}
|
||||
|
||||
context
|
||||
.sql
|
||||
|
||||
@@ -2,6 +2,7 @@ use super::*;
|
||||
use crate::chat;
|
||||
use crate::chat::ChatId;
|
||||
use crate::config::Config;
|
||||
use crate::constants;
|
||||
use crate::contact::Contact;
|
||||
use crate::contact::ContactId;
|
||||
use crate::contact::Origin;
|
||||
@@ -43,18 +44,11 @@ async fn test_key_contacts_migration_autocrypt() -> Result<()> {
|
||||
t.sql.run_migrations(&t).await?;
|
||||
|
||||
//std::thread::sleep(std::time::Duration::from_secs(1000));
|
||||
let email_bob_id = Contact::lookup_id_by_addr(&t, "bob@example.net", Origin::Hidden)
|
||||
let pgp_bob_id = Contact::lookup_id_by_addr(&t, "bob@example.net", Origin::Hidden)
|
||||
.await?
|
||||
.unwrap();
|
||||
let email_bob = Contact::get_by_id(&t, email_bob_id).await?;
|
||||
assert_eq!(email_bob.origin, Origin::Hidden); // Email bob is in no chats, so, contact is hidden
|
||||
assert_eq!(email_bob.e2ee_avail(&t).await?, false);
|
||||
assert_eq!(email_bob.fingerprint(), None);
|
||||
assert_eq!(email_bob.get_verifier_id(&t).await?, None);
|
||||
|
||||
let bob_chat_contacts = chat::get_chat_contacts(&t, ChatId::new(10)).await?;
|
||||
let pgp_bob_id = tools::single_value(bob_chat_contacts).unwrap();
|
||||
let pgp_bob = Contact::get_by_id(&t, pgp_bob_id).await?;
|
||||
assert_eq!(pgp_bob.is_key_contact(), true);
|
||||
assert_eq!(pgp_bob.origin, Origin::OutgoingTo);
|
||||
assert_eq!(pgp_bob.e2ee_avail(&t).await?, true);
|
||||
assert_eq!(
|
||||
@@ -63,6 +57,16 @@ async fn test_key_contacts_migration_autocrypt() -> Result<()> {
|
||||
);
|
||||
assert_eq!(pgp_bob.get_verifier_id(&t).await?, None);
|
||||
|
||||
// Hidden address-contact can't be looked up.
|
||||
assert!(
|
||||
Contact::get_all(&t, constants::DC_GCL_ADDRESS, Some("bob@example.net"))
|
||||
.await?
|
||||
.is_empty()
|
||||
);
|
||||
|
||||
let bob_chat_contacts = chat::get_chat_contacts(&t, ChatId::new(10)).await?;
|
||||
assert_eq!(tools::single_value(bob_chat_contacts).unwrap(), pgp_bob_id);
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
@@ -81,10 +85,12 @@ async fn test_key_contacts_migration_email1() -> Result<()> {
|
||||
)?)).await?;
|
||||
t.sql.run_migrations(&t).await?;
|
||||
|
||||
let email_bob_id = Contact::lookup_id_by_addr(&t, "bob@example.net", Origin::Hidden)
|
||||
let email_bob_id = *Contact::get_all(&t, constants::DC_GCL_ADDRESS, Some("bob@example.net"))
|
||||
.await?
|
||||
.first()
|
||||
.unwrap();
|
||||
let email_bob = Contact::get_by_id(&t, email_bob_id).await?;
|
||||
assert_eq!(email_bob.is_key_contact(), false);
|
||||
assert_eq!(email_bob.origin, Origin::OutgoingTo);
|
||||
assert_eq!(email_bob.e2ee_avail(&t).await?, false);
|
||||
assert_eq!(email_bob.fingerprint(), None);
|
||||
@@ -110,11 +116,23 @@ async fn test_key_contacts_migration_email2() -> Result<()> {
|
||||
)?)).await?;
|
||||
t.sql.run_migrations(&t).await?;
|
||||
|
||||
let email_bob_id = Contact::lookup_id_by_addr(&t, "bob@example.net", Origin::Hidden)
|
||||
// Hidden key-contact can't be looked up.
|
||||
assert!(
|
||||
Contact::get_all(&t, 0, Some("bob@example.net"))
|
||||
.await?
|
||||
.is_empty()
|
||||
);
|
||||
let pgp_bob = Contact::get_by_id(&t, ContactId::new(11)).await?;
|
||||
assert_eq!(pgp_bob.is_key_contact(), true);
|
||||
assert_eq!(pgp_bob.origin, Origin::Hidden);
|
||||
|
||||
let email_bob_id = *Contact::get_all(&t, constants::DC_GCL_ADDRESS, Some("bob@example.net"))
|
||||
.await?
|
||||
.first()
|
||||
.unwrap();
|
||||
let email_bob = Contact::get_by_id(&t, email_bob_id).await?;
|
||||
assert_eq!(email_bob.origin, Origin::OutgoingTo); // Email bob is in no chats, so, contact is hidden
|
||||
assert_eq!(email_bob.is_key_contact(), false);
|
||||
assert_eq!(email_bob.origin, Origin::OutgoingTo);
|
||||
assert_eq!(email_bob.e2ee_avail(&t).await?, false);
|
||||
assert_eq!(email_bob.fingerprint(), None);
|
||||
assert_eq!(email_bob.get_verifier_id(&t).await?, None);
|
||||
@@ -143,15 +161,12 @@ async fn test_key_contacts_migration_verified() -> Result<()> {
|
||||
)?)).await?;
|
||||
t.sql.run_migrations(&t).await?;
|
||||
|
||||
let email_bob_id = Contact::lookup_id_by_addr(&t, "bob@example.net", Origin::Hidden)
|
||||
.await?
|
||||
.unwrap();
|
||||
let email_bob = Contact::get_by_id(&t, email_bob_id).await?;
|
||||
dbg!(&email_bob);
|
||||
assert_eq!(email_bob.origin, Origin::Hidden); // Email bob is in no chats, so, contact is hidden
|
||||
assert_eq!(email_bob.e2ee_avail(&t).await?, false);
|
||||
assert_eq!(email_bob.fingerprint(), None);
|
||||
assert_eq!(email_bob.get_verifier_id(&t).await?, None);
|
||||
// Hidden address-contact can't be looked up.
|
||||
assert!(
|
||||
Contact::get_all(&t, constants::DC_GCL_ADDRESS, Some("bob@example.net"))
|
||||
.await?
|
||||
.is_empty()
|
||||
);
|
||||
|
||||
let mut bob_chat_contacts = chat::get_chat_contacts(&t, ChatId::new(10)).await?;
|
||||
assert_eq!(bob_chat_contacts.len(), 2);
|
||||
|
||||
@@ -362,6 +362,12 @@ pub enum StockMessage {
|
||||
#[strum(props(fallback = "Message deletion timer is set to %1$s weeks by %2$s."))]
|
||||
MsgEphemeralTimerWeeksBy = 157,
|
||||
|
||||
#[strum(props(fallback = "You set message deletion timer to 1 year."))]
|
||||
MsgYouEphemeralTimerYear = 158,
|
||||
|
||||
#[strum(props(fallback = "Message deletion timer is set to 1 year by %1$s."))]
|
||||
MsgEphemeralTimerYearBy = 159,
|
||||
|
||||
#[strum(props(fallback = "Scan to set up second device for %1$s"))]
|
||||
BackupTransferQr = 162,
|
||||
|
||||
@@ -992,6 +998,17 @@ pub(crate) async fn msg_ephemeral_timer_week(context: &Context, by_contact: Cont
|
||||
}
|
||||
}
|
||||
|
||||
/// Stock string: `Message deletion timer is set to 1 year.`.
|
||||
pub(crate) async fn msg_ephemeral_timer_year(context: &Context, by_contact: ContactId) -> String {
|
||||
if by_contact == ContactId::SELF {
|
||||
translated(context, StockMessage::MsgYouEphemeralTimerYear).await
|
||||
} else {
|
||||
translated(context, StockMessage::MsgEphemeralTimerYearBy)
|
||||
.await
|
||||
.replace1(&by_contact.get_stock_name(context).await)
|
||||
}
|
||||
}
|
||||
|
||||
/// Stock string: `Video chat invitation`.
|
||||
pub(crate) async fn videochat_invitation(context: &Context) -> String {
|
||||
translated(context, StockMessage::VideochatInvitation).await
|
||||
|
||||
+15
-1
@@ -11,7 +11,7 @@ use crate::contact::ContactId;
|
||||
use crate::context::Context;
|
||||
use crate::log::LogExt;
|
||||
use crate::log::{info, warn};
|
||||
use crate::message::{Message, MsgId, Viewtype};
|
||||
use crate::message::{Message, MsgId, Viewtype, get_webxdc_info_messages};
|
||||
use crate::mimeparser::SystemMessage;
|
||||
use crate::param::Param;
|
||||
use crate::sync::SyncData::{AddQrToken, AlterChat, DeleteQrToken};
|
||||
@@ -306,10 +306,15 @@ impl Context {
|
||||
|
||||
async fn sync_message_deletion(&self, msgs: &Vec<String>) -> Result<()> {
|
||||
let mut modified_chat_ids = HashSet::new();
|
||||
let mut deleted_info_msgs = vec![];
|
||||
let mut msg_ids = Vec::new();
|
||||
for rfc724_mid in msgs {
|
||||
if let Some((msg_id, _)) = message::rfc724_mid_exists(self, rfc724_mid).await? {
|
||||
if let Some(msg) = Message::load_from_db_optional(self, msg_id).await? {
|
||||
if msg.viewtype == Viewtype::Webxdc {
|
||||
let info_msgs = get_webxdc_info_messages(&self, &msg).await?;
|
||||
deleted_info_msgs.extend(&info_msgs);
|
||||
}
|
||||
message::delete_msg_locally(self, &msg).await?;
|
||||
msg_ids.push(msg.id);
|
||||
modified_chat_ids.insert(msg.chat_id);
|
||||
@@ -320,6 +325,15 @@ impl Context {
|
||||
warn!(self, "Sync message delete: {rfc724_mid:?} not found.");
|
||||
}
|
||||
}
|
||||
if !deleted_info_msgs.is_empty() {
|
||||
message::delete_msgs(&self, &deleted_info_msgs).await?;
|
||||
for msg_id in deleted_info_msgs {
|
||||
if let Some(msg) = Message::load_from_db_optional(&self, msg_id).await? {
|
||||
message::delete_msg_locally(&self, &msg).await?;
|
||||
msg_ids.push(msg.id);
|
||||
}
|
||||
}
|
||||
}
|
||||
message::delete_msgs_locally_done(self, &msg_ids, modified_chat_ids).await?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
@@ -12,6 +12,7 @@ use crate::chatlist::Chatlist;
|
||||
use crate::config::Config;
|
||||
use crate::download::DownloadState;
|
||||
use crate::ephemeral;
|
||||
use crate::message::delete_msgs;
|
||||
use crate::receive_imf::{receive_imf, receive_imf_from_inbox};
|
||||
use crate::test_utils::{E2EE_INFO_MSGS, TestContext, TestContextManager};
|
||||
use crate::tools::{self, SystemTime};
|
||||
@@ -1616,6 +1617,26 @@ async fn test_webxdc_info_msg_no_cleanup_on_interrupted_series() -> Result<()> {
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[tokio::test(flavor = "multi_thread", worker_threads = 2)]
|
||||
async fn test_webxdc_info_msg_delete() -> Result<()> {
|
||||
let t = TestContext::new_alice().await;
|
||||
let chat_id = create_group_chat(&t, ProtectionStatus::Unprotected, "c").await?;
|
||||
let instance = send_webxdc_instance(&t, chat_id).await?;
|
||||
|
||||
t.send_webxdc_status_update(instance.id, r#"{"info":"i1", "payload":1}"#)
|
||||
.await?;
|
||||
assert_eq!(chat_id.get_msg_cnt(&t).await?, 2);
|
||||
send_text_msg(&t, chat_id, "msg between info".to_string()).await?;
|
||||
assert_eq!(chat_id.get_msg_cnt(&t).await?, 3);
|
||||
t.send_webxdc_status_update(instance.id, r#"{"info":"i2", "payload":2}"#)
|
||||
.await?;
|
||||
assert_eq!(chat_id.get_msg_cnt(&t).await?, 4);
|
||||
delete_msgs(&t, &[instance.id]).await?;
|
||||
assert_eq!(chat_id.get_msg_cnt(&t).await?, 1);
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
// check that `info.internet_access` is not set for normal, non-integrated webxdc -
|
||||
// even if they use the deprecated option `request_internet_access` in manifest.toml
|
||||
#[tokio::test(flavor = "multi_thread", worker_threads = 2)]
|
||||
|
||||
Reference in New Issue
Block a user