mirror of
https://github.com/chatmail/core.git
synced 2026-06-20 14:46:36 +03:00
Compare commits
35 Commits
v2.50.0
...
iequidoo/d
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
974e32dd76 | ||
|
|
c91608e9f1 | ||
|
|
207c2e6e4c | ||
|
|
c0705a8d92 | ||
|
|
18ce5a02cc | ||
|
|
e87e269f98 | ||
|
|
4bb557cf53 | ||
|
|
9b4503e3f5 | ||
|
|
4428382433 | ||
|
|
8c56b63f21 | ||
|
|
50e83f2072 | ||
|
|
4a94a34c6d | ||
|
|
bd6c9908e4 | ||
|
|
dd5ec9621b | ||
|
|
92d522473f | ||
|
|
049fd4f355 | ||
|
|
7ea637c930 | ||
|
|
3a1b0d4679 | ||
|
|
7a60c79301 | ||
|
|
0bc7849e8a | ||
|
|
dfea8b0134 | ||
|
|
376c819374 | ||
|
|
02827406f3 | ||
|
|
9d9f61d9eb | ||
|
|
a2816d7bd3 | ||
|
|
9719aa1415 | ||
|
|
5733a783fb | ||
|
|
c26b6a017c | ||
|
|
bb6a478430 | ||
|
|
480248a168 | ||
|
|
627f98915d | ||
|
|
24848c0265 | ||
|
|
98123afb62 | ||
|
|
16b65c59bf | ||
|
|
bafb5f71ad |
4
.github/workflows/ci.yml
vendored
4
.github/workflows/ci.yml
vendored
@@ -62,7 +62,7 @@ jobs:
|
|||||||
with:
|
with:
|
||||||
show-progress: false
|
show-progress: false
|
||||||
persist-credentials: false
|
persist-credentials: false
|
||||||
- uses: EmbarkStudios/cargo-deny-action@6c8f9facfa5047ec02d8485b6bf52b587b7777d1
|
- uses: EmbarkStudios/cargo-deny-action@a531616d8ce3b9177443e48a1159bc945a099823
|
||||||
with:
|
with:
|
||||||
arguments: --workspace --all-features --locked
|
arguments: --workspace --all-features --locked
|
||||||
command: check
|
command: check
|
||||||
@@ -146,7 +146,7 @@ jobs:
|
|||||||
cache-bin: false
|
cache-bin: false
|
||||||
|
|
||||||
- name: Install nextest
|
- name: Install nextest
|
||||||
uses: taiki-e/install-action@184183c2401be73c3bf42c2e61268aa5855379c1
|
uses: taiki-e/install-action@60ae4ce63c7aeb6e96d7f572c1ec7fafbb17ca80
|
||||||
with:
|
with:
|
||||||
tool: nextest
|
tool: nextest
|
||||||
|
|
||||||
|
|||||||
1
.github/workflows/nix.yml
vendored
1
.github/workflows/nix.yml
vendored
@@ -63,7 +63,6 @@ jobs:
|
|||||||
- deltachat-rpc-server-armv7l-linux-wheel
|
- deltachat-rpc-server-armv7l-linux-wheel
|
||||||
- deltachat-rpc-server-i686-linux
|
- deltachat-rpc-server-i686-linux
|
||||||
- deltachat-rpc-server-i686-linux-wheel
|
- deltachat-rpc-server-i686-linux-wheel
|
||||||
- deltachat-rpc-server-source
|
|
||||||
- deltachat-rpc-server-win32
|
- deltachat-rpc-server-win32
|
||||||
- deltachat-rpc-server-win32-wheel
|
- deltachat-rpc-server-win32-wheel
|
||||||
- deltachat-rpc-server-win64
|
- deltachat-rpc-server-win64
|
||||||
|
|||||||
2
.github/workflows/zizmor-scan.yml
vendored
2
.github/workflows/zizmor-scan.yml
vendored
@@ -23,4 +23,4 @@ jobs:
|
|||||||
persist-credentials: false
|
persist-credentials: false
|
||||||
|
|
||||||
- name: Run zizmor
|
- name: Run zizmor
|
||||||
uses: zizmorcore/zizmor-action@b1d7e1fb5de872772f31590499237e7cce841e8e # v0.5.3
|
uses: zizmorcore/zizmor-action@5f14fd08f7cf1cb1609c1e344975f152c7ee938d # v0.5.6
|
||||||
|
|||||||
40
CHANGELOG.md
40
CHANGELOG.md
@@ -1,5 +1,44 @@
|
|||||||
# Changelog
|
# 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
|
## [2.50.0] - 2026-05-22
|
||||||
|
|
||||||
### API-Changes
|
### API-Changes
|
||||||
@@ -8257,3 +8296,4 @@ https://github.com/chatmail/core/pulls?q=is%3Apr+is%3Aclosed
|
|||||||
[2.48.0]: https://github.com/chatmail/core/compare/v2.47.0..v2.48.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.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.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
|
||||||
|
|||||||
112
Cargo.lock
generated
112
Cargo.lock
generated
@@ -391,6 +391,28 @@ version = "1.4.0"
|
|||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "ace50bade8e6234aa140d9a2f552bbee1db4d353f69b8217bc503490fc1a9f26"
|
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]]
|
[[package]]
|
||||||
name = "backon"
|
name = "backon"
|
||||||
version = "1.5.0"
|
version = "1.5.0"
|
||||||
@@ -763,10 +785,13 @@ dependencies = [
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "cc"
|
name = "cc"
|
||||||
version = "1.2.14"
|
version = "1.2.63"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "0c3d1b2e905a3a7b00a6141adb0e4c0bb941d11caf55349d863942a1cc44e3c9"
|
checksum = "556e016178bb5662a08681bbe0f00f8e17631781a4dfc8c45e466e4b185ec27f"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
|
"find-msvc-tools",
|
||||||
|
"jobserver",
|
||||||
|
"libc",
|
||||||
"shlex",
|
"shlex",
|
||||||
]
|
]
|
||||||
|
|
||||||
@@ -920,6 +945,15 @@ dependencies = [
|
|||||||
"digest",
|
"digest",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "cmake"
|
||||||
|
version = "0.1.58"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "c0f78a02292a74a88ac736019ab962ece0bc380e3f977bf72e376c5d78ff0678"
|
||||||
|
dependencies = [
|
||||||
|
"cc",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "cobs"
|
name = "cobs"
|
||||||
version = "0.2.3"
|
version = "0.2.3"
|
||||||
@@ -1316,7 +1350,7 @@ dependencies = [
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "deltachat"
|
name = "deltachat"
|
||||||
version = "2.50.0"
|
version = "2.51.0-dev"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"anyhow",
|
"anyhow",
|
||||||
"astral-tokio-tar",
|
"astral-tokio-tar",
|
||||||
@@ -1425,7 +1459,7 @@ dependencies = [
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "deltachat-jsonrpc"
|
name = "deltachat-jsonrpc"
|
||||||
version = "2.50.0"
|
version = "2.51.0-dev"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"anyhow",
|
"anyhow",
|
||||||
"async-channel 2.5.0",
|
"async-channel 2.5.0",
|
||||||
@@ -1446,7 +1480,7 @@ dependencies = [
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "deltachat-repl"
|
name = "deltachat-repl"
|
||||||
version = "2.50.0"
|
version = "2.51.0-dev"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"anyhow",
|
"anyhow",
|
||||||
"deltachat",
|
"deltachat",
|
||||||
@@ -1462,7 +1496,7 @@ dependencies = [
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "deltachat-rpc-server"
|
name = "deltachat-rpc-server"
|
||||||
version = "2.50.0"
|
version = "2.51.0-dev"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"anyhow",
|
"anyhow",
|
||||||
"deltachat",
|
"deltachat",
|
||||||
@@ -1491,7 +1525,7 @@ dependencies = [
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "deltachat_ffi"
|
name = "deltachat_ffi"
|
||||||
version = "2.50.0"
|
version = "2.51.0-dev"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"anyhow",
|
"anyhow",
|
||||||
"deltachat",
|
"deltachat",
|
||||||
@@ -1676,7 +1710,7 @@ dependencies = [
|
|||||||
"libc",
|
"libc",
|
||||||
"option-ext",
|
"option-ext",
|
||||||
"redox_users",
|
"redox_users",
|
||||||
"windows-sys 0.61.1",
|
"windows-sys 0.59.0",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
@@ -1726,6 +1760,12 @@ dependencies = [
|
|||||||
"zeroize",
|
"zeroize",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "dunce"
|
||||||
|
version = "1.0.5"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "92773504d58c093f6de2459af4af33faa518c13451eb8f2b5698ed3d36e7c813"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "dyn-clone"
|
name = "dyn-clone"
|
||||||
version = "1.0.18"
|
version = "1.0.18"
|
||||||
@@ -2051,6 +2091,12 @@ dependencies = [
|
|||||||
"windows-sys 0.59.0",
|
"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]]
|
[[package]]
|
||||||
name = "fixedbitset"
|
name = "fixedbitset"
|
||||||
version = "0.5.7"
|
version = "0.5.7"
|
||||||
@@ -2108,6 +2154,12 @@ dependencies = [
|
|||||||
name = "format-flowed"
|
name = "format-flowed"
|
||||||
version = "1.0.0"
|
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]]
|
[[package]]
|
||||||
name = "funty"
|
name = "funty"
|
||||||
version = "2.0.0"
|
version = "2.0.0"
|
||||||
@@ -2681,7 +2733,7 @@ dependencies = [
|
|||||||
"hyper",
|
"hyper",
|
||||||
"libc",
|
"libc",
|
||||||
"pin-project-lite",
|
"pin-project-lite",
|
||||||
"socket2 0.6.3",
|
"socket2 0.5.9",
|
||||||
"tokio",
|
"tokio",
|
||||||
"tower-service",
|
"tower-service",
|
||||||
"tracing",
|
"tracing",
|
||||||
@@ -3234,6 +3286,16 @@ version = "1.0.10"
|
|||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "b1a46d1a171d865aa5f83f92695765caa047a9b4cbae2cbf37dbd613a793fd4c"
|
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]]
|
[[package]]
|
||||||
name = "js-sys"
|
name = "js-sys"
|
||||||
version = "0.3.77"
|
version = "0.3.77"
|
||||||
@@ -3374,9 +3436,9 @@ dependencies = [
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "log"
|
name = "log"
|
||||||
version = "0.4.29"
|
version = "0.4.31"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "5e5032e24019045c762d3c0f28f5b6b8bbf38563a65908389bf7978758920897"
|
checksum = "113b30b4cd05f7c06868fdb2854f66a7b9fece9a48425351cd532e810d74024f"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "loom"
|
name = "loom"
|
||||||
@@ -3818,7 +3880,7 @@ version = "0.50.3"
|
|||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "7957b9740744892f114936ab4a57b3f487491bbeafaf8083688b16841a4240e5"
|
checksum = "7957b9740744892f114936ab4a57b3f487491bbeafaf8083688b16841a4240e5"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"windows-sys 0.61.1",
|
"windows-sys 0.59.0",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
@@ -4303,18 +4365,18 @@ dependencies = [
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "pin-project"
|
name = "pin-project"
|
||||||
version = "1.1.11"
|
version = "1.1.13"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "f1749c7ed4bcaf4c3d0a3efc28538844fb29bcdd7d2b67b2be7e20ba861ff517"
|
checksum = "2466b2336ed02bcdca6b294417127b90ec92038d1d5c4fbeac971a922e0e0924"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"pin-project-internal",
|
"pin-project-internal",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "pin-project-internal"
|
name = "pin-project-internal"
|
||||||
version = "1.1.11"
|
version = "1.1.13"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "d9b20ed30f105399776b9c883e68e536ef602a16ae6f596d2c473591d6ad64c6"
|
checksum = "c96395f0a926bc13b1c17622aaddda1ecb55d49c8f1bf9777e4d877800a43f8b"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"proc-macro2",
|
"proc-macro2",
|
||||||
"quote",
|
"quote",
|
||||||
@@ -5219,7 +5281,7 @@ dependencies = [
|
|||||||
"errno",
|
"errno",
|
||||||
"libc",
|
"libc",
|
||||||
"linux-raw-sys 0.12.1",
|
"linux-raw-sys 0.12.1",
|
||||||
"windows-sys 0.61.1",
|
"windows-sys 0.52.0",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
@@ -5228,6 +5290,7 @@ version = "0.23.37"
|
|||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "758025cb5fccfd3bc2fd74708fd4682be41d99e5dff73c377c0646c6012c73a4"
|
checksum = "758025cb5fccfd3bc2fd74708fd4682be41d99e5dff73c377c0646c6012c73a4"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
|
"aws-lc-rs",
|
||||||
"log",
|
"log",
|
||||||
"once_cell",
|
"once_cell",
|
||||||
"ring",
|
"ring",
|
||||||
@@ -5273,6 +5336,7 @@ version = "0.103.13"
|
|||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "61c429a8649f110dddef65e2a5ad240f747e85f7758a6bccc7e5777bd33f756e"
|
checksum = "61c429a8649f110dddef65e2a5ad240f747e85f7758a6bccc7e5777bd33f756e"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
|
"aws-lc-rs",
|
||||||
"ring",
|
"ring",
|
||||||
"rustls-pki-types",
|
"rustls-pki-types",
|
||||||
"untrusted",
|
"untrusted",
|
||||||
@@ -5517,9 +5581,9 @@ dependencies = [
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "serde_json"
|
name = "serde_json"
|
||||||
version = "1.0.149"
|
version = "1.0.150"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "83fc039473c5595ace860d8c4fafa220ff474b3fc6bfdb4293327f1a37e94d86"
|
checksum = "e8014e44b4736ed0538adeecded0fce2a272f22dc9578a7eb6b2d9993c74cfb9"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"itoa",
|
"itoa",
|
||||||
"memchr",
|
"memchr",
|
||||||
@@ -5695,9 +5759,9 @@ dependencies = [
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "shlex"
|
name = "shlex"
|
||||||
version = "1.3.0"
|
version = "2.0.1"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "0fda2ff0d084019ba4d7c6f371c95d8fd75ce3524c3cb8fb653a3023f6323e64"
|
checksum = "f8fadd59c855ef2080decdef8ff161eb6661b86933c9d82e5ba29dc602a55aba"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "signal-hook-registry"
|
name = "signal-hook-registry"
|
||||||
@@ -6083,7 +6147,7 @@ dependencies = [
|
|||||||
"getrandom 0.3.3",
|
"getrandom 0.3.3",
|
||||||
"once_cell",
|
"once_cell",
|
||||||
"rustix 1.1.4",
|
"rustix 1.1.4",
|
||||||
"windows-sys 0.61.1",
|
"windows-sys 0.52.0",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
@@ -6231,9 +6295,9 @@ checksum = "1f3ccbac311fea05f86f61904b462b55fb3df8837a366dfc601a0161d0532f20"
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "tokio"
|
name = "tokio"
|
||||||
version = "1.52.1"
|
version = "1.52.3"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "b67dee974fe86fd92cc45b7a95fdd2f99a36a6d7b0d431a231178d3d670bbcc6"
|
checksum = "8fc7f01b389ac15039e4dc9531aa973a135d7a4135281b12d7c1bc79fd57fffe"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"bytes",
|
"bytes",
|
||||||
"libc",
|
"libc",
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
[package]
|
[package]
|
||||||
name = "deltachat"
|
name = "deltachat"
|
||||||
version = "2.50.0"
|
version = "2.51.0-dev"
|
||||||
edition = "2024"
|
edition = "2024"
|
||||||
license = "MPL-2.0"
|
license = "MPL-2.0"
|
||||||
rust-version = "1.89"
|
rust-version = "1.89"
|
||||||
@@ -101,7 +101,7 @@ tagger = "4.3.4"
|
|||||||
textwrap = "0.16.2"
|
textwrap = "0.16.2"
|
||||||
thiserror = { workspace = true }
|
thiserror = { workspace = true }
|
||||||
tokio-io-timeout = "1.2.1"
|
tokio-io-timeout = "1.2.1"
|
||||||
tokio-rustls = { version = "0.26.2", default-features = false }
|
tokio-rustls = { version = "0.26.2", default-features = false, features = ["aws-lc-rs", "tls12"] }
|
||||||
tokio-stream = { version = "0.1.17", features = ["fs"] }
|
tokio-stream = { version = "0.1.17", features = ["fs"] }
|
||||||
astral-tokio-tar = { version = "0.6.2", default-features = false }
|
astral-tokio-tar = { version = "0.6.2", default-features = false }
|
||||||
tokio-util = { workspace = true }
|
tokio-util = { workspace = true }
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
[package]
|
[package]
|
||||||
name = "deltachat_ffi"
|
name = "deltachat_ffi"
|
||||||
version = "2.50.0"
|
version = "2.51.0-dev"
|
||||||
description = "Deltachat FFI"
|
description = "Deltachat FFI"
|
||||||
edition = "2018"
|
edition = "2018"
|
||||||
readme = "README.md"
|
readme = "README.md"
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
[package]
|
[package]
|
||||||
name = "deltachat-jsonrpc"
|
name = "deltachat-jsonrpc"
|
||||||
version = "2.50.0"
|
version = "2.51.0-dev"
|
||||||
description = "DeltaChat JSON-RPC API"
|
description = "DeltaChat JSON-RPC API"
|
||||||
edition = "2021"
|
edition = "2021"
|
||||||
license = "MPL-2.0"
|
license = "MPL-2.0"
|
||||||
|
|||||||
@@ -1106,9 +1106,6 @@ impl CommandApi {
|
|||||||
/// because the word "channel" already appears a lot in the code,
|
/// because the word "channel" already appears a lot in the code,
|
||||||
/// which would make it hard to grep for it.
|
/// 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.
|
/// Returns the created chat's id.
|
||||||
async fn create_broadcast(&self, account_id: u32, chat_name: String) -> Result<u32> {
|
async fn create_broadcast(&self, account_id: u32, chat_name: String) -> Result<u32> {
|
||||||
let ctx = self.get_context(account_id).await?;
|
let ctx = self.get_context(account_id).await?;
|
||||||
|
|||||||
@@ -54,5 +54,5 @@
|
|||||||
},
|
},
|
||||||
"type": "module",
|
"type": "module",
|
||||||
"types": "dist/deltachat.d.ts",
|
"types": "dist/deltachat.d.ts",
|
||||||
"version": "2.50.0"
|
"version": "2.51.0-dev"
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
[package]
|
[package]
|
||||||
name = "deltachat-repl"
|
name = "deltachat-repl"
|
||||||
version = "2.50.0"
|
version = "2.51.0-dev"
|
||||||
license = "MPL-2.0"
|
license = "MPL-2.0"
|
||||||
edition = "2021"
|
edition = "2021"
|
||||||
repository = "https://github.com/chatmail/core"
|
repository = "https://github.com/chatmail/core"
|
||||||
|
|||||||
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
|
|||||||
|
|
||||||
[project]
|
[project]
|
||||||
name = "deltachat-rpc-client"
|
name = "deltachat-rpc-client"
|
||||||
version = "2.50.0"
|
version = "2.51.0-dev"
|
||||||
license = "MPL-2.0"
|
license = "MPL-2.0"
|
||||||
description = "Python client for Delta Chat core JSON-RPC interface"
|
description = "Python client for Delta Chat core JSON-RPC interface"
|
||||||
classifiers = [
|
classifiers = [
|
||||||
|
|||||||
@@ -340,9 +340,6 @@ class Account:
|
|||||||
because the word "channel" already appears a lot in the code,
|
because the word "channel" already appears a lot in the code,
|
||||||
which would make it hard to grep for it.
|
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.
|
Returns the created chat.
|
||||||
"""
|
"""
|
||||||
return Chat(self, self._rpc.create_broadcast(self.id, name))
|
return Chat(self, self._rpc.create_broadcast(self.id, name))
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
[package]
|
[package]
|
||||||
name = "deltachat-rpc-server"
|
name = "deltachat-rpc-server"
|
||||||
version = "2.50.0"
|
version = "2.51.0-dev"
|
||||||
description = "DeltaChat JSON-RPC server"
|
description = "DeltaChat JSON-RPC server"
|
||||||
edition = "2021"
|
edition = "2021"
|
||||||
readme = "README.md"
|
readme = "README.md"
|
||||||
|
|||||||
@@ -15,5 +15,5 @@
|
|||||||
},
|
},
|
||||||
"type": "module",
|
"type": "module",
|
||||||
"types": "index.d.ts",
|
"types": "index.d.ts",
|
||||||
"version": "2.50.0"
|
"version": "2.51.0-dev"
|
||||||
}
|
}
|
||||||
|
|||||||
151
flake.lock
generated
151
flake.lock
generated
@@ -3,15 +3,19 @@
|
|||||||
"android": {
|
"android": {
|
||||||
"inputs": {
|
"inputs": {
|
||||||
"devshell": "devshell",
|
"devshell": "devshell",
|
||||||
"flake-utils": "flake-utils",
|
"flake-utils": [
|
||||||
"nixpkgs": "nixpkgs"
|
"flake-utils"
|
||||||
|
],
|
||||||
|
"nixpkgs": [
|
||||||
|
"nixpkgs"
|
||||||
|
]
|
||||||
},
|
},
|
||||||
"locked": {
|
"locked": {
|
||||||
"lastModified": 1731356359,
|
"lastModified": 1779918845,
|
||||||
"narHash": "sha256-vYqJnu6jotmWpPT4DgzHVdvNIZcKZCIUqS8QaptsZA0=",
|
"narHash": "sha256-FbpOOBg15L7X6NWWmTKbSdccnH59Jq53wWmAO37d2Q8=",
|
||||||
"owner": "tadfisher",
|
"owner": "tadfisher",
|
||||||
"repo": "android-nixpkgs",
|
"repo": "android-nixpkgs",
|
||||||
"rev": "c028ead7e88edb2e94cd7c90ee37593f63ae494a",
|
"rev": "105c093afc8c8fbeea98f8e398403f93043eba17",
|
||||||
"type": "github"
|
"type": "github"
|
||||||
},
|
},
|
||||||
"original": {
|
"original": {
|
||||||
@@ -28,11 +32,11 @@
|
|||||||
]
|
]
|
||||||
},
|
},
|
||||||
"locked": {
|
"locked": {
|
||||||
"lastModified": 1728330715,
|
"lastModified": 1768818222,
|
||||||
"narHash": "sha256-xRJ2nPOXb//u1jaBnDP56M7v5ldavjbtR6lfGqSvcKg=",
|
"narHash": "sha256-460jc0+CZfyaO8+w8JNtlClB2n4ui1RbHfPTLkpwhU8=",
|
||||||
"owner": "numtide",
|
"owner": "numtide",
|
||||||
"repo": "devshell",
|
"repo": "devshell",
|
||||||
"rev": "dd6b80932022cea34a019e2bb32f6fa9e494dfef",
|
"rev": "255a2b1725a20d060f566e4755dbf571bbbb5f76",
|
||||||
"type": "github"
|
"type": "github"
|
||||||
},
|
},
|
||||||
"original": {
|
"original": {
|
||||||
@@ -43,15 +47,17 @@
|
|||||||
},
|
},
|
||||||
"fenix": {
|
"fenix": {
|
||||||
"inputs": {
|
"inputs": {
|
||||||
"nixpkgs": "nixpkgs_2",
|
"nixpkgs": [
|
||||||
|
"nixpkgs"
|
||||||
|
],
|
||||||
"rust-analyzer-src": "rust-analyzer-src"
|
"rust-analyzer-src": "rust-analyzer-src"
|
||||||
},
|
},
|
||||||
"locked": {
|
"locked": {
|
||||||
"lastModified": 1763361733,
|
"lastModified": 1779876442,
|
||||||
"narHash": "sha256-ka7dpwH3HIXCyD2wl5F7cPLeRbqZoY2ullALsvxdPt8=",
|
"narHash": "sha256-O25HomVNmdROO13PEQ3Ran8Hq5EsyLmVn8Gb8JvJtJE=",
|
||||||
"owner": "nix-community",
|
"owner": "nix-community",
|
||||||
"repo": "fenix",
|
"repo": "fenix",
|
||||||
"rev": "6c8d48e3b0ae371b19ac1485744687b788e80193",
|
"rev": "2eff81fc84390a35e1565395ae945d9394856824",
|
||||||
"type": "github"
|
"type": "github"
|
||||||
},
|
},
|
||||||
"original": {
|
"original": {
|
||||||
@@ -65,29 +71,11 @@
|
|||||||
"systems": "systems"
|
"systems": "systems"
|
||||||
},
|
},
|
||||||
"locked": {
|
"locked": {
|
||||||
"lastModified": 1726560853,
|
"lastModified": 1731533236,
|
||||||
"narHash": "sha256-X6rJYSESBVr3hBoH0WbKE5KvhPU5bloyZ2L4K60/fPQ=",
|
"narHash": "sha256-l0KFg5HjrsfsO/JpG+r7fRrqm12kzFHyUHqHCVpMMbI=",
|
||||||
"owner": "numtide",
|
"owner": "numtide",
|
||||||
"repo": "flake-utils",
|
"repo": "flake-utils",
|
||||||
"rev": "c1dfcf08411b08f6b8615f7d8971a2bfa81d5e8a",
|
"rev": "11707dc2f618dd54ca8739b309ec4fc024de578b",
|
||||||
"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"
|
"type": "github"
|
||||||
},
|
},
|
||||||
"original": {
|
"original": {
|
||||||
@@ -98,29 +86,35 @@
|
|||||||
},
|
},
|
||||||
"naersk": {
|
"naersk": {
|
||||||
"inputs": {
|
"inputs": {
|
||||||
"nixpkgs": "nixpkgs_3"
|
"fenix": [
|
||||||
|
"fenix"
|
||||||
|
],
|
||||||
|
"nixpkgs": [
|
||||||
|
"nixpkgs"
|
||||||
|
]
|
||||||
},
|
},
|
||||||
"locked": {
|
"locked": {
|
||||||
"lastModified": 1721727458,
|
"lastModified": 1779912356,
|
||||||
"narHash": "sha256-r/xppY958gmZ4oTfLiHN0ZGuQ+RSTijDblVgVLFi1mw=",
|
"narHash": "sha256-yj5O6vmAj+OfhTQMiUwhmQRP0HAII3BxEI6zuY6h/5k=",
|
||||||
"owner": "nix-community",
|
"owner": "nix-community",
|
||||||
"repo": "naersk",
|
"repo": "naersk",
|
||||||
"rev": "3fb418eaf352498f6b6c30592e3beb63df42ef11",
|
"rev": "33eaf5c72a67db15073322d26cd342c443556214",
|
||||||
"type": "github"
|
"type": "github"
|
||||||
},
|
},
|
||||||
"original": {
|
"original": {
|
||||||
"owner": "nix-community",
|
"owner": "nix-community",
|
||||||
|
"ref": "pull/391/head",
|
||||||
"repo": "naersk",
|
"repo": "naersk",
|
||||||
"type": "github"
|
"type": "github"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"nix-filter": {
|
"nix-filter": {
|
||||||
"locked": {
|
"locked": {
|
||||||
"lastModified": 1730207686,
|
"lastModified": 1757882181,
|
||||||
"narHash": "sha256-SCHiL+1f7q9TAnxpasriP6fMarWE5H43t25F5/9e28I=",
|
"narHash": "sha256-+cCxYIh2UNalTz364p+QYmWHs0P+6wDhiWR4jDIKQIU=",
|
||||||
"owner": "numtide",
|
"owner": "numtide",
|
||||||
"repo": "nix-filter",
|
"repo": "nix-filter",
|
||||||
"rev": "776e68c1d014c3adde193a18db9d738458cd2ba4",
|
"rev": "59c44d1909c72441144b93cf0f054be7fe764de5",
|
||||||
"type": "github"
|
"type": "github"
|
||||||
},
|
},
|
||||||
"original": {
|
"original": {
|
||||||
@@ -131,60 +125,16 @@
|
|||||||
},
|
},
|
||||||
"nixpkgs": {
|
"nixpkgs": {
|
||||||
"locked": {
|
"locked": {
|
||||||
"lastModified": 1731139594,
|
"lastModified": 1779931091,
|
||||||
"narHash": "sha256-IigrKK3vYRpUu+HEjPL/phrfh7Ox881er1UEsZvw9Q4=",
|
"narHash": "sha256-gc8NEz7a++7OQPGvMv+zIjXCec1PO38XRXZRa3m97ew=",
|
||||||
"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",
|
"owner": "nixos",
|
||||||
"repo": "nixpkgs",
|
"repo": "nixpkgs",
|
||||||
"rev": "c5ae371f1a6a7fd27823bc500d9390b38c05fa55",
|
"rev": "3052ddf0614791c1869384a868248be5607a309f",
|
||||||
"type": "github"
|
"type": "github"
|
||||||
},
|
},
|
||||||
"original": {
|
"original": {
|
||||||
"owner": "nixos",
|
"owner": "nixos",
|
||||||
"ref": "nixos-unstable",
|
"ref": "master",
|
||||||
"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",
|
"repo": "nixpkgs",
|
||||||
"type": "github"
|
"type": "github"
|
||||||
}
|
}
|
||||||
@@ -193,20 +143,20 @@
|
|||||||
"inputs": {
|
"inputs": {
|
||||||
"android": "android",
|
"android": "android",
|
||||||
"fenix": "fenix",
|
"fenix": "fenix",
|
||||||
"flake-utils": "flake-utils_2",
|
"flake-utils": "flake-utils",
|
||||||
"naersk": "naersk",
|
"naersk": "naersk",
|
||||||
"nix-filter": "nix-filter",
|
"nix-filter": "nix-filter",
|
||||||
"nixpkgs": "nixpkgs_4"
|
"nixpkgs": "nixpkgs"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"rust-analyzer-src": {
|
"rust-analyzer-src": {
|
||||||
"flake": false,
|
"flake": false,
|
||||||
"locked": {
|
"locked": {
|
||||||
"lastModified": 1762860488,
|
"lastModified": 1779827300,
|
||||||
"narHash": "sha256-rMfWMCOo/pPefM2We0iMBLi2kLBAnYoB9thi4qS7uk4=",
|
"narHash": "sha256-J6pHxKoZzWCrAvOVInwBcYYWix/NWwM10Ad+i29Qc5s=",
|
||||||
"owner": "rust-lang",
|
"owner": "rust-lang",
|
||||||
"repo": "rust-analyzer",
|
"repo": "rust-analyzer",
|
||||||
"rev": "2efc80078029894eec0699f62ec8d5c1a56af763",
|
"rev": "c3af07ad84d68adc5e652e86f0c20009caa29014",
|
||||||
"type": "github"
|
"type": "github"
|
||||||
},
|
},
|
||||||
"original": {
|
"original": {
|
||||||
@@ -230,21 +180,6 @@
|
|||||||
"repo": "default",
|
"repo": "default",
|
||||||
"type": "github"
|
"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",
|
"root": "root",
|
||||||
|
|||||||
38
flake.nix
38
flake.nix
@@ -2,11 +2,16 @@
|
|||||||
description = "Chatmail core";
|
description = "Chatmail core";
|
||||||
inputs = {
|
inputs = {
|
||||||
fenix.url = "github:nix-community/fenix";
|
fenix.url = "github:nix-community/fenix";
|
||||||
|
fenix.inputs.nixpkgs.follows = "nixpkgs";
|
||||||
flake-utils.url = "github:numtide/flake-utils";
|
flake-utils.url = "github:numtide/flake-utils";
|
||||||
naersk.url = "github:nix-community/naersk";
|
naersk.url = "github:nix-community/naersk/pull/391/head";
|
||||||
|
naersk.inputs.nixpkgs.follows = "nixpkgs";
|
||||||
|
naersk.inputs.fenix.follows = "fenix";
|
||||||
nix-filter.url = "github:numtide/nix-filter";
|
nix-filter.url = "github:numtide/nix-filter";
|
||||||
nixpkgs.url = "github:nixos/nixpkgs/nixos-unstable";
|
nixpkgs.url = "github:nixos/nixpkgs/master";
|
||||||
android.url = "github:tadfisher/android-nixpkgs";
|
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 }:
|
outputs = { self, nixpkgs, flake-utils, nix-filter, naersk, fenix, android }:
|
||||||
flake-utils.lib.eachDefaultSystem (system:
|
flake-utils.lib.eachDefaultSystem (system:
|
||||||
@@ -133,6 +138,8 @@
|
|||||||
];
|
];
|
||||||
depsBuildBuild = [
|
depsBuildBuild = [
|
||||||
pkgsWin64.stdenv.cc
|
pkgsWin64.stdenv.cc
|
||||||
|
];
|
||||||
|
buildInputs = [
|
||||||
pkgsWin64.windows.pthreads
|
pkgsWin64.windows.pthreads
|
||||||
];
|
];
|
||||||
auditable = false; # Avoid cargo-auditable failures.
|
auditable = false; # Avoid cargo-auditable failures.
|
||||||
@@ -143,6 +150,8 @@
|
|||||||
CARGO_BUILD_RUSTFLAGS = [
|
CARGO_BUILD_RUSTFLAGS = [
|
||||||
"-C"
|
"-C"
|
||||||
"linker=${TARGET_CC}"
|
"linker=${TARGET_CC}"
|
||||||
|
"-L"
|
||||||
|
"native=${pkgsWin64.windows.pthreads}/lib"
|
||||||
];
|
];
|
||||||
|
|
||||||
CC = "${pkgsWin64.stdenv.cc}/bin/${pkgsWin64.stdenv.cc.targetPrefix}cc";
|
CC = "${pkgsWin64.stdenv.cc}/bin/${pkgsWin64.stdenv.cc.targetPrefix}cc";
|
||||||
@@ -180,7 +189,8 @@
|
|||||||
};
|
};
|
||||||
})).overrideAttrs (oldAttr: {
|
})).overrideAttrs (oldAttr: {
|
||||||
configureFlags = oldAttr.configureFlags ++ [
|
configureFlags = oldAttr.configureFlags ++ [
|
||||||
"--disable-sjlj-exceptions --with-dwarf2"
|
"--disable-sjlj-exceptions"
|
||||||
|
"--with-dwarf2"
|
||||||
];
|
];
|
||||||
})
|
})
|
||||||
);
|
);
|
||||||
@@ -196,6 +206,8 @@
|
|||||||
];
|
];
|
||||||
depsBuildBuild = [
|
depsBuildBuild = [
|
||||||
winCC
|
winCC
|
||||||
|
];
|
||||||
|
buildInputs = [
|
||||||
pkgsWin32.windows.pthreads
|
pkgsWin32.windows.pthreads
|
||||||
];
|
];
|
||||||
auditable = false; # Avoid cargo-auditable failures.
|
auditable = false; # Avoid cargo-auditable failures.
|
||||||
@@ -206,6 +218,8 @@
|
|||||||
CARGO_BUILD_RUSTFLAGS = [
|
CARGO_BUILD_RUSTFLAGS = [
|
||||||
"-C"
|
"-C"
|
||||||
"linker=${TARGET_CC}"
|
"linker=${TARGET_CC}"
|
||||||
|
"-L"
|
||||||
|
"native=${pkgsWin32.windows.pthreads}/lib"
|
||||||
];
|
];
|
||||||
|
|
||||||
CC = "${winCC}/bin/${winCC.targetPrefix}cc";
|
CC = "${winCC}/bin/${winCC.targetPrefix}cc";
|
||||||
@@ -504,22 +518,6 @@
|
|||||||
'';
|
'';
|
||||||
};
|
};
|
||||||
|
|
||||||
# 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 =
|
deltachat-rpc-client =
|
||||||
pkgs.python3Packages.buildPythonPackage {
|
pkgs.python3Packages.buildPythonPackage {
|
||||||
pname = "deltachat-rpc-client";
|
pname = "deltachat-rpc-client";
|
||||||
@@ -562,7 +560,7 @@
|
|||||||
deltachat-python
|
deltachat-python
|
||||||
deltachat-rpc-client
|
deltachat-rpc-client
|
||||||
pkgs.python3Packages.breathe
|
pkgs.python3Packages.breathe
|
||||||
pkgs.python3Packages.sphinx_rtd_theme
|
pkgs.python3Packages.sphinx-rtd-theme
|
||||||
];
|
];
|
||||||
nativeBuildInputs = [ pkgs.sphinx ];
|
nativeBuildInputs = [ pkgs.sphinx ];
|
||||||
buildPhase = ''sphinx-build -b html -a python/doc/ dist/html'';
|
buildPhase = ''sphinx-build -b html -a python/doc/ dist/html'';
|
||||||
|
|||||||
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
|
|||||||
|
|
||||||
[project]
|
[project]
|
||||||
name = "deltachat"
|
name = "deltachat"
|
||||||
version = "2.50.0"
|
version = "2.51.0-dev"
|
||||||
license = "MPL-2.0"
|
license = "MPL-2.0"
|
||||||
description = "Python bindings for the Delta Chat Core library using CFFI against the Rust-implemented libdeltachat"
|
description = "Python bindings for the Delta Chat Core library using CFFI against the Rust-implemented libdeltachat"
|
||||||
readme = "README.rst"
|
readme = "README.rst"
|
||||||
|
|||||||
@@ -1 +1 @@
|
|||||||
2026-05-22
|
2026-05-29
|
||||||
@@ -20,86 +20,6 @@ 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):
|
def build_wheel(version, binary, tag, windows=False):
|
||||||
filename = f"deltachat_rpc_server-{version}-{tag}.whl"
|
filename = f"deltachat_rpc_server-{version}-{tag}.whl"
|
||||||
|
|
||||||
@@ -168,23 +88,19 @@ def main():
|
|||||||
with Path("Cargo.toml").open("rb") as fp:
|
with Path("Cargo.toml").open("rb") as fp:
|
||||||
cargo_manifest = tomllib.load(fp)
|
cargo_manifest = tomllib.load(fp)
|
||||||
version = cargo_manifest["package"]["version"]
|
version = cargo_manifest["package"]["version"]
|
||||||
if sys.argv[1] == "source":
|
arch = sys.argv[1]
|
||||||
filename = f"deltachat_rpc_server-{version}.tar.gz"
|
executable = sys.argv[2]
|
||||||
build_source_package(version, filename)
|
tags = arch2tags[arch]
|
||||||
else:
|
|
||||||
arch = sys.argv[1]
|
|
||||||
executable = sys.argv[2]
|
|
||||||
tags = arch2tags[arch]
|
|
||||||
|
|
||||||
if arch in ["win32", "win64"]:
|
if arch in ["win32", "win64"]:
|
||||||
build_wheel(
|
build_wheel(
|
||||||
version,
|
version,
|
||||||
executable,
|
executable,
|
||||||
f"py3-none-{tags}",
|
f"py3-none-{tags}",
|
||||||
windows=True,
|
windows=True,
|
||||||
)
|
)
|
||||||
else:
|
else:
|
||||||
build_wheel(version, executable, f"py3-none-{tags}")
|
build_wheel(version, executable, f"py3-none-{tags}")
|
||||||
|
|
||||||
|
|
||||||
main()
|
main()
|
||||||
|
|||||||
87
src/chat.rs
87
src/chat.rs
@@ -32,6 +32,7 @@ use crate::debug_logging::maybe_set_logging_xdc;
|
|||||||
use crate::download::{
|
use crate::download::{
|
||||||
DownloadState, PRE_MSG_ATTACHMENT_SIZE_THRESHOLD, PRE_MSG_SIZE_WARNING_THRESHOLD,
|
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::ephemeral::{Timer as EphemeralTimer, start_chat_ephemeral_timers};
|
||||||
use crate::events::EventType;
|
use crate::events::EventType;
|
||||||
use crate::key::{Fingerprint, self_fingerprint};
|
use crate::key::{Fingerprint, self_fingerprint};
|
||||||
@@ -1782,9 +1783,8 @@ impl Chat {
|
|||||||
);
|
);
|
||||||
bail!("Cannot set message, contact for {} not found.", self.id);
|
bail!("Cannot set message, contact for {} not found.", self.id);
|
||||||
}
|
}
|
||||||
} else if matches!(self.typ, Chattype::Group | Chattype::OutBroadcast)
|
} else if self.param.get_int(Param::Unpromoted).unwrap_or_default() == 1 {
|
||||||
&& self.param.get_int(Param::Unpromoted).unwrap_or_default() == 1
|
ensure_and_debug_assert_eq!(self.typ, Chattype::Group,);
|
||||||
{
|
|
||||||
msg.param.set_int(Param::AttachChatAvatarAndDescription, 1);
|
msg.param.set_int(Param::AttachChatAvatarAndDescription, 1);
|
||||||
self.param
|
self.param
|
||||||
.remove(Param::Unpromoted)
|
.remove(Param::Unpromoted)
|
||||||
@@ -3626,9 +3626,6 @@ pub(crate) async fn create_group_ex(
|
|||||||
/// because the word "channel" already appears a lot in the code,
|
/// because the word "channel" already appears a lot in the code,
|
||||||
/// which would make it hard to grep for it.
|
/// 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.
|
/// Returns the created chat's id.
|
||||||
pub async fn create_broadcast(context: &Context, chat_name: String) -> Result<ChatId> {
|
pub async fn create_broadcast(context: &Context, chat_name: String) -> Result<ChatId> {
|
||||||
let grpid = create_id();
|
let grpid = create_id();
|
||||||
@@ -3660,17 +3657,20 @@ pub(crate) async fn create_out_broadcast_ex(
|
|||||||
|row| row.get(0),
|
|row| row.get(0),
|
||||||
)?;
|
)?;
|
||||||
ensure!(cnt == 0, "{cnt} chats exist with grpid {grpid}");
|
ensure!(cnt == 0, "{cnt} chats exist with grpid {grpid}");
|
||||||
|
let mut params: Params = Params::new();
|
||||||
|
params.update_timestamp(Param::GroupNameTimestamp, time())?;
|
||||||
|
|
||||||
t.execute(
|
t.execute(
|
||||||
"INSERT INTO chats
|
"INSERT INTO chats
|
||||||
(type, name, name_normalized, grpid, created_timestamp)
|
(type, name, name_normalized, grpid, created_timestamp, param)
|
||||||
VALUES(?, ?, ?, ?, ?)",
|
VALUES(?, ?, ?, ?, ?, ?)",
|
||||||
(
|
(
|
||||||
Chattype::OutBroadcast,
|
Chattype::OutBroadcast,
|
||||||
&chat_name,
|
&chat_name,
|
||||||
normalize_text(&chat_name),
|
normalize_text(&chat_name),
|
||||||
&grpid,
|
&grpid,
|
||||||
timestamp,
|
timestamp,
|
||||||
|
params.to_string(),
|
||||||
),
|
),
|
||||||
)?;
|
)?;
|
||||||
let chat_id = ChatId::new(t.last_insert_rowid().try_into()?);
|
let chat_id = ChatId::new(t.last_insert_rowid().try_into()?);
|
||||||
@@ -3738,17 +3738,19 @@ pub(crate) async fn update_chat_contacts_table(
|
|||||||
id: ChatId,
|
id: ChatId,
|
||||||
contacts: &BTreeSet<ContactId>,
|
contacts: &BTreeSet<ContactId>,
|
||||||
) -> Result<()> {
|
) -> Result<()> {
|
||||||
|
// See add_to_chat_contacts_table() for reasoning.
|
||||||
|
let limit = cmp::max(time().saturating_add(TIMESTAMP_SENT_TOLERANCE), timestamp);
|
||||||
context
|
context
|
||||||
.sql
|
.sql
|
||||||
.transaction(move |transaction| {
|
.transaction(move |transaction| {
|
||||||
// Bump `remove_timestamp` to at least `now`
|
// Bump `remove_timestamp` even for members from `contacts`.
|
||||||
// even for members from `contacts`.
|
|
||||||
// We add members from `contacts` back below.
|
// We add members from `contacts` back below.
|
||||||
transaction.execute(
|
transaction.execute(
|
||||||
"UPDATE chats_contacts
|
"UPDATE chats_contacts SET
|
||||||
SET remove_timestamp=MAX(add_timestamp+1, ?)
|
add_timestamp=MIN(add_timestamp, ?1),
|
||||||
|
remove_timestamp=MAX(MIN(remove_timestamp,?1), MIN(add_timestamp,?1)+1, ?)
|
||||||
WHERE chat_id=?",
|
WHERE chat_id=?",
|
||||||
(timestamp, id),
|
(limit, timestamp, id),
|
||||||
)?;
|
)?;
|
||||||
|
|
||||||
if !contacts.is_empty() {
|
if !contacts.is_empty() {
|
||||||
@@ -3760,9 +3762,8 @@ pub(crate) async fn update_chat_contacts_table(
|
|||||||
)?;
|
)?;
|
||||||
|
|
||||||
for contact_id in contacts {
|
for contact_id in contacts {
|
||||||
// We bumped `add_timestamp` for existing rows above,
|
// We bumped `remove_timestamp` for existing rows above,
|
||||||
// so on conflict it is enough to set `add_timestamp = remove_timestamp`
|
// 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))?;
|
statement.execute((id, contact_id, timestamp))?;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -3779,17 +3780,24 @@ pub(crate) async fn add_to_chat_contacts_table(
|
|||||||
chat_id: ChatId,
|
chat_id: ChatId,
|
||||||
contact_ids: &[ContactId],
|
contact_ids: &[ContactId],
|
||||||
) -> Result<()> {
|
) -> 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
|
context
|
||||||
.sql
|
.sql
|
||||||
.transaction(move |transaction| {
|
.transaction(move |transaction| {
|
||||||
let mut add_statement = transaction.prepare(
|
let mut add_statement = transaction.prepare(
|
||||||
"INSERT INTO chats_contacts (chat_id, contact_id, add_timestamp) VALUES(?1, ?2, ?3)
|
"INSERT INTO chats_contacts (chat_id, contact_id, add_timestamp) VALUES(?1, ?2, ?3)
|
||||||
ON CONFLICT (chat_id, contact_id)
|
ON CONFLICT (chat_id, contact_id)
|
||||||
DO UPDATE SET add_timestamp=MAX(remove_timestamp, ?3)",
|
DO UPDATE SET
|
||||||
|
remove_timestamp=MIN(remove_timestamp, ?4),
|
||||||
|
add_timestamp=MIN(MAX(add_timestamp,remove_timestamp,?3), ?4)",
|
||||||
)?;
|
)?;
|
||||||
|
|
||||||
for contact_id in contact_ids {
|
for contact_id in contact_ids {
|
||||||
add_statement.execute((chat_id, contact_id, timestamp))?;
|
add_statement.execute((chat_id, contact_id, timestamp, limit))?;
|
||||||
}
|
}
|
||||||
Ok(())
|
Ok(())
|
||||||
})
|
})
|
||||||
@@ -3800,26 +3808,34 @@ pub(crate) async fn add_to_chat_contacts_table(
|
|||||||
|
|
||||||
/// Removes a contact from the chat
|
/// Removes a contact from the chat
|
||||||
/// by updating the `remove_timestamp`.
|
/// 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(
|
pub(crate) async fn remove_from_chat_contacts_table(
|
||||||
context: &Context,
|
context: &Context,
|
||||||
chat_id: ChatId,
|
chat_id: ChatId,
|
||||||
contact_id: ContactId,
|
contact_id: ContactId,
|
||||||
) -> Result<()> {
|
) -> Result<bool> {
|
||||||
let now = time();
|
let now = time();
|
||||||
context
|
// See add_to_chat_contacts_table() for reasoning.
|
||||||
|
let limit = now.saturating_add(TIMESTAMP_SENT_TOLERANCE);
|
||||||
|
let is_past_member = context
|
||||||
.sql
|
.sql
|
||||||
.execute(
|
.execute(
|
||||||
"UPDATE chats_contacts
|
"UPDATE chats_contacts SET
|
||||||
SET remove_timestamp=MAX(add_timestamp+1, ?)
|
add_timestamp=MIN(add_timestamp, ?1),
|
||||||
|
remove_timestamp=MAX(MIN(remove_timestamp,?1), MIN(add_timestamp,?1)+1, ?)
|
||||||
WHERE chat_id=? AND contact_id=?",
|
WHERE chat_id=? AND contact_id=?",
|
||||||
(now, chat_id, contact_id),
|
(limit, now, chat_id, contact_id),
|
||||||
)
|
)
|
||||||
.await?;
|
.await?
|
||||||
Ok(())
|
> 0;
|
||||||
|
Ok(is_past_member)
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Removes a contact from the chat
|
/// Removes a contact from the chat
|
||||||
/// without leaving a trace.
|
/// 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.
|
||||||
///
|
///
|
||||||
/// Note that if we call this function,
|
/// Note that if we call this function,
|
||||||
/// and then receive a message from another device
|
/// and then receive a message from another device
|
||||||
@@ -3829,17 +3845,17 @@ pub(crate) async fn remove_from_chat_contacts_table_without_trace(
|
|||||||
context: &Context,
|
context: &Context,
|
||||||
chat_id: ChatId,
|
chat_id: ChatId,
|
||||||
contact_id: ContactId,
|
contact_id: ContactId,
|
||||||
) -> Result<()> {
|
) -> Result<bool> {
|
||||||
context
|
let removed = context
|
||||||
.sql
|
.sql
|
||||||
.execute(
|
.execute(
|
||||||
"DELETE FROM chats_contacts
|
"DELETE FROM chats_contacts
|
||||||
WHERE chat_id=? AND contact_id=?",
|
WHERE chat_id=? AND contact_id=?",
|
||||||
(chat_id, contact_id),
|
(chat_id, contact_id),
|
||||||
)
|
)
|
||||||
.await?;
|
.await?
|
||||||
|
> 0;
|
||||||
Ok(())
|
Ok(removed)
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Adds a contact to the chat.
|
/// Adds a contact to the chat.
|
||||||
@@ -4159,10 +4175,13 @@ pub async fn remove_contact_from_chat(
|
|||||||
|
|
||||||
let mut sync = Nosync;
|
let mut sync = Nosync;
|
||||||
|
|
||||||
if chat.is_promoted() && chat.typ != Chattype::OutBroadcast {
|
let removed = if chat.is_promoted() && chat.typ != Chattype::OutBroadcast {
|
||||||
remove_from_chat_contacts_table(context, chat_id, contact_id).await?;
|
remove_from_chat_contacts_table(context, chat_id, contact_id).await?
|
||||||
} else {
|
} else {
|
||||||
remove_from_chat_contacts_table_without_trace(context, chat_id, contact_id).await?;
|
remove_from_chat_contacts_table_without_trace(context, chat_id, contact_id).await?
|
||||||
|
};
|
||||||
|
if !removed {
|
||||||
|
return Ok(());
|
||||||
}
|
}
|
||||||
|
|
||||||
// We do not return an error if the contact does not exist in the database.
|
// We do not return an error if the contact does not exist in the database.
|
||||||
|
|||||||
@@ -9,6 +9,7 @@ use crate::headerdef::HeaderDef;
|
|||||||
use crate::imex::{ImexMode, has_backup, imex};
|
use crate::imex::{ImexMode, has_backup, imex};
|
||||||
use crate::message::{Message, MessengerMessage, delete_msgs};
|
use crate::message::{Message, MessengerMessage, delete_msgs};
|
||||||
use crate::mimeparser::{self, MimeMessage};
|
use crate::mimeparser::{self, MimeMessage};
|
||||||
|
use crate::qr::{Qr, check_qr};
|
||||||
use crate::receive_imf::receive_imf;
|
use crate::receive_imf::receive_imf;
|
||||||
use crate::securejoin::{get_securejoin_qr, join_securejoin};
|
use crate::securejoin::{get_securejoin_qr, join_securejoin};
|
||||||
use crate::test_utils;
|
use crate::test_utils;
|
||||||
@@ -2799,6 +2800,30 @@ async fn test_can_send_group() -> Result<()> {
|
|||||||
Ok(())
|
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,
|
/// Tests that in a broadcast channel,
|
||||||
/// the recipients can't see the identity of their fellow recipients.
|
/// the recipients can't see the identity of their fellow recipients.
|
||||||
#[tokio::test(flavor = "multi_thread", worker_threads = 2)]
|
#[tokio::test(flavor = "multi_thread", worker_threads = 2)]
|
||||||
@@ -2922,10 +2947,24 @@ async fn test_broadcast_change_name() -> Result<()> {
|
|||||||
let fiona = &tcm.fiona().await;
|
let fiona = &tcm.fiona().await;
|
||||||
|
|
||||||
let broadcast_id = create_broadcast(alice, "Channel".to_string()).await?;
|
let broadcast_id = create_broadcast(alice, "Channel".to_string()).await?;
|
||||||
let qr = get_securejoin_qr(alice, Some(broadcast_id)).await.unwrap();
|
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");
|
||||||
|
}
|
||||||
|
|
||||||
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.section("Alice invites Fiona to her channel");
|
||||||
tcm.exec_securejoin_qr(fiona, alice, &qr).await;
|
tcm.exec_securejoin_qr(fiona, alice, &qr).await;
|
||||||
|
|
||||||
@@ -3049,6 +3088,31 @@ async fn test_broadcast_resend_to_new_member() -> Result<()> {
|
|||||||
Ok(())
|
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 has multiple devices
|
||||||
/// - Alice creates a broadcast and sends a message into it
|
/// - Alice creates a broadcast and sends a message into it
|
||||||
/// - Alice's second device sees the broadcast
|
/// - Alice's second device sees the broadcast
|
||||||
|
|||||||
@@ -452,11 +452,6 @@ pub enum Config {
|
|||||||
/// storing the same token multiple times on the server.
|
/// storing the same token multiple times on the server.
|
||||||
EncryptedDeviceToken,
|
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.
|
/// Return an error from `receive_imf_inner()`. For tests.
|
||||||
SimulateReceiveImfError,
|
SimulateReceiveImfError,
|
||||||
|
|
||||||
|
|||||||
@@ -680,6 +680,8 @@ async fn get_autoconfig(
|
|||||||
param: &EnteredLoginParam,
|
param: &EnteredLoginParam,
|
||||||
param_domain: &str,
|
param_domain: &str,
|
||||||
) -> Option<Vec<ServerParams>> {
|
) -> Option<Vec<ServerParams>> {
|
||||||
|
let accept_invalid_certificates = param.certificate_checks.accept_invalid_certificates();
|
||||||
|
|
||||||
// Make sure to not encode `.` as `%2E` here.
|
// Make sure to not encode `.` as `%2E` here.
|
||||||
// Some servers like murena.io on 2024-11-01 produce incorrect autoconfig XML
|
// Some servers like murena.io on 2024-11-01 produce incorrect autoconfig XML
|
||||||
// when address is encoded.
|
// when address is encoded.
|
||||||
@@ -696,6 +698,7 @@ async fn get_autoconfig(
|
|||||||
"https://autoconfig.{param_domain}/mail/config-v1.1.xml?emailaddress={param_addr_urlencoded}"
|
"https://autoconfig.{param_domain}/mail/config-v1.1.xml?emailaddress={param_addr_urlencoded}"
|
||||||
),
|
),
|
||||||
¶m.addr,
|
¶m.addr,
|
||||||
|
accept_invalid_certificates,
|
||||||
)
|
)
|
||||||
.await
|
.await
|
||||||
{
|
{
|
||||||
@@ -710,6 +713,7 @@ async fn get_autoconfig(
|
|||||||
"https://{param_domain}/.well-known/autoconfig/mail/config-v1.1.xml?emailaddress={param_addr_urlencoded}"
|
"https://{param_domain}/.well-known/autoconfig/mail/config-v1.1.xml?emailaddress={param_addr_urlencoded}"
|
||||||
),
|
),
|
||||||
¶m.addr,
|
¶m.addr,
|
||||||
|
accept_invalid_certificates,
|
||||||
)
|
)
|
||||||
.await
|
.await
|
||||||
{
|
{
|
||||||
@@ -721,6 +725,7 @@ async fn get_autoconfig(
|
|||||||
if let Ok(res) = outlk_autodiscover(
|
if let Ok(res) = outlk_autodiscover(
|
||||||
ctx,
|
ctx,
|
||||||
format!("https://{param_domain}/autodiscover/autodiscover.xml"),
|
format!("https://{param_domain}/autodiscover/autodiscover.xml"),
|
||||||
|
accept_invalid_certificates,
|
||||||
)
|
)
|
||||||
.await
|
.await
|
||||||
{
|
{
|
||||||
@@ -731,6 +736,7 @@ async fn get_autoconfig(
|
|||||||
if let Ok(res) = outlk_autodiscover(
|
if let Ok(res) = outlk_autodiscover(
|
||||||
ctx,
|
ctx,
|
||||||
format!("https://autodiscover.{param_domain}/autodiscover/autodiscover.xml",),
|
format!("https://autodiscover.{param_domain}/autodiscover/autodiscover.xml",),
|
||||||
|
accept_invalid_certificates,
|
||||||
)
|
)
|
||||||
.await
|
.await
|
||||||
{
|
{
|
||||||
@@ -743,6 +749,7 @@ async fn get_autoconfig(
|
|||||||
ctx,
|
ctx,
|
||||||
&format!("https://autoconfig.thunderbird.net/v1.1/{param_domain}"),
|
&format!("https://autoconfig.thunderbird.net/v1.1/{param_domain}"),
|
||||||
¶m.addr,
|
¶m.addr,
|
||||||
|
accept_invalid_certificates,
|
||||||
)
|
)
|
||||||
.await
|
.await
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -10,7 +10,7 @@ use quick_xml::events::{BytesStart, Event};
|
|||||||
use super::{Error, ServerParams};
|
use super::{Error, ServerParams};
|
||||||
use crate::context::Context;
|
use crate::context::Context;
|
||||||
use crate::log::warn;
|
use crate::log::warn;
|
||||||
use crate::net::read_url;
|
use crate::net::read_url_with_tls;
|
||||||
use crate::provider::{Protocol, Socket};
|
use crate::provider::{Protocol, Socket};
|
||||||
|
|
||||||
#[derive(Debug)]
|
#[derive(Debug)]
|
||||||
@@ -249,8 +249,9 @@ pub(crate) async fn moz_autoconfigure(
|
|||||||
context: &Context,
|
context: &Context,
|
||||||
url: &str,
|
url: &str,
|
||||||
addr: &str,
|
addr: &str,
|
||||||
|
accept_invalid_certificates: bool,
|
||||||
) -> Result<Vec<ServerParams>, Error> {
|
) -> Result<Vec<ServerParams>, Error> {
|
||||||
let xml_raw = read_url(context, url).await?;
|
let xml_raw = read_url_with_tls(context, url, !accept_invalid_certificates).await?;
|
||||||
|
|
||||||
let res = parse_serverparams(addr, &xml_raw);
|
let res = parse_serverparams(addr, &xml_raw);
|
||||||
if let Err(err) = &res {
|
if let Err(err) = &res {
|
||||||
|
|||||||
@@ -10,7 +10,7 @@ use quick_xml::events::Event;
|
|||||||
use super::{Error, ServerParams};
|
use super::{Error, ServerParams};
|
||||||
use crate::context::Context;
|
use crate::context::Context;
|
||||||
use crate::log::warn;
|
use crate::log::warn;
|
||||||
use crate::net::read_url;
|
use crate::net::read_url_with_tls;
|
||||||
use crate::provider::{Protocol, Socket};
|
use crate::provider::{Protocol, Socket};
|
||||||
|
|
||||||
/// Result of parsing a single `Protocol` tag.
|
/// Result of parsing a single `Protocol` tag.
|
||||||
@@ -196,10 +196,11 @@ fn protocols_to_serverparams(protocols: Vec<ProtocolTag>) -> Vec<ServerParams> {
|
|||||||
pub(crate) async fn outlk_autodiscover(
|
pub(crate) async fn outlk_autodiscover(
|
||||||
context: &Context,
|
context: &Context,
|
||||||
mut url: String,
|
mut url: String,
|
||||||
|
accept_invalid_certificates: bool,
|
||||||
) -> Result<Vec<ServerParams>, Error> {
|
) -> Result<Vec<ServerParams>, Error> {
|
||||||
/* Follow up to 10 xml-redirects (http-redirects are followed in read_url() */
|
/* Follow up to 10 xml-redirects (http-redirects are followed in read_url() */
|
||||||
for _i in 0..10 {
|
for _i in 0..10 {
|
||||||
let xml_raw = read_url(context, &url).await?;
|
let xml_raw = read_url_with_tls(context, &url, !accept_invalid_certificates).await?;
|
||||||
let res = parse_xml(&xml_raw);
|
let res = parse_xml(&xml_raw);
|
||||||
if let Err(err) = &res {
|
if let Err(err) = &res {
|
||||||
warn!(context, "{}", err);
|
warn!(context, "{}", err);
|
||||||
|
|||||||
@@ -332,17 +332,6 @@ pub struct InnerContext {
|
|||||||
/// `Connectivity` values for mailboxes, unordered. Used to compute the aggregate connectivity,
|
/// `Connectivity` values for mailboxes, unordered. Used to compute the aggregate connectivity,
|
||||||
/// see [`Context::get_connectivity()`].
|
/// see [`Context::get_connectivity()`].
|
||||||
pub(crate) connectivities: parking_lot::Mutex<Vec<ConnectivityStore>>,
|
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.
|
/// The state of ongoing process.
|
||||||
@@ -522,7 +511,6 @@ impl Context {
|
|||||||
self_fingerprint: OnceLock::new(),
|
self_fingerprint: OnceLock::new(),
|
||||||
self_public_key: Mutex::new(None),
|
self_public_key: Mutex::new(None),
|
||||||
connectivities: parking_lot::Mutex::new(Vec::new()),
|
connectivities: parking_lot::Mutex::new(Vec::new()),
|
||||||
pre_encrypt_mime_hook: None.into(),
|
|
||||||
};
|
};
|
||||||
|
|
||||||
let ctx = Context {
|
let ctx = Context {
|
||||||
|
|||||||
@@ -6,6 +6,7 @@ use anyhow::{Result, anyhow, bail, ensure};
|
|||||||
use deltachat_derive::{FromSql, ToSql};
|
use deltachat_derive::{FromSql, ToSql};
|
||||||
use serde::{Deserialize, Serialize};
|
use serde::{Deserialize, Serialize};
|
||||||
|
|
||||||
|
use crate::config::Config;
|
||||||
use crate::context::Context;
|
use crate::context::Context;
|
||||||
use crate::imap::session::Session;
|
use crate::imap::session::Session;
|
||||||
use crate::log::warn;
|
use crate::log::warn;
|
||||||
@@ -169,7 +170,8 @@ pub(crate) async fn download_msg(
|
|||||||
}
|
}
|
||||||
Box::pin(session.fetch_single_msg(context, &server_folder, server_uid, rfc724_mid)).await?;
|
Box::pin(session.fetch_single_msg(context, &server_folder, server_uid, rfc724_mid)).await?;
|
||||||
|
|
||||||
if ephemeral::should_delete_all_downloaded_messages(context, session.is_chatmail()).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;
|
// Now that the message was downloaded, it likely needs to be deleted;
|
||||||
// trigger a re-check by interrupting the inbox folder.
|
// trigger a re-check by interrupting the inbox folder.
|
||||||
// This is mainly needed to make the tests pass;
|
// This is mainly needed to make the tests pass;
|
||||||
|
|||||||
@@ -654,7 +654,7 @@ pub(crate) async fn ephemeral_loop(context: &Context, interrupt_receiver: Receiv
|
|||||||
|
|
||||||
/// Schedules expired IMAP messages for deletion on the server.
|
/// Schedules expired IMAP messages for deletion on the server.
|
||||||
///
|
///
|
||||||
/// Also see [`delete_expired_imap_messages`],
|
/// Also see [`delete_expired_messages`],
|
||||||
/// which locally deletes expired messages.
|
/// which locally deletes expired messages.
|
||||||
pub(crate) async fn delete_expired_imap_messages(
|
pub(crate) async fn delete_expired_imap_messages(
|
||||||
context: &Context,
|
context: &Context,
|
||||||
@@ -663,7 +663,8 @@ pub(crate) async fn delete_expired_imap_messages(
|
|||||||
) -> Result<()> {
|
) -> Result<()> {
|
||||||
let now = time();
|
let now = time();
|
||||||
|
|
||||||
if should_delete_all_downloaded_messages(context, is_chatmail).await? {
|
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.
|
// This is the only device using this relay.
|
||||||
// Mark all downloaded messages for deletion, because they are not needed anymore.
|
// Mark all downloaded messages for deletion, because they are not needed anymore.
|
||||||
//
|
//
|
||||||
@@ -690,7 +691,7 @@ pub(crate) async fn delete_expired_imap_messages(
|
|||||||
(transport_id, now, DownloadState::Done),
|
(transport_id, now, DownloadState::Done),
|
||||||
)
|
)
|
||||||
.await?;
|
.await?;
|
||||||
} else {
|
} else if bcc_self {
|
||||||
// There may be other devices using this relay,
|
// There may be other devices using this relay,
|
||||||
// either because there is multi-device or because this is a classical email server.
|
// either because there is multi-device or because this is a classical email server.
|
||||||
// Only delete expired ephemeral messages.
|
// Only delete expired ephemeral messages.
|
||||||
@@ -711,16 +712,37 @@ pub(crate) async fn delete_expired_imap_messages(
|
|||||||
(transport_id, now),
|
(transport_id, now),
|
||||||
)
|
)
|
||||||
.await?;
|
.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?;
|
||||||
}
|
}
|
||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
pub(crate) async fn should_delete_all_downloaded_messages(
|
pub(crate) fn should_delete_all_downloaded_messages(bcc_self: bool, is_chatmail: bool) -> bool {
|
||||||
context: &Context,
|
!bcc_self && is_chatmail
|
||||||
is_chatmail: bool,
|
|
||||||
) -> Result<bool> {
|
|
||||||
Ok(!context.get_config_bool(Config::BccSelf).await? && is_chatmail)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Start ephemeral timers for seen messages if they are not started
|
/// Start ephemeral timers for seen messages if they are not started
|
||||||
|
|||||||
@@ -478,9 +478,10 @@ async fn test_delete_expired_imap_messages() -> Result<()> {
|
|||||||
|
|
||||||
// Test messages:
|
// Test messages:
|
||||||
//
|
//
|
||||||
// Three messages that were not split into pre- and post- message:
|
// Four messages that were not split into pre- and post- message:
|
||||||
// "expired@localhost" - expired ephemeral message
|
// "expired@localhost" - expired ephemeral message
|
||||||
// "no_expire@localhost" - non-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
|
// "future@localhost" - will expire in the future, but not yet
|
||||||
//
|
//
|
||||||
// And four messages that were split into pre- and post-message.
|
// And four messages that were split into pre- and post-message.
|
||||||
@@ -489,57 +490,81 @@ async fn test_delete_expired_imap_messages() -> Result<()> {
|
|||||||
// "future_*@localhost" - has pre-msg, not expired yet, not downloaded yet
|
// "future_*@localhost" - has pre-msg, not expired yet, not downloaded yet
|
||||||
// "done_*@localhost" - Fully downloaded -> post- message can be deleted
|
// "done_*@localhost" - Fully downloaded -> post- message can be deleted
|
||||||
//
|
//
|
||||||
// The tuple is (rfc724_mid, ephemeral_timestamp, download_state, pre_rfc724_mid)
|
// The tuple is (rfc724_mid, ephemeral_timestamp, download_state, pre_rfc724_mid, is_encrypted)
|
||||||
let msgs: [(&str, i64, DownloadState, &str); 7] = [
|
let msgs: [(&str, i64, DownloadState, &str, bool); 8] = [
|
||||||
("expired@localhost", now - 1, DownloadState::Done, ""),
|
("expired@localhost", now - 1, DownloadState::Done, "", true),
|
||||||
("no_expire@localhost", 0, DownloadState::Done, ""),
|
("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
|
// Use "now + 3600" rather than "now + 1", otherwise the test may be flaky
|
||||||
// if it is slow and the message expires in a second
|
// if it is slow and the message expires in a second
|
||||||
("future@localhost", now + 3600, DownloadState::Done, ""),
|
(
|
||||||
|
"future@localhost",
|
||||||
|
now + 3600,
|
||||||
|
DownloadState::Done,
|
||||||
|
"",
|
||||||
|
true,
|
||||||
|
),
|
||||||
(
|
(
|
||||||
"expired_post@localhost",
|
"expired_post@localhost",
|
||||||
now - 1,
|
now - 1,
|
||||||
DownloadState::Available,
|
DownloadState::Available,
|
||||||
"expired_pre@localhost",
|
"expired_pre@localhost",
|
||||||
|
true,
|
||||||
),
|
),
|
||||||
(
|
(
|
||||||
"no_expire_post@localhost",
|
"no_expire_post@localhost",
|
||||||
0,
|
0,
|
||||||
DownloadState::Available,
|
DownloadState::Available,
|
||||||
"no_expire_pre@localhost",
|
"no_expire_pre@localhost",
|
||||||
|
true,
|
||||||
),
|
),
|
||||||
(
|
(
|
||||||
"future_post@localhost",
|
"future_post@localhost",
|
||||||
now + 3600,
|
now + 3600,
|
||||||
DownloadState::Available,
|
DownloadState::Available,
|
||||||
"future_pre@localhost",
|
"future_pre@localhost",
|
||||||
|
true,
|
||||||
),
|
),
|
||||||
(
|
(
|
||||||
"done_post@localhost",
|
"done_post@localhost",
|
||||||
0,
|
0,
|
||||||
DownloadState::Done,
|
DownloadState::Done,
|
||||||
"done_pre@localhost",
|
"done_pre@localhost",
|
||||||
|
true,
|
||||||
),
|
),
|
||||||
];
|
];
|
||||||
for (rfc724_mid, ephemeral_timestamp, download_state, pre_rfc724_mid) in msgs {
|
for (rfc724_mid, ephemeral_timestamp, download_state, pre_rfc724_mid, is_encrypted) in msgs {
|
||||||
t.sql
|
t.sql
|
||||||
.execute(
|
.execute(
|
||||||
"INSERT INTO msgs \
|
"INSERT INTO msgs \
|
||||||
(rfc724_mid, timestamp, ephemeral_timestamp, download_state, pre_rfc724_mid) \
|
(rfc724_mid, timestamp, ephemeral_timestamp, download_state, pre_rfc724_mid, param) \
|
||||||
VALUES (?,?,?,?,?)",
|
VALUES (?,?,?,?,?,?)",
|
||||||
(
|
(
|
||||||
rfc724_mid,
|
rfc724_mid,
|
||||||
now,
|
now,
|
||||||
ephemeral_timestamp,
|
ephemeral_timestamp,
|
||||||
download_state,
|
download_state,
|
||||||
pre_rfc724_mid,
|
pre_rfc724_mid,
|
||||||
|
if is_encrypted {
|
||||||
|
"c=1"
|
||||||
|
} else {
|
||||||
|
""
|
||||||
|
}
|
||||||
),
|
),
|
||||||
)
|
)
|
||||||
.await?;
|
.await?;
|
||||||
}
|
}
|
||||||
let rfc724_mids: Vec<&str> = msgs
|
let rfc724_mids: Vec<&str> = msgs
|
||||||
.iter()
|
.iter()
|
||||||
.flat_map(|(rfc724_mid, _, _, pre_rfc724_mid)| [*rfc724_mid, *pre_rfc724_mid])
|
.flat_map(|(rfc724_mid, _, _, pre_rfc724_mid, _is_encrypted)| {
|
||||||
|
[*rfc724_mid, *pre_rfc724_mid]
|
||||||
|
})
|
||||||
.filter(|s| !s.is_empty())
|
.filter(|s| !s.is_empty())
|
||||||
.collect();
|
.collect();
|
||||||
|
|
||||||
@@ -554,13 +579,22 @@ async fn test_delete_expired_imap_messages() -> Result<()> {
|
|||||||
.await?;
|
.await?;
|
||||||
}
|
}
|
||||||
|
|
||||||
for (is_chatmail, other_transport) in
|
for (is_chatmail, other_transport, bcc_self) in [
|
||||||
[(false, false), (false, true), (true, false), (true, true)]
|
(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!(
|
println!(
|
||||||
"Testing combination is_chatmail={is_chatmail}, other_transport={other_transport}"
|
"Testing combination is_chatmail={is_chatmail}, other_transport={other_transport}, bcc_self={bcc_self}"
|
||||||
);
|
);
|
||||||
|
|
||||||
|
t.set_config_bool(Config::BccSelf, bcc_self).await?;
|
||||||
|
|
||||||
delete_expired_imap_messages(
|
delete_expired_imap_messages(
|
||||||
&t,
|
&t,
|
||||||
if other_transport {
|
if other_transport {
|
||||||
@@ -581,19 +615,20 @@ async fn test_delete_expired_imap_messages() -> Result<()> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
assert_eq!(is_deleted(&t, "expired@localhost").await?, true);
|
assert_eq!(is_deleted(&t, "expired@localhost").await?, true);
|
||||||
assert_eq!(is_deleted(&t, "no_expire@localhost").await?, is_chatmail);
|
assert_eq!(is_deleted(&t, "no_expire@localhost").await?, !bcc_self);
|
||||||
assert_eq!(is_deleted(&t, "future@localhost").await?, is_chatmail);
|
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_post@localhost").await?, true);
|
||||||
assert_eq!(is_deleted(&t, "expired_pre@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_post@localhost").await?, false);
|
||||||
assert_eq!(
|
assert_eq!(is_deleted(&t, "no_expire_pre@localhost").await?, !bcc_self);
|
||||||
is_deleted(&t, "no_expire_pre@localhost").await?,
|
|
||||||
is_chatmail
|
|
||||||
);
|
|
||||||
assert_eq!(is_deleted(&t, "future_post@localhost").await?, false);
|
assert_eq!(is_deleted(&t, "future_post@localhost").await?, false);
|
||||||
assert_eq!(is_deleted(&t, "future_pre@localhost").await?, is_chatmail);
|
assert_eq!(is_deleted(&t, "future_pre@localhost").await?, !bcc_self);
|
||||||
assert_eq!(is_deleted(&t, "done_pre@localhost").await?, is_chatmail);
|
assert_eq!(is_deleted(&t, "done_pre@localhost").await?, !bcc_self);
|
||||||
assert_eq!(is_deleted(&t, "done_post@localhost").await?, is_chatmail);
|
assert_eq!(is_deleted(&t, "done_post@localhost").await?, !bcc_self);
|
||||||
|
|
||||||
reset_targets(&t).await;
|
reset_targets(&t).await;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -56,6 +56,15 @@ pub enum EnteredCertificateChecks {
|
|||||||
AcceptInvalidCertificates2 = 3,
|
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.
|
/// Login parameters for a single IMAP server.
|
||||||
#[derive(Debug, Default, Clone, PartialEq, Eq, Serialize, Deserialize)]
|
#[derive(Debug, Default, Clone, PartialEq, Eq, Serialize, Deserialize)]
|
||||||
pub struct EnteredImapLoginParam {
|
pub struct EnteredImapLoginParam {
|
||||||
|
|||||||
@@ -140,8 +140,20 @@ SELECT ?1, rfc724_mid, pre_rfc724_mid, timestamp, ?, ? FROM msgs WHERE id=?1
|
|||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
pub(crate) async fn set_delivered(self, context: &Context) -> Result<()> {
|
/// Returns whether the message state is updated to `OutDelivered`.
|
||||||
update_msg_state(context, self, MessageState::OutDelivered).await?;
|
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);
|
||||||
|
}
|
||||||
let chat_id: Option<ChatId> = context
|
let chat_id: Option<ChatId> = context
|
||||||
.sql
|
.sql
|
||||||
.query_get_value("SELECT chat_id FROM msgs WHERE id=?", (self,))
|
.query_get_value("SELECT chat_id FROM msgs WHERE id=?", (self,))
|
||||||
@@ -153,7 +165,7 @@ SELECT ?1, rfc724_mid, pre_rfc724_mid, timestamp, ?, ? FROM msgs WHERE id=?1
|
|||||||
if let Some(chat_id) = chat_id {
|
if let Some(chat_id) = chat_id {
|
||||||
chatlist_events::emit_chatlist_item_changed(context, chat_id);
|
chatlist_events::emit_chatlist_item_changed(context, chat_id);
|
||||||
}
|
}
|
||||||
Ok(())
|
Ok(true)
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Bad evil escape hatch.
|
/// Bad evil escape hatch.
|
||||||
@@ -312,6 +324,7 @@ SELECT ?1, rfc724_mid, pre_rfc724_mid, timestamp, ?, ? FROM msgs WHERE id=?1
|
|||||||
if duration != 0 {
|
if duration != 0 {
|
||||||
ret += &format!("Duration: {duration} ms\n",);
|
ret += &format!("Duration: {duration} ms\n",);
|
||||||
}
|
}
|
||||||
|
ret += &format!("\nDatabase ID: {}", msg.id);
|
||||||
if !msg.rfc724_mid.is_empty() {
|
if !msg.rfc724_mid.is_empty() {
|
||||||
ret += &format!("\nMessage-ID: {}", msg.rfc724_mid);
|
ret += &format!("\nMessage-ID: {}", msg.rfc724_mid);
|
||||||
|
|
||||||
@@ -602,6 +615,31 @@ impl Message {
|
|||||||
Ok(msg)
|
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
|
/// Returns additional text which is appended to the message's text field
|
||||||
/// when it is loaded from the database.
|
/// 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
|
/// Currently this is used to add infomation to pre-messages of what the download will be and how large it is
|
||||||
@@ -1389,6 +1427,9 @@ pub enum MessageState {
|
|||||||
/// The user has pressed the "send" button but the message is not
|
/// 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
|
/// yet sent and is pending in some way. Maybe we're offline (no
|
||||||
/// checkmark).
|
/// 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,
|
OutPending = 20,
|
||||||
|
|
||||||
/// *Unrecoverable* error (*recoverable* errors result in pending
|
/// *Unrecoverable* error (*recoverable* errors result in pending
|
||||||
@@ -2001,13 +2042,6 @@ pub(crate) async fn update_msg_state(
|
|||||||
Ok(())
|
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(
|
pub(crate) async fn set_msg_failed(
|
||||||
context: &Context,
|
context: &Context,
|
||||||
msg: &mut Message,
|
msg: &mut Message,
|
||||||
|
|||||||
@@ -1168,12 +1168,6 @@ impl MimeFactory {
|
|||||||
_ => None,
|
_ => 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 encrypted = if let Some(shared_secret) = shared_secret {
|
||||||
let sign = true;
|
let sign = true;
|
||||||
encrypt_helper
|
encrypt_helper
|
||||||
|
|||||||
@@ -1790,35 +1790,27 @@ async fn test_time_in_future() -> Result<()> {
|
|||||||
Ok(())
|
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)]
|
#[tokio::test(flavor = "multi_thread", worker_threads = 2)]
|
||||||
async fn test_hp_legacy_display() -> Result<()> {
|
async fn test_hp_legacy_display() -> Result<()> {
|
||||||
let mut tcm = TestContextManager::new();
|
let mut tcm = TestContextManager::new();
|
||||||
let alice = &tcm.alice().await;
|
|
||||||
let bob = &tcm.bob().await;
|
let bob = &tcm.bob().await;
|
||||||
|
|
||||||
let mut msg = Message::new_text(
|
let msg_id = receive_imf(
|
||||||
"Subject: Dinner plans\n\
|
bob,
|
||||||
\n\
|
include_bytes!("../../test-data/message/hp_legacy_display.eml"),
|
||||||
Let's eat"
|
false,
|
||||||
.to_string(),
|
)
|
||||||
);
|
.await?
|
||||||
msg.set_subject("Dinner plans".to_string());
|
.unwrap()
|
||||||
let chat_id = alice.create_chat(bob).await.id;
|
.msg_ids[0];
|
||||||
alice.set_config_bool(Config::TestHooks, true).await?;
|
let msg_bob = Message::load_from_db(bob, msg_id).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;
|
|
||||||
|
|
||||||
let msg_bob = bob.recv_msg(&sent_msg).await;
|
|
||||||
assert_eq!(msg_bob.subject, "Dinner plans");
|
assert_eq!(msg_bob.subject, "Dinner plans");
|
||||||
|
|
||||||
|
// Legacy display element is removed from the text/plain body.
|
||||||
assert_eq!(msg_bob.text, "Let's eat");
|
assert_eq!(msg_bob.text, "Let's eat");
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -23,6 +23,7 @@ pub(crate) mod session;
|
|||||||
pub(crate) mod tls;
|
pub(crate) mod tls;
|
||||||
|
|
||||||
use dns::lookup_host_with_cache;
|
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};
|
pub use http::{Response as HttpResponse, read_url, read_url_blob};
|
||||||
use tls::wrap_tls;
|
use tls::wrap_tls;
|
||||||
|
|
||||||
|
|||||||
@@ -231,7 +231,10 @@ static DNS_PRELOAD: LazyLock<HashMap<&'static str, Vec<IpAddr>>> = LazyLock::new
|
|||||||
HashMap::from([
|
HashMap::from([
|
||||||
(
|
(
|
||||||
"imap.163.com",
|
"imap.163.com",
|
||||||
vec![IpAddr::V4(Ipv4Addr::new(111, 124, 203, 45))],
|
vec![
|
||||||
|
IpAddr::V4(Ipv4Addr::new(111, 124, 203, 45)),
|
||||||
|
IpAddr::V4(Ipv4Addr::new(111, 124, 203, 50)),
|
||||||
|
],
|
||||||
),
|
),
|
||||||
(
|
(
|
||||||
"smtp.163.com",
|
"smtp.163.com",
|
||||||
@@ -422,12 +425,12 @@ static DNS_PRELOAD: LazyLock<HashMap<&'static str, Vec<IpAddr>>> = LazyLock::new
|
|||||||
"nine.testrun.org",
|
"nine.testrun.org",
|
||||||
vec![
|
vec![
|
||||||
IpAddr::V4(Ipv4Addr::new(128, 140, 126, 197)),
|
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(216, 144, 228, 100)),
|
||||||
IpAddr::V6(Ipv6Addr::new(0x2a01, 0x4f8, 0x241, 0x4ce8, 0, 0, 0, 2)),
|
IpAddr::V4(Ipv4Addr::new(77, 42, 49, 41)),
|
||||||
IpAddr::V6(Ipv6Addr::new(
|
IpAddr::V6(Ipv6Addr::new(
|
||||||
0x2001, 0x41d0, 0x701, 0x1100, 0, 0, 0, 0x8ab1,
|
0x2001, 0x41d0, 0x701, 0x1100, 0, 0, 0, 0x8ab1,
|
||||||
)),
|
)),
|
||||||
|
IpAddr::V6(Ipv6Addr::new(0x2a01, 0x4f9, 0xfff1, 0x59, 0, 0, 0, 1)),
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
(
|
(
|
||||||
@@ -697,6 +700,10 @@ static DNS_PRELOAD: LazyLock<HashMap<&'static str, Vec<IpAddr>>> = LazyLock::new
|
|||||||
"chatmail.hackea.org",
|
"chatmail.hackea.org",
|
||||||
vec![IpAddr::V4(Ipv4Addr::new(82, 165, 11, 85))],
|
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",
|
"chika.aangat.lahat.computer",
|
||||||
vec![IpAddr::V4(Ipv4Addr::new(71, 19, 150, 113))],
|
vec![IpAddr::V4(Ipv4Addr::new(71, 19, 150, 113))],
|
||||||
@@ -738,6 +745,46 @@ static DNS_PRELOAD: LazyLock<HashMap<&'static str, Vec<IpAddr>>> = LazyLock::new
|
|||||||
"danneskjold.de",
|
"danneskjold.de",
|
||||||
vec![IpAddr::V4(Ipv4Addr::new(46, 62, 216, 132))],
|
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",
|
"darkrun.dev",
|
||||||
vec![IpAddr::V4(Ipv4Addr::new(72, 11, 149, 146))],
|
vec![IpAddr::V4(Ipv4Addr::new(72, 11, 149, 146))],
|
||||||
|
|||||||
@@ -13,7 +13,7 @@ use crate::context::Context;
|
|||||||
use crate::log::warn;
|
use crate::log::warn;
|
||||||
use crate::net::proxy::ProxyConfig;
|
use crate::net::proxy::ProxyConfig;
|
||||||
use crate::net::session::SessionStream;
|
use crate::net::session::SessionStream;
|
||||||
use crate::net::tls::wrap_rustls;
|
use crate::net::tls::wrap_tls;
|
||||||
use crate::tools::time;
|
use crate::tools::time;
|
||||||
|
|
||||||
/// User-Agent for HTTP requests if a resource usage policy requires it.
|
/// User-Agent for HTTP requests if a resource usage policy requires it.
|
||||||
@@ -35,7 +35,16 @@ pub struct Response {
|
|||||||
|
|
||||||
/// Retrieves the text contents of URL using HTTP GET request.
|
/// Retrieves the text contents of URL using HTTP GET request.
|
||||||
pub async fn read_url(context: &Context, url: &str) -> Result<String> {
|
pub async fn read_url(context: &Context, url: &str) -> Result<String> {
|
||||||
let response = read_url_blob(context, url).await?;
|
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 text = String::from_utf8_lossy(&response.blob);
|
let text = String::from_utf8_lossy(&response.blob);
|
||||||
Ok(text.to_string())
|
Ok(text.to_string())
|
||||||
}
|
}
|
||||||
@@ -43,6 +52,7 @@ pub async fn read_url(context: &Context, url: &str) -> Result<String> {
|
|||||||
async fn get_http_sender<B>(
|
async fn get_http_sender<B>(
|
||||||
context: &Context,
|
context: &Context,
|
||||||
parsed_url: hyper::Uri,
|
parsed_url: hyper::Uri,
|
||||||
|
strict_tls: bool,
|
||||||
) -> Result<hyper::client::conn::http1::SendRequest<B>>
|
) -> Result<hyper::client::conn::http1::SendRequest<B>>
|
||||||
where
|
where
|
||||||
B: hyper::body::Body + 'static + Send,
|
B: hyper::body::Body + 'static + Send,
|
||||||
@@ -76,37 +86,29 @@ where
|
|||||||
let port = parsed_url.port_u16().unwrap_or(443);
|
let port = parsed_url.port_u16().unwrap_or(443);
|
||||||
let (use_sni, load_cache) = (true, true);
|
let (use_sni, load_cache) = (true, true);
|
||||||
|
|
||||||
if let Some(proxy_config) = proxy_config_opt {
|
let tcp_stream: Box<dyn SessionStream> = if let Some(proxy_config) = proxy_config_opt {
|
||||||
let proxy_stream = proxy_config
|
let proxy_stream = proxy_config
|
||||||
.connect(context, host, port, load_cache)
|
.connect(context, host, port, load_cache)
|
||||||
.await?;
|
.await?;
|
||||||
let tls_stream = wrap_rustls(
|
Box::new(proxy_stream)
|
||||||
host,
|
|
||||||
port,
|
|
||||||
use_sni,
|
|
||||||
"",
|
|
||||||
proxy_stream,
|
|
||||||
&context.tls_session_store,
|
|
||||||
&context.spki_hash_store,
|
|
||||||
&context.sql,
|
|
||||||
)
|
|
||||||
.await?;
|
|
||||||
Box::new(tls_stream)
|
|
||||||
} else {
|
} else {
|
||||||
let tcp_stream = crate::net::connect_tcp(context, host, port, load_cache).await?;
|
let tcp_stream = crate::net::connect_tcp(context, host, port, load_cache).await?;
|
||||||
let tls_stream = wrap_rustls(
|
Box::new(tcp_stream)
|
||||||
host,
|
};
|
||||||
port,
|
|
||||||
use_sni,
|
let tls_stream = wrap_tls(
|
||||||
"",
|
strict_tls,
|
||||||
tcp_stream,
|
host,
|
||||||
&context.tls_session_store,
|
port,
|
||||||
&context.spki_hash_store,
|
use_sni,
|
||||||
&context.sql,
|
"",
|
||||||
)
|
tcp_stream,
|
||||||
.await?;
|
&context.tls_session_store,
|
||||||
Box::new(tls_stream)
|
&context.spki_hash_store,
|
||||||
}
|
&context.sql,
|
||||||
|
)
|
||||||
|
.await?;
|
||||||
|
Box::new(tls_stream)
|
||||||
}
|
}
|
||||||
_ => bail!("Unknown URL scheme"),
|
_ => bail!("Unknown URL scheme"),
|
||||||
};
|
};
|
||||||
@@ -260,7 +262,7 @@ pub(crate) async fn http_cache_cleanup(context: &Context) -> Result<()> {
|
|||||||
/// Fetches URL and updates the cache.
|
/// Fetches URL and updates the cache.
|
||||||
///
|
///
|
||||||
/// URL is fetched regardless of whether there is an existing result in 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) -> Result<Response> {
|
async fn fetch_url(context: &Context, original_url: &str, strict_tls: bool) -> Result<Response> {
|
||||||
let mut url = original_url.to_string();
|
let mut url = original_url.to_string();
|
||||||
|
|
||||||
// Follow up to 10 http-redirects
|
// Follow up to 10 http-redirects
|
||||||
@@ -269,7 +271,7 @@ async fn fetch_url(context: &Context, original_url: &str) -> Result<Response> {
|
|||||||
.parse::<hyper::Uri>()
|
.parse::<hyper::Uri>()
|
||||||
.with_context(|| format!("Failed to parse URL {url:?}"))?;
|
.with_context(|| format!("Failed to parse URL {url:?}"))?;
|
||||||
|
|
||||||
let mut sender = get_http_sender(context, parsed_url.clone()).await?;
|
let mut sender = get_http_sender(context, parsed_url.clone(), strict_tls).await?;
|
||||||
let authority = parsed_url
|
let authority = parsed_url
|
||||||
.authority()
|
.authority()
|
||||||
.context("URL has no authority")?
|
.context("URL has no authority")?
|
||||||
@@ -339,8 +341,10 @@ async fn fetch_url(context: &Context, original_url: &str) -> Result<Response> {
|
|||||||
mimetype,
|
mimetype,
|
||||||
encoding,
|
encoding,
|
||||||
};
|
};
|
||||||
info!(context, "Inserting {original_url:?} into cache.");
|
if strict_tls {
|
||||||
http_cache_put(context, &url, &response).await?;
|
info!(context, "Inserting {original_url:?} into cache.");
|
||||||
|
http_cache_put(context, &url, &response).await?;
|
||||||
|
}
|
||||||
return Ok(response);
|
return Ok(response);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -349,6 +353,23 @@ async fn fetch_url(context: &Context, original_url: &str) -> Result<Response> {
|
|||||||
|
|
||||||
/// Retrieves the binary contents of URL using HTTP GET request.
|
/// Retrieves the binary contents of URL using HTTP GET request.
|
||||||
pub async fn read_url_blob(context: &Context, url: &str) -> Result<Response> {
|
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? {
|
if let Some((response, is_stale)) = http_cache_get(context, url).await? {
|
||||||
info!(context, "Returning {url:?} from cache.");
|
info!(context, "Returning {url:?} from cache.");
|
||||||
if is_stale {
|
if is_stale {
|
||||||
@@ -357,7 +378,7 @@ pub async fn read_url_blob(context: &Context, url: &str) -> Result<Response> {
|
|||||||
tokio::spawn(async move {
|
tokio::spawn(async move {
|
||||||
// Fetch URL in background to update the cache.
|
// Fetch URL in background to update the cache.
|
||||||
info!(context, "Fetching stale {url:?} in background.");
|
info!(context, "Fetching stale {url:?} in background.");
|
||||||
if let Err(err) = fetch_url(&context, &url).await {
|
if let Err(err) = fetch_url(&context, &url, true).await {
|
||||||
warn!(context, "Failed to revalidate {url:?}: {err:#}.");
|
warn!(context, "Failed to revalidate {url:?}: {err:#}.");
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
@@ -366,7 +387,7 @@ pub async fn read_url_blob(context: &Context, url: &str) -> Result<Response> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
info!(context, "Not found {url:?} in cache, fetching.");
|
info!(context, "Not found {url:?} in cache, fetching.");
|
||||||
let response = fetch_url(context, url).await?;
|
let response = fetch_url(context, url, true).await?;
|
||||||
Ok(response)
|
Ok(response)
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -384,7 +405,7 @@ pub(crate) async fn post_empty(context: &Context, url: &str) -> Result<(String,
|
|||||||
bail!("POST requests to non-HTTPS URLs are not allowed");
|
bail!("POST requests to non-HTTPS URLs are not allowed");
|
||||||
}
|
}
|
||||||
|
|
||||||
let mut sender = get_http_sender(context, parsed_url.clone()).await?;
|
let mut sender = get_http_sender(context, parsed_url.clone(), true).await?;
|
||||||
let authority = parsed_url
|
let authority = parsed_url
|
||||||
.authority()
|
.authority()
|
||||||
.context("URL has no authority")?
|
.context("URL has no authority")?
|
||||||
@@ -418,7 +439,7 @@ pub(crate) async fn post_string(context: &Context, url: &str, body: String) -> R
|
|||||||
bail!("POST requests to non-HTTPS URLs are not allowed");
|
bail!("POST requests to non-HTTPS URLs are not allowed");
|
||||||
}
|
}
|
||||||
|
|
||||||
let mut sender = get_http_sender(context, parsed_url.clone()).await?;
|
let mut sender = get_http_sender(context, parsed_url.clone(), true).await?;
|
||||||
let authority = parsed_url
|
let authority = parsed_url
|
||||||
.authority()
|
.authority()
|
||||||
.context("URL has no authority")?
|
.context("URL has no authority")?
|
||||||
@@ -449,7 +470,7 @@ pub(crate) async fn post_form<T: Serialize + ?Sized>(
|
|||||||
}
|
}
|
||||||
|
|
||||||
let encoded_body = serde_urlencoded::to_string(form).context("Failed to encode data")?;
|
let encoded_body = serde_urlencoded::to_string(form).context("Failed to encode data")?;
|
||||||
let mut sender = get_http_sender(context, parsed_url.clone()).await?;
|
let mut sender = get_http_sender(context, parsed_url.clone(), true).await?;
|
||||||
let authority = parsed_url
|
let authority = parsed_url
|
||||||
.authority()
|
.authority()
|
||||||
.context("URL has no authority")?
|
.context("URL has no authority")?
|
||||||
|
|||||||
151
src/net/proxy.rs
151
src/net/proxy.rs
@@ -50,7 +50,7 @@ impl ShadowsocksConfig {
|
|||||||
#[derive(Debug, Clone, PartialEq, Eq)]
|
#[derive(Debug, Clone, PartialEq, Eq)]
|
||||||
pub struct HttpConfig {
|
pub struct HttpConfig {
|
||||||
/// HTTP proxy host.
|
/// HTTP proxy host.
|
||||||
pub host: String,
|
pub host: url::Host,
|
||||||
|
|
||||||
/// HTTP proxy port.
|
/// HTTP proxy port.
|
||||||
pub port: u16,
|
pub port: u16,
|
||||||
@@ -63,10 +63,7 @@ pub struct HttpConfig {
|
|||||||
|
|
||||||
impl HttpConfig {
|
impl HttpConfig {
|
||||||
fn from_url(url: Url) -> Result<Self> {
|
fn from_url(url: Url) -> Result<Self> {
|
||||||
let host = url
|
let host = url.host().context("HTTP proxy URL has no host")?.to_owned();
|
||||||
.host_str()
|
|
||||||
.context("HTTP proxy URL has no host")?
|
|
||||||
.to_string();
|
|
||||||
let port = url
|
let port = url
|
||||||
.port_or_known_default()
|
.port_or_known_default()
|
||||||
.context("HTTP(S) URLs are guaranteed to return Some port")?;
|
.context("HTTP(S) URLs are guaranteed to return Some port")?;
|
||||||
@@ -104,7 +101,8 @@ impl HttpConfig {
|
|||||||
|
|
||||||
#[derive(Debug, Clone, PartialEq, Eq)]
|
#[derive(Debug, Clone, PartialEq, Eq)]
|
||||||
pub struct Socks5Config {
|
pub struct Socks5Config {
|
||||||
pub host: String,
|
/// Hostname or IP address.
|
||||||
|
pub host: url::Host,
|
||||||
pub port: u16,
|
pub port: u16,
|
||||||
pub user_password: Option<(String, String)>,
|
pub user_password: Option<(String, String)>,
|
||||||
}
|
}
|
||||||
@@ -117,7 +115,13 @@ impl Socks5Config {
|
|||||||
target_port: u16,
|
target_port: u16,
|
||||||
load_dns_cache: bool,
|
load_dns_cache: bool,
|
||||||
) -> Result<Socks5Stream<Pin<Box<TimeoutStream<TcpStream>>>>> {
|
) -> Result<Socks5Stream<Pin<Box<TimeoutStream<TcpStream>>>>> {
|
||||||
let tcp_stream = connect_tcp(context, &self.host, self.port, load_dns_cache)
|
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)
|
||||||
.await
|
.await
|
||||||
.context("Failed to connect to SOCKS5 proxy")?;
|
.context("Failed to connect to SOCKS5 proxy")?;
|
||||||
|
|
||||||
@@ -273,10 +277,7 @@ impl ProxyConfig {
|
|||||||
// Because of this we do not distinguish
|
// Because of this we do not distinguish
|
||||||
// between `socks5` and `socks5h`.
|
// between `socks5` and `socks5h`.
|
||||||
"socks5" => {
|
"socks5" => {
|
||||||
let host = url
|
let host = url.host().context("socks5 URL has no host")?.to_owned();
|
||||||
.host_str()
|
|
||||||
.context("socks5 URL has no host")?
|
|
||||||
.to_string();
|
|
||||||
let port = url.port().unwrap_or(DEFAULT_SOCKS_PORT);
|
let port = url.port().unwrap_or(DEFAULT_SOCKS_PORT);
|
||||||
let user_password = if let Some(password) = url.password() {
|
let user_password = if let Some(password) = url.password() {
|
||||||
let username = percent_encoding::percent_decode_str(url.username())
|
let username = percent_encoding::percent_decode_str(url.username())
|
||||||
@@ -402,13 +403,14 @@ impl ProxyConfig {
|
|||||||
match self {
|
match self {
|
||||||
ProxyConfig::Http(http_config) => {
|
ProxyConfig::Http(http_config) => {
|
||||||
let load_cache = false;
|
let load_cache = false;
|
||||||
let tcp_stream = crate::net::connect_tcp(
|
let hostname = match &http_config.host {
|
||||||
context,
|
url::Host::Domain(domain) => domain.to_string(),
|
||||||
&http_config.host,
|
url::Host::Ipv4(addr) => addr.to_string(),
|
||||||
http_config.port,
|
url::Host::Ipv6(addr) => addr.to_string(),
|
||||||
load_cache,
|
};
|
||||||
)
|
let tcp_stream =
|
||||||
.await?;
|
crate::net::connect_tcp(context, &hostname, http_config.port, load_cache)
|
||||||
|
.await?;
|
||||||
let auth = if let Some((username, password)) = &http_config.user_password {
|
let auth = if let Some((username, password)) = &http_config.user_password {
|
||||||
Some((username.as_str(), password.as_str()))
|
Some((username.as_str(), password.as_str()))
|
||||||
} else {
|
} else {
|
||||||
@@ -419,16 +421,18 @@ impl ProxyConfig {
|
|||||||
}
|
}
|
||||||
ProxyConfig::Https(https_config) => {
|
ProxyConfig::Https(https_config) => {
|
||||||
let load_cache = true;
|
let load_cache = true;
|
||||||
let tcp_stream = crate::net::connect_tcp(
|
let hostname = match &https_config.host {
|
||||||
context,
|
url::Host::Domain(domain) => domain.to_string(),
|
||||||
&https_config.host,
|
url::Host::Ipv4(addr) => addr.to_string(),
|
||||||
https_config.port,
|
url::Host::Ipv6(addr) => addr.to_string(),
|
||||||
load_cache,
|
};
|
||||||
)
|
|
||||||
.await?;
|
let tcp_stream =
|
||||||
|
crate::net::connect_tcp(context, &hostname, https_config.port, load_cache)
|
||||||
|
.await?;
|
||||||
let use_sni = true;
|
let use_sni = true;
|
||||||
let tls_stream = wrap_rustls(
|
let tls_stream = wrap_rustls(
|
||||||
&https_config.host,
|
&hostname,
|
||||||
https_config.port,
|
https_config.port,
|
||||||
use_sni,
|
use_sni,
|
||||||
"",
|
"",
|
||||||
@@ -500,6 +504,7 @@ mod tests {
|
|||||||
use super::*;
|
use super::*;
|
||||||
use crate::config::Config;
|
use crate::config::Config;
|
||||||
use crate::test_utils::TestContext;
|
use crate::test_utils::TestContext;
|
||||||
|
use std::net::{Ipv4Addr, Ipv6Addr};
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn test_socks5_url() {
|
fn test_socks5_url() {
|
||||||
@@ -507,17 +512,35 @@ mod tests {
|
|||||||
assert_eq!(
|
assert_eq!(
|
||||||
proxy_config,
|
proxy_config,
|
||||||
ProxyConfig::Socks5(Socks5Config {
|
ProxyConfig::Socks5(Socks5Config {
|
||||||
host: "127.0.0.1".to_string(),
|
// 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()),
|
||||||
port: 9050,
|
port: 9050,
|
||||||
user_password: None
|
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();
|
let proxy_config = ProxyConfig::from_url("socks5://foo:bar@127.0.0.1:9150").unwrap();
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
proxy_config,
|
proxy_config,
|
||||||
ProxyConfig::Socks5(Socks5Config {
|
ProxyConfig::Socks5(Socks5Config {
|
||||||
host: "127.0.0.1".to_string(),
|
host: url::Host::Domain("127.0.0.1".to_string()),
|
||||||
port: 9150,
|
port: 9150,
|
||||||
user_password: Some(("foo".to_string(), "bar".to_string()))
|
user_password: Some(("foo".to_string(), "bar".to_string()))
|
||||||
})
|
})
|
||||||
@@ -527,7 +550,7 @@ mod tests {
|
|||||||
assert_eq!(
|
assert_eq!(
|
||||||
proxy_config,
|
proxy_config,
|
||||||
ProxyConfig::Socks5(Socks5Config {
|
ProxyConfig::Socks5(Socks5Config {
|
||||||
host: "127.0.0.1".to_string(),
|
host: url::Host::Domain("127.0.0.1".to_string()),
|
||||||
port: 9150,
|
port: 9150,
|
||||||
user_password: Some(("foo".to_string(), "bar".to_string()))
|
user_password: Some(("foo".to_string(), "bar".to_string()))
|
||||||
})
|
})
|
||||||
@@ -537,7 +560,7 @@ mod tests {
|
|||||||
assert_eq!(
|
assert_eq!(
|
||||||
proxy_config,
|
proxy_config,
|
||||||
ProxyConfig::Socks5(Socks5Config {
|
ProxyConfig::Socks5(Socks5Config {
|
||||||
host: "127.0.0.1".to_string(),
|
host: url::Host::Domain("127.0.0.1".to_string()),
|
||||||
port: 80,
|
port: 80,
|
||||||
user_password: None
|
user_password: None
|
||||||
})
|
})
|
||||||
@@ -547,7 +570,7 @@ mod tests {
|
|||||||
assert_eq!(
|
assert_eq!(
|
||||||
proxy_config,
|
proxy_config,
|
||||||
ProxyConfig::Socks5(Socks5Config {
|
ProxyConfig::Socks5(Socks5Config {
|
||||||
host: "127.0.0.1".to_string(),
|
host: url::Host::Domain("127.0.0.1".to_string()),
|
||||||
port: 1080,
|
port: 1080,
|
||||||
user_password: None
|
user_password: None
|
||||||
})
|
})
|
||||||
@@ -557,7 +580,7 @@ mod tests {
|
|||||||
assert_eq!(
|
assert_eq!(
|
||||||
proxy_config,
|
proxy_config,
|
||||||
ProxyConfig::Socks5(Socks5Config {
|
ProxyConfig::Socks5(Socks5Config {
|
||||||
host: "127.0.0.1".to_string(),
|
host: url::Host::Domain("127.0.0.1".to_string()),
|
||||||
port: 1080,
|
port: 1080,
|
||||||
user_password: None
|
user_password: None
|
||||||
})
|
})
|
||||||
@@ -567,7 +590,7 @@ mod tests {
|
|||||||
assert_eq!(
|
assert_eq!(
|
||||||
proxy_config,
|
proxy_config,
|
||||||
ProxyConfig::Socks5(Socks5Config {
|
ProxyConfig::Socks5(Socks5Config {
|
||||||
host: "my-proxy.example.org".to_string(),
|
host: url::Host::Domain("my-proxy.example.org".to_string()),
|
||||||
port: 1080,
|
port: 1080,
|
||||||
user_password: None
|
user_password: None
|
||||||
})
|
})
|
||||||
@@ -584,37 +607,61 @@ mod tests {
|
|||||||
assert_eq!(
|
assert_eq!(
|
||||||
proxy_config,
|
proxy_config,
|
||||||
ProxyConfig::Http(HttpConfig {
|
ProxyConfig::Http(HttpConfig {
|
||||||
host: "127.0.0.1".to_string(),
|
host: url::Host::Ipv4(Ipv4Addr::LOCALHOST),
|
||||||
port: 80,
|
port: 80,
|
||||||
user_password: None
|
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();
|
let proxy_config = ProxyConfig::from_url("http://127.0.0.1:80").unwrap();
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
proxy_config,
|
proxy_config,
|
||||||
ProxyConfig::Http(HttpConfig {
|
ProxyConfig::Http(HttpConfig {
|
||||||
host: "127.0.0.1".to_string(),
|
host: url::Host::Ipv4(Ipv4Addr::LOCALHOST),
|
||||||
port: 80,
|
port: 80,
|
||||||
user_password: None
|
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();
|
let proxy_config = ProxyConfig::from_url("http://127.0.0.1:443").unwrap();
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
proxy_config,
|
proxy_config,
|
||||||
ProxyConfig::Http(HttpConfig {
|
ProxyConfig::Http(HttpConfig {
|
||||||
host: "127.0.0.1".to_string(),
|
host: url::Host::Ipv4(Ipv4Addr::LOCALHOST),
|
||||||
port: 443,
|
port: 443,
|
||||||
user_password: None
|
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();
|
let proxy_config = ProxyConfig::from_url("http://my-proxy.example.org").unwrap();
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
proxy_config,
|
proxy_config,
|
||||||
ProxyConfig::Http(HttpConfig {
|
ProxyConfig::Http(HttpConfig {
|
||||||
host: "my-proxy.example.org".to_string(),
|
host: url::Host::Domain("my-proxy.example.org".to_string()),
|
||||||
port: 80,
|
port: 80,
|
||||||
user_password: None
|
user_password: None
|
||||||
})
|
})
|
||||||
@@ -631,7 +678,7 @@ mod tests {
|
|||||||
assert_eq!(
|
assert_eq!(
|
||||||
proxy_config,
|
proxy_config,
|
||||||
ProxyConfig::Https(HttpConfig {
|
ProxyConfig::Https(HttpConfig {
|
||||||
host: "127.0.0.1".to_string(),
|
host: url::Host::Ipv4(Ipv4Addr::LOCALHOST),
|
||||||
port: 443,
|
port: 443,
|
||||||
user_password: None
|
user_password: None
|
||||||
})
|
})
|
||||||
@@ -641,7 +688,17 @@ mod tests {
|
|||||||
assert_eq!(
|
assert_eq!(
|
||||||
proxy_config,
|
proxy_config,
|
||||||
ProxyConfig::Https(HttpConfig {
|
ProxyConfig::Https(HttpConfig {
|
||||||
host: "127.0.0.1".to_string(),
|
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),
|
||||||
port: 80,
|
port: 80,
|
||||||
user_password: None
|
user_password: None
|
||||||
})
|
})
|
||||||
@@ -651,17 +708,29 @@ mod tests {
|
|||||||
assert_eq!(
|
assert_eq!(
|
||||||
proxy_config,
|
proxy_config,
|
||||||
ProxyConfig::Https(HttpConfig {
|
ProxyConfig::Https(HttpConfig {
|
||||||
host: "127.0.0.1".to_string(),
|
host: url::Host::Ipv4(Ipv4Addr::LOCALHOST),
|
||||||
port: 443,
|
port: 443,
|
||||||
user_password: None
|
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();
|
let proxy_config = ProxyConfig::from_url("https://my-proxy.example.org").unwrap();
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
proxy_config,
|
proxy_config,
|
||||||
ProxyConfig::Https(HttpConfig {
|
ProxyConfig::Https(HttpConfig {
|
||||||
host: "my-proxy.example.org".to_string(),
|
host: url::Host::Domain("my-proxy.example.org".to_string()),
|
||||||
port: 443,
|
port: 443,
|
||||||
user_password: None
|
user_password: None
|
||||||
})
|
})
|
||||||
|
|||||||
@@ -126,9 +126,12 @@ pub async fn wrap_rustls<'a>(
|
|||||||
let root_cert_store =
|
let root_cert_store =
|
||||||
rustls::RootCertStore::from_iter(webpki_roots::TLS_SERVER_ROOTS.iter().cloned());
|
rustls::RootCertStore::from_iter(webpki_roots::TLS_SERVER_ROOTS.iter().cloned());
|
||||||
|
|
||||||
let mut config = rustls::ClientConfig::builder()
|
let mut config = rustls::ClientConfig::builder_with_provider(Arc::new(
|
||||||
.with_root_certificates(root_cert_store)
|
rustls::crypto::aws_lc_rs::default_provider(),
|
||||||
.with_no_client_auth();
|
))
|
||||||
|
.with_safe_default_protocol_versions()?
|
||||||
|
.with_root_certificates(root_cert_store)
|
||||||
|
.with_no_client_auth();
|
||||||
config.alpn_protocols = if alpn.is_empty() {
|
config.alpn_protocols = if alpn.is_empty() {
|
||||||
vec![]
|
vec![]
|
||||||
} else {
|
} else {
|
||||||
|
|||||||
@@ -51,7 +51,7 @@ impl rustls::client::danger::ServerCertVerifier for CustomCertificateVerifier {
|
|||||||
|
|
||||||
let spki = parsed_certificate.subject_public_key_info();
|
let spki = parsed_certificate.subject_public_key_info();
|
||||||
|
|
||||||
let provider = rustls::crypto::ring::default_provider();
|
let provider = rustls::crypto::aws_lc_rs::default_provider();
|
||||||
|
|
||||||
if let ServerName::DnsName(dns_name) = server_name
|
if let ServerName::DnsName(dns_name) = server_name
|
||||||
&& dns_name.as_ref().starts_with("_")
|
&& dns_name.as_ref().starts_with("_")
|
||||||
@@ -97,7 +97,7 @@ impl rustls::client::danger::ServerCertVerifier for CustomCertificateVerifier {
|
|||||||
cert: &CertificateDer<'_>,
|
cert: &CertificateDer<'_>,
|
||||||
dss: &rustls::DigitallySignedStruct,
|
dss: &rustls::DigitallySignedStruct,
|
||||||
) -> Result<rustls::client::danger::HandshakeSignatureValid, rustls::Error> {
|
) -> Result<rustls::client::danger::HandshakeSignatureValid, rustls::Error> {
|
||||||
let provider = rustls::crypto::ring::default_provider();
|
let provider = rustls::crypto::aws_lc_rs::default_provider();
|
||||||
let supported_schemes = &provider.signature_verification_algorithms;
|
let supported_schemes = &provider.signature_verification_algorithms;
|
||||||
rustls::crypto::verify_tls12_signature(message, cert, dss, supported_schemes)
|
rustls::crypto::verify_tls12_signature(message, cert, dss, supported_schemes)
|
||||||
}
|
}
|
||||||
@@ -108,13 +108,13 @@ impl rustls::client::danger::ServerCertVerifier for CustomCertificateVerifier {
|
|||||||
cert: &CertificateDer<'_>,
|
cert: &CertificateDer<'_>,
|
||||||
dss: &rustls::DigitallySignedStruct,
|
dss: &rustls::DigitallySignedStruct,
|
||||||
) -> Result<rustls::client::danger::HandshakeSignatureValid, rustls::Error> {
|
) -> Result<rustls::client::danger::HandshakeSignatureValid, rustls::Error> {
|
||||||
let provider = rustls::crypto::ring::default_provider();
|
let provider = rustls::crypto::aws_lc_rs::default_provider();
|
||||||
let supported_schemes = &provider.signature_verification_algorithms;
|
let supported_schemes = &provider.signature_verification_algorithms;
|
||||||
rustls::crypto::verify_tls13_signature(message, cert, dss, supported_schemes)
|
rustls::crypto::verify_tls13_signature(message, cert, dss, supported_schemes)
|
||||||
}
|
}
|
||||||
|
|
||||||
fn supported_verify_schemes(&self) -> Vec<rustls::SignatureScheme> {
|
fn supported_verify_schemes(&self) -> Vec<rustls::SignatureScheme> {
|
||||||
let provider = rustls::crypto::ring::default_provider();
|
let provider = rustls::crypto::aws_lc_rs::default_provider();
|
||||||
provider
|
provider
|
||||||
.signature_verification_algorithms
|
.signature_verification_algorithms
|
||||||
.supported_schemes()
|
.supported_schemes()
|
||||||
|
|||||||
@@ -835,21 +835,16 @@ UPDATE config SET value=? WHERE keyname='configured_addr' AND value!=?1
|
|||||||
{
|
{
|
||||||
can_info_msg = false;
|
can_info_msg = false;
|
||||||
if mime_parser.pre_message == PreMessageMode::Post
|
if mime_parser.pre_message == PreMessageMode::Post
|
||||||
&& let Some(msg_id) = message::rfc724_mid_exists(context, rfc724_mid_orig).await?
|
&& let Some(msg) =
|
||||||
|
Message::load_by_rfc724_mid_optional(context, rfc724_mid_orig).await?
|
||||||
{
|
{
|
||||||
// The messsage is a post-message and pre-message exists.
|
// The messsage is a post-message and pre-message exists.
|
||||||
// Assign status update to existing message because just received post-message will be trashed.
|
// Assign status update to existing message because just received post-message will be trashed.
|
||||||
Some(
|
Some(msg)
|
||||||
Message::load_from_db(context, msg_id)
|
|
||||||
.await
|
|
||||||
.context("Failed to load webxdc instance that we just checked exists")?,
|
|
||||||
)
|
|
||||||
} else {
|
} else {
|
||||||
Some(
|
Message::load_from_db_optional(context, insert_msg_id)
|
||||||
Message::load_from_db(context, insert_msg_id)
|
.await
|
||||||
.await
|
.context("Failed to load just created webxdc instance")?
|
||||||
.context("Failed to load just created webxdc instance")?,
|
|
||||||
)
|
|
||||||
}
|
}
|
||||||
} else if let Some(field) = mime_parser.get_header(HeaderDef::InReplyTo) {
|
} else if let Some(field) = mime_parser.get_header(HeaderDef::InReplyTo) {
|
||||||
if let Some(instance) =
|
if let Some(instance) =
|
||||||
@@ -3795,13 +3790,17 @@ async fn apply_out_broadcast_changes(
|
|||||||
} else if from_id == ContactId::SELF
|
} else if from_id == ContactId::SELF
|
||||||
&& let Some(removed_id) = removed_id
|
&& let Some(removed_id) = removed_id
|
||||||
{
|
{
|
||||||
chat::remove_from_chat_contacts_table_without_trace(context, chat.id, removed_id)
|
if chat::remove_from_chat_contacts_table_without_trace(context, chat.id, removed_id)
|
||||||
.await?;
|
.await?
|
||||||
|
{
|
||||||
better_msg.get_or_insert(
|
better_msg.get_or_insert(
|
||||||
stock_str::msg_del_member_local(context, removed_id, ContactId::SELF).await,
|
stock_str::msg_del_member_local(context, removed_id, ContactId::SELF).await,
|
||||||
);
|
);
|
||||||
added_removed_id = Some(removed_id);
|
added_removed_id = Some(removed_id);
|
||||||
|
} else {
|
||||||
|
info!(context, "No-op broadcast member removal message (TRASH).");
|
||||||
|
better_msg = Some("".to_string());
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -3875,17 +3874,20 @@ async fn apply_in_broadcast_changes(
|
|||||||
}
|
}
|
||||||
chat::delete_broadcast_secret(context, chat.id).await?;
|
chat::delete_broadcast_secret(context, chat.id).await?;
|
||||||
|
|
||||||
if from_id == ContactId::SELF {
|
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 {
|
||||||
better_msg.get_or_insert(stock_str::msg_you_left_broadcast(context));
|
better_msg.get_or_insert(stock_str::msg_you_left_broadcast(context));
|
||||||
} else {
|
} else {
|
||||||
better_msg.get_or_insert(
|
better_msg.get_or_insert(
|
||||||
stock_str::msg_del_member_local(context, ContactId::SELF, from_id).await,
|
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? {
|
} else if !chat.is_self_in_chat(context).await? {
|
||||||
chat::add_to_chat_contacts_table(
|
chat::add_to_chat_contacts_table(
|
||||||
context,
|
context,
|
||||||
|
|||||||
@@ -2346,7 +2346,7 @@ ALTER TABLE contacts ADD COLUMN name_normalized TEXT;
|
|||||||
transaction.execute(
|
transaction.execute(
|
||||||
"UPDATE transports
|
"UPDATE transports
|
||||||
SET entered_param=json_set(entered_param, '$.imap.folder', ?1),
|
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,),
|
(mvbox_folder,),
|
||||||
)?;
|
)?;
|
||||||
}
|
}
|
||||||
|
|||||||
26
src/stats.rs
26
src/stats.rs
@@ -18,7 +18,7 @@ use crate::config::Config;
|
|||||||
use crate::constants::{Chattype, DC_VERSION_STR};
|
use crate::constants::{Chattype, DC_VERSION_STR};
|
||||||
use crate::contact::{Contact, ContactId, Origin, import_vcard, mark_contact_id_as_verified};
|
use crate::contact::{Contact, ContactId, Origin, import_vcard, mark_contact_id_as_verified};
|
||||||
use crate::context::Context;
|
use crate::context::Context;
|
||||||
use crate::key::load_self_public_keyring;
|
use crate::key::{DcKey, load_self_public_key};
|
||||||
use crate::log::LogExt;
|
use crate::log::LogExt;
|
||||||
use crate::message::{Message, Viewtype};
|
use crate::message::{Message, Viewtype};
|
||||||
use crate::securejoin::QrInvite;
|
use crate::securejoin::QrInvite;
|
||||||
@@ -33,7 +33,14 @@ const MESSAGE_STATS_UPDATE_INTERVAL_SECONDS: i64 = 4 * 60; // 4 minutes (less th
|
|||||||
#[derive(Serialize)]
|
#[derive(Serialize)]
|
||||||
struct Statistics {
|
struct Statistics {
|
||||||
core_version: String,
|
core_version: String,
|
||||||
|
number_of_transports: usize,
|
||||||
key_create_timestamps: Vec<u32>,
|
key_create_timestamps: Vec<u32>,
|
||||||
|
number_of_keys: u32,
|
||||||
|
/// OpenPGP version of the key.
|
||||||
|
key_version: u8,
|
||||||
|
key_algorithm: String,
|
||||||
|
/// Size of the public key in bytes (encoded in binary, not base64).
|
||||||
|
pubkey_size: usize,
|
||||||
stats_id: String,
|
stats_id: String,
|
||||||
is_chatmail: bool,
|
is_chatmail: bool,
|
||||||
contact_stats: Vec<ContactStat>,
|
contact_stats: Vec<ContactStat>,
|
||||||
@@ -345,11 +352,15 @@ async fn get_stats(context: &Context) -> Result<String> {
|
|||||||
.get_config_u32(Config::StatsLastOldContactId)
|
.get_config_u32(Config::StatsLastOldContactId)
|
||||||
.await?;
|
.await?;
|
||||||
|
|
||||||
let key_create_timestamps: Vec<u32> = load_self_public_keyring(context)
|
let self_public_key = load_self_public_key(context).await?;
|
||||||
|
// `key_create_timestamps` is a `Vec` for historical reasons,
|
||||||
|
// support for using multiple keys is being phased out.
|
||||||
|
let key_create_timestamps: Vec<u32> = vec![self_public_key.created_at().as_secs()];
|
||||||
|
let number_of_keys: u32 = context
|
||||||
|
.sql
|
||||||
|
.query_get_value("SELECT COUNT(*) FROM keypairs", ())
|
||||||
.await?
|
.await?
|
||||||
.iter()
|
.unwrap_or(0);
|
||||||
.map(|k| k.created_at().as_secs())
|
|
||||||
.collect();
|
|
||||||
|
|
||||||
let sending_enabled_timestamps =
|
let sending_enabled_timestamps =
|
||||||
get_timestamps(context, "stats_sending_enabled_events").await?;
|
get_timestamps(context, "stats_sending_enabled_events").await?;
|
||||||
@@ -358,7 +369,12 @@ async fn get_stats(context: &Context) -> Result<String> {
|
|||||||
|
|
||||||
let stats = Statistics {
|
let stats = Statistics {
|
||||||
core_version: DC_VERSION_STR.to_string(),
|
core_version: DC_VERSION_STR.to_string(),
|
||||||
|
number_of_transports: context.count_transports().await?,
|
||||||
key_create_timestamps,
|
key_create_timestamps,
|
||||||
|
number_of_keys,
|
||||||
|
key_version: self_public_key.primary_key.version().into(),
|
||||||
|
key_algorithm: format!("{:?}", self_public_key.algorithm()),
|
||||||
|
pubkey_size: DcKey::to_bytes(&self_public_key).len(),
|
||||||
stats_id: stats_id(context).await?,
|
stats_id: stats_id(context).await?,
|
||||||
is_chatmail: context.is_chatmail().await?,
|
is_chatmail: context.is_chatmail().await?,
|
||||||
contact_stats: get_contact_stats(context, last_old_contact).await?,
|
contact_stats: get_contact_stats(context, last_old_contact).await?,
|
||||||
|
|||||||
@@ -595,3 +595,33 @@ async fn test_stats_enable_disable_timestamps() -> Result<()> {
|
|||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[tokio::test(flavor = "multi_thread", worker_threads = 2)]
|
||||||
|
async fn test_cryptography_stats() -> Result<()> {
|
||||||
|
let alice = &TestContext::new_alice().await;
|
||||||
|
let stats = get_stats(alice).await.unwrap();
|
||||||
|
let stats: serde_json::Value = serde_json::from_str(&stats)?;
|
||||||
|
|
||||||
|
let number_of_transports: u64 = stats.get("number_of_transports").unwrap().as_u64().unwrap();
|
||||||
|
assert_eq!(number_of_transports, 1);
|
||||||
|
|
||||||
|
let key_version = stats.get("key_version").unwrap().as_u64().unwrap();
|
||||||
|
// Alice's key is v4
|
||||||
|
assert_eq!(key_version, 4);
|
||||||
|
|
||||||
|
let key_algorithm = stats.get("key_algorithm").unwrap().as_str().unwrap();
|
||||||
|
assert_eq!(key_algorithm, "EdDSALegacy");
|
||||||
|
|
||||||
|
let pubkey_size = stats.get("pubkey_size").unwrap().as_u64().unwrap();
|
||||||
|
assert_eq!(pubkey_size, 583);
|
||||||
|
|
||||||
|
crate::transport::add_pseudo_transport(alice, "alice@ten.testrun.org").await?;
|
||||||
|
|
||||||
|
let stats = get_stats(alice).await.unwrap();
|
||||||
|
let stats: serde_json::Value = serde_json::from_str(&stats)?;
|
||||||
|
|
||||||
|
let number_of_transports: u64 = stats.get("number_of_transports").unwrap().as_u64().unwrap();
|
||||||
|
assert_eq!(number_of_transports, 2);
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|||||||
@@ -38,7 +38,7 @@ use crate::events::{Event, EventEmitter, EventType, Events};
|
|||||||
use crate::key::{self, DcKey, self_fingerprint};
|
use crate::key::{self, DcKey, self_fingerprint};
|
||||||
use crate::log::warn;
|
use crate::log::warn;
|
||||||
use crate::login_param::EnteredLoginParam;
|
use crate::login_param::EnteredLoginParam;
|
||||||
use crate::message::{Message, MessageState, MsgId, update_msg_state};
|
use crate::message::{Message, MessageState, MsgId};
|
||||||
use crate::mimeparser::{MimeMessage, SystemMessage};
|
use crate::mimeparser::{MimeMessage, SystemMessage};
|
||||||
use crate::pgp::SeipdVersion;
|
use crate::pgp::SeipdVersion;
|
||||||
use crate::receive_imf::{ReceivedMsg, receive_imf};
|
use crate::receive_imf::{ReceivedMsg, receive_imf};
|
||||||
@@ -692,10 +692,11 @@ ORDER BY id"
|
|||||||
if !msg_has_pending_smtp_job(self, msg_id)
|
if !msg_has_pending_smtp_job(self, msg_id)
|
||||||
.await
|
.await
|
||||||
.expect("Failed to check for more jobs")
|
.expect("Failed to check for more jobs")
|
||||||
{
|
&& msg_id
|
||||||
update_msg_state(&self.ctx, msg_id, MessageState::OutDelivered)
|
.set_delivered(self)
|
||||||
.await
|
.await
|
||||||
.expect("failed to update message state");
|
.expect("MsgId::set_delivered")
|
||||||
|
{
|
||||||
self.sql
|
self.sql
|
||||||
.execute(
|
.execute(
|
||||||
"UPDATE msgs SET timestamp_sent=? WHERE id=?",
|
"UPDATE msgs SET timestamp_sent=? WHERE id=?",
|
||||||
@@ -772,16 +773,19 @@ ORDER BY id"
|
|||||||
.execute("DELETE FROM smtp WHERE msg_id=?", (msg_id,))
|
.execute("DELETE FROM smtp WHERE msg_id=?", (msg_id,))
|
||||||
.await
|
.await
|
||||||
.expect("Delete smtp jobs");
|
.expect("Delete smtp jobs");
|
||||||
update_msg_state(&self.ctx, msg_id, MessageState::OutDelivered)
|
if msg_id
|
||||||
|
.set_delivered(self)
|
||||||
.await
|
.await
|
||||||
.expect("Update message state");
|
.expect("MsgId::set_delivered")
|
||||||
self.sql
|
{
|
||||||
.execute(
|
self.sql
|
||||||
"UPDATE msgs SET timestamp_sent=? WHERE id=?",
|
.execute(
|
||||||
(time(), msg_id),
|
"UPDATE msgs SET timestamp_sent=? WHERE id=?",
|
||||||
)
|
(time(), msg_id),
|
||||||
.await
|
)
|
||||||
.expect("Update timestamp_sent");
|
.await
|
||||||
|
.expect("Update timestamp_sent");
|
||||||
|
}
|
||||||
sent_msgs
|
sent_msgs
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -565,6 +565,45 @@ async fn test_webxdc_updates_in_post_message_after_pre_message() -> Result<()> {
|
|||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[tokio::test(flavor = "multi_thread", worker_threads = 2)]
|
||||||
|
async fn test_webxdc_updates_in_post_message_after_deleted_pre_message() -> Result<()> {
|
||||||
|
let mut tcm = TestContextManager::new();
|
||||||
|
let alice = &tcm.alice().await;
|
||||||
|
let bob = &tcm.bob().await;
|
||||||
|
|
||||||
|
let alice_chat_id = alice.create_chat_id(bob).await;
|
||||||
|
let big_webxdc_app = big_webxdc_app().await?;
|
||||||
|
|
||||||
|
let mut alice_instance = Message::new(Viewtype::Webxdc);
|
||||||
|
alice_instance.set_file_from_bytes(alice, "test.xdc", &big_webxdc_app, None)?;
|
||||||
|
alice_instance.set_text("Test".to_string());
|
||||||
|
alice_chat_id
|
||||||
|
.set_draft(alice, Some(&mut alice_instance))
|
||||||
|
.await?;
|
||||||
|
alice
|
||||||
|
.send_webxdc_status_update(alice_instance.id, r#"{"payload":42, "info":"i"}"#)
|
||||||
|
.await?;
|
||||||
|
|
||||||
|
send_msg(alice, alice_chat_id, &mut alice_instance).await?;
|
||||||
|
let post_message = alice.pop_sent_msg().await;
|
||||||
|
let pre_message = alice.pop_sent_msg().await;
|
||||||
|
|
||||||
|
let bob_instance = bob.recv_msg(&pre_message).await;
|
||||||
|
assert_eq!(bob_instance.download_state, DownloadState::Available);
|
||||||
|
delete_msgs(bob, &[bob_instance.id]).await?;
|
||||||
|
|
||||||
|
bob.recv_msg_trash(&post_message).await;
|
||||||
|
|
||||||
|
// Deleted message stays trashed because of a tombstone.
|
||||||
|
assert!(
|
||||||
|
Message::load_from_db_optional(bob, bob_instance.id)
|
||||||
|
.await?
|
||||||
|
.is_none()
|
||||||
|
);
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
/// Tests receiving of a large webxdc post-message with updates attached
|
/// Tests receiving of a large webxdc post-message with updates attached
|
||||||
/// to the the .xdc post-message when pre-message arrives later.
|
/// to the the .xdc post-message when pre-message arrives later.
|
||||||
#[tokio::test(flavor = "multi_thread", worker_threads = 2)]
|
#[tokio::test(flavor = "multi_thread", worker_threads = 2)]
|
||||||
|
|||||||
@@ -41,6 +41,7 @@ async fn test_parse_receive_headers_integration() {
|
|||||||
let raw = include_bytes!("../../test-data/message/mail_with_cc.txt");
|
let raw = include_bytes!("../../test-data/message/mail_with_cc.txt");
|
||||||
let expected = r"State: Fresh
|
let expected = r"State: Fresh
|
||||||
|
|
||||||
|
Database ID: X
|
||||||
Message-ID: 2dfdbde7@example.org
|
Message-ID: 2dfdbde7@example.org
|
||||||
|
|
||||||
Hop: From: localhost; By: hq5.merlinux.eu; Date: Sat, 14 Sep 2019 17:00:22 +0000
|
Hop: From: localhost; By: hq5.merlinux.eu; Date: Sat, 14 Sep 2019 17:00:22 +0000
|
||||||
@@ -50,6 +51,7 @@ Hop: From: hq5.merlinux.eu; By: hq5.merlinux.eu; Date: Sat, 14 Sep 2019 17:00:25
|
|||||||
let raw = include_bytes!("../../test-data/message/encrypted_with_received_headers.eml");
|
let raw = include_bytes!("../../test-data/message/encrypted_with_received_headers.eml");
|
||||||
let expected = "State: Fresh, Encrypted
|
let expected = "State: Fresh, Encrypted
|
||||||
|
|
||||||
|
Database ID: X
|
||||||
Message-ID: Mr.adQpEwndXLH.LPDdlFVJ7wG@example.net
|
Message-ID: Mr.adQpEwndXLH.LPDdlFVJ7wG@example.net
|
||||||
|
|
||||||
Hop: From: [127.0.0.1]; By: mail.example.org; Date: Mon, 27 Dec 2021 11:21:21 +0000
|
Hop: From: [127.0.0.1]; By: mail.example.org; Date: Mon, 27 Dec 2021 11:21:21 +0000
|
||||||
@@ -71,7 +73,10 @@ async fn check_parse_receive_headers_integration(raw: &[u8], expected: &str) {
|
|||||||
// received time that depends on the test time which makes it impossible to
|
// received time that depends on the test time which makes it impossible to
|
||||||
// compare with a static string
|
// compare with a static string
|
||||||
let capped_result = &msg_info[msg_info.find("State").unwrap()..];
|
let capped_result = &msg_info[msg_info.find("State").unwrap()..];
|
||||||
assert_eq!(expected, capped_result);
|
assert_eq!(
|
||||||
|
expected.replace("\nDatabase ID: X", &format!("\nDatabase ID: {msg_id}")),
|
||||||
|
capped_result
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
|
|||||||
@@ -252,7 +252,11 @@ impl fmt::Display for ConfiguredLoginParam {
|
|||||||
write!(f, "{imap}")?;
|
write!(f, "{imap}")?;
|
||||||
first = false;
|
first = false;
|
||||||
}
|
}
|
||||||
write!(f, "] smtp:[")?;
|
write!(f, "]")?;
|
||||||
|
if let Some(folder) = &self.imap_folder {
|
||||||
|
write!(f, " folder:{folder:?}")?;
|
||||||
|
}
|
||||||
|
write!(f, " smtp:[")?;
|
||||||
let mut first = true;
|
let mut first = true;
|
||||||
for smtp in &self.smtp {
|
for smtp in &self.smtp {
|
||||||
if !first {
|
if !first {
|
||||||
|
|||||||
@@ -34,7 +34,7 @@ async fn test_save_load_login_param() -> Result<()> {
|
|||||||
},
|
},
|
||||||
user: "alice".to_string(),
|
user: "alice".to_string(),
|
||||||
}],
|
}],
|
||||||
imap_folder: None,
|
imap_folder: Some("Folder".to_string()),
|
||||||
imap_user: "".to_string(),
|
imap_user: "".to_string(),
|
||||||
imap_password: "foo".to_string(),
|
imap_password: "foo".to_string(),
|
||||||
smtp: vec![ConfiguredServerLoginParam {
|
smtp: vec![ConfiguredServerLoginParam {
|
||||||
@@ -56,7 +56,7 @@ async fn test_save_load_login_param() -> Result<()> {
|
|||||||
.clone()
|
.clone()
|
||||||
.save_to_transports_table(&t, &EnteredLoginParam::default(), time())
|
.save_to_transports_table(&t, &EnteredLoginParam::default(), time())
|
||||||
.await?;
|
.await?;
|
||||||
let expected_param = r#"{"addr":"alice@example.org","imap":[{"connection":{"host":"imap.example.com","port":123,"security":"Starttls"},"user":"alice"}],"imap_user":"","imap_password":"foo","smtp":[{"connection":{"host":"smtp.example.com","port":456,"security":"Tls"},"user":"alice@example.org"}],"smtp_user":"","smtp_password":"bar","provider_id":null,"certificate_checks":"Strict","oauth2":false}"#;
|
let expected_param = r#"{"addr":"alice@example.org","imap":[{"connection":{"host":"imap.example.com","port":123,"security":"Starttls"},"user":"alice"}],"imap_folder":"Folder","imap_user":"","imap_password":"foo","smtp":[{"connection":{"host":"smtp.example.com","port":456,"security":"Tls"},"user":"alice@example.org"}],"smtp_user":"","smtp_password":"bar","provider_id":null,"certificate_checks":"Strict","oauth2":false}"#;
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
t.sql
|
t.sql
|
||||||
.query_get_value::<String>("SELECT configured_param FROM transports", ())
|
.query_get_value::<String>("SELECT configured_param FROM transports", ())
|
||||||
@@ -68,6 +68,14 @@ async fn test_save_load_login_param() -> Result<()> {
|
|||||||
let (_transport_id, loaded) = ConfiguredLoginParam::load(&t).await?.unwrap();
|
let (_transport_id, loaded) = ConfiguredLoginParam::load(&t).await?.unwrap();
|
||||||
assert_eq!(param, loaded);
|
assert_eq!(param, loaded);
|
||||||
|
|
||||||
|
let formatted = format!(" {loaded}");
|
||||||
|
assert!(formatted.contains(" ***@example.org"));
|
||||||
|
assert!(formatted.contains(" imap:[imap.example.com:123:starttls]"));
|
||||||
|
assert!(formatted.contains(" folder:\"Folder\""));
|
||||||
|
assert!(formatted.contains(" smtp:[smtp.example.com:456:tls]"));
|
||||||
|
assert!(formatted.contains(" provider:none"));
|
||||||
|
assert!(formatted.contains(" cert_strict"));
|
||||||
|
|
||||||
// Legacy ConfiguredImapCertificateChecks config is ignored
|
// Legacy ConfiguredImapCertificateChecks config is ignored
|
||||||
t.set_config(Config::ConfiguredImapCertificateChecks, Some("999"))
|
t.set_config(Config::ConfiguredImapCertificateChecks, Some("999"))
|
||||||
.await?;
|
.await?;
|
||||||
|
|||||||
63
test-data/message/hp_legacy_display.eml
Normal file
63
test-data/message/hp_legacy_display.eml
Normal file
@@ -0,0 +1,63 @@
|
|||||||
|
Content-Type: multipart/encrypted; protocol="application/pgp-encrypted";
|
||||||
|
boundary="18b255ee53144064_9da6abff99cb33df_3854fea559a18923"
|
||||||
|
MIME-Version: 1.0
|
||||||
|
From: <alice@example.org>
|
||||||
|
To: "hidden-recipients": ;
|
||||||
|
Subject: [...]
|
||||||
|
Date: Fri, 22 May 2026 22:48:16 +0000
|
||||||
|
Message-ID: <3a5ae0d2-be4b-4463-8011-ab4a354ee690@localhost>
|
||||||
|
Chat-Version: 1.0
|
||||||
|
|
||||||
|
|
||||||
|
--18b255ee53144064_9da6abff99cb33df_3854fea559a18923
|
||||||
|
Content-Type: application/pgp-encrypted; charset="utf-8"
|
||||||
|
Content-Transfer-Encoding: 7bit
|
||||||
|
|
||||||
|
Version: 1
|
||||||
|
|
||||||
|
--18b255ee53144064_9da6abff99cb33df_3854fea559a18923
|
||||||
|
Content-Type: application/octet-stream; charset="utf-8"
|
||||||
|
Content-Transfer-Encoding: 7bit
|
||||||
|
|
||||||
|
-----BEGIN PGP MESSAGE-----
|
||||||
|
|
||||||
|
wUcGABIBB0BNBleU/G9GwCpizow72+5GANtiX+F7y/gI45x7MJLpFiBi+7OqwdFk
|
||||||
|
dmINorerXmnk1jR7TEppDYhjCBRWaCXHJsHARQYAAQf/Zj43ZUA3rtGS6Nq9vUcc
|
||||||
|
0L6knU0ukLc3KGTExQXu4ktiPT0RIjQ11wtSGlS75Bj7tu3syDf2/oQXi7eUh35T
|
||||||
|
IULpBQ5Pnpk6h26GX9WbRYR5zmBNnlyppqJcaYueQzPX1gvybJ4cwfOmnMdUqzFr
|
||||||
|
D9BR/YeyrBETsGKR+UfH167xgGY8d0ev8Tt4X01muiyrwLVqfoGCRUwjQFUY+ClY
|
||||||
|
9yuCrNDnu8fHY/WRTKXoDkA8Qn9/6TdJM7G41wOaJYbWn4R8WqjqoQNdUYwL2cBg
|
||||||
|
Pj1SNgaTaLMMH8YEtlRUN3JHiiqaBVIJIKBduOXnVEovf8FVXlmqX8DARLvqFCk2
|
||||||
|
LdLEjQIHAgdxw8asmmKOpatAEwFHuVwkWe/ZbRhu3D1V38BzZEuSLHzQ/nWbWWVP
|
||||||
|
GRmcZPmpusvRGX5W4zC6ybr5jePkW1dEqpgFFeoywz5REQxnl2QDPMHbWRFmC+q5
|
||||||
|
FSA/zacYGIKmU761xWc1raaosN4uzf8ujJA8H+TQT4AKu4K2nzSMFpUl05sNToGN
|
||||||
|
FdCNAvsnM9/PAraE3pju6YK+l4Woo/MLVEkrDEcLYEC/TrbNCJ6cnx/3uU+9gZyp
|
||||||
|
ntXzwOJIIQ5el2qcSQuzec931Lr4UuX4k0FUKeLS24EiYid4K59iPHgKJn6E7grb
|
||||||
|
34bxbQXkIMug0nTBKuAOTv7MFF4/rzn5deAUXpKty5zYLjqGApo4o5nnEvY9inXP
|
||||||
|
3489C77lCTLuXgkASrYhqTZcFT6ewaLNJxKRQgLB/V8BY4DrckA/If31z57EK2Ju
|
||||||
|
5kXpUfZKw/qjnHELLLSmYqx0Sd6w37d0x4LxHIjnpBEmsjWagM9fhiBJyaitNfCz
|
||||||
|
hBEUAQoO2DlqrZMwAhKxADH8WUbXOhgXcj68NM185A+/UzMbbdV0cmLKynH3tlLX
|
||||||
|
d16ZyriiRcllSuISXYLTsUwlQeA0zIyuYItzk7bGkw/2k4AmxFAFZjZPvaHOrt98
|
||||||
|
BapEkxYp8wyhXdloknJ687E5Oa4UTsMKzRTrXRiPhVNstYcYnde4fZs0cUX0JJqE
|
||||||
|
bZrR+T8biflfGWidc0D0cSd2rx6RryvykFBzvZJH4zqNaHap3w5F0miZWfrePQ8Y
|
||||||
|
ljPCb162x3SAHc5RzhHH7tq4YatWlnncSAuGkZA7XMqBgFbIRdY2z+Hv5AQLj563
|
||||||
|
OHD0Dc0pXUZYcHVdJnY4svuiekjEYLaGzjklULBsQo4KQXb8i3SufS8JUwypwAT1
|
||||||
|
BzMWYQrT6cv3IQHtF9Yys6Z3ngYs9UoCIyqPHYFZYV8Y64injpBFtiY6kd0zvm/3
|
||||||
|
5D5c0DcmI6kq0LNNNzZg5F0oMoDvyny1pI5/IGxS1H3OyBNJlNZkftMadyvDydY7
|
||||||
|
khCjWzB9mTStrmeYVqJof/NyfMWJMa0NtJrqW+730FYnld95cGzf3gnRsTYd/CgD
|
||||||
|
vXVcj5F2/fxsDUekmMMAYBB3DCBv0yCJJfqaTgQKcwngOL0+kuGVnjT8qfjf0FQR
|
||||||
|
yzImEPJFTQFe+mpRALBoMSDRpzja6ZkU/AIOTT/xy1ZkfpLU/TpNk7XS6InsIjdH
|
||||||
|
wBzfPvDUV7hHWwgILE+DTlebZVEuVBuoKTM72paMomYzskTkCGnIkTxioA1SyJxJ
|
||||||
|
eOsSlNC30Le6rcbvz+kl+H7cW2RGZuCUub/UtCMlcdjlzEVAjwXgQZgys2mAwLPx
|
||||||
|
n61I6rO+eEphXr5F/S6/0NwomR/02FY5AmqeViKC0G6/bDJOO374vU3bix28xqeB
|
||||||
|
yYYeO+ZymhjqA7tLTEjrkrCh0vo1WoKSAWbhVqM9YUzAeusqyndZ2uLbTgCEayER
|
||||||
|
MviI27DrXTDpExV9qLyELpo0z18kwoFScJ5ULXDN4lpwfsCeg3ThvBJD86V+4faT
|
||||||
|
4neKUwzasjYvuzKqSRV4j89I5Bs1g8ERaQr3VZ4BnyjKxQ91eLsMDD+LMx2QW2lB
|
||||||
|
HPUTWc4qmSAItogwZRxGUj+dASjcuGwqVhabUxWpxPsVGdOp6D3au1M3k7EuvLLa
|
||||||
|
NmnDDyNRSU8d7Mc/Tp9swfPKd2ItI4cREXTpyu53ayzSD5/7LaDtpHUASdaDLgDv
|
||||||
|
2evlUTBRk2a8+trbhE0oy8zzRdbgiNrwcegGM+y5BYCdV5Gooerk4g8pnULtKT1R
|
||||||
|
VgWMKvXvFduvQsfXW+PKChw=
|
||||||
|
=EBeW
|
||||||
|
-----END PGP MESSAGE-----
|
||||||
|
|
||||||
|
--18b255ee53144064_9da6abff99cb33df_3854fea559a18923--
|
||||||
Reference in New Issue
Block a user