mirror of
https://github.com/chatmail/core.git
synced 2026-04-02 21:42:10 +03:00
Compare commits
111 Commits
safe-e2ee
...
refactor/p
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
e571750f96 | ||
|
|
0f22b75207 | ||
|
|
13991d8dc5 | ||
|
|
75d9eb08f7 | ||
|
|
8cf571c78a | ||
|
|
77a7efc920 | ||
|
|
de604e744e | ||
|
|
24c0a833bd | ||
|
|
45f011c63c | ||
|
|
bc86201b44 | ||
|
|
b82af9fff3 | ||
|
|
3a1e74a306 | ||
|
|
e4cca92910 | ||
|
|
102220834c | ||
|
|
24d744b94c | ||
|
|
1df6229e99 | ||
|
|
c23e98ff83 | ||
|
|
b7c81f37c0 | ||
|
|
5c3a7e4119 | ||
|
|
a94acef49b | ||
|
|
7f5b362eda | ||
|
|
ba5b3ad675 | ||
|
|
c1e4d1e7a4 | ||
|
|
dd8744b74e | ||
|
|
b775ecca08 | ||
|
|
b8f211a013 | ||
|
|
51534b2fae | ||
|
|
710db2ba0a | ||
|
|
32ef0d4dc3 | ||
|
|
b3cd80ba6d | ||
|
|
3f053f899e | ||
|
|
22b4d1734c | ||
|
|
46a71e81a0 | ||
|
|
b4851187ba | ||
|
|
0252969f7e | ||
|
|
f86cec4844 | ||
|
|
cd2e36da92 | ||
|
|
1b13107181 | ||
|
|
6fbde21995 | ||
|
|
a6608513ac | ||
|
|
d43c225be3 | ||
|
|
1802d7658d | ||
|
|
275f5d713f | ||
|
|
e40cfeec58 | ||
|
|
275b4b8d36 | ||
|
|
77cef632c7 | ||
|
|
db2064de14 | ||
|
|
e251c7b1c8 | ||
|
|
2fe98775f9 | ||
|
|
187179d87b | ||
|
|
dd03f6e8af | ||
|
|
07b32241bd | ||
|
|
abfff96cd4 | ||
|
|
735bdd1c20 | ||
|
|
9cae075b6f | ||
|
|
2317518e5e | ||
|
|
477af413c6 | ||
|
|
93f0f5ccae | ||
|
|
79b92727cc | ||
|
|
8dfd04672f | ||
|
|
603761e4b7 | ||
|
|
467c09f491 | ||
|
|
a953b494cb | ||
|
|
dca9afa10b | ||
|
|
23d2d87c24 | ||
|
|
c6b2d640ae | ||
|
|
b4b8a1d15b | ||
|
|
130d485cac | ||
|
|
d5b92744ed | ||
|
|
a5c4e16405 | ||
|
|
216266d7bf | ||
|
|
bf1652a1be | ||
|
|
f93f3d6012 | ||
|
|
41806f86ba | ||
|
|
59df97944f | ||
|
|
468651534e | ||
|
|
6343ae8161 | ||
|
|
641bd5eb15 | ||
|
|
063d989225 | ||
|
|
b8ca7b1591 | ||
|
|
e222f49c9d | ||
|
|
297bc635e8 | ||
|
|
230c65594c | ||
|
|
509a21ff05 | ||
|
|
96066712bd | ||
|
|
d83aa1e898 | ||
|
|
f0a7bdb6d6 | ||
|
|
9c077c98cd | ||
|
|
6dc45642b7 | ||
|
|
5c1b9c83f7 | ||
|
|
92438737c9 | ||
|
|
489cdd1b24 | ||
|
|
3f7995a7ea | ||
|
|
f7ad93229d | ||
|
|
555b4bc8c7 | ||
|
|
75f41bcb90 | ||
|
|
97e1fbc198 | ||
|
|
ee6d16f1b1 | ||
|
|
22d2097132 | ||
|
|
c376de9b5e | ||
|
|
ab2ef1e1e4 | ||
|
|
18030fa61e | ||
|
|
064337b5d3 | ||
|
|
a6a6fc48c1 | ||
|
|
d72e9bb05b | ||
|
|
7a9fdb4acd | ||
|
|
a6d0464735 | ||
|
|
52f69cc7dc | ||
|
|
0beadde758 | ||
|
|
618abd63cf | ||
|
|
34b3ddf63b |
@@ -4,6 +4,9 @@ executors:
|
||||
docker:
|
||||
- image: filecoin/rust:latest
|
||||
working_directory: /mnt/crate
|
||||
doxygen:
|
||||
docker:
|
||||
- image: hrektts/doxygen
|
||||
|
||||
restore-workspace: &restore-workspace
|
||||
attach_workspace:
|
||||
@@ -113,6 +116,18 @@ jobs:
|
||||
target: "aarch64-linux-android"
|
||||
|
||||
|
||||
build_doxygen:
|
||||
executor: doxygen
|
||||
steps:
|
||||
- checkout
|
||||
- run: bash ci_scripts/run-doxygen.sh
|
||||
- run: mkdir -p workspace/c-docs
|
||||
- run: cp -av deltachat-ffi/{html,xml} workspace/c-docs/
|
||||
- persist_to_workspace:
|
||||
root: workspace
|
||||
paths:
|
||||
- c-docs
|
||||
|
||||
build_test_docs_wheel:
|
||||
docker:
|
||||
- image: deltachat/coredeps
|
||||
@@ -148,7 +163,7 @@ jobs:
|
||||
at: workspace
|
||||
- run: pyenv global 3.5.2
|
||||
- run: ls -laR workspace
|
||||
- run: ci_scripts/ci_upload.sh workspace/py-docs workspace/wheelhouse
|
||||
- run: ci_scripts/ci_upload.sh workspace/py-docs workspace/wheelhouse workspace/c-docs
|
||||
|
||||
clippy:
|
||||
executor: default
|
||||
@@ -166,18 +181,21 @@ workflows:
|
||||
test:
|
||||
jobs:
|
||||
- cargo_fetch
|
||||
- build_doxygen
|
||||
|
||||
- build_test_docs_wheel:
|
||||
requires:
|
||||
- cargo_fetch
|
||||
- upload_docs_wheels:
|
||||
requires:
|
||||
- build_test_docs_wheel
|
||||
- build_doxygen
|
||||
- rustfmt:
|
||||
requires:
|
||||
- cargo_fetch
|
||||
- clippy:
|
||||
requires:
|
||||
- cargo_fetch
|
||||
- cargo_fetch
|
||||
|
||||
# Linux Desktop 64bit
|
||||
- test_x86_64-unknown-linux-gnu:
|
||||
|
||||
259
Cargo.lock
generated
259
Cargo.lock
generated
@@ -57,10 +57,10 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
|
||||
[[package]]
|
||||
name = "arrayvec"
|
||||
version = "0.4.11"
|
||||
version = "0.4.12"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
dependencies = [
|
||||
"nodrop 0.1.13 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"nodrop 0.1.14 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -88,7 +88,7 @@ version = "0.3.38"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
dependencies = [
|
||||
"backtrace-sys 0.1.31 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"cfg-if 0.1.9 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"cfg-if 0.1.10 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"libc 0.2.62 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"rustc-demangle 0.1.16 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
]
|
||||
@@ -139,7 +139,7 @@ version = "0.5.8"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
dependencies = [
|
||||
"arrayref 0.3.5 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"arrayvec 0.4.11 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"arrayvec 0.4.12 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"constant_time_eq 0.1.4 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
]
|
||||
|
||||
@@ -226,7 +226,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
dependencies = [
|
||||
"byteorder 1.3.2 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"either 1.5.3 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"iovec 0.1.2 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"iovec 0.1.4 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -247,7 +247,7 @@ dependencies = [
|
||||
"semver 0.9.0 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"serde 1.0.101 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"serde_derive 1.0.101 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"serde_json 1.0.40 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"serde_json 1.0.41 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -276,7 +276,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "cfg-if"
|
||||
version = "0.1.9"
|
||||
version = "0.1.10"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
|
||||
[[package]]
|
||||
@@ -301,7 +301,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "circular"
|
||||
version = "0.2.0"
|
||||
version = "0.3.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
|
||||
[[package]]
|
||||
@@ -345,7 +345,7 @@ dependencies = [
|
||||
"log 0.4.8 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"publicsuffix 1.5.3 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"serde 1.0.101 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"serde_json 1.0.40 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"serde_json 1.0.41 (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)",
|
||||
"url 1.7.2 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
@@ -375,7 +375,7 @@ name = "crc32fast"
|
||||
version = "1.2.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
dependencies = [
|
||||
"cfg-if 0.1.9 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"cfg-if 0.1.10 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -392,8 +392,8 @@ name = "crossbeam-epoch"
|
||||
version = "0.7.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
dependencies = [
|
||||
"arrayvec 0.4.11 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"cfg-if 0.1.9 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"arrayvec 0.4.12 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"cfg-if 0.1.10 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"crossbeam-utils 0.6.6 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"lazy_static 1.4.0 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"memoffset 0.5.1 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
@@ -413,13 +413,13 @@ name = "crossbeam-utils"
|
||||
version = "0.6.6"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
dependencies = [
|
||||
"cfg-if 0.1.9 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"cfg-if 0.1.10 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"lazy_static 1.4.0 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "ctor"
|
||||
version = "0.1.11"
|
||||
version = "0.1.12"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
dependencies = [
|
||||
"quote 1.0.2 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
@@ -435,7 +435,7 @@ dependencies = [
|
||||
"clear_on_drop 0.2.3 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"digest 0.8.1 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"rand_core 0.3.1 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"subtle 2.1.1 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"subtle 2.2.1 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -500,14 +500,14 @@ dependencies = [
|
||||
"itertools 0.8.0 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"jetscii 0.4.4 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"lazy_static 1.4.0 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"lettre 0.9.2 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"lettre 0.9.2 (git+https://github.com/deltachat/lettre)",
|
||||
"libc 0.2.62 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"mmime 0.1.2",
|
||||
"native-tls 0.2.3 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"num-derive 0.2.5 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"num-traits 0.2.8 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"percent-encoding 2.1.0 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"pgp 0.2.1 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"pgp 0.2.3 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"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)",
|
||||
@@ -516,11 +516,11 @@ dependencies = [
|
||||
"r2d2_sqlite 0.12.0 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"rand 0.6.5 (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.20 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"reqwest 0.9.21 (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)",
|
||||
"serde 1.0.101 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"serde_json 1.0.40 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"serde_json 1.0.41 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"sha2 0.8.0 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"smallvec 0.6.10 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"strum 0.16.0 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
@@ -633,13 +633,14 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
|
||||
[[package]]
|
||||
name = "ed25519-dalek"
|
||||
version = "1.0.0-pre.1"
|
||||
version = "1.0.0-pre.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
dependencies = [
|
||||
"clear_on_drop 0.2.3 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"curve25519-dalek 1.2.3 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"failure 0.1.5 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"rand 0.6.5 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"rand_core 0.3.1 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"rand_os 0.1.3 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"sha2 0.8.0 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
]
|
||||
|
||||
@@ -653,7 +654,7 @@ name = "encoding_rs"
|
||||
version = "0.8.20"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
dependencies = [
|
||||
"cfg-if 0.1.9 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"cfg-if 0.1.10 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -743,12 +744,13 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "flate2"
|
||||
version = "1.0.11"
|
||||
version = "1.0.12"
|
||||
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)",
|
||||
"crc32fast 1.2.0 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"libc 0.2.62 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"miniz_oxide 0.3.2 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"miniz_oxide 0.3.3 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -815,7 +817,7 @@ name = "getrandom"
|
||||
version = "0.1.12"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
dependencies = [
|
||||
"cfg-if 0.1.9 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"cfg-if 0.1.10 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"libc 0.2.62 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"wasi 0.7.0 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
]
|
||||
@@ -860,6 +862,11 @@ name = "hex"
|
||||
version = "0.3.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
|
||||
[[package]]
|
||||
name = "hex"
|
||||
version = "0.4.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
|
||||
[[package]]
|
||||
name = "hostname"
|
||||
version = "0.1.5"
|
||||
@@ -931,7 +938,7 @@ dependencies = [
|
||||
"http 0.1.18 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"http-body 0.1.0 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"httparse 1.3.4 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"iovec 0.1.2 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"iovec 0.1.4 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"itoa 0.4.4 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"log 0.4.8 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"net2 0.2.33 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
@@ -941,9 +948,9 @@ dependencies = [
|
||||
"tokio-buf 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"tokio-executor 0.1.8 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"tokio-io 0.1.12 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"tokio-reactor 0.1.9 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"tokio-reactor 0.1.10 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"tokio-tcp 0.1.3 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"tokio-threadpool 0.1.15 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"tokio-threadpool 0.1.16 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"tokio-timer 0.2.11 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"want 0.2.0 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
]
|
||||
@@ -1025,11 +1032,10 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
|
||||
[[package]]
|
||||
name = "iovec"
|
||||
version = "0.1.2"
|
||||
version = "0.1.4"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
dependencies = [
|
||||
"libc 0.2.62 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"winapi 0.2.8 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -1072,7 +1078,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
[[package]]
|
||||
name = "lettre"
|
||||
version = "0.9.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
source = "git+https://github.com/deltachat/lettre#aeec999c27a70870e4b7b149e45f24188c1ff351"
|
||||
dependencies = [
|
||||
"base64 0.10.1 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"bufstream 0.1.4 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
@@ -1080,10 +1086,10 @@ dependencies = [
|
||||
"hostname 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)",
|
||||
"native-tls 0.2.3 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"nom 4.2.3 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"nom 5.0.1 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"serde 1.0.101 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"serde_derive 1.0.101 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"serde_json 1.0.40 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"serde_json 1.0.41 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -1091,8 +1097,8 @@ name = "lexical-core"
|
||||
version = "0.4.6"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
dependencies = [
|
||||
"arrayvec 0.4.11 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"cfg-if 0.1.9 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"arrayvec 0.4.12 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"cfg-if 0.1.10 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"rustc_version 0.2.3 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"ryu 1.0.0 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"static_assertions 0.3.4 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
@@ -1118,15 +1124,6 @@ name = "linked-hash-map"
|
||||
version = "0.5.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
|
||||
[[package]]
|
||||
name = "lock_api"
|
||||
version = "0.1.5"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
dependencies = [
|
||||
"owning_ref 0.4.0 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"scopeguard 0.3.3 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "lock_api"
|
||||
version = "0.2.0"
|
||||
@@ -1148,7 +1145,7 @@ name = "log"
|
||||
version = "0.4.8"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
dependencies = [
|
||||
"cfg-if 0.1.9 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"cfg-if 0.1.10 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -1220,7 +1217,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "miniz_oxide"
|
||||
version = "0.3.2"
|
||||
version = "0.3.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
dependencies = [
|
||||
"adler32 1.0.4 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
@@ -1233,7 +1230,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
dependencies = [
|
||||
"fuchsia-zircon 0.3.3 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"fuchsia-zircon-sys 0.3.3 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"iovec 0.1.2 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"iovec 0.1.4 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"kernel32-sys 0.2.2 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"libc 0.2.62 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"log 0.4.8 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
@@ -1275,9 +1272,9 @@ dependencies = [
|
||||
"lazy_static 1.4.0 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"libc 0.2.62 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"log 0.4.8 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"openssl 0.10.24 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"openssl 0.10.25 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"openssl-probe 0.1.2 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"openssl-sys 0.9.49 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"openssl-sys 0.9.50 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"schannel 0.1.16 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"security-framework 0.3.1 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"security-framework-sys 0.3.1 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
@@ -1289,7 +1286,7 @@ name = "net2"
|
||||
version = "0.2.33"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
dependencies = [
|
||||
"cfg-if 0.1.9 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"cfg-if 0.1.10 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"libc 0.2.62 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"winapi 0.3.8 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
]
|
||||
@@ -1301,14 +1298,14 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
dependencies = [
|
||||
"bitflags 1.2.0 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"cc 1.0.45 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"cfg-if 0.1.9 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"cfg-if 0.1.10 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"libc 0.2.62 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"void 1.0.2 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "nodrop"
|
||||
version = "0.1.13"
|
||||
version = "0.1.14"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
|
||||
[[package]]
|
||||
@@ -1405,15 +1402,15 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
|
||||
[[package]]
|
||||
name = "openssl"
|
||||
version = "0.10.24"
|
||||
version = "0.10.25"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
dependencies = [
|
||||
"bitflags 1.2.0 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"cfg-if 0.1.9 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"cfg-if 0.1.10 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"foreign-types 0.3.2 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"lazy_static 1.4.0 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"libc 0.2.62 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"openssl-sys 0.9.49 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"openssl-sys 0.9.50 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -1431,7 +1428,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "openssl-sys"
|
||||
version = "0.9.49"
|
||||
version = "0.9.50"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
dependencies = [
|
||||
"autocfg 0.1.6 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
@@ -1458,29 +1455,12 @@ dependencies = [
|
||||
"winapi 0.3.8 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "owning_ref"
|
||||
version = "0.4.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
dependencies = [
|
||||
"stable_deref_trait 1.1.1 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "packed_simd"
|
||||
version = "0.3.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
dependencies = [
|
||||
"cfg-if 0.1.9 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "parking_lot"
|
||||
version = "0.7.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
dependencies = [
|
||||
"lock_api 0.1.5 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"parking_lot_core 0.4.0 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"cfg-if 0.1.10 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -1503,24 +1483,12 @@ dependencies = [
|
||||
"rustc_version 0.2.3 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "parking_lot_core"
|
||||
version = "0.4.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
dependencies = [
|
||||
"libc 0.2.62 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"rand 0.6.5 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"rustc_version 0.2.3 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"smallvec 0.6.10 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"winapi 0.3.8 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "parking_lot_core"
|
||||
version = "0.5.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
dependencies = [
|
||||
"cfg-if 0.1.9 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"cfg-if 0.1.10 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"cloudabi 0.0.3 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"libc 0.2.62 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"rand 0.6.5 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
@@ -1535,7 +1503,7 @@ name = "parking_lot_core"
|
||||
version = "0.6.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
dependencies = [
|
||||
"cfg-if 0.1.9 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"cfg-if 0.1.10 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"cloudabi 0.0.3 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"libc 0.2.62 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"redox_syscall 0.1.56 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
@@ -1556,7 +1524,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
|
||||
[[package]]
|
||||
name = "pgp"
|
||||
version = "0.2.1"
|
||||
version = "0.2.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
dependencies = [
|
||||
"aes 0.3.2 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
@@ -1570,17 +1538,17 @@ dependencies = [
|
||||
"cast5 0.6.0 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"cfb-mode 0.3.2 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"chrono 0.4.9 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"circular 0.2.0 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"circular 0.3.0 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"crc24 0.1.6 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"derive_builder 0.7.2 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"des 0.3.0 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"digest 0.8.1 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"ed25519-dalek 1.0.0-pre.1 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"ed25519-dalek 1.0.0-pre.2 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"enum_primitive 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"failure 0.1.5 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"flate2 1.0.11 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"flate2 1.0.12 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"generic-array 0.12.3 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"hex 0.3.2 (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)",
|
||||
"log 0.4.8 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"md-5 0.8.0 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
@@ -1616,7 +1584,7 @@ version = "0.6.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
dependencies = [
|
||||
"ansi_term 0.11.0 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"ctor 0.1.11 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"ctor 0.1.12 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"difference 2.0.0 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"output_vt100 0.1.2 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
]
|
||||
@@ -1641,7 +1609,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "proc-macro2"
|
||||
version = "1.0.4"
|
||||
version = "1.0.5"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
dependencies = [
|
||||
"unicode-xid 0.2.0 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
@@ -1721,7 +1689,7 @@ name = "quote"
|
||||
version = "1.0.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
dependencies = [
|
||||
"proc-macro2 1.0.4 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"proc-macro2 1.0.5 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -1939,7 +1907,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "reqwest"
|
||||
version = "0.9.20"
|
||||
version = "0.9.21"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
dependencies = [
|
||||
"base64 0.10.1 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
@@ -1947,7 +1915,7 @@ dependencies = [
|
||||
"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.20 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"flate2 1.0.11 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"flate2 1.0.12 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"futures 0.1.29 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"http 0.1.18 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"hyper 0.12.35 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
@@ -1957,13 +1925,13 @@ dependencies = [
|
||||
"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.101 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"serde_json 1.0.40 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"serde_json 1.0.41 (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)",
|
||||
"tokio 0.1.22 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"tokio-executor 0.1.8 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"tokio-io 0.1.12 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"tokio-threadpool 0.1.15 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"tokio-threadpool 0.1.16 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"tokio-timer 0.2.11 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"url 1.7.2 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"uuid 0.7.4 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
@@ -1993,7 +1961,7 @@ dependencies = [
|
||||
"num-iter 0.1.39 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"num-traits 0.2.8 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"rand 0.6.5 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"subtle 2.1.1 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"subtle 2.2.1 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"zeroize 0.6.0 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
]
|
||||
|
||||
@@ -2096,11 +2064,6 @@ dependencies = [
|
||||
"parking_lot 0.9.0 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "scopeguard"
|
||||
version = "0.3.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
|
||||
[[package]]
|
||||
name = "scopeguard"
|
||||
version = "1.0.0"
|
||||
@@ -2152,14 +2115,14 @@ name = "serde_derive"
|
||||
version = "1.0.101"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
dependencies = [
|
||||
"proc-macro2 1.0.4 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"proc-macro2 1.0.5 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"quote 1.0.2 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"syn 1.0.5 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "serde_json"
|
||||
version = "1.0.40"
|
||||
version = "1.0.41"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
dependencies = [
|
||||
"itoa 0.4.4 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
@@ -2222,7 +2185,7 @@ dependencies = [
|
||||
"error-chain 0.12.1 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"glob 0.2.11 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"pulldown-cmark 0.2.0 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"serde_json 1.0.40 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"serde_json 1.0.41 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"tempdir 0.3.7 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"walkdir 2.2.9 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
]
|
||||
@@ -2247,11 +2210,6 @@ name = "smallvec"
|
||||
version = "0.6.10"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
|
||||
[[package]]
|
||||
name = "stable_deref_trait"
|
||||
version = "1.1.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
|
||||
[[package]]
|
||||
name = "static_assertions"
|
||||
version = "0.3.4"
|
||||
@@ -2289,14 +2247,14 @@ version = "0.16.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
dependencies = [
|
||||
"heck 0.3.1 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"proc-macro2 1.0.4 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"proc-macro2 1.0.5 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"quote 1.0.2 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"syn 1.0.5 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "subtle"
|
||||
version = "2.1.1"
|
||||
version = "2.2.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
|
||||
[[package]]
|
||||
@@ -2334,7 +2292,7 @@ name = "syn"
|
||||
version = "1.0.5"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
dependencies = [
|
||||
"proc-macro2 1.0.4 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"proc-macro2 1.0.5 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"quote 1.0.2 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"unicode-xid 0.2.0 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
]
|
||||
@@ -2372,7 +2330,7 @@ name = "tempfile"
|
||||
version = "3.1.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
dependencies = [
|
||||
"cfg-if 0.1.9 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"cfg-if 0.1.10 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"libc 0.2.62 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"rand 0.7.2 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"redox_syscall 0.1.56 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
@@ -2434,9 +2392,9 @@ dependencies = [
|
||||
"tokio-current-thread 0.1.6 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"tokio-executor 0.1.8 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"tokio-io 0.1.12 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"tokio-reactor 0.1.9 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"tokio-reactor 0.1.10 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"tokio-tcp 0.1.3 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"tokio-threadpool 0.1.15 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"tokio-threadpool 0.1.16 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"tokio-timer 0.2.11 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
]
|
||||
|
||||
@@ -2480,7 +2438,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "tokio-reactor"
|
||||
version = "0.1.9"
|
||||
version = "0.1.10"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
dependencies = [
|
||||
"crossbeam-utils 0.6.6 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
@@ -2489,7 +2447,7 @@ dependencies = [
|
||||
"log 0.4.8 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"mio 0.6.19 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"num_cpus 1.10.1 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"parking_lot 0.7.1 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"parking_lot 0.9.0 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"slab 0.4.2 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"tokio-executor 0.1.8 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"tokio-io 0.1.12 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
@@ -2512,24 +2470,24 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
dependencies = [
|
||||
"bytes 0.4.12 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"futures 0.1.29 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"iovec 0.1.2 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"iovec 0.1.4 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"mio 0.6.19 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"tokio-io 0.1.12 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"tokio-reactor 0.1.9 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"tokio-reactor 0.1.10 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "tokio-threadpool"
|
||||
version = "0.1.15"
|
||||
version = "0.1.16"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
dependencies = [
|
||||
"crossbeam-deque 0.7.1 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"crossbeam-queue 0.1.2 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"crossbeam-utils 0.6.6 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"futures 0.1.29 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"lazy_static 1.4.0 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"log 0.4.8 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"num_cpus 1.10.1 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"rand 0.6.5 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"slab 0.4.2 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"tokio-executor 0.1.8 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
]
|
||||
@@ -2568,7 +2526,7 @@ name = "try_from"
|
||||
version = "0.3.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
dependencies = [
|
||||
"cfg-if 0.1.9 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"cfg-if 0.1.10 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -2673,7 +2631,7 @@ name = "uuid"
|
||||
version = "0.6.5"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
dependencies = [
|
||||
"cfg-if 0.1.9 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"cfg-if 0.1.10 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"rand 0.4.6 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
]
|
||||
|
||||
@@ -2856,7 +2814,7 @@ dependencies = [
|
||||
"checksum aho-corasick 0.7.6 (registry+https://github.com/rust-lang/crates.io-index)" = "58fb5e95d83b38284460a5fda7d6470aa0b8844d283a0b614b8535e880800d2d"
|
||||
"checksum ansi_term 0.11.0 (registry+https://github.com/rust-lang/crates.io-index)" = "ee49baf6cb617b853aa8d93bf420db2383fab46d314482ca2803b40d5fde979b"
|
||||
"checksum arrayref 0.3.5 (registry+https://github.com/rust-lang/crates.io-index)" = "0d382e583f07208808f6b1249e60848879ba3543f57c32277bf52d69c2f0f0ee"
|
||||
"checksum arrayvec 0.4.11 (registry+https://github.com/rust-lang/crates.io-index)" = "b8d73f9beda665eaa98ab9e4f7442bd4e7de6652587de55b2525e52e29c1b0ba"
|
||||
"checksum arrayvec 0.4.12 (registry+https://github.com/rust-lang/crates.io-index)" = "cd9fd44efafa8690358b7408d253adf110036b88f55672a933f01d616ad9b1b9"
|
||||
"checksum ascii_utils 0.9.3 (registry+https://github.com/rust-lang/crates.io-index)" = "71938f30533e4d95a6d17aa530939da3842c2ab6f4f84b9dae68447e4129f74a"
|
||||
"checksum atty 0.2.13 (registry+https://github.com/rust-lang/crates.io-index)" = "1803c647a3ec87095e7ae7acfca019e98de5ec9a7d01343f611cf3152ed71a90"
|
||||
"checksum autocfg 0.1.6 (registry+https://github.com/rust-lang/crates.io-index)" = "b671c8fb71b457dd4ae18c4ba1e59aa81793daacc361d82fcd410cef0d491875"
|
||||
@@ -2884,10 +2842,10 @@ dependencies = [
|
||||
"checksum cast5 0.6.0 (registry+https://github.com/rust-lang/crates.io-index)" = "4ce5759b4c52ca74f9a98421817c882f1fd9b0071ae41cd61ab9f9d059c04fd6"
|
||||
"checksum cc 1.0.45 (registry+https://github.com/rust-lang/crates.io-index)" = "4fc9a35e1f4290eb9e5fc54ba6cf40671ed2a2514c3eeb2b2a908dda2ea5a1be"
|
||||
"checksum cfb-mode 0.3.2 (registry+https://github.com/rust-lang/crates.io-index)" = "190e7b55d3a27cf8879becf61035a141cbc783f3258a41d16d1706719f991345"
|
||||
"checksum cfg-if 0.1.9 (registry+https://github.com/rust-lang/crates.io-index)" = "b486ce3ccf7ffd79fdeb678eac06a9e6c09fc88d33836340becb8fffe87c5e33"
|
||||
"checksum cfg-if 0.1.10 (registry+https://github.com/rust-lang/crates.io-index)" = "4785bdd1c96b2a846b2bd7cc02e86b6b3dbf14e7e53446c4f54c92a361040822"
|
||||
"checksum charset 0.1.2 (registry+https://github.com/rust-lang/crates.io-index)" = "4f426e64df1c3de26cbf44593c6ffff5dbfd43bbf9de0d075058558126b3fc73"
|
||||
"checksum chrono 0.4.9 (registry+https://github.com/rust-lang/crates.io-index)" = "e8493056968583b0193c1bb04d6f7684586f3726992d6c573261941a895dbd68"
|
||||
"checksum circular 0.2.0 (registry+https://github.com/rust-lang/crates.io-index)" = "82945ea9ff134eba321833377d0c485f5c6fb4c8e26cfec199174e2970d5385a"
|
||||
"checksum circular 0.3.0 (registry+https://github.com/rust-lang/crates.io-index)" = "b0fc239e0f6cb375d2402d48afb92f76f5404fd1df208a41930ec81eda078bea"
|
||||
"checksum clear_on_drop 0.2.3 (registry+https://github.com/rust-lang/crates.io-index)" = "97276801e127ffb46b66ce23f35cc96bd454fa311294bced4bbace7baa8b1d17"
|
||||
"checksum cloudabi 0.0.3 (registry+https://github.com/rust-lang/crates.io-index)" = "ddfc5b9aa5d4507acaf872de71051dfd0e309860e88966e1051e462a077aac4f"
|
||||
"checksum constant_time_eq 0.1.4 (registry+https://github.com/rust-lang/crates.io-index)" = "995a44c877f9212528ccc74b21a232f66ad69001e40ede5bcee2ac9ef2657120"
|
||||
@@ -2901,7 +2859,7 @@ dependencies = [
|
||||
"checksum crossbeam-epoch 0.7.2 (registry+https://github.com/rust-lang/crates.io-index)" = "fedcd6772e37f3da2a9af9bf12ebe046c0dfe657992377b4df982a2b54cd37a9"
|
||||
"checksum crossbeam-queue 0.1.2 (registry+https://github.com/rust-lang/crates.io-index)" = "7c979cd6cfe72335896575c6b5688da489e420d36a27a0b9eb0c73db574b4a4b"
|
||||
"checksum crossbeam-utils 0.6.6 (registry+https://github.com/rust-lang/crates.io-index)" = "04973fa96e96579258a5091af6003abde64af786b860f18622b82e026cca60e6"
|
||||
"checksum ctor 0.1.11 (registry+https://github.com/rust-lang/crates.io-index)" = "3e061727ebef83bbccac7c27b9a5ff9fd83094d34cb20f4005440a9562a27de7"
|
||||
"checksum ctor 0.1.12 (registry+https://github.com/rust-lang/crates.io-index)" = "cd8ce37ad4184ab2ce004c33bf6379185d3b1c95801cab51026bd271bf68eedc"
|
||||
"checksum curve25519-dalek 1.2.3 (registry+https://github.com/rust-lang/crates.io-index)" = "8b7dcd30ba50cdf88b55b033456138b7c0ac4afdc436d82e1b79f370f24cc66d"
|
||||
"checksum darling 0.9.0 (registry+https://github.com/rust-lang/crates.io-index)" = "fcfbcb0c5961907597a7d1148e3af036268f2b773886b8bb3eeb1e1281d3d3d6"
|
||||
"checksum darling_core 0.9.0 (registry+https://github.com/rust-lang/crates.io-index)" = "6afc018370c3bff3eb51f89256a6bdb18b4fdcda72d577982a14954a7a0b402c"
|
||||
@@ -2916,7 +2874,7 @@ dependencies = [
|
||||
"checksum digest 0.8.1 (registry+https://github.com/rust-lang/crates.io-index)" = "f3d0c8c8752312f9713efd397ff63acb9f85585afbf179282e720e7704954dd5"
|
||||
"checksum dirs 1.0.5 (registry+https://github.com/rust-lang/crates.io-index)" = "3fd78930633bd1c6e35c4b42b1df7b0cbc6bc191146e512bb3bedf243fcc3901"
|
||||
"checksum dtoa 0.4.4 (registry+https://github.com/rust-lang/crates.io-index)" = "ea57b42383d091c85abcc2706240b94ab2a8fa1fc81c10ff23c4de06e2a90b5e"
|
||||
"checksum ed25519-dalek 1.0.0-pre.1 (registry+https://github.com/rust-lang/crates.io-index)" = "81956bcf7ef761fb4e1d88de3fa181358a0d26cbcb9755b587a08f9119824b86"
|
||||
"checksum ed25519-dalek 1.0.0-pre.2 (registry+https://github.com/rust-lang/crates.io-index)" = "845aaacc16f01178f33349e7c992ecd0cee095aa5e577f0f4dee35971bd36455"
|
||||
"checksum either 1.5.3 (registry+https://github.com/rust-lang/crates.io-index)" = "bb1f6b1ce1c140482ea30ddd3335fc0024ac7ee112895426e0a629a6c20adfe3"
|
||||
"checksum 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"
|
||||
@@ -2930,7 +2888,7 @@ dependencies = [
|
||||
"checksum fallible-iterator 0.2.0 (registry+https://github.com/rust-lang/crates.io-index)" = "4443176a9f2c162692bd3d352d745ef9413eec5782a80d8fd6f8a1ac692a07f7"
|
||||
"checksum fallible-streaming-iterator 0.1.9 (registry+https://github.com/rust-lang/crates.io-index)" = "7360491ce676a36bf9bb3c56c1aa791658183a54d2744120f27285738d90465a"
|
||||
"checksum fast_chemail 0.9.6 (registry+https://github.com/rust-lang/crates.io-index)" = "495a39d30d624c2caabe6312bfead73e7717692b44e0b32df168c275a2e8e9e4"
|
||||
"checksum flate2 1.0.11 (registry+https://github.com/rust-lang/crates.io-index)" = "2adaffba6388640136149e18ed080b77a78611c1e1d6de75aedcdf78df5d4682"
|
||||
"checksum flate2 1.0.12 (registry+https://github.com/rust-lang/crates.io-index)" = "ad3c5233c9a940c8719031b423d7e6c16af66e031cb0420b0896f5245bf181d3"
|
||||
"checksum fnv 1.0.6 (registry+https://github.com/rust-lang/crates.io-index)" = "2fad85553e09a6f881f739c29f0b00b0f01357c743266d478b68951ce23285f3"
|
||||
"checksum foreign-types 0.3.2 (registry+https://github.com/rust-lang/crates.io-index)" = "f6f339eb8adc052cd2ca78910fda869aefa38d22d5cb648e6485e4d3fc06f3b1"
|
||||
"checksum foreign-types-shared 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)" = "00b0228411908ca8685dba7fc2cdd70ec9990a6e753e89b6ac91a84c40fbaf4b"
|
||||
@@ -2946,6 +2904,7 @@ dependencies = [
|
||||
"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 hex 0.3.2 (registry+https://github.com/rust-lang/crates.io-index)" = "805026a5d0141ffc30abb3be3173848ad46a1b1664fe632428479619a3644d77"
|
||||
"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.18 (registry+https://github.com/rust-lang/crates.io-index)" = "372bcb56f939e449117fb0869c2e8fd8753a8223d92a172c6e808cf123a5b6e4"
|
||||
"checksum http-body 0.1.0 (registry+https://github.com/rust-lang/crates.io-index)" = "6741c859c1b2463a423a1dbce98d418e6c3c3fc720fb0d45528657320920292d"
|
||||
@@ -2961,19 +2920,18 @@ dependencies = [
|
||||
"checksum imap 1.0.2 (git+https://github.com/jonhoo/rust-imap?rev=281d2eb8ab50dc656ceff2ae749ca5045f334e15)" = "<none>"
|
||||
"checksum imap-proto 0.9.0 (registry+https://github.com/rust-lang/crates.io-index)" = "1b92ca529b24c5f80a950abe993d3883df6fe6791d4a46b1fda1eb339796c589"
|
||||
"checksum indexmap 1.2.0 (registry+https://github.com/rust-lang/crates.io-index)" = "a61202fbe46c4a951e9404a720a0180bcf3212c750d735cb5c4ba4dc551299f3"
|
||||
"checksum iovec 0.1.2 (registry+https://github.com/rust-lang/crates.io-index)" = "dbe6e417e7d0975db6512b90796e8ce223145ac4e33c377e4a42882a0e88bb08"
|
||||
"checksum iovec 0.1.4 (registry+https://github.com/rust-lang/crates.io-index)" = "b2b3ea6ff95e175473f8ffe6a7eb7c00d054240321b84c57051175fe3c1e075e"
|
||||
"checksum itertools 0.8.0 (registry+https://github.com/rust-lang/crates.io-index)" = "5b8467d9c1cebe26feb08c640139247fac215782d35371ade9a2136ed6085358"
|
||||
"checksum itoa 0.4.4 (registry+https://github.com/rust-lang/crates.io-index)" = "501266b7edd0174f8530248f87f99c88fbe60ca4ef3dd486835b8d8d53136f7f"
|
||||
"checksum jetscii 0.4.4 (registry+https://github.com/rust-lang/crates.io-index)" = "5f25cca2463cb19dbb1061eb3bd38a8b5e4ce1cc5a5a9fc0e02de486d92b9b05"
|
||||
"checksum keccak 0.1.0 (registry+https://github.com/rust-lang/crates.io-index)" = "67c21572b4949434e4fc1e1978b99c5f77064153c59d998bf13ecd96fb5ecba7"
|
||||
"checksum kernel32-sys 0.2.2 (registry+https://github.com/rust-lang/crates.io-index)" = "7507624b29483431c0ba2d82aece8ca6cdba9382bff4ddd0f7490560c056098d"
|
||||
"checksum lazy_static 1.4.0 (registry+https://github.com/rust-lang/crates.io-index)" = "e2abad23fbc42b3700f2f279844dc832adb2b2eb069b2df918f455c4e18cc646"
|
||||
"checksum lettre 0.9.2 (registry+https://github.com/rust-lang/crates.io-index)" = "c66afaa5dfadbb81d4e00fd1d1ab057c7cd4c799c5a44e0009386d553587e728"
|
||||
"checksum lettre 0.9.2 (git+https://github.com/deltachat/lettre)" = "<none>"
|
||||
"checksum lexical-core 0.4.6 (registry+https://github.com/rust-lang/crates.io-index)" = "2304bccb228c4b020f3a4835d247df0a02a7c4686098d4167762cfbbe4c5cb14"
|
||||
"checksum libc 0.2.62 (registry+https://github.com/rust-lang/crates.io-index)" = "34fcd2c08d2f832f376f4173a231990fa5aef4e99fb569867318a227ef4c06ba"
|
||||
"checksum libsqlite3-sys 0.16.0 (registry+https://github.com/rust-lang/crates.io-index)" = "5e5b95e89c330291768dc840238db7f9e204fd208511ab6319b56193a7f2ae25"
|
||||
"checksum linked-hash-map 0.5.2 (registry+https://github.com/rust-lang/crates.io-index)" = "ae91b68aebc4ddb91978b11a1b02ddd8602a05ec19002801c5666000e05e0f83"
|
||||
"checksum lock_api 0.1.5 (registry+https://github.com/rust-lang/crates.io-index)" = "62ebf1391f6acad60e5c8b43706dde4582df75c06698ab44511d15016bc2442c"
|
||||
"checksum lock_api 0.2.0 (registry+https://github.com/rust-lang/crates.io-index)" = "ed946d4529956a20f2d63ebe1b69996d5a2137c91913fe3ebbeff957f5bca7ff"
|
||||
"checksum lock_api 0.3.1 (registry+https://github.com/rust-lang/crates.io-index)" = "f8912e782533a93a167888781b836336a6ca5da6175c05944c86cf28c31104dc"
|
||||
"checksum log 0.4.8 (registry+https://github.com/rust-lang/crates.io-index)" = "14b6052be84e6b71ab17edffc2eeabf5c2c3ae1fdb464aae35ac50c67a44e1f7"
|
||||
@@ -2986,13 +2944,13 @@ dependencies = [
|
||||
"checksum memoffset 0.5.1 (registry+https://github.com/rust-lang/crates.io-index)" = "ce6075db033bbbb7ee5a0bbd3a3186bbae616f57fb001c485c7ff77955f8177f"
|
||||
"checksum mime 0.3.14 (registry+https://github.com/rust-lang/crates.io-index)" = "dd1d63acd1b78403cc0c325605908475dd9b9a3acbf65ed8bcab97e27014afcf"
|
||||
"checksum mime_guess 2.0.1 (registry+https://github.com/rust-lang/crates.io-index)" = "1a0ed03949aef72dbdf3116a383d7b38b4768e6f960528cd6a6044aa9ed68599"
|
||||
"checksum miniz_oxide 0.3.2 (registry+https://github.com/rust-lang/crates.io-index)" = "7108aff85b876d06f22503dcce091e29f76733b2bfdd91eebce81f5e68203a10"
|
||||
"checksum miniz_oxide 0.3.3 (registry+https://github.com/rust-lang/crates.io-index)" = "304f66c19be2afa56530fa7c39796192eef38618da8d19df725ad7c6d6b2aaae"
|
||||
"checksum mio 0.6.19 (registry+https://github.com/rust-lang/crates.io-index)" = "83f51996a3ed004ef184e16818edc51fadffe8e7ca68be67f9dee67d84d0ff23"
|
||||
"checksum miow 0.2.1 (registry+https://github.com/rust-lang/crates.io-index)" = "8c1f2f3b1cf331de6896aabf6e9d55dca90356cc9960cca7eaaf408a355ae919"
|
||||
"checksum native-tls 0.2.3 (registry+https://github.com/rust-lang/crates.io-index)" = "4b2df1a4c22fd44a62147fd8f13dd0f95c9d8ca7b2610299b2a2f9cf8964274e"
|
||||
"checksum net2 0.2.33 (registry+https://github.com/rust-lang/crates.io-index)" = "42550d9fb7b6684a6d404d9fa7250c2eb2646df731d1c06afc06dcee9e1bcf88"
|
||||
"checksum nix 0.13.1 (registry+https://github.com/rust-lang/crates.io-index)" = "4dbdc256eaac2e3bd236d93ad999d3479ef775c863dbda3068c4006a92eec51b"
|
||||
"checksum nodrop 0.1.13 (registry+https://github.com/rust-lang/crates.io-index)" = "2f9667ddcc6cc8a43afc9b7917599d7216aa09c463919ea32c59ed6cac8bc945"
|
||||
"checksum nodrop 0.1.14 (registry+https://github.com/rust-lang/crates.io-index)" = "72ef4a56884ca558e5ddb05a1d1e7e1bfd9a68d9ed024c21704cc98872dae1bb"
|
||||
"checksum nom 4.2.3 (registry+https://github.com/rust-lang/crates.io-index)" = "2ad2a91a8e869eeb30b9cb3119ae87773a8f4ae617f41b1eb9c154b2905f7bd6"
|
||||
"checksum nom 5.0.1 (registry+https://github.com/rust-lang/crates.io-index)" = "c618b63422da4401283884e6668d39f819a106ef51f5f59b81add00075da35ca"
|
||||
"checksum num-bigint-dig 0.4.0 (registry+https://github.com/rust-lang/crates.io-index)" = "3cd60678022301da54082fcc383647fc895cba2795f868c871d58d29c8922595"
|
||||
@@ -3003,29 +2961,26 @@ dependencies = [
|
||||
"checksum num-traits 0.2.8 (registry+https://github.com/rust-lang/crates.io-index)" = "6ba9a427cfca2be13aa6f6403b0b7e7368fe982bfa16fccc450ce74c46cd9b32"
|
||||
"checksum num_cpus 1.10.1 (registry+https://github.com/rust-lang/crates.io-index)" = "bcef43580c035376c0705c42792c294b66974abbfd2789b511784023f71f3273"
|
||||
"checksum opaque-debug 0.2.3 (registry+https://github.com/rust-lang/crates.io-index)" = "2839e79665f131bdb5782e51f2c6c9599c133c6098982a54c794358bf432529c"
|
||||
"checksum openssl 0.10.24 (registry+https://github.com/rust-lang/crates.io-index)" = "8152bb5a9b5b721538462336e3bef9a539f892715e5037fda0f984577311af15"
|
||||
"checksum openssl 0.10.25 (registry+https://github.com/rust-lang/crates.io-index)" = "2f372b2b53ce10fb823a337aaa674e3a7d072b957c6264d0f4ff0bd86e657449"
|
||||
"checksum openssl-probe 0.1.2 (registry+https://github.com/rust-lang/crates.io-index)" = "77af24da69f9d9341038eba93a073b1fdaaa1b788221b00a69bce9e762cb32de"
|
||||
"checksum openssl-src 111.6.0+1.1.1d (registry+https://github.com/rust-lang/crates.io-index)" = "b9c2da1de8a7a3f860919c01540b03a6db16de042405a8a07a5e9d0b4b825d9c"
|
||||
"checksum openssl-sys 0.9.49 (registry+https://github.com/rust-lang/crates.io-index)" = "f4fad9e54bd23bd4cbbe48fdc08a1b8091707ac869ef8508edea2fec77dcc884"
|
||||
"checksum openssl-sys 0.9.50 (registry+https://github.com/rust-lang/crates.io-index)" = "2c42dcccb832556b5926bc9ae61e8775f2a61e725ab07ab3d1e7fcf8ae62c3b6"
|
||||
"checksum os_type 2.2.0 (registry+https://github.com/rust-lang/crates.io-index)" = "7edc011af0ae98b7f88cf7e4a83b70a54a75d2b8cb013d6efd02e5956207e9eb"
|
||||
"checksum output_vt100 0.1.2 (registry+https://github.com/rust-lang/crates.io-index)" = "53cdc5b785b7a58c5aad8216b3dfa114df64b0b06ae6e1501cef91df2fbdf8f9"
|
||||
"checksum owning_ref 0.4.0 (registry+https://github.com/rust-lang/crates.io-index)" = "49a4b8ea2179e6a2e27411d3bca09ca6dd630821cf6894c6c7c8467a8ee7ef13"
|
||||
"checksum packed_simd 0.3.3 (registry+https://github.com/rust-lang/crates.io-index)" = "a85ea9fc0d4ac0deb6fe7911d38786b32fc11119afd9e9d38b84ff691ce64220"
|
||||
"checksum parking_lot 0.7.1 (registry+https://github.com/rust-lang/crates.io-index)" = "ab41b4aed082705d1056416ae4468b6ea99d52599ecf3169b00088d43113e337"
|
||||
"checksum parking_lot 0.8.0 (registry+https://github.com/rust-lang/crates.io-index)" = "fa7767817701cce701d5585b9c4db3cdd02086398322c1d7e8bf5094a96a2ce7"
|
||||
"checksum parking_lot 0.9.0 (registry+https://github.com/rust-lang/crates.io-index)" = "f842b1982eb6c2fe34036a4fbfb06dd185a3f5c8edfaacdf7d1ea10b07de6252"
|
||||
"checksum parking_lot_core 0.4.0 (registry+https://github.com/rust-lang/crates.io-index)" = "94c8c7923936b28d546dfd14d4472eaf34c99b14e1c973a32b3e6d4eb04298c9"
|
||||
"checksum parking_lot_core 0.5.0 (registry+https://github.com/rust-lang/crates.io-index)" = "cb88cb1cb3790baa6776844f968fea3be44956cf184fa1be5a03341f5491278c"
|
||||
"checksum parking_lot_core 0.6.2 (registry+https://github.com/rust-lang/crates.io-index)" = "b876b1b9e7ac6e1a74a6da34d25c42e17e8862aa409cbbbdcfc8d86c6f3bc62b"
|
||||
"checksum percent-encoding 1.0.1 (registry+https://github.com/rust-lang/crates.io-index)" = "31010dd2e1ac33d5b46a5b413495239882813e0369f8ed8a5e266f173602f831"
|
||||
"checksum percent-encoding 2.1.0 (registry+https://github.com/rust-lang/crates.io-index)" = "d4fd5641d01c8f18a23da7b6fe29298ff4b55afcccdf78973b24cf3175fee32e"
|
||||
"checksum pgp 0.2.1 (registry+https://github.com/rust-lang/crates.io-index)" = "bb80b37b7debf9a98dc0caca3ed40ddf1d383691208763d0458df0b91521020f"
|
||||
"checksum pgp 0.2.3 (registry+https://github.com/rust-lang/crates.io-index)" = "1c4b0aed94774465356a8cb00525c7312c465072293ff53ed7b39f4f9cbefb41"
|
||||
"checksum pkg-config 0.3.16 (registry+https://github.com/rust-lang/crates.io-index)" = "72d5370d90f49f70bd033c3d75e87fc529fbfff9d6f7cccef07d6170079d91ea"
|
||||
"checksum ppv-lite86 0.2.5 (registry+https://github.com/rust-lang/crates.io-index)" = "e3cbf9f658cdb5000fcf6f362b8ea2ba154b9f146a61c7a20d647034c6b6561b"
|
||||
"checksum pretty_assertions 0.6.1 (registry+https://github.com/rust-lang/crates.io-index)" = "3f81e1644e1b54f5a68959a29aa86cde704219254669da328ecfdf6a1f09d427"
|
||||
"checksum pretty_env_logger 0.3.1 (registry+https://github.com/rust-lang/crates.io-index)" = "717ee476b1690853d222af4634056d830b5197ffd747726a9a1eee6da9f49074"
|
||||
"checksum proc-macro2 0.4.30 (registry+https://github.com/rust-lang/crates.io-index)" = "cf3d2011ab5c909338f7887f4fc896d35932e29146c12c8d01da6b22a80ba759"
|
||||
"checksum proc-macro2 1.0.4 (registry+https://github.com/rust-lang/crates.io-index)" = "afdc77cc74ec70ed262262942ebb7dac3d479e9e5cfa2da1841c0806f6cdabcc"
|
||||
"checksum proc-macro2 1.0.5 (registry+https://github.com/rust-lang/crates.io-index)" = "90cf5f418035b98e655e9cdb225047638296b862b42411c4e45bb88d700f7fc0"
|
||||
"checksum proptest 0.9.4 (registry+https://github.com/rust-lang/crates.io-index)" = "cf147e022eacf0c8a054ab864914a7602618adba841d800a9a9868a5237a529f"
|
||||
"checksum publicsuffix 1.5.3 (registry+https://github.com/rust-lang/crates.io-index)" = "9bf259a81de2b2eb9850ec990ec78e6a25319715584fd7652b9b26f96fcb1510"
|
||||
"checksum pulldown-cmark 0.2.0 (registry+https://github.com/rust-lang/crates.io-index)" = "eef52fac62d0ea7b9b4dc7da092aa64ea7ec3d90af6679422d3d7e0e14b6ee15"
|
||||
@@ -3057,7 +3012,7 @@ dependencies = [
|
||||
"checksum regex 1.3.1 (registry+https://github.com/rust-lang/crates.io-index)" = "dc220bd33bdce8f093101afe22a037b8eb0e5af33592e6a9caafff0d4cb81cbd"
|
||||
"checksum regex-syntax 0.6.12 (registry+https://github.com/rust-lang/crates.io-index)" = "11a7e20d1cce64ef2fed88b66d347f88bd9babb82845b2b858f3edbf59a4f716"
|
||||
"checksum remove_dir_all 0.5.2 (registry+https://github.com/rust-lang/crates.io-index)" = "4a83fa3702a688b9359eccba92d153ac33fd2e8462f9e0e3fdf155239ea7792e"
|
||||
"checksum reqwest 0.9.20 (registry+https://github.com/rust-lang/crates.io-index)" = "0f6d896143a583047512e59ac54a215cb203c29cc941917343edea3be8df9c78"
|
||||
"checksum reqwest 0.9.21 (registry+https://github.com/rust-lang/crates.io-index)" = "02b7e953e14c6f3102b7e8d1f1ee3abf5ecee80b427f5565c9389835cecae95c"
|
||||
"checksum ripemd160 0.8.0 (registry+https://github.com/rust-lang/crates.io-index)" = "ad5112e0dbbb87577bfbc56c42450235e3012ce336e29c5befd7807bd626da4a"
|
||||
"checksum rsa 0.1.3 (registry+https://github.com/rust-lang/crates.io-index)" = "6ad8d3632f6745bb671c8637e2aa44015537c5e384789d2ea3235739301ed1e0"
|
||||
"checksum rusqlite 0.20.0 (registry+https://github.com/rust-lang/crates.io-index)" = "2a194373ef527035645a1bc21b10dc2125f73497e6e155771233eb187aedd051"
|
||||
@@ -3071,7 +3026,6 @@ dependencies = [
|
||||
"checksum same-file 1.0.5 (registry+https://github.com/rust-lang/crates.io-index)" = "585e8ddcedc187886a30fa705c47985c3fa88d06624095856b36ca0b82ff4421"
|
||||
"checksum schannel 0.1.16 (registry+https://github.com/rust-lang/crates.io-index)" = "87f550b06b6cba9c8b8be3ee73f391990116bf527450d2556e9b9ce263b9a021"
|
||||
"checksum scheduled-thread-pool 0.2.2 (registry+https://github.com/rust-lang/crates.io-index)" = "bd07742e081ff6c077f5f6b283f12f32b9e7cc765b316160d66289b74546fbb3"
|
||||
"checksum scopeguard 0.3.3 (registry+https://github.com/rust-lang/crates.io-index)" = "94258f53601af11e6a49f722422f6e3425c52b06245a5cf9bc09908b174f5e27"
|
||||
"checksum scopeguard 1.0.0 (registry+https://github.com/rust-lang/crates.io-index)" = "b42e15e59b18a828bbf5c58ea01debb36b9b096346de35d941dcb89009f24a0d"
|
||||
"checksum security-framework 0.3.1 (registry+https://github.com/rust-lang/crates.io-index)" = "eee63d0f4a9ec776eeb30e220f0bc1e092c3ad744b2a379e3993070364d3adc2"
|
||||
"checksum security-framework-sys 0.3.1 (registry+https://github.com/rust-lang/crates.io-index)" = "9636f8989cbf61385ae4824b98c1aaa54c994d7d8b41f11c601ed799f0549a56"
|
||||
@@ -3079,7 +3033,7 @@ dependencies = [
|
||||
"checksum semver-parser 0.7.0 (registry+https://github.com/rust-lang/crates.io-index)" = "388a1df253eca08550bef6c72392cfe7c30914bf41df5269b68cbd6ff8f570a3"
|
||||
"checksum serde 1.0.101 (registry+https://github.com/rust-lang/crates.io-index)" = "9796c9b7ba2ffe7a9ce53c2287dfc48080f4b2b362fcc245a259b3a7201119dd"
|
||||
"checksum serde_derive 1.0.101 (registry+https://github.com/rust-lang/crates.io-index)" = "4b133a43a1ecd55d4086bd5b4dc6c1751c68b1bfbeba7a5040442022c7e7c02e"
|
||||
"checksum serde_json 1.0.40 (registry+https://github.com/rust-lang/crates.io-index)" = "051c49229f282f7c6f3813f8286cc1e3323e8051823fce42c7ea80fe13521704"
|
||||
"checksum serde_json 1.0.41 (registry+https://github.com/rust-lang/crates.io-index)" = "2f72eb2a68a7dc3f9a691bfda9305a1c017a6215e5a4545c258500d2099a37c2"
|
||||
"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"
|
||||
"checksum sha2 0.8.0 (registry+https://github.com/rust-lang/crates.io-index)" = "7b4d8bfd0e469f417657573d8451fb33d16cfe0989359b93baf3a1ffc639543d"
|
||||
@@ -3088,14 +3042,13 @@ 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.10 (registry+https://github.com/rust-lang/crates.io-index)" = "ab606a9c5e214920bb66c458cd7be8ef094f813f20fe77a54cc7dbfff220d4b7"
|
||||
"checksum stable_deref_trait 1.1.1 (registry+https://github.com/rust-lang/crates.io-index)" = "dba1a27d3efae4351c8051072d619e3ade2820635c3958d826bfea39d59b54c8"
|
||||
"checksum static_assertions 0.3.4 (registry+https://github.com/rust-lang/crates.io-index)" = "7f3eb36b47e512f8f1c9e3d10c2c1965bc992bd9cdb024fa581e2194501c83d3"
|
||||
"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.7.0 (registry+https://github.com/rust-lang/crates.io-index)" = "bb4f380125926a99e52bc279241539c018323fab05ad6368b56f93d9369ff550"
|
||||
"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.1.1 (registry+https://github.com/rust-lang/crates.io-index)" = "01f40907d9ffc762709e4ff3eb4a6f6b41b650375a3f09ac92b641942b7fb082"
|
||||
"checksum subtle 2.2.1 (registry+https://github.com/rust-lang/crates.io-index)" = "ab3af2eb31c42e8f0ccf43548232556c42737e01a96db6e1777b0be108e79799"
|
||||
"checksum syn 0.11.11 (registry+https://github.com/rust-lang/crates.io-index)" = "d3b891b9015c88c576343b9b3e41c2c11a51c219ef067b264bd9c8aa9b441dad"
|
||||
"checksum syn 0.14.9 (registry+https://github.com/rust-lang/crates.io-index)" = "261ae9ecaa397c42b960649561949d69311f08eeaea86a65696e6e46517cf741"
|
||||
"checksum syn 0.15.44 (registry+https://github.com/rust-lang/crates.io-index)" = "9ca4b3b69a77cbe1ffc9e198781b7acb0c7365a883670e8f1c1bc66fba79a5c5"
|
||||
@@ -3114,10 +3067,10 @@ dependencies = [
|
||||
"checksum tokio-current-thread 0.1.6 (registry+https://github.com/rust-lang/crates.io-index)" = "d16217cad7f1b840c5a97dfb3c43b0c871fef423a6e8d2118c604e843662a443"
|
||||
"checksum tokio-executor 0.1.8 (registry+https://github.com/rust-lang/crates.io-index)" = "0f27ee0e6db01c5f0b2973824547ce7e637b2ed79b891a9677b0de9bd532b6ac"
|
||||
"checksum tokio-io 0.1.12 (registry+https://github.com/rust-lang/crates.io-index)" = "5090db468dad16e1a7a54c8c67280c5e4b544f3d3e018f0b913b400261f85926"
|
||||
"checksum tokio-reactor 0.1.9 (registry+https://github.com/rust-lang/crates.io-index)" = "6af16bfac7e112bea8b0442542161bfc41cbfa4466b580bdda7d18cb88b911ce"
|
||||
"checksum tokio-reactor 0.1.10 (registry+https://github.com/rust-lang/crates.io-index)" = "c56391be9805bc80163151c0b9e5164ee64f4b0200962c346fea12773158f22d"
|
||||
"checksum tokio-sync 0.1.6 (registry+https://github.com/rust-lang/crates.io-index)" = "2162248ff317e2bc713b261f242b69dbb838b85248ed20bb21df56d60ea4cae7"
|
||||
"checksum tokio-tcp 0.1.3 (registry+https://github.com/rust-lang/crates.io-index)" = "1d14b10654be682ac43efee27401d792507e30fd8d26389e1da3b185de2e4119"
|
||||
"checksum tokio-threadpool 0.1.15 (registry+https://github.com/rust-lang/crates.io-index)" = "90ca01319dea1e376a001e8dc192d42ebde6dd532532a5bad988ac37db365b19"
|
||||
"checksum tokio-threadpool 0.1.16 (registry+https://github.com/rust-lang/crates.io-index)" = "2bd2c6a3885302581f4401c82af70d792bb9df1700e7437b0aeb4ada94d5388c"
|
||||
"checksum tokio-timer 0.2.11 (registry+https://github.com/rust-lang/crates.io-index)" = "f2106812d500ed25a4f38235b9cae8f78a09edf43203e16e59c3b769a342a60e"
|
||||
"checksum toml 0.4.10 (registry+https://github.com/rust-lang/crates.io-index)" = "758664fc71a3a69038656bee8b6be6477d2a6c315a6b81f7081f591bffa4111f"
|
||||
"checksum traitobject 0.1.0 (registry+https://github.com/rust-lang/crates.io-index)" = "efd1f82c56340fdf16f2a953d7bda4f8fdffba13d93b00844c25572110b26079"
|
||||
|
||||
@@ -10,7 +10,7 @@ deltachat_derive = { path = "./deltachat_derive" }
|
||||
mmime = { version = "0.1.2", path = "./mmime" }
|
||||
|
||||
libc = "0.2.51"
|
||||
pgp = { version = "0.2", default-features = false }
|
||||
pgp = "0.2.3"
|
||||
hex = "0.3.2"
|
||||
sha2 = "0.8.0"
|
||||
rand = "0.6.5"
|
||||
@@ -19,7 +19,7 @@ reqwest = "0.9.15"
|
||||
num-derive = "0.2.5"
|
||||
num-traits = "0.2.6"
|
||||
native-tls = "0.2.3"
|
||||
lettre = "0.9.0"
|
||||
lettre = { git = "https://github.com/deltachat/lettre" }
|
||||
imap = { git = "https://github.com/jonhoo/rust-imap", rev = "281d2eb8ab50dc656ceff2ae749ca5045f334e15" }
|
||||
base64 = "0.10"
|
||||
charset = "0.1"
|
||||
|
||||
@@ -7,9 +7,9 @@ fi
|
||||
|
||||
set -xe
|
||||
|
||||
#DOXYDOCDIR=${1:?directory where doxygen docs to be found}
|
||||
PYDOCDIR=${1:?directory with python docs}
|
||||
WHEELHOUSEDIR=${2:?directory with pre-built wheels}
|
||||
DOXYDOCDIR=${3:?directory where doxygen docs to be found}
|
||||
|
||||
export BRANCH=${CIRCLE_BRANCH:?specify branch for uploading purposes}
|
||||
|
||||
@@ -22,10 +22,11 @@ rsync -avz \
|
||||
delta@py.delta.chat:build/${BRANCH}
|
||||
|
||||
# C docs to c.delta.chat
|
||||
#rsync -avz \
|
||||
# -e "ssh -o StrictHostKeyChecking=no -o UserKnownHostsFile=/dev/null" \
|
||||
# "$DOXYDOCDIR/html/" \
|
||||
# delta@py.delta.chat:build-c/${BRANCH}
|
||||
ssh -o StrictHostKeyChecking=no -o UserKnownHostsFile=/dev/null delta@c.delta.chat mkdir -p build-c/${BRANCH}
|
||||
rsync -avz \
|
||||
-e "ssh -o StrictHostKeyChecking=no -o UserKnownHostsFile=/dev/null" \
|
||||
"$DOXYDOCDIR/html/" \
|
||||
delta@c.delta.chat:build-c/${BRANCH}
|
||||
|
||||
echo -----------------------
|
||||
echo upload wheels
|
||||
|
||||
7
ci_scripts/run-doxygen.sh
Executable file
7
ci_scripts/run-doxygen.sh
Executable file
@@ -0,0 +1,7 @@
|
||||
#!/usr/bin/env bash
|
||||
|
||||
set -ex
|
||||
|
||||
cd deltachat-ffi
|
||||
doxygen
|
||||
|
||||
@@ -402,6 +402,18 @@ int dc_set_config (dc_context_t* context, const char*
|
||||
*/
|
||||
char* dc_get_config (dc_context_t* context, const char* key);
|
||||
|
||||
/**
|
||||
* Set stock string translation.
|
||||
*
|
||||
* The function will emit warnings if it returns an error state.
|
||||
*
|
||||
* @param context The context object
|
||||
* @param stock_id the integer id of the stock message (DC_STR_*)
|
||||
* @param stock_msg the message to be used
|
||||
* @return int (==0 on error, 1 on success)
|
||||
*/
|
||||
int dc_set_stock_translation(dc_context_t* context, uint32_t, const char* value);
|
||||
|
||||
|
||||
/**
|
||||
* Get information about the context.
|
||||
@@ -3719,6 +3731,14 @@ int64_t dc_lot_get_timestamp (const dc_lot_t* lot);
|
||||
#define DC_MSG_GIF 21
|
||||
|
||||
|
||||
/**
|
||||
* Message containing a sticker, similar to image.
|
||||
* If possible, the ui should display the image without borders in a transparent way.
|
||||
* A click on a sticker will offer to install the sticker set in some future.
|
||||
*/
|
||||
#define DC_MSG_STICKER 23
|
||||
|
||||
|
||||
/**
|
||||
* Message containing an Audio file.
|
||||
* File and duration are set via dc_msg_set_file(), dc_msg_set_duration()
|
||||
@@ -3896,6 +3916,45 @@ int64_t dc_lot_get_timestamp (const dc_lot_t* lot);
|
||||
*/
|
||||
#define DC_EVENT_SMTP_MESSAGE_SENT 103
|
||||
|
||||
/**
|
||||
* Emitted when a message was successfully marked as deleted on the IMAP server.
|
||||
*
|
||||
* @param data1 0
|
||||
* @param data2 (const char*) Info string in english language.
|
||||
* Must not be free()'d or modified and is valid only until the callback returns.
|
||||
* @return 0
|
||||
*/
|
||||
#define DC_EVENT_IMAP_MESSAGE_DELETED 104
|
||||
|
||||
/**
|
||||
* Emitted when a message was successfully moved on IMAP.
|
||||
*
|
||||
* @param data1 0
|
||||
* @param data2 (const char*) Info string in english language.
|
||||
* Must not be free()'d or modified and is valid only until the callback returns.
|
||||
* @return 0
|
||||
*/
|
||||
#define DC_EVENT_IMAP_MESSAGE_MOVED 105
|
||||
|
||||
/**
|
||||
* Emitted when a new blob file was successfully written
|
||||
*
|
||||
* @param data1 0
|
||||
* @param data2 (const char*) path name
|
||||
* Must not be free()'d or modified and is valid only until the callback returns.
|
||||
* @return 0
|
||||
*/
|
||||
#define DC_EVENT_NEW_BLOB_FILE 150
|
||||
|
||||
/**
|
||||
* Emitted when a blob file was successfully deleted
|
||||
*
|
||||
* @param data1 0
|
||||
* @param data2 (const char*) path name
|
||||
* Must not be free()'d or modified and is valid only until the callback returns.
|
||||
* @return 0
|
||||
*/
|
||||
#define DC_EVENT_DELETED_BLOB_FILE 151
|
||||
|
||||
/**
|
||||
* The library-user should write a warning string to the log.
|
||||
@@ -4142,16 +4201,10 @@ int64_t dc_lot_get_timestamp (const dc_lot_t* lot);
|
||||
|
||||
|
||||
/**
|
||||
* Requeste a localized string from the frontend.
|
||||
* (DEPRECATED, DC_EVENT_GET_STRING is not emmitted anymore. Use
|
||||
* dc_set_stock_translation() to pre-fill translations for stock
|
||||
* messages.
|
||||
*
|
||||
* @param data1 (int) ID of the string to request, one of the DC_STR_* constants.
|
||||
* @param data2 (int) The count. If the requested string contains a placeholder for a numeric value,
|
||||
* the ui may use this value to return different strings on different plural forms.
|
||||
* @return (const char*) Null-terminated UTF-8 string.
|
||||
* The string will be free()'d by the core,
|
||||
* so it must be allocated using malloc() or a compatible function.
|
||||
* Return 0 if the ui cannot provide the requested string
|
||||
* the core will use a default string in english language then.
|
||||
*/
|
||||
#define DC_EVENT_GET_STRING 2091
|
||||
|
||||
@@ -4269,7 +4322,8 @@ void dc_array_add_id (dc_array_t*, uint32_t); // depreca
|
||||
#define DC_STR_MSGLOCATIONENABLED 64
|
||||
#define DC_STR_MSGLOCATIONDISABLED 65
|
||||
#define DC_STR_LOCATION 66
|
||||
#define DC_STR_COUNT 66
|
||||
#define DC_STR_STICKER 67
|
||||
#define DC_STR_COUNT 67
|
||||
|
||||
void dc_str_unref (char*);
|
||||
|
||||
|
||||
@@ -24,9 +24,8 @@ use num_traits::{FromPrimitive, ToPrimitive};
|
||||
|
||||
use deltachat::contact::Contact;
|
||||
use deltachat::context::Context;
|
||||
use deltachat::dc_tools::{
|
||||
as_path, as_str, dc_strdup, to_string, to_string_lossy, OsStrExt, StrExt,
|
||||
};
|
||||
use deltachat::dc_tools::{as_path, as_str, dc_strdup, to_string_lossy, OsStrExt, StrExt};
|
||||
use deltachat::stock::StockMessage;
|
||||
use deltachat::*;
|
||||
|
||||
// as C lacks a good and portable error handling,
|
||||
@@ -125,11 +124,15 @@ impl ContextWrapper {
|
||||
| Event::SmtpConnected(msg)
|
||||
| Event::ImapConnected(msg)
|
||||
| Event::SmtpMessageSent(msg)
|
||||
| Event::ImapMessageDeleted(msg)
|
||||
| Event::ImapMessageMoved(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();
|
||||
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 }
|
||||
@@ -148,7 +151,7 @@ impl ContextWrapper {
|
||||
ffi_cb(self, event_id, progress as uintptr_t, 0)
|
||||
}
|
||||
Event::ImexFileWritten(file) => {
|
||||
let data1 = file.to_c_string().unwrap();
|
||||
let data1 = file.to_c_string().unwrap_or_default();
|
||||
ffi_cb(self, event_id, data1.as_ptr() as uintptr_t, 0)
|
||||
}
|
||||
Event::SecurejoinInviterProgress {
|
||||
@@ -164,12 +167,6 @@ impl ContextWrapper {
|
||||
contact_id as uintptr_t,
|
||||
progress as uintptr_t,
|
||||
),
|
||||
Event::GetString { id, count } => ffi_cb(
|
||||
self,
|
||||
event_id,
|
||||
id.to_u32().unwrap_or_default() as uintptr_t,
|
||||
count as uintptr_t,
|
||||
),
|
||||
}
|
||||
}
|
||||
None => 0,
|
||||
@@ -340,6 +337,35 @@ pub unsafe extern "C" fn dc_get_config(
|
||||
}
|
||||
}
|
||||
|
||||
#[no_mangle]
|
||||
pub unsafe extern "C" fn dc_set_stock_translation(
|
||||
context: *mut dc_context_t,
|
||||
stock_id: u32,
|
||||
stock_msg: *mut libc::c_char,
|
||||
) -> libc::c_int {
|
||||
if context.is_null() || stock_msg.is_null() {
|
||||
eprintln!("ignoring careless call to dc_set_stock_string");
|
||||
return 0;
|
||||
}
|
||||
let msg = as_str(stock_msg).to_string();
|
||||
let ffi_context = &*context;
|
||||
ffi_context
|
||||
.with_inner(|ctx| match StockMessage::from_u32(stock_id) {
|
||||
Some(id) => match ctx.set_stock_translation(id, msg) {
|
||||
Ok(()) => 1,
|
||||
Err(err) => {
|
||||
warn!(ctx, "set_stock_translation failed: {}", err);
|
||||
0
|
||||
}
|
||||
},
|
||||
None => {
|
||||
warn!(ctx, "invalid stock message id {}", stock_id);
|
||||
0
|
||||
}
|
||||
})
|
||||
.unwrap_or(0)
|
||||
}
|
||||
|
||||
#[no_mangle]
|
||||
pub unsafe extern "C" fn dc_get_info(context: *mut dc_context_t) -> *mut libc::c_char {
|
||||
if context.is_null() {
|
||||
@@ -377,8 +403,8 @@ pub unsafe extern "C" fn dc_get_oauth2_url(
|
||||
return ptr::null_mut(); // NULL explicitly defined as "unknown"
|
||||
}
|
||||
let ffi_context = &*context;
|
||||
let addr = to_string(addr);
|
||||
let redirect = to_string(redirect);
|
||||
let addr = to_string_lossy(addr);
|
||||
let redirect = to_string_lossy(redirect);
|
||||
ffi_context
|
||||
.with_inner(|ctx| match oauth2::dc_get_oauth2_url(ctx, addr, redirect) {
|
||||
Some(res) => res.strdup(),
|
||||
@@ -1245,8 +1271,11 @@ pub unsafe extern "C" fn dc_forward_msgs(
|
||||
|
||||
let ffi_context = &*context;
|
||||
ffi_context
|
||||
.with_inner(|ctx| chat::forward_msgs(ctx, ids, chat_id))
|
||||
.unwrap_or(())
|
||||
.with_inner(|ctx| {
|
||||
chat::forward_msgs(ctx, ids, chat_id)
|
||||
.unwrap_or_log_default(ctx, "Failed to forward message")
|
||||
})
|
||||
.unwrap_or_default()
|
||||
}
|
||||
|
||||
#[no_mangle]
|
||||
@@ -1615,9 +1644,7 @@ pub unsafe extern "C" fn dc_stop_ongoing_process(context: *mut dc_context_t) {
|
||||
return;
|
||||
}
|
||||
let ffi_context = &*context;
|
||||
ffi_context
|
||||
.with_inner(|ctx| configure::dc_stop_ongoing_process(ctx))
|
||||
.ok();
|
||||
ffi_context.with_inner(|ctx| ctx.stop_ongoing()).ok();
|
||||
}
|
||||
|
||||
#[no_mangle]
|
||||
@@ -2131,7 +2158,7 @@ pub unsafe extern "C" fn dc_chat_get_profile_image(chat: *mut dc_chat_t) -> *mut
|
||||
let ffi_context = &*ffi_chat.context;
|
||||
ffi_context
|
||||
.with_inner(|ctx| match ffi_chat.chat.get_profile_image(ctx) {
|
||||
Some(p) => p.to_str().unwrap().to_string().strdup(),
|
||||
Some(p) => p.to_string_lossy().strdup(),
|
||||
None => ptr::null_mut(),
|
||||
})
|
||||
.unwrap_or_else(|_| ptr::null_mut())
|
||||
@@ -2476,7 +2503,7 @@ pub unsafe extern "C" fn dc_msg_get_summarytext(
|
||||
.with_inner(|ctx| {
|
||||
ffi_msg
|
||||
.message
|
||||
.get_summarytext(ctx, approx_characters.try_into().unwrap())
|
||||
.get_summarytext(ctx, approx_characters.try_into().unwrap_or_default())
|
||||
})
|
||||
.unwrap_or_default()
|
||||
.strdup()
|
||||
@@ -2768,7 +2795,7 @@ pub unsafe extern "C" fn dc_contact_get_profile_image(
|
||||
ffi_contact
|
||||
.contact
|
||||
.get_profile_image(ctx)
|
||||
.map(|p| p.to_str().unwrap().to_string().strdup())
|
||||
.map(|p| p.to_string_lossy().strdup())
|
||||
.unwrap_or_else(|| std::ptr::null_mut())
|
||||
})
|
||||
.unwrap_or_else(|_| ptr::null_mut())
|
||||
|
||||
@@ -4,7 +4,6 @@ use std::str::FromStr;
|
||||
use deltachat::chat::{self, Chat};
|
||||
use deltachat::chatlist::*;
|
||||
use deltachat::config;
|
||||
use deltachat::configure::*;
|
||||
use deltachat::constants::*;
|
||||
use deltachat::contact::*;
|
||||
use deltachat::context::*;
|
||||
@@ -119,13 +118,13 @@ fn poke_spec(context: &Context, spec: *const libc::c_char) -> libc::c_int {
|
||||
|
||||
/* if `spec` is given, remember it for later usage; if it is not given, try to use the last one */
|
||||
if !spec.is_null() {
|
||||
real_spec = to_string(spec);
|
||||
real_spec = to_string_lossy(spec);
|
||||
context
|
||||
.sql
|
||||
.set_config(context, "import_spec", Some(&real_spec))
|
||||
.set_raw_config(context, "import_spec", Some(&real_spec))
|
||||
.unwrap();
|
||||
} else {
|
||||
let rs = context.sql.get_config(context, "import_spec");
|
||||
let rs = context.sql.get_raw_config(context, "import_spec");
|
||||
if rs.is_none() {
|
||||
error!(context, "Import: No file or folder given.");
|
||||
return 0;
|
||||
@@ -471,7 +470,7 @@ pub unsafe fn dc_cmdline(context: &Context, line: &str) -> Result<(), failure::E
|
||||
ensure!(0 != dc_reset_tables(context, bits), "Reset failed");
|
||||
}
|
||||
"stop" => {
|
||||
dc_stop_ongoing_process(context);
|
||||
context.stop_ongoing();
|
||||
}
|
||||
"set" => {
|
||||
ensure!(!arg1.is_empty(), "Argument <key> missing.");
|
||||
@@ -864,7 +863,7 @@ pub unsafe fn dc_cmdline(context: &Context, line: &str) -> Result<(), failure::E
|
||||
let mut msg_ids = [0; 1];
|
||||
let chat_id = arg2.parse()?;
|
||||
msg_ids[0] = arg1.parse()?;
|
||||
chat::forward_msgs(context, &msg_ids, chat_id);
|
||||
chat::forward_msgs(context, &msg_ids, chat_id)?;
|
||||
}
|
||||
"markseen" => {
|
||||
ensure!(!arg1.is_empty(), "Argument <msg-id> missing.");
|
||||
|
||||
@@ -43,7 +43,6 @@ use self::cmdline::*;
|
||||
|
||||
fn receive_event(_context: &Context, event: Event) -> libc::uintptr_t {
|
||||
match event {
|
||||
Event::GetString { .. } => {}
|
||||
Event::Info(msg) => {
|
||||
/* do not show the event as this would fill the screen */
|
||||
println!("{}", msg);
|
||||
|
||||
@@ -35,93 +35,90 @@ fn cb(_ctx: &Context, event: Event) -> usize {
|
||||
}
|
||||
|
||||
fn main() {
|
||||
unsafe {
|
||||
let dir = tempdir().unwrap();
|
||||
let dbfile = dir.path().join("db.sqlite");
|
||||
println!("creating database {:?}", dbfile);
|
||||
let ctx =
|
||||
Context::new(Box::new(cb), "FakeOs".into(), dbfile).expect("Failed to create context");
|
||||
let running = Arc::new(RwLock::new(true));
|
||||
let info = ctx.get_info();
|
||||
let duration = time::Duration::from_millis(4000);
|
||||
println!("info: {:#?}", info);
|
||||
let dir = tempdir().unwrap();
|
||||
let dbfile = dir.path().join("db.sqlite");
|
||||
println!("creating database {:?}", dbfile);
|
||||
let ctx =
|
||||
Context::new(Box::new(cb), "FakeOs".into(), dbfile).expect("Failed to create context");
|
||||
let running = Arc::new(RwLock::new(true));
|
||||
let info = ctx.get_info();
|
||||
let duration = time::Duration::from_millis(4000);
|
||||
println!("info: {:#?}", info);
|
||||
|
||||
let ctx = Arc::new(ctx);
|
||||
let ctx1 = ctx.clone();
|
||||
let r1 = running.clone();
|
||||
let t1 = thread::spawn(move || {
|
||||
while *r1.read().unwrap() {
|
||||
perform_imap_jobs(&ctx1);
|
||||
if *r1.read().unwrap() {
|
||||
perform_imap_fetch(&ctx1);
|
||||
|
||||
let ctx = Arc::new(ctx);
|
||||
let ctx1 = ctx.clone();
|
||||
let r1 = running.clone();
|
||||
let t1 = thread::spawn(move || {
|
||||
while *r1.read().unwrap() {
|
||||
perform_imap_jobs(&ctx1);
|
||||
if *r1.read().unwrap() {
|
||||
perform_imap_fetch(&ctx1);
|
||||
|
||||
if *r1.read().unwrap() {
|
||||
perform_imap_idle(&ctx1);
|
||||
}
|
||||
perform_imap_idle(&ctx1);
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
let ctx1 = ctx.clone();
|
||||
let r1 = running.clone();
|
||||
let t2 = thread::spawn(move || {
|
||||
while *r1.read().unwrap() {
|
||||
perform_smtp_jobs(&ctx1);
|
||||
if *r1.read().unwrap() {
|
||||
perform_smtp_idle(&ctx1);
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
println!("configuring");
|
||||
let args = std::env::args().collect::<Vec<String>>();
|
||||
assert_eq!(args.len(), 2, "missing password");
|
||||
let pw = args[1].clone();
|
||||
ctx.set_config(config::Config::Addr, Some("d@testrun.org"))
|
||||
.unwrap();
|
||||
ctx.set_config(config::Config::MailPw, Some(&pw)).unwrap();
|
||||
configure(&ctx);
|
||||
|
||||
thread::sleep(duration);
|
||||
|
||||
println!("sending a message");
|
||||
let contact_id =
|
||||
Contact::create(&ctx, "dignifiedquire", "dignifiedquire@gmail.com").unwrap();
|
||||
let chat_id = chat::create_by_contact_id(&ctx, contact_id).unwrap();
|
||||
chat::send_text_msg(&ctx, chat_id, "Hi, here is my first message!".into()).unwrap();
|
||||
|
||||
println!("fetching chats..");
|
||||
let chats = Chatlist::try_load(&ctx, 0, None, None).unwrap();
|
||||
|
||||
for i in 0..chats.len() {
|
||||
let summary = chats.get_summary(&ctx, 0, None);
|
||||
let text1 = summary.get_text1();
|
||||
let text2 = summary.get_text2();
|
||||
println!("chat: {} - {:?} - {:?}", i, text1, text2,);
|
||||
}
|
||||
});
|
||||
|
||||
thread::sleep(duration);
|
||||
let ctx1 = ctx.clone();
|
||||
let r1 = running.clone();
|
||||
let t2 = thread::spawn(move || {
|
||||
while *r1.read().unwrap() {
|
||||
perform_smtp_jobs(&ctx1);
|
||||
if *r1.read().unwrap() {
|
||||
perform_smtp_idle(&ctx1);
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
// let msglist = dc_get_chat_msgs(&ctx, chat_id, 0, 0);
|
||||
// for i in 0..dc_array_get_cnt(msglist) {
|
||||
// let msg_id = dc_array_get_id(msglist, i);
|
||||
// let msg = dc_get_msg(context, msg_id);
|
||||
// let text = CStr::from_ptr(dc_msg_get_text(msg)).unwrap();
|
||||
// println!("Message {}: {}\n", i + 1, text.to_str().unwrap());
|
||||
// dc_msg_unref(msg);
|
||||
// }
|
||||
// dc_array_unref(msglist);
|
||||
println!("configuring");
|
||||
let args = std::env::args().collect::<Vec<String>>();
|
||||
assert_eq!(args.len(), 2, "missing password");
|
||||
let pw = args[1].clone();
|
||||
ctx.set_config(config::Config::Addr, Some("d@testrun.org"))
|
||||
.unwrap();
|
||||
ctx.set_config(config::Config::MailPw, Some(&pw)).unwrap();
|
||||
configure(&ctx);
|
||||
|
||||
println!("stopping threads");
|
||||
thread::sleep(duration);
|
||||
|
||||
*running.clone().write().unwrap() = false;
|
||||
deltachat::job::interrupt_imap_idle(&ctx);
|
||||
deltachat::job::interrupt_smtp_idle(&ctx);
|
||||
println!("sending a message");
|
||||
let contact_id = Contact::create(&ctx, "dignifiedquire", "dignifiedquire@gmail.com").unwrap();
|
||||
let chat_id = chat::create_by_contact_id(&ctx, contact_id).unwrap();
|
||||
chat::send_text_msg(&ctx, chat_id, "Hi, here is my first message!".into()).unwrap();
|
||||
|
||||
println!("joining");
|
||||
t1.join().unwrap();
|
||||
t2.join().unwrap();
|
||||
println!("fetching chats..");
|
||||
let chats = Chatlist::try_load(&ctx, 0, None, None).unwrap();
|
||||
|
||||
println!("closing");
|
||||
for i in 0..chats.len() {
|
||||
let summary = chats.get_summary(&ctx, 0, None);
|
||||
let text1 = summary.get_text1();
|
||||
let text2 = summary.get_text2();
|
||||
println!("chat: {} - {:?} - {:?}", i, text1, text2,);
|
||||
}
|
||||
|
||||
thread::sleep(duration);
|
||||
|
||||
// let msglist = dc_get_chat_msgs(&ctx, chat_id, 0, 0);
|
||||
// for i in 0..dc_array_get_cnt(msglist) {
|
||||
// let msg_id = dc_array_get_id(msglist, i);
|
||||
// let msg = dc_get_msg(context, msg_id);
|
||||
// let text = CStr::from_ptr(dc_msg_get_text(msg)).unwrap();
|
||||
// println!("Message {}: {}\n", i + 1, text.to_str().unwrap());
|
||||
// dc_msg_unref(msg);
|
||||
// }
|
||||
// dc_array_unref(msglist);
|
||||
|
||||
println!("stopping threads");
|
||||
|
||||
*running.clone().write().unwrap() = false;
|
||||
deltachat::job::interrupt_imap_idle(&ctx);
|
||||
deltachat::job::interrupt_smtp_idle(&ctx);
|
||||
|
||||
println!("joining");
|
||||
t1.join().unwrap();
|
||||
t2.join().unwrap();
|
||||
|
||||
println!("closing");
|
||||
}
|
||||
|
||||
@@ -17,12 +17,12 @@ pub unsafe fn charconv(
|
||||
assert!(!fromcode.is_null(), "invalid fromcode");
|
||||
assert!(!s.is_null(), "invalid input string");
|
||||
if let Some(encoding) =
|
||||
charset::Charset::for_label(CStr::from_ptr(fromcode).to_str().unwrap().as_bytes())
|
||||
charset::Charset::for_label(CStr::from_ptr(fromcode).to_string_lossy().as_bytes())
|
||||
{
|
||||
let data = std::slice::from_raw_parts(s as *const u8, strlen(s));
|
||||
|
||||
let (res, _, _) = encoding.decode(data);
|
||||
let res_c = CString::new(res.as_bytes()).unwrap();
|
||||
let res_c = CString::new(res.as_bytes()).unwrap_or_default();
|
||||
*result = strdup(res_c.as_ptr()) as *mut _;
|
||||
|
||||
MAIL_CHARCONV_NO_ERROR as libc::c_int
|
||||
|
||||
@@ -123,7 +123,7 @@ unsafe fn display_mime_discrete_type(mut discrete_type: *mut mailmime_discrete_t
|
||||
_ => {}
|
||||
};
|
||||
}
|
||||
unsafe fn display_mime_data(mut data: *mut mailmime_data) {
|
||||
pub unsafe fn display_mime_data(mut data: *mut mailmime_data) {
|
||||
match (*data).dt_type {
|
||||
0 => {
|
||||
println!(
|
||||
|
||||
@@ -1598,7 +1598,7 @@ unsafe fn mailimf_date_time_write_driver(
|
||||
(*date_time).dt_sec,
|
||||
(*date_time).dt_zone,
|
||||
);
|
||||
let date_str_c = std::ffi::CString::new(date_str).unwrap();
|
||||
let date_str_c = std::ffi::CString::new(date_str).unwrap_or_default();
|
||||
let r = mailimf_string_write_driver(
|
||||
do_write,
|
||||
data,
|
||||
|
||||
@@ -848,7 +848,7 @@ pub unsafe fn mailmime_generate_boundary() -> *mut libc::c_char {
|
||||
hex::encode(&std::process::id().to_le_bytes()[..2])
|
||||
);
|
||||
|
||||
let c = std::ffi::CString::new(raw).unwrap();
|
||||
let c = std::ffi::CString::new(raw).unwrap_or_default();
|
||||
strdup(c.as_ptr())
|
||||
}
|
||||
|
||||
|
||||
@@ -338,7 +338,7 @@ unsafe fn mailmime_disposition_param_write_driver(
|
||||
4 => {
|
||||
let value = (*param).pa_data.pa_size as u32;
|
||||
let raw = format!("{}", value);
|
||||
let raw_c = std::ffi::CString::new(raw).unwrap();
|
||||
let raw_c = std::ffi::CString::new(raw).unwrap_or_default();
|
||||
sizestr = strdup(raw_c.as_ptr());
|
||||
len = strlen(b"size=\x00" as *const u8 as *const libc::c_char)
|
||||
.wrapping_add(strlen(sizestr))
|
||||
@@ -542,7 +542,7 @@ unsafe fn mailmime_version_write_driver(
|
||||
}
|
||||
|
||||
let raw = format!("{}.{}", (version >> 16) as i32, (version & 0xffff) as i32);
|
||||
let raw_c = std::ffi::CString::new(raw).unwrap();
|
||||
let raw_c = std::ffi::CString::new(raw).unwrap_or_default();
|
||||
let mut versionstr = strdup(raw_c.as_ptr());
|
||||
r = mailimf_string_write_driver(do_write, data, col, versionstr, strlen(versionstr));
|
||||
if r != MAILIMF_NO_ERROR as libc::c_int {
|
||||
@@ -1515,8 +1515,8 @@ pub unsafe fn mailmime_data_write_driver(
|
||||
}
|
||||
1 => {
|
||||
let filename = CStr::from_ptr((*mime_data).dt_data.dt_filename)
|
||||
.to_str()
|
||||
.unwrap();
|
||||
.to_string_lossy()
|
||||
.into_owned();
|
||||
if let Ok(file) = std::fs::File::open(filename) {
|
||||
if let Ok(mut text) = memmap::MmapOptions::new().map_copy(&file) {
|
||||
if 0 != (*mime_data).dt_encoded {
|
||||
@@ -1797,7 +1797,7 @@ pub unsafe fn mailmime_quoted_printable_write_driver(
|
||||
start = text.offset(i as isize).offset(1isize);
|
||||
|
||||
let raw = format!("={:02X}", (ch as libc::c_int));
|
||||
let raw_c = std::ffi::CString::new(raw).unwrap();
|
||||
let raw_c = std::ffi::CString::new(raw).unwrap_or_default();
|
||||
let mut hexstr = strdup(raw_c.as_ptr());
|
||||
r = mailimf_string_write_driver(
|
||||
do_write,
|
||||
@@ -1822,7 +1822,7 @@ pub unsafe fn mailmime_quoted_printable_write_driver(
|
||||
}
|
||||
start = text.offset(i as isize).offset(1isize);
|
||||
let raw = format!("={:02X}", ch as libc::c_int);
|
||||
let raw_c = std::ffi::CString::new(raw).unwrap();
|
||||
let raw_c = std::ffi::CString::new(raw).unwrap_or_default();
|
||||
let mut hexstr = strdup(raw_c.as_ptr());
|
||||
r = mailimf_string_write_driver(
|
||||
do_write,
|
||||
@@ -1866,7 +1866,7 @@ pub unsafe fn mailmime_quoted_printable_write_driver(
|
||||
}
|
||||
start = text.offset(i as isize);
|
||||
let raw = format!("={:02X}", b'\r' as i32);
|
||||
let raw_c = std::ffi::CString::new(raw).unwrap();
|
||||
let raw_c = std::ffi::CString::new(raw).unwrap_or_default();
|
||||
let mut hexstr = strdup(raw_c.as_ptr());
|
||||
r = mailimf_string_write_driver(do_write, data, col, hexstr, 3i32 as size_t);
|
||||
if r != MAILIMF_NO_ERROR as libc::c_int {
|
||||
@@ -1890,7 +1890,7 @@ pub unsafe fn mailmime_quoted_printable_write_driver(
|
||||
"={:02X}\r\n",
|
||||
*text.offset(i.wrapping_sub(1i32 as libc::size_t) as isize) as libc::c_int
|
||||
);
|
||||
let raw_c = std::ffi::CString::new(raw).unwrap();
|
||||
let raw_c = std::ffi::CString::new(raw).unwrap_or_default();
|
||||
let mut hexstr = strdup(raw_c.as_ptr());
|
||||
|
||||
r = mailimf_string_write_driver(do_write, data, col, hexstr, strlen(hexstr));
|
||||
@@ -1917,7 +1917,7 @@ pub unsafe fn mailmime_quoted_printable_write_driver(
|
||||
"={:02X}\r\n",
|
||||
*text.offset(i.wrapping_sub(2i32 as libc::size_t) as isize) as libc::c_int
|
||||
);
|
||||
let raw_c = std::ffi::CString::new(raw).unwrap();
|
||||
let raw_c = std::ffi::CString::new(raw).unwrap_or_default();
|
||||
let mut hexstr = strdup(raw_c.as_ptr());
|
||||
|
||||
r = mailimf_string_write_driver(do_write, data, col, hexstr, strlen(hexstr));
|
||||
@@ -1938,7 +1938,7 @@ pub unsafe fn mailmime_quoted_printable_write_driver(
|
||||
(*text.offset(i.wrapping_sub(2i32 as libc::size_t) as isize) as u8 as char),
|
||||
b'\r' as i32
|
||||
);
|
||||
let raw_c = std::ffi::CString::new(raw).unwrap();
|
||||
let raw_c = std::ffi::CString::new(raw).unwrap_or_default();
|
||||
let mut hexstr = strdup(raw_c.as_ptr());
|
||||
|
||||
r = mailimf_string_write_driver(do_write, data, col, hexstr, strlen(hexstr));
|
||||
|
||||
@@ -12,6 +12,10 @@ pub(crate) use libc::{
|
||||
};
|
||||
|
||||
pub(crate) unsafe fn strcasecmp(s1: *const libc::c_char, s2: *const libc::c_char) -> libc::c_int {
|
||||
if s1.is_null() || s2.is_null() {
|
||||
return 1;
|
||||
}
|
||||
|
||||
let s1 = std::ffi::CStr::from_ptr(s1)
|
||||
.to_string_lossy()
|
||||
.to_lowercase();
|
||||
@@ -30,16 +34,23 @@ pub(crate) unsafe fn strncasecmp(
|
||||
s2: *const libc::c_char,
|
||||
n: libc::size_t,
|
||||
) -> libc::c_int {
|
||||
let s1 = std::ffi::CStr::from_ptr(s1)
|
||||
.to_string_lossy()
|
||||
.to_lowercase();
|
||||
let s2 = std::ffi::CStr::from_ptr(s2)
|
||||
.to_string_lossy()
|
||||
.to_lowercase();
|
||||
let m1 = std::cmp::min(n, s1.len());
|
||||
let m2 = std::cmp::min(n, s2.len());
|
||||
if s1.is_null() || s2.is_null() {
|
||||
return 1;
|
||||
}
|
||||
|
||||
if s1[..m1] == s2[..m2] {
|
||||
// s1 and s2 might not be null terminated.
|
||||
|
||||
let s1_slice = std::slice::from_raw_parts(s1 as *const u8, n);
|
||||
let s2_slice = std::slice::from_raw_parts(s2 as *const u8, n);
|
||||
|
||||
let s1 = std::ffi::CStr::from_bytes_with_nul_unchecked(s1_slice)
|
||||
.to_string_lossy()
|
||||
.to_lowercase();
|
||||
let s2 = std::ffi::CStr::from_bytes_with_nul_unchecked(s2_slice)
|
||||
.to_string_lossy()
|
||||
.to_lowercase();
|
||||
|
||||
if s1 == s2 {
|
||||
0
|
||||
} else {
|
||||
1
|
||||
|
||||
@@ -97,7 +97,7 @@ If you want to run "liveconfig" functional tests you can set
|
||||
chat devs.
|
||||
|
||||
- or the path of a file that contains two lines, each describing
|
||||
via "addr=... mail_pwd=..." a test account login that will
|
||||
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
|
||||
|
||||
1
python/doc/_templates/globaltoc.html
vendored
1
python/doc/_templates/globaltoc.html
vendored
@@ -6,7 +6,6 @@
|
||||
<li><a href="{{ pathto('install') }}">install</a></li>
|
||||
<li><a href="{{ pathto('api') }}">high level API</a></li>
|
||||
<li><a href="{{ pathto('lapi') }}">low level API</a></li>
|
||||
<li><a href="{{ pathto('capi') }}">C deltachat.h</a></li>
|
||||
</ul>
|
||||
<b>external links:</b>
|
||||
<ul>
|
||||
|
||||
@@ -2,7 +2,13 @@
|
||||
low level API reference
|
||||
===================================
|
||||
|
||||
for full C-docs, defines and function checkout :doc:`capi`
|
||||
for full doxygen-generated C-docs, defines and functions please checkout
|
||||
|
||||
https://c.delta.chat
|
||||
|
||||
|
||||
Python low-level capi calls
|
||||
---------------------------
|
||||
|
||||
|
||||
.. automodule:: deltachat.capi.lib
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
""" Account class implementation. """
|
||||
|
||||
from __future__ import print_function
|
||||
import atexit
|
||||
import threading
|
||||
import re
|
||||
import time
|
||||
@@ -49,9 +50,10 @@ class Account(object):
|
||||
raise ValueError("Could not dc_open: {}".format(db_path))
|
||||
self._configkeys = self.get_config("sys.config_keys").split()
|
||||
self._imex_events = Queue()
|
||||
atexit.register(self.shutdown)
|
||||
|
||||
def __del__(self):
|
||||
self.shutdown()
|
||||
# def __del__(self):
|
||||
# self.shutdown()
|
||||
|
||||
def _check_config_key(self, name):
|
||||
if name not in self._configkeys:
|
||||
@@ -69,6 +71,18 @@ class Account(object):
|
||||
d[key.lower()] = value
|
||||
return d
|
||||
|
||||
def set_stock_translation(self, id, string):
|
||||
""" set stock translation string.
|
||||
|
||||
:param id: id of stock string (const.DC_STR_*)
|
||||
:param value: string to set as new transalation
|
||||
:returns: None
|
||||
"""
|
||||
string = string.encode("utf8")
|
||||
res = lib.dc_set_stock_translation(self._dc_context, id, string)
|
||||
if res == 0:
|
||||
raise ValueError("could not set translation string")
|
||||
|
||||
def set_config(self, name, value):
|
||||
""" set configuration values.
|
||||
|
||||
@@ -411,6 +425,9 @@ class Account(object):
|
||||
raise ValueError("could not join group")
|
||||
return Chat(self, chat_id)
|
||||
|
||||
def stop_ongoing(self):
|
||||
lib.dc_stop_ongoing_process(self._dc_context)
|
||||
|
||||
#
|
||||
# meta API for start/stop and event based processing
|
||||
#
|
||||
@@ -432,7 +449,7 @@ class Account(object):
|
||||
|
||||
def stop_threads(self, wait=True):
|
||||
""" stop IMAP/SMTP threads. """
|
||||
lib.dc_stop_ongoing_process(self._dc_context)
|
||||
self.stop_ongoing()
|
||||
self._threads.stop(wait=wait)
|
||||
|
||||
def shutdown(self, wait=True):
|
||||
@@ -444,6 +461,7 @@ class Account(object):
|
||||
self.stop_threads(wait=wait) # to wait for threads
|
||||
deltachat.clear_context_callback(self._dc_context)
|
||||
del self._dc_context
|
||||
atexit.unregister(self.shutdown)
|
||||
|
||||
def _process_event(self, ctx, evt_name, data1, data2):
|
||||
assert ctx == self._dc_context
|
||||
@@ -574,11 +592,11 @@ class EventLogger:
|
||||
else:
|
||||
assert not rex.match(ev[0]), "event found {}".format(ev)
|
||||
|
||||
def get_matching(self, event_name_regex, check_error=True):
|
||||
def get_matching(self, event_name_regex, check_error=True, timeout=None):
|
||||
self._log("-- waiting for event with regex: {} --".format(event_name_regex))
|
||||
rex = re.compile("(?:{}).*".format(event_name_regex))
|
||||
while 1:
|
||||
ev = self.get()
|
||||
ev = self.get(timeout=timeout, check_error=check_error)
|
||||
if rex.match(ev[0]):
|
||||
return ev
|
||||
|
||||
|
||||
@@ -8,9 +8,6 @@ from os.path import join as joinpath
|
||||
# this works well when you in a git-checkout
|
||||
# run "python deltachat/const.py" to regenerate events
|
||||
# begin const generated
|
||||
DC_PROVIDER_STATUS_OK = 1
|
||||
DC_PROVIDER_STATUS_PREPARATION = 2
|
||||
DC_PROVIDER_STATUS_BROKEN = 3
|
||||
DC_GCL_ARCHIVED_ONLY = 0x01
|
||||
DC_GCL_NO_SPECIALS = 0x02
|
||||
DC_GCL_ADD_ALLDONE_HINT = 0x04
|
||||
@@ -55,6 +52,7 @@ DC_CONTACT_ID_LAST_SPECIAL = 9
|
||||
DC_MSG_TEXT = 10
|
||||
DC_MSG_IMAGE = 20
|
||||
DC_MSG_GIF = 21
|
||||
DC_MSG_STICKER = 23
|
||||
DC_MSG_AUDIO = 40
|
||||
DC_MSG_VOICE = 41
|
||||
DC_MSG_VIDEO = 50
|
||||
@@ -63,6 +61,10 @@ DC_EVENT_INFO = 100
|
||||
DC_EVENT_SMTP_CONNECTED = 101
|
||||
DC_EVENT_IMAP_CONNECTED = 102
|
||||
DC_EVENT_SMTP_MESSAGE_SENT = 103
|
||||
DC_EVENT_IMAP_MESSAGE_DELETED = 104
|
||||
DC_EVENT_IMAP_MESSAGE_MOVED = 105
|
||||
DC_EVENT_NEW_BLOB_FILE = 150
|
||||
DC_EVENT_DELETED_BLOB_FILE = 151
|
||||
DC_EVENT_WARNING = 300
|
||||
DC_EVENT_ERROR = 400
|
||||
DC_EVENT_ERROR_NETWORK = 401
|
||||
@@ -83,11 +85,61 @@ DC_EVENT_SECUREJOIN_JOINER_PROGRESS = 2061
|
||||
DC_EVENT_GET_STRING = 2091
|
||||
DC_EVENT_FILE_COPIED = 2055
|
||||
DC_EVENT_IS_OFFLINE = 2081
|
||||
DC_STR_SELFNOTINGRP = 21
|
||||
DC_PROVIDER_STATUS_OK = 1
|
||||
DC_PROVIDER_STATUS_PREPARATION = 2
|
||||
DC_PROVIDER_STATUS_BROKEN = 3
|
||||
DC_STR_NOMESSAGES = 1
|
||||
DC_STR_SELF = 2
|
||||
DC_STR_DRAFT = 3
|
||||
DC_STR_MEMBER = 4
|
||||
DC_STR_CONTACT = 6
|
||||
DC_STR_VOICEMESSAGE = 7
|
||||
DC_STR_DEADDROP = 8
|
||||
DC_STR_IMAGE = 9
|
||||
DC_STR_VIDEO = 10
|
||||
DC_STR_AUDIO = 11
|
||||
DC_STR_FILE = 12
|
||||
DC_STR_STATUSLINE = 13
|
||||
DC_STR_NEWGROUPDRAFT = 14
|
||||
DC_STR_MSGGRPNAME = 15
|
||||
DC_STR_MSGGRPIMGCHANGED = 16
|
||||
DC_STR_MSGADDMEMBER = 17
|
||||
DC_STR_MSGDELMEMBER = 18
|
||||
DC_STR_MSGGROUPLEFT = 19
|
||||
DC_STR_GIF = 23
|
||||
DC_STR_ENCRYPTEDMSG = 24
|
||||
DC_STR_E2E_AVAILABLE = 25
|
||||
DC_STR_ENCR_TRANSP = 27
|
||||
DC_STR_ENCR_NONE = 28
|
||||
DC_STR_CANTDECRYPT_MSG_BODY = 29
|
||||
DC_STR_FINGERPRINTS = 30
|
||||
DC_STR_READRCPT = 31
|
||||
DC_STR_READRCPT_MAILBODY = 32
|
||||
DC_STR_MSGGRPIMGDELETED = 33
|
||||
DC_STR_E2E_PREFERRED = 34
|
||||
DC_STR_CONTACT_VERIFIED = 35
|
||||
DC_STR_CONTACT_NOT_VERIFIED = 36
|
||||
DC_STR_CONTACT_SETUP_CHANGED = 37
|
||||
DC_STR_ARCHIVEDCHATS = 40
|
||||
DC_STR_STARREDMSGS = 41
|
||||
DC_STR_AC_SETUP_MSG_SUBJECT = 42
|
||||
DC_STR_AC_SETUP_MSG_BODY = 43
|
||||
DC_STR_SELFTALK_SUBTITLE = 50
|
||||
DC_STR_CANNOT_LOGIN = 60
|
||||
DC_STR_SERVER_RESPONSE = 61
|
||||
DC_STR_MSGACTIONBYUSER = 62
|
||||
DC_STR_MSGACTIONBYME = 63
|
||||
DC_STR_MSGLOCATIONENABLED = 64
|
||||
DC_STR_MSGLOCATIONDISABLED = 65
|
||||
DC_STR_LOCATION = 66
|
||||
DC_STR_STICKER = 67
|
||||
DC_STR_COUNT = 67
|
||||
# end const generated
|
||||
|
||||
|
||||
def read_event_defines(f):
|
||||
rex = re.compile(r'#define\s+((?:DC_EVENT_|DC_QR|DC_MSG|DC_STATE_|'
|
||||
rex = re.compile(r'#define\s+((?:DC_EVENT_|DC_QR|DC_MSG|DC_STATE_|DC_STR|'
|
||||
r'DC_CONTACT_ID_|DC_GCL|DC_CHAT|DC_PROVIDER)\S+)\s+([x\d]+).*')
|
||||
for line in f:
|
||||
m = rex.match(line)
|
||||
|
||||
@@ -157,6 +157,11 @@ def acfactory(pytestconfig, tmpdir, request, session_liveconfig):
|
||||
self.live_count += 1
|
||||
if "e2ee_enabled" not in configdict:
|
||||
configdict["e2ee_enabled"] = "1"
|
||||
|
||||
# Enable strict certificate checks for online accounts
|
||||
configdict["imap_certificate_checks"] = "1"
|
||||
configdict["smtp_certificate_checks"] = "1"
|
||||
|
||||
tmpdb = tmpdir.join("livedb%d" % self.live_count)
|
||||
ac = self.make_account(tmpdb.strpath, logid="ac{}".format(self.live_count))
|
||||
ac._evlogger.init_time = self.init_time
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
from __future__ import print_function
|
||||
import pytest
|
||||
import os
|
||||
import queue
|
||||
from deltachat import const, Account
|
||||
from deltachat.message import Message
|
||||
from datetime import datetime, timedelta
|
||||
@@ -19,6 +20,7 @@ class TestOfflineAccountBasic:
|
||||
d = ac1.get_info()
|
||||
assert d["arch"]
|
||||
assert d["number_of_chats"] == "0"
|
||||
assert d["bcc_self"] == "1"
|
||||
|
||||
def test_is_not_configured(self, acfactory):
|
||||
ac1 = acfactory.get_unconfigured_account()
|
||||
@@ -37,6 +39,11 @@ class TestOfflineAccountBasic:
|
||||
ac1 = acfactory.get_unconfigured_account()
|
||||
assert "save_mime_headers" in ac1.get_config("sys.config_keys").split()
|
||||
|
||||
def test_has_bccself(self, acfactory):
|
||||
ac1 = acfactory.get_unconfigured_account()
|
||||
assert "bcc_self" in ac1.get_config("sys.config_keys").split()
|
||||
assert ac1.get_config("bcc_self") == "1"
|
||||
|
||||
def test_selfcontact_if_unconfigured(self, acfactory):
|
||||
ac1 = acfactory.get_unconfigured_account()
|
||||
with pytest.raises(ValueError):
|
||||
@@ -93,7 +100,7 @@ class TestOfflineContact:
|
||||
ac1 = acfactory.get_configured_offline_account()
|
||||
contact1 = ac1.create_contact(email="some1@example.com", name="some1")
|
||||
chat = ac1.create_chat_by_contact(contact1)
|
||||
msg = chat.send_text("one messae")
|
||||
msg = chat.send_text("one message")
|
||||
assert not ac1.delete_contact(contact1)
|
||||
assert not msg.filemime
|
||||
|
||||
@@ -141,6 +148,27 @@ class TestOfflineChat:
|
||||
chat.set_name("title2")
|
||||
assert chat.get_name() == "title2"
|
||||
|
||||
def test_group_chat_creation_with_translation(self, ac1):
|
||||
ac1.set_stock_translation(const.DC_STR_NEWGROUPDRAFT, "xyz %1$s")
|
||||
ac1._evlogger.consume_events()
|
||||
with pytest.raises(ValueError):
|
||||
ac1.set_stock_translation(const.DC_STR_NEWGROUPDRAFT, "xyz %2$s")
|
||||
ac1._evlogger.get_matching("DC_EVENT_WARNING")
|
||||
with pytest.raises(ValueError):
|
||||
ac1.set_stock_translation(500, "xyz %1$s")
|
||||
ac1._evlogger.get_matching("DC_EVENT_WARNING")
|
||||
contact1 = ac1.create_contact("some1@hello.com", name="some1")
|
||||
contact2 = ac1.create_contact("some2@hello.com", name="some2")
|
||||
chat = ac1.create_group_chat(name="title1")
|
||||
chat.add_contact(contact1)
|
||||
chat.add_contact(contact2)
|
||||
assert chat.get_name() == "title1"
|
||||
assert contact1 in chat.get_contacts()
|
||||
assert contact2 in chat.get_contacts()
|
||||
assert not chat.is_promoted()
|
||||
msg = chat.get_draft()
|
||||
assert msg.text == "xyz title1"
|
||||
|
||||
@pytest.mark.parametrize("verified", [True, False])
|
||||
def test_group_chat_qr(self, acfactory, ac1, verified):
|
||||
ac2 = acfactory.get_configured_offline_account()
|
||||
@@ -222,7 +250,9 @@ class TestOfflineChat:
|
||||
chat1.send_image(path="notexists")
|
||||
fn = data.get_path("d.png")
|
||||
lp.sec("sending image")
|
||||
chat1.account._evlogger.consume_events()
|
||||
msg = chat1.send_image(fn)
|
||||
chat1.account._evlogger.get_matching("DC_EVENT_NEW_BLOB_FILE")
|
||||
assert msg.is_image()
|
||||
assert msg
|
||||
assert msg.id > 0
|
||||
@@ -341,6 +371,12 @@ class TestOnlineAccount:
|
||||
assert chat.id > const.DC_CHAT_ID_LAST_SPECIAL
|
||||
return chat
|
||||
|
||||
def test_configure_canceled(self, acfactory):
|
||||
ac1 = acfactory.get_online_configuring_account()
|
||||
wait_configuration_progress(ac1, 200)
|
||||
ac1.stop_ongoing()
|
||||
wait_configuration_progress(ac1, 0, 0)
|
||||
|
||||
def test_export_import_self_keys(self, acfactory, tmpdir):
|
||||
ac1, ac2 = acfactory.get_two_online_accounts()
|
||||
dir = tmpdir.mkdir("exportdir")
|
||||
@@ -351,18 +387,34 @@ class TestOnlineAccount:
|
||||
ac1._evlogger.consume_events()
|
||||
ac1.import_self_keys(dir.strpath)
|
||||
|
||||
def test_one_account_send(self, acfactory):
|
||||
def test_one_account_send_bcc_setting(self, acfactory, lp):
|
||||
ac1 = acfactory.get_online_configuring_account()
|
||||
c2 = ac1.create_contact(email=ac1.get_config("addr"))
|
||||
c2 = ac1.create_contact(email="notexists@testrun.org")
|
||||
chat = ac1.create_chat_by_contact(c2)
|
||||
assert chat.id > const.DC_CHAT_ID_LAST_SPECIAL
|
||||
wait_successful_IMAP_SMTP_connection(ac1)
|
||||
wait_configuration_progress(ac1, 1000)
|
||||
|
||||
lp.sec("send out message with bcc to ourselves")
|
||||
msg_out = chat.send_text("message2")
|
||||
# wait for own account to receive
|
||||
ev = ac1._evlogger.get_matching("DC_EVENT_INCOMING_MSG|DC_EVENT_MSGS_CHANGED")
|
||||
assert ev[1] == msg_out.id
|
||||
ev = ac1._evlogger.get_matching("DC_EVENT_MSGS_CHANGED")
|
||||
assert ev[2] == msg_out.id
|
||||
# wait for send out (BCC)
|
||||
assert ac1.get_config("bcc_self") == "1"
|
||||
self_addr = ac1.get_config("addr")
|
||||
ev = ac1._evlogger.get_matching("DC_EVENT_SMTP_MESSAGE_SENT")
|
||||
assert self_addr in ev[2]
|
||||
ev = ac1._evlogger.get_matching("DC_EVENT_DELETED_BLOB_FILE")
|
||||
|
||||
ac1._evlogger.consume_events()
|
||||
lp.sec("send out message without bcc")
|
||||
ac1.set_config("bcc_self", "0")
|
||||
msg_out = chat.send_text("message3")
|
||||
ev = ac1._evlogger.get_matching("DC_EVENT_MSGS_CHANGED")
|
||||
assert ev[2] == msg_out.id
|
||||
ev = ac1._evlogger.get_matching("DC_EVENT_SMTP_MESSAGE_SENT")
|
||||
assert self_addr not in ev[2]
|
||||
ev = ac1._evlogger.get_matching("DC_EVENT_DELETED_BLOB_FILE")
|
||||
|
||||
def test_mvbox_sentbox_threads(self, acfactory):
|
||||
ac1 = acfactory.get_online_configuring_account(mvbox=True, sentbox=True)
|
||||
@@ -374,6 +426,17 @@ class TestOnlineAccount:
|
||||
ev = ac2._evlogger.get_matching("DC_EVENT_INCOMING_MSG|DC_EVENT_MSGS_CHANGED")
|
||||
assert ev[2] > const.DC_CHAT_ID_LAST_SPECIAL
|
||||
|
||||
def test_move_works(self, acfactory):
|
||||
ac1 = acfactory.get_online_configuring_account()
|
||||
ac2 = acfactory.get_online_configuring_account(mvbox=True)
|
||||
wait_configuration_progress(ac2, 1000)
|
||||
wait_configuration_progress(ac1, 1000)
|
||||
chat = self.get_chat(ac1, ac2)
|
||||
chat.send_text("message1")
|
||||
ev = ac2._evlogger.get_matching("DC_EVENT_INCOMING_MSG|DC_EVENT_MSGS_CHANGED")
|
||||
assert ev[2] > const.DC_CHAT_ID_LAST_SPECIAL
|
||||
ev = ac2._evlogger.get_matching("DC_EVENT_IMAP_MESSAGE_MOVED")
|
||||
|
||||
def test_forward_messages(self, acfactory):
|
||||
ac1, ac2 = acfactory.get_two_online_accounts()
|
||||
chat = self.get_chat(ac1, ac2)
|
||||
@@ -444,6 +507,14 @@ class TestOnlineAccount:
|
||||
lp.step("2")
|
||||
assert msg_out.is_out_mdn_received()
|
||||
|
||||
lp.sec("check that a second call to mark_seen does not create change or smtp job")
|
||||
ac2._evlogger.consume_events()
|
||||
ac2.mark_seen_messages([msg_in])
|
||||
try:
|
||||
ac2._evlogger.get_matching("DC_EVENT_MSG_READ", timeout=0.01)
|
||||
except queue.Empty:
|
||||
pass # mark_seen_messages() has generated events before it returns
|
||||
|
||||
def test_send_and_receive_will_encrypt_decrypt(self, acfactory, lp):
|
||||
ac1, ac2 = acfactory.get_two_online_accounts()
|
||||
|
||||
@@ -472,6 +543,14 @@ class TestOnlineAccount:
|
||||
assert msg_back.text == "message-back"
|
||||
assert msg_back.is_encrypted()
|
||||
|
||||
lp.sec("create group chat with two members, one of which has no encrypt state")
|
||||
chat = ac1.create_group_chat("encryption test")
|
||||
chat.add_contact(ac1.create_contact(ac2.get_config("addr")))
|
||||
chat.add_contact(ac1.create_contact("notexisting@testrun.org"))
|
||||
msg = chat.send_text("test not encrypt")
|
||||
ev = ac1._evlogger.get_matching("DC_EVENT_SMTP_MESSAGE_SENT")
|
||||
assert not msg.is_encrypted()
|
||||
|
||||
def test_saved_mime_on_received_message(self, acfactory, lp):
|
||||
ac1, ac2 = acfactory.get_two_online_accounts()
|
||||
|
||||
@@ -576,6 +655,9 @@ class TestOnlineAccount:
|
||||
lp.sec("ac2: start QR-code based join-group protocol")
|
||||
ch = ac2.qr_join_chat(qr)
|
||||
assert ch.id >= 10
|
||||
# check that at least some of the handshake messages are deleted
|
||||
ac1._evlogger.get_matching("DC_EVENT_IMAP_MESSAGE_DELETED")
|
||||
ac2._evlogger.get_matching("DC_EVENT_IMAP_MESSAGE_DELETED")
|
||||
wait_securejoin_inviter_progress(ac1, 1000)
|
||||
|
||||
def test_qr_verified_group_and_chatting(self, acfactory, lp):
|
||||
|
||||
@@ -79,27 +79,21 @@ impl Aheader {
|
||||
let optional_field = unsafe { (*field).fld_data.fld_optional_field };
|
||||
if !optional_field.is_null()
|
||||
&& unsafe { !(*optional_field).fld_name.is_null() }
|
||||
&& unsafe { CStr::from_ptr((*optional_field).fld_name).to_str().unwrap() }
|
||||
&& unsafe { CStr::from_ptr((*optional_field).fld_name).to_string_lossy() }
|
||||
== "Autocrypt"
|
||||
{
|
||||
let value = unsafe {
|
||||
CStr::from_ptr((*optional_field).fld_value)
|
||||
.to_str()
|
||||
.unwrap()
|
||||
};
|
||||
let value =
|
||||
unsafe { CStr::from_ptr((*optional_field).fld_value).to_string_lossy() };
|
||||
|
||||
match Self::from_str(value) {
|
||||
Ok(test) => {
|
||||
if addr_cmp(&test.addr, wanted_from) {
|
||||
if fine_header.is_none() {
|
||||
fine_header = Some(test);
|
||||
} else {
|
||||
// TODO: figure out what kind of error case this is
|
||||
return None;
|
||||
}
|
||||
if let Ok(test) = Self::from_str(&value) {
|
||||
if addr_cmp(&test.addr, wanted_from) {
|
||||
if fine_header.is_none() {
|
||||
fine_header = Some(test);
|
||||
} else {
|
||||
// TODO: figure out what kind of error case this is
|
||||
return None;
|
||||
}
|
||||
}
|
||||
_ => {}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -131,9 +125,9 @@ impl str::FromStr for Aheader {
|
||||
|
||||
fn from_str(s: &str) -> Result<Self, Self::Err> {
|
||||
let mut attributes: BTreeMap<String, String> = s
|
||||
.split(";")
|
||||
.split(';')
|
||||
.filter_map(|a| {
|
||||
let attribute: Vec<&str> = a.trim().splitn(2, "=").collect();
|
||||
let attribute: Vec<&str> = a.trim().splitn(2, '=').collect();
|
||||
if attribute.len() < 2 {
|
||||
return None;
|
||||
}
|
||||
@@ -178,7 +172,7 @@ impl str::FromStr for Aheader {
|
||||
|
||||
// Autocrypt-Level0: unknown attributes starting with an underscore can be safely ignored
|
||||
// Autocrypt-Level0: unknown attribute, treat the header as invalid
|
||||
if attributes.keys().find(|k| !k.starts_with("_")).is_some() {
|
||||
if attributes.keys().any(|k| !k.starts_with('_')) {
|
||||
return Err(());
|
||||
}
|
||||
|
||||
|
||||
154
src/chat.rs
154
src/chat.rs
@@ -152,21 +152,23 @@ impl Chat {
|
||||
return context.stock_str(StockMessage::DeadDrop).into();
|
||||
}
|
||||
let cnt = get_chat_contact_cnt(context, self.id);
|
||||
return context
|
||||
.stock_string_repl_int(StockMessage::Member, cnt as i32)
|
||||
.into();
|
||||
return context.stock_string_repl_int(StockMessage::Member, cnt as i32);
|
||||
}
|
||||
|
||||
return "Err".into();
|
||||
"Err".to_string()
|
||||
}
|
||||
|
||||
pub fn get_parent_mime_headers(&self, context: &Context) -> Option<(String, String, String)> {
|
||||
let collect = |row: &rusqlite::Row| Ok((row.get(0)?, row.get(1)?, row.get(2)?));
|
||||
let params = params![self.id as i32, DC_CONTACT_ID_SELF as i32];
|
||||
let sql = &context.sql;
|
||||
|
||||
// use the last messsage of another user in the group as the parent
|
||||
let main_query = "SELECT rfc724_mid, mime_in_reply_to, mime_references \
|
||||
FROM msgs WHERE chat_id=?1 AND timestamp=(SELECT max(timestamp) \
|
||||
FROM msgs WHERE chat_id=?1 AND from_id!=?2);";
|
||||
|
||||
// there are no messages of other users - use the first message if SELF as parent
|
||||
let fallback_query = "SELECT rfc724_mid, mime_in_reply_to, mime_references \
|
||||
FROM msgs WHERE chat_id=?1 AND timestamp=(SELECT min(timestamp) \
|
||||
FROM msgs WHERE chat_id=?1 AND from_id==?2);";
|
||||
@@ -289,7 +291,7 @@ impl Chat {
|
||||
if self.typ == Chattype::Group || self.typ == Chattype::VerifiedGroup {
|
||||
if self.param.get_int(Param::Unpromoted).unwrap_or_default() == 1 {
|
||||
self.param.remove(Param::Unpromoted);
|
||||
self.update_param(context).unwrap();
|
||||
self.update_param(context)?;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -299,16 +301,15 @@ impl Chat {
|
||||
so that E2EE is no longer available at a later point (reset, changed settings),
|
||||
we do not send the message out at all */
|
||||
do_guarantee_e2ee = false;
|
||||
e2ee_enabled = context
|
||||
.sql
|
||||
.get_config_int(context, "e2ee_enabled")
|
||||
.unwrap_or_else(|| 1)
|
||||
== 1;
|
||||
e2ee_enabled = context.get_config_bool(Config::E2eeEnabled);
|
||||
if e2ee_enabled && msg.param.get_int(Param::ForcePlaintext).unwrap_or_default() == 0 {
|
||||
let mut can_encrypt = 1;
|
||||
let mut all_mutual = 1;
|
||||
let mut can_encrypt = true;
|
||||
let mut all_mutual = true;
|
||||
|
||||
let res = context.sql.query_row(
|
||||
// take care that this statement returns NULL rows
|
||||
// if there is no peerstates for a chat member!
|
||||
// for DC_PARAM_SELFTALK this statement does not return any row
|
||||
let res = context.sql.query_map(
|
||||
"SELECT ps.prefer_encrypted, c.addr \
|
||||
FROM chats_contacts cc \
|
||||
LEFT JOIN contacts c ON cc.contact_id=c.id \
|
||||
@@ -316,29 +317,32 @@ impl Chat {
|
||||
WHERE cc.chat_id=? AND cc.contact_id>9;",
|
||||
params![self.id],
|
||||
|row| {
|
||||
let state: String = row.get(1)?;
|
||||
let addr: String = row.get(1)?;
|
||||
|
||||
if let Some(prefer_encrypted) = row.get::<_, Option<i32>>(0)? {
|
||||
// the peerstate exist, so we have either public_key or gossip_key
|
||||
// and can encrypt potentially
|
||||
if prefer_encrypted != 1 {
|
||||
info!(
|
||||
context,
|
||||
"[autocrypt] peerstate for {} is {}",
|
||||
state,
|
||||
addr,
|
||||
if prefer_encrypted == 0 {
|
||||
"NOPREFERENCE"
|
||||
} else {
|
||||
"RESET"
|
||||
},
|
||||
);
|
||||
all_mutual = 0;
|
||||
all_mutual = false;
|
||||
}
|
||||
} else {
|
||||
info!(context, "[autocrypt] no peerstate for {}", state,);
|
||||
can_encrypt = 0;
|
||||
all_mutual = 0;
|
||||
info!(context, "[autocrypt] no peerstate for {}", addr,);
|
||||
can_encrypt = false;
|
||||
all_mutual = false;
|
||||
}
|
||||
Ok(())
|
||||
},
|
||||
|rows| rows.collect::<Result<Vec<_>, _>>().map_err(Into::into),
|
||||
);
|
||||
match res {
|
||||
Ok(_) => {}
|
||||
@@ -347,8 +351,8 @@ impl Chat {
|
||||
}
|
||||
}
|
||||
|
||||
if 0 != can_encrypt {
|
||||
if 0 != all_mutual {
|
||||
if can_encrypt {
|
||||
if all_mutual {
|
||||
do_guarantee_e2ee = true;
|
||||
} else if last_msg_in_chat_encrypted(context, &context.sql, self.id) {
|
||||
do_guarantee_e2ee = true;
|
||||
@@ -358,7 +362,15 @@ impl Chat {
|
||||
if do_guarantee_e2ee {
|
||||
msg.param.set_int(Param::GuranteeE2ee, 1);
|
||||
}
|
||||
// reset eg. for forwarding
|
||||
msg.param.remove(Param::ErroneousE2ee);
|
||||
|
||||
// set "In-Reply-To:" to identify the message to which the composed message is a reply;
|
||||
// set "References:" to identify the "thread" of the conversation;
|
||||
// both according to RFC 5322 3.6.4, page 25
|
||||
//
|
||||
// as self-talks are mainly used to transfer data between devices,
|
||||
// we do not set In-Reply-To/References in this case.
|
||||
if !self.is_self_talk() {
|
||||
if let Some((parent_rfc724_mid, parent_in_reply_to, parent_references)) =
|
||||
self.get_parent_mime_headers(context)
|
||||
@@ -366,6 +378,9 @@ impl Chat {
|
||||
if !parent_rfc724_mid.is_empty() {
|
||||
new_in_reply_to = parent_rfc724_mid.clone();
|
||||
}
|
||||
|
||||
// the whole list of messages referenced may be huge;
|
||||
// only use the oldest and and the parent message
|
||||
let parent_references = if let Some(n) = parent_references.find(' ') {
|
||||
&parent_references[0..n]
|
||||
} else {
|
||||
@@ -373,6 +388,7 @@ impl Chat {
|
||||
};
|
||||
|
||||
if !parent_references.is_empty() && !parent_rfc724_mid.is_empty() {
|
||||
// angle brackets are added by the mimefactory later
|
||||
new_references = format!("{} {}", parent_references, parent_rfc724_mid);
|
||||
} else if !parent_references.is_empty() {
|
||||
new_references = parent_references.to_string();
|
||||
@@ -646,6 +662,7 @@ pub fn msgtype_has_file(msgtype: Viewtype) -> bool {
|
||||
match msgtype {
|
||||
Viewtype::Image => true,
|
||||
Viewtype::Gif => true,
|
||||
Viewtype::Sticker => true,
|
||||
Viewtype::Audio => true,
|
||||
Viewtype::Voice => true,
|
||||
Viewtype::Video => true,
|
||||
@@ -783,10 +800,7 @@ pub fn send_msg(context: &Context, chat_id: u32, msg: &mut Message) -> Result<u3
|
||||
message::update_msg_state(context, msg.id, MessageState::OutPending);
|
||||
}
|
||||
|
||||
ensure!(
|
||||
job_send_msg(context, msg.id) != 0,
|
||||
"Failed to initiate send job"
|
||||
);
|
||||
job_send_msg(context, msg.id)?;
|
||||
|
||||
context.call_cb(Event::MsgsChanged {
|
||||
chat_id: msg.chat_id,
|
||||
@@ -805,11 +819,8 @@ pub fn send_msg(context: &Context, chat_id: u32, msg: &mut Message) -> Result<u3
|
||||
if 0 == id {
|
||||
// avoid hanging if user tampers with db
|
||||
break;
|
||||
} else {
|
||||
if let Ok(mut copy) = Message::load_from_db(context, id as u32) {
|
||||
// TODO: handle cleanup and return early instead
|
||||
send_msg(context, 0, &mut copy).unwrap();
|
||||
}
|
||||
} else if let Ok(mut copy) = Message::load_from_db(context, id as u32) {
|
||||
send_msg(context, 0, &mut copy)?;
|
||||
}
|
||||
}
|
||||
msg.param.remove(Param::PrepForwards);
|
||||
@@ -962,10 +973,7 @@ pub fn get_chat_msgs(context: &Context, chat_id: u32, flags: u32, marker1before:
|
||||
};
|
||||
|
||||
let success = if chat_id == 1 {
|
||||
let show_emails = context
|
||||
.sql
|
||||
.get_config_int(context, "show_emails")
|
||||
.unwrap_or_default();
|
||||
let show_emails = context.get_config_int(Config::ShowEmails);
|
||||
context.sql.query_map(
|
||||
"SELECT m.id, m.timestamp FROM msgs m \
|
||||
LEFT JOIN chats ON m.chat_id=chats.id \
|
||||
@@ -1381,11 +1389,10 @@ pub(crate) fn add_contact_to_chat_ex(
|
||||
}
|
||||
if from_handshake && chat.param.get_int(Param::Unpromoted).unwrap_or_default() == 1 {
|
||||
chat.param.remove(Param::Unpromoted);
|
||||
chat.update_param(context).unwrap();
|
||||
chat.update_param(context)?;
|
||||
}
|
||||
let self_addr = context
|
||||
.sql
|
||||
.get_config(context, "configured_addr")
|
||||
.get_config(Config::ConfiguredAddr)
|
||||
.unwrap_or_default();
|
||||
if contact.get_addr() == &self_addr {
|
||||
bail!("invalid attempt to add self e-mail address to group");
|
||||
@@ -1399,14 +1406,14 @@ pub(crate) fn add_contact_to_chat_ex(
|
||||
}
|
||||
} else {
|
||||
// else continue and send status mail
|
||||
if chat.typ == Chattype::VerifiedGroup {
|
||||
if contact.is_verified(context) != VerifiedStatus::BidirectVerified {
|
||||
error!(
|
||||
context,
|
||||
"Only bidirectional verified contacts can be added to verified groups."
|
||||
);
|
||||
return Ok(false);
|
||||
}
|
||||
if chat.typ == Chattype::VerifiedGroup
|
||||
&& contact.is_verified(context) != VerifiedStatus::BidirectVerified
|
||||
{
|
||||
error!(
|
||||
context,
|
||||
"Only bidirectional verified contacts can be added to verified groups."
|
||||
);
|
||||
return Ok(false);
|
||||
}
|
||||
if !add_to_chat_contacts_table(context, chat_id, contact_id) {
|
||||
return Ok(false);
|
||||
@@ -1423,14 +1430,14 @@ pub(crate) fn add_contact_to_chat_ex(
|
||||
msg.param.set_int(Param::Cmd, 4);
|
||||
msg.param.set(Param::Arg, contact.get_addr());
|
||||
msg.param.set_int(Param::Arg2, from_handshake.into());
|
||||
msg.id = send_msg(context, chat_id, &mut msg).unwrap_or_default();
|
||||
msg.id = send_msg(context, chat_id, &mut msg)?;
|
||||
context.call_cb(Event::MsgsChanged {
|
||||
chat_id,
|
||||
msg_id: msg.id,
|
||||
});
|
||||
}
|
||||
context.call_cb(Event::MsgsChanged { chat_id, msg_id: 0 });
|
||||
return Ok(true);
|
||||
Ok(true)
|
||||
}
|
||||
|
||||
fn real_group_exists(context: &Context, chat_id: u32) -> bool {
|
||||
@@ -1517,7 +1524,7 @@ pub fn remove_contact_from_chat(
|
||||
if chat.is_promoted() {
|
||||
msg.type_0 = Viewtype::Text;
|
||||
if contact.id == DC_CONTACT_ID_SELF {
|
||||
set_group_explicitly_left(context, chat.grpid).unwrap();
|
||||
set_group_explicitly_left(context, chat.grpid)?;
|
||||
msg.text = Some(context.stock_system_msg(
|
||||
StockMessage::MsgGroupLeft,
|
||||
"",
|
||||
@@ -1534,7 +1541,7 @@ pub fn remove_contact_from_chat(
|
||||
}
|
||||
msg.param.set_int(Param::Cmd, 5);
|
||||
msg.param.set(Param::Arg, contact.get_addr());
|
||||
msg.id = send_msg(context, chat_id, &mut msg).unwrap_or_default();
|
||||
msg.id = send_msg(context, chat_id, &mut msg)?;
|
||||
context.call_cb(Event::MsgsChanged {
|
||||
chat_id,
|
||||
msg_id: msg.id,
|
||||
@@ -1598,7 +1605,7 @@ pub fn set_chat_name(
|
||||
let mut msg = Message::default();
|
||||
|
||||
if real_group_exists(context, chat_id) {
|
||||
if &chat.name == new_name.as_ref() {
|
||||
if chat.name == new_name.as_ref() {
|
||||
success = true;
|
||||
} else if !is_contact_in_chat(context, chat_id, 1) {
|
||||
emit_event!(
|
||||
@@ -1631,7 +1638,7 @@ pub fn set_chat_name(
|
||||
if !chat.name.is_empty() {
|
||||
msg.param.set(Param::Arg, &chat.name);
|
||||
}
|
||||
msg.id = send_msg(context, chat_id, &mut msg).unwrap_or_default();
|
||||
msg.id = send_msg(context, chat_id, &mut msg)?;
|
||||
context.call_cb(Event::MsgsChanged {
|
||||
chat_id,
|
||||
msg_id: msg.id,
|
||||
@@ -1700,7 +1707,7 @@ pub fn set_chat_profile_image(
|
||||
"",
|
||||
DC_CONTACT_ID_SELF,
|
||||
));
|
||||
msg.id = send_msg(context, chat_id, &mut msg).unwrap_or_default();
|
||||
msg.id = send_msg(context, chat_id, &mut msg)?;
|
||||
emit_event!(
|
||||
context,
|
||||
Event::MsgsChanged {
|
||||
@@ -1716,36 +1723,35 @@ pub fn set_chat_profile_image(
|
||||
bail!("Failed to set profile image");
|
||||
}
|
||||
|
||||
pub fn forward_msgs(context: &Context, msg_ids: &[u32], chat_id: u32) {
|
||||
if msg_ids.is_empty() || chat_id <= DC_CHAT_ID_LAST_SPECIAL {
|
||||
return;
|
||||
}
|
||||
pub fn forward_msgs(context: &Context, msg_ids: &[u32], chat_id: u32) -> Result<(), Error> {
|
||||
ensure!(!msg_ids.is_empty(), "empty msgs_ids: no one to forward to");
|
||||
ensure!(
|
||||
chat_id > DC_CHAT_ID_LAST_SPECIAL,
|
||||
"can not forward to special chat"
|
||||
);
|
||||
|
||||
let mut created_db_entries = Vec::new();
|
||||
let mut curr_timestamp: i64;
|
||||
|
||||
unarchive(context, chat_id).unwrap();
|
||||
unarchive(context, chat_id)?;
|
||||
if let Ok(mut chat) = Chat::load_from_db(context, chat_id) {
|
||||
curr_timestamp = dc_create_smeared_timestamps(context, msg_ids.len());
|
||||
let idsstr = msg_ids
|
||||
.into_iter()
|
||||
.iter()
|
||||
.enumerate()
|
||||
.fold(String::with_capacity(2 * msg_ids.len()), |acc, (i, n)| {
|
||||
(if i == 0 { acc } else { acc + "," }) + &n.to_string()
|
||||
});
|
||||
|
||||
let ids = context
|
||||
.sql
|
||||
.query_map(
|
||||
format!(
|
||||
"SELECT id FROM msgs WHERE id IN({}) ORDER BY timestamp,id",
|
||||
idsstr
|
||||
),
|
||||
params![],
|
||||
|row| row.get::<_, i32>(0),
|
||||
|ids| ids.collect::<Result<Vec<_>, _>>().map_err(Into::into),
|
||||
)
|
||||
.unwrap(); // TODO: better error handling
|
||||
let ids = context.sql.query_map(
|
||||
format!(
|
||||
"SELECT id FROM msgs WHERE id IN({}) ORDER BY timestamp,id",
|
||||
idsstr
|
||||
),
|
||||
params![],
|
||||
|row| row.get::<_, i32>(0),
|
||||
|ids| ids.collect::<Result<Vec<_>, _>>().map_err(Into::into),
|
||||
)?;
|
||||
|
||||
for id in ids {
|
||||
let src_msg_id = id;
|
||||
@@ -1765,7 +1771,7 @@ pub fn forward_msgs(context: &Context, msg_ids: &[u32], chat_id: u32) {
|
||||
let new_msg_id: u32;
|
||||
if msg.state == MessageState::OutPreparing {
|
||||
let fresh9 = curr_timestamp;
|
||||
curr_timestamp = curr_timestamp + 1;
|
||||
curr_timestamp += 1;
|
||||
new_msg_id = chat
|
||||
.prepare_msg_raw(context, &mut msg, fresh9)
|
||||
.unwrap_or_default();
|
||||
@@ -1785,11 +1791,11 @@ pub fn forward_msgs(context: &Context, msg_ids: &[u32], chat_id: u32) {
|
||||
} else {
|
||||
msg.state = MessageState::OutPending;
|
||||
let fresh10 = curr_timestamp;
|
||||
curr_timestamp = curr_timestamp + 1;
|
||||
curr_timestamp += 1;
|
||||
new_msg_id = chat
|
||||
.prepare_msg_raw(context, &mut msg, fresh10)
|
||||
.unwrap_or_default();
|
||||
job_send_msg(context, new_msg_id);
|
||||
job_send_msg(context, new_msg_id)?;
|
||||
}
|
||||
created_db_entries.push(chat_id);
|
||||
created_db_entries.push(new_msg_id);
|
||||
@@ -1802,6 +1808,8 @@ pub fn forward_msgs(context: &Context, msg_ids: &[u32], chat_id: u32) {
|
||||
msg_id: created_db_entries[i + 1],
|
||||
});
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub fn get_chat_contact_cnt(context: &Context, chat_id: u32) -> usize {
|
||||
|
||||
@@ -182,7 +182,7 @@ impl Chatlist {
|
||||
if 0 == listflags & DC_GCL_NO_SPECIALS {
|
||||
let last_deaddrop_fresh_msg_id = get_last_deaddrop_fresh_msg(context);
|
||||
if last_deaddrop_fresh_msg_id > 0 {
|
||||
ids.push((1, last_deaddrop_fresh_msg_id));
|
||||
ids.insert(0, (DC_CHAT_ID_DEADDROP, last_deaddrop_fresh_msg_id));
|
||||
}
|
||||
add_archived_link_item = 1;
|
||||
}
|
||||
@@ -258,13 +258,11 @@ impl Chatlist {
|
||||
let chat_loaded: Chat;
|
||||
let chat = if let Some(chat) = chat {
|
||||
chat
|
||||
} else if let Ok(chat) = Chat::load_from_db(context, self.ids[index].0) {
|
||||
chat_loaded = chat;
|
||||
&chat_loaded
|
||||
} else {
|
||||
if let Ok(chat) = Chat::load_from_db(context, self.ids[index].0) {
|
||||
chat_loaded = chat;
|
||||
&chat_loaded
|
||||
} else {
|
||||
return ret;
|
||||
}
|
||||
return ret;
|
||||
};
|
||||
|
||||
let lastmsg_id = self.ids[index].1;
|
||||
|
||||
@@ -19,10 +19,12 @@ pub enum Config {
|
||||
MailUser,
|
||||
MailPw,
|
||||
MailPort,
|
||||
ImapCertificateChecks,
|
||||
SendServer,
|
||||
SendUser,
|
||||
SendPw,
|
||||
SendPort,
|
||||
SmtpCertificateChecks,
|
||||
ServerFlags,
|
||||
#[strum(props(default = "INBOX"))]
|
||||
ImapFolder,
|
||||
@@ -30,6 +32,8 @@ pub enum Config {
|
||||
Selfstatus,
|
||||
Selfavatar,
|
||||
#[strum(props(default = "1"))]
|
||||
BccSelf,
|
||||
#[strum(props(default = "1"))]
|
||||
E2eeEnabled,
|
||||
#[strum(props(default = "1"))]
|
||||
MdnsEnabled,
|
||||
@@ -50,10 +54,12 @@ pub enum Config {
|
||||
ConfiguredMailPw,
|
||||
ConfiguredMailPort,
|
||||
ConfiguredMailSecurity,
|
||||
ConfiguredImapCertificateChecks,
|
||||
ConfiguredSendServer,
|
||||
ConfiguredSendUser,
|
||||
ConfiguredSendPw,
|
||||
ConfiguredSendPort,
|
||||
ConfiguredSmtpCertificateChecks,
|
||||
ConfiguredServerFlags,
|
||||
ConfiguredSendSecurity,
|
||||
ConfiguredE2EEEnabled,
|
||||
@@ -72,13 +78,13 @@ impl Context {
|
||||
pub fn get_config(&self, key: Config) -> Option<String> {
|
||||
let value = match key {
|
||||
Config::Selfavatar => {
|
||||
let rel_path = self.sql.get_config(self, key);
|
||||
rel_path.map(|p| dc_get_abs_path(self, &p).to_str().unwrap().to_string())
|
||||
let rel_path = self.sql.get_raw_config(self, key);
|
||||
rel_path.map(|p| dc_get_abs_path(self, &p).to_string_lossy().into_owned())
|
||||
}
|
||||
Config::SysVersion => Some((&*DC_VERSION_STR).clone()),
|
||||
Config::SysMsgsizeMaxRecommended => Some(format!("{}", 24 * 1024 * 1024 / 4 * 3)),
|
||||
Config::SysConfigKeys => Some(get_config_keys_string()),
|
||||
_ => self.sql.get_config(self, key),
|
||||
_ => self.sql.get_raw_config(self, key),
|
||||
};
|
||||
|
||||
if value.is_some() {
|
||||
@@ -92,6 +98,16 @@ impl Context {
|
||||
}
|
||||
}
|
||||
|
||||
pub fn get_config_int(&self, key: Config) -> i32 {
|
||||
self.get_config(key)
|
||||
.and_then(|s| s.parse().ok())
|
||||
.unwrap_or_default()
|
||||
}
|
||||
|
||||
pub fn get_config_bool(&self, key: Config) -> bool {
|
||||
self.get_config_int(key) != 0
|
||||
}
|
||||
|
||||
/// Set the given config key.
|
||||
/// If `None` is passed as a value the value is cleared and set to the default if there is one.
|
||||
pub fn set_config(&self, key: Config, value: Option<&str>) -> Result<(), Error> {
|
||||
@@ -99,20 +115,20 @@ impl Context {
|
||||
Config::Selfavatar if value.is_some() => {
|
||||
let rel_path = std::fs::canonicalize(value.unwrap())?;
|
||||
self.sql
|
||||
.set_config(self, key, Some(&rel_path.to_string_lossy()))
|
||||
.set_raw_config(self, key, Some(&rel_path.to_string_lossy()))
|
||||
}
|
||||
Config::InboxWatch => {
|
||||
let ret = self.sql.set_config(self, key, value);
|
||||
let ret = self.sql.set_raw_config(self, key, value);
|
||||
interrupt_imap_idle(self);
|
||||
ret
|
||||
}
|
||||
Config::SentboxWatch => {
|
||||
let ret = self.sql.set_config(self, key, value);
|
||||
let ret = self.sql.set_raw_config(self, key, value);
|
||||
interrupt_sentbox_idle(self);
|
||||
ret
|
||||
}
|
||||
Config::MvboxWatch => {
|
||||
let ret = self.sql.set_config(self, key, value);
|
||||
let ret = self.sql.set_raw_config(self, key, value);
|
||||
interrupt_mvbox_idle(self);
|
||||
ret
|
||||
}
|
||||
@@ -124,9 +140,9 @@ impl Context {
|
||||
value
|
||||
};
|
||||
|
||||
self.sql.set_config(self, key, val)
|
||||
self.sql.set_raw_config(self, key, val)
|
||||
}
|
||||
_ => self.sql.set_config(self, key, value),
|
||||
_ => self.sql.set_raw_config(self, key, value),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,10 +1,8 @@
|
||||
use libc::free;
|
||||
use quick_xml;
|
||||
use quick_xml::events::{BytesEnd, BytesStart, BytesText};
|
||||
|
||||
use crate::constants::*;
|
||||
use crate::context::Context;
|
||||
use crate::dc_tools::*;
|
||||
use crate::login_param::LoginParam;
|
||||
|
||||
use super::read_autoconf_file;
|
||||
@@ -12,51 +10,57 @@ use super::read_autoconf_file;
|
||||
* Thunderbird's Autoconfigure
|
||||
******************************************************************************/
|
||||
/* documentation: https://developer.mozilla.org/en-US/docs/Mozilla/Thunderbird/Autoconfiguration */
|
||||
#[repr(C)]
|
||||
struct moz_autoconfigure_t<'a> {
|
||||
struct MozAutoconfigure<'a> {
|
||||
pub in_0: &'a LoginParam,
|
||||
pub in_emaildomain: &'a str,
|
||||
pub in_emaillocalpart: &'a str,
|
||||
pub out: LoginParam,
|
||||
pub out_imap_set: libc::c_int,
|
||||
pub out_smtp_set: libc::c_int,
|
||||
pub tag_server: libc::c_int,
|
||||
pub tag_config: libc::c_int,
|
||||
pub out_imap_set: bool,
|
||||
pub out_smtp_set: bool,
|
||||
pub tag_server: MozServer,
|
||||
pub tag_config: MozConfigTag,
|
||||
}
|
||||
|
||||
pub unsafe fn moz_autoconfigure(
|
||||
enum MozServer {
|
||||
Undefined,
|
||||
Imap,
|
||||
Smtp,
|
||||
}
|
||||
|
||||
enum MozConfigTag {
|
||||
Undefined,
|
||||
Hostname,
|
||||
Port,
|
||||
Sockettype,
|
||||
Username,
|
||||
}
|
||||
|
||||
pub fn moz_autoconfigure(
|
||||
context: &Context,
|
||||
url: &str,
|
||||
param_in: &LoginParam,
|
||||
) -> Option<LoginParam> {
|
||||
let xml_raw = read_autoconf_file(context, url);
|
||||
if xml_raw.is_null() {
|
||||
return None;
|
||||
}
|
||||
let xml_raw = read_autoconf_file(context, url)?;
|
||||
|
||||
// Split address into local part and domain part.
|
||||
let p = param_in.addr.find("@");
|
||||
if p.is_none() {
|
||||
free(xml_raw as *mut libc::c_void);
|
||||
return None;
|
||||
}
|
||||
let (in_emaillocalpart, in_emaildomain) = param_in.addr.split_at(p.unwrap());
|
||||
let p = param_in.addr.find('@')?;
|
||||
let (in_emaillocalpart, in_emaildomain) = param_in.addr.split_at(p);
|
||||
let in_emaildomain = &in_emaildomain[1..];
|
||||
|
||||
let mut reader = quick_xml::Reader::from_str(as_str(xml_raw));
|
||||
let mut reader = quick_xml::Reader::from_str(&xml_raw);
|
||||
reader.trim_text(true);
|
||||
|
||||
let mut buf = Vec::new();
|
||||
|
||||
let mut moz_ac = moz_autoconfigure_t {
|
||||
let mut moz_ac = MozAutoconfigure {
|
||||
in_0: param_in,
|
||||
in_emaildomain,
|
||||
in_emaillocalpart,
|
||||
out: LoginParam::new(),
|
||||
out_imap_set: 0,
|
||||
out_smtp_set: 0,
|
||||
tag_server: 0,
|
||||
tag_config: 0,
|
||||
out_imap_set: false,
|
||||
out_smtp_set: false,
|
||||
tag_server: MozServer::Undefined,
|
||||
tag_config: MozConfigTag::Undefined,
|
||||
};
|
||||
loop {
|
||||
match reader.read_event(&mut buf) {
|
||||
@@ -68,7 +72,7 @@ pub unsafe fn moz_autoconfigure(
|
||||
moz_autoconfigure_text_cb(e, &mut moz_ac, &reader)
|
||||
}
|
||||
Err(e) => {
|
||||
error!(
|
||||
warn!(
|
||||
context,
|
||||
"Configure xml: Error at position {}: {:?}",
|
||||
reader.buffer_position(),
|
||||
@@ -88,17 +92,15 @@ pub unsafe fn moz_autoconfigure(
|
||||
{
|
||||
let r = moz_ac.out.to_string();
|
||||
warn!(context, "Bad or incomplete autoconfig: {}", r,);
|
||||
free(xml_raw as *mut libc::c_void);
|
||||
return None;
|
||||
}
|
||||
|
||||
free(xml_raw as *mut libc::c_void);
|
||||
Some(moz_ac.out)
|
||||
}
|
||||
|
||||
fn moz_autoconfigure_text_cb<B: std::io::BufRead>(
|
||||
event: &BytesText,
|
||||
moz_ac: &mut moz_autoconfigure_t,
|
||||
moz_ac: &mut MozAutoconfigure,
|
||||
reader: &quick_xml::Reader<B>,
|
||||
) {
|
||||
let val = event.unescape_and_decode(reader).unwrap_or_default();
|
||||
@@ -113,12 +115,12 @@ fn moz_autoconfigure_text_cb<B: std::io::BufRead>(
|
||||
.replace("%EMAILLOCALPART%", email_local)
|
||||
.replace("%EMAILDOMAIN%", email_domain);
|
||||
|
||||
if moz_ac.tag_server == 1 {
|
||||
match moz_ac.tag_config {
|
||||
10 => moz_ac.out.mail_server = val,
|
||||
11 => moz_ac.out.mail_port = val.parse().unwrap_or_default(),
|
||||
12 => moz_ac.out.mail_user = val,
|
||||
13 => {
|
||||
match moz_ac.tag_server {
|
||||
MozServer::Imap => match moz_ac.tag_config {
|
||||
MozConfigTag::Hostname => moz_ac.out.mail_server = val,
|
||||
MozConfigTag::Port => moz_ac.out.mail_port = val.parse().unwrap_or_default(),
|
||||
MozConfigTag::Username => moz_ac.out.mail_user = val,
|
||||
MozConfigTag::Sockettype => {
|
||||
let val_lower = val.to_lowercase();
|
||||
if val_lower == "ssl" {
|
||||
moz_ac.out.server_flags |= DC_LP_IMAP_SOCKET_SSL as i32
|
||||
@@ -131,13 +133,12 @@ fn moz_autoconfigure_text_cb<B: std::io::BufRead>(
|
||||
}
|
||||
}
|
||||
_ => {}
|
||||
}
|
||||
} else if moz_ac.tag_server == 2 {
|
||||
match moz_ac.tag_config {
|
||||
10 => moz_ac.out.send_server = val,
|
||||
11 => moz_ac.out.send_port = val.parse().unwrap_or_default(),
|
||||
12 => moz_ac.out.send_user = val,
|
||||
13 => {
|
||||
},
|
||||
MozServer::Smtp => match moz_ac.tag_config {
|
||||
MozConfigTag::Hostname => moz_ac.out.send_server = val,
|
||||
MozConfigTag::Port => moz_ac.out.send_port = val.parse().unwrap_or_default(),
|
||||
MozConfigTag::Username => moz_ac.out.send_user = val,
|
||||
MozConfigTag::Sockettype => {
|
||||
let val_lower = val.to_lowercase();
|
||||
if val_lower == "ssl" {
|
||||
moz_ac.out.server_flags |= DC_LP_SMTP_SOCKET_SSL as i32
|
||||
@@ -150,29 +151,30 @@ fn moz_autoconfigure_text_cb<B: std::io::BufRead>(
|
||||
}
|
||||
}
|
||||
_ => {}
|
||||
}
|
||||
},
|
||||
MozServer::Undefined => {}
|
||||
}
|
||||
}
|
||||
|
||||
fn moz_autoconfigure_endtag_cb(event: &BytesEnd, moz_ac: &mut moz_autoconfigure_t) {
|
||||
fn moz_autoconfigure_endtag_cb(event: &BytesEnd, moz_ac: &mut MozAutoconfigure) {
|
||||
let tag = String::from_utf8_lossy(event.name()).trim().to_lowercase();
|
||||
|
||||
if tag == "incomingserver" {
|
||||
moz_ac.tag_server = 0;
|
||||
moz_ac.tag_config = 0;
|
||||
moz_ac.out_imap_set = 1;
|
||||
moz_ac.tag_server = MozServer::Undefined;
|
||||
moz_ac.tag_config = MozConfigTag::Undefined;
|
||||
moz_ac.out_imap_set = true;
|
||||
} else if tag == "outgoingserver" {
|
||||
moz_ac.tag_server = 0;
|
||||
moz_ac.tag_config = 0;
|
||||
moz_ac.out_smtp_set = 1;
|
||||
moz_ac.tag_server = MozServer::Undefined;
|
||||
moz_ac.tag_config = MozConfigTag::Undefined;
|
||||
moz_ac.out_smtp_set = true;
|
||||
} else {
|
||||
moz_ac.tag_config = 0;
|
||||
moz_ac.tag_config = MozConfigTag::Undefined;
|
||||
}
|
||||
}
|
||||
|
||||
fn moz_autoconfigure_starttag_cb<B: std::io::BufRead>(
|
||||
event: &BytesStart,
|
||||
moz_ac: &mut moz_autoconfigure_t,
|
||||
moz_ac: &mut MozAutoconfigure,
|
||||
reader: &quick_xml::Reader<B>,
|
||||
) {
|
||||
let tag = String::from_utf8_lossy(event.name()).trim().to_lowercase();
|
||||
@@ -189,25 +191,29 @@ fn moz_autoconfigure_starttag_cb<B: std::io::BufRead>(
|
||||
.unwrap_or_default()
|
||||
.to_lowercase();
|
||||
|
||||
if typ == "imap" && moz_ac.out_imap_set == 0 {
|
||||
1
|
||||
if typ == "imap" && !moz_ac.out_imap_set {
|
||||
MozServer::Imap
|
||||
} else {
|
||||
0
|
||||
MozServer::Undefined
|
||||
}
|
||||
} else {
|
||||
0
|
||||
MozServer::Undefined
|
||||
};
|
||||
moz_ac.tag_config = 0;
|
||||
moz_ac.tag_config = MozConfigTag::Undefined;
|
||||
} else if tag == "outgoingserver" {
|
||||
moz_ac.tag_server = if moz_ac.out_smtp_set == 0 { 2 } else { 0 };
|
||||
moz_ac.tag_config = 0;
|
||||
moz_ac.tag_server = if !moz_ac.out_smtp_set {
|
||||
MozServer::Smtp
|
||||
} else {
|
||||
MozServer::Undefined
|
||||
};
|
||||
moz_ac.tag_config = MozConfigTag::Undefined;
|
||||
} else if tag == "hostname" {
|
||||
moz_ac.tag_config = 10;
|
||||
moz_ac.tag_config = MozConfigTag::Hostname;
|
||||
} else if tag == "port" {
|
||||
moz_ac.tag_config = 11;
|
||||
moz_ac.tag_config = MozConfigTag::Port;
|
||||
} else if tag == "sockettype" {
|
||||
moz_ac.tag_config = 13;
|
||||
moz_ac.tag_config = MozConfigTag::Sockettype;
|
||||
} else if tag == "username" {
|
||||
moz_ac.tag_config = 12;
|
||||
moz_ac.tag_config = MozConfigTag::Username;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,214 +1,157 @@
|
||||
use std::ptr;
|
||||
|
||||
use libc::free;
|
||||
use quick_xml;
|
||||
use quick_xml::events::{BytesEnd, BytesStart, BytesText};
|
||||
use quick_xml::events::BytesEnd;
|
||||
|
||||
use crate::constants::*;
|
||||
use crate::context::Context;
|
||||
use crate::dc_tools::*;
|
||||
use crate::login_param::LoginParam;
|
||||
|
||||
use super::read_autoconf_file;
|
||||
/* ******************************************************************************
|
||||
* Outlook's Autodiscover
|
||||
******************************************************************************/
|
||||
#[repr(C)]
|
||||
struct outlk_autodiscover_t<'a> {
|
||||
pub in_0: &'a LoginParam,
|
||||
|
||||
/// Outlook's Autodiscover
|
||||
struct OutlookAutodiscover {
|
||||
pub out: LoginParam,
|
||||
pub out_imap_set: libc::c_int,
|
||||
pub out_smtp_set: libc::c_int,
|
||||
pub tag_config: libc::c_int,
|
||||
pub config: [*mut libc::c_char; 6],
|
||||
pub redirect: *mut libc::c_char,
|
||||
pub out_imap_set: bool,
|
||||
pub out_smtp_set: bool,
|
||||
pub config_type: Option<String>,
|
||||
pub config_server: String,
|
||||
pub config_port: i32,
|
||||
pub config_ssl: String,
|
||||
pub config_redirecturl: Option<String>,
|
||||
}
|
||||
|
||||
pub unsafe fn outlk_autodiscover(
|
||||
pub fn outlk_autodiscover(
|
||||
context: &Context,
|
||||
url__: &str,
|
||||
param_in: &LoginParam,
|
||||
url: &str,
|
||||
_param_in: &LoginParam,
|
||||
) -> Option<LoginParam> {
|
||||
let mut xml_raw: *mut libc::c_char = ptr::null_mut();
|
||||
let mut url = url__.strdup();
|
||||
let mut outlk_ad = outlk_autodiscover_t {
|
||||
in_0: param_in,
|
||||
out: LoginParam::new(),
|
||||
out_imap_set: 0,
|
||||
out_smtp_set: 0,
|
||||
tag_config: 0,
|
||||
config: [ptr::null_mut(); 6],
|
||||
redirect: ptr::null_mut(),
|
||||
};
|
||||
let ok_to_continue;
|
||||
let mut i = 0;
|
||||
loop {
|
||||
if !(i < 10) {
|
||||
ok_to_continue = true;
|
||||
break;
|
||||
}
|
||||
libc::memset(
|
||||
&mut outlk_ad as *mut outlk_autodiscover_t as *mut libc::c_void,
|
||||
0,
|
||||
::std::mem::size_of::<outlk_autodiscover_t>(),
|
||||
);
|
||||
xml_raw = read_autoconf_file(context, as_str(url));
|
||||
if xml_raw.is_null() {
|
||||
ok_to_continue = false;
|
||||
break;
|
||||
}
|
||||
let mut url = url.to_string();
|
||||
/* Follow up to 10 xml-redirects (http-redirects are followed in read_autoconf_file() */
|
||||
for _i in 0..10 {
|
||||
let mut outlk_ad = OutlookAutodiscover {
|
||||
out: LoginParam::new(),
|
||||
out_imap_set: false,
|
||||
out_smtp_set: false,
|
||||
config_type: None,
|
||||
config_server: String::new(),
|
||||
config_port: 0,
|
||||
config_ssl: String::new(),
|
||||
config_redirecturl: None,
|
||||
};
|
||||
|
||||
let mut reader = quick_xml::Reader::from_str(as_str(xml_raw));
|
||||
reader.trim_text(true);
|
||||
if let Some(xml_raw) = read_autoconf_file(context, &url) {
|
||||
let mut reader = quick_xml::Reader::from_str(&xml_raw);
|
||||
reader.trim_text(true);
|
||||
|
||||
let mut buf = Vec::new();
|
||||
let mut buf = Vec::new();
|
||||
|
||||
loop {
|
||||
match reader.read_event(&mut buf) {
|
||||
Ok(quick_xml::events::Event::Start(ref e)) => {
|
||||
outlk_autodiscover_starttag_cb(e, &mut outlk_ad)
|
||||
let mut current_tag: Option<String> = None;
|
||||
|
||||
loop {
|
||||
match reader.read_event(&mut buf) {
|
||||
Ok(quick_xml::events::Event::Start(ref e)) => {
|
||||
let tag = String::from_utf8_lossy(e.name()).trim().to_lowercase();
|
||||
|
||||
if tag == "protocol" {
|
||||
outlk_ad.config_type = None;
|
||||
outlk_ad.config_server = String::new();
|
||||
outlk_ad.config_port = 0;
|
||||
outlk_ad.config_ssl = String::new();
|
||||
outlk_ad.config_redirecturl = None;
|
||||
|
||||
current_tag = None;
|
||||
} else {
|
||||
current_tag = Some(tag);
|
||||
}
|
||||
}
|
||||
Ok(quick_xml::events::Event::End(ref e)) => {
|
||||
outlk_autodiscover_endtag_cb(e, &mut outlk_ad);
|
||||
current_tag = None;
|
||||
}
|
||||
Ok(quick_xml::events::Event::Text(ref e)) => {
|
||||
let val = e.unescape_and_decode(&reader).unwrap_or_default();
|
||||
|
||||
if let Some(ref tag) = current_tag {
|
||||
match tag.as_str() {
|
||||
"type" => outlk_ad.config_type = Some(val.trim().to_string()),
|
||||
"server" => outlk_ad.config_server = val.trim().to_string(),
|
||||
"port" => {
|
||||
outlk_ad.config_port = val.trim().parse().unwrap_or_default()
|
||||
}
|
||||
"ssl" => outlk_ad.config_ssl = val.trim().to_string(),
|
||||
"redirecturl" => {
|
||||
outlk_ad.config_redirecturl = Some(val.trim().to_string())
|
||||
}
|
||||
_ => {}
|
||||
};
|
||||
}
|
||||
}
|
||||
Err(e) => {
|
||||
error!(
|
||||
context,
|
||||
"Configure xml: Error at position {}: {:?}",
|
||||
reader.buffer_position(),
|
||||
e
|
||||
);
|
||||
}
|
||||
Ok(quick_xml::events::Event::Eof) => break,
|
||||
_ => (),
|
||||
}
|
||||
Ok(quick_xml::events::Event::End(ref e)) => {
|
||||
outlk_autodiscover_endtag_cb(e, &mut outlk_ad)
|
||||
}
|
||||
Ok(quick_xml::events::Event::Text(ref e)) => {
|
||||
outlk_autodiscover_text_cb(e, &mut outlk_ad, &reader)
|
||||
}
|
||||
Err(e) => {
|
||||
error!(
|
||||
context,
|
||||
"Configure xml: Error at position {}: {:?}",
|
||||
reader.buffer_position(),
|
||||
e
|
||||
);
|
||||
}
|
||||
Ok(quick_xml::events::Event::Eof) => break,
|
||||
_ => (),
|
||||
buf.clear();
|
||||
}
|
||||
buf.clear();
|
||||
}
|
||||
|
||||
if !(!outlk_ad.config[5].is_null()
|
||||
&& 0 != *outlk_ad.config[5usize].offset(0isize) as libc::c_int)
|
||||
{
|
||||
ok_to_continue = true;
|
||||
break;
|
||||
}
|
||||
free(url as *mut libc::c_void);
|
||||
url = dc_strdup(outlk_ad.config[5usize]);
|
||||
|
||||
outlk_clean_config(&mut outlk_ad);
|
||||
free(xml_raw as *mut libc::c_void);
|
||||
xml_raw = ptr::null_mut();
|
||||
i += 1;
|
||||
}
|
||||
|
||||
if ok_to_continue {
|
||||
if outlk_ad.out.mail_server.is_empty()
|
||||
|| outlk_ad.out.mail_port == 0
|
||||
|| outlk_ad.out.send_server.is_empty()
|
||||
|| outlk_ad.out.send_port == 0
|
||||
{
|
||||
let r = outlk_ad.out.to_string();
|
||||
warn!(context, "Bad or incomplete autoconfig: {}", r,);
|
||||
free(url as *mut libc::c_void);
|
||||
free(xml_raw as *mut libc::c_void);
|
||||
outlk_clean_config(&mut outlk_ad);
|
||||
|
||||
// XML redirect via redirecturl
|
||||
if outlk_ad.config_redirecturl.is_none()
|
||||
|| outlk_ad.config_redirecturl.as_ref().unwrap().is_empty()
|
||||
{
|
||||
if outlk_ad.out.mail_server.is_empty()
|
||||
|| outlk_ad.out.mail_port == 0
|
||||
|| outlk_ad.out.send_server.is_empty()
|
||||
|| outlk_ad.out.send_port == 0
|
||||
{
|
||||
let r = outlk_ad.out.to_string();
|
||||
warn!(context, "Bad or incomplete autoconfig: {}", r,);
|
||||
return None;
|
||||
}
|
||||
return Some(outlk_ad.out);
|
||||
} else {
|
||||
url = outlk_ad.config_redirecturl.unwrap();
|
||||
}
|
||||
} else {
|
||||
return None;
|
||||
}
|
||||
}
|
||||
free(url as *mut libc::c_void);
|
||||
free(xml_raw as *mut libc::c_void);
|
||||
outlk_clean_config(&mut outlk_ad);
|
||||
Some(outlk_ad.out)
|
||||
None
|
||||
}
|
||||
|
||||
unsafe fn outlk_clean_config(mut outlk_ad: *mut outlk_autodiscover_t) {
|
||||
for i in 0..6 {
|
||||
free((*outlk_ad).config[i] as *mut libc::c_void);
|
||||
(*outlk_ad).config[i] = ptr::null_mut();
|
||||
}
|
||||
}
|
||||
|
||||
fn outlk_autodiscover_text_cb<B: std::io::BufRead>(
|
||||
event: &BytesText,
|
||||
outlk_ad: &mut outlk_autodiscover_t,
|
||||
reader: &quick_xml::Reader<B>,
|
||||
) {
|
||||
let val = event.unescape_and_decode(reader).unwrap_or_default();
|
||||
|
||||
unsafe {
|
||||
free(outlk_ad.config[outlk_ad.tag_config as usize].cast());
|
||||
outlk_ad.config[outlk_ad.tag_config as usize] = val.trim().strdup();
|
||||
}
|
||||
}
|
||||
|
||||
unsafe fn outlk_autodiscover_endtag_cb(event: &BytesEnd, outlk_ad: &mut outlk_autodiscover_t) {
|
||||
fn outlk_autodiscover_endtag_cb(event: &BytesEnd, outlk_ad: &mut OutlookAutodiscover) {
|
||||
let tag = String::from_utf8_lossy(event.name()).trim().to_lowercase();
|
||||
|
||||
if tag == "protocol" {
|
||||
if !outlk_ad.config[1].is_null() {
|
||||
let port = dc_atoi_null_is_0(outlk_ad.config[3]);
|
||||
let ssl_on = (!outlk_ad.config[4].is_null()
|
||||
&& strcasecmp(
|
||||
outlk_ad.config[4],
|
||||
b"on\x00" as *const u8 as *const libc::c_char,
|
||||
) == 0) as libc::c_int;
|
||||
let ssl_off = (!outlk_ad.config[4].is_null()
|
||||
&& strcasecmp(
|
||||
outlk_ad.config[4],
|
||||
b"off\x00" as *const u8 as *const libc::c_char,
|
||||
) == 0) as libc::c_int;
|
||||
if strcasecmp(
|
||||
outlk_ad.config[1],
|
||||
b"imap\x00" as *const u8 as *const libc::c_char,
|
||||
) == 0
|
||||
&& outlk_ad.out_imap_set == 0
|
||||
{
|
||||
outlk_ad.out.mail_server = to_string(outlk_ad.config[2]);
|
||||
if let Some(type_) = &outlk_ad.config_type {
|
||||
let port = outlk_ad.config_port;
|
||||
let ssl_on = outlk_ad.config_ssl == "on";
|
||||
let ssl_off = outlk_ad.config_ssl == "off";
|
||||
if type_ == "imap" && !outlk_ad.out_imap_set {
|
||||
outlk_ad.out.mail_server =
|
||||
std::mem::replace(&mut outlk_ad.config_server, String::new());
|
||||
outlk_ad.out.mail_port = port;
|
||||
if 0 != ssl_on {
|
||||
if ssl_on {
|
||||
outlk_ad.out.server_flags |= DC_LP_IMAP_SOCKET_SSL as i32
|
||||
} else if 0 != ssl_off {
|
||||
} else if ssl_off {
|
||||
outlk_ad.out.server_flags |= DC_LP_IMAP_SOCKET_PLAIN as i32
|
||||
}
|
||||
outlk_ad.out_imap_set = 1
|
||||
} else if strcasecmp(
|
||||
outlk_ad.config[1usize],
|
||||
b"smtp\x00" as *const u8 as *const libc::c_char,
|
||||
) == 0
|
||||
&& outlk_ad.out_smtp_set == 0
|
||||
{
|
||||
outlk_ad.out.send_server = to_string(outlk_ad.config[2]);
|
||||
outlk_ad.out.send_port = port;
|
||||
if 0 != ssl_on {
|
||||
outlk_ad.out_imap_set = true
|
||||
} else if type_ == "smtp" && !outlk_ad.out_smtp_set {
|
||||
outlk_ad.out.send_server =
|
||||
std::mem::replace(&mut outlk_ad.config_server, String::new());
|
||||
outlk_ad.out.send_port = outlk_ad.config_port;
|
||||
if ssl_on {
|
||||
outlk_ad.out.server_flags |= DC_LP_SMTP_SOCKET_SSL as i32
|
||||
} else if 0 != ssl_off {
|
||||
} else if ssl_off {
|
||||
outlk_ad.out.server_flags |= DC_LP_SMTP_SOCKET_PLAIN as i32
|
||||
}
|
||||
outlk_ad.out_smtp_set = 1
|
||||
outlk_ad.out_smtp_set = true
|
||||
}
|
||||
}
|
||||
outlk_clean_config(outlk_ad);
|
||||
}
|
||||
outlk_ad.tag_config = 0;
|
||||
}
|
||||
|
||||
fn outlk_autodiscover_starttag_cb(event: &BytesStart, outlk_ad: &mut outlk_autodiscover_t) {
|
||||
let tag = String::from_utf8_lossy(event.name()).trim().to_lowercase();
|
||||
|
||||
if tag == "protocol" {
|
||||
unsafe { outlk_clean_config(outlk_ad) };
|
||||
} else if tag == "type" {
|
||||
outlk_ad.tag_config = 1
|
||||
} else if tag == "server" {
|
||||
outlk_ad.tag_config = 2
|
||||
} else if tag == "port" {
|
||||
outlk_ad.tag_config = 3
|
||||
} else if tag == "ssl" {
|
||||
outlk_ad.tag_config = 4
|
||||
} else if tag == "redirecturl" {
|
||||
outlk_ad.tag_config = 5
|
||||
};
|
||||
}
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -25,7 +25,6 @@ impl Default for MoveState {
|
||||
|
||||
// some defaults
|
||||
const DC_E2EE_DEFAULT_ENABLED: i32 = 1;
|
||||
pub const DC_MDNS_DEFAULT_ENABLED: i32 = 1;
|
||||
const DC_INBOX_WATCH_DEFAULT: i32 = 1;
|
||||
const DC_SENTBOX_WATCH_DEFAULT: i32 = 1;
|
||||
const DC_MVBOX_WATCH_DEFAULT: i32 = 1;
|
||||
@@ -130,23 +129,23 @@ pub const DC_CREATE_MVBOX: usize = 1;
|
||||
/// Force OAuth2 authorization. This flag does not skip automatic configuration.
|
||||
/// Before calling configure() with DC_LP_AUTH_OAUTH2 set,
|
||||
/// the user has to confirm access at the URL returned by dc_get_oauth2_url().
|
||||
pub const DC_LP_AUTH_OAUTH2: usize = 0x2;
|
||||
pub const DC_LP_AUTH_OAUTH2: i32 = 0x2;
|
||||
|
||||
/// Force NORMAL authorization, this is the default.
|
||||
/// If this flag is set, automatic configuration is skipped.
|
||||
pub const DC_LP_AUTH_NORMAL: usize = 0x4;
|
||||
pub const DC_LP_AUTH_NORMAL: i32 = 0x4;
|
||||
|
||||
/// Connect to IMAP via STARTTLS.
|
||||
/// If this flag is set, automatic configuration is skipped.
|
||||
pub const DC_LP_IMAP_SOCKET_STARTTLS: usize = 0x100;
|
||||
pub const DC_LP_IMAP_SOCKET_STARTTLS: i32 = 0x100;
|
||||
|
||||
/// Connect to IMAP via SSL.
|
||||
/// If this flag is set, automatic configuration is skipped.
|
||||
pub const DC_LP_IMAP_SOCKET_SSL: usize = 0x200;
|
||||
pub const DC_LP_IMAP_SOCKET_SSL: i32 = 0x200;
|
||||
|
||||
/// Connect to IMAP unencrypted, this should not be used.
|
||||
/// If this flag is set, automatic configuration is skipped.
|
||||
pub const DC_LP_IMAP_SOCKET_PLAIN: usize = 0x400;
|
||||
pub const DC_LP_IMAP_SOCKET_PLAIN: i32 = 0x400;
|
||||
|
||||
/// Connect to SMTP via STARTTLS.
|
||||
/// If this flag is set, automatic configuration is skipped.
|
||||
@@ -161,9 +160,9 @@ pub const DC_LP_SMTP_SOCKET_SSL: usize = 0x20000;
|
||||
pub const DC_LP_SMTP_SOCKET_PLAIN: usize = 0x40000;
|
||||
|
||||
/// if none of these flags are set, the default is chosen
|
||||
pub const DC_LP_AUTH_FLAGS: usize = (DC_LP_AUTH_OAUTH2 | DC_LP_AUTH_NORMAL);
|
||||
pub const DC_LP_AUTH_FLAGS: i32 = (DC_LP_AUTH_OAUTH2 | DC_LP_AUTH_NORMAL);
|
||||
/// if none of these flags are set, the default is chosen
|
||||
pub const DC_LP_IMAP_SOCKET_FLAGS: usize =
|
||||
pub const DC_LP_IMAP_SOCKET_FLAGS: i32 =
|
||||
(DC_LP_IMAP_SOCKET_STARTTLS | DC_LP_IMAP_SOCKET_SSL | DC_LP_IMAP_SOCKET_PLAIN);
|
||||
/// if none of these flags are set, the default is chosen
|
||||
pub const DC_LP_SMTP_SOCKET_FLAGS: usize =
|
||||
@@ -195,6 +194,11 @@ pub enum Viewtype {
|
||||
/// and retrieved via dc_msg_get_file(), dc_msg_get_width(), dc_msg_get_height().
|
||||
Gif = 21,
|
||||
|
||||
/// Message containing a sticker, similar to image.
|
||||
/// If possible, the ui should display the image without borders in a transparent way.
|
||||
/// A click on a sticker will offer to install the sticker set in some future.
|
||||
Sticker = 23,
|
||||
|
||||
/// Message containing an Audio file.
|
||||
/// File and duration are set via dc_msg_set_file(), dc_msg_set_duration()
|
||||
/// and retrieved via dc_msg_get_file(), dc_msg_get_duration().
|
||||
@@ -253,7 +257,7 @@ const DC_SHOW_EMAILS_ACCEPTED_CONTACTS: usize = 1;
|
||||
const DC_SHOW_EMAILS_ALL: usize = 2;
|
||||
|
||||
// TODO: Strings need some doumentation about used placeholders.
|
||||
// These constants are used to request strings using #DC_EVENT_GET_STRING.
|
||||
// These constants are used to set stock translation strings
|
||||
|
||||
const DC_STR_NOMESSAGES: usize = 1;
|
||||
const DC_STR_SELF: usize = 2;
|
||||
@@ -299,7 +303,8 @@ const DC_STR_MSGACTIONBYME: usize = 63;
|
||||
const DC_STR_MSGLOCATIONENABLED: usize = 64;
|
||||
const DC_STR_MSGLOCATIONDISABLED: usize = 65;
|
||||
const DC_STR_LOCATION: usize = 66;
|
||||
const DC_STR_COUNT: usize = 66;
|
||||
const DC_STR_STICKER: usize = 67;
|
||||
const DC_STR_COUNT: usize = 67;
|
||||
|
||||
pub const DC_JOB_DELETE_MSG_ON_IMAP: i32 = 110;
|
||||
|
||||
|
||||
@@ -390,20 +390,18 @@ impl Contact {
|
||||
}
|
||||
sth_modified = Modifier::Modified;
|
||||
}
|
||||
} else if sql::execute(
|
||||
context,
|
||||
&context.sql,
|
||||
"INSERT INTO contacts (name, addr, origin) VALUES(?, ?, ?);",
|
||||
params![name.as_ref(), addr, origin,],
|
||||
)
|
||||
.is_ok()
|
||||
{
|
||||
row_id = sql::get_rowid(context, &context.sql, "contacts", "addr", addr);
|
||||
sth_modified = Modifier::Created;
|
||||
} else {
|
||||
if sql::execute(
|
||||
context,
|
||||
&context.sql,
|
||||
"INSERT INTO contacts (name, addr, origin) VALUES(?, ?, ?);",
|
||||
params![name.as_ref(), addr, origin,],
|
||||
)
|
||||
.is_ok()
|
||||
{
|
||||
row_id = sql::get_rowid(context, &context.sql, "contacts", "addr", addr);
|
||||
sth_modified = Modifier::Created;
|
||||
} else {
|
||||
error!(context, "Cannot add contact.");
|
||||
}
|
||||
error!(context, "Cannot add contact.");
|
||||
}
|
||||
|
||||
Ok((row_id, sth_modified))
|
||||
@@ -827,7 +825,7 @@ impl Contact {
|
||||
if let Ok(contact) = Contact::load_from_db(context, contact_id) {
|
||||
if !contact.addr.is_empty() {
|
||||
let normalized_addr = addr_normalize(addr.as_ref());
|
||||
if &contact.addr == &normalized_addr {
|
||||
if contact.addr == normalized_addr {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
@@ -963,9 +961,9 @@ pub fn normalize_name(full_name: impl AsRef<str>) -> String {
|
||||
if len > 0 {
|
||||
let firstchar = full_name.as_bytes()[0];
|
||||
let lastchar = full_name.as_bytes()[len - 1];
|
||||
if firstchar == '\'' as u8 && lastchar == '\'' as u8
|
||||
|| firstchar == '\"' as u8 && lastchar == '\"' as u8
|
||||
|| firstchar == '<' as u8 && lastchar == '>' as u8
|
||||
if firstchar == b'\'' && lastchar == b'\''
|
||||
|| firstchar == b'\"' && lastchar == b'\"'
|
||||
|| firstchar == b'<' && lastchar == b'>'
|
||||
{
|
||||
full_name = &full_name[1..len - 1];
|
||||
}
|
||||
|
||||
230
src/context.rs
230
src/context.rs
@@ -1,13 +1,17 @@
|
||||
use std::collections::HashMap;
|
||||
use std::ffi::OsString;
|
||||
use std::fs;
|
||||
use std::io::Write;
|
||||
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::*;
|
||||
use crate::contact::*;
|
||||
use crate::dc_tools::{dc_copy_file, dc_derive_safe_stem_ext};
|
||||
use crate::error::*;
|
||||
use crate::events::Event;
|
||||
use crate::imap::*;
|
||||
@@ -20,6 +24,7 @@ use crate::message::{self, Message};
|
||||
use crate::param::Params;
|
||||
use crate::smtp::*;
|
||||
use crate::sql::Sql;
|
||||
use rand::{thread_rng, Rng};
|
||||
|
||||
/// Callback function type for [Context]
|
||||
///
|
||||
@@ -58,12 +63,13 @@ pub struct Context {
|
||||
pub running_state: Arc<RwLock<RunningState>>,
|
||||
/// Mutex to avoid generating the key for the user more than once.
|
||||
pub generating_key_mutex: Mutex<()>,
|
||||
pub translated_stockstrings: RwLock<HashMap<usize, String>>,
|
||||
}
|
||||
|
||||
#[derive(Debug, PartialEq, Eq)]
|
||||
pub struct RunningState {
|
||||
pub ongoing_running: bool,
|
||||
pub shall_stop_ongoing: bool,
|
||||
shall_stop_ongoing: bool,
|
||||
}
|
||||
|
||||
/// Return some info about deltachat-core
|
||||
@@ -140,6 +146,7 @@ impl Context {
|
||||
probe_imap_network: Arc::new(RwLock::new(false)),
|
||||
perform_inbox_jobs_needed: Arc::new(RwLock::new(false)),
|
||||
generating_key_mutex: Mutex::new(()),
|
||||
translated_stockstrings: RwLock::new(HashMap::new()),
|
||||
};
|
||||
|
||||
ensure!(
|
||||
@@ -158,35 +165,141 @@ impl Context {
|
||||
self.blobdir.as_path()
|
||||
}
|
||||
|
||||
pub fn copy_to_blobdir(&self, orig_filename: impl AsRef<str>) -> Result<String> {
|
||||
// return a $BLOBDIR/<filename> with the content of orig_filename
|
||||
// copied into it. The <filename> will be safely derived from
|
||||
// orig_filename, and will not clash with existing filenames.
|
||||
let dest = self.new_blob_file(&orig_filename, b"")?;
|
||||
if dc_copy_file(
|
||||
&self,
|
||||
PathBuf::from(orig_filename.as_ref()),
|
||||
PathBuf::from(&dest),
|
||||
) {
|
||||
Ok(dest)
|
||||
} else {
|
||||
bail!("could not copy {} to {}", orig_filename.as_ref(), dest);
|
||||
}
|
||||
}
|
||||
|
||||
pub fn new_blob_file(&self, orig_filename: impl AsRef<str>, data: &[u8]) -> Result<String> {
|
||||
// return a $BLOBDIR/<FILENAME> string which corresponds to the
|
||||
// respective file in the blobdir, and which contains the data.
|
||||
// FILENAME is computed by looking and possibly mangling the
|
||||
// basename of orig_filename. The resulting filenames are meant
|
||||
// to be human-readable.
|
||||
let (stem, ext) = dc_derive_safe_stem_ext(orig_filename.as_ref());
|
||||
|
||||
// ext starts with "." or is empty string, so we can always resconstruct
|
||||
|
||||
for i in 0..3 {
|
||||
let candidate_basename = match i {
|
||||
// first a try to just use the (possibly mangled) original basename
|
||||
0 => format!("{}{}", stem, ext),
|
||||
|
||||
// otherwise extend stem with random numbers
|
||||
_ => {
|
||||
let mut rng = thread_rng();
|
||||
let random_id: u32 = rng.gen();
|
||||
format!("{}-{}{}", stem, random_id, ext)
|
||||
}
|
||||
};
|
||||
let path = self.get_blobdir().join(&candidate_basename);
|
||||
if let Ok(mut file) = fs::OpenOptions::new()
|
||||
.create_new(true)
|
||||
.write(true)
|
||||
.open(&path)
|
||||
{
|
||||
file.write_all(data)?;
|
||||
let db_entry = format!("$BLOBDIR/{}", candidate_basename);
|
||||
self.call_cb(Event::NewBlobFile(db_entry.clone()));
|
||||
return Ok(db_entry);
|
||||
}
|
||||
}
|
||||
bail!("out of luck to create new blob file");
|
||||
}
|
||||
|
||||
pub fn call_cb(&self, event: Event) -> uintptr_t {
|
||||
(*self.cb)(self, event)
|
||||
}
|
||||
|
||||
/*******************************************************************************
|
||||
* Ongoing process allocation/free/check
|
||||
******************************************************************************/
|
||||
|
||||
pub fn alloc_ongoing(&self) -> bool {
|
||||
if self.has_ongoing() {
|
||||
warn!(self, "There is already another ongoing process running.",);
|
||||
|
||||
false
|
||||
} else {
|
||||
let s_a = self.running_state.clone();
|
||||
let mut s = s_a.write().unwrap();
|
||||
|
||||
s.ongoing_running = true;
|
||||
s.shall_stop_ongoing = false;
|
||||
|
||||
true
|
||||
}
|
||||
}
|
||||
|
||||
pub fn free_ongoing(&self) {
|
||||
let s_a = self.running_state.clone();
|
||||
let mut s = s_a.write().unwrap();
|
||||
|
||||
s.ongoing_running = false;
|
||||
s.shall_stop_ongoing = true;
|
||||
}
|
||||
|
||||
pub fn has_ongoing(&self) -> bool {
|
||||
let s_a = self.running_state.clone();
|
||||
let s = s_a.read().unwrap();
|
||||
|
||||
s.ongoing_running || !s.shall_stop_ongoing
|
||||
}
|
||||
|
||||
/// Signal an ongoing process to stop.
|
||||
pub fn stop_ongoing(&self) {
|
||||
let s_a = self.running_state.clone();
|
||||
let mut s = s_a.write().unwrap();
|
||||
|
||||
if s.ongoing_running && !s.shall_stop_ongoing {
|
||||
info!(self, "Signaling the ongoing process to stop ASAP.",);
|
||||
s.shall_stop_ongoing = true;
|
||||
} else {
|
||||
info!(self, "No ongoing process to stop.",);
|
||||
};
|
||||
}
|
||||
|
||||
pub fn shall_stop_ongoing(&self) -> bool {
|
||||
self.running_state
|
||||
.clone()
|
||||
.read()
|
||||
.unwrap()
|
||||
.shall_stop_ongoing
|
||||
}
|
||||
|
||||
/*******************************************************************************
|
||||
* UI chat/message related API
|
||||
******************************************************************************/
|
||||
|
||||
pub fn get_info(&self) -> HashMap<&'static str, String> {
|
||||
let unset = "0";
|
||||
let l = LoginParam::from_database(self, "");
|
||||
let l2 = LoginParam::from_database(self, "configured_");
|
||||
let displayname = self.sql.get_config(self, "displayname");
|
||||
let displayname = self.get_config(Config::Displayname);
|
||||
let chats = get_chat_cnt(self) as usize;
|
||||
let real_msgs = message::get_real_msg_cnt(self) as usize;
|
||||
let deaddrop_msgs = message::get_deaddrop_msg_cnt(self) as usize;
|
||||
let contacts = Contact::get_real_cnt(self) as usize;
|
||||
let is_configured = self
|
||||
.sql
|
||||
.get_config_int(self, "configured")
|
||||
.unwrap_or_default();
|
||||
let is_configured = self.get_config_int(Config::Configured);
|
||||
let dbversion = self
|
||||
.sql
|
||||
.get_config_int(self, "dbversion")
|
||||
.get_raw_config_int(self, "dbversion")
|
||||
.unwrap_or_default();
|
||||
let e2ee_enabled = self
|
||||
.sql
|
||||
.get_config_int(self, "e2ee_enabled")
|
||||
.unwrap_or_else(|| 1);
|
||||
let mdns_enabled = self
|
||||
.sql
|
||||
.get_config_int(self, "mdns_enabled")
|
||||
.unwrap_or_else(|| 1);
|
||||
|
||||
let e2ee_enabled = self.get_config_int(Config::E2eeEnabled);
|
||||
let mdns_enabled = self.get_config_int(Config::MdnsEnabled);
|
||||
let bcc_self = self.get_config_int(Config::BccSelf);
|
||||
|
||||
let prv_key_cnt: Option<isize> =
|
||||
self.sql
|
||||
@@ -204,33 +317,22 @@ impl Context {
|
||||
"<Not yet calculated>".into()
|
||||
};
|
||||
|
||||
let inbox_watch = self
|
||||
.sql
|
||||
.get_config_int(self, "inbox_watch")
|
||||
.unwrap_or_else(|| 1);
|
||||
let sentbox_watch = self
|
||||
.sql
|
||||
.get_config_int(self, "sentbox_watch")
|
||||
.unwrap_or_else(|| 1);
|
||||
let mvbox_watch = self
|
||||
.sql
|
||||
.get_config_int(self, "mvbox_watch")
|
||||
.unwrap_or_else(|| 1);
|
||||
let mvbox_move = self
|
||||
.sql
|
||||
.get_config_int(self, "mvbox_move")
|
||||
.unwrap_or_else(|| 1);
|
||||
let inbox_watch = self.get_config_int(Config::InboxWatch);
|
||||
let sentbox_watch = self.get_config_int(Config::SentboxWatch);
|
||||
let mvbox_watch = self.get_config_int(Config::MvboxWatch);
|
||||
let mvbox_move = self.get_config_int(Config::MvboxMove);
|
||||
let folders_configured = self
|
||||
.sql
|
||||
.get_config_int(self, "folders_configured")
|
||||
.get_raw_config_int(self, "folders_configured")
|
||||
.unwrap_or_default();
|
||||
|
||||
let configured_sentbox_folder = self
|
||||
.sql
|
||||
.get_config(self, "configured_sentbox_folder")
|
||||
.get_raw_config(self, "configured_sentbox_folder")
|
||||
.unwrap_or_else(|| "<unset>".to_string());
|
||||
let configured_mvbox_folder = self
|
||||
.sql
|
||||
.get_config(self, "configured_mvbox_folder")
|
||||
.get_raw_config(self, "configured_mvbox_folder")
|
||||
.unwrap_or_else(|| "<unset>".to_string());
|
||||
|
||||
let mut res = get_info();
|
||||
@@ -254,6 +356,7 @@ impl Context {
|
||||
res.insert("configured_mvbox_folder", configured_mvbox_folder);
|
||||
res.insert("mdns_enabled", mdns_enabled.to_string());
|
||||
res.insert("e2ee_enabled", e2ee_enabled.to_string());
|
||||
res.insert("bcc_self", bcc_self.to_string());
|
||||
res.insert(
|
||||
"private_key_count",
|
||||
prv_key_cnt.unwrap_or_default().to_string(),
|
||||
@@ -289,7 +392,7 @@ impl Context {
|
||||
Ok(ret)
|
||||
},
|
||||
)
|
||||
.unwrap()
|
||||
.unwrap_or_default()
|
||||
}
|
||||
|
||||
#[allow(non_snake_case)]
|
||||
@@ -333,7 +436,7 @@ impl Context {
|
||||
}
|
||||
|
||||
pub fn is_sentbox(&self, folder_name: impl AsRef<str>) -> bool {
|
||||
let sentbox_name = self.sql.get_config(self, "configured_sentbox_folder");
|
||||
let sentbox_name = self.sql.get_raw_config(self, "configured_sentbox_folder");
|
||||
if let Some(name) = sentbox_name {
|
||||
name == folder_name.as_ref()
|
||||
} else {
|
||||
@@ -342,7 +445,7 @@ impl Context {
|
||||
}
|
||||
|
||||
pub fn is_mvbox(&self, folder_name: impl AsRef<str>) -> bool {
|
||||
let mvbox_name = self.sql.get_config(self, "configured_mvbox_folder");
|
||||
let mvbox_name = self.sql.get_raw_config(self, "configured_mvbox_folder");
|
||||
|
||||
if let Some(name) = mvbox_name {
|
||||
name == folder_name.as_ref()
|
||||
@@ -352,12 +455,7 @@ impl Context {
|
||||
}
|
||||
|
||||
pub fn do_heuristics_moves(&self, folder: &str, msg_id: u32) {
|
||||
if self
|
||||
.sql
|
||||
.get_config_int(self, "mvbox_move")
|
||||
.unwrap_or_else(|| 1)
|
||||
== 0
|
||||
{
|
||||
if !self.get_config_bool(Config::MvboxMove) {
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -438,6 +536,7 @@ pub fn get_version_str() -> &'static str {
|
||||
mod tests {
|
||||
use super::*;
|
||||
|
||||
use crate::dc_tools::*;
|
||||
use crate::test_utils::*;
|
||||
|
||||
#[test]
|
||||
@@ -475,6 +574,51 @@ mod tests {
|
||||
assert!(res.is_err());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_new_blob_file() {
|
||||
let t = dummy_context();
|
||||
let context = t.ctx;
|
||||
let x = &context.new_blob_file("hello", b"data").unwrap();
|
||||
assert!(dc_file_exist(&context, x));
|
||||
assert!(x.starts_with("$BLOBDIR"));
|
||||
assert!(dc_read_file(&context, x).unwrap() == b"data");
|
||||
|
||||
let y = &context.new_blob_file("hello", b"data").unwrap();
|
||||
assert!(dc_file_exist(&context, y));
|
||||
assert!(y.starts_with("$BLOBDIR/hello-"));
|
||||
|
||||
let x = &context.new_blob_file("xyz/hello.png", b"data").unwrap();
|
||||
assert!(dc_file_exist(&context, x));
|
||||
assert_eq!(x, "$BLOBDIR/hello.png");
|
||||
|
||||
let y = &context.new_blob_file("hello\\world.png", b"data").unwrap();
|
||||
assert!(dc_file_exist(&context, y));
|
||||
assert_eq!(y, "$BLOBDIR/world.png");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_new_blob_file_long_names() {
|
||||
let t = dummy_context();
|
||||
let context = t.ctx;
|
||||
let s = "12312312039182039182039812039810293810293810293810293801293801293123123";
|
||||
let x = &context.new_blob_file(s, b"data").unwrap();
|
||||
println!("blobfilename '{}'", x);
|
||||
println!("xxxxfilename '{}'", s);
|
||||
assert!(x.len() < s.len());
|
||||
assert!(dc_file_exist(&context, x));
|
||||
assert!(x.starts_with("$BLOBDIR"));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_new_blob_file_unicode() {
|
||||
let t = dummy_context();
|
||||
let context = t.ctx;
|
||||
let s = "helloäworld.qwe";
|
||||
let x = &context.new_blob_file(s, b"data").unwrap();
|
||||
assert_eq!(x, "$BLOBDIR/hello-world.qwe");
|
||||
assert_eq!(dc_read_file(&context, x).unwrap(), b"data");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_sqlite_parent_not_exists() {
|
||||
let tmp = tempfile::tempdir().unwrap();
|
||||
|
||||
@@ -11,7 +11,6 @@ use mmime::mailmime::content::*;
|
||||
use mmime::mailmime::disposition::*;
|
||||
use mmime::mailmime::types::*;
|
||||
use mmime::mailmime::*;
|
||||
use mmime::mmapstring::*;
|
||||
use mmime::other::*;
|
||||
|
||||
use crate::constants::Viewtype;
|
||||
@@ -25,6 +24,7 @@ use crate::error::Error;
|
||||
use crate::location;
|
||||
use crate::param::*;
|
||||
use crate::stock::StockMessage;
|
||||
use crate::wrapmime;
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct MimeParser<'a> {
|
||||
@@ -127,7 +127,7 @@ impl<'a> MimeParser<'a> {
|
||||
if (*field).fld_type == MAILIMF_FIELD_SUBJECT as libc::c_int {
|
||||
let subj = (*(*field).fld_data.fld_subject).sbj_value;
|
||||
|
||||
self.subject = as_opt_str(subj).map(dc_decode_header_words_safe);
|
||||
self.subject = as_opt_str(subj).map(dc_decode_header_words);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -164,21 +164,17 @@ impl<'a> MimeParser<'a> {
|
||||
}
|
||||
}
|
||||
}
|
||||
} else {
|
||||
if let Some(optional_field) = self.lookup_optional_field("Chat-Content") {
|
||||
if optional_field == "location-streaming-enabled" {
|
||||
self.is_system_message = SystemMessage::LocationStreamingEnabled;
|
||||
}
|
||||
} else if let Some(optional_field) = self.lookup_optional_field("Chat-Content") {
|
||||
if optional_field == "location-streaming-enabled" {
|
||||
self.is_system_message = SystemMessage::LocationStreamingEnabled;
|
||||
}
|
||||
}
|
||||
if self.lookup_field("Chat-Group-Image").is_some() && !self.parts.is_empty() {
|
||||
let textpart = &self.parts[0];
|
||||
if textpart.typ == Viewtype::Text {
|
||||
if self.parts.len() >= 2 {
|
||||
let imgpart = &mut self.parts[1];
|
||||
if imgpart.typ == Viewtype::Image {
|
||||
imgpart.is_meta = true;
|
||||
}
|
||||
if textpart.typ == Viewtype::Text && self.parts.len() >= 2 {
|
||||
let imgpart = &mut self.parts[1];
|
||||
if imgpart.typ == Viewtype::Image {
|
||||
imgpart.is_meta = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -189,6 +185,7 @@ impl<'a> MimeParser<'a> {
|
||||
textpart.typ == Viewtype::Text
|
||||
&& (filepart.typ == Viewtype::Image
|
||||
|| filepart.typ == Viewtype::Gif
|
||||
|| filepart.typ == Viewtype::Sticker
|
||||
|| filepart.typ == Viewtype::Audio
|
||||
|| filepart.typ == Viewtype::Voice
|
||||
|| filepart.typ == Viewtype::Video
|
||||
@@ -256,6 +253,14 @@ impl<'a> MimeParser<'a> {
|
||||
part_mut.typ = Viewtype::Voice;
|
||||
}
|
||||
}
|
||||
if self.parts[0].typ == Viewtype::Image {
|
||||
if let Some(content_type) = self.lookup_optional_field("Chat-Content") {
|
||||
if content_type == "sticker" {
|
||||
let part_mut = &mut self.parts[0];
|
||||
part_mut.typ = Viewtype::Sticker;
|
||||
}
|
||||
}
|
||||
}
|
||||
let part = &self.parts[0];
|
||||
if part.typ == Viewtype::Audio
|
||||
|| part.typ == Viewtype::Voice
|
||||
@@ -277,7 +282,7 @@ impl<'a> MimeParser<'a> {
|
||||
if self.get_last_nonmeta().is_some() {
|
||||
let mut mb_list: *mut mailimf_mailbox_list = ptr::null_mut();
|
||||
let mut index_0 = 0;
|
||||
let dn_field_c = CString::new(dn_field).unwrap();
|
||||
let dn_field_c = CString::new(dn_field).unwrap_or_default();
|
||||
|
||||
if mailimf_mailbox_list_parse(
|
||||
dn_field_c.as_ptr(),
|
||||
@@ -287,12 +292,12 @@ impl<'a> MimeParser<'a> {
|
||||
) == MAILIMF_NO_ERROR as libc::c_int
|
||||
&& !mb_list.is_null()
|
||||
{
|
||||
if let Some(dn_to_addr) = mailimf_find_first_addr(mb_list) {
|
||||
if let Some(dn_to_addr) = wrapmime::mailimf_find_first_addr(mb_list) {
|
||||
if let Some(from_field) = self.lookup_field("From") {
|
||||
if (*from_field).fld_type == MAILIMF_FIELD_FROM as libc::c_int
|
||||
&& !(*from_field).fld_data.fld_from.is_null()
|
||||
{
|
||||
let from_addr = mailimf_find_first_addr(
|
||||
let from_addr = wrapmime::mailimf_find_first_addr(
|
||||
(*(*from_field).fld_data.fld_from).frm_mb_list,
|
||||
);
|
||||
if let Some(from_addr) = from_addr {
|
||||
@@ -590,7 +595,7 @@ impl<'a> MimeParser<'a> {
|
||||
return false;
|
||||
}
|
||||
|
||||
let mut decoded_data = match mailmime_transfer_decode(mime) {
|
||||
let mut decoded_data = match wrapmime::mailmime_transfer_decode(mime) {
|
||||
Ok(decoded_data) => decoded_data,
|
||||
Err(_) => {
|
||||
// Note that it's now always an error - might be no data
|
||||
@@ -601,7 +606,6 @@ impl<'a> MimeParser<'a> {
|
||||
let old_part_count = self.parts.len();
|
||||
|
||||
/* regard `Content-Transfer-Encoding:` */
|
||||
let mut ok_to_continue = true;
|
||||
let mut desired_filename = String::default();
|
||||
let mut simplifier: Option<Simplify> = None;
|
||||
match mime_type {
|
||||
@@ -616,15 +620,14 @@ impl<'a> MimeParser<'a> {
|
||||
&& strcmp(charset, b"UTF-8\x00" as *const u8 as *const libc::c_char) != 0i32
|
||||
{
|
||||
if let Some(encoding) =
|
||||
Charset::for_label(CStr::from_ptr(charset).to_str().unwrap().as_bytes())
|
||||
Charset::for_label(CStr::from_ptr(charset).to_string_lossy().as_bytes())
|
||||
{
|
||||
let (res, _, _) = encoding.decode(&decoded_data);
|
||||
if res.is_empty() {
|
||||
/* no error - but nothing to add */
|
||||
ok_to_continue = false;
|
||||
} else {
|
||||
decoded_data = res.as_bytes().to_vec()
|
||||
return false;
|
||||
}
|
||||
decoded_data = res.as_bytes().to_vec()
|
||||
} else {
|
||||
warn!(
|
||||
self.context,
|
||||
@@ -634,31 +637,29 @@ impl<'a> MimeParser<'a> {
|
||||
);
|
||||
}
|
||||
}
|
||||
if ok_to_continue {
|
||||
/* check header directly as is_send_by_messenger is not yet set up */
|
||||
let is_msgrmsg = self.lookup_optional_field("Chat-Version").is_some();
|
||||
/* check header directly as is_send_by_messenger is not yet set up */
|
||||
let is_msgrmsg = self.lookup_optional_field("Chat-Version").is_some();
|
||||
|
||||
let simplified_txt = if decoded_data.is_empty() {
|
||||
"".into()
|
||||
} else {
|
||||
let input = std::string::String::from_utf8_lossy(&decoded_data);
|
||||
let is_html = mime_type == 70;
|
||||
let simplified_txt = if decoded_data.is_empty() {
|
||||
"".into()
|
||||
} else {
|
||||
let input = std::string::String::from_utf8_lossy(&decoded_data);
|
||||
let is_html = mime_type == 70;
|
||||
|
||||
simplifier.unwrap().simplify(&input, is_html, is_msgrmsg)
|
||||
};
|
||||
if !simplified_txt.is_empty() {
|
||||
let mut part = Part::default();
|
||||
part.typ = Viewtype::Text;
|
||||
part.mimetype = mime_type;
|
||||
part.msg = Some(simplified_txt);
|
||||
part.msg_raw =
|
||||
Some(std::string::String::from_utf8_lossy(&decoded_data).to_string());
|
||||
self.do_add_single_part(part);
|
||||
}
|
||||
simplifier.unwrap().simplify(&input, is_html, is_msgrmsg)
|
||||
};
|
||||
if !simplified_txt.is_empty() {
|
||||
let mut part = Part::default();
|
||||
part.typ = Viewtype::Text;
|
||||
part.mimetype = mime_type;
|
||||
part.msg = Some(simplified_txt);
|
||||
part.msg_raw =
|
||||
Some(std::string::String::from_utf8_lossy(&decoded_data).to_string());
|
||||
self.do_add_single_part(part);
|
||||
}
|
||||
|
||||
if simplifier.unwrap().is_forwarded {
|
||||
self.is_forwarded = true;
|
||||
}
|
||||
if simplifier.unwrap().is_forwarded {
|
||||
self.is_forwarded = true;
|
||||
}
|
||||
}
|
||||
DC_MIMETYPE_IMAGE
|
||||
@@ -704,7 +705,7 @@ impl<'a> MimeParser<'a> {
|
||||
// might be a wrongly encoded filename
|
||||
let s = to_string_lossy((*dsp_param).pa_data.pa_filename);
|
||||
// this is used only if the parts buffer stays empty
|
||||
desired_filename = dc_decode_header_words_safe(&s)
|
||||
desired_filename = dc_decode_header_words(&s)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -733,33 +734,29 @@ impl<'a> MimeParser<'a> {
|
||||
desired_filename =
|
||||
format!("file.{}", as_str((*(*mime).mm_content_type).ct_subtype));
|
||||
} else {
|
||||
ok_to_continue = false;
|
||||
return false;
|
||||
}
|
||||
}
|
||||
if ok_to_continue {
|
||||
if desired_filename.starts_with("location")
|
||||
&& desired_filename.ends_with(".kml")
|
||||
{
|
||||
if !decoded_data.is_empty() {
|
||||
let d = std::string::String::from_utf8_lossy(&decoded_data);
|
||||
self.location_kml = location::Kml::parse(self.context, &d).ok();
|
||||
}
|
||||
} else if desired_filename.starts_with("message")
|
||||
&& desired_filename.ends_with(".kml")
|
||||
{
|
||||
if !decoded_data.is_empty() {
|
||||
let d = std::string::String::from_utf8_lossy(&decoded_data);
|
||||
self.message_kml = location::Kml::parse(self.context, &d).ok();
|
||||
}
|
||||
} else if !decoded_data.is_empty() {
|
||||
self.do_add_single_file_part(
|
||||
msg_type,
|
||||
mime_type,
|
||||
raw_mime.as_ref(),
|
||||
&decoded_data,
|
||||
&desired_filename,
|
||||
);
|
||||
if desired_filename.starts_with("location") && desired_filename.ends_with(".kml") {
|
||||
if !decoded_data.is_empty() {
|
||||
let d = std::string::String::from_utf8_lossy(&decoded_data);
|
||||
self.location_kml = location::Kml::parse(self.context, &d).ok();
|
||||
}
|
||||
} else if desired_filename.starts_with("message")
|
||||
&& desired_filename.ends_with(".kml")
|
||||
{
|
||||
if !decoded_data.is_empty() {
|
||||
let d = std::string::String::from_utf8_lossy(&decoded_data);
|
||||
self.message_kml = location::Kml::parse(self.context, &d).ok();
|
||||
}
|
||||
} else if !decoded_data.is_empty() {
|
||||
self.do_add_single_file_part(
|
||||
msg_type,
|
||||
mime_type,
|
||||
raw_mime.as_ref(),
|
||||
&decoded_data,
|
||||
&desired_filename,
|
||||
);
|
||||
}
|
||||
}
|
||||
_ => {}
|
||||
@@ -776,28 +773,34 @@ impl<'a> MimeParser<'a> {
|
||||
decoded_data: &[u8],
|
||||
desired_filename: &str,
|
||||
) {
|
||||
/* create a free file name to use */
|
||||
let path_filename = dc_get_fine_path_filename(self.context, "$BLOBDIR", desired_filename);
|
||||
|
||||
/* copy data to file */
|
||||
if dc_write_file(self.context, &path_filename, decoded_data) {
|
||||
let mut part = Part::default();
|
||||
part.typ = msg_type;
|
||||
part.mimetype = mime_type;
|
||||
part.bytes = decoded_data.len() as libc::c_int;
|
||||
part.param.set(Param::File, path_filename.to_string_lossy());
|
||||
if let Some(raw_mime) = raw_mime {
|
||||
part.param.set(Param::MimeType, raw_mime);
|
||||
/* write decoded data to new blob file */
|
||||
let bpath = match self.context.new_blob_file(desired_filename, decoded_data) {
|
||||
Ok(path) => path,
|
||||
Err(err) => {
|
||||
error!(
|
||||
self.context,
|
||||
"Could not add blob for mime part {}, error {}", desired_filename, err
|
||||
);
|
||||
return;
|
||||
}
|
||||
};
|
||||
|
||||
if mime_type == DC_MIMETYPE_IMAGE {
|
||||
if let Ok((width, height)) = dc_get_filemeta(decoded_data) {
|
||||
part.param.set_int(Param::Width, width as i32);
|
||||
part.param.set_int(Param::Height, height as i32);
|
||||
}
|
||||
}
|
||||
self.do_add_single_part(part);
|
||||
let mut part = Part::default();
|
||||
part.typ = msg_type;
|
||||
part.mimetype = mime_type;
|
||||
part.bytes = decoded_data.len() as libc::c_int;
|
||||
part.param.set(Param::File, bpath);
|
||||
if let Some(raw_mime) = raw_mime {
|
||||
part.param.set(Param::MimeType, raw_mime);
|
||||
}
|
||||
|
||||
if mime_type == DC_MIMETYPE_IMAGE {
|
||||
if let Ok((width, height)) = dc_get_filemeta(decoded_data) {
|
||||
part.param.set_int(Param::Width, width as i32);
|
||||
part.param.set_int(Param::Height, height as i32);
|
||||
}
|
||||
}
|
||||
self.do_add_single_part(part);
|
||||
}
|
||||
|
||||
fn do_add_single_part(&mut self, mut part: Part) {
|
||||
@@ -832,7 +835,7 @@ impl<'a> MimeParser<'a> {
|
||||
let mut fld_from: *const mailimf_from = ptr::null();
|
||||
|
||||
/* get From: and check there is exactly one sender */
|
||||
let fld = mailimf_find_field(self.header_root, MAILIMF_FIELD_FROM as libc::c_int);
|
||||
let fld = wrapmime::mailimf_find_field(self.header_root, MAILIMF_FIELD_FROM as libc::c_int);
|
||||
if !(fld.is_null()
|
||||
|| {
|
||||
fld_from = (*fld).fld_data.fld_from;
|
||||
@@ -850,11 +853,9 @@ impl<'a> MimeParser<'a> {
|
||||
|
||||
if !mb.is_null() {
|
||||
let from_addr_norm = addr_normalize(as_str((*mb).mb_addr_spec));
|
||||
let recipients = mailimf_get_recipients(self.header_root);
|
||||
if recipients.len() == 1 {
|
||||
if recipients.contains(from_addr_norm) {
|
||||
sender_equals_recipient = true;
|
||||
}
|
||||
let recipients = wrapmime::mailimf_get_recipients(self.header_root);
|
||||
if recipients.len() == 1 && recipients.contains(from_addr_norm) {
|
||||
sender_equals_recipient = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -881,7 +882,7 @@ impl<'a> MimeParser<'a> {
|
||||
unsafe {
|
||||
let fld_message_id = (*field).fld_data.fld_message_id;
|
||||
if !fld_message_id.is_null() {
|
||||
return Some(to_string((*fld_message_id).mid_value));
|
||||
return Some(to_string_lossy((*fld_message_id).mid_value));
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -911,22 +912,6 @@ pub struct Part {
|
||||
pub param: Params,
|
||||
}
|
||||
|
||||
pub fn mailimf_find_first_addr(mb_list: *const mailimf_mailbox_list) -> Option<String> {
|
||||
if mb_list.is_null() {
|
||||
return None;
|
||||
}
|
||||
|
||||
for cur in unsafe { (*(*mb_list).mb_list).into_iter() } {
|
||||
let mb = cur as *mut mailimf_mailbox;
|
||||
if !mb.is_null() && !unsafe { (*mb).mb_addr_spec.is_null() } {
|
||||
let addr = unsafe { as_str((*mb).mb_addr_spec) };
|
||||
return Some(addr_normalize(addr).to_string());
|
||||
}
|
||||
}
|
||||
|
||||
None
|
||||
}
|
||||
|
||||
unsafe fn hash_header(out: &mut HashMap<String, *mut mailimf_field>, in_0: *const mailimf_fields) {
|
||||
if in_0.is_null() {
|
||||
return;
|
||||
@@ -997,14 +982,12 @@ unsafe fn mailmime_get_mime_type(mime: *mut Mailmime) -> (libc::c_int, Viewtype,
|
||||
) == 0i32
|
||||
{
|
||||
return (DC_MIMETYPE_TEXT_PLAIN, Viewtype::Text, None);
|
||||
} else {
|
||||
if strcmp(
|
||||
(*c).ct_subtype,
|
||||
b"html\x00" as *const u8 as *const libc::c_char,
|
||||
) == 0i32
|
||||
{
|
||||
return (DC_MIMETYPE_TEXT_HTML, Viewtype::Text, None);
|
||||
}
|
||||
} else if strcmp(
|
||||
(*c).ct_subtype,
|
||||
b"html\x00" as *const u8 as *const libc::c_char,
|
||||
) == 0i32
|
||||
{
|
||||
return (DC_MIMETYPE_TEXT_HTML, Viewtype::Text, None);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1148,214 +1131,6 @@ pub unsafe fn mailmime_find_ct_parameter(
|
||||
ptr::null_mut()
|
||||
}
|
||||
|
||||
pub unsafe fn mailmime_transfer_decode(mime: *mut Mailmime) -> Result<Vec<u8>, Error> {
|
||||
ensure!(!mime.is_null(), "invalid inputs");
|
||||
|
||||
let mut mime_transfer_encoding = MAILMIME_MECHANISM_BINARY as libc::c_int;
|
||||
|
||||
let mime_data = (*mime).mm_data.mm_single;
|
||||
if !(*mime).mm_mime_fields.is_null() {
|
||||
for cur in (*(*(*mime).mm_mime_fields).fld_list).into_iter() {
|
||||
let field = cur as *mut mailmime_field;
|
||||
|
||||
if !field.is_null()
|
||||
&& (*field).fld_type == MAILMIME_FIELD_TRANSFER_ENCODING as libc::c_int
|
||||
&& !(*field).fld_data.fld_encoding.is_null()
|
||||
{
|
||||
mime_transfer_encoding = (*(*field).fld_data.fld_encoding).enc_type;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if mime_transfer_encoding == MAILMIME_MECHANISM_7BIT as libc::c_int
|
||||
|| mime_transfer_encoding == MAILMIME_MECHANISM_8BIT as libc::c_int
|
||||
|| mime_transfer_encoding == MAILMIME_MECHANISM_BINARY as libc::c_int
|
||||
{
|
||||
let decoded_data = (*mime_data).dt_data.dt_text.dt_data;
|
||||
let decoded_data_bytes = (*mime_data).dt_data.dt_text.dt_length;
|
||||
|
||||
if decoded_data.is_null() || decoded_data_bytes <= 0 {
|
||||
bail!("No data to decode found");
|
||||
} else {
|
||||
let result = std::slice::from_raw_parts(decoded_data as *const u8, decoded_data_bytes);
|
||||
return Ok(result.to_vec());
|
||||
}
|
||||
}
|
||||
|
||||
let mut current_index = 0;
|
||||
let mut transfer_decoding_buffer = ptr::null_mut();
|
||||
let mut decoded_data_bytes = 0;
|
||||
|
||||
let r = mailmime_part_parse(
|
||||
(*mime_data).dt_data.dt_text.dt_data,
|
||||
(*mime_data).dt_data.dt_text.dt_length,
|
||||
&mut current_index,
|
||||
mime_transfer_encoding,
|
||||
&mut transfer_decoding_buffer,
|
||||
&mut decoded_data_bytes,
|
||||
);
|
||||
|
||||
if r == MAILIMF_NO_ERROR as libc::c_int
|
||||
&& !transfer_decoding_buffer.is_null()
|
||||
&& decoded_data_bytes > 0
|
||||
{
|
||||
let result =
|
||||
std::slice::from_raw_parts(transfer_decoding_buffer as *const u8, decoded_data_bytes)
|
||||
.to_vec();
|
||||
mmap_string_unref(transfer_decoding_buffer);
|
||||
|
||||
return Ok(result);
|
||||
}
|
||||
|
||||
Err(format_err!("Failed to to decode"))
|
||||
}
|
||||
|
||||
pub fn mailimf_get_recipients(imffields: *mut mailimf_fields) -> HashSet<String> {
|
||||
/* returned addresses are normalized. */
|
||||
let mut recipients: HashSet<String> = Default::default();
|
||||
|
||||
for cur in unsafe { (*(*imffields).fld_list).into_iter() } {
|
||||
let fld = cur as *mut mailimf_field;
|
||||
|
||||
let fld_to: *mut mailimf_to;
|
||||
let fld_cc: *mut mailimf_cc;
|
||||
|
||||
let mut addr_list: *mut mailimf_address_list = ptr::null_mut();
|
||||
if fld.is_null() {
|
||||
continue;
|
||||
}
|
||||
|
||||
let fld = unsafe { *fld };
|
||||
|
||||
// TODO match on enums /rtn
|
||||
match fld.fld_type {
|
||||
13 => {
|
||||
fld_to = unsafe { fld.fld_data.fld_to };
|
||||
if !fld_to.is_null() {
|
||||
addr_list = unsafe { (*fld_to).to_addr_list };
|
||||
}
|
||||
}
|
||||
14 => {
|
||||
fld_cc = unsafe { fld.fld_data.fld_cc };
|
||||
if !fld_cc.is_null() {
|
||||
addr_list = unsafe { (*fld_cc).cc_addr_list };
|
||||
}
|
||||
}
|
||||
_ => {}
|
||||
}
|
||||
|
||||
if !addr_list.is_null() {
|
||||
for cur2 in unsafe { &(*(*addr_list).ad_list) } {
|
||||
let adr = cur2 as *mut mailimf_address;
|
||||
|
||||
if adr.is_null() {
|
||||
continue;
|
||||
}
|
||||
let adr = unsafe { *adr };
|
||||
|
||||
if adr.ad_type == MAILIMF_ADDRESS_MAILBOX as libc::c_int {
|
||||
mailimf_get_recipients_add_addr(&mut recipients, unsafe {
|
||||
adr.ad_data.ad_mailbox
|
||||
});
|
||||
} else if adr.ad_type == MAILIMF_ADDRESS_GROUP as libc::c_int {
|
||||
let group = unsafe { adr.ad_data.ad_group };
|
||||
if !group.is_null() && unsafe { !(*group).grp_mb_list.is_null() } {
|
||||
for cur3 in unsafe { &(*(*(*group).grp_mb_list).mb_list) } {
|
||||
mailimf_get_recipients_add_addr(
|
||||
&mut recipients,
|
||||
cur3 as *mut mailimf_mailbox,
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
recipients
|
||||
}
|
||||
|
||||
fn mailimf_get_recipients_add_addr(recipients: &mut HashSet<String>, mb: *mut mailimf_mailbox) {
|
||||
if !mb.is_null() {
|
||||
let addr_norm = addr_normalize(as_str(unsafe { (*mb).mb_addr_spec }));
|
||||
recipients.insert(addr_norm.into());
|
||||
}
|
||||
}
|
||||
|
||||
/*the result is a pointer to mime, must not be freed*/
|
||||
pub fn mailimf_find_field(
|
||||
header: *mut mailimf_fields,
|
||||
wanted_fld_type: libc::c_int,
|
||||
) -> *mut mailimf_field {
|
||||
if header.is_null() {
|
||||
return ptr::null_mut();
|
||||
}
|
||||
|
||||
let header = unsafe { (*header) };
|
||||
if header.fld_list.is_null() {
|
||||
return ptr::null_mut();
|
||||
}
|
||||
|
||||
for cur in unsafe { &(*header.fld_list) } {
|
||||
let field = cur as *mut mailimf_field;
|
||||
if !field.is_null() {
|
||||
if unsafe { (*field).fld_type } == wanted_fld_type {
|
||||
return field;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
ptr::null_mut()
|
||||
}
|
||||
|
||||
/*the result is a pointer to mime, must not be freed*/
|
||||
pub unsafe fn mailmime_find_mailimf_fields(mime: *mut Mailmime) -> *mut mailimf_fields {
|
||||
if mime.is_null() {
|
||||
return ptr::null_mut();
|
||||
}
|
||||
|
||||
match (*mime).mm_type as _ {
|
||||
MAILMIME_MULTIPLE => {
|
||||
for cur_data in (*(*mime).mm_data.mm_multipart.mm_mp_list).into_iter() {
|
||||
let header = mailmime_find_mailimf_fields(cur_data as *mut _);
|
||||
if !header.is_null() {
|
||||
return header;
|
||||
}
|
||||
}
|
||||
}
|
||||
MAILMIME_MESSAGE => return (*mime).mm_data.mm_message.mm_fields,
|
||||
_ => {}
|
||||
}
|
||||
|
||||
ptr::null_mut()
|
||||
}
|
||||
|
||||
pub unsafe fn mailimf_find_optional_field(
|
||||
header: *mut mailimf_fields,
|
||||
wanted_fld_name: *const libc::c_char,
|
||||
) -> *mut mailimf_optional_field {
|
||||
if header.is_null() || (*header).fld_list.is_null() {
|
||||
return ptr::null_mut();
|
||||
}
|
||||
for cur_data in (*(*header).fld_list).into_iter() {
|
||||
let field: *mut mailimf_field = cur_data as *mut _;
|
||||
|
||||
if (*field).fld_type == MAILIMF_FIELD_OPTIONAL_FIELD as libc::c_int {
|
||||
let optional_field: *mut mailimf_optional_field = (*field).fld_data.fld_optional_field;
|
||||
if !optional_field.is_null()
|
||||
&& !(*optional_field).fld_name.is_null()
|
||||
&& !(*optional_field).fld_value.is_null()
|
||||
&& strcasecmp((*optional_field).fld_name, wanted_fld_name) == 0i32
|
||||
{
|
||||
return optional_field;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
ptr::null_mut()
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
@@ -1371,14 +1146,13 @@ mod tests {
|
||||
let mut mime: *mut Mailmime = ptr::null_mut();
|
||||
let mut dummy = 0;
|
||||
let res = mailmime_parse(txt, strlen(txt), &mut dummy, &mut mime);
|
||||
|
||||
assert_eq!(res, MAIL_NO_ERROR as libc::c_int);
|
||||
assert!(!mime.is_null());
|
||||
|
||||
let fields: *mut mailimf_fields = mailmime_find_mailimf_fields(mime);
|
||||
let fields: *mut mailimf_fields = wrapmime::mailmime_find_mailimf_fields(mime);
|
||||
assert!(!fields.is_null());
|
||||
|
||||
let mut of_a: *mut mailimf_optional_field = mailimf_find_optional_field(
|
||||
let mut of_a: *mut mailimf_optional_field = wrapmime::mailimf_find_optional_field(
|
||||
fields,
|
||||
b"fielda\x00" as *const u8 as *const libc::c_char,
|
||||
);
|
||||
@@ -1398,7 +1172,7 @@ mod tests {
|
||||
"ValueA",
|
||||
);
|
||||
|
||||
of_a = mailimf_find_optional_field(
|
||||
of_a = wrapmime::mailimf_find_optional_field(
|
||||
fields,
|
||||
b"FIELDA\x00" as *const u8 as *const libc::c_char,
|
||||
);
|
||||
@@ -1418,7 +1192,7 @@ mod tests {
|
||||
"ValueA",
|
||||
);
|
||||
|
||||
let of_b: *mut mailimf_optional_field = mailimf_find_optional_field(
|
||||
let of_b: *mut mailimf_optional_field = wrapmime::mailimf_find_optional_field(
|
||||
fields,
|
||||
b"FieldB\x00" as *const u8 as *const libc::c_char,
|
||||
);
|
||||
@@ -1441,7 +1215,9 @@ mod tests {
|
||||
let context = dummy_context();
|
||||
let raw = include_bytes!("../test-data/message/issue_523.txt");
|
||||
let mut mimeparser = MimeParser::new(&context.ctx);
|
||||
unsafe { mimeparser.parse(&raw[..]).unwrap() };
|
||||
unsafe {
|
||||
mimeparser.parse(&raw[..]).unwrap();
|
||||
};
|
||||
assert_eq!(mimeparser.subject, None);
|
||||
assert_eq!(mimeparser.parts.len(), 1);
|
||||
}
|
||||
@@ -1449,9 +1225,13 @@ mod tests {
|
||||
proptest! {
|
||||
#[test]
|
||||
fn test_dc_mailmime_parse_crash_fuzzy(data in "[!-~\t ]{2000,}") {
|
||||
// this test doesn't exercise much of dc_mimeparser anymore
|
||||
// because a missing From-field early aborts parsing
|
||||
let context = dummy_context();
|
||||
let mut mimeparser = MimeParser::new(&context.ctx);
|
||||
unsafe { mimeparser.parse(data.as_bytes()).unwrap() };
|
||||
unsafe {
|
||||
assert!(mimeparser.parse(data.as_bytes()).is_err());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1480,7 +1260,7 @@ mod tests {
|
||||
fn test_mimeparser_with_context() {
|
||||
unsafe {
|
||||
let context = dummy_context();
|
||||
let raw = b"Content-Type: multipart/mixed; boundary=\"==break==\";\nSubject: outer-subject\nX-Special-A: special-a\nFoo: Bar\nChat-Version: 0.0\n\n--==break==\nContent-Type: text/plain; protected-headers=\"v1\";\nSubject: inner-subject\nX-Special-B: special-b\nFoo: Xy\nChat-Version: 1.0\n\ntest1\n\n--==break==--\n\n\x00";
|
||||
let raw = b"From: hello\nContent-Type: multipart/mixed; boundary=\"==break==\";\nSubject: outer-subject\nX-Special-A: special-a\nFoo: Bar\nChat-Version: 0.0\n\n--==break==\nContent-Type: text/plain; protected-headers=\"v1\";\nSubject: inner-subject\nX-Special-B: special-b\nFoo: Xy\nChat-Version: 1.0\n\ntest1\n\n--==break==--\n\n\x00";
|
||||
let mut mimeparser = MimeParser::new(&context.ctx);
|
||||
mimeparser.parse(&raw[..]).unwrap();
|
||||
|
||||
|
||||
@@ -1,11 +1,9 @@
|
||||
use std::ffi::CString;
|
||||
use std::ptr;
|
||||
|
||||
use itertools::join;
|
||||
use libc::{free, strcmp, strlen};
|
||||
use libc::{free, strcmp};
|
||||
use mmime::clist::*;
|
||||
use mmime::mailimf::types::*;
|
||||
use mmime::mailimf::*;
|
||||
use mmime::mailmime::content::*;
|
||||
use mmime::mailmime::types::*;
|
||||
use mmime::mailmime::*;
|
||||
@@ -13,6 +11,7 @@ use mmime::other::*;
|
||||
use sha2::{Digest, Sha256};
|
||||
|
||||
use crate::chat::{self, Chat};
|
||||
use crate::config::Config;
|
||||
use crate::constants::*;
|
||||
use crate::contact::*;
|
||||
use crate::context::Context;
|
||||
@@ -29,6 +28,7 @@ use crate::peerstate::*;
|
||||
use crate::securejoin::handle_securejoin_handshake;
|
||||
use crate::sql;
|
||||
use crate::stock::StockMessage;
|
||||
use crate::wrapmime;
|
||||
|
||||
#[derive(Debug, PartialEq, Eq)]
|
||||
enum CreateEvent {
|
||||
@@ -81,7 +81,7 @@ pub unsafe fn dc_receive_imf(
|
||||
let mut chat_id = 0;
|
||||
let mut hidden = 0;
|
||||
|
||||
let mut add_delete_job: libc::c_int = 0;
|
||||
let mut needs_delete_job = false;
|
||||
let mut insert_msg_id = 0;
|
||||
|
||||
let mut sent_timestamp = 0;
|
||||
@@ -147,7 +147,7 @@ pub unsafe fn dc_receive_imf(
|
||||
if mime_parser.sender_equals_recipient() {
|
||||
from_id = DC_CONTACT_ID_SELF;
|
||||
}
|
||||
} else if from_list.len() >= 1 {
|
||||
} else if !from_list.is_empty() {
|
||||
// if there is no from given, from_id stays 0 which is just fine. These messages
|
||||
// are very rare, however, we have to add them to the database (they go to the
|
||||
// "deaddrop" chat) to avoid a re-download from the server. See also [**]
|
||||
@@ -218,7 +218,7 @@ pub unsafe fn dc_receive_imf(
|
||||
&mut chat_id,
|
||||
&mut to_id,
|
||||
flags,
|
||||
&mut add_delete_job,
|
||||
&mut needs_delete_job,
|
||||
to_self,
|
||||
&mut insert_msg_id,
|
||||
&mut created_db_entries,
|
||||
@@ -249,7 +249,7 @@ pub unsafe fn dc_receive_imf(
|
||||
from_id,
|
||||
sent_timestamp,
|
||||
&mut rr_event_to_send,
|
||||
server_folder,
|
||||
&server_folder,
|
||||
server_uid,
|
||||
);
|
||||
}
|
||||
@@ -265,7 +265,8 @@ pub unsafe fn dc_receive_imf(
|
||||
);
|
||||
}
|
||||
|
||||
if 0 != add_delete_job && !created_db_entries.is_empty() {
|
||||
// if we delete we don't need to try moving messages
|
||||
if needs_delete_job && !created_db_entries.is_empty() {
|
||||
job_add(
|
||||
context,
|
||||
Action::DeleteMsgOnImap,
|
||||
@@ -273,6 +274,8 @@ pub unsafe fn dc_receive_imf(
|
||||
Params::new(),
|
||||
0,
|
||||
);
|
||||
} else {
|
||||
context.do_heuristics_moves(server_folder.as_ref(), insert_msg_id);
|
||||
}
|
||||
|
||||
info!(
|
||||
@@ -305,7 +308,7 @@ unsafe fn add_parts(
|
||||
chat_id: &mut u32,
|
||||
to_id: &mut u32,
|
||||
flags: u32,
|
||||
add_delete_job: &mut libc::c_int,
|
||||
needs_delete_job: &mut bool,
|
||||
to_self: i32,
|
||||
insert_msg_id: &mut u32,
|
||||
created_db_entries: &mut Vec<(usize, usize)>,
|
||||
@@ -370,10 +373,7 @@ unsafe fn add_parts(
|
||||
// maybe this can be optimized later, by checking the state before the message body is downloaded
|
||||
let mut allow_creation = 1;
|
||||
if mime_parser.is_system_message != SystemMessage::AutocryptSetupMessage && msgrmsg == 0 {
|
||||
let show_emails = context
|
||||
.sql
|
||||
.get_config_int(context, "show_emails")
|
||||
.unwrap_or_default();
|
||||
let show_emails = context.get_config_int(Config::ShowEmails);
|
||||
if show_emails == 0 {
|
||||
*chat_id = 3;
|
||||
allow_creation = 0
|
||||
@@ -403,7 +403,7 @@ unsafe fn add_parts(
|
||||
let handshake = handle_securejoin_handshake(context, mime_parser, *from_id);
|
||||
if 0 != handshake & DC_HANDSHAKE_STOP_NORMAL_PROCESSING {
|
||||
*hidden = 1;
|
||||
*add_delete_job = handshake & DC_HANDSHAKE_ADD_DELETE_JOB;
|
||||
*needs_delete_job = 0 != handshake & DC_HANDSHAKE_ADD_DELETE_JOB;
|
||||
state = MessageState::InSeen;
|
||||
}
|
||||
}
|
||||
@@ -435,7 +435,7 @@ unsafe fn add_parts(
|
||||
to_ids,
|
||||
chat_id,
|
||||
&mut chat_id_blocked,
|
||||
);
|
||||
)?;
|
||||
if 0 != *chat_id && Blocked::Not != chat_id_blocked && create_blocked == Blocked::Not {
|
||||
chat::unblock(context, *chat_id);
|
||||
chat_id_blocked = Blocked::Not;
|
||||
@@ -494,10 +494,12 @@ unsafe fn add_parts(
|
||||
// if the chat_id is blocked,
|
||||
// for unknown senders and non-delta messages set the state to NOTICED
|
||||
// to not result in a contact request (this would require the state FRESH)
|
||||
if Blocked::Not != chat_id_blocked && state == MessageState::InFresh {
|
||||
if !incoming_origin.is_verified() && msgrmsg == 0 {
|
||||
state = MessageState::InNoticed;
|
||||
}
|
||||
if Blocked::Not != chat_id_blocked
|
||||
&& state == MessageState::InFresh
|
||||
&& !incoming_origin.is_verified()
|
||||
&& msgrmsg == 0
|
||||
{
|
||||
state = MessageState::InNoticed;
|
||||
}
|
||||
} else {
|
||||
// Outgoing
|
||||
@@ -518,7 +520,7 @@ unsafe fn add_parts(
|
||||
to_ids,
|
||||
chat_id,
|
||||
&mut chat_id_blocked,
|
||||
);
|
||||
)?;
|
||||
if 0 != *chat_id && Blocked::Not != chat_id_blocked {
|
||||
chat::unblock(context, *chat_id);
|
||||
chat_id_blocked = Blocked::Not;
|
||||
@@ -578,11 +580,11 @@ unsafe fn add_parts(
|
||||
);
|
||||
|
||||
// unarchive chat
|
||||
chat::unarchive(context, *chat_id).unwrap();
|
||||
chat::unarchive(context, *chat_id)?;
|
||||
|
||||
// if the mime-headers should be saved, find out its size
|
||||
// (the mime-header ends with an empty line)
|
||||
let save_mime_headers = context.sql.get_config_bool(context, "save_mime_headers");
|
||||
let save_mime_headers = context.get_config_bool(Config::SaveMimeHeaders);
|
||||
if let Some(field) = mime_parser.lookup_field_typ("In-Reply-To", MAILIMF_FIELD_IN_REPLY_TO) {
|
||||
let fld_in_reply_to = (*field).fld_data.fld_in_reply_to;
|
||||
if !fld_in_reply_to.is_null() {
|
||||
@@ -626,7 +628,7 @@ unsafe fn add_parts(
|
||||
}
|
||||
|
||||
if let Some(ref msg) = part.msg {
|
||||
if !mime_parser.location_kml.is_none()
|
||||
if mime_parser.location_kml.is_some()
|
||||
&& icnt == 1
|
||||
&& (msg == "-location-" || msg.is_empty())
|
||||
{
|
||||
@@ -685,8 +687,8 @@ unsafe fn add_parts(
|
||||
} else {
|
||||
None
|
||||
},
|
||||
to_string(mime_in_reply_to),
|
||||
to_string(mime_references),
|
||||
to_string_lossy(mime_in_reply_to),
|
||||
to_string_lossy(mime_references),
|
||||
])?;
|
||||
|
||||
txt_raw = None;
|
||||
@@ -720,7 +722,6 @@ unsafe fn add_parts(
|
||||
}
|
||||
}
|
||||
|
||||
context.do_heuristics_moves(server_folder.as_ref(), *insert_msg_id);
|
||||
cleanup(mime_in_reply_to, mime_references);
|
||||
|
||||
Ok(())
|
||||
@@ -736,10 +737,7 @@ unsafe fn handle_reports(
|
||||
server_folder: impl AsRef<str>,
|
||||
server_uid: u32,
|
||||
) {
|
||||
let mdns_enabled = context
|
||||
.sql
|
||||
.get_config_int(context, "mdns_enabled")
|
||||
.unwrap_or_else(|| DC_MDNS_DEFAULT_ENABLED);
|
||||
let mdns_enabled = context.get_config_bool(Config::MdnsEnabled);
|
||||
|
||||
for report_root in &mime_parser.reports {
|
||||
let report_root = *report_root;
|
||||
@@ -758,7 +756,7 @@ unsafe fn handle_reports(
|
||||
&& (*(*report_root).mm_data.mm_multipart.mm_mp_list).count >= 2
|
||||
{
|
||||
// to get a clear functionality, do not show incoming MDNs if the options is disabled
|
||||
if 0 != mdns_enabled {
|
||||
if mdns_enabled {
|
||||
let report_data = (if !if !(*(*report_root).mm_data.mm_multipart.mm_mp_list)
|
||||
.first
|
||||
.is_null()
|
||||
@@ -795,7 +793,7 @@ unsafe fn handle_reports(
|
||||
b"disposition-notification\x00" as *const u8 as *const libc::c_char,
|
||||
) == 0
|
||||
{
|
||||
if let Ok(report_body) = mailmime_transfer_decode(report_data) {
|
||||
if let Ok(report_body) = wrapmime::mailmime_transfer_decode(report_data) {
|
||||
let mut report_parsed = std::ptr::null_mut();
|
||||
let mut dummy = 0;
|
||||
|
||||
@@ -807,13 +805,14 @@ unsafe fn handle_reports(
|
||||
) == MAIL_NO_ERROR as libc::c_int
|
||||
&& !report_parsed.is_null()
|
||||
{
|
||||
let report_fields = mailmime_find_mailimf_fields(report_parsed);
|
||||
let report_fields =
|
||||
wrapmime::mailmime_find_mailimf_fields(report_parsed);
|
||||
if !report_fields.is_null() {
|
||||
let of_disposition = mailimf_find_optional_field(
|
||||
let of_disposition = wrapmime::mailimf_find_optional_field(
|
||||
report_fields,
|
||||
b"Disposition\x00" as *const u8 as *const libc::c_char,
|
||||
);
|
||||
let of_org_msgid = mailimf_find_optional_field(
|
||||
let of_org_msgid = wrapmime::mailimf_find_optional_field(
|
||||
report_fields,
|
||||
b"Original-Message-ID\x00" as *const u8 as *const libc::c_char,
|
||||
);
|
||||
@@ -822,24 +821,16 @@ unsafe fn handle_reports(
|
||||
&& !of_org_msgid.is_null()
|
||||
&& !(*of_org_msgid).fld_value.is_null()
|
||||
{
|
||||
let mut rfc724_mid_0 = std::ptr::null_mut();
|
||||
dummy = 0;
|
||||
|
||||
if mailimf_msg_id_parse(
|
||||
if let Ok(rfc724_mid) = wrapmime::parse_message_id(as_str(
|
||||
(*of_org_msgid).fld_value,
|
||||
strlen((*of_org_msgid).fld_value),
|
||||
&mut dummy,
|
||||
&mut rfc724_mid_0,
|
||||
) == MAIL_NO_ERROR as libc::c_int
|
||||
&& !rfc724_mid_0.is_null()
|
||||
{
|
||||
)) {
|
||||
let mut chat_id_0 = 0;
|
||||
let mut msg_id = 0;
|
||||
|
||||
if message::mdn_from_ext(
|
||||
context,
|
||||
from_id,
|
||||
as_str(rfc724_mid_0),
|
||||
&rfc724_mid,
|
||||
sent_timestamp,
|
||||
&mut chat_id_0,
|
||||
&mut msg_id,
|
||||
@@ -847,7 +838,6 @@ unsafe fn handle_reports(
|
||||
rr_event_to_send.push((chat_id_0, msg_id));
|
||||
}
|
||||
mdn_consumed = (msg_id != 0) as libc::c_int;
|
||||
free(rfc724_mid_0.cast());
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -861,12 +851,7 @@ unsafe fn handle_reports(
|
||||
let mut param = Params::new();
|
||||
param.set(Param::ServerFolder, server_folder.as_ref());
|
||||
param.set_int(Param::ServerUid, server_uid as i32);
|
||||
if mime_parser.is_send_by_messenger
|
||||
&& 0 != context
|
||||
.sql
|
||||
.get_config_int(context, "mvbox_move")
|
||||
.unwrap_or_else(|| 1)
|
||||
{
|
||||
if mime_parser.is_send_by_messenger && context.get_config_bool(Config::MvboxMove) {
|
||||
param.set_int(Param::AlsoMove, 1);
|
||||
}
|
||||
job_add(context, Action::MarkseenMdnOnImap, 0, param, 0);
|
||||
@@ -982,7 +967,7 @@ unsafe fn create_or_lookup_group(
|
||||
to_ids: &mut Vec<u32>,
|
||||
ret_chat_id: *mut u32,
|
||||
ret_chat_id_blocked: &mut Blocked,
|
||||
) {
|
||||
) -> Result<()> {
|
||||
let group_explicitly_left: bool;
|
||||
let mut chat_id = 0;
|
||||
let mut chat_id_blocked = Blocked::Not;
|
||||
@@ -1040,7 +1025,7 @@ unsafe fn create_or_lookup_group(
|
||||
{
|
||||
let fld_in_reply_to = (*field).fld_data.fld_in_reply_to;
|
||||
if !fld_in_reply_to.is_null() {
|
||||
grpid = to_string(dc_extract_grpid_from_rfc724_mid_list(
|
||||
grpid = to_string_lossy(dc_extract_grpid_from_rfc724_mid_list(
|
||||
(*fld_in_reply_to).mid_list,
|
||||
));
|
||||
}
|
||||
@@ -1051,7 +1036,7 @@ unsafe fn create_or_lookup_group(
|
||||
{
|
||||
let fld_references = (*field).fld_data.fld_references;
|
||||
if !fld_references.is_null() {
|
||||
grpid = to_string(dc_extract_grpid_from_rfc724_mid_list(
|
||||
grpid = to_string_lossy(dc_extract_grpid_from_rfc724_mid_list(
|
||||
(*fld_references).mid_list,
|
||||
));
|
||||
}
|
||||
@@ -1067,16 +1052,16 @@ unsafe fn create_or_lookup_group(
|
||||
to_ids,
|
||||
&mut chat_id,
|
||||
&mut chat_id_blocked,
|
||||
);
|
||||
)?;
|
||||
cleanup(ret_chat_id, ret_chat_id_blocked, chat_id, chat_id_blocked);
|
||||
return;
|
||||
return Ok(());
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if let Some(optional_field) = mime_parser.lookup_optional_field("Chat-Group-Name") {
|
||||
grpname = Some(dc_decode_header_words_safe(&optional_field));
|
||||
grpname = Some(dc_decode_header_words(&optional_field));
|
||||
}
|
||||
if let Some(optional_field) = mime_parser.lookup_optional_field("Chat-Group-Member-Removed") {
|
||||
X_MrRemoveFromGrp = Some(optional_field);
|
||||
@@ -1168,8 +1153,7 @@ unsafe fn create_or_lookup_group(
|
||||
group_explicitly_left = chat::is_group_explicitly_left(context, &grpid).unwrap_or_default();
|
||||
|
||||
let self_addr = context
|
||||
.sql
|
||||
.get_config(context, "configured_addr")
|
||||
.get_config(Config::ConfiguredAddr)
|
||||
.unwrap_or_default();
|
||||
if chat_id == 0
|
||||
&& !mime_parser.is_mailinglist_message()
|
||||
@@ -1195,7 +1179,7 @@ unsafe fn create_or_lookup_group(
|
||||
}
|
||||
if 0 == allow_creation {
|
||||
cleanup(ret_chat_id, ret_chat_id_blocked, chat_id, chat_id_blocked);
|
||||
return;
|
||||
return Ok(());
|
||||
}
|
||||
chat_id = create_group_record(
|
||||
context,
|
||||
@@ -1223,10 +1207,10 @@ unsafe fn create_or_lookup_group(
|
||||
to_ids,
|
||||
&mut chat_id,
|
||||
&mut chat_id_blocked,
|
||||
);
|
||||
)?;
|
||||
}
|
||||
cleanup(ret_chat_id, ret_chat_id_blocked, chat_id, chat_id_blocked);
|
||||
return;
|
||||
return Ok(());
|
||||
}
|
||||
|
||||
// execute group commands
|
||||
@@ -1279,7 +1263,7 @@ unsafe fn create_or_lookup_group(
|
||||
} else {
|
||||
chat.param.set(Param::ProfileImage, grpimage);
|
||||
}
|
||||
chat.update_param(context).unwrap();
|
||||
chat.update_param(context)?;
|
||||
send_EVENT_CHAT_MODIFIED = 1;
|
||||
}
|
||||
}
|
||||
@@ -1343,11 +1327,12 @@ unsafe fn create_or_lookup_group(
|
||||
to_ids,
|
||||
&mut chat_id,
|
||||
&mut chat_id_blocked,
|
||||
);
|
||||
)?;
|
||||
}
|
||||
}
|
||||
|
||||
cleanup(ret_chat_id, ret_chat_id_blocked, chat_id, chat_id_blocked);
|
||||
return Ok(());
|
||||
}
|
||||
|
||||
/// Handle groups for received messages
|
||||
@@ -1360,7 +1345,7 @@ unsafe fn create_or_lookup_adhoc_group(
|
||||
to_ids: &mut Vec<u32>,
|
||||
ret_chat_id: *mut u32,
|
||||
ret_chat_id_blocked: &mut Blocked,
|
||||
) {
|
||||
) -> Result<()> {
|
||||
// if we're here, no grpid was found, check there is an existing ad-hoc
|
||||
// group matching the to-list or if we can create one
|
||||
let mut chat_id = 0;
|
||||
@@ -1380,7 +1365,7 @@ unsafe fn create_or_lookup_adhoc_group(
|
||||
if to_ids.is_empty() || mime_parser.is_mailinglist_message() {
|
||||
// too few contacts or a mailinglist
|
||||
cleanup(ret_chat_id, ret_chat_id_blocked, chat_id, chat_id_blocked);
|
||||
return;
|
||||
return Ok(());
|
||||
}
|
||||
|
||||
let mut member_ids = to_ids.clone();
|
||||
@@ -1393,10 +1378,10 @@ unsafe fn create_or_lookup_adhoc_group(
|
||||
if member_ids.len() < 3 {
|
||||
// too few contacts given
|
||||
cleanup(ret_chat_id, ret_chat_id_blocked, chat_id, chat_id_blocked);
|
||||
return;
|
||||
return Ok(());
|
||||
}
|
||||
|
||||
let chat_ids = search_chat_ids_by_contact_ids(context, &member_ids);
|
||||
let chat_ids = search_chat_ids_by_contact_ids(context, &member_ids)?;
|
||||
if !chat_ids.is_empty() {
|
||||
let chat_ids_str = join(chat_ids.iter().map(|x| x.to_string()), ",");
|
||||
let res = context.sql.query_row(
|
||||
@@ -1416,13 +1401,13 @@ unsafe fn create_or_lookup_adhoc_group(
|
||||
chat_id_blocked = id_blocked;
|
||||
/* success, chat found */
|
||||
cleanup(ret_chat_id, ret_chat_id_blocked, chat_id, chat_id_blocked);
|
||||
return;
|
||||
return Ok(());
|
||||
}
|
||||
}
|
||||
|
||||
if 0 == allow_creation {
|
||||
cleanup(ret_chat_id, ret_chat_id_blocked, chat_id, chat_id_blocked);
|
||||
return;
|
||||
return Ok(());
|
||||
}
|
||||
// we do not check if the message is a reply to another group, this may result in
|
||||
// chats with unclear member list. instead we create a new group in the following lines ...
|
||||
@@ -1432,7 +1417,7 @@ unsafe fn create_or_lookup_adhoc_group(
|
||||
let grpid = create_adhoc_grp_id(context, &member_ids);
|
||||
if grpid.is_empty() {
|
||||
cleanup(ret_chat_id, ret_chat_id_blocked, chat_id, chat_id_blocked);
|
||||
return;
|
||||
return Ok(());
|
||||
}
|
||||
|
||||
// use subject as initial chat name
|
||||
@@ -1458,6 +1443,7 @@ unsafe fn create_or_lookup_adhoc_group(
|
||||
context.call_cb(Event::ChatModified(chat_id));
|
||||
|
||||
cleanup(ret_chat_id, ret_chat_id_blocked, chat_id, chat_id_blocked);
|
||||
return Ok(());
|
||||
}
|
||||
|
||||
fn create_group_record(
|
||||
@@ -1490,7 +1476,7 @@ fn create_group_record(
|
||||
sql::get_rowid(context, &context.sql, "chats", "grpid", grpid.as_ref())
|
||||
}
|
||||
|
||||
fn create_adhoc_grp_id(context: &Context, member_ids: &Vec<u32>) -> String {
|
||||
fn create_adhoc_grp_id(context: &Context, member_ids: &[u32]) -> String {
|
||||
/* algorithm:
|
||||
- sort normalized, lowercased, e-mail addresses alphabetically
|
||||
- put all e-mail addresses into a single string, separate the address by a single comma
|
||||
@@ -1499,8 +1485,7 @@ fn create_adhoc_grp_id(context: &Context, member_ids: &Vec<u32>) -> String {
|
||||
*/
|
||||
let member_ids_str = join(member_ids.iter().map(|x| x.to_string()), ",");
|
||||
let member_cs = context
|
||||
.sql
|
||||
.get_config(context, "configured_addr")
|
||||
.get_config(Config::ConfiguredAddr)
|
||||
.unwrap_or_else(|| "no-self".to_string())
|
||||
.to_lowercase();
|
||||
|
||||
@@ -1536,7 +1521,10 @@ fn hex_hash(s: impl AsRef<str>) -> String {
|
||||
}
|
||||
|
||||
#[allow(non_snake_case)]
|
||||
fn search_chat_ids_by_contact_ids(context: &Context, unsorted_contact_ids: &Vec<u32>) -> Vec<u32> {
|
||||
fn search_chat_ids_by_contact_ids(
|
||||
context: &Context,
|
||||
unsorted_contact_ids: &Vec<u32>,
|
||||
) -> Result<Vec<u32>> {
|
||||
/* searches chat_id's by the given contact IDs, may return zero, one or more chat_id's */
|
||||
let mut contact_ids = Vec::with_capacity(23);
|
||||
let mut chat_ids = Vec::with_capacity(23);
|
||||
@@ -1591,18 +1579,18 @@ fn search_chat_ids_by_contact_ids(context: &Context, unsorted_contact_ids: &Vec<
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
).unwrap(); // TODO: better error handling
|
||||
)?;
|
||||
}
|
||||
}
|
||||
|
||||
chat_ids
|
||||
Ok(chat_ids)
|
||||
}
|
||||
|
||||
fn check_verified_properties(
|
||||
context: &Context,
|
||||
mimeparser: &MimeParser,
|
||||
from_id: u32,
|
||||
to_ids: &Vec<u32>,
|
||||
to_ids: &[u32],
|
||||
) -> Result<()> {
|
||||
let contact = Contact::load_from_db(context, from_id)?;
|
||||
|
||||
@@ -1670,7 +1658,7 @@ fn check_verified_properties(
|
||||
let fp = peerstate.gossip_key_fingerprint.clone();
|
||||
if let Some(fp) = fp {
|
||||
peerstate.set_verified(0, &fp, 2);
|
||||
peerstate.save_to_db(&context.sql, false).unwrap();
|
||||
peerstate.save_to_db(&context.sql, false)?;
|
||||
is_verified = true;
|
||||
}
|
||||
}
|
||||
@@ -1697,13 +1685,7 @@ fn set_better_msg(mime_parser: &mut MimeParser, better_msg: impl AsRef<str>) {
|
||||
|
||||
unsafe fn dc_is_reply_to_known_message(context: &Context, mime_parser: &MimeParser) -> libc::c_int {
|
||||
/* check if the message is a reply to a known message; the replies are identified by the Message-ID from
|
||||
`In-Reply-To`/`References:` (to support non-Delta-Clients) or from `Chat-Predecessor:` (Delta clients, see comment in dc_chat.c) */
|
||||
if let Some(optional_field) = mime_parser.lookup_optional_field("Chat-Predecessor") {
|
||||
let optional_field_c = CString::new(optional_field).unwrap();
|
||||
if 0 != is_known_rfc724_mid(context, optional_field_c.as_ptr()) {
|
||||
return 1;
|
||||
}
|
||||
}
|
||||
`In-Reply-To`/`References:` (to support non-Delta-Clients) */
|
||||
|
||||
if let Some(field) = mime_parser.lookup_field("In-Reply-To") {
|
||||
if (*field).fld_type == MAILIMF_FIELD_IN_REPLY_TO as libc::c_int {
|
||||
@@ -1722,13 +1704,13 @@ unsafe fn dc_is_reply_to_known_message(context: &Context, mime_parser: &MimePars
|
||||
if let Some(field) = mime_parser.lookup_field("References") {
|
||||
if (*field).fld_type == MAILIMF_FIELD_REFERENCES as libc::c_int {
|
||||
let fld_references = (*field).fld_data.fld_references;
|
||||
if !fld_references.is_null() {
|
||||
if is_known_rfc724_mid_in_list(
|
||||
if !fld_references.is_null()
|
||||
&& is_known_rfc724_mid_in_list(
|
||||
context,
|
||||
(*(*field).fld_data.fld_references).mid_list,
|
||||
) {
|
||||
return 1;
|
||||
}
|
||||
)
|
||||
{
|
||||
return 1;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1742,12 +1724,12 @@ unsafe fn is_known_rfc724_mid_in_list(context: &Context, mid_list: *const clist)
|
||||
}
|
||||
|
||||
for data in &*mid_list {
|
||||
if 0 != is_known_rfc724_mid(context, data.cast()) {
|
||||
if is_known_rfc724_mid(context, data.cast()) != 0 {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
false
|
||||
}
|
||||
|
||||
/// Check if a message is a reply to a known message (messenger or non-messenger).
|
||||
@@ -1951,8 +1933,7 @@ unsafe fn add_or_lookup_contact_by_addr(
|
||||
}
|
||||
*check_self = 0;
|
||||
let self_addr = context
|
||||
.sql
|
||||
.get_config(context, "configured_addr")
|
||||
.get_config(Config::ConfiguredAddr)
|
||||
.unwrap_or_default();
|
||||
|
||||
if addr_cmp(self_addr, as_str(addr_spec)) {
|
||||
@@ -1965,17 +1946,15 @@ unsafe fn add_or_lookup_contact_by_addr(
|
||||
/* add addr_spec if missing, update otherwise */
|
||||
let mut display_name_dec = "".to_string();
|
||||
if !display_name_enc.is_null() {
|
||||
let tmp = as_str(dc_decode_header_words(display_name_enc));
|
||||
let tmp = dc_decode_header_words(as_str(display_name_enc));
|
||||
display_name_dec = normalize_name(&tmp);
|
||||
}
|
||||
/*can be NULL*/
|
||||
let row_id = Contact::add_or_lookup(context, display_name_dec, as_str(addr_spec), origin)
|
||||
.map(|(id, _)| id)
|
||||
.unwrap_or_default();
|
||||
if 0 != row_id {
|
||||
if !ids.contains(&row_id) {
|
||||
ids.push(row_id);
|
||||
}
|
||||
if 0 != row_id && !ids.contains(&row_id) {
|
||||
ids.push(row_id);
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
@@ -10,18 +10,16 @@ pub struct Simplify {
|
||||
///
|
||||
/// Also return whether not-standard (rfc3676, §4.3) footer is found.
|
||||
fn find_message_footer(lines: &[&str]) -> (usize, bool) {
|
||||
for ix in 0..lines.len() {
|
||||
let line = lines[ix];
|
||||
|
||||
for (ix, &line) in lines.iter().enumerate() {
|
||||
// quoted-printable may encode `-- ` to `-- =20` which is converted
|
||||
// back to `-- `
|
||||
match line.as_ref() {
|
||||
match line {
|
||||
"-- " | "-- " => return (ix, false),
|
||||
"--" | "---" | "----" => return (ix, true),
|
||||
_ => (),
|
||||
}
|
||||
}
|
||||
return (lines.len(), false);
|
||||
(lines.len(), false)
|
||||
}
|
||||
|
||||
impl Simplify {
|
||||
@@ -103,10 +101,8 @@ impl Simplify {
|
||||
if let Some(last_quoted_line) = l_lastQuotedLine {
|
||||
l_last = last_quoted_line;
|
||||
is_cut_at_end = true;
|
||||
if l_last > 1 {
|
||||
if is_empty_line(lines[l_last - 1]) {
|
||||
l_last -= 1
|
||||
}
|
||||
if l_last > 1 && is_empty_line(lines[l_last - 1]) {
|
||||
l_last -= 1
|
||||
}
|
||||
if l_last > 1 {
|
||||
let line = lines[l_last - 1];
|
||||
@@ -205,7 +201,7 @@ fn is_quoted_headline(buf: &str) -> bool {
|
||||
}
|
||||
|
||||
fn is_plain_quote(buf: &str) -> bool {
|
||||
buf.starts_with(">")
|
||||
buf.starts_with('>')
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
|
||||
@@ -4,7 +4,7 @@ use std::ffi::CString;
|
||||
use std::ptr;
|
||||
|
||||
use charset::Charset;
|
||||
use libc::{free, strlen};
|
||||
use libc::free;
|
||||
use mmime::mailmime::decode::mailmime_encoded_phrase_parse;
|
||||
use mmime::other::*;
|
||||
use percent_encoding::{percent_decode, utf8_percent_encode, AsciiSet, CONTROLS};
|
||||
@@ -68,28 +68,7 @@ fn quote_word(word: &[u8]) -> String {
|
||||
* Encode/decode header words, RFC 2047
|
||||
******************************************************************************/
|
||||
|
||||
pub unsafe fn dc_decode_header_words(in_0: *const libc::c_char) -> *mut libc::c_char {
|
||||
if in_0.is_null() {
|
||||
return ptr::null_mut();
|
||||
}
|
||||
let mut out: *mut libc::c_char = ptr::null_mut();
|
||||
let mut cur_token = 0;
|
||||
let r: libc::c_int = mailmime_encoded_phrase_parse(
|
||||
b"iso-8859-1\x00" as *const u8 as *const libc::c_char,
|
||||
in_0,
|
||||
strlen(in_0),
|
||||
&mut cur_token,
|
||||
b"utf-8\x00" as *const u8 as *const libc::c_char,
|
||||
&mut out,
|
||||
);
|
||||
if r != MAILIMF_NO_ERROR as libc::c_int || out.is_null() {
|
||||
out = dc_strdup(in_0)
|
||||
}
|
||||
|
||||
out
|
||||
}
|
||||
|
||||
pub fn dc_decode_header_words_safe(input: &str) -> String {
|
||||
pub(crate) fn dc_decode_header_words(input: &str) -> String {
|
||||
static FROM_ENCODING: &[u8] = b"iso-8859-1\x00";
|
||||
static TO_ENCODING: &[u8] = b"utf-8\x00";
|
||||
let mut out = ptr::null_mut();
|
||||
@@ -107,7 +86,7 @@ pub fn dc_decode_header_words_safe(input: &str) -> String {
|
||||
if r as u32 != MAILIMF_NO_ERROR || out.is_null() {
|
||||
input.to_string()
|
||||
} else {
|
||||
let res = to_string(out);
|
||||
let res = to_string_lossy(out);
|
||||
free(out.cast());
|
||||
res
|
||||
}
|
||||
@@ -177,60 +156,31 @@ pub fn dc_decode_ext_header(to_decode: &[u8]) -> Cow<str> {
|
||||
mod tests {
|
||||
use super::*;
|
||||
|
||||
use libc::strcmp;
|
||||
use std::ffi::CStr;
|
||||
|
||||
#[test]
|
||||
fn test_dc_decode_header_words() {
|
||||
unsafe {
|
||||
let mut buf1: *mut libc::c_char = dc_decode_header_words(
|
||||
b"=?utf-8?B?dGVzdMOkw7bDvC50eHQ=?=\x00" as *const u8 as *const libc::c_char,
|
||||
);
|
||||
assert_eq!(
|
||||
strcmp(
|
||||
buf1,
|
||||
b"test\xc3\xa4\xc3\xb6\xc3\xbc.txt\x00" as *const u8 as *const libc::c_char
|
||||
),
|
||||
0
|
||||
);
|
||||
free(buf1 as *mut libc::c_void);
|
||||
assert_eq!(
|
||||
dc_decode_header_words("=?utf-8?B?dGVzdMOkw7bDvC50eHQ=?="),
|
||||
std::string::String::from_utf8(b"test\xc3\xa4\xc3\xb6\xc3\xbc.txt".to_vec()).unwrap(),
|
||||
);
|
||||
|
||||
buf1 =
|
||||
dc_decode_header_words(b"just ascii test\x00" as *const u8 as *const libc::c_char);
|
||||
assert_eq!(CStr::from_ptr(buf1).to_str().unwrap(), "just ascii test");
|
||||
free(buf1 as *mut libc::c_void);
|
||||
assert_eq!(dc_decode_header_words("just ascii test"), "just ascii test");
|
||||
|
||||
assert_eq!(dc_encode_header_words("abcdef"), "abcdef");
|
||||
assert_eq!(dc_encode_header_words("abcdef"), "abcdef");
|
||||
|
||||
let r = dc_encode_header_words(
|
||||
std::string::String::from_utf8(b"test\xc3\xa4\xc3\xb6\xc3\xbc.txt".to_vec())
|
||||
.unwrap(),
|
||||
let r = dc_encode_header_words(
|
||||
std::string::String::from_utf8(b"test\xc3\xa4\xc3\xb6\xc3\xbc.txt".to_vec()).unwrap(),
|
||||
);
|
||||
assert!(r.starts_with("=?utf-8"));
|
||||
|
||||
assert_eq!(
|
||||
dc_decode_header_words(&r),
|
||||
std::string::String::from_utf8(b"test\xc3\xa4\xc3\xb6\xc3\xbc.txt".to_vec()).unwrap(),
|
||||
);
|
||||
|
||||
assert_eq!(
|
||||
dc_decode_header_words("=?ISO-8859-1?Q?attachment=3B=0D=0A_filename=3D?= =?ISO-8859-1?Q?=22test=E4=F6=FC=2Etxt=22=3B=0D=0A_size=3D39?="),
|
||||
std::string::String::from_utf8(b"attachment;\r\n filename=\"test\xc3\xa4\xc3\xb6\xc3\xbc.txt\";\r\n size=39".to_vec()).unwrap(),
|
||||
);
|
||||
assert!(r.starts_with("=?utf-8"));
|
||||
|
||||
buf1 = r.strdup();
|
||||
let buf2: *mut libc::c_char = dc_decode_header_words(buf1);
|
||||
assert_eq!(
|
||||
strcmp(
|
||||
buf2,
|
||||
b"test\xc3\xa4\xc3\xb6\xc3\xbc.txt\x00" as *const u8 as *const libc::c_char
|
||||
),
|
||||
0
|
||||
);
|
||||
free(buf2 as *mut libc::c_void);
|
||||
|
||||
buf1 = dc_decode_header_words(
|
||||
b"=?ISO-8859-1?Q?attachment=3B=0D=0A_filename=3D?= =?ISO-8859-1?Q?=22test=E4=F6=FC=2Etxt=22=3B=0D=0A_size=3D39?=\x00" as *const u8 as *const libc::c_char
|
||||
);
|
||||
assert_eq!(
|
||||
strcmp(
|
||||
buf1,
|
||||
b"attachment;\r\n filename=\"test\xc3\xa4\xc3\xb6\xc3\xbc.txt\";\r\n size=39\x00" as *const u8 as *const libc::c_char,
|
||||
|
||||
),
|
||||
0
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
@@ -293,7 +243,7 @@ mod tests {
|
||||
#[test]
|
||||
fn test_dc_header_roundtrip(input: String) {
|
||||
let encoded = dc_encode_header_words(&input);
|
||||
let decoded = dc_decode_header_words_safe(&encoded);
|
||||
let decoded = dc_decode_header_words(&encoded);
|
||||
|
||||
assert_eq!(input, decoded);
|
||||
}
|
||||
|
||||
348
src/dc_tools.rs
348
src/dc_tools.rs
@@ -1,8 +1,9 @@
|
||||
//! Some tools and enhancements to the used libraries, there should be
|
||||
//! no references to Context and other "larger" entities here.
|
||||
|
||||
use core::cmp::max;
|
||||
use std::borrow::Cow;
|
||||
use std::ffi::{CStr, CString, OsString};
|
||||
use std::ffi::{CStr, CString};
|
||||
use std::path::{Path, PathBuf};
|
||||
use std::str::FromStr;
|
||||
use std::time::SystemTime;
|
||||
@@ -16,6 +17,7 @@ use rand::{thread_rng, Rng};
|
||||
|
||||
use crate::context::Context;
|
||||
use crate::error::Error;
|
||||
use crate::events::Event;
|
||||
|
||||
pub(crate) fn dc_exactly_one_bit_set(v: libc::c_int) -> bool {
|
||||
0 != v && 0 == v & (v - 1)
|
||||
@@ -28,11 +30,11 @@ pub(crate) fn dc_exactly_one_bit_set(v: libc::c_int) -> bool {
|
||||
/// # Examples
|
||||
///
|
||||
/// ```
|
||||
/// use deltachat::dc_tools::{dc_strdup, to_string};
|
||||
/// use deltachat::dc_tools::{dc_strdup, to_string_lossy};
|
||||
/// unsafe {
|
||||
/// let str_a = b"foobar\x00" as *const u8 as *const libc::c_char;
|
||||
/// let str_a_copy = dc_strdup(str_a);
|
||||
/// assert_eq!(to_string(str_a_copy), "foobar");
|
||||
/// assert_eq!(to_string_lossy(str_a_copy), "foobar");
|
||||
/// assert_ne!(str_a, str_a_copy);
|
||||
/// }
|
||||
/// ```
|
||||
@@ -49,83 +51,6 @@ pub unsafe fn dc_strdup(s: *const libc::c_char) -> *mut libc::c_char {
|
||||
ret
|
||||
}
|
||||
|
||||
pub(crate) fn dc_atoi_null_is_0(s: *const libc::c_char) -> libc::c_int {
|
||||
if !s.is_null() {
|
||||
as_str(s).parse().unwrap_or_default()
|
||||
} else {
|
||||
0
|
||||
}
|
||||
}
|
||||
|
||||
unsafe fn dc_ltrim(buf: *mut libc::c_char) {
|
||||
let mut len: libc::size_t;
|
||||
let mut cur: *const libc::c_uchar;
|
||||
if !buf.is_null() && 0 != *buf as libc::c_int {
|
||||
len = strlen(buf);
|
||||
cur = buf as *const libc::c_uchar;
|
||||
while 0 != *cur as libc::c_int && 0 != libc::isspace(*cur as libc::c_int) {
|
||||
cur = cur.offset(1isize);
|
||||
len = len.wrapping_sub(1)
|
||||
}
|
||||
if buf as *const libc::c_uchar != cur {
|
||||
libc::memmove(
|
||||
buf as *mut libc::c_void,
|
||||
cur as *const libc::c_void,
|
||||
len.wrapping_add(1),
|
||||
);
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
unsafe fn dc_rtrim(buf: *mut libc::c_char) {
|
||||
let mut len: libc::size_t;
|
||||
let mut cur: *mut libc::c_uchar;
|
||||
if !buf.is_null() && 0 != *buf as libc::c_int {
|
||||
len = strlen(buf);
|
||||
cur = (buf as *mut libc::c_uchar)
|
||||
.offset(len as isize)
|
||||
.offset(-1isize);
|
||||
while cur != buf as *mut libc::c_uchar && 0 != libc::isspace(*cur as libc::c_int) {
|
||||
cur = cur.offset(-1isize);
|
||||
len = len.wrapping_sub(1)
|
||||
}
|
||||
*cur.offset(
|
||||
(if 0 != libc::isspace(*cur as libc::c_int) {
|
||||
0
|
||||
} else {
|
||||
1
|
||||
}) as isize,
|
||||
) = '\u{0}' as i32 as libc::c_uchar
|
||||
};
|
||||
}
|
||||
|
||||
pub(crate) unsafe fn dc_trim(buf: *mut libc::c_char) {
|
||||
dc_ltrim(buf);
|
||||
dc_rtrim(buf);
|
||||
}
|
||||
|
||||
/* remove all \r characters from string */
|
||||
pub(crate) unsafe fn dc_remove_cr_chars(buf: *mut libc::c_char) {
|
||||
/* search for first `\r` */
|
||||
let mut p1: *const libc::c_char = buf;
|
||||
while 0 != *p1 {
|
||||
if *p1 as libc::c_int == '\r' as i32 {
|
||||
break;
|
||||
}
|
||||
p1 = p1.offset(1isize)
|
||||
}
|
||||
/* p1 is `\r` or null-byte; start removing `\r` */
|
||||
let mut p2: *mut libc::c_char = p1 as *mut libc::c_char;
|
||||
while 0 != *p1 {
|
||||
if *p1 as libc::c_int != '\r' as i32 {
|
||||
*p2 = *p1;
|
||||
p2 = p2.offset(1isize)
|
||||
}
|
||||
p1 = p1.offset(1isize)
|
||||
}
|
||||
*p2 = 0 as libc::c_char;
|
||||
}
|
||||
|
||||
/// Shortens a string to a specified length and adds "..." or "[...]" to the end of
|
||||
/// the shortened string.
|
||||
pub(crate) fn dc_truncate(buf: &str, approx_chars: usize, do_unwrap: bool) -> Cow<str> {
|
||||
@@ -140,7 +65,7 @@ pub(crate) fn dc_truncate(buf: &str, approx_chars: usize, do_unwrap: bool) -> Co
|
||||
.unwrap_or_default();
|
||||
|
||||
if let Some(index) = buf[..end_pos].rfind(|c| c == ' ' || c == '\n') {
|
||||
Cow::Owned(format!("{}{}", &buf[..index + 1], ellipse))
|
||||
Cow::Owned(format!("{}{}", &buf[..=index], ellipse))
|
||||
} else {
|
||||
Cow::Owned(format!("{}{}", &buf[..end_pos], ellipse))
|
||||
}
|
||||
@@ -333,14 +258,14 @@ fn encode_66bits_as_base64(v1: u32, v2: u32, fill: u32) -> String {
|
||||
enc.write_u8(((fill & 0x3) as u8) << 6).unwrap();
|
||||
enc.finish().unwrap();
|
||||
}
|
||||
assert_eq!(wrapped_writer.pop(), Some('A' as u8)); // Remove last "A"
|
||||
assert_eq!(wrapped_writer.pop(), Some(b'A')); // Remove last "A"
|
||||
String::from_utf8(wrapped_writer).unwrap()
|
||||
}
|
||||
|
||||
pub(crate) fn dc_create_incoming_rfc724_mid(
|
||||
message_timestamp: i64,
|
||||
contact_id_from: u32,
|
||||
contact_ids_to: &Vec<u32>,
|
||||
contact_ids_to: &[u32],
|
||||
) -> Option<String> {
|
||||
if contact_ids_to.is_empty() {
|
||||
return None;
|
||||
@@ -415,12 +340,41 @@ pub(crate) fn dc_ensure_no_slash_safe(path: &str) -> &str {
|
||||
path
|
||||
}
|
||||
|
||||
/// Function modifies the given buffer and replaces all characters not valid in filenames by a "-".
|
||||
fn validate_filename(filename: &str) -> String {
|
||||
filename
|
||||
.replace('/', "-")
|
||||
.replace('\\', "-")
|
||||
.replace(':', "-")
|
||||
// Function returns a sanitized basename that does not contain
|
||||
// win/linux path separators and also not any non-ascii chars
|
||||
fn get_safe_basename(filename: &str) -> String {
|
||||
// return the (potentially mangled) basename of the input filename
|
||||
// this might be a path that comes in from another operating system
|
||||
let mut index: usize = 0;
|
||||
|
||||
if let Some(unix_index) = filename.rfind('/') {
|
||||
index = unix_index + 1;
|
||||
}
|
||||
if let Some(win_index) = filename.rfind('\\') {
|
||||
index = max(index, win_index + 1);
|
||||
}
|
||||
if index >= filename.len() {
|
||||
"nobasename".to_string()
|
||||
} else {
|
||||
// we don't allow any non-ascii to be super-safe
|
||||
filename[index..].replace(|c: char| !c.is_ascii() || c == ':', "-")
|
||||
}
|
||||
}
|
||||
|
||||
pub fn dc_derive_safe_stem_ext(filename: &str) -> (String, String) {
|
||||
let basename = get_safe_basename(&filename);
|
||||
let (mut stem, mut ext) = if let Some(index) = basename.rfind('.') {
|
||||
(
|
||||
basename[0..index].to_string(),
|
||||
basename[index..].to_string(),
|
||||
)
|
||||
} else {
|
||||
(basename, "".to_string())
|
||||
};
|
||||
// limit length of stem and ext
|
||||
stem.truncate(32);
|
||||
ext.truncate(32);
|
||||
(stem, ext)
|
||||
}
|
||||
|
||||
// the returned suffix is lower-case
|
||||
@@ -482,10 +436,14 @@ pub(crate) fn dc_delete_file(context: &Context, path: impl AsRef<std::path::Path
|
||||
return false;
|
||||
}
|
||||
|
||||
let dpath = format!("{}", path.as_ref().to_string_lossy());
|
||||
match fs::remove_file(path_abs) {
|
||||
Ok(_) => true,
|
||||
Ok(_) => {
|
||||
context.call_cb(Event::DeletedBlobFile(dpath));
|
||||
true
|
||||
}
|
||||
Err(_err) => {
|
||||
warn!(context, "Cannot delete \"{}\".", path.as_ref().display());
|
||||
warn!(context, "Cannot delete \"{}\".", dpath);
|
||||
false
|
||||
}
|
||||
}
|
||||
@@ -566,49 +524,43 @@ pub fn dc_read_file<P: AsRef<std::path::Path>>(
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) fn dc_get_fine_path_filename(
|
||||
pub fn dc_open_file<P: AsRef<std::path::Path>>(
|
||||
context: &Context,
|
||||
folder: impl AsRef<Path>,
|
||||
desired_filename_suffix: impl AsRef<str>,
|
||||
) -> PathBuf {
|
||||
let now = time();
|
||||
path: P,
|
||||
) -> Result<std::fs::File, Error> {
|
||||
let path_abs = dc_get_abs_path(context, &path);
|
||||
|
||||
let folder = PathBuf::from(folder.as_ref());
|
||||
// XXX sanitize desired_filename eg using
|
||||
// https://github.com/kardeiz/sanitize-filename/blob/master/src/lib.rs
|
||||
let suffix = validate_filename(desired_filename_suffix.as_ref());
|
||||
let file_name = PathBuf::from(suffix);
|
||||
let extension = file_name.extension().map(|c| c.clone());
|
||||
|
||||
for i in 0..100_000 {
|
||||
let ret = if i == 0 {
|
||||
let mut folder = folder.clone();
|
||||
folder.push(&file_name);
|
||||
folder
|
||||
} else {
|
||||
let idx = if i < 100 { i } else { now + i };
|
||||
let file_name = if let Some(stem) = file_name.file_stem() {
|
||||
let mut stem = stem.to_os_string();
|
||||
stem.push(format!("-{}", idx));
|
||||
stem
|
||||
} else {
|
||||
OsString::from(idx.to_string())
|
||||
};
|
||||
let mut folder = folder.clone();
|
||||
folder.push(file_name);
|
||||
if let Some(ext) = extension {
|
||||
folder.set_extension(&ext);
|
||||
}
|
||||
folder
|
||||
};
|
||||
|
||||
if !dc_file_exist(context, &ret) {
|
||||
// fine filename found
|
||||
return ret;
|
||||
match fs::File::open(&path_abs) {
|
||||
Ok(bytes) => Ok(bytes),
|
||||
Err(err) => {
|
||||
warn!(
|
||||
context,
|
||||
"Cannot read \"{}\" or file is empty.",
|
||||
path.as_ref().display()
|
||||
);
|
||||
Err(err.into())
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
panic!("Something is really wrong, you need to clean up your disk");
|
||||
pub(crate) fn dc_get_next_backup_path(
|
||||
folder: impl AsRef<Path>,
|
||||
backup_time: i64,
|
||||
) -> Result<PathBuf, Error> {
|
||||
let folder = PathBuf::from(folder.as_ref());
|
||||
let stem = chrono::NaiveDateTime::from_timestamp(backup_time, 0)
|
||||
.format("delta-chat-%Y-%m-%d")
|
||||
.to_string();
|
||||
|
||||
// 64 backup files per day should be enough for everyone
|
||||
for i in 0..64 {
|
||||
let mut path = folder.clone();
|
||||
path.push(format!("{}-{}.bak", stem, i));
|
||||
if !path.exists() {
|
||||
return Ok(path);
|
||||
}
|
||||
}
|
||||
bail!("could not create backup file, disk full?");
|
||||
}
|
||||
|
||||
pub(crate) fn dc_is_blobdir_path(context: &Context, path: impl AsRef<str>) -> bool {
|
||||
@@ -627,7 +579,10 @@ fn dc_make_rel_path(context: &Context, path: &mut String) {
|
||||
.map(|s| path.starts_with(s))
|
||||
.unwrap_or_default()
|
||||
{
|
||||
*path = path.replace(context.get_blobdir().to_str().unwrap(), "$BLOBDIR");
|
||||
*path = path.replace(
|
||||
context.get_blobdir().to_str().unwrap_or_default(),
|
||||
"$BLOBDIR",
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -636,13 +591,10 @@ pub(crate) fn dc_make_rel_and_copy(context: &Context, path: &mut String) -> bool
|
||||
dc_make_rel_path(context, path);
|
||||
return true;
|
||||
}
|
||||
let blobdir_path = dc_get_fine_path_filename(context, "$BLOBDIR", &path);
|
||||
if dc_copy_file(context, &path, &blobdir_path) {
|
||||
*path = blobdir_path.to_string_lossy().to_string();
|
||||
dc_make_rel_path(context, path);
|
||||
if let Ok(blobdir_path) = context.copy_to_blobdir(&path) {
|
||||
*path = blobdir_path;
|
||||
return true;
|
||||
}
|
||||
|
||||
false
|
||||
}
|
||||
|
||||
@@ -791,22 +743,6 @@ impl<T: AsRef<str>> StrExt for T {
|
||||
}
|
||||
}
|
||||
|
||||
pub fn to_string(s: *const libc::c_char) -> String {
|
||||
if s.is_null() {
|
||||
return "".into();
|
||||
}
|
||||
|
||||
let cstr = unsafe { CStr::from_ptr(s) };
|
||||
|
||||
cstr.to_str().map(|s| s.to_string()).unwrap_or_else(|err| {
|
||||
panic!(
|
||||
"Non utf8 string: '{:?}' ({:?})",
|
||||
cstr.to_string_lossy(),
|
||||
err
|
||||
);
|
||||
})
|
||||
}
|
||||
|
||||
pub fn to_string_lossy(s: *const libc::c_char) -> String {
|
||||
if s.is_null() {
|
||||
return "".into();
|
||||
@@ -814,9 +750,7 @@ pub fn to_string_lossy(s: *const libc::c_char) -> String {
|
||||
|
||||
let cstr = unsafe { CStr::from_ptr(s) };
|
||||
|
||||
cstr.to_str()
|
||||
.map(|s| s.to_string())
|
||||
.unwrap_or_else(|_| cstr.to_string_lossy().to_string())
|
||||
cstr.to_string_lossy().to_string()
|
||||
}
|
||||
|
||||
pub fn as_str<'a>(s: *const libc::c_char) -> &'a str {
|
||||
@@ -1021,54 +955,6 @@ mod tests {
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_dc_ltrim() {
|
||||
unsafe {
|
||||
let html: *const libc::c_char =
|
||||
b"\r\r\nline1<br>\r\n\r\n\r\rline2\n\r\x00" as *const u8 as *const libc::c_char;
|
||||
let out: *mut libc::c_char = strndup(html, strlen(html) as libc::c_ulong);
|
||||
|
||||
dc_ltrim(out);
|
||||
|
||||
assert_eq!(
|
||||
CStr::from_ptr(out as *const libc::c_char).to_str().unwrap(),
|
||||
"line1<br>\r\n\r\n\r\rline2\n\r"
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_dc_rtrim() {
|
||||
unsafe {
|
||||
let html: *const libc::c_char =
|
||||
b"\r\r\nline1<br>\r\n\r\n\r\rline2\n\r\x00" as *const u8 as *const libc::c_char;
|
||||
let out: *mut libc::c_char = strndup(html, strlen(html) as libc::c_ulong);
|
||||
|
||||
dc_rtrim(out);
|
||||
|
||||
assert_eq!(
|
||||
CStr::from_ptr(out as *const libc::c_char).to_str().unwrap(),
|
||||
"\r\r\nline1<br>\r\n\r\n\r\rline2"
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_dc_trim() {
|
||||
unsafe {
|
||||
let html: *const libc::c_char =
|
||||
b"\r\r\nline1<br>\r\n\r\n\r\rline2\n\r\x00" as *const u8 as *const libc::c_char;
|
||||
let out: *mut libc::c_char = strndup(html, strlen(html) as libc::c_ulong);
|
||||
|
||||
dc_trim(out);
|
||||
|
||||
assert_eq!(
|
||||
CStr::from_ptr(out as *const libc::c_char).to_str().unwrap(),
|
||||
"line1<br>\r\n\r\n\r\rline2"
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_rust_ftoa() {
|
||||
assert_eq!("1.22", format!("{}", 1.22));
|
||||
@@ -1345,6 +1231,25 @@ mod tests {
|
||||
assert_eq!(grpid, Some("1234567890123456"));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_dc_create_outgoing_rfc724_mid() {
|
||||
// create a normal message-id
|
||||
let mid = dc_create_outgoing_rfc724_mid(None, "foo@bar.de");
|
||||
assert!(mid.starts_with("Mr."));
|
||||
assert!(mid.ends_with("bar.de"));
|
||||
assert!(dc_extract_grpid_from_rfc724_mid(mid.as_str()).is_none());
|
||||
|
||||
// create a message-id containing a group-id
|
||||
let grpid = dc_create_id();
|
||||
let mid = dc_create_outgoing_rfc724_mid(Some(&grpid), "foo@bar.de");
|
||||
assert!(mid.starts_with("Gr."));
|
||||
assert!(mid.ends_with("bar.de"));
|
||||
assert_eq!(
|
||||
dc_extract_grpid_from_rfc724_mid(mid.as_str()),
|
||||
Some(grpid.as_str())
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_emailaddress_parse() {
|
||||
assert_eq!(EmailAddress::new("").is_ok(), false);
|
||||
@@ -1429,14 +1334,27 @@ mod tests {
|
||||
unsafe {
|
||||
let res = strndup(b"helloworld\x00" as *const u8 as *const libc::c_char, 4);
|
||||
assert_eq!(
|
||||
to_string(res),
|
||||
to_string(b"hell\x00" as *const u8 as *const libc::c_char)
|
||||
to_string_lossy(res),
|
||||
to_string_lossy(b"hell\x00" as *const u8 as *const libc::c_char)
|
||||
);
|
||||
assert_eq!(strlen(res), 4);
|
||||
free(res as *mut _);
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_file_get_safe_basename() {
|
||||
assert_eq!(get_safe_basename("12312/hello"), "hello");
|
||||
assert_eq!(get_safe_basename("12312\\hello"), "hello");
|
||||
assert_eq!(get_safe_basename("//12312\\hello"), "hello");
|
||||
assert_eq!(get_safe_basename("//123:12\\hello"), "hello");
|
||||
assert_eq!(get_safe_basename("//123:12/\\\\hello"), "hello");
|
||||
assert_eq!(get_safe_basename("//123:12//hello"), "hello");
|
||||
assert_eq!(get_safe_basename("//123:12//"), "nobasename");
|
||||
assert_eq!(get_safe_basename("//123:12/"), "nobasename");
|
||||
assert!(get_safe_basename("123\x012.hello").ends_with(".hello"));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_file_handling() {
|
||||
let t = dummy_context();
|
||||
@@ -1471,6 +1389,7 @@ mod tests {
|
||||
assert!(dc_file_exist(context, &abs_path));
|
||||
|
||||
assert!(dc_copy_file(context, "$BLOBDIR/foobar", "$BLOBDIR/dada",));
|
||||
|
||||
assert_eq!(dc_get_filebytes(context, "$BLOBDIR/dada",), 7);
|
||||
|
||||
let buf = dc_read_file(context, "$BLOBDIR/dada").unwrap();
|
||||
@@ -1483,14 +1402,12 @@ mod tests {
|
||||
assert!(dc_create_folder(context, "$BLOBDIR/foobar-folder"));
|
||||
assert!(dc_file_exist(context, "$BLOBDIR/foobar-folder",));
|
||||
assert!(!dc_delete_file(context, "$BLOBDIR/foobar-folder"));
|
||||
let fn0 = dc_get_fine_path_filename(context, "$BLOBDIR", "foobar.dadada");
|
||||
assert_eq!(fn0, PathBuf::from("$BLOBDIR/foobar.dadada"));
|
||||
|
||||
let fn0 = "$BLOBDIR/data.data";
|
||||
assert!(dc_write_file(context, &fn0, b"content"));
|
||||
let fn1 = dc_get_fine_path_filename(context, "$BLOBDIR", "foobar.dadada");
|
||||
assert_eq!(fn1, PathBuf::from("$BLOBDIR/foobar-1.dadada"));
|
||||
|
||||
assert!(dc_delete_file(context, &fn0));
|
||||
assert!(!dc_file_exist(context, &fn0));
|
||||
}
|
||||
|
||||
#[test]
|
||||
@@ -1506,19 +1423,4 @@ mod tests {
|
||||
let listflags: u32 = DC_GCL_VERIFIED_ONLY.try_into().unwrap();
|
||||
assert!(listflags_has(listflags, DC_GCL_ADD_SELF) == false);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_dc_remove_cr_chars() {
|
||||
unsafe {
|
||||
let input = "foo\r\nbar".strdup();
|
||||
dc_remove_cr_chars(input);
|
||||
assert_eq!("foo\nbar", to_string(input));
|
||||
free(input.cast());
|
||||
|
||||
let input = "\rfoo\r\rbar\r".strdup();
|
||||
dc_remove_cr_chars(input);
|
||||
assert_eq!("foobar", to_string(input));
|
||||
free(input.cast());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
195
src/e2ee.rs
195
src/e2ee.rs
@@ -22,14 +22,13 @@ use num_traits::FromPrimitive;
|
||||
use crate::aheader::*;
|
||||
use crate::config::Config;
|
||||
use crate::context::Context;
|
||||
use crate::dc_mimeparser::*;
|
||||
use crate::dc_tools::*;
|
||||
use crate::error::*;
|
||||
use crate::key::*;
|
||||
use crate::keyring::*;
|
||||
use crate::mimefactory::MimeFactory;
|
||||
use crate::peerstate::*;
|
||||
use crate::pgp::*;
|
||||
use crate::pgp;
|
||||
use crate::securejoin::handle_degrade_event;
|
||||
use crate::wrapmime;
|
||||
use crate::wrapmime::*;
|
||||
@@ -47,12 +46,9 @@ pub struct EncryptHelper {
|
||||
|
||||
impl EncryptHelper {
|
||||
pub fn new(context: &Context) -> Result<EncryptHelper> {
|
||||
let prefer_encrypt = context
|
||||
.sql
|
||||
.get_config_int(&context, "e2ee_enabled")
|
||||
.and_then(EncryptPreference::from_i32)
|
||||
.unwrap_or_default();
|
||||
|
||||
let prefer_encrypt =
|
||||
EncryptPreference::from_i32(context.get_config_int(Config::E2eeEnabled))
|
||||
.unwrap_or_default();
|
||||
let addr = match context.get_config(Config::ConfiguredAddr) {
|
||||
None => {
|
||||
bail!("addr not configured!");
|
||||
@@ -215,7 +211,7 @@ impl EncryptHelper {
|
||||
"could not write/allocate"
|
||||
);
|
||||
|
||||
let ctext = dc_pgp_pk_encrypt(
|
||||
let ctext = pgp::pk_encrypt(
|
||||
std::slice::from_raw_parts((*plain).str_0 as *const u8, (*plain).len),
|
||||
&keyring,
|
||||
sign_key.as_ref(),
|
||||
@@ -266,103 +262,80 @@ pub fn try_decrypt(
|
||||
context: &Context,
|
||||
in_out_message: *mut Mailmime,
|
||||
) -> Result<(bool, HashSet<String>, HashSet<String>)> {
|
||||
// just a pointer into mailmime structure, must not be freed
|
||||
let imffields = mailmime_find_mailimf_fields(in_out_message);
|
||||
ensure!(
|
||||
!in_out_message.is_null() && !imffields.is_null(),
|
||||
"corrupt invalid mime inputs"
|
||||
);
|
||||
|
||||
let from = wrapmime::get_field_from(imffields)?;
|
||||
let message_time = wrapmime::get_field_date(imffields)?;
|
||||
|
||||
let mut peerstate = None;
|
||||
let autocryptheader = Aheader::from_imffields(&from, imffields);
|
||||
|
||||
if message_time > 0 {
|
||||
peerstate = Peerstate::from_addr(context, &context.sql, &from);
|
||||
|
||||
if let Some(ref mut peerstate) = peerstate {
|
||||
if let Some(ref header) = autocryptheader {
|
||||
peerstate.apply_header(&header, message_time);
|
||||
peerstate.save_to_db(&context.sql, false)?;
|
||||
} else if message_time > peerstate.last_seen_autocrypt
|
||||
&& !contains_report(in_out_message)
|
||||
{
|
||||
peerstate.degrade_encryption(message_time);
|
||||
peerstate.save_to_db(&context.sql, false)?;
|
||||
}
|
||||
} else if let Some(ref header) = autocryptheader {
|
||||
let p = Peerstate::from_header(context, header, message_time);
|
||||
p.save_to_db(&context.sql, true)?;
|
||||
peerstate = Some(p);
|
||||
}
|
||||
}
|
||||
/* possibly perform decryption */
|
||||
let mut private_keyring = Keyring::default();
|
||||
let mut public_keyring_for_validate = Keyring::default();
|
||||
let mut encrypted = false;
|
||||
let mut signatures = HashSet::default();
|
||||
let mut gossipped_addr = HashSet::default();
|
||||
|
||||
// just a pointer into mailmime structure, must not be freed
|
||||
let imffields = unsafe { mailmime_find_mailimf_fields(in_out_message) };
|
||||
let mut message_time = 0;
|
||||
let mut from = None;
|
||||
let mut private_keyring = Keyring::default();
|
||||
let mut public_keyring_for_validate = Keyring::default();
|
||||
let mut gossip_headers = ptr::null_mut();
|
||||
let self_addr = context.get_config(Config::ConfiguredAddr);
|
||||
|
||||
// XXX do wrapmime:: helper for the next block
|
||||
if !(in_out_message.is_null() || imffields.is_null()) {
|
||||
let mut field = mailimf_find_field(imffields, MAILIMF_FIELD_FROM as libc::c_int);
|
||||
|
||||
if !field.is_null() && unsafe { !(*field).fld_data.fld_from.is_null() } {
|
||||
let mb_list = unsafe { (*(*field).fld_data.fld_from).frm_mb_list };
|
||||
from = mailimf_find_first_addr(mb_list);
|
||||
}
|
||||
|
||||
field = mailimf_find_field(imffields, MAILIMF_FIELD_ORIG_DATE as libc::c_int);
|
||||
if !field.is_null() && unsafe { !(*field).fld_data.fld_orig_date.is_null() } {
|
||||
let orig_date = unsafe { (*field).fld_data.fld_orig_date };
|
||||
|
||||
if !orig_date.is_null() {
|
||||
let dt = unsafe { (*orig_date).dt_date_time };
|
||||
message_time = dc_timestamp_from_date(dt);
|
||||
if message_time != 0 && message_time > time() {
|
||||
message_time = time()
|
||||
if let Some(self_addr) = self_addr {
|
||||
if private_keyring.load_self_private_for_decrypting(context, self_addr, &context.sql) {
|
||||
if peerstate.as_ref().map(|p| p.last_seen).unwrap_or_else(|| 0) == 0 {
|
||||
peerstate = Peerstate::from_addr(&context, &context.sql, &from);
|
||||
}
|
||||
if let Some(ref peerstate) = peerstate {
|
||||
if peerstate.degrade_event.is_some() {
|
||||
handle_degrade_event(context, &peerstate)?;
|
||||
}
|
||||
if let Some(ref key) = peerstate.gossip_key {
|
||||
public_keyring_for_validate.add_ref(key);
|
||||
}
|
||||
if let Some(ref key) = peerstate.public_key {
|
||||
public_keyring_for_validate.add_ref(key);
|
||||
}
|
||||
}
|
||||
}
|
||||
let mut peerstate = None;
|
||||
let autocryptheader = from
|
||||
.as_ref()
|
||||
.and_then(|from| Aheader::from_imffields(from, imffields));
|
||||
if message_time > 0 {
|
||||
if let Some(ref from) = from {
|
||||
peerstate = Peerstate::from_addr(context, &context.sql, from);
|
||||
|
||||
if let Some(ref mut peerstate) = peerstate {
|
||||
if let Some(ref header) = autocryptheader {
|
||||
peerstate.apply_header(&header, message_time);
|
||||
peerstate.save_to_db(&context.sql, false).unwrap();
|
||||
} else if message_time > peerstate.last_seen_autocrypt
|
||||
&& !contains_report(in_out_message)
|
||||
{
|
||||
peerstate.degrade_encryption(message_time);
|
||||
peerstate.save_to_db(&context.sql, false).unwrap();
|
||||
}
|
||||
} else if let Some(ref header) = autocryptheader {
|
||||
let p = Peerstate::from_header(context, header, message_time);
|
||||
p.save_to_db(&context.sql, true).unwrap();
|
||||
peerstate = Some(p);
|
||||
}
|
||||
}
|
||||
}
|
||||
/* load private key for decryption */
|
||||
let self_addr = context.get_config(Config::ConfiguredAddr);
|
||||
if let Some(self_addr) = self_addr {
|
||||
if private_keyring.load_self_private_for_decrypting(context, self_addr, &context.sql) {
|
||||
if peerstate.as_ref().map(|p| p.last_seen).unwrap_or_else(|| 0) == 0 {
|
||||
peerstate =
|
||||
Peerstate::from_addr(&context, &context.sql, &from.unwrap_or_default());
|
||||
}
|
||||
if let Some(ref peerstate) = peerstate {
|
||||
if peerstate.degrade_event.is_some() {
|
||||
handle_degrade_event(context, &peerstate)?;
|
||||
}
|
||||
if let Some(ref key) = peerstate.gossip_key {
|
||||
public_keyring_for_validate.add_ref(key);
|
||||
}
|
||||
if let Some(ref key) = peerstate.public_key {
|
||||
public_keyring_for_validate.add_ref(key);
|
||||
}
|
||||
}
|
||||
|
||||
encrypted = decrypt_if_autocrypt_message(
|
||||
context,
|
||||
in_out_message,
|
||||
&private_keyring,
|
||||
&public_keyring_for_validate,
|
||||
&mut signatures,
|
||||
&mut gossip_headers,
|
||||
)?;
|
||||
if !gossip_headers.is_null() {
|
||||
gossipped_addr =
|
||||
update_gossip_peerstates(context, message_time, imffields, gossip_headers)?;
|
||||
}
|
||||
let mut gossip_headers = ptr::null_mut();
|
||||
encrypted = decrypt_if_autocrypt_message(
|
||||
context,
|
||||
in_out_message,
|
||||
&private_keyring,
|
||||
&public_keyring_for_validate,
|
||||
&mut signatures,
|
||||
&mut gossip_headers,
|
||||
)?;
|
||||
if !gossip_headers.is_null() {
|
||||
gossipped_addr =
|
||||
update_gossip_peerstates(context, message_time, imffields, gossip_headers)?;
|
||||
unsafe { mailimf_fields_free(gossip_headers) };
|
||||
}
|
||||
}
|
||||
}
|
||||
if !gossip_headers.is_null() {
|
||||
unsafe { mailimf_fields_free(gossip_headers) };
|
||||
}
|
||||
|
||||
Ok((encrypted, signatures, gossipped_addr))
|
||||
}
|
||||
|
||||
@@ -425,7 +398,7 @@ fn load_or_generate_self_public_key(context: &Context, self_addr: impl AsRef<str
|
||||
context,
|
||||
"Generating keypair with {} bits, e={} ...", 2048, 65537,
|
||||
);
|
||||
match dc_pgp_create_keypair(&self_addr) {
|
||||
match pgp::create_keypair(&self_addr) {
|
||||
Some((public_key, private_key)) => {
|
||||
match dc_key_save_self_keypair(
|
||||
context,
|
||||
@@ -560,7 +533,7 @@ fn decrypt_if_autocrypt_message(
|
||||
// XXX better return parsed headers so that upstream
|
||||
// does not need to dive into mmime-stuff again.
|
||||
unsafe {
|
||||
if (*ret_gossip_headers).is_null() && ret_valid_signatures.len() > 0 {
|
||||
if (*ret_gossip_headers).is_null() && !ret_valid_signatures.is_empty() {
|
||||
let mut dummy: libc::size_t = 0;
|
||||
let mut test: *mut mailimf_fields = ptr::null_mut();
|
||||
if mailimf_envelope_and_optional_fields_parse(
|
||||
@@ -600,22 +573,16 @@ fn decrypt_part(
|
||||
mime_transfer_encoding = enc;
|
||||
}
|
||||
|
||||
let (decoded_data, decoded_data_bytes) =
|
||||
wrapmime::decode_dt_data(mime_data, mime_transfer_encoding)?;
|
||||
let data: Vec<u8> = wrapmime::decode_dt_data(mime_data, mime_transfer_encoding)?;
|
||||
|
||||
// encrypted, non-NULL decoded data in decoded_data now ...
|
||||
// Note that we need to take care of freeing decoded_data ourself,
|
||||
// after encryption has been attempted.
|
||||
let mut ret_decrypted_mime = ptr::null_mut();
|
||||
|
||||
ensure!(!decoded_data.is_null(), "Missing data");
|
||||
let data = unsafe { std::slice::from_raw_parts(decoded_data as *const u8, decoded_data_bytes) };
|
||||
if has_decrypted_pgp_armor(data) {
|
||||
if has_decrypted_pgp_armor(&data) {
|
||||
// we should only have one decryption happening
|
||||
ensure!(ret_valid_signatures.is_empty(), "corrupt signatures");
|
||||
|
||||
let plain = match dc_pgp_pk_decrypt(
|
||||
data,
|
||||
let plain = match pgp::pk_decrypt(
|
||||
&data,
|
||||
&private_keyring,
|
||||
&public_keyring_for_validate,
|
||||
Some(ret_valid_signatures),
|
||||
@@ -624,10 +591,7 @@ fn decrypt_part(
|
||||
ensure!(!ret_valid_signatures.is_empty(), "no valid signatures");
|
||||
plain
|
||||
}
|
||||
Err(err) => {
|
||||
unsafe { mmap_string_unref(decoded_data) };
|
||||
bail!("could not decrypt: {}", err)
|
||||
}
|
||||
Err(err) => bail!("could not decrypt: {}", err),
|
||||
};
|
||||
let plain_bytes = plain.len();
|
||||
let plain_buf = plain.as_ptr() as *const libc::c_char;
|
||||
@@ -655,7 +619,6 @@ fn decrypt_part(
|
||||
ret_decrypted_mime = decrypted_mime;
|
||||
}
|
||||
}
|
||||
unsafe { mmap_string_unref(decoded_data) };
|
||||
|
||||
Ok(ret_decrypted_mime)
|
||||
}
|
||||
@@ -723,12 +686,12 @@ fn contains_report(mime: *mut Mailmime) -> bool {
|
||||
/// If this succeeds you are also guaranteed that the
|
||||
/// [Config::ConfiguredAddr] is configured, this address is returned.
|
||||
pub fn ensure_secret_key_exists(context: &Context) -> Result<String> {
|
||||
let self_addr = context
|
||||
.get_config(Config::ConfiguredAddr)
|
||||
.ok_or(format_err!(concat!(
|
||||
let self_addr = context.get_config(Config::ConfiguredAddr).ok_or_else(|| {
|
||||
format_err!(concat!(
|
||||
"Failed to get self address, ",
|
||||
"cannot ensure secret key if not configured."
|
||||
)))?;
|
||||
))
|
||||
})?;
|
||||
load_or_generate_self_public_key(context, &self_addr)?;
|
||||
Ok(self_addr)
|
||||
}
|
||||
|
||||
12
src/error.rs
12
src/error.rs
@@ -70,12 +70,6 @@ impl From<std::str::Utf8Error> for Error {
|
||||
}
|
||||
}
|
||||
|
||||
impl From<std::string::FromUtf8Error> for Error {
|
||||
fn from(err: std::string::FromUtf8Error) -> Error {
|
||||
Error::FromUtf8(err)
|
||||
}
|
||||
}
|
||||
|
||||
impl From<image_meta::ImageError> for Error {
|
||||
fn from(err: image_meta::ImageError) -> Error {
|
||||
Error::Image(err)
|
||||
@@ -94,6 +88,12 @@ impl From<pgp::errors::Error> for Error {
|
||||
}
|
||||
}
|
||||
|
||||
impl From<std::string::FromUtf8Error> for Error {
|
||||
fn from(err: std::string::FromUtf8Error) -> Error {
|
||||
Error::FromUtf8(err)
|
||||
}
|
||||
}
|
||||
|
||||
#[macro_export]
|
||||
macro_rules! bail {
|
||||
($e:expr) => {
|
||||
|
||||
@@ -2,8 +2,6 @@ use std::path::PathBuf;
|
||||
|
||||
use strum::EnumProperty;
|
||||
|
||||
use crate::stock::StockMessage;
|
||||
|
||||
impl Event {
|
||||
/// Returns the corresponding Event id.
|
||||
pub fn as_id(&self) -> i32 {
|
||||
@@ -42,6 +40,30 @@ pub enum Event {
|
||||
#[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 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),
|
||||
|
||||
/// The library-user should write a warning string to the log.
|
||||
/// Passed to the callback given to dc_context_new().
|
||||
///
|
||||
@@ -213,17 +235,4 @@ pub enum Event {
|
||||
/// @return 0
|
||||
#[strum(props(id = "2061"))]
|
||||
SecurejoinJoinerProgress { contact_id: u32, progress: usize },
|
||||
|
||||
// the following events are functions that should be provided by the frontends
|
||||
/// Requeste a localized string from the frontend.
|
||||
/// @param data1 (int) ID of the string to request, one of the DC_STR_/// constants.
|
||||
/// @param data2 (int) The count. If the requested string contains a placeholder for a numeric value,
|
||||
/// the ui may use this value to return different strings on different plural forms.
|
||||
/// @return (const char*) Null-terminated UTF-8 string.
|
||||
/// The string will be free()'d by the core,
|
||||
/// so it must be allocated using malloc() or a compatible function.
|
||||
/// Return 0 if the ui cannot provide the requested string
|
||||
/// the core will use a default string in english language then.
|
||||
#[strum(props(id = "2091"))]
|
||||
GetString { id: StockMessage, count: usize },
|
||||
}
|
||||
|
||||
623
src/imap.rs
623
src/imap.rs
@@ -8,12 +8,14 @@ use std::time::{Duration, SystemTime};
|
||||
use crate::constants::*;
|
||||
use crate::context::Context;
|
||||
use crate::dc_receive_imf::dc_receive_imf;
|
||||
use crate::error::Error;
|
||||
use crate::events::Event;
|
||||
use crate::job::{job_add, Action};
|
||||
use crate::login_param::LoginParam;
|
||||
use crate::job::{connect_to_inbox, job_add, Action};
|
||||
use crate::login_param::{dc_build_tls, CertificateChecks, LoginParam};
|
||||
use crate::message::{self, update_msg_move_state, update_server_uid};
|
||||
use crate::oauth2::dc_get_oauth2_access_token;
|
||||
use crate::param::Params;
|
||||
use crate::wrapmime;
|
||||
|
||||
const DC_IMAP_SEEN: usize = 0x0001;
|
||||
|
||||
@@ -27,7 +29,6 @@ pub enum ImapResult {
|
||||
|
||||
const PREFETCH_FLAGS: &str = "(UID ENVELOPE)";
|
||||
const BODY_FLAGS: &str = "(FLAGS BODY.PEEK[])";
|
||||
const FETCH_FLAGS: &str = "(FLAGS)";
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct Imap {
|
||||
@@ -107,14 +108,10 @@ impl Client {
|
||||
pub fn connect_secure<A: net::ToSocketAddrs, S: AsRef<str>>(
|
||||
addr: A,
|
||||
domain: S,
|
||||
certificate_checks: CertificateChecks,
|
||||
) -> imap::error::Result<Self> {
|
||||
let stream = net::TcpStream::connect(addr)?;
|
||||
let tls = native_tls::TlsConnector::builder()
|
||||
// see also: https://github.com/deltachat/deltachat-core-rust/issues/203
|
||||
.danger_accept_invalid_certs(true)
|
||||
.danger_accept_invalid_hostnames(true)
|
||||
.build()
|
||||
.unwrap();
|
||||
let tls = dc_build_tls(certificate_checks).unwrap();
|
||||
|
||||
let s = stream.try_clone().expect("cloning the stream failed");
|
||||
let tls_stream = native_tls::TlsConnector::connect(&tls, domain.as_ref(), s)?;
|
||||
@@ -134,13 +131,14 @@ impl Client {
|
||||
Ok(Client::Insecure(client, stream))
|
||||
}
|
||||
|
||||
pub fn secure<S: AsRef<str>>(self, domain: S) -> imap::error::Result<Client> {
|
||||
pub fn secure<S: AsRef<str>>(
|
||||
self,
|
||||
domain: S,
|
||||
certificate_checks: CertificateChecks,
|
||||
) -> imap::error::Result<Client> {
|
||||
match self {
|
||||
Client::Insecure(client, stream) => {
|
||||
let tls = native_tls::TlsConnector::builder()
|
||||
.danger_accept_invalid_hostnames(true)
|
||||
.build()
|
||||
.unwrap();
|
||||
let tls = dc_build_tls(certificate_checks).unwrap();
|
||||
|
||||
let client_sec = client.secure(domain, &tls)?;
|
||||
|
||||
@@ -320,6 +318,7 @@ struct ImapConfig {
|
||||
pub imap_port: u16,
|
||||
pub imap_user: String,
|
||||
pub imap_pw: String,
|
||||
pub certificate_checks: CertificateChecks,
|
||||
pub server_flags: usize,
|
||||
pub selected_folder: Option<String>,
|
||||
pub selected_mailbox: Option<imap::types::Mailbox>,
|
||||
@@ -332,12 +331,13 @@ struct ImapConfig {
|
||||
|
||||
impl Default for ImapConfig {
|
||||
fn default() -> Self {
|
||||
let cfg = ImapConfig {
|
||||
ImapConfig {
|
||||
addr: "".into(),
|
||||
imap_server: "".into(),
|
||||
imap_port: 0,
|
||||
imap_user: "".into(),
|
||||
imap_pw: "".into(),
|
||||
certificate_checks: Default::default(),
|
||||
server_flags: 0,
|
||||
selected_folder: None,
|
||||
selected_mailbox: None,
|
||||
@@ -346,9 +346,7 @@ impl Default for ImapConfig {
|
||||
has_xlist: false,
|
||||
imap_delimiter: '.',
|
||||
watch_folder: None,
|
||||
};
|
||||
|
||||
cfg
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -386,7 +384,7 @@ impl Imap {
|
||||
return true;
|
||||
}
|
||||
|
||||
let server_flags = self.config.read().unwrap().server_flags;
|
||||
let server_flags = self.config.read().unwrap().server_flags as i32;
|
||||
|
||||
let connection_res: imap::error::Result<Client> =
|
||||
if (server_flags & (DC_LP_IMAP_SOCKET_STARTTLS | DC_LP_IMAP_SOCKET_PLAIN)) != 0 {
|
||||
@@ -396,7 +394,7 @@ impl Imap {
|
||||
|
||||
Client::connect_insecure((imap_server, imap_port)).and_then(|client| {
|
||||
if (server_flags & DC_LP_IMAP_SOCKET_STARTTLS) != 0 {
|
||||
client.secure(imap_server)
|
||||
client.secure(imap_server, config.certificate_checks)
|
||||
} else {
|
||||
Ok(client)
|
||||
}
|
||||
@@ -406,7 +404,11 @@ impl Imap {
|
||||
let imap_server: &str = config.imap_server.as_ref();
|
||||
let imap_port = config.imap_port;
|
||||
|
||||
Client::connect_secure((imap_server, imap_port), imap_server)
|
||||
Client::connect_secure(
|
||||
(imap_server, imap_port),
|
||||
imap_server,
|
||||
config.certificate_checks,
|
||||
)
|
||||
};
|
||||
|
||||
let login_res = match connection_res {
|
||||
@@ -533,6 +535,7 @@ impl Imap {
|
||||
config.imap_port = imap_port;
|
||||
config.imap_user = imap_user.to_string();
|
||||
config.imap_pw = imap_pw.to_string();
|
||||
config.certificate_checks = lp.imap_certificate_checks;
|
||||
config.server_flags = server_flags;
|
||||
}
|
||||
|
||||
@@ -681,7 +684,7 @@ impl Imap {
|
||||
}
|
||||
}
|
||||
} else {
|
||||
return 0;
|
||||
unreachable!();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -690,12 +693,20 @@ impl Imap {
|
||||
|
||||
fn get_config_last_seen_uid<S: AsRef<str>>(&self, context: &Context, folder: S) -> (u32, u32) {
|
||||
let key = format!("imap.mailbox.{}", folder.as_ref());
|
||||
if let Some(entry) = context.sql.get_config(context, &key) {
|
||||
if let Some(entry) = context.sql.get_raw_config(context, &key) {
|
||||
// the entry has the format `imap.mailbox.<folder>=<uidvalidity>:<lastseenuid>`
|
||||
let mut parts = entry.split(':');
|
||||
(
|
||||
parts.next().unwrap().parse().unwrap_or_else(|_| 0),
|
||||
parts.next().unwrap().parse().unwrap_or_else(|_| 0),
|
||||
parts
|
||||
.next()
|
||||
.unwrap_or_default()
|
||||
.parse()
|
||||
.unwrap_or_else(|_| 0),
|
||||
parts
|
||||
.next()
|
||||
.unwrap_or_default()
|
||||
.parse()
|
||||
.unwrap_or_else(|_| 0),
|
||||
)
|
||||
} else {
|
||||
(0, 0)
|
||||
@@ -739,7 +750,7 @@ impl Imap {
|
||||
return 0;
|
||||
}
|
||||
|
||||
if mailbox.uid_validity.unwrap() != uid_validity {
|
||||
if mailbox.uid_validity.unwrap_or_default() != uid_validity {
|
||||
// first time this folder is selected or UIDVALIDITY has changed, init lastseenuid and save it to config
|
||||
|
||||
if mailbox.exists == 0 {
|
||||
@@ -749,7 +760,12 @@ impl Imap {
|
||||
// id we do not do this here, we'll miss the first message
|
||||
// as we will get in here again and fetch from lastseenuid+1 then
|
||||
|
||||
self.set_config_last_seen_uid(context, &folder, mailbox.uid_validity.unwrap(), 0);
|
||||
self.set_config_last_seen_uid(
|
||||
context,
|
||||
&folder,
|
||||
mailbox.uid_validity.unwrap_or_default(),
|
||||
0,
|
||||
);
|
||||
return 0;
|
||||
}
|
||||
|
||||
@@ -780,7 +796,7 @@ impl Imap {
|
||||
last_seen_uid -= 1;
|
||||
}
|
||||
|
||||
uid_validity = mailbox.uid_validity.unwrap();
|
||||
uid_validity = mailbox.uid_validity.unwrap_or_default();
|
||||
self.set_config_last_seen_uid(context, &folder, uid_validity, last_seen_uid);
|
||||
info!(
|
||||
context,
|
||||
@@ -816,11 +832,7 @@ impl Imap {
|
||||
if cur_uid > last_seen_uid {
|
||||
read_cnt += 1;
|
||||
|
||||
let message_id = msg
|
||||
.envelope()
|
||||
.expect("missing envelope")
|
||||
.message_id
|
||||
.expect("missing message id");
|
||||
let message_id = prefetch_get_message_id(msg).unwrap_or_default();
|
||||
|
||||
if !precheck_imf(context, &message_id, folder.as_ref(), cur_uid) {
|
||||
// check passed, go fetch the rest
|
||||
@@ -885,7 +897,7 @@ impl Imap {
|
||||
let key = format!("imap.mailbox.{}", folder.as_ref());
|
||||
let val = format!("{}:{}", uidvalidity, lastseenuid);
|
||||
|
||||
context.sql.set_config(context, &key, Some(&val)).ok();
|
||||
context.sql.set_raw_config(context, &key, Some(&val)).ok();
|
||||
}
|
||||
|
||||
fn fetch_single_msg<S: AsRef<str>>(
|
||||
@@ -933,27 +945,20 @@ impl Imap {
|
||||
} else {
|
||||
let msg = &msgs[0];
|
||||
|
||||
let is_deleted = msg
|
||||
.flags()
|
||||
.iter()
|
||||
.find(|flag| match flag {
|
||||
imap::types::Flag::Deleted => true,
|
||||
_ => false,
|
||||
})
|
||||
.is_some();
|
||||
let is_seen = msg
|
||||
.flags()
|
||||
.iter()
|
||||
.find(|flag| match flag {
|
||||
imap::types::Flag::Seen => true,
|
||||
_ => false,
|
||||
})
|
||||
.is_some();
|
||||
// XXX put flags into a set and pass them to dc_receive_imf
|
||||
let is_deleted = msg.flags().iter().any(|flag| match flag {
|
||||
imap::types::Flag::Deleted => true,
|
||||
_ => false,
|
||||
});
|
||||
let is_seen = msg.flags().iter().any(|flag| match flag {
|
||||
imap::types::Flag::Seen => true,
|
||||
_ => false,
|
||||
});
|
||||
|
||||
let flags = if is_seen { DC_IMAP_SEEN } else { 0 };
|
||||
|
||||
if !is_deleted && msg.body().is_some() {
|
||||
let body = msg.body().unwrap();
|
||||
let body = msg.body().unwrap_or_default();
|
||||
unsafe {
|
||||
dc_receive_imf(context, &body, folder.as_ref(), server_uid, flags as u32);
|
||||
}
|
||||
@@ -1064,13 +1069,14 @@ impl Imap {
|
||||
let mut do_fake_idle = true;
|
||||
while do_fake_idle {
|
||||
// wait a moment: every 5 seconds in the first 3 minutes after a new message, after that every 60 seconds.
|
||||
let seconds_to_wait =
|
||||
if fake_idle_start_time.elapsed().unwrap() < Duration::new(3 * 60, 0) && !wait_long
|
||||
{
|
||||
Duration::new(5, 0)
|
||||
} else {
|
||||
Duration::new(60, 0)
|
||||
};
|
||||
let seconds_to_wait = if fake_idle_start_time.elapsed().unwrap_or_default()
|
||||
< Duration::new(3 * 60, 0)
|
||||
&& !wait_long
|
||||
{
|
||||
Duration::new(5, 0)
|
||||
} else {
|
||||
Duration::new(60, 0)
|
||||
};
|
||||
|
||||
let &(ref lock, ref cvar) = &*self.watch.clone();
|
||||
let mut watch = lock.lock().unwrap();
|
||||
@@ -1119,117 +1125,94 @@ impl Imap {
|
||||
cvar.notify_one();
|
||||
}
|
||||
|
||||
pub fn mv<S1: AsRef<str>, S2: AsRef<str>>(
|
||||
pub fn mv(
|
||||
&self,
|
||||
context: &Context,
|
||||
folder: S1,
|
||||
folder: &str,
|
||||
uid: u32,
|
||||
dest_folder: S2,
|
||||
dest_folder: &str,
|
||||
dest_uid: &mut u32,
|
||||
) -> ImapResult {
|
||||
let mut res = ImapResult::RetryLater;
|
||||
if folder == dest_folder {
|
||||
info!(
|
||||
context,
|
||||
"Skip moving message; message {}/{} is already in {}...", folder, uid, dest_folder,
|
||||
);
|
||||
return ImapResult::AlreadyDone;
|
||||
}
|
||||
if let Some(imapresult) = self.prepare_imap_operation_on_msg(context, folder, uid) {
|
||||
return imapresult;
|
||||
}
|
||||
// we are connected, and the folder is selected
|
||||
|
||||
// XXX Rust-Imap provides no target uid on mv, so just set it to 0
|
||||
*dest_uid = 0;
|
||||
|
||||
let set = format!("{}", uid);
|
||||
|
||||
if uid == 0 {
|
||||
res = ImapResult::Failed;
|
||||
} else if folder.as_ref() == dest_folder.as_ref() {
|
||||
info!(
|
||||
context,
|
||||
"Skip moving message; message {}/{} is already in {}...",
|
||||
folder.as_ref(),
|
||||
uid,
|
||||
dest_folder.as_ref()
|
||||
);
|
||||
|
||||
res = ImapResult::AlreadyDone;
|
||||
} else {
|
||||
info!(
|
||||
context,
|
||||
"Moving message {}/{} to {}...",
|
||||
folder.as_ref(),
|
||||
uid,
|
||||
dest_folder.as_ref()
|
||||
);
|
||||
|
||||
if self.select_folder(context, Some(folder.as_ref())) == 0 {
|
||||
warn!(
|
||||
context,
|
||||
"Cannot select folder {} for moving message.",
|
||||
folder.as_ref()
|
||||
);
|
||||
} else {
|
||||
let moved = if let Some(ref mut session) = &mut *self.session.lock().unwrap() {
|
||||
match session.uid_mv(&set, &dest_folder) {
|
||||
Ok(_) => {
|
||||
res = ImapResult::Success;
|
||||
true
|
||||
}
|
||||
Err(err) => {
|
||||
info!(
|
||||
context,
|
||||
"Cannot move message, fallback to COPY/DELETE {}/{} to {}: {}",
|
||||
folder.as_ref(),
|
||||
uid,
|
||||
dest_folder.as_ref(),
|
||||
err
|
||||
);
|
||||
|
||||
false
|
||||
}
|
||||
}
|
||||
} else {
|
||||
unreachable!();
|
||||
};
|
||||
|
||||
if !moved {
|
||||
let copied = if let Some(ref mut session) = &mut *self.session.lock().unwrap() {
|
||||
match session.uid_copy(&set, &dest_folder) {
|
||||
Ok(_) => true,
|
||||
Err(err) => {
|
||||
eprintln!("error copy: {:?}", err);
|
||||
info!(context, "Cannot copy message.",);
|
||||
|
||||
false
|
||||
}
|
||||
}
|
||||
} else {
|
||||
unreachable!();
|
||||
};
|
||||
|
||||
if copied {
|
||||
if self.add_flag(context, uid, "\\Deleted") == 0 {
|
||||
warn!(context, "Cannot mark message as \"Deleted\".",);
|
||||
}
|
||||
self.config.write().unwrap().selected_folder_needs_expunge = true;
|
||||
res = ImapResult::Success;
|
||||
}
|
||||
let display_folder_id = format!("{}/{}", folder, uid);
|
||||
if let Some(ref mut session) = &mut *self.session.lock().unwrap() {
|
||||
match session.uid_mv(&set, &dest_folder) {
|
||||
Ok(_) => {
|
||||
emit_event!(
|
||||
context,
|
||||
Event::ImapMessageMoved(format!(
|
||||
"IMAP Message {} moved to {}",
|
||||
display_folder_id, dest_folder
|
||||
))
|
||||
);
|
||||
return ImapResult::Success;
|
||||
}
|
||||
Err(err) => {
|
||||
warn!(
|
||||
context,
|
||||
"Cannot move message, fallback to COPY/DELETE {}/{} to {}: {}",
|
||||
folder,
|
||||
uid,
|
||||
dest_folder,
|
||||
err
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
} else {
|
||||
unreachable!();
|
||||
};
|
||||
|
||||
if res == ImapResult::Success {
|
||||
// TODO: is this correct?
|
||||
*dest_uid = uid;
|
||||
}
|
||||
|
||||
if res == ImapResult::RetryLater {
|
||||
if self.should_reconnect() {
|
||||
ImapResult::RetryLater
|
||||
} else {
|
||||
ImapResult::Failed
|
||||
if let Some(ref mut session) = &mut *self.session.lock().unwrap() {
|
||||
match session.uid_copy(&set, &dest_folder) {
|
||||
Ok(_) => {
|
||||
if !self.add_flag_finalized(context, uid, "\\Deleted") {
|
||||
warn!(context, "Cannot mark {} as \"Deleted\" after copy.", uid);
|
||||
ImapResult::Failed
|
||||
} else {
|
||||
self.config.write().unwrap().selected_folder_needs_expunge = true;
|
||||
ImapResult::Success
|
||||
}
|
||||
}
|
||||
Err(err) => {
|
||||
warn!(context, "Could not copy message: {}", err);
|
||||
ImapResult::Failed
|
||||
}
|
||||
}
|
||||
} else {
|
||||
res
|
||||
unreachable!();
|
||||
}
|
||||
}
|
||||
|
||||
fn add_flag<S: AsRef<str>>(&self, context: &Context, server_uid: u32, flag: S) -> usize {
|
||||
fn add_flag_finalized(&self, context: &Context, server_uid: u32, flag: &str) -> bool {
|
||||
// return true if we successfully set the flag or we otherwise
|
||||
// think add_flag should not be retried: Disconnection during setting
|
||||
// the flag, or other imap-errors, returns true as well.
|
||||
//
|
||||
// returning false means that the operation can be retried.
|
||||
if server_uid == 0 {
|
||||
return 0;
|
||||
return true; // might be moved but we don't want to have a stuck job
|
||||
}
|
||||
if self.should_reconnect() {
|
||||
return false;
|
||||
}
|
||||
if let Some(ref mut session) = &mut *self.session.lock().unwrap() {
|
||||
let set = format!("{}", server_uid);
|
||||
let query = format!("+FLAGS ({})", flag.as_ref());
|
||||
let query = format!("+FLAGS ({})", flag);
|
||||
match session.uid_store(&set, &query) {
|
||||
Ok(_) => {}
|
||||
Err(err) => {
|
||||
@@ -1239,243 +1222,126 @@ impl Imap {
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// All non-connection states are treated as success - the mail may
|
||||
// already be deleted or moved away on the server.
|
||||
if self.should_reconnect() {
|
||||
0
|
||||
true // we tried once, that's probably enough for setting flag
|
||||
} else {
|
||||
1
|
||||
unreachable!();
|
||||
}
|
||||
}
|
||||
|
||||
pub fn set_seen<S: AsRef<str>>(&self, context: &Context, folder: S, uid: u32) -> ImapResult {
|
||||
let mut res = ImapResult::RetryLater;
|
||||
|
||||
pub fn prepare_imap_operation_on_msg(
|
||||
&self,
|
||||
context: &Context,
|
||||
folder: &str,
|
||||
uid: u32,
|
||||
) -> Option<ImapResult> {
|
||||
if uid == 0 {
|
||||
res = ImapResult::Failed
|
||||
} else if self.is_connected() {
|
||||
info!(
|
||||
context,
|
||||
"Marking message {}/{} as seen...",
|
||||
folder.as_ref(),
|
||||
uid,
|
||||
);
|
||||
|
||||
if self.select_folder(context, Some(folder.as_ref())) == 0 {
|
||||
warn!(
|
||||
context,
|
||||
"Cannot select folder {} for setting SEEN flag.",
|
||||
folder.as_ref(),
|
||||
);
|
||||
} else if self.add_flag(context, uid, "\\Seen") == 0 {
|
||||
warn!(context, "Cannot mark message as seen.",);
|
||||
} else {
|
||||
res = ImapResult::Success
|
||||
return Some(ImapResult::Failed);
|
||||
} else if !self.is_connected() {
|
||||
connect_to_inbox(context, &self);
|
||||
if !self.is_connected() {
|
||||
return Some(ImapResult::RetryLater);
|
||||
}
|
||||
}
|
||||
|
||||
if res == ImapResult::RetryLater {
|
||||
if self.should_reconnect() {
|
||||
ImapResult::RetryLater
|
||||
} else {
|
||||
ImapResult::Failed
|
||||
}
|
||||
if self.select_folder(context, Some(&folder)) == 0 {
|
||||
warn!(
|
||||
context,
|
||||
"Cannot select folder {} for preparing IMAP operation", folder
|
||||
);
|
||||
Some(ImapResult::RetryLater)
|
||||
} else {
|
||||
res
|
||||
None
|
||||
}
|
||||
}
|
||||
|
||||
pub fn set_mdnsent<S: AsRef<str>>(&self, context: &Context, folder: S, uid: u32) -> ImapResult {
|
||||
// returns 0=job should be retried later, 1=job done, 2=job done and flag just set
|
||||
let mut res = ImapResult::RetryLater;
|
||||
let set = format!("{}", uid);
|
||||
|
||||
if uid == 0 {
|
||||
res = ImapResult::Failed;
|
||||
} else if self.is_connected() {
|
||||
info!(
|
||||
context,
|
||||
"Marking message {}/{} as $MDNSent...",
|
||||
folder.as_ref(),
|
||||
uid,
|
||||
);
|
||||
|
||||
if self.select_folder(context, Some(folder.as_ref())) == 0 {
|
||||
warn!(
|
||||
context,
|
||||
"Cannot select folder {} for setting $MDNSent flag.",
|
||||
folder.as_ref()
|
||||
);
|
||||
} else {
|
||||
// Check if the folder can handle the `$MDNSent` flag (see RFC 3503). If so, and not
|
||||
// set: set the flags and return this information.
|
||||
// If the folder cannot handle the `$MDNSent` flag, we risk duplicated MDNs; it's up
|
||||
// to the receiving MUA to handle this then (eg. Delta Chat has no problem with this).
|
||||
|
||||
let can_create_flag = self
|
||||
.config
|
||||
.read()
|
||||
.unwrap()
|
||||
.selected_mailbox
|
||||
.as_ref()
|
||||
.map(|mbox| {
|
||||
// empty means, everything can be stored
|
||||
mbox.permanent_flags.is_empty()
|
||||
|| mbox
|
||||
.permanent_flags
|
||||
.iter()
|
||||
.find(|flag| match flag {
|
||||
imap::types::Flag::Custom(s) => s == "$MDNSent",
|
||||
_ => false,
|
||||
})
|
||||
.is_some()
|
||||
})
|
||||
.expect("just selected folder");
|
||||
|
||||
if can_create_flag {
|
||||
let fetched_msgs =
|
||||
if let Some(ref mut session) = &mut *self.session.lock().unwrap() {
|
||||
match session.uid_fetch(set, FETCH_FLAGS) {
|
||||
Ok(res) => Some(res),
|
||||
Err(err) => {
|
||||
eprintln!("fetch error: {:?}", err);
|
||||
None
|
||||
}
|
||||
}
|
||||
} else {
|
||||
unreachable!();
|
||||
};
|
||||
|
||||
if let Some(msgs) = fetched_msgs {
|
||||
let flag_set = msgs
|
||||
.first()
|
||||
.map(|msg| {
|
||||
msg.flags()
|
||||
.iter()
|
||||
.find(|flag| match flag {
|
||||
imap::types::Flag::Custom(s) => s == "$MDNSent",
|
||||
_ => false,
|
||||
})
|
||||
.is_some()
|
||||
})
|
||||
.unwrap_or_else(|| false);
|
||||
|
||||
res = if flag_set {
|
||||
ImapResult::AlreadyDone
|
||||
} else if self.add_flag(context, uid, "$MDNSent") != 0 {
|
||||
ImapResult::Success
|
||||
} else {
|
||||
res
|
||||
};
|
||||
|
||||
if res == ImapResult::Success {
|
||||
info!(context, "$MDNSent just set and MDN will be sent.");
|
||||
} else {
|
||||
info!(context, "$MDNSent already set and MDN already sent.");
|
||||
}
|
||||
}
|
||||
} else {
|
||||
res = ImapResult::Success;
|
||||
info!(
|
||||
context,
|
||||
"Cannot store $MDNSent flags, risk sending duplicate MDN.",
|
||||
);
|
||||
}
|
||||
}
|
||||
pub fn set_seen(&self, context: &Context, folder: &str, uid: u32) -> ImapResult {
|
||||
if let Some(imapresult) = self.prepare_imap_operation_on_msg(context, folder, uid) {
|
||||
return imapresult;
|
||||
}
|
||||
// we are connected, and the folder is selected
|
||||
info!(context, "Marking message {}/{} as seen...", folder, uid,);
|
||||
|
||||
if res == ImapResult::RetryLater {
|
||||
if self.should_reconnect() {
|
||||
ImapResult::RetryLater
|
||||
} else {
|
||||
ImapResult::Failed
|
||||
}
|
||||
if self.add_flag_finalized(context, uid, "\\Seen") {
|
||||
ImapResult::Success
|
||||
} else {
|
||||
res
|
||||
warn!(
|
||||
context,
|
||||
"Cannot mark message {} in folder {} as seen, ignoring.", uid, folder
|
||||
);
|
||||
ImapResult::Failed
|
||||
}
|
||||
}
|
||||
|
||||
// only returns 0 on connection problems; we should try later again in this case *
|
||||
pub fn delete_msg<S1: AsRef<str>, S2: AsRef<str>>(
|
||||
pub fn delete_msg(
|
||||
&self,
|
||||
context: &Context,
|
||||
message_id: S1,
|
||||
folder: S2,
|
||||
server_uid: &mut u32,
|
||||
) -> usize {
|
||||
let mut success = false;
|
||||
if *server_uid == 0 {
|
||||
success = true
|
||||
} else {
|
||||
info!(
|
||||
context,
|
||||
"Marking message \"{}\", {}/{} for deletion...",
|
||||
message_id.as_ref(),
|
||||
folder.as_ref(),
|
||||
server_uid,
|
||||
);
|
||||
message_id: &str,
|
||||
folder: &str,
|
||||
uid: &mut u32,
|
||||
) -> ImapResult {
|
||||
if let Some(imapresult) = self.prepare_imap_operation_on_msg(context, folder, *uid) {
|
||||
return imapresult;
|
||||
}
|
||||
// we are connected, and the folder is selected
|
||||
|
||||
if self.select_folder(context, Some(&folder)) == 0 {
|
||||
warn!(
|
||||
context,
|
||||
"Cannot select folder {} for deleting message.",
|
||||
folder.as_ref()
|
||||
);
|
||||
} else {
|
||||
let set = format!("{}", server_uid);
|
||||
if let Some(ref mut session) = &mut *self.session.lock().unwrap() {
|
||||
match session.uid_fetch(set, PREFETCH_FLAGS) {
|
||||
Ok(msgs) => {
|
||||
if msgs.is_empty()
|
||||
|| msgs
|
||||
.first()
|
||||
.unwrap()
|
||||
.envelope()
|
||||
.expect("missing envelope")
|
||||
.message_id
|
||||
.expect("missing message id")
|
||||
!= message_id.as_ref()
|
||||
{
|
||||
warn!(
|
||||
context,
|
||||
"Cannot delete on IMAP, {}/{} does not match {}.",
|
||||
folder.as_ref(),
|
||||
server_uid,
|
||||
message_id.as_ref(),
|
||||
);
|
||||
*server_uid = 0;
|
||||
}
|
||||
}
|
||||
Err(err) => {
|
||||
eprintln!("fetch error: {:?}", err);
|
||||
let set = format!("{}", uid);
|
||||
let display_imap_id = format!("{}/{}", folder, uid);
|
||||
|
||||
warn!(
|
||||
context,
|
||||
"Cannot delete on IMAP, {}/{} not found.",
|
||||
folder.as_ref(),
|
||||
server_uid,
|
||||
);
|
||||
*server_uid = 0;
|
||||
}
|
||||
// double-check that we are deleting the correct message-id
|
||||
// this comes at the expense of another imap query
|
||||
if let Some(ref mut session) = &mut *self.session.lock().unwrap() {
|
||||
match session.uid_fetch(set, PREFETCH_FLAGS) {
|
||||
Ok(msgs) => {
|
||||
if msgs.is_empty() {
|
||||
warn!(
|
||||
context,
|
||||
"Cannot delete on IMAP, {}: imap entry gone '{}'",
|
||||
display_imap_id,
|
||||
message_id,
|
||||
);
|
||||
return ImapResult::Failed;
|
||||
}
|
||||
}
|
||||
let remote_message_id =
|
||||
prefetch_get_message_id(msgs.first().unwrap()).unwrap_or_default();
|
||||
|
||||
// mark the message for deletion
|
||||
if self.add_flag(context, *server_uid, "\\Deleted") == 0 {
|
||||
warn!(context, "Cannot mark message as \"Deleted\".");
|
||||
} else {
|
||||
self.config.write().unwrap().selected_folder_needs_expunge = true;
|
||||
success = true
|
||||
if remote_message_id != message_id {
|
||||
warn!(
|
||||
context,
|
||||
"Cannot delete on IMAP, {}: remote message-id '{}' != '{}'",
|
||||
display_imap_id,
|
||||
remote_message_id,
|
||||
message_id,
|
||||
);
|
||||
}
|
||||
*uid = 0;
|
||||
}
|
||||
Err(err) => {
|
||||
warn!(
|
||||
context,
|
||||
"Cannot delete {} on IMAP: {}", display_imap_id, err
|
||||
);
|
||||
*uid = 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if success {
|
||||
1
|
||||
// mark the message for deletion
|
||||
if !self.add_flag_finalized(context, *uid, "\\Deleted") {
|
||||
warn!(
|
||||
context,
|
||||
"Cannot mark message {} as \"Deleted\".", display_imap_id
|
||||
);
|
||||
ImapResult::Failed
|
||||
} else {
|
||||
self.is_connected() as usize
|
||||
emit_event!(
|
||||
context,
|
||||
Event::ImapMessageDeleted(format!(
|
||||
"IMAP Message {} marked as deleted [{}]",
|
||||
display_imap_id, message_id
|
||||
))
|
||||
);
|
||||
self.config.write().unwrap().selected_folder_needs_expunge = true;
|
||||
ImapResult::Success
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1544,18 +1410,18 @@ impl Imap {
|
||||
|
||||
context
|
||||
.sql
|
||||
.set_config_int(context, "folders_configured", 3)
|
||||
.set_raw_config_int(context, "folders_configured", 3)
|
||||
.ok();
|
||||
if let Some(ref mvbox_folder) = mvbox_folder {
|
||||
context
|
||||
.sql
|
||||
.set_config(context, "configured_mvbox_folder", Some(mvbox_folder))
|
||||
.set_raw_config(context, "configured_mvbox_folder", Some(mvbox_folder))
|
||||
.ok();
|
||||
}
|
||||
if let Some(ref sentbox_folder) = sentbox_folder {
|
||||
context
|
||||
.sql
|
||||
.set_config(
|
||||
.set_raw_config(
|
||||
context,
|
||||
"configured_sentbox_folder",
|
||||
Some(sentbox_folder.name()),
|
||||
@@ -1635,27 +1501,11 @@ fn get_folder_meaning(folder_name: &imap::types::Name) -> FolderMeaning {
|
||||
}
|
||||
|
||||
fn precheck_imf(context: &Context, rfc724_mid: &str, server_folder: &str, server_uid: u32) -> bool {
|
||||
let mut rfc724_mid_exists = false;
|
||||
let mut mark_seen = false;
|
||||
|
||||
if let Ok((old_server_folder, old_server_uid, msg_id)) =
|
||||
message::rfc724_mid_exists(context, &rfc724_mid)
|
||||
{
|
||||
rfc724_mid_exists = true;
|
||||
|
||||
if old_server_folder.is_empty() && old_server_uid == 0 {
|
||||
info!(context, "[move] detected bbc-self {}", rfc724_mid,);
|
||||
mark_seen = true;
|
||||
} else if old_server_folder != server_folder {
|
||||
info!(context, "[move] detected moved message {}", rfc724_mid,);
|
||||
update_msg_move_state(context, &rfc724_mid, MoveState::Stay);
|
||||
}
|
||||
if old_server_folder != server_folder || old_server_uid != server_uid {
|
||||
update_server_uid(context, &rfc724_mid, server_folder, server_uid);
|
||||
}
|
||||
context.do_heuristics_moves(server_folder, msg_id);
|
||||
|
||||
if mark_seen {
|
||||
job_add(
|
||||
context,
|
||||
Action::MarkseenMsgOnImap,
|
||||
@@ -1663,8 +1513,21 @@ fn precheck_imf(context: &Context, rfc724_mid: &str, server_folder: &str, server
|
||||
Params::new(),
|
||||
0,
|
||||
);
|
||||
} else if old_server_folder != server_folder {
|
||||
info!(context, "[move] detected moved message {}", rfc724_mid,);
|
||||
update_msg_move_state(context, &rfc724_mid, MoveState::Stay);
|
||||
}
|
||||
}
|
||||
|
||||
rfc724_mid_exists
|
||||
if old_server_folder != server_folder || old_server_uid != server_uid {
|
||||
update_server_uid(context, &rfc724_mid, server_folder, server_uid);
|
||||
}
|
||||
true
|
||||
} else {
|
||||
false
|
||||
}
|
||||
}
|
||||
|
||||
fn prefetch_get_message_id(prefetch_msg: &imap::types::Fetch) -> Result<String, Error> {
|
||||
let message_id = prefetch_msg.envelope().unwrap().message_id.unwrap();
|
||||
wrapmime::parse_message_id(&message_id)
|
||||
}
|
||||
|
||||
588
src/imex.rs
588
src/imex.rs
@@ -1,6 +1,5 @@
|
||||
use std::ffi::CString;
|
||||
use std::path::Path;
|
||||
use std::ptr;
|
||||
use core::cmp::{max, min};
|
||||
use std::path::{Path, PathBuf};
|
||||
|
||||
use num_traits::FromPrimitive;
|
||||
use rand::{thread_rng, Rng};
|
||||
@@ -18,7 +17,7 @@ use crate::job::*;
|
||||
use crate::key::*;
|
||||
use crate::message::Message;
|
||||
use crate::param::*;
|
||||
use crate::pgp::*;
|
||||
use crate::pgp;
|
||||
use crate::sql::{self, Sql};
|
||||
use crate::stock::StockMessage;
|
||||
|
||||
@@ -90,7 +89,7 @@ pub fn has_backup(context: &Context, dir_name: impl AsRef<Path>) -> Result<Strin
|
||||
let sql = Sql::new();
|
||||
if sql.open(context, &path, 0x1) {
|
||||
let curr_backup_time =
|
||||
sql.get_config_int(context, "backup_time")
|
||||
sql.get_raw_config_int(context, "backup_time")
|
||||
.unwrap_or_default() as u64;
|
||||
if curr_backup_time > newest_backup_time {
|
||||
newest_backup_path = Some(path);
|
||||
@@ -109,77 +108,48 @@ pub fn has_backup(context: &Context, dir_name: impl AsRef<Path>) -> Result<Strin
|
||||
}
|
||||
|
||||
pub fn initiate_key_transfer(context: &Context) -> Result<String> {
|
||||
ensure!(context.alloc_ongoing(), "could not allocate ongoing");
|
||||
let res = do_initiate_key_transfer(context);
|
||||
context.free_ongoing();
|
||||
res
|
||||
}
|
||||
|
||||
fn do_initiate_key_transfer(context: &Context) -> Result<String> {
|
||||
let mut msg: Message;
|
||||
ensure!(dc_alloc_ongoing(context), "could not allocate ongoing");
|
||||
let setup_code = create_setup_code(context);
|
||||
/* this may require a keypair to be created. this may take a second ... */
|
||||
if !context
|
||||
.running_state
|
||||
.clone()
|
||||
.read()
|
||||
.unwrap()
|
||||
.shall_stop_ongoing
|
||||
{
|
||||
if let Ok(ref setup_file_content) = render_setup_file(context, &setup_code) {
|
||||
/* encrypting may also take a while ... */
|
||||
if !context
|
||||
.running_state
|
||||
.clone()
|
||||
.read()
|
||||
.unwrap()
|
||||
.shall_stop_ongoing
|
||||
{
|
||||
let setup_file_name =
|
||||
dc_get_fine_path_filename(context, "$BLOBDIR", "autocrypt-setup-message.html");
|
||||
if dc_write_file(context, &setup_file_name, setup_file_content.as_bytes()) {
|
||||
if let Ok(chat_id) = chat::create_by_contact_id(context, 1) {
|
||||
msg = Message::default();
|
||||
msg.type_0 = Viewtype::File;
|
||||
msg.param
|
||||
.set(Param::File, setup_file_name.to_string_lossy());
|
||||
ensure!(!context.shall_stop_ongoing(), "canceled");
|
||||
let setup_file_content = render_setup_file(context, &setup_code)?;
|
||||
/* encrypting may also take a while ... */
|
||||
ensure!(!context.shall_stop_ongoing(), "canceled");
|
||||
let setup_file_name = context.new_blob_file(
|
||||
"autocrypt-setup-message.html",
|
||||
setup_file_content.as_bytes(),
|
||||
)?;
|
||||
|
||||
msg.param
|
||||
.set(Param::MimeType, "application/autocrypt-setup");
|
||||
msg.param.set_int(Param::Cmd, 6);
|
||||
msg.param
|
||||
.set_int(Param::ForcePlaintext, DC_FP_NO_AUTOCRYPT_HEADER);
|
||||
let chat_id = chat::create_by_contact_id(context, 1)?;
|
||||
msg = Message::default();
|
||||
msg.type_0 = Viewtype::File;
|
||||
msg.param.set(Param::File, setup_file_name);
|
||||
|
||||
if !context
|
||||
.running_state
|
||||
.clone()
|
||||
.read()
|
||||
.unwrap()
|
||||
.shall_stop_ongoing
|
||||
{
|
||||
if let Ok(msg_id) = chat::send_msg(context, chat_id, &mut msg) {
|
||||
info!(context, "Wait for setup message being sent ...",);
|
||||
loop {
|
||||
if context
|
||||
.running_state
|
||||
.clone()
|
||||
.read()
|
||||
.unwrap()
|
||||
.shall_stop_ongoing
|
||||
{
|
||||
break;
|
||||
}
|
||||
std::thread::sleep(std::time::Duration::from_secs(1));
|
||||
if let Ok(msg) = Message::load_from_db(context, msg_id) {
|
||||
if msg.is_sent() {
|
||||
info!(context, "... setup message sent.",);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
msg.param
|
||||
.set(Param::MimeType, "application/autocrypt-setup");
|
||||
msg.param.set_int(Param::Cmd, 6);
|
||||
msg.param
|
||||
.set_int(Param::ForcePlaintext, DC_FP_NO_AUTOCRYPT_HEADER);
|
||||
|
||||
ensure!(!context.shall_stop_ongoing(), "canceled");
|
||||
let msg_id = chat::send_msg(context, chat_id, &mut msg)?;
|
||||
info!(context, "Wait for setup message being sent ...",);
|
||||
while !context.shall_stop_ongoing() {
|
||||
std::thread::sleep(std::time::Duration::from_secs(1));
|
||||
if let Ok(msg) = Message::load_from_db(context, msg_id) {
|
||||
if msg.is_sent() {
|
||||
info!(context, "... setup message sent.",);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
dc_free_ongoing(context);
|
||||
|
||||
Ok(setup_code)
|
||||
}
|
||||
|
||||
@@ -194,16 +164,12 @@ pub fn render_setup_file(context: &Context, passphrase: &str) -> Result<String>
|
||||
let self_addr = e2ee::ensure_secret_key_exists(context)?;
|
||||
let private_key = Key::from_self_private(context, self_addr, &context.sql)
|
||||
.ok_or(format_err!("Failed to get private key."))?;
|
||||
let ac_headers = match context
|
||||
.sql
|
||||
.get_config_int(context, Config::E2eeEnabled)
|
||||
.unwrap_or(1)
|
||||
{
|
||||
0 => None,
|
||||
_ => Some(("Autocrypt-Prefer-Encrypt", "mutual")),
|
||||
let ac_headers = match context.get_config_bool(Config::E2eeEnabled) {
|
||||
false => None,
|
||||
true => Some(("Autocrypt-Prefer-Encrypt", "mutual")),
|
||||
};
|
||||
let private_key_asc = private_key.to_asc(ac_headers);
|
||||
let encr = dc_pgp_symm_encrypt(&passphrase, private_key_asc.as_bytes())?;
|
||||
let encr = pgp::symm_encrypt(&passphrase, private_key_asc.as_bytes())?;
|
||||
|
||||
let replacement = format!(
|
||||
concat!(
|
||||
@@ -266,25 +232,19 @@ pub fn continue_key_transfer(context: &Context, msg_id: u32, setup_code: &str) -
|
||||
if msg.is_err() {
|
||||
bail!("Message is no Autocrypt Setup Message.");
|
||||
}
|
||||
let msg = msg.unwrap();
|
||||
let msg = msg.unwrap_or_default();
|
||||
ensure!(
|
||||
msg.is_setupmessage(),
|
||||
"Message is no Autocrypt Setup Message."
|
||||
);
|
||||
|
||||
if let Some(filename) = msg.get_file(context) {
|
||||
if let Ok(ref mut buf) = dc_read_file(context, filename) {
|
||||
let sc = normalize_setup_code(setup_code);
|
||||
if let Ok(armored_key) = decrypt_setup_file(context, sc, buf) {
|
||||
set_self_key(context, &armored_key, true, true)?;
|
||||
} else {
|
||||
bail!("Bad setup code.")
|
||||
}
|
||||
let file = dc_open_file(context, filename)?;
|
||||
let sc = normalize_setup_code(setup_code);
|
||||
let armored_key = decrypt_setup_file(context, &sc, file)?;
|
||||
set_self_key(context, &armored_key, true, true)?;
|
||||
|
||||
Ok(())
|
||||
} else {
|
||||
bail!("Cannot read Autocrypt Setup Message file.");
|
||||
}
|
||||
Ok(())
|
||||
} else {
|
||||
bail!("Message is no Autocrypt Setup Message.");
|
||||
}
|
||||
@@ -316,7 +276,7 @@ fn set_self_key(
|
||||
};
|
||||
context
|
||||
.sql
|
||||
.set_config_int(context, "e2ee_enabled", e2ee_enabled)?;
|
||||
.set_raw_config_int(context, "e2ee_enabled", e2ee_enabled)?;
|
||||
}
|
||||
None => {
|
||||
if prefer_encrypt_required {
|
||||
@@ -349,7 +309,7 @@ fn set_self_key(
|
||||
context,
|
||||
&public_key,
|
||||
&private_key,
|
||||
self_addr.unwrap(),
|
||||
self_addr.unwrap_or_default(),
|
||||
set_default,
|
||||
&context.sql,
|
||||
) {
|
||||
@@ -358,53 +318,15 @@ fn set_self_key(
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn decrypt_setup_file(
|
||||
fn decrypt_setup_file<T: std::io::Read + std::io::Seek>(
|
||||
_context: &Context,
|
||||
passphrase: impl AsRef<str>,
|
||||
filecontent: &mut [u8],
|
||||
passphrase: &str,
|
||||
file: T,
|
||||
) -> Result<String> {
|
||||
let mut fc_headerline = String::default();
|
||||
let mut fc_base64: *const libc::c_char = ptr::null();
|
||||
let plain_bytes = pgp::symm_decrypt(passphrase, file)?;
|
||||
let plain_text = std::string::String::from_utf8(plain_bytes)?;
|
||||
|
||||
let split_result = unsafe {
|
||||
dc_split_armored_data(
|
||||
filecontent.as_mut_ptr().cast(),
|
||||
&mut fc_headerline,
|
||||
ptr::null_mut(),
|
||||
ptr::null_mut(),
|
||||
&mut fc_base64,
|
||||
)
|
||||
};
|
||||
|
||||
if !split_result || fc_headerline != "-----BEGIN PGP MESSAGE-----" || fc_base64.is_null() {
|
||||
bail!("Invalid armored data");
|
||||
}
|
||||
|
||||
// convert base64 to binary
|
||||
let base64_encoded =
|
||||
unsafe { std::slice::from_raw_parts(fc_base64 as *const u8, libc::strlen(fc_base64)) };
|
||||
|
||||
let data = base64_decode(&base64_encoded)?;
|
||||
|
||||
// decrypt symmetrically
|
||||
let payload = dc_pgp_symm_decrypt(passphrase.as_ref(), &data)?;
|
||||
let payload_str = String::from_utf8(payload)?;
|
||||
|
||||
Ok(payload_str)
|
||||
}
|
||||
|
||||
/// Decode the base64 encoded slice. Handles line breaks.
|
||||
fn base64_decode(input: &[u8]) -> Result<Vec<u8>> {
|
||||
use std::io::Read;
|
||||
let c = std::io::Cursor::new(input);
|
||||
let lr = pgp::line_reader::LineReader::new(c);
|
||||
let br = pgp::base64_reader::Base64Reader::new(lr);
|
||||
let mut reader = pgp::base64_decoder::Base64Decoder::new(br);
|
||||
|
||||
let mut data = Vec::new();
|
||||
reader.read_to_end(&mut data)?;
|
||||
|
||||
Ok(data)
|
||||
Ok(plain_text)
|
||||
}
|
||||
|
||||
pub fn normalize_setup_code(s: &str) -> String {
|
||||
@@ -422,7 +344,7 @@ pub fn normalize_setup_code(s: &str) -> String {
|
||||
|
||||
#[allow(non_snake_case)]
|
||||
pub fn job_do_DC_JOB_IMEX_IMAP(context: &Context, job: &Job) -> Result<()> {
|
||||
ensure!(dc_alloc_ongoing(context), "could not allocate ongoing");
|
||||
ensure!(context.alloc_ongoing(), "could not allocate ongoing");
|
||||
let what: Option<ImexMode> = job.param.get_int(Param::Cmd).and_then(ImexMode::from_i32);
|
||||
let param = job.param.get(Param::Arg).unwrap_or_default();
|
||||
|
||||
@@ -434,7 +356,7 @@ pub fn job_do_DC_JOB_IMEX_IMAP(context: &Context, job: &Job) -> Result<()> {
|
||||
if what == Some(ImexMode::ExportBackup) || what == Some(ImexMode::ExportSelfKeys) {
|
||||
// before we export anything, make sure the private key exists
|
||||
if e2ee::ensure_secret_key_exists(context).is_err() {
|
||||
dc_free_ongoing(context);
|
||||
context.free_ongoing();
|
||||
bail!("Cannot create private key or private key not available.");
|
||||
} else {
|
||||
dc_create_folder(context, ¶m);
|
||||
@@ -450,7 +372,7 @@ pub fn job_do_DC_JOB_IMEX_IMAP(context: &Context, job: &Job) -> Result<()> {
|
||||
bail!("unknown IMEX type");
|
||||
}
|
||||
};
|
||||
dc_free_ongoing(context);
|
||||
context.free_ongoing();
|
||||
match success {
|
||||
Ok(()) => {
|
||||
info!(context, "IMEX successfully completed");
|
||||
@@ -477,7 +399,7 @@ fn import_backup(context: &Context, backup_to_import: impl AsRef<Path>) -> Resul
|
||||
!dc_is_configured(context),
|
||||
"Cannot import backups to accounts in use."
|
||||
);
|
||||
&context.sql.close(&context);
|
||||
context.sql.close(&context);
|
||||
dc_delete_file(context, context.get_dbfile());
|
||||
ensure!(
|
||||
!dc_file_exist(context, context.get_dbfile()),
|
||||
@@ -514,20 +436,9 @@ fn import_backup(context: &Context, backup_to_import: impl AsRef<Path>) -> Resul
|
||||
Ok((name, blob))
|
||||
},
|
||||
|files| {
|
||||
let mut processed_files_cnt = 0;
|
||||
|
||||
for file in files {
|
||||
for (processed_files_cnt, file) in files.enumerate() {
|
||||
let (file_name, file_blob) = file?;
|
||||
if context
|
||||
.running_state
|
||||
.clone()
|
||||
.read()
|
||||
.unwrap()
|
||||
.shall_stop_ongoing
|
||||
{
|
||||
bail!("received stop signal");
|
||||
}
|
||||
processed_files_cnt += 1;
|
||||
ensure!(!context.shall_stop_ongoing(), "received stop signal");
|
||||
let mut permille = processed_files_cnt * 1000 / total_files_cnt;
|
||||
if permille < 10 {
|
||||
permille = 10
|
||||
@@ -568,18 +479,11 @@ fn import_backup(context: &Context, backup_to_import: impl AsRef<Path>) -> Resul
|
||||
/* the FILE_PROGRESS macro calls the callback with the permille of files processed.
|
||||
The macro avoids weird values of 0% or 100% while still working. */
|
||||
fn export_backup(context: &Context, dir: impl AsRef<Path>) -> Result<()> {
|
||||
let mut ok_to_continue = true;
|
||||
let mut success = false;
|
||||
|
||||
let mut delete_dest_file: libc::c_int = 0;
|
||||
// get a fine backup file name (the name includes the date so that multiple backup instances are possible)
|
||||
// FIXME: we should write to a temporary file first and rename it on success. this would guarantee the backup is complete. however, currently it is not clear it the import exists in the long run (may be replaced by a restore-from-imap)
|
||||
// FIXME: we should write to a temporary file first and rename it on success. this would guarantee the backup is complete.
|
||||
// let dest_path_filename = dc_get_next_backup_file(context, dir, res);
|
||||
let now = time();
|
||||
let res = chrono::NaiveDateTime::from_timestamp(now as i64, 0)
|
||||
.format("delta-chat-%Y-%m-%d.bak")
|
||||
.to_string();
|
||||
|
||||
let dest_path_filename = dc_get_fine_path_filename(context, dir, res);
|
||||
let dest_path_filename = dc_get_next_backup_path(dir, now)?;
|
||||
|
||||
sql::housekeeping(context);
|
||||
|
||||
@@ -601,130 +505,79 @@ fn export_backup(context: &Context, dir: impl AsRef<Path>) -> Result<()> {
|
||||
s
|
||||
);
|
||||
}
|
||||
/* add all files as blobs to the database copy (this does not require the source to be locked, neigher the destination as it is used only here) */
|
||||
/*for logging only*/
|
||||
match add_files_to_export(context, &dest_path_filename) {
|
||||
Err(err) => {
|
||||
dc_delete_file(context, &dest_path_filename);
|
||||
error!(context, "backup failed: {}", err);
|
||||
Err(err)
|
||||
}
|
||||
Ok(()) => {
|
||||
context
|
||||
.sql
|
||||
.set_raw_config_int(context, "backup_time", now as i32)?;
|
||||
context.call_cb(Event::ImexFileWritten(dest_path_filename.clone()));
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn add_files_to_export(context: &Context, dest_path_filename: &PathBuf) -> Result<()> {
|
||||
// add all files as blobs to the database copy (this does not require
|
||||
// the source to be locked, neigher the destination as it is used only here)
|
||||
let sql = Sql::new();
|
||||
if sql.open(context, &dest_path_filename, 0) {
|
||||
if !sql.table_exists("backup_blobs") {
|
||||
if sql::execute(
|
||||
context,
|
||||
&sql,
|
||||
"CREATE TABLE backup_blobs (id INTEGER PRIMARY KEY, file_name, file_content);",
|
||||
params![],
|
||||
)
|
||||
.is_err()
|
||||
{
|
||||
/* error already logged */
|
||||
ok_to_continue = false;
|
||||
}
|
||||
}
|
||||
if ok_to_continue {
|
||||
let mut total_files_cnt = 0;
|
||||
let dir = context.get_blobdir();
|
||||
if let Ok(dir_handle) = std::fs::read_dir(&dir) {
|
||||
total_files_cnt += dir_handle.filter(|r| r.is_ok()).count();
|
||||
ensure!(
|
||||
sql.open(context, &dest_path_filename, 0),
|
||||
"could not open db"
|
||||
);
|
||||
if !sql.table_exists("backup_blobs") {
|
||||
sql::execute(
|
||||
context,
|
||||
&sql,
|
||||
"CREATE TABLE backup_blobs (id INTEGER PRIMARY KEY, file_name, file_content);",
|
||||
params![],
|
||||
)?
|
||||
}
|
||||
// copy all files from BLOBDIR into backup-db
|
||||
let mut total_files_cnt = 0;
|
||||
let dir = context.get_blobdir();
|
||||
let dir_handle = std::fs::read_dir(&dir)?;
|
||||
total_files_cnt += dir_handle.filter(|r| r.is_ok()).count();
|
||||
|
||||
info!(context, "EXPORT: total_files_cnt={}", total_files_cnt);
|
||||
if total_files_cnt > 0 {
|
||||
// scan directory, pass 2: copy files
|
||||
if let Ok(dir_handle) = std::fs::read_dir(&dir) {
|
||||
sql.prepare(
|
||||
"INSERT INTO backup_blobs (file_name, file_content) VALUES (?, ?);",
|
||||
move |mut stmt, _| {
|
||||
let mut processed_files_cnt = 0;
|
||||
for entry in dir_handle {
|
||||
if entry.is_err() {
|
||||
break;
|
||||
}
|
||||
let entry = entry.unwrap();
|
||||
if context
|
||||
.running_state
|
||||
.clone()
|
||||
.read()
|
||||
.unwrap()
|
||||
.shall_stop_ongoing
|
||||
{
|
||||
delete_dest_file = 1;
|
||||
ok_to_continue = false;
|
||||
break;
|
||||
} else {
|
||||
processed_files_cnt += 1;
|
||||
let mut permille =
|
||||
processed_files_cnt * 1000 / total_files_cnt;
|
||||
if permille < 10 {
|
||||
permille = 10;
|
||||
}
|
||||
if permille > 990 {
|
||||
permille = 990;
|
||||
}
|
||||
context.call_cb(Event::ImexProgress(permille));
|
||||
|
||||
let name_f = entry.file_name();
|
||||
let name = name_f.to_string_lossy();
|
||||
if name.starts_with("delta-chat") && name.ends_with(".bak")
|
||||
{
|
||||
continue;
|
||||
} else {
|
||||
info!(context, "EXPORTing filename={}", name);
|
||||
let curr_path_filename = context.get_blobdir().join(entry.file_name());
|
||||
if let Ok(buf) =
|
||||
dc_read_file(context, &curr_path_filename)
|
||||
{
|
||||
if buf.is_empty() {
|
||||
continue;
|
||||
}
|
||||
if stmt.execute(params![name, buf]).is_err() {
|
||||
error!(
|
||||
context,
|
||||
"Disk full? Cannot add file \"{}\" to backup.",
|
||||
curr_path_filename.display(),
|
||||
);
|
||||
/* this is not recoverable! writing to the sqlite database should work! */
|
||||
ok_to_continue = false;
|
||||
break;
|
||||
}
|
||||
} else {
|
||||
continue;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
).unwrap();
|
||||
} else {
|
||||
error!(
|
||||
context,
|
||||
"Backup: Cannot copy from blob-directory \"{}\".",
|
||||
context.get_blobdir().display(),
|
||||
);
|
||||
}
|
||||
} else {
|
||||
info!(context, "Backup: No files to copy.",);
|
||||
}
|
||||
if ok_to_continue {
|
||||
if sql
|
||||
.set_config_int(context, "backup_time", now as i32)
|
||||
.is_ok()
|
||||
{
|
||||
context.call_cb(Event::ImexFileWritten(dest_path_filename.clone()));
|
||||
success = true;
|
||||
}
|
||||
}
|
||||
} else {
|
||||
error!(
|
||||
context,
|
||||
"Backup: Cannot get info for blob-directory \"{}\".",
|
||||
context.get_blobdir().display(),
|
||||
info!(context, "EXPORT: total_files_cnt={}", total_files_cnt);
|
||||
// scan directory, pass 2: copy files
|
||||
let dir_handle = std::fs::read_dir(&dir)?;
|
||||
sql.prepare(
|
||||
"INSERT INTO backup_blobs (file_name, file_content) VALUES (?, ?);",
|
||||
|mut stmt, _| {
|
||||
let mut processed_files_cnt = 0;
|
||||
for entry in dir_handle {
|
||||
let entry = entry?;
|
||||
ensure!(
|
||||
!context.shall_stop_ongoing(),
|
||||
"canceled during export-files"
|
||||
);
|
||||
};
|
||||
}
|
||||
}
|
||||
if 0 != delete_dest_file {
|
||||
dc_delete_file(context, &dest_path_filename);
|
||||
}
|
||||
processed_files_cnt += 1;
|
||||
let permille = max(min(processed_files_cnt * 1000 / total_files_cnt, 990), 10);
|
||||
context.call_cb(Event::ImexProgress(permille));
|
||||
|
||||
ensure!(success, "failed");
|
||||
let name_f = entry.file_name();
|
||||
let name = name_f.to_string_lossy();
|
||||
if name.starts_with("delta-chat") && name.ends_with(".bak") {
|
||||
continue;
|
||||
}
|
||||
info!(context, "EXPORT: copying filename={}", name);
|
||||
let curr_path_filename = context.get_blobdir().join(entry.file_name());
|
||||
if let Ok(buf) = dc_read_file(context, &curr_path_filename) {
|
||||
if buf.is_empty() {
|
||||
continue;
|
||||
}
|
||||
// bail out if we can't insert
|
||||
stmt.execute(params![name, buf])?;
|
||||
}
|
||||
}
|
||||
Ok(())
|
||||
},
|
||||
)?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
@@ -739,81 +592,48 @@ fn import_self_keys(context: &Context, dir: impl AsRef<Path>) -> Result<()> {
|
||||
Maybe we should make the "default" key handlong also a little bit smarter
|
||||
(currently, the last imported key is the standard key unless it contains the string "legacy" in its name) */
|
||||
let mut set_default: bool;
|
||||
let mut key: String;
|
||||
let mut imported_cnt = 0;
|
||||
|
||||
let dir_name = dir.as_ref().to_string_lossy();
|
||||
if let Ok(dir_handle) = std::fs::read_dir(&dir) {
|
||||
for entry in dir_handle {
|
||||
let entry_fn = match entry {
|
||||
Err(err) => {
|
||||
info!(context, "file-dir error: {}", err);
|
||||
break;
|
||||
let dir_handle = std::fs::read_dir(&dir)?;
|
||||
for entry in dir_handle {
|
||||
let entry_fn = entry?.file_name();
|
||||
let name_f = entry_fn.to_string_lossy();
|
||||
let path_plus_name = dir.as_ref().join(&entry_fn);
|
||||
match dc_get_filesuffix_lc(&name_f) {
|
||||
Some(suffix) => {
|
||||
if suffix != "asc" {
|
||||
continue;
|
||||
}
|
||||
Ok(res) => res.file_name(),
|
||||
};
|
||||
let name_f = entry_fn.to_string_lossy();
|
||||
let path_plus_name = dir.as_ref().join(&entry_fn);
|
||||
match dc_get_filesuffix_lc(&name_f) {
|
||||
Some(suffix) => {
|
||||
if suffix != "asc" {
|
||||
continue;
|
||||
}
|
||||
set_default = if name_f.contains("legacy") {
|
||||
info!(context, "found legacy key '{}'", path_plus_name.display());
|
||||
false
|
||||
} else {
|
||||
true
|
||||
}
|
||||
set_default = if name_f.contains("legacy") {
|
||||
info!(context, "found legacy key '{}'", path_plus_name.display());
|
||||
false
|
||||
} else {
|
||||
true
|
||||
}
|
||||
None => {
|
||||
}
|
||||
None => {
|
||||
continue;
|
||||
}
|
||||
}
|
||||
match dc_read_file(context, &path_plus_name) {
|
||||
Ok(buf) => {
|
||||
let armored = std::string::String::from_utf8_lossy(&buf);
|
||||
if let Err(err) = set_self_key(context, &armored, set_default, false) {
|
||||
error!(context, "set_self_key: {}", err);
|
||||
continue;
|
||||
}
|
||||
}
|
||||
let ccontent = if let Ok(content) = dc_read_file(context, &path_plus_name) {
|
||||
key = String::from_utf8_lossy(&content).to_string();
|
||||
CString::new(content).unwrap()
|
||||
} else {
|
||||
continue;
|
||||
};
|
||||
|
||||
/* only import if we have a private key */
|
||||
let mut buf2_headerline = String::default();
|
||||
let split_res: bool;
|
||||
unsafe {
|
||||
let buf2 = dc_strdup(ccontent.as_ptr());
|
||||
split_res = dc_split_armored_data(
|
||||
buf2,
|
||||
&mut buf2_headerline,
|
||||
ptr::null_mut(),
|
||||
ptr::null_mut(),
|
||||
ptr::null_mut(),
|
||||
);
|
||||
libc::free(buf2 as *mut libc::c_void);
|
||||
}
|
||||
if split_res
|
||||
&& buf2_headerline.contains("-----BEGIN PGP PUBLIC KEY BLOCK-----")
|
||||
&& !key.contains("-----BEGIN PGP PRIVATE KEY BLOCK")
|
||||
{
|
||||
info!(context, "ignoring public key file '{}", name_f);
|
||||
// it's fine: DC exports public with private
|
||||
continue;
|
||||
}
|
||||
if let Err(err) = set_self_key(context, &key, set_default, false) {
|
||||
error!(context, "set_self_key: {}", err);
|
||||
continue;
|
||||
}
|
||||
imported_cnt += 1
|
||||
Err(_) => continue,
|
||||
}
|
||||
ensure!(
|
||||
imported_cnt > 0,
|
||||
"No private keys found in \"{}\".",
|
||||
dir_name
|
||||
);
|
||||
return Ok(());
|
||||
} else {
|
||||
bail!("Import: Cannot open directory \"{}\".", dir_name);
|
||||
imported_cnt += 1;
|
||||
}
|
||||
ensure!(
|
||||
imported_cnt > 0,
|
||||
"No private keys found in \"{}\".",
|
||||
dir_name
|
||||
);
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn export_self_keys(context: &Context, dir: impl AsRef<Path>) -> Result<()> {
|
||||
@@ -856,7 +676,7 @@ fn export_self_keys(context: &Context, dir: impl AsRef<Path>) -> Result<()> {
|
||||
},
|
||||
)?;
|
||||
|
||||
ensure!(export_errors == 0, "errors while importing");
|
||||
ensure!(export_errors == 0, "errors while exporting keys");
|
||||
Ok(())
|
||||
}
|
||||
|
||||
@@ -893,7 +713,9 @@ fn export_key_to_asc_file(
|
||||
mod tests {
|
||||
use super::*;
|
||||
|
||||
use crate::pgp::{split_armored_data, HEADER_AUTOCRYPT, HEADER_SETUPCODE};
|
||||
use crate::test_utils::*;
|
||||
use ::pgp::armor::BlockType;
|
||||
|
||||
#[test]
|
||||
fn test_render_setup_file() {
|
||||
@@ -915,19 +737,12 @@ mod tests {
|
||||
assert!(msg.contains("-----END PGP MESSAGE-----\n"));
|
||||
}
|
||||
|
||||
fn ac_setup_msg_cb(ctx: &Context, evt: Event) -> libc::uintptr_t {
|
||||
match evt {
|
||||
Event::GetString {
|
||||
id: StockMessage::AcSetupMsgBody,
|
||||
..
|
||||
} => unsafe { "hello\r\nthere".strdup() as usize },
|
||||
_ => logging_cb(ctx, evt),
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn otest_render_setup_file_newline_replace() {
|
||||
let t = test_context(Some(Box::new(ac_setup_msg_cb)));
|
||||
fn test_render_setup_file_newline_replace() {
|
||||
let t = dummy_context();
|
||||
t.ctx
|
||||
.set_stock_translation(StockMessage::AcSetupMsgBody, "hello\r\nthere".to_string())
|
||||
.unwrap();
|
||||
configure_alice_keypair(&t.ctx);
|
||||
let msg = render_setup_file(&t.ctx, "pw").unwrap();
|
||||
println!("{}", &msg);
|
||||
@@ -983,47 +798,26 @@ mod tests {
|
||||
let ctx = dummy_context();
|
||||
let context = &ctx.ctx;
|
||||
|
||||
let mut headerline = String::default();
|
||||
let mut setupcodebegin = ptr::null();
|
||||
let mut preferencrypt = ptr::null();
|
||||
let buf_1 = S_EM_SETUPFILE.as_bytes().to_vec();
|
||||
let (typ, headers, base64) = split_armored_data(&buf_1).unwrap();
|
||||
assert_eq!(typ, BlockType::Message);
|
||||
assert!(S_EM_SETUPCODE.starts_with(headers.get(HEADER_SETUPCODE).unwrap()));
|
||||
assert!(headers.get(HEADER_AUTOCRYPT).is_none());
|
||||
|
||||
let mut buf_1 = S_EM_SETUPFILE.to_string();
|
||||
assert!(!base64.is_empty());
|
||||
|
||||
unsafe {
|
||||
assert!(dc_split_armored_data(
|
||||
buf_1.as_mut_ptr().cast(),
|
||||
&mut headerline,
|
||||
&mut setupcodebegin,
|
||||
&mut preferencrypt,
|
||||
ptr::null_mut(),
|
||||
));
|
||||
}
|
||||
assert_eq!(headerline, "-----BEGIN PGP MESSAGE-----");
|
||||
assert!(!setupcodebegin.is_null());
|
||||
let setup_file = S_EM_SETUPFILE.to_string();
|
||||
let decrypted = decrypt_setup_file(
|
||||
context,
|
||||
S_EM_SETUPCODE,
|
||||
std::io::Cursor::new(setup_file.as_bytes()),
|
||||
)
|
||||
.unwrap();
|
||||
|
||||
// TODO: verify that this is the right check
|
||||
assert!(S_EM_SETUPCODE.starts_with(as_str(setupcodebegin)));
|
||||
let (typ, headers, _base64) = split_armored_data(decrypted.as_bytes()).unwrap();
|
||||
|
||||
assert!(preferencrypt.is_null());
|
||||
|
||||
let mut setup_file = S_EM_SETUPFILE.to_string();
|
||||
let mut decrypted = unsafe {
|
||||
decrypt_setup_file(context, S_EM_SETUPCODE, setup_file.as_bytes_mut()).unwrap()
|
||||
};
|
||||
|
||||
unsafe {
|
||||
assert!(dc_split_armored_data(
|
||||
decrypted.as_mut_ptr().cast(),
|
||||
&mut headerline,
|
||||
&mut setupcodebegin,
|
||||
&mut preferencrypt,
|
||||
ptr::null_mut(),
|
||||
));
|
||||
}
|
||||
|
||||
assert_eq!(headerline, "-----BEGIN PGP PRIVATE KEY BLOCK-----");
|
||||
assert!(setupcodebegin.is_null());
|
||||
assert!(!preferencrypt.is_null());
|
||||
assert_eq!(as_str(preferencrypt), "mutual",);
|
||||
assert_eq!(typ, BlockType::PrivateKey);
|
||||
assert_eq!(headers.get(HEADER_AUTOCRYPT), Some(&"mutual".to_string()));
|
||||
assert!(headers.get(HEADER_SETUPCODE).is_none());
|
||||
}
|
||||
}
|
||||
|
||||
448
src/job.rs
448
src/job.rs
@@ -4,10 +4,12 @@ use deltachat_derive::{FromSql, ToSql};
|
||||
use rand::{thread_rng, Rng};
|
||||
|
||||
use crate::chat;
|
||||
use crate::config::Config;
|
||||
use crate::configure::*;
|
||||
use crate::constants::*;
|
||||
use crate::context::Context;
|
||||
use crate::dc_tools::*;
|
||||
use crate::error::Error;
|
||||
use crate::events::Event;
|
||||
use crate::imap::*;
|
||||
use crate::imex::*;
|
||||
@@ -140,11 +142,11 @@ impl Job {
|
||||
if let Ok(body) = dc_read_file(context, filename) {
|
||||
if let Some(recipients) = self.param.get(Param::Recipients) {
|
||||
let recipients_list = recipients
|
||||
.split("\x1e")
|
||||
.split('\x1e')
|
||||
.filter_map(|addr| match lettre::EmailAddress::new(addr.to_string()) {
|
||||
Ok(addr) => Some(addr),
|
||||
Err(err) => {
|
||||
eprintln!("WARNING: invalid recipient: {} {:?}", addr, err);
|
||||
warn!(context, "invalid recipient: {} {:?}", addr, err);
|
||||
None
|
||||
}
|
||||
})
|
||||
@@ -156,39 +158,45 @@ impl Job {
|
||||
if 0 != self.foreign_id && !message::exists(context, self.foreign_id) {
|
||||
warn!(
|
||||
context,
|
||||
"Message {} for job {} does not exist", self.foreign_id, self.job_id,
|
||||
"Not sending Message {} as it was deleted", self.foreign_id
|
||||
);
|
||||
return;
|
||||
};
|
||||
|
||||
// hold the smtp lock during sending of a job and
|
||||
// its ok/error response processing. Note that if a message
|
||||
// was sent we need to mark it in the database as we
|
||||
// was sent we need to mark it in the database ASAP as we
|
||||
// otherwise might send it twice.
|
||||
let mut sock = context.smtp.lock().unwrap();
|
||||
if 0 == sock.send(context, recipients_list, body) {
|
||||
sock.disconnect();
|
||||
self.try_again_later(-1i32, sock.error.clone());
|
||||
} else {
|
||||
dc_delete_file(context, filename);
|
||||
if 0 != self.foreign_id {
|
||||
message::update_msg_state(
|
||||
context,
|
||||
self.foreign_id,
|
||||
MessageState::OutDelivered,
|
||||
);
|
||||
let chat_id: i32 = context
|
||||
.sql
|
||||
.query_get_value(
|
||||
match sock.send(context, recipients_list, body) {
|
||||
Err(err) => {
|
||||
sock.disconnect();
|
||||
warn!(context, "smtp failed: {}", err);
|
||||
self.try_again_later(-1i32, Some(err.to_string()));
|
||||
}
|
||||
Ok(()) => {
|
||||
// smtp success, update db ASAP, then delete smtp file
|
||||
if 0 != self.foreign_id {
|
||||
message::update_msg_state(
|
||||
context,
|
||||
"SELECT chat_id FROM msgs WHERE id=?",
|
||||
params![self.foreign_id as i32],
|
||||
)
|
||||
.unwrap_or_default();
|
||||
context.call_cb(Event::MsgDelivered {
|
||||
chat_id: chat_id as u32,
|
||||
msg_id: self.foreign_id,
|
||||
});
|
||||
self.foreign_id,
|
||||
MessageState::OutDelivered,
|
||||
);
|
||||
let chat_id: i32 = context
|
||||
.sql
|
||||
.query_get_value(
|
||||
context,
|
||||
"SELECT chat_id FROM msgs WHERE id=?",
|
||||
params![self.foreign_id as i32],
|
||||
)
|
||||
.unwrap_or_default();
|
||||
context.call_cb(Event::MsgDelivered {
|
||||
chat_id: chat_id as u32,
|
||||
msg_id: self.foreign_id,
|
||||
});
|
||||
}
|
||||
// now also delete the generated file
|
||||
dc_delete_file(context, filename);
|
||||
}
|
||||
}
|
||||
} else {
|
||||
@@ -208,23 +216,18 @@ impl Job {
|
||||
fn do_DC_JOB_MOVE_MSG(&mut self, context: &Context) {
|
||||
let inbox = context.inbox.read().unwrap();
|
||||
|
||||
if !inbox.is_connected() {
|
||||
connect_to_inbox(context, &inbox);
|
||||
if !inbox.is_connected() {
|
||||
self.try_again_later(3, None);
|
||||
return;
|
||||
}
|
||||
}
|
||||
if let Ok(msg) = Message::load_from_db(context, self.foreign_id) {
|
||||
if context
|
||||
.sql
|
||||
.get_config_int(context, "folders_configured")
|
||||
.get_raw_config_int(context, "folders_configured")
|
||||
.unwrap_or_default()
|
||||
< 3
|
||||
{
|
||||
inbox.configure_folders(context, 0x1i32);
|
||||
}
|
||||
let dest_folder = context.sql.get_config(context, "configured_mvbox_folder");
|
||||
let dest_folder = context
|
||||
.sql
|
||||
.get_raw_config(context, "configured_mvbox_folder");
|
||||
|
||||
if let Some(dest_folder) = dest_folder {
|
||||
let server_folder = msg.server_folder.as_ref().unwrap();
|
||||
@@ -261,26 +264,18 @@ impl Job {
|
||||
if let Ok(mut msg) = Message::load_from_db(context, self.foreign_id) {
|
||||
if !msg.rfc724_mid.is_empty() {
|
||||
/* eg. device messages have no Message-ID */
|
||||
let mut delete_from_server = true;
|
||||
if message::rfc724_mid_cnt(context, &msg.rfc724_mid) != 1 {
|
||||
if message::rfc724_mid_cnt(context, &msg.rfc724_mid) > 1 {
|
||||
info!(
|
||||
context,
|
||||
"The message is deleted from the server when all parts are deleted.",
|
||||
);
|
||||
delete_from_server = false;
|
||||
}
|
||||
/* if this is the last existing part of the message, we delete the message from the server */
|
||||
if delete_from_server {
|
||||
if !inbox.is_connected() {
|
||||
connect_to_inbox(context, &inbox);
|
||||
if !inbox.is_connected() {
|
||||
self.try_again_later(3i32, None);
|
||||
return;
|
||||
}
|
||||
}
|
||||
} else {
|
||||
/* if this is the last existing part of the message,
|
||||
we delete the message from the server */
|
||||
let mid = msg.rfc724_mid;
|
||||
let server_folder = msg.server_folder.as_ref().unwrap();
|
||||
if 0 == inbox.delete_msg(context, &mid, server_folder, &mut msg.server_uid) {
|
||||
let res = inbox.delete_msg(context, &mid, server_folder, &mut msg.server_uid);
|
||||
if res == ImapResult::RetryLater {
|
||||
self.try_again_later(-1i32, None);
|
||||
return;
|
||||
}
|
||||
@@ -294,37 +289,23 @@ impl Job {
|
||||
fn do_DC_JOB_MARKSEEN_MSG_ON_IMAP(&mut self, context: &Context) {
|
||||
let inbox = context.inbox.read().unwrap();
|
||||
|
||||
if !inbox.is_connected() {
|
||||
connect_to_inbox(context, &inbox);
|
||||
if !inbox.is_connected() {
|
||||
self.try_again_later(3i32, None);
|
||||
return;
|
||||
}
|
||||
}
|
||||
if let Ok(msg) = Message::load_from_db(context, self.foreign_id) {
|
||||
let server_folder = msg.server_folder.as_ref().unwrap();
|
||||
match inbox.set_seen(context, server_folder, msg.server_uid) {
|
||||
ImapResult::Failed => {}
|
||||
let folder = msg.server_folder.as_ref().unwrap();
|
||||
match inbox.set_seen(context, folder, msg.server_uid) {
|
||||
ImapResult::RetryLater => {
|
||||
self.try_again_later(3i32, None);
|
||||
}
|
||||
_ => {
|
||||
ImapResult::AlreadyDone => {}
|
||||
ImapResult::Success | ImapResult::Failed => {
|
||||
// XXX the message might just have been moved
|
||||
// we want to send out an MDN anyway
|
||||
// The job will not be retried so locally
|
||||
// there is no risk of double-sending MDNs.
|
||||
if 0 != msg.param.get_int(Param::WantsMdn).unwrap_or_default()
|
||||
&& 0 != context
|
||||
.sql
|
||||
.get_config_int(context, "mdns_enabled")
|
||||
.unwrap_or_else(|| 1)
|
||||
&& context.get_config_bool(Config::MdnsEnabled)
|
||||
{
|
||||
let folder = msg.server_folder.as_ref().unwrap();
|
||||
|
||||
match inbox.set_mdnsent(context, folder, msg.server_uid) {
|
||||
ImapResult::RetryLater => {
|
||||
self.try_again_later(3i32, None);
|
||||
}
|
||||
ImapResult::Success => {
|
||||
send_mdn(context, msg.id);
|
||||
}
|
||||
ImapResult::Failed | ImapResult::AlreadyDone => {}
|
||||
if let Err(err) = send_mdn(context, msg.id) {
|
||||
warn!(context, "could not send out mdn for {}: {}", msg.id, err);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -341,31 +322,26 @@ impl Job {
|
||||
.to_string();
|
||||
let uid = self.param.get_int(Param::ServerUid).unwrap_or_default() as u32;
|
||||
let inbox = context.inbox.read().unwrap();
|
||||
|
||||
if !inbox.is_connected() {
|
||||
connect_to_inbox(context, &inbox);
|
||||
if !inbox.is_connected() {
|
||||
self.try_again_later(3, None);
|
||||
return;
|
||||
}
|
||||
}
|
||||
if inbox.set_seen(context, &folder, uid) == ImapResult::Failed {
|
||||
if inbox.set_seen(context, &folder, uid) == ImapResult::RetryLater {
|
||||
self.try_again_later(3i32, None);
|
||||
return;
|
||||
}
|
||||
if 0 != self.param.get_int(Param::AlsoMove).unwrap_or_default() {
|
||||
if context
|
||||
.sql
|
||||
.get_config_int(context, "folders_configured")
|
||||
.get_raw_config_int(context, "folders_configured")
|
||||
.unwrap_or_default()
|
||||
< 3
|
||||
{
|
||||
inbox.configure_folders(context, 0x1i32);
|
||||
}
|
||||
let dest_folder = context.sql.get_config(context, "configured_mvbox_folder");
|
||||
let dest_folder = context
|
||||
.sql
|
||||
.get_raw_config(context, "configured_mvbox_folder");
|
||||
if let Some(dest_folder) = dest_folder {
|
||||
let mut dest_uid = 0;
|
||||
if ImapResult::RetryLater
|
||||
== inbox.mv(context, folder, uid, dest_folder, &mut dest_uid)
|
||||
== inbox.mv(context, &folder, uid, &dest_folder, &mut dest_uid)
|
||||
{
|
||||
self.try_again_later(3, None);
|
||||
}
|
||||
@@ -392,12 +368,7 @@ pub fn perform_imap_fetch(context: &Context) {
|
||||
if 0 == connect_to_inbox(context, &inbox) {
|
||||
return;
|
||||
}
|
||||
if context
|
||||
.sql
|
||||
.get_config_int(context, "inbox_watch")
|
||||
.unwrap_or_else(|| 1)
|
||||
== 0
|
||||
{
|
||||
if !context.get_config_bool(Config::InboxWatch) {
|
||||
info!(context, "INBOX-watch disabled.",);
|
||||
return;
|
||||
}
|
||||
@@ -432,29 +403,23 @@ pub fn perform_imap_idle(context: &Context) {
|
||||
}
|
||||
|
||||
pub fn perform_mvbox_fetch(context: &Context) {
|
||||
let use_network = context
|
||||
.sql
|
||||
.get_config_int(context, "mvbox_watch")
|
||||
.unwrap_or_else(|| 1);
|
||||
let use_network = context.get_config_bool(Config::MvboxWatch);
|
||||
|
||||
context
|
||||
.mvbox_thread
|
||||
.write()
|
||||
.unwrap()
|
||||
.fetch(context, use_network == 1);
|
||||
.fetch(context, use_network);
|
||||
}
|
||||
|
||||
pub fn perform_mvbox_idle(context: &Context) {
|
||||
let use_network = context
|
||||
.sql
|
||||
.get_config_int(context, "mvbox_watch")
|
||||
.unwrap_or_else(|| 1);
|
||||
let use_network = context.get_config_bool(Config::MvboxWatch);
|
||||
|
||||
context
|
||||
.mvbox_thread
|
||||
.read()
|
||||
.unwrap()
|
||||
.idle(context, use_network == 1);
|
||||
.idle(context, use_network);
|
||||
}
|
||||
|
||||
pub fn interrupt_mvbox_idle(context: &Context) {
|
||||
@@ -462,29 +427,23 @@ pub fn interrupt_mvbox_idle(context: &Context) {
|
||||
}
|
||||
|
||||
pub fn perform_sentbox_fetch(context: &Context) {
|
||||
let use_network = context
|
||||
.sql
|
||||
.get_config_int(context, "sentbox_watch")
|
||||
.unwrap_or_else(|| 1);
|
||||
let use_network = context.get_config_bool(Config::SentboxWatch);
|
||||
|
||||
context
|
||||
.sentbox_thread
|
||||
.write()
|
||||
.unwrap()
|
||||
.fetch(context, use_network == 1);
|
||||
.fetch(context, use_network);
|
||||
}
|
||||
|
||||
pub fn perform_sentbox_idle(context: &Context) {
|
||||
let use_network = context
|
||||
.sql
|
||||
.get_config_int(context, "sentbox_watch")
|
||||
.unwrap_or_else(|| 1);
|
||||
let use_network = context.get_config_bool(Config::SentboxWatch);
|
||||
|
||||
context
|
||||
.sentbox_thread
|
||||
.read()
|
||||
.unwrap()
|
||||
.idle(context, use_network == 1);
|
||||
.idle(context, use_network);
|
||||
}
|
||||
|
||||
pub fn interrupt_sentbox_idle(context: &Context) {
|
||||
@@ -601,109 +560,108 @@ pub fn job_action_exists(context: &Context, action: Action) -> bool {
|
||||
|
||||
/* special case for DC_JOB_SEND_MSG_TO_SMTP */
|
||||
#[allow(non_snake_case)]
|
||||
pub fn job_send_msg(context: &Context, msg_id: u32) -> libc::c_int {
|
||||
let mut success = 0;
|
||||
pub fn job_send_msg(context: &Context, msg_id: u32) -> Result<(), Error> {
|
||||
let mut mimefactory = MimeFactory::load_msg(context, msg_id)?;
|
||||
|
||||
/* load message data */
|
||||
let mimefactory = MimeFactory::load_msg(context, msg_id);
|
||||
if mimefactory.is_err() {
|
||||
warn!(
|
||||
context,
|
||||
"Cannot load data to send, maybe the message is deleted in between.",
|
||||
);
|
||||
} else {
|
||||
let mut mimefactory = mimefactory.unwrap();
|
||||
// no redo, no IMAP. moreover, as the data does not exist, there is no need in calling dc_set_msg_failed()
|
||||
if chat::msgtype_has_file(mimefactory.msg.type_0) {
|
||||
let file_param = mimefactory
|
||||
.msg
|
||||
.param
|
||||
.get(Param::File)
|
||||
.map(|s| s.to_string());
|
||||
if let Some(pathNfilename) = file_param {
|
||||
if (mimefactory.msg.type_0 == Viewtype::Image
|
||||
|| mimefactory.msg.type_0 == Viewtype::Gif)
|
||||
&& !mimefactory.msg.param.exists(Param::Width)
|
||||
{
|
||||
mimefactory.msg.param.set_int(Param::Width, 0);
|
||||
mimefactory.msg.param.set_int(Param::Height, 0);
|
||||
|
||||
if let Ok(buf) = dc_read_file(context, pathNfilename) {
|
||||
if let Ok((width, height)) = dc_get_filemeta(&buf) {
|
||||
mimefactory.msg.param.set_int(Param::Width, width as i32);
|
||||
mimefactory.msg.param.set_int(Param::Height, height as i32);
|
||||
}
|
||||
}
|
||||
mimefactory.msg.save_param_to_disk(context);
|
||||
}
|
||||
}
|
||||
}
|
||||
/* create message */
|
||||
if let Err(msg) = unsafe { mimefactory.render() } {
|
||||
let e = msg.to_string();
|
||||
message::set_msg_failed(context, msg_id, Some(e));
|
||||
} else if 0
|
||||
!= mimefactory
|
||||
.msg
|
||||
.param
|
||||
.get_int(Param::GuranteeE2ee)
|
||||
.unwrap_or_default()
|
||||
&& !mimefactory.out_encrypted
|
||||
{
|
||||
/* unrecoverable */
|
||||
warn!(
|
||||
context,
|
||||
"e2e encryption unavailable {} - {:?}",
|
||||
msg_id,
|
||||
mimefactory.msg.param.get_int(Param::GuranteeE2ee),
|
||||
);
|
||||
message::set_msg_failed(
|
||||
context,
|
||||
msg_id,
|
||||
Some("End-to-end-encryption unavailable unexpectedly."),
|
||||
);
|
||||
} else {
|
||||
if !vec_contains_lowercase(&mimefactory.recipients_addr, &mimefactory.from_addr) {
|
||||
mimefactory.recipients_names.push("".to_string());
|
||||
mimefactory
|
||||
.recipients_addr
|
||||
.push(mimefactory.from_addr.to_string());
|
||||
}
|
||||
if mimefactory.out_gossiped {
|
||||
chat::set_gossiped_timestamp(context, mimefactory.msg.chat_id, time());
|
||||
}
|
||||
if 0 != mimefactory.out_last_added_location_id {
|
||||
if let Err(err) =
|
||||
location::set_kml_sent_timestamp(context, mimefactory.msg.chat_id, time())
|
||||
{
|
||||
error!(context, "Failed to set kml sent_timestamp: {:?}", err);
|
||||
}
|
||||
if !mimefactory.msg.hidden {
|
||||
if let Err(err) = location::set_msg_location_id(
|
||||
context,
|
||||
mimefactory.msg.id,
|
||||
mimefactory.out_last_added_location_id,
|
||||
) {
|
||||
error!(context, "Failed to set msg_location_id: {:?}", err);
|
||||
}
|
||||
}
|
||||
}
|
||||
if mimefactory.out_encrypted
|
||||
&& mimefactory
|
||||
.msg
|
||||
.param
|
||||
.get_int(Param::GuranteeE2ee)
|
||||
.unwrap_or_default()
|
||||
== 0
|
||||
if chat::msgtype_has_file(mimefactory.msg.type_0) {
|
||||
let file_param = mimefactory
|
||||
.msg
|
||||
.param
|
||||
.get(Param::File)
|
||||
.map(|s| s.to_string());
|
||||
if let Some(pathNfilename) = file_param {
|
||||
if (mimefactory.msg.type_0 == Viewtype::Image
|
||||
|| mimefactory.msg.type_0 == Viewtype::Gif)
|
||||
&& !mimefactory.msg.param.exists(Param::Width)
|
||||
{
|
||||
mimefactory.msg.param.set_int(Param::GuranteeE2ee, 1);
|
||||
mimefactory.msg.param.set_int(Param::Width, 0);
|
||||
mimefactory.msg.param.set_int(Param::Height, 0);
|
||||
|
||||
if let Ok(buf) = dc_read_file(context, pathNfilename) {
|
||||
if let Ok((width, height)) = dc_get_filemeta(&buf) {
|
||||
mimefactory.msg.param.set_int(Param::Width, width as i32);
|
||||
mimefactory.msg.param.set_int(Param::Height, height as i32);
|
||||
}
|
||||
}
|
||||
mimefactory.msg.save_param_to_disk(context);
|
||||
}
|
||||
success = add_smtp_job(context, Action::SendMsgToSmtp, &mut mimefactory);
|
||||
}
|
||||
}
|
||||
|
||||
success
|
||||
/* create message */
|
||||
if let Err(msg) = unsafe { mimefactory.render() } {
|
||||
let e = msg.to_string();
|
||||
message::set_msg_failed(context, msg_id, Some(e));
|
||||
return Err(msg);
|
||||
}
|
||||
if 0 != mimefactory
|
||||
.msg
|
||||
.param
|
||||
.get_int(Param::GuranteeE2ee)
|
||||
.unwrap_or_default()
|
||||
&& !mimefactory.out_encrypted
|
||||
{
|
||||
/* unrecoverable */
|
||||
message::set_msg_failed(
|
||||
context,
|
||||
msg_id,
|
||||
Some("End-to-end-encryption unavailable unexpectedly."),
|
||||
);
|
||||
bail!(
|
||||
"e2e encryption unavailable {} - {:?}",
|
||||
msg_id,
|
||||
mimefactory.msg.param.get_int(Param::GuranteeE2ee),
|
||||
);
|
||||
}
|
||||
if context.get_config_bool(Config::BccSelf)
|
||||
&& !vec_contains_lowercase(&mimefactory.recipients_addr, &mimefactory.from_addr)
|
||||
{
|
||||
mimefactory.recipients_names.push("".to_string());
|
||||
mimefactory
|
||||
.recipients_addr
|
||||
.push(mimefactory.from_addr.to_string());
|
||||
}
|
||||
|
||||
if mimefactory.recipients_addr.is_empty() {
|
||||
warn!(
|
||||
context,
|
||||
"message {} has no recipient, skipping smtp-send", msg_id
|
||||
);
|
||||
return Ok(());
|
||||
}
|
||||
|
||||
if mimefactory.out_gossiped {
|
||||
chat::set_gossiped_timestamp(context, mimefactory.msg.chat_id, time());
|
||||
}
|
||||
if 0 != mimefactory.out_last_added_location_id {
|
||||
if let Err(err) = location::set_kml_sent_timestamp(context, mimefactory.msg.chat_id, time())
|
||||
{
|
||||
error!(context, "Failed to set kml sent_timestamp: {:?}", err);
|
||||
}
|
||||
if !mimefactory.msg.hidden {
|
||||
if let Err(err) = location::set_msg_location_id(
|
||||
context,
|
||||
mimefactory.msg.id,
|
||||
mimefactory.out_last_added_location_id,
|
||||
) {
|
||||
error!(context, "Failed to set msg_location_id: {:?}", err);
|
||||
}
|
||||
}
|
||||
}
|
||||
if mimefactory.out_encrypted
|
||||
&& mimefactory
|
||||
.msg
|
||||
.param
|
||||
.get_int(Param::GuranteeE2ee)
|
||||
.unwrap_or_default()
|
||||
== 0
|
||||
{
|
||||
mimefactory.msg.param.set_int(Param::GuranteeE2ee, 1);
|
||||
mimefactory.msg.save_param_to_disk(context);
|
||||
}
|
||||
add_smtp_job(context, Action::SendMsgToSmtp, &mut mimefactory)?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub fn perform_imap_jobs(context: &Context) {
|
||||
@@ -793,13 +751,13 @@ fn job_perform(context: &Context, thread: Thread, probe_network: bool) {
|
||||
// - they can be re-executed one time AT_ONCE, but they are not save in the database for later execution
|
||||
if Action::ConfigureImap == job.action || Action::ImexImap == job.action {
|
||||
job_kill_action(context, job.action);
|
||||
&context
|
||||
context
|
||||
.sentbox_thread
|
||||
.clone()
|
||||
.read()
|
||||
.unwrap()
|
||||
.suspend(context);
|
||||
&context
|
||||
context
|
||||
.mvbox_thread
|
||||
.clone()
|
||||
.read()
|
||||
@@ -823,7 +781,7 @@ fn job_perform(context: &Context, thread: Thread, probe_network: bool) {
|
||||
Action::MarkseenMdnOnImap => job.do_DC_JOB_MARKSEEN_MDN_ON_IMAP(context),
|
||||
Action::MoveMsg => job.do_DC_JOB_MOVE_MSG(context),
|
||||
Action::SendMdn => job.do_DC_JOB_SEND(context),
|
||||
Action::ConfigureImap => unsafe { dc_job_do_DC_JOB_CONFIGURE_IMAP(context) },
|
||||
Action::ConfigureImap => dc_job_do_DC_JOB_CONFIGURE_IMAP(context),
|
||||
Action::ImexImap => match job_do_DC_JOB_IMEX_IMAP(context, &job) {
|
||||
Ok(()) => {}
|
||||
Err(err) => {
|
||||
@@ -924,8 +882,7 @@ fn job_perform(context: &Context, thread: Thread, probe_network: bool) {
|
||||
#[allow(non_snake_case)]
|
||||
fn get_backoff_time_offset(c_tries: libc::c_int) -> i64 {
|
||||
// results in ~3 weeks for the last backoff timespan
|
||||
let mut N = 2_i32.pow((c_tries - 1) as u32);
|
||||
N = N * 60;
|
||||
let N = 2_i32.pow((c_tries - 1) as u32) * 60;
|
||||
let mut rng = thread_rng();
|
||||
let n: i32 = rng.gen();
|
||||
let mut seconds = n % (N + 1);
|
||||
@@ -947,7 +904,7 @@ fn suspend_smtp_thread(context: &Context, suspend: bool) {
|
||||
}
|
||||
}
|
||||
|
||||
fn connect_to_inbox(context: &Context, inbox: &Imap) -> libc::c_int {
|
||||
pub fn connect_to_inbox(context: &Context, inbox: &Imap) -> libc::c_int {
|
||||
let ret_connected = dc_connect_to_configured_imap(context, inbox);
|
||||
if 0 != ret_connected {
|
||||
inbox.set_watch_folder("INBOX".into());
|
||||
@@ -955,51 +912,44 @@ fn connect_to_inbox(context: &Context, inbox: &Imap) -> libc::c_int {
|
||||
ret_connected
|
||||
}
|
||||
|
||||
fn send_mdn(context: &Context, msg_id: u32) {
|
||||
if let Ok(mut mimefactory) = MimeFactory::load_mdn(context, msg_id) {
|
||||
if unsafe { mimefactory.render() }.is_ok() {
|
||||
add_smtp_job(context, Action::SendMdn, &mut mimefactory);
|
||||
}
|
||||
}
|
||||
fn send_mdn(context: &Context, msg_id: u32) -> Result<(), Error> {
|
||||
let mut mimefactory = MimeFactory::load_mdn(context, msg_id)?;
|
||||
unsafe { mimefactory.render()? };
|
||||
add_smtp_job(context, Action::SendMdn, &mut mimefactory)?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[allow(non_snake_case)]
|
||||
fn add_smtp_job(context: &Context, action: Action, mimefactory: &MimeFactory) -> libc::c_int {
|
||||
let mut success: libc::c_int = 0i32;
|
||||
fn add_smtp_job(context: &Context, action: Action, mimefactory: &MimeFactory) -> Result<(), Error> {
|
||||
ensure!(
|
||||
!mimefactory.recipients_addr.is_empty(),
|
||||
"no recipients for smtp job set"
|
||||
);
|
||||
let mut param = Params::new();
|
||||
let path_filename = dc_get_fine_path_filename(context, "$BLOBDIR", &mimefactory.rfc724_mid);
|
||||
let bytes = unsafe {
|
||||
std::slice::from_raw_parts(
|
||||
(*mimefactory.out).str_0 as *const u8,
|
||||
(*mimefactory.out).len,
|
||||
)
|
||||
};
|
||||
if !dc_write_file(context, &path_filename, bytes) {
|
||||
error!(
|
||||
context,
|
||||
"Could not write message <{}> to \"{}\".",
|
||||
mimefactory.rfc724_mid,
|
||||
path_filename.display(),
|
||||
);
|
||||
} else {
|
||||
info!(context, "add_smtp_job file written: {:?}", path_filename);
|
||||
let recipients = mimefactory.recipients_addr.join("\x1e");
|
||||
param.set(Param::File, path_filename.to_string_lossy());
|
||||
param.set(Param::Recipients, &recipients);
|
||||
job_add(
|
||||
context,
|
||||
action,
|
||||
(if mimefactory.loaded == Loaded::Message {
|
||||
mimefactory.msg.id
|
||||
} else {
|
||||
0
|
||||
}) as libc::c_int,
|
||||
param,
|
||||
0,
|
||||
);
|
||||
success = 1;
|
||||
}
|
||||
success
|
||||
let bpath = context.new_blob_file(&mimefactory.rfc724_mid, bytes)?;
|
||||
let recipients = mimefactory.recipients_addr.join("\x1e");
|
||||
param.set(Param::File, &bpath);
|
||||
param.set(Param::Recipients, &recipients);
|
||||
job_add(
|
||||
context,
|
||||
action,
|
||||
(if mimefactory.loaded == Loaded::Message {
|
||||
mimefactory.msg.id
|
||||
} else {
|
||||
0
|
||||
}) as libc::c_int,
|
||||
param,
|
||||
0,
|
||||
);
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub fn job_add(
|
||||
|
||||
@@ -116,14 +116,14 @@ impl JobThread {
|
||||
if ret_connected {
|
||||
if context
|
||||
.sql
|
||||
.get_config_int(context, "folders_configured")
|
||||
.get_raw_config_int(context, "folders_configured")
|
||||
.unwrap_or_default()
|
||||
< 3
|
||||
{
|
||||
self.imap.configure_folders(context, 0x1);
|
||||
}
|
||||
|
||||
if let Some(mvbox_name) = context.sql.get_config(context, self.folder_config_name) {
|
||||
if let Some(mvbox_name) = context.sql.get_raw_config(context, self.folder_config_name) {
|
||||
self.imap.set_watch_folder(mvbox_name);
|
||||
} else {
|
||||
self.imap.disconnect(context);
|
||||
|
||||
12
src/key.rs
12
src/key.rs
@@ -163,8 +163,8 @@ impl Key {
|
||||
|
||||
pub fn to_bytes(&self) -> Vec<u8> {
|
||||
match self {
|
||||
Key::Public(k) => k.to_bytes().unwrap(),
|
||||
Key::Secret(k) => k.to_bytes().unwrap(),
|
||||
Key::Public(k) => k.to_bytes().unwrap_or_default(),
|
||||
Key::Secret(k) => k.to_bytes().unwrap_or_default(),
|
||||
}
|
||||
}
|
||||
|
||||
@@ -218,10 +218,10 @@ impl Key {
|
||||
let file_content = self.to_asc(None).into_bytes();
|
||||
|
||||
if dc_write_file(context, &file, &file_content) {
|
||||
return true;
|
||||
true
|
||||
} else {
|
||||
error!(context, "Cannot write key to {}", file.as_ref().display());
|
||||
return false;
|
||||
false
|
||||
}
|
||||
}
|
||||
|
||||
@@ -381,7 +381,7 @@ i8pcjGO+IZffvyZJVRWfVooBJmWWbPB1pueo3tx8w3+fcuzpxz+RLFKaPyqXO+dD
|
||||
#[test]
|
||||
#[ignore] // is too expensive
|
||||
fn test_from_slice_roundtrip() {
|
||||
let (public_key, private_key) = crate::pgp::dc_pgp_create_keypair("hello").unwrap();
|
||||
let (public_key, private_key) = crate::pgp::create_keypair("hello").unwrap();
|
||||
|
||||
let binary = public_key.to_bytes();
|
||||
let public_key2 = Key::from_slice(&binary, KeyType::Public).expect("invalid public key");
|
||||
@@ -416,7 +416,7 @@ i8pcjGO+IZffvyZJVRWfVooBJmWWbPB1pueo3tx8w3+fcuzpxz+RLFKaPyqXO+dD
|
||||
#[test]
|
||||
#[ignore] // is too expensive
|
||||
fn test_ascii_roundtrip() {
|
||||
let (public_key, private_key) = crate::pgp::dc_pgp_create_keypair("hello").unwrap();
|
||||
let (public_key, private_key) = crate::pgp::create_keypair("hello").unwrap();
|
||||
|
||||
let s = public_key.to_armored_string(None).unwrap();
|
||||
let (public_key2, _) =
|
||||
|
||||
@@ -57,7 +57,7 @@ pub mod qr;
|
||||
pub mod securejoin;
|
||||
mod smtp;
|
||||
pub mod sql;
|
||||
mod stock;
|
||||
pub mod stock;
|
||||
mod token;
|
||||
#[macro_use]
|
||||
mod wrapmime;
|
||||
|
||||
@@ -3,6 +3,7 @@ use quick_xml;
|
||||
use quick_xml::events::{BytesEnd, BytesStart, BytesText};
|
||||
|
||||
use crate::chat;
|
||||
use crate::config::Config;
|
||||
use crate::constants::*;
|
||||
use crate::context::*;
|
||||
use crate::dc_tools::*;
|
||||
@@ -62,7 +63,7 @@ impl Kml {
|
||||
|
||||
pub fn parse(context: &Context, content: impl AsRef<str>) -> Result<Self, Error> {
|
||||
ensure!(
|
||||
content.as_ref().len() <= (1 * 1024 * 1024),
|
||||
content.as_ref().len() <= (1024 * 1024),
|
||||
"A kml-files with {} bytes is larger than reasonably expected.",
|
||||
content.as_ref().len()
|
||||
);
|
||||
@@ -218,7 +219,7 @@ pub fn send_locations_to_chat(context: &Context, chat_id: u32, seconds: i64) {
|
||||
msg.text =
|
||||
Some(context.stock_system_msg(StockMessage::MsgLocationEnabled, "", "", 0));
|
||||
msg.param.set_int(Param::Cmd, 8);
|
||||
chat::send_msg(context, chat_id, &mut msg).unwrap();
|
||||
chat::send_msg(context, chat_id, &mut msg).unwrap_or_default();
|
||||
} else if 0 == seconds && is_sending_locations_before {
|
||||
let stock_str =
|
||||
context.stock_system_msg(StockMessage::MsgLocationDisabled, "", "", 0);
|
||||
@@ -359,7 +360,7 @@ pub fn get_range(
|
||||
}
|
||||
|
||||
fn is_marker(txt: &str) -> bool {
|
||||
txt.len() == 1 && txt.chars().next().unwrap() != ' '
|
||||
txt.len() == 1 && !txt.starts_with(' ')
|
||||
}
|
||||
|
||||
pub fn delete_all(context: &Context) -> Result<(), Error> {
|
||||
@@ -375,8 +376,7 @@ pub fn get_kml(context: &Context, chat_id: u32) -> Result<(String, u32), Error>
|
||||
let mut last_added_location_id = 0;
|
||||
|
||||
let self_addr = context
|
||||
.sql
|
||||
.get_config(context, "configured_addr")
|
||||
.get_config(Config::ConfiguredAddr)
|
||||
.unwrap_or_default();
|
||||
|
||||
let (locations_send_begin, locations_send_until, locations_last_sent) = context.sql.query_row(
|
||||
@@ -615,7 +615,7 @@ pub fn job_do_DC_JOB_MAYBE_SEND_LOCATIONS(context: &Context, _job: &Job) {
|
||||
|
||||
for (chat_id, mut msg) in msgs.into_iter() {
|
||||
// TODO: better error handling
|
||||
chat::send_msg(context, chat_id as u32, &mut msg).unwrap();
|
||||
chat::send_msg(context, chat_id as u32, &mut msg).unwrap_or_default();
|
||||
}
|
||||
}
|
||||
if 0 != continue_streaming {
|
||||
|
||||
@@ -4,6 +4,22 @@ use std::fmt;
|
||||
use crate::context::Context;
|
||||
use crate::error::Error;
|
||||
|
||||
#[derive(Copy, Clone, Debug, Display, FromPrimitive)]
|
||||
#[repr(i32)]
|
||||
#[strum(serialize_all = "snake_case")]
|
||||
pub enum CertificateChecks {
|
||||
Automatic = 0,
|
||||
Strict = 1,
|
||||
AcceptInvalidHostnames = 2,
|
||||
AcceptInvalidCertificates = 3,
|
||||
}
|
||||
|
||||
impl Default for CertificateChecks {
|
||||
fn default() -> Self {
|
||||
Self::Automatic
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Default, Debug)]
|
||||
pub struct LoginParam {
|
||||
pub addr: String,
|
||||
@@ -11,10 +27,14 @@ pub struct LoginParam {
|
||||
pub mail_user: String,
|
||||
pub mail_pw: String,
|
||||
pub mail_port: i32,
|
||||
/// IMAP TLS options: whether to allow invalid certificates and/or invalid hostnames
|
||||
pub imap_certificate_checks: CertificateChecks,
|
||||
pub send_server: String,
|
||||
pub send_user: String,
|
||||
pub send_pw: String,
|
||||
pub send_port: i32,
|
||||
/// SMTP TLS options: whether to allow invalid certificates and/or invalid hostnames
|
||||
pub smtp_certificate_checks: CertificateChecks,
|
||||
pub server_flags: i32,
|
||||
}
|
||||
|
||||
@@ -31,37 +51,53 @@ impl LoginParam {
|
||||
|
||||
let key = format!("{}addr", prefix);
|
||||
let addr = sql
|
||||
.get_config(context, key)
|
||||
.get_raw_config(context, key)
|
||||
.unwrap_or_default()
|
||||
.trim()
|
||||
.to_string();
|
||||
|
||||
let key = format!("{}mail_server", prefix);
|
||||
let mail_server = sql.get_config(context, key).unwrap_or_default();
|
||||
let mail_server = sql.get_raw_config(context, key).unwrap_or_default();
|
||||
|
||||
let key = format!("{}mail_port", prefix);
|
||||
let mail_port = sql.get_config_int(context, key).unwrap_or_default();
|
||||
let mail_port = sql.get_raw_config_int(context, key).unwrap_or_default();
|
||||
|
||||
let key = format!("{}mail_user", prefix);
|
||||
let mail_user = sql.get_config(context, key).unwrap_or_default();
|
||||
let mail_user = sql.get_raw_config(context, key).unwrap_or_default();
|
||||
|
||||
let key = format!("{}mail_pw", prefix);
|
||||
let mail_pw = sql.get_config(context, key).unwrap_or_default();
|
||||
let mail_pw = sql.get_raw_config(context, key).unwrap_or_default();
|
||||
|
||||
let key = format!("{}imap_certificate_checks", prefix);
|
||||
let imap_certificate_checks =
|
||||
if let Some(certificate_checks) = sql.get_raw_config_int(context, key) {
|
||||
num_traits::FromPrimitive::from_i32(certificate_checks).unwrap()
|
||||
} else {
|
||||
Default::default()
|
||||
};
|
||||
|
||||
let key = format!("{}send_server", prefix);
|
||||
let send_server = sql.get_config(context, key).unwrap_or_default();
|
||||
let send_server = sql.get_raw_config(context, key).unwrap_or_default();
|
||||
|
||||
let key = format!("{}send_port", prefix);
|
||||
let send_port = sql.get_config_int(context, key).unwrap_or_default();
|
||||
let send_port = sql.get_raw_config_int(context, key).unwrap_or_default();
|
||||
|
||||
let key = format!("{}send_user", prefix);
|
||||
let send_user = sql.get_config(context, key).unwrap_or_default();
|
||||
let send_user = sql.get_raw_config(context, key).unwrap_or_default();
|
||||
|
||||
let key = format!("{}send_pw", prefix);
|
||||
let send_pw = sql.get_config(context, key).unwrap_or_default();
|
||||
let send_pw = sql.get_raw_config(context, key).unwrap_or_default();
|
||||
|
||||
let key = format!("{}smtp_certificate_checks", prefix);
|
||||
let smtp_certificate_checks =
|
||||
if let Some(certificate_checks) = sql.get_raw_config_int(context, key) {
|
||||
num_traits::FromPrimitive::from_i32(certificate_checks).unwrap()
|
||||
} else {
|
||||
Default::default()
|
||||
};
|
||||
|
||||
let key = format!("{}server_flags", prefix);
|
||||
let server_flags = sql.get_config_int(context, key).unwrap_or_default();
|
||||
let server_flags = sql.get_raw_config_int(context, key).unwrap_or_default();
|
||||
|
||||
LoginParam {
|
||||
addr: addr.to_string(),
|
||||
@@ -69,10 +105,12 @@ impl LoginParam {
|
||||
mail_user,
|
||||
mail_pw,
|
||||
mail_port,
|
||||
imap_certificate_checks,
|
||||
send_server,
|
||||
send_user,
|
||||
send_pw,
|
||||
send_port,
|
||||
smtp_certificate_checks,
|
||||
server_flags,
|
||||
}
|
||||
}
|
||||
@@ -91,34 +129,40 @@ impl LoginParam {
|
||||
let sql = &context.sql;
|
||||
|
||||
let key = format!("{}addr", prefix);
|
||||
sql.set_config(context, key, Some(&self.addr))?;
|
||||
sql.set_raw_config(context, key, Some(&self.addr))?;
|
||||
|
||||
let key = format!("{}mail_server", prefix);
|
||||
sql.set_config(context, key, Some(&self.mail_server))?;
|
||||
sql.set_raw_config(context, key, Some(&self.mail_server))?;
|
||||
|
||||
let key = format!("{}mail_port", prefix);
|
||||
sql.set_config_int(context, key, self.mail_port)?;
|
||||
sql.set_raw_config_int(context, key, self.mail_port)?;
|
||||
|
||||
let key = format!("{}mail_user", prefix);
|
||||
sql.set_config(context, key, Some(&self.mail_user))?;
|
||||
sql.set_raw_config(context, key, Some(&self.mail_user))?;
|
||||
|
||||
let key = format!("{}mail_pw", prefix);
|
||||
sql.set_config(context, key, Some(&self.mail_pw))?;
|
||||
sql.set_raw_config(context, key, Some(&self.mail_pw))?;
|
||||
|
||||
let key = format!("{}imap_certificate_checks", prefix);
|
||||
sql.set_raw_config_int(context, key, self.imap_certificate_checks as i32)?;
|
||||
|
||||
let key = format!("{}send_server", prefix);
|
||||
sql.set_config(context, key, Some(&self.send_server))?;
|
||||
sql.set_raw_config(context, key, Some(&self.send_server))?;
|
||||
|
||||
let key = format!("{}send_port", prefix);
|
||||
sql.set_config_int(context, key, self.send_port)?;
|
||||
sql.set_raw_config_int(context, key, self.send_port)?;
|
||||
|
||||
let key = format!("{}send_user", prefix);
|
||||
sql.set_config(context, key, Some(&self.send_user))?;
|
||||
sql.set_raw_config(context, key, Some(&self.send_user))?;
|
||||
|
||||
let key = format!("{}send_pw", prefix);
|
||||
sql.set_config(context, key, Some(&self.send_pw))?;
|
||||
sql.set_raw_config(context, key, Some(&self.send_pw))?;
|
||||
|
||||
let key = format!("{}smtp_certificate_checks", prefix);
|
||||
sql.set_raw_config_int(context, key, self.smtp_certificate_checks as i32)?;
|
||||
|
||||
let key = format!("{}server_flags", prefix);
|
||||
sql.set_config_int(context, key, self.server_flags)?;
|
||||
sql.set_raw_config_int(context, key, self.server_flags)?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
@@ -133,16 +177,18 @@ impl fmt::Display for LoginParam {
|
||||
|
||||
write!(
|
||||
f,
|
||||
"{} {}:{}:{}:{} {}:{}:{}:{} {}",
|
||||
"{} imap:{}:{}:{}:{}:cert_{} smtp:{}:{}:{}:{}:cert_{} {}",
|
||||
unset_empty(&self.addr),
|
||||
unset_empty(&self.mail_user),
|
||||
if !self.mail_pw.is_empty() { pw } else { unset },
|
||||
unset_empty(&self.mail_server),
|
||||
self.mail_port,
|
||||
self.imap_certificate_checks,
|
||||
unset_empty(&self.send_user),
|
||||
if !self.send_pw.is_empty() { pw } else { unset },
|
||||
unset_empty(&self.send_server),
|
||||
self.send_port,
|
||||
self.smtp_certificate_checks,
|
||||
flags_readable,
|
||||
)
|
||||
}
|
||||
@@ -204,3 +250,41 @@ fn get_readable_flags(flags: i32) -> String {
|
||||
|
||||
res
|
||||
}
|
||||
|
||||
pub fn dc_build_tls(
|
||||
certificate_checks: CertificateChecks,
|
||||
) -> Result<native_tls::TlsConnector, native_tls::Error> {
|
||||
let mut tls_builder = native_tls::TlsConnector::builder();
|
||||
match certificate_checks {
|
||||
CertificateChecks::Automatic => {
|
||||
// Same as AcceptInvalidCertificates for now.
|
||||
// TODO: use provider database when it becomes available
|
||||
tls_builder
|
||||
.danger_accept_invalid_hostnames(true)
|
||||
.danger_accept_invalid_certs(true)
|
||||
}
|
||||
CertificateChecks::Strict => &mut tls_builder,
|
||||
CertificateChecks::AcceptInvalidHostnames => {
|
||||
tls_builder.danger_accept_invalid_hostnames(true)
|
||||
}
|
||||
CertificateChecks::AcceptInvalidCertificates => tls_builder
|
||||
.danger_accept_invalid_hostnames(true)
|
||||
.danger_accept_invalid_certs(true),
|
||||
}
|
||||
.build()
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
|
||||
#[test]
|
||||
fn test_certificate_checks_display() {
|
||||
use std::string::ToString;
|
||||
|
||||
assert_eq!(
|
||||
"accept_invalid_hostnames".to_string(),
|
||||
CertificateChecks::AcceptInvalidHostnames.to_string()
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,5 +1,4 @@
|
||||
use std::path::{Path, PathBuf};
|
||||
use std::ptr;
|
||||
|
||||
use deltachat_derive::{FromSql, ToSql};
|
||||
|
||||
@@ -107,6 +106,7 @@ impl Message {
|
||||
msg.hidden = row.get(18)?;
|
||||
msg.location_id = row.get(19)?;
|
||||
msg.chat_blocked = row.get::<_, Option<Blocked>>(20)?.unwrap_or_default();
|
||||
|
||||
Ok(msg)
|
||||
})
|
||||
}
|
||||
@@ -339,23 +339,10 @@ impl Message {
|
||||
}
|
||||
|
||||
if let Some(filename) = self.get_file(context) {
|
||||
if let Ok(mut buf) = dc_read_file(context, filename) {
|
||||
unsafe {
|
||||
// just a pointer inside buf, MUST NOT be free()'d
|
||||
let mut buf_headerline = String::default();
|
||||
// just a pointer inside buf, MUST NOT be free()'d
|
||||
let mut buf_setupcodebegin = ptr::null();
|
||||
|
||||
if dc_split_armored_data(
|
||||
buf.as_mut_ptr().cast(),
|
||||
&mut buf_headerline,
|
||||
&mut buf_setupcodebegin,
|
||||
ptr::null_mut(),
|
||||
ptr::null_mut(),
|
||||
) && buf_headerline == "-----BEGIN PGP MESSAGE-----"
|
||||
&& !buf_setupcodebegin.is_null()
|
||||
{
|
||||
return Some(to_string(buf_setupcodebegin));
|
||||
if let Ok(ref buf) = dc_read_file(context, filename) {
|
||||
if let Ok((typ, headers, _)) = split_armored_data(buf) {
|
||||
if typ == pgp::armor::BlockType::Message {
|
||||
return headers.get(crate::pgp::HEADER_SETUPCODE).cloned();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -494,12 +481,10 @@ impl Lot {
|
||||
} else {
|
||||
self.text1 = None;
|
||||
}
|
||||
} else if let Some(contact) = contact {
|
||||
self.text1 = Some(contact.get_first_name().into());
|
||||
} else {
|
||||
if let Some(contact) = contact {
|
||||
self.text1 = Some(contact.get_first_name().into());
|
||||
} else {
|
||||
self.text1 = None;
|
||||
}
|
||||
self.text1 = None;
|
||||
}
|
||||
self.text1_meaning = Meaning::Text1Username;
|
||||
}
|
||||
@@ -526,7 +511,7 @@ pub fn get_msg_info(context: &Context, msg_id: u32) -> String {
|
||||
return ret;
|
||||
}
|
||||
|
||||
let msg = msg.unwrap();
|
||||
let msg = msg.unwrap_or_default();
|
||||
|
||||
let rawtxt: Option<String> = context.sql.query_get_value(
|
||||
context,
|
||||
@@ -538,7 +523,7 @@ pub fn get_msg_info(context: &Context, msg_id: u32) -> String {
|
||||
ret += &format!("Cannot load message #{}.", msg_id as usize);
|
||||
return ret;
|
||||
}
|
||||
let rawtxt = rawtxt.unwrap();
|
||||
let rawtxt = rawtxt.unwrap_or_default();
|
||||
let rawtxt = dc_truncate(rawtxt.trim(), 100000, false);
|
||||
|
||||
let fts = dc_timestamp_to_str(msg.get_timestamp());
|
||||
@@ -618,9 +603,8 @@ pub fn get_msg_info(context: &Context, msg_id: u32) -> String {
|
||||
}
|
||||
|
||||
ret += "\n";
|
||||
match msg.param.get(Param::Error) {
|
||||
Some(err) => ret += &format!("Error: {}", err),
|
||||
_ => {}
|
||||
if let Some(err) = msg.param.get(Param::Error) {
|
||||
ret += &format!("Error: {}", err)
|
||||
}
|
||||
|
||||
if let Some(path) = msg.get_file(context) {
|
||||
@@ -688,7 +672,7 @@ pub fn get_mime_headers(context: &Context, msg_id: u32) -> Option<String> {
|
||||
}
|
||||
|
||||
pub fn delete_msgs(context: &Context, msg_ids: &[u32]) {
|
||||
for msg_id in msg_ids.into_iter() {
|
||||
for msg_id in msg_ids.iter() {
|
||||
update_msg_chat_id(context, *msg_id, DC_CHAT_ID_TRASH);
|
||||
job_add(
|
||||
context,
|
||||
@@ -728,7 +712,7 @@ pub fn markseen_msgs(context: &Context, msg_ids: &[u32]) -> bool {
|
||||
"SELECT m.state, c.blocked FROM msgs m LEFT JOIN chats c ON c.id=m.chat_id WHERE m.id=? AND m.chat_id>9",
|
||||
|mut stmt, _| {
|
||||
let mut res = Vec::with_capacity(msg_ids.len());
|
||||
for id in msg_ids.into_iter() {
|
||||
for id in msg_ids.iter() {
|
||||
let query_res = stmt.query_row(params![*id as i32], |row| {
|
||||
Ok((row.get::<_, MessageState>(0)?, row.get::<_, Option<Blocked>>(1)?.unwrap_or_default()))
|
||||
});
|
||||
@@ -748,7 +732,7 @@ pub fn markseen_msgs(context: &Context, msg_ids: &[u32]) -> bool {
|
||||
return false;
|
||||
}
|
||||
let mut send_event = false;
|
||||
let msgs = msgs.unwrap();
|
||||
let msgs = msgs.unwrap_or_default();
|
||||
|
||||
for (id, curr_state, curr_blocked) in msgs.into_iter() {
|
||||
if curr_blocked == Blocked::Not {
|
||||
@@ -798,7 +782,7 @@ pub fn star_msgs(context: &Context, msg_ids: &[u32], star: bool) -> bool {
|
||||
context
|
||||
.sql
|
||||
.prepare("UPDATE msgs SET starred=? WHERE id=?;", |mut stmt, _| {
|
||||
for msg_id in msg_ids.into_iter() {
|
||||
for msg_id in msg_ids.iter() {
|
||||
stmt.execute(params![star as i32, *msg_id as i32])?;
|
||||
}
|
||||
Ok(())
|
||||
@@ -818,6 +802,7 @@ pub fn get_summarytext_by_raw(
|
||||
let prefix = match viewtype {
|
||||
Viewtype::Image => context.stock_str(StockMessage::Image).into_owned(),
|
||||
Viewtype::Gif => context.stock_str(StockMessage::Gif).into_owned(),
|
||||
Viewtype::Sticker => context.stock_str(StockMessage::Sticker).into_owned(),
|
||||
Viewtype::Video => context.stock_str(StockMessage::Video).into_owned(),
|
||||
Viewtype::Voice => context.stock_str(StockMessage::VoiceMessage).into_owned(),
|
||||
Viewtype::Audio | Viewtype::File => {
|
||||
@@ -836,7 +821,7 @@ pub fn get_summarytext_by_raw(
|
||||
} else {
|
||||
None
|
||||
}
|
||||
.unwrap_or("ErrFileName".to_string());
|
||||
.unwrap_or_else(|| "ErrFileName".to_string());
|
||||
|
||||
let label = context.stock_str(if viewtype == Viewtype::Audio {
|
||||
StockMessage::Audio
|
||||
@@ -984,7 +969,7 @@ pub fn mdn_from_ext(
|
||||
context.sql.execute(
|
||||
"INSERT INTO msgs_mdns (msg_id, contact_id, timestamp_sent) VALUES (?, ?, ?);",
|
||||
params![*ret_msg_id as i32, from_id as i32, timestamp_sent],
|
||||
).unwrap(); // TODO: better error handling
|
||||
).unwrap_or_default(); // TODO: better error handling
|
||||
}
|
||||
|
||||
// Normal chat? that's quite easy.
|
||||
|
||||
@@ -13,10 +13,11 @@ use mmime::mmapstring::*;
|
||||
use mmime::other::*;
|
||||
|
||||
use crate::chat::{self, Chat};
|
||||
use crate::config::Config;
|
||||
use crate::constants::*;
|
||||
use crate::contact::*;
|
||||
use crate::context::{get_version_str, Context};
|
||||
use crate::dc_mimeparser::{mailmime_find_mailimf_fields, SystemMessage};
|
||||
use crate::dc_mimeparser::SystemMessage;
|
||||
use crate::dc_strencode::*;
|
||||
use crate::dc_tools::*;
|
||||
use crate::e2ee::*;
|
||||
@@ -59,11 +60,13 @@ pub struct MimeFactory<'a> {
|
||||
|
||||
impl<'a> MimeFactory<'a> {
|
||||
fn new(context: &'a Context, msg: Message) -> Self {
|
||||
let cget = |context: &Context, name: &str| context.sql.get_config(context, name);
|
||||
MimeFactory {
|
||||
from_addr: cget(&context, "configured_addr").unwrap_or_default(),
|
||||
from_displayname: cget(&context, "displayname").unwrap_or_default(),
|
||||
selfstatus: cget(&context, "selfstatus")
|
||||
from_addr: context
|
||||
.get_config(Config::ConfiguredAddr)
|
||||
.unwrap_or_default(),
|
||||
from_displayname: context.get_config(Config::Displayname).unwrap_or_default(),
|
||||
selfstatus: context
|
||||
.get_config(Config::Selfstatus)
|
||||
.unwrap_or_else(|| context.stock_str(StockMessage::StatusLine).to_string()),
|
||||
recipients_names: Vec::with_capacity(5),
|
||||
recipients_addr: Vec::with_capacity(5),
|
||||
@@ -106,15 +109,10 @@ impl<'a> MimeFactory<'a> {
|
||||
}
|
||||
|
||||
pub fn load_mdn(context: &'a Context, msg_id: u32) -> Result<MimeFactory, Error> {
|
||||
if 0 == context
|
||||
.sql
|
||||
.get_config_int(context, "mdns_enabled")
|
||||
.unwrap_or_else(|| 1)
|
||||
{
|
||||
// MDNs not enabled - check this is late, in the job. the use may have changed its
|
||||
// choice while offline ...
|
||||
|
||||
bail!("MDNs disabled ")
|
||||
if !context.get_config_bool(Config::MdnsEnabled) {
|
||||
// MDNs not enabled - check this is late, in the job. the
|
||||
// user may have changed its choice while offline ...
|
||||
bail!("MDNs meanwhile disabled")
|
||||
}
|
||||
|
||||
let msg = Message::load_from_db(context, msg_id)?;
|
||||
@@ -141,52 +139,35 @@ impl<'a> MimeFactory<'a> {
|
||||
}
|
||||
|
||||
/*******************************************************************************
|
||||
* Render
|
||||
* Render a basic email
|
||||
******************************************************************************/
|
||||
// restrict unsafe to parts, introduce wrapmime helpers where appropriate
|
||||
// XXX restrict unsafe to parts, introduce wrapmime helpers where appropriate
|
||||
pub unsafe fn render(&mut self) -> Result<(), Error> {
|
||||
if self.loaded == Loaded::Nothing || !self.out.is_null() {
|
||||
bail!("Invalid use of mimefactory-object.");
|
||||
}
|
||||
let context = &self.context;
|
||||
let from = wrapmime::new_mailbox_list(&self.from_displayname, &self.from_addr);
|
||||
|
||||
/* create basic mail
|
||||
*************************************************************************/
|
||||
|
||||
let from: *mut mailimf_mailbox_list = mailimf_mailbox_list_new_empty();
|
||||
mailimf_mailbox_list_add(
|
||||
from,
|
||||
mailimf_mailbox_new(
|
||||
if !self.from_displayname.is_empty() {
|
||||
dc_encode_header_words(&self.from_displayname).strdup()
|
||||
} else {
|
||||
ptr::null_mut()
|
||||
},
|
||||
self.from_addr.strdup(),
|
||||
),
|
||||
);
|
||||
let mut to: *mut mailimf_address_list = ptr::null_mut();
|
||||
if !self.recipients_names.is_empty() && !self.recipients_addr.is_empty() {
|
||||
to = mailimf_address_list_new_empty();
|
||||
let name_iter = self.recipients_names.iter();
|
||||
let addr_iter = self.recipients_addr.iter();
|
||||
for (name, addr) in name_iter.zip(addr_iter) {
|
||||
mailimf_address_list_add(
|
||||
to,
|
||||
mailimf_address_new(
|
||||
MAILIMF_ADDRESS_MAILBOX as libc::c_int,
|
||||
mailimf_mailbox_new(
|
||||
if !name.is_empty() {
|
||||
dc_encode_header_words(&name).strdup()
|
||||
} else {
|
||||
ptr::null_mut()
|
||||
},
|
||||
addr.strdup(),
|
||||
),
|
||||
ptr::null_mut(),
|
||||
let to = mailimf_address_list_new_empty();
|
||||
let name_iter = self.recipients_names.iter();
|
||||
let addr_iter = self.recipients_addr.iter();
|
||||
for (name, addr) in name_iter.zip(addr_iter) {
|
||||
mailimf_address_list_add(
|
||||
to,
|
||||
mailimf_address_new(
|
||||
MAILIMF_ADDRESS_MAILBOX as libc::c_int,
|
||||
mailimf_mailbox_new(
|
||||
if !name.is_empty() {
|
||||
dc_encode_header_words(&name).strdup()
|
||||
} else {
|
||||
ptr::null_mut()
|
||||
},
|
||||
addr.strdup(),
|
||||
),
|
||||
);
|
||||
}
|
||||
ptr::null_mut(),
|
||||
),
|
||||
);
|
||||
}
|
||||
let references_list = if !self.references.is_empty() {
|
||||
dc_str_to_clist(&self.references, " ")
|
||||
@@ -198,6 +179,7 @@ impl<'a> MimeFactory<'a> {
|
||||
} else {
|
||||
ptr::null_mut()
|
||||
};
|
||||
|
||||
let imf_fields = mailimf_fields_new_with_data_all(
|
||||
mailimf_get_date(self.timestamp as i64),
|
||||
from,
|
||||
@@ -402,15 +384,8 @@ impl<'a> MimeFactory<'a> {
|
||||
&fingerprint,
|
||||
);
|
||||
}
|
||||
match msg.param.get(Param::Arg4) {
|
||||
Some(id) => {
|
||||
wrapmime::new_custom_field(
|
||||
imf_fields,
|
||||
"Secure-Join-Group",
|
||||
&id,
|
||||
);
|
||||
}
|
||||
None => {}
|
||||
if let Some(id) = msg.param.get(Param::Arg4) {
|
||||
wrapmime::new_custom_field(imf_fields, "Secure-Join-Group", &id);
|
||||
};
|
||||
}
|
||||
}
|
||||
@@ -435,6 +410,10 @@ impl<'a> MimeFactory<'a> {
|
||||
}
|
||||
}
|
||||
|
||||
if self.msg.type_0 == Viewtype::Sticker {
|
||||
wrapmime::new_custom_field(imf_fields, "Chat-Content", "sticker");
|
||||
}
|
||||
|
||||
if self.msg.type_0 == Viewtype::Voice
|
||||
|| self.msg.type_0 == Viewtype::Audio
|
||||
|| self.msg.type_0 == Viewtype::Video
|
||||
@@ -594,7 +573,6 @@ impl<'a> MimeFactory<'a> {
|
||||
wrapmime::set_body_text(mach_mime_part, &message_text2)?;
|
||||
mailmime_add_part(multipart, mach_mime_part);
|
||||
force_plaintext = DC_FP_NO_AUTOCRYPT_HEADER;
|
||||
info!(context, "sending MDM {:?}", message_text2);
|
||||
/* currently, we do not send MDNs encrypted:
|
||||
- in a multi-device-setup that is not set up properly, MDNs would disturb the communication as they
|
||||
are send automatically which may lead to spreading outdated Autocrypt headers.
|
||||
@@ -645,7 +623,7 @@ impl<'a> MimeFactory<'a> {
|
||||
);
|
||||
|
||||
/*just a pointer into mailmime structure, must not be freed*/
|
||||
let imffields_unprotected = mailmime_find_mailimf_fields(message);
|
||||
let imffields_unprotected = wrapmime::mailmime_find_mailimf_fields(message);
|
||||
ensure!(
|
||||
!imffields_unprotected.is_null(),
|
||||
"could not find mime fields"
|
||||
@@ -657,17 +635,18 @@ impl<'a> MimeFactory<'a> {
|
||||
let aheader = encrypt_helper.get_aheader().to_string();
|
||||
wrapmime::new_custom_field(imffields_unprotected, "Autocrypt", &aheader);
|
||||
}
|
||||
let mut finalized = false;
|
||||
if force_plaintext == 0 {
|
||||
finalized = encrypt_helper.try_encrypt(
|
||||
let finalized = if force_plaintext == 0 {
|
||||
encrypt_helper.try_encrypt(
|
||||
self,
|
||||
e2ee_guaranteed,
|
||||
min_verified,
|
||||
do_gossip,
|
||||
message,
|
||||
imffields_unprotected,
|
||||
)?;
|
||||
}
|
||||
)?
|
||||
} else {
|
||||
false
|
||||
};
|
||||
if !finalized {
|
||||
self.finalize_mime_message(message, false, false)?;
|
||||
}
|
||||
@@ -692,31 +671,28 @@ impl<'a> MimeFactory<'a> {
|
||||
.push(factory.from_displayname.to_string());
|
||||
factory.recipients_addr.push(factory.from_addr.to_string());
|
||||
} else {
|
||||
context
|
||||
.sql
|
||||
.query_map(
|
||||
"SELECT c.authname, c.addr \
|
||||
FROM chats_contacts cc \
|
||||
LEFT JOIN contacts c ON cc.contact_id=c.id \
|
||||
WHERE cc.chat_id=? AND cc.contact_id>9;",
|
||||
params![factory.msg.chat_id as i32],
|
||||
|row| {
|
||||
let authname: String = row.get(0)?;
|
||||
let addr: String = row.get(1)?;
|
||||
Ok((authname, addr))
|
||||
},
|
||||
|rows| {
|
||||
for row in rows {
|
||||
let (authname, addr) = row?;
|
||||
if !vec_contains_lowercase(&factory.recipients_addr, &addr) {
|
||||
factory.recipients_addr.push(addr);
|
||||
factory.recipients_names.push(authname);
|
||||
}
|
||||
context.sql.query_map(
|
||||
"SELECT c.authname, c.addr \
|
||||
FROM chats_contacts cc \
|
||||
LEFT JOIN contacts c ON cc.contact_id=c.id \
|
||||
WHERE cc.chat_id=? AND cc.contact_id>9;",
|
||||
params![factory.msg.chat_id as i32],
|
||||
|row| {
|
||||
let authname: String = row.get(0)?;
|
||||
let addr: String = row.get(1)?;
|
||||
Ok((authname, addr))
|
||||
},
|
||||
|rows| {
|
||||
for row in rows {
|
||||
let (authname, addr) = row?;
|
||||
if !vec_contains_lowercase(&factory.recipients_addr, &addr) {
|
||||
factory.recipients_addr.push(addr);
|
||||
factory.recipients_names.push(authname);
|
||||
}
|
||||
Ok(())
|
||||
},
|
||||
)
|
||||
.unwrap();
|
||||
}
|
||||
Ok(())
|
||||
},
|
||||
)?;
|
||||
|
||||
let command = factory.msg.param.get_cmd();
|
||||
let msg = &factory.msg;
|
||||
@@ -726,8 +702,7 @@ impl<'a> MimeFactory<'a> {
|
||||
let email_to_remove = msg.param.get(Param::Arg).unwrap_or_default();
|
||||
|
||||
let self_addr = context
|
||||
.sql
|
||||
.get_config(context, "configured_addr")
|
||||
.get_config(Config::ConfiguredAddr)
|
||||
.unwrap_or_default();
|
||||
|
||||
if !email_to_remove.is_empty() && email_to_remove != self_addr {
|
||||
@@ -739,10 +714,7 @@ impl<'a> MimeFactory<'a> {
|
||||
}
|
||||
if command != SystemMessage::AutocryptSetupMessage
|
||||
&& command != SystemMessage::SecurejoinMessage
|
||||
&& 0 != context
|
||||
.sql
|
||||
.get_config_int(context, "mdns_enabled")
|
||||
.unwrap_or_else(|| 1)
|
||||
&& context.get_config_bool(Config::MdnsEnabled)
|
||||
{
|
||||
factory.req_mdn = true;
|
||||
}
|
||||
@@ -923,15 +895,13 @@ fn build_body_file(
|
||||
wrapmime::append_ct_param(content, "name", &filename_encoded)?;
|
||||
|
||||
let mime_sub = mailmime_new_empty(content, mime_fields);
|
||||
let abs_path = dc_get_abs_path(context, path_filename)
|
||||
.to_c_string()
|
||||
.unwrap();
|
||||
let abs_path = dc_get_abs_path(context, path_filename).to_c_string()?;
|
||||
mailmime_set_body_file(mime_sub, dc_strdup(abs_path.as_ptr()));
|
||||
Ok((mime_sub, filename_to_send))
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) fn vec_contains_lowercase(vec: &Vec<String>, part: &str) -> bool {
|
||||
pub(crate) fn vec_contains_lowercase(vec: &[String], part: &str) -> bool {
|
||||
let partlc = part.to_lowercase();
|
||||
for cur in vec.iter() {
|
||||
if cur.to_lowercase() == partlc {
|
||||
|
||||
@@ -50,7 +50,7 @@ pub fn dc_get_oauth2_url(
|
||||
if let Some(oauth2) = Oauth2::from_address(addr) {
|
||||
if context
|
||||
.sql
|
||||
.set_config(
|
||||
.set_raw_config(
|
||||
context,
|
||||
"oauth2_pending_redirect_uri",
|
||||
Some(redirect_uri.as_ref()),
|
||||
@@ -82,17 +82,17 @@ pub fn dc_get_oauth2_access_token(
|
||||
|
||||
// read generated token
|
||||
if !regenerate && !is_expired(context) {
|
||||
let access_token = context.sql.get_config(context, "oauth2_access_token");
|
||||
let access_token = context.sql.get_raw_config(context, "oauth2_access_token");
|
||||
if access_token.is_some() {
|
||||
// success
|
||||
return access_token;
|
||||
}
|
||||
}
|
||||
|
||||
let refresh_token = context.sql.get_config(context, "oauth2_refresh_token");
|
||||
let refresh_token = context.sql.get_raw_config(context, "oauth2_refresh_token");
|
||||
let refresh_token_for = context
|
||||
.sql
|
||||
.get_config(context, "oauth2_refresh_token_for")
|
||||
.get_raw_config(context, "oauth2_refresh_token_for")
|
||||
.unwrap_or_else(|| "unset".into());
|
||||
|
||||
let (redirect_uri, token_url, update_redirect_uri_on_success) =
|
||||
@@ -101,7 +101,7 @@ pub fn dc_get_oauth2_access_token(
|
||||
(
|
||||
context
|
||||
.sql
|
||||
.get_config(context, "oauth2_pending_redirect_uri")
|
||||
.get_raw_config(context, "oauth2_pending_redirect_uri")
|
||||
.unwrap_or_else(|| "unset".into()),
|
||||
oauth2.init_token,
|
||||
true,
|
||||
@@ -114,7 +114,7 @@ pub fn dc_get_oauth2_access_token(
|
||||
(
|
||||
context
|
||||
.sql
|
||||
.get_config(context, "oauth2_redirect_uri")
|
||||
.get_raw_config(context, "oauth2_redirect_uri")
|
||||
.unwrap_or_else(|| "unset".into()),
|
||||
oauth2.refresh_token,
|
||||
false,
|
||||
@@ -159,11 +159,11 @@ pub fn dc_get_oauth2_access_token(
|
||||
if let Some(ref token) = response.refresh_token {
|
||||
context
|
||||
.sql
|
||||
.set_config(context, "oauth2_refresh_token", Some(token))
|
||||
.set_raw_config(context, "oauth2_refresh_token", Some(token))
|
||||
.ok();
|
||||
context
|
||||
.sql
|
||||
.set_config(context, "oauth2_refresh_token_for", Some(code.as_ref()))
|
||||
.set_raw_config(context, "oauth2_refresh_token_for", Some(code.as_ref()))
|
||||
.ok();
|
||||
}
|
||||
|
||||
@@ -172,7 +172,7 @@ pub fn dc_get_oauth2_access_token(
|
||||
if let Some(ref token) = response.access_token {
|
||||
context
|
||||
.sql
|
||||
.set_config(context, "oauth2_access_token", Some(token))
|
||||
.set_raw_config(context, "oauth2_access_token", Some(token))
|
||||
.ok();
|
||||
let expires_in = response
|
||||
.expires_in
|
||||
@@ -181,13 +181,13 @@ pub fn dc_get_oauth2_access_token(
|
||||
.unwrap_or_else(|| 0);
|
||||
context
|
||||
.sql
|
||||
.set_config_int64(context, "oauth2_timestamp_expires", expires_in)
|
||||
.set_raw_config_int64(context, "oauth2_timestamp_expires", expires_in)
|
||||
.ok();
|
||||
|
||||
if update_redirect_uri_on_success {
|
||||
context
|
||||
.sql
|
||||
.set_config(context, "oauth2_redirect_uri", Some(redirect_uri.as_ref()))
|
||||
.set_raw_config(context, "oauth2_redirect_uri", Some(redirect_uri.as_ref()))
|
||||
.ok();
|
||||
}
|
||||
} else {
|
||||
@@ -207,14 +207,8 @@ pub fn dc_get_oauth2_addr(
|
||||
addr: impl AsRef<str>,
|
||||
code: impl AsRef<str>,
|
||||
) -> Option<String> {
|
||||
let oauth2 = Oauth2::from_address(addr.as_ref());
|
||||
if oauth2.is_none() {
|
||||
return None;
|
||||
}
|
||||
let oauth2 = oauth2.unwrap();
|
||||
if oauth2.get_userinfo.is_none() {
|
||||
return None;
|
||||
}
|
||||
let oauth2 = Oauth2::from_address(addr.as_ref())?;
|
||||
oauth2.get_userinfo?;
|
||||
|
||||
if let Some(access_token) =
|
||||
dc_get_oauth2_access_token(context, addr.as_ref(), code.as_ref(), false)
|
||||
@@ -299,7 +293,7 @@ impl Oauth2 {
|
||||
fn is_expired(context: &Context) -> bool {
|
||||
let expire_timestamp = context
|
||||
.sql
|
||||
.get_config_int64(context, "oauth2_timestamp_expires")
|
||||
.get_raw_config_int64(context, "oauth2_timestamp_expires")
|
||||
.unwrap_or_default();
|
||||
|
||||
if expire_timestamp <= 0 {
|
||||
|
||||
62
src/param.rs
62
src/param.rs
@@ -12,65 +12,65 @@ use crate::error;
|
||||
#[repr(u8)]
|
||||
pub enum Param {
|
||||
/// For messages and jobs
|
||||
File = 'f' as u8,
|
||||
File = b'f',
|
||||
/// For Messages
|
||||
Width = 'w' as u8,
|
||||
Width = b'w',
|
||||
/// For Messages
|
||||
Height = 'h' as u8,
|
||||
Height = b'h',
|
||||
/// For Messages
|
||||
Duration = 'd' as u8,
|
||||
Duration = b'd',
|
||||
/// For Messages
|
||||
MimeType = 'm' as u8,
|
||||
MimeType = b'm',
|
||||
/// For Messages: message is encryoted, outgoing: guarantee E2EE or the message is not send
|
||||
GuranteeE2ee = 'c' as u8,
|
||||
GuranteeE2ee = 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 = 'e' as u8,
|
||||
ErroneousE2ee = b'e',
|
||||
/// For Messages: force unencrypted message, either `ForcePlaintext::AddAutocryptHeader` (1),
|
||||
/// `ForcePlaintext::NoAutocryptHeader` (2) or 0.
|
||||
ForcePlaintext = 'u' as u8,
|
||||
ForcePlaintext = b'u',
|
||||
/// For Messages
|
||||
WantsMdn = 'r' as u8,
|
||||
WantsMdn = b'r',
|
||||
/// For Messages
|
||||
Forwarded = 'a' as u8,
|
||||
Forwarded = b'a',
|
||||
/// For Messages
|
||||
Cmd = 'S' as u8,
|
||||
Cmd = b'S',
|
||||
/// For Messages
|
||||
Arg = 'E' as u8,
|
||||
Arg = b'E',
|
||||
/// For Messages
|
||||
Arg2 = 'F' as u8,
|
||||
Arg2 = b'F',
|
||||
/// For Messages
|
||||
Arg3 = 'G' as u8,
|
||||
Arg3 = b'G',
|
||||
/// For Messages
|
||||
Arg4 = 'H' as u8,
|
||||
Arg4 = b'H',
|
||||
/// For Messages
|
||||
Error = 'L' as u8,
|
||||
Error = b'L',
|
||||
/// For Messages: space-separated list of messaged IDs of forwarded copies.
|
||||
PrepForwards = 'P' as u8,
|
||||
PrepForwards = b'P',
|
||||
/// For Jobs
|
||||
SetLatitude = 'l' as u8,
|
||||
SetLatitude = b'l',
|
||||
/// For Jobs
|
||||
SetLongitude = 'n' as u8,
|
||||
SetLongitude = b'n',
|
||||
/// For Jobs
|
||||
ServerFolder = 'Z' as u8,
|
||||
ServerFolder = b'Z',
|
||||
/// For Jobs
|
||||
ServerUid = 'z' as u8,
|
||||
ServerUid = b'z',
|
||||
/// For Jobs
|
||||
AlsoMove = 'M' as u8,
|
||||
AlsoMove = b'M',
|
||||
/// For Jobs: space-separated list of message recipients
|
||||
Recipients = 'R' as u8,
|
||||
Recipients = b'R',
|
||||
// For Groups
|
||||
Unpromoted = 'U' as u8,
|
||||
Unpromoted = b'U',
|
||||
// For Groups and Contacts
|
||||
ProfileImage = 'i' as u8,
|
||||
ProfileImage = b'i',
|
||||
// For Chats
|
||||
Selftalk = 'K' as u8,
|
||||
Selftalk = b'K',
|
||||
// For QR
|
||||
Auth = 's' as u8,
|
||||
Auth = b's',
|
||||
// For QR
|
||||
GroupId = 'x' as u8,
|
||||
GroupId = b'x',
|
||||
// For QR
|
||||
GroupName = 'g' as u8,
|
||||
GroupName = b'g',
|
||||
}
|
||||
|
||||
/// Possible values for `Param::ForcePlaintext`.
|
||||
@@ -122,8 +122,8 @@ impl str::FromStr for Params {
|
||||
ensure!(key.is_some(), "Missing key");
|
||||
ensure!(value.is_some(), "Missing value");
|
||||
|
||||
let key = key.unwrap().trim();
|
||||
let value = value.unwrap().trim();
|
||||
let key = key.unwrap_or_default().trim();
|
||||
let value = value.unwrap_or_default().trim();
|
||||
|
||||
if let Some(key) = Param::from_u8(key.as_bytes()[0]) {
|
||||
inner.insert(key, value.to_string());
|
||||
|
||||
@@ -400,7 +400,8 @@ impl<'a> Peerstate<'a> {
|
||||
&self.verified_key_fingerprint,
|
||||
&self.addr,
|
||||
],
|
||||
)?
|
||||
)?;
|
||||
reset_gossiped_timestamp(self.context, 0);
|
||||
} else if self.to_save == Some(ToSave::Timestamps) {
|
||||
sql::execute(
|
||||
self.context,
|
||||
@@ -416,10 +417,6 @@ impl<'a> Peerstate<'a> {
|
||||
)?;
|
||||
}
|
||||
|
||||
if self.to_save == Some(ToSave::All) || create {
|
||||
reset_gossiped_timestamp(self.context, 0);
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
@@ -475,7 +472,7 @@ mod tests {
|
||||
"failed to save to db"
|
||||
);
|
||||
|
||||
let peerstate_new = Peerstate::from_addr(&ctx.ctx, &ctx.ctx.sql, addr.into())
|
||||
let peerstate_new = Peerstate::from_addr(&ctx.ctx, &ctx.ctx.sql, addr)
|
||||
.expect("failed to load peerstate from db");
|
||||
|
||||
// clear to_save, as that is not persissted
|
||||
@@ -487,6 +484,44 @@ mod tests {
|
||||
assert_eq!(peerstate, peerstate_new2);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_peerstate_double_create() {
|
||||
let ctx = crate::test_utils::dummy_context();
|
||||
let addr = "hello@mail.com";
|
||||
|
||||
let pub_key = crate::key::Key::from_base64(
|
||||
include_str!("../test-data/key/public.asc"),
|
||||
KeyType::Public,
|
||||
)
|
||||
.unwrap();
|
||||
|
||||
let peerstate = Peerstate {
|
||||
context: &ctx.ctx,
|
||||
addr: Some(addr.into()),
|
||||
last_seen: 10,
|
||||
last_seen_autocrypt: 11,
|
||||
prefer_encrypt: EncryptPreference::Mutual,
|
||||
public_key: Some(pub_key.clone()),
|
||||
public_key_fingerprint: Some(pub_key.fingerprint()),
|
||||
gossip_key: None,
|
||||
gossip_timestamp: 12,
|
||||
gossip_key_fingerprint: None,
|
||||
verified_key: None,
|
||||
verified_key_fingerprint: None,
|
||||
to_save: Some(ToSave::All),
|
||||
degrade_event: None,
|
||||
};
|
||||
|
||||
assert!(
|
||||
peerstate.save_to_db(&ctx.ctx.sql, true).is_ok(),
|
||||
"failed to save"
|
||||
);
|
||||
assert!(
|
||||
peerstate.save_to_db(&ctx.ctx.sql, true).is_ok(),
|
||||
"double-call with create failed"
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_peerstate_with_empty_gossip_key_save_to_db() {
|
||||
let ctx = crate::test_utils::dummy_context();
|
||||
@@ -520,7 +555,7 @@ mod tests {
|
||||
"failed to save"
|
||||
);
|
||||
|
||||
let peerstate_new = Peerstate::from_addr(&ctx.ctx, &ctx.ctx.sql, addr.into())
|
||||
let peerstate_new = Peerstate::from_addr(&ctx.ctx, &ctx.ctx.sql, addr)
|
||||
.expect("failed to load peerstate from db");
|
||||
|
||||
// clear to_save, as that is not persissted
|
||||
|
||||
193
src/pgp.rs
193
src/pgp.rs
@@ -1,9 +1,8 @@
|
||||
use std::collections::HashSet;
|
||||
use std::collections::{BTreeMap, HashSet};
|
||||
use std::convert::TryInto;
|
||||
use std::io::Cursor;
|
||||
use std::ptr;
|
||||
|
||||
use libc::{strchr, strlen, strncmp, strspn, strstr};
|
||||
use pgp::armor::BlockType;
|
||||
use pgp::composed::{
|
||||
Deserializable, KeyType as PgpKeyType, Message, SecretKeyParamsBuilder, SignedPublicKey,
|
||||
SignedSecretKey, SubkeyParamsBuilder,
|
||||
@@ -12,126 +11,43 @@ use pgp::crypto::{HashAlgorithm, SymmetricKeyAlgorithm};
|
||||
use pgp::types::{CompressionAlgorithm, KeyTrait, SecretKeyTrait, StringToKey};
|
||||
use rand::thread_rng;
|
||||
|
||||
use crate::dc_tools::*;
|
||||
use crate::error::Error;
|
||||
use crate::key::*;
|
||||
use crate::keyring::*;
|
||||
|
||||
pub unsafe fn dc_split_armored_data(
|
||||
buf: *mut libc::c_char,
|
||||
ret_headerline: *mut String,
|
||||
ret_setupcodebegin: *mut *const libc::c_char,
|
||||
ret_preferencrypt: *mut *const libc::c_char,
|
||||
ret_base64: *mut *const libc::c_char,
|
||||
) -> bool {
|
||||
let mut success = false;
|
||||
let mut line_chars: libc::size_t = 0;
|
||||
let mut line: *mut libc::c_char = buf;
|
||||
let mut p1: *mut libc::c_char = buf;
|
||||
let mut p2: *mut libc::c_char;
|
||||
let mut headerline: *mut libc::c_char = ptr::null_mut();
|
||||
let mut base64: *mut libc::c_char = ptr::null_mut();
|
||||
if !ret_setupcodebegin.is_null() {
|
||||
*ret_setupcodebegin = ptr::null_mut();
|
||||
}
|
||||
if !ret_preferencrypt.is_null() {
|
||||
*ret_preferencrypt = ptr::null();
|
||||
}
|
||||
if !ret_base64.is_null() {
|
||||
*ret_base64 = ptr::null();
|
||||
}
|
||||
if !buf.is_null() {
|
||||
dc_remove_cr_chars(buf);
|
||||
while 0 != *p1 {
|
||||
if *p1 as libc::c_int == '\n' as i32 {
|
||||
*line.offset(line_chars as isize) = 0i32 as libc::c_char;
|
||||
if headerline.is_null() {
|
||||
dc_trim(line);
|
||||
if strncmp(
|
||||
line,
|
||||
b"-----BEGIN \x00" as *const u8 as *const libc::c_char,
|
||||
1,
|
||||
) == 0i32
|
||||
&& strncmp(
|
||||
&mut *line.offset(strlen(line).wrapping_sub(5) as isize),
|
||||
b"-----\x00" as *const u8 as *const libc::c_char,
|
||||
5,
|
||||
) == 0i32
|
||||
{
|
||||
headerline = line;
|
||||
*ret_headerline = as_str(headerline).to_string();
|
||||
}
|
||||
} else if strspn(line, b"\t\r\n \x00" as *const u8 as *const libc::c_char)
|
||||
== strlen(line)
|
||||
{
|
||||
base64 = p1.offset(1isize);
|
||||
break;
|
||||
} else {
|
||||
p2 = strchr(line, ':' as i32);
|
||||
if p2.is_null() {
|
||||
*line.offset(line_chars as isize) = '\n' as i32 as libc::c_char;
|
||||
base64 = line;
|
||||
break;
|
||||
} else {
|
||||
*p2 = 0i32 as libc::c_char;
|
||||
dc_trim(line);
|
||||
if strcasecmp(
|
||||
line,
|
||||
b"Passphrase-Begin\x00" as *const u8 as *const libc::c_char,
|
||||
) == 0i32
|
||||
{
|
||||
p2 = p2.offset(1isize);
|
||||
dc_trim(p2);
|
||||
if !ret_setupcodebegin.is_null() {
|
||||
*ret_setupcodebegin = p2
|
||||
}
|
||||
} else if strcasecmp(
|
||||
line,
|
||||
b"Autocrypt-Prefer-Encrypt\x00" as *const u8 as *const libc::c_char,
|
||||
) == 0i32
|
||||
{
|
||||
p2 = p2.offset(1isize);
|
||||
dc_trim(p2);
|
||||
if !ret_preferencrypt.is_null() {
|
||||
*ret_preferencrypt = p2
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
p1 = p1.offset(1isize);
|
||||
line = p1;
|
||||
line_chars = 0;
|
||||
} else {
|
||||
p1 = p1.offset(1isize);
|
||||
line_chars = line_chars.wrapping_add(1)
|
||||
}
|
||||
}
|
||||
if !(headerline.is_null() || base64.is_null()) {
|
||||
/* now, line points to beginning of base64 data, search end */
|
||||
/*the trailing space makes sure, this is not a normal base64 sequence*/
|
||||
p1 = strstr(base64, b"-----END \x00" as *const u8 as *const libc::c_char);
|
||||
if !(p1.is_null()
|
||||
|| strncmp(
|
||||
p1.offset(9isize),
|
||||
headerline.offset(11isize),
|
||||
strlen(headerline.offset(11isize)),
|
||||
) != 0i32)
|
||||
{
|
||||
*p1 = 0i32 as libc::c_char;
|
||||
dc_trim(base64);
|
||||
if !ret_base64.is_null() {
|
||||
*ret_base64 = base64
|
||||
}
|
||||
success = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
pub const HEADER_AUTOCRYPT: &str = "autocrypt-prefer-encrypt";
|
||||
pub const HEADER_SETUPCODE: &str = "passphrase-begin";
|
||||
|
||||
success
|
||||
/// Split data from PGP Armored Data as defined in https://tools.ietf.org/html/rfc4880#section-6.2.
|
||||
///
|
||||
/// Returns (type, headers, base64 encoded body).
|
||||
pub fn split_armored_data(
|
||||
buf: &[u8],
|
||||
) -> Result<(BlockType, BTreeMap<String, String>, Vec<u8>), Error> {
|
||||
use std::io::Read;
|
||||
|
||||
let cursor = Cursor::new(buf);
|
||||
let mut dearmor = pgp::armor::Dearmor::new(cursor);
|
||||
|
||||
let mut bytes = Vec::with_capacity(buf.len());
|
||||
|
||||
dearmor.read_to_end(&mut bytes)?;
|
||||
ensure!(dearmor.typ.is_some(), "Failed to parse type");
|
||||
|
||||
let typ = dearmor.typ.unwrap();
|
||||
|
||||
// normalize headers
|
||||
let headers = dearmor
|
||||
.headers
|
||||
.into_iter()
|
||||
.map(|(key, value)| (key.trim().to_lowercase(), value.trim().to_string()))
|
||||
.collect();
|
||||
|
||||
Ok((typ, headers, bytes))
|
||||
}
|
||||
|
||||
/// Create a new key pair.
|
||||
pub fn dc_pgp_create_keypair(addr: impl AsRef<str>) -> Option<(Key, Key)> {
|
||||
pub fn create_keypair(addr: impl AsRef<str>) -> Option<(Key, Key)> {
|
||||
let user_id = format!("<{}>", addr.as_ref());
|
||||
|
||||
let key_params = SecretKeyParamsBuilder::default()
|
||||
@@ -181,7 +97,7 @@ pub fn dc_pgp_create_keypair(addr: impl AsRef<str>) -> Option<(Key, Key)> {
|
||||
Some((Key::Public(public_key), Key::Secret(private_key)))
|
||||
}
|
||||
|
||||
pub fn dc_pgp_pk_encrypt(
|
||||
pub fn pk_encrypt(
|
||||
plain: &[u8],
|
||||
public_keys_for_encryption: &Keyring,
|
||||
private_key_for_signing: Option<&Key>,
|
||||
@@ -189,7 +105,7 @@ pub fn dc_pgp_pk_encrypt(
|
||||
let lit_msg = Message::new_literal_bytes("", plain);
|
||||
let pkeys: Vec<&SignedPublicKey> = public_keys_for_encryption
|
||||
.keys()
|
||||
.into_iter()
|
||||
.iter()
|
||||
.filter_map(|key| {
|
||||
let k: &Key = &key;
|
||||
k.try_into().ok()
|
||||
@@ -218,7 +134,7 @@ pub fn dc_pgp_pk_encrypt(
|
||||
Ok(encoded_msg)
|
||||
}
|
||||
|
||||
pub fn dc_pgp_pk_decrypt(
|
||||
pub fn pk_decrypt(
|
||||
ctext: &[u8],
|
||||
private_keys_for_decryption: &Keyring,
|
||||
public_keys_for_validation: &Keyring,
|
||||
@@ -267,7 +183,7 @@ pub fn dc_pgp_pk_decrypt(
|
||||
}
|
||||
|
||||
/// Symmetric encryption.
|
||||
pub fn dc_pgp_symm_encrypt(passphrase: &str, plain: &[u8]) -> Result<String, Error> {
|
||||
pub fn symm_encrypt(passphrase: &str, plain: &[u8]) -> Result<String, Error> {
|
||||
let mut rng = thread_rng();
|
||||
let lit_msg = Message::new_literal_bytes("", plain);
|
||||
|
||||
@@ -281,8 +197,11 @@ pub fn dc_pgp_symm_encrypt(passphrase: &str, plain: &[u8]) -> Result<String, Err
|
||||
}
|
||||
|
||||
/// Symmetric decryption.
|
||||
pub fn dc_pgp_symm_decrypt(passphrase: &str, ctext: &[u8]) -> Result<Vec<u8>, Error> {
|
||||
let enc_msg = Message::from_bytes(Cursor::new(ctext))?;
|
||||
pub fn symm_decrypt<T: std::io::Read + std::io::Seek>(
|
||||
passphrase: &str,
|
||||
ctext: T,
|
||||
) -> Result<Vec<u8>, Error> {
|
||||
let (enc_msg, _) = Message::from_armor_single(ctext)?;
|
||||
let decryptor = enc_msg.decrypt_with_password(|| passphrase.into())?;
|
||||
|
||||
let msgs = decryptor.collect::<Result<Vec<_>, _>>()?;
|
||||
@@ -293,3 +212,35 @@ pub fn dc_pgp_symm_decrypt(passphrase: &str, ctext: &[u8]) -> Result<Vec<u8>, Er
|
||||
None => bail!("Decrypted message is empty"),
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
|
||||
#[test]
|
||||
fn test_split_armored_data_1() {
|
||||
let (typ, _headers, base64) = split_armored_data(
|
||||
b"-----BEGIN PGP MESSAGE-----\nNoVal:\n\naGVsbG8gd29ybGQ=\n-----END PGP MESSAGE----",
|
||||
)
|
||||
.unwrap();
|
||||
|
||||
assert_eq!(typ, BlockType::Message);
|
||||
assert!(!base64.is_empty());
|
||||
assert_eq!(
|
||||
std::string::String::from_utf8(base64).unwrap(),
|
||||
"hello world"
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_split_armored_data_2() {
|
||||
let (typ, headers, base64) = split_armored_data(
|
||||
b"-----BEGIN PGP PRIVATE KEY BLOCK-----\nAutocrypt-Prefer-Encrypt: mutual \n\naGVsbG8gd29ybGQ=\n-----END PGP PRIVATE KEY BLOCK-----"
|
||||
)
|
||||
.unwrap();
|
||||
|
||||
assert_eq!(typ, BlockType::PrivateKey);
|
||||
assert!(!base64.is_empty());
|
||||
assert_eq!(headers.get(HEADER_AUTOCRYPT), Some(&"mutual".to_string()));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -3,7 +3,6 @@ use percent_encoding::{utf8_percent_encode, AsciiSet, NON_ALPHANUMERIC};
|
||||
use crate::aheader::EncryptPreference;
|
||||
use crate::chat::{self, Chat};
|
||||
use crate::config::*;
|
||||
use crate::configure::*;
|
||||
use crate::constants::*;
|
||||
use crate::contact::*;
|
||||
use crate::context::Context;
|
||||
@@ -82,10 +81,7 @@ pub fn dc_get_securejoin_qr(context: &Context, group_chat_id: u32) -> Option<Str
|
||||
}
|
||||
};
|
||||
|
||||
let self_name = context
|
||||
.sql
|
||||
.get_config(context, "displayname")
|
||||
.unwrap_or_default();
|
||||
let self_name = context.get_config(Config::Displayname).unwrap_or_default();
|
||||
|
||||
fingerprint = match get_self_fingerprint(context) {
|
||||
Some(fp) => fp,
|
||||
@@ -160,7 +156,7 @@ pub fn dc_join_securejoin(context: &Context, qr: &str) -> u32 {
|
||||
bob.qr_scan = None;
|
||||
|
||||
if ongoing_allocated {
|
||||
dc_free_ongoing(context);
|
||||
context.free_ongoing();
|
||||
}
|
||||
ret_chat_id as u32
|
||||
};
|
||||
@@ -173,7 +169,7 @@ pub fn dc_join_securejoin(context: &Context, qr: &str) -> u32 {
|
||||
|
||||
info!(context, "Requesting secure-join ...",);
|
||||
ensure_secret_key_exists(context).ok();
|
||||
if !dc_alloc_ongoing(context) {
|
||||
if !context.alloc_ongoing() {
|
||||
return cleanup(&context, contact_chat_id, false, join_vg);
|
||||
}
|
||||
let qr_scan = check_qr(context, &qr);
|
||||
@@ -187,7 +183,7 @@ pub fn dc_join_securejoin(context: &Context, qr: &str) -> u32 {
|
||||
error!(context, "Unknown contact.",);
|
||||
return cleanup(&context, contact_chat_id, true, join_vg);
|
||||
}
|
||||
if check_exit(context) {
|
||||
if context.shall_stop_ongoing() {
|
||||
return cleanup(&context, contact_chat_id, true, join_vg);
|
||||
}
|
||||
join_vg = qr_scan.get_state() == LotState::QrAskVerifyGroup;
|
||||
@@ -213,7 +209,7 @@ pub fn dc_join_securejoin(context: &Context, qr: &str) -> u32 {
|
||||
info!(context, "Taking protocol shortcut.");
|
||||
context.bob.write().unwrap().expects = DC_VC_CONTACT_CONFIRM;
|
||||
joiner_progress!(context, chat_id_2_contact_id(context, contact_chat_id), 400);
|
||||
let own_fingerprint = get_self_fingerprint(context).unwrap();
|
||||
let own_fingerprint = get_self_fingerprint(context).unwrap_or_default();
|
||||
send_handshake_msg(
|
||||
context,
|
||||
contact_chat_id,
|
||||
@@ -243,21 +239,12 @@ pub fn dc_join_securejoin(context: &Context, qr: &str) -> u32 {
|
||||
}
|
||||
|
||||
// Bob -> Alice
|
||||
while !check_exit(&context) {
|
||||
while !context.shall_stop_ongoing() {
|
||||
std::thread::sleep(std::time::Duration::new(0, 3_000_000));
|
||||
}
|
||||
cleanup(&context, contact_chat_id, true, join_vg)
|
||||
}
|
||||
|
||||
fn check_exit(context: &Context) -> bool {
|
||||
context
|
||||
.running_state
|
||||
.clone()
|
||||
.read()
|
||||
.unwrap()
|
||||
.shall_stop_ongoing
|
||||
}
|
||||
|
||||
fn send_handshake_msg(
|
||||
context: &Context,
|
||||
contact_chat_id: u32,
|
||||
@@ -294,7 +281,7 @@ fn send_handshake_msg(
|
||||
msg.param.set_int(Param::GuranteeE2ee, 1);
|
||||
}
|
||||
// TODO. handle cleanup on error
|
||||
chat::send_msg(context, contact_chat_id, &mut msg).unwrap();
|
||||
chat::send_msg(context, contact_chat_id, &mut msg).unwrap_or_default();
|
||||
}
|
||||
|
||||
fn chat_id_2_contact_id(context: &Context, contact_chat_id: u32) -> u32 {
|
||||
@@ -639,7 +626,7 @@ pub fn handle_securejoin_handshake(
|
||||
|
||||
fn end_bobs_joining(context: &Context, status: libc::c_int) {
|
||||
context.bob.write().unwrap().status = status;
|
||||
dc_stop_ongoing_process(context);
|
||||
context.stop_ongoing();
|
||||
}
|
||||
|
||||
fn secure_connection_established(context: &Context, contact_chat_id: u32) {
|
||||
@@ -678,7 +665,9 @@ fn mark_peer_as_verified(context: &Context, fingerprint: impl AsRef<str>) -> Res
|
||||
if peerstate.set_verified(1, fingerprint.as_ref(), 2) {
|
||||
peerstate.prefer_encrypt = EncryptPreference::Mutual;
|
||||
peerstate.to_save = Some(ToSave::All);
|
||||
peerstate.save_to_db(&context.sql, false).unwrap();
|
||||
peerstate
|
||||
.save_to_db(&context.sql, false)
|
||||
.unwrap_or_default();
|
||||
return Ok(());
|
||||
}
|
||||
}
|
||||
@@ -696,7 +685,7 @@ fn encrypted_and_signed(mimeparser: &MimeParser, expected_fingerprint: impl AsRe
|
||||
if !mimeparser.encrypted {
|
||||
warn!(mimeparser.context, "Message not encrypted.",);
|
||||
false
|
||||
} else if mimeparser.signatures.len() <= 0 {
|
||||
} else if mimeparser.signatures.is_empty() {
|
||||
warn!(mimeparser.context, "Message not signed.",);
|
||||
false
|
||||
} else if expected_fingerprint.as_ref().is_empty() {
|
||||
|
||||
58
src/smtp.rs
58
src/smtp.rs
@@ -3,8 +3,9 @@ use lettre::*;
|
||||
|
||||
use crate::constants::*;
|
||||
use crate::context::Context;
|
||||
use crate::error::Error;
|
||||
use crate::events::Event;
|
||||
use crate::login_param::LoginParam;
|
||||
use crate::login_param::{dc_build_tls, LoginParam};
|
||||
use crate::oauth2::*;
|
||||
|
||||
#[derive(DebugStub)]
|
||||
@@ -14,7 +15,6 @@ pub struct Smtp {
|
||||
transport_connected: bool,
|
||||
/// Email address we are sending from.
|
||||
from: Option<EmailAddress>,
|
||||
pub error: Option<String>,
|
||||
}
|
||||
|
||||
impl Smtp {
|
||||
@@ -24,7 +24,6 @@ impl Smtp {
|
||||
transport: None,
|
||||
transport_connected: false,
|
||||
from: None,
|
||||
error: None,
|
||||
}
|
||||
}
|
||||
|
||||
@@ -69,14 +68,7 @@ impl Smtp {
|
||||
let domain = &lp.send_server;
|
||||
let port = lp.send_port as u16;
|
||||
|
||||
let tls = native_tls::TlsConnector::builder()
|
||||
// see also: https://github.com/deltachat/deltachat-core-rust/issues/203
|
||||
.danger_accept_invalid_hostnames(true)
|
||||
.danger_accept_invalid_certs(true)
|
||||
.min_protocol_version(Some(DEFAULT_TLS_PROTOCOLS[0]))
|
||||
.build()
|
||||
.unwrap();
|
||||
|
||||
let tls = dc_build_tls(lp.smtp_certificate_checks).unwrap();
|
||||
let tls_parameters = ClientTlsParameters::new(domain.to_string(), tls);
|
||||
|
||||
let creds = if 0 != lp.server_flags & (DC_LP_AUTH_OAUTH2 as i32) {
|
||||
@@ -89,7 +81,10 @@ impl Smtp {
|
||||
}
|
||||
let user = &lp.send_user;
|
||||
|
||||
lettre::smtp::authentication::Credentials::new(user.to_string(), access_token.unwrap())
|
||||
lettre::smtp::authentication::Credentials::new(
|
||||
user.to_string(),
|
||||
access_token.unwrap_or_default(),
|
||||
)
|
||||
} else {
|
||||
// plain
|
||||
let user = lp.send_user.clone();
|
||||
@@ -125,37 +120,50 @@ impl Smtp {
|
||||
}
|
||||
}
|
||||
|
||||
/// SMTP-Send a prepared mail to recipients.
|
||||
/// returns boolean whether send was successful.
|
||||
pub fn send<'a>(
|
||||
&mut self,
|
||||
context: &Context,
|
||||
recipients: Vec<EmailAddress>,
|
||||
body: Vec<u8>,
|
||||
) -> usize {
|
||||
message: Vec<u8>,
|
||||
) -> Result<(), Error> {
|
||||
let message_len = message.len();
|
||||
|
||||
let recipients_display = recipients
|
||||
.iter()
|
||||
.map(|x| format!("{}", x))
|
||||
.collect::<Vec<String>>()
|
||||
.join(",");
|
||||
|
||||
if let Some(ref mut transport) = self.transport {
|
||||
let envelope = Envelope::new(self.from.clone(), recipients).expect("invalid envelope");
|
||||
let envelope = Envelope::new(self.from.clone(), recipients);
|
||||
ensure!(envelope.is_ok(), "internal smtp-message construction fail");
|
||||
let envelope = envelope.unwrap();
|
||||
let mail = SendableEmail::new(
|
||||
envelope,
|
||||
"mail-id".into(), // TODO: random id
|
||||
body,
|
||||
message,
|
||||
);
|
||||
|
||||
match transport.send(mail) {
|
||||
Ok(_) => {
|
||||
context.call_cb(Event::SmtpMessageSent(
|
||||
"Message was sent to SMTP server".into(),
|
||||
));
|
||||
context.call_cb(Event::SmtpMessageSent(format!(
|
||||
"Message len={} was smtp-sent to {}",
|
||||
message_len, recipients_display
|
||||
)));
|
||||
self.transport_connected = true;
|
||||
1
|
||||
Ok(())
|
||||
}
|
||||
Err(err) => {
|
||||
warn!(context, "SMTP failed to send message: {}", err);
|
||||
self.error = Some(format!("{}", err));
|
||||
0
|
||||
bail!("SMTP failed len={}: error: {}", message_len, err);
|
||||
}
|
||||
}
|
||||
} else {
|
||||
// TODO: log error
|
||||
0
|
||||
bail!(
|
||||
"uh? SMTP has no transport, failed to send to {:?}",
|
||||
recipients_display
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
138
src/sql.rs
138
src/sql.rs
@@ -1,5 +1,6 @@
|
||||
use std::collections::HashSet;
|
||||
use std::sync::{Arc, RwLock};
|
||||
use std::time::Duration;
|
||||
|
||||
use rusqlite::{Connection, OpenFlags, Statement, NO_PARAMS};
|
||||
use thread_local_object::ThreadLocal;
|
||||
@@ -68,6 +69,16 @@ impl Sql {
|
||||
let res = match &*self.pool.read().unwrap() {
|
||||
Some(pool) => {
|
||||
let conn = pool.get()?;
|
||||
|
||||
// Only one process can make changes to the database at one time.
|
||||
// busy_timeout defines, that if a seconds process wants write access,
|
||||
// this second process will wait some milliseconds
|
||||
// and try over until it gets write access or the given timeout is elapsed.
|
||||
// If the second process does not get write access within the given timeout,
|
||||
// sqlite3_step() will return the error SQLITE_BUSY.
|
||||
// (without a busy_timeout, sqlite3_step() would return SQLITE_BUSY _at once_)
|
||||
conn.busy_timeout(Duration::from_secs(10))?;
|
||||
|
||||
g(&conn)
|
||||
}
|
||||
None => Err(Error::SqlNoConnection),
|
||||
@@ -182,14 +193,14 @@ impl Sql {
|
||||
///
|
||||
/// Setting `None` deletes the value. On failure an error message
|
||||
/// will already have been logged.
|
||||
pub fn set_config(
|
||||
pub fn set_raw_config(
|
||||
&self,
|
||||
context: &Context,
|
||||
key: impl AsRef<str>,
|
||||
value: Option<&str>,
|
||||
) -> Result<()> {
|
||||
if !self.is_open() {
|
||||
error!(context, "set_config(): Database not ready.");
|
||||
error!(context, "set_raw_config(): Database not ready.");
|
||||
return Err(Error::SqlNoConnection);
|
||||
}
|
||||
|
||||
@@ -223,14 +234,14 @@ impl Sql {
|
||||
match res {
|
||||
Ok(_) => Ok(()),
|
||||
Err(err) => {
|
||||
error!(context, "set_config(): Cannot change value. {:?}", &err);
|
||||
Err(err.into())
|
||||
error!(context, "set_raw_config(): Cannot change value. {:?}", &err);
|
||||
Err(err)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Get configuration options from the database.
|
||||
pub fn get_config(&self, context: &Context, key: impl AsRef<str>) -> Option<String> {
|
||||
pub fn get_raw_config(&self, context: &Context, key: impl AsRef<str>) -> Option<String> {
|
||||
if !self.is_open() || key.as_ref().is_empty() {
|
||||
return None;
|
||||
}
|
||||
@@ -241,44 +252,46 @@ impl Sql {
|
||||
)
|
||||
}
|
||||
|
||||
pub fn set_config_int(
|
||||
pub fn set_raw_config_int(
|
||||
&self,
|
||||
context: &Context,
|
||||
key: impl AsRef<str>,
|
||||
value: i32,
|
||||
) -> Result<()> {
|
||||
self.set_config(context, key, Some(&format!("{}", value)))
|
||||
self.set_raw_config(context, key, Some(&format!("{}", value)))
|
||||
}
|
||||
|
||||
pub fn get_config_int(&self, context: &Context, key: impl AsRef<str>) -> Option<i32> {
|
||||
self.get_config(context, key).and_then(|s| s.parse().ok())
|
||||
pub fn get_raw_config_int(&self, context: &Context, key: impl AsRef<str>) -> Option<i32> {
|
||||
self.get_raw_config(context, key)
|
||||
.and_then(|s| s.parse().ok())
|
||||
}
|
||||
|
||||
pub fn get_config_bool(&self, context: &Context, key: impl AsRef<str>) -> bool {
|
||||
pub fn get_raw_config_bool(&self, context: &Context, key: impl AsRef<str>) -> bool {
|
||||
// Not the most obvious way to encode bool as string, but it is matter
|
||||
// of backward compatibility.
|
||||
self.get_config_int(context, key).unwrap_or_default() > 0
|
||||
self.get_raw_config_int(context, key).unwrap_or_default() > 0
|
||||
}
|
||||
|
||||
pub fn set_config_bool<T>(&self, context: &Context, key: T, value: bool) -> Result<()>
|
||||
pub fn set_raw_config_bool<T>(&self, context: &Context, key: T, value: bool) -> Result<()>
|
||||
where
|
||||
T: AsRef<str>,
|
||||
{
|
||||
let value = if value { Some("1") } else { None };
|
||||
self.set_config(context, key, value)
|
||||
self.set_raw_config(context, key, value)
|
||||
}
|
||||
|
||||
pub fn set_config_int64(
|
||||
pub fn set_raw_config_int64(
|
||||
&self,
|
||||
context: &Context,
|
||||
key: impl AsRef<str>,
|
||||
value: i64,
|
||||
) -> Result<()> {
|
||||
self.set_config(context, key, Some(&format!("{}", value)))
|
||||
self.set_raw_config(context, key, Some(&format!("{}", value)))
|
||||
}
|
||||
|
||||
pub fn get_config_int64(&self, context: &Context, key: impl AsRef<str>) -> Option<i64> {
|
||||
self.get_config(context, key).and_then(|r| r.parse().ok())
|
||||
pub fn get_raw_config_int64(&self, context: &Context, key: impl AsRef<str>) -> Option<i64> {
|
||||
self.get_raw_config(context, key)
|
||||
.and_then(|r| r.parse().ok())
|
||||
}
|
||||
|
||||
fn start_stmt(&self, stmt: impl AsRef<str>) {
|
||||
@@ -330,7 +343,7 @@ fn open(
|
||||
.with_init(|c| c.execute_batch("PRAGMA secure_delete=on;"));
|
||||
let pool = r2d2::Pool::builder()
|
||||
.min_idle(Some(2))
|
||||
.max_size(4)
|
||||
.max_size(10)
|
||||
.connection_timeout(std::time::Duration::new(60, 0))
|
||||
.build(mgr)?;
|
||||
|
||||
@@ -466,11 +479,13 @@ fn open(
|
||||
// cannot create the tables - maybe we cannot write?
|
||||
return Err(Error::SqlFailedToOpen);
|
||||
} else {
|
||||
sql.set_config_int(context, "dbversion", 0)?;
|
||||
sql.set_raw_config_int(context, "dbversion", 0)?;
|
||||
}
|
||||
} else {
|
||||
exists_before_update = 1;
|
||||
dbversion_before_update = sql.get_config_int(context, "dbversion").unwrap_or_default();
|
||||
dbversion_before_update = sql
|
||||
.get_raw_config_int(context, "dbversion")
|
||||
.unwrap_or_default();
|
||||
}
|
||||
|
||||
// (1) update low-level database structure.
|
||||
@@ -482,6 +497,7 @@ fn open(
|
||||
let mut update_file_paths = 0;
|
||||
|
||||
if dbversion < 1 {
|
||||
info!(context, "[migration] v1");
|
||||
sql.execute(
|
||||
"CREATE TABLE leftgrps ( id INTEGER PRIMARY KEY, grpid TEXT DEFAULT '');",
|
||||
params![],
|
||||
@@ -491,17 +507,19 @@ fn open(
|
||||
params![],
|
||||
)?;
|
||||
dbversion = 1;
|
||||
sql.set_config_int(context, "dbversion", 1)?;
|
||||
sql.set_raw_config_int(context, "dbversion", 1)?;
|
||||
}
|
||||
if dbversion < 2 {
|
||||
info!(context, "[migration] v2");
|
||||
sql.execute(
|
||||
"ALTER TABLE contacts ADD COLUMN authname TEXT DEFAULT '';",
|
||||
params![],
|
||||
)?;
|
||||
dbversion = 2;
|
||||
sql.set_config_int(context, "dbversion", 2)?;
|
||||
sql.set_raw_config_int(context, "dbversion", 2)?;
|
||||
}
|
||||
if dbversion < 7 {
|
||||
info!(context, "[migration] v7");
|
||||
sql.execute(
|
||||
"CREATE TABLE keypairs (\
|
||||
id INTEGER PRIMARY KEY, \
|
||||
@@ -513,9 +531,10 @@ fn open(
|
||||
params![],
|
||||
)?;
|
||||
dbversion = 7;
|
||||
sql.set_config_int(context, "dbversion", 7)?;
|
||||
sql.set_raw_config_int(context, "dbversion", 7)?;
|
||||
}
|
||||
if dbversion < 10 {
|
||||
info!(context, "[migration] v10");
|
||||
sql.execute(
|
||||
"CREATE TABLE acpeerstates (\
|
||||
id INTEGER PRIMARY KEY, \
|
||||
@@ -531,9 +550,10 @@ fn open(
|
||||
params![],
|
||||
)?;
|
||||
dbversion = 10;
|
||||
sql.set_config_int(context, "dbversion", 10)?;
|
||||
sql.set_raw_config_int(context, "dbversion", 10)?;
|
||||
}
|
||||
if dbversion < 12 {
|
||||
info!(context, "[migration] v12");
|
||||
sql.execute(
|
||||
"CREATE TABLE msgs_mdns ( msg_id INTEGER, contact_id INTEGER);",
|
||||
params![],
|
||||
@@ -543,9 +563,10 @@ fn open(
|
||||
params![],
|
||||
)?;
|
||||
dbversion = 12;
|
||||
sql.set_config_int(context, "dbversion", 12)?;
|
||||
sql.set_raw_config_int(context, "dbversion", 12)?;
|
||||
}
|
||||
if dbversion < 17 {
|
||||
info!(context, "[migration] v17");
|
||||
sql.execute(
|
||||
"ALTER TABLE chats ADD COLUMN archived INTEGER DEFAULT 0;",
|
||||
params![],
|
||||
@@ -557,18 +578,20 @@ fn open(
|
||||
)?;
|
||||
sql.execute("CREATE INDEX msgs_index5 ON msgs (starred);", params![])?;
|
||||
dbversion = 17;
|
||||
sql.set_config_int(context, "dbversion", 17)?;
|
||||
sql.set_raw_config_int(context, "dbversion", 17)?;
|
||||
}
|
||||
if dbversion < 18 {
|
||||
info!(context, "[migration] v18");
|
||||
sql.execute(
|
||||
"ALTER TABLE acpeerstates ADD COLUMN gossip_timestamp INTEGER DEFAULT 0;",
|
||||
params![],
|
||||
)?;
|
||||
sql.execute("ALTER TABLE acpeerstates ADD COLUMN gossip_key;", params![])?;
|
||||
dbversion = 18;
|
||||
sql.set_config_int(context, "dbversion", 18)?;
|
||||
sql.set_raw_config_int(context, "dbversion", 18)?;
|
||||
}
|
||||
if dbversion < 27 {
|
||||
info!(context, "[migration] v27");
|
||||
sql.execute("DELETE FROM msgs WHERE chat_id=1 OR chat_id=2;", params![])?;
|
||||
sql.execute(
|
||||
"CREATE INDEX chats_contacts_index2 ON chats_contacts (contact_id);",
|
||||
@@ -583,9 +606,10 @@ fn open(
|
||||
params![],
|
||||
)?;
|
||||
dbversion = 27;
|
||||
sql.set_config_int(context, "dbversion", 27)?;
|
||||
sql.set_raw_config_int(context, "dbversion", 27)?;
|
||||
}
|
||||
if dbversion < 34 {
|
||||
info!(context, "[migration] v34");
|
||||
sql.execute(
|
||||
"ALTER TABLE msgs ADD COLUMN hidden INTEGER DEFAULT 0;",
|
||||
params![],
|
||||
@@ -612,9 +636,10 @@ fn open(
|
||||
)?;
|
||||
recalc_fingerprints = 1;
|
||||
dbversion = 34;
|
||||
sql.set_config_int(context, "dbversion", 34)?;
|
||||
sql.set_raw_config_int(context, "dbversion", 34)?;
|
||||
}
|
||||
if dbversion < 39 {
|
||||
info!(context, "[migration] v39");
|
||||
sql.execute(
|
||||
"CREATE TABLE tokens ( id INTEGER PRIMARY KEY, namespc INTEGER DEFAULT 0, foreign_id INTEGER DEFAULT 0, token TEXT DEFAULT '', timestamp INTEGER DEFAULT 0);",
|
||||
params![]
|
||||
@@ -642,32 +667,37 @@ fn open(
|
||||
)?;
|
||||
}
|
||||
dbversion = 39;
|
||||
sql.set_config_int(context, "dbversion", 39)?;
|
||||
sql.set_raw_config_int(context, "dbversion", 39)?;
|
||||
}
|
||||
if dbversion < 40 {
|
||||
info!(context, "[migration] v40");
|
||||
sql.execute(
|
||||
"ALTER TABLE jobs ADD COLUMN thread INTEGER DEFAULT 0;",
|
||||
params![],
|
||||
)?;
|
||||
dbversion = 40;
|
||||
sql.set_config_int(context, "dbversion", 40)?;
|
||||
sql.set_raw_config_int(context, "dbversion", 40)?;
|
||||
}
|
||||
if dbversion < 41 {
|
||||
info!(context, "[migration] v41");
|
||||
update_file_paths = 1;
|
||||
dbversion = 41;
|
||||
sql.set_config_int(context, "dbversion", 41)?;
|
||||
sql.set_raw_config_int(context, "dbversion", 41)?;
|
||||
}
|
||||
if dbversion < 42 {
|
||||
info!(context, "[migration] v42");
|
||||
sql.execute("UPDATE msgs SET txt='' WHERE type!=10", params![])?;
|
||||
dbversion = 42;
|
||||
sql.set_config_int(context, "dbversion", 42)?;
|
||||
sql.set_raw_config_int(context, "dbversion", 42)?;
|
||||
}
|
||||
if dbversion < 44 {
|
||||
info!(context, "[migration] v44");
|
||||
sql.execute("ALTER TABLE msgs ADD COLUMN mime_headers TEXT;", params![])?;
|
||||
dbversion = 44;
|
||||
sql.set_config_int(context, "dbversion", 44)?;
|
||||
sql.set_raw_config_int(context, "dbversion", 44)?;
|
||||
}
|
||||
if dbversion < 46 {
|
||||
info!(context, "[migration] v46");
|
||||
sql.execute(
|
||||
"ALTER TABLE msgs ADD COLUMN mime_in_reply_to TEXT;",
|
||||
params![],
|
||||
@@ -677,7 +707,7 @@ fn open(
|
||||
params![],
|
||||
)?;
|
||||
dbversion = 46;
|
||||
sql.set_config_int(context, "dbversion", 46)?;
|
||||
sql.set_raw_config_int(context, "dbversion", 46)?;
|
||||
}
|
||||
if dbversion < 47 {
|
||||
info!(context, "[migration] v47");
|
||||
@@ -686,7 +716,7 @@ fn open(
|
||||
params![],
|
||||
)?;
|
||||
dbversion = 47;
|
||||
sql.set_config_int(context, "dbversion", 47)?;
|
||||
sql.set_raw_config_int(context, "dbversion", 47)?;
|
||||
}
|
||||
if dbversion < 48 {
|
||||
info!(context, "[migration] v48");
|
||||
@@ -696,7 +726,7 @@ fn open(
|
||||
)?;
|
||||
|
||||
dbversion = 48;
|
||||
sql.set_config_int(context, "dbversion", 48)?;
|
||||
sql.set_raw_config_int(context, "dbversion", 48)?;
|
||||
}
|
||||
if dbversion < 49 {
|
||||
info!(context, "[migration] v49");
|
||||
@@ -705,15 +735,15 @@ fn open(
|
||||
params![],
|
||||
)?;
|
||||
dbversion = 49;
|
||||
sql.set_config_int(context, "dbversion", 49)?;
|
||||
sql.set_raw_config_int(context, "dbversion", 49)?;
|
||||
}
|
||||
if dbversion < 50 {
|
||||
info!(context, "[migration] v50");
|
||||
if 0 != exists_before_update {
|
||||
sql.set_config_int(context, "show_emails", 2)?;
|
||||
sql.set_raw_config_int(context, "show_emails", 2)?;
|
||||
}
|
||||
dbversion = 50;
|
||||
sql.set_config_int(context, "dbversion", 50)?;
|
||||
sql.set_raw_config_int(context, "dbversion", 50)?;
|
||||
}
|
||||
if dbversion < 53 {
|
||||
info!(context, "[migration] v53");
|
||||
@@ -746,7 +776,7 @@ fn open(
|
||||
params![],
|
||||
)?;
|
||||
dbversion = 53;
|
||||
sql.set_config_int(context, "dbversion", 53)?;
|
||||
sql.set_raw_config_int(context, "dbversion", 53)?;
|
||||
}
|
||||
if dbversion < 54 {
|
||||
info!(context, "[migration] v54");
|
||||
@@ -756,18 +786,20 @@ fn open(
|
||||
)?;
|
||||
sql.execute("CREATE INDEX msgs_index6 ON msgs (location_id);", params![])?;
|
||||
dbversion = 54;
|
||||
sql.set_config_int(context, "dbversion", 54)?;
|
||||
sql.set_raw_config_int(context, "dbversion", 54)?;
|
||||
}
|
||||
if dbversion < 55 {
|
||||
info!(context, "[migration] v55");
|
||||
sql.execute(
|
||||
"ALTER TABLE locations ADD COLUMN independent INTEGER DEFAULT 0;",
|
||||
params![],
|
||||
)?;
|
||||
|
||||
sql.set_config_int(context, "dbversion", 55)?;
|
||||
sql.set_raw_config_int(context, "dbversion", 55)?;
|
||||
}
|
||||
|
||||
if 0 != recalc_fingerprints {
|
||||
info!(context, "[migration] recalc fingerprints");
|
||||
sql.query_map(
|
||||
"SELECT addr FROM acpeerstates;",
|
||||
params![],
|
||||
@@ -777,7 +809,7 @@ fn open(
|
||||
if let Some(ref mut peerstate) = Peerstate::from_addr(context, sql, &addr?)
|
||||
{
|
||||
peerstate.recalc_fingerprint();
|
||||
peerstate.save_to_db(sql, false).unwrap();
|
||||
peerstate.save_to_db(sql, false)?;
|
||||
}
|
||||
}
|
||||
Ok(())
|
||||
@@ -788,11 +820,9 @@ fn open(
|
||||
// versions before 2018-08 save the absolute paths in the database files at "param.f=";
|
||||
// for newer versions, we copy files always to the blob directory and store relative paths.
|
||||
// this snippet converts older databases and can be removed after some time.
|
||||
|
||||
info!(context, "[open] update file paths");
|
||||
|
||||
info!(context, "[migration] update file paths");
|
||||
let repl_from = sql
|
||||
.get_config(context, "backup_for")
|
||||
.get_raw_config(context, "backup_for")
|
||||
.unwrap_or_else(|| context.get_blobdir().to_string_lossy().into());
|
||||
|
||||
let repl_from = dc_ensure_no_slash_safe(&repl_from);
|
||||
@@ -812,7 +842,7 @@ fn open(
|
||||
NO_PARAMS,
|
||||
)?;
|
||||
|
||||
sql.set_config(context, "backup_for", None)?;
|
||||
sql.set_raw_config(context, "backup_for", None)?;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1133,12 +1163,8 @@ mod test {
|
||||
maybe_add_file(&mut files, "$BLOBDIR/world.txt");
|
||||
maybe_add_file(&mut files, "world2.txt");
|
||||
|
||||
assert!(is_file_in_use(&mut files, None, "hello"));
|
||||
assert!(!is_file_in_use(&mut files, Some(".txt"), "hello"));
|
||||
assert!(is_file_in_use(
|
||||
&mut files,
|
||||
Some("-suffix"),
|
||||
"world.txt-suffix"
|
||||
));
|
||||
assert!(is_file_in_use(&files, None, "hello"));
|
||||
assert!(!is_file_in_use(&files, Some(".txt"), "hello"));
|
||||
assert!(is_file_in_use(&files, Some("-suffix"), "world.txt-suffix"));
|
||||
}
|
||||
}
|
||||
|
||||
96
src/stock.rs
96
src/stock.rs
@@ -5,8 +5,7 @@ use strum_macros::EnumProperty;
|
||||
|
||||
use crate::contact::*;
|
||||
use crate::context::Context;
|
||||
use crate::dc_tools::*;
|
||||
use crate::events::Event;
|
||||
use crate::error::Error;
|
||||
|
||||
/// Stock strings
|
||||
///
|
||||
@@ -109,6 +108,8 @@ pub enum StockMessage {
|
||||
MsgLocationDisabled = 65,
|
||||
#[strum(props(fallback = "Location"))]
|
||||
Location = 66,
|
||||
#[strum(props(fallback = "Sticker"))]
|
||||
Sticker = 67,
|
||||
}
|
||||
|
||||
impl StockMessage {
|
||||
@@ -116,24 +117,52 @@ impl StockMessage {
|
||||
///
|
||||
/// These could be used in logging calls, so no logging here.
|
||||
fn fallback(&self) -> &'static str {
|
||||
self.get_str("fallback").unwrap()
|
||||
self.get_str("fallback").unwrap_or_default()
|
||||
}
|
||||
}
|
||||
|
||||
impl Context {
|
||||
/// Set the stock string for the [StockMessage].
|
||||
///
|
||||
pub fn set_stock_translation(
|
||||
&self,
|
||||
id: StockMessage,
|
||||
stockstring: String,
|
||||
) -> Result<(), Error> {
|
||||
if stockstring.contains("%1") && !id.fallback().contains("%1") {
|
||||
bail!(
|
||||
"translation {} contains invalid %1 placeholder, default is {}",
|
||||
stockstring,
|
||||
id.fallback()
|
||||
);
|
||||
}
|
||||
if stockstring.contains("%2") && !id.fallback().contains("%2") {
|
||||
bail!(
|
||||
"translation {} contains invalid %2 placeholder, default is {}",
|
||||
stockstring,
|
||||
id.fallback()
|
||||
);
|
||||
}
|
||||
self.translated_stockstrings
|
||||
.write()
|
||||
.unwrap()
|
||||
.insert(id as usize, stockstring);
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Return the stock string for the [StockMessage].
|
||||
///
|
||||
/// If the context callback responds with a string to use, e.g. a
|
||||
/// translation, then this string will be returned. Otherwise a
|
||||
/// default (English) string is returned.
|
||||
/// Return a translation (if it was set with set_stock_translation before)
|
||||
/// or a default (English) string.
|
||||
pub fn stock_str(&self, id: StockMessage) -> Cow<str> {
|
||||
let ptr = self.call_cb(Event::GetString { id, count: 0 }) as *mut libc::c_char;
|
||||
if ptr.is_null() {
|
||||
Cow::Borrowed(id.fallback())
|
||||
} else {
|
||||
let ret = to_string(ptr);
|
||||
unsafe { libc::free(ptr as *mut libc::c_void) };
|
||||
Cow::Owned(ret)
|
||||
match self
|
||||
.translated_stockstrings
|
||||
.read()
|
||||
.unwrap()
|
||||
.get(&(id as usize))
|
||||
{
|
||||
Some(ref x) => Cow::Owned(x.to_string()),
|
||||
None => Cow::Borrowed(id.fallback()),
|
||||
}
|
||||
}
|
||||
|
||||
@@ -237,7 +266,6 @@ mod tests {
|
||||
use crate::test_utils::*;
|
||||
|
||||
use crate::constants::DC_CONTACT_ID_SELF;
|
||||
use libc::uintptr_t;
|
||||
|
||||
use num_traits::ToPrimitive;
|
||||
|
||||
@@ -253,25 +281,31 @@ mod tests {
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_stock_str() {
|
||||
fn test_set_stock_translation() {
|
||||
let t = dummy_context();
|
||||
assert_eq!(t.ctx.stock_str(StockMessage::NoMessages), "No messages.");
|
||||
}
|
||||
|
||||
fn test_stock_str_no_fallback_cb(_ctx: &Context, evt: Event) -> uintptr_t {
|
||||
match evt {
|
||||
Event::GetString {
|
||||
id: StockMessage::NoMessages,
|
||||
..
|
||||
} => unsafe { "Hello there".strdup() as usize },
|
||||
_ => 0,
|
||||
}
|
||||
t.ctx
|
||||
.set_stock_translation(StockMessage::NoMessages, "xyz".to_string())
|
||||
.unwrap();
|
||||
assert_eq!(t.ctx.stock_str(StockMessage::NoMessages), "xyz")
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_stock_str_no_fallback() {
|
||||
let t = test_context(Some(Box::new(test_stock_str_no_fallback_cb)));
|
||||
assert_eq!(t.ctx.stock_str(StockMessage::NoMessages), "Hello there");
|
||||
fn test_set_stock_translation_wrong_replacements() {
|
||||
let t = dummy_context();
|
||||
assert!(t
|
||||
.ctx
|
||||
.set_stock_translation(StockMessage::NoMessages, "xyz %1$s ".to_string())
|
||||
.is_err());
|
||||
assert!(t
|
||||
.ctx
|
||||
.set_stock_translation(StockMessage::NoMessages, "xyz %2$s ".to_string())
|
||||
.is_err());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_stock_str() {
|
||||
let t = dummy_context();
|
||||
assert_eq!(t.ctx.stock_str(StockMessage::NoMessages), "No messages.");
|
||||
}
|
||||
|
||||
#[test]
|
||||
@@ -349,9 +383,7 @@ mod tests {
|
||||
let contact_id = {
|
||||
Contact::create(&t.ctx, "Alice", "alice@example.com")
|
||||
.expect("Failed to create contact Alice");
|
||||
let id =
|
||||
Contact::create(&t.ctx, "Bob", "bob@example.com").expect("failed to create bob");
|
||||
id
|
||||
Contact::create(&t.ctx, "Bob", "bob@example.com").expect("failed to create bob")
|
||||
};
|
||||
assert_eq!(
|
||||
t.ctx.stock_system_msg(
|
||||
|
||||
@@ -14,22 +14,24 @@ if __name__ == "__main__":
|
||||
s = re.sub(r"(?m)///.*$", "", s) # remove comments
|
||||
unsafe = s.count("unsafe")
|
||||
free = s.count("free(")
|
||||
gotoblocks = s.count("ok_to_continue") + s.count('OK_TO_CONTINUE')
|
||||
unsafe_fn = s.count("unsafe fn")
|
||||
chars = s.count("c_char") + s.count("CStr")
|
||||
filestats.append((fn, unsafe, free, gotoblocks, chars))
|
||||
filestats.append((fn, unsafe, free, unsafe_fn, chars))
|
||||
|
||||
sum_unsafe, sum_free, sum_gotoblocks, sum_chars = 0, 0, 0, 0
|
||||
sum_unsafe, sum_free, sum_unsafe_fn, sum_chars = 0, 0, 0, 0
|
||||
|
||||
for fn, unsafe, free, gotoblocks, chars in reversed(sorted(filestats, key=lambda x: sum(x[1:]))):
|
||||
print("{0: <25} unsafe: {1: >3} free: {2: >3} ok_to_cont: {3: >3} chars: {4: >3}".format(str(fn), unsafe, free, gotoblocks, chars))
|
||||
for fn, unsafe, free, unsafe_fn, chars in reversed(sorted(filestats, key=lambda x: sum(x[1:]))):
|
||||
if unsafe + free + unsafe_fn + chars == 0:
|
||||
continue
|
||||
print("{0: <25} unsafe: {1: >3} free: {2: >3} unsafe-fn: {3: >3} chars: {4: >3}".format(str(fn), unsafe, free, unsafe_fn, chars))
|
||||
sum_unsafe += unsafe
|
||||
sum_free += free
|
||||
sum_gotoblocks += gotoblocks
|
||||
sum_unsafe_fn += unsafe_fn
|
||||
sum_chars += chars
|
||||
|
||||
|
||||
print()
|
||||
print("total unsafe:", sum_unsafe)
|
||||
print("total free:", sum_free)
|
||||
print("total ok_to_continue:", sum_gotoblocks)
|
||||
print("total unsafe-fn:", sum_unsafe_fn)
|
||||
print("total c_chars:", sum_chars)
|
||||
|
||||
340
src/wrapmime.rs
340
src/wrapmime.rs
@@ -1,9 +1,14 @@
|
||||
use std::collections::HashSet;
|
||||
use std::ffi::CString;
|
||||
use std::ptr;
|
||||
|
||||
use crate::contact::addr_normalize;
|
||||
use crate::dc_strencode::*;
|
||||
use crate::dc_tools::*;
|
||||
use crate::error::Error;
|
||||
use mmime::clist::*;
|
||||
// use mmime::display::*;
|
||||
use mmime::mailimf::mailimf_msg_id_parse;
|
||||
use mmime::mailimf::types::*;
|
||||
use mmime::mailimf::types_helper::*;
|
||||
use mmime::mailmime::content::*;
|
||||
@@ -11,6 +16,7 @@ use mmime::mailmime::disposition::*;
|
||||
use mmime::mailmime::types::*;
|
||||
use mmime::mailmime::types_helper::*;
|
||||
use mmime::mailmime::*;
|
||||
use mmime::mmapstring::*;
|
||||
use mmime::other::*;
|
||||
|
||||
#[macro_export]
|
||||
@@ -36,13 +42,30 @@ pub fn get_ct_subtype(mime: *mut Mailmime) -> Option<String> {
|
||||
let ct: *mut mailmime_content = (*mime).mm_content_type;
|
||||
|
||||
if !ct.is_null() && !(*ct).ct_subtype.is_null() {
|
||||
Some(to_string((*ct).ct_subtype))
|
||||
Some(to_string_lossy((*ct).ct_subtype))
|
||||
} else {
|
||||
None
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub fn parse_message_id(message_id: &str) -> Result<String, Error> {
|
||||
let mut dummy = 0;
|
||||
let c_message_id = CString::new(message_id).unwrap_or_default();
|
||||
let c_ptr = c_message_id.as_ptr();
|
||||
let mut rfc724_mid_c = std::ptr::null_mut();
|
||||
if unsafe { mailimf_msg_id_parse(c_ptr, libc::strlen(c_ptr), &mut dummy, &mut rfc724_mid_c) }
|
||||
== MAIL_NO_ERROR as libc::c_int
|
||||
&& !rfc724_mid_c.is_null()
|
||||
{
|
||||
let res = to_string_lossy(rfc724_mid_c);
|
||||
unsafe { libc::free(rfc724_mid_c.cast()) };
|
||||
Ok(res)
|
||||
} else {
|
||||
bail!("could not parse message_id: {}", message_id);
|
||||
}
|
||||
}
|
||||
|
||||
pub fn get_autocrypt_mime(
|
||||
mime_undetermined: *mut Mailmime,
|
||||
) -> Result<(*mut Mailmime, *mut Mailmime), Error> {
|
||||
@@ -88,6 +111,192 @@ pub fn has_decryptable_data(mime_data: *mut mailmime_data) -> bool {
|
||||
}
|
||||
}
|
||||
|
||||
pub fn get_field_from(imffields: *mut mailimf_fields) -> Result<String, Error> {
|
||||
let field = mailimf_find_field(imffields, MAILIMF_FIELD_FROM as libc::c_int);
|
||||
if !field.is_null() && unsafe { !(*field).fld_data.fld_from.is_null() } {
|
||||
let mb_list = unsafe { (*(*field).fld_data.fld_from).frm_mb_list };
|
||||
if let Some(addr) = mailimf_find_first_addr(mb_list) {
|
||||
return Ok(addr);
|
||||
}
|
||||
}
|
||||
bail!("not From field found");
|
||||
}
|
||||
|
||||
pub fn get_field_date(imffields: *mut mailimf_fields) -> Result<i64, Error> {
|
||||
let field = mailimf_find_field(imffields, MAILIMF_FIELD_ORIG_DATE as libc::c_int);
|
||||
let mut message_time = 0;
|
||||
|
||||
if !field.is_null() && unsafe { !(*field).fld_data.fld_orig_date.is_null() } {
|
||||
let orig_date = unsafe { (*field).fld_data.fld_orig_date };
|
||||
|
||||
if !orig_date.is_null() {
|
||||
let dt = unsafe { (*orig_date).dt_date_time };
|
||||
message_time = dc_timestamp_from_date(dt);
|
||||
if message_time != 0 && message_time > time() {
|
||||
message_time = time()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Ok(message_time)
|
||||
}
|
||||
|
||||
fn mailimf_get_recipients_add_addr(recipients: &mut HashSet<String>, mb: *mut mailimf_mailbox) {
|
||||
if !mb.is_null() {
|
||||
let addr_norm = addr_normalize(as_str(unsafe { (*mb).mb_addr_spec }));
|
||||
recipients.insert(addr_norm.into());
|
||||
}
|
||||
}
|
||||
|
||||
/*the result is a pointer to mime, must not be freed*/
|
||||
pub fn mailimf_find_field(
|
||||
header: *mut mailimf_fields,
|
||||
wanted_fld_type: libc::c_int,
|
||||
) -> *mut mailimf_field {
|
||||
if header.is_null() {
|
||||
return ptr::null_mut();
|
||||
}
|
||||
|
||||
let header = unsafe { (*header) };
|
||||
if header.fld_list.is_null() {
|
||||
return ptr::null_mut();
|
||||
}
|
||||
|
||||
for cur in unsafe { &(*header.fld_list) } {
|
||||
let field = cur as *mut mailimf_field;
|
||||
if !field.is_null() {
|
||||
if unsafe { (*field).fld_type } == wanted_fld_type {
|
||||
return field;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
ptr::null_mut()
|
||||
}
|
||||
|
||||
/*the result is a pointer to mime, must not be freed*/
|
||||
pub fn mailmime_find_mailimf_fields(mime: *mut Mailmime) -> *mut mailimf_fields {
|
||||
if mime.is_null() {
|
||||
return ptr::null_mut();
|
||||
}
|
||||
|
||||
match unsafe { (*mime).mm_type as _ } {
|
||||
MAILMIME_MULTIPLE => {
|
||||
for cur_data in unsafe { (*(*mime).mm_data.mm_multipart.mm_mp_list).into_iter() } {
|
||||
let header = mailmime_find_mailimf_fields(cur_data as *mut _);
|
||||
if !header.is_null() {
|
||||
return header;
|
||||
}
|
||||
}
|
||||
}
|
||||
MAILMIME_MESSAGE => return unsafe { (*mime).mm_data.mm_message.mm_fields },
|
||||
_ => {}
|
||||
}
|
||||
|
||||
ptr::null_mut()
|
||||
}
|
||||
|
||||
pub unsafe fn mailimf_find_optional_field(
|
||||
header: *mut mailimf_fields,
|
||||
wanted_fld_name: *const libc::c_char,
|
||||
) -> *mut mailimf_optional_field {
|
||||
if header.is_null() || (*header).fld_list.is_null() {
|
||||
return ptr::null_mut();
|
||||
}
|
||||
for cur_data in (*(*header).fld_list).into_iter() {
|
||||
let field: *mut mailimf_field = cur_data as *mut _;
|
||||
|
||||
if (*field).fld_type == MAILIMF_FIELD_OPTIONAL_FIELD as libc::c_int {
|
||||
let optional_field: *mut mailimf_optional_field = (*field).fld_data.fld_optional_field;
|
||||
if !optional_field.is_null()
|
||||
&& !(*optional_field).fld_name.is_null()
|
||||
&& !(*optional_field).fld_value.is_null()
|
||||
&& strcasecmp((*optional_field).fld_name, wanted_fld_name) == 0i32
|
||||
{
|
||||
return optional_field;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
ptr::null_mut()
|
||||
}
|
||||
|
||||
pub fn mailimf_get_recipients(imffields: *mut mailimf_fields) -> HashSet<String> {
|
||||
/* returned addresses are normalized. */
|
||||
let mut recipients: HashSet<String> = Default::default();
|
||||
|
||||
for cur in unsafe { (*(*imffields).fld_list).into_iter() } {
|
||||
let fld = cur as *mut mailimf_field;
|
||||
|
||||
let fld_to: *mut mailimf_to;
|
||||
let fld_cc: *mut mailimf_cc;
|
||||
|
||||
let mut addr_list: *mut mailimf_address_list = ptr::null_mut();
|
||||
if fld.is_null() {
|
||||
continue;
|
||||
}
|
||||
|
||||
let fld = unsafe { *fld };
|
||||
|
||||
// TODO match on enums /rtn
|
||||
match fld.fld_type {
|
||||
13 => {
|
||||
fld_to = unsafe { fld.fld_data.fld_to };
|
||||
if !fld_to.is_null() {
|
||||
addr_list = unsafe { (*fld_to).to_addr_list };
|
||||
}
|
||||
}
|
||||
14 => {
|
||||
fld_cc = unsafe { fld.fld_data.fld_cc };
|
||||
if !fld_cc.is_null() {
|
||||
addr_list = unsafe { (*fld_cc).cc_addr_list };
|
||||
}
|
||||
}
|
||||
_ => {}
|
||||
}
|
||||
|
||||
if !addr_list.is_null() {
|
||||
for cur2 in unsafe { &(*(*addr_list).ad_list) } {
|
||||
let adr = cur2 as *mut mailimf_address;
|
||||
|
||||
if adr.is_null() {
|
||||
continue;
|
||||
}
|
||||
let adr = unsafe { *adr };
|
||||
|
||||
if adr.ad_type == MAILIMF_ADDRESS_MAILBOX as libc::c_int {
|
||||
mailimf_get_recipients_add_addr(&mut recipients, unsafe {
|
||||
adr.ad_data.ad_mailbox
|
||||
});
|
||||
} else if adr.ad_type == MAILIMF_ADDRESS_GROUP as libc::c_int {
|
||||
let group = unsafe { adr.ad_data.ad_group };
|
||||
if !group.is_null() && unsafe { !(*group).grp_mb_list.is_null() } {
|
||||
for cur3 in unsafe { &(*(*(*group).grp_mb_list).mb_list) } {
|
||||
mailimf_get_recipients_add_addr(
|
||||
&mut recipients,
|
||||
cur3 as *mut mailimf_mailbox,
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
recipients
|
||||
}
|
||||
|
||||
pub fn mailmime_transfer_decode(mime: *mut Mailmime) -> Result<Vec<u8>, Error> {
|
||||
ensure!(!mime.is_null(), "invalid inputs");
|
||||
|
||||
let mime_transfer_encoding =
|
||||
get_mime_transfer_encoding(mime).unwrap_or(MAILMIME_MECHANISM_BINARY as i32);
|
||||
|
||||
let mime_data = unsafe { (*mime).mm_data.mm_single };
|
||||
|
||||
decode_dt_data(mime_data, mime_transfer_encoding)
|
||||
}
|
||||
|
||||
pub fn get_mime_transfer_encoding(mime: *mut Mailmime) -> Option<libc::c_int> {
|
||||
unsafe {
|
||||
let mm_mime_fields = (*mime).mm_mime_fields;
|
||||
@@ -108,48 +317,77 @@ pub fn get_mime_transfer_encoding(mime: *mut Mailmime) -> Option<libc::c_int> {
|
||||
pub fn decode_dt_data(
|
||||
mime_data: *mut mailmime_data,
|
||||
mime_transfer_encoding: libc::c_int,
|
||||
) -> Result<(*mut libc::c_char, libc::size_t), Error> {
|
||||
) -> Result<Vec<u8>, Error> {
|
||||
// Decode data according to mime_transfer_encoding
|
||||
// returns Ok with a (decoded_data,decoded_data_bytes) pointer
|
||||
// where the caller must make sure to free it.
|
||||
// It may return Ok(ptr::null_mut(), 0)
|
||||
|
||||
let mut transfer_decoding_buffer: *mut libc::c_char = ptr::null_mut();
|
||||
let decoded_data: *mut libc::c_char;
|
||||
let mut decoded_data_bytes: libc::size_t = 0;
|
||||
if mime_transfer_encoding == MAILMIME_MECHANISM_7BIT as libc::c_int
|
||||
|| mime_transfer_encoding == MAILMIME_MECHANISM_8BIT as libc::c_int
|
||||
|| mime_transfer_encoding == MAILMIME_MECHANISM_BINARY as libc::c_int
|
||||
{
|
||||
unsafe {
|
||||
decoded_data = (*mime_data).dt_data.dt_text.dt_data as *mut _;
|
||||
decoded_data_bytes = (*mime_data).dt_data.dt_text.dt_length;
|
||||
}
|
||||
ensure!(
|
||||
!decoded_data.is_null() && decoded_data_bytes > 0,
|
||||
"could not decode mime message"
|
||||
);
|
||||
} else {
|
||||
let mut current_index: libc::size_t = 0;
|
||||
unsafe {
|
||||
let r = mailmime_part_parse(
|
||||
(*mime_data).dt_data.dt_text.dt_data,
|
||||
(*mime_data).dt_data.dt_text.dt_length,
|
||||
&mut current_index,
|
||||
mime_transfer_encoding,
|
||||
&mut transfer_decoding_buffer,
|
||||
&mut decoded_data_bytes,
|
||||
);
|
||||
if r != MAILIMF_NO_ERROR as libc::c_int
|
||||
|| transfer_decoding_buffer.is_null()
|
||||
|| decoded_data_bytes <= 0
|
||||
{
|
||||
bail!("mailmime_part_parse returned error or invalid data");
|
||||
}
|
||||
decoded_data = transfer_decoding_buffer;
|
||||
let decoded_data = unsafe { (*mime_data).dt_data.dt_text.dt_data };
|
||||
let decoded_data_bytes = unsafe { (*mime_data).dt_data.dt_text.dt_length };
|
||||
|
||||
if decoded_data.is_null() || decoded_data_bytes == 0 {
|
||||
bail!("No data to decode found");
|
||||
} else {
|
||||
let result = unsafe {
|
||||
std::slice::from_raw_parts(decoded_data as *const u8, decoded_data_bytes)
|
||||
};
|
||||
return Ok(result.to_vec());
|
||||
}
|
||||
}
|
||||
Ok((decoded_data, decoded_data_bytes))
|
||||
// unsafe { display_mime_data(mime_data) };
|
||||
|
||||
let mut current_index = 0;
|
||||
let mut transfer_decoding_buffer = ptr::null_mut();
|
||||
let mut decoded_data_bytes = 0;
|
||||
|
||||
let r = unsafe {
|
||||
mailmime_part_parse(
|
||||
(*mime_data).dt_data.dt_text.dt_data,
|
||||
(*mime_data).dt_data.dt_text.dt_length,
|
||||
&mut current_index,
|
||||
mime_transfer_encoding,
|
||||
&mut transfer_decoding_buffer,
|
||||
&mut decoded_data_bytes,
|
||||
)
|
||||
};
|
||||
|
||||
if r == MAILIMF_NO_ERROR as libc::c_int
|
||||
&& !transfer_decoding_buffer.is_null()
|
||||
&& decoded_data_bytes > 0
|
||||
{
|
||||
let result = unsafe {
|
||||
std::slice::from_raw_parts(transfer_decoding_buffer as *const u8, decoded_data_bytes)
|
||||
}
|
||||
.to_vec();
|
||||
// we return a fresh vec and transfer_decoding_buffer is not used or passed anywhere
|
||||
// so it's safe to free it right away, as mailman_part_parse has
|
||||
// allocated it fresh.
|
||||
unsafe { mmap_string_unref(transfer_decoding_buffer) };
|
||||
|
||||
return Ok(result);
|
||||
}
|
||||
|
||||
Err(format_err!("Failed to to decode"))
|
||||
}
|
||||
|
||||
pub fn mailimf_find_first_addr(mb_list: *const mailimf_mailbox_list) -> Option<String> {
|
||||
if mb_list.is_null() {
|
||||
return None;
|
||||
}
|
||||
|
||||
for cur in unsafe { (*(*mb_list).mb_list).into_iter() } {
|
||||
let mb = cur as *mut mailimf_mailbox;
|
||||
if !mb.is_null() && !unsafe { (*mb).mb_addr_spec.is_null() } {
|
||||
let addr = unsafe { as_str((*mb).mb_addr_spec) };
|
||||
return Some(addr_normalize(addr).to_string());
|
||||
}
|
||||
}
|
||||
|
||||
None
|
||||
}
|
||||
|
||||
/**************************************
|
||||
@@ -210,8 +448,8 @@ pub fn append_ct_param(
|
||||
value: &str,
|
||||
) -> Result<(), Error> {
|
||||
unsafe {
|
||||
let name_c = CString::new(name).unwrap();
|
||||
let value_c = CString::new(value).unwrap();
|
||||
let name_c = CString::new(name).unwrap_or_default();
|
||||
let value_c = CString::new(value).unwrap_or_default();
|
||||
|
||||
clist_append!(
|
||||
(*content).ct_parameters,
|
||||
@@ -225,7 +463,7 @@ pub fn append_ct_param(
|
||||
}
|
||||
|
||||
pub fn new_content_type(content_type: &str) -> Result<*mut mailmime_content, Error> {
|
||||
let ct = CString::new(content_type).unwrap();
|
||||
let ct = CString::new(content_type).unwrap_or_default();
|
||||
let content: *mut mailmime_content;
|
||||
// mailmime_content_new_with_str only parses but does not retain/own ct
|
||||
unsafe {
|
||||
@@ -261,6 +499,24 @@ pub fn content_type_needs_encoding(content: *const mailmime_content) -> bool {
|
||||
}
|
||||
}
|
||||
|
||||
pub fn new_mailbox_list(displayname: &str, addr: &str) -> *mut mailimf_mailbox_list {
|
||||
let mbox: *mut mailimf_mailbox_list = unsafe { mailimf_mailbox_list_new_empty() };
|
||||
unsafe {
|
||||
mailimf_mailbox_list_add(
|
||||
mbox,
|
||||
mailimf_mailbox_new(
|
||||
if !displayname.is_empty() {
|
||||
dc_encode_header_words(&displayname).strdup()
|
||||
} else {
|
||||
ptr::null_mut()
|
||||
},
|
||||
addr.strdup(),
|
||||
),
|
||||
);
|
||||
}
|
||||
mbox
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
@@ -280,4 +536,16 @@ mod tests {
|
||||
new_content_type("application/pgp-encrypted").unwrap()
|
||||
));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_parse_message_id() {
|
||||
assert_eq!(
|
||||
parse_message_id("Mr.PRUe8HJBoaO.3whNvLCMFU0@testrun.org").unwrap(),
|
||||
"Mr.PRUe8HJBoaO.3whNvLCMFU0@testrun.org"
|
||||
);
|
||||
assert_eq!(
|
||||
parse_message_id("<Mr.PRUe8HJBoaO.3whNvLCMFU0@testrun.org>").unwrap(),
|
||||
"Mr.PRUe8HJBoaO.3whNvLCMFU0@testrun.org"
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
155
tests/stress.rs
155
tests/stress.rs
@@ -1,18 +1,15 @@
|
||||
//! Stress some functions for testing; if used as a lib, this file is obsolete.
|
||||
|
||||
use std::collections::HashSet;
|
||||
use std::ptr;
|
||||
|
||||
use deltachat::chat::{self, Chat};
|
||||
use deltachat::config;
|
||||
use deltachat::contact::*;
|
||||
use deltachat::context::*;
|
||||
use deltachat::dc_tools::*;
|
||||
use deltachat::keyring::*;
|
||||
use deltachat::oauth2::*;
|
||||
use deltachat::pgp::*;
|
||||
use deltachat::pgp;
|
||||
use deltachat::Event;
|
||||
use libc::{free, strcmp, strdup};
|
||||
use tempfile::{tempdir, TempDir};
|
||||
|
||||
/* some data used for testing
|
||||
@@ -50,135 +47,6 @@ unsafe fn stress_functions(context: &Context) {
|
||||
assert!(res.contains(" configured_send_port "));
|
||||
assert!(res.contains(" configured_server_flags "));
|
||||
|
||||
let mut buf_0: *mut libc::c_char;
|
||||
let mut headerline = String::default();
|
||||
let mut setupcodebegin: *const libc::c_char = ptr::null();
|
||||
let mut preferencrypt: *const libc::c_char = ptr::null();
|
||||
let mut base64: *const libc::c_char = ptr::null();
|
||||
buf_0 = strdup(
|
||||
b"-----BEGIN PGP MESSAGE-----\nNoVal:\n\ndata\n-----END PGP MESSAGE-----\x00" as *const u8
|
||||
as *const libc::c_char,
|
||||
);
|
||||
let ok = dc_split_armored_data(
|
||||
buf_0,
|
||||
&mut headerline,
|
||||
&mut setupcodebegin,
|
||||
ptr::null_mut(),
|
||||
&mut base64,
|
||||
);
|
||||
assert!(ok);
|
||||
assert!(!headerline.is_empty());
|
||||
assert_eq!(headerline, "-----BEGIN PGP MESSAGE-----");
|
||||
|
||||
assert!(!base64.is_null());
|
||||
assert_eq!(as_str(base64 as *const libc::c_char), "data",);
|
||||
|
||||
free(buf_0 as *mut libc::c_void);
|
||||
|
||||
buf_0 =
|
||||
strdup(b"-----BEGIN PGP MESSAGE-----\n\ndat1\n-----END PGP MESSAGE-----\n-----BEGIN PGP MESSAGE-----\n\ndat2\n-----END PGP MESSAGE-----\x00"
|
||||
as *const u8 as *const libc::c_char);
|
||||
let ok = dc_split_armored_data(
|
||||
buf_0,
|
||||
&mut headerline,
|
||||
&mut setupcodebegin,
|
||||
ptr::null_mut(),
|
||||
&mut base64,
|
||||
);
|
||||
|
||||
assert!(ok);
|
||||
assert_eq!(headerline, "-----BEGIN PGP MESSAGE-----");
|
||||
|
||||
assert!(!base64.is_null());
|
||||
assert_eq!(as_str(base64 as *const libc::c_char), "dat1",);
|
||||
|
||||
free(buf_0 as *mut libc::c_void);
|
||||
|
||||
buf_0 = strdup(
|
||||
b"foo \n -----BEGIN PGP MESSAGE----- \n base64-123 \n -----END PGP MESSAGE-----\x00"
|
||||
as *const u8 as *const libc::c_char,
|
||||
);
|
||||
let ok = dc_split_armored_data(
|
||||
buf_0,
|
||||
&mut headerline,
|
||||
&mut setupcodebegin,
|
||||
ptr::null_mut(),
|
||||
&mut base64,
|
||||
);
|
||||
|
||||
assert!(ok);
|
||||
assert_eq!(headerline, "-----BEGIN PGP MESSAGE-----");
|
||||
assert!(setupcodebegin.is_null());
|
||||
|
||||
assert!(!base64.is_null());
|
||||
assert_eq!(as_str(base64 as *const libc::c_char), "base64-123",);
|
||||
|
||||
free(buf_0 as *mut libc::c_void);
|
||||
|
||||
buf_0 = strdup(b"foo-----BEGIN PGP MESSAGE-----\x00" as *const u8 as *const libc::c_char);
|
||||
let ok = dc_split_armored_data(
|
||||
buf_0,
|
||||
&mut headerline,
|
||||
&mut setupcodebegin,
|
||||
ptr::null_mut(),
|
||||
&mut base64,
|
||||
);
|
||||
|
||||
assert!(!ok);
|
||||
free(buf_0 as *mut libc::c_void);
|
||||
buf_0 =
|
||||
strdup(b"foo \n -----BEGIN PGP MESSAGE-----\n Passphrase-BeGIN : 23 \n \n base64-567 \r\n abc \n -----END PGP MESSAGE-----\n\n\n\x00"
|
||||
as *const u8 as *const libc::c_char);
|
||||
let ok = dc_split_armored_data(
|
||||
buf_0,
|
||||
&mut headerline,
|
||||
&mut setupcodebegin,
|
||||
ptr::null_mut(),
|
||||
&mut base64,
|
||||
);
|
||||
assert!(ok);
|
||||
assert_eq!(headerline, "-----BEGIN PGP MESSAGE-----");
|
||||
|
||||
assert!(!setupcodebegin.is_null());
|
||||
assert_eq!(
|
||||
strcmp(
|
||||
setupcodebegin,
|
||||
b"23\x00" as *const u8 as *const libc::c_char,
|
||||
),
|
||||
0
|
||||
);
|
||||
|
||||
assert!(!base64.is_null());
|
||||
assert_eq!(as_str(base64 as *const libc::c_char), "base64-567 \n abc",);
|
||||
|
||||
free(buf_0 as *mut libc::c_void);
|
||||
|
||||
buf_0 =
|
||||
strdup(b"-----BEGIN PGP PRIVATE KEY BLOCK-----\n Autocrypt-Prefer-Encrypt : mutual \n\nbase64\n-----END PGP PRIVATE KEY BLOCK-----\x00"
|
||||
as *const u8 as *const libc::c_char);
|
||||
let ok = dc_split_armored_data(
|
||||
buf_0,
|
||||
&mut headerline,
|
||||
ptr::null_mut(),
|
||||
&mut preferencrypt,
|
||||
&mut base64,
|
||||
);
|
||||
assert!(ok);
|
||||
assert_eq!(headerline, "-----BEGIN PGP PRIVATE KEY BLOCK-----");
|
||||
assert!(!preferencrypt.is_null());
|
||||
assert_eq!(
|
||||
strcmp(
|
||||
preferencrypt,
|
||||
b"mutual\x00" as *const u8 as *const libc::c_char,
|
||||
),
|
||||
0
|
||||
);
|
||||
|
||||
assert!(!base64.is_null());
|
||||
assert_eq!(as_str(base64 as *const libc::c_char), "base64",);
|
||||
|
||||
free(buf_0 as *mut libc::c_void);
|
||||
|
||||
// Cant check, no configured context
|
||||
// assert!(dc_is_configured(context) != 0, "Missing configured context");
|
||||
|
||||
@@ -232,11 +100,11 @@ unsafe fn stress_functions(context: &Context) {
|
||||
#[test]
|
||||
#[ignore] // is too expensive
|
||||
fn test_encryption_decryption() {
|
||||
let (public_key, private_key) = dc_pgp_create_keypair("foo@bar.de").unwrap();
|
||||
let (public_key, private_key) = pgp::create_keypair("foo@bar.de").unwrap();
|
||||
|
||||
private_key.split_key().unwrap();
|
||||
|
||||
let (public_key2, private_key2) = dc_pgp_create_keypair("two@zwo.de").unwrap();
|
||||
let (public_key2, private_key2) = pgp::create_keypair("two@zwo.de").unwrap();
|
||||
|
||||
assert_ne!(public_key, public_key2);
|
||||
|
||||
@@ -245,11 +113,11 @@ fn test_encryption_decryption() {
|
||||
keyring.add_owned(public_key.clone());
|
||||
keyring.add_ref(&public_key2);
|
||||
|
||||
let ctext_signed = dc_pgp_pk_encrypt(original_text, &keyring, Some(&private_key)).unwrap();
|
||||
let ctext_signed = pgp::pk_encrypt(original_text, &keyring, Some(&private_key)).unwrap();
|
||||
assert!(!ctext_signed.is_empty());
|
||||
assert!(ctext_signed.starts_with("-----BEGIN PGP MESSAGE-----"));
|
||||
|
||||
let ctext_unsigned = dc_pgp_pk_encrypt(original_text, &keyring, None).unwrap();
|
||||
let ctext_unsigned = pgp::pk_encrypt(original_text, &keyring, None).unwrap();
|
||||
assert!(!ctext_unsigned.is_empty());
|
||||
assert!(ctext_unsigned.starts_with("-----BEGIN PGP MESSAGE-----"));
|
||||
|
||||
@@ -264,7 +132,7 @@ fn test_encryption_decryption() {
|
||||
|
||||
let mut valid_signatures: HashSet<String> = Default::default();
|
||||
|
||||
let plain = dc_pgp_pk_decrypt(
|
||||
let plain = pgp::pk_decrypt(
|
||||
ctext_signed.as_bytes(),
|
||||
&keyring,
|
||||
&public_keyring,
|
||||
@@ -278,7 +146,7 @@ fn test_encryption_decryption() {
|
||||
valid_signatures.clear();
|
||||
|
||||
let empty_keyring = Keyring::default();
|
||||
let plain = dc_pgp_pk_decrypt(
|
||||
let plain = pgp::pk_decrypt(
|
||||
ctext_signed.as_bytes(),
|
||||
&keyring,
|
||||
&empty_keyring,
|
||||
@@ -290,7 +158,7 @@ fn test_encryption_decryption() {
|
||||
|
||||
valid_signatures.clear();
|
||||
|
||||
let plain = dc_pgp_pk_decrypt(
|
||||
let plain = pgp::pk_decrypt(
|
||||
ctext_signed.as_bytes(),
|
||||
&keyring,
|
||||
&public_keyring2,
|
||||
@@ -304,7 +172,7 @@ fn test_encryption_decryption() {
|
||||
|
||||
public_keyring2.add_ref(&public_key);
|
||||
|
||||
let plain = dc_pgp_pk_decrypt(
|
||||
let plain = pgp::pk_decrypt(
|
||||
ctext_signed.as_bytes(),
|
||||
&keyring,
|
||||
&public_keyring2,
|
||||
@@ -316,7 +184,7 @@ fn test_encryption_decryption() {
|
||||
|
||||
valid_signatures.clear();
|
||||
|
||||
let plain = dc_pgp_pk_decrypt(
|
||||
let plain = pgp::pk_decrypt(
|
||||
ctext_unsigned.as_bytes(),
|
||||
&keyring,
|
||||
&public_keyring,
|
||||
@@ -333,8 +201,7 @@ fn test_encryption_decryption() {
|
||||
let mut public_keyring = Keyring::default();
|
||||
public_keyring.add_ref(&public_key);
|
||||
|
||||
let plain =
|
||||
dc_pgp_pk_decrypt(ctext_signed.as_bytes(), &keyring, &public_keyring, None).unwrap();
|
||||
let plain = pgp::pk_decrypt(ctext_signed.as_bytes(), &keyring, &public_keyring, None).unwrap();
|
||||
|
||||
assert_eq!(plain, original_text);
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user