mirror of
https://github.com/chatmail/core.git
synced 2026-06-30 03:26:36 +03:00
Compare commits
42 Commits
1.58.0
...
draft-dl-f
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
fd57253cf3 | ||
|
|
25ee17cc6a | ||
|
|
1df131c6ae | ||
|
|
aa942b0c2a | ||
|
|
3aa2b57ac1 | ||
|
|
ab1de69fbc | ||
|
|
90703b0dd2 | ||
|
|
a163be9248 | ||
|
|
2b7bf11b05 | ||
|
|
f95e1db8e2 | ||
|
|
d0c97bce4c | ||
|
|
3440daca1a | ||
|
|
d0bfb555dd | ||
|
|
6ffaa38b37 | ||
|
|
339d46ecf0 | ||
|
|
5399c9151d | ||
|
|
53cd633e8d | ||
|
|
ade39fe026 | ||
|
|
b8dad1dbaf | ||
|
|
72d503fa32 | ||
|
|
223aeb7b1a | ||
|
|
b315c6f6d5 | ||
|
|
481276cf46 | ||
|
|
faab61b0d4 | ||
|
|
20bf41b4e6 | ||
|
|
5a5b80c960 | ||
|
|
ac245a6cb2 | ||
|
|
126beb62f3 | ||
|
|
1f642046bc | ||
|
|
d79e4a6571 | ||
|
|
cadc0b2c00 | ||
|
|
87071e6d4b | ||
|
|
057b004553 | ||
|
|
4071fe53a0 | ||
|
|
c3062976c0 | ||
|
|
85efc0ea26 | ||
|
|
4ef80aaea5 | ||
|
|
0f86800f5d | ||
|
|
9c2035538c | ||
|
|
0276938975 | ||
|
|
ee44a162b6 | ||
|
|
49fc72fa42 |
21
.github/workflows/ci.yml
vendored
21
.github/workflows/ci.yml
vendored
@@ -18,7 +18,7 @@ jobs:
|
||||
- uses: actions-rs/toolchain@v1
|
||||
with:
|
||||
profile: minimal
|
||||
toolchain: 1.50.0
|
||||
toolchain: stable
|
||||
override: true
|
||||
- run: rustup component add rustfmt
|
||||
- uses: actions-rs/cargo@v1
|
||||
@@ -32,7 +32,7 @@ jobs:
|
||||
- uses: actions/checkout@v2
|
||||
- uses: actions-rs/toolchain@v1
|
||||
with:
|
||||
toolchain: 1.50.0
|
||||
toolchain: stable
|
||||
components: clippy
|
||||
override: true
|
||||
- uses: actions-rs/clippy-check@v1
|
||||
@@ -68,12 +68,23 @@ jobs:
|
||||
strategy:
|
||||
matrix:
|
||||
include:
|
||||
# Currently used Rust version, same as in `rust-toolchain` file.
|
||||
- os: ubuntu-latest
|
||||
rust: 1.50.0
|
||||
python: 3.6
|
||||
rust: 1.54.0
|
||||
python: 3.9
|
||||
- os: windows-latest
|
||||
rust: 1.50.0
|
||||
rust: 1.54.0
|
||||
python: false # Python bindings compilation on Windows is not supported.
|
||||
|
||||
# Minimum Supported Rust Version = 1.48.0
|
||||
# This is the Debian "bullseye" release version of Rust.
|
||||
#
|
||||
# Minimum Supported Python Version = 3.7
|
||||
# This is the minimum version for which manylinux Python wheels are
|
||||
# built.
|
||||
- os: ubuntu-latest
|
||||
rust: 1.48.0
|
||||
python: 3.7
|
||||
runs-on: ${{ matrix.os }}
|
||||
steps:
|
||||
- uses: actions/checkout@master
|
||||
|
||||
20
CHANGELOG.md
20
CHANGELOG.md
@@ -1,5 +1,25 @@
|
||||
# Changelog
|
||||
|
||||
## 1.59.0
|
||||
|
||||
### Added
|
||||
- add quota information to `dc_get_connectivity_html()`
|
||||
|
||||
### Changes
|
||||
- refactorings #2592 #2570 #2581
|
||||
- add 'device chat about' to now existing status #2613
|
||||
- update provider database #2608
|
||||
|
||||
### Fixes
|
||||
- provider database supports socket=PLAIN and dotless domains now #2604 #2608
|
||||
- add migrated accounts to events emitter #2607
|
||||
- fix forwarding quote-only mails #2600
|
||||
- do not set WantsMdn param for outgoing messages #2603
|
||||
- set timestamps for system messages #2593
|
||||
- do not treat gmail labels as folders #2587
|
||||
- avoid timing problems in `dc_maybe_network_lost()` #2551
|
||||
- only set smtp to "connected" if the last message was actually sent #2541
|
||||
|
||||
|
||||
## 1.58.0
|
||||
|
||||
|
||||
118
Cargo.lock
generated
118
Cargo.lock
generated
@@ -1,5 +1,7 @@
|
||||
# This file is automatically @generated by Cargo.
|
||||
# It is not intended for manual editing.
|
||||
version = 3
|
||||
|
||||
[[package]]
|
||||
name = "addr2line"
|
||||
version = "0.15.1"
|
||||
@@ -236,8 +238,7 @@ dependencies = [
|
||||
[[package]]
|
||||
name = "async-imap"
|
||||
version = "0.5.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "bbb2df4b37a99456360a9ab475b723e3a499d51e060ab1bdd8d7565d23dcb74b"
|
||||
source = "git+https://github.com/async-email/async-imap#4ce7da455618c387b87b2905a80935107bc69afc"
|
||||
dependencies = [
|
||||
"async-native-tls",
|
||||
"async-std",
|
||||
@@ -248,7 +249,7 @@ dependencies = [
|
||||
"imap-proto",
|
||||
"lazy_static",
|
||||
"log",
|
||||
"nom 5.1.2",
|
||||
"nom 6.2.1",
|
||||
"pin-utils",
|
||||
"rental",
|
||||
"stop-token",
|
||||
@@ -324,13 +325,14 @@ dependencies = [
|
||||
[[package]]
|
||||
name = "async-smtp"
|
||||
version = "0.4.0"
|
||||
source = "git+https://github.com/async-email/async-smtp?rev=c8800625f7cf29f437143ac7e720ac2730a0962f#c8800625f7cf29f437143ac7e720ac2730a0962f"
|
||||
source = "git+https://github.com/async-email/async-smtp?branch=master#2c21f5fb643e9a24c1097f13db4dfcd7818ada06"
|
||||
dependencies = [
|
||||
"async-native-tls",
|
||||
"async-std",
|
||||
"async-trait",
|
||||
"base64 0.13.0",
|
||||
"bufstream",
|
||||
"fast-socks5",
|
||||
"hostname 0.1.5",
|
||||
"log",
|
||||
"nom 5.1.2",
|
||||
@@ -516,9 +518,21 @@ checksum = "46afbd2983a5d5a7bd740ccb198caf5b82f45c40c09c0eed36052d91cb92e719"
|
||||
|
||||
[[package]]
|
||||
name = "bitflags"
|
||||
version = "1.2.1"
|
||||
version = "1.3.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "cf1de2fe8c75bc145a2f577add951f8134889b4795d47466a54a5c846d691693"
|
||||
checksum = "2da1976d75adbe5fbc88130ecd119529cf1cc6a93ae1546d8696ee66f0d21af1"
|
||||
|
||||
[[package]]
|
||||
name = "bitvec"
|
||||
version = "0.19.5"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "8942c8d352ae1838c9dda0b0ca2ab657696ef2232a20147cf1b30ae1a9cb4321"
|
||||
dependencies = [
|
||||
"funty",
|
||||
"radium",
|
||||
"tap",
|
||||
"wyz",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "block-buffer"
|
||||
@@ -1107,7 +1121,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "deltachat"
|
||||
version = "1.58.0"
|
||||
version = "1.59.0"
|
||||
dependencies = [
|
||||
"ansi_term",
|
||||
"anyhow",
|
||||
@@ -1129,9 +1143,11 @@ dependencies = [
|
||||
"email",
|
||||
"encoded-words",
|
||||
"escaper",
|
||||
"fast-socks5",
|
||||
"futures",
|
||||
"futures-lite",
|
||||
"hex",
|
||||
"humansize",
|
||||
"image",
|
||||
"indexmap",
|
||||
"itertools 0.10.1",
|
||||
@@ -1185,7 +1201,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "deltachat_ffi"
|
||||
version = "1.58.0"
|
||||
version = "1.59.0"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"async-std",
|
||||
@@ -1501,6 +1517,19 @@ version = "0.1.9"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "7360491ce676a36bf9bb3c56c1aa791658183a54d2744120f27285738d90465a"
|
||||
|
||||
[[package]]
|
||||
name = "fast-socks5"
|
||||
version = "0.4.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "6c1955b65d95243f547eb1d1ee6e5ce75ecf6daaeb72b08cd6c66e549d6d88e1"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"async-std",
|
||||
"futures",
|
||||
"log",
|
||||
"thiserror",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "fast_chemail"
|
||||
version = "0.9.6"
|
||||
@@ -1584,6 +1613,12 @@ dependencies = [
|
||||
"percent-encoding",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "funty"
|
||||
version = "1.1.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "fed34cd105917e91daa4da6b3728c47b068749d6a62c59811f06ed2ac71d9da7"
|
||||
|
||||
[[package]]
|
||||
name = "futures"
|
||||
version = "0.3.16"
|
||||
@@ -1914,6 +1949,12 @@ dependencies = [
|
||||
"uuid",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "humansize"
|
||||
version = "1.1.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "02296996cb8796d7c6e3bc2d9211b7802812d36999a51bb754123ead7d37d026"
|
||||
|
||||
[[package]]
|
||||
name = "humantime"
|
||||
version = "1.3.0"
|
||||
@@ -1959,11 +2000,11 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "imap-proto"
|
||||
version = "0.11.0"
|
||||
version = "0.14.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "3091b99ee5b80f9b010eb6f962af9495ad06561bf662126b077e8ca30e463182"
|
||||
checksum = "3ad9b46a79efb6078e578ae04e51463d7c3e8767864687f7e63095b3cbefafbb"
|
||||
dependencies = [
|
||||
"nom 5.1.2",
|
||||
"nom 6.2.1",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -2120,9 +2161,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "libc"
|
||||
version = "0.2.97"
|
||||
version = "0.2.98"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "12b8adadd720df158f4d70dfe7ccc6adb0472d7c55ca83445f6a5ab3e36f8fb6"
|
||||
checksum = "320cfe77175da3a483efed4bc0adc1968ca050b098ce4f2f1c13a56626128790"
|
||||
|
||||
[[package]]
|
||||
name = "libm"
|
||||
@@ -2341,6 +2382,19 @@ dependencies = [
|
||||
"version_check 0.9.3",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "nom"
|
||||
version = "6.2.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "9c5c51b9083a3c620fa67a2a635d1ce7d95b897e957d6b28ff9a5da960a103a6"
|
||||
dependencies = [
|
||||
"bitvec",
|
||||
"funty",
|
||||
"lexical-core",
|
||||
"memchr",
|
||||
"version_check 0.9.3",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "num-bigint"
|
||||
version = "0.2.6"
|
||||
@@ -2872,6 +2926,12 @@ dependencies = [
|
||||
"rusqlite",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "radium"
|
||||
version = "0.5.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "941ba9d78d8e2f7ce474c015eea4d9c6d25b6a3327f9832ee29a4de27f91bbb8"
|
||||
|
||||
[[package]]
|
||||
name = "radix_trie"
|
||||
version = "0.2.1"
|
||||
@@ -3290,9 +3350,9 @@ checksum = "388a1df253eca08550bef6c72392cfe7c30914bf41df5269b68cbd6ff8f570a3"
|
||||
|
||||
[[package]]
|
||||
name = "serde"
|
||||
version = "1.0.126"
|
||||
version = "1.0.127"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "ec7505abeacaec74ae4778d9d9328fe5a5d04253220a85c4ee022239fc996d03"
|
||||
checksum = "f03b9878abf6d14e6779d3f24f07b2cfa90352cfec4acc5aab8f1ac7f146fae8"
|
||||
dependencies = [
|
||||
"serde_derive",
|
||||
]
|
||||
@@ -3309,9 +3369,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "serde_derive"
|
||||
version = "1.0.126"
|
||||
version = "1.0.127"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "963a7dbc9895aeac7ac90e74f34a5d5261828f79df35cbed41e10189d3804d43"
|
||||
checksum = "a024926d3432516606328597e0f224a51355a493b49fdd67e9209187cbe55ecc"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
@@ -3320,9 +3380,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "serde_json"
|
||||
version = "1.0.64"
|
||||
version = "1.0.66"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "799e97dc9fdae36a5c8b8f2cae9ce2ee9fdce2058c57a93e6099d919fd982f79"
|
||||
checksum = "336b10da19a12ad094b59d870ebde26a45402e5b470add4b5fd03c5048a32127"
|
||||
dependencies = [
|
||||
"itoa",
|
||||
"ryu",
|
||||
@@ -3649,6 +3709,12 @@ dependencies = [
|
||||
"unicode-xid",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "tap"
|
||||
version = "1.0.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "55937e1799185b12863d447f42597ed69d9928686b8d88a1df17376a097d8369"
|
||||
|
||||
[[package]]
|
||||
name = "tempfile"
|
||||
version = "3.2.0"
|
||||
@@ -3683,18 +3749,18 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "thiserror"
|
||||
version = "1.0.25"
|
||||
version = "1.0.26"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "fa6f76457f59514c7eeb4e59d891395fab0b2fd1d40723ae737d64153392e9c6"
|
||||
checksum = "93119e4feac1cbe6c798c34d3a53ea0026b0b1de6a120deef895137c0529bfe2"
|
||||
dependencies = [
|
||||
"thiserror-impl",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "thiserror-impl"
|
||||
version = "1.0.25"
|
||||
version = "1.0.26"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "8a36768c0fbf1bb15eca10defa29526bda730a2376c2ab4393ccfa16fb1a318d"
|
||||
checksum = "060d69a0afe7796bf42e9e2ff91f5ee691fb15c53d38b4b62a9a53eb23164745"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
@@ -4164,6 +4230,12 @@ dependencies = [
|
||||
"winapi",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "wyz"
|
||||
version = "0.2.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "85e60b0d1b5f99db2556934e21937020776a5d31520bf169e851ac44e6420214"
|
||||
|
||||
[[package]]
|
||||
name = "x25519-dalek"
|
||||
version = "1.1.0"
|
||||
|
||||
14
Cargo.toml
14
Cargo.toml
@@ -1,6 +1,6 @@
|
||||
[package]
|
||||
name = "deltachat"
|
||||
version = "1.58.0"
|
||||
version = "1.59.0"
|
||||
authors = ["Delta Chat Developers (ML) <delta@codespeak.net>"]
|
||||
edition = "2018"
|
||||
license = "MPL-2.0"
|
||||
@@ -16,16 +16,16 @@ deltachat_derive = { path = "./deltachat_derive" }
|
||||
|
||||
ansi_term = { version = "0.12.1", optional = true }
|
||||
anyhow = "1.0.42"
|
||||
async-imap = "0.5.0"
|
||||
async-imap = { git = "https://github.com/async-email/async-imap" }
|
||||
async-native-tls = { version = "0.3.3" }
|
||||
async-smtp = { git = "https://github.com/async-email/async-smtp", rev="c8800625f7cf29f437143ac7e720ac2730a0962f" }
|
||||
async-smtp = { git = "https://github.com/async-email/async-smtp", branch="master", features = ["socks5"] }
|
||||
async-std-resolver = "0.20.3"
|
||||
async-std = { version = "~1.9.0", features = ["unstable"] }
|
||||
async-tar = "0.3.0"
|
||||
async-trait = "0.1.50"
|
||||
backtrace = "0.3.59"
|
||||
base64 = "0.13"
|
||||
bitflags = "1.1.0"
|
||||
bitflags = "1.3.1"
|
||||
byteorder = "1.3.1"
|
||||
chrono = "0.4.6"
|
||||
dirs = { version = "3.0.2", optional=true }
|
||||
@@ -39,7 +39,7 @@ indexmap = "1.7.0"
|
||||
itertools = "0.10.1"
|
||||
kamadak-exif = "0.5"
|
||||
lettre_email = { git = "https://github.com/deltachat/lettre", branch = "master" }
|
||||
libc = "0.2.97"
|
||||
libc = "0.2.98"
|
||||
log = {version = "0.4.8", optional = true }
|
||||
mailparse = "0.13.5"
|
||||
native-tls = "0.2.3"
|
||||
@@ -68,10 +68,12 @@ stop-token = "0.2.0"
|
||||
strum = "0.21.0"
|
||||
strum_macros = "0.21.1"
|
||||
surf = { version = "2.0.0-alpha.4", default-features = false, features = ["h1-client"] }
|
||||
thiserror = "1.0.25"
|
||||
thiserror = "1.0.26"
|
||||
toml = "0.5.6"
|
||||
url = "2.2.2"
|
||||
uuid = { version = "0.8", features = ["serde", "v4"] }
|
||||
fast-socks5 = "0.4.2"
|
||||
humansize = "1.1.1"
|
||||
|
||||
[dev-dependencies]
|
||||
ansi_term = "0.12.0"
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
[package]
|
||||
name = "deltachat_ffi"
|
||||
version = "1.58.0"
|
||||
version = "1.59.0"
|
||||
description = "Deltachat FFI"
|
||||
authors = ["Delta Chat Developers (ML) <delta@codespeak.net>"]
|
||||
edition = "2018"
|
||||
@@ -22,7 +22,7 @@ num-traits = "0.2.6"
|
||||
serde_json = "1.0"
|
||||
async-std = "1.9.0"
|
||||
anyhow = "1.0.42"
|
||||
thiserror = "1.0.25"
|
||||
thiserror = "1.0.26"
|
||||
rand = "0.7.3"
|
||||
|
||||
[features]
|
||||
|
||||
@@ -30,6 +30,6 @@ fn main() {
|
||||
fs::create_dir_all(target_path.join("pkgconfig")).unwrap();
|
||||
fs::File::create(target_path.join("pkgconfig").join("deltachat.pc"))
|
||||
.unwrap()
|
||||
.write_all(&pkg_config.as_bytes())
|
||||
.write_all(pkg_config.as_bytes())
|
||||
.unwrap();
|
||||
}
|
||||
|
||||
@@ -271,6 +271,11 @@ 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
|
||||
* - `imap_certificate_checks` = how to check IMAP certificates, one of the @ref DC_CERTCK flags, defaults to #DC_CERTCK_AUTO (0)
|
||||
* - `smtp_certificate_checks` = how to check SMTP certificates, one of the @ref DC_CERTCK flags, defaults to #DC_CERTCK_AUTO (0)
|
||||
* - `displayname` = Own name to use when sending messages. MUAs are allowed to spread this way e.g. using CC, defaults to empty
|
||||
@@ -3220,10 +3225,6 @@ int64_t dc_chat_get_remaining_mute_duration (const dc_chat_t* chat);
|
||||
#define DC_STATE_OUT_MDN_RCVD 28
|
||||
|
||||
|
||||
#define DC_MAX_GET_TEXT_LEN 30000 // approx. max. length returned by dc_msg_get_text()
|
||||
#define DC_MAX_GET_INFO_LEN 100000 // approx. max. length returned by dc_get_msg_info()
|
||||
|
||||
|
||||
/**
|
||||
* Create new message object. Message objects are needed e.g. for sending messages using
|
||||
* dc_send_msg(). Moreover, they are returned e.g. from dc_get_msg(),
|
||||
@@ -3902,6 +3903,54 @@ int dc_msg_get_videochat_type (const dc_msg_t* msg);
|
||||
int dc_msg_has_html (dc_msg_t* msg);
|
||||
|
||||
|
||||
/**
|
||||
* Check if the message is completely downloaded
|
||||
* or if some further action is needed.
|
||||
*
|
||||
* The function returns one of:
|
||||
* - @ref DC_DOWNLOAD_NO_URL - The message does not need any further download action
|
||||
* and should be rendered as usual.
|
||||
* - @ref DC_DOWNLOAD_AVAILABLE - There is additional content to download.
|
||||
* Tn addition to the usual message rendering,
|
||||
* the UI shall show a download button that starts dc_schedule_download()
|
||||
* - @ref DC_DOWNLOAD_IN_PROGRESS - Download was started with dc_schedule_download() and is still in progress.
|
||||
* On progress changes and if the download fails or succeeds,
|
||||
* the event @ref DC_EVENT_DOWNLOAD_PROGRESS will be emitted.
|
||||
* - @ref DC_DOWNLOAD_DONE - Download finished successfully
|
||||
* - @ref DC_DOWNLOAD_FAILURE - Download error, the user may start over calling dc_schedule_download() again.
|
||||
*
|
||||
* @memberof dc_msg_t
|
||||
* @param msg The message object.
|
||||
* @return One of the @ref DC_DOWNLOAD values
|
||||
*/
|
||||
int dc_msg_download_status(const dc_msg_t* msg);
|
||||
|
||||
|
||||
/**
|
||||
* Advices the core to start downloading a message.
|
||||
* This function is typically called when the user hits the "Download" button
|
||||
* that is shown by the UI in case dc_msg_download_status()
|
||||
* returns @ref DC_DOWNLOAD_AVAILABLE or @ref DC_DOWNLOAD_FAILURE.
|
||||
*
|
||||
* The UI may want to show a file selector and let the user chose a download location.
|
||||
* The file name in the file selector may be prefilled using dc_msg_get_filename().
|
||||
*
|
||||
* During the download, the progress, errors and success
|
||||
* are reported using @ref DC_EVENT_DOWNLOAD_PROGRESS.
|
||||
*
|
||||
* Once the @ref DC_EVENT_DOWNLOAD_PROGRESS reports success,
|
||||
* The file can be accessed as usual using dc_msg_get_file().
|
||||
*
|
||||
* @memberof dc_context_t
|
||||
* @param context The context object.
|
||||
* @param path Path to the destination file.
|
||||
* You can specify NULL here to download
|
||||
* to a reasonable file name in the internal blob-directory.
|
||||
* @param msg_id Message-ID to download the content for.
|
||||
*/
|
||||
void dc_schedule_download(dc_context_t* context, int msg_id, const char* path);
|
||||
|
||||
|
||||
/**
|
||||
* Set the text of a message object.
|
||||
* This does not alter any information in the database; this may be done by dc_send_msg() later.
|
||||
@@ -5152,6 +5201,16 @@ void dc_event_unref(dc_event_t* event);
|
||||
*/
|
||||
#define DC_EVENT_CONNECTIVITY_CHANGED 2100
|
||||
|
||||
|
||||
/**
|
||||
* Inform about the progress of a download started by dc_schedule_download().
|
||||
*
|
||||
* @param data1 (int) Message-ID the progress is reported for.
|
||||
* @param data2 (int) 0=error, 1-999=progress in permille, 1000=success and done
|
||||
*/
|
||||
#define DC_EVENT_DOWNLOAD_PROGRESS 2120
|
||||
|
||||
|
||||
/**
|
||||
* @}
|
||||
*/
|
||||
@@ -5282,6 +5341,31 @@ void dc_event_unref(dc_event_t* event);
|
||||
|
||||
|
||||
/**
|
||||
* @defgroup DC_DOWNLOAD DC_DOWNLOAD
|
||||
*
|
||||
* These constants describe the download state of a message.
|
||||
* The download state can be retrieved using dc_msg_download_status()
|
||||
* and usually changes after calling dc_schedule_download().
|
||||
*
|
||||
* @addtogroup DC_DOWNLOAD
|
||||
* @{
|
||||
*/
|
||||
|
||||
#define DC_DOWNLOAD_NO_URL 10 ///< Download not needed, see dc_msg_download_status() for details.
|
||||
#define DC_DOWNLOAD_AVAILABLE 20 ///< Download available, see dc_msg_download_status() for details.
|
||||
#define DC_DOWNLOAD_IN_PROGRESS 30 ///< Download in progress, see dc_msg_download_status() for details.
|
||||
#define DC_DOWNLOAD_DONE 40 ///< Download done, see dc_msg_download_status() for details.
|
||||
#define DC_DOWNLOAD_FAILURE 50 ///< Download failed, see dc_msg_download_status() for details.
|
||||
|
||||
/**
|
||||
* @}
|
||||
*/
|
||||
|
||||
|
||||
|
||||
/*
|
||||
* TODO: Strings need some doumentation about used placeholders.
|
||||
*
|
||||
* @defgroup DC_STR DC_STR
|
||||
*
|
||||
* These constants are used to define strings using dc_set_stock_translation().
|
||||
|
||||
@@ -217,7 +217,7 @@ pub unsafe extern "C" fn dc_set_config_from_qr(
|
||||
let ctx = &*context;
|
||||
|
||||
block_on(async move {
|
||||
match qr::set_config_from_qr(&ctx, &qr).await {
|
||||
match qr::set_config_from_qr(ctx, &qr).await {
|
||||
Ok(()) => 1,
|
||||
Err(err) => {
|
||||
error!(ctx, "Failed to create account from QR code: {}", err);
|
||||
@@ -275,7 +275,15 @@ pub unsafe extern "C" fn dc_get_connectivity_html(
|
||||
return "".strdup();
|
||||
}
|
||||
let ctx = &*context;
|
||||
block_on(async move { ctx.get_connectivity_html().await.strdup() })
|
||||
block_on(async move {
|
||||
match ctx.get_connectivity_html().await {
|
||||
Ok(html) => html.strdup(),
|
||||
Err(err) => {
|
||||
error!(ctx, "Failed to get connectivity html: {}", err);
|
||||
"".strdup()
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
#[no_mangle]
|
||||
@@ -303,9 +311,12 @@ pub unsafe extern "C" fn dc_get_oauth2_url(
|
||||
let redirect = to_string_lossy(redirect);
|
||||
|
||||
block_on(async move {
|
||||
match oauth2::dc_get_oauth2_url(&ctx, &addr, &redirect).await {
|
||||
Some(res) => res.strdup(),
|
||||
None => ptr::null_mut(),
|
||||
match oauth2::dc_get_oauth2_url(ctx, &addr, &redirect)
|
||||
.await
|
||||
.log_err(ctx, "dc_get_oauth2_url failed")
|
||||
{
|
||||
Ok(Some(res)) => res.strdup(),
|
||||
Ok(None) | Err(_) => ptr::null_mut(),
|
||||
}
|
||||
})
|
||||
}
|
||||
@@ -608,7 +619,7 @@ pub unsafe extern "C" fn dc_preconfigure_keypair(
|
||||
public,
|
||||
secret,
|
||||
};
|
||||
key::store_self_keypair(&ctx, &keypair, key::KeyPairUse::Default).await?;
|
||||
key::store_self_keypair(ctx, &keypair, key::KeyPairUse::Default).await?;
|
||||
Ok::<_, anyhow::Error>(1)
|
||||
})
|
||||
.log_err(ctx, "Failed to save keypair")
|
||||
@@ -632,7 +643,10 @@ pub unsafe extern "C" fn dc_get_chatlist(
|
||||
let qi = if query_id == 0 { None } else { Some(query_id) };
|
||||
|
||||
block_on(async move {
|
||||
match chatlist::Chatlist::try_load(&ctx, flags as usize, qs.as_deref(), qi).await {
|
||||
match chatlist::Chatlist::try_load(ctx, flags as usize, qs.as_deref(), qi)
|
||||
.await
|
||||
.log_err(ctx, "Failed to get chatlist")
|
||||
{
|
||||
Ok(list) => {
|
||||
let ffi_list = ChatlistWrapper { context, list };
|
||||
Box::into_raw(Box::new(ffi_list))
|
||||
@@ -654,7 +668,7 @@ pub unsafe extern "C" fn dc_create_chat_by_contact_id(
|
||||
let ctx = &*context;
|
||||
|
||||
block_on(async move {
|
||||
ChatId::create_for_contact(&ctx, contact_id)
|
||||
ChatId::create_for_contact(ctx, contact_id)
|
||||
.await
|
||||
.log_err(ctx, "Failed to create chat from contact_id")
|
||||
.map(|id| id.to_u32())
|
||||
@@ -674,7 +688,7 @@ pub unsafe extern "C" fn dc_get_chat_id_by_contact_id(
|
||||
let ctx = &*context;
|
||||
|
||||
block_on(async move {
|
||||
ChatId::lookup_by_contact(&ctx, contact_id)
|
||||
ChatId::lookup_by_contact(ctx, contact_id)
|
||||
.await
|
||||
.log_err(ctx, "Failed to get chat for contact_id")
|
||||
.unwrap_or_default() // unwraps the Result
|
||||
@@ -697,9 +711,9 @@ pub unsafe extern "C" fn dc_prepare_msg(
|
||||
let ffi_msg: &mut MessageWrapper = &mut *msg;
|
||||
|
||||
block_on(async move {
|
||||
chat::prepare_msg(&ctx, ChatId::new(chat_id), &mut ffi_msg.message)
|
||||
chat::prepare_msg(ctx, ChatId::new(chat_id), &mut ffi_msg.message)
|
||||
.await
|
||||
.unwrap_or_log_default(&ctx, "Failed to prepare message")
|
||||
.unwrap_or_log_default(ctx, "Failed to prepare message")
|
||||
})
|
||||
.to_u32()
|
||||
}
|
||||
@@ -718,9 +732,9 @@ pub unsafe extern "C" fn dc_send_msg(
|
||||
let ffi_msg = &mut *msg;
|
||||
|
||||
block_on(async move {
|
||||
chat::send_msg(&ctx, ChatId::new(chat_id), &mut ffi_msg.message)
|
||||
chat::send_msg(ctx, ChatId::new(chat_id), &mut ffi_msg.message)
|
||||
.await
|
||||
.unwrap_or_log_default(&ctx, "Failed to send message")
|
||||
.unwrap_or_log_default(ctx, "Failed to send message")
|
||||
})
|
||||
.to_u32()
|
||||
}
|
||||
@@ -739,9 +753,9 @@ pub unsafe extern "C" fn dc_send_msg_sync(
|
||||
let ffi_msg = &mut *msg;
|
||||
|
||||
block_on(async move {
|
||||
chat::send_msg_sync(&ctx, ChatId::new(chat_id), &mut ffi_msg.message)
|
||||
chat::send_msg_sync(ctx, ChatId::new(chat_id), &mut ffi_msg.message)
|
||||
.await
|
||||
.unwrap_or_log_default(&ctx, "Failed to send message")
|
||||
.unwrap_or_log_default(ctx, "Failed to send message")
|
||||
})
|
||||
.to_u32()
|
||||
}
|
||||
@@ -760,10 +774,10 @@ pub unsafe extern "C" fn dc_send_text_msg(
|
||||
let text_to_send = to_string_lossy(text_to_send);
|
||||
|
||||
block_on(async move {
|
||||
chat::send_text_msg(&ctx, ChatId::new(chat_id), text_to_send)
|
||||
chat::send_text_msg(ctx, ChatId::new(chat_id), text_to_send)
|
||||
.await
|
||||
.map(|msg_id| msg_id.to_u32())
|
||||
.unwrap_or_log_default(&ctx, "Failed to send text message")
|
||||
.unwrap_or_log_default(ctx, "Failed to send text message")
|
||||
})
|
||||
}
|
||||
|
||||
@@ -779,10 +793,10 @@ pub unsafe extern "C" fn dc_send_videochat_invitation(
|
||||
let ctx = &*context;
|
||||
|
||||
block_on(async move {
|
||||
chat::send_videochat_invitation(&ctx, ChatId::new(chat_id))
|
||||
chat::send_videochat_invitation(ctx, ChatId::new(chat_id))
|
||||
.await
|
||||
.map(|msg_id| msg_id.to_u32())
|
||||
.unwrap_or_log_default(&ctx, "Failed to send video chat invitation")
|
||||
.unwrap_or_log_default(ctx, "Failed to send video chat invitation")
|
||||
})
|
||||
}
|
||||
|
||||
@@ -806,7 +820,7 @@ pub unsafe extern "C" fn dc_set_draft(
|
||||
|
||||
block_on(async move {
|
||||
ChatId::new(chat_id)
|
||||
.set_draft(&ctx, msg)
|
||||
.set_draft(ctx, msg)
|
||||
.await
|
||||
.unwrap_or_log_default(ctx, "failed to set draft");
|
||||
});
|
||||
@@ -831,9 +845,9 @@ pub unsafe extern "C" fn dc_add_device_msg(
|
||||
};
|
||||
|
||||
block_on(async move {
|
||||
chat::add_device_msg(&ctx, to_opt_string_lossy(label).as_deref(), msg)
|
||||
chat::add_device_msg(ctx, to_opt_string_lossy(label).as_deref(), msg)
|
||||
.await
|
||||
.unwrap_or_log_default(&ctx, "Failed to add device message")
|
||||
.unwrap_or_log_default(ctx, "Failed to add device message")
|
||||
})
|
||||
.to_u32()
|
||||
}
|
||||
@@ -850,7 +864,7 @@ pub unsafe extern "C" fn dc_was_device_msg_ever_added(
|
||||
let ctx = &mut *context;
|
||||
|
||||
block_on(async move {
|
||||
chat::was_device_msg_ever_added(&ctx, &to_string_lossy(label))
|
||||
chat::was_device_msg_ever_added(ctx, &to_string_lossy(label))
|
||||
.await
|
||||
.unwrap_or(false) as libc::c_int
|
||||
})
|
||||
@@ -865,7 +879,7 @@ pub unsafe extern "C" fn dc_get_draft(context: *mut dc_context_t, chat_id: u32)
|
||||
let ctx = &*context;
|
||||
|
||||
block_on(async move {
|
||||
match ChatId::new(chat_id).get_draft(&ctx).await {
|
||||
match ChatId::new(chat_id).get_draft(ctx).await {
|
||||
Ok(Some(draft)) => {
|
||||
let ffi_msg = MessageWrapper {
|
||||
context,
|
||||
@@ -902,7 +916,7 @@ pub unsafe extern "C" fn dc_get_chat_msgs(
|
||||
|
||||
block_on(async move {
|
||||
Box::into_raw(Box::new(
|
||||
chat::get_chat_msgs(&ctx, ChatId::new(chat_id), flags, marker_flag)
|
||||
chat::get_chat_msgs(ctx, ChatId::new(chat_id), flags, marker_flag)
|
||||
.await
|
||||
.unwrap_or_log_default(ctx, "failed to get chat msgs")
|
||||
.into(),
|
||||
@@ -920,7 +934,7 @@ pub unsafe extern "C" fn dc_get_msg_cnt(context: *mut dc_context_t, chat_id: u32
|
||||
|
||||
block_on(async move {
|
||||
ChatId::new(chat_id)
|
||||
.get_msg_cnt(&ctx)
|
||||
.get_msg_cnt(ctx)
|
||||
.await
|
||||
.unwrap_or_log_default(ctx, "failed to get msg count") as libc::c_int
|
||||
})
|
||||
@@ -939,7 +953,7 @@ pub unsafe extern "C" fn dc_get_fresh_msg_cnt(
|
||||
|
||||
block_on(async move {
|
||||
ChatId::new(chat_id)
|
||||
.get_fresh_msg_cnt(&ctx)
|
||||
.get_fresh_msg_cnt(ctx)
|
||||
.await
|
||||
.unwrap_or_log_default(ctx, "failed to get fresh msg cnt") as libc::c_int
|
||||
})
|
||||
@@ -996,7 +1010,7 @@ pub unsafe extern "C" fn dc_marknoticed_chat(context: *mut dc_context_t, chat_id
|
||||
let ctx = &*context;
|
||||
|
||||
block_on(async move {
|
||||
chat::marknoticed_chat(&ctx, ChatId::new(chat_id))
|
||||
chat::marknoticed_chat(ctx, ChatId::new(chat_id))
|
||||
.await
|
||||
.log_err(ctx, "Failed marknoticed chat")
|
||||
.unwrap_or(())
|
||||
@@ -1033,7 +1047,7 @@ pub unsafe extern "C" fn dc_get_chat_media(
|
||||
block_on(async move {
|
||||
Box::into_raw(Box::new(
|
||||
chat::get_chat_media(
|
||||
&ctx,
|
||||
ctx,
|
||||
ChatId::new(chat_id),
|
||||
msg_type,
|
||||
or_msg_type2,
|
||||
@@ -1074,7 +1088,7 @@ pub unsafe extern "C" fn dc_get_next_media(
|
||||
|
||||
block_on(async move {
|
||||
chat::get_next_media(
|
||||
&ctx,
|
||||
ctx,
|
||||
MsgId::new(msg_id),
|
||||
direction,
|
||||
msg_type,
|
||||
@@ -1106,7 +1120,7 @@ pub unsafe extern "C" fn dc_set_chat_protection(
|
||||
};
|
||||
|
||||
block_on(async move {
|
||||
match ChatId::new(chat_id).set_protection(&ctx, protect).await {
|
||||
match ChatId::new(chat_id).set_protection(ctx, protect).await {
|
||||
Ok(()) => 1,
|
||||
Err(_) => 0,
|
||||
}
|
||||
@@ -1139,7 +1153,7 @@ pub unsafe extern "C" fn dc_set_chat_visibility(
|
||||
|
||||
block_on(async move {
|
||||
ChatId::new(chat_id)
|
||||
.set_visibility(&ctx, visibility)
|
||||
.set_visibility(ctx, visibility)
|
||||
.await
|
||||
.log_err(ctx, "Failed setting chat visibility")
|
||||
.unwrap_or(())
|
||||
@@ -1156,7 +1170,7 @@ pub unsafe extern "C" fn dc_delete_chat(context: *mut dc_context_t, chat_id: u32
|
||||
|
||||
block_on(async move {
|
||||
ChatId::new(chat_id)
|
||||
.delete(&ctx)
|
||||
.delete(ctx)
|
||||
.await
|
||||
.ok_or_log_msg(ctx, "Failed chat delete");
|
||||
})
|
||||
@@ -1172,7 +1186,7 @@ pub unsafe extern "C" fn dc_block_chat(context: *mut dc_context_t, chat_id: u32)
|
||||
|
||||
block_on(async move {
|
||||
ChatId::new(chat_id)
|
||||
.block(&ctx)
|
||||
.block(ctx)
|
||||
.await
|
||||
.ok_or_log_msg(ctx, "Failed chat block");
|
||||
})
|
||||
@@ -1188,7 +1202,7 @@ pub unsafe extern "C" fn dc_accept_chat(context: *mut dc_context_t, chat_id: u32
|
||||
|
||||
block_on(async move {
|
||||
ChatId::new(chat_id)
|
||||
.accept(&ctx)
|
||||
.accept(ctx)
|
||||
.await
|
||||
.ok_or_log_msg(ctx, "Failed chat accept");
|
||||
})
|
||||
@@ -1207,7 +1221,7 @@ pub unsafe extern "C" fn dc_get_chat_contacts(
|
||||
|
||||
block_on(async move {
|
||||
let arr = dc_array_t::from(
|
||||
chat::get_chat_contacts(&ctx, ChatId::new(chat_id))
|
||||
chat::get_chat_contacts(ctx, ChatId::new(chat_id))
|
||||
.await
|
||||
.unwrap_or_log_default(ctx, "Failed get_chat_contacts"),
|
||||
);
|
||||
@@ -1254,7 +1268,7 @@ pub unsafe extern "C" fn dc_get_chat(context: *mut dc_context_t, chat_id: u32) -
|
||||
let ctx = &*context;
|
||||
|
||||
block_on(async move {
|
||||
match chat::Chat::load_from_db(&ctx, ChatId::new(chat_id)).await {
|
||||
match chat::Chat::load_from_db(ctx, ChatId::new(chat_id)).await {
|
||||
Ok(chat) => {
|
||||
let ffi_chat = ChatWrapper { context, chat };
|
||||
Box::into_raw(Box::new(ffi_chat))
|
||||
@@ -1283,7 +1297,7 @@ pub unsafe extern "C" fn dc_create_group_chat(
|
||||
};
|
||||
|
||||
block_on(async move {
|
||||
chat::create_group_chat(&ctx, protect, &to_string_lossy(name))
|
||||
chat::create_group_chat(ctx, protect, &to_string_lossy(name))
|
||||
.await
|
||||
.log_err(ctx, "Failed to create group chat")
|
||||
.map(|id| id.to_u32())
|
||||
@@ -1303,7 +1317,7 @@ pub unsafe extern "C" fn dc_is_contact_in_chat(
|
||||
}
|
||||
let ctx = &*context;
|
||||
|
||||
block_on(async move { chat::is_contact_in_chat(&ctx, ChatId::new(chat_id), contact_id).await })
|
||||
block_on(async move { chat::is_contact_in_chat(ctx, ChatId::new(chat_id), contact_id).await })
|
||||
.into()
|
||||
}
|
||||
|
||||
@@ -1320,7 +1334,7 @@ pub unsafe extern "C" fn dc_add_contact_to_chat(
|
||||
let ctx = &*context;
|
||||
|
||||
block_on(async move {
|
||||
chat::add_contact_to_chat(&ctx, ChatId::new(chat_id), contact_id).await as libc::c_int
|
||||
chat::add_contact_to_chat(ctx, ChatId::new(chat_id), contact_id).await as libc::c_int
|
||||
})
|
||||
}
|
||||
|
||||
@@ -1337,10 +1351,10 @@ pub unsafe extern "C" fn dc_remove_contact_from_chat(
|
||||
let ctx = &*context;
|
||||
|
||||
block_on(async move {
|
||||
chat::remove_contact_from_chat(&ctx, ChatId::new(chat_id), contact_id)
|
||||
chat::remove_contact_from_chat(ctx, ChatId::new(chat_id), contact_id)
|
||||
.await
|
||||
.map(|_| 1)
|
||||
.unwrap_or_log_default(&ctx, "Failed to remove contact")
|
||||
.unwrap_or_log_default(ctx, "Failed to remove contact")
|
||||
})
|
||||
}
|
||||
|
||||
@@ -1358,10 +1372,10 @@ pub unsafe extern "C" fn dc_set_chat_name(
|
||||
let ctx = &*context;
|
||||
|
||||
block_on(async move {
|
||||
chat::set_chat_name(&ctx, ChatId::new(chat_id), &to_string_lossy(name))
|
||||
chat::set_chat_name(ctx, ChatId::new(chat_id), &to_string_lossy(name))
|
||||
.await
|
||||
.map(|_| 1)
|
||||
.unwrap_or_log_default(&ctx, "Failed to set chat name")
|
||||
.unwrap_or_log_default(ctx, "Failed to set chat name")
|
||||
})
|
||||
}
|
||||
|
||||
@@ -1378,10 +1392,10 @@ pub unsafe extern "C" fn dc_set_chat_profile_image(
|
||||
let ctx = &*context;
|
||||
|
||||
block_on(async move {
|
||||
chat::set_chat_profile_image(&ctx, ChatId::new(chat_id), to_string_lossy(image))
|
||||
chat::set_chat_profile_image(ctx, ChatId::new(chat_id), to_string_lossy(image))
|
||||
.await
|
||||
.map(|_| 1)
|
||||
.unwrap_or_log_default(&ctx, "Failed to set profile image")
|
||||
.unwrap_or_log_default(ctx, "Failed to set profile image")
|
||||
})
|
||||
}
|
||||
|
||||
@@ -1412,10 +1426,10 @@ pub unsafe extern "C" fn dc_set_chat_mute_duration(
|
||||
};
|
||||
|
||||
block_on(async move {
|
||||
chat::set_muted(&ctx, ChatId::new(chat_id), muteDuration)
|
||||
chat::set_muted(ctx, ChatId::new(chat_id), muteDuration)
|
||||
.await
|
||||
.map(|_| 1)
|
||||
.unwrap_or_log_default(&ctx, "Failed to set mute duration")
|
||||
.unwrap_or_log_default(ctx, "Failed to set mute duration")
|
||||
})
|
||||
}
|
||||
|
||||
@@ -1432,11 +1446,11 @@ pub unsafe extern "C" fn dc_get_chat_encrinfo(
|
||||
|
||||
block_on(async move {
|
||||
ChatId::new(chat_id)
|
||||
.get_encryption_info(&ctx)
|
||||
.get_encryption_info(ctx)
|
||||
.await
|
||||
.map(|s| s.strdup())
|
||||
.unwrap_or_else(|e| {
|
||||
error!(&ctx, "{}", e);
|
||||
error!(ctx, "{}", e);
|
||||
ptr::null_mut()
|
||||
})
|
||||
})
|
||||
@@ -1497,7 +1511,7 @@ pub unsafe extern "C" fn dc_get_msg_info(
|
||||
let ctx = &*context;
|
||||
|
||||
block_on(async move {
|
||||
message::get_msg_info(&ctx, MsgId::new(msg_id))
|
||||
message::get_msg_info(ctx, MsgId::new(msg_id))
|
||||
.await
|
||||
.unwrap_or_log_default(ctx, "failed to get msg id")
|
||||
.strdup()
|
||||
@@ -1515,7 +1529,7 @@ pub unsafe extern "C" fn dc_get_msg_html(
|
||||
}
|
||||
let ctx = &*context;
|
||||
|
||||
block_on(MsgId::new(msg_id).get_html(&ctx))
|
||||
block_on(MsgId::new(msg_id).get_html(ctx))
|
||||
.unwrap_or_log_default(ctx, "Failed get_msg_html")
|
||||
.strdup()
|
||||
}
|
||||
@@ -1532,7 +1546,7 @@ pub unsafe extern "C" fn dc_get_mime_headers(
|
||||
let ctx = &*context;
|
||||
|
||||
block_on(async move {
|
||||
let mime = message::get_mime_headers(&ctx, MsgId::new(msg_id))
|
||||
let mime = message::get_mime_headers(ctx, MsgId::new(msg_id))
|
||||
.await
|
||||
.unwrap_or_log_default(ctx, "failed to get mime headers");
|
||||
if mime.is_empty() {
|
||||
@@ -1555,7 +1569,7 @@ pub unsafe extern "C" fn dc_delete_msgs(
|
||||
let ctx = &*context;
|
||||
let msg_ids = convert_and_prune_message_ids(msg_ids, msg_cnt);
|
||||
|
||||
block_on(message::delete_msgs(&ctx, &msg_ids))
|
||||
block_on(message::delete_msgs(ctx, &msg_ids))
|
||||
}
|
||||
|
||||
#[no_mangle]
|
||||
@@ -1577,9 +1591,9 @@ pub unsafe extern "C" fn dc_forward_msgs(
|
||||
let ctx = &*context;
|
||||
|
||||
block_on(async move {
|
||||
chat::forward_msgs(&ctx, &msg_ids[..], ChatId::new(chat_id))
|
||||
chat::forward_msgs(ctx, &msg_ids[..], ChatId::new(chat_id))
|
||||
.await
|
||||
.unwrap_or_log_default(&ctx, "Failed to forward message")
|
||||
.unwrap_or_log_default(ctx, "Failed to forward message")
|
||||
})
|
||||
}
|
||||
|
||||
@@ -1596,7 +1610,7 @@ pub unsafe extern "C" fn dc_markseen_msgs(
|
||||
let msg_ids = convert_and_prune_message_ids(msg_ids, msg_cnt);
|
||||
let ctx = &*context;
|
||||
|
||||
block_on(message::markseen_msgs(&ctx, msg_ids))
|
||||
block_on(message::markseen_msgs(ctx, msg_ids))
|
||||
.log_err(ctx, "failed dc_markseen_msgs() call")
|
||||
.ok();
|
||||
}
|
||||
@@ -1610,19 +1624,19 @@ pub unsafe extern "C" fn dc_get_msg(context: *mut dc_context_t, msg_id: u32) ->
|
||||
let ctx = &*context;
|
||||
|
||||
block_on(async move {
|
||||
let message = match message::Message::load_from_db(&ctx, MsgId::new(msg_id)).await {
|
||||
let message = match message::Message::load_from_db(ctx, MsgId::new(msg_id)).await {
|
||||
Ok(msg) => msg,
|
||||
Err(e) => {
|
||||
if msg_id <= constants::DC_MSG_ID_LAST_SPECIAL {
|
||||
// C-core API returns empty messages, do the same
|
||||
warn!(
|
||||
&ctx,
|
||||
ctx,
|
||||
"dc_get_msg called with special msg_id={}, returning empty msg", msg_id
|
||||
);
|
||||
message::Message::default()
|
||||
} else {
|
||||
error!(
|
||||
&ctx,
|
||||
ctx,
|
||||
"dc_get_msg could not retrieve msg_id {}: {}", msg_id, e
|
||||
);
|
||||
return ptr::null_mut();
|
||||
@@ -1656,7 +1670,7 @@ pub unsafe extern "C" fn dc_lookup_contact_id_by_addr(
|
||||
let ctx = &*context;
|
||||
|
||||
block_on(async move {
|
||||
Contact::lookup_id_by_addr(&ctx, to_string_lossy(addr), Origin::IncomingReplyTo)
|
||||
Contact::lookup_id_by_addr(ctx, to_string_lossy(addr), Origin::IncomingReplyTo)
|
||||
.await
|
||||
.unwrap_or_log_default(ctx, "failed to lookup id")
|
||||
.unwrap_or(0)
|
||||
@@ -1677,7 +1691,7 @@ pub unsafe extern "C" fn dc_create_contact(
|
||||
let name = to_string_lossy(name);
|
||||
|
||||
block_on(async move {
|
||||
Contact::create(&ctx, &name, &to_string_lossy(addr))
|
||||
Contact::create(ctx, &name, &to_string_lossy(addr))
|
||||
.await
|
||||
.unwrap_or(0)
|
||||
})
|
||||
@@ -1695,7 +1709,7 @@ pub unsafe extern "C" fn dc_add_address_book(
|
||||
let ctx = &*context;
|
||||
|
||||
block_on(async move {
|
||||
match Contact::add_address_book(&ctx, &to_string_lossy(addr_book)).await {
|
||||
match Contact::add_address_book(ctx, &to_string_lossy(addr_book)).await {
|
||||
Ok(cnt) => cnt as libc::c_int,
|
||||
Err(_) => 0,
|
||||
}
|
||||
@@ -1716,7 +1730,7 @@ pub unsafe extern "C" fn dc_get_contacts(
|
||||
let query = to_opt_string_lossy(query);
|
||||
|
||||
block_on(async move {
|
||||
match Contact::get_all(&ctx, flags, query).await {
|
||||
match Contact::get_all(ctx, flags, query).await {
|
||||
Ok(contacts) => Box::into_raw(Box::new(dc_array_t::from(contacts))),
|
||||
Err(_) => ptr::null_mut(),
|
||||
}
|
||||
@@ -1732,7 +1746,7 @@ pub unsafe extern "C" fn dc_get_blocked_cnt(context: *mut dc_context_t) -> libc:
|
||||
let ctx = &*context;
|
||||
|
||||
block_on(async move {
|
||||
Contact::get_all_blocked(&ctx)
|
||||
Contact::get_all_blocked(ctx)
|
||||
.await
|
||||
.unwrap_or_log_default(ctx, "failed to get blocked count")
|
||||
.len() as libc::c_int
|
||||
@@ -1751,9 +1765,9 @@ pub unsafe extern "C" fn dc_get_blocked_contacts(
|
||||
|
||||
block_on(async move {
|
||||
Box::into_raw(Box::new(dc_array_t::from(
|
||||
Contact::get_all_blocked(&ctx)
|
||||
Contact::get_all_blocked(ctx)
|
||||
.await
|
||||
.log_err(&ctx, "Can't get blocked contacts")
|
||||
.log_err(ctx, "Can't get blocked contacts")
|
||||
.unwrap_or_default(),
|
||||
)))
|
||||
})
|
||||
@@ -1772,13 +1786,13 @@ pub unsafe extern "C" fn dc_block_contact(
|
||||
let ctx = &*context;
|
||||
block_on(async move {
|
||||
if block == 0 {
|
||||
Contact::unblock(&ctx, contact_id)
|
||||
Contact::unblock(ctx, contact_id)
|
||||
.await
|
||||
.ok_or_log_msg(&ctx, "Can't unblock contact");
|
||||
.ok_or_log_msg(ctx, "Can't unblock contact");
|
||||
} else {
|
||||
Contact::block(&ctx, contact_id)
|
||||
Contact::block(ctx, contact_id)
|
||||
.await
|
||||
.ok_or_log_msg(&ctx, "Can't block contact");
|
||||
.ok_or_log_msg(ctx, "Can't block contact");
|
||||
}
|
||||
});
|
||||
}
|
||||
@@ -1795,11 +1809,11 @@ pub unsafe extern "C" fn dc_get_contact_encrinfo(
|
||||
let ctx = &*context;
|
||||
|
||||
block_on(async move {
|
||||
Contact::get_encrinfo(&ctx, contact_id)
|
||||
Contact::get_encrinfo(ctx, contact_id)
|
||||
.await
|
||||
.map(|s| s.strdup())
|
||||
.unwrap_or_else(|e| {
|
||||
error!(&ctx, "{}", e);
|
||||
error!(ctx, "{}", e);
|
||||
ptr::null_mut()
|
||||
})
|
||||
})
|
||||
@@ -1817,7 +1831,7 @@ pub unsafe extern "C" fn dc_delete_contact(
|
||||
let ctx = &*context;
|
||||
|
||||
block_on(async move {
|
||||
match Contact::delete(&ctx, contact_id).await {
|
||||
match Contact::delete(ctx, contact_id).await {
|
||||
Ok(_) => 1,
|
||||
Err(_) => 0,
|
||||
}
|
||||
@@ -1836,7 +1850,7 @@ pub unsafe extern "C" fn dc_get_contact(
|
||||
let ctx = &*context;
|
||||
|
||||
block_on(async move {
|
||||
Contact::get_by_id(&ctx, contact_id)
|
||||
Contact::get_by_id(ctx, contact_id)
|
||||
.await
|
||||
.map(|contact| Box::into_raw(Box::new(ContactWrapper { context, contact })))
|
||||
.unwrap_or_else(|_| ptr::null_mut())
|
||||
@@ -1866,7 +1880,7 @@ pub unsafe extern "C" fn dc_imex(
|
||||
|
||||
if let Some(param1) = to_opt_string_lossy(param1) {
|
||||
spawn(async move {
|
||||
imex::imex(&ctx, what, param1.as_ref())
|
||||
imex::imex(ctx, what, param1.as_ref())
|
||||
.await
|
||||
.log_err(ctx, "IMEX failed")
|
||||
});
|
||||
@@ -1887,12 +1901,12 @@ pub unsafe extern "C" fn dc_imex_has_backup(
|
||||
let ctx = &*context;
|
||||
|
||||
block_on(async move {
|
||||
match imex::has_backup(&ctx, to_string_lossy(dir).as_ref()).await {
|
||||
match imex::has_backup(ctx, to_string_lossy(dir).as_ref()).await {
|
||||
Ok(res) => res.strdup(),
|
||||
Err(err) => {
|
||||
// do not bubble up error to the user,
|
||||
// the ui will expect that the file does not exist or cannot be accessed
|
||||
warn!(&ctx, "dc_imex_has_backup: {}", err);
|
||||
warn!(ctx, "dc_imex_has_backup: {}", err);
|
||||
ptr::null_mut()
|
||||
}
|
||||
}
|
||||
@@ -1908,10 +1922,10 @@ pub unsafe extern "C" fn dc_initiate_key_transfer(context: *mut dc_context_t) ->
|
||||
let ctx = &*context;
|
||||
|
||||
block_on(async move {
|
||||
match imex::initiate_key_transfer(&ctx).await {
|
||||
match imex::initiate_key_transfer(ctx).await {
|
||||
Ok(res) => res.strdup(),
|
||||
Err(err) => {
|
||||
error!(&ctx, "dc_initiate_key_transfer(): {}", err);
|
||||
error!(ctx, "dc_initiate_key_transfer(): {}", err);
|
||||
ptr::null_mut()
|
||||
}
|
||||
}
|
||||
@@ -1934,12 +1948,12 @@ pub unsafe extern "C" fn dc_continue_key_transfer(
|
||||
let ctx = &*context;
|
||||
|
||||
block_on(async move {
|
||||
match imex::continue_key_transfer(&ctx, MsgId::new(msg_id), &to_string_lossy(setup_code))
|
||||
match imex::continue_key_transfer(ctx, MsgId::new(msg_id), &to_string_lossy(setup_code))
|
||||
.await
|
||||
{
|
||||
Ok(()) => 1,
|
||||
Err(err) => {
|
||||
warn!(&ctx, "dc_continue_key_transfer: {}", err);
|
||||
warn!(ctx, "dc_continue_key_transfer: {}", err);
|
||||
0
|
||||
}
|
||||
}
|
||||
@@ -1968,7 +1982,7 @@ pub unsafe extern "C" fn dc_check_qr(
|
||||
let ctx = &*context;
|
||||
|
||||
block_on(async move {
|
||||
let lot = qr::check_qr(&ctx, &to_string_lossy(qr)).await;
|
||||
let lot = qr::check_qr(ctx, &to_string_lossy(qr)).await;
|
||||
Box::into_raw(Box::new(lot))
|
||||
})
|
||||
}
|
||||
@@ -1990,7 +2004,7 @@ pub unsafe extern "C" fn dc_get_securejoin_qr(
|
||||
};
|
||||
|
||||
block_on(async move {
|
||||
securejoin::dc_get_securejoin_qr(&ctx, chat_id)
|
||||
securejoin::dc_get_securejoin_qr(ctx, chat_id)
|
||||
.await
|
||||
.unwrap_or_else(|| "".to_string())
|
||||
.strdup()
|
||||
@@ -2009,7 +2023,7 @@ pub unsafe extern "C" fn dc_join_securejoin(
|
||||
let ctx = &*context;
|
||||
|
||||
block_on(async move {
|
||||
securejoin::dc_join_securejoin(&ctx, &to_string_lossy(qr))
|
||||
securejoin::dc_join_securejoin(ctx, &to_string_lossy(qr))
|
||||
.await
|
||||
.map(|chatid| chatid.to_u32())
|
||||
.log_err(ctx, "failed dc_join_securejoin() call")
|
||||
@@ -2030,7 +2044,7 @@ pub unsafe extern "C" fn dc_send_locations_to_chat(
|
||||
let ctx = &*context;
|
||||
|
||||
block_on(location::send_locations_to_chat(
|
||||
&ctx,
|
||||
ctx,
|
||||
ChatId::new(chat_id),
|
||||
seconds as i64,
|
||||
));
|
||||
@@ -2052,7 +2066,7 @@ pub unsafe extern "C" fn dc_is_sending_locations_to_chat(
|
||||
Some(ChatId::new(chat_id))
|
||||
};
|
||||
|
||||
block_on(location::is_sending_locations_to_chat(&ctx, chat_id)) as libc::c_int
|
||||
block_on(location::is_sending_locations_to_chat(ctx, chat_id)) as libc::c_int
|
||||
}
|
||||
|
||||
#[no_mangle]
|
||||
@@ -2068,7 +2082,7 @@ pub unsafe extern "C" fn dc_set_location(
|
||||
}
|
||||
let ctx = &*context;
|
||||
|
||||
block_on(location::set(&ctx, latitude, longitude, accuracy)) as _
|
||||
block_on(location::set(ctx, latitude, longitude, accuracy)) as _
|
||||
}
|
||||
|
||||
#[no_mangle]
|
||||
@@ -2097,7 +2111,7 @@ pub unsafe extern "C" fn dc_get_locations(
|
||||
|
||||
block_on(async move {
|
||||
let res = location::get_range(
|
||||
&ctx,
|
||||
ctx,
|
||||
chat_id,
|
||||
contact_id,
|
||||
timestamp_begin as i64,
|
||||
@@ -2118,7 +2132,7 @@ pub unsafe extern "C" fn dc_delete_all_locations(context: *mut dc_context_t) {
|
||||
let ctx = &*context;
|
||||
|
||||
block_on(async move {
|
||||
location::delete_all(&ctx)
|
||||
location::delete_all(ctx)
|
||||
.await
|
||||
.log_err(ctx, "Failed to delete locations")
|
||||
.ok()
|
||||
@@ -2385,7 +2399,7 @@ pub unsafe extern "C" fn dc_chatlist_get_summary(
|
||||
block_on(async move {
|
||||
let lot = ffi_list
|
||||
.list
|
||||
.get_summary(&ctx, index as usize, maybe_chat)
|
||||
.get_summary(ctx, index as usize, maybe_chat)
|
||||
.await
|
||||
.log_err(ctx, "get_summary failed")
|
||||
.unwrap_or_default();
|
||||
@@ -2410,7 +2424,7 @@ pub unsafe extern "C" fn dc_chatlist_get_summary2(
|
||||
Some(MsgId::new(msg_id))
|
||||
};
|
||||
block_on(async move {
|
||||
let lot = Chatlist::get_summary2(&ctx, ChatId::new(chat_id), msg_id, None)
|
||||
let lot = Chatlist::get_summary2(ctx, ChatId::new(chat_id), msg_id, None)
|
||||
.await
|
||||
.log_err(ctx, "get_summary2 failed")
|
||||
.unwrap_or_default();
|
||||
@@ -2496,7 +2510,7 @@ pub unsafe extern "C" fn dc_chat_get_profile_image(chat: *mut dc_chat_t) -> *mut
|
||||
let ctx = &*ffi_chat.context;
|
||||
|
||||
block_on(async move {
|
||||
match ffi_chat.chat.get_profile_image(&ctx).await {
|
||||
match ffi_chat.chat.get_profile_image(ctx).await {
|
||||
Ok(Some(p)) => p.to_string_lossy().strdup(),
|
||||
Ok(None) => ptr::null_mut(),
|
||||
Err(err) => {
|
||||
@@ -2516,7 +2530,7 @@ pub unsafe extern "C" fn dc_chat_get_color(chat: *mut dc_chat_t) -> u32 {
|
||||
let ffi_chat = &*chat;
|
||||
let ctx = &*ffi_chat.context;
|
||||
|
||||
block_on(ffi_chat.chat.get_color(&ctx)).unwrap_or_log_default(ctx, "Failed get_color")
|
||||
block_on(ffi_chat.chat.get_color(ctx)).unwrap_or_log_default(ctx, "Failed get_color")
|
||||
}
|
||||
|
||||
#[no_mangle]
|
||||
@@ -2647,25 +2661,25 @@ pub unsafe extern "C" fn dc_chat_get_info_json(
|
||||
let ctx = &*context;
|
||||
|
||||
block_on(async move {
|
||||
let chat = match chat::Chat::load_from_db(&ctx, ChatId::new(chat_id)).await {
|
||||
let chat = match chat::Chat::load_from_db(ctx, ChatId::new(chat_id)).await {
|
||||
Ok(chat) => chat,
|
||||
Err(err) => {
|
||||
error!(&ctx, "dc_get_chat_info_json() failed to load chat: {}", err);
|
||||
error!(ctx, "dc_get_chat_info_json() failed to load chat: {}", err);
|
||||
return "".strdup();
|
||||
}
|
||||
};
|
||||
let info = match chat.get_info(&ctx).await {
|
||||
let info = match chat.get_info(ctx).await {
|
||||
Ok(info) => info,
|
||||
Err(err) => {
|
||||
error!(
|
||||
&ctx,
|
||||
ctx,
|
||||
"dc_get_chat_info_json() failed to get chat info: {}", err
|
||||
);
|
||||
return "".strdup();
|
||||
}
|
||||
};
|
||||
serde_json::to_string(&info)
|
||||
.unwrap_or_log_default(&ctx, "dc_get_chat_info_json() failed to serialise to json")
|
||||
.unwrap_or_log_default(ctx, "dc_get_chat_info_json() failed to serialise to json")
|
||||
.strdup()
|
||||
})
|
||||
}
|
||||
@@ -2866,7 +2880,7 @@ pub unsafe extern "C" fn dc_msg_get_filebytes(msg: *mut dc_msg_t) -> u64 {
|
||||
let ffi_msg = &*msg;
|
||||
let ctx = &*ffi_msg.context;
|
||||
|
||||
block_on(ffi_msg.message.get_filebytes(&ctx))
|
||||
block_on(ffi_msg.message.get_filebytes(ctx))
|
||||
}
|
||||
|
||||
#[no_mangle]
|
||||
@@ -2958,7 +2972,7 @@ pub unsafe extern "C" fn dc_msg_get_summary(
|
||||
let ctx = &*ffi_msg.context;
|
||||
|
||||
block_on(async move {
|
||||
let lot = ffi_msg.message.get_summary(&ctx, maybe_chat).await;
|
||||
let lot = ffi_msg.message.get_summary(ctx, maybe_chat).await;
|
||||
Box::into_raw(Box::new(lot))
|
||||
})
|
||||
}
|
||||
@@ -2978,7 +2992,7 @@ pub unsafe extern "C" fn dc_msg_get_summarytext(
|
||||
block_on({
|
||||
ffi_msg
|
||||
.message
|
||||
.get_summarytext(&ctx, approx_characters.try_into().unwrap_or_default())
|
||||
.get_summarytext(ctx, approx_characters.try_into().unwrap_or_default())
|
||||
})
|
||||
.strdup()
|
||||
}
|
||||
@@ -3118,7 +3132,7 @@ pub unsafe extern "C" fn dc_msg_get_setupcodebegin(msg: *mut dc_msg_t) -> *mut l
|
||||
let ffi_msg = &*msg;
|
||||
let ctx = &*ffi_msg.context;
|
||||
|
||||
block_on(ffi_msg.message.get_setupcodebegin(&ctx))
|
||||
block_on(ffi_msg.message.get_setupcodebegin(ctx))
|
||||
.unwrap_or_default()
|
||||
.strdup()
|
||||
}
|
||||
@@ -3230,7 +3244,7 @@ pub unsafe extern "C" fn dc_msg_latefiling_mediasize(
|
||||
block_on({
|
||||
ffi_msg
|
||||
.message
|
||||
.latefiling_mediasize(&ctx, width, height, duration)
|
||||
.latefiling_mediasize(ctx, width, height, duration)
|
||||
});
|
||||
}
|
||||
|
||||
@@ -3410,7 +3424,7 @@ pub unsafe extern "C" fn dc_contact_get_profile_image(
|
||||
block_on(async move {
|
||||
ffi_contact
|
||||
.contact
|
||||
.get_profile_image(&ctx)
|
||||
.get_profile_image(ctx)
|
||||
.await
|
||||
.unwrap_or_log_default(ctx, "failed to get profile image")
|
||||
.map(|p| p.to_string_lossy().strdup())
|
||||
@@ -3457,7 +3471,7 @@ pub unsafe extern "C" fn dc_contact_is_verified(contact: *mut dc_contact_t) -> l
|
||||
let ffi_contact = &*contact;
|
||||
let ctx = &*ffi_contact.context;
|
||||
|
||||
block_on(async move { ffi_contact.contact.is_verified(&ctx).await as libc::c_int })
|
||||
block_on(async move { ffi_contact.contact.is_verified(ctx).await as libc::c_int })
|
||||
}
|
||||
|
||||
// dc_lot_t
|
||||
@@ -3603,9 +3617,22 @@ pub unsafe extern "C" fn dc_provider_new_from_email(
|
||||
return ptr::null();
|
||||
}
|
||||
let addr = to_string_lossy(addr);
|
||||
match block_on(provider::get_provider_info(addr.as_str())) {
|
||||
Some(provider) => provider,
|
||||
None => ptr::null_mut(),
|
||||
|
||||
let ctx = &*context;
|
||||
let socks5_enabled = block_on(async move {
|
||||
ctx.get_config_bool(config::Config::Socks5Enabled)
|
||||
.await
|
||||
.log_err(ctx, "Can't get config")
|
||||
});
|
||||
|
||||
match socks5_enabled {
|
||||
Ok(socks5_enabled) => {
|
||||
match block_on(provider::get_provider_info(addr.as_str(), socks5_enabled)) {
|
||||
Some(provider) => provider,
|
||||
None => ptr::null_mut(),
|
||||
}
|
||||
}
|
||||
Err(_) => ptr::null_mut(),
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -17,15 +17,12 @@ use std::ptr;
|
||||
/// }
|
||||
/// ```
|
||||
unsafe fn dc_strdup(s: *const libc::c_char) -> *mut libc::c_char {
|
||||
let ret: *mut libc::c_char;
|
||||
if !s.is_null() {
|
||||
ret = libc::strdup(s);
|
||||
assert!(!ret.is_null());
|
||||
let ret: *mut libc::c_char = if !s.is_null() {
|
||||
libc::strdup(s)
|
||||
} else {
|
||||
ret = libc::calloc(1, 1) as *mut libc::c_char;
|
||||
assert!(!ret.is_null());
|
||||
}
|
||||
|
||||
libc::calloc(1, 1) as *mut libc::c_char
|
||||
};
|
||||
assert!(!ret.is_null());
|
||||
ret
|
||||
}
|
||||
|
||||
|
||||
@@ -514,9 +514,15 @@ pub async fn cmdline(context: Context, line: &str, chat_id: &mut ChatId) -> Resu
|
||||
let file = dirs::home_dir()
|
||||
.unwrap_or_default()
|
||||
.join("connectivity.html");
|
||||
let html = context.get_connectivity_html().await;
|
||||
fs::write(&file, html)?;
|
||||
println!("Report written to: {:#?}", file);
|
||||
match context.get_connectivity_html().await {
|
||||
Ok(html) => {
|
||||
fs::write(&file, html)?;
|
||||
println!("Report written to: {:#?}", file);
|
||||
}
|
||||
Err(err) => {
|
||||
bail!("Failed to get connectivity html: {}", err);
|
||||
}
|
||||
}
|
||||
}
|
||||
"maybenetwork" => {
|
||||
context.maybe_network().await;
|
||||
@@ -1162,7 +1168,10 @@ pub async fn cmdline(context: Context, line: &str, chat_id: &mut ChatId) -> Resu
|
||||
}
|
||||
"providerinfo" => {
|
||||
ensure!(!arg1.is_empty(), "Argument <addr> missing.");
|
||||
match provider::get_provider_info(arg1).await {
|
||||
let socks5_enabled = context
|
||||
.get_config_bool(config::Config::Socks5Enabled)
|
||||
.await?;
|
||||
match provider::get_provider_info(arg1, socks5_enabled).await {
|
||||
Some(info) => {
|
||||
println!("Information for provider belonging to {}:", arg1);
|
||||
println!("status: {}", info.status as u32);
|
||||
|
||||
@@ -392,7 +392,7 @@ async fn handle_cmd(
|
||||
"oauth2" => {
|
||||
if let Some(addr) = ctx.get_config(config::Config::Addr).await? {
|
||||
let oauth2_url =
|
||||
dc_get_oauth2_url(&ctx, &addr, "chat.delta:/com.b44t.messenger").await;
|
||||
dc_get_oauth2_url(&ctx, &addr, "chat.delta:/com.b44t.messenger").await?;
|
||||
if oauth2_url.is_none() {
|
||||
println!("OAuth2 not available for {}.", &addr);
|
||||
} else {
|
||||
|
||||
@@ -1 +1 @@
|
||||
1.50.0
|
||||
1.54.0
|
||||
|
||||
@@ -6,6 +6,14 @@ resources:
|
||||
branch: master
|
||||
uri: https://github.com/deltachat/deltachat-core-rust.git
|
||||
|
||||
- name: deltachat-core-rust-release
|
||||
type: git
|
||||
icon: github
|
||||
source:
|
||||
branch: master
|
||||
uri: https://github.com/deltachat/deltachat-core-rust.git
|
||||
tag_filter: "py-*"
|
||||
|
||||
jobs:
|
||||
- name: doxygen
|
||||
plan:
|
||||
@@ -58,12 +66,15 @@ jobs:
|
||||
- name: python-x86_64
|
||||
plan:
|
||||
- get: deltachat-core-rust
|
||||
- get: deltachat-core-rust-release
|
||||
trigger: true
|
||||
|
||||
# Build manylinux image with additional dependencies
|
||||
- task: build-coredeps
|
||||
privileged: true
|
||||
config:
|
||||
inputs:
|
||||
# Building the latest, not tagged coredeps
|
||||
- name: deltachat-core-rust
|
||||
image_resource:
|
||||
source:
|
||||
@@ -86,7 +97,7 @@ jobs:
|
||||
image: coredeps-image
|
||||
config:
|
||||
inputs:
|
||||
- name: deltachat-core-rust
|
||||
- name: deltachat-core-rust-release
|
||||
path: .
|
||||
outputs:
|
||||
- name: py-docs
|
||||
@@ -154,12 +165,15 @@ jobs:
|
||||
- name: python-aarch64
|
||||
plan:
|
||||
- get: deltachat-core-rust
|
||||
- get: deltachat-core-rust-release
|
||||
trigger: true
|
||||
|
||||
# Build manylinux image with additional dependencies
|
||||
- task: build-coredeps
|
||||
privileged: true
|
||||
config:
|
||||
inputs:
|
||||
# Building the latest, not tagged coredeps
|
||||
- name: deltachat-core-rust
|
||||
image_resource:
|
||||
source:
|
||||
@@ -182,7 +196,7 @@ jobs:
|
||||
image: coredeps-image
|
||||
config:
|
||||
inputs:
|
||||
- name: deltachat-core-rust
|
||||
- name: deltachat-core-rust-release
|
||||
path: .
|
||||
outputs:
|
||||
- name: py-wheels
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
#!/bin/bash
|
||||
|
||||
PERL_VERSION=5.30.0
|
||||
# PERL_SHA256=7e929f64d4cb0e9d1159d4a59fc89394e27fa1f7004d0836ca0d514685406ea8
|
||||
PERL_VERSION=5.34.0
|
||||
# PERL_SHA256=551efc818b968b05216024fb0b727ef2ad4c100f8cb6b43fab615fa78ae5be9a
|
||||
curl -O https://www.cpan.org/src/5.0/perl-${PERL_VERSION}.tar.gz
|
||||
# echo "${PERL_SHA256} perl-${PERL_VERSION}.tar.gz" | sha256sum -c -
|
||||
tar -xzf perl-${PERL_VERSION}.tar.gz
|
||||
|
||||
@@ -8,9 +8,11 @@ set -e -x
|
||||
#
|
||||
# Avoid using rustup here as it depends on reading /proc/self/exe and
|
||||
# has problems running under QEMU.
|
||||
curl "https://static.rust-lang.org/dist/rust-1.52.1-$(uname -m)-unknown-linux-gnu.tar.gz" | tar xz
|
||||
cd "rust-1.52.1-$(uname -m)-unknown-linux-gnu"
|
||||
RUST_VERSION=1.54.0
|
||||
|
||||
curl "https://static.rust-lang.org/dist/rust-${RUST_VERSION}-$(uname -m)-unknown-linux-gnu.tar.gz" | tar xz
|
||||
cd "rust-${RUST_VERSION}-$(uname -m)-unknown-linux-gnu"
|
||||
./install.sh --prefix=/usr --components=rustc,cargo,"rust-std-$(uname -m)-unknown-linux-gnu"
|
||||
rustc --version
|
||||
cd ..
|
||||
rm -fr "rust-1.52.1-$(uname -m)-unknown-linux-gnu"
|
||||
rm -fr "rust-${RUST_VERSION}-$(uname -m)-unknown-linux-gnu"
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
#!/bin/bash
|
||||
|
||||
PERL_VERSION=5.30.0
|
||||
# PERL_SHA256=7e929f64d4cb0e9d1159d4a59fc89394e27fa1f7004d0836ca0d514685406ea8
|
||||
PERL_VERSION=5.34.0
|
||||
# PERL_SHA256=551efc818b968b05216024fb0b727ef2ad4c100f8cb6b43fab615fa78ae5be9a
|
||||
curl -O https://www.cpan.org/src/5.0/perl-${PERL_VERSION}.tar.gz
|
||||
# echo "${PERL_SHA256} perl-${PERL_VERSION}.tar.gz" | sha256sum -c -
|
||||
tar -xzf perl-${PERL_VERSION}.tar.gz
|
||||
|
||||
@@ -3,9 +3,16 @@
|
||||
set -e -x
|
||||
|
||||
# Install Rust
|
||||
curl https://sh.rustup.rs -sSf | sh -s -- --default-toolchain "1.50.0-$(uname -m)-unknown-linux-gnu" -y
|
||||
export PATH=/root/.cargo/bin:$PATH
|
||||
rustc --version
|
||||
#
|
||||
# Path from https://forge.rust-lang.org/infra/other-installation-methods.html
|
||||
#
|
||||
# Avoid using rustup here as it depends on reading /proc/self/exe and
|
||||
# has problems running under QEMU.
|
||||
RUST_VERSION=1.54.0
|
||||
|
||||
# remove some 300-400 MB that we don't need for automated builds
|
||||
rm -rf "/root/.rustup/toolchains/1.50.0-$(uname -m)-unknown-linux-gnu/share"
|
||||
curl "https://static.rust-lang.org/dist/rust-${RUST_VERSION}-$(uname -m)-unknown-linux-gnu.tar.gz" | tar xz
|
||||
cd "rust-${RUST_VERSION}-$(uname -m)-unknown-linux-gnu"
|
||||
./install.sh --prefix=/usr --components=rustc,cargo,"rust-std-$(uname -m)-unknown-linux-gnu"
|
||||
rustc --version
|
||||
cd ..
|
||||
rm -fr "rust-${RUST_VERSION}-$(uname -m)-unknown-linux-gnu"
|
||||
|
||||
@@ -1,3 +1,5 @@
|
||||
//! # Account manager module.
|
||||
|
||||
use std::collections::BTreeMap;
|
||||
|
||||
use async_std::channel::{Receiver, Sender};
|
||||
@@ -78,6 +80,14 @@ impl Accounts {
|
||||
self.accounts.read().await.get(&id).cloned()
|
||||
}
|
||||
|
||||
/// Returns the currently selected account's id or None if no account is selected.
|
||||
pub async fn get_selected_account_id(&self) -> Option<u32> {
|
||||
match self.config.get_selected_account().await {
|
||||
0 => None,
|
||||
id => Some(id),
|
||||
}
|
||||
}
|
||||
|
||||
/// Select the given account.
|
||||
pub async fn select_account(&self, id: u32) -> Result<()> {
|
||||
self.config.select_account(id).await?;
|
||||
@@ -171,6 +181,7 @@ impl Accounts {
|
||||
account_config.id,
|
||||
)
|
||||
.await?;
|
||||
self.emitter.add_account(&ctx).await?;
|
||||
self.accounts.write().await.insert(account_config.id, ctx);
|
||||
Ok(account_config.id)
|
||||
}
|
||||
@@ -242,12 +253,13 @@ impl Accounts {
|
||||
}
|
||||
}
|
||||
|
||||
/// Unified event emitter.
|
||||
/// Returns unified event emitter.
|
||||
pub async fn get_event_emitter(&self) -> EventEmitter {
|
||||
self.emitter.clone()
|
||||
}
|
||||
}
|
||||
|
||||
/// Unified event emitter for multiple accounts.
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct EventEmitter {
|
||||
/// Aggregate stream of events from all accounts.
|
||||
@@ -315,6 +327,7 @@ impl async_std::stream::Stream for EventEmitter {
|
||||
pub const CONFIG_NAME: &str = "accounts.toml";
|
||||
pub const DB_NAME: &str = "dc.db";
|
||||
|
||||
/// Account manager configuration file.
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct Config {
|
||||
file: PathBuf,
|
||||
@@ -389,7 +402,7 @@ impl Config {
|
||||
}
|
||||
|
||||
/// Create a new account in the given root directory.
|
||||
pub async fn new_account(&self, dir: &PathBuf) -> Result<AccountConfig> {
|
||||
async fn new_account(&self, dir: &PathBuf) -> Result<AccountConfig> {
|
||||
let id = {
|
||||
let inner = &mut self.inner.write().await;
|
||||
let id = inner.next_id;
|
||||
@@ -429,7 +442,7 @@ impl Config {
|
||||
self.sync().await
|
||||
}
|
||||
|
||||
pub async fn get_account(&self, id: u32) -> Option<AccountConfig> {
|
||||
async fn get_account(&self, id: u32) -> Option<AccountConfig> {
|
||||
self.inner
|
||||
.read()
|
||||
.await
|
||||
@@ -460,8 +473,9 @@ impl Config {
|
||||
}
|
||||
}
|
||||
|
||||
/// Configuration of a single account.
|
||||
#[derive(Serialize, Deserialize, Debug, Clone, PartialEq)]
|
||||
pub struct AccountConfig {
|
||||
struct AccountConfig {
|
||||
/// Unique id.
|
||||
pub id: u32,
|
||||
/// Root directory for all data for this account.
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
//! # Autocrypt header module
|
||||
//! # Autocrypt header module.
|
||||
//!
|
||||
//! Parse and create [Autocrypt-headers](https://autocrypt.org/en/latest/level1.html#the-autocrypt-header).
|
||||
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
//! # Blob directory management
|
||||
//! # Blob directory management.
|
||||
|
||||
use core::cmp::max;
|
||||
use std::ffi::OsStr;
|
||||
|
||||
41
src/chat.rs
41
src/chat.rs
@@ -1,4 +1,4 @@
|
||||
//! # Chat module
|
||||
//! # Chat module.
|
||||
|
||||
use std::convert::{TryFrom, TryInto};
|
||||
use std::str::FromStr;
|
||||
@@ -381,7 +381,14 @@ impl ChatId {
|
||||
msg.param.set_cmd(cmd);
|
||||
send_msg(context, self, &mut msg).await?;
|
||||
} else {
|
||||
add_info_msg_with_cmd(context, self, msg_text, cmd).await?;
|
||||
add_info_msg_with_cmd(
|
||||
context,
|
||||
self,
|
||||
msg_text,
|
||||
cmd,
|
||||
dc_create_smeared_timestamp(context).await,
|
||||
)
|
||||
.await?;
|
||||
}
|
||||
|
||||
Ok(())
|
||||
@@ -968,6 +975,7 @@ impl Chat {
|
||||
&self.name
|
||||
}
|
||||
|
||||
/// Returns profile image path for the chat.
|
||||
pub async fn get_profile_image(&self, context: &Context) -> Result<Option<PathBuf>> {
|
||||
if let Some(image_rel) = self.param.get(Param::ProfileImage) {
|
||||
if !image_rel.is_empty() {
|
||||
@@ -1982,6 +1990,7 @@ pub(crate) async fn marknoticed_chat_if_older_than(
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Marks all messages in the chat as noticed.
|
||||
pub async fn marknoticed_chat(context: &Context, chat_id: ChatId) -> Result<()> {
|
||||
// "WHERE" below uses the index `(state, hidden, chat_id)`, see get_fresh_msg_cnt() for reasoning
|
||||
// the additional SELECT statement may speed up things as no write-blocking is needed.
|
||||
@@ -2102,6 +2111,7 @@ pub async fn get_next_media(
|
||||
Ok(ret)
|
||||
}
|
||||
|
||||
/// Returns a vector of contact IDs for given chat ID.
|
||||
pub async fn get_chat_contacts(context: &Context, chat_id: ChatId) -> Result<Vec<u32>> {
|
||||
// Normal chats do not include SELF. Group chats do (as it may happen that one is deleted from a
|
||||
// groupchat but the chats stays visible, moreover, this makes displaying lists easier)
|
||||
@@ -2124,6 +2134,7 @@ pub async fn get_chat_contacts(context: &Context, chat_id: ChatId) -> Result<Vec
|
||||
Ok(list)
|
||||
}
|
||||
|
||||
/// Creates a group chat with a given `name`.
|
||||
pub async fn create_group_chat(
|
||||
context: &Context,
|
||||
protect: ProtectionStatus,
|
||||
@@ -2171,7 +2182,7 @@ pub async fn create_group_chat(
|
||||
Ok(chat_id)
|
||||
}
|
||||
|
||||
/// add a contact to the chats_contact table
|
||||
/// Adds a contact to the `chats_contacts` table.
|
||||
pub(crate) async fn add_to_chat_contacts_table(
|
||||
context: &Context,
|
||||
chat_id: ChatId,
|
||||
@@ -2559,6 +2570,7 @@ pub(crate) async fn is_group_explicitly_left(
|
||||
Ok(exists)
|
||||
}
|
||||
|
||||
/// Sets group or mailing list chat name.
|
||||
pub async fn set_chat_name(context: &Context, chat_id: ChatId, new_name: &str) -> Result<()> {
|
||||
let new_name = improve_single_line_input(new_name);
|
||||
/* the function only sets the names of group chats; normal chats get their names from the contacts */
|
||||
@@ -2808,10 +2820,10 @@ pub(crate) async fn get_chat_cnt(context: &Context) -> Result<usize> {
|
||||
pub(crate) async fn get_chat_id_by_grpid(
|
||||
context: &Context,
|
||||
grpid: impl AsRef<str>,
|
||||
) -> Result<(ChatId, bool, Blocked)> {
|
||||
) -> Result<Option<(ChatId, bool, Blocked)>> {
|
||||
context
|
||||
.sql
|
||||
.query_row(
|
||||
.query_row_optional(
|
||||
"SELECT id, blocked, protected FROM chats WHERE grpid=?;",
|
||||
paramsv![grpid.as_ref()],
|
||||
|row| {
|
||||
@@ -2932,6 +2944,7 @@ pub async fn add_device_msg_with_importance(
|
||||
Ok(msg_id)
|
||||
}
|
||||
|
||||
/// Adds a message to device chat.
|
||||
pub async fn add_device_msg(
|
||||
context: &Context,
|
||||
label: Option<&str>,
|
||||
@@ -2981,6 +2994,7 @@ pub(crate) async fn add_info_msg_with_cmd(
|
||||
chat_id: ChatId,
|
||||
text: impl AsRef<str>,
|
||||
cmd: SystemMessage,
|
||||
timestamp: i64,
|
||||
) -> Result<MsgId> {
|
||||
let rfc724_mid = dc_create_outgoing_rfc724_mid(None, "@device");
|
||||
let ephemeral_timer = chat_id.get_ephemeral_timer(context).await?;
|
||||
@@ -2997,7 +3011,7 @@ pub(crate) async fn add_info_msg_with_cmd(
|
||||
chat_id,
|
||||
DC_CONTACT_ID_INFO,
|
||||
DC_CONTACT_ID_INFO,
|
||||
dc_create_smeared_timestamp(context).await,
|
||||
timestamp,
|
||||
Viewtype::Text,
|
||||
MessageState::InNoticed,
|
||||
text.as_ref().to_string(),
|
||||
@@ -3012,8 +3026,16 @@ pub(crate) async fn add_info_msg_with_cmd(
|
||||
Ok(msg_id)
|
||||
}
|
||||
|
||||
pub(crate) async fn add_info_msg(context: &Context, chat_id: ChatId, text: impl AsRef<str>) {
|
||||
if let Err(e) = add_info_msg_with_cmd(context, chat_id, text, SystemMessage::Unknown).await {
|
||||
/// Adds info message with a given text and `timestamp` to the chat.
|
||||
pub(crate) async fn add_info_msg(
|
||||
context: &Context,
|
||||
chat_id: ChatId,
|
||||
text: impl AsRef<str>,
|
||||
timestamp: i64,
|
||||
) {
|
||||
if let Err(e) =
|
||||
add_info_msg_with_cmd(context, chat_id, text, SystemMessage::Unknown, timestamp).await
|
||||
{
|
||||
warn!(context, "Could not add info msg: {}", e);
|
||||
}
|
||||
}
|
||||
@@ -3637,7 +3659,7 @@ mod tests {
|
||||
let chat_id = create_group_chat(&t, ProtectionStatus::Unprotected, "foo")
|
||||
.await
|
||||
.unwrap();
|
||||
add_info_msg(&t, chat_id, "foo info").await;
|
||||
add_info_msg(&t, chat_id, "foo info", 200000).await;
|
||||
|
||||
let msg = t.get_last_msg_in(chat_id).await;
|
||||
assert_eq!(msg.get_chat_id(), chat_id);
|
||||
@@ -3658,6 +3680,7 @@ mod tests {
|
||||
chat_id,
|
||||
"foo bar info",
|
||||
SystemMessage::EphemeralTimerChanged,
|
||||
10000,
|
||||
)
|
||||
.await
|
||||
.unwrap();
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
//! # Chat list module
|
||||
//! # Chat list module.
|
||||
|
||||
use anyhow::{bail, ensure, Result};
|
||||
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
//! Implementation of Consistent Color Generation
|
||||
//! Implementation of Consistent Color Generation.
|
||||
//!
|
||||
//! Consistent Color Generation is defined in XEP-0392.
|
||||
//!
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
//! # Key-value configuration management
|
||||
//! # Key-value configuration management.
|
||||
|
||||
use anyhow::Result;
|
||||
use strum::{EnumProperty, IntoEnumIterator};
|
||||
@@ -48,6 +48,12 @@ pub enum Config {
|
||||
SmtpCertificateChecks,
|
||||
ServerFlags,
|
||||
|
||||
Socks5Enabled,
|
||||
Socks5Host,
|
||||
Socks5Port,
|
||||
Socks5User,
|
||||
Socks5Password,
|
||||
|
||||
Displayname,
|
||||
Selfstatus,
|
||||
Selfavatar,
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
//! Email accounts autoconfiguration process module
|
||||
//! Email accounts autoconfiguration process module.
|
||||
|
||||
mod auto_mozilla;
|
||||
mod auto_outlook;
|
||||
@@ -14,6 +14,7 @@ use percent_encoding::{utf8_percent_encode, NON_ALPHANUMERIC};
|
||||
|
||||
use crate::dc_tools::EmailAddress;
|
||||
use crate::imap::Imap;
|
||||
use crate::login_param::Socks5Config;
|
||||
use crate::login_param::{LoginParam, ServerLoginParam};
|
||||
use crate::message::Message;
|
||||
use crate::oauth2::dc_get_oauth2_addr;
|
||||
@@ -170,12 +171,17 @@ async fn configure(ctx: &Context, param: &mut LoginParam) -> Result<()> {
|
||||
DC_LP_AUTH_NORMAL as i32
|
||||
};
|
||||
|
||||
let socks5_config = param.socks5_config.clone();
|
||||
let socks5_enabled = socks5_config.is_some();
|
||||
|
||||
let ctx2 = ctx.clone();
|
||||
let update_device_chats_handle = task::spawn(async move { ctx2.update_device_chats().await });
|
||||
|
||||
// Step 1: Load the parameters and check email-address and password
|
||||
|
||||
if oauth2 {
|
||||
// Do oauth2 only if socks5 is disabled. As soon as we have a http library that can do
|
||||
// socks5 requests, this can work with socks5 too
|
||||
if oauth2 && !socks5_enabled {
|
||||
// the used oauth2 addr may differ, check this.
|
||||
// if dc_get_oauth2_addr() is not available in the oauth2 implementation, just use the given one.
|
||||
progress!(ctx, 10);
|
||||
@@ -217,7 +223,7 @@ async fn configure(ctx: &Context, param: &mut LoginParam) -> Result<()> {
|
||||
"checking internal provider-info for offline autoconfig"
|
||||
);
|
||||
|
||||
if let Some(provider) = provider::get_provider_info(¶m_domain).await {
|
||||
if let Some(provider) = provider::get_provider_info(¶m_domain, socks5_enabled).await {
|
||||
param.provider = Some(provider);
|
||||
match provider.status {
|
||||
provider::Status::Ok | provider::Status::Preparation => {
|
||||
@@ -256,9 +262,16 @@ async fn configure(ctx: &Context, param: &mut LoginParam) -> Result<()> {
|
||||
}
|
||||
}
|
||||
} else {
|
||||
// Try receiving autoconfig
|
||||
info!(ctx, "no offline autoconfig found");
|
||||
param_autoconfig =
|
||||
get_autoconfig(ctx, param, ¶m_domain, ¶m_addr_urlencoded).await;
|
||||
param_autoconfig = if socks5_enabled {
|
||||
// Currently we can't do http requests through socks5, to not leak
|
||||
// the ip, just don't do online autoconfig
|
||||
info!(ctx, "socks5 enabled, skipping autoconfig");
|
||||
None
|
||||
} else {
|
||||
get_autoconfig(ctx, param, ¶m_domain, ¶m_addr_urlencoded).await
|
||||
}
|
||||
}
|
||||
} else {
|
||||
param_autoconfig = None;
|
||||
@@ -320,6 +333,7 @@ async fn configure(ctx: &Context, param: &mut LoginParam) -> Result<()> {
|
||||
match try_smtp_one_param(
|
||||
&context_smtp,
|
||||
&smtp_param,
|
||||
&socks5_config,
|
||||
&smtp_addr,
|
||||
oauth2,
|
||||
provider_strict_tls,
|
||||
@@ -359,7 +373,16 @@ async fn configure(ctx: &Context, param: &mut LoginParam) -> Result<()> {
|
||||
param.imap.port = imap_server.port;
|
||||
param.imap.security = imap_server.socket;
|
||||
|
||||
match try_imap_one_param(ctx, ¶m.imap, ¶m.addr, oauth2, provider_strict_tls).await {
|
||||
match try_imap_one_param(
|
||||
ctx,
|
||||
¶m.imap,
|
||||
¶m.socks5_config,
|
||||
¶m.addr,
|
||||
oauth2,
|
||||
provider_strict_tls,
|
||||
)
|
||||
.await
|
||||
{
|
||||
Ok(configured_imap) => {
|
||||
imap = Some(configured_imap);
|
||||
break;
|
||||
@@ -507,6 +530,7 @@ async fn get_autoconfig(
|
||||
async fn try_imap_one_param(
|
||||
context: &Context,
|
||||
param: &ServerLoginParam,
|
||||
socks5_config: &Option<Socks5Config>,
|
||||
addr: &str,
|
||||
oauth2: bool,
|
||||
provider_strict_tls: bool,
|
||||
@@ -519,7 +543,16 @@ async fn try_imap_one_param(
|
||||
|
||||
let (_s, r) = async_std::channel::bounded(1);
|
||||
|
||||
let mut imap = match Imap::new(param, addr, oauth2, provider_strict_tls, r).await {
|
||||
let mut imap = match Imap::new(
|
||||
param,
|
||||
socks5_config.clone(),
|
||||
addr,
|
||||
oauth2,
|
||||
provider_strict_tls,
|
||||
r,
|
||||
)
|
||||
.await
|
||||
{
|
||||
Err(err) => {
|
||||
info!(context, "failure: {}", err);
|
||||
return Err(ConfigurationError {
|
||||
@@ -548,19 +581,37 @@ async fn try_imap_one_param(
|
||||
async fn try_smtp_one_param(
|
||||
context: &Context,
|
||||
param: &ServerLoginParam,
|
||||
socks5_config: &Option<Socks5Config>,
|
||||
addr: &str,
|
||||
oauth2: bool,
|
||||
provider_strict_tls: bool,
|
||||
smtp: &mut Smtp,
|
||||
) -> Result<(), ConfigurationError> {
|
||||
let inf = format!(
|
||||
"smtp: {}@{}:{} security={} certificate_checks={} oauth2={}",
|
||||
param.user, param.server, param.port, param.security, param.certificate_checks, oauth2
|
||||
"smtp: {}@{}:{} security={} certificate_checks={} oauth2={} socks5_config={}",
|
||||
param.user,
|
||||
param.server,
|
||||
param.port,
|
||||
param.security,
|
||||
param.certificate_checks,
|
||||
oauth2,
|
||||
if let Some(socks5_config) = socks5_config {
|
||||
socks5_config.to_string()
|
||||
} else {
|
||||
"None".to_string()
|
||||
}
|
||||
);
|
||||
info!(context, "Trying: {}", inf);
|
||||
|
||||
if let Err(err) = smtp
|
||||
.connect(context, param, addr, oauth2, provider_strict_tls)
|
||||
.connect(
|
||||
context,
|
||||
param,
|
||||
socks5_config,
|
||||
addr,
|
||||
oauth2,
|
||||
provider_strict_tls,
|
||||
)
|
||||
.await
|
||||
{
|
||||
info!(context, "failure: {}", err);
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
//! # Constants
|
||||
//! # Constants.
|
||||
use deltachat_derive::{FromSql, ToSql};
|
||||
use once_cell::sync::Lazy;
|
||||
use serde::{Deserialize, Serialize};
|
||||
@@ -165,36 +165,18 @@ pub const DC_MSG_ID_MARKER1: u32 = 1;
|
||||
pub const DC_MSG_ID_DAYMARKER: u32 = 9;
|
||||
pub const DC_MSG_ID_LAST_SPECIAL: u32 = 9;
|
||||
|
||||
/// string that indicates sth. is left out or truncated
|
||||
pub const DC_ELLIPSE: &str = "[...]";
|
||||
/// String that indicates that something is left out or truncated.
|
||||
pub const DC_ELLIPSIS: &str = "[...]";
|
||||
|
||||
/// to keep bubbles and chat flow usable,
|
||||
/// and to avoid problems with controls using very long texts,
|
||||
/// we limit the text length to DC_DESIRED_TEXT_LEN.
|
||||
/// if the text is longer, the full text can be retrieved using has_html()/get_html().
|
||||
/// Message length limit.
|
||||
///
|
||||
/// we are using a bit less than DC_MAX_GET_TEXT_LEN to avoid cutting twice
|
||||
/// (a bit less as truncation may not be exact and ellipses may be added).
|
||||
/// To keep bubbles and chat flow usable and to avoid problems with controls using very long texts,
|
||||
/// we limit the text length to `DC_DESIRED_TEXT_LEN`. If the text is longer, the full text can be
|
||||
/// retrieved using has_html()/get_html().
|
||||
///
|
||||
/// note, that DC_DESIRED_TEXT_LEN and DC_MAX_GET_TEXT_LEN
|
||||
/// define max. number of bytes, _not_ unicode graphemes.
|
||||
/// in general, that seems to be okay for such an upper limit,
|
||||
/// esp. as calculating the number of graphemes is not simple
|
||||
/// (one graphemes may be a sequence of code points which is a sequence of bytes).
|
||||
/// also even if we have the exact number of graphemes,
|
||||
/// that would not always help on getting an idea about the screen space used
|
||||
/// (to keep bubbles and chat flow usable).
|
||||
///
|
||||
/// therefore, the number of bytes is only a very rough estimation,
|
||||
/// however, the ~30K seems to work okayish for a while,
|
||||
/// if it turns out, it is too few for some alphabet, we can still increase.
|
||||
pub const DC_DESIRED_TEXT_LEN: usize = 29_000;
|
||||
|
||||
/// approx. max. length (number of bytes) returned by dc_msg_get_text()
|
||||
pub const DC_MAX_GET_TEXT_LEN: usize = 30_000;
|
||||
|
||||
/// approx. max. length returned by dc_get_msg_info()
|
||||
pub const DC_MAX_GET_INFO_LEN: usize = 100_000;
|
||||
/// Note that for simplicity maximum length is defined as the number of Unicode Scalar Values (Rust
|
||||
/// `char`s), not Unicode Grapheme Clusters.
|
||||
pub const DC_DESIRED_TEXT_LEN: usize = 5000;
|
||||
|
||||
pub const DC_CONTACT_ID_UNDEFINED: u32 = 0;
|
||||
pub const DC_CONTACT_ID_SELF: u32 = 1;
|
||||
|
||||
@@ -218,6 +218,7 @@ impl Contact {
|
||||
} else if contact_id == DC_CONTACT_ID_DEVICE {
|
||||
contact.name = stock_str::device_messages(context).await;
|
||||
contact.addr = DC_CONTACT_ID_DEVICE_ADDR.to_string();
|
||||
contact.status = stock_str::device_messages_hint(context).await;
|
||||
}
|
||||
Ok(contact)
|
||||
}
|
||||
@@ -1202,7 +1203,8 @@ WHERE type=? AND id IN (
|
||||
// also unblock mailinglist
|
||||
// if the contact is a mailinglist address explicitly created to allow unblocking
|
||||
if !new_blocking && contact.origin == Origin::MailinglistAddress {
|
||||
if let Ok((chat_id, _, _)) = chat::get_chat_id_by_grpid(context, contact.addr).await {
|
||||
if let Some((chat_id, _, _)) = chat::get_chat_id_by_grpid(context, contact.addr).await?
|
||||
{
|
||||
chat_id.unblock(context).await?;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
//! Context module
|
||||
//! Context module.
|
||||
|
||||
use std::collections::{BTreeMap, HashMap};
|
||||
use std::ffi::OsString;
|
||||
@@ -22,6 +22,7 @@ use crate::events::{Event, EventEmitter, EventType, Events};
|
||||
use crate::key::{DcKey, SignedPublicKey};
|
||||
use crate::login_param::LoginParam;
|
||||
use crate::message::{self, MessageState, MsgId};
|
||||
use crate::quota::QuotaInfo;
|
||||
use crate::scheduler::Scheduler;
|
||||
use crate::securejoin::Bob;
|
||||
use crate::sql::Sql;
|
||||
@@ -62,6 +63,10 @@ pub struct InnerContext {
|
||||
pub(crate) scheduler: RwLock<Scheduler>,
|
||||
pub(crate) ephemeral_task: RwLock<Option<task::JoinHandle<()>>>,
|
||||
|
||||
/// Recently loaded quota information, if any.
|
||||
/// Set to `None` if quota was never tried to load.
|
||||
pub(crate) quota: RwLock<Option<QuotaInfo>>,
|
||||
|
||||
pub(crate) last_full_folder_scan: Mutex<Option<Instant>>,
|
||||
|
||||
/// ID for this `Context` in the current process.
|
||||
@@ -139,6 +144,7 @@ impl Context {
|
||||
events: Events::default(),
|
||||
scheduler: RwLock::new(Scheduler::Stopped),
|
||||
ephemeral_task: RwLock::new(None),
|
||||
quota: RwLock::new(None),
|
||||
creation_time: std::time::SystemTime::now(),
|
||||
last_full_folder_scan: Mutex::new(None),
|
||||
};
|
||||
@@ -283,6 +289,7 @@ impl Context {
|
||||
let request_msgs = message::get_request_msg_cnt(self).await as usize;
|
||||
let contacts = Contact::get_real_cnt(self).await? as usize;
|
||||
let is_configured = self.get_config_int(Config::Configured).await?;
|
||||
let socks5_enabled = self.get_config_int(Config::Socks5Enabled).await?;
|
||||
let dbversion = self
|
||||
.sql
|
||||
.get_raw_config_int("dbversion")
|
||||
@@ -351,6 +358,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("entered_account_settings", l.to_string());
|
||||
res.insert("used_account_settings", l2.to_string());
|
||||
res.insert(
|
||||
@@ -872,6 +880,10 @@ mod tests {
|
||||
"send_security",
|
||||
"server_flags",
|
||||
"smtp_certificate_checks",
|
||||
"socks5_host",
|
||||
"socks5_port",
|
||||
"socks5_user",
|
||||
"socks5_password",
|
||||
];
|
||||
let t = TestContext::new().await;
|
||||
let info = t.get_info().await.unwrap();
|
||||
|
||||
@@ -1,3 +1,5 @@
|
||||
//! Internet Message Format reception pipeline.
|
||||
|
||||
use std::convert::TryFrom;
|
||||
|
||||
use anyhow::{bail, ensure, format_err, Result};
|
||||
@@ -91,7 +93,7 @@ pub(crate) async fn dc_receive_imf_inner(
|
||||
}
|
||||
|
||||
let mut sent_timestamp = if let Some(value) = mime_parser
|
||||
.get(HeaderDef::Date)
|
||||
.get_header(HeaderDef::Date)
|
||||
.and_then(|value| mailparse::dateparse(value).ok())
|
||||
{
|
||||
value
|
||||
@@ -132,7 +134,7 @@ pub(crate) async fn dc_receive_imf_inner(
|
||||
let mut create_event_to_send = Some(CreateEvent::MsgsChanged);
|
||||
|
||||
let prevent_rename =
|
||||
mime_parser.is_mailinglist_message() || mime_parser.get(HeaderDef::Sender).is_some();
|
||||
mime_parser.is_mailinglist_message() || mime_parser.get_header(HeaderDef::Sender).is_some();
|
||||
|
||||
// get From: (it can be an address list!) and check if it is known (for known From:'s we add
|
||||
// the other To:/Cc: in the 3rd pass)
|
||||
@@ -371,7 +373,7 @@ async fn add_parts(
|
||||
prevent_rename: bool,
|
||||
) -> Result<ChatId> {
|
||||
let mut state: MessageState;
|
||||
let mut chat_id = ChatId::new(0);
|
||||
let mut chat_id = None;
|
||||
let mut chat_id_blocked = Blocked::Not;
|
||||
let mut incoming_origin = incoming_origin;
|
||||
|
||||
@@ -400,7 +402,7 @@ async fn add_parts(
|
||||
match show_emails {
|
||||
ShowEmails::Off => {
|
||||
info!(context, "Classical email not shown (TRASH)");
|
||||
chat_id = DC_CHAT_ID_TRASH;
|
||||
chat_id = Some(DC_CHAT_ID_TRASH);
|
||||
allow_creation = false;
|
||||
}
|
||||
ShowEmails::AcceptedContacts => allow_creation = false,
|
||||
@@ -423,9 +425,9 @@ async fn add_parts(
|
||||
to_id = DC_CONTACT_ID_SELF;
|
||||
|
||||
// handshake may mark contacts as verified and must be processed before chats are created
|
||||
if mime_parser.get(HeaderDef::SecureJoin).is_some() {
|
||||
if mime_parser.get_header(HeaderDef::SecureJoin).is_some() {
|
||||
is_dc_message = MessengerMessage::Yes; // avoid discarding by show_emails setting
|
||||
chat_id = ChatId::new(0);
|
||||
chat_id = None;
|
||||
allow_creation = true;
|
||||
match handle_securejoin_handshake(context, mime_parser, from_id).await {
|
||||
Ok(securejoin::HandshakeMessage::Done) => {
|
||||
@@ -441,9 +443,8 @@ async fn add_parts(
|
||||
// process messages as "member added" normally
|
||||
}
|
||||
Err(err) => {
|
||||
*hidden = true;
|
||||
warn!(context, "Error in Secure-Join message handling: {}", err);
|
||||
return Ok(chat_id);
|
||||
return Ok(DC_CHAT_ID_TRASH);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -452,21 +453,23 @@ async fn add_parts(
|
||||
.await
|
||||
.unwrap_or_default();
|
||||
|
||||
if chat_id.is_unset() && mime_parser.failure_report.is_some() {
|
||||
chat_id = DC_CHAT_ID_TRASH;
|
||||
if chat_id.is_none() && mime_parser.failure_report.is_some() {
|
||||
chat_id = Some(DC_CHAT_ID_TRASH);
|
||||
info!(context, "Message belongs to an NDN (TRASH)",);
|
||||
}
|
||||
|
||||
if chat_id.is_unset() {
|
||||
if chat_id.is_none() {
|
||||
// try to assign to a chat based on In-Reply-To/References:
|
||||
|
||||
let (new_chat_id, new_chat_id_blocked) =
|
||||
lookup_chat_by_reply(context, &mime_parser, &parent, from_id, to_ids).await?;
|
||||
chat_id = new_chat_id;
|
||||
chat_id_blocked = new_chat_id_blocked;
|
||||
if let Some((new_chat_id, new_chat_id_blocked)) =
|
||||
lookup_chat_by_reply(context, &mime_parser, &parent, from_id, to_ids).await?
|
||||
{
|
||||
chat_id = Some(new_chat_id);
|
||||
chat_id_blocked = new_chat_id_blocked;
|
||||
}
|
||||
}
|
||||
|
||||
if chat_id.is_unset() {
|
||||
if chat_id.is_none() {
|
||||
// try to create a group
|
||||
|
||||
let create_blocked = match test_normal_chat {
|
||||
@@ -477,7 +480,7 @@ async fn add_parts(
|
||||
_ => Blocked::Request,
|
||||
};
|
||||
|
||||
let (new_chat_id, new_chat_id_blocked) = create_or_lookup_group(
|
||||
if let Some((new_chat_id, new_chat_id_blocked)) = create_or_lookup_group(
|
||||
context,
|
||||
&mut mime_parser,
|
||||
if test_normal_chat.is_none() {
|
||||
@@ -489,63 +492,69 @@ async fn add_parts(
|
||||
from_id,
|
||||
to_ids,
|
||||
)
|
||||
.await?;
|
||||
chat_id = new_chat_id;
|
||||
chat_id_blocked = new_chat_id_blocked;
|
||||
if !chat_id.is_unset()
|
||||
&& chat_id_blocked != Blocked::Not
|
||||
&& create_blocked == Blocked::Not
|
||||
.await?
|
||||
{
|
||||
new_chat_id.unblock(context).await?;
|
||||
chat_id_blocked = Blocked::Not;
|
||||
chat_id = Some(new_chat_id);
|
||||
chat_id_blocked = new_chat_id_blocked;
|
||||
if chat_id_blocked != Blocked::Not && create_blocked == Blocked::Not {
|
||||
new_chat_id.unblock(context).await?;
|
||||
chat_id_blocked = Blocked::Not;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// In lookup_chat_by_reply() and create_or_lookup_group(), it can happen that the message is put into a chat
|
||||
// but the From-address is not a member of this chat.
|
||||
if !chat_id.is_unset() && !chat::is_contact_in_chat(context, chat_id, from_id as u32).await
|
||||
{
|
||||
let chat = Chat::load_from_db(context, chat_id).await?;
|
||||
if chat.is_protected() {
|
||||
let s = stock_str::unknown_sender_for_chat(context).await;
|
||||
mime_parser.repl_msg_by_error(s);
|
||||
} else if let Some(from) = mime_parser.from.first() {
|
||||
// In non-protected chats, just mark the sender as overridden. Therefore, the UI will prepend `~`
|
||||
// to the sender's name, indicating to the user that he/she is not part of the group.
|
||||
let name: &str = from.display_name.as_ref().unwrap_or(&from.addr);
|
||||
for part in mime_parser.parts.iter_mut() {
|
||||
part.param.set(Param::OverrideSenderDisplayname, name);
|
||||
if let Some(chat_id) = chat_id {
|
||||
if !chat::is_contact_in_chat(context, chat_id, from_id as u32).await {
|
||||
let chat = Chat::load_from_db(context, chat_id).await?;
|
||||
if chat.is_protected() {
|
||||
let s = stock_str::unknown_sender_for_chat(context).await;
|
||||
mime_parser.repl_msg_by_error(s);
|
||||
} else if let Some(from) = mime_parser.from.first() {
|
||||
// In non-protected chats, just mark the sender as overridden. Therefore, the UI will prepend `~`
|
||||
// to the sender's name, indicating to the user that he/she is not part of the group.
|
||||
let name: &str = from.display_name.as_ref().unwrap_or(&from.addr);
|
||||
for part in mime_parser.parts.iter_mut() {
|
||||
part.param.set(Param::OverrideSenderDisplayname, name);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if chat_id.is_unset() {
|
||||
if chat_id.is_none() {
|
||||
// check if the message belongs to a mailing list
|
||||
match mime_parser.get_mailinglist_type() {
|
||||
MailinglistType::ListIdBased => {
|
||||
if let Some(list_id) = mime_parser.get(HeaderDef::ListId) {
|
||||
let (new_chat_id, new_chat_id_blocked) = create_or_lookup_mailinglist(
|
||||
context,
|
||||
allow_creation,
|
||||
list_id,
|
||||
mime_parser,
|
||||
)
|
||||
.await;
|
||||
chat_id = new_chat_id;
|
||||
chat_id_blocked = new_chat_id_blocked;
|
||||
if let Some(list_id) = mime_parser.get_header(HeaderDef::ListId) {
|
||||
if let Some((new_chat_id, new_chat_id_blocked)) =
|
||||
create_or_lookup_mailinglist(
|
||||
context,
|
||||
allow_creation,
|
||||
list_id,
|
||||
mime_parser,
|
||||
)
|
||||
.await?
|
||||
{
|
||||
chat_id = Some(new_chat_id);
|
||||
chat_id_blocked = new_chat_id_blocked;
|
||||
}
|
||||
}
|
||||
}
|
||||
MailinglistType::SenderBased => {
|
||||
if let Some(sender) = mime_parser.get(HeaderDef::Sender) {
|
||||
let (new_chat_id, new_chat_id_blocked) = create_or_lookup_mailinglist(
|
||||
context,
|
||||
allow_creation,
|
||||
sender,
|
||||
mime_parser,
|
||||
)
|
||||
.await;
|
||||
chat_id = new_chat_id;
|
||||
chat_id_blocked = new_chat_id_blocked;
|
||||
if let Some(sender) = mime_parser.get_header(HeaderDef::Sender) {
|
||||
if let Some((new_chat_id, new_chat_id_blocked)) =
|
||||
create_or_lookup_mailinglist(
|
||||
context,
|
||||
allow_creation,
|
||||
sender,
|
||||
mime_parser,
|
||||
)
|
||||
.await?
|
||||
{
|
||||
chat_id = Some(new_chat_id);
|
||||
chat_id_blocked = new_chat_id_blocked;
|
||||
}
|
||||
}
|
||||
}
|
||||
MailinglistType::None => {}
|
||||
@@ -564,7 +573,7 @@ async fn add_parts(
|
||||
}
|
||||
}
|
||||
|
||||
if chat_id.is_unset() {
|
||||
if chat_id.is_none() {
|
||||
// try to create a normal chat
|
||||
let create_blocked = if from_id == to_id {
|
||||
Blocked::Not
|
||||
@@ -573,40 +582,39 @@ async fn add_parts(
|
||||
};
|
||||
|
||||
if let Some(chat) = test_normal_chat {
|
||||
chat_id = chat.id;
|
||||
chat_id = Some(chat.id);
|
||||
chat_id_blocked = chat.blocked;
|
||||
} else if allow_creation {
|
||||
if let Ok(chat) = ChatIdBlocked::get_for_contact(context, from_id, create_blocked)
|
||||
.await
|
||||
.log_err(context, "Failed to get (new) chat for contact")
|
||||
{
|
||||
chat_id = chat.id;
|
||||
chat_id = Some(chat.id);
|
||||
chat_id_blocked = chat.blocked;
|
||||
}
|
||||
}
|
||||
if !chat_id.is_unset() && Blocked::Not != chat_id_blocked {
|
||||
if Blocked::Not == create_blocked {
|
||||
chat_id.unblock(context).await?;
|
||||
chat_id_blocked = Blocked::Not;
|
||||
} else if parent.is_some() {
|
||||
// we do not want any chat to be created implicitly. Because of the origin-scale-up,
|
||||
// the contact requests will pop up and this should be just fine.
|
||||
Contact::scaleup_origin_by_id(context, from_id, Origin::IncomingReplyTo).await;
|
||||
info!(
|
||||
context,
|
||||
"Message is a reply to a known message, mark sender as known.",
|
||||
);
|
||||
if !incoming_origin.is_known() {
|
||||
incoming_origin = Origin::IncomingReplyTo;
|
||||
|
||||
if let Some(chat_id) = chat_id {
|
||||
if Blocked::Not != chat_id_blocked {
|
||||
if Blocked::Not == create_blocked {
|
||||
chat_id.unblock(context).await?;
|
||||
chat_id_blocked = Blocked::Not;
|
||||
} else if parent.is_some() {
|
||||
// we do not want any chat to be created implicitly. Because of the origin-scale-up,
|
||||
// the contact requests will pop up and this should be just fine.
|
||||
Contact::scaleup_origin_by_id(context, from_id, Origin::IncomingReplyTo)
|
||||
.await;
|
||||
info!(
|
||||
context,
|
||||
"Message is a reply to a known message, mark sender as known.",
|
||||
);
|
||||
if !incoming_origin.is_known() {
|
||||
incoming_origin = Origin::IncomingReplyTo;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
if chat_id.is_unset() {
|
||||
// maybe from_id is null or sth. else is suspicious, move message to trash
|
||||
chat_id = DC_CHAT_ID_TRASH;
|
||||
info!(context, "No chat id for incoming msg (TRASH)")
|
||||
}
|
||||
|
||||
// if the chat_id is blocked,
|
||||
// for unknown senders and non-delta-messages set the state to NOTICED
|
||||
@@ -629,7 +637,7 @@ async fn add_parts(
|
||||
&& (is_dc_message == MessengerMessage::No)
|
||||
&& context.is_spam_folder(server_folder).await?;
|
||||
if is_spam {
|
||||
chat_id = DC_CHAT_ID_TRASH;
|
||||
chat_id = Some(DC_CHAT_ID_TRASH);
|
||||
info!(context, "Message is probably spam (TRASH)");
|
||||
}
|
||||
} else {
|
||||
@@ -641,9 +649,9 @@ async fn add_parts(
|
||||
to_id = to_ids.get_index(0).cloned().unwrap_or_default();
|
||||
|
||||
// handshake may mark contacts as verified and must be processed before chats are created
|
||||
if mime_parser.get(HeaderDef::SecureJoin).is_some() {
|
||||
if mime_parser.get_header(HeaderDef::SecureJoin).is_some() {
|
||||
is_dc_message = MessengerMessage::Yes; // avoid discarding by show_emails setting
|
||||
chat_id = ChatId::new(0);
|
||||
chat_id = None;
|
||||
allow_creation = true;
|
||||
match observe_securejoin_on_other_device(context, mime_parser, to_id).await {
|
||||
Ok(securejoin::HandshakeMessage::Done)
|
||||
@@ -654,9 +662,8 @@ async fn add_parts(
|
||||
// process messages as "member added" normally
|
||||
}
|
||||
Err(err) => {
|
||||
*hidden = true;
|
||||
warn!(context, "Error in Secure-Join watching: {}", err);
|
||||
return Ok(chat_id);
|
||||
return Ok(DC_CHAT_ID_TRASH);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -668,8 +675,8 @@ async fn add_parts(
|
||||
// such as systemli.org in June 2021 remove their own Received headers on incoming mails)
|
||||
// and we know Delta Chat never stores drafts on IMAP servers.
|
||||
let is_draft = !context.is_sentbox(server_folder).await?
|
||||
&& mime_parser.get(HeaderDef::Received).is_none()
|
||||
&& mime_parser.get(HeaderDef::ChatVersion).is_none();
|
||||
&& mime_parser.get_header(HeaderDef::Received).is_none()
|
||||
&& mime_parser.get_header(HeaderDef::ChatVersion).is_none();
|
||||
|
||||
// Mozilla Thunderbird does not set \Draft flag on "Templates", but sets
|
||||
// X-Mozilla-Draft-Info header, which can be used to detect both drafts and templates
|
||||
@@ -677,27 +684,32 @@ async fn add_parts(
|
||||
//
|
||||
// This check is not necessary now, but may become useful if the `Received:` header check
|
||||
// is removed completely later.
|
||||
let is_draft = is_draft || mime_parser.get(HeaderDef::XMozillaDraftInfo).is_some();
|
||||
let is_draft = is_draft
|
||||
|| mime_parser
|
||||
.get_header(HeaderDef::XMozillaDraftInfo)
|
||||
.is_some();
|
||||
|
||||
if is_draft {
|
||||
// Most mailboxes have a "Drafts" folder where constantly new emails appear but we don't actually want to show them
|
||||
info!(context, "Email is probably just a draft (TRASH)");
|
||||
chat_id = DC_CHAT_ID_TRASH;
|
||||
chat_id = Some(DC_CHAT_ID_TRASH);
|
||||
allow_creation = false;
|
||||
}
|
||||
|
||||
if chat_id.is_unset() {
|
||||
if chat_id.is_none() {
|
||||
// try to assign to a chat based on In-Reply-To/References:
|
||||
|
||||
let (new_chat_id, new_chat_id_blocked) =
|
||||
lookup_chat_by_reply(context, &mime_parser, &parent, from_id, to_ids).await?;
|
||||
chat_id = new_chat_id;
|
||||
chat_id_blocked = new_chat_id_blocked;
|
||||
if let Some((new_chat_id, new_chat_id_blocked)) =
|
||||
lookup_chat_by_reply(context, &mime_parser, &parent, from_id, to_ids).await?
|
||||
{
|
||||
chat_id = Some(new_chat_id);
|
||||
chat_id_blocked = new_chat_id_blocked;
|
||||
}
|
||||
}
|
||||
|
||||
if !to_ids.is_empty() {
|
||||
if chat_id.is_unset() {
|
||||
let (new_chat_id, new_chat_id_blocked) = create_or_lookup_group(
|
||||
if chat_id.is_none() {
|
||||
if let Some((new_chat_id, new_chat_id_blocked)) = create_or_lookup_group(
|
||||
context,
|
||||
&mut mime_parser,
|
||||
allow_creation,
|
||||
@@ -705,16 +717,18 @@ async fn add_parts(
|
||||
from_id,
|
||||
to_ids,
|
||||
)
|
||||
.await?;
|
||||
chat_id = new_chat_id;
|
||||
chat_id_blocked = new_chat_id_blocked;
|
||||
// automatically unblock chat when the user sends a message
|
||||
if !chat_id.is_unset() && chat_id_blocked != Blocked::Not {
|
||||
new_chat_id.unblock(context).await?;
|
||||
chat_id_blocked = Blocked::Not;
|
||||
.await?
|
||||
{
|
||||
chat_id = Some(new_chat_id);
|
||||
chat_id_blocked = new_chat_id_blocked;
|
||||
// automatically unblock chat when the user sends a message
|
||||
if chat_id_blocked != Blocked::Not {
|
||||
new_chat_id.unblock(context).await?;
|
||||
chat_id_blocked = Blocked::Not;
|
||||
}
|
||||
}
|
||||
}
|
||||
if chat_id.is_unset() && allow_creation {
|
||||
if chat_id.is_none() && allow_creation {
|
||||
let create_blocked = if !Contact::is_blocked_load(context, to_id).await {
|
||||
Blocked::Not
|
||||
} else {
|
||||
@@ -723,16 +737,15 @@ async fn add_parts(
|
||||
if let Ok(chat) =
|
||||
ChatIdBlocked::get_for_contact(context, to_id, create_blocked).await
|
||||
{
|
||||
chat_id = chat.id;
|
||||
chat_id = Some(chat.id);
|
||||
chat_id_blocked = chat.blocked;
|
||||
}
|
||||
|
||||
if !chat_id.is_unset()
|
||||
&& Blocked::Not != chat_id_blocked
|
||||
&& Blocked::Not == create_blocked
|
||||
{
|
||||
chat_id.unblock(context).await?;
|
||||
chat_id_blocked = Blocked::Not;
|
||||
if let Some(chat_id) = chat_id {
|
||||
if Blocked::Not != chat_id_blocked && Blocked::Not == create_blocked {
|
||||
chat_id.unblock(context).await?;
|
||||
chat_id_blocked = Blocked::Not;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -740,7 +753,7 @@ async fn add_parts(
|
||||
&& to_ids.len() == 1
|
||||
&& to_ids.contains(&DC_CONTACT_ID_SELF);
|
||||
|
||||
if chat_id.is_unset() && self_sent {
|
||||
if chat_id.is_none() && self_sent {
|
||||
// from_id==to_id==DC_CONTACT_ID_SELF - this is a self-sent messages,
|
||||
// maybe an Autocrypt Setup Message
|
||||
if let Ok(chat) =
|
||||
@@ -748,29 +761,33 @@ async fn add_parts(
|
||||
.await
|
||||
.log_err(context, "Failed to get (new) chat for contact")
|
||||
{
|
||||
chat_id = chat.id;
|
||||
chat_id = Some(chat.id);
|
||||
chat_id_blocked = chat.blocked;
|
||||
}
|
||||
|
||||
if !chat_id.is_unset() && Blocked::Not != chat_id_blocked {
|
||||
chat_id.unblock(context).await?;
|
||||
chat_id_blocked = Blocked::Not;
|
||||
if let Some(chat_id) = chat_id {
|
||||
if Blocked::Not != chat_id_blocked {
|
||||
chat_id.unblock(context).await?;
|
||||
chat_id_blocked = Blocked::Not;
|
||||
}
|
||||
}
|
||||
}
|
||||
if chat_id.is_unset() {
|
||||
chat_id = DC_CHAT_ID_TRASH;
|
||||
info!(context, "No chat id for outgoing message (TRASH)")
|
||||
}
|
||||
}
|
||||
|
||||
if fetching_existing_messages && mime_parser.decrypting_failed {
|
||||
chat_id = DC_CHAT_ID_TRASH;
|
||||
chat_id = Some(DC_CHAT_ID_TRASH);
|
||||
// We are only gathering old messages on first start. We do not want to add loads of non-decryptable messages to the chats.
|
||||
info!(context, "Existing non-decipherable message. (TRASH)");
|
||||
}
|
||||
|
||||
let chat_id = chat_id.unwrap_or_else(|| {
|
||||
info!(context, "No chat id for message (TRASH)");
|
||||
DC_CHAT_ID_TRASH
|
||||
});
|
||||
|
||||
// Extract ephemeral timer from the message.
|
||||
let mut ephemeral_timer = if let Some(value) = mime_parser.get(HeaderDef::EphemeralTimer) {
|
||||
let mut ephemeral_timer = if let Some(value) = mime_parser.get_header(HeaderDef::EphemeralTimer)
|
||||
{
|
||||
match value.parse::<EphemeralTimer>() {
|
||||
Ok(timer) => timer,
|
||||
Err(err) => {
|
||||
@@ -787,6 +804,12 @@ async fn add_parts(
|
||||
|
||||
let location_kml_is = mime_parser.location_kml.is_some();
|
||||
|
||||
// correct message_timestamp, it should not be used before,
|
||||
// however, we cannot do this earlier as we need from_id to be set
|
||||
let in_fresh = state == MessageState::InFresh;
|
||||
let rcvd_timestamp = time();
|
||||
let sort_timestamp = calc_sort_timestamp(context, *sent_timestamp, chat_id, in_fresh).await?;
|
||||
|
||||
// Apply ephemeral timer changes to the chat.
|
||||
//
|
||||
// Only non-hidden timers are applied now. Timers from hidden
|
||||
@@ -814,6 +837,7 @@ async fn add_parts(
|
||||
context,
|
||||
chat_id,
|
||||
stock_ephemeral_timer_changed(context, ephemeral_timer, from_id).await,
|
||||
sort_timestamp,
|
||||
)
|
||||
.await;
|
||||
}
|
||||
@@ -857,6 +881,7 @@ async fn add_parts(
|
||||
context,
|
||||
chat_id,
|
||||
format!("Cannot set protection: {}", e),
|
||||
sort_timestamp,
|
||||
)
|
||||
.await;
|
||||
return Ok(chat_id); // do not return an error as this would result in retrying the message
|
||||
@@ -872,12 +897,6 @@ async fn add_parts(
|
||||
}
|
||||
}
|
||||
|
||||
// correct message_timestamp, it should not be used before,
|
||||
// however, we cannot do this earlier as we need from_id to be set
|
||||
let in_fresh = state == MessageState::InFresh;
|
||||
let rcvd_timestamp = time();
|
||||
let sort_timestamp = calc_sort_timestamp(context, *sent_timestamp, chat_id, in_fresh).await?;
|
||||
|
||||
// Ensure replies to messages are sorted after the parent message.
|
||||
//
|
||||
// This is useful in a case where sender clocks are not
|
||||
@@ -898,11 +917,11 @@ async fn add_parts(
|
||||
let save_mime_headers = context.get_config_bool(Config::SaveMimeHeaders).await?;
|
||||
|
||||
let mime_in_reply_to = mime_parser
|
||||
.get(HeaderDef::InReplyTo)
|
||||
.get_header(HeaderDef::InReplyTo)
|
||||
.cloned()
|
||||
.unwrap_or_default();
|
||||
let mime_references = mime_parser
|
||||
.get(HeaderDef::References)
|
||||
.get_header(HeaderDef::References)
|
||||
.cloned()
|
||||
.unwrap_or_default();
|
||||
|
||||
@@ -936,7 +955,6 @@ async fn add_parts(
|
||||
|
||||
let sent_timestamp = *sent_timestamp;
|
||||
let is_hidden = *hidden;
|
||||
let chat_id = chat_id;
|
||||
|
||||
let mut is_hidden = is_hidden;
|
||||
let mut ids = Vec::with_capacity(parts.len());
|
||||
@@ -979,7 +997,8 @@ INSERT INTO msgs
|
||||
}
|
||||
}
|
||||
|
||||
let mime_modified = save_mime_modified && !part.msg.is_empty();
|
||||
let part_is_empty = part.msg.is_empty() && part.param.get(Param::Quote).is_none();
|
||||
let mime_modified = save_mime_modified && !part_is_empty;
|
||||
if mime_modified {
|
||||
// Avoid setting mime_modified for more than one part.
|
||||
save_mime_modified = false;
|
||||
@@ -1201,7 +1220,7 @@ async fn lookup_chat_by_reply(
|
||||
parent: &Option<Message>,
|
||||
from_id: u32,
|
||||
to_ids: &ContactIds,
|
||||
) -> Result<(ChatId, Blocked)> {
|
||||
) -> Result<Option<(ChatId, Blocked)>> {
|
||||
// Try to assign message to the same chat as the parent message.
|
||||
|
||||
// If this was a private message just to self, it was probably a private reply.
|
||||
@@ -1215,7 +1234,7 @@ async fn lookup_chat_by_reply(
|
||||
// This message will be assigned to this chat, anyway.
|
||||
// But if we assigned it now, create_or_lookup_group() will not be called
|
||||
// and group commands will not be executed.
|
||||
return Ok((ChatId::new(0), Blocked::Not));
|
||||
return Ok(None);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1224,25 +1243,25 @@ async fn lookup_chat_by_reply(
|
||||
// (undecipherable group msgs often get assigned to the 1:1 chat with the sender).
|
||||
// We don't have any way of finding out whether a msg is undecipherable, so we check for
|
||||
// error.is_some() instead.
|
||||
return Ok((ChatId::new(0), Blocked::Not));
|
||||
return Ok(None);
|
||||
}
|
||||
|
||||
if parent_chat.id == DC_CHAT_ID_TRASH {
|
||||
return Ok((ChatId::new(0), Blocked::Not));
|
||||
return Ok(None);
|
||||
}
|
||||
|
||||
if is_probably_private_reply(context, to_ids, mime_parser, parent_chat.id, from_id).await? {
|
||||
return Ok((ChatId::new(0), Blocked::Not));
|
||||
return Ok(None);
|
||||
}
|
||||
|
||||
info!(
|
||||
context,
|
||||
"Assigning message to {} as it's a reply to {}", parent_chat.id, parent.rfc724_mid
|
||||
);
|
||||
return Ok((parent_chat.id, parent_chat.blocked));
|
||||
return Ok(Some((parent_chat.id, parent_chat.blocked)));
|
||||
}
|
||||
|
||||
Ok((ChatId::new(0), Blocked::Not))
|
||||
Ok(None)
|
||||
}
|
||||
|
||||
/// If this method returns true, the message shall be assigned to the 1:1 chat with the sender.
|
||||
@@ -1299,7 +1318,7 @@ async fn create_or_lookup_group(
|
||||
create_blocked: Blocked,
|
||||
from_id: u32,
|
||||
to_ids: &ContactIds,
|
||||
) -> Result<(ChatId, Blocked)> {
|
||||
) -> Result<Option<(ChatId, Blocked)>> {
|
||||
let mut chat_id_blocked = Blocked::Not;
|
||||
let mut recreate_member_list = false;
|
||||
let mut send_EVENT_CHAT_MODIFIED = false;
|
||||
@@ -1324,16 +1343,12 @@ async fn create_or_lookup_group(
|
||||
}
|
||||
if !allow_creation {
|
||||
info!(context, "creating ad-hoc group prevented from caller");
|
||||
return Ok((ChatId::new(0), Blocked::Not));
|
||||
return Ok(None);
|
||||
}
|
||||
|
||||
return create_adhoc_group(context, mime_parser, create_blocked, &member_ids)
|
||||
.await
|
||||
.map(|chat_id| {
|
||||
chat_id
|
||||
.map(|chat_id| (chat_id, create_blocked))
|
||||
.unwrap_or((ChatId::new(0), Blocked::Not))
|
||||
})
|
||||
.map(|chat_id| chat_id.map(|chat_id| (chat_id, create_blocked)))
|
||||
.map_err(|err| {
|
||||
info!(context, "could not create adhoc-group: {:?}", err);
|
||||
err
|
||||
@@ -1341,25 +1356,30 @@ async fn create_or_lookup_group(
|
||||
};
|
||||
|
||||
// check, if we have a chat with this group ID
|
||||
let (mut chat_id, _, _blocked) = chat::get_chat_id_by_grpid(context, &grpid)
|
||||
.await
|
||||
.unwrap_or((ChatId::new(0), false, Blocked::Not));
|
||||
let mut chat_id = chat::get_chat_id_by_grpid(context, &grpid)
|
||||
.await?
|
||||
.map(|(chat_id, _protected, _blocked)| chat_id);
|
||||
|
||||
// For chat messages, we don't have to guess (is_*probably*_private_reply()) but we know for sure that
|
||||
// they belong to the group because of the Chat-Group-Id or Message-Id header
|
||||
if !mime_parser.has_chat_version()
|
||||
&& is_probably_private_reply(context, to_ids, mime_parser, chat_id, from_id).await?
|
||||
{
|
||||
return Ok((ChatId::new(0), Blocked::Not));
|
||||
if let Some(chat_id) = chat_id {
|
||||
if !mime_parser.has_chat_version()
|
||||
&& is_probably_private_reply(context, to_ids, mime_parser, chat_id, from_id).await?
|
||||
{
|
||||
return Ok(None);
|
||||
}
|
||||
}
|
||||
|
||||
// now we have a grpid that is non-empty
|
||||
// but we might not know about this group
|
||||
|
||||
let grpname = mime_parser.get(HeaderDef::ChatGroupName).cloned();
|
||||
let grpname = mime_parser.get_header(HeaderDef::ChatGroupName).cloned();
|
||||
let mut removed_id = None;
|
||||
|
||||
if let Some(removed_addr) = mime_parser.get(HeaderDef::ChatGroupMemberRemoved).cloned() {
|
||||
if let Some(removed_addr) = mime_parser
|
||||
.get_header(HeaderDef::ChatGroupMemberRemoved)
|
||||
.cloned()
|
||||
{
|
||||
removed_id = Contact::lookup_id_by_addr(context, &removed_addr, Origin::Unknown).await?;
|
||||
match removed_id {
|
||||
Some(contact_id) => {
|
||||
@@ -1373,12 +1393,14 @@ async fn create_or_lookup_group(
|
||||
None => warn!(context, "removed {:?} has no contact_id", removed_addr),
|
||||
}
|
||||
} else {
|
||||
let field = mime_parser.get(HeaderDef::ChatGroupMemberAdded).cloned();
|
||||
let field = mime_parser
|
||||
.get_header(HeaderDef::ChatGroupMemberAdded)
|
||||
.cloned();
|
||||
if let Some(added_member) = field {
|
||||
mime_parser.is_system_message = SystemMessage::MemberAddedToGroup;
|
||||
better_msg = stock_str::msg_add_member(context, &added_member, from_id).await;
|
||||
X_MrAddToGrp = Some(added_member);
|
||||
} else if let Some(old_name) = mime_parser.get(HeaderDef::ChatGroupNameChanged) {
|
||||
} else if let Some(old_name) = mime_parser.get_header(HeaderDef::ChatGroupNameChanged) {
|
||||
X_MrGrpNameChanged = true;
|
||||
better_msg = stock_str::msg_grp_name(
|
||||
context,
|
||||
@@ -1392,7 +1414,7 @@ async fn create_or_lookup_group(
|
||||
)
|
||||
.await;
|
||||
mime_parser.is_system_message = SystemMessage::GroupNameChanged;
|
||||
} else if let Some(value) = mime_parser.get(HeaderDef::ChatContent) {
|
||||
} else if let Some(value) = mime_parser.get_header(HeaderDef::ChatContent) {
|
||||
if value == "group-avatar-changed" {
|
||||
if let Some(avatar_action) = &mime_parser.group_avatar {
|
||||
// this is just an explicit message containing the group-avatar,
|
||||
@@ -1421,7 +1443,7 @@ async fn create_or_lookup_group(
|
||||
.await?
|
||||
.unwrap_or_default();
|
||||
|
||||
if chat_id.is_unset()
|
||||
if chat_id.is_none()
|
||||
&& !mime_parser.is_mailinglist_message()
|
||||
&& !grpid.is_empty()
|
||||
&& grpname.is_some()
|
||||
@@ -1432,7 +1454,7 @@ async fn create_or_lookup_group(
|
||||
|| X_MrAddToGrp.is_some() && addr_cmp(&self_addr, X_MrAddToGrp.as_ref().unwrap()))
|
||||
{
|
||||
// group does not exist but should be created
|
||||
let create_protected = if mime_parser.get(HeaderDef::ChatVerified).is_some() {
|
||||
let create_protected = if mime_parser.get_header(HeaderDef::ChatVerified).is_some() {
|
||||
if let Err(err) = check_verified_properties(context, mime_parser, from_id, to_ids).await
|
||||
{
|
||||
warn!(context, "verification problem: {}", err);
|
||||
@@ -1446,7 +1468,7 @@ async fn create_or_lookup_group(
|
||||
|
||||
if !allow_creation {
|
||||
info!(context, "creating group forbidden by caller");
|
||||
return Ok((ChatId::new(0), Blocked::Not));
|
||||
return Ok(None);
|
||||
}
|
||||
|
||||
chat_id = create_multiuser_record(
|
||||
@@ -1458,6 +1480,7 @@ async fn create_or_lookup_group(
|
||||
create_protected,
|
||||
)
|
||||
.await
|
||||
.map(Some)
|
||||
.unwrap_or_else(|err| {
|
||||
warn!(
|
||||
context,
|
||||
@@ -1467,7 +1490,7 @@ async fn create_or_lookup_group(
|
||||
err,
|
||||
);
|
||||
|
||||
ChatId::new(0)
|
||||
None
|
||||
});
|
||||
|
||||
chat_id_blocked = create_blocked;
|
||||
@@ -1486,22 +1509,22 @@ async fn create_or_lookup_group(
|
||||
}
|
||||
|
||||
// again, check chat_id
|
||||
if chat_id.is_special() {
|
||||
if mime_parser.decrypting_failed {
|
||||
// It is possible that the message was sent to a valid,
|
||||
// yet unknown group, which was rejected because
|
||||
// Chat-Group-Name, which is in the encrypted part, was
|
||||
// not found. We can't create a properly named group in
|
||||
// this case, so assign error message to 1:1 chat with the
|
||||
// sender instead.
|
||||
return Ok((ChatId::new(0), Blocked::Not));
|
||||
} else {
|
||||
// The message was decrypted successfully, but contains a late "quit" or otherwise
|
||||
// unwanted message.
|
||||
info!(context, "message belongs to unwanted group (TRASH)");
|
||||
return Ok((DC_CHAT_ID_TRASH, chat_id_blocked));
|
||||
}
|
||||
}
|
||||
let chat_id = if let Some(chat_id) = chat_id {
|
||||
chat_id
|
||||
} else if mime_parser.decrypting_failed {
|
||||
// It is possible that the message was sent to a valid,
|
||||
// yet unknown group, which was rejected because
|
||||
// Chat-Group-Name, which is in the encrypted part, was
|
||||
// not found. We can't create a properly named group in
|
||||
// this case, so assign error message to 1:1 chat with the
|
||||
// sender instead.
|
||||
return Ok(None);
|
||||
} else {
|
||||
// The message was decrypted successfully, but contains a late "quit" or otherwise
|
||||
// unwanted message.
|
||||
info!(context, "message belongs to unwanted group (TRASH)");
|
||||
return Ok(Some((DC_CHAT_ID_TRASH, chat_id_blocked)));
|
||||
};
|
||||
|
||||
// We have a valid chat_id > DC_CHAT_ID_LAST_SPECIAL.
|
||||
|
||||
@@ -1585,7 +1608,7 @@ async fn create_or_lookup_group(
|
||||
if send_EVENT_CHAT_MODIFIED {
|
||||
context.emit_event(EventType::ChatModified(chat_id));
|
||||
}
|
||||
Ok((chat_id, chat_id_blocked))
|
||||
Ok(Some((chat_id, chat_id_blocked)))
|
||||
}
|
||||
|
||||
/// Create or lookup a mailing list chat.
|
||||
@@ -1603,7 +1626,7 @@ async fn create_or_lookup_mailinglist(
|
||||
allow_creation: bool,
|
||||
list_id_header: &str,
|
||||
mime_parser: &MimeMessage,
|
||||
) -> (ChatId, Blocked) {
|
||||
) -> Result<Option<(ChatId, Blocked)>> {
|
||||
static LIST_ID: Lazy<Regex> = Lazy::new(|| Regex::new(r"^(.+)<(.+)>$").unwrap());
|
||||
let (mut name, listid) = match LIST_ID.captures(list_id_header) {
|
||||
Some(cap) => (cap[1].trim().to_string(), cap[2].trim().to_string()),
|
||||
@@ -1617,8 +1640,8 @@ async fn create_or_lookup_mailinglist(
|
||||
),
|
||||
};
|
||||
|
||||
if let Ok((chat_id, _, blocked)) = chat::get_chat_id_by_grpid(context, &listid).await {
|
||||
return (chat_id, blocked);
|
||||
if let Some((chat_id, _, blocked)) = chat::get_chat_id_by_grpid(context, &listid).await? {
|
||||
return Ok(Some((chat_id, blocked)));
|
||||
}
|
||||
|
||||
// for mailchimp lists, the name in `ListId` is just a long number.
|
||||
@@ -1665,7 +1688,7 @@ async fn create_or_lookup_mailinglist(
|
||||
|
||||
if allow_creation {
|
||||
// list does not exist but should be created
|
||||
match create_multiuser_record(
|
||||
let chat_id = create_multiuser_record(
|
||||
context,
|
||||
Chattype::Mailinglist,
|
||||
&listid,
|
||||
@@ -1674,30 +1697,23 @@ async fn create_or_lookup_mailinglist(
|
||||
ProtectionStatus::Unprotected,
|
||||
)
|
||||
.await
|
||||
{
|
||||
Ok(chat_id) => {
|
||||
chat::add_to_chat_contacts_table(context, chat_id, DC_CONTACT_ID_SELF).await;
|
||||
(chat_id, Blocked::Request)
|
||||
}
|
||||
Err(e) => {
|
||||
warn!(
|
||||
context,
|
||||
"Failed to create mailinglist '{}' for grpid={}: {}",
|
||||
&name,
|
||||
&listid,
|
||||
e.to_string()
|
||||
);
|
||||
(ChatId::new(0), Blocked::Request)
|
||||
}
|
||||
}
|
||||
.map_err(|err| {
|
||||
err.context(format!(
|
||||
"Failed to create mailinglist '{}' for grpid={}",
|
||||
&name, &listid
|
||||
))
|
||||
})?;
|
||||
|
||||
chat::add_to_chat_contacts_table(context, chat_id, DC_CONTACT_ID_SELF).await;
|
||||
Ok(Some((chat_id, Blocked::Request)))
|
||||
} else {
|
||||
info!(context, "creating list forbidden by caller");
|
||||
(ChatId::new(0), Blocked::Not)
|
||||
Ok(None)
|
||||
}
|
||||
}
|
||||
|
||||
fn try_getting_grpid(mime_parser: &MimeMessage) -> Option<String> {
|
||||
if let Some(optional_field) = mime_parser.get(HeaderDef::ChatGroupId) {
|
||||
if let Some(optional_field) = mime_parser.get_header(HeaderDef::ChatGroupId) {
|
||||
return Some(optional_field.clone());
|
||||
}
|
||||
|
||||
@@ -1719,7 +1735,7 @@ fn try_getting_grpid(mime_parser: &MimeMessage) -> Option<String> {
|
||||
|
||||
/// try extract a grpid from a message-id list header value
|
||||
fn extract_grpid(mime_parser: &MimeMessage, headerdef: HeaderDef) -> Option<&str> {
|
||||
let header = mime_parser.get(headerdef)?;
|
||||
let header = mime_parser.get_header(headerdef)?;
|
||||
let parts = header
|
||||
.split(',')
|
||||
.map(str::trim)
|
||||
@@ -1884,7 +1900,7 @@ async fn check_verified_properties(
|
||||
|
||||
ensure!(mimeparser.was_encrypted(), "This message is not encrypted.");
|
||||
|
||||
if mimeparser.get(HeaderDef::ChatVerified).is_none() {
|
||||
if mimeparser.get_header(HeaderDef::ChatVerified).is_none() {
|
||||
// we do not fail here currently, this would exclude (a) non-deltas
|
||||
// and (b) deltas with different protection views across multiple devices.
|
||||
// for group creation or protection enabled/disabled, however, Chat-Verified is respected.
|
||||
@@ -2030,13 +2046,13 @@ async fn get_parent_message(
|
||||
context: &Context,
|
||||
mime_parser: &MimeMessage,
|
||||
) -> Result<Option<Message>> {
|
||||
if let Some(field) = mime_parser.get(HeaderDef::References) {
|
||||
if let Some(field) = mime_parser.get_header(HeaderDef::References) {
|
||||
if let Some(msg) = get_rfc724_mid_in_list(context, field).await? {
|
||||
return Ok(Some(msg));
|
||||
}
|
||||
}
|
||||
|
||||
if let Some(field) = mime_parser.get(HeaderDef::InReplyTo) {
|
||||
if let Some(field) = mime_parser.get_header(HeaderDef::InReplyTo) {
|
||||
if let Some(msg) = get_rfc724_mid_in_list(context, field).await? {
|
||||
return Ok(Some(msg));
|
||||
}
|
||||
@@ -2117,13 +2133,13 @@ fn dc_create_incoming_rfc724_mid(mime: &MimeMessage) -> String {
|
||||
"{}@stub",
|
||||
hex_hash(&format!(
|
||||
"{}-{}-{}",
|
||||
mime.get(HeaderDef::Date)
|
||||
mime.get_header(HeaderDef::Date)
|
||||
.map(|s| s.to_string())
|
||||
.unwrap_or_default(),
|
||||
mime.get(HeaderDef::From_)
|
||||
mime.get_header(HeaderDef::From_)
|
||||
.map(|s| s.to_string())
|
||||
.unwrap_or_default(),
|
||||
mime.get(HeaderDef::To)
|
||||
mime.get_header(HeaderDef::To)
|
||||
.map(|s| s.to_string())
|
||||
.unwrap_or_default()
|
||||
))
|
||||
@@ -4441,4 +4457,25 @@ Reply to all"#,
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[async_std::test]
|
||||
async fn test_gmx_forwarded_msg() -> Result<()> {
|
||||
let t = TestContext::new_alice().await;
|
||||
t.set_config(Config::ShowEmails, Some("2")).await?;
|
||||
|
||||
dc_receive_imf(
|
||||
&t,
|
||||
include_bytes!("../test-data/message/gmx-forward.eml"),
|
||||
"INBOX",
|
||||
1,
|
||||
false,
|
||||
)
|
||||
.await?;
|
||||
|
||||
let msg = t.get_last_msg().await;
|
||||
assert!(msg.has_html());
|
||||
assert_eq!(msg.id.get_html(&t).await?.unwrap().replace("\r\n", "\n"), "<html><head></head><body><div style=\"font-family: Verdana;font-size: 12.0px;\"><div> </div>\n\n<div> \n<div> \n<div data-darkreader-inline-border-left=\"\" name=\"quote\" style=\"margin: 10px 5px 5px 10px; padding: 10px 0px 10px 10px; border-left: 2px solid rgb(195, 217, 229); overflow-wrap: break-word; --darkreader-inline-border-left:#274759;\">\n<div style=\"margin:0 0 10px 0;\"><b>Gesendet:</b> Donnerstag, 12. August 2021 um 15:52 Uhr<br/>\n<b>Von:</b> "Claire" <claire@example.org><br/>\n<b>An:</b> alice@example.com<br/>\n<b>Betreff:</b> subject</div>\n\n<div name=\"quoted-content\">bodytext</div>\n</div>\n</div>\n</div></div></body></html>\n\n");
|
||||
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
@@ -17,7 +17,7 @@ use chrono::{Local, TimeZone};
|
||||
use rand::{thread_rng, Rng};
|
||||
|
||||
use crate::chat::{add_device_msg, add_device_msg_with_importance};
|
||||
use crate::constants::{Viewtype, DC_ELLIPSE, DC_OUTDATED_WARNING_DAYS};
|
||||
use crate::constants::{Viewtype, DC_ELLIPSIS, DC_OUTDATED_WARNING_DAYS};
|
||||
use crate::context::Context;
|
||||
use crate::events::EventType;
|
||||
use crate::message::Message;
|
||||
@@ -29,7 +29,7 @@ use crate::stock_str;
|
||||
#[allow(clippy::indexing_slicing)]
|
||||
pub(crate) fn dc_truncate(buf: &str, approx_chars: usize) -> Cow<str> {
|
||||
let count = buf.chars().count();
|
||||
if approx_chars > 0 && count > approx_chars + DC_ELLIPSE.len() {
|
||||
if count > approx_chars + DC_ELLIPSIS.len() {
|
||||
let end_pos = buf
|
||||
.char_indices()
|
||||
.nth(approx_chars)
|
||||
@@ -37,9 +37,9 @@ pub(crate) fn dc_truncate(buf: &str, approx_chars: usize) -> Cow<str> {
|
||||
.unwrap_or_default();
|
||||
|
||||
if let Some(index) = buf[..end_pos].rfind(|c| c == ' ' || c == '\n') {
|
||||
Cow::Owned(format!("{}{}", &buf[..=index], DC_ELLIPSE))
|
||||
Cow::Owned(format!("{}{}", &buf[..=index], DC_ELLIPSIS))
|
||||
} else {
|
||||
Cow::Owned(format!("{}{}", &buf[..end_pos], DC_ELLIPSE))
|
||||
Cow::Owned(format!("{}{}", &buf[..end_pos], DC_ELLIPSIS))
|
||||
}
|
||||
} else {
|
||||
Cow::Borrowed(buf)
|
||||
@@ -711,10 +711,7 @@ mod tests {
|
||||
assert_eq!(dc_truncate("\n hello \n world", 4), "\n [...]");
|
||||
|
||||
assert_eq!(dc_truncate("𐠈0Aᝮa𫝀®!ꫛa¡0A𐢧00𐹠®A 丽ⷐએ", 1), "𐠈[...]");
|
||||
assert_eq!(
|
||||
dc_truncate("𐠈0Aᝮa𫝀®!ꫛa¡0A𐢧00𐹠®A 丽ⷐએ", 0),
|
||||
"𐠈0Aᝮa𫝀®!ꫛa¡0A𐢧00𐹠®A 丽ⷐએ"
|
||||
);
|
||||
assert_eq!(dc_truncate("𐠈0Aᝮa𫝀®!ꫛa¡0A𐢧00𐹠®A 丽ⷐએ", 0), "[...]");
|
||||
|
||||
// 9 characters, so no truncation
|
||||
assert_eq!(dc_truncate("𑒀ὐ¢🜀\u{1e01b}A a🟠", 6), "𑒀ὐ¢🜀\u{1e01b}A a🟠",);
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
//! De-HTML
|
||||
//! De-HTML.
|
||||
//!
|
||||
//! A module to remove HTML tags from the email text
|
||||
|
||||
|
||||
@@ -178,7 +178,9 @@ pub async fn try_decrypt(
|
||||
let mut signatures = HashSet::default();
|
||||
|
||||
if let Some(ref mut peerstate) = peerstate {
|
||||
peerstate.handle_fingerprint_change(context).await?;
|
||||
peerstate
|
||||
.handle_fingerprint_change(context, message_time)
|
||||
.await?;
|
||||
if let Some(key) = &peerstate.public_key {
|
||||
public_keyring_for_validate.add(key.clone());
|
||||
} else if let Some(key) = &peerstate.gossip_key {
|
||||
@@ -333,7 +335,7 @@ fn has_decrypted_pgp_armor(input: &[u8]) -> bool {
|
||||
false
|
||||
}
|
||||
|
||||
/// Check if a MIME structure contains a multipart/report part.
|
||||
/// Checks if a MIME structure contains a multipart/report part.
|
||||
///
|
||||
/// As reports are often unencrypted, we do not reset the Autocrypt header in
|
||||
/// this case.
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
//! # Ephemeral messages
|
||||
//! # Ephemeral messages.
|
||||
//!
|
||||
//! Ephemeral messages are messages that have an Ephemeral-Timer
|
||||
//! header attached to them, which specifies time in seconds after
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
//! # Events specification
|
||||
//! # Events specification.
|
||||
|
||||
use std::ops::Deref;
|
||||
|
||||
|
||||
@@ -1,13 +1,13 @@
|
||||
///! # format=flowed support
|
||||
///!
|
||||
///! Format=flowed is defined in
|
||||
///! [RFC 3676](https://tools.ietf.org/html/rfc3676).
|
||||
///!
|
||||
///! Older [RFC 2646](https://tools.ietf.org/html/rfc2646) is used
|
||||
///! during formatting, i.e., DelSp parameter introduced in RFC 3676
|
||||
///! is assumed to be set to "no".
|
||||
///!
|
||||
///! For received messages, DelSp parameter is honoured.
|
||||
//! # format=flowed support.
|
||||
//!
|
||||
//! Format=flowed is defined in
|
||||
//! [RFC 3676](https://tools.ietf.org/html/rfc3676).
|
||||
//!
|
||||
//! Older [RFC 2646](https://tools.ietf.org/html/rfc2646) is used
|
||||
//! during formatting, i.e., DelSp parameter introduced in RFC 3676
|
||||
//! is assumed to be set to "no".
|
||||
//!
|
||||
//! For received messages, DelSp parameter is honoured.
|
||||
|
||||
/// Wraps line to 72 characters using format=flowed soft breaks.
|
||||
///
|
||||
|
||||
@@ -1,3 +1,5 @@
|
||||
//! # List of email headers.
|
||||
|
||||
use crate::strum::AsStaticRef;
|
||||
use mailparse::{MailHeader, MailHeaderMap};
|
||||
|
||||
|
||||
17
src/html.rs
17
src/html.rs
@@ -1,11 +1,12 @@
|
||||
///! # Get message as HTML.
|
||||
///!
|
||||
///! Use `Message.has_html()` to check if the UI shall render a
|
||||
///! corresponding button and `MsgId.get_html()` to get the full message.
|
||||
///!
|
||||
///! Even when the original mime-message is not HTML,
|
||||
///! `MsgId.get_html()` will return HTML -
|
||||
///! this allows nice quoting, handling linebreaks properly etc.
|
||||
//! # Get message as HTML.
|
||||
//!
|
||||
//! Use `Message.has_html()` to check if the UI shall render a
|
||||
//! corresponding button and `MsgId.get_html()` to get the full message.
|
||||
//!
|
||||
//! Even when the original mime-message is not HTML,
|
||||
//! `MsgId.get_html()` will return HTML -
|
||||
//! this allows nice quoting, handling linebreaks properly etc.
|
||||
|
||||
use futures::future::FutureExt;
|
||||
use std::future::Future;
|
||||
use std::pin::Pin;
|
||||
|
||||
65
src/imap.rs
65
src/imap.rs
@@ -1,14 +1,14 @@
|
||||
//! # Imap handling module
|
||||
//! # IMAP handling module.
|
||||
//!
|
||||
//! uses [async-email/async-imap](https://github.com/async-email/async-imap)
|
||||
//! to implement connect, fetch, delete functionality with standard IMAP servers.
|
||||
|
||||
use std::{cmp, cmp::max, collections::BTreeMap};
|
||||
|
||||
use anyhow::{bail, format_err, Context as _, Result};
|
||||
use anyhow::{anyhow, bail, format_err, Context as _, Result};
|
||||
use async_imap::{
|
||||
error::Result as ImapResult,
|
||||
types::{Fetch, Flag, Mailbox, Name, NameAttribute, UnsolicitedResponse},
|
||||
types::{Fetch, Flag, Mailbox, Name, NameAttribute, Quota, QuotaRoot, UnsolicitedResponse},
|
||||
};
|
||||
use async_std::channel::Receiver;
|
||||
use async_std::prelude::*;
|
||||
@@ -27,6 +27,7 @@ use crate::events::EventType;
|
||||
use crate::headerdef::{HeaderDef, HeaderDefMap};
|
||||
use crate::job::{self, Action};
|
||||
use crate::login_param::{CertificateChecks, LoginParam, ServerLoginParam};
|
||||
use crate::login_param::{ServerAddress, Socks5Config};
|
||||
use crate::message::{self, update_server_uid, MessageState};
|
||||
use crate::mimeparser;
|
||||
use crate::oauth2::dc_get_oauth2_access_token;
|
||||
@@ -142,6 +143,7 @@ impl FolderMeaning {
|
||||
struct ImapConfig {
|
||||
pub addr: String,
|
||||
pub lp: ServerLoginParam,
|
||||
pub socks5_config: Option<Socks5Config>,
|
||||
pub strict_tls: bool,
|
||||
pub oauth2: bool,
|
||||
pub selected_folder: Option<String>,
|
||||
@@ -153,6 +155,10 @@ struct ImapConfig {
|
||||
/// True if the server has MOVE capability as defined in
|
||||
/// <https://tools.ietf.org/html/rfc6851>
|
||||
pub can_move: bool,
|
||||
|
||||
/// True if the server has QUOTA capability as defined in
|
||||
/// <https://tools.ietf.org/html/rfc2087>
|
||||
pub can_check_quota: bool,
|
||||
}
|
||||
|
||||
impl Imap {
|
||||
@@ -161,6 +167,7 @@ impl Imap {
|
||||
/// `addr` is used to renew token if OAuth2 authentication is used.
|
||||
pub async fn new(
|
||||
lp: &ServerLoginParam,
|
||||
socks5_config: Option<Socks5Config>,
|
||||
addr: &str,
|
||||
oauth2: bool,
|
||||
provider_strict_tls: bool,
|
||||
@@ -179,6 +186,7 @@ impl Imap {
|
||||
let config = ImapConfig {
|
||||
addr: addr.to_string(),
|
||||
lp: lp.clone(),
|
||||
socks5_config,
|
||||
strict_tls,
|
||||
oauth2,
|
||||
selected_folder: None,
|
||||
@@ -186,6 +194,7 @@ impl Imap {
|
||||
selected_folder_needs_expunge: false,
|
||||
can_idle: false,
|
||||
can_move: false,
|
||||
can_check_quota: false,
|
||||
};
|
||||
|
||||
let imap = Imap {
|
||||
@@ -217,6 +226,7 @@ impl Imap {
|
||||
|
||||
let imap = Self::new(
|
||||
¶m.imap,
|
||||
param.socks5_config.clone(),
|
||||
¶m.addr,
|
||||
param.server_flags & DC_LP_AUTH_OAUTH2 != 0,
|
||||
param.provider.map_or(false, |provider| provider.strict_tls),
|
||||
@@ -256,7 +266,20 @@ impl Imap {
|
||||
let imap_server: &str = config.lp.server.as_ref();
|
||||
let imap_port = config.lp.port;
|
||||
|
||||
match Client::connect_insecure((imap_server, imap_port)).await {
|
||||
let connection = if let Some(socks5_config) = &config.socks5_config {
|
||||
Client::connect_insecure_socks5(
|
||||
&ServerAddress {
|
||||
host: imap_server.to_string(),
|
||||
port: imap_port,
|
||||
},
|
||||
socks5_config.clone(),
|
||||
)
|
||||
.await
|
||||
} else {
|
||||
Client::connect_insecure((imap_server, imap_port)).await
|
||||
};
|
||||
|
||||
match connection {
|
||||
Ok(client) => {
|
||||
if config.lp.security == Socket::Starttls {
|
||||
client.secure(imap_server, config.strict_tls).await
|
||||
@@ -271,7 +294,20 @@ impl Imap {
|
||||
let imap_server: &str = config.lp.server.as_ref();
|
||||
let imap_port = config.lp.port;
|
||||
|
||||
Client::connect_secure((imap_server, imap_port), imap_server, config.strict_tls).await
|
||||
if let Some(socks5_config) = &config.socks5_config {
|
||||
Client::connect_secure_socks5(
|
||||
&ServerAddress {
|
||||
host: imap_server.to_string(),
|
||||
port: imap_port,
|
||||
},
|
||||
config.strict_tls,
|
||||
socks5_config.clone(),
|
||||
)
|
||||
.await
|
||||
} else {
|
||||
Client::connect_secure((imap_server, imap_port), imap_server, config.strict_tls)
|
||||
.await
|
||||
}
|
||||
};
|
||||
|
||||
let login_res = match connection_res {
|
||||
@@ -362,6 +398,7 @@ impl Imap {
|
||||
Ok(caps) => {
|
||||
self.config.can_idle = caps.has_str("IDLE");
|
||||
self.config.can_move = caps.has_str("MOVE");
|
||||
self.config.can_check_quota = caps.has_str("QUOTA");
|
||||
self.capabilities_determined = true;
|
||||
Ok(())
|
||||
}
|
||||
@@ -1392,6 +1429,22 @@ impl Imap {
|
||||
}
|
||||
unsolicited_exists
|
||||
}
|
||||
|
||||
pub fn can_check_quota(&self) -> bool {
|
||||
self.config.can_check_quota
|
||||
}
|
||||
|
||||
pub async fn get_quota_roots(
|
||||
&mut self,
|
||||
mailbox_name: &str,
|
||||
) -> Result<(Vec<QuotaRoot>, Vec<Quota>)> {
|
||||
if let Some(session) = self.session.as_mut() {
|
||||
let quota_roots = session.get_quota_root(mailbox_name).await?;
|
||||
Ok(quota_roots)
|
||||
} else {
|
||||
Err(anyhow!("Not connected to IMAP, no session"))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Try to get the folder meaning by the name of the folder only used if the server does not support XLIST.
|
||||
@@ -1636,7 +1689,7 @@ pub(crate) async fn prefetch_should_download(
|
||||
// deleted from the database or has not arrived yet.
|
||||
if let Some(rfc724_mid) = headers.get_header_value(HeaderDef::MessageId) {
|
||||
if let Some(group_id) = dc_extract_grpid_from_rfc724_mid(&rfc724_mid) {
|
||||
if let Ok((_chat_id, _, _)) = get_chat_id_by_grpid(context, group_id).await {
|
||||
if get_chat_id_by_grpid(context, group_id).await?.is_some() {
|
||||
return Ok(true);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,16 +1,24 @@
|
||||
use std::ops::{Deref, DerefMut};
|
||||
use std::{
|
||||
ops::{Deref, DerefMut},
|
||||
time::Duration,
|
||||
};
|
||||
|
||||
use async_imap::{
|
||||
error::{Error as ImapError, Result as ImapResult},
|
||||
Client as ImapClient,
|
||||
};
|
||||
|
||||
use async_smtp::ServerAddress;
|
||||
use async_std::net::{self, TcpStream};
|
||||
|
||||
use super::session::Session;
|
||||
use crate::login_param::dc_build_tls;
|
||||
use crate::login_param::{dc_build_tls, Socks5Config};
|
||||
|
||||
use super::session::SessionStream;
|
||||
|
||||
/// IMAP write and read timeout in seconds.
|
||||
const IMAP_TIMEOUT: u64 = 30;
|
||||
|
||||
#[derive(Debug)]
|
||||
pub(crate) struct Client {
|
||||
is_secure: bool,
|
||||
@@ -111,6 +119,63 @@ impl Client {
|
||||
})
|
||||
}
|
||||
|
||||
pub async fn connect_secure_socks5(
|
||||
target_addr: &ServerAddress,
|
||||
strict_tls: bool,
|
||||
socks5_config: Socks5Config,
|
||||
) -> ImapResult<Self> {
|
||||
let socks5_stream: Box<dyn SessionStream> = Box::new(
|
||||
match socks5_config
|
||||
.connect(target_addr, Some(Duration::from_secs(IMAP_TIMEOUT)))
|
||||
.await
|
||||
{
|
||||
Ok(s) => s,
|
||||
Err(e) => return ImapResult::Err(async_imap::error::Error::Bad(e.to_string())),
|
||||
},
|
||||
);
|
||||
|
||||
let tls = dc_build_tls(strict_tls);
|
||||
let tls_stream: Box<dyn SessionStream> =
|
||||
Box::new(tls.connect(target_addr.host.clone(), socks5_stream).await?);
|
||||
let mut client = ImapClient::new(tls_stream);
|
||||
|
||||
let _greeting = client
|
||||
.read_response()
|
||||
.await
|
||||
.ok_or_else(|| ImapError::Bad("failed to read greeting".to_string()))?;
|
||||
|
||||
Ok(Client {
|
||||
is_secure: true,
|
||||
inner: client,
|
||||
})
|
||||
}
|
||||
|
||||
pub async fn connect_insecure_socks5(
|
||||
target_addr: &ServerAddress,
|
||||
socks5_config: Socks5Config,
|
||||
) -> ImapResult<Self> {
|
||||
let socks5_stream: Box<dyn SessionStream> = Box::new(
|
||||
match socks5_config
|
||||
.connect(target_addr, Some(Duration::from_secs(IMAP_TIMEOUT)))
|
||||
.await
|
||||
{
|
||||
Ok(s) => s,
|
||||
Err(e) => return ImapResult::Err(async_imap::error::Error::Bad(e.to_string())),
|
||||
},
|
||||
);
|
||||
|
||||
let mut client = ImapClient::new(socks5_stream);
|
||||
let _greeting = client
|
||||
.read_response()
|
||||
.await
|
||||
.ok_or_else(|| ImapError::Bad("failed to read greeting".to_string()))?;
|
||||
|
||||
Ok(Client {
|
||||
is_secure: false,
|
||||
inner: client,
|
||||
})
|
||||
}
|
||||
|
||||
pub async fn secure(self, domain: &str, strict_tls: bool) -> ImapResult<Client> {
|
||||
if self.is_secure {
|
||||
Ok(self)
|
||||
|
||||
@@ -42,6 +42,15 @@ impl Imap {
|
||||
}
|
||||
};
|
||||
|
||||
// Gmail labels are not folders and should be skipped. For example,
|
||||
// emails appear in the inbox and under "All Mail" as soon as it is
|
||||
// received. The code used to wrongly conclude that the email had
|
||||
// already been moved and left it in the inbox.
|
||||
let folder_name = folder.name();
|
||||
if folder_name.starts_with("[Gmail]") {
|
||||
continue;
|
||||
}
|
||||
|
||||
let folder_meaning = get_folder_meaning(&folder);
|
||||
let folder_name_meaning = get_folder_meaning_by_name(folder.name());
|
||||
|
||||
@@ -93,7 +102,7 @@ impl Imap {
|
||||
}
|
||||
}
|
||||
|
||||
async fn get_watched_folders(context: &Context) -> Vec<String> {
|
||||
pub(crate) async fn get_watched_folders(context: &Context) -> Vec<String> {
|
||||
let mut res = Vec::new();
|
||||
let folder_watched_configured = &[
|
||||
(Config::SentboxWatch, Config::ConfiguredSentboxFolder),
|
||||
|
||||
@@ -3,6 +3,7 @@ use std::ops::{Deref, DerefMut};
|
||||
use async_imap::Session as ImapSession;
|
||||
use async_native_tls::TlsStream;
|
||||
use async_std::net::TcpStream;
|
||||
use fast_socks5::client::Socks5Stream;
|
||||
|
||||
#[derive(Debug)]
|
||||
pub(crate) struct Session {
|
||||
@@ -17,6 +18,7 @@ pub(crate) trait SessionStream:
|
||||
impl SessionStream for TlsStream<Box<dyn SessionStream>> {}
|
||||
impl SessionStream for TlsStream<TcpStream> {}
|
||||
impl SessionStream for TcpStream {}
|
||||
impl SessionStream for Socks5Stream<TcpStream> {}
|
||||
|
||||
impl Deref for Session {
|
||||
type Target = ImapSession<Box<dyn SessionStream>>;
|
||||
|
||||
11
src/imex.rs
11
src/imex.rs
@@ -1,4 +1,4 @@
|
||||
//! # Import/export module
|
||||
//! # Import/export module.
|
||||
|
||||
use std::any::Any;
|
||||
use std::ffi::OsStr;
|
||||
@@ -66,15 +66,15 @@ pub enum ImexMode {
|
||||
|
||||
/// Import/export things.
|
||||
///
|
||||
/// What to do is defined by the *what* parameter.
|
||||
/// What to do is defined by the `what` parameter.
|
||||
///
|
||||
/// During execution of the job,
|
||||
/// some events are sent out:
|
||||
///
|
||||
/// - A number of #DC_EVENT_IMEX_PROGRESS events are sent and may be used to create
|
||||
/// - A number of `DC_EVENT_IMEX_PROGRESS` events are sent and may be used to create
|
||||
/// a progress bar or stuff like that. Moreover, you'll be informed when the imex-job is done.
|
||||
///
|
||||
/// - For each file written on export, the function sends #DC_EVENT_IMEX_FILE_WRITTEN
|
||||
/// - For each file written on export, the function sends `DC_EVENT_IMEX_FILE_WRITTEN`
|
||||
///
|
||||
/// Only one import-/export-progress can run at the same time.
|
||||
/// To cancel an import-/export-progress, drop the future returned by this function.
|
||||
@@ -204,6 +204,7 @@ pub async fn has_backup_old(context: &Context, dir_name: &Path) -> Result<String
|
||||
}
|
||||
}
|
||||
|
||||
/// Initiates key transfer via Autocrypt Setup Message.
|
||||
pub async fn initiate_key_transfer(context: &Context) -> Result<String> {
|
||||
use futures::future::FutureExt;
|
||||
|
||||
@@ -435,7 +436,7 @@ async fn decrypt_setup_file<T: std::io::Read + std::io::Seek>(
|
||||
Ok(plain_text)
|
||||
}
|
||||
|
||||
pub fn normalize_setup_code(s: &str) -> String {
|
||||
fn normalize_setup_code(s: &str) -> String {
|
||||
let mut out = String::new();
|
||||
for c in s.chars() {
|
||||
if ('0'..='9').contains(&c) {
|
||||
|
||||
24
src/job.rs
24
src/job.rs
@@ -1,4 +1,4 @@
|
||||
//! # Job module
|
||||
//! # Job module.
|
||||
//!
|
||||
//! This module implements a job queue maintained in the SQLite database
|
||||
//! and job types.
|
||||
@@ -95,6 +95,9 @@ pub enum Action {
|
||||
FetchExistingMsgs = 110,
|
||||
MarkseenMsgOnImap = 130,
|
||||
|
||||
// this is user initiated so it should have a fairly high priority
|
||||
UpdateRecentQuota = 140,
|
||||
|
||||
// Moving message is prioritized lower than deletion so we don't
|
||||
// bother moving message if it is already scheduled for deletion.
|
||||
MoveMsg = 200,
|
||||
@@ -130,6 +133,7 @@ impl From<Action> for Thread {
|
||||
ResyncFolders => Thread::Imap,
|
||||
MarkseenMsgOnImap => Thread::Imap,
|
||||
MoveMsg => Thread::Imap,
|
||||
UpdateRecentQuota => Thread::Imap,
|
||||
|
||||
MaybeSendLocations => Thread::Smtp,
|
||||
MaybeSendLocationsEnded => Thread::Smtp,
|
||||
@@ -148,7 +152,6 @@ pub struct Job {
|
||||
pub added_timestamp: i64,
|
||||
pub tries: u32,
|
||||
pub param: Params,
|
||||
pub pending_error: Option<String>,
|
||||
}
|
||||
|
||||
impl fmt::Display for Job {
|
||||
@@ -169,7 +172,6 @@ impl Job {
|
||||
added_timestamp: timestamp,
|
||||
tries: 0,
|
||||
param,
|
||||
pending_error: None,
|
||||
}
|
||||
}
|
||||
|
||||
@@ -251,12 +253,13 @@ impl Job {
|
||||
|
||||
smtp.connectivity.set_working(context).await;
|
||||
|
||||
let status = match smtp.send(context, recipients, message, job_id).await {
|
||||
let send_result = smtp.send(context, recipients, message, job_id).await;
|
||||
smtp.last_send_error = send_result.as_ref().err().map(|e| e.to_string());
|
||||
|
||||
let status = match send_result {
|
||||
Err(crate::smtp::send::Error::SmtpSend(err)) => {
|
||||
// Remote error, retry later.
|
||||
warn!(context, "SMTP failed to send: {:?}", &err);
|
||||
smtp.connectivity.set_err(context, &err).await;
|
||||
self.pending_error = Some(err.to_string());
|
||||
|
||||
let res = match err {
|
||||
async_smtp::smtp::error::Error::Permanent(ref response) => {
|
||||
@@ -365,6 +368,7 @@ impl Job {
|
||||
// SMTP server, if not yet done
|
||||
if let Err(err) = smtp.connect_configured(context).await {
|
||||
warn!(context, "SMTP connection failure: {:?}", err);
|
||||
smtp.last_send_error = Some(format!("SMTP connection failure: {:#}", err));
|
||||
return Status::RetryLater;
|
||||
}
|
||||
|
||||
@@ -407,6 +411,8 @@ impl Job {
|
||||
}
|
||||
Err(err) => {
|
||||
warn!(context, "failed to check message existence: {:?}", err);
|
||||
smtp.last_send_error =
|
||||
Some(format!("failed to check message existence: {:#}", err));
|
||||
return Status::RetryLater;
|
||||
}
|
||||
}
|
||||
@@ -521,6 +527,7 @@ impl Job {
|
||||
// connect to SMTP server, if not yet done
|
||||
if let Err(err) = smtp.connect_configured(context).await {
|
||||
warn!(context, "SMTP connection failure: {:?}", err);
|
||||
smtp.last_send_error = Some(err.to_string());
|
||||
return Status::RetryLater;
|
||||
}
|
||||
|
||||
@@ -1145,6 +1152,7 @@ async fn perform_job_action(
|
||||
sql::housekeeping(context).await.ok_or_log(context);
|
||||
Status::Finished(Ok(()))
|
||||
}
|
||||
Action::UpdateRecentQuota => context.update_recent_quota(connection.inbox()).await,
|
||||
};
|
||||
|
||||
info!(context, "Finished immediate try {} of job {}", tries, job);
|
||||
@@ -1207,7 +1215,8 @@ pub async fn add(context: &Context, job: Job) {
|
||||
| Action::ResyncFolders
|
||||
| Action::MarkseenMsgOnImap
|
||||
| Action::FetchExistingMsgs
|
||||
| Action::MoveMsg => {
|
||||
| Action::MoveMsg
|
||||
| Action::UpdateRecentQuota => {
|
||||
info!(context, "interrupt: imap");
|
||||
context
|
||||
.interrupt_inbox(InterruptInfo::new(false, None))
|
||||
@@ -1321,7 +1330,6 @@ LIMIT 1;
|
||||
added_timestamp: row.get("added_timestamp")?,
|
||||
tries: row.get("tries")?,
|
||||
param: row.get::<_, String>("param")?.parse().unwrap_or_default(),
|
||||
pending_error: None,
|
||||
};
|
||||
|
||||
Ok(job)
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
//! Cryptographic key module
|
||||
//! Cryptographic key module.
|
||||
|
||||
use std::collections::BTreeMap;
|
||||
use std::fmt;
|
||||
|
||||
11
src/lib.rs
11
src/lib.rs
@@ -1,3 +1,5 @@
|
||||
//! # Delta Chat Core Library.
|
||||
|
||||
#![forbid(unsafe_code)]
|
||||
#![deny(
|
||||
clippy::correctness,
|
||||
@@ -7,7 +9,11 @@
|
||||
clippy::wildcard_imports,
|
||||
clippy::needless_borrow
|
||||
)]
|
||||
#![allow(clippy::match_bool, clippy::eval_order_dependence)]
|
||||
#![allow(
|
||||
clippy::match_bool,
|
||||
clippy::eval_order_dependence,
|
||||
clippy::bool_assert_comparison
|
||||
)]
|
||||
|
||||
#[macro_use]
|
||||
extern crate num_derive;
|
||||
@@ -55,7 +61,7 @@ mod imap;
|
||||
pub mod imex;
|
||||
mod scheduler;
|
||||
#[macro_use]
|
||||
pub mod job;
|
||||
mod job;
|
||||
mod format_flowed;
|
||||
pub mod key;
|
||||
mod keyring;
|
||||
@@ -71,6 +77,7 @@ pub mod peerstate;
|
||||
pub mod pgp;
|
||||
pub mod provider;
|
||||
pub mod qr;
|
||||
pub mod quota;
|
||||
pub mod securejoin;
|
||||
mod simplify;
|
||||
mod smtp;
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
//! Location handling
|
||||
//! Location handling.
|
||||
use std::convert::TryFrom;
|
||||
|
||||
use anyhow::{ensure, Error};
|
||||
@@ -190,7 +190,7 @@ impl Kml {
|
||||
}
|
||||
}
|
||||
|
||||
// location streaming
|
||||
/// Enables location streaming in chat identified by `chat_id` for `seconds` seconds.
|
||||
pub async fn send_locations_to_chat(context: &Context, chat_id: ChatId, seconds: i64) {
|
||||
let now = time();
|
||||
if !(seconds < 0 || chat_id.is_special()) {
|
||||
@@ -221,7 +221,7 @@ pub async fn send_locations_to_chat(context: &Context, chat_id: ChatId, seconds:
|
||||
.unwrap_or_default();
|
||||
} else if 0 == seconds && is_sending_locations_before {
|
||||
let stock_str = stock_str::msg_location_disabled(context).await;
|
||||
chat::add_info_msg(context, chat_id, stock_str).await;
|
||||
chat::add_info_msg(context, chat_id, stock_str, now).await;
|
||||
}
|
||||
context.emit_event(EventType::ChatModified(chat_id));
|
||||
if 0 != seconds {
|
||||
@@ -716,7 +716,8 @@ pub(crate) async fn job_maybe_send_locations_ended(
|
||||
.await
|
||||
);
|
||||
|
||||
if !(send_begin != 0 && time() <= send_until) {
|
||||
let now = time();
|
||||
if !(send_begin != 0 && now <= send_until) {
|
||||
// still streaming -
|
||||
// may happen as several calls to dc_send_locations_to_chat()
|
||||
// do not un-schedule pending DC_MAYBE_SEND_LOC_ENDED jobs
|
||||
@@ -735,7 +736,7 @@ pub(crate) async fn job_maybe_send_locations_ended(
|
||||
);
|
||||
|
||||
let stock_str = stock_str::msg_location_disabled(context).await;
|
||||
chat::add_info_msg(context, chat_id, stock_str).await;
|
||||
chat::add_info_msg(context, chat_id, stock_str, now).await;
|
||||
context.emit_event(EventType::ChatModified(chat_id));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
//! # Logging
|
||||
//! # Logging.
|
||||
|
||||
use crate::context::Context;
|
||||
|
||||
#[macro_export]
|
||||
|
||||
@@ -1,12 +1,19 @@
|
||||
//! # Login parameters
|
||||
//! # Login parameters.
|
||||
|
||||
use std::borrow::Cow;
|
||||
use std::fmt;
|
||||
use std::time::Duration;
|
||||
|
||||
use crate::provider::{get_provider_by_id, Provider};
|
||||
use crate::{context::Context, provider::Socket};
|
||||
use anyhow::Result;
|
||||
|
||||
use async_std::io;
|
||||
use async_std::net::TcpStream;
|
||||
|
||||
pub use async_smtp::ServerAddress;
|
||||
use fast_socks5::client::Socks5Stream;
|
||||
|
||||
#[derive(Copy, Clone, Debug, Display, FromPrimitive, PartialEq, Eq)]
|
||||
#[repr(u32)]
|
||||
#[strum(serialize_all = "snake_case")]
|
||||
@@ -44,6 +51,81 @@ pub struct ServerLoginParam {
|
||||
pub certificate_checks: CertificateChecks,
|
||||
}
|
||||
|
||||
#[derive(Default, Debug, Clone, PartialEq)]
|
||||
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(context: &Context) -> Result<Option<Self>> {
|
||||
let sql = &context.sql;
|
||||
|
||||
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)
|
||||
}
|
||||
}
|
||||
|
||||
pub async fn connect(
|
||||
&self,
|
||||
target_addr: &ServerAddress,
|
||||
timeout: Option<Duration>,
|
||||
) -> io::Result<Socks5Stream<TcpStream>> {
|
||||
self.to_async_smtp_socks5_config()
|
||||
.connect(target_addr, timeout)
|
||||
.await
|
||||
}
|
||||
|
||||
pub fn to_async_smtp_socks5_config(&self) -> async_smtp::smtp::Socks5Config {
|
||||
async_smtp::smtp::Socks5Config {
|
||||
host: self.host.clone(),
|
||||
port: self.port,
|
||||
user_password: self.user_password.clone(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
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()
|
||||
}
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Default, Debug, Clone, PartialEq)]
|
||||
pub struct LoginParam {
|
||||
pub addr: String,
|
||||
@@ -51,6 +133,7 @@ pub struct LoginParam {
|
||||
pub smtp: ServerLoginParam,
|
||||
pub server_flags: i32,
|
||||
pub provider: Option<&'static Provider>,
|
||||
pub socks5_config: Option<Socks5Config>,
|
||||
}
|
||||
|
||||
impl LoginParam {
|
||||
@@ -130,6 +213,8 @@ impl LoginParam {
|
||||
.await?
|
||||
.and_then(|provider_id| get_provider_by_id(&provider_id));
|
||||
|
||||
let socks5_config = Socks5Config::from_database(context).await?;
|
||||
|
||||
Ok(LoginParam {
|
||||
addr,
|
||||
imap: ServerLoginParam {
|
||||
@@ -150,6 +235,7 @@ impl LoginParam {
|
||||
},
|
||||
provider,
|
||||
server_flags,
|
||||
socks5_config,
|
||||
})
|
||||
}
|
||||
|
||||
@@ -334,6 +420,8 @@ mod tests {
|
||||
},
|
||||
server_flags: 0,
|
||||
provider: get_provider_by_id("example.com"),
|
||||
// socks5_config is not saved by `save_to_database`, using default value
|
||||
socks5_config: None,
|
||||
};
|
||||
|
||||
param.save_to_database(&t, "foobar_").await?;
|
||||
|
||||
@@ -1,3 +1,5 @@
|
||||
//! # Legacy generic return values for C API.
|
||||
|
||||
use deltachat_derive::{FromSql, ToSql};
|
||||
|
||||
use crate::key::Fingerprint;
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
//! # Messages and their identifiers
|
||||
//! # Messages and their identifiers.
|
||||
|
||||
use std::collections::BTreeMap;
|
||||
use std::convert::TryInto;
|
||||
@@ -14,13 +14,13 @@ use crate::chat::{self, Chat, ChatId};
|
||||
use crate::config::Config;
|
||||
use crate::constants::{
|
||||
Blocked, Chattype, VideochatType, Viewtype, DC_CHAT_ID_TRASH, DC_CONTACT_ID_INFO,
|
||||
DC_CONTACT_ID_SELF, DC_MAX_GET_INFO_LEN, DC_MAX_GET_TEXT_LEN, DC_MSG_ID_LAST_SPECIAL,
|
||||
DC_CONTACT_ID_SELF, DC_DESIRED_TEXT_LEN, DC_MSG_ID_LAST_SPECIAL,
|
||||
};
|
||||
use crate::contact::{Contact, Origin};
|
||||
use crate::context::Context;
|
||||
use crate::dc_tools::{
|
||||
dc_get_filebytes, dc_get_filemeta, dc_gm2local_offset, dc_read_file, dc_timestamp_to_str,
|
||||
dc_truncate, time,
|
||||
dc_create_smeared_timestamp, dc_get_filebytes, dc_get_filemeta, dc_gm2local_offset,
|
||||
dc_read_file, dc_timestamp_to_str, dc_truncate, time,
|
||||
};
|
||||
use crate::ephemeral::Timer as EphemeralTimer;
|
||||
use crate::events::EventType;
|
||||
@@ -541,9 +541,7 @@ impl Message {
|
||||
}
|
||||
|
||||
pub fn get_text(&self) -> Option<String> {
|
||||
self.text
|
||||
.as_ref()
|
||||
.map(|text| dc_truncate(text, DC_MAX_GET_TEXT_LEN).to_string())
|
||||
self.text.as_ref().map(|s| s.to_string())
|
||||
}
|
||||
|
||||
pub fn get_subject(&self) -> &str {
|
||||
@@ -1142,7 +1140,7 @@ pub async fn get_msg_info(context: &Context, msg_id: MsgId) -> Result<String> {
|
||||
return Ok(ret);
|
||||
}
|
||||
let rawtxt = rawtxt.unwrap_or_default();
|
||||
let rawtxt = dc_truncate(rawtxt.trim(), DC_MAX_GET_INFO_LEN);
|
||||
let rawtxt = dc_truncate(rawtxt.trim(), DC_DESIRED_TEXT_LEN);
|
||||
|
||||
let fts = dc_timestamp_to_str(msg.get_timestamp());
|
||||
ret += &format!("Sent: {}", fts);
|
||||
@@ -1805,7 +1803,13 @@ async fn ndn_maybe_add_info_msg(
|
||||
// Tell the user which of the recipients failed if we know that (because in
|
||||
// a group, this might otherwise be unclear)
|
||||
let text = stock_str::failed_sending_to(context, contact.get_display_name()).await;
|
||||
chat::add_info_msg(context, chat_id, text).await;
|
||||
chat::add_info_msg(
|
||||
context,
|
||||
chat_id,
|
||||
text,
|
||||
dc_create_smeared_timestamp(context).await,
|
||||
)
|
||||
.await;
|
||||
context.emit_event(EventType::ChatModified(chat_id));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,3 +1,5 @@
|
||||
//! # MIME message production.
|
||||
|
||||
use std::convert::TryInto;
|
||||
|
||||
use anyhow::{bail, ensure, format_err, Result};
|
||||
|
||||
@@ -1,3 +1,5 @@
|
||||
//! # MIME message parsing module.
|
||||
|
||||
use std::collections::{HashMap, HashSet};
|
||||
use std::future::Future;
|
||||
use std::pin::Pin;
|
||||
@@ -10,7 +12,7 @@ use once_cell::sync::Lazy;
|
||||
|
||||
use crate::aheader::Aheader;
|
||||
use crate::blob::BlobObject;
|
||||
use crate::constants::{Viewtype, DC_DESIRED_TEXT_LEN, DC_ELLIPSE};
|
||||
use crate::constants::{Viewtype, DC_DESIRED_TEXT_LEN, DC_ELLIPSIS};
|
||||
use crate::contact::addr_normalize;
|
||||
use crate::context::Context;
|
||||
use crate::dc_tools::{dc_get_filemeta, dc_truncate};
|
||||
@@ -276,7 +278,7 @@ impl MimeMessage {
|
||||
parser.maybe_remove_bad_parts();
|
||||
parser.maybe_remove_inline_mailinglist_footer();
|
||||
parser.heuristically_parse_ndn(context).await;
|
||||
parser.parse_headers(context).await;
|
||||
parser.parse_headers(context).await?;
|
||||
|
||||
if warn_empty_signature && parser.signatures.is_empty() {
|
||||
for part in parser.parts.iter_mut() {
|
||||
@@ -293,7 +295,7 @@ impl MimeMessage {
|
||||
|
||||
/// Parses system messages.
|
||||
fn parse_system_message_headers(&mut self, context: &Context) {
|
||||
if self.get(HeaderDef::AutocryptSetupMessage).is_some() {
|
||||
if self.get_header(HeaderDef::AutocryptSetupMessage).is_some() {
|
||||
self.parts = self
|
||||
.parts
|
||||
.iter()
|
||||
@@ -309,7 +311,7 @@ impl MimeMessage {
|
||||
} else {
|
||||
warn!(context, "could not determine ASM mime-part");
|
||||
}
|
||||
} else if let Some(value) = self.get(HeaderDef::ChatContent) {
|
||||
} else if let Some(value) = self.get_header(HeaderDef::ChatContent) {
|
||||
if value == "location-streaming-enabled" {
|
||||
self.is_system_message = SystemMessage::LocationStreamingEnabled;
|
||||
} else if value == "ephemeral-timer-changed" {
|
||||
@@ -324,19 +326,19 @@ impl MimeMessage {
|
||||
|
||||
/// Parses avatar action headers.
|
||||
async fn parse_avatar_headers(&mut self, context: &Context) {
|
||||
if let Some(header_value) = self.get(HeaderDef::ChatGroupAvatar).cloned() {
|
||||
if let Some(header_value) = self.get_header(HeaderDef::ChatGroupAvatar).cloned() {
|
||||
self.group_avatar = self.avatar_action_from_header(context, header_value).await;
|
||||
}
|
||||
|
||||
if let Some(header_value) = self.get(HeaderDef::ChatUserAvatar).cloned() {
|
||||
if let Some(header_value) = self.get_header(HeaderDef::ChatUserAvatar).cloned() {
|
||||
self.user_avatar = self.avatar_action_from_header(context, header_value).await;
|
||||
}
|
||||
}
|
||||
|
||||
fn parse_videochat_headers(&mut self) {
|
||||
if let Some(value) = self.get(HeaderDef::ChatContent).cloned() {
|
||||
if let Some(value) = self.get_header(HeaderDef::ChatContent).cloned() {
|
||||
if value == "videochat-invitation" {
|
||||
let instance = self.get(HeaderDef::ChatWebrtcRoom).cloned();
|
||||
let instance = self.get_header(HeaderDef::ChatWebrtcRoom).cloned();
|
||||
if let Some(part) = self.parts.first_mut() {
|
||||
part.typ = Viewtype::VideochatInvitation;
|
||||
part.param
|
||||
@@ -393,11 +395,12 @@ impl MimeMessage {
|
||||
}
|
||||
|
||||
if let Some(mut part) = self.parts.pop() {
|
||||
if part.typ == Viewtype::Audio && self.get(HeaderDef::ChatVoiceMessage).is_some() {
|
||||
if part.typ == Viewtype::Audio && self.get_header(HeaderDef::ChatVoiceMessage).is_some()
|
||||
{
|
||||
part.typ = Viewtype::Voice;
|
||||
}
|
||||
if part.typ == Viewtype::Image || part.typ == Viewtype::Gif {
|
||||
if let Some(value) = self.get(HeaderDef::ChatContent) {
|
||||
if let Some(value) = self.get_header(HeaderDef::ChatContent) {
|
||||
if value == "sticker" {
|
||||
part.typ = Viewtype::Sticker;
|
||||
}
|
||||
@@ -407,7 +410,7 @@ impl MimeMessage {
|
||||
|| part.typ == Viewtype::Voice
|
||||
|| part.typ == Viewtype::Video
|
||||
{
|
||||
if let Some(field_0) = self.get(HeaderDef::ChatDuration) {
|
||||
if let Some(field_0) = self.get_header(HeaderDef::ChatDuration) {
|
||||
let duration_ms = field_0.parse().unwrap_or_default();
|
||||
if duration_ms > 0 && duration_ms < 24 * 60 * 60 * 1000 {
|
||||
part.param.set_int(Param::Duration, duration_ms);
|
||||
@@ -419,7 +422,7 @@ impl MimeMessage {
|
||||
}
|
||||
}
|
||||
|
||||
async fn parse_headers(&mut self, context: &Context) {
|
||||
async fn parse_headers(&mut self, context: &Context) -> Result<()> {
|
||||
self.parse_system_message_headers(context);
|
||||
self.parse_avatar_headers(context).await;
|
||||
self.parse_videochat_headers();
|
||||
@@ -464,15 +467,20 @@ impl MimeMessage {
|
||||
if !self.decrypting_failed && !self.parts.is_empty() {
|
||||
if let Some(ref dn_to) = self.chat_disposition_notification_to {
|
||||
if let Some(from) = self.from.get(0) {
|
||||
if from.addr.to_lowercase() == dn_to.addr.to_lowercase() {
|
||||
if let Some(part) = self.parts.last_mut() {
|
||||
part.param.set_int(Param::WantsMdn, 1);
|
||||
// Check that the message is not outgoing.
|
||||
if !context.is_self_addr(&from.addr).await? {
|
||||
if from.addr.to_lowercase() == dn_to.addr.to_lowercase() {
|
||||
if let Some(part) = self.parts.last_mut() {
|
||||
part.param.set_int(Param::WantsMdn, 1);
|
||||
}
|
||||
} else {
|
||||
warn!(
|
||||
context,
|
||||
"{} requested a read receipt to {}, ignoring",
|
||||
from.addr,
|
||||
dn_to.addr
|
||||
);
|
||||
}
|
||||
} else {
|
||||
warn!(
|
||||
context,
|
||||
"{} requested a read receipt to {}, ignoring", from.addr, dn_to.addr
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -502,6 +510,8 @@ impl MimeMessage {
|
||||
part.param.set(Param::Bot, "1");
|
||||
}
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
async fn avatar_action_from_header(
|
||||
@@ -582,12 +592,12 @@ impl MimeMessage {
|
||||
}
|
||||
|
||||
pub(crate) fn get_subject(&self) -> Option<String> {
|
||||
self.get(HeaderDef::Subject)
|
||||
self.get_header(HeaderDef::Subject)
|
||||
.filter(|s| !s.is_empty())
|
||||
.map(|s| s.to_string())
|
||||
}
|
||||
|
||||
pub fn get(&self, headerdef: HeaderDef) -> Option<&String> {
|
||||
pub fn get_header(&self, headerdef: HeaderDef) -> Option<&String> {
|
||||
self.header.get(headerdef.get_headername())
|
||||
}
|
||||
|
||||
@@ -896,13 +906,14 @@ impl MimeMessage {
|
||||
(simplified_txt, top_quote)
|
||||
};
|
||||
|
||||
let simplified_txt =
|
||||
if simplified_txt.len() > DC_DESIRED_TEXT_LEN + DC_ELLIPSE.len() {
|
||||
self.is_mime_modified = true;
|
||||
dc_truncate(&*simplified_txt, DC_DESIRED_TEXT_LEN).to_string()
|
||||
} else {
|
||||
simplified_txt
|
||||
};
|
||||
let simplified_txt = if simplified_txt.chars().count()
|
||||
> DC_DESIRED_TEXT_LEN + DC_ELLIPSIS.len()
|
||||
{
|
||||
self.is_mime_modified = true;
|
||||
dc_truncate(&*simplified_txt, DC_DESIRED_TEXT_LEN).to_string()
|
||||
} else {
|
||||
simplified_txt
|
||||
};
|
||||
|
||||
if !simplified_txt.is_empty() || simplified_quote.is_some() {
|
||||
let mut part = Part {
|
||||
@@ -1009,12 +1020,12 @@ impl MimeMessage {
|
||||
}
|
||||
|
||||
pub(crate) fn get_mailinglist_type(&self) -> MailinglistType {
|
||||
if self.get(HeaderDef::ListId).is_some() {
|
||||
if self.get_header(HeaderDef::ListId).is_some() {
|
||||
return MailinglistType::ListIdBased;
|
||||
} else if self.get(HeaderDef::Sender).is_some() {
|
||||
} else if self.get_header(HeaderDef::Sender).is_some() {
|
||||
// the `Sender:`-header alone is no indicator for mailing list
|
||||
// as also used for bot-impersonation via `set_override_sender_name()`
|
||||
if let Some(precedence) = self.get(HeaderDef::Precedence) {
|
||||
if let Some(precedence) = self.get_header(HeaderDef::Precedence) {
|
||||
if precedence == "list" || precedence == "bulk" {
|
||||
return MailinglistType::SenderBased;
|
||||
}
|
||||
@@ -1040,8 +1051,8 @@ impl MimeMessage {
|
||||
}
|
||||
|
||||
pub fn get_rfc724_mid(&self) -> Option<String> {
|
||||
self.get(HeaderDef::XMicrosoftOriginalMessageId)
|
||||
.or_else(|| self.get(HeaderDef::MessageId))
|
||||
self.get_header(HeaderDef::XMicrosoftOriginalMessageId)
|
||||
.or_else(|| self.get_header(HeaderDef::MessageId))
|
||||
.and_then(|msgid| parse_message_id(msgid).ok())
|
||||
}
|
||||
|
||||
@@ -1228,7 +1239,7 @@ impl MimeMessage {
|
||||
/// Also you should add a test in dc_receive_imf.rs (there already are lots of test_parse_ndn_* tests).
|
||||
#[allow(clippy::indexing_slicing)]
|
||||
async fn heuristically_parse_ndn(&mut self, context: &Context) {
|
||||
let maybe_ndn = if let Some(from) = self.get(HeaderDef::From_) {
|
||||
let maybe_ndn = if let Some(from) = self.get_header(HeaderDef::From_) {
|
||||
let from = from.to_ascii_lowercase();
|
||||
from.contains("mailer-daemon") || from.contains("mail-daemon")
|
||||
} else {
|
||||
@@ -1303,7 +1314,7 @@ impl MimeMessage {
|
||||
/// database, returns None.
|
||||
pub async fn get_parent_timestamp(&self, context: &Context) -> Result<Option<i64>> {
|
||||
let parent_timestamp = if let Some(field) = self
|
||||
.get(HeaderDef::InReplyTo)
|
||||
.get_header(HeaderDef::InReplyTo)
|
||||
.and_then(|msgid| parse_message_id(msgid).ok())
|
||||
{
|
||||
context
|
||||
@@ -1347,7 +1358,9 @@ async fn update_gossip_peerstates(
|
||||
peerstate = Some(p);
|
||||
}
|
||||
if let Some(peerstate) = peerstate {
|
||||
peerstate.handle_fingerprint_change(context).await?;
|
||||
peerstate
|
||||
.handle_fingerprint_change(context, message_time)
|
||||
.await?;
|
||||
}
|
||||
|
||||
gossipped_addr.insert(header.addr.clone());
|
||||
@@ -1595,7 +1608,6 @@ mod tests {
|
||||
#![allow(clippy::indexing_slicing)]
|
||||
|
||||
use super::*;
|
||||
use crate::constants::DC_MAX_GET_TEXT_LEN;
|
||||
use crate::{
|
||||
chatlist::Chatlist,
|
||||
config::Config,
|
||||
@@ -1971,11 +1983,11 @@ mod tests {
|
||||
.unwrap();
|
||||
|
||||
// non-overwritten headers do not bubble up
|
||||
let of = mimeparser.get(HeaderDef::SecureJoinGroup).unwrap();
|
||||
let of = mimeparser.get_header(HeaderDef::SecureJoinGroup).unwrap();
|
||||
assert_eq!(of, "no");
|
||||
|
||||
// unknown headers do not bubble upwards
|
||||
let of = mimeparser.get(HeaderDef::_TestHeader).unwrap();
|
||||
let of = mimeparser.get_header(HeaderDef::_TestHeader).unwrap();
|
||||
assert_eq!(of, "Bar");
|
||||
|
||||
// the following fields would bubble up
|
||||
@@ -1984,13 +1996,15 @@ mod tests {
|
||||
// for Chat-Version, also the case-insensivity is tested.
|
||||
assert_eq!(mimeparser.get_subject(), Some("outer-subject".into()));
|
||||
|
||||
let of = mimeparser.get(HeaderDef::ChatVersion).unwrap();
|
||||
let of = mimeparser.get_header(HeaderDef::ChatVersion).unwrap();
|
||||
assert_eq!(of, "0.0");
|
||||
assert_eq!(mimeparser.parts.len(), 1);
|
||||
|
||||
// make sure, headers that are only allowed in the encrypted part
|
||||
// cannot be set from the outer part
|
||||
assert!(mimeparser.get(HeaderDef::SecureJoinFingerprint).is_none());
|
||||
assert!(mimeparser
|
||||
.get_header(HeaderDef::SecureJoinFingerprint)
|
||||
.is_none());
|
||||
}
|
||||
|
||||
#[async_std::test]
|
||||
@@ -2871,21 +2885,20 @@ On 2020-10-25, Bob wrote:
|
||||
let t = TestContext::new().await;
|
||||
|
||||
static REPEAT_TXT: &str = "this text with 42 chars is just repeated.\n";
|
||||
static REPEAT_CNT: usize = 2000; // results in a text of 84k, should be more than DC_MAX_GET_TEXT_LEN
|
||||
static REPEAT_CNT: usize = 2000; // results in a text of 84k, should be more than DC_DESIRED_TEXT_LEN
|
||||
let long_txt = format!("From: alice@c.de\n\n{}", REPEAT_TXT.repeat(REPEAT_CNT));
|
||||
assert!(DC_DESIRED_TEXT_LEN + DC_ELLIPSE.len() < DC_MAX_GET_TEXT_LEN);
|
||||
|
||||
let mimemsg = MimeMessage::from_bytes(&t, long_txt.as_ref())
|
||||
.await
|
||||
.unwrap();
|
||||
assert_eq!(long_txt.matches("just repeated").count(), REPEAT_CNT);
|
||||
assert!(long_txt.len() > DC_MAX_GET_TEXT_LEN);
|
||||
assert!(long_txt.len() > DC_DESIRED_TEXT_LEN);
|
||||
assert!(mimemsg.is_mime_modified);
|
||||
assert!(
|
||||
mimemsg.parts[0].msg.matches("just repeated").count()
|
||||
< DC_MAX_GET_TEXT_LEN / REPEAT_TXT.len()
|
||||
<= DC_DESIRED_TEXT_LEN / REPEAT_TXT.len()
|
||||
);
|
||||
assert!(mimemsg.parts[0].msg.len() <= DC_MAX_GET_TEXT_LEN);
|
||||
assert!(mimemsg.parts[0].msg.len() <= DC_DESIRED_TEXT_LEN + DC_ELLIPSIS.len());
|
||||
}
|
||||
|
||||
#[async_std::test]
|
||||
@@ -2952,4 +2965,36 @@ Some reply
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
// Test that WantsMdn parameter is not set on outgoing messages.
|
||||
#[async_std::test]
|
||||
async fn test_outgoing_wants_mdn() -> Result<()> {
|
||||
let alice = TestContext::new_alice().await;
|
||||
let bob = TestContext::new_bob().await;
|
||||
|
||||
let raw = br###"Date: Thu, 28 Jan 2021 00:26:57 +0000
|
||||
Chat-Version: 1.0\n\
|
||||
Message-ID: <foobarbaz@example.org>
|
||||
To: Bob <bob@example.org>
|
||||
From: Alice <alice@example.com>
|
||||
Subject: subject
|
||||
Chat-Disposition-Notification-To: alice@example.com
|
||||
|
||||
Message.
|
||||
"###;
|
||||
|
||||
// Bob receives message.
|
||||
dc_receive_imf(&bob, raw, "INBOX", 1, false).await?;
|
||||
let msg = bob.get_last_msg().await;
|
||||
// Message is incoming.
|
||||
assert!(msg.param.get_bool(Param::WantsMdn).unwrap());
|
||||
|
||||
// Alice receives copy-to-self.
|
||||
dc_receive_imf(&alice, raw, "INBOX", 1, false).await?;
|
||||
let msg = alice.get_last_msg().await;
|
||||
// Message is outgoing, don't send read receipt to self.
|
||||
assert!(msg.param.get_bool(Param::WantsMdn).is_none());
|
||||
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
//! OAuth 2 module
|
||||
//! OAuth 2 module.
|
||||
|
||||
use std::collections::HashMap;
|
||||
|
||||
@@ -6,6 +6,7 @@ use anyhow::Result;
|
||||
use percent_encoding::{utf8_percent_encode, NON_ALPHANUMERIC};
|
||||
use serde::Deserialize;
|
||||
|
||||
use crate::config::Config;
|
||||
use crate::context::Context;
|
||||
use crate::dc_tools::time;
|
||||
use crate::provider;
|
||||
@@ -55,22 +56,19 @@ pub async fn dc_get_oauth2_url(
|
||||
context: &Context,
|
||||
addr: &str,
|
||||
redirect_uri: &str,
|
||||
) -> Option<String> {
|
||||
if let Some(oauth2) = Oauth2::from_address(addr).await {
|
||||
if context
|
||||
) -> Result<Option<String>> {
|
||||
let socks5_enabled = context.get_config_bool(Config::Socks5Enabled).await?;
|
||||
if let Some(oauth2) = Oauth2::from_address(addr, socks5_enabled).await {
|
||||
context
|
||||
.sql
|
||||
.set_raw_config("oauth2_pending_redirect_uri", Some(redirect_uri))
|
||||
.await
|
||||
.is_err()
|
||||
{
|
||||
return None;
|
||||
}
|
||||
.await?;
|
||||
let oauth2_url = replace_in_uri(oauth2.get_code, "$CLIENT_ID", oauth2.client_id);
|
||||
let oauth2_url = replace_in_uri(&oauth2_url, "$REDIRECT_URI", redirect_uri);
|
||||
|
||||
Some(oauth2_url)
|
||||
Ok(Some(oauth2_url))
|
||||
} else {
|
||||
None
|
||||
Ok(None)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -80,7 +78,8 @@ pub async fn dc_get_oauth2_access_token(
|
||||
code: &str,
|
||||
regenerate: bool,
|
||||
) -> Result<Option<String>> {
|
||||
if let Some(oauth2) = Oauth2::from_address(addr).await {
|
||||
let socks5_enabled = context.get_config_bool(Config::Socks5Enabled).await?;
|
||||
if let Some(oauth2) = Oauth2::from_address(addr, socks5_enabled).await {
|
||||
let lock = context.oauth2_mutex.lock().await;
|
||||
|
||||
// read generated token
|
||||
@@ -225,7 +224,8 @@ pub async fn dc_get_oauth2_addr(
|
||||
addr: &str,
|
||||
code: &str,
|
||||
) -> Result<Option<String>> {
|
||||
let oauth2 = match Oauth2::from_address(addr).await {
|
||||
let socks5_enabled = context.get_config_bool(Config::Socks5Enabled).await?;
|
||||
let oauth2 = match Oauth2::from_address(addr, socks5_enabled).await {
|
||||
Some(o) => o,
|
||||
None => return Ok(None),
|
||||
};
|
||||
@@ -253,13 +253,13 @@ pub async fn dc_get_oauth2_addr(
|
||||
}
|
||||
|
||||
impl Oauth2 {
|
||||
async fn from_address(addr: &str) -> Option<Self> {
|
||||
async fn from_address(addr: &str, skip_mx: bool) -> Option<Self> {
|
||||
let addr_normalized = normalize_addr(addr);
|
||||
if let Some(domain) = addr_normalized
|
||||
.find('@')
|
||||
.map(|index| addr_normalized.split_at(index + 1).1)
|
||||
{
|
||||
if let Some(oauth2_authorizer) = provider::get_provider_info(domain)
|
||||
if let Some(oauth2_authorizer) = provider::get_provider_info(domain, skip_mx)
|
||||
.await
|
||||
.and_then(|provider| provider.oauth2_authorizer.as_ref())
|
||||
{
|
||||
@@ -357,29 +357,29 @@ mod tests {
|
||||
#[async_std::test]
|
||||
async fn test_oauth_from_address() {
|
||||
assert_eq!(
|
||||
Oauth2::from_address("hello@gmail.com").await,
|
||||
Oauth2::from_address("hello@gmail.com", false).await,
|
||||
Some(OAUTH2_GMAIL)
|
||||
);
|
||||
assert_eq!(
|
||||
Oauth2::from_address("hello@googlemail.com").await,
|
||||
Oauth2::from_address("hello@googlemail.com", false).await,
|
||||
Some(OAUTH2_GMAIL)
|
||||
);
|
||||
assert_eq!(
|
||||
Oauth2::from_address("hello@yandex.com").await,
|
||||
Oauth2::from_address("hello@yandex.com", false).await,
|
||||
Some(OAUTH2_YANDEX)
|
||||
);
|
||||
assert_eq!(
|
||||
Oauth2::from_address("hello@yandex.ru").await,
|
||||
Oauth2::from_address("hello@yandex.ru", false).await,
|
||||
Some(OAUTH2_YANDEX)
|
||||
);
|
||||
|
||||
assert_eq!(Oauth2::from_address("hello@web.de").await, None);
|
||||
assert_eq!(Oauth2::from_address("hello@web.de", false).await, None);
|
||||
}
|
||||
|
||||
#[async_std::test]
|
||||
async fn test_oauth_from_mx() {
|
||||
assert_eq!(
|
||||
Oauth2::from_address("hello@google.com").await,
|
||||
Oauth2::from_address("hello@google.com", false).await,
|
||||
Some(OAUTH2_GMAIL)
|
||||
);
|
||||
}
|
||||
@@ -399,7 +399,9 @@ mod tests {
|
||||
let ctx = TestContext::new().await;
|
||||
let addr = "dignifiedquire@gmail.com";
|
||||
let redirect_uri = "chat.delta:/com.b44t.messenger";
|
||||
let res = dc_get_oauth2_url(&ctx.ctx, addr, redirect_uri).await;
|
||||
let res = dc_get_oauth2_url(&ctx.ctx, addr, redirect_uri)
|
||||
.await
|
||||
.unwrap();
|
||||
|
||||
assert_eq!(res, Some("https://accounts.google.com/o/oauth2/auth?client_id=959970109878%2D4mvtgf6feshskf7695nfln6002mom908%2Eapps%2Egoogleusercontent%2Ecom&redirect_uri=chat%2Edelta%3A%2Fcom%2Eb44t%2Emessenger&response_type=code&scope=https%3A%2F%2Fmail.google.com%2F%20email&access_type=offline".into()));
|
||||
}
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
//! # [Autocrypt Peer State](https://autocrypt.org/level1.html#peer-state-management) module
|
||||
//! # [Autocrypt Peer State](https://autocrypt.org/level1.html#peer-state-management) module.
|
||||
|
||||
use std::collections::HashSet;
|
||||
use std::fmt;
|
||||
@@ -260,7 +260,11 @@ impl Peerstate {
|
||||
}
|
||||
|
||||
/// Adds a warning to the chat corresponding to peerstate if fingerprint has changed.
|
||||
pub(crate) async fn handle_fingerprint_change(&self, context: &Context) -> Result<()> {
|
||||
pub(crate) async fn handle_fingerprint_change(
|
||||
&self,
|
||||
context: &Context,
|
||||
timestamp: i64,
|
||||
) -> Result<()> {
|
||||
if self.fingerprint_changed {
|
||||
if let Some(contact_id) = context
|
||||
.sql
|
||||
@@ -273,7 +277,7 @@ impl Peerstate {
|
||||
|
||||
let msg = stock_str::contact_setup_changed(context, self.addr.clone()).await;
|
||||
|
||||
chat::add_info_msg(context, chat_id, msg).await;
|
||||
chat::add_info_msg(context, chat_id, msg, timestamp).await;
|
||||
emit_event!(context, EventType::ChatModified(chat_id));
|
||||
} else {
|
||||
bail!("contact with peerstate.addr {:?} not found", &self.addr);
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
//! OpenPGP helper module using [rPGP facilities](https://github.com/rpgp/rpgp)
|
||||
//! OpenPGP helper module using [rPGP facilities](https://github.com/rpgp/rpgp).
|
||||
|
||||
use std::collections::{BTreeMap, HashSet};
|
||||
use std::io;
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
///! Handle plain text together with some attributes.
|
||||
//! Handle plain text together with some attributes.
|
||||
|
||||
use crate::simplify::split_lines;
|
||||
use once_cell::sync::Lazy;
|
||||
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
//! [Provider database](https://providers.delta.chat/) module
|
||||
//! [Provider database](https://providers.delta.chat/) module.
|
||||
|
||||
mod data;
|
||||
|
||||
@@ -89,15 +89,17 @@ pub struct Provider {
|
||||
///
|
||||
/// For compatibility, email address can be passed to this function
|
||||
/// instead of the domain.
|
||||
pub async fn get_provider_info(domain: &str) -> Option<&'static Provider> {
|
||||
pub async fn get_provider_info(domain: &str, skip_mx: bool) -> Option<&'static Provider> {
|
||||
let domain = domain.rsplitn(2, '@').next()?;
|
||||
|
||||
if let Some(provider) = get_provider_by_domain(domain) {
|
||||
return Some(provider);
|
||||
}
|
||||
|
||||
if let Some(provider) = get_provider_by_mx(domain).await {
|
||||
return Some(provider);
|
||||
if !skip_mx {
|
||||
if let Some(provider) = get_provider_by_mx(domain).await {
|
||||
return Some(provider);
|
||||
}
|
||||
}
|
||||
|
||||
None
|
||||
@@ -221,11 +223,17 @@ mod tests {
|
||||
|
||||
#[async_std::test]
|
||||
async fn test_get_provider_info() {
|
||||
assert!(get_provider_info("").await.is_none());
|
||||
assert!(get_provider_info("google.com").await.unwrap().id == "gmail");
|
||||
assert!(get_provider_info("", false).await.is_none());
|
||||
assert!(get_provider_info("google.com", false).await.unwrap().id == "gmail");
|
||||
|
||||
// get_provider_info() accepts email addresses for backwards compatibility
|
||||
assert!(get_provider_info("example@google.com").await.unwrap().id == "gmail");
|
||||
assert!(
|
||||
get_provider_info("example@google.com", false)
|
||||
.await
|
||||
.unwrap()
|
||||
.id
|
||||
== "gmail"
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
|
||||
@@ -585,8 +585,8 @@ static P_HEY_COM: Lazy<Provider> = Lazy::new(|| {
|
||||
// i.ua.md: i.ua
|
||||
static P_I_UA: Lazy<Provider> = Lazy::new(|| Provider {
|
||||
id: "i.ua",
|
||||
status: Status::Ok,
|
||||
before_login_hint: "",
|
||||
status: Status::Broken,
|
||||
before_login_hint: "Протокол IMAP не предоставляется и не планируется.",
|
||||
after_login_hint: "",
|
||||
overview_page: "https://providers.delta.chat/i-ua",
|
||||
server: vec![],
|
||||
@@ -686,6 +686,35 @@ static P_MAIL_RU: Lazy<Provider> = Lazy::new(|| {
|
||||
}
|
||||
});
|
||||
|
||||
// mail2tor.md: mail2tor.com
|
||||
static P_MAIL2TOR: Lazy<Provider> = Lazy::new(|| Provider {
|
||||
id: "mail2tor",
|
||||
status: Status::Preparation,
|
||||
before_login_hint: "Tor is needed to connect to the email servers.",
|
||||
after_login_hint: "",
|
||||
overview_page: "https://providers.delta.chat/mail2tor",
|
||||
server: vec![
|
||||
Server {
|
||||
protocol: Imap,
|
||||
socket: Plain,
|
||||
hostname: "g77kjrad6bafzzyldqvffq6kxlsgphcygptxhnn4xlnktfgaqshilmyd.onion",
|
||||
port: 143,
|
||||
username_pattern: Email,
|
||||
},
|
||||
Server {
|
||||
protocol: Smtp,
|
||||
socket: Plain,
|
||||
hostname: "xc7tgk2c5onxni2wsy76jslfsitxjbbptejnqhw6gy2ft7khpevhc7ad.onion",
|
||||
port: 25,
|
||||
username_pattern: Email,
|
||||
},
|
||||
],
|
||||
config_defaults: None,
|
||||
strict_tls: true,
|
||||
max_smtp_rcpt_to: None,
|
||||
oauth2_authorizer: None,
|
||||
});
|
||||
|
||||
// mailbox.org.md: mailbox.org, secure.mailbox.org
|
||||
static P_MAILBOX_ORG: Lazy<Provider> = Lazy::new(|| Provider {
|
||||
id: "mailbox.org",
|
||||
@@ -1286,6 +1315,25 @@ static P_YANDEX_RU: Lazy<Provider> = Lazy::new(|| Provider {
|
||||
oauth2_authorizer: Some(Oauth2Authorizer::Yandex),
|
||||
});
|
||||
|
||||
// yggmail.md: yggmail
|
||||
static P_YGGMAIL: Lazy<Provider> = Lazy::new(|| {
|
||||
Provider {
|
||||
id: "yggmail",
|
||||
status: Status::Preparation,
|
||||
before_login_hint: "An Yggmail companion app needs to be installed on your device to access the Yggmail network.",
|
||||
after_login_hint: "Make sure, the Yggmail companion app runs whenever you want to use this account. Note, that you usually cannot write from @yggmail addresses to normal e-mail-addresses (as @gmx.net). However, you can create another account in the normal e-mail-network for this purpose.",
|
||||
overview_page: "https://providers.delta.chat/yggmail",
|
||||
server: vec![
|
||||
Server { protocol: Imap, socket: Plain, hostname: "localhost", port: 1143, username_pattern: Email },
|
||||
Server { protocol: Smtp, socket: Plain, hostname: "localhost", port: 1025, username_pattern: Email },
|
||||
],
|
||||
config_defaults: None,
|
||||
strict_tls: true,
|
||||
max_smtp_rcpt_to: None,
|
||||
oauth2_authorizer: None,
|
||||
}
|
||||
});
|
||||
|
||||
// ziggo.nl.md: ziggo.nl
|
||||
static P_ZIGGO_NL: Lazy<Provider> = Lazy::new(|| Provider {
|
||||
id: "ziggo.nl",
|
||||
@@ -1395,6 +1443,7 @@ pub(crate) static PROVIDER_DATA: Lazy<HashMap<&'static str, &'static Provider>>
|
||||
("internet.ru", &*P_MAIL_RU),
|
||||
("bk.ru", &*P_MAIL_RU),
|
||||
("list.ru", &*P_MAIL_RU),
|
||||
("mail2tor.com", &*P_MAIL2TOR),
|
||||
("mailbox.org", &*P_MAILBOX_ORG),
|
||||
("secure.mailbox.org", &*P_MAILBOX_ORG),
|
||||
("mailo.com", &*P_MAILO_COM),
|
||||
@@ -1528,6 +1577,7 @@ pub(crate) static PROVIDER_DATA: Lazy<HashMap<&'static str, &'static Provider>>
|
||||
("yandex.ua", &*P_YANDEX_RU),
|
||||
("ya.ru", &*P_YANDEX_RU),
|
||||
("narod.ru", &*P_YANDEX_RU),
|
||||
("yggmail", &*P_YGGMAIL),
|
||||
("ziggo.nl", &*P_ZIGGO_NL),
|
||||
("zohomail.eu", &*P_ZOHO),
|
||||
("zoho.com", &*P_ZOHO),
|
||||
@@ -1568,6 +1618,7 @@ pub(crate) static PROVIDER_IDS: Lazy<HashMap<&'static str, &'static Provider>> =
|
||||
("kolst.com", &*P_KOLST_COM),
|
||||
("kontent.com", &*P_KONTENT_COM),
|
||||
("mail.ru", &*P_MAIL_RU),
|
||||
("mail2tor", &*P_MAIL2TOR),
|
||||
("mailbox.org", &*P_MAILBOX_ORG),
|
||||
("mailo.com", &*P_MAILO_COM),
|
||||
("nauta.cu", &*P_NAUTA_CU),
|
||||
@@ -1592,6 +1643,7 @@ pub(crate) static PROVIDER_IDS: Lazy<HashMap<&'static str, &'static Provider>> =
|
||||
("web.de", &*P_WEB_DE),
|
||||
("yahoo", &*P_YAHOO),
|
||||
("yandex.ru", &*P_YANDEX_RU),
|
||||
("yggmail", &*P_YGGMAIL),
|
||||
("ziggo.nl", &*P_ZIGGO_NL),
|
||||
("zoho", &*P_ZOHO),
|
||||
]
|
||||
@@ -1601,4 +1653,4 @@ pub(crate) static PROVIDER_IDS: Lazy<HashMap<&'static str, &'static Provider>> =
|
||||
});
|
||||
|
||||
pub static PROVIDER_UPDATED: Lazy<chrono::NaiveDate> =
|
||||
Lazy::new(|| chrono::NaiveDate::from_ymd(2021, 7, 28));
|
||||
Lazy::new(|| chrono::NaiveDate::from_ymd(2021, 8, 17));
|
||||
|
||||
@@ -63,7 +63,7 @@ def process_data(data, file):
|
||||
raise TypeError("no domains found")
|
||||
for domain in data["domains"]:
|
||||
domain = cleanstr(domain)
|
||||
if domain == "" or domain.count(".") < 1 or domain.lower() != domain:
|
||||
if domain == "" or domain.lower() != domain:
|
||||
raise TypeError("bad domain: " + domain)
|
||||
|
||||
global domains_set
|
||||
@@ -84,7 +84,7 @@ def process_data(data, file):
|
||||
for s in data["server"]:
|
||||
hostname = cleanstr(s.get("hostname", ""))
|
||||
port = int(s.get("port", ""))
|
||||
if hostname == "" or hostname.count(".") < 1 or port <= 0:
|
||||
if hostname == "" or hostname.lower() != hostname or port <= 0:
|
||||
raise TypeError("bad hostname or port")
|
||||
|
||||
protocol = s.get("type", "").upper()
|
||||
@@ -96,7 +96,7 @@ def process_data(data, file):
|
||||
raise TypeError("bad protocol")
|
||||
|
||||
socket = s.get("socket", "").upper()
|
||||
if socket != "STARTTLS" and socket != "SSL":
|
||||
if socket != "STARTTLS" and socket != "SSL" and socket != "PLAIN":
|
||||
raise TypeError("bad socket")
|
||||
|
||||
username_pattern = s.get("username_pattern", "EMAIL").upper()
|
||||
|
||||
19
src/qr.rs
19
src/qr.rs
@@ -1,4 +1,4 @@
|
||||
//! # QR code module
|
||||
//! # QR code module.
|
||||
|
||||
use anyhow::{bail, ensure, format_err, Error};
|
||||
use once_cell::sync::Lazy;
|
||||
@@ -11,6 +11,7 @@ use crate::config::Config;
|
||||
use crate::constants::Blocked;
|
||||
use crate::contact::{addr_normalize, may_be_valid_addr, Contact, Origin};
|
||||
use crate::context::Context;
|
||||
use crate::dc_tools::time;
|
||||
use crate::key::Fingerprint;
|
||||
use crate::log::LogExt;
|
||||
use crate::lot::{Lot, LotState};
|
||||
@@ -160,7 +161,13 @@ async fn decode_openpgp(context: &Context, qr: &str) -> Lot {
|
||||
.await
|
||||
.log_err(context, "Failed to create (new) chat for contact")
|
||||
{
|
||||
chat::add_info_msg(context, chat.id, format!("{} verified.", peerstate.addr)).await;
|
||||
chat::add_info_msg(
|
||||
context,
|
||||
chat.id,
|
||||
format!("{} verified.", peerstate.addr),
|
||||
time(),
|
||||
)
|
||||
.await;
|
||||
}
|
||||
} else if let Some(addr) = addr {
|
||||
lot.state = LotState::QrFprMismatch;
|
||||
@@ -324,11 +331,9 @@ pub async fn set_config_from_qr(context: &Context, qr: &str) -> Result<(), Error
|
||||
let chat_id = if lot.state == LotState::QrReviveVerifyContact {
|
||||
None
|
||||
} else {
|
||||
Some(
|
||||
get_chat_id_by_grpid(context, &lot.text2.unwrap_or_default())
|
||||
.await?
|
||||
.0,
|
||||
)
|
||||
get_chat_id_by_grpid(context, &lot.text2.unwrap_or_default())
|
||||
.await?
|
||||
.map(|(chat_id, _protected, _blocked)| chat_id)
|
||||
};
|
||||
token::save(
|
||||
context,
|
||||
|
||||
102
src/quota.rs
Normal file
102
src/quota.rs
Normal file
@@ -0,0 +1,102 @@
|
||||
//! # Support for IMAP QUOTA extension.
|
||||
|
||||
use anyhow::{anyhow, Result};
|
||||
use async_imap::types::{Quota, QuotaResource};
|
||||
use indexmap::IndexMap;
|
||||
|
||||
use crate::context::Context;
|
||||
use crate::dc_tools::time;
|
||||
use crate::imap::scan_folders::get_watched_folders;
|
||||
use crate::imap::Imap;
|
||||
use crate::job::{Action, Status};
|
||||
use crate::param::Params;
|
||||
use crate::{job, EventType};
|
||||
|
||||
/// warn about a nearly full mailbox after this usage percentage is reached.
|
||||
/// quota icon is "yellow".
|
||||
pub const QUOTA_WARN_THRESHOLD_PERCENTAGE: u64 = 80;
|
||||
|
||||
// warning is already issued at QUOTA_WARN_THRESHOLD_PERCENTAGE,
|
||||
// this threshold only makes the quota icon "red".
|
||||
pub const QUOTA_ERROR_THRESHOLD_PERCENTAGE: u64 = 99;
|
||||
|
||||
// if recent quota is older,
|
||||
// it is re-fetched on dc_get_connectivity_html()
|
||||
pub const QUOTA_MAX_AGE_SECONDS: i64 = 60;
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct QuotaInfo {
|
||||
/// Recently loaded quota information.
|
||||
/// set to `Err()` if the provider does not support quota or on other errors,
|
||||
/// set to `Ok()` for valid quota information.
|
||||
/// Updated by `Action::UpdateRecentQuota`
|
||||
pub(crate) recent: Result<IndexMap<String, Vec<QuotaResource>>>,
|
||||
|
||||
/// Timestamp when structure was modified.
|
||||
pub(crate) modified: i64,
|
||||
}
|
||||
|
||||
async fn get_unique_quota_roots_and_usage(
|
||||
folders: Vec<String>,
|
||||
imap: &mut Imap,
|
||||
) -> Result<IndexMap<String, Vec<QuotaResource>>> {
|
||||
let mut unique_quota_roots: IndexMap<String, Vec<QuotaResource>> = IndexMap::new();
|
||||
for folder in folders {
|
||||
let (quota_roots, quotas) = &imap.get_quota_roots(&folder).await?;
|
||||
// if there are new quota roots found in this imap folder, add them to the list
|
||||
for qr_entries in quota_roots {
|
||||
for quota_root_name in &qr_entries.quota_root_names {
|
||||
// the quota for that quota root
|
||||
let quota: Quota = quotas
|
||||
.iter()
|
||||
.find(|q| &q.root_name == quota_root_name)
|
||||
.cloned()
|
||||
.ok_or_else(|| anyhow!("quota_root should have a quota"))?;
|
||||
// replace old quotas, because between fetching quotaroots for folders,
|
||||
// messages could be recieved and so the usage could have been changed
|
||||
*unique_quota_roots
|
||||
.entry(quota_root_name.clone())
|
||||
.or_insert(vec![]) = quota.resources;
|
||||
}
|
||||
}
|
||||
}
|
||||
Ok(unique_quota_roots)
|
||||
}
|
||||
|
||||
impl Context {
|
||||
// Adds a job to update `quota.recent`
|
||||
pub(crate) async fn schedule_quota_update(&self) {
|
||||
job::kill_action(self, Action::UpdateRecentQuota).await;
|
||||
job::add(
|
||||
self,
|
||||
job::Job::new(Action::UpdateRecentQuota, 0, Params::new(), 0),
|
||||
)
|
||||
.await;
|
||||
}
|
||||
|
||||
/// Updates `quota.recent`, sets `quota.modified` to the current time
|
||||
/// and emits an event to let the UIs update connectivity view.
|
||||
///
|
||||
/// Called in response to `Action::UpdateRecentQuota`.
|
||||
pub(crate) async fn update_recent_quota(&self, imap: &mut Imap) -> Status {
|
||||
if let Err(err) = imap.prepare(self).await {
|
||||
warn!(self, "could not connect: {:?}", err);
|
||||
return Status::RetryNow;
|
||||
}
|
||||
|
||||
let quota = if imap.can_check_quota() {
|
||||
let folders = get_watched_folders(self).await;
|
||||
get_unique_quota_roots_and_usage(folders, imap).await
|
||||
} else {
|
||||
Err(anyhow!("Quota not supported by your provider."))
|
||||
};
|
||||
|
||||
*self.quota.write().await = Some(QuotaInfo {
|
||||
recent: quota,
|
||||
modified: time(),
|
||||
});
|
||||
|
||||
self.emit_event(EventType::ConnectivityChanged);
|
||||
Status::Finished(Ok(()))
|
||||
}
|
||||
}
|
||||
@@ -48,7 +48,7 @@ impl Context {
|
||||
pub async fn maybe_network_lost(&self) {
|
||||
let lock = self.scheduler.read().await;
|
||||
lock.maybe_network_lost().await;
|
||||
connectivity::idle_interrupted(lock).await;
|
||||
connectivity::maybe_network_lost(self, lock).await;
|
||||
}
|
||||
|
||||
pub(crate) async fn interrupt_inbox(&self, info: InterruptInfo) {
|
||||
@@ -298,7 +298,11 @@ async fn smtp_loop(ctx: Context, started: Sender<()>, smtp_handlers: SmtpConnect
|
||||
None => {
|
||||
// Fake Idle
|
||||
info!(ctx, "smtp fake idle - started");
|
||||
connection.connectivity.set_connected(&ctx).await;
|
||||
match &connection.last_send_error {
|
||||
None => connection.connectivity.set_connected(&ctx).await,
|
||||
Some(err) => connection.connectivity.set_err(&ctx, err).await,
|
||||
}
|
||||
|
||||
interrupt_info = idle_interrupt_receiver.recv().await.unwrap_or_default();
|
||||
info!(ctx, "smtp fake idle - interrupted")
|
||||
}
|
||||
|
||||
@@ -3,9 +3,15 @@ use std::{ops::Deref, sync::Arc};
|
||||
|
||||
use async_std::sync::{Mutex, RwLockReadGuard};
|
||||
|
||||
use crate::dc_tools::time;
|
||||
use crate::events::EventType;
|
||||
use crate::{config::Config, scheduler::Scheduler};
|
||||
use crate::quota::{
|
||||
QUOTA_ERROR_THRESHOLD_PERCENTAGE, QUOTA_MAX_AGE_SECONDS, QUOTA_WARN_THRESHOLD_PERCENTAGE,
|
||||
};
|
||||
use crate::{config::Config, dc_tools, scheduler::Scheduler};
|
||||
use crate::{context::Context, log::LogExt};
|
||||
use anyhow::{anyhow, Result};
|
||||
use humansize::{file_size_opts, FileSize};
|
||||
|
||||
#[derive(Debug, Clone, Copy, PartialEq, Eq, EnumProperty, PartialOrd, Ord)]
|
||||
pub enum Connectivity {
|
||||
@@ -20,7 +26,7 @@ pub enum Connectivity {
|
||||
// the top) take priority. This means that e.g. if any folder has an error - usually
|
||||
// because there is no internet connection - the connectivity for the whole
|
||||
// account will be `Notconnected`.
|
||||
#[derive(Debug, Clone, PartialEq, Eq, EnumProperty)]
|
||||
#[derive(Debug, Clone, PartialEq, Eq, EnumProperty, PartialOrd)]
|
||||
enum DetailedConnectivity {
|
||||
Error(String),
|
||||
Uninitialized,
|
||||
@@ -193,6 +199,43 @@ pub(crate) async fn idle_interrupted(scheduler: RwLockReadGuard<'_, Scheduler>)
|
||||
// of what we do here.
|
||||
}
|
||||
|
||||
/// Set the connectivity to "Not connected" after a call to dc_maybe_network_lost().
|
||||
/// If we did not do this, the connectivity would stay "Connected" for quite a long time
|
||||
/// after `maybe_network_lost()` was called.
|
||||
pub(crate) async fn maybe_network_lost(
|
||||
context: &Context,
|
||||
scheduler: RwLockReadGuard<'_, Scheduler>,
|
||||
) {
|
||||
let stores = match &*scheduler {
|
||||
Scheduler::Running {
|
||||
inbox,
|
||||
mvbox,
|
||||
sentbox,
|
||||
..
|
||||
} => [
|
||||
inbox.state.connectivity.clone(),
|
||||
mvbox.state.connectivity.clone(),
|
||||
sentbox.state.connectivity.clone(),
|
||||
],
|
||||
Scheduler::Stopped => return,
|
||||
};
|
||||
drop(scheduler);
|
||||
|
||||
for store in &stores {
|
||||
let mut connectivity_lock = store.0.lock().await;
|
||||
if !matches!(
|
||||
*connectivity_lock,
|
||||
DetailedConnectivity::Uninitialized
|
||||
| DetailedConnectivity::Error(_)
|
||||
| DetailedConnectivity::NotConfigured,
|
||||
) {
|
||||
*connectivity_lock = DetailedConnectivity::Error("Connection lost".to_string());
|
||||
}
|
||||
drop(connectivity_lock);
|
||||
}
|
||||
context.emit_event(EventType::ConnectivityChanged);
|
||||
}
|
||||
|
||||
impl fmt::Debug for ConnectivityStore {
|
||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||
if let Some(guard) = self.0.try_lock() {
|
||||
@@ -255,7 +298,7 @@ impl Context {
|
||||
///
|
||||
/// This comes as an HTML from the core so that we can easily improve it
|
||||
/// and the improvement instantly reaches all UIs.
|
||||
pub async fn get_connectivity_html(&self) -> String {
|
||||
pub async fn get_connectivity_html(&self) -> Result<String> {
|
||||
let mut ret = r#"<!DOCTYPE html>
|
||||
<html>
|
||||
<head>
|
||||
@@ -266,13 +309,29 @@ impl Context {
|
||||
list-style-type: none;
|
||||
padding-left: 1em;
|
||||
}
|
||||
|
||||
.dot {
|
||||
height: 0.9em; width: 0.9em;
|
||||
border: 1px solid #888;
|
||||
border-radius: 50%;
|
||||
display: inline-block;
|
||||
position: relative; left: -0.1em; top: 0.1em;
|
||||
}
|
||||
.bar {
|
||||
width: 90%;
|
||||
border: 1px solid #888;
|
||||
border-radius: .5em;
|
||||
margin-top: .2em;
|
||||
margin-bottom: 1em;
|
||||
position: relative; left: -0.2em;
|
||||
}
|
||||
.progress {
|
||||
min-width:1.8em;
|
||||
height: 1em;
|
||||
border-radius: .45em;
|
||||
color: white;
|
||||
text-align: center;
|
||||
padding-bottom: 2px;
|
||||
}
|
||||
.red {
|
||||
background-color: #f33b2d;
|
||||
}
|
||||
@@ -316,8 +375,7 @@ impl Context {
|
||||
smtp.state.connectivity.clone(),
|
||||
),
|
||||
Scheduler::Stopped => {
|
||||
ret += "Not started</body></html>\n";
|
||||
return ret;
|
||||
return Err(anyhow!("Not started"));
|
||||
}
|
||||
};
|
||||
drop(lock);
|
||||
@@ -366,8 +424,96 @@ impl Context {
|
||||
ret += &*escaper::encode_minimal(&detailed.to_string_smtp(self));
|
||||
ret += "</li></ul>";
|
||||
|
||||
let domain = dc_tools::EmailAddress::new(
|
||||
&self
|
||||
.get_config(Config::ConfiguredAddr)
|
||||
.await?
|
||||
.unwrap_or_default(),
|
||||
)?
|
||||
.domain;
|
||||
ret += &format!("<h3>Storage on {}</h3><ul>", domain);
|
||||
let quota = self.quota.read().await;
|
||||
if let Some(quota) = &*quota {
|
||||
match "a.recent {
|
||||
Ok(quota) => {
|
||||
let roots_cnt = quota.len();
|
||||
for (root_name, resources) in quota {
|
||||
use async_imap::types::QuotaResourceName::*;
|
||||
for resource in resources {
|
||||
ret += "<li>";
|
||||
|
||||
// root name is empty eg. for gmail and redundant eg. for riseup.
|
||||
// therefore, use it only if there are really several roots.
|
||||
if roots_cnt > 1 && !root_name.is_empty() {
|
||||
ret +=
|
||||
&format!("<b>{}:</b> ", &*escaper::encode_minimal(root_name));
|
||||
} else {
|
||||
info!(self, "connectivity: root name hidden: \"{}\"", root_name);
|
||||
}
|
||||
|
||||
ret += &match &resource.name {
|
||||
Atom(resource_name) => {
|
||||
format!(
|
||||
"<b>{}:</b> {} of {} used",
|
||||
&*escaper::encode_minimal(resource_name),
|
||||
resource.usage.to_string(),
|
||||
resource.limit.to_string(),
|
||||
)
|
||||
}
|
||||
Message => {
|
||||
format!(
|
||||
"<b>Messages:</b> {} of {} used",
|
||||
resource.usage.to_string(),
|
||||
resource.limit.to_string(),
|
||||
)
|
||||
}
|
||||
Storage => {
|
||||
// do not use a special title needed for "Storage":
|
||||
// - it is usually shown directly under the "Storage" headline
|
||||
// - by the units "1 MB of 10 MB used" there is some difference to eg. "Messages: 1 of 10 used"
|
||||
// - the string is not longer than the other strings that way (minus title, plus units) -
|
||||
// additional linebreaks on small displays are unlikely therefore
|
||||
// - most times, this is the only item anyway
|
||||
let usage = (resource.usage * 1024)
|
||||
.file_size(file_size_opts::BINARY)
|
||||
.unwrap_or_default();
|
||||
let limit = (resource.limit * 1024)
|
||||
.file_size(file_size_opts::BINARY)
|
||||
.unwrap_or_default();
|
||||
format!("{} of {} used", usage, limit)
|
||||
}
|
||||
};
|
||||
|
||||
let percent = resource.get_usage_percentage();
|
||||
let color = if percent >= QUOTA_ERROR_THRESHOLD_PERCENTAGE {
|
||||
"red"
|
||||
} else if percent >= QUOTA_WARN_THRESHOLD_PERCENTAGE {
|
||||
"yellow"
|
||||
} else {
|
||||
"green"
|
||||
};
|
||||
ret += &format!("<div class=\"bar\"><div class=\"progress {}\" style=\"width: {}%\">{}%</div></div>", color, percent, percent);
|
||||
|
||||
ret += "</li>";
|
||||
}
|
||||
}
|
||||
}
|
||||
Err(e) => {
|
||||
ret += format!("<li>{}</li>", e).as_str();
|
||||
}
|
||||
}
|
||||
|
||||
if quota.modified + QUOTA_MAX_AGE_SECONDS < time() {
|
||||
self.schedule_quota_update().await;
|
||||
}
|
||||
} else {
|
||||
ret += "<li>One moment...</li>";
|
||||
self.schedule_quota_update().await;
|
||||
}
|
||||
ret += "</ul>";
|
||||
|
||||
ret += "</body></html>\n";
|
||||
ret
|
||||
Ok(ret)
|
||||
}
|
||||
|
||||
pub async fn all_work_done(&self) -> bool {
|
||||
|
||||
@@ -1,9 +1,9 @@
|
||||
//! Verified contact protocol implementation as [specified by countermitm project](https://countermitm.readthedocs.io/en/stable/new.html#setup-contact-protocol)
|
||||
//! Verified contact protocol implementation as [specified by countermitm project](https://countermitm.readthedocs.io/en/stable/new.html#setup-contact-protocol).
|
||||
|
||||
use std::convert::TryFrom;
|
||||
use std::time::{Duration, Instant};
|
||||
|
||||
use anyhow::{bail, Context as _, Error, Result};
|
||||
use anyhow::{anyhow, bail, Context as _, Error, Result};
|
||||
use async_std::channel::Receiver;
|
||||
use async_std::sync::Mutex;
|
||||
use percent_encoding::{utf8_percent_encode, AsciiSet, NON_ALPHANUMERIC};
|
||||
@@ -14,6 +14,7 @@ use crate::config::Config;
|
||||
use crate::constants::{Blocked, Viewtype, DC_CONTACT_ID_LAST_SPECIAL};
|
||||
use crate::contact::{Contact, Origin, VerifiedStatus};
|
||||
use crate::context::Context;
|
||||
use crate::dc_tools::time;
|
||||
use crate::e2ee::ensure_secret_key_exists;
|
||||
use crate::events::EventType;
|
||||
use crate::headerdef::HeaderDef;
|
||||
@@ -327,14 +328,14 @@ async fn securejoin(context: &Context, qr: &str) -> Result<ChatId, JoinError> {
|
||||
let start = Instant::now();
|
||||
let chatid = loop {
|
||||
{
|
||||
match chat::get_chat_id_by_grpid(context, &group_id).await {
|
||||
Ok((chatid, _is_protected, _blocked)) => break chatid,
|
||||
Err(err) => {
|
||||
match chat::get_chat_id_by_grpid(context, &group_id).await? {
|
||||
Some((chatid, _is_protected, _blocked)) => break chatid,
|
||||
None => {
|
||||
if start.elapsed() > Duration::from_secs(7) {
|
||||
context.free_ongoing().await;
|
||||
return Err(err
|
||||
.context("Ongoing sender dropped (this is a bug)")
|
||||
.into());
|
||||
return Err(JoinError::Other(anyhow!(
|
||||
"Ongoing sender dropped (this is a bug)"
|
||||
)));
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -483,7 +484,7 @@ pub(crate) async fn handle_securejoin_handshake(
|
||||
return Err(Error::msg("Can not be called with special contact ID"));
|
||||
}
|
||||
let step = mime_message
|
||||
.get(HeaderDef::SecureJoin)
|
||||
.get_header(HeaderDef::SecureJoin)
|
||||
.context("Not a Secure-Join message")?;
|
||||
|
||||
info!(
|
||||
@@ -519,7 +520,7 @@ pub(crate) async fn handle_securejoin_handshake(
|
||||
// it just ensures, we have Bobs key now. If we do _not_ have the key because eg. MitM has removed it,
|
||||
// send_message() will fail with the error "End-to-end-encryption unavailable unexpectedly.", so, there is no additional check needed here.
|
||||
// verify that the `Secure-Join-Invitenumber:`-header matches invitenumber written to the QR code
|
||||
let invitenumber = match mime_message.get(HeaderDef::SecureJoinInvitenumber) {
|
||||
let invitenumber = match mime_message.get_header(HeaderDef::SecureJoinInvitenumber) {
|
||||
Some(n) => n,
|
||||
None => {
|
||||
warn!(context, "Secure-join denied (invitenumber missing)");
|
||||
@@ -575,19 +576,19 @@ pub(crate) async fn handle_securejoin_handshake(
|
||||
==========================================================*/
|
||||
|
||||
// verify that Secure-Join-Fingerprint:-header matches the fingerprint of Bob
|
||||
let fingerprint: Fingerprint = match mime_message.get(HeaderDef::SecureJoinFingerprint)
|
||||
{
|
||||
Some(fp) => fp.parse()?,
|
||||
None => {
|
||||
could_not_establish_secure_connection(
|
||||
context,
|
||||
contact_chat_id,
|
||||
"Fingerprint not provided.",
|
||||
)
|
||||
.await?;
|
||||
return Ok(HandshakeMessage::Ignore);
|
||||
}
|
||||
};
|
||||
let fingerprint: Fingerprint =
|
||||
match mime_message.get_header(HeaderDef::SecureJoinFingerprint) {
|
||||
Some(fp) => fp.parse()?,
|
||||
None => {
|
||||
could_not_establish_secure_connection(
|
||||
context,
|
||||
contact_chat_id,
|
||||
"Fingerprint not provided.",
|
||||
)
|
||||
.await?;
|
||||
return Ok(HandshakeMessage::Ignore);
|
||||
}
|
||||
};
|
||||
if !encrypted_and_signed(context, mime_message, Some(&fingerprint)) {
|
||||
could_not_establish_secure_connection(
|
||||
context,
|
||||
@@ -608,7 +609,7 @@ pub(crate) async fn handle_securejoin_handshake(
|
||||
}
|
||||
info!(context, "Fingerprint verified.",);
|
||||
// verify that the `Secure-Join-Auth:`-header matches the secret written to the QR code
|
||||
let auth_0 = match mime_message.get(HeaderDef::SecureJoinAuth) {
|
||||
let auth_0 = match mime_message.get_header(HeaderDef::SecureJoinAuth) {
|
||||
Some(auth) => auth,
|
||||
None => {
|
||||
could_not_establish_secure_connection(
|
||||
@@ -643,15 +644,15 @@ pub(crate) async fn handle_securejoin_handshake(
|
||||
// the vg-member-added message is special:
|
||||
// this is a normal Chat-Group-Member-Added message
|
||||
// with an additional Secure-Join header
|
||||
let field_grpid = match mime_message.get(HeaderDef::SecureJoinGroup) {
|
||||
let field_grpid = match mime_message.get_header(HeaderDef::SecureJoinGroup) {
|
||||
Some(s) => s.as_str(),
|
||||
None => {
|
||||
warn!(context, "Missing Secure-Join-Group header");
|
||||
return Ok(HandshakeMessage::Ignore);
|
||||
}
|
||||
};
|
||||
match chat::get_chat_id_by_grpid(context, field_grpid).await {
|
||||
Ok((group_chat_id, _, _)) => {
|
||||
match chat::get_chat_id_by_grpid(context, field_grpid).await? {
|
||||
Some((group_chat_id, _, _)) => {
|
||||
if let Err(err) =
|
||||
chat::add_contact_to_chat_ex(context, group_chat_id, contact_id, true)
|
||||
.await
|
||||
@@ -659,12 +660,7 @@ pub(crate) async fn handle_securejoin_handshake(
|
||||
error!(context, "failed to add contact: {}", err);
|
||||
}
|
||||
}
|
||||
Err(err) => {
|
||||
error!(context, "Chat {} not found: {}", &field_grpid, err);
|
||||
return Err(
|
||||
err.context(format!("Chat for group {} not found", &field_grpid))
|
||||
);
|
||||
}
|
||||
None => bail!("Chat {} not found", &field_grpid),
|
||||
}
|
||||
} else {
|
||||
// Alice -> Bob
|
||||
@@ -733,7 +729,7 @@ pub(crate) async fn handle_securejoin_handshake(
|
||||
inviter_progress!(context, contact_id, 800);
|
||||
inviter_progress!(context, contact_id, 1000);
|
||||
let field_grpid = mime_message
|
||||
.get(HeaderDef::SecureJoinGroup)
|
||||
.get_header(HeaderDef::SecureJoinGroup)
|
||||
.map(|s| s.as_str())
|
||||
.unwrap_or_else(|| "");
|
||||
if let Err(err) = chat::get_chat_id_by_grpid(context, &field_grpid).await {
|
||||
@@ -782,7 +778,7 @@ pub(crate) async fn observe_securejoin_on_other_device(
|
||||
return Err(Error::msg("Can not be called with special contact ID"));
|
||||
}
|
||||
let step = mime_message
|
||||
.get(HeaderDef::SecureJoin)
|
||||
.get_header(HeaderDef::SecureJoin)
|
||||
.context("Not a Secure-Join message")?;
|
||||
info!(context, "observing secure-join message \'{}\'", step);
|
||||
|
||||
@@ -819,19 +815,19 @@ pub(crate) async fn observe_securejoin_on_other_device(
|
||||
.await?;
|
||||
return Ok(HandshakeMessage::Ignore);
|
||||
}
|
||||
let fingerprint: Fingerprint = match mime_message.get(HeaderDef::SecureJoinFingerprint)
|
||||
{
|
||||
Some(fp) => fp.parse()?,
|
||||
None => {
|
||||
could_not_establish_secure_connection(
|
||||
let fingerprint: Fingerprint =
|
||||
match mime_message.get_header(HeaderDef::SecureJoinFingerprint) {
|
||||
Some(fp) => fp.parse()?,
|
||||
None => {
|
||||
could_not_establish_secure_connection(
|
||||
context,
|
||||
contact_chat_id,
|
||||
"Fingerprint not provided, please update Delta Chat on all your devices.",
|
||||
)
|
||||
.await?;
|
||||
return Ok(HandshakeMessage::Ignore);
|
||||
}
|
||||
};
|
||||
return Ok(HandshakeMessage::Ignore);
|
||||
}
|
||||
};
|
||||
if mark_peer_as_verified(context, &fingerprint).await.is_err() {
|
||||
could_not_establish_secure_connection(
|
||||
context,
|
||||
@@ -864,7 +860,7 @@ async fn secure_connection_established(
|
||||
"?"
|
||||
};
|
||||
let msg = stock_str::contact_verified(context, addr).await;
|
||||
chat::add_info_msg(context, contact_chat_id, msg).await;
|
||||
chat::add_info_msg(context, contact_chat_id, msg, time()).await;
|
||||
emit_event!(context, EventType::ChatModified(contact_chat_id));
|
||||
info!(context, "StockMessage::ContactVerified posted to 1:1 chat");
|
||||
|
||||
@@ -888,7 +884,7 @@ async fn could_not_establish_secure_connection(
|
||||
)
|
||||
.await;
|
||||
|
||||
chat::add_info_msg(context, contact_chat_id, &msg).await;
|
||||
chat::add_info_msg(context, contact_chat_id, &msg, time()).await;
|
||||
error!(
|
||||
context,
|
||||
"StockMessage::ContactNotVerified posted to 1:1 chat ({})", details
|
||||
@@ -989,8 +985,8 @@ mod tests {
|
||||
assert_eq!(sent.recipient(), "alice@example.com".parse().unwrap());
|
||||
let msg = alice.parse_msg(&sent).await;
|
||||
assert!(!msg.was_encrypted());
|
||||
assert_eq!(msg.get(HeaderDef::SecureJoin).unwrap(), "vc-request");
|
||||
assert!(msg.get(HeaderDef::SecureJoinInvitenumber).is_some());
|
||||
assert_eq!(msg.get_header(HeaderDef::SecureJoin).unwrap(), "vc-request");
|
||||
assert!(msg.get_header(HeaderDef::SecureJoinInvitenumber).is_some());
|
||||
|
||||
// Step 3: Alice receives vc-request, sends vc-auth-required
|
||||
alice.recv_msg(&sent).await;
|
||||
@@ -998,7 +994,10 @@ mod tests {
|
||||
let sent = alice.pop_sent_msg().await;
|
||||
let msg = bob.parse_msg(&sent).await;
|
||||
assert!(msg.was_encrypted());
|
||||
assert_eq!(msg.get(HeaderDef::SecureJoin).unwrap(), "vc-auth-required");
|
||||
assert_eq!(
|
||||
msg.get_header(HeaderDef::SecureJoin).unwrap(),
|
||||
"vc-auth-required"
|
||||
);
|
||||
|
||||
// Step 4: Bob receives vc-auth-required, sends vc-request-with-auth
|
||||
bob.recv_msg(&sent).await;
|
||||
@@ -1033,16 +1032,16 @@ mod tests {
|
||||
let msg = alice.parse_msg(&sent).await;
|
||||
assert!(msg.was_encrypted());
|
||||
assert_eq!(
|
||||
msg.get(HeaderDef::SecureJoin).unwrap(),
|
||||
msg.get_header(HeaderDef::SecureJoin).unwrap(),
|
||||
"vc-request-with-auth"
|
||||
);
|
||||
assert!(msg.get(HeaderDef::SecureJoinAuth).is_some());
|
||||
assert!(msg.get_header(HeaderDef::SecureJoinAuth).is_some());
|
||||
let bob_fp = SignedPublicKey::load_self(&bob.ctx)
|
||||
.await
|
||||
.unwrap()
|
||||
.fingerprint();
|
||||
assert_eq!(
|
||||
*msg.get(HeaderDef::SecureJoinFingerprint).unwrap(),
|
||||
*msg.get_header(HeaderDef::SecureJoinFingerprint).unwrap(),
|
||||
bob_fp.hex()
|
||||
);
|
||||
|
||||
@@ -1091,7 +1090,7 @@ mod tests {
|
||||
let msg = bob.parse_msg(&sent).await;
|
||||
assert!(msg.was_encrypted());
|
||||
assert_eq!(
|
||||
msg.get(HeaderDef::SecureJoin).unwrap(),
|
||||
msg.get_header(HeaderDef::SecureJoin).unwrap(),
|
||||
"vc-contact-confirm"
|
||||
);
|
||||
|
||||
@@ -1140,7 +1139,7 @@ mod tests {
|
||||
let msg = alice.parse_msg(&sent).await;
|
||||
assert!(msg.was_encrypted());
|
||||
assert_eq!(
|
||||
msg.get(HeaderDef::SecureJoin).unwrap(),
|
||||
msg.get_header(HeaderDef::SecureJoin).unwrap(),
|
||||
"vc-contact-confirm-received"
|
||||
);
|
||||
}
|
||||
@@ -1225,16 +1224,16 @@ mod tests {
|
||||
let msg = alice.parse_msg(&sent).await;
|
||||
assert!(msg.was_encrypted());
|
||||
assert_eq!(
|
||||
msg.get(HeaderDef::SecureJoin).unwrap(),
|
||||
msg.get_header(HeaderDef::SecureJoin).unwrap(),
|
||||
"vc-request-with-auth"
|
||||
);
|
||||
assert!(msg.get(HeaderDef::SecureJoinAuth).is_some());
|
||||
assert!(msg.get_header(HeaderDef::SecureJoinAuth).is_some());
|
||||
let bob_fp = SignedPublicKey::load_self(&bob.ctx)
|
||||
.await
|
||||
.unwrap()
|
||||
.fingerprint();
|
||||
assert_eq!(
|
||||
*msg.get(HeaderDef::SecureJoinFingerprint).unwrap(),
|
||||
*msg.get_header(HeaderDef::SecureJoinFingerprint).unwrap(),
|
||||
bob_fp.hex()
|
||||
);
|
||||
|
||||
@@ -1266,7 +1265,7 @@ mod tests {
|
||||
let msg = bob.parse_msg(&sent).await;
|
||||
assert!(msg.was_encrypted());
|
||||
assert_eq!(
|
||||
msg.get(HeaderDef::SecureJoin).unwrap(),
|
||||
msg.get_header(HeaderDef::SecureJoin).unwrap(),
|
||||
"vc-contact-confirm"
|
||||
);
|
||||
|
||||
@@ -1295,7 +1294,7 @@ mod tests {
|
||||
let msg = alice.parse_msg(&sent).await;
|
||||
assert!(msg.was_encrypted());
|
||||
assert_eq!(
|
||||
msg.get(HeaderDef::SecureJoin).unwrap(),
|
||||
msg.get_header(HeaderDef::SecureJoin).unwrap(),
|
||||
"vc-contact-confirm-received"
|
||||
);
|
||||
}
|
||||
@@ -1338,8 +1337,8 @@ mod tests {
|
||||
assert_eq!(sent.recipient(), "alice@example.com".parse().unwrap());
|
||||
let msg = alice.parse_msg(&sent).await;
|
||||
assert!(!msg.was_encrypted());
|
||||
assert_eq!(msg.get(HeaderDef::SecureJoin).unwrap(), "vg-request");
|
||||
assert!(msg.get(HeaderDef::SecureJoinInvitenumber).is_some());
|
||||
assert_eq!(msg.get_header(HeaderDef::SecureJoin).unwrap(), "vg-request");
|
||||
assert!(msg.get_header(HeaderDef::SecureJoinInvitenumber).is_some());
|
||||
|
||||
// Step 3: Alice receives vg-request, sends vg-auth-required
|
||||
alice.recv_msg(&sent).await;
|
||||
@@ -1347,7 +1346,10 @@ mod tests {
|
||||
let sent = alice.pop_sent_msg().await;
|
||||
let msg = bob.parse_msg(&sent).await;
|
||||
assert!(msg.was_encrypted());
|
||||
assert_eq!(msg.get(HeaderDef::SecureJoin).unwrap(), "vg-auth-required");
|
||||
assert_eq!(
|
||||
msg.get_header(HeaderDef::SecureJoin).unwrap(),
|
||||
"vg-auth-required"
|
||||
);
|
||||
|
||||
// Step 4: Bob receives vg-auth-required, sends vg-request-with-auth
|
||||
bob.recv_msg(&sent).await;
|
||||
@@ -1382,16 +1384,16 @@ mod tests {
|
||||
let msg = alice.parse_msg(&sent).await;
|
||||
assert!(msg.was_encrypted());
|
||||
assert_eq!(
|
||||
msg.get(HeaderDef::SecureJoin).unwrap(),
|
||||
msg.get_header(HeaderDef::SecureJoin).unwrap(),
|
||||
"vg-request-with-auth"
|
||||
);
|
||||
assert!(msg.get(HeaderDef::SecureJoinAuth).is_some());
|
||||
assert!(msg.get_header(HeaderDef::SecureJoinAuth).is_some());
|
||||
let bob_fp = SignedPublicKey::load_self(&bob.ctx)
|
||||
.await
|
||||
.unwrap()
|
||||
.fingerprint();
|
||||
assert_eq!(
|
||||
*msg.get(HeaderDef::SecureJoinFingerprint).unwrap(),
|
||||
*msg.get_header(HeaderDef::SecureJoinFingerprint).unwrap(),
|
||||
bob_fp.hex()
|
||||
);
|
||||
|
||||
@@ -1419,7 +1421,10 @@ mod tests {
|
||||
let sent = alice.pop_sent_msg().await;
|
||||
let msg = bob.parse_msg(&sent).await;
|
||||
assert!(msg.was_encrypted());
|
||||
assert_eq!(msg.get(HeaderDef::SecureJoin).unwrap(), "vg-member-added");
|
||||
assert_eq!(
|
||||
msg.get_header(HeaderDef::SecureJoin).unwrap(),
|
||||
"vg-member-added"
|
||||
);
|
||||
|
||||
// Bob should not yet have Alice verified
|
||||
let contact_alice_id =
|
||||
@@ -1446,7 +1451,7 @@ mod tests {
|
||||
let msg = alice.parse_msg(&sent).await;
|
||||
assert!(msg.was_encrypted());
|
||||
assert_eq!(
|
||||
msg.get(HeaderDef::SecureJoin).unwrap(),
|
||||
msg.get_header(HeaderDef::SecureJoin).unwrap(),
|
||||
"vg-member-added-received"
|
||||
);
|
||||
|
||||
|
||||
@@ -12,7 +12,7 @@ use anyhow::{Error, Result};
|
||||
use async_std::sync::MutexGuard;
|
||||
|
||||
use crate::chat::{self, ChatId};
|
||||
use crate::constants::{Blocked, Viewtype};
|
||||
use crate::constants::Viewtype;
|
||||
use crate::contact::{Contact, Origin};
|
||||
use crate::context::Context;
|
||||
use crate::events::EventType;
|
||||
@@ -236,7 +236,7 @@ impl BobState {
|
||||
context: &Context,
|
||||
mime_message: &MimeMessage,
|
||||
) -> Result<Option<BobHandshakeStage>> {
|
||||
let step = match mime_message.get(HeaderDef::SecureJoin) {
|
||||
let step = match mime_message.get_header(HeaderDef::SecureJoin) {
|
||||
Some(step) => step,
|
||||
None => {
|
||||
warn!(
|
||||
@@ -336,9 +336,10 @@ impl BobState {
|
||||
// the very handshake message we're handling now. But
|
||||
// only after we have returned. It does not impact
|
||||
// the security invariants of secure-join however.
|
||||
let (_, is_verified_group, _) = chat::get_chat_id_by_grpid(context, grpid)
|
||||
.await
|
||||
.unwrap_or((ChatId::new(0), false, Blocked::Not));
|
||||
|
||||
let is_verified_group = chat::get_chat_id_by_grpid(context, grpid)
|
||||
.await?
|
||||
.map_or(false, |(_chat_id, is_protected, _blocked)| is_protected);
|
||||
// when joining a non-verified group
|
||||
// the vg-member-added message may be unencrypted
|
||||
// when not all group members have keys or prefer encryption.
|
||||
@@ -361,7 +362,7 @@ impl BobState {
|
||||
|
||||
if let QrInvite::Group { .. } = self.invite {
|
||||
let member_added = mime_message
|
||||
.get(HeaderDef::ChatGroupMemberAdded)
|
||||
.get_header(HeaderDef::ChatGroupMemberAdded)
|
||||
.map(|s| s.as_str())
|
||||
.ok_or_else(|| Error::msg("Missing Chat-Group-Member-Added header"))?;
|
||||
if !context.is_self_addr(member_added).await? {
|
||||
|
||||
@@ -1,3 +1,5 @@
|
||||
//! # Simplify incoming plaintext.
|
||||
|
||||
use itertools::Itertools;
|
||||
|
||||
// protect lines starting with `--` against being treated as a footer.
|
||||
@@ -241,9 +243,7 @@ fn render_message(lines: &[&str], is_cut_at_end: bool) -> String {
|
||||
ret.replace("\u{200B}", "")
|
||||
}
|
||||
|
||||
/**
|
||||
* Tools
|
||||
*/
|
||||
/// Returns true if the line contains only whitespace.
|
||||
fn is_empty_line(buf: &str) -> bool {
|
||||
buf.chars().all(char::is_whitespace)
|
||||
// for some time, this checked for `char <= ' '`,
|
||||
|
||||
48
src/smtp.rs
48
src/smtp.rs
@@ -1,15 +1,17 @@
|
||||
//! # SMTP transport module
|
||||
//! # SMTP transport module.
|
||||
|
||||
pub mod send;
|
||||
|
||||
use std::time::{Duration, SystemTime};
|
||||
|
||||
use async_smtp::smtp::client::net::ClientTlsParameters;
|
||||
use async_smtp::{error, smtp, EmailAddress};
|
||||
use async_smtp::{error, smtp, EmailAddress, ServerAddress};
|
||||
|
||||
use crate::constants::DC_LP_AUTH_OAUTH2;
|
||||
use crate::events::EventType;
|
||||
use crate::login_param::{dc_build_tls, CertificateChecks, LoginParam, ServerLoginParam};
|
||||
use crate::login_param::{
|
||||
dc_build_tls, CertificateChecks, LoginParam, ServerLoginParam, Socks5Config,
|
||||
};
|
||||
use crate::oauth2::dc_get_oauth2_access_token;
|
||||
use crate::provider::Socket;
|
||||
use crate::{context::Context, scheduler::connectivity::ConnectivityStore};
|
||||
@@ -29,8 +31,6 @@ pub enum Error {
|
||||
},
|
||||
#[error("SMTP failed to connect: {0}")]
|
||||
ConnectionFailure(#[source] smtp::error::Error),
|
||||
#[error("SMTP failed to setup connection: {0}")]
|
||||
ConnectionSetupFailure(#[source] smtp::error::Error),
|
||||
#[error("SMTP oauth2 error {address}")]
|
||||
Oauth2 { address: String },
|
||||
#[error("TLS error {0}")]
|
||||
@@ -54,6 +54,9 @@ pub(crate) struct Smtp {
|
||||
last_success: Option<SystemTime>,
|
||||
|
||||
pub(crate) connectivity: ConnectivityStore,
|
||||
|
||||
/// If sending the last message failed, contains the error message.
|
||||
pub(crate) last_send_error: Option<String>,
|
||||
}
|
||||
|
||||
impl Smtp {
|
||||
@@ -100,20 +103,15 @@ impl Smtp {
|
||||
|
||||
self.connectivity.set_connecting(context).await;
|
||||
let lp = LoginParam::from_database(context, "configured_").await?;
|
||||
let res = self
|
||||
.connect(
|
||||
context,
|
||||
&lp.smtp,
|
||||
&lp.addr,
|
||||
lp.server_flags & DC_LP_AUTH_OAUTH2 != 0,
|
||||
lp.provider.map_or(false, |provider| provider.strict_tls),
|
||||
)
|
||||
.await;
|
||||
|
||||
if let Err(err) = &res {
|
||||
self.connectivity.set_err(context, err).await;
|
||||
}
|
||||
res
|
||||
self.connect(
|
||||
context,
|
||||
&lp.smtp,
|
||||
&lp.socks5_config,
|
||||
&lp.addr,
|
||||
lp.server_flags & DC_LP_AUTH_OAUTH2 != 0,
|
||||
lp.provider.map_or(false, |provider| provider.strict_tls),
|
||||
)
|
||||
.await
|
||||
}
|
||||
|
||||
/// Connect using the provided login params.
|
||||
@@ -121,6 +119,7 @@ impl Smtp {
|
||||
&mut self,
|
||||
context: &Context,
|
||||
lp: &ServerLoginParam,
|
||||
socks5_config: &Option<Socks5Config>,
|
||||
addr: &str,
|
||||
oauth2: bool,
|
||||
provider_strict_tls: bool,
|
||||
@@ -190,17 +189,20 @@ impl Smtp {
|
||||
_ => smtp::ClientSecurity::Wrapper(tls_parameters),
|
||||
};
|
||||
|
||||
let client = smtp::SmtpClient::with_security((domain.as_str(), port), security)
|
||||
.await
|
||||
.map_err(Error::ConnectionSetupFailure)?;
|
||||
let client =
|
||||
smtp::SmtpClient::with_security(ServerAddress::new(domain.to_string(), port), security);
|
||||
|
||||
let client = client
|
||||
let mut client = client
|
||||
.smtp_utf8(true)
|
||||
.credentials(creds)
|
||||
.authentication_mechanism(mechanism)
|
||||
.connection_reuse(smtp::ConnectionReuseParameters::ReuseUnlimited)
|
||||
.timeout(Some(Duration::from_secs(SMTP_TIMEOUT)));
|
||||
|
||||
if let Some(socks5_config) = socks5_config {
|
||||
client = client.use_socks5(socks5_config.to_async_smtp_socks5_config());
|
||||
}
|
||||
|
||||
let mut trans = client.into_transport();
|
||||
if let Err(err) = trans.connect().await {
|
||||
return Err(Error::ConnectionFailure(err));
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
//! # SQLite wrapper
|
||||
//! # SQLite wrapper.
|
||||
|
||||
use async_std::path::Path;
|
||||
use async_std::sync::RwLock;
|
||||
|
||||
@@ -1,3 +1,5 @@
|
||||
//! Migrations module.
|
||||
|
||||
use anyhow::Result;
|
||||
|
||||
use crate::config::Config;
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
//! Module to work with translatable stock strings
|
||||
//! Module to work with translatable stock strings.
|
||||
|
||||
use std::future::Future;
|
||||
use std::pin::Pin;
|
||||
@@ -897,10 +897,6 @@ impl Context {
|
||||
|
||||
// add welcome-messages. by the label, this is done only once,
|
||||
// if the user has deleted the message or the chat, it is not added again.
|
||||
let mut msg = Message::new(Viewtype::Text);
|
||||
msg.text = Some(device_messages_hint(self).await);
|
||||
chat::add_device_msg(self, Some("core-about-device-chat"), Some(&mut msg)).await?;
|
||||
|
||||
let image = include_bytes!("../assets/welcome-image.jpg");
|
||||
let blob = BlobObject::create(self, "welcome-image.jpg", image).await?;
|
||||
let mut msg = Message::new(Viewtype::Image);
|
||||
|
||||
@@ -413,7 +413,7 @@ impl TestContext {
|
||||
// This code is mainly the same as `log_msglist` in `cmdline.rs`, so one day, we could
|
||||
// merge them to a public function in the `deltachat` crate.
|
||||
#[allow(dead_code)]
|
||||
#[allow(clippy::clippy::indexing_slicing)]
|
||||
#[allow(clippy::indexing_slicing)]
|
||||
pub async fn print_chat(&self, chat_id: ChatId) {
|
||||
let msglist = chat::get_chat_msgs(self, chat_id, 0x1, None).await.unwrap();
|
||||
let msglist: Vec<MsgId> = msglist
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
//! # Token module
|
||||
//! # Token module.
|
||||
//!
|
||||
//! Functions to read/write token from/to the database. A token is any string associated with a key.
|
||||
//!
|
||||
|
||||
@@ -5,11 +5,13 @@ Some of the standards Delta Chat is based on:
|
||||
Tasks | Standards
|
||||
---------------------------------|---------------------------------------------
|
||||
Transport | IMAP v4 ([RFC 3501](https://tools.ietf.org/html/rfc3501)), SMTP ([RFC 5321](https://tools.ietf.org/html/rfc5321)) and Internet Message Format (IMF, [RFC 5322](https://tools.ietf.org/html/rfc5322))
|
||||
Proxy | SOCKS5 ([RFC 1928](https://tools.ietf.org/html/rfc1928))
|
||||
Embedded media | MIME Document Series ([RFC 2045](https://tools.ietf.org/html/rfc2045), [RFC 2046](https://tools.ietf.org/html/rfc2046)), Content-Disposition Header ([RFC 2183](https://tools.ietf.org/html/rfc2183)), Multipart/Related ([RFC 2387](https://tools.ietf.org/html/rfc2387))
|
||||
Text and Quote encoding | Fixed, Flowed ([RFC 3676](https://tools.ietf.org/html/rfc3676))
|
||||
Filename encoding | Encoded Words ([RFC 2047](https://tools.ietf.org/html/rfc2047)), Encoded Word Extensions ([RFC 2231](https://tools.ietf.org/html/rfc2231))
|
||||
Identify server folders | IMAP LIST Extension ([RFC 6154](https://tools.ietf.org/html/rfc6154))
|
||||
Push | IMAP IDLE ([RFC 2177](https://tools.ietf.org/html/rfc2177))
|
||||
Quota | IMAP QUOTA extension ([RFC 2087](https://tools.ietf.org/html/rfc2087))
|
||||
Authorization | OAuth2 ([RFC 6749](https://tools.ietf.org/html/rfc6749))
|
||||
End-to-end encryption | [Autocrypt Level 1](https://autocrypt.org/level1.html), OpenPGP ([RFC 4880](https://tools.ietf.org/html/rfc4880)), Security Multiparts for MIME ([RFC 1847](https://tools.ietf.org/html/rfc1847)) and [“Mixed Up” Encryption repairing](https://tools.ietf.org/id/draft-dkg-openpgp-pgpmime-message-mangling-00.html)
|
||||
Configuration assistance | [Autoconfigure](https://developer.mozilla.org/en-US/docs/Mozilla/Thunderbird/Autoconfiguration) and [Autodiscover](https://technet.microsoft.com/library/bb124251(v=exchg.150).aspx)
|
||||
|
||||
70
test-data/message/gmx-forward.eml
Normal file
70
test-data/message/gmx-forward.eml
Normal file
@@ -0,0 +1,70 @@
|
||||
Return-Path: <alice@example.com>
|
||||
Delivered-To: bob@example.org
|
||||
Received: from hq5.merlinux.eu
|
||||
by hq5.merlinux.eu with LMTP
|
||||
id GJ4eNagpFWF5UwAAPzvFDg
|
||||
(envelope-from <alice@example.com>)
|
||||
for <bob@example.org>; Thu, 12 Aug 2021 16:01:12 +0200
|
||||
Received: from mout.gmx.net (mout.gmx.net [212.227.17.22])
|
||||
by hq5.merlinux.eu (Postfix) with ESMTPS id 3033227A0003
|
||||
for <bob@example.org>; Thu, 12 Aug 2021 16:01:12 +0200 (CEST)
|
||||
Authentication-Results: hq5.merlinux.eu;
|
||||
dkim=pass (1024-bit key; secure) header.d=gmx.net header.i=@gmx.net header.b="I/oyQzjt";
|
||||
dkim-atps=neutral
|
||||
DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/simple; d=gmx.net;
|
||||
s=badeba3b8450; t=1628776871;
|
||||
bh=yVNQf4XVEEjSt/PzPPM4F9JXYSv2/ynVmb/E4dc6Qpk=;
|
||||
h=X-UI-Sender-Class:From:To:Subject:Date;
|
||||
b=I/oyQzjtFVDJiKkKV2/9DimrUXwhNtrHc5sgFkO7HNz6sheW8t0+8WpL76AfLuUU2
|
||||
KZ/bCPyX3oItKl+31HZMoekrRnDyHiahsF1h3VrSzDXo3K0sk6nmZBjIQLuksGFW5i
|
||||
/+5TkQ+p79YB/HioYm08pewz08caHfCt3EqcuJik=
|
||||
X-UI-Sender-Class: 01bb95c1-4bf8-414a-932a-4f6e2808ef9c
|
||||
Received: from [193.96.224.73] ([193.96.224.73]) by web-mail.gmx.net
|
||||
(3c-app-gmx-bap57.server.lan [172.19.172.127]) (via HTTP); Thu, 12 Aug 2021
|
||||
16:01:11 +0200
|
||||
MIME-Version: 1.0
|
||||
Message-ID: <trinity-18545f24-4f02-4dc8-9f80-8d2646646d03-1628776871644@3c-app-gmx-bap57>
|
||||
From: Alice <alice@example.com>
|
||||
To: bob@example.org
|
||||
Subject: Fw: subject
|
||||
Content-Type: text/html; charset=UTF-8
|
||||
Date: Thu, 12 Aug 2021 16:01:11 +0200
|
||||
Importance: normal
|
||||
Sensitivity: Normal
|
||||
X-UI-Message-Type: mail
|
||||
X-Priority: 3
|
||||
X-Provags-ID: V03:K1:vvJyGpka3W40JnBiz1FlxtNcdZw52KVodkX04BbaREoVau28F88gbimeb2Tm1t58pzQDe
|
||||
HPoPiTTgz3Roj8GM/iIs4FqZxzcPiekR59a/GwFr16mPZQj+1cq6QOk144bXysBz3PHroQrc7Ctx
|
||||
MtVLAY9w5+Lpuql24x9IqjA0eN2ytYrYYgX60d2FgU8CN/azK0bdEcsdyfwnAcf0bW9UY4ghE8Gt
|
||||
hRe8z4WV6qEEzlhU+cI1uAixvNdQ6MFoi1oT7LdvfbUdcm1CBytWbbieGF1LjMa5Y+D4MZ3zUiiY
|
||||
Ys=
|
||||
X-Spam-Flag: NO
|
||||
X-UI-Out-Filterresults: notjunk:1;V03:K0:pksZU4GoRZI=:jPKwLt7m9sSdgel28Ha/o7
|
||||
UdLaJvQkSOD2tUGBq7n9rGeKT3opdBO5SWDRhn/qWLn+muPPYIjwmyE0XiGIjgTxLDJbY/LHL
|
||||
bNWfcZ+geulQn9vH9muMcAW7ThwACRj3CCtWpc4y5ffTbo8VEinde4C4XFuhSUUdqyzu0GxYc
|
||||
FklFTMlpL9ELxn5Mo3MaOnzznwrchd/2ogGzFz9wOtYUot+llyK+VLaylMeSSTIWbSLHwmA7l
|
||||
MwsujGm4OvqP4VXSpVY2MecAGGwEvPsMQ/hfMgDsxRRm3sFsVZf6KFKcngZte0Nq6LZO9QU1x
|
||||
tAmMgjZYPfOE+YSFiuKJ8E3YlsMk58HYTw/ON+m5T+lXSJeWVLA7sOgk9NKBGi2VzvrRz3YSg
|
||||
MysXD8/h8PU7Rj7a2pttFyGxuN397xP3u1A+15LH5M2+AhUy4quzmxC0Ozb2chPdMJHgTO99e
|
||||
5tmLkyYeeREmSB89pFzyOHGghENBflocaDiCidgWm6pd1lfMMjMQ8bA3S/QpE8e913WGCWhVQ
|
||||
uecX4FBK1VEl8WkE/0GQhY8+2mzBE0+Jo1LCKJtAo9h8bG2fNJkujOpKKvUoududAYuajaHuq
|
||||
rVl6G/xOP8JB3FDDNhZQptleN3KU5qPqNYz0qYibUCJNadS6XlwrfkZReJOk3yHnbIUvB9IG1
|
||||
WGu+K/8WtQaYtzmNtZLD3c7YzQZT4v5xzxQ3TtROawkGNGk4gYJTnAd1ZWOkBHEjcSLsYFVYg
|
||||
nhkLeamJ3KnnkBMJromM0tc0PmSdb/hqD/8hkrWQFvK/nmdNm9+z8UCmCTSDV98UodcwpAkJB
|
||||
D+/kEFR3Y5K904h2dhgmSbnqZAVEziDNT2TylwBnxrpvIKX5Xw=
|
||||
|
||||
<html><head></head><body><div style="font-family: Verdana;font-size: 12.0px;"><div> </div>
|
||||
|
||||
<div>
|
||||
<div>
|
||||
<div data-darkreader-inline-border-left="" name="quote" style="margin: 10px 5px 5px 10px; padding: 10px 0px 10px 10px; border-left: 2px solid rgb(195, 217, 229); overflow-wrap: break-word; --darkreader-inline-border-left:#274759;">
|
||||
<div style="margin:0 0 10px 0;"><b>Gesendet:</b> Donnerstag, 12. August 2021 um 15:52 Uhr<br/>
|
||||
<b>Von:</b> "Claire" <claire@example.org><br/>
|
||||
<b>An:</b> alice@example.com<br/>
|
||||
<b>Betreff:</b> subject</div>
|
||||
|
||||
<div name="quoted-content">bodytext</div>
|
||||
</div>
|
||||
</div>
|
||||
</div></div></body></html>
|
||||
|
||||
Reference in New Issue
Block a user