From ab08a47298aa8e37b029052b23f0c8d835600aff Mon Sep 17 00:00:00 2001 From: Hocuri Date: Mon, 2 Mar 2026 22:11:05 +0100 Subject: [PATCH 01/33] fix: Handle the case that the user starts a securejoin, and then deletes the contact (#7883) fix https://github.com/chatmail/core/issues/7880 depends on #7754 (merged) With this change, a securejoin message is just ignored if the contact was deleted in the meantime; apparently the user is not interested in the securejoin process anymore if they deleted the contact. But other, parallel securejoin processes must not be affected; the test also tests this. --- src/securejoin.rs | 4 ++- src/securejoin/securejoin_tests.rs | 40 ++++++++++++++++++++++-------- 2 files changed, 33 insertions(+), 11 deletions(-) diff --git a/src/securejoin.rs b/src/securejoin.rs index 251cca5228..46e56cf921 100644 --- a/src/securejoin.rs +++ b/src/securejoin.rs @@ -304,7 +304,9 @@ async fn verify_sender_by_fingerprint( fingerprint: &Fingerprint, contact_id: ContactId, ) -> Result { - let contact = Contact::get_by_id(context, contact_id).await?; + let Some(contact) = Contact::get_by_id_optional(context, contact_id).await? else { + return Ok(false); + }; let is_verified = contact.fingerprint().is_some_and(|fp| &fp == fingerprint); if is_verified { mark_contact_id_as_verified(context, contact_id, Some(ContactId::SELF)).await?; diff --git a/src/securejoin/securejoin_tests.rs b/src/securejoin/securejoin_tests.rs index a429bff4cb..7dabd1ba10 100644 --- a/src/securejoin/securejoin_tests.rs +++ b/src/securejoin/securejoin_tests.rs @@ -910,7 +910,18 @@ async fn test_parallel_securejoin() -> Result<()> { /// Tests Bob scanning setup contact QR codes of Alice and Fiona /// concurrently. #[tokio::test(flavor = "multi_thread", worker_threads = 2)] -async fn test_parallel_setup_contact() -> Result<()> { +async fn test_parallel_setup_contact_basic() -> Result<()> { + test_parallel_setup_contact(false).await +} + +/// Tests Bob scanning setup contact QR codes of Alice and Fiona +/// concurrently, and then deleting the Fiona contact. +#[tokio::test(flavor = "multi_thread", worker_threads = 2)] +async fn test_parallel_setup_contact_bob_deletes_fiona() -> Result<()> { + test_parallel_setup_contact(true).await +} + +async fn test_parallel_setup_contact(bob_deletes_fiona_contact: bool) -> Result<()> { let mut tcm = TestContextManager::new(); let alice = &tcm.alice().await; let bob = &tcm.bob().await; @@ -931,16 +942,25 @@ async fn test_parallel_setup_contact() -> Result<()> { fiona.recv_msg_trash(&sent_fiona_vc_request).await; let sent_fiona_vc_auth_required = fiona.pop_sent_msg().await; - bob.recv_msg_trash(&sent_fiona_vc_auth_required).await; - let sent_fiona_vc_request_with_auth = bob.pop_sent_msg().await; - - fiona.recv_msg_trash(&sent_fiona_vc_request_with_auth).await; - let sent_fiona_vc_contact_confirm = fiona.pop_sent_msg().await; - - bob.recv_msg_trash(&sent_fiona_vc_contact_confirm).await; let bob_fiona_contact_id = bob.add_or_lookup_contact_id(fiona).await; - let bob_fiona_contact = Contact::get_by_id(bob, bob_fiona_contact_id).await.unwrap(); - assert_eq!(bob_fiona_contact.is_verified(bob).await.unwrap(), true); + if bob_deletes_fiona_contact { + bob.get_chat(fiona).await.id.delete(bob).await?; + Contact::delete(bob, bob_fiona_contact_id).await?; + + bob.recv_msg_trash(&sent_fiona_vc_auth_required).await; + let sent = bob.pop_sent_msg_opt(Duration::ZERO).await; + assert!(sent.is_none()); + } else { + bob.recv_msg_trash(&sent_fiona_vc_auth_required).await; + let sent_fiona_vc_request_with_auth = bob.pop_sent_msg().await; + + fiona.recv_msg_trash(&sent_fiona_vc_request_with_auth).await; + let sent_fiona_vc_contact_confirm = fiona.pop_sent_msg().await; + + bob.recv_msg_trash(&sent_fiona_vc_contact_confirm).await; + let bob_fiona_contact = Contact::get_by_id(bob, bob_fiona_contact_id).await.unwrap(); + assert_eq!(bob_fiona_contact.is_verified(bob).await.unwrap(), true); + } // Alice gets online and previously started SecureJoin process finishes. alice.recv_msg_trash(&sent_alice_vc_request).await; From e7625ca231fa9f7506c7e4b7b18850f82db7fb24 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 2 Mar 2026 17:50:44 +0000 Subject: [PATCH 02/33] chore(cargo): bump proptest from 1.9.0 to 1.10.0 Bumps [proptest](https://github.com/proptest-rs/proptest) from 1.9.0 to 1.10.0. - [Release notes](https://github.com/proptest-rs/proptest/releases) - [Changelog](https://github.com/proptest-rs/proptest/blob/main/CHANGELOG.md) - [Commits](https://github.com/proptest-rs/proptest/compare/v1.9.0...v1.10.0) --- updated-dependencies: - dependency-name: proptest dependency-version: 1.10.0 dependency-type: direct:production update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] --- Cargo.lock | 42 +++++++++++++++++++++--------------------- 1 file changed, 21 insertions(+), 21 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 9a1081b80a..5d2dada334 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -470,9 +470,9 @@ checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a" [[package]] name = "bitflags" -version = "2.9.1" +version = "2.11.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1b8e56985ec62d17e9c1001dc89c88ecd7dc08e47eba5ec7c29c7b5eeecde967" +checksum = "843867be96c8daad0d758b57df9392b6d8d271134fce549de6ce169ff98a92af" [[package]] name = "bitvec" @@ -1116,7 +1116,7 @@ version = "0.28.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "829d955a0bb380ef178a640b91779e3987da38c9aea133b20614cfed8cdea9c6" dependencies = [ - "bitflags 2.9.1", + "bitflags 2.11.0", "crossterm_winapi", "parking_lot", "rustix 0.38.44", @@ -3270,7 +3270,7 @@ version = "0.1.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c0ff37bd590ca25063e35af745c343cb7a0271906fb7b37e4813e8f79f00268d" dependencies = [ - "bitflags 2.9.1", + "bitflags 2.11.0", "libc", "redox_syscall 0.5.12", ] @@ -3584,7 +3584,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0800eae8638a299eaa67476e1c6b6692922273e0f7939fd188fc861c837b9cd2" dependencies = [ "anyhow", - "bitflags 2.9.1", + "bitflags 2.11.0", "byteorder", "libc", "log", @@ -3679,7 +3679,7 @@ version = "0.30.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "74523f3a35e05aba87a1d978330aef40f67b0304ac79c1c00b294c9830543db6" dependencies = [ - "bitflags 2.9.1", + "bitflags 2.11.0", "cfg-if", "cfg_aliases", "libc", @@ -3854,7 +3854,7 @@ version = "0.3.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "2a180dd8642fa45cdb7dd721cd4c11b1cadd4929ce112ebd8b9f5803cc79d536" dependencies = [ - "bitflags 2.9.1", + "bitflags 2.11.0", ] [[package]] @@ -3925,7 +3925,7 @@ version = "0.10.72" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "fedfea7d58a1f73118430a55da6a286e7b044961736ce96a16a17068ea25e5da" dependencies = [ - "bitflags 2.9.1", + "bitflags 2.11.0", "cfg-if", "foreign-types", "libc", @@ -4387,7 +4387,7 @@ version = "0.18.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "97baced388464909d42d89643fe4361939af9b7ce7a31ee32a168f832a70f2a0" dependencies = [ - "bitflags 2.9.1", + "bitflags 2.11.0", "crc32fast", "fdeflate", "flate2", @@ -4603,11 +4603,11 @@ dependencies = [ [[package]] name = "proptest" -version = "1.9.0" +version = "1.10.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bee689443a2bd0a16ab0348b52ee43e3b2d1b1f931c8aa5c9f8de4c86fbe8c40" +checksum = "37566cb3fdacef14c0737f9546df7cfeadbfbc9fef10991038bf5015d0c80532" dependencies = [ - "bitflags 2.9.1", + "bitflags 2.11.0", "num-traits", "rand 0.9.2", "rand_chacha 0.9.0", @@ -4890,7 +4890,7 @@ version = "0.5.12" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "928fca9cf2aa042393a8325b9ead81d2f0df4cb12e1e24cef072922ccd99c5af" dependencies = [ - "bitflags 2.9.1", + "bitflags 2.11.0", ] [[package]] @@ -5077,7 +5077,7 @@ version = "0.37.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "165ca6e57b20e1351573e3729b958bc62f0e48025386970b6e4d29e7a7e71f3f" dependencies = [ - "bitflags 2.9.1", + "bitflags 2.11.0", "fallible-iterator", "fallible-streaming-iterator", "hashlink", @@ -5121,7 +5121,7 @@ version = "0.38.44" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "fdb5bc1ae2baa591800df16c9ca78619bf65c0488b41b96ccec5d11220d8c154" dependencies = [ - "bitflags 2.9.1", + "bitflags 2.11.0", "errno", "libc", "linux-raw-sys 0.4.14", @@ -5134,7 +5134,7 @@ version = "1.1.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b6fe4565b9518b83ef4f91bb47ce29620ca828bd32cb7e408f0062e9930ba190" dependencies = [ - "bitflags 2.9.1", + "bitflags 2.11.0", "errno", "libc", "linux-raw-sys 0.12.1", @@ -5198,7 +5198,7 @@ version = "16.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "62fd9ca5ebc709e8535e8ef7c658eb51457987e48c98ead2be482172accc408d" dependencies = [ - "bitflags 2.9.1", + "bitflags 2.11.0", "cfg-if", "clipboard-win", "fd-lock", @@ -5325,7 +5325,7 @@ version = "2.11.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "897b2245f0b511c87893af39b033e5ca9cce68824c4d7e7630b5a1d339658d02" dependencies = [ - "bitflags 2.9.1", + "bitflags 2.11.0", "core-foundation", "core-foundation-sys", "libc", @@ -5644,7 +5644,7 @@ version = "0.9.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "dee851d0e5e7af3721faea1843e8015e820a234f81fda3dea9247e15bac9a86a" dependencies = [ - "bitflags 2.9.1", + "bitflags 2.11.0", ] [[package]] @@ -5929,7 +5929,7 @@ version = "0.6.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3c879d448e9d986b661742763247d3693ed13609438cf3d006f51f5368a5ba6b" dependencies = [ - "bitflags 2.9.1", + "bitflags 2.11.0", "core-foundation", "system-configuration-sys", ] @@ -7212,7 +7212,7 @@ version = "0.39.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6f42320e61fe2cfd34354ecb597f86f413484a798ba44a8ca1165c58d42da6c1" dependencies = [ - "bitflags 2.9.1", + "bitflags 2.11.0", ] [[package]] From bfae2296b7a45dedf9861db00837f24100d6d3e4 Mon Sep 17 00:00:00 2001 From: Hocuri Date: Tue, 3 Mar 2026 10:08:56 +0100 Subject: [PATCH 03/33] test: Fix flaky test_qr_securejoin_broadcast (#7937) I assume that the problem was that sometimes, alice2 or fiona doesn't accept alice's smeared timestamp, because `calc_sort_timestamp()` doesn't allow the timestamp of a received message to be in the future. I tried this patch: ```diff diff --cc src/chat.rs index 9565437cf,9565437cf..a2e4f97d0 --- a/src/chat.rs +++ b/src/chat.rs @@@ -46,6 -46,6 +46,7 @@@ use crate::receive_imf::ReceivedMsg use crate::smtp::{self, send_msg_to_smtp}; use crate::stock_str; use crate::sync::{self, Sync::*, SyncData}; ++use crate::timesmearing::MAX_SECONDS_TO_LEND_FROM_FUTURE; use crate::tools::{ IsNoneOrEmpty, SystemTime, buf_compress, create_broadcast_secret, create_id, create_outgoing_rfc724_mid, create_smeared_timestamp, create_smeared_timestamps, get_abs_path, @@@ -1212,7 -1212,7 +1213,11 @@@ SELECT id, rfc724_mid, pre_rfc724_mid, received: bool, incoming: bool, ) -> Result { -- let mut sort_timestamp = cmp::min(message_timestamp, smeared_time(context)); ++ let mut sort_timestamp = cmp::min( ++ message_timestamp, ++ // Add MAX_SECONDS_TO_LEND_FROM_FUTURE in order to allow other senders to do timesmearing, too: ++ smeared_time(context) + MAX_SECONDS_TO_LEND_FROM_FUTURE, ++ ); let last_msg_time: Option = if always_sort_to_bottom { // get newest message for this chat ``` ...maybe this patch makes sense anyways, but you still get the problem that the message sent by alice2 (i.e. the add-fiona message) will have an earlier timestamp than the message sent by alice, because alice already sent more messages, and therefore has more timesmearing-seconds. It's unsure it makes sense to modify calc_sort_timestamp() this way because if some chat member has the clock in the future (even unintentionally), their fresh messages will be sorted to the bottom relatively to others' fresh messages. Maybe it's even better to limit the message timestamp ("Date") by the current system time there. To really fix the problem, we could send a serial number together with the timestamp, that distinguishes two messages sent in the same second. But since we haven't gotten complaints about message ordering since some time, let's just leave things as they are. Since all this timesmearing is a bit best-effort right now, I decided to instead just make the test more relaxed. --- deltachat-rpc-client/tests/test_securejoin.py | 11 +++++++---- 1 file changed, 7 insertions(+), 4 deletions(-) diff --git a/deltachat-rpc-client/tests/test_securejoin.py b/deltachat-rpc-client/tests/test_securejoin.py index 1accec207c..54799adf13 100644 --- a/deltachat-rpc-client/tests/test_securejoin.py +++ b/deltachat-rpc-client/tests/test_securejoin.py @@ -150,7 +150,7 @@ def wait_for_broadcast_messages(ac): assert snapshot1.chat_id == chat.id assert snapshot2.chat_id == chat.id - def check_account(ac, contact, inviter_side, please_wait_info_msg=False, resent_msg=False): + def check_account(ac, contact, inviter_side, please_wait_info_msg=False): # Check that the chat partner is verified. contact_snapshot = contact.get_snapshot() assert contact_snapshot.is_verified @@ -172,8 +172,11 @@ def check_account(ac, contact, inviter_side, please_wait_info_msg=False, resent_ assert member_added_msg.text == f"Member {contact_snapshot.display_name} added." assert member_added_msg.info_contact_id == contact_snapshot.id else: - member_added_msg = chat_msgs.pop(1 if resent_msg else 0).get_snapshot() - assert member_added_msg.text == "You joined the channel." + if chat_msgs[0].get_snapshot().text == "You joined the channel.": + member_added_msg = chat_msgs.pop(0).get_snapshot() + else: + member_added_msg = chat_msgs.pop(1).get_snapshot() + assert member_added_msg.text == "You joined the channel." assert member_added_msg.is_info hello_msg = chat_msgs.pop(0).get_snapshot() @@ -243,7 +246,7 @@ def check_account(ac, contact, inviter_side, please_wait_info_msg=False, resent_ snapshot = fiona.wait_for_incoming_msg().get_snapshot() assert snapshot.text == "Hello everyone!" - check_account(fiona, fiona.create_contact(alice), inviter_side=False, please_wait_info_msg=True, resent_msg=True) + check_account(fiona, fiona.create_contact(alice), inviter_side=False, please_wait_info_msg=True) # For Bob, the channel must not have changed: check_account(bob, bob.create_contact(alice), inviter_side=False, please_wait_info_msg=True) From b94792706a3c6094a95b83b3210d50a98ccf4820 Mon Sep 17 00:00:00 2001 From: Hocuri Date: Tue, 3 Mar 2026 10:12:02 +0100 Subject: [PATCH 04/33] feat: Don't depend on cleartext Chat-Version, In-Reply-To, and References headers for prefetch_should_download (#7932) Don't depend on these 3 cleartext headers for the question whether we download a message. This PR will waste a bit of bandwidth for people who use the legacy show_emails option; apart from that, there is no user-visible change yet. It's a preparation for being able to remove these headers, in order to further reduce unencrypted metadata. Removing In-Reply-To and References will be easy; removing Chat-Version must happen at least one release after the PR here is released, so that people don't miss messages. Also, maybe some nerds depend on the Chat-Version header for server-side filtering of messages, but we shall have this discussion at some other time. For the question whether a message should be moved, we do still depend on them; this will be fixed with https://github.com/chatmail/core/pull/7780. When both this PR and #7780 are merged, we can stop requesting Chat-Version header during prefetch. --- src/imap.rs | 33 +++------------------------------ src/imap/session.rs | 1 - 2 files changed, 3 insertions(+), 31 deletions(-) diff --git a/src/imap.rs b/src/imap.rs index 0125f5390c..89cadd6d75 100644 --- a/src/imap.rs +++ b/src/imap.rs @@ -18,7 +18,6 @@ use async_channel::{self, Receiver, Sender}; use async_imap::types::{Fetch, Flag, Name, NameAttribute, UnsolicitedResponse}; use futures::{FutureExt as _, TryStreamExt}; use futures_lite::FutureExt; -use num_traits::FromPrimitive; use ratelimit::Ratelimit; use url::Url; @@ -28,7 +27,7 @@ use crate::calls::{ use crate::chat::{self, ChatId, ChatIdBlocked, add_device_msg}; use crate::chatlist_events; use crate::config::Config; -use crate::constants::{self, Blocked, DC_VERSION_STR, ShowEmails}; +use crate::constants::{self, Blocked, DC_VERSION_STR}; use crate::contact::ContactId; use crate::context::Context; use crate::events::EventType; @@ -2131,16 +2130,11 @@ pub(crate) async fn prefetch_should_download( false }; - // Autocrypt Setup Message should be shown even if it is from non-chat client. - let is_autocrypt_setup_message = headers - .get_header_value(HeaderDef::AutocryptSetupMessage) - .is_some(); - let from = match mimeparser::get_from(headers) { Some(f) => f, None => return Ok(false), }; - let (_from_id, blocked_contact, origin) = + let (_from_id, blocked_contact, _origin) = match from_field_to_contact_id(context, &from, None, true, true).await? { Some(res) => res, None => return Ok(false), @@ -2153,28 +2147,7 @@ pub(crate) async fn prefetch_should_download( return Ok(false); } - let is_chat_message = headers.get_header_value(HeaderDef::ChatVersion).is_some(); - let accepted_contact = origin.is_known(); - let is_reply_to_chat_message = get_prefetch_parent_message(context, headers) - .await? - .is_some_and(|parent| match parent.is_dc_message { - MessengerMessage::No => false, - MessengerMessage::Yes | MessengerMessage::Reply => true, - }); - - let show_emails = - ShowEmails::from_i32(context.get_config_int(Config::ShowEmails).await?).unwrap_or_default(); - - let show = is_autocrypt_setup_message - || match show_emails { - ShowEmails::Off => is_chat_message || is_reply_to_chat_message, - ShowEmails::AcceptedContacts => { - is_chat_message || is_reply_to_chat_message || accepted_contact - } - ShowEmails::All => true, - }; - - let should_download = (show && !blocked_contact) || maybe_ndn; + let should_download = (!blocked_contact) || maybe_ndn; Ok(should_download) } diff --git a/src/imap/session.rs b/src/imap/session.rs index b810feef53..3a3a6a483e 100644 --- a/src/imap/session.rs +++ b/src/imap/session.rs @@ -21,7 +21,6 @@ const PREFETCH_FLAGS: &str = "(UID INTERNALDATE RFC822.SIZE BODY.PEEK[HEADER.FIE DATE \ X-MICROSOFT-ORIGINAL-MESSAGE-ID \ FROM \ - IN-REPLY-TO REFERENCES \ CHAT-VERSION \ CHAT-IS-POST-MESSAGE \ AUTO-SUBMITTED \ From b10acd194e355fd7ae1ac4eb357cb8e18ee8144e Mon Sep 17 00:00:00 2001 From: Francisco Castro Date: Tue, 3 Mar 2026 08:28:52 -0300 Subject: [PATCH 05/33] Add support to gif stickers (#7941) Minimal change lets the desktop client select gif files placed in the stickers folders. --- deltachat-jsonrpc/src/api.rs | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/deltachat-jsonrpc/src/api.rs b/deltachat-jsonrpc/src/api.rs index fca11560de..3f438565b2 100644 --- a/deltachat-jsonrpc/src/api.rs +++ b/deltachat-jsonrpc/src/api.rs @@ -2506,7 +2506,10 @@ impl CommandApi { continue; } let sticker_name = sticker_entry.file_name().into_string().unwrap_or_default(); - if sticker_name.ends_with(".png") || sticker_name.ends_with(".webp") { + if sticker_name.ends_with(".png") + || sticker_name.ends_with(".webp") + || sticker_name.ends_with(".gif") + { sticker_paths.push( sticker_entry .path() From c928015f2083b2d48113858f6d86bec66d96eb65 Mon Sep 17 00:00:00 2001 From: Hocuri Date: Tue, 3 Mar 2026 12:36:34 +0100 Subject: [PATCH 06/33] fix: Use the correct chat description stock string again (#7939) Fix https://github.com/chatmail/core/issues/7933 Apparently I was inattentive when reviewing https://github.com/chatmail/core/pull/7870/; there even was a test that tested that the incorrect description is used XD Thanks for noticing @r10s! --- src/chat.rs | 4 +-- src/chat/chat_tests.rs | 78 +++++++++++++++++++++++++++++++++++------- src/mimefactory.rs | 3 ++ 3 files changed, 69 insertions(+), 16 deletions(-) diff --git a/src/chat.rs b/src/chat.rs index 9565437cf6..78b6d189b0 100644 --- a/src/chat.rs +++ b/src/chat.rs @@ -4263,9 +4263,7 @@ async fn set_chat_description_ex( if chat.is_promoted() { let mut msg = Message::new(Viewtype::Text); - msg.text = - "[Chat description changed. To see this and other new features, please update the app]" - .to_string(); + msg.text = stock_str::msg_chat_description_changed(context, ContactId::SELF).await; msg.param.set_cmd(SystemMessage::GroupDescriptionChanged); msg.id = send_msg(context, chat_id, &mut msg).await?; diff --git a/src/chat/chat_tests.rs b/src/chat/chat_tests.rs index a1541cb99c..7a9a9b7809 100644 --- a/src/chat/chat_tests.rs +++ b/src/chat/chat_tests.rs @@ -3154,29 +3154,59 @@ async fn test_broadcasts_name_and_avatar() -> Result<()> { #[tokio::test(flavor = "multi_thread", worker_threads = 2)] async fn test_chat_description_basic() { - test_chat_description("", false).await.unwrap() + test_chat_description("", false, Chattype::Group) + .await + .unwrap(); + // Don't test with broadcast channels, + // because broadcast channels can only be joined via a QR code } #[tokio::test(flavor = "multi_thread", worker_threads = 2)] async fn test_chat_description_unpromoted_description() { - test_chat_description("Unpromoted description in the beginning", false) - .await - .unwrap() + test_chat_description( + "Unpromoted description in the beginning", + false, + Chattype::Group, + ) + .await + .unwrap(); + // Don't test with broadcast channels, + // because broadcast channels can only be joined via a QR code } #[tokio::test(flavor = "multi_thread", worker_threads = 2)] async fn test_chat_description_qr() { - test_chat_description("", true).await.unwrap() + test_chat_description("", true, Chattype::Group) + .await + .unwrap(); + test_chat_description("", true, Chattype::OutBroadcast) + .await + .unwrap(); } #[tokio::test(flavor = "multi_thread", worker_threads = 2)] async fn test_chat_description_unpromoted_description_qr() { - test_chat_description("Unpromoted description in the beginning", true) - .await - .unwrap() + test_chat_description( + "Unpromoted description in the beginning", + true, + Chattype::Group, + ) + .await + .unwrap(); + test_chat_description( + "Unpromoted description in the beginning", + true, + Chattype::OutBroadcast, + ) + .await + .unwrap(); } -async fn test_chat_description(initial_description: &str, join_via_qr: bool) -> Result<()> { +async fn test_chat_description( + initial_description: &str, + join_via_qr: bool, + chattype: Chattype, +) -> Result<()> { let mut tcm = TestContextManager::new(); let alice = &tcm.alice().await; let alice2 = &tcm.alice().await; @@ -3186,12 +3216,29 @@ async fn test_chat_description(initial_description: &str, join_via_qr: bool) -> alice2.set_config_bool(Config::SyncMsgs, true).await?; tcm.section("Create a group chat, and add Bob"); - let alice_chat_id = create_group(alice, "My Group").await?; + let alice_chat_id = if chattype == Chattype::Group { + create_group(alice, "My Group").await? + } else { + create_broadcast(alice, "My Channel".to_string()).await? + }; + sync(alice, alice2).await; if !initial_description.is_empty() { set_chat_description(alice, alice_chat_id, initial_description).await?; + + if chattype == Chattype::OutBroadcast { + // Broadcast channels are always promoted, so, a message is sent: + let sent = alice.pop_sent_msg().await; + assert_eq!( + sent.load_from_db().await.text, + "You changed the chat description." + ); + let rcvd = alice2.recv_msg(&sent).await; + assert_eq!(rcvd.text, "You changed the chat description."); + } else { + sync(alice, alice2).await; + } } - sync(alice, alice2).await; let alice2_chat_id = get_chat_id_by_grpid( alice2, @@ -3219,7 +3266,7 @@ async fn test_chat_description(initial_description: &str, join_via_qr: bool) -> initial_description ); - for description in ["This is a cool group", "", "ä ẟ 😂"] { + for description in ["This is a cool chat", "", "ä ẟ 😂"] { tcm.section(&format!( "Alice sets the chat description to '{description}'" )); @@ -3227,10 +3274,15 @@ async fn test_chat_description(initial_description: &str, join_via_qr: bool) -> let sent = alice.pop_sent_msg().await; assert_eq!( sent.load_from_db().await.text, - "[Chat description changed. To see this and other new features, please update the app]" + "You changed the chat description." ); tcm.section("Bob receives the description change"); + let parsed = MimeMessage::from_bytes(bob, sent.payload().as_bytes()).await?; + assert_eq!( + parsed.parts[0].msg, + "[Chat description changed. To see this and other new features, please update the app]" + ); let rcvd = bob.recv_msg(&sent).await; assert_eq!(rcvd.get_info_type(), SystemMessage::GroupDescriptionChanged); assert_eq!(rcvd.text, "Chat description changed by alice@example.org."); diff --git a/src/mimefactory.rs b/src/mimefactory.rs index 3750102cfe..3acfb12d80 100644 --- a/src/mimefactory.rs +++ b/src/mimefactory.rs @@ -1466,6 +1466,9 @@ impl MimeFactory { )); } SystemMessage::GroupDescriptionChanged => { + placeholdertext = Some( + "[Chat description changed. To see this and other new features, please update the app]".to_string(), + ); headers.push(( "Chat-Group-Description-Changed", mail_builder::headers::text::Text::new("").into(), From 0622289420c74b551a96cc370daf436da280cd6a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jagoda=20Estera=20=C5=9Al=C4=85zak?= <128227338+j-g00da@users.noreply.github.com> Date: Tue, 3 Mar 2026 13:45:32 +0100 Subject: [PATCH 07/33] fix(vcard): Improve property value escaping (#7931) Implements property value escaping according to RFC6350 section 3.4. Fixes: #7893 --- deltachat-contact-tools/src/vcard.rs | 45 ++++++++++++++++--- .../src/vcard/vcard_tests.rs | 15 ++++++- src/contact/contact_tests.rs | 7 ++- 3 files changed, 58 insertions(+), 9 deletions(-) diff --git a/deltachat-contact-tools/src/vcard.rs b/deltachat-contact-tools/src/vcard.rs index 77ad5bef43..0b09e97277 100644 --- a/deltachat-contact-tools/src/vcard.rs +++ b/deltachat-contact-tools/src/vcard.rs @@ -36,6 +36,45 @@ impl VcardContact { } } +fn escape(s: &str) -> String { + // https://www.rfc-editor.org/rfc/rfc6350.html#section-3.4 + s + // backslash must be first! + .replace(r"\", r"\\") + .replace(',', r"\,") + .replace(';', r"\;") + .replace('\n', r"\n") +} + +fn unescape(s: &str) -> String { + // https://www.rfc-editor.org/rfc/rfc6350.html#section-3.4 + let mut out = String::new(); + + let mut chars = s.chars(); + while let Some(c) = chars.next() { + if c == '\\' { + if let Some(next) = chars.next() { + match next { + '\\' | ',' | ';' => out.push(next), + 'n' | 'N' => out.push('\n'), + _ => { + // Invalid escape sequence (keep unchanged) + out.push('\\'); + out.push(next); + } + } + } else { + // Invalid escape sequence (keep unchanged) + out.push('\\'); + } + } else { + out.push(c); + } + } + + out +} + /// Returns a vCard containing given contacts. /// /// Calling [`parse_vcard()`] on the returned result is a reverse operation. @@ -46,10 +85,6 @@ pub fn make_vcard(contacts: &[VcardContact]) -> String { Some(datetime.format("%Y%m%dT%H%M%SZ").to_string()) } - fn escape(s: &str) -> String { - s.replace(',', "\\,") - } - let mut res = "".to_string(); for c in contacts { // Mustn't contain ',', but it's easier to escape than to error out. @@ -124,7 +159,7 @@ pub fn parse_vcard(vcard: &str) -> Vec { fn vcard_property<'a>(line: &'a str, property: &str) -> Option<(&'a str, String)> { let (params, value) = vcard_property_raw(line, property)?; // Some fields can't contain commas, but unescape them everywhere for safety. - Some((params, value.replace("\\,", ","))) + Some((params, unescape(value))) } fn base64_key(line: &str) -> Option<&str> { let (params, value) = vcard_property_raw(line, "key")?; diff --git a/deltachat-contact-tools/src/vcard/vcard_tests.rs b/deltachat-contact-tools/src/vcard/vcard_tests.rs index e8eb3afe9a..5ebc8d6165 100644 --- a/deltachat-contact-tools/src/vcard/vcard_tests.rs +++ b/deltachat-contact-tools/src/vcard/vcard_tests.rs @@ -91,7 +91,7 @@ fn test_make_and_parse_vcard() { authname: "Alice Wonderland".to_string(), key: Some("[base64-data]".to_string()), profile_image: Some("image in Base64".to_string()), - biography: Some("Hi, I'm Alice".to_string()), + biography: Some("Hi,\nI'm Alice; and this is a backslash: \\".to_string()), timestamp: Ok(1713465762), }, VcardContact { @@ -110,7 +110,7 @@ fn test_make_and_parse_vcard() { FN:Alice Wonderland\r\n\ KEY:data:application/pgp-keys;base64\\,[base64-data]\r\n\ PHOTO:data:image/jpeg;base64\\,image in Base64\r\n\ - NOTE:Hi\\, I'm Alice\r\n\ + NOTE:Hi\\,\\nI'm Alice\\; and this is a backslash: \\\\\r\n\ REV:20240418T184242Z\r\n\ END:VCARD\r\n", "BEGIN:VCARD\r\n\ @@ -276,3 +276,14 @@ END:VCARD", assert!(contacts[0].timestamp.is_err()); assert_eq!(contacts[0].profile_image.as_ref().unwrap(), "/9aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa/Z"); } + +#[test] +fn test_vcard_value_escape_unescape() { + let original = "Text, with; chars and a \\ and a newline\nand a literal newline \\n"; + let expected_escaped = r"Text\, with\; chars and a \\ and a newline\nand a literal newline \\n"; + + let escaped = escape(original); + assert_eq!(escaped, expected_escaped); + let unescaped = unescape(&escaped); + assert_eq!(original, unescaped); +} diff --git a/src/contact/contact_tests.rs b/src/contact/contact_tests.rs index b4682ebdcd..fd3aa66b9a 100644 --- a/src/contact/contact_tests.rs +++ b/src/contact/contact_tests.rs @@ -1145,8 +1145,11 @@ async fn test_make_n_import_vcard() -> Result<()> { let alice = &tcm.alice().await; let bob = &tcm.bob().await; bob.set_config(Config::Displayname, Some("Bob")).await?; - bob.set_config(Config::Selfstatus, Some("It's me, bob")) - .await?; + bob.set_config( + Config::Selfstatus, + Some("It's me,\nbob; and here's a backslash: \\"), + ) + .await?; let avatar_path = bob.dir.path().join("avatar.png"); let avatar_bytes = include_bytes!("../../test-data/image/avatar64x64.png"); let avatar_base64 = base64::engine::general_purpose::STANDARD.encode(avatar_bytes); From 3c4ce17f1e3a692bebb61bcda4248c5a3a6434ee Mon Sep 17 00:00:00 2001 From: iequidoo Date: Mon, 9 Feb 2026 10:33:30 -0300 Subject: [PATCH 08/33] feat: Remove QR code tokens sync compatibility code Remove compatibility code needed for Core <= v1.143, Core 1.144 was released on 2024-09-21. --- src/chat.rs | 30 ------------------------------ src/sync.rs | 14 +------------- 2 files changed, 1 insertion(+), 43 deletions(-) diff --git a/src/chat.rs b/src/chat.rs index 78b6d189b0..57ab53084e 100644 --- a/src/chat.rs +++ b/src/chat.rs @@ -1772,16 +1772,6 @@ impl Chat { .set_i64(Param::GroupNameTimestamp, msg.timestamp_sort) .set_i64(Param::GroupDescriptionTimestamp, msg.timestamp_sort); self.update_param(context).await?; - // TODO: Remove this compat code needed because Core <= v1.143: - // - doesn't accept synchronization of QR code tokens for unpromoted groups, so we also - // send them when the group is promoted. - // - doesn't sync QR code tokens for unpromoted groups and the group might be created - // before an upgrade. - context - .sync_qr_code_tokens(Some(self.grpid.as_str())) - .await - .log_err(context) - .ok(); } let is_bot = context.get_config_bool(Config::Bot).await?; @@ -3894,8 +3884,6 @@ pub(crate) async fn add_contact_to_chat_ex( ); return Ok(false); } - - let sync_qr_code_tokens; if from_handshake && chat.param.get_int(Param::Unpromoted).unwrap_or_default() == 1 { let smeared_time = smeared_time(context); chat.param @@ -3903,11 +3891,7 @@ pub(crate) async fn add_contact_to_chat_ex( .set_i64(Param::GroupNameTimestamp, smeared_time) .set_i64(Param::GroupDescriptionTimestamp, smeared_time); chat.update_param(context).await?; - sync_qr_code_tokens = true; - } else { - sync_qr_code_tokens = false; } - if context.is_self_addr(contact.get_addr()).await? { // ourself is added using ContactId::SELF, do not add this address explicitly. // if SELF is not in the group, members cannot be added at all. @@ -3956,20 +3940,6 @@ pub(crate) async fn add_contact_to_chat_ex( send_msg(context, chat_id, &mut msg).await?; sync = Nosync; - // TODO: Remove this compat code needed because Core <= v1.143: - // - doesn't accept synchronization of QR code tokens for unpromoted groups, so we also send - // them when the group is promoted. - // - doesn't sync QR code tokens for unpromoted groups and the group might be created before - // an upgrade. - if sync_qr_code_tokens - && context - .sync_qr_code_tokens(Some(chat.grpid.as_str())) - .await - .log_err(context) - .is_ok() - { - context.scheduler.interrupt_smtp().await; - } } context.emit_event(EventType::ChatModified(chat_id)); if sync.into() { diff --git a/src/sync.rs b/src/sync.rs index 5eb403d102..b0dc32c755 100644 --- a/src/sync.rs +++ b/src/sync.rs @@ -789,19 +789,7 @@ mod tests { let bob = &tcm.bob().await; tcm.exec_securejoin_qr(bob, alice, &qr).await; - let msg_id = alice.send_sync_msg().await?; - // Core <= v1.143 doesn't sync QR code tokens immediately, so current Core does that when a - // group is promoted for compatibility (because the group could be created by older Core). - // TODO: assert!(msg_id.is_none()); - assert!(msg_id.is_some()); - let sent = alice.pop_sent_msg().await; - let msg = alice.parse_msg(&sent).await; - let mut sync_items = msg.sync_items.unwrap().items; - assert_eq!(sync_items.len(), 1); - let data = sync_items.pop().unwrap().data; - let SyncDataOrUnknown::SyncData(AddQrToken(_)) = data else { - unreachable!(); - }; + assert!(alice.send_sync_msg().await?.is_none()); // Remove Bob because alice2 doesn't have their key. let alice_bob_id = alice.add_or_lookup_contact(bob).await.id; From a1eb3761314ddff70315dd1fdbda77f4e3542dee Mon Sep 17 00:00:00 2001 From: Hocuri Date: Wed, 4 Mar 2026 17:31:54 +0100 Subject: [PATCH 09/33] feat: Don't send unencrypted In-Reply-To and References headers (#7935) --- src/mimefactory.rs | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/src/mimefactory.rs b/src/mimefactory.rs index 3acfb12d80..a1f7bb0f3c 100644 --- a/src/mimefactory.rs +++ b/src/mimefactory.rs @@ -2164,9 +2164,7 @@ fn group_headers_by_confidentiality( mail_builder::headers::raw::Raw::new("[...]").into(), )); } - "in-reply-to" - | "references" - | "auto-submitted" + "auto-submitted" | "chat-version" | "autocrypt-setup-message" | "chat-is-post-message" => { From 964bbad53e451aefd79dcfb1bc7c0df62ee0fc66 Mon Sep 17 00:00:00 2001 From: Nico de Haen Date: Wed, 4 Mar 2026 23:07:23 +0100 Subject: [PATCH 10/33] api: add createQrSvg to jsonrpc (#7949) --- deltachat-jsonrpc/src/api.rs | 11 ++++++++++- 1 file changed, 10 insertions(+), 1 deletion(-) diff --git a/deltachat-jsonrpc/src/api.rs b/deltachat-jsonrpc/src/api.rs index 3f438565b2..3bf8288503 100644 --- a/deltachat-jsonrpc/src/api.rs +++ b/deltachat-jsonrpc/src/api.rs @@ -31,7 +31,7 @@ use deltachat::peer_channels::{ }; use deltachat::provider::get_provider_info; use deltachat::qr::{self, Qr}; -use deltachat::qr_code_generator::{generate_backup_qr, get_securejoin_qr_svg}; +use deltachat::qr_code_generator::{create_qr_svg, generate_backup_qr, get_securejoin_qr_svg}; use deltachat::reaction::{get_msg_reactions, send_reaction}; use deltachat::securejoin; use deltachat::stock_str::StockMessage; @@ -864,6 +864,8 @@ impl CommandApi { /// if `checkQr()` returns `askVerifyContact` or `askVerifyGroup` /// an out-of-band-verification can be joined using `secure_join()` /// + /// @deprecated as of 2026-03; use create_qr_svg(get_chat_securejoin_qr_code()) instead. + /// /// chat_id: If set to a group-chat-id, /// the Verified-Group-Invite protocol is offered in the QR code; /// works for protected groups as well as for normal groups. @@ -1980,6 +1982,8 @@ impl CommandApi { /// even if there is no concurrent call to [`CommandApi::provide_backup`], /// but will fail after 60 seconds to avoid deadlocks. /// + /// @deprecated as of 2026-03; use `create_qr_svg(get_backup_qr())` instead. + /// /// Returns the QR code rendered as an SVG image. async fn get_backup_qr_svg(&self, account_id: u32) -> Result { let ctx = self.get_context(account_id).await?; @@ -1993,6 +1997,11 @@ impl CommandApi { generate_backup_qr(&ctx, &qr).await } + /// Renders the given text as a QR code SVG image. + async fn create_qr_svg(&self, text: String) -> Result { + create_qr_svg(&text) + } + /// Gets a backup from a remote provider. /// /// This retrieves the backup from a remote device over the network and imports it into From e3a7d555a811c750f116eadb7e2281ff441a8afa Mon Sep 17 00:00:00 2001 From: Hocuri Date: Thu, 5 Mar 2026 10:39:38 +0100 Subject: [PATCH 11/33] docs: Fix documentation for membership change stock strings (#7944) --- src/stock_str.rs | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/src/stock_str.rs b/src/stock_str.rs index b2830955ea..198835210e 100644 --- a/src/stock_str.rs +++ b/src/stock_str.rs @@ -620,10 +620,10 @@ pub(crate) async fn msg_chat_description_changed( } } -/// Stock string: `You added member %1$s.` or `Member %1$s added by %2$s.`. +/// Stock string: `Member %1$s added.`, `You added member %1$s.` or `Member %1$s added by %2$s.`. /// -/// The `added_member_addr` parameter should be an email address and is looked up in the -/// contacts to combine with the display name. +/// The `added_member` and `by_contact` contacts +/// are looked up in the database to get the display names. pub(crate) async fn msg_add_member_local( context: &Context, added_member: ContactId, @@ -646,10 +646,10 @@ pub(crate) async fn msg_add_member_local( } } -/// Stock string: `I added member %1$s.` or `Member %1$s removed by %2$s.`. +/// Stock string: `Member %1$s removed.` or `You removed member %1$s.` or `Member %1$s removed by %2$s.` /// -/// The `removed_member_addr` parameter should be an email address and is looked up in -/// the contacts to combine with the display name. +/// The `removed_member` and `by_contact` contacts +/// are looked up in the database to get the display names. pub(crate) async fn msg_del_member_local( context: &Context, removed_member: ContactId, From 8ff8ba7416a01b92f659ec1a07e5c3c7ad05ed92 Mon Sep 17 00:00:00 2001 From: link2xt Date: Wed, 4 Mar 2026 11:23:25 +0000 Subject: [PATCH 12/33] refactor: `use super::*` in qr::dclogin_scheme --- src/qr/dclogin_scheme.rs | 20 +++++++++----------- 1 file changed, 9 insertions(+), 11 deletions(-) diff --git a/src/qr/dclogin_scheme.rs b/src/qr/dclogin_scheme.rs index 4059ee0ab2..466adb04e1 100644 --- a/src/qr/dclogin_scheme.rs +++ b/src/qr/dclogin_scheme.rs @@ -200,9 +200,7 @@ pub(crate) fn login_param_from_login_qr( #[cfg(test)] mod test { - use anyhow::bail; - - use super::{LoginOptions, decode_login}; + use super::*; use crate::{login_param::EnteredCertificateChecks, provider::Socket, qr::Qr}; macro_rules! login_options_just_pw { @@ -225,7 +223,7 @@ mod test { } #[test] - fn minimal_no_options() -> anyhow::Result<()> { + fn minimal_no_options() -> Result<()> { let result = decode_login("dclogin://email@host.tld?p=123&v=1")?; if let Qr::Login { address, options } = result { assert_eq!(address, "email@host.tld".to_owned()); @@ -250,7 +248,7 @@ mod test { Ok(()) } #[test] - fn minimal_no_options_no_double_slash() -> anyhow::Result<()> { + fn minimal_no_options_no_double_slash() -> Result<()> { let result = decode_login("dclogin:email@host.tld?p=123&v=1")?; if let Qr::Login { address, options } = result { assert_eq!(address, "email@host.tld".to_owned()); @@ -289,7 +287,7 @@ mod test { } #[test] - fn version_too_new() -> anyhow::Result<()> { + fn version_too_new() -> Result<()> { let result = decode_login("dclogin:email@host.tld/?p=123456&v=2")?; if let Qr::Login { options, .. } = result { assert_eq!(options, LoginOptions::UnsuportedVersion(2)); @@ -306,7 +304,7 @@ mod test { } #[test] - fn all_advanced_options() -> anyhow::Result<()> { + fn all_advanced_options() -> Result<()> { let result = decode_login( "dclogin:email@host.tld?p=secret&v=1&ih=imap.host.tld&ip=4000&iu=max&ipw=87654&is=ssl&ic=1&sh=mail.host.tld&sp=3000&su=max@host.tld&spw=3242HS&ss=plain&sc=3", )?; @@ -336,7 +334,7 @@ mod test { } #[test] - fn uri_encoded_password() -> anyhow::Result<()> { + fn uri_encoded_password() -> Result<()> { let result = decode_login( "dclogin:email@host.tld?p=%7BDaehFl%3B%22as%40%21fhdodn5%24234%22%7B%7Dfg&v=1", )?; @@ -353,7 +351,7 @@ mod test { } #[test] - fn email_with_plus_extension() -> anyhow::Result<()> { + fn email_with_plus_extension() -> Result<()> { let result = decode_login("dclogin:usename+extension@host?p=1234&v=1")?; if let Qr::Login { address, options } = result { assert_eq!(address, "usename+extension@host".to_owned()); @@ -365,7 +363,7 @@ mod test { } #[tokio::test(flavor = "multi_thread", worker_threads = 2)] - async fn test_decode_dclogin_ipv4() -> anyhow::Result<()> { + async fn test_decode_dclogin_ipv4() -> Result<()> { let result = decode_login("dclogin://test@[127.0.0.1]?p=1234&v=1")?; if let Qr::Login { address, options } = result { assert_eq!(address, "test@[127.0.0.1]".to_owned()); @@ -377,7 +375,7 @@ mod test { } #[tokio::test(flavor = "multi_thread", worker_threads = 2)] - async fn test_decode_dclogin_ipv6() -> anyhow::Result<()> { + async fn test_decode_dclogin_ipv6() -> Result<()> { let result = decode_login("dclogin://test@[2001:0db8:85a3:0000:0000:8a2e:0370:7334]?p=1234&v=1")?; if let Qr::Login { address, options } = result { From 89b5675b830e61ca13c26bf2bdcd6cd95febda4d Mon Sep 17 00:00:00 2001 From: link2xt Date: Wed, 4 Mar 2026 14:11:06 +0000 Subject: [PATCH 13/33] fix: percent-decode the address in `dclogin://` URLs --- src/qr/dclogin_scheme.rs | 21 +++++++++++++++++++-- 1 file changed, 19 insertions(+), 2 deletions(-) diff --git a/src/qr/dclogin_scheme.rs b/src/qr/dclogin_scheme.rs index 466adb04e1..7cc0e58a8b 100644 --- a/src/qr/dclogin_scheme.rs +++ b/src/qr/dclogin_scheme.rs @@ -81,9 +81,14 @@ pub(super) fn decode_login(qr: &str) -> Result { .map(|(key, value)| (key.into_owned(), value.into_owned())) .collect(); + let addr = percent_encoding::percent_decode_str(addr) + .decode_utf8() + .context("Address must be UTF-8")? + .to_string(); + // check if username is there - if !may_be_valid_addr(addr) { - bail!("invalid DCLOGIN payload: invalid username E5"); + if !may_be_valid_addr(&addr) { + bail!("Invalid DCLOGIN payload: invalid username {addr:?}."); } // apply to result struct @@ -333,6 +338,18 @@ mod test { Ok(()) } + #[test] + fn uri_encoded_login() -> Result<()> { + let result = decode_login("dclogin:username@%5b192.168.1.1%5d?p=1234&v=1")?; + if let Qr::Login { address, options } = result { + assert_eq!(address, "username@[192.168.1.1]".to_owned()); + assert_eq!(options, login_options_just_pw!("1234".to_owned())); + } else { + bail!("wrong type") + } + Ok(()) + } + #[test] fn uri_encoded_password() -> Result<()> { let result = decode_login( From 0c4e32363ed50507ce788517fbdd64b91cc830f3 Mon Sep 17 00:00:00 2001 From: iequidoo Date: Wed, 4 Mar 2026 12:16:02 -0300 Subject: [PATCH 14/33] fix: Make broadcast owner and subscriber hidden contacts for each other (#7856) --- src/chat.rs | 6 +++++- src/chat/chat_tests.rs | 16 ++++++++++++++++ src/mimefactory.rs | 11 +++++++++-- src/securejoin.rs | 7 ++++--- src/securejoin/bob.rs | 10 ++++++++-- 5 files changed, 42 insertions(+), 8 deletions(-) diff --git a/src/chat.rs b/src/chat.rs index 57ab53084e..307aa6d12f 100644 --- a/src/chat.rs +++ b/src/chat.rs @@ -257,7 +257,11 @@ impl ChatId { ChatIdBlocked::get_for_contact(context, contact_id, create_blocked) .await .map(|chat| chat.id)?; - ContactId::scaleup_origin(context, &[contact_id], Origin::CreateChat).await?; + if create_blocked != Blocked::Yes { + info!(context, "Scale up origin of {contact_id} to CreateChat."); + ContactId::scaleup_origin(context, &[contact_id], Origin::CreateChat) + .await?; + } chat_id } else { warn!( diff --git a/src/chat/chat_tests.rs b/src/chat/chat_tests.rs index 7a9a9b7809..70914ae8af 100644 --- a/src/chat/chat_tests.rs +++ b/src/chat/chat_tests.rs @@ -4835,6 +4835,22 @@ async fn test_sync_create_group() -> Result<()> { Ok(()) } +#[tokio::test(flavor = "multi_thread", worker_threads = 2)] +async fn test_broadcast_contacts_are_hidden() -> Result<()> { + let mut tcm = TestContextManager::new(); + let alice = &tcm.alice().await; + let bob = &tcm.bob().await; + + let alice_chat_id = create_broadcast(alice, "Channel".to_string()).await?; + let qr = get_securejoin_qr(alice, Some(alice_chat_id)).await?; + tcm.exec_securejoin_qr(bob, alice, &qr).await; + send_text_msg(alice, alice_chat_id, "hello".to_string()).await?; + bob.recv_msg(&alice.pop_sent_msg().await).await; + assert_eq!(Contact::get_all(alice, 0, None).await?.len(), 0); + assert_eq!(Contact::get_all(bob, 0, None).await?.len(), 0); + Ok(()) +} + /// Tests sending JPEG image with .png extension. /// /// This is a regression test, previously sending failed diff --git a/src/mimefactory.rs b/src/mimefactory.rs index a1f7bb0f3c..b2090b27c6 100644 --- a/src/mimefactory.rs +++ b/src/mimefactory.rs @@ -456,9 +456,16 @@ impl MimeFactory { .filter(|id| *id != ContactId::SELF) .collect(); if recipient_ids.len() == 1 - && msg.param.get_cmd() != SystemMessage::MemberRemovedFromGroup - && chat.typ != Chattype::OutBroadcast + && !matches!( + msg.param.get_cmd(), + SystemMessage::MemberRemovedFromGroup | SystemMessage::SecurejoinMessage + ) + && !matches!(chat.typ, Chattype::OutBroadcast | Chattype::InBroadcast) { + info!( + context, + "Scale up origin of {} recipients to OutgoingTo.", chat.id + ); ContactId::scaleup_origin(context, &recipient_ids, Origin::OutgoingTo).await?; } diff --git a/src/securejoin.rs b/src/securejoin.rs index 46e56cf921..8484fbc422 100644 --- a/src/securejoin.rs +++ b/src/securejoin.rs @@ -641,15 +641,12 @@ pub(crate) async fn handle_securejoin_handshake( mark_contact_id_as_verified(context, contact_id, Some(ContactId::SELF)).await?; } contact_id.regossip_keys(context).await?; - ContactId::scaleup_origin(context, &[contact_id], Origin::SecurejoinInvited).await?; // for setup-contact, make Alice's one-to-one chat with Bob visible // (secure-join-information are shown in the group chat) if grpid.is_empty() { ChatId::create_for_contact(context, contact_id).await?; } - context.emit_event(EventType::ContactsChanged(Some(contact_id))); if let Some(joining_chat_id) = joining_chat_id { - // Join group. chat::add_contact_to_chat_ex(context, Nosync, joining_chat_id, contact_id, true) .await?; @@ -659,6 +656,10 @@ pub(crate) async fn handle_securejoin_handshake( // We don't use the membership consistency algorithm for broadcast channels, // so, sync the memberlist when adding a contact chat.sync_contacts(context).await.log_err(context).ok(); + } else { + ContactId::scaleup_origin(context, &[contact_id], Origin::SecurejoinInvited) + .await?; + context.emit_event(EventType::ContactsChanged(Some(contact_id))); } inviter_progress(context, contact_id, joining_chat_id, chat.typ)?; diff --git a/src/securejoin/bob.rs b/src/securejoin/bob.rs index e676b274af..db26f6ad79 100644 --- a/src/securejoin/bob.rs +++ b/src/securejoin/bob.rs @@ -49,8 +49,14 @@ pub(super) async fn start_protocol(context: &Context, invite: QrInvite) -> Resul // receive_imf. let private_chat_id = private_chat_id(context, &invite).await?; - ContactId::scaleup_origin(context, &[invite.contact_id()], Origin::SecurejoinJoined).await?; - context.emit_event(EventType::ContactsChanged(None)); + match invite { + QrInvite::Group { .. } | QrInvite::Contact { .. } => { + ContactId::scaleup_origin(context, &[invite.contact_id()], Origin::SecurejoinJoined) + .await?; + context.emit_event(EventType::ContactsChanged(None)); + } + QrInvite::Broadcast { .. } => {} + } let has_key = context .sql From d1c3a679a0358cd41d4565f289f1e85f2672c30a Mon Sep 17 00:00:00 2001 From: link2xt Date: Tue, 3 Mar 2026 02:38:08 +0000 Subject: [PATCH 15/33] ci: allow non-hash references for actions/* and dependabot/* --- .github/zizmor.yml | 6 ++++++ 1 file changed, 6 insertions(+) create mode 100644 .github/zizmor.yml diff --git a/.github/zizmor.yml b/.github/zizmor.yml new file mode 100644 index 0000000000..a40e4188f9 --- /dev/null +++ b/.github/zizmor.yml @@ -0,0 +1,6 @@ +rules: + unpinned-uses: + config: + policies: + actions/*: ref-pin + dependabot/*: ref-pin From 5f84be718a83a8c0c305bc88e0f6a2781f56b1eb Mon Sep 17 00:00:00 2001 From: link2xt Date: Tue, 3 Mar 2026 02:38:08 +0000 Subject: [PATCH 16/33] ci: update zizmor workflow to use zizmorcore/zizmor-action --- .github/workflows/zizmor-scan.yml | 19 +++++++------------ 1 file changed, 7 insertions(+), 12 deletions(-) diff --git a/.github/workflows/zizmor-scan.yml b/.github/workflows/zizmor-scan.yml index bed873f4e4..214dd03a2b 100644 --- a/.github/workflows/zizmor-scan.yml +++ b/.github/workflows/zizmor-scan.yml @@ -6,26 +6,21 @@ on: pull_request: branches: ["**"] +permissions: {} + jobs: zizmor: - name: zizmor latest via PyPI + name: Run zizmor runs-on: ubuntu-latest permissions: - security-events: write + security-events: write # Required for upload-sarif (used by zizmor-action) to upload SARIF files. + contents: read + actions: read steps: - name: Checkout repository uses: actions/checkout@v6 with: persist-credentials: false - - name: Install the latest version of uv - uses: astral-sh/setup-uv@eac588ad8def6316056a12d4907a9d4d84ff7a3b - - name: Run zizmor - run: uvx zizmor --format sarif . > results.sarif - - - name: Upload SARIF file - uses: github/codeql-action/upload-sarif@v4 - with: - sarif_file: results.sarif - category: zizmor + uses: zizmorcore/zizmor-action@0dce2577a4760a2749d8cfb7a84b7d5585ebcb7d # v0.5.0 From abb93cd79d78ba759fc36d872f7f90f76a00c906 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jagoda=20Estera=20=C5=9Al=C4=85zak?= <128227338+j-g00da@users.noreply.github.com> Date: Thu, 5 Mar 2026 15:56:23 +0100 Subject: [PATCH 17/33] fix: Set proper placeholder texts for system messages (#7953) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Don't use first-person form in placeholder texts, as these can be misleading when broadcasted to group. Additionally ensures that broadcasted system messages are not localized to not leak locally-set language to the group chat. Fixes #7930 Signed-off-by: Jagoda Ślązak --- src/chat/chat_tests.rs | 4 ++-- src/mimefactory.rs | 27 +++++++++++++++++++++++---- 2 files changed, 25 insertions(+), 6 deletions(-) diff --git a/src/chat/chat_tests.rs b/src/chat/chat_tests.rs index 70914ae8af..7da7e7c76b 100644 --- a/src/chat/chat_tests.rs +++ b/src/chat/chat_tests.rs @@ -3716,7 +3716,7 @@ async fn test_leave_broadcast_multidevice() -> Result<()> { let leave_msg = bob0.pop_sent_msg().await; let parsed = MimeMessage::from_bytes(bob1, leave_msg.payload().as_bytes()).await?; - assert_eq!(parsed.parts[0].msg, "I left the group."); + assert_eq!(parsed.parts[0].msg, "bob@example.net left the group."); let rcvd = bob1.recv_msg(&leave_msg).await; @@ -3783,7 +3783,7 @@ async fn test_only_broadcast_owner_can_send_1() -> Result<()> { "Bob receives an answer, but shows it in 1:1 chat because of a fingerprint mismatch", ); let rcvd = bob.recv_msg(&member_added).await; - assert_eq!(rcvd.text, "I added member bob@example.net."); + assert_eq!(rcvd.text, "Member bob@example.net was added."); let bob_alice_chat_id = bob.get_chat(alice).await.id; assert_eq!(rcvd.chat_id, bob_alice_chat_id); diff --git a/src/mimefactory.rs b/src/mimefactory.rs index b2090b27c6..976f62dda9 100644 --- a/src/mimefactory.rs +++ b/src/mimefactory.rs @@ -1416,9 +1416,9 @@ impl MimeFactory { .await? .unwrap_or_default() { - placeholdertext = Some("I left the group.".to_string()); + placeholdertext = Some(format!("{email_to_remove} left the group.")); } else { - placeholdertext = Some(format!("I removed member {email_to_remove}.")); + placeholdertext = Some(format!("Member {email_to_remove} was removed.")); }; if !email_to_remove.is_empty() { @@ -1441,7 +1441,7 @@ impl MimeFactory { let email_to_add = msg.param.get(Param::Arg).unwrap_or_default(); let fingerprint_to_add = msg.param.get(Param::Arg4).unwrap_or_default(); - placeholdertext = Some(format!("I added member {email_to_add}.")); + placeholdertext = Some(format!("Member {email_to_add} was added.")); if !email_to_add.is_empty() { headers.push(( @@ -1466,6 +1466,7 @@ impl MimeFactory { } } SystemMessage::GroupNameChanged => { + placeholdertext = Some("Group name changed.".to_string()); let old_name = msg.param.get(Param::Arg).unwrap_or_default().to_string(); headers.push(( "Chat-Group-Name-Changed", @@ -1482,6 +1483,7 @@ impl MimeFactory { )); } SystemMessage::GroupImageChanged => { + placeholdertext = Some("Group image changed.".to_string()); headers.push(( "Chat-Content", mail_builder::headers::text::Text::new("group-avatar-changed").into(), @@ -1493,7 +1495,24 @@ impl MimeFactory { )); } } - _ => {} + SystemMessage::Unknown => {} + SystemMessage::AutocryptSetupMessage => {} + SystemMessage::SecurejoinMessage => {} + SystemMessage::LocationStreamingEnabled => {} + SystemMessage::LocationOnly => {} + SystemMessage::EphemeralTimerChanged => {} + SystemMessage::ChatProtectionEnabled => {} + SystemMessage::ChatProtectionDisabled => {} + SystemMessage::InvalidUnencryptedMail => {} + SystemMessage::SecurejoinWait => {} + SystemMessage::SecurejoinWaitTimeout => {} + SystemMessage::MultiDeviceSync => {} + SystemMessage::WebxdcStatusUpdate => {} + SystemMessage::WebxdcInfoMessage => {} + SystemMessage::IrohNodeAddr => {} + SystemMessage::ChatE2ee => {} + SystemMessage::CallAccepted => {} + SystemMessage::CallEnded => {} } if command == SystemMessage::GroupDescriptionChanged From 1e20055523dbaff6a7f0c865b3a21130b03678a8 Mon Sep 17 00:00:00 2001 From: Hocuri Date: Fri, 6 Mar 2026 10:29:17 +0100 Subject: [PATCH 18/33] feat: Don't send unencrypted Auto-Submitted header (#7938) Cherry-picked 8c09ca3 Follow-up to https://github.com/chatmail/core/pull/7935 --- src/imap/session.rs | 1 - src/mimefactory.rs | 12 +++--------- src/securejoin/securejoin_tests.rs | 11 ++++++----- 3 files changed, 9 insertions(+), 15 deletions(-) diff --git a/src/imap/session.rs b/src/imap/session.rs index 3a3a6a483e..05e3c6d3d0 100644 --- a/src/imap/session.rs +++ b/src/imap/session.rs @@ -23,7 +23,6 @@ const PREFETCH_FLAGS: &str = "(UID INTERNALDATE RFC822.SIZE BODY.PEEK[HEADER.FIE FROM \ CHAT-VERSION \ CHAT-IS-POST-MESSAGE \ - AUTO-SUBMITTED \ AUTOCRYPT-SETUP-MESSAGE\ )])"; diff --git a/src/mimefactory.rs b/src/mimefactory.rs index 976f62dda9..8c5d060389 100644 --- a/src/mimefactory.rs +++ b/src/mimefactory.rs @@ -1553,13 +1553,10 @@ impl MimeFactory { | SystemMessage::MultiDeviceSync | SystemMessage::WebxdcStatusUpdate => { // This should prevent automatic replies, - // such as non-delivery reports. + // such as non-delivery reports, + // if the message is unencrypted. // // See - // - // Adding this header without encryption leaks some - // information about the message contents, but it can - // already be easily guessed from message timing and size. headers.push(( "Auto-Submitted", mail_builder::headers::raw::Raw::new("auto-generated").into(), @@ -2190,10 +2187,7 @@ fn group_headers_by_confidentiality( mail_builder::headers::raw::Raw::new("[...]").into(), )); } - "auto-submitted" - | "chat-version" - | "autocrypt-setup-message" - | "chat-is-post-message" => { + "chat-version" | "autocrypt-setup-message" | "chat-is-post-message" => { unprotected_headers.push(header.clone()); } _ => { diff --git a/src/securejoin/securejoin_tests.rs b/src/securejoin/securejoin_tests.rs index 7dabd1ba10..5d7bc015b8 100644 --- a/src/securejoin/securejoin_tests.rs +++ b/src/securejoin/securejoin_tests.rs @@ -138,14 +138,15 @@ async fn test_setup_contact_ex(case: SetupContactCase) { ); let sent = alice.pop_sent_msg().await; - assert_eq!( - sent.payload.contains("Auto-Submitted: auto-generated"), - alice_auto_submitted_hdr - ); + assert_eq!(sent.payload.contains("Auto-Submitted:"), false); assert!(!sent.payload.contains("Alice Exampleorg")); let msg = bob.parse_msg(&sent).await; assert!(msg.was_encrypted()); assert_eq!(msg.get_header(HeaderDef::SecureJoin).unwrap(), "vc-pubkey"); + assert_eq!( + msg.get_header(HeaderDef::AutoSubmitted), + alice_auto_submitted_hdr.then_some("auto-generated") + ); let bob_chat = bob.get_chat(&alice).await; assert_eq!(bob_chat.can_send(&bob).await.unwrap(), true); @@ -266,7 +267,7 @@ async fn test_setup_contact_ex(case: SetupContactCase) { let sent = alice.pop_sent_msg().await; assert_eq!( sent.payload.contains("Auto-Submitted: auto-generated"), - alice_auto_submitted_hdr + false ); assert!(!sent.payload.contains("Alice Exampleorg")); let msg = bob.parse_msg(&sent).await; From cce8e3bc5ac66b6b33e0721deebbcb0697906861 Mon Sep 17 00:00:00 2001 From: link2xt Date: Fri, 6 Mar 2026 08:49:03 +0000 Subject: [PATCH 19/33] fix: do not run more than one housekeeping at a time With multiple transports there are multiple inbox loops on the same profile `Context`. They tend to start running housekeeping at the same time, e.g. when deleting a message with an attachment, and then `remove_unused_files()` tries to remove the same files that are already deleted by another thread and logs errors. --- src/context.rs | 4 ++++ src/sql.rs | 4 ++++ 2 files changed, 8 insertions(+) diff --git a/src/context.rs b/src/context.rs index 144b0ac9b4..6f57fc52b7 100644 --- a/src/context.rs +++ b/src/context.rs @@ -239,6 +239,9 @@ pub struct InnerContext { 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. + pub(crate) housekeeping_mutex: Mutex<()>, + pub(crate) translated_stockstrings: StockStrings, pub(crate) events: Events, @@ -478,6 +481,7 @@ impl Context { generating_key_mutex: Mutex::new(()), oauth2_mutex: Mutex::new(()), wrong_pw_warning_mutex: Mutex::new(()), + housekeeping_mutex: Mutex::new(()), translated_stockstrings: stockstrings, events, scheduler: SchedulerState::new(), diff --git a/src/sql.rs b/src/sql.rs index dbd4ffba34..29be64a5d3 100644 --- a/src/sql.rs +++ b/src/sql.rs @@ -828,6 +828,10 @@ async fn incremental_vacuum(context: &Context) -> Result<()> { /// Cleanup the account to restore some storage and optimize the database. pub async fn housekeeping(context: &Context) -> Result<()> { + let Ok(_housekeeping_lock) = context.housekeeping_mutex.try_lock() else { + // Housekeeping is already running in another thread, do nothing. + return Ok(()); + }; // Setting `Config::LastHousekeeping` at the beginning avoids endless loops when things do not // work out for whatever reason or are interrupted by the OS. if let Err(e) = context From 874e38c1464c99ca625a4e0a7abe8c8c169a05d2 Mon Sep 17 00:00:00 2001 From: link2xt Date: Fri, 6 Mar 2026 04:34:24 +0000 Subject: [PATCH 20/33] refactor: move WAL checkpointing into `sql::pool` submodule This change is mainly to avoid exposing the write lock outside the pool module. To avoid deadlocks, outside code should work only with the pooled connections and use no more than one connection per thread. --- src/sql.rs | 73 ++++++--------------------- src/sql/pool.rs | 12 ++--- src/sql/pool/wal_checkpoint.rs | 92 ++++++++++++++++++++++++++++++++++ 3 files changed, 113 insertions(+), 64 deletions(-) create mode 100644 src/sql/pool/wal_checkpoint.rs diff --git a/src/sql.rs b/src/sql.rs index 29be64a5d3..48cdb1b919 100644 --- a/src/sql.rs +++ b/src/sql.rs @@ -4,7 +4,7 @@ use std::collections::{HashMap, HashSet}; use std::path::{Path, PathBuf}; use std::time::Duration; -use anyhow::{Context as _, Result, bail, ensure}; +use anyhow::{Context as _, Result, bail}; use rusqlite::{Connection, OpenFlags, Row, config::DbConfig, types::ValueRef}; use tokio::sync::RwLock; @@ -25,7 +25,7 @@ use crate::net::http::http_cache_cleanup; use crate::net::prune_connection_history; use crate::param::{Param, Params}; use crate::stock_str; -use crate::tools::{SystemTime, Time, delete_file, time, time_elapsed}; +use crate::tools::{SystemTime, delete_file, time}; /// Extension to [`rusqlite::ToSql`] trait /// which also includes [`Send`] and [`Sync`]. @@ -48,7 +48,7 @@ macro_rules! params_slice { mod migrations; mod pool; -use pool::Pool; +use pool::{Pool, WalCheckpointStats}; /// A wrapper around the underlying Sqlite3 object. #[derive(Debug)] @@ -663,73 +663,30 @@ impl Sql { &self.config_cache } - /// Runs a checkpoint operation in TRUNCATE mode, so the WAL file is truncated to 0 bytes. - pub(crate) async fn wal_checkpoint(context: &Context) -> Result<()> { - let t_start = Time::now(); - let lock = context.sql.pool.read().await; + /// Attempts to truncate the WAL file. + pub(crate) async fn wal_checkpoint(&self, context: &Context) -> Result<()> { + let lock = self.pool.read().await; let Some(pool) = lock.as_ref() else { // No db connections, nothing to checkpoint. return Ok(()); }; - // Do as much work as possible without blocking anybody. - let query_only = true; - let conn = pool.get(query_only).await?; - tokio::task::block_in_place(|| { - // Execute some transaction causing the WAL file to be opened so that the - // `wal_checkpoint()` can proceed, otherwise it fails when called the first time, - // see https://sqlite.org/forum/forumpost/7512d76a05268fc8. - conn.query_row("PRAGMA table_list", [], |_| Ok(()))?; - conn.query_row("PRAGMA wal_checkpoint(PASSIVE)", [], |_| Ok(())) - })?; - - // Kick out writers. - const _: () = assert!(Sql::N_DB_CONNECTIONS > 1, "Deadlock possible"); - let _write_lock = pool.write_lock().await; - let t_writers_blocked = Time::now(); - // Ensure that all readers use the most recent database snapshot (are at the end of WAL) so - // that `wal_checkpoint(FULL)` isn't blocked. We could use `PASSIVE` as well, but it's - // documented poorly, https://www.sqlite.org/pragma.html#pragma_wal_checkpoint and - // https://www.sqlite.org/c3ref/wal_checkpoint_v2.html don't tell how it interacts with new - // readers. - let mut read_conns = Vec::with_capacity(Self::N_DB_CONNECTIONS - 1); - for _ in 0..(Self::N_DB_CONNECTIONS - 1) { - read_conns.push(pool.get(query_only).await?); - } - read_conns.clear(); - // Checkpoint the remaining WAL pages without blocking readers. - let (pages_total, pages_checkpointed) = tokio::task::block_in_place(|| { - conn.query_row("PRAGMA wal_checkpoint(FULL)", [], |row| { - let pages_total: i64 = row.get(1)?; - let pages_checkpointed: i64 = row.get(2)?; - Ok((pages_total, pages_checkpointed)) - }) - })?; + let WalCheckpointStats { + total_duration, + writers_blocked_duration, + readers_blocked_duration, + pages_total, + pages_checkpointed, + } = pool.wal_checkpoint().await?; if pages_checkpointed < pages_total { warn!( context, "Cannot checkpoint whole WAL. Pages total: {pages_total}, checkpointed: {pages_checkpointed}. Make sure there are no external connections running transactions.", ); } - // Kick out readers to avoid blocking/SQLITE_BUSY. - for _ in 0..(Self::N_DB_CONNECTIONS - 1) { - read_conns.push(pool.get(query_only).await?); - } - let t_readers_blocked = Time::now(); - tokio::task::block_in_place(|| { - let blocked = conn.query_row("PRAGMA wal_checkpoint(TRUNCATE)", [], |row| { - let blocked: i64 = row.get(0)?; - Ok(blocked) - })?; - ensure!(blocked == 0); - Ok(()) - })?; info!( context, - "wal_checkpoint: Total time: {:?}. Writers blocked for: {:?}. Readers blocked for: {:?}.", - time_elapsed(&t_start), - time_elapsed(&t_writers_blocked), - time_elapsed(&t_readers_blocked), + "wal_checkpoint: Total time: {total_duration:?}. Writers blocked for: {writers_blocked_duration:?}. Readers blocked for: {readers_blocked_duration:?}." ); Ok(()) } @@ -886,7 +843,7 @@ pub async fn housekeeping(context: &Context) -> Result<()> { // bigger than 200M) and also make sure we truncate the WAL periodically. Auto-checkponting does // not normally truncate the WAL (unless the `journal_size_limit` pragma is set), see // https://www.sqlite.org/wal.html. - if let Err(err) = Sql::wal_checkpoint(context).await { + if let Err(err) = Sql::wal_checkpoint(&context.sql, context).await { warn!(context, "wal_checkpoint() failed: {err:#}."); debug_assert!(false); } diff --git a/src/sql/pool.rs b/src/sql/pool.rs index 6de8da6ac5..2a428cd8c2 100644 --- a/src/sql/pool.rs +++ b/src/sql/pool.rs @@ -51,6 +51,9 @@ use anyhow::{Context, Result}; use rusqlite::Connection; use tokio::sync::{Mutex, OwnedMutexGuard, OwnedSemaphorePermit, Semaphore}; +mod wal_checkpoint; +pub(crate) use wal_checkpoint::WalCheckpointStats; + /// Inner connection pool. #[derive(Debug)] struct InnerPool { @@ -196,11 +199,8 @@ impl Pool { Arc::clone(&self.inner).get(query_only).await } - /// Returns a mutex guard guaranteeing that there are no concurrent write connections. - /// - /// NB: Make sure you're not holding all connections when calling this, otherwise it deadlocks - /// if there is a concurrent writer waiting for available connection. - pub(crate) async fn write_lock(&self) -> OwnedMutexGuard<()> { - Arc::clone(&self.inner.write_mutex).lock_owned().await + /// Truncates the WAL file. + pub(crate) async fn wal_checkpoint(&self) -> Result { + wal_checkpoint::wal_checkpoint(self).await } } diff --git a/src/sql/pool/wal_checkpoint.rs b/src/sql/pool/wal_checkpoint.rs new file mode 100644 index 0000000000..ff83d0e545 --- /dev/null +++ b/src/sql/pool/wal_checkpoint.rs @@ -0,0 +1,92 @@ +//! # WAL checkpointing for SQLite connection pool. + +use anyhow::{Result, ensure}; +use std::sync::Arc; +use std::time::Duration; + +use crate::sql::Sql; +use crate::tools::{Time, time_elapsed}; + +use super::Pool; + +/// Information about WAL checkpointing call for logging. +#[derive(Debug)] +pub(crate) struct WalCheckpointStats { + /// Duration of the whole WAL checkpointing. + pub total_duration: Duration, + + /// Duration for which WAL checkpointing blocked the writers. + pub writers_blocked_duration: Duration, + + /// Duration for which WAL checkpointing blocked the readers. + pub readers_blocked_duration: Duration, + + /// Number of pages in WAL before truncating. + pub pages_total: i64, + + /// Number of checkpointed WAL pages. + /// + /// It should be the same as `pages_total` + /// unless there are external connections to the database + /// that are not in the pool. + pub pages_checkpointed: i64, +} + +/// Runs a checkpoint operation in TRUNCATE mode, so the WAL file is truncated to 0 bytes. +pub(super) async fn wal_checkpoint(pool: &Pool) -> Result { + let t_start = Time::now(); + + // Do as much work as possible without blocking anybody. + let query_only = true; + let conn = pool.get(query_only).await?; + tokio::task::block_in_place(|| { + // Execute some transaction causing the WAL file to be opened so that the + // `wal_checkpoint()` can proceed, otherwise it fails when called the first time, + // see https://sqlite.org/forum/forumpost/7512d76a05268fc8. + conn.query_row("PRAGMA table_list", [], |_| Ok(()))?; + conn.query_row("PRAGMA wal_checkpoint(PASSIVE)", [], |_| Ok(())) + })?; + + // Kick out writers. + const _: () = assert!(Sql::N_DB_CONNECTIONS > 1, "Deadlock possible"); + let _write_lock = Arc::clone(&pool.inner.write_mutex).lock_owned().await; + let t_writers_blocked = Time::now(); + // Ensure that all readers use the most recent database snapshot (are at the end of WAL) so + // that `wal_checkpoint(FULL)` isn't blocked. We could use `PASSIVE` as well, but it's + // documented poorly, https://www.sqlite.org/pragma.html#pragma_wal_checkpoint and + // https://www.sqlite.org/c3ref/wal_checkpoint_v2.html don't tell how it interacts with new + // readers. + let mut read_conns = Vec::with_capacity(crate::sql::Sql::N_DB_CONNECTIONS - 1); + for _ in 0..(crate::sql::Sql::N_DB_CONNECTIONS - 1) { + read_conns.push(pool.get(query_only).await?); + } + read_conns.clear(); + // Checkpoint the remaining WAL pages without blocking readers. + let (pages_total, pages_checkpointed) = tokio::task::block_in_place(|| { + conn.query_row("PRAGMA wal_checkpoint(FULL)", [], |row| { + let pages_total: i64 = row.get(1)?; + let pages_checkpointed: i64 = row.get(2)?; + Ok((pages_total, pages_checkpointed)) + }) + })?; + // Kick out readers to avoid blocking/SQLITE_BUSY. + for _ in 0..(crate::sql::Sql::N_DB_CONNECTIONS - 1) { + read_conns.push(pool.get(query_only).await?); + } + let t_readers_blocked = Time::now(); + tokio::task::block_in_place(|| { + let blocked = conn.query_row("PRAGMA wal_checkpoint(TRUNCATE)", [], |row| { + let blocked: i64 = row.get(0)?; + Ok(blocked) + })?; + ensure!(blocked == 0); + Ok(()) + })?; + Ok(WalCheckpointStats { + total_duration: time_elapsed(&t_start), + writers_blocked_duration: time_elapsed(&t_writers_blocked), + readers_blocked_duration: time_elapsed(&t_readers_blocked), + pages_total, + pages_checkpointed, + }) +} From 53acfaa054394e54c8435645ed7bad3fbe21cc06 Mon Sep 17 00:00:00 2001 From: link2xt Date: Fri, 6 Mar 2026 04:34:24 +0000 Subject: [PATCH 21/33] fix: add mutex around wal_checkpoint() Documentation comment explains how it prevents the deadlock. --- src/sql/pool.rs | 19 +++++++++++++++++++ src/sql/pool/wal_checkpoint.rs | 1 + 2 files changed, 20 insertions(+) diff --git a/src/sql/pool.rs b/src/sql/pool.rs index 2a428cd8c2..0feb305e0e 100644 --- a/src/sql/pool.rs +++ b/src/sql/pool.rs @@ -71,6 +71,24 @@ struct InnerPool { /// This mutex is locked when write connection /// is outside the pool. pub(crate) write_mutex: Arc>, + + /// WAL checkpointing mutex. + /// + /// This mutex ensures that no more than one thread + /// runs WAL checkpointing at the same time. + /// + /// Normal procedures acquire either one read connection + /// or one write connection with a write mutex, + /// and return the resources without trying to acquire + /// more connections or trying to acquire write mutex + /// without returning the read connection first. + /// WAL checkpointing is special, it tries to acquire all + /// connections and the write mutex, + /// so two threads doing this at the same time + /// may result in a deadlock with one thread + /// waiting for a write lock and the other thread + /// waiting for a connection. + wal_checkpoint_mutex: Mutex<()>, } impl InnerPool { @@ -191,6 +209,7 @@ impl Pool { connections: parking_lot::Mutex::new(connections), semaphore, write_mutex: Default::default(), + wal_checkpoint_mutex: Default::default(), }); Pool { inner } } diff --git a/src/sql/pool/wal_checkpoint.rs b/src/sql/pool/wal_checkpoint.rs index ff83d0e545..c32e92656a 100644 --- a/src/sql/pool/wal_checkpoint.rs +++ b/src/sql/pool/wal_checkpoint.rs @@ -34,6 +34,7 @@ pub(crate) struct WalCheckpointStats { /// Runs a checkpoint operation in TRUNCATE mode, so the WAL file is truncated to 0 bytes. pub(super) async fn wal_checkpoint(pool: &Pool) -> Result { + let _guard = pool.inner.wal_checkpoint_mutex.lock().await; let t_start = Time::now(); // Do as much work as possible without blocking anybody. From 1b43aac3565df5c349812bed6fccb459293083a8 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Sun, 1 Mar 2026 22:00:16 +0000 Subject: [PATCH 22/33] chore(cargo): bump strum from 0.27.2 to 0.28.0 Bumps [strum](https://github.com/Peternator7/strum) from 0.27.2 to 0.28.0. - [Release notes](https://github.com/Peternator7/strum/releases) - [Changelog](https://github.com/Peternator7/strum/blob/master/CHANGELOG.md) - [Commits](https://github.com/Peternator7/strum/compare/v0.27.2...v0.28.0) --- updated-dependencies: - dependency-name: strum dependency-version: 0.28.0 dependency-type: direct:production update-type: version-update:semver-minor ... --- Cargo.lock | 6 +++--- Cargo.toml | 2 +- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 5d2dada334..6c61804389 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1368,7 +1368,7 @@ dependencies = [ "sha2", "shadowsocks", "smallvec", - "strum 0.27.2", + "strum 0.28.0", "strum_macros 0.27.2", "tagger", "tempfile", @@ -5769,9 +5769,9 @@ dependencies = [ [[package]] name = "strum" -version = "0.27.2" +version = "0.28.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "af23d6f6c1a224baef9d3f61e287d2761385a5b88fdab4eb4c6f11aeb54c4bcf" +checksum = "9628de9b8791db39ceda2b119bbe13134770b56c138ec1d3af810d045c04f9bd" [[package]] name = "strum_macros" diff --git a/Cargo.toml b/Cargo.toml index dd30a4627e..2a6f9904cc 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -96,7 +96,7 @@ sha-1 = "0.10" sha2 = "0.10" shadowsocks = { version = "1.23.1", default-features = false, features = ["aead-cipher", "aead-cipher-2022"] } smallvec = "1.15.1" -strum = "0.27" +strum = "0.28" strum_macros = "0.27" tagger = "4.3.4" textwrap = "0.16.2" From d26fa715b545b750a6de1904155419e692e462ec Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Sun, 1 Mar 2026 22:00:37 +0000 Subject: [PATCH 23/33] chore(cargo): bump strum_macros from 0.27.2 to 0.28.0 Bumps [strum_macros](https://github.com/Peternator7/strum) from 0.27.2 to 0.28.0. - [Release notes](https://github.com/Peternator7/strum/releases) - [Changelog](https://github.com/Peternator7/strum/blob/master/CHANGELOG.md) - [Commits](https://github.com/Peternator7/strum/compare/v0.27.2...v0.28.0) --- updated-dependencies: - dependency-name: strum_macros dependency-version: 0.28.0 dependency-type: direct:production update-type: version-update:semver-minor ... --- Cargo.lock | 6 +++--- Cargo.toml | 2 +- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 6c61804389..7f43aff724 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1369,7 +1369,7 @@ dependencies = [ "shadowsocks", "smallvec", "strum 0.28.0", - "strum_macros 0.27.2", + "strum_macros 0.28.0", "tagger", "tempfile", "testdir", @@ -5788,9 +5788,9 @@ dependencies = [ [[package]] name = "strum_macros" -version = "0.27.2" +version = "0.28.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7695ce3845ea4b33927c055a39dc438a45b059f7c1b3d91d38d10355fb8cbca7" +checksum = "ab85eea0270ee17587ed4156089e10b9e6880ee688791d45a905f5b1ca36f664" dependencies = [ "heck 0.5.0", "proc-macro2", diff --git a/Cargo.toml b/Cargo.toml index 2a6f9904cc..4ea9f98867 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -97,7 +97,7 @@ sha2 = "0.10" shadowsocks = { version = "1.23.1", default-features = false, features = ["aead-cipher", "aead-cipher-2022"] } smallvec = "1.15.1" strum = "0.28" -strum_macros = "0.27" +strum_macros = "0.28" tagger = "4.3.4" textwrap = "0.16.2" thiserror = { workspace = true } From 4be35e1cae200f6b86c564953519167508d66927 Mon Sep 17 00:00:00 2001 From: link2xt Date: Mon, 2 Mar 2026 06:37:23 +0000 Subject: [PATCH 24/33] refactor: use re-exported rustls::pki_types --- Cargo.lock | 1 - Cargo.toml | 1 - src/net/tls.rs | 2 +- 3 files changed, 1 insertion(+), 3 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 7f43aff724..410bacd7fe 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1358,7 +1358,6 @@ dependencies = [ "ratelimit", "regex", "rusqlite", - "rustls-pki-types", "sanitize-filename", "sdp", "serde", diff --git a/Cargo.toml b/Cargo.toml index 4ea9f98867..0a972dcf5e 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -86,7 +86,6 @@ rand-old = { package = "rand", version = "0.8" } rand = { workspace = true } regex = { workspace = true } rusqlite = { workspace = true, features = ["sqlcipher"] } -rustls-pki-types = "1.12.0" sanitize-filename = { workspace = true } sdp = "0.10.0" serde_json = { workspace = true } diff --git a/src/net/tls.rs b/src/net/tls.rs index d339f3b2ba..452cd93367 100644 --- a/src/net/tls.rs +++ b/src/net/tls.rs @@ -124,7 +124,7 @@ pub async fn wrap_rustls<'a>( config.enable_sni = use_sni; let tls = tokio_rustls::TlsConnector::from(Arc::new(config)); - let name = rustls_pki_types::ServerName::try_from(hostname)?.to_owned(); + let name = tokio_rustls::rustls::pki_types::ServerName::try_from(hostname)?.to_owned(); let tls_stream = tls.connect(name, stream).await?; Ok(tls_stream) } From 8113520191a89437f9290d2819fceeac18772cd9 Mon Sep 17 00:00:00 2001 From: link2xt Date: Mon, 2 Mar 2026 06:01:13 +0000 Subject: [PATCH 25/33] refactor: import tokio_rustls::rustls --- src/net/tls.rs | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) diff --git a/src/net/tls.rs b/src/net/tls.rs index 452cd93367..0745b32608 100644 --- a/src/net/tls.rs +++ b/src/net/tls.rs @@ -7,6 +7,7 @@ use anyhow::Result; use crate::net::session::SessionStream; +use tokio_rustls::rustls; use tokio_rustls::rustls::client::ClientSessionStore; pub async fn wrap_tls<'a>( @@ -82,7 +83,7 @@ impl TlsSessionStore { .lock() .entry((port, alpn.to_string())) .or_insert_with(|| { - Arc::new(tokio_rustls::rustls::client::ClientSessionMemoryCache::new( + Arc::new(rustls::client::ClientSessionMemoryCache::new( TLS_CACHE_SIZE, )) }), @@ -98,10 +99,10 @@ pub async fn wrap_rustls<'a>( stream: impl SessionStream + 'a, tls_session_store: &TlsSessionStore, ) -> Result { - let mut root_cert_store = tokio_rustls::rustls::RootCertStore::empty(); + let mut root_cert_store = rustls::RootCertStore::empty(); root_cert_store.extend(webpki_roots::TLS_SERVER_ROOTS.iter().cloned()); - let mut config = tokio_rustls::rustls::ClientConfig::builder() + let mut config = rustls::ClientConfig::builder() .with_root_certificates(root_cert_store) .with_no_client_auth(); config.alpn_protocols = if alpn.is_empty() { @@ -118,8 +119,8 @@ pub async fn wrap_rustls<'a>( // and are not worth increasing // attack surface: . let resumption_store = tls_session_store.get(port, alpn); - let resumption = tokio_rustls::rustls::client::Resumption::store(resumption_store) - .tls12_resumption(tokio_rustls::rustls::client::Tls12Resumption::Disabled); + let resumption = rustls::client::Resumption::store(resumption_store) + .tls12_resumption(rustls::client::Tls12Resumption::Disabled); config.resumption = resumption; config.enable_sni = use_sni; From 98068f5945e1b5df9b6e734855111ffd0adb862c Mon Sep 17 00:00:00 2001 From: link2xt Date: Mon, 2 Mar 2026 06:01:13 +0000 Subject: [PATCH 26/33] feat(tls): do not verify TLS certificates for hostnames starting with `_` --- src/net/tls.rs | 10 +++++++- src/net/tls/danger.rs | 55 +++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 64 insertions(+), 1 deletion(-) create mode 100644 src/net/tls/danger.rs diff --git a/src/net/tls.rs b/src/net/tls.rs index 0745b32608..cca4e918f5 100644 --- a/src/net/tls.rs +++ b/src/net/tls.rs @@ -10,6 +10,9 @@ use crate::net::session::SessionStream; use tokio_rustls::rustls; use tokio_rustls::rustls::client::ClientSessionStore; +mod danger; +use danger::NoCertificateVerification; + pub async fn wrap_tls<'a>( strict_tls: bool, hostname: &str, @@ -90,7 +93,6 @@ impl TlsSessionStore { ) } } - pub async fn wrap_rustls<'a>( hostname: &str, port: u16, @@ -124,6 +126,12 @@ pub async fn wrap_rustls<'a>( config.resumption = resumption; config.enable_sni = use_sni; + if hostname.starts_with("_") { + config + .dangerous() + .set_certificate_verifier(Arc::new(NoCertificateVerification::new())); + } + let tls = tokio_rustls::TlsConnector::from(Arc::new(config)); let name = tokio_rustls::rustls::pki_types::ServerName::try_from(hostname)?.to_owned(); let tls_stream = tls.connect(name, stream).await?; diff --git a/src/net/tls/danger.rs b/src/net/tls/danger.rs new file mode 100644 index 0000000000..bd0267ddc0 --- /dev/null +++ b/src/net/tls/danger.rs @@ -0,0 +1,55 @@ +//! Dangerous TLS implementation of accepting invalid certificates for Rustls. + +use rustls::pki_types::{CertificateDer, ServerName, UnixTime}; +use tokio_rustls::rustls; + +#[derive(Debug)] +pub(super) struct NoCertificateVerification(); + +impl NoCertificateVerification { + pub(super) fn new() -> Self { + Self() + } +} + +impl rustls::client::danger::ServerCertVerifier for NoCertificateVerification { + fn verify_server_cert( + &self, + _end_entity: &CertificateDer<'_>, + _intermediates: &[CertificateDer<'_>], + _server_name: &ServerName<'_>, + _ocsp_response: &[u8], + _now: UnixTime, + ) -> Result { + Ok(rustls::client::danger::ServerCertVerified::assertion()) + } + + fn verify_tls12_signature( + &self, + message: &[u8], + cert: &CertificateDer<'_>, + dss: &rustls::DigitallySignedStruct, + ) -> Result { + let provider = rustls::crypto::ring::default_provider(); + let supported_schemes = &provider.signature_verification_algorithms; + rustls::crypto::verify_tls12_signature(message, cert, dss, supported_schemes) + } + + fn verify_tls13_signature( + &self, + message: &[u8], + cert: &CertificateDer<'_>, + dss: &rustls::DigitallySignedStruct, + ) -> Result { + let provider = rustls::crypto::ring::default_provider(); + let supported_schemes = &provider.signature_verification_algorithms; + rustls::crypto::verify_tls13_signature(message, cert, dss, supported_schemes) + } + + fn supported_verify_schemes(&self) -> Vec { + let provider = rustls::crypto::ring::default_provider(); + provider + .signature_verification_algorithms + .supported_schemes() + } +} From a5e875e08ef204234c6b9b76d94166a05eb88a61 Mon Sep 17 00:00:00 2001 From: holger krekel Date: Sun, 1 Mar 2026 23:51:44 +0100 Subject: [PATCH 27/33] feat: support underscore-prefixed domains with self-signed TLS certificates Allow Delta Chat core to work with chatmail servers running on underscore-prefixed domains (e.g. _alice.localchat) which use self-signed TLS certificates. This is mirroring related work on chatmail relays: https://github.com/chatmail/relay/pull/855 Underscore domains with self-signed TLS certs can be used by LXC test containers where obtaining real certificates is not practical. When the domain starts with '_', certificate verification is automatically relaxed for IMAP/SMTP connections, dcaccount QR code handling, and iroh relay endpoints. The Python test suite is adapted to also work against such underscore-domain servers, including cross-core tests with older Delta Chat versions. Note: this PR does not support HTTPS requests with underscore domains. They are not currently needed for working with LXC test containers. 14 files changed, +102/-31 lines (excluding Cargo.lock). Cargo.lock: +606/-11 lines from enabling iroh features needed for connecting to iroh relay endpoint on underscore domains. The added dependencies are unfortunate but best considered when finally upgrading to iroh 1.0 (tm). --- Cargo.lock | 671 +++++++++++++++++- Cargo.toml | 2 +- .../src/deltachat_rpc_client/pytestplugin.py | 26 +- deltachat-rpc-client/tests/conftest.py | 6 +- deltachat-rpc-client/tests/test_calls.py | 2 +- .../tests/test_chatlist_events.py | 4 +- deltachat-rpc-client/tests/test_cross_core.py | 4 +- .../tests/test_multitransport.py | 4 +- deltachat-rpc-client/tests/test_securejoin.py | 5 +- deltachat-rpc-client/tests/test_something.py | 16 +- deltachat-rpc-client/tests/test_webxdc.py | 8 +- src/configure.rs | 8 +- src/peer_channels.rs | 10 +- src/qr.rs | 7 +- src/qr/qr_tests.rs | 29 + 15 files changed, 733 insertions(+), 69 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 410bacd7fe..b333c880cb 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -62,6 +62,19 @@ dependencies = [ "aes", ] +[[package]] +name = "ahash" +version = "0.8.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5a15f179cd60c4584b8a8c596927aadc462e27f2ca70c04e0071964a73ba7a75" +dependencies = [ + "cfg-if", + "getrandom 0.3.3", + "once_cell", + "version_check", + "zerocopy 0.8.40", +] + [[package]] name = "aho-corasick" version = "1.1.3" @@ -116,18 +129,71 @@ version = "0.1.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "4b46cbb362ab8752921c97e041f5e366ee6297bd428a31275b9fcf1e380f7299" +[[package]] +name = "anstream" +version = "0.6.21" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "43d5b281e737544384e969a5ccad3f1cdd24b48086a0fc1b2a5262a26b8f4f4a" +dependencies = [ + "anstyle", + "anstyle-parse", + "anstyle-query", + "anstyle-wincon", + "colorchoice", + "is_terminal_polyfill", + "utf8parse", +] + [[package]] name = "anstyle" version = "1.0.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8901269c6307e8d93993578286ac0edf7f195079ffff5ebdeea6a59ffb7e36bc" +[[package]] +name = "anstyle-parse" +version = "0.2.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4e7644824f0aa2c7b9384579234ef10eb7efb6a0deb83f9630a49594dd9c15c2" +dependencies = [ + "utf8parse", +] + +[[package]] +name = "anstyle-query" +version = "1.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "40c48f72fd53cd289104fc64099abca73db4166ad86ea0b4341abe65af83dadc" +dependencies = [ + "windows-sys 0.61.1", +] + +[[package]] +name = "anstyle-wincon" +version = "3.0.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "291e6a250ff86cd4a820112fb8898808a366d8f9f58ce16d1f538353ad55747d" +dependencies = [ + "anstyle", + "once_cell_polyfill", + "windows-sys 0.61.1", +] + [[package]] name = "anyhow" version = "1.0.102" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7f202df86484c868dbad7eaa557ef785d5c66295e41b460ef922eca0723b842c" +[[package]] +name = "arc-swap" +version = "1.8.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f9f3647c145568cec02c42054e07bdf9a5a698e15b466fb2341bfc393cd24aa5" +dependencies = [ + "rustversion", +] + [[package]] name = "argon2" version = "0.5.3" @@ -391,6 +457,58 @@ version = "1.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ace50bade8e6234aa140d9a2f552bbee1db4d353f69b8217bc503490fc1a9f26" +[[package]] +name = "axum" +version = "0.8.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8b52af3cb4058c895d37317bb27508dccc8e5f2d39454016b297bf4a400597b8" +dependencies = [ + "axum-core", + "bytes", + "form_urlencoded", + "futures-util", + "http 1.1.0", + "http-body", + "http-body-util", + "hyper", + "hyper-util", + "itoa", + "matchit", + "memchr", + "mime", + "percent-encoding", + "pin-project-lite", + "serde_core", + "serde_json", + "serde_path_to_error", + "serde_urlencoded", + "sync_wrapper", + "tokio", + "tower", + "tower-layer", + "tower-service", + "tracing", +] + +[[package]] +name = "axum-core" +version = "0.5.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "08c78f31d7b1291f7ee735c1c6780ccde7785daae9a9206026862dab7d8792d1" +dependencies = [ + "bytes", + "futures-core", + "http 1.1.0", + "http-body", + "http-body-util", + "mime", + "pin-project-lite", + "sync_wrapper", + "tower-layer", + "tower-service", + "tracing", +] + [[package]] name = "backon" version = "1.5.0" @@ -470,9 +588,9 @@ checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a" [[package]] name = "bitflags" -version = "2.11.0" +version = "2.9.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "843867be96c8daad0d758b57df9392b6d8d271134fce549de6ce169ff98a92af" +checksum = "1b8e56985ec62d17e9c1001dc89c88ecd7dc08e47eba5ec7c29c7b5eeecde967" [[package]] name = "bitvec" @@ -769,6 +887,12 @@ dependencies = [ "shlex", ] +[[package]] +name = "cesu8" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6d43a04d8753f35258c91f8ec639f792891f748a1edbd759cf1dcea3382ad83c" + [[package]] name = "cfb-mode" version = "0.8.2" @@ -881,6 +1005,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0fbb260a053428790f3de475e304ff84cdbc4face759ea7a3e64c1edd938a7fc" dependencies = [ "clap_builder", + "clap_derive", ] [[package]] @@ -889,8 +1014,22 @@ version = "4.5.13" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "64b17d7ea74e9f833c7dbf2cbe4fb12ff26783eda4782a8975b72f895c9b4d99" dependencies = [ + "anstream", "anstyle", "clap_lex", + "strsim", +] + +[[package]] +name = "clap_derive" +version = "4.5.13" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "501d359d5f3dcaf6ecdeee48833ae73ec6e42723a1e52419c79abf9507eec0a0" +dependencies = [ + "heck 0.5.0", + "proc-macro2", + "quote", + "syn 2.0.114", ] [[package]] @@ -931,6 +1070,12 @@ version = "1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3d7b894f5411737b7867f4827955924d7c254fc9f4d91a6aad6b097804b1018b" +[[package]] +name = "colorchoice" +version = "1.0.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b05b61dc5112cbb17e4b6cd61790d9845d13888356391624cbe7e41efeac1e75" + [[package]] name = "colorutils-rs" version = "0.7.6" @@ -942,6 +1087,16 @@ dependencies = [ "num-traits", ] +[[package]] +name = "combine" +version = "4.6.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ba5a308b75df32fe02788e748662718f03fde005016435c444eea572398219fd" +dependencies = [ + "bytes", + "memchr", +] + [[package]] name = "concurrent-queue" version = "2.5.0" @@ -989,6 +1144,16 @@ dependencies = [ "libc", ] +[[package]] +name = "core-foundation" +version = "0.10.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b2a6cd9ae233e7f62ba4e9353e81a88df7fc8a5987b8d445b4d90c879bd156f6" +dependencies = [ + "core-foundation-sys", + "libc", +] + [[package]] name = "core-foundation-sys" version = "0.8.7" @@ -1116,7 +1281,7 @@ version = "0.28.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "829d955a0bb380ef178a640b91779e3987da38c9aea133b20614cfed8cdea9c6" dependencies = [ - "bitflags 2.11.0", + "bitflags 2.9.1", "crossterm_winapi", "parking_lot", "rustix 0.38.44", @@ -1283,6 +1448,20 @@ dependencies = [ "syn 2.0.117", ] +[[package]] +name = "dashmap" +version = "6.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5041cc499144891f3790297212f32a74fb938e5136a14943f338ef9e0ae276cf" +dependencies = [ + "cfg-if", + "crossbeam-utils", + "hashbrown 0.14.5", + "lock_api", + "once_cell", + "parking_lot_core", +] + [[package]] name = "data-encoding" version = "2.10.0" @@ -1367,8 +1546,8 @@ dependencies = [ "sha2", "shadowsocks", "smallvec", - "strum 0.28.0", - "strum_macros 0.28.0", + "strum 0.27.2", + "strum_macros 0.27.2", "tagger", "tempfile", "testdir", @@ -1379,7 +1558,7 @@ dependencies = [ "tokio-rustls", "tokio-stream", "tokio-util", - "toml", + "toml 0.9.11+spec-1.1.0", "tracing", "url", "uuid", @@ -2210,6 +2389,12 @@ version = "0.3.32" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "037711b3d59c33004d3856fbdc83b99d4ff37a24768fa1be9ce3538a1cde4393" +[[package]] +name = "futures-timer" +version = "3.0.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f288b0a4f20f9a56b5d1da57e2227c661b7b16168e2f72365f57b63326e29b24" + [[package]] name = "futures-util" version = "0.3.32" @@ -2322,6 +2507,29 @@ dependencies = [ "wasm-bindgen", ] +[[package]] +name = "governor" +version = "0.8.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "be93b4ec2e4710b04d9264c0c7350cdd62a8c20e5e4ac732552ebb8f0debe8eb" +dependencies = [ + "cfg-if", + "dashmap", + "futures-sink", + "futures-timer", + "futures-util", + "getrandom 0.3.3", + "no-std-compat", + "nonzero_ext", + "parking_lot", + "portable-atomic", + "quanta", + "rand 0.9.2", + "smallvec", + "spinning_top", + "web-time", +] + [[package]] name = "group" version = "0.13.0" @@ -2362,6 +2570,12 @@ dependencies = [ "crunchy", ] +[[package]] +name = "hashbrown" +version = "0.14.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e5274423e17b7c9fc20b6e7e208532f9b19825d82dfd615708b70edd83df41f1" + [[package]] name = "hashbrown" version = "0.15.4" @@ -2379,7 +2593,7 @@ version = "0.10.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7382cf6263419f2d8df38c55d7da83da5c18aef87fc7a7fc1fb1e344edfe14c1" dependencies = [ - "hashbrown", + "hashbrown 0.15.4", ] [[package]] @@ -2579,7 +2793,7 @@ dependencies = [ "serde", "serde_derive", "sysinfo 0.37.2", - "toml", + "toml 0.9.11+spec-1.1.0", "uuid", ] @@ -2895,7 +3109,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "4b0f83760fb341a774ed326568e19f5a863af4a952def8c39f9ab92fd95b88e5" dependencies = [ "equivalent", - "hashbrown", + "hashbrown 0.15.4", ] [[package]] @@ -2946,6 +3160,7 @@ dependencies = [ "aead", "anyhow", "atomic-waker", + "axum", "backon", "bytes", "cfg_aliases", @@ -3065,10 +3280,15 @@ version = "0.34.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f70466f14caff7420a14373676947e25e2917af6a5b1bec45825beb2bf1eb6a7" dependencies = [ + "http-body-util", + "hyper", + "hyper-util", "iroh-metrics-derive", "itoa", + "reqwest", "serde", "snafu", + "tokio", "tracing", ] @@ -3117,6 +3337,7 @@ dependencies = [ "rustc-hash", "rustls", "rustls-pki-types", + "rustls-platform-verifier", "slab", "thiserror 2.0.18", "tinyvec", @@ -3144,12 +3365,17 @@ version = "0.35.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c63f122cdfaa4b4e0e7d6d3921d2b878f42a0c6d3ee5a29456dc3f5ab5ec931f" dependencies = [ + "ahash", "anyhow", "bytes", "cfg_aliases", + "clap", + "dashmap", "data-encoding", "derive_more 1.0.0", "getrandom 0.3.3", + "governor", + "hickory-proto", "hickory-resolver", "http 1.1.0", "http-body-util", @@ -3166,25 +3392,42 @@ dependencies = [ "pkarr", "postcard", "rand 0.8.5", + "rcgen", + "regex", + "reloadable-state", "reqwest", "rustls", + "rustls-cert-file-reader", + "rustls-cert-reloadable-resolver", + "rustls-pemfile", "rustls-webpki", "serde", "sha1", + "simdutf8", "strum 0.26.2", "stun-rs", "thiserror 2.0.18", + "time", "tokio", "tokio-rustls", + "tokio-rustls-acme", "tokio-util", "tokio-websockets", + "toml 0.8.23", "tracing", + "tracing-subscriber", "url", "webpki-roots", "ws_stream_wasm", "z32", ] +[[package]] +name = "is_terminal_polyfill" +version = "1.70.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a6cb138bb79a146c1bd460005623e142ef0181e3d0219cb493e02f7d08a35695" + [[package]] name = "itertools" version = "0.13.0" @@ -3200,6 +3443,28 @@ version = "1.0.10" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b1a46d1a171d865aa5f83f92695765caa047a9b4cbae2cbf37dbd613a793fd4c" +[[package]] +name = "jni" +version = "0.21.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1a87aa2bb7d2af34197c04845522473242e1aa17c12f4935d5856491a7fb8c97" +dependencies = [ + "cesu8", + "cfg-if", + "combine", + "jni-sys", + "log", + "thiserror 1.0.69", + "walkdir", + "windows-sys 0.45.0", +] + +[[package]] +name = "jni-sys" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8eaf4bc02d17cbdd7ff4c7438cafcdf7fb9a4613313ad11b4f8fefe7d3fa0130" + [[package]] name = "js-sys" version = "0.3.77" @@ -3269,7 +3534,7 @@ version = "0.1.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c0ff37bd590ca25063e35af745c343cb7a0271906fb7b37e4813e8f79f00268d" dependencies = [ - "bitflags 2.11.0", + "bitflags 2.9.1", "libc", "redox_syscall 0.5.12", ] @@ -3353,7 +3618,7 @@ version = "0.12.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "234cf4f4a04dc1f57e24b96cc0cd600cf2af460d4161ac5ecdd0af8e1f3b2a38" dependencies = [ - "hashbrown", + "hashbrown 0.15.4", ] [[package]] @@ -3394,6 +3659,12 @@ dependencies = [ "regex-automata", ] +[[package]] +name = "matchit" +version = "0.8.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "47e1ffaa40ddd1f3ed91f717a33c8c0ee23fff369e3aa8772b9605cc1d22f4c3" + [[package]] name = "md-5" version = "0.10.6" @@ -3517,7 +3788,7 @@ dependencies = [ "openssl-probe", "openssl-sys", "schannel", - "security-framework", + "security-framework 2.11.1", "security-framework-sys", "tempfile", ] @@ -3583,7 +3854,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0800eae8638a299eaa67476e1c6b6692922273e0f7939fd188fc861c837b9cd2" dependencies = [ "anyhow", - "bitflags 2.11.0", + "bitflags 2.9.1", "byteorder", "libc", "log", @@ -3678,12 +3949,18 @@ version = "0.30.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "74523f3a35e05aba87a1d978330aef40f67b0304ac79c1c00b294c9830543db6" dependencies = [ - "bitflags 2.11.0", + "bitflags 2.9.1", "cfg-if", "cfg_aliases", "libc", ] +[[package]] +name = "no-std-compat" +version = "0.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b93853da6d84c2e3c7d730d6473e8817692dd89be387eb01b94d7f108ecb5b8c" + [[package]] name = "no-std-net" version = "0.6.0" @@ -3709,6 +3986,12 @@ dependencies = [ "memchr", ] +[[package]] +name = "nonzero_ext" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "38bf9645c8b145698bb0b18a4637dcacbc421ea49bef2317e4fd8065a387cf21" + [[package]] name = "ntapi" version = "0.4.1" @@ -3853,7 +4136,7 @@ version = "0.3.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "2a180dd8642fa45cdb7dd721cd4c11b1cadd4929ce112ebd8b9f5803cc79d536" dependencies = [ - "bitflags 2.11.0", + "bitflags 2.9.1", ] [[package]] @@ -3906,6 +4189,12 @@ dependencies = [ "portable-atomic", ] +[[package]] +name = "once_cell_polyfill" +version = "1.70.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "384b8ab6d37215f3c5301a95a4accb5d64aa607f1fcb26a11b5303878451b4fe" + [[package]] name = "oorandom" version = "11.1.4" @@ -3924,7 +4213,7 @@ version = "0.10.72" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "fedfea7d58a1f73118430a55da6a286e7b044961736ce96a16a17068ea25e5da" dependencies = [ - "bitflags 2.11.0", + "bitflags 2.9.1", "cfg-if", "foreign-types", "libc", @@ -4386,7 +4675,7 @@ version = "0.18.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "97baced388464909d42d89643fe4361939af9b7ce7a31ee32a168f832a70f2a0" dependencies = [ - "bitflags 2.11.0", + "bitflags 2.9.1", "crc32fast", "fdeflate", "flate2", @@ -4489,7 +4778,7 @@ version = "0.2.20" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "77957b295656769bb8ad2b6a6b09d897d94f05c41b069aede1fcdaa675eaea04" dependencies = [ - "zerocopy", + "zerocopy 0.7.32", ] [[package]] @@ -4602,11 +4891,11 @@ dependencies = [ [[package]] name = "proptest" -version = "1.10.0" +version = "1.9.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "37566cb3fdacef14c0737f9546df7cfeadbfbc9fef10991038bf5015d0c80532" +checksum = "bee689443a2bd0a16ab0348b52ee43e3b2d1b1f931c8aa5c9f8de4c86fbe8c40" dependencies = [ - "bitflags 2.11.0", + "bitflags 2.9.1", "num-traits", "rand 0.9.2", "rand_chacha 0.9.0", @@ -4646,6 +4935,21 @@ version = "1.8.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "4339fc7a1021c9c1621d87f5e3505f2805c8c105420ba2f2a4df86814590c142" +[[package]] +name = "quanta" +version = "0.12.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f3ab5a9d756f0d97bdc89019bd2e4ea098cf9cde50ee7564dde6b81ccc8f06c7" +dependencies = [ + "crossbeam-utils", + "libc", + "once_cell", + "raw-cpuid", + "wasi 0.11.0+wasi-snapshot-preview1", + "web-sys", + "winapi", +] + [[package]] name = "quick-error" version = "2.0.1" @@ -4841,6 +5145,15 @@ dependencies = [ name = "ratelimit" version = "1.0.0" +[[package]] +name = "raw-cpuid" +version = "11.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "498cd0dc59d73224351ee52a95fee0f1a617a2eae0e7d9d720cc622c73a54186" +dependencies = [ + "bitflags 2.9.1", +] + [[package]] name = "rayon" version = "1.10.0" @@ -4889,7 +5202,7 @@ version = "0.5.12" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "928fca9cf2aa042393a8325b9ead81d2f0df4cb12e1e24cef072922ccd99c5af" dependencies = [ - "bitflags 2.11.0", + "bitflags 2.9.1", ] [[package]] @@ -4938,6 +5251,23 @@ version = "0.8.8" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7a2d987857b319362043e95f5353c0535c1f58eec5336fdfcf626430af7def58" +[[package]] +name = "reloadable-core" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1dc20ac1418988b60072d783c9f68e28a173fb63493c127952f6face3b40c6e0" + +[[package]] +name = "reloadable-state" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3853ef78d45b50f8b989896304a85239539d39b7f866a000e8846b9b72d74ce8" +dependencies = [ + "arc-swap", + "reloadable-core", + "tokio", +] + [[package]] name = "replace_with" version = "0.1.8" @@ -5076,7 +5406,7 @@ version = "0.37.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "165ca6e57b20e1351573e3729b958bc62f0e48025386970b6e4d29e7a7e71f3f" dependencies = [ - "bitflags 2.11.0", + "bitflags 2.9.1", "fallible-iterator", "fallible-streaming-iterator", "hashlink", @@ -5120,7 +5450,7 @@ version = "0.38.44" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "fdb5bc1ae2baa591800df16c9ca78619bf65c0488b41b96ccec5d11220d8c154" dependencies = [ - "bitflags 2.11.0", + "bitflags 2.9.1", "errno", "libc", "linux-raw-sys 0.4.14", @@ -5133,7 +5463,7 @@ version = "1.1.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b6fe4565b9518b83ef4f91bb47ce29620ca828bd32cb7e408f0062e9930ba190" dependencies = [ - "bitflags 2.11.0", + "bitflags 2.9.1", "errno", "libc", "linux-raw-sys 0.12.1", @@ -5155,6 +5485,52 @@ dependencies = [ "zeroize", ] +[[package]] +name = "rustls-cert-file-reader" +version = "0.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8bb47c2a50fdfdaf95b0ac8b12620fc327da1fd4adbb30d0c56d866b005873ff" +dependencies = [ + "rustls-cert-read", + "rustls-pki-types", + "thiserror 2.0.18", + "tokio", +] + +[[package]] +name = "rustls-cert-read" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dd46e8c5ae4de3345c4786a83f99ec7aff287209b9e26fa883c473aeb28f19d5" +dependencies = [ + "rustls-pki-types", +] + +[[package]] +name = "rustls-cert-reloadable-resolver" +version = "0.7.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fe1baa8a3a1f05eaa9fc55aed4342867f70e5c170ea3bfed1b38c51a4857c0c8" +dependencies = [ + "futures-util", + "reloadable-state", + "rustls", + "rustls-cert-read", + "thiserror 2.0.18", +] + +[[package]] +name = "rustls-native-certs" +version = "0.8.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9980d917ebb0c0536119ba501e90834767bffc3d60641457fd84a1f3fd337923" +dependencies = [ + "openssl-probe", + "rustls-pki-types", + "schannel", + "security-framework 3.3.0", +] + [[package]] name = "rustls-pemfile" version = "2.2.0" @@ -5174,6 +5550,33 @@ dependencies = [ "zeroize", ] +[[package]] +name = "rustls-platform-verifier" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e012c45844a1790332c9386ed4ca3a06def221092eda277e6f079728f8ea99da" +dependencies = [ + "core-foundation 0.10.1", + "core-foundation-sys", + "jni", + "log", + "once_cell", + "rustls", + "rustls-native-certs", + "rustls-platform-verifier-android", + "rustls-webpki", + "security-framework 3.3.0", + "security-framework-sys", + "webpki-root-certs 0.26.11", + "windows-sys 0.52.0", +] + +[[package]] +name = "rustls-platform-verifier-android" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f87165f0995f63a9fbeea62b64d10b4d9d8e78ec6d7d51fb2125fda7bb36788f" + [[package]] name = "rustls-webpki" version = "0.102.8" @@ -5197,7 +5600,7 @@ version = "16.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "62fd9ca5ebc709e8535e8ef7c658eb51457987e48c98ead2be482172accc408d" dependencies = [ - "bitflags 2.11.0", + "bitflags 2.9.1", "cfg-if", "clipboard-win", "fd-lock", @@ -5324,8 +5727,21 @@ version = "2.11.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "897b2245f0b511c87893af39b033e5ca9cce68824c4d7e7630b5a1d339658d02" dependencies = [ - "bitflags 2.11.0", - "core-foundation", + "bitflags 2.9.1", + "core-foundation 0.9.4", + "core-foundation-sys", + "libc", + "security-framework-sys", +] + +[[package]] +name = "security-framework" +version = "3.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "80fb1d92c5028aa318b4b8bd7302a5bfcf48be96a37fc6fc790f806b0004ee0c" +dependencies = [ + "bitflags 2.9.1", + "core-foundation 0.10.1", "core-foundation-sys", "libc", "security-framework-sys", @@ -5435,6 +5851,26 @@ dependencies = [ "zmij", ] +[[package]] +name = "serde_path_to_error" +version = "0.1.20" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "10a9ff822e371bb5403e391ecd83e182e0e77ba7f6fe0160b795797109d1b457" +dependencies = [ + "itoa", + "serde", + "serde_core", +] + +[[package]] +name = "serde_spanned" +version = "0.6.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bf41e0cfaf7226dca15e8197172c295a782857fcb97fad1808a166870dee75a3" +dependencies = [ + "serde", +] + [[package]] name = "serde_spanned" version = "1.0.4" @@ -5643,7 +6079,7 @@ version = "0.9.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "dee851d0e5e7af3721faea1843e8015e820a234f81fda3dea9247e15bac9a86a" dependencies = [ - "bitflags 2.11.0", + "bitflags 2.9.1", ] [[package]] @@ -5723,6 +6159,15 @@ dependencies = [ "lock_api", ] +[[package]] +name = "spinning_top" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d96d2d1d716fb500937168cc09353ffdc7a012be8475ac7308e1bdf0e3923300" +dependencies = [ + "lock_api", +] + [[package]] name = "spki" version = "0.7.3" @@ -5768,9 +6213,9 @@ dependencies = [ [[package]] name = "strum" -version = "0.28.0" +version = "0.27.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9628de9b8791db39ceda2b119bbe13134770b56c138ec1d3af810d045c04f9bd" +checksum = "af23d6f6c1a224baef9d3f61e287d2761385a5b88fdab4eb4c6f11aeb54c4bcf" [[package]] name = "strum_macros" @@ -5787,9 +6232,9 @@ dependencies = [ [[package]] name = "strum_macros" -version = "0.28.0" +version = "0.27.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ab85eea0270ee17587ed4156089e10b9e6880ee688791d45a905f5b1ca36f664" +checksum = "7695ce3845ea4b33927c055a39dc438a45b059f7c1b3d91d38d10355fb8cbca7" dependencies = [ "heck 0.5.0", "proc-macro2", @@ -5928,8 +6373,8 @@ version = "0.6.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3c879d448e9d986b661742763247d3693ed13609438cf3d006f51f5368a5ba6b" dependencies = [ - "bitflags 2.11.0", - "core-foundation", + "bitflags 2.9.1", + "core-foundation 0.9.4", "system-configuration-sys", ] @@ -6165,6 +6610,34 @@ dependencies = [ "tokio", ] +[[package]] +name = "tokio-rustls-acme" +version = "0.7.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f296d48ff72e0df96e2d7ef064ad5904d016a130869e542f00b08c8e05cc18cf" +dependencies = [ + "async-trait", + "base64", + "chrono", + "futures", + "log", + "num-bigint", + "pem", + "proc-macro2", + "rcgen", + "reqwest", + "ring", + "rustls", + "serde", + "serde_json", + "thiserror 2.0.18", + "time", + "tokio", + "tokio-rustls", + "webpki-roots", + "x509-parser", +] + [[package]] name = "tokio-stream" version = "0.1.18" @@ -6231,6 +6704,18 @@ dependencies = [ "tokio-util", ] +[[package]] +name = "toml" +version = "0.8.23" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dc1beb996b9d83529a9e75c17a1686767d148d70663143c7854d8b4a09ced362" +dependencies = [ + "serde", + "serde_spanned 0.6.9", + "toml_datetime 0.6.11", + "toml_edit 0.22.27", +] + [[package]] name = "toml" version = "0.9.11+spec-1.1.0" @@ -6239,7 +6724,7 @@ checksum = "f3afc9a848309fe1aaffaed6e1546a7a14de1f935dc9d89d32afd9a44bab7c46" dependencies = [ "indexmap", "serde_core", - "serde_spanned", + "serde_spanned 1.0.4", "toml_datetime 0.7.5+spec-1.1.0", "toml_parser", "toml_writer", @@ -6251,6 +6736,9 @@ name = "toml_datetime" version = "0.6.11" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "22cddaf88f4fbc13c51aebbf5f8eceb5c7c5a9da2ac40a13519eb5b0a0e8f11c" +dependencies = [ + "serde", +] [[package]] name = "toml_datetime" @@ -6279,6 +6767,8 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "41fe8c660ae4257887cf66394862d21dbca4a6ddd26f04a3560410406a2f819a" dependencies = [ "indexmap", + "serde", + "serde_spanned 0.6.9", "toml_datetime 0.6.11", "toml_write", "winnow 0.7.13", @@ -6318,6 +6808,7 @@ dependencies = [ "tokio", "tower-layer", "tower-service", + "tracing", ] [[package]] @@ -6730,6 +7221,24 @@ dependencies = [ "wasm-bindgen", ] +[[package]] +name = "webpki-root-certs" +version = "0.26.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "75c7f0ef91146ebfb530314f5f1d24528d7f0767efbfd31dce919275413e393e" +dependencies = [ + "webpki-root-certs 1.0.0", +] + +[[package]] +name = "webpki-root-certs" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "01a83f7e1a9f8712695c03eabe9ed3fbca0feff0152f33f12593e5a6303cb1a4" +dependencies = [ + "rustls-pki-types", +] + [[package]] name = "webpki-roots" version = "0.26.8" @@ -6956,6 +7465,15 @@ dependencies = [ "windows-targets 0.53.0", ] +[[package]] +name = "windows-sys" +version = "0.45.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "75283be5efb2831d37ea142365f009c02ec203cd29a3ebecbc093d52315b66d0" +dependencies = [ + "windows-targets 0.42.2", +] + [[package]] name = "windows-sys" version = "0.48.0" @@ -6992,6 +7510,21 @@ dependencies = [ "windows-link", ] +[[package]] +name = "windows-targets" +version = "0.42.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8e5180c00cd44c9b1c88adb3693291f1cd93605ded80c250a75d472756b4d071" +dependencies = [ + "windows_aarch64_gnullvm 0.42.2", + "windows_aarch64_msvc 0.42.2", + "windows_i686_gnu 0.42.2", + "windows_i686_msvc 0.42.2", + "windows_x86_64_gnu 0.42.2", + "windows_x86_64_gnullvm 0.42.2", + "windows_x86_64_msvc 0.42.2", +] + [[package]] name = "windows-targets" version = "0.48.5" @@ -7039,6 +7572,12 @@ dependencies = [ "windows_x86_64_msvc 0.53.0", ] +[[package]] +name = "windows_aarch64_gnullvm" +version = "0.42.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "597a5118570b68bc08d8d59125332c54f1ba9d9adeedeef5b99b02ba2b0698f8" + [[package]] name = "windows_aarch64_gnullvm" version = "0.48.5" @@ -7057,6 +7596,12 @@ version = "0.53.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "86b8d5f90ddd19cb4a147a5fa63ca848db3df085e25fee3cc10b39b6eebae764" +[[package]] +name = "windows_aarch64_msvc" +version = "0.42.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e08e8864a60f06ef0d0ff4ba04124db8b0fb3be5776a5cd47641e942e58c4d43" + [[package]] name = "windows_aarch64_msvc" version = "0.48.5" @@ -7075,6 +7620,12 @@ version = "0.53.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c7651a1f62a11b8cbd5e0d42526e55f2c99886c77e007179efff86c2b137e66c" +[[package]] +name = "windows_i686_gnu" +version = "0.42.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c61d927d8da41da96a81f029489353e68739737d3beca43145c8afec9a31a84f" + [[package]] name = "windows_i686_gnu" version = "0.48.5" @@ -7105,6 +7656,12 @@ version = "0.53.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9ce6ccbdedbf6d6354471319e781c0dfef054c81fbc7cf83f338a4296c0cae11" +[[package]] +name = "windows_i686_msvc" +version = "0.42.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "44d840b6ec649f480a41c8d80f9c65108b92d89345dd94027bfe06ac444d1060" + [[package]] name = "windows_i686_msvc" version = "0.48.5" @@ -7123,6 +7680,12 @@ version = "0.53.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "581fee95406bb13382d2f65cd4a908ca7b1e4c2f1917f143ba16efe98a589b5d" +[[package]] +name = "windows_x86_64_gnu" +version = "0.42.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8de912b8b8feb55c064867cf047dda097f92d51efad5b491dfb98f6bbb70cb36" + [[package]] name = "windows_x86_64_gnu" version = "0.48.5" @@ -7141,6 +7704,12 @@ version = "0.53.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "2e55b5ac9ea33f2fc1716d1742db15574fd6fc8dadc51caab1c16a3d3b4190ba" +[[package]] +name = "windows_x86_64_gnullvm" +version = "0.42.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "26d41b46a36d453748aedef1486d5c7a85db22e56aff34643984ea85514e94a3" + [[package]] name = "windows_x86_64_gnullvm" version = "0.48.5" @@ -7159,6 +7728,12 @@ version = "0.53.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0a6e035dd0599267ce1ee132e51c27dd29437f63325753051e71dd9e42406c57" +[[package]] +name = "windows_x86_64_msvc" +version = "0.42.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9aec5da331524158c6d1a4ac0ab1541149c0b9505fde06423b02f5ef0106b9f0" + [[package]] name = "windows_x86_64_msvc" version = "0.48.5" @@ -7211,7 +7786,7 @@ version = "0.39.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6f42320e61fe2cfd34354ecb597f86f413484a798ba44a8ca1165c58d42da6c1" dependencies = [ - "bitflags 2.11.0", + "bitflags 2.9.1", ] [[package]] @@ -7398,7 +7973,16 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "74d4d3961e53fa4c9a25a8637fc2bfaf2595b3d3ae34875568a5cf64787716be" dependencies = [ "byteorder", - "zerocopy-derive", + "zerocopy-derive 0.7.32", +] + +[[package]] +name = "zerocopy" +version = "0.8.40" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a789c6e490b576db9f7e6b6d661bcc9799f7c0ac8352f56ea20193b2681532e5" +dependencies = [ + "zerocopy-derive 0.8.40", ] [[package]] @@ -7412,6 +7996,17 @@ dependencies = [ "syn 2.0.117", ] +[[package]] +name = "zerocopy-derive" +version = "0.8.40" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f65c489a7071a749c849713807783f70672b28094011623e200cb86dcb835953" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.114", +] + [[package]] name = "zerofrom" version = "0.1.5" diff --git a/Cargo.toml b/Cargo.toml index 0a972dcf5e..6efa85e1b9 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -67,7 +67,7 @@ hyper = "1" hyper-util = "0.1.16" image = { version = "0.25.6", default-features=false, features = ["gif", "jpeg", "ico", "png", "pnm", "webp", "bmp"] } iroh-gossip = { version = "0.35", default-features = false, features = ["net"] } -iroh = { version = "0.35", default-features = false } +iroh = { version = "0.35", default-features = false, features = ["test-utils", "metrics"] } kamadak-exif = "0.6.1" libc = { workspace = true } mail-builder = { version = "0.4.4", default-features = false } diff --git a/deltachat-rpc-client/src/deltachat_rpc_client/pytestplugin.py b/deltachat-rpc-client/src/deltachat_rpc_client/pytestplugin.py index 7ee8e61e38..4daa2ec964 100644 --- a/deltachat-rpc-client/src/deltachat_rpc_client/pytestplugin.py +++ b/deltachat-rpc-client/src/deltachat_rpc_client/pytestplugin.py @@ -9,6 +9,7 @@ import random import subprocess import sys +import urllib.parse from typing import AsyncGenerator, Optional import execnet @@ -283,8 +284,16 @@ def factory(core_version): channel = gw.remote_exec(remote_bob_loop) cm = os.environ.get("CHATMAIL_DOMAIN") + # Build a dclogin QR code for the remote bob account. + # Using dclogin scheme with ic=3&sc=3 allows old cores + # to accept invalid certificates for underscore-prefixed domains. + addr, password = acfactory.get_credentials() + dclogin_qr = f"dclogin://{urllib.parse.quote(addr, safe='@')}?p={urllib.parse.quote(password)}&v=1" + if cm and cm.startswith("_"): + dclogin_qr += "&ic=3&sc=3" + # trigger getting an online account on bob's side - channel.send((accounts_dir, str(rpc_server_path), cm)) + channel.send((accounts_dir, str(rpc_server_path), dclogin_qr)) # meanwhile get a local alice account alice = acfactory.get_online_account() @@ -316,10 +325,8 @@ def remote_bob_loop(channel): import os from deltachat_rpc_client import DeltaChat, Rpc - from deltachat_rpc_client.pytestplugin import ACFactory - accounts_dir, rpc_server_path, chatmail_domain = channel.receive() - os.environ["CHATMAIL_DOMAIN"] = chatmail_domain + accounts_dir, rpc_server_path, dclogin_qr = channel.receive() # older core versions don't support specifying rpc_server_path # so we can't just pass `rpc_server_path` argument to Rpc constructor @@ -330,8 +337,14 @@ def remote_bob_loop(channel): with rpc: dc = DeltaChat(rpc) channel.send(dc.rpc.get_system_info()["deltachat_core_version"]) - acfactory = ACFactory(dc) - bob = acfactory.get_online_account() + + # Configure account using dclogin scheme directly, + # avoiding the old ACFactory which doesn't handle + # underscore-prefixed domains' TLS on older cores. + bob = dc.add_account() + bob.set_config_from_qr(dclogin_qr) + bob.bring_online() + alice_vcard = channel.receive() [alice_contact] = bob.import_vcard(alice_vcard) ns = {"bob": bob, "bob_contact_alice": alice_contact} @@ -345,3 +358,4 @@ def remote_bob_loop(channel): except Exception: # some unserializable result channel.send(None) + diff --git a/deltachat-rpc-client/tests/conftest.py b/deltachat-rpc-client/tests/conftest.py index 75de80869c..b8f1600457 100644 --- a/deltachat-rpc-client/tests/conftest.py +++ b/deltachat-rpc-client/tests/conftest.py @@ -37,7 +37,11 @@ def connect(self): host = user.rsplit("@")[-1] pw = self.account.get_config("mail_pw") - self.conn = MailBox(host, port, ssl_context=ssl.create_default_context()) + ssl_context = ssl.create_default_context() + if host.startswith("_"): + ssl_context.check_hostname = False + ssl_context.verify_mode = ssl.CERT_NONE + self.conn = MailBox(host, port, ssl_context=ssl_context) self.conn.login(user, pw) self.select_folder("INBOX") diff --git a/deltachat-rpc-client/tests/test_calls.py b/deltachat-rpc-client/tests/test_calls.py index 6e61a63c09..c5f68c8c4b 100644 --- a/deltachat-rpc-client/tests/test_calls.py +++ b/deltachat-rpc-client/tests/test_calls.py @@ -77,7 +77,7 @@ def test_ice_servers(acfactory) -> None: alice = acfactory.get_online_account() ice_servers = alice.ice_servers() - assert len(ice_servers) == 1 + assert len(ice_servers) >= 1 def test_no_contact_request_call(acfactory) -> None: diff --git a/deltachat-rpc-client/tests/test_chatlist_events.py b/deltachat-rpc-client/tests/test_chatlist_events.py index f13769e071..19d870d875 100644 --- a/deltachat-rpc-client/tests/test_chatlist_events.py +++ b/deltachat-rpc-client/tests/test_chatlist_events.py @@ -109,7 +109,7 @@ def test_delivery_status_failed(acfactory: ACFactory) -> None: assert failing_message.get_snapshot().state == const.MessageState.OUT_FAILED -def test_download_on_demand(acfactory: ACFactory) -> None: +def test_download_on_demand(acfactory: ACFactory, data) -> None: """ Test if download on demand emits chatlist update events. This is only needed for last message in chat, but finding that out is too expensive, so it's always emitted @@ -127,7 +127,7 @@ def test_download_on_demand(acfactory: ACFactory) -> None: msg.get_snapshot().chat.accept() bob.get_chat_by_id(chat_id).send_message( "Hello World, this message is bigger than 5 bytes", - file="../test-data/image/screenshot.jpg", + file=data.get_path("image/screenshot.jpg"), ) message = alice.wait_for_incoming_msg() diff --git a/deltachat-rpc-client/tests/test_cross_core.py b/deltachat-rpc-client/tests/test_cross_core.py index 31d39c7906..c18edf162c 100644 --- a/deltachat-rpc-client/tests/test_cross_core.py +++ b/deltachat-rpc-client/tests/test_cross_core.py @@ -36,7 +36,7 @@ def test_qr_setup_contact(alice_and_remote_bob, version) -> None: def test_send_and_receive_message(alice_and_remote_bob) -> None: """Test other-core Bob profile can send a message to Alice on current core.""" - alice, alice_contact_bob, remote_eval = alice_and_remote_bob("2.20.0") + alice, alice_contact_bob, remote_eval = alice_and_remote_bob("2.24.0") remote_eval("bob_contact_alice.create_chat().send_text('hello')") @@ -46,7 +46,7 @@ def test_send_and_receive_message(alice_and_remote_bob) -> None: def test_second_device(acfactory, alice_and_remote_bob) -> None: """Test setting up current version as a second device for old version.""" - _alice, alice_contact_bob, remote_eval = alice_and_remote_bob("2.20.0") + _alice, alice_contact_bob, remote_eval = alice_and_remote_bob("2.24.0") remote_eval("locals().setdefault('future', bob._rpc.provide_backup.future(bob.id))") qr = remote_eval("bob._rpc.get_backup_qr(bob.id)") diff --git a/deltachat-rpc-client/tests/test_multitransport.py b/deltachat-rpc-client/tests/test_multitransport.py index 4ccba8474b..4593c84477 100644 --- a/deltachat-rpc-client/tests/test_multitransport.py +++ b/deltachat-rpc-client/tests/test_multitransport.py @@ -120,7 +120,7 @@ def test_change_address(acfactory) -> None: assert sender_addr2 == new_alice_addr -def test_download_on_demand(acfactory) -> None: +def test_download_on_demand(acfactory, data) -> None: alice, bob = acfactory.get_online_accounts(2) alice.set_config("download_limit", "1") @@ -131,7 +131,7 @@ def test_download_on_demand(acfactory) -> None: alice.create_chat(bob) chat_bob_alice = bob.create_chat(alice) - chat_bob_alice.send_message(file="../test-data/image/screenshot.jpg") + chat_bob_alice.send_message(file=data.get_path("image/screenshot.jpg")) msg = alice.wait_for_incoming_msg() snapshot = msg.get_snapshot() assert snapshot.download_state == DownloadState.AVAILABLE diff --git a/deltachat-rpc-client/tests/test_securejoin.py b/deltachat-rpc-client/tests/test_securejoin.py index 54799adf13..e9f66fafd7 100644 --- a/deltachat-rpc-client/tests/test_securejoin.py +++ b/deltachat-rpc-client/tests/test_securejoin.py @@ -141,10 +141,9 @@ def get_broadcast(ac): def wait_for_broadcast_messages(ac): snapshot1 = ac.wait_for_incoming_msg().get_snapshot() - assert snapshot1.text == "You joined the channel." - snapshot2 = ac.wait_for_incoming_msg().get_snapshot() - assert snapshot2.text == "Hello everyone!" + texts = {snapshot1.text, snapshot2.text} + assert texts == {"You joined the channel.", "Hello everyone!"} chat = get_broadcast(ac) assert snapshot1.chat_id == chat.id diff --git a/deltachat-rpc-client/tests/test_something.py b/deltachat-rpc-client/tests/test_something.py index 9f41b09f64..d21916bf2e 100644 --- a/deltachat-rpc-client/tests/test_something.py +++ b/deltachat-rpc-client/tests/test_something.py @@ -97,8 +97,11 @@ def test_lowercase_address(acfactory) -> None: def test_configure_ip(acfactory) -> None: addr, password = acfactory.get_credentials() + domain = addr.rsplit("@")[-1] + if domain.startswith("_"): + pytest.skip("Underscore domains accept invalid certificates") account = acfactory.get_unconfigured_account() - ip_address = socket.gethostbyname(addr.rsplit("@")[-1]) + ip_address = socket.gethostbyname(domain) with pytest.raises(JsonRpcError): account.add_or_update_transport( @@ -1012,7 +1015,12 @@ def test_configured_imap_certificate_checks(acfactory): alice = acfactory.new_configured_account() # Certificate checks should be configured (not None) - assert "cert_strict" in alice.get_info().used_transport_settings + info = alice.get_info() + domain = alice.get_config("addr").split("@")[-1] + if domain.startswith("_"): + assert "cert_accept_invalid_certificates" in info.used_transport_settings + else: + assert "cert_strict" in info.used_transport_settings # "cert_old_automatic" is the value old Delta Chat core versions used # to mean user entered "imap_certificate_checks=0" (Automatic) @@ -1361,7 +1369,7 @@ def test_synchronize_member_list_on_group_rejoin(acfactory, log): assert msg.get_snapshot().chat.num_contacts() == 2 -def test_large_message(acfactory) -> None: +def test_large_message(acfactory, data) -> None: """ Test sending large message without download limit set, so it is sent with pre-message but downloaded without user interaction. @@ -1371,7 +1379,7 @@ def test_large_message(acfactory) -> None: alice_chat_bob = alice.create_chat(bob) alice_chat_bob.send_message( "Hello World, this message is bigger than 5 bytes", - file="../test-data/image/screenshot.jpg", + file=data.get_path("image/screenshot.jpg"), ) msg = bob.wait_for_incoming_msg() diff --git a/deltachat-rpc-client/tests/test_webxdc.py b/deltachat-rpc-client/tests/test_webxdc.py index 314e8f6747..11ec514ed9 100644 --- a/deltachat-rpc-client/tests/test_webxdc.py +++ b/deltachat-rpc-client/tests/test_webxdc.py @@ -1,9 +1,9 @@ -def test_webxdc(acfactory) -> None: +def test_webxdc(acfactory, data) -> None: alice, bob = acfactory.get_online_accounts(2) alice_contact_bob = alice.create_contact(bob, "Bob") alice_chat_bob = alice_contact_bob.create_chat() - alice_chat_bob.send_message(text="Let's play chess!", file="../test-data/webxdc/chess.xdc") + alice_chat_bob.send_message(text="Let's play chess!", file=data.get_path("webxdc/chess.xdc")) event = bob.wait_for_incoming_msg_event() bob_chat_alice = bob.get_chat_by_id(event.chat_id) @@ -41,12 +41,12 @@ def test_webxdc(acfactory) -> None: ] -def test_webxdc_insert_lots_of_updates(acfactory) -> None: +def test_webxdc_insert_lots_of_updates(acfactory, data) -> None: alice, bob = acfactory.get_online_accounts(2) alice_contact_bob = alice.create_contact(bob, "Bob") alice_chat_bob = alice_contact_bob.create_chat() - message = alice_chat_bob.send_message(text="Let's play chess!", file="../test-data/webxdc/chess.xdc") + message = alice_chat_bob.send_message(text="Let's play chess!", file=data.get_path("webxdc/chess.xdc")) for i in range(2000): message.send_webxdc_status_update({"payload": str(i)}, "description") diff --git a/src/configure.rs b/src/configure.rs index aeea22e5c5..fe565784f4 100644 --- a/src/configure.rs +++ b/src/configure.rs @@ -534,7 +534,13 @@ async fn get_configured_param( smtp_password, provider, certificate_checks: match param.certificate_checks { - EnteredCertificateChecks::Automatic => ConfiguredCertificateChecks::Automatic, + EnteredCertificateChecks::Automatic => { + if param_domain.starts_with('_') { + ConfiguredCertificateChecks::AcceptInvalidCertificates + } else { + ConfiguredCertificateChecks::Automatic + } + } EnteredCertificateChecks::Strict => ConfiguredCertificateChecks::Strict, EnteredCertificateChecks::AcceptInvalidCertificates | EnteredCertificateChecks::AcceptInvalidCertificates2 => { diff --git a/src/peer_channels.rs b/src/peer_channels.rs index 618bf03d06..7dc9d24026 100644 --- a/src/peer_channels.rs +++ b/src/peer_channels.rs @@ -238,18 +238,21 @@ impl Context { let secret_key = SecretKey::generate(rand_old::rngs::OsRng); let public_key = secret_key.public(); - let relay_mode = if let Some(relay_url) = self + let (relay_mode, skip_relay_tls) = if let Some(relay_url) = self .metadata .read() .await .as_ref() .and_then(|conf| conf.iroh_relay.clone()) { - RelayMode::Custom(RelayUrl::from(relay_url).into()) + // Underscore-prefixed domains use self-signed TLS certificates, + // so we need to skip relay certificate verification for them. + let skip = relay_url.host_str().map_or(false, |h| h.starts_with('_')); + (RelayMode::Custom(RelayUrl::from(relay_url).into()), skip) } else { // FIXME: this should be RelayMode::Disabled instead. // Currently using default relays because otherwise Rust tests fail. - RelayMode::Default + (RelayMode::Default, false) }; let endpoint = Endpoint::builder() @@ -257,6 +260,7 @@ impl Context { .secret_key(secret_key) .alpns(vec![GOSSIP_ALPN.to_vec()]) .relay_mode(relay_mode) + .insecure_skip_relay_cert_verify(skip_relay_tls) .bind() .await?; diff --git a/src/qr.rs b/src/qr.rs index 1cbf734a6a..c35544c69f 100644 --- a/src/qr.rs +++ b/src/qr.rs @@ -805,6 +805,11 @@ pub(crate) async fn login_param_from_account_qr( .context("Invalid DCACCOUNT scheme")?; if !payload.starts_with(HTTPS_SCHEME) { + let certificate_checks = if payload.starts_with('_') { + EnteredCertificateChecks::AcceptInvalidCertificates + } else { + EnteredCertificateChecks::Strict + }; let rng = &mut rand::rngs::OsRng.unwrap_err(); let username = Alphanumeric.sample_string(rng, 9); let addr = username + "@" + payload; @@ -817,7 +822,7 @@ pub(crate) async fn login_param_from_account_qr( ..Default::default() }, smtp: Default::default(), - certificate_checks: EnteredCertificateChecks::Strict, + certificate_checks, oauth2: false, }; return Ok(param); diff --git a/src/qr/qr_tests.rs b/src/qr/qr_tests.rs index 43516c419c..9c4369dd46 100644 --- a/src/qr/qr_tests.rs +++ b/src/qr/qr_tests.rs @@ -737,6 +737,35 @@ async fn test_decode_account() -> Result<()> { Ok(()) } +#[tokio::test(flavor = "multi_thread", worker_threads = 2)] +async fn test_decode_account_underscore_domain() -> Result<()> { + let ctx = TestContext::new().await; + + // Underscore domain is kept as-is. + let qr = check_qr(&ctx.ctx, "dcaccount:_example.org").await?; + assert_eq!( + qr, + Qr::Account { + domain: "_example.org".to_string() + } + ); + + // Verify login params use AcceptInvalidCertificates for underscore domain. + let param = login_param_from_account_qr(&ctx.ctx, "dcaccount:_example.org").await?; + assert!(param.addr.ends_with("@_example.org")); + assert_eq!( + param.certificate_checks, + EnteredCertificateChecks::AcceptInvalidCertificates + ); + + // Regular domain still uses Strict. + let param = login_param_from_account_qr(&ctx.ctx, "dcaccount:example.org").await?; + assert!(param.addr.ends_with("@example.org")); + assert_eq!(param.certificate_checks, EnteredCertificateChecks::Strict); + + Ok(()) +} + #[tokio::test(flavor = "multi_thread", worker_threads = 2)] async fn test_decode_tg_socks_proxy() -> Result<()> { let t = TestContext::new().await; From 429ba11f5348494c4076e74b325e68ea2d2379d0 Mon Sep 17 00:00:00 2001 From: holger krekel Date: Mon, 2 Mar 2026 13:00:16 +0100 Subject: [PATCH 28/33] fix: use Rustls NoCertificateVerification for underscore domains instead of AcceptInvalidCertificates Remove AcceptInvalidCertificates overrides in configure.rs and qr.rs that caused a fallback to OpenSSL/native-tls. The upstream Rustls TLS layer now handles underscore-prefixed domains via NoCertificateVerification directly. Also fix clippy lint in peer_channels.rs (map_or -> is_some_and). --- Cargo.lock | 4 ++-- .../src/deltachat_rpc_client/pytestplugin.py | 1 - deltachat-rpc-client/tests/test_something.py | 2 +- src/configure.rs | 8 +------- src/peer_channels.rs | 2 +- src/qr.rs | 7 +------ src/qr/qr_tests.rs | 12 ++++++++---- 7 files changed, 14 insertions(+), 22 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index b333c880cb..c6333b45a8 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1029,7 +1029,7 @@ dependencies = [ "heck 0.5.0", "proc-macro2", "quote", - "syn 2.0.114", + "syn 2.0.117", ] [[package]] @@ -8004,7 +8004,7 @@ checksum = "f65c489a7071a749c849713807783f70672b28094011623e200cb86dcb835953" dependencies = [ "proc-macro2", "quote", - "syn 2.0.114", + "syn 2.0.117", ] [[package]] diff --git a/deltachat-rpc-client/src/deltachat_rpc_client/pytestplugin.py b/deltachat-rpc-client/src/deltachat_rpc_client/pytestplugin.py index 4daa2ec964..90ea326ea6 100644 --- a/deltachat-rpc-client/src/deltachat_rpc_client/pytestplugin.py +++ b/deltachat-rpc-client/src/deltachat_rpc_client/pytestplugin.py @@ -358,4 +358,3 @@ def remote_bob_loop(channel): except Exception: # some unserializable result channel.send(None) - diff --git a/deltachat-rpc-client/tests/test_something.py b/deltachat-rpc-client/tests/test_something.py index d21916bf2e..0597779967 100644 --- a/deltachat-rpc-client/tests/test_something.py +++ b/deltachat-rpc-client/tests/test_something.py @@ -1018,7 +1018,7 @@ def test_configured_imap_certificate_checks(acfactory): info = alice.get_info() domain = alice.get_config("addr").split("@")[-1] if domain.startswith("_"): - assert "cert_accept_invalid_certificates" in info.used_transport_settings + assert "cert_automatic" in info.used_transport_settings else: assert "cert_strict" in info.used_transport_settings diff --git a/src/configure.rs b/src/configure.rs index fe565784f4..aeea22e5c5 100644 --- a/src/configure.rs +++ b/src/configure.rs @@ -534,13 +534,7 @@ async fn get_configured_param( smtp_password, provider, certificate_checks: match param.certificate_checks { - EnteredCertificateChecks::Automatic => { - if param_domain.starts_with('_') { - ConfiguredCertificateChecks::AcceptInvalidCertificates - } else { - ConfiguredCertificateChecks::Automatic - } - } + EnteredCertificateChecks::Automatic => ConfiguredCertificateChecks::Automatic, EnteredCertificateChecks::Strict => ConfiguredCertificateChecks::Strict, EnteredCertificateChecks::AcceptInvalidCertificates | EnteredCertificateChecks::AcceptInvalidCertificates2 => { diff --git a/src/peer_channels.rs b/src/peer_channels.rs index 7dc9d24026..efb4445796 100644 --- a/src/peer_channels.rs +++ b/src/peer_channels.rs @@ -247,7 +247,7 @@ impl Context { { // Underscore-prefixed domains use self-signed TLS certificates, // so we need to skip relay certificate verification for them. - let skip = relay_url.host_str().map_or(false, |h| h.starts_with('_')); + let skip = relay_url.host_str().is_some_and(|h| h.starts_with('_')); (RelayMode::Custom(RelayUrl::from(relay_url).into()), skip) } else { // FIXME: this should be RelayMode::Disabled instead. diff --git a/src/qr.rs b/src/qr.rs index c35544c69f..bd53924847 100644 --- a/src/qr.rs +++ b/src/qr.rs @@ -805,11 +805,6 @@ pub(crate) async fn login_param_from_account_qr( .context("Invalid DCACCOUNT scheme")?; if !payload.starts_with(HTTPS_SCHEME) { - let certificate_checks = if payload.starts_with('_') { - EnteredCertificateChecks::AcceptInvalidCertificates - } else { - EnteredCertificateChecks::Strict - }; let rng = &mut rand::rngs::OsRng.unwrap_err(); let username = Alphanumeric.sample_string(rng, 9); let addr = username + "@" + payload; @@ -822,7 +817,7 @@ pub(crate) async fn login_param_from_account_qr( ..Default::default() }, smtp: Default::default(), - certificate_checks, + certificate_checks: EnteredCertificateChecks::Automatic, oauth2: false, }; return Ok(param); diff --git a/src/qr/qr_tests.rs b/src/qr/qr_tests.rs index 9c4369dd46..dc4921dbf7 100644 --- a/src/qr/qr_tests.rs +++ b/src/qr/qr_tests.rs @@ -750,18 +750,22 @@ async fn test_decode_account_underscore_domain() -> Result<()> { } ); - // Verify login params use AcceptInvalidCertificates for underscore domain. + // Verify login params use Automatic for underscore domain. + // The TLS layer handles underscore domains via NoCertificateVerification in Rustls. let param = login_param_from_account_qr(&ctx.ctx, "dcaccount:_example.org").await?; assert!(param.addr.ends_with("@_example.org")); assert_eq!( param.certificate_checks, - EnteredCertificateChecks::AcceptInvalidCertificates + EnteredCertificateChecks::Automatic ); - // Regular domain still uses Strict. + // Regular domain also uses Automatic. let param = login_param_from_account_qr(&ctx.ctx, "dcaccount:example.org").await?; assert!(param.addr.ends_with("@example.org")); - assert_eq!(param.certificate_checks, EnteredCertificateChecks::Strict); + assert_eq!( + param.certificate_checks, + EnteredCertificateChecks::Automatic + ); Ok(()) } From 77bfc8cfeec9846ab5f910db78eb9cc4ddf0a84c Mon Sep 17 00:00:00 2001 From: holger krekel Date: Mon, 2 Mar 2026 20:02:05 +0100 Subject: [PATCH 29/33] i guess this core-foundation thing can be prevented from duplication and the webpki-root-certs needs the license admission --- deny.toml | 3 +++ 1 file changed, 3 insertions(+) diff --git a/deny.toml b/deny.toml index 2ce6ac2734..36d5e55a7c 100644 --- a/deny.toml +++ b/deny.toml @@ -27,6 +27,8 @@ ignore = [ skip = [ { name = "async-channel", version = "1.9.0" }, { name = "bitflags", version = "1.3.2" }, + { name = "core-foundation", version = "0.9.4" }, + { name = "core-foundation", version = "0.10.1" }, { name = "derive_more-impl", version = "1.0.0" }, { name = "derive_more", version = "1.0.0" }, { name = "event-listener", version = "2.5.3" }, @@ -78,6 +80,7 @@ allow = [ "BSD-3-Clause", "BSL-1.0", # Boost Software License 1.0 "CC0-1.0", + "CDLA-Permissive-2.0", "ISC", "MIT", "MPL-2.0", From ff2482c01c9d9a0aa71b69f5c2ba8103af8a3263 Mon Sep 17 00:00:00 2001 From: holger krekel Date: Mon, 2 Mar 2026 20:05:26 +0100 Subject: [PATCH 30/33] revert ice server test --- deltachat-rpc-client/tests/test_calls.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/deltachat-rpc-client/tests/test_calls.py b/deltachat-rpc-client/tests/test_calls.py index c5f68c8c4b..6e61a63c09 100644 --- a/deltachat-rpc-client/tests/test_calls.py +++ b/deltachat-rpc-client/tests/test_calls.py @@ -77,7 +77,7 @@ def test_ice_servers(acfactory) -> None: alice = acfactory.get_online_account() ice_servers = alice.ice_servers() - assert len(ice_servers) >= 1 + assert len(ice_servers) == 1 def test_no_contact_request_call(acfactory) -> None: From a6dbd3801c63b4b2367ad2b2991215674d05b06b Mon Sep 17 00:00:00 2001 From: holger krekel Date: Mon, 2 Mar 2026 20:36:46 +0100 Subject: [PATCH 31/33] fix the check, it's now always automat3ic --- deltachat-rpc-client/tests/test_something.py | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/deltachat-rpc-client/tests/test_something.py b/deltachat-rpc-client/tests/test_something.py index 0597779967..15e8b3630a 100644 --- a/deltachat-rpc-client/tests/test_something.py +++ b/deltachat-rpc-client/tests/test_something.py @@ -1017,10 +1017,7 @@ def test_configured_imap_certificate_checks(acfactory): # Certificate checks should be configured (not None) info = alice.get_info() domain = alice.get_config("addr").split("@")[-1] - if domain.startswith("_"): - assert "cert_automatic" in info.used_transport_settings - else: - assert "cert_strict" in info.used_transport_settings + assert "cert_automatic" in info.used_transport_settings # "cert_old_automatic" is the value old Delta Chat core versions used # to mean user entered "imap_certificate_checks=0" (Automatic) From 6592aa8ac61623ae4aeb23a8cb7eca9798fc408e Mon Sep 17 00:00:00 2001 From: holger krekel Date: Wed, 4 Mar 2026 10:53:26 +0100 Subject: [PATCH 32/33] remove iroh underscore domain support for now. --- Cargo.lock | 617 +----------------- Cargo.toml | 2 +- .../tests/test_iroh_webxdc.py | 7 + deltachat-rpc-client/tests/test_something.py | 1 - deny.toml | 3 - src/peer_channels.rs | 10 +- 6 files changed, 22 insertions(+), 618 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index c6333b45a8..501060c682 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -62,19 +62,6 @@ dependencies = [ "aes", ] -[[package]] -name = "ahash" -version = "0.8.12" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5a15f179cd60c4584b8a8c596927aadc462e27f2ca70c04e0071964a73ba7a75" -dependencies = [ - "cfg-if", - "getrandom 0.3.3", - "once_cell", - "version_check", - "zerocopy 0.8.40", -] - [[package]] name = "aho-corasick" version = "1.1.3" @@ -129,71 +116,18 @@ version = "0.1.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "4b46cbb362ab8752921c97e041f5e366ee6297bd428a31275b9fcf1e380f7299" -[[package]] -name = "anstream" -version = "0.6.21" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "43d5b281e737544384e969a5ccad3f1cdd24b48086a0fc1b2a5262a26b8f4f4a" -dependencies = [ - "anstyle", - "anstyle-parse", - "anstyle-query", - "anstyle-wincon", - "colorchoice", - "is_terminal_polyfill", - "utf8parse", -] - [[package]] name = "anstyle" version = "1.0.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8901269c6307e8d93993578286ac0edf7f195079ffff5ebdeea6a59ffb7e36bc" -[[package]] -name = "anstyle-parse" -version = "0.2.7" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4e7644824f0aa2c7b9384579234ef10eb7efb6a0deb83f9630a49594dd9c15c2" -dependencies = [ - "utf8parse", -] - -[[package]] -name = "anstyle-query" -version = "1.1.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "40c48f72fd53cd289104fc64099abca73db4166ad86ea0b4341abe65af83dadc" -dependencies = [ - "windows-sys 0.61.1", -] - -[[package]] -name = "anstyle-wincon" -version = "3.0.11" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "291e6a250ff86cd4a820112fb8898808a366d8f9f58ce16d1f538353ad55747d" -dependencies = [ - "anstyle", - "once_cell_polyfill", - "windows-sys 0.61.1", -] - [[package]] name = "anyhow" version = "1.0.102" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7f202df86484c868dbad7eaa557ef785d5c66295e41b460ef922eca0723b842c" -[[package]] -name = "arc-swap" -version = "1.8.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f9f3647c145568cec02c42054e07bdf9a5a698e15b466fb2341bfc393cd24aa5" -dependencies = [ - "rustversion", -] - [[package]] name = "argon2" version = "0.5.3" @@ -457,58 +391,6 @@ version = "1.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ace50bade8e6234aa140d9a2f552bbee1db4d353f69b8217bc503490fc1a9f26" -[[package]] -name = "axum" -version = "0.8.8" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8b52af3cb4058c895d37317bb27508dccc8e5f2d39454016b297bf4a400597b8" -dependencies = [ - "axum-core", - "bytes", - "form_urlencoded", - "futures-util", - "http 1.1.0", - "http-body", - "http-body-util", - "hyper", - "hyper-util", - "itoa", - "matchit", - "memchr", - "mime", - "percent-encoding", - "pin-project-lite", - "serde_core", - "serde_json", - "serde_path_to_error", - "serde_urlencoded", - "sync_wrapper", - "tokio", - "tower", - "tower-layer", - "tower-service", - "tracing", -] - -[[package]] -name = "axum-core" -version = "0.5.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "08c78f31d7b1291f7ee735c1c6780ccde7785daae9a9206026862dab7d8792d1" -dependencies = [ - "bytes", - "futures-core", - "http 1.1.0", - "http-body", - "http-body-util", - "mime", - "pin-project-lite", - "sync_wrapper", - "tower-layer", - "tower-service", - "tracing", -] - [[package]] name = "backon" version = "1.5.0" @@ -887,12 +769,6 @@ dependencies = [ "shlex", ] -[[package]] -name = "cesu8" -version = "1.1.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6d43a04d8753f35258c91f8ec639f792891f748a1edbd759cf1dcea3382ad83c" - [[package]] name = "cfb-mode" version = "0.8.2" @@ -1005,7 +881,6 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0fbb260a053428790f3de475e304ff84cdbc4face759ea7a3e64c1edd938a7fc" dependencies = [ "clap_builder", - "clap_derive", ] [[package]] @@ -1014,22 +889,8 @@ version = "4.5.13" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "64b17d7ea74e9f833c7dbf2cbe4fb12ff26783eda4782a8975b72f895c9b4d99" dependencies = [ - "anstream", "anstyle", "clap_lex", - "strsim", -] - -[[package]] -name = "clap_derive" -version = "4.5.13" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "501d359d5f3dcaf6ecdeee48833ae73ec6e42723a1e52419c79abf9507eec0a0" -dependencies = [ - "heck 0.5.0", - "proc-macro2", - "quote", - "syn 2.0.117", ] [[package]] @@ -1070,12 +931,6 @@ version = "1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3d7b894f5411737b7867f4827955924d7c254fc9f4d91a6aad6b097804b1018b" -[[package]] -name = "colorchoice" -version = "1.0.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b05b61dc5112cbb17e4b6cd61790d9845d13888356391624cbe7e41efeac1e75" - [[package]] name = "colorutils-rs" version = "0.7.6" @@ -1087,16 +942,6 @@ dependencies = [ "num-traits", ] -[[package]] -name = "combine" -version = "4.6.7" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ba5a308b75df32fe02788e748662718f03fde005016435c444eea572398219fd" -dependencies = [ - "bytes", - "memchr", -] - [[package]] name = "concurrent-queue" version = "2.5.0" @@ -1144,16 +989,6 @@ dependencies = [ "libc", ] -[[package]] -name = "core-foundation" -version = "0.10.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b2a6cd9ae233e7f62ba4e9353e81a88df7fc8a5987b8d445b4d90c879bd156f6" -dependencies = [ - "core-foundation-sys", - "libc", -] - [[package]] name = "core-foundation-sys" version = "0.8.7" @@ -1448,20 +1283,6 @@ dependencies = [ "syn 2.0.117", ] -[[package]] -name = "dashmap" -version = "6.1.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5041cc499144891f3790297212f32a74fb938e5136a14943f338ef9e0ae276cf" -dependencies = [ - "cfg-if", - "crossbeam-utils", - "hashbrown 0.14.5", - "lock_api", - "once_cell", - "parking_lot_core", -] - [[package]] name = "data-encoding" version = "2.10.0" @@ -1558,7 +1379,7 @@ dependencies = [ "tokio-rustls", "tokio-stream", "tokio-util", - "toml 0.9.11+spec-1.1.0", + "toml", "tracing", "url", "uuid", @@ -2389,12 +2210,6 @@ version = "0.3.32" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "037711b3d59c33004d3856fbdc83b99d4ff37a24768fa1be9ce3538a1cde4393" -[[package]] -name = "futures-timer" -version = "3.0.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f288b0a4f20f9a56b5d1da57e2227c661b7b16168e2f72365f57b63326e29b24" - [[package]] name = "futures-util" version = "0.3.32" @@ -2507,29 +2322,6 @@ dependencies = [ "wasm-bindgen", ] -[[package]] -name = "governor" -version = "0.8.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "be93b4ec2e4710b04d9264c0c7350cdd62a8c20e5e4ac732552ebb8f0debe8eb" -dependencies = [ - "cfg-if", - "dashmap", - "futures-sink", - "futures-timer", - "futures-util", - "getrandom 0.3.3", - "no-std-compat", - "nonzero_ext", - "parking_lot", - "portable-atomic", - "quanta", - "rand 0.9.2", - "smallvec", - "spinning_top", - "web-time", -] - [[package]] name = "group" version = "0.13.0" @@ -2570,12 +2362,6 @@ dependencies = [ "crunchy", ] -[[package]] -name = "hashbrown" -version = "0.14.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e5274423e17b7c9fc20b6e7e208532f9b19825d82dfd615708b70edd83df41f1" - [[package]] name = "hashbrown" version = "0.15.4" @@ -2593,7 +2379,7 @@ version = "0.10.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7382cf6263419f2d8df38c55d7da83da5c18aef87fc7a7fc1fb1e344edfe14c1" dependencies = [ - "hashbrown 0.15.4", + "hashbrown", ] [[package]] @@ -2793,7 +2579,7 @@ dependencies = [ "serde", "serde_derive", "sysinfo 0.37.2", - "toml 0.9.11+spec-1.1.0", + "toml", "uuid", ] @@ -3109,7 +2895,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "4b0f83760fb341a774ed326568e19f5a863af4a952def8c39f9ab92fd95b88e5" dependencies = [ "equivalent", - "hashbrown 0.15.4", + "hashbrown", ] [[package]] @@ -3160,7 +2946,6 @@ dependencies = [ "aead", "anyhow", "atomic-waker", - "axum", "backon", "bytes", "cfg_aliases", @@ -3280,15 +3065,10 @@ version = "0.34.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f70466f14caff7420a14373676947e25e2917af6a5b1bec45825beb2bf1eb6a7" dependencies = [ - "http-body-util", - "hyper", - "hyper-util", "iroh-metrics-derive", "itoa", - "reqwest", "serde", "snafu", - "tokio", "tracing", ] @@ -3337,7 +3117,6 @@ dependencies = [ "rustc-hash", "rustls", "rustls-pki-types", - "rustls-platform-verifier", "slab", "thiserror 2.0.18", "tinyvec", @@ -3365,17 +3144,12 @@ version = "0.35.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c63f122cdfaa4b4e0e7d6d3921d2b878f42a0c6d3ee5a29456dc3f5ab5ec931f" dependencies = [ - "ahash", "anyhow", "bytes", "cfg_aliases", - "clap", - "dashmap", "data-encoding", "derive_more 1.0.0", "getrandom 0.3.3", - "governor", - "hickory-proto", "hickory-resolver", "http 1.1.0", "http-body-util", @@ -3392,42 +3166,25 @@ dependencies = [ "pkarr", "postcard", "rand 0.8.5", - "rcgen", - "regex", - "reloadable-state", "reqwest", "rustls", - "rustls-cert-file-reader", - "rustls-cert-reloadable-resolver", - "rustls-pemfile", "rustls-webpki", "serde", "sha1", - "simdutf8", "strum 0.26.2", "stun-rs", "thiserror 2.0.18", - "time", "tokio", "tokio-rustls", - "tokio-rustls-acme", "tokio-util", "tokio-websockets", - "toml 0.8.23", "tracing", - "tracing-subscriber", "url", "webpki-roots", "ws_stream_wasm", "z32", ] -[[package]] -name = "is_terminal_polyfill" -version = "1.70.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a6cb138bb79a146c1bd460005623e142ef0181e3d0219cb493e02f7d08a35695" - [[package]] name = "itertools" version = "0.13.0" @@ -3443,28 +3200,6 @@ version = "1.0.10" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b1a46d1a171d865aa5f83f92695765caa047a9b4cbae2cbf37dbd613a793fd4c" -[[package]] -name = "jni" -version = "0.21.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1a87aa2bb7d2af34197c04845522473242e1aa17c12f4935d5856491a7fb8c97" -dependencies = [ - "cesu8", - "cfg-if", - "combine", - "jni-sys", - "log", - "thiserror 1.0.69", - "walkdir", - "windows-sys 0.45.0", -] - -[[package]] -name = "jni-sys" -version = "0.3.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8eaf4bc02d17cbdd7ff4c7438cafcdf7fb9a4613313ad11b4f8fefe7d3fa0130" - [[package]] name = "js-sys" version = "0.3.77" @@ -3618,7 +3353,7 @@ version = "0.12.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "234cf4f4a04dc1f57e24b96cc0cd600cf2af460d4161ac5ecdd0af8e1f3b2a38" dependencies = [ - "hashbrown 0.15.4", + "hashbrown", ] [[package]] @@ -3659,12 +3394,6 @@ dependencies = [ "regex-automata", ] -[[package]] -name = "matchit" -version = "0.8.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "47e1ffaa40ddd1f3ed91f717a33c8c0ee23fff369e3aa8772b9605cc1d22f4c3" - [[package]] name = "md-5" version = "0.10.6" @@ -3788,7 +3517,7 @@ dependencies = [ "openssl-probe", "openssl-sys", "schannel", - "security-framework 2.11.1", + "security-framework", "security-framework-sys", "tempfile", ] @@ -3955,12 +3684,6 @@ dependencies = [ "libc", ] -[[package]] -name = "no-std-compat" -version = "0.4.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b93853da6d84c2e3c7d730d6473e8817692dd89be387eb01b94d7f108ecb5b8c" - [[package]] name = "no-std-net" version = "0.6.0" @@ -3986,12 +3709,6 @@ dependencies = [ "memchr", ] -[[package]] -name = "nonzero_ext" -version = "0.3.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "38bf9645c8b145698bb0b18a4637dcacbc421ea49bef2317e4fd8065a387cf21" - [[package]] name = "ntapi" version = "0.4.1" @@ -4189,12 +3906,6 @@ dependencies = [ "portable-atomic", ] -[[package]] -name = "once_cell_polyfill" -version = "1.70.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "384b8ab6d37215f3c5301a95a4accb5d64aa607f1fcb26a11b5303878451b4fe" - [[package]] name = "oorandom" version = "11.1.4" @@ -4778,7 +4489,7 @@ version = "0.2.20" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "77957b295656769bb8ad2b6a6b09d897d94f05c41b069aede1fcdaa675eaea04" dependencies = [ - "zerocopy 0.7.32", + "zerocopy", ] [[package]] @@ -4935,21 +4646,6 @@ version = "1.8.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "4339fc7a1021c9c1621d87f5e3505f2805c8c105420ba2f2a4df86814590c142" -[[package]] -name = "quanta" -version = "0.12.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f3ab5a9d756f0d97bdc89019bd2e4ea098cf9cde50ee7564dde6b81ccc8f06c7" -dependencies = [ - "crossbeam-utils", - "libc", - "once_cell", - "raw-cpuid", - "wasi 0.11.0+wasi-snapshot-preview1", - "web-sys", - "winapi", -] - [[package]] name = "quick-error" version = "2.0.1" @@ -5145,15 +4841,6 @@ dependencies = [ name = "ratelimit" version = "1.0.0" -[[package]] -name = "raw-cpuid" -version = "11.6.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "498cd0dc59d73224351ee52a95fee0f1a617a2eae0e7d9d720cc622c73a54186" -dependencies = [ - "bitflags 2.9.1", -] - [[package]] name = "rayon" version = "1.10.0" @@ -5251,23 +4938,6 @@ version = "0.8.8" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7a2d987857b319362043e95f5353c0535c1f58eec5336fdfcf626430af7def58" -[[package]] -name = "reloadable-core" -version = "0.1.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1dc20ac1418988b60072d783c9f68e28a173fb63493c127952f6face3b40c6e0" - -[[package]] -name = "reloadable-state" -version = "0.1.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3853ef78d45b50f8b989896304a85239539d39b7f866a000e8846b9b72d74ce8" -dependencies = [ - "arc-swap", - "reloadable-core", - "tokio", -] - [[package]] name = "replace_with" version = "0.1.8" @@ -5485,52 +5155,6 @@ dependencies = [ "zeroize", ] -[[package]] -name = "rustls-cert-file-reader" -version = "0.4.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8bb47c2a50fdfdaf95b0ac8b12620fc327da1fd4adbb30d0c56d866b005873ff" -dependencies = [ - "rustls-cert-read", - "rustls-pki-types", - "thiserror 2.0.18", - "tokio", -] - -[[package]] -name = "rustls-cert-read" -version = "0.3.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dd46e8c5ae4de3345c4786a83f99ec7aff287209b9e26fa883c473aeb28f19d5" -dependencies = [ - "rustls-pki-types", -] - -[[package]] -name = "rustls-cert-reloadable-resolver" -version = "0.7.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fe1baa8a3a1f05eaa9fc55aed4342867f70e5c170ea3bfed1b38c51a4857c0c8" -dependencies = [ - "futures-util", - "reloadable-state", - "rustls", - "rustls-cert-read", - "thiserror 2.0.18", -] - -[[package]] -name = "rustls-native-certs" -version = "0.8.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9980d917ebb0c0536119ba501e90834767bffc3d60641457fd84a1f3fd337923" -dependencies = [ - "openssl-probe", - "rustls-pki-types", - "schannel", - "security-framework 3.3.0", -] - [[package]] name = "rustls-pemfile" version = "2.2.0" @@ -5550,33 +5174,6 @@ dependencies = [ "zeroize", ] -[[package]] -name = "rustls-platform-verifier" -version = "0.5.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e012c45844a1790332c9386ed4ca3a06def221092eda277e6f079728f8ea99da" -dependencies = [ - "core-foundation 0.10.1", - "core-foundation-sys", - "jni", - "log", - "once_cell", - "rustls", - "rustls-native-certs", - "rustls-platform-verifier-android", - "rustls-webpki", - "security-framework 3.3.0", - "security-framework-sys", - "webpki-root-certs 0.26.11", - "windows-sys 0.52.0", -] - -[[package]] -name = "rustls-platform-verifier-android" -version = "0.1.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f87165f0995f63a9fbeea62b64d10b4d9d8e78ec6d7d51fb2125fda7bb36788f" - [[package]] name = "rustls-webpki" version = "0.102.8" @@ -5728,20 +5325,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "897b2245f0b511c87893af39b033e5ca9cce68824c4d7e7630b5a1d339658d02" dependencies = [ "bitflags 2.9.1", - "core-foundation 0.9.4", - "core-foundation-sys", - "libc", - "security-framework-sys", -] - -[[package]] -name = "security-framework" -version = "3.3.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "80fb1d92c5028aa318b4b8bd7302a5bfcf48be96a37fc6fc790f806b0004ee0c" -dependencies = [ - "bitflags 2.9.1", - "core-foundation 0.10.1", + "core-foundation", "core-foundation-sys", "libc", "security-framework-sys", @@ -5851,26 +5435,6 @@ dependencies = [ "zmij", ] -[[package]] -name = "serde_path_to_error" -version = "0.1.20" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "10a9ff822e371bb5403e391ecd83e182e0e77ba7f6fe0160b795797109d1b457" -dependencies = [ - "itoa", - "serde", - "serde_core", -] - -[[package]] -name = "serde_spanned" -version = "0.6.9" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bf41e0cfaf7226dca15e8197172c295a782857fcb97fad1808a166870dee75a3" -dependencies = [ - "serde", -] - [[package]] name = "serde_spanned" version = "1.0.4" @@ -6159,15 +5723,6 @@ dependencies = [ "lock_api", ] -[[package]] -name = "spinning_top" -version = "0.3.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d96d2d1d716fb500937168cc09353ffdc7a012be8475ac7308e1bdf0e3923300" -dependencies = [ - "lock_api", -] - [[package]] name = "spki" version = "0.7.3" @@ -6374,7 +5929,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3c879d448e9d986b661742763247d3693ed13609438cf3d006f51f5368a5ba6b" dependencies = [ "bitflags 2.9.1", - "core-foundation 0.9.4", + "core-foundation", "system-configuration-sys", ] @@ -6610,34 +6165,6 @@ dependencies = [ "tokio", ] -[[package]] -name = "tokio-rustls-acme" -version = "0.7.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f296d48ff72e0df96e2d7ef064ad5904d016a130869e542f00b08c8e05cc18cf" -dependencies = [ - "async-trait", - "base64", - "chrono", - "futures", - "log", - "num-bigint", - "pem", - "proc-macro2", - "rcgen", - "reqwest", - "ring", - "rustls", - "serde", - "serde_json", - "thiserror 2.0.18", - "time", - "tokio", - "tokio-rustls", - "webpki-roots", - "x509-parser", -] - [[package]] name = "tokio-stream" version = "0.1.18" @@ -6704,18 +6231,6 @@ dependencies = [ "tokio-util", ] -[[package]] -name = "toml" -version = "0.8.23" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dc1beb996b9d83529a9e75c17a1686767d148d70663143c7854d8b4a09ced362" -dependencies = [ - "serde", - "serde_spanned 0.6.9", - "toml_datetime 0.6.11", - "toml_edit 0.22.27", -] - [[package]] name = "toml" version = "0.9.11+spec-1.1.0" @@ -6724,7 +6239,7 @@ checksum = "f3afc9a848309fe1aaffaed6e1546a7a14de1f935dc9d89d32afd9a44bab7c46" dependencies = [ "indexmap", "serde_core", - "serde_spanned 1.0.4", + "serde_spanned", "toml_datetime 0.7.5+spec-1.1.0", "toml_parser", "toml_writer", @@ -6736,9 +6251,6 @@ name = "toml_datetime" version = "0.6.11" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "22cddaf88f4fbc13c51aebbf5f8eceb5c7c5a9da2ac40a13519eb5b0a0e8f11c" -dependencies = [ - "serde", -] [[package]] name = "toml_datetime" @@ -6767,8 +6279,6 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "41fe8c660ae4257887cf66394862d21dbca4a6ddd26f04a3560410406a2f819a" dependencies = [ "indexmap", - "serde", - "serde_spanned 0.6.9", "toml_datetime 0.6.11", "toml_write", "winnow 0.7.13", @@ -6808,7 +6318,6 @@ dependencies = [ "tokio", "tower-layer", "tower-service", - "tracing", ] [[package]] @@ -7221,24 +6730,6 @@ dependencies = [ "wasm-bindgen", ] -[[package]] -name = "webpki-root-certs" -version = "0.26.11" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "75c7f0ef91146ebfb530314f5f1d24528d7f0767efbfd31dce919275413e393e" -dependencies = [ - "webpki-root-certs 1.0.0", -] - -[[package]] -name = "webpki-root-certs" -version = "1.0.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "01a83f7e1a9f8712695c03eabe9ed3fbca0feff0152f33f12593e5a6303cb1a4" -dependencies = [ - "rustls-pki-types", -] - [[package]] name = "webpki-roots" version = "0.26.8" @@ -7465,15 +6956,6 @@ dependencies = [ "windows-targets 0.53.0", ] -[[package]] -name = "windows-sys" -version = "0.45.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "75283be5efb2831d37ea142365f009c02ec203cd29a3ebecbc093d52315b66d0" -dependencies = [ - "windows-targets 0.42.2", -] - [[package]] name = "windows-sys" version = "0.48.0" @@ -7510,21 +6992,6 @@ dependencies = [ "windows-link", ] -[[package]] -name = "windows-targets" -version = "0.42.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8e5180c00cd44c9b1c88adb3693291f1cd93605ded80c250a75d472756b4d071" -dependencies = [ - "windows_aarch64_gnullvm 0.42.2", - "windows_aarch64_msvc 0.42.2", - "windows_i686_gnu 0.42.2", - "windows_i686_msvc 0.42.2", - "windows_x86_64_gnu 0.42.2", - "windows_x86_64_gnullvm 0.42.2", - "windows_x86_64_msvc 0.42.2", -] - [[package]] name = "windows-targets" version = "0.48.5" @@ -7572,12 +7039,6 @@ dependencies = [ "windows_x86_64_msvc 0.53.0", ] -[[package]] -name = "windows_aarch64_gnullvm" -version = "0.42.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "597a5118570b68bc08d8d59125332c54f1ba9d9adeedeef5b99b02ba2b0698f8" - [[package]] name = "windows_aarch64_gnullvm" version = "0.48.5" @@ -7596,12 +7057,6 @@ version = "0.53.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "86b8d5f90ddd19cb4a147a5fa63ca848db3df085e25fee3cc10b39b6eebae764" -[[package]] -name = "windows_aarch64_msvc" -version = "0.42.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e08e8864a60f06ef0d0ff4ba04124db8b0fb3be5776a5cd47641e942e58c4d43" - [[package]] name = "windows_aarch64_msvc" version = "0.48.5" @@ -7620,12 +7075,6 @@ version = "0.53.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c7651a1f62a11b8cbd5e0d42526e55f2c99886c77e007179efff86c2b137e66c" -[[package]] -name = "windows_i686_gnu" -version = "0.42.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c61d927d8da41da96a81f029489353e68739737d3beca43145c8afec9a31a84f" - [[package]] name = "windows_i686_gnu" version = "0.48.5" @@ -7656,12 +7105,6 @@ version = "0.53.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9ce6ccbdedbf6d6354471319e781c0dfef054c81fbc7cf83f338a4296c0cae11" -[[package]] -name = "windows_i686_msvc" -version = "0.42.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "44d840b6ec649f480a41c8d80f9c65108b92d89345dd94027bfe06ac444d1060" - [[package]] name = "windows_i686_msvc" version = "0.48.5" @@ -7680,12 +7123,6 @@ version = "0.53.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "581fee95406bb13382d2f65cd4a908ca7b1e4c2f1917f143ba16efe98a589b5d" -[[package]] -name = "windows_x86_64_gnu" -version = "0.42.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8de912b8b8feb55c064867cf047dda097f92d51efad5b491dfb98f6bbb70cb36" - [[package]] name = "windows_x86_64_gnu" version = "0.48.5" @@ -7704,12 +7141,6 @@ version = "0.53.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "2e55b5ac9ea33f2fc1716d1742db15574fd6fc8dadc51caab1c16a3d3b4190ba" -[[package]] -name = "windows_x86_64_gnullvm" -version = "0.42.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "26d41b46a36d453748aedef1486d5c7a85db22e56aff34643984ea85514e94a3" - [[package]] name = "windows_x86_64_gnullvm" version = "0.48.5" @@ -7728,12 +7159,6 @@ version = "0.53.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0a6e035dd0599267ce1ee132e51c27dd29437f63325753051e71dd9e42406c57" -[[package]] -name = "windows_x86_64_msvc" -version = "0.42.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9aec5da331524158c6d1a4ac0ab1541149c0b9505fde06423b02f5ef0106b9f0" - [[package]] name = "windows_x86_64_msvc" version = "0.48.5" @@ -7973,16 +7398,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "74d4d3961e53fa4c9a25a8637fc2bfaf2595b3d3ae34875568a5cf64787716be" dependencies = [ "byteorder", - "zerocopy-derive 0.7.32", -] - -[[package]] -name = "zerocopy" -version = "0.8.40" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a789c6e490b576db9f7e6b6d661bcc9799f7c0ac8352f56ea20193b2681532e5" -dependencies = [ - "zerocopy-derive 0.8.40", + "zerocopy-derive", ] [[package]] @@ -7996,17 +7412,6 @@ dependencies = [ "syn 2.0.117", ] -[[package]] -name = "zerocopy-derive" -version = "0.8.40" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f65c489a7071a749c849713807783f70672b28094011623e200cb86dcb835953" -dependencies = [ - "proc-macro2", - "quote", - "syn 2.0.117", -] - [[package]] name = "zerofrom" version = "0.1.5" diff --git a/Cargo.toml b/Cargo.toml index 6efa85e1b9..0a972dcf5e 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -67,7 +67,7 @@ hyper = "1" hyper-util = "0.1.16" image = { version = "0.25.6", default-features=false, features = ["gif", "jpeg", "ico", "png", "pnm", "webp", "bmp"] } iroh-gossip = { version = "0.35", default-features = false, features = ["net"] } -iroh = { version = "0.35", default-features = false, features = ["test-utils", "metrics"] } +iroh = { version = "0.35", default-features = false } kamadak-exif = "0.6.1" libc = { workspace = true } mail-builder = { version = "0.4.4", default-features = false } diff --git a/deltachat-rpc-client/tests/test_iroh_webxdc.py b/deltachat-rpc-client/tests/test_iroh_webxdc.py index 53217c922c..093db49d63 100644 --- a/deltachat-rpc-client/tests/test_iroh_webxdc.py +++ b/deltachat-rpc-client/tests/test_iroh_webxdc.py @@ -17,6 +17,13 @@ from deltachat_rpc_client import EventType +@pytest.fixture(autouse=True) +def _xfail_underscore_domain(): + domain = os.environ.get("CHATMAIL_DOMAIN", "") + if domain.startswith("_"): + pytest.xfail("Iroh tests are expected to fail on underscore domains (self-signed TLS certificates)") + + @pytest.fixture def path_to_webxdc(request): p = request.path.parent.parent.parent.joinpath("test-data/webxdc/chess.xdc") diff --git a/deltachat-rpc-client/tests/test_something.py b/deltachat-rpc-client/tests/test_something.py index 15e8b3630a..4e2984d1c3 100644 --- a/deltachat-rpc-client/tests/test_something.py +++ b/deltachat-rpc-client/tests/test_something.py @@ -1016,7 +1016,6 @@ def test_configured_imap_certificate_checks(acfactory): # Certificate checks should be configured (not None) info = alice.get_info() - domain = alice.get_config("addr").split("@")[-1] assert "cert_automatic" in info.used_transport_settings # "cert_old_automatic" is the value old Delta Chat core versions used diff --git a/deny.toml b/deny.toml index 36d5e55a7c..2ce6ac2734 100644 --- a/deny.toml +++ b/deny.toml @@ -27,8 +27,6 @@ ignore = [ skip = [ { name = "async-channel", version = "1.9.0" }, { name = "bitflags", version = "1.3.2" }, - { name = "core-foundation", version = "0.9.4" }, - { name = "core-foundation", version = "0.10.1" }, { name = "derive_more-impl", version = "1.0.0" }, { name = "derive_more", version = "1.0.0" }, { name = "event-listener", version = "2.5.3" }, @@ -80,7 +78,6 @@ allow = [ "BSD-3-Clause", "BSL-1.0", # Boost Software License 1.0 "CC0-1.0", - "CDLA-Permissive-2.0", "ISC", "MIT", "MPL-2.0", diff --git a/src/peer_channels.rs b/src/peer_channels.rs index efb4445796..618bf03d06 100644 --- a/src/peer_channels.rs +++ b/src/peer_channels.rs @@ -238,21 +238,18 @@ impl Context { let secret_key = SecretKey::generate(rand_old::rngs::OsRng); let public_key = secret_key.public(); - let (relay_mode, skip_relay_tls) = if let Some(relay_url) = self + let relay_mode = if let Some(relay_url) = self .metadata .read() .await .as_ref() .and_then(|conf| conf.iroh_relay.clone()) { - // Underscore-prefixed domains use self-signed TLS certificates, - // so we need to skip relay certificate verification for them. - let skip = relay_url.host_str().is_some_and(|h| h.starts_with('_')); - (RelayMode::Custom(RelayUrl::from(relay_url).into()), skip) + RelayMode::Custom(RelayUrl::from(relay_url).into()) } else { // FIXME: this should be RelayMode::Disabled instead. // Currently using default relays because otherwise Rust tests fail. - (RelayMode::Default, false) + RelayMode::Default }; let endpoint = Endpoint::builder() @@ -260,7 +257,6 @@ impl Context { .secret_key(secret_key) .alpns(vec![GOSSIP_ALPN.to_vec()]) .relay_mode(relay_mode) - .insecure_skip_relay_cert_verify(skip_relay_tls) .bind() .await?; From 1f9674b46390765677383e28f05aac844e2aab80 Mon Sep 17 00:00:00 2001 From: holger krekel Date: Sat, 7 Mar 2026 10:28:47 +0100 Subject: [PATCH 33/33] try to make cargo happy --- Cargo.lock | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 501060c682..1c54e8bcd9 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1367,8 +1367,8 @@ dependencies = [ "sha2", "shadowsocks", "smallvec", - "strum 0.27.2", - "strum_macros 0.27.2", + "strum 0.28.0", + "strum_macros 0.28.0", "tagger", "tempfile", "testdir", @@ -5768,9 +5768,9 @@ dependencies = [ [[package]] name = "strum" -version = "0.27.2" +version = "0.28.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "af23d6f6c1a224baef9d3f61e287d2761385a5b88fdab4eb4c6f11aeb54c4bcf" +checksum = "9628de9b8791db39ceda2b119bbe13134770b56c138ec1d3af810d045c04f9bd" [[package]] name = "strum_macros" @@ -5787,9 +5787,9 @@ dependencies = [ [[package]] name = "strum_macros" -version = "0.27.2" +version = "0.28.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7695ce3845ea4b33927c055a39dc438a45b059f7c1b3d91d38d10355fb8cbca7" +checksum = "ab85eea0270ee17587ed4156089e10b9e6880ee688791d45a905f5b1ca36f664" dependencies = [ "heck 0.5.0", "proc-macro2",