Compare commits

..

75 Commits

Author SHA1 Message Date
link2xt
31e83160f2 build: update all crates to Rust 2024 edition
Largest change in the FFI crate.
With 2024 (but not 2021) edition unsafe code
inside unsafe functions should be marked separately
so we can mark exactly the code that is unsafe.
Some CFFI functions even have no unsafe code inside.

Most interesting change is that .strdup()
functions are not marked as unsafe anymore.
They are allocating memory and return raw pointers,
but there is nothing unsafe about it.
Only using the returned raw pointers is unsafe.
This way calls to .strdup() don't have to be marked
with unsafe{} blocks.
2026-07-22 20:37:52 +00:00
holger krekel
0e4574cd41 feat: read SMTP recipient limit from relay IMAP metadata
Remove is_chatmail flag, and rely on IMAP metadata
advertising the recipients limit (usually 1000),
falling back to 50 (or fewer in some exceptional cases)
just as before when is_chatmail was false.

Also, server metadata is now keyed per transport,
which in the future eases collecting ICE servers
from all relays (instead of just any first relay).
2026-07-22 20:31:50 +02:00
link2xt
3a913dfb07 feat: enable TLS certificate compression
Enabling brotli does not add new dependencies,
we have it already.
Documentation at <https://docs.rs/rustls/0.23.42/rustls/compress/>
recommends enabling at least brotli.

zlib feature is not enabled as it pulls in duplicate zlib-rs 0.6.6
zlib was even never supported in Chromium (while brotli is).
It might still be interesting to enable it in the future
because it seems to be the only option supported by OpenSSL
and there may be servers that support zlib but not brotli.

RFC 8879 specifies zstd as well, but there is no feature to enable it yet.

This also brings back explicit TLS 1.2 feature that was reverted.
We don't want TLS 1.2 support to be accidentally dropped
if no dependencies explicitly require it anymore.
2026-07-22 14:03:36 +00:00
link2xt
bdd9d96844 feat: accept messages from key contacts with forged From address
From address is not used for key contacts
other than as the address to send replies to.
2026-07-21 19:59:32 +00:00
dependabot[bot]
4e2238a38f chore(deps): bump actions/setup-node from 6 to 7
Bumps [actions/setup-node](https://github.com/actions/setup-node) from 6 to 7.
- [Release notes](https://github.com/actions/setup-node/releases)
- [Commits](https://github.com/actions/setup-node/compare/v6...v7)

---
updated-dependencies:
- dependency-name: actions/setup-node
  dependency-version: '7'
  dependency-type: direct:production
  update-type: version-update:semver-major
...

Signed-off-by: dependabot[bot] <support@github.com>
2026-07-21 19:57:42 +00:00
link2xt
8c53f86fbc chore: bump version to 2.57.0-dev 2026-07-21 19:35:26 +00:00
holger krekel
ca1fe8e352 chore(release): prepare for 2.56.0 2026-07-21 19:17:51 +02:00
holger krekel
61898b4789 fix: revert 207c2e6e4c because some users reported problems with it 2026-07-21 18:55:22 +02:00
holger krekel
5c1e695237 api!: remove all oauth support and drop DC_LP_AUTH flags
BREAKING CHANGE: removed oauth2 module, dc_get_oauth2_url FFI function, DC_LP_AUTH flags and configured/serverflags, and the oauth2 parameter/field from SMTP/IMAP clients, JSON-RPC interfaces, and CLI tools.

also contains regenerated provider data after dropping oauth in the update script.
2026-07-21 15:55:28 +02:00
link2xt
1721c0316a feat: do not set backup_time in exported databases
backup_time is unused, it is only set, but nobody reads it.
2026-07-21 12:02:39 +00:00
holger krekel
398ddac0b9 chore: bump version to 2.56.0-dev 2026-07-21 12:17:59 +02:00
link2xt
35d7b4e21c chore(release): prepare for 2.55.0 2026-07-20 18:47:28 +00:00
link2xt
03725c5f32 ci: update Node version to 24
https://docs.npmjs.com/trusted-publishers recommends node-version 24
and not updating npm separately, this change copies the recommended configuration.

Also update node version to 24 in non-release workflows, version 18 is EOL.
There is a version 26 already, but 24 is LTS.

Also disable cache for release pipelines.
2026-07-20 17:39:13 +00:00
link2xt
a0c96f4ce1 chore: bump version to 2.55.0-dev 2026-07-20 15:20:53 +00:00
link2xt
b432d08aa8 chore(release): prepare for 2.54.0 2026-07-20 14:57:57 +00:00
Jagoda Estera Ślązak
85db29bdfb fix: Apply reactions that arrived before the message at later time (#8415)
A new table `pending_reactions` is now used,
to store reactions that arrived before the referenced message.

When receiving a new message, pending_reactions is checked and any
reactions that arrived earlier are applied.

Fixes: #8367

Signed-off-by: Jagoda Ślązak <jslazak@jslazak.com>
2026-07-20 13:50:05 +00:00
Hocuri
23c216c84b fix: Update last_rcvd_timestamp in migration (#8434)
Fix https://github.com/chatmail/core/issues/8429
2026-07-20 15:17:36 +02:00
Hocuri
d34dd40aa0 feat: Automatically remove oldest unpublished relay in order to make space when the user wants to add more; don't allow more than 5 relays overall (#8428)
Unpublished relays aren't shown in the list of relays anymore, and can't
be deleted manually. Therefore, we want to automatically delete them in
order to make space for more relays when the limit (5) is reached and
the user wants to add another relay.
2026-07-20 15:11:13 +02:00
link2xt
ebd1c20997 fix: do not log errors if full message is not available on any transport
Before this change "Err" case spams the logs with
"Failed to download message rfc724_mid=...: IMAP location for ... post-message is unknown."
logged by every transports when the message is not available on any transport.
2026-07-20 09:50:27 +00:00
Jagoda Estera Ślązak
0c40b52ec8 fix: Un-escape message footer marks in full messages (get_html) (#8427)
Un-escapes footer marks in long plain-text messages when retrieved with
`get_message_html`/`dc_get_msg_html`.

Additionally, makes escaping stricter,
only matching lines starting with `-- ` instead of `--`.

Fixes: #8269

Signed-off-by: Jagoda Ślązak <jslazak@jslazak.com>
2026-07-20 09:44:14 +02:00
link2xt
aadd11a771 feat(imap): use CAPABILITY response code if LOGIN command returns it
This saves one round trip during connection establishment
if the server returns capabilities in the LOGIN command response already.
2026-07-17 21:36:45 +00:00
link2xt
17b32a17f5 chore: update async-imap to 0.11.3 2026-07-17 21:36:45 +00:00
link2xt
355ab8b3bc ci: update Rust to 1.97.1 2026-07-17 13:49:35 +00:00
iequidoo
e2b67d20fb refactor: Remove GROUP BY c.id from chatlist queries
Not clear why `GROUP BY` was needed -- a chat can't appear in the selected rows multiple times
because we do JOIN on the last message in the chat, there can't be multiple last messages.
2026-07-16 20:03:49 -03:00
iequidoo
0560c13ae5 perf: Add timestamp to msgs_index7 and speed up Chatlist::try_load() (#7848)
This speeds up chatlisting by 3--4 times on my biggest profile, so the difference is visually
noticeable, particularly after dropping disk caches. Before, `msgs_index2` was used which has less
ordering and requires a full scan of `msgs` table.
2026-07-16 20:03:49 -03:00
link2xt
1a24bf612a refactor: label the loop iterating over the candidates
The loop is large, so having an explicit label
makes it easier to understand what the "continue" statement does.
2026-07-16 09:49:41 +00:00
link2xt
492741ac60 fix(imap): do not bubble up errors in IMAP candidate loop
Network errors such as timeouts should not break out of the loop
by bubbling up errors. There may be more candidates to try,
breaking out of the loop skips trying them.
2026-07-16 09:49:41 +00:00
link2xt
d940f96a5c feat: limit the number of published relays instead of all relays 2026-07-15 17:24:58 +00:00
link2xt
e5c920f1a1 api(deltachat-rpc-client): add Account.set_transport_unpublished() 2026-07-15 17:24:58 +00:00
Jagoda Estera Ślązak
5d0e5efc94 feat: Remove hidden relays automatically (#8402)
Automatically remove relays that are hidden (`is_published=0`) and
haven't been used to receive new messages for over 90 days.

Closes: #8384

Signed-off-by: Jagoda Ślązak <jslazak@jslazak.com>
2026-07-15 12:31:11 +00:00
dependabot[bot]
94fc4f7550 chore(deps): bump taiki-e/install-action from 2.82.6 to 2.82.10
Bumps [taiki-e/install-action](https://github.com/taiki-e/install-action) from 2.82.6 to 2.82.10.
- [Release notes](https://github.com/taiki-e/install-action/releases)
- [Changelog](https://github.com/taiki-e/install-action/blob/main/CHANGELOG.md)
- [Commits](9bcaee1dca...50414676f9)

---
updated-dependencies:
- dependency-name: taiki-e/install-action
  dependency-version: 2.82.9
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
2026-07-14 13:13:43 +00:00
link2xt
ce2d237ffd refactor: remove unnecessary reference in format string 2026-07-14 11:14:36 +00:00
link2xt
f2d11305bd chore: update yanked spin@0.9.8 and spin@0.10.0
Upstream issue with the reason for yanking the crate is <https://codeberg.org/zesterer/spin/issues/192>
2026-07-14 11:14:10 +00:00
link2xt
5adf7454ea ci: update Rust to 1.97.0 2026-07-12 13:10:21 +00:00
link2xt
079fb352e3 refactor: remove set_modseq() function
This is a follow-up to 8f51c7b9d5
which removed sync_seen_flags().

MODSEQ is a message attribute related to
CONDSTORE IMAP extension and is not used anymore
since CONDSTORE is not used.
2026-07-12 06:16:01 +00:00
Hocuri
823b0741df docs: Update STYLE.md: macros should be used only when necessary (#8410) 2026-07-10 10:52:33 +02:00
Hocuri
6322758482 api!: list_transports() doesn't return unpublished relays anymore
BREAKING CHANGE: UIs should use `list_transports()` rather than `list_transports_ex()`, because unpublished transports count as removed from the user point of view, and should not be shown in the relay list anymore.
2026-07-09 15:24:53 +02:00
Hocuri
90ac058043 api!: delete_transport() must not be used by UIs anymore. Instead, set_transport_unpublished() must be called when a user clicks on "Remove"
I marked this as a breaking change so that it visibly appears in the changelog.
2026-07-09 15:24:53 +02:00
dependabot[bot]
212aaaad77 chore(deps): bump taiki-e/install-action from 2.81.11 to 2.82.6
Bumps [taiki-e/install-action](https://github.com/taiki-e/install-action) from 2.81.11 to 2.82.6.
- [Release notes](https://github.com/taiki-e/install-action/releases)
- [Changelog](https://github.com/taiki-e/install-action/blob/main/CHANGELOG.md)
- [Commits](15449e3094...9bcaee1dca)

---
updated-dependencies:
- dependency-name: taiki-e/install-action
  dependency-version: 2.82.6
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
2026-07-08 22:07:06 +00:00
link2xt
31b2d7961a fix: ensure public key signatures are not in the past compared to the public key
We use the timestamp of the latest transport modification
as the timestamp of the Direct Key Signature.
However, first transport is created before the public key
and existing transports may even have zero timestamp
if they were created before migration 142.

For interoperability with Sequoia-PGP,
make sure signatures always have a timestamp
that is not in the past compared to the timestamp
of the primary key.
2026-07-08 17:41:45 +00:00
missytake
2b6515e3b5 ci: rrsync prepends the restricted upload path, we need to leave it out (#8405)
This is necessary because
https://github.com/deltachat/sysadmin/issues/270 switches our
server-side authentication to rrsync, and that uploads to a restricted
path already; keeping it like this will result in a destination path
like `/var/www/html/var/www/html`.

Co-authored-by: j4n <j4n@systemli.org>
2026-07-08 17:30:11 +02:00
Jagoda Estera Ślązak
cac8c06c78 fix: RUSTSEC-2026-0204 (#8403)
https://github.com/crossbeam-rs/crossbeam/pull/1276

Signed-off-by: Jagoda Ślązak <jslazak@jslazak.com>
2026-07-07 17:13:25 +02:00
dependabot[bot]
446cdabd21 chore(cargo): bump smallvec from 1.15.1 to 1.15.2
Bumps [smallvec](https://github.com/servo/rust-smallvec) from 1.15.1 to 1.15.2.
- [Release notes](https://github.com/servo/rust-smallvec/releases)
- [Commits](https://github.com/servo/rust-smallvec/compare/v1.15.1...v1.15.2)

---
updated-dependencies:
- dependency-name: smallvec
  dependency-version: 1.15.2
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
2026-07-04 15:12:57 +00:00
iequidoo
d9ab7a7a43 test: securejoin: Check that "vc-{,request-}pubkey" messages don't contain displayname
These messages are AUTH-encrypted and shalln't contain profile data as per the SecureJoin v3 spec,
otherwise the profile data can be seen e.g. by other broadcast subscribers.
2026-07-03 22:42:01 -03:00
dependabot[bot]
e5d56a3b70 chore(cargo): bump brotli from 8.0.2 to 8.0.4
Bumps [brotli](https://github.com/dropbox/rust-brotli) from 8.0.2 to 8.0.4.
- [Release notes](https://github.com/dropbox/rust-brotli/releases)
- [Commits](https://github.com/dropbox/rust-brotli/compare/8.0.2...8.0.4)

---
updated-dependencies:
- dependency-name: brotli
  dependency-version: 8.0.4
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
2026-07-04 01:21:00 +00:00
link2xt
ee1095be9d chore: update quick-xml to 0.41.0
Fixes cargo-deny complaining about https://rustsec.org/advisories/RUSTSEC-2026-0194
2026-07-03 16:21:02 +00:00
dependabot[bot]
520ede1fa5 chore(cargo): bump chrono from 0.4.44 to 0.4.45
Bumps [chrono](https://github.com/chronotope/chrono) from 0.4.44 to 0.4.45.
- [Release notes](https://github.com/chronotope/chrono/releases)
- [Changelog](https://github.com/chronotope/chrono/blob/main/CHANGELOG.md)
- [Commits](https://github.com/chronotope/chrono/compare/v0.4.44...v0.4.45)

---
updated-dependencies:
- dependency-name: chrono
  dependency-version: 0.4.45
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
2026-07-02 11:53:04 +00:00
link2xt
89a1f55a87 feat: increase max idle timeout for backup receiver to 60 seconds
Default is 30 seconds according to
<https://docs.rs/iroh/0.35.0/iroh/endpoint/struct.TransportConfig.html#method.max_idle_timeout>
2026-07-02 05:00:38 +00:00
dependabot[bot]
90e6fa055f chore(deps): bump zizmorcore/zizmor-action from 0.5.6 to 0.5.7
Bumps [zizmorcore/zizmor-action](https://github.com/zizmorcore/zizmor-action) from 0.5.6 to 0.5.7.
- [Release notes](https://github.com/zizmorcore/zizmor-action/releases)
- [Commits](5f14fd08f7...192e21d79a)

---
updated-dependencies:
- dependency-name: zizmorcore/zizmor-action
  dependency-version: 0.5.7
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
2026-07-02 04:55:32 +00:00
dependabot[bot]
093ebe537f chore(cargo): bump hyper from 1.9.0 to 1.10.1
Bumps [hyper](https://github.com/hyperium/hyper) from 1.9.0 to 1.10.1.
- [Release notes](https://github.com/hyperium/hyper/releases)
- [Changelog](https://github.com/hyperium/hyper/blob/master/CHANGELOG.md)
- [Commits](https://github.com/hyperium/hyper/compare/v1.9.0...v1.10.1)

---
updated-dependencies:
- dependency-name: hyper
  dependency-version: 1.10.1
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
2026-07-02 04:54:50 +00:00
dependabot[bot]
fa91b168a9 chore(cargo): bump log from 0.4.31 to 0.4.33
Bumps [log](https://github.com/rust-lang/log) from 0.4.31 to 0.4.33.
- [Release notes](https://github.com/rust-lang/log/releases)
- [Changelog](https://github.com/rust-lang/log/blob/master/CHANGELOG.md)
- [Commits](https://github.com/rust-lang/log/compare/0.4.31...0.4.33)

---
updated-dependencies:
- dependency-name: log
  dependency-version: 0.4.33
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
2026-07-02 04:54:27 +00:00
dependabot[bot]
8c12804257 chore(cargo): bump regex from 1.12.3 to 1.12.4
Bumps [regex](https://github.com/rust-lang/regex) from 1.12.3 to 1.12.4.
- [Release notes](https://github.com/rust-lang/regex/releases)
- [Changelog](https://github.com/rust-lang/regex/blob/master/CHANGELOG.md)
- [Commits](https://github.com/rust-lang/regex/compare/1.12.3...1.12.4)

---
updated-dependencies:
- dependency-name: regex
  dependency-version: 1.12.4
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
2026-07-02 04:54:06 +00:00
dependabot[bot]
5beef1a2d0 chore(cargo): bump bytes from 1.11.1 to 1.12.0
Bumps [bytes](https://github.com/tokio-rs/bytes) from 1.11.1 to 1.12.0.
- [Release notes](https://github.com/tokio-rs/bytes/releases)
- [Changelog](https://github.com/tokio-rs/bytes/blob/master/CHANGELOG.md)
- [Commits](https://github.com/tokio-rs/bytes/compare/v1.11.1...v1.12.0)

---
updated-dependencies:
- dependency-name: bytes
  dependency-version: 1.12.0
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
2026-07-02 04:53:40 +00:00
dependabot[bot]
cc08d5de81 chore(cargo): bump quote from 1.0.45 to 1.0.46
Bumps [quote](https://github.com/dtolnay/quote) from 1.0.45 to 1.0.46.
- [Release notes](https://github.com/dtolnay/quote/releases)
- [Commits](https://github.com/dtolnay/quote/compare/1.0.45...1.0.46)

---
updated-dependencies:
- dependency-name: quote
  dependency-version: 1.0.46
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
2026-07-02 04:53:21 +00:00
dependabot[bot]
ba1063ad0f chore(cargo): bump syn from 2.0.117 to 2.0.118
Bumps [syn](https://github.com/dtolnay/syn) from 2.0.117 to 2.0.118.
- [Release notes](https://github.com/dtolnay/syn/releases)
- [Commits](https://github.com/dtolnay/syn/compare/2.0.117...2.0.118)

---
updated-dependencies:
- dependency-name: syn
  dependency-version: 2.0.118
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
2026-07-02 04:53:00 +00:00
link2xt
30bdcd6809 fix: do not put locations into pre-messages 2026-07-01 12:02:53 +00:00
link2xt
5eb5d22d7b fix: replace last_added_location_id with last_added_location_timestamp
Stored location ID was not actually used
and locations_last_sent timestamp was updated
with the current time, possibly skipping
locations.
2026-07-01 12:02:53 +00:00
dependabot[bot]
a22ba021e5 chore(deps): bump actions/checkout from 6 to 7
Bumps [actions/checkout](https://github.com/actions/checkout) from 6 to 7.
- [Release notes](https://github.com/actions/checkout/releases)
- [Changelog](https://github.com/actions/checkout/blob/main/CHANGELOG.md)
- [Commits](https://github.com/actions/checkout/compare/v6...v7)

---
updated-dependencies:
- dependency-name: actions/checkout
  dependency-version: '7'
  dependency-type: direct:production
  update-type: version-update:semver-major
...

Signed-off-by: dependabot[bot] <support@github.com>
2026-07-01 11:49:38 +00:00
iequidoo
6e633eebe1 chore(deps): bump anyhow to 1.0.103 2026-06-29 19:08:41 -03:00
link2xt
a14e617059 feat: remove ?emailaddress argument from autoconfig URL that is not using a dedicated domain
This brings the implementation closer to the specification <https://datatracker.ietf.org/doc/draft-ietf-mailmaint-autoconfig/06/>
as it is confirmed that `?emailaddress` should not be included.
2026-06-29 18:07:17 +00:00
iequidoo
b4471f2434 feat: Remove trailing '.' from device message about receive_imf failure
This is to avoid from the Android UI treating it as a part of URL.
2026-06-29 14:33:52 -03:00
iequidoo
abaa48d0a1 fix: Return early from receive_imf to not tombstone Iroh-Node-Addr message if webxdc instance isn't found (#8372)
An Iroh-Node-Addr message (realtime advertisement) may arrive earlier than the referenced webxdc in
case of multi-transport. By not creating a tombstone we still can receive it later on another
transport.
2026-06-29 14:33:52 -03:00
link2xt
1b2ec2e88c chore: update astral-tokio-tar from 0.6.2 to 0.6.3 2026-06-27 06:04:18 +00:00
Hocuri
f6e819d2a5 refactor: improve comment (#8366)
It's unclear whether this logic works, but I think we should at least
properly document why it exists (I already sent this text while figuring
out what the code does, so it was easy making this PR now)

Could add a XXX note that it's unsure whether this works
2026-06-25 17:06:22 +00:00
Hocuri
abb84efc37 fix: Rerun the full securejoin protocol if the address was outdated (#8358)
Alternative to https://github.com/chatmail/core/pull/8355/, fixes
https://github.com/chatmail/core/issues/8329

This fixes the following bug:

Scanning QR for existing contact doesn't update relay list

Two chat partners suddenly lost connectivity to all of their relays. They added another relay, which is accessible, and set it as main. Of course, they still couldn't communicate, because there was no way to announce a new relay list to each other (clients know only about old relays so they try to send updates there).

That's why the friends used a different channel (e.g. met IRL) to send their QR codes once again.

Expected behavior: They would expect that clients update their relay lists according to the contact info inside the QR codes, and they can continue talking in Delta.

Actual behavior: But Delta Chat client doesn't update a list of announced relays on scanning a QR in case a contact already exists. It simply opens the existing chat without altering any contact info.

---------

Co-authored-by: holger krekel <holger@merlinux.eu>
2026-06-25 16:59:08 +00:00
link2xt
09d5d0bddf chore: update rPGP from 0.19.0 to 0.20.0 2026-06-25 16:17:24 +00:00
link2xt
b38277c294 feat: log transport ID in store_seen_flags_on_imap 2026-06-23 05:05:19 +00:00
dependabot[bot]
975fd8aee3 chore(deps): bump taiki-e/install-action from 2.81.8 to 2.81.11
Bumps [taiki-e/install-action](https://github.com/taiki-e/install-action) from 2.81.8 to 2.81.11.
- [Release notes](https://github.com/taiki-e/install-action/releases)
- [Changelog](https://github.com/taiki-e/install-action/blob/main/CHANGELOG.md)
- [Commits](0631aa6515...15449e3094)

---
updated-dependencies:
- dependency-name: taiki-e/install-action
  dependency-version: 2.81.11
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
2026-06-23 04:41:22 +00:00
link2xt
5a54e18fee fix: recreate imap_markseen with PRIMARY KEY constraint
It is needed to speed up "DELETE FROM imap_markseen_new WHERE id = ?".
Otherwise if imap_markseen table grows large,
marking a lot of messages at once as seen and trying to delete them one by one
from imap_markseen table may have quadratic complexity.
2026-06-23 03:33:33 +00:00
iequidoo
8b80ac146f fix: Tombstone MDN before sending it (#8252)
Otherwise, when it appears on IMAP, it will mark chat messages as seen/noticed even if
markfresh_chat() is called meanwhile.
2026-06-22 13:52:04 -03:00
Hocuri
d1f4e59d82 api: Deprecate is_chatmail
BREAKING CHANGE: UIs should not behave differently for chatmail relays than for classical email servers; most usages of `is_chatmail` can be replaced by `force_encryption`
2026-06-22 17:48:56 +02:00
link2xt
0a82d73eb5 refactor: move ensure_secret_key_exists into key.rs 2026-06-21 04:18:39 +00:00
link2xt
c026910fa7 chore: deprecate unused SkipAutocrypt param
I renamed SkipAutocrypt into DeprecatedSkipAutocrypt
instead of removing to make sure the letter is not reused.
2026-06-21 04:18:20 +00:00
link2xt
2aba6aa339 refactor: make crate::pgp::symm_encrypt_message non-async 2026-06-20 00:36:05 +00:00
link2xt
93a6f35ef7 refactor: remove the ability to send messages with non-standard header protection 2026-06-20 00:09:48 +00:00
138 changed files with 3623 additions and 3431 deletions

View File

@@ -20,7 +20,7 @@ permissions: {}
env:
RUSTFLAGS: -Dwarnings
RUST_VERSION: 1.95.0
RUST_VERSION: 1.97.1
# Minimum Supported Rust Version
MSRV: 1.89.0
@@ -31,7 +31,7 @@ jobs:
runs-on: ubuntu-latest
timeout-minutes: 60
steps:
- uses: actions/checkout@v6
- uses: actions/checkout@v7
with:
show-progress: false
persist-credentials: false
@@ -58,7 +58,7 @@ jobs:
runs-on: ubuntu-latest
timeout-minutes: 60
steps:
- uses: actions/checkout@v6
- uses: actions/checkout@v7
with:
show-progress: false
persist-credentials: false
@@ -73,7 +73,7 @@ jobs:
runs-on: ubuntu-latest
timeout-minutes: 60
steps:
- uses: actions/checkout@v6
- uses: actions/checkout@v7
with:
show-progress: false
persist-credentials: false
@@ -89,7 +89,7 @@ jobs:
env:
RUSTDOCFLAGS: -Dwarnings
steps:
- uses: actions/checkout@v6
- uses: actions/checkout@v7
with:
show-progress: false
persist-credentials: false
@@ -128,7 +128,7 @@ jobs:
shell: bash
if: matrix.rust == 'latest'
- uses: actions/checkout@v6
- uses: actions/checkout@v7
with:
show-progress: false
persist-credentials: false
@@ -146,7 +146,7 @@ jobs:
cache-bin: false
- name: Install nextest
uses: taiki-e/install-action@0631aa6515c7d545823c67cfae7ef4fc7f490154
uses: taiki-e/install-action@50414676f9f5d50a65992c6dd2ed02641263226c
with:
tool: nextest
@@ -171,7 +171,7 @@ jobs:
runs-on: ${{ matrix.os }}
timeout-minutes: 60
steps:
- uses: actions/checkout@v6
- uses: actions/checkout@v7
with:
show-progress: false
persist-credentials: false
@@ -200,7 +200,7 @@ jobs:
runs-on: ${{ matrix.os }}
timeout-minutes: 60
steps:
- uses: actions/checkout@v6
- uses: actions/checkout@v7
with:
show-progress: false
persist-credentials: false
@@ -226,7 +226,7 @@ jobs:
runs-on: ubuntu-latest
timeout-minutes: 60
steps:
- uses: actions/checkout@v6
- uses: actions/checkout@v7
with:
show-progress: false
persist-credentials: false
@@ -252,7 +252,7 @@ jobs:
runs-on: ubuntu-latest
timeout-minutes: 60
steps:
- uses: actions/checkout@v6
- uses: actions/checkout@v7
with:
show-progress: false
persist-credentials: false
@@ -302,7 +302,7 @@ jobs:
runs-on: ${{ matrix.os }}
timeout-minutes: 60
steps:
- uses: actions/checkout@v6
- uses: actions/checkout@v7
with:
show-progress: false
persist-credentials: false
@@ -356,7 +356,7 @@ jobs:
runs-on: ${{ matrix.os }}
timeout-minutes: 60
steps:
- uses: actions/checkout@v6
- uses: actions/checkout@v7
with:
show-progress: false
persist-credentials: false

View File

@@ -30,7 +30,7 @@ jobs:
arch: [aarch64, armv7l, armv6l, i686, x86_64]
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v6
- uses: actions/checkout@v7
with:
show-progress: false
persist-credentials: false
@@ -54,7 +54,7 @@ jobs:
arch: [aarch64, armv7l, armv6l, i686, x86_64]
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v6
- uses: actions/checkout@v7
with:
show-progress: false
persist-credentials: false
@@ -78,7 +78,7 @@ jobs:
arch: [win32, win64]
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v6
- uses: actions/checkout@v7
with:
show-progress: false
persist-credentials: false
@@ -102,7 +102,7 @@ jobs:
arch: [win32, win64]
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v6
- uses: actions/checkout@v7
with:
show-progress: false
persist-credentials: false
@@ -127,7 +127,7 @@ jobs:
runs-on: macos-latest
steps:
- uses: actions/checkout@v6
- uses: actions/checkout@v7
with:
show-progress: false
persist-credentials: false
@@ -153,7 +153,7 @@ jobs:
arch: [arm64-v8a, armeabi-v7a]
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v6
- uses: actions/checkout@v7
with:
show-progress: false
persist-credentials: false
@@ -177,7 +177,7 @@ jobs:
arch: [arm64-v8a, armeabi-v7a]
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v6
- uses: actions/checkout@v7
with:
show-progress: false
persist-credentials: false
@@ -204,7 +204,7 @@ jobs:
contents: write
runs-on: "ubuntu-latest"
steps:
- uses: actions/checkout@v6
- uses: actions/checkout@v7
with:
show-progress: false
persist-credentials: false
@@ -397,7 +397,7 @@ jobs:
# Needed to publish the binaries to the release.
contents: write
steps:
- uses: actions/checkout@v6
- uses: actions/checkout@v7
with:
show-progress: false
persist-credentials: false
@@ -513,15 +513,12 @@ jobs:
deltachat-rpc-server/npm-package/*.tgz
# Configure Node.js for publishing.
- uses: actions/setup-node@v6
# Check <https://docs.npmjs.com/trusted-publishers> for the version requirements.
- uses: actions/setup-node@v7
with:
node-version: 20
node-version: 24
registry-url: "https://registry.npmjs.org"
# Ensure npm 11.5.1 or later is installed.
# It is needed for <https://docs.npmjs.com/trusted-publishers>
- name: Update npm
run: npm install -g npm@latest
package-manager-cache: false # never use caching in release builds
- name: Publish npm packets for prebuilds and `@deltachat/stdio-rpc-server`
if: github.event_name == 'release'

View File

@@ -14,7 +14,7 @@ jobs:
name: Check that current version ends with -dev
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v6
- uses: actions/checkout@v7
with:
show-progress: false
persist-credentials: false

View File

@@ -17,23 +17,21 @@ jobs:
id-token: write
contents: read
steps:
- uses: actions/checkout@v6
- uses: actions/checkout@v7
with:
show-progress: false
persist-credentials: false
- uses: actions/setup-node@v6
# Configure Node.js for publishing.
# Check <https://docs.npmjs.com/trusted-publishers> for the version requirements.
- uses: actions/setup-node@v7
with:
node-version: 20
node-version: 24
registry-url: "https://registry.npmjs.org"
# Ensure npm 11.5.1 or later is installed.
# It is needed for <https://docs.npmjs.com/trusted-publishers>
- name: Update npm
run: npm install -g npm@latest
package-manager-cache: false # never use caching in release builds
- name: Install dependencies without running scripts
working-directory: deltachat-jsonrpc-bindings/typescript
working-directory: deltachat-jsonrpc/typescript
run: npm install --ignore-scripts
- name: Package
@@ -43,5 +41,5 @@ jobs:
npm pack .
- name: Publish
working-directory: deltachat-jsonrpc-bindings/typescript
working-directory: deltachat-jsonrpc/typescript
run: npm publish --provenance deltachat-jsonrpc-client-* --access public

View File

@@ -16,30 +16,30 @@ jobs:
build_and_test:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v6
- uses: actions/checkout@v7
with:
show-progress: false
persist-credentials: false
- name: Use Node.js 18.x
uses: actions/setup-node@v6
- name: Use Node.js 24
uses: actions/setup-node@v7
with:
node-version: 18.x
node-version: 24
- name: Add Rust cache
uses: swatinem/rust-cache@c19371144df3bb44fab255c43d04cbc2ab54d1c4
with:
save-if: ${{ github.ref == 'refs/heads/main' }}
cache-bin: false
- name: npm install
working-directory: deltachat-jsonrpc-bindings/typescript
working-directory: deltachat-jsonrpc/typescript
run: npm install
- name: Build TypeScript, run Rust tests, generate bindings
working-directory: deltachat-jsonrpc-bindings/typescript
working-directory: deltachat-jsonrpc/typescript
run: npm run build
- name: Run integration tests
working-directory: deltachat-jsonrpc-bindings/typescript
working-directory: deltachat-jsonrpc/typescript
run: npm run test
env:
CHATMAIL_DOMAIN: ${{ vars.CHATMAIL_DOMAIN }}
- name: Run linter
working-directory: deltachat-jsonrpc-bindings/typescript
working-directory: deltachat-jsonrpc/typescript
run: npm run prettier:check

View File

@@ -21,7 +21,7 @@ jobs:
name: check flake formatting
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v6
- uses: actions/checkout@v7
with:
show-progress: false
persist-credentials: false
@@ -79,7 +79,7 @@ jobs:
#- deltachat-rpc-server-x86_64-android
#- deltachat-rpc-server-x86-android
steps:
- uses: actions/checkout@v6
- uses: actions/checkout@v7
with:
show-progress: false
persist-credentials: false
@@ -100,7 +100,7 @@ jobs:
# because of <https://github.com/NixOS/nixpkgs/issues/413910>.
# - deltachat-rpc-server-aarch64-darwin
steps:
- uses: actions/checkout@v6
- uses: actions/checkout@v7
with:
show-progress: false
persist-credentials: false

View File

@@ -13,7 +13,7 @@ jobs:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v6
- uses: actions/checkout@v7
with:
show-progress: false
persist-credentials: false

View File

@@ -14,7 +14,7 @@ jobs:
name: Build REPL example
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v6
- uses: actions/checkout@v7
with:
show-progress: false
persist-credentials: false

View File

@@ -15,7 +15,7 @@ jobs:
url: https://rs.delta.chat/
steps:
- uses: actions/checkout@v6
- uses: actions/checkout@v7
with:
show-progress: false
persist-credentials: false
@@ -27,7 +27,7 @@ jobs:
mkdir -p "$HOME/.ssh"
echo "${{ secrets.RS_DOCS_SSH_KEY }}" > "$HOME/.ssh/key"
chmod 600 "$HOME/.ssh/key"
rsync -avzh -e "ssh -i $HOME/.ssh/key -o StrictHostKeyChecking=no" $GITHUB_WORKSPACE/target/doc "${{ secrets.RS_DOCS_SSH_USER }}@rs.delta.chat:/var/www/html/rs.delta.chat/"
rsync -avzh -e "ssh -i $HOME/.ssh/key -o StrictHostKeyChecking=no" $GITHUB_WORKSPACE/target/doc/ "${{ secrets.RS_DOCS_SSH_USER }}@rs.delta.chat:"
build-python:
runs-on: ubuntu-latest
@@ -36,7 +36,7 @@ jobs:
url: https://py.delta.chat/
steps:
- uses: actions/checkout@v6
- uses: actions/checkout@v7
with:
show-progress: false
persist-credentials: false
@@ -49,7 +49,7 @@ jobs:
mkdir -p "$HOME/.ssh"
echo "${{ secrets.PY_DOCS_SSH_KEY }}" > "$HOME/.ssh/key"
chmod 600 "$HOME/.ssh/key"
rsync -avzh --delete -e "ssh -i $HOME/.ssh/key -o StrictHostKeyChecking=no" $GITHUB_WORKSPACE/result/html/ "${{ secrets.PY_DOCS_SSH_USER }}@py.delta.chat:/var/www/html/py.delta.chat"
rsync -avzh --delete -e "ssh -i $HOME/.ssh/key -o StrictHostKeyChecking=no" $GITHUB_WORKSPACE/result/html/ "${{ secrets.PY_DOCS_SSH_USER }}@py.delta.chat:"
build-c:
runs-on: ubuntu-latest
@@ -58,7 +58,7 @@ jobs:
url: https://c.delta.chat/
steps:
- uses: actions/checkout@v6
- uses: actions/checkout@v7
with:
show-progress: false
persist-credentials: false
@@ -71,7 +71,7 @@ jobs:
mkdir -p "$HOME/.ssh"
echo "${{ secrets.C_DOCS_SSH_KEY }}" > "$HOME/.ssh/key"
chmod 600 "$HOME/.ssh/key"
rsync -avzh --delete -e "ssh -i $HOME/.ssh/key -o StrictHostKeyChecking=no" $GITHUB_WORKSPACE/result/html/ "${{ secrets.C_DOCS_SSH_USER }}@c.delta.chat:/var/www/html/c.delta.chat"
rsync -avzh --delete -e "ssh -i $HOME/.ssh/key -o StrictHostKeyChecking=no" $GITHUB_WORKSPACE/result/html/ "${{ secrets.C_DOCS_SSH_USER }}@c.delta.chat:"
build-ts:
runs-on: ubuntu-latest
@@ -81,18 +81,18 @@ jobs:
defaults:
run:
working-directory: ./deltachat-jsonrpc-bindings/typescript
working-directory: ./deltachat-jsonrpc/typescript
steps:
- uses: actions/checkout@v6
- uses: actions/checkout@v7
with:
show-progress: false
persist-credentials: false
fetch-depth: 0 # Fetch history to calculate VCS version number.
- name: Use Node.js
uses: actions/setup-node@v6
uses: actions/setup-node@v7
with:
node-version: '18'
node-version: 24
- name: npm install
run: npm install
- name: npm run build
@@ -104,7 +104,7 @@ jobs:
mkdir -p "$HOME/.ssh"
echo "${{ secrets.JS_JSONRPC_DOCS_SSH_KEY }}" > "$HOME/.ssh/key"
chmod 600 "$HOME/.ssh/key"
rsync -avzh --delete -e "ssh -i $HOME/.ssh/key -o StrictHostKeyChecking=no" $GITHUB_WORKSPACE/deltachat-jsonrpc-bindings/typescript/docs/ "${{ secrets.JS_JSONRPC_DOCS_SSH_USER }}@js.jsonrpc.delta.chat:/var/www/html/js.jsonrpc.delta.chat/"
rsync -avzh --delete -e "ssh -i $HOME/.ssh/key -o StrictHostKeyChecking=no" $GITHUB_WORKSPACE/deltachat-jsonrpc/typescript/docs/ "${{ secrets.JS_JSONRPC_DOCS_SSH_USER }}@js.jsonrpc.delta.chat:"
build-cffi:
runs-on: ubuntu-latest
@@ -113,7 +113,7 @@ jobs:
url: https://cffi.delta.chat/
steps:
- uses: actions/checkout@v6
- uses: actions/checkout@v7
with:
show-progress: false
persist-credentials: false
@@ -125,4 +125,4 @@ jobs:
mkdir -p "$HOME/.ssh"
echo "${{ secrets.CFFI_DOCS_SSH_KEY }}" > "$HOME/.ssh/key"
chmod 600 "$HOME/.ssh/key"
rsync -avzh --delete -e "ssh -i $HOME/.ssh/key -o StrictHostKeyChecking=no" $GITHUB_WORKSPACE/target/doc/ "${{ secrets.CFFI_DOCS_SSH_USER }}@delta.chat:/var/www/html/cffi.delta.chat/"
rsync -avzh --delete -e "ssh -i $HOME/.ssh/key -o StrictHostKeyChecking=no" $GITHUB_WORKSPACE/target/doc/ "${{ secrets.CFFI_DOCS_SSH_USER }}@delta.chat:"

View File

@@ -18,9 +18,9 @@ jobs:
actions: read
steps:
- name: Checkout repository
uses: actions/checkout@v6
uses: actions/checkout@v7
with:
persist-credentials: false
- name: Run zizmor
uses: zizmorcore/zizmor-action@5f14fd08f7cf1cb1609c1e344975f152c7ee938d # v0.5.6
uses: zizmorcore/zizmor-action@192e21d79ab29983730a13d1382995c2307fbcaa # v0.5.7

View File

@@ -1,5 +1,132 @@
# Changelog
## [2.56.0] - 2026-07-21
### API-Changes
- [**breaking**] remove all oauth support and drop DC_LP_AUTH flags.
- removed oauth2 module, dc_get_oauth2_url FFI function, DC_LP_AUTH flags and configured/serverflags, and the oauth2 parameter/field from SMTP/IMAP clients, JSON-RPC interfaces, and CLI tools.
also contains regenerated provider data after dropping oauth in the update script.
### Features / Changes
- do not set backup_time in exported databases.
### Fixes
- revert 207c2e6e4c1bec43204c3b8a46fcbbff67d54b3f because some users reported problems with it.
### Miscellaneous Tasks
- bump version to 2.56.0-dev.
## [2.55.0] - 2026-07-20
Minor release to fix CI because releasing 2.54.0 failed.
### CI
- Update Node version to 24.
## [2.54.0] - 2026-07-20
### API-Changes
- [**breaking**] Deprecate `is_chatmail`.
- UIs should not behave differently for chatmail relays than for classical email servers; most usages of `is_chatmail` can be replaced by `force_encryption`.
- [**breaking**] `delete_transport()` must not be used by UIs anymore. Instead, `set_transport_unpublished()` must be called when a user clicks on "Remove".
- [**breaking**] `list_transports()` doesn't return unpublished relays anymore.
- UIs should use `list_transports()` rather than `list_transports_ex()`, because unpublished transports count as removed from the user point of view, and should not be shown in the relay list anymore.
- deltachat-rpc-client: add `Account.set_transport_unpublished()`.
- Add `MsgReadCountChanged` event.
### Features / Changes
- Implement support for populating and maintaining a list of default relays ([#8341](https://github.com/chatmail/core/pull/8341)).
- Remove hidden relays automatically ([#8402](https://github.com/chatmail/core/pull/8402)).
- Automatically remove oldest unpublished relay in order to make space when the user wants to add more; don't allow more than 5 relays overall ([#8428](https://github.com/chatmail/core/pull/8428)).
- Add silent group changes messages as InNoticed, not InSeen.
- Remove `?emailaddress` argument from autoconfig URL that is not using a dedicated domain.
- Remove `imap::Session::sync_seen_flags()` ([#7742](https://github.com/chatmail/core/pull/7742)).
- Use CAPABILITY response code if IMAP LOGIN command returns it.
- Increase max idle timeout for iroh backup receiver to 60 seconds.
### Fixes
- Request MDNs for resent channel messages.
- Make pre-messages w/o text want MDNs ([#8004](https://github.com/chatmail/core/pull/8004)).
- Make truncated edited messages have HTML for receivers ([#8249](https://github.com/chatmail/core/pull/8249)).
- Un-escape message footer marks in full messages (`get_html`) ([#8427](https://github.com/chatmail/core/pull/8427)).
- Hide synced chat if we only know its visibility ([#8343](https://github.com/chatmail/core/pull/8343)).
- Tombstone MDN before sending it ([#8252](https://github.com/chatmail/core/pull/8252)).
- Recreate `imap_markseen` with `PRIMARY KEY` constraint.
- Rerun the full securejoin protocol if the address was outdated ([#8358](https://github.com/chatmail/core/pull/8358)).
- Return early from `receive_imf` to not tombstone Iroh-Node-Addr message if webxdc instance isn't found ([#8372](https://github.com/chatmail/core/pull/8372)).
- Replace `last_added_location_id` with `last_added_location_timestamp`.
- Do not put locations into pre-messages.
- RUSTSEC-2026-0204 ([#8403](https://github.com/chatmail/core/pull/8403)).
- Ensure public key signatures are not in the past compared to the public key.
- Do not bubble up errors in IMAP candidate loop.
- Do not log errors if full message is not available on any transport.
- Apply reactions that arrived before the message at later time ([#8415](https://github.com/chatmail/core/pull/8415)).
### Performance
- Add timestamp to `msgs_index7` and speed up `Chatlist::try_load()` ([#7848](https://github.com/chatmail/core/pull/7848)).
### CI
- Update Rust to 1.97.1.
- rrsync prepends the restricted upload path, we need to leave it out ([#8405](https://github.com/chatmail/core/pull/8405)).
### Documentation
- Update STYLE.md: macros should be used only when necessary ([#8410](https://github.com/chatmail/core/pull/8410)).
- `create_group_chat_unencrypted()` may lead to chat split on the first device.
### Refactor
- Deprecate unused `SkipAutocrypt` param.
- Remove commented out `RenderedEmail.envelope`.
- Remove the ability to send messages with non-standard header protection.
- Make `crate::pgp::symm_encrypt_message` non-async.
- Move `ensure_secret_key_exists` into key.rs.
- Improve comment ([#8366](https://github.com/chatmail/core/pull/8366)).
- Remove `set_modseq()` function.
- Remove unnecessary reference in format string.
- Label the loop iterating over the candidates.
- Remove `GROUP BY c.id` from chatlist queries.
### Tests
- securejoin: Check that "vc-{,request-}pubkey" messages don't contain displayname.
### Miscellaneous Tasks
- bump version to 2.54.0-dev.
- deps: bump taiki-e/install-action from 2.81.1 to 2.81.8.
- deps: bump taiki-e/install-action from 2.81.8 to 2.81.11.
- update rPGP from 0.19.0 to 0.20.0.
- update astral-tokio-tar from 0.6.2 to 0.6.3.
- deps: bump anyhow to 1.0.103.
- deps: bump actions/checkout from 6 to 7.
- cargo: bump syn from 2.0.117 to 2.0.118.
- cargo: bump quote from 1.0.45 to 1.0.46.
- cargo: bump bytes from 1.11.1 to 1.12.0.
- cargo: bump regex from 1.12.3 to 1.12.4.
- cargo: bump log from 0.4.31 to 0.4.33.
- cargo: bump hyper from 1.9.0 to 1.10.1.
- deps: bump zizmorcore/zizmor-action from 0.5.6 to 0.5.7.
- cargo: bump chrono from 0.4.44 to 0.4.45.
- update quick-xml to 0.41.0.
- cargo: bump brotli from 8.0.2 to 8.0.4.
- cargo: bump smallvec from 1.15.1 to 1.15.2.
- deps: bump taiki-e/install-action from 2.81.11 to 2.82.6.
- update yanked spin@0.9.8 and spin@0.10.0.
- deps: bump taiki-e/install-action from 2.82.6 to 2.82.10.
- update async-imap to 0.11.3.
## [2.53.0] - 2026-06-15
### Features / Changes
@@ -8374,3 +8501,6 @@ https://github.com/chatmail/core/pulls?q=is%3Apr+is%3Aclosed
[2.51.0]: https://github.com/chatmail/core/compare/v2.50.0..v2.51.0
[2.52.0]: https://github.com/chatmail/core/compare/v2.51.0..v2.52.0
[2.53.0]: https://github.com/chatmail/core/compare/v2.52.0..v2.53.0
[2.54.0]: https://github.com/chatmail/core/compare/v2.53.0..v2.54.0
[2.55.0]: https://github.com/chatmail/core/compare/v2.54.0..v2.55.0
[2.56.0]: https://github.com/chatmail/core/compare/v2.55.0..v2.56.0

View File

@@ -2,51 +2,45 @@ cmake_minimum_required(VERSION 3.16)
project(deltachat LANGUAGES C)
include(GNUInstallDirs)
option(WITH_JSONRPC_BINDINGS "Generate jsonrpc bindings" OFF)
find_program(CARGO cargo)
if(APPLE)
set(DYNAMIC_EXT "dylib")
set(DYNAMIC_EXT "dylib")
elseif(UNIX)
set(DYNAMIC_EXT "so")
set(DYNAMIC_EXT "so")
else()
set(DYNAMIC_EXT "dll")
set(DYNAMIC_EXT "dll")
endif()
if(DEFINED ENV{CARGO_BUILD_TARGET})
set(CARGO_OUT_DIR "${CMAKE_BINARY_DIR}/target/$ENV{CARGO_BUILD_TARGET}/release")
set(ARCH_DIR "$ENV{CARGO_BUILD_TARGET}")
else()
set(CARGO_OUT_DIR "${CMAKE_BINARY_DIR}/target/release")
set(ARCH_DIR "./")
endif()
if(WITH_JSONRPC_BINDINGS)
set(JSONRPC_ARGS --package deltachat-jsonrpc-bindings)
endif()
add_custom_target(
lib_deltachat
ALL
COMMAND
${CMAKE_COMMAND} -E env
CARGO_TARGET_DIR="${CMAKE_BINARY_DIR}/target"
PREFIX="${CMAKE_INSTALL_PREFIX}"
LIBDIR="${CMAKE_INSTALL_FULL_LIBDIR}"
INCLUDEDIR="${CMAKE_INSTALL_FULL_INCLUDEDIR}"
${CARGO} build --release --package deltachat_ffi ${JSONRPC_ARGS}
WORKING_DIRECTORY
"${CMAKE_CURRENT_SOURCE_DIR}"
add_custom_command(
OUTPUT
"${CMAKE_BINARY_DIR}/target/release/libdeltachat.a"
"${CMAKE_BINARY_DIR}/target/release/libdeltachat.${DYNAMIC_EXT}"
"${CMAKE_BINARY_DIR}/target/release/pkgconfig/deltachat.pc"
COMMAND
PREFIX=${CMAKE_INSTALL_PREFIX}
LIBDIR=${CMAKE_INSTALL_FULL_LIBDIR}
INCLUDEDIR=${CMAKE_INSTALL_FULL_INCLUDEDIR}
${CARGO} build --target-dir=${CMAKE_BINARY_DIR}/target --release
WORKING_DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR}/deltachat-ffi
)
install(FILES "deltachat-ffi/deltachat.h" DESTINATION "${CMAKE_INSTALL_INCLUDEDIR}")
install(FILES "${CARGO_OUT_DIR}/libdeltachat.a" DESTINATION "${CMAKE_INSTALL_LIBDIR}")
install(FILES "${CARGO_OUT_DIR}/libdeltachat.${DYNAMIC_EXT}" DESTINATION "${CMAKE_INSTALL_LIBDIR}")
install(FILES "${CARGO_OUT_DIR}/pkgconfig/deltachat.pc" DESTINATION "${CMAKE_INSTALL_LIBDIR}/pkgconfig")
add_custom_target(
lib_deltachat
ALL
DEPENDS
"${CMAKE_BINARY_DIR}/target/release/libdeltachat.a"
"${CMAKE_BINARY_DIR}/target/release/libdeltachat.${DYNAMIC_EXT}"
"${CMAKE_BINARY_DIR}/target/release/pkgconfig/deltachat.pc"
)
if(WITH_JSONRPC_BINDINGS)
set(JSONRPC_HPP "${CMAKE_CURRENT_SOURCE_DIR}/deltachat-jsonrpc-bindings/qt/deltachat-jsonrpc")
install(FILES "${JSONRPC_HPP}/generated/types.hpp" "${JSONRPC_HPP}/generated/client.hpp"
DESTINATION "${CMAKE_INSTALL_INCLUDEDIR}/deltachat-jsonrpc/generated")
install(FILES "${JSONRPC_HPP}/cffi_client.hpp"
DESTINATION "${CMAKE_INSTALL_INCLUDEDIR}/deltachat-jsonrpc")
endif()
install(FILES "deltachat-ffi/deltachat.h" DESTINATION ${CMAKE_INSTALL_INCLUDEDIR})
install(FILES "${CMAKE_BINARY_DIR}/target/${ARCH_DIR}/release/libdeltachat.a" DESTINATION ${CMAKE_INSTALL_LIBDIR})
install(FILES "${CMAKE_BINARY_DIR}/target/${ARCH_DIR}/release/libdeltachat.${DYNAMIC_EXT}" DESTINATION ${CMAKE_INSTALL_LIBDIR})
install(FILES "${CMAKE_BINARY_DIR}/target/${ARCH_DIR}/release/pkgconfig/deltachat.pc" DESTINATION ${CMAKE_INSTALL_LIBDIR}/pkgconfig)

336
Cargo.lock generated
View File

@@ -124,9 +124,9 @@ checksum = "8901269c6307e8d93993578286ac0edf7f195079ffff5ebdeea6a59ffb7e36bc"
[[package]]
name = "anyhow"
version = "1.0.102"
version = "1.0.103"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7f202df86484c868dbad7eaa557ef785d5c66295e41b460ef922eca0723b842c"
checksum = "2a4385e2e34eb35d6b3efe798b9eb88096925d87726c0798709bf56d9ed84af3"
[[package]]
name = "argon2"
@@ -177,7 +177,7 @@ checksum = "965c2d33e53cb6b267e148a4cb0760bc01f4904c1cd4bb4002a085bb016d1490"
dependencies = [
"proc-macro2",
"quote",
"syn 2.0.117",
"syn 2.0.118",
"synstructure",
]
@@ -189,20 +189,20 @@ checksum = "7b18050c2cd6fe86c3a76584ef5e0baf286d038cda203eb6223df2cc413565f7"
dependencies = [
"proc-macro2",
"quote",
"syn 2.0.117",
"syn 2.0.118",
]
[[package]]
name = "astral-tokio-tar"
version = "0.6.2"
version = "0.6.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "cb50a7aae84a03bf55b067832bc376f4961b790c97e64d3eacee97d389b90277"
checksum = "08648fef353ab39a9d26f909ad53fc4f071be4c91853b78523f5cc3d9e5ebffd"
dependencies = [
"filetime",
"futures-core",
"libc",
"portable-atomic",
"rustc-hash",
"rustix 0.38.44",
"tokio",
"tokio-stream",
]
@@ -271,9 +271,9 @@ dependencies = [
[[package]]
name = "async-imap"
version = "0.11.2"
version = "0.11.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a78dceaba06f029d8f4d7df20addd4b7370a30206e3926267ecda2915b0f3f66"
checksum = "9a6728e0f7931b36d725ac234fcb02539e9f7888dbeaaa8a18d9ea5792181570"
dependencies = [
"async-channel 2.5.0",
"async-compression",
@@ -339,7 +339,7 @@ checksum = "644dd749086bf3771a2fbc5f256fdb982d53f011c7d5d560304eafeecebce79d"
dependencies = [
"proc-macro2",
"quote",
"syn 2.0.117",
"syn 2.0.118",
]
[[package]]
@@ -391,28 +391,6 @@ version = "1.4.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ace50bade8e6234aa140d9a2f552bbee1db4d353f69b8217bc503490fc1a9f26"
[[package]]
name = "aws-lc-rs"
version = "1.17.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5ec2f1fc3ec205783a5da9a7e6c1509cc69dedf09a1949e412c1e18469326d00"
dependencies = [
"aws-lc-sys",
"zeroize",
]
[[package]]
name = "aws-lc-sys"
version = "0.41.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1a2f9779ce85b93ab6170dd940ad0169b5766ff848247aff13bb788b832fe3f4"
dependencies = [
"cc",
"cmake",
"dunce",
"fs_extra",
]
[[package]]
name = "backon"
version = "1.5.0"
@@ -480,7 +458,7 @@ checksum = "57413e4b276d883b77fb368b7b33ae6a5eb97692852d49a5394d4f72ba961827"
dependencies = [
"proc-macro2",
"quote",
"syn 2.0.117",
"syn 2.0.118",
"thiserror 2.0.18",
]
@@ -621,7 +599,7 @@ dependencies = [
"proc-macro-crate 2.0.0",
"proc-macro2",
"quote",
"syn 2.0.117",
"syn 2.0.118",
]
[[package]]
@@ -660,9 +638,9 @@ checksum = "102dbef1187b1893e6dfe05a774e79fd52265f49f214f6879c8ff49f52c8188b"
[[package]]
name = "brotli"
version = "8.0.2"
version = "8.0.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "4bd8b9603c7aa97359dbd97ecf258968c95f3adddd6db2f7e7a5bef101c84560"
checksum = "5cc91aac060a7a1e25823bdccbfb6af1875b88f17c6daac97894eed8207166b3"
dependencies = [
"alloc-no-stdlib",
"alloc-stdlib",
@@ -720,9 +698,9 @@ checksum = "8f1fe948ff07f4bd06c30984e69f5b4899c516a3ef74f34df92a2df2ab535495"
[[package]]
name = "bytes"
version = "1.11.1"
version = "1.12.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1e748733b7cbc798e1434b6ac524f0c1ff2ab456fe201501e6497c8417a4fc33"
checksum = "8ae3f5d315924270530207e2a68396c3cc547f6dca3fbdca317cfb1a51edb593"
dependencies = [
"serde",
]
@@ -790,8 +768,6 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "556e016178bb5662a08681bbe0f00f8e17631781a4dfc8c45e466e4b185ec27f"
dependencies = [
"find-msvc-tools",
"jobserver",
"libc",
"shlex",
]
@@ -852,9 +828,9 @@ dependencies = [
[[package]]
name = "chrono"
version = "0.4.44"
version = "0.4.45"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c673075a2e0e5f4a1dde27ce9dee1ea4558c7ffe648f576438a20ca1d2acc4b0"
checksum = "1aa79e62e7697b8e29b513a68abacf485adcd1fe8284a4316c5ae868e6633327"
dependencies = [
"iana-time-zone",
"num-traits",
@@ -945,15 +921,6 @@ dependencies = [
"digest",
]
[[package]]
name = "cmake"
version = "0.1.58"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c0f78a02292a74a88ac736019ab962ece0bc380e3f977bf72e376c5d78ff0678"
dependencies = [
"cc",
]
[[package]]
name = "cobs"
version = "0.2.3"
@@ -1147,9 +1114,9 @@ dependencies = [
[[package]]
name = "crossbeam-epoch"
version = "0.9.18"
version = "0.9.20"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5b82ac4a3c2ca9c3460964f020e1402edd5753411d7737aa39c3714ad1b5420e"
checksum = "2d6914041f254d6e9176c01941b21115dcfb7089e55135a35411081bd106ef3f"
dependencies = [
"crossbeam-utils",
]
@@ -1278,7 +1245,7 @@ checksum = "f46882e17999c6cc590af592290432be3bce0428cb0d5f8b6715e4dc7b383eb3"
dependencies = [
"proc-macro2",
"quote",
"syn 2.0.117",
"syn 2.0.118",
]
[[package]]
@@ -1319,7 +1286,7 @@ dependencies = [
"proc-macro2",
"quote",
"strsim",
"syn 2.0.117",
"syn 2.0.118",
]
[[package]]
@@ -1330,7 +1297,7 @@ checksum = "d336a2a514f6ccccaa3e09b02d41d35330c07ddf03a62165fcec10bb561c7806"
dependencies = [
"darling_core",
"quote",
"syn 2.0.117",
"syn 2.0.118",
]
[[package]]
@@ -1350,7 +1317,7 @@ dependencies = [
[[package]]
name = "deltachat"
version = "2.54.0-dev"
version = "2.57.0-dev"
dependencies = [
"anyhow",
"astral-tokio-tar",
@@ -1412,7 +1379,6 @@ dependencies = [
"sdp",
"serde",
"serde_json",
"serde_urlencoded",
"sha-1",
"sha2",
"shadowsocks",
@@ -1459,7 +1425,7 @@ dependencies = [
[[package]]
name = "deltachat-jsonrpc"
version = "2.54.0-dev"
version = "2.57.0-dev"
dependencies = [
"anyhow",
"async-channel 2.5.0",
@@ -1478,16 +1444,9 @@ dependencies = [
"yerpc",
]
[[package]]
name = "deltachat-jsonrpc-bindings"
version = "2.54.0-dev"
dependencies = [
"deltachat-jsonrpc",
]
[[package]]
name = "deltachat-repl"
version = "2.54.0-dev"
version = "2.57.0-dev"
dependencies = [
"anyhow",
"deltachat",
@@ -1503,7 +1462,7 @@ dependencies = [
[[package]]
name = "deltachat-rpc-server"
version = "2.54.0-dev"
version = "2.57.0-dev"
dependencies = [
"anyhow",
"deltachat",
@@ -1527,12 +1486,12 @@ name = "deltachat_derive"
version = "2.0.0"
dependencies = [
"quote",
"syn 2.0.117",
"syn 2.0.118",
]
[[package]]
name = "deltachat_ffi"
version = "2.54.0-dev"
version = "2.57.0-dev"
dependencies = [
"anyhow",
"deltachat",
@@ -1581,7 +1540,7 @@ checksum = "8034092389675178f570469e6c3b0465d3d30b4505c294a6550db47f3c17ad18"
dependencies = [
"proc-macro2",
"quote",
"syn 2.0.117",
"syn 2.0.118",
]
[[package]]
@@ -1611,7 +1570,7 @@ dependencies = [
"darling",
"proc-macro2",
"quote",
"syn 2.0.117",
"syn 2.0.118",
]
[[package]]
@@ -1621,7 +1580,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ab63b0e2bf4d5928aff72e83a7dace85d7bba5fe12dcc3c5a572d78caffd3f3c"
dependencies = [
"derive_builder_core",
"syn 2.0.117",
"syn 2.0.118",
]
[[package]]
@@ -1650,7 +1609,7 @@ checksum = "cb7330aeadfbe296029522e6c40f315320aba36fc43a5b3632f3795348f3bd22"
dependencies = [
"proc-macro2",
"quote",
"syn 2.0.117",
"syn 2.0.118",
"unicode-xid",
]
@@ -1662,7 +1621,7 @@ checksum = "bda628edc44c4bb645fbe0f758797143e4e07926f7ebf4e9bdfbd3d2ce621df3"
dependencies = [
"proc-macro2",
"quote",
"syn 2.0.117",
"syn 2.0.118",
"unicode-xid",
]
@@ -1717,7 +1676,7 @@ dependencies = [
"libc",
"option-ext",
"redox_users",
"windows-sys 0.59.0",
"windows-sys 0.61.1",
]
[[package]]
@@ -1728,7 +1687,7 @@ checksum = "487585f4d0c6655fe74905e2504d8ad6908e4db67f744eb140876906c2f3175d"
dependencies = [
"proc-macro2",
"quote",
"syn 2.0.117",
"syn 2.0.118",
]
[[package]]
@@ -1767,12 +1726,6 @@ dependencies = [
"zeroize",
]
[[package]]
name = "dunce"
version = "1.0.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "92773504d58c093f6de2459af4af33faa518c13451eb8f2b5698ed3d36e7c813"
[[package]]
name = "dyn-clone"
version = "1.0.18"
@@ -1797,7 +1750,7 @@ checksum = "7a4102713839a8c01c77c165bc38ef2e83948f6397fa1e1dcfacec0f07b149d3"
dependencies = [
"proc-macro2",
"quote",
"syn 2.0.117",
"syn 2.0.118",
]
[[package]]
@@ -1926,7 +1879,7 @@ dependencies = [
"heck 0.4.1",
"proc-macro2",
"quote",
"syn 2.0.117",
"syn 2.0.118",
]
[[package]]
@@ -1946,7 +1899,7 @@ checksum = "fc4caf64a58d7a6d65ab00639b046ff54399a39f5f2554728895ace4b297cd79"
dependencies = [
"proc-macro2",
"quote",
"syn 2.0.117",
"syn 2.0.118",
]
[[package]]
@@ -2086,18 +2039,6 @@ version = "0.2.9"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "28dea519a9695b9977216879a3ebfddf92f1c08c05d984f8996aecd6ecdc811d"
[[package]]
name = "filetime"
version = "0.2.25"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "35c0522e981e68cbfa8c3f978441a5f34b30b96e146b33cd3359176b50fe8586"
dependencies = [
"cfg-if",
"libc",
"libredox",
"windows-sys 0.59.0",
]
[[package]]
name = "find-msvc-tools"
version = "0.1.9"
@@ -2161,12 +2102,6 @@ dependencies = [
name = "format-flowed"
version = "1.0.0"
[[package]]
name = "fs_extra"
version = "1.3.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "42703706b716c37f96a77aea830392ad231f44c9e9a67872fa5548707e11b11c"
[[package]]
name = "funty"
version = "2.0.0"
@@ -2198,7 +2133,7 @@ dependencies = [
"diatomic-waker",
"futures-core",
"pin-project-lite",
"spin 0.9.8",
"spin 0.9.9",
]
[[package]]
@@ -2270,7 +2205,7 @@ checksum = "e835b70203e41293343137df5c0664546da5745f82ec9b84d40be8336958447b"
dependencies = [
"proc-macro2",
"quote",
"syn 2.0.117",
"syn 2.0.118",
]
[[package]]
@@ -2410,9 +2345,9 @@ dependencies = [
[[package]]
name = "h2"
version = "0.4.7"
version = "0.4.15"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ccae279728d634d083c00f6099cb58f01cc99c145b84b8be2f6c74618d79922e"
checksum = "6cb093c84e8bd9b188d4c4a8cb6579fc016968d14c99882163cd3ff402a4f155"
dependencies = [
"atomic-waker",
"bytes",
@@ -2688,9 +2623,9 @@ dependencies = [
[[package]]
name = "hyper"
version = "1.9.0"
version = "1.10.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "6299f016b246a94207e63da54dbe807655bf9e00044f73ded42c3ac5305fbcca"
checksum = "55281c53a1894c864990125767da440a4e630446785086f52523b20033b74498"
dependencies = [
"atomic-waker",
"bytes",
@@ -2740,7 +2675,7 @@ dependencies = [
"hyper",
"libc",
"pin-project-lite",
"socket2 0.5.9",
"socket2 0.6.3",
"tokio",
"tower-service",
"tracing",
@@ -2884,7 +2819,7 @@ checksum = "1ec89e9337638ecdc08744df490b221a7399bf8d164eb52a665454e60e075ad6"
dependencies = [
"proc-macro2",
"quote",
"syn 2.0.117",
"syn 2.0.118",
]
[[package]]
@@ -3161,7 +3096,7 @@ dependencies = [
"iroh-metrics-derive",
"itoa",
"serde",
"snafu",
"snafu 0.8.5",
"tracing",
]
@@ -3174,7 +3109,7 @@ dependencies = [
"heck 0.5.0",
"proc-macro2",
"quote",
"syn 2.0.117",
"syn 2.0.118",
]
[[package]]
@@ -3293,16 +3228,6 @@ version = "1.0.10"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b1a46d1a171d865aa5f83f92695765caa047a9b4cbae2cbf37dbd613a793fd4c"
[[package]]
name = "jobserver"
version = "0.1.34"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9afb3de4395d6b3e67a780b6de64b51c978ecf11cb9a462c66be7d4ca9039d33"
dependencies = [
"getrandom 0.3.3",
"libc",
]
[[package]]
name = "js-sys"
version = "0.3.77"
@@ -3361,7 +3286,7 @@ version = "1.5.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "bbd2bcb4c963f2ddae06a2efc7e9f3591312473c50c6685e1f298068316e66fe"
dependencies = [
"spin 0.9.8",
"spin 0.9.9",
]
[[package]]
@@ -3384,7 +3309,6 @@ checksum = "c0ff37bd590ca25063e35af745c343cb7a0271906fb7b37e4813e8f79f00268d"
dependencies = [
"bitflags 2.11.0",
"libc",
"redox_syscall 0.5.12",
]
[[package]]
@@ -3443,9 +3367,9 @@ dependencies = [
[[package]]
name = "log"
version = "0.4.31"
version = "0.4.33"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "113b30b4cd05f7c06868fdb2854f66a7b9fece9a48425351cd532e810d74024f"
checksum = "0ceec5bc11778974d1bcb055b18002eba7f4b3518b6a0081b3af5f21666da9ad"
[[package]]
name = "loom"
@@ -3531,9 +3455,9 @@ checksum = "490cc448043f947bae3cbee9c203358d62dbee0db12107a74be5c30ccfd09771"
[[package]]
name = "memchr"
version = "2.7.4"
version = "2.8.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "78ca9ab1a0babb1e7d5695e3530886289c18cf2f87ec19a575a0abdce112e3a3"
checksum = "88904434abc2901f197fe8cc55f0445e7ded921dba5911dad2e2b39b48e663c4"
[[package]]
name = "mime"
@@ -3799,7 +3723,7 @@ dependencies = [
"netlink-proto",
"netlink-sys",
"serde",
"snafu",
"snafu 0.8.5",
"socket2 0.5.9",
"time",
"tokio",
@@ -3887,7 +3811,7 @@ version = "0.50.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7957b9740744892f114936ab4a57b3f487491bbeafaf8083688b16841a4240e5"
dependencies = [
"windows-sys 0.59.0",
"windows-sys 0.61.1",
]
[[package]]
@@ -3931,7 +3855,7 @@ checksum = "ed3955f1a9c7c0c15e092f9c887db08b1fc683305fdf6eb6684f22555355e202"
dependencies = [
"proc-macro2",
"quote",
"syn 2.0.117",
"syn 2.0.118",
]
[[package]]
@@ -3992,7 +3916,7 @@ dependencies = [
"proc-macro-crate 3.2.0",
"proc-macro2",
"quote",
"syn 2.0.117",
"syn 2.0.118",
]
[[package]]
@@ -4088,7 +4012,7 @@ checksum = "a948666b637a0f465e8564c73e89d4dde00d72d4d473cc972f390fc3dcee7d9c"
dependencies = [
"proc-macro2",
"quote",
"syn 2.0.117",
"syn 2.0.118",
]
[[package]]
@@ -4275,7 +4199,7 @@ dependencies = [
"pest_meta",
"proc-macro2",
"quote",
"syn 2.0.117",
"syn 2.0.118",
]
[[package]]
@@ -4291,9 +4215,9 @@ dependencies = [
[[package]]
name = "pgp"
version = "0.19.0"
version = "0.20.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "eaffe1ec22db286599c30ae6be75b37493b558735d86c8e59ec5c38794415fe4"
checksum = "1cfa4743b28656065ff4c0ba09e46b357a65e8c00fc2341e89084b82f87cbdf1"
dependencies = [
"aead",
"aes",
@@ -4332,6 +4256,7 @@ dependencies = [
"k256",
"log",
"md-5",
"memchr",
"ml-dsa",
"ml-kem",
"nom 8.0.0",
@@ -4343,7 +4268,6 @@ dependencies = [
"p384",
"p521",
"rand 0.8.6",
"regex",
"replace_with",
"ripemd",
"rsa",
@@ -4354,7 +4278,8 @@ dependencies = [
"signature",
"slh-dsa",
"smallvec",
"snafu",
"snafu 0.9.1",
"subtle",
"twofish",
"x25519-dalek",
"zeroize",
@@ -4387,7 +4312,7 @@ checksum = "c96395f0a926bc13b1c17622aaddda1ecb55d49c8f1bf9777e4d877800a43f8b"
dependencies = [
"proc-macro2",
"quote",
"syn 2.0.117",
"syn 2.0.118",
]
[[package]]
@@ -4506,7 +4431,7 @@ dependencies = [
"proc-macro2",
"quote",
"regex",
"syn 2.0.117",
"syn 2.0.118",
]
[[package]]
@@ -4593,7 +4518,7 @@ dependencies = [
"rand 0.8.6",
"serde",
"smallvec",
"snafu",
"snafu 0.8.5",
"socket2 0.5.9",
"time",
"tokio",
@@ -4738,7 +4663,7 @@ dependencies = [
"proc-macro-error-attr2",
"proc-macro2",
"quote",
"syn 2.0.117",
"syn 2.0.118",
]
[[package]]
@@ -4804,9 +4729,9 @@ checksum = "a993555f31e5a609f617c12db6250dedcac1b0a85076912c436e6fc9b2c8e6a3"
[[package]]
name = "quick-xml"
version = "0.39.2"
version = "0.41.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "958f21e8e7ceb5a1aa7fa87fab28e7c75976e0bfe7e23ff069e0a260f894067d"
checksum = "e660451e55124f798a69a5af3f49ccfbefbd41910eefd25caf2393e1f3473ec1"
dependencies = [
"memchr",
]
@@ -4866,9 +4791,9 @@ dependencies = [
[[package]]
name = "quote"
version = "1.0.45"
version = "1.0.46"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "41f2619966050689382d2b44f664f4bc593e129785a36d6ee376ddf37259b924"
checksum = "dfbc457d0c7a0759a614551b11a6409e5951f6c7537be1f1b7682b9ae9230368"
dependencies = [
"proc-macro2",
]
@@ -5056,9 +4981,9 @@ dependencies = [
[[package]]
name = "regex"
version = "1.12.3"
version = "1.12.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e10754a14b9137dd7b1e3e5b0493cc9171fdd105e0ab477f51b72e7f3ac0e276"
checksum = "f1292b7759ae1cb9ec195452d1390a074f0cd8541ab7a5a8c31cd6db45d4a6ba"
dependencies = [
"aho-corasick",
"memchr",
@@ -5085,9 +5010,9 @@ checksum = "53a49587ad06b26609c52e423de037e7f57f20d53535d66e08c695f347df952a"
[[package]]
name = "regex-syntax"
version = "0.8.8"
version = "0.8.11"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7a2d987857b319362043e95f5353c0535c1f58eec5336fdfcf626430af7def58"
checksum = "d6f6ff9a378485b298a5286656da665ba74413d36db0979633275d2e708145d4"
[[package]]
name = "replace_with"
@@ -5288,7 +5213,7 @@ dependencies = [
"errno",
"libc",
"linux-raw-sys 0.12.1",
"windows-sys 0.52.0",
"windows-sys 0.61.1",
]
[[package]]
@@ -5297,7 +5222,8 @@ version = "0.23.37"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "758025cb5fccfd3bc2fd74708fd4682be41d99e5dff73c377c0646c6012c73a4"
dependencies = [
"aws-lc-rs",
"brotli",
"brotli-decompressor",
"log",
"once_cell",
"ring",
@@ -5343,7 +5269,6 @@ version = "0.103.13"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "61c429a8649f110dddef65e2a5ad240f747e85f7758a6bccc7e5777bd33f756e"
dependencies = [
"aws-lc-rs",
"ring",
"rustls-pki-types",
"untrusted",
@@ -5440,7 +5365,7 @@ dependencies = [
"proc-macro2",
"quote",
"serde_derive_internals",
"syn 2.0.117",
"syn 2.0.118",
]
[[package]]
@@ -5572,7 +5497,7 @@ checksum = "d540f220d3187173da220f885ab66608367b6574e925011a9353e4badda91d79"
dependencies = [
"proc-macro2",
"quote",
"syn 2.0.117",
"syn 2.0.118",
]
[[package]]
@@ -5583,7 +5508,7 @@ checksum = "330f01ce65a3a5fe59a60c82f3c9a024b573b8a6e875bd233fe5f934e71d54e3"
dependencies = [
"proc-macro2",
"quote",
"syn 2.0.117",
"syn 2.0.118",
]
[[package]]
@@ -5727,7 +5652,7 @@ dependencies = [
"serde_urlencoded",
"shadowsocks-crypto",
"socket2 0.5.9",
"spin 0.10.0",
"spin 0.10.1",
"thiserror 2.0.18",
"tokio",
"tokio-tfo",
@@ -5840,9 +5765,9 @@ dependencies = [
[[package]]
name = "smallvec"
version = "1.15.1"
version = "1.15.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "67b1b7a3b5fe4f1376887184045fcf45c69e92af734b7aaddc05fb777b6fbd03"
checksum = "8ed6a63f02c8539c91a8685a86f4099661ba3da017932f6ebbea6de3f0fa7c90"
[[package]]
name = "smawk"
@@ -5856,7 +5781,16 @@ version = "0.8.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "223891c85e2a29c3fe8fb900c1fae5e69c2e42415e3177752e8718475efa5019"
dependencies = [
"snafu-derive",
"snafu-derive 0.8.5",
]
[[package]]
name = "snafu"
version = "0.9.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d1a012328be2e3f5d5f6f3218147ca02588cea4cb865e876849ab6debcf36522"
dependencies = [
"snafu-derive 0.9.1",
]
[[package]]
@@ -5868,7 +5802,19 @@ dependencies = [
"heck 0.5.0",
"proc-macro2",
"quote",
"syn 2.0.117",
"syn 2.0.118",
]
[[package]]
name = "snafu-derive"
version = "0.9.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5f103c50866b8743da9429b8a581d81a27c2d3a9c4ac7df8f8571c1dd7896eda"
dependencies = [
"heck 0.5.0",
"proc-macro2",
"quote",
"syn 2.0.118",
]
[[package]]
@@ -5893,15 +5839,15 @@ dependencies = [
[[package]]
name = "spin"
version = "0.9.8"
version = "0.9.9"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "6980e8d7511241f8acf4aebddbb1ff938df5eebe98691418c4468d0b72a96a67"
checksum = "3763264f6b73151db08c50ff20d7d8a0b8796e021cdea7ceedad07b80155fa0e"
[[package]]
name = "spin"
version = "0.10.0"
version = "0.10.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d5fe4ccb98d9c292d56fec89a5e07da7fc4cf0dc11e156b41793132775d3e591"
checksum = "023a211cb3138dbc438680b32560ad89f699977624c9f8dbb95a47d5b4c07dd3"
dependencies = [
"lock_api",
]
@@ -5965,7 +5911,7 @@ dependencies = [
"proc-macro2",
"quote",
"rustversion",
"syn 2.0.117",
"syn 2.0.118",
]
[[package]]
@@ -5977,7 +5923,7 @@ dependencies = [
"heck 0.5.0",
"proc-macro2",
"quote",
"syn 2.0.117",
"syn 2.0.118",
]
[[package]]
@@ -6048,9 +5994,9 @@ dependencies = [
[[package]]
name = "syn"
version = "2.0.117"
version = "2.0.118"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e665b8803e7b1d2a727f4023456bbbbe74da67099c585258af0ad9c5013b9b99"
checksum = "1b9ae57f904213ebb649ce6895b8a66c66f0203b9319718f69a5612a065b1422"
dependencies = [
"proc-macro2",
"quote",
@@ -6074,7 +6020,7 @@ checksum = "c8af7666ab7b6390ab78131fb5b0fce11d6b7a6951602017c35fa82800708971"
dependencies = [
"proc-macro2",
"quote",
"syn 2.0.117",
"syn 2.0.118",
]
[[package]]
@@ -6154,7 +6100,7 @@ dependencies = [
"getrandom 0.3.3",
"once_cell",
"rustix 1.1.4",
"windows-sys 0.52.0",
"windows-sys 0.61.1",
]
[[package]]
@@ -6209,7 +6155,7 @@ checksum = "4fee6c4efc90059e10f81e6d42c60a18f76588c3d74cb83a0b242a2b6c7504c1"
dependencies = [
"proc-macro2",
"quote",
"syn 2.0.117",
"syn 2.0.118",
]
[[package]]
@@ -6220,7 +6166,7 @@ checksum = "ebc4ee7f67670e9b64d05fa4253e753e016c6c95ff35b89b7941d6b856dec1d5"
dependencies = [
"proc-macro2",
"quote",
"syn 2.0.117",
"syn 2.0.118",
]
[[package]]
@@ -6335,7 +6281,7 @@ checksum = "385a6cb71ab9ab790c5fe8d67f1645e6c450a7ce006a33de03daa956cf70a496"
dependencies = [
"proc-macro2",
"quote",
"syn 2.0.117",
"syn 2.0.118",
]
[[package]]
@@ -6535,7 +6481,7 @@ checksum = "7490cfa5ec963746568740651ac6781f701c9c5ea257c58e057f3ba8cf69e8da"
dependencies = [
"proc-macro2",
"quote",
"syn 2.0.117",
"syn 2.0.118",
]
[[package]]
@@ -6585,7 +6531,7 @@ checksum = "70977707304198400eb4835a78f6a9f928bf41bba420deb8fdb175cd965d77a7"
dependencies = [
"proc-macro2",
"quote",
"syn 2.0.117",
"syn 2.0.118",
]
[[package]]
@@ -6630,7 +6576,7 @@ dependencies = [
"proc-macro-error2",
"proc-macro2",
"quote",
"syn 2.0.117",
"syn 2.0.118",
]
[[package]]
@@ -6831,7 +6777,7 @@ dependencies = [
"log",
"proc-macro2",
"quote",
"syn 2.0.117",
"syn 2.0.118",
"wasm-bindgen-shared",
]
@@ -6866,7 +6812,7 @@ checksum = "8ae87ea40c9f689fc23f209965b6fb8a99ad69aeeb0231408be24920604395de"
dependencies = [
"proc-macro2",
"quote",
"syn 2.0.117",
"syn 2.0.118",
"wasm-bindgen-backend",
"wasm-bindgen-shared",
]
@@ -7049,7 +6995,7 @@ checksum = "2bbd5b46c938e506ecbce286b6628a02171d56153ba733b6c741fc627ec9579b"
dependencies = [
"proc-macro2",
"quote",
"syn 2.0.117",
"syn 2.0.118",
]
[[package]]
@@ -7060,7 +7006,7 @@ checksum = "83577b051e2f49a058c308f17f273b570a6a758386fc291b5f6a934dd84e48c1"
dependencies = [
"proc-macro2",
"quote",
"syn 2.0.117",
"syn 2.0.118",
]
[[package]]
@@ -7071,7 +7017,7 @@ checksum = "053c4c462dc91d3b1504c6fe5a726dd15e216ba718e84a0e46a88fbe5ded3515"
dependencies = [
"proc-macro2",
"quote",
"syn 2.0.117",
"syn 2.0.118",
]
[[package]]
@@ -7082,7 +7028,7 @@ checksum = "cb26fd936d991781ea39e87c3a27285081e3c0da5ca0fcbc02d368cc6f52ff01"
dependencies = [
"proc-macro2",
"quote",
"syn 2.0.117",
"syn 2.0.118",
]
[[package]]
@@ -7514,7 +7460,8 @@ dependencies = [
[[package]]
name = "yerpc"
version = "0.6.4"
source = "git+https://github.com/d2weber/yerpc.git?branch=qt_bindings#ba7aead1ffa49dc89bb85322aba38a65a0694e7b"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1dc24983fbe850227bfc1de89bf8cbfb3e2463afc322e0de2f155c4c23d06445"
dependencies = [
"anyhow",
"async-channel 1.9.0",
@@ -7532,14 +7479,15 @@ dependencies = [
[[package]]
name = "yerpc_derive"
version = "0.6.4"
source = "git+https://github.com/d2weber/yerpc.git?branch=qt_bindings#ba7aead1ffa49dc89bb85322aba38a65a0694e7b"
version = "0.6.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "4d8560d021437420316370db865e44c000bf86380b47cf05e49be9d652042bf5"
dependencies = [
"convert_case",
"darling",
"proc-macro2",
"quote",
"syn 2.0.117",
"syn 2.0.118",
]
[[package]]
@@ -7562,7 +7510,7 @@ checksum = "2380878cad4ac9aac1e2435f3eb4020e8374b5f13c296cb75b4620ff8e229154"
dependencies = [
"proc-macro2",
"quote",
"syn 2.0.117",
"syn 2.0.118",
"synstructure",
]
@@ -7590,7 +7538,7 @@ checksum = "fa4f8080344d4671fb4e831a13ad1e68092748387dfc4f55e356242fae12ce3e"
dependencies = [
"proc-macro2",
"quote",
"syn 2.0.117",
"syn 2.0.118",
]
[[package]]
@@ -7610,7 +7558,7 @@ checksum = "595eed982f7d355beb85837f651fa22e90b3c044842dc7f2c2842c086f295808"
dependencies = [
"proc-macro2",
"quote",
"syn 2.0.117",
"syn 2.0.118",
"synstructure",
]
@@ -7631,7 +7579,7 @@ checksum = "ce36e65b0d2999d2aafac989fb249189a141aee1f53c612c1f37d72631959f69"
dependencies = [
"proc-macro2",
"quote",
"syn 2.0.117",
"syn 2.0.118",
]
[[package]]
@@ -7653,7 +7601,7 @@ checksum = "6eafa6dfb17584ea3e2bd6e76e0cc15ad7af12b09abdd1ca55961bed9b1063c6"
dependencies = [
"proc-macro2",
"quote",
"syn 2.0.117",
"syn 2.0.118",
]
[[package]]

View File

@@ -1,6 +1,6 @@
[package]
name = "deltachat"
version = "2.54.0-dev"
version = "2.57.0-dev"
edition = "2024"
license = "MPL-2.0"
rust-version = "1.89"
@@ -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.1", default-features = false, features = ["runtime-tokio", "compress"] }
async-imap = { version = "0.11.3", default-features = false, features = ["runtime-tokio", "compress"] }
async-native-tls = { version = "0.6", default-features = false, features = ["runtime-tokio"] }
async-smtp = { version = "0.10.2", default-features = false, features = ["runtime-tokio"] }
async_zip = { version = "0.0.18", default-features = false, features = ["deflate", "tokio-fs"] }
@@ -78,10 +78,10 @@ num-derive = "0.4"
num-traits = { workspace = true }
parking_lot = "0.12.4"
percent-encoding = "2.3"
pgp = { version = "0.19.0", features = ["draft-pqc"], default-features = false }
pgp = { version = "0.20.0", features = ["draft-pqc"], default-features = false }
pin-project = "1"
qrcodegen = "1.7.0"
quick-xml = { version = "0.39", features = ["escape-html"] }
quick-xml = { version = "0.41", features = ["escape-html"] }
rand-old = { package = "rand", version = "0.8" }
rand = { workspace = true }
regex = { workspace = true }
@@ -89,7 +89,6 @@ rusqlite = { workspace = true, features = ["sqlcipher"] }
sanitize-filename = { workspace = true }
sdp = "0.17.1"
serde_json = { workspace = true }
serde_urlencoded = "0.7.1"
serde = { workspace = true, features = ["derive"] }
sha-1 = "0.10"
sha2 = "0.10"
@@ -101,9 +100,9 @@ tagger = "4.3.4"
textwrap = "0.16.2"
thiserror = { workspace = true }
tokio-io-timeout = "1.2.1"
tokio-rustls = { version = "0.26.2", default-features = false, features = ["aws-lc-rs", "tls12"] }
tokio-rustls = { version = "0.26.2", default-features = false, features = ["tls12", "brotli"] }
tokio-stream = { version = "0.1.17", features = ["fs"] }
astral-tokio-tar = { version = "0.6.2", default-features = false }
astral-tokio-tar = { version = "0.6.3", default-features = false }
tokio-util = { workspace = true }
tokio = { workspace = true, features = ["fs", "rt-multi-thread", "macros"] }
toml = "0.9"
@@ -130,7 +129,6 @@ members = [
"deltachat-ffi",
"deltachat_derive",
"deltachat-jsonrpc",
"deltachat-jsonrpc-bindings",
"deltachat-rpc-server",
"deltachat-ratelimit",
"deltachat-repl",
@@ -185,7 +183,6 @@ base64 = "0.22"
chrono = { version = "0.4.44", default-features = false }
deltachat-contact-tools = { path = "deltachat-contact-tools" }
deltachat-jsonrpc = { path = "deltachat-jsonrpc", default-features = false }
deltachat-jsonrpc-bindings = { path = "deltachat-jsonrpc-bindings", default-features = false }
deltachat = { path = ".", default-features = false }
futures = "0.3.32"
futures-lite = "2.6.1"
@@ -205,7 +202,7 @@ thiserror = "2"
tokio = "1"
tokio-util = "0.7.18"
tracing-subscriber = "0.3"
yerpc = { git="https://github.com/d2weber/yerpc.git", branch="qt_bindings" }
yerpc = "0.6.4"
[features]
default = ["vendored"]

View File

@@ -181,3 +181,10 @@ Calling `into()`, `try_into()` or `parse()`
creates an indirection,
which is hard to follow for people who are not familiar with Rust,
or who are not using rust-analyzer.
## Use macros only when really needed
Macros can be hard to read for people unfamiliar with Rust,
and can have surprising effects like evaluating arguments multiple times.
Therefore, macros should only be used when really needed;
using functions is usually better.

View File

@@ -1,7 +1,7 @@
[package]
name = "deltachat-contact-tools"
version = "0.0.0" # No semver-stable versioning
edition = "2021"
edition = "2024"
description = "Contact-related tools, like parsing vcards and sanitizing name and address. Meant for internal use in the deltachat crate."
license = "MPL-2.0"

View File

@@ -31,15 +31,15 @@ use std::fmt;
use std::ops::Deref;
use std::sync::LazyLock;
use anyhow::bail;
use anyhow::Result;
use anyhow::bail;
use regex::Regex;
mod vcard;
pub use vcard::{make_vcard, parse_vcard, VcardContact};
pub use vcard::{VcardContact, make_vcard, parse_vcard};
/// Valid contact address.
#[derive(Debug, Clone)]
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct ContactAddress(String);
impl Deref for ContactAddress {

View File

@@ -220,7 +220,10 @@ END:VCARD
assert_eq!(contacts[0].addr, "bob@example.org".to_string());
assert_eq!(contacts[0].authname, "Bob".to_string());
assert_eq!(contacts[0].key, None);
assert_eq!(contacts[0].profile_image.as_deref().unwrap(), "/9j/4AAQSkZJRgABAQAAAQABAAD/4gIoSUNDX1BST0ZJTEUAAQEAAAIYAAAAAAQwAABtbnRyUkdCIFhZWiAAAAAAAAAAAAAAAABhY3NwAAAAAAAAAAAAAAAAL8bRuAJYoZUYrI4ZY3VWwxw4Ay28AAGBISScmf/2Q==");
assert_eq!(
contacts[0].profile_image.as_deref().unwrap(),
"/9j/4AAQSkZJRgABAQAAAQABAAD/4gIoSUNDX1BST0ZJTEUAAQEAAAIYAAAAAAQwAABtbnRyUkdCIFhZWiAAAAAAAAAAAAAAAABhY3NwAAAAAAAAAAAAAAAAL8bRuAJYoZUYrI4ZY3VWwxw4Ay28AAGBISScmf/2Q=="
);
}
}
@@ -244,7 +247,10 @@ END:VCARD",
assert_eq!(contacts.len(), 1);
assert_eq!(&contacts[0].addr, "alice@example.org");
assert_eq!(&contacts[0].authname, "Alice Wonderland");
assert_eq!(contacts[0].key.as_ref().unwrap(), "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa");
assert_eq!(
contacts[0].key.as_ref().unwrap(),
"aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa"
);
assert!(contacts[0].timestamp.is_err());
assert_eq!(contacts[0].profile_image, None);
}
@@ -272,9 +278,15 @@ END:VCARD",
assert_eq!(contacts.len(), 1);
assert_eq!(&contacts[0].addr, "alice@example.org");
assert_eq!(&contacts[0].authname, "Alice");
assert_eq!(contacts[0].key.as_ref().unwrap(), "xsaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa==");
assert_eq!(
contacts[0].key.as_ref().unwrap(),
"xsaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa=="
);
assert!(contacts[0].timestamp.is_err());
assert_eq!(contacts[0].profile_image.as_ref().unwrap(), "/9aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa/Z");
assert_eq!(
contacts[0].profile_image.as_ref().unwrap(),
"/9aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa/Z"
);
}
#[test]

View File

@@ -1,8 +1,8 @@
[package]
name = "deltachat_ffi"
version = "2.54.0-dev"
version = "2.57.0-dev"
description = "Deltachat FFI"
edition = "2018"
edition = "2024"
readme = "README.md"
license = "MPL-2.0"

View File

@@ -462,7 +462,10 @@ char* dc_get_blobdir (const dc_context_t* context);
* - `gossip_period` = How often to gossip Autocrypt keys in chats with multiple recipients, in
* seconds. 2 days by default.
* This is not supposed to be changed by UIs and only used for testing.
* - `is_chatmail` = 1 if the the server is a chatmail server, 0 otherwise.
* - `is_chatmail` = (deprecated) 1 if the the server is a chatmail server, 0 otherwise.
* This is deprecated, UIs should not behave differently
* for chatmail relays and classical email servers.
* Most usages in UIs can be replaced by `force_encryption`.
* - `is_muted` = Whether a context is muted by the user.
* Muted contexts should not sound, vibrate or show notifications.
* In contrast to `dc_set_chat_mute_duration()`,
@@ -500,7 +503,6 @@ char* dc_get_blobdir (const dc_context_t* context);
* - `send_pw` = SMTP-password, guessed if left out
* - `send_port` = SMTP-port, guessed if left out
* - `send_security`= SMTP-socket, one of @ref DC_SOCKET, defaults to #DC_SOCKET_AUTO
* - `server_flags` = IMAP-/SMTP-flags as a combination of @ref DC_LP flags, guessed if left out
* - `proxy_enabled` = Proxy enabled. Disabled by default.
* - `proxy_url` = Proxy URL. May contain multiple URLs separated by newline, but only the first one is used.
* - `imap_certificate_checks` = how to check IMAP and SMTP certificates, one of the @ref DC_CERTCK flags, defaults to #DC_CERTCK_AUTO (0)
@@ -587,36 +589,7 @@ int dc_set_config_from_qr (dc_context_t* context, const char* qr);
char* dc_get_info (const dc_context_t* context);
/**
* Get URL that can be used to initiate an OAuth2 authorization.
*
* If an OAuth2 authorization is possible for a given e-mail address,
* this function returns the URL that should be opened in a browser.
*
* If the user authorizes access,
* the given redirect_uri is called by the provider.
* It's up to the UI to handle this call.
*
* The provider will attach some parameters to the URL,
* most important the parameter `code` that should be set as the `mail_pw`.
* With `server_flags` set to #DC_LP_AUTH_OAUTH2,
* dc_configure() can be called as usual afterwards.
*
* @memberof dc_context_t
* @param context The context object.
* @param addr E-mail address the user has entered.
* In case the user selects a different e-mail address during
* authorization, this is corrected in dc_configure()
* @param redirect_uri URL that will get `code` that is used as `mail_pw` then.
* Not all URLs are allowed here, however, the following should work:
* `chat.delta:/PATH`, `http://localhost:PORT/PATH`,
* `https://localhost:PORT/PATH`, `urn:ietf:wg:oauth:2.0:oob`
* (the latter just displays the code the user can copy+paste then)
* @return URL that can be opened in the browser to start OAuth2.
* Returned strings must be released using dc_str_unref().
* If OAuth2 is not possible for the given e-mail address, NULL is returned.
*/
char* dc_get_oauth2_url (dc_context_t* context, const char* addr, const char* redirect_uri);
#define DC_CONNECTIVITY_NOT_CONNECTED 1000
@@ -710,7 +683,7 @@ int dc_get_push_state (dc_context_t* context);
* to get the full configuration from well-known URLs.
*
* - If _more_ options as `mail_server`, `mail_port`, `send_server`,
* `send_port`, `send_user` or `server_flags` are specified,
* `send_port` or `send_user` are specified,
* **autoconfigure/autodiscover is skipped**.
*
* While dc_configure() returns immediately,
@@ -5733,41 +5706,6 @@ int64_t dc_lot_get_timestamp (const dc_lot_t* lot);
* @}
*/
/**
* @defgroup DC_LP DC_LP
*
* Flags for configuring IMAP and SMTP servers.
* These flags are optional
* and may be set together with the username, password etc.
* via dc_set_config() using the key "server_flags".
*
* @addtogroup DC_LP
* @{
*/
/**
* Force OAuth2 authorization. This flag does not skip automatic configuration.
* Before calling dc_configure() with DC_LP_AUTH_OAUTH2 set,
* the user has to confirm access at the URL returned by dc_get_oauth2_url().
*/
#define DC_LP_AUTH_OAUTH2 0x2
/**
* Force NORMAL authorization, this is the default.
* If this flag is set, automatic configuration is skipped.
*/
#define DC_LP_AUTH_NORMAL 0x4
/**
* @}
*/
#define DC_LP_AUTH_FLAGS (DC_LP_AUTH_OAUTH2|DC_LP_AUTH_NORMAL) // if none of these flags are set, the default is chosen
/**
* @defgroup DC_CERTCK DC_CERTCK
*

File diff suppressed because it is too large Load Diff

View File

@@ -17,13 +17,15 @@ use std::ptr;
/// }
/// ```
unsafe fn dc_strdup(s: *const libc::c_char) -> *mut libc::c_char {
let ret: *mut libc::c_char = if !s.is_null() {
libc::strdup(s)
} else {
libc::calloc(1, 1) as *mut libc::c_char
};
assert!(!ret.is_null());
ret
unsafe {
let ret: *mut libc::c_char = if !s.is_null() {
libc::strdup(s)
} else {
libc::calloc(1, 1) as *mut libc::c_char
};
assert!(!ret.is_null());
ret
}
}
/// Error type for the [OsStrExt] trait
@@ -164,34 +166,40 @@ pub(crate) trait Strdup {
/// This function will panic when the original string contains an
/// interior null byte as this can not be represented in raw C
/// strings.
unsafe fn strdup(&self) -> *mut libc::c_char;
fn strdup(&self) -> *mut libc::c_char;
}
impl Strdup for str {
unsafe fn strdup(&self) -> *mut libc::c_char {
let tmp = CString::new_lossy(self);
dc_strdup(tmp.as_ptr())
fn strdup(&self) -> *mut libc::c_char {
unsafe {
let tmp = CString::new_lossy(self);
dc_strdup(tmp.as_ptr())
}
}
}
impl Strdup for String {
unsafe fn strdup(&self) -> *mut libc::c_char {
fn strdup(&self) -> *mut libc::c_char {
let s: &str = self;
s.strdup()
}
}
impl Strdup for std::path::Path {
unsafe fn strdup(&self) -> *mut libc::c_char {
let tmp = self.to_c_string().unwrap_or_else(|_| CString::default());
dc_strdup(tmp.as_ptr())
fn strdup(&self) -> *mut libc::c_char {
unsafe {
let tmp = self.to_c_string().unwrap_or_else(|_| CString::default());
dc_strdup(tmp.as_ptr())
}
}
}
impl Strdup for [u8] {
unsafe fn strdup(&self) -> *mut libc::c_char {
let tmp = CString::new_lossy(self);
dc_strdup(tmp.as_ptr())
fn strdup(&self) -> *mut libc::c_char {
unsafe {
let tmp = CString::new_lossy(self);
dc_strdup(tmp.as_ptr())
}
}
}
@@ -209,15 +217,15 @@ pub(crate) trait OptStrdup {
/// Allocate a new raw C `*char` version of this string, or NULL.
///
/// See [Strdup::strdup] for details.
unsafe fn strdup(&self) -> *mut libc::c_char;
fn strdup(&self) -> *mut libc::c_char;
}
impl<T: AsRef<str>> OptStrdup for Option<T> {
unsafe fn strdup(&self) -> *mut libc::c_char {
fn strdup(&self) -> *mut libc::c_char {
match self {
Some(s) => {
let tmp = CString::new_lossy(s.as_ref());
dc_strdup(tmp.as_ptr())
unsafe { dc_strdup(tmp.as_ptr()) }
}
None => ptr::null_mut(),
}
@@ -258,11 +266,9 @@ pub(crate) fn to_opt_string_lossy(s: *const libc::c_char) -> Option<String> {
pub(crate) fn as_path<'a>(s: *const libc::c_char) -> &'a std::path::Path {
assert!(!s.is_null(), "cannot be used on null pointers");
use std::os::unix::ffi::OsStrExt;
unsafe {
let c_str = std::ffi::CStr::from_ptr(s).to_bytes();
let os_str = std::ffi::OsStr::from_bytes(c_str);
std::path::Path::new(os_str)
}
let c_str = unsafe { std::ffi::CStr::from_ptr(s) }.to_bytes();
let os_str = std::ffi::OsStr::from_bytes(c_str);
std::path::Path::new(os_str)
}
// as_path() implementation for windows, documented above.

View File

@@ -1 +0,0 @@
generated

View File

@@ -1,14 +0,0 @@
[package]
name = "deltachat-jsonrpc-bindings"
version = "2.54.0-dev"
description = "Autogenerate DeltaChat JSON-RPC API bindings at build time"
edition = "2024"
license = "MPL-2.0"
repository = "https://github.com/chatmail/core"
[build-dependencies]
deltachat-jsonrpc = { workspace = true }
[features]
default = ["vendored"]
vendored = ["deltachat-jsonrpc/vendored"]

View File

@@ -1,7 +0,0 @@
use deltachat_jsonrpc::api::{write_qt_bindings, write_ts_bindings};
use std::path::Path;
fn main() {
write_ts_bindings(Path::new("typescript/generated"));
write_qt_bindings(Path::new("qt/generated"), "deltachat");
}

View File

@@ -1 +0,0 @@
generated

View File

@@ -1,104 +0,0 @@
#pragma once
#include "deltachat-jsonrpc/generated/types.hpp"
#include "deltachat-jsonrpc/generated/client.hpp"
#include "deltachat.h"
#include <cstdint>
#include <mutex>
#include <thread>
namespace deltachat {
class CffiTransport : public Transport {
public:
explicit CffiTransport(dc_accounts_t* accounts)
: jsonrpc_(dc_jsonrpc_init(accounts))
{
if (!jsonrpc_) std::abort();
thread_ = std::thread([this] { run(); });
}
virtual ~CffiTransport() {
done_ = true;
// Unblock dc_jsonrpc_next_response by sending a dummy request
if (jsonrpc_) dc_jsonrpc_request(jsonrpc_, "{\"jsonrpc\":\"2.0\",\"id\":0,\"method\":\"get_system_info\"}");
if (thread_.joinable()) thread_.join();
std::lock_guard lk(mu_);
for (auto& [id, prom] : pending_) {
prom.set_value({{}, "Transport destructed", -32060});
}
pending_.clear();
if (jsonrpc_) dc_jsonrpc_unref(jsonrpc_);
}
virtual std::future<Result<QJsonValue>> send(const QString method, const QJsonValue params) override {
uint32_t id = next_id_++;
QJsonObject envelope{
{"jsonrpc", "2.0"},
{"id", static_cast<qint64>(id)},
{"method", method},
{"params", params},
};
std::promise<Result<QJsonValue>> prom;
std::future<Result<QJsonValue>> fut = prom.get_future();
{
std::lock_guard lk(mu_);
pending_[id] = std::move(prom);
}
QByteArray json = QJsonDocument(envelope).toJson(QJsonDocument::Compact);
dc_jsonrpc_request(jsonrpc_, json.constData());
return fut;
}
private:
void run() {
while (!done_) {
char* raw_json = dc_jsonrpc_next_response(jsonrpc_);
if (!raw_json) {
break;
}
QByteArray json{raw_json};
dc_str_unref(raw_json);
if (done_) break;
QJsonObject obj = QJsonDocument::fromJson(json).object();
if (!obj["id"].isDouble()) {
qCritical() << "No valid rpc id in" << QString{json};
continue;
}
uint32_t id = static_cast<uint32_t>(obj["id"].toInt());
std::promise<Result<QJsonValue>> prom;
{
std::lock_guard<std::mutex> lk(mu_);
if (auto nh = pending_.extract(id)) {
prom = std::move(nh.mapped());
} else {
qCritical() << "Could not map response" << QString{json};
continue;
}
}
prom.set_value(parseResult(obj));
}
}
private:
dc_jsonrpc_instance_t* jsonrpc_;
std::thread thread_;
std::mutex mu_;
std::atomic<uint32_t> next_id_{1};
std::atomic<bool> done_{false};
std::unordered_map<uint32_t, std::promise<Result<QJsonValue>>> pending_;
};
class CffiDeltaChat : public RawClient {
public:
explicit CffiDeltaChat(dc_accounts_t* accounts)
: RawClient(std::make_unique<CffiTransport>(accounts)) {}
};
}

View File

@@ -1 +0,0 @@

View File

@@ -1,3 +0,0 @@
fn main() {
println!("Hello, world!");
}

View File

@@ -1,8 +1,8 @@
[package]
name = "deltachat-jsonrpc"
version = "2.54.0-dev"
version = "2.57.0-dev"
description = "DeltaChat JSON-RPC API"
edition = "2021"
edition = "2024"
license = "MPL-2.0"
repository = "https://github.com/chatmail/core"

View File

@@ -5,26 +5,27 @@ use std::sync::Arc;
use std::time::Duration;
use std::{collections::HashMap, str::FromStr};
use anyhow::{anyhow, bail, ensure, Context, Result};
use anyhow::{Context, Result, anyhow, bail, ensure};
use deltachat::EventEmitter;
pub use deltachat::accounts::Accounts;
use deltachat::blob::BlobObject;
use deltachat::calls::ice_servers;
use deltachat::chat::{
self, add_contact_to_chat, forward_msgs, forward_msgs_2ctx, get_chat_media, get_chat_msgs,
get_chat_msgs_ex, markfresh_chat, marknoticed_all_chats, marknoticed_chat,
remove_contact_from_chat, Chat, ChatId, ChatItem, MessageListOptions,
self, Chat, ChatId, ChatItem, MessageListOptions, add_contact_to_chat, forward_msgs,
forward_msgs_2ctx, get_chat_media, get_chat_msgs, get_chat_msgs_ex, markfresh_chat,
marknoticed_all_chats, marknoticed_chat, remove_contact_from_chat,
};
use deltachat::chatlist::Chatlist;
use deltachat::config::{get_all_ui_config_keys, Config};
use deltachat::config::{Config, get_all_ui_config_keys};
use deltachat::constants::DC_MSG_ID_DAYMARKER;
use deltachat::contact::{may_be_valid_addr, Contact, ContactId, Origin};
use deltachat::contact::{Contact, ContactId, Origin, may_be_valid_addr};
use deltachat::context::get_info;
use deltachat::ephemeral::Timer;
use deltachat::imex;
use deltachat::location;
use deltachat::message::{
self, delete_msgs_ex, get_existing_msg_ids, get_msg_read_receipt_count, get_msg_read_receipts,
markseen_msgs, Message, MessageState, MsgId, Viewtype,
self, Message, MessageState, MsgId, Viewtype, delete_msgs_ex, get_existing_msg_ids,
get_msg_read_receipt_count, get_msg_read_receipts, markseen_msgs,
};
use deltachat::peer_channels::{
leave_webxdc_realtime, send_webxdc_realtime_advertisement, send_webxdc_realtime_data,
@@ -37,10 +38,9 @@ use deltachat::securejoin;
use deltachat::stock_str::StockMessage;
use deltachat::storage_usage::{get_blobdir_storage_usage, get_storage_usage};
use deltachat::webxdc::StatusUpdateSerial;
use deltachat::EventEmitter;
use sanitize_filename::is_sanitized;
use tokio::fs;
use tokio::sync::{watch, Mutex, RwLock};
use tokio::sync::{Mutex, RwLock, watch};
use types::login_param::EnteredLoginParam;
use yerpc::rpc;
@@ -67,7 +67,7 @@ use self::types::{
JsonrpcMessageListItem, MessageNotificationInfo, MessageSearchResult, MessageViewtype,
},
};
use crate::api::types::chat_list::{get_chat_list_item_by_id, ChatListItemFetchResult};
use crate::api::types::chat_list::{ChatListItemFetchResult, get_chat_list_item_by_id};
use crate::api::types::login_param::TransportListEntry;
use crate::api::types::qr::{QrObject, SecurejoinSource, SecurejoinUiPath};
@@ -157,7 +157,7 @@ impl CommandApi {
}
}
#[rpc(all_positional)]
#[rpc(all_positional, ts_outdir = "typescript/generated")]
impl CommandApi {
/// Test function.
async fn sleep(&self, delay: f64) {
@@ -526,7 +526,7 @@ impl CommandApi {
/// - [Self::add_transport_from_qr()] to add a transport
/// from a server encoded in a QR code.
/// - [Self::list_transports()] to get a list of all configured transports.
/// - [Self::delete_transport()] to remove a transport.
/// - [Self::set_transport_unpublished()] to remove a transport.
/// - [Self::set_transport_unpublished()] to set whether contacts see this transport.
async fn add_or_update_transport(
&self,
@@ -552,23 +552,31 @@ impl CommandApi {
/// Returns the list of all email accounts that are used as a transport in the current profile.
/// Use [Self::add_or_update_transport()] to add or change a transport
/// and [Self::delete_transport()] to delete a transport.
/// Use [Self::list_transports_ex()] to additionally query
/// whether the transports are marked as 'unpublished'.
/// and [Self::set_transport_unpublished()] to remove a transport.
async fn list_transports(&self, account_id: u32) -> Result<Vec<EnteredLoginParam>> {
let ctx = self.get_context(account_id).await?;
let res = ctx
.list_transports()
.await?
.into_iter()
.filter(|t| !t.is_unpublished)
.map(|t| t.param.into())
.collect();
Ok(res)
}
/// Deprecated 2026-06: This is not needed by UI implementations anymore,
/// because unpublished relays now count as removed from the user point of view,
/// and must not be shown in the list of relays.
/// This means that UIs should use `list_transports()` instead of this function.
///
/// Returns the list of all email accounts that are used as a transport in the current profile.
///
/// As opposed to `list_transports()`, this function also returns unpublished transports,
/// and for each returned transport it returns the information whether or not is `unpublished`.
///
/// Use [Self::add_or_update_transport()] to add or change a transport
/// and [Self::delete_transport()] to delete a transport.
/// and [Self::set_transport_unpublished()] to change whether a transport is 'published'.
async fn list_transports_ex(&self, account_id: u32) -> Result<Vec<TransportListEntry>> {
let ctx = self.get_context(account_id).await?;
let res = ctx
@@ -580,23 +588,31 @@ impl CommandApi {
Ok(res)
}
/// Removes the transport with the specified email address
/// (i.e. [EnteredLoginParam::addr]).
/// Immediately deletes a transport, potentially causing messages not to arrive.
/// This must ONLY be used by the automated tests.
/// UI implementations must use [`Self::set_transport_unpublished`] instead.
async fn delete_transport(&self, account_id: u32, addr: String) -> Result<()> {
let ctx = self.get_context(account_id).await?;
ctx.delete_transport(&addr).await
}
/// Change whether the transport is unpublished.
/// UIs should call this function when the user clicks on "Remove".
/// Core will keep listening on this transport for some time,
/// and automatically remove it once it is no longer needed.
///
/// Unpublished transports are not advertised to contacts,
/// and self-sent messages are not sent there,
/// so that we don't cause extra messages to the corresponding inbox,
/// but can still receive messages from contacts who don't know our new transport addresses yet.
///
/// The default is false, but when the user updates from a version that didn't have this flag,
/// existing secondary transports are set to unpublished,
/// so that an existing transport address doesn't suddenly get spammed with a lot of messages.
/// When more transports are added by [`Self::add_or_update_transport()`] or [`Self::add_transport_from_qr`],
/// the least recently needed unpublished transport is automatically removed
/// if this is necessary in order to stay below the maximum number of allowed relays.
/// Also, unpublished transports that are not used to receive any new messages for a time defined by
/// [`UNPUBLISHED_TRANSPORT_KEEP_TIME`] are automatically removed.
///
/// [`UNPUBLISHED_TRANSPORT_KEEP_TIME`]: deltachat::sql::UNPUBLISHED_TRANSPORT_KEEP_TIME
async fn set_transport_unpublished(
&self,
account_id: u32,
@@ -2279,7 +2295,7 @@ impl CommandApi {
let message = Message::load_from_db(&ctx, MsgId::new(instance_msg_id)).await?;
let blob = message.get_webxdc_blob(&ctx, &path).await?;
use base64::{engine::general_purpose, Engine as _};
use base64::{Engine as _, engine::general_purpose};
Ok(general_purpose::STANDARD_NO_PAD.encode(blob))
}

View File

@@ -1,6 +1,6 @@
use anyhow::{Context as _, Result};
use deltachat::calls::{call_state, CallState};
use deltachat::calls::{CallState, call_state};
use deltachat::context::Context;
use deltachat::message::MsgId;
use serde::Serialize;

View File

@@ -1,7 +1,7 @@
use std::time::{Duration, SystemTime};
use anyhow::{bail, Context as _, Result};
use deltachat::chat::{self, get_chat_contacts, get_past_chat_contacts, ChatVisibility};
use anyhow::{Context as _, Result, bail};
use deltachat::chat::{self, ChatVisibility, get_chat_contacts, get_past_chat_contacts};
use deltachat::chat::{Chat, ChatId};
use deltachat::constants::Chattype;
use deltachat::contact::{Contact, ContactId};

View File

@@ -4,7 +4,7 @@ use deltachat::chatlist::get_last_message_for_chat;
use deltachat::constants::*;
use deltachat::contact::Contact;
use deltachat::{
chat::{get_chat_contacts, ChatVisibility},
chat::{ChatVisibility, get_chat_contacts},
chatlist::Chatlist,
};
use num_traits::cast::ToPrimitive;

View File

@@ -16,7 +16,7 @@ pub struct HttpResponse {
impl From<CoreHttpResponse> for HttpResponse {
fn from(response: CoreHttpResponse) -> Self {
use base64::{engine::general_purpose, Engine as _};
use base64::{Engine as _, engine::general_purpose};
let blob = general_purpose::STANDARD_NO_PAD.encode(response.blob);
let mimetype = response.mimetype;
let encoding = response.encoding;

View File

@@ -66,10 +66,6 @@ pub struct EnteredLoginParam {
/// invalid hostnames.
/// Default: Automatic
pub certificate_checks: Option<EnteredCertificateChecks>,
/// If true, login via OAUTH2 (not recommended anymore).
/// Default: false
pub oauth2: Option<bool>,
}
impl From<dc::TransportListEntry> for TransportListEntry {
@@ -100,7 +96,6 @@ impl From<dc::EnteredLoginParam> for EnteredLoginParam {
smtp_user: param.smtp.user.into_option(),
smtp_password: param.smtp.password.into_option(),
certificate_checks: certificate_checks.into_option(),
oauth2: param.oauth2.into_option(),
}
}
}
@@ -127,7 +122,6 @@ impl TryFrom<EnteredLoginParam> for dc::EnteredLoginParam {
password: param.smtp_password.unwrap_or_default(),
},
certificate_checks: param.certificate_checks.unwrap_or_default().into(),
oauth2: param.oauth2.unwrap_or_default(),
})
}
}

View File

@@ -236,6 +236,7 @@ impl From<Qr> for QrObject {
invitenumber,
authcode,
is_v3,
..
} => {
let contact_id = contact_id.to_u32();
let fingerprint = fingerprint.human_readable();
@@ -255,6 +256,7 @@ impl From<Qr> for QrObject {
invitenumber,
authcode,
is_v3,
..
} => {
let contact_id = contact_id.to_u32();
let fingerprint = fingerprint.human_readable();
@@ -276,6 +278,7 @@ impl From<Qr> for QrObject {
authcode,
invitenumber,
is_v3,
..
} => {
let contact_id = contact_id.to_u32();
let fingerprint = fingerprint.human_readable();

View File

@@ -54,5 +54,5 @@
},
"type": "module",
"types": "dist/deltachat.d.ts",
"version": "2.54.0-dev"
"version": "2.57.0-dev"
}

View File

@@ -2,7 +2,7 @@
name = "ratelimit"
version = "1.0.0"
description = "Token bucket implementation"
edition = "2021"
edition = "2024"
license = "MPL-2.0"
[dependencies]

View File

@@ -1,8 +1,8 @@
[package]
name = "deltachat-repl"
version = "2.54.0-dev"
version = "2.57.0-dev"
license = "MPL-2.0"
edition = "2021"
edition = "2024"
repository = "https://github.com/chatmail/core"
[dependencies]

View File

@@ -5,7 +5,7 @@ use std::path::Path;
use std::str::FromStr;
use std::time::Duration;
use anyhow::{bail, ensure, Result};
use anyhow::{Result, bail, ensure};
use deltachat::chat::{self, Chat, ChatId, ChatItem, ChatVisibility, MuteDuration};
use deltachat::chatlist::*;
use deltachat::constants::*;
@@ -319,7 +319,6 @@ pub async fn cmdline(context: Context, line: &str, chat_id: &mut ChatId) -> Resu
info\n\
set <configuration-key> [<value>]\n\
get <configuration-key>\n\
oauth2\n\
configure\n\
connect\n\
disconnect\n\
@@ -1226,12 +1225,11 @@ pub async fn cmdline(context: Context, line: &str, chat_id: &mut ChatId) -> Resu
"fileinfo" => {
ensure!(!arg1.is_empty(), "Argument <file> missing.");
if let Ok(buf) = read_file(&context, Path::new(arg1)).await {
let (width, height) = get_filemeta(&buf)?;
println!("width={width}, height={height}");
} else {
let Ok(buf) = read_file(&context, Path::new(arg1)).await else {
bail!("Command failed.");
}
};
let (width, height) = get_filemeta(&buf)?;
println!("width={width}, height={height}");
}
"estimatedeletion" => {
ensure!(!arg1.is_empty(), "Argument <seconds> missing");

View File

@@ -9,14 +9,12 @@ extern crate deltachat;
use std::borrow::Cow::{self, Borrowed, Owned};
use anyhow::{bail, Error};
use anyhow::{Error, bail};
use deltachat::EventType;
use deltachat::chat::ChatId;
use deltachat::config;
use deltachat::context::*;
use deltachat::oauth2::*;
use deltachat::qr_code_generator::get_securejoin_qr_svg;
use deltachat::securejoin::*;
use deltachat::EventType;
use log::{error, info, warn};
use nu_ansi_term::Color;
use rustyline::completion::{Completer, FilenameCompleter, Pair};
@@ -162,11 +160,10 @@ const IMEX_COMMANDS: [&str; 10] = [
"stop",
];
const DB_COMMANDS: [&str; 11] = [
const DB_COMMANDS: [&str; 10] = [
"info",
"set",
"get",
"oauth2",
"configure",
"connect",
"disconnect",
@@ -425,19 +422,6 @@ async fn handle_cmd(
"configure" => {
ctx.configure().await?;
}
"oauth2" => {
if let Some(addr) = ctx.get_config(config::Config::Addr).await? {
if let Some(oauth2_url) =
get_oauth2_url(&ctx, &addr, "chat.delta:/com.b44t.messenger").await?
{
println!("Open the following url, set mail_pw to the generated token and server_flags to 2:\n{oauth2_url}");
} else {
println!("OAuth2 not available for {addr}.");
}
} else {
println!("oauth2: set addr first.");
}
}
"clear" => {
println!("\n\n\n");
print!("\x1b[1;1H\x1b[2J");

View File

@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
[project]
name = "deltachat-rpc-client"
version = "2.54.0-dev"
version = "2.57.0-dev"
license = "MPL-2.0"
description = "Python client for Delta Chat core JSON-RPC interface"
classifiers = [

View File

@@ -133,6 +133,10 @@ class Account:
"""Delete a transport."""
self._rpc.delete_transport(self.id, addr)
def set_transport_unpublished(self, addr: str, unpublished: bool = True):
"""Unpublish the transport."""
self._rpc.set_transport_unpublished(self.id, addr, unpublished)
@futuremethod
def list_transports(self):
"""Return the list of all email accounts that are used as a transport in the current profile."""

View File

@@ -231,14 +231,6 @@ class KeyGenType(IntEnum):
RSA4096 = 3
# "Lp" means "login parameters"
class LpAuthFlag(IntEnum):
"""Authorization flags."""
OAUTH2 = 0x2
NORMAL = 0x4
class MediaQuality(IntEnum):
"""Media quality setting."""

View File

@@ -237,10 +237,21 @@ def test_transport_limit(acfactory) -> None:
account.add_transport_from_qr(qr)
second_addr = account.list_transports()[1]["addr"]
account.delete_transport(second_addr)
third_addr = account.list_transports()[2]["addr"]
# test that adding a transport after deleting one works again
# test that adding a transport after unpublishing one works again
account.set_transport_unpublished(second_addr)
account.add_transport_from_qr(qr)
with pytest.raises(JsonRpcError):
account.add_transport_from_qr(qr)
# UIs are not expected to delete transports directly,
# but we still test that adding a transport
# after deleting one instead of unpublishing works.
account.delete_transport(third_addr)
account.add_transport_from_qr(qr)
with pytest.raises(JsonRpcError):
account.add_transport_from_qr(qr)
def test_message_info_imap_urls(acfactory) -> None:

View File

@@ -704,3 +704,25 @@ def test_withdraw_securejoin_qr(acfactory):
and "Ignoring RequestWithAuth message because of invalid auth code." in event.msg
):
break
def test_qr_scan_updates_new_relay_address(acfactory):
alice, bob = acfactory.get_online_accounts(2)
bob_alice_chat = bob.secure_join(alice.get_qr_code())
alice.wait_for_securejoin_inviter_success()
bob.wait_for_securejoin_joiner_success()
for ac in [alice, bob]:
old_addr = ac.get_config("configured_addr")
ac.add_transport_from_qr(acfactory.get_account_qr())
ac.set_config("configured_addr", ac.list_transports()[1]["addr"])
ac.delete_transport(old_addr)
bob.secure_join(alice.get_qr_code())
alice.wait_for_securejoin_inviter_success()
bob.wait_for_securejoin_joiner_success()
bob_alice_chat.send_text("hi")
snapshot = alice.wait_for_incoming_msg().get_snapshot()
assert snapshot.text == "hi"

View File

@@ -221,6 +221,30 @@ def test_account(acfactory) -> None:
alice.stop_io()
def test_mark_fresh_vs_self_mdn(acfactory) -> None:
alice, bob = acfactory.get_online_accounts(2)
bob.set_config("bcc_self", "1")
alice_contact_bob = alice.create_contact(bob)
alice_chat = alice_contact_bob.create_chat()
alice_chat.send_text("Hello!")
event = bob.wait_for_incoming_msg_event()
chat_id = event.chat_id
msg_id = event.msg_id
bob_chat = bob.get_chat_by_id(chat_id)
message = bob.get_message_by_id(msg_id)
bob_chat.accept()
bob.mark_seen_messages([message])
bob_chat.mark_fresh()
assert bob_chat.get_fresh_message_count() == 1
alice.wait_for_event(EventType.MSG_READ)
alice_chat.send_text("You've read 'Hello!'")
bob.wait_for_incoming_msg_event()
assert bob_chat.get_fresh_message_count() == 2
def test_chat(acfactory) -> None:
alice, bob = acfactory.get_online_accounts(2)
@@ -356,7 +380,7 @@ def test_receive_imf_failure(acfactory) -> None:
snapshot.text == "❌ Failed to receive a message:"
" Condition failed: `!context.get_config_bool(Config::SimulateReceiveImfError).await?`."
f" Core version {version}."
" Please report this bug to delta@merlinux.eu or https://support.delta.chat/."
" Please report this bug to delta@merlinux.eu or https://support.delta.chat/"
)
# The failed message doesn't break the IMAP loop.

View File

@@ -1,8 +1,8 @@
[package]
name = "deltachat-rpc-server"
version = "2.54.0-dev"
version = "2.57.0-dev"
description = "DeltaChat JSON-RPC server"
edition = "2021"
edition = "2024"
readme = "README.md"
license = "MPL-2.0"

View File

@@ -15,5 +15,5 @@
},
"type": "module",
"types": "index.d.ts",
"version": "2.54.0-dev"
"version": "2.57.0-dev"
}

View File

@@ -56,7 +56,7 @@ for (const { folder_name, package_name } of platform_package_names) {
if (is_local) {
package_json.peerDependencies["@deltachat/jsonrpc-client"] =
`file:${join(expected_cwd, "/../../deltachat-jsonrpc-bindings/typescript")}`;
`file:${join(expected_cwd, "/../../deltachat-jsonrpc/typescript")}`;
} else {
package_json.peerDependencies["@deltachat/jsonrpc-client"] = "*";
}

View File

@@ -6,7 +6,7 @@ use std::env;
use std::path::PathBuf;
use std::sync::Arc;
use anyhow::{anyhow, Context as _, Result};
use anyhow::{Context as _, Result, anyhow};
use deltachat::constants::DC_VERSION_STR;
use deltachat_jsonrpc::api::{Accounts, CommandApi};
use futures_lite::stream::StreamExt;

View File

@@ -2,7 +2,7 @@
name = "deltachat-time"
version = "1.0.0"
description = "Time-related tools"
edition = "2021"
edition = "2024"
license = "MPL-2.0"
[dependencies]

View File

@@ -1,7 +1,7 @@
[package]
name = "deltachat_derive"
version = "2.0.0"
edition = "2018"
edition = "2024"
license = "MPL-2.0"
[lib]

View File

@@ -14,7 +14,7 @@ pub fn to_sql_derive(input: TokenStream) -> TokenStream {
let ast: syn::DeriveInput = syn::parse(input).unwrap();
let name = &ast.ident;
let gen = quote! {
let r#gen = quote! {
impl rusqlite::types::ToSql for #name {
fn to_sql(&self) -> rusqlite::Result<rusqlite::types::ToSqlOutput> {
let num = *self as i64;
@@ -24,7 +24,7 @@ pub fn to_sql_derive(input: TokenStream) -> TokenStream {
}
}
};
gen.into()
r#gen.into()
}
#[proc_macro_derive(FromSql)]
@@ -32,7 +32,7 @@ pub fn from_sql_derive(input: TokenStream) -> TokenStream {
let ast: syn::DeriveInput = syn::parse(input).unwrap();
let name = &ast.ident;
let gen = quote! {
let r#gen = quote! {
impl rusqlite::types::FromSql for #name {
fn column_result(col: rusqlite::types::ValueRef) -> rusqlite::types::FromSqlResult<Self> {
let inner = rusqlite::types::FromSql::column_result(col)?;
@@ -44,5 +44,5 @@ pub fn from_sql_derive(input: TokenStream) -> TokenStream {
}
}
};
gen.into()
r#gen.into()
}

View File

@@ -83,6 +83,8 @@ skip = [
{ name = "rustix", version = "0.38.44" },
{ name = "rustls-webpki", version = "0.102.8" },
{ name = "serdect", version = "0.2.0" },
{ name = "snafu-derive", version = "0.8.5" },
{ name = "snafu", version = "0.8.5" },
{ name = "socket2", version = "0.5.9" },
{ name = "spin", version = "0.9.8" },
{ name = "strum_macros", version = "0.26.2" },

View File

@@ -51,7 +51,6 @@
./deltachat-contact-tools
./deltachat-ffi
./deltachat-jsonrpc
./deltachat-jsonrpc-bindings
./deltachat-ratelimit
./deltachat-repl
./deltachat-rpc-client

View File

@@ -2,7 +2,7 @@
name = "format-flowed"
version = "1.0.0"
description = "format=flowed support"
edition = "2021"
edition = "2024"
license = "MPL-2.0"
keywords = ["email"]

View File

@@ -224,8 +224,7 @@ mod tests {
fn test_unformat_flowed() {
let text = "this is a very long message that should be wrapped using format=flowed and \n\
unwrapped on the receiver";
let expected =
"this is a very long message that should be wrapped using format=flowed and \
let expected = "this is a very long message that should be wrapped using format=flowed and \
unwrapped on the receiver";
assert_eq!(unformat_flowed(text, false), expected);
@@ -255,8 +254,7 @@ mod tests {
assert_eq!(format_flowed_quote(quote), expected);
let quote = "this is a very long quote that should be wrapped using format=flowed and unwrapped on the receiver";
let expected =
"> this is a very long quote that should be wrapped using format=flowed and \r\n\
let expected = "> this is a very long quote that should be wrapped using format=flowed and \r\n\
> unwrapped on the receiver";
assert_eq!(format_flowed_quote(quote), expected);
}

View File

@@ -2,7 +2,7 @@
name = "deltachat-fuzz"
version = "0.0.0"
publish = false
edition = "2021"
edition = "2024"
license = "MPL-2.0"
[dev-dependencies]

View File

@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
[project]
name = "deltachat"
version = "2.54.0-dev"
version = "2.57.0-dev"
license = "MPL-2.0"
description = "Python bindings for the Delta Chat Core library using CFFI against the Rust-implemented libdeltachat"
readme = "README.rst"

View File

@@ -1 +1 @@
2026-06-15
2026-07-21

View File

@@ -145,9 +145,6 @@ def process_data(data, file):
opt = process_opt(data)
config_defaults = process_config_defaults(data)
oauth2 = data.get("oauth2", "")
oauth2 = "Some(Oauth2Authorizer::" + camel(oauth2) + ")" if oauth2 != "" else "None"
provider = ""
before_login_hint = cleanstr(data.get("before_login_hint", "") or "")
after_login_hint = cleanstr(data.get("after_login_hint", ""))
@@ -165,7 +162,6 @@ def process_data(data, file):
provider += " server: &[\n" + server + " ],\n"
provider += " opt: " + opt + ",\n"
provider += " config_defaults: " + config_defaults + ",\n"
provider += " oauth2_authorizer: " + oauth2 + ",\n"
provider += "};\n\n"
else:
raise TypeError("SMTP and IMAP must be specified together or left out both")
@@ -180,7 +176,7 @@ def process_data(data, file):
out_all += "// " + file.name + ": " + comment.strip(", ") + "\n"
# also add provider with no special things to do -
# eg. _not_ supporting oauth2 is also an information and we can skip the mx-lookup in this case
# eg. we can skip the mx-lookup in this case
out_all += provider
out_domains += domains
out_ids += ids
@@ -212,7 +208,7 @@ if __name__ == "__main__":
"use crate::provider::Socket::*;\n"
"use crate::provider::UsernamePattern::*;\n"
"use crate::provider::{\n"
" Config, ConfigDefault, Oauth2Authorizer, Provider, ProviderOptions, Server, Status,\n"
" Config, ConfigDefault, Provider, ProviderOptions, Server, Status,\n"
"};\n"
"use std::collections::HashMap;\n\n"
"use std::sync::LazyLock;\n\n"

View File

@@ -67,14 +67,13 @@ def main():
parser.add_argument("newversion")
json_list = [
"deltachat-jsonrpc-bindings/typescript/package.json",
"deltachat-jsonrpc/typescript/package.json",
"deltachat-rpc-server/npm-package/package.json",
]
toml_list = [
"Cargo.toml",
"deltachat-ffi/Cargo.toml",
"deltachat-jsonrpc/Cargo.toml",
"deltachat-jsonrpc-bindings/Cargo.toml",
"deltachat-rpc-server/Cargo.toml",
"deltachat-repl/Cargo.toml",
"python/pyproject.toml",

View File

@@ -770,7 +770,7 @@ pub(crate) fn create_fallback_ice_servers() -> Vec<UnresolvedIceServer> {
/// because it itself cannot utilize DNS. See
/// <https://github.com/deltachat/deltachat-desktop/issues/5447>.
pub async fn ice_servers(context: &Context) -> Result<String> {
if let Some(ref metadata) = *context.metadata.read().await {
if let Some(metadata) = context.metadata.read().await.values().next() {
let ice_servers = resolve_ice_servers(context, metadata.ice_servers.clone()).await?;
Ok(ice_servers)
} else {

View File

@@ -1794,8 +1794,7 @@ impl Chat {
}
let is_bot = context.get_config_bool(Config::Bot).await?;
msg.param
.set_optional(Param::Bot, Some("1").filter(|_| is_bot));
msg.param.set_optional(Param::Bot, is_bot.then_some("1"));
// Set "In-Reply-To:" to identify the message to which the composed message is a reply.
// Set "References:" to identify the "thread" of the conversation.
@@ -2926,10 +2925,9 @@ pub(crate) async fn create_send_msg_jobs(context: &Context, msg: &mut Message) -
let now = time();
if rendered_msg.last_added_location_id.is_some()
&& let Err(err) = location::set_kml_sent_timestamp(context, msg.chat_id, now).await
{
error!(context, "Failed to set kml sent_timestamp: {err:#}.");
if let Some(last_added_location_timestamp) = rendered_msg.last_added_location_timestamp {
location::set_kml_sent_timestamp(context, msg.chat_id, last_added_location_timestamp)
.await?;
}
if attach_selfavatar && let Err(err) = msg.chat_id.set_selfavatar_timestamp(context, now).await
@@ -2971,7 +2969,6 @@ WHERE id=?
)
.await?;
let chunk_size = context.get_max_smtp_rcpt_to().await?;
let trans_fn = |t: &mut rusqlite::Transaction| {
let mut row_ids = Vec::<i64>::new();
@@ -2981,16 +2978,16 @@ WHERE id=?
(),
)?;
}
let mut stmt = t.prepare(
"INSERT INTO smtp (rfc724_mid, recipients, mime, msg_id)
VALUES (?1, ?2, ?3, ?4)",
)?;
for recipients_chunk in recipients.chunks(chunk_size) {
let recipients_chunk = recipients_chunk.join(" ");
if !recipients.is_empty() {
let mut stmt = t.prepare(
"INSERT INTO smtp (rfc724_mid, recipients, mime, msg_id)
VALUES (?1, ?2, ?3, ?4)",
)?;
let all_recipients = recipients.join(" ");
if let Some(pre_msg) = &rendered_pre_msg {
let row_id = stmt.execute((
&pre_msg.rfc724_mid,
&recipients_chunk,
&all_recipients,
&pre_msg.message,
msg.id,
))?;
@@ -2998,7 +2995,7 @@ WHERE id=?
}
let row_id = stmt.execute((
&rendered_msg.rfc724_mid,
&recipients_chunk,
&all_recipients,
&rendered_msg.message,
msg.id,
))?;

View File

@@ -1374,6 +1374,18 @@ async fn test_markfresh_chat() -> Result<()> {
assert_eq!(bob_chat_id.get_fresh_msg_cnt(bob).await?, 0);
assert_eq!(bob.get_fresh_msgs().await?.len(), 0);
// Marking a message as seen results to sending an MDN to the contact and self.
message::markseen_msgs(bob, vec![bob_msg2.id]).await?;
assert_eq!(
bob.sql
.count(
"SELECT COUNT(*) FROM smtp_mdns WHERE from_id=?",
(bob_msg2.from_id,)
)
.await?,
1
);
// bob marks the chat as fresh again, fresh count is 1 again
markfresh_chat(bob, bob_chat_id).await?;
let bob_msg1 = Message::load_from_db(bob, bob_msg1.id).await?;

View File

@@ -115,35 +115,40 @@ impl Chatlist {
ChatId::new(0)
};
// select with left join and minimum:
//
// - the inner select must use `hidden` and _not_ `m.hidden`
// which would refer the outer select and take a lot of time
// - `GROUP BY` is needed several messages may have the same
// timestamp
// - the list starts with the newest chats
macro_rules! last_visible_msg_id_in {
($chat_id:expr) => {
concat!(
"
(SELECT id FROM msgs WHERE
-- state=`OutDraft`.
state=19 AND hidden=1 AND chat_id=",
$chat_id,
" OR
-- `InFresh`...`OutDelivered` inclusive, except `OutDraft`.
state IN (10,13,16,20,24,26) AND hidden=0 AND chat_id=",
$chat_id,
"
ORDER BY timestamp DESC, id DESC LIMIT 1)"
)
};
}
// The list starts with the newest chats.
//
// The query shows messages from blocked contacts in
// groups. Otherwise it would be hard to follow conversations.
let ids = if let Some(query_contact_id) = query_contact_id {
// show chats shared with a given contact
context.sql.query_map_vec(
context.sql.query_map_vec(concat!(
"SELECT c.id, m.id
FROM chats c
LEFT JOIN msgs m
ON c.id=m.chat_id
AND m.id=(
SELECT id
FROM msgs
WHERE chat_id=c.id
AND (hidden=0 OR state=?1)
ORDER BY timestamp DESC, id DESC LIMIT 1)
AND m.id=", last_visible_msg_id_in!("c.id"), "
WHERE c.id>9
AND c.blocked!=1
AND c.id IN(SELECT chat_id FROM chats_contacts WHERE contact_id=?2 AND add_timestamp >= remove_timestamp)
GROUP BY c.id
ORDER BY c.archived=?3 DESC, IFNULL(NULLIF(m.timestamp,0),c.created_timestamp) DESC, m.id DESC;",
(MessageState::OutDraft, query_contact_id, ChatVisibility::Pinned),
AND c.id IN(SELECT chat_id FROM chats_contacts WHERE contact_id=? AND add_timestamp >= remove_timestamp)
ORDER BY c.archived=? DESC, IFNULL(NULLIF(m.timestamp,0),c.created_timestamp) DESC, m.id DESC"),
(query_contact_id, ChatVisibility::Pinned),
process_row,
).await?
} else if flag_archived_only {
@@ -154,22 +159,21 @@ impl Chatlist {
context
.sql
.query_map_vec(
"SELECT c.id, m.id
concat!(
"
SELECT c.id, m.id
FROM chats c
LEFT JOIN msgs m
ON c.id=m.chat_id
AND m.id=(
SELECT id
FROM msgs
WHERE chat_id=c.id
AND (hidden=0 OR state=?)
ORDER BY timestamp DESC, id DESC LIMIT 1)
AND m.id=",
last_visible_msg_id_in!("c.id"),
"
WHERE c.id>9
AND c.blocked!=1
AND c.archived=1
GROUP BY c.id
ORDER BY IFNULL(NULLIF(m.timestamp,0),c.created_timestamp) DESC, m.id DESC;",
(MessageState::OutDraft,),
ORDER BY IFNULL(NULLIF(m.timestamp,0),c.created_timestamp) DESC, m.id DESC"
),
(),
process_row,
)
.await?
@@ -188,24 +192,18 @@ impl Chatlist {
let str_like_cmd = format!("%{}%", query.to_lowercase());
context
.sql
.query_map_vec(
.query_map_vec(concat!(
"SELECT c.id, m.id
FROM chats c
LEFT JOIN msgs m
ON c.id=m.chat_id
AND m.id=(
SELECT id
FROM msgs
WHERE chat_id=c.id
AND (hidden=0 OR state=?1)
ORDER BY timestamp DESC, id DESC LIMIT 1)
WHERE c.id>9 AND c.id!=?2
AND m.id=", last_visible_msg_id_in!("c.id"), "
WHERE c.id>9 AND c.id!=?
AND c.blocked!=1
AND IFNULL(c.name_normalized,c.name) LIKE ?3
AND (NOT ?4 OR EXISTS (SELECT 1 FROM msgs m WHERE m.chat_id = c.id AND m.state == ?5 AND hidden=0))
GROUP BY c.id
ORDER BY IFNULL(NULLIF(m.timestamp,0),c.created_timestamp) DESC, m.id DESC;",
(MessageState::OutDraft, skip_id, str_like_cmd, only_unread, MessageState::InFresh),
AND IFNULL(c.name_normalized,c.name) LIKE ?
AND (NOT ? OR EXISTS (SELECT 1 FROM msgs m WHERE m.chat_id = c.id AND m.state == ? AND hidden=0))
ORDER BY IFNULL(NULLIF(m.timestamp,0),c.created_timestamp) DESC, m.id DESC"),
(skip_id, str_like_cmd, only_unread, MessageState::InFresh),
process_row,
)
.await?
@@ -237,25 +235,19 @@ impl Chatlist {
})
.collect::<std::result::Result<Vec<_>, _>>()
};
context.sql.query_map(
context.sql.query_map(concat!(
"SELECT c.id, c.type, c.param, m.id
FROM chats c
LEFT JOIN msgs m
ON c.id=m.chat_id
AND m.id=(
SELECT id
FROM msgs
WHERE chat_id=c.id
AND (hidden=0 OR state=?)
ORDER BY timestamp DESC, id DESC LIMIT 1)
AND m.id=", last_visible_msg_id_in!("c.id"), "
WHERE c.id>9 AND c.id!=?
AND c.blocked=0
AND NOT c.archived=?
AND (c.type!=? OR c.id IN(SELECT chat_id FROM chats_contacts WHERE contact_id=? AND add_timestamp >= remove_timestamp))
GROUP BY c.id
ORDER BY c.id=? DESC, c.archived=? DESC, IFNULL(NULLIF(m.timestamp,0),c.created_timestamp) DESC, m.id DESC;",
ORDER BY c.id=? DESC, c.archived=? DESC, IFNULL(NULLIF(m.timestamp,0),c.created_timestamp) DESC, m.id DESC"),
(
MessageState::OutDraft, skip_id, ChatVisibility::Archived,
skip_id, ChatVisibility::Archived,
Chattype::Group, ContactId::SELF,
sort_id_up, ChatVisibility::Pinned,
),
@@ -264,23 +256,17 @@ impl Chatlist {
).await?
} else {
// show normal chatlist
context.sql.query_map_vec(
context.sql.query_map_vec(concat!(
"SELECT c.id, m.id
FROM chats c
LEFT JOIN msgs m
ON c.id=m.chat_id
AND m.id=(
SELECT id
FROM msgs
WHERE chat_id=c.id
AND (hidden=0 OR state=?)
ORDER BY timestamp DESC, id DESC LIMIT 1)
AND m.id=", last_visible_msg_id_in!("c.id"), "
WHERE c.id>9 AND c.id!=?
AND (c.blocked=0 OR c.blocked=2)
AND NOT c.archived=?
GROUP BY c.id
ORDER BY c.id=0 DESC, c.archived=? DESC, IFNULL(NULLIF(m.timestamp,0),c.created_timestamp) DESC, m.id DESC;",
(MessageState::OutDraft, skip_id, ChatVisibility::Archived, ChatVisibility::Pinned),
ORDER BY c.id=0 DESC, c.archived=? DESC, IFNULL(NULLIF(m.timestamp,0),c.created_timestamp) DESC, m.id DESC"),
(skip_id, ChatVisibility::Archived, ChatVisibility::Pinned),
process_row,
).await?
};

View File

@@ -17,10 +17,9 @@ use crate::context::Context;
use crate::events::EventType;
use crate::log::LogExt;
use crate::mimefactory::RECOMMENDED_FILE_SIZE;
use crate::provider::Provider;
use crate::sync::{self, Sync::*, SyncData};
use crate::tools::{get_abs_path, time};
use crate::transport::{ConfiguredLoginParam, add_pseudo_transport, send_sync_transports};
use crate::transport::{add_pseudo_transport, send_sync_transports};
use crate::{constants, stats};
/// The available configuration keys.
@@ -118,15 +117,6 @@ pub enum Config {
/// SMTP server security (e.g. TLS, STARTTLS).
SendSecurity,
/// Deprecated(2026-04).
/// Use EnteredLoginParam and add_transport{from_qr}()/list_transports() instead.
///
/// Whether to use OAuth 2.
///
/// Historically contained other bitflags, which are now deprecated.
/// Should not be extended in the future, create new config keys instead.
ServerFlags,
/// True if proxy is enabled.
///
/// Can be used to disable proxy without erasing known URLs.
@@ -298,12 +288,6 @@ pub enum Config {
/// Configured SMTP server password.
ConfiguredSendPw,
/// Deprecated(2026-04).
/// Use ConfiguredLoginParam and add_transport{from_qr}()/list_transports() instead.
///
/// Whether OAuth 2 is used with configured provider.
ConfiguredServerFlags,
/// Configured folder for incoming messages.
ConfiguredInboxFolder,
@@ -319,6 +303,12 @@ pub enum Config {
/// True if account is configured.
Configured,
/// Deprecated, we are trying to get rid of this global setting.
/// It is possible to configure a profile with both chatmail relays
/// and classical email servers.
///
/// Most usages in UIs can be replaced by `force_encryption`.
///
/// True if account is a chatmail account.
IsChatmail,
@@ -455,12 +445,6 @@ pub enum Config {
/// Return an error from `receive_imf_inner()`. For tests.
SimulateReceiveImfError,
/// Enable composing emails with Header Protection as defined in
/// <https://www.rfc-editor.org/rfc/rfc9788.html> "Header Protection for Cryptographically
/// Protected Email".
#[strum(props(default = "1"))]
StdHeaderProtectionComposing,
/// Who can call me.
///
/// The options are from the `WhoCanCallMe` enum.
@@ -629,16 +613,6 @@ impl Context {
self.get_config_bool(Config::MdnsEnabled).await
}
/// Gets the configured provider.
///
/// The provider is determined by the current primary transport.
pub async fn get_configured_provider(&self) -> Result<Option<&'static Provider>> {
let provider = ConfiguredLoginParam::load(self)
.await?
.and_then(|(_transport_id, param)| param.provider);
Ok(provider)
}
/// Gets configured "delete_device_after" value.
///
/// `None` means never delete the message, `Some(x)` means delete

View File

@@ -32,7 +32,6 @@ pub use crate::login_param::EnteredLoginParam;
use crate::login_param::{EnteredCertificateChecks, TransportListEntry};
use crate::message::Message;
use crate::net::proxy::ProxyConfig;
use crate::oauth2::get_oauth2_addr;
use crate::provider::{Protocol, Provider, Socket, UsernamePattern};
use crate::qr::{login_param_from_account_qr, login_param_from_login_qr};
use crate::smtp::Smtp;
@@ -45,9 +44,10 @@ use crate::transport::{
use crate::{EventType, stock_str};
use crate::{chat, provider};
/// Maximum number of relays
/// see <https://github.com/chatmail/core/issues/7608>
pub(crate) const MAX_TRANSPORT_RELAYS: usize = 5;
/// Maximum number of relays.
///
/// See <https://github.com/chatmail/core/issues/7608>.
pub(crate) const MAX_RELAYS: usize = 5;
/// Hard-coded candidates for default relays.
/// In the future, we want to use it during onboarding;
@@ -230,8 +230,9 @@ impl Context {
self.sql.count("SELECT COUNT(*) FROM transports", ()).await
}
/// Removes the transport with the specified email address
/// (i.e. [EnteredLoginParam::addr]).
/// Immediately deletes a transport, potentially causing messages not to arrive.
/// This must ONLY be used internally and by the automated tests.
/// UI implementations must use [`Self::set_transport_unpublished`] instead.
pub async fn delete_transport(&self, addr: &str) -> Result<()> {
let now = time();
let removed_transport_id = self
@@ -282,15 +283,20 @@ impl Context {
}
/// Change whether the transport is unpublished.
/// UIs should call this function when the user clicks on "Remove".
/// Core will keep listening on this transport for some time,
/// and automatically remove it once it is no longer needed.
///
/// Unpublished transports are not advertised to contacts,
/// and self-sent messages are not sent there,
/// so that we don't cause extra messages to the corresponding inbox,
/// but can still receive messages from contacts who don't know our new transport addresses yet.
///
/// The default is false, but when the user updates from a version that didn't have this flag,
/// existing secondary transports are set to unpublished,
/// so that an existing transport address doesn't suddenly get spammed with a lot of messages.
/// When more transports are added by [`Self::add_or_update_transport()`] or [`Self::add_transport_from_qr`],
/// the least recently needed unpublished transport is automatically removed
/// if this is necessary in order to stay below the maximum number of allowed relays.
/// Also, unpublished transports that are not used to receive any new messages for a time defined by
/// `UNPUBLISHED_TRANSPORT_KEEP_TIME` are automatically removed.
pub async fn set_transport_unpublished(&self, addr: &str, unpublished: bool) -> Result<()> {
self.sql
.transaction(|trans| {
@@ -322,25 +328,15 @@ impl Context {
async fn inner_configure(&self, param: &EnteredLoginParam) -> Result<()> {
info!(self, "Configure ...");
let old_addr = self.get_config(Config::ConfiguredAddr).await?;
if old_addr.is_some()
&& !self
.sql
.exists(
"SELECT COUNT(*) FROM transports WHERE addr=?",
(&param.addr,),
)
.await?
&& self
.sql
.count("SELECT COUNT(*) FROM transports", ())
.await?
>= MAX_TRANSPORT_RELAYS
{
bail!(
"You have reached the maximum number of relays ({}).",
MAX_TRANSPORT_RELAYS
if !self
.sql
.exists(
"SELECT COUNT(*) FROM transports WHERE addr=?",
(&param.addr,),
)
.await?
{
self.try_make_space_for_new_relay().await?;
}
let provider = match configure(self, param).await {
@@ -364,6 +360,42 @@ impl Context {
on_configure_completed(self, provider).await?;
Ok(())
}
/// This function is called before adding a new relay.
/// If the maximum number of relays ([`MAX_RELAYS`]) is already reached,
/// then it tries to make space by removing an unpublished relay.
/// If there are multiple unpublished relays,
/// the one that hasn't received a message for longest is removed.
/// If there are no unpublished relays, an error is returned.
///
/// Note that eviction happens before we know that a new relay works,
/// which is a trade-off we made in favor of implementation complexity.
async fn try_make_space_for_new_relay(&self) -> Result<()> {
if self.count_transports().await? >= MAX_RELAYS {
// Try to automatically remove the unpublished transport that wasn't used for the longest time:
if let Some(addr) = self
.sql
.query_get_value::<String>(
"SELECT addr FROM transports WHERE is_published=0
ORDER BY last_rcvd_timestamp, add_timestamp LIMIT 1",
(),
)
.await?
{
info!(
self,
"Auto-deleting relay {addr} to make space for new relay."
);
self.delete_transport(&addr).await?;
}
if self.count_transports().await? >= MAX_RELAYS {
// Apparently, all the transports are published
bail!("You have reached the maximum number of relays ({MAX_RELAYS})");
}
};
Ok(())
}
}
async fn on_configure_completed(
@@ -418,24 +450,7 @@ async fn get_configured_param(
param.smtp.password.clone()
};
let mut addr = param.addr.clone();
if param.oauth2 {
// the used oauth2 addr may differ, check this.
// if get_oauth2_addr() is not available in the oauth2 implementation, just use the given one.
progress!(ctx, 10);
if let Some(oauth2_addr) = get_oauth2_addr(ctx, &param.addr, &param.imap.password)
.await?
.and_then(|e| e.parse().ok())
{
info!(ctx, "Authorized address is {}", oauth2_addr);
addr = oauth2_addr;
ctx.sql
.set_raw_config("addr", Some(param.addr.as_str()))
.await?;
}
progress!(ctx, 20);
}
// no oauth? - just continue it's no error
let addr = param.addr.clone();
let parsed = EmailAddress::new(&param.addr).context("Bad email-address")?;
let param_domain = parsed.domain;
@@ -585,7 +600,6 @@ async fn get_configured_param(
ConfiguredCertificateChecks::AcceptInvalidCertificates
}
},
oauth2: param.oauth2,
};
Ok(configured_login_param)
}
@@ -616,7 +630,6 @@ async fn configure(ctx: &Context, param: &EnteredLoginParam) -> Result<Option<&'
&proxy_config2,
&smtp_addr,
strict_tls,
configured_param.oauth2,
)
.await?;
@@ -721,12 +734,12 @@ async fn get_autoconfig(
}
progress!(ctx, 300);
// `?emailaddress=` query string is excluded on purpose.
// It is not part of the URL according to <https://datatracker.ietf.org/doc/draft-ietf-mailmaint-autoconfig/06/>.
// Related discussion confirming this is at <https://github.com/benbucksch/autoconfig-spec/issues/17>.
if let Ok(res) = moz_autoconfigure(
ctx,
// the doc does not mention `emailaddress=`, however, Thunderbird adds it, see <https://releases.mozilla.org/pub/thunderbird/>, which makes some sense
&format!(
"https://{param_domain}/.well-known/autoconfig/mail/config-v1.1.xml?emailaddress={param_addr_urlencoded}"
),
&format!("https://{param_domain}/.well-known/autoconfig/mail/config-v1.1.xml"),
&param.addr,
accept_invalid_certificates,
)
@@ -810,10 +823,14 @@ pub enum Error {
#[cfg(test)]
mod tests {
use crate::tools::SystemTime;
use super::*;
use crate::config::Config;
use crate::login_param::EnteredImapLoginParam;
use crate::sql::update_transport_last_rcvd_timestamp;
use crate::test_utils::TestContext;
use crate::transport::add_pseudo_transport;
#[tokio::test(flavor = "multi_thread", worker_threads = 2)]
async fn test_no_panic_on_bad_credentials() {
@@ -844,4 +861,107 @@ mod tests {
assert_eq!(configured_param.smtp_user, "");
Ok(())
}
#[tokio::test(flavor = "multi_thread", worker_threads = 2)]
async fn test_try_make_place_for_new_relay() -> Result<()> {
let t = TestContext::new().await;
// Setting ConfiguredAddr on an unconfigured account creates a pseudo primary transport
t.set_config(Config::ConfiguredAddr, Some("primary@example.org"))
.await?;
// Test that try_make_place_for_new_relay() doesn't do anything when we're below the limit
assert_eq!(t.count_transports().await?, 1);
t.try_make_space_for_new_relay().await?;
assert_eq!(t.count_transports().await?, 1);
for i in 0..(MAX_RELAYS - 2) {
add_pseudo_transport(&t, &format!("transport{i}@example.org")).await?;
}
assert_eq!(t.count_transports().await?, MAX_RELAYS - 1);
t.try_make_space_for_new_relay().await?;
assert_eq!(t.count_transports().await?, MAX_RELAYS - 1);
// Test that try_make_place_for_new_relay() removes the unpublished transport
// when we're at the limit
add_pseudo_transport(&t, "unpublished@example.org").await?;
t.set_transport_unpublished("unpublished@example.org", true)
.await?;
assert_eq!(t.count_transports().await?, MAX_RELAYS);
t.try_make_space_for_new_relay().await?;
assert_eq!(t.count_transports().await?, MAX_RELAYS - 1);
assert_eq!(
t.sql
.exists(
"SELECT COUNT(*) FROM transports WHERE addr=?",
("unpublished@example.org",),
)
.await?,
false
);
// Test that if there are multiple unpublished relays,
// the one that was used least recently is removed
t.set_transport_unpublished("transport0@example.org", true)
.await?;
add_pseudo_transport(&t, "other_unpublished@example.org").await?;
t.set_transport_unpublished("other_unpublished@example.org", true)
.await?;
assert_eq!(t.count_transports().await?, MAX_RELAYS);
let transport0_id: u32 = t
.sql
.query_get_value(
"SELECT id FROM transports WHERE addr=?",
("transport0@example.org",),
)
.await?
.unwrap();
let other_unpublished_id: u32 = t
.sql
.query_get_value(
"SELECT id FROM transports WHERE addr=?",
("other_unpublished@example.org",),
)
.await?
.unwrap();
update_transport_last_rcvd_timestamp(&t, transport0_id).await?;
SystemTime::shift(std::time::Duration::from_secs(10));
update_transport_last_rcvd_timestamp(&t, other_unpublished_id).await?;
// Test that try_make_place_for_new_relay()
// removes the relay with the oldest last_rcvd_timestamp
t.try_make_space_for_new_relay().await?;
assert_eq!(t.count_transports().await?, MAX_RELAYS - 1);
assert_eq!(
t.sql
.exists(
"SELECT COUNT(*) FROM transports WHERE addr=?",
("transport0@example.org",),
)
.await?,
false
);
assert_eq!(
t.sql
.exists(
"SELECT COUNT(*) FROM transports WHERE addr=?",
("other_unpublished@example.org",),
)
.await?,
true
);
// Test that try_make_place_for_new_relay() fails
// if there are MAX_RELAYS published transports
add_pseudo_transport(&t, "published_extra@example.org").await?;
t.set_transport_unpublished("other_unpublished@example.org", false)
.await?;
assert_eq!(t.count_transports().await?, MAX_RELAYS);
assert!(t.try_make_space_for_new_relay().await.is_err());
assert_eq!(t.count_transports().await?, MAX_RELAYS);
Ok(())
}
}

View File

@@ -5,6 +5,7 @@
use std::io::BufRead;
use std::str::FromStr;
use quick_xml::XmlVersion;
use quick_xml::events::{BytesStart, Event};
use super::{Error, ServerParams};
@@ -71,7 +72,7 @@ fn parse_server<B: BufRead>(
})
})
.map(|typ| {
typ.decode_and_unescape_value(reader.decoder())
typ.decoded_and_normalized_value(XmlVersion::Implicit1_0, reader.decoder())
.unwrap_or_default()
.to_lowercase()
})
@@ -101,7 +102,11 @@ fn parse_server<B: BufRead>(
}
}
Event::Text(ref event) => {
let val = event.xml_content().unwrap_or_default().trim().to_owned();
let val = event
.xml_content(XmlVersion::Implicit1_0)
.unwrap_or_default()
.trim()
.to_owned();
match tag_config {
MozConfigTag::Hostname => hostname = Some(val),

View File

@@ -5,6 +5,7 @@
use std::io::BufRead;
use quick_xml::XmlVersion;
use quick_xml::events::Event;
use super::{Error, ServerParams};
@@ -79,7 +80,7 @@ fn parse_protocol<B: BufRead>(
}
}
Event::Text(ref e) => {
let val = e.xml_content().unwrap_or_default();
let val = e.xml_content(XmlVersion::Implicit1_0).unwrap_or_default();
if let Some(ref tag) = current_tag {
match tag.as_str() {
@@ -123,7 +124,7 @@ fn parse_redirecturl<B: BufRead>(
let mut buf = Vec::new();
match reader.read_event_into(&mut buf)? {
Event::Text(ref e) => {
let val = e.xml_content().unwrap_or_default();
let val = e.xml_content(XmlVersion::Implicit1_0).unwrap_or_default();
Ok(val.trim().to_string())
}
_ => Ok("".to_string()),

View File

@@ -162,25 +162,6 @@ pub const DC_DESIRED_TEXT_LINE_LEN: usize = 100;
/// `char`s), not Unicode Grapheme Clusters.
pub const DC_DESIRED_TEXT_LEN: usize = DC_DESIRED_TEXT_LINE_LEN * DC_DESIRED_TEXT_LINES;
// Flags for configuring IMAP and SMTP servers.
// These flags are optional
// and may be set together with the username, password etc.
// via dc_set_config() using the key "server_flags".
/// Force OAuth2 authorization.
///
/// This flag does not skip automatic configuration.
/// Before calling configure() with DC_LP_AUTH_OAUTH2 set,
/// the user has to confirm access at the URL returned by dc_get_oauth2_url().
pub const DC_LP_AUTH_OAUTH2: i32 = 0x2;
/// Force NORMAL authorization, this is the default.
/// If this flag is set, automatic configuration is skipped.
pub const DC_LP_AUTH_NORMAL: i32 = 0x4;
/// if none of these flags are set, the default is chosen
pub const DC_LP_AUTH_FLAGS: i32 = DC_LP_AUTH_OAUTH2 | DC_LP_AUTH_NORMAL;
// max. weight of images to send w/o recoding
pub const BALANCED_IMAGE_BYTES: usize = 500_000;
pub const WORSE_IMAGE_BYTES: usize = 130_000;
@@ -199,13 +180,10 @@ pub const WORSE_IMAGE_SIZE: u32 = 640;
/// usage by UIs.
pub const MAX_RCVD_IMAGE_PIXELS: u32 = 50_000_000;
// If more recipients are needed in SMTP's `RCPT TO:` header, the recipient list is split into
// chunks. This does not affect MIME's `To:` header. Can be overwritten by setting
// `max_smtp_rcpt_to` in the provider db.
pub(crate) const DEFAULT_MAX_SMTP_RCPT_TO: usize = 50;
/// Same as `DEFAULT_MAX_SMTP_RCPT_TO`, but for chatmail relays.
pub(crate) const DEFAULT_CHATMAIL_MAX_SMTP_RCPT_TO: usize = 999;
// Fallback for the maximum number of recipients in SMTP's `RCPT TO:`;
// recipient lists exceeding the limit are sent in chunks.
// Relays typically advertise their limit via IMAP METADATA.
pub(crate) const DEFAULT_MAX_SMTP_RCPT_TO: u32 = 50;
/// How far the last quota check needs to be in the past to be checked by the background function (in seconds).
pub(crate) const DC_BACKGROUND_FETCH_QUOTA_CHECK_RATELIMIT: u64 = 12 * 60 * 60; // 12 hours

View File

@@ -231,8 +231,6 @@ pub struct InnerContext {
/// This is a global mutex-like state for operations which should be modal in the
/// clients.
running_state: RwLock<RunningState>,
/// Mutex to enforce only a single running oauth2 is running.
pub(crate) oauth2_mutex: Mutex<()>,
/// Mutex to prevent a race condition when a "your pw is wrong" warning is sent, resulting in multiple messages being sent.
pub(crate) wrong_pw_warning_mutex: Mutex<()>,
/// Mutex to prevent running housekeeping from multiple threads at once.
@@ -266,8 +264,8 @@ pub struct InnerContext {
/// <https://datatracker.ietf.org/doc/html/rfc2971>
pub(crate) server_id: RwLock<Option<HashMap<String, String>>>,
/// IMAP METADATA.
pub(crate) metadata: RwLock<Option<ServerMetadata>>,
/// IMAP METADATA, per transport id.
pub(crate) metadata: RwLock<BTreeMap<u32, ServerMetadata>>,
/// ID for this `Context` in the current process.
///
@@ -484,7 +482,6 @@ impl Context {
blobdir,
running_state: RwLock::new(Default::default()),
sql: Sql::new(dbfile),
oauth2_mutex: Mutex::new(()),
wrong_pw_warning_mutex: Mutex::new(()),
housekeeping_mutex: Mutex::new(()),
fetch_msgs_mutex: Mutex::new(()),
@@ -495,7 +492,7 @@ impl Context {
quota: RwLock::new(BTreeMap::new()),
new_msgs_notify,
server_id: RwLock::new(None),
metadata: RwLock::new(None),
metadata: RwLock::new(BTreeMap::new()),
creation_time: tools::Time::now(),
last_error: parking_lot::RwLock::new("".to_string()),
migration_error: parking_lot::RwLock::new(None),
@@ -566,25 +563,33 @@ impl Context {
self.scheduler.maybe_network().await;
}
/// Deprecated, we are trying to get rid of this global setting.
/// It is possible to configure a profile with both chatmail relays
/// and classical email servers.
///
/// Returns true if an account is on a chatmail server.
pub async fn is_chatmail(&self) -> Result<bool> {
self.get_config_bool(Config::IsChatmail).await
}
/// Returns maximum number of recipients the provider allows to send a single email to.
pub(crate) async fn get_max_smtp_rcpt_to(&self) -> Result<usize> {
let is_chatmail = self.is_chatmail().await?;
let val = self
.get_configured_provider()
.await?
/// Returns maximum number of recipients a single email can be sent to.
pub(crate) async fn get_max_smtp_rcpt_to(&self) -> Result<u32> {
let Some((transport_id, param)) = ConfiguredLoginParam::load(self).await? else {
bail!("Not configured");
};
let metadata_limit = self
.metadata
.read()
.await
.get(&transport_id)
.and_then(|metadata| metadata.max_smtp_rcpt_to);
if let Some(limit) = metadata_limit {
return Ok(limit);
}
let val = param
.provider
.and_then(|provider| provider.opt.max_smtp_rcpt_to)
.map_or_else(
|| match is_chatmail {
true => constants::DEFAULT_CHATMAIL_MAX_SMTP_RCPT_TO,
false => constants::DEFAULT_MAX_SMTP_RCPT_TO,
},
usize::from,
);
.map_or(constants::DEFAULT_MAX_SMTP_RCPT_TO, u32::from);
Ok(val)
}
@@ -914,7 +919,7 @@ impl Context {
.unwrap_or_else(|| "<unset>".to_string()),
);
if let Some(metadata) = &*self.metadata.read().await {
if let Some(metadata) = self.metadata.read().await.values().next() {
if let Some(comment) = &metadata.comment {
res.insert("imap_server_comment", format!("{comment:?}"));
}

View File

@@ -285,7 +285,6 @@ async fn test_get_info_completeness() {
"send_pw",
"send_port",
"send_security",
"server_flags",
"skip_start_messages",
"proxy_url", // May contain passwords, don't leak it to the logs.
"socks5_enabled", // SOCKS5 options are deprecated.

View File

@@ -6,13 +6,16 @@ use std::io::Cursor;
use anyhow::{Context as _, Result, bail};
use mailparse::ParsedMail;
use pgp::composed::DecryptionOptions;
use pgp::composed::Esk;
use pgp::composed::Message;
use pgp::composed::PlainSessionKey;
use pgp::composed::SignedSecretKey;
use pgp::composed::TheRing;
use pgp::composed::decrypt_session_key_with_password;
use pgp::packet::SymKeyEncryptedSessionKey;
use pgp::types::Password;
use pgp::types::Seipdv1ReadMode;
use pgp::types::StringToKey;
use crate::chat::ChatId;
@@ -48,6 +51,15 @@ pub(crate) async fn decrypt(
};
let expected_sender_fingerprint: Option<String>;
let abort_early = true;
// Use streaming mode for SEIPDv1 decryption to save memory.
// This was the default in rPGP 0.19.0
// and requires explicitly changing the mode in rPGP 0.20.0.
// SEPIDv2 is decrypted in streaming mode in any case.
let decrypt_options =
DecryptionOptions::new().set_seipdv1_read_mode(Seipdv1ReadMode::Streaming);
let plain = if let Message::Encrypted { esk, .. } = &*msg
// We only allow one ESK for symmetrically encrypted messages
// to avoid dealing with messages that are encrypted to multiple symmetric keys
@@ -61,9 +73,15 @@ pub(crate) async fn decrypt(
expected_sender_fingerprint = fingerprint;
tokio::task::spawn_blocking(move || -> Result<Message<'_>> {
let plain = msg
.decrypt_with_session_key(psk)
.context("decrypt_with_session_key")?;
let ring = TheRing {
session_keys: vec![psk],
decrypt_options,
..Default::default()
};
let (plain, _ring_result) = msg
.decrypt_the_ring(ring, abort_early)
.context("decrypt_the_ring")?;
let plain: Message<'static> = plain.decompress()?;
Ok(plain)
@@ -75,11 +93,15 @@ pub(crate) async fn decrypt(
expected_sender_fingerprint = None;
tokio::task::spawn_blocking(move || -> Result<Message<'_>> {
let empty_pw = Password::empty();
let secret_keys: Vec<&SignedSecretKey> = secret_keys.iter().collect();
let plain = msg
.decrypt_with_keys(vec![&empty_pw], secret_keys)
.context("decrypt_with_keys")?;
let ring = TheRing {
secret_keys,
decrypt_options,
..Default::default()
};
let (plain, _ring_result) = msg
.decrypt_the_ring(ring, abort_early)
.context("decrypt_the_ring")?;
let plain: Message<'static> = plain.decompress()?;
Ok(plain)

View File

@@ -6,7 +6,7 @@ use std::io::BufRead;
use std::sync::LazyLock;
use quick_xml::{
Reader,
Reader, XmlVersion,
errors::Error as QuickXmlError,
events::{BytesEnd, BytesStart, BytesText},
};
@@ -327,7 +327,7 @@ fn dehtml_starttag_cb<B: std::io::BufRead>(
})
{
let href = href
.decode_and_unescape_value(reader.decoder())
.decoded_and_normalized_value(XmlVersion::Implicit1_0, reader.decoder())
.unwrap_or_default()
.to_string();
@@ -374,7 +374,7 @@ fn maybe_push_tag(
fn tag_contains_attr(event: &BytesStart, reader: &Reader<impl BufRead>, name: &str) -> bool {
event.attributes().any(|r| {
r.map(|a| {
a.decode_and_unescape_value(reader.decoder())
a.decoded_and_normalized_value(XmlVersion::Implicit1_0, reader.decoder())
.map(|v| v == name)
.unwrap_or(false)
})

View File

@@ -132,7 +132,7 @@ impl Message {
}
/// Actually downloads a message partially downloaded before if the message is available on the
/// session transport, in which case returns `Some`. If the message is available on another
/// session transport, in which case returns `Some`. If the message is not available on this
/// transport, returns `None`.
///
/// Most messages are downloaded automatically on fetch instead.
@@ -145,29 +145,24 @@ pub(crate) async fn download_msg(
let row = context
.sql
.query_row_optional(
"SELECT uid, folder, transport_id FROM imap
"SELECT uid, folder FROM imap
WHERE rfc724_mid=? AND target!=''
ORDER BY transport_id=? DESC LIMIT 1",
AND transport_id=?
LIMIT 1",
(&rfc724_mid, transport_id),
|row| {
let server_uid: u32 = row.get(0)?;
let server_folder: String = row.get(1)?;
let msg_transport_id: u32 = row.get(2)?;
Ok((server_uid, server_folder, msg_transport_id))
Ok((server_uid, server_folder))
},
)
.await?;
let Some((server_uid, server_folder, msg_transport_id)) = row else {
// No IMAP record found, we don't know the UID and folder.
let Some((server_uid, server_folder)) = row else {
// No IMAP record found, the message is not available on this transport.
delete_from_available_post_msgs(context, &rfc724_mid).await?;
return Err(anyhow!(
"IMAP location for {rfc724_mid:?} post-message is unknown"
));
};
if msg_transport_id != transport_id {
return Ok(None);
}
};
Box::pin(session.fetch_single_msg(context, &server_folder, server_uid, rfc724_mid)).await?;
let bcc_self = context.get_config_bool(Config::BccSelf).await?;
@@ -283,35 +278,39 @@ pub(crate) async fn download_msgs(context: &Context, session: &mut Session) -> R
.await?;
for rfc724_mid in &rfc724_mids {
let res = download_msg(context, rfc724_mid.clone(), session).await;
if let Ok(Some(())) = res {
delete_from_downloads(context, rfc724_mid).await?;
delete_from_available_post_msgs(context, rfc724_mid).await?;
}
if let Err(err) = res {
warn!(
context,
"Failed to download message rfc724_mid={rfc724_mid}: {:#}.", err
);
if !msg_is_downloaded_for(context, rfc724_mid).await? {
// This is probably a classical email that vanished before we could download it
warn!(
context,
"{rfc724_mid} download failed and there is no downloaded pre-message."
);
delete_from_downloads(context, rfc724_mid).await?;
} else if available_post_msgs_contains_rfc724_mid(context, rfc724_mid).await? {
warn!(
context,
"{rfc724_mid} is in available_post_msgs table but we failed to fetch it,
so set the message to DownloadState::Failure - probably it was deleted on the server in the meantime"
);
set_state_to_failure(context, rfc724_mid).await?;
match download_msg(context, rfc724_mid.clone(), session).await {
Ok(Some(())) => {
delete_from_downloads(context, rfc724_mid).await?;
delete_from_available_post_msgs(context, rfc724_mid).await?;
} else {
// leave the message in DownloadState::InProgress;
// it will be downloaded once it arrives.
}
Ok(None) => {
// The message is not available on this transport.
}
Err(err) => {
warn!(
context,
"Failed to download message rfc724_mid={rfc724_mid}: {:#}.", err
);
if !msg_is_downloaded_for(context, rfc724_mid).await? {
// This is probably a classical email that vanished before we could download it
warn!(
context,
"{rfc724_mid} download failed and there is no downloaded pre-message."
);
delete_from_downloads(context, rfc724_mid).await?;
} else if available_post_msgs_contains_rfc724_mid(context, rfc724_mid).await? {
warn!(
context,
"{rfc724_mid} is in available_post_msgs table but we failed to fetch it,
so set the message to DownloadState::Failure - probably it was deleted on the server in the meantime"
);
set_state_to_failure(context, rfc724_mid).await?;
delete_from_downloads(context, rfc724_mid).await?;
delete_from_available_post_msgs(context, rfc724_mid).await?;
} else {
// leave the message in DownloadState::InProgress;
// it will be downloaded once it arrives.
}
}
}
}

View File

@@ -83,29 +83,20 @@ impl EncryptHelper {
None
};
let shared_secret = shared_secret.to_string();
let mut raw_message = Vec::new();
let cursor = Cursor::new(&mut raw_message);
mail_to_encrypt.clone().write_part(cursor).ok();
let ctext =
pgp::symm_encrypt_message(raw_message, sign_key, shared_secret, compress).await?;
let ctext = tokio::task::spawn_blocking(move || {
pgp::symm_encrypt_message(raw_message, sign_key, shared_secret, compress)
})
.await??;
Ok(ctext)
}
}
/// Ensures a private key exists for the configured user.
///
/// Normally the private key is generated when the first message is
/// sent but in a few locations there are no such guarantees,
/// e.g. when exporting keys, and calling this function ensures a
/// private key will be present.
// TODO, remove this once deltachat::key::Key no longer exists.
pub async fn ensure_secret_key_exists(context: &Context) -> Result<()> {
load_self_public_key(context).await?;
Ok(())
}
#[cfg(test)]
mod tests {
use super::*;
@@ -115,23 +106,7 @@ mod tests {
use crate::message::Message;
use crate::mimeparser::SystemMessage;
use crate::receive_imf::receive_imf;
use crate::test_utils::{TestContext, TestContextManager};
mod ensure_secret_key_exists {
use super::*;
#[tokio::test(flavor = "multi_thread", worker_threads = 2)]
async fn test_prexisting() {
let t = TestContext::new_alice().await;
assert!(ensure_secret_key_exists(&t).await.is_ok());
}
#[tokio::test(flavor = "multi_thread", worker_threads = 2)]
async fn test_not_configured() {
let t = TestContext::new().await;
assert!(ensure_secret_key_exists(&t).await.is_err());
}
}
use crate::test_utils::TestContextManager;
#[test]
fn test_mailmime_parse() {

View File

@@ -21,6 +21,7 @@ use crate::message::{Message, MsgId};
use crate::mimeparser::parse_message_id;
use crate::param::{Param::SendHtml, Params};
use crate::plaintext::PlainText;
use crate::simplify::unescape_message_footer_marks;
use crate::sql;
use crate::tools::{buf_compress, buf_decompress};
@@ -174,14 +175,14 @@ impl HtmlMsgParser {
if self.html.is_empty()
&& let Ok(decoded_data) = mail.get_body()
{
self.html = decoded_data;
self.html = unescape_message_footer_marks(&decoded_data);
}
} else if mimetype == mime::TEXT_PLAIN
&& self.plain.is_none()
&& let Ok(decoded_data) = mail.get_body()
{
self.plain = Some(PlainText {
text: decoded_data,
text: unescape_message_footer_marks(&decoded_data),
flowed: if let Some(format) = mail.ctype.params.get("format") {
format.as_str().eq_ignore_ascii_case("flowed")
} else {
@@ -417,6 +418,25 @@ test some special html-characters as &lt; &gt; and &amp; but also &quot; and &#x
);
}
#[tokio::test(flavor = "multi_thread", worker_threads = 2)]
async fn test_htmlparse_plain_escaped_footer() {
let t = TestContext::new().await;
let raw = include_bytes!("../test-data/message/text_plain_escaped_footer.eml");
let (parser, _) = HtmlMsgParser::from_bytes(&t.ctx, raw).unwrap();
assert_eq!(
parser.html,
r#"<!DOCTYPE html>
<html><head>
<meta http-equiv="Content-Type" content="text/html; charset=utf-8" />
<meta name="color-scheme" content="light dark" />
</head><body dir="auto" style="unicode-bidi: plaintext">
-- escaped footer 1<br/>
-- escaped footer 2<br/>
</body></html>
"#
);
}
#[tokio::test(flavor = "multi_thread", worker_threads = 2)]
async fn test_htmlparse_html() {
let t = TestContext::new().await;

View File

@@ -9,6 +9,7 @@ use std::{
collections::{BTreeMap, HashMap},
iter::Peekable,
mem::take,
str::FromStr,
sync::atomic::Ordering,
time::{Duration, UNIX_EPOCH},
};
@@ -34,7 +35,6 @@ use crate::message::{self, Message};
use crate::mimeparser;
use crate::net::proxy::ProxyConfig;
use crate::net::session::SessionStream;
use crate::oauth2::get_oauth2_access_token;
use crate::push::encrypt_device_token;
use crate::receive_imf::{
ReceivedMsg, from_field_to_contact_id, get_prefetch_parent_message, receive_imf_inner,
@@ -76,9 +76,6 @@ pub(crate) struct Imap {
pub(crate) idle_interrupt_receiver: Receiver<()>,
/// Email address.
pub(crate) addr: String,
/// Login parameters.
lp: Vec<ConfiguredServerLoginParam>,
@@ -90,8 +87,6 @@ pub(crate) struct Imap {
strict_tls: bool,
oauth2: bool,
/// Watched folder.
pub(crate) folder: String,
@@ -118,12 +113,6 @@ pub(crate) struct Imap {
pub(crate) resync_request_receiver: async_channel::Receiver<()>,
}
#[derive(Debug)]
struct OAuth2 {
user: String,
access_token: String,
}
#[derive(Debug, Default)]
pub(crate) struct ServerMetadata {
/// IMAP METADATA `/shared/comment` as defined in
@@ -136,6 +125,9 @@ pub(crate) struct ServerMetadata {
pub iroh_relay: Option<Url>,
/// Maximum number of recipients for SMTP `RCPT TO:`.
pub max_smtp_rcpt_to: Option<u32>,
/// ICE servers for WebRTC calls.
pub ice_servers: Vec<UnresolvedIceServer>,
@@ -148,17 +140,6 @@ pub(crate) struct ServerMetadata {
pub ice_servers_expiration_timestamp: i64,
}
impl async_imap::Authenticator for OAuth2 {
type Response = String;
fn process(&mut self, _data: &[u8]) -> Self::Response {
format!(
"user={}\x01auth=Bearer {}\x01\x01",
self.user, self.access_token
)
}
}
#[derive(Debug, Display, PartialEq, Eq, Clone, Copy)]
pub enum FolderMeaning {
Unknown,
@@ -250,9 +231,7 @@ impl Imap {
let lp = param.imap.clone();
let password = param.imap_password.clone();
let proxy_config = ProxyConfig::load(context).await?;
let addr = &param.addr;
let strict_tls = param.strict_tls(proxy_config.is_some());
let oauth2 = param.oauth2;
let folder = param
.imap_folder
.clone()
@@ -262,12 +241,10 @@ impl Imap {
Ok(Imap {
transport_id,
idle_interrupt_receiver,
addr: addr.to_string(),
lp,
password,
proxy_config,
strict_tls,
oauth2,
folder,
authentication_failed_once: false,
connectivity: Default::default(),
@@ -350,8 +327,8 @@ impl Imap {
let login_params = prioritize_server_login_params(&context.sql, &self.lp, "imap").await?;
let mut first_error = None;
for lp in login_params {
info!(context, "IMAP trying to connect to {}.", &lp.connection);
'candidate: for lp in login_params {
info!(context, "IMAP trying to connect to {}.", lp.connection);
let connection_candidate = lp.connection.clone();
let client = match Client::connect(
context,
@@ -366,7 +343,7 @@ impl Imap {
Err(err) => {
warn!(context, "{err:#}.");
first_error.get_or_insert(err);
continue;
continue 'candidate;
}
};
@@ -376,37 +353,41 @@ impl Imap {
let imap_user: &str = lp.user.as_ref();
let imap_pw: &str = &self.password;
let login_res = if self.oauth2 {
info!(context, "Logging into IMAP server with OAuth 2.");
let addr: &str = self.addr.as_ref();
let token = get_oauth2_access_token(context, addr, imap_pw, true)
.await?
.context("IMAP could not get OAUTH token")?;
let auth = OAuth2 {
user: imap_user.into(),
access_token: token,
};
client.authenticate("XOAUTH2", auth).await
} else {
info!(context, "Logging into IMAP server with LOGIN.");
client.login(imap_user, imap_pw).await
};
info!(context, "Logging into IMAP server with LOGIN.");
let login_res = client.login(imap_user, imap_pw).await;
match login_res {
Ok(mut session) => {
let capabilities = determine_capabilities(&mut session).await?;
Ok((mut session, login_capabilities_opt)) => {
let capabilities = if let Some(login_capabilities) = login_capabilities_opt {
login_capabilities
} else {
// OK response did not contain the CAPABILITY response code.
// Request capabilities explicitly.
match determine_capabilities(&mut session).await {
Ok(capabilities) => capabilities,
Err(err) => {
warn!(context, "Failed to determine capabilities: {err:#}.");
continue 'candidate;
}
}
};
let resync_request_sender = self.resync_request_sender.clone();
let session = if capabilities.can_compress {
info!(context, "Enabling IMAP compression.");
let compressed_session = session
let compressed_session = match session
.compress(|s| {
let session_stream: Box<dyn SessionStream> = Box::new(s);
session_stream
})
.await
.context("Failed to enable IMAP compression")?;
{
Ok(compressed_session) => compressed_session,
Err(err) => {
warn!(context, "Failed to enable IMAP compression: {err:#}.");
continue 'candidate;
}
};
Session::new(
compressed_session,
capabilities,
@@ -1117,7 +1098,7 @@ impl Session {
Err(err) => {
warn!(
context,
"store_seen_flags_on_imap: Failed to select {folder}, will retry later: {err:#}."
"store_seen_flags_on_imap: Transport {transport_id}: Failed to select {folder}, will retry later: {err:#}."
);
continue;
}
@@ -1128,13 +1109,13 @@ impl Session {
} else if let Err(err) = self.add_flag_finalized_with_set(&uid_set, "\\Seen").await {
warn!(
context,
"Cannot mark messages {uid_set} in {folder} as seen, will retry later: {err:#}."
"Transport {transport_id}: Cannot mark messages {uid_set} in {folder} as seen, will retry later: {err:#}."
);
continue;
} else {
info!(
context,
"Marked messages {} in folder {} as seen.", uid_set, folder
"Transport {transport_id}: Marked messages {uid_set} in folder {folder} as seen."
);
}
context
@@ -1182,6 +1163,7 @@ impl Session {
for (request_uids, set) in build_sequence_sets(&request_uids)? {
info!(context, "Starting UID FETCH of message set \"{}\".", set);
let transport_id = self.transport_id();
let mut fetch_responses = self
.uid_fetch(&set, BODY_FULL)
.await
@@ -1277,6 +1259,12 @@ impl Session {
"Passing message UID {} to receive_imf().", request_uid
);
let res = receive_imf_inner(context, rfc724_mid, body, is_seen).await;
crate::sql::update_transport_last_rcvd_timestamp(context, transport_id)
.await
.context(format!(
"Failed to update last_rcvd_timestamp of transport {}",
transport_id
))?;
// If there was an error receiving the message, show a device message:
let received_msg = match res {
@@ -1284,7 +1272,9 @@ impl Session {
warn!(context, "receive_imf error: {err:#}.");
let text = format!(
"❌ Failed to receive a message: {err:#}. Core version v{DC_VERSION_STR}. Please report this bug to delta@merlinux.eu or https://support.delta.chat/.",
// No trailing '.' to avoid from the Android UI treating it as a part of
// URL.
"❌ Failed to receive a message: {err:#}. Core version v{DC_VERSION_STR}. Please report this bug to delta@merlinux.eu or https://support.delta.chat/",
);
let mut msg = Message::new_text(text);
add_device_msg(context, None, Some(&mut msg)).await?;
@@ -1338,11 +1328,12 @@ impl Session {
#[expect(clippy::arithmetic_side_effects)]
pub(crate) async fn update_metadata(&mut self, context: &Context) -> Result<()> {
let mut lock = context.metadata.write().await;
let transport_id = self.transport_id();
if !self.can_metadata() {
*lock = Some(Default::default());
lock.entry(transport_id).or_default();
}
if let Some(ref mut old_metadata) = *lock {
if let Some(old_metadata) = lock.get_mut(&transport_id) {
let now = time();
// Refresh TURN server credentials if they expire in 12 hours.
@@ -1392,6 +1383,7 @@ impl Session {
let mut comment = None;
let mut admin = None;
let mut iroh_relay = None;
let mut max_smtp_rcpt_to = None;
let mut ice_servers = None;
let mut ice_servers_expiration_timestamp = 0;
@@ -1401,7 +1393,7 @@ impl Session {
.get_metadata(
mailbox,
options,
"(/shared/comment /shared/admin /shared/vendor/deltachat/irohrelay /shared/vendor/deltachat/turn)",
"(/shared/comment /shared/admin /shared/vendor/deltachat/irohrelay /shared/vendor/deltachat/turn /shared/vendor/deltachat/maxsmtprecipients)",
)
.await?;
for m in metadata {
@@ -1437,6 +1429,18 @@ impl Session {
}
}
}
"/shared/vendor/deltachat/maxsmtprecipients" => {
if let Some(value) = m.value {
if let Ok(limit) = u32::from_str(&value) {
max_smtp_rcpt_to = Some(limit);
} else {
warn!(
context,
"Got invalid maxsmtprecipients metadata: {:?}.", value
);
}
}
}
_ => {}
}
}
@@ -1448,13 +1452,17 @@ impl Session {
create_fallback_ice_servers()
};
*lock = Some(ServerMetadata {
comment,
admin,
iroh_relay,
ice_servers,
ice_servers_expiration_timestamp,
});
lock.insert(
transport_id,
ServerMetadata {
comment,
admin,
iroh_relay,
max_smtp_rcpt_to,
ice_servers,
ice_servers_expiration_timestamp,
},
);
Ok(())
}
@@ -1997,23 +2005,6 @@ async fn get_uidvalidity(context: &Context, transport_id: u32, folder: &str) ->
.unwrap_or(0))
}
pub(crate) async fn set_modseq(
context: &Context,
transport_id: u32,
folder: &str,
modseq: u64,
) -> Result<()> {
context
.sql
.execute(
"INSERT INTO imap_sync (transport_id, folder, modseq) VALUES (?,?,?)
ON CONFLICT(transport_id, folder) DO UPDATE SET modseq=excluded.modseq",
(transport_id, folder, modseq),
)
.await?;
Ok(())
}
/// Builds a list of sequence/uid sets. The returned sets have each no more than around 1000
/// characters because according to <https://tools.ietf.org/html/rfc2683#section-3.2.1.5>
/// command lines should not be much more than 1000 chars (servers should allow at least 8000 chars)

View File

@@ -53,10 +53,24 @@ fn alpn(port: u16) -> &'static str {
pub(crate) async fn determine_capabilities(
session: &mut ImapSession<Box<dyn SessionStream>>,
) -> Result<Capabilities> {
let caps = session
let imap_capabilities = session
.capabilities()
.await
.context("CAPABILITY command error")?;
identify_server(session, imap_capabilities).await
}
/// Identifies the server by sending ID command if it is supported.
///
/// Some IMAP servers require sending this command,
/// see <https://github.com/chatmail/core/issues/3458>,
/// <https://github.com/OfflineIMAP/offlineimap3/issues/71>
/// and <https://github.com/pimalaya/himalaya/issues/652>
/// for details.
pub(crate) async fn identify_server(
session: &mut ImapSession<Box<dyn SessionStream>>,
caps: async_imap::types::Capabilities,
) -> Result<Capabilities> {
let server_id = if caps.has_str("ID") {
session.id([("name", Some("Delta Chat"))]).await?
} else {
@@ -82,31 +96,30 @@ impl Client {
}
}
/// Logs in with the LOGIN command.
///
/// If the server supports [ID extension], sends ID command
/// and records the server response in the [`Capabilities`] structure.
///
/// [ID extension]: https://datatracker.ietf.org/doc/rfc2971/
pub(crate) async fn login(
self,
username: &str,
password: &str,
) -> Result<ImapSession<Box<dyn SessionStream>>> {
) -> Result<(ImapSession<Box<dyn SessionStream>>, Option<Capabilities>)> {
let Client { inner, .. } = self;
let session = inner
.login(username, password)
let (mut session, login_capabilities_opt) = inner
.login_with_capabilities(username, password)
.await
.map_err(|(err, _client)| err)?;
Ok(session)
}
pub(crate) async fn authenticate(
self,
auth_type: &str,
authenticator: impl async_imap::Authenticator,
) -> Result<ImapSession<Box<dyn SessionStream>>> {
let Client { inner, .. } = self;
let session = inner
.authenticate(auth_type, authenticator)
.await
.map_err(|(err, _client)| err)?;
Ok(session)
let capabilities = if let Some(login_capabilities) = login_capabilities_opt {
Some(identify_server(&mut session, login_capabilities).await?)
} else {
None
};
Ok((session, capabilities))
}
async fn connection_attempt(

View File

@@ -3,7 +3,7 @@
use anyhow::Context as _;
use super::session::Session as ImapSession;
use super::{get_uid_next, get_uidvalidity, set_modseq, set_uid_next, set_uidvalidity};
use super::{get_uid_next, get_uidvalidity, set_uid_next, set_uidvalidity};
use crate::context::Context;
use crate::ensure_and_debug_assert;
use crate::log::warn;
@@ -200,9 +200,6 @@ impl ImapSession {
return Ok(true);
}
// UIDVALIDITY is modified, reset highest seen MODSEQ.
set_modseq(context, transport_id, folder, 0).await?;
// ============== uid_validity has changed or is being set the first time. ==============
let new_uid_next = new_uid_next.unwrap_or_default();

View File

@@ -111,7 +111,7 @@ impl Session {
}
// Returns true if IMAP server has `XCHATMAIL` capability.
pub fn is_chatmail(&self) -> bool {
pub(crate) fn is_chatmail(&self) -> bool {
self.capabilities.is_chatmail
}

View File

@@ -17,7 +17,6 @@ use crate::blob::BlobDirContents;
use crate::chat::delete_and_reset_all_device_msgs;
use crate::config::Config;
use crate::context::Context;
use crate::e2ee;
use crate::events::EventType;
use crate::key::{self, DcKey, SignedSecretKey};
use crate::log::{LogExt, warn};
@@ -170,7 +169,7 @@ async fn imex_inner(
if what == ImexMode::ExportBackup || what == ImexMode::ExportSelfKeys {
// before we export anything, make sure the private key exists
e2ee::ensure_secret_key_exists(context)
key::ensure_secret_key_exists(context)
.await
.context("Cannot create private key or private key not available")?;
@@ -454,7 +453,7 @@ async fn export_backup(context: &Context, dir: &Path, passphrase: String) -> Res
let temp_db_path = TempPathGuard::new(temp_db_path);
let temp_path = TempPathGuard::new(temp_path);
export_database(context, &temp_db_path, passphrase, now)
export_database(context, &temp_db_path, passphrase)
.await
.context("could not export database")?;
@@ -674,7 +673,7 @@ async fn export_self_keys(context: &Context, dir: &Path) -> Result<()> {
.await?;
let self_addr = context.get_primary_self_addr().await?;
for (id, private_key, is_default) in keys {
let id = Some(id).filter(|_| is_default == 0);
let id = (is_default == 0).then_some(id);
let Ok(private_key) = private_key else {
export_errors += 1;
@@ -738,17 +737,11 @@ where
/// overwritten.
///
/// This also verifies that IO is not running during the export.
async fn export_database(
context: &Context,
dest: &Path,
passphrase: String,
timestamp: i64,
) -> Result<()> {
async fn export_database(context: &Context, dest: &Path, passphrase: String) -> Result<()> {
ensure!(
!context.scheduler.is_running().await,
"cannot export backup, IO is running"
);
let timestamp = timestamp.try_into().context("32-bit UNIX time overflow")?;
// TODO: Maybe introduce camino crate for UTF-8 paths where we need them.
let dest = dest
@@ -756,10 +749,6 @@ async fn export_database(
.with_context(|| format!("path {} is not valid unicode", dest.display()))?;
context.set_config(Config::BccSelf, Some("1")).await?;
context
.sql
.set_raw_config_int("backup_time", timestamp)
.await?;
context
.sql
.set_raw_config_int("backup_version", DCBACKUP_VERSION)

View File

@@ -30,6 +30,7 @@ use std::future::Future;
use std::pin::Pin;
use std::sync::Arc;
use std::task::Poll;
use std::time::Duration;
use anyhow::{Context as _, Result, bail, format_err};
use futures_lite::FutureExt;
@@ -38,15 +39,16 @@ use tokio::fs;
use tokio::task::JoinHandle;
use tokio_util::sync::CancellationToken;
use crate::EventType;
use crate::chat::add_device_msg;
use crate::context::Context;
use crate::imex::BlobDirContents;
use crate::key;
use crate::log::warn;
use crate::message::Message;
use crate::qr::Qr;
use crate::stock_str::backup_transfer_msg_body;
use crate::tools::{TempPathGuard, create_id, time};
use crate::{EventType, e2ee};
use crate::tools::{TempPathGuard, create_id};
use super::{DBFILE_BACKUP_NAME, export_backup_stream, export_database, import_backup_stream};
@@ -112,7 +114,7 @@ impl BackupProvider {
.context("Context dir not found")?;
// before we export, make sure the private key exists
e2ee::ensure_secret_key_exists(context)
key::ensure_secret_key_exists(context)
.await
.context("Cannot create private key or private key not available")?;
@@ -128,7 +130,7 @@ impl BackupProvider {
let passphrase = String::new();
export_database(context, &dbfile, passphrase, time())
export_database(context, &dbfile, passphrase)
.await
.context("Database export failed")?;
@@ -304,9 +306,12 @@ pub async fn get_backup2(
) -> Result<()> {
let relay_mode = RelayMode::Disabled;
let mut transport_config = iroh::endpoint::TransportConfig::default();
transport_config.max_idle_timeout(Some(Duration::from_secs(60).try_into()?));
let endpoint = Endpoint::builder()
.tls_x509() // For compatibility with iroh <0.34.0
.relay_mode(relay_mode)
.transport_config(transport_config)
.bind()
.await?;

Some files were not shown because too many files have changed in this diff Show More