mirror of
https://github.com/chatmail/core.git
synced 2026-10-07 05:30:22 +03:00
Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
0dd4edd4e6 | ||
|
|
0df7cc2612 | ||
|
|
24876f92aa |
@@ -43,7 +43,6 @@ jobs:
|
||||
uses: swatinem/rust-cache@c19371144df3bb44fab255c43d04cbc2ab54d1c4
|
||||
with:
|
||||
save-if: ${{ github.ref == 'refs/heads/main' }}
|
||||
cache-bin: false
|
||||
- name: Run rustfmt
|
||||
run: cargo fmt --all -- --check
|
||||
- name: Run clippy
|
||||
@@ -62,7 +61,7 @@ jobs:
|
||||
with:
|
||||
show-progress: false
|
||||
persist-credentials: false
|
||||
- uses: EmbarkStudios/cargo-deny-action@a531616d8ce3b9177443e48a1159bc945a099823
|
||||
- uses: EmbarkStudios/cargo-deny-action@91bf2b620e09e18d6eb78b92e7861937469acedb
|
||||
with:
|
||||
arguments: --workspace --all-features --locked
|
||||
command: check
|
||||
@@ -97,7 +96,6 @@ jobs:
|
||||
uses: swatinem/rust-cache@c19371144df3bb44fab255c43d04cbc2ab54d1c4
|
||||
with:
|
||||
save-if: ${{ github.ref == 'refs/heads/main' }}
|
||||
cache-bin: false
|
||||
- name: Rustdoc
|
||||
run: cargo doc --document-private-items --no-deps
|
||||
|
||||
@@ -143,10 +141,9 @@ jobs:
|
||||
uses: swatinem/rust-cache@c19371144df3bb44fab255c43d04cbc2ab54d1c4
|
||||
with:
|
||||
save-if: ${{ github.ref == 'refs/heads/main' }}
|
||||
cache-bin: false
|
||||
|
||||
- name: Install nextest
|
||||
uses: taiki-e/install-action@60ae4ce63c7aeb6e96d7f572c1ec7fafbb17ca80
|
||||
uses: taiki-e/install-action@5f57d6cb7cd20b14a8a27f522884c4bc8a187458
|
||||
with:
|
||||
tool: nextest
|
||||
|
||||
@@ -180,10 +177,9 @@ jobs:
|
||||
uses: swatinem/rust-cache@c19371144df3bb44fab255c43d04cbc2ab54d1c4
|
||||
with:
|
||||
save-if: ${{ github.ref == 'refs/heads/main' }}
|
||||
cache-bin: false
|
||||
|
||||
- name: Build C library
|
||||
run: cargo build -p deltachat_ffi --locked
|
||||
run: cargo build -p deltachat_ffi
|
||||
|
||||
- name: Upload C library
|
||||
uses: actions/upload-artifact@v7
|
||||
@@ -209,10 +205,9 @@ jobs:
|
||||
uses: swatinem/rust-cache@c19371144df3bb44fab255c43d04cbc2ab54d1c4
|
||||
with:
|
||||
save-if: ${{ github.ref == 'refs/heads/main' }}
|
||||
cache-bin: false
|
||||
|
||||
- name: Build deltachat-rpc-server
|
||||
run: cargo build -p deltachat-rpc-server --locked
|
||||
run: cargo build -p deltachat-rpc-server
|
||||
|
||||
- name: Upload deltachat-rpc-server
|
||||
uses: actions/upload-artifact@v7
|
||||
|
||||
@@ -34,7 +34,7 @@ jobs:
|
||||
with:
|
||||
show-progress: false
|
||||
persist-credentials: false
|
||||
- uses: cachix/install-nix-action@8aa03977d8d733052d78f4e008a241fd1dbf36b3 # v31.10.6
|
||||
- uses: cachix/install-nix-action@ab739621df7a23f52766f9ccc97f38da6b7af14f # v31.10.5
|
||||
|
||||
- name: Build deltachat-rpc-server binaries
|
||||
run: nix build .#deltachat-rpc-server-${{ matrix.arch }}-linux
|
||||
@@ -58,7 +58,7 @@ jobs:
|
||||
with:
|
||||
show-progress: false
|
||||
persist-credentials: false
|
||||
- uses: cachix/install-nix-action@8aa03977d8d733052d78f4e008a241fd1dbf36b3 # v31.10.6
|
||||
- uses: cachix/install-nix-action@ab739621df7a23f52766f9ccc97f38da6b7af14f # v31.10.5
|
||||
|
||||
- name: Build deltachat-rpc-server wheels
|
||||
run: nix build .#deltachat-rpc-server-${{ matrix.arch }}-linux-wheel
|
||||
@@ -82,7 +82,7 @@ jobs:
|
||||
with:
|
||||
show-progress: false
|
||||
persist-credentials: false
|
||||
- uses: cachix/install-nix-action@8aa03977d8d733052d78f4e008a241fd1dbf36b3 # v31.10.6
|
||||
- uses: cachix/install-nix-action@ab739621df7a23f52766f9ccc97f38da6b7af14f # v31.10.5
|
||||
|
||||
- name: Build deltachat-rpc-server binaries
|
||||
run: nix build .#deltachat-rpc-server-${{ matrix.arch }}
|
||||
@@ -106,7 +106,7 @@ jobs:
|
||||
with:
|
||||
show-progress: false
|
||||
persist-credentials: false
|
||||
- uses: cachix/install-nix-action@8aa03977d8d733052d78f4e008a241fd1dbf36b3 # v31.10.6
|
||||
- uses: cachix/install-nix-action@ab739621df7a23f52766f9ccc97f38da6b7af14f # v31.10.5
|
||||
|
||||
- name: Build deltachat-rpc-server wheels
|
||||
run: nix build .#deltachat-rpc-server-${{ matrix.arch }}-wheel
|
||||
@@ -157,7 +157,7 @@ jobs:
|
||||
with:
|
||||
show-progress: false
|
||||
persist-credentials: false
|
||||
- uses: cachix/install-nix-action@8aa03977d8d733052d78f4e008a241fd1dbf36b3 # v31.10.6
|
||||
- uses: cachix/install-nix-action@ab739621df7a23f52766f9ccc97f38da6b7af14f # v31.10.5
|
||||
|
||||
- name: Build deltachat-rpc-server binaries
|
||||
run: nix build .#deltachat-rpc-server-${{ matrix.arch }}-android
|
||||
@@ -181,7 +181,7 @@ jobs:
|
||||
with:
|
||||
show-progress: false
|
||||
persist-credentials: false
|
||||
- uses: cachix/install-nix-action@8aa03977d8d733052d78f4e008a241fd1dbf36b3 # v31.10.6
|
||||
- uses: cachix/install-nix-action@ab739621df7a23f52766f9ccc97f38da6b7af14f # v31.10.5
|
||||
|
||||
- name: Build deltachat-rpc-server wheels
|
||||
run: nix build .#deltachat-rpc-server-${{ matrix.arch }}-android-wheel
|
||||
@@ -208,7 +208,7 @@ jobs:
|
||||
with:
|
||||
show-progress: false
|
||||
persist-credentials: false
|
||||
- uses: cachix/install-nix-action@8aa03977d8d733052d78f4e008a241fd1dbf36b3 # v31.10.6
|
||||
- uses: cachix/install-nix-action@ab739621df7a23f52766f9ccc97f38da6b7af14f # v31.10.5
|
||||
|
||||
- name: Download Linux aarch64 binary
|
||||
uses: actions/download-artifact@v7
|
||||
|
||||
@@ -25,10 +25,7 @@ jobs:
|
||||
with:
|
||||
node-version: 18.x
|
||||
- name: Add Rust cache
|
||||
uses: swatinem/rust-cache@c19371144df3bb44fab255c43d04cbc2ab54d1c4
|
||||
with:
|
||||
save-if: ${{ github.ref == 'refs/heads/main' }}
|
||||
cache-bin: false
|
||||
uses: Swatinem/rust-cache@c19371144df3bb44fab255c43d04cbc2ab54d1c4
|
||||
- name: npm install
|
||||
working-directory: deltachat-jsonrpc/typescript
|
||||
run: npm install
|
||||
|
||||
@@ -25,7 +25,7 @@ jobs:
|
||||
with:
|
||||
show-progress: false
|
||||
persist-credentials: false
|
||||
- uses: cachix/install-nix-action@8aa03977d8d733052d78f4e008a241fd1dbf36b3 # v31.10.6
|
||||
- uses: cachix/install-nix-action@ab739621df7a23f52766f9ccc97f38da6b7af14f # v31.10.5
|
||||
- run: nix fmt flake.nix -- --check
|
||||
|
||||
build:
|
||||
@@ -63,6 +63,7 @@ jobs:
|
||||
- deltachat-rpc-server-armv7l-linux-wheel
|
||||
- deltachat-rpc-server-i686-linux
|
||||
- deltachat-rpc-server-i686-linux-wheel
|
||||
- deltachat-rpc-server-source
|
||||
- deltachat-rpc-server-win32
|
||||
- deltachat-rpc-server-win32-wheel
|
||||
- deltachat-rpc-server-win64
|
||||
@@ -83,7 +84,7 @@ jobs:
|
||||
with:
|
||||
show-progress: false
|
||||
persist-credentials: false
|
||||
- uses: cachix/install-nix-action@8aa03977d8d733052d78f4e008a241fd1dbf36b3 # v31.10.6
|
||||
- uses: cachix/install-nix-action@ab739621df7a23f52766f9ccc97f38da6b7af14f # v31.10.5
|
||||
- run: nix build .#${{ matrix.installable }}
|
||||
|
||||
build-macos:
|
||||
@@ -104,5 +105,5 @@ jobs:
|
||||
with:
|
||||
show-progress: false
|
||||
persist-credentials: false
|
||||
- uses: cachix/install-nix-action@8aa03977d8d733052d78f4e008a241fd1dbf36b3 # v31.10.6
|
||||
- uses: cachix/install-nix-action@ab739621df7a23f52766f9ccc97f38da6b7af14f # v31.10.5
|
||||
- run: nix build .#${{ matrix.installable }}
|
||||
|
||||
@@ -18,7 +18,7 @@ jobs:
|
||||
with:
|
||||
show-progress: false
|
||||
persist-credentials: false
|
||||
- uses: cachix/install-nix-action@8aa03977d8d733052d78f4e008a241fd1dbf36b3 # v31.10.6
|
||||
- uses: cachix/install-nix-action@ab739621df7a23f52766f9ccc97f38da6b7af14f # v31.10.5
|
||||
- name: Build
|
||||
run: nix build .#deltachat-repl-win64
|
||||
- name: Upload binary
|
||||
|
||||
@@ -41,7 +41,7 @@ jobs:
|
||||
show-progress: false
|
||||
persist-credentials: false
|
||||
fetch-depth: 0 # Fetch history to calculate VCS version number.
|
||||
- uses: cachix/install-nix-action@8aa03977d8d733052d78f4e008a241fd1dbf36b3 # v31.10.6
|
||||
- uses: cachix/install-nix-action@ab739621df7a23f52766f9ccc97f38da6b7af14f # v31.10.5
|
||||
- name: Build Python documentation
|
||||
run: nix build .#python-docs
|
||||
- name: Upload to py.delta.chat
|
||||
@@ -63,7 +63,7 @@ jobs:
|
||||
show-progress: false
|
||||
persist-credentials: false
|
||||
fetch-depth: 0 # Fetch history to calculate VCS version number.
|
||||
- uses: cachix/install-nix-action@8aa03977d8d733052d78f4e008a241fd1dbf36b3 # v31.10.6
|
||||
- uses: cachix/install-nix-action@ab739621df7a23f52766f9ccc97f38da6b7af14f # v31.10.5
|
||||
- name: Build C documentation
|
||||
run: nix build .#docs
|
||||
- name: Upload to c.delta.chat
|
||||
|
||||
@@ -23,4 +23,4 @@ jobs:
|
||||
persist-credentials: false
|
||||
|
||||
- name: Run zizmor
|
||||
uses: zizmorcore/zizmor-action@5f14fd08f7cf1cb1609c1e344975f152c7ee938d # v0.5.6
|
||||
uses: zizmorcore/zizmor-action@b1d7e1fb5de872772f31590499237e7cce841e8e # v0.5.3
|
||||
|
||||
-194
@@ -1,197 +1,5 @@
|
||||
# Changelog
|
||||
|
||||
## [2.51.0] - 2026-05-29
|
||||
|
||||
### Features / Changes
|
||||
|
||||
- Follow certificate check parameter in autoconfig.
|
||||
- Immediately remove all encrypted messages from the server in single-device mode.
|
||||
|
||||
### Fixes
|
||||
|
||||
- Fix syntax error in `only_fetch_mvbox` migration 150 resulting in failure to upgrade for `only_fetch_mvbox` users.
|
||||
- Do not try to resolve proxy IPv6 addresses in square brackets.
|
||||
- Do not fail to receive post-message with status updates for deleted webxdc.
|
||||
- Don't make message `OutDelivered` after successful resending to new broadcast member.
|
||||
|
||||
### Build system
|
||||
|
||||
- nix: fix downloads from crates.io in nix builds.
|
||||
|
||||
### Documentation
|
||||
|
||||
- Fix reference in `delete_expired_imap_messages` comment.
|
||||
|
||||
### Refactor
|
||||
|
||||
- Remove `pre_encrypt_mime_hook`.
|
||||
- Make `should_delete_all_downloaded_messages` non-async.
|
||||
|
||||
### Tests
|
||||
|
||||
- Test IPv6 addresses in HTTP(S) proxies.
|
||||
- Test `bcc_self` in `test_delete_expired_imap_messages`.
|
||||
- Test encrypted messages in `test_delete_expired_imap_messages`.
|
||||
|
||||
### Miscellaneous Tasks
|
||||
|
||||
- Bump version to 2.51.0-dev.
|
||||
- deps: bump zizmorcore/zizmor-action from 0.5.3 to 0.5.6.
|
||||
- deps: bump taiki-e/install-action from 2.78.1 to 2.79.2.
|
||||
|
||||
## [2.50.0] - 2026-05-22
|
||||
|
||||
### API-Changes
|
||||
|
||||
- Add JSON-RPC APIs for location streaming.
|
||||
- [**breaking**] Remove unused config `smtp_certificate_checks`.
|
||||
- Deprecate old server config keys that were replaced by `add_or_update_transport()`.
|
||||
- [**breaking**] remove `dc_delete_all_locations`.
|
||||
- [**breaking**] Remove unused `info_only` option when loading a chatlist ([#8171](https://github.com/chatmail/core/pull/8171)).
|
||||
- [**breaking**] location: avoid repeating module name in function names
|
||||
- [**breaking**] deltachat-rpc-client: remove deprecated `get_fresh_messages_in_arrival_order()`.
|
||||
- Remove unused `set_draft_vcard()` JSON-RPC API.
|
||||
- Remove mostly-unused `sign_unencrypted` config ([#8190](https://github.com/chatmail/core/pull/8190)).
|
||||
|
||||
### Features / Changes
|
||||
|
||||
- [**breaking**] Remove `mvbox_move` and `only_fetch_mvbox` configs. Transports have `folder` configuration to watch the folder other than `INBOX` as a replacement for `only_fetch_mvbox`. Non-chatmail transports no longer watch mvbox, each transport watches exactly one folder now.
|
||||
- Add `force_encryption` config to ignore incoming unencrypted messages and enforce encryption for outgoing messages.
|
||||
- Remove "Delete Messages from Server" (`delete_server_after`) config ([#8240](https://github.com/chatmail/core/pull/8240)).
|
||||
- Remove `show_emails` config.
|
||||
- Remove non-sticker heuristics and `force_sticker()`. UIs should make sure not to send images from gallery such as screenshots as stickers.
|
||||
- Enable PQC (Post-Quantum Cryptography) support for OpenPGP. We do not generate PQC keys yet, this step is needed for forward compatibility.
|
||||
- Resend the last 10 messages to new broadcast member ([#8151](https://github.com/chatmail/core/pull/8151)).
|
||||
- Allow TLS connections with invalid certificate if the key is unchanged.
|
||||
- Add `is_app_sender` and `is_broadcast` contexts for webxdc.
|
||||
- Increase the resolution-limit `WORSE_AVATAR_SIZE` from 128 to 256.
|
||||
- Change multiplier to 7/8 when scaling down avatars.
|
||||
- Add error cause to connectivity view for IMAP errors.
|
||||
- Remove the largely-unused ability to send multiple reactions to one message ([#8131](https://github.com/chatmail/core/pull/8131)).
|
||||
- Don't show non-delivery-notfications in broadcast channels ([#8159](https://github.com/chatmail/core/pull/8159)).
|
||||
- Adapt quota warning to automatic cleanup.
|
||||
- Remove `Content-Description` and `Content-Disposition` from `multipart/encrypted` parts.
|
||||
- Log all connection attempt errors instead of the first one.
|
||||
- Remove workaround for old filtermail (part of chatmail relay) which expected exact number of newlines in OpenPGP messages.
|
||||
- Remove key fingerprint from `Context.get_info()`.
|
||||
- Mask local part of email addresses in `used_transport_settings`.
|
||||
|
||||
### Fixes
|
||||
|
||||
- Trash no-op messages about self being added to groups.
|
||||
- `decide_chat_assignment`: Log correct `post_msg_exists` value.
|
||||
- Don't send `Chat-Group-Name*` headers for InBroadcast-s.
|
||||
- Restart io on transport deletion.
|
||||
- Never remove primary transport when applying `SyncTransports` message.
|
||||
- Set Param::GuaranteeE2ee before preparing message blob ([#8090](https://github.com/chatmail/core/pull/8090)).
|
||||
- `fetch_single_msg()`: Lock `fetch_msgs_mutex` before fetching.
|
||||
- Set dir to "auto" in body tag when converting plain-text to HTML ([#8227](https://github.com/chatmail/core/pull/8227)).
|
||||
- Scale up contacts messaged in groups to `IncomingTo`.
|
||||
- Do not sort prefetched messages by INTERNALDATE.
|
||||
- Don't resort re-sent message to the bottom ([#8145](https://github.com/chatmail/core/pull/8145)).
|
||||
- Ensure that message being sent is added to the bottom ([#8027](https://github.com/chatmail/core/pull/8027)).
|
||||
- Don't receive message if a deletion request was received before ([#8143](https://github.com/chatmail/core/pull/8143)).
|
||||
- Emit `MsgsChanged`, not `IncomingMsg`, for messages only having special parts ([#8157](https://github.com/chatmail/core/pull/8157)).
|
||||
- Generate new pre-message `Message-ID` when forwarding.
|
||||
- use correct dir converting plaintext to HTML ([#8248](https://github.com/chatmail/core/pull/8248)).
|
||||
- hide connectivity HTML quota if not supported.
|
||||
- Delete pre-messages on the server for single-device chatmail transports ([#8240](https://github.com/chatmail/core/pull/8240)).
|
||||
|
||||
### Build system
|
||||
|
||||
- Upgrade rustls-webpki to 0.103.12.
|
||||
- Remove coredeps `Dockerfile`.
|
||||
- Increase MSRV to 1.89.
|
||||
|
||||
### CI
|
||||
|
||||
- Remove Concourse CI pipelines.
|
||||
- Update Rust to 1.95.0.
|
||||
- Do not store Rust cache from PRs.
|
||||
- Set cache-bin to "false" for `swatinem/rust-cache` action.
|
||||
- Use `--locked` flag with `cargo build`.
|
||||
- Upgrade `cargo-deny-action` to v2.0.17.
|
||||
|
||||
### Documentation
|
||||
|
||||
- Update `echobot_no_hooks.py` example.
|
||||
- Discourage `into()`, `try_into()` and `parse()` ([#8180](https://github.com/chatmail/core/pull/8180)).
|
||||
- Remove outdated comment about "quota warning" device message.
|
||||
- Update README.md: Use ci-chatmail instead of nine ([#8238](https://github.com/chatmail/core/pull/8238)).
|
||||
- spec: remove AEAP section.
|
||||
|
||||
### Performance
|
||||
|
||||
- Enable `clippy::large_futures` lint.
|
||||
- Stop sending locations concurrently.
|
||||
- Set location for all accounts in parallel.
|
||||
- `is_self_addr()`: Employ the config cache to optimize for `ConfiguredAddr` passed.
|
||||
|
||||
### Refactor
|
||||
|
||||
- Get rid of `MessageState::{OutPreparing,OutMdnRcvd}` in the db.
|
||||
- Make HTML parser non-async.
|
||||
- Replace `HashSet` with `BTreeSet`.
|
||||
- Rename `EnteredLoginParam::load()` and save() to `load_legacy()` and `save_legacy()`.
|
||||
- Remove unnecessary async block in `dc_set_location`.
|
||||
- Remove unused Authentication-Results parsing ([#8172](https://github.com/chatmail/core/pull/8172)).
|
||||
- Remove mostly-unused function `get_secondary_self_addrs()` ([#8173](https://github.com/chatmail/core/pull/8173)).
|
||||
- Use `self_fingerprint()` where it makes sense ([#8174](https://github.com/chatmail/core/pull/8174)).
|
||||
- Split `is_sending_locations_to_chat()` into two functions.
|
||||
- Use regular functions rather than FromStr impls ([#8178](https://github.com/chatmail/core/pull/8178)).
|
||||
- Make `Fingerprint` not implement `Display` ([#8177](https://github.com/chatmail/core/pull/8177)).
|
||||
- Don't temporarily extend `signatures` for signed-only messages.
|
||||
- Use some more let..else.
|
||||
- Remove outdated comment.
|
||||
- Un-nest `handle_edit_delete`.
|
||||
- Drop support for replacing partial download stubs.
|
||||
|
||||
### Tests
|
||||
|
||||
- Use `displayname` instead of `show_emails` for config cache test.
|
||||
- Remove unused test data related to Authentication-Result parsing ([#8175](https://github.com/chatmail/core/pull/8175)).
|
||||
- `EventTracker::get_matching_opt`: Return the first matching event, not last.
|
||||
- Set email addresses explicitly for the test accounts.
|
||||
- Use encrypted messages in more tests.
|
||||
- Add `TestContext.allow_unencrypted()`.
|
||||
- Online test for legacy Secure-Join key request.
|
||||
|
||||
### Miscellaneous Tasks
|
||||
|
||||
- cargo: bump rand from 0.9.2 to 0.9.3.
|
||||
- deps: bump taiki-e/install-action from 2.64.0 to 2.74.0.
|
||||
- add exception for RUSTSEC-2026-0097.
|
||||
- deps: bump swatinem/rust-cache from 2.8.2 to 2.9.1.
|
||||
- cargo: upgrade rand 0.8.5 to rand 0.8.6.
|
||||
- deps: bump zizmorcore/zizmor-action from 0.5.2 to 0.5.3.
|
||||
- deps: bump pypa/gh-action-pypi-publish from 1.13.0 to 1.14.0.
|
||||
- update provider database.
|
||||
- cargo: update rustls-webpki to 0.103.13.
|
||||
- cargo: bump openssl from 0.10.72 to 0.10.78.
|
||||
- Apply rustmft after the previous commit.
|
||||
- json-rpc: deprecate `send_sticker` ([#8189](https://github.com/chatmail/core/pull/8189)).
|
||||
- deps: bump cachix/install-nix-action from 31.9.1 to 31.10.5.
|
||||
- deps: bump taiki-e/install-action from 2.75.10 to 2.75.19.
|
||||
- update astral-tokio-tar from 0.6.0 to 0.6.1.
|
||||
- add exceptions for hickory-proto 0.25.2 in deny.toml.
|
||||
- cargo: bump blake3 from 1.8.3 to 1.8.5.
|
||||
- deny.toml: add cpufeatures duplicate dependency exception.
|
||||
- cargo: bump hyper from 1.8.1 to 1.9.0.
|
||||
- cargo: bump tokio from 1.50.0 to 1.52.1.
|
||||
- cargo: bump libc from 0.2.184 to 0.2.186.
|
||||
- cargo: bump colorutils-rs from 0.7.6 to 0.8.0.
|
||||
- cargo: bump data-encoding from 2.10.0 to 2.11.0.
|
||||
- cargo: bump openssl from 0.10.78 to 0.10.79.
|
||||
- deps: bump taiki-e/install-action from 2.75.19 to 2.77.1.
|
||||
- deps: bump cachix/install-nix-action from 31.10.5 to 31.10.6.
|
||||
- allow passing arguments to scripts/clippy.sh.
|
||||
- clippy::useless-borrows-in-formatting fixes.
|
||||
- update zerocopy from 0.7.32 to 0.7.35.
|
||||
- upgrade astral-tokio-tar to 0.6.2 ([#8255](https://github.com/chatmail/core/pull/8255)).
|
||||
- deps: bump EmbarkStudios/cargo-deny-action from 2.0.17 to 2.0.18.
|
||||
- cargo: bump openssl from 0.10.79 to 0.10.80.
|
||||
- deps: bump taiki-e/install-action from 2.77.1 to 2.78.1.
|
||||
|
||||
## [2.49.0] - 2026-04-13
|
||||
|
||||
### Features / Changes
|
||||
@@ -8295,5 +8103,3 @@ https://github.com/chatmail/core/pulls?q=is%3Apr+is%3Aclosed
|
||||
[2.47.0]: https://github.com/chatmail/core/compare/v2.46.0..v2.47.0
|
||||
[2.48.0]: https://github.com/chatmail/core/compare/v2.47.0..v2.48.0
|
||||
[2.49.0]: https://github.com/chatmail/core/compare/v2.48.0..v2.49.0
|
||||
[2.50.0]: https://github.com/chatmail/core/compare/v2.49.0..v2.50.0
|
||||
[2.51.0]: https://github.com/chatmail/core/compare/v2.50.0..v2.51.0
|
||||
|
||||
Generated
+35
-178
@@ -194,9 +194,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "astral-tokio-tar"
|
||||
version = "0.6.2"
|
||||
version = "0.6.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "cb50a7aae84a03bf55b067832bc376f4961b790c97e64d3eacee97d389b90277"
|
||||
checksum = "4ce73b17c62717c4b6a9af10b43e87c578b0cac27e00666d48304d3b7d2c0693"
|
||||
dependencies = [
|
||||
"filetime",
|
||||
"futures-core",
|
||||
@@ -391,28 +391,6 @@ version = "1.4.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "ace50bade8e6234aa140d9a2f552bbee1db4d353f69b8217bc503490fc1a9f26"
|
||||
|
||||
[[package]]
|
||||
name = "aws-lc-rs"
|
||||
version = "1.17.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "5ec2f1fc3ec205783a5da9a7e6c1509cc69dedf09a1949e412c1e18469326d00"
|
||||
dependencies = [
|
||||
"aws-lc-sys",
|
||||
"zeroize",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "aws-lc-sys"
|
||||
version = "0.41.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "1a2f9779ce85b93ab6170dd940ad0169b5766ff848247aff13bb788b832fe3f4"
|
||||
dependencies = [
|
||||
"cc",
|
||||
"cmake",
|
||||
"dunce",
|
||||
"fs_extra",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "backon"
|
||||
version = "1.5.0"
|
||||
@@ -785,13 +763,10 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "cc"
|
||||
version = "1.2.63"
|
||||
version = "1.2.14"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "556e016178bb5662a08681bbe0f00f8e17631781a4dfc8c45e466e4b185ec27f"
|
||||
checksum = "0c3d1b2e905a3a7b00a6141adb0e4c0bb941d11caf55349d863942a1cc44e3c9"
|
||||
dependencies = [
|
||||
"find-msvc-tools",
|
||||
"jobserver",
|
||||
"libc",
|
||||
"shlex",
|
||||
]
|
||||
|
||||
@@ -945,15 +920,6 @@ dependencies = [
|
||||
"digest",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "cmake"
|
||||
version = "0.1.58"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "c0f78a02292a74a88ac736019ab962ece0bc380e3f977bf72e376c5d78ff0678"
|
||||
dependencies = [
|
||||
"cc",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "cobs"
|
||||
version = "0.2.3"
|
||||
@@ -1350,7 +1316,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "deltachat"
|
||||
version = "2.51.0-dev"
|
||||
version = "2.50.0-dev"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"astral-tokio-tar",
|
||||
@@ -1459,7 +1425,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "deltachat-jsonrpc"
|
||||
version = "2.51.0-dev"
|
||||
version = "2.50.0-dev"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"async-channel 2.5.0",
|
||||
@@ -1480,7 +1446,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "deltachat-repl"
|
||||
version = "2.51.0-dev"
|
||||
version = "2.50.0-dev"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"deltachat",
|
||||
@@ -1496,7 +1462,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "deltachat-rpc-server"
|
||||
version = "2.51.0-dev"
|
||||
version = "2.50.0-dev"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"deltachat",
|
||||
@@ -1525,7 +1491,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "deltachat_ffi"
|
||||
version = "2.51.0-dev"
|
||||
version = "2.50.0-dev"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"deltachat",
|
||||
@@ -1710,7 +1676,7 @@ dependencies = [
|
||||
"libc",
|
||||
"option-ext",
|
||||
"redox_users",
|
||||
"windows-sys 0.59.0",
|
||||
"windows-sys 0.61.1",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -1760,12 +1726,6 @@ dependencies = [
|
||||
"zeroize",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "dunce"
|
||||
version = "1.0.5"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "92773504d58c093f6de2459af4af33faa518c13451eb8f2b5698ed3d36e7c813"
|
||||
|
||||
[[package]]
|
||||
name = "dyn-clone"
|
||||
version = "1.0.18"
|
||||
@@ -2091,12 +2051,6 @@ dependencies = [
|
||||
"windows-sys 0.59.0",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "find-msvc-tools"
|
||||
version = "0.1.9"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "5baebc0774151f905a1a2cc41989300b1e6fbb29aff0ceffa1064fdd3088d582"
|
||||
|
||||
[[package]]
|
||||
name = "fixedbitset"
|
||||
version = "0.5.7"
|
||||
@@ -2154,12 +2108,6 @@ dependencies = [
|
||||
name = "format-flowed"
|
||||
version = "1.0.0"
|
||||
|
||||
[[package]]
|
||||
name = "fs_extra"
|
||||
version = "1.3.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "42703706b716c37f96a77aea830392ad231f44c9e9a67872fa5548707e11b11c"
|
||||
|
||||
[[package]]
|
||||
name = "funty"
|
||||
version = "2.0.0"
|
||||
@@ -2660,25 +2608,6 @@ dependencies = [
|
||||
"libm",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "hybrid-array"
|
||||
version = "0.2.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "f2d35805454dc9f8662a98d6d61886ffe26bd465f5960e0e55345c70d5c0d2a9"
|
||||
dependencies = [
|
||||
"typenum",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "hybrid-array"
|
||||
version = "0.3.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "891d15931895091dea5c47afa5b3c9a01ba634b311919fd4d41388fa0e3d76af"
|
||||
dependencies = [
|
||||
"typenum",
|
||||
"zeroize",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "hyper"
|
||||
version = "1.9.0"
|
||||
@@ -2733,7 +2662,7 @@ dependencies = [
|
||||
"hyper",
|
||||
"libc",
|
||||
"pin-project-lite",
|
||||
"socket2 0.5.9",
|
||||
"socket2 0.6.3",
|
||||
"tokio",
|
||||
"tower-service",
|
||||
"tracing",
|
||||
@@ -3286,16 +3215,6 @@ version = "1.0.10"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "b1a46d1a171d865aa5f83f92695765caa047a9b4cbae2cbf37dbd613a793fd4c"
|
||||
|
||||
[[package]]
|
||||
name = "jobserver"
|
||||
version = "0.1.34"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "9afb3de4395d6b3e67a780b6de64b51c978ecf11cb9a462c66be7d4ca9039d33"
|
||||
dependencies = [
|
||||
"getrandom 0.3.3",
|
||||
"libc",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "js-sys"
|
||||
version = "0.3.77"
|
||||
@@ -3338,16 +3257,6 @@ dependencies = [
|
||||
"cpufeatures 0.2.17",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "kem"
|
||||
version = "0.3.0-pre.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "2b8645470337db67b01a7f966decf7d0bafedbae74147d33e641c67a91df239f"
|
||||
dependencies = [
|
||||
"rand_core 0.6.4",
|
||||
"zeroize",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "lazy_static"
|
||||
version = "1.5.0"
|
||||
@@ -3436,9 +3345,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "log"
|
||||
version = "0.4.31"
|
||||
version = "0.4.29"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "113b30b4cd05f7c06868fdb2854f66a7b9fece9a48425351cd532e810d74024f"
|
||||
checksum = "5e5032e24019045c762d3c0f28f5b6b8bbf38563a65908389bf7978758920897"
|
||||
|
||||
[[package]]
|
||||
name = "loom"
|
||||
@@ -3561,35 +3470,6 @@ dependencies = [
|
||||
"windows-sys 0.61.1",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "ml-dsa"
|
||||
version = "0.0.4"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "ac4a46643af2001eafebcc37031fc459eb72d45057aac5d7a15b00046a2ad6db"
|
||||
dependencies = [
|
||||
"const-oid",
|
||||
"hybrid-array 0.3.1",
|
||||
"num-traits",
|
||||
"pkcs8",
|
||||
"rand_core 0.6.4",
|
||||
"sha3",
|
||||
"signature",
|
||||
"zeroize",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "ml-kem"
|
||||
version = "0.2.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "8de49b3df74c35498c0232031bb7e85f9389f913e2796169c8ab47a53993a18f"
|
||||
dependencies = [
|
||||
"hybrid-array 0.2.3",
|
||||
"kem",
|
||||
"rand_core 0.6.4",
|
||||
"sha3",
|
||||
"zeroize",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "moka"
|
||||
version = "0.12.10"
|
||||
@@ -3880,7 +3760,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]]
|
||||
@@ -4061,14 +3941,15 @@ checksum = "c08d65885ee38876c4f86fa503fb49d7b507c2b62552df7c70b2fce627e06381"
|
||||
|
||||
[[package]]
|
||||
name = "openssl"
|
||||
version = "0.10.80"
|
||||
version = "0.10.78"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "a45fa2aa886c42762255da344f0a0d313e254066c46aad76f300c3d3da62d967"
|
||||
checksum = "f38c4372413cdaaf3cc79dd92d29d7d9f5ab09b51b10dded508fb90bb70b9222"
|
||||
dependencies = [
|
||||
"bitflags 2.11.0",
|
||||
"cfg-if",
|
||||
"foreign-types",
|
||||
"libc",
|
||||
"once_cell",
|
||||
"openssl-macros",
|
||||
"openssl-sys",
|
||||
]
|
||||
@@ -4101,9 +3982,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "openssl-sys"
|
||||
version = "0.9.116"
|
||||
version = "0.9.114"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "f28a22dc7140cda5f096e5e7724a6962ca81a7f8bfd2979f9b18c11af56318c4"
|
||||
checksum = "13ce1245cd07fcc4cfdb438f7507b0c7e4f3849a69fd84d52374c66d83741bb6"
|
||||
dependencies = [
|
||||
"cc",
|
||||
"libc",
|
||||
@@ -4325,8 +4206,6 @@ dependencies = [
|
||||
"k256",
|
||||
"log",
|
||||
"md-5",
|
||||
"ml-dsa",
|
||||
"ml-kem",
|
||||
"nom 8.0.0",
|
||||
"num-bigint-dig",
|
||||
"num-traits",
|
||||
@@ -4345,7 +4224,6 @@ dependencies = [
|
||||
"sha2",
|
||||
"sha3",
|
||||
"signature",
|
||||
"slh-dsa",
|
||||
"smallvec",
|
||||
"snafu",
|
||||
"twofish",
|
||||
@@ -4365,18 +4243,18 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "pin-project"
|
||||
version = "1.1.13"
|
||||
version = "1.1.11"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "2466b2336ed02bcdca6b294417127b90ec92038d1d5c4fbeac971a922e0e0924"
|
||||
checksum = "f1749c7ed4bcaf4c3d0a3efc28538844fb29bcdd7d2b67b2be7e20ba861ff517"
|
||||
dependencies = [
|
||||
"pin-project-internal",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "pin-project-internal"
|
||||
version = "1.1.13"
|
||||
version = "1.1.11"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "c96395f0a926bc13b1c17622aaddda1ecb55d49c8f1bf9777e4d877800a43f8b"
|
||||
checksum = "d9b20ed30f105399776b9c883e68e536ef602a16ae6f596d2c473591d6ad64c6"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
@@ -5281,7 +5159,7 @@ dependencies = [
|
||||
"errno",
|
||||
"libc",
|
||||
"linux-raw-sys 0.12.1",
|
||||
"windows-sys 0.52.0",
|
||||
"windows-sys 0.61.1",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -5290,7 +5168,6 @@ version = "0.23.37"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "758025cb5fccfd3bc2fd74708fd4682be41d99e5dff73c377c0646c6012c73a4"
|
||||
dependencies = [
|
||||
"aws-lc-rs",
|
||||
"log",
|
||||
"once_cell",
|
||||
"ring",
|
||||
@@ -5336,7 +5213,6 @@ version = "0.103.13"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "61c429a8649f110dddef65e2a5ad240f747e85f7758a6bccc7e5777bd33f756e"
|
||||
dependencies = [
|
||||
"aws-lc-rs",
|
||||
"ring",
|
||||
"rustls-pki-types",
|
||||
"untrusted",
|
||||
@@ -5581,9 +5457,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "serde_json"
|
||||
version = "1.0.150"
|
||||
version = "1.0.149"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "e8014e44b4736ed0538adeecded0fce2a272f22dc9578a7eb6b2d9993c74cfb9"
|
||||
checksum = "83fc039473c5595ace860d8c4fafa220ff474b3fc6bfdb4293327f1a37e94d86"
|
||||
dependencies = [
|
||||
"itoa",
|
||||
"memchr",
|
||||
@@ -5759,9 +5635,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "shlex"
|
||||
version = "2.0.1"
|
||||
version = "1.3.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "f8fadd59c855ef2080decdef8ff161eb6661b86933c9d82e5ba29dc602a55aba"
|
||||
checksum = "0fda2ff0d084019ba4d7c6f371c95d8fd75ce3524c3cb8fb653a3023f6323e64"
|
||||
|
||||
[[package]]
|
||||
name = "signal-hook-registry"
|
||||
@@ -5812,25 +5688,6 @@ dependencies = [
|
||||
"autocfg",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "slh-dsa"
|
||||
version = "0.0.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "bd2f20f4049197e03db1104a6452f4d9e96665d79f880198dce4a7026ba5f267"
|
||||
dependencies = [
|
||||
"const-oid",
|
||||
"digest",
|
||||
"hmac",
|
||||
"hybrid-array 0.3.1",
|
||||
"pkcs8",
|
||||
"rand_core 0.6.4",
|
||||
"sha2",
|
||||
"sha3",
|
||||
"signature",
|
||||
"typenum",
|
||||
"zerocopy",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "smallvec"
|
||||
version = "1.15.1"
|
||||
@@ -6147,7 +6004,7 @@ dependencies = [
|
||||
"getrandom 0.3.3",
|
||||
"once_cell",
|
||||
"rustix 1.1.4",
|
||||
"windows-sys 0.52.0",
|
||||
"windows-sys 0.61.1",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -6295,9 +6152,9 @@ checksum = "1f3ccbac311fea05f86f61904b462b55fb3df8837a366dfc601a0161d0532f20"
|
||||
|
||||
[[package]]
|
||||
name = "tokio"
|
||||
version = "1.52.3"
|
||||
version = "1.52.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "8fc7f01b389ac15039e4dc9531aa973a135d7a4135281b12d7c1bc79fd57fffe"
|
||||
checksum = "b67dee974fe86fd92cc45b7a95fdd2f99a36a6d7b0d431a231178d3d670bbcc6"
|
||||
dependencies = [
|
||||
"bytes",
|
||||
"libc",
|
||||
@@ -7569,9 +7426,9 @@ checksum = "2164e798d9e3d84ee2c91139ace54638059a3b23e361f5c11781c2c6459bde0f"
|
||||
|
||||
[[package]]
|
||||
name = "zerocopy"
|
||||
version = "0.7.35"
|
||||
version = "0.7.32"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "1b9b4fd18abc82b8136838da5d50bae7bdea537c574d8dc1a34ed098d6c166f0"
|
||||
checksum = "74d4d3961e53fa4c9a25a8637fc2bfaf2595b3d3ae34875568a5cf64787716be"
|
||||
dependencies = [
|
||||
"byteorder",
|
||||
"zerocopy-derive",
|
||||
@@ -7579,9 +7436,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "zerocopy-derive"
|
||||
version = "0.7.35"
|
||||
version = "0.7.32"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "fa4f8080344d4671fb4e831a13ad1e68092748387dfc4f55e356242fae12ce3e"
|
||||
checksum = "9ce1b18ccd8e73a9321186f97e46f9f04b778851177567b1975109d26a08d2a6"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
|
||||
+4
-4
@@ -1,6 +1,6 @@
|
||||
[package]
|
||||
name = "deltachat"
|
||||
version = "2.51.0-dev"
|
||||
version = "2.50.0-dev"
|
||||
edition = "2024"
|
||||
license = "MPL-2.0"
|
||||
rust-version = "1.89"
|
||||
@@ -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.19.0", default-features = false }
|
||||
pin-project = "1"
|
||||
qrcodegen = "1.7.0"
|
||||
quick-xml = { version = "0.39", features = ["escape-html"] }
|
||||
@@ -101,9 +101,9 @@ tagger = "4.3.4"
|
||||
textwrap = "0.16.2"
|
||||
thiserror = { workspace = true }
|
||||
tokio-io-timeout = "1.2.1"
|
||||
tokio-rustls = { version = "0.26.2", default-features = false, features = ["aws-lc-rs", "tls12"] }
|
||||
tokio-rustls = { version = "0.26.2", default-features = false }
|
||||
tokio-stream = { version = "0.1.17", features = ["fs"] }
|
||||
astral-tokio-tar = { version = "0.6.2", default-features = false }
|
||||
astral-tokio-tar = { version = "0.6.1", default-features = false }
|
||||
tokio-util = { workspace = true }
|
||||
tokio = { workspace = true, features = ["fs", "rt-multi-thread", "macros"] }
|
||||
toml = "0.9"
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
[package]
|
||||
name = "deltachat_ffi"
|
||||
version = "2.51.0-dev"
|
||||
version = "2.50.0-dev"
|
||||
description = "Deltachat FFI"
|
||||
edition = "2018"
|
||||
readme = "README.md"
|
||||
|
||||
@@ -413,6 +413,11 @@ char* dc_get_blobdir (const dc_context_t* context);
|
||||
* Messages in the "saved messages" chat (see dc_chat_is_self_talk()) are skipped.
|
||||
* Messages are deleted whether they were seen or not, the UI should clearly point that out.
|
||||
* See also dc_estimate_deletion_cnt().
|
||||
* - `delete_server_after` = 0=do not delete messages from server automatically (default),
|
||||
* 1=delete messages directly after receiving from server, mvbox is skipped.
|
||||
* >1=seconds, after which messages are deleted automatically from the server, mvbox is used as defined.
|
||||
* "Saved messages" are deleted from the server as well as emails, the UI should clearly point that out.
|
||||
* See also dc_estimate_deletion_cnt().
|
||||
* - `media_quality` = DC_MEDIA_QUALITY_BALANCED (0) =
|
||||
* good outgoing images/videos/voice quality at reasonable sizes (default)
|
||||
* DC_MEDIA_QUALITY_WORSE (1)
|
||||
@@ -482,7 +487,6 @@ char* dc_get_blobdir (const dc_context_t* context);
|
||||
* 0 = Everybody (except explicitly blocked contacts),
|
||||
* 1 = Contacts (default, does not include contact requests),
|
||||
* 2 = Nobody (calls never result in a notification).
|
||||
* - `force_encryption` = 1 (default) to force encryption, 0 to allow unencrypted messages.
|
||||
*
|
||||
* Also, there are configs that are only needed
|
||||
* if you want to use the deprecated dc_configure() API, such as:
|
||||
@@ -1457,16 +1461,16 @@ dc_chatlist_t* dc_get_similar_chatlist (dc_context_t* context, uint32_t ch
|
||||
|
||||
/**
|
||||
* Estimate the number of messages that will be deleted
|
||||
* by the dc_set_config()-option `delete_device_after`.
|
||||
* by the dc_set_config()-options `delete_device_after` or `delete_server_after`.
|
||||
* This is typically used to show the estimated impact to the user
|
||||
* before actually enabling deletion of old messages.
|
||||
*
|
||||
* @memberof dc_context_t
|
||||
* @param context The context object as returned from dc_context_new().
|
||||
* @param from_server Deprecated, pass 0 here
|
||||
* @param from_server 1=Estimate deletion count for server, 0=Estimate deletion count for device
|
||||
* @param seconds Count messages older than the given number of seconds.
|
||||
* @return Number of messages that are older than the given number of seconds.
|
||||
* Messages in the "Saved Messages" chat are not counted as they will not be deleted automatically.
|
||||
* Messages in the "saved messages" folder are not counted as they will not be deleted automatically.
|
||||
*/
|
||||
int dc_estimate_deletion_cnt (dc_context_t* context, int from_server, int64_t seconds);
|
||||
|
||||
@@ -3999,6 +4003,8 @@ int dc_msg_get_viewtype (const dc_msg_t* msg);
|
||||
* Marked as read on IMAP and MDN may be sent. Use dc_markseen_msgs() to mark messages as being seen.
|
||||
*
|
||||
* Outgoing message states:
|
||||
* - @ref DC_STATE_OUT_PREPARING - For files which need time to be prepared before they can be sent,
|
||||
* the message enters this state before @ref DC_STATE_OUT_PENDING. Deprecated.
|
||||
* - @ref DC_STATE_OUT_DRAFT - Message saved as draft using dc_set_draft()
|
||||
* - @ref DC_STATE_OUT_PENDING - The user has pressed the "send" button but the
|
||||
* message is not yet sent and is pending in some way. Maybe we're offline (no checkmark).
|
||||
@@ -5583,6 +5589,13 @@ int64_t dc_lot_get_timestamp (const dc_lot_t* lot);
|
||||
*/
|
||||
#define DC_STATE_IN_SEEN 16
|
||||
|
||||
/**
|
||||
* Outgoing message being prepared. See dc_msg_get_state() for details.
|
||||
*
|
||||
* @deprecated 2024-12-07
|
||||
*/
|
||||
#define DC_STATE_OUT_PREPARING 18
|
||||
|
||||
/**
|
||||
* Outgoing message drafted. See dc_msg_get_state() for details.
|
||||
*/
|
||||
@@ -7052,6 +7065,11 @@ void dc_event_unref(dc_event_t* event);
|
||||
/// `%1$s` will be replaced by a possibly more detailed, typically english, error description.
|
||||
#define DC_STR_ERROR 112
|
||||
|
||||
/// "Not supported by your provider."
|
||||
///
|
||||
/// Used in the connectivity view.
|
||||
#define DC_STR_NOT_SUPPORTED_BY_PROVIDER 113
|
||||
|
||||
/// "Messages"
|
||||
///
|
||||
/// Used as a subtitle in quota context; can be plural always.
|
||||
|
||||
@@ -275,7 +275,7 @@ pub unsafe extern "C" fn dc_get_config(
|
||||
.strdup()
|
||||
} else {
|
||||
match config::Config::from_str(&key)
|
||||
.with_context(|| format!("Invalid key {key:?}"))
|
||||
.with_context(|| format!("Invalid key {:?}", &key))
|
||||
.log_err(ctx)
|
||||
{
|
||||
Ok(key) => ctx
|
||||
|
||||
@@ -230,6 +230,7 @@ pub enum LotState {
|
||||
MsgInFresh = 10,
|
||||
MsgInNoticed = 13,
|
||||
MsgInSeen = 16,
|
||||
MsgOutPreparing = 18,
|
||||
MsgOutDraft = 19,
|
||||
MsgOutPending = 20,
|
||||
MsgOutFailed = 24,
|
||||
@@ -245,6 +246,7 @@ impl From<MessageState> for LotState {
|
||||
InFresh => LotState::MsgInFresh,
|
||||
InNoticed => LotState::MsgInNoticed,
|
||||
InSeen => LotState::MsgInSeen,
|
||||
OutPreparing => LotState::MsgOutPreparing,
|
||||
OutDraft => LotState::MsgOutDraft,
|
||||
OutPending => LotState::MsgOutPending,
|
||||
OutFailed => LotState::MsgOutFailed,
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
[package]
|
||||
name = "deltachat-jsonrpc"
|
||||
version = "2.51.0-dev"
|
||||
version = "2.50.0-dev"
|
||||
description = "DeltaChat JSON-RPC API"
|
||||
edition = "2021"
|
||||
license = "MPL-2.0"
|
||||
|
||||
@@ -735,19 +735,10 @@ impl CommandApi {
|
||||
Ok(msg_ids)
|
||||
}
|
||||
|
||||
/// Estimates the number of messages that will be deleted
|
||||
/// by the `set_config()`-option `delete_device_after`.
|
||||
///
|
||||
/// Estimate the number of messages that will be deleted
|
||||
/// by the set_config()-options `delete_device_after` or `delete_server_after`.
|
||||
/// This is typically used to show the estimated impact to the user
|
||||
/// before actually enabling deletion of old messages.
|
||||
///
|
||||
/// Messages in the "Saved Messages" chat are not counted as they will not be deleted automatically.
|
||||
///
|
||||
/// Parameters:
|
||||
/// - `from_server`: Deprecated, pass `false` here
|
||||
/// - `seconds`: Count messages older than the given number of seconds.
|
||||
///
|
||||
/// Returns the number of messages that are older than the given number of seconds.
|
||||
async fn estimate_auto_deletion_count(
|
||||
&self,
|
||||
account_id: u32,
|
||||
@@ -1106,6 +1097,9 @@ impl CommandApi {
|
||||
/// because the word "channel" already appears a lot in the code,
|
||||
/// which would make it hard to grep for it.
|
||||
///
|
||||
/// After creation, the chat contains no recipients and is in _unpromoted_ state;
|
||||
/// see [`CommandApi::create_group_chat`] for more information on the unpromoted state.
|
||||
///
|
||||
/// Returns the created chat's id.
|
||||
async fn create_broadcast(&self, account_id: u32, chat_name: String) -> Result<u32> {
|
||||
let ctx = self.get_context(account_id).await?;
|
||||
|
||||
@@ -54,5 +54,5 @@
|
||||
},
|
||||
"type": "module",
|
||||
"types": "dist/deltachat.d.ts",
|
||||
"version": "2.51.0-dev"
|
||||
"version": "2.50.0-dev"
|
||||
}
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
[package]
|
||||
name = "deltachat-repl"
|
||||
version = "2.51.0-dev"
|
||||
version = "2.50.0-dev"
|
||||
license = "MPL-2.0"
|
||||
edition = "2021"
|
||||
repository = "https://github.com/chatmail/core"
|
||||
|
||||
@@ -122,7 +122,7 @@ async fn poke_spec(context: &Context, spec: Option<&str>) -> bool {
|
||||
let name_f = entry.file_name();
|
||||
let name = name_f.to_string_lossy();
|
||||
if name.ends_with(".eml") {
|
||||
let path_plus_name = format!("{real_spec}/{name}");
|
||||
let path_plus_name = format!("{}/{}", &real_spec, name);
|
||||
println!("Import: {path_plus_name}");
|
||||
if poke_eml_file(context, Path::new(&path_plus_name))
|
||||
.await
|
||||
@@ -133,11 +133,11 @@ async fn poke_spec(context: &Context, spec: Option<&str>) -> bool {
|
||||
}
|
||||
}
|
||||
} else {
|
||||
eprintln!("Import: Cannot open directory {real_spec:?}.");
|
||||
eprintln!("Import: Cannot open directory \"{}\".", &real_spec);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
println!("Import: {read_cnt} items read from {real_spec:?}.");
|
||||
println!("Import: {} items read from \"{}\".", read_cnt, &real_spec);
|
||||
if read_cnt > 0 {
|
||||
context.emit_msgs_changed_without_ids();
|
||||
}
|
||||
@@ -179,7 +179,7 @@ async fn log_msg(context: &Context, prefix: impl AsRef<str>, msg: &Message) {
|
||||
msg.get_id(),
|
||||
if msg.get_showpadlock() { "🔒" } else { "" },
|
||||
if msg.has_location() { "📍" } else { "" },
|
||||
contact_name,
|
||||
&contact_name,
|
||||
contact_id,
|
||||
msgtext,
|
||||
if msg.has_html() { "[HAS-HTML]️" } else { "" },
|
||||
@@ -221,7 +221,7 @@ async fn log_msg(context: &Context, prefix: impl AsRef<str>, msg: &Message) {
|
||||
},
|
||||
statestr,
|
||||
downloadstate,
|
||||
temp2,
|
||||
&temp2,
|
||||
);
|
||||
}
|
||||
|
||||
@@ -561,7 +561,7 @@ pub async fn cmdline(context: Context, line: &str, chat_id: &mut ChatId) -> Resu
|
||||
.map_or_else(String::new, |prefix| format!("{prefix}: ")),
|
||||
summary.text,
|
||||
statestr,
|
||||
timestr,
|
||||
×tr,
|
||||
if chat.is_sending_locations() {
|
||||
"📍"
|
||||
} else {
|
||||
@@ -1236,9 +1236,11 @@ pub async fn cmdline(context: Context, line: &str, chat_id: &mut ChatId) -> Resu
|
||||
"estimatedeletion" => {
|
||||
ensure!(!arg1.is_empty(), "Argument <seconds> missing");
|
||||
let seconds = arg1.parse()?;
|
||||
let from_server = false;
|
||||
let device_cnt = message::estimate_deletion_cnt(&context, from_server, seconds).await?;
|
||||
println!("estimated count of messages older than {seconds} seconds: {device_cnt}");
|
||||
let device_cnt = message::estimate_deletion_cnt(&context, false, seconds).await?;
|
||||
let server_cnt = message::estimate_deletion_cnt(&context, true, seconds).await?;
|
||||
println!(
|
||||
"estimated count of messages older than {seconds} seconds:\non device: {device_cnt}\non server: {server_cnt}"
|
||||
);
|
||||
}
|
||||
"" => (),
|
||||
_ => bail!("Unknown command: \"{arg0}\" type ? for help."),
|
||||
|
||||
@@ -432,7 +432,7 @@ async fn handle_cmd(
|
||||
{
|
||||
println!("Open the following url, set mail_pw to the generated token and server_flags to 2:\n{oauth2_url}");
|
||||
} else {
|
||||
println!("OAuth2 not available for {addr}.");
|
||||
println!("OAuth2 not available for {}.", &addr);
|
||||
}
|
||||
} else {
|
||||
println!("oauth2: set addr first.");
|
||||
|
||||
@@ -29,7 +29,7 @@ $ pip install .
|
||||
|
||||
1. Build `deltachat-rpc-server` with `cargo build -p deltachat-rpc-server`.
|
||||
2. Install tox `pip install -U tox`
|
||||
3. Run `CHATMAIL_DOMAIN=ci-chatmail.testrun.org PATH="../target/debug:$PATH" tox`.
|
||||
3. Run `CHATMAIL_DOMAIN=nine.testrun.org PATH="../target/debug:$PATH" tox`.
|
||||
|
||||
Additional arguments to `tox` are passed to pytest, e.g. `tox -- -s` does not capture test output.
|
||||
|
||||
|
||||
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
|
||||
|
||||
[project]
|
||||
name = "deltachat-rpc-client"
|
||||
version = "2.51.0-dev"
|
||||
version = "2.50.0-dev"
|
||||
license = "MPL-2.0"
|
||||
description = "Python client for Delta Chat core JSON-RPC interface"
|
||||
classifiers = [
|
||||
|
||||
@@ -340,6 +340,9 @@ class Account:
|
||||
because the word "channel" already appears a lot in the code,
|
||||
which would make it hard to grep for it.
|
||||
|
||||
After creation, the chat contains no recipients and is in _unpromoted_ state;
|
||||
see `create_group()` for more information on the unpromoted state.
|
||||
|
||||
Returns the created chat.
|
||||
"""
|
||||
return Chat(self, self._rpc.create_broadcast(self.id, name))
|
||||
|
||||
@@ -190,6 +190,7 @@ class MessageState(IntEnum):
|
||||
IN_FRESH = 10
|
||||
IN_NOTICED = 13
|
||||
IN_SEEN = 16
|
||||
OUT_PREPARING = 18
|
||||
OUT_DRAFT = 19
|
||||
OUT_PENDING = 20
|
||||
OUT_FAILED = 24
|
||||
|
||||
@@ -87,7 +87,6 @@ def test_delivery_status_failed(acfactory: ACFactory) -> None:
|
||||
Test change status on chatlistitem when status changes failed
|
||||
"""
|
||||
(alice,) = acfactory.get_online_accounts(1)
|
||||
alice.set_config("force_encryption", "0")
|
||||
|
||||
invalid_contact = alice.create_contact("example@example.com", "invalid address")
|
||||
invalid_chat = alice.get_chat_by_id(alice._rpc.create_chat_by_contact_id(alice.id, invalid_contact.id))
|
||||
|
||||
@@ -16,7 +16,7 @@ def test_install_venv_and_use_other_core(tmp_path, get_core_python_env):
|
||||
|
||||
|
||||
@pytest.mark.parametrize("version", ["2.24.0"])
|
||||
def test_qr_setup_contact(acfactory, alice_and_remote_bob, version) -> None:
|
||||
def test_qr_setup_contact(alice_and_remote_bob, version) -> None:
|
||||
"""Test other-core Bob profile can do securejoin with Alice on current core."""
|
||||
alice, alice_contact_bob, remote_eval = alice_and_remote_bob(version)
|
||||
|
||||
@@ -33,15 +33,6 @@ def test_qr_setup_contact(acfactory, alice_and_remote_bob, version) -> None:
|
||||
# Test that Bob verified Alice's profile.
|
||||
assert remote_eval("bob_contact_alice.get_snapshot().is_verified")
|
||||
|
||||
# Test that Bob can also scan a QR code
|
||||
# of Alice for which the key is not known yet.
|
||||
# For the test above Bob already knew the key from a vCard.
|
||||
alice2 = acfactory.get_online_account()
|
||||
qr_code = alice2.get_qr_code()
|
||||
remote_eval(f"bob.secure_join({qr_code!r})")
|
||||
remote_eval("bob.wait_for_securejoin_joiner_success()")
|
||||
alice2.wait_for_securejoin_inviter_success()
|
||||
|
||||
|
||||
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."""
|
||||
|
||||
@@ -14,13 +14,10 @@ def test_moved_markseen(acfactory, direct_imap, log):
|
||||
ac2.add_or_update_transport({"addr": addr, "password": password})
|
||||
ac2.bring_online()
|
||||
|
||||
# Make sure that messages are not immediately auto-deleted on the server:
|
||||
ac1.set_config("bcc_self", "1")
|
||||
ac2.set_config("bcc_self", "1")
|
||||
|
||||
log.section("ac2: creating DeltaChat folder")
|
||||
ac2_direct_imap = direct_imap(ac2)
|
||||
ac2_direct_imap.create_folder("DeltaChat")
|
||||
ac2.set_config("delete_server_after", "0")
|
||||
ac2.set_config("sync_msgs", "0") # Do not send a sync message when accepting a contact request.
|
||||
|
||||
ac2.add_or_update_transport({"addr": addr, "password": password, "imapFolder": "DeltaChat"})
|
||||
@@ -60,9 +57,11 @@ def test_moved_markseen(acfactory, direct_imap, log):
|
||||
def test_markseen_message_and_mdn(acfactory, direct_imap):
|
||||
ac1, ac2 = acfactory.get_online_accounts(2)
|
||||
|
||||
# Make sure that messages are not immediately auto-deleted on the server:
|
||||
ac1.set_config("bcc_self", "1")
|
||||
ac2.set_config("bcc_self", "1")
|
||||
for ac in ac1, ac2:
|
||||
ac.set_config("delete_server_after", "0")
|
||||
|
||||
# Do not send BCC to self, we only want to test MDN on ac1.
|
||||
ac1.set_config("bcc_self", "0")
|
||||
|
||||
acfactory.get_accepted_chat(ac1, ac2).send_text("hi")
|
||||
msg = ac2.wait_for_incoming_msg()
|
||||
@@ -82,18 +81,17 @@ def test_markseen_message_and_mdn(acfactory, direct_imap):
|
||||
ac1_direct_imap.select_folder("INBOX")
|
||||
ac2_direct_imap.select_folder("INBOX")
|
||||
|
||||
# Check that the mdn and original message is marked as seen
|
||||
assert len(list(ac1_direct_imap.conn.fetch(AND(seen=True), mark_seen=False))) == 2
|
||||
assert len(list(ac2_direct_imap.conn.fetch(AND(seen=True), mark_seen=False))) == 2
|
||||
# Check that the mdn is marked as seen
|
||||
assert len(list(ac1_direct_imap.conn.fetch(AND(seen=True), mark_seen=False))) == 1
|
||||
# Check original message is marked as seen
|
||||
assert len(list(ac2_direct_imap.conn.fetch(AND(seen=True), mark_seen=False))) == 1
|
||||
|
||||
|
||||
def test_trash_multiple_messages(acfactory, direct_imap, log):
|
||||
ac1, ac2 = acfactory.get_online_accounts(2)
|
||||
ac2.stop_io()
|
||||
|
||||
# Make sure that messages are not immediately auto-deleted on the server:
|
||||
ac2.set_config("bcc_self", "1")
|
||||
|
||||
ac2.set_config("delete_server_after", "0")
|
||||
ac2.set_config("sync_msgs", "0")
|
||||
|
||||
ac2.start_io()
|
||||
|
||||
@@ -4,29 +4,39 @@ from deltachat_rpc_client import EventType
|
||||
from deltachat_rpc_client.const import MessageState
|
||||
|
||||
|
||||
def test_bcc_self_is_enabled_when_setting_up_second_device(acfactory):
|
||||
def test_bcc_self_delete_server_after_defaults(acfactory):
|
||||
"""Test default values for bcc_self and delete_server_after."""
|
||||
ac = acfactory.get_online_account()
|
||||
|
||||
# Initially after getting online
|
||||
# the setting bcc_self is set to 0 because there is only one device
|
||||
# and delete_server_after is "1", meaning immediate deletion.
|
||||
assert ac.get_config("bcc_self") == "0"
|
||||
assert ac.get_config("delete_server_after") == "1"
|
||||
|
||||
# Setup a second device.
|
||||
ac_clone = ac.clone()
|
||||
ac_clone.bring_online()
|
||||
|
||||
# Second device setup enables bcc_self.
|
||||
# Second device setup
|
||||
# enables bcc_self and changes default delete_server_after.
|
||||
assert ac.get_config("bcc_self") == "1"
|
||||
assert ac_clone.get_config("bcc_self") == "1"
|
||||
assert ac.get_config("delete_server_after") == "0"
|
||||
|
||||
# Test manually disabling bcc_self
|
||||
assert ac_clone.get_config("bcc_self") == "1"
|
||||
assert ac_clone.get_config("delete_server_after") == "0"
|
||||
|
||||
# Manually disabling bcc_self
|
||||
# also restores the default for delete_server_after.
|
||||
ac.set_config("bcc_self", "0")
|
||||
assert ac.get_config("bcc_self") == "0"
|
||||
assert ac.get_config("delete_server_after") == "1"
|
||||
|
||||
# Cloning the account again enables bcc_self again
|
||||
# Cloning the account again enables bcc_self
|
||||
# even though it was manually disabled.
|
||||
ac_clone = ac.clone()
|
||||
assert ac.get_config("bcc_self") == "1"
|
||||
assert ac.get_config("delete_server_after") == "0"
|
||||
|
||||
|
||||
def test_one_account_send_bcc_setting(acfactory, log, direct_imap):
|
||||
|
||||
@@ -91,15 +91,7 @@ def test_lowercase_address(acfactory) -> None:
|
||||
assert account.list_transports()[0]["addr"] == addr
|
||||
|
||||
param = account.get_info()["used_transport_settings"]
|
||||
|
||||
domain = addr.rsplit("@")[-1]
|
||||
domain_upper = addr_upper.rsplit("@")[-1]
|
||||
assert domain in param
|
||||
assert domain_upper not in param
|
||||
|
||||
# Whole address should not appear in the info,
|
||||
# does not matter if uppercase or lowercase.
|
||||
assert addr not in param
|
||||
assert addr in param
|
||||
assert addr_upper not in param
|
||||
|
||||
|
||||
@@ -1240,12 +1232,10 @@ def test_leave_and_delete_group(acfactory, log):
|
||||
|
||||
|
||||
def test_immediate_autodelete(acfactory, direct_imap, log):
|
||||
"""
|
||||
`bcc_self` is off by default,
|
||||
so that messages are supposed to be immediately autodeleted
|
||||
"""
|
||||
ac1, ac2 = acfactory.get_online_accounts(2)
|
||||
assert ac1.get_config("bcc_self") == "0"
|
||||
|
||||
# "1" means delete immediately, while "0" means do not delete
|
||||
ac2.set_config("delete_server_after", "1")
|
||||
|
||||
log.section("ac1: create chat with ac2")
|
||||
chat1 = ac1.create_chat(ac2)
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
[package]
|
||||
name = "deltachat-rpc-server"
|
||||
version = "2.51.0-dev"
|
||||
version = "2.50.0-dev"
|
||||
description = "DeltaChat JSON-RPC server"
|
||||
edition = "2021"
|
||||
readme = "README.md"
|
||||
|
||||
@@ -15,5 +15,5 @@
|
||||
},
|
||||
"type": "module",
|
||||
"types": "index.d.ts",
|
||||
"version": "2.51.0-dev"
|
||||
"version": "2.50.0-dev"
|
||||
}
|
||||
|
||||
@@ -43,12 +43,7 @@ ignore = [
|
||||
# hickory-proto 0.25.2 quadratic complexity issue.
|
||||
# Dependency of iroh 0.35.0, cannot be updated as of 2026-05-02.
|
||||
# <https://rustsec.org/advisories/RUSTSEC-2026-0119>
|
||||
"RUSTSEC-2026-0119",
|
||||
|
||||
# Timing side channel in ml-dsa dependency of rPGP.
|
||||
# We enable PQC for encryption rather than signatures.
|
||||
# <https://rustsec.org/advisories/RUSTSEC-2025-0144>
|
||||
"RUSTSEC-2025-0144",
|
||||
"RUSTSEC-2026-0119"
|
||||
]
|
||||
|
||||
[bans]
|
||||
@@ -67,7 +62,6 @@ skip = [
|
||||
{ name = "getrandom", version = "0.2.12" },
|
||||
{ name = "heck", version = "0.4.1" },
|
||||
{ name = "http", version = "0.2.12" },
|
||||
{ name = "hybrid-array", version = "0.2.3" },
|
||||
{ name = "linux-raw-sys", version = "0.4.14" },
|
||||
{ name = "lru", version = "0.12.5" },
|
||||
{ name = "netlink-packet-route", version = "0.17.1" },
|
||||
|
||||
Generated
+108
-43
@@ -3,19 +3,15 @@
|
||||
"android": {
|
||||
"inputs": {
|
||||
"devshell": "devshell",
|
||||
"flake-utils": [
|
||||
"flake-utils"
|
||||
],
|
||||
"nixpkgs": [
|
||||
"nixpkgs"
|
||||
]
|
||||
"flake-utils": "flake-utils",
|
||||
"nixpkgs": "nixpkgs"
|
||||
},
|
||||
"locked": {
|
||||
"lastModified": 1779918845,
|
||||
"narHash": "sha256-FbpOOBg15L7X6NWWmTKbSdccnH59Jq53wWmAO37d2Q8=",
|
||||
"lastModified": 1731356359,
|
||||
"narHash": "sha256-vYqJnu6jotmWpPT4DgzHVdvNIZcKZCIUqS8QaptsZA0=",
|
||||
"owner": "tadfisher",
|
||||
"repo": "android-nixpkgs",
|
||||
"rev": "105c093afc8c8fbeea98f8e398403f93043eba17",
|
||||
"rev": "c028ead7e88edb2e94cd7c90ee37593f63ae494a",
|
||||
"type": "github"
|
||||
},
|
||||
"original": {
|
||||
@@ -32,11 +28,11 @@
|
||||
]
|
||||
},
|
||||
"locked": {
|
||||
"lastModified": 1768818222,
|
||||
"narHash": "sha256-460jc0+CZfyaO8+w8JNtlClB2n4ui1RbHfPTLkpwhU8=",
|
||||
"lastModified": 1728330715,
|
||||
"narHash": "sha256-xRJ2nPOXb//u1jaBnDP56M7v5ldavjbtR6lfGqSvcKg=",
|
||||
"owner": "numtide",
|
||||
"repo": "devshell",
|
||||
"rev": "255a2b1725a20d060f566e4755dbf571bbbb5f76",
|
||||
"rev": "dd6b80932022cea34a019e2bb32f6fa9e494dfef",
|
||||
"type": "github"
|
||||
},
|
||||
"original": {
|
||||
@@ -47,17 +43,15 @@
|
||||
},
|
||||
"fenix": {
|
||||
"inputs": {
|
||||
"nixpkgs": [
|
||||
"nixpkgs"
|
||||
],
|
||||
"nixpkgs": "nixpkgs_2",
|
||||
"rust-analyzer-src": "rust-analyzer-src"
|
||||
},
|
||||
"locked": {
|
||||
"lastModified": 1779876442,
|
||||
"narHash": "sha256-O25HomVNmdROO13PEQ3Ran8Hq5EsyLmVn8Gb8JvJtJE=",
|
||||
"lastModified": 1763361733,
|
||||
"narHash": "sha256-ka7dpwH3HIXCyD2wl5F7cPLeRbqZoY2ullALsvxdPt8=",
|
||||
"owner": "nix-community",
|
||||
"repo": "fenix",
|
||||
"rev": "2eff81fc84390a35e1565395ae945d9394856824",
|
||||
"rev": "6c8d48e3b0ae371b19ac1485744687b788e80193",
|
||||
"type": "github"
|
||||
},
|
||||
"original": {
|
||||
@@ -71,11 +65,29 @@
|
||||
"systems": "systems"
|
||||
},
|
||||
"locked": {
|
||||
"lastModified": 1731533236,
|
||||
"narHash": "sha256-l0KFg5HjrsfsO/JpG+r7fRrqm12kzFHyUHqHCVpMMbI=",
|
||||
"lastModified": 1726560853,
|
||||
"narHash": "sha256-X6rJYSESBVr3hBoH0WbKE5KvhPU5bloyZ2L4K60/fPQ=",
|
||||
"owner": "numtide",
|
||||
"repo": "flake-utils",
|
||||
"rev": "11707dc2f618dd54ca8739b309ec4fc024de578b",
|
||||
"rev": "c1dfcf08411b08f6b8615f7d8971a2bfa81d5e8a",
|
||||
"type": "github"
|
||||
},
|
||||
"original": {
|
||||
"owner": "numtide",
|
||||
"repo": "flake-utils",
|
||||
"type": "github"
|
||||
}
|
||||
},
|
||||
"flake-utils_2": {
|
||||
"inputs": {
|
||||
"systems": "systems_2"
|
||||
},
|
||||
"locked": {
|
||||
"lastModified": 1726560853,
|
||||
"narHash": "sha256-X6rJYSESBVr3hBoH0WbKE5KvhPU5bloyZ2L4K60/fPQ=",
|
||||
"owner": "numtide",
|
||||
"repo": "flake-utils",
|
||||
"rev": "c1dfcf08411b08f6b8615f7d8971a2bfa81d5e8a",
|
||||
"type": "github"
|
||||
},
|
||||
"original": {
|
||||
@@ -86,35 +98,29 @@
|
||||
},
|
||||
"naersk": {
|
||||
"inputs": {
|
||||
"fenix": [
|
||||
"fenix"
|
||||
],
|
||||
"nixpkgs": [
|
||||
"nixpkgs"
|
||||
]
|
||||
"nixpkgs": "nixpkgs_3"
|
||||
},
|
||||
"locked": {
|
||||
"lastModified": 1779912356,
|
||||
"narHash": "sha256-yj5O6vmAj+OfhTQMiUwhmQRP0HAII3BxEI6zuY6h/5k=",
|
||||
"lastModified": 1721727458,
|
||||
"narHash": "sha256-r/xppY958gmZ4oTfLiHN0ZGuQ+RSTijDblVgVLFi1mw=",
|
||||
"owner": "nix-community",
|
||||
"repo": "naersk",
|
||||
"rev": "33eaf5c72a67db15073322d26cd342c443556214",
|
||||
"rev": "3fb418eaf352498f6b6c30592e3beb63df42ef11",
|
||||
"type": "github"
|
||||
},
|
||||
"original": {
|
||||
"owner": "nix-community",
|
||||
"ref": "pull/391/head",
|
||||
"repo": "naersk",
|
||||
"type": "github"
|
||||
}
|
||||
},
|
||||
"nix-filter": {
|
||||
"locked": {
|
||||
"lastModified": 1757882181,
|
||||
"narHash": "sha256-+cCxYIh2UNalTz364p+QYmWHs0P+6wDhiWR4jDIKQIU=",
|
||||
"lastModified": 1730207686,
|
||||
"narHash": "sha256-SCHiL+1f7q9TAnxpasriP6fMarWE5H43t25F5/9e28I=",
|
||||
"owner": "numtide",
|
||||
"repo": "nix-filter",
|
||||
"rev": "59c44d1909c72441144b93cf0f054be7fe764de5",
|
||||
"rev": "776e68c1d014c3adde193a18db9d738458cd2ba4",
|
||||
"type": "github"
|
||||
},
|
||||
"original": {
|
||||
@@ -125,16 +131,60 @@
|
||||
},
|
||||
"nixpkgs": {
|
||||
"locked": {
|
||||
"lastModified": 1779931091,
|
||||
"narHash": "sha256-gc8NEz7a++7OQPGvMv+zIjXCec1PO38XRXZRa3m97ew=",
|
||||
"lastModified": 1731139594,
|
||||
"narHash": "sha256-IigrKK3vYRpUu+HEjPL/phrfh7Ox881er1UEsZvw9Q4=",
|
||||
"owner": "NixOS",
|
||||
"repo": "nixpkgs",
|
||||
"rev": "76612b17c0ce71689921ca12d9ffdc9c23ce40b2",
|
||||
"type": "github"
|
||||
},
|
||||
"original": {
|
||||
"owner": "NixOS",
|
||||
"ref": "nixos-unstable",
|
||||
"repo": "nixpkgs",
|
||||
"type": "github"
|
||||
}
|
||||
},
|
||||
"nixpkgs_2": {
|
||||
"locked": {
|
||||
"lastModified": 1762977756,
|
||||
"narHash": "sha256-4PqRErxfe+2toFJFgcRKZ0UI9NSIOJa+7RXVtBhy4KE=",
|
||||
"owner": "nixos",
|
||||
"repo": "nixpkgs",
|
||||
"rev": "3052ddf0614791c1869384a868248be5607a309f",
|
||||
"rev": "c5ae371f1a6a7fd27823bc500d9390b38c05fa55",
|
||||
"type": "github"
|
||||
},
|
||||
"original": {
|
||||
"owner": "nixos",
|
||||
"ref": "master",
|
||||
"ref": "nixos-unstable",
|
||||
"repo": "nixpkgs",
|
||||
"type": "github"
|
||||
}
|
||||
},
|
||||
"nixpkgs_3": {
|
||||
"locked": {
|
||||
"lastModified": 0,
|
||||
"narHash": "sha256-Dqg6si5CqIzm87sp57j5nTaeBbWhHFaVyG7V6L8k3lY=",
|
||||
"path": "/nix/store/zq2axpgzd5kykk1v446rkffj3bxa2m2h-source",
|
||||
"type": "path"
|
||||
},
|
||||
"original": {
|
||||
"id": "nixpkgs",
|
||||
"type": "indirect"
|
||||
}
|
||||
},
|
||||
"nixpkgs_4": {
|
||||
"locked": {
|
||||
"lastModified": 1747179050,
|
||||
"narHash": "sha256-qhFMmDkeJX9KJwr5H32f1r7Prs7XbQWtO0h3V0a0rFY=",
|
||||
"owner": "nixos",
|
||||
"repo": "nixpkgs",
|
||||
"rev": "adaa24fbf46737f3f1b5497bf64bae750f82942e",
|
||||
"type": "github"
|
||||
},
|
||||
"original": {
|
||||
"owner": "nixos",
|
||||
"ref": "nixos-unstable",
|
||||
"repo": "nixpkgs",
|
||||
"type": "github"
|
||||
}
|
||||
@@ -143,20 +193,20 @@
|
||||
"inputs": {
|
||||
"android": "android",
|
||||
"fenix": "fenix",
|
||||
"flake-utils": "flake-utils",
|
||||
"flake-utils": "flake-utils_2",
|
||||
"naersk": "naersk",
|
||||
"nix-filter": "nix-filter",
|
||||
"nixpkgs": "nixpkgs"
|
||||
"nixpkgs": "nixpkgs_4"
|
||||
}
|
||||
},
|
||||
"rust-analyzer-src": {
|
||||
"flake": false,
|
||||
"locked": {
|
||||
"lastModified": 1779827300,
|
||||
"narHash": "sha256-J6pHxKoZzWCrAvOVInwBcYYWix/NWwM10Ad+i29Qc5s=",
|
||||
"lastModified": 1762860488,
|
||||
"narHash": "sha256-rMfWMCOo/pPefM2We0iMBLi2kLBAnYoB9thi4qS7uk4=",
|
||||
"owner": "rust-lang",
|
||||
"repo": "rust-analyzer",
|
||||
"rev": "c3af07ad84d68adc5e652e86f0c20009caa29014",
|
||||
"rev": "2efc80078029894eec0699f62ec8d5c1a56af763",
|
||||
"type": "github"
|
||||
},
|
||||
"original": {
|
||||
@@ -180,6 +230,21 @@
|
||||
"repo": "default",
|
||||
"type": "github"
|
||||
}
|
||||
},
|
||||
"systems_2": {
|
||||
"locked": {
|
||||
"lastModified": 1681028828,
|
||||
"narHash": "sha256-Vy1rq5AaRuLzOxct8nz4T6wlgyUR7zLU309k9mBC768=",
|
||||
"owner": "nix-systems",
|
||||
"repo": "default",
|
||||
"rev": "da67096a3b9bf56a91d16901293e51ba5b49a27e",
|
||||
"type": "github"
|
||||
},
|
||||
"original": {
|
||||
"owner": "nix-systems",
|
||||
"repo": "default",
|
||||
"type": "github"
|
||||
}
|
||||
}
|
||||
},
|
||||
"root": "root",
|
||||
|
||||
@@ -2,16 +2,11 @@
|
||||
description = "Chatmail core";
|
||||
inputs = {
|
||||
fenix.url = "github:nix-community/fenix";
|
||||
fenix.inputs.nixpkgs.follows = "nixpkgs";
|
||||
flake-utils.url = "github:numtide/flake-utils";
|
||||
naersk.url = "github:nix-community/naersk/pull/391/head";
|
||||
naersk.inputs.nixpkgs.follows = "nixpkgs";
|
||||
naersk.inputs.fenix.follows = "fenix";
|
||||
naersk.url = "github:nix-community/naersk";
|
||||
nix-filter.url = "github:numtide/nix-filter";
|
||||
nixpkgs.url = "github:nixos/nixpkgs/master";
|
||||
nixpkgs.url = "github:nixos/nixpkgs/nixos-unstable";
|
||||
android.url = "github:tadfisher/android-nixpkgs";
|
||||
android.inputs.nixpkgs.follows = "nixpkgs";
|
||||
android.inputs.flake-utils.follows = "flake-utils";
|
||||
};
|
||||
outputs = { self, nixpkgs, flake-utils, nix-filter, naersk, fenix, android }:
|
||||
flake-utils.lib.eachDefaultSystem (system:
|
||||
@@ -138,8 +133,6 @@
|
||||
];
|
||||
depsBuildBuild = [
|
||||
pkgsWin64.stdenv.cc
|
||||
];
|
||||
buildInputs = [
|
||||
pkgsWin64.windows.pthreads
|
||||
];
|
||||
auditable = false; # Avoid cargo-auditable failures.
|
||||
@@ -150,8 +143,6 @@
|
||||
CARGO_BUILD_RUSTFLAGS = [
|
||||
"-C"
|
||||
"linker=${TARGET_CC}"
|
||||
"-L"
|
||||
"native=${pkgsWin64.windows.pthreads}/lib"
|
||||
];
|
||||
|
||||
CC = "${pkgsWin64.stdenv.cc}/bin/${pkgsWin64.stdenv.cc.targetPrefix}cc";
|
||||
@@ -189,8 +180,7 @@
|
||||
};
|
||||
})).overrideAttrs (oldAttr: {
|
||||
configureFlags = oldAttr.configureFlags ++ [
|
||||
"--disable-sjlj-exceptions"
|
||||
"--with-dwarf2"
|
||||
"--disable-sjlj-exceptions --with-dwarf2"
|
||||
];
|
||||
})
|
||||
);
|
||||
@@ -206,8 +196,6 @@
|
||||
];
|
||||
depsBuildBuild = [
|
||||
winCC
|
||||
];
|
||||
buildInputs = [
|
||||
pkgsWin32.windows.pthreads
|
||||
];
|
||||
auditable = false; # Avoid cargo-auditable failures.
|
||||
@@ -218,8 +206,6 @@
|
||||
CARGO_BUILD_RUSTFLAGS = [
|
||||
"-C"
|
||||
"linker=${TARGET_CC}"
|
||||
"-L"
|
||||
"native=${pkgsWin32.windows.pthreads}/lib"
|
||||
];
|
||||
|
||||
CC = "${winCC}/bin/${winCC.targetPrefix}cc";
|
||||
@@ -518,6 +504,22 @@
|
||||
'';
|
||||
};
|
||||
|
||||
# Source package for deltachat-rpc-server.
|
||||
# Fake package that downloads Linux version,
|
||||
# needed to install deltachat-rpc-server on Android with `pip`.
|
||||
deltachat-rpc-server-source =
|
||||
pkgs.stdenv.mkDerivation {
|
||||
pname = "deltachat-rpc-server-source";
|
||||
version = manifest.version;
|
||||
src = pkgs.lib.cleanSource ./.;
|
||||
nativeBuildInputs = [
|
||||
pkgs.python3
|
||||
pkgs.python3Packages.wheel
|
||||
];
|
||||
buildPhase = ''python3 scripts/wheel-rpc-server.py source deltachat_rpc_server-${manifest.version}.tar.gz'';
|
||||
installPhase = ''mkdir -p $out; cp -av deltachat_rpc_server-${manifest.version}.tar.gz $out'';
|
||||
};
|
||||
|
||||
deltachat-rpc-client =
|
||||
pkgs.python3Packages.buildPythonPackage {
|
||||
pname = "deltachat-rpc-client";
|
||||
@@ -560,7 +562,7 @@
|
||||
deltachat-python
|
||||
deltachat-rpc-client
|
||||
pkgs.python3Packages.breathe
|
||||
pkgs.python3Packages.sphinx-rtd-theme
|
||||
pkgs.python3Packages.sphinx_rtd_theme
|
||||
];
|
||||
nativeBuildInputs = [ pkgs.sphinx ];
|
||||
buildPhase = ''sphinx-build -b html -a python/doc/ dist/html'';
|
||||
|
||||
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
|
||||
|
||||
[project]
|
||||
name = "deltachat"
|
||||
version = "2.51.0-dev"
|
||||
version = "2.50.0-dev"
|
||||
license = "MPL-2.0"
|
||||
description = "Python bindings for the Delta Chat Core library using CFFI against the Rust-implemented libdeltachat"
|
||||
readme = "README.rst"
|
||||
|
||||
@@ -271,6 +271,15 @@ class Chat:
|
||||
sent out. This is the same object as was passed in, which
|
||||
has been modified with the new state of the core.
|
||||
"""
|
||||
if msg.is_out_preparing():
|
||||
assert msg.id != 0
|
||||
# get a fresh copy of dc_msg, the core needs it
|
||||
maybe_msg = Message.from_db(self.account, msg.id)
|
||||
if maybe_msg is not None:
|
||||
msg = maybe_msg
|
||||
else:
|
||||
raise ValueError("message does not exist")
|
||||
|
||||
sent_id = lib.dc_send_msg(self.account._dc_context, self.id, msg._dc_msg)
|
||||
if sent_id == 0:
|
||||
raise ValueError("message could not be sent")
|
||||
@@ -324,6 +333,26 @@ class Chat:
|
||||
raise ValueError("message could not be sent")
|
||||
return Message.from_db(self.account, sent_id)
|
||||
|
||||
def send_prepared(self, message):
|
||||
"""send a previously prepared message.
|
||||
|
||||
:param message: a :class:`Message` instance previously returned by
|
||||
:meth:`prepare_file`.
|
||||
:raises ValueError: if message can not be sent.
|
||||
:returns: a :class:`deltachat.message.Message` instance as sent out.
|
||||
"""
|
||||
assert message.id != 0 and message.is_out_preparing()
|
||||
# get a fresh copy of dc_msg, the core needs it
|
||||
msg = Message.from_db(self.account, message.id)
|
||||
|
||||
# pass 0 as chat-id because core-docs say it's ok when out-preparing
|
||||
sent_id = lib.dc_send_msg(self.account._dc_context, 0, msg._dc_msg)
|
||||
if sent_id == 0:
|
||||
raise ValueError("message could not be sent")
|
||||
assert sent_id == msg.id
|
||||
# modify message in place to avoid bad state for the caller
|
||||
msg._dc_msg = Message.from_db(self.account, sent_id)._dc_msg
|
||||
|
||||
def set_draft(self, message):
|
||||
"""set message as draft.
|
||||
|
||||
|
||||
@@ -351,12 +351,17 @@ class Message:
|
||||
def is_outgoing(self):
|
||||
"""Return True if Message is outgoing."""
|
||||
return lib.dc_msg_get_state(self._dc_msg) in (
|
||||
const.DC_STATE_OUT_PREPARING,
|
||||
const.DC_STATE_OUT_PENDING,
|
||||
const.DC_STATE_OUT_FAILED,
|
||||
const.DC_STATE_OUT_MDN_RCVD,
|
||||
const.DC_STATE_OUT_DELIVERED,
|
||||
)
|
||||
|
||||
def is_out_preparing(self):
|
||||
"""Return True if Message is outgoing, but its file is being prepared."""
|
||||
return self._msgstate == const.DC_STATE_OUT_PREPARING
|
||||
|
||||
def is_out_pending(self):
|
||||
"""Return True if Message is outgoing, but is pending (no single checkmark)."""
|
||||
return self._msgstate == const.DC_STATE_OUT_PENDING
|
||||
|
||||
@@ -521,6 +521,7 @@ class ACFactory:
|
||||
assert "addr" in configdict and "mail_pw" in configdict, configdict
|
||||
configdict.setdefault("bcc_self", False)
|
||||
configdict.setdefault("sync_msgs", False)
|
||||
configdict.setdefault("delete_server_after", 0)
|
||||
ac.update_config(configdict)
|
||||
self._acsetup._account2config[ac] = configdict
|
||||
self._preconfigure_key(ac)
|
||||
|
||||
@@ -298,6 +298,73 @@ def test_use_new_verified_group_after_going_online(acfactory, data, tmp_path, lp
|
||||
assert msg_in.text == msg_out.text
|
||||
|
||||
|
||||
def test_verified_group_vs_delete_server_after(acfactory, tmp_path, lp):
|
||||
"""Test for the issue #4346:
|
||||
- User is added to a verified group.
|
||||
- First device of the user downloads "member added" from the group.
|
||||
- First device removes "member added" from the server.
|
||||
- Some new messages are sent to the group.
|
||||
- Second device comes online, receives these new messages.
|
||||
The result is an unverified group with unverified members.
|
||||
- First device re-gossips Autocrypt keys to the group.
|
||||
- Now the second device has all members and group verified.
|
||||
"""
|
||||
ac1, ac2 = acfactory.get_online_accounts(2)
|
||||
acfactory.remove_preconfigured_keys()
|
||||
ac2_offl = acfactory.new_online_configuring_account(cloned_from=ac2)
|
||||
for ac in [ac2, ac2_offl]:
|
||||
ac.set_config("bcc_self", "1")
|
||||
ac2.set_config("delete_server_after", "1")
|
||||
ac2.set_config("gossip_period", "0") # Re-gossip in every message
|
||||
acfactory.bring_accounts_online()
|
||||
dir = tmp_path / "exportdir"
|
||||
dir.mkdir()
|
||||
ac2.export_self_keys(str(dir))
|
||||
ac2_offl.import_self_keys(str(dir))
|
||||
ac2_offl.stop_io()
|
||||
|
||||
lp.sec("ac1: create verified-group QR, ac2 scans and joins")
|
||||
chat1 = ac1.create_group_chat("hello")
|
||||
qr = chat1.get_join_qr()
|
||||
lp.sec("ac2: start QR-code based join-group protocol")
|
||||
chat2 = ac2.qr_join_chat(qr)
|
||||
ac1._evtracker.wait_securejoin_inviter_progress(1000)
|
||||
# Wait for "Member Me (<addr>) added by <addr>." message.
|
||||
msg_in = ac2._evtracker.wait_next_incoming_message()
|
||||
assert msg_in.is_system_message()
|
||||
|
||||
lp.sec("ac2: waiting for 'member added' to be deleted on the server")
|
||||
ac2._evtracker.get_matching("DC_EVENT_IMAP_MESSAGE_DELETED")
|
||||
|
||||
lp.sec("ac1: sending 'hi' to the group")
|
||||
ac2.set_config("delete_server_after", "0")
|
||||
chat1.send_text("hi")
|
||||
|
||||
lp.sec("ac2_offl: going online, checking the 'hi' message")
|
||||
ac2_offl.start_io()
|
||||
msg_in = ac2_offl._evtracker.wait_next_incoming_message()
|
||||
assert not msg_in.is_system_message()
|
||||
assert msg_in.text == "hi"
|
||||
ac2_offl_ac1_contact = msg_in.get_sender_contact()
|
||||
assert ac2_offl_ac1_contact.addr == ac1.get_config("addr")
|
||||
assert not ac2_offl_ac1_contact.is_verified()
|
||||
chat2_offl = msg_in.chat
|
||||
|
||||
lp.sec("ac2: sending message re-gossiping Autocrypt keys")
|
||||
chat2.send_text("hi2")
|
||||
|
||||
lp.sec("ac2_offl: receiving message")
|
||||
ev = ac2_offl._evtracker.get_matching("DC_EVENT_INCOMING_MSG|DC_EVENT_MSGS_CHANGED")
|
||||
msg_in = ac2_offl.get_message_by_id(ev.data2)
|
||||
assert not msg_in.is_system_message()
|
||||
assert msg_in.text == "hi2"
|
||||
assert msg_in.chat == chat2_offl
|
||||
assert msg_in.get_sender_contact().addr == ac2.get_config("addr")
|
||||
# Until we reset verifications and then send the _verified header,
|
||||
# verification is not gossiped here:
|
||||
assert not ac2_offl_ac1_contact.is_verified()
|
||||
|
||||
|
||||
def test_deleted_msgs_dont_reappear(acfactory):
|
||||
ac1 = acfactory.new_online_configuring_account()
|
||||
acfactory.bring_accounts_online()
|
||||
|
||||
@@ -15,9 +15,6 @@ def test_basic_imap_api(acfactory, tmp_path):
|
||||
ac1, ac2 = acfactory.get_online_accounts(2)
|
||||
chat12 = acfactory.get_accepted_chat(ac1, ac2)
|
||||
|
||||
# Make sure that messages are not immediately auto-deleted on the server:
|
||||
ac2.set_config("bcc_self", "1")
|
||||
|
||||
imap2 = ac2.direct_imap
|
||||
|
||||
with imap2.idle() as idle2:
|
||||
@@ -165,9 +162,6 @@ def test_webxdc_message(acfactory, data, lp):
|
||||
ac1, ac2 = acfactory.get_online_accounts(2)
|
||||
chat = acfactory.get_accepted_chat(ac1, ac2)
|
||||
|
||||
# Make sure that messages are not immediately auto-deleted on the server:
|
||||
ac2.set_config("bcc_self", "1")
|
||||
|
||||
lp.sec("ac1: prepare and send text message to ac2")
|
||||
msg1 = chat.send_text("message0")
|
||||
assert not msg1.is_webxdc()
|
||||
@@ -368,10 +362,6 @@ def test_send_and_receive_message_markseen(acfactory, lp):
|
||||
# make DC's life harder wrt to encodings
|
||||
ac1.set_config("displayname", "ä name")
|
||||
|
||||
# Make sure that messages are not immediately auto-deleted on the server:
|
||||
ac1.set_config("bcc_self", "1")
|
||||
ac2.set_config("bcc_self", "1")
|
||||
|
||||
# clear any fresh device messages
|
||||
ac1.get_device_chat().mark_noticed()
|
||||
ac2.get_device_chat().mark_noticed()
|
||||
@@ -516,15 +506,9 @@ def test_mdn_asymmetric(acfactory, lp):
|
||||
ac1.set_config("mdns_enabled", "1")
|
||||
ac2.set_config("mdns_enabled", "1")
|
||||
|
||||
# Make sure that the mdn is not immediately auto-deleted on the server:
|
||||
ac1.set_config("bcc_self", "1")
|
||||
|
||||
lp.sec("sending text message from ac1 to ac2")
|
||||
msg_out = chat.send_text("message1")
|
||||
|
||||
# Wait for the message to be marked as seen on IMAP.
|
||||
ac1._evtracker.get_info_contains("Marked messages [0-9]+ in folder INBOX as seen.")
|
||||
|
||||
assert len(chat.get_messages()) == 1 + E2EE_INFO_MSGS
|
||||
|
||||
lp.sec("disable ac1 MDNs")
|
||||
@@ -541,7 +525,7 @@ def test_mdn_asymmetric(acfactory, lp):
|
||||
lp.sec("ac1: waiting for incoming activity")
|
||||
assert len(chat.get_messages()) == 1 + E2EE_INFO_MSGS
|
||||
|
||||
# Wait for the mdn to be marked as seen on IMAP.
|
||||
# Wait for the message to be marked as seen on IMAP.
|
||||
ac1._evtracker.get_info_contains("Marked messages [0-9]+ in folder INBOX as seen.")
|
||||
|
||||
# MDN is received even though MDNs are already disabled
|
||||
@@ -1089,8 +1073,6 @@ def test_send_receive_locations(acfactory, lp):
|
||||
|
||||
def test_delete_multiple_messages(acfactory, lp):
|
||||
ac1, ac2 = acfactory.get_online_accounts(2)
|
||||
# Make sure that messages are not immediately auto-deleted on the server:
|
||||
ac2.set_config("bcc_self", "1")
|
||||
chat12 = acfactory.get_accepted_chat(ac1, ac2)
|
||||
|
||||
lp.sec("ac1: sending seven messages")
|
||||
|
||||
@@ -153,8 +153,7 @@ class TestOfflineContact:
|
||||
|
||||
def test_delete_referenced_contact_hides_contact(self, acfactory):
|
||||
ac1 = acfactory.get_pseudo_configured_account()
|
||||
ac2 = acfactory.get_pseudo_configured_account()
|
||||
contact1 = ac1.create_contact(ac2)
|
||||
contact1 = ac1.create_contact("some1@example.com", name="some1")
|
||||
msg = contact1.create_chat().send_text("one message")
|
||||
assert ac1.delete_contact(contact1)
|
||||
assert not msg.filemime
|
||||
@@ -186,9 +185,8 @@ class TestOfflineChat:
|
||||
return acfactory.get_pseudo_configured_account()
|
||||
|
||||
@pytest.fixture()
|
||||
def chat1(self, ac1, acfactory):
|
||||
ac2 = acfactory.get_pseudo_configured_account()
|
||||
return ac1.create_contact(ac2).create_chat()
|
||||
def chat1(self, ac1):
|
||||
return ac1.create_contact("some1@example.org", name="some1").create_chat()
|
||||
|
||||
def test_display(self, chat1):
|
||||
str(chat1)
|
||||
@@ -406,7 +404,7 @@ class TestOfflineChat:
|
||||
contact2 = ac1.create_contact("display1 <x@example.org>", "real")
|
||||
assert contact2.name == "real"
|
||||
|
||||
def test_send_lots_of_offline_msgs(self, acfactory, chat1):
|
||||
def test_send_lots_of_offline_msgs(self, acfactory):
|
||||
ac1 = acfactory.get_pseudo_configured_account()
|
||||
ac1.set_config("configured_mail_server", "example.org")
|
||||
ac1.set_config("configured_mail_user", "example.org")
|
||||
@@ -415,13 +413,13 @@ class TestOfflineChat:
|
||||
ac1.set_config("configured_send_user", "example.org")
|
||||
ac1.set_config("configured_send_pw", "example.org")
|
||||
ac1.start_io()
|
||||
chat = ac1.create_contact("some1@example.org", name="some1").create_chat()
|
||||
for i in range(50):
|
||||
chat1.send_text("hello")
|
||||
chat.send_text("hello")
|
||||
|
||||
def test_create_chat_simple(self, acfactory):
|
||||
ac1 = acfactory.get_pseudo_configured_account()
|
||||
ac2 = acfactory.get_pseudo_configured_account()
|
||||
contact1 = ac1.create_contact(ac2)
|
||||
contact1 = ac1.create_contact("some1@example.org", name="some1")
|
||||
contact1.create_chat().send_text("hello")
|
||||
|
||||
def test_chat_message_distinctions(self, ac1, chat1):
|
||||
|
||||
@@ -152,8 +152,7 @@ def test_sig():
|
||||
|
||||
def test_markseen_invalid_message_ids(acfactory):
|
||||
ac1 = acfactory.get_pseudo_configured_account()
|
||||
ac2 = acfactory.get_pseudo_configured_account()
|
||||
contact1 = ac1.create_contact(ac2)
|
||||
contact1 = ac1.create_contact("some1@example.com", name="some1")
|
||||
chat = contact1.create_chat()
|
||||
chat.send_text("one message")
|
||||
ac1._evtracker.get_matching("DC_EVENT_MSGS_CHANGED")
|
||||
|
||||
+1
-1
@@ -1 +1 @@
|
||||
2026-05-29
|
||||
2026-04-13
|
||||
+1
-7
@@ -1,9 +1,3 @@
|
||||
#!/bin/sh
|
||||
# Run clippy for all Rust code in the project.
|
||||
#
|
||||
# To check, run
|
||||
# scripts/clippy.sh
|
||||
#
|
||||
# To automatically fix warnings, run
|
||||
# scripts/clippy.sh --fix --allow-dirty
|
||||
cargo clippy --workspace --all-targets --all-features "$@" -- -D warnings
|
||||
cargo clippy --workspace --all-targets --all-features -- -D warnings
|
||||
|
||||
@@ -6,7 +6,7 @@ set -euo pipefail
|
||||
export TZ=UTC
|
||||
|
||||
# Provider database revision.
|
||||
REV=2cba4b72f4c6e6417b83ba549aff7781be5f166c
|
||||
REV=ad097ee40579c884e7757de2d3bb0a51f481a32a
|
||||
|
||||
CORE_ROOT="$PWD"
|
||||
TMP="$(mktemp -d)"
|
||||
|
||||
+96
-12
@@ -20,6 +20,86 @@ Description-Content-Type: text/markdown
|
||||
"""
|
||||
|
||||
|
||||
def build_source_package(version, filename):
|
||||
with tarfile.open(filename, "w:gz") as pkg:
|
||||
|
||||
def pack(name, contents):
|
||||
contents = contents.encode()
|
||||
tar_info = tarfile.TarInfo(f"deltachat_rpc_server-{version}/{name}")
|
||||
tar_info.mode = 0o644
|
||||
tar_info.size = len(contents)
|
||||
pkg.addfile(tar_info, BytesIO(contents))
|
||||
|
||||
pack("PKG-INFO", metadata_contents(version))
|
||||
pack(
|
||||
"pyproject.toml",
|
||||
f"""[build-system]
|
||||
requires = ["setuptools==68.2.2", "pip"]
|
||||
build-backend = "setuptools.build_meta"
|
||||
|
||||
[project]
|
||||
name = "deltachat-rpc-server"
|
||||
version = "{version}"
|
||||
|
||||
[project.scripts]
|
||||
deltachat-rpc-server = "deltachat_rpc_server:main"
|
||||
""",
|
||||
)
|
||||
pack(
|
||||
"setup.py",
|
||||
f"""
|
||||
import sys
|
||||
from setuptools import setup, find_packages
|
||||
from distutils.cmd import Command
|
||||
from setuptools.command.install import install
|
||||
from setuptools.command.build import build
|
||||
import subprocess
|
||||
import platform
|
||||
import tempfile
|
||||
from zipfile import ZipFile
|
||||
from pathlib import Path
|
||||
import shutil
|
||||
|
||||
|
||||
class BuildCommand(build):
|
||||
def run(self):
|
||||
tmpdir = tempfile.mkdtemp()
|
||||
subprocess.run(
|
||||
[
|
||||
sys.executable,
|
||||
"-m",
|
||||
"pip",
|
||||
"download",
|
||||
"--no-input",
|
||||
"--timeout",
|
||||
"1000",
|
||||
"--platform",
|
||||
"musllinux_1_1_" + platform.machine(),
|
||||
"--only-binary=:all:",
|
||||
"deltachat-rpc-server=={version}",
|
||||
],
|
||||
cwd=tmpdir,
|
||||
)
|
||||
|
||||
wheel_path = next(Path(tmpdir).glob("*.whl"))
|
||||
with ZipFile(wheel_path, "r") as wheel:
|
||||
exe_path = wheel.extract("deltachat_rpc_server/deltachat-rpc-server", "src")
|
||||
Path(exe_path).chmod(0o700)
|
||||
wheel.extract("deltachat_rpc_server/__init__.py", "src")
|
||||
|
||||
shutil.rmtree(tmpdir)
|
||||
return super().run()
|
||||
|
||||
|
||||
setup(
|
||||
cmdclass={{"build": BuildCommand}},
|
||||
package_data={{"deltachat_rpc_server": ["deltachat-rpc-server"]}},
|
||||
)
|
||||
""",
|
||||
)
|
||||
pack("src/deltachat_rpc_server/__init__.py", "")
|
||||
|
||||
|
||||
def build_wheel(version, binary, tag, windows=False):
|
||||
filename = f"deltachat_rpc_server-{version}-{tag}.whl"
|
||||
|
||||
@@ -88,19 +168,23 @@ def main():
|
||||
with Path("Cargo.toml").open("rb") as fp:
|
||||
cargo_manifest = tomllib.load(fp)
|
||||
version = cargo_manifest["package"]["version"]
|
||||
arch = sys.argv[1]
|
||||
executable = sys.argv[2]
|
||||
tags = arch2tags[arch]
|
||||
|
||||
if arch in ["win32", "win64"]:
|
||||
build_wheel(
|
||||
version,
|
||||
executable,
|
||||
f"py3-none-{tags}",
|
||||
windows=True,
|
||||
)
|
||||
if sys.argv[1] == "source":
|
||||
filename = f"deltachat_rpc_server-{version}.tar.gz"
|
||||
build_source_package(version, filename)
|
||||
else:
|
||||
build_wheel(version, executable, f"py3-none-{tags}")
|
||||
arch = sys.argv[1]
|
||||
executable = sys.argv[2]
|
||||
tags = arch2tags[arch]
|
||||
|
||||
if arch in ["win32", "win64"]:
|
||||
build_wheel(
|
||||
version,
|
||||
executable,
|
||||
f"py3-none-{tags}",
|
||||
windows=True,
|
||||
)
|
||||
else:
|
||||
build_wheel(version, executable, f"py3-none-{tags}")
|
||||
|
||||
|
||||
main()
|
||||
|
||||
@@ -28,6 +28,7 @@ to implement typical messenger functions.
|
||||
- [Voice messages](#voice-messages)
|
||||
- [Reactions](#reactions)
|
||||
- [Attaching a contact to a message](#attaching-a-contact-to-a-message)
|
||||
- [Transitioning to a new e-mail address (AEAP)](#transitioning-to-a-new-e-mail-address-aeap)
|
||||
- [Miscellaneous](#miscellaneous)
|
||||
- [Sync messages](#sync-messages)
|
||||
|
||||
@@ -614,6 +615,23 @@ chatmail clients mark the gossiped key
|
||||
as indirectly verified.
|
||||
|
||||
|
||||
# Transitioning to a new e-mail address (AEAP)
|
||||
|
||||
When receiving a message:
|
||||
- If the key exists, but belongs to another address
|
||||
- AND there is a `Chat-Version` header
|
||||
- AND the message is signed correctly
|
||||
- AND the From address is (also) in the encrypted (and therefore signed) headers
|
||||
- AND the message timestamp is newer than the contact's `lastseen`
|
||||
(to prevent changing the address back when messages arrive out of order)
|
||||
(this condition is not that important
|
||||
since we will have eventual consistency even without it):
|
||||
|
||||
Replace the contact in _all_ groups,
|
||||
possibly deduplicate the members list,
|
||||
and add a system message to all of these chats.
|
||||
|
||||
|
||||
# Miscellaneous
|
||||
|
||||
Messengers SHOULD use the header `In-Reply-To` as usual.
|
||||
|
||||
+1
-1
@@ -794,7 +794,7 @@ impl Config {
|
||||
.with_push_subscriber(push_subscriber.clone())
|
||||
.build()
|
||||
.await
|
||||
.with_context(|| format!("failed to create context from file {dbfile:?}"))?;
|
||||
.with_context(|| format!("failed to create context from file {:?}", &dbfile))?;
|
||||
// Try to open without a passphrase,
|
||||
// but do not return an error if account is passphare-protected.
|
||||
ctx.open("".to_string()).await?;
|
||||
|
||||
+26
-32
@@ -4,7 +4,6 @@ use crate::config::Config;
|
||||
use crate::constants::DC_CHAT_ID_TRASH;
|
||||
use crate::message::MessageState;
|
||||
use crate::receive_imf::receive_imf;
|
||||
use crate::test_utils;
|
||||
use crate::test_utils::{TestContext, TestContextManager};
|
||||
|
||||
struct CallSetup {
|
||||
@@ -635,23 +634,20 @@ async fn test_forward_call() -> Result<()> {
|
||||
async fn test_end_text_call() -> Result<()> {
|
||||
let mut tcm = TestContextManager::new();
|
||||
let alice = &tcm.alice().await;
|
||||
let bob = &tcm.bob().await;
|
||||
|
||||
let encrypted_message = test_utils::encrypt_raw_message(
|
||||
bob,
|
||||
&[alice],
|
||||
b"From: bob@example.net\r\n\
|
||||
To: alice@example.org\r\n\
|
||||
Message-ID: <first@example.net>\r\n\
|
||||
Date: Sun, 22 Mar 2020 22:37:57 +0000\r\n\
|
||||
Chat-Version: 1.0\r\n\
|
||||
\r\n\
|
||||
Hello\r\n",
|
||||
let received1 = receive_imf(
|
||||
alice,
|
||||
b"From: bob@example.net\n\
|
||||
To: alice@example.org\n\
|
||||
Message-ID: <first@example.net>\n\
|
||||
Date: Sun, 22 Mar 2020 22:37:57 +0000\n\
|
||||
Chat-Version: 1.0\n\
|
||||
\n\
|
||||
Hello\n",
|
||||
false,
|
||||
)
|
||||
.await?;
|
||||
let received1 = receive_imf(alice, encrypted_message.as_bytes(), false)
|
||||
.await?
|
||||
.unwrap();
|
||||
.await?
|
||||
.unwrap();
|
||||
assert_eq!(received1.msg_ids.len(), 1);
|
||||
let msg = Message::load_from_db(alice, received1.msg_ids[0])
|
||||
.await
|
||||
@@ -660,23 +656,21 @@ async fn test_end_text_call() -> Result<()> {
|
||||
|
||||
// Receiving "Call ended" message that refers
|
||||
// to the text message does not result in an error.
|
||||
let encrypted_message2 = test_utils::encrypt_raw_message(
|
||||
bob,
|
||||
&[alice],
|
||||
b"From: bob@example.net\r\n\
|
||||
To: alice@example.org\r\n\
|
||||
Message-ID: <second@example.net>\r\n\
|
||||
Date: Sun, 22 Mar 2020 23:37:57 +0000\r\n\
|
||||
In-Reply-To: <first@example.net>\r\n\
|
||||
Chat-Version: 1.0\r\n\
|
||||
Chat-Content: call-ended\r\n\
|
||||
\r\n\
|
||||
Call ended\r\n",
|
||||
let received2 = receive_imf(
|
||||
alice,
|
||||
b"From: bob@example.net\n\
|
||||
To: alice@example.org\n\
|
||||
Message-ID: <second@example.net>\n\
|
||||
Date: Sun, 22 Mar 2020 23:37:57 +0000\n\
|
||||
In-Reply-To: <first@example.net>\n\
|
||||
Chat-Version: 1.0\n\
|
||||
Chat-Content: call-ended\n\
|
||||
\n\
|
||||
Call ended\n",
|
||||
false,
|
||||
)
|
||||
.await?;
|
||||
let received2 = receive_imf(alice, encrypted_message2.as_bytes(), false)
|
||||
.await?
|
||||
.unwrap();
|
||||
.await?
|
||||
.unwrap();
|
||||
assert_eq!(received2.msg_ids.len(), 1);
|
||||
assert_eq!(received2.chat_id, DC_CHAT_ID_TRASH);
|
||||
|
||||
|
||||
+52
-86
@@ -32,7 +32,6 @@ use crate::debug_logging::maybe_set_logging_xdc;
|
||||
use crate::download::{
|
||||
DownloadState, PRE_MSG_ATTACHMENT_SIZE_THRESHOLD, PRE_MSG_SIZE_WARNING_THRESHOLD,
|
||||
};
|
||||
use crate::ensure_and_debug_assert_eq;
|
||||
use crate::ephemeral::{Timer as EphemeralTimer, start_chat_ephemeral_timers};
|
||||
use crate::events::EventType;
|
||||
use crate::key::{Fingerprint, self_fingerprint};
|
||||
@@ -1783,8 +1782,9 @@ impl Chat {
|
||||
);
|
||||
bail!("Cannot set message, contact for {} not found.", self.id);
|
||||
}
|
||||
} else if self.param.get_int(Param::Unpromoted).unwrap_or_default() == 1 {
|
||||
ensure_and_debug_assert_eq!(self.typ, Chattype::Group,);
|
||||
} else if matches!(self.typ, Chattype::Group | Chattype::OutBroadcast)
|
||||
&& self.param.get_int(Param::Unpromoted).unwrap_or_default() == 1
|
||||
{
|
||||
msg.param.set_int(Param::AttachChatAvatarAndDescription, 1);
|
||||
self.param
|
||||
.remove(Param::Unpromoted)
|
||||
@@ -2529,7 +2529,7 @@ async fn prepare_msg_blob(context: &Context, msg: &mut Message) -> Result<()> {
|
||||
// running numbers, etc.
|
||||
let filename: String = match viewtype_orig {
|
||||
Viewtype::Voice => format!(
|
||||
"voice-messsage_{}.{suffix}",
|
||||
"voice-messsage_{}.{}",
|
||||
chrono::Utc
|
||||
.timestamp_opt(msg.timestamp_sort, 0)
|
||||
.single()
|
||||
@@ -2537,9 +2537,10 @@ async fn prepare_msg_blob(context: &Context, msg: &mut Message) -> Result<()> {
|
||||
|| "YY-mm-dd_hh:mm:ss".to_string(),
|
||||
|ts| ts.format("%Y-%m-%d_%H-%M-%S").to_string()
|
||||
),
|
||||
&suffix
|
||||
),
|
||||
Viewtype::Image | Viewtype::Gif => format!(
|
||||
"image_{}.{suffix}",
|
||||
"image_{}.{}",
|
||||
chrono::Utc
|
||||
.timestamp_opt(msg.timestamp_sort, 0)
|
||||
.single()
|
||||
@@ -2547,9 +2548,10 @@ async fn prepare_msg_blob(context: &Context, msg: &mut Message) -> Result<()> {
|
||||
|| "YY-mm-dd_hh:mm:ss".to_string(),
|
||||
|ts| ts.format("%Y-%m-%d_%H-%M-%S").to_string(),
|
||||
),
|
||||
&suffix,
|
||||
),
|
||||
Viewtype::Video => format!(
|
||||
"video_{}.{suffix}",
|
||||
"video_{}.{}",
|
||||
chrono::Utc
|
||||
.timestamp_opt(msg.timestamp_sort, 0)
|
||||
.single()
|
||||
@@ -2557,6 +2559,7 @@ async fn prepare_msg_blob(context: &Context, msg: &mut Message) -> Result<()> {
|
||||
|| "YY-mm-dd_hh:mm:ss".to_string(),
|
||||
|ts| ts.format("%Y-%m-%d_%H-%M-%S").to_string()
|
||||
),
|
||||
&suffix
|
||||
),
|
||||
_ => filename,
|
||||
};
|
||||
@@ -2610,7 +2613,7 @@ pub async fn send_msg(context: &Context, chat_id: ChatId, msg: &mut Message) ->
|
||||
"chat_id cannot be a special chat: {chat_id}"
|
||||
);
|
||||
|
||||
if msg.state != MessageState::Undefined {
|
||||
if msg.state != MessageState::Undefined && msg.state != MessageState::OutPreparing {
|
||||
msg.param.remove(Param::GuaranteeE2ee);
|
||||
msg.param.remove(Param::ForcePlaintext);
|
||||
// create_send_msg_jobs() will update `param` in the db.
|
||||
@@ -2718,7 +2721,10 @@ async fn prepare_send_msg(
|
||||
None
|
||||
};
|
||||
|
||||
if msg.state == MessageState::Undefined
|
||||
if matches!(
|
||||
msg.state,
|
||||
MessageState::Undefined | MessageState::OutPreparing
|
||||
)
|
||||
// Legacy SecureJoin "v*-request" messages are unencrypted.
|
||||
&& msg.param.get_cmd() != SystemMessage::SecurejoinMessage
|
||||
&& chat.is_encrypted(context).await?
|
||||
@@ -2823,12 +2829,7 @@ pub(crate) async fn create_send_msg_jobs(context: &Context, msg: &mut Message) -
|
||||
.await?;
|
||||
}
|
||||
|
||||
let needs_encryption = msg.param.get_bool(Param::GuaranteeE2ee).unwrap_or_default()
|
||||
|| (!msg
|
||||
.param
|
||||
.get_bool(Param::ForcePlaintext)
|
||||
.unwrap_or_default()
|
||||
&& context.get_config_bool(Config::ForceEncryption).await?);
|
||||
let needs_encryption = msg.param.get_bool(Param::GuaranteeE2ee).unwrap_or_default();
|
||||
let mimefactory = match MimeFactory::from_msg(context, msg.clone()).await {
|
||||
Ok(mf) => mf,
|
||||
Err(err) => {
|
||||
@@ -2895,26 +2896,11 @@ pub(crate) async fn create_send_msg_jobs(context: &Context, msg: &mut Message) -
|
||||
}
|
||||
|
||||
if needs_encryption && !rendered_msg.is_encrypted {
|
||||
let addr = context.get_config(Config::ConfiguredAddr).await?;
|
||||
let text = stock_str::unencrypted_email(
|
||||
/* unrecoverable */
|
||||
message::set_msg_failed(
|
||||
context,
|
||||
addr.unwrap_or_default()
|
||||
.split('@')
|
||||
.nth(1)
|
||||
.unwrap_or_default(),
|
||||
)
|
||||
.await;
|
||||
message::set_msg_failed(context, msg, &text).await?;
|
||||
add_info_msg_with_cmd(
|
||||
context,
|
||||
msg.chat_id,
|
||||
&text,
|
||||
SystemMessage::InvalidUnencryptedMail,
|
||||
Some(msg.timestamp_sort),
|
||||
msg.timestamp_sort,
|
||||
None,
|
||||
None,
|
||||
None,
|
||||
msg,
|
||||
"End-to-end-encryption unavailable unexpectedly.",
|
||||
)
|
||||
.await?;
|
||||
bail!(
|
||||
@@ -2951,8 +2937,8 @@ pub(crate) async fn create_send_msg_jobs(context: &Context, msg: &mut Message) -
|
||||
UPDATE msgs SET
|
||||
timestamp=(
|
||||
SELECT MAX(timestamp) FROM msgs INDEXED BY msgs_index7 WHERE
|
||||
-- From `InFresh` to `OutDelivered` inclusive, except `OutDraft`.
|
||||
state IN(10,13,16,18,20,24,26) AND
|
||||
-- From `InFresh` to `OutMdnRcvd` inclusive except `OutDraft`.
|
||||
state IN(10,13,16,18,20,24,26,28) AND
|
||||
hidden IN(0,1) AND
|
||||
chat_id=? AND
|
||||
id<=?
|
||||
@@ -3626,6 +3612,9 @@ pub(crate) async fn create_group_ex(
|
||||
/// because the word "channel" already appears a lot in the code,
|
||||
/// which would make it hard to grep for it.
|
||||
///
|
||||
/// After creation, the chat contains no recipients and is in _unpromoted_ state;
|
||||
/// see [`create_group`] for more information on the unpromoted state.
|
||||
///
|
||||
/// Returns the created chat's id.
|
||||
pub async fn create_broadcast(context: &Context, chat_name: String) -> Result<ChatId> {
|
||||
let grpid = create_id();
|
||||
@@ -3657,20 +3646,17 @@ pub(crate) async fn create_out_broadcast_ex(
|
||||
|row| row.get(0),
|
||||
)?;
|
||||
ensure!(cnt == 0, "{cnt} chats exist with grpid {grpid}");
|
||||
let mut params: Params = Params::new();
|
||||
params.update_timestamp(Param::GroupNameTimestamp, time())?;
|
||||
|
||||
t.execute(
|
||||
"INSERT INTO chats
|
||||
(type, name, name_normalized, grpid, created_timestamp, param)
|
||||
VALUES(?, ?, ?, ?, ?, ?)",
|
||||
(type, name, name_normalized, grpid, created_timestamp)
|
||||
VALUES(?, ?, ?, ?, ?)",
|
||||
(
|
||||
Chattype::OutBroadcast,
|
||||
&chat_name,
|
||||
normalize_text(&chat_name),
|
||||
&grpid,
|
||||
timestamp,
|
||||
params.to_string(),
|
||||
),
|
||||
)?;
|
||||
let chat_id = ChatId::new(t.last_insert_rowid().try_into()?);
|
||||
@@ -3738,19 +3724,17 @@ pub(crate) async fn update_chat_contacts_table(
|
||||
id: ChatId,
|
||||
contacts: &BTreeSet<ContactId>,
|
||||
) -> Result<()> {
|
||||
// See add_to_chat_contacts_table() for reasoning.
|
||||
let limit = cmp::max(time().saturating_add(TIMESTAMP_SENT_TOLERANCE), timestamp);
|
||||
context
|
||||
.sql
|
||||
.transaction(move |transaction| {
|
||||
// Bump `remove_timestamp` even for members from `contacts`.
|
||||
// Bump `remove_timestamp` to at least `now`
|
||||
// even for members from `contacts`.
|
||||
// We add members from `contacts` back below.
|
||||
transaction.execute(
|
||||
"UPDATE chats_contacts SET
|
||||
add_timestamp=MIN(add_timestamp, ?1),
|
||||
remove_timestamp=MAX(MIN(remove_timestamp,?1), MIN(add_timestamp,?1)+1, ?)
|
||||
"UPDATE chats_contacts
|
||||
SET remove_timestamp=MAX(add_timestamp+1, ?)
|
||||
WHERE chat_id=?",
|
||||
(limit, timestamp, id),
|
||||
(timestamp, id),
|
||||
)?;
|
||||
|
||||
if !contacts.is_empty() {
|
||||
@@ -3762,8 +3746,9 @@ pub(crate) async fn update_chat_contacts_table(
|
||||
)?;
|
||||
|
||||
for contact_id in contacts {
|
||||
// We bumped `remove_timestamp` for existing rows above,
|
||||
// so on conflict it is enough to set `add_timestamp = remove_timestamp`.
|
||||
// We bumped `add_timestamp` for existing rows above,
|
||||
// so on conflict it is enough to set `add_timestamp = remove_timestamp`
|
||||
// and this guarantees that `add_timestamp` is no less than `timestamp`.
|
||||
statement.execute((id, contact_id, timestamp))?;
|
||||
}
|
||||
}
|
||||
@@ -3780,24 +3765,17 @@ pub(crate) async fn add_to_chat_contacts_table(
|
||||
chat_id: ChatId,
|
||||
contact_ids: &[ContactId],
|
||||
) -> Result<()> {
|
||||
// Our clock may be slow, so limit stored timestamps with `timestamp` if it's bigger. This way
|
||||
// we only cap remote timestamps if, in addition, remote changes arrive reordered or we do local
|
||||
// changes. Also allow some tolerance, moreover, previous removals might lend time from the
|
||||
// future.
|
||||
let limit = cmp::max(time().saturating_add(TIMESTAMP_SENT_TOLERANCE), timestamp);
|
||||
context
|
||||
.sql
|
||||
.transaction(move |transaction| {
|
||||
let mut add_statement = transaction.prepare(
|
||||
"INSERT INTO chats_contacts (chat_id, contact_id, add_timestamp) VALUES(?1, ?2, ?3)
|
||||
ON CONFLICT (chat_id, contact_id)
|
||||
DO UPDATE SET
|
||||
remove_timestamp=MIN(remove_timestamp, ?4),
|
||||
add_timestamp=MIN(MAX(add_timestamp,remove_timestamp,?3), ?4)",
|
||||
DO UPDATE SET add_timestamp=MAX(remove_timestamp, ?3)",
|
||||
)?;
|
||||
|
||||
for contact_id in contact_ids {
|
||||
add_statement.execute((chat_id, contact_id, timestamp, limit))?;
|
||||
add_statement.execute((chat_id, contact_id, timestamp))?;
|
||||
}
|
||||
Ok(())
|
||||
})
|
||||
@@ -3808,34 +3786,26 @@ pub(crate) async fn add_to_chat_contacts_table(
|
||||
|
||||
/// Removes a contact from the chat
|
||||
/// by updating the `remove_timestamp`.
|
||||
/// Returns whether the contact has been a chat member recently. If so, a removal message should be
|
||||
/// sent.
|
||||
pub(crate) async fn remove_from_chat_contacts_table(
|
||||
context: &Context,
|
||||
chat_id: ChatId,
|
||||
contact_id: ContactId,
|
||||
) -> Result<bool> {
|
||||
) -> Result<()> {
|
||||
let now = time();
|
||||
// See add_to_chat_contacts_table() for reasoning.
|
||||
let limit = now.saturating_add(TIMESTAMP_SENT_TOLERANCE);
|
||||
let is_past_member = context
|
||||
context
|
||||
.sql
|
||||
.execute(
|
||||
"UPDATE chats_contacts SET
|
||||
add_timestamp=MIN(add_timestamp, ?1),
|
||||
remove_timestamp=MAX(MIN(remove_timestamp,?1), MIN(add_timestamp,?1)+1, ?)
|
||||
"UPDATE chats_contacts
|
||||
SET remove_timestamp=MAX(add_timestamp+1, ?)
|
||||
WHERE chat_id=? AND contact_id=?",
|
||||
(limit, now, chat_id, contact_id),
|
||||
(now, chat_id, contact_id),
|
||||
)
|
||||
.await?
|
||||
> 0;
|
||||
Ok(is_past_member)
|
||||
.await?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Removes a contact from the chat
|
||||
/// without leaving a trace in the db.
|
||||
/// Returns whether the contact was removed, even if it was a past contact. If so, a removal message
|
||||
/// should be sent if the removal is issued by this device.
|
||||
/// without leaving a trace.
|
||||
///
|
||||
/// Note that if we call this function,
|
||||
/// and then receive a message from another device
|
||||
@@ -3845,17 +3815,17 @@ pub(crate) async fn remove_from_chat_contacts_table_without_trace(
|
||||
context: &Context,
|
||||
chat_id: ChatId,
|
||||
contact_id: ContactId,
|
||||
) -> Result<bool> {
|
||||
let removed = context
|
||||
) -> Result<()> {
|
||||
context
|
||||
.sql
|
||||
.execute(
|
||||
"DELETE FROM chats_contacts
|
||||
WHERE chat_id=? AND contact_id=?",
|
||||
(chat_id, contact_id),
|
||||
)
|
||||
.await?
|
||||
> 0;
|
||||
Ok(removed)
|
||||
.await?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Adds a contact to the chat.
|
||||
@@ -4175,13 +4145,10 @@ pub async fn remove_contact_from_chat(
|
||||
|
||||
let mut sync = Nosync;
|
||||
|
||||
let removed = if chat.is_promoted() && chat.typ != Chattype::OutBroadcast {
|
||||
remove_from_chat_contacts_table(context, chat_id, contact_id).await?
|
||||
if chat.is_promoted() && chat.typ != Chattype::OutBroadcast {
|
||||
remove_from_chat_contacts_table(context, chat_id, contact_id).await?;
|
||||
} else {
|
||||
remove_from_chat_contacts_table_without_trace(context, chat_id, contact_id).await?
|
||||
};
|
||||
if !removed {
|
||||
return Ok(());
|
||||
remove_from_chat_contacts_table_without_trace(context, chat_id, contact_id).await?;
|
||||
}
|
||||
|
||||
// We do not return an error if the contact does not exist in the database.
|
||||
@@ -4572,7 +4539,6 @@ pub async fn forward_msgs_2ctx(
|
||||
|
||||
msg.state = MessageState::OutPending;
|
||||
msg.rfc724_mid = create_outgoing_rfc724_mid();
|
||||
msg.pre_rfc724_mid.clear();
|
||||
msg.timestamp_sort = curr_timestamp;
|
||||
chat.prepare_msg_raw(ctx_dst, &mut msg, None).await?;
|
||||
|
||||
|
||||
+91
-167
@@ -9,10 +9,8 @@ use crate::headerdef::HeaderDef;
|
||||
use crate::imex::{ImexMode, has_backup, imex};
|
||||
use crate::message::{Message, MessengerMessage, delete_msgs};
|
||||
use crate::mimeparser::{self, MimeMessage};
|
||||
use crate::qr::{Qr, check_qr};
|
||||
use crate::receive_imf::receive_imf;
|
||||
use crate::securejoin::{get_securejoin_qr, join_securejoin};
|
||||
use crate::test_utils;
|
||||
use crate::test_utils::{
|
||||
AVATAR_64x64_BYTES, AVATAR_64x64_DEDUPLICATED, E2EE_INFO_MSGS, TestContext, TestContextManager,
|
||||
TimeShiftFalsePositiveNote, sync,
|
||||
@@ -1161,51 +1159,46 @@ async fn test_archive() {
|
||||
|
||||
#[tokio::test(flavor = "multi_thread", worker_threads = 2)]
|
||||
async fn test_unarchive_if_muted() -> Result<()> {
|
||||
let mut tcm = TestContextManager::new();
|
||||
let t = &tcm.alice().await;
|
||||
let bob = &tcm.bob().await;
|
||||
let t = TestContext::new_alice().await;
|
||||
|
||||
let msg_from_bob = async |num: u32| {
|
||||
let encrypted_message = test_utils::encrypt_raw_message(
|
||||
bob,
|
||||
&[t],
|
||||
async fn msg_from_bob(t: &TestContext, num: u32) -> Result<()> {
|
||||
receive_imf(
|
||||
t,
|
||||
format!(
|
||||
"From: bob@example.net\r\n\
|
||||
To: alice@example.org\r\n\
|
||||
Message-ID: <{num}@example.org>\r\n\
|
||||
Chat-Version: 1.0\r\n\
|
||||
Date: Sun, 22 Mar 2022 19:37:57 +0000\r\n\
|
||||
\r\n\
|
||||
hello\r\n"
|
||||
"From: bob@example.net\n\
|
||||
To: alice@example.org\n\
|
||||
Message-ID: <{num}@example.org>\n\
|
||||
Chat-Version: 1.0\n\
|
||||
Date: Sun, 22 Mar 2022 19:37:57 +0000\n\
|
||||
\n\
|
||||
hello\n"
|
||||
)
|
||||
.as_bytes(),
|
||||
false,
|
||||
)
|
||||
.await
|
||||
.unwrap();
|
||||
receive_imf(t, encrypted_message.as_bytes(), false)
|
||||
.await
|
||||
.unwrap();
|
||||
};
|
||||
.await?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
msg_from_bob(1).await;
|
||||
msg_from_bob(&t, 1).await?;
|
||||
let chat_id = t.get_last_msg().await.get_chat_id();
|
||||
chat_id.accept(t).await?;
|
||||
chat_id.set_visibility(t, ChatVisibility::Archived).await?;
|
||||
assert_eq!(get_archived_cnt(t).await?, 1);
|
||||
chat_id.accept(&t).await?;
|
||||
chat_id.set_visibility(&t, ChatVisibility::Archived).await?;
|
||||
assert_eq!(get_archived_cnt(&t).await?, 1);
|
||||
|
||||
// not muted chat is unarchived on receiving a message
|
||||
msg_from_bob(2).await;
|
||||
assert_eq!(get_archived_cnt(t).await?, 0);
|
||||
msg_from_bob(&t, 2).await?;
|
||||
assert_eq!(get_archived_cnt(&t).await?, 0);
|
||||
|
||||
// forever muted chat is not unarchived on receiving a message
|
||||
chat_id.set_visibility(t, ChatVisibility::Archived).await?;
|
||||
set_muted(t, chat_id, MuteDuration::Forever).await?;
|
||||
msg_from_bob(3).await;
|
||||
assert_eq!(get_archived_cnt(t).await?, 1);
|
||||
chat_id.set_visibility(&t, ChatVisibility::Archived).await?;
|
||||
set_muted(&t, chat_id, MuteDuration::Forever).await?;
|
||||
msg_from_bob(&t, 3).await?;
|
||||
assert_eq!(get_archived_cnt(&t).await?, 1);
|
||||
|
||||
// otherwise muted chat is not unarchived on receiving a message
|
||||
set_muted(
|
||||
t,
|
||||
&t,
|
||||
chat_id,
|
||||
MuteDuration::Until(
|
||||
SystemTime::now()
|
||||
@@ -1214,12 +1207,12 @@ async fn test_unarchive_if_muted() -> Result<()> {
|
||||
),
|
||||
)
|
||||
.await?;
|
||||
msg_from_bob(4).await;
|
||||
assert_eq!(get_archived_cnt(t).await?, 1);
|
||||
msg_from_bob(&t, 4).await?;
|
||||
assert_eq!(get_archived_cnt(&t).await?, 1);
|
||||
|
||||
// expired mute will unarchive the chat
|
||||
set_muted(
|
||||
t,
|
||||
&t,
|
||||
chat_id,
|
||||
MuteDuration::Until(
|
||||
SystemTime::now()
|
||||
@@ -1228,20 +1221,20 @@ async fn test_unarchive_if_muted() -> Result<()> {
|
||||
),
|
||||
)
|
||||
.await?;
|
||||
msg_from_bob(5).await;
|
||||
assert_eq!(get_archived_cnt(t).await?, 0);
|
||||
msg_from_bob(&t, 5).await?;
|
||||
assert_eq!(get_archived_cnt(&t).await?, 0);
|
||||
|
||||
// no unarchiving on sending to muted chat or on adding info messages to muted chat
|
||||
chat_id.set_visibility(t, ChatVisibility::Archived).await?;
|
||||
set_muted(t, chat_id, MuteDuration::Forever).await?;
|
||||
send_text_msg(t, chat_id, "out".to_string()).await?;
|
||||
add_info_msg(t, chat_id, "info").await?;
|
||||
assert_eq!(get_archived_cnt(t).await?, 1);
|
||||
chat_id.set_visibility(&t, ChatVisibility::Archived).await?;
|
||||
set_muted(&t, chat_id, MuteDuration::Forever).await?;
|
||||
send_text_msg(&t, chat_id, "out".to_string()).await?;
|
||||
add_info_msg(&t, chat_id, "info").await?;
|
||||
assert_eq!(get_archived_cnt(&t).await?, 1);
|
||||
|
||||
// finally, unarchive on sending to not muted chat
|
||||
set_muted(t, chat_id, MuteDuration::NotMuted).await?;
|
||||
send_text_msg(t, chat_id, "out2".to_string()).await?;
|
||||
assert_eq!(get_archived_cnt(t).await?, 0);
|
||||
set_muted(&t, chat_id, MuteDuration::NotMuted).await?;
|
||||
send_text_msg(&t, chat_id, "out2".to_string()).await?;
|
||||
assert_eq!(get_archived_cnt(&t).await?, 0);
|
||||
|
||||
Ok(())
|
||||
}
|
||||
@@ -1387,7 +1380,6 @@ async fn test_markfresh_chat() -> Result<()> {
|
||||
#[tokio::test(flavor = "multi_thread", worker_threads = 2)]
|
||||
async fn test_archive_fresh_msgs() -> Result<()> {
|
||||
let t = TestContext::new_alice().await;
|
||||
t.allow_unencrypted().await?;
|
||||
|
||||
async fn msg_from(t: &TestContext, name: &str, num: u32) -> Result<()> {
|
||||
receive_imf(
|
||||
@@ -1881,38 +1873,45 @@ async fn test_lookup_self_by_contact_id() {
|
||||
|
||||
#[tokio::test(flavor = "multi_thread", worker_threads = 2)]
|
||||
async fn test_marknoticed_chat() -> Result<()> {
|
||||
let mut tcm = TestContextManager::new();
|
||||
let alice = &tcm.alice().await;
|
||||
let bob = &tcm.bob().await;
|
||||
let chat = alice.create_chat(bob).await;
|
||||
let t = TestContext::new_alice().await;
|
||||
let chat = t.create_chat_with_contact("bob", "bob@example.org").await;
|
||||
|
||||
let bob_chat_id = bob.create_chat_id(alice).await;
|
||||
let sent = bob.send_text(bob_chat_id, "hello").await;
|
||||
alice.recv_msg(&sent).await;
|
||||
receive_imf(
|
||||
&t,
|
||||
b"From: bob@example.org\n\
|
||||
To: alice@example.org\n\
|
||||
Message-ID: <1@example.org>\n\
|
||||
Chat-Version: 1.0\n\
|
||||
Date: Fri, 23 Apr 2021 10:00:57 +0000\n\
|
||||
\n\
|
||||
hello\n",
|
||||
false,
|
||||
)
|
||||
.await?;
|
||||
|
||||
let chats = Chatlist::try_load(alice, 0, None, None).await?;
|
||||
let chats = Chatlist::try_load(&t, 0, None, None).await?;
|
||||
assert_eq!(chats.len(), 1);
|
||||
assert_eq!(chats.get_chat_id(0)?, chat.id);
|
||||
assert_eq!(chat.id.get_fresh_msg_cnt(alice).await?, 1);
|
||||
assert_eq!(alice.get_fresh_msgs().await?.len(), 1);
|
||||
assert_eq!(chat.id.get_fresh_msg_cnt(&t).await?, 1);
|
||||
assert_eq!(t.get_fresh_msgs().await?.len(), 1);
|
||||
|
||||
let msgs = get_chat_msgs(alice, chat.id).await?;
|
||||
assert_eq!(msgs.len(), 2);
|
||||
let msg_id = match msgs.last().unwrap() {
|
||||
let msgs = get_chat_msgs(&t, chat.id).await?;
|
||||
assert_eq!(msgs.len(), 1);
|
||||
let msg_id = match msgs.first().unwrap() {
|
||||
ChatItem::Message { msg_id } => *msg_id,
|
||||
_ => MsgId::new_unset(),
|
||||
};
|
||||
let msg = message::Message::load_from_db(alice, msg_id).await?;
|
||||
let msg = message::Message::load_from_db(&t, msg_id).await?;
|
||||
assert_eq!(msg.state, MessageState::InFresh);
|
||||
|
||||
marknoticed_chat(alice, chat.id).await?;
|
||||
marknoticed_chat(&t, chat.id).await?;
|
||||
|
||||
let chats = Chatlist::try_load(alice, 0, None, None).await?;
|
||||
let chats = Chatlist::try_load(&t, 0, None, None).await?;
|
||||
assert_eq!(chats.len(), 1);
|
||||
let msg = message::Message::load_from_db(alice, msg_id).await?;
|
||||
let msg = message::Message::load_from_db(&t, msg_id).await?;
|
||||
assert_eq!(msg.state, MessageState::InNoticed);
|
||||
assert_eq!(chat.id.get_fresh_msg_cnt(alice).await?, 0);
|
||||
assert_eq!(alice.get_fresh_msgs().await?.len(), 0);
|
||||
assert_eq!(chat.id.get_fresh_msg_cnt(&t).await?, 0);
|
||||
assert_eq!(t.get_fresh_msgs().await?.len(), 0);
|
||||
|
||||
Ok(())
|
||||
}
|
||||
@@ -1920,7 +1919,6 @@ async fn test_marknoticed_chat() -> Result<()> {
|
||||
#[tokio::test(flavor = "multi_thread", worker_threads = 2)]
|
||||
async fn test_contact_request_fresh_messages() -> Result<()> {
|
||||
let t = TestContext::new_alice().await;
|
||||
t.allow_unencrypted().await?;
|
||||
|
||||
let chats = Chatlist::try_load(&t, 0, None, None).await?;
|
||||
assert_eq!(chats.len(), 0);
|
||||
@@ -1972,43 +1970,40 @@ async fn test_contact_request_fresh_messages() -> Result<()> {
|
||||
|
||||
#[tokio::test(flavor = "multi_thread", worker_threads = 2)]
|
||||
async fn test_contact_request_archive() -> Result<()> {
|
||||
let mut tcm = TestContextManager::new();
|
||||
let alice = &tcm.alice().await;
|
||||
let bob = &tcm.bob().await;
|
||||
let t = TestContext::new_alice().await;
|
||||
|
||||
let bob_chat_id = bob.create_chat_id(alice).await;
|
||||
let bob_sent_text = bob.send_text(bob_chat_id, "hello").await;
|
||||
alice.recv_msg(&bob_sent_text).await;
|
||||
receive_imf(
|
||||
&t,
|
||||
b"From: bob@example.org\n\
|
||||
To: alice@example.org\n\
|
||||
Message-ID: <2@example.org>\n\
|
||||
Chat-Version: 1.0\n\
|
||||
Date: Sun, 22 Mar 2021 19:37:57 +0000\n\
|
||||
\n\
|
||||
hello\n",
|
||||
false,
|
||||
)
|
||||
.await?;
|
||||
|
||||
let chats = Chatlist::try_load(alice, 0, None, None).await?;
|
||||
let chats = Chatlist::try_load(&t, 0, None, None).await?;
|
||||
assert_eq!(chats.len(), 1);
|
||||
let chat_id = chats.get_chat_id(0)?;
|
||||
assert!(
|
||||
Chat::load_from_db(alice, chat_id)
|
||||
.await?
|
||||
.is_contact_request()
|
||||
);
|
||||
assert_eq!(get_archived_cnt(alice).await?, 0);
|
||||
assert!(Chat::load_from_db(&t, chat_id).await?.is_contact_request());
|
||||
assert_eq!(get_archived_cnt(&t).await?, 0);
|
||||
|
||||
// archive request without accepting or blocking
|
||||
chat_id
|
||||
.set_visibility(alice, ChatVisibility::Archived)
|
||||
.await?;
|
||||
chat_id.set_visibility(&t, ChatVisibility::Archived).await?;
|
||||
|
||||
let chats = Chatlist::try_load(alice, 0, None, None).await?;
|
||||
let chats = Chatlist::try_load(&t, 0, None, None).await?;
|
||||
assert_eq!(chats.len(), 1);
|
||||
let chat_id = chats.get_chat_id(0)?;
|
||||
assert!(chat_id.is_archived_link());
|
||||
assert_eq!(get_archived_cnt(alice).await?, 1);
|
||||
assert_eq!(get_archived_cnt(&t).await?, 1);
|
||||
|
||||
let chats = Chatlist::try_load(alice, DC_GCL_ARCHIVED_ONLY, None, None).await?;
|
||||
let chats = Chatlist::try_load(&t, DC_GCL_ARCHIVED_ONLY, None, None).await?;
|
||||
assert_eq!(chats.len(), 1);
|
||||
let chat_id = chats.get_chat_id(0)?;
|
||||
assert!(
|
||||
Chat::load_from_db(alice, chat_id)
|
||||
.await?
|
||||
.is_contact_request()
|
||||
);
|
||||
assert!(Chat::load_from_db(&t, chat_id).await?.is_contact_request());
|
||||
|
||||
Ok(())
|
||||
}
|
||||
@@ -2016,7 +2011,6 @@ async fn test_contact_request_archive() -> Result<()> {
|
||||
#[tokio::test(flavor = "multi_thread", worker_threads = 2)]
|
||||
async fn test_classic_email_chat() -> Result<()> {
|
||||
let alice = TestContext::new_alice().await;
|
||||
alice.allow_unencrypted().await?;
|
||||
|
||||
// Alice receives a classic (non-chat) message from Bob.
|
||||
receive_imf(
|
||||
@@ -2570,7 +2564,6 @@ async fn test_forward_encrypted_to_unencrypted() -> Result<()> {
|
||||
let alice = &tcm.alice().await;
|
||||
let bob = &tcm.bob().await;
|
||||
let charlie = &tcm.charlie().await;
|
||||
bob.allow_unencrypted().await?;
|
||||
|
||||
let txt = "This should be encrypted";
|
||||
let sent = alice.send_text(alice.create_chat(bob).await.id, txt).await;
|
||||
@@ -2800,30 +2793,6 @@ async fn test_can_send_group() -> Result<()> {
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[tokio::test(flavor = "multi_thread", worker_threads = 2)]
|
||||
async fn test_cant_remove_nonmember() -> Result<()> {
|
||||
let mut tcm = TestContextManager::new();
|
||||
let alice = &tcm.alice().await;
|
||||
let bob = &tcm.bob().await;
|
||||
let charlie = &tcm.charlie().await;
|
||||
|
||||
let alice_broadcast_id = create_broadcast(alice, "Channel".to_string()).await?;
|
||||
let qr = get_securejoin_qr(alice, Some(alice_broadcast_id))
|
||||
.await
|
||||
.unwrap();
|
||||
tcm.exec_securejoin_qr(bob, alice, &qr).await;
|
||||
|
||||
let alice_charlie_id = alice.add_or_lookup_contact_id(charlie).await;
|
||||
remove_contact_from_chat(alice, alice_broadcast_id, alice_charlie_id).await?;
|
||||
assert!(alice.pop_sent_msg_opt(Duration::ZERO).await.is_none());
|
||||
assert!(!remove_from_chat_contacts_table(alice, alice_broadcast_id, alice_charlie_id).await?);
|
||||
assert!(
|
||||
!remove_from_chat_contacts_table_without_trace(alice, alice_broadcast_id, alice_charlie_id)
|
||||
.await?
|
||||
);
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Tests that in a broadcast channel,
|
||||
/// the recipients can't see the identity of their fellow recipients.
|
||||
#[tokio::test(flavor = "multi_thread", worker_threads = 2)]
|
||||
@@ -2947,24 +2916,10 @@ async fn test_broadcast_change_name() -> Result<()> {
|
||||
let fiona = &tcm.fiona().await;
|
||||
|
||||
let broadcast_id = create_broadcast(alice, "Channel".to_string()).await?;
|
||||
let mut qr = get_securejoin_qr(alice, Some(broadcast_id)).await.unwrap();
|
||||
// Something goes wrong with the title, e.g. maybe it gets ellipsized
|
||||
// Note that the title always comes at the end for human readability
|
||||
qr += "+modified+title";
|
||||
|
||||
{
|
||||
tcm.section("Alice invites Bob to her channel");
|
||||
let Qr::AskJoinBroadcast { name, .. } = check_qr(bob, &qr).await? else {
|
||||
panic!();
|
||||
};
|
||||
assert_eq!(name, "Channel modified title");
|
||||
|
||||
// The channel's name gets fixed after actually joining the channel:
|
||||
let bob_chat_id = tcm.exec_securejoin_qr(bob, alice, &qr).await;
|
||||
let bob_chat = Chat::load_from_db(bob, bob_chat_id).await?;
|
||||
assert_eq!(bob_chat.name, "Channel");
|
||||
}
|
||||
let qr = get_securejoin_qr(alice, Some(broadcast_id)).await.unwrap();
|
||||
|
||||
tcm.section("Alice invites Bob to her channel");
|
||||
tcm.exec_securejoin_qr(bob, alice, &qr).await;
|
||||
tcm.section("Alice invites Fiona to her channel");
|
||||
tcm.exec_securejoin_qr(fiona, alice, &qr).await;
|
||||
|
||||
@@ -3088,31 +3043,6 @@ async fn test_broadcast_resend_to_new_member() -> Result<()> {
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[tokio::test(flavor = "multi_thread", worker_threads = 2)]
|
||||
async fn test_broadcast_resend_failed_msg_to_new_member() -> Result<()> {
|
||||
let mut tcm = TestContextManager::new();
|
||||
let alice = &tcm.alice().await;
|
||||
let bob = &tcm.bob().await;
|
||||
let fiona = &tcm.fiona().await;
|
||||
let alice_bc_id = create_broadcast(alice, "bc".to_string()).await?;
|
||||
let qr = get_securejoin_qr(alice, Some(alice_bc_id)).await.unwrap();
|
||||
|
||||
tcm.exec_securejoin_qr(bob, alice, &qr).await;
|
||||
let alice_msg_id = alice.send_text(alice_bc_id, "text").await.sender_msg_id;
|
||||
let mut msg = Message::load_from_db(alice, alice_msg_id).await?;
|
||||
message::set_msg_failed(alice, &mut msg, "error").await?;
|
||||
let fiona_bc_id = tcm.exec_securejoin_qr(fiona, alice, &qr).await;
|
||||
let resent_msg = alice.pop_sent_msg().await;
|
||||
let fiona_msg = fiona.recv_msg(&resent_msg).await;
|
||||
assert_eq!(fiona_msg.chat_id, fiona_bc_id);
|
||||
assert_eq!(fiona_msg.text, "text");
|
||||
assert_eq!(
|
||||
alice_msg_id.get_state(alice).await?,
|
||||
MessageState::OutFailed
|
||||
);
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// - Alice has multiple devices
|
||||
/// - Alice creates a broadcast and sends a message into it
|
||||
/// - Alice's second device sees the broadcast
|
||||
@@ -4765,12 +4695,10 @@ async fn test_sync_delete_chat() -> Result<()> {
|
||||
|
||||
#[tokio::test(flavor = "multi_thread", worker_threads = 2)]
|
||||
async fn test_sync_adhoc_grp() -> Result<()> {
|
||||
let mut tcm = TestContextManager::new();
|
||||
let alice0 = &tcm.alice().await;
|
||||
let alice1 = &tcm.alice().await;
|
||||
let alice0 = &TestContext::new_alice().await;
|
||||
let alice1 = &TestContext::new_alice().await;
|
||||
for a in [alice0, alice1] {
|
||||
a.set_config_bool(Config::SyncMsgs, true).await?;
|
||||
a.allow_unencrypted().await?;
|
||||
}
|
||||
|
||||
let mut chat_ids = Vec::new();
|
||||
@@ -5711,7 +5639,6 @@ async fn test_restore_backup_after_60_days() -> Result<()> {
|
||||
#[tokio::test(flavor = "multi_thread", worker_threads = 2)]
|
||||
async fn test_one_to_one_chat_no_group_member_timestamps() {
|
||||
let t = TestContext::new_alice().await;
|
||||
t.allow_unencrypted().await.unwrap();
|
||||
let chat = t.create_chat_with_contact("bob", "bob@example.com").await;
|
||||
let sent = t.send_text(chat.id, "Hi!").await;
|
||||
let payload = sent.payload;
|
||||
@@ -5925,7 +5852,6 @@ async fn test_send_delete_request_no_encryption() -> Result<()> {
|
||||
let mut tcm = TestContextManager::new();
|
||||
let alice = &tcm.alice().await;
|
||||
let bob = &tcm.bob().await;
|
||||
alice.allow_unencrypted().await?;
|
||||
let alice_chat = alice.create_email_chat(bob).await;
|
||||
|
||||
// Alice sends a message, then tries to send a deletion request which fails.
|
||||
@@ -6144,7 +6070,6 @@ async fn test_no_key_contacts_in_adhoc_chats() -> Result<()> {
|
||||
let alice = &tcm.alice().await;
|
||||
let bob = &tcm.bob().await;
|
||||
let charlie = &tcm.charlie().await;
|
||||
alice.allow_unencrypted().await?;
|
||||
|
||||
let chat_id = receive_imf(
|
||||
alice,
|
||||
@@ -6180,7 +6105,6 @@ async fn test_no_key_contacts_in_adhoc_chats() -> Result<()> {
|
||||
async fn test_create_unencrypted_group_chat() -> Result<()> {
|
||||
let mut tcm = TestContextManager::new();
|
||||
let alice = &tcm.alice().await;
|
||||
alice.allow_unencrypted().await?;
|
||||
let bob = &tcm.bob().await;
|
||||
let charlie = &tcm.charlie().await;
|
||||
|
||||
|
||||
@@ -665,7 +665,6 @@ mod tests {
|
||||
#[tokio::test(flavor = "multi_thread", worker_threads = 2)]
|
||||
async fn test_search_single_chat() -> anyhow::Result<()> {
|
||||
let t = TestContext::new_alice().await;
|
||||
t.allow_unencrypted().await?;
|
||||
|
||||
// receive a one-to-one-message
|
||||
receive_imf(
|
||||
@@ -726,7 +725,6 @@ mod tests {
|
||||
#[tokio::test(flavor = "multi_thread", worker_threads = 2)]
|
||||
async fn test_search_single_chat_without_authname() -> anyhow::Result<()> {
|
||||
let t = TestContext::new_alice().await;
|
||||
t.allow_unencrypted().await?;
|
||||
|
||||
// receive a one-to-one-message without authname set
|
||||
receive_imf(
|
||||
|
||||
+42
-12
@@ -194,6 +194,17 @@ pub enum Config {
|
||||
#[strum(props(default = "0"))] // also change MediaQuality.default() on changes
|
||||
MediaQuality,
|
||||
|
||||
/// Timer in seconds after which the message is deleted from the
|
||||
/// server.
|
||||
///
|
||||
/// 0 means messages are never deleted by Delta Chat.
|
||||
///
|
||||
/// Value 1 is treated as "delete at once": messages are deleted
|
||||
/// immediately, without moving to DeltaChat folder.
|
||||
///
|
||||
/// Default is 1 for chatmail accounts without `BccSelf`, 0 otherwise.
|
||||
DeleteServerAfter,
|
||||
|
||||
/// Timer in seconds after which the message is deleted from the
|
||||
/// device.
|
||||
///
|
||||
@@ -452,6 +463,11 @@ pub enum Config {
|
||||
/// storing the same token multiple times on the server.
|
||||
EncryptedDeviceToken,
|
||||
|
||||
/// Enables running test hooks, e.g. see `InnerContext::pre_encrypt_mime_hook`.
|
||||
/// This way is better than conditional compilation, i.e. `#[cfg(test)]`, because tests not
|
||||
/// using this still run unmodified code.
|
||||
TestHooks,
|
||||
|
||||
/// Return an error from `receive_imf_inner()`. For tests.
|
||||
SimulateReceiveImfError,
|
||||
|
||||
@@ -470,13 +486,6 @@ pub enum Config {
|
||||
/// Experimental option denoting that the current profile is shared between multiple team members.
|
||||
/// For now, the only effect of this option is that seen flags are not synchronized.
|
||||
TeamProfile,
|
||||
|
||||
/// Force encryption.
|
||||
///
|
||||
/// When enabled, unencrypted messages cannot be sent
|
||||
/// and incoming unencrypted messages are not fetched and not processed.
|
||||
#[strum(props(default = "1"))]
|
||||
ForceEncryption,
|
||||
}
|
||||
|
||||
impl Config {
|
||||
@@ -492,11 +501,7 @@ impl Config {
|
||||
pub(crate) fn is_synced(&self) -> bool {
|
||||
matches!(
|
||||
self,
|
||||
Self::Displayname
|
||||
| Self::MdnsEnabled
|
||||
| Self::Selfavatar
|
||||
| Self::Selfstatus
|
||||
| Self::ForceEncryption,
|
||||
Self::Displayname | Self::MdnsEnabled | Self::Selfavatar | Self::Selfstatus,
|
||||
)
|
||||
}
|
||||
|
||||
@@ -549,6 +554,14 @@ impl Context {
|
||||
// Default values
|
||||
let val = match key {
|
||||
Config::ConfiguredInboxFolder => Some("INBOX".to_string()),
|
||||
Config::DeleteServerAfter => {
|
||||
match !Box::pin(self.get_config_bool(Config::BccSelf)).await?
|
||||
&& Box::pin(self.is_chatmail()).await?
|
||||
{
|
||||
true => Some("1".to_string()),
|
||||
false => Some("0".to_string()),
|
||||
}
|
||||
}
|
||||
Config::Addr => self.get_config_opt(Config::ConfiguredAddr).await?,
|
||||
_ => key.get_str("default").map(|s| s.to_string()),
|
||||
};
|
||||
@@ -629,6 +642,23 @@ impl Context {
|
||||
self.get_config_bool(Config::MdnsEnabled).await
|
||||
}
|
||||
|
||||
/// Gets configured "delete_server_after" value.
|
||||
///
|
||||
/// `None` means never delete the message, `Some(0)` means delete
|
||||
/// at once, `Some(x)` means delete after `x` seconds.
|
||||
pub async fn get_config_delete_server_after(&self) -> Result<Option<i64>> {
|
||||
let val = match self
|
||||
.get_config_parsed::<i64>(Config::DeleteServerAfter)
|
||||
.await?
|
||||
.unwrap_or(0)
|
||||
{
|
||||
0 => None,
|
||||
1 => Some(0),
|
||||
x => Some(x),
|
||||
};
|
||||
Ok(val)
|
||||
}
|
||||
|
||||
/// Gets the configured provider.
|
||||
///
|
||||
/// The provider is determined by the current primary transport.
|
||||
|
||||
@@ -142,6 +142,28 @@ async fn test_mdns_default_behaviour() -> Result<()> {
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[tokio::test(flavor = "multi_thread", worker_threads = 2)]
|
||||
async fn test_delete_server_after_default() -> Result<()> {
|
||||
let t = &TestContext::new_alice().await;
|
||||
|
||||
// Check that the settings are displayed correctly.
|
||||
assert_eq!(t.get_config(Config::BccSelf).await?, Some("1".to_string()));
|
||||
assert_eq!(
|
||||
t.get_config(Config::DeleteServerAfter).await?,
|
||||
Some("0".to_string())
|
||||
);
|
||||
|
||||
// Leaving emails on the server even w/o `BccSelf` is a good default at least because other
|
||||
// MUAs do so even if the server doesn't save sent messages to some sentbox (like Gmail
|
||||
// does).
|
||||
t.set_config_bool(Config::BccSelf, false).await?;
|
||||
assert_eq!(
|
||||
t.get_config(Config::DeleteServerAfter).await?,
|
||||
Some("0".to_string())
|
||||
);
|
||||
Ok(())
|
||||
}
|
||||
|
||||
const SAVED_MESSAGES_DEDUPLICATED_FILE: &str = "969142cb84015bc135767bc2370934a.png";
|
||||
|
||||
#[tokio::test(flavor = "multi_thread", worker_threads = 2)]
|
||||
|
||||
+8
-11
@@ -680,8 +680,6 @@ async fn get_autoconfig(
|
||||
param: &EnteredLoginParam,
|
||||
param_domain: &str,
|
||||
) -> Option<Vec<ServerParams>> {
|
||||
let accept_invalid_certificates = param.certificate_checks.accept_invalid_certificates();
|
||||
|
||||
// Make sure to not encode `.` as `%2E` here.
|
||||
// Some servers like murena.io on 2024-11-01 produce incorrect autoconfig XML
|
||||
// when address is encoded.
|
||||
@@ -698,7 +696,6 @@ async fn get_autoconfig(
|
||||
"https://autoconfig.{param_domain}/mail/config-v1.1.xml?emailaddress={param_addr_urlencoded}"
|
||||
),
|
||||
¶m.addr,
|
||||
accept_invalid_certificates,
|
||||
)
|
||||
.await
|
||||
{
|
||||
@@ -710,10 +707,10 @@ async fn get_autoconfig(
|
||||
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}"
|
||||
"https://{}/.well-known/autoconfig/mail/config-v1.1.xml?emailaddress={}",
|
||||
¶m_domain, ¶m_addr_urlencoded
|
||||
),
|
||||
¶m.addr,
|
||||
accept_invalid_certificates,
|
||||
)
|
||||
.await
|
||||
{
|
||||
@@ -724,8 +721,7 @@ async fn get_autoconfig(
|
||||
// Outlook uses always SSL but different domains (this comment describes the next two steps)
|
||||
if let Ok(res) = outlk_autodiscover(
|
||||
ctx,
|
||||
format!("https://{param_domain}/autodiscover/autodiscover.xml"),
|
||||
accept_invalid_certificates,
|
||||
format!("https://{}/autodiscover/autodiscover.xml", ¶m_domain),
|
||||
)
|
||||
.await
|
||||
{
|
||||
@@ -735,8 +731,10 @@ async fn get_autoconfig(
|
||||
|
||||
if let Ok(res) = outlk_autodiscover(
|
||||
ctx,
|
||||
format!("https://autodiscover.{param_domain}/autodiscover/autodiscover.xml",),
|
||||
accept_invalid_certificates,
|
||||
format!(
|
||||
"https://autodiscover.{}/autodiscover/autodiscover.xml",
|
||||
¶m_domain
|
||||
),
|
||||
)
|
||||
.await
|
||||
{
|
||||
@@ -747,9 +745,8 @@ async fn get_autoconfig(
|
||||
// always SSL for Thunderbird's database
|
||||
if let Ok(res) = moz_autoconfigure(
|
||||
ctx,
|
||||
&format!("https://autoconfig.thunderbird.net/v1.1/{param_domain}"),
|
||||
&format!("https://autoconfig.thunderbird.net/v1.1/{}", ¶m_domain),
|
||||
¶m.addr,
|
||||
accept_invalid_certificates,
|
||||
)
|
||||
.await
|
||||
{
|
||||
|
||||
@@ -10,7 +10,7 @@ use quick_xml::events::{BytesStart, Event};
|
||||
use super::{Error, ServerParams};
|
||||
use crate::context::Context;
|
||||
use crate::log::warn;
|
||||
use crate::net::read_url_with_tls;
|
||||
use crate::net::read_url;
|
||||
use crate::provider::{Protocol, Socket};
|
||||
|
||||
#[derive(Debug)]
|
||||
@@ -249,9 +249,8 @@ pub(crate) async fn moz_autoconfigure(
|
||||
context: &Context,
|
||||
url: &str,
|
||||
addr: &str,
|
||||
accept_invalid_certificates: bool,
|
||||
) -> Result<Vec<ServerParams>, Error> {
|
||||
let xml_raw = read_url_with_tls(context, url, !accept_invalid_certificates).await?;
|
||||
let xml_raw = read_url(context, url).await?;
|
||||
|
||||
let res = parse_serverparams(addr, &xml_raw);
|
||||
if let Err(err) = &res {
|
||||
|
||||
@@ -10,7 +10,7 @@ use quick_xml::events::Event;
|
||||
use super::{Error, ServerParams};
|
||||
use crate::context::Context;
|
||||
use crate::log::warn;
|
||||
use crate::net::read_url_with_tls;
|
||||
use crate::net::read_url;
|
||||
use crate::provider::{Protocol, Socket};
|
||||
|
||||
/// Result of parsing a single `Protocol` tag.
|
||||
@@ -196,11 +196,10 @@ fn protocols_to_serverparams(protocols: Vec<ProtocolTag>) -> Vec<ServerParams> {
|
||||
pub(crate) async fn outlk_autodiscover(
|
||||
context: &Context,
|
||||
mut url: String,
|
||||
accept_invalid_certificates: bool,
|
||||
) -> Result<Vec<ServerParams>, Error> {
|
||||
/* Follow up to 10 xml-redirects (http-redirects are followed in read_url() */
|
||||
for _i in 0..10 {
|
||||
let xml_raw = read_url_with_tls(context, &url, !accept_invalid_certificates).await?;
|
||||
let xml_raw = read_url(context, &url).await?;
|
||||
let res = parse_xml(&xml_raw);
|
||||
if let Err(err) = &res {
|
||||
warn!(context, "{}", err);
|
||||
|
||||
@@ -335,7 +335,6 @@ async fn test_add_or_lookup() {
|
||||
#[tokio::test(flavor = "multi_thread", worker_threads = 2)]
|
||||
async fn test_contact_name_changes() -> Result<()> {
|
||||
let t = TestContext::new_alice().await;
|
||||
t.allow_unencrypted().await?;
|
||||
|
||||
// first message creates contact and one-to-one-chat without name set
|
||||
receive_imf(
|
||||
@@ -928,12 +927,9 @@ async fn test_synchronize_status() -> Result<()> {
|
||||
// Alice has two devices.
|
||||
let alice1 = &tcm.alice().await;
|
||||
let alice2 = &tcm.alice().await;
|
||||
alice1.allow_unencrypted().await?;
|
||||
alice2.allow_unencrypted().await?;
|
||||
|
||||
// Bob has one device.
|
||||
let bob = &tcm.bob().await;
|
||||
bob.allow_unencrypted().await?;
|
||||
|
||||
let default_status = alice1.get_config(Config::Selfstatus).await?;
|
||||
|
||||
@@ -1002,18 +998,33 @@ async fn test_selfavatar_changed_event() -> Result<()> {
|
||||
|
||||
#[tokio::test(flavor = "multi_thread", worker_threads = 2)]
|
||||
async fn test_last_seen() -> Result<()> {
|
||||
let mut tcm = TestContextManager::new();
|
||||
let alice = &tcm.alice().await;
|
||||
let bob = &tcm.bob().await;
|
||||
let alice = TestContext::new_alice().await;
|
||||
|
||||
let contact = alice.add_or_lookup_contact(bob).await;
|
||||
let (contact_id, _) = Contact::add_or_lookup(
|
||||
&alice,
|
||||
"Bob",
|
||||
&ContactAddress::new("bob@example.net")?,
|
||||
Origin::ManuallyCreated,
|
||||
)
|
||||
.await?;
|
||||
let contact = Contact::get_by_id(&alice, contact_id).await?;
|
||||
assert_eq!(contact.last_seen(), 0);
|
||||
|
||||
let msg = tcm.send_recv(bob, alice, "Hi.").await;
|
||||
let mime = br#"Subject: Hello
|
||||
Message-ID: message@example.net
|
||||
To: Alice <alice@example.org>
|
||||
From: Bob <bob@example.net>
|
||||
Content-Type: text/plain; charset=utf-8; format=flowed; delsp=no
|
||||
Chat-Version: 1.0
|
||||
Date: Sun, 22 Mar 2020 22:37:55 +0000
|
||||
|
||||
Hi."#;
|
||||
receive_imf(&alice, mime, false).await?;
|
||||
let msg = alice.get_last_msg().await;
|
||||
|
||||
let timestamp = msg.get_timestamp();
|
||||
assert!(timestamp > 0);
|
||||
let contact = Contact::get_by_id(alice, contact.id).await?;
|
||||
let contact = Contact::get_by_id(&alice, contact_id).await?;
|
||||
assert_eq!(contact.last_seen(), timestamp);
|
||||
|
||||
Ok(())
|
||||
@@ -1087,7 +1098,6 @@ async fn test_was_seen_recently_event() -> Result<()> {
|
||||
async fn test_lookup_id_by_addr_recent_ex(accept_unencrypted_chat: bool) -> Result<()> {
|
||||
let mut tcm = TestContextManager::new();
|
||||
let bob = &tcm.bob().await;
|
||||
bob.allow_unencrypted().await?;
|
||||
|
||||
let raw = include_bytes!("../../test-data/message/thunderbird_with_autocrypt.eml");
|
||||
assert!(std::str::from_utf8(raw)?.contains("Date: Thu, 24 Nov 2022 20:05:57 +0100"));
|
||||
|
||||
+26
-6
@@ -21,6 +21,7 @@ use crate::contact::{Contact, ContactId};
|
||||
use crate::debug_logging::DebugLogging;
|
||||
use crate::events::{Event, EventEmitter, EventType, Events};
|
||||
use crate::imap::{Imap, ServerMetadata};
|
||||
use crate::key::self_fingerprint;
|
||||
use crate::log::warn;
|
||||
use crate::logged_debug_assert;
|
||||
use crate::message::{self, MessageState, MsgId};
|
||||
@@ -332,6 +333,17 @@ pub struct InnerContext {
|
||||
/// `Connectivity` values for mailboxes, unordered. Used to compute the aggregate connectivity,
|
||||
/// see [`Context::get_connectivity()`].
|
||||
pub(crate) connectivities: parking_lot::Mutex<Vec<ConnectivityStore>>,
|
||||
|
||||
#[expect(clippy::type_complexity)]
|
||||
/// Transforms the root of the cryptographic payload before encryption.
|
||||
pub(crate) pre_encrypt_mime_hook: parking_lot::Mutex<
|
||||
Option<
|
||||
for<'a> fn(
|
||||
&Context,
|
||||
mail_builder::mime::MimePart<'a>,
|
||||
) -> mail_builder::mime::MimePart<'a>,
|
||||
>,
|
||||
>,
|
||||
}
|
||||
|
||||
/// The state of ongoing process.
|
||||
@@ -511,6 +523,7 @@ impl Context {
|
||||
self_fingerprint: OnceLock::new(),
|
||||
self_public_key: Mutex::new(None),
|
||||
connectivities: parking_lot::Mutex::new(Vec::new()),
|
||||
pre_encrypt_mime_hook: None.into(),
|
||||
};
|
||||
|
||||
let ctx = Context {
|
||||
@@ -830,6 +843,7 @@ impl Context {
|
||||
|
||||
/// Returns information about the context as key-value pairs.
|
||||
pub async fn get_info(&self) -> Result<BTreeMap<&'static str, String>> {
|
||||
let all_self_addrs = self.get_all_self_addrs().await?.join(", ");
|
||||
let all_transports: Vec<String> = ConfiguredLoginParam::load_all(self)
|
||||
.await?
|
||||
.into_iter()
|
||||
@@ -866,6 +880,10 @@ impl Context {
|
||||
.sql
|
||||
.count("SELECT COUNT(*) FROM public_keys;", ())
|
||||
.await?;
|
||||
let fingerprint_str = match self_fingerprint(self).await {
|
||||
Ok(fp) => fp.to_string(),
|
||||
Err(err) => format!("<key failure: {err}>"),
|
||||
};
|
||||
|
||||
let mut res = get_info();
|
||||
|
||||
@@ -927,6 +945,7 @@ impl Context {
|
||||
}
|
||||
}
|
||||
|
||||
res.insert("all_self_addrs", all_self_addrs);
|
||||
res.insert(
|
||||
"who_can_call_me",
|
||||
self.get_config_int(Config::WhoCanCallMe).await?.to_string(),
|
||||
@@ -943,6 +962,7 @@ impl Context {
|
||||
res.insert("disable_idle", disable_idle.to_string());
|
||||
res.insert("private_key_count", prv_key_cnt.to_string());
|
||||
res.insert("public_key_count", pub_key_cnt.to_string());
|
||||
res.insert("fingerprint", fingerprint_str);
|
||||
res.insert(
|
||||
"media_quality",
|
||||
self.get_config_int(Config::MediaQuality).await?.to_string(),
|
||||
@@ -953,6 +973,12 @@ impl Context {
|
||||
.await?
|
||||
.to_string(),
|
||||
);
|
||||
res.insert(
|
||||
"delete_server_after",
|
||||
self.get_config_int(Config::DeleteServerAfter)
|
||||
.await?
|
||||
.to_string(),
|
||||
);
|
||||
res.insert(
|
||||
"last_housekeeping",
|
||||
self.get_config_int(Config::LastHousekeeping)
|
||||
@@ -1030,12 +1056,6 @@ impl Context {
|
||||
"team_profile",
|
||||
self.get_config_bool(Config::TeamProfile).await?.to_string(),
|
||||
);
|
||||
res.insert(
|
||||
"force_encryption",
|
||||
self.get_config_bool(Config::ForceEncryption)
|
||||
.await?
|
||||
.to_string(),
|
||||
);
|
||||
|
||||
let elapsed = time_elapsed(&self.creation_time);
|
||||
res.insert("uptime", duration_to_str(elapsed));
|
||||
|
||||
@@ -8,7 +8,7 @@ use crate::chatlist::Chatlist;
|
||||
use crate::constants::Chattype;
|
||||
use crate::message::Message;
|
||||
use crate::receive_imf::receive_imf;
|
||||
use crate::test_utils::{E2EE_INFO_MSGS, TestContext, TestContextManager};
|
||||
use crate::test_utils::{E2EE_INFO_MSGS, TestContext};
|
||||
use crate::tools::{SystemTime, create_outgoing_rfc724_mid};
|
||||
|
||||
#[tokio::test(flavor = "multi_thread", worker_threads = 2)]
|
||||
@@ -54,7 +54,6 @@ async fn receive_msg(t: &TestContext, chat: &Chat) {
|
||||
async fn test_get_fresh_msgs_and_muted_chats() {
|
||||
// receive various mails in 3 chats
|
||||
let t = TestContext::new_alice().await;
|
||||
t.allow_unencrypted().await.unwrap();
|
||||
let bob = t.create_chat_with_contact("", "bob@g.it").await;
|
||||
let claire = t.create_chat_with_contact("", "claire@g.it").await;
|
||||
let dave = t.create_chat_with_contact("", "dave@g.it").await;
|
||||
@@ -104,7 +103,6 @@ async fn test_get_fresh_msgs_and_muted_chats() {
|
||||
#[tokio::test(flavor = "multi_thread", worker_threads = 2)]
|
||||
async fn test_get_fresh_msgs_and_muted_until() {
|
||||
let t = TestContext::new_alice().await;
|
||||
t.allow_unencrypted().await.unwrap();
|
||||
let bob = t.create_chat_with_contact("", "bob@g.it").await;
|
||||
receive_msg(&t, &bob).await;
|
||||
assert_eq!(get_chat_msgs(&t, bob.id).await.unwrap().len(), 1);
|
||||
@@ -160,7 +158,6 @@ async fn test_get_fresh_msgs_and_muted_until() {
|
||||
#[tokio::test(flavor = "multi_thread", worker_threads = 2)]
|
||||
async fn test_muted_context() -> Result<()> {
|
||||
let t = TestContext::new_alice().await;
|
||||
t.allow_unencrypted().await?;
|
||||
assert_eq!(t.get_fresh_msgs().await.unwrap().len(), 0);
|
||||
t.set_config(Config::IsMuted, Some("1")).await?;
|
||||
let chat = t.create_chat_with_contact("", "bob@g.it").await;
|
||||
@@ -327,7 +324,6 @@ async fn test_get_info_completeness() {
|
||||
#[tokio::test(flavor = "multi_thread", worker_threads = 2)]
|
||||
async fn test_search_msgs() -> Result<()> {
|
||||
let alice = TestContext::new_alice().await;
|
||||
alice.allow_unencrypted().await?;
|
||||
let self_talk = ChatId::create_for_contact(&alice, ContactId::SELF).await?;
|
||||
let chat = alice
|
||||
.create_chat_with_contact("Bob", "bob@example.org")
|
||||
@@ -389,40 +385,49 @@ async fn test_search_msgs() -> Result<()> {
|
||||
|
||||
#[tokio::test(flavor = "multi_thread", worker_threads = 2)]
|
||||
async fn test_search_unaccepted_requests() -> Result<()> {
|
||||
let mut tcm = TestContextManager::new();
|
||||
let t = &tcm.alice().await;
|
||||
let bob = &tcm.bob().await;
|
||||
bob.set_config(Config::Displayname, Some("BobBar")).await?;
|
||||
let msg = tcm.send_recv(bob, t, "hello bob, foobar test!").await;
|
||||
let chat_id = msg.get_chat_id();
|
||||
let chat = Chat::load_from_db(t, chat_id).await?;
|
||||
let t = TestContext::new_alice().await;
|
||||
receive_imf(
|
||||
&t,
|
||||
b"From: BobBar <bob@example.org>\n\
|
||||
To: alice@example.org\n\
|
||||
Subject: foo\n\
|
||||
Message-ID: <msg1234@example.org>\n\
|
||||
Chat-Version: 1.0\n\
|
||||
Date: Tue, 25 Oct 2022 13:37:00 +0000\n\
|
||||
\n\
|
||||
hello bob, foobar test!\n",
|
||||
false,
|
||||
)
|
||||
.await?;
|
||||
let chat_id = t.get_last_msg().await.get_chat_id();
|
||||
let chat = Chat::load_from_db(&t, chat_id).await?;
|
||||
assert_eq!(chat.get_type(), Chattype::Single);
|
||||
assert!(chat.is_contact_request());
|
||||
assert_eq!(Chatlist::try_load(t, 0, None, None).await?.len(), 1);
|
||||
|
||||
assert_eq!(Chatlist::try_load(&t, 0, None, None).await?.len(), 1);
|
||||
assert_eq!(
|
||||
Chatlist::try_load(t, 0, Some("BobBar"), None).await?.len(),
|
||||
Chatlist::try_load(&t, 0, Some("BobBar"), None).await?.len(),
|
||||
1
|
||||
);
|
||||
assert_eq!(t.search_msgs(None, "foobar").await?.len(), 1);
|
||||
assert_eq!(t.search_msgs(Some(chat_id), "foobar").await?.len(), 1);
|
||||
|
||||
chat_id.block(t).await?;
|
||||
chat_id.block(&t).await?;
|
||||
|
||||
assert_eq!(Chatlist::try_load(t, 0, None, None).await?.len(), 0);
|
||||
assert_eq!(Chatlist::try_load(&t, 0, None, None).await?.len(), 0);
|
||||
assert_eq!(
|
||||
Chatlist::try_load(t, 0, Some("BobBar"), None).await?.len(),
|
||||
Chatlist::try_load(&t, 0, Some("BobBar"), None).await?.len(),
|
||||
0
|
||||
);
|
||||
assert_eq!(t.search_msgs(None, "foobar").await?.len(), 0);
|
||||
assert_eq!(t.search_msgs(Some(chat_id), "foobar").await?.len(), 0);
|
||||
|
||||
let contact_ids = get_chat_contacts(t, chat_id).await?;
|
||||
Contact::unblock(t, *contact_ids.first().unwrap()).await?;
|
||||
let contact_ids = get_chat_contacts(&t, chat_id).await?;
|
||||
Contact::unblock(&t, *contact_ids.first().unwrap()).await?;
|
||||
|
||||
assert_eq!(Chatlist::try_load(t, 0, None, None).await?.len(), 1);
|
||||
assert_eq!(Chatlist::try_load(&t, 0, None, None).await?.len(), 1);
|
||||
assert_eq!(
|
||||
Chatlist::try_load(t, 0, Some("BobBar"), None).await?.len(),
|
||||
Chatlist::try_load(&t, 0, Some("BobBar"), None).await?.len(),
|
||||
1
|
||||
);
|
||||
assert_eq!(t.search_msgs(None, "foobar").await?.len(), 1);
|
||||
@@ -434,7 +439,6 @@ async fn test_search_unaccepted_requests() -> Result<()> {
|
||||
#[tokio::test(flavor = "multi_thread", worker_threads = 2)]
|
||||
async fn test_limit_search_msgs() -> Result<()> {
|
||||
let alice = TestContext::new_alice().await;
|
||||
alice.allow_unencrypted().await?;
|
||||
let chat = alice
|
||||
.create_chat_with_contact("Bob", "bob@example.org")
|
||||
.await;
|
||||
|
||||
@@ -402,7 +402,6 @@ mod tests {
|
||||
assert!(get_attachment_mime(&mail).is_some());
|
||||
|
||||
let bob = TestContext::new_bob().await;
|
||||
bob.allow_unencrypted().await?;
|
||||
receive_imf(&bob, attachment_mime, false).await?;
|
||||
let msg = bob.get_last_msg().await;
|
||||
// Subject should be prepended because the attachment doesn't have "Chat-Version".
|
||||
@@ -417,7 +416,6 @@ mod tests {
|
||||
// Desktop via MS Exchange (actually made with TB though).
|
||||
let mixed_up_mime = include_bytes!("../test-data/message/mixed-up-long.eml");
|
||||
let bob = TestContext::new_bob().await;
|
||||
bob.allow_unencrypted().await?;
|
||||
receive_imf(&bob, mixed_up_mime, false).await?;
|
||||
let msg = bob.get_last_msg().await;
|
||||
assert!(!msg.get_text().is_empty());
|
||||
|
||||
+17
-22
@@ -6,16 +6,21 @@ use anyhow::{Result, anyhow, bail, ensure};
|
||||
use deltachat_derive::{FromSql, ToSql};
|
||||
use serde::{Deserialize, Serialize};
|
||||
|
||||
use crate::config::Config;
|
||||
use crate::context::Context;
|
||||
use crate::imap::session::Session;
|
||||
use crate::log::warn;
|
||||
use crate::message::{self, Message, MsgId, rfc724_mid_exists};
|
||||
use crate::{EventType, chatlist_events, ephemeral};
|
||||
use crate::{EventType, chatlist_events};
|
||||
|
||||
pub(crate) mod post_msg_metadata;
|
||||
pub(crate) use post_msg_metadata::PostMsgMetadata;
|
||||
|
||||
/// If a message is downloaded only partially
|
||||
/// and `delete_server_after` is set to small timeouts (eg. "at once"),
|
||||
/// the user might have no chance to actually download that message.
|
||||
/// `MIN_DELETE_SERVER_AFTER` increases the timeout in this case.
|
||||
pub(crate) const MIN_DELETE_SERVER_AFTER: i64 = 48 * 60 * 60;
|
||||
|
||||
/// From this point onward outgoing messages are considered large
|
||||
/// and get a Pre-Message, which announces the Post-Message.
|
||||
/// This is only about sending so we can modify it any time.
|
||||
@@ -169,16 +174,6 @@ pub(crate) async fn download_msg(
|
||||
return Ok(None);
|
||||
}
|
||||
Box::pin(session.fetch_single_msg(context, &server_folder, server_uid, rfc724_mid)).await?;
|
||||
|
||||
let bcc_self = context.get_config_bool(Config::BccSelf).await?;
|
||||
if ephemeral::should_delete_all_downloaded_messages(bcc_self, session.is_chatmail()) {
|
||||
// Now that the message was downloaded, it likely needs to be deleted;
|
||||
// trigger a re-check by interrupting the inbox folder.
|
||||
// This is mainly needed to make the tests pass;
|
||||
// on real devices, it would be fine to delete the message at the next iteration.
|
||||
context.scheduler.interrupt_inbox().await;
|
||||
}
|
||||
|
||||
Ok(Some(()))
|
||||
}
|
||||
|
||||
@@ -363,7 +358,7 @@ mod tests {
|
||||
|
||||
use super::*;
|
||||
use crate::chat::send_msg;
|
||||
use crate::test_utils::TestContextManager;
|
||||
use crate::test_utils::TestContext;
|
||||
|
||||
#[test]
|
||||
fn test_downloadstate_values() {
|
||||
@@ -383,14 +378,12 @@ mod tests {
|
||||
|
||||
#[tokio::test(flavor = "multi_thread", worker_threads = 2)]
|
||||
async fn test_update_download_state() -> Result<()> {
|
||||
let mut tcm = TestContextManager::new();
|
||||
let t = &tcm.alice().await;
|
||||
let bob = &tcm.bob().await;
|
||||
let chat_id = t.create_chat_id(bob).await;
|
||||
let t = TestContext::new_alice().await;
|
||||
let chat = t.create_chat_with_contact("Bob", "bob@example.org").await;
|
||||
|
||||
let mut msg = Message::new_text("Hi Bob".to_owned());
|
||||
let msg_id = send_msg(t, chat_id, &mut msg).await?;
|
||||
let msg = Message::load_from_db(t, msg_id).await?;
|
||||
let msg_id = send_msg(&t, chat.id, &mut msg).await?;
|
||||
let msg = Message::load_from_db(&t, msg_id).await?;
|
||||
assert_eq!(msg.download_state(), DownloadState::Done);
|
||||
|
||||
for s in &[
|
||||
@@ -400,15 +393,17 @@ mod tests {
|
||||
DownloadState::Done,
|
||||
DownloadState::Done,
|
||||
] {
|
||||
msg_id.update_download_state(t, *s).await?;
|
||||
let msg = Message::load_from_db(t, msg_id).await?;
|
||||
msg_id.update_download_state(&t, *s).await?;
|
||||
let msg = Message::load_from_db(&t, msg_id).await?;
|
||||
assert_eq!(msg.download_state(), *s);
|
||||
}
|
||||
t.sql
|
||||
.execute("DELETE FROM msgs WHERE id=?", (msg_id,))
|
||||
.await?;
|
||||
// Nothing to do is ok.
|
||||
msg_id.update_download_state(t, DownloadState::Done).await?;
|
||||
msg_id
|
||||
.update_download_state(&t, DownloadState::Done)
|
||||
.await?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
+2
-40
@@ -42,25 +42,12 @@ impl EncryptHelper {
|
||||
compress: bool,
|
||||
seipd_version: SeipdVersion,
|
||||
) -> Result<String> {
|
||||
let sign_key = load_self_secret_key(context).await?;
|
||||
|
||||
let mut raw_message = Vec::new();
|
||||
let cursor = Cursor::new(&mut raw_message);
|
||||
mail_to_encrypt.clone().write_part(cursor).ok();
|
||||
|
||||
let ctext = self
|
||||
.encrypt_raw(context, keyring, raw_message, compress, seipd_version)
|
||||
.await?;
|
||||
Ok(ctext)
|
||||
}
|
||||
|
||||
pub async fn encrypt_raw(
|
||||
self,
|
||||
context: &Context,
|
||||
keyring: Vec<SignedPublicKey>,
|
||||
raw_message: Vec<u8>,
|
||||
compress: bool,
|
||||
seipd_version: SeipdVersion,
|
||||
) -> Result<String> {
|
||||
let sign_key = load_self_secret_key(context).await?;
|
||||
let ctext =
|
||||
pgp::pk_encrypt(raw_message, keyring, sign_key, compress, seipd_version).await?;
|
||||
|
||||
@@ -109,11 +96,9 @@ pub async fn ensure_secret_key_exists(context: &Context) -> Result<()> {
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
use crate::chat;
|
||||
use crate::chat::send_text_msg;
|
||||
use crate::config::Config;
|
||||
use crate::message::Message;
|
||||
use crate::mimeparser::SystemMessage;
|
||||
use crate::receive_imf::receive_imf;
|
||||
use crate::test_utils::{TestContext, TestContextManager};
|
||||
|
||||
@@ -157,34 +142,11 @@ Sent with my Delta Chat Messenger: https://delta.chat";
|
||||
);
|
||||
}
|
||||
|
||||
#[tokio::test(flavor = "multi_thread", worker_threads = 2)]
|
||||
async fn test_cannot_send_unencrypted_by_default() -> Result<()> {
|
||||
let mut tcm = TestContextManager::new();
|
||||
let alice = &tcm.alice().await;
|
||||
let bob = &tcm.bob().await;
|
||||
let chat = alice.create_email_chat(bob).await;
|
||||
|
||||
let mut msg = Message::new_text("Hello!".to_string());
|
||||
assert!(chat::send_msg(alice, chat.id, &mut msg).await.is_err());
|
||||
assert_eq!(
|
||||
msg.error().unwrap(),
|
||||
"\u{26a0}\u{fe0f} Your email provider example.org requires end-to-end encryption which is not setup yet."
|
||||
);
|
||||
let info_msg = alice.get_last_msg().await;
|
||||
assert_eq!(
|
||||
info_msg.get_info_type(),
|
||||
SystemMessage::InvalidUnencryptedMail
|
||||
);
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[tokio::test(flavor = "multi_thread", worker_threads = 2)]
|
||||
async fn test_chatmail_can_send_unencrypted() -> Result<()> {
|
||||
let mut tcm = TestContextManager::new();
|
||||
let bob = &tcm.bob().await;
|
||||
bob.set_config_bool(Config::IsChatmail, true).await?;
|
||||
bob.allow_unencrypted().await?;
|
||||
let bob_chat_id = receive_imf(
|
||||
bob,
|
||||
b"From: alice@example.org\n\
|
||||
|
||||
+40
-99
@@ -23,15 +23,16 @@
|
||||
//! ## Device settings
|
||||
//!
|
||||
//! In addition to per-chat ephemeral message setting, each device has
|
||||
//! a global user-configured setting that complements per-chat
|
||||
//! settings, `delete_device_after`.
|
||||
//! This setting is not synchronized among devices and applies to all
|
||||
//! two global user-configured settings that complement per-chat
|
||||
//! settings: `delete_device_after` and `delete_server_after`. These
|
||||
//! settings are not synchronized among devices and apply to all
|
||||
//! messages known to the device, including messages sent or received
|
||||
//! before configuring the setting.
|
||||
//!
|
||||
//! `delete_device_after` configures the maximum time device is
|
||||
//! storing the messages locally,
|
||||
//! but does not delete messages from the server.
|
||||
//! storing the messages locally. `delete_server_after` configures the
|
||||
//! time after which device will delete the messages it knows about
|
||||
//! from the server.
|
||||
//!
|
||||
//! ## How messages are deleted
|
||||
//!
|
||||
@@ -59,8 +60,9 @@
|
||||
//!
|
||||
//! Server deletion happens by updating the `imap` table based on
|
||||
//! the database entries which are expired either according to their
|
||||
//! ephemeral message timers.
|
||||
//! ephemeral message timers or global `delete_server_after` setting.
|
||||
|
||||
use std::cmp::max;
|
||||
use std::collections::BTreeSet;
|
||||
use std::fmt;
|
||||
use std::num::ParseIntError;
|
||||
@@ -73,11 +75,10 @@ use serde::{Deserialize, Serialize};
|
||||
use tokio::time::timeout;
|
||||
|
||||
use crate::chat::{ChatId, ChatIdBlocked, send_msg};
|
||||
use crate::config::Config;
|
||||
use crate::constants::{DC_CHAT_ID_LAST_SPECIAL, DC_CHAT_ID_TRASH};
|
||||
use crate::contact::ContactId;
|
||||
use crate::context::Context;
|
||||
use crate::download::DownloadState;
|
||||
use crate::download::MIN_DELETE_SERVER_AFTER;
|
||||
use crate::events::EventType;
|
||||
use crate::log::{LogExt, warn};
|
||||
use crate::message::{Message, MessageState, MsgId, Viewtype};
|
||||
@@ -454,14 +455,11 @@ WHERE
|
||||
Ok(rows)
|
||||
}
|
||||
|
||||
/// Locally deletes messages which are expired according to
|
||||
/// Deletes messages which are expired according to
|
||||
/// `delete_device_after` setting or `ephemeral_timestamp` column.
|
||||
///
|
||||
/// Emits relevant `MsgsChanged` and `WebxdcInstanceDeleted` events
|
||||
/// if messages are deleted.
|
||||
///
|
||||
/// Also see [`delete_expired_imap_messages`],
|
||||
/// which marks the messages for deletion on the IMAP server.
|
||||
pub(crate) async fn delete_expired_messages(context: &Context, now: i64) -> Result<()> {
|
||||
let rows = select_expired_messages(context, now).await?;
|
||||
|
||||
@@ -652,99 +650,42 @@ pub(crate) async fn ephemeral_loop(context: &Context, interrupt_receiver: Receiv
|
||||
}
|
||||
}
|
||||
|
||||
/// Schedules expired IMAP messages for deletion on the server.
|
||||
///
|
||||
/// Also see [`delete_expired_messages`],
|
||||
/// which locally deletes expired messages.
|
||||
pub(crate) async fn delete_expired_imap_messages(
|
||||
context: &Context,
|
||||
transport_id: u32,
|
||||
is_chatmail: bool,
|
||||
) -> Result<()> {
|
||||
/// Schedules expired IMAP messages for deletion.
|
||||
#[expect(clippy::arithmetic_side_effects)]
|
||||
pub(crate) async fn delete_expired_imap_messages(context: &Context) -> Result<()> {
|
||||
let now = time();
|
||||
|
||||
let bcc_self = context.get_config_bool(Config::BccSelf).await?;
|
||||
if should_delete_all_downloaded_messages(bcc_self, is_chatmail) {
|
||||
// This is the only device using this relay.
|
||||
// Mark all downloaded messages for deletion, because they are not needed anymore.
|
||||
//
|
||||
// For pre- and post-messages, `rfc724_mid` contains the post-message's Message-Id.
|
||||
// The pre-message's Message-Id is in pre_rfc724_mid, if it exists.
|
||||
//
|
||||
// Pre-messages can be deleted even if the message wasn't fully downloaded yet,
|
||||
// because it's only the post-message that hasn't been downloaded.
|
||||
context
|
||||
.sql
|
||||
.execute(
|
||||
"UPDATE imap
|
||||
SET target=''
|
||||
WHERE transport_id=?1
|
||||
AND rfc724_mid IN (
|
||||
SELECT rfc724_mid FROM msgs
|
||||
WHERE ((ephemeral_timestamp!=0 AND ephemeral_timestamp<=?2) OR download_state=?3)
|
||||
AND id>9
|
||||
UNION
|
||||
SELECT pre_rfc724_mid FROM msgs
|
||||
WHERE pre_rfc724_mid!=''
|
||||
AND id>9
|
||||
)",
|
||||
(transport_id, now, DownloadState::Done),
|
||||
)
|
||||
.await?;
|
||||
} else if bcc_self {
|
||||
// There may be other devices using this relay,
|
||||
// either because there is multi-device or because this is a classical email server.
|
||||
// Only delete expired ephemeral messages.
|
||||
context
|
||||
.sql
|
||||
.execute(
|
||||
"UPDATE imap
|
||||
SET target=''
|
||||
WHERE transport_id=?1
|
||||
AND rfc724_mid IN (
|
||||
SELECT rfc724_mid FROM msgs
|
||||
WHERE ephemeral_timestamp!=0 AND ephemeral_timestamp<=?2 AND id>9
|
||||
UNION
|
||||
SELECT pre_rfc724_mid FROM msgs
|
||||
WHERE pre_rfc724_mid!=''
|
||||
AND ephemeral_timestamp!=0 AND ephemeral_timestamp<=?2 AND id>9
|
||||
)",
|
||||
(transport_id, now),
|
||||
)
|
||||
.await?;
|
||||
} else {
|
||||
// Single device.
|
||||
// Delete all expired and encrypted messages.
|
||||
context
|
||||
.sql
|
||||
.execute(
|
||||
"UPDATE imap
|
||||
SET target=''
|
||||
WHERE transport_id=?1
|
||||
AND rfc724_mid IN (
|
||||
SELECT rfc724_mid FROM msgs
|
||||
WHERE id>9
|
||||
AND ((ephemeral_timestamp!=0 AND ephemeral_timestamp<=?2) OR
|
||||
((param GLOB '*\nc=1*' OR param GLOB 'c=1*') AND download_state=?3))
|
||||
UNION
|
||||
SELECT pre_rfc724_mid FROM msgs
|
||||
WHERE pre_rfc724_mid!=''
|
||||
AND id>9
|
||||
AND ((ephemeral_timestamp!=0 AND ephemeral_timestamp<=?2) OR
|
||||
(param GLOB '*\nc=1*' OR param GLOB 'c=1*'))
|
||||
)",
|
||||
(transport_id, now, DownloadState::Done),
|
||||
)
|
||||
.await?;
|
||||
}
|
||||
let (threshold_timestamp, threshold_timestamp_extended) =
|
||||
match context.get_config_delete_server_after().await? {
|
||||
None => (0, 0),
|
||||
Some(delete_server_after) => (
|
||||
match delete_server_after {
|
||||
// Guarantee immediate deletion.
|
||||
0 => i64::MAX,
|
||||
_ => now - delete_server_after,
|
||||
},
|
||||
now - max(delete_server_after, MIN_DELETE_SERVER_AFTER),
|
||||
),
|
||||
};
|
||||
|
||||
context
|
||||
.sql
|
||||
.execute(
|
||||
"UPDATE imap
|
||||
SET target=''
|
||||
WHERE rfc724_mid IN (
|
||||
SELECT rfc724_mid FROM msgs
|
||||
WHERE ((download_state = 0 AND timestamp < ?) OR
|
||||
(download_state != 0 AND timestamp < ?) OR
|
||||
(ephemeral_timestamp != 0 AND ephemeral_timestamp <= ?))
|
||||
)",
|
||||
(threshold_timestamp, threshold_timestamp_extended, now),
|
||||
)
|
||||
.await?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub(crate) fn should_delete_all_downloaded_messages(bcc_self: bool, is_chatmail: bool) -> bool {
|
||||
!bcc_self && is_chatmail
|
||||
}
|
||||
|
||||
/// Start ephemeral timers for seen messages if they are not started
|
||||
/// yet.
|
||||
///
|
||||
|
||||
+148
-239
@@ -9,7 +9,6 @@ use crate::download::DownloadState;
|
||||
use crate::location;
|
||||
use crate::message::markseen_msgs;
|
||||
use crate::receive_imf::receive_imf;
|
||||
use crate::test_utils;
|
||||
use crate::test_utils::{TestContext, TestContextManager};
|
||||
use crate::timesmearing::MAX_SECONDS_TO_LEND_FROM_FUTURE;
|
||||
use crate::{
|
||||
@@ -311,22 +310,20 @@ async fn test_ephemeral_timer_rollback() -> Result<()> {
|
||||
|
||||
#[tokio::test(flavor = "multi_thread", worker_threads = 2)]
|
||||
async fn test_ephemeral_delete_msgs() -> Result<()> {
|
||||
let mut tcm = TestContextManager::new();
|
||||
let t = &tcm.alice().await;
|
||||
let bob = &tcm.bob().await;
|
||||
let t = TestContext::new_alice().await;
|
||||
let self_chat = t.get_self_chat().await;
|
||||
|
||||
assert_eq!(next_expiration_timestamp(t).await, None);
|
||||
assert_eq!(next_expiration_timestamp(&t).await, None);
|
||||
|
||||
t.send_text(self_chat.id, "Saved message, which we delete manually")
|
||||
.await;
|
||||
let msg = t.get_last_msg_in(self_chat.id).await;
|
||||
msg.id.trash(t, false).await?;
|
||||
check_msg_is_deleted(t, &self_chat, msg.id).await;
|
||||
msg.id.trash(&t, false).await?;
|
||||
check_msg_is_deleted(&t, &self_chat, msg.id).await;
|
||||
|
||||
self_chat
|
||||
.id
|
||||
.set_ephemeral_timer(t, Timer::Enabled { duration: 3600 })
|
||||
.set_ephemeral_timer(&t, Timer::Enabled { duration: 3600 })
|
||||
.await
|
||||
.unwrap();
|
||||
|
||||
@@ -334,7 +331,7 @@ async fn test_ephemeral_delete_msgs() -> Result<()> {
|
||||
let now = time();
|
||||
let msg = t.send_text(self_chat.id, "Message text").await;
|
||||
|
||||
check_msg_will_be_deleted(t, msg.sender_msg_id, &self_chat, now + 3599, time() + 3601)
|
||||
check_msg_will_be_deleted(&t, msg.sender_msg_id, &self_chat, now + 3599, time() + 3601)
|
||||
.await
|
||||
.unwrap();
|
||||
|
||||
@@ -346,17 +343,17 @@ async fn test_ephemeral_delete_msgs() -> Result<()> {
|
||||
let now = time();
|
||||
let msg = t.send_text(self_chat.id, "Message text").await;
|
||||
|
||||
check_msg_will_be_deleted(t, msg.sender_msg_id, &self_chat, now + 3559, time() + 3601)
|
||||
check_msg_will_be_deleted(&t, msg.sender_msg_id, &self_chat, now + 3559, time() + 3601)
|
||||
.await
|
||||
.unwrap();
|
||||
|
||||
// Send a message to Bob which will be deleted after 1800s because of DeleteDeviceAfter.
|
||||
let bob_chat = t.create_chat(bob).await;
|
||||
let bob_chat = t.create_chat_with_contact("", "bob@example.net").await;
|
||||
let now = time();
|
||||
let msg = t.send_text(bob_chat.id, "Message text").await;
|
||||
|
||||
check_msg_will_be_deleted(
|
||||
t,
|
||||
&t,
|
||||
msg.sender_msg_id,
|
||||
&bob_chat,
|
||||
now + 1799,
|
||||
@@ -371,13 +368,13 @@ async fn test_ephemeral_delete_msgs() -> Result<()> {
|
||||
// This tests that the message is deleted at min(ephemeral deletion time, DeleteDeviceAfter deletion time).
|
||||
bob_chat
|
||||
.id
|
||||
.set_ephemeral_timer(t, Timer::Enabled { duration: 60 })
|
||||
.set_ephemeral_timer(&t, Timer::Enabled { duration: 60 })
|
||||
.await?;
|
||||
|
||||
let now = time();
|
||||
let msg = t.send_text(bob_chat.id, "Message text").await;
|
||||
|
||||
check_msg_will_be_deleted(t, msg.sender_msg_id, &bob_chat, now + 59, time() + 61)
|
||||
check_msg_will_be_deleted(&t, msg.sender_msg_id, &bob_chat, now + 59, time() + 61)
|
||||
.await
|
||||
.unwrap();
|
||||
|
||||
@@ -452,194 +449,107 @@ async fn check_msg_is_deleted(t: &TestContext, chat: &Chat, msg_id: MsgId) {
|
||||
#[tokio::test(flavor = "multi_thread", worker_threads = 2)]
|
||||
async fn test_delete_expired_imap_messages() -> Result<()> {
|
||||
let t = TestContext::new_alice().await;
|
||||
const HOUR: i64 = 60 * 60;
|
||||
let now = time();
|
||||
let transport_id: u32 = 1;
|
||||
let uidvalidity = 12345u32;
|
||||
t.set_config_bool(Config::BccSelf, false).await?;
|
||||
|
||||
async fn is_deleted(context: &Context, mid: &str) -> Result<bool> {
|
||||
Ok(context
|
||||
.sql
|
||||
.count(
|
||||
"SELECT COUNT(*) FROM imap WHERE target='' AND rfc724_mid=?",
|
||||
(mid,),
|
||||
let transport_id = 1;
|
||||
let uidvalidity = 12345;
|
||||
for (id, timestamp, ephemeral_timestamp) in &[
|
||||
(900, now - 2 * HOUR, 0),
|
||||
(1000, now - 23 * HOUR - MIN_DELETE_SERVER_AFTER, 0),
|
||||
(1010, now - 23 * HOUR, 0),
|
||||
(1020, now - 21 * HOUR, 0),
|
||||
(1030, now - 19 * HOUR, 0),
|
||||
(2000, now - 18 * HOUR, now - HOUR),
|
||||
(2020, now - 17 * HOUR, now + HOUR),
|
||||
(3000, now + HOUR, 0),
|
||||
] {
|
||||
let message_id = id.to_string();
|
||||
t.sql
|
||||
.execute(
|
||||
"INSERT INTO msgs (id, rfc724_mid, timestamp, ephemeral_timestamp) VALUES (?,?,?,?);",
|
||||
(id, &message_id, timestamp, ephemeral_timestamp),
|
||||
)
|
||||
.await?;
|
||||
t.sql
|
||||
.execute(
|
||||
"INSERT INTO imap (transport_id, rfc724_mid, folder, uid, target, uidvalidity) VALUES (?, ?,'INBOX',?, 'INBOX', ?);",
|
||||
(transport_id, &message_id, id, uidvalidity),
|
||||
)
|
||||
.await?
|
||||
== 1)
|
||||
.await?;
|
||||
}
|
||||
|
||||
async fn reset_targets(context: &Context) {
|
||||
async fn test_marked_for_deletion(context: &Context, id: u32) -> Result<()> {
|
||||
assert_eq!(
|
||||
context
|
||||
.sql
|
||||
.count(
|
||||
"SELECT COUNT(*) FROM imap WHERE target='' AND rfc724_mid=?",
|
||||
(id.to_string(),),
|
||||
)
|
||||
.await?,
|
||||
1
|
||||
);
|
||||
Ok(())
|
||||
}
|
||||
|
||||
async fn remove_uid(context: &Context, id: u32) -> Result<()> {
|
||||
context
|
||||
.sql
|
||||
.execute("UPDATE imap SET target='INBOX'", ())
|
||||
.await
|
||||
.unwrap();
|
||||
}
|
||||
|
||||
// Test messages:
|
||||
//
|
||||
// Four messages that were not split into pre- and post- message:
|
||||
// "expired@localhost" - expired ephemeral message
|
||||
// "no_expire@localhost" - non-ephemeral message
|
||||
// "no_expire_unencrypted@localhost" - non-ephemeral message, not encrypted
|
||||
// "future@localhost" - will expire in the future, but not yet
|
||||
//
|
||||
// And four messages that were split into pre- and post-message.
|
||||
// "expired_*@localhost" - has pre-msg, expired ephemeral message, not downloaded yet
|
||||
// "no_expire_*@localhost" - has pre-msg, non-ephemeral, not downloaded yet
|
||||
// "future_*@localhost" - has pre-msg, not expired yet, not downloaded yet
|
||||
// "done_*@localhost" - Fully downloaded -> post- message can be deleted
|
||||
//
|
||||
// The tuple is (rfc724_mid, ephemeral_timestamp, download_state, pre_rfc724_mid, is_encrypted)
|
||||
let msgs: [(&str, i64, DownloadState, &str, bool); 8] = [
|
||||
("expired@localhost", now - 1, DownloadState::Done, "", true),
|
||||
("no_expire@localhost", 0, DownloadState::Done, "", true),
|
||||
(
|
||||
"no_expire_unencrypted@localhost",
|
||||
0,
|
||||
DownloadState::Done,
|
||||
"",
|
||||
false,
|
||||
),
|
||||
// Use "now + 3600" rather than "now + 1", otherwise the test may be flaky
|
||||
// if it is slow and the message expires in a second
|
||||
(
|
||||
"future@localhost",
|
||||
now + 3600,
|
||||
DownloadState::Done,
|
||||
"",
|
||||
true,
|
||||
),
|
||||
(
|
||||
"expired_post@localhost",
|
||||
now - 1,
|
||||
DownloadState::Available,
|
||||
"expired_pre@localhost",
|
||||
true,
|
||||
),
|
||||
(
|
||||
"no_expire_post@localhost",
|
||||
0,
|
||||
DownloadState::Available,
|
||||
"no_expire_pre@localhost",
|
||||
true,
|
||||
),
|
||||
(
|
||||
"future_post@localhost",
|
||||
now + 3600,
|
||||
DownloadState::Available,
|
||||
"future_pre@localhost",
|
||||
true,
|
||||
),
|
||||
(
|
||||
"done_post@localhost",
|
||||
0,
|
||||
DownloadState::Done,
|
||||
"done_pre@localhost",
|
||||
true,
|
||||
),
|
||||
];
|
||||
for (rfc724_mid, ephemeral_timestamp, download_state, pre_rfc724_mid, is_encrypted) in msgs {
|
||||
t.sql
|
||||
.execute(
|
||||
"INSERT INTO msgs \
|
||||
(rfc724_mid, timestamp, ephemeral_timestamp, download_state, pre_rfc724_mid, param) \
|
||||
VALUES (?,?,?,?,?,?)",
|
||||
(
|
||||
rfc724_mid,
|
||||
now,
|
||||
ephemeral_timestamp,
|
||||
download_state,
|
||||
pre_rfc724_mid,
|
||||
if is_encrypted {
|
||||
"c=1"
|
||||
} else {
|
||||
""
|
||||
}
|
||||
),
|
||||
)
|
||||
.execute("DELETE FROM imap WHERE rfc724_mid=?", (id.to_string(),))
|
||||
.await?;
|
||||
Ok(())
|
||||
}
|
||||
let rfc724_mids: Vec<&str> = msgs
|
||||
.iter()
|
||||
.flat_map(|(rfc724_mid, _, _, pre_rfc724_mid, _is_encrypted)| {
|
||||
[*rfc724_mid, *pre_rfc724_mid]
|
||||
})
|
||||
.filter(|s| !s.is_empty())
|
||||
.collect();
|
||||
|
||||
for (i, rfc724_mid) in rfc724_mids.iter().enumerate() {
|
||||
// This should mark message 2000 for deletion.
|
||||
delete_expired_imap_messages(&t).await?;
|
||||
test_marked_for_deletion(&t, 2000).await?;
|
||||
remove_uid(&t, 2000).await?;
|
||||
// No other messages are marked for deletion.
|
||||
assert_eq!(
|
||||
t.sql
|
||||
.execute(
|
||||
"INSERT INTO imap \
|
||||
(transport_id, rfc724_mid, folder, uid, target, uidvalidity) \
|
||||
VALUES (?,?,'INBOX',?,'INBOX',?)",
|
||||
(transport_id, *rfc724_mid, (i + 1) as u32, uidvalidity),
|
||||
)
|
||||
.await?;
|
||||
}
|
||||
.count("SELECT COUNT(*) FROM imap WHERE target=''", ())
|
||||
.await?,
|
||||
0
|
||||
);
|
||||
|
||||
for (is_chatmail, other_transport, bcc_self) in [
|
||||
(false, false, false),
|
||||
(false, false, true),
|
||||
(false, true, false),
|
||||
(false, true, true),
|
||||
(true, false, false),
|
||||
(true, false, true),
|
||||
(true, true, false),
|
||||
(true, true, true),
|
||||
] {
|
||||
println!(
|
||||
"Testing combination is_chatmail={is_chatmail}, other_transport={other_transport}, bcc_self={bcc_self}"
|
||||
);
|
||||
t.set_config(Config::DeleteServerAfter, Some(&*(25 * HOUR).to_string()))
|
||||
.await?;
|
||||
delete_expired_imap_messages(&t).await?;
|
||||
test_marked_for_deletion(&t, 1000).await?;
|
||||
|
||||
t.set_config_bool(Config::BccSelf, bcc_self).await?;
|
||||
MsgId::new(1000)
|
||||
.update_download_state(&t, DownloadState::Available)
|
||||
.await?;
|
||||
t.sql
|
||||
.execute("UPDATE imap SET target=folder WHERE rfc724_mid='1000'", ())
|
||||
.await?;
|
||||
delete_expired_imap_messages(&t).await?;
|
||||
test_marked_for_deletion(&t, 1000).await?; // Delete downloadable anyway.
|
||||
remove_uid(&t, 1000).await?;
|
||||
|
||||
delete_expired_imap_messages(
|
||||
&t,
|
||||
if other_transport {
|
||||
transport_id + 1
|
||||
} else {
|
||||
transport_id
|
||||
},
|
||||
is_chatmail,
|
||||
)
|
||||
t.set_config(Config::DeleteServerAfter, Some(&*(22 * HOUR).to_string()))
|
||||
.await?;
|
||||
delete_expired_imap_messages(&t).await?;
|
||||
test_marked_for_deletion(&t, 1010).await?;
|
||||
t.sql
|
||||
.execute("UPDATE imap SET target=folder WHERE rfc724_mid='1010'", ())
|
||||
.await?;
|
||||
|
||||
if other_transport {
|
||||
// Nothing should be deleted on another transport
|
||||
for rfc724_mid in &rfc724_mids {
|
||||
assert_eq!(is_deleted(&t, rfc724_mid).await?, false);
|
||||
}
|
||||
continue;
|
||||
}
|
||||
MsgId::new(1010)
|
||||
.update_download_state(&t, DownloadState::Available)
|
||||
.await?;
|
||||
delete_expired_imap_messages(&t).await?;
|
||||
// Keep downloadable for now.
|
||||
assert_eq!(
|
||||
t.sql
|
||||
.count("SELECT COUNT(*) FROM imap WHERE target=''", ())
|
||||
.await?,
|
||||
0
|
||||
);
|
||||
|
||||
assert_eq!(is_deleted(&t, "expired@localhost").await?, true);
|
||||
assert_eq!(is_deleted(&t, "no_expire@localhost").await?, !bcc_self);
|
||||
assert_eq!(
|
||||
is_deleted(&t, "no_expire_unencrypted@localhost").await?,
|
||||
is_chatmail && !bcc_self
|
||||
);
|
||||
assert_eq!(is_deleted(&t, "future@localhost").await?, !bcc_self);
|
||||
assert_eq!(is_deleted(&t, "expired_post@localhost").await?, true);
|
||||
assert_eq!(is_deleted(&t, "expired_pre@localhost").await?, true);
|
||||
assert_eq!(is_deleted(&t, "no_expire_post@localhost").await?, false);
|
||||
assert_eq!(is_deleted(&t, "no_expire_pre@localhost").await?, !bcc_self);
|
||||
assert_eq!(is_deleted(&t, "future_post@localhost").await?, false);
|
||||
assert_eq!(is_deleted(&t, "future_pre@localhost").await?, !bcc_self);
|
||||
assert_eq!(is_deleted(&t, "done_pre@localhost").await?, !bcc_self);
|
||||
assert_eq!(is_deleted(&t, "done_post@localhost").await?, !bcc_self);
|
||||
|
||||
reset_targets(&t).await;
|
||||
}
|
||||
|
||||
// With BccSelf=true, non-expired messages are kept even if `is_chatmail` is true
|
||||
t.set_config_bool(Config::BccSelf, true).await?;
|
||||
delete_expired_imap_messages(&t, transport_id, true).await?;
|
||||
assert_eq!(is_deleted(&t, "expired@localhost").await?, true);
|
||||
assert_eq!(is_deleted(&t, "no_expire@localhost").await?, false);
|
||||
assert_eq!(is_deleted(&t, "done_pre@localhost").await?, false);
|
||||
assert_eq!(is_deleted(&t, "done_post@localhost").await?, false);
|
||||
t.set_config(Config::DeleteServerAfter, Some("1")).await?;
|
||||
delete_expired_imap_messages(&t).await?;
|
||||
test_marked_for_deletion(&t, 3000).await?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
@@ -647,54 +557,50 @@ async fn test_delete_expired_imap_messages() -> Result<()> {
|
||||
// Regression test for a bug in the timer rollback protection.
|
||||
#[tokio::test(flavor = "multi_thread", worker_threads = 2)]
|
||||
async fn test_ephemeral_timer_references() -> Result<()> {
|
||||
let mut tcm = TestContextManager::new();
|
||||
let alice = &tcm.alice().await;
|
||||
let bob = &tcm.bob().await;
|
||||
let alice = TestContext::new_alice().await;
|
||||
|
||||
// Message with Message-ID <first@example.com> and no timer is received.
|
||||
let encrypted_msg = test_utils::encrypt_raw_message(
|
||||
bob,
|
||||
&[alice],
|
||||
b"From: Bob <bob@example.net>\r\n\
|
||||
To: Alice <alice@example.org>\r\n\
|
||||
Chat-Version: 1.0\r\n\
|
||||
Subject: Subject\r\n\
|
||||
Message-ID: <first@example.com>\r\n\
|
||||
Date: Sun, 22 Mar 2020 00:10:00 +0000\r\n\
|
||||
\r\n\
|
||||
hello\r\n",
|
||||
receive_imf(
|
||||
&alice,
|
||||
b"From: Bob <bob@example.com>\n\
|
||||
To: Alice <alice@example.org>\n\
|
||||
Chat-Version: 1.0\n\
|
||||
Subject: Subject\n\
|
||||
Message-ID: <first@example.com>\n\
|
||||
Date: Sun, 22 Mar 2020 00:10:00 +0000\n\
|
||||
\n\
|
||||
hello\n",
|
||||
false,
|
||||
)
|
||||
.await?;
|
||||
receive_imf(alice, encrypted_msg.as_bytes(), false).await?;
|
||||
|
||||
let msg = alice.get_last_msg().await;
|
||||
let chat_id = msg.chat_id;
|
||||
assert_eq!(chat_id.get_ephemeral_timer(alice).await?, Timer::Disabled);
|
||||
assert_eq!(chat_id.get_ephemeral_timer(&alice).await?, Timer::Disabled);
|
||||
|
||||
// Message with Message-ID <second@example.com> is received.
|
||||
let encrypted_msg = test_utils::encrypt_raw_message(
|
||||
bob,
|
||||
&[alice],
|
||||
b"From: Bob <bob@example.net>\r\n\
|
||||
To: Alice <alice@example.org>\r\n\
|
||||
Chat-Version: 1.0\r\n\
|
||||
Subject: Subject\r\n\
|
||||
Message-ID: <second@example.com>\r\n\
|
||||
Date: Sun, 22 Mar 2020 00:11:00 +0000\r\n\
|
||||
Ephemeral-Timer: 60\r\n\
|
||||
\r\n\
|
||||
second message\r\n",
|
||||
receive_imf(
|
||||
&alice,
|
||||
b"From: Bob <bob@example.com>\n\
|
||||
To: Alice <alice@example.org>\n\
|
||||
Chat-Version: 1.0\n\
|
||||
Subject: Subject\n\
|
||||
Message-ID: <second@example.com>\n\
|
||||
Date: Sun, 22 Mar 2020 00:11:00 +0000\n\
|
||||
Ephemeral-Timer: 60\n\
|
||||
\n\
|
||||
second message\n",
|
||||
false,
|
||||
)
|
||||
.await?;
|
||||
receive_imf(alice, encrypted_msg.as_bytes(), false).await?;
|
||||
assert_eq!(
|
||||
chat_id.get_ephemeral_timer(alice).await?,
|
||||
chat_id.get_ephemeral_timer(&alice).await?,
|
||||
Timer::Enabled { duration: 60 }
|
||||
);
|
||||
let msg = alice.get_last_msg().await;
|
||||
|
||||
// Message is deleted when its timer expires.
|
||||
msg.id.trash(alice, false).await?;
|
||||
msg.id.trash(&alice, false).await?;
|
||||
|
||||
// Message with Message-ID <third@example.com>, referencing <first@example.com> and
|
||||
// <second@example.com>, is received. The message <second@example.come> is not in the
|
||||
@@ -708,26 +614,25 @@ async fn test_ephemeral_timer_references() -> Result<()> {
|
||||
//
|
||||
// The message also contains a quote of the first message to test that only References:
|
||||
// header and not In-Reply-To: is consulted by the rollback protection.
|
||||
let encrypted_msg = test_utils::encrypt_raw_message(
|
||||
bob,
|
||||
&[alice],
|
||||
b"From: Bob <bob@example.net>\r\n\
|
||||
To: Alice <alice@example.org>\r\n\
|
||||
Chat-Version: 1.0\r\n\
|
||||
Subject: Subject\r\n\
|
||||
Message-ID: <third@example.com>\r\n\
|
||||
Date: Sun, 22 Mar 2020 00:12:00 +0000\r\n\
|
||||
References: <first@example.com> <second@example.com>\r\n\
|
||||
In-Reply-To: <first@example.com>\r\n\
|
||||
\r\n\
|
||||
> hello\r\n",
|
||||
receive_imf(
|
||||
&alice,
|
||||
b"From: Bob <bob@example.com>\n\
|
||||
To: Alice <alice@example.org>\n\
|
||||
Chat-Version: 1.0\n\
|
||||
Subject: Subject\n\
|
||||
Message-ID: <third@example.com>\n\
|
||||
Date: Sun, 22 Mar 2020 00:12:00 +0000\n\
|
||||
References: <first@example.com> <second@example.com>\n\
|
||||
In-Reply-To: <first@example.com>\n\
|
||||
\n\
|
||||
> hello\n",
|
||||
false,
|
||||
)
|
||||
.await?;
|
||||
receive_imf(alice, encrypted_msg.as_bytes(), false).await?;
|
||||
|
||||
let msg = alice.get_last_msg().await;
|
||||
assert_eq!(
|
||||
msg.chat_id.get_ephemeral_timer(alice).await?,
|
||||
msg.chat_id.get_ephemeral_timer(&alice).await?,
|
||||
Timer::Disabled
|
||||
);
|
||||
|
||||
@@ -739,20 +644,24 @@ async fn test_ephemeral_timer_references() -> Result<()> {
|
||||
// successful reconnection.
|
||||
#[tokio::test(flavor = "multi_thread", worker_threads = 2)]
|
||||
async fn test_ephemeral_msg_offline() -> Result<()> {
|
||||
let mut tcm = TestContextManager::new();
|
||||
let alice = &tcm.alice().await;
|
||||
let bob = &tcm.bob().await;
|
||||
let chat = alice.create_chat(bob).await;
|
||||
let alice = TestContext::new_alice().await;
|
||||
let chat = alice
|
||||
.create_chat_with_contact("Bob", "bob@example.org")
|
||||
.await;
|
||||
let duration = 60;
|
||||
chat.id
|
||||
.set_ephemeral_timer(alice, Timer::Enabled { duration })
|
||||
.set_ephemeral_timer(&alice, Timer::Enabled { duration })
|
||||
.await?;
|
||||
let mut msg = Message::new_text("hi".to_string());
|
||||
assert!(chat::send_msg_sync(alice, chat.id, &mut msg).await.is_err());
|
||||
assert!(
|
||||
chat::send_msg_sync(&alice, chat.id, &mut msg)
|
||||
.await
|
||||
.is_err()
|
||||
);
|
||||
let stmt = "SELECT COUNT(*) FROM smtp WHERE msg_id=?";
|
||||
assert!(alice.sql.exists(stmt, (msg.id,)).await?);
|
||||
let now = time();
|
||||
check_msg_will_be_deleted(alice, msg.id, &chat, now, now + i64::from(duration) + 1).await?;
|
||||
check_msg_will_be_deleted(&alice, msg.id, &chat, now, now + i64::from(duration) + 1).await?;
|
||||
assert!(alice.sql.exists(stmt, (msg.id,)).await?);
|
||||
|
||||
Ok(())
|
||||
|
||||
@@ -521,7 +521,6 @@ mod test_chatlist_events {
|
||||
#[tokio::test(flavor = "multi_thread", worker_threads = 2)]
|
||||
async fn test_adhoc_group() -> Result<()> {
|
||||
let alice = TestContext::new_alice().await;
|
||||
alice.allow_unencrypted().await?;
|
||||
let mime = br#"Subject: First thread
|
||||
Message-ID: first@example.org
|
||||
To: Alice <alice@example.org>, Bob <bob@example.net>
|
||||
|
||||
+16
-21
@@ -287,7 +287,6 @@ impl MsgId {
|
||||
mod tests {
|
||||
use super::*;
|
||||
use crate::chat::{self, Chat, forward_msgs, save_msgs};
|
||||
|
||||
use crate::constants;
|
||||
use crate::contact::ContactId;
|
||||
use crate::message::{MessengerMessage, Viewtype};
|
||||
@@ -305,7 +304,7 @@ mod tests {
|
||||
<html><head>
|
||||
<meta http-equiv="Content-Type" content="text/html; charset=utf-8" />
|
||||
<meta name="color-scheme" content="light dark" />
|
||||
</head><body dir="auto" style="unicode-bidi: plaintext">
|
||||
</head><body>
|
||||
This message does not have Content-Type nor Subject.<br/>
|
||||
</body></html>
|
||||
"#
|
||||
@@ -323,7 +322,7 @@ This message does not have Content-Type nor Subject.<br/>
|
||||
<html><head>
|
||||
<meta http-equiv="Content-Type" content="text/html; charset=utf-8" />
|
||||
<meta name="color-scheme" content="light dark" />
|
||||
</head><body dir="auto" style="unicode-bidi: plaintext">
|
||||
</head><body>
|
||||
message with a non-UTF-8 encoding: äöüßÄÖÜ<br/>
|
||||
</body></html>
|
||||
"#
|
||||
@@ -342,7 +341,7 @@ message with a non-UTF-8 encoding: äöüßÄÖÜ<br/>
|
||||
<html><head>
|
||||
<meta http-equiv="Content-Type" content="text/html; charset=utf-8" />
|
||||
<meta name="color-scheme" content="light dark" />
|
||||
</head><body dir="auto" style="unicode-bidi: plaintext">
|
||||
</head><body>
|
||||
This line ends with a space and will be merged with the next one due to format=flowed.<br/>
|
||||
<br/>
|
||||
This line does not end with a space<br/>
|
||||
@@ -363,7 +362,7 @@ and will be wrapped as usual.<br/>
|
||||
<html><head>
|
||||
<meta http-equiv="Content-Type" content="text/html; charset=utf-8" />
|
||||
<meta name="color-scheme" content="light dark" />
|
||||
</head><body dir="auto" style="unicode-bidi: plaintext">
|
||||
</head><body>
|
||||
mime-modified should not be set set as there is no html and no special stuff;<br/>
|
||||
although not being a delta-message.<br/>
|
||||
test some special html-characters as < > and & but also " and ' :)<br/>
|
||||
@@ -451,7 +450,6 @@ test some special html-characters as < > and & but also " and &#x
|
||||
// alice receives a non-delta html-message
|
||||
let mut tcm = TestContextManager::new();
|
||||
let alice = &tcm.alice().await;
|
||||
alice.allow_unencrypted().await?;
|
||||
let chat = alice
|
||||
.create_chat_with_contact("", "sender@testrun.org")
|
||||
.await;
|
||||
@@ -485,7 +483,6 @@ test some special html-characters as < > and & but also " and &#x
|
||||
|
||||
// bob: check that bob also got the html-part of the forwarded message
|
||||
let bob = &tcm.bob().await;
|
||||
bob.allow_unencrypted().await?;
|
||||
let chat_bob = bob.create_chat_with_contact("", "alice@example.org").await;
|
||||
async fn check_receiver(ctx: &TestContext, chat: &Chat, sender: &TestContext) {
|
||||
let msg = ctx.recv_msg(&sender.pop_sent_msg().await).await;
|
||||
@@ -523,30 +520,31 @@ test some special html-characters as < > and & but also " and &#x
|
||||
|
||||
#[tokio::test(flavor = "multi_thread", worker_threads = 2)]
|
||||
async fn test_html_save_msg() -> Result<()> {
|
||||
let mut tcm = TestContextManager::new();
|
||||
let alice = &tcm.alice().await;
|
||||
alice.allow_unencrypted().await?;
|
||||
// Alice receives a non-delta html-message
|
||||
let alice = TestContext::new_alice().await;
|
||||
let chat = alice
|
||||
.create_chat_with_contact("", "sender@testrun.org")
|
||||
.await;
|
||||
let raw = include_bytes!("../test-data/message/text_alt_plain_html.eml");
|
||||
receive_imf(alice, raw, false).await?;
|
||||
receive_imf(&alice, raw, false).await?;
|
||||
let msg = alice.get_last_msg_in(chat.get_id()).await;
|
||||
|
||||
// Alice saves the message
|
||||
let self_chat = alice.get_self_chat().await;
|
||||
save_msgs(alice, &[msg.id]).await?;
|
||||
save_msgs(&alice, &[msg.id]).await?;
|
||||
let saved_msg = alice.get_last_msg_in(self_chat.get_id()).await;
|
||||
assert_ne!(saved_msg.id, msg.id);
|
||||
assert_eq!(saved_msg.get_original_msg_id(alice).await?.unwrap(), msg.id);
|
||||
assert_eq!(
|
||||
saved_msg.get_original_msg_id(&alice).await?.unwrap(),
|
||||
msg.id
|
||||
);
|
||||
assert!(!saved_msg.is_forwarded()); // UI should not flag "saved messages" as "forwarded"
|
||||
assert_ne!(saved_msg.get_from_id(), ContactId::SELF);
|
||||
assert_eq!(saved_msg.get_from_id(), msg.get_from_id());
|
||||
assert_eq!(saved_msg.is_dc_message, MessengerMessage::No);
|
||||
assert!(saved_msg.get_text().contains("this is plain"));
|
||||
assert!(saved_msg.has_html());
|
||||
let html = saved_msg.get_id().get_html(alice).await?.unwrap();
|
||||
let html = saved_msg.get_id().get_html(&alice).await?.unwrap();
|
||||
assert!(html.contains("this is <b>html</b>"));
|
||||
|
||||
Ok(())
|
||||
@@ -557,7 +555,6 @@ test some special html-characters as < > and & but also " and &#x
|
||||
let mut tcm = TestContextManager::new();
|
||||
// Alice receives a non-delta html-message
|
||||
let alice = &tcm.alice().await;
|
||||
alice.allow_unencrypted().await.unwrap();
|
||||
let chat = alice
|
||||
.create_chat_with_contact("", "sender@testrun.org")
|
||||
.await;
|
||||
@@ -621,20 +618,18 @@ test some special html-characters as < > and & but also " and &#x
|
||||
|
||||
#[tokio::test(flavor = "multi_thread", worker_threads = 2)]
|
||||
async fn test_cp1252_html() -> Result<()> {
|
||||
let mut tcm = TestContextManager::new();
|
||||
let alice = &tcm.alice().await;
|
||||
alice.allow_unencrypted().await?;
|
||||
let t = TestContext::new_alice().await;
|
||||
receive_imf(
|
||||
alice,
|
||||
&t,
|
||||
include_bytes!("../test-data/message/cp1252-html.eml"),
|
||||
false,
|
||||
)
|
||||
.await?;
|
||||
let msg = alice.get_last_msg().await;
|
||||
let msg = t.get_last_msg().await;
|
||||
assert_eq!(msg.viewtype, Viewtype::Text);
|
||||
assert!(msg.text.contains("foo bar ä ö ü ß"));
|
||||
assert!(msg.has_html());
|
||||
let html = msg.get_id().get_html(alice).await?.unwrap();
|
||||
let html = msg.get_id().get_html(&t).await?.unwrap();
|
||||
println!("{html}");
|
||||
assert!(html.contains("foo bar ä ö ü ß"));
|
||||
Ok(())
|
||||
|
||||
+12
-33
@@ -21,6 +21,9 @@ use futures_lite::FutureExt;
|
||||
use ratelimit::Ratelimit;
|
||||
use url::Url;
|
||||
|
||||
use crate::calls::{
|
||||
UnresolvedIceServer, create_fallback_ice_servers, create_ice_servers_from_metadata,
|
||||
};
|
||||
use crate::chat::{self, ChatId, ChatIdBlocked, add_device_msg};
|
||||
use crate::chatlist_events;
|
||||
use crate::config::Config;
|
||||
@@ -46,10 +49,6 @@ use crate::tools::{self, create_id, duration_to_str, time};
|
||||
use crate::transport::{
|
||||
ConfiguredLoginParam, ConfiguredServerLoginParam, prioritize_server_login_params,
|
||||
};
|
||||
use crate::{
|
||||
calls::{UnresolvedIceServer, create_fallback_ice_servers, create_ice_servers_from_metadata},
|
||||
ephemeral::delete_expired_imap_messages,
|
||||
};
|
||||
|
||||
pub(crate) mod capabilities;
|
||||
mod client;
|
||||
@@ -526,12 +525,6 @@ impl Imap {
|
||||
context.scheduler.interrupt_ephemeral_task().await;
|
||||
}
|
||||
|
||||
// Mark expired messages for deletion. Note that `delete_expired_imap_messages` is
|
||||
// not well optimized and should not be called before fetching.
|
||||
delete_expired_imap_messages(context, session.transport_id(), session.is_chatmail())
|
||||
.await
|
||||
.context("delete_expired_imap_messages")?;
|
||||
|
||||
session
|
||||
.move_delete_messages(context, watch_folder)
|
||||
.await
|
||||
@@ -1052,12 +1045,15 @@ impl Session {
|
||||
if target.is_empty() {
|
||||
self.delete_message_batch(context, &uid_set, rowid_set)
|
||||
.await
|
||||
.with_context(|| format!("cannot delete batch of messages {uid_set:?}"))?;
|
||||
.with_context(|| format!("cannot delete batch of messages {:?}", &uid_set))?;
|
||||
} else {
|
||||
self.move_message_batch(context, &uid_set, rowid_set, &target)
|
||||
.await
|
||||
.with_context(|| {
|
||||
format!("cannot move batch of messages {uid_set:?} to folder {target:?}",)
|
||||
format!(
|
||||
"cannot move batch of messages {:?} to folder {:?}",
|
||||
&uid_set, target
|
||||
)
|
||||
})?;
|
||||
}
|
||||
}
|
||||
@@ -1291,10 +1287,9 @@ impl Session {
|
||||
|
||||
for (request_uids, set) in build_sequence_sets(&request_uids)? {
|
||||
info!(context, "Starting UID FETCH of message set \"{}\".", set);
|
||||
let mut fetch_responses = self
|
||||
.uid_fetch(&set, BODY_FULL)
|
||||
.await
|
||||
.with_context(|| format!("fetching messages {set} from folder {folder:?}"))?;
|
||||
let mut fetch_responses = self.uid_fetch(&set, BODY_FULL).await.with_context(|| {
|
||||
format!("fetching messages {} from folder \"{}\"", &set, folder)
|
||||
})?;
|
||||
|
||||
// Map from UIDs to unprocessed FETCH results. We put unprocessed FETCH results here
|
||||
// when we want to process other messages first.
|
||||
@@ -1386,8 +1381,6 @@ impl Session {
|
||||
"Passing message UID {} to receive_imf().", request_uid
|
||||
);
|
||||
let res = receive_imf_inner(context, rfc724_mid, body, is_seen).await;
|
||||
|
||||
// If there was an error receiving the message, show a device message:
|
||||
let received_msg = match res {
|
||||
Err(err) => {
|
||||
warn!(context, "receive_imf error: {err:#}.");
|
||||
@@ -2003,26 +1996,12 @@ pub(crate) async fn prefetch_should_download(
|
||||
// prevent_rename=true as this might be a mailing list message and in this case it would be bad if we rename the contact.
|
||||
// (prevent_rename is the last argument of from_field_to_contact_id())
|
||||
|
||||
// New SecureJoin is fully encrypted,
|
||||
// but for compatibility we still download legacy `Secure-Join: vc-request` messages.
|
||||
let is_legacy_securejoin = headers.get_header_value(HeaderDef::SecureJoin).is_some();
|
||||
|
||||
let is_encrypted = headers
|
||||
.get_header_value(HeaderDef::ContentType)
|
||||
.is_some_and(|content_type| {
|
||||
mailparse::parse_content_type(&content_type).mimetype == "multipart/encrypted"
|
||||
});
|
||||
|
||||
if flags.any(|f| f == Flag::Draft) {
|
||||
info!(context, "Ignoring draft message");
|
||||
return Ok(false);
|
||||
}
|
||||
|
||||
let should_download = maybe_ndn
|
||||
|| (!blocked_contact
|
||||
&& (is_legacy_securejoin
|
||||
|| is_encrypted
|
||||
|| !context.get_config_bool(Config::ForceEncryption).await?));
|
||||
let should_download = !blocked_contact || maybe_ndn;
|
||||
Ok(should_download)
|
||||
}
|
||||
|
||||
|
||||
@@ -21,8 +21,6 @@ const PREFETCH_FLAGS: &str = "(UID RFC822.SIZE BODY.PEEK[HEADER.FIELDS (\
|
||||
DATE \
|
||||
X-MICROSOFT-ORIGINAL-MESSAGE-ID \
|
||||
FROM \
|
||||
CONTENT-TYPE \
|
||||
SECURE-JOIN \
|
||||
CHAT-VERSION \
|
||||
CHAT-IS-POST-MESSAGE \
|
||||
AUTOCRYPT-SETUP-MESSAGE\
|
||||
|
||||
+19
-8
@@ -980,15 +980,20 @@ mod tests {
|
||||
|
||||
// Check that the settings are displayed correctly.
|
||||
assert_eq!(
|
||||
context1.get_config(Config::BccSelf).await?,
|
||||
context1.get_config(Config::DeleteServerAfter).await?,
|
||||
Some("0".to_string())
|
||||
);
|
||||
context1.set_config_bool(Config::IsChatmail, true).await?;
|
||||
assert_eq!(
|
||||
context1.get_config(Config::BccSelf).await?,
|
||||
Some("0".to_string())
|
||||
);
|
||||
assert_eq!(
|
||||
context1.get_config(Config::DeleteServerAfter).await?,
|
||||
Some("1".to_string())
|
||||
);
|
||||
|
||||
assert_eq!(context1.get_config_bool(Config::IsMuted).await?, false);
|
||||
context1.set_config_bool(Config::IsMuted, true).await?;
|
||||
assert_eq!(context1.get_config_bool(Config::IsMuted).await?, true);
|
||||
|
||||
assert_eq!(context1.get_config_delete_server_after().await?, Some(0));
|
||||
imex(context1, ImexMode::ExportBackup, backup_dir.path(), None).await?;
|
||||
let _event = context1
|
||||
.evtracker
|
||||
@@ -1005,9 +1010,15 @@ mod tests {
|
||||
assert!(context2.is_configured().await?);
|
||||
assert!(context2.is_chatmail().await?);
|
||||
for ctx in [context1, context2] {
|
||||
// BccSelf should be enabled automatically when exporting a backup
|
||||
assert_eq!(ctx.get_config_bool(Config::BccSelf).await?, true);
|
||||
assert_eq!(ctx.get_config_bool(Config::IsMuted).await?, true);
|
||||
assert_eq!(
|
||||
ctx.get_config(Config::BccSelf).await?,
|
||||
Some("1".to_string())
|
||||
);
|
||||
assert_eq!(
|
||||
ctx.get_config(Config::DeleteServerAfter).await?,
|
||||
Some("0".to_string())
|
||||
);
|
||||
assert_eq!(ctx.get_config_delete_server_after().await?, None);
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
@@ -427,16 +427,18 @@ mod tests {
|
||||
let self_chat = ctx1.get_self_chat().await;
|
||||
let msgs = get_chat_msgs(&ctx1, self_chat.id).await.unwrap();
|
||||
assert_eq!(msgs.len(), 2);
|
||||
let ChatItem::Message { msg_id } = msgs.first().unwrap() else {
|
||||
panic!("wrong chat item");
|
||||
let msgid = match msgs.first().unwrap() {
|
||||
ChatItem::Message { msg_id } => msg_id,
|
||||
_ => panic!("wrong chat item"),
|
||||
};
|
||||
let msg = Message::load_from_db(&ctx1, *msg_id).await.unwrap();
|
||||
let msg = Message::load_from_db(&ctx1, *msgid).await.unwrap();
|
||||
let text = msg.get_text();
|
||||
assert_eq!(text, "hi there");
|
||||
let ChatItem::Message { msg_id } = msgs.get(1).unwrap() else {
|
||||
panic!("wrong chat item");
|
||||
let msgid = match msgs.get(1).unwrap() {
|
||||
ChatItem::Message { msg_id } => msg_id,
|
||||
_ => panic!("wrong chat item"),
|
||||
};
|
||||
let msg = Message::load_from_db(&ctx1, *msg_id).await.unwrap();
|
||||
let msg = Message::load_from_db(&ctx1, *msgid).await.unwrap();
|
||||
|
||||
let path = msg.get_file(&ctx1).unwrap();
|
||||
assert_eq!(
|
||||
|
||||
+3
-8
@@ -570,11 +570,9 @@ pub async fn preconfigure_keypair(context: &Context, secret_data: &str) -> Resul
|
||||
pub struct Fingerprint(Vec<u8>);
|
||||
|
||||
impl Fingerprint {
|
||||
/// Creates new fingerprint.
|
||||
///
|
||||
/// It is 160-bit (20 bytes) for v4 keys and 32 bytes for v6 keys.
|
||||
/// Creates new 160-bit (20 bytes) fingerprint.
|
||||
pub fn new(v: Vec<u8>) -> Fingerprint {
|
||||
debug_assert!(v.len() == 20 || v.len() == 32);
|
||||
debug_assert_eq!(v.len(), 20);
|
||||
Fingerprint(v)
|
||||
}
|
||||
|
||||
@@ -627,10 +625,7 @@ impl std::str::FromStr for Fingerprint {
|
||||
.filter(|&c| c.is_ascii_hexdigit())
|
||||
.collect();
|
||||
let v: Vec<u8> = hex::decode(&hex_repr)?;
|
||||
ensure!(
|
||||
v.len() == 20 || v.len() == 32,
|
||||
"wrong fingerprint length: {hex_repr}"
|
||||
);
|
||||
ensure!(v.len() == 20, "wrong fingerprint length: {hex_repr}");
|
||||
let fp = Fingerprint::new(v);
|
||||
Ok(fp)
|
||||
}
|
||||
|
||||
+20
-25
@@ -871,7 +871,6 @@ mod tests {
|
||||
use crate::config::Config;
|
||||
use crate::message::MessageState;
|
||||
use crate::receive_imf::receive_imf;
|
||||
use crate::test_utils;
|
||||
use crate::test_utils::{ExpectedEvents, TestContext, TestContextManager};
|
||||
use crate::tools::SystemTime;
|
||||
|
||||
@@ -940,15 +939,12 @@ mod tests {
|
||||
/// Tests that location.kml is hidden.
|
||||
#[tokio::test(flavor = "multi_thread", worker_threads = 2)]
|
||||
async fn receive_location_kml() -> Result<()> {
|
||||
let mut tcm = TestContextManager::new();
|
||||
let alice = &tcm.alice().await;
|
||||
let bob = &tcm.bob().await;
|
||||
let alice = TestContext::new_alice().await;
|
||||
|
||||
let encrypted_message = test_utils::encrypt_raw_message(
|
||||
bob,
|
||||
&[alice],
|
||||
receive_imf(
|
||||
&alice,
|
||||
br#"Subject: Hello
|
||||
Message-ID: <hello@example.net>
|
||||
Message-ID: hello@example.net
|
||||
To: Alice <alice@example.org>
|
||||
From: Bob <bob@example.net>
|
||||
Date: Mon, 20 Dec 2021 00:00:00 +0000
|
||||
@@ -956,15 +952,14 @@ Chat-Version: 1.0
|
||||
Content-Type: text/plain; charset=utf-8; format=flowed; delsp=no
|
||||
|
||||
Text message."#,
|
||||
false,
|
||||
)
|
||||
.await?;
|
||||
receive_imf(alice, encrypted_message.as_bytes(), false).await?;
|
||||
let received_msg = alice.get_last_msg().await;
|
||||
assert_eq!(received_msg.text, "Text message.");
|
||||
|
||||
let encrypted_message = test_utils::encrypt_raw_message(
|
||||
bob,
|
||||
&[alice],
|
||||
receive_imf(
|
||||
&alice,
|
||||
br#"Subject: locations
|
||||
MIME-Version: 1.0
|
||||
To: <alice@example.org>
|
||||
@@ -991,14 +986,16 @@ Content-Disposition: attachment; filename="location.kml"
|
||||
</Document>
|
||||
</kml>
|
||||
|
||||
--U8BOG8qNXfB0GgLiQ3PKUjlvdIuLRF--"#).await?;
|
||||
receive_imf(alice, encrypted_message.as_bytes(), false).await?;
|
||||
--U8BOG8qNXfB0GgLiQ3PKUjlvdIuLRF--"#,
|
||||
false,
|
||||
)
|
||||
.await?;
|
||||
|
||||
// Received location message is not visible, last message stays the same.
|
||||
let received_msg2 = alice.get_last_msg().await;
|
||||
assert_eq!(received_msg2.id, received_msg.id);
|
||||
|
||||
let locations = get_range(alice, None, None, 0, 0).await?;
|
||||
let locations = get_range(&alice, None, None, 0, 0).await?;
|
||||
assert_eq!(locations.len(), 1);
|
||||
Ok(())
|
||||
}
|
||||
@@ -1006,13 +1003,10 @@ Content-Disposition: attachment; filename="location.kml"
|
||||
/// Tests that `location.kml` is not hidden and not seen if it contains a message.
|
||||
#[tokio::test(flavor = "multi_thread", worker_threads = 2)]
|
||||
async fn receive_visible_location_kml() -> Result<()> {
|
||||
let mut tcm = TestContextManager::new();
|
||||
let alice = &tcm.alice().await;
|
||||
let bob = &tcm.bob().await;
|
||||
let alice = TestContext::new_alice().await;
|
||||
|
||||
let encrypted_message = test_utils::encrypt_raw_message(
|
||||
bob,
|
||||
&[alice],
|
||||
receive_imf(
|
||||
&alice,
|
||||
br#"Subject: locations
|
||||
MIME-Version: 1.0
|
||||
To: <alice@example.org>
|
||||
@@ -1040,15 +1034,16 @@ Content-Disposition: attachment; filename="location.kml"
|
||||
</Document>
|
||||
</kml>
|
||||
|
||||
--U8BOG8qNXfB0GgLiQ3PKUjlvdIuLRF--"#).await?;
|
||||
|
||||
receive_imf(alice, encrypted_message.as_bytes(), false).await?;
|
||||
--U8BOG8qNXfB0GgLiQ3PKUjlvdIuLRF--"#,
|
||||
false,
|
||||
)
|
||||
.await?;
|
||||
|
||||
let received_msg = alice.get_last_msg().await;
|
||||
assert_eq!(received_msg.text, "Text message.");
|
||||
assert_eq!(received_msg.state, MessageState::InFresh);
|
||||
|
||||
let locations = get_range(alice, None, None, 0, 0).await?;
|
||||
let locations = get_range(&alice, None, None, 0, 0).await?;
|
||||
assert_eq!(locations.len(), 1);
|
||||
Ok(())
|
||||
}
|
||||
|
||||
@@ -56,15 +56,6 @@ pub enum EnteredCertificateChecks {
|
||||
AcceptInvalidCertificates2 = 3,
|
||||
}
|
||||
|
||||
impl EnteredCertificateChecks {
|
||||
pub(crate) fn accept_invalid_certificates(self) -> bool {
|
||||
matches!(
|
||||
self,
|
||||
Self::AcceptInvalidCertificates | Self::AcceptInvalidCertificates2
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
/// Login parameters for a single IMAP server.
|
||||
#[derive(Debug, Default, Clone, PartialEq, Eq, Serialize, Deserialize)]
|
||||
pub struct EnteredImapLoginParam {
|
||||
|
||||
+55
-72
@@ -140,20 +140,8 @@ SELECT ?1, rfc724_mid, pre_rfc724_mid, timestamp, ?, ? FROM msgs WHERE id=?1
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Returns whether the message state is updated to `OutDelivered`.
|
||||
pub(crate) async fn set_delivered(self, context: &Context) -> Result<bool> {
|
||||
if context
|
||||
.sql
|
||||
.execute(
|
||||
// Only update `OutPending` i.e. if the message is (re-)sent to all chat members.
|
||||
"UPDATE msgs SET state=?, error='' WHERE id=? AND state=?",
|
||||
(MessageState::OutDelivered, self, MessageState::OutPending),
|
||||
)
|
||||
.await?
|
||||
== 0
|
||||
{
|
||||
return Ok(false);
|
||||
}
|
||||
pub(crate) async fn set_delivered(self, context: &Context) -> Result<()> {
|
||||
update_msg_state(context, self, MessageState::OutDelivered).await?;
|
||||
let chat_id: Option<ChatId> = context
|
||||
.sql
|
||||
.query_get_value("SELECT chat_id FROM msgs WHERE id=?", (self,))
|
||||
@@ -165,7 +153,7 @@ SELECT ?1, rfc724_mid, pre_rfc724_mid, timestamp, ?, ? FROM msgs WHERE id=?1
|
||||
if let Some(chat_id) = chat_id {
|
||||
chatlist_events::emit_chatlist_item_changed(context, chat_id);
|
||||
}
|
||||
Ok(true)
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Bad evil escape hatch.
|
||||
@@ -234,7 +222,7 @@ SELECT ?1, rfc724_mid, pre_rfc724_mid, timestamp, ?, ? FROM msgs WHERE id=?1
|
||||
} else {
|
||||
msg.timestamp_sort
|
||||
});
|
||||
ret += &format!("Received: {s}");
|
||||
ret += &format!("Received: {}", &s);
|
||||
ret += "\n";
|
||||
}
|
||||
|
||||
@@ -313,7 +301,7 @@ SELECT ?1, rfc724_mid, pre_rfc724_mid, timestamp, ?, ? FROM msgs WHERE id=?1
|
||||
ret += "Type: ";
|
||||
ret += &format!("{}", msg.viewtype);
|
||||
ret += "\n";
|
||||
ret += &format!("Mimetype: {}\n", msg.get_filemime().unwrap_or_default());
|
||||
ret += &format!("Mimetype: {}\n", &msg.get_filemime().unwrap_or_default());
|
||||
}
|
||||
let w = msg.param.get_int(Param::Width).unwrap_or_default();
|
||||
let h = msg.param.get_int(Param::Height).unwrap_or_default();
|
||||
@@ -324,7 +312,6 @@ SELECT ?1, rfc724_mid, pre_rfc724_mid, timestamp, ?, ? FROM msgs WHERE id=?1
|
||||
if duration != 0 {
|
||||
ret += &format!("Duration: {duration} ms\n",);
|
||||
}
|
||||
ret += &format!("\nDatabase ID: {}", msg.id);
|
||||
if !msg.rfc724_mid.is_empty() {
|
||||
ret += &format!("\nMessage-ID: {}", msg.rfc724_mid);
|
||||
|
||||
@@ -615,31 +602,6 @@ impl Message {
|
||||
Ok(msg)
|
||||
}
|
||||
|
||||
/// Loads the message with given Message-ID from the database.
|
||||
///
|
||||
/// Cannot return a trashed message.
|
||||
pub async fn load_by_rfc724_mid_optional(
|
||||
context: &Context,
|
||||
rfc724_mid: &str,
|
||||
) -> Result<Option<Message>> {
|
||||
if let Some(msg_id) = context
|
||||
.sql
|
||||
.query_row_optional(
|
||||
"SELECT id FROM msgs WHERE rfc724_mid=? AND chat_id != ?",
|
||||
(rfc724_mid, DC_CHAT_ID_TRASH),
|
||||
|row| {
|
||||
let msg_id: MsgId = row.get(0)?;
|
||||
Ok(msg_id)
|
||||
},
|
||||
)
|
||||
.await?
|
||||
{
|
||||
Self::load_from_db_optional(context, msg_id).await
|
||||
} else {
|
||||
Ok(None)
|
||||
}
|
||||
}
|
||||
|
||||
/// Returns additional text which is appended to the message's text field
|
||||
/// when it is loaded from the database.
|
||||
/// Currently this is used to add infomation to pre-messages of what the download will be and how large it is
|
||||
@@ -1419,17 +1381,19 @@ pub enum MessageState {
|
||||
/// IMAP and MDN may be sent.
|
||||
InSeen = 16,
|
||||
|
||||
// Deprecated 2024-12-07. Removed 2026-04.
|
||||
// OutPreparing = 18,
|
||||
/// For files which need time to be prepared before they can be
|
||||
/// sent, the message enters this state before
|
||||
/// OutPending.
|
||||
///
|
||||
/// Deprecated 2024-12-07.
|
||||
OutPreparing = 18,
|
||||
|
||||
/// Message saved as draft.
|
||||
OutDraft = 19,
|
||||
|
||||
/// The user has pressed the "send" button but the message is not
|
||||
/// yet sent and is pending in some way. Maybe we're offline (no
|
||||
/// checkmark).
|
||||
///
|
||||
/// This state means that the message is being (re-)sent to all chat members. It shalln't be
|
||||
/// used e.g. for resending only to a new broadcast member.
|
||||
OutPending = 20,
|
||||
|
||||
/// *Unrecoverable* error (*recoverable* errors result in pending
|
||||
@@ -1456,6 +1420,7 @@ impl std::fmt::Display for MessageState {
|
||||
Self::InFresh => "Fresh",
|
||||
Self::InNoticed => "Noticed",
|
||||
Self::InSeen => "Seen",
|
||||
Self::OutPreparing => "Preparing",
|
||||
Self::OutDraft => "Draft",
|
||||
Self::OutPending => "Pending",
|
||||
Self::OutFailed => "Failed",
|
||||
@@ -1472,7 +1437,7 @@ impl MessageState {
|
||||
use MessageState::*;
|
||||
matches!(
|
||||
self,
|
||||
OutPending | OutDelivered | OutMdnRcvd // OutMdnRcvd can still fail because it could be a group message and only some recipients failed.
|
||||
OutPreparing | OutPending | OutDelivered | OutMdnRcvd // OutMdnRcvd can still fail because it could be a group message and only some recipients failed.
|
||||
)
|
||||
}
|
||||
|
||||
@@ -1481,7 +1446,7 @@ impl MessageState {
|
||||
use MessageState::*;
|
||||
matches!(
|
||||
self,
|
||||
OutDraft | OutPending | OutFailed | OutDelivered | OutMdnRcvd
|
||||
OutPreparing | OutDraft | OutPending | OutFailed | OutDelivered | OutMdnRcvd
|
||||
)
|
||||
}
|
||||
|
||||
@@ -2042,6 +2007,13 @@ pub(crate) async fn update_msg_state(
|
||||
Ok(())
|
||||
}
|
||||
|
||||
// as we do not cut inside words, this results in about 32-42 characters.
|
||||
// Do not use too long subjects - we add a tag after the subject which gets truncated by the clients otherwise.
|
||||
// It should also be very clear, the subject is _not_ the whole message.
|
||||
// The value is also used for CC:-summaries
|
||||
|
||||
// Context functions to work with messages
|
||||
|
||||
pub(crate) async fn set_msg_failed(
|
||||
context: &Context,
|
||||
msg: &mut Message,
|
||||
@@ -2133,52 +2105,63 @@ pub async fn get_request_msg_cnt(context: &Context) -> usize {
|
||||
}
|
||||
|
||||
/// Estimates the number of messages that will be deleted
|
||||
/// by the `set_config()`-option `delete_device_after`.
|
||||
/// by the options `delete_device_after` or `delete_server_after`.
|
||||
///
|
||||
/// This is typically used to show the estimated impact to the user
|
||||
/// before actually enabling deletion of old messages.
|
||||
///
|
||||
/// Messages in the "Saved Messages" chat are not counted as they will not be deleted automatically.
|
||||
/// If `from_server` is true,
|
||||
/// estimate deletion count for server,
|
||||
/// otherwise estimate deletion count for device.
|
||||
///
|
||||
/// Parameters:
|
||||
/// - `from_server`: Deprecated, pass `false` here
|
||||
/// - `seconds`: Count messages older than the given number of seconds.
|
||||
/// Count messages older than the given number of `seconds`.
|
||||
///
|
||||
/// Returns the number of messages that are older than the given number of seconds.
|
||||
/// Messages in the "saved messages" folder are not counted as they will not be deleted automatically.
|
||||
#[expect(clippy::arithmetic_side_effects)]
|
||||
pub async fn estimate_deletion_cnt(
|
||||
context: &Context,
|
||||
from_server: bool,
|
||||
seconds: i64,
|
||||
) -> Result<usize> {
|
||||
ensure!(
|
||||
!from_server,
|
||||
"The `delete_server_after` config option was removed. You need to pass `false` for `from_server`"
|
||||
);
|
||||
|
||||
let self_chat_id = ChatIdBlocked::lookup_by_contact(context, ContactId::SELF)
|
||||
.await?
|
||||
.map(|c| c.id)
|
||||
.unwrap_or_default();
|
||||
let threshold_timestamp = time() - seconds;
|
||||
|
||||
let cnt = context
|
||||
.sql
|
||||
.count(
|
||||
"SELECT COUNT(*)
|
||||
let cnt = if from_server {
|
||||
context
|
||||
.sql
|
||||
.count(
|
||||
"SELECT COUNT(*)
|
||||
FROM msgs m
|
||||
WHERE m.id > ?
|
||||
AND timestamp < ?
|
||||
AND chat_id != ?
|
||||
AND EXISTS (SELECT * FROM imap WHERE rfc724_mid=m.rfc724_mid);",
|
||||
(DC_MSG_ID_LAST_SPECIAL, threshold_timestamp, self_chat_id),
|
||||
)
|
||||
.await?
|
||||
} else {
|
||||
context
|
||||
.sql
|
||||
.count(
|
||||
"SELECT COUNT(*)
|
||||
FROM msgs m
|
||||
WHERE m.id > ?
|
||||
AND timestamp < ?
|
||||
AND chat_id != ?
|
||||
AND chat_id != ? AND hidden = 0;",
|
||||
(
|
||||
DC_MSG_ID_LAST_SPECIAL,
|
||||
threshold_timestamp,
|
||||
self_chat_id,
|
||||
DC_CHAT_ID_TRASH,
|
||||
),
|
||||
)
|
||||
.await?;
|
||||
(
|
||||
DC_MSG_ID_LAST_SPECIAL,
|
||||
threshold_timestamp,
|
||||
self_chat_id,
|
||||
DC_CHAT_ID_TRASH,
|
||||
),
|
||||
)
|
||||
.await?
|
||||
};
|
||||
Ok(cnt)
|
||||
}
|
||||
|
||||
|
||||
@@ -56,25 +56,28 @@ async fn test_get_width_height() {
|
||||
|
||||
#[tokio::test(flavor = "multi_thread", worker_threads = 2)]
|
||||
async fn test_quote_basic() {
|
||||
let mut tcm = TestContextManager::new();
|
||||
let alice = &tcm.alice().await;
|
||||
let bob = &tcm.bob().await;
|
||||
let d = TestContext::new().await;
|
||||
let ctx = &d.ctx;
|
||||
|
||||
let chat = alice.create_chat(bob).await;
|
||||
ctx.set_config(Config::ConfiguredAddr, Some("self@example.com"))
|
||||
.await
|
||||
.unwrap();
|
||||
|
||||
let chat = d.create_chat_with_contact("", "dest@example.com").await;
|
||||
let mut msg = Message::new_text("Quoted message".to_string());
|
||||
|
||||
// Message has to be sent such that it gets saved to db.
|
||||
chat::send_msg(alice, chat.id, &mut msg).await.unwrap();
|
||||
chat::send_msg(ctx, chat.id, &mut msg).await.unwrap();
|
||||
assert!(!msg.rfc724_mid.is_empty());
|
||||
|
||||
let mut msg2 = Message::new(Viewtype::Text);
|
||||
msg2.set_quote(alice, Some(&msg))
|
||||
msg2.set_quote(ctx, Some(&msg))
|
||||
.await
|
||||
.expect("can't set quote");
|
||||
assert_eq!(msg2.quoted_text().unwrap(), msg.get_text());
|
||||
|
||||
let quoted_msg = msg2
|
||||
.quoted_message(alice)
|
||||
.quoted_message(ctx)
|
||||
.await
|
||||
.expect("error while retrieving quoted message")
|
||||
.expect("quoted message not found");
|
||||
@@ -108,8 +111,6 @@ async fn test_unencrypted_quote_encrypted_message() -> Result<()> {
|
||||
|
||||
let alice = &tcm.alice().await;
|
||||
let bob = &tcm.bob().await;
|
||||
alice.allow_unencrypted().await?;
|
||||
bob.allow_unencrypted().await?;
|
||||
|
||||
tcm.section("Bob sends encrypted message to Alice");
|
||||
let alice_chat = alice.create_chat(bob).await;
|
||||
@@ -138,14 +139,23 @@ async fn test_unencrypted_quote_encrypted_message() -> Result<()> {
|
||||
#[tokio::test(flavor = "multi_thread", worker_threads = 2)]
|
||||
async fn test_get_chat_id() {
|
||||
// Alice receives a message that pops up as a contact request
|
||||
let mut tcm = TestContextManager::new();
|
||||
let alice = &tcm.alice().await;
|
||||
let bob = &tcm.bob().await;
|
||||
let chat_id = bob.create_chat_id(alice).await;
|
||||
let sent = bob.send_text(chat_id, "hello").await;
|
||||
let msg = alice.recv_msg(&sent).await;
|
||||
let alice = TestContext::new_alice().await;
|
||||
receive_imf(
|
||||
&alice,
|
||||
b"From: Bob <bob@example.com>\n\
|
||||
To: alice@example.org\n\
|
||||
Chat-Version: 1.0\n\
|
||||
Message-ID: <123@example.com>\n\
|
||||
Date: Fri, 29 Jan 2021 21:37:55 +0000\n\
|
||||
\n\
|
||||
hello\n",
|
||||
false,
|
||||
)
|
||||
.await
|
||||
.unwrap();
|
||||
|
||||
// check chat-id of this message
|
||||
let msg = alice.get_last_msg().await;
|
||||
assert!(!msg.get_chat_id().is_special());
|
||||
assert_eq!(msg.get_text(), "hello".to_string());
|
||||
}
|
||||
@@ -455,9 +465,7 @@ async fn test_get_state() -> Result<()> {
|
||||
|
||||
#[tokio::test(flavor = "multi_thread", worker_threads = 2)]
|
||||
async fn test_is_bot() -> Result<()> {
|
||||
let mut tcm = TestContextManager::new();
|
||||
let alice = &tcm.alice().await;
|
||||
alice.allow_unencrypted().await?;
|
||||
let alice = TestContext::new_alice().await;
|
||||
|
||||
// Alice receives an auto-generated non-chat message.
|
||||
//
|
||||
@@ -465,7 +473,7 @@ async fn test_is_bot() -> Result<()> {
|
||||
// in which case the message should be marked as bot-generated,
|
||||
// but the contact should not.
|
||||
receive_imf(
|
||||
alice,
|
||||
&alice,
|
||||
b"From: Claire <claire@example.com>\n\
|
||||
To: alice@example.org\n\
|
||||
Message-ID: <789@example.com>\n\
|
||||
@@ -479,12 +487,12 @@ async fn test_is_bot() -> Result<()> {
|
||||
let msg = alice.get_last_msg().await;
|
||||
assert_eq!(msg.get_text(), "hello".to_string());
|
||||
assert!(msg.is_bot());
|
||||
let contact = Contact::get_by_id(alice, msg.from_id).await?;
|
||||
let contact = Contact::get_by_id(&alice, msg.from_id).await?;
|
||||
assert!(!contact.is_bot());
|
||||
|
||||
// Alice receives a message from Bob the bot.
|
||||
receive_imf(
|
||||
alice,
|
||||
&alice,
|
||||
b"From: Bob <bob@example.com>\n\
|
||||
To: alice@example.org\n\
|
||||
Chat-Version: 1.0\n\
|
||||
@@ -499,12 +507,12 @@ async fn test_is_bot() -> Result<()> {
|
||||
let msg = alice.get_last_msg().await;
|
||||
assert_eq!(msg.get_text(), "hello".to_string());
|
||||
assert!(msg.is_bot());
|
||||
let contact = Contact::get_by_id(alice, msg.from_id).await?;
|
||||
let contact = Contact::get_by_id(&alice, msg.from_id).await?;
|
||||
assert!(contact.is_bot());
|
||||
|
||||
// Alice receives a message from Bob who is not the bot anymore.
|
||||
receive_imf(
|
||||
alice,
|
||||
&alice,
|
||||
b"From: Bob <bob@example.com>\n\
|
||||
To: alice@example.org\n\
|
||||
Chat-Version: 1.0\n\
|
||||
@@ -518,7 +526,7 @@ async fn test_is_bot() -> Result<()> {
|
||||
let msg = alice.get_last_msg().await;
|
||||
assert_eq!(msg.get_text(), "hello again".to_string());
|
||||
assert!(!msg.is_bot());
|
||||
let contact = Contact::get_by_id(alice, msg.from_id).await?;
|
||||
let contact = Contact::get_by_id(&alice, msg.from_id).await?;
|
||||
assert!(!contact.is_bot());
|
||||
|
||||
Ok(())
|
||||
@@ -619,15 +627,19 @@ def hello():
|
||||
|
||||
#[tokio::test(flavor = "multi_thread", worker_threads = 2)]
|
||||
async fn test_delete_msgs_offline() -> Result<()> {
|
||||
let mut tcm = TestContextManager::new();
|
||||
let alice = &tcm.alice().await;
|
||||
let bob = &tcm.bob().await;
|
||||
let chat_id = alice.create_chat_id(bob).await;
|
||||
let alice = TestContext::new_alice().await;
|
||||
let chat = alice
|
||||
.create_chat_with_contact("Bob", "bob@example.org")
|
||||
.await;
|
||||
let mut msg = Message::new_text("hi".to_string());
|
||||
assert!(chat::send_msg_sync(alice, chat_id, &mut msg).await.is_err());
|
||||
assert!(
|
||||
chat::send_msg_sync(&alice, chat.id, &mut msg)
|
||||
.await
|
||||
.is_err()
|
||||
);
|
||||
let stmt = "SELECT COUNT(*) FROM smtp WHERE msg_id=?";
|
||||
assert!(alice.sql.exists(stmt, (msg.id,)).await?);
|
||||
delete_msgs(alice, &[msg.id]).await?;
|
||||
delete_msgs(&alice, &[msg.id]).await?;
|
||||
assert!(!alice.sql.exists(stmt, (msg.id,)).await?);
|
||||
|
||||
Ok(())
|
||||
|
||||
+27
-2
@@ -1168,6 +1168,12 @@ impl MimeFactory {
|
||||
_ => None,
|
||||
};
|
||||
|
||||
if context.get_config_bool(Config::TestHooks).await?
|
||||
&& let Some(hook) = &*context.pre_encrypt_mime_hook.lock()
|
||||
{
|
||||
message = hook(context, message);
|
||||
}
|
||||
|
||||
let encrypted = if let Some(shared_secret) = shared_secret {
|
||||
let sign = true;
|
||||
encrypt_helper
|
||||
@@ -1933,13 +1939,32 @@ pub(crate) fn render_outer_message(
|
||||
/// Takes the encrypted part, wraps it in a MimePart,
|
||||
/// and sets the appropriate Content-Type for the outer message
|
||||
pub(crate) fn wrap_encrypted_part(encrypted: String) -> MimePart<'static> {
|
||||
// XXX: additional newline is needed
|
||||
// to pass filtermail at
|
||||
// <https://github.com/deltachat/chatmail/blob/4d915f9800435bf13057d41af8d708abd34dbfa8/chatmaild/src/chatmaild/filtermail.py#L84-L86>:
|
||||
let encrypted = encrypted + "\n";
|
||||
|
||||
MimePart::new(
|
||||
"multipart/encrypted; protocol=\"application/pgp-encrypted\"",
|
||||
vec![
|
||||
// Autocrypt part 1
|
||||
MimePart::new("application/pgp-encrypted", "Version: 1\r\n"),
|
||||
MimePart::new("application/pgp-encrypted", "Version: 1\r\n").header(
|
||||
"Content-Description",
|
||||
mail_builder::headers::raw::Raw::new("PGP/MIME version identification"),
|
||||
),
|
||||
// Autocrypt part 2
|
||||
MimePart::new("application/octet-stream", encrypted),
|
||||
MimePart::new(
|
||||
"application/octet-stream; name=\"encrypted.asc\"",
|
||||
encrypted,
|
||||
)
|
||||
.header(
|
||||
"Content-Description",
|
||||
mail_builder::headers::raw::Raw::new("OpenPGP encrypted message"),
|
||||
)
|
||||
.header(
|
||||
"Content-Disposition",
|
||||
mail_builder::headers::raw::Raw::new("inline; filename=\"encrypted.asc\";"),
|
||||
),
|
||||
],
|
||||
)
|
||||
}
|
||||
|
||||
@@ -19,7 +19,6 @@ use crate::headerdef::HeaderDef;
|
||||
use crate::message;
|
||||
use crate::mimeparser::MimeMessage;
|
||||
use crate::receive_imf::receive_imf;
|
||||
use crate::test_utils;
|
||||
use crate::test_utils::{TestContext, TestContextManager, get_chat_msg};
|
||||
use crate::tools::SystemTime;
|
||||
|
||||
@@ -115,7 +114,6 @@ fn test_header_encoding() {
|
||||
#[tokio::test(flavor = "multi_thread", worker_threads = 2)]
|
||||
async fn test_manually_set_subject() -> Result<()> {
|
||||
let t = TestContext::new_alice().await;
|
||||
t.allow_unencrypted().await?;
|
||||
let chat = t.create_chat_with_contact("bob", "bob@example.org").await;
|
||||
|
||||
let mut msg = Message::new(Viewtype::Text);
|
||||
@@ -134,13 +132,14 @@ async fn test_subject_from_mua() {
|
||||
// 1.: Receive a mail from an MUA
|
||||
assert_eq!(
|
||||
msg_to_subject_str(
|
||||
b"From: Bob <bob@example.net>\r\n\
|
||||
To: alice@example.org\r\n\
|
||||
Subject: Antw: Chat: hello\r\n\
|
||||
Message-ID: <2222@example.net>\r\n\
|
||||
Date: Sun, 22 Mar 2020 22:37:56 +0000\r\n\
|
||||
\r\n\
|
||||
hello\r\n"
|
||||
b"Received: (Postfix, from userid 1000); Mon, 4 Dec 2006 14:51:39 +0100 (CET)\n\
|
||||
From: Bob <bob@example.com>\n\
|
||||
To: alice@example.org\n\
|
||||
Subject: Antw: Chat: hello\n\
|
||||
Message-ID: <2222@example.com>\n\
|
||||
Date: Sun, 22 Mar 2020 22:37:56 +0000\n\
|
||||
\n\
|
||||
hello\n"
|
||||
)
|
||||
.await,
|
||||
"Re: Chat: hello"
|
||||
@@ -148,13 +147,14 @@ async fn test_subject_from_mua() {
|
||||
|
||||
assert_eq!(
|
||||
msg_to_subject_str(
|
||||
b"From: Bob <bob@example.net>\r\n\
|
||||
To: alice@example.org\r\n\
|
||||
Subject: Infos: 42\r\n\
|
||||
Message-ID: <2222@example.net>\r\n\
|
||||
Date: Sun, 22 Mar 2020 22:37:56 +0000\r\n\
|
||||
\r\n\
|
||||
hello\r\n"
|
||||
b"Received: (Postfix, from userid 1000); Mon, 4 Dec 2006 14:51:39 +0100 (CET)\n\
|
||||
From: Bob <bob@example.com>\n\
|
||||
To: alice@example.org\n\
|
||||
Subject: Infos: 42\n\
|
||||
Message-ID: <2222@example.com>\n\
|
||||
Date: Sun, 22 Mar 2020 22:37:56 +0000\n\
|
||||
\n\
|
||||
hello\n"
|
||||
)
|
||||
.await,
|
||||
"Re: Infos: 42"
|
||||
@@ -166,14 +166,15 @@ async fn test_subject_from_dc() {
|
||||
// 2. Receive a message from Delta Chat
|
||||
assert_eq!(
|
||||
msg_to_subject_str(
|
||||
b"From: bob@example.net\r\n\
|
||||
To: alice@example.org\r\n\
|
||||
Subject: Chat: hello\r\n\
|
||||
Chat-Version: 1.0\r\n\
|
||||
Message-ID: <2223@example.net>\r\n\
|
||||
Date: Sun, 22 Mar 2020 22:37:56 +0000\r\n\
|
||||
\r\n\
|
||||
hello\r\n"
|
||||
b"Received: (Postfix, from userid 1000); Mon, 4 Dec 2006 14:51:39 +0100 (CET)\n\
|
||||
From: bob@example.com\n\
|
||||
To: alice@example.org\n\
|
||||
Subject: Chat: hello\n\
|
||||
Chat-Version: 1.0\n\
|
||||
Message-ID: <2223@example.com>\n\
|
||||
Date: Sun, 22 Mar 2020 22:37:56 +0000\n\
|
||||
\n\
|
||||
hello\n"
|
||||
)
|
||||
.await,
|
||||
"Re: Chat: hello"
|
||||
@@ -184,12 +185,10 @@ async fn test_subject_from_dc() {
|
||||
async fn test_subject_outgoing() {
|
||||
// 3. Send the first message to a new contact
|
||||
let t = TestContext::new_alice().await;
|
||||
t.allow_unencrypted().await.unwrap();
|
||||
|
||||
assert_eq!(first_subject_str(t).await, "Message from alice@example.org");
|
||||
|
||||
let t = TestContext::new_alice().await;
|
||||
t.allow_unencrypted().await.unwrap();
|
||||
t.set_config(Config::Displayname, Some("Alice"))
|
||||
.await
|
||||
.unwrap();
|
||||
@@ -200,27 +199,29 @@ async fn test_subject_outgoing() {
|
||||
async fn test_subject_unicode() {
|
||||
// 4. Receive messages with unicode characters and make sure that we do not panic (we do not care about the result)
|
||||
msg_to_subject_str(
|
||||
"From: bob@example.net\r\n\
|
||||
To: alice@example.org\r\n\
|
||||
Subject: äääää\r\n\
|
||||
Chat-Version: 1.0\r\n\
|
||||
Message-ID: <2893@example.com>\r\n\
|
||||
Date: Sun, 22 Mar 2020 22:37:56 +0000\r\n\
|
||||
\r\n\
|
||||
hello\r\n"
|
||||
"Received: (Postfix, from userid 1000); Mon, 4 Dec 2006 14:51:39 +0100 (CET)\n\
|
||||
From: bob@example.com\n\
|
||||
To: alice@example.org\n\
|
||||
Subject: äääää\n\
|
||||
Chat-Version: 1.0\n\
|
||||
Message-ID: <2893@example.com>\n\
|
||||
Date: Sun, 22 Mar 2020 22:37:56 +0000\n\
|
||||
\n\
|
||||
hello\n"
|
||||
.as_bytes(),
|
||||
)
|
||||
.await;
|
||||
|
||||
msg_to_subject_str(
|
||||
"From: bob@example.net\r\n\
|
||||
To: alice@example.org\r\n\
|
||||
Subject: aäääää\r\n\
|
||||
Chat-Version: 1.0\r\n\
|
||||
Message-ID: <2893@example.com>\r\n\
|
||||
Date: Sun, 22 Mar 2020 22:37:56 +0000\r\n\
|
||||
\r\n\
|
||||
hello\r\n"
|
||||
"Received: (Postfix, from userid 1000); Mon, 4 Dec 2006 14:51:39 +0100 (CET)\n\
|
||||
From: bob@example.com\n\
|
||||
To: alice@example.org\n\
|
||||
Subject: aäääää\n\
|
||||
Chat-Version: 1.0\n\
|
||||
Message-ID: <2893@example.com>\n\
|
||||
Date: Sun, 22 Mar 2020 22:37:56 +0000\n\
|
||||
\n\
|
||||
hello\n"
|
||||
.as_bytes(),
|
||||
)
|
||||
.await;
|
||||
@@ -229,76 +230,73 @@ async fn test_subject_unicode() {
|
||||
#[tokio::test(flavor = "multi_thread", worker_threads = 2)]
|
||||
async fn test_subject_mdn() {
|
||||
// 5. Receive an mdn (read receipt) and make sure the mdn's subject is not used
|
||||
let mut tcm = TestContextManager::new();
|
||||
let t = &tcm.alice().await;
|
||||
let bob = &tcm.bob().await;
|
||||
t.allow_unencrypted().await.unwrap();
|
||||
let t = TestContext::new_alice().await;
|
||||
receive_imf(
|
||||
t,
|
||||
b"From: alice@example.org\r\n\
|
||||
To: bob@example.net\r\n\
|
||||
Subject: Hello, Bob\r\n\
|
||||
Chat-Version: 1.0\r\n\
|
||||
Message-ID: <2893@example.com>\r\n\
|
||||
Date: Sun, 22 Mar 2020 22:37:56 +0000\r\n\
|
||||
\r\n\
|
||||
hello\r\n",
|
||||
&t,
|
||||
b"Received: (Postfix, from userid 1000); Mon, 4 Dec 2006 14:51:39 +0100 (CET)\n\
|
||||
From: alice@example.org\n\
|
||||
To: bob@example.com\n\
|
||||
Subject: Hello, Bob\n\
|
||||
Chat-Version: 1.0\n\
|
||||
Message-ID: <2893@example.com>\n\
|
||||
Date: Sun, 22 Mar 2020 22:37:56 +0000\n\
|
||||
\n\
|
||||
hello\n",
|
||||
false,
|
||||
)
|
||||
.await
|
||||
.unwrap();
|
||||
let mut new_msg = incoming_msg_to_reply_msg(
|
||||
b"From: bob@example.net\r\n\
|
||||
To: alice@example.org\r\n\
|
||||
Subject: message opened\r\n\
|
||||
Date: Sun, 22 Mar 2020 23:37:57 +0000\r\n\
|
||||
Chat-Version: 1.0\r\n\
|
||||
Message-ID: <Mr.12345678902@example.com>\r\n\
|
||||
Content-Type: multipart/report; report-type=disposition-notification; boundary=\"SNIPP\"\r\n\
|
||||
\r\n\
|
||||
\r\n\
|
||||
--SNIPP\r\n\
|
||||
Content-Type: text/plain; charset=utf-8\r\n\
|
||||
\r\n\
|
||||
Read receipts do not guarantee sth. was read.\r\n\
|
||||
\r\n\
|
||||
\r\n\
|
||||
--SNIPP\r\n\
|
||||
Content-Type: message/disposition-notification\r\n\
|
||||
\r\n\
|
||||
Reporting-UA: Delta Chat 1.28.0\r\n\
|
||||
Original-Recipient: rfc822;bob@example.com\r\n\
|
||||
Final-Recipient: rfc822;bob@example.com\r\n\
|
||||
Original-Message-ID: <2893@example.com>\r\n\
|
||||
Disposition: manual-action/MDN-sent-automatically; displayed\r\n\
|
||||
\r\n", t, bob).await;
|
||||
chat::send_msg(t, new_msg.chat_id, &mut new_msg)
|
||||
b"Received: (Postfix, from userid 1000); Mon, 4 Dec 2006 14:51:39 +0100 (CET)\n\
|
||||
From: bob@example.com\n\
|
||||
To: alice@example.org\n\
|
||||
Subject: message opened\n\
|
||||
Date: Sun, 22 Mar 2020 23:37:57 +0000\n\
|
||||
Chat-Version: 1.0\n\
|
||||
Message-ID: <Mr.12345678902@example.com>\n\
|
||||
Content-Type: multipart/report; report-type=disposition-notification; boundary=\"SNIPP\"\n\
|
||||
\n\
|
||||
\n\
|
||||
--SNIPP\n\
|
||||
Content-Type: text/plain; charset=utf-8\n\
|
||||
\n\
|
||||
Read receipts do not guarantee sth. was read.\n\
|
||||
\n\
|
||||
\n\
|
||||
--SNIPP\n\
|
||||
Content-Type: message/disposition-notification\n\
|
||||
\n\
|
||||
Reporting-UA: Delta Chat 1.28.0\n\
|
||||
Original-Recipient: rfc822;bob@example.com\n\
|
||||
Final-Recipient: rfc822;bob@example.com\n\
|
||||
Original-Message-ID: <2893@example.com>\n\
|
||||
Disposition: manual-action/MDN-sent-automatically; displayed\n\
|
||||
\n", &t).await;
|
||||
chat::send_msg(&t, new_msg.chat_id, &mut new_msg)
|
||||
.await
|
||||
.unwrap();
|
||||
let mf = MimeFactory::from_msg(t, new_msg).await.unwrap();
|
||||
let mf = MimeFactory::from_msg(&t, new_msg).await.unwrap();
|
||||
// The subject string should not be "Re: message opened"
|
||||
assert_eq!("Re: Hello, Bob", mf.subject_str(t).await.unwrap());
|
||||
assert_eq!("Re: Hello, Bob", mf.subject_str(&t).await.unwrap());
|
||||
}
|
||||
|
||||
#[tokio::test(flavor = "multi_thread", worker_threads = 2)]
|
||||
async fn test_mdn_create_encrypted() -> Result<()> {
|
||||
let mut tcm = TestContextManager::new();
|
||||
let alice = tcm.alice().await;
|
||||
alice.allow_unencrypted().await?;
|
||||
alice
|
||||
.set_config(Config::Displayname, Some("Alice Exampleorg"))
|
||||
.await?;
|
||||
let bob = tcm.bob().await;
|
||||
bob.allow_unencrypted().await?;
|
||||
bob.set_config(Config::Displayname, Some("Bob Examplenet"))
|
||||
.await?;
|
||||
bob.set_config(Config::Selfstatus, Some("Bob Examplenet"))
|
||||
.await?;
|
||||
bob.set_config_bool(Config::MdnsEnabled, true).await?;
|
||||
|
||||
// MDN for unencrypted message is not encrypted.
|
||||
let mut msg = Message::new(Viewtype::Text);
|
||||
let chat_alice = alice.create_email_chat(&bob).await.id;
|
||||
msg.param.set_int(Param::SkipAutocrypt, 1);
|
||||
let chat_alice = alice.create_chat(&bob).await.id;
|
||||
let sent = alice.send_msg(chat_alice, &mut msg).await;
|
||||
|
||||
let rcvd = bob.recv_msg(&sent).await;
|
||||
@@ -313,13 +311,13 @@ async fn test_mdn_create_encrypted() -> Result<()> {
|
||||
let bob_alice_contact = bob.add_or_lookup_contact(&alice).await;
|
||||
assert_eq!(bob_alice_contact.get_authname(), "Alice Exampleorg");
|
||||
|
||||
// MDN for encrypted message is encrypted.
|
||||
let rcvd = tcm.send_recv(&alice, &bob, "Heyho").await;
|
||||
message::markseen_msgs(&bob, vec![rcvd.id]).await?;
|
||||
|
||||
let mimefactory = MimeFactory::from_mdn(&bob, rcvd.from_id, rcvd.rfc724_mid, vec![]).await?;
|
||||
let rendered_msg = mimefactory.render(&bob).await?;
|
||||
|
||||
// When encrypted, the MDN should be encrypted as well
|
||||
assert!(rendered_msg.is_encrypted);
|
||||
assert!(!rendered_msg.message.contains("Bob Examplenet"));
|
||||
assert!(!rendered_msg.message.contains("Alice Exampleorg"));
|
||||
@@ -419,7 +417,7 @@ async fn first_subject_str(t: TestContext) -> String {
|
||||
mf.subject_str(&t).await.unwrap()
|
||||
}
|
||||
|
||||
// In `imf_raw`, From has to be bob@example.net, To has to be alice@example.org
|
||||
// In `imf_raw`, From has to be bob@example.com, To has to be alice@example.org
|
||||
async fn msg_to_subject_str(imf_raw: &[u8]) -> String {
|
||||
let subject_str = msg_to_subject_str_inner(imf_raw, false, false, false).await;
|
||||
|
||||
@@ -467,62 +465,48 @@ async fn msg_to_subject_str_inner(
|
||||
reply: bool,
|
||||
message_arrives_inbetween: bool,
|
||||
) -> String {
|
||||
let mut tcm = TestContextManager::new();
|
||||
let t = &tcm.alice().await;
|
||||
let bob = &tcm.bob().await;
|
||||
let mut new_msg = incoming_msg_to_reply_msg(imf_raw, t, bob).await;
|
||||
let incoming_msg = get_chat_msg(t, new_msg.chat_id, 1, 2).await;
|
||||
let t = TestContext::new_alice().await;
|
||||
let mut new_msg = incoming_msg_to_reply_msg(imf_raw, &t).await;
|
||||
let incoming_msg = get_chat_msg(&t, new_msg.chat_id, 0, 1).await;
|
||||
|
||||
if delete_original_msg {
|
||||
incoming_msg.id.trash(t, false).await.unwrap();
|
||||
incoming_msg.id.trash(&t, false).await.unwrap();
|
||||
}
|
||||
|
||||
if message_arrives_inbetween {
|
||||
let encrypted_msg = test_utils::encrypt_raw_message(
|
||||
bob,
|
||||
&[t],
|
||||
b"From: Bob <bob@example.net>\r\n\
|
||||
To: alice@example.org\r\n\
|
||||
Subject: Some other, completely unrelated subject\r\n\
|
||||
Message-ID: <3cl4@example.com>\r\n\
|
||||
Date: Sun, 22 Mar 2020 22:37:56 +0000\r\n\
|
||||
\r\n\
|
||||
Some other, completely unrelated content\r\n",
|
||||
receive_imf(
|
||||
&t,
|
||||
b"Received: (Postfix, from userid 1000); Mon, 4 Dec 2006 14:51:39 +0100 (CET)\n\
|
||||
From: Bob <bob@example.com>\n\
|
||||
To: alice@example.org\n\
|
||||
Subject: Some other, completely unrelated subject\n\
|
||||
Message-ID: <3cl4@example.com>\n\
|
||||
Date: Sun, 22 Mar 2020 22:37:56 +0000\n\
|
||||
\n\
|
||||
Some other, completely unrelated content\n",
|
||||
false,
|
||||
)
|
||||
.await
|
||||
.unwrap();
|
||||
receive_imf(t, encrypted_msg.as_bytes(), false)
|
||||
.await
|
||||
.unwrap();
|
||||
|
||||
let arrived_msg = t.get_last_msg().await;
|
||||
assert_eq!(arrived_msg.chat_id, incoming_msg.chat_id);
|
||||
}
|
||||
|
||||
if reply {
|
||||
new_msg.set_quote(t, Some(&incoming_msg)).await.unwrap();
|
||||
new_msg.set_quote(&t, Some(&incoming_msg)).await.unwrap();
|
||||
}
|
||||
|
||||
chat::send_msg(t, new_msg.chat_id, &mut new_msg)
|
||||
chat::send_msg(&t, new_msg.chat_id, &mut new_msg)
|
||||
.await
|
||||
.unwrap();
|
||||
let mf = MimeFactory::from_msg(t, new_msg).await.unwrap();
|
||||
mf.subject_str(t).await.unwrap()
|
||||
let mf = MimeFactory::from_msg(&t, new_msg).await.unwrap();
|
||||
mf.subject_str(&t).await.unwrap()
|
||||
}
|
||||
|
||||
// Creates a `Message` that replies "Hi" to the incoming email in `imf_raw`.
|
||||
async fn incoming_msg_to_reply_msg(
|
||||
imf_raw: &[u8],
|
||||
context: &TestContext,
|
||||
from: &TestContext,
|
||||
) -> Message {
|
||||
let encrypted_msg = test_utils::encrypt_raw_message(from, &[context], imf_raw)
|
||||
.await
|
||||
.unwrap();
|
||||
|
||||
receive_imf(context, encrypted_msg.as_bytes(), false)
|
||||
.await
|
||||
.unwrap();
|
||||
async fn incoming_msg_to_reply_msg(imf_raw: &[u8], context: &Context) -> Message {
|
||||
receive_imf(context, imf_raw, false).await.unwrap();
|
||||
|
||||
let chats = Chatlist::try_load(context, 0, None, None).await.unwrap();
|
||||
|
||||
@@ -538,31 +522,30 @@ async fn incoming_msg_to_reply_msg(
|
||||
#[tokio::test(flavor = "multi_thread", worker_threads = 2)]
|
||||
// This test could still be extended
|
||||
async fn test_render_reply() {
|
||||
let mut tcm = TestContextManager::new();
|
||||
let t = &tcm.alice().await;
|
||||
let charlie = &tcm.charlie().await;
|
||||
let t = TestContext::new_alice().await;
|
||||
let context = &t;
|
||||
|
||||
let mut msg = incoming_msg_to_reply_msg(
|
||||
b"From: Charlie <charlie@example.net>\r\n\
|
||||
To: alice@example.org\r\n\
|
||||
Subject: Chat: hello\r\n\
|
||||
Chat-Version: 1.0\r\n\
|
||||
Message-ID: <2223@example.com>\r\n\
|
||||
Date: Sun, 22 Mar 2020 22:37:56 +0000\r\n\
|
||||
\r\n\
|
||||
hello\r\n",
|
||||
t,
|
||||
charlie,
|
||||
b"Received: (Postfix, from userid 1000); Mon, 4 Dec 2006 14:51:39 +0100 (CET)\n\
|
||||
From: Charlie <charlie@example.com>\n\
|
||||
To: alice@example.org\n\
|
||||
Subject: Chat: hello\n\
|
||||
Chat-Version: 1.0\n\
|
||||
Message-ID: <2223@example.com>\n\
|
||||
Date: Sun, 22 Mar 2020 22:37:56 +0000\n\
|
||||
\n\
|
||||
hello\n",
|
||||
context,
|
||||
)
|
||||
.await;
|
||||
chat::send_msg(t, msg.chat_id, &mut msg).await.unwrap();
|
||||
chat::send_msg(&t, msg.chat_id, &mut msg).await.unwrap();
|
||||
|
||||
let mimefactory = MimeFactory::from_msg(t, msg).await.unwrap();
|
||||
let mimefactory = MimeFactory::from_msg(&t, msg).await.unwrap();
|
||||
|
||||
let recipients = mimefactory.recipients();
|
||||
assert_eq!(recipients, vec!["charlie@example.net"]);
|
||||
assert_eq!(recipients, vec!["charlie@example.com"]);
|
||||
|
||||
let rendered_msg = mimefactory.render(t).await.unwrap();
|
||||
let rendered_msg = mimefactory.render(context).await.unwrap();
|
||||
|
||||
let mail = mailparse::parse_mail(rendered_msg.message.as_bytes()).unwrap();
|
||||
assert_eq!(
|
||||
@@ -574,7 +557,7 @@ async fn test_render_reply() {
|
||||
"1.0"
|
||||
);
|
||||
|
||||
let _mime_msg = MimeMessage::from_bytes(t, rendered_msg.message.as_bytes())
|
||||
let _mime_msg = MimeMessage::from_bytes(context, rendered_msg.message.as_bytes())
|
||||
.await
|
||||
.unwrap();
|
||||
}
|
||||
@@ -583,7 +566,6 @@ async fn test_render_reply() {
|
||||
async fn test_selfavatar_unencrypted() -> anyhow::Result<()> {
|
||||
// create chat with bob, set selfavatar
|
||||
let t = TestContext::new_alice().await;
|
||||
t.allow_unencrypted().await?;
|
||||
let chat = t.create_chat_with_contact("bob", "bob@example.org").await;
|
||||
|
||||
let file = t.dir.path().join("avatar.png");
|
||||
@@ -629,7 +611,6 @@ async fn test_remove_member_bcc() -> Result<()> {
|
||||
let alice = &tcm.alice().await;
|
||||
let bob = &tcm.bob().await;
|
||||
let charlie = &tcm.charlie().await;
|
||||
alice.allow_unencrypted().await?;
|
||||
|
||||
let alice_addr = alice.get_config(Config::Addr).await?.unwrap();
|
||||
let bob_addr = bob.get_config(Config::Addr).await?.unwrap();
|
||||
@@ -673,7 +654,6 @@ async fn test_remove_member_bcc() -> Result<()> {
|
||||
async fn test_from_before_autocrypt() -> Result<()> {
|
||||
// create chat with bob
|
||||
let t = TestContext::new_alice().await;
|
||||
t.allow_unencrypted().await?;
|
||||
let chat = t.create_chat_with_contact("bob", "bob@example.org").await;
|
||||
|
||||
// send message to bob: that should get multipart/mixed because of the avatar moved to inner header;
|
||||
@@ -817,7 +797,6 @@ async fn test_new_member_is_first_recipient() -> Result<()> {
|
||||
#[tokio::test(flavor = "multi_thread", worker_threads = 2)]
|
||||
async fn test_no_empty_to_header() -> Result<()> {
|
||||
let alice = &TestContext::new_alice().await;
|
||||
alice.allow_unencrypted().await?;
|
||||
let mut self_chat = alice.get_self_chat().await;
|
||||
self_chat.param.remove(Param::Selftalk);
|
||||
self_chat.update_param(alice).await?;
|
||||
|
||||
+1
-1
@@ -356,7 +356,7 @@ impl MimeMessage {
|
||||
let decrypted_msg; // Decrypted signed OpenPGP message.
|
||||
let expected_sender_fingerprint: Option<String>;
|
||||
|
||||
let (mail, is_encrypted) = match Box::pin(decrypt::decrypt(context, &mail)).await {
|
||||
let (mail, is_encrypted) = match decrypt::decrypt(context, &mail).await {
|
||||
Ok(Some((mut msg, expected_sender_fp))) => {
|
||||
mail_raw = msg.as_data_vec().unwrap_or_default();
|
||||
|
||||
|
||||
+115
-124
@@ -12,7 +12,7 @@ use crate::{
|
||||
message::{MessageState, MessengerMessage},
|
||||
receive_imf::receive_imf,
|
||||
securejoin::QrInvite,
|
||||
test_utils::{self, TestContext, TestContextManager},
|
||||
test_utils::{TestContext, TestContextManager},
|
||||
tools::time,
|
||||
};
|
||||
|
||||
@@ -1185,7 +1185,6 @@ async fn test_allinkl_blockquote() {
|
||||
#[tokio::test(flavor = "multi_thread", worker_threads = 2)]
|
||||
async fn test_add_subj_to_multimedia_msg() {
|
||||
let t = TestContext::new_alice().await;
|
||||
t.allow_unencrypted().await.unwrap();
|
||||
receive_imf(
|
||||
&t.ctx,
|
||||
include_bytes!("../../test-data/message/subj_with_multimedia_msg.eml"),
|
||||
@@ -1462,7 +1461,6 @@ async fn test_intended_recipient_fingerprint() -> Result<()> {
|
||||
#[tokio::test(flavor = "multi_thread", worker_threads = 2)]
|
||||
async fn test_long_in_reply_to() -> Result<()> {
|
||||
let t = TestContext::new_alice().await;
|
||||
t.allow_unencrypted().await?;
|
||||
|
||||
// A message with a long Message-ID.
|
||||
// Long message-IDs are generated by Mailjet.
|
||||
@@ -1505,89 +1503,96 @@ Some reply
|
||||
// Test that WantsMdn parameter is not set on outgoing messages.
|
||||
#[tokio::test(flavor = "multi_thread", worker_threads = 2)]
|
||||
async fn test_outgoing_wants_mdn() -> Result<()> {
|
||||
let mut tcm = TestContextManager::new();
|
||||
let alice = &tcm.alice().await;
|
||||
let alice2 = &tcm.alice().await;
|
||||
let bob = &tcm.bob().await;
|
||||
let alice = TestContext::new_alice().await;
|
||||
let bob = TestContext::new_bob().await;
|
||||
|
||||
let chat_id = alice.create_chat(bob).await.id;
|
||||
let sent = alice.send_text(chat_id, "Message.").await;
|
||||
let raw = br"Date: Thu, 28 Jan 2021 00:26:57 +0000
|
||||
Chat-Version: 1.0\n\
|
||||
Message-ID: <foobarbaz@example.org>
|
||||
To: Bob <bob@example.org>
|
||||
From: Alice <alice@example.org>
|
||||
Subject: subject
|
||||
Chat-Disposition-Notification-To: alice@example.org
|
||||
|
||||
Message.
|
||||
";
|
||||
|
||||
// Bob receives message.
|
||||
let bob_msg = bob.recv_msg(&sent).await;
|
||||
receive_imf(&bob, raw, false).await?;
|
||||
let msg = bob.get_last_msg().await;
|
||||
// Message is incoming.
|
||||
assert!(bob_msg.param.get_bool(Param::WantsMdn).unwrap());
|
||||
assert!(msg.param.get_bool(Param::WantsMdn).unwrap());
|
||||
|
||||
// Alice receives copy-to-self.
|
||||
let alice2_msg = alice2.recv_msg(&sent).await;
|
||||
receive_imf(&alice, raw, false).await?;
|
||||
let msg = alice.get_last_msg().await;
|
||||
// Message is outgoing, don't send read receipt to self.
|
||||
assert!(alice2_msg.param.get_bool(Param::WantsMdn).is_none());
|
||||
assert!(msg.param.get_bool(Param::WantsMdn).is_none());
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[tokio::test(flavor = "multi_thread", worker_threads = 2)]
|
||||
async fn test_ignore_read_receipt_to_self() -> Result<()> {
|
||||
let mut tcm = TestContextManager::new();
|
||||
let alice = &tcm.alice().await;
|
||||
let bob = &tcm.bob().await;
|
||||
let alice = TestContext::new_alice().await;
|
||||
|
||||
// Alice receives BCC-self copy of a message sent to Bob.
|
||||
let encrypted_message = test_utils::encrypt_raw_message(
|
||||
alice,
|
||||
&[bob],
|
||||
"From: alice@example.org\r\n\
|
||||
To: bob@example.net\r\n\
|
||||
Subject: foo\r\n\
|
||||
Message-ID: first@example.com\r\n\
|
||||
Chat-Version: 1.0\r\n\
|
||||
Chat-Disposition-Notification-To: alice@example.org\r\n\
|
||||
Date: Sun, 22 Mar 2020 22:37:57 +0000\r\n\
|
||||
\r\n\
|
||||
hello\r\n"
|
||||
receive_imf(
|
||||
&alice,
|
||||
"Received: (Postfix, from userid 1000); Mon, 4 Dec 2006 14:51:39 +0100 (CET)\n\
|
||||
From: alice@example.org\n\
|
||||
To: bob@example.net\n\
|
||||
Subject: foo\n\
|
||||
Message-ID: first@example.com\n\
|
||||
Chat-Version: 1.0\n\
|
||||
Chat-Disposition-Notification-To: alice@example.org\n\
|
||||
Date: Sun, 22 Mar 2020 22:37:57 +0000\n\
|
||||
\n\
|
||||
hello\n"
|
||||
.as_bytes(),
|
||||
false,
|
||||
)
|
||||
.await?;
|
||||
receive_imf(alice, encrypted_message.as_bytes(), false).await?;
|
||||
let msg = alice.get_last_msg().await;
|
||||
assert_eq!(msg.state, MessageState::OutDelivered);
|
||||
|
||||
// Alice receives a read receipt from self.
|
||||
//
|
||||
// This should mark the message as seen, i.e. not counted as fresh,
|
||||
// but not "read" (having double checkmark in the UI).
|
||||
let encrypted_message = test_utils::encrypt_raw_message(
|
||||
alice,
|
||||
&[alice],
|
||||
"From: alice@example.org\r\n\
|
||||
To: alice@example.org\r\n\
|
||||
Subject: message opened\r\n\
|
||||
Date: Sun, 22 Mar 2020 23:37:57 +0000\r\n\
|
||||
Chat-Version: 1.0\r\n\
|
||||
Message-ID: second@example.com\r\n\
|
||||
Content-Type: multipart/report; report-type=disposition-notification; boundary=\"SNIPP\"\r\n\
|
||||
\r\n\
|
||||
\r\n\
|
||||
--SNIPP\r\n\
|
||||
Content-Type: text/plain; charset=utf-8\r\n\
|
||||
\r\n\
|
||||
Read receipts do not guarantee sth. was read.\r\n\
|
||||
\r\n\
|
||||
\r\n\
|
||||
--SNIPP\r\n\
|
||||
Content-Type: message/disposition-notification\r\n\
|
||||
\r\n\
|
||||
Original-Recipient: rfc822;bob@example.com\r\n\
|
||||
Final-Recipient: rfc822;bob@example.com\r\n\
|
||||
Original-Message-ID: <first@example.com>\r\n\
|
||||
Disposition: manual-action/MDN-sent-automatically; displayed\r\n\
|
||||
\r\n\
|
||||
\r\n\
|
||||
--SNIPP--".as_bytes()).await?;
|
||||
receive_imf(alice, encrypted_message.as_bytes(), false).await?;
|
||||
// Due to a bug in the old version running on the other device, Alice receives a read
|
||||
// receipt from self.
|
||||
receive_imf(
|
||||
&alice,
|
||||
"Received: (Postfix, from userid 1000); Mon, 4 Dec 2006 14:51:39 +0100 (CET)\n\
|
||||
From: alice@example.org\n\
|
||||
To: alice@example.org\n\
|
||||
Subject: message opened\n\
|
||||
Date: Sun, 22 Mar 2020 23:37:57 +0000\n\
|
||||
Chat-Version: 1.0\n\
|
||||
Message-ID: second@example.com\n\
|
||||
Content-Type: multipart/report; report-type=disposition-notification; boundary=\"SNIPP\"\n\
|
||||
\n\
|
||||
\n\
|
||||
--SNIPP\n\
|
||||
Content-Type: text/plain; charset=utf-8\n\
|
||||
\n\
|
||||
Read receipts do not guarantee sth. was read.\n\
|
||||
\n\
|
||||
\n\
|
||||
--SNIPP\n\
|
||||
Content-Type: message/disposition-notification\n\
|
||||
\n\
|
||||
Original-Recipient: rfc822;bob@example.com\n\
|
||||
Final-Recipient: rfc822;bob@example.com\n\
|
||||
Original-Message-ID: <first@example.com>\n\
|
||||
Disposition: manual-action/MDN-sent-automatically; displayed\n\
|
||||
\n\
|
||||
\n\
|
||||
--SNIPP--"
|
||||
.as_bytes(),
|
||||
false,
|
||||
)
|
||||
.await?;
|
||||
|
||||
// Check that the state has not changed to `MessageState::OutMdnRcvd`.
|
||||
let msg = Message::load_from_db(alice, msg.id).await?;
|
||||
let msg = Message::load_from_db(&alice, msg.id).await?;
|
||||
assert_eq!(msg.state, MessageState::OutDelivered);
|
||||
|
||||
Ok(())
|
||||
@@ -1599,9 +1604,7 @@ async fn test_ignore_read_receipt_to_self() -> Result<()> {
|
||||
/// recognize it as MDN nevertheless to avoid displaying it in the chat as normal message.
|
||||
#[tokio::test(flavor = "multi_thread", worker_threads = 2)]
|
||||
async fn test_ms_exchange_mdn() -> Result<()> {
|
||||
let mut tcm = TestContextManager::new();
|
||||
let t = tcm.alice().await;
|
||||
t.allow_unencrypted().await?;
|
||||
let t = TestContext::new_alice().await;
|
||||
|
||||
let original =
|
||||
include_bytes!("../../test-data/message/ms_exchange_report_original_message.eml");
|
||||
@@ -1697,24 +1700,17 @@ Content-Disposition: reaction\n\
|
||||
|
||||
#[tokio::test(flavor = "multi_thread", worker_threads = 2)]
|
||||
async fn test_jpeg_as_application_octet_stream() -> Result<()> {
|
||||
let mut tcm = TestContextManager::new();
|
||||
let alice = &tcm.alice().await;
|
||||
let bob = &tcm.bob().await;
|
||||
let encrypted_msg = test_utils::encrypt_raw_message(
|
||||
alice,
|
||||
&[bob],
|
||||
include_bytes!("../../test-data/message/jpeg-as-application-octet-stream.eml"),
|
||||
)
|
||||
.await?;
|
||||
let context = TestContext::new_alice().await;
|
||||
let raw = include_bytes!("../../test-data/message/jpeg-as-application-octet-stream.eml");
|
||||
|
||||
let msg = MimeMessage::from_bytes(bob, encrypted_msg.as_bytes())
|
||||
let msg = MimeMessage::from_bytes(&context.ctx, &raw[..])
|
||||
.await
|
||||
.unwrap();
|
||||
assert_eq!(msg.parts.len(), 1);
|
||||
assert_eq!(msg.parts[0].typ, Viewtype::Image);
|
||||
|
||||
receive_imf(alice, encrypted_msg.as_bytes(), false).await?;
|
||||
let msg = alice.get_last_msg().await;
|
||||
receive_imf(&context, &raw[..], false).await?;
|
||||
let msg = context.get_last_msg().await;
|
||||
assert_eq!(msg.get_viewtype(), Viewtype::Image);
|
||||
|
||||
Ok(())
|
||||
@@ -1790,27 +1786,35 @@ async fn test_time_in_future() -> Result<()> {
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Tests receiving a message with RFC 9788 header protection and legacy display element.
|
||||
///
|
||||
/// Legacy display elements should not be rendered:
|
||||
/// <https://www.rfc-editor.org/rfc/rfc9788.html#name-do-not-render-legacy-displa>
|
||||
#[tokio::test(flavor = "multi_thread", worker_threads = 2)]
|
||||
async fn test_hp_legacy_display() -> Result<()> {
|
||||
let mut tcm = TestContextManager::new();
|
||||
let alice = &tcm.alice().await;
|
||||
let bob = &tcm.bob().await;
|
||||
|
||||
let msg_id = receive_imf(
|
||||
bob,
|
||||
include_bytes!("../../test-data/message/hp_legacy_display.eml"),
|
||||
false,
|
||||
)
|
||||
.await?
|
||||
.unwrap()
|
||||
.msg_ids[0];
|
||||
let msg_bob = Message::load_from_db(bob, msg_id).await?;
|
||||
assert_eq!(msg_bob.subject, "Dinner plans");
|
||||
let mut msg = Message::new_text(
|
||||
"Subject: Dinner plans\n\
|
||||
\n\
|
||||
Let's eat"
|
||||
.to_string(),
|
||||
);
|
||||
msg.set_subject("Dinner plans".to_string());
|
||||
let chat_id = alice.create_chat(bob).await.id;
|
||||
alice.set_config_bool(Config::TestHooks, true).await?;
|
||||
*alice.pre_encrypt_mime_hook.lock() = Some(|_, mut mime| {
|
||||
for (h, v) in &mut mime.headers {
|
||||
if h == "Content-Type"
|
||||
&& let mail_builder::headers::HeaderType::ContentType(ct) = v
|
||||
{
|
||||
*ct = ct.clone().attribute("hp-legacy-display", "1");
|
||||
}
|
||||
}
|
||||
mime
|
||||
});
|
||||
let sent_msg = alice.send_msg(chat_id, &mut msg).await;
|
||||
|
||||
// Legacy display element is removed from the text/plain body.
|
||||
let msg_bob = bob.recv_msg(&sent_msg).await;
|
||||
assert_eq!(msg_bob.subject, "Dinner plans");
|
||||
assert_eq!(msg_bob.text, "Let's eat");
|
||||
Ok(())
|
||||
}
|
||||
@@ -1980,8 +1984,6 @@ async fn test_chat_edit_imf_header() -> Result<()> {
|
||||
let mut tcm = TestContextManager::new();
|
||||
let alice = &tcm.alice().await;
|
||||
let bob = &tcm.bob().await;
|
||||
alice.allow_unencrypted().await?;
|
||||
bob.allow_unencrypted().await?;
|
||||
let alice_chat = alice.create_email_chat(bob).await;
|
||||
|
||||
// Alice sends a message, then sends an invalid edit request.
|
||||
@@ -2046,7 +2048,6 @@ async fn test_multiple_autocrypt_hdrs() -> Result<()> {
|
||||
async fn test_receive_signed_only() -> Result<()> {
|
||||
let mut tcm = TestContextManager::new();
|
||||
let bob = &tcm.bob().await;
|
||||
bob.allow_unencrypted().await?;
|
||||
|
||||
let imf_raw = include_bytes!("../../test-data/message/unencrypted_signed_simple.eml");
|
||||
let msg = receive_imf(bob, imf_raw, false).await?.unwrap();
|
||||
@@ -2064,24 +2065,19 @@ async fn test_receive_signed_only() -> Result<()> {
|
||||
|
||||
#[tokio::test(flavor = "multi_thread", worker_threads = 2)]
|
||||
async fn test_huge_image_becomes_file() -> Result<()> {
|
||||
let mut tcm = TestContextManager::new();
|
||||
let alice = &tcm.alice().await;
|
||||
let bob = &tcm.bob().await;
|
||||
let encrypted_msg = test_utils::encrypt_raw_message(
|
||||
bob,
|
||||
&[alice],
|
||||
let t = TestContext::new_alice().await;
|
||||
let msg_id = receive_imf(
|
||||
&t,
|
||||
include_bytes!("../../test-data/message/image_huge_64M.eml"),
|
||||
false,
|
||||
)
|
||||
.await?;
|
||||
|
||||
let msg_id = receive_imf(alice, encrypted_msg.as_bytes(), false)
|
||||
.await?
|
||||
.unwrap()
|
||||
.msg_ids[0];
|
||||
let msg = Message::load_from_db(alice, msg_id).await.unwrap();
|
||||
.await?
|
||||
.unwrap()
|
||||
.msg_ids[0];
|
||||
let msg = Message::load_from_db(&t.ctx, msg_id).await.unwrap();
|
||||
// Huge image should be treated as file:
|
||||
assert_eq!(msg.viewtype, Viewtype::File);
|
||||
assert!(msg.get_file(alice).is_some());
|
||||
assert!(msg.get_file(&t).is_some());
|
||||
assert_eq!(msg.get_filename().unwrap(), "huge_image.png");
|
||||
assert_eq!(msg.get_filemime().unwrap(), "image/png");
|
||||
// File has no width or height
|
||||
@@ -2092,24 +2088,19 @@ async fn test_huge_image_becomes_file() -> Result<()> {
|
||||
|
||||
#[tokio::test(flavor = "multi_thread", worker_threads = 2)]
|
||||
async fn test_4k_image_stays_image() -> Result<()> {
|
||||
let mut tcm = TestContextManager::new();
|
||||
let alice = &tcm.alice().await;
|
||||
let bob = &tcm.bob().await;
|
||||
let encrypted_msg = test_utils::encrypt_raw_message(
|
||||
bob,
|
||||
&[alice],
|
||||
let t = TestContext::new_alice().await;
|
||||
let msg_id = receive_imf(
|
||||
&t,
|
||||
include_bytes!("../../test-data/message/image_4k.eml"),
|
||||
false,
|
||||
)
|
||||
.await?;
|
||||
|
||||
let msg_id = receive_imf(alice, encrypted_msg.as_bytes(), false)
|
||||
.await?
|
||||
.unwrap()
|
||||
.msg_ids[0];
|
||||
let msg = Message::load_from_db(alice, msg_id).await.unwrap();
|
||||
.await?
|
||||
.unwrap()
|
||||
.msg_ids[0];
|
||||
let msg = Message::load_from_db(&t.ctx, msg_id).await.unwrap();
|
||||
// 4K image should be treated as image:
|
||||
assert_eq!(msg.viewtype, Viewtype::Image);
|
||||
assert!(msg.get_file(alice).is_some());
|
||||
assert!(msg.get_file(&t).is_some());
|
||||
assert_eq!(msg.get_filename().unwrap(), "4k_image.png");
|
||||
assert_eq!(msg.get_filemime().unwrap(), "image/png");
|
||||
assert_eq!(msg.param.get_int(Param::Width).unwrap_or_default(), 3840);
|
||||
|
||||
+7
-10
@@ -23,7 +23,6 @@ pub(crate) mod session;
|
||||
pub(crate) mod tls;
|
||||
|
||||
use dns::lookup_host_with_cache;
|
||||
pub(crate) use http::read_url_with_tls;
|
||||
pub use http::{Response as HttpResponse, read_url, read_url_blob};
|
||||
use tls::wrap_tls;
|
||||
|
||||
@@ -110,8 +109,8 @@ pub(crate) async fn connect_tcp_inner(
|
||||
) -> Result<Pin<Box<TimeoutStream<TcpStream>>>> {
|
||||
let tcp_stream = timeout(TIMEOUT, TcpStream::connect(addr))
|
||||
.await
|
||||
.with_context(|| format!("Connection to {addr} timed out"))?
|
||||
.with_context(|| format!("Connection to {addr} failed"))?;
|
||||
.context("Connection timeout")?
|
||||
.context("Connection failure")?;
|
||||
|
||||
// Disable Nagle's algorithm.
|
||||
tcp_stream.set_nodelay(true)?;
|
||||
@@ -181,7 +180,7 @@ where
|
||||
delay_set.spawn(tokio::time::sleep(delay));
|
||||
}
|
||||
|
||||
let mut all_errors = Vec::new();
|
||||
let mut first_error = None;
|
||||
|
||||
let res = loop {
|
||||
if let Some(fut) = futures.next() {
|
||||
@@ -201,7 +200,7 @@ where
|
||||
}
|
||||
Ok(Err(err)) => {
|
||||
// Some connection attempt failed.
|
||||
all_errors.push(err);
|
||||
first_error.get_or_insert(err);
|
||||
}
|
||||
Err(err) => {
|
||||
break Err(err);
|
||||
@@ -212,11 +211,9 @@ where
|
||||
// Out of connection attempts.
|
||||
//
|
||||
// Break out of the loop and return error.
|
||||
break if all_errors.is_empty() {
|
||||
Err(format_err!("No connection attempts were made"))
|
||||
} else {
|
||||
Err(format_err!("All connection attempts failed: {}", all_errors.into_iter().map(|err| format!("{err:#}")).collect::<Vec<String>>().join("; ")))
|
||||
};
|
||||
break Err(
|
||||
first_error.unwrap_or_else(|| format_err!("No connection attempts were made"))
|
||||
);
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
+3
-50
@@ -231,10 +231,7 @@ static DNS_PRELOAD: LazyLock<HashMap<&'static str, Vec<IpAddr>>> = LazyLock::new
|
||||
HashMap::from([
|
||||
(
|
||||
"imap.163.com",
|
||||
vec![
|
||||
IpAddr::V4(Ipv4Addr::new(111, 124, 203, 45)),
|
||||
IpAddr::V4(Ipv4Addr::new(111, 124, 203, 50)),
|
||||
],
|
||||
vec![IpAddr::V4(Ipv4Addr::new(111, 124, 203, 45))],
|
||||
),
|
||||
(
|
||||
"smtp.163.com",
|
||||
@@ -425,12 +422,12 @@ static DNS_PRELOAD: LazyLock<HashMap<&'static str, Vec<IpAddr>>> = LazyLock::new
|
||||
"nine.testrun.org",
|
||||
vec![
|
||||
IpAddr::V4(Ipv4Addr::new(128, 140, 126, 197)),
|
||||
IpAddr::V4(Ipv4Addr::new(116, 202, 233, 236)),
|
||||
IpAddr::V4(Ipv4Addr::new(216, 144, 228, 100)),
|
||||
IpAddr::V4(Ipv4Addr::new(77, 42, 49, 41)),
|
||||
IpAddr::V6(Ipv6Addr::new(0x2a01, 0x4f8, 0x241, 0x4ce8, 0, 0, 0, 2)),
|
||||
IpAddr::V6(Ipv6Addr::new(
|
||||
0x2001, 0x41d0, 0x701, 0x1100, 0, 0, 0, 0x8ab1,
|
||||
)),
|
||||
IpAddr::V6(Ipv6Addr::new(0x2a01, 0x4f9, 0xfff1, 0x59, 0, 0, 0, 1)),
|
||||
],
|
||||
),
|
||||
(
|
||||
@@ -700,10 +697,6 @@ static DNS_PRELOAD: LazyLock<HashMap<&'static str, Vec<IpAddr>>> = LazyLock::new
|
||||
"chatmail.hackea.org",
|
||||
vec![IpAddr::V4(Ipv4Addr::new(82, 165, 11, 85))],
|
||||
),
|
||||
(
|
||||
"chat.adminforge.de",
|
||||
vec![IpAddr::V4(Ipv4Addr::new(94, 130, 17, 142))],
|
||||
),
|
||||
(
|
||||
"chika.aangat.lahat.computer",
|
||||
vec![IpAddr::V4(Ipv4Addr::new(71, 19, 150, 113))],
|
||||
@@ -745,46 +738,6 @@ static DNS_PRELOAD: LazyLock<HashMap<&'static str, Vec<IpAddr>>> = LazyLock::new
|
||||
"danneskjold.de",
|
||||
vec![IpAddr::V4(Ipv4Addr::new(46, 62, 216, 132))],
|
||||
),
|
||||
(
|
||||
"chat.in-the.eu",
|
||||
vec![IpAddr::V4(Ipv4Addr::new(78, 46, 190, 129))],
|
||||
),
|
||||
(
|
||||
"chat.nuvon.app",
|
||||
vec![IpAddr::V4(Ipv4Addr::new(178, 238, 38, 165))],
|
||||
),
|
||||
(
|
||||
"nibblehole.com",
|
||||
vec![IpAddr::V4(Ipv4Addr::new(94, 247, 42, 209))],
|
||||
),
|
||||
(
|
||||
"chat.zashm.org",
|
||||
vec![IpAddr::V4(Ipv4Addr::new(91, 245, 76, 39))],
|
||||
),
|
||||
(
|
||||
"chat.sus.fr",
|
||||
vec![IpAddr::V4(Ipv4Addr::new(152, 67, 76, 190))],
|
||||
),
|
||||
(
|
||||
"delta.thelab.uno",
|
||||
vec![IpAddr::V4(Ipv4Addr::new(146, 59, 228, 39))],
|
||||
),
|
||||
(
|
||||
"chat.vim.wtf",
|
||||
vec![IpAddr::V4(Ipv4Addr::new(116, 203, 206, 170))],
|
||||
),
|
||||
(
|
||||
"uninterest.ing",
|
||||
vec![IpAddr::V4(Ipv4Addr::new(172, 245, 70, 237))],
|
||||
),
|
||||
(
|
||||
"sweetfern.net",
|
||||
vec![IpAddr::V4(Ipv4Addr::new(178, 156, 228, 133))],
|
||||
),
|
||||
(
|
||||
"delta.disobey.net",
|
||||
vec![IpAddr::V4(Ipv4Addr::new(37, 74, 102, 44))],
|
||||
),
|
||||
(
|
||||
"darkrun.dev",
|
||||
vec![IpAddr::V4(Ipv4Addr::new(72, 11, 149, 146))],
|
||||
|
||||
+37
-58
@@ -13,7 +13,7 @@ use crate::context::Context;
|
||||
use crate::log::warn;
|
||||
use crate::net::proxy::ProxyConfig;
|
||||
use crate::net::session::SessionStream;
|
||||
use crate::net::tls::wrap_tls;
|
||||
use crate::net::tls::wrap_rustls;
|
||||
use crate::tools::time;
|
||||
|
||||
/// User-Agent for HTTP requests if a resource usage policy requires it.
|
||||
@@ -35,16 +35,7 @@ pub struct Response {
|
||||
|
||||
/// Retrieves the text contents of URL using HTTP GET request.
|
||||
pub async fn read_url(context: &Context, url: &str) -> Result<String> {
|
||||
read_url_with_tls(context, url, true).await
|
||||
}
|
||||
|
||||
/// Retrieves the text contents of URL using HTTP GET request.
|
||||
pub(crate) async fn read_url_with_tls(
|
||||
context: &Context,
|
||||
url: &str,
|
||||
strict_tls: bool,
|
||||
) -> Result<String> {
|
||||
let response = read_url_blob_with_tls(context, url, strict_tls).await?;
|
||||
let response = read_url_blob(context, url).await?;
|
||||
let text = String::from_utf8_lossy(&response.blob);
|
||||
Ok(text.to_string())
|
||||
}
|
||||
@@ -52,7 +43,6 @@ pub(crate) async fn read_url_with_tls(
|
||||
async fn get_http_sender<B>(
|
||||
context: &Context,
|
||||
parsed_url: hyper::Uri,
|
||||
strict_tls: bool,
|
||||
) -> Result<hyper::client::conn::http1::SendRequest<B>>
|
||||
where
|
||||
B: hyper::body::Body + 'static + Send,
|
||||
@@ -86,29 +76,37 @@ where
|
||||
let port = parsed_url.port_u16().unwrap_or(443);
|
||||
let (use_sni, load_cache) = (true, true);
|
||||
|
||||
let tcp_stream: Box<dyn SessionStream> = if let Some(proxy_config) = proxy_config_opt {
|
||||
if let Some(proxy_config) = proxy_config_opt {
|
||||
let proxy_stream = proxy_config
|
||||
.connect(context, host, port, load_cache)
|
||||
.await?;
|
||||
Box::new(proxy_stream)
|
||||
let tls_stream = wrap_rustls(
|
||||
host,
|
||||
port,
|
||||
use_sni,
|
||||
"",
|
||||
proxy_stream,
|
||||
&context.tls_session_store,
|
||||
&context.spki_hash_store,
|
||||
&context.sql,
|
||||
)
|
||||
.await?;
|
||||
Box::new(tls_stream)
|
||||
} else {
|
||||
let tcp_stream = crate::net::connect_tcp(context, host, port, load_cache).await?;
|
||||
Box::new(tcp_stream)
|
||||
};
|
||||
|
||||
let tls_stream = wrap_tls(
|
||||
strict_tls,
|
||||
host,
|
||||
port,
|
||||
use_sni,
|
||||
"",
|
||||
tcp_stream,
|
||||
&context.tls_session_store,
|
||||
&context.spki_hash_store,
|
||||
&context.sql,
|
||||
)
|
||||
.await?;
|
||||
Box::new(tls_stream)
|
||||
let tls_stream = wrap_rustls(
|
||||
host,
|
||||
port,
|
||||
use_sni,
|
||||
"",
|
||||
tcp_stream,
|
||||
&context.tls_session_store,
|
||||
&context.spki_hash_store,
|
||||
&context.sql,
|
||||
)
|
||||
.await?;
|
||||
Box::new(tls_stream)
|
||||
}
|
||||
}
|
||||
_ => bail!("Unknown URL scheme"),
|
||||
};
|
||||
@@ -262,7 +260,7 @@ pub(crate) async fn http_cache_cleanup(context: &Context) -> Result<()> {
|
||||
/// Fetches URL and updates the cache.
|
||||
///
|
||||
/// URL is fetched regardless of whether there is an existing result in the cache.
|
||||
async fn fetch_url(context: &Context, original_url: &str, strict_tls: bool) -> Result<Response> {
|
||||
async fn fetch_url(context: &Context, original_url: &str) -> Result<Response> {
|
||||
let mut url = original_url.to_string();
|
||||
|
||||
// Follow up to 10 http-redirects
|
||||
@@ -271,7 +269,7 @@ async fn fetch_url(context: &Context, original_url: &str, strict_tls: bool) -> R
|
||||
.parse::<hyper::Uri>()
|
||||
.with_context(|| format!("Failed to parse URL {url:?}"))?;
|
||||
|
||||
let mut sender = get_http_sender(context, parsed_url.clone(), strict_tls).await?;
|
||||
let mut sender = get_http_sender(context, parsed_url.clone()).await?;
|
||||
let authority = parsed_url
|
||||
.authority()
|
||||
.context("URL has no authority")?
|
||||
@@ -341,10 +339,8 @@ async fn fetch_url(context: &Context, original_url: &str, strict_tls: bool) -> R
|
||||
mimetype,
|
||||
encoding,
|
||||
};
|
||||
if strict_tls {
|
||||
info!(context, "Inserting {original_url:?} into cache.");
|
||||
http_cache_put(context, &url, &response).await?;
|
||||
}
|
||||
info!(context, "Inserting {original_url:?} into cache.");
|
||||
http_cache_put(context, &url, &response).await?;
|
||||
return Ok(response);
|
||||
}
|
||||
|
||||
@@ -353,23 +349,6 @@ async fn fetch_url(context: &Context, original_url: &str, strict_tls: bool) -> R
|
||||
|
||||
/// Retrieves the binary contents of URL using HTTP GET request.
|
||||
pub async fn read_url_blob(context: &Context, url: &str) -> Result<Response> {
|
||||
read_url_blob_with_tls(context, url, true).await
|
||||
}
|
||||
|
||||
/// Retrieves the binary contents of URL using HTTP GET request.
|
||||
pub(crate) async fn read_url_blob_with_tls(
|
||||
context: &Context,
|
||||
url: &str,
|
||||
strict_tls: bool,
|
||||
) -> Result<Response> {
|
||||
if !strict_tls {
|
||||
info!(
|
||||
context,
|
||||
"Fetching {url:?} without HTTP cache due to relaxed TLS."
|
||||
);
|
||||
return fetch_url(context, url, strict_tls).await;
|
||||
}
|
||||
|
||||
if let Some((response, is_stale)) = http_cache_get(context, url).await? {
|
||||
info!(context, "Returning {url:?} from cache.");
|
||||
if is_stale {
|
||||
@@ -378,7 +357,7 @@ pub(crate) async fn read_url_blob_with_tls(
|
||||
tokio::spawn(async move {
|
||||
// Fetch URL in background to update the cache.
|
||||
info!(context, "Fetching stale {url:?} in background.");
|
||||
if let Err(err) = fetch_url(&context, &url, true).await {
|
||||
if let Err(err) = fetch_url(&context, &url).await {
|
||||
warn!(context, "Failed to revalidate {url:?}: {err:#}.");
|
||||
}
|
||||
});
|
||||
@@ -387,7 +366,7 @@ pub(crate) async fn read_url_blob_with_tls(
|
||||
}
|
||||
|
||||
info!(context, "Not found {url:?} in cache, fetching.");
|
||||
let response = fetch_url(context, url, true).await?;
|
||||
let response = fetch_url(context, url).await?;
|
||||
Ok(response)
|
||||
}
|
||||
|
||||
@@ -405,7 +384,7 @@ pub(crate) async fn post_empty(context: &Context, url: &str) -> Result<(String,
|
||||
bail!("POST requests to non-HTTPS URLs are not allowed");
|
||||
}
|
||||
|
||||
let mut sender = get_http_sender(context, parsed_url.clone(), true).await?;
|
||||
let mut sender = get_http_sender(context, parsed_url.clone()).await?;
|
||||
let authority = parsed_url
|
||||
.authority()
|
||||
.context("URL has no authority")?
|
||||
@@ -439,7 +418,7 @@ pub(crate) async fn post_string(context: &Context, url: &str, body: String) -> R
|
||||
bail!("POST requests to non-HTTPS URLs are not allowed");
|
||||
}
|
||||
|
||||
let mut sender = get_http_sender(context, parsed_url.clone(), true).await?;
|
||||
let mut sender = get_http_sender(context, parsed_url.clone()).await?;
|
||||
let authority = parsed_url
|
||||
.authority()
|
||||
.context("URL has no authority")?
|
||||
@@ -470,7 +449,7 @@ pub(crate) async fn post_form<T: Serialize + ?Sized>(
|
||||
}
|
||||
|
||||
let encoded_body = serde_urlencoded::to_string(form).context("Failed to encode data")?;
|
||||
let mut sender = get_http_sender(context, parsed_url.clone(), true).await?;
|
||||
let mut sender = get_http_sender(context, parsed_url.clone()).await?;
|
||||
let authority = parsed_url
|
||||
.authority()
|
||||
.context("URL has no authority")?
|
||||
|
||||
+41
-110
@@ -50,7 +50,7 @@ impl ShadowsocksConfig {
|
||||
#[derive(Debug, Clone, PartialEq, Eq)]
|
||||
pub struct HttpConfig {
|
||||
/// HTTP proxy host.
|
||||
pub host: url::Host,
|
||||
pub host: String,
|
||||
|
||||
/// HTTP proxy port.
|
||||
pub port: u16,
|
||||
@@ -63,7 +63,10 @@ pub struct HttpConfig {
|
||||
|
||||
impl HttpConfig {
|
||||
fn from_url(url: Url) -> Result<Self> {
|
||||
let host = url.host().context("HTTP proxy URL has no host")?.to_owned();
|
||||
let host = url
|
||||
.host_str()
|
||||
.context("HTTP proxy URL has no host")?
|
||||
.to_string();
|
||||
let port = url
|
||||
.port_or_known_default()
|
||||
.context("HTTP(S) URLs are guaranteed to return Some port")?;
|
||||
@@ -101,8 +104,7 @@ impl HttpConfig {
|
||||
|
||||
#[derive(Debug, Clone, PartialEq, Eq)]
|
||||
pub struct Socks5Config {
|
||||
/// Hostname or IP address.
|
||||
pub host: url::Host,
|
||||
pub host: String,
|
||||
pub port: u16,
|
||||
pub user_password: Option<(String, String)>,
|
||||
}
|
||||
@@ -115,13 +117,7 @@ impl Socks5Config {
|
||||
target_port: u16,
|
||||
load_dns_cache: bool,
|
||||
) -> Result<Socks5Stream<Pin<Box<TimeoutStream<TcpStream>>>>> {
|
||||
let hostname = match &self.host {
|
||||
url::Host::Domain(domain) => domain.to_string(),
|
||||
url::Host::Ipv4(addr) => addr.to_string(),
|
||||
url::Host::Ipv6(addr) => addr.to_string(),
|
||||
};
|
||||
|
||||
let tcp_stream = connect_tcp(context, &hostname, self.port, load_dns_cache)
|
||||
let tcp_stream = connect_tcp(context, &self.host, self.port, load_dns_cache)
|
||||
.await
|
||||
.context("Failed to connect to SOCKS5 proxy")?;
|
||||
|
||||
@@ -277,7 +273,10 @@ impl ProxyConfig {
|
||||
// Because of this we do not distinguish
|
||||
// between `socks5` and `socks5h`.
|
||||
"socks5" => {
|
||||
let host = url.host().context("socks5 URL has no host")?.to_owned();
|
||||
let host = url
|
||||
.host_str()
|
||||
.context("socks5 URL has no host")?
|
||||
.to_string();
|
||||
let port = url.port().unwrap_or(DEFAULT_SOCKS_PORT);
|
||||
let user_password = if let Some(password) = url.password() {
|
||||
let username = percent_encoding::percent_decode_str(url.username())
|
||||
@@ -403,14 +402,13 @@ impl ProxyConfig {
|
||||
match self {
|
||||
ProxyConfig::Http(http_config) => {
|
||||
let load_cache = false;
|
||||
let hostname = match &http_config.host {
|
||||
url::Host::Domain(domain) => domain.to_string(),
|
||||
url::Host::Ipv4(addr) => addr.to_string(),
|
||||
url::Host::Ipv6(addr) => addr.to_string(),
|
||||
};
|
||||
let tcp_stream =
|
||||
crate::net::connect_tcp(context, &hostname, http_config.port, load_cache)
|
||||
.await?;
|
||||
let tcp_stream = crate::net::connect_tcp(
|
||||
context,
|
||||
&http_config.host,
|
||||
http_config.port,
|
||||
load_cache,
|
||||
)
|
||||
.await?;
|
||||
let auth = if let Some((username, password)) = &http_config.user_password {
|
||||
Some((username.as_str(), password.as_str()))
|
||||
} else {
|
||||
@@ -421,18 +419,16 @@ impl ProxyConfig {
|
||||
}
|
||||
ProxyConfig::Https(https_config) => {
|
||||
let load_cache = true;
|
||||
let hostname = match &https_config.host {
|
||||
url::Host::Domain(domain) => domain.to_string(),
|
||||
url::Host::Ipv4(addr) => addr.to_string(),
|
||||
url::Host::Ipv6(addr) => addr.to_string(),
|
||||
};
|
||||
|
||||
let tcp_stream =
|
||||
crate::net::connect_tcp(context, &hostname, https_config.port, load_cache)
|
||||
.await?;
|
||||
let tcp_stream = crate::net::connect_tcp(
|
||||
context,
|
||||
&https_config.host,
|
||||
https_config.port,
|
||||
load_cache,
|
||||
)
|
||||
.await?;
|
||||
let use_sni = true;
|
||||
let tls_stream = wrap_rustls(
|
||||
&hostname,
|
||||
&https_config.host,
|
||||
https_config.port,
|
||||
use_sni,
|
||||
"",
|
||||
@@ -504,7 +500,6 @@ mod tests {
|
||||
use super::*;
|
||||
use crate::config::Config;
|
||||
use crate::test_utils::TestContext;
|
||||
use std::net::{Ipv4Addr, Ipv6Addr};
|
||||
|
||||
#[test]
|
||||
fn test_socks5_url() {
|
||||
@@ -512,35 +507,17 @@ mod tests {
|
||||
assert_eq!(
|
||||
proxy_config,
|
||||
ProxyConfig::Socks5(Socks5Config {
|
||||
// IPv4 address is parsed as a domain and not url::Host::Ipv4.
|
||||
// This is expected: <https://github.com/servo/rust-url/issues/767>.
|
||||
// We only need a distinction for IPv6 to remove square brackets
|
||||
// before passing the address to `lookup_host()`.
|
||||
host: url::Host::Domain("127.0.0.1".to_string()),
|
||||
host: "127.0.0.1".to_string(),
|
||||
port: 9050,
|
||||
user_password: None
|
||||
})
|
||||
);
|
||||
assert_eq!(proxy_config.to_url(), "socks5://127.0.0.1:9050".to_string());
|
||||
|
||||
let proxy_config = ProxyConfig::from_url("socks5://[::1]:9050").unwrap();
|
||||
assert_eq!(
|
||||
proxy_config,
|
||||
ProxyConfig::Socks5(Socks5Config {
|
||||
// IPv6 address should be recognized as IPv6 address and not "[::1]" hostname.
|
||||
// Otherwise we may try to resolve "[::1]" and fail to connect.
|
||||
host: url::Host::Ipv6(Ipv6Addr::LOCALHOST),
|
||||
port: 9050,
|
||||
user_password: None
|
||||
})
|
||||
);
|
||||
assert_eq!(proxy_config.to_url(), "socks5://[::1]:9050".to_string());
|
||||
|
||||
let proxy_config = ProxyConfig::from_url("socks5://foo:bar@127.0.0.1:9150").unwrap();
|
||||
assert_eq!(
|
||||
proxy_config,
|
||||
ProxyConfig::Socks5(Socks5Config {
|
||||
host: url::Host::Domain("127.0.0.1".to_string()),
|
||||
host: "127.0.0.1".to_string(),
|
||||
port: 9150,
|
||||
user_password: Some(("foo".to_string(), "bar".to_string()))
|
||||
})
|
||||
@@ -550,7 +527,7 @@ mod tests {
|
||||
assert_eq!(
|
||||
proxy_config,
|
||||
ProxyConfig::Socks5(Socks5Config {
|
||||
host: url::Host::Domain("127.0.0.1".to_string()),
|
||||
host: "127.0.0.1".to_string(),
|
||||
port: 9150,
|
||||
user_password: Some(("foo".to_string(), "bar".to_string()))
|
||||
})
|
||||
@@ -560,7 +537,7 @@ mod tests {
|
||||
assert_eq!(
|
||||
proxy_config,
|
||||
ProxyConfig::Socks5(Socks5Config {
|
||||
host: url::Host::Domain("127.0.0.1".to_string()),
|
||||
host: "127.0.0.1".to_string(),
|
||||
port: 80,
|
||||
user_password: None
|
||||
})
|
||||
@@ -570,7 +547,7 @@ mod tests {
|
||||
assert_eq!(
|
||||
proxy_config,
|
||||
ProxyConfig::Socks5(Socks5Config {
|
||||
host: url::Host::Domain("127.0.0.1".to_string()),
|
||||
host: "127.0.0.1".to_string(),
|
||||
port: 1080,
|
||||
user_password: None
|
||||
})
|
||||
@@ -580,7 +557,7 @@ mod tests {
|
||||
assert_eq!(
|
||||
proxy_config,
|
||||
ProxyConfig::Socks5(Socks5Config {
|
||||
host: url::Host::Domain("127.0.0.1".to_string()),
|
||||
host: "127.0.0.1".to_string(),
|
||||
port: 1080,
|
||||
user_password: None
|
||||
})
|
||||
@@ -590,7 +567,7 @@ mod tests {
|
||||
assert_eq!(
|
||||
proxy_config,
|
||||
ProxyConfig::Socks5(Socks5Config {
|
||||
host: url::Host::Domain("my-proxy.example.org".to_string()),
|
||||
host: "my-proxy.example.org".to_string(),
|
||||
port: 1080,
|
||||
user_password: None
|
||||
})
|
||||
@@ -607,61 +584,37 @@ mod tests {
|
||||
assert_eq!(
|
||||
proxy_config,
|
||||
ProxyConfig::Http(HttpConfig {
|
||||
host: url::Host::Ipv4(Ipv4Addr::LOCALHOST),
|
||||
host: "127.0.0.1".to_string(),
|
||||
port: 80,
|
||||
user_password: None
|
||||
})
|
||||
);
|
||||
|
||||
let proxy_config = ProxyConfig::from_url("http://[::1]").unwrap();
|
||||
assert_eq!(
|
||||
proxy_config,
|
||||
ProxyConfig::Http(HttpConfig {
|
||||
host: url::Host::Ipv6(Ipv6Addr::LOCALHOST),
|
||||
port: 80,
|
||||
user_password: None
|
||||
})
|
||||
);
|
||||
assert_eq!(proxy_config.to_url(), "http://[::1]:80".to_string());
|
||||
|
||||
let proxy_config = ProxyConfig::from_url("http://127.0.0.1:80").unwrap();
|
||||
assert_eq!(
|
||||
proxy_config,
|
||||
ProxyConfig::Http(HttpConfig {
|
||||
host: url::Host::Ipv4(Ipv4Addr::LOCALHOST),
|
||||
host: "127.0.0.1".to_string(),
|
||||
port: 80,
|
||||
user_password: None
|
||||
})
|
||||
);
|
||||
assert_eq!(proxy_config.to_url(), "http://127.0.0.1:80".to_string());
|
||||
|
||||
let proxy_config = ProxyConfig::from_url("http://[::1]:80").unwrap();
|
||||
assert_eq!(
|
||||
proxy_config,
|
||||
ProxyConfig::Http(HttpConfig {
|
||||
host: url::Host::Ipv6(Ipv6Addr::LOCALHOST),
|
||||
port: 80,
|
||||
user_password: None
|
||||
})
|
||||
);
|
||||
assert_eq!(proxy_config.to_url(), "http://[::1]:80".to_string());
|
||||
|
||||
let proxy_config = ProxyConfig::from_url("http://127.0.0.1:443").unwrap();
|
||||
assert_eq!(
|
||||
proxy_config,
|
||||
ProxyConfig::Http(HttpConfig {
|
||||
host: url::Host::Ipv4(Ipv4Addr::LOCALHOST),
|
||||
host: "127.0.0.1".to_string(),
|
||||
port: 443,
|
||||
user_password: None
|
||||
})
|
||||
);
|
||||
assert_eq!(proxy_config.to_url(), "http://127.0.0.1:443".to_string());
|
||||
|
||||
let proxy_config = ProxyConfig::from_url("http://my-proxy.example.org").unwrap();
|
||||
assert_eq!(
|
||||
proxy_config,
|
||||
ProxyConfig::Http(HttpConfig {
|
||||
host: url::Host::Domain("my-proxy.example.org".to_string()),
|
||||
host: "my-proxy.example.org".to_string(),
|
||||
port: 80,
|
||||
user_password: None
|
||||
})
|
||||
@@ -678,7 +631,7 @@ mod tests {
|
||||
assert_eq!(
|
||||
proxy_config,
|
||||
ProxyConfig::Https(HttpConfig {
|
||||
host: url::Host::Ipv4(Ipv4Addr::LOCALHOST),
|
||||
host: "127.0.0.1".to_string(),
|
||||
port: 443,
|
||||
user_password: None
|
||||
})
|
||||
@@ -688,17 +641,7 @@ mod tests {
|
||||
assert_eq!(
|
||||
proxy_config,
|
||||
ProxyConfig::Https(HttpConfig {
|
||||
host: url::Host::Ipv4(Ipv4Addr::LOCALHOST),
|
||||
port: 80,
|
||||
user_password: None
|
||||
})
|
||||
);
|
||||
|
||||
let proxy_config = ProxyConfig::from_url("https://[::1]:80").unwrap();
|
||||
assert_eq!(
|
||||
proxy_config,
|
||||
ProxyConfig::Https(HttpConfig {
|
||||
host: url::Host::Ipv6(Ipv6Addr::LOCALHOST),
|
||||
host: "127.0.0.1".to_string(),
|
||||
port: 80,
|
||||
user_password: None
|
||||
})
|
||||
@@ -708,29 +651,17 @@ mod tests {
|
||||
assert_eq!(
|
||||
proxy_config,
|
||||
ProxyConfig::Https(HttpConfig {
|
||||
host: url::Host::Ipv4(Ipv4Addr::LOCALHOST),
|
||||
host: "127.0.0.1".to_string(),
|
||||
port: 443,
|
||||
user_password: None
|
||||
})
|
||||
);
|
||||
assert_eq!(proxy_config.to_url(), "https://127.0.0.1:443".to_string());
|
||||
|
||||
let proxy_config = ProxyConfig::from_url("https://[::1]:443").unwrap();
|
||||
assert_eq!(
|
||||
proxy_config,
|
||||
ProxyConfig::Https(HttpConfig {
|
||||
host: url::Host::Ipv6(Ipv6Addr::LOCALHOST),
|
||||
port: 443,
|
||||
user_password: None
|
||||
})
|
||||
);
|
||||
assert_eq!(proxy_config.to_url(), "https://[::1]:443".to_string());
|
||||
|
||||
let proxy_config = ProxyConfig::from_url("https://my-proxy.example.org").unwrap();
|
||||
assert_eq!(
|
||||
proxy_config,
|
||||
ProxyConfig::Https(HttpConfig {
|
||||
host: url::Host::Domain("my-proxy.example.org".to_string()),
|
||||
host: "my-proxy.example.org".to_string(),
|
||||
port: 443,
|
||||
user_password: None
|
||||
})
|
||||
|
||||
+3
-6
@@ -126,12 +126,9 @@ pub async fn wrap_rustls<'a>(
|
||||
let root_cert_store =
|
||||
rustls::RootCertStore::from_iter(webpki_roots::TLS_SERVER_ROOTS.iter().cloned());
|
||||
|
||||
let mut config = rustls::ClientConfig::builder_with_provider(Arc::new(
|
||||
rustls::crypto::aws_lc_rs::default_provider(),
|
||||
))
|
||||
.with_safe_default_protocol_versions()?
|
||||
.with_root_certificates(root_cert_store)
|
||||
.with_no_client_auth();
|
||||
let mut config = rustls::ClientConfig::builder()
|
||||
.with_root_certificates(root_cert_store)
|
||||
.with_no_client_auth();
|
||||
config.alpn_protocols = if alpn.is_empty() {
|
||||
vec![]
|
||||
} else {
|
||||
|
||||
@@ -51,7 +51,7 @@ impl rustls::client::danger::ServerCertVerifier for CustomCertificateVerifier {
|
||||
|
||||
let spki = parsed_certificate.subject_public_key_info();
|
||||
|
||||
let provider = rustls::crypto::aws_lc_rs::default_provider();
|
||||
let provider = rustls::crypto::ring::default_provider();
|
||||
|
||||
if let ServerName::DnsName(dns_name) = server_name
|
||||
&& dns_name.as_ref().starts_with("_")
|
||||
@@ -97,7 +97,7 @@ impl rustls::client::danger::ServerCertVerifier for CustomCertificateVerifier {
|
||||
cert: &CertificateDer<'_>,
|
||||
dss: &rustls::DigitallySignedStruct,
|
||||
) -> Result<rustls::client::danger::HandshakeSignatureValid, rustls::Error> {
|
||||
let provider = rustls::crypto::aws_lc_rs::default_provider();
|
||||
let provider = rustls::crypto::ring::default_provider();
|
||||
let supported_schemes = &provider.signature_verification_algorithms;
|
||||
rustls::crypto::verify_tls12_signature(message, cert, dss, supported_schemes)
|
||||
}
|
||||
@@ -108,13 +108,13 @@ impl rustls::client::danger::ServerCertVerifier for CustomCertificateVerifier {
|
||||
cert: &CertificateDer<'_>,
|
||||
dss: &rustls::DigitallySignedStruct,
|
||||
) -> Result<rustls::client::danger::HandshakeSignatureValid, rustls::Error> {
|
||||
let provider = rustls::crypto::aws_lc_rs::default_provider();
|
||||
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<rustls::SignatureScheme> {
|
||||
let provider = rustls::crypto::aws_lc_rs::default_provider();
|
||||
let provider = rustls::crypto::ring::default_provider();
|
||||
provider
|
||||
.signature_verification_algorithms
|
||||
.supported_schemes()
|
||||
|
||||
-37
@@ -847,41 +847,4 @@ mod tests {
|
||||
assert!(merge_openpgp_certificates(alice.clone(), bob.clone()).is_err());
|
||||
assert!(merge_openpgp_certificates(bob.clone(), alice.clone()).is_err());
|
||||
}
|
||||
|
||||
/// Test PQC support.
|
||||
#[tokio::test(flavor = "multi_thread", worker_threads = 2)]
|
||||
async fn test_pqc() -> Result<()> {
|
||||
let mut tcm = TestContextManager::new();
|
||||
let alice = &tcm.alice().await;
|
||||
let pqc = &tcm.pqc().await;
|
||||
|
||||
let pqc_received_message = tcm.send_recv_accept(alice, pqc, "Hi!").await;
|
||||
let pqc_chat_id = pqc_received_message.chat_id;
|
||||
let pqc_sent = pqc.send_text(pqc_chat_id, "Hello back!").await;
|
||||
|
||||
let alice_rcvd = alice.recv_msg(&pqc_sent).await;
|
||||
assert_eq!(alice_rcvd.text, "Hello back!");
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Tests securejoin with inviter using PQC key.
|
||||
#[tokio::test(flavor = "multi_thread", worker_threads = 2)]
|
||||
async fn test_securejoin_pqc_inviter() {
|
||||
let mut tcm = TestContextManager::new();
|
||||
let alice = &tcm.alice().await;
|
||||
let pqc = &tcm.pqc().await;
|
||||
|
||||
tcm.execute_securejoin(pqc, alice).await;
|
||||
}
|
||||
|
||||
/// Tests securejoin with joiner using PQC key.
|
||||
#[tokio::test(flavor = "multi_thread", worker_threads = 2)]
|
||||
async fn test_securejoin_pqc_joiner() {
|
||||
let mut tcm = TestContextManager::new();
|
||||
let pqc = &tcm.pqc().await;
|
||||
let bob = &tcm.bob().await;
|
||||
|
||||
tcm.execute_securejoin(bob, pqc).await;
|
||||
}
|
||||
}
|
||||
|
||||
+10
-10
@@ -39,7 +39,7 @@ impl PlainText {
|
||||
<html><head>
|
||||
<meta http-equiv="Content-Type" content="text/html; charset=utf-8" />
|
||||
<meta name="color-scheme" content="light dark" />
|
||||
</head><body dir="auto" style="unicode-bidi: plaintext">
|
||||
</head><body>
|
||||
"#
|
||||
.to_string();
|
||||
|
||||
@@ -132,7 +132,7 @@ http://link-at-start-of-line.org
|
||||
<html><head>
|
||||
<meta http-equiv="Content-Type" content="text/html; charset=utf-8" />
|
||||
<meta name="color-scheme" content="light dark" />
|
||||
</head><body dir="auto" style="unicode-bidi: plaintext">
|
||||
</head><body>
|
||||
line 1<br/>
|
||||
line 2<br/>
|
||||
line with <a href="https://link-mid-of-line.org">https://link-mid-of-line.org</a> and <a href="http://link-end-of-line.com/file?foo=bar%20">http://link-end-of-line.com/file?foo=bar%20</a><br/>
|
||||
@@ -156,7 +156,7 @@ line with <a href="https://link-mid-of-line.org">https://link-mid-of-line.org</a
|
||||
<html><head>
|
||||
<meta http-equiv="Content-Type" content="text/html; charset=utf-8" />
|
||||
<meta name="color-scheme" content="light dark" />
|
||||
</head><body dir="auto" style="unicode-bidi: plaintext">
|
||||
</head><body>
|
||||
Foo<br/>
|
||||
bar<br/>
|
||||
</body></html>
|
||||
@@ -178,7 +178,7 @@ bar<br/>
|
||||
<html><head>
|
||||
<meta http-equiv="Content-Type" content="text/html; charset=utf-8" />
|
||||
<meta name="color-scheme" content="light dark" />
|
||||
</head><body dir="auto" style="unicode-bidi: plaintext">
|
||||
</head><body>
|
||||
line with <<a href="http://encapsulated.link/?foo=_bar">http://encapsulated.link/?foo=_bar</a>> here!<br/>
|
||||
</body></html>
|
||||
"#
|
||||
@@ -199,7 +199,7 @@ line with <<a href="http://encapsulated.link/?foo=_bar">http://encapsulated.l
|
||||
<html><head>
|
||||
<meta http-equiv="Content-Type" content="text/html; charset=utf-8" />
|
||||
<meta name="color-scheme" content="light dark" />
|
||||
</head><body dir="auto" style="unicode-bidi: plaintext">
|
||||
</head><body>
|
||||
line with nohttp://no.link here<br/>
|
||||
</body></html>
|
||||
"#
|
||||
@@ -220,7 +220,7 @@ line with nohttp://no.link here<br/>
|
||||
<html><head>
|
||||
<meta http-equiv="Content-Type" content="text/html; charset=utf-8" />
|
||||
<meta name="color-scheme" content="light dark" />
|
||||
</head><body dir="auto" style="unicode-bidi: plaintext">
|
||||
</head><body>
|
||||
just an address: <a href="mailto:foo@bar.org">foo@bar.org</a> <a href="mailto:another@one.de">another@one.de</a><br/>
|
||||
</body></html>
|
||||
"#
|
||||
@@ -241,7 +241,7 @@ just an address: <a href="mailto:foo@bar.org">foo@bar.org</a> <a href="mailto:an
|
||||
<html><head>
|
||||
<meta http-equiv="Content-Type" content="text/html; charset=utf-8" />
|
||||
<meta name="color-scheme" content="light dark" />
|
||||
</head><body dir="auto" style="unicode-bidi: plaintext">
|
||||
</head><body>
|
||||
line still line<br/>
|
||||
<em>>quote </em><br/>
|
||||
<em>>still quote</em><br/>
|
||||
@@ -265,7 +265,7 @@ line still line<br/>
|
||||
<html><head>
|
||||
<meta http-equiv="Content-Type" content="text/html; charset=utf-8" />
|
||||
<meta name="color-scheme" content="light dark" />
|
||||
</head><body dir="auto" style="unicode-bidi: plaintext">
|
||||
</head><body>
|
||||
linestill line<br/>
|
||||
<em>>quote </em><br/>
|
||||
<em>>still quote</em><br/>
|
||||
@@ -289,7 +289,7 @@ linestill line<br/>
|
||||
<html><head>
|
||||
<meta http-equiv="Content-Type" content="text/html; charset=utf-8" />
|
||||
<meta name="color-scheme" content="light dark" />
|
||||
</head><body dir="auto" style="unicode-bidi: plaintext">
|
||||
</head><body>
|
||||
line <br/>
|
||||
still line<br/>
|
||||
<em>>quote </em><br/>
|
||||
@@ -314,7 +314,7 @@ still line<br/>
|
||||
<html><head>
|
||||
<meta http-equiv="Content-Type" content="text/html; charset=utf-8" />
|
||||
<meta name="color-scheme" content="light dark" />
|
||||
</head><body dir="auto" style="unicode-bidi: plaintext">
|
||||
</head><body>
|
||||
def foo():<br/>
|
||||
pass<br/>
|
||||
<br/>
|
||||
|
||||
+11
-5
@@ -890,10 +890,16 @@ static P_NAUTA_CU: Provider = Provider {
|
||||
strict_tls: false,
|
||||
..ProviderOptions::new()
|
||||
},
|
||||
config_defaults: Some(&[ConfigDefault {
|
||||
key: Config::MediaQuality,
|
||||
value: "1",
|
||||
}]),
|
||||
config_defaults: Some(&[
|
||||
ConfigDefault {
|
||||
key: Config::DeleteServerAfter,
|
||||
value: "1",
|
||||
},
|
||||
ConfigDefault {
|
||||
key: Config::MediaQuality,
|
||||
value: "1",
|
||||
},
|
||||
]),
|
||||
oauth2_authorizer: None,
|
||||
};
|
||||
|
||||
@@ -2376,4 +2382,4 @@ pub(crate) static PROVIDER_IDS: LazyLock<HashMap<&'static str, &'static Provider
|
||||
});
|
||||
|
||||
pub static _PROVIDER_UPDATED: LazyLock<chrono::NaiveDate> =
|
||||
LazyLock::new(|| chrono::NaiveDate::from_ymd_opt(2026, 5, 6).unwrap());
|
||||
LazyLock::new(|| chrono::NaiveDate::from_ymd_opt(2026, 4, 21).unwrap());
|
||||
|
||||
@@ -332,7 +332,7 @@ fn inner_generate_secure_join_qr_code(
|
||||
d.attr("cx", logo_position_x + HALF_LOGO_SIZE)?;
|
||||
d.attr("cy", logo_position_y + HALF_LOGO_SIZE)?;
|
||||
d.attr("r", HALF_LOGO_SIZE)?;
|
||||
d.attr("style", format!("fill:{color}"))
|
||||
d.attr("style", format!("fill:{}", &color))
|
||||
})?;
|
||||
|
||||
let avatar_font_size = LOGO_SIZE * 0.65;
|
||||
|
||||
+11
-19
@@ -3,13 +3,13 @@
|
||||
use std::collections::BTreeMap;
|
||||
use std::time::Duration;
|
||||
|
||||
use anyhow::{Context as _, Result};
|
||||
use anyhow::{Context as _, Result, anyhow};
|
||||
use async_imap::types::{Quota, QuotaResource};
|
||||
|
||||
use crate::EventType;
|
||||
use crate::context::Context;
|
||||
use crate::imap::session::Session as ImapSession;
|
||||
use crate::tools::{self, time_elapsed};
|
||||
use crate::{EventType, stock_str};
|
||||
|
||||
/// quota icon in connectivity is "yellow".
|
||||
pub const QUOTA_WARN_THRESHOLD_PERCENTAGE: u64 = 80;
|
||||
@@ -17,25 +17,13 @@ pub const QUOTA_WARN_THRESHOLD_PERCENTAGE: u64 = 80;
|
||||
/// quota icon in connectivity is "red".
|
||||
pub const QUOTA_ERROR_THRESHOLD_PERCENTAGE: u64 = 95;
|
||||
|
||||
/// [QuotaInfo] error.
|
||||
#[derive(Debug, thiserror::Error)]
|
||||
pub enum Error {
|
||||
/// Quota info not supported by the provider
|
||||
#[error("Quota info not supported by the provider")]
|
||||
NotSupportedByProvider,
|
||||
|
||||
/// Any other error: network, parsing, etc.
|
||||
#[error("{0:#}")]
|
||||
Other(#[from] anyhow::Error),
|
||||
}
|
||||
|
||||
/// Server quota information with an update timestamp.
|
||||
#[derive(Debug)]
|
||||
pub struct QuotaInfo {
|
||||
/// Recently loaded quota information.
|
||||
/// set to `Err()` if the provider does not support quota or on other errors,
|
||||
/// set to `Ok()` for valid quota information.
|
||||
pub(crate) recent: Result<BTreeMap<String, Vec<QuotaResource>>, Error>,
|
||||
pub(crate) recent: Result<BTreeMap<String, Vec<QuotaResource>>>,
|
||||
|
||||
/// When the structure was modified.
|
||||
pub(crate) modified: tools::Time,
|
||||
@@ -78,6 +66,12 @@ impl Context {
|
||||
|
||||
/// Updates `quota.recent`, sets `quota.modified` to the current time
|
||||
/// and emits an event to let the UIs update connectivity view.
|
||||
///
|
||||
/// Moreover, once each time quota gets larger than `QUOTA_WARN_THRESHOLD_PERCENTAGE`,
|
||||
/// a device message is added.
|
||||
/// As the message is added only once, the user is not spammed
|
||||
/// in case for some providers the quota is always at ~100%
|
||||
/// and new space is allocated as needed.
|
||||
pub(crate) async fn update_recent_quota(
|
||||
&self,
|
||||
session: &mut ImapSession,
|
||||
@@ -88,11 +82,9 @@ impl Context {
|
||||
info!(self, "Transport {transport_id}: Updating quota.");
|
||||
|
||||
let quota = if session.can_check_quota() {
|
||||
get_unique_quota_roots_and_usage(session, folder)
|
||||
.await
|
||||
.map_err(Error::Other)
|
||||
get_unique_quota_roots_and_usage(session, folder).await
|
||||
} else {
|
||||
Err(Error::NotSupportedByProvider)
|
||||
Err(anyhow!(stock_str::not_supported_by_provider(self)))
|
||||
};
|
||||
|
||||
self.quota.write().await.insert(
|
||||
|
||||
+49
-43
@@ -335,17 +335,18 @@ impl Chat {
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use deltachat_contact_tools::ContactAddress;
|
||||
|
||||
use super::*;
|
||||
use crate::chat::{forward_msgs, get_chat_msgs, marknoticed_chat, send_text_msg};
|
||||
use crate::chatlist::Chatlist;
|
||||
use crate::config::Config;
|
||||
use crate::contact::Contact;
|
||||
use crate::contact::{Contact, Origin};
|
||||
use crate::key::{load_self_public_key, load_self_secret_key};
|
||||
use crate::message::{MessageState, Viewtype, delete_msgs, markseen_msgs};
|
||||
use crate::pgp::{SeipdVersion, pk_encrypt};
|
||||
use crate::receive_imf::receive_imf;
|
||||
use crate::sql::housekeeping;
|
||||
use crate::test_utils;
|
||||
use crate::test_utils::E2EE_INFO_MSGS;
|
||||
use crate::test_utils::TestContext;
|
||||
use crate::test_utils::TestContextManager;
|
||||
@@ -385,54 +386,59 @@ mod tests {
|
||||
|
||||
#[tokio::test(flavor = "multi_thread", worker_threads = 2)]
|
||||
async fn test_receive_reaction() -> Result<()> {
|
||||
let mut tcm = TestContextManager::new();
|
||||
let alice = &tcm.alice().await;
|
||||
let bob = &tcm.bob().await;
|
||||
let alice = TestContext::new_alice().await;
|
||||
|
||||
// Alice receives BCC-self copy of a message sent to Bob.
|
||||
let encrypted_message = test_utils::encrypt_raw_message(
|
||||
alice,
|
||||
&[alice, bob],
|
||||
b"To: bob@example.net\r\n\
|
||||
From: alice@example.org\r\n\
|
||||
Date: Today, 29 February 2021 00:00:00 -800\r\n\
|
||||
Message-ID: 12345@example.org\r\n\
|
||||
Subject: Meeting\r\n\
|
||||
\r\n\
|
||||
Can we chat at 1pm pacific, today?",
|
||||
receive_imf(
|
||||
&alice,
|
||||
"To: bob@example.net\n\
|
||||
From: alice@example.org\n\
|
||||
Date: Today, 29 February 2021 00:00:00 -800\n\
|
||||
Message-ID: 12345@example.org\n\
|
||||
Subject: Meeting\n\
|
||||
\n\
|
||||
Can we chat at 1pm pacific, today?"
|
||||
.as_bytes(),
|
||||
false,
|
||||
)
|
||||
.await?;
|
||||
receive_imf(alice, encrypted_message.as_bytes(), false).await?;
|
||||
let msg = alice.get_last_msg().await;
|
||||
assert_eq!(msg.state, MessageState::OutDelivered);
|
||||
let reactions = get_msg_reactions(alice, msg.id).await?;
|
||||
let reactions = get_msg_reactions(&alice, msg.id).await?;
|
||||
let contacts = reactions.contacts();
|
||||
assert_eq!(contacts.len(), 0);
|
||||
|
||||
let bob_id = alice.add_or_lookup_contact_id(bob).await;
|
||||
let bob_id = Contact::add_or_lookup(
|
||||
&alice,
|
||||
"",
|
||||
&ContactAddress::new("bob@example.net")?,
|
||||
Origin::ManuallyCreated,
|
||||
)
|
||||
.await?
|
||||
.0;
|
||||
let bob_reaction = reactions.get(bob_id);
|
||||
assert!(bob_reaction.is_empty()); // Bob has not reacted to message yet.
|
||||
|
||||
// Alice receives reaction to her message from Bob.
|
||||
test_utils::receive_encrypted_imf(
|
||||
alice,
|
||||
bob,
|
||||
"To: alice@example.org\r\n\
|
||||
From: bob@example.net\r\n\
|
||||
Date: Today, 29 February 2021 00:00:10 -800\r\n\
|
||||
Message-ID: 56789@example.net\r\n\
|
||||
In-Reply-To: 12345@example.org\r\n\
|
||||
Subject: Meeting\r\n\
|
||||
Mime-Version: 1.0 (1.0)\r\n\
|
||||
Content-Type: text/plain; charset=utf-8\r\n\
|
||||
Content-Disposition: reaction\r\n\
|
||||
\r\n\
|
||||
receive_imf(
|
||||
&alice,
|
||||
"To: alice@example.org\n\
|
||||
From: bob@example.net\n\
|
||||
Date: Today, 29 February 2021 00:00:10 -800\n\
|
||||
Message-ID: 56789@example.net\n\
|
||||
In-Reply-To: 12345@example.org\n\
|
||||
Subject: Meeting\n\
|
||||
Mime-Version: 1.0 (1.0)\n\
|
||||
Content-Type: text/plain; charset=utf-8\n\
|
||||
Content-Disposition: reaction\n\
|
||||
\n\
|
||||
\u{1F44D}"
|
||||
.as_bytes(),
|
||||
false,
|
||||
)
|
||||
.await?;
|
||||
|
||||
let reactions = get_msg_reactions(alice, msg.id).await?;
|
||||
let reactions = get_msg_reactions(&alice, msg.id).await?;
|
||||
assert_eq!(reactions.to_string(), "👍1");
|
||||
|
||||
let contacts = reactions.contacts();
|
||||
@@ -445,9 +451,8 @@ Content-Disposition: reaction\r\n\
|
||||
assert_eq!(bob_reaction.as_str(), "👍");
|
||||
|
||||
// Alice receives reaction to her message from Bob with a footer.
|
||||
test_utils::receive_encrypted_imf(
|
||||
alice,
|
||||
bob,
|
||||
receive_imf(
|
||||
&alice,
|
||||
"To: alice@example.org\n\
|
||||
From: bob@example.net\n\
|
||||
Date: Today, 29 February 2021 00:00:10 -800\n\
|
||||
@@ -464,16 +469,16 @@ Content-Disposition: reaction\n\
|
||||
_______________________________________________\n\
|
||||
Here's my footer -- bob@example.net"
|
||||
.as_bytes(),
|
||||
false,
|
||||
)
|
||||
.await?;
|
||||
|
||||
let reactions = get_msg_reactions(alice, msg.id).await?;
|
||||
let reactions = get_msg_reactions(&alice, msg.id).await?;
|
||||
assert_eq!(reactions.to_string(), "😀1");
|
||||
|
||||
// Alice receives a message with reaction to her message from Bob.
|
||||
let msg_bob = test_utils::receive_encrypted_imf(
|
||||
alice,
|
||||
bob,
|
||||
let msg_bob = receive_imf(
|
||||
&alice,
|
||||
"To: alice@example.org\n\
|
||||
From: bob@example.net\n\
|
||||
Date: Today, 29 February 2021 00:00:10 -800\n\
|
||||
@@ -497,16 +502,18 @@ Content-Disposition: reaction\n\
|
||||
\n\
|
||||
--YiEDa0DAkWCtVeE4--"
|
||||
.as_bytes(),
|
||||
false,
|
||||
)
|
||||
.await?;
|
||||
let msg_bob = Message::load_from_db(alice, msg_bob.msg_ids[0]).await?;
|
||||
.await?
|
||||
.unwrap();
|
||||
let msg_bob = Message::load_from_db(&alice, msg_bob.msg_ids[0]).await?;
|
||||
assert_eq!(msg_bob.from_id, bob_id);
|
||||
assert_eq!(msg_bob.chat_id, msg.chat_id);
|
||||
assert_eq!(msg_bob.viewtype, Viewtype::Text);
|
||||
assert_eq!(msg_bob.state, MessageState::InFresh);
|
||||
assert_eq!(msg_bob.hidden, false);
|
||||
assert_eq!(msg_bob.text, "Reply + reaction");
|
||||
let reactions = get_msg_reactions(alice, msg.id).await?;
|
||||
let reactions = get_msg_reactions(&alice, msg.id).await?;
|
||||
assert_eq!(reactions.to_string(), "👍1");
|
||||
|
||||
Ok(())
|
||||
@@ -811,7 +818,6 @@ Content-Disposition: reaction\n\
|
||||
#[tokio::test(flavor = "multi_thread", worker_threads = 2)]
|
||||
async fn test_reaction_forwarded_summary() -> Result<()> {
|
||||
let alice = TestContext::new_alice().await;
|
||||
alice.allow_unencrypted().await?;
|
||||
|
||||
// Alice adds a message to "Saved Messages"
|
||||
let self_chat = alice.get_self_chat().await;
|
||||
|
||||
+88
-91
@@ -505,14 +505,6 @@ pub(crate) async fn receive_imf_inner(
|
||||
Ok(mime_parser) => mime_parser,
|
||||
};
|
||||
|
||||
if !mime_parser.was_encrypted()
|
||||
&& mime_parser.get_header(HeaderDef::SecureJoin).is_none()
|
||||
&& context.get_config_bool(Config::ForceEncryption).await?
|
||||
{
|
||||
warn!(context, "Fetched unencrypted message, ignoring");
|
||||
return trash().await;
|
||||
}
|
||||
|
||||
let rfc724_mid_orig = &mime_parser
|
||||
.get_rfc724_mid()
|
||||
.unwrap_or(rfc724_mid.to_string());
|
||||
@@ -814,6 +806,8 @@ UPDATE config SET value=? WHERE keyname='configured_addr' AND value!=?1
|
||||
if transport_changed {
|
||||
info!(context, "Primary transport changed to {from_addr:?}.");
|
||||
context.sql.uncache_raw_config("configured_addr").await;
|
||||
|
||||
// Regenerate User ID in V4 keys.
|
||||
context.self_public_key.lock().await.take();
|
||||
|
||||
context.emit_event(EventType::TransportsModified);
|
||||
@@ -835,16 +829,21 @@ UPDATE config SET value=? WHERE keyname='configured_addr' AND value!=?1
|
||||
{
|
||||
can_info_msg = false;
|
||||
if mime_parser.pre_message == PreMessageMode::Post
|
||||
&& let Some(msg) =
|
||||
Message::load_by_rfc724_mid_optional(context, rfc724_mid_orig).await?
|
||||
&& let Some(msg_id) = message::rfc724_mid_exists(context, rfc724_mid_orig).await?
|
||||
{
|
||||
// The messsage is a post-message and pre-message exists.
|
||||
// Assign status update to existing message because just received post-message will be trashed.
|
||||
Some(msg)
|
||||
Some(
|
||||
Message::load_from_db(context, msg_id)
|
||||
.await
|
||||
.context("Failed to load webxdc instance that we just checked exists")?,
|
||||
)
|
||||
} else {
|
||||
Message::load_from_db_optional(context, insert_msg_id)
|
||||
.await
|
||||
.context("Failed to load just created webxdc instance")?
|
||||
Some(
|
||||
Message::load_from_db(context, insert_msg_id)
|
||||
.await
|
||||
.context("Failed to load just created webxdc instance")?,
|
||||
)
|
||||
}
|
||||
} else if let Some(field) = mime_parser.get_header(HeaderDef::InReplyTo) {
|
||||
if let Some(instance) =
|
||||
@@ -905,8 +904,10 @@ UPDATE config SET value=? WHERE keyname='configured_addr' AND value!=?1
|
||||
}
|
||||
|
||||
// Get user-configured server deletion
|
||||
let delete_server_after = context.get_config_delete_server_after().await?;
|
||||
|
||||
if !received_msg.msg_ids.is_empty() {
|
||||
let target = if received_msg.needs_delete_job {
|
||||
let target = if received_msg.needs_delete_job || delete_server_after == Some(0) {
|
||||
Some("".to_string())
|
||||
} else {
|
||||
None
|
||||
@@ -2345,81 +2346,79 @@ INSERT INTO msgs
|
||||
|
||||
/// Checks for "Chat-Edit" and "Chat-Delete" headers,
|
||||
/// and edits/deletes existing messages accordingly.
|
||||
///
|
||||
/// Returns `true` if this message is an edit/deletion request.
|
||||
async fn handle_edit_delete(
|
||||
context: &Context,
|
||||
mime_parser: &MimeMessage,
|
||||
from_id: ContactId,
|
||||
) -> Result<()> {
|
||||
if let Some(rfc724_mid) = mime_parser.get_header(HeaderDef::ChatEdit) {
|
||||
let Some(original_msg_id) = rfc724_mid_exists(context, rfc724_mid).await? else {
|
||||
if let Some(original_msg_id) = rfc724_mid_exists(context, rfc724_mid).await? {
|
||||
if let Some(mut original_msg) =
|
||||
Message::load_from_db_optional(context, original_msg_id).await?
|
||||
{
|
||||
if original_msg.from_id == from_id {
|
||||
if let Some(part) = mime_parser.parts.first() {
|
||||
let edit_msg_showpadlock = part
|
||||
.param
|
||||
.get_bool(Param::GuaranteeE2ee)
|
||||
.unwrap_or_default();
|
||||
if edit_msg_showpadlock || !original_msg.get_showpadlock() {
|
||||
let new_text =
|
||||
part.msg.strip_prefix(EDITED_PREFIX).unwrap_or(&part.msg);
|
||||
chat::save_text_edit_to_db(context, &mut original_msg, new_text)
|
||||
.await?;
|
||||
} else {
|
||||
warn!(context, "Edit message: Not encrypted.");
|
||||
}
|
||||
}
|
||||
} else {
|
||||
warn!(context, "Edit message: Bad sender.");
|
||||
}
|
||||
} else {
|
||||
warn!(context, "Edit message: Database entry does not exist.");
|
||||
}
|
||||
} else {
|
||||
warn!(
|
||||
context,
|
||||
"Edit message: rfc724_mid {rfc724_mid:?} not found."
|
||||
);
|
||||
return Ok(());
|
||||
};
|
||||
let Some(mut original_msg) =
|
||||
Message::load_from_db_optional(context, original_msg_id).await?
|
||||
else {
|
||||
warn!(context, "Edit message: Database entry does not exist.");
|
||||
return Ok(());
|
||||
};
|
||||
if original_msg.from_id != from_id {
|
||||
warn!(context, "Edit message: Bad sender.");
|
||||
return Ok(());
|
||||
}
|
||||
let Some(part) = mime_parser.parts.first() else {
|
||||
return Ok(());
|
||||
};
|
||||
|
||||
let edit_msg_showpadlock = part
|
||||
.param
|
||||
.get_bool(Param::GuaranteeE2ee)
|
||||
.unwrap_or_default();
|
||||
if !edit_msg_showpadlock && original_msg.get_showpadlock() {
|
||||
warn!(context, "Edit message: Not encrypted.");
|
||||
return Ok(());
|
||||
}
|
||||
|
||||
let new_text = part.msg.strip_prefix(EDITED_PREFIX).unwrap_or(&part.msg);
|
||||
chat::save_text_edit_to_db(context, &mut original_msg, new_text).await?;
|
||||
} else if let Some(rfc724_mid_list) = mime_parser.get_header(HeaderDef::ChatDelete)
|
||||
&& let Some(part) = mime_parser.parts.first()
|
||||
{
|
||||
// See `message::delete_msgs_ex()`, unlike edit requests, DC doesn't send unencrypted
|
||||
// deletion requests, so there's no need to support them.
|
||||
if part.param.get_bool(Param::GuaranteeE2ee) != Some(true) {
|
||||
warn!(context, "Delete message: Not encrypted.");
|
||||
return Ok(());
|
||||
}
|
||||
if part.param.get_bool(Param::GuaranteeE2ee).unwrap_or(false) {
|
||||
let mut modified_chat_ids = BTreeSet::new();
|
||||
let mut msg_ids = Vec::new();
|
||||
|
||||
let mut modified_chat_ids = BTreeSet::new();
|
||||
let mut msg_ids = Vec::new();
|
||||
|
||||
let rfc724_mid_vec: Vec<&str> = rfc724_mid_list.split_whitespace().collect();
|
||||
for rfc724_mid in rfc724_mid_vec {
|
||||
let rfc724_mid = rfc724_mid.trim_start_matches('<').trim_end_matches('>');
|
||||
let Some(msg_id) = message::rfc724_mid_exists(context, rfc724_mid).await? else {
|
||||
warn!(context, "Delete message: {rfc724_mid:?} not found.");
|
||||
// Insert a tombstone so that the message will be ignored if it arrives later within a period specified in prune_tombstones().
|
||||
insert_tombstone(context, rfc724_mid).await?;
|
||||
continue;
|
||||
};
|
||||
|
||||
let Some(msg) = Message::load_from_db_optional(context, msg_id).await? else {
|
||||
warn!(context, "Delete message: Database entry does not exist.");
|
||||
continue;
|
||||
};
|
||||
if msg.from_id != from_id {
|
||||
warn!(context, "Delete message: Bad sender.");
|
||||
continue;
|
||||
let rfc724_mid_vec: Vec<&str> = rfc724_mid_list.split_whitespace().collect();
|
||||
for rfc724_mid in rfc724_mid_vec {
|
||||
let rfc724_mid = rfc724_mid.trim_start_matches('<').trim_end_matches('>');
|
||||
if let Some(msg_id) = message::rfc724_mid_exists(context, rfc724_mid).await? {
|
||||
if let Some(msg) = Message::load_from_db_optional(context, msg_id).await? {
|
||||
if msg.from_id == from_id {
|
||||
message::delete_msg_locally(context, &msg).await?;
|
||||
msg_ids.push(msg.id);
|
||||
modified_chat_ids.insert(msg.chat_id);
|
||||
} else {
|
||||
warn!(context, "Delete message: Bad sender.");
|
||||
}
|
||||
} else {
|
||||
warn!(context, "Delete message: Database entry does not exist.");
|
||||
}
|
||||
} else {
|
||||
warn!(context, "Delete message: {rfc724_mid:?} not found.");
|
||||
// Insert a tombstone so that the message will be ignored if it arrives later within a period specified in prune_tombstones().
|
||||
insert_tombstone(context, rfc724_mid).await?;
|
||||
}
|
||||
}
|
||||
|
||||
message::delete_msg_locally(context, &msg).await?;
|
||||
msg_ids.push(msg.id);
|
||||
modified_chat_ids.insert(msg.chat_id);
|
||||
message::delete_msgs_locally_done(context, &msg_ids, modified_chat_ids).await?;
|
||||
} else {
|
||||
warn!(context, "Delete message: Not encrypted.");
|
||||
}
|
||||
message::delete_msgs_locally_done(context, &msg_ids, modified_chat_ids).await?;
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
@@ -3563,7 +3562,12 @@ async fn create_or_lookup_mailinglist_or_broadcast(
|
||||
mime_parser.timestamp_sent,
|
||||
)
|
||||
.await
|
||||
.with_context(|| format!("failed to create mailinglist '{name}' for grpid={listid}",))?;
|
||||
.with_context(|| {
|
||||
format!(
|
||||
"failed to create mailinglist '{}' for grpid={}",
|
||||
&name, &listid
|
||||
)
|
||||
})?;
|
||||
|
||||
if chattype == Chattype::InBroadcast {
|
||||
chat::add_to_chat_contacts_table(
|
||||
@@ -3790,17 +3794,13 @@ async fn apply_out_broadcast_changes(
|
||||
} else if from_id == ContactId::SELF
|
||||
&& let Some(removed_id) = removed_id
|
||||
{
|
||||
if chat::remove_from_chat_contacts_table_without_trace(context, chat.id, removed_id)
|
||||
.await?
|
||||
{
|
||||
better_msg.get_or_insert(
|
||||
stock_str::msg_del_member_local(context, removed_id, ContactId::SELF).await,
|
||||
);
|
||||
added_removed_id = Some(removed_id);
|
||||
} else {
|
||||
info!(context, "No-op broadcast member removal message (TRASH).");
|
||||
better_msg = Some("".to_string());
|
||||
}
|
||||
chat::remove_from_chat_contacts_table_without_trace(context, chat.id, removed_id)
|
||||
.await?;
|
||||
|
||||
better_msg.get_or_insert(
|
||||
stock_str::msg_del_member_local(context, removed_id, ContactId::SELF).await,
|
||||
);
|
||||
added_removed_id = Some(removed_id);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -3874,20 +3874,17 @@ async fn apply_in_broadcast_changes(
|
||||
}
|
||||
chat::delete_broadcast_secret(context, chat.id).await?;
|
||||
|
||||
let removed =
|
||||
chat::remove_from_chat_contacts_table_without_trace(context, chat.id, ContactId::SELF)
|
||||
.await?;
|
||||
if !removed {
|
||||
info!(context, "No-op broadcast SELF-removal message (TRASH).");
|
||||
better_msg = Some("".to_string());
|
||||
} else if from_id == ContactId::SELF {
|
||||
if from_id == ContactId::SELF {
|
||||
better_msg.get_or_insert(stock_str::msg_you_left_broadcast(context));
|
||||
} else {
|
||||
better_msg.get_or_insert(
|
||||
stock_str::msg_del_member_local(context, ContactId::SELF, from_id).await,
|
||||
);
|
||||
}
|
||||
send_event_chat_modified |= removed;
|
||||
|
||||
chat::remove_from_chat_contacts_table_without_trace(context, chat.id, ContactId::SELF)
|
||||
.await?;
|
||||
send_event_chat_modified = true;
|
||||
} else if !chat.is_self_in_chat(context).await? {
|
||||
chat::add_to_chat_contacts_table(
|
||||
context,
|
||||
|
||||
@@ -15,7 +15,6 @@ use crate::imap::prefetch_should_download;
|
||||
use crate::imex::{ImexMode, imex};
|
||||
use crate::key;
|
||||
use crate::securejoin::get_securejoin_qr;
|
||||
use crate::test_utils;
|
||||
use crate::test_utils::{
|
||||
TestContext, TestContextManager, alice_keypair, get_chat_msg, mark_as_verified,
|
||||
};
|
||||
@@ -81,7 +80,6 @@ static GRP_MAIL: &[u8] =
|
||||
#[tokio::test(flavor = "multi_thread", worker_threads = 2)]
|
||||
async fn test_adhoc_group_is_shown() {
|
||||
let t = TestContext::new_alice().await;
|
||||
t.allow_unencrypted().await.unwrap();
|
||||
|
||||
let chats = Chatlist::try_load(&t, 0, None, None).await.unwrap();
|
||||
assert_eq!(chats.len(), 0);
|
||||
@@ -102,7 +100,6 @@ async fn test_adhoc_group_is_shown() {
|
||||
#[tokio::test(flavor = "multi_thread", worker_threads = 2)]
|
||||
async fn test_adhoc_group_show_accepted_contact_accepted() {
|
||||
let t = TestContext::new_alice().await;
|
||||
t.allow_unencrypted().await.unwrap();
|
||||
|
||||
// accept Bob by accepting a delta-message from Bob
|
||||
receive_imf(&t, MSGRMSG, false).await.unwrap();
|
||||
@@ -138,7 +135,6 @@ async fn test_adhoc_group_show_accepted_contact_accepted() {
|
||||
#[tokio::test(flavor = "multi_thread", worker_threads = 2)]
|
||||
async fn test_adhoc_group_show_all() {
|
||||
let t = TestContext::new_alice().await;
|
||||
t.allow_unencrypted().await.unwrap();
|
||||
receive_imf(&t, GRP_MAIL, false).await.unwrap();
|
||||
|
||||
// adhoc-group with unknown contacts with show_emails=all will show up in a single chat
|
||||
@@ -158,7 +154,6 @@ async fn test_adhoc_group_show_all() {
|
||||
async fn test_adhoc_groups_merge() -> Result<()> {
|
||||
let mut tcm = TestContextManager::new();
|
||||
let alice = &tcm.alice().await;
|
||||
alice.allow_unencrypted().await?;
|
||||
receive_imf(
|
||||
alice,
|
||||
b"From: bob@example.net\n\
|
||||
@@ -359,7 +354,6 @@ async fn test_no_message_id_header() {
|
||||
#[tokio::test(flavor = "multi_thread", worker_threads = 2)]
|
||||
async fn test_escaped_from() {
|
||||
let t = TestContext::new_alice().await;
|
||||
t.allow_unencrypted().await.unwrap();
|
||||
let contact_id = Contact::create(&t, "foobar", "foobar@example.com")
|
||||
.await
|
||||
.unwrap();
|
||||
@@ -393,7 +387,6 @@ async fn test_escaped_from() {
|
||||
#[tokio::test(flavor = "multi_thread", worker_threads = 2)]
|
||||
async fn test_escaped_recipients() {
|
||||
let t = TestContext::new_alice().await;
|
||||
t.allow_unencrypted().await.unwrap();
|
||||
Contact::create(&t, "foobar", "foobar@example.com")
|
||||
.await
|
||||
.unwrap();
|
||||
@@ -441,7 +434,6 @@ async fn test_escaped_recipients() {
|
||||
#[tokio::test(flavor = "multi_thread", worker_threads = 2)]
|
||||
async fn test_cc_to_contact() {
|
||||
let t = TestContext::new_alice().await;
|
||||
t.allow_unencrypted().await.unwrap();
|
||||
Contact::create(&t, "foobar", "foobar@example.com")
|
||||
.await
|
||||
.unwrap();
|
||||
@@ -598,7 +590,6 @@ async fn test_parse_ndn(
|
||||
) -> (TestContext, MsgId) {
|
||||
let t = TestContext::new().await;
|
||||
t.configure_addr(self_addr).await;
|
||||
t.allow_unencrypted().await.unwrap();
|
||||
|
||||
receive_imf(
|
||||
&t,
|
||||
@@ -683,7 +674,6 @@ async fn test_resend_after_ndn() -> Result<()> {
|
||||
async fn test_parse_ndn_group_msg() -> Result<()> {
|
||||
let t = TestContext::new().await;
|
||||
t.configure_addr("alice@gmail.com").await;
|
||||
t.allow_unencrypted().await?;
|
||||
|
||||
receive_imf(
|
||||
&t,
|
||||
@@ -714,10 +704,10 @@ async fn test_parse_ndn_group_msg() -> Result<()> {
|
||||
assert_eq!(msg.state, MessageState::OutFailed);
|
||||
|
||||
let msgs = chat::get_chat_msgs(&t, msg.chat_id).await?;
|
||||
assert!(matches!(
|
||||
*msgs.last().unwrap(),
|
||||
ChatItem::Message { msg_id } if msg_id == msg.id
|
||||
));
|
||||
let ChatItem::Message { msg_id } = *msgs.last().unwrap() else {
|
||||
panic!("Wrong item type");
|
||||
};
|
||||
assert_eq!(msg_id, msg.id);
|
||||
Ok(())
|
||||
}
|
||||
|
||||
@@ -725,7 +715,6 @@ async fn test_parse_ndn_group_msg() -> Result<()> {
|
||||
async fn test_concat_multiple_ndns() -> Result<()> {
|
||||
let t = TestContext::new().await;
|
||||
t.configure_addr("alice@posteo.org").await;
|
||||
t.allow_unencrypted().await?;
|
||||
let mid = "1234@mail.gmail.com";
|
||||
receive_imf(
|
||||
&t,
|
||||
@@ -783,7 +772,6 @@ async fn load_imf_email(context: &Context, imf_raw: &[u8]) -> Message {
|
||||
#[tokio::test(flavor = "multi_thread", worker_threads = 2)]
|
||||
async fn test_html_only_mail() {
|
||||
let t = TestContext::new_alice().await;
|
||||
t.allow_unencrypted().await.unwrap();
|
||||
let msg = load_imf_email(&t, include_bytes!("../../test-data/message/wrong-html.eml")).await;
|
||||
assert_eq!(
|
||||
msg.text,
|
||||
@@ -819,7 +807,6 @@ static GH_MAILINGLIST2: &str = "Received: (Postfix, from userid 1000); Mon, 4 De
|
||||
#[tokio::test(flavor = "multi_thread", worker_threads = 2)]
|
||||
async fn test_github_mailing_list() -> Result<()> {
|
||||
let t = TestContext::new_alice().await;
|
||||
t.allow_unencrypted().await?;
|
||||
|
||||
receive_imf(&t.ctx, GH_MAILINGLIST, false).await?;
|
||||
|
||||
@@ -893,8 +880,6 @@ static DC_MAILINGLIST2: &[u8] = b"Received: (Postfix, from userid 1000); Mon, 4
|
||||
#[tokio::test(flavor = "multi_thread", worker_threads = 2)]
|
||||
async fn test_classic_mailing_list() -> Result<()> {
|
||||
let t = TestContext::new_alice().await;
|
||||
t.allow_unencrypted().await?;
|
||||
|
||||
receive_imf(&t.ctx, DC_MAILINGLIST, false).await.unwrap();
|
||||
let chats = Chatlist::try_load(&t.ctx, 0, None, None).await.unwrap();
|
||||
let chat_id = chats.get_chat_id(0).unwrap();
|
||||
@@ -936,8 +921,6 @@ Hello mailinglist!\r\n"
|
||||
#[tokio::test(flavor = "multi_thread", worker_threads = 2)]
|
||||
async fn test_other_device_writes_to_mailinglist() -> Result<()> {
|
||||
let t = TestContext::new_alice().await;
|
||||
t.allow_unencrypted().await?;
|
||||
|
||||
receive_imf(&t, DC_MAILINGLIST, false).await.unwrap();
|
||||
let first_msg = t.get_last_msg().await;
|
||||
let first_chat = Chat::load_from_db(&t, first_msg.chat_id).await?;
|
||||
@@ -988,7 +971,6 @@ async fn test_other_device_writes_to_mailinglist() -> Result<()> {
|
||||
#[tokio::test(flavor = "multi_thread", worker_threads = 2)]
|
||||
async fn test_block_mailing_list() {
|
||||
let t = TestContext::new_alice().await;
|
||||
t.allow_unencrypted().await.unwrap();
|
||||
|
||||
receive_imf(&t.ctx, DC_MAILINGLIST, false).await.unwrap();
|
||||
t.evtracker.wait_next_incoming_message().await;
|
||||
@@ -1023,7 +1005,6 @@ async fn test_block_mailing_list() {
|
||||
#[tokio::test(flavor = "multi_thread", worker_threads = 2)]
|
||||
async fn test_mailing_list_decide_block_then_unblock() {
|
||||
let t = TestContext::new_alice().await;
|
||||
t.allow_unencrypted().await.unwrap();
|
||||
|
||||
receive_imf(&t, DC_MAILINGLIST, false).await.unwrap();
|
||||
let blocked = Contact::get_all_blocked(&t).await.unwrap();
|
||||
@@ -1054,7 +1035,6 @@ async fn test_mailing_list_decide_block_then_unblock() {
|
||||
#[tokio::test(flavor = "multi_thread", worker_threads = 2)]
|
||||
async fn test_mailing_list_decide_not_now() {
|
||||
let t = TestContext::new_alice().await;
|
||||
t.allow_unencrypted().await.unwrap();
|
||||
|
||||
receive_imf(&t.ctx, DC_MAILINGLIST, false).await.unwrap();
|
||||
|
||||
@@ -1082,7 +1062,6 @@ async fn test_mailing_list_decide_not_now() {
|
||||
#[tokio::test(flavor = "multi_thread", worker_threads = 2)]
|
||||
async fn test_mailing_list_decide_accept() {
|
||||
let t = TestContext::new_alice().await;
|
||||
t.allow_unencrypted().await.unwrap();
|
||||
|
||||
receive_imf(&t.ctx, DC_MAILINGLIST, false).await.unwrap();
|
||||
|
||||
@@ -1105,8 +1084,6 @@ async fn test_mailing_list_decide_accept() {
|
||||
#[tokio::test(flavor = "multi_thread", worker_threads = 2)]
|
||||
async fn test_mailing_list_multiple_names_in_subject() -> Result<()> {
|
||||
let t = TestContext::new_alice().await;
|
||||
t.allow_unencrypted().await?;
|
||||
|
||||
receive_imf(
|
||||
&t,
|
||||
b"From: Foo Bar <foo@bar.org>\n\
|
||||
@@ -1131,7 +1108,6 @@ async fn test_mailing_list_multiple_names_in_subject() -> Result<()> {
|
||||
#[tokio::test(flavor = "multi_thread", worker_threads = 2)]
|
||||
async fn test_majordomo_mailing_list() -> Result<()> {
|
||||
let t = TestContext::new_alice().await;
|
||||
t.allow_unencrypted().await?;
|
||||
|
||||
// test mailing lists not having a `ListId:`-header
|
||||
receive_imf(
|
||||
@@ -1184,7 +1160,6 @@ async fn test_majordomo_mailing_list() -> Result<()> {
|
||||
#[tokio::test(flavor = "multi_thread", worker_threads = 2)]
|
||||
async fn test_mailchimp_mailing_list() -> Result<()> {
|
||||
let t = TestContext::new_alice().await;
|
||||
t.allow_unencrypted().await?;
|
||||
|
||||
receive_imf(
|
||||
&t,
|
||||
@@ -1218,7 +1193,6 @@ async fn test_mailchimp_mailing_list() -> Result<()> {
|
||||
#[tokio::test(flavor = "multi_thread", worker_threads = 2)]
|
||||
async fn test_dhl_mailing_list() -> Result<()> {
|
||||
let t = TestContext::new_alice().await;
|
||||
t.allow_unencrypted().await?;
|
||||
|
||||
receive_imf(
|
||||
&t,
|
||||
@@ -1244,7 +1218,6 @@ async fn test_dhl_mailing_list() -> Result<()> {
|
||||
#[tokio::test(flavor = "multi_thread", worker_threads = 2)]
|
||||
async fn test_dpd_mailing_list() -> Result<()> {
|
||||
let t = TestContext::new_alice().await;
|
||||
t.allow_unencrypted().await?;
|
||||
|
||||
receive_imf(
|
||||
&t,
|
||||
@@ -1270,7 +1243,6 @@ async fn test_dpd_mailing_list() -> Result<()> {
|
||||
#[tokio::test(flavor = "multi_thread", worker_threads = 2)]
|
||||
async fn test_xt_local_mailing_list() -> Result<()> {
|
||||
let t = TestContext::new_alice().await;
|
||||
t.allow_unencrypted().await?;
|
||||
|
||||
receive_imf(
|
||||
&t,
|
||||
@@ -1304,7 +1276,6 @@ async fn test_xt_local_mailing_list() -> Result<()> {
|
||||
#[tokio::test(flavor = "multi_thread", worker_threads = 2)]
|
||||
async fn test_xing_mailing_list() -> Result<()> {
|
||||
let t = TestContext::new_alice().await;
|
||||
t.allow_unencrypted().await?;
|
||||
|
||||
receive_imf(
|
||||
&t,
|
||||
@@ -1327,7 +1298,6 @@ async fn test_xing_mailing_list() -> Result<()> {
|
||||
#[tokio::test(flavor = "multi_thread", worker_threads = 2)]
|
||||
async fn test_ttline_mailing_list() -> Result<()> {
|
||||
let t = TestContext::new_alice().await;
|
||||
t.allow_unencrypted().await?;
|
||||
|
||||
receive_imf(
|
||||
&t,
|
||||
@@ -1348,7 +1318,6 @@ async fn test_ttline_mailing_list() -> Result<()> {
|
||||
#[tokio::test(flavor = "multi_thread", worker_threads = 2)]
|
||||
async fn test_mailing_list_with_mimepart_footer() {
|
||||
let t = TestContext::new_alice().await;
|
||||
t.allow_unencrypted().await.unwrap();
|
||||
|
||||
// the mailing list message contains two top-level texts.
|
||||
// the second text is a footer that is added by some mailing list software
|
||||
@@ -1376,7 +1345,6 @@ async fn test_mailing_list_with_mimepart_footer() {
|
||||
#[tokio::test(flavor = "multi_thread", worker_threads = 2)]
|
||||
async fn test_mailing_list_with_mimepart_footer_signed() {
|
||||
let t = TestContext::new_alice().await;
|
||||
t.allow_unencrypted().await.unwrap();
|
||||
|
||||
receive_imf(
|
||||
&t,
|
||||
@@ -1401,7 +1369,6 @@ async fn test_mailing_list_with_mimepart_footer_signed() {
|
||||
#[tokio::test(flavor = "multi_thread", worker_threads = 2)]
|
||||
async fn test_apply_mailinglist_changes_assigned_by_reply() {
|
||||
let t = TestContext::new_alice().await;
|
||||
t.allow_unencrypted().await.unwrap();
|
||||
|
||||
receive_imf(&t, GH_MAILINGLIST, false).await.unwrap();
|
||||
|
||||
@@ -1440,7 +1407,6 @@ async fn test_apply_mailinglist_changes_assigned_by_reply() {
|
||||
#[tokio::test(flavor = "multi_thread", worker_threads = 2)]
|
||||
async fn test_mailing_list_chat_message() {
|
||||
let t = TestContext::new_alice().await;
|
||||
t.allow_unencrypted().await.unwrap();
|
||||
|
||||
receive_imf(
|
||||
&t,
|
||||
@@ -1463,7 +1429,6 @@ async fn test_mailing_list_chat_message() {
|
||||
#[tokio::test(flavor = "multi_thread", worker_threads = 2)]
|
||||
async fn test_mailing_list_bot() {
|
||||
let t = TestContext::new_alice().await;
|
||||
t.allow_unencrypted().await.unwrap();
|
||||
t.set_config(Config::Bot, Some("1")).await.unwrap();
|
||||
|
||||
receive_imf(
|
||||
@@ -1496,8 +1461,6 @@ async fn test_dont_show_noreply_in_contacts_list() {
|
||||
|
||||
async fn check_dont_show_in_contacts_list(addr: &str) {
|
||||
let t = TestContext::new_alice().await;
|
||||
t.allow_unencrypted().await.unwrap();
|
||||
|
||||
receive_imf(
|
||||
&t,
|
||||
format!(
|
||||
@@ -1527,7 +1490,6 @@ YEAAAAAA!.
|
||||
#[tokio::test(flavor = "multi_thread", worker_threads = 2)]
|
||||
async fn test_pdf_filename_simple() {
|
||||
let t = TestContext::new_alice().await;
|
||||
t.allow_unencrypted().await.unwrap();
|
||||
let msg = load_imf_email(
|
||||
&t,
|
||||
include_bytes!("../../test-data/message/pdf_filename_simple.eml"),
|
||||
@@ -1548,7 +1510,6 @@ async fn test_pdf_filename_simple() {
|
||||
async fn test_pdf_filename_continuation() {
|
||||
// test filenames split across multiple header lines, see rfc 2231
|
||||
let t = TestContext::new_alice().await;
|
||||
t.allow_unencrypted().await.unwrap();
|
||||
let msg = load_imf_email(
|
||||
&t,
|
||||
include_bytes!("../../test-data/message/pdf_filename_continuation.eml"),
|
||||
@@ -1574,7 +1535,6 @@ async fn test_pdf_filename_continuation() {
|
||||
#[tokio::test(flavor = "multi_thread", worker_threads = 2)]
|
||||
async fn test_many_images() {
|
||||
let t = TestContext::new_alice().await;
|
||||
t.allow_unencrypted().await.unwrap();
|
||||
|
||||
receive_imf(
|
||||
&t,
|
||||
@@ -1595,7 +1555,6 @@ async fn test_many_images() {
|
||||
#[tokio::test(flavor = "multi_thread", worker_threads = 2)]
|
||||
async fn test_in_reply_to() {
|
||||
let t = TestContext::new().await;
|
||||
t.allow_unencrypted().await.unwrap();
|
||||
t.configure_addr("bob@example.com").await;
|
||||
|
||||
// Receive message from Alice about group "foo".
|
||||
@@ -1639,7 +1598,9 @@ async fn test_in_reply_to() {
|
||||
|
||||
// Load the first message from the same chat.
|
||||
let msgs = chat::get_chat_msgs(&t, msg.chat_id).await.unwrap();
|
||||
let ChatItem::Message { msg_id } = msgs.first().unwrap() else {
|
||||
let msg_id = if let ChatItem::Message { msg_id } = msgs.first().unwrap() {
|
||||
msg_id
|
||||
} else {
|
||||
panic!("Wrong item type");
|
||||
};
|
||||
|
||||
@@ -1671,7 +1632,6 @@ async fn test_save_mime_headers_off() -> anyhow::Result<()> {
|
||||
async fn check_alias_reply(from_dc: bool, chat_request: bool, group_request: bool) {
|
||||
let mut tcm = TestContextManager::new();
|
||||
let alice = tcm.alice().await;
|
||||
alice.allow_unencrypted().await.unwrap();
|
||||
|
||||
// Claire, a customer, sends a support request
|
||||
// to the alias address <support@example.org>.
|
||||
@@ -1738,8 +1698,6 @@ async fn check_alias_reply(from_dc: bool, chat_request: bool, group_request: boo
|
||||
|
||||
let claire = tcm.unconfigured().await;
|
||||
claire.configure_addr("claire@example.org").await;
|
||||
claire.allow_unencrypted().await.unwrap();
|
||||
|
||||
receive_imf(&claire, claire_request.as_bytes(), false)
|
||||
.await
|
||||
.unwrap();
|
||||
@@ -1876,57 +1834,47 @@ async fn test_alias_support_answer_from_dc_chat_group() {
|
||||
|
||||
#[tokio::test(flavor = "multi_thread", worker_threads = 2)]
|
||||
async fn test_dont_assign_to_trash_by_parent() {
|
||||
let mut tcm = TestContextManager::new();
|
||||
let alice = &tcm.alice().await;
|
||||
let bob = &tcm.bob().await;
|
||||
let charlie = &tcm.charlie().await;
|
||||
|
||||
tcm.section("Receive a message");
|
||||
let encrypted_message = test_utils::encrypt_raw_message(
|
||||
charlie,
|
||||
&[alice, bob],
|
||||
b"From: Charlie <charlie@example.net>\r\n\
|
||||
To: alice@example.org, bob@example.org\r\n\
|
||||
Subject: Hi\r\n\
|
||||
Message-ID: <4444@example.org>\r\n\
|
||||
\r\n\
|
||||
hello\r\n",
|
||||
let t = TestContext::new_alice().await;
|
||||
println!("\n========= Receive a message ==========");
|
||||
receive_imf(
|
||||
&t,
|
||||
b"From: Nu Bar <nu@bar.org>\n\
|
||||
To: alice@example.org, bob@example.org\n\
|
||||
Subject: Hi\n\
|
||||
Message-ID: <4444@example.org>\n\
|
||||
\n\
|
||||
hello\n",
|
||||
false,
|
||||
)
|
||||
.await
|
||||
.unwrap();
|
||||
receive_imf(alice, encrypted_message.as_bytes(), false)
|
||||
.await
|
||||
.unwrap();
|
||||
let chat_id = alice.get_last_msg().await.chat_id;
|
||||
chat_id.accept(alice).await.unwrap();
|
||||
let msg = get_chat_msg(alice, chat_id, 0, 1).await; // Make sure that the message is actually in the chat
|
||||
let chat_id = t.get_last_msg().await.chat_id;
|
||||
chat_id.accept(&t).await.unwrap();
|
||||
let msg = get_chat_msg(&t, chat_id, 0, 1).await; // Make sure that the message is actually in the chat
|
||||
assert!(!msg.chat_id.is_special());
|
||||
assert_eq!(msg.text, "Hi – hello");
|
||||
|
||||
tcm.section("Delete the message");
|
||||
msg.id.trash(alice, false).await.unwrap();
|
||||
println!("\n========= Delete the message ==========");
|
||||
msg.id.trash(&t, false).await.unwrap();
|
||||
|
||||
let msgs = chat::get_chat_msgs(alice, chat_id).await.unwrap();
|
||||
let msgs = chat::get_chat_msgs(&t.ctx, chat_id).await.unwrap();
|
||||
assert_eq!(msgs.len(), 0);
|
||||
|
||||
tcm.section("Receive a message that is a reply to the deleted message");
|
||||
let encrypted_message = test_utils::encrypt_raw_message(
|
||||
charlie,
|
||||
&[alice, bob],
|
||||
b"From: Charlie <charlie@example.net>\r\n\
|
||||
To: alice@example.org, bob@example.org\r\n\
|
||||
Subject: Re: Hi\r\n\
|
||||
Message-ID: <5555@example.org>\r\n\
|
||||
In-Reply-To: <4444@example.org\r\n\
|
||||
\r\n\
|
||||
Reply\r\n",
|
||||
println!("\n========= Receive a message that is a reply to the deleted message ==========");
|
||||
receive_imf(
|
||||
&t,
|
||||
b"From: Nu Bar <nu@bar.org>\n\
|
||||
To: alice@example.org, bob@example.org\n\
|
||||
Subject: Re: Hi\n\
|
||||
Message-ID: <5555@example.org>\n\
|
||||
In-Reply-To: <4444@example.org\n\
|
||||
\n\
|
||||
Reply\n",
|
||||
false,
|
||||
)
|
||||
.await
|
||||
.unwrap();
|
||||
receive_imf(alice, encrypted_message.as_bytes(), false)
|
||||
.await
|
||||
.unwrap();
|
||||
let msg = alice.get_last_msg().await;
|
||||
let msg = t.get_last_msg().await;
|
||||
assert!(!msg.chat_id.is_special()); // Esp. check that the chat_id is not TRASH
|
||||
assert_eq!(msg.text, "Reply");
|
||||
}
|
||||
@@ -1936,12 +1884,10 @@ async fn test_dont_show_all_outgoing_msgs_in_self_chat() {
|
||||
// Regression test for <https://github.com/deltachat/deltachat-android/issues/1940>:
|
||||
// Some servers add a `Bcc: <Self>` header, which caused all outgoing messages to
|
||||
// be shown in the self-chat.
|
||||
let mut tcm = TestContextManager::new();
|
||||
let t = &tcm.alice().await;
|
||||
t.allow_unencrypted().await.unwrap();
|
||||
let t = TestContext::new_alice().await;
|
||||
|
||||
receive_imf(
|
||||
t,
|
||||
&t,
|
||||
b"Bcc: alice@example.org
|
||||
Received: from [127.0.0.1]
|
||||
Subject: s
|
||||
@@ -1966,7 +1912,6 @@ Message content",
|
||||
async fn test_unencrypted_doesnt_goto_self_chat() -> Result<()> {
|
||||
let mut tcm = TestContextManager::new();
|
||||
let t = &tcm.alice().await;
|
||||
t.allow_unencrypted().await?;
|
||||
let mut chat_id = None;
|
||||
|
||||
for (i, to) in [
|
||||
@@ -2033,17 +1978,21 @@ async fn test_no_smtp_job_for_self_chat() -> Result<()> {
|
||||
assert!(bob.pop_sent_msg_opt(Duration::ZERO).await.is_none());
|
||||
|
||||
bob.set_config_bool(Config::BccSelf, true).await?;
|
||||
bob.set_config(Config::DeleteServerAfter, Some("1")).await?;
|
||||
let mut msg = Message::new_text("Happy birthday to me".to_string());
|
||||
chat::send_msg(bob, chat_id, &mut msg).await?;
|
||||
assert!(bob.pop_sent_msg_opt(Duration::ZERO).await.is_none());
|
||||
|
||||
bob.set_config(Config::DeleteServerAfter, None).await?;
|
||||
let mut msg = Message::new_text("Happy birthday to me".to_string());
|
||||
chat::send_msg(bob, chat_id, &mut msg).await?;
|
||||
assert!(bob.pop_sent_msg_opt(Duration::ZERO).await.is_some());
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[tokio::test(flavor = "multi_thread", worker_threads = 2)]
|
||||
async fn test_outgoing_classic_mail_creates_chat() {
|
||||
let alice = TestContext::new_alice().await;
|
||||
alice.allow_unencrypted().await.unwrap();
|
||||
|
||||
// Alice downloads outgoing classic email.
|
||||
receive_imf(
|
||||
@@ -2069,7 +2018,6 @@ Message content",
|
||||
async fn test_duplicate_message() -> Result<()> {
|
||||
// Test that duplicate messages are ignored based on the Message-ID
|
||||
let alice = TestContext::new_alice().await;
|
||||
alice.allow_unencrypted().await?;
|
||||
|
||||
let bob_contact_id = Contact::add_or_lookup(
|
||||
&alice,
|
||||
@@ -2128,8 +2076,6 @@ Second signature";
|
||||
#[tokio::test(flavor = "multi_thread", worker_threads = 2)]
|
||||
async fn test_ignore_footer_status_from_mailinglist() -> Result<()> {
|
||||
let t = TestContext::new_alice().await;
|
||||
t.allow_unencrypted().await?;
|
||||
|
||||
let bob_id = Contact::add_or_lookup(
|
||||
&t,
|
||||
"",
|
||||
@@ -2209,8 +2155,6 @@ Original signature updated",
|
||||
#[tokio::test(flavor = "multi_thread", worker_threads = 2)]
|
||||
async fn test_ignore_old_status_updates() -> Result<()> {
|
||||
let t = TestContext::new_alice().await;
|
||||
t.allow_unencrypted().await?;
|
||||
|
||||
let bob_id = Contact::add_or_lookup(
|
||||
&t,
|
||||
"",
|
||||
@@ -2280,13 +2224,11 @@ sig thursday",
|
||||
|
||||
#[tokio::test(flavor = "multi_thread", worker_threads = 2)]
|
||||
async fn test_chat_assignment_private_classical_reply() {
|
||||
let mut tcm = TestContextManager::new();
|
||||
for outgoing_is_classical in &[true, false] {
|
||||
let t = &tcm.alice().await;
|
||||
t.allow_unencrypted().await.unwrap();
|
||||
let t = TestContext::new_alice().await;
|
||||
|
||||
receive_imf(
|
||||
t,
|
||||
&t,
|
||||
format!(
|
||||
r#"Received: from mout.gmx.net (mout.gmx.net [212.227.17.22])
|
||||
Subject: =?utf-8?q?single_reply-to?=
|
||||
@@ -2323,12 +2265,12 @@ Message-ID: <Gr.eJ_llQIXf0K.buxmrnMmG0Y@gmx.de>"
|
||||
"Hello, I've just created the group \"single reply-to\" for us."
|
||||
}
|
||||
);
|
||||
let group_chat = Chat::load_from_db(t, group_msg.chat_id).await.unwrap();
|
||||
let group_chat = Chat::load_from_db(&t, group_msg.chat_id).await.unwrap();
|
||||
assert_eq!(group_chat.typ, Chattype::Group);
|
||||
assert_eq!(group_chat.name, "single reply-to");
|
||||
|
||||
receive_imf(
|
||||
t,
|
||||
&t,
|
||||
format!(
|
||||
r#"Subject: Re: single reply-to
|
||||
To: "Alice" <alice@example.org>
|
||||
@@ -2357,7 +2299,7 @@ Private reply"#,
|
||||
|
||||
let private_msg = t.get_last_msg().await;
|
||||
assert_eq!(private_msg.text, "Private reply");
|
||||
let private_chat = Chat::load_from_db(t, private_msg.chat_id).await.unwrap();
|
||||
let private_chat = Chat::load_from_db(&t, private_msg.chat_id).await.unwrap();
|
||||
assert_eq!(private_chat.typ, Chattype::Single);
|
||||
assert_ne!(private_msg.chat_id, group_msg.chat_id);
|
||||
}
|
||||
@@ -2365,17 +2307,16 @@ Private reply"#,
|
||||
|
||||
#[tokio::test(flavor = "multi_thread", worker_threads = 2)]
|
||||
async fn test_chat_assignment_private_chat_reply() {
|
||||
let mut tcm = TestContextManager::new();
|
||||
for (outgoing_is_classical, outgoing_has_multiple_recipients) in
|
||||
&[(true, true), (false, true), (false, false)]
|
||||
{
|
||||
let t = &tcm.alice().await;
|
||||
t.allow_unencrypted().await.unwrap();
|
||||
let t = TestContext::new_alice().await;
|
||||
|
||||
receive_imf(
|
||||
t,
|
||||
&t,
|
||||
format!(
|
||||
r#"Subject: =?utf-8?q?single_reply-to?=
|
||||
r#"Received: from mout.gmx.net (mout.gmx.net [212.227.17.22])
|
||||
Subject: =?utf-8?q?single_reply-to?=
|
||||
{}
|
||||
Date: Fri, 28 May 2021 10:15:05 +0000
|
||||
To: Bob <bob@example.com>, Charlie <charlie@example.net>{}
|
||||
@@ -2413,12 +2354,12 @@ Message-ID: <Gr.iy1KCE2y65_.mH2TM52miv9@testrun.org>"
|
||||
"Hello, I've just created the group \"single reply-to\" for us."
|
||||
}
|
||||
);
|
||||
let group_chat = Chat::load_from_db(t, group_msg.chat_id).await.unwrap();
|
||||
let group_chat = Chat::load_from_db(&t, group_msg.chat_id).await.unwrap();
|
||||
assert_eq!(group_chat.typ, Chattype::Group);
|
||||
assert_eq!(group_chat.name, "single reply-to");
|
||||
|
||||
receive_imf(
|
||||
t,
|
||||
&t,
|
||||
format!(
|
||||
r#"Subject: =?utf-8?q?Re=3A_single_reply-to?=
|
||||
MIME-Version: 1.0
|
||||
@@ -2453,7 +2394,7 @@ Sent with my Delta Chat Messenger: https://delta.chat
|
||||
|
||||
let private_msg = t.get_last_msg().await;
|
||||
assert_eq!(private_msg.text, "Private reply");
|
||||
let private_chat = Chat::load_from_db(t, private_msg.chat_id).await.unwrap();
|
||||
let private_chat = Chat::load_from_db(&t, private_msg.chat_id).await.unwrap();
|
||||
assert_eq!(private_chat.typ, Chattype::Single);
|
||||
assert_ne!(private_msg.chat_id, group_msg.chat_id);
|
||||
}
|
||||
@@ -2461,13 +2402,11 @@ Sent with my Delta Chat Messenger: https://delta.chat
|
||||
|
||||
#[tokio::test(flavor = "multi_thread", worker_threads = 2)]
|
||||
async fn test_chat_assignment_nonprivate_classical_reply() {
|
||||
let mut tcm = TestContextManager::new();
|
||||
for outgoing_is_classical in &[true, false] {
|
||||
let t = &tcm.alice().await;
|
||||
t.allow_unencrypted().await.unwrap();
|
||||
let t = TestContext::new_alice().await;
|
||||
|
||||
receive_imf(
|
||||
t,
|
||||
&t,
|
||||
format!(
|
||||
r#"Received: from mout.gmx.net (mout.gmx.net [212.227.17.22])
|
||||
Subject: =?utf-8?q?single_reply-to?=
|
||||
@@ -2503,13 +2442,13 @@ Message-ID: <Gr.eJ_llQIXf0K.buxmrnMmG0Y@gmx.de>"
|
||||
"Hello, I've just created the group \"single reply-to\" for us."
|
||||
}
|
||||
);
|
||||
let group_chat = Chat::load_from_db(t, group_msg.chat_id).await.unwrap();
|
||||
let group_chat = Chat::load_from_db(&t, group_msg.chat_id).await.unwrap();
|
||||
assert_eq!(group_chat.typ, Chattype::Group);
|
||||
assert_eq!(group_chat.name, "single reply-to");
|
||||
|
||||
// =============== Receive another outgoing message and check that it is put into the same chat ===============
|
||||
receive_imf(
|
||||
t,
|
||||
&t,
|
||||
format!(
|
||||
r#"Received: from mout.gmx.net (mout.gmx.net [212.227.17.22])
|
||||
Subject: Out subj
|
||||
@@ -2534,13 +2473,13 @@ Outgoing reply to all"#,
|
||||
|
||||
let reply = t.get_last_msg().await;
|
||||
assert_eq!(reply.text, "Out subj – Outgoing reply to all");
|
||||
let reply_chat = Chat::load_from_db(t, reply.chat_id).await.unwrap();
|
||||
let reply_chat = Chat::load_from_db(&t, reply.chat_id).await.unwrap();
|
||||
assert_eq!(reply_chat.typ, Chattype::Group);
|
||||
assert_eq!(reply.chat_id, group_msg.chat_id);
|
||||
|
||||
// =============== Receive an incoming message and check that it is put into the same chat ===============
|
||||
receive_imf(
|
||||
t,
|
||||
&t,
|
||||
br#"Received: from mout.gmx.net (mout.gmx.net [212.227.17.22])
|
||||
Subject: In subj
|
||||
To: "Bob" <bob@example.com>, "Claire" <claire@example.com>
|
||||
@@ -2557,7 +2496,7 @@ Reply to all"#,
|
||||
|
||||
let reply = t.get_last_msg().await;
|
||||
assert_eq!(reply.text, "In subj – Reply to all");
|
||||
let reply_chat = Chat::load_from_db(t, reply.chat_id).await.unwrap();
|
||||
let reply_chat = Chat::load_from_db(&t, reply.chat_id).await.unwrap();
|
||||
assert_eq!(reply_chat.typ, Chattype::Group);
|
||||
assert_eq!(reply.chat_id, group_msg.chat_id);
|
||||
}
|
||||
@@ -2575,9 +2514,6 @@ async fn test_chat_assignment_adhoc() -> Result<()> {
|
||||
let alice = tcm.alice().await;
|
||||
let bob = tcm.bob().await;
|
||||
let fiona = tcm.fiona().await;
|
||||
alice.allow_unencrypted().await?;
|
||||
bob.allow_unencrypted().await?;
|
||||
fiona.allow_unencrypted().await?;
|
||||
|
||||
let first_thread_mime = br#"Subject: First thread
|
||||
Message-ID: first@example.org
|
||||
@@ -2722,7 +2658,6 @@ async fn test_read_receipts_dont_unmark_bots() -> Result<()> {
|
||||
#[tokio::test(flavor = "multi_thread", worker_threads = 2)]
|
||||
async fn test_gmx_forwarded_msg() -> Result<()> {
|
||||
let t = TestContext::new_alice().await;
|
||||
t.allow_unencrypted().await?;
|
||||
|
||||
receive_imf(
|
||||
&t,
|
||||
@@ -2810,9 +2745,7 @@ async fn get_parent_message(
|
||||
|
||||
#[tokio::test(flavor = "multi_thread", worker_threads = 2)]
|
||||
async fn test_get_parent_message() -> Result<()> {
|
||||
let mut tcm = TestContextManager::new();
|
||||
let alice = &tcm.alice().await;
|
||||
let bob = &tcm.bob().await;
|
||||
let t = TestContext::new_alice().await;
|
||||
|
||||
let mime = br#"Subject: First
|
||||
Message-ID: first@example.net
|
||||
@@ -2821,8 +2754,8 @@ From: Bob <bob@example.net>
|
||||
Content-Type: text/plain; charset=utf-8; format=flowed; delsp=no
|
||||
|
||||
First."#;
|
||||
test_utils::receive_encrypted_imf(alice, bob, mime).await?;
|
||||
let first = alice.get_last_msg().await;
|
||||
receive_imf(&t, mime, false).await?;
|
||||
let first = t.get_last_msg().await;
|
||||
let mime = br#"Subject: Second
|
||||
Message-ID: second@example.net
|
||||
To: Alice <alice@example.org>
|
||||
@@ -2830,8 +2763,8 @@ From: Bob <bob@example.net>
|
||||
Content-Type: text/plain; charset=utf-8; format=flowed; delsp=no
|
||||
|
||||
First."#;
|
||||
test_utils::receive_encrypted_imf(alice, bob, mime).await?;
|
||||
let second = alice.get_last_msg().await;
|
||||
receive_imf(&t, mime, false).await?;
|
||||
let second = t.get_last_msg().await;
|
||||
let mime = br#"Subject: Third
|
||||
Message-ID: third@example.net
|
||||
To: Alice <alice@example.org>
|
||||
@@ -2839,8 +2772,8 @@ From: Bob <bob@example.net>
|
||||
Content-Type: text/plain; charset=utf-8; format=flowed; delsp=no
|
||||
|
||||
First."#;
|
||||
test_utils::receive_encrypted_imf(alice, bob, mime).await?;
|
||||
let third = alice.get_last_msg().await;
|
||||
receive_imf(&t, mime, false).await?;
|
||||
let third = t.get_last_msg().await;
|
||||
|
||||
let mime = br#"Subject: Message with references.
|
||||
Message-ID: second@example.net
|
||||
@@ -2851,22 +2784,21 @@ References: <second@example.net> <nonexistent@example.net> <first@example.net>
|
||||
Content-Type: text/plain; charset=utf-8; format=flowed; delsp=no
|
||||
|
||||
Message with references."#;
|
||||
let encrypted_mime = test_utils::encrypt_raw_message(bob, &[alice], mime).await?;
|
||||
let mime_parser = MimeMessage::from_bytes(alice, encrypted_mime.as_bytes()).await?;
|
||||
let mime_parser = MimeMessage::from_bytes(&t, &mime[..]).await?;
|
||||
|
||||
let parent = get_parent_message(alice, &mime_parser).await?.unwrap();
|
||||
let parent = get_parent_message(&t, &mime_parser).await?.unwrap();
|
||||
assert_eq!(parent.id, first.id);
|
||||
|
||||
message::delete_msgs(alice, &[first.id]).await?;
|
||||
let parent = get_parent_message(alice, &mime_parser).await?.unwrap();
|
||||
message::delete_msgs(&t, &[first.id]).await?;
|
||||
let parent = get_parent_message(&t, &mime_parser).await?.unwrap();
|
||||
assert_eq!(parent.id, second.id);
|
||||
|
||||
message::delete_msgs(alice, &[second.id]).await?;
|
||||
let parent = get_parent_message(alice, &mime_parser).await?.unwrap();
|
||||
message::delete_msgs(&t, &[second.id]).await?;
|
||||
let parent = get_parent_message(&t, &mime_parser).await?.unwrap();
|
||||
assert_eq!(parent.id, third.id);
|
||||
|
||||
message::delete_msgs(alice, &[third.id]).await?;
|
||||
let parent = get_parent_message(alice, &mime_parser).await?;
|
||||
message::delete_msgs(&t, &[third.id]).await?;
|
||||
let parent = get_parent_message(&t, &mime_parser).await?;
|
||||
assert!(parent.is_none());
|
||||
|
||||
Ok(())
|
||||
@@ -3092,8 +3024,7 @@ async fn test_auto_accept_for_bots() -> Result<()> {
|
||||
#[tokio::test(flavor = "multi_thread", worker_threads = 2)]
|
||||
async fn test_auto_accept_group_for_bots() -> Result<()> {
|
||||
let t = TestContext::new_alice().await;
|
||||
t.set_config_bool(Config::Bot, true).await.unwrap();
|
||||
t.allow_unencrypted().await.unwrap();
|
||||
t.set_config(Config::Bot, Some("1")).await.unwrap();
|
||||
let msg = load_imf_email(&t, GRP_MAIL).await;
|
||||
|
||||
let chat = chat::Chat::load_from_db(&t, msg.chat_id).await?;
|
||||
@@ -3322,7 +3253,6 @@ async fn test_blocked_contact_creates_group() -> Result<()> {
|
||||
async fn test_outgoing_undecryptable() -> Result<()> {
|
||||
let alice = &TestContext::new().await;
|
||||
alice.configure_addr("alice@example.org").await;
|
||||
alice.allow_unencrypted().await?;
|
||||
|
||||
let raw = include_bytes!("../../test-data/message/thunderbird_with_autocrypt.eml");
|
||||
receive_imf(alice, raw, false).await?;
|
||||
@@ -3359,7 +3289,6 @@ async fn test_outgoing_undecryptable() -> Result<()> {
|
||||
#[tokio::test(flavor = "multi_thread", worker_threads = 2)]
|
||||
async fn test_thunderbird_autocrypt() -> Result<()> {
|
||||
let t = TestContext::new_bob().await;
|
||||
t.allow_unencrypted().await?;
|
||||
|
||||
let raw = include_bytes!("../../test-data/message/thunderbird_with_autocrypt.eml");
|
||||
let received_msg = receive_imf(&t, raw, false).await?.unwrap();
|
||||
@@ -3407,7 +3336,6 @@ async fn test_issuer_fingerprint() -> Result<()> {
|
||||
#[tokio::test(flavor = "multi_thread", worker_threads = 2)]
|
||||
async fn test_prefer_encrypt_mutual_if_encrypted() -> Result<()> {
|
||||
let t = TestContext::new_bob().await;
|
||||
t.allow_unencrypted().await?;
|
||||
|
||||
// The message has public key attached *and* Autocrypt header.
|
||||
//
|
||||
@@ -3458,9 +3386,7 @@ async fn test_prefer_encrypt_mutual_if_encrypted() -> Result<()> {
|
||||
|
||||
#[tokio::test(flavor = "multi_thread", worker_threads = 2)]
|
||||
async fn test_forged_from_and_no_valid_signatures() -> Result<()> {
|
||||
let mut tcm = TestContextManager::new();
|
||||
let t = &tcm.bob().await;
|
||||
t.allow_unencrypted().await?;
|
||||
let t = &TestContext::new_bob().await;
|
||||
let raw = include_bytes!("../../test-data/message/thunderbird_encrypted_signed.eml");
|
||||
let received_msg = receive_imf(t, raw, false).await?.unwrap();
|
||||
|
||||
@@ -3470,8 +3396,7 @@ async fn test_forged_from_and_no_valid_signatures() -> Result<()> {
|
||||
assert!(!msg.chat_id.is_trash());
|
||||
assert!(!msg.get_showpadlock());
|
||||
|
||||
let t = &tcm.bob().await;
|
||||
t.allow_unencrypted().await?;
|
||||
let t = &TestContext::new_bob().await;
|
||||
let raw = String::from_utf8(raw.to_vec())?.replace("alice@example.org", "clarice@example.org");
|
||||
let received_msg = receive_imf(t, raw.as_bytes(), false).await?.unwrap();
|
||||
assert!(received_msg.chat_id.is_trash());
|
||||
@@ -3482,8 +3407,6 @@ async fn test_forged_from_and_no_valid_signatures() -> Result<()> {
|
||||
#[tokio::test(flavor = "multi_thread", worker_threads = 2)]
|
||||
async fn test_wrong_from_name_and_no_valid_signatures() -> Result<()> {
|
||||
let t = &TestContext::new_bob().await;
|
||||
t.allow_unencrypted().await?;
|
||||
|
||||
let raw = include_bytes!("../../test-data/message/thunderbird_encrypted_signed.eml");
|
||||
let raw = String::from_utf8(raw.to_vec())?.replace("From: Alice", "From: A");
|
||||
receive_imf(t, raw.as_bytes(), false).await?.unwrap();
|
||||
@@ -3498,7 +3421,6 @@ async fn test_wrong_from_name_and_no_valid_signatures() -> Result<()> {
|
||||
#[tokio::test(flavor = "multi_thread", worker_threads = 2)]
|
||||
async fn test_thunderbird_autocrypt_unencrypted() -> Result<()> {
|
||||
let bob = &TestContext::new_bob().await;
|
||||
bob.allow_unencrypted().await?;
|
||||
|
||||
// Thunderbird message with Autocrypt header and a signature,
|
||||
// but not encrypted.
|
||||
@@ -3537,7 +3459,6 @@ async fn test_thunderbird_autocrypt_unencrypted() -> Result<()> {
|
||||
#[tokio::test(flavor = "multi_thread", worker_threads = 2)]
|
||||
async fn test_thunderbird_unsigned() -> Result<()> {
|
||||
let alice = TestContext::new_alice().await;
|
||||
alice.allow_unencrypted().await.unwrap();
|
||||
|
||||
// Alice receives an unsigned message from Bob.
|
||||
let raw = include_bytes!("../../test-data/message/thunderbird_encrypted_unsigned.eml");
|
||||
@@ -3557,7 +3478,6 @@ async fn test_thunderbird_unsigned() -> Result<()> {
|
||||
#[tokio::test(flavor = "multi_thread", worker_threads = 2)]
|
||||
async fn test_thunderbird_unsigned_with_unencrypted_subject() -> Result<()> {
|
||||
let bob = TestContext::new_bob().await;
|
||||
bob.allow_unencrypted().await.unwrap();
|
||||
|
||||
let raw = include_bytes!(
|
||||
"../../test-data/message/thunderbird_encrypted_unsigned_with_unencrypted_subject.eml"
|
||||
@@ -3590,20 +3510,18 @@ async fn test_messed_up_message_id() -> Result<()> {
|
||||
|
||||
#[tokio::test(flavor = "multi_thread", worker_threads = 2)]
|
||||
async fn test_big_forwarded_with_big_attachment() -> Result<()> {
|
||||
let mut tcm = TestContextManager::new();
|
||||
let alice = &tcm.alice().await;
|
||||
let bob = &tcm.bob().await;
|
||||
let t = &TestContext::new_bob().await;
|
||||
|
||||
let raw = include_bytes!("../../test-data/message/big_forwarded_with_big_attachment.eml");
|
||||
let rcvd = test_utils::receive_encrypted_imf(bob, alice, raw).await?;
|
||||
let rcvd = receive_imf(t, raw, false).await?.unwrap();
|
||||
assert_eq!(rcvd.msg_ids.len(), 3);
|
||||
|
||||
let msg = Message::load_from_db(bob, rcvd.msg_ids[0]).await?;
|
||||
let msg = Message::load_from_db(t, rcvd.msg_ids[0]).await?;
|
||||
assert_eq!(msg.get_viewtype(), Viewtype::Text);
|
||||
assert_eq!(msg.get_text(), "Hello!");
|
||||
assert!(!msg.has_html());
|
||||
|
||||
let msg = Message::load_from_db(bob, rcvd.msg_ids[1]).await?;
|
||||
let msg = Message::load_from_db(t, rcvd.msg_ids[1]).await?;
|
||||
assert_eq!(msg.get_viewtype(), Viewtype::Text);
|
||||
assert!(
|
||||
msg.get_text()
|
||||
@@ -3612,10 +3530,10 @@ async fn test_big_forwarded_with_big_attachment() -> Result<()> {
|
||||
assert!(msg.get_text().ends_with("[...]"));
|
||||
assert!(!msg.has_html());
|
||||
|
||||
let msg = Message::load_from_db(bob, rcvd.msg_ids[2]).await?;
|
||||
let msg = Message::load_from_db(t, rcvd.msg_ids[2]).await?;
|
||||
assert_eq!(msg.get_viewtype(), Viewtype::File);
|
||||
assert!(msg.has_html());
|
||||
let html = msg.id.get_html(bob).await?.unwrap();
|
||||
let html = msg.id.get_html(t).await?.unwrap();
|
||||
let tail = html
|
||||
.split_once("Hello!")
|
||||
.unwrap()
|
||||
@@ -3650,7 +3568,6 @@ async fn test_big_forwarded_with_big_attachment() -> Result<()> {
|
||||
#[tokio::test(flavor = "multi_thread", worker_threads = 2)]
|
||||
async fn test_mua_user_adds_member() -> Result<()> {
|
||||
let t = TestContext::new_alice().await;
|
||||
t.allow_unencrypted().await?;
|
||||
|
||||
receive_imf(
|
||||
&t,
|
||||
@@ -3702,7 +3619,6 @@ async fn test_mua_user_adds_member() -> Result<()> {
|
||||
#[tokio::test(flavor = "multi_thread", worker_threads = 2)]
|
||||
async fn test_mua_user_adds_recipient_to_single_chat() -> Result<()> {
|
||||
let alice = TestContext::new_alice().await;
|
||||
alice.allow_unencrypted().await?;
|
||||
|
||||
// Alice sends a 1:1 message to Bob, creating a 1:1 chat.
|
||||
let msg = receive_imf(
|
||||
@@ -4135,7 +4051,6 @@ async fn test_dont_readd_with_normal_msg() -> Result<()> {
|
||||
#[tokio::test(flavor = "multi_thread", worker_threads = 2)]
|
||||
async fn test_mua_cant_remove() -> Result<()> {
|
||||
let alice = TestContext::new_alice().await;
|
||||
alice.allow_unencrypted().await?;
|
||||
|
||||
let now = time();
|
||||
|
||||
@@ -4228,7 +4143,6 @@ async fn test_mua_cant_remove() -> Result<()> {
|
||||
#[tokio::test(flavor = "multi_thread", worker_threads = 2)]
|
||||
async fn test_mua_can_add() -> Result<()> {
|
||||
let alice = TestContext::new_alice().await;
|
||||
alice.allow_unencrypted().await?;
|
||||
|
||||
let now = time();
|
||||
|
||||
@@ -4288,7 +4202,6 @@ async fn test_mua_can_add() -> Result<()> {
|
||||
#[tokio::test(flavor = "multi_thread", worker_threads = 2)]
|
||||
async fn test_mua_can_readd() -> Result<()> {
|
||||
let alice = TestContext::new_alice().await;
|
||||
alice.allow_unencrypted().await?;
|
||||
|
||||
// Alice creates chat with 3 contacts.
|
||||
let msg = receive_imf(
|
||||
@@ -4452,8 +4365,6 @@ async fn test_keep_member_list_if_possibly_nomember() -> Result<()> {
|
||||
async fn test_adhoc_grp_name_no_prefix() -> Result<()> {
|
||||
let mut tcm = TestContextManager::new();
|
||||
let alice = &tcm.alice().await;
|
||||
alice.allow_unencrypted().await?;
|
||||
|
||||
let chat_id = receive_imf(
|
||||
alice,
|
||||
b"Subject: Re: Once upon a time this was with the only Re: here\n\
|
||||
@@ -4489,12 +4400,12 @@ async fn test_outgoing_msg_forgery() -> Result<()> {
|
||||
imex(alice, ImexMode::ExportSelfKeys, export_dir.path(), None).await?;
|
||||
// We need Bob only to encrypt the forged message to Alice's key, actually Bob doesn't
|
||||
// participate in the scenario.
|
||||
let bob = &tcm.unconfigured().await;
|
||||
let bob = &TestContext::new().await;
|
||||
assert_eq!(crate::key::load_self_secret_keyring(bob).await?.len(), 0);
|
||||
bob.configure_addr("bob@example.net").await;
|
||||
imex(bob, ImexMode::ImportSelfKeys, export_dir.path(), None).await?;
|
||||
assert_eq!(crate::key::load_self_secret_keyring(bob).await?.len(), 1);
|
||||
let malice = &tcm.unconfigured().await;
|
||||
let malice = &TestContext::new().await;
|
||||
malice.configure_addr(alice_addr).await;
|
||||
|
||||
let malice_chat_id = tcm
|
||||
@@ -4504,8 +4415,9 @@ async fn test_outgoing_msg_forgery() -> Result<()> {
|
||||
assert_eq!(crate::key::load_self_secret_keyring(bob).await?.len(), 1);
|
||||
|
||||
let sent_msg = malice.send_text(malice_chat_id, "hi from malice").await;
|
||||
let msg = alice.recv_msg_opt(&sent_msg).await;
|
||||
assert!(msg.is_none());
|
||||
let msg = alice.recv_msg(&sent_msg).await;
|
||||
assert_eq!(msg.state, MessageState::OutDelivered);
|
||||
assert!(!msg.get_showpadlock());
|
||||
|
||||
Ok(())
|
||||
}
|
||||
@@ -4600,7 +4512,6 @@ async fn test_protected_group_add_remove_member_missing_key() -> Result<()> {
|
||||
async fn test_older_message_from_2nd_device() -> Result<()> {
|
||||
let mut tcm = TestContextManager::new();
|
||||
let alice = &tcm.alice().await;
|
||||
alice.allow_unencrypted().await?;
|
||||
let chat_id = alice
|
||||
.create_chat_with_contact("", "bob@example.net")
|
||||
.await
|
||||
@@ -4736,7 +4647,6 @@ async fn test_forged_from() -> Result<()> {
|
||||
#[tokio::test(flavor = "multi_thread", worker_threads = 2)]
|
||||
async fn test_multiline_iso_8859_1_subject() -> Result<()> {
|
||||
let t = &TestContext::new_alice().await;
|
||||
t.allow_unencrypted().await?;
|
||||
let mail = b"Received: (Postfix, from userid 1000); Mon, 4 Dec 2006 14:51:39 +0100 (CET)\n\
|
||||
From: bob@example.com\n\
|
||||
To: alice@example.org, claire@example.com\n\
|
||||
@@ -4801,7 +4711,6 @@ async fn test_references() -> Result<()> {
|
||||
#[tokio::test(flavor = "multi_thread", worker_threads = 2)]
|
||||
async fn test_list_from() -> Result<()> {
|
||||
let t = &TestContext::new_alice().await;
|
||||
t.allow_unencrypted().await?;
|
||||
|
||||
let raw = include_bytes!("../../test-data/message/list-from.eml");
|
||||
let received = receive_imf(t, raw, false).await?.unwrap();
|
||||
@@ -4931,7 +4840,6 @@ async fn test_make_n_send_vcard() -> Result<()> {
|
||||
#[tokio::test(flavor = "multi_thread", worker_threads = 2)]
|
||||
async fn test_unencrypted_group_id_no_recipients() -> Result<()> {
|
||||
let t = &TestContext::new_alice().await;
|
||||
t.allow_unencrypted().await?;
|
||||
let raw = "From: alice@example.org
|
||||
Subject: Group
|
||||
Chat-Version: 1.0
|
||||
@@ -5223,12 +5131,10 @@ async fn test_recv_outgoing_msg_no_intended_recipient_fingerprint() -> Result<()
|
||||
|
||||
#[tokio::test(flavor = "multi_thread", worker_threads = 2)]
|
||||
async fn test_sanitize_filename_in_received() -> Result<()> {
|
||||
let mut tcm = TestContextManager::new();
|
||||
let alice = &tcm.alice().await;
|
||||
let bob = &tcm.bob().await;
|
||||
let alice = &TestContext::new_alice().await;
|
||||
let raw = b"Message-ID: Mr.XA6y3og8-az.WGbH9_dNcQx@testr
|
||||
To: <alice@example.org>
|
||||
From: \"=?utf-8?q??=\" <bob@example.net>
|
||||
To: <tmp_5890965001269692@testrun.org>
|
||||
From: \"=?utf-8?q??=\" <tmp_6272287793210918@testrun.org>
|
||||
Content-Type: multipart/mixed; boundary=\"mwkNRwaJw1M5n2xcr2ODfAqvTjcj9Z\"
|
||||
|
||||
|
||||
@@ -5247,7 +5153,7 @@ PGh0bWw+PGJvZHk+dGV4dDwvYm9keT5kYXRh
|
||||
|
||||
--mwkNRwaJw1M5n2xcr2ODfAqvTjcj9Z--";
|
||||
|
||||
let msg = test_utils::receive_encrypted_imf(alice, bob, raw).await?;
|
||||
let msg = receive_imf(alice, raw, false).await?.unwrap();
|
||||
let msg = Message::load_from_db(alice, msg.msg_ids[0]).await?;
|
||||
|
||||
assert_eq!(msg.get_filename().unwrap(), "test.HTML");
|
||||
@@ -5363,7 +5269,6 @@ async fn test_outgoing_unencrypted_chat_assignment() {
|
||||
let mut tcm = TestContextManager::new();
|
||||
let alice = &tcm.alice().await;
|
||||
let bob = &tcm.bob().await;
|
||||
alice.allow_unencrypted().await.unwrap();
|
||||
|
||||
tcm.section("Alice receives unencrypted message from Bob");
|
||||
receive_imf(
|
||||
@@ -5408,7 +5313,6 @@ async fn test_outgoing_unencrypted_chat_assignment() {
|
||||
async fn test_incoming_reply_with_date_in_past() -> Result<()> {
|
||||
let mut tcm = TestContextManager::new();
|
||||
let alice = &tcm.alice().await;
|
||||
alice.allow_unencrypted().await?;
|
||||
|
||||
let msg0 = receive_imf(
|
||||
alice,
|
||||
@@ -5550,8 +5454,6 @@ async fn test_small_unencrypted_group() -> Result<()> {
|
||||
let mut tcm = TestContextManager::new();
|
||||
let alice = &tcm.alice().await;
|
||||
let bob = &tcm.bob().await;
|
||||
alice.allow_unencrypted().await?;
|
||||
bob.allow_unencrypted().await?;
|
||||
|
||||
let alice_chat_id = chat::create_group_unencrypted(alice, "Unencrypted group").await?;
|
||||
let alice_bob_id = alice.add_or_lookup_address_contact_id(bob).await;
|
||||
@@ -5584,7 +5486,6 @@ async fn test_small_unencrypted_group() -> Result<()> {
|
||||
async fn test_bcc_not_a_group() -> Result<()> {
|
||||
let mut tcm = TestContextManager::new();
|
||||
let alice = &tcm.alice().await;
|
||||
alice.allow_unencrypted().await.unwrap();
|
||||
|
||||
let received = receive_imf(
|
||||
alice,
|
||||
@@ -5632,7 +5533,6 @@ async fn test_lookup_key_contact_by_address_self() -> Result<()> {
|
||||
async fn test_calendar_alternative() -> Result<()> {
|
||||
let mut tcm = TestContextManager::new();
|
||||
let t = &tcm.alice().await;
|
||||
t.allow_unencrypted().await?;
|
||||
let raw = include_bytes!("../../test-data/message/calendar-alternative.eml");
|
||||
let msg = receive_imf(t, raw, false).await?.unwrap();
|
||||
assert_eq!(msg.msg_ids.len(), 1);
|
||||
|
||||
+9
-1
@@ -15,7 +15,7 @@ use crate::config::Config;
|
||||
use crate::contact::{ContactId, RecentlySeenLoop};
|
||||
use crate::context::Context;
|
||||
use crate::download::{download_known_post_messages_without_pre_message, download_msgs};
|
||||
use crate::ephemeral;
|
||||
use crate::ephemeral::{self, delete_expired_imap_messages};
|
||||
use crate::events::EventType;
|
||||
use crate::imap::{Imap, session::Session};
|
||||
use crate::location;
|
||||
@@ -484,6 +484,14 @@ async fn fetch_idle(ctx: &Context, connection: &mut Imap, mut session: Session)
|
||||
.await
|
||||
.context("fetch_move_delete")?;
|
||||
|
||||
// Mark expired messages for deletion. Marked messages will be deleted from the server
|
||||
// on the next iteration of `fetch_move_delete`. `delete_expired_imap_messages` is not
|
||||
// called right before `fetch_move_delete` because it is not well optimized and would
|
||||
// otherwise slow down message fetching.
|
||||
delete_expired_imap_messages(ctx)
|
||||
.await
|
||||
.context("delete_expired_imap_messages")?;
|
||||
|
||||
download_known_post_messages_without_pre_message(ctx, &mut session).await?;
|
||||
download_msgs(ctx, &mut session)
|
||||
.await
|
||||
|
||||
@@ -218,7 +218,7 @@ pub(crate) fn maybe_network_lost(context: &Context, stores: Vec<ConnectivityStor
|
||||
impl fmt::Debug for ConnectivityStore {
|
||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||
if let Some(guard) = self.0.try_lock() {
|
||||
write!(f, "ConnectivityStore {:?}", *guard)
|
||||
write!(f, "ConnectivityStore {:?}", &*guard)
|
||||
} else {
|
||||
write!(f, "ConnectivityStore [LOCKED]")
|
||||
}
|
||||
@@ -418,11 +418,7 @@ impl Context {
|
||||
};
|
||||
match "a.recent {
|
||||
Err(e) => {
|
||||
// If not supported by the provider,
|
||||
// just skip the "quota" section.
|
||||
if !matches!(e, crate::quota::Error::NotSupportedByProvider) {
|
||||
ret += &escaper::encode_minimal(&e.to_string());
|
||||
}
|
||||
ret += &escaper::encode_minimal(&e.to_string());
|
||||
}
|
||||
Ok(quota) => {
|
||||
if quota.is_empty() {
|
||||
|
||||
+2
-1
@@ -73,7 +73,8 @@ fn shorten_name(name: &str, length: usize) -> String {
|
||||
// We use _ rather than ... to avoid dots at the end of the URL, which would confuse linkifiers
|
||||
format!(
|
||||
"{}_",
|
||||
name.chars()
|
||||
&name
|
||||
.chars()
|
||||
.take(length.saturating_sub(1))
|
||||
.collect::<String>()
|
||||
)
|
||||
|
||||
@@ -772,7 +772,6 @@ fn manipulate_qr(v3: bool, remove_invite: bool, qr: &mut String) {
|
||||
#[tokio::test(flavor = "multi_thread", worker_threads = 2)]
|
||||
async fn test_adhoc_group_no_qr() -> Result<()> {
|
||||
let alice = TestContext::new_alice().await;
|
||||
alice.allow_unencrypted().await?;
|
||||
|
||||
let mime = br#"Subject: First thread
|
||||
Message-ID: first@example.org
|
||||
@@ -993,7 +992,7 @@ async fn test_wrong_auth_token() -> Result<()> {
|
||||
tcm.send_recv(alice, bob, "hi").await;
|
||||
|
||||
let alice_qr = get_securejoin_qr(alice, None).await?;
|
||||
println!("{alice_qr}");
|
||||
println!("{}", &alice_qr);
|
||||
let invalid_alice_qr = alice_qr.replace("&s=", "&s=INVALIDAUTHTOKEN&someotherkey=");
|
||||
|
||||
join_securejoin(bob, &invalid_alice_qr).await?;
|
||||
@@ -1416,7 +1415,6 @@ async fn test_vc_request_encrypted_at_rest() -> Result<()> {
|
||||
let mut tcm = TestContextManager::new();
|
||||
let alice = &tcm.alice().await;
|
||||
let bob = &tcm.bob().await;
|
||||
alice.allow_unencrypted().await?;
|
||||
|
||||
let qr = get_securejoin_qr(alice, None).await?;
|
||||
|
||||
|
||||
+19
-15
@@ -699,22 +699,26 @@ pub(crate) async fn add_self_recipients(
|
||||
recipients: &mut Vec<String>,
|
||||
encrypted: bool,
|
||||
) -> Result<()> {
|
||||
// Avoid sending unencrypted messages to all transports, chatmail relays won't accept
|
||||
// them. Normally the user should have a non-chatmail primary transport to send unencrypted
|
||||
// messages.
|
||||
if encrypted {
|
||||
for addr in context.get_published_secondary_self_addrs().await? {
|
||||
recipients.push(addr);
|
||||
// Previous versions of Delta Chat did not send BCC self
|
||||
// if DeleteServerAfter was set to immediately delete messages
|
||||
// from the server. This is not the case anymore
|
||||
// because BCC-self messages are also used to detect
|
||||
// that message was sent if SMTP server is slow to respond
|
||||
// and connection is frequently lost
|
||||
// before receiving status line. NB: This is not a problem for chatmail servers, so `BccSelf`
|
||||
// disabled by default is fine.
|
||||
if context.get_config_delete_server_after().await? != Some(0) || !recipients.is_empty() {
|
||||
// Avoid sending unencrypted messages to all transports, chatmail relays won't accept
|
||||
// them. Normally the user should have a non-chatmail primary transport to send unencrypted
|
||||
// messages.
|
||||
if encrypted {
|
||||
for addr in context.get_published_secondary_self_addrs().await? {
|
||||
recipients.push(addr);
|
||||
}
|
||||
}
|
||||
// `from` must be the last addr, see `receive_imf_inner()` why.
|
||||
let from = context.get_primary_self_addr().await?;
|
||||
recipients.push(from);
|
||||
}
|
||||
// `from` must be the last addr
|
||||
// because `receive_imf_inner()` marks the message as 'delivered'
|
||||
// if it arrives to the self-server via `bcc_self`.
|
||||
// This helps with marking messages as delivered
|
||||
// if the server is slow and we never get an `OK` response
|
||||
// before the connection times out.
|
||||
let from = context.get_primary_self_addr().await?;
|
||||
recipients.push(from);
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
+2
-63
@@ -17,7 +17,7 @@ use crate::key::DcKey;
|
||||
use crate::log::warn;
|
||||
use crate::provider::get_provider_info;
|
||||
use crate::sql::Sql;
|
||||
use crate::tools::{self, Time, inc_and_check, time_elapsed};
|
||||
use crate::tools::{Time, inc_and_check, time_elapsed};
|
||||
use crate::transport::ConfiguredLoginParam;
|
||||
|
||||
const DBVERSION: i32 = 68;
|
||||
@@ -2346,7 +2346,7 @@ ALTER TABLE contacts ADD COLUMN name_normalized TEXT;
|
||||
transaction.execute(
|
||||
"UPDATE transports
|
||||
SET entered_param=json_set(entered_param, '$.imap.folder', ?1),
|
||||
configured_param=json_set(configured_param, '$.imap_folder', ?1)",
|
||||
configured_param=json_set(configured_param', '$.imap_folder', ?1)",
|
||||
(mvbox_folder,),
|
||||
)?;
|
||||
}
|
||||
@@ -2373,67 +2373,6 @@ ALTER TABLE contacts ADD COLUMN name_normalized TEXT;
|
||||
.await?;
|
||||
}
|
||||
|
||||
inc_and_check(&mut migration_version, 152)?;
|
||||
if dbversion < migration_version {
|
||||
sql.execute_migration(
|
||||
"
|
||||
UPDATE msgs SET state=26 WHERE state=28; -- Change OutMdnRcvd to OutDelivered.
|
||||
UPDATE msgs SET state=24 WHERE state=18; -- Change OutPreparing to OutFailed.
|
||||
",
|
||||
migration_version,
|
||||
)
|
||||
.await?;
|
||||
}
|
||||
|
||||
inc_and_check(&mut migration_version, 153)?;
|
||||
if dbversion < migration_version {
|
||||
sql.execute_migration_transaction(
|
||||
|transaction| {
|
||||
// Newest timestamp of message sent to unencrypted chat with contacts.
|
||||
// This is for 1:1 chats and ad hoc groups.
|
||||
//
|
||||
// Corner case of ad hoc groups with only self as a member is ignored.
|
||||
let max_unencrypted_timestamp: i64 = transaction.query_row(
|
||||
"SELECT IFNULL(MAX(msgs.timestamp), 0)
|
||||
FROM msgs
|
||||
INNER JOIN chats_contacts
|
||||
ON chats_contacts.chat_id = msgs.chat_id
|
||||
INNER JOIN contacts
|
||||
ON chats_contacts.contact_id = contacts.id
|
||||
WHERE contacts.id > 9
|
||||
AND contacts.fingerprint = ''
|
||||
", (),
|
||||
|row| row.get(0)
|
||||
).context("Failed to select largest unencrypted message timestamp")?;
|
||||
|
||||
// Find the newest unencrypted mailing list message.
|
||||
// Mailing lists have only self-contact as a member,
|
||||
// so we look for them separately.
|
||||
let max_mailing_list_timestamp: i64 =
|
||||
transaction.query_row(
|
||||
"SELECT IFNULL(MAX(msgs.timestamp), 0)
|
||||
FROM msgs
|
||||
INNER JOIN chats ON chats.id = msgs.chat_id
|
||||
WHERE chats.type = 140",
|
||||
(), |row| row.get(0)).context("Failed to select largest mailing list timestamp")?;
|
||||
|
||||
let now = tools::time();
|
||||
let max_unencrypted_timestamp = std::cmp::max(max_unencrypted_timestamp, max_mailing_list_timestamp);
|
||||
if max_unencrypted_timestamp.saturating_add(3600 * 24 * 90) > now {
|
||||
// There are recent active unencrypted chats, don't enforce encryption.
|
||||
// Otherwise `force_encryption` is enabled by default.
|
||||
transaction
|
||||
.execute(
|
||||
"INSERT OR REPLACE INTO config (keyname, value) VALUES ('force_encryption', '0')", ()
|
||||
)?;
|
||||
}
|
||||
Ok(())
|
||||
},
|
||||
migration_version,
|
||||
)
|
||||
.await?;
|
||||
}
|
||||
|
||||
let new_version = sql
|
||||
.get_raw_config_int(VERSION_CFG)
|
||||
.await?
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user