mirror of
https://github.com/chatmail/core.git
synced 2026-04-02 13:32:11 +03:00
Compare commits
19 Commits
fix_subjec
...
fix_few_ma
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
7a208c4f6b | ||
|
|
25ab59eb93 | ||
|
|
e6f737be9d | ||
|
|
e94a896478 | ||
|
|
a7a0277330 | ||
|
|
62a2f70fab | ||
|
|
04ec7f86ef | ||
|
|
f9e27934f5 | ||
|
|
f1b978c9c7 | ||
|
|
4eea6b297a | ||
|
|
0c77cadf19 | ||
|
|
b616830aea | ||
|
|
4bc637d6d8 | ||
|
|
bb605e28aa | ||
|
|
deddb8fde4 | ||
|
|
a59f0bd885 | ||
|
|
881e15d218 | ||
|
|
7bf825a253 | ||
|
|
fe7d99ae11 |
40
CHANGELOG.md
40
CHANGELOG.md
@@ -1,45 +1,5 @@
|
||||
# Changelog
|
||||
|
||||
## 1.0.0-beta.10 (pending)
|
||||
|
||||
- fix grpid-determination from in-reply-to and references headers. @hpk42
|
||||
|
||||
- only send Autocrypt-gossip headers on encrypted messages. @dignifiedquire
|
||||
|
||||
- fix reply-to-encrypted message to also be encrypted. @hpk42
|
||||
|
||||
- remove last unsafe code from dc_receive_imf :) @hpk42
|
||||
|
||||
|
||||
## 1.0.0-beta.9
|
||||
|
||||
- historic: we now use the mailparse crate and lettre-email to generate mime
|
||||
messages. This got rid of mmime completely, the C2rust generated port of the libetpan
|
||||
mime-parse -- IOW 22KLocs of cumbersome code removed! see
|
||||
https://github.com/deltachat/deltachat-core-rust/pull/904#issuecomment-561163330
|
||||
many thanks @dignifiedquire for making everybody's life easier
|
||||
and @jonhoo (from rust-imap fame) for suggesting to use the mailparse crate :)
|
||||
|
||||
- lots of improvements and better error handling in many rust modules
|
||||
thanks @link2xt @flub @r10s, @hpk42 and @dignifiedquire
|
||||
|
||||
- @r10s introduced a new device chat which has an initial
|
||||
welcome message. See
|
||||
https://c.delta.chat/classdc__context__t.html#a1a2aad98bd23c1d21ee42374e241f389
|
||||
for the main new FFI-API.
|
||||
|
||||
- fix moving self-sent messages, thanks @r10s, @flub, @hpk42
|
||||
|
||||
- fix flakyness/sometimes-failing verified/join-protocols,
|
||||
thanks @flub, @r10s, @hpk42
|
||||
|
||||
- fix reply-to-encrypted message to keep encryption
|
||||
|
||||
- new DC_EVENT_SECUREJOIN_MEMBER_ADDED event
|
||||
|
||||
- many little fixes and rustifications (@link2xt, @flub, @hpk42)
|
||||
|
||||
|
||||
## 1.0.0-beta.8
|
||||
|
||||
- now uses async-email/async-imap as the new base
|
||||
|
||||
64
Cargo.lock
generated
64
Cargo.lock
generated
@@ -607,7 +607,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "deltachat"
|
||||
version = "1.0.0-beta.9"
|
||||
version = "1.0.0-beta.8"
|
||||
dependencies = [
|
||||
"async-imap 0.1.1 (git+https://github.com/async-email/async-imap)",
|
||||
"async-std 1.2.0 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
@@ -620,7 +620,6 @@ dependencies = [
|
||||
"chrono 0.4.10 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"debug_stub_derive 0.3.0 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"deltachat_derive 0.1.0",
|
||||
"encoded-words 0.1.0 (git+https://github.com/async-email/encoded-words)",
|
||||
"escaper 0.1.0 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"failure 0.1.6 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"failure_derive 0.1.6 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
@@ -683,9 +682,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "deltachat_ffi"
|
||||
version = "1.0.0-beta.9"
|
||||
version = "1.0.0-beta.8"
|
||||
dependencies = [
|
||||
"deltachat 1.0.0-beta.9",
|
||||
"deltachat 1.0.0-beta.8",
|
||||
"deltachat-provider-database 0.2.1 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"failure 0.1.6 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"human-panic 1.0.1 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
@@ -797,20 +796,6 @@ dependencies = [
|
||||
"version_check 0.9.1 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "encoded-words"
|
||||
version = "0.1.0"
|
||||
source = "git+https://github.com/async-email/encoded-words#019e833f0c9ea7d4b0b693aab44e66d78d18f1d0"
|
||||
dependencies = [
|
||||
"base64 0.11.0 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"charset 0.1.2 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"encoding_rs 0.8.20 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"hex 0.4.0 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"lazy_static 1.4.0 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"regex 1.3.1 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"thiserror 1.0.9 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "encoding"
|
||||
version = "0.2.33"
|
||||
@@ -1167,7 +1152,7 @@ dependencies = [
|
||||
"bytes 0.4.12 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"fnv 1.0.6 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"futures 0.1.29 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"http 0.1.21 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"http 0.1.20 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"indexmap 1.3.0 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"log 0.4.8 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"slab 0.4.2 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
@@ -1207,7 +1192,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "http"
|
||||
version = "0.1.21"
|
||||
version = "0.1.20"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
dependencies = [
|
||||
"bytes 0.4.12 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
@@ -1222,7 +1207,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
dependencies = [
|
||||
"bytes 0.4.12 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"futures 0.1.29 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"http 0.1.21 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"http 0.1.20 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"tokio-buf 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
]
|
||||
|
||||
@@ -1264,7 +1249,7 @@ dependencies = [
|
||||
"futures 0.1.29 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"futures-cpupool 0.1.8 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"h2 0.1.26 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"http 0.1.21 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"http 0.1.20 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"http-body 0.1.0 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"httparse 1.3.4 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"iovec 0.1.4 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
@@ -1338,7 +1323,7 @@ dependencies = [
|
||||
[[package]]
|
||||
name = "imap-proto"
|
||||
version = "0.9.1"
|
||||
source = "git+https://github.com/djc/tokio-imap#2b8701b83c50085b7ffcdf26a95146b91f93a6d5"
|
||||
source = "git+https://github.com/djc/tokio-imap#a26056915f1d715f97935da1f0c97c6d0174f292"
|
||||
dependencies = [
|
||||
"nom 5.0.1 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
]
|
||||
@@ -1471,7 +1456,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
dependencies = [
|
||||
"cc 1.0.47 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"pkg-config 0.3.17 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"vcpkg 0.2.8 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"vcpkg 0.2.7 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -1818,7 +1803,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
[[package]]
|
||||
name = "pgp"
|
||||
version = "0.3.2"
|
||||
source = "git+https://github.com/rpgp/rpgp#8928a249f848d46889f618fde914417f698ea76f"
|
||||
source = "git+https://github.com/rpgp/rpgp#4cc60a1e45a781ea6e7f394ae2583844ac75d214"
|
||||
dependencies = [
|
||||
"aes 0.3.2 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"base64 0.10.1 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
@@ -2278,7 +2263,7 @@ dependencies = [
|
||||
"encoding_rs 0.8.20 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"flate2 1.0.13 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"futures 0.1.29 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"http 0.1.21 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"http 0.1.20 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"hyper 0.12.35 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"hyper-rustls 0.17.1 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"log 0.4.8 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
@@ -2775,24 +2760,6 @@ dependencies = [
|
||||
"wincolor 1.0.2 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "thiserror"
|
||||
version = "1.0.9"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
dependencies = [
|
||||
"thiserror-impl 1.0.9 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "thiserror-impl"
|
||||
version = "1.0.9"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
dependencies = [
|
||||
"proc-macro2 1.0.6 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"quote 1.0.2 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"syn 1.0.11 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "thread-local-object"
|
||||
version = "0.1.0"
|
||||
@@ -3102,7 +3069,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "vcpkg"
|
||||
version = "0.2.8"
|
||||
version = "0.2.7"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
|
||||
[[package]]
|
||||
@@ -3461,7 +3428,6 @@ dependencies = [
|
||||
"checksum ed25519-dalek 1.0.0-pre.2 (registry+https://github.com/rust-lang/crates.io-index)" = "845aaacc16f01178f33349e7c992ecd0cee095aa5e577f0f4dee35971bd36455"
|
||||
"checksum either 1.5.3 (registry+https://github.com/rust-lang/crates.io-index)" = "bb1f6b1ce1c140482ea30ddd3335fc0024ac7ee112895426e0a629a6c20adfe3"
|
||||
"checksum email 0.0.21 (git+https://github.com/deltachat/rust-email)" = "<none>"
|
||||
"checksum encoded-words 0.1.0 (git+https://github.com/async-email/encoded-words)" = "<none>"
|
||||
"checksum encoding 0.2.33 (registry+https://github.com/rust-lang/crates.io-index)" = "6b0d943856b990d12d3b55b359144ff341533e516d94098b1d3fc1ac666d36ec"
|
||||
"checksum encoding-index-japanese 1.20141219.5 (registry+https://github.com/rust-lang/crates.io-index)" = "04e8b2ff42e9a05335dbf8b5c6f7567e5591d0d916ccef4e0b1710d32a0d0c91"
|
||||
"checksum encoding-index-korean 1.20141219.5 (registry+https://github.com/rust-lang/crates.io-index)" = "4dc33fb8e6bcba213fe2f14275f0963fd16f0a02c878e3095ecfdf5bee529d81"
|
||||
@@ -3511,7 +3477,7 @@ dependencies = [
|
||||
"checksum hermit-abi 0.1.3 (registry+https://github.com/rust-lang/crates.io-index)" = "307c3c9f937f38e3534b1d6447ecf090cafcc9744e4a6360e8b037b2cf5af120"
|
||||
"checksum hex 0.4.0 (registry+https://github.com/rust-lang/crates.io-index)" = "023b39be39e3a2da62a94feb433e91e8bcd37676fbc8bea371daf52b7a769a3e"
|
||||
"checksum hostname 0.1.5 (registry+https://github.com/rust-lang/crates.io-index)" = "21ceb46a83a85e824ef93669c8b390009623863b5c195d1ba747292c0c72f94e"
|
||||
"checksum http 0.1.21 (registry+https://github.com/rust-lang/crates.io-index)" = "d6ccf5ede3a895d8856620237b2f02972c1bbc78d2965ad7fe8838d4a0ed41f0"
|
||||
"checksum http 0.1.20 (registry+https://github.com/rust-lang/crates.io-index)" = "2790658cddc82e82b08e25176c431d7015a0adeb1718498715cbd20138a0bf68"
|
||||
"checksum http-body 0.1.0 (registry+https://github.com/rust-lang/crates.io-index)" = "6741c859c1b2463a423a1dbce98d418e6c3c3fc720fb0d45528657320920292d"
|
||||
"checksum httparse 1.3.4 (registry+https://github.com/rust-lang/crates.io-index)" = "cd179ae861f0c2e53da70d892f5f3029f9594be0c41dc5269cd371691b1dc2f9"
|
||||
"checksum human-panic 1.0.1 (registry+https://github.com/rust-lang/crates.io-index)" = "21638c5955a6daf3ecc42cae702335fc37a72a4abcc6959ce457b31a7d43bbdd"
|
||||
@@ -3678,8 +3644,6 @@ dependencies = [
|
||||
"checksum tempfile 3.1.0 (registry+https://github.com/rust-lang/crates.io-index)" = "7a6e24d9338a0a5be79593e2fa15a648add6138caa803e2d5bc782c371732ca9"
|
||||
"checksum termcolor 0.3.6 (registry+https://github.com/rust-lang/crates.io-index)" = "adc4587ead41bf016f11af03e55a624c06568b5a19db4e90fde573d805074f83"
|
||||
"checksum termcolor 1.0.5 (registry+https://github.com/rust-lang/crates.io-index)" = "96d6098003bde162e4277c70665bd87c326f5a0c3f3fbfb285787fa482d54e6e"
|
||||
"checksum thiserror 1.0.9 (registry+https://github.com/rust-lang/crates.io-index)" = "6f357d1814b33bc2dc221243f8424104bfe72dbe911d5b71b3816a2dff1c977e"
|
||||
"checksum thiserror-impl 1.0.9 (registry+https://github.com/rust-lang/crates.io-index)" = "eb2e25d25307eb8436894f727aba8f65d07adf02e5b35a13cebed48bd282bfef"
|
||||
"checksum thread-local-object 0.1.0 (registry+https://github.com/rust-lang/crates.io-index)" = "7da3caa820d0308c84c8654f6cafd81cc3195d45433311cbe22fcf44fc8be071"
|
||||
"checksum thread_local 0.3.6 (registry+https://github.com/rust-lang/crates.io-index)" = "c6b53e329000edc2b34dbe8545fd20e55a333362d0a321909685a19bd28c3f1b"
|
||||
"checksum time 0.1.42 (registry+https://github.com/rust-lang/crates.io-index)" = "db8dcfca086c1143c9270ac42a2bbd8a7ee477b78ac8e45b19abfb0cbede4b6f"
|
||||
@@ -3715,7 +3679,7 @@ dependencies = [
|
||||
"checksum utf8parse 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)" = "8772a4ccbb4e89959023bc5b7cb8623a795caa7092d99f3aa9501b9484d4557d"
|
||||
"checksum uuid 0.6.5 (registry+https://github.com/rust-lang/crates.io-index)" = "e1436e58182935dcd9ce0add9ea0b558e8a87befe01c1a301e6020aeb0876363"
|
||||
"checksum uuid 0.7.4 (registry+https://github.com/rust-lang/crates.io-index)" = "90dbc611eb48397705a6b0f6e917da23ae517e4d127123d2cf7674206627d32a"
|
||||
"checksum vcpkg 0.2.8 (registry+https://github.com/rust-lang/crates.io-index)" = "3fc439f2794e98976c88a2a2dafce96b930fe8010b0a256b3c2199a773933168"
|
||||
"checksum vcpkg 0.2.7 (registry+https://github.com/rust-lang/crates.io-index)" = "33dd455d0f96e90a75803cfeb7f948768c08d70a6de9a8d2362461935698bf95"
|
||||
"checksum version_check 0.1.5 (registry+https://github.com/rust-lang/crates.io-index)" = "914b1a6776c4c929a602fafd8bc742e06365d4bcbe48c30f9cca5824f70dc9dd"
|
||||
"checksum version_check 0.9.1 (registry+https://github.com/rust-lang/crates.io-index)" = "078775d0255232fb988e6fccf26ddc9d1ac274299aaedcedce21c6f72cc533ce"
|
||||
"checksum void 1.0.2 (registry+https://github.com/rust-lang/crates.io-index)" = "6a02e4885ed3bc0f2de90ea6dd45ebcbb66dacffe03547fadbb0eeae2770887d"
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
[package]
|
||||
name = "deltachat"
|
||||
version = "1.0.0-beta.9"
|
||||
version = "1.0.0-beta.8"
|
||||
authors = ["Delta Chat Developers (ML) <delta@codespeak.net>"]
|
||||
edition = "2018"
|
||||
license = "MPL"
|
||||
@@ -54,7 +54,6 @@ rustls = "0.16.0"
|
||||
webpki-roots = "0.18.0"
|
||||
webpki = "0.21.0"
|
||||
mailparse = "0.10.1"
|
||||
encoded-words = { git = "https://github.com/async-email/encoded-words", branch="master" }
|
||||
|
||||
[dev-dependencies]
|
||||
tempfile = "3.0"
|
||||
|
||||
@@ -87,15 +87,6 @@ $ cargo test --all
|
||||
$ cargo build -p deltachat_ffi --release
|
||||
```
|
||||
|
||||
## Debugging environment variables
|
||||
|
||||
- `DCC_IMAP_DEBUG`: if set IMAP protocol commands and responses will be
|
||||
printed
|
||||
|
||||
- `DCC_MIME_DEBUG`: if set outgoing and incoming message will be printed
|
||||
|
||||
|
||||
|
||||
### Expensive tests
|
||||
|
||||
Some tests are expensive and marked with `#[ignore]`, to run these
|
||||
|
||||
Binary file not shown.
|
Before Width: | Height: | Size: 113 KiB |
@@ -1,6 +1,6 @@
|
||||
[package]
|
||||
name = "deltachat_ffi"
|
||||
version = "1.0.0-beta.9"
|
||||
version = "1.0.0-beta.8"
|
||||
description = "Deltachat FFI"
|
||||
authors = ["Delta Chat Developers (ML) <delta@codespeak.net>"]
|
||||
edition = "2018"
|
||||
|
||||
@@ -1139,24 +1139,6 @@ void dc_set_draft (dc_context_t* context, uint32_t ch
|
||||
uint32_t dc_add_device_msg (dc_context_t* context, const char* label, dc_msg_t* msg);
|
||||
|
||||
|
||||
/**
|
||||
* Init device-messages and saved-messages chat.
|
||||
* This function adds the device-chat and saved-messages chat
|
||||
* and adds one or more welcome or update-messages.
|
||||
* The ui can add messages on its own using dc_add_device_msg() -
|
||||
* for ordering, either before or after or even without calling this function.
|
||||
*
|
||||
* Chat and message creation is done only once.
|
||||
* So if the user has manually deleted things, they won't be re-created
|
||||
* (however, not seen device messages are added and may re-create the device-chat).
|
||||
*
|
||||
* @memberof dc_context_t
|
||||
* @param context The context as created by dc_context_new().
|
||||
* @return None.
|
||||
*/
|
||||
void dc_update_device_chats (dc_context_t* context);
|
||||
|
||||
|
||||
/**
|
||||
* Check if a device-message with a given label was ever added.
|
||||
* Device-messages can be added dc_add_device_msg().
|
||||
|
||||
@@ -851,21 +851,6 @@ pub unsafe extern "C" fn dc_add_device_msg(
|
||||
.unwrap_or(0)
|
||||
}
|
||||
|
||||
#[no_mangle]
|
||||
pub unsafe extern "C" fn dc_update_device_chats(context: *mut dc_context_t) {
|
||||
if context.is_null() {
|
||||
eprintln!("ignoring careless call to dc_update_device_chats()");
|
||||
return;
|
||||
}
|
||||
let ffi_context = &mut *context;
|
||||
ffi_context
|
||||
.with_inner(|ctx| {
|
||||
ctx.update_device_chats()
|
||||
.unwrap_or_log_default(ctx, "Failed to add device message")
|
||||
})
|
||||
.unwrap_or(())
|
||||
}
|
||||
|
||||
#[no_mangle]
|
||||
pub unsafe extern "C" fn dc_was_device_msg_ever_added(
|
||||
context: *mut dc_context_t,
|
||||
|
||||
@@ -832,9 +832,6 @@ pub fn dc_cmdline(context: &Context, line: &str) -> Result<(), failure::Error> {
|
||||
msg.set_text(Some(arg1.to_string()));
|
||||
chat::add_device_msg(context, None, Some(&mut msg))?;
|
||||
}
|
||||
"updatedevicechats" => {
|
||||
context.update_device_chats()?;
|
||||
}
|
||||
"listmedia" => {
|
||||
ensure!(sel_chat.is_some(), "No chat selected.");
|
||||
|
||||
@@ -879,7 +876,7 @@ pub fn dc_cmdline(context: &Context, line: &str) -> Result<(), failure::Error> {
|
||||
}
|
||||
"forward" => {
|
||||
ensure!(
|
||||
!arg1.is_empty() && !arg2.is_empty(),
|
||||
!arg1.is_empty() && arg2.is_empty(),
|
||||
"Arguments <msg-id> <chat-id> expected"
|
||||
);
|
||||
|
||||
|
||||
@@ -58,6 +58,8 @@ class Message(object):
|
||||
|
||||
def set_text(self, text):
|
||||
"""set text of this message. """
|
||||
assert self.id > 0, "message not prepared"
|
||||
assert self.is_out_preparing()
|
||||
lib.dc_msg_set_text(self._dc_msg, as_dc_charpointer(text))
|
||||
|
||||
@props.with_doc
|
||||
|
||||
@@ -430,32 +430,6 @@ class TestOnlineAccount:
|
||||
assert self_addr not in ev[2]
|
||||
ev = ac1._evlogger.get_matching("DC_EVENT_DELETED_BLOB_FILE")
|
||||
|
||||
def test_prepare_file_with_unicode(self, acfactory, lp):
|
||||
ac1, ac2 = acfactory.get_two_online_accounts()
|
||||
chat = self.get_chat(ac1, ac2)
|
||||
|
||||
lp.sec("ac1: prepare and send attachment + text to ac2")
|
||||
blobdir = ac1.get_blobdir()
|
||||
basename = "somedäüta.txt"
|
||||
p = os.path.join(blobdir, basename)
|
||||
with open(p, "w") as f:
|
||||
f.write("some data")
|
||||
msg = Message.new_empty(ac1, "file")
|
||||
msg.set_text("hello ä world")
|
||||
msg.set_file(p)
|
||||
message = chat.prepare_message(msg)
|
||||
assert message.is_out_preparing()
|
||||
assert message.text == "hello ä world"
|
||||
chat.send_prepared(message)
|
||||
|
||||
lp.sec("ac2: receive message")
|
||||
ev = ac2._evlogger.get_matching("DC_EVENT_INCOMING_MSG|DC_EVENT_MSGS_CHANGED")
|
||||
assert ev[2] > const.DC_CHAT_ID_LAST_SPECIAL
|
||||
msg = ac2.get_message_by_id(ev[1])
|
||||
assert msg.text == "hello ä world"
|
||||
assert open(msg.filename).read() == "some data"
|
||||
assert msg.filename.endswith(basename)
|
||||
|
||||
def test_mvbox_sentbox_threads(self, acfactory, lp):
|
||||
lp.sec("ac1: start with mvbox thread")
|
||||
ac1 = acfactory.get_online_configuring_account(mvbox=True, sentbox=True)
|
||||
@@ -501,32 +475,27 @@ class TestOnlineAccount:
|
||||
ac1._evlogger.get_matching("DC_EVENT_IMAP_MESSAGE_MOVED")
|
||||
ac1._evlogger.get_matching("DC_EVENT_IMAP_MESSAGE_MOVED")
|
||||
|
||||
def test_forward_messages(self, acfactory, lp):
|
||||
def test_forward_messages(self, acfactory):
|
||||
ac1, ac2 = acfactory.get_two_online_accounts()
|
||||
chat = self.get_chat(ac1, ac2)
|
||||
|
||||
lp.sec("ac1: send message to ac2")
|
||||
msg_out = chat.send_text("message2")
|
||||
|
||||
lp.sec("ac2: wait for receive")
|
||||
# wait for other account to receive
|
||||
ev = ac2._evlogger.get_matching("DC_EVENT_INCOMING_MSG|DC_EVENT_MSGS_CHANGED")
|
||||
assert ev[2] == msg_out.id
|
||||
msg_in = ac2.get_message_by_id(msg_out.id)
|
||||
assert msg_in.text == "message2"
|
||||
|
||||
lp.sec("ac2: check that the message arrive in deaddrop")
|
||||
# check the message arrived in contact-requests/deaddrop
|
||||
chat2 = msg_in.chat
|
||||
assert msg_in in chat2.get_messages()
|
||||
assert not msg_in.is_forwarded()
|
||||
assert chat2.is_deaddrop()
|
||||
assert chat2 == ac2.get_deaddrop_chat()
|
||||
|
||||
lp.sec("ac2: create new chat and forward message to it")
|
||||
chat3 = ac2.create_group_chat("newgroup")
|
||||
assert not chat3.is_promoted()
|
||||
ac2.forward_messages([msg_in], chat3)
|
||||
|
||||
lp.sec("ac2: check new chat has a forwarded message")
|
||||
assert chat3.is_promoted()
|
||||
messages = chat3.get_messages()
|
||||
msg = messages[-1]
|
||||
@@ -660,71 +629,6 @@ class TestOnlineAccount:
|
||||
ev = ac1._evlogger.get_matching("DC_EVENT_SMTP_MESSAGE_SENT")
|
||||
assert not msg.is_encrypted()
|
||||
|
||||
def test_send_first_message_as_long_unicode_with_cr(self, acfactory, lp):
|
||||
ac1, ac2 = acfactory.get_two_online_accounts()
|
||||
ac2.set_config("save_mime_headers", "1")
|
||||
|
||||
lp.sec("ac1: create chat with ac2")
|
||||
chat = self.get_chat(ac1, ac2, both_created=True)
|
||||
|
||||
lp.sec("sending multi-line non-unicode message from ac1 to ac2")
|
||||
text1 = "hello\nworld"
|
||||
msg_out = chat.send_text(text1)
|
||||
assert not msg_out.is_encrypted()
|
||||
|
||||
lp.sec("sending multi-line unicode text message from ac1 to ac2")
|
||||
text2 = "äalis\nthis is ßßÄ"
|
||||
msg_out = chat.send_text(text2)
|
||||
assert not msg_out.is_encrypted()
|
||||
|
||||
lp.sec("wait for ac2 to receive multi-line non-unicode message")
|
||||
msg_in = ac2.wait_next_incoming_message()
|
||||
assert msg_in.text == text1
|
||||
|
||||
lp.sec("wait for ac2 to receive multi-line unicode message")
|
||||
msg_in = ac2.wait_next_incoming_message()
|
||||
assert msg_in.text == text2
|
||||
assert ac1.get_config("addr") in msg_in.chat.get_name()
|
||||
|
||||
def test_reply_encrypted(self, acfactory, lp):
|
||||
ac1, ac2 = acfactory.get_two_online_accounts()
|
||||
|
||||
lp.sec("ac1: create chat with ac2")
|
||||
chat = self.get_chat(ac1, ac2)
|
||||
|
||||
lp.sec("sending text message from ac1 to ac2")
|
||||
msg_out = chat.send_text("message1")
|
||||
assert not msg_out.is_encrypted()
|
||||
|
||||
lp.sec("wait for ac2 to receive message")
|
||||
ev = ac2._evlogger.get_matching("DC_EVENT_MSGS_CHANGED")
|
||||
msg_in = ac2.get_message_by_id(msg_out.id)
|
||||
assert msg_in.text == "message1"
|
||||
assert not msg_in.is_encrypted()
|
||||
|
||||
lp.sec("create new chat with contact and send back (encrypted) message")
|
||||
chat2b = ac2.create_chat_by_message(msg_in)
|
||||
chat2b.send_text("message-back")
|
||||
|
||||
lp.sec("wait for ac1 to receive message")
|
||||
ev = ac1._evlogger.get_matching("DC_EVENT_INCOMING_MSG")
|
||||
assert ev[1] == chat.id
|
||||
msg_back = ac1.get_message_by_id(ev[2])
|
||||
assert msg_back.text == "message-back"
|
||||
assert msg_back.is_encrypted()
|
||||
|
||||
lp.sec("ac1: e2ee_enabled=0 and see if reply is encrypted")
|
||||
print("ac1: e2ee_enabled={}".format(ac1.get_config("e2ee_enabled")))
|
||||
print("ac2: e2ee_enabled={}".format(ac2.get_config("e2ee_enabled")))
|
||||
ac1.set_config("e2ee_enabled", "0")
|
||||
chat.send_text("message2 -- should be encrypted")
|
||||
|
||||
lp.sec("wait for ac2 to receive message")
|
||||
ev = ac2._evlogger.get_matching("DC_EVENT_INCOMING_MSG")
|
||||
msg_in = ac2.get_message_by_id(ev[2])
|
||||
assert msg_in.text == "message2 -- should be encrypted"
|
||||
assert msg_in.is_encrypted()
|
||||
|
||||
def test_saved_mime_on_received_message(self, acfactory, lp):
|
||||
ac1, ac2 = acfactory.get_two_online_accounts()
|
||||
|
||||
|
||||
@@ -57,13 +57,13 @@ class TestOnlineInCreation:
|
||||
|
||||
lp.sec("wait1 for original or forwarded messages to arrive")
|
||||
ev1 = ac2._evlogger.get_matching("DC_EVENT_MSGS_CHANGED")
|
||||
assert ev1[1] > const.DC_CHAT_ID_LAST_SPECIAL
|
||||
assert ev1[1] >= const.DC_CHAT_ID_LAST_SPECIAL
|
||||
received_original = ac2.get_message_by_id(ev1[2])
|
||||
assert cmp(received_original.filename, path, False)
|
||||
|
||||
lp.sec("wait2 for original or forwarded messages to arrive")
|
||||
ev2 = ac2._evlogger.get_matching("DC_EVENT_MSGS_CHANGED")
|
||||
assert ev2[1] > const.DC_CHAT_ID_LAST_SPECIAL
|
||||
assert ev2[1] >= const.DC_CHAT_ID_LAST_SPECIAL
|
||||
assert ev2[1] != ev1[1]
|
||||
received_copy = ac2.get_message_by_id(ev2[2])
|
||||
assert cmp(received_copy.filename, path, False)
|
||||
|
||||
@@ -33,8 +33,6 @@ def replace_toml_version(relpath, newversion):
|
||||
if __name__ == "__main__":
|
||||
|
||||
if len(sys.argv) < 2:
|
||||
for x in ("Cargo.toml", "deltachat-ffi/Cargo.toml"):
|
||||
print("{}: {}".format(x, read_toml_version(x)))
|
||||
raise SystemExit("need argument: new version, example 1.0.0-beta.27")
|
||||
newversion = sys.argv[1]
|
||||
if newversion.count(".") < 2:
|
||||
|
||||
@@ -76,6 +76,7 @@ impl Aheader {
|
||||
if let Ok(Some(value)) = headers.get_first_value("Autocrypt") {
|
||||
match Self::from_str(&value) {
|
||||
Ok(header) => {
|
||||
info!(context, "comparing {} - {}", header.addr, wanted_from);
|
||||
if addr_cmp(&header.addr, wanted_from) {
|
||||
return Some(header);
|
||||
}
|
||||
|
||||
24
src/blob.rs
24
src/blob.rs
@@ -483,23 +483,23 @@ mod tests {
|
||||
#[test]
|
||||
fn test_suffix() {
|
||||
let t = dummy_context();
|
||||
let blob = BlobObject::create(&t.ctx, "foo.txt", b"hello").unwrap();
|
||||
assert_eq!(blob.suffix(), Some("txt"));
|
||||
let blob = BlobObject::create(&t.ctx, "bar", b"world").unwrap();
|
||||
assert_eq!(blob.suffix(), None);
|
||||
let foo = BlobObject::create(&t.ctx, "foo.txt", b"hello").unwrap();
|
||||
assert_eq!(foo.suffix(), Some("txt"));
|
||||
let bar = BlobObject::create(&t.ctx, "bar", b"world").unwrap();
|
||||
assert_eq!(bar.suffix(), None);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_create_dup() {
|
||||
let t = dummy_context();
|
||||
BlobObject::create(&t.ctx, "foo.txt", b"hello").unwrap();
|
||||
let foo_path = t.ctx.get_blobdir().join("foo.txt");
|
||||
assert!(foo_path.exists());
|
||||
let foo = t.ctx.get_blobdir().join("foo.txt");
|
||||
assert!(foo.exists());
|
||||
BlobObject::create(&t.ctx, "foo.txt", b"world").unwrap();
|
||||
for dirent in fs::read_dir(t.ctx.get_blobdir()).unwrap() {
|
||||
let fname = dirent.unwrap().file_name();
|
||||
if fname == foo_path.file_name().unwrap() {
|
||||
assert_eq!(fs::read(&foo_path).unwrap(), b"hello");
|
||||
if fname == foo.file_name().unwrap() {
|
||||
assert_eq!(fs::read(&foo).unwrap(), b"hello");
|
||||
} else {
|
||||
let name = fname.to_str().unwrap();
|
||||
assert!(name.starts_with("foo"));
|
||||
@@ -512,13 +512,13 @@ mod tests {
|
||||
fn test_double_ext_preserved() {
|
||||
let t = dummy_context();
|
||||
BlobObject::create(&t.ctx, "foo.tar.gz", b"hello").unwrap();
|
||||
let foo_path = t.ctx.get_blobdir().join("foo.tar.gz");
|
||||
assert!(foo_path.exists());
|
||||
let foo = t.ctx.get_blobdir().join("foo.tar.gz");
|
||||
assert!(foo.exists());
|
||||
BlobObject::create(&t.ctx, "foo.tar.gz", b"world").unwrap();
|
||||
for dirent in fs::read_dir(t.ctx.get_blobdir()).unwrap() {
|
||||
let fname = dirent.unwrap().file_name();
|
||||
if fname == foo_path.file_name().unwrap() {
|
||||
assert_eq!(fs::read(&foo_path).unwrap(), b"hello");
|
||||
if fname == foo.file_name().unwrap() {
|
||||
assert_eq!(fs::read(&foo).unwrap(), b"hello");
|
||||
} else {
|
||||
let name = fname.to_str().unwrap();
|
||||
println!("{}", name);
|
||||
|
||||
130
src/chat.rs
130
src/chat.rs
@@ -259,6 +259,8 @@ impl Chat {
|
||||
msg: &mut Message,
|
||||
timestamp: i64,
|
||||
) -> Result<MsgId, Error> {
|
||||
let mut do_guarantee_e2ee: bool;
|
||||
let e2ee_enabled: bool;
|
||||
let mut new_references = "".into();
|
||||
let mut new_in_reply_to = "".into();
|
||||
let mut msg_id = 0;
|
||||
@@ -309,20 +311,24 @@ impl Chat {
|
||||
self.id
|
||||
);
|
||||
}
|
||||
} else if self.typ == Chattype::Group
|
||||
|| self.typ == Chattype::VerifiedGroup
|
||||
&& self.param.get_int(Param::Unpromoted).unwrap_or_default() == 1
|
||||
{
|
||||
self.param.remove(Param::Unpromoted);
|
||||
self.update_param(context)?;
|
||||
} else {
|
||||
if self.typ == Chattype::Group || self.typ == Chattype::VerifiedGroup {
|
||||
if self.param.get_int(Param::Unpromoted).unwrap_or_default() == 1 {
|
||||
self.param.remove(Param::Unpromoted);
|
||||
self.update_param(context)?;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/* check if we want to encrypt this message. If yes and circumstances change
|
||||
/* check if we can guarantee E2EE for this message.
|
||||
if we guarantee E2EE, and circumstances change
|
||||
so that E2EE is no longer available at a later point (reset, changed settings),
|
||||
we might not send the message out at all */
|
||||
if msg.param.get_int(Param::ForcePlaintext).unwrap_or_default() == 0 {
|
||||
we do not send the message out at all */
|
||||
do_guarantee_e2ee = false;
|
||||
e2ee_enabled = context.get_config_bool(Config::E2eeEnabled);
|
||||
if e2ee_enabled && msg.param.get_int(Param::ForcePlaintext).unwrap_or_default() == 0 {
|
||||
let mut can_encrypt = true;
|
||||
let mut all_mutual = context.get_config_bool(Config::E2eeEnabled);
|
||||
let mut all_mutual = true;
|
||||
|
||||
// take care that this statement returns NULL rows
|
||||
// if there is no peerstates for a chat member!
|
||||
@@ -369,13 +375,18 @@ impl Chat {
|
||||
}
|
||||
}
|
||||
|
||||
if can_encrypt
|
||||
&& (all_mutual || last_msg_in_chat_encrypted(context, &context.sql, self.id))
|
||||
{
|
||||
msg.param.set_int(Param::GuaranteeE2ee, 1);
|
||||
if can_encrypt {
|
||||
if all_mutual {
|
||||
do_guarantee_e2ee = true;
|
||||
} else if last_msg_in_chat_encrypted(context, &context.sql, self.id) {
|
||||
do_guarantee_e2ee = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
// reset encrypt error state eg. for forwarding
|
||||
if do_guarantee_e2ee {
|
||||
msg.param.set_int(Param::GuaranteeE2ee, 1);
|
||||
}
|
||||
// reset eg. for forwarding
|
||||
msg.param.remove(Param::ErroneousE2ee);
|
||||
|
||||
// set "In-Reply-To:" to identify the message to which the composed message is a reply;
|
||||
@@ -408,15 +419,15 @@ impl Chat {
|
||||
} else if !parent_in_reply_to.is_empty() && !parent_rfc724_mid.is_empty() {
|
||||
new_references = format!("{} {}", parent_in_reply_to, parent_rfc724_mid);
|
||||
} else if !parent_in_reply_to.is_empty() {
|
||||
new_references = parent_in_reply_to;
|
||||
new_references = parent_in_reply_to.clone();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// add independent location to database
|
||||
|
||||
if msg.param.exists(Param::SetLatitude)
|
||||
&& sql::execute(
|
||||
if msg.param.exists(Param::SetLatitude) {
|
||||
if sql::execute(
|
||||
context,
|
||||
&context.sql,
|
||||
"INSERT INTO locations \
|
||||
@@ -431,16 +442,17 @@ impl Chat {
|
||||
],
|
||||
)
|
||||
.is_ok()
|
||||
{
|
||||
location_id = sql::get_rowid2(
|
||||
context,
|
||||
&context.sql,
|
||||
"locations",
|
||||
"timestamp",
|
||||
timestamp,
|
||||
"from_id",
|
||||
DC_CONTACT_ID_SELF as i32,
|
||||
);
|
||||
{
|
||||
location_id = sql::get_rowid2(
|
||||
context,
|
||||
&context.sql,
|
||||
"locations",
|
||||
"timestamp",
|
||||
timestamp,
|
||||
"from_id",
|
||||
DC_CONTACT_ID_SELF as i32,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
// add message to the database
|
||||
@@ -497,7 +509,7 @@ impl Chat {
|
||||
/// chat messages, use dc_get_chat_msgs().
|
||||
///
|
||||
/// If the user is asked before creation, he should be
|
||||
/// asked whether he wants to chat with the *contact* belonging to the message;
|
||||
/// asked whether he wants to chat with the _contact_ belonging to the message;
|
||||
/// the group names may be really weird when taken from the subject of implicit
|
||||
/// groups and this may look confusing.
|
||||
///
|
||||
@@ -579,10 +591,6 @@ pub fn unblock(context: &Context, chat_id: u32) {
|
||||
}
|
||||
|
||||
pub fn set_blocking(context: &Context, chat_id: u32, new_blocking: Blocked) -> bool {
|
||||
if chat_id == 0 {
|
||||
warn!(context, "ignoring setting of Block-status for chat_id=0");
|
||||
return false;
|
||||
}
|
||||
sql::execute(
|
||||
context,
|
||||
&context.sql,
|
||||
@@ -866,14 +874,17 @@ pub fn send_msg(context: &Context, chat_id: u32, msg: &mut Message) -> Result<Ms
|
||||
let forwards = msg.param.get(Param::PrepForwards);
|
||||
if let Some(forwards) = forwards {
|
||||
for forward in forwards.split(' ') {
|
||||
if let Ok(msg_id) = forward
|
||||
match forward
|
||||
.parse::<u32>()
|
||||
.map_err(|_| InvalidMsgId)
|
||||
.map(MsgId::new)
|
||||
.map(|id| MsgId::new(id))
|
||||
{
|
||||
if let Ok(mut msg) = Message::load_from_db(context, msg_id) {
|
||||
send_msg(context, 0, &mut msg)?;
|
||||
};
|
||||
Ok(msg_id) => {
|
||||
if let Ok(mut msg) = Message::load_from_db(context, msg_id) {
|
||||
send_msg(context, 0, &mut msg)?;
|
||||
};
|
||||
}
|
||||
Err(_) => (),
|
||||
}
|
||||
}
|
||||
msg.param.remove(Param::PrepForwards);
|
||||
@@ -1715,8 +1726,12 @@ pub fn set_chat_name(
|
||||
if sql::execute(
|
||||
context,
|
||||
&context.sql,
|
||||
"UPDATE chats SET name=? WHERE id=?;",
|
||||
params![new_name.as_ref(), chat_id as i32],
|
||||
format!(
|
||||
"UPDATE chats SET name='{}' WHERE id={};",
|
||||
new_name.as_ref(),
|
||||
chat_id as i32
|
||||
),
|
||||
params![],
|
||||
)
|
||||
.is_ok()
|
||||
{
|
||||
@@ -1834,7 +1849,6 @@ pub fn forward_msgs(context: &Context, msg_ids: &[MsgId], chat_id: u32) -> Resul
|
||||
|
||||
unarchive(context, chat_id)?;
|
||||
if let Ok(mut chat) = Chat::load_from_db(context, chat_id) {
|
||||
ensure!(chat.can_send(), "cannot send to chat #{}", chat_id);
|
||||
curr_timestamp = dc_create_smeared_timestamps(context, msg_ids.len());
|
||||
let ids = context.sql.query_map(
|
||||
format!(
|
||||
@@ -1970,7 +1984,6 @@ pub fn add_device_msg(
|
||||
chat_id = create_or_lookup_by_contact_id(context, DC_CONTACT_ID_DEVICE, Blocked::Not)?.0;
|
||||
|
||||
let rfc724_mid = dc_create_outgoing_rfc724_mid(None, "@device");
|
||||
msg.try_calc_and_set_dimensions(context).ok();
|
||||
prepare_msg_blob(context, msg)?;
|
||||
unarchive(context, chat_id)?;
|
||||
|
||||
@@ -2198,7 +2211,6 @@ mod tests {
|
||||
let chat = Chat::load_from_db(&t.ctx, chat_id);
|
||||
assert!(chat.is_ok());
|
||||
let chat = chat.unwrap();
|
||||
assert_eq!(chat.get_type(), Chattype::Single);
|
||||
assert!(chat.is_device_talk());
|
||||
assert!(!chat.is_self_talk());
|
||||
assert!(!chat.can_send());
|
||||
@@ -2266,22 +2278,6 @@ mod tests {
|
||||
assert_eq!(chatlist_len(&t.ctx, 0), 0)
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_device_chat_cannot_sent() {
|
||||
let t = test_context(Some(Box::new(logging_cb)));
|
||||
t.ctx.update_device_chats().unwrap();
|
||||
let (device_chat_id, _) =
|
||||
create_or_lookup_by_contact_id(&t.ctx, DC_CONTACT_ID_DEVICE, Blocked::Not).unwrap();
|
||||
|
||||
let mut msg = Message::new(Viewtype::Text);
|
||||
msg.text = Some("message text".to_string());
|
||||
assert!(send_msg(&t.ctx, device_chat_id, &mut msg).is_err());
|
||||
assert!(prepare_msg(&t.ctx, device_chat_id, &mut msg).is_err());
|
||||
|
||||
let msg_id = add_device_msg(&t.ctx, None, Some(&mut msg)).unwrap();
|
||||
assert!(forward_msgs(&t.ctx, &[msg_id], device_chat_id).is_err());
|
||||
}
|
||||
|
||||
fn chatlist_len(ctx: &Context, listflags: usize) -> usize {
|
||||
Chatlist::try_load(ctx, listflags, None, None)
|
||||
.unwrap()
|
||||
@@ -2337,20 +2333,4 @@ mod tests {
|
||||
assert_eq!(chatlist_len(&t.ctx, DC_GCL_NO_SPECIALS), 1);
|
||||
assert_eq!(chatlist_len(&t.ctx, DC_GCL_ARCHIVED_ONLY), 1);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_set_chat_name() {
|
||||
let t = dummy_context();
|
||||
let chat_id = create_group_chat(&t.ctx, VerifiedStatus::Unverified, "foo").unwrap();
|
||||
assert_eq!(
|
||||
Chat::load_from_db(&t.ctx, chat_id).unwrap().get_name(),
|
||||
"foo"
|
||||
);
|
||||
|
||||
set_chat_name(&t.ctx, chat_id, "bar").unwrap();
|
||||
assert_eq!(
|
||||
Chat::load_from_db(&t.ctx, chat_id).unwrap().get_name(),
|
||||
"bar"
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -60,7 +60,7 @@ impl Chatlist {
|
||||
/// or "Not now".
|
||||
/// The UI can also offer a "Close" button that calls dc_marknoticed_contact() then.
|
||||
/// - DC_CHAT_ID_ARCHIVED_LINK (6) - this special chat is present if the user has
|
||||
/// archived *any* chat using dc_archive_chat(). The UI should show a link as
|
||||
/// archived _any_ chat using dc_archive_chat(). The UI should show a link as
|
||||
/// "Show archived chats", if the user clicks this item, the UI should show a
|
||||
/// list of all archived chats that can be created by this function hen using
|
||||
/// the DC_GCL_ARCHIVED_ONLY flag.
|
||||
@@ -71,7 +71,7 @@ impl Chatlist {
|
||||
/// The `listflags` is a combination of flags:
|
||||
/// - if the flag DC_GCL_ARCHIVED_ONLY is set, only archived chats are returned.
|
||||
/// if DC_GCL_ARCHIVED_ONLY is not set, only unarchived chats are returned and
|
||||
/// the pseudo-chat DC_CHAT_ID_ARCHIVED_LINK is added if there are *any* archived
|
||||
/// the pseudo-chat DC_CHAT_ID_ARCHIVED_LINK is added if there are _any_ archived
|
||||
/// chats
|
||||
/// - if the flag DC_GCL_NO_SPECIALS is set, deaddrop and archive link are not added
|
||||
/// to the list (may be used eg. for selecting chats on forwarding, the flag is
|
||||
|
||||
@@ -28,38 +28,27 @@ pub enum Config {
|
||||
SendPort,
|
||||
SmtpCertificateChecks,
|
||||
ServerFlags,
|
||||
|
||||
#[strum(props(default = "INBOX"))]
|
||||
ImapFolder,
|
||||
|
||||
Displayname,
|
||||
Selfstatus,
|
||||
Selfavatar,
|
||||
|
||||
#[strum(props(default = "0"))]
|
||||
BccSelf,
|
||||
|
||||
#[strum(props(default = "1"))]
|
||||
E2eeEnabled,
|
||||
|
||||
#[strum(props(default = "1"))]
|
||||
MdnsEnabled,
|
||||
|
||||
#[strum(props(default = "1"))]
|
||||
InboxWatch,
|
||||
|
||||
#[strum(props(default = "1"))]
|
||||
SentboxWatch,
|
||||
|
||||
#[strum(props(default = "1"))]
|
||||
MvboxWatch,
|
||||
|
||||
#[strum(props(default = "1"))]
|
||||
MvboxMove,
|
||||
|
||||
#[strum(props(default = "0"))] // also change ShowEmails.default() on changes
|
||||
ShowEmails,
|
||||
|
||||
SaveMimeHeaders,
|
||||
ConfiguredAddr,
|
||||
ConfiguredMailServer,
|
||||
@@ -77,13 +66,11 @@ pub enum Config {
|
||||
ConfiguredSendSecurity,
|
||||
ConfiguredE2EEEnabled,
|
||||
Configured,
|
||||
|
||||
// Deprecated
|
||||
#[strum(serialize = "sys.version")]
|
||||
SysVersion,
|
||||
|
||||
#[strum(serialize = "sys.msgsize_max_recommended")]
|
||||
SysMsgsizeMaxRecommended,
|
||||
|
||||
#[strum(serialize = "sys.config_keys")]
|
||||
SysConfigKeys,
|
||||
}
|
||||
|
||||
@@ -1,45 +1,17 @@
|
||||
//! # Thunderbird's Autoconfiguration implementation
|
||||
//!
|
||||
//! Documentation: https://developer.mozilla.org/en-US/docs/Mozilla/Thunderbird/Autoconfiguration */
|
||||
use failure::Fail;
|
||||
|
||||
//! Thunderbird's Autoconfiguration implementation
|
||||
use quick_xml;
|
||||
use quick_xml::events::{BytesEnd, BytesStart, BytesText};
|
||||
|
||||
use crate::constants::*;
|
||||
use crate::context::Context;
|
||||
use crate::error::Error;
|
||||
use crate::login_param::LoginParam;
|
||||
|
||||
use super::read_url::read_url;
|
||||
|
||||
#[derive(Debug, Fail)]
|
||||
pub enum Error {
|
||||
#[fail(display = "Invalid email address: {:?}", _0)]
|
||||
InvalidEmailAddress(String),
|
||||
|
||||
#[fail(display = "XML error at position {}", position)]
|
||||
InvalidXml {
|
||||
position: usize,
|
||||
#[cause]
|
||||
error: quick_xml::Error,
|
||||
},
|
||||
|
||||
#[fail(display = "Bad or incomplete autoconfig")]
|
||||
IncompleteAutoconfig(LoginParam),
|
||||
|
||||
#[fail(display = "Failed to get URL {}", _0)]
|
||||
ReadUrlError(#[cause] super::read_url::Error),
|
||||
}
|
||||
|
||||
pub type Result<T> = std::result::Result<T, Error>;
|
||||
|
||||
impl From<super::read_url::Error> for Error {
|
||||
fn from(err: super::read_url::Error) -> Error {
|
||||
Error::ReadUrlError(err)
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
use super::read_autoconf_file;
|
||||
/* ******************************************************************************
|
||||
* Thunderbird's Autoconfigure
|
||||
******************************************************************************/
|
||||
/* documentation: https://developer.mozilla.org/en-US/docs/Mozilla/Thunderbird/Autoconfiguration */
|
||||
struct MozAutoconfigure<'a> {
|
||||
pub in_emailaddr: &'a str,
|
||||
pub in_emaildomain: &'a str,
|
||||
@@ -51,14 +23,13 @@ struct MozAutoconfigure<'a> {
|
||||
pub tag_config: MozConfigTag,
|
||||
}
|
||||
|
||||
#[derive(Debug, PartialEq)]
|
||||
#[derive(PartialEq)]
|
||||
enum MozServer {
|
||||
Undefined,
|
||||
Imap,
|
||||
Smtp,
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
enum MozConfigTag {
|
||||
Undefined,
|
||||
Hostname,
|
||||
@@ -67,14 +38,15 @@ enum MozConfigTag {
|
||||
Username,
|
||||
}
|
||||
|
||||
fn parse_xml(in_emailaddr: &str, xml_raw: &str) -> Result<LoginParam> {
|
||||
pub fn moz_parse_xml(in_emailaddr: &str, xml_raw: &str) -> Result<LoginParam, Error> {
|
||||
let mut reader = quick_xml::Reader::from_str(xml_raw);
|
||||
reader.trim_text(true);
|
||||
|
||||
// Split address into local part and domain part.
|
||||
let p = in_emailaddr
|
||||
.find('@')
|
||||
.ok_or_else(|| Error::InvalidEmailAddress(in_emailaddr.to_string()))?;
|
||||
let p = match in_emailaddr.find('@') {
|
||||
Some(i) => i,
|
||||
None => bail!("Email address {} does not contain @", in_emailaddr),
|
||||
};
|
||||
let (in_emaillocalpart, in_emaildomain) = in_emailaddr.split_at(p);
|
||||
let in_emaildomain = &in_emaildomain[1..];
|
||||
|
||||
@@ -91,22 +63,22 @@ fn parse_xml(in_emailaddr: &str, xml_raw: &str) -> Result<LoginParam> {
|
||||
|
||||
let mut buf = Vec::new();
|
||||
loop {
|
||||
let event = reader
|
||||
.read_event(&mut buf)
|
||||
.map_err(|error| Error::InvalidXml {
|
||||
position: reader.buffer_position(),
|
||||
error,
|
||||
})?;
|
||||
|
||||
match event {
|
||||
quick_xml::events::Event::Start(ref e) => {
|
||||
match reader.read_event(&mut buf) {
|
||||
Ok(quick_xml::events::Event::Start(ref e)) => {
|
||||
moz_autoconfigure_starttag_cb(e, &mut moz_ac, &reader)
|
||||
}
|
||||
quick_xml::events::Event::End(ref e) => moz_autoconfigure_endtag_cb(e, &mut moz_ac),
|
||||
quick_xml::events::Event::Text(ref e) => {
|
||||
Ok(quick_xml::events::Event::End(ref e)) => moz_autoconfigure_endtag_cb(e, &mut moz_ac),
|
||||
Ok(quick_xml::events::Event::Text(ref e)) => {
|
||||
moz_autoconfigure_text_cb(e, &mut moz_ac, &reader)
|
||||
}
|
||||
quick_xml::events::Event::Eof => break,
|
||||
Err(e) => {
|
||||
bail!(
|
||||
"Configure xml: Error at position {}: {:?}",
|
||||
reader.buffer_position(),
|
||||
e
|
||||
);
|
||||
}
|
||||
Ok(quick_xml::events::Event::Eof) => break,
|
||||
_ => (),
|
||||
}
|
||||
buf.clear();
|
||||
@@ -117,27 +89,27 @@ fn parse_xml(in_emailaddr: &str, xml_raw: &str) -> Result<LoginParam> {
|
||||
|| moz_ac.out.send_server.is_empty()
|
||||
|| moz_ac.out.send_port == 0
|
||||
{
|
||||
Err(Error::IncompleteAutoconfig(moz_ac.out))
|
||||
} else {
|
||||
Ok(moz_ac.out)
|
||||
let r = moz_ac.out.to_string();
|
||||
bail!("Bad or incomplete autoconfig: {}", r,);
|
||||
}
|
||||
|
||||
Ok(moz_ac.out)
|
||||
}
|
||||
|
||||
pub fn moz_autoconfigure(
|
||||
context: &Context,
|
||||
url: &str,
|
||||
param_in: &LoginParam,
|
||||
) -> Result<LoginParam> {
|
||||
let xml_raw = read_url(context, url)?;
|
||||
) -> Option<LoginParam> {
|
||||
let xml_raw = read_autoconf_file(context, url)?;
|
||||
|
||||
let res = parse_xml(¶m_in.addr, &xml_raw);
|
||||
if let Err(err) = &res {
|
||||
warn!(
|
||||
context,
|
||||
"Failed to parse Thunderbird autoconfiguration XML: {}", err
|
||||
);
|
||||
match moz_parse_xml(¶m_in.addr, &xml_raw) {
|
||||
Err(err) => {
|
||||
warn!(context, "{}", err);
|
||||
None
|
||||
}
|
||||
Ok(lp) => Some(lp),
|
||||
}
|
||||
res
|
||||
}
|
||||
|
||||
fn moz_autoconfigure_text_cb<B: std::io::BufRead>(
|
||||
@@ -342,7 +314,7 @@ mod tests {
|
||||
</loginPageInfo>
|
||||
</webMail>
|
||||
</clientConfig>";
|
||||
let res = parse_xml("example@outlook.com", xml_raw).expect("XML parsing failed");
|
||||
let res = moz_parse_xml("example@outlook.com", xml_raw).expect("XML parsing failed");
|
||||
assert_eq!(res.mail_server, "outlook.office365.com");
|
||||
assert_eq!(res.mail_port, 993);
|
||||
assert_eq!(res.send_server, "smtp.office365.com");
|
||||
|
||||
@@ -1,43 +1,14 @@
|
||||
//! Outlook's Autodiscover
|
||||
|
||||
use failure::Fail;
|
||||
|
||||
use quick_xml;
|
||||
use quick_xml::events::BytesEnd;
|
||||
|
||||
use crate::constants::*;
|
||||
use crate::context::Context;
|
||||
use crate::error::Error;
|
||||
use crate::login_param::LoginParam;
|
||||
|
||||
use super::read_url::read_url;
|
||||
|
||||
#[derive(Debug, Fail)]
|
||||
pub enum Error {
|
||||
#[fail(display = "XML error at position {}", position)]
|
||||
InvalidXml {
|
||||
position: usize,
|
||||
#[cause]
|
||||
error: quick_xml::Error,
|
||||
},
|
||||
|
||||
#[fail(display = "Bad or incomplete autoconfig")]
|
||||
IncompleteAutoconfig(LoginParam),
|
||||
|
||||
#[fail(display = "Failed to get URL {}", _0)]
|
||||
ReadUrlError(#[cause] super::read_url::Error),
|
||||
|
||||
#[fail(display = "Number of redirection is exceeded")]
|
||||
RedirectionError,
|
||||
}
|
||||
|
||||
pub type Result<T> = std::result::Result<T, Error>;
|
||||
|
||||
impl From<super::read_url::Error> for Error {
|
||||
fn from(err: super::read_url::Error) -> Error {
|
||||
Error::ReadUrlError(err)
|
||||
}
|
||||
}
|
||||
use super::read_autoconf_file;
|
||||
|
||||
/// Outlook's Autodiscover
|
||||
struct OutlookAutodiscover {
|
||||
pub out: LoginParam,
|
||||
pub out_imap_set: bool,
|
||||
@@ -54,7 +25,7 @@ enum ParsingResult {
|
||||
RedirectUrl(String),
|
||||
}
|
||||
|
||||
fn parse_xml(xml_raw: &str) -> Result<ParsingResult> {
|
||||
fn outlk_parse_xml(xml_raw: &str) -> Result<ParsingResult, Error> {
|
||||
let mut outlk_ad = OutlookAutodiscover {
|
||||
out: LoginParam::new(),
|
||||
out_imap_set: false,
|
||||
@@ -74,15 +45,8 @@ fn parse_xml(xml_raw: &str) -> Result<ParsingResult> {
|
||||
let mut current_tag: Option<String> = None;
|
||||
|
||||
loop {
|
||||
let event = reader
|
||||
.read_event(&mut buf)
|
||||
.map_err(|error| Error::InvalidXml {
|
||||
position: reader.buffer_position(),
|
||||
error,
|
||||
})?;
|
||||
|
||||
match event {
|
||||
quick_xml::events::Event::Start(ref e) => {
|
||||
match reader.read_event(&mut buf) {
|
||||
Ok(quick_xml::events::Event::Start(ref e)) => {
|
||||
let tag = String::from_utf8_lossy(e.name()).trim().to_lowercase();
|
||||
|
||||
if tag == "protocol" {
|
||||
@@ -97,11 +61,11 @@ fn parse_xml(xml_raw: &str) -> Result<ParsingResult> {
|
||||
current_tag = Some(tag);
|
||||
}
|
||||
}
|
||||
quick_xml::events::Event::End(ref e) => {
|
||||
Ok(quick_xml::events::Event::End(ref e)) => {
|
||||
outlk_autodiscover_endtag_cb(e, &mut outlk_ad);
|
||||
current_tag = None;
|
||||
}
|
||||
quick_xml::events::Event::Text(ref e) => {
|
||||
Ok(quick_xml::events::Event::Text(ref e)) => {
|
||||
let val = e.unescape_and_decode(&reader).unwrap_or_default();
|
||||
|
||||
if let Some(ref tag) = current_tag {
|
||||
@@ -117,14 +81,21 @@ fn parse_xml(xml_raw: &str) -> Result<ParsingResult> {
|
||||
};
|
||||
}
|
||||
}
|
||||
quick_xml::events::Event::Eof => break,
|
||||
Err(e) => {
|
||||
bail!(
|
||||
"Configure xml: Error at position {}: {:?}",
|
||||
reader.buffer_position(),
|
||||
e
|
||||
);
|
||||
}
|
||||
Ok(quick_xml::events::Event::Eof) => break,
|
||||
_ => (),
|
||||
}
|
||||
buf.clear();
|
||||
}
|
||||
|
||||
// XML redirect via redirecturl
|
||||
let res = if outlk_ad.config_redirecturl.is_none()
|
||||
if outlk_ad.config_redirecturl.is_none()
|
||||
|| outlk_ad.config_redirecturl.as_ref().unwrap().is_empty()
|
||||
{
|
||||
if outlk_ad.out.mail_server.is_empty()
|
||||
@@ -132,34 +103,41 @@ fn parse_xml(xml_raw: &str) -> Result<ParsingResult> {
|
||||
|| outlk_ad.out.send_server.is_empty()
|
||||
|| outlk_ad.out.send_port == 0
|
||||
{
|
||||
return Err(Error::IncompleteAutoconfig(outlk_ad.out));
|
||||
let r = outlk_ad.out.to_string();
|
||||
bail!("Bad or incomplete autoconfig: {}", r,);
|
||||
}
|
||||
ParsingResult::LoginParam(outlk_ad.out)
|
||||
Ok(ParsingResult::LoginParam(outlk_ad.out))
|
||||
} else {
|
||||
ParsingResult::RedirectUrl(outlk_ad.config_redirecturl.unwrap())
|
||||
};
|
||||
Ok(res)
|
||||
Ok(ParsingResult::RedirectUrl(
|
||||
outlk_ad.config_redirecturl.unwrap(),
|
||||
))
|
||||
}
|
||||
}
|
||||
|
||||
pub fn outlk_autodiscover(
|
||||
context: &Context,
|
||||
url: &str,
|
||||
_param_in: &LoginParam,
|
||||
) -> Result<LoginParam> {
|
||||
) -> Option<LoginParam> {
|
||||
let mut url = url.to_string();
|
||||
/* Follow up to 10 xml-redirects (http-redirects are followed in read_url() */
|
||||
/* Follow up to 10 xml-redirects (http-redirects are followed in read_autoconf_file() */
|
||||
for _i in 0..10 {
|
||||
let xml_raw = read_url(context, &url)?;
|
||||
let res = parse_xml(&xml_raw);
|
||||
if let Err(err) = &res {
|
||||
warn!(context, "{}", err);
|
||||
}
|
||||
match res? {
|
||||
ParsingResult::RedirectUrl(redirect_url) => url = redirect_url,
|
||||
ParsingResult::LoginParam(login_param) => return Ok(login_param),
|
||||
if let Some(xml_raw) = read_autoconf_file(context, &url) {
|
||||
match outlk_parse_xml(&xml_raw) {
|
||||
Err(err) => {
|
||||
warn!(context, "{}", err);
|
||||
return None;
|
||||
}
|
||||
Ok(res) => match res {
|
||||
ParsingResult::RedirectUrl(redirect_url) => url = redirect_url,
|
||||
ParsingResult::LoginParam(login_param) => return Some(login_param),
|
||||
},
|
||||
}
|
||||
} else {
|
||||
return None;
|
||||
}
|
||||
}
|
||||
Err(Error::RedirectionError)
|
||||
None
|
||||
}
|
||||
|
||||
fn outlk_autodiscover_endtag_cb(event: &BytesEnd, outlk_ad: &mut OutlookAutodiscover) {
|
||||
@@ -201,7 +179,7 @@ mod tests {
|
||||
|
||||
#[test]
|
||||
fn test_parse_redirect() {
|
||||
let res = parse_xml("
|
||||
let res = outlk_parse_xml("
|
||||
<?xml version=\"1.0\" encoding=\"utf-8\"?>
|
||||
<Autodiscover xmlns=\"http://schemas.microsoft.com/exchange/autodiscover/responseschema/2006\">
|
||||
<Response xmlns=\"http://schemas.microsoft.com/exchange/autodiscover/outlook/responseschema/2006a\">
|
||||
@@ -228,7 +206,7 @@ mod tests {
|
||||
|
||||
#[test]
|
||||
fn test_parse_loginparam() {
|
||||
let res = parse_xml(
|
||||
let res = outlk_parse_xml(
|
||||
"\
|
||||
<?xml version=\"1.0\" encoding=\"utf-8\"?>
|
||||
<Autodiscover xmlns=\"http://schemas.microsoft.com/exchange/autodiscover/responseschema/2006\">
|
||||
|
||||
@@ -1,9 +1,5 @@
|
||||
//! Email accounts autoconfiguration process module
|
||||
|
||||
mod auto_mozilla;
|
||||
mod auto_outlook;
|
||||
mod read_url;
|
||||
|
||||
use percent_encoding::{utf8_percent_encode, NON_ALPHANUMERIC};
|
||||
|
||||
use crate::config::Config;
|
||||
@@ -16,8 +12,10 @@ use crate::login_param::LoginParam;
|
||||
use crate::oauth2::*;
|
||||
use crate::param::Params;
|
||||
|
||||
use auto_mozilla::moz_autoconfigure;
|
||||
mod auto_outlook;
|
||||
use auto_outlook::outlk_autodiscover;
|
||||
mod auto_mozilla;
|
||||
use auto_mozilla::moz_autoconfigure;
|
||||
|
||||
macro_rules! progress {
|
||||
($context:tt, $progress:expr) => {
|
||||
@@ -168,7 +166,7 @@ pub fn JobConfigureImap(context: &Context) {
|
||||
"https://autoconfig.{}/mail/config-v1.1.xml?emailaddress={}",
|
||||
param_domain, param_addr_urlencoded
|
||||
);
|
||||
param_autoconfig = moz_autoconfigure(context, &url, ¶m).ok();
|
||||
param_autoconfig = moz_autoconfigure(context, &url, ¶m);
|
||||
}
|
||||
true
|
||||
}
|
||||
@@ -180,7 +178,7 @@ pub fn JobConfigureImap(context: &Context) {
|
||||
"https://{}/.well-known/autoconfig/mail/config-v1.1.xml?emailaddress={}",
|
||||
param_domain, param_addr_urlencoded
|
||||
);
|
||||
param_autoconfig = moz_autoconfigure(context, &url, ¶m).ok();
|
||||
param_autoconfig = moz_autoconfigure(context, &url, ¶m);
|
||||
}
|
||||
true
|
||||
}
|
||||
@@ -193,7 +191,7 @@ pub fn JobConfigureImap(context: &Context) {
|
||||
"https://{}{}/autodiscover/autodiscover.xml",
|
||||
"", param_domain
|
||||
);
|
||||
param_autoconfig = outlk_autodiscover(context, &url, ¶m).ok();
|
||||
param_autoconfig = outlk_autodiscover(context, &url, ¶m);
|
||||
}
|
||||
true
|
||||
}
|
||||
@@ -204,7 +202,7 @@ pub fn JobConfigureImap(context: &Context) {
|
||||
"https://{}{}/autodiscover/autodiscover.xml",
|
||||
"autodiscover.", param_domain
|
||||
);
|
||||
param_autoconfig = outlk_autodiscover(context, &url, ¶m).ok();
|
||||
param_autoconfig = outlk_autodiscover(context, &url, ¶m);
|
||||
}
|
||||
true
|
||||
}
|
||||
@@ -216,7 +214,7 @@ pub fn JobConfigureImap(context: &Context) {
|
||||
"http://autoconfig.{}/mail/config-v1.1.xml?emailaddress={}",
|
||||
param_domain, param_addr_urlencoded
|
||||
);
|
||||
param_autoconfig = moz_autoconfigure(context, &url, ¶m).ok();
|
||||
param_autoconfig = moz_autoconfigure(context, &url, ¶m);
|
||||
}
|
||||
true
|
||||
}
|
||||
@@ -228,7 +226,7 @@ pub fn JobConfigureImap(context: &Context) {
|
||||
"http://{}/.well-known/autoconfig/mail/config-v1.1.xml",
|
||||
param_domain
|
||||
);
|
||||
param_autoconfig = moz_autoconfigure(context, &url, ¶m).ok();
|
||||
param_autoconfig = moz_autoconfigure(context, &url, ¶m);
|
||||
}
|
||||
true
|
||||
}
|
||||
@@ -238,7 +236,7 @@ pub fn JobConfigureImap(context: &Context) {
|
||||
if param_autoconfig.is_none() {
|
||||
/* always SSL for Thunderbird's database */
|
||||
let url = format!("https://autoconfig.thunderbird.net/v1.1/{}", param_domain);
|
||||
param_autoconfig = moz_autoconfigure(context, &url, ¶m).ok();
|
||||
param_autoconfig = moz_autoconfigure(context, &url, ¶m);
|
||||
}
|
||||
true
|
||||
}
|
||||
@@ -564,6 +562,27 @@ fn try_smtp_one_param(context: &Context, param: &LoginParam) -> Option<bool> {
|
||||
}
|
||||
}
|
||||
|
||||
/*******************************************************************************
|
||||
* Configure a Context
|
||||
******************************************************************************/
|
||||
|
||||
pub fn read_autoconf_file(context: &Context, url: &str) -> Option<String> {
|
||||
info!(context, "Testing {} ...", url);
|
||||
|
||||
match reqwest::Client::new()
|
||||
.get(url)
|
||||
.send()
|
||||
.and_then(|mut res| res.text())
|
||||
{
|
||||
Ok(res) => Some(res),
|
||||
Err(_err) => {
|
||||
info!(context, "Can\'t read file.",);
|
||||
|
||||
None
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
|
||||
|
||||
@@ -1,27 +0,0 @@
|
||||
use crate::context::Context;
|
||||
use failure::Fail;
|
||||
|
||||
#[derive(Debug, Fail)]
|
||||
pub enum Error {
|
||||
#[fail(display = "URL request error")]
|
||||
GetError(#[cause] reqwest::Error),
|
||||
}
|
||||
|
||||
pub type Result<T> = std::result::Result<T, Error>;
|
||||
|
||||
pub fn read_url(context: &Context, url: &str) -> Result<String> {
|
||||
info!(context, "Requesting URL {}", url);
|
||||
|
||||
match reqwest::Client::new()
|
||||
.get(url)
|
||||
.send()
|
||||
.and_then(|mut res| res.text())
|
||||
{
|
||||
Ok(res) => Ok(res),
|
||||
Err(err) => {
|
||||
info!(context, "Can\'t read URL {}", url);
|
||||
|
||||
Err(Error::GetError(err))
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -23,17 +23,15 @@ use crate::stock::StockMessage;
|
||||
const DC_ORIGIN_MIN_CONTACT_LIST: i32 = 0x100;
|
||||
|
||||
/// An object representing a single contact in memory.
|
||||
///
|
||||
/// The contact object is not updated.
|
||||
/// If you want an update, you have to recreate the object.
|
||||
///
|
||||
/// The library makes sure
|
||||
/// only to use names _authorized_ by the contact in `To:` or `Cc:`.
|
||||
/// *Given-names* as "Daddy" or "Honey" are not used there.
|
||||
/// _Given-names _as "Daddy" or "Honey" are not used there.
|
||||
/// For this purpose, internally, two names are tracked -
|
||||
/// authorized name and given name.
|
||||
/// authorized-name and given-name.
|
||||
/// By default, these names are equal, but functions working with contact names
|
||||
/// only affect the given name.
|
||||
#[derive(Debug)]
|
||||
pub struct Contact {
|
||||
/// The contact ID.
|
||||
@@ -108,6 +106,11 @@ impl Default for Origin {
|
||||
}
|
||||
|
||||
impl Origin {
|
||||
/// Contacts that start a new "normal" chat, defaults to off.
|
||||
pub fn is_start_new_chat(self) -> bool {
|
||||
self as i32 >= 0x7FFFFFFF
|
||||
}
|
||||
|
||||
/// Contacts that are verified and known not to be spam.
|
||||
pub fn is_verified(self) -> bool {
|
||||
self as i32 >= 0x100
|
||||
@@ -205,7 +208,7 @@ impl Contact {
|
||||
/// Add a single contact as a result of an _explicit_ user action.
|
||||
///
|
||||
/// We assume, the contact name, if any, is entered by the user and is used "as is" therefore,
|
||||
/// normalize() is *not* called for the name. If the contact is blocked, it is unblocked.
|
||||
/// normalize() is _not_ called for the name. If the contact is blocked, it is unblocked.
|
||||
///
|
||||
/// To add a number of contacts, see `dc_add_address_book()` which is much faster for adding
|
||||
/// a bunch of addresses.
|
||||
@@ -235,7 +238,7 @@ impl Contact {
|
||||
}
|
||||
|
||||
/// Mark all messages sent by the given contact
|
||||
/// as *noticed*. See also dc_marknoticed_chat() and dc_markseen_msgs()
|
||||
/// as _noticed_. See also dc_marknoticed_chat() and dc_markseen_msgs()
|
||||
///
|
||||
/// Calling this function usually results in the event `#DC_EVENT_MSGS_CHANGED`.
|
||||
pub fn mark_noticed(context: &Context, id: u32) {
|
||||
@@ -425,7 +428,7 @@ impl Contact {
|
||||
/// the event `DC_EVENT_CONTACTS_CHANGED` is sent.
|
||||
///
|
||||
/// To add a single contact entered by the user, you should prefer `Contact::create`,
|
||||
/// however, for adding a bunch of addresses, this function is much faster.
|
||||
/// however, for adding a bunch of addresses, this function is _much_ faster.
|
||||
///
|
||||
/// The `addr_book` is a multiline string in the format `Name one\nAddress one\nName two\nAddress two`.
|
||||
///
|
||||
@@ -903,7 +906,7 @@ impl Contact {
|
||||
}
|
||||
|
||||
/// Extracts first name from full name.
|
||||
fn get_first_name(full_name: &str) -> &str {
|
||||
fn get_first_name<'a>(full_name: &'a str) -> &'a str {
|
||||
full_name.splitn(2, ' ').next().unwrap_or_default()
|
||||
}
|
||||
|
||||
@@ -930,21 +933,21 @@ fn set_block_contact(context: &Context, contact_id: u32, new_blocking: bool) {
|
||||
}
|
||||
|
||||
if let Ok(contact) = Contact::load_from_db(context, contact_id) {
|
||||
if contact.blocked != new_blocking
|
||||
&& sql::execute(
|
||||
if contact.blocked != new_blocking {
|
||||
if sql::execute(
|
||||
context,
|
||||
&context.sql,
|
||||
"UPDATE contacts SET blocked=? WHERE id=?;",
|
||||
params![new_blocking as i32, contact_id as i32],
|
||||
)
|
||||
.is_ok()
|
||||
{
|
||||
// also (un)block all chats with _only_ this contact - we do not delete them to allow a
|
||||
// non-destructive blocking->unblocking.
|
||||
// (Maybe, beside normal chats (type=100) we should also block group chats with only this user.
|
||||
// However, I'm not sure about this point; it may be confusing if the user wants to add other people;
|
||||
// this would result in recreating the same group...)
|
||||
if sql::execute(
|
||||
{
|
||||
// also (un)block all chats with _only_ this contact - we do not delete them to allow a
|
||||
// non-destructive blocking->unblocking.
|
||||
// (Maybe, beside normal chats (type=100) we should also block group chats with only this user.
|
||||
// However, I'm not sure about this point; it may be confusing if the user wants to add other people;
|
||||
// this would result in recreating the same group...)
|
||||
if sql::execute(
|
||||
context,
|
||||
&context.sql,
|
||||
"UPDATE chats SET blocked=? WHERE type=? AND id IN (SELECT chat_id FROM chats_contacts WHERE contact_id=?);",
|
||||
@@ -953,6 +956,7 @@ fn set_block_contact(context: &Context, contact_id: u32, new_blocking: bool) {
|
||||
Contact::mark_noticed(context, contact_id);
|
||||
context.call_cb(Event::ContactsChanged(None));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,5 +1,3 @@
|
||||
//! Contacts module
|
||||
|
||||
use std::collections::HashMap;
|
||||
use std::ffi::OsString;
|
||||
use std::path::{Path, PathBuf};
|
||||
|
||||
@@ -47,10 +47,10 @@ pub fn dc_receive_imf(
|
||||
server_uid,
|
||||
);
|
||||
|
||||
if std::env::var(crate::DCC_MIME_DEBUG).is_ok() {
|
||||
info!(context, "dc_receive_imf: incoming message mime-body:");
|
||||
println!("{}", String::from_utf8_lossy(imf_raw));
|
||||
}
|
||||
// Parse the imf to mailimf_message. normally, this is done by mailimf_message_parse(),
|
||||
// however, as we also need the MIME data,
|
||||
// we use mailmime_parse() through dc_mimeparser (both call mailimf_struct_multiple_parse()
|
||||
// somewhen, I did not found out anything that speaks against this approach yet)
|
||||
|
||||
let mime_parser = MimeParser::from_bytes(context, imf_raw);
|
||||
let mut mime_parser = if let Err(err) = mime_parser {
|
||||
@@ -172,7 +172,7 @@ pub fn dc_receive_imf(
|
||||
// client that relies in the SMTP server to generate one.
|
||||
// true eg. for the Webmailer used in all-inkl-KAS
|
||||
match dc_create_incoming_rfc724_mid(sent_timestamp, from_id, &to_ids) {
|
||||
Some(x) => x,
|
||||
Some(x) => x.to_string(),
|
||||
None => {
|
||||
error!(context, "can not create incoming rfc724_mid");
|
||||
cleanup(
|
||||
@@ -210,7 +210,7 @@ pub fn dc_receive_imf(
|
||||
&mut created_db_entries,
|
||||
&mut create_event_to_send,
|
||||
) {
|
||||
warn!(context, "add_parts error: {:?}", err);
|
||||
warn!(context, "{}", err);
|
||||
|
||||
cleanup(
|
||||
context,
|
||||
@@ -407,25 +407,27 @@ fn add_parts(
|
||||
// try to create a group
|
||||
// (groups appear automatically only if the _sender_ is known, see core issue #54)
|
||||
|
||||
let create_blocked =
|
||||
if 0 != test_normal_chat_id && test_normal_chat_id_blocked == Blocked::Not {
|
||||
Blocked::Not
|
||||
} else {
|
||||
Blocked::Deaddrop
|
||||
};
|
||||
let create_blocked = if 0 != test_normal_chat_id
|
||||
&& test_normal_chat_id_blocked == Blocked::Not
|
||||
|| incoming_origin.is_start_new_chat()
|
||||
{
|
||||
Blocked::Not
|
||||
} else {
|
||||
Blocked::Deaddrop
|
||||
};
|
||||
|
||||
let (new_chat_id, new_chat_id_blocked) = create_or_lookup_group(
|
||||
create_or_lookup_group(
|
||||
context,
|
||||
&mut mime_parser,
|
||||
allow_creation,
|
||||
create_blocked,
|
||||
*from_id,
|
||||
to_ids,
|
||||
chat_id,
|
||||
&mut chat_id_blocked,
|
||||
)?;
|
||||
*chat_id = new_chat_id;
|
||||
chat_id_blocked = new_chat_id_blocked;
|
||||
if *chat_id != 0 && chat_id_blocked != Blocked::Not && create_blocked == Blocked::Not {
|
||||
chat::unblock(context, new_chat_id);
|
||||
if 0 != *chat_id && Blocked::Not != chat_id_blocked && create_blocked == Blocked::Not {
|
||||
chat::unblock(context, *chat_id);
|
||||
chat_id_blocked = Blocked::Not;
|
||||
}
|
||||
}
|
||||
@@ -440,7 +442,7 @@ fn add_parts(
|
||||
|
||||
if *chat_id == 0 {
|
||||
// try to create a normal chat
|
||||
let create_blocked = if *from_id == *to_id {
|
||||
let create_blocked = if incoming_origin.is_start_new_chat() || *from_id == *to_id {
|
||||
Blocked::Not
|
||||
} else {
|
||||
Blocked::Deaddrop
|
||||
@@ -507,19 +509,18 @@ fn add_parts(
|
||||
if !to_ids.is_empty() {
|
||||
*to_id = to_ids[0];
|
||||
if *chat_id == 0 {
|
||||
let (new_chat_id, new_chat_id_blocked) = create_or_lookup_group(
|
||||
create_or_lookup_group(
|
||||
context,
|
||||
&mut mime_parser,
|
||||
allow_creation,
|
||||
Blocked::Not,
|
||||
*from_id,
|
||||
to_ids,
|
||||
chat_id,
|
||||
&mut chat_id_blocked,
|
||||
)?;
|
||||
*chat_id = new_chat_id;
|
||||
chat_id_blocked = new_chat_id_blocked;
|
||||
// automatically unblock chat when the user sends a message
|
||||
if *chat_id != 0 && chat_id_blocked != Blocked::Not {
|
||||
chat::unblock(context, new_chat_id);
|
||||
if 0 != *chat_id && Blocked::Not != chat_id_blocked {
|
||||
chat::unblock(context, *chat_id);
|
||||
chat_id_blocked = Blocked::Not;
|
||||
}
|
||||
}
|
||||
@@ -544,18 +545,20 @@ fn add_parts(
|
||||
}
|
||||
}
|
||||
}
|
||||
if *chat_id == 0 && to_ids.is_empty() && to_self {
|
||||
// from_id==to_id==DC_CONTACT_ID_SELF - this is a self-sent messages,
|
||||
// maybe an Autocrypt Setup Messag
|
||||
let (id, bl) =
|
||||
chat::create_or_lookup_by_contact_id(context, DC_CONTACT_ID_SELF, Blocked::Not)
|
||||
.unwrap_or_default();
|
||||
*chat_id = id;
|
||||
chat_id_blocked = bl;
|
||||
if *chat_id == 0 {
|
||||
if to_ids.is_empty() && to_self {
|
||||
// from_id==to_id==DC_CONTACT_ID_SELF - this is a self-sent messages,
|
||||
// maybe an Autocrypt Setup Messag
|
||||
let (id, bl) =
|
||||
chat::create_or_lookup_by_contact_id(context, DC_CONTACT_ID_SELF, Blocked::Not)
|
||||
.unwrap_or_default();
|
||||
*chat_id = id;
|
||||
chat_id_blocked = bl;
|
||||
|
||||
if 0 != *chat_id && Blocked::Not != chat_id_blocked {
|
||||
chat::unblock(context, *chat_id);
|
||||
chat_id_blocked = Blocked::Not;
|
||||
if 0 != *chat_id && Blocked::Not != chat_id_blocked {
|
||||
chat::unblock(context, *chat_id);
|
||||
chat_id_blocked = Blocked::Not;
|
||||
}
|
||||
}
|
||||
}
|
||||
if *chat_id == 0 {
|
||||
@@ -626,7 +629,7 @@ fn add_parts(
|
||||
.subject
|
||||
.as_ref()
|
||||
.map(|s| s.to_string())
|
||||
.unwrap_or_else(|| "".into());
|
||||
.unwrap_or("".into());
|
||||
txt_raw = Some(format!("{}\n\n{}", subject, msg_raw));
|
||||
}
|
||||
if mime_parser.is_system_message != SystemMessage::Unknown {
|
||||
@@ -702,7 +705,7 @@ fn save_locations(
|
||||
hidden: i32,
|
||||
) {
|
||||
if chat_id <= DC_CHAT_ID_LAST_SPECIAL {
|
||||
return;
|
||||
return ();
|
||||
}
|
||||
let mut location_id_written = false;
|
||||
let mut send_event = false;
|
||||
@@ -711,12 +714,11 @@ fn save_locations(
|
||||
let locations = &mime_parser.message_kml.as_ref().unwrap().locations;
|
||||
let newest_location_id =
|
||||
location::save(context, chat_id, from_id, locations, true).unwrap_or_default();
|
||||
if 0 != newest_location_id
|
||||
&& 0 == hidden
|
||||
&& location::set_msg_location_id(context, insert_msg_id, newest_location_id).is_ok()
|
||||
{
|
||||
location_id_written = true;
|
||||
send_event = true;
|
||||
if 0 != newest_location_id && 0 == hidden {
|
||||
if location::set_msg_location_id(context, insert_msg_id, newest_location_id).is_ok() {
|
||||
location_id_written = true;
|
||||
send_event = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -770,8 +772,10 @@ fn calc_timestamps(
|
||||
params![chat_id as i32, from_id as i32, *sort_timestamp],
|
||||
);
|
||||
if let Some(last_msg_time) = last_msg_time {
|
||||
if last_msg_time > 0 && *sort_timestamp <= last_msg_time {
|
||||
*sort_timestamp = last_msg_time + 1;
|
||||
if last_msg_time > 0 {
|
||||
if *sort_timestamp <= last_msg_time {
|
||||
*sort_timestamp = last_msg_time + 1;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -790,7 +794,7 @@ fn calc_timestamps(
|
||||
/// - is there a group with the same recipients? if so, use this (if there are multiple, use the most recent one)
|
||||
/// - create an ad-hoc group based on the recipient list
|
||||
///
|
||||
/// on success the function returns the found/created (chat_id, chat_blocked) tuple .
|
||||
/// So when the function returns, the caller has the group id matching the current state of the group.
|
||||
#[allow(non_snake_case)]
|
||||
fn create_or_lookup_group(
|
||||
context: &Context,
|
||||
@@ -798,9 +802,14 @@ fn create_or_lookup_group(
|
||||
allow_creation: i32,
|
||||
create_blocked: Blocked,
|
||||
from_id: u32,
|
||||
to_ids: &[u32],
|
||||
) -> Result<(u32, Blocked)> {
|
||||
to_ids: &mut Vec<u32>,
|
||||
ret_chat_id: *mut u32,
|
||||
ret_chat_id_blocked: &mut Blocked,
|
||||
) -> Result<()> {
|
||||
let group_explicitly_left: bool;
|
||||
let mut chat_id = 0;
|
||||
let mut chat_id_blocked = Blocked::Not;
|
||||
let mut grpid = "".to_string();
|
||||
let mut grpname = None;
|
||||
let to_ids_cnt = to_ids.len();
|
||||
let mut recreate_member_list = 0;
|
||||
@@ -811,13 +820,26 @@ fn create_or_lookup_group(
|
||||
let mut X_MrGrpImageChanged = "".to_string();
|
||||
let mut better_msg: String = From::from("");
|
||||
|
||||
let cleanup = |ret_chat_id: *mut u32,
|
||||
ret_chat_id_blocked: &mut Blocked,
|
||||
chat_id: u32,
|
||||
chat_id_blocked: Blocked| {
|
||||
if !ret_chat_id.is_null() {
|
||||
unsafe { *ret_chat_id = chat_id };
|
||||
}
|
||||
*ret_chat_id_blocked = if 0 != chat_id {
|
||||
chat_id_blocked
|
||||
} else {
|
||||
Blocked::Not
|
||||
};
|
||||
};
|
||||
|
||||
if mime_parser.is_system_message == SystemMessage::LocationStreamingEnabled {
|
||||
better_msg =
|
||||
context.stock_system_msg(StockMessage::MsgLocationEnabled, "", "", from_id as u32)
|
||||
}
|
||||
set_better_msg(mime_parser, &better_msg);
|
||||
|
||||
let mut grpid = "".to_string();
|
||||
if let Some(optional_field) = mime_parser.lookup_field("Chat-Group-ID") {
|
||||
grpid = optional_field.clone();
|
||||
}
|
||||
@@ -826,26 +848,33 @@ fn create_or_lookup_group(
|
||||
if let Some(value) = mime_parser.lookup_field("Message-ID") {
|
||||
if let Some(extracted_grpid) = dc_extract_grpid_from_rfc724_mid(&value) {
|
||||
grpid = extracted_grpid.to_string();
|
||||
} else {
|
||||
grpid = "".to_string();
|
||||
}
|
||||
}
|
||||
if grpid.is_empty() {
|
||||
if let Some(extracted_grpid) = get_grpid_from_list(mime_parser, "In-Reply-To") {
|
||||
grpid = extracted_grpid;
|
||||
} else if let Some(extracted_grpid) = get_grpid_from_list(mime_parser, "References") {
|
||||
grpid = extracted_grpid;
|
||||
} else {
|
||||
return create_or_lookup_adhoc_group(
|
||||
context,
|
||||
mime_parser,
|
||||
allow_creation,
|
||||
create_blocked,
|
||||
from_id,
|
||||
to_ids,
|
||||
)
|
||||
.map_err(|err| {
|
||||
info!(context, "could not create adhoc-group: {:?}", err);
|
||||
err
|
||||
});
|
||||
if let Some(value) = mime_parser.lookup_field("In-Reply-To") {
|
||||
grpid = value.clone();
|
||||
}
|
||||
if grpid.is_empty() {
|
||||
if let Some(value) = mime_parser.lookup_field("References") {
|
||||
grpid = value.clone();
|
||||
}
|
||||
|
||||
if grpid.is_empty() {
|
||||
create_or_lookup_adhoc_group(
|
||||
context,
|
||||
mime_parser,
|
||||
allow_creation,
|
||||
create_blocked,
|
||||
from_id,
|
||||
to_ids,
|
||||
&mut chat_id,
|
||||
&mut chat_id_blocked,
|
||||
)?;
|
||||
cleanup(ret_chat_id, ret_chat_id_blocked, chat_id, chat_id_blocked);
|
||||
return Ok(());
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -887,11 +916,11 @@ fn create_or_lookup_group(
|
||||
)
|
||||
} else {
|
||||
let field = mime_parser.lookup_field("Chat-Group-Name-Changed");
|
||||
if let Some(field) = field {
|
||||
if field.is_some() {
|
||||
X_MrGrpNameChanged = 1;
|
||||
better_msg = context.stock_system_msg(
|
||||
StockMessage::MsgGrpName,
|
||||
field,
|
||||
&field.unwrap(),
|
||||
if let Some(ref name) = grpname {
|
||||
name
|
||||
} else {
|
||||
@@ -901,22 +930,23 @@ fn create_or_lookup_group(
|
||||
);
|
||||
|
||||
mime_parser.is_system_message = SystemMessage::GroupNameChanged;
|
||||
} else if let Some(optional_field) =
|
||||
mime_parser.lookup_field("Chat-Group-Image").cloned()
|
||||
{
|
||||
// fld_value is a pointer somewhere into mime_parser, must not be freed
|
||||
X_MrGrpImageChanged = optional_field;
|
||||
mime_parser.is_system_message = SystemMessage::GroupImageChanged;
|
||||
better_msg = context.stock_system_msg(
|
||||
if X_MrGrpImageChanged == "0" {
|
||||
StockMessage::MsgGrpImgDeleted
|
||||
} else {
|
||||
StockMessage::MsgGrpImgChanged
|
||||
},
|
||||
"",
|
||||
"",
|
||||
from_id as u32,
|
||||
)
|
||||
} else {
|
||||
if let Some(optional_field) = mime_parser.lookup_field("Chat-Group-Image").cloned()
|
||||
{
|
||||
// fld_value is a pointer somewhere into mime_parser, must not be freed
|
||||
X_MrGrpImageChanged = optional_field;
|
||||
mime_parser.is_system_message = SystemMessage::GroupImageChanged;
|
||||
better_msg = context.stock_system_msg(
|
||||
if X_MrGrpImageChanged == "0" {
|
||||
StockMessage::MsgGrpImgDeleted
|
||||
} else {
|
||||
StockMessage::MsgGrpImgChanged
|
||||
},
|
||||
"",
|
||||
"",
|
||||
from_id as u32,
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -934,19 +964,20 @@ fn create_or_lookup_group(
|
||||
mime_parser.repl_msg_by_error(s);
|
||||
}
|
||||
}
|
||||
// check if the sender is a member of the existing group -
|
||||
// if not, we'll recreate the group list
|
||||
if !chat::is_contact_in_chat(context, chat_id, from_id as u32) {
|
||||
recreate_member_list = 1;
|
||||
}
|
||||
}
|
||||
|
||||
// check if the sender is a member of the existing group -
|
||||
// if not, we'll recreate the group list
|
||||
if chat_id != 0 && !chat::is_contact_in_chat(context, chat_id, from_id as u32) {
|
||||
recreate_member_list = 1;
|
||||
}
|
||||
|
||||
// check if the group does not exist but should be created
|
||||
let group_explicitly_left = chat::is_group_explicitly_left(context, &grpid).unwrap_or_default();
|
||||
group_explicitly_left = chat::is_group_explicitly_left(context, &grpid).unwrap_or_default();
|
||||
|
||||
let self_addr = context
|
||||
.get_config(Config::ConfiguredAddr)
|
||||
.unwrap_or_default();
|
||||
|
||||
if chat_id == 0
|
||||
&& !mime_parser.is_mailinglist_message()
|
||||
&& !grpid.is_empty()
|
||||
@@ -957,8 +988,10 @@ fn create_or_lookup_group(
|
||||
&& (!group_explicitly_left
|
||||
|| X_MrAddToGrp.is_some() && addr_cmp(&self_addr, X_MrAddToGrp.as_ref().unwrap()))
|
||||
{
|
||||
// group does not exist but should be created
|
||||
let create_verified = if mime_parser.lookup_field("Chat-Verified").is_some() {
|
||||
let mut create_verified = VerifiedStatus::Unverified;
|
||||
if mime_parser.lookup_field("Chat-Verified").is_some() {
|
||||
create_verified = VerifiedStatus::Verified;
|
||||
|
||||
if let Err(err) =
|
||||
check_verified_properties(context, mime_parser, from_id as u32, to_ids)
|
||||
{
|
||||
@@ -966,16 +999,11 @@ fn create_or_lookup_group(
|
||||
let s = format!("{}. See 'Info' for more details", err);
|
||||
mime_parser.repl_msg_by_error(&s);
|
||||
}
|
||||
VerifiedStatus::Verified
|
||||
} else {
|
||||
VerifiedStatus::Unverified
|
||||
};
|
||||
|
||||
if allow_creation == 0 {
|
||||
info!(context, "creating group forbidden by caller");
|
||||
return Ok((0, Blocked::Not));
|
||||
}
|
||||
|
||||
if 0 == allow_creation {
|
||||
cleanup(ret_chat_id, ret_chat_id_blocked, chat_id, chat_id_blocked);
|
||||
return Ok(());
|
||||
}
|
||||
chat_id = create_group_record(
|
||||
context,
|
||||
&grpid,
|
||||
@@ -989,8 +1017,9 @@ fn create_or_lookup_group(
|
||||
|
||||
// again, check chat_id
|
||||
if chat_id <= DC_CHAT_ID_LAST_SPECIAL {
|
||||
return if group_explicitly_left {
|
||||
Ok((DC_CHAT_ID_TRASH, chat_id_blocked))
|
||||
chat_id = 0;
|
||||
if group_explicitly_left {
|
||||
chat_id = DC_CHAT_ID_TRASH;
|
||||
} else {
|
||||
create_or_lookup_adhoc_group(
|
||||
context,
|
||||
@@ -999,12 +1028,12 @@ fn create_or_lookup_group(
|
||||
create_blocked,
|
||||
from_id,
|
||||
to_ids,
|
||||
)
|
||||
.map_err(|err| {
|
||||
warn!(context, "failed to create ad-hoc group: {:?}", err);
|
||||
err
|
||||
})
|
||||
};
|
||||
&mut chat_id,
|
||||
&mut chat_id_blocked,
|
||||
)?;
|
||||
}
|
||||
cleanup(ret_chat_id, ret_chat_id_blocked, chat_id, chat_id_blocked);
|
||||
return Ok(());
|
||||
}
|
||||
|
||||
// execute group commands
|
||||
@@ -1086,12 +1115,13 @@ fn create_or_lookup_group(
|
||||
if skip.is_none() || !addr_cmp(&self_addr, skip.unwrap()) {
|
||||
chat::add_to_chat_contacts_table(context, chat_id, DC_CONTACT_ID_SELF);
|
||||
}
|
||||
if from_id > DC_CHAT_ID_LAST_SPECIAL
|
||||
&& !Contact::addr_equals_contact(context, &self_addr, from_id as u32)
|
||||
&& (skip.is_none()
|
||||
|| !Contact::addr_equals_contact(context, skip.unwrap(), from_id as u32))
|
||||
{
|
||||
chat::add_to_chat_contacts_table(context, chat_id, from_id as u32);
|
||||
if from_id > DC_CHAT_ID_LAST_SPECIAL {
|
||||
if !Contact::addr_equals_contact(context, &self_addr, from_id as u32)
|
||||
&& (skip.is_none()
|
||||
|| !Contact::addr_equals_contact(context, skip.unwrap(), from_id as u32))
|
||||
{
|
||||
chat::add_to_chat_contacts_table(context, chat_id, from_id as u32);
|
||||
}
|
||||
}
|
||||
for &to_id in to_ids.iter() {
|
||||
if !Contact::addr_equals_contact(context, &self_addr, to_id)
|
||||
@@ -1109,78 +1139,74 @@ fn create_or_lookup_group(
|
||||
}
|
||||
|
||||
// check the number of receivers -
|
||||
// the only critical situation is if the user hits "Reply" instead
|
||||
// of "Reply all" in a non-messenger-client */
|
||||
if to_ids_cnt == 1
|
||||
&& !mime_parser.is_send_by_messenger
|
||||
&& chat::get_chat_contact_cnt(context, chat_id) > 3
|
||||
{
|
||||
// to_ids_cnt==1 may be "From: A, To: B, SELF" as SELF is not counted in to_ids_cnt.
|
||||
// So everything up to 3 is no error.
|
||||
create_or_lookup_adhoc_group(
|
||||
context,
|
||||
mime_parser,
|
||||
allow_creation,
|
||||
create_blocked,
|
||||
from_id,
|
||||
to_ids,
|
||||
)
|
||||
.map_err(|err| {
|
||||
warn!(context, "could not create ad-hoc group: {:?}", err);
|
||||
err
|
||||
})?;
|
||||
}
|
||||
Ok((chat_id, chat_id_blocked))
|
||||
}
|
||||
|
||||
/// try extract a grpid from a message-id list header value
|
||||
fn get_grpid_from_list(mime_parser: &MimeParser, header_key: &str) -> Option<String> {
|
||||
if let Some(value) = mime_parser.lookup_field(header_key) {
|
||||
for part in value.split(',').map(str::trim) {
|
||||
if !part.is_empty() {
|
||||
if let Some(extracted_grpid) = dc_extract_grpid_from_rfc724_mid(part) {
|
||||
return Some(extracted_grpid.to_string());
|
||||
}
|
||||
}
|
||||
// the only critical situation is if the user hits "Reply" instead of "Reply all" in a non-messenger-client */
|
||||
if to_ids_cnt == 1 && !mime_parser.is_send_by_messenger {
|
||||
let is_contact_cnt = chat::get_chat_contact_cnt(context, chat_id);
|
||||
if is_contact_cnt > 3 {
|
||||
// to_ids_cnt==1 may be "From: A, To: B, SELF" as SELF is not counted in to_ids_cnt.
|
||||
// So everything up to 3 is no error.
|
||||
chat_id = 0;
|
||||
create_or_lookup_adhoc_group(
|
||||
context,
|
||||
mime_parser,
|
||||
allow_creation,
|
||||
create_blocked,
|
||||
from_id,
|
||||
to_ids,
|
||||
&mut chat_id,
|
||||
&mut chat_id_blocked,
|
||||
)?;
|
||||
}
|
||||
}
|
||||
None
|
||||
|
||||
cleanup(ret_chat_id, ret_chat_id_blocked, chat_id, chat_id_blocked);
|
||||
return Ok(());
|
||||
}
|
||||
|
||||
/// Handle groups for received messages, return chat_id/Blocked status on success
|
||||
/// Handle groups for received messages
|
||||
fn create_or_lookup_adhoc_group(
|
||||
context: &Context,
|
||||
mime_parser: &MimeParser,
|
||||
allow_creation: i32,
|
||||
create_blocked: Blocked,
|
||||
from_id: u32,
|
||||
to_ids: &[u32],
|
||||
) -> Result<(u32, Blocked)> {
|
||||
// if we're here, no grpid was found, check if there is an existing
|
||||
// ad-hoc group matching the to-list or if we should and can create one
|
||||
// (we do not want to heuristically look at the likely mangled Subject)
|
||||
to_ids: &mut Vec<u32>,
|
||||
ret_chat_id: *mut u32,
|
||||
ret_chat_id_blocked: &mut Blocked,
|
||||
) -> Result<()> {
|
||||
// if we're here, no grpid was found, check there is an existing ad-hoc
|
||||
// group matching the to-list or if we can create one
|
||||
let mut chat_id = 0;
|
||||
let mut chat_id_blocked = Blocked::Not;
|
||||
|
||||
if mime_parser.is_mailinglist_message() {
|
||||
// XXX we could parse List-* headers and actually create and
|
||||
// manage a mailing list group, eventually
|
||||
info!(
|
||||
context,
|
||||
"not creating ad-hoc group for mailing list message"
|
||||
);
|
||||
return Ok((0, Blocked::Not));
|
||||
let cleanup = |ret_chat_id: *mut u32,
|
||||
ret_chat_id_blocked: &mut Blocked,
|
||||
chat_id: u32,
|
||||
chat_id_blocked: Blocked| {
|
||||
if !ret_chat_id.is_null() {
|
||||
unsafe { *ret_chat_id = chat_id };
|
||||
}
|
||||
*ret_chat_id_blocked = chat_id_blocked;
|
||||
};
|
||||
|
||||
// build member list from the given ids
|
||||
if to_ids.is_empty() || mime_parser.is_mailinglist_message() {
|
||||
// too few contacts or a mailinglist
|
||||
cleanup(ret_chat_id, ret_chat_id_blocked, chat_id, chat_id_blocked);
|
||||
return Ok(());
|
||||
}
|
||||
|
||||
let mut member_ids = to_ids.to_vec();
|
||||
let mut member_ids = to_ids.clone();
|
||||
if !member_ids.contains(&from_id) {
|
||||
member_ids.push(from_id);
|
||||
}
|
||||
if !member_ids.contains(&DC_CONTACT_ID_SELF) {
|
||||
member_ids.push(DC_CONTACT_ID_SELF);
|
||||
}
|
||||
|
||||
if member_ids.len() < 3 {
|
||||
info!(context, "not creating ad-hoc group: too few contacts");
|
||||
return Ok((0, Blocked::Not));
|
||||
// too few contacts given
|
||||
cleanup(ret_chat_id, ret_chat_id_blocked, chat_id, chat_id_blocked);
|
||||
return Ok(());
|
||||
}
|
||||
|
||||
let chat_ids = search_chat_ids_by_contact_ids(context, &member_ids)?;
|
||||
@@ -1199,16 +1225,18 @@ fn create_or_lookup_adhoc_group(
|
||||
);
|
||||
|
||||
if let Ok((id, id_blocked)) = res {
|
||||
chat_id = id as u32;
|
||||
chat_id_blocked = id_blocked;
|
||||
/* success, chat found */
|
||||
return Ok((id as u32, id_blocked));
|
||||
cleanup(ret_chat_id, ret_chat_id_blocked, chat_id, chat_id_blocked);
|
||||
return Ok(());
|
||||
}
|
||||
}
|
||||
|
||||
if allow_creation == 0 {
|
||||
info!(context, "creating ad-hoc group prevented from caller");
|
||||
return Ok((0, Blocked::Not));
|
||||
if 0 == allow_creation {
|
||||
cleanup(ret_chat_id, ret_chat_id_blocked, chat_id, chat_id_blocked);
|
||||
return Ok(());
|
||||
}
|
||||
|
||||
// we do not check if the message is a reply to another group, this may result in
|
||||
// chats with unclear member list. instead we create a new group in the following lines ...
|
||||
|
||||
@@ -1216,12 +1244,10 @@ fn create_or_lookup_adhoc_group(
|
||||
// - there is no need to check if this group exists; otherwise we would have caught it above
|
||||
let grpid = create_adhoc_grp_id(context, &member_ids);
|
||||
if grpid.is_empty() {
|
||||
warn!(
|
||||
context,
|
||||
"failed to create ad-hoc grpid for {:?}", member_ids
|
||||
);
|
||||
return Ok((0, Blocked::Not));
|
||||
cleanup(ret_chat_id, ret_chat_id_blocked, chat_id, chat_id_blocked);
|
||||
return Ok(());
|
||||
}
|
||||
|
||||
// use subject as initial chat name
|
||||
let grpname = if let Some(subject) = mime_parser.subject.as_ref().filter(|s| !s.is_empty()) {
|
||||
subject.to_string()
|
||||
@@ -1230,20 +1256,22 @@ fn create_or_lookup_adhoc_group(
|
||||
};
|
||||
|
||||
// create group record
|
||||
let new_chat_id = create_group_record(
|
||||
chat_id = create_group_record(
|
||||
context,
|
||||
&grpid,
|
||||
grpname,
|
||||
create_blocked,
|
||||
VerifiedStatus::Unverified,
|
||||
);
|
||||
chat_id_blocked = create_blocked;
|
||||
for &member_id in &member_ids {
|
||||
chat::add_to_chat_contacts_table(context, new_chat_id, member_id);
|
||||
chat::add_to_chat_contacts_table(context, chat_id, member_id);
|
||||
}
|
||||
|
||||
context.call_cb(Event::ChatModified(new_chat_id));
|
||||
context.call_cb(Event::ChatModified(chat_id));
|
||||
|
||||
Ok((new_chat_id, create_blocked))
|
||||
cleanup(ret_chat_id, ret_chat_id_blocked, chat_id, chat_id_blocked);
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn create_group_record(
|
||||
@@ -1324,7 +1352,7 @@ fn hex_hash(s: impl AsRef<str>) -> String {
|
||||
#[allow(non_snake_case)]
|
||||
fn search_chat_ids_by_contact_ids(
|
||||
context: &Context,
|
||||
unsorted_contact_ids: &[u32],
|
||||
unsorted_contact_ids: &Vec<u32>,
|
||||
) -> Result<Vec<u32>> {
|
||||
/* searches chat_id's by the given contact IDs, may return zero, one or more chat_id's */
|
||||
let mut contact_ids = Vec::with_capacity(23);
|
||||
@@ -1480,7 +1508,7 @@ fn check_verified_properties(
|
||||
|
||||
fn set_better_msg(mime_parser: &mut MimeParser, better_msg: impl AsRef<str>) {
|
||||
let msg = better_msg.as_ref();
|
||||
if !msg.is_empty() && !mime_parser.parts.is_empty() {
|
||||
if msg.len() > 0 && !mime_parser.parts.is_empty() {
|
||||
let part = &mut mime_parser.parts[0];
|
||||
if part.typ == Viewtype::Text {
|
||||
part.msg = msg.to_string();
|
||||
@@ -1507,12 +1535,12 @@ fn dc_is_reply_to_known_message(context: &Context, mime_parser: &MimeParser) ->
|
||||
0
|
||||
}
|
||||
|
||||
fn is_known_rfc724_mid_in_list(context: &Context, mid_list: &str) -> bool {
|
||||
fn is_known_rfc724_mid_in_list(context: &Context, mid_list: &String) -> bool {
|
||||
if mid_list.is_empty() {
|
||||
return false;
|
||||
}
|
||||
|
||||
if let Ok(ids) = mailparse::addrparse(mid_list) {
|
||||
if let Ok(ids) = mailparse::addrparse(mid_list.as_str()) {
|
||||
for id in ids.iter() {
|
||||
if is_known_rfc724_mid(context, id) {
|
||||
return true;
|
||||
@@ -1560,8 +1588,8 @@ fn dc_is_reply_to_messenger_message(context: &Context, mime_parser: &MimeParser)
|
||||
0
|
||||
}
|
||||
|
||||
fn is_msgrmsg_rfc724_mid_in_list(context: &Context, mid_list: &str) -> bool {
|
||||
if let Ok(ids) = mailparse::addrparse(mid_list) {
|
||||
fn is_msgrmsg_rfc724_mid_in_list(context: &Context, mid_list: &String) -> bool {
|
||||
if let Ok(ids) = mailparse::addrparse(mid_list.as_str()) {
|
||||
for id in ids.iter() {
|
||||
if is_msgrmsg_rfc724_mid(context, id) {
|
||||
return true;
|
||||
@@ -1633,7 +1661,7 @@ fn dc_add_or_lookup_contacts_by_address_list(
|
||||
fn add_or_lookup_contact_by_addr(
|
||||
context: &Context,
|
||||
display_name: &Option<String>,
|
||||
addr: &str,
|
||||
addr: &String,
|
||||
origin: Origin,
|
||||
ids: &mut Vec<u32>,
|
||||
check_self: &mut bool,
|
||||
@@ -1670,7 +1698,6 @@ fn add_or_lookup_contact_by_addr(
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
use crate::test_utils::dummy_context;
|
||||
|
||||
#[test]
|
||||
fn test_hex_hash() {
|
||||
@@ -1679,34 +1706,4 @@ mod tests {
|
||||
let res = hex_hash(data);
|
||||
assert_eq!(res, "b94d27b9934d3e08");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_grpid_simple() {
|
||||
let context = dummy_context();
|
||||
let raw = b"From: hello\n\
|
||||
Subject: outer-subject\n\
|
||||
In-Reply-To: <lqkjwelq123@123123>\n\
|
||||
References: <Gr.HcxyMARjyJy.9-uvzWPTLtV@nauta.cu>\n\
|
||||
\n\
|
||||
hello\x00";
|
||||
let mimeparser = MimeParser::from_bytes(&context.ctx, &raw[..]).unwrap();
|
||||
assert_eq!(get_grpid_from_list(&mimeparser, "In-Reply-To"), None);
|
||||
let grpid = Some("HcxyMARjyJy".to_string());
|
||||
assert_eq!(get_grpid_from_list(&mimeparser, "References"), grpid);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_grpid_from_multiple() {
|
||||
let context = dummy_context();
|
||||
let raw = b"From: hello\n\
|
||||
Subject: outer-subject\n\
|
||||
In-Reply-To: <Gr.HcxyMARjyJy.9-qweqwe@asd.net>\n\
|
||||
References: <qweqweqwe>, <Gr.HcxyMARjyJy.9-uvzWPTLtV@nau.ca>\n\
|
||||
\n\
|
||||
hello\x00";
|
||||
let mimeparser = MimeParser::from_bytes(&context.ctx, &raw[..]).unwrap();
|
||||
let grpid = Some("HcxyMARjyJy".to_string());
|
||||
assert_eq!(get_grpid_from_list(&mimeparser, "In-Reply-To"), grpid);
|
||||
assert_eq!(get_grpid_from_list(&mimeparser, "References"), grpid);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -49,7 +49,7 @@ impl Simplify {
|
||||
/**
|
||||
* Simplify Plain Text
|
||||
*/
|
||||
#[allow(non_snake_case, clippy::mut_range_bound)]
|
||||
#[allow(non_snake_case)]
|
||||
fn simplify_plain_text(&mut self, buf_terminated: &str, is_msgrmsg: bool) -> String {
|
||||
/* This function ...
|
||||
... removes all text after the line `-- ` (footer mark)
|
||||
|
||||
74
src/dc_strencode.rs
Normal file
74
src/dc_strencode.rs
Normal file
@@ -0,0 +1,74 @@
|
||||
use itertools::Itertools;
|
||||
|
||||
/// Encode non-ascii-strings as `=?UTF-8?Q?Bj=c3=b6rn_Petersen?=`.
|
||||
/// Belongs to RFC 2047: https://tools.ietf.org/html/rfc2047
|
||||
///
|
||||
/// We do not fold at position 72; this would result in empty words as `=?utf-8?Q??=` which are correct,
|
||||
/// but cannot be displayed by some mail programs (eg. Android Stock Mail).
|
||||
/// however, this is not needed, as long as _one_ word is not longer than 72 characters.
|
||||
/// _if_ it is, the display may get weird. This affects the subject only.
|
||||
/// the best solution wor all this would be if libetpan encodes the line as only libetpan knowns when a header line is full.
|
||||
///
|
||||
/// @param to_encode Null-terminated UTF-8-string to encode.
|
||||
/// @return Returns the encoded string which must be free()'d when no longed needed.
|
||||
/// On errors, NULL is returned.
|
||||
pub fn dc_encode_header_words(input: impl AsRef<str>) -> String {
|
||||
let mut result = String::default();
|
||||
for (_, group) in &input.as_ref().chars().group_by(|c| c.is_whitespace()) {
|
||||
let word: String = group.collect();
|
||||
result.push_str("e_word(&word.as_bytes()));
|
||||
}
|
||||
|
||||
result
|
||||
}
|
||||
|
||||
fn must_encode(byte: u8) -> bool {
|
||||
static SPECIALS: &[u8] = b",:!\"#$@[\\]^`{|}~=?_";
|
||||
|
||||
SPECIALS.iter().any(|b| *b == byte)
|
||||
}
|
||||
|
||||
fn quote_word(word: &[u8]) -> String {
|
||||
let mut result = String::default();
|
||||
let mut encoded = false;
|
||||
|
||||
for byte in word {
|
||||
let byte = *byte;
|
||||
if byte >= 128 || must_encode(byte) {
|
||||
result.push_str(&format!("={:2X}", byte));
|
||||
encoded = true;
|
||||
} else if byte == b' ' {
|
||||
result.push('_');
|
||||
encoded = true;
|
||||
} else {
|
||||
result.push(byte as _);
|
||||
}
|
||||
}
|
||||
|
||||
if encoded {
|
||||
result = format!("=?utf-8?Q?{}?=", &result);
|
||||
}
|
||||
result
|
||||
}
|
||||
|
||||
/* ******************************************************************************
|
||||
* Encode/decode header words, RFC 2047
|
||||
******************************************************************************/
|
||||
|
||||
pub fn dc_needs_ext_header(to_check: impl AsRef<str>) -> bool {
|
||||
let to_check = to_check.as_ref();
|
||||
|
||||
if to_check.is_empty() {
|
||||
return false;
|
||||
}
|
||||
|
||||
to_check.chars().any(|c| {
|
||||
!(c.is_ascii_alphanumeric()
|
||||
|| c == '-'
|
||||
|| c == '_'
|
||||
|| c == '_'
|
||||
|| c == '.'
|
||||
|| c == '~'
|
||||
|| c == '%')
|
||||
})
|
||||
}
|
||||
@@ -217,11 +217,8 @@ pub(crate) fn dc_create_outgoing_rfc724_mid(grpid: Option<&str>, from_addr: &str
|
||||
///
|
||||
/// # Arguments
|
||||
///
|
||||
/// * `mid` - A string that holds the message id. Leading/Trailing <>
|
||||
/// characters are automatically stripped.
|
||||
/// * `mid` - A string that holds the message id
|
||||
pub(crate) fn dc_extract_grpid_from_rfc724_mid(mid: &str) -> Option<&str> {
|
||||
let mid = mid.trim_start_matches('<').trim_end_matches('>');
|
||||
|
||||
if mid.len() < 9 || !mid.starts_with("Gr.") {
|
||||
return None;
|
||||
}
|
||||
@@ -238,6 +235,13 @@ pub(crate) fn dc_extract_grpid_from_rfc724_mid(mid: &str) -> Option<&str> {
|
||||
None
|
||||
}
|
||||
|
||||
pub(crate) fn dc_ensure_no_slash_safe(path: &str) -> &str {
|
||||
if path.ends_with('/') || path.ends_with('\\') {
|
||||
return &path[..path.len() - 1];
|
||||
}
|
||||
path
|
||||
}
|
||||
|
||||
// Function returns a sanitized basename that does not contain
|
||||
// win/linux path separators and also not any non-ascii chars
|
||||
fn get_safe_basename(filename: &str) -> String {
|
||||
@@ -691,16 +695,6 @@ mod tests {
|
||||
let mid = "Gr.1234567890123456.morerandom@domain.de";
|
||||
let grpid = dc_extract_grpid_from_rfc724_mid(mid);
|
||||
assert_eq!(grpid, Some("1234567890123456"));
|
||||
|
||||
// Should return extracted grpid for grpid with length of 11
|
||||
let mid = "<Gr.12345678901.morerandom@domain.de>";
|
||||
let grpid = dc_extract_grpid_from_rfc724_mid(mid);
|
||||
assert_eq!(grpid, Some("12345678901"));
|
||||
|
||||
// Should return extracted grpid for grpid with length of 11
|
||||
let mid = "<Gr.1234567890123456.morerandom@domain.de>";
|
||||
let grpid = dc_extract_grpid_from_rfc724_mid(mid);
|
||||
assert_eq!(grpid, Some("1234567890123456"));
|
||||
}
|
||||
|
||||
#[test]
|
||||
@@ -784,9 +778,9 @@ mod tests {
|
||||
|
||||
#[test]
|
||||
fn test_dc_create_incoming_rfc724_mid() {
|
||||
let res = dc_create_incoming_rfc724_mid(123, 45, &[6, 7]);
|
||||
let res = dc_create_incoming_rfc724_mid(123, 45, &vec![6, 7]);
|
||||
assert_eq!(res, Some("123-45-7@stub".into()));
|
||||
let res = dc_create_incoming_rfc724_mid(123, 45, &[]);
|
||||
let res = dc_create_incoming_rfc724_mid(123, 45, &vec![]);
|
||||
assert_eq!(res, Some("123-45-0@stub".into()));
|
||||
}
|
||||
|
||||
@@ -865,15 +859,15 @@ mod tests {
|
||||
#[test]
|
||||
fn test_listflags_has() {
|
||||
let listflags: u32 = 0x1101;
|
||||
assert!(listflags_has(listflags, 0x1));
|
||||
assert!(!listflags_has(listflags, 0x10));
|
||||
assert!(listflags_has(listflags, 0x100));
|
||||
assert!(listflags_has(listflags, 0x1000));
|
||||
assert!(listflags_has(listflags, 0x1) == true);
|
||||
assert!(listflags_has(listflags, 0x10) == false);
|
||||
assert!(listflags_has(listflags, 0x100) == true);
|
||||
assert!(listflags_has(listflags, 0x1000) == true);
|
||||
let listflags: u32 = (DC_GCL_ADD_SELF | DC_GCL_VERIFIED_ONLY).try_into().unwrap();
|
||||
assert!(listflags_has(listflags, DC_GCL_VERIFIED_ONLY));
|
||||
assert!(listflags_has(listflags, DC_GCL_ADD_SELF));
|
||||
assert!(listflags_has(listflags, DC_GCL_VERIFIED_ONLY) == true);
|
||||
assert!(listflags_has(listflags, DC_GCL_ADD_SELF) == true);
|
||||
let listflags: u32 = DC_GCL_VERIFIED_ONLY.try_into().unwrap();
|
||||
assert!(!listflags_has(listflags, DC_GCL_ADD_SELF));
|
||||
assert!(listflags_has(listflags, DC_GCL_ADD_SELF) == false);
|
||||
}
|
||||
|
||||
#[test]
|
||||
|
||||
27
src/e2ee.rs
27
src/e2ee.rs
@@ -98,6 +98,7 @@ impl EncryptHelper {
|
||||
.iter()
|
||||
.filter_map(|(state, addr)| state.as_ref().map(|s| (s, addr)))
|
||||
{
|
||||
info!(context, "adding for {}: {:?}", addr, peerstate);
|
||||
let key = peerstate.peek_key(min_verified).ok_or_else(|| {
|
||||
format_err!("proper enc-key for {} missing, cannot encrypt", addr)
|
||||
})?;
|
||||
@@ -121,6 +122,8 @@ pub fn try_decrypt(
|
||||
mail: &mailparse::ParsedMail<'_>,
|
||||
message_time: i64,
|
||||
) -> Result<(Option<Vec<u8>>, HashSet<String>)> {
|
||||
info!(context, "trying to decrypt");
|
||||
|
||||
let from = mail
|
||||
.headers
|
||||
.get_first_value("From")?
|
||||
@@ -210,7 +213,7 @@ fn load_or_generate_self_public_key(context: &Context, self_addr: impl AsRef<str
|
||||
);
|
||||
match pgp::create_keypair(&self_addr) {
|
||||
Some((public_key, private_key)) => {
|
||||
if dc_key_save_self_keypair(
|
||||
match dc_key_save_self_keypair(
|
||||
context,
|
||||
&public_key,
|
||||
&private_key,
|
||||
@@ -218,14 +221,15 @@ fn load_or_generate_self_public_key(context: &Context, self_addr: impl AsRef<str
|
||||
true,
|
||||
&context.sql,
|
||||
) {
|
||||
info!(
|
||||
context,
|
||||
"Keypair generated in {:.3}s.",
|
||||
start.elapsed().as_secs()
|
||||
);
|
||||
Ok(public_key)
|
||||
} else {
|
||||
Err(format_err!("Failed to save keypair"))
|
||||
true => {
|
||||
info!(
|
||||
context,
|
||||
"Keypair generated in {:.3}s.",
|
||||
start.elapsed().as_secs()
|
||||
);
|
||||
Ok(public_key)
|
||||
}
|
||||
false => Err(format_err!("Failed to save keypair")),
|
||||
}
|
||||
}
|
||||
None => Err(format_err!("Failed to generate keypair")),
|
||||
@@ -249,7 +253,7 @@ fn decrypt_if_autocrypt_message<'a>(
|
||||
let encrypted_data_part = match wrapmime::get_autocrypt_mime(mail) {
|
||||
Err(err) => {
|
||||
// not a proper autocrypt message, abort and ignore
|
||||
info!(context, "Not an autocrypt message: {:?}", err);
|
||||
warn!(context, "Invalid autocrypt message: {:?}", err);
|
||||
return Ok(None);
|
||||
}
|
||||
Ok(res) => res,
|
||||
@@ -266,12 +270,13 @@ fn decrypt_if_autocrypt_message<'a>(
|
||||
|
||||
/// Returns Ok(None) if nothing encrypted was found.
|
||||
fn decrypt_part(
|
||||
_context: &Context,
|
||||
context: &Context,
|
||||
mail: &mailparse::ParsedMail<'_>,
|
||||
private_keyring: &Keyring,
|
||||
public_keyring_for_validate: &Keyring,
|
||||
ret_valid_signatures: &mut HashSet<String>,
|
||||
) -> Result<Option<Vec<u8>>> {
|
||||
info!(context, "decrypting part");
|
||||
let data = mail.get_body_raw()?;
|
||||
|
||||
if has_decrypted_pgp_armor(&data) {
|
||||
|
||||
@@ -1,5 +1,3 @@
|
||||
//! # Error handling
|
||||
|
||||
use failure::Fail;
|
||||
use lettre_email::mime;
|
||||
|
||||
|
||||
@@ -1,5 +1,3 @@
|
||||
//! # Events specification
|
||||
|
||||
use std::path::PathBuf;
|
||||
|
||||
use strum::EnumProperty;
|
||||
@@ -92,7 +90,7 @@ pub enum Event {
|
||||
/// However, for ongoing processes (eg. configure())
|
||||
/// or for functions that are expected to fail (eg. dc_continue_key_transfer())
|
||||
/// it might be better to delay showing these events until the function has really
|
||||
/// failed (returned false). It should be sufficient to report only the *last* error
|
||||
/// failed (returned false). It should be sufficient to report only the _last_ error
|
||||
/// in a messasge box then.
|
||||
///
|
||||
/// @return
|
||||
|
||||
@@ -28,14 +28,15 @@ use crate::param::Params;
|
||||
use crate::stock::StockMessage;
|
||||
use crate::wrapmime;
|
||||
|
||||
pub mod select_folder;
|
||||
|
||||
const DC_IMAP_SEEN: usize = 0x0001;
|
||||
|
||||
type Result<T> = std::result::Result<T, Error>;
|
||||
|
||||
#[derive(Debug, Fail)]
|
||||
pub enum Error {
|
||||
#[fail(display = "IMAP Could not obtain imap-session object.")]
|
||||
NoSession,
|
||||
|
||||
#[fail(display = "IMAP Connect without configured params")]
|
||||
ConnectWithoutConfigure,
|
||||
|
||||
@@ -60,6 +61,15 @@ pub enum Error {
|
||||
#[fail(display = "IMAP server does not have IDLE capability")]
|
||||
IdleAbilityMissing,
|
||||
|
||||
#[fail(display = "IMAP Connection Lost or no connection established")]
|
||||
ConnectionLost,
|
||||
|
||||
#[fail(display = "IMAP close/expunge failed: {}", _0)]
|
||||
CloseExpungeFailed(#[cause] async_imap::error::Error),
|
||||
|
||||
#[fail(display = "IMAP Folder name invalid: {:?}", _0)]
|
||||
BadFolderName(String),
|
||||
|
||||
#[fail(display = "IMAP operation attempted while it is torn down")]
|
||||
InTeardown,
|
||||
|
||||
@@ -69,9 +79,6 @@ pub enum Error {
|
||||
#[fail(display = "IMAP got error from elsewhere: {:?}", _0)]
|
||||
WrappedError(#[cause] crate::error::Error),
|
||||
|
||||
#[fail(display = "IMAP select folder error")]
|
||||
SelectFolderError(#[cause] select_folder::Error),
|
||||
|
||||
#[fail(display = "IMAP other error: {:?}", _0)]
|
||||
Other(String),
|
||||
}
|
||||
@@ -94,12 +101,6 @@ impl From<Error> for crate::error::Error {
|
||||
}
|
||||
}
|
||||
|
||||
impl From<select_folder::Error> for Error {
|
||||
fn from(err: select_folder::Error) -> Error {
|
||||
Error::SelectFolderError(err)
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Display, Clone, Copy, PartialEq, Eq)]
|
||||
pub enum ImapActionResult {
|
||||
Failed,
|
||||
@@ -113,7 +114,7 @@ const JUST_UID: &str = "(UID)";
|
||||
const BODY_FLAGS: &str = "(FLAGS BODY.PEEK[])";
|
||||
const SELECT_ALL: &str = "1:*";
|
||||
|
||||
#[derive(Debug, Default)]
|
||||
#[derive(Debug)]
|
||||
pub struct Imap {
|
||||
config: RwLock<ImapConfig>,
|
||||
session: Mutex<Option<Session>>,
|
||||
@@ -186,7 +187,14 @@ impl Default for ImapConfig {
|
||||
|
||||
impl Imap {
|
||||
pub fn new() -> Self {
|
||||
Default::default()
|
||||
Imap {
|
||||
session: Mutex::new(None),
|
||||
config: RwLock::new(ImapConfig::default()),
|
||||
interrupt: Mutex::new(None),
|
||||
connected: Mutex::new(false),
|
||||
skip_next_idle_wait: AtomicBool::new(false),
|
||||
should_reconnect: AtomicBool::new(false),
|
||||
}
|
||||
}
|
||||
|
||||
pub async fn is_connected(&self) -> bool {
|
||||
@@ -225,7 +233,9 @@ impl Imap {
|
||||
match Client::connect_insecure((imap_server, imap_port)).await {
|
||||
Ok(client) => {
|
||||
if (server_flags & DC_LP_IMAP_SOCKET_STARTTLS) != 0 {
|
||||
client.secure(imap_server, config.certificate_checks).await
|
||||
let res =
|
||||
client.secure(imap_server, config.certificate_checks).await;
|
||||
res
|
||||
} else {
|
||||
Ok(client)
|
||||
}
|
||||
@@ -261,12 +271,14 @@ impl Imap {
|
||||
user: imap_user.into(),
|
||||
access_token: token,
|
||||
};
|
||||
client.authenticate("XOAUTH2", &auth).await
|
||||
let res = client.authenticate("XOAUTH2", &auth).await;
|
||||
res
|
||||
} else {
|
||||
return Err(Error::OauthError);
|
||||
}
|
||||
} else {
|
||||
client.login(imap_user, imap_pw).await
|
||||
let res = client.login(imap_user, imap_pw).await;
|
||||
res
|
||||
}
|
||||
}
|
||||
Err(err) => {
|
||||
@@ -357,7 +369,7 @@ impl Imap {
|
||||
if self.connect(context, ¶m) {
|
||||
self.ensure_configured_folders(context, true)
|
||||
} else {
|
||||
Err(Error::ConnectionFailed(format!("{}", param)))
|
||||
Err(Error::ConnectionFailed(format!("{}", param).to_string()))
|
||||
}
|
||||
}
|
||||
|
||||
@@ -459,6 +471,92 @@ impl Imap {
|
||||
})
|
||||
}
|
||||
|
||||
/// select a folder, possibly update uid_validity and, if needed,
|
||||
/// expunge the folder to remove delete-marked messages.
|
||||
async fn select_folder<S: AsRef<str>>(
|
||||
&self,
|
||||
context: &Context,
|
||||
folder: Option<S>,
|
||||
) -> Result<()> {
|
||||
if self.session.lock().await.is_none() {
|
||||
let mut cfg = self.config.write().await;
|
||||
cfg.selected_folder = None;
|
||||
cfg.selected_folder_needs_expunge = false;
|
||||
return Err(Error::NoSession);
|
||||
}
|
||||
|
||||
// if there is a new folder and the new folder is equal to the selected one, there's nothing to do.
|
||||
// if there is _no_ new folder, we continue as we might want to expunge below.
|
||||
if let Some(ref folder) = folder {
|
||||
if let Some(ref selected_folder) = self.config.read().await.selected_folder {
|
||||
if folder.as_ref() == selected_folder {
|
||||
return Ok(());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// deselect existing folder, if needed (it's also done implicitly by SELECT, however, without EXPUNGE then)
|
||||
let needs_expunge = { self.config.read().await.selected_folder_needs_expunge };
|
||||
if needs_expunge {
|
||||
if let Some(ref folder) = self.config.read().await.selected_folder {
|
||||
info!(context, "Expunge messages in \"{}\".", folder);
|
||||
|
||||
// A CLOSE-SELECT is considerably faster than an EXPUNGE-SELECT, see
|
||||
// https://tools.ietf.org/html/rfc3501#section-6.4.2
|
||||
if let Some(ref mut session) = &mut *self.session.lock().await {
|
||||
match session.close().await {
|
||||
Ok(_) => {
|
||||
info!(context, "close/expunge succeeded");
|
||||
}
|
||||
Err(err) => {
|
||||
return Err(Error::CloseExpungeFailed(err));
|
||||
}
|
||||
}
|
||||
} else {
|
||||
return Err(Error::NoSession);
|
||||
}
|
||||
}
|
||||
self.config.write().await.selected_folder_needs_expunge = false;
|
||||
}
|
||||
|
||||
// select new folder
|
||||
if let Some(ref folder) = folder {
|
||||
if let Some(ref mut session) = &mut *self.session.lock().await {
|
||||
let res = session.select(folder).await;
|
||||
|
||||
// https://tools.ietf.org/html/rfc3501#section-6.3.1
|
||||
// says that if the server reports select failure we are in
|
||||
// authenticated (not-select) state.
|
||||
|
||||
match res {
|
||||
Ok(mailbox) => {
|
||||
let mut config = self.config.write().await;
|
||||
config.selected_folder = Some(folder.as_ref().to_string());
|
||||
config.selected_mailbox = Some(mailbox);
|
||||
Ok(())
|
||||
}
|
||||
Err(async_imap::error::Error::ConnectionLost) => {
|
||||
self.trigger_reconnect();
|
||||
self.config.write().await.selected_folder = None;
|
||||
Err(Error::ConnectionLost)
|
||||
}
|
||||
Err(async_imap::error::Error::Validate(_)) => {
|
||||
Err(Error::BadFolderName(folder.as_ref().to_string()))
|
||||
}
|
||||
Err(err) => {
|
||||
self.config.write().await.selected_folder = None;
|
||||
self.trigger_reconnect();
|
||||
Err(Error::Other(err.to_string()))
|
||||
}
|
||||
}
|
||||
} else {
|
||||
Err(Error::NoSession)
|
||||
}
|
||||
} else {
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
fn get_config_last_seen_uid<S: AsRef<str>>(&self, context: &Context, folder: S) -> (u32, u32) {
|
||||
let key = format!("imap.mailbox.{}", folder.as_ref());
|
||||
if let Some(entry) = context.sql.get_raw_config(context, &key) {
|
||||
@@ -1077,15 +1175,15 @@ impl Imap {
|
||||
}
|
||||
match self.select_folder(context, Some(&folder)).await {
|
||||
Ok(()) => None,
|
||||
Err(select_folder::Error::ConnectionLost) => {
|
||||
Err(Error::ConnectionLost) => {
|
||||
warn!(context, "Lost imap connection");
|
||||
Some(ImapActionResult::RetryLater)
|
||||
}
|
||||
Err(select_folder::Error::NoSession) => {
|
||||
Err(Error::NoSession) => {
|
||||
warn!(context, "no imap session");
|
||||
Some(ImapActionResult::Failed)
|
||||
}
|
||||
Err(select_folder::Error::BadFolderName(folder_name)) => {
|
||||
Err(Error::BadFolderName(folder_name)) => {
|
||||
warn!(context, "invalid folder name: {:?}", folder_name);
|
||||
Some(ImapActionResult::Failed)
|
||||
}
|
||||
@@ -1300,7 +1398,11 @@ impl Imap {
|
||||
})
|
||||
}
|
||||
|
||||
async fn list_folders(&self, session: &mut Session, context: &Context) -> Option<Vec<Name>> {
|
||||
async fn list_folders<'a>(
|
||||
&self,
|
||||
session: &'a mut Session,
|
||||
context: &Context,
|
||||
) -> Option<Vec<Name>> {
|
||||
// TODO: use xlist when available
|
||||
match session.list(Some(""), Some("*")).await {
|
||||
Ok(list) => {
|
||||
@@ -1368,7 +1470,7 @@ fn get_folder_meaning_by_name(folder_name: &Name) -> FolderMeaning {
|
||||
let sent_names = vec!["sent", "sent objects", "gesendet"];
|
||||
let lower = folder_name.name().to_lowercase();
|
||||
|
||||
if sent_names.into_iter().any(|s| s == lower) {
|
||||
if sent_names.into_iter().find(|s| *s == lower).is_some() {
|
||||
FolderMeaning::SentObjects
|
||||
} else {
|
||||
FolderMeaning::Unknown
|
||||
@@ -1384,12 +1486,15 @@ fn get_folder_meaning(folder_name: &Name) -> FolderMeaning {
|
||||
let special_names = vec!["\\Spam", "\\Trash", "\\Drafts", "\\Junk"];
|
||||
|
||||
for attr in folder_name.attributes() {
|
||||
if let NameAttribute::Custom(ref label) = attr {
|
||||
if special_names.iter().any(|s| *s == label) {
|
||||
res = FolderMeaning::Other;
|
||||
} else if label == "\\Sent" {
|
||||
res = FolderMeaning::SentObjects
|
||||
match attr {
|
||||
NameAttribute::Custom(ref label) => {
|
||||
if special_names.iter().find(|s| *s == label).is_some() {
|
||||
res = FolderMeaning::Other;
|
||||
} else if label == "\\Sent" {
|
||||
res = FolderMeaning::SentObjects
|
||||
}
|
||||
}
|
||||
_ => {}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,111 +0,0 @@
|
||||
use super::Imap;
|
||||
|
||||
use crate::context::Context;
|
||||
|
||||
type Result<T> = std::result::Result<T, Error>;
|
||||
|
||||
#[derive(Debug, Fail)]
|
||||
pub enum Error {
|
||||
#[fail(display = "IMAP Could not obtain imap-session object.")]
|
||||
NoSession,
|
||||
|
||||
#[fail(display = "IMAP Connection Lost or no connection established")]
|
||||
ConnectionLost,
|
||||
|
||||
#[fail(display = "IMAP Folder name invalid: {:?}", _0)]
|
||||
BadFolderName(String),
|
||||
|
||||
#[fail(display = "IMAP close/expunge failed: {}", _0)]
|
||||
CloseExpungeFailed(#[cause] async_imap::error::Error),
|
||||
|
||||
#[fail(display = "IMAP other error: {:?}", _0)]
|
||||
Other(String),
|
||||
}
|
||||
|
||||
impl Imap {
|
||||
/// select a folder, possibly update uid_validity and, if needed,
|
||||
/// expunge the folder to remove delete-marked messages.
|
||||
pub(super) async fn select_folder<S: AsRef<str>>(
|
||||
&self,
|
||||
context: &Context,
|
||||
folder: Option<S>,
|
||||
) -> Result<()> {
|
||||
if self.session.lock().await.is_none() {
|
||||
let mut cfg = self.config.write().await;
|
||||
cfg.selected_folder = None;
|
||||
cfg.selected_folder_needs_expunge = false;
|
||||
return Err(Error::NoSession);
|
||||
}
|
||||
|
||||
// if there is a new folder and the new folder is equal to the selected one, there's nothing to do.
|
||||
// if there is _no_ new folder, we continue as we might want to expunge below.
|
||||
if let Some(ref folder) = folder {
|
||||
if let Some(ref selected_folder) = self.config.read().await.selected_folder {
|
||||
if folder.as_ref() == selected_folder {
|
||||
return Ok(());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// deselect existing folder, if needed (it's also done implicitly by SELECT, however, without EXPUNGE then)
|
||||
let needs_expunge = { self.config.read().await.selected_folder_needs_expunge };
|
||||
if needs_expunge {
|
||||
if let Some(ref folder) = self.config.read().await.selected_folder {
|
||||
info!(context, "Expunge messages in \"{}\".", folder);
|
||||
|
||||
// A CLOSE-SELECT is considerably faster than an EXPUNGE-SELECT, see
|
||||
// https://tools.ietf.org/html/rfc3501#section-6.4.2
|
||||
if let Some(ref mut session) = &mut *self.session.lock().await {
|
||||
match session.close().await {
|
||||
Ok(_) => {
|
||||
info!(context, "close/expunge succeeded");
|
||||
}
|
||||
Err(err) => {
|
||||
return Err(Error::CloseExpungeFailed(err));
|
||||
}
|
||||
}
|
||||
} else {
|
||||
return Err(Error::NoSession);
|
||||
}
|
||||
}
|
||||
self.config.write().await.selected_folder_needs_expunge = false;
|
||||
}
|
||||
|
||||
// select new folder
|
||||
if let Some(ref folder) = folder {
|
||||
if let Some(ref mut session) = &mut *self.session.lock().await {
|
||||
let res = session.select(folder).await;
|
||||
|
||||
// https://tools.ietf.org/html/rfc3501#section-6.3.1
|
||||
// says that if the server reports select failure we are in
|
||||
// authenticated (not-select) state.
|
||||
|
||||
match res {
|
||||
Ok(mailbox) => {
|
||||
let mut config = self.config.write().await;
|
||||
config.selected_folder = Some(folder.as_ref().to_string());
|
||||
config.selected_mailbox = Some(mailbox);
|
||||
Ok(())
|
||||
}
|
||||
Err(async_imap::error::Error::ConnectionLost) => {
|
||||
self.trigger_reconnect();
|
||||
self.config.write().await.selected_folder = None;
|
||||
Err(Error::ConnectionLost)
|
||||
}
|
||||
Err(async_imap::error::Error::Validate(_)) => {
|
||||
Err(Error::BadFolderName(folder.as_ref().to_string()))
|
||||
}
|
||||
Err(err) => {
|
||||
self.config.write().await.selected_folder = None;
|
||||
self.trigger_reconnect();
|
||||
Err(Error::Other(err.to_string()))
|
||||
}
|
||||
}
|
||||
} else {
|
||||
Err(Error::NoSession)
|
||||
}
|
||||
} else {
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -11,6 +11,8 @@ use async_tls::client::TlsStream;
|
||||
|
||||
use crate::login_param::{dc_build_tls_config, CertificateChecks};
|
||||
|
||||
const DCC_IMAP_DEBUG: &str = "DCC_IMAP_DEBUG";
|
||||
|
||||
#[derive(Debug)]
|
||||
pub(crate) enum Client {
|
||||
Secure(ImapClient<TlsStream<TcpStream>>),
|
||||
@@ -40,7 +42,7 @@ impl Client {
|
||||
let tls_connector: async_tls::TlsConnector = Arc::new(tls_config).into();
|
||||
let tls_stream = tls_connector.connect(domain.as_ref(), stream)?.await?;
|
||||
let mut client = ImapClient::new(tls_stream);
|
||||
if std::env::var(crate::DCC_IMAP_DEBUG).is_ok() {
|
||||
if std::env::var(DCC_IMAP_DEBUG).is_ok() {
|
||||
client.debug = true;
|
||||
}
|
||||
|
||||
@@ -56,7 +58,7 @@ impl Client {
|
||||
let stream = TcpStream::connect(addr).await?;
|
||||
|
||||
let mut client = ImapClient::new(stream);
|
||||
if std::env::var(crate::DCC_IMAP_DEBUG).is_ok() {
|
||||
if std::env::var(DCC_IMAP_DEBUG).is_ok() {
|
||||
client.debug = true;
|
||||
}
|
||||
let _greeting = client
|
||||
|
||||
41
src/imex.rs
41
src/imex.rs
@@ -53,7 +53,7 @@ pub enum ImexMode {
|
||||
/// For this purpose, the function creates a job that is executed in the IMAP-thread then;
|
||||
/// this requires to call dc_perform_inbox_jobs() regularly.
|
||||
///
|
||||
/// What to do is defined by the *what* parameter.
|
||||
/// What to do is defined by the _what_ parameter.
|
||||
///
|
||||
/// While dc_imex() returns immediately, the started job may take a while,
|
||||
/// you can stop it using dc_stop_ongoing_process(). During execution of the job,
|
||||
@@ -84,24 +84,27 @@ pub fn has_backup(context: &Context, dir_name: impl AsRef<Path>) -> Result<Strin
|
||||
let mut newest_backup_time = 0;
|
||||
let mut newest_backup_path: Option<std::path::PathBuf> = None;
|
||||
for dirent in dir_iter {
|
||||
if let Ok(dirent) = dirent {
|
||||
let path = dirent.path();
|
||||
let name = dirent.file_name();
|
||||
let name = name.to_string_lossy();
|
||||
if name.starts_with("delta-chat") && name.ends_with(".bak") {
|
||||
let sql = Sql::new();
|
||||
if sql.open(context, &path, true) {
|
||||
let curr_backup_time = sql
|
||||
.get_raw_config_int(context, "backup_time")
|
||||
.unwrap_or_default();
|
||||
if curr_backup_time > newest_backup_time {
|
||||
newest_backup_path = Some(path);
|
||||
newest_backup_time = curr_backup_time;
|
||||
match dirent {
|
||||
Ok(dirent) => {
|
||||
let path = dirent.path();
|
||||
let name = dirent.file_name();
|
||||
let name = name.to_string_lossy();
|
||||
if name.starts_with("delta-chat") && name.ends_with(".bak") {
|
||||
let sql = Sql::new();
|
||||
if sql.open(context, &path, true) {
|
||||
let curr_backup_time = sql
|
||||
.get_raw_config_int(context, "backup_time")
|
||||
.unwrap_or_default();
|
||||
if curr_backup_time > newest_backup_time {
|
||||
newest_backup_path = Some(path);
|
||||
newest_backup_time = curr_backup_time;
|
||||
}
|
||||
info!(context, "backup_time of {} is {}", name, curr_backup_time);
|
||||
sql.close(&context);
|
||||
}
|
||||
info!(context, "backup_time of {} is {}", name, curr_backup_time);
|
||||
sql.close(&context);
|
||||
}
|
||||
}
|
||||
Err(_) => (),
|
||||
}
|
||||
}
|
||||
match newest_backup_path {
|
||||
@@ -172,7 +175,7 @@ pub fn render_setup_file(context: &Context, passphrase: &str) -> Result<String>
|
||||
);
|
||||
let self_addr = e2ee::ensure_secret_key_exists(context)?;
|
||||
let private_key = Key::from_self_private(context, self_addr, &context.sql)
|
||||
.ok_or_else(|| format_err!("Failed to get private key."))?;
|
||||
.ok_or(format_err!("Failed to get private key."))?;
|
||||
let ac_headers = match context.get_config_bool(Config::E2eeEnabled) {
|
||||
false => None,
|
||||
true => Some(("Autocrypt-Prefer-Encrypt", "mutual")),
|
||||
@@ -219,7 +222,7 @@ pub fn create_setup_code(_context: &Context) -> String {
|
||||
for i in 0..9 {
|
||||
loop {
|
||||
random_val = rng.gen();
|
||||
if random_val as usize <= 60000 {
|
||||
if !(random_val as usize > 60000) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
@@ -545,7 +548,7 @@ fn export_backup(context: &Context, dir: impl AsRef<Path>) -> Result<()> {
|
||||
}
|
||||
Ok(()) => {
|
||||
dest_sql.set_raw_config_int(context, "backup_time", now as i32)?;
|
||||
context.call_cb(Event::ImexFileWritten(dest_path_filename));
|
||||
context.call_cb(Event::ImexFileWritten(dest_path_filename.clone()));
|
||||
Ok(())
|
||||
}
|
||||
};
|
||||
|
||||
38
src/job.rs
38
src/job.rs
@@ -1,8 +1,3 @@
|
||||
//! # Job module
|
||||
//!
|
||||
//! This module implements a job queue maintained in the SQLite database
|
||||
//! and job types.
|
||||
|
||||
use std::time::Duration;
|
||||
|
||||
use deltachat_derive::{FromSql, ToSql};
|
||||
@@ -122,7 +117,6 @@ pub struct Job {
|
||||
}
|
||||
|
||||
impl Job {
|
||||
/// Deletes the job from the database.
|
||||
fn delete(&self, context: &Context) -> bool {
|
||||
context
|
||||
.sql
|
||||
@@ -130,9 +124,6 @@ impl Job {
|
||||
.is_ok()
|
||||
}
|
||||
|
||||
/// Updates the job already stored in the database.
|
||||
///
|
||||
/// To add a new job, use [job_add].
|
||||
fn update(&self, context: &Context) -> bool {
|
||||
sql::execute(
|
||||
context,
|
||||
@@ -192,10 +183,6 @@ impl Job {
|
||||
// was sent we need to mark it in the database ASAP as we
|
||||
// otherwise might send it twice.
|
||||
let mut smtp = context.smtp.lock().unwrap();
|
||||
if std::env::var(crate::DCC_MIME_DEBUG).is_ok() {
|
||||
info!(context, "smtp-sending out mime message:");
|
||||
println!("{}", String::from_utf8_lossy(&body));
|
||||
}
|
||||
match smtp.send(context, recipients_list, body, self.job_id) {
|
||||
Err(crate::smtp::send::Error::SendError(err)) => {
|
||||
// Remote error, retry later.
|
||||
@@ -610,7 +597,7 @@ fn set_delivered(context: &Context, msg_id: MsgId) {
|
||||
.unwrap_or_default();
|
||||
context.call_cb(Event::MsgDelivered {
|
||||
chat_id: chat_id as u32,
|
||||
msg_id,
|
||||
msg_id: msg_id,
|
||||
});
|
||||
}
|
||||
|
||||
@@ -618,7 +605,26 @@ fn set_delivered(context: &Context, msg_id: MsgId) {
|
||||
#[allow(non_snake_case)]
|
||||
pub fn job_send_msg(context: &Context, msg_id: MsgId) -> Result<(), Error> {
|
||||
let mut msg = Message::load_from_db(context, msg_id)?;
|
||||
msg.try_calc_and_set_dimensions(context).ok();
|
||||
|
||||
if chat::msgtype_has_file(msg.type_0) {
|
||||
let file_param = msg.param.get_path(Param::File, context)?;
|
||||
if let Some(pathNfilename) = file_param {
|
||||
if (msg.type_0 == Viewtype::Image || msg.type_0 == Viewtype::Gif)
|
||||
&& !msg.param.exists(Param::Width)
|
||||
{
|
||||
msg.param.set_int(Param::Width, 0);
|
||||
msg.param.set_int(Param::Height, 0);
|
||||
|
||||
if let Ok(buf) = dc_read_file(context, pathNfilename) {
|
||||
if let Ok((width, height)) = dc_get_filemeta(&buf) {
|
||||
msg.param.set_int(Param::Width, width as i32);
|
||||
msg.param.set_int(Param::Height, height as i32);
|
||||
}
|
||||
}
|
||||
msg.save_param_to_disk(context);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/* create message */
|
||||
let needs_encryption = msg.param.get_int(Param::GuaranteeE2ee).unwrap_or_default();
|
||||
@@ -906,8 +912,6 @@ fn add_smtp_job(
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Adds a job to the database, scheduling it `delay_seconds`
|
||||
/// after the current time.
|
||||
pub fn job_add(
|
||||
context: &Context,
|
||||
action: Action,
|
||||
|
||||
@@ -192,6 +192,7 @@ impl Key {
|
||||
res.push(c);
|
||||
res
|
||||
})
|
||||
.to_string()
|
||||
}
|
||||
|
||||
pub fn to_armored_string(
|
||||
|
||||
26
src/lib.rs
26
src/lib.rs
@@ -1,17 +1,8 @@
|
||||
#![deny(clippy::correctness, missing_debug_implementations, clippy::all)]
|
||||
// for now we hide warnings to not clutter/hide errors during "cargo clippy"
|
||||
#![allow(
|
||||
clippy::type_complexity,
|
||||
clippy::cognitive_complexity,
|
||||
clippy::too_many_arguments,
|
||||
clippy::block_in_if_condition_stmt,
|
||||
clippy::large_enum_variant
|
||||
)]
|
||||
#![allow(
|
||||
clippy::unreadable_literal,
|
||||
clippy::needless_range_loop,
|
||||
clippy::match_bool
|
||||
)]
|
||||
#![deny(clippy::correctness, missing_debug_implementations)]
|
||||
// TODO: make all of these errors, such that clippy actually passes.
|
||||
#![warn(clippy::all, clippy::perf, clippy::not_unsafe_ptr_arg_deref)]
|
||||
// This is nice, but for now just annoying.
|
||||
#![allow(clippy::unreadable_literal)]
|
||||
#![feature(ptr_wrapping_offset_from)]
|
||||
|
||||
#[macro_use]
|
||||
@@ -76,13 +67,8 @@ mod dehtml;
|
||||
pub mod dc_array;
|
||||
pub mod dc_receive_imf;
|
||||
mod dc_simplify;
|
||||
mod dc_strencode;
|
||||
pub mod dc_tools;
|
||||
|
||||
/// if set imap/incoming and smtp/outgoing MIME messages will be printed
|
||||
pub const DCC_MIME_DEBUG: &str = "DCC_MIME_DEBUG";
|
||||
|
||||
/// if set IMAP protocol commands and responses will be printed
|
||||
pub const DCC_IMAP_DEBUG: &str = "DCC_IMAP_DEBUG";
|
||||
|
||||
#[cfg(test)]
|
||||
mod test_utils;
|
||||
|
||||
@@ -1,5 +1,3 @@
|
||||
//! # Login parameters
|
||||
|
||||
use std::borrow::Cow;
|
||||
use std::fmt;
|
||||
|
||||
@@ -106,7 +104,7 @@ impl LoginParam {
|
||||
let server_flags = sql.get_raw_config_int(context, key).unwrap_or_default();
|
||||
|
||||
LoginParam {
|
||||
addr,
|
||||
addr: addr.to_string(),
|
||||
mail_server,
|
||||
mail_user,
|
||||
mail_pw,
|
||||
@@ -200,7 +198,6 @@ impl fmt::Display for LoginParam {
|
||||
}
|
||||
}
|
||||
|
||||
#[allow(clippy::ptr_arg)]
|
||||
fn unset_empty(s: &String) -> Cow<String> {
|
||||
if s.is_empty() {
|
||||
Cow::Owned("unset".to_string())
|
||||
@@ -209,45 +206,44 @@ fn unset_empty(s: &String) -> Cow<String> {
|
||||
}
|
||||
}
|
||||
|
||||
#[allow(clippy::useless_let_if_seq)]
|
||||
fn get_readable_flags(flags: i32) -> String {
|
||||
let mut res = String::new();
|
||||
for bit in 0..31 {
|
||||
if 0 != flags & 1 << bit {
|
||||
let mut flag_added = false;
|
||||
let mut flag_added = 0;
|
||||
if 1 << bit == 0x2 {
|
||||
res += "OAUTH2 ";
|
||||
flag_added = true;
|
||||
flag_added = 1;
|
||||
}
|
||||
if 1 << bit == 0x4 {
|
||||
res += "AUTH_NORMAL ";
|
||||
flag_added = true;
|
||||
flag_added = 1;
|
||||
}
|
||||
if 1 << bit == 0x100 {
|
||||
res += "IMAP_STARTTLS ";
|
||||
flag_added = true;
|
||||
flag_added = 1;
|
||||
}
|
||||
if 1 << bit == 0x200 {
|
||||
res += "IMAP_SSL ";
|
||||
flag_added = true;
|
||||
flag_added = 1;
|
||||
}
|
||||
if 1 << bit == 0x400 {
|
||||
res += "IMAP_PLAIN ";
|
||||
flag_added = true;
|
||||
flag_added = 1;
|
||||
}
|
||||
if 1 << bit == 0x10000 {
|
||||
res += "SMTP_STARTTLS ";
|
||||
flag_added = true;
|
||||
flag_added = 1
|
||||
}
|
||||
if 1 << bit == 0x20000 {
|
||||
res += "SMTP_SSL ";
|
||||
flag_added = true;
|
||||
flag_added = 1
|
||||
}
|
||||
if 1 << bit == 0x40000 {
|
||||
res += "SMTP_PLAIN ";
|
||||
flag_added = true;
|
||||
flag_added = 1
|
||||
}
|
||||
if flag_added {
|
||||
if 0 == flag_added {
|
||||
res += &format!("{:#0x}", 1 << bit);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -5,7 +5,7 @@ use deltachat_derive::{FromSql, ToSql};
|
||||
/// Lot objects are created
|
||||
/// eg. by chatlist.get_summary() or dc_msg_get_summary().
|
||||
///
|
||||
/// *Lot* is used in the meaning *heap* here.
|
||||
/// _Lot_ is used in the meaning _heap_ here.
|
||||
#[derive(Default, Debug, Clone)]
|
||||
pub struct Lot {
|
||||
pub(crate) text1_meaning: Meaning,
|
||||
|
||||
101
src/message.rs
101
src/message.rs
@@ -1,5 +1,3 @@
|
||||
//! # Messages and their identifiers
|
||||
|
||||
use std::path::{Path, PathBuf};
|
||||
|
||||
use deltachat_derive::{FromSql, ToSql};
|
||||
@@ -46,7 +44,7 @@ impl MsgId {
|
||||
/// Whether the message ID signifies a special message.
|
||||
///
|
||||
/// This kind of message ID can not be used for real messages.
|
||||
pub fn is_special(self) -> bool {
|
||||
pub fn is_special(&self) -> bool {
|
||||
match self.0 {
|
||||
0..=DC_MSG_ID_LAST_SPECIAL => true,
|
||||
_ => false,
|
||||
@@ -62,21 +60,21 @@ impl MsgId {
|
||||
///
|
||||
/// When this is `true`, [MsgId::is_special] will also always be
|
||||
/// `true`.
|
||||
pub fn is_unset(self) -> bool {
|
||||
pub fn is_unset(&self) -> bool {
|
||||
self.0 == 0
|
||||
}
|
||||
|
||||
/// Whether the message ID is the special marker1 marker.
|
||||
///
|
||||
/// See the docs of the `dc_get_chat_msgs` C API for details.
|
||||
pub fn is_marker1(self) -> bool {
|
||||
pub fn is_marker1(&self) -> bool {
|
||||
self.0 == DC_MSG_ID_MARKER1
|
||||
}
|
||||
|
||||
/// Whether the message ID is the special day marker.
|
||||
///
|
||||
/// See the docs of the `dc_get_chat_msgs` C API for details.
|
||||
pub fn is_daymarker(self) -> bool {
|
||||
pub fn is_daymarker(&self) -> bool {
|
||||
self.0 == DC_MSG_ID_DAYMARKER
|
||||
}
|
||||
|
||||
@@ -84,7 +82,7 @@ impl MsgId {
|
||||
///
|
||||
/// Avoid using this, eventually types should be cleaned up enough
|
||||
/// that it is no longer necessary.
|
||||
pub fn to_u32(self) -> u32 {
|
||||
pub fn to_u32(&self) -> u32 {
|
||||
self.0
|
||||
}
|
||||
}
|
||||
@@ -311,32 +309,6 @@ impl Message {
|
||||
self.param.get_path(Param::File, context).unwrap_or(None)
|
||||
}
|
||||
|
||||
pub fn try_calc_and_set_dimensions(&mut self, context: &Context) -> Result<(), Error> {
|
||||
if chat::msgtype_has_file(self.type_0) {
|
||||
let file_param = self.param.get_path(Param::File, context)?;
|
||||
if let Some(path_and_filename) = file_param {
|
||||
if (self.type_0 == Viewtype::Image || self.type_0 == Viewtype::Gif)
|
||||
&& !self.param.exists(Param::Width)
|
||||
{
|
||||
self.param.set_int(Param::Width, 0);
|
||||
self.param.set_int(Param::Height, 0);
|
||||
|
||||
if let Ok(buf) = dc_read_file(context, path_and_filename) {
|
||||
if let Ok((width, height)) = dc_get_filemeta(&buf) {
|
||||
self.param.set_int(Param::Width, width as i32);
|
||||
self.param.set_int(Param::Height, height as i32);
|
||||
}
|
||||
}
|
||||
|
||||
if !self.id.is_unset() {
|
||||
self.save_param_to_disk(context);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Check if a message has a location bound to it.
|
||||
/// These messages are also returned by dc_get_locations()
|
||||
/// and the UI may decide to display a special icon beside such messages,
|
||||
@@ -689,7 +661,7 @@ impl Lot {
|
||||
self.text2 = Some(get_summarytext_by_raw(
|
||||
msg.type_0,
|
||||
msg.text.as_ref(),
|
||||
&msg.param,
|
||||
&mut msg.param,
|
||||
SUMMARY_CHARACTERS,
|
||||
context,
|
||||
));
|
||||
@@ -1220,10 +1192,9 @@ pub fn mdn_from_ext(
|
||||
} // else wait for more receipts
|
||||
}
|
||||
}
|
||||
return if read_by_all {
|
||||
Some((chat_id, msg_id))
|
||||
} else {
|
||||
None
|
||||
return match read_by_all {
|
||||
true => Some((chat_id, msg_id)),
|
||||
false => None,
|
||||
};
|
||||
}
|
||||
None
|
||||
@@ -1371,32 +1342,50 @@ mod tests {
|
||||
some_file.set(Param::File, "foo.bar");
|
||||
|
||||
assert_eq!(
|
||||
get_summarytext_by_raw(Viewtype::Text, some_text.as_ref(), &Params::new(), 50, &ctx),
|
||||
get_summarytext_by_raw(
|
||||
Viewtype::Text,
|
||||
some_text.as_ref(),
|
||||
&mut Params::new(),
|
||||
50,
|
||||
&ctx
|
||||
),
|
||||
"bla bla" // for simple text, the type is not added to the summary
|
||||
);
|
||||
|
||||
assert_eq!(
|
||||
get_summarytext_by_raw(Viewtype::Image, no_text.as_ref(), &some_file, 50, &ctx,),
|
||||
get_summarytext_by_raw(Viewtype::Image, no_text.as_ref(), &mut some_file, 50, &ctx,),
|
||||
"Image" // file names are not added for images
|
||||
);
|
||||
|
||||
assert_eq!(
|
||||
get_summarytext_by_raw(Viewtype::Video, no_text.as_ref(), &some_file, 50, &ctx,),
|
||||
get_summarytext_by_raw(Viewtype::Video, no_text.as_ref(), &mut some_file, 50, &ctx,),
|
||||
"Video" // file names are not added for videos
|
||||
);
|
||||
|
||||
assert_eq!(
|
||||
get_summarytext_by_raw(Viewtype::Gif, no_text.as_ref(), &some_file, 50, &ctx,),
|
||||
get_summarytext_by_raw(Viewtype::Gif, no_text.as_ref(), &mut some_file, 50, &ctx,),
|
||||
"GIF" // file names are not added for GIFs
|
||||
);
|
||||
|
||||
assert_eq!(
|
||||
get_summarytext_by_raw(Viewtype::Sticker, no_text.as_ref(), &some_file, 50, &ctx,),
|
||||
get_summarytext_by_raw(
|
||||
Viewtype::Sticker,
|
||||
no_text.as_ref(),
|
||||
&mut some_file,
|
||||
50,
|
||||
&ctx,
|
||||
),
|
||||
"Sticker" // file names are not added for stickers
|
||||
);
|
||||
|
||||
assert_eq!(
|
||||
get_summarytext_by_raw(Viewtype::Voice, empty_text.as_ref(), &some_file, 50, &ctx,),
|
||||
get_summarytext_by_raw(
|
||||
Viewtype::Voice,
|
||||
empty_text.as_ref(),
|
||||
&mut some_file,
|
||||
50,
|
||||
&ctx,
|
||||
),
|
||||
"Voice message" // file names are not added for voice messages, empty text is skipped
|
||||
);
|
||||
|
||||
@@ -1406,7 +1395,13 @@ mod tests {
|
||||
);
|
||||
|
||||
assert_eq!(
|
||||
get_summarytext_by_raw(Viewtype::Voice, some_text.as_ref(), &some_file, 50, &ctx),
|
||||
get_summarytext_by_raw(
|
||||
Viewtype::Voice,
|
||||
some_text.as_ref(),
|
||||
&mut some_file,
|
||||
50,
|
||||
&ctx
|
||||
),
|
||||
"Voice message \u{2013} bla bla" // `\u{2013}` explicitly checks for "EN DASH"
|
||||
);
|
||||
|
||||
@@ -1416,12 +1411,24 @@ mod tests {
|
||||
);
|
||||
|
||||
assert_eq!(
|
||||
get_summarytext_by_raw(Viewtype::Audio, empty_text.as_ref(), &some_file, 50, &ctx,),
|
||||
get_summarytext_by_raw(
|
||||
Viewtype::Audio,
|
||||
empty_text.as_ref(),
|
||||
&mut some_file,
|
||||
50,
|
||||
&ctx,
|
||||
),
|
||||
"Audio \u{2013} foo.bar" // file name is added for audio, empty text is not added
|
||||
);
|
||||
|
||||
assert_eq!(
|
||||
get_summarytext_by_raw(Viewtype::Audio, some_text.as_ref(), &some_file, 50, &ctx),
|
||||
get_summarytext_by_raw(
|
||||
Viewtype::Audio,
|
||||
some_text.as_ref(),
|
||||
&mut some_file,
|
||||
50,
|
||||
&ctx
|
||||
),
|
||||
"Audio \u{2013} foo.bar \u{2013} bla bla" // file name and text added for audio
|
||||
);
|
||||
|
||||
|
||||
@@ -6,6 +6,7 @@ use crate::config::Config;
|
||||
use crate::constants::*;
|
||||
use crate::contact::*;
|
||||
use crate::context::{get_version_str, Context};
|
||||
use crate::dc_strencode::*;
|
||||
use crate::dc_tools::*;
|
||||
use crate::e2ee::*;
|
||||
use crate::error::Error;
|
||||
@@ -130,12 +131,11 @@ impl<'a, 'b> MimeFactory<'a, 'b> {
|
||||
.get_config(Config::ConfiguredAddr)
|
||||
.unwrap_or_default();
|
||||
|
||||
if !email_to_remove.is_empty()
|
||||
&& !addr_cmp(email_to_remove, self_addr)
|
||||
&& !vec_contains_lowercase(&factory.recipients_addr, &email_to_remove)
|
||||
{
|
||||
factory.recipients_names.push("".to_string());
|
||||
factory.recipients_addr.push(email_to_remove.to_string());
|
||||
if !email_to_remove.is_empty() && !addr_cmp(email_to_remove, self_addr) {
|
||||
if !vec_contains_lowercase(&factory.recipients_addr, &email_to_remove) {
|
||||
factory.recipients_names.push("".to_string());
|
||||
factory.recipients_addr.push(email_to_remove.to_string());
|
||||
}
|
||||
}
|
||||
}
|
||||
if command != SystemMessage::AutocryptSetupMessage
|
||||
@@ -299,7 +299,14 @@ impl<'a, 'b> MimeFactory<'a, 'b> {
|
||||
return true;
|
||||
}
|
||||
|
||||
self.msg.param.get_cmd() == SystemMessage::MemberAddedToGroup
|
||||
match self.msg.param.get_cmd() {
|
||||
SystemMessage::MemberAddedToGroup => {
|
||||
return true;
|
||||
}
|
||||
_ => {}
|
||||
}
|
||||
|
||||
false
|
||||
}
|
||||
Loaded::MDN => false,
|
||||
}
|
||||
@@ -332,19 +339,13 @@ impl<'a, 'b> MimeFactory<'a, 'b> {
|
||||
Loaded::Message => {
|
||||
match self.chat {
|
||||
Some(ref chat) => {
|
||||
let raw = message::get_summarytext_by_raw(
|
||||
let raw_subject = message::get_summarytext_by_raw(
|
||||
self.msg.type_0,
|
||||
self.msg.text.as_ref(),
|
||||
&self.msg.param,
|
||||
32,
|
||||
self.context,
|
||||
);
|
||||
let mut lines = raw.lines();
|
||||
let raw_subject = if let Some(line) = lines.next() {
|
||||
line
|
||||
} else {
|
||||
""
|
||||
};
|
||||
|
||||
let afwd_email = self.msg.param.exists(Param::Forwarded);
|
||||
let fwd = if afwd_email { "Fwd: " } else { "" };
|
||||
@@ -382,7 +383,7 @@ impl<'a, 'b> MimeFactory<'a, 'b> {
|
||||
let mut unprotected_headers: Vec<Header> = Vec::new();
|
||||
|
||||
let from = Address::new_mailbox_with_name(
|
||||
encode_words(&self.from_displayname),
|
||||
dc_encode_header_words(&self.from_displayname),
|
||||
self.from_addr.clone(),
|
||||
);
|
||||
|
||||
@@ -394,7 +395,7 @@ impl<'a, 'b> MimeFactory<'a, 'b> {
|
||||
to.push(Address::new_mailbox(addr.clone()));
|
||||
} else {
|
||||
to.push(Address::new_mailbox_with_name(
|
||||
encode_words(name),
|
||||
dc_encode_header_words(name),
|
||||
addr.clone(),
|
||||
));
|
||||
}
|
||||
@@ -450,7 +451,7 @@ impl<'a, 'b> MimeFactory<'a, 'b> {
|
||||
let e2ee_guranteed = self.is_e2ee_guranteed();
|
||||
let mut encrypt_helper = EncryptHelper::new(self.context)?;
|
||||
|
||||
let subject = encode_words(&subject_str);
|
||||
let subject = dc_encode_header_words(subject_str);
|
||||
|
||||
let mut message = match self.loaded {
|
||||
Loaded::Message => {
|
||||
@@ -472,6 +473,17 @@ impl<'a, 'b> MimeFactory<'a, 'b> {
|
||||
encrypt_helper.should_encrypt(self.context, e2ee_guranteed, &peerstates)?;
|
||||
let is_encrypted = should_encrypt && force_plaintext == 0;
|
||||
|
||||
// Add gossip headers
|
||||
if do_gossip {
|
||||
for peerstate in peerstates.iter().filter_map(|(state, _)| state.as_ref()) {
|
||||
if peerstate.peek_key(min_verified).is_some() {
|
||||
if let Some(header) = peerstate.render_gossip_header(min_verified) {
|
||||
protected_headers.push(Header::new("Autocrypt-Gossip".into(), header));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
let rfc724_mid = match self.loaded {
|
||||
Loaded::Message => self.msg.rfc724_mid.clone(),
|
||||
Loaded::MDN => dc_create_outgoing_rfc724_mid(None, &self.from_addr),
|
||||
@@ -483,23 +495,10 @@ impl<'a, 'b> MimeFactory<'a, 'b> {
|
||||
unprotected_headers.push(Header::new_with_value("From".into(), vec![from]).unwrap());
|
||||
|
||||
let outer_message = if is_encrypted {
|
||||
// Add gossip headers
|
||||
if do_gossip {
|
||||
for peerstate in peerstates.iter().filter_map(|(state, _)| state.as_ref()) {
|
||||
if peerstate.peek_key(min_verified).is_some() {
|
||||
if let Some(header) = peerstate.render_gossip_header(min_verified) {
|
||||
message =
|
||||
message.header(Header::new("Autocrypt-Gossip".into(), header));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Store protected headers in the inner message.
|
||||
for header in protected_headers.into_iter() {
|
||||
message = message.header(header);
|
||||
}
|
||||
|
||||
// Set the appropriate Content-Type for the inner message.
|
||||
let mut existing_ct = message
|
||||
.get_header("Content-Type".to_string())
|
||||
@@ -517,7 +516,7 @@ impl<'a, 'b> MimeFactory<'a, 'b> {
|
||||
// Set the appropriate Content-Type for the outer message
|
||||
let mut outer_message = PartBuilder::new().header((
|
||||
"Content-Type".to_string(),
|
||||
"multipart/encrypted; protocol=\"application/pgp-encrypted\"".to_string(),
|
||||
"multipart/encrypted; protocol=\"application/pgp-encrypted\";".to_string(),
|
||||
));
|
||||
|
||||
// Store the unprotected headers on the outer message.
|
||||
@@ -610,7 +609,7 @@ impl<'a, 'b> MimeFactory<'a, 'b> {
|
||||
if chat.typ == Chattype::Group || chat.typ == Chattype::VerifiedGroup {
|
||||
protected_headers.push(Header::new("Chat-Group-ID".into(), chat.grpid.clone()));
|
||||
|
||||
let encoded = encode_words(&chat.name);
|
||||
let encoded = dc_encode_header_words(&chat.name);
|
||||
protected_headers.push(Header::new("Chat-Group-Name".into(), encoded));
|
||||
|
||||
match command {
|
||||
@@ -980,18 +979,20 @@ fn build_body_file(
|
||||
}
|
||||
};
|
||||
|
||||
let needs_ext = dc_needs_ext_header(&filename_to_send);
|
||||
|
||||
// create mime part, for Content-Disposition, see RFC 2183.
|
||||
// `Content-Disposition: attachment` seems not to make a difference to `Content-Disposition: inline`
|
||||
// at least on tested Thunderbird and Gma'l in 2017.
|
||||
// But I've heard about problems with inline and outl'k, so we just use the attachment-type until we
|
||||
// run into other problems ...
|
||||
let cd_value = if needs_encoding(&filename_to_send) {
|
||||
let cd_value = if needs_ext {
|
||||
format!("attachment; filename=\"{}\"", &filename_to_send)
|
||||
} else {
|
||||
format!(
|
||||
"attachment; filename*=\"{}\"",
|
||||
encode_words(&filename_to_send)
|
||||
dc_encode_header_words(&filename_to_send)
|
||||
)
|
||||
} else {
|
||||
format!("attachment; filename=\"{}\"", &filename_to_send)
|
||||
};
|
||||
|
||||
let body = std::fs::read(blob.to_abs_path())?;
|
||||
@@ -1025,23 +1026,3 @@ fn is_file_size_okay(context: &Context, msg: &Message) -> bool {
|
||||
None => false,
|
||||
}
|
||||
}
|
||||
|
||||
/* ******************************************************************************
|
||||
* Encode/decode header words, RFC 2047
|
||||
******************************************************************************/
|
||||
|
||||
fn encode_words(word: &str) -> String {
|
||||
encoded_words::encode(word, None, encoded_words::EncodingFlag::Shortest, None)
|
||||
}
|
||||
|
||||
pub fn needs_encoding(to_check: impl AsRef<str>) -> bool {
|
||||
let to_check = to_check.as_ref();
|
||||
|
||||
if to_check.is_empty() {
|
||||
return false;
|
||||
}
|
||||
|
||||
to_check.chars().any(|c| {
|
||||
!c.is_ascii_alphanumeric() && c != '-' && c != '_' && c != '.' && c != '~' && c != '%'
|
||||
})
|
||||
}
|
||||
|
||||
@@ -148,11 +148,11 @@ impl<'a> MimeParser<'a> {
|
||||
self.subject = Some(field.clone());
|
||||
}
|
||||
|
||||
if self.lookup_field("Chat-Version").is_some() {
|
||||
if let Some(_) = self.lookup_field("Chat-Version") {
|
||||
self.is_send_by_messenger = true
|
||||
}
|
||||
|
||||
if self.lookup_field("Autocrypt-Setup-Message").is_some() {
|
||||
if let Some(_) = self.lookup_field("Autocrypt-Setup-Message") {
|
||||
let has_setup_file = self.parts.iter().any(|p| {
|
||||
p.mimetype.is_some() && p.mimetype.as_ref().unwrap().as_ref() == MIME_AC_SETUP_FILE
|
||||
});
|
||||
@@ -188,12 +188,14 @@ impl<'a> MimeParser<'a> {
|
||||
self.is_system_message = SystemMessage::LocationStreamingEnabled;
|
||||
}
|
||||
}
|
||||
if self.lookup_field("Chat-Group-Image").is_some() && !self.parts.is_empty() {
|
||||
let textpart = &self.parts[0];
|
||||
if textpart.typ == Viewtype::Text && self.parts.len() >= 2 {
|
||||
let imgpart = &mut self.parts[1];
|
||||
if imgpart.typ == Viewtype::Image {
|
||||
imgpart.is_meta = true;
|
||||
if let Some(_) = self.lookup_field("Chat-Group-Image") {
|
||||
if !self.parts.is_empty() {
|
||||
let textpart = &self.parts[0];
|
||||
if textpart.typ == Viewtype::Text && self.parts.len() >= 2 {
|
||||
let imgpart = &mut self.parts[1];
|
||||
if imgpart.typ == Viewtype::Image {
|
||||
imgpart.is_meta = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -262,11 +264,11 @@ impl<'a> MimeParser<'a> {
|
||||
}
|
||||
}
|
||||
if self.parts.len() == 1 {
|
||||
if self.parts[0].typ == Viewtype::Audio
|
||||
&& self.lookup_field("Chat-Voice-Message").is_some()
|
||||
{
|
||||
let part_mut = &mut self.parts[0];
|
||||
part_mut.typ = Viewtype::Voice;
|
||||
if self.parts[0].typ == Viewtype::Audio {
|
||||
if let Some(_) = self.lookup_field("Chat-Voice-Message") {
|
||||
let part_mut = &mut self.parts[0];
|
||||
part_mut.typ = Viewtype::Voice;
|
||||
}
|
||||
}
|
||||
if self.parts[0].typ == Viewtype::Image {
|
||||
if let Some(value) = self.lookup_field("Chat-Content") {
|
||||
@@ -599,7 +601,7 @@ impl<'a> MimeParser<'a> {
|
||||
|
||||
// if there is still no filename, guess one
|
||||
if desired_filename.is_empty() {
|
||||
if let Some(subtype) = mail.ctype.mimetype.split('/').nth(1) {
|
||||
if let Some(subtype) = mail.ctype.mimetype.split('/').skip(1).next() {
|
||||
desired_filename = format!("file.{}", subtype,);
|
||||
} else {
|
||||
return Ok(false);
|
||||
@@ -624,7 +626,7 @@ impl<'a> MimeParser<'a> {
|
||||
&mut self,
|
||||
msg_type: Viewtype,
|
||||
mime_type: Mime,
|
||||
raw_mime: &str,
|
||||
raw_mime: &String,
|
||||
decoded_data: &[u8],
|
||||
filename: &str,
|
||||
) {
|
||||
@@ -683,7 +685,7 @@ impl<'a> MimeParser<'a> {
|
||||
|
||||
fn do_add_single_part(&mut self, mut part: Part) {
|
||||
if self.encrypted {
|
||||
if !self.signatures.is_empty() {
|
||||
if self.signatures.len() > 0 {
|
||||
part.param.set_int(Param::GuaranteeE2ee, 1);
|
||||
} else {
|
||||
// XXX if the message was encrypted but not signed
|
||||
@@ -696,15 +698,17 @@ impl<'a> MimeParser<'a> {
|
||||
}
|
||||
|
||||
pub fn is_mailinglist_message(&self) -> bool {
|
||||
if self.lookup_field("List-Id").is_some() {
|
||||
if let Some(_) = self.lookup_field("List-Id") {
|
||||
return true;
|
||||
}
|
||||
|
||||
if let Some(precedence) = self.lookup_field("Precedence") {
|
||||
precedence == "list" || precedence == "bulk"
|
||||
} else {
|
||||
false
|
||||
if precedence == "list" || precedence == "bulk" {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
false
|
||||
}
|
||||
|
||||
pub fn sender_equals_recipient(&self) -> bool {
|
||||
@@ -961,12 +965,13 @@ fn mailmime_is_attachment_disposition(mail: &mailparse::ParsedMail<'_>) -> bool
|
||||
}
|
||||
|
||||
// returned addresses are normalized.
|
||||
fn get_recipients<S: AsRef<str>, T: Iterator<Item = (S, S)>>(headers: T) -> HashSet<String> {
|
||||
fn get_recipients<'a, S: AsRef<str>, T: Iterator<Item = (S, S)>>(headers: T) -> HashSet<String> {
|
||||
let mut recipients: HashSet<String> = Default::default();
|
||||
|
||||
for (hkey, hvalue) in headers {
|
||||
let hkey = hkey.as_ref().to_lowercase();
|
||||
let hkey = hkey.as_ref();
|
||||
let hvalue = hvalue.as_ref();
|
||||
|
||||
if hkey == "to" || hkey == "cc" {
|
||||
if let Ok(addrs) = mailparse::addrparse(hvalue) {
|
||||
for addr in addrs.iter() {
|
||||
@@ -1053,17 +1058,6 @@ mod tests {
|
||||
assert_eq!(mimeparser.get_rfc724_mid(), None);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_get_recipients() {
|
||||
let context = dummy_context();
|
||||
let raw = include_bytes!("../test-data/message/mail_with_cc.txt");
|
||||
let mimeparser = MimeParser::from_bytes(&context.ctx, &raw[..]).unwrap();
|
||||
let recipients = get_recipients(mimeparser.header.iter());
|
||||
assert!(recipients.contains("abc@bcd.com"));
|
||||
assert!(recipients.contains("def@def.de"));
|
||||
assert_eq!(recipients.len(), 2);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_mailparse_content_type() {
|
||||
let ctype =
|
||||
|
||||
10
src/param.rs
10
src/param.rs
@@ -50,8 +50,8 @@ pub enum Param {
|
||||
Error = b'L',
|
||||
/// For Messages: space-separated list of messaged IDs of forwarded copies.
|
||||
///
|
||||
/// This is used when a [crate::message::Message] is in the
|
||||
/// [crate::message::MessageState::OutPending] state but is already forwarded.
|
||||
/// This is used when a [Message] is in the
|
||||
/// [MessageState::OutPending] state but is already forwarded.
|
||||
/// In this case the forwarded messages are written to the
|
||||
/// database and their message IDs are added to this parameter of
|
||||
/// the original message, which is also saved in the database.
|
||||
@@ -222,7 +222,7 @@ impl Params {
|
||||
Some(val) => val,
|
||||
None => return Ok(None),
|
||||
};
|
||||
ParamsFile::from_param(context, val).map(Some)
|
||||
ParamsFile::from_param(context, val).map(|file| Some(file))
|
||||
}
|
||||
|
||||
/// Gets the parameter and returns a [BlobObject] for it.
|
||||
@@ -373,7 +373,7 @@ mod tests {
|
||||
if let ParamsFile::FsPath(p) = ParamsFile::from_param(&t.ctx, "/foo/bar/baz").unwrap() {
|
||||
assert_eq!(p, Path::new("/foo/bar/baz"));
|
||||
} else {
|
||||
panic!("Wrong enum variant");
|
||||
assert!(false, "Wrong enum variant");
|
||||
}
|
||||
}
|
||||
|
||||
@@ -383,7 +383,7 @@ mod tests {
|
||||
if let ParamsFile::Blob(b) = ParamsFile::from_param(&t.ctx, "$BLOBDIR/foo").unwrap() {
|
||||
assert_eq!(b.as_name(), "$BLOBDIR/foo");
|
||||
} else {
|
||||
panic!("Wrong enum variant");
|
||||
assert!(false, "Wrong enum variant");
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -230,7 +230,6 @@ pub fn pk_encrypt(
|
||||
Ok(encoded_msg)
|
||||
}
|
||||
|
||||
#[allow(clippy::implicit_hasher)]
|
||||
pub fn pk_decrypt(
|
||||
ctext: &[u8],
|
||||
private_keys_for_decryption: &Keyring,
|
||||
|
||||
@@ -533,10 +533,12 @@ pub(crate) fn handle_securejoin_handshake(
|
||||
if group_chat_id == 0 {
|
||||
error!(context, "Chat {} not found.", &field_grpid);
|
||||
return Ok(ret);
|
||||
} else if let Err(err) =
|
||||
chat::add_contact_to_chat_ex(context, group_chat_id, contact_id, true)
|
||||
{
|
||||
error!(context, "failed to add contact: {}", err);
|
||||
} else {
|
||||
if let Err(err) =
|
||||
chat::add_contact_to_chat_ex(context, group_chat_id, contact_id, true)
|
||||
{
|
||||
error!(context, "failed to add contact: {}", err);
|
||||
}
|
||||
}
|
||||
} else {
|
||||
send_handshake_msg(context, contact_chat_id, "vc-contact-confirm", "", None, "");
|
||||
@@ -641,7 +643,7 @@ pub(crate) fn handle_securejoin_handshake(
|
||||
let (group_chat_id, _, _) = chat::get_chat_id_by_grpid(context, &field_grpid);
|
||||
context.call_cb(Event::SecurejoinMemberAdded {
|
||||
chat_id: group_chat_id,
|
||||
contact_id,
|
||||
contact_id: contact_id,
|
||||
});
|
||||
} else {
|
||||
warn!(context, "vg-member-added-received invalid.",);
|
||||
|
||||
@@ -33,7 +33,7 @@ pub enum Error {
|
||||
|
||||
pub type Result<T> = std::result::Result<T, Error>;
|
||||
|
||||
#[derive(Default, DebugStub)]
|
||||
#[derive(DebugStub)]
|
||||
pub struct Smtp {
|
||||
#[debug_stub(some = "SmtpTransport")]
|
||||
transport: Option<lettre::smtp::SmtpTransport>,
|
||||
@@ -45,7 +45,11 @@ pub struct Smtp {
|
||||
impl Smtp {
|
||||
/// Create a new Smtp instances.
|
||||
pub fn new() -> Self {
|
||||
Default::default()
|
||||
Smtp {
|
||||
transport: None,
|
||||
transport_connected: false,
|
||||
from: None,
|
||||
}
|
||||
}
|
||||
|
||||
/// Disconnect the SMTP transport and drop it entirely.
|
||||
@@ -76,12 +80,15 @@ impl Smtp {
|
||||
return Err(Error::BadParameters);
|
||||
}
|
||||
|
||||
let from =
|
||||
EmailAddress::new(lp.addr.clone()).map_err(|err| Error::InvalidLoginAddress {
|
||||
address: lp.addr.clone(),
|
||||
error: err,
|
||||
})?;
|
||||
self.from = Some(from);
|
||||
self.from = match EmailAddress::new(lp.addr.clone()) {
|
||||
Ok(addr) => Some(addr),
|
||||
Err(err) => {
|
||||
return Err(Error::InvalidLoginAddress {
|
||||
address: lp.addr.clone(),
|
||||
error: err,
|
||||
})
|
||||
}
|
||||
};
|
||||
|
||||
let domain = &lp.send_server;
|
||||
let port = lp.send_port as u16;
|
||||
@@ -128,23 +135,30 @@ impl Smtp {
|
||||
lettre::smtp::ClientSecurity::Wrapper(tls_parameters)
|
||||
};
|
||||
|
||||
let client = lettre::smtp::SmtpClient::new((domain.as_str(), port), security)
|
||||
.map_err(Error::ConnectionSetupFailure)?;
|
||||
|
||||
let client = client
|
||||
.smtp_utf8(true)
|
||||
.credentials(creds)
|
||||
.authentication_mechanism(mechanism)
|
||||
.connection_reuse(lettre::smtp::ConnectionReuseParameters::ReuseUnlimited);
|
||||
let mut trans = client.transport();
|
||||
trans.connect().map_err(Error::ConnectionFailure)?;
|
||||
|
||||
self.transport = Some(trans);
|
||||
self.transport_connected = true;
|
||||
context.call_cb(Event::SmtpConnected(format!(
|
||||
"SMTP-LOGIN as {} ok",
|
||||
lp.send_user,
|
||||
)));
|
||||
Ok(())
|
||||
match lettre::smtp::SmtpClient::new((domain.as_str(), port), security) {
|
||||
Ok(client) => {
|
||||
let client = client
|
||||
.smtp_utf8(true)
|
||||
.credentials(creds)
|
||||
.authentication_mechanism(mechanism)
|
||||
.connection_reuse(lettre::smtp::ConnectionReuseParameters::ReuseUnlimited);
|
||||
let mut trans = client.transport();
|
||||
match trans.connect() {
|
||||
Ok(()) => {
|
||||
self.transport = Some(trans);
|
||||
self.transport_connected = true;
|
||||
context.call_cb(Event::SmtpConnected(format!(
|
||||
"SMTP-LOGIN as {} ok",
|
||||
lp.send_user,
|
||||
)));
|
||||
return Ok(());
|
||||
}
|
||||
Err(err) => return Err(Error::ConnectionFailure(err)),
|
||||
}
|
||||
}
|
||||
Err(err) => {
|
||||
return Err(Error::ConnectionSetupFailure(err));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -58,7 +58,7 @@ impl Smtp {
|
||||
context,
|
||||
"uh? SMTP has no transport, failed to send to {}", recipients_display
|
||||
);
|
||||
Err(Error::NoTransport)
|
||||
return Err(Error::NoTransport);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
106
src/sql.rs
106
src/sql.rs
@@ -68,19 +68,13 @@ pub struct Sql {
|
||||
in_use: Arc<ThreadLocal<String>>,
|
||||
}
|
||||
|
||||
impl Default for Sql {
|
||||
fn default() -> Self {
|
||||
Self {
|
||||
impl Sql {
|
||||
pub fn new() -> Sql {
|
||||
Sql {
|
||||
pool: RwLock::new(None),
|
||||
in_use: Arc::new(ThreadLocal::new()),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Sql {
|
||||
pub fn new() -> Sql {
|
||||
Self::default()
|
||||
}
|
||||
|
||||
pub fn is_open(&self) -> bool {
|
||||
self.pool.read().unwrap().is_some()
|
||||
@@ -547,6 +541,7 @@ fn open(
|
||||
|
||||
let mut dbversion = dbversion_before_update;
|
||||
let mut recalc_fingerprints = 0;
|
||||
let mut update_file_paths = 0;
|
||||
let mut update_icons = false;
|
||||
|
||||
if dbversion < 1 {
|
||||
@@ -711,6 +706,19 @@ fn open(
|
||||
"CREATE INDEX acpeerstates_index5 ON acpeerstates (verified_key_fingerprint);",
|
||||
params![],
|
||||
)?;
|
||||
if dbversion_before_update == 34 {
|
||||
// migrate database from the use of verified-flags to verified_key,
|
||||
// _only_ version 34 (0.17.0) has the fields public_key_verified and gossip_key_verified
|
||||
// this block can be deleted in half a year or so (created 5/2018)
|
||||
sql.execute(
|
||||
"UPDATE acpeerstates SET verified_key=gossip_key, verified_key_fingerprint=gossip_key_fingerprint WHERE gossip_key_verified=2;",
|
||||
params![]
|
||||
)?;
|
||||
sql.execute(
|
||||
"UPDATE acpeerstates SET verified_key=public_key, verified_key_fingerprint=public_key_fingerprint WHERE public_key_verified=2;",
|
||||
params![]
|
||||
)?;
|
||||
}
|
||||
dbversion = 39;
|
||||
sql.set_raw_config_int(context, "dbversion", 39)?;
|
||||
}
|
||||
@@ -723,6 +731,20 @@ fn open(
|
||||
dbversion = 40;
|
||||
sql.set_raw_config_int(context, "dbversion", 40)?;
|
||||
}
|
||||
if dbversion < 41 {
|
||||
info!(context, "[migration] v41");
|
||||
update_file_paths = 1;
|
||||
dbversion = 41;
|
||||
sql.set_raw_config_int(context, "dbversion", 41)?;
|
||||
}
|
||||
if dbversion < 42 {
|
||||
info!(context, "[migration] v42");
|
||||
// older versions set the txt-field to the filenames, for debugging and fulltext search.
|
||||
// to allow text+attachment compound messages, we need to reset these fields.
|
||||
sql.execute("UPDATE msgs SET txt='' WHERE type!=10", params![])?;
|
||||
dbversion = 42;
|
||||
sql.set_raw_config_int(context, "dbversion", 42)?;
|
||||
}
|
||||
if dbversion < 44 {
|
||||
info!(context, "[migration] v44");
|
||||
sql.execute("ALTER TABLE msgs ADD COLUMN mime_headers TEXT;", params![])?;
|
||||
@@ -884,6 +906,34 @@ fn open(
|
||||
},
|
||||
)?;
|
||||
}
|
||||
if 0 != update_file_paths {
|
||||
// versions before 2018-08 save the absolute paths in the database files at "param.f=";
|
||||
// for newer versions, we copy files always to the blob directory and store relative paths.
|
||||
// this snippet converts older databases and can be removed after some time.
|
||||
info!(context, "[migration] update file paths");
|
||||
let repl_from = sql
|
||||
.get_raw_config(context, "backup_for")
|
||||
.unwrap_or_else(|| context.get_blobdir().to_string_lossy().into());
|
||||
|
||||
let repl_from = dc_ensure_no_slash_safe(&repl_from);
|
||||
sql.execute(
|
||||
&format!(
|
||||
"UPDATE msgs SET param=replace(param, 'f={}/', 'f=$BLOBDIR/')",
|
||||
repl_from
|
||||
),
|
||||
NO_PARAMS,
|
||||
)?;
|
||||
|
||||
sql.execute(
|
||||
&format!(
|
||||
"UPDATE chats SET param=replace(param, 'i={}/', 'i=$BLOBDIR/');",
|
||||
repl_from
|
||||
),
|
||||
NO_PARAMS,
|
||||
)?;
|
||||
|
||||
sql.set_raw_config(context, "backup_for", None)?;
|
||||
}
|
||||
if update_icons {
|
||||
update_saved_messages_icon(context)?;
|
||||
}
|
||||
@@ -952,12 +1002,13 @@ pub fn get_rowid_with_conn(
|
||||
// the ORDER BY ensures, this function always returns the most recent id,
|
||||
// eg. if a Message-ID is split into different messages.
|
||||
let query = format!(
|
||||
"SELECT id FROM {} WHERE {}=? ORDER BY id DESC",
|
||||
"SELECT id FROM {} WHERE {}='{}' ORDER BY id DESC",
|
||||
table.as_ref(),
|
||||
field.as_ref(),
|
||||
value.as_ref()
|
||||
);
|
||||
|
||||
match conn.query_row(&query, params![value.as_ref()], |row| row.get::<_, u32>(0)) {
|
||||
match conn.query_row(&query, NO_PARAMS, |row| row.get::<_, u32>(0)) {
|
||||
Ok(id) => id,
|
||||
Err(err) => {
|
||||
error!(
|
||||
@@ -1089,23 +1140,26 @@ pub fn housekeeping(context: &Context) {
|
||||
|
||||
unreferenced_count += 1;
|
||||
|
||||
if let Ok(stats) = std::fs::metadata(entry.path()) {
|
||||
let recently_created =
|
||||
stats.created().is_ok() && stats.created().unwrap() > keep_files_newer_than;
|
||||
let recently_modified = stats.modified().is_ok()
|
||||
&& stats.modified().unwrap() > keep_files_newer_than;
|
||||
let recently_accessed = stats.accessed().is_ok()
|
||||
&& stats.accessed().unwrap() > keep_files_newer_than;
|
||||
match std::fs::metadata(entry.path()) {
|
||||
Ok(stats) => {
|
||||
let recently_created = stats.created().is_ok()
|
||||
&& stats.created().unwrap() > keep_files_newer_than;
|
||||
let recently_modified = stats.modified().is_ok()
|
||||
&& stats.modified().unwrap() > keep_files_newer_than;
|
||||
let recently_accessed = stats.accessed().is_ok()
|
||||
&& stats.accessed().unwrap() > keep_files_newer_than;
|
||||
|
||||
if recently_created || recently_modified || recently_accessed {
|
||||
info!(
|
||||
context,
|
||||
"Housekeeping: Keeping new unreferenced file #{}: {:?}",
|
||||
unreferenced_count,
|
||||
entry.file_name(),
|
||||
);
|
||||
continue;
|
||||
if recently_created || recently_modified || recently_accessed {
|
||||
info!(
|
||||
context,
|
||||
"Housekeeping: Keeping new unreferenced file #{}: {:?}",
|
||||
unreferenced_count,
|
||||
entry.file_name(),
|
||||
);
|
||||
continue;
|
||||
}
|
||||
}
|
||||
Err(_) => {}
|
||||
}
|
||||
info!(
|
||||
context,
|
||||
|
||||
130
src/stock.rs
130
src/stock.rs
@@ -5,15 +5,9 @@ use std::borrow::Cow;
|
||||
use strum::EnumProperty;
|
||||
use strum_macros::EnumProperty;
|
||||
|
||||
use crate::blob::BlobObject;
|
||||
use crate::chat;
|
||||
use crate::constants::{Viewtype, DC_CONTACT_ID_SELF};
|
||||
use crate::contact::*;
|
||||
use crate::context::Context;
|
||||
use crate::error::Error;
|
||||
use crate::message::Message;
|
||||
use crate::param::Param;
|
||||
use crate::stock::StockMessage::{DeviceMessagesHint, WelcomeMessage};
|
||||
|
||||
/// Stock strings
|
||||
///
|
||||
@@ -28,174 +22,107 @@ use crate::stock::StockMessage::{DeviceMessagesHint, WelcomeMessage};
|
||||
pub enum StockMessage {
|
||||
#[strum(props(fallback = "No messages."))]
|
||||
NoMessages = 1,
|
||||
|
||||
#[strum(props(fallback = "Me"))]
|
||||
SelfMsg = 2,
|
||||
|
||||
#[strum(props(fallback = "Draft"))]
|
||||
Draft = 3,
|
||||
|
||||
#[strum(props(fallback = "%1$s member(s)"))]
|
||||
Member = 4,
|
||||
|
||||
#[strum(props(fallback = "%1$s contact(s)"))]
|
||||
Contact = 6,
|
||||
|
||||
#[strum(props(fallback = "Voice message"))]
|
||||
VoiceMessage = 7,
|
||||
|
||||
#[strum(props(fallback = "Contact requests"))]
|
||||
DeadDrop = 8,
|
||||
|
||||
#[strum(props(fallback = "Image"))]
|
||||
Image = 9,
|
||||
|
||||
#[strum(props(fallback = "Video"))]
|
||||
Video = 10,
|
||||
|
||||
#[strum(props(fallback = "Audio"))]
|
||||
Audio = 11,
|
||||
|
||||
#[strum(props(fallback = "File"))]
|
||||
File = 12,
|
||||
|
||||
#[strum(props(fallback = "Sent with my Delta Chat Messenger: https://delta.chat"))]
|
||||
StatusLine = 13,
|
||||
|
||||
#[strum(props(fallback = "Hello, I\'ve just created the group \"%1$s\" for us."))]
|
||||
NewGroupDraft = 14,
|
||||
|
||||
#[strum(props(fallback = "Group name changed from \"%1$s\" to \"%2$s\"."))]
|
||||
MsgGrpName = 15,
|
||||
|
||||
#[strum(props(fallback = "Group image changed."))]
|
||||
MsgGrpImgChanged = 16,
|
||||
|
||||
#[strum(props(fallback = "Member %1$s added."))]
|
||||
MsgAddMember = 17,
|
||||
|
||||
#[strum(props(fallback = "Member %1$s removed."))]
|
||||
MsgDelMember = 18,
|
||||
|
||||
#[strum(props(fallback = "Group left."))]
|
||||
MsgGroupLeft = 19,
|
||||
|
||||
#[strum(props(fallback = "GIF"))]
|
||||
Gif = 23,
|
||||
|
||||
#[strum(props(fallback = "Encrypted message"))]
|
||||
EncryptedMsg = 24,
|
||||
|
||||
#[strum(props(fallback = "End-to-end encryption available."))]
|
||||
E2eAvailable = 25,
|
||||
|
||||
#[strum(props(fallback = "Transport-encryption."))]
|
||||
EncrTransp = 27,
|
||||
|
||||
#[strum(props(fallback = "No encryption."))]
|
||||
EncrNone = 28,
|
||||
|
||||
#[strum(props(fallback = "This message was encrypted for another setup."))]
|
||||
CantDecryptMsgBody = 29,
|
||||
|
||||
#[strum(props(fallback = "Fingerprints"))]
|
||||
FingerPrints = 30,
|
||||
|
||||
#[strum(props(fallback = "Return receipt"))]
|
||||
ReadRcpt = 31,
|
||||
|
||||
#[strum(props(fallback = "This is a return receipt for the message \"%1$s\"."))]
|
||||
ReadRcptMailBody = 32,
|
||||
|
||||
#[strum(props(fallback = "Group image deleted."))]
|
||||
MsgGrpImgDeleted = 33,
|
||||
|
||||
#[strum(props(fallback = "End-to-end encryption preferred."))]
|
||||
E2ePreferred = 34,
|
||||
|
||||
#[strum(props(fallback = "%1$s verified."))]
|
||||
ContactVerified = 35,
|
||||
|
||||
#[strum(props(fallback = "Cannot verify %1$s"))]
|
||||
ContactNotVerified = 36,
|
||||
|
||||
#[strum(props(fallback = "Changed setup for %1$s"))]
|
||||
ContactSetupChanged = 37,
|
||||
|
||||
#[strum(props(fallback = "Archived chats"))]
|
||||
ArchivedChats = 40,
|
||||
|
||||
#[strum(props(fallback = "Starred messages"))]
|
||||
StarredMsgs = 41,
|
||||
|
||||
#[strum(props(fallback = "Autocrypt Setup Message"))]
|
||||
AcSetupMsgSubject = 42,
|
||||
|
||||
#[strum(props(
|
||||
fallback = "This is the Autocrypt Setup Message used to transfer your key between clients.\n\nTo decrypt and use your key, open the message in an Autocrypt-compliant client and enter the setup code presented on the generating device."
|
||||
))]
|
||||
AcSetupMsgBody = 43,
|
||||
|
||||
#[strum(props(fallback = "Messages I sent to myself"))]
|
||||
SelfTalkSubTitle = 50,
|
||||
|
||||
#[strum(props(fallback = "Cannot login as %1$s."))]
|
||||
CannotLogin = 60,
|
||||
|
||||
#[strum(props(fallback = "Could not connect to %1$s: %2$s"))]
|
||||
ServerResponse = 61,
|
||||
|
||||
#[strum(props(fallback = "%1$s by %2$s."))]
|
||||
MsgActionByUser = 62,
|
||||
|
||||
#[strum(props(fallback = "%1$s by me."))]
|
||||
MsgActionByMe = 63,
|
||||
|
||||
#[strum(props(fallback = "Location streaming enabled."))]
|
||||
MsgLocationEnabled = 64,
|
||||
|
||||
#[strum(props(fallback = "Location streaming disabled."))]
|
||||
MsgLocationDisabled = 65,
|
||||
|
||||
#[strum(props(fallback = "Location"))]
|
||||
Location = 66,
|
||||
|
||||
#[strum(props(fallback = "Sticker"))]
|
||||
Sticker = 67,
|
||||
|
||||
#[strum(props(fallback = "Device messages"))]
|
||||
DeviceMessages = 68,
|
||||
|
||||
#[strum(props(fallback = "Saved messages"))]
|
||||
SavedMessages = 69,
|
||||
|
||||
#[strum(props(
|
||||
fallback = "Messages in this chat are generated locally by your Delta Chat app. \
|
||||
Its makers use it to inform about app updates and problems during usage."
|
||||
))]
|
||||
DeviceMessagesHint = 70,
|
||||
|
||||
#[strum(props(fallback = "Welcome to Delta Chat! – \
|
||||
Delta Chat looks and feels like other popular messenger apps, \
|
||||
but does not involve centralized control, \
|
||||
tracking or selling you, friends, colleagues or family out to large organizations.\n\n\
|
||||
Technically, Delta Chat is an email application with a modern chat interface. \
|
||||
Email in a new dress if you will 👻\n\n\
|
||||
Use Delta Chat with anyone out of billions of people: just use their e-mail address. \
|
||||
Recipients don't need to install Delta Chat, visit websites or sign up anywhere - \
|
||||
however, of course, if they like, you may point them to 👉 https://get.delta.chat"))]
|
||||
WelcomeMessage = 71,
|
||||
}
|
||||
|
||||
/*
|
||||
"
|
||||
*/
|
||||
|
||||
impl StockMessage {
|
||||
/// Default untranslated strings for stock messages.
|
||||
///
|
||||
/// These could be used in logging calls, so no logging here.
|
||||
fn fallback(self) -> &'static str {
|
||||
fn fallback(&self) -> &'static str {
|
||||
self.get_str("fallback").unwrap_or_default()
|
||||
}
|
||||
}
|
||||
@@ -240,7 +167,7 @@ impl Context {
|
||||
.unwrap()
|
||||
.get(&(id as usize))
|
||||
{
|
||||
Some(ref x) => Cow::Owned((*x).to_string()),
|
||||
Some(ref x) => Cow::Owned(x.to_string()),
|
||||
None => Cow::Borrowed(id.fallback()),
|
||||
}
|
||||
}
|
||||
@@ -337,40 +264,6 @@ impl Context {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub fn update_device_chats(&self) -> Result<(), Error> {
|
||||
// check for the LAST added device message - if it is present, we can skip message creation.
|
||||
// this is worthwhile as this function is typically called
|
||||
// by the ui on every probram start or even on every opening of the chatlist.
|
||||
if chat::was_device_msg_ever_added(&self, "core-welcome")? {
|
||||
return Ok(());
|
||||
}
|
||||
|
||||
// create saved-messages chat;
|
||||
// we do this only once, if the user has deleted the chat, he can recreate it manually.
|
||||
if !self.sql.get_raw_config_bool(&self, "self-chat-added") {
|
||||
self.sql
|
||||
.set_raw_config_bool(&self, "self-chat-added", true)?;
|
||||
chat::create_by_contact_id(&self, DC_CONTACT_ID_SELF)?;
|
||||
}
|
||||
|
||||
// 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(self.stock_str(DeviceMessagesHint).to_string());
|
||||
chat::add_device_msg(&self, Some("core-about-device-chat"), Some(&mut msg))?;
|
||||
|
||||
let image = include_bytes!("../assets/welcome-image.jpg");
|
||||
let blob = BlobObject::create(&self, "welcome-image.jpg".to_string(), image)?;
|
||||
let mut msg = Message::new(Viewtype::Image);
|
||||
msg.param.set(Param::File, blob.as_name());
|
||||
chat::add_device_msg(&self, Some("core-welcome-image"), Some(&mut msg))?;
|
||||
|
||||
let mut msg = Message::new(Viewtype::Text);
|
||||
msg.text = Some(self.stock_str(WelcomeMessage).to_string());
|
||||
chat::add_device_msg(&self, Some("core-welcome"), Some(&mut msg))?;
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
@@ -380,7 +273,6 @@ mod tests {
|
||||
|
||||
use crate::constants::DC_CONTACT_ID_SELF;
|
||||
|
||||
use crate::chatlist::Chatlist;
|
||||
use num_traits::ToPrimitive;
|
||||
|
||||
#[test]
|
||||
@@ -536,22 +428,4 @@ mod tests {
|
||||
"Group name changed from \"Some chat\" to \"Other chat\" by Alice (alice@example.com)."
|
||||
)
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_update_device_chats() {
|
||||
let t = dummy_context();
|
||||
t.ctx.update_device_chats().ok();
|
||||
let chats = Chatlist::try_load(&t.ctx, 0, None, None).unwrap();
|
||||
assert_eq!(chats.len(), 2);
|
||||
|
||||
chat::delete(&t.ctx, chats.get_chat_id(0)).ok();
|
||||
chat::delete(&t.ctx, chats.get_chat_id(1)).ok();
|
||||
let chats = Chatlist::try_load(&t.ctx, 0, None, None).unwrap();
|
||||
assert_eq!(chats.len(), 0);
|
||||
|
||||
// a subsequent call to update_device_chats() must not re-add manally deleted messages or chats
|
||||
t.ctx.update_device_chats().ok();
|
||||
let chats = Chatlist::try_load(&t.ctx, 0, None, None).unwrap();
|
||||
assert_eq!(chats.len(), 0);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -34,7 +34,7 @@ pub fn test_context(callback: Option<Box<ContextCallback>>) -> TestContext {
|
||||
None => Box::new(|_, _| 0),
|
||||
};
|
||||
let ctx = Context::new(cb, "FakeOs".into(), dbfile).unwrap();
|
||||
TestContext { ctx, dir }
|
||||
TestContext { ctx: ctx, dir: dir }
|
||||
}
|
||||
|
||||
/// Return a dummy [TestContext].
|
||||
|
||||
@@ -1,14 +0,0 @@
|
||||
Return-Path: <x@testrun.org>
|
||||
Received: from hq5.merlinux.eu
|
||||
by hq5.merlinux.eu (Dovecot) with LMTP id yRKOBakcfV1AewAAPzvFDg
|
||||
; Sat, 14 Sep 2019 19:00:25 +0200
|
||||
Received: from localhost (unknown 7.165.105.24])
|
||||
by hq5.merlinux.eu (Postfix) with ESMTPSA id 8D9844E023;
|
||||
Sat, 14 Sep 2019 19:00:22 +0200 (CEST)
|
||||
message-id: <2dfdbde7@example.org>
|
||||
Date: Sat, 14 Sep 2019 19:00:13 +0200
|
||||
From: lmn <x@tux.org>
|
||||
To: abc <abc@bcd.com>
|
||||
CC: def <def@def.de>
|
||||
|
||||
hi
|
||||
Reference in New Issue
Block a user