mirror of
https://github.com/chatmail/core.git
synced 2026-07-01 20:14:57 +03:00
Compare commits
3 Commits
1.0.0-beta
...
recode-ava
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
4737062325 | ||
|
|
6ee1e506da | ||
|
|
2481dd0ee2 |
69
CHANGELOG.md
69
CHANGELOG.md
@@ -1,74 +1,5 @@
|
||||
# Changelog
|
||||
|
||||
## 1.0.0-beta.22
|
||||
|
||||
- #1095 normalize email lineends to CRLF
|
||||
|
||||
- #1095 enable link-time-optimization, saves eg. on android 11 mb
|
||||
|
||||
- #1099 fix import regarding devicechats
|
||||
|
||||
- #1092 improve logging
|
||||
|
||||
- #1096 #1097 #1094 #1090 #1091 internal cleanups
|
||||
|
||||
## 1.0.0-beta.21
|
||||
|
||||
- #1078 #1082 ensure RFC compliance by producing 78 column lines for
|
||||
encoded attachments.
|
||||
|
||||
- #1080 don't recreate and thus break group membership if an unknown
|
||||
sender (or mailer-daemon) sends a message referencing the group chat
|
||||
|
||||
- #1081 #1079 some internal cleanups
|
||||
|
||||
- update imap-proto dependency, to fix yandex/oauth
|
||||
|
||||
## 1.0.0-beta.20
|
||||
|
||||
- #1074 fix OAUTH2/gmail
|
||||
- #1072 fix group members not appearing in contact list
|
||||
- #1071 never block interrupt_idle (thus hopefully also not on maybe_network())
|
||||
- #1069 reduce smtp-timeout to 30 seconds
|
||||
- #1066 #1065 avoid unwrap in dehtml, make literals more readable
|
||||
|
||||
## 1.0.0-beta.19
|
||||
|
||||
- #1058 timeout smtp-send if it doesn't complete in 15 minutes
|
||||
|
||||
- #1059 trim down logging
|
||||
|
||||
## 1.0.0-beta.18
|
||||
|
||||
- #1056 avoid panicking when we couldn't read imap-server's greeting
|
||||
message
|
||||
|
||||
- #1055 avoid panicking when we don't have a selected folder
|
||||
|
||||
- #1052 #1049 #1051 improve logging to add thread-id/name and
|
||||
file/lineno to each info/warn message.
|
||||
|
||||
- #1050 allow python bindings to initialize Account with "os_name".
|
||||
|
||||
|
||||
## 1.0.0-beta.17
|
||||
|
||||
- #1044 implement avatar recoding to 192x192 in core to keep file sizes small.
|
||||
|
||||
- #1024 fix #1021 SQL/injection malformed Chat-Group-Name breakage
|
||||
|
||||
- #1036 fix smtp crash by pulling in a fixed async-smtp
|
||||
|
||||
- #1039 fix read-receipts appearing as normal messages when you change
|
||||
MDN settings
|
||||
|
||||
- #1040 do not panic on SystemTimeDifference
|
||||
|
||||
- #1043 avoid potential crashes in malformed From/Chat-Disposition... headers
|
||||
|
||||
- #1045 #1041 #1038 #1035 #1034 #1029 #1025 various cleanups and doc
|
||||
improvments
|
||||
|
||||
## 1.0.0-beta.16
|
||||
|
||||
- alleviate login problems with providers which only
|
||||
|
||||
141
Cargo.lock
generated
141
Cargo.lock
generated
@@ -85,18 +85,17 @@ dependencies = [
|
||||
[[package]]
|
||||
name = "async-imap"
|
||||
version = "0.1.1"
|
||||
source = "git+https://github.com/async-email/async-imap?branch=dcc-stable#50e843113e3a67e924a8a14c477833da3ebc1b44"
|
||||
source = "git+https://github.com/async-email/async-imap#d7836416766b55d8d03587ea5326eecf501c2030"
|
||||
dependencies = [
|
||||
"async-attributes 1.1.1 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"async-native-tls 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"async-std 1.4.0 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"async-std 1.2.0 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"base64 0.11.0 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"bytes 0.4.12 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"chrono 0.4.10 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"futures 0.3.1 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"futures_codec 0.3.3 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"imap-proto 0.9.1 (git+https://github.com/djc/tokio-imap)",
|
||||
"log 0.4.8 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"native-tls 0.2.3 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"nom 5.0.1 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"pin-utils 0.1.0-alpha.4 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
@@ -104,6 +103,15 @@ dependencies = [
|
||||
"stop-token 0.1.2 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "async-macros"
|
||||
version = "2.0.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
dependencies = [
|
||||
"futures-core 0.3.1 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"pin-utils 0.1.0-alpha.4 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "async-native-tls"
|
||||
version = "0.1.1"
|
||||
@@ -116,10 +124,10 @@ dependencies = [
|
||||
[[package]]
|
||||
name = "async-smtp"
|
||||
version = "0.1.0"
|
||||
source = "git+https://github.com/async-email/async-smtp#6a4830032953f06020edc09db8daa06193e2b93f"
|
||||
source = "git+https://github.com/async-email/async-smtp#4f8416a0b8e0f8369459bb2fd342e79a17eb836b"
|
||||
dependencies = [
|
||||
"async-native-tls 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"async-std 1.4.0 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"async-std 1.2.0 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"async-trait 0.1.21 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"base64 0.11.0 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"bufstream 0.1.4 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
@@ -130,17 +138,18 @@ dependencies = [
|
||||
"nom 5.0.1 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"pin-project 0.4.6 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"pin-utils 0.1.0-alpha.4 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"serde 1.0.104 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"serde_derive 1.0.104 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"serde 1.0.103 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"serde_derive 1.0.103 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"serde_json 1.0.44 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"snafu 0.6.0 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "async-std"
|
||||
version = "1.4.0"
|
||||
version = "1.2.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
dependencies = [
|
||||
"async-macros 2.0.0 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"async-task 1.0.0 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"broadcaster 0.2.6 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"crossbeam-channel 0.4.0 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
@@ -373,8 +382,8 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
dependencies = [
|
||||
"error-chain 0.12.1 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"semver 0.9.0 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"serde 1.0.104 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"serde_derive 1.0.104 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"serde 1.0.103 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"serde_derive 1.0.103 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"serde_json 1.0.44 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
]
|
||||
|
||||
@@ -413,7 +422,7 @@ version = "0.1.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
dependencies = [
|
||||
"base64 0.10.1 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"encoding_rs 0.8.22 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"encoding_rs 0.8.20 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -476,7 +485,7 @@ dependencies = [
|
||||
"idna 0.1.5 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"log 0.4.8 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"publicsuffix 1.5.4 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"serde 1.0.104 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"serde 1.0.103 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"serde_json 1.0.44 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"time 0.1.42 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"try_from 0.3.2 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
@@ -614,7 +623,7 @@ dependencies = [
|
||||
"ident_case 1.0.1 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"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)",
|
||||
"strsim 0.9.3 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"strsim 0.9.2 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"syn 1.0.11 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
]
|
||||
|
||||
@@ -648,12 +657,12 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "deltachat"
|
||||
version = "1.0.0-beta.22"
|
||||
version = "1.0.0-beta.16"
|
||||
dependencies = [
|
||||
"async-imap 0.1.1 (git+https://github.com/async-email/async-imap?branch=dcc-stable)",
|
||||
"async-imap 0.1.1 (git+https://github.com/async-email/async-imap)",
|
||||
"async-native-tls 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"async-smtp 0.1.0 (git+https://github.com/async-email/async-smtp)",
|
||||
"async-std 1.4.0 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"async-std 1.2.0 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"backtrace 0.3.40 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"base64 0.11.0 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"bitflags 1.2.1 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
@@ -673,7 +682,7 @@ dependencies = [
|
||||
"indexmap 1.3.0 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"itertools 0.8.2 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"lazy_static 1.4.0 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"lettre_email 0.9.2 (git+https://github.com/deltachat/lettre)",
|
||||
"lettre_email 0.9.2 (git+https://github.com/deltachat/lettre?branch=native_tls)",
|
||||
"libc 0.2.66 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"mailparse 0.10.1 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"native-tls 0.2.3 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
@@ -684,19 +693,19 @@ dependencies = [
|
||||
"pretty_assertions 0.6.1 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"pretty_env_logger 0.3.1 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"proptest 0.9.4 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"quick-xml 0.17.2 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"quick-xml 0.17.1 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"r2d2 0.8.7 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"r2d2_sqlite 0.12.0 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"rand 0.7.2 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"regex 1.3.1 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"reqwest 0.9.24 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"reqwest 0.9.22 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"rusqlite 0.20.0 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"rustyline 4.1.0 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"sanitize-filename 0.2.1 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"serde 1.0.104 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"serde 1.0.103 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"serde_json 1.0.44 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"sha2 0.8.0 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"smallvec 1.1.0 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"smallvec 1.0.0 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"stop-token 0.1.2 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"strum 0.16.0 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"strum_macros 0.16.0 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
@@ -725,15 +734,14 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "deltachat_ffi"
|
||||
version = "1.0.0-beta.22"
|
||||
version = "1.0.0-beta.16"
|
||||
dependencies = [
|
||||
"deltachat 1.0.0-beta.22",
|
||||
"deltachat 1.0.0-beta.16",
|
||||
"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)",
|
||||
"libc 0.2.66 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"num-traits 0.2.10 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"serde_json 1.0.44 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -840,7 +848,7 @@ source = "git+https://github.com/async-email/encoded-words#2631c258183620f6d976a
|
||||
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.22 (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)",
|
||||
@@ -906,7 +914,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
|
||||
[[package]]
|
||||
name = "encoding_rs"
|
||||
version = "0.8.22"
|
||||
version = "0.8.20"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
dependencies = [
|
||||
"cfg-if 0.1.10 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
@@ -1243,7 +1251,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "hermit-abi"
|
||||
version = "0.1.5"
|
||||
version = "0.1.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
dependencies = [
|
||||
"libc 0.2.66 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
@@ -1297,8 +1305,8 @@ dependencies = [
|
||||
"backtrace 0.3.40 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"failure 0.1.6 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"os_type 2.2.0 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"serde 1.0.104 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"serde_derive 1.0.104 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"serde 1.0.103 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"serde_derive 1.0.103 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"tempdir 0.3.7 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"termcolor 0.3.6 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"toml 0.4.10 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
@@ -1408,7 +1416,7 @@ dependencies = [
|
||||
[[package]]
|
||||
name = "imap-proto"
|
||||
version = "0.9.1"
|
||||
source = "git+https://github.com/djc/tokio-imap#de420791b4e240242592616938407b742454c46c"
|
||||
source = "git+https://github.com/djc/tokio-imap#2b8701b83c50085b7ffcdf26a95146b91f93a6d5"
|
||||
dependencies = [
|
||||
"nom 5.0.1 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
]
|
||||
@@ -1492,7 +1500,7 @@ dependencies = [
|
||||
[[package]]
|
||||
name = "lettre"
|
||||
version = "0.9.2"
|
||||
source = "git+https://github.com/deltachat/lettre#12e4f22d8d691fb5d1606e4c9034f5d4d9ba635b"
|
||||
source = "git+https://github.com/deltachat/lettre?branch=native_tls#6453051a470e020a02228dc62f7aed2d9c90cf01"
|
||||
dependencies = [
|
||||
"fast_chemail 0.9.6 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"log 0.4.8 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
@@ -1501,14 +1509,12 @@ dependencies = [
|
||||
[[package]]
|
||||
name = "lettre_email"
|
||||
version = "0.9.2"
|
||||
source = "git+https://github.com/deltachat/lettre#12e4f22d8d691fb5d1606e4c9034f5d4d9ba635b"
|
||||
source = "git+https://github.com/deltachat/lettre?branch=native_tls#6453051a470e020a02228dc62f7aed2d9c90cf01"
|
||||
dependencies = [
|
||||
"base64 0.11.0 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"email 0.0.21 (git+https://github.com/deltachat/rust-email)",
|
||||
"lazy_static 1.4.0 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"lettre 0.9.2 (git+https://github.com/deltachat/lettre)",
|
||||
"lettre 0.9.2 (git+https://github.com/deltachat/lettre?branch=native_tls)",
|
||||
"mime 0.3.14 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"regex 1.3.1 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"time 0.1.42 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"uuid 0.8.1 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
]
|
||||
@@ -1767,8 +1773,8 @@ dependencies = [
|
||||
"num-iter 0.1.39 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"num-traits 0.2.10 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"rand 0.7.2 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"serde 1.0.104 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"smallvec 1.1.0 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"serde 1.0.103 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"smallvec 1.0.0 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"zeroize 1.1.0 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
]
|
||||
|
||||
@@ -1834,7 +1840,7 @@ name = "num_cpus"
|
||||
version = "1.11.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
dependencies = [
|
||||
"hermit-abi 0.1.5 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"hermit-abi 0.1.3 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"libc 0.2.66 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
]
|
||||
|
||||
@@ -1953,7 +1959,7 @@ dependencies = [
|
||||
"cloudabi 0.0.3 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"libc 0.2.66 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"redox_syscall 0.1.56 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"smallvec 1.1.0 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"smallvec 1.0.0 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"winapi 0.3.8 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
]
|
||||
|
||||
@@ -2007,7 +2013,7 @@ dependencies = [
|
||||
"sha-1 0.8.1 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"sha2 0.8.0 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"sha3 0.8.2 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"smallvec 1.1.0 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"smallvec 1.0.0 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"try_from 0.3.2 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"twofish 0.2.0 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"x25519-dalek 0.6.0 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
@@ -2161,7 +2167,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
|
||||
[[package]]
|
||||
name = "quick-xml"
|
||||
version = "0.17.2"
|
||||
version = "0.17.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
dependencies = [
|
||||
"memchr 2.2.1 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
@@ -2449,14 +2455,14 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "reqwest"
|
||||
version = "0.9.24"
|
||||
version = "0.9.22"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
dependencies = [
|
||||
"base64 0.10.1 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"bytes 0.4.12 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"cookie 0.12.0 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"cookie_store 0.7.0 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"encoding_rs 0.8.22 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"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)",
|
||||
@@ -2466,7 +2472,7 @@ dependencies = [
|
||||
"mime 0.3.14 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"mime_guess 2.0.1 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"native-tls 0.2.3 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"serde 1.0.104 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"serde 1.0.103 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"serde_json 1.0.44 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"serde_urlencoded 0.5.5 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"time 0.1.42 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
@@ -2650,7 +2656,7 @@ version = "0.9.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
dependencies = [
|
||||
"semver-parser 0.7.0 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"serde 1.0.104 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"serde 1.0.103 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -2660,15 +2666,15 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
|
||||
[[package]]
|
||||
name = "serde"
|
||||
version = "1.0.104"
|
||||
version = "1.0.103"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
dependencies = [
|
||||
"serde_derive 1.0.104 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"serde_derive 1.0.103 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "serde_derive"
|
||||
version = "1.0.104"
|
||||
version = "1.0.103"
|
||||
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)",
|
||||
@@ -2683,7 +2689,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
dependencies = [
|
||||
"itoa 0.4.4 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"ryu 1.0.2 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"serde 1.0.104 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"serde 1.0.103 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -2693,7 +2699,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
dependencies = [
|
||||
"dtoa 0.4.4 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"itoa 0.4.4 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"serde 1.0.104 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"serde 1.0.103 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"url 1.7.2 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
]
|
||||
|
||||
@@ -2771,7 +2777,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "smallvec"
|
||||
version = "1.1.0"
|
||||
version = "1.0.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
|
||||
[[package]]
|
||||
@@ -2813,7 +2819,7 @@ name = "stop-token"
|
||||
version = "0.1.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
dependencies = [
|
||||
"async-std 1.4.0 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"async-std 1.2.0 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"pin-project-lite 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
]
|
||||
|
||||
@@ -2835,7 +2841,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "strsim"
|
||||
version = "0.9.3"
|
||||
version = "0.9.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
|
||||
[[package]]
|
||||
@@ -3139,7 +3145,7 @@ name = "toml"
|
||||
version = "0.4.10"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
dependencies = [
|
||||
"serde 1.0.104 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"serde 1.0.103 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -3196,7 +3202,7 @@ name = "unicode-normalization"
|
||||
version = "0.1.11"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
dependencies = [
|
||||
"smallvec 1.1.0 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"smallvec 1.0.0 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -3463,10 +3469,11 @@ dependencies = [
|
||||
"checksum arrayvec 0.5.1 (registry+https://github.com/rust-lang/crates.io-index)" = "cff77d8686867eceff3105329d4698d96c2391c176d5d03adc90c7389162b5b8"
|
||||
"checksum ascii_utils 0.9.3 (registry+https://github.com/rust-lang/crates.io-index)" = "71938f30533e4d95a6d17aa530939da3842c2ab6f4f84b9dae68447e4129f74a"
|
||||
"checksum async-attributes 1.1.1 (registry+https://github.com/rust-lang/crates.io-index)" = "efd3d156917d94862e779f356c5acae312b08fd3121e792c857d7928c8088423"
|
||||
"checksum async-imap 0.1.1 (git+https://github.com/async-email/async-imap?branch=dcc-stable)" = "<none>"
|
||||
"checksum async-imap 0.1.1 (git+https://github.com/async-email/async-imap)" = "<none>"
|
||||
"checksum async-macros 2.0.0 (registry+https://github.com/rust-lang/crates.io-index)" = "644a5a8de80f2085a1e7e57cd1544a2a7438f6e003c0790999bd43b92a77cdb2"
|
||||
"checksum async-native-tls 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)" = "a29e9e4ed87f4070dd6099d0bd5edfc7692c94442c80d656b50a802c6fde23b7"
|
||||
"checksum async-smtp 0.1.0 (git+https://github.com/async-email/async-smtp)" = "<none>"
|
||||
"checksum async-std 1.4.0 (registry+https://github.com/rust-lang/crates.io-index)" = "0bf6039b315300e057d198b9d3ab92ee029e31c759b7f1afae538145e6f18a3e"
|
||||
"checksum async-std 1.2.0 (registry+https://github.com/rust-lang/crates.io-index)" = "513ee3c49800679a319912340f5601afda9e72848d7dea3a48bab489e8c1a46f"
|
||||
"checksum async-task 1.0.0 (registry+https://github.com/rust-lang/crates.io-index)" = "de6bd58f7b9cc49032559422595c81cbfcf04db2f2133592f70af19e258a1ced"
|
||||
"checksum async-trait 0.1.21 (registry+https://github.com/rust-lang/crates.io-index)" = "8b6dd385bb33043b833ba049048d57bdbb4d654a121ed68c71871ca51ff67070"
|
||||
"checksum atty 0.2.13 (registry+https://github.com/rust-lang/crates.io-index)" = "1803c647a3ec87095e7ae7acfca019e98de5ec9a7d01343f611cf3152ed71a90"
|
||||
@@ -3545,7 +3552,7 @@ dependencies = [
|
||||
"checksum encoding-index-singlebyte 1.20141219.5 (registry+https://github.com/rust-lang/crates.io-index)" = "3351d5acffb224af9ca265f435b859c7c01537c0849754d3db3fdf2bfe2ae84a"
|
||||
"checksum encoding-index-tradchinese 1.20141219.5 (registry+https://github.com/rust-lang/crates.io-index)" = "fd0e20d5688ce3cab59eb3ef3a2083a5c77bf496cb798dc6fcdb75f323890c18"
|
||||
"checksum encoding_index_tests 0.1.4 (registry+https://github.com/rust-lang/crates.io-index)" = "a246d82be1c9d791c5dfde9a2bd045fc3cbba3fa2b11ad558f27d01712f00569"
|
||||
"checksum encoding_rs 0.8.22 (registry+https://github.com/rust-lang/crates.io-index)" = "cd8d03faa7fe0c1431609dfad7bbe827af30f82e1e2ae6f7ee4fca6bd764bc28"
|
||||
"checksum encoding_rs 0.8.20 (registry+https://github.com/rust-lang/crates.io-index)" = "87240518927716f79692c2ed85bfe6e98196d18c6401ec75355760233a7e12e9"
|
||||
"checksum entities 1.0.1 (registry+https://github.com/rust-lang/crates.io-index)" = "b5320ae4c3782150d900b79807611a59a99fc9a1d61d686faafc24b93fc8d7ca"
|
||||
"checksum env_logger 0.6.2 (registry+https://github.com/rust-lang/crates.io-index)" = "aafcde04e90a5226a6443b7aabdb016ba2f8307c847d524724bd9b346dd1a2d3"
|
||||
"checksum error-chain 0.12.1 (registry+https://github.com/rust-lang/crates.io-index)" = "3ab49e9dcb602294bc42f9a7dfc9bc6e936fca4418ea300dbfb84fe16de0b7d9"
|
||||
@@ -3587,7 +3594,7 @@ dependencies = [
|
||||
"checksum glob 0.3.0 (registry+https://github.com/rust-lang/crates.io-index)" = "9b919933a397b79c37e33b77bb2aa3dc8eb6e165ad809e58ff75bc7db2e34574"
|
||||
"checksum h2 0.1.26 (registry+https://github.com/rust-lang/crates.io-index)" = "a5b34c246847f938a410a03c5458c7fee2274436675e76d8b903c08efc29c462"
|
||||
"checksum heck 0.3.1 (registry+https://github.com/rust-lang/crates.io-index)" = "20564e78d53d2bb135c343b3f47714a56af2061f1c928fdb541dc7b9fdd94205"
|
||||
"checksum hermit-abi 0.1.5 (registry+https://github.com/rust-lang/crates.io-index)" = "f629dc602392d3ec14bfc8a09b5e644d7ffd725102b48b81e59f90f2633621d7"
|
||||
"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"
|
||||
@@ -3613,8 +3620,8 @@ dependencies = [
|
||||
"checksum kernel32-sys 0.2.2 (registry+https://github.com/rust-lang/crates.io-index)" = "7507624b29483431c0ba2d82aece8ca6cdba9382bff4ddd0f7490560c056098d"
|
||||
"checksum kv-log-macro 1.0.4 (registry+https://github.com/rust-lang/crates.io-index)" = "8c54d9f465d530a752e6ebdc217e081a7a614b48cb200f6f0aee21ba6bc9aabb"
|
||||
"checksum lazy_static 1.4.0 (registry+https://github.com/rust-lang/crates.io-index)" = "e2abad23fbc42b3700f2f279844dc832adb2b2eb069b2df918f455c4e18cc646"
|
||||
"checksum lettre 0.9.2 (git+https://github.com/deltachat/lettre)" = "<none>"
|
||||
"checksum lettre_email 0.9.2 (git+https://github.com/deltachat/lettre)" = "<none>"
|
||||
"checksum lettre 0.9.2 (git+https://github.com/deltachat/lettre?branch=native_tls)" = "<none>"
|
||||
"checksum lettre_email 0.9.2 (git+https://github.com/deltachat/lettre?branch=native_tls)" = "<none>"
|
||||
"checksum lexical-core 0.4.6 (registry+https://github.com/rust-lang/crates.io-index)" = "2304bccb228c4b020f3a4835d247df0a02a7c4686098d4167762cfbbe4c5cb14"
|
||||
"checksum libc 0.2.66 (registry+https://github.com/rust-lang/crates.io-index)" = "d515b1f41455adea1313a4a2ac8a8a477634fbae63cc6100e3aebb207ce61558"
|
||||
"checksum libm 0.2.1 (registry+https://github.com/rust-lang/crates.io-index)" = "c7d73b3f436185384286bd8098d17ec07c9a7d2388a6599f824d8502b529702a"
|
||||
@@ -3684,7 +3691,7 @@ dependencies = [
|
||||
"checksum publicsuffix 1.5.4 (registry+https://github.com/rust-lang/crates.io-index)" = "3bbaa49075179162b49acac1c6aa45fb4dafb5f13cf6794276d77bc7fd95757b"
|
||||
"checksum pulldown-cmark 0.2.0 (registry+https://github.com/rust-lang/crates.io-index)" = "eef52fac62d0ea7b9b4dc7da092aa64ea7ec3d90af6679422d3d7e0e14b6ee15"
|
||||
"checksum quick-error 1.2.2 (registry+https://github.com/rust-lang/crates.io-index)" = "9274b940887ce9addde99c4eee6b5c44cc494b182b97e73dc8ffdcb3397fd3f0"
|
||||
"checksum quick-xml 0.17.2 (registry+https://github.com/rust-lang/crates.io-index)" = "fe1e430bdcf30c9fdc25053b9c459bb1a4672af4617b6c783d7d91dc17c6bbb0"
|
||||
"checksum quick-xml 0.17.1 (registry+https://github.com/rust-lang/crates.io-index)" = "7219717e55033c0c1c2368d6c469baaf10115ffdb54b1e83a8b1e8bfd95cde19"
|
||||
"checksum quote 0.3.15 (registry+https://github.com/rust-lang/crates.io-index)" = "7a6e920b65c65f10b2ae65c831a81a073a89edd28c7cce89475bff467ab4167a"
|
||||
"checksum quote 0.6.13 (registry+https://github.com/rust-lang/crates.io-index)" = "6ce23b6b870e8f94f81fb0a363d65d86675884b34a09043c81e5562f11c1f8e1"
|
||||
"checksum quote 1.0.2 (registry+https://github.com/rust-lang/crates.io-index)" = "053a8c8bcc71fcce321828dc897a98ab9760bef03a4fc36693c231e5b3216cfe"
|
||||
@@ -3716,7 +3723,7 @@ dependencies = [
|
||||
"checksum remove_dir_all 0.5.2 (registry+https://github.com/rust-lang/crates.io-index)" = "4a83fa3702a688b9359eccba92d153ac33fd2e8462f9e0e3fdf155239ea7792e"
|
||||
"checksum rental 0.5.5 (registry+https://github.com/rust-lang/crates.io-index)" = "8545debe98b2b139fb04cad8618b530e9b07c152d99a5de83c860b877d67847f"
|
||||
"checksum rental-impl 0.5.5 (registry+https://github.com/rust-lang/crates.io-index)" = "475e68978dc5b743f2f40d8e0a8fdc83f1c5e78cbf4b8fa5e74e73beebc340de"
|
||||
"checksum reqwest 0.9.24 (registry+https://github.com/rust-lang/crates.io-index)" = "f88643aea3c1343c804950d7bf983bd2067f5ab59db6d613a08e05572f2714ab"
|
||||
"checksum reqwest 0.9.22 (registry+https://github.com/rust-lang/crates.io-index)" = "2c2064233e442ce85c77231ebd67d9eca395207dec2127fe0bbedde4bd29a650"
|
||||
"checksum ripemd160 0.8.0 (registry+https://github.com/rust-lang/crates.io-index)" = "ad5112e0dbbb87577bfbc56c42450235e3012ce336e29c5befd7807bd626da4a"
|
||||
"checksum rsa 0.2.0 (registry+https://github.com/rust-lang/crates.io-index)" = "6ed8692d8e0ea3baae03f0f32ecfc13a6c6f1f85fcd6d9fdefcdf364e70f4df9"
|
||||
"checksum rusqlite 0.20.0 (registry+https://github.com/rust-lang/crates.io-index)" = "2a194373ef527035645a1bc21b10dc2125f73497e6e155771233eb187aedd051"
|
||||
@@ -3737,8 +3744,8 @@ dependencies = [
|
||||
"checksum security-framework-sys 0.3.3 (registry+https://github.com/rust-lang/crates.io-index)" = "e31493fc37615debb8c5090a7aeb4a9730bc61e77ab10b9af59f1a202284f895"
|
||||
"checksum semver 0.9.0 (registry+https://github.com/rust-lang/crates.io-index)" = "1d7eb9ef2c18661902cc47e535f9bc51b78acd254da71d375c2f6720d9a40403"
|
||||
"checksum semver-parser 0.7.0 (registry+https://github.com/rust-lang/crates.io-index)" = "388a1df253eca08550bef6c72392cfe7c30914bf41df5269b68cbd6ff8f570a3"
|
||||
"checksum serde 1.0.104 (registry+https://github.com/rust-lang/crates.io-index)" = "414115f25f818d7dfccec8ee535d76949ae78584fc4f79a6f45a904bf8ab4449"
|
||||
"checksum serde_derive 1.0.104 (registry+https://github.com/rust-lang/crates.io-index)" = "128f9e303a5a29922045a830221b8f78ec74a5f544944f3d5984f8ec3895ef64"
|
||||
"checksum serde 1.0.103 (registry+https://github.com/rust-lang/crates.io-index)" = "1217f97ab8e8904b57dd22eb61cde455fa7446a9c1cf43966066da047c1f3702"
|
||||
"checksum serde_derive 1.0.103 (registry+https://github.com/rust-lang/crates.io-index)" = "a8c6faef9a2e64b0064f48570289b4bf8823b7581f1d6157c1b52152306651d0"
|
||||
"checksum serde_json 1.0.44 (registry+https://github.com/rust-lang/crates.io-index)" = "48c575e0cc52bdd09b47f330f646cf59afc586e9c4e3ccd6fc1f625b8ea1dad7"
|
||||
"checksum serde_urlencoded 0.5.5 (registry+https://github.com/rust-lang/crates.io-index)" = "642dd69105886af2efd227f75a520ec9b44a820d65bc133a9131f7d229fd165a"
|
||||
"checksum sha-1 0.8.1 (registry+https://github.com/rust-lang/crates.io-index)" = "23962131a91661d643c98940b20fcaffe62d776a823247be80a48fcb8b6fce68"
|
||||
@@ -3748,7 +3755,7 @@ dependencies = [
|
||||
"checksum slab 0.4.2 (registry+https://github.com/rust-lang/crates.io-index)" = "c111b5bd5695e56cffe5129854aa230b39c93a305372fdbb2668ca2394eea9f8"
|
||||
"checksum slice-deque 0.2.4 (registry+https://github.com/rust-lang/crates.io-index)" = "ffddf594f5f597f63533d897427a570dbaa9feabaaa06595b74b71b7014507d7"
|
||||
"checksum smallvec 0.6.13 (registry+https://github.com/rust-lang/crates.io-index)" = "f7b0758c52e15a8b5e3691eae6cc559f08eee9406e548a4477ba4e67770a82b6"
|
||||
"checksum smallvec 1.1.0 (registry+https://github.com/rust-lang/crates.io-index)" = "44e59e0c9fa00817912ae6e4e6e3c4fe04455e75699d06eedc7d85917ed8e8f4"
|
||||
"checksum smallvec 1.0.0 (registry+https://github.com/rust-lang/crates.io-index)" = "4ecf3b85f68e8abaa7555aa5abdb1153079387e60b718283d732f03897fcfc86"
|
||||
"checksum snafu 0.6.0 (registry+https://github.com/rust-lang/crates.io-index)" = "41207ca11f96a62cd34e6b7fdf73d322b25ae3848eb9d38302169724bb32cf27"
|
||||
"checksum snafu-derive 0.6.0 (registry+https://github.com/rust-lang/crates.io-index)" = "4c5e338c8b0577457c9dda8e794b6ad7231c96e25b1b0dd5842d52249020c1c0"
|
||||
"checksum spin 0.5.2 (registry+https://github.com/rust-lang/crates.io-index)" = "6e63cff320ae2c57904679ba7cb63280a3dc4613885beafb148ee7bf9aa9042d"
|
||||
@@ -3757,7 +3764,7 @@ dependencies = [
|
||||
"checksum stop-token 0.1.2 (registry+https://github.com/rust-lang/crates.io-index)" = "06855fb7c94d3be9b3a57c4d82dfc8a43bb658fbb3b1dda79de89e748d9eb9dd"
|
||||
"checksum stream-cipher 0.3.2 (registry+https://github.com/rust-lang/crates.io-index)" = "8131256a5896cabcf5eb04f4d6dacbe1aefda854b0d9896e09cb58829ec5638c"
|
||||
"checksum string 0.2.1 (registry+https://github.com/rust-lang/crates.io-index)" = "d24114bfcceb867ca7f71a0d3fe45d45619ec47a6fbfa98cb14e14250bfa5d6d"
|
||||
"checksum strsim 0.9.3 (registry+https://github.com/rust-lang/crates.io-index)" = "6446ced80d6c486436db5c078dde11a9f73d42b57fb273121e160b84f63d894c"
|
||||
"checksum strsim 0.9.2 (registry+https://github.com/rust-lang/crates.io-index)" = "032c03039aae92b350aad2e3779c352e104d919cb192ba2fabbd7b831ce4f0f6"
|
||||
"checksum strum 0.16.0 (registry+https://github.com/rust-lang/crates.io-index)" = "6138f8f88a16d90134763314e3fc76fa3ed6a7db4725d6acf9a3ef95a3188d22"
|
||||
"checksum strum_macros 0.16.0 (registry+https://github.com/rust-lang/crates.io-index)" = "0054a7df764039a6cd8592b9de84be4bec368ff081d203a7d5371cbfa8e65c81"
|
||||
"checksum subtle 2.2.2 (registry+https://github.com/rust-lang/crates.io-index)" = "7c65d530b10ccaeac294f349038a597e435b18fb456aadd0840a623f83b9e941"
|
||||
|
||||
14
Cargo.toml
14
Cargo.toml
@@ -1,13 +1,10 @@
|
||||
[package]
|
||||
name = "deltachat"
|
||||
version = "1.0.0-beta.22"
|
||||
version = "1.0.0-beta.16"
|
||||
authors = ["Delta Chat Developers (ML) <delta@codespeak.net>"]
|
||||
edition = "2018"
|
||||
license = "MPL"
|
||||
|
||||
[profile.release]
|
||||
lto = true
|
||||
|
||||
[dependencies]
|
||||
deltachat_derive = { path = "./deltachat_derive" }
|
||||
|
||||
@@ -20,13 +17,10 @@ smallvec = "1.0.0"
|
||||
reqwest = { version = "0.9.15" }
|
||||
num-derive = "0.3.0"
|
||||
num-traits = "0.2.6"
|
||||
async-smtp = { git = "https://github.com/async-email/async-smtp" }
|
||||
async-smtp = { git = "https://github.com/async-email/async-smtp", branch = "master" }
|
||||
email = { git = "https://github.com/deltachat/rust-email", branch = "master" }
|
||||
lettre_email = { git = "https://github.com/deltachat/lettre", branch = "master" }
|
||||
|
||||
# XXX newer commits of async-imap lead to import-export tests hanging
|
||||
async-imap = { git = "https://github.com/async-email/async-imap", branch = "dcc-stable" }
|
||||
|
||||
lettre_email = { git = "https://github.com/deltachat/lettre", branch = "native_tls" }
|
||||
async-imap = { git = "https://github.com/async-email/async-imap" }
|
||||
async-native-tls = "0.1.1"
|
||||
async-std = { version = "1.0", features = ["unstable"] }
|
||||
base64 = "0.11"
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
[package]
|
||||
name = "deltachat_ffi"
|
||||
version = "1.0.0-beta.22"
|
||||
version = "1.0.0-beta.16"
|
||||
description = "Deltachat FFI"
|
||||
authors = ["Delta Chat Developers (ML) <delta@codespeak.net>"]
|
||||
edition = "2018"
|
||||
@@ -21,7 +21,6 @@ libc = "0.2"
|
||||
human-panic = "1.0.1"
|
||||
num-traits = "0.2.6"
|
||||
failure = "0.1.6"
|
||||
serde_json = "1.0"
|
||||
|
||||
[features]
|
||||
default = ["vendored", "nightly", "ringbuf"]
|
||||
|
||||
@@ -10,7 +10,6 @@
|
||||
#[macro_use]
|
||||
extern crate human_panic;
|
||||
extern crate num_traits;
|
||||
extern crate serde_json;
|
||||
|
||||
use std::collections::HashMap;
|
||||
use std::convert::TryInto;
|
||||
@@ -122,79 +121,74 @@ impl ContextWrapper {
|
||||
}
|
||||
|
||||
/// Translates the callback from the rust style to the C-style version.
|
||||
unsafe fn translate_cb(&self, event: Event) {
|
||||
if let Some(ffi_cb) = self.cb {
|
||||
let event_id = event.as_id();
|
||||
match event {
|
||||
Event::Info(msg)
|
||||
| Event::SmtpConnected(msg)
|
||||
| Event::ImapConnected(msg)
|
||||
| Event::SmtpMessageSent(msg)
|
||||
| Event::ImapMessageDeleted(msg)
|
||||
| Event::ImapMessageMoved(msg)
|
||||
| Event::ImapFolderEmptied(msg)
|
||||
| Event::NewBlobFile(msg)
|
||||
| Event::DeletedBlobFile(msg)
|
||||
| Event::Warning(msg)
|
||||
| Event::Error(msg)
|
||||
| Event::ErrorNetwork(msg)
|
||||
| Event::ErrorSelfNotInGroup(msg) => {
|
||||
let data2 = CString::new(msg).unwrap_or_default();
|
||||
ffi_cb(self, event_id, 0, data2.as_ptr() as uintptr_t);
|
||||
}
|
||||
Event::MsgsChanged { chat_id, msg_id }
|
||||
| Event::IncomingMsg { chat_id, msg_id }
|
||||
| Event::MsgDelivered { chat_id, msg_id }
|
||||
| Event::MsgFailed { chat_id, msg_id }
|
||||
| Event::MsgRead { chat_id, msg_id } => {
|
||||
ffi_cb(
|
||||
unsafe fn translate_cb(&self, event: Event) -> uintptr_t {
|
||||
match self.cb {
|
||||
Some(ffi_cb) => {
|
||||
let event_id = event.as_id();
|
||||
match event {
|
||||
Event::Info(msg)
|
||||
| Event::SmtpConnected(msg)
|
||||
| Event::ImapConnected(msg)
|
||||
| Event::SmtpMessageSent(msg)
|
||||
| Event::ImapMessageDeleted(msg)
|
||||
| Event::ImapMessageMoved(msg)
|
||||
| Event::ImapFolderEmptied(msg)
|
||||
| Event::NewBlobFile(msg)
|
||||
| Event::DeletedBlobFile(msg)
|
||||
| Event::Warning(msg)
|
||||
| Event::Error(msg)
|
||||
| Event::ErrorNetwork(msg)
|
||||
| Event::ErrorSelfNotInGroup(msg) => {
|
||||
let data2 = CString::new(msg).unwrap_or_default();
|
||||
ffi_cb(self, event_id, 0, data2.as_ptr() as uintptr_t)
|
||||
}
|
||||
Event::MsgsChanged { chat_id, msg_id }
|
||||
| Event::IncomingMsg { chat_id, msg_id }
|
||||
| Event::MsgDelivered { chat_id, msg_id }
|
||||
| Event::MsgFailed { chat_id, msg_id }
|
||||
| Event::MsgRead { chat_id, msg_id } => ffi_cb(
|
||||
self,
|
||||
event_id,
|
||||
chat_id as uintptr_t,
|
||||
msg_id.to_u32() as uintptr_t,
|
||||
);
|
||||
}
|
||||
Event::ChatModified(chat_id) => {
|
||||
ffi_cb(self, event_id, chat_id as uintptr_t, 0);
|
||||
}
|
||||
Event::ContactsChanged(id) | Event::LocationChanged(id) => {
|
||||
let id = id.unwrap_or_default();
|
||||
ffi_cb(self, event_id, id as uintptr_t, 0);
|
||||
}
|
||||
Event::ConfigureProgress(progress) | Event::ImexProgress(progress) => {
|
||||
ffi_cb(self, event_id, progress as uintptr_t, 0);
|
||||
}
|
||||
Event::ImexFileWritten(file) => {
|
||||
let data1 = file.to_c_string().unwrap_or_default();
|
||||
ffi_cb(self, event_id, data1.as_ptr() as uintptr_t, 0);
|
||||
}
|
||||
Event::SecurejoinInviterProgress {
|
||||
contact_id,
|
||||
progress,
|
||||
}
|
||||
| Event::SecurejoinJoinerProgress {
|
||||
contact_id,
|
||||
progress,
|
||||
} => {
|
||||
ffi_cb(
|
||||
),
|
||||
Event::ChatModified(chat_id) => ffi_cb(self, event_id, chat_id as uintptr_t, 0),
|
||||
Event::ContactsChanged(id) | Event::LocationChanged(id) => {
|
||||
let id = id.unwrap_or_default();
|
||||
ffi_cb(self, event_id, id as uintptr_t, 0)
|
||||
}
|
||||
Event::ConfigureProgress(progress) | Event::ImexProgress(progress) => {
|
||||
ffi_cb(self, event_id, progress as uintptr_t, 0)
|
||||
}
|
||||
Event::ImexFileWritten(file) => {
|
||||
let data1 = file.to_c_string().unwrap_or_default();
|
||||
ffi_cb(self, event_id, data1.as_ptr() as uintptr_t, 0)
|
||||
}
|
||||
Event::SecurejoinInviterProgress {
|
||||
contact_id,
|
||||
progress,
|
||||
}
|
||||
| Event::SecurejoinJoinerProgress {
|
||||
contact_id,
|
||||
progress,
|
||||
} => ffi_cb(
|
||||
self,
|
||||
event_id,
|
||||
contact_id as uintptr_t,
|
||||
progress as uintptr_t,
|
||||
);
|
||||
}
|
||||
Event::SecurejoinMemberAdded {
|
||||
chat_id,
|
||||
contact_id,
|
||||
} => {
|
||||
ffi_cb(
|
||||
),
|
||||
Event::SecurejoinMemberAdded {
|
||||
chat_id,
|
||||
contact_id,
|
||||
} => ffi_cb(
|
||||
self,
|
||||
event_id,
|
||||
chat_id as uintptr_t,
|
||||
contact_id as uintptr_t,
|
||||
);
|
||||
),
|
||||
}
|
||||
}
|
||||
None => 0,
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -515,7 +509,7 @@ pub unsafe extern "C" fn dc_interrupt_imap_idle(context: *mut dc_context_t) {
|
||||
}
|
||||
let ffi_context = &*context;
|
||||
ffi_context
|
||||
.with_inner(|ctx| job::interrupt_inbox_idle(ctx))
|
||||
.with_inner(|ctx| job::interrupt_inbox_idle(ctx, true))
|
||||
.unwrap_or(())
|
||||
}
|
||||
|
||||
@@ -2396,27 +2390,12 @@ pub unsafe extern "C" fn dc_chat_get_info_json(
|
||||
}
|
||||
let ffi_context = &*context;
|
||||
ffi_context
|
||||
.with_inner(|ctx| {
|
||||
let chat = match chat::Chat::load_from_db(ctx, chat_id) {
|
||||
Ok(chat) => chat,
|
||||
Err(err) => {
|
||||
error!(ctx, "dc_get_chat_info_json() failed to load chat: {}", err);
|
||||
return "".strdup();
|
||||
}
|
||||
};
|
||||
let info = match chat.get_info(ctx) {
|
||||
Ok(info) => info,
|
||||
Err(err) => {
|
||||
error!(
|
||||
ctx,
|
||||
"dc_get_chat_info_json() failed to get chat info: {}", err
|
||||
);
|
||||
return "".strdup();
|
||||
}
|
||||
};
|
||||
serde_json::to_string(&info)
|
||||
.unwrap_or_log_default(ctx, "dc_get_chat_info_json() failed to serialise to json")
|
||||
.strdup()
|
||||
.with_inner(|ctx| match chat::get_info_json(ctx, chat_id) {
|
||||
Ok(s) => s.strdup(),
|
||||
Err(err) => {
|
||||
error!(ctx, "get_info_json({}) returned: {}", chat_id, err);
|
||||
return "".strdup();
|
||||
}
|
||||
})
|
||||
.unwrap_or_else(|_| "".strdup())
|
||||
}
|
||||
|
||||
@@ -20,14 +20,14 @@ use deltachat::qr::*;
|
||||
use deltachat::sql;
|
||||
use deltachat::Event;
|
||||
|
||||
/// Reset database tables.
|
||||
/// Reset database tables. This function is called from Core cmdline.
|
||||
/// Argument is a bitmask, executing single or multiple actions in one call.
|
||||
/// e.g. bitmask 7 triggers actions definded with bits 1, 2 and 4.
|
||||
fn dc_reset_tables(context: &Context, bits: i32) -> i32 {
|
||||
println!("Resetting tables ({})...", bits);
|
||||
pub fn dc_reset_tables(context: &Context, bits: i32) -> i32 {
|
||||
info!(context, "Resetting tables ({})...", bits);
|
||||
if 0 != bits & 1 {
|
||||
sql::execute(context, &context.sql, "DELETE FROM jobs;", params![]).unwrap();
|
||||
println!("(1) Jobs reset.");
|
||||
info!(context, "(1) Jobs reset.");
|
||||
}
|
||||
if 0 != bits & 2 {
|
||||
sql::execute(
|
||||
@@ -37,11 +37,11 @@ fn dc_reset_tables(context: &Context, bits: i32) -> i32 {
|
||||
params![],
|
||||
)
|
||||
.unwrap();
|
||||
println!("(2) Peerstates reset.");
|
||||
info!(context, "(2) Peerstates reset.");
|
||||
}
|
||||
if 0 != bits & 4 {
|
||||
sql::execute(context, &context.sql, "DELETE FROM keypairs;", params![]).unwrap();
|
||||
println!("(4) Private keypairs reset.");
|
||||
info!(context, "(4) Private keypairs reset.");
|
||||
}
|
||||
if 0 != bits & 8 {
|
||||
sql::execute(
|
||||
@@ -80,7 +80,7 @@ fn dc_reset_tables(context: &Context, bits: i32) -> i32 {
|
||||
)
|
||||
.unwrap();
|
||||
sql::execute(context, &context.sql, "DELETE FROM leftgrps;", params![]).unwrap();
|
||||
println!("(8) Rest but server config reset.");
|
||||
info!(context, "(8) Rest but server config reset.");
|
||||
}
|
||||
|
||||
context.call_cb(Event::MsgsChanged {
|
||||
@@ -155,7 +155,7 @@ fn poke_spec(context: &Context, spec: Option<&str>) -> libc::c_int {
|
||||
let name = name_f.to_string_lossy();
|
||||
if name.ends_with(".eml") {
|
||||
let path_plus_name = format!("{}/{}", &real_spec, name);
|
||||
println!("Import: {}", path_plus_name);
|
||||
info!(context, "Import: {}", path_plus_name);
|
||||
if dc_poke_eml_file(context, path_plus_name).is_ok() {
|
||||
read_cnt += 1
|
||||
}
|
||||
@@ -163,7 +163,10 @@ fn poke_spec(context: &Context, spec: Option<&str>) -> libc::c_int {
|
||||
}
|
||||
}
|
||||
}
|
||||
println!("Import: {} items read from \"{}\".", read_cnt, &real_spec);
|
||||
info!(
|
||||
context,
|
||||
"Import: {} items read from \"{}\".", read_cnt, &real_spec
|
||||
);
|
||||
if read_cnt > 0 {
|
||||
context.call_cb(Event::MsgsChanged {
|
||||
chat_id: 0,
|
||||
@@ -187,7 +190,8 @@ fn log_msg(context: &Context, prefix: impl AsRef<str>, msg: &Message) {
|
||||
};
|
||||
let temp2 = dc_timestamp_to_str(msg.get_timestamp());
|
||||
let msgtext = msg.get_text();
|
||||
println!(
|
||||
info!(
|
||||
context,
|
||||
"{}{}{}{}: {} (Contact#{}): {} {}{}{}{}{} [{}]",
|
||||
prefix.as_ref(),
|
||||
msg.get_id(),
|
||||
@@ -221,14 +225,16 @@ fn log_msglist(context: &Context, msglist: &Vec<MsgId>) -> Result<(), Error> {
|
||||
let mut lines_out = 0;
|
||||
for &msg_id in msglist {
|
||||
if msg_id.is_daymarker() {
|
||||
println!(
|
||||
info!(
|
||||
context,
|
||||
"--------------------------------------------------------------------------------"
|
||||
);
|
||||
|
||||
lines_out += 1
|
||||
} else if !msg_id.is_special() {
|
||||
if lines_out == 0 {
|
||||
println!(
|
||||
info!(
|
||||
context,
|
||||
"--------------------------------------------------------------------------------",
|
||||
);
|
||||
lines_out += 1
|
||||
@@ -238,7 +244,8 @@ fn log_msglist(context: &Context, msglist: &Vec<MsgId>) -> Result<(), Error> {
|
||||
}
|
||||
}
|
||||
if lines_out > 0 {
|
||||
println!(
|
||||
info!(
|
||||
context,
|
||||
"--------------------------------------------------------------------------------"
|
||||
);
|
||||
}
|
||||
@@ -288,7 +295,7 @@ fn log_contactlist(context: &Context, contacts: &Vec<u32>) {
|
||||
);
|
||||
}
|
||||
|
||||
println!("Contact#{}: {}{}", contact_id, line, line2);
|
||||
info!(context, "Contact#{}: {}{}", contact_id, line, line2);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -484,7 +491,7 @@ pub fn dc_cmdline(context: &Context, line: &str) -> Result<(), failure::Error> {
|
||||
println!("{:#?}", context.get_info());
|
||||
}
|
||||
"interrupt" => {
|
||||
interrupt_inbox_idle(context);
|
||||
interrupt_inbox_idle(context, true);
|
||||
}
|
||||
"maybenetwork" => {
|
||||
maybe_network(context);
|
||||
@@ -503,13 +510,15 @@ pub fn dc_cmdline(context: &Context, line: &str) -> Result<(), failure::Error> {
|
||||
|
||||
let cnt = chatlist.len();
|
||||
if cnt > 0 {
|
||||
println!(
|
||||
info!(
|
||||
context,
|
||||
"================================================================================"
|
||||
);
|
||||
|
||||
for i in (0..cnt).rev() {
|
||||
let chat = Chat::load_from_db(context, chatlist.get_chat_id(i))?;
|
||||
println!(
|
||||
info!(
|
||||
context,
|
||||
"{}#{}: {} [{} fresh]",
|
||||
chat_prefix(&chat),
|
||||
chat.get_id(),
|
||||
@@ -531,7 +540,8 @@ pub fn dc_cmdline(context: &Context, line: &str) -> Result<(), failure::Error> {
|
||||
let timestr = dc_timestamp_to_str(lot.get_timestamp());
|
||||
let text1 = lot.get_text1();
|
||||
let text2 = lot.get_text2();
|
||||
println!(
|
||||
info!(
|
||||
context,
|
||||
"{}{}{}{} [{}]{}",
|
||||
text1.unwrap_or(""),
|
||||
if text1.is_some() { ": " } else { "" },
|
||||
@@ -544,13 +554,14 @@ pub fn dc_cmdline(context: &Context, line: &str) -> Result<(), failure::Error> {
|
||||
""
|
||||
},
|
||||
);
|
||||
println!(
|
||||
info!(
|
||||
context,
|
||||
"================================================================================"
|
||||
);
|
||||
}
|
||||
}
|
||||
if location::is_sending_locations_to_chat(context, 0) {
|
||||
println!("Location streaming enabled.");
|
||||
info!(context, "Location streaming enabled.");
|
||||
}
|
||||
println!("{} chats", cnt);
|
||||
}
|
||||
@@ -578,7 +589,8 @@ pub fn dc_cmdline(context: &Context, line: &str) -> Result<(), failure::Error> {
|
||||
} else {
|
||||
format!("{} member(s)", members.len())
|
||||
};
|
||||
println!(
|
||||
info!(
|
||||
context,
|
||||
"{}#{}: {} [{}]{}{}",
|
||||
chat_prefix(sel_chat),
|
||||
sel_chat.get_id(),
|
||||
@@ -681,7 +693,7 @@ pub fn dc_cmdline(context: &Context, line: &str) -> Result<(), failure::Error> {
|
||||
ensure!(sel_chat.is_some(), "No chat selected.");
|
||||
|
||||
let contacts = chat::get_chat_contacts(context, sel_chat.as_ref().unwrap().get_id());
|
||||
println!("Memberlist:");
|
||||
info!(context, "Memberlist:");
|
||||
|
||||
log_contactlist(context, &contacts);
|
||||
println!(
|
||||
@@ -707,7 +719,8 @@ pub fn dc_cmdline(context: &Context, line: &str) -> Result<(), failure::Error> {
|
||||
let default_marker = "-".to_string();
|
||||
for location in &locations {
|
||||
let marker = location.marker.as_ref().unwrap_or(&default_marker);
|
||||
println!(
|
||||
info!(
|
||||
context,
|
||||
"Loc#{}: {}: lat={} lng={} acc={} Chat#{} Contact#{} {} {}",
|
||||
location.location_id,
|
||||
dc_timestamp_to_str(location.timestamp),
|
||||
@@ -721,7 +734,7 @@ pub fn dc_cmdline(context: &Context, line: &str) -> Result<(), failure::Error> {
|
||||
);
|
||||
}
|
||||
if locations.is_empty() {
|
||||
println!("No locations.");
|
||||
info!(context, "No locations.");
|
||||
}
|
||||
}
|
||||
"sendlocations" => {
|
||||
|
||||
@@ -41,7 +41,7 @@ use self::cmdline::*;
|
||||
|
||||
// Event Handler
|
||||
|
||||
fn receive_event(_context: &Context, event: Event) {
|
||||
fn receive_event(_context: &Context, event: Event) -> libc::uintptr_t {
|
||||
match event {
|
||||
Event::Info(msg) => {
|
||||
/* do not show the event as this would fill the screen */
|
||||
@@ -111,6 +111,8 @@ fn receive_event(_context: &Context, event: Event) {
|
||||
print!("\x1b[33m{{Received {:?}}}\n\x1b[0m", event);
|
||||
}
|
||||
}
|
||||
|
||||
0
|
||||
}
|
||||
|
||||
// Threads for waiting for messages and for jobs
|
||||
@@ -200,7 +202,7 @@ fn stop_threads(context: &Context) {
|
||||
println!("Stopping threads");
|
||||
IS_RUNNING.store(false, Ordering::Relaxed);
|
||||
|
||||
interrupt_inbox_idle(context);
|
||||
interrupt_inbox_idle(context, true);
|
||||
interrupt_mvbox_idle(context);
|
||||
interrupt_sentbox_idle(context);
|
||||
interrupt_smtp_idle(context);
|
||||
|
||||
@@ -16,18 +16,21 @@ use deltachat::job::{
|
||||
};
|
||||
use deltachat::Event;
|
||||
|
||||
fn cb(_ctx: &Context, event: Event) {
|
||||
fn cb(_ctx: &Context, event: Event) -> usize {
|
||||
print!("[{:?}]", event);
|
||||
|
||||
match event {
|
||||
Event::ConfigureProgress(progress) => {
|
||||
print!(" progress: {}\n", progress);
|
||||
0
|
||||
}
|
||||
Event::Info(msg) | Event::Warning(msg) | Event::Error(msg) | Event::ErrorNetwork(msg) => {
|
||||
print!(" {}\n", msg);
|
||||
0
|
||||
}
|
||||
_ => {
|
||||
print!("\n");
|
||||
0
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -101,7 +104,7 @@ fn main() {
|
||||
println!("stopping threads");
|
||||
|
||||
*running.write().unwrap() = false;
|
||||
deltachat::job::interrupt_inbox_idle(&ctx);
|
||||
deltachat::job::interrupt_inbox_idle(&ctx, true);
|
||||
deltachat::job::interrupt_smtp_idle(&ctx);
|
||||
|
||||
println!("joining");
|
||||
|
||||
@@ -6,81 +6,8 @@ This package provides bindings to the deltachat-core_ Rust -library
|
||||
which provides imap/smtp/crypto handling as well as chat/group/messages
|
||||
handling to Android, Desktop and IO user interfaces.
|
||||
|
||||
|
||||
Installing bindings from source (Updated: 21-Dec-2019)
|
||||
=========================================================
|
||||
|
||||
Install Rust and Cargo first. Deltachat needs a specific nightly
|
||||
version, the easiest is probably to first install Rust stable from
|
||||
rustup and then use this to install the correct nightly version.
|
||||
|
||||
Install rustup using::
|
||||
|
||||
curl https://sh.rustup.rs -sSf | sh
|
||||
|
||||
GIT clone the repo and use rustup to check the correct nightly version
|
||||
is available, if you do not have the right nightly version rustup will
|
||||
download and install it::
|
||||
|
||||
git clone https://github.com/deltachat/deltachat-core-rust
|
||||
cd deltachat-core-rust
|
||||
rustup show
|
||||
|
||||
To install the python bindings make sure you have python installed, a
|
||||
recent 3.x version will also come with the required venv module.
|
||||
E.g. on Debian-based systems `apt install python3 python3-pip
|
||||
python3-venv` should give you a usable python installation. If you
|
||||
prefer you can also
|
||||
`Install virtualenv <https://virtualenv.pypa.io/en/stable/installation/>`_
|
||||
as an alternative to `venv`.
|
||||
|
||||
Ensure you are in the deltachat-core-rust/python directory, create the
|
||||
vivrtual environment and activate it in your shell::
|
||||
|
||||
cd python
|
||||
python3 -m venv venv # or: virtualenv venv
|
||||
source venv/bin/activate
|
||||
|
||||
You should now be able to build the python bindings using the supplied
|
||||
script::
|
||||
|
||||
./install_python_bindings.py
|
||||
|
||||
The installation might take a while, depending on your machine.
|
||||
The bindings will be installed in release mode but with debug symbols.
|
||||
The release mode is necessary because some tests generate RSA keys
|
||||
which is prohibitively slow in debug mode.
|
||||
|
||||
After successful binding installation you can install a few more
|
||||
python packages before finally running the tests::
|
||||
|
||||
python -m pip install pytest pytest-timeout pytest-rerunfailures
|
||||
pytest -v tests
|
||||
|
||||
|
||||
running "live" tests (experimental)
|
||||
-----------------------------------
|
||||
|
||||
If you want to run "liveconfig" functional tests you can set
|
||||
``DCC_PY_LIVECONFIG`` to:
|
||||
|
||||
- a particular https-url that you can ask for from the delta
|
||||
chat devs.
|
||||
|
||||
- or the path of a file that contains two lines, each describing
|
||||
via "addr=... mail_pw=..." a test account login that will
|
||||
be used for the live tests.
|
||||
|
||||
With ``DCC_PY_LIVECONFIG`` set pytest invocations will use real
|
||||
e-mail accounts and run through all functional "liveconfig" tests.
|
||||
|
||||
|
||||
============================================================================================================================
|
||||
(21-Dec-2019) THE BELOW WHEELS ARE CURRENTLY NOT WORKING/BROKEN, COMPILE FROM SOURCE USING ABOVE INSTRUCTIONS INSTEAD
|
||||
============================================================================================================================
|
||||
|
||||
Installing pre-built packages (linux-only) (OUTDATED)
|
||||
========================================================
|
||||
Installing pre-built packages (linux-only)
|
||||
==========================================
|
||||
|
||||
If you have a linux system you may install the ``deltachat`` binary "wheel" package
|
||||
without any "build-from-source" steps.
|
||||
@@ -104,8 +31,8 @@ without any "build-from-source" steps.
|
||||
python -c "import deltachat"
|
||||
|
||||
|
||||
Installing a wheel from a PR/branch (OUTDATED)
|
||||
-------------------------------------------------
|
||||
Installing a wheel from a PR/branch
|
||||
---------------------------------------
|
||||
|
||||
For Linux, we automatically build wheels for all github PR branches
|
||||
and push them to a python package index. To install the latest github ``master`` branch::
|
||||
@@ -119,7 +46,62 @@ and push them to a python package index. To install the latest github ``master``
|
||||
`in contact with us <https://delta.chat/en/contribute>`_.
|
||||
|
||||
|
||||
Installing bindings from source
|
||||
===============================
|
||||
|
||||
If you can't use "binary" method above then you need to compile
|
||||
to core deltachat library::
|
||||
|
||||
git clone https://github.com/deltachat/deltachat-core-rust
|
||||
cd deltachat-core-rust
|
||||
cd python
|
||||
|
||||
If you don't have one active, create and activate a python "virtualenv":
|
||||
|
||||
python virtualenv venv # or python -m venv
|
||||
source venv/bin/activate
|
||||
|
||||
Afterwards ``which python`` tells you that it comes out of the "venv"
|
||||
directory that contains all python install artifacts. Let's first
|
||||
install test tools::
|
||||
|
||||
pip install pytest pytest-timeout pytest-rerunfailures requests
|
||||
|
||||
then cargo-build and install the deltachat bindings::
|
||||
|
||||
python install_python_bindings.py
|
||||
|
||||
The bindings will be installed in release mode but with debug symbols.
|
||||
The release mode is necessary because some tests generate RSA keys
|
||||
which is prohibitively slow in debug mode.
|
||||
|
||||
After successful binding installation you can finally run the tests::
|
||||
|
||||
pytest -v tests
|
||||
|
||||
.. note::
|
||||
|
||||
Some tests are sometimes failing/hanging because of
|
||||
https://github.com/deltachat/deltachat-core-rust/issues/331
|
||||
and
|
||||
https://github.com/deltachat/deltachat-core-rust/issues/326
|
||||
|
||||
|
||||
running "live" tests (experimental)
|
||||
-----------------------------------
|
||||
|
||||
If you want to run "liveconfig" functional tests you can set
|
||||
``DCC_PY_LIVECONFIG`` to:
|
||||
|
||||
- a particular https-url that you can ask for from the delta
|
||||
chat devs.
|
||||
|
||||
- or the path of a file that contains two lines, each describing
|
||||
via "addr=... mail_pw=..." a test account login that will
|
||||
be used for the live tests.
|
||||
|
||||
With ``DCC_PY_LIVECONFIG`` set pytest invocations will use real
|
||||
e-mail accounts and run through all functional "liveconfig" tests.
|
||||
|
||||
|
||||
|
||||
|
||||
@@ -26,7 +26,7 @@ class Account(object):
|
||||
by the underlying deltachat core library. All public Account methods are
|
||||
meant to be memory-safe and return memory-safe objects.
|
||||
"""
|
||||
def __init__(self, db_path, logid=None, eventlogging=True, os_name=None, debug=True):
|
||||
def __init__(self, db_path, logid=None, eventlogging=True, debug=True):
|
||||
""" initialize account object.
|
||||
|
||||
:param db_path: a path to the account database. The database
|
||||
@@ -34,11 +34,10 @@ class Account(object):
|
||||
:param logid: an optional logging prefix that should be used with
|
||||
the default internal logging.
|
||||
:param eventlogging: if False no eventlogging and no context callback will be configured
|
||||
:param os_name: this will be put to the X-Mailer header in outgoing messages
|
||||
:param debug: turn on debug logging for events.
|
||||
"""
|
||||
self._dc_context = ffi.gc(
|
||||
lib.dc_context_new(lib.py_dc_callback, ffi.NULL, as_dc_charpointer(os_name)),
|
||||
lib.dc_context_new(lib.py_dc_callback, ffi.NULL, ffi.NULL),
|
||||
_destroy_dc_context,
|
||||
)
|
||||
if eventlogging:
|
||||
|
||||
Binary file not shown.
|
Before Width: | Height: | Size: 2.7 KiB After Width: | Height: | Size: 1.9 KiB |
@@ -16,14 +16,6 @@ class TestOfflineAccountBasic:
|
||||
with pytest.raises(ValueError):
|
||||
Account(p.strpath)
|
||||
|
||||
def test_os_name(self, tmpdir):
|
||||
p = tmpdir.join("hello.db")
|
||||
# we can't easily test if os_name is used in X-Mailer
|
||||
# outgoing messages without a full Online test
|
||||
# but we at least check Account accepts the arg
|
||||
ac1 = Account(p.strpath, os_name="solarpunk")
|
||||
ac1.get_info()
|
||||
|
||||
def test_getinfo(self, acfactory):
|
||||
ac1 = acfactory.get_unconfigured_account()
|
||||
d = ac1.get_info()
|
||||
@@ -695,39 +687,6 @@ class TestOnlineAccount:
|
||||
except queue.Empty:
|
||||
pass # mark_seen_messages() has generated events before it returns
|
||||
|
||||
def test_mdn_asymetric(self, acfactory, lp):
|
||||
ac1, ac2 = acfactory.get_two_online_accounts()
|
||||
|
||||
lp.sec("ac1: create chat with ac2")
|
||||
chat = self.get_chat(ac1, ac2, both_created=True)
|
||||
|
||||
# make sure mdns are enabled (usually enabled by default already)
|
||||
ac1.set_config("mdns_enabled", "1")
|
||||
ac2.set_config("mdns_enabled", "1")
|
||||
|
||||
lp.sec("sending text message from ac1 to ac2")
|
||||
msg_out = chat.send_text("message1")
|
||||
|
||||
assert len(chat.get_messages()) == 1
|
||||
|
||||
lp.sec("disable ac1 MDNs")
|
||||
ac1.set_config("mdns_enabled", "0")
|
||||
|
||||
lp.sec("wait for ac2 to receive message")
|
||||
msg = ac2.wait_next_incoming_message()
|
||||
|
||||
assert len(msg.chat.get_messages()) == 1
|
||||
|
||||
lp.sec("ac2: mark incoming message as seen")
|
||||
ac2.mark_seen_messages([msg])
|
||||
|
||||
lp.sec("ac1: waiting for incoming activity")
|
||||
# wait for MOVED event because even ignored read-receipts should be moved
|
||||
ac1._evlogger.get_matching("DC_EVENT_IMAP_MESSAGE_MOVED")
|
||||
|
||||
assert len(chat.get_messages()) == 1
|
||||
assert not msg_out.is_out_mdn_received()
|
||||
|
||||
def test_send_and_receive_will_encrypt_decrypt(self, acfactory, lp):
|
||||
ac1, ac2 = acfactory.get_two_online_accounts()
|
||||
|
||||
@@ -1209,15 +1168,6 @@ class TestGroupStressTests:
|
||||
print("chat is", msg.chat)
|
||||
assert len(msg.chat.get_contacts()) == 4
|
||||
|
||||
lp.sec("ac3: checking that 'ac4' is a known contact")
|
||||
ac3 = accounts[1]
|
||||
msg3 = ac3.wait_next_incoming_message()
|
||||
assert msg3.text == "hello"
|
||||
contacts = ac3.get_contacts()
|
||||
assert len(contacts) == 3
|
||||
ac4_contacts = ac3.get_contacts(query=accounts[2].get_config("addr"))
|
||||
assert len(ac4_contacts) == 1
|
||||
|
||||
lp.sec("ac1: removing one contacts and checking things are right")
|
||||
to_remove = msg.chat.get_contacts()[-1]
|
||||
msg.chat.remove_contact(to_remove)
|
||||
|
||||
@@ -55,7 +55,6 @@ if __name__ == "__main__":
|
||||
replace_toml_version("Cargo.toml", newversion)
|
||||
replace_toml_version("deltachat-ffi/Cargo.toml", newversion)
|
||||
|
||||
subprocess.call(["cargo", "check"])
|
||||
subprocess.call(["git", "add", "-u"])
|
||||
# subprocess.call(["cargo", "update", "-p", "deltachat"])
|
||||
|
||||
|
||||
204
src/chat.rs
204
src/chat.rs
@@ -4,7 +4,7 @@ use std::path::{Path, PathBuf};
|
||||
|
||||
use itertools::Itertools;
|
||||
use num_traits::FromPrimitive;
|
||||
use serde::{Deserialize, Serialize};
|
||||
use serde_json::json;
|
||||
|
||||
use crate::blob::{BlobError, BlobObject};
|
||||
use crate::chatlist::*;
|
||||
@@ -240,30 +240,6 @@ impl Chat {
|
||||
color
|
||||
}
|
||||
|
||||
/// Returns a struct describing the current state of the chat.
|
||||
///
|
||||
/// This is somewhat experimental, even more so than the rest of
|
||||
/// deltachat, and the data returned is still subject to change.
|
||||
pub fn get_info(&self, context: &Context) -> Result<ChatInfo, Error> {
|
||||
let draft = match get_draft(context, self.id)? {
|
||||
Some(message) => message.text.unwrap_or_else(String::new),
|
||||
_ => String::new(),
|
||||
};
|
||||
Ok(ChatInfo {
|
||||
id: self.id,
|
||||
type_: self.typ as u32,
|
||||
name: self.name.clone(),
|
||||
archived: self.archived,
|
||||
param: self.param.to_string(),
|
||||
gossiped_timestamp: self.get_gossiped_timestamp(context),
|
||||
is_sending_locations: self.is_sending_locations,
|
||||
color: self.get_color(context),
|
||||
profile_image: self.get_profile_image(context).unwrap_or_else(PathBuf::new),
|
||||
subtitle: self.get_subtitle(context),
|
||||
draft,
|
||||
})
|
||||
}
|
||||
|
||||
/// Returns true if the chat is archived.
|
||||
pub fn is_archived(&self) -> bool {
|
||||
self.archived
|
||||
@@ -521,68 +497,6 @@ impl Chat {
|
||||
}
|
||||
}
|
||||
|
||||
/// The current state of a chat.
|
||||
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
|
||||
pub struct ChatInfo {
|
||||
/// The chat ID.
|
||||
pub id: u32,
|
||||
|
||||
/// The type of chat as a `u32` representation of [Chattype].
|
||||
///
|
||||
/// On the C API this number is one of the
|
||||
/// `DC_CHAT_TYPE_UNDEFINED`, `DC_CHAT_TYPE_SINGLE`,
|
||||
/// `DC_CHAT_TYPE_GROUP` or `DC_CHAT_TYPE_VERIFIED_GROUP`
|
||||
/// constants.
|
||||
#[serde(rename = "type")]
|
||||
pub type_: u32,
|
||||
|
||||
/// The name of the chat.
|
||||
pub name: String,
|
||||
|
||||
/// Whether the chat is archived.
|
||||
pub archived: bool,
|
||||
|
||||
/// The "params" of the chat.
|
||||
///
|
||||
/// This is the string-serialised version of [Params] currently.
|
||||
pub param: String,
|
||||
|
||||
/// Something to do with gossiping and timestamps?
|
||||
pub gossiped_timestamp: i64,
|
||||
|
||||
/// Whether this chat is currently sending location-stream messages.
|
||||
pub is_sending_locations: bool,
|
||||
|
||||
/// Colour this chat should be represented in by the UI.
|
||||
///
|
||||
/// Yes, spelling colour is hard.
|
||||
pub color: u32,
|
||||
|
||||
/// The path to the profile image.
|
||||
///
|
||||
/// If there is no profile image set this will be an empty string
|
||||
/// currently.
|
||||
pub profile_image: PathBuf,
|
||||
|
||||
/// Subtitle for the chat.
|
||||
pub subtitle: String,
|
||||
|
||||
/// The draft message text.
|
||||
///
|
||||
/// If the chat has not draft this is an empty string.
|
||||
///
|
||||
/// TODO: This doesn't seem rich enough, it can not handle drafts
|
||||
/// which contain non-text parts. Perhaps it should be a
|
||||
/// simple `has_draft` bool instead.
|
||||
pub draft: String,
|
||||
// ToDo:
|
||||
// - [ ] deaddrop,
|
||||
// - [ ] summary,
|
||||
// - [ ] lastUpdated,
|
||||
// - [ ] freshMessageCounter,
|
||||
// - [ ] email
|
||||
}
|
||||
|
||||
/// Create a normal chat or a group chat by a messages ID that comes typically
|
||||
/// from the deaddrop, DC_CHAT_ID_DEADDROP (1).
|
||||
///
|
||||
@@ -2076,6 +1990,54 @@ pub fn forward_msgs(context: &Context, msg_ids: &[MsgId], chat_id: u32) -> Resul
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub fn get_info_json(context: &Context, chat_id: u32) -> Result<String, Error> {
|
||||
let chat = Chat::load_from_db(context, chat_id).unwrap();
|
||||
|
||||
// ToDo:
|
||||
// - [x] id
|
||||
// - [x] type
|
||||
// - [x] name
|
||||
// - [x] archived
|
||||
// - [x] color
|
||||
// - [x] profileImage
|
||||
// - [x] subtitle
|
||||
// - [x] draft,
|
||||
// - [ ] deaddrop,
|
||||
// - [ ] summary,
|
||||
// - [ ] lastUpdated,
|
||||
// - [ ] freshMessageCounter,
|
||||
// - [ ] email
|
||||
|
||||
let profile_image = match chat.get_profile_image(context) {
|
||||
Some(path) => path.into_os_string().into_string().unwrap(),
|
||||
None => "".to_string(),
|
||||
};
|
||||
|
||||
let draft = match get_draft(context, chat_id) {
|
||||
Ok(message) => match message {
|
||||
Some(m) => m.text.unwrap_or_else(|| "".to_string()),
|
||||
None => "".to_string(),
|
||||
},
|
||||
Err(_) => "".to_string(),
|
||||
};
|
||||
|
||||
let s = json!({
|
||||
"id": chat.id,
|
||||
"type": chat.typ as u32,
|
||||
"name": chat.name,
|
||||
"archived": chat.archived,
|
||||
"param": chat.param.to_string(),
|
||||
"gossiped_timestamp": chat.get_gossiped_timestamp(context),
|
||||
"is_sending_locations": chat.is_sending_locations,
|
||||
"color": chat.get_color(context),
|
||||
"profile_image": profile_image,
|
||||
"subtitle": chat.get_subtitle(context),
|
||||
"draft": draft
|
||||
});
|
||||
|
||||
Ok(s.to_string())
|
||||
}
|
||||
|
||||
pub fn get_chat_contact_cnt(context: &Context, chat_id: u32) -> usize {
|
||||
context
|
||||
.sql
|
||||
@@ -2197,22 +2159,6 @@ pub fn was_device_msg_ever_added(context: &Context, label: &str) -> Result<bool,
|
||||
Ok(false)
|
||||
}
|
||||
|
||||
// needed on device-switches during export/import;
|
||||
// - deletion in `msgs` with `DC_CONTACT_ID_DEVICE` makes sure,
|
||||
// no wrong information are shown in the device chat
|
||||
// - deletion in `devmsglabels` makes sure,
|
||||
// deleted messages are resetted and useful messages can be added again
|
||||
pub fn delete_and_reset_all_device_msgs(context: &Context) -> Result<(), Error> {
|
||||
context.sql.execute(
|
||||
"DELETE FROM msgs WHERE from_id=?;",
|
||||
params![DC_CONTACT_ID_DEVICE],
|
||||
)?;
|
||||
context
|
||||
.sql
|
||||
.execute("DELETE FROM devmsglabels;", params![])?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Adds an informational message to chat.
|
||||
///
|
||||
/// For example, it can be a message showing that a member was added to a group.
|
||||
@@ -2246,41 +2192,8 @@ pub fn add_info_msg(context: &Context, chat_id: u32, text: impl AsRef<str>) {
|
||||
mod tests {
|
||||
use super::*;
|
||||
|
||||
use crate::contact::Contact;
|
||||
use crate::test_utils::*;
|
||||
|
||||
#[test]
|
||||
fn test_chat_info() {
|
||||
let t = dummy_context();
|
||||
let bob = Contact::create(&t.ctx, "bob", "bob@example.com").unwrap();
|
||||
let chat_id = create_by_contact_id(&t.ctx, bob).unwrap();
|
||||
let chat = Chat::load_from_db(&t.ctx, chat_id).unwrap();
|
||||
let info = chat.get_info(&t.ctx).unwrap();
|
||||
|
||||
// Ensure we can serialise this.
|
||||
println!("{}", serde_json::to_string_pretty(&info).unwrap());
|
||||
|
||||
let expected = r#"
|
||||
{
|
||||
"id": 10,
|
||||
"type": 100,
|
||||
"name": "bob",
|
||||
"archived": false,
|
||||
"param": "",
|
||||
"gossiped_timestamp": 0,
|
||||
"is_sending_locations": false,
|
||||
"color": 15895624,
|
||||
"profile_image": "",
|
||||
"subtitle": "bob@example.com",
|
||||
"draft": ""
|
||||
}
|
||||
"#;
|
||||
|
||||
// Ensure we can deserialise this.
|
||||
let loaded: ChatInfo = serde_json::from_str(expected).unwrap();
|
||||
assert_eq!(info, loaded);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_get_draft_no_draft() {
|
||||
let t = dummy_context();
|
||||
@@ -2510,25 +2423,6 @@ mod tests {
|
||||
assert!(forward_msgs(&t.ctx, &[msg_id], device_chat_id).is_err());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_delete_and_reset_all_device_msgs() {
|
||||
let t = test_context(Some(Box::new(logging_cb)));
|
||||
let mut msg = Message::new(Viewtype::Text);
|
||||
msg.text = Some("message text".to_string());
|
||||
let msg_id1 = add_device_msg(&t.ctx, Some("some-label"), Some(&mut msg)).unwrap();
|
||||
|
||||
// adding a device message with the same label won't be executed again ...
|
||||
assert!(was_device_msg_ever_added(&t.ctx, "some-label").unwrap());
|
||||
let msg_id2 = add_device_msg(&t.ctx, Some("some-label"), Some(&mut msg)).unwrap();
|
||||
assert!(msg_id2.is_unset());
|
||||
|
||||
// ... unless everything is deleted and resetted - as needed eg. on device switch
|
||||
delete_and_reset_all_device_msgs(&t.ctx).unwrap();
|
||||
assert!(!was_device_msg_ever_added(&t.ctx, "some-label").unwrap());
|
||||
let msg_id3 = add_device_msg(&t.ctx, Some("some-label"), Some(&mut msg)).unwrap();
|
||||
assert_ne!(msg_id1, msg_id3);
|
||||
}
|
||||
|
||||
fn chatlist_len(ctx: &Context, listflags: usize) -> usize {
|
||||
Chatlist::try_load(ctx, listflags, None, None)
|
||||
.unwrap()
|
||||
|
||||
@@ -144,7 +144,7 @@ impl Context {
|
||||
}
|
||||
Config::InboxWatch => {
|
||||
let ret = self.sql.set_raw_config(self, key, value);
|
||||
interrupt_inbox_idle(self);
|
||||
interrupt_inbox_idle(self, true);
|
||||
ret
|
||||
}
|
||||
Config::SentboxWatch => {
|
||||
|
||||
@@ -114,7 +114,7 @@ pub const DC_MSG_ID_LAST_SPECIAL: u32 = 9;
|
||||
/// approx. max. length returned by dc_msg_get_text()
|
||||
const DC_MAX_GET_TEXT_LEN: usize = 30000;
|
||||
/// approx. max. length returned by dc_get_msg_info()
|
||||
const DC_MAX_GET_INFO_LEN: usize = 100_000;
|
||||
const DC_MAX_GET_INFO_LEN: usize = 100000;
|
||||
|
||||
pub const DC_CONTACT_ID_UNDEFINED: u32 = 0;
|
||||
pub const DC_CONTACT_ID_SELF: u32 = 1;
|
||||
@@ -191,7 +191,6 @@ pub const AVATAR_SIZE: u32 = 192;
|
||||
#[repr(i32)]
|
||||
pub enum Viewtype {
|
||||
Unknown = 0,
|
||||
|
||||
/// Text message.
|
||||
/// The text of the message is set using dc_msg_set_text()
|
||||
/// and retrieved with dc_msg_get_text().
|
||||
|
||||
@@ -48,26 +48,20 @@ pub struct Contact {
|
||||
///
|
||||
/// Normal contact IDs are larger than these special ones (larger than DC_CONTACT_ID_LAST_SPECIAL).
|
||||
pub id: u32,
|
||||
|
||||
/// Contact name. It is recommended to use `Contact::get_name`,
|
||||
/// `Contact::get_display_name` or `Contact::get_name_n_addr` to access this field.
|
||||
/// May be empty, initially set to `authname`.
|
||||
name: String,
|
||||
|
||||
/// Name authorized by the contact himself. Only this name may be spread to others,
|
||||
/// e.g. in To:-lists. May be empty. It is recommended to use `Contact::get_authname`,
|
||||
/// to access this field.
|
||||
authname: String,
|
||||
|
||||
/// E-Mail-Address of the contact. It is recommended to use `Contact::get_addr`` to access this field.
|
||||
addr: String,
|
||||
|
||||
/// Blocked state. Use dc_contact_is_blocked to access this field.
|
||||
blocked: bool,
|
||||
|
||||
/// The origin/source of the contact.
|
||||
pub origin: Origin,
|
||||
|
||||
/// Parameters as Param::ProfileImage
|
||||
pub param: Params,
|
||||
}
|
||||
@@ -79,54 +73,38 @@ pub struct Contact {
|
||||
#[repr(i32)]
|
||||
pub enum Origin {
|
||||
Unknown = 0,
|
||||
|
||||
/// From: of incoming messages of unknown sender
|
||||
IncomingUnknownFrom = 0x10,
|
||||
|
||||
/// Cc: of incoming messages of unknown sender
|
||||
IncomingUnknownCc = 0x20,
|
||||
|
||||
/// To: of incoming messages of unknown sender
|
||||
IncomingUnknownTo = 0x40,
|
||||
|
||||
/// address scanned but not verified
|
||||
UnhandledQrScan = 0x80,
|
||||
|
||||
/// Reply-To: of incoming message of known sender
|
||||
IncomingReplyTo = 0x100,
|
||||
|
||||
/// Cc: of incoming message of known sender
|
||||
IncomingCc = 0x200,
|
||||
|
||||
/// additional To:'s of incoming message of known sender
|
||||
IncomingTo = 0x400,
|
||||
|
||||
/// a chat was manually created for this user, but no message yet sent
|
||||
CreateChat = 0x800,
|
||||
|
||||
/// message sent by us
|
||||
OutgoingBcc = 0x1000,
|
||||
|
||||
/// message sent by us
|
||||
OutgoingCc = 0x2000,
|
||||
|
||||
/// message sent by us
|
||||
OutgoingTo = 0x4000,
|
||||
|
||||
/// internal use
|
||||
Internal = 0x40000,
|
||||
|
||||
/// address is in our address book
|
||||
AdressBook = 0x80000,
|
||||
|
||||
/// set on Alice's side for contacts like Bob that have scanned the QR code offered by her. Only means the contact has once been established using the "securejoin" procedure in the past, getting the current key verification status requires calling dc_contact_is_verified() !
|
||||
SecurejoinInvited = 0x0100_0000,
|
||||
|
||||
SecurejoinInvited = 0x1000000,
|
||||
/// set on Bob's side for contacts scanned and verified from a QR code. Only means the contact has once been established using the "securejoin" procedure in the past, getting the current key verification status requires calling dc_contact_is_verified() !
|
||||
SecurejoinJoined = 0x0200_0000,
|
||||
|
||||
SecurejoinJoined = 0x2000000,
|
||||
/// contact added mannually by dc_create_contact(), this should be the largets origin as otherwise the user cannot modify the names
|
||||
ManuallyCreated = 0x0400_0000,
|
||||
ManuallyCreated = 0x4000000,
|
||||
}
|
||||
|
||||
impl Default for Origin {
|
||||
@@ -136,11 +114,14 @@ impl Default for Origin {
|
||||
}
|
||||
|
||||
impl Origin {
|
||||
/// Contacts that are known, i. e. they came in via accepted contacts or
|
||||
/// themselves an accepted contact. Known contacts are shown in the
|
||||
/// contact list when one creates a chat and wants to add members etc.
|
||||
pub fn is_known(self) -> bool {
|
||||
self >= Origin::IncomingReplyTo
|
||||
/// Contacts that are verified and known not to be spam.
|
||||
pub fn is_verified(self) -> bool {
|
||||
self as i32 >= 0x100
|
||||
}
|
||||
|
||||
/// Contacts that are shown in the contact list.
|
||||
pub fn include_in_contactlist(self) -> bool {
|
||||
self as i32 >= DC_ORIGIN_MIN_CONTACT_LIST
|
||||
}
|
||||
}
|
||||
|
||||
@@ -420,7 +401,6 @@ impl Contact {
|
||||
{
|
||||
row_id = sql::get_rowid(context, &context.sql, "contacts", "addr", addr);
|
||||
sth_modified = Modifier::Created;
|
||||
info!(context, "added contact id={} addr={}", row_id, addr);
|
||||
} else {
|
||||
error!(context, "Cannot add contact.");
|
||||
}
|
||||
@@ -506,7 +486,7 @@ impl Contact {
|
||||
params![
|
||||
self_addr,
|
||||
DC_CONTACT_ID_LAST_SPECIAL as i32,
|
||||
Origin::IncomingReplyTo,
|
||||
0x100,
|
||||
&s3str_like_cmd,
|
||||
&s3str_like_cmd,
|
||||
if flag_verified_only { 0 } else { 1 },
|
||||
@@ -991,7 +971,7 @@ fn set_block_contact(context: &Context, contact_id: u32, new_blocking: bool) {
|
||||
pub fn set_profile_image(
|
||||
context: &Context,
|
||||
contact_id: u32,
|
||||
profile_image: &AvatarAction,
|
||||
profile_image: AvatarAction,
|
||||
) -> Result<()> {
|
||||
// the given profile image is expected to be already in the blob directory
|
||||
// as profile images can be set only by receiving messages, this should be always the case, however.
|
||||
|
||||
@@ -5,6 +5,8 @@ use std::ffi::OsString;
|
||||
use std::path::{Path, PathBuf};
|
||||
use std::sync::{Arc, Condvar, Mutex, RwLock};
|
||||
|
||||
use libc::uintptr_t;
|
||||
|
||||
use crate::chat::*;
|
||||
use crate::config::Config;
|
||||
use crate::constants::*;
|
||||
@@ -30,7 +32,12 @@ use crate::sql::Sql;
|
||||
/// * `event` - One of the [Event] items.
|
||||
/// * `data1` - Depends on the event parameter, see [Event].
|
||||
/// * `data2` - Depends on the event parameter, see [Event].
|
||||
pub type ContextCallback = dyn Fn(&Context, Event) -> () + Send + Sync;
|
||||
///
|
||||
/// # Returns
|
||||
///
|
||||
/// This callback must return 0 unless stated otherwise in the event
|
||||
/// description at [Event].
|
||||
pub type ContextCallback = dyn Fn(&Context, Event) -> uintptr_t + Send + Sync;
|
||||
|
||||
#[derive(DebugStub)]
|
||||
pub struct Context {
|
||||
@@ -79,7 +86,12 @@ pub fn get_info() -> HashMap<&'static str, String> {
|
||||
"sqlite_thread_safe",
|
||||
unsafe { rusqlite::ffi::sqlite3_threadsafe() }.to_string(),
|
||||
);
|
||||
res.insert("arch", (std::mem::size_of::<usize>() * 8).to_string());
|
||||
res.insert(
|
||||
"arch",
|
||||
(std::mem::size_of::<*mut libc::c_void>())
|
||||
.wrapping_mul(8)
|
||||
.to_string(),
|
||||
);
|
||||
res.insert("level", "awesome".into());
|
||||
res
|
||||
}
|
||||
@@ -160,8 +172,8 @@ impl Context {
|
||||
self.blobdir.as_path()
|
||||
}
|
||||
|
||||
pub fn call_cb(&self, event: Event) {
|
||||
(*self.cb)(self, event);
|
||||
pub fn call_cb(&self, event: Event) -> uintptr_t {
|
||||
(*self.cb)(self, event)
|
||||
}
|
||||
|
||||
/*******************************************************************************
|
||||
@@ -521,7 +533,7 @@ mod tests {
|
||||
let tmp = tempfile::tempdir().unwrap();
|
||||
let dbfile = tmp.path().join("db.sqlite");
|
||||
std::fs::write(&dbfile, b"123").unwrap();
|
||||
let res = Context::new(Box::new(|_, _| ()), "FakeOs".into(), dbfile);
|
||||
let res = Context::new(Box::new(|_, _| 0), "FakeOs".into(), dbfile);
|
||||
assert!(res.is_err());
|
||||
}
|
||||
|
||||
@@ -536,7 +548,7 @@ mod tests {
|
||||
fn test_blobdir_exists() {
|
||||
let tmp = tempfile::tempdir().unwrap();
|
||||
let dbfile = tmp.path().join("db.sqlite");
|
||||
Context::new(Box::new(|_, _| ()), "FakeOS".into(), dbfile).unwrap();
|
||||
Context::new(Box::new(|_, _| 0), "FakeOS".into(), dbfile).unwrap();
|
||||
let blobdir = tmp.path().join("db.sqlite-blobs");
|
||||
assert!(blobdir.is_dir());
|
||||
}
|
||||
@@ -547,7 +559,7 @@ mod tests {
|
||||
let dbfile = tmp.path().join("db.sqlite");
|
||||
let blobdir = tmp.path().join("db.sqlite-blobs");
|
||||
std::fs::write(&blobdir, b"123").unwrap();
|
||||
let res = Context::new(Box::new(|_, _| ()), "FakeOS".into(), dbfile);
|
||||
let res = Context::new(Box::new(|_, _| 0), "FakeOS".into(), dbfile);
|
||||
assert!(res.is_err());
|
||||
}
|
||||
|
||||
@@ -557,7 +569,7 @@ mod tests {
|
||||
let subdir = tmp.path().join("subdir");
|
||||
let dbfile = subdir.join("db.sqlite");
|
||||
let dbfile2 = dbfile.clone();
|
||||
Context::new(Box::new(|_, _| ()), "FakeOS".into(), dbfile).unwrap();
|
||||
Context::new(Box::new(|_, _| 0), "FakeOS".into(), dbfile).unwrap();
|
||||
assert!(subdir.is_dir());
|
||||
assert!(dbfile2.is_file());
|
||||
}
|
||||
@@ -567,7 +579,7 @@ mod tests {
|
||||
let tmp = tempfile::tempdir().unwrap();
|
||||
let dbfile = tmp.path().join("db.sqlite");
|
||||
let blobdir = PathBuf::new();
|
||||
let res = Context::with_blobdir(Box::new(|_, _| ()), "FakeOS".into(), dbfile, blobdir);
|
||||
let res = Context::with_blobdir(Box::new(|_, _| 0), "FakeOS".into(), dbfile, blobdir);
|
||||
assert!(res.is_err());
|
||||
}
|
||||
|
||||
@@ -576,7 +588,7 @@ mod tests {
|
||||
let tmp = tempfile::tempdir().unwrap();
|
||||
let dbfile = tmp.path().join("db.sqlite");
|
||||
let blobdir = tmp.path().join("blobs");
|
||||
let res = Context::with_blobdir(Box::new(|_, _| ()), "FakeOS".into(), dbfile, blobdir);
|
||||
let res = Context::with_blobdir(Box::new(|_, _| 0), "FakeOS".into(), dbfile, blobdir);
|
||||
assert!(res.is_err());
|
||||
}
|
||||
|
||||
|
||||
@@ -61,6 +61,9 @@ pub fn dc_receive_imf(
|
||||
ensure!(mime_parser.has_headers(), "No Headers Found");
|
||||
|
||||
// the function returns the number of created messages in the database
|
||||
let mut incoming = true;
|
||||
let mut incoming_origin = Origin::Unknown;
|
||||
let mut to_id = 0u32;
|
||||
let mut chat_id = 0;
|
||||
let mut hidden = false;
|
||||
|
||||
@@ -70,11 +73,15 @@ pub fn dc_receive_imf(
|
||||
let mut sent_timestamp = 0;
|
||||
let mut created_db_entries = Vec::new();
|
||||
let mut create_event_to_send = Some(CreateEvent::MsgsChanged);
|
||||
let mut rr_event_to_send = Vec::new();
|
||||
|
||||
let mut to_ids = ContactIds::new();
|
||||
|
||||
// helper method to handle early exit and memory cleanup
|
||||
let cleanup = |context: &Context,
|
||||
create_event_to_send: &Option<CreateEvent>,
|
||||
created_db_entries: &Vec<(usize, MsgId)>| {
|
||||
created_db_entries: &Vec<(usize, MsgId)>,
|
||||
rr_event_to_send: &Vec<(u32, MsgId)>| {
|
||||
if let Some(create_event_to_send) = create_event_to_send {
|
||||
for (chat_id, msg_id) in created_db_entries {
|
||||
let event = match create_event_to_send {
|
||||
@@ -90,6 +97,12 @@ pub fn dc_receive_imf(
|
||||
context.call_cb(event);
|
||||
}
|
||||
}
|
||||
for (chat_id, msg_id) in rr_event_to_send {
|
||||
context.call_cb(Event::MsgRead {
|
||||
chat_id: *chat_id,
|
||||
msg_id: *msg_id,
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
if let Some(value) = mime_parser.get(HeaderDef::Date) {
|
||||
@@ -97,31 +110,48 @@ pub fn dc_receive_imf(
|
||||
sent_timestamp = mailparse::dateparse(value).unwrap_or_default();
|
||||
}
|
||||
|
||||
// Make sure, to_ids starts with the first To:-address (Cc: is added in the loop below pass)
|
||||
if let Some(field) = mime_parser.get(HeaderDef::To) {
|
||||
dc_add_or_lookup_contacts_by_address_list(
|
||||
context,
|
||||
&field,
|
||||
if !incoming {
|
||||
Origin::OutgoingTo
|
||||
} else if incoming_origin.is_verified() {
|
||||
Origin::IncomingTo
|
||||
} else {
|
||||
Origin::IncomingUnknownTo
|
||||
},
|
||||
&mut to_ids,
|
||||
)?;
|
||||
}
|
||||
|
||||
// get From: (it can be an address list!) and check if it is known (for known From:'s we add
|
||||
// the other To:/Cc: in the 3rd pass)
|
||||
// or if From: is equal to SELF (in this case, it is any outgoing messages,
|
||||
// we do not check Return-Path any more as this is unreliable, see issue #150)
|
||||
let mut from_id = 0;
|
||||
let mut from_id_blocked = false;
|
||||
let mut incoming = true;
|
||||
let mut incoming_origin = Origin::Unknown;
|
||||
|
||||
if let Some(field_from) = mime_parser.get(HeaderDef::From_) {
|
||||
let from_ids = dc_add_or_lookup_contacts_by_address_list(
|
||||
let mut from_ids = ContactIds::new();
|
||||
dc_add_or_lookup_contacts_by_address_list(
|
||||
context,
|
||||
&field_from,
|
||||
Origin::IncomingUnknownFrom,
|
||||
&mut from_ids,
|
||||
)?;
|
||||
if from_ids.len() > 1 {
|
||||
warn!(
|
||||
context,
|
||||
"mail has more than one From address, only using first: {:?}", field_from
|
||||
"mail has more than one address in From: {:?}", field_from
|
||||
);
|
||||
}
|
||||
if from_ids.contains(&DC_CONTACT_ID_SELF) {
|
||||
incoming = false;
|
||||
from_id = DC_CONTACT_ID_SELF;
|
||||
incoming_origin = Origin::OutgoingBcc;
|
||||
if to_ids.len() == 1 && to_ids.contains(&DC_CONTACT_ID_SELF) {
|
||||
from_id = DC_CONTACT_ID_SELF;
|
||||
}
|
||||
} else if !from_ids.is_empty() {
|
||||
from_id = from_ids.get_index(0).cloned().unwrap_or_default();
|
||||
incoming_origin = Contact::get_origin_by_id(context, from_id, &mut from_id_blocked)
|
||||
@@ -133,23 +163,6 @@ pub fn dc_receive_imf(
|
||||
}
|
||||
}
|
||||
|
||||
let mut to_ids = ContactIds::new();
|
||||
for header_def in &[HeaderDef::To, HeaderDef::Cc] {
|
||||
if let Some(field) = mime_parser.get(header_def.clone()) {
|
||||
to_ids.extend(&dc_add_or_lookup_contacts_by_address_list(
|
||||
context,
|
||||
&field,
|
||||
if !incoming {
|
||||
Origin::OutgoingTo
|
||||
} else if incoming_origin.is_known() {
|
||||
Origin::IncomingTo
|
||||
} else {
|
||||
Origin::IncomingUnknownTo
|
||||
},
|
||||
)?);
|
||||
}
|
||||
}
|
||||
|
||||
// Add parts
|
||||
|
||||
let rfc724_mid = match mime_parser.get_rfc724_mid() {
|
||||
@@ -166,39 +179,53 @@ pub fn dc_receive_imf(
|
||||
}
|
||||
}
|
||||
};
|
||||
if mime_parser.parts.last().is_some() {
|
||||
if mime_parser.get_last_nonmeta().is_some() {
|
||||
if let Err(err) = add_parts(
|
||||
context,
|
||||
&mut mime_parser,
|
||||
imf_raw,
|
||||
incoming,
|
||||
incoming_origin,
|
||||
&mut incoming_origin,
|
||||
server_folder.as_ref(),
|
||||
server_uid,
|
||||
&to_ids,
|
||||
&mut to_ids,
|
||||
&rfc724_mid,
|
||||
&mut sent_timestamp,
|
||||
from_id,
|
||||
&mut from_id,
|
||||
from_id_blocked,
|
||||
&mut hidden,
|
||||
&mut chat_id,
|
||||
&mut to_id,
|
||||
flags,
|
||||
&mut needs_delete_job,
|
||||
&mut insert_msg_id,
|
||||
&mut created_db_entries,
|
||||
&mut create_event_to_send,
|
||||
) {
|
||||
cleanup(context, &create_event_to_send, &created_db_entries);
|
||||
cleanup(
|
||||
context,
|
||||
&create_event_to_send,
|
||||
&created_db_entries,
|
||||
&rr_event_to_send,
|
||||
);
|
||||
bail!("add_parts error: {:?}", err);
|
||||
}
|
||||
} else {
|
||||
// there are parts in this message, do some basic calculations so that the variables
|
||||
// there are no non-meta data in message, do some basic calculations so that the varaiables
|
||||
// are correct in the further processing
|
||||
if sent_timestamp > time() {
|
||||
sent_timestamp = time()
|
||||
}
|
||||
}
|
||||
|
||||
mime_parser.handle_reports(
|
||||
from_id,
|
||||
sent_timestamp,
|
||||
&mut rr_event_to_send,
|
||||
&server_folder,
|
||||
server_uid,
|
||||
);
|
||||
|
||||
if mime_parser.location_kml.is_some() || mime_parser.message_kml.is_some() {
|
||||
save_locations(
|
||||
context,
|
||||
@@ -211,7 +238,7 @@ pub fn dc_receive_imf(
|
||||
}
|
||||
|
||||
if mime_parser.user_avatar != AvatarAction::None {
|
||||
match contact::set_profile_image(&context, from_id, &mime_parser.user_avatar) {
|
||||
match contact::set_profile_image(&context, from_id, mime_parser.user_avatar) {
|
||||
Ok(()) => {
|
||||
context.call_cb(Event::ChatModified(chat_id));
|
||||
}
|
||||
@@ -239,9 +266,12 @@ pub fn dc_receive_imf(
|
||||
"received message {} has Message-Id: {}", server_uid, rfc724_mid
|
||||
);
|
||||
|
||||
cleanup(context, &create_event_to_send, &created_db_entries);
|
||||
|
||||
mime_parser.handle_reports(from_id, sent_timestamp, &server_folder, server_uid);
|
||||
cleanup(
|
||||
context,
|
||||
&create_event_to_send,
|
||||
&created_db_entries,
|
||||
&rr_event_to_send,
|
||||
);
|
||||
|
||||
Ok(())
|
||||
}
|
||||
@@ -251,16 +281,17 @@ fn add_parts(
|
||||
mut mime_parser: &mut MimeParser,
|
||||
imf_raw: &[u8],
|
||||
incoming: bool,
|
||||
incoming_origin: Origin,
|
||||
incoming_origin: &mut Origin,
|
||||
server_folder: impl AsRef<str>,
|
||||
server_uid: u32,
|
||||
to_ids: &ContactIds,
|
||||
to_ids: &mut ContactIds,
|
||||
rfc724_mid: &str,
|
||||
sent_timestamp: &mut i64,
|
||||
from_id: u32,
|
||||
from_id: &mut u32,
|
||||
from_id_blocked: bool,
|
||||
hidden: &mut bool,
|
||||
chat_id: &mut u32,
|
||||
to_id: &mut u32,
|
||||
flags: u32,
|
||||
needs_delete_job: &mut bool,
|
||||
insert_msg_id: &mut MsgId,
|
||||
@@ -274,7 +305,24 @@ fn add_parts(
|
||||
let mut rcvd_timestamp = 0;
|
||||
let mut mime_in_reply_to = String::new();
|
||||
let mut mime_references = String::new();
|
||||
let mut incoming_origin = incoming_origin;
|
||||
|
||||
// collect the rest information, CC: is added to the to-list, BCC: is ignored
|
||||
// (we should not add BCC to groups as this would split groups. We could add them as "known contacts",
|
||||
// however, the benefit is very small and this may leak data that is expected to be hidden)
|
||||
if let Some(fld_cc) = mime_parser.get(HeaderDef::Cc) {
|
||||
dc_add_or_lookup_contacts_by_address_list(
|
||||
context,
|
||||
fld_cc,
|
||||
if !incoming {
|
||||
Origin::OutgoingCc
|
||||
} else if incoming_origin.is_verified() {
|
||||
Origin::IncomingCc
|
||||
} else {
|
||||
Origin::IncomingUnknownCc
|
||||
},
|
||||
to_ids,
|
||||
)?;
|
||||
}
|
||||
|
||||
// check, if the mail is already in our database - if so, just update the folder/uid
|
||||
// (if the mail was moved around) and finish. (we may get a mail twice eg. if it is
|
||||
@@ -314,15 +362,13 @@ fn add_parts(
|
||||
// - outgoing messages introduce a chat with the first to: address if they are sent by a messenger
|
||||
// - incoming messages introduce a chat only for known contacts if they are sent by a messenger
|
||||
// (of course, the user can add other chats manually later)
|
||||
let to_id: u32;
|
||||
|
||||
if incoming {
|
||||
state = if 0 != flags & DC_IMAP_SEEN {
|
||||
MessageState::InSeen
|
||||
} else {
|
||||
MessageState::InFresh
|
||||
};
|
||||
to_id = DC_CONTACT_ID_SELF;
|
||||
*to_id = DC_CONTACT_ID_SELF;
|
||||
let mut needs_stop_ongoing_process = false;
|
||||
|
||||
// handshake messages must be processed _before_ chats are created
|
||||
@@ -332,7 +378,7 @@ fn add_parts(
|
||||
msgrmsg = 1;
|
||||
*chat_id = 0;
|
||||
allow_creation = true;
|
||||
match handle_securejoin_handshake(context, mime_parser, from_id) {
|
||||
match handle_securejoin_handshake(context, mime_parser, *from_id) {
|
||||
Ok(ret) => {
|
||||
if ret.hide_this_msg {
|
||||
*hidden = true;
|
||||
@@ -354,7 +400,7 @@ fn add_parts(
|
||||
}
|
||||
|
||||
let (test_normal_chat_id, test_normal_chat_id_blocked) =
|
||||
chat::lookup_by_contact_id(context, from_id).unwrap_or_default();
|
||||
chat::lookup_by_contact_id(context, *from_id).unwrap_or_default();
|
||||
|
||||
// get the chat_id - a chat_id here is no indicator that the chat is displayed in the normal list,
|
||||
// it might also be blocked and displayed in the deaddrop as a result
|
||||
@@ -374,7 +420,7 @@ fn add_parts(
|
||||
&mut mime_parser,
|
||||
allow_creation,
|
||||
create_blocked,
|
||||
from_id,
|
||||
*from_id,
|
||||
to_ids,
|
||||
)?;
|
||||
*chat_id = new_chat_id;
|
||||
@@ -395,7 +441,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 *from_id == *to_id {
|
||||
Blocked::Not
|
||||
} else {
|
||||
Blocked::Deaddrop
|
||||
@@ -406,7 +452,7 @@ fn add_parts(
|
||||
chat_id_blocked = test_normal_chat_id_blocked;
|
||||
} else if allow_creation {
|
||||
let (id, bl) =
|
||||
chat::create_or_lookup_by_contact_id(context, from_id, create_blocked)
|
||||
chat::create_or_lookup_by_contact_id(context, *from_id, create_blocked)
|
||||
.unwrap_or_default();
|
||||
*chat_id = id;
|
||||
chat_id_blocked = bl;
|
||||
@@ -418,13 +464,13 @@ fn add_parts(
|
||||
} else if is_reply_to_known_message(context, mime_parser) {
|
||||
// we do not want any chat to be created implicitly. Because of the origin-scale-up,
|
||||
// the contact requests will pop up and this should be just fine.
|
||||
Contact::scaleup_origin_by_id(context, from_id, Origin::IncomingReplyTo);
|
||||
Contact::scaleup_origin_by_id(context, *from_id, Origin::IncomingReplyTo);
|
||||
info!(
|
||||
context,
|
||||
"Message is a reply to a known message, mark sender as known.",
|
||||
);
|
||||
if !incoming_origin.is_known() {
|
||||
incoming_origin = Origin::IncomingReplyTo;
|
||||
if !incoming_origin.is_verified() {
|
||||
*incoming_origin = Origin::IncomingReplyTo;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -439,7 +485,7 @@ fn add_parts(
|
||||
// to not result in a chatlist-contact-request (this would require the state FRESH)
|
||||
if Blocked::Not != chat_id_blocked
|
||||
&& state == MessageState::InFresh
|
||||
&& !incoming_origin.is_known()
|
||||
&& !incoming_origin.is_verified()
|
||||
&& msgrmsg == 0
|
||||
&& show_emails != ShowEmails::All
|
||||
{
|
||||
@@ -458,15 +504,16 @@ fn add_parts(
|
||||
// the mail is on the IMAP server, probably it is also delivered.
|
||||
// We cannot recreate other states (read, error).
|
||||
state = MessageState::OutDelivered;
|
||||
to_id = to_ids.get_index(0).cloned().unwrap_or_default();
|
||||
*from_id = DC_CONTACT_ID_SELF;
|
||||
if !to_ids.is_empty() {
|
||||
*to_id = to_ids.get_index(0).cloned().unwrap_or_default();
|
||||
if *chat_id == 0 {
|
||||
let (new_chat_id, new_chat_id_blocked) = create_or_lookup_group(
|
||||
context,
|
||||
&mut mime_parser,
|
||||
allow_creation,
|
||||
Blocked::Not,
|
||||
from_id,
|
||||
*from_id,
|
||||
to_ids,
|
||||
)?;
|
||||
*chat_id = new_chat_id;
|
||||
@@ -478,13 +525,14 @@ fn add_parts(
|
||||
}
|
||||
}
|
||||
if *chat_id == 0 && allow_creation {
|
||||
let create_blocked = if 0 != msgrmsg && !Contact::is_blocked_load(context, to_id) {
|
||||
let create_blocked = if 0 != msgrmsg && !Contact::is_blocked_load(context, *to_id) {
|
||||
Blocked::Not
|
||||
} else {
|
||||
Blocked::Deaddrop
|
||||
};
|
||||
let (id, bl) = chat::create_or_lookup_by_contact_id(context, to_id, create_blocked)
|
||||
.unwrap_or_default();
|
||||
let (id, bl) =
|
||||
chat::create_or_lookup_by_contact_id(context, *to_id, create_blocked)
|
||||
.unwrap_or_default();
|
||||
*chat_id = id;
|
||||
chat_id_blocked = bl;
|
||||
|
||||
@@ -497,7 +545,7 @@ fn add_parts(
|
||||
}
|
||||
}
|
||||
}
|
||||
let self_sent = from_id == DC_CONTACT_ID_SELF
|
||||
let self_sent = *from_id == DC_CONTACT_ID_SELF
|
||||
&& to_ids.len() == 1
|
||||
&& to_ids.contains(&DC_CONTACT_ID_SELF);
|
||||
|
||||
@@ -524,7 +572,7 @@ fn add_parts(
|
||||
calc_timestamps(
|
||||
context,
|
||||
*chat_id,
|
||||
from_id,
|
||||
*from_id,
|
||||
*sent_timestamp,
|
||||
0 == flags & DC_IMAP_SEEN,
|
||||
&mut sort_timestamp,
|
||||
@@ -564,6 +612,10 @@ fn add_parts(
|
||||
let subject = mime_parser.get_subject().unwrap_or_default();
|
||||
|
||||
for part in mime_parser.parts.iter_mut() {
|
||||
if part.is_meta {
|
||||
continue;
|
||||
}
|
||||
|
||||
if mime_parser.location_kml.is_some()
|
||||
&& icnt == 1
|
||||
&& (part.msg == "-location-" || part.msg.is_empty())
|
||||
@@ -588,8 +640,8 @@ fn add_parts(
|
||||
server_folder.as_ref(),
|
||||
server_uid as i32,
|
||||
*chat_id as i32,
|
||||
from_id as i32,
|
||||
to_id as i32,
|
||||
*from_id as i32,
|
||||
*to_id as i32,
|
||||
sort_timestamp,
|
||||
*sent_timestamp,
|
||||
rcvd_timestamp,
|
||||
@@ -877,15 +929,10 @@ 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) {
|
||||
// The From-address is not part of this group.
|
||||
// It could be a new user or a DSN from a mailer-daemon.
|
||||
// in any case we do not want to recreate the member list
|
||||
// but still show the message as part of the chat.
|
||||
// After all, the sender has a reference/in-reply-to that
|
||||
// points to this chat.
|
||||
let s = context.stock_str(StockMessage::UnknownSenderForChat);
|
||||
mime_parser.repl_msg_by_error(s.to_string());
|
||||
recreate_member_list = true;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1518,7 +1565,8 @@ fn dc_add_or_lookup_contacts_by_address_list(
|
||||
context: &Context,
|
||||
addr_list_raw: &str,
|
||||
origin: Origin,
|
||||
) -> Result<ContactIds> {
|
||||
to_ids: &mut ContactIds,
|
||||
) -> Result<()> {
|
||||
let addrs = match mailparse::addrparse(addr_list_raw) {
|
||||
Ok(addrs) => addrs,
|
||||
Err(err) => {
|
||||
@@ -1526,11 +1574,14 @@ fn dc_add_or_lookup_contacts_by_address_list(
|
||||
}
|
||||
};
|
||||
|
||||
let mut contact_ids = ContactIds::new();
|
||||
info!(
|
||||
context,
|
||||
"dc_add_or_lookup_contacts_by_address raw={:?} addrs={:?}", addr_list_raw, addrs
|
||||
);
|
||||
for addr in addrs.iter() {
|
||||
match addr {
|
||||
mailparse::MailAddr::Single(info) => {
|
||||
contact_ids.insert(add_or_lookup_contact_by_addr(
|
||||
to_ids.insert(add_or_lookup_contact_by_addr(
|
||||
context,
|
||||
&info.display_name,
|
||||
&info.addr,
|
||||
@@ -1539,7 +1590,7 @@ fn dc_add_or_lookup_contacts_by_address_list(
|
||||
}
|
||||
mailparse::MailAddr::Group(infos) => {
|
||||
for info in &infos.addrs {
|
||||
contact_ids.insert(add_or_lookup_contact_by_addr(
|
||||
to_ids.insert(add_or_lookup_contact_by_addr(
|
||||
context,
|
||||
&info.display_name,
|
||||
&info.addr,
|
||||
@@ -1550,7 +1601,7 @@ fn dc_add_or_lookup_contacts_by_address_list(
|
||||
}
|
||||
}
|
||||
|
||||
Ok(contact_ids)
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Add contacts to database on receiving messages.
|
||||
@@ -1568,6 +1619,10 @@ fn add_or_lookup_contact_by_addr(
|
||||
.map(normalize_name)
|
||||
.unwrap_or_default();
|
||||
|
||||
info!(
|
||||
context,
|
||||
"looking up addr={:?} display_name={:?}", addr, display_name_normalized
|
||||
);
|
||||
let (row_id, _modified) =
|
||||
Contact::add_or_lookup(context, display_name_normalized, addr, origin)?;
|
||||
ensure!(row_id > 0, "could not add contact: {:?}", addr);
|
||||
|
||||
273
src/dc_simplify.rs
Normal file
273
src/dc_simplify.rs
Normal file
@@ -0,0 +1,273 @@
|
||||
use crate::dehtml::*;
|
||||
|
||||
#[derive(Copy, Clone)]
|
||||
pub struct Simplify {
|
||||
pub is_forwarded: bool,
|
||||
}
|
||||
|
||||
/// Return index of footer line in vector of message lines, or vector length if
|
||||
/// no footer is found.
|
||||
///
|
||||
/// Also return whether not-standard (rfc3676, §4.3) footer is found.
|
||||
fn find_message_footer(lines: &[&str]) -> (usize, bool) {
|
||||
for (ix, &line) in lines.iter().enumerate() {
|
||||
// quoted-printable may encode `-- ` to `-- =20` which is converted
|
||||
// back to `-- `
|
||||
match line {
|
||||
"-- " | "-- " => return (ix, false),
|
||||
"--" | "---" | "----" => return (ix, true),
|
||||
_ => (),
|
||||
}
|
||||
}
|
||||
(lines.len(), false)
|
||||
}
|
||||
|
||||
impl Simplify {
|
||||
pub fn new() -> Self {
|
||||
Simplify {
|
||||
is_forwarded: false,
|
||||
}
|
||||
}
|
||||
|
||||
/// Simplify and normalise text: Remove quotes, signatures, unnecessary
|
||||
/// lineends etc.
|
||||
/// The data returned from simplify() must be free()'d when no longer used.
|
||||
pub fn simplify(&mut self, input: &str, is_html: bool, is_msgrmsg: bool) -> String {
|
||||
let mut out = if is_html {
|
||||
dehtml(input)
|
||||
} else {
|
||||
input.to_string()
|
||||
};
|
||||
|
||||
out.retain(|c| c != '\r');
|
||||
out = self.simplify_plain_text(&out, is_msgrmsg);
|
||||
out.retain(|c| c != '\r');
|
||||
|
||||
out
|
||||
}
|
||||
|
||||
/**
|
||||
* Simplify Plain Text
|
||||
*/
|
||||
#[allow(non_snake_case, clippy::mut_range_bound, clippy::needless_range_loop)]
|
||||
fn simplify_plain_text(&mut self, buf_terminated: &str, is_msgrmsg: bool) -> String {
|
||||
/* This function ...
|
||||
... removes all text after the line `-- ` (footer mark)
|
||||
... removes full quotes at the beginning and at the end of the text -
|
||||
these are all lines starting with the character `>`
|
||||
... remove a non-empty line before the removed quote (contains sth. like "On 2.9.2016, Bjoern wrote:" in different formats and lanugages) */
|
||||
/* split the given buffer into lines */
|
||||
let lines: Vec<_> = buf_terminated.split('\n').collect();
|
||||
let mut l_first: usize = 0;
|
||||
let mut is_cut_at_begin = false;
|
||||
let (mut l_last, mut is_cut_at_end) = find_message_footer(&lines);
|
||||
|
||||
if l_last > l_first + 2 {
|
||||
let line0 = lines[l_first];
|
||||
let line1 = lines[l_first + 1];
|
||||
let line2 = lines[l_first + 2];
|
||||
if line0 == "---------- Forwarded message ----------"
|
||||
&& line1.starts_with("From: ")
|
||||
&& line2.is_empty()
|
||||
{
|
||||
self.is_forwarded = true;
|
||||
l_first += 3
|
||||
}
|
||||
}
|
||||
for l in l_first..l_last {
|
||||
let line = lines[l];
|
||||
if line == "-----"
|
||||
|| line == "_____"
|
||||
|| line == "====="
|
||||
|| line == "*****"
|
||||
|| line == "~~~~~"
|
||||
{
|
||||
l_last = l;
|
||||
is_cut_at_end = true;
|
||||
/* done */
|
||||
break;
|
||||
}
|
||||
}
|
||||
if !is_msgrmsg {
|
||||
let mut l_lastQuotedLine = None;
|
||||
for l in (l_first..l_last).rev() {
|
||||
let line = lines[l];
|
||||
if is_plain_quote(line) {
|
||||
l_lastQuotedLine = Some(l)
|
||||
} else if !is_empty_line(line) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
if let Some(last_quoted_line) = l_lastQuotedLine {
|
||||
l_last = last_quoted_line;
|
||||
is_cut_at_end = true;
|
||||
if l_last > 1 && is_empty_line(lines[l_last - 1]) {
|
||||
l_last -= 1
|
||||
}
|
||||
if l_last > 1 {
|
||||
let line = lines[l_last - 1];
|
||||
if is_quoted_headline(line) {
|
||||
l_last -= 1
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
if !is_msgrmsg {
|
||||
let mut l_lastQuotedLine_0 = None;
|
||||
let mut hasQuotedHeadline = 0;
|
||||
for l in l_first..l_last {
|
||||
let line = lines[l];
|
||||
if is_plain_quote(line) {
|
||||
l_lastQuotedLine_0 = Some(l)
|
||||
} else if !is_empty_line(line) {
|
||||
if is_quoted_headline(line)
|
||||
&& 0 == hasQuotedHeadline
|
||||
&& l_lastQuotedLine_0.is_none()
|
||||
{
|
||||
hasQuotedHeadline = 1i32
|
||||
} else {
|
||||
/* non-quoting line found */
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
if let Some(last_quoted_line) = l_lastQuotedLine_0 {
|
||||
l_first = last_quoted_line + 1;
|
||||
is_cut_at_begin = true
|
||||
}
|
||||
}
|
||||
/* re-create buffer from the remaining lines */
|
||||
let mut ret = String::new();
|
||||
if is_cut_at_begin {
|
||||
ret += "[...]";
|
||||
}
|
||||
/* we write empty lines only in case and non-empty line follows */
|
||||
let mut pending_linebreaks = 0;
|
||||
let mut content_lines_added = 0;
|
||||
for l in l_first..l_last {
|
||||
let line = lines[l];
|
||||
if is_empty_line(line) {
|
||||
pending_linebreaks += 1
|
||||
} else {
|
||||
if 0 != content_lines_added {
|
||||
if pending_linebreaks > 2i32 {
|
||||
pending_linebreaks = 2i32
|
||||
}
|
||||
while 0 != pending_linebreaks {
|
||||
ret += "\n";
|
||||
pending_linebreaks -= 1
|
||||
}
|
||||
}
|
||||
// the incoming message might contain invalid UTF8
|
||||
ret += line;
|
||||
content_lines_added += 1;
|
||||
pending_linebreaks = 1i32
|
||||
}
|
||||
}
|
||||
if is_cut_at_end && (!is_cut_at_begin || 0 != content_lines_added) {
|
||||
ret += " [...]";
|
||||
}
|
||||
|
||||
ret
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Tools
|
||||
*/
|
||||
fn is_empty_line(buf: &str) -> bool {
|
||||
// XXX: can it be simplified to buf.chars().all(|c| c.is_whitespace())?
|
||||
//
|
||||
// Strictly speaking, it is not equivalent (^A is not whitespace, but less than ' '),
|
||||
// but having control sequences in email body?!
|
||||
//
|
||||
// See discussion at: https://github.com/deltachat/deltachat-core-rust/pull/402#discussion_r317062392
|
||||
for c in buf.chars() {
|
||||
if c > ' ' {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
true
|
||||
}
|
||||
|
||||
fn is_quoted_headline(buf: &str) -> bool {
|
||||
/* This function may be called for the line _directly_ before a quote.
|
||||
The function checks if the line contains sth. like "On 01.02.2016, xy@z wrote:" in various languages.
|
||||
- Currently, we simply check if the last character is a ':'.
|
||||
- Checking for the existence of an email address may fail (headlines may show the user's name instead of the address) */
|
||||
|
||||
buf.len() <= 80 && buf.ends_with(':')
|
||||
}
|
||||
|
||||
fn is_plain_quote(buf: &str) -> bool {
|
||||
buf.starts_with('>')
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
use proptest::prelude::*;
|
||||
|
||||
proptest! {
|
||||
#[test]
|
||||
// proptest does not support [[:graphical:][:space:]] regex.
|
||||
fn test_simplify_plain_text_fuzzy(input in "[!-~\t \n]+") {
|
||||
let output = Simplify::new().simplify_plain_text(&input, true);
|
||||
assert!(output.split('\n').all(|s| s != "-- "));
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_simplify_trim() {
|
||||
let mut simplify = Simplify::new();
|
||||
let html = "\r\r\nline1<br>\r\n\r\n\r\rline2\n\r";
|
||||
let plain = simplify.simplify(html, true, false);
|
||||
|
||||
assert_eq!(plain, "line1\nline2");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_simplify_parse_href() {
|
||||
let mut simplify = Simplify::new();
|
||||
let html = "<a href=url>text</a";
|
||||
let plain = simplify.simplify(html, true, false);
|
||||
|
||||
assert_eq!(plain, "[text](url)");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_simplify_bold_text() {
|
||||
let mut simplify = Simplify::new();
|
||||
let html = "<!DOCTYPE name [<!DOCTYPE ...>]><!-- comment -->text <b><?php echo ... ?>bold</b><![CDATA[<>]]>";
|
||||
let plain = simplify.simplify(html, true, false);
|
||||
|
||||
assert_eq!(plain, "text *bold*<>");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_simplify_html_encoded() {
|
||||
let mut simplify = Simplify::new();
|
||||
let html =
|
||||
"<>"'& äÄöÖüÜß fooÆçÇ ♦‎‏‌&noent;‍";
|
||||
|
||||
let plain = simplify.simplify(html, true, false);
|
||||
|
||||
assert_eq!(
|
||||
plain,
|
||||
"<>\"\'& äÄöÖüÜß fooÆçÇ \u{2666}\u{200e}\u{200f}\u{200c}&noent;\u{200d}"
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_simplify_utilities() {
|
||||
assert!(is_empty_line(" \t"));
|
||||
assert!(is_empty_line(""));
|
||||
assert!(is_empty_line(" \r"));
|
||||
assert!(!is_empty_line(" x"));
|
||||
assert!(is_plain_quote("> hello world"));
|
||||
assert!(is_plain_quote(">>"));
|
||||
assert!(!is_plain_quote("Life is pain"));
|
||||
assert!(!is_plain_quote(""));
|
||||
}
|
||||
}
|
||||
@@ -49,8 +49,8 @@ pub(crate) fn dc_truncate(buf: &str, approx_chars: usize, do_unwrap: bool) -> Co
|
||||
/// - harmonize together while being different enough
|
||||
/// (therefore, we cannot just use random rgb colors :)
|
||||
const COLORS: [u32; 16] = [
|
||||
0xe5_65_55, 0xf2_8c_48, 0x8e_85_ee, 0x76_c8_4d, 0x5b_b6_cc, 0x54_9c_dd, 0xd2_5c_99, 0xb3_78_00,
|
||||
0xf2_30_30, 0x39_b2_49, 0xbb_24_3b, 0x96_40_78, 0x66_87_4f, 0x30_8a_b9, 0x12_7e_d0, 0xbe_45_0c,
|
||||
0xe56555, 0xf28c48, 0x8e85ee, 0x76c84d, 0x5bb6cc, 0x549cdd, 0xd25c99, 0xb37800, 0xf23030,
|
||||
0x39b249, 0xbb243b, 0x964078, 0x66874f, 0x308ab9, 0x127ed0, 0xbe450c,
|
||||
];
|
||||
|
||||
pub(crate) fn dc_str_to_color(s: impl AsRef<str>) -> u32 {
|
||||
@@ -59,7 +59,7 @@ pub(crate) fn dc_str_to_color(s: impl AsRef<str>) -> u32 {
|
||||
let bytes = str_lower.as_bytes();
|
||||
for (i, byte) in bytes.iter().enumerate() {
|
||||
checksum += (i + 1) * *byte as usize;
|
||||
checksum %= 0x00ff_ffff;
|
||||
checksum %= 0xffffff;
|
||||
}
|
||||
let color_index = checksum % COLORS.len();
|
||||
|
||||
@@ -381,14 +381,11 @@ pub(crate) fn dc_copy_file(
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) fn dc_create_folder(
|
||||
context: &Context,
|
||||
path: impl AsRef<std::path::Path>,
|
||||
) -> Result<(), std::io::Error> {
|
||||
pub(crate) fn dc_create_folder(context: &Context, path: impl AsRef<std::path::Path>) -> bool {
|
||||
let path_abs = dc_get_abs_path(context, &path);
|
||||
if !path_abs.exists() {
|
||||
match fs::create_dir_all(path_abs) {
|
||||
Ok(_) => Ok(()),
|
||||
Ok(_) => true,
|
||||
Err(err) => {
|
||||
warn!(
|
||||
context,
|
||||
@@ -396,11 +393,11 @@ pub(crate) fn dc_create_folder(
|
||||
path.as_ref().display(),
|
||||
err
|
||||
);
|
||||
Err(err)
|
||||
false
|
||||
}
|
||||
}
|
||||
} else {
|
||||
Ok(())
|
||||
true
|
||||
}
|
||||
}
|
||||
|
||||
@@ -486,7 +483,7 @@ pub(crate) fn dc_get_next_backup_path(
|
||||
pub(crate) fn time() -> i64 {
|
||||
SystemTime::now()
|
||||
.duration_since(SystemTime::UNIX_EPOCH)
|
||||
.unwrap_or_default()
|
||||
.unwrap()
|
||||
.as_secs() as i64
|
||||
}
|
||||
|
||||
@@ -828,7 +825,7 @@ mod tests {
|
||||
|
||||
assert!(dc_delete_file(context, "$BLOBDIR/foobar"));
|
||||
assert!(dc_delete_file(context, "$BLOBDIR/dada"));
|
||||
assert!(dc_create_folder(context, "$BLOBDIR/foobar-folder").is_ok());
|
||||
assert!(dc_create_folder(context, "$BLOBDIR/foobar-folder"));
|
||||
assert!(dc_file_exist(context, "$BLOBDIR/foobar-folder",));
|
||||
assert!(!dc_delete_file(context, "$BLOBDIR/foobar-folder"));
|
||||
|
||||
|
||||
@@ -139,12 +139,13 @@ fn dehtml_starttag_cb<B: std::io::BufRead>(
|
||||
dehtml.add_text = AddText::YesPreserveLineEnds;
|
||||
}
|
||||
"a" => {
|
||||
if let Some(href) = event
|
||||
.html_attributes()
|
||||
.filter_map(|attr| attr.ok())
|
||||
.find(|attr| String::from_utf8_lossy(attr.key).trim().to_lowercase() == "href")
|
||||
{
|
||||
if let Some(href) = event.html_attributes().find(|attr| {
|
||||
attr.as_ref()
|
||||
.map(|a| String::from_utf8_lossy(a.key).trim().to_lowercase() == "href")
|
||||
.unwrap_or_default()
|
||||
}) {
|
||||
let href = href
|
||||
.unwrap()
|
||||
.unescape_and_decode_value(reader)
|
||||
.unwrap_or_default()
|
||||
.to_lowercase();
|
||||
@@ -188,41 +189,4 @@ mod tests {
|
||||
assert_eq!(dehtml(input), output);
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_dehtml_parse_br() {
|
||||
let html = "\r\r\nline1<br>\r\n\r\n\r\rline2\n\r";
|
||||
let plain = dehtml(html);
|
||||
|
||||
assert_eq!(plain, "line1\n\r\r\rline2");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_dehtml_parse_href() {
|
||||
let html = "<a href=url>text</a";
|
||||
let plain = dehtml(html);
|
||||
|
||||
assert_eq!(plain, "[text](url)");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_dehtml_bold_text() {
|
||||
let html = "<!DOCTYPE name [<!DOCTYPE ...>]><!-- comment -->text <b><?php echo ... ?>bold</b><![CDATA[<>]]>";
|
||||
let plain = dehtml(html);
|
||||
|
||||
assert_eq!(plain, "text *bold*<>");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_dehtml_html_encoded() {
|
||||
let html =
|
||||
"<>"'& äÄöÖüÜß fooÆçÇ ♦‎‏‌&noent;‍";
|
||||
|
||||
let plain = dehtml(html);
|
||||
|
||||
assert_eq!(
|
||||
plain,
|
||||
"<>\"\'& äÄöÖüÜß fooÆçÇ \u{2666}\u{200e}\u{200f}\u{200c}&noent;\u{200d}"
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
40
src/e2ee.rs
40
src/e2ee.rs
@@ -2,7 +2,7 @@
|
||||
|
||||
use std::collections::HashSet;
|
||||
|
||||
use mailparse::{MailHeaderMap, ParsedMail};
|
||||
use mailparse::MailHeaderMap;
|
||||
use num_traits::FromPrimitive;
|
||||
|
||||
use crate::aheader::*;
|
||||
@@ -14,6 +14,7 @@ use crate::keyring::*;
|
||||
use crate::peerstate::*;
|
||||
use crate::pgp;
|
||||
use crate::securejoin::handle_degrade_event;
|
||||
use crate::wrapmime;
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct EncryptHelper {
|
||||
@@ -117,7 +118,7 @@ impl EncryptHelper {
|
||||
|
||||
pub fn try_decrypt(
|
||||
context: &Context,
|
||||
mail: &ParsedMail<'_>,
|
||||
mail: &mailparse::ParsedMail<'_>,
|
||||
message_time: i64,
|
||||
) -> Result<(Option<Vec<u8>>, HashSet<String>)> {
|
||||
let from = mail
|
||||
@@ -231,36 +232,9 @@ fn load_or_generate_self_public_key(context: &Context, self_addr: impl AsRef<str
|
||||
}
|
||||
}
|
||||
|
||||
/// Returns a reference to the encrypted payload and validates the autocrypt structure.
|
||||
fn get_autocrypt_mime<'a, 'b>(mail: &'a ParsedMail<'b>) -> Result<&'a ParsedMail<'b>> {
|
||||
ensure!(
|
||||
mail.ctype.mimetype == "multipart/encrypted",
|
||||
"Not a multipart/encrypted message: {}",
|
||||
mail.ctype.mimetype
|
||||
);
|
||||
ensure!(
|
||||
mail.subparts.len() == 2,
|
||||
"Invalid Autocrypt Level 1 Mime Parts"
|
||||
);
|
||||
|
||||
ensure!(
|
||||
mail.subparts[0].ctype.mimetype == "application/pgp-encrypted",
|
||||
"Invalid Autocrypt Level 1 version part: {:?}",
|
||||
mail.subparts[0].ctype,
|
||||
);
|
||||
|
||||
ensure!(
|
||||
mail.subparts[1].ctype.mimetype == "application/octet-stream",
|
||||
"Invalid Autocrypt Level 1 encrypted part: {:?}",
|
||||
mail.subparts[1].ctype
|
||||
);
|
||||
|
||||
Ok(&mail.subparts[1])
|
||||
}
|
||||
|
||||
fn decrypt_if_autocrypt_message<'a>(
|
||||
context: &Context,
|
||||
mail: &ParsedMail<'a>,
|
||||
mail: &mailparse::ParsedMail<'a>,
|
||||
private_keyring: &Keyring,
|
||||
public_keyring_for_validate: &Keyring,
|
||||
ret_valid_signatures: &mut HashSet<String>,
|
||||
@@ -272,7 +246,7 @@ fn decrypt_if_autocrypt_message<'a>(
|
||||
//
|
||||
// Errors are returned for failures related to decryption of AC-messages.
|
||||
|
||||
let encrypted_data_part = match get_autocrypt_mime(mail) {
|
||||
let encrypted_data_part = match wrapmime::get_autocrypt_mime(mail) {
|
||||
Err(_) => {
|
||||
// not an autocrypt mime message, abort and ignore
|
||||
return Ok(None);
|
||||
@@ -293,7 +267,7 @@ fn decrypt_if_autocrypt_message<'a>(
|
||||
/// Returns Ok(None) if nothing encrypted was found.
|
||||
fn decrypt_part(
|
||||
_context: &Context,
|
||||
mail: &ParsedMail<'_>,
|
||||
mail: &mailparse::ParsedMail<'_>,
|
||||
private_keyring: &Keyring,
|
||||
public_keyring_for_validate: &Keyring,
|
||||
ret_valid_signatures: &mut HashSet<String>,
|
||||
@@ -339,7 +313,7 @@ fn has_decrypted_pgp_armor(input: &[u8]) -> bool {
|
||||
/// However, Delta Chat itself has no problem with encrypted multipart/report
|
||||
/// parts and MUAs should be encouraged to encrpyt multipart/reports as well so
|
||||
/// that we could use the normal Autocrypt processing.
|
||||
fn contains_report(mail: &ParsedMail<'_>) -> bool {
|
||||
fn contains_report(mail: &mailparse::ParsedMail<'_>) -> bool {
|
||||
mail.ctype.mimetype == "multipart/report"
|
||||
}
|
||||
|
||||
|
||||
15
src/error.rs
15
src/error.rs
@@ -6,49 +6,34 @@ use lettre_email::mime;
|
||||
pub enum Error {
|
||||
#[fail(display = "{:?}", _0)]
|
||||
Failure(failure::Error),
|
||||
|
||||
#[fail(display = "SQL error: {:?}", _0)]
|
||||
SqlError(#[cause] crate::sql::Error),
|
||||
|
||||
#[fail(display = "{:?}", _0)]
|
||||
Io(std::io::Error),
|
||||
|
||||
#[fail(display = "{:?}", _0)]
|
||||
Message(String),
|
||||
|
||||
#[fail(display = "{:?}", _0)]
|
||||
Image(image_meta::ImageError),
|
||||
|
||||
#[fail(display = "{:?}", _0)]
|
||||
Utf8(std::str::Utf8Error),
|
||||
|
||||
#[fail(display = "PGP: {:?}", _0)]
|
||||
Pgp(pgp::errors::Error),
|
||||
|
||||
#[fail(display = "Base64Decode: {:?}", _0)]
|
||||
Base64Decode(base64::DecodeError),
|
||||
|
||||
#[fail(display = "{:?}", _0)]
|
||||
FromUtf8(std::string::FromUtf8Error),
|
||||
|
||||
#[fail(display = "{}", _0)]
|
||||
BlobError(#[cause] crate::blob::BlobError),
|
||||
|
||||
#[fail(display = "Invalid Message ID.")]
|
||||
InvalidMsgId,
|
||||
|
||||
#[fail(display = "Watch folder not found {:?}", _0)]
|
||||
WatchFolderNotFound(String),
|
||||
|
||||
#[fail(display = "Invalid Email: {:?}", _0)]
|
||||
MailParseError(#[cause] mailparse::MailParseError),
|
||||
|
||||
#[fail(display = "Building invalid Email: {:?}", _0)]
|
||||
LettreError(#[cause] lettre_email::error::Error),
|
||||
|
||||
#[fail(display = "FromStr error: {:?}", _0)]
|
||||
FromStr(#[cause] mime::FromStrError),
|
||||
|
||||
#[fail(display = "Not Configured")]
|
||||
NotConfigured,
|
||||
}
|
||||
|
||||
@@ -21,38 +21,56 @@ pub enum Event {
|
||||
/// The library-user may write an informational string to the log.
|
||||
/// Passed to the callback given to dc_context_new().
|
||||
/// This event should not be reported to the end-user using a popup or something like that.
|
||||
///
|
||||
/// @return 0
|
||||
#[strum(props(id = "100"))]
|
||||
Info(String),
|
||||
|
||||
/// Emitted when SMTP connection is established and login was successful.
|
||||
///
|
||||
/// @return 0
|
||||
#[strum(props(id = "101"))]
|
||||
SmtpConnected(String),
|
||||
|
||||
/// Emitted when IMAP connection is established and login was successful.
|
||||
///
|
||||
/// @return 0
|
||||
#[strum(props(id = "102"))]
|
||||
ImapConnected(String),
|
||||
|
||||
/// Emitted when a message was successfully sent to the SMTP server.
|
||||
///
|
||||
/// @return 0
|
||||
#[strum(props(id = "103"))]
|
||||
SmtpMessageSent(String),
|
||||
|
||||
/// Emitted when an IMAP message has been marked as deleted
|
||||
///
|
||||
/// @return 0
|
||||
#[strum(props(id = "104"))]
|
||||
ImapMessageDeleted(String),
|
||||
|
||||
/// Emitted when an IMAP message has been moved
|
||||
///
|
||||
/// @return 0
|
||||
#[strum(props(id = "105"))]
|
||||
ImapMessageMoved(String),
|
||||
|
||||
/// Emitted when an IMAP folder was emptied
|
||||
///
|
||||
/// @return 0
|
||||
#[strum(props(id = "106"))]
|
||||
ImapFolderEmptied(String),
|
||||
|
||||
/// Emitted when an new file in the $BLOBDIR was created
|
||||
///
|
||||
/// @return 0
|
||||
#[strum(props(id = "150"))]
|
||||
NewBlobFile(String),
|
||||
|
||||
/// Emitted when an new file in the $BLOBDIR was created
|
||||
///
|
||||
/// @return 0
|
||||
#[strum(props(id = "151"))]
|
||||
DeletedBlobFile(String),
|
||||
|
||||
@@ -60,6 +78,8 @@ pub enum Event {
|
||||
/// Passed to the callback given to dc_context_new().
|
||||
///
|
||||
/// This event should not be reported to the end-user using a popup or something like that.
|
||||
///
|
||||
/// @return 0
|
||||
#[strum(props(id = "300"))]
|
||||
Warning(String),
|
||||
|
||||
@@ -74,6 +94,8 @@ pub enum Event {
|
||||
/// 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
|
||||
/// in a messasge box then.
|
||||
///
|
||||
/// @return
|
||||
#[strum(props(id = "400"))]
|
||||
Error(String),
|
||||
|
||||
@@ -90,6 +112,8 @@ pub enum Event {
|
||||
/// Moreover, if the UI detects that the device is offline,
|
||||
/// it is probably more useful to report this to the user
|
||||
/// instead of the string from data2.
|
||||
///
|
||||
/// @return 0
|
||||
#[strum(props(id = "401"))]
|
||||
ErrorNetwork(String),
|
||||
|
||||
@@ -98,6 +122,8 @@ pub enum Event {
|
||||
/// dc_set_chat_name(), dc_set_chat_profile_image(),
|
||||
/// dc_add_contact_to_chat(), dc_remove_contact_from_chat(),
|
||||
/// dc_send_text_msg() or another sending function.
|
||||
///
|
||||
/// @return 0
|
||||
#[strum(props(id = "410"))]
|
||||
ErrorSelfNotInGroup(String),
|
||||
|
||||
@@ -106,6 +132,8 @@ pub enum Event {
|
||||
/// - Messages sent, received or removed
|
||||
/// - Chats created, deleted or archived
|
||||
/// - A draft has been set
|
||||
///
|
||||
/// @return 0
|
||||
#[strum(props(id = "2000"))]
|
||||
MsgsChanged { chat_id: u32, msg_id: MsgId },
|
||||
|
||||
@@ -113,21 +141,29 @@ pub enum Event {
|
||||
/// when receiving this message.
|
||||
///
|
||||
/// There is no extra #DC_EVENT_MSGS_CHANGED event send together with this event.
|
||||
///
|
||||
/// @return 0
|
||||
#[strum(props(id = "2005"))]
|
||||
IncomingMsg { chat_id: u32, msg_id: MsgId },
|
||||
|
||||
/// A single message is sent successfully. State changed from DC_STATE_OUT_PENDING to
|
||||
/// DC_STATE_OUT_DELIVERED, see dc_msg_get_state().
|
||||
///
|
||||
/// @return 0
|
||||
#[strum(props(id = "2010"))]
|
||||
MsgDelivered { chat_id: u32, msg_id: MsgId },
|
||||
|
||||
/// A single message could not be sent. State changed from DC_STATE_OUT_PENDING or DC_STATE_OUT_DELIVERED to
|
||||
/// DC_STATE_OUT_FAILED, see dc_msg_get_state().
|
||||
///
|
||||
/// @return 0
|
||||
#[strum(props(id = "2012"))]
|
||||
MsgFailed { chat_id: u32, msg_id: MsgId },
|
||||
|
||||
/// A single message is read by the receiver. State changed from DC_STATE_OUT_DELIVERED to
|
||||
/// DC_STATE_OUT_MDN_RCVD, see dc_msg_get_state().
|
||||
///
|
||||
/// @return 0
|
||||
#[strum(props(id = "2015"))]
|
||||
MsgRead { chat_id: u32, msg_id: MsgId },
|
||||
|
||||
@@ -135,12 +171,15 @@ pub enum Event {
|
||||
/// Or the verify state of a chat has changed.
|
||||
/// See dc_set_chat_name(), dc_set_chat_profile_image(), dc_add_contact_to_chat()
|
||||
/// and dc_remove_contact_from_chat().
|
||||
///
|
||||
/// @return 0
|
||||
#[strum(props(id = "2020"))]
|
||||
ChatModified(u32),
|
||||
|
||||
/// Contact(s) created, renamed, blocked or deleted.
|
||||
///
|
||||
/// @param data1 (int) If set, this is the contact_id of an added contact that should be selected.
|
||||
/// @return 0
|
||||
#[strum(props(id = "2030"))]
|
||||
ContactsChanged(Option<u32>),
|
||||
|
||||
@@ -149,12 +188,14 @@ pub enum Event {
|
||||
/// @param data1 (u32) contact_id of the contact for which the location has changed.
|
||||
/// If the locations of several contacts have been changed,
|
||||
/// eg. after calling dc_delete_all_locations(), this parameter is set to `None`.
|
||||
/// @return 0
|
||||
#[strum(props(id = "2035"))]
|
||||
LocationChanged(Option<u32>),
|
||||
|
||||
/// Inform about the configuration progress started by configure().
|
||||
///
|
||||
/// @param data1 (usize) 0=error, 1-999=progress in permille, 1000=success and done
|
||||
/// @return 0
|
||||
#[strum(props(id = "2041"))]
|
||||
ConfigureProgress(usize),
|
||||
|
||||
@@ -162,6 +203,7 @@ pub enum Event {
|
||||
///
|
||||
/// @param data1 (usize) 0=error, 1-999=progress in permille, 1000=success and done
|
||||
/// @param data2 0
|
||||
/// @return 0
|
||||
#[strum(props(id = "2051"))]
|
||||
ImexProgress(usize),
|
||||
|
||||
@@ -172,6 +214,7 @@ pub enum Event {
|
||||
/// services.
|
||||
///
|
||||
/// @param data2 0
|
||||
/// @return 0
|
||||
#[strum(props(id = "2052"))]
|
||||
ImexFileWritten(PathBuf),
|
||||
|
||||
@@ -187,6 +230,7 @@ pub enum Event {
|
||||
/// 600=vg-/vc-request-with-auth received, vg-member-added/vc-contact-confirm sent, typically shown as "bob@addr verified".
|
||||
/// 800=vg-member-added-received received, shown as "bob@addr securely joined GROUP", only sent for the verified-group-protocol.
|
||||
/// 1000=Protocol finished for this contact.
|
||||
/// @return 0
|
||||
#[strum(props(id = "2060"))]
|
||||
SecurejoinInviterProgress { contact_id: u32, progress: usize },
|
||||
|
||||
@@ -198,12 +242,14 @@ pub enum Event {
|
||||
/// @param data2 (int) Progress as:
|
||||
/// 400=vg-/vc-request-with-auth sent, typically shown as "alice@addr verified, introducing myself."
|
||||
/// (Bob has verified alice and waits until Alice does the same for him)
|
||||
/// @return 0
|
||||
#[strum(props(id = "2061"))]
|
||||
SecurejoinJoinerProgress { contact_id: u32, progress: usize },
|
||||
|
||||
/// This event is sent out to the inviter when a joiner successfully joined a group.
|
||||
/// @param data1 (int) chat_id
|
||||
/// @param data2 (int) contact_id
|
||||
/// @return 0
|
||||
#[strum(props(id = "2062"))]
|
||||
SecurejoinMemberAdded { chat_id: u32, contact_id: u32 },
|
||||
}
|
||||
|
||||
@@ -241,7 +241,7 @@ impl Imap {
|
||||
"IMAP-fake-IDLE done after {:.4}s",
|
||||
SystemTime::now()
|
||||
.duration_since(fake_idle_start_time)
|
||||
.unwrap_or_default()
|
||||
.unwrap()
|
||||
.as_millis() as f64
|
||||
/ 1000.,
|
||||
);
|
||||
|
||||
@@ -23,6 +23,7 @@ use crate::message::{self, update_server_uid};
|
||||
use crate::oauth2::dc_get_oauth2_access_token;
|
||||
use crate::param::Params;
|
||||
use crate::stock::StockMessage;
|
||||
use crate::wrapmime;
|
||||
|
||||
mod idle;
|
||||
pub mod select_folder;
|
||||
@@ -63,9 +64,6 @@ pub enum Error {
|
||||
#[fail(display = "IMAP select folder error")]
|
||||
SelectFolderError(#[cause] select_folder::Error),
|
||||
|
||||
#[fail(display = "No mailbox selected, folder: {:?}", _0)]
|
||||
NoMailbox(String),
|
||||
|
||||
#[fail(display = "IMAP other error: {:?}", _0)]
|
||||
Other(String),
|
||||
}
|
||||
@@ -481,10 +479,7 @@ impl Imap {
|
||||
let (uid_validity, last_seen_uid) = self.get_config_last_seen_uid(context, &folder);
|
||||
|
||||
let config = self.config.read().await;
|
||||
let mailbox = config
|
||||
.selected_mailbox
|
||||
.as_ref()
|
||||
.ok_or_else(|| Error::NoMailbox(folder.to_string()))?;
|
||||
let mailbox = config.selected_mailbox.as_ref().expect("just selected");
|
||||
|
||||
let new_uid_validity = match mailbox.uid_validity {
|
||||
Some(v) => v,
|
||||
@@ -1228,18 +1223,6 @@ fn precheck_imf(context: &Context, rfc724_mid: &str, server_folder: &str, server
|
||||
}
|
||||
}
|
||||
|
||||
fn parse_message_id(message_id: &[u8]) -> crate::error::Result<String> {
|
||||
let value = std::str::from_utf8(message_id)?;
|
||||
let addrs = mailparse::addrparse(value)
|
||||
.map_err(|err| format_err!("failed to parse message id {:?}", err))?;
|
||||
|
||||
if let Some(info) = addrs.extract_single_info() {
|
||||
return Ok(info.addr);
|
||||
}
|
||||
|
||||
bail!("could not parse message_id: {}", value);
|
||||
}
|
||||
|
||||
fn prefetch_get_message_id(prefetch_msg: &Fetch) -> Result<String> {
|
||||
if prefetch_msg.envelope().is_none() {
|
||||
return Err(Error::Other(
|
||||
@@ -1252,22 +1235,5 @@ fn prefetch_get_message_id(prefetch_msg: &Fetch) -> Result<String> {
|
||||
return Err(Error::Other("prefetch: No message ID found".to_string()));
|
||||
}
|
||||
|
||||
parse_message_id(&message_id.unwrap()).map_err(Into::into)
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
|
||||
#[test]
|
||||
fn test_parse_message_id() {
|
||||
assert_eq!(
|
||||
parse_message_id(b"Mr.PRUe8HJBoaO.3whNvLCMFU0@testrun.org").unwrap(),
|
||||
"Mr.PRUe8HJBoaO.3whNvLCMFU0@testrun.org"
|
||||
);
|
||||
assert_eq!(
|
||||
parse_message_id(b"<Mr.PRUe8HJBoaO.3whNvLCMFU0@testrun.org>").unwrap(),
|
||||
"Mr.PRUe8HJBoaO.3whNvLCMFU0@testrun.org"
|
||||
);
|
||||
}
|
||||
wrapmime::parse_message_id(&message_id.unwrap()).map_err(Into::into)
|
||||
}
|
||||
|
||||
@@ -46,7 +46,7 @@ impl Client {
|
||||
let _greeting = client
|
||||
.read_response()
|
||||
.await
|
||||
.ok_or_else(|| ImapError::Bad("failed to read greeting".to_string()))?;
|
||||
.expect("failed to read greeting");
|
||||
|
||||
Ok(Client::Secure(client))
|
||||
}
|
||||
@@ -61,7 +61,7 @@ impl Client {
|
||||
let _greeting = client
|
||||
.read_response()
|
||||
.await
|
||||
.ok_or_else(|| ImapError::Bad("failed to read greeting".to_string()))?;
|
||||
.expect("failed to read greeting");
|
||||
|
||||
Ok(Client::Insecure(client))
|
||||
}
|
||||
|
||||
@@ -8,7 +8,6 @@ use rand::{thread_rng, Rng};
|
||||
|
||||
use crate::blob::BlobObject;
|
||||
use crate::chat;
|
||||
use crate::chat::delete_and_reset_all_device_msgs;
|
||||
use crate::config::Config;
|
||||
use crate::configure::*;
|
||||
use crate::constants::*;
|
||||
@@ -34,19 +33,16 @@ pub enum ImexMode {
|
||||
/// and `private-key-default.asc`, if there are more keys, they are written to files as
|
||||
/// `public-key-<id>.asc` and `private-key-<id>.asc`
|
||||
ExportSelfKeys = 1,
|
||||
|
||||
/// Import private keys found in the directory given as `param1`.
|
||||
/// The last imported key is made the default keys unless its name contains the string `legacy`.
|
||||
/// Public keys are not imported.
|
||||
ImportSelfKeys = 2,
|
||||
|
||||
/// Export a backup to the directory given as `param1`.
|
||||
/// The backup contains all contacts, chats, images and other data and device independent settings.
|
||||
/// The backup does not contain device dependent settings as ringtones or LED notification settings.
|
||||
/// The name of the backup is typically `delta-chat.<day>.bak`, if more than one backup is create on a day,
|
||||
/// the format is `delta-chat.<day>-<number>.bak`
|
||||
ExportBackup = 11,
|
||||
|
||||
/// `param1` is the file (not: directory) to import. The file is normally
|
||||
/// created by DC_IMEX_EXPORT_BACKUP and detected by dc_imex_has_backup(). Importing a backup
|
||||
/// is only possible as long as the context is not configured or used in another way.
|
||||
@@ -384,7 +380,7 @@ pub fn JobImexImap(context: &Context, job: &Job) -> Result<()> {
|
||||
context.free_ongoing();
|
||||
bail!("Cannot create private key or private key not available.");
|
||||
} else {
|
||||
dc_create_folder(context, ¶m)?;
|
||||
dc_create_folder(context, ¶m);
|
||||
}
|
||||
}
|
||||
let path = Path::new(param);
|
||||
@@ -442,8 +438,6 @@ fn import_backup(context: &Context, backup_to_import: impl AsRef<Path>) -> Resul
|
||||
"could not re-open db"
|
||||
);
|
||||
|
||||
delete_and_reset_all_device_msgs(&context)?;
|
||||
|
||||
let total_files_cnt = context
|
||||
.sql
|
||||
.query_get_value::<_, isize>(context, "SELECT COUNT(*) FROM backup_blobs;", params![])
|
||||
|
||||
99
src/job.rs
99
src/job.rs
@@ -3,7 +3,7 @@
|
||||
//! This module implements a job queue maintained in the SQLite database
|
||||
//! and job types.
|
||||
|
||||
use std::{fmt, time};
|
||||
use std::time::Duration;
|
||||
|
||||
use deltachat_derive::{FromSql, ToSql};
|
||||
use rand::{thread_rng, Rng};
|
||||
@@ -123,12 +123,6 @@ pub struct Job {
|
||||
pub pending_error: Option<String>,
|
||||
}
|
||||
|
||||
impl fmt::Display for Job {
|
||||
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
|
||||
write!(f, "#{}, action {}", self.job_id, self.action)
|
||||
}
|
||||
}
|
||||
|
||||
impl Job {
|
||||
/// Deletes the job from the database.
|
||||
fn delete(&self, context: &Context) -> bool {
|
||||
@@ -209,7 +203,7 @@ impl Job {
|
||||
match task::block_on(smtp.send(context, recipients_list, body, self.job_id)) {
|
||||
Err(crate::smtp::send::Error::SendError(err)) => {
|
||||
// Remote error, retry later.
|
||||
warn!(context, "SMTP failed to send: {}", err);
|
||||
info!(context, "SMTP failed to send: {}", err);
|
||||
smtp.disconnect();
|
||||
self.try_again_later(TryAgain::AtOnce, Some(err.to_string()));
|
||||
}
|
||||
@@ -478,19 +472,19 @@ pub fn perform_sentbox_idle(context: &Context) {
|
||||
.idle(context, use_network);
|
||||
}
|
||||
|
||||
pub fn interrupt_inbox_idle(context: &Context) {
|
||||
info!(context, "interrupt_inbox_idle called");
|
||||
// we do not block on trying to obtain the thread lock
|
||||
// because we don't know in which state the thread is.
|
||||
// If it's currently fetching then we can not get the lock
|
||||
// but we flag it for checking jobs so that idle will be skipped.
|
||||
match context.inbox_thread.try_read() {
|
||||
Ok(inbox_thread) => {
|
||||
inbox_thread.interrupt_idle(context);
|
||||
}
|
||||
Err(err) => {
|
||||
*context.perform_inbox_jobs_needed.write().unwrap() = true;
|
||||
warn!(context, "could not interrupt idle: {}", err);
|
||||
pub fn interrupt_inbox_idle(context: &Context, block: bool) {
|
||||
info!(context, "interrupt_inbox_idle called blocking={}", block);
|
||||
if block {
|
||||
context.inbox_thread.read().unwrap().interrupt_idle(context);
|
||||
} else {
|
||||
match context.inbox_thread.try_read() {
|
||||
Ok(inbox_thread) => {
|
||||
inbox_thread.interrupt_idle(context);
|
||||
}
|
||||
Err(err) => {
|
||||
*context.perform_inbox_jobs_needed.write().unwrap() = true;
|
||||
warn!(context, "could not interrupt idle: {}", err);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -569,7 +563,7 @@ pub fn perform_smtp_idle(context: &Context) {
|
||||
info!(context, "SMTP-idle ended.",);
|
||||
}
|
||||
|
||||
fn get_next_wakeup_time(context: &Context, thread: Thread) -> time::Duration {
|
||||
fn get_next_wakeup_time(context: &Context, thread: Thread) -> Duration {
|
||||
let t: i64 = context
|
||||
.sql
|
||||
.query_get_value(
|
||||
@@ -579,13 +573,13 @@ fn get_next_wakeup_time(context: &Context, thread: Thread) -> time::Duration {
|
||||
)
|
||||
.unwrap_or_default();
|
||||
|
||||
let mut wakeup_time = time::Duration::new(10 * 60, 0);
|
||||
let mut wakeup_time = Duration::new(10 * 60, 0);
|
||||
let now = time();
|
||||
if t > 0 {
|
||||
if t > now {
|
||||
wakeup_time = time::Duration::new((t - now) as u64, 0);
|
||||
wakeup_time = Duration::new((t - now) as u64, 0);
|
||||
} else {
|
||||
wakeup_time = time::Duration::new(0, 0);
|
||||
wakeup_time = Duration::new(0, 0);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -602,7 +596,7 @@ pub fn maybe_network(context: &Context) {
|
||||
}
|
||||
|
||||
interrupt_smtp_idle(context);
|
||||
interrupt_inbox_idle(context);
|
||||
interrupt_inbox_idle(context, true);
|
||||
interrupt_mvbox_idle(context);
|
||||
interrupt_sentbox_idle(context);
|
||||
}
|
||||
@@ -637,7 +631,7 @@ pub fn job_send_msg(context: &Context, msg_id: MsgId) -> Result<(), Error> {
|
||||
msg.try_calc_and_set_dimensions(context).ok();
|
||||
|
||||
/* create message */
|
||||
let needs_encryption = msg.param.get_bool(Param::GuaranteeE2ee).unwrap_or_default();
|
||||
let needs_encryption = msg.param.get_int(Param::GuaranteeE2ee).unwrap_or_default();
|
||||
|
||||
let attach_selfavatar = match chat::shall_attach_selfavatar(context, msg.chat_id) {
|
||||
Ok(attach_selfavatar) => attach_selfavatar,
|
||||
@@ -653,7 +647,7 @@ pub fn job_send_msg(context: &Context, msg_id: MsgId) -> Result<(), Error> {
|
||||
err
|
||||
})?;
|
||||
|
||||
if needs_encryption && !rendered_msg.is_encrypted {
|
||||
if 0 != needs_encryption && !rendered_msg.is_encrypted {
|
||||
/* unrecoverable */
|
||||
message::set_msg_failed(
|
||||
context,
|
||||
@@ -706,7 +700,7 @@ pub fn job_send_msg(context: &Context, msg_id: MsgId) -> Result<(), Error> {
|
||||
}
|
||||
}
|
||||
|
||||
if rendered_msg.is_encrypted && !needs_encryption {
|
||||
if rendered_msg.is_encrypted && needs_encryption == 0 {
|
||||
msg.param.set_int(Param::GuaranteeE2ee, 1);
|
||||
msg.save_param_to_disk(context);
|
||||
}
|
||||
@@ -739,7 +733,17 @@ fn job_perform(context: &Context, thread: Thread, probe_network: bool) {
|
||||
let jobs: Vec<Job> = load_jobs(context, thread, probe_network);
|
||||
|
||||
for mut job in jobs {
|
||||
info!(context, "{}-job {} started...", thread, job);
|
||||
info!(
|
||||
context,
|
||||
"{}-job #{}, action {} started...",
|
||||
if thread == Thread::Imap {
|
||||
"INBOX"
|
||||
} else {
|
||||
"SMTP"
|
||||
},
|
||||
job.job_id,
|
||||
job.action,
|
||||
);
|
||||
|
||||
// some configuration jobs are "exclusive":
|
||||
// - they are always executed in the imap-thread and the smtp-thread is suspended during execution
|
||||
@@ -762,12 +766,7 @@ fn job_perform(context: &Context, thread: Thread, probe_network: bool) {
|
||||
suspend_smtp_thread(context, true);
|
||||
}
|
||||
|
||||
for tries in 0..2 {
|
||||
info!(
|
||||
context,
|
||||
"{} performs immediate try {} of job {}", thread, tries, job
|
||||
);
|
||||
|
||||
for _tries in 0..2 {
|
||||
// this can be modified by a job using dc_job_try_again_later()
|
||||
job.try_again = TryAgain::Dont;
|
||||
|
||||
@@ -797,11 +796,6 @@ fn job_perform(context: &Context, thread: Thread, probe_network: bool) {
|
||||
Action::SendMdnOld => {}
|
||||
Action::SendMsgToSmtpOld => {}
|
||||
}
|
||||
|
||||
info!(
|
||||
context,
|
||||
"{} finished immediate try {} of job {}", thread, tries, job
|
||||
);
|
||||
if job.try_again != TryAgain::AtOnce {
|
||||
break;
|
||||
}
|
||||
@@ -823,12 +817,7 @@ fn job_perform(context: &Context, thread: Thread, probe_network: bool) {
|
||||
break;
|
||||
} else if job.try_again == TryAgain::AtOnce || job.try_again == TryAgain::StandardDelay {
|
||||
let tries = job.tries + 1;
|
||||
|
||||
if tries < JOB_RETRIES {
|
||||
info!(
|
||||
context,
|
||||
"{} thread increases job {} tries to {}", thread, job, tries
|
||||
);
|
||||
job.tries = tries;
|
||||
let time_offset = get_backoff_time_offset(tries);
|
||||
job.desired_timestamp = time() + time_offset;
|
||||
@@ -836,7 +825,11 @@ fn job_perform(context: &Context, thread: Thread, probe_network: bool) {
|
||||
info!(
|
||||
context,
|
||||
"{}-job #{} not succeeded on try #{}, retry in {} seconds.",
|
||||
thread,
|
||||
if thread == Thread::Imap {
|
||||
"INBOX"
|
||||
} else {
|
||||
"SMTP"
|
||||
},
|
||||
job.job_id as u32,
|
||||
tries,
|
||||
time_offset
|
||||
@@ -851,10 +844,6 @@ fn job_perform(context: &Context, thread: Thread, probe_network: bool) {
|
||||
.perform_jobs_needed = PerformJobsNeeded::AvoidDos;
|
||||
}
|
||||
} else {
|
||||
info!(
|
||||
context,
|
||||
"{} thread removes job {} as it exhausted {} retries", thread, job, JOB_RETRIES
|
||||
);
|
||||
if job.action == Action::SendMsgToSmtp {
|
||||
message::set_msg_failed(
|
||||
context,
|
||||
@@ -873,10 +862,6 @@ fn job_perform(context: &Context, thread: Thread, probe_network: bool) {
|
||||
// to give other jobs a chance being tried at least once.
|
||||
break;
|
||||
} else {
|
||||
info!(
|
||||
context,
|
||||
"{} removes job {} as it cannot be retried", thread, job
|
||||
);
|
||||
job.delete(context);
|
||||
}
|
||||
}
|
||||
@@ -900,7 +885,7 @@ fn suspend_smtp_thread(context: &Context, suspend: bool) {
|
||||
if !context.smtp_state.0.lock().unwrap().doing_jobs {
|
||||
return;
|
||||
}
|
||||
std::thread::sleep(time::Duration::from_micros(300 * 1000));
|
||||
std::thread::sleep(std::time::Duration::from_micros(300 * 1000));
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -979,7 +964,7 @@ pub fn job_add(
|
||||
).ok();
|
||||
|
||||
match thread {
|
||||
Thread::Imap => interrupt_inbox_idle(context),
|
||||
Thread::Imap => interrupt_inbox_idle(context, false),
|
||||
Thread::Smtp => interrupt_smtp_idle(context),
|
||||
Thread::Unknown => {}
|
||||
}
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
#![deny(clippy::correctness, missing_debug_implementations, clippy::all)]
|
||||
// for now we hide warnings to not clutter/hide errors during "cargo clippy"
|
||||
#![allow(clippy::cognitive_complexity, clippy::too_many_arguments)]
|
||||
#![allow(clippy::match_bool)]
|
||||
#![allow(clippy::unreadable_literal, clippy::match_bool)]
|
||||
#![feature(ptr_wrapping_offset_from)]
|
||||
#![feature(drain_filter)]
|
||||
|
||||
@@ -20,7 +20,7 @@ extern crate strum_macros;
|
||||
extern crate debug_stub_derive;
|
||||
|
||||
#[macro_use]
|
||||
pub mod log;
|
||||
mod log;
|
||||
#[macro_use]
|
||||
pub mod error;
|
||||
|
||||
@@ -58,15 +58,16 @@ pub mod peerstate;
|
||||
pub mod pgp;
|
||||
pub mod qr;
|
||||
pub mod securejoin;
|
||||
mod simplify;
|
||||
mod smtp;
|
||||
pub mod sql;
|
||||
pub mod stock;
|
||||
mod token;
|
||||
#[macro_use]
|
||||
mod wrapmime;
|
||||
mod dehtml;
|
||||
|
||||
pub mod dc_receive_imf;
|
||||
mod dc_simplify;
|
||||
pub mod dc_tools;
|
||||
|
||||
/// if set imap/incoming and smtp/outgoing MIME messages will be printed
|
||||
|
||||
16
src/log.rs
16
src/log.rs
@@ -7,13 +7,7 @@ macro_rules! info {
|
||||
};
|
||||
($ctx:expr, $msg:expr, $($args:expr),* $(,)?) => {{
|
||||
let formatted = format!($msg, $($args),*);
|
||||
let thread = ::std::thread::current();
|
||||
let full = format!("{thid:?} {file}:{line}: {msg}",
|
||||
thid = thread.id(),
|
||||
file = file!(),
|
||||
line = line!(),
|
||||
msg = &formatted);
|
||||
emit_event!($ctx, $crate::Event::Info(full));
|
||||
emit_event!($ctx, $crate::Event::Info(formatted));
|
||||
}};
|
||||
}
|
||||
|
||||
@@ -24,13 +18,7 @@ macro_rules! warn {
|
||||
};
|
||||
($ctx:expr, $msg:expr, $($args:expr),* $(,)?) => {{
|
||||
let formatted = format!($msg, $($args),*);
|
||||
let thread = ::std::thread::current();
|
||||
let full = format!("{thid:?} {file}:{line}: {msg}",
|
||||
thid = thread.id(),
|
||||
file = file!(),
|
||||
line = line!(),
|
||||
msg = &formatted);
|
||||
emit_event!($ctx, $crate::Event::Warning(full));
|
||||
emit_event!($ctx, $crate::Event::Warning(formatted));
|
||||
}};
|
||||
}
|
||||
|
||||
|
||||
@@ -73,28 +73,20 @@ pub enum LotState {
|
||||
// Qr States
|
||||
/// id=contact
|
||||
QrAskVerifyContact = 200,
|
||||
|
||||
/// text1=groupname
|
||||
QrAskVerifyGroup = 202,
|
||||
|
||||
/// id=contact
|
||||
QrFprOk = 210,
|
||||
|
||||
/// id=contact
|
||||
QrFprMissmatch = 220,
|
||||
|
||||
/// test1=formatted fingerprint
|
||||
QrFprWithoutAddr = 230,
|
||||
|
||||
/// id=contact
|
||||
QrAddr = 320,
|
||||
|
||||
/// text1=text
|
||||
QrText = 330,
|
||||
|
||||
/// text1=URL
|
||||
QrUrl = 332,
|
||||
|
||||
/// text1=error string
|
||||
QrError = 400,
|
||||
|
||||
|
||||
@@ -720,7 +720,7 @@ pub fn get_msg_info(context: &Context, msg_id: MsgId) -> String {
|
||||
return ret;
|
||||
}
|
||||
let rawtxt = rawtxt.unwrap_or_default();
|
||||
let rawtxt = dc_truncate(rawtxt.trim(), 100_000, false);
|
||||
let rawtxt = dc_truncate(rawtxt.trim(), 100000, false);
|
||||
|
||||
let fts = dc_timestamp_to_str(msg.get_timestamp());
|
||||
ret += &format!("Sent: {}", fts);
|
||||
|
||||
@@ -1002,20 +1002,6 @@ impl<'a, 'b> MimeFactory<'a, 'b> {
|
||||
}
|
||||
}
|
||||
|
||||
/// Returns base64-encoded buffer `buf` split into 78-bytes long
|
||||
/// chunks separated by CRLF.
|
||||
///
|
||||
/// This line length limit is an
|
||||
/// [RFC5322 requirement](https://tools.ietf.org/html/rfc5322#section-2.1.1).
|
||||
fn wrapped_base64_encode(buf: &[u8]) -> String {
|
||||
let base64 = base64::encode(&buf);
|
||||
let mut chars = base64.chars();
|
||||
std::iter::repeat_with(|| chars.by_ref().take(78).collect::<String>())
|
||||
.take_while(|s| !s.is_empty())
|
||||
.collect::<Vec<_>>()
|
||||
.join("\r\n")
|
||||
}
|
||||
|
||||
fn build_body_file(
|
||||
context: &Context,
|
||||
msg: &Message,
|
||||
@@ -1076,7 +1062,7 @@ fn build_body_file(
|
||||
};
|
||||
|
||||
let body = std::fs::read(blob.to_abs_path())?;
|
||||
let encoded_body = wrapped_base64_encode(&body);
|
||||
let encoded_body = base64::encode(&body);
|
||||
|
||||
let mail = PartBuilder::new()
|
||||
.content_type(&mimetype)
|
||||
@@ -1098,7 +1084,7 @@ fn build_selfavatar_file(context: &Context, path: String) -> Result<(PartBuilder
|
||||
None => mime::APPLICATION_OCTET_STREAM,
|
||||
};
|
||||
let body = std::fs::read(blob.to_abs_path())?;
|
||||
let encoded_body = wrapped_base64_encode(&body);
|
||||
let encoded_body = base64::encode(&body);
|
||||
|
||||
let part = PartBuilder::new()
|
||||
.content_type(&mimetype)
|
||||
@@ -1217,13 +1203,4 @@ mod tests {
|
||||
"<123@q> <456@d>".to_string()
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_wrapped_base64_encode() {
|
||||
let input = b"AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA";
|
||||
let output =
|
||||
"QUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQU\r\n\
|
||||
FBQUFBQUFBQQ==";
|
||||
assert_eq!(wrapped_base64_encode(input), output);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2,7 +2,7 @@ use std::collections::{HashMap, HashSet};
|
||||
|
||||
use deltachat_derive::{FromSql, ToSql};
|
||||
use lettre_email::mime::{self, Mime};
|
||||
use mailparse::{DispositionType, MailAddr, MailHeaderMap};
|
||||
use mailparse::{DispositionType, MailHeaderMap};
|
||||
|
||||
use crate::aheader::Aheader;
|
||||
use crate::blob::BlobObject;
|
||||
@@ -10,19 +10,18 @@ use crate::config::Config;
|
||||
use crate::constants::Viewtype;
|
||||
use crate::contact::*;
|
||||
use crate::context::Context;
|
||||
use crate::dc_simplify::*;
|
||||
use crate::dc_tools::*;
|
||||
use crate::dehtml::dehtml;
|
||||
use crate::e2ee;
|
||||
use crate::error::Result;
|
||||
use crate::events::Event;
|
||||
use crate::headerdef::HeaderDef;
|
||||
use crate::job::{job_add, Action};
|
||||
use crate::location;
|
||||
use crate::message;
|
||||
use crate::message::MsgId;
|
||||
use crate::param::*;
|
||||
use crate::peerstate::Peerstate;
|
||||
use crate::securejoin::handle_degrade_event;
|
||||
use crate::simplify::*;
|
||||
use crate::stock::StockMessage;
|
||||
use crate::{bail, ensure};
|
||||
|
||||
@@ -41,6 +40,7 @@ pub struct MimeParser<'a> {
|
||||
pub user_avatar: AvatarAction,
|
||||
pub group_avatar: AvatarAction,
|
||||
reports: Vec<Report>,
|
||||
mdns_enabled: bool,
|
||||
parsed_protected_headers: bool,
|
||||
}
|
||||
|
||||
@@ -86,6 +86,7 @@ const MIME_AC_SETUP_FILE: &str = "application/autocrypt-setup";
|
||||
impl<'a> MimeParser<'a> {
|
||||
pub fn from_bytes(context: &'a Context, body: &[u8]) -> Result<Self> {
|
||||
let mail = mailparse::parse_mail(body)?;
|
||||
let mdns_enabled = context.get_config_bool(Config::MdnsEnabled);
|
||||
|
||||
let mut parser = MimeParser {
|
||||
parts: Vec::new(),
|
||||
@@ -103,6 +104,7 @@ impl<'a> MimeParser<'a> {
|
||||
message_kml: None,
|
||||
user_avatar: AvatarAction::None,
|
||||
group_avatar: AvatarAction::None,
|
||||
mdns_enabled,
|
||||
parsed_protected_headers: false,
|
||||
};
|
||||
|
||||
@@ -209,6 +211,7 @@ impl<'a> MimeParser<'a> {
|
||||
|| filepart.typ == Viewtype::Voice
|
||||
|| filepart.typ == Viewtype::Video
|
||||
|| filepart.typ == Viewtype::File)
|
||||
&& !filepart.is_meta
|
||||
};
|
||||
|
||||
if need_drop {
|
||||
@@ -225,7 +228,7 @@ impl<'a> MimeParser<'a> {
|
||||
}
|
||||
}
|
||||
if let Some(ref subject) = self.get_subject() {
|
||||
let mut prepend_subject = true;
|
||||
let mut prepend_subject = 1i32;
|
||||
if !self.decrypting_failed {
|
||||
let colon = subject.find(':');
|
||||
if colon == Some(2)
|
||||
@@ -233,10 +236,10 @@ impl<'a> MimeParser<'a> {
|
||||
|| self.has_chat_version()
|
||||
|| subject.contains("Chat:")
|
||||
{
|
||||
prepend_subject = false
|
||||
prepend_subject = 0i32
|
||||
}
|
||||
}
|
||||
if prepend_subject {
|
||||
if 0 != prepend_subject {
|
||||
let subj = if let Some(n) = subject.find('[') {
|
||||
&subject[0..n]
|
||||
} else {
|
||||
@@ -288,27 +291,30 @@ impl<'a> MimeParser<'a> {
|
||||
}
|
||||
}
|
||||
}
|
||||
if !self.decrypting_failed {
|
||||
if let Some(dn_field) = self.get(HeaderDef::ChatDispositionNotificationTo) {
|
||||
if self.get_last_nonmeta().is_some() {
|
||||
let addrs = mailparse::addrparse(&dn_field).unwrap();
|
||||
|
||||
// See if an MDN is requested from the other side
|
||||
if !self.decrypting_failed && !self.parts.is_empty() {
|
||||
if let Some(ref dn_to_addr) =
|
||||
self.parse_first_addr(HeaderDef::ChatDispositionNotificationTo)
|
||||
{
|
||||
if let Some(ref from_addr) = self.parse_first_addr(HeaderDef::From_) {
|
||||
if compare_addrs(from_addr, dn_to_addr) {
|
||||
if let Some(part) = self.parts.last_mut() {
|
||||
part.param.set_int(Param::WantsMdn, 1);
|
||||
if let Some(dn_to_addr) = addrs.first() {
|
||||
if let Some(from_field) = self.get(HeaderDef::From_) {
|
||||
let from_addrs = mailparse::addrparse(&from_field).unwrap();
|
||||
|
||||
if let Some(from_addr) = from_addrs.first() {
|
||||
if compare_addrs(from_addr, dn_to_addr) {
|
||||
if let Some(part_4) = self.get_last_nonmeta_mut() {
|
||||
part_4.param.set_int(Param::WantsMdn, 1);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// If there were no parts, especially a non-DC mail user may
|
||||
// just have send a message in the subject with an empty body.
|
||||
// Besides, we want to show something in case our incoming-processing
|
||||
// failed to properly handle an incoming message.
|
||||
if self.parts.is_empty() && self.reports.is_empty() {
|
||||
// Cleanup - and try to create at least an empty part if there are no parts yet
|
||||
if self.get_last_nonmeta().is_none() && self.reports.is_empty() {
|
||||
let mut part = Part::default();
|
||||
part.typ = Viewtype::Text;
|
||||
|
||||
@@ -347,6 +353,14 @@ impl<'a> MimeParser<'a> {
|
||||
AvatarAction::None
|
||||
}
|
||||
|
||||
pub fn get_last_nonmeta(&self) -> Option<&Part> {
|
||||
self.parts.iter().rev().find(|part| !part.is_meta)
|
||||
}
|
||||
|
||||
pub fn get_last_nonmeta_mut(&mut self) -> Option<&mut Part> {
|
||||
self.parts.iter_mut().rev().find(|part| !part.is_meta)
|
||||
}
|
||||
|
||||
pub fn was_encrypted(&self) -> bool {
|
||||
!self.signatures.is_empty()
|
||||
}
|
||||
@@ -374,20 +388,6 @@ impl<'a> MimeParser<'a> {
|
||||
self.header.get(&headerdef.get_headername())
|
||||
}
|
||||
|
||||
fn parse_first_addr(&self, headerdef: HeaderDef) -> Option<MailAddr> {
|
||||
if let Some(value) = self.get(headerdef.clone()) {
|
||||
match mailparse::addrparse(&value) {
|
||||
Ok(ref addrs) => {
|
||||
return addrs.first().cloned();
|
||||
}
|
||||
Err(err) => {
|
||||
warn!(self.context, "header {} parse error: {:?}", headerdef, err);
|
||||
}
|
||||
}
|
||||
}
|
||||
None
|
||||
}
|
||||
|
||||
fn parse_mime_recursive(&mut self, mail: &mailparse::ParsedMail<'_>) -> Result<bool> {
|
||||
if mail.ctype.params.get("protected-headers").is_some() {
|
||||
if mail.ctype.mimetype == "text/rfc822-headers" {
|
||||
@@ -577,16 +577,12 @@ impl<'a> MimeParser<'a> {
|
||||
}
|
||||
};
|
||||
|
||||
let (simplified_txt, is_forwarded) = if decoded_data.is_empty() {
|
||||
("".into(), false)
|
||||
let mut simplifier = Simplify::new();
|
||||
let simplified_txt = if decoded_data.is_empty() {
|
||||
"".into()
|
||||
} else {
|
||||
let is_html = mime_type == mime::TEXT_HTML;
|
||||
let out = if is_html {
|
||||
dehtml(&decoded_data)
|
||||
} else {
|
||||
decoded_data.clone()
|
||||
};
|
||||
simplify(out, self.has_chat_version())
|
||||
simplifier.simplify(&decoded_data, is_html, self.has_chat_version())
|
||||
};
|
||||
|
||||
if !simplified_txt.is_empty() {
|
||||
@@ -598,7 +594,7 @@ impl<'a> MimeParser<'a> {
|
||||
self.do_add_single_part(part);
|
||||
}
|
||||
|
||||
if is_forwarded {
|
||||
if simplifier.is_forwarded {
|
||||
self.is_forwarded = true;
|
||||
}
|
||||
}
|
||||
@@ -730,6 +726,11 @@ impl<'a> MimeParser<'a> {
|
||||
}
|
||||
|
||||
fn process_report(&self, report: &mailparse::ParsedMail<'_>) -> Result<Option<Report>> {
|
||||
// to get a clear functionality, do not show incoming MDNs if the options is disabled
|
||||
if !self.mdns_enabled {
|
||||
return Ok(None);
|
||||
}
|
||||
|
||||
// parse as mailheaders
|
||||
let report_body = report.subparts[1].get_body_raw()?;
|
||||
let (report_fields, _) = mailparse::parse_headers(&report_body)?;
|
||||
@@ -748,46 +749,33 @@ impl<'a> MimeParser<'a> {
|
||||
}));
|
||||
}
|
||||
}
|
||||
warn!(
|
||||
self.context,
|
||||
"ignoring unknown disposition-notification, Message-Id: {:?}",
|
||||
report_fields.get_first_value("Message-ID").ok()
|
||||
);
|
||||
|
||||
Ok(None)
|
||||
}
|
||||
|
||||
// Handle reports (only MDNs for now)
|
||||
// Handle reports (mainly MDNs)
|
||||
pub fn handle_reports(
|
||||
&self,
|
||||
from_id: u32,
|
||||
sent_timestamp: i64,
|
||||
rr_event_to_send: &mut Vec<(u32, MsgId)>,
|
||||
server_folder: impl AsRef<str>,
|
||||
server_uid: u32,
|
||||
) {
|
||||
if self.reports.is_empty() {
|
||||
return;
|
||||
}
|
||||
// If a user disabled MDNs we do not show pending incoming ones anymore
|
||||
// but we do want them to potentially get moved from the INBOX still.
|
||||
let mdns_enabled = self.context.get_config_bool(Config::MdnsEnabled);
|
||||
|
||||
for report in &self.reports {
|
||||
let mut mdn_recognized = false;
|
||||
let mut mdn_consumed = false;
|
||||
|
||||
if mdns_enabled {
|
||||
if let Some((chat_id, msg_id)) = message::mdn_from_ext(
|
||||
self.context,
|
||||
from_id,
|
||||
&report.original_message_id,
|
||||
sent_timestamp,
|
||||
) {
|
||||
self.context.call_cb(Event::MsgRead { chat_id, msg_id });
|
||||
mdn_recognized = true;
|
||||
}
|
||||
if let Some((chat_id, msg_id)) = message::mdn_from_ext(
|
||||
self.context,
|
||||
from_id,
|
||||
&report.original_message_id,
|
||||
sent_timestamp,
|
||||
) {
|
||||
rr_event_to_send.push((chat_id, msg_id));
|
||||
mdn_consumed = true;
|
||||
}
|
||||
|
||||
if self.has_chat_version() || mdn_recognized || !mdns_enabled {
|
||||
if self.has_chat_version() || mdn_consumed {
|
||||
let mut param = Params::new();
|
||||
param.set(Param::ServerFolder, server_folder.as_ref());
|
||||
param.set_int(Param::ServerUid, server_uid as i32);
|
||||
@@ -880,6 +868,7 @@ fn is_known(key: &str) -> bool {
|
||||
#[derive(Debug, Default, Clone)]
|
||||
pub struct Part {
|
||||
pub typ: Viewtype,
|
||||
pub is_meta: bool,
|
||||
pub mimetype: Option<Mime>,
|
||||
pub msg: String,
|
||||
pub msg_raw: Option<String>,
|
||||
@@ -1121,26 +1110,6 @@ mod tests {
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_parse_first_addr() {
|
||||
let context = dummy_context();
|
||||
let raw = b"From: hello@one.org, world@two.org\n\
|
||||
Chat-Disposition-Notification-To: wrong
|
||||
Content-Type: text/plain;
|
||||
Chat-Version: 1.0\n\
|
||||
\n\
|
||||
test1\n\
|
||||
\x00";
|
||||
|
||||
let mimeparser = MimeParser::from_bytes(&context.ctx, &raw[..]).unwrap();
|
||||
|
||||
let of = mimeparser.parse_first_addr(HeaderDef::From_).unwrap();
|
||||
assert_eq!(of, mailparse::addrparse("hello@one.org").unwrap()[0]);
|
||||
|
||||
let of = mimeparser.parse_first_addr(HeaderDef::ChatDispositionNotificationTo);
|
||||
assert!(of.is_none());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_mimeparser_with_context() {
|
||||
let context = dummy_context();
|
||||
|
||||
30
src/param.rs
30
src/param.rs
@@ -16,51 +16,36 @@ use crate::mimeparser::SystemMessage;
|
||||
pub enum Param {
|
||||
/// For messages and jobs
|
||||
File = b'f',
|
||||
|
||||
/// For Messages
|
||||
Width = b'w',
|
||||
|
||||
/// For Messages
|
||||
Height = b'h',
|
||||
|
||||
/// For Messages
|
||||
Duration = b'd',
|
||||
|
||||
/// For Messages
|
||||
MimeType = b'm',
|
||||
|
||||
/// For Messages: message is encrypted, outgoing: guarantee E2EE or the message is not send
|
||||
/// For Messages: message is encryoted, outgoing: guarantee E2EE or the message is not send
|
||||
GuaranteeE2ee = b'c',
|
||||
|
||||
/// For Messages: decrypted with validation errors or without mutual set, if neither
|
||||
/// 'c' nor 'e' are preset, the messages is only transport encrypted.
|
||||
ErroneousE2ee = b'e',
|
||||
|
||||
/// For Messages: force unencrypted message, either `ForcePlaintext::AddAutocryptHeader` (1),
|
||||
/// `ForcePlaintext::NoAutocryptHeader` (2) or 0.
|
||||
ForcePlaintext = b'u',
|
||||
|
||||
/// For Messages
|
||||
WantsMdn = b'r',
|
||||
|
||||
/// For Messages
|
||||
Forwarded = b'a',
|
||||
|
||||
/// For Messages
|
||||
Cmd = b'S',
|
||||
|
||||
/// For Messages
|
||||
Arg = b'E',
|
||||
|
||||
/// For Messages
|
||||
Arg2 = b'F',
|
||||
|
||||
/// For Messages
|
||||
Arg3 = b'G',
|
||||
|
||||
/// For Messages
|
||||
Arg4 = b'H',
|
||||
|
||||
/// For Messages
|
||||
Error = b'L',
|
||||
|
||||
@@ -77,43 +62,30 @@ pub enum Param {
|
||||
/// When the original message is then finally sent this parameter
|
||||
/// is used to also send all the forwarded messages.
|
||||
PrepForwards = b'P',
|
||||
|
||||
/// For Jobs
|
||||
SetLatitude = b'l',
|
||||
|
||||
/// For Jobs
|
||||
SetLongitude = b'n',
|
||||
|
||||
/// For Jobs
|
||||
ServerFolder = b'Z',
|
||||
|
||||
/// For Jobs
|
||||
ServerUid = b'z',
|
||||
|
||||
/// For Jobs
|
||||
AlsoMove = b'M',
|
||||
|
||||
/// For Jobs: space-separated list of message recipients
|
||||
Recipients = b'R',
|
||||
|
||||
// For Groups
|
||||
Unpromoted = b'U',
|
||||
|
||||
// For Groups and Contacts
|
||||
ProfileImage = b'i',
|
||||
|
||||
// For Chats
|
||||
Selftalk = b'K',
|
||||
|
||||
// For Chats
|
||||
Devicetalk = b'D',
|
||||
|
||||
// For QR
|
||||
Auth = b's',
|
||||
|
||||
// For QR
|
||||
GroupId = b'x',
|
||||
|
||||
// For QR
|
||||
GroupName = b'g',
|
||||
}
|
||||
|
||||
@@ -95,7 +95,6 @@ pub enum ToSave {
|
||||
pub enum DegradeEvent {
|
||||
/// Recoverable by an incoming encrypted mail.
|
||||
EncryptionPaused = 0x01,
|
||||
|
||||
/// Recoverable by a new verify.
|
||||
FingerprintChanged = 0x02,
|
||||
}
|
||||
|
||||
251
src/simplify.rs
251
src/simplify.rs
@@ -1,251 +0,0 @@
|
||||
/// Remove standard (RFC 3676, §4.3) footer if it is found.
|
||||
fn remove_message_footer<'a>(lines: &'a [&str]) -> &'a [&'a str] {
|
||||
for (ix, &line) in lines.iter().enumerate() {
|
||||
// quoted-printable may encode `-- ` to `-- =20` which is converted
|
||||
// back to `-- `
|
||||
match line {
|
||||
"-- " | "-- " => return &lines[..ix],
|
||||
_ => (),
|
||||
}
|
||||
}
|
||||
lines
|
||||
}
|
||||
|
||||
/// Remove nonstandard footer and a boolean indicating whether such
|
||||
/// footer was removed.
|
||||
fn remove_nonstandard_footer<'a>(lines: &'a [&str]) -> (&'a [&'a str], bool) {
|
||||
for (ix, &line) in lines.iter().enumerate() {
|
||||
if line == "--"
|
||||
|| line == "---"
|
||||
|| line == "----"
|
||||
|| line.starts_with("-----")
|
||||
|| line.starts_with("_____")
|
||||
|| line.starts_with("=====")
|
||||
|| line.starts_with("*****")
|
||||
|| line.starts_with("~~~~~")
|
||||
{
|
||||
return (&lines[..ix], true);
|
||||
}
|
||||
}
|
||||
(lines, false)
|
||||
}
|
||||
|
||||
fn split_lines(buf: &str) -> Vec<&str> {
|
||||
buf.split('\n').collect()
|
||||
}
|
||||
|
||||
/// Simplify message text for chat display.
|
||||
/// Remove quotes, signatures, trailing empty lines etc.
|
||||
pub fn simplify(mut input: String, is_chat_message: bool) -> (String, bool) {
|
||||
input.retain(|c| c != '\r');
|
||||
let lines = split_lines(&input);
|
||||
let (lines, is_forwarded) = skip_forward_header(&lines);
|
||||
|
||||
let lines = remove_message_footer(lines);
|
||||
let (lines, has_nonstandard_footer) = remove_nonstandard_footer(lines);
|
||||
let (lines, has_bottom_quote) = if !is_chat_message {
|
||||
remove_bottom_quote(lines)
|
||||
} else {
|
||||
(lines, false)
|
||||
};
|
||||
let (lines, has_top_quote) = if !is_chat_message {
|
||||
remove_top_quote(lines)
|
||||
} else {
|
||||
(lines, false)
|
||||
};
|
||||
|
||||
// re-create buffer from the remaining lines
|
||||
let text = render_message(
|
||||
lines,
|
||||
has_top_quote,
|
||||
has_nonstandard_footer || has_bottom_quote,
|
||||
);
|
||||
(text, is_forwarded)
|
||||
}
|
||||
|
||||
/// Skips "forwarded message" header.
|
||||
/// Returns message body lines and a boolean indicating whether
|
||||
/// a message is forwarded or not.
|
||||
fn skip_forward_header<'a>(lines: &'a [&str]) -> (&'a [&'a str], bool) {
|
||||
if lines.len() >= 3
|
||||
&& lines[0] == "---------- Forwarded message ----------"
|
||||
&& lines[1].starts_with("From: ")
|
||||
&& lines[2].is_empty()
|
||||
{
|
||||
(&lines[3..], true)
|
||||
} else {
|
||||
(lines, false)
|
||||
}
|
||||
}
|
||||
|
||||
fn remove_bottom_quote<'a>(lines: &'a [&str]) -> (&'a [&'a str], bool) {
|
||||
let mut last_quoted_line = None;
|
||||
for (l, line) in lines.iter().enumerate().rev() {
|
||||
if is_plain_quote(line) {
|
||||
last_quoted_line = Some(l)
|
||||
} else if !is_empty_line(line) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
if let Some(mut l_last) = last_quoted_line {
|
||||
if l_last > 1 && is_empty_line(lines[l_last - 1]) {
|
||||
l_last -= 1
|
||||
}
|
||||
if l_last > 1 {
|
||||
let line = lines[l_last - 1];
|
||||
if is_quoted_headline(line) {
|
||||
l_last -= 1
|
||||
}
|
||||
}
|
||||
(&lines[..l_last], true)
|
||||
} else {
|
||||
(lines, false)
|
||||
}
|
||||
}
|
||||
|
||||
fn remove_top_quote<'a>(lines: &'a [&str]) -> (&'a [&'a str], bool) {
|
||||
let mut last_quoted_line = None;
|
||||
let mut has_quoted_headline = false;
|
||||
for (l, line) in lines.iter().enumerate() {
|
||||
if is_plain_quote(line) {
|
||||
last_quoted_line = Some(l)
|
||||
} else if !is_empty_line(line) {
|
||||
if is_quoted_headline(line) && !has_quoted_headline && last_quoted_line.is_none() {
|
||||
has_quoted_headline = true
|
||||
} else {
|
||||
/* non-quoting line found */
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
if let Some(last_quoted_line) = last_quoted_line {
|
||||
(&lines[last_quoted_line + 1..], true)
|
||||
} else {
|
||||
(lines, false)
|
||||
}
|
||||
}
|
||||
|
||||
fn render_message(lines: &[&str], is_cut_at_begin: bool, is_cut_at_end: bool) -> String {
|
||||
let mut ret = String::new();
|
||||
if is_cut_at_begin {
|
||||
ret += "[...]";
|
||||
}
|
||||
/* we write empty lines only in case and non-empty line follows */
|
||||
let mut pending_linebreaks = 0;
|
||||
let mut empty_body = true;
|
||||
for line in lines {
|
||||
if is_empty_line(line) {
|
||||
pending_linebreaks += 1
|
||||
} else {
|
||||
if !empty_body {
|
||||
if pending_linebreaks > 2 {
|
||||
pending_linebreaks = 2
|
||||
}
|
||||
while 0 != pending_linebreaks {
|
||||
ret += "\n";
|
||||
pending_linebreaks -= 1
|
||||
}
|
||||
}
|
||||
// the incoming message might contain invalid UTF8
|
||||
ret += line;
|
||||
empty_body = false;
|
||||
pending_linebreaks = 1
|
||||
}
|
||||
}
|
||||
if is_cut_at_end && (!is_cut_at_begin || !empty_body) {
|
||||
ret += " [...]";
|
||||
}
|
||||
ret
|
||||
}
|
||||
|
||||
/**
|
||||
* Tools
|
||||
*/
|
||||
fn is_empty_line(buf: &str) -> bool {
|
||||
// XXX: can it be simplified to buf.chars().all(|c| c.is_whitespace())?
|
||||
//
|
||||
// Strictly speaking, it is not equivalent (^A is not whitespace, but less than ' '),
|
||||
// but having control sequences in email body?!
|
||||
//
|
||||
// See discussion at: https://github.com/deltachat/deltachat-core-rust/pull/402#discussion_r317062392
|
||||
for c in buf.chars() {
|
||||
if c > ' ' {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
true
|
||||
}
|
||||
|
||||
fn is_quoted_headline(buf: &str) -> bool {
|
||||
/* This function may be called for the line _directly_ before a quote.
|
||||
The function checks if the line contains sth. like "On 01.02.2016, xy@z wrote:" in various languages.
|
||||
- Currently, we simply check if the last character is a ':'.
|
||||
- Checking for the existence of an email address may fail (headlines may show the user's name instead of the address) */
|
||||
|
||||
buf.len() <= 80 && buf.ends_with(':')
|
||||
}
|
||||
|
||||
fn is_plain_quote(buf: &str) -> bool {
|
||||
buf.starts_with('>')
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
use proptest::prelude::*;
|
||||
|
||||
proptest! {
|
||||
#[test]
|
||||
// proptest does not support [[:graphical:][:space:]] regex.
|
||||
fn test_simplify_plain_text_fuzzy(input in "[!-~\t \n]+") {
|
||||
let (output, _is_forwarded) = simplify(input, true);
|
||||
assert!(output.split('\n').all(|s| s != "-- "));
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_simplify_trim() {
|
||||
let input = "line1\n\r\r\rline2".to_string();
|
||||
let (plain, is_forwarded) = simplify(input, false);
|
||||
|
||||
assert_eq!(plain, "line1\nline2");
|
||||
assert!(!is_forwarded);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_simplify_forwarded_message() {
|
||||
let input = "---------- Forwarded message ----------\r\nFrom: test@example.com\r\n\r\nForwarded message\r\n-- \r\nSignature goes here".to_string();
|
||||
let (plain, is_forwarded) = simplify(input, false);
|
||||
|
||||
assert_eq!(plain, "Forwarded message");
|
||||
assert!(is_forwarded);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_simplify_utilities() {
|
||||
assert!(is_empty_line(" \t"));
|
||||
assert!(is_empty_line(""));
|
||||
assert!(is_empty_line(" \r"));
|
||||
assert!(!is_empty_line(" x"));
|
||||
assert!(is_plain_quote("> hello world"));
|
||||
assert!(is_plain_quote(">>"));
|
||||
assert!(!is_plain_quote("Life is pain"));
|
||||
assert!(!is_plain_quote(""));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_remove_top_quote() {
|
||||
let (lines, has_top_quote) = remove_top_quote(&["> first", "> second"]);
|
||||
assert!(lines.is_empty());
|
||||
assert!(has_top_quote);
|
||||
|
||||
let (lines, has_top_quote) = remove_top_quote(&["> first", "> second", "not a quote"]);
|
||||
assert_eq!(lines, &["not a quote"]);
|
||||
assert!(has_top_quote);
|
||||
|
||||
let (lines, has_top_quote) = remove_top_quote(&["not a quote", "> first", "> second"]);
|
||||
assert_eq!(lines, &["not a quote", "> first", "> second"]);
|
||||
assert!(!has_top_quote);
|
||||
}
|
||||
}
|
||||
@@ -2,41 +2,33 @@
|
||||
|
||||
pub mod send;
|
||||
|
||||
use std::time::Duration;
|
||||
|
||||
use async_smtp::smtp::client::net::*;
|
||||
use async_smtp::*;
|
||||
|
||||
use async_std::task;
|
||||
|
||||
use crate::constants::*;
|
||||
use crate::context::Context;
|
||||
use crate::events::Event;
|
||||
use crate::login_param::{dc_build_tls, LoginParam};
|
||||
use crate::oauth2::*;
|
||||
|
||||
/// SMTP write and read timeout in seconds.
|
||||
const SMTP_TIMEOUT: u64 = 30;
|
||||
|
||||
#[derive(Debug, Fail)]
|
||||
pub enum Error {
|
||||
#[fail(display = "Bad parameters")]
|
||||
BadParameters,
|
||||
|
||||
#[fail(display = "Invalid login address {}: {}", address, error)]
|
||||
InvalidLoginAddress {
|
||||
address: String,
|
||||
#[cause]
|
||||
error: error::Error,
|
||||
error: async_smtp::error::Error,
|
||||
},
|
||||
|
||||
#[fail(display = "SMTP failed to connect: {:?}", _0)]
|
||||
ConnectionFailure(#[cause] smtp::error::Error),
|
||||
|
||||
ConnectionFailure(#[cause] async_smtp::smtp::error::Error),
|
||||
#[fail(display = "SMTP: failed to setup connection {:?}", _0)]
|
||||
ConnectionSetupFailure(#[cause] smtp::error::Error),
|
||||
|
||||
ConnectionSetupFailure(#[cause] async_smtp::smtp::error::Error),
|
||||
#[fail(display = "SMTP: oauth2 error {:?}", _0)]
|
||||
Oauth2Error { address: String },
|
||||
|
||||
#[fail(display = "TLS error")]
|
||||
Tls(#[cause] native_tls::Error),
|
||||
}
|
||||
@@ -52,7 +44,7 @@ pub type Result<T> = std::result::Result<T, Error>;
|
||||
#[derive(Default, DebugStub)]
|
||||
pub struct Smtp {
|
||||
#[debug_stub(some = "SmtpTransport")]
|
||||
transport: Option<smtp::SmtpTransport>,
|
||||
transport: Option<async_smtp::smtp::SmtpTransport>,
|
||||
/// Email address we are sending from.
|
||||
from: Option<EmailAddress>,
|
||||
}
|
||||
@@ -65,25 +57,18 @@ impl Smtp {
|
||||
|
||||
/// Disconnect the SMTP transport and drop it entirely.
|
||||
pub fn disconnect(&mut self) {
|
||||
if let Some(mut transport) = self.transport.take() {
|
||||
async_std::task::block_on(transport.close()).ok();
|
||||
if let Some(ref mut transport) = self.transport.take() {
|
||||
transport.close();
|
||||
}
|
||||
}
|
||||
|
||||
/// Check whether we are connected.
|
||||
/// check whether we are connected
|
||||
pub fn is_connected(&self) -> bool {
|
||||
self.transport
|
||||
.as_ref()
|
||||
.map(|t| t.is_connected())
|
||||
.unwrap_or_default()
|
||||
self.transport.is_some()
|
||||
}
|
||||
|
||||
/// Connect using the provided login params.
|
||||
/// Connect using the provided login params
|
||||
pub fn connect(&mut self, context: &Context, lp: &LoginParam) -> Result<()> {
|
||||
async_std::task::block_on(self.inner_connect(context, lp))
|
||||
}
|
||||
|
||||
async fn inner_connect(&mut self, context: &Context, lp: &LoginParam) -> Result<()> {
|
||||
if self.is_connected() {
|
||||
warn!(context, "SMTP already connected.");
|
||||
return Ok(());
|
||||
@@ -119,21 +104,21 @@ impl Smtp {
|
||||
}
|
||||
let user = &lp.send_user;
|
||||
(
|
||||
smtp::authentication::Credentials::new(
|
||||
async_smtp::smtp::authentication::Credentials::new(
|
||||
user.to_string(),
|
||||
access_token.unwrap_or_default(),
|
||||
),
|
||||
vec![smtp::authentication::Mechanism::Xoauth2],
|
||||
vec![async_smtp::smtp::authentication::Mechanism::Xoauth2],
|
||||
)
|
||||
} else {
|
||||
// plain
|
||||
let user = lp.send_user.clone();
|
||||
let pw = lp.send_pw.clone();
|
||||
(
|
||||
smtp::authentication::Credentials::new(user, pw),
|
||||
async_smtp::smtp::authentication::Credentials::new(user, pw),
|
||||
vec![
|
||||
smtp::authentication::Mechanism::Plain,
|
||||
smtp::authentication::Mechanism::Login,
|
||||
async_smtp::smtp::authentication::Mechanism::Plain,
|
||||
async_smtp::smtp::authentication::Mechanism::Login,
|
||||
],
|
||||
)
|
||||
};
|
||||
@@ -141,31 +126,30 @@ impl Smtp {
|
||||
let security = if 0
|
||||
!= lp.server_flags & (DC_LP_SMTP_SOCKET_STARTTLS | DC_LP_SMTP_SOCKET_PLAIN) as i32
|
||||
{
|
||||
smtp::ClientSecurity::Opportunistic(tls_parameters)
|
||||
async_smtp::smtp::ClientSecurity::Opportunistic(tls_parameters)
|
||||
} else {
|
||||
smtp::ClientSecurity::Wrapper(tls_parameters)
|
||||
async_smtp::smtp::ClientSecurity::Wrapper(tls_parameters)
|
||||
};
|
||||
|
||||
let client = smtp::SmtpClient::with_security((domain.as_str(), port), security)
|
||||
.await
|
||||
.map_err(Error::ConnectionSetupFailure)?;
|
||||
let client = task::block_on(async_smtp::smtp::SmtpClient::with_security(
|
||||
(domain.as_str(), port),
|
||||
security,
|
||||
))
|
||||
.map_err(Error::ConnectionSetupFailure)?;
|
||||
|
||||
let client = client
|
||||
.smtp_utf8(true)
|
||||
.credentials(creds)
|
||||
.authentication_mechanism(mechanism)
|
||||
.connection_reuse(smtp::ConnectionReuseParameters::ReuseUnlimited)
|
||||
.timeout(Some(Duration::from_secs(SMTP_TIMEOUT)));
|
||||
|
||||
.connection_reuse(async_smtp::smtp::ConnectionReuseParameters::ReuseUnlimited);
|
||||
let mut trans = client.into_transport();
|
||||
trans.connect().await.map_err(Error::ConnectionFailure)?;
|
||||
task::block_on(trans.connect()).map_err(Error::ConnectionFailure)?;
|
||||
|
||||
self.transport = Some(trans);
|
||||
context.call_cb(Event::SmtpConnected(format!(
|
||||
"SMTP-LOGIN as {} ok",
|
||||
lp.send_user,
|
||||
)));
|
||||
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
@@ -12,10 +12,8 @@ pub type Result<T> = std::result::Result<T, Error>;
|
||||
pub enum Error {
|
||||
#[fail(display = "Envelope error: {}", _0)]
|
||||
EnvelopeError(#[cause] async_smtp::error::Error),
|
||||
|
||||
#[fail(display = "Send error: {}", _0)]
|
||||
SendError(#[cause] async_smtp::smtp::error::Error),
|
||||
|
||||
#[fail(display = "SMTP has no transport")]
|
||||
NoTransport,
|
||||
}
|
||||
@@ -45,7 +43,6 @@ impl Smtp {
|
||||
format!("{}", job_id), // only used for internal logging
|
||||
message,
|
||||
);
|
||||
|
||||
if let Some(ref mut transport) = self.transport {
|
||||
transport.send(mail).await.map_err(Error::SendError)?;
|
||||
|
||||
@@ -53,6 +50,7 @@ impl Smtp {
|
||||
"Message len={} was smtp-sent to {}",
|
||||
message_len, recipients_display
|
||||
)));
|
||||
|
||||
Ok(())
|
||||
} else {
|
||||
warn!(
|
||||
|
||||
@@ -544,7 +544,7 @@ fn open(
|
||||
// --------------------------------------------------------------------
|
||||
|
||||
let mut dbversion = dbversion_before_update;
|
||||
let mut recalc_fingerprints = false;
|
||||
let mut recalc_fingerprints = 0;
|
||||
let mut update_icons = false;
|
||||
|
||||
if dbversion < 1 {
|
||||
@@ -687,7 +687,7 @@ fn open(
|
||||
"CREATE INDEX acpeerstates_index4 ON acpeerstates (gossip_key_fingerprint);",
|
||||
params![],
|
||||
)?;
|
||||
recalc_fingerprints = true;
|
||||
recalc_fingerprints = 1;
|
||||
dbversion = 34;
|
||||
sql.set_raw_config_int(context, "dbversion", 34)?;
|
||||
}
|
||||
@@ -872,7 +872,7 @@ fn open(
|
||||
// (the structure is complete now and all objects are usable)
|
||||
// --------------------------------------------------------------------
|
||||
|
||||
if recalc_fingerprints {
|
||||
if 0 != recalc_fingerprints {
|
||||
info!(context, "[migration] recalc fingerprints");
|
||||
sql.query_map(
|
||||
"SELECT addr FROM acpeerstates;",
|
||||
|
||||
@@ -185,9 +185,6 @@ pub enum StockMessage {
|
||||
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,
|
||||
|
||||
#[strum(props(fallback = "Unknown Sender for this chat. See 'info' for more details."))]
|
||||
UnknownSenderForChat = 72,
|
||||
}
|
||||
|
||||
/*
|
||||
|
||||
@@ -2,6 +2,7 @@
|
||||
//!
|
||||
//! This module is only compiled for test runs.
|
||||
|
||||
use libc::uintptr_t;
|
||||
use tempfile::{tempdir, TempDir};
|
||||
|
||||
use crate::config::Config;
|
||||
@@ -30,7 +31,7 @@ pub fn test_context(callback: Option<Box<ContextCallback>>) -> TestContext {
|
||||
let dbfile = dir.path().join("db.sqlite");
|
||||
let cb: Box<ContextCallback> = match callback {
|
||||
Some(cb) => cb,
|
||||
None => Box::new(|_, _| ()),
|
||||
None => Box::new(|_, _| 0),
|
||||
};
|
||||
let ctx = Context::new(cb, "FakeOs".into(), dbfile).unwrap();
|
||||
TestContext { ctx, dir }
|
||||
@@ -45,13 +46,14 @@ pub fn dummy_context() -> TestContext {
|
||||
test_context(None)
|
||||
}
|
||||
|
||||
pub fn logging_cb(_ctx: &Context, evt: Event) {
|
||||
pub fn logging_cb(_ctx: &Context, evt: Event) -> uintptr_t {
|
||||
match evt {
|
||||
Event::Info(msg) => println!("I: {}", msg),
|
||||
Event::Warning(msg) => println!("W: {}", msg),
|
||||
Event::Error(msg) => println!("E: {}", msg),
|
||||
_ => (),
|
||||
}
|
||||
0
|
||||
}
|
||||
|
||||
/// Creates Alice with a pre-generated keypair.
|
||||
|
||||
59
src/wrapmime.rs
Normal file
59
src/wrapmime.rs
Normal file
@@ -0,0 +1,59 @@
|
||||
use mailparse::ParsedMail;
|
||||
|
||||
use crate::error::Error;
|
||||
|
||||
pub fn parse_message_id(message_id: &[u8]) -> Result<String, Error> {
|
||||
let value = std::str::from_utf8(message_id)?;
|
||||
let addrs = mailparse::addrparse(value)
|
||||
.map_err(|err| format_err!("failed to parse message id {:?}", err))?;
|
||||
|
||||
if let Some(info) = addrs.extract_single_info() {
|
||||
return Ok(info.addr);
|
||||
}
|
||||
|
||||
bail!("could not parse message_id: {}", value);
|
||||
}
|
||||
|
||||
/// Returns a reference to the encrypted payload and validates the autocrypt structure.
|
||||
pub fn get_autocrypt_mime<'a, 'b>(mail: &'a ParsedMail<'b>) -> Result<&'a ParsedMail<'b>, Error> {
|
||||
ensure!(
|
||||
mail.ctype.mimetype == "multipart/encrypted",
|
||||
"Not a multipart/encrypted message: {}",
|
||||
mail.ctype.mimetype
|
||||
);
|
||||
ensure!(
|
||||
mail.subparts.len() == 2,
|
||||
"Invalid Autocrypt Level 1 Mime Parts"
|
||||
);
|
||||
|
||||
ensure!(
|
||||
mail.subparts[0].ctype.mimetype == "application/pgp-encrypted",
|
||||
"Invalid Autocrypt Level 1 version part: {:?}",
|
||||
mail.subparts[0].ctype,
|
||||
);
|
||||
|
||||
ensure!(
|
||||
mail.subparts[1].ctype.mimetype == "application/octet-stream",
|
||||
"Invalid Autocrypt Level 1 encrypted part: {:?}",
|
||||
mail.subparts[1].ctype
|
||||
);
|
||||
|
||||
Ok(&mail.subparts[1])
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
|
||||
#[test]
|
||||
fn test_parse_message_id() {
|
||||
assert_eq!(
|
||||
parse_message_id(b"Mr.PRUe8HJBoaO.3whNvLCMFU0@testrun.org").unwrap(),
|
||||
"Mr.PRUe8HJBoaO.3whNvLCMFU0@testrun.org"
|
||||
);
|
||||
assert_eq!(
|
||||
parse_message_id(b"<Mr.PRUe8HJBoaO.3whNvLCMFU0@testrun.org>").unwrap(),
|
||||
"Mr.PRUe8HJBoaO.3whNvLCMFU0@testrun.org"
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -203,7 +203,9 @@ fn test_encryption_decryption() {
|
||||
assert_eq!(plain, original_text);
|
||||
}
|
||||
|
||||
fn cb(_context: &Context, _event: Event) {}
|
||||
fn cb(_context: &Context, _event: Event) -> libc::uintptr_t {
|
||||
0
|
||||
}
|
||||
|
||||
#[allow(dead_code)]
|
||||
struct TestContext {
|
||||
|
||||
Reference in New Issue
Block a user