mirror of
https://github.com/chatmail/core.git
synced 2026-04-07 08:02:11 +03:00
Compare commits
12 Commits
link2xt/ra
...
v1.143.0
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
0c33064193 | ||
|
|
61d77584e8 | ||
|
|
37ca9d7319 | ||
|
|
2c136f6355 | ||
|
|
52dcc7e350 | ||
|
|
ff6488371c | ||
|
|
0782b5abdd | ||
|
|
2e2ba96d75 | ||
|
|
853e38e054 | ||
|
|
418dfbf994 | ||
|
|
533a872118 | ||
|
|
2ae854e8ea |
8
.github/workflows/ci.yml
vendored
8
.github/workflows/ci.yml
vendored
@@ -24,7 +24,7 @@ jobs:
|
||||
name: Lint Rust
|
||||
runs-on: ubuntu-latest
|
||||
env:
|
||||
RUSTUP_TOOLCHAIN: 1.80.1
|
||||
RUSTUP_TOOLCHAIN: 1.81.0
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
with:
|
||||
@@ -95,11 +95,11 @@ jobs:
|
||||
matrix:
|
||||
include:
|
||||
- os: ubuntu-latest
|
||||
rust: 1.80.1
|
||||
rust: 1.81.0
|
||||
- os: windows-latest
|
||||
rust: 1.80.1
|
||||
rust: 1.81.0
|
||||
- os: macos-latest
|
||||
rust: 1.80.1
|
||||
rust: 1.81.0
|
||||
|
||||
# Minimum Supported Rust Version = 1.77.0
|
||||
- os: ubuntu-latest
|
||||
|
||||
50
CHANGELOG.md
50
CHANGELOG.md
@@ -1,5 +1,54 @@
|
||||
# Changelog
|
||||
|
||||
## [1.143.0] - 2024-09-12
|
||||
|
||||
### Features / Changes
|
||||
|
||||
- Automatic reconfiguration, e.g. switching to implicit TLS if STARTTLS port stops working.
|
||||
- Always use preloaded DNS results.
|
||||
- Add "Auto-Submitted: auto-replied" header to appropriate SecureJoin messages.
|
||||
- Parallelize IMAP and SMTP connection attempts ([#5915](https://github.com/deltachat/deltachat-core-rust/pull/5915)).
|
||||
- securejoin: Ignore invalid *-request-with-auth messages silently.
|
||||
- ChatId::create_for_contact_with_blocked: Don't emit events on no op.
|
||||
- Delete messages from a chatmail server immediately by default ([#5805](https://github.com/deltachat/deltachat-core-rust/pull/5805)) ([#5840](https://github.com/deltachat/deltachat-core-rust/pull/5840)).
|
||||
- Shadowsocks support.
|
||||
- Recognize t.me SOCKS5 proxy QR codes ([#5895](https://github.com/deltachat/deltachat-core-rust/pull/5895))
|
||||
- Remove old iroh 0.4 and support for old `DCBACKUP` QR codes.
|
||||
|
||||
### Fixes
|
||||
|
||||
- http: Set I/O timeout to 1 minute rather than whole request timeout.
|
||||
- Add Auto-Submitted header in a single place.
|
||||
- Do not allow quotes with "... wrote:" headers in chat messages.
|
||||
- Don't sync QR code token before populating the group ([#5935](https://github.com/deltachat/deltachat-core-rust/pull/5935)).
|
||||
|
||||
### Documentation
|
||||
|
||||
- Document that `bcc_self` is enabled by default.
|
||||
|
||||
### CI
|
||||
|
||||
- Update Rust to 1.81.0.
|
||||
|
||||
### Miscellaneous Tasks
|
||||
|
||||
- Update provider database.
|
||||
- cargo: Update iroh to 0.23.0.
|
||||
- cargo: Reduce number of duplicate dependencies.
|
||||
- cargo: Replace unmaintained ansi_term with nu-ansi-term.
|
||||
- Replace `reqwest` with direct usage of `hyper`.
|
||||
|
||||
### Refactor
|
||||
|
||||
- login_param: Use Config:: constants to avoid typos in key names.
|
||||
- Make Context::config_exists() crate-public.
|
||||
- Get_config_bool_opt(): Return None if only default value exists.
|
||||
|
||||
### Tests
|
||||
|
||||
- Test that alternative port 443 works.
|
||||
- Alice is (non-)bot on Bob's side after QR contact setup.
|
||||
|
||||
## [1.142.12] - 2024-09-02
|
||||
|
||||
### Fixes
|
||||
@@ -4806,3 +4855,4 @@ https://github.com/deltachat/deltachat-core-rust/pulls?q=is%3Apr+is%3Aclosed
|
||||
[1.142.10]: https://github.com/deltachat/deltachat-core-rust/compare/v1.142.9..v1.142.10
|
||||
[1.142.11]: https://github.com/deltachat/deltachat-core-rust/compare/v1.142.10..v1.142.11
|
||||
[1.142.12]: https://github.com/deltachat/deltachat-core-rust/compare/v1.142.11..v1.142.12
|
||||
[1.143.0]: https://github.com/deltachat/deltachat-core-rust/compare/v1.142.12..v1.143.0
|
||||
|
||||
353
Cargo.lock
generated
353
Cargo.lock
generated
@@ -551,6 +551,19 @@ dependencies = [
|
||||
"digest",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "blake3"
|
||||
version = "1.5.4"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "d82033247fd8e890df8f740e407ad4d038debb9eb1f40533fffb32e7d17dc6f7"
|
||||
dependencies = [
|
||||
"arrayref",
|
||||
"arrayvec",
|
||||
"cc",
|
||||
"cfg-if",
|
||||
"constant_time_eq",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "block-buffer"
|
||||
version = "0.10.4"
|
||||
@@ -631,6 +644,12 @@ version = "3.15.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "8ea184aa71bb362a1157c896979544cc23974e08fd265f29ea96b59f0b4a555b"
|
||||
|
||||
[[package]]
|
||||
name = "byte_string"
|
||||
version = "1.0.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "11aade7a05aa8c3a351cedc44c3fc45806430543382fcc4743a9b757a2a0b4ed"
|
||||
|
||||
[[package]]
|
||||
name = "bytemuck"
|
||||
version = "1.16.3"
|
||||
@@ -716,9 +735,12 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "cc"
|
||||
version = "1.1.7"
|
||||
version = "1.1.16"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "26a5c3fd7bfa1ce3897a3a3501d362b2d87b7f2583ebcb4a949ec25911025cbc"
|
||||
checksum = "e9d013ecb737093c0e86b151a7b837993cf9ec6c502946cfb44bedc392421e0b"
|
||||
dependencies = [
|
||||
"shlex",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "cfb-mode"
|
||||
@@ -752,6 +774,19 @@ dependencies = [
|
||||
"cpufeatures",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "chacha20poly1305"
|
||||
version = "0.10.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "10cd79432192d1c0f4e1a0fef9527696cc039165d729fb41b3f4f4f354c2dc35"
|
||||
dependencies = [
|
||||
"aead",
|
||||
"chacha20",
|
||||
"cipher",
|
||||
"poly1305",
|
||||
"zeroize",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "charset"
|
||||
version = "0.1.3"
|
||||
@@ -774,7 +809,7 @@ dependencies = [
|
||||
"num-traits",
|
||||
"serde",
|
||||
"wasm-bindgen",
|
||||
"windows-targets 0.52.5",
|
||||
"windows-targets 0.52.6",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -1142,70 +1177,35 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "darling"
|
||||
version = "0.13.4"
|
||||
version = "0.20.10"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "a01d95850c592940db9b8194bc39f4bc0e89dee5c4265e4b1807c34a9aba453c"
|
||||
checksum = "6f63b86c8a8826a49b8c21f08a2d07338eec8d900540f8630dc76284be802989"
|
||||
dependencies = [
|
||||
"darling_core 0.13.4",
|
||||
"darling_macro 0.13.4",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "darling"
|
||||
version = "0.20.9"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "83b2eb4d90d12bdda5ed17de686c2acb4c57914f8f921b8da7e112b5a36f3fe1"
|
||||
dependencies = [
|
||||
"darling_core 0.20.9",
|
||||
"darling_macro 0.20.9",
|
||||
"darling_core",
|
||||
"darling_macro",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "darling_core"
|
||||
version = "0.13.4"
|
||||
version = "0.20.10"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "859d65a907b6852c9361e3185c862aae7fafd2887876799fa55f5f99dc40d610"
|
||||
checksum = "95133861a8032aaea082871032f5815eb9e98cef03fa916ab4500513994df9e5"
|
||||
dependencies = [
|
||||
"fnv",
|
||||
"ident_case",
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"strsim 0.10.0",
|
||||
"syn 1.0.109",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "darling_core"
|
||||
version = "0.20.9"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "622687fe0bac72a04e5599029151f5796111b90f1baaa9b544d807a5e31cd120"
|
||||
dependencies = [
|
||||
"fnv",
|
||||
"ident_case",
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"strsim 0.11.1",
|
||||
"strsim",
|
||||
"syn 2.0.77",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "darling_macro"
|
||||
version = "0.13.4"
|
||||
version = "0.20.10"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "9c972679f83bdf9c42bd905396b6c3588a843a17f0f16dfcfa3e2c5d57441835"
|
||||
checksum = "d336a2a514f6ccccaa3e09b02d41d35330c07ddf03a62165fcec10bb561c7806"
|
||||
dependencies = [
|
||||
"darling_core 0.13.4",
|
||||
"quote",
|
||||
"syn 1.0.109",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "darling_macro"
|
||||
version = "0.20.9"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "733cabb43482b1a1b53eee8583c2b9e8684d592215ea83efd305dd31bc2f0178"
|
||||
dependencies = [
|
||||
"darling_core 0.20.9",
|
||||
"darling_core",
|
||||
"quote",
|
||||
"syn 2.0.77",
|
||||
]
|
||||
@@ -1240,7 +1240,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "deltachat"
|
||||
version = "1.142.12"
|
||||
version = "1.143.0"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"async-broadcast",
|
||||
@@ -1288,6 +1288,7 @@ dependencies = [
|
||||
"parking_lot",
|
||||
"percent-encoding",
|
||||
"pgp",
|
||||
"pin-project",
|
||||
"pretty_assertions",
|
||||
"proptest",
|
||||
"qrcodegen",
|
||||
@@ -1303,6 +1304,7 @@ dependencies = [
|
||||
"serde_json",
|
||||
"serde_urlencoded",
|
||||
"sha-1",
|
||||
"shadowsocks",
|
||||
"smallvec",
|
||||
"strum",
|
||||
"strum_macros",
|
||||
@@ -1334,7 +1336,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "deltachat-jsonrpc"
|
||||
version = "1.142.12"
|
||||
version = "1.143.0"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"async-channel 2.3.1",
|
||||
@@ -1359,7 +1361,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "deltachat-repl"
|
||||
version = "1.142.12"
|
||||
version = "1.143.0"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"deltachat",
|
||||
@@ -1374,7 +1376,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "deltachat-rpc-server"
|
||||
version = "1.142.12"
|
||||
version = "1.143.0"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"deltachat",
|
||||
@@ -1403,7 +1405,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "deltachat_ffi"
|
||||
version = "1.142.12"
|
||||
version = "1.143.0"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"deltachat",
|
||||
@@ -1481,7 +1483,7 @@ version = "0.20.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "d48cda787f839151732d396ac69e3473923d54312c070ee21e9effcaa8ca0b1d"
|
||||
dependencies = [
|
||||
"darling 0.20.9",
|
||||
"darling",
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn 2.0.77",
|
||||
@@ -3066,7 +3068,7 @@ dependencies = [
|
||||
"iroh-quinn-proto",
|
||||
"iroh-quinn-udp",
|
||||
"pin-project-lite",
|
||||
"rustc-hash",
|
||||
"rustc-hash 1.1.0",
|
||||
"rustls 0.21.11",
|
||||
"thiserror",
|
||||
"tokio",
|
||||
@@ -3082,7 +3084,7 @@ dependencies = [
|
||||
"bytes",
|
||||
"rand 0.8.5",
|
||||
"ring",
|
||||
"rustc-hash",
|
||||
"rustc-hash 1.1.0",
|
||||
"rustls 0.21.11",
|
||||
"rustls-native-certs",
|
||||
"slab",
|
||||
@@ -3294,6 +3296,12 @@ dependencies = [
|
||||
"linked-hash-map",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "lru_time_cache"
|
||||
version = "0.11.11"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "9106e1d747ffd48e6be5bb2d97fa706ed25b144fbee4d5c02eae110cd8d6badd"
|
||||
|
||||
[[package]]
|
||||
name = "mailparse"
|
||||
version = "0.15.0"
|
||||
@@ -4335,34 +4343,32 @@ dependencies = [
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "proc-macro-error"
|
||||
version = "1.0.4"
|
||||
name = "proc-macro-error-attr2"
|
||||
version = "2.0.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "da25490ff9892aab3fcf7c36f08cfb902dd3e71ca0f9f9517bea02a73a5ce38c"
|
||||
checksum = "96de42df36bb9bba5542fe9f1a054b8cc87e172759a1868aa05c1f3acc89dfc5"
|
||||
dependencies = [
|
||||
"proc-macro-error-attr",
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn 1.0.109",
|
||||
"version_check",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "proc-macro-error-attr"
|
||||
version = "1.0.4"
|
||||
name = "proc-macro-error2"
|
||||
version = "2.0.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "a1be40180e52ecc98ad80b184934baf3d0d29f979574e439af5a55274b35f869"
|
||||
checksum = "11ec05c52be0a07b08061f7dd003e7d7092e0472bc731b4af7bb1ef876109802"
|
||||
dependencies = [
|
||||
"proc-macro-error-attr2",
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"version_check",
|
||||
"syn 2.0.77",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "proc-macro2"
|
||||
version = "1.0.85"
|
||||
version = "1.0.86"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "22244ce15aa966053a896d1accb3a6e68469b97c7f33f284b99f0d576879fc23"
|
||||
checksum = "5e719e8df665df0d1c8fbfd238015744736151d4445ec0836b8e628aae103b77"
|
||||
dependencies = [
|
||||
"unicode-ident",
|
||||
]
|
||||
@@ -4452,7 +4458,7 @@ dependencies = [
|
||||
"pin-project-lite",
|
||||
"quinn-proto",
|
||||
"quinn-udp",
|
||||
"rustc-hash",
|
||||
"rustc-hash 1.1.0",
|
||||
"rustls 0.23.10",
|
||||
"thiserror",
|
||||
"tokio",
|
||||
@@ -4461,14 +4467,14 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "quinn-proto"
|
||||
version = "0.11.3"
|
||||
version = "0.11.8"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "ddf517c03a109db8100448a4be38d498df8a210a99fe0e1b9eaf39e78c640efe"
|
||||
checksum = "fadfaed2cd7f389d0161bb73eeb07b7b78f8691047a6f3e73caaeae55310a4a6"
|
||||
dependencies = [
|
||||
"bytes",
|
||||
"rand 0.8.5",
|
||||
"ring",
|
||||
"rustc-hash",
|
||||
"rustc-hash 2.0.0",
|
||||
"rustls 0.23.10",
|
||||
"slab",
|
||||
"thiserror",
|
||||
@@ -4811,6 +4817,25 @@ dependencies = [
|
||||
"windows-sys 0.52.0",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "ring-compat"
|
||||
version = "0.8.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "ccce7bae150b815f0811db41b8312fcb74bffa4cab9cee5429ee00f356dd5bd4"
|
||||
dependencies = [
|
||||
"aead",
|
||||
"digest",
|
||||
"ecdsa",
|
||||
"ed25519",
|
||||
"generic-array",
|
||||
"p256",
|
||||
"p384",
|
||||
"pkcs8",
|
||||
"rand_core 0.6.4",
|
||||
"ring",
|
||||
"signature",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "ripemd"
|
||||
version = "0.1.3"
|
||||
@@ -4891,6 +4916,12 @@ version = "1.1.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "08d43f7aa6b08d49f382cde6a7982047c3426db949b1424bc4b7ec9ae12c6ce2"
|
||||
|
||||
[[package]]
|
||||
name = "rustc-hash"
|
||||
version = "2.0.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "583034fd73374156e66797ed8e5b0d5690409c9226b22d87cb7f19821c05d152"
|
||||
|
||||
[[package]]
|
||||
name = "rustc_version"
|
||||
version = "0.4.0"
|
||||
@@ -5170,6 +5201,16 @@ dependencies = [
|
||||
"serde",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "sendfd"
|
||||
version = "0.4.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "604b71b8fc267e13bb3023a2c901126c8f349393666a6d98ac1ae5729b701798"
|
||||
dependencies = [
|
||||
"libc",
|
||||
"tokio",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "serde"
|
||||
version = "1.0.209"
|
||||
@@ -5212,9 +5253,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "serde_json"
|
||||
version = "1.0.127"
|
||||
version = "1.0.128"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "8043c06d9f82bd7271361ed64f415fe5e12a77fdb52e573e7f06a516dea329ad"
|
||||
checksum = "6ff5456707a1de34e7e37f2a6fd3d3f808c318259cbd01ab6377795054b483d8"
|
||||
dependencies = [
|
||||
"itoa",
|
||||
"memchr",
|
||||
@@ -5316,6 +5357,60 @@ dependencies = [
|
||||
"keccak",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "shadowsocks"
|
||||
version = "1.20.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "06b6af20f0f009894644c9fb149ce6244c69b0a264ffcf7a53cbb3dd4883e4a3"
|
||||
dependencies = [
|
||||
"aes",
|
||||
"async-trait",
|
||||
"base64 0.22.1",
|
||||
"blake3",
|
||||
"byte_string",
|
||||
"bytes",
|
||||
"cfg-if",
|
||||
"futures",
|
||||
"libc",
|
||||
"log",
|
||||
"lru_time_cache",
|
||||
"once_cell",
|
||||
"percent-encoding",
|
||||
"pin-project",
|
||||
"rand 0.8.5",
|
||||
"sendfd",
|
||||
"serde",
|
||||
"serde_json",
|
||||
"serde_urlencoded",
|
||||
"shadowsocks-crypto",
|
||||
"socket2",
|
||||
"spin 0.9.8",
|
||||
"thiserror",
|
||||
"tokio",
|
||||
"tokio-tfo",
|
||||
"url",
|
||||
"windows-sys 0.59.0",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "shadowsocks-crypto"
|
||||
version = "0.5.5"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "a9e49ecfad8b27f3df28848af11f08aa10df0c6b74b45748131753913be23373"
|
||||
dependencies = [
|
||||
"aes",
|
||||
"aes-gcm",
|
||||
"blake3",
|
||||
"bytes",
|
||||
"cfg-if",
|
||||
"chacha20poly1305",
|
||||
"hkdf",
|
||||
"md-5",
|
||||
"rand 0.8.5",
|
||||
"ring-compat",
|
||||
"sha1",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "sharded-slab"
|
||||
version = "0.1.7"
|
||||
@@ -5335,6 +5430,12 @@ dependencies = [
|
||||
"winapi",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "shlex"
|
||||
version = "1.3.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "0fda2ff0d084019ba4d7c6f371c95d8fd75ce3524c3cb8fb653a3023f6323e64"
|
||||
|
||||
[[package]]
|
||||
name = "signal-hook-registry"
|
||||
version = "1.4.1"
|
||||
@@ -5488,12 +5589,6 @@ dependencies = [
|
||||
"pin-project-lite",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "strsim"
|
||||
version = "0.10.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "73473c0e59e6d5812c5dfe2a064a6444949f089e20eec9a2e5506596494e4623"
|
||||
|
||||
[[package]]
|
||||
name = "strsim"
|
||||
version = "0.11.1"
|
||||
@@ -5911,6 +6006,23 @@ dependencies = [
|
||||
"xattr",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "tokio-tfo"
|
||||
version = "0.3.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "3fb4382c6371e29365853d2b71e915d5398df46312a2158097d8bb3f54d0f1b4"
|
||||
dependencies = [
|
||||
"cfg-if",
|
||||
"futures",
|
||||
"libc",
|
||||
"log",
|
||||
"once_cell",
|
||||
"pin-project",
|
||||
"socket2",
|
||||
"tokio",
|
||||
"windows-sys 0.52.0",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "tokio-tungstenite"
|
||||
version = "0.21.0"
|
||||
@@ -6129,9 +6241,9 @@ checksum = "42ff0bf0c66b8238c6f3b578df37d0b7848e55df8577b3f74f92a69acceeb825"
|
||||
|
||||
[[package]]
|
||||
name = "typescript-type-def"
|
||||
version = "0.5.11"
|
||||
version = "0.5.12"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "19d9560109be8840693dcb09ed745851f3713bee7b23534b8cab65b7ff9383f1"
|
||||
checksum = "3c9a1ec7a0e59e03c7ab74e924abef19d2669aa9255b9821e55854b08453eb71"
|
||||
dependencies = [
|
||||
"serde_json",
|
||||
"typescript-type-def-derive",
|
||||
@@ -6139,16 +6251,16 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "typescript-type-def-derive"
|
||||
version = "0.5.11"
|
||||
version = "0.5.12"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "2835fe6badda3e20a012d19d6593ded0fc11f659d5d5152394061ffbb03b4b04"
|
||||
checksum = "34c1213de2386875250474a109f0985ce83333038c885d2fec4de79d9dfcee8c"
|
||||
dependencies = [
|
||||
"darling 0.13.4",
|
||||
"darling",
|
||||
"ident_case",
|
||||
"proc-macro-error",
|
||||
"proc-macro-error2",
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn 1.0.109",
|
||||
"syn 2.0.77",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -6525,7 +6637,7 @@ dependencies = [
|
||||
"windows-core 0.52.0",
|
||||
"windows-implement",
|
||||
"windows-interface",
|
||||
"windows-targets 0.52.5",
|
||||
"windows-targets 0.52.6",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -6543,7 +6655,7 @@ version = "0.52.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "33ab640c8d7e35bf8ba19b884ba838ceb4fba93a4e8c65a9059d08afcfc683d9"
|
||||
dependencies = [
|
||||
"windows-targets 0.52.5",
|
||||
"windows-targets 0.52.6",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -6583,7 +6695,16 @@ version = "0.52.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "282be5f36a8ce781fad8c8ae18fa3f9beff57ec1b52cb3de0789201425d9a33d"
|
||||
dependencies = [
|
||||
"windows-targets 0.52.5",
|
||||
"windows-targets 0.52.6",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "windows-sys"
|
||||
version = "0.59.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "1e38bc4d79ed67fd075bcc251a1c39b32a1776bbe92e5bef1f0bf1f8c531853b"
|
||||
dependencies = [
|
||||
"windows-targets 0.52.6",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -6603,18 +6724,18 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "windows-targets"
|
||||
version = "0.52.5"
|
||||
version = "0.52.6"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "6f0713a46559409d202e70e28227288446bf7841d3211583a4b53e3f6d96e7eb"
|
||||
checksum = "9b724f72796e036ab90c1021d4780d4d3d648aca59e491e6b98e725b84e99973"
|
||||
dependencies = [
|
||||
"windows_aarch64_gnullvm 0.52.5",
|
||||
"windows_aarch64_msvc 0.52.5",
|
||||
"windows_i686_gnu 0.52.5",
|
||||
"windows_aarch64_gnullvm 0.52.6",
|
||||
"windows_aarch64_msvc 0.52.6",
|
||||
"windows_i686_gnu 0.52.6",
|
||||
"windows_i686_gnullvm",
|
||||
"windows_i686_msvc 0.52.5",
|
||||
"windows_x86_64_gnu 0.52.5",
|
||||
"windows_x86_64_gnullvm 0.52.5",
|
||||
"windows_x86_64_msvc 0.52.5",
|
||||
"windows_i686_msvc 0.52.6",
|
||||
"windows_x86_64_gnu 0.52.6",
|
||||
"windows_x86_64_gnullvm 0.52.6",
|
||||
"windows_x86_64_msvc 0.52.6",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -6625,9 +6746,9 @@ checksum = "2b38e32f0abccf9987a4e3079dfb67dcd799fb61361e53e2882c3cbaf0d905d8"
|
||||
|
||||
[[package]]
|
||||
name = "windows_aarch64_gnullvm"
|
||||
version = "0.52.5"
|
||||
version = "0.52.6"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "7088eed71e8b8dda258ecc8bac5fb1153c5cffaf2578fc8ff5d61e23578d3263"
|
||||
checksum = "32a4622180e7a0ec044bb555404c800bc9fd9ec262ec147edd5989ccd0c02cd3"
|
||||
|
||||
[[package]]
|
||||
name = "windows_aarch64_msvc"
|
||||
@@ -6637,9 +6758,9 @@ checksum = "dc35310971f3b2dbbf3f0690a219f40e2d9afcf64f9ab7cc1be722937c26b4bc"
|
||||
|
||||
[[package]]
|
||||
name = "windows_aarch64_msvc"
|
||||
version = "0.52.5"
|
||||
version = "0.52.6"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "9985fd1504e250c615ca5f281c3f7a6da76213ebd5ccc9561496568a2752afb6"
|
||||
checksum = "09ec2a7bb152e2252b53fa7803150007879548bc709c039df7627cabbd05d469"
|
||||
|
||||
[[package]]
|
||||
name = "windows_i686_gnu"
|
||||
@@ -6649,15 +6770,15 @@ checksum = "a75915e7def60c94dcef72200b9a8e58e5091744960da64ec734a6c6e9b3743e"
|
||||
|
||||
[[package]]
|
||||
name = "windows_i686_gnu"
|
||||
version = "0.52.5"
|
||||
version = "0.52.6"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "88ba073cf16d5372720ec942a8ccbf61626074c6d4dd2e745299726ce8b89670"
|
||||
checksum = "8e9b5ad5ab802e97eb8e295ac6720e509ee4c243f69d781394014ebfe8bbfa0b"
|
||||
|
||||
[[package]]
|
||||
name = "windows_i686_gnullvm"
|
||||
version = "0.52.5"
|
||||
version = "0.52.6"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "87f4261229030a858f36b459e748ae97545d6f1ec60e5e0d6a3d32e0dc232ee9"
|
||||
checksum = "0eee52d38c090b3caa76c563b86c3a4bd71ef1a819287c19d586d7334ae8ed66"
|
||||
|
||||
[[package]]
|
||||
name = "windows_i686_msvc"
|
||||
@@ -6667,9 +6788,9 @@ checksum = "8f55c233f70c4b27f66c523580f78f1004e8b5a8b659e05a4eb49d4166cca406"
|
||||
|
||||
[[package]]
|
||||
name = "windows_i686_msvc"
|
||||
version = "0.52.5"
|
||||
version = "0.52.6"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "db3c2bf3d13d5b658be73463284eaf12830ac9a26a90c717b7f771dfe97487bf"
|
||||
checksum = "240948bc05c5e7c6dabba28bf89d89ffce3e303022809e73deaefe4f6ec56c66"
|
||||
|
||||
[[package]]
|
||||
name = "windows_x86_64_gnu"
|
||||
@@ -6679,9 +6800,9 @@ checksum = "53d40abd2583d23e4718fddf1ebec84dbff8381c07cae67ff7768bbf19c6718e"
|
||||
|
||||
[[package]]
|
||||
name = "windows_x86_64_gnu"
|
||||
version = "0.52.5"
|
||||
version = "0.52.6"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "4e4246f76bdeff09eb48875a0fd3e2af6aada79d409d33011886d3e1581517d9"
|
||||
checksum = "147a5c80aabfbf0c7d901cb5895d1de30ef2907eb21fbbab29ca94c5b08b1a78"
|
||||
|
||||
[[package]]
|
||||
name = "windows_x86_64_gnullvm"
|
||||
@@ -6691,9 +6812,9 @@ checksum = "0b7b52767868a23d5bab768e390dc5f5c55825b6d30b86c844ff2dc7414044cc"
|
||||
|
||||
[[package]]
|
||||
name = "windows_x86_64_gnullvm"
|
||||
version = "0.52.5"
|
||||
version = "0.52.6"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "852298e482cd67c356ddd9570386e2862b5673c85bd5f88df9ab6802b334c596"
|
||||
checksum = "24d5b23dc417412679681396f2b49f3de8c1473deb516bd34410872eff51ed0d"
|
||||
|
||||
[[package]]
|
||||
name = "windows_x86_64_msvc"
|
||||
@@ -6703,9 +6824,9 @@ checksum = "ed94fce61571a4006852b7389a063ab983c02eb1bb37b47f8272ce92d06d9538"
|
||||
|
||||
[[package]]
|
||||
name = "windows_x86_64_msvc"
|
||||
version = "0.52.5"
|
||||
version = "0.52.6"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "bec47e5bfd1bff0eeaf6d8b485cc1074891a197ab4225d504cb7a1ab88b02bf0"
|
||||
checksum = "589f6da84c646204747d1270a2a5661ea66ed1cced2631d546fdfb155959f9ec"
|
||||
|
||||
[[package]]
|
||||
name = "winnow"
|
||||
@@ -6859,7 +6980,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "35ba6b4417cfeb26cd806f3aabc22e7c4097632e07b5b61a4c818bccb2df4f21"
|
||||
dependencies = [
|
||||
"convert_case",
|
||||
"darling 0.20.9",
|
||||
"darling",
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn 2.0.77",
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
[package]
|
||||
name = "deltachat"
|
||||
version = "1.142.12"
|
||||
version = "1.143.0"
|
||||
edition = "2021"
|
||||
license = "MPL-2.0"
|
||||
rust-version = "1.77"
|
||||
@@ -77,6 +77,7 @@ once_cell = { workspace = true }
|
||||
percent-encoding = "2.3"
|
||||
parking_lot = "0.12"
|
||||
pgp = { version = "0.13.2", default-features = false }
|
||||
pin-project = "1"
|
||||
qrcodegen = "1.7.0"
|
||||
quick-xml = "0.36"
|
||||
quoted_printable = "0.5"
|
||||
@@ -89,6 +90,7 @@ serde_json = { workspace = true }
|
||||
serde_urlencoded = "0.7.1"
|
||||
serde = { workspace = true, features = ["derive"] }
|
||||
sha-1 = "0.10"
|
||||
shadowsocks = { version = "1.20.2", default-features = false, features = ["aead-cipher-2022"] }
|
||||
smallvec = "1.13.2"
|
||||
strum = "0.26"
|
||||
strum_macros = "0.26"
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
[package]
|
||||
name = "deltachat_ffi"
|
||||
version = "1.142.12"
|
||||
version = "1.143.0"
|
||||
description = "Deltachat FFI"
|
||||
edition = "2018"
|
||||
readme = "README.md"
|
||||
|
||||
@@ -403,11 +403,8 @@ char* dc_get_blobdir (const dc_context_t* context);
|
||||
* - `send_port` = SMTP-port, guessed if left out
|
||||
* - `send_security`= SMTP-socket, one of @ref DC_SOCKET, defaults to #DC_SOCKET_AUTO
|
||||
* - `server_flags` = IMAP-/SMTP-flags as a combination of @ref DC_LP flags, guessed if left out
|
||||
* - `socks5_enabled` = SOCKS5 enabled
|
||||
* - `socks5_host` = SOCKS5 proxy server host
|
||||
* - `socks5_port` = SOCKS5 proxy server port
|
||||
* - `socks5_user` = SOCKS5 proxy username
|
||||
* - `socks5_password` = SOCKS5 proxy password
|
||||
* - `proxy_enabled` = Proxy enabled. Disabled by default.
|
||||
* - `proxy_url` = Proxy URL. May contain multiple URLs separated by newline, but only the first one is used.
|
||||
* - `imap_certificate_checks` = how to check IMAP certificates, one of the @ref DC_CERTCK flags, defaults to #DC_CERTCK_AUTO (0)
|
||||
* - `smtp_certificate_checks` = deprecated option, should be set to the same value as `imap_certificate_checks` but ignored by the new core
|
||||
* - `displayname` = Own name to use when sending messages. MUAs are allowed to spread this way e.g. using CC, defaults to empty
|
||||
|
||||
@@ -4537,19 +4537,16 @@ pub unsafe extern "C" fn dc_provider_new_from_email_with_dns(
|
||||
let addr = to_string_lossy(addr);
|
||||
|
||||
let ctx = &*context;
|
||||
let socks5_enabled = block_on(async move {
|
||||
ctx.get_config_bool(config::Config::Socks5Enabled)
|
||||
.await
|
||||
.context("Can't get config")
|
||||
.log_err(ctx)
|
||||
});
|
||||
let proxy_enabled = block_on(ctx.get_config_bool(config::Config::ProxyEnabled))
|
||||
.context("Can't get config")
|
||||
.log_err(ctx);
|
||||
|
||||
match socks5_enabled {
|
||||
Ok(socks5_enabled) => {
|
||||
match proxy_enabled {
|
||||
Ok(proxy_enabled) => {
|
||||
match block_on(provider::get_provider_info_by_addr(
|
||||
ctx,
|
||||
addr.as_str(),
|
||||
socks5_enabled,
|
||||
proxy_enabled,
|
||||
))
|
||||
.log_err(ctx)
|
||||
.unwrap_or_default()
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
[package]
|
||||
name = "deltachat-jsonrpc"
|
||||
version = "1.142.12"
|
||||
version = "1.143.0"
|
||||
description = "DeltaChat JSON-RPC API"
|
||||
edition = "2021"
|
||||
default-run = "deltachat-jsonrpc-server"
|
||||
@@ -25,7 +25,7 @@ async-channel = { workspace = true }
|
||||
futures = { workspace = true }
|
||||
serde_json = { workspace = true }
|
||||
yerpc = { workspace = true, features = ["anyhow_expose", "openrpc"] }
|
||||
typescript-type-def = { version = "0.5.8", features = ["json_value"] }
|
||||
typescript-type-def = { version = "0.5.12", features = ["json_value"] }
|
||||
tokio = { workspace = true }
|
||||
sanitize-filename = { workspace = true }
|
||||
walkdir = "2.5.0"
|
||||
|
||||
@@ -321,12 +321,12 @@ impl CommandApi {
|
||||
) -> Result<Option<ProviderInfo>> {
|
||||
let ctx = self.get_context(account_id).await?;
|
||||
|
||||
let socks5_enabled = ctx
|
||||
.get_config_bool(deltachat::config::Config::Socks5Enabled)
|
||||
let proxy_enabled = ctx
|
||||
.get_config_bool(deltachat::config::Config::ProxyEnabled)
|
||||
.await?;
|
||||
|
||||
let provider_info =
|
||||
get_provider_info(&ctx, email.split('@').last().unwrap_or(""), socks5_enabled).await;
|
||||
get_provider_info(&ctx, email.split('@').last().unwrap_or(""), proxy_enabled).await;
|
||||
Ok(ProviderInfo::from_dc_type(provider_info))
|
||||
}
|
||||
|
||||
|
||||
@@ -83,7 +83,7 @@ mod tests {
|
||||
assert_eq!(result, response.to_owned());
|
||||
}
|
||||
{
|
||||
let request = r#"{"jsonrpc":"2.0","method":"batch_set_config","id":2,"params":[1,{"addr":"","mail_user":"","mail_pw":"","mail_server":"","mail_port":"","mail_security":"","imap_certificate_checks":"","send_user":"","send_pw":"","send_server":"","send_port":"","send_security":"","smtp_certificate_checks":"","socks5_enabled":"0","socks5_host":"","socks5_port":"","socks5_user":"","socks5_password":""}]}"#;
|
||||
let request = r#"{"jsonrpc":"2.0","method":"batch_set_config","id":2,"params":[1,{"addr":"","mail_user":"","mail_pw":"","mail_server":"","mail_port":"","mail_security":"","imap_certificate_checks":"","send_user":"","send_pw":"","send_server":"","send_port":"","send_security":"","smtp_certificate_checks":""}]}"#;
|
||||
let response = r#"{"jsonrpc":"2.0","id":2,"result":null}"#;
|
||||
session.handle_incoming(request).await;
|
||||
let result = receiver.recv().await?;
|
||||
|
||||
@@ -58,5 +58,5 @@
|
||||
},
|
||||
"type": "module",
|
||||
"types": "dist/deltachat.d.ts",
|
||||
"version": "1.142.12"
|
||||
"version": "1.143.0"
|
||||
}
|
||||
|
||||
@@ -1,43 +1,57 @@
|
||||
//! # Rate limiting module.
|
||||
//!
|
||||
//! This module contains implementation
|
||||
//! of [exponential rate limiting](https://dotat.at/@/2024-09-02-ewma.html).
|
||||
//! Implementation is simplified to only use one variable (`next_time`) to store the state.
|
||||
//! This module contains implementation of token bucket policy.
|
||||
//! Its primary use is preventing Delta Chat from sending too many messages, especially automatic,
|
||||
//! such as read receipts.
|
||||
|
||||
use std::time::{Duration, SystemTime, UNIX_EPOCH};
|
||||
use std::time::{Duration, SystemTime};
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct Ratelimit {
|
||||
/// Next time we are allowed to send, i.e. when measured rate (number of messages sent within
|
||||
/// the window) drops down to the limit `quota`.
|
||||
///
|
||||
/// Measured in seconds since unix epoch.
|
||||
next_time: SystemTime,
|
||||
/// Time of the last update.
|
||||
last_update: SystemTime,
|
||||
|
||||
/// Number of messages sent within the time window ending at `last_update`.
|
||||
current_value: f64,
|
||||
|
||||
/// Time window size.
|
||||
window: f64,
|
||||
window: Duration,
|
||||
|
||||
/// Number of messages allowed to send within the time window.
|
||||
limit: f64,
|
||||
quota: f64,
|
||||
}
|
||||
|
||||
impl Ratelimit {
|
||||
/// Returns a new rate limiter with the given constraints.
|
||||
///
|
||||
/// Rate limiter will allow to send no more than `limit` messages within duration `window`.
|
||||
pub fn new(window: Duration, limit: f64) -> Self {
|
||||
/// Rate limiter will allow to send no more than `quota` messages within duration `window`.
|
||||
pub fn new(window: Duration, quota: f64) -> Self {
|
||||
Self::new_at(window, quota, SystemTime::now())
|
||||
}
|
||||
|
||||
/// Returns a new rate limiter with given current time for testing purposes.
|
||||
fn new_at(window: Duration, quota: f64, now: SystemTime) -> Self {
|
||||
Self {
|
||||
next_time: UNIX_EPOCH,
|
||||
window: window.as_secs_f64(),
|
||||
limit,
|
||||
last_update: now,
|
||||
current_value: 0.0,
|
||||
window,
|
||||
quota,
|
||||
}
|
||||
}
|
||||
|
||||
/// Returns current number of sent messages.
|
||||
fn current_value_at(&self, now: SystemTime) -> f64 {
|
||||
let rate: f64 = self.quota / self.window.as_secs_f64();
|
||||
let elapsed = now
|
||||
.duration_since(self.last_update)
|
||||
.unwrap_or(Duration::ZERO)
|
||||
.as_secs_f64();
|
||||
f64::max(0.0, self.current_value - rate * elapsed)
|
||||
}
|
||||
|
||||
/// Returns true if it is allowed to send a message.
|
||||
fn can_send_at(&self, now: SystemTime) -> bool {
|
||||
now >= self.next_time
|
||||
self.current_value_at(now) + 1.0 <= self.quota
|
||||
}
|
||||
|
||||
/// Returns true if can send another message now.
|
||||
@@ -48,20 +62,8 @@ impl Ratelimit {
|
||||
}
|
||||
|
||||
fn send_at(&mut self, now: SystemTime) {
|
||||
let now = now
|
||||
.duration_since(UNIX_EPOCH)
|
||||
.unwrap_or(Duration::ZERO)
|
||||
.as_secs_f64();
|
||||
let next_time = self
|
||||
.next_time
|
||||
.duration_since(UNIX_EPOCH)
|
||||
.unwrap_or(Duration::ZERO)
|
||||
.as_secs_f64();
|
||||
self.next_time = UNIX_EPOCH
|
||||
+ Duration::from_secs_f64(
|
||||
now + self.window
|
||||
* (((next_time - now) / self.window).exp() + 1.0 / self.limit).ln(),
|
||||
);
|
||||
self.current_value = f64::min(self.quota, self.current_value_at(now) + 1.0);
|
||||
self.last_update = now;
|
||||
}
|
||||
|
||||
/// Increases current usage value.
|
||||
@@ -74,7 +76,14 @@ impl Ratelimit {
|
||||
}
|
||||
|
||||
fn until_can_send_at(&self, now: SystemTime) -> Duration {
|
||||
self.next_time.duration_since(now).unwrap_or(Duration::ZERO)
|
||||
let current_value = self.current_value_at(now);
|
||||
if current_value + 1.0 <= self.quota {
|
||||
Duration::ZERO
|
||||
} else {
|
||||
let requirement = current_value + 1.0 - self.quota;
|
||||
let rate = self.quota / self.window.as_secs_f64();
|
||||
Duration::from_secs_f64(requirement / rate)
|
||||
}
|
||||
}
|
||||
|
||||
/// Calculates the time until `can_send` will return `true`.
|
||||
@@ -91,7 +100,7 @@ mod tests {
|
||||
fn test_ratelimit() {
|
||||
let now = SystemTime::now();
|
||||
|
||||
let mut ratelimit = Ratelimit::new(Duration::new(60, 0), 3.0);
|
||||
let mut ratelimit = Ratelimit::new_at(Duration::new(60, 0), 3.0, now);
|
||||
assert!(ratelimit.can_send_at(now));
|
||||
|
||||
// Send burst of 3 messages.
|
||||
@@ -100,8 +109,6 @@ mod tests {
|
||||
ratelimit.send_at(now);
|
||||
assert!(ratelimit.can_send_at(now));
|
||||
ratelimit.send_at(now);
|
||||
assert!(ratelimit.can_send_at(now));
|
||||
ratelimit.send_at(now);
|
||||
|
||||
// Can't send more messages now.
|
||||
assert!(!ratelimit.can_send_at(now));
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
[package]
|
||||
name = "deltachat-repl"
|
||||
version = "1.142.12"
|
||||
version = "1.143.0"
|
||||
license = "MPL-2.0"
|
||||
edition = "2021"
|
||||
repository = "https://github.com/deltachat/deltachat-core-rust"
|
||||
|
||||
@@ -1249,10 +1249,10 @@ pub async fn cmdline(context: Context, line: &str, chat_id: &mut ChatId) -> Resu
|
||||
}
|
||||
"providerinfo" => {
|
||||
ensure!(!arg1.is_empty(), "Argument <addr> missing.");
|
||||
let socks5_enabled = context
|
||||
.get_config_bool(config::Config::Socks5Enabled)
|
||||
let proxy_enabled = context
|
||||
.get_config_bool(config::Config::ProxyEnabled)
|
||||
.await?;
|
||||
match provider::get_provider_info(&context, arg1, socks5_enabled).await {
|
||||
match provider::get_provider_info(&context, arg1, proxy_enabled).await {
|
||||
Some(info) => {
|
||||
println!("Information for provider belonging to {arg1}:");
|
||||
println!("status: {}", info.status as u32);
|
||||
|
||||
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
|
||||
|
||||
[project]
|
||||
name = "deltachat-rpc-client"
|
||||
version = "1.142.12"
|
||||
version = "1.143.0"
|
||||
description = "Python client for Delta Chat core JSON-RPC interface"
|
||||
classifiers = [
|
||||
"Development Status :: 5 - Production/Stable",
|
||||
|
||||
@@ -62,7 +62,7 @@ def test_qr_setup_contact_svg(acfactory) -> None:
|
||||
|
||||
@pytest.mark.parametrize("protect", [True, False])
|
||||
def test_qr_securejoin(acfactory, protect, tmp_path):
|
||||
alice, bob = acfactory.get_online_accounts(2)
|
||||
alice, bob, fiona = acfactory.get_online_accounts(3)
|
||||
|
||||
# Setup second device for Alice
|
||||
# to test observing securejoin protocol.
|
||||
@@ -112,6 +112,13 @@ def test_qr_securejoin(acfactory, protect, tmp_path):
|
||||
alice2_contact_bob_snapshot = alice2_contact_bob.get_snapshot()
|
||||
assert alice2_contact_bob_snapshot.is_verified
|
||||
|
||||
# The QR code token is synced, so alice2 must be able to handle join requests.
|
||||
logging.info("Fiona joins verified group via alice2")
|
||||
alice.stop_io()
|
||||
fiona.secure_join(qr_code)
|
||||
alice2.wait_for_securejoin_inviter_success()
|
||||
fiona.wait_for_securejoin_joiner_success()
|
||||
|
||||
|
||||
def test_qr_securejoin_contact_request(acfactory) -> None:
|
||||
"""Alice invites Bob to a group when Bob's chat with Alice is in a contact request mode."""
|
||||
|
||||
@@ -433,7 +433,7 @@ def test_provider_info(rpc) -> None:
|
||||
assert provider_info["id"] == "gmail"
|
||||
|
||||
# Disable MX record resolution.
|
||||
rpc.set_config(account_id, "socks5_enabled", "1")
|
||||
rpc.set_config(account_id, "proxy_enabled", "1")
|
||||
provider_info = rpc.get_provider_info(account_id, "github.com")
|
||||
assert provider_info is None
|
||||
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
[package]
|
||||
name = "deltachat-rpc-server"
|
||||
version = "1.142.12"
|
||||
version = "1.143.0"
|
||||
description = "DeltaChat JSON-RPC server"
|
||||
edition = "2021"
|
||||
readme = "README.md"
|
||||
|
||||
@@ -15,5 +15,5 @@
|
||||
},
|
||||
"type": "module",
|
||||
"types": "index.d.ts",
|
||||
"version": "1.142.12"
|
||||
"version": "1.143.0"
|
||||
}
|
||||
|
||||
@@ -26,9 +26,6 @@ skip = [
|
||||
{ name = "base64", version = "<0.21" },
|
||||
{ name = "base64", version = "0.21.7" },
|
||||
{ name = "bitflags", version = "1.3.2" },
|
||||
{ name = "darling_core", version = "<0.14" },
|
||||
{ name = "darling_macro", version = "<0.14" },
|
||||
{ name = "darling", version = "<0.14" },
|
||||
{ name = "der-parser", version = "8.2.0" },
|
||||
{ name = "event-listener", version = "2.5.3" },
|
||||
{ name = "event-listener", version = "4.0.3" },
|
||||
@@ -53,7 +50,6 @@ skip = [
|
||||
{ name = "rustls", version = "0.21.11" },
|
||||
{ name = "rustls-webpki", version = "0.101.7" },
|
||||
{ name = "spin", version = "<0.9.6" },
|
||||
{ name = "strsim", version = "0.10.0" },
|
||||
{ name = "sync_wrapper", version = "0.1.2" },
|
||||
{ name = "synstructure", version = "0.12.6" },
|
||||
{ name = "syn", version = "1.0.109" },
|
||||
@@ -66,7 +62,7 @@ skip = [
|
||||
{ name = "windows-core", version = "<0.54.0" },
|
||||
{ name = "windows_i686_gnu", version = "<0.52" },
|
||||
{ name = "windows_i686_msvc", version = "<0.52" },
|
||||
{ name = "windows-sys", version = "<0.52" },
|
||||
{ name = "windows-sys", version = "<0.59" },
|
||||
{ name = "windows-targets", version = "<0.52" },
|
||||
{ name = "windows", version = "<0.54.0" },
|
||||
{ name = "windows_x86_64_gnullvm", version = "<0.52" },
|
||||
|
||||
@@ -271,7 +271,7 @@ describe('Basic offline Tests', function () {
|
||||
'sync_msgs',
|
||||
'sentbox_watch',
|
||||
'show_emails',
|
||||
'socks5_enabled',
|
||||
'proxy_enabled',
|
||||
'sqlite_version',
|
||||
'uptime',
|
||||
'used_account_settings',
|
||||
|
||||
@@ -55,5 +55,5 @@
|
||||
"test:mocha": "mocha node/test/test.mjs --growl --reporter=spec --bail --exit"
|
||||
},
|
||||
"types": "node/dist/index.d.ts",
|
||||
"version": "1.142.12"
|
||||
"version": "1.143.0"
|
||||
}
|
||||
|
||||
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
|
||||
|
||||
[project]
|
||||
name = "deltachat"
|
||||
version = "1.142.12"
|
||||
version = "1.143.0"
|
||||
description = "Python bindings for the Delta Chat Core library using CFFI against the Rust-implemented libdeltachat"
|
||||
readme = "README.rst"
|
||||
requires-python = ">=3.7"
|
||||
|
||||
@@ -525,6 +525,7 @@ class ACFactory:
|
||||
configdict.setdefault("mvbox_move", False)
|
||||
configdict.setdefault("sentbox_watch", False)
|
||||
configdict.setdefault("sync_msgs", False)
|
||||
configdict.setdefault("delete_server_after", 0)
|
||||
ac.update_config(configdict)
|
||||
self._acsetup._account2config[ac] = configdict
|
||||
self._preconfigure_key(ac, configdict["addr"])
|
||||
|
||||
@@ -2076,12 +2076,11 @@ def test_send_receive_locations(acfactory, lp):
|
||||
def test_immediate_autodelete(acfactory, lp):
|
||||
ac1 = acfactory.new_online_configuring_account()
|
||||
ac2 = acfactory.new_online_configuring_account()
|
||||
acfactory.bring_accounts_online()
|
||||
|
||||
# "1" means delete immediately, while "0" means do not delete
|
||||
ac2.set_config("delete_server_after", "1")
|
||||
|
||||
acfactory.bring_accounts_online()
|
||||
|
||||
lp.sec("ac1: create chat with ac2")
|
||||
chat1 = ac1.create_chat(ac2)
|
||||
ac2.create_chat(ac1)
|
||||
|
||||
@@ -1 +1 @@
|
||||
2024-09-02
|
||||
2024-09-12
|
||||
@@ -7,7 +7,7 @@ set -euo pipefail
|
||||
#
|
||||
# Avoid using rustup here as it depends on reading /proc/self/exe and
|
||||
# has problems running under QEMU.
|
||||
RUST_VERSION=1.80.1
|
||||
RUST_VERSION=1.81.0
|
||||
|
||||
ARCH="$(uname -m)"
|
||||
test -f "/lib/libc.musl-$ARCH.so.1" && LIBC=musl || LIBC=gnu
|
||||
|
||||
30
src/chat.rs
30
src/chat.rs
@@ -279,9 +279,10 @@ impl ChatId {
|
||||
) -> Result<Self> {
|
||||
let chat_id = match ChatIdBlocked::lookup_by_contact(context, contact_id).await? {
|
||||
Some(chat) => {
|
||||
if create_blocked == Blocked::Not && chat.blocked != Blocked::Not {
|
||||
chat.id.set_blocked(context, Blocked::Not).await?;
|
||||
if create_blocked != Blocked::Not || chat.blocked == Blocked::Not {
|
||||
return Ok(chat.id);
|
||||
}
|
||||
chat.id.set_blocked(context, Blocked::Not).await?;
|
||||
chat.id
|
||||
}
|
||||
None => {
|
||||
@@ -1934,8 +1935,8 @@ impl Chat {
|
||||
msg.param.set_int(Param::AttachGroupImage, 1);
|
||||
self.param.remove(Param::Unpromoted);
|
||||
self.update_param(context).await?;
|
||||
// send_sync_msg() is called (usually) a moment later at send_msg_to_smtp()
|
||||
// when the group creation message is actually sent through SMTP --
|
||||
// send_sync_msg() is called a moment later at `smtp::send_smtp_messages()`
|
||||
// when the group creation message is already in the `smtp` table --
|
||||
// this makes sure, the other devices are aware of grpid that is used in the sync-message.
|
||||
context
|
||||
.sync_qr_code_tokens(Some(self.id))
|
||||
@@ -3726,17 +3727,13 @@ pub(crate) async fn add_contact_to_chat_ex(
|
||||
bail!("can not add contact because the account is not part of the group/broadcast");
|
||||
}
|
||||
|
||||
let sync_qr_code_tokens;
|
||||
if from_handshake && chat.param.get_int(Param::Unpromoted).unwrap_or_default() == 1 {
|
||||
chat.param.remove(Param::Unpromoted);
|
||||
chat.update_param(context).await?;
|
||||
if context
|
||||
.sync_qr_code_tokens(Some(chat_id))
|
||||
.await
|
||||
.log_err(context)
|
||||
.is_ok()
|
||||
{
|
||||
context.scheduler.interrupt_smtp().await;
|
||||
}
|
||||
sync_qr_code_tokens = true;
|
||||
} else {
|
||||
sync_qr_code_tokens = false;
|
||||
}
|
||||
|
||||
if context.is_self_addr(contact.get_addr()).await? {
|
||||
@@ -3780,6 +3777,15 @@ pub(crate) async fn add_contact_to_chat_ex(
|
||||
return Err(e);
|
||||
}
|
||||
sync = Nosync;
|
||||
if sync_qr_code_tokens
|
||||
&& context
|
||||
.sync_qr_code_tokens(Some(chat_id))
|
||||
.await
|
||||
.log_err(context)
|
||||
.is_ok()
|
||||
{
|
||||
context.scheduler.interrupt_smtp().await;
|
||||
}
|
||||
}
|
||||
context.emit_event(EventType::ChatModified(chat_id));
|
||||
if sync.into() {
|
||||
|
||||
107
src/config.rs
107
src/config.rs
@@ -91,21 +91,43 @@ pub enum Config {
|
||||
/// Should not be extended in the future, create new config keys instead.
|
||||
ServerFlags,
|
||||
|
||||
/// True if proxy is enabled.
|
||||
///
|
||||
/// Can be used to disable proxy without erasing known URLs.
|
||||
ProxyEnabled,
|
||||
|
||||
/// Proxy URL.
|
||||
///
|
||||
/// Supported URLs schemes are `socks5://` (SOCKS5) and `ss://` (Shadowsocks).
|
||||
///
|
||||
/// May contain multiple URLs separated by newline, in which case the first one is used.
|
||||
ProxyUrl,
|
||||
|
||||
/// True if SOCKS5 is enabled.
|
||||
///
|
||||
/// Can be used to disable SOCKS5 without erasing SOCKS5 configuration.
|
||||
///
|
||||
/// Deprecated in favor of `ProxyEnabled`.
|
||||
Socks5Enabled,
|
||||
|
||||
/// SOCKS5 proxy server hostname or address.
|
||||
///
|
||||
/// Deprecated in favor of `ProxyUrl`.
|
||||
Socks5Host,
|
||||
|
||||
/// SOCKS5 proxy server port.
|
||||
///
|
||||
/// Deprecated in favor of `ProxyUrl`.
|
||||
Socks5Port,
|
||||
|
||||
/// SOCKS5 proxy server username.
|
||||
///
|
||||
/// Deprecated in favor of `ProxyUrl`.
|
||||
Socks5User,
|
||||
|
||||
/// SOCKS5 proxy server password.
|
||||
///
|
||||
/// Deprecated in favor of `ProxyUrl`.
|
||||
Socks5Password,
|
||||
|
||||
/// Own name to use in the `From:` field when sending messages.
|
||||
@@ -174,12 +196,12 @@ pub enum Config {
|
||||
/// Timer in seconds after which the message is deleted from the
|
||||
/// server.
|
||||
///
|
||||
/// Equals to 0 by default, which means the message is never
|
||||
/// deleted.
|
||||
/// 0 means messages are never deleted by Delta Chat.
|
||||
///
|
||||
/// Value 1 is treated as "delete at once": messages are deleted
|
||||
/// immediately, without moving to DeltaChat folder.
|
||||
#[strum(props(default = "0"))]
|
||||
///
|
||||
/// Default is 1 for chatmail accounts before a backup export, 0 otherwise.
|
||||
DeleteServerAfter,
|
||||
|
||||
/// Timer in seconds after which the message is deleted from the
|
||||
@@ -438,13 +460,16 @@ impl Config {
|
||||
}
|
||||
|
||||
impl Context {
|
||||
/// Returns true if configuration value is set for the given key.
|
||||
pub async fn config_exists(&self, key: Config) -> Result<bool> {
|
||||
/// Returns true if configuration value is set in the db for the given key.
|
||||
///
|
||||
/// NB: Don't use this to check if the key is configured because this doesn't look into
|
||||
/// environment. The proper use of this function is e.g. checking a key before setting it.
|
||||
pub(crate) async fn config_exists(&self, key: Config) -> Result<bool> {
|
||||
Ok(self.sql.get_raw_config(key.as_ref()).await?.is_some())
|
||||
}
|
||||
|
||||
/// Get a configuration key. Returns `None` if no value is set, and no default value found.
|
||||
pub async fn get_config(&self, key: Config) -> Result<Option<String>> {
|
||||
/// Get a config key value. Returns `None` if no value is set.
|
||||
pub(crate) async fn get_config_opt(&self, key: Config) -> Result<Option<String>> {
|
||||
let env_key = format!("DELTACHAT_{}", key.as_ref().to_uppercase());
|
||||
if let Ok(value) = env::var(env_key) {
|
||||
return Ok(Some(value));
|
||||
@@ -464,19 +489,38 @@ impl Context {
|
||||
Config::SysConfigKeys => Some(get_config_keys_string()),
|
||||
_ => self.sql.get_raw_config(key.as_ref()).await?,
|
||||
};
|
||||
Ok(value)
|
||||
}
|
||||
|
||||
/// Get a config key value if set, or a default value. Returns `None` if no value exists.
|
||||
pub async fn get_config(&self, key: Config) -> Result<Option<String>> {
|
||||
let value = self.get_config_opt(key).await?;
|
||||
if value.is_some() {
|
||||
return Ok(value);
|
||||
}
|
||||
|
||||
// Default values
|
||||
match key {
|
||||
Config::ConfiguredInboxFolder => Ok(Some("INBOX".to_owned())),
|
||||
_ => Ok(key.get_str("default").map(|s| s.to_string())),
|
||||
}
|
||||
let val = match key {
|
||||
Config::ConfiguredInboxFolder => Some("INBOX"),
|
||||
Config::DeleteServerAfter => match Box::pin(self.is_chatmail()).await? {
|
||||
false => Some("0"),
|
||||
true => Some("1"),
|
||||
},
|
||||
_ => key.get_str("default"),
|
||||
};
|
||||
Ok(val.map(|s| s.to_string()))
|
||||
}
|
||||
|
||||
/// Returns Some(T) if a value for the given key exists and was successfully parsed.
|
||||
/// Returns Some(T) if a value for the given key is set and was successfully parsed.
|
||||
/// Returns None if could not parse.
|
||||
pub(crate) async fn get_config_opt_parsed<T: FromStr>(&self, key: Config) -> Result<Option<T>> {
|
||||
self.get_config_opt(key)
|
||||
.await
|
||||
.map(|s: Option<String>| s.and_then(|s| s.parse().ok()))
|
||||
}
|
||||
|
||||
/// Returns Some(T) if a value for the given key exists (incl. default value) and was
|
||||
/// successfully parsed.
|
||||
/// Returns None if could not parse.
|
||||
pub async fn get_config_parsed<T: FromStr>(&self, key: Config) -> Result<Option<T>> {
|
||||
self.get_config(key)
|
||||
@@ -504,14 +548,21 @@ impl Context {
|
||||
Ok(self.get_config_parsed(key).await?.unwrap_or_default())
|
||||
}
|
||||
|
||||
/// Returns boolean configuration value (if any) for the given key.
|
||||
pub async fn get_config_bool_opt(&self, key: Config) -> Result<Option<bool>> {
|
||||
Ok(self.get_config_parsed::<i32>(key).await?.map(|x| x != 0))
|
||||
/// Returns boolean configuration value (if set) for the given key.
|
||||
pub(crate) async fn get_config_bool_opt(&self, key: Config) -> Result<Option<bool>> {
|
||||
Ok(self
|
||||
.get_config_opt_parsed::<i32>(key)
|
||||
.await?
|
||||
.map(|x| x != 0))
|
||||
}
|
||||
|
||||
/// Returns boolean configuration value for the given key.
|
||||
pub async fn get_config_bool(&self, key: Config) -> Result<bool> {
|
||||
Ok(self.get_config_bool_opt(key).await?.unwrap_or_default())
|
||||
Ok(self
|
||||
.get_config_parsed::<i32>(key)
|
||||
.await?
|
||||
.map(|x| x != 0)
|
||||
.unwrap_or_default())
|
||||
}
|
||||
|
||||
/// Returns true if movebox ("DeltaChat" folder) should be watched.
|
||||
@@ -539,9 +590,9 @@ impl Context {
|
||||
|
||||
/// Returns whether MDNs should be requested.
|
||||
pub(crate) async fn should_request_mdns(&self) -> Result<bool> {
|
||||
match self.config_exists(Config::MdnsEnabled).await? {
|
||||
true => self.get_config_bool(Config::MdnsEnabled).await,
|
||||
false => Ok(!self.get_config_bool(Config::Bot).await?),
|
||||
match self.get_config_bool_opt(Config::MdnsEnabled).await? {
|
||||
Some(val) => Ok(val),
|
||||
None => Ok(!self.get_config_bool(Config::Bot).await?),
|
||||
}
|
||||
}
|
||||
|
||||
@@ -555,11 +606,16 @@ impl Context {
|
||||
/// `None` means never delete the message, `Some(0)` means delete
|
||||
/// at once, `Some(x)` means delete after `x` seconds.
|
||||
pub async fn get_config_delete_server_after(&self) -> Result<Option<i64>> {
|
||||
match self.get_config_int(Config::DeleteServerAfter).await? {
|
||||
0 => Ok(None),
|
||||
1 => Ok(Some(0)),
|
||||
x => Ok(Some(i64::from(x))),
|
||||
}
|
||||
let val = match self
|
||||
.get_config_parsed::<i64>(Config::DeleteServerAfter)
|
||||
.await?
|
||||
.unwrap_or(0)
|
||||
{
|
||||
0 => None,
|
||||
1 => Some(0),
|
||||
x => Some(x),
|
||||
};
|
||||
Ok(val)
|
||||
}
|
||||
|
||||
/// Gets the configured provider, as saved in the `configured_provider` value.
|
||||
@@ -604,6 +660,7 @@ impl Context {
|
||||
fn check_config(key: Config, value: Option<&str>) -> Result<()> {
|
||||
match key {
|
||||
Config::Socks5Enabled
|
||||
| Config::ProxyEnabled
|
||||
| Config::BccSelf
|
||||
| Config::E2eeEnabled
|
||||
| Config::MdnsEnabled
|
||||
@@ -1004,12 +1061,14 @@ mod tests {
|
||||
let t = &TestContext::new_alice().await;
|
||||
assert!(t.should_request_mdns().await?);
|
||||
assert!(t.should_send_mdns().await?);
|
||||
assert!(t.get_config_bool_opt(Config::MdnsEnabled).await?.is_none());
|
||||
// The setting should be displayed correctly.
|
||||
assert!(t.get_config_bool(Config::MdnsEnabled).await?);
|
||||
|
||||
t.set_config_bool(Config::Bot, true).await?;
|
||||
assert!(!t.should_request_mdns().await?);
|
||||
assert!(t.should_send_mdns().await?);
|
||||
assert!(t.get_config_bool_opt(Config::MdnsEnabled).await?.is_none());
|
||||
assert!(t.get_config_bool(Config::MdnsEnabled).await?);
|
||||
Ok(())
|
||||
}
|
||||
|
||||
@@ -196,8 +196,8 @@ async fn get_configured_param(
|
||||
param.smtp.password.clone()
|
||||
};
|
||||
|
||||
let socks5_config = param.socks5_config.clone();
|
||||
let socks5_enabled = socks5_config.is_some();
|
||||
let proxy_config = param.proxy_config.clone();
|
||||
let proxy_enabled = proxy_config.is_some();
|
||||
|
||||
let mut addr = param.addr.clone();
|
||||
if param.oauth2 {
|
||||
@@ -240,7 +240,7 @@ async fn get_configured_param(
|
||||
"checking internal provider-info for offline autoconfig"
|
||||
);
|
||||
|
||||
provider = provider::get_provider_info(ctx, ¶m_domain, socks5_enabled).await;
|
||||
provider = provider::get_provider_info(ctx, ¶m_domain, proxy_enabled).await;
|
||||
if let Some(provider) = provider {
|
||||
if provider.server.is_empty() {
|
||||
info!(ctx, "Offline autoconfig found, but no servers defined.");
|
||||
@@ -356,7 +356,7 @@ async fn get_configured_param(
|
||||
.collect(),
|
||||
smtp_user: param.smtp.user.clone(),
|
||||
smtp_password,
|
||||
socks5_config: param.socks5_config.clone(),
|
||||
proxy_config: param.proxy_config.clone(),
|
||||
provider,
|
||||
certificate_checks: match param.certificate_checks {
|
||||
EnteredCertificateChecks::Automatic => ConfiguredCertificateChecks::Automatic,
|
||||
@@ -388,7 +388,7 @@ async fn configure(ctx: &Context, param: &EnteredLoginParam) -> Result<Configure
|
||||
let smtp_param = configured_param.smtp.clone();
|
||||
let smtp_password = configured_param.smtp_password.clone();
|
||||
let smtp_addr = configured_param.addr.clone();
|
||||
let smtp_socks5 = configured_param.socks5_config.clone();
|
||||
let proxy_config = configured_param.proxy_config.clone();
|
||||
|
||||
let smtp_config_task = task::spawn(async move {
|
||||
let mut smtp = Smtp::new();
|
||||
@@ -396,7 +396,7 @@ async fn configure(ctx: &Context, param: &EnteredLoginParam) -> Result<Configure
|
||||
&context_smtp,
|
||||
&smtp_param,
|
||||
&smtp_password,
|
||||
&smtp_socks5,
|
||||
&proxy_config,
|
||||
&smtp_addr,
|
||||
strict_tls,
|
||||
configured_param.oauth2,
|
||||
@@ -414,7 +414,7 @@ async fn configure(ctx: &Context, param: &EnteredLoginParam) -> Result<Configure
|
||||
let mut imap = Imap::new(
|
||||
configured_param.imap.clone(),
|
||||
configured_param.imap_password.clone(),
|
||||
configured_param.socks5_config.clone(),
|
||||
configured_param.proxy_config.clone(),
|
||||
&configured_param.addr,
|
||||
strict_tls,
|
||||
configured_param.oauth2,
|
||||
|
||||
@@ -726,7 +726,7 @@ impl Context {
|
||||
let request_msgs = message::get_request_msg_cnt(self).await;
|
||||
let contacts = Contact::get_real_cnt(self).await?;
|
||||
let is_configured = self.get_config_int(Config::Configured).await?;
|
||||
let socks5_enabled = self.get_config_int(Config::Socks5Enabled).await?;
|
||||
let proxy_enabled = self.get_config_int(Config::ProxyEnabled).await?;
|
||||
let dbversion = self
|
||||
.sql
|
||||
.get_raw_config_int("dbversion")
|
||||
@@ -807,7 +807,7 @@ impl Context {
|
||||
.unwrap_or_else(|| "<unset>".to_string()),
|
||||
);
|
||||
res.insert("is_configured", is_configured.to_string());
|
||||
res.insert("socks5_enabled", socks5_enabled.to_string());
|
||||
res.insert("proxy_enabled", proxy_enabled.to_string());
|
||||
res.insert("entered_account_settings", l.to_string());
|
||||
res.insert("used_account_settings", l2);
|
||||
|
||||
@@ -1693,6 +1693,8 @@ mod tests {
|
||||
"server_flags",
|
||||
"skip_start_messages",
|
||||
"smtp_certificate_checks",
|
||||
"proxy_url", // May contain passwords, don't leak it to the logs.
|
||||
"socks5_enabled", // SOCKS5 options are deprecated.
|
||||
"socks5_host",
|
||||
"socks5_port",
|
||||
"socks5_user",
|
||||
|
||||
15
src/imap.rs
15
src/imap.rs
@@ -37,12 +37,12 @@ use crate::login_param::{
|
||||
};
|
||||
use crate::message::{self, Message, MessageState, MessengerMessage, MsgId, Viewtype};
|
||||
use crate::mimeparser;
|
||||
use crate::net::proxy::ProxyConfig;
|
||||
use crate::oauth2::get_oauth2_access_token;
|
||||
use crate::receive_imf::{
|
||||
from_field_to_contact_id, get_prefetch_parent_message, receive_imf_inner, ReceivedMsg,
|
||||
};
|
||||
use crate::scheduler::connectivity::ConnectivityStore;
|
||||
use crate::socks::Socks5Config;
|
||||
use crate::sql;
|
||||
use crate::stock_str;
|
||||
use crate::tools::{self, create_id, duration_to_str};
|
||||
@@ -80,8 +80,9 @@ pub(crate) struct Imap {
|
||||
/// Password.
|
||||
password: String,
|
||||
|
||||
/// SOCKS 5 configuration.
|
||||
socks5_config: Option<Socks5Config>,
|
||||
/// Proxy configuration.
|
||||
proxy_config: Option<ProxyConfig>,
|
||||
|
||||
strict_tls: bool,
|
||||
|
||||
oauth2: bool,
|
||||
@@ -237,7 +238,7 @@ impl Imap {
|
||||
pub fn new(
|
||||
lp: Vec<ConfiguredServerLoginParam>,
|
||||
password: String,
|
||||
socks5_config: Option<Socks5Config>,
|
||||
proxy_config: Option<ProxyConfig>,
|
||||
addr: &str,
|
||||
strict_tls: bool,
|
||||
oauth2: bool,
|
||||
@@ -248,7 +249,7 @@ impl Imap {
|
||||
addr: addr.to_string(),
|
||||
lp,
|
||||
password,
|
||||
socks5_config,
|
||||
proxy_config,
|
||||
strict_tls,
|
||||
oauth2,
|
||||
login_failed_once: false,
|
||||
@@ -271,7 +272,7 @@ impl Imap {
|
||||
let imap = Self::new(
|
||||
param.imap.clone(),
|
||||
param.imap_password.clone(),
|
||||
param.socks5_config.clone(),
|
||||
param.proxy_config.clone(),
|
||||
¶m.addr,
|
||||
param.strict_tls(),
|
||||
param.oauth2,
|
||||
@@ -336,7 +337,7 @@ impl Imap {
|
||||
let connection_candidate = lp.connection.clone();
|
||||
let client = match Client::connect(
|
||||
context,
|
||||
self.socks5_config.clone(),
|
||||
self.proxy_config.clone(),
|
||||
self.strict_tls,
|
||||
connection_candidate,
|
||||
)
|
||||
|
||||
@@ -4,7 +4,6 @@ use std::ops::{Deref, DerefMut};
|
||||
use anyhow::{Context as _, Result};
|
||||
use async_imap::Client as ImapClient;
|
||||
use async_imap::Session as ImapSession;
|
||||
use fast_socks5::client::Socks5Stream;
|
||||
use tokio::io::BufWriter;
|
||||
|
||||
use super::capabilities::Capabilities;
|
||||
@@ -12,12 +11,12 @@ use super::session::Session;
|
||||
use crate::context::Context;
|
||||
use crate::login_param::{ConnectionCandidate, ConnectionSecurity};
|
||||
use crate::net::dns::{lookup_host_with_cache, update_connect_timestamp};
|
||||
use crate::net::proxy::ProxyConfig;
|
||||
use crate::net::session::SessionStream;
|
||||
use crate::net::tls::wrap_tls;
|
||||
use crate::net::{
|
||||
connect_tcp_inner, connect_tls_inner, run_connection_attempts, update_connection_history,
|
||||
};
|
||||
use crate::socks::Socks5Config;
|
||||
use crate::tools::time;
|
||||
|
||||
#[derive(Debug)]
|
||||
@@ -157,25 +156,25 @@ impl Client {
|
||||
|
||||
pub async fn connect(
|
||||
context: &Context,
|
||||
socks5_config: Option<Socks5Config>,
|
||||
proxy_config: Option<ProxyConfig>,
|
||||
strict_tls: bool,
|
||||
candidate: ConnectionCandidate,
|
||||
) -> Result<Self> {
|
||||
let host = &candidate.host;
|
||||
let port = candidate.port;
|
||||
let security = candidate.security;
|
||||
if let Some(socks5_config) = socks5_config {
|
||||
if let Some(proxy_config) = proxy_config {
|
||||
let client = match security {
|
||||
ConnectionSecurity::Tls => {
|
||||
Client::connect_secure_socks5(context, host, port, strict_tls, socks5_config)
|
||||
Client::connect_secure_proxy(context, host, port, strict_tls, proxy_config)
|
||||
.await?
|
||||
}
|
||||
ConnectionSecurity::Starttls => {
|
||||
Client::connect_starttls_socks5(context, host, port, socks5_config, strict_tls)
|
||||
Client::connect_starttls_proxy(context, host, port, proxy_config, strict_tls)
|
||||
.await?
|
||||
}
|
||||
ConnectionSecurity::Plain => {
|
||||
Client::connect_insecure_socks5(context, host, port, socks5_config).await?
|
||||
Client::connect_insecure_proxy(context, host, port, proxy_config).await?
|
||||
}
|
||||
};
|
||||
Ok(client)
|
||||
@@ -249,17 +248,17 @@ impl Client {
|
||||
Ok(client)
|
||||
}
|
||||
|
||||
async fn connect_secure_socks5(
|
||||
async fn connect_secure_proxy(
|
||||
context: &Context,
|
||||
domain: &str,
|
||||
port: u16,
|
||||
strict_tls: bool,
|
||||
socks5_config: Socks5Config,
|
||||
proxy_config: ProxyConfig,
|
||||
) -> Result<Self> {
|
||||
let socks5_stream = socks5_config
|
||||
let proxy_stream = proxy_config
|
||||
.connect(context, domain, port, strict_tls)
|
||||
.await?;
|
||||
let tls_stream = wrap_tls(strict_tls, domain, alpn(port), socks5_stream).await?;
|
||||
let tls_stream = wrap_tls(strict_tls, domain, alpn(port), proxy_stream).await?;
|
||||
let buffered_stream = BufWriter::new(tls_stream);
|
||||
let session_stream: Box<dyn SessionStream> = Box::new(buffered_stream);
|
||||
let mut client = Client::new(session_stream);
|
||||
@@ -270,14 +269,14 @@ impl Client {
|
||||
Ok(client)
|
||||
}
|
||||
|
||||
async fn connect_insecure_socks5(
|
||||
async fn connect_insecure_proxy(
|
||||
context: &Context,
|
||||
domain: &str,
|
||||
port: u16,
|
||||
socks5_config: Socks5Config,
|
||||
proxy_config: ProxyConfig,
|
||||
) -> Result<Self> {
|
||||
let socks5_stream = socks5_config.connect(context, domain, port, false).await?;
|
||||
let buffered_stream = BufWriter::new(socks5_stream);
|
||||
let proxy_stream = proxy_config.connect(context, domain, port, false).await?;
|
||||
let buffered_stream = BufWriter::new(proxy_stream);
|
||||
let session_stream: Box<dyn SessionStream> = Box::new(buffered_stream);
|
||||
let mut client = Client::new(session_stream);
|
||||
let _greeting = client
|
||||
@@ -287,20 +286,20 @@ impl Client {
|
||||
Ok(client)
|
||||
}
|
||||
|
||||
async fn connect_starttls_socks5(
|
||||
async fn connect_starttls_proxy(
|
||||
context: &Context,
|
||||
hostname: &str,
|
||||
port: u16,
|
||||
socks5_config: Socks5Config,
|
||||
proxy_config: ProxyConfig,
|
||||
strict_tls: bool,
|
||||
) -> Result<Self> {
|
||||
let socks5_stream = socks5_config
|
||||
let proxy_stream = proxy_config
|
||||
.connect(context, hostname, port, strict_tls)
|
||||
.await?;
|
||||
|
||||
// Run STARTTLS command and convert the client back into a stream.
|
||||
let buffered_socks5_stream = BufWriter::new(socks5_stream);
|
||||
let mut client = ImapClient::new(buffered_socks5_stream);
|
||||
let buffered_proxy_stream = BufWriter::new(proxy_stream);
|
||||
let mut client = ImapClient::new(buffered_proxy_stream);
|
||||
let _greeting = client
|
||||
.read_response()
|
||||
.await
|
||||
@@ -309,10 +308,10 @@ impl Client {
|
||||
.run_command_and_check_ok("STARTTLS", None)
|
||||
.await
|
||||
.context("STARTTLS command failed")?;
|
||||
let buffered_socks5_stream = client.into_inner();
|
||||
let socks5_stream: Socks5Stream<_> = buffered_socks5_stream.into_inner();
|
||||
let buffered_proxy_stream = client.into_inner();
|
||||
let proxy_stream = buffered_proxy_stream.into_inner();
|
||||
|
||||
let tls_stream = wrap_tls(strict_tls, hostname, &[], socks5_stream)
|
||||
let tls_stream = wrap_tls(strict_tls, hostname, &[], proxy_stream)
|
||||
.await
|
||||
.context("STARTTLS upgrade failed")?;
|
||||
let buffered_stream = BufWriter::new(tls_stream);
|
||||
|
||||
61
src/imex.rs
61
src/imex.rs
@@ -14,6 +14,7 @@ use tokio_tar::Archive;
|
||||
|
||||
use crate::blob::BlobDirContents;
|
||||
use crate::chat::{self, delete_and_reset_all_device_msgs};
|
||||
use crate::config::Config;
|
||||
use crate::context::Context;
|
||||
use crate::e2ee;
|
||||
use crate::events::EventType;
|
||||
@@ -373,6 +374,9 @@ async fn import_backup_stream_inner<R: tokio::io::AsyncRead + Unpin>(
|
||||
.await
|
||||
.context("cannot import unpacked database");
|
||||
}
|
||||
if res.is_ok() {
|
||||
res = adjust_delete_server_after(context).await;
|
||||
}
|
||||
fs::remove_file(unpacked_database)
|
||||
.await
|
||||
.context("cannot remove unpacked database")
|
||||
@@ -677,6 +681,7 @@ async fn export_database(
|
||||
.to_str()
|
||||
.with_context(|| format!("path {} is not valid unicode", dest.display()))?;
|
||||
|
||||
adjust_delete_server_after(context).await?;
|
||||
context
|
||||
.sql
|
||||
.set_raw_config_int("backup_time", timestamp)
|
||||
@@ -706,6 +711,19 @@ async fn export_database(
|
||||
.await
|
||||
}
|
||||
|
||||
/// Sets `Config::DeleteServerAfter` to "never" if needed so that new messages are present on the
|
||||
/// server after a backup restoration or available for all devices in multi-device case.
|
||||
/// NB: Calling this after a backup import isn't reliable as we can crash in between, but this is a
|
||||
/// problem only for old backups, new backups already have `DeleteServerAfter` set if necessary.
|
||||
async fn adjust_delete_server_after(context: &Context) -> Result<()> {
|
||||
if context.is_chatmail().await? && !context.config_exists(Config::DeleteServerAfter).await? {
|
||||
context
|
||||
.set_config(Config::DeleteServerAfter, Some("0"))
|
||||
.await?;
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use std::time::Duration;
|
||||
@@ -891,6 +909,49 @@ mod tests {
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[tokio::test(flavor = "multi_thread", worker_threads = 2)]
|
||||
async fn test_export_import_chatmail_backup() -> Result<()> {
|
||||
let backup_dir = tempfile::tempdir().unwrap();
|
||||
|
||||
let context1 = &TestContext::new_alice().await;
|
||||
|
||||
// Check that the setting is displayed correctly.
|
||||
assert_eq!(
|
||||
context1.get_config(Config::DeleteServerAfter).await?,
|
||||
Some("0".to_string())
|
||||
);
|
||||
context1.set_config_bool(Config::IsChatmail, true).await?;
|
||||
assert_eq!(
|
||||
context1.get_config(Config::DeleteServerAfter).await?,
|
||||
Some("1".to_string())
|
||||
);
|
||||
|
||||
assert_eq!(context1.get_config_delete_server_after().await?, Some(0));
|
||||
imex(context1, ImexMode::ExportBackup, backup_dir.path(), None).await?;
|
||||
let _event = context1
|
||||
.evtracker
|
||||
.get_matching(|evt| matches!(evt, EventType::ImexProgress(1000)))
|
||||
.await;
|
||||
|
||||
let context2 = &TestContext::new().await;
|
||||
let backup = has_backup(context2, backup_dir.path()).await?;
|
||||
imex(context2, ImexMode::ImportBackup, backup.as_ref(), None).await?;
|
||||
let _event = context2
|
||||
.evtracker
|
||||
.get_matching(|evt| matches!(evt, EventType::ImexProgress(1000)))
|
||||
.await;
|
||||
assert!(context2.is_configured().await?);
|
||||
assert!(context2.is_chatmail().await?);
|
||||
for ctx in [context1, context2] {
|
||||
assert_eq!(
|
||||
ctx.get_config(Config::DeleteServerAfter).await?,
|
||||
Some("0".to_string())
|
||||
);
|
||||
assert_eq!(ctx.get_config_delete_server_after().await?, None);
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// This is a regression test for
|
||||
/// https://github.com/deltachat/deltachat-android/issues/2263
|
||||
/// where the config cache wasn't reset properly after a backup.
|
||||
|
||||
@@ -84,7 +84,6 @@ mod scheduler;
|
||||
pub mod securejoin;
|
||||
mod simplify;
|
||||
mod smtp;
|
||||
mod socks;
|
||||
pub mod stock_str;
|
||||
mod sync;
|
||||
mod timesmearing;
|
||||
|
||||
@@ -11,8 +11,8 @@ use crate::configure::server_params::{expand_param_vector, ServerParams};
|
||||
use crate::constants::{DC_LP_AUTH_FLAGS, DC_LP_AUTH_NORMAL, DC_LP_AUTH_OAUTH2};
|
||||
use crate::context::Context;
|
||||
use crate::net::load_connection_timestamp;
|
||||
use crate::net::proxy::ProxyConfig;
|
||||
use crate::provider::{Protocol, Provider, Socket, UsernamePattern};
|
||||
use crate::socks::Socks5Config;
|
||||
use crate::sql::Sql;
|
||||
|
||||
/// User-entered setting for certificate checks.
|
||||
@@ -116,7 +116,8 @@ pub struct EnteredLoginParam {
|
||||
/// invalid hostnames
|
||||
pub certificate_checks: EnteredCertificateChecks,
|
||||
|
||||
pub socks5_config: Option<Socks5Config>,
|
||||
/// Proxy configuration.
|
||||
pub proxy_config: Option<ProxyConfig>,
|
||||
|
||||
pub oauth2: bool,
|
||||
}
|
||||
@@ -195,7 +196,7 @@ impl EnteredLoginParam {
|
||||
.unwrap_or_default();
|
||||
let oauth2 = matches!(server_flags & DC_LP_AUTH_FLAGS, DC_LP_AUTH_OAUTH2);
|
||||
|
||||
let socks5_config = Socks5Config::from_database(&context.sql).await?;
|
||||
let proxy_config = ProxyConfig::load(context).await?;
|
||||
|
||||
Ok(EnteredLoginParam {
|
||||
addr,
|
||||
@@ -214,7 +215,7 @@ impl EnteredLoginParam {
|
||||
password: send_pw,
|
||||
},
|
||||
certificate_checks,
|
||||
socks5_config,
|
||||
proxy_config,
|
||||
oauth2,
|
||||
})
|
||||
}
|
||||
@@ -380,7 +381,8 @@ pub struct ConfiguredLoginParam {
|
||||
|
||||
pub smtp_password: String,
|
||||
|
||||
pub socks5_config: Option<Socks5Config>,
|
||||
/// Proxy configuration.
|
||||
pub proxy_config: Option<ProxyConfig>,
|
||||
|
||||
pub provider: Option<&'static Provider>,
|
||||
|
||||
@@ -679,7 +681,7 @@ impl ConfiguredLoginParam {
|
||||
}];
|
||||
}
|
||||
|
||||
let socks5_config = Socks5Config::from_database(&context.sql).await?;
|
||||
let proxy_config = ProxyConfig::load(context).await?;
|
||||
|
||||
Ok(Some(ConfiguredLoginParam {
|
||||
addr,
|
||||
@@ -691,7 +693,7 @@ impl ConfiguredLoginParam {
|
||||
smtp_password: send_pw,
|
||||
certificate_checks,
|
||||
provider,
|
||||
socks5_config,
|
||||
proxy_config,
|
||||
oauth2,
|
||||
}))
|
||||
}
|
||||
@@ -778,7 +780,7 @@ impl ConfiguredLoginParam {
|
||||
let provider_strict_tls = self.provider.map(|provider| provider.opt.strict_tls);
|
||||
match self.certificate_checks {
|
||||
ConfiguredCertificateChecks::OldAutomatic => {
|
||||
provider_strict_tls.unwrap_or(self.socks5_config.is_some())
|
||||
provider_strict_tls.unwrap_or(self.proxy_config.is_some())
|
||||
}
|
||||
ConfiguredCertificateChecks::Automatic => provider_strict_tls.unwrap_or(true),
|
||||
ConfiguredCertificateChecks::Strict => true,
|
||||
@@ -863,8 +865,8 @@ mod tests {
|
||||
}],
|
||||
smtp_user: "".to_string(),
|
||||
smtp_password: "bar".to_string(),
|
||||
// socks5_config is not saved by `save_to_database`, using default value
|
||||
socks5_config: None,
|
||||
// proxy_config is not saved by `save_to_database`, using default value
|
||||
proxy_config: None,
|
||||
provider: None,
|
||||
certificate_checks: ConfiguredCertificateChecks::Strict,
|
||||
oauth2: false,
|
||||
@@ -967,7 +969,7 @@ mod tests {
|
||||
],
|
||||
smtp_user: "alice@posteo.de".to_string(),
|
||||
smtp_password: "foobarbaz".to_string(),
|
||||
socks5_config: None,
|
||||
proxy_config: None,
|
||||
provider: get_provider_by_id("posteo"),
|
||||
certificate_checks: ConfiguredCertificateChecks::Strict,
|
||||
oauth2: false,
|
||||
|
||||
@@ -17,6 +17,7 @@ use crate::tools::time;
|
||||
|
||||
pub(crate) mod dns;
|
||||
pub(crate) mod http;
|
||||
pub(crate) mod proxy;
|
||||
pub(crate) mod session;
|
||||
pub(crate) mod tls;
|
||||
|
||||
|
||||
@@ -8,9 +8,9 @@ use mime::Mime;
|
||||
use serde::Serialize;
|
||||
|
||||
use crate::context::Context;
|
||||
use crate::net::proxy::ProxyConfig;
|
||||
use crate::net::session::SessionStream;
|
||||
use crate::net::tls::wrap_tls;
|
||||
use crate::socks::Socks5Config;
|
||||
|
||||
/// HTTP(S) GET response.
|
||||
#[derive(Debug)]
|
||||
@@ -43,7 +43,7 @@ where
|
||||
{
|
||||
let scheme = parsed_url.scheme_str().context("URL has no scheme")?;
|
||||
let host = parsed_url.host().context("URL has no host")?;
|
||||
let socks5_config_opt = Socks5Config::from_database(&context.sql).await?;
|
||||
let proxy_config_opt = ProxyConfig::load(context).await?;
|
||||
|
||||
let stream: Box<dyn SessionStream> = match scheme {
|
||||
"http" => {
|
||||
@@ -54,11 +54,11 @@ where
|
||||
// better resolve from scratch each time to prevent
|
||||
// cache poisoning attacks from having lasting effects.
|
||||
let load_cache = false;
|
||||
if let Some(socks5_config) = socks5_config_opt {
|
||||
let socks5_stream = socks5_config
|
||||
if let Some(proxy_config) = proxy_config_opt {
|
||||
let proxy_stream = proxy_config
|
||||
.connect(context, host, port, load_cache)
|
||||
.await?;
|
||||
Box::new(socks5_stream)
|
||||
Box::new(proxy_stream)
|
||||
} else {
|
||||
let tcp_stream = crate::net::connect_tcp(context, host, port, load_cache).await?;
|
||||
Box::new(tcp_stream)
|
||||
@@ -69,11 +69,11 @@ where
|
||||
let load_cache = true;
|
||||
let strict_tls = true;
|
||||
|
||||
if let Some(socks5_config) = socks5_config_opt {
|
||||
let socks5_stream = socks5_config
|
||||
if let Some(proxy_config) = proxy_config_opt {
|
||||
let proxy_stream = proxy_config
|
||||
.connect(context, host, port, load_cache)
|
||||
.await?;
|
||||
let tls_stream = wrap_tls(strict_tls, host, &[], socks5_stream).await?;
|
||||
let tls_stream = wrap_tls(strict_tls, host, &[], proxy_stream).await?;
|
||||
Box::new(tls_stream)
|
||||
} else {
|
||||
let tcp_stream = crate::net::connect_tcp(context, host, port, load_cache).await?;
|
||||
|
||||
435
src/net/proxy.rs
Normal file
435
src/net/proxy.rs
Normal file
@@ -0,0 +1,435 @@
|
||||
//! # Proxy support.
|
||||
//!
|
||||
//! Delta Chat supports SOCKS5 and Shadowsocks protocols.
|
||||
|
||||
use std::fmt;
|
||||
use std::pin::Pin;
|
||||
|
||||
use anyhow::{format_err, Context as _, Result};
|
||||
use fast_socks5::client::Socks5Stream;
|
||||
use fast_socks5::util::target_addr::ToTargetAddr;
|
||||
use fast_socks5::AuthenticationMethod;
|
||||
use fast_socks5::Socks5Command;
|
||||
use percent_encoding::{percent_encode, NON_ALPHANUMERIC};
|
||||
use pin_project::pin_project;
|
||||
use tokio::io::{AsyncRead, AsyncWrite};
|
||||
use tokio::net::TcpStream;
|
||||
use tokio_io_timeout::TimeoutStream;
|
||||
|
||||
use crate::config::Config;
|
||||
use crate::context::Context;
|
||||
use crate::net::connect_tcp;
|
||||
use crate::net::session::SessionStream;
|
||||
use crate::sql::Sql;
|
||||
|
||||
/// Default SOCKS5 port according to [RFC 1928](https://tools.ietf.org/html/rfc1928).
|
||||
pub const DEFAULT_SOCKS_PORT: u16 = 1080;
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct ShadowsocksConfig {
|
||||
pub server_config: shadowsocks::config::ServerConfig,
|
||||
}
|
||||
|
||||
impl PartialEq for ShadowsocksConfig {
|
||||
fn eq(&self, other: &Self) -> bool {
|
||||
self.server_config.to_url() == other.server_config.to_url()
|
||||
}
|
||||
}
|
||||
|
||||
impl Eq for ShadowsocksConfig {}
|
||||
|
||||
/// Wrapper for Shadowsocks stream implementing
|
||||
/// `Debug` and `SessionStream`.
|
||||
///
|
||||
/// Passes `AsyncRead` and `AsyncWrite` traits through.
|
||||
#[pin_project]
|
||||
pub(crate) struct ShadowsocksStream<S> {
|
||||
#[pin]
|
||||
pub(crate) stream: shadowsocks::ProxyClientStream<S>,
|
||||
}
|
||||
|
||||
impl<S> std::fmt::Debug for ShadowsocksStream<S> {
|
||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||
write!(f, "ShadowsocksStream")
|
||||
}
|
||||
}
|
||||
|
||||
impl<S> AsyncRead for ShadowsocksStream<S>
|
||||
where
|
||||
S: AsyncRead + AsyncWrite + Unpin,
|
||||
{
|
||||
fn poll_read(
|
||||
self: Pin<&mut Self>,
|
||||
cx: &mut std::task::Context<'_>,
|
||||
buf: &mut tokio::io::ReadBuf<'_>,
|
||||
) -> std::task::Poll<std::io::Result<()>> {
|
||||
self.project().stream.poll_read(cx, buf)
|
||||
}
|
||||
}
|
||||
|
||||
impl<S> AsyncWrite for ShadowsocksStream<S>
|
||||
where
|
||||
S: AsyncRead + AsyncWrite + Unpin,
|
||||
{
|
||||
fn poll_write(
|
||||
self: Pin<&mut Self>,
|
||||
cx: &mut std::task::Context<'_>,
|
||||
buf: &[u8],
|
||||
) -> std::task::Poll<Result<usize, std::io::Error>> {
|
||||
self.project().stream.poll_write(cx, buf)
|
||||
}
|
||||
|
||||
fn poll_flush(
|
||||
self: Pin<&mut Self>,
|
||||
cx: &mut std::task::Context<'_>,
|
||||
) -> std::task::Poll<Result<(), std::io::Error>> {
|
||||
self.project().stream.poll_flush(cx)
|
||||
}
|
||||
|
||||
fn poll_shutdown(
|
||||
self: Pin<&mut Self>,
|
||||
cx: &mut std::task::Context<'_>,
|
||||
) -> std::task::Poll<Result<(), std::io::Error>> {
|
||||
self.project().stream.poll_shutdown(cx)
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, PartialEq, Eq)]
|
||||
pub struct Socks5Config {
|
||||
pub host: String,
|
||||
pub port: u16,
|
||||
pub user_password: Option<(String, String)>,
|
||||
}
|
||||
|
||||
impl Socks5Config {
|
||||
async fn connect(
|
||||
&self,
|
||||
context: &Context,
|
||||
target_host: &str,
|
||||
target_port: u16,
|
||||
load_dns_cache: bool,
|
||||
) -> Result<Socks5Stream<Pin<Box<TimeoutStream<TcpStream>>>>> {
|
||||
let tcp_stream = connect_tcp(context, &self.host, self.port, load_dns_cache)
|
||||
.await
|
||||
.context("Failed to connect to SOCKS5 proxy")?;
|
||||
|
||||
let authentication_method = if let Some((username, password)) = self.user_password.as_ref()
|
||||
{
|
||||
Some(AuthenticationMethod::Password {
|
||||
username: username.into(),
|
||||
password: password.into(),
|
||||
})
|
||||
} else {
|
||||
None
|
||||
};
|
||||
let mut socks_stream =
|
||||
Socks5Stream::use_stream(tcp_stream, authentication_method, Default::default()).await?;
|
||||
let target_addr = (target_host, target_port).to_target_addr()?;
|
||||
socks_stream
|
||||
.request(Socks5Command::TCPConnect, target_addr)
|
||||
.await?;
|
||||
|
||||
Ok(socks_stream)
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, PartialEq, Eq)]
|
||||
pub enum ProxyConfig {
|
||||
Socks5(Socks5Config),
|
||||
|
||||
Shadowsocks(ShadowsocksConfig),
|
||||
}
|
||||
|
||||
impl ProxyConfig {
|
||||
/// Creates a new proxy configuration by parsing given proxy URL.
|
||||
fn from_url(url: &str) -> Result<Self> {
|
||||
let url = url::Url::parse(url).context("Cannot parse proxy URL")?;
|
||||
match url.scheme() {
|
||||
"ss" => {
|
||||
let server_config = shadowsocks::config::ServerConfig::from_url(url.as_str())?;
|
||||
let shadowsocks_config = ShadowsocksConfig { server_config };
|
||||
Ok(Self::Shadowsocks(shadowsocks_config))
|
||||
}
|
||||
|
||||
// Because of `curl` convention,
|
||||
// `socks5` URL scheme may be expected to resolve domain names locally
|
||||
// with `socks5h` URL scheme meaning that hostnames are passed to the proxy.
|
||||
// Resolving hostnames locally is not supported
|
||||
// in Delta Chat when using a proxy
|
||||
// to prevent DNS leaks.
|
||||
// Because of this we do not distinguish
|
||||
// between `socks5` and `socks5h`.
|
||||
"socks5" => {
|
||||
let host = url
|
||||
.host_str()
|
||||
.context("socks5 URL has no host")?
|
||||
.to_string();
|
||||
let port = url.port().unwrap_or(DEFAULT_SOCKS_PORT);
|
||||
let user_password = if let Some(password) = url.password() {
|
||||
let username = percent_encoding::percent_decode_str(url.username())
|
||||
.decode_utf8()
|
||||
.context("SOCKS5 username is not a valid UTF-8")?
|
||||
.to_string();
|
||||
let password = percent_encoding::percent_decode_str(password)
|
||||
.decode_utf8()
|
||||
.context("SOCKS5 password is not a valid UTF-8")?
|
||||
.to_string();
|
||||
Some((username, password))
|
||||
} else {
|
||||
None
|
||||
};
|
||||
let socks5_config = Socks5Config {
|
||||
host,
|
||||
port,
|
||||
user_password,
|
||||
};
|
||||
Ok(Self::Socks5(socks5_config))
|
||||
}
|
||||
scheme => Err(format_err!("Unknown URL scheme {scheme:?}")),
|
||||
}
|
||||
}
|
||||
|
||||
/// Migrates legacy `socks5_host`, `socks5_port`, `socks5_user` and `socks5_password`
|
||||
/// config into `proxy_url` if `proxy_url` is unset or empty.
|
||||
///
|
||||
/// Unsets `socks5_host`, `socks5_port`, `socks5_user` and `socks5_password` in any case.
|
||||
async fn migrate_socks_config(sql: &Sql) -> Result<()> {
|
||||
if sql.get_raw_config("proxy_url").await?.is_none() {
|
||||
// Load legacy SOCKS5 settings.
|
||||
if let Some(host) = sql
|
||||
.get_raw_config("socks5_host")
|
||||
.await?
|
||||
.filter(|s| !s.is_empty())
|
||||
{
|
||||
let port: u16 = sql
|
||||
.get_raw_config_int("socks5_port")
|
||||
.await?
|
||||
.unwrap_or(DEFAULT_SOCKS_PORT.into()) as u16;
|
||||
let user = sql.get_raw_config("socks5_user").await?.unwrap_or_default();
|
||||
let pass = sql
|
||||
.get_raw_config("socks5_password")
|
||||
.await?
|
||||
.unwrap_or_default();
|
||||
|
||||
let mut proxy_url = "socks5://".to_string();
|
||||
if !pass.is_empty() {
|
||||
proxy_url += &percent_encode(user.as_bytes(), NON_ALPHANUMERIC).to_string();
|
||||
proxy_url += ":";
|
||||
proxy_url += &percent_encode(pass.as_bytes(), NON_ALPHANUMERIC).to_string();
|
||||
proxy_url += "@";
|
||||
};
|
||||
proxy_url += &host;
|
||||
proxy_url += ":";
|
||||
proxy_url += &port.to_string();
|
||||
|
||||
sql.set_raw_config("proxy_url", Some(&proxy_url)).await?;
|
||||
} else {
|
||||
sql.set_raw_config("proxy_url", Some("")).await?;
|
||||
}
|
||||
|
||||
let socks5_enabled = sql.get_raw_config("socks5_enabled").await?;
|
||||
sql.set_raw_config("proxy_enabled", socks5_enabled.as_deref())
|
||||
.await?;
|
||||
}
|
||||
|
||||
sql.set_raw_config("socks5_enabled", None).await?;
|
||||
sql.set_raw_config("socks5_host", None).await?;
|
||||
sql.set_raw_config("socks5_port", None).await?;
|
||||
sql.set_raw_config("socks5_user", None).await?;
|
||||
sql.set_raw_config("socks5_password", None).await?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Reads proxy configuration from the database.
|
||||
pub async fn load(context: &Context) -> Result<Option<Self>> {
|
||||
Self::migrate_socks_config(&context.sql)
|
||||
.await
|
||||
.context("Failed to migrate legacy SOCKS config")?;
|
||||
|
||||
let enabled = context.get_config_bool(Config::ProxyEnabled).await?;
|
||||
if !enabled {
|
||||
return Ok(None);
|
||||
}
|
||||
|
||||
let proxy_url = context
|
||||
.get_config(Config::ProxyUrl)
|
||||
.await?
|
||||
.unwrap_or_default();
|
||||
let proxy_url = proxy_url
|
||||
.split_once('\n')
|
||||
.map_or(proxy_url.clone(), |(first_url, _rest)| {
|
||||
first_url.to_string()
|
||||
});
|
||||
let proxy_config = Self::from_url(&proxy_url).context("Failed to parse proxy URL")?;
|
||||
Ok(Some(proxy_config))
|
||||
}
|
||||
|
||||
/// If `load_dns_cache` is true, loads cached DNS resolution results.
|
||||
/// Use this only if the connection is going to be protected with TLS checks.
|
||||
pub async fn connect(
|
||||
&self,
|
||||
context: &Context,
|
||||
target_host: &str,
|
||||
target_port: u16,
|
||||
load_dns_cache: bool,
|
||||
) -> Result<Box<dyn SessionStream>> {
|
||||
match self {
|
||||
ProxyConfig::Socks5(socks5_config) => {
|
||||
let socks5_stream = socks5_config
|
||||
.connect(context, target_host, target_port, load_dns_cache)
|
||||
.await?;
|
||||
Ok(Box::new(socks5_stream))
|
||||
}
|
||||
ProxyConfig::Shadowsocks(ShadowsocksConfig { server_config }) => {
|
||||
let shadowsocks_context = shadowsocks::context::Context::new_shared(
|
||||
shadowsocks::config::ServerType::Local,
|
||||
);
|
||||
|
||||
let tcp_stream = {
|
||||
let server_addr = server_config.addr();
|
||||
let host = server_addr.host();
|
||||
let port = server_addr.port();
|
||||
connect_tcp(context, &host, port, load_dns_cache)
|
||||
.await
|
||||
.context("Failed to connect to Shadowsocks proxy")?
|
||||
};
|
||||
|
||||
let proxy_client_stream = shadowsocks::ProxyClientStream::from_stream(
|
||||
shadowsocks_context,
|
||||
tcp_stream,
|
||||
server_config,
|
||||
(target_host.to_string(), target_port),
|
||||
);
|
||||
let shadowsocks_stream = ShadowsocksStream {
|
||||
stream: proxy_client_stream,
|
||||
};
|
||||
|
||||
Ok(Box::new(shadowsocks_stream))
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl fmt::Display for Socks5Config {
|
||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||
write!(
|
||||
f,
|
||||
"host:{},port:{},user_password:{}",
|
||||
self.host,
|
||||
self.port,
|
||||
if let Some(user_password) = self.user_password.clone() {
|
||||
format!("user: {}, password: ***", user_password.0)
|
||||
} else {
|
||||
"user: None".to_string()
|
||||
}
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
use crate::config::Config;
|
||||
use crate::test_utils::TestContext;
|
||||
|
||||
#[test]
|
||||
fn test_socks5_url() {
|
||||
let proxy_config = ProxyConfig::from_url("socks5://127.0.0.1:9050").unwrap();
|
||||
assert_eq!(
|
||||
proxy_config,
|
||||
ProxyConfig::Socks5(Socks5Config {
|
||||
host: "127.0.0.1".to_string(),
|
||||
port: 9050,
|
||||
user_password: None
|
||||
})
|
||||
);
|
||||
|
||||
let proxy_config = ProxyConfig::from_url("socks5://foo:bar@127.0.0.1:9150").unwrap();
|
||||
assert_eq!(
|
||||
proxy_config,
|
||||
ProxyConfig::Socks5(Socks5Config {
|
||||
host: "127.0.0.1".to_string(),
|
||||
port: 9150,
|
||||
user_password: Some(("foo".to_string(), "bar".to_string()))
|
||||
})
|
||||
);
|
||||
|
||||
let proxy_config = ProxyConfig::from_url("socks5://%66oo:b%61r@127.0.0.1:9150").unwrap();
|
||||
assert_eq!(
|
||||
proxy_config,
|
||||
ProxyConfig::Socks5(Socks5Config {
|
||||
host: "127.0.0.1".to_string(),
|
||||
port: 9150,
|
||||
user_password: Some(("foo".to_string(), "bar".to_string()))
|
||||
})
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_shadowsocks_url() {
|
||||
// Example URL from <https://shadowsocks.org/doc/sip002.html>.
|
||||
let proxy_config =
|
||||
ProxyConfig::from_url("ss://YWVzLTEyOC1nY206dGVzdA@192.168.100.1:8888#Example1")
|
||||
.unwrap();
|
||||
assert!(matches!(proxy_config, ProxyConfig::Shadowsocks(_)));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_invalid_proxy_url() {
|
||||
assert!(ProxyConfig::from_url("foobar://127.0.0.1:9050").is_err());
|
||||
assert!(ProxyConfig::from_url("abc").is_err());
|
||||
}
|
||||
|
||||
#[tokio::test(flavor = "multi_thread", worker_threads = 2)]
|
||||
async fn test_socks5_migration() -> Result<()> {
|
||||
let t = TestContext::new().await;
|
||||
|
||||
// Test that config is migrated on attempt to load even if disabled.
|
||||
t.set_config(Config::Socks5Host, Some("127.0.0.1")).await?;
|
||||
t.set_config(Config::Socks5Port, Some("9050")).await?;
|
||||
|
||||
let proxy_config = ProxyConfig::load(&t).await?;
|
||||
// Even though proxy is not enabled, config should be migrated.
|
||||
assert_eq!(proxy_config, None);
|
||||
|
||||
assert_eq!(
|
||||
t.get_config(Config::ProxyUrl).await?.unwrap(),
|
||||
"socks5://127.0.0.1:9050"
|
||||
);
|
||||
Ok(())
|
||||
}
|
||||
|
||||
// Test SOCKS5 setting migration if proxy was never configured.
|
||||
#[tokio::test(flavor = "multi_thread", worker_threads = 2)]
|
||||
async fn test_socks5_migration_unconfigured() -> Result<()> {
|
||||
let t = TestContext::new().await;
|
||||
|
||||
// Try to load config to trigger migration.
|
||||
assert_eq!(ProxyConfig::load(&t).await?, None);
|
||||
|
||||
assert_eq!(t.get_config(Config::ProxyEnabled).await?, None);
|
||||
assert_eq!(
|
||||
t.get_config(Config::ProxyUrl).await?.unwrap(),
|
||||
String::new()
|
||||
);
|
||||
Ok(())
|
||||
}
|
||||
|
||||
// Test SOCKS5 setting migration if SOCKS5 host is empty.
|
||||
#[tokio::test(flavor = "multi_thread", worker_threads = 2)]
|
||||
async fn test_socks5_migration_empty() -> Result<()> {
|
||||
let t = TestContext::new().await;
|
||||
|
||||
t.set_config(Config::Socks5Host, Some("")).await?;
|
||||
|
||||
// Try to load config to trigger migration.
|
||||
assert_eq!(ProxyConfig::load(&t).await?, None);
|
||||
|
||||
assert_eq!(t.get_config(Config::ProxyEnabled).await?, None);
|
||||
assert_eq!(
|
||||
t.get_config(Config::ProxyUrl).await?.unwrap(),
|
||||
String::new()
|
||||
);
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
@@ -1,3 +1,4 @@
|
||||
use crate::net::proxy::ShadowsocksStream;
|
||||
use async_native_tls::TlsStream;
|
||||
use fast_socks5::client::Socks5Stream;
|
||||
use std::pin::Pin;
|
||||
@@ -44,6 +45,11 @@ impl<T: SessionStream> SessionStream for Socks5Stream<T> {
|
||||
self.get_socket_mut().set_read_timeout(timeout)
|
||||
}
|
||||
}
|
||||
impl<T: SessionStream> SessionStream for ShadowsocksStream<T> {
|
||||
fn set_read_timeout(&mut self, timeout: Option<Duration>) {
|
||||
self.stream.get_mut().set_read_timeout(timeout)
|
||||
}
|
||||
}
|
||||
|
||||
/// Session stream with a read buffer.
|
||||
pub(crate) trait SessionBufStream: SessionStream + AsyncBufRead {}
|
||||
|
||||
@@ -62,8 +62,8 @@ pub async fn get_oauth2_url(
|
||||
addr: &str,
|
||||
redirect_uri: &str,
|
||||
) -> Result<Option<String>> {
|
||||
let socks5_enabled = context.get_config_bool(Config::Socks5Enabled).await?;
|
||||
if let Some(oauth2) = Oauth2::from_address(context, addr, socks5_enabled).await {
|
||||
let proxy_enabled = context.get_config_bool(Config::ProxyEnabled).await?;
|
||||
if let Some(oauth2) = Oauth2::from_address(context, addr, proxy_enabled).await {
|
||||
context
|
||||
.sql
|
||||
.set_raw_config("oauth2_pending_redirect_uri", Some(redirect_uri))
|
||||
@@ -83,8 +83,8 @@ pub(crate) async fn get_oauth2_access_token(
|
||||
code: &str,
|
||||
regenerate: bool,
|
||||
) -> Result<Option<String>> {
|
||||
let socks5_enabled = context.get_config_bool(Config::Socks5Enabled).await?;
|
||||
if let Some(oauth2) = Oauth2::from_address(context, addr, socks5_enabled).await {
|
||||
let proxy_enabled = context.get_config_bool(Config::ProxyEnabled).await?;
|
||||
if let Some(oauth2) = Oauth2::from_address(context, addr, proxy_enabled).await {
|
||||
let lock = context.oauth2_mutex.lock().await;
|
||||
|
||||
// read generated token
|
||||
@@ -232,8 +232,8 @@ pub(crate) async fn get_oauth2_addr(
|
||||
addr: &str,
|
||||
code: &str,
|
||||
) -> Result<Option<String>> {
|
||||
let socks5_enabled = context.get_config_bool(Config::Socks5Enabled).await?;
|
||||
let oauth2 = match Oauth2::from_address(context, addr, socks5_enabled).await {
|
||||
let proxy_enabled = context.get_config_bool(Config::ProxyEnabled).await?;
|
||||
let oauth2 = match Oauth2::from_address(context, addr, proxy_enabled).await {
|
||||
Some(o) => o,
|
||||
None => return Ok(None),
|
||||
};
|
||||
|
||||
95
src/qr.rs
95
src/qr.rs
@@ -7,7 +7,7 @@ use anyhow::{anyhow, bail, ensure, Context as _, Result};
|
||||
pub use dclogin_scheme::LoginOptions;
|
||||
use deltachat_contact_tools::{addr_normalize, may_be_valid_addr, ContactAddress};
|
||||
use once_cell::sync::Lazy;
|
||||
use percent_encoding::percent_decode_str;
|
||||
use percent_encoding::{percent_decode_str, percent_encode, NON_ALPHANUMERIC};
|
||||
use serde::Deserialize;
|
||||
|
||||
use self::dclogin_scheme::configure_from_login_qr;
|
||||
@@ -20,6 +20,7 @@ use crate::events::EventType;
|
||||
use crate::key::Fingerprint;
|
||||
use crate::message::Message;
|
||||
use crate::net::http::post_empty;
|
||||
use crate::net::proxy::DEFAULT_SOCKS_PORT;
|
||||
use crate::peerstate::Peerstate;
|
||||
use crate::token;
|
||||
use crate::tools::validate_id;
|
||||
@@ -541,16 +542,15 @@ fn decode_webrtc_instance(_context: &Context, qr: &str) -> Result<Qr> {
|
||||
fn decode_tg_socks_proxy(_context: &Context, qr: &str) -> Result<Qr> {
|
||||
let url = url::Url::parse(qr).context("Invalid t.me/socks url")?;
|
||||
|
||||
const SOCKS5_DEFAULT_PORT: u16 = 1080;
|
||||
let mut host: Option<String> = None;
|
||||
let mut port: u16 = SOCKS5_DEFAULT_PORT;
|
||||
let mut port: u16 = DEFAULT_SOCKS_PORT;
|
||||
let mut user: Option<String> = None;
|
||||
let mut pass: Option<String> = None;
|
||||
for (key, value) in url.query_pairs() {
|
||||
if key == "server" {
|
||||
host = Some(value.to_string());
|
||||
} else if key == "port" {
|
||||
port = value.parse().unwrap_or(SOCKS5_DEFAULT_PORT);
|
||||
port = value.parse().unwrap_or(DEFAULT_SOCKS_PORT);
|
||||
} else if key == "user" {
|
||||
user = Some(value.to_string());
|
||||
} else if key == "pass" {
|
||||
@@ -661,22 +661,33 @@ pub async fn set_config_from_qr(context: &Context, qr: &str) -> Result<()> {
|
||||
user,
|
||||
pass,
|
||||
} => {
|
||||
// disable proxy before changing settings to not use a combination of old and new
|
||||
context
|
||||
.set_config_bool(Config::Socks5Enabled, false)
|
||||
.await?;
|
||||
let mut proxy_url = "socks5://".to_string();
|
||||
if let Some(pass) = pass {
|
||||
proxy_url += &percent_encode(user.unwrap_or_default().as_bytes(), NON_ALPHANUMERIC)
|
||||
.to_string();
|
||||
proxy_url += ":";
|
||||
proxy_url += &percent_encode(pass.as_bytes(), NON_ALPHANUMERIC).to_string();
|
||||
proxy_url += "@";
|
||||
};
|
||||
proxy_url += &host;
|
||||
proxy_url += ":";
|
||||
proxy_url += &port.to_string();
|
||||
|
||||
context.set_config(Config::Socks5Host, Some(&host)).await?;
|
||||
let old_proxy_url_value = context
|
||||
.get_config(Config::ProxyUrl)
|
||||
.await?
|
||||
.unwrap_or_default();
|
||||
let proxy_urls: Vec<&str> = std::iter::once(proxy_url.as_str())
|
||||
.chain(
|
||||
old_proxy_url_value
|
||||
.split('\n')
|
||||
.filter(|s| !s.is_empty() && *s != proxy_url),
|
||||
)
|
||||
.collect();
|
||||
context
|
||||
.set_config_u32(Config::Socks5Port, u32::from(port))
|
||||
.set_config(Config::ProxyUrl, Some(&proxy_urls.join("\n")))
|
||||
.await?;
|
||||
context
|
||||
.set_config(Config::Socks5User, user.as_deref())
|
||||
.await?;
|
||||
context
|
||||
.set_config(Config::Socks5Password, pass.as_deref())
|
||||
.await?;
|
||||
context.set_config_bool(Config::Socks5Enabled, true).await?;
|
||||
context.set_config_bool(Config::ProxyEnabled, true).await?;
|
||||
}
|
||||
Qr::WithdrawVerifyContact {
|
||||
invitenumber,
|
||||
@@ -1630,50 +1641,48 @@ mod tests {
|
||||
async fn test_set_socks5_proxy_config_from_qr() -> Result<()> {
|
||||
let t = TestContext::new().await;
|
||||
|
||||
assert_eq!(t.get_config_bool(Config::Socks5Enabled).await?, false);
|
||||
assert_eq!(t.get_config_bool(Config::ProxyEnabled).await?, false);
|
||||
|
||||
let res = set_config_from_qr(&t, "https://t.me/socks?server=foo&port=666").await;
|
||||
assert!(res.is_ok());
|
||||
assert_eq!(t.get_config_bool(Config::Socks5Enabled).await?, true);
|
||||
assert_eq!(t.get_config_bool(Config::ProxyEnabled).await?, true);
|
||||
assert_eq!(
|
||||
t.get_config(Config::Socks5Host).await?,
|
||||
Some("foo".to_string())
|
||||
t.get_config(Config::ProxyUrl).await?,
|
||||
Some("socks5://foo:666".to_string())
|
||||
);
|
||||
assert_eq!(t.get_config_u32(Config::Socks5Port).await?, 666);
|
||||
assert_eq!(t.get_config(Config::Socks5User).await?, None);
|
||||
assert_eq!(t.get_config(Config::Socks5Password).await?, None);
|
||||
|
||||
// make sure, user&password are reset when not specified in the URL
|
||||
t.set_config(Config::Socks5User, Some("alice")).await?;
|
||||
t.set_config(Config::Socks5Password, Some("secret")).await?;
|
||||
// Test URL without port.
|
||||
let res = set_config_from_qr(&t, "https://t.me/socks?server=1.2.3.4").await;
|
||||
assert!(res.is_ok());
|
||||
assert_eq!(t.get_config_bool(Config::Socks5Enabled).await?, true);
|
||||
assert_eq!(t.get_config_bool(Config::ProxyEnabled).await?, true);
|
||||
assert_eq!(
|
||||
t.get_config(Config::Socks5Host).await?,
|
||||
Some("1.2.3.4".to_string())
|
||||
t.get_config(Config::ProxyUrl).await?,
|
||||
Some("socks5://1.2.3.4:1080\nsocks5://foo:666".to_string())
|
||||
);
|
||||
assert_eq!(t.get_config_u32(Config::Socks5Port).await?, 1080);
|
||||
assert_eq!(t.get_config(Config::Socks5User).await?, None);
|
||||
assert_eq!(t.get_config(Config::Socks5Password).await?, None);
|
||||
|
||||
// make sure, user&password are set when specified in the URL
|
||||
// Password is an URL-encoded "x&%$X".
|
||||
let res =
|
||||
set_config_from_qr(&t, "https://t.me/socks?server=jau&user=Da&pass=x%26%25%24X").await;
|
||||
assert!(res.is_ok());
|
||||
assert_eq!(t.get_config_bool(Config::Socks5Enabled).await?, true);
|
||||
assert_eq!(
|
||||
t.get_config(Config::Socks5Host).await?,
|
||||
Some("jau".to_string())
|
||||
t.get_config(Config::ProxyUrl).await?,
|
||||
Some(
|
||||
"socks5://Da:x%26%25%24X@jau:1080\nsocks5://1.2.3.4:1080\nsocks5://foo:666"
|
||||
.to_string()
|
||||
)
|
||||
);
|
||||
assert_eq!(t.get_config_u32(Config::Socks5Port).await?, 1080);
|
||||
|
||||
// Scanning existing proxy brings it to the top in the list.
|
||||
let res = set_config_from_qr(&t, "https://t.me/socks?server=foo&port=666").await;
|
||||
assert!(res.is_ok());
|
||||
assert_eq!(t.get_config_bool(Config::ProxyEnabled).await?, true);
|
||||
assert_eq!(
|
||||
t.get_config(Config::Socks5User).await?,
|
||||
Some("Da".to_string())
|
||||
);
|
||||
assert_eq!(
|
||||
t.get_config(Config::Socks5Password).await?,
|
||||
Some("x&%$X".to_string())
|
||||
t.get_config(Config::ProxyUrl).await?,
|
||||
Some(
|
||||
"socks5://foo:666\nsocks5://Da:x%26%25%24X@jau:1080\nsocks5://1.2.3.4:1080"
|
||||
.to_string()
|
||||
)
|
||||
);
|
||||
|
||||
Ok(())
|
||||
|
||||
@@ -72,7 +72,7 @@ pub(super) fn decode_login(qr: &str) -> Result<Qr> {
|
||||
.unwrap_or(url_without_scheme);
|
||||
|
||||
let addr = payload
|
||||
.split(|c| c == '?' || c == '/')
|
||||
.split(['?', '/'])
|
||||
.next()
|
||||
.context("invalid DCLOGIN payload E3")?;
|
||||
|
||||
|
||||
@@ -18,9 +18,9 @@ use crate::login_param::{ConfiguredLoginParam, ConfiguredServerLoginParam};
|
||||
use crate::message::Message;
|
||||
use crate::message::{self, MsgId};
|
||||
use crate::mimefactory::MimeFactory;
|
||||
use crate::net::proxy::ProxyConfig;
|
||||
use crate::net::session::SessionBufStream;
|
||||
use crate::scheduler::connectivity::ConnectivityStore;
|
||||
use crate::socks::Socks5Config;
|
||||
use crate::sql;
|
||||
use crate::stock_str::unencrypted_email;
|
||||
use crate::tools::{self, time_elapsed};
|
||||
@@ -95,7 +95,7 @@ impl Smtp {
|
||||
context,
|
||||
&lp.smtp,
|
||||
&lp.smtp_password,
|
||||
&lp.socks5_config,
|
||||
&lp.proxy_config,
|
||||
&lp.addr,
|
||||
lp.strict_tls(),
|
||||
lp.oauth2,
|
||||
@@ -110,7 +110,7 @@ impl Smtp {
|
||||
context: &Context,
|
||||
login_params: &[ConfiguredServerLoginParam],
|
||||
password: &str,
|
||||
socks5_config: &Option<Socks5Config>,
|
||||
proxy_config: &Option<ProxyConfig>,
|
||||
addr: &str,
|
||||
strict_tls: bool,
|
||||
oauth2: bool,
|
||||
@@ -130,7 +130,7 @@ impl Smtp {
|
||||
info!(context, "SMTP trying to connect to {}.", &lp.connection);
|
||||
let transport = match connect::connect_and_auth(
|
||||
context,
|
||||
socks5_config,
|
||||
proxy_config,
|
||||
strict_tls,
|
||||
lp.connection.clone(),
|
||||
oauth2,
|
||||
|
||||
@@ -9,13 +9,13 @@ use tokio::io::BufStream;
|
||||
use crate::context::Context;
|
||||
use crate::login_param::{ConnectionCandidate, ConnectionSecurity};
|
||||
use crate::net::dns::{lookup_host_with_cache, update_connect_timestamp};
|
||||
use crate::net::proxy::ProxyConfig;
|
||||
use crate::net::session::SessionBufStream;
|
||||
use crate::net::tls::wrap_tls;
|
||||
use crate::net::{
|
||||
connect_tcp_inner, connect_tls_inner, run_connection_attempts, update_connection_history,
|
||||
};
|
||||
use crate::oauth2::get_oauth2_access_token;
|
||||
use crate::socks::Socks5Config;
|
||||
use crate::tools::time;
|
||||
|
||||
/// Converts port number to ALPN list.
|
||||
@@ -31,7 +31,7 @@ fn alpn(port: u16) -> &'static [&'static str] {
|
||||
#[allow(clippy::too_many_arguments)]
|
||||
pub(crate) async fn connect_and_auth(
|
||||
context: &Context,
|
||||
socks5_config: &Option<Socks5Config>,
|
||||
proxy_config: &Option<ProxyConfig>,
|
||||
strict_tls: bool,
|
||||
candidate: ConnectionCandidate,
|
||||
oauth2: bool,
|
||||
@@ -40,7 +40,7 @@ pub(crate) async fn connect_and_auth(
|
||||
password: &str,
|
||||
) -> Result<SmtpTransport<Box<dyn SessionBufStream>>> {
|
||||
let session_stream =
|
||||
connect_stream(context, socks5_config.clone(), strict_tls, candidate).await?;
|
||||
connect_stream(context, proxy_config.clone(), strict_tls, candidate).await?;
|
||||
let client = async_smtp::SmtpClient::new()
|
||||
.smtp_utf8(true)
|
||||
.without_greeting();
|
||||
@@ -127,7 +127,7 @@ async fn connection_attempt(
|
||||
/// to unify the result regardless of whether TLS or STARTTLS is used.
|
||||
async fn connect_stream(
|
||||
context: &Context,
|
||||
socks5_config: Option<Socks5Config>,
|
||||
proxy_config: Option<ProxyConfig>,
|
||||
strict_tls: bool,
|
||||
candidate: ConnectionCandidate,
|
||||
) -> Result<Box<dyn SessionBufStream>> {
|
||||
@@ -135,18 +135,17 @@ async fn connect_stream(
|
||||
let port = candidate.port;
|
||||
let security = candidate.security;
|
||||
|
||||
if let Some(socks5_config) = socks5_config {
|
||||
if let Some(proxy_config) = proxy_config {
|
||||
let stream = match security {
|
||||
ConnectionSecurity::Tls => {
|
||||
connect_secure_socks5(context, host, port, strict_tls, socks5_config.clone())
|
||||
.await?
|
||||
connect_secure_proxy(context, host, port, strict_tls, proxy_config.clone()).await?
|
||||
}
|
||||
ConnectionSecurity::Starttls => {
|
||||
connect_starttls_socks5(context, host, port, strict_tls, socks5_config.clone())
|
||||
connect_starttls_proxy(context, host, port, strict_tls, proxy_config.clone())
|
||||
.await?
|
||||
}
|
||||
ConnectionSecurity::Plain => {
|
||||
connect_insecure_socks5(context, host, port, socks5_config.clone()).await?
|
||||
connect_insecure_proxy(context, host, port, proxy_config.clone()).await?
|
||||
}
|
||||
};
|
||||
Ok(stream)
|
||||
@@ -192,37 +191,37 @@ async fn skip_smtp_greeting<R: tokio::io::AsyncBufReadExt + Unpin>(stream: &mut
|
||||
}
|
||||
}
|
||||
|
||||
async fn connect_secure_socks5(
|
||||
async fn connect_secure_proxy(
|
||||
context: &Context,
|
||||
hostname: &str,
|
||||
port: u16,
|
||||
strict_tls: bool,
|
||||
socks5_config: Socks5Config,
|
||||
proxy_config: ProxyConfig,
|
||||
) -> Result<Box<dyn SessionBufStream>> {
|
||||
let socks5_stream = socks5_config
|
||||
let proxy_stream = proxy_config
|
||||
.connect(context, hostname, port, strict_tls)
|
||||
.await?;
|
||||
let tls_stream = wrap_tls(strict_tls, hostname, alpn(port), socks5_stream).await?;
|
||||
let tls_stream = wrap_tls(strict_tls, hostname, alpn(port), proxy_stream).await?;
|
||||
let mut buffered_stream = BufStream::new(tls_stream);
|
||||
skip_smtp_greeting(&mut buffered_stream).await?;
|
||||
let session_stream: Box<dyn SessionBufStream> = Box::new(buffered_stream);
|
||||
Ok(session_stream)
|
||||
}
|
||||
|
||||
async fn connect_starttls_socks5(
|
||||
async fn connect_starttls_proxy(
|
||||
context: &Context,
|
||||
hostname: &str,
|
||||
port: u16,
|
||||
strict_tls: bool,
|
||||
socks5_config: Socks5Config,
|
||||
proxy_config: ProxyConfig,
|
||||
) -> Result<Box<dyn SessionBufStream>> {
|
||||
let socks5_stream = socks5_config
|
||||
let proxy_stream = proxy_config
|
||||
.connect(context, hostname, port, strict_tls)
|
||||
.await?;
|
||||
|
||||
// Run STARTTLS command and convert the client back into a stream.
|
||||
let client = SmtpClient::new().smtp_utf8(true);
|
||||
let transport = SmtpTransport::new(client, BufStream::new(socks5_stream)).await?;
|
||||
let transport = SmtpTransport::new(client, BufStream::new(proxy_stream)).await?;
|
||||
let tcp_stream = transport.starttls().await?.into_inner();
|
||||
let tls_stream = wrap_tls(strict_tls, hostname, &[], tcp_stream)
|
||||
.await
|
||||
@@ -232,16 +231,14 @@ async fn connect_starttls_socks5(
|
||||
Ok(session_stream)
|
||||
}
|
||||
|
||||
async fn connect_insecure_socks5(
|
||||
async fn connect_insecure_proxy(
|
||||
context: &Context,
|
||||
hostname: &str,
|
||||
port: u16,
|
||||
socks5_config: Socks5Config,
|
||||
proxy_config: ProxyConfig,
|
||||
) -> Result<Box<dyn SessionBufStream>> {
|
||||
let socks5_stream = socks5_config
|
||||
.connect(context, hostname, port, false)
|
||||
.await?;
|
||||
let mut buffered_stream = BufStream::new(socks5_stream);
|
||||
let proxy_stream = proxy_config.connect(context, hostname, port, false).await?;
|
||||
let mut buffered_stream = BufStream::new(proxy_stream);
|
||||
skip_smtp_greeting(&mut buffered_stream).await?;
|
||||
let session_stream: Box<dyn SessionBufStream> = Box::new(buffered_stream);
|
||||
Ok(session_stream)
|
||||
|
||||
101
src/socks.rs
101
src/socks.rs
@@ -1,101 +0,0 @@
|
||||
//! # SOCKS5 support.
|
||||
|
||||
use std::fmt;
|
||||
use std::pin::Pin;
|
||||
|
||||
use anyhow::Result;
|
||||
use fast_socks5::client::{Config, Socks5Stream};
|
||||
use fast_socks5::util::target_addr::ToTargetAddr;
|
||||
use fast_socks5::AuthenticationMethod;
|
||||
use fast_socks5::Socks5Command;
|
||||
use tokio::net::TcpStream;
|
||||
use tokio_io_timeout::TimeoutStream;
|
||||
|
||||
use crate::context::Context;
|
||||
use crate::net::connect_tcp;
|
||||
use crate::sql::Sql;
|
||||
|
||||
#[derive(Default, Debug, Clone, PartialEq, Eq)]
|
||||
pub struct Socks5Config {
|
||||
pub host: String,
|
||||
pub port: u16,
|
||||
pub user_password: Option<(String, String)>,
|
||||
}
|
||||
|
||||
impl Socks5Config {
|
||||
/// Reads SOCKS5 configuration from the database.
|
||||
pub async fn from_database(sql: &Sql) -> Result<Option<Self>> {
|
||||
let enabled = sql.get_raw_config_bool("socks5_enabled").await?;
|
||||
if enabled {
|
||||
let host = sql.get_raw_config("socks5_host").await?.unwrap_or_default();
|
||||
let port: u16 = sql
|
||||
.get_raw_config_int("socks5_port")
|
||||
.await?
|
||||
.unwrap_or_default() as u16;
|
||||
let user = sql.get_raw_config("socks5_user").await?.unwrap_or_default();
|
||||
let password = sql
|
||||
.get_raw_config("socks5_password")
|
||||
.await?
|
||||
.unwrap_or_default();
|
||||
|
||||
let socks5_config = Self {
|
||||
host,
|
||||
port,
|
||||
user_password: if !user.is_empty() {
|
||||
Some((user, password))
|
||||
} else {
|
||||
None
|
||||
},
|
||||
};
|
||||
Ok(Some(socks5_config))
|
||||
} else {
|
||||
Ok(None)
|
||||
}
|
||||
}
|
||||
|
||||
/// If `load_dns_cache` is true, loads cached DNS resolution results.
|
||||
/// Use this only if the connection is going to be protected with TLS checks.
|
||||
pub async fn connect(
|
||||
&self,
|
||||
context: &Context,
|
||||
target_host: &str,
|
||||
target_port: u16,
|
||||
load_dns_cache: bool,
|
||||
) -> Result<Socks5Stream<Pin<Box<TimeoutStream<TcpStream>>>>> {
|
||||
let tcp_stream = connect_tcp(context, &self.host, self.port, load_dns_cache).await?;
|
||||
|
||||
let authentication_method = if let Some((username, password)) = self.user_password.as_ref()
|
||||
{
|
||||
Some(AuthenticationMethod::Password {
|
||||
username: username.into(),
|
||||
password: password.into(),
|
||||
})
|
||||
} else {
|
||||
None
|
||||
};
|
||||
let mut socks_stream =
|
||||
Socks5Stream::use_stream(tcp_stream, authentication_method, Config::default()).await?;
|
||||
let target_addr = (target_host, target_port).to_target_addr()?;
|
||||
socks_stream
|
||||
.request(Socks5Command::TCPConnect, target_addr)
|
||||
.await?;
|
||||
|
||||
Ok(socks_stream)
|
||||
}
|
||||
}
|
||||
|
||||
impl fmt::Display for Socks5Config {
|
||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||
write!(
|
||||
f,
|
||||
"host:{},port:{},user_password:{}",
|
||||
self.host,
|
||||
self.port,
|
||||
if let Some(user_password) = self.user_password.clone() {
|
||||
format!("user: {}, password: ***", user_password.0)
|
||||
} else {
|
||||
"user: None".to_string()
|
||||
}
|
||||
)
|
||||
}
|
||||
}
|
||||
35
src/sync.rs
35
src/sync.rs
@@ -328,8 +328,10 @@ mod tests {
|
||||
use anyhow::bail;
|
||||
|
||||
use super::*;
|
||||
use crate::chat::ProtectionStatus;
|
||||
use crate::chatlist::Chatlist;
|
||||
use crate::contact::{Contact, Origin};
|
||||
use crate::securejoin::get_securejoin_qr;
|
||||
use crate::test_utils::{TestContext, TestContextManager};
|
||||
use crate::tools::SystemTime;
|
||||
|
||||
@@ -630,4 +632,37 @@ mod tests {
|
||||
assert_eq!(msg.text, "hi");
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[tokio::test(flavor = "multi_thread", worker_threads = 2)]
|
||||
async fn test_unpromoted_group_no_qr_sync() -> Result<()> {
|
||||
let mut tcm = TestContextManager::new();
|
||||
let alice = &tcm.alice().await;
|
||||
alice.set_config_bool(Config::SyncMsgs, true).await?;
|
||||
let alice_chatid =
|
||||
chat::create_group_chat(alice, ProtectionStatus::Protected, "the chat").await?;
|
||||
let qr = get_securejoin_qr(alice, Some(alice_chatid)).await?;
|
||||
let msg_id = alice.send_sync_msg().await?;
|
||||
assert!(msg_id.is_none());
|
||||
|
||||
let bob = &tcm.bob().await;
|
||||
tcm.exec_securejoin_qr(bob, alice, &qr).await;
|
||||
let msg_id = alice.send_sync_msg().await?;
|
||||
// The group becomes promoted when Bob joins, so the QR code token is synced.
|
||||
assert!(msg_id.is_some());
|
||||
let sent = alice.pop_sent_msg().await;
|
||||
let msg = alice.parse_msg(&sent).await;
|
||||
let mut sync_items = msg.sync_items.unwrap().items;
|
||||
assert_eq!(sync_items.len(), 1);
|
||||
let data = sync_items.pop().unwrap().data;
|
||||
let SyncDataOrUnknown::SyncData(AddQrToken(_)) = data else {
|
||||
unreachable!();
|
||||
};
|
||||
|
||||
let fiona = &tcm.fiona().await;
|
||||
tcm.exec_securejoin_qr(fiona, alice, &qr).await;
|
||||
let msg_id = alice.send_sync_msg().await?;
|
||||
// The QR code token was already synced before.
|
||||
assert!(msg_id.is_none());
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
@@ -54,7 +54,7 @@ pub(crate) fn truncate(buf: &str, approx_chars: usize) -> Cow<str> {
|
||||
.map(|(n, _)| n)
|
||||
.unwrap_or_default();
|
||||
|
||||
if let Some(index) = buf[..end_pos].rfind(|c| c == ' ' || c == '\n') {
|
||||
if let Some(index) = buf[..end_pos].rfind([' ', '\n']) {
|
||||
Cow::Owned(format!("{}{}", &buf[..=index], DC_ELLIPSIS))
|
||||
} else {
|
||||
Cow::Owned(format!("{}{}", &buf[..end_pos], DC_ELLIPSIS))
|
||||
@@ -98,7 +98,7 @@ pub(crate) fn truncate_by_lines(
|
||||
// Text has too many lines and needs to be truncated.
|
||||
let text = {
|
||||
if let Some(buffer) = buf.get(..end_pos) {
|
||||
if let Some(index) = buffer.rfind(|c| c == ' ' || c == '\n') {
|
||||
if let Some(index) = buffer.rfind([' ', '\n']) {
|
||||
buf.get(..=index)
|
||||
} else {
|
||||
buf.get(..end_pos)
|
||||
|
||||
Reference in New Issue
Block a user