Compare commits

..
Author SHA1 Message Date
Sebastian Klähn 7436368b50 Delete webxdc info messages in sync 2025-08-09 17:23:12 +02:00
Sebastian Klähn c9fdfc32f2 Delete info messages on delete requets
When receiving a delete request for webxdcs, delete the associated info
messages
2025-08-09 17:22:52 +02:00
Septias 0ce2f35909 clippy 2025-08-09 16:42:38 +02:00
Septias 5096a43831 remove clone 2025-08-09 16:42:38 +02:00
Septias 2db390b25a fixes 2025-08-09 16:42:38 +02:00
Sebastian Klähn 028b0211c9 add failing test 2025-08-09 16:42:38 +02:00
Sebastian Klähn 3a03c9993c Extend delete queue with webxdc info messages
This allocates a vector of size #messagesToDelete which should be no
problem.
2025-08-09 16:42:38 +02:00
bjoern 6c536f3a9b fix: log and set imex progress error (#7091)
IMEX_PROGRESS(0) event is fired in case of errors, however, the last
error was not set in this case.

this is similar to the fix at #4195
and improves the error shown in the dialog for android and iOS; desktop
does not show an error dialog at all.

<img width="320" alt="IMG_9995"
src="https://github.com/user-attachments/assets/7065fc3d-3f30-4691-b1b2-1950564a25e2"
/>

relates to https://github.com/deltachat/deltachat-android/issues/3533
2025-08-09 01:17:27 +02:00
link2xt 4b24b6a848 refactor: skip loading the contact of 1:1 unencrypted chat to show the avatar
If the chat is unencrypted, it gets unencrypted icon.
There is no need to load the address-contact to get the same icon.
2025-08-08 19:08:23 +00:00
link2xt 5f254a929f refactor: rename icon-address-contact to icon-unencrypted
The icon is mainly used to identify unencrypted chats
in the chatlist where encrypted and unencrypted chats are mixed.
It is used for group chats rather than only for 1:1 chats
with address-contacts.
2025-08-08 19:08:23 +00:00
B. Petersen 8df1a01ace feat: better string when using disappearing messages of one year (365..367 days, so it can be tweaked later) 2025-08-08 18:45:35 +00:00
link2xt 27b5ffb34f fix: do not remove query parameters from URLs
The fix is similar to the one done in
4ca0ce2fb2,
but now extended to requests other than empty POST requests as well.
2025-08-07 14:49:18 +00:00
link2xt 80af012962 fix: set correct sent_timestamp for saved outgoing messages 2025-08-07 07:58:53 +00:00
iequidoo 615c80bef4 feat: Contact::lookup_id_by_addr_ex: Prefer returning accepted contacts
We don't want to prefer returning verified contacts because e.g. if a bot was reinstalled and its
key changed, it may not be verified, and we don't want to bring the user to the old chat if they
click on the bot email address. But trying to return accepted contacts increases security and
doesn't break the described scenario.
2025-08-06 17:37:17 -03:00
iequidoo f5f4026dbb feat: Contact::lookup_id_by_addr_ex: Prefer returning key-contact
If an address-contact and a key-contact were seen at exactly the same time, that doesn't necessarily
mean that it's a random event, it might occur because some code updates contacts this way in some
scenario. While this is unlikely, prefer to look up the key-contact.
2025-08-06 17:37:17 -03:00
link2xt b431206ede fix: allow receiving empty files 2025-08-06 13:33:04 +00:00
iequidoo c4878e9b49 fix: Run wal_checkpoint during housekeeping (#6089)
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.
2025-08-05 14:58:32 -03:00
B. Petersen aa452971a6 fix: ignore case when trying to detect 'invalid unencrypted mail' and add an info-message 2025-08-05 15:58:43 +02:00
dependabot[bot] 2d798f7cfe Merge pull request #7066 from chatmail/dependabot/cargo/toml-0.9.4 2025-08-04 23:53:05 +00:00
link2xt 08bb0484eb chore(release): prepare for 2.10.0 2025-08-04 22:33:59 +00:00
link2xt b0b7337f5a chore: upgrade async-imap to 0.11.1 2025-08-04 21:42:28 +00:00
Hocuri 93241a4beb feat: Also lookup key contacts in lookup_id_by_addr() (#7073)
If there is both a key and an address contact, return the most recently
seen one.
2025-08-04 21:32:09 +02:00
iequidoo 4f1bf1f13c chore(deny.toml): add exception for duplicate toml_datetime 0.6.11 dependency 2025-08-02 16:16:43 -03:00
iequidoo 2d0b7b5bd8 chore(cargo): bump human-panic from 2.0.2 to 2.0.3 2025-08-02 16:14:48 -03:00
dependabot[bot] 8fe3ce5cab chore(cargo): bump strum_macros from 0.27.1 to 0.27.2
Bumps [strum_macros](https://github.com/Peternator7/strum) from 0.27.1 to 0.27.2.
- [Release notes](https://github.com/Peternator7/strum/releases)
- [Changelog](https://github.com/Peternator7/strum/blob/master/CHANGELOG.md)
- [Commits](https://github.com/Peternator7/strum/compare/v0.27.1...v0.27.2)

---
updated-dependencies:
- dependency-name: strum_macros
  dependency-version: 0.27.2
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
2025-08-02 14:15:55 -03:00
dependabot[bot] 59a0f1d94f chore(cargo): bump strum from 0.27.1 to 0.27.2
Bumps [strum](https://github.com/Peternator7/strum) from 0.27.1 to 0.27.2.
- [Release notes](https://github.com/Peternator7/strum/releases)
- [Changelog](https://github.com/Peternator7/strum/blob/master/CHANGELOG.md)
- [Commits](https://github.com/Peternator7/strum/compare/v0.27.1...v0.27.2)

---
updated-dependencies:
- dependency-name: strum
  dependency-version: 0.27.2
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
2025-08-02 13:55:09 -03:00
dependabot[bot] 5175dc3450 chore(cargo): bump criterion from 0.6.0 to 0.7.0
Bumps [criterion](https://github.com/bheisler/criterion.rs) from 0.6.0 to 0.7.0.
- [Changelog](https://github.com/bheisler/criterion.rs/blob/master/CHANGELOG.md)
- [Commits](https://github.com/bheisler/criterion.rs/compare/0.6.0...0.7.0)

---
updated-dependencies:
- dependency-name: criterion
  dependency-version: 0.7.0
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
2025-08-02 13:39:05 -03:00
dependabot[bot] 9a22ccd058 chore(cargo): bump hyper-util from 0.1.14 to 0.1.16
Bumps [hyper-util](https://github.com/hyperium/hyper-util) from 0.1.14 to 0.1.16.
- [Release notes](https://github.com/hyperium/hyper-util/releases)
- [Changelog](https://github.com/hyperium/hyper-util/blob/master/CHANGELOG.md)
- [Commits](https://github.com/hyperium/hyper-util/compare/v0.1.14...v0.1.16)

---
updated-dependencies:
- dependency-name: hyper-util
  dependency-version: 0.1.16
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
2025-08-02 13:38:26 -03:00
dependabot[bot] c06ed49a2a chore(cargo): bump async-channel from 2.3.1 to 2.5.0
Bumps [async-channel](https://github.com/smol-rs/async-channel) from 2.3.1 to 2.5.0.
- [Release notes](https://github.com/smol-rs/async-channel/releases)
- [Changelog](https://github.com/smol-rs/async-channel/blob/master/CHANGELOG.md)
- [Commits](https://github.com/smol-rs/async-channel/compare/v2.3.1...v2.5.0)

---
updated-dependencies:
- dependency-name: async-channel
  dependency-version: 2.5.0
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
2025-08-02 13:33:11 -03:00
dependabot[bot] 2e51a5a454 chore(cargo): bump bolero from 0.13.3 to 0.13.4
Bumps [bolero](https://github.com/camshaft/bolero) from 0.13.3 to 0.13.4.
- [Changelog](https://github.com/camshaft/bolero/blob/master/CHANGELOG.md)
- [Commits](https://github.com/camshaft/bolero/commits)

---
updated-dependencies:
- dependency-name: bolero
  dependency-version: 0.13.4
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
2025-08-02 12:59:56 -03:00
dependabot[bot] 75cc353528 chore(cargo): bump serde_json from 1.0.140 to 1.0.142
Bumps [serde_json](https://github.com/serde-rs/json) from 1.0.140 to 1.0.142.
- [Release notes](https://github.com/serde-rs/json/releases)
- [Commits](https://github.com/serde-rs/json/compare/v1.0.140...v1.0.142)

---
updated-dependencies:
- dependency-name: serde_json
  dependency-version: 1.0.142
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
2025-08-02 12:51:47 -03:00
dependabot[bot] 3977580426 chore(cargo): bump toml from 0.8.23 to 0.9.4
Bumps [toml](https://github.com/toml-rs/toml) from 0.8.23 to 0.9.4.
- [Commits](https://github.com/toml-rs/toml/compare/toml-v0.8.23...toml-v0.9.4)

---
updated-dependencies:
- dependency-name: toml
  dependency-version: 0.9.4
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
2025-08-01 22:00:13 +00:00
link2xt 3a1370e174 chore(release): prepare for 2.9.0 2025-07-31 18:57:58 +00:00
iequidoo c218c05b96 fix: get_chat_msgs_ex(): Report local midnight in ChatItem::DayMarker
We were reporting the UTC midnight timestamp instead. For UTC-N timezones that means reporting
"yesterday".

Fixes https://github.com/deltachat/deltachat-desktop/issues/5215.
2025-07-31 13:55:28 -03:00
iequidoo db247d9f9a refactor: Don't call add_or_lookup_key_contacts() in advance
Its result isn't needed in all the branches.
2025-07-31 12:07:58 -03:00
iequidoo 78b7715ea6 refactor: Rename add_or_lookup_key_contacts_by_address_list() to add_or_lookup_key_contacts()
- It's obvious that addresses are needed to add contacts, so `_by_address_list` looks excessive.
- The function only looks up `SELF` by address.
- The name was confusing because there's also `lookup_key_contacts_by_address_list()` that actually
  looks up key contacts by addresses (not only `SELF`).
2025-07-31 12:07:58 -03:00
link2xt ba76944d75 fix: display correct timer value for ephemeral timer changes
The timer change should not disappear,
but should display correct timer change.
2025-07-31 15:00:08 +00:00
cliffmccarthyandiequidoo 4a1a2122f0 feat(repl): Add import-vcard and make-vcard commands (#7048)
- Added import-vcard and make-vcard commands to deltachat-repl. These
  wrap the corresponding import_vcard() and make_vcard() operations from
  src/contact.rs. Each command takes a file path argument specifying the
  location of the vCard file to be read or written. The make-vcard command
  takes one or more IDs to write to the file.
- Documented commands in "Using the CLI client" section of README.md.

Co-authored-by: iequidoo <117991069+iequidoo@users.noreply.github.com>
2025-07-30 13:16:29 -03:00
44 changed files with 650 additions and 186 deletions
+35
View File
@@ -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
View File
@@ -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
View File
@@ -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" }
+10 -2
View File
@@ -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 -1
View File
@@ -1,6 +1,6 @@
[package]
name = "deltachat_ffi"
version = "2.8.0"
version = "2.10.0"
description = "Deltachat FFI"
edition = "2018"
readme = "README.md"
+33 -2
View File
@@ -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 -1
View File
@@ -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"
+23 -4
View File
@@ -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(
+1 -1
View File
@@ -54,5 +54,5 @@
},
"type": "module",
"types": "dist/deltachat.d.ts",
"version": "2.8.0"
"version": "2.10.0"
}
+1 -1
View File
@@ -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"
+20
View File
@@ -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?;
+3 -1
View File
@@ -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",
+1 -1
View File
@@ -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)
+4 -1
View File
@@ -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 -1
View File
@@ -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"
}
+1
View File
@@ -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
View File
@@ -6,7 +6,7 @@ edition = "2021"
license = "MPL-2.0"
[dev-dependencies]
bolero = "0.13.3"
bolero = "0.13.4"
[dependencies]
mailparse = { workspace = true }
+1 -1
View File
@@ -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"
+15 -1
View File
@@ -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
View File
@@ -1 +1 @@
2025-07-28
2025-08-04
+28 -16
View File
@@ -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,
+9 -2
View File
@@ -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
View File
@@ -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() {
+46
View File
@@ -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(())
}
+1
View File
@@ -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,
+14 -12
View File
@@ -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(())
}
+3 -2
View File
@@ -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
View File
@@ -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;
+19
View File
@@ -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(())
}
+1
View File
@@ -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)
-4
View File
@@ -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
View File
@@ -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
View File
@@ -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)
+9 -4
View File
@@ -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
View File
@@ -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
View File
@@ -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
+36 -21
View File
@@ -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);
+17
View File
@@ -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
View File
@@ -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(())
}
+21
View File
@@ -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)]