mirror of
https://github.com/chatmail/core.git
synced 2026-06-30 19:46:35 +03:00
Compare commits
17 Commits
d2weber/bu
...
main
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
6e633eebe1 | ||
|
|
a14e617059 | ||
|
|
b4471f2434 | ||
|
|
abaa48d0a1 | ||
|
|
1b2ec2e88c | ||
|
|
f6e819d2a5 | ||
|
|
abb84efc37 | ||
|
|
09d5d0bddf | ||
|
|
b38277c294 | ||
|
|
975fd8aee3 | ||
|
|
5a54e18fee | ||
|
|
8b80ac146f | ||
|
|
d1f4e59d82 | ||
|
|
0a82d73eb5 | ||
|
|
c026910fa7 | ||
|
|
2aba6aa339 | ||
|
|
93a6f35ef7 |
2
.github/workflows/ci.yml
vendored
2
.github/workflows/ci.yml
vendored
@@ -146,7 +146,7 @@ jobs:
|
||||
cache-bin: false
|
||||
|
||||
- name: Install nextest
|
||||
uses: taiki-e/install-action@0631aa6515c7d545823c67cfae7ef4fc7f490154
|
||||
uses: taiki-e/install-action@15449e3094499af05d8d964a1c884208e4b8b595
|
||||
with:
|
||||
tool: nextest
|
||||
|
||||
|
||||
@@ -33,7 +33,7 @@ jobs:
|
||||
run: npm install -g npm@latest
|
||||
|
||||
- name: Install dependencies without running scripts
|
||||
working-directory: deltachat-jsonrpc-bindings/typescript
|
||||
working-directory: deltachat-jsonrpc/typescript
|
||||
run: npm install --ignore-scripts
|
||||
|
||||
- name: Package
|
||||
@@ -43,5 +43,5 @@ jobs:
|
||||
npm pack .
|
||||
|
||||
- name: Publish
|
||||
working-directory: deltachat-jsonrpc-bindings/typescript
|
||||
working-directory: deltachat-jsonrpc/typescript
|
||||
run: npm publish --provenance deltachat-jsonrpc-client-* --access public
|
||||
|
||||
8
.github/workflows/jsonrpc.yml
vendored
8
.github/workflows/jsonrpc.yml
vendored
@@ -30,16 +30,16 @@ jobs:
|
||||
save-if: ${{ github.ref == 'refs/heads/main' }}
|
||||
cache-bin: false
|
||||
- name: npm install
|
||||
working-directory: deltachat-jsonrpc-bindings/typescript
|
||||
working-directory: deltachat-jsonrpc/typescript
|
||||
run: npm install
|
||||
- name: Build TypeScript, run Rust tests, generate bindings
|
||||
working-directory: deltachat-jsonrpc-bindings/typescript
|
||||
working-directory: deltachat-jsonrpc/typescript
|
||||
run: npm run build
|
||||
- name: Run integration tests
|
||||
working-directory: deltachat-jsonrpc-bindings/typescript
|
||||
working-directory: deltachat-jsonrpc/typescript
|
||||
run: npm run test
|
||||
env:
|
||||
CHATMAIL_DOMAIN: ${{ vars.CHATMAIL_DOMAIN }}
|
||||
- name: Run linter
|
||||
working-directory: deltachat-jsonrpc-bindings/typescript
|
||||
working-directory: deltachat-jsonrpc/typescript
|
||||
run: npm run prettier:check
|
||||
|
||||
4
.github/workflows/upload-docs.yml
vendored
4
.github/workflows/upload-docs.yml
vendored
@@ -81,7 +81,7 @@ jobs:
|
||||
|
||||
defaults:
|
||||
run:
|
||||
working-directory: ./deltachat-jsonrpc-bindings/typescript
|
||||
working-directory: ./deltachat-jsonrpc/typescript
|
||||
|
||||
steps:
|
||||
- uses: actions/checkout@v6
|
||||
@@ -104,7 +104,7 @@ jobs:
|
||||
mkdir -p "$HOME/.ssh"
|
||||
echo "${{ secrets.JS_JSONRPC_DOCS_SSH_KEY }}" > "$HOME/.ssh/key"
|
||||
chmod 600 "$HOME/.ssh/key"
|
||||
rsync -avzh --delete -e "ssh -i $HOME/.ssh/key -o StrictHostKeyChecking=no" $GITHUB_WORKSPACE/deltachat-jsonrpc-bindings/typescript/docs/ "${{ secrets.JS_JSONRPC_DOCS_SSH_USER }}@js.jsonrpc.delta.chat:/var/www/html/js.jsonrpc.delta.chat/"
|
||||
rsync -avzh --delete -e "ssh -i $HOME/.ssh/key -o StrictHostKeyChecking=no" $GITHUB_WORKSPACE/deltachat-jsonrpc/typescript/docs/ "${{ secrets.JS_JSONRPC_DOCS_SSH_USER }}@js.jsonrpc.delta.chat:/var/www/html/js.jsonrpc.delta.chat/"
|
||||
|
||||
build-cffi:
|
||||
runs-on: ubuntu-latest
|
||||
|
||||
90
Cargo.lock
generated
90
Cargo.lock
generated
@@ -124,9 +124,9 @@ checksum = "8901269c6307e8d93993578286ac0edf7f195079ffff5ebdeea6a59ffb7e36bc"
|
||||
|
||||
[[package]]
|
||||
name = "anyhow"
|
||||
version = "1.0.102"
|
||||
version = "1.0.103"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "7f202df86484c868dbad7eaa557ef785d5c66295e41b460ef922eca0723b842c"
|
||||
checksum = "2a4385e2e34eb35d6b3efe798b9eb88096925d87726c0798709bf56d9ed84af3"
|
||||
|
||||
[[package]]
|
||||
name = "argon2"
|
||||
@@ -194,15 +194,15 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "astral-tokio-tar"
|
||||
version = "0.6.2"
|
||||
version = "0.6.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "cb50a7aae84a03bf55b067832bc376f4961b790c97e64d3eacee97d389b90277"
|
||||
checksum = "08648fef353ab39a9d26f909ad53fc4f071be4c91853b78523f5cc3d9e5ebffd"
|
||||
dependencies = [
|
||||
"filetime",
|
||||
"futures-core",
|
||||
"libc",
|
||||
"portable-atomic",
|
||||
"rustc-hash",
|
||||
"rustix 0.38.44",
|
||||
"tokio",
|
||||
"tokio-stream",
|
||||
]
|
||||
@@ -1478,13 +1478,6 @@ dependencies = [
|
||||
"yerpc",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "deltachat-jsonrpc-bindings"
|
||||
version = "2.54.0-dev"
|
||||
dependencies = [
|
||||
"deltachat-jsonrpc",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "deltachat-repl"
|
||||
version = "2.54.0-dev"
|
||||
@@ -1717,7 +1710,7 @@ dependencies = [
|
||||
"libc",
|
||||
"option-ext",
|
||||
"redox_users",
|
||||
"windows-sys 0.59.0",
|
||||
"windows-sys 0.61.1",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -2086,18 +2079,6 @@ version = "0.2.9"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "28dea519a9695b9977216879a3ebfddf92f1c08c05d984f8996aecd6ecdc811d"
|
||||
|
||||
[[package]]
|
||||
name = "filetime"
|
||||
version = "0.2.25"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "35c0522e981e68cbfa8c3f978441a5f34b30b96e146b33cd3359176b50fe8586"
|
||||
dependencies = [
|
||||
"cfg-if",
|
||||
"libc",
|
||||
"libredox",
|
||||
"windows-sys 0.59.0",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "find-msvc-tools"
|
||||
version = "0.1.9"
|
||||
@@ -2740,7 +2721,7 @@ dependencies = [
|
||||
"hyper",
|
||||
"libc",
|
||||
"pin-project-lite",
|
||||
"socket2 0.5.9",
|
||||
"socket2 0.6.3",
|
||||
"tokio",
|
||||
"tower-service",
|
||||
"tracing",
|
||||
@@ -3161,7 +3142,7 @@ dependencies = [
|
||||
"iroh-metrics-derive",
|
||||
"itoa",
|
||||
"serde",
|
||||
"snafu",
|
||||
"snafu 0.8.5",
|
||||
"tracing",
|
||||
]
|
||||
|
||||
@@ -3384,7 +3365,6 @@ checksum = "c0ff37bd590ca25063e35af745c343cb7a0271906fb7b37e4813e8f79f00268d"
|
||||
dependencies = [
|
||||
"bitflags 2.11.0",
|
||||
"libc",
|
||||
"redox_syscall 0.5.12",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -3531,9 +3511,9 @@ checksum = "490cc448043f947bae3cbee9c203358d62dbee0db12107a74be5c30ccfd09771"
|
||||
|
||||
[[package]]
|
||||
name = "memchr"
|
||||
version = "2.7.4"
|
||||
version = "2.8.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "78ca9ab1a0babb1e7d5695e3530886289c18cf2f87ec19a575a0abdce112e3a3"
|
||||
checksum = "88904434abc2901f197fe8cc55f0445e7ded921dba5911dad2e2b39b48e663c4"
|
||||
|
||||
[[package]]
|
||||
name = "mime"
|
||||
@@ -3799,7 +3779,7 @@ dependencies = [
|
||||
"netlink-proto",
|
||||
"netlink-sys",
|
||||
"serde",
|
||||
"snafu",
|
||||
"snafu 0.8.5",
|
||||
"socket2 0.5.9",
|
||||
"time",
|
||||
"tokio",
|
||||
@@ -3887,7 +3867,7 @@ version = "0.50.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "7957b9740744892f114936ab4a57b3f487491bbeafaf8083688b16841a4240e5"
|
||||
dependencies = [
|
||||
"windows-sys 0.59.0",
|
||||
"windows-sys 0.61.1",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -4291,9 +4271,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "pgp"
|
||||
version = "0.19.0"
|
||||
version = "0.20.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "eaffe1ec22db286599c30ae6be75b37493b558735d86c8e59ec5c38794415fe4"
|
||||
checksum = "1cfa4743b28656065ff4c0ba09e46b357a65e8c00fc2341e89084b82f87cbdf1"
|
||||
dependencies = [
|
||||
"aead",
|
||||
"aes",
|
||||
@@ -4332,6 +4312,7 @@ dependencies = [
|
||||
"k256",
|
||||
"log",
|
||||
"md-5",
|
||||
"memchr",
|
||||
"ml-dsa",
|
||||
"ml-kem",
|
||||
"nom 8.0.0",
|
||||
@@ -4343,7 +4324,6 @@ dependencies = [
|
||||
"p384",
|
||||
"p521",
|
||||
"rand 0.8.6",
|
||||
"regex",
|
||||
"replace_with",
|
||||
"ripemd",
|
||||
"rsa",
|
||||
@@ -4354,7 +4334,8 @@ dependencies = [
|
||||
"signature",
|
||||
"slh-dsa",
|
||||
"smallvec",
|
||||
"snafu",
|
||||
"snafu 0.9.1",
|
||||
"subtle",
|
||||
"twofish",
|
||||
"x25519-dalek",
|
||||
"zeroize",
|
||||
@@ -4593,7 +4574,7 @@ dependencies = [
|
||||
"rand 0.8.6",
|
||||
"serde",
|
||||
"smallvec",
|
||||
"snafu",
|
||||
"snafu 0.8.5",
|
||||
"socket2 0.5.9",
|
||||
"time",
|
||||
"tokio",
|
||||
@@ -5288,7 +5269,7 @@ dependencies = [
|
||||
"errno",
|
||||
"libc",
|
||||
"linux-raw-sys 0.12.1",
|
||||
"windows-sys 0.52.0",
|
||||
"windows-sys 0.61.1",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -5856,7 +5837,16 @@ version = "0.8.5"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "223891c85e2a29c3fe8fb900c1fae5e69c2e42415e3177752e8718475efa5019"
|
||||
dependencies = [
|
||||
"snafu-derive",
|
||||
"snafu-derive 0.8.5",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "snafu"
|
||||
version = "0.9.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "d1a012328be2e3f5d5f6f3218147ca02588cea4cb865e876849ab6debcf36522"
|
||||
dependencies = [
|
||||
"snafu-derive 0.9.1",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -5871,6 +5861,18 @@ dependencies = [
|
||||
"syn 2.0.117",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "snafu-derive"
|
||||
version = "0.9.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "5f103c50866b8743da9429b8a581d81a27c2d3a9c4ac7df8f8571c1dd7896eda"
|
||||
dependencies = [
|
||||
"heck 0.5.0",
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn 2.0.117",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "socket2"
|
||||
version = "0.5.9"
|
||||
@@ -6154,7 +6156,7 @@ dependencies = [
|
||||
"getrandom 0.3.3",
|
||||
"once_cell",
|
||||
"rustix 1.1.4",
|
||||
"windows-sys 0.52.0",
|
||||
"windows-sys 0.61.1",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -7514,7 +7516,8 @@ dependencies = [
|
||||
[[package]]
|
||||
name = "yerpc"
|
||||
version = "0.6.4"
|
||||
source = "git+https://github.com/chatmail/yerpc.git?branch=gen_fns#f92899f570ba07c0e6ff548d8162e2118f938d43"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "1dc24983fbe850227bfc1de89bf8cbfb3e2463afc322e0de2f155c4c23d06445"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"async-channel 1.9.0",
|
||||
@@ -7532,8 +7535,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "yerpc_derive"
|
||||
version = "0.6.4"
|
||||
source = "git+https://github.com/chatmail/yerpc.git?branch=gen_fns#f92899f570ba07c0e6ff548d8162e2118f938d43"
|
||||
version = "0.6.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "4d8560d021437420316370db865e44c000bf86380b47cf05e49be9d652042bf5"
|
||||
dependencies = [
|
||||
"convert_case",
|
||||
"darling",
|
||||
|
||||
@@ -78,7 +78,7 @@ num-derive = "0.4"
|
||||
num-traits = { workspace = true }
|
||||
parking_lot = "0.12.4"
|
||||
percent-encoding = "2.3"
|
||||
pgp = { version = "0.19.0", features = ["draft-pqc"], default-features = false }
|
||||
pgp = { version = "0.20.0", features = ["draft-pqc"], default-features = false }
|
||||
pin-project = "1"
|
||||
qrcodegen = "1.7.0"
|
||||
quick-xml = { version = "0.39", features = ["escape-html"] }
|
||||
@@ -103,7 +103,7 @@ thiserror = { workspace = true }
|
||||
tokio-io-timeout = "1.2.1"
|
||||
tokio-rustls = { version = "0.26.2", default-features = false, features = ["aws-lc-rs", "tls12"] }
|
||||
tokio-stream = { version = "0.1.17", features = ["fs"] }
|
||||
astral-tokio-tar = { version = "0.6.2", default-features = false }
|
||||
astral-tokio-tar = { version = "0.6.3", default-features = false }
|
||||
tokio-util = { workspace = true }
|
||||
tokio = { workspace = true, features = ["fs", "rt-multi-thread", "macros"] }
|
||||
toml = "0.9"
|
||||
@@ -130,7 +130,6 @@ members = [
|
||||
"deltachat-ffi",
|
||||
"deltachat_derive",
|
||||
"deltachat-jsonrpc",
|
||||
"deltachat-jsonrpc-bindings",
|
||||
"deltachat-rpc-server",
|
||||
"deltachat-ratelimit",
|
||||
"deltachat-repl",
|
||||
@@ -185,7 +184,6 @@ base64 = "0.22"
|
||||
chrono = { version = "0.4.44", default-features = false }
|
||||
deltachat-contact-tools = { path = "deltachat-contact-tools" }
|
||||
deltachat-jsonrpc = { path = "deltachat-jsonrpc", default-features = false }
|
||||
deltachat-jsonrpc-bindings = { path = "deltachat-jsonrpc-bindings", default-features = false }
|
||||
deltachat = { path = ".", default-features = false }
|
||||
futures = "0.3.32"
|
||||
futures-lite = "2.6.1"
|
||||
@@ -205,7 +203,7 @@ thiserror = "2"
|
||||
tokio = "1"
|
||||
tokio-util = "0.7.18"
|
||||
tracing-subscriber = "0.3"
|
||||
yerpc = { git="https://github.com/chatmail/yerpc.git", branch="gen_fns" }
|
||||
yerpc = "0.6.4"
|
||||
|
||||
[features]
|
||||
default = ["vendored"]
|
||||
|
||||
@@ -39,7 +39,7 @@ mod vcard;
|
||||
pub use vcard::{make_vcard, parse_vcard, VcardContact};
|
||||
|
||||
/// Valid contact address.
|
||||
#[derive(Debug, Clone)]
|
||||
#[derive(Debug, Clone, PartialEq, Eq)]
|
||||
pub struct ContactAddress(String);
|
||||
|
||||
impl Deref for ContactAddress {
|
||||
|
||||
@@ -462,7 +462,10 @@ char* dc_get_blobdir (const dc_context_t* context);
|
||||
* - `gossip_period` = How often to gossip Autocrypt keys in chats with multiple recipients, in
|
||||
* seconds. 2 days by default.
|
||||
* This is not supposed to be changed by UIs and only used for testing.
|
||||
* - `is_chatmail` = 1 if the the server is a chatmail server, 0 otherwise.
|
||||
* - `is_chatmail` = (deprecated) 1 if the the server is a chatmail server, 0 otherwise.
|
||||
* This is deprecated, UIs should not behave differently
|
||||
* for chatmail relays and classical email servers.
|
||||
* Most usages in UIs can be replaced by `force_encryption`.
|
||||
* - `is_muted` = Whether a context is muted by the user.
|
||||
* Muted contexts should not sound, vibrate or show notifications.
|
||||
* In contrast to `dc_set_chat_mute_duration()`,
|
||||
|
||||
@@ -1,16 +0,0 @@
|
||||
[package]
|
||||
name = "deltachat-jsonrpc-bindings"
|
||||
version = "2.54.0-dev"
|
||||
description = "Autogenerate DeltaChat JSON-RPC API bindings at build time"
|
||||
edition = "2024"
|
||||
license = "MPL-2.0"
|
||||
repository = "https://github.com/chatmail/core"
|
||||
|
||||
[build-dependencies]
|
||||
deltachat-jsonrpc = { workspace = true }
|
||||
|
||||
[dependencies]
|
||||
|
||||
[features]
|
||||
default = ["vendored"]
|
||||
vendored = ["deltachat-jsonrpc/vendored"]
|
||||
@@ -1,6 +0,0 @@
|
||||
use deltachat_jsonrpc::api::write_ts_bindings;
|
||||
use std::path::Path;
|
||||
|
||||
fn main() {
|
||||
write_ts_bindings(Path::new("typescript/generated"));
|
||||
}
|
||||
@@ -1 +0,0 @@
|
||||
|
||||
@@ -157,7 +157,7 @@ impl CommandApi {
|
||||
}
|
||||
}
|
||||
|
||||
#[rpc(all_positional)]
|
||||
#[rpc(all_positional, ts_outdir = "typescript/generated")]
|
||||
impl CommandApi {
|
||||
/// Test function.
|
||||
async fn sleep(&self, delay: f64) {
|
||||
|
||||
@@ -236,6 +236,7 @@ impl From<Qr> for QrObject {
|
||||
invitenumber,
|
||||
authcode,
|
||||
is_v3,
|
||||
..
|
||||
} => {
|
||||
let contact_id = contact_id.to_u32();
|
||||
let fingerprint = fingerprint.human_readable();
|
||||
@@ -255,6 +256,7 @@ impl From<Qr> for QrObject {
|
||||
invitenumber,
|
||||
authcode,
|
||||
is_v3,
|
||||
..
|
||||
} => {
|
||||
let contact_id = contact_id.to_u32();
|
||||
let fingerprint = fingerprint.human_readable();
|
||||
@@ -276,6 +278,7 @@ impl From<Qr> for QrObject {
|
||||
authcode,
|
||||
invitenumber,
|
||||
is_v3,
|
||||
..
|
||||
} => {
|
||||
let contact_id = contact_id.to_u32();
|
||||
let fingerprint = fingerprint.human_readable();
|
||||
|
||||
@@ -704,3 +704,25 @@ def test_withdraw_securejoin_qr(acfactory):
|
||||
and "Ignoring RequestWithAuth message because of invalid auth code." in event.msg
|
||||
):
|
||||
break
|
||||
|
||||
|
||||
def test_qr_scan_updates_new_relay_address(acfactory):
|
||||
alice, bob = acfactory.get_online_accounts(2)
|
||||
|
||||
bob_alice_chat = bob.secure_join(alice.get_qr_code())
|
||||
alice.wait_for_securejoin_inviter_success()
|
||||
bob.wait_for_securejoin_joiner_success()
|
||||
|
||||
for ac in [alice, bob]:
|
||||
old_addr = ac.get_config("configured_addr")
|
||||
ac.add_transport_from_qr(acfactory.get_account_qr())
|
||||
ac.set_config("configured_addr", ac.list_transports()[1]["addr"])
|
||||
ac.delete_transport(old_addr)
|
||||
|
||||
bob.secure_join(alice.get_qr_code())
|
||||
alice.wait_for_securejoin_inviter_success()
|
||||
bob.wait_for_securejoin_joiner_success()
|
||||
|
||||
bob_alice_chat.send_text("hi")
|
||||
snapshot = alice.wait_for_incoming_msg().get_snapshot()
|
||||
assert snapshot.text == "hi"
|
||||
|
||||
@@ -221,6 +221,30 @@ def test_account(acfactory) -> None:
|
||||
alice.stop_io()
|
||||
|
||||
|
||||
def test_mark_fresh_vs_self_mdn(acfactory) -> None:
|
||||
alice, bob = acfactory.get_online_accounts(2)
|
||||
bob.set_config("bcc_self", "1")
|
||||
|
||||
alice_contact_bob = alice.create_contact(bob)
|
||||
alice_chat = alice_contact_bob.create_chat()
|
||||
alice_chat.send_text("Hello!")
|
||||
|
||||
event = bob.wait_for_incoming_msg_event()
|
||||
chat_id = event.chat_id
|
||||
msg_id = event.msg_id
|
||||
|
||||
bob_chat = bob.get_chat_by_id(chat_id)
|
||||
message = bob.get_message_by_id(msg_id)
|
||||
bob_chat.accept()
|
||||
bob.mark_seen_messages([message])
|
||||
bob_chat.mark_fresh()
|
||||
assert bob_chat.get_fresh_message_count() == 1
|
||||
alice.wait_for_event(EventType.MSG_READ)
|
||||
alice_chat.send_text("You've read 'Hello!'")
|
||||
bob.wait_for_incoming_msg_event()
|
||||
assert bob_chat.get_fresh_message_count() == 2
|
||||
|
||||
|
||||
def test_chat(acfactory) -> None:
|
||||
alice, bob = acfactory.get_online_accounts(2)
|
||||
|
||||
@@ -356,7 +380,7 @@ def test_receive_imf_failure(acfactory) -> None:
|
||||
snapshot.text == "❌ Failed to receive a message:"
|
||||
" Condition failed: `!context.get_config_bool(Config::SimulateReceiveImfError).await?`."
|
||||
f" Core version {version}."
|
||||
" Please report this bug to delta@merlinux.eu or https://support.delta.chat/."
|
||||
" Please report this bug to delta@merlinux.eu or https://support.delta.chat/"
|
||||
)
|
||||
|
||||
# The failed message doesn't break the IMAP loop.
|
||||
|
||||
@@ -56,7 +56,7 @@ for (const { folder_name, package_name } of platform_package_names) {
|
||||
|
||||
if (is_local) {
|
||||
package_json.peerDependencies["@deltachat/jsonrpc-client"] =
|
||||
`file:${join(expected_cwd, "/../../deltachat-jsonrpc-bindings/typescript")}`;
|
||||
`file:${join(expected_cwd, "/../../deltachat-jsonrpc/typescript")}`;
|
||||
} else {
|
||||
package_json.peerDependencies["@deltachat/jsonrpc-client"] = "*";
|
||||
}
|
||||
|
||||
@@ -83,6 +83,8 @@ skip = [
|
||||
{ name = "rustix", version = "0.38.44" },
|
||||
{ name = "rustls-webpki", version = "0.102.8" },
|
||||
{ name = "serdect", version = "0.2.0" },
|
||||
{ name = "snafu-derive", version = "0.8.5" },
|
||||
{ name = "snafu", version = "0.8.5" },
|
||||
{ name = "socket2", version = "0.5.9" },
|
||||
{ name = "spin", version = "0.9.8" },
|
||||
{ name = "strum_macros", version = "0.26.2" },
|
||||
|
||||
@@ -51,7 +51,6 @@
|
||||
./deltachat-contact-tools
|
||||
./deltachat-ffi
|
||||
./deltachat-jsonrpc
|
||||
./deltachat-jsonrpc-bindings
|
||||
./deltachat-ratelimit
|
||||
./deltachat-repl
|
||||
./deltachat-rpc-client
|
||||
|
||||
@@ -67,14 +67,13 @@ def main():
|
||||
parser.add_argument("newversion")
|
||||
|
||||
json_list = [
|
||||
"deltachat-jsonrpc-bindings/typescript/package.json",
|
||||
"deltachat-jsonrpc/typescript/package.json",
|
||||
"deltachat-rpc-server/npm-package/package.json",
|
||||
]
|
||||
toml_list = [
|
||||
"Cargo.toml",
|
||||
"deltachat-ffi/Cargo.toml",
|
||||
"deltachat-jsonrpc/Cargo.toml",
|
||||
"deltachat-jsonrpc-bindings/Cargo.toml",
|
||||
"deltachat-rpc-server/Cargo.toml",
|
||||
"deltachat-repl/Cargo.toml",
|
||||
"python/pyproject.toml",
|
||||
|
||||
@@ -1374,6 +1374,18 @@ async fn test_markfresh_chat() -> Result<()> {
|
||||
assert_eq!(bob_chat_id.get_fresh_msg_cnt(bob).await?, 0);
|
||||
assert_eq!(bob.get_fresh_msgs().await?.len(), 0);
|
||||
|
||||
// Marking a message as seen results to sending an MDN to the contact and self.
|
||||
message::markseen_msgs(bob, vec![bob_msg2.id]).await?;
|
||||
assert_eq!(
|
||||
bob.sql
|
||||
.count(
|
||||
"SELECT COUNT(*) FROM smtp_mdns WHERE from_id=?",
|
||||
(bob_msg2.from_id,)
|
||||
)
|
||||
.await?,
|
||||
1
|
||||
);
|
||||
|
||||
// bob marks the chat as fresh again, fresh count is 1 again
|
||||
markfresh_chat(bob, bob_chat_id).await?;
|
||||
let bob_msg1 = Message::load_from_db(bob, bob_msg1.id).await?;
|
||||
|
||||
@@ -319,6 +319,12 @@ pub enum Config {
|
||||
/// True if account is configured.
|
||||
Configured,
|
||||
|
||||
/// Deprecated, we are trying to get rid of this global setting.
|
||||
/// It is possible to configure a profile with both chatmail relays
|
||||
/// and classical email servers.
|
||||
///
|
||||
/// Most usages in UIs can be replaced by `force_encryption`.
|
||||
///
|
||||
/// True if account is a chatmail account.
|
||||
IsChatmail,
|
||||
|
||||
@@ -455,12 +461,6 @@ pub enum Config {
|
||||
/// Return an error from `receive_imf_inner()`. For tests.
|
||||
SimulateReceiveImfError,
|
||||
|
||||
/// Enable composing emails with Header Protection as defined in
|
||||
/// <https://www.rfc-editor.org/rfc/rfc9788.html> "Header Protection for Cryptographically
|
||||
/// Protected Email".
|
||||
#[strum(props(default = "1"))]
|
||||
StdHeaderProtectionComposing,
|
||||
|
||||
/// Who can call me.
|
||||
///
|
||||
/// The options are from the `WhoCanCallMe` enum.
|
||||
|
||||
@@ -721,12 +721,12 @@ async fn get_autoconfig(
|
||||
}
|
||||
progress!(ctx, 300);
|
||||
|
||||
// `?emailaddress=` query string is excluded on purpose.
|
||||
// It is not part of the URL according to <https://datatracker.ietf.org/doc/draft-ietf-mailmaint-autoconfig/06/>.
|
||||
// Related discussion confirming this is at <https://github.com/benbucksch/autoconfig-spec/issues/17>.
|
||||
if let Ok(res) = moz_autoconfigure(
|
||||
ctx,
|
||||
// the doc does not mention `emailaddress=`, however, Thunderbird adds it, see <https://releases.mozilla.org/pub/thunderbird/>, which makes some sense
|
||||
&format!(
|
||||
"https://{param_domain}/.well-known/autoconfig/mail/config-v1.1.xml?emailaddress={param_addr_urlencoded}"
|
||||
),
|
||||
&format!("https://{param_domain}/.well-known/autoconfig/mail/config-v1.1.xml"),
|
||||
¶m.addr,
|
||||
accept_invalid_certificates,
|
||||
)
|
||||
|
||||
@@ -566,6 +566,10 @@ impl Context {
|
||||
self.scheduler.maybe_network().await;
|
||||
}
|
||||
|
||||
/// Deprecated, we are trying to get rid of this global setting.
|
||||
/// It is possible to configure a profile with both chatmail relays
|
||||
/// and classical email servers.
|
||||
///
|
||||
/// Returns true if an account is on a chatmail server.
|
||||
pub async fn is_chatmail(&self) -> Result<bool> {
|
||||
self.get_config_bool(Config::IsChatmail).await
|
||||
|
||||
@@ -6,13 +6,16 @@ use std::io::Cursor;
|
||||
|
||||
use anyhow::{Context as _, Result, bail};
|
||||
use mailparse::ParsedMail;
|
||||
use pgp::composed::DecryptionOptions;
|
||||
use pgp::composed::Esk;
|
||||
use pgp::composed::Message;
|
||||
use pgp::composed::PlainSessionKey;
|
||||
use pgp::composed::SignedSecretKey;
|
||||
use pgp::composed::TheRing;
|
||||
use pgp::composed::decrypt_session_key_with_password;
|
||||
use pgp::packet::SymKeyEncryptedSessionKey;
|
||||
use pgp::types::Password;
|
||||
use pgp::types::Seipdv1ReadMode;
|
||||
use pgp::types::StringToKey;
|
||||
|
||||
use crate::chat::ChatId;
|
||||
@@ -48,6 +51,15 @@ pub(crate) async fn decrypt(
|
||||
};
|
||||
let expected_sender_fingerprint: Option<String>;
|
||||
|
||||
let abort_early = true;
|
||||
|
||||
// Use streaming mode for SEIPDv1 decryption to save memory.
|
||||
// This was the default in rPGP 0.19.0
|
||||
// and requires explicitly changing the mode in rPGP 0.20.0.
|
||||
// SEPIDv2 is decrypted in streaming mode in any case.
|
||||
let decrypt_options =
|
||||
DecryptionOptions::new().set_seipdv1_read_mode(Seipdv1ReadMode::Streaming);
|
||||
|
||||
let plain = if let Message::Encrypted { esk, .. } = &*msg
|
||||
// We only allow one ESK for symmetrically encrypted messages
|
||||
// to avoid dealing with messages that are encrypted to multiple symmetric keys
|
||||
@@ -61,9 +73,15 @@ pub(crate) async fn decrypt(
|
||||
expected_sender_fingerprint = fingerprint;
|
||||
|
||||
tokio::task::spawn_blocking(move || -> Result<Message<'_>> {
|
||||
let plain = msg
|
||||
.decrypt_with_session_key(psk)
|
||||
.context("decrypt_with_session_key")?;
|
||||
let ring = TheRing {
|
||||
session_keys: vec![psk],
|
||||
decrypt_options,
|
||||
..Default::default()
|
||||
};
|
||||
|
||||
let (plain, _ring_result) = msg
|
||||
.decrypt_the_ring(ring, abort_early)
|
||||
.context("decrypt_the_ring")?;
|
||||
|
||||
let plain: Message<'static> = plain.decompress()?;
|
||||
Ok(plain)
|
||||
@@ -75,11 +93,15 @@ pub(crate) async fn decrypt(
|
||||
expected_sender_fingerprint = None;
|
||||
|
||||
tokio::task::spawn_blocking(move || -> Result<Message<'_>> {
|
||||
let empty_pw = Password::empty();
|
||||
let secret_keys: Vec<&SignedSecretKey> = secret_keys.iter().collect();
|
||||
let plain = msg
|
||||
.decrypt_with_keys(vec![&empty_pw], secret_keys)
|
||||
.context("decrypt_with_keys")?;
|
||||
let ring = TheRing {
|
||||
secret_keys,
|
||||
decrypt_options,
|
||||
..Default::default()
|
||||
};
|
||||
let (plain, _ring_result) = msg
|
||||
.decrypt_the_ring(ring, abort_early)
|
||||
.context("decrypt_the_ring")?;
|
||||
|
||||
let plain: Message<'static> = plain.decompress()?;
|
||||
Ok(plain)
|
||||
|
||||
37
src/e2ee.rs
37
src/e2ee.rs
@@ -83,29 +83,20 @@ impl EncryptHelper {
|
||||
None
|
||||
};
|
||||
|
||||
let shared_secret = shared_secret.to_string();
|
||||
let mut raw_message = Vec::new();
|
||||
let cursor = Cursor::new(&mut raw_message);
|
||||
mail_to_encrypt.clone().write_part(cursor).ok();
|
||||
|
||||
let ctext =
|
||||
pgp::symm_encrypt_message(raw_message, sign_key, shared_secret, compress).await?;
|
||||
let ctext = tokio::task::spawn_blocking(move || {
|
||||
pgp::symm_encrypt_message(raw_message, sign_key, shared_secret, compress)
|
||||
})
|
||||
.await??;
|
||||
|
||||
Ok(ctext)
|
||||
}
|
||||
}
|
||||
|
||||
/// Ensures a private key exists for the configured user.
|
||||
///
|
||||
/// Normally the private key is generated when the first message is
|
||||
/// sent but in a few locations there are no such guarantees,
|
||||
/// e.g. when exporting keys, and calling this function ensures a
|
||||
/// private key will be present.
|
||||
// TODO, remove this once deltachat::key::Key no longer exists.
|
||||
pub async fn ensure_secret_key_exists(context: &Context) -> Result<()> {
|
||||
load_self_public_key(context).await?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
@@ -115,23 +106,7 @@ mod tests {
|
||||
use crate::message::Message;
|
||||
use crate::mimeparser::SystemMessage;
|
||||
use crate::receive_imf::receive_imf;
|
||||
use crate::test_utils::{TestContext, TestContextManager};
|
||||
|
||||
mod ensure_secret_key_exists {
|
||||
use super::*;
|
||||
|
||||
#[tokio::test(flavor = "multi_thread", worker_threads = 2)]
|
||||
async fn test_prexisting() {
|
||||
let t = TestContext::new_alice().await;
|
||||
assert!(ensure_secret_key_exists(&t).await.is_ok());
|
||||
}
|
||||
|
||||
#[tokio::test(flavor = "multi_thread", worker_threads = 2)]
|
||||
async fn test_not_configured() {
|
||||
let t = TestContext::new().await;
|
||||
assert!(ensure_secret_key_exists(&t).await.is_err());
|
||||
}
|
||||
}
|
||||
use crate::test_utils::TestContextManager;
|
||||
|
||||
#[test]
|
||||
fn test_mailmime_parse() {
|
||||
|
||||
10
src/imap.rs
10
src/imap.rs
@@ -1117,7 +1117,7 @@ impl Session {
|
||||
Err(err) => {
|
||||
warn!(
|
||||
context,
|
||||
"store_seen_flags_on_imap: Failed to select {folder}, will retry later: {err:#}."
|
||||
"store_seen_flags_on_imap: Transport {transport_id}: Failed to select {folder}, will retry later: {err:#}."
|
||||
);
|
||||
continue;
|
||||
}
|
||||
@@ -1128,13 +1128,13 @@ impl Session {
|
||||
} else if let Err(err) = self.add_flag_finalized_with_set(&uid_set, "\\Seen").await {
|
||||
warn!(
|
||||
context,
|
||||
"Cannot mark messages {uid_set} in {folder} as seen, will retry later: {err:#}."
|
||||
"Transport {transport_id}: Cannot mark messages {uid_set} in {folder} as seen, will retry later: {err:#}."
|
||||
);
|
||||
continue;
|
||||
} else {
|
||||
info!(
|
||||
context,
|
||||
"Marked messages {} in folder {} as seen.", uid_set, folder
|
||||
"Transport {transport_id}: Marked messages {uid_set} in folder {folder} as seen."
|
||||
);
|
||||
}
|
||||
context
|
||||
@@ -1284,7 +1284,9 @@ impl Session {
|
||||
warn!(context, "receive_imf error: {err:#}.");
|
||||
|
||||
let text = format!(
|
||||
"❌ Failed to receive a message: {err:#}. Core version v{DC_VERSION_STR}. Please report this bug to delta@merlinux.eu or https://support.delta.chat/.",
|
||||
// No trailing '.' to avoid from the Android UI treating it as a part of
|
||||
// URL.
|
||||
"❌ Failed to receive a message: {err:#}. Core version v{DC_VERSION_STR}. Please report this bug to delta@merlinux.eu or https://support.delta.chat/",
|
||||
);
|
||||
let mut msg = Message::new_text(text);
|
||||
add_device_msg(context, None, Some(&mut msg)).await?;
|
||||
|
||||
@@ -111,7 +111,7 @@ impl Session {
|
||||
}
|
||||
|
||||
// Returns true if IMAP server has `XCHATMAIL` capability.
|
||||
pub fn is_chatmail(&self) -> bool {
|
||||
pub(crate) fn is_chatmail(&self) -> bool {
|
||||
self.capabilities.is_chatmail
|
||||
}
|
||||
|
||||
|
||||
@@ -17,7 +17,6 @@ use crate::blob::BlobDirContents;
|
||||
use crate::chat::delete_and_reset_all_device_msgs;
|
||||
use crate::config::Config;
|
||||
use crate::context::Context;
|
||||
use crate::e2ee;
|
||||
use crate::events::EventType;
|
||||
use crate::key::{self, DcKey, SignedSecretKey};
|
||||
use crate::log::{LogExt, warn};
|
||||
@@ -170,7 +169,7 @@ async fn imex_inner(
|
||||
|
||||
if what == ImexMode::ExportBackup || what == ImexMode::ExportSelfKeys {
|
||||
// before we export anything, make sure the private key exists
|
||||
e2ee::ensure_secret_key_exists(context)
|
||||
key::ensure_secret_key_exists(context)
|
||||
.await
|
||||
.context("Cannot create private key or private key not available")?;
|
||||
|
||||
|
||||
@@ -38,15 +38,16 @@ use tokio::fs;
|
||||
use tokio::task::JoinHandle;
|
||||
use tokio_util::sync::CancellationToken;
|
||||
|
||||
use crate::EventType;
|
||||
use crate::chat::add_device_msg;
|
||||
use crate::context::Context;
|
||||
use crate::imex::BlobDirContents;
|
||||
use crate::key;
|
||||
use crate::log::warn;
|
||||
use crate::message::Message;
|
||||
use crate::qr::Qr;
|
||||
use crate::stock_str::backup_transfer_msg_body;
|
||||
use crate::tools::{TempPathGuard, create_id, time};
|
||||
use crate::{EventType, e2ee};
|
||||
|
||||
use super::{DBFILE_BACKUP_NAME, export_backup_stream, export_database, import_backup_stream};
|
||||
|
||||
@@ -112,7 +113,7 @@ impl BackupProvider {
|
||||
.context("Context dir not found")?;
|
||||
|
||||
// before we export, make sure the private key exists
|
||||
e2ee::ensure_secret_key_exists(context)
|
||||
key::ensure_secret_key_exists(context)
|
||||
.await
|
||||
.context("Cannot create private key or private key not available")?;
|
||||
|
||||
|
||||
27
src/key.rs
27
src/key.rs
@@ -320,6 +320,17 @@ pub(crate) async fn load_self_public_key(context: &Context) -> Result<SignedPubl
|
||||
}
|
||||
}
|
||||
|
||||
/// Ensures a private key exists for the configured user.
|
||||
///
|
||||
/// Normally the private key is generated when the first message is
|
||||
/// sent but in a few locations there are no such guarantees,
|
||||
/// e.g. when exporting keys, and calling this function ensures a
|
||||
/// private key will be present.
|
||||
pub async fn ensure_secret_key_exists(context: &Context) -> Result<()> {
|
||||
load_self_public_key(context).await?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Returns our own public keyring.
|
||||
///
|
||||
/// No keys are generated and at most one key is returned.
|
||||
@@ -898,4 +909,20 @@ i8pcjGO+IZffvyZJVRWfVooBJmWWbPB1pueo3tx8w3+fcuzpxz+RLFKaPyqXO+dD
|
||||
"0102 0408 1020 4080 FF01\n0204 0810 2040 80FF 1314"
|
||||
);
|
||||
}
|
||||
|
||||
mod ensure_secret_key_exists {
|
||||
use super::*;
|
||||
|
||||
#[tokio::test(flavor = "multi_thread", worker_threads = 2)]
|
||||
async fn test_prexisting() {
|
||||
let t = TestContext::new_alice().await;
|
||||
assert!(ensure_secret_key_exists(&t).await.is_ok());
|
||||
}
|
||||
|
||||
#[tokio::test(flavor = "multi_thread", worker_threads = 2)]
|
||||
async fn test_not_configured() {
|
||||
let t = TestContext::new().await;
|
||||
assert!(ensure_secret_key_exists(&t).await.is_err());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -618,9 +618,7 @@ impl MimeFactory {
|
||||
|
||||
fn should_skip_autocrypt(&self) -> bool {
|
||||
match &self.loaded {
|
||||
Loaded::Message { msg, .. } => {
|
||||
msg.param.get_bool(Param::SkipAutocrypt).unwrap_or_default()
|
||||
}
|
||||
Loaded::Message { .. } => false,
|
||||
Loaded::Mdn { .. } => true,
|
||||
}
|
||||
}
|
||||
@@ -1030,16 +1028,12 @@ impl MimeFactory {
|
||||
is_securejoin_message,
|
||||
);
|
||||
|
||||
let use_std_header_protection = context
|
||||
.get_config_bool(Config::StdHeaderProtectionComposing)
|
||||
.await?;
|
||||
let outer_message = if let Some(encryption_pubkeys) = self.encryption_pubkeys {
|
||||
let mut message = add_headers_to_encrypted_part(
|
||||
message,
|
||||
&unprotected_headers,
|
||||
hidden_headers,
|
||||
protected_headers,
|
||||
use_std_header_protection,
|
||||
);
|
||||
|
||||
// Add gossip headers in chats with multiple recipients
|
||||
@@ -1941,7 +1935,6 @@ fn add_headers_to_encrypted_part(
|
||||
unprotected_headers: &[(&'static str, HeaderType<'static>)],
|
||||
hidden_headers: Vec<(&'static str, HeaderType<'static>)>,
|
||||
protected_headers: Vec<(&'static str, HeaderType<'static>)>,
|
||||
use_std_header_protection: bool,
|
||||
) -> MimePart<'static> {
|
||||
// Store protected headers in the inner message.
|
||||
let message = protected_headers
|
||||
@@ -1957,21 +1950,19 @@ fn add_headers_to_encrypted_part(
|
||||
message.header(header, value)
|
||||
});
|
||||
|
||||
if use_std_header_protection {
|
||||
message = unprotected_headers
|
||||
.iter()
|
||||
// Structural headers shouldn't be added as "HP-Outer". They are defined in
|
||||
// <https://www.rfc-editor.org/rfc/rfc9787.html#structural-header-fields>.
|
||||
.filter(|(name, _)| {
|
||||
!(name.eq_ignore_ascii_case("mime-version")
|
||||
|| name.eq_ignore_ascii_case("content-type")
|
||||
|| name.eq_ignore_ascii_case("content-transfer-encoding")
|
||||
|| name.eq_ignore_ascii_case("content-disposition"))
|
||||
})
|
||||
.fold(message, |message, (name, value)| {
|
||||
message.header(format!("HP-Outer: {name}"), value.clone())
|
||||
});
|
||||
}
|
||||
message = unprotected_headers
|
||||
.iter()
|
||||
// Structural headers shouldn't be added as "HP-Outer". They are defined in
|
||||
// <https://www.rfc-editor.org/rfc/rfc9787.html#structural-header-fields>.
|
||||
.filter(|(name, _)| {
|
||||
!(name.eq_ignore_ascii_case("mime-version")
|
||||
|| name.eq_ignore_ascii_case("content-type")
|
||||
|| name.eq_ignore_ascii_case("content-transfer-encoding")
|
||||
|| name.eq_ignore_ascii_case("content-disposition"))
|
||||
})
|
||||
.fold(message, |message, (name, value)| {
|
||||
message.header(format!("HP-Outer: {name}"), value.clone())
|
||||
});
|
||||
|
||||
// Set the appropriate Content-Type for the inner message
|
||||
for (h, v) in &mut message.headers {
|
||||
@@ -1980,9 +1971,7 @@ fn add_headers_to_encrypted_part(
|
||||
{
|
||||
let mut ct_new = ct.clone();
|
||||
ct_new = ct_new.attribute("protected-headers", "v1");
|
||||
if use_std_header_protection {
|
||||
ct_new = ct_new.attribute("hp", "cipher");
|
||||
}
|
||||
ct_new = ct_new.attribute("hp", "cipher");
|
||||
*ct = ct_new;
|
||||
break;
|
||||
}
|
||||
@@ -2324,13 +2313,11 @@ pub(crate) async fn render_symm_encrypted_securejoin_message(
|
||||
);
|
||||
|
||||
let outer_message = {
|
||||
let use_std_header_protection = true;
|
||||
let message = add_headers_to_encrypted_part(
|
||||
message,
|
||||
&unprotected_headers,
|
||||
hidden_headers,
|
||||
protected_headers,
|
||||
use_std_header_protection,
|
||||
);
|
||||
|
||||
// Disable compression for SecureJoin to ensure
|
||||
|
||||
@@ -730,21 +730,14 @@ async fn test_hp_outer_headers() -> Result<()> {
|
||||
let t = &tcm.alice().await;
|
||||
let chat_id = t.get_self_chat().await.id;
|
||||
|
||||
for std_hp_composing in [false, true] {
|
||||
t.set_config_bool(Config::StdHeaderProtectionComposing, std_hp_composing)
|
||||
.await?;
|
||||
chat::send_text_msg(t, chat_id, "hi!".to_string()).await?;
|
||||
let sent_msg = t.pop_sent_msg().await;
|
||||
let msg = MimeMessage::from_bytes(t, sent_msg.payload.as_bytes()).await?;
|
||||
assert_eq!(msg.header_exists(HeaderDef::HpOuter), std_hp_composing);
|
||||
for hdr in ["Date", "From", "Message-ID"] {
|
||||
assert_eq!(
|
||||
msg.decoded_data_contains(&format!("HP-Outer: {hdr}:")),
|
||||
std_hp_composing,
|
||||
);
|
||||
}
|
||||
assert!(!msg.decoded_data_contains("HP-Outer: Content-Type"));
|
||||
chat::send_text_msg(t, chat_id, "hi!".to_string()).await?;
|
||||
let sent_msg = t.pop_sent_msg().await;
|
||||
let msg = MimeMessage::from_bytes(t, sent_msg.payload.as_bytes()).await?;
|
||||
assert!(msg.header_exists(HeaderDef::HpOuter));
|
||||
for hdr in ["Date", "From", "Message-ID"] {
|
||||
assert!(msg.decoded_data_contains(&format!("HP-Outer: {hdr}:")),);
|
||||
}
|
||||
assert!(!msg.decoded_data_contains("HP-Outer: Content-Type"));
|
||||
Ok(())
|
||||
}
|
||||
|
||||
|
||||
@@ -1404,23 +1404,17 @@ async fn test_extra_imf_headers() -> Result<()> {
|
||||
let t = &tcm.alice().await;
|
||||
let chat_id = t.get_self_chat().await.id;
|
||||
|
||||
for std_hp_composing in [false, true] {
|
||||
t.set_config_bool(Config::StdHeaderProtectionComposing, std_hp_composing)
|
||||
.await?;
|
||||
chat::send_text_msg(t, chat_id, "hi!".to_string()).await?;
|
||||
let sent_msg = t.pop_sent_msg().await;
|
||||
// Check removal of some nonexistent "Chat-*" header to protect the code from future
|
||||
// breakages. But headers not prefixed with "Chat-" remain unless a message has standard
|
||||
// Header Protection.
|
||||
let payload = sent_msg.payload.replace(
|
||||
"Message-ID:",
|
||||
"Chat-Forty-Two: 42\r\nForty-Two: 42\r\nMessage-ID:",
|
||||
);
|
||||
let msg = MimeMessage::from_bytes(t, payload.as_bytes()).await?;
|
||||
assert!(msg.headers.contains_key("chat-version"));
|
||||
assert!(!msg.headers.contains_key("chat-forty-two"));
|
||||
assert_ne!(msg.headers.contains_key("forty-two"), std_hp_composing);
|
||||
}
|
||||
chat::send_text_msg(t, chat_id, "hi!".to_string()).await?;
|
||||
let sent_msg = t.pop_sent_msg().await;
|
||||
// Check that extra headers are ignored with RFC 9788 Header Protection.
|
||||
let payload = sent_msg.payload.replace(
|
||||
"Message-ID:",
|
||||
"Chat-Forty-Two: 42\r\nForty-Two: 42\r\nMessage-ID:",
|
||||
);
|
||||
let msg = MimeMessage::from_bytes(t, payload.as_bytes()).await?;
|
||||
assert!(msg.headers.contains_key("chat-version"));
|
||||
assert!(!msg.headers.contains_key("chat-forty-two"));
|
||||
assert!(!msg.headers.contains_key("forty-two"));
|
||||
Ok(())
|
||||
}
|
||||
|
||||
|
||||
@@ -48,10 +48,9 @@ async fn test_shared_secret_decryption_ex(
|
||||
let encrypted_msg = pgp::symm_encrypt_message(
|
||||
plain_text.as_bytes().to_vec(),
|
||||
signer_key,
|
||||
secret_for_encryption,
|
||||
secret_for_encryption.to_string(),
|
||||
true,
|
||||
)
|
||||
.await?;
|
||||
)?;
|
||||
|
||||
let boundary = "boundary123";
|
||||
let rcvd_mail = format!(
|
||||
|
||||
@@ -64,7 +64,8 @@ pub enum Param {
|
||||
ForcePlaintext = b'u',
|
||||
|
||||
/// For Messages: do not include Autocrypt header.
|
||||
SkipAutocrypt = b'o',
|
||||
/// Deprecated on 2026-06-20
|
||||
DeprecatedSkipAutocrypt = b'o',
|
||||
|
||||
/// For Messages
|
||||
WantsMdn = b'r',
|
||||
|
||||
@@ -589,6 +589,7 @@ mod tests {
|
||||
EventType,
|
||||
chat::{self, ChatId, add_contact_to_chat, resend_msgs, send_msg},
|
||||
message::{Message, Viewtype},
|
||||
receive_imf::receive_imf,
|
||||
test_utils::{TestContext, TestContextManager},
|
||||
};
|
||||
|
||||
@@ -759,6 +760,67 @@ mod tests {
|
||||
assert!(alice.iroh.read().await.is_none());
|
||||
}
|
||||
|
||||
#[tokio::test(flavor = "multi_thread", worker_threads = 2)]
|
||||
async fn test_duplicated_out_of_order_advertisement() -> Result<()> {
|
||||
let mut tcm = TestContextManager::new();
|
||||
let alice = &mut tcm.alice().await;
|
||||
let bob = &mut tcm.bob().await;
|
||||
|
||||
let alice_chat = alice.create_chat(bob).await;
|
||||
let mut instance = Message::new(Viewtype::File);
|
||||
instance.set_file_from_bytes(
|
||||
alice,
|
||||
"minimal.xdc",
|
||||
include_bytes!("../test-data/webxdc/minimal.xdc"),
|
||||
None,
|
||||
)?;
|
||||
|
||||
send_msg(alice, alice_chat.id, &mut instance).await?;
|
||||
let alice_webxdc = alice.get_last_msg().await;
|
||||
assert_eq!(alice_webxdc.get_viewtype(), Viewtype::Webxdc);
|
||||
|
||||
let webxdc = alice.pop_sent_msg().await;
|
||||
// Imagine that at this point Alice learns about Bob's new transport...
|
||||
send_webxdc_realtime_advertisement(alice, alice_webxdc.id).await?;
|
||||
let advertisement = alice.pop_sent_msg().await;
|
||||
|
||||
// Bob receives an out-of-order advertisement from his new transport.
|
||||
receive_imf(bob, advertisement.payload().as_bytes(), false).await?;
|
||||
|
||||
let bob_webxdc = bob.recv_msg(&webxdc).await;
|
||||
assert_eq!(bob_webxdc.get_viewtype(), Viewtype::Webxdc);
|
||||
|
||||
bob_webxdc.chat_id.accept(bob).await?;
|
||||
|
||||
bob.recv_msg_trash(&advertisement).await;
|
||||
loop {
|
||||
let event = bob.evtracker.recv().await.unwrap();
|
||||
if let EventType::WebxdcRealtimeAdvertisementReceived { msg_id } = event.typ {
|
||||
assert!(msg_id == bob_webxdc.id);
|
||||
break;
|
||||
}
|
||||
}
|
||||
let members = get_iroh_gossip_peers(bob, bob_webxdc.id)
|
||||
.await?
|
||||
.into_iter()
|
||||
.map(|addr| addr.node_id)
|
||||
.collect::<Vec<_>>();
|
||||
assert_eq!(
|
||||
members,
|
||||
vec![
|
||||
alice
|
||||
.get_or_try_init_peer_channel()
|
||||
.await
|
||||
.unwrap()
|
||||
.get_node_addr()
|
||||
.await
|
||||
.unwrap()
|
||||
.node_id
|
||||
]
|
||||
);
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[tokio::test(flavor = "multi_thread", worker_threads = 2)]
|
||||
async fn test_can_reconnect() {
|
||||
let mut tcm = TestContextManager::new();
|
||||
|
||||
74
src/pgp.rs
74
src/pgp.rs
@@ -34,7 +34,7 @@ const SYMMETRIC_KEY_ALGORITHM: SymmetricKeyAlgorithm = SymmetricKeyAlgorithm::AE
|
||||
/// as [described in the Autocrypt standard](https://autocrypt.org/level1.html#openpgp-based-key-data).
|
||||
pub(crate) fn create_keypair(addr: EmailAddress) -> Result<SignedSecretKey> {
|
||||
let signing_key_type = PgpKeyType::Ed25519Legacy;
|
||||
let encryption_key_type = PgpKeyType::ECDH(ECCCurve::Curve25519);
|
||||
let encryption_key_type = PgpKeyType::ECDH(ECCCurve::Curve25519Legacy);
|
||||
|
||||
let user_id = format!("<{addr}>");
|
||||
let key_params = SecretKeyParamsBuilder::default()
|
||||
@@ -254,44 +254,41 @@ pub fn pk_validate(
|
||||
/// Symmetrically encrypt the message.
|
||||
/// This is used for broadcast channels and for version 2 of the Securejoin protocol.
|
||||
/// `shared secret` is the secret that will be used for symmetric encryption.
|
||||
pub async fn symm_encrypt_message(
|
||||
pub fn symm_encrypt_message(
|
||||
plain: Vec<u8>,
|
||||
private_key_for_signing: Option<SignedSecretKey>,
|
||||
shared_secret: &str,
|
||||
shared_secret: String,
|
||||
compress: bool,
|
||||
) -> Result<String> {
|
||||
let shared_secret = Password::from(shared_secret.to_string());
|
||||
let shared_secret = Password::from(shared_secret);
|
||||
|
||||
tokio::task::spawn_blocking(move || {
|
||||
let msg = MessageBuilder::from_bytes("", plain);
|
||||
let mut rng = thread_rng();
|
||||
let mut salt = [0u8; 8];
|
||||
rng.fill(&mut salt[..]);
|
||||
let s2k = StringToKey::Salted {
|
||||
hash_alg: HashAlgorithm::default(),
|
||||
salt,
|
||||
};
|
||||
let mut msg = msg.seipd_v2(
|
||||
&mut rng,
|
||||
SYMMETRIC_KEY_ALGORITHM,
|
||||
AeadAlgorithm::Ocb,
|
||||
ChunkSize::C8KiB,
|
||||
);
|
||||
msg.encrypt_with_password(&mut rng, s2k, &shared_secret)?;
|
||||
let msg = MessageBuilder::from_bytes("", plain);
|
||||
let mut rng = thread_rng();
|
||||
let mut salt = [0u8; 8];
|
||||
rng.fill(&mut salt[..]);
|
||||
let s2k = StringToKey::Salted {
|
||||
hash_alg: HashAlgorithm::default(),
|
||||
salt,
|
||||
};
|
||||
let mut msg = msg.seipd_v2(
|
||||
&mut rng,
|
||||
SYMMETRIC_KEY_ALGORITHM,
|
||||
AeadAlgorithm::Ocb,
|
||||
ChunkSize::C8KiB,
|
||||
);
|
||||
msg.encrypt_with_password(&mut rng, s2k, &shared_secret)?;
|
||||
|
||||
if let Some(private_key_for_signing) = private_key_for_signing.as_deref() {
|
||||
let hash_algorithm = private_key_for_signing.hash_alg();
|
||||
msg.sign(private_key_for_signing, Password::empty(), hash_algorithm);
|
||||
}
|
||||
if compress {
|
||||
msg.compression(CompressionAlgorithm::ZLIB);
|
||||
}
|
||||
if let Some(private_key_for_signing) = private_key_for_signing.as_deref() {
|
||||
let hash_algorithm = private_key_for_signing.hash_alg();
|
||||
msg.sign(private_key_for_signing, Password::empty(), hash_algorithm);
|
||||
}
|
||||
if compress {
|
||||
msg.compression(CompressionAlgorithm::ZLIB);
|
||||
}
|
||||
|
||||
let encoded_msg = msg.to_armored_string(&mut rng, Default::default())?;
|
||||
let encoded_msg = msg.to_armored_string(&mut rng, Default::default())?;
|
||||
|
||||
Ok(encoded_msg)
|
||||
})
|
||||
.await?
|
||||
Ok(encoded_msg)
|
||||
}
|
||||
|
||||
/// Merges and minimizes OpenPGP certificates.
|
||||
@@ -374,10 +371,7 @@ pub fn merge_openpgp_certificates(
|
||||
.into_iter()
|
||||
.chain(new_direct_signatures)
|
||||
.filter(|x: &Signature| x.verify_key(&old_primary_key).is_ok())
|
||||
.max_by_key(|x: &Signature|
|
||||
// Converting to seconds because `Ord` is not derived for `Timestamp`:
|
||||
// <https://github.com/rpgp/rpgp/issues/737>
|
||||
x.created().map_or(0, |ts| ts.as_secs()));
|
||||
.max_by_key(|x: &Signature| x.created());
|
||||
let direct_signatures: Vec<Signature> = best_direct_key_signature.into_iter().collect();
|
||||
|
||||
// Select at most one User ID.
|
||||
@@ -399,12 +393,10 @@ pub fn merge_openpgp_certificates(
|
||||
.verify_certification(&old_primary_key, pgp::types::Tag::UserId, &id)
|
||||
.is_ok()
|
||||
})
|
||||
.max_by_key(|signature: &Signature| {
|
||||
signature.created().map_or(0, |ts| ts.as_secs())
|
||||
});
|
||||
.max_by_key(|signature: &Signature| signature.created());
|
||||
best_user_signature.map(|signature| (id, signature))
|
||||
})
|
||||
.max_by_key(|(_id, signature)| signature.created().map_or(0, |ts| ts.as_secs()))
|
||||
.max_by_key(|(_id, signature)| signature.created())
|
||||
.map(|(id, signature)| SignedUser {
|
||||
id,
|
||||
signatures: vec![signature],
|
||||
@@ -710,7 +702,7 @@ mod tests {
|
||||
|
||||
// This error message is actually not great,
|
||||
// but grepping for it will lead to the correct code
|
||||
test_dont_decrypt_expensive_message_ex(s2k, true, Some("decrypt_with_keys: missing key"))
|
||||
test_dont_decrypt_expensive_message_ex(s2k, true, Some("decrypt_the_ring: missing key"))
|
||||
.await
|
||||
}
|
||||
|
||||
@@ -793,7 +785,7 @@ mod tests {
|
||||
.await
|
||||
.unwrap_err();
|
||||
|
||||
assert_eq!(format!("{error:#}"), "decrypt_with_keys: missing key");
|
||||
assert_eq!(format!("{error:#}"), "decrypt_the_ring: missing key");
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
12
src/qr.rs
12
src/qr.rs
@@ -56,6 +56,9 @@ pub enum Qr {
|
||||
/// Fingerprint of the contact key as scanned from the QR code.
|
||||
fingerprint: Fingerprint,
|
||||
|
||||
/// The inviter's addresses.
|
||||
addrs: Vec<String>,
|
||||
|
||||
/// Invite number.
|
||||
invitenumber: String,
|
||||
|
||||
@@ -80,6 +83,9 @@ pub enum Qr {
|
||||
/// Fingerprint of the contact key as scanned from the QR code.
|
||||
fingerprint: Fingerprint,
|
||||
|
||||
/// The inviter's addresses.
|
||||
addrs: Vec<String>,
|
||||
|
||||
/// Invite number.
|
||||
invitenumber: String,
|
||||
|
||||
@@ -108,6 +114,9 @@ pub enum Qr {
|
||||
/// Fingerprint of the contact's key as scanned from the QR code.
|
||||
fingerprint: Fingerprint,
|
||||
|
||||
/// The inviter's addresses.
|
||||
addrs: Vec<String>,
|
||||
|
||||
/// Invite number.
|
||||
invitenumber: String,
|
||||
/// Authentication code.
|
||||
@@ -563,6 +572,7 @@ async fn decode_openpgp(context: &Context, qr: &str) -> Result<Qr> {
|
||||
grpid,
|
||||
contact_id,
|
||||
fingerprint,
|
||||
addrs: vec![addr.to_string()],
|
||||
invitenumber,
|
||||
authcode,
|
||||
is_v3,
|
||||
@@ -599,6 +609,7 @@ async fn decode_openpgp(context: &Context, qr: &str) -> Result<Qr> {
|
||||
grpid,
|
||||
contact_id,
|
||||
fingerprint,
|
||||
addrs: vec![addr.to_string()],
|
||||
invitenumber,
|
||||
authcode,
|
||||
is_v3,
|
||||
@@ -624,6 +635,7 @@ async fn decode_openpgp(context: &Context, qr: &str) -> Result<Qr> {
|
||||
Ok(Qr::AskVerifyContact {
|
||||
contact_id,
|
||||
fingerprint,
|
||||
addrs: vec![addr.to_string()],
|
||||
invitenumber,
|
||||
authcode,
|
||||
is_v3,
|
||||
|
||||
@@ -61,7 +61,7 @@ use crate::{logged_debug_assert, mimeparser};
|
||||
///
|
||||
/// One email with multiple attachments can end up as multiple chat messages, but they
|
||||
/// all have the same chat_id, state and sort_timestamp.
|
||||
#[derive(Debug)]
|
||||
#[derive(Debug, Default)]
|
||||
pub struct ReceivedMsg {
|
||||
/// Chat the message is assigned to.
|
||||
pub chat_id: ChatId,
|
||||
@@ -545,8 +545,15 @@ pub(crate) async fn receive_imf_inner(
|
||||
if mime_parser.incoming {
|
||||
return Ok(None);
|
||||
}
|
||||
// For the case if we missed a successful SMTP response. Be optimistic that the message is
|
||||
// delivered also.
|
||||
|
||||
// It sometimes happens that a slow server (usually a classical email server)
|
||||
// receives a message via SMTP,
|
||||
// but then the connection to the server dies before it sends the OK response.
|
||||
// In order to handle this case, we delete the SMTP send jobs if we receive our own message via IMAP.
|
||||
//
|
||||
// Now, messages with long recipient lists are split into multiple SMTP jobs.
|
||||
// In this case, we only want to delete the SMTP job that was sent to self
|
||||
// because this is the only chunk we can be sure was sent out.
|
||||
let self_addr = context.get_primary_self_addr().await?;
|
||||
context
|
||||
.sql
|
||||
@@ -2067,8 +2074,15 @@ async fn add_parts(
|
||||
None => {
|
||||
warn!(
|
||||
context,
|
||||
"Cannot add iroh peer because WebXDC instance does not exist."
|
||||
"Cannot add iroh peer because WebXDC instance {in_reply_to} does not exist."
|
||||
);
|
||||
return Ok(ReceivedMsg {
|
||||
chat_id,
|
||||
state,
|
||||
hidden: true,
|
||||
sort_timestamp,
|
||||
..Default::default()
|
||||
});
|
||||
}
|
||||
},
|
||||
None => {
|
||||
|
||||
@@ -14,9 +14,9 @@ use crate::constants::{
|
||||
use crate::contact::mark_contact_id_as_verified;
|
||||
use crate::contact::{Contact, ContactId, Origin};
|
||||
use crate::context::Context;
|
||||
use crate::e2ee::ensure_secret_key_exists;
|
||||
use crate::events::EventType;
|
||||
use crate::headerdef::HeaderDef;
|
||||
use crate::key;
|
||||
use crate::key::{DcKey, Fingerprint, load_self_public_key, self_fingerprint};
|
||||
use crate::log::LogExt as _;
|
||||
use crate::log::warn;
|
||||
@@ -92,7 +92,7 @@ pub async fn get_securejoin_qr(context: &Context, chat: Option<ChatId>) -> Resul
|
||||
==== Step 1 in "Setup verified contact" protocol ====
|
||||
=======================================================*/
|
||||
|
||||
ensure_secret_key_exists(context).await.ok();
|
||||
key::ensure_secret_key_exists(context).await.ok();
|
||||
|
||||
let chat = match chat {
|
||||
Some(id) => {
|
||||
@@ -741,7 +741,7 @@ pub(crate) async fn handle_securejoin_handshake(
|
||||
async fn insert_into_smtp(
|
||||
context: &Context,
|
||||
rfc724_mid: &str,
|
||||
recipient: &str,
|
||||
recipients: &str,
|
||||
rendered_message: String,
|
||||
msg_id: MsgId,
|
||||
) -> Result<(), Error> {
|
||||
@@ -750,7 +750,7 @@ async fn insert_into_smtp(
|
||||
.execute(
|
||||
"INSERT INTO smtp (rfc724_mid, recipients, mime, msg_id)
|
||||
VALUES (?1, ?2, ?3, ?4)",
|
||||
(&rfc724_mid, &recipient, &rendered_message, msg_id),
|
||||
(&rfc724_mid, &recipients, &rendered_message, msg_id),
|
||||
)
|
||||
.await?;
|
||||
Ok(())
|
||||
|
||||
@@ -1,19 +1,21 @@
|
||||
//! Bob's side of SecureJoin handling, the joiner-side.
|
||||
|
||||
use anyhow::{Context as _, Result};
|
||||
use pgp::composed::SignedPublicKey;
|
||||
|
||||
use super::HandshakeMessage;
|
||||
use super::qrinvite::QrInvite;
|
||||
use crate::chat::{self, ChatId, is_contact_in_chat};
|
||||
use crate::constants::{Blocked, Chattype};
|
||||
use crate::contact::{Contact, Origin};
|
||||
use crate::contact::Origin;
|
||||
use crate::context::Context;
|
||||
use crate::events::EventType;
|
||||
use crate::key::self_fingerprint;
|
||||
use crate::key::{DcKey as _, self_fingerprint};
|
||||
use crate::log::LogExt;
|
||||
use crate::message::{self, Message, MsgId, Viewtype};
|
||||
use crate::mimeparser::{MimeMessage, SystemMessage};
|
||||
use crate::param::{Param, Params};
|
||||
use crate::pgp::addresses_from_public_key;
|
||||
use crate::securejoin::{
|
||||
ContactId, encrypted_and_signed, insert_into_smtp, verify_sender_by_fingerprint,
|
||||
};
|
||||
@@ -58,14 +60,28 @@ pub(super) async fn start_protocol(context: &Context, invite: QrInvite) -> Resul
|
||||
QrInvite::Broadcast { .. } => {}
|
||||
}
|
||||
|
||||
let has_key = context
|
||||
let public_key_bytes: Option<Vec<u8>> = context
|
||||
.sql
|
||||
.exists(
|
||||
"SELECT COUNT(*) FROM public_keys WHERE fingerprint=?",
|
||||
.query_get_value(
|
||||
"SELECT public_key FROM public_keys WHERE fingerprint=?",
|
||||
(invite.fingerprint().hex(),),
|
||||
)
|
||||
.await?;
|
||||
|
||||
let key_contains_all_invite_addrs = if let Some(public_key_bytes) = public_key_bytes {
|
||||
let public_key = SignedPublicKey::from_slice(&public_key_bytes)?;
|
||||
if let Some(addrs_in_key) = addresses_from_public_key(&public_key) {
|
||||
invite.addrs().iter().all(|a| addrs_in_key.contains(a))
|
||||
} else {
|
||||
// This can happen if the inviter is using an old version of Delta Chat
|
||||
// that doesn't put the relay list into the key.
|
||||
// In this case, we never take the securejoin protocol shortcut, which is fine.
|
||||
false
|
||||
}
|
||||
} else {
|
||||
false
|
||||
};
|
||||
|
||||
// Now start the protocol and initialise the state.
|
||||
{
|
||||
// `joining_chat_id` is `Some` if group chat
|
||||
@@ -97,7 +113,7 @@ pub(super) async fn start_protocol(context: &Context, invite: QrInvite) -> Resul
|
||||
progress: JoinerProgress::Succeeded.into_u16(),
|
||||
});
|
||||
return Ok(joining_chat_id);
|
||||
} else if has_key
|
||||
} else if key_contains_all_invite_addrs
|
||||
&& verify_sender_by_fingerprint(context, invite.fingerprint(), invite.contact_id())
|
||||
.await?
|
||||
{
|
||||
@@ -154,7 +170,7 @@ pub(super) async fn start_protocol(context: &Context, invite: QrInvite) -> Resul
|
||||
QrInvite::Contact { .. } => {
|
||||
// For setup-contact the BobState already ensured the 1:1 chat exists because it is
|
||||
// used to send the handshake messages.
|
||||
if !has_key {
|
||||
if !key_contains_all_invite_addrs {
|
||||
chat::add_info_msg_with_cmd(
|
||||
context,
|
||||
private_chat_id,
|
||||
@@ -310,8 +326,7 @@ pub(crate) async fn send_handshake_message(
|
||||
if invite.is_v3() && matches!(step, BobHandshakeMsg::Request) {
|
||||
// Send a minimal symmetrically-encrypted vc-request-pubkey message
|
||||
let rfc724_mid = create_outgoing_rfc724_mid();
|
||||
let contact = Contact::get_by_id(context, invite.contact_id()).await?;
|
||||
let recipient = contact.get_addr();
|
||||
let recipients = invite.addrs().join(" ");
|
||||
let alice_fp = invite.fingerprint().hex();
|
||||
let auth = invite.authcode();
|
||||
let shared_secret = format!("securejoin/{alice_fp}/{auth}");
|
||||
@@ -327,7 +342,7 @@ pub(crate) async fn send_handshake_message(
|
||||
.await?;
|
||||
|
||||
let msg_id = message::insert_tombstone(context, &rfc724_mid).await?;
|
||||
insert_into_smtp(context, &rfc724_mid, recipient, rendered_message, msg_id).await?;
|
||||
insert_into_smtp(context, &rfc724_mid, &recipients, rendered_message, msg_id).await?;
|
||||
context.scheduler.interrupt_smtp().await;
|
||||
} else {
|
||||
let mut msg = Message {
|
||||
|
||||
@@ -18,6 +18,8 @@ pub enum QrInvite {
|
||||
Contact {
|
||||
contact_id: ContactId,
|
||||
fingerprint: Fingerprint,
|
||||
#[serde(default)]
|
||||
addrs: Vec<String>,
|
||||
invitenumber: String,
|
||||
authcode: String,
|
||||
#[serde(default)]
|
||||
@@ -26,6 +28,8 @@ pub enum QrInvite {
|
||||
Group {
|
||||
contact_id: ContactId,
|
||||
fingerprint: Fingerprint,
|
||||
#[serde(default)]
|
||||
addrs: Vec<String>,
|
||||
name: String,
|
||||
grpid: String,
|
||||
invitenumber: String,
|
||||
@@ -36,6 +40,8 @@ pub enum QrInvite {
|
||||
Broadcast {
|
||||
contact_id: ContactId,
|
||||
fingerprint: Fingerprint,
|
||||
#[serde(default)]
|
||||
addrs: Vec<String>,
|
||||
name: String,
|
||||
grpid: String,
|
||||
invitenumber: String,
|
||||
@@ -92,6 +98,14 @@ impl QrInvite {
|
||||
QrInvite::Broadcast { is_v3, .. } => is_v3,
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) fn addrs(&self) -> &Vec<String> {
|
||||
match self {
|
||||
QrInvite::Contact { addrs, .. } => addrs,
|
||||
QrInvite::Group { addrs, .. } => addrs,
|
||||
QrInvite::Broadcast { addrs, .. } => addrs,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl TryFrom<Qr> for QrInvite {
|
||||
@@ -102,12 +116,14 @@ impl TryFrom<Qr> for QrInvite {
|
||||
Qr::AskVerifyContact {
|
||||
contact_id,
|
||||
fingerprint,
|
||||
addrs,
|
||||
invitenumber,
|
||||
authcode,
|
||||
is_v3,
|
||||
} => Ok(QrInvite::Contact {
|
||||
contact_id,
|
||||
fingerprint,
|
||||
addrs,
|
||||
invitenumber,
|
||||
authcode,
|
||||
is_v3,
|
||||
@@ -117,12 +133,14 @@ impl TryFrom<Qr> for QrInvite {
|
||||
grpid,
|
||||
contact_id,
|
||||
fingerprint,
|
||||
addrs,
|
||||
invitenumber,
|
||||
authcode,
|
||||
is_v3,
|
||||
} => Ok(QrInvite::Group {
|
||||
contact_id,
|
||||
fingerprint,
|
||||
addrs,
|
||||
name: grpname,
|
||||
grpid,
|
||||
invitenumber,
|
||||
@@ -134,6 +152,7 @@ impl TryFrom<Qr> for QrInvite {
|
||||
grpid,
|
||||
contact_id,
|
||||
fingerprint,
|
||||
addrs,
|
||||
authcode,
|
||||
invitenumber,
|
||||
is_v3,
|
||||
@@ -142,6 +161,7 @@ impl TryFrom<Qr> for QrInvite {
|
||||
grpid,
|
||||
contact_id,
|
||||
fingerprint,
|
||||
addrs,
|
||||
authcode,
|
||||
invitenumber,
|
||||
is_v3,
|
||||
|
||||
@@ -599,7 +599,7 @@ async fn send_mdn_rfc724_mid(
|
||||
.ok()
|
||||
})
|
||||
.collect();
|
||||
|
||||
message::insert_tombstone(context, &rendered_msg.rfc724_mid).await?;
|
||||
match smtp_send(context, &recipients, &body, smtp, None).await {
|
||||
SendResult::Success => {
|
||||
if !recipients.is_empty() {
|
||||
|
||||
@@ -2434,6 +2434,28 @@ UPDATE msgs SET state=24 WHERE state=18; -- Change OutPreparing to OutFailed.
|
||||
.await?;
|
||||
}
|
||||
|
||||
inc_and_check(&mut migration_version, 154)?;
|
||||
if dbversion < migration_version {
|
||||
// Recreate imap_markseen with PRIMARY KEY and NOT NULL constraints.
|
||||
// PRIMARY KEY is needed to turn
|
||||
// "DELETE FROM imap_markseen_new WHERE id = ?"
|
||||
// query from SCAN into SEARCH.
|
||||
sql.execute_migration(
|
||||
"
|
||||
CREATE TABLE new_imap_markseen (
|
||||
id INTEGER PRIMARY KEY NOT NULL,
|
||||
FOREIGN KEY(id) REFERENCES imap(id) ON DELETE CASCADE
|
||||
);
|
||||
INSERT OR IGNORE INTO new_imap_markseen (id)
|
||||
SELECT id FROM imap_markseen;
|
||||
DROP TABLE imap_markseen;
|
||||
ALTER TABLE new_imap_markseen RENAME TO imap_markseen;
|
||||
",
|
||||
migration_version,
|
||||
)
|
||||
.await?;
|
||||
}
|
||||
|
||||
let new_version = sql
|
||||
.get_raw_config_int(VERSION_CFG)
|
||||
.await?
|
||||
|
||||
@@ -232,9 +232,7 @@ impl TestContextManager {
|
||||
|
||||
test_context.set_primary_self_addr(new_addr).await.unwrap();
|
||||
// ensure_secret_key_exists() is called during configure
|
||||
crate::e2ee::ensure_secret_key_exists(test_context)
|
||||
.await
|
||||
.unwrap();
|
||||
key::ensure_secret_key_exists(test_context).await.unwrap();
|
||||
|
||||
assert_eq!(
|
||||
test_context.get_primary_self_addr().await.unwrap(),
|
||||
|
||||
@@ -6,7 +6,9 @@ use crate::chat::{self, Chat, add_contact_to_chat, remove_contact_from_chat, sen
|
||||
use crate::config::Config;
|
||||
use crate::constants::Chattype;
|
||||
use crate::contact::{Contact, ContactId};
|
||||
use crate::key;
|
||||
use crate::key::self_fingerprint;
|
||||
use crate::message;
|
||||
use crate::message::{Message, Viewtype};
|
||||
use crate::mimefactory::MimeFactory;
|
||||
use crate::mimeparser::SystemMessage;
|
||||
@@ -18,7 +20,6 @@ use crate::test_utils::{
|
||||
E2EE_INFO_MSGS, TestContext, TestContextManager, get_chat_msg, mark_as_verified,
|
||||
};
|
||||
use crate::tools::SystemTime;
|
||||
use crate::{e2ee, message};
|
||||
|
||||
#[tokio::test(flavor = "multi_thread", worker_threads = 2)]
|
||||
async fn test_verified_oneonone_chat_not_broken_by_classical() {
|
||||
@@ -126,7 +127,7 @@ async fn test_create_verified_oneonone_chat() -> Result<()> {
|
||||
|
||||
let fiona_new = tcm.unconfigured().await;
|
||||
fiona_new.configure_addr("fiona@example.net").await;
|
||||
e2ee::ensure_secret_key_exists(&fiona_new).await?;
|
||||
key::ensure_secret_key_exists(&fiona_new).await?;
|
||||
|
||||
tcm.send_recv(&fiona_new, &alice, "I have a new device")
|
||||
.await;
|
||||
@@ -428,7 +429,7 @@ async fn test_verify_then_verify_again() -> Result<()> {
|
||||
drop(bob);
|
||||
let bob_new = tcm.unconfigured().await;
|
||||
bob_new.configure_addr("bob@example.net").await;
|
||||
e2ee::ensure_secret_key_exists(&bob_new).await?;
|
||||
key::ensure_secret_key_exists(&bob_new).await?;
|
||||
|
||||
tcm.execute_securejoin(&bob_new, &alice).await;
|
||||
assert_verified(&alice, &bob_new).await;
|
||||
|
||||
Reference in New Issue
Block a user