mirror of
https://github.com/chatmail/core.git
synced 2026-04-05 15:02:11 +03:00
Compare commits
161 Commits
rental-cha
...
safe-dc_ms
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
3a602ee313 | ||
|
|
e0c1b91a7a | ||
|
|
3614d57f9f | ||
|
|
f3e53a05a6 | ||
|
|
dd381a5c1c | ||
|
|
8eee449305 | ||
|
|
96e02af0da | ||
|
|
60fb1478c3 | ||
|
|
aa7d0679df | ||
|
|
8e3cc192a5 | ||
|
|
0f939995d1 | ||
|
|
2e1bc9b14e | ||
|
|
fd1ac6ab2d | ||
|
|
d224924dc8 | ||
|
|
00e5ddd6f0 | ||
|
|
c603ca0e7a | ||
|
|
d07ef01204 | ||
|
|
d8630b5029 | ||
|
|
3b397326f8 | ||
|
|
81cabd08a9 | ||
|
|
5663c7dec3 | ||
|
|
06673b2108 | ||
|
|
6b7498a4b1 | ||
|
|
7f4ef493b9 | ||
|
|
d9d0dee0d5 | ||
|
|
9605370f0b | ||
|
|
7d9fc682a0 | ||
|
|
c4c08f2552 | ||
|
|
400740fdba | ||
|
|
42bce7c0bf | ||
|
|
a2281489a6 | ||
|
|
9bf7b0bf96 | ||
|
|
1f82ba74aa | ||
|
|
1062ac6ade | ||
|
|
aa5304a4f3 | ||
|
|
3a57ba1142 | ||
|
|
c0e7293360 | ||
|
|
dc1839760c | ||
|
|
a4e4b0fc17 | ||
|
|
743e4deb36 | ||
|
|
1d75f8478c | ||
|
|
cc0428aa50 | ||
|
|
4be481275f | ||
|
|
28cfe36f43 | ||
|
|
e0df78c5f7 | ||
|
|
4d8b058b65 | ||
|
|
da25611758 | ||
|
|
27732c85af | ||
|
|
5ffc84eb59 | ||
|
|
0a6e540394 | ||
|
|
9f09c73ec1 | ||
|
|
4bbab876ae | ||
|
|
b2fafeff19 | ||
|
|
79510a83de | ||
|
|
dd0afdfeb0 | ||
|
|
b6997c4455 | ||
|
|
2920732435 | ||
|
|
b9cfcce284 | ||
|
|
70d997964b | ||
|
|
4ffe71e1df | ||
|
|
cc2339fbe2 | ||
|
|
8fb859c0c4 | ||
|
|
5ff472dae0 | ||
|
|
6be4a6ed00 | ||
|
|
094d46293e | ||
|
|
c8d945db56 | ||
|
|
f78f0079c1 | ||
|
|
98d6bdb48a | ||
|
|
0391aebaeb | ||
|
|
46520edd87 | ||
|
|
14daa99802 | ||
|
|
4f9f67a477 | ||
|
|
85f182067c | ||
|
|
66ab6874f8 | ||
|
|
906b901e3d | ||
|
|
65adff4bdd | ||
|
|
e60fc0dc30 | ||
|
|
ffd719962c | ||
|
|
1a1f0c0a7c | ||
|
|
3944592c09 | ||
|
|
a5f862a564 | ||
|
|
489f25940f | ||
|
|
6288909481 | ||
|
|
c95f134963 | ||
|
|
024c2883c0 | ||
|
|
d7b0ecad75 | ||
|
|
50947b81c0 | ||
|
|
e5a1b721f1 | ||
|
|
aeb1a88e7a | ||
|
|
c406675d6a | ||
|
|
1ec193991b | ||
|
|
1d09d2f0d1 | ||
|
|
5b993f601f | ||
|
|
77be6c4294 | ||
|
|
1497f263dc | ||
|
|
d6e2798f5a | ||
|
|
8e41037eb1 | ||
|
|
0bcd3bc94e | ||
|
|
fb5d53226d | ||
|
|
b5a9cc1380 | ||
|
|
1ba7368c6d | ||
|
|
c1a78d9c3e | ||
|
|
ad36671adb | ||
|
|
8902d0843b | ||
|
|
010ac6a6ac | ||
|
|
c71589a710 | ||
|
|
9b70ea0595 | ||
|
|
a6928c29e8 | ||
|
|
3d9129c198 | ||
|
|
cf8f37449c | ||
|
|
886870964b | ||
|
|
6a5e107e64 | ||
|
|
723624b3f7 | ||
|
|
634dfaa1ca | ||
|
|
2d00495d29 | ||
|
|
ea964fd733 | ||
|
|
dd5803e576 | ||
|
|
85e16f6e82 | ||
|
|
0a9f61783d | ||
|
|
19a0071585 | ||
|
|
2fe07e86c7 | ||
|
|
22c1b34ebf | ||
|
|
eca11a74d7 | ||
|
|
b3df24d188 | ||
|
|
6fcd6419bd | ||
|
|
edb0fa17af | ||
|
|
86eb9cc058 | ||
|
|
47c0526026 | ||
|
|
f5416b1c2c | ||
|
|
a9f42f7a9e | ||
|
|
7650e1c7df | ||
|
|
0de76d6d3f | ||
|
|
db4f972315 | ||
|
|
8e4a01c98d | ||
|
|
21976b14a6 | ||
|
|
d3048aa06f | ||
|
|
a1a85270e6 | ||
|
|
2f6438f75a | ||
|
|
ff0fdd5837 | ||
|
|
25b0a26ff9 | ||
|
|
3f8abd2218 | ||
|
|
4ec214b3fc | ||
|
|
cefbd64f37 | ||
|
|
d25d839d6a | ||
|
|
23d49560bf | ||
|
|
bb16849eef | ||
|
|
ae17971599 | ||
|
|
f13b068479 | ||
|
|
850941bf8b | ||
|
|
11dd156594 | ||
|
|
78587ee6b1 | ||
|
|
3eeb184278 | ||
|
|
7632eb1ce0 | ||
|
|
3eea175d36 | ||
|
|
8f24ff74dd | ||
|
|
6fab7d0a27 | ||
|
|
7cc52e0a55 | ||
|
|
aa0801014a | ||
|
|
9e379338bc | ||
|
|
ba62d13a14 | ||
|
|
d7d7147549 |
@@ -1,5 +1,4 @@
|
||||
version: 2.1
|
||||
|
||||
executors:
|
||||
default:
|
||||
docker:
|
||||
@@ -153,11 +152,12 @@ jobs:
|
||||
- wheelhouse
|
||||
|
||||
upload_docs_wheels:
|
||||
machine: True
|
||||
machine: true
|
||||
steps:
|
||||
- checkout
|
||||
- attach_workspace:
|
||||
at: workspace
|
||||
- run: pyenv global 3.5.2
|
||||
- run: ls -laR workspace
|
||||
- run: ci_scripts/ci_upload.sh workspace/py-docs workspace/wheelhouse
|
||||
|
||||
|
||||
4
.gitignore
vendored
4
.gitignore
vendored
@@ -18,3 +18,7 @@ __pycache__
|
||||
python/src/deltachat/capi*.so
|
||||
|
||||
python/liveconfig*
|
||||
|
||||
# ignore doxgen generated files
|
||||
deltachat-ffi/html
|
||||
deltachat-ffi/xml
|
||||
|
||||
171
Cargo.lock
generated
171
Cargo.lock
generated
@@ -110,6 +110,19 @@ dependencies = [
|
||||
"byteorder 1.3.2 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "bit-set"
|
||||
version = "0.5.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
dependencies = [
|
||||
"bit-vec 0.5.1 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "bit-vec"
|
||||
version = "0.5.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
|
||||
[[package]]
|
||||
name = "bitfield"
|
||||
version = "0.13.2"
|
||||
@@ -221,7 +234,7 @@ name = "c2-chacha"
|
||||
version = "0.2.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
dependencies = [
|
||||
"lazy_static 1.3.0 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"lazy_static 1.4.0 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"ppv-lite86 0.2.5 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
]
|
||||
|
||||
@@ -382,7 +395,7 @@ 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)",
|
||||
"crossbeam-utils 0.6.6 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"lazy_static 1.3.0 (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)",
|
||||
"scopeguard 1.0.0 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
]
|
||||
@@ -401,7 +414,7 @@ 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)",
|
||||
"lazy_static 1.3.0 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"lazy_static 1.4.0 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -463,6 +476,7 @@ version = "1.0.0-alpha.4"
|
||||
dependencies = [
|
||||
"backtrace 0.3.34 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"base64 0.10.1 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"bitflags 1.1.0 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"byteorder 1.3.2 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"cc 1.0.40 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"charset 0.1.2 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
@@ -473,12 +487,13 @@ dependencies = [
|
||||
"failure_derive 0.1.5 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"hex 0.3.2 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"image-meta 0.1.0 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"imap 1.0.2 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"imap 1.0.2 (git+https://github.com/jonhoo/rust-imap?rev=281d2eb8ab50dc656ceff2ae749ca5045f334e15)",
|
||||
"itertools 0.8.0 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"lazy_static 1.3.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)",
|
||||
"libc 0.2.62 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"mmime 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"mmime 0.1.2-alpha.0 (git+https://github.com/dignifiedquire/mmime?rev=bccd2c2)",
|
||||
"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)",
|
||||
@@ -488,6 +503,7 @@ dependencies = [
|
||||
"pkg-config 0.3.15 (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)",
|
||||
"quick-xml 0.15.0 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"r2d2 0.8.5 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"r2d2_sqlite 0.12.0 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
@@ -948,23 +964,24 @@ dependencies = [
|
||||
[[package]]
|
||||
name = "imap"
|
||||
version = "1.0.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
source = "git+https://github.com/jonhoo/rust-imap?rev=281d2eb8ab50dc656ceff2ae749ca5045f334e15#281d2eb8ab50dc656ceff2ae749ca5045f334e15"
|
||||
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)",
|
||||
"chrono 0.4.7 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"imap-proto 0.7.0 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"imap-proto 0.9.0 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"lazy_static 1.4.0 (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)",
|
||||
"regex 1.2.1 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "imap-proto"
|
||||
version = "0.7.0"
|
||||
version = "0.9.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
dependencies = [
|
||||
"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)",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -994,6 +1011,11 @@ name = "itoa"
|
||||
version = "0.4.4"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
|
||||
[[package]]
|
||||
name = "jetscii"
|
||||
version = "0.4.4"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
|
||||
[[package]]
|
||||
name = "keccak"
|
||||
version = "0.1.0"
|
||||
@@ -1010,7 +1032,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "lazy_static"
|
||||
version = "1.3.0"
|
||||
version = "1.4.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
|
||||
[[package]]
|
||||
@@ -1030,6 +1052,18 @@ dependencies = [
|
||||
"serde_json 1.0.40 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "lexical-core"
|
||||
version = "0.4.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)",
|
||||
"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)",
|
||||
"stackvector 1.0.7 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"static_assertions 0.3.4 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "libc"
|
||||
version = "0.2.62"
|
||||
@@ -1191,13 +1225,13 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "mmime"
|
||||
version = "0.1.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
version = "0.1.2-alpha.0"
|
||||
source = "git+https://github.com/dignifiedquire/mmime?rev=bccd2c2#bccd2c2c89e9241e05f321c963f638affdccad96"
|
||||
dependencies = [
|
||||
"charset 0.1.2 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"chrono 0.4.7 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"hex 0.3.2 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"lazy_static 1.3.0 (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)",
|
||||
"memmap 0.7.0 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"rand 0.6.5 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
@@ -1208,7 +1242,7 @@ name = "native-tls"
|
||||
version = "0.2.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
dependencies = [
|
||||
"lazy_static 1.3.0 (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)",
|
||||
"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)",
|
||||
@@ -1256,13 +1290,23 @@ dependencies = [
|
||||
"version_check 0.1.5 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "nom"
|
||||
version = "5.0.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
dependencies = [
|
||||
"lexical-core 0.4.3 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"memchr 2.2.1 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"version_check 0.1.5 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "num-bigint-dig"
|
||||
version = "0.4.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
dependencies = [
|
||||
"byteorder 1.3.2 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"lazy_static 1.3.0 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"lazy_static 1.4.0 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"num-integer 0.1.41 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"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)",
|
||||
@@ -1337,7 +1381,7 @@ dependencies = [
|
||||
"bitflags 1.1.0 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"cfg-if 0.1.9 (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.3.0 (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)",
|
||||
]
|
||||
@@ -1507,7 +1551,7 @@ dependencies = [
|
||||
"flate2 1.0.11 (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)",
|
||||
"lazy_static 1.3.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)",
|
||||
"nom 4.2.3 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
@@ -1623,6 +1667,25 @@ dependencies = [
|
||||
"unicode-xid 0.2.0 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "proptest"
|
||||
version = "0.9.4"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
dependencies = [
|
||||
"bit-set 0.5.1 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"bitflags 1.1.0 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"byteorder 1.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)",
|
||||
"num-traits 0.2.8 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"quick-error 1.2.2 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"rand 0.6.5 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"rand_chacha 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"rand_xorshift 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"regex-syntax 0.6.11 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"rusty-fork 0.2.2 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"tempfile 3.1.0 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "publicsuffix"
|
||||
version = "1.5.2"
|
||||
@@ -1630,7 +1693,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
dependencies = [
|
||||
"error-chain 0.12.1 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"idna 0.1.5 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"lazy_static 1.3.0 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"lazy_static 1.4.0 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"regex 1.2.1 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"url 1.7.2 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
]
|
||||
@@ -1949,7 +2012,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)",
|
||||
"failure 0.1.5 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"lazy_static 1.3.0 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"lazy_static 1.4.0 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"num-bigint-dig 0.4.0 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"num-integer 0.1.41 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"num-iter 0.1.39 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
@@ -1996,6 +2059,17 @@ dependencies = [
|
||||
"semver 0.9.0 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "rusty-fork"
|
||||
version = "0.2.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
dependencies = [
|
||||
"fnv 1.0.6 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"quick-error 1.2.2 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"tempfile 3.1.0 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"wait-timeout 0.2.0 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "rustyline"
|
||||
version = "4.1.0"
|
||||
@@ -2035,7 +2109,7 @@ name = "schannel"
|
||||
version = "0.1.15"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
dependencies = [
|
||||
"lazy_static 1.3.0 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"lazy_static 1.4.0 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"winapi 0.3.7 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
]
|
||||
|
||||
@@ -2208,6 +2282,20 @@ name = "stable_deref_trait"
|
||||
version = "1.1.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
|
||||
[[package]]
|
||||
name = "stackvector"
|
||||
version = "1.0.7"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
dependencies = [
|
||||
"rustc_version 0.2.3 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"unreachable 1.0.0 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "static_assertions"
|
||||
version = "0.3.4"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
|
||||
[[package]]
|
||||
name = "stream-cipher"
|
||||
version = "0.3.0"
|
||||
@@ -2342,7 +2430,7 @@ name = "thread_local"
|
||||
version = "0.3.6"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
dependencies = [
|
||||
"lazy_static 1.3.0 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"lazy_static 1.4.0 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -2418,7 +2506,7 @@ 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)",
|
||||
"futures 0.1.28 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"lazy_static 1.3.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)",
|
||||
"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)",
|
||||
@@ -2563,6 +2651,14 @@ name = "unicode-xid"
|
||||
version = "0.2.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
|
||||
[[package]]
|
||||
name = "unreachable"
|
||||
version = "1.0.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
dependencies = [
|
||||
"void 1.0.2 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "unsafe-any"
|
||||
version = "0.4.2"
|
||||
@@ -2618,6 +2714,14 @@ name = "void"
|
||||
version = "1.0.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
|
||||
[[package]]
|
||||
name = "wait-timeout"
|
||||
version = "0.2.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)",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "walkdir"
|
||||
version = "2.2.9"
|
||||
@@ -2765,6 +2869,8 @@ dependencies = [
|
||||
"checksum backtrace 0.3.34 (registry+https://github.com/rust-lang/crates.io-index)" = "b5164d292487f037ece34ec0de2fcede2faa162f085dd96d2385ab81b12765ba"
|
||||
"checksum backtrace-sys 0.1.31 (registry+https://github.com/rust-lang/crates.io-index)" = "82a830b4ef2d1124a711c71d263c5abdc710ef8e907bd508c88be475cebc422b"
|
||||
"checksum base64 0.10.1 (registry+https://github.com/rust-lang/crates.io-index)" = "0b25d992356d2eb0ed82172f5248873db5560c4721f564b13cb5193bda5e668e"
|
||||
"checksum bit-set 0.5.1 (registry+https://github.com/rust-lang/crates.io-index)" = "e84c238982c4b1e1ee668d136c510c67a13465279c0cb367ea6baf6310620a80"
|
||||
"checksum bit-vec 0.5.1 (registry+https://github.com/rust-lang/crates.io-index)" = "f59bbe95d4e52a6398ec21238d31577f2b28a9d86807f06ca59d191d8440d0bb"
|
||||
"checksum bitfield 0.13.2 (registry+https://github.com/rust-lang/crates.io-index)" = "46afbd2983a5d5a7bd740ccb198caf5b82f45c40c09c0eed36052d91cb92e719"
|
||||
"checksum bitflags 1.1.0 (registry+https://github.com/rust-lang/crates.io-index)" = "3d155346769a6855b86399e9bc3814ab343cd3d62c7e985113d46a0ec3c281fd"
|
||||
"checksum blake2b_simd 0.5.6 (registry+https://github.com/rust-lang/crates.io-index)" = "461f4b879a8eb70c1debf7d0788a9a5ff15f1ea9d25925fea264ef4258bed6b2"
|
||||
@@ -2854,16 +2960,18 @@ dependencies = [
|
||||
"checksum ident_case 1.0.1 (registry+https://github.com/rust-lang/crates.io-index)" = "b9e0384b61958566e926dc50660321d12159025e767c18e043daf26b70104c39"
|
||||
"checksum idna 0.1.5 (registry+https://github.com/rust-lang/crates.io-index)" = "38f09e0f0b1fb55fdee1f17470ad800da77af5186a1a76c026b679358b7e844e"
|
||||
"checksum image-meta 0.1.0 (registry+https://github.com/rust-lang/crates.io-index)" = "b00861cbbb254a627d8acc0cec786b484297d896ab8f20fdc8e28536a3e918ef"
|
||||
"checksum imap 1.0.2 (registry+https://github.com/rust-lang/crates.io-index)" = "178392198cb2716f4fe34b86a1bf1de1240a3e31136a199b16490fa87538fa25"
|
||||
"checksum imap-proto 0.7.0 (registry+https://github.com/rust-lang/crates.io-index)" = "c4e77b1d61faf028893531b071cc5584cdd02b6186cebe7f7168ffd8d591339a"
|
||||
"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.0.2 (registry+https://github.com/rust-lang/crates.io-index)" = "7e81a7c05f79578dbc15793d8b619db9ba32b4577003ef3af1a91c416798c58d"
|
||||
"checksum iovec 0.1.2 (registry+https://github.com/rust-lang/crates.io-index)" = "dbe6e417e7d0975db6512b90796e8ce223145ac4e33c377e4a42882a0e88bb08"
|
||||
"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.3.0 (registry+https://github.com/rust-lang/crates.io-index)" = "bc5729f27f159ddd61f4df6228e827e86643d4d3e7c32183cb30a1c08f604a14"
|
||||
"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 lexical-core 0.4.3 (registry+https://github.com/rust-lang/crates.io-index)" = "b8b0f90c979adde96d19eb10eb6431ba0c441e2f9e9bdff868b2f6f5114ff519"
|
||||
"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"
|
||||
@@ -2883,12 +2991,13 @@ dependencies = [
|
||||
"checksum miniz_oxide 0.3.2 (registry+https://github.com/rust-lang/crates.io-index)" = "7108aff85b876d06f22503dcce091e29f76733b2bfdd91eebce81f5e68203a10"
|
||||
"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 mmime 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)" = "a1246fa340840c36f1fca1507db82463fbc4c2f7763fe84bfde666c7381e0593"
|
||||
"checksum mmime 0.1.2-alpha.0 (git+https://github.com/dignifiedquire/mmime?rev=bccd2c2)" = "<none>"
|
||||
"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 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"
|
||||
"checksum num-derive 0.2.5 (registry+https://github.com/rust-lang/crates.io-index)" = "eafd0b45c5537c3ba526f79d3e75120036502bebacbb3f3220914067ce39dbf2"
|
||||
"checksum num-integer 0.1.41 (registry+https://github.com/rust-lang/crates.io-index)" = "b85e541ef8255f6cf42bbfe4ef361305c6c135d10919ecc26126c4e5ae94bc09"
|
||||
@@ -2925,6 +3034,7 @@ dependencies = [
|
||||
"checksum proc-macro-hack 0.5.9 (registry+https://github.com/rust-lang/crates.io-index)" = "e688f31d92ffd7c1ddc57a1b4e6d773c0f2a14ee437a4b0a4f5a69c80eb221c8"
|
||||
"checksum proc-macro2 0.4.30 (registry+https://github.com/rust-lang/crates.io-index)" = "cf3d2011ab5c909338f7887f4fc896d35932e29146c12c8d01da6b22a80ba759"
|
||||
"checksum proc-macro2 1.0.1 (registry+https://github.com/rust-lang/crates.io-index)" = "4c5c2380ae88876faae57698be9e9775e3544decad214599c3a6266cca6ac802"
|
||||
"checksum proptest 0.9.4 (registry+https://github.com/rust-lang/crates.io-index)" = "cf147e022eacf0c8a054ab864914a7602618adba841d800a9a9868a5237a529f"
|
||||
"checksum publicsuffix 1.5.2 (registry+https://github.com/rust-lang/crates.io-index)" = "5afecba86dcf1e4fd610246f89899d1924fe12e1e89f555eb7c7f710f3c5ad1d"
|
||||
"checksum pulldown-cmark 0.2.0 (registry+https://github.com/rust-lang/crates.io-index)" = "eef52fac62d0ea7b9b4dc7da092aa64ea7ec3d90af6679422d3d7e0e14b6ee15"
|
||||
"checksum quick-error 1.2.2 (registry+https://github.com/rust-lang/crates.io-index)" = "9274b940887ce9addde99c4eee6b5c44cc494b182b97e73dc8ffdcb3397fd3f0"
|
||||
@@ -2962,6 +3072,7 @@ dependencies = [
|
||||
"checksum rust-argon2 0.5.1 (registry+https://github.com/rust-lang/crates.io-index)" = "4ca4eaef519b494d1f2848fc602d18816fed808a981aedf4f1f00ceb7c9d32cf"
|
||||
"checksum rustc-demangle 0.1.16 (registry+https://github.com/rust-lang/crates.io-index)" = "4c691c0e608126e00913e33f0ccf3727d5fc84573623b8d65b2df340b5201783"
|
||||
"checksum rustc_version 0.2.3 (registry+https://github.com/rust-lang/crates.io-index)" = "138e3e0acb6c9fb258b19b67cb8abd63c00679d2851805ea151465464fe9030a"
|
||||
"checksum rusty-fork 0.2.2 (registry+https://github.com/rust-lang/crates.io-index)" = "3dd93264e10c577503e926bd1430193eeb5d21b059148910082245309b424fae"
|
||||
"checksum rustyline 4.1.0 (registry+https://github.com/rust-lang/crates.io-index)" = "0f47ea1ceb347d2deae482d655dc8eef4bd82363d3329baffa3818bd76fea48b"
|
||||
"checksum ryu 1.0.0 (registry+https://github.com/rust-lang/crates.io-index)" = "c92464b447c0ee8c4fb3824ecc8383b81717b9f1e74ba2e72540aef7b9f82997"
|
||||
"checksum safemem 0.3.2 (registry+https://github.com/rust-lang/crates.io-index)" = "d2b08423011dae9a5ca23f07cf57dac3857f5c885d352b76f6d95f4aea9434d0"
|
||||
@@ -2987,6 +3098,8 @@ dependencies = [
|
||||
"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 stackvector 1.0.7 (registry+https://github.com/rust-lang/crates.io-index)" = "4dade1e9ad1ce13baaba168a04cdefb5c0cbc100242a4035d6dfa9c64348f9c5"
|
||||
"checksum static_assertions 0.3.4 (registry+https://github.com/rust-lang/crates.io-index)" = "7f3eb36b47e512f8f1c9e3d10c2c1965bc992bd9cdb024fa581e2194501c83d3"
|
||||
"checksum stream-cipher 0.3.0 (registry+https://github.com/rust-lang/crates.io-index)" = "8861bc80f649f5b4c9bd38b696ae9af74499d479dbfb327f0607de6b326a36bc"
|
||||
"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"
|
||||
@@ -3027,6 +3140,7 @@ dependencies = [
|
||||
"checksum unicode-width 0.1.5 (registry+https://github.com/rust-lang/crates.io-index)" = "882386231c45df4700b275c7ff55b6f3698780a650026380e72dabe76fa46526"
|
||||
"checksum unicode-xid 0.1.0 (registry+https://github.com/rust-lang/crates.io-index)" = "fc72304796d0818e357ead4e000d19c9c174ab23dc11093ac919054d20a6a7fc"
|
||||
"checksum unicode-xid 0.2.0 (registry+https://github.com/rust-lang/crates.io-index)" = "826e7639553986605ec5979c7dd957c7895e93eabed50ab2ffa7f6128a75097c"
|
||||
"checksum unreachable 1.0.0 (registry+https://github.com/rust-lang/crates.io-index)" = "382810877fe448991dfc7f0dd6e3ae5d58088fd0ea5e35189655f84e6814fa56"
|
||||
"checksum unsafe-any 0.4.2 (registry+https://github.com/rust-lang/crates.io-index)" = "f30360d7979f5e9c6e6cea48af192ea8fab4afb3cf72597154b8f08935bc9c7f"
|
||||
"checksum url 1.7.2 (registry+https://github.com/rust-lang/crates.io-index)" = "dd4e7c0d531266369519a4aa4f399d748bd37043b00bde1e4ff1f60a120b355a"
|
||||
"checksum utf8parse 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)" = "8772a4ccbb4e89959023bc5b7cb8623a795caa7092d99f3aa9501b9484d4557d"
|
||||
@@ -3035,6 +3149,7 @@ dependencies = [
|
||||
"checksum vcpkg 0.2.7 (registry+https://github.com/rust-lang/crates.io-index)" = "33dd455d0f96e90a75803cfeb7f948768c08d70a6de9a8d2362461935698bf95"
|
||||
"checksum version_check 0.1.5 (registry+https://github.com/rust-lang/crates.io-index)" = "914b1a6776c4c929a602fafd8bc742e06365d4bcbe48c30f9cca5824f70dc9dd"
|
||||
"checksum void 1.0.2 (registry+https://github.com/rust-lang/crates.io-index)" = "6a02e4885ed3bc0f2de90ea6dd45ebcbb66dacffe03547fadbb0eeae2770887d"
|
||||
"checksum wait-timeout 0.2.0 (registry+https://github.com/rust-lang/crates.io-index)" = "9f200f5b12eb75f8c1ed65abd4b2db8a6e1b138a20de009dacee265a2498f3f6"
|
||||
"checksum walkdir 2.2.9 (registry+https://github.com/rust-lang/crates.io-index)" = "9658c94fa8b940eab2250bd5a457f9c48b748420d71293b165c8cdbe2f55f71e"
|
||||
"checksum want 0.2.0 (registry+https://github.com/rust-lang/crates.io-index)" = "b6395efa4784b027708f7451087e647ec73cc74f5d9bc2e418404248d679a230"
|
||||
"checksum wasi 0.5.0 (registry+https://github.com/rust-lang/crates.io-index)" = "fd5442abcac6525a045cc8c795aedb60da7a2e5e89c7bf18a0d5357849bb23c7"
|
||||
|
||||
@@ -23,8 +23,8 @@ num-derive = "0.2.5"
|
||||
num-traits = "0.2.6"
|
||||
native-tls = "0.2.3"
|
||||
lettre = "0.9.0"
|
||||
imap = "1.0.1"
|
||||
mmime = "0.1.0"
|
||||
imap = { git = "https://github.com/jonhoo/rust-imap", rev = "281d2eb8ab50dc656ceff2ae749ca5045f334e15" }
|
||||
mmime = { git = "https://github.com/dignifiedquire/mmime", rev = "bccd2c2" }
|
||||
base64 = "0.10"
|
||||
charset = "0.1"
|
||||
percent-encoding = "2.0"
|
||||
@@ -35,7 +35,7 @@ failure = "0.1.5"
|
||||
failure_derive = "0.1.5"
|
||||
# TODO: make optional
|
||||
rustyline = "4.1.0"
|
||||
lazy_static = "1.3.0"
|
||||
lazy_static = "1.4.0"
|
||||
regex = "1.1.6"
|
||||
rusqlite = { version = "0.20", features = ["bundled"] }
|
||||
r2d2_sqlite = "0.12.0"
|
||||
@@ -49,11 +49,14 @@ itertools = "0.8.0"
|
||||
image-meta = "0.1.0"
|
||||
quick-xml = "0.15.0"
|
||||
escaper = "0.1.0"
|
||||
bitflags = "1.1.0"
|
||||
jetscii = "0.4.4"
|
||||
|
||||
[dev-dependencies]
|
||||
tempfile = "3.0"
|
||||
pretty_assertions = "0.6.1"
|
||||
pretty_env_logger = "0.3.0"
|
||||
proptest = "0.9.4"
|
||||
|
||||
[workspace]
|
||||
members = [
|
||||
|
||||
@@ -34,7 +34,7 @@ echo -----------------------
|
||||
# Bundle external shared libraries into the wheels
|
||||
pushd $WHEELHOUSEDIR
|
||||
|
||||
pip install devpi-client
|
||||
pip3 install devpi-client
|
||||
devpi use https://m.devpi.net
|
||||
devpi login dc --password $DEVPI_LOGIN
|
||||
|
||||
|
||||
@@ -41,7 +41,7 @@ if [ -n "$TESTS" ]; then
|
||||
# see https://github.com/deltachat/deltachat-core-rust/issues/331
|
||||
# unset DCC_PY_LIVECONFIG
|
||||
|
||||
tox --workdir "$TOXWORKDIR" -e lint,py27,py35,py36,py37,auditwheels
|
||||
tox --workdir "$TOXWORKDIR" -e lint,py35,py36,py37,auditwheels
|
||||
popd
|
||||
fi
|
||||
|
||||
|
||||
@@ -19,7 +19,6 @@ deltachat = { path = "../", default-features = false }
|
||||
libc = "0.2"
|
||||
human-panic = "1.0.1"
|
||||
num-traits = "0.2.6"
|
||||
rental = "0.5.4"
|
||||
|
||||
[features]
|
||||
default = ["vendored", "nightly", "ringbuf"]
|
||||
|
||||
2423
deltachat-ffi/Doxyfile
Normal file
2423
deltachat-ffi/Doxyfile
Normal file
File diff suppressed because it is too large
Load Diff
BIN
deltachat-ffi/Doxyfile-logo.png
Normal file
BIN
deltachat-ffi/Doxyfile-logo.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 3.9 KiB |
7
deltachat-ffi/Doxyfile.css
Normal file
7
deltachat-ffi/Doxyfile.css
Normal file
@@ -0,0 +1,7 @@
|
||||
|
||||
/* the code snippet frame, defaults to white which tends to get badly readable in combination with explaining text around */
|
||||
div.fragment {
|
||||
background-color: #e0e0e0;
|
||||
border: 0;
|
||||
padding: 1em;
|
||||
}
|
||||
@@ -1 +1,10 @@
|
||||
# Delta Chat C Interface
|
||||
|
||||
## Documentation
|
||||
|
||||
To generate the C Interface documentation,
|
||||
call doxygen in the `deltachat-ffi` directory
|
||||
and browse the `html` subdirectory.
|
||||
|
||||
If thinks work,
|
||||
the documentation is also available online at <https://c.delta.chat>
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
@@ -36,7 +36,7 @@ pub fn from_sql_derive(input: TokenStream) -> TokenStream {
|
||||
impl rusqlite::types::FromSql for #name {
|
||||
fn column_result(col: rusqlite::types::ValueRef) -> rusqlite::types::FromSqlResult<Self> {
|
||||
let inner = rusqlite::types::FromSql::column_result(col)?;
|
||||
num_traits::FromPrimitive::from_i64(inner).ok_or(rusqlite::types::FromSqlError::InvalidType)
|
||||
Ok(num_traits::FromPrimitive::from_i64(inner).unwrap_or_default())
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
use std::ffi::CString;
|
||||
use std::ptr;
|
||||
use std::str::FromStr;
|
||||
|
||||
use deltachat::chat::{self, Chat};
|
||||
@@ -9,11 +10,11 @@ use deltachat::constants::*;
|
||||
use deltachat::contact::*;
|
||||
use deltachat::context::*;
|
||||
use deltachat::dc_imex::*;
|
||||
use deltachat::dc_location::*;
|
||||
use deltachat::dc_receive_imf::*;
|
||||
use deltachat::dc_tools::*;
|
||||
use deltachat::error::Error;
|
||||
use deltachat::job::*;
|
||||
use deltachat::location;
|
||||
use deltachat::lot::LotState;
|
||||
use deltachat::message::*;
|
||||
use deltachat::peerstate::*;
|
||||
@@ -94,7 +95,7 @@ pub unsafe fn dc_reset_tables(context: &Context, bits: i32) -> i32 {
|
||||
unsafe fn dc_poke_eml_file(context: &Context, filename: *const libc::c_char) -> libc::c_int {
|
||||
/* mainly for testing, may be called by dc_import_spec() */
|
||||
let mut success: libc::c_int = 0i32;
|
||||
let mut data: *mut libc::c_char = 0 as *mut libc::c_char;
|
||||
let mut data: *mut libc::c_char = ptr::null_mut();
|
||||
let mut data_bytes: size_t = 0;
|
||||
if !(dc_read_file(
|
||||
context,
|
||||
@@ -128,7 +129,7 @@ unsafe fn poke_spec(context: &Context, spec: *const libc::c_char) -> libc::c_int
|
||||
let ok_to_continue;
|
||||
let mut success: libc::c_int = 0;
|
||||
let real_spec: *mut libc::c_char;
|
||||
let mut suffix: *mut libc::c_char = 0 as *mut libc::c_char;
|
||||
let mut suffix: *mut libc::c_char = ptr::null_mut();
|
||||
let mut read_cnt: libc::c_int = 0;
|
||||
|
||||
/* if `spec` is given, remember it for later usage; if it is not given, try to use the last one */
|
||||
@@ -341,12 +342,6 @@ unsafe fn log_contactlist(context: &Context, contacts: &Vec<u32>) {
|
||||
}
|
||||
}
|
||||
|
||||
static mut S_IS_AUTH: libc::c_int = 0;
|
||||
|
||||
pub unsafe fn dc_cmdline_skip_auth() {
|
||||
S_IS_AUTH = 1;
|
||||
}
|
||||
|
||||
fn chat_prefix(chat: &Chat) -> &'static str {
|
||||
chat.typ.into()
|
||||
}
|
||||
@@ -396,6 +391,8 @@ pub unsafe fn dc_cmdline(context: &Context, line: &str) -> Result<(), failure::E
|
||||
_ => println!(
|
||||
"==========================Database commands==\n\
|
||||
info\n\
|
||||
open <file to open or create>\n\
|
||||
close\n\
|
||||
set <configuration-key> [<value>]\n\
|
||||
get <configuration-key>\n\
|
||||
oauth2\n\
|
||||
@@ -425,7 +422,7 @@ pub unsafe fn dc_cmdline(context: &Context, line: &str) -> Result<(), failure::E
|
||||
send <text>\n\
|
||||
send-garbage\n\
|
||||
sendimage <file> [<text>]\n\
|
||||
sendfile <file>\n\
|
||||
sendfile <file> [<text>]\n\
|
||||
draft [<text>]\n\
|
||||
listmedia\n\
|
||||
archive <chat-id>\n\
|
||||
@@ -458,19 +455,13 @@ pub unsafe fn dc_cmdline(context: &Context, line: &str) -> Result<(), failure::E
|
||||
============================================="
|
||||
),
|
||||
},
|
||||
"auth" => {
|
||||
if 0 == S_IS_AUTH {
|
||||
let is_pw = context
|
||||
.get_config(config::Config::MailPw)
|
||||
.unwrap_or_default();
|
||||
if arg1 == is_pw {
|
||||
S_IS_AUTH = 1;
|
||||
} else {
|
||||
println!("Bad password.");
|
||||
}
|
||||
} else {
|
||||
println!("Already authorized.");
|
||||
}
|
||||
"open" => {
|
||||
ensure!(!arg1.is_empty(), "Argument <file> missing");
|
||||
dc_close(context);
|
||||
ensure!(dc_open(context, arg1, None), "Open failed");
|
||||
}
|
||||
"close" => {
|
||||
dc_close(context);
|
||||
}
|
||||
"initiate-key-transfer" => {
|
||||
let setup_code = dc_initiate_key_transfer(context);
|
||||
@@ -516,17 +507,17 @@ pub unsafe fn dc_cmdline(context: &Context, line: &str) -> Result<(), failure::E
|
||||
}
|
||||
}
|
||||
"export-backup" => {
|
||||
dc_imex(context, 11, context.get_blobdir(), 0 as *const libc::c_char);
|
||||
dc_imex(context, 11, context.get_blobdir(), ptr::null());
|
||||
}
|
||||
"import-backup" => {
|
||||
ensure!(!arg1.is_empty(), "Argument <backup-file> missing.");
|
||||
dc_imex(context, 12, arg1_c, 0 as *const libc::c_char);
|
||||
dc_imex(context, 12, arg1_c, ptr::null());
|
||||
}
|
||||
"export-keys" => {
|
||||
dc_imex(context, 1, context.get_blobdir(), 0 as *const libc::c_char);
|
||||
dc_imex(context, 1, context.get_blobdir(), ptr::null());
|
||||
}
|
||||
"import-keys" => {
|
||||
dc_imex(context, 2, context.get_blobdir(), 0 as *const libc::c_char);
|
||||
dc_imex(context, 2, context.get_blobdir(), ptr::null());
|
||||
}
|
||||
"export-setup" => {
|
||||
let setup_code = dc_create_setup_code(context);
|
||||
@@ -642,7 +633,7 @@ pub unsafe fn dc_cmdline(context: &Context, line: &str) -> Result<(), failure::E
|
||||
);
|
||||
}
|
||||
}
|
||||
if dc_is_sending_locations_to_chat(context, 0 as uint32_t) {
|
||||
if location::is_sending_locations_to_chat(context, 0 as uint32_t) {
|
||||
info!(context, 0, "Location streaming enabled.");
|
||||
}
|
||||
println!("{} chats", cnt);
|
||||
@@ -768,14 +759,17 @@ pub unsafe fn dc_cmdline(context: &Context, line: &str) -> Result<(), failure::E
|
||||
println!(
|
||||
"{} contacts\nLocation streaming: {}",
|
||||
contacts.len(),
|
||||
dc_is_sending_locations_to_chat(context, sel_chat.as_ref().unwrap().get_id()),
|
||||
location::is_sending_locations_to_chat(
|
||||
context,
|
||||
sel_chat.as_ref().unwrap().get_id()
|
||||
),
|
||||
);
|
||||
}
|
||||
"getlocations" => {
|
||||
ensure!(sel_chat.is_some(), "No chat selected.");
|
||||
|
||||
let contact_id = arg1.parse().unwrap_or_default();
|
||||
let locations = dc_get_locations(
|
||||
let locations = location::get_range(
|
||||
context,
|
||||
sel_chat.as_ref().unwrap().get_id(),
|
||||
contact_id,
|
||||
@@ -809,7 +803,7 @@ pub unsafe fn dc_cmdline(context: &Context, line: &str) -> Result<(), failure::E
|
||||
ensure!(!arg1.is_empty(), "No timeout given.");
|
||||
|
||||
let seconds = arg1.parse()?;
|
||||
dc_send_locations_to_chat(context, sel_chat.as_ref().unwrap().get_id(), seconds);
|
||||
location::send_locations_to_chat(context, sel_chat.as_ref().unwrap().get_id(), seconds);
|
||||
println!(
|
||||
"Locations will be sent to Chat#{} for {} seconds. Use 'setlocation <lat> <lng>' to play around.",
|
||||
sel_chat.as_ref().unwrap().get_id(),
|
||||
@@ -824,7 +818,7 @@ pub unsafe fn dc_cmdline(context: &Context, line: &str) -> Result<(), failure::E
|
||||
let latitude = arg1.parse()?;
|
||||
let longitude = arg2.parse()?;
|
||||
|
||||
let continue_streaming = dc_set_location(context, latitude, longitude, 0.);
|
||||
let continue_streaming = location::set(context, latitude, longitude, 0.);
|
||||
if 0 != continue_streaming {
|
||||
println!("Success, streaming should be continued.");
|
||||
} else {
|
||||
@@ -832,7 +826,7 @@ pub unsafe fn dc_cmdline(context: &Context, line: &str) -> Result<(), failure::E
|
||||
}
|
||||
}
|
||||
"dellocations" => {
|
||||
dc_delete_all_locations(context);
|
||||
location::delete_all(context)?;
|
||||
}
|
||||
"send" => {
|
||||
ensure!(sel_chat.is_some(), "No chat selected.");
|
||||
@@ -848,7 +842,7 @@ pub unsafe fn dc_cmdline(context: &Context, line: &str) -> Result<(), failure::E
|
||||
}
|
||||
"sendimage" | "sendfile" => {
|
||||
ensure!(sel_chat.is_some(), "No chat selected.");
|
||||
ensure!(!arg1.is_empty() && !arg2.is_empty(), "No file given.");
|
||||
ensure!(!arg1.is_empty(), "No file given.");
|
||||
|
||||
let mut msg = dc_msg_new(
|
||||
context,
|
||||
@@ -858,8 +852,10 @@ pub unsafe fn dc_cmdline(context: &Context, line: &str) -> Result<(), failure::E
|
||||
Viewtype::File
|
||||
},
|
||||
);
|
||||
dc_msg_set_file(&mut msg, arg1_c, 0 as *const libc::c_char);
|
||||
dc_msg_set_text(&mut msg, arg2_c);
|
||||
dc_msg_set_file(&mut msg, arg1_c, ptr::null());
|
||||
if !arg2.is_empty() {
|
||||
dc_msg_set_text(&mut msg, arg2_c);
|
||||
}
|
||||
chat::send_msg(context, sel_chat.as_ref().unwrap().get_id(), &mut msg)?;
|
||||
}
|
||||
"listmsgs" => {
|
||||
|
||||
@@ -14,7 +14,9 @@ extern crate lazy_static;
|
||||
extern crate rusqlite;
|
||||
|
||||
use std::borrow::Cow::{self, Borrowed, Owned};
|
||||
use std::path::Path;
|
||||
use std::io::{self, Write};
|
||||
use std::process::Command;
|
||||
use std::ptr;
|
||||
use std::sync::atomic::{AtomicBool, Ordering};
|
||||
use std::sync::{Arc, Mutex, RwLock};
|
||||
|
||||
@@ -22,10 +24,10 @@ use deltachat::config;
|
||||
use deltachat::configure::*;
|
||||
use deltachat::constants::*;
|
||||
use deltachat::context::*;
|
||||
use deltachat::dc_securejoin::*;
|
||||
use deltachat::dc_tools::*;
|
||||
use deltachat::job::*;
|
||||
use deltachat::oauth2::*;
|
||||
use deltachat::securejoin::*;
|
||||
use deltachat::types::*;
|
||||
use deltachat::x::*;
|
||||
use rustyline::completion::{Completer, FilenameCompleter, Pair};
|
||||
@@ -42,7 +44,7 @@ use self::cmdline::*;
|
||||
|
||||
// Event Handler
|
||||
|
||||
fn receive_event(
|
||||
unsafe extern "C" fn receive_event(
|
||||
_context: &Context,
|
||||
event: Event,
|
||||
data1: uintptr_t,
|
||||
@@ -385,16 +387,15 @@ impl Highlighter for DcHelper {
|
||||
impl Helper for DcHelper {}
|
||||
|
||||
fn main_0(args: Vec<String>) -> Result<(), failure::Error> {
|
||||
unsafe { dc_cmdline_skip_auth() };
|
||||
if args.len() != 2 {
|
||||
let mut context = dc_context_new(Some(receive_event), ptr::null_mut(), Some("CLI".into()));
|
||||
|
||||
if args.len() == 2 {
|
||||
if unsafe { !dc_open(&mut context, &args[1], None) } {
|
||||
println!("Error: Cannot open {}.", args[0],);
|
||||
}
|
||||
} else if args.len() != 1 {
|
||||
println!("Error: Bad arguments, expected [db-name].");
|
||||
return Err(format_err!("No db-name specified"));
|
||||
}
|
||||
let context = Context::new(
|
||||
Box::new(receive_event),
|
||||
"CLI".into(),
|
||||
Path::new(&args[1]).to_path_buf(),
|
||||
)?;
|
||||
|
||||
println!("Delta Chat Core is awaiting your commands.");
|
||||
|
||||
@@ -515,30 +516,27 @@ unsafe fn handle_cmd(line: &str, ctx: Arc<RwLock<Context>>) -> Result<ExitResult
|
||||
}
|
||||
"getqr" | "getbadqr" => {
|
||||
start_threads(ctx.clone());
|
||||
let qrstr =
|
||||
dc_get_securejoin_qr(&ctx.read().unwrap(), arg1.parse().unwrap_or_default());
|
||||
if !qrstr.is_null() && 0 != *qrstr.offset(0isize) as libc::c_int {
|
||||
if arg0 == "getbadqr" && strlen(qrstr) > 40 {
|
||||
let mut i: libc::c_int = 12i32;
|
||||
while i < 22i32 {
|
||||
*qrstr.offset(i as isize) = '0' as i32 as libc::c_char;
|
||||
i += 1
|
||||
if let Some(mut qr) =
|
||||
dc_get_securejoin_qr(&ctx.read().unwrap(), arg1.parse().unwrap_or_default())
|
||||
{
|
||||
if !qr.is_empty() {
|
||||
if arg0 == "getbadqr" && qr.len() > 40 {
|
||||
qr.replace_range(12..22, "0000000000")
|
||||
}
|
||||
println!("{}", qr);
|
||||
let output = Command::new("qrencode")
|
||||
.args(&["-t", "ansiutf8", qr.as_str(), "-o", "-"])
|
||||
.output()
|
||||
.expect("failed to execute process");
|
||||
io::stdout().write_all(&output.stdout).unwrap();
|
||||
io::stderr().write_all(&output.stderr).unwrap();
|
||||
}
|
||||
println!("{}", to_string(qrstr as *const _));
|
||||
let syscmd = dc_mprintf(
|
||||
b"qrencode -t ansiutf8 \"%s\" -o -\x00" as *const u8 as *const libc::c_char,
|
||||
qrstr,
|
||||
);
|
||||
system(syscmd);
|
||||
free(syscmd as *mut libc::c_void);
|
||||
}
|
||||
free(qrstr as *mut libc::c_void);
|
||||
}
|
||||
"joinqr" => {
|
||||
start_threads(ctx.clone());
|
||||
if !arg0.is_empty() {
|
||||
dc_join_securejoin(&ctx.read().unwrap(), arg1_c);
|
||||
dc_join_securejoin(&ctx.read().unwrap(), arg1);
|
||||
}
|
||||
}
|
||||
"exit" | "quit" => return Ok(ExitResult::Exit),
|
||||
|
||||
@@ -16,8 +16,9 @@ use deltachat::job::{
|
||||
perform_imap_fetch, perform_imap_idle, perform_imap_jobs, perform_smtp_idle, perform_smtp_jobs,
|
||||
};
|
||||
|
||||
fn cb(_ctx: &Context, event: Event, data1: usize, data2: usize) -> usize {
|
||||
extern "C" fn cb(_ctx: &Context, event: Event, data1: usize, data2: usize) -> usize {
|
||||
println!("[{:?}]", event);
|
||||
|
||||
match event {
|
||||
Event::CONFIGURE_PROGRESS => {
|
||||
println!(" progress: {}", data1);
|
||||
@@ -38,11 +39,7 @@ fn cb(_ctx: &Context, event: Event, data1: usize, data2: usize) -> 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 ctx = dc_context_new(Some(cb), std::ptr::null_mut(), None);
|
||||
let running = Arc::new(RwLock::new(true));
|
||||
let info = dc_get_info(&ctx);
|
||||
let info_s = CStr::from_ptr(info);
|
||||
@@ -76,6 +73,13 @@ fn main() {
|
||||
}
|
||||
});
|
||||
|
||||
let dir = tempdir().unwrap();
|
||||
let dbfile = dir.path().join("db.sqlite");
|
||||
|
||||
println!("opening database {:?}", dbfile);
|
||||
|
||||
assert!(dc_open(&ctx, dbfile.to_str().unwrap(), None));
|
||||
|
||||
println!("configuring");
|
||||
let args = std::env::args().collect::<Vec<String>>();
|
||||
assert_eq!(args.len(), 2, "missing password");
|
||||
|
||||
7
proptest-regressions/dc_strencode.txt
Normal file
7
proptest-regressions/dc_strencode.txt
Normal file
@@ -0,0 +1,7 @@
|
||||
# Seeds for failure cases proptest has generated in the past. It is
|
||||
# automatically read and these particular cases re-run before any
|
||||
# novel cases are generated.
|
||||
#
|
||||
# It is recommended to check this file in to source control so that
|
||||
# everyone who runs the test benefits from these saved cases.
|
||||
cc 679506fe9ac59df773f8cfa800fdab5f0a32fe49d2ab370394000a1aa5bc2a72 # shrinks to buf = "%0A"
|
||||
9
proptest-regressions/dc_tools.txt
Normal file
9
proptest-regressions/dc_tools.txt
Normal file
@@ -0,0 +1,9 @@
|
||||
# Seeds for failure cases proptest has generated in the past. It is
|
||||
# automatically read and these particular cases re-run before any
|
||||
# novel cases are generated.
|
||||
#
|
||||
# It is recommended to check this file in to source control so that
|
||||
# everyone who runs the test benefits from these saved cases.
|
||||
cc c310754465ee0261807b96fa9bcc4861ff9aa286e94667524b5960c69f9b6620 # shrinks to buf = "", approx_chars = 0, do_unwrap = false
|
||||
cc 5fd8d730b0a9cdf7308ce58818ca9aefc0255c9ba2a0878944fc48d43a67315b # shrinks to buf = "𑒀ὐ¢🜀\u{1e01b}A a🟠", approx_chars = 0, do_unwrap = false
|
||||
cc c6a0029a54137a4b9efc9ef2ea6d9a7dd1d60d1c937bb472b66a174618ba8013 # shrinks to buf = "𐠈0Aᝮa𫝀®!ꫛa¡0A𐢧00𐹠®A 丽ⷐએ ", approx_chars = 0, do_unwrap = false
|
||||
@@ -72,10 +72,10 @@ then cargo-build and install the deltachat bindings::
|
||||
python install_python_bindings.py
|
||||
|
||||
The bindings will be installed in release mode but with debug symbols.
|
||||
The release mode is neccessary because some tests generate RSA keys
|
||||
The release mode is necessary because some tests generate RSA keys
|
||||
which is prohibitively slow in debug mode.
|
||||
|
||||
After succcessul binding installation you can finally run the tests::
|
||||
After successful binding installation you can finally run the tests::
|
||||
|
||||
pytest -v tests
|
||||
|
||||
|
||||
@@ -14,7 +14,7 @@ except ImportError:
|
||||
import deltachat
|
||||
from . import const
|
||||
from .capi import ffi, lib
|
||||
from .cutil import as_dc_charpointer, from_dc_charpointer, iter_array
|
||||
from .cutil import as_dc_charpointer, from_dc_charpointer, iter_array, DCLot
|
||||
from .chatting import Contact, Chat, Message
|
||||
|
||||
|
||||
@@ -329,6 +329,56 @@ class Account(object):
|
||||
raise RuntimeError("could not send out autocrypt setup message")
|
||||
return from_dc_charpointer(res)
|
||||
|
||||
def get_setup_contact_qr(self):
|
||||
""" get/create Setup-Contact QR Code as ascii-string.
|
||||
|
||||
this string needs to be transferred to another DC account
|
||||
in a second channel (typically used by mobiles with QRcode-show + scan UX)
|
||||
where qr_setup_contact(qr) is called.
|
||||
"""
|
||||
res = lib.dc_get_securejoin_qr(self._dc_context, 0)
|
||||
return from_dc_charpointer(res)
|
||||
|
||||
def check_qr(self, qr):
|
||||
""" check qr code and return :class:`ScannedQRCode` instance representing the result"""
|
||||
res = ffi.gc(
|
||||
lib.dc_check_qr(self._dc_context, as_dc_charpointer(qr)),
|
||||
lib.dc_lot_unref
|
||||
)
|
||||
lot = DCLot(res)
|
||||
if lot.state() == const.DC_QR_ERROR:
|
||||
raise ValueError("invalid or unknown QR code: {}".format(lot.text1()))
|
||||
return ScannedQRCode(lot)
|
||||
|
||||
def qr_setup_contact(self, qr):
|
||||
""" setup contact and return a Chat after contact is established.
|
||||
|
||||
Note that this function may block for a long time as messages are exchanged
|
||||
with the emitter of the QR code. On success a :class:`deltachat.chatting.Chat` instance
|
||||
is returned.
|
||||
:param qr: valid "setup contact" QR code (all other QR codes will result in an exception)
|
||||
"""
|
||||
assert self.check_qr(qr).is_ask_verifycontact()
|
||||
chat_id = lib.dc_join_securejoin(self._dc_context, as_dc_charpointer(qr))
|
||||
if chat_id == 0:
|
||||
raise ValueError("could not setup secure contact")
|
||||
return Chat(self, chat_id)
|
||||
|
||||
def qr_join_chat(self, qr):
|
||||
""" join a chat group through a QR code.
|
||||
|
||||
Note that this function may block for a long time as messages are exchanged
|
||||
with the emitter of the QR code. On success a :class:`deltachat.chatting.Chat` instance
|
||||
is returned which is the chat that we just joined.
|
||||
|
||||
:param qr: valid "join-group" QR code (all other QR codes will result in an exception)
|
||||
"""
|
||||
assert self.check_qr(qr).is_ask_verifygroup()
|
||||
chat_id = lib.dc_join_securejoin(self._dc_context, as_dc_charpointer(qr))
|
||||
if chat_id == 0:
|
||||
raise ValueError("could not join group")
|
||||
return Chat(self, chat_id)
|
||||
|
||||
def start_threads(self):
|
||||
""" start IMAP/SMTP threads (and configure account if it hasn't happened).
|
||||
|
||||
@@ -492,3 +542,18 @@ def _destroy_dc_context(dc_context, dc_context_unref=lib.dc_context_unref):
|
||||
# we are deep into Python Interpreter shutdown,
|
||||
# so no need to clear the callback context mapping.
|
||||
pass
|
||||
|
||||
|
||||
class ScannedQRCode:
|
||||
def __init__(self, dc_lot):
|
||||
self._dc_lot = dc_lot
|
||||
|
||||
def is_ask_verifycontact(self):
|
||||
return self._dc_lot.state() == const.DC_QR_ASK_VERIFYCONTACT
|
||||
|
||||
def is_ask_verifygroup(self):
|
||||
return self._dc_lot.state() == const.DC_QR_ASK_VERIFYGROUP
|
||||
|
||||
@property
|
||||
def contact_id(self):
|
||||
return self._dc_lot.id()
|
||||
|
||||
@@ -131,6 +131,16 @@ class Chat(object):
|
||||
"""
|
||||
return lib.dc_chat_get_type(self._dc_chat)
|
||||
|
||||
def get_join_qr(self):
|
||||
""" get/create Join-Group QR Code as ascii-string.
|
||||
|
||||
this string needs to be transferred to another DC account
|
||||
in a second channel (typically used by mobiles with QRcode-show + scan UX)
|
||||
where account.join_with_qrcode(qr) needs to be called.
|
||||
"""
|
||||
res = lib.dc_get_securejoin_qr(self._dc_context, self.id)
|
||||
return from_dc_charpointer(res)
|
||||
|
||||
# ------ chat messaging API ------------------------------
|
||||
|
||||
def send_text(self, text):
|
||||
|
||||
@@ -13,6 +13,15 @@ DC_GCL_NO_SPECIALS = 0x02
|
||||
DC_GCL_ADD_ALLDONE_HINT = 0x04
|
||||
DC_GCL_VERIFIED_ONLY = 0x01
|
||||
DC_GCL_ADD_SELF = 0x02
|
||||
DC_QR_ASK_VERIFYCONTACT = 200
|
||||
DC_QR_ASK_VERIFYGROUP = 202
|
||||
DC_QR_FPR_OK = 210
|
||||
DC_QR_FPR_MISMATCH = 220
|
||||
DC_QR_FPR_WITHOUT_ADDR = 230
|
||||
DC_QR_ADDR = 320
|
||||
DC_QR_TEXT = 330
|
||||
DC_QR_URL = 332
|
||||
DC_QR_ERROR = 400
|
||||
DC_CHAT_ID_DEADDROP = 1
|
||||
DC_CHAT_ID_TRASH = 3
|
||||
DC_CHAT_ID_MSGS_IN_CREATION = 4
|
||||
@@ -69,15 +78,13 @@ DC_EVENT_IMEX_FILE_WRITTEN = 2052
|
||||
DC_EVENT_SECUREJOIN_INVITER_PROGRESS = 2060
|
||||
DC_EVENT_SECUREJOIN_JOINER_PROGRESS = 2061
|
||||
DC_EVENT_GET_STRING = 2091
|
||||
DC_EVENT_HTTP_GET = 2100
|
||||
DC_EVENT_HTTP_POST = 2110
|
||||
DC_EVENT_FILE_COPIED = 2055
|
||||
DC_EVENT_IS_OFFLINE = 2081
|
||||
# end const generated
|
||||
|
||||
|
||||
def read_event_defines(f):
|
||||
rex = re.compile(r'#define\s+((?:DC_EVENT_|DC_MSG|DC_STATE_|DC_CONTACT_ID_|DC_GCL|DC_CHAT)\S+)\s+([x\d]+).*')
|
||||
rex = re.compile(r'#define\s+((?:DC_EVENT_|DC_QR|DC_MSG|DC_STATE_|DC_CONTACT_ID_|DC_GCL|DC_CHAT)\S+)\s+([x\d]+).*')
|
||||
for line in f:
|
||||
m = rex.match(line)
|
||||
if m:
|
||||
@@ -90,7 +97,7 @@ if __name__ == "__main__":
|
||||
if len(sys.argv) >= 2:
|
||||
deltah = sys.argv[1]
|
||||
else:
|
||||
deltah = joinpath(dirname(dirname(dirname(here_dir))), "src", "deltachat.h")
|
||||
deltah = joinpath(dirname(dirname(dirname(here_dir))), "deltachat-ffi", "deltachat.h")
|
||||
assert os.path.exists(deltah)
|
||||
|
||||
lines = []
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
from .capi import lib
|
||||
from .capi import ffi
|
||||
from datetime import datetime
|
||||
|
||||
|
||||
def as_dc_charpointer(obj):
|
||||
@@ -17,3 +18,29 @@ def iter_array(dc_array_t, constructor):
|
||||
|
||||
def from_dc_charpointer(obj):
|
||||
return ffi.string(ffi.gc(obj, lib.dc_str_unref)).decode("utf8")
|
||||
|
||||
|
||||
class DCLot:
|
||||
def __init__(self, dc_lot):
|
||||
self._dc_lot = dc_lot
|
||||
|
||||
def id(self):
|
||||
return lib.dc_lot_get_id(self._dc_lot)
|
||||
|
||||
def state(self):
|
||||
return lib.dc_lot_get_state(self._dc_lot)
|
||||
|
||||
def text1(self):
|
||||
return from_dc_charpointer(lib.dc_lot_get_text1(self._dc_lot))
|
||||
|
||||
def text1_meaning(self):
|
||||
return lib.dc_lot_get_text1_meaning(self._dc_lot)
|
||||
|
||||
def text2(self):
|
||||
return from_dc_charpointer(lib.dc_lot_get_text2(self._dc_lot))
|
||||
|
||||
def timestamp(self):
|
||||
ts = lib.dc_lot_get_timestamp(self._dc_lot)
|
||||
if ts == 0:
|
||||
return None
|
||||
return datetime.utcfromtimestamp(ts)
|
||||
|
||||
@@ -213,6 +213,15 @@ def wait_configuration_progress(account, target):
|
||||
break
|
||||
|
||||
|
||||
def wait_securejoin_inviter_progress(account, target):
|
||||
while 1:
|
||||
evt_name, data1, data2 = \
|
||||
account._evlogger.get_matching("DC_EVENT_SECUREJOIN_INVITER_PROGRESS")
|
||||
if data2 >= target:
|
||||
print("** SECUREJOINT-INVITER PROGRESS {}".format(target), account)
|
||||
break
|
||||
|
||||
|
||||
def wait_successful_IMAP_SMTP_connection(account):
|
||||
imap_ok = smtp_ok = False
|
||||
while not imap_ok or not smtp_ok:
|
||||
|
||||
@@ -4,7 +4,7 @@ import os
|
||||
from deltachat import const, Account
|
||||
from deltachat.message import Message
|
||||
from datetime import datetime, timedelta
|
||||
from conftest import wait_configuration_progress, wait_successful_IMAP_SMTP_connection
|
||||
from conftest import wait_configuration_progress, wait_successful_IMAP_SMTP_connection, wait_securejoin_inviter_progress
|
||||
|
||||
|
||||
class TestOfflineAccountBasic:
|
||||
@@ -140,6 +140,13 @@ class TestOfflineChat:
|
||||
chat.set_name("title2")
|
||||
assert chat.get_name() == "title2"
|
||||
|
||||
@pytest.mark.parametrize("verified", [True, False])
|
||||
def test_group_chat_qr(self, acfactory, ac1, verified):
|
||||
ac2 = acfactory.get_configured_offline_account()
|
||||
chat = ac1.create_group_chat(name="title1", verified=verified)
|
||||
qr = chat.get_join_qr()
|
||||
assert ac2.check_qr(qr).is_ask_verifygroup
|
||||
|
||||
def test_delete_and_send_fails(self, ac1, chat1):
|
||||
chat1.delete()
|
||||
ac1._evlogger.get_matching("DC_EVENT_MSGS_CHANGED")
|
||||
@@ -306,6 +313,16 @@ class TestOfflineChat:
|
||||
chat1.set_draft(None)
|
||||
assert chat1.get_draft() is None
|
||||
|
||||
def test_qr_setup_contact(self, acfactory, lp):
|
||||
ac1 = acfactory.get_configured_offline_account()
|
||||
ac2 = acfactory.get_configured_offline_account()
|
||||
qr = ac1.get_setup_contact_qr()
|
||||
assert qr.startswith("OPENPGP4FPR:")
|
||||
res = ac2.check_qr(qr)
|
||||
assert res.is_ask_verifycontact()
|
||||
assert not res.is_ask_verifygroup()
|
||||
assert res.contact_id == 10
|
||||
|
||||
|
||||
class TestOnlineAccount:
|
||||
def test_one_account_init(self, acfactory):
|
||||
@@ -546,3 +563,29 @@ class TestOnlineAccount:
|
||||
print("*************** Setup Code: ", setup_code)
|
||||
msg.continue_key_transfer(setup_code)
|
||||
assert ac1.get_info()["fingerprint"] == ac2.get_info()["fingerprint"]
|
||||
|
||||
def test_qr_setup_contact(self, acfactory, lp):
|
||||
ac1 = acfactory.get_online_configuring_account()
|
||||
ac2 = acfactory.get_online_configuring_account()
|
||||
wait_configuration_progress(ac2, 1000)
|
||||
wait_configuration_progress(ac1, 1000)
|
||||
lp.sec("ac1: create QR code and let ac2 scan it, starting the securejoin")
|
||||
qr = ac1.get_setup_contact_qr()
|
||||
lp.sec("ac2: start QR-code based setup contact protocol")
|
||||
ch = ac2.qr_setup_contact(qr)
|
||||
assert ch.id >= 10
|
||||
wait_securejoin_inviter_progress(ac1, 1000)
|
||||
|
||||
def test_qr_join_chat(self, acfactory, lp):
|
||||
ac1 = acfactory.get_online_configuring_account()
|
||||
ac2 = acfactory.get_online_configuring_account()
|
||||
wait_configuration_progress(ac2, 1000)
|
||||
wait_configuration_progress(ac1, 1000)
|
||||
lp.sec("ac1: create QR code and let ac2 scan it, starting the securejoin")
|
||||
|
||||
chat = ac1.create_group_chat("hello")
|
||||
qr = chat.get_join_qr()
|
||||
lp.sec("ac2: start QR-code based join-group protocol")
|
||||
ch = ac2.qr_join_chat(qr)
|
||||
assert ch.id >= 10
|
||||
wait_securejoin_inviter_progress(ac1, 1000)
|
||||
|
||||
@@ -17,19 +17,11 @@ def test_callback_None2int():
|
||||
clear_context_callback(ctx)
|
||||
|
||||
|
||||
def test_dc_close_events(tmpdir):
|
||||
ctx = ffi.gc(
|
||||
capi.lib.dc_context_new(capi.lib.py_dc_callback, ffi.NULL, ffi.NULL),
|
||||
lib.dc_context_unref,
|
||||
)
|
||||
def test_dc_close_events():
|
||||
ctx = capi.lib.dc_context_new(capi.lib.py_dc_callback, ffi.NULL, ffi.NULL)
|
||||
evlog = EventLogger(ctx)
|
||||
evlog.set_timeout(5)
|
||||
set_context_callback(
|
||||
ctx,
|
||||
lambda ctx, evt_name, data1, data2: evlog(evt_name, data1, data2),
|
||||
)
|
||||
p = tmpdir.join("hello.db")
|
||||
lib.dc_open(ctx, p.strpath.encode("ascii"), ffi.NULL)
|
||||
set_context_callback(ctx, lambda ctx, evt_name, data1, data2: evlog(evt_name, data1, data2))
|
||||
capi.lib.dc_close(ctx)
|
||||
|
||||
def find(info_string):
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
[tox]
|
||||
# make sure to update environment list in travis.yml and appveyor.yml
|
||||
envlist =
|
||||
py27
|
||||
py35
|
||||
lint
|
||||
auditwheels
|
||||
|
||||
719
src/chat.rs
719
src/chat.rs
@@ -171,51 +171,20 @@ impl<'a> Chat<'a> {
|
||||
return "Err".into();
|
||||
}
|
||||
|
||||
unsafe fn get_parent_mime_headers(
|
||||
&self,
|
||||
parent_rfc724_mid: *mut *mut libc::c_char,
|
||||
parent_in_reply_to: *mut *mut libc::c_char,
|
||||
parent_references: *mut *mut libc::c_char,
|
||||
) -> Result<(), Error> {
|
||||
if !(parent_rfc724_mid.is_null()
|
||||
|| parent_in_reply_to.is_null()
|
||||
|| parent_references.is_null())
|
||||
{
|
||||
// prefer a last message that isn't from us
|
||||
let next = self
|
||||
.context
|
||||
.sql
|
||||
.query_row(
|
||||
"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);",
|
||||
params![self.id as i32, DC_CONTACT_ID_SELF as i32],
|
||||
|row| {
|
||||
*parent_rfc724_mid = row.get::<_, String>(0)?.strdup();
|
||||
*parent_in_reply_to = row.get::<_, String>(1)?.strdup();
|
||||
*parent_references = row.get::<_, String>(2)?.strdup();
|
||||
Ok(())
|
||||
},
|
||||
)
|
||||
.is_ok();
|
||||
pub fn get_parent_mime_headers(&self) -> 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 = &self.context.sql;
|
||||
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);";
|
||||
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);";
|
||||
|
||||
if !next {
|
||||
self.context.sql.query_row(
|
||||
"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);",
|
||||
params![self.id as i32, DC_CONTACT_ID_SELF as i32],
|
||||
|row| {
|
||||
*parent_rfc724_mid = row.get::<_, String>(0)?.strdup();
|
||||
*parent_in_reply_to = row.get::<_, String>(1)?.strdup();
|
||||
*parent_references = row.get::<_, String>(2)?.strdup();
|
||||
Ok(())
|
||||
},
|
||||
)?;
|
||||
}
|
||||
}
|
||||
|
||||
Ok(())
|
||||
sql.query_row(main_query, params, collect)
|
||||
.or_else(|_| sql.query_row(fallback_query, params, collect))
|
||||
.ok()
|
||||
}
|
||||
|
||||
pub unsafe fn get_profile_image(&self) -> Option<String> {
|
||||
@@ -277,13 +246,8 @@ impl<'a> Chat<'a> {
|
||||
) -> Result<u32, Error> {
|
||||
let mut do_guarantee_e2ee: libc::c_int;
|
||||
let e2ee_enabled: libc::c_int;
|
||||
let mut OK_TO_CONTINUE = true;
|
||||
let mut parent_rfc724_mid = ptr::null_mut();
|
||||
let mut parent_references = ptr::null_mut();
|
||||
let mut parent_in_reply_to = ptr::null_mut();
|
||||
let mut new_rfc724_mid = ptr::null_mut();
|
||||
let mut new_references = ptr::null_mut();
|
||||
let mut new_in_reply_to = ptr::null_mut();
|
||||
let mut new_references = "".into();
|
||||
let mut new_in_reply_to = "".into();
|
||||
let mut msg_id = 0;
|
||||
let mut to_id = 0;
|
||||
let mut location_id = 0;
|
||||
@@ -293,7 +257,10 @@ impl<'a> Chat<'a> {
|
||||
|| self.typ == Chattype::VerifiedGroup)
|
||||
{
|
||||
error!(context, 0, "Cannot send to chat type #{}.", self.typ,);
|
||||
} else if (self.typ == Chattype::Group || self.typ == Chattype::VerifiedGroup)
|
||||
return Ok(0);
|
||||
}
|
||||
|
||||
if (self.typ == Chattype::Group || self.typ == Chattype::VerifiedGroup)
|
||||
&& 0 == is_contact_in_chat(context, self.id, 1 as u32)
|
||||
{
|
||||
log_event!(
|
||||
@@ -302,214 +269,184 @@ impl<'a> Chat<'a> {
|
||||
0,
|
||||
"Cannot send message; self not in group.",
|
||||
);
|
||||
} else {
|
||||
let from = context.sql.get_config(context, "configured_addr");
|
||||
if from.is_none() {
|
||||
error!(context, 0, "Cannot send message, not configured.",);
|
||||
} else {
|
||||
let from_c = CString::yolo(from.unwrap());
|
||||
new_rfc724_mid = dc_create_outgoing_rfc724_mid(
|
||||
if self.typ == Chattype::Group || self.typ == Chattype::VerifiedGroup {
|
||||
self.grpid.strdup()
|
||||
} else {
|
||||
ptr::null_mut()
|
||||
},
|
||||
from_c.as_ptr(),
|
||||
);
|
||||
return Ok(0);
|
||||
}
|
||||
|
||||
if self.typ == Chattype::Single {
|
||||
if let Some(id) = context.sql.query_row_col(
|
||||
context,
|
||||
"SELECT contact_id FROM chats_contacts WHERE chat_id=?;",
|
||||
params![self.id as i32],
|
||||
0,
|
||||
) {
|
||||
to_id = id;
|
||||
} else {
|
||||
error!(
|
||||
context,
|
||||
0, "Cannot send message, contact for chat #{} not found.", self.id,
|
||||
);
|
||||
OK_TO_CONTINUE = false;
|
||||
}
|
||||
if let Some(from) = context.sql.get_config(context, "configured_addr") {
|
||||
let new_rfc724_mid = {
|
||||
let grpid = match self.typ {
|
||||
Chattype::Group | Chattype::VerifiedGroup => Some(self.grpid.as_str()),
|
||||
_ => None,
|
||||
};
|
||||
dc_create_outgoing_rfc724_mid_safe(grpid, &from)
|
||||
};
|
||||
|
||||
if self.typ == Chattype::Single {
|
||||
if let Some(id) = context.sql.query_row_col(
|
||||
context,
|
||||
"SELECT contact_id FROM chats_contacts WHERE chat_id=?;",
|
||||
params![self.id as i32],
|
||||
0,
|
||||
) {
|
||||
to_id = id;
|
||||
} else {
|
||||
if self.typ == Chattype::Group || self.typ == Chattype::VerifiedGroup {
|
||||
if self.param.get_int(Param::Unpromoted).unwrap_or_default() == 1 {
|
||||
self.param.remove(Param::Unpromoted);
|
||||
self.update_param().unwrap();
|
||||
}
|
||||
error!(
|
||||
context,
|
||||
0, "Cannot send message, contact for chat #{} not found.", self.id,
|
||||
);
|
||||
return Ok(0);
|
||||
}
|
||||
} else {
|
||||
if self.typ == Chattype::Group || self.typ == Chattype::VerifiedGroup {
|
||||
if self.param.get_int(Param::Unpromoted).unwrap_or_default() == 1 {
|
||||
self.param.remove(Param::Unpromoted);
|
||||
self.update_param().unwrap();
|
||||
}
|
||||
}
|
||||
if OK_TO_CONTINUE {
|
||||
/* check if we can guarantee E2EE for this message.
|
||||
if we guarantee E2EE, and circumstances change
|
||||
so that E2EE is no longer available at a later point (reset, changed settings),
|
||||
we do not send the message out at all */
|
||||
do_guarantee_e2ee = 0;
|
||||
e2ee_enabled = context
|
||||
.sql
|
||||
.get_config_int(context, "e2ee_enabled")
|
||||
.unwrap_or_else(|| 1);
|
||||
if 0 != e2ee_enabled
|
||||
&& msg.param.get_int(Param::ForcePlaintext).unwrap_or_default() == 0
|
||||
{
|
||||
let mut can_encrypt = 1;
|
||||
let mut all_mutual = 1;
|
||||
}
|
||||
|
||||
let res = context.sql.query_row(
|
||||
"SELECT ps.prefer_encrypted, c.addr \
|
||||
FROM chats_contacts cc \
|
||||
LEFT JOIN contacts c ON cc.contact_id=c.id \
|
||||
LEFT JOIN acpeerstates ps ON c.addr=ps.addr \
|
||||
WHERE cc.chat_id=? AND cc.contact_id>9;",
|
||||
params![self.id],
|
||||
|row| {
|
||||
let state: String = row.get(1)?;
|
||||
/* check if we can guarantee E2EE for this message.
|
||||
if we guarantee E2EE, and circumstances change
|
||||
so that E2EE is no longer available at a later point (reset, changed settings),
|
||||
we do not send the message out at all */
|
||||
do_guarantee_e2ee = 0;
|
||||
e2ee_enabled = context
|
||||
.sql
|
||||
.get_config_int(context, "e2ee_enabled")
|
||||
.unwrap_or_else(|| 1);
|
||||
if 0 != e2ee_enabled
|
||||
&& msg.param.get_int(Param::ForcePlaintext).unwrap_or_default() == 0
|
||||
{
|
||||
let mut can_encrypt = 1;
|
||||
let mut all_mutual = 1;
|
||||
|
||||
if let Some(prefer_encrypted) = row.get::<_, Option<i32>>(0)? {
|
||||
if prefer_encrypted != 1 {
|
||||
info!(
|
||||
context,
|
||||
0,
|
||||
"[autocrypt] peerstate for {} is {}",
|
||||
state,
|
||||
if prefer_encrypted == 0 {
|
||||
"NOPREFERENCE"
|
||||
} else {
|
||||
"RESET"
|
||||
},
|
||||
);
|
||||
all_mutual = 0;
|
||||
}
|
||||
} else {
|
||||
info!(context, 0, "[autocrypt] no peerstate for {}", state,);
|
||||
can_encrypt = 0;
|
||||
all_mutual = 0;
|
||||
}
|
||||
Ok(())
|
||||
},
|
||||
);
|
||||
match res {
|
||||
Ok(_) => {}
|
||||
Err(err) => {
|
||||
warn!(context, 0, "chat: failed to load peerstates: {:?}", err);
|
||||
}
|
||||
}
|
||||
|
||||
if 0 != can_encrypt {
|
||||
if 0 != all_mutual {
|
||||
do_guarantee_e2ee = 1;
|
||||
} else if last_msg_in_chat_encrypted(context, &context.sql, self.id) {
|
||||
do_guarantee_e2ee = 1;
|
||||
let res = context.sql.query_row(
|
||||
"SELECT ps.prefer_encrypted, c.addr \
|
||||
FROM chats_contacts cc \
|
||||
LEFT JOIN contacts c ON cc.contact_id=c.id \
|
||||
LEFT JOIN acpeerstates ps ON c.addr=ps.addr \
|
||||
WHERE cc.chat_id=? AND cc.contact_id>9;",
|
||||
params![self.id],
|
||||
|row| {
|
||||
let state: String = row.get(1)?;
|
||||
|
||||
if let Some(prefer_encrypted) = row.get::<_, Option<i32>>(0)? {
|
||||
if prefer_encrypted != 1 {
|
||||
info!(
|
||||
context,
|
||||
0,
|
||||
"[autocrypt] peerstate for {} is {}",
|
||||
state,
|
||||
if prefer_encrypted == 0 {
|
||||
"NOPREFERENCE"
|
||||
} else {
|
||||
"RESET"
|
||||
},
|
||||
);
|
||||
all_mutual = 0;
|
||||
}
|
||||
} else {
|
||||
info!(context, 0, "[autocrypt] no peerstate for {}", state,);
|
||||
can_encrypt = 0;
|
||||
all_mutual = 0;
|
||||
}
|
||||
Ok(())
|
||||
},
|
||||
);
|
||||
match res {
|
||||
Ok(_) => {}
|
||||
Err(err) => {
|
||||
warn!(context, 0, "chat: failed to load peerstates: {:?}", err);
|
||||
}
|
||||
if 0 != do_guarantee_e2ee {
|
||||
msg.param.set_int(Param::GuranteeE2ee, 1);
|
||||
}
|
||||
|
||||
if 0 != can_encrypt {
|
||||
if 0 != all_mutual {
|
||||
do_guarantee_e2ee = 1;
|
||||
} else if last_msg_in_chat_encrypted(context, &context.sql, self.id) {
|
||||
do_guarantee_e2ee = 1;
|
||||
}
|
||||
msg.param.remove(Param::ErroneousE2ee);
|
||||
if !self.is_self_talk()
|
||||
&& self
|
||||
.get_parent_mime_headers(
|
||||
&mut parent_rfc724_mid,
|
||||
&mut parent_in_reply_to,
|
||||
&mut parent_references,
|
||||
)
|
||||
.is_ok()
|
||||
{
|
||||
if !parent_rfc724_mid.is_null()
|
||||
&& 0 != *parent_rfc724_mid.offset(0isize) as libc::c_int
|
||||
{
|
||||
new_in_reply_to = dc_strdup(parent_rfc724_mid)
|
||||
}
|
||||
if !parent_references.is_null() {
|
||||
let space: *mut libc::c_char;
|
||||
space = strchr(parent_references, ' ' as i32);
|
||||
if !space.is_null() {
|
||||
*space = 0 as libc::c_char
|
||||
}
|
||||
}
|
||||
if !parent_references.is_null()
|
||||
&& 0 != *parent_references.offset(0isize) as libc::c_int
|
||||
&& !parent_rfc724_mid.is_null()
|
||||
&& 0 != *parent_rfc724_mid.offset(0isize) as libc::c_int
|
||||
{
|
||||
new_references = dc_mprintf(
|
||||
b"%s %s\x00" as *const u8 as *const libc::c_char,
|
||||
parent_references,
|
||||
parent_rfc724_mid,
|
||||
)
|
||||
} else if !parent_references.is_null()
|
||||
&& 0 != *parent_references.offset(0isize) as libc::c_int
|
||||
{
|
||||
new_references = dc_strdup(parent_references)
|
||||
} else if !parent_in_reply_to.is_null()
|
||||
&& 0 != *parent_in_reply_to.offset(0isize) as libc::c_int
|
||||
&& !parent_rfc724_mid.is_null()
|
||||
&& 0 != *parent_rfc724_mid.offset(0isize) as libc::c_int
|
||||
{
|
||||
new_references = dc_mprintf(
|
||||
b"%s %s\x00" as *const u8 as *const libc::c_char,
|
||||
parent_in_reply_to,
|
||||
parent_rfc724_mid,
|
||||
)
|
||||
} else if !parent_in_reply_to.is_null()
|
||||
&& 0 != *parent_in_reply_to.offset(0isize) as libc::c_int
|
||||
{
|
||||
new_references = dc_strdup(parent_in_reply_to)
|
||||
}
|
||||
}
|
||||
}
|
||||
if 0 != do_guarantee_e2ee {
|
||||
msg.param.set_int(Param::GuranteeE2ee, 1);
|
||||
}
|
||||
msg.param.remove(Param::ErroneousE2ee);
|
||||
if !self.is_self_talk() {
|
||||
if let Some((parent_rfc724_mid, parent_in_reply_to, parent_references)) =
|
||||
self.get_parent_mime_headers()
|
||||
{
|
||||
if !parent_rfc724_mid.is_empty() {
|
||||
new_in_reply_to = parent_rfc724_mid.clone();
|
||||
}
|
||||
let parent_references = if let Some(n) = parent_references.find(' ') {
|
||||
&parent_references[0..n]
|
||||
} else {
|
||||
&parent_references
|
||||
};
|
||||
|
||||
// add independent location to database
|
||||
|
||||
if msg.param.exists(Param::SetLatitude) {
|
||||
if sql::execute(
|
||||
context,
|
||||
&context.sql,
|
||||
"INSERT INTO locations \
|
||||
(timestamp,from_id,chat_id, latitude,longitude,independent)\
|
||||
VALUES (?,?,?, ?,?,1);",
|
||||
params![
|
||||
timestamp,
|
||||
DC_CONTACT_ID_SELF as i32,
|
||||
self.id as i32,
|
||||
msg.param.get_float(Param::SetLatitude).unwrap_or_default(),
|
||||
msg.param.get_float(Param::SetLongitude).unwrap_or_default(),
|
||||
],
|
||||
)
|
||||
.is_ok()
|
||||
{
|
||||
location_id = sql::get_rowid2(
|
||||
context,
|
||||
&context.sql,
|
||||
"locations",
|
||||
"timestamp",
|
||||
timestamp,
|
||||
"from_id",
|
||||
DC_CONTACT_ID_SELF as i32,
|
||||
);
|
||||
}
|
||||
if !parent_references.is_empty() && !parent_rfc724_mid.is_empty() {
|
||||
new_references = format!("{} {}", parent_references, parent_rfc724_mid);
|
||||
} else if !parent_references.is_empty() {
|
||||
new_references = parent_references.to_string();
|
||||
} else if !parent_in_reply_to.is_empty() && !parent_rfc724_mid.is_empty() {
|
||||
new_references = format!("{} {}", parent_in_reply_to, parent_rfc724_mid);
|
||||
} else if !parent_in_reply_to.is_empty() {
|
||||
new_references = parent_in_reply_to.clone();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// add message to the database
|
||||
// add independent location to database
|
||||
|
||||
if sql::execute(
|
||||
if msg.param.exists(Param::SetLatitude) {
|
||||
if sql::execute(
|
||||
context,
|
||||
&context.sql,
|
||||
"INSERT INTO locations \
|
||||
(timestamp,from_id,chat_id, latitude,longitude,independent)\
|
||||
VALUES (?,?,?, ?,?,1);",
|
||||
params![
|
||||
timestamp,
|
||||
DC_CONTACT_ID_SELF,
|
||||
self.id as i32,
|
||||
msg.param.get_float(Param::SetLatitude).unwrap_or_default(),
|
||||
msg.param.get_float(Param::SetLongitude).unwrap_or_default(),
|
||||
],
|
||||
)
|
||||
.is_ok()
|
||||
{
|
||||
location_id = sql::get_rowid2(
|
||||
context,
|
||||
&context.sql,
|
||||
"locations",
|
||||
"timestamp",
|
||||
timestamp,
|
||||
"from_id",
|
||||
DC_CONTACT_ID_SELF as i32,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
// add message to the database
|
||||
|
||||
if sql::execute(
|
||||
context,
|
||||
&context.sql,
|
||||
"INSERT INTO msgs (rfc724_mid, chat_id, from_id, to_id, timestamp, type, state, txt, param, hidden, mime_in_reply_to, mime_references, location_id) VALUES (?,?,?,?,?, ?,?,?,?,?, ?,?,?);",
|
||||
params![
|
||||
as_str(new_rfc724_mid),
|
||||
new_rfc724_mid,
|
||||
self.id as i32,
|
||||
1i32,
|
||||
to_id as i32,
|
||||
timestamp,
|
||||
msg.type_0,
|
||||
msg.state,
|
||||
msg.text,
|
||||
msg.text.as_ref().map_or("", String::as_str),
|
||||
msg.param.to_string(),
|
||||
msg.hidden,
|
||||
to_string(new_in_reply_to),
|
||||
to_string(new_references),
|
||||
new_in_reply_to,
|
||||
new_references,
|
||||
location_id as i32,
|
||||
]
|
||||
).is_ok() {
|
||||
@@ -518,7 +455,7 @@ impl<'a> Chat<'a> {
|
||||
&context.sql,
|
||||
"msgs",
|
||||
"rfc724_mid",
|
||||
as_str(new_rfc724_mid),
|
||||
new_rfc724_mid,
|
||||
);
|
||||
} else {
|
||||
error!(
|
||||
@@ -528,17 +465,10 @@ impl<'a> Chat<'a> {
|
||||
self.id,
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
} else {
|
||||
error!(context, 0, "Cannot send message, not configured.",);
|
||||
}
|
||||
|
||||
free(parent_rfc724_mid as *mut libc::c_void);
|
||||
free(parent_in_reply_to as *mut libc::c_void);
|
||||
free(parent_references as *mut libc::c_void);
|
||||
free(new_rfc724_mid as *mut libc::c_void);
|
||||
free(new_in_reply_to as *mut libc::c_void);
|
||||
free(new_references as *mut libc::c_void);
|
||||
|
||||
Ok(msg_id)
|
||||
}
|
||||
}
|
||||
@@ -567,7 +497,7 @@ pub fn create_by_msg_id(context: &Context, msg_id: u32) -> Result<u32, Error> {
|
||||
|
||||
if let Ok(msg) = dc_msg_load_from_db(context, msg_id) {
|
||||
if let Ok(chat) = Chat::load_from_db(context, msg.chat_id) {
|
||||
if chat.id > DC_CHAT_ID_LAST_SPECIAL as u32 {
|
||||
if chat.id > DC_CHAT_ID_LAST_SPECIAL {
|
||||
chat_id = chat.id;
|
||||
if chat.blocked != Blocked::Not {
|
||||
unblock(context, chat.id);
|
||||
@@ -603,8 +533,7 @@ pub fn create_by_contact_id(context: &Context, contact_id: u32) -> Result<u32, E
|
||||
chat_id
|
||||
}
|
||||
Err(err) => {
|
||||
if !Contact::real_exists_by_id(context, contact_id)
|
||||
&& contact_id != DC_CONTACT_ID_SELF as u32
|
||||
if !Contact::real_exists_by_id(context, contact_id) && contact_id != DC_CONTACT_ID_SELF
|
||||
{
|
||||
warn!(
|
||||
context,
|
||||
@@ -707,7 +636,7 @@ pub fn prepare_msg<'a>(
|
||||
msg: &mut Message<'a>,
|
||||
) -> Result<u32, Error> {
|
||||
ensure!(
|
||||
chat_id > DC_CHAT_ID_LAST_SPECIAL as u32,
|
||||
chat_id > DC_CHAT_ID_LAST_SPECIAL,
|
||||
"Cannot prepare message for special chat"
|
||||
);
|
||||
|
||||
@@ -856,7 +785,7 @@ pub fn unarchive(context: &Context, chat_id: u32) -> Result<(), Error> {
|
||||
/// However, this does not imply, the message really reached the recipient -
|
||||
/// sending may be delayed eg. due to network problems. However, from your
|
||||
/// view, you're done with the message. Sooner or later it will find its way.
|
||||
pub unsafe fn send_msg<'a>(
|
||||
pub fn send_msg<'a>(
|
||||
context: &'a Context,
|
||||
chat_id: u32,
|
||||
msg: &mut Message<'a>,
|
||||
@@ -874,7 +803,7 @@ pub unsafe fn send_msg<'a>(
|
||||
}
|
||||
|
||||
ensure!(
|
||||
job_send_msg(context, msg.id) != 0,
|
||||
unsafe { job_send_msg(context, msg.id) } != 0,
|
||||
"Failed to initiate send job"
|
||||
);
|
||||
|
||||
@@ -885,7 +814,7 @@ pub unsafe fn send_msg<'a>(
|
||||
);
|
||||
|
||||
if msg.param.exists(Param::SetLatitude) {
|
||||
context.call_cb(Event::LOCATION_CHANGED, DC_CONTACT_ID_SELF, 0);
|
||||
context.call_cb(Event::LOCATION_CHANGED, DC_CONTACT_ID_SELF as usize, 0);
|
||||
}
|
||||
|
||||
if 0 == chat_id {
|
||||
@@ -917,7 +846,7 @@ pub unsafe fn send_text_msg(
|
||||
text_to_send: String,
|
||||
) -> Result<u32, Error> {
|
||||
ensure!(
|
||||
chat_id > DC_CHAT_ID_LAST_SPECIAL as u32,
|
||||
chat_id > DC_CHAT_ID_LAST_SPECIAL,
|
||||
"bad chat_id = {} <= 9",
|
||||
chat_id
|
||||
);
|
||||
@@ -929,7 +858,7 @@ pub unsafe fn send_text_msg(
|
||||
|
||||
// passing `None` as message jsut deletes the draft
|
||||
pub unsafe fn set_draft(context: &Context, chat_id: u32, msg: Option<&mut Message>) {
|
||||
if chat_id <= DC_CHAT_ID_LAST_SPECIAL as u32 {
|
||||
if chat_id <= DC_CHAT_ID_LAST_SPECIAL {
|
||||
return;
|
||||
}
|
||||
if set_draft_raw(context, chat_id, msg) {
|
||||
@@ -1007,7 +936,7 @@ fn get_draft_msg_id(context: &Context, chat_id: u32) -> u32 {
|
||||
}
|
||||
|
||||
pub unsafe fn get_draft(context: &Context, chat_id: u32) -> Result<Message, Error> {
|
||||
ensure!(chat_id > DC_CHAT_ID_LAST_SPECIAL as u32, "Invalid chat ID");
|
||||
ensure!(chat_id > DC_CHAT_ID_LAST_SPECIAL, "Invalid chat ID");
|
||||
let draft_msg_id = get_draft_msg_id(context, chat_id);
|
||||
ensure!(draft_msg_id != 0, "Invalid draft message ID");
|
||||
|
||||
@@ -1025,13 +954,13 @@ pub fn get_chat_msgs(context: &Context, chat_id: u32, flags: u32, marker1before:
|
||||
for row in rows {
|
||||
let (curr_id, ts) = row?;
|
||||
if curr_id as u32 == marker1before {
|
||||
ret.push(DC_MSG_ID_MARKER1 as u32);
|
||||
ret.push(DC_MSG_ID_MARKER1);
|
||||
}
|
||||
if 0 != flags & 0x1 {
|
||||
let curr_local_timestamp = ts + cnv_to_local;
|
||||
let curr_day = (curr_local_timestamp / 86400) as libc::c_int;
|
||||
if curr_day != last_day {
|
||||
ret.push(DC_MSG_ID_LAST_SPECIAL as u32);
|
||||
ret.push(DC_MSG_ID_LAST_SPECIAL);
|
||||
last_day = curr_day;
|
||||
}
|
||||
}
|
||||
@@ -1234,7 +1163,7 @@ pub unsafe fn get_next_media(
|
||||
|
||||
pub fn archive(context: &Context, chat_id: u32, archive: bool) -> Result<(), Error> {
|
||||
ensure!(
|
||||
chat_id > DC_CHAT_ID_LAST_SPECIAL as u32,
|
||||
chat_id > DC_CHAT_ID_LAST_SPECIAL,
|
||||
"bad chat_id = {} <= 9",
|
||||
chat_id
|
||||
);
|
||||
@@ -1266,7 +1195,7 @@ pub fn archive(context: &Context, chat_id: u32, archive: bool) -> Result<(), Err
|
||||
|
||||
pub fn delete(context: &Context, chat_id: u32) -> Result<(), Error> {
|
||||
ensure!(
|
||||
chat_id > DC_CHAT_ID_LAST_SPECIAL as u32,
|
||||
chat_id > DC_CHAT_ID_LAST_SPECIAL,
|
||||
"bad chat_id = {} <= 9",
|
||||
chat_id
|
||||
);
|
||||
@@ -1396,7 +1325,7 @@ pub unsafe fn add_contact_to_chat(context: &Context, chat_id: u32, contact_id: u
|
||||
|
||||
// TODO should return bool /rtn
|
||||
#[allow(non_snake_case)]
|
||||
pub unsafe fn add_contact_to_chat_ex(
|
||||
pub fn add_contact_to_chat_ex(
|
||||
context: &Context,
|
||||
chat_id: u32,
|
||||
contact_id: u32,
|
||||
@@ -1406,99 +1335,97 @@ pub unsafe fn add_contact_to_chat_ex(
|
||||
let mut success: libc::c_int = 0;
|
||||
let contact = Contact::get_by_id(context, contact_id);
|
||||
|
||||
if contact.is_err() || chat_id <= DC_CHAT_ID_LAST_SPECIAL as u32 {
|
||||
if contact.is_err() || chat_id <= DC_CHAT_ID_LAST_SPECIAL {
|
||||
return 0;
|
||||
}
|
||||
let mut msg = dc_msg_new_untyped(context);
|
||||
let mut msg = unsafe { dc_msg_new_untyped(context) };
|
||||
|
||||
reset_gossiped_timestamp(context, chat_id);
|
||||
let contact = contact.unwrap();
|
||||
|
||||
/*this also makes sure, not contacts are added to special or normal chats*/
|
||||
let chat = Chat::load_from_db(context, chat_id);
|
||||
if let Ok(mut chat) = Chat::load_from_db(context, chat_id) {
|
||||
if !(!real_group_exists(context, chat_id)
|
||||
|| !Contact::real_exists_by_id(context, contact_id) && contact_id != DC_CONTACT_ID_SELF)
|
||||
{
|
||||
if !(is_contact_in_chat(context, chat_id, 1 as u32) == 1) {
|
||||
log_event!(
|
||||
context,
|
||||
Event::ERROR_SELF_NOT_IN_GROUP,
|
||||
0,
|
||||
"Cannot add contact to group; self not in group.",
|
||||
);
|
||||
} else {
|
||||
/* we should respect this - whatever we send to the group, it gets discarded anyway! */
|
||||
if 0 != flags & 0x1
|
||||
&& chat.param.get_int(Param::Unpromoted).unwrap_or_default() == 1
|
||||
{
|
||||
chat.param.remove(Param::Unpromoted);
|
||||
chat.update_param().unwrap();
|
||||
}
|
||||
let self_addr = context
|
||||
.sql
|
||||
.get_config(context, "configured_addr")
|
||||
.unwrap_or_default();
|
||||
if contact.get_addr() != &self_addr {
|
||||
// ourself is added using DC_CONTACT_ID_SELF, do not add it explicitly.
|
||||
// if SELF is not in the group, members cannot be added at all.
|
||||
|
||||
if !(!real_group_exists(context, chat_id)
|
||||
|| !Contact::real_exists_by_id(context, contact_id)
|
||||
&& contact_id != DC_CONTACT_ID_SELF as u32
|
||||
|| chat.is_err())
|
||||
{
|
||||
let mut chat = chat.unwrap();
|
||||
|
||||
if !(is_contact_in_chat(context, chat_id, 1 as u32) == 1) {
|
||||
log_event!(
|
||||
context,
|
||||
Event::ERROR_SELF_NOT_IN_GROUP,
|
||||
0,
|
||||
"Cannot add contact to group; self not in group.",
|
||||
);
|
||||
} else {
|
||||
/* we should respect this - whatever we send to the group, it gets discarded anyway! */
|
||||
if 0 != flags & 0x1 && chat.param.get_int(Param::Unpromoted).unwrap_or_default() == 1 {
|
||||
chat.param.remove(Param::Unpromoted);
|
||||
chat.update_param().unwrap();
|
||||
}
|
||||
let self_addr = context
|
||||
.sql
|
||||
.get_config(context, "configured_addr")
|
||||
.unwrap_or_default();
|
||||
if contact.get_addr() != &self_addr {
|
||||
// ourself is added using DC_CONTACT_ID_SELF, do not add it explicitly.
|
||||
// if SELF is not in the group, members cannot be added at all.
|
||||
|
||||
if 0 != is_contact_in_chat(context, chat_id, contact_id) {
|
||||
if 0 == flags & 0x1 {
|
||||
success = 1;
|
||||
OK_TO_CONTINUE = false;
|
||||
}
|
||||
} else {
|
||||
// else continue and send status mail
|
||||
if chat.typ == Chattype::VerifiedGroup {
|
||||
if contact.is_verified() != VerifiedStatus::BidirectVerified {
|
||||
error!(
|
||||
if 0 != is_contact_in_chat(context, chat_id, contact_id) {
|
||||
if 0 == flags & 0x1 {
|
||||
success = 1;
|
||||
OK_TO_CONTINUE = false;
|
||||
}
|
||||
} else {
|
||||
// else continue and send status mail
|
||||
if chat.typ == Chattype::VerifiedGroup {
|
||||
if contact.is_verified() != VerifiedStatus::BidirectVerified {
|
||||
error!(
|
||||
context, 0,
|
||||
"Only bidirectional verified contacts can be added to verified groups."
|
||||
);
|
||||
OK_TO_CONTINUE = false;
|
||||
OK_TO_CONTINUE = false;
|
||||
}
|
||||
}
|
||||
if OK_TO_CONTINUE {
|
||||
if 0 == add_to_chat_contacts_table(context, chat_id, contact_id) {
|
||||
OK_TO_CONTINUE = false;
|
||||
}
|
||||
}
|
||||
}
|
||||
if OK_TO_CONTINUE {
|
||||
if 0 == add_to_chat_contacts_table(context, chat_id, contact_id) {
|
||||
OK_TO_CONTINUE = false;
|
||||
if chat.param.get_int(Param::Unpromoted).unwrap_or_default() == 0 {
|
||||
msg.type_0 = Viewtype::Text;
|
||||
msg.text = Some(context.stock_system_msg(
|
||||
StockMessage::MsgAddMember,
|
||||
contact.get_addr(),
|
||||
"",
|
||||
DC_CONTACT_ID_SELF as u32,
|
||||
));
|
||||
msg.param.set_int(Param::Cmd, 4);
|
||||
msg.param.set(Param::Arg, contact.get_addr());
|
||||
msg.param.set_int(Param::Arg2, flags);
|
||||
msg.id = send_msg(context, chat_id, &mut msg).unwrap_or_default();
|
||||
context.call_cb(
|
||||
Event::MSGS_CHANGED,
|
||||
chat_id as uintptr_t,
|
||||
msg.id as uintptr_t,
|
||||
);
|
||||
}
|
||||
context.call_cb(Event::MSGS_CHANGED, chat_id as uintptr_t, 0 as uintptr_t);
|
||||
success = 1;
|
||||
}
|
||||
}
|
||||
if OK_TO_CONTINUE {
|
||||
if chat.param.get_int(Param::Unpromoted).unwrap_or_default() == 0 {
|
||||
msg.type_0 = Viewtype::Text;
|
||||
msg.text = Some(context.stock_system_msg(
|
||||
StockMessage::MsgAddMember,
|
||||
contact.get_addr(),
|
||||
"",
|
||||
DC_CONTACT_ID_SELF as u32,
|
||||
));
|
||||
msg.param.set_int(Param::Cmd, 4);
|
||||
msg.param.set(Param::Arg, contact.get_addr());
|
||||
msg.param.set_int(Param::Arg2, flags);
|
||||
msg.id = send_msg(context, chat_id, &mut msg).unwrap_or_default();
|
||||
context.call_cb(
|
||||
Event::MSGS_CHANGED,
|
||||
chat_id as uintptr_t,
|
||||
msg.id as uintptr_t,
|
||||
);
|
||||
}
|
||||
context.call_cb(Event::MSGS_CHANGED, chat_id as uintptr_t, 0 as uintptr_t);
|
||||
success = 1;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
success
|
||||
}
|
||||
|
||||
fn real_group_exists(context: &Context, chat_id: u32) -> bool {
|
||||
// check if a group or a verified group exists under the given ID
|
||||
if !context.sql.is_open() || chat_id <= DC_CHAT_ID_LAST_SPECIAL as u32 {
|
||||
if !context.sql.is_open() || chat_id <= DC_CHAT_ID_LAST_SPECIAL {
|
||||
return false;
|
||||
}
|
||||
|
||||
@@ -1551,72 +1478,68 @@ pub unsafe fn remove_contact_from_chat(
|
||||
contact_id: u32,
|
||||
) -> Result<(), Error> {
|
||||
ensure!(
|
||||
chat_id > DC_CHAT_ID_LAST_SPECIAL as u32,
|
||||
chat_id > DC_CHAT_ID_LAST_SPECIAL,
|
||||
"bad chat_id = {} <= 9",
|
||||
chat_id
|
||||
);
|
||||
ensure!(
|
||||
contact_id != DC_CONTACT_ID_SELF as u32,
|
||||
"Cannot remove self"
|
||||
);
|
||||
ensure!(contact_id != DC_CONTACT_ID_SELF, "Cannot remove self");
|
||||
|
||||
let mut msg = dc_msg_new_untyped(context);
|
||||
let mut success = false;
|
||||
|
||||
/* we do not check if "contact_id" exists but just delete all records with the id from chats_contacts */
|
||||
/* this allows to delete pending references to deleted contacts. Of course, this should _not_ happen. */
|
||||
let chat = Chat::load_from_db(context, chat_id);
|
||||
|
||||
if !(!real_group_exists(context, chat_id) || chat.is_err()) {
|
||||
let chat = chat.unwrap();
|
||||
if !(is_contact_in_chat(context, chat_id, 1 as u32) == 1) {
|
||||
log_event!(
|
||||
context,
|
||||
Event::ERROR_SELF_NOT_IN_GROUP,
|
||||
0,
|
||||
"Cannot remove contact from chat; self not in group.",
|
||||
);
|
||||
} else {
|
||||
/* we should respect this - whatever we send to the group, it gets discarded anyway! */
|
||||
if let Ok(contact) = Contact::get_by_id(context, contact_id) {
|
||||
if chat.param.get_int(Param::Unpromoted).unwrap_or_default() == 0 {
|
||||
msg.type_0 = Viewtype::Text;
|
||||
if contact.id == DC_CONTACT_ID_SELF as u32 {
|
||||
set_group_explicitly_left(context, chat.grpid).unwrap();
|
||||
msg.text = Some(context.stock_system_msg(
|
||||
StockMessage::MsgGroupLeft,
|
||||
"",
|
||||
"",
|
||||
DC_CONTACT_ID_SELF as u32,
|
||||
));
|
||||
} else {
|
||||
msg.text = Some(context.stock_system_msg(
|
||||
StockMessage::MsgDelMember,
|
||||
contact.get_addr(),
|
||||
"",
|
||||
DC_CONTACT_ID_SELF as u32,
|
||||
));
|
||||
if let Ok(chat) = Chat::load_from_db(context, chat_id) {
|
||||
if real_group_exists(context, chat_id) {
|
||||
if !(is_contact_in_chat(context, chat_id, 1 as u32) == 1) {
|
||||
log_event!(
|
||||
context,
|
||||
Event::ERROR_SELF_NOT_IN_GROUP,
|
||||
0,
|
||||
"Cannot remove contact from chat; self not in group.",
|
||||
);
|
||||
} else {
|
||||
/* we should respect this - whatever we send to the group, it gets discarded anyway! */
|
||||
if let Ok(contact) = Contact::get_by_id(context, contact_id) {
|
||||
if chat.param.get_int(Param::Unpromoted).unwrap_or_default() == 0 {
|
||||
msg.type_0 = Viewtype::Text;
|
||||
if contact.id == DC_CONTACT_ID_SELF {
|
||||
set_group_explicitly_left(context, chat.grpid).unwrap();
|
||||
msg.text = Some(context.stock_system_msg(
|
||||
StockMessage::MsgGroupLeft,
|
||||
"",
|
||||
"",
|
||||
DC_CONTACT_ID_SELF,
|
||||
));
|
||||
} else {
|
||||
msg.text = Some(context.stock_system_msg(
|
||||
StockMessage::MsgDelMember,
|
||||
contact.get_addr(),
|
||||
"",
|
||||
DC_CONTACT_ID_SELF,
|
||||
));
|
||||
}
|
||||
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();
|
||||
context.call_cb(
|
||||
Event::MSGS_CHANGED,
|
||||
chat_id as uintptr_t,
|
||||
msg.id as uintptr_t,
|
||||
);
|
||||
}
|
||||
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();
|
||||
context.call_cb(
|
||||
Event::MSGS_CHANGED,
|
||||
chat_id as uintptr_t,
|
||||
msg.id as uintptr_t,
|
||||
);
|
||||
}
|
||||
}
|
||||
if sql::execute(
|
||||
context,
|
||||
&context.sql,
|
||||
"DELETE FROM chats_contacts WHERE chat_id=? AND contact_id=?;",
|
||||
params![chat_id as i32, contact_id as i32],
|
||||
)
|
||||
.is_ok()
|
||||
{
|
||||
context.call_cb(Event::CHAT_MODIFIED, chat_id as uintptr_t, 0 as uintptr_t);
|
||||
success = true;
|
||||
if sql::execute(
|
||||
context,
|
||||
&context.sql,
|
||||
"DELETE FROM chats_contacts WHERE chat_id=? AND contact_id=?;",
|
||||
params![chat_id as i32, contact_id as i32],
|
||||
)
|
||||
.is_ok()
|
||||
{
|
||||
context.call_cb(Event::CHAT_MODIFIED, chat_id as uintptr_t, 0 as uintptr_t);
|
||||
success = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1657,7 +1580,7 @@ pub unsafe fn set_chat_name(
|
||||
let mut success = false;
|
||||
|
||||
ensure!(!new_name.as_ref().is_empty(), "Invalid name");
|
||||
ensure!(chat_id > DC_CHAT_ID_LAST_SPECIAL as u32, "Invalid chat ID");
|
||||
ensure!(chat_id > DC_CHAT_ID_LAST_SPECIAL, "Invalid chat ID");
|
||||
|
||||
let chat = Chat::load_from_db(context, chat_id)?;
|
||||
let mut msg = dc_msg_new_untyped(context);
|
||||
@@ -1692,7 +1615,7 @@ pub unsafe fn set_chat_name(
|
||||
StockMessage::MsgGrpName,
|
||||
&chat.name,
|
||||
new_name.as_ref(),
|
||||
DC_CONTACT_ID_SELF as u32,
|
||||
DC_CONTACT_ID_SELF,
|
||||
));
|
||||
msg.param.set_int(Param::Cmd, 2);
|
||||
if !chat.name.is_empty() {
|
||||
@@ -1728,7 +1651,7 @@ pub unsafe fn set_chat_profile_image(
|
||||
chat_id: u32,
|
||||
new_image: impl AsRef<str>,
|
||||
) -> Result<(), Error> {
|
||||
ensure!(chat_id > DC_CHAT_ID_LAST_SPECIAL as u32, "Invalid chat ID");
|
||||
ensure!(chat_id > DC_CHAT_ID_LAST_SPECIAL, "Invalid chat ID");
|
||||
|
||||
let mut OK_TO_CONTINUE = true;
|
||||
let mut success = false;
|
||||
@@ -1776,7 +1699,7 @@ pub unsafe fn set_chat_profile_image(
|
||||
},
|
||||
"",
|
||||
"",
|
||||
DC_CONTACT_ID_SELF as u32,
|
||||
DC_CONTACT_ID_SELF,
|
||||
));
|
||||
msg.id = send_msg(context, chat_id, &mut msg).unwrap_or_default();
|
||||
context.call_cb(
|
||||
@@ -1808,7 +1731,7 @@ pub unsafe fn forward_msgs(
|
||||
msg_cnt: libc::c_int,
|
||||
chat_id: u32,
|
||||
) {
|
||||
if msg_ids.is_null() || msg_cnt <= 0 || chat_id <= DC_CHAT_ID_LAST_SPECIAL as u32 {
|
||||
if msg_ids.is_null() || msg_cnt <= 0 || chat_id <= DC_CHAT_ID_LAST_SPECIAL {
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -1847,7 +1770,7 @@ pub unsafe fn forward_msgs(
|
||||
}
|
||||
let mut msg = msg.unwrap();
|
||||
let original_param = msg.param.clone();
|
||||
if msg.from_id != DC_CONTACT_ID_SELF as u32 {
|
||||
if msg.from_id != DC_CONTACT_ID_SELF {
|
||||
msg.param.set_int(Param::Forwarded, 1);
|
||||
}
|
||||
msg.param.remove(Param::GuranteeE2ee);
|
||||
@@ -1926,16 +1849,7 @@ pub fn get_chat_cnt(context: &Context) -> usize {
|
||||
}
|
||||
}
|
||||
|
||||
pub unsafe fn get_chat_id_by_grpid(
|
||||
context: &Context,
|
||||
grpid: impl AsRef<str>,
|
||||
ret_blocked: Option<&mut Blocked>,
|
||||
ret_verified: *mut libc::c_int,
|
||||
) -> u32 {
|
||||
if !ret_verified.is_null() {
|
||||
*ret_verified = 0;
|
||||
}
|
||||
|
||||
pub fn get_chat_id_by_grpid(context: &Context, grpid: impl AsRef<str>) -> (u32, bool, Blocked) {
|
||||
context
|
||||
.sql
|
||||
.query_row(
|
||||
@@ -1944,17 +1858,12 @@ pub unsafe fn get_chat_id_by_grpid(
|
||||
|row| {
|
||||
let chat_id = row.get(0)?;
|
||||
|
||||
if let Some(b) = ret_blocked {
|
||||
*b = row.get::<_, Option<Blocked>>(1)?.unwrap_or_default();
|
||||
}
|
||||
|
||||
let b = row.get::<_, Option<Blocked>>(1)?.unwrap_or_default();
|
||||
let v = row.get::<_, Option<Chattype>>(2)?.unwrap_or_default();
|
||||
*ret_verified = (v == Chattype::VerifiedGroup) as libc::c_int;
|
||||
|
||||
Ok(chat_id)
|
||||
Ok((chat_id, v == Chattype::VerifiedGroup, b))
|
||||
},
|
||||
)
|
||||
.unwrap_or_default()
|
||||
.unwrap_or((0, false, Blocked::Not))
|
||||
}
|
||||
|
||||
pub fn add_device_msg(context: &Context, chat_id: u32, text: impl AsRef<str>) {
|
||||
|
||||
@@ -195,9 +195,9 @@ impl<'a> Chatlist<'a> {
|
||||
|
||||
if 0 != add_archived_link_item && dc_get_archived_cnt(context) > 0 {
|
||||
if ids.is_empty() && 0 != listflags & DC_GCL_ADD_ALLDONE_HINT {
|
||||
ids.push((DC_CHAT_ID_ALLDONE_HINT as u32, 0));
|
||||
ids.push((DC_CHAT_ID_ALLDONE_HINT, 0));
|
||||
}
|
||||
ids.push((DC_CHAT_ID_ARCHIVED_LINK as u32, 0));
|
||||
ids.push((DC_CHAT_ID_ARCHIVED_LINK, 0));
|
||||
}
|
||||
|
||||
Ok(Chatlist { context, ids })
|
||||
@@ -247,7 +247,7 @@ impl<'a> Chatlist<'a> {
|
||||
/// - dc_lot_t::timestamp: the timestamp of the message. 0 if not applicable.
|
||||
/// - dc_lot_t::state: The state of the message as one of the DC_STATE_* constants (see #dc_msg_get_state()).
|
||||
// 0 if not applicable.
|
||||
pub unsafe fn get_summary(&self, index: usize, chat: Option<&Chat<'a>>) -> Lot {
|
||||
pub fn get_summary(&self, index: usize, chat: Option<&Chat<'a>>) -> Lot {
|
||||
// The summary is created by the chat, not by the last message.
|
||||
// This is because we may want to display drafts here or stuff as
|
||||
// "is typing".
|
||||
@@ -290,10 +290,9 @@ impl<'a> Chatlist<'a> {
|
||||
None
|
||||
};
|
||||
|
||||
if chat.id == DC_CHAT_ID_ARCHIVED_LINK as u32 {
|
||||
if chat.id == DC_CHAT_ID_ARCHIVED_LINK {
|
||||
ret.text2 = None;
|
||||
} else if lastmsg.is_none()
|
||||
|| lastmsg.as_ref().unwrap().from_id == DC_CONTACT_ID_UNDEFINED as u32
|
||||
} else if lastmsg.is_none() || lastmsg.as_ref().unwrap().from_id == DC_CONTACT_ID_UNDEFINED
|
||||
{
|
||||
ret.text2 = Some(self.context.stock_str(StockMessage::NoMessages).to_string());
|
||||
} else {
|
||||
|
||||
@@ -33,6 +33,7 @@ pub enum Config {
|
||||
E2eeEnabled,
|
||||
#[strum(props(default = "1"))]
|
||||
MdnsEnabled,
|
||||
#[strum(props(default = "1"))]
|
||||
InboxWatch,
|
||||
#[strum(props(default = "1"))]
|
||||
SentboxWatch,
|
||||
|
||||
@@ -2,8 +2,8 @@ use quick_xml;
|
||||
use quick_xml::events::{BytesEnd, BytesStart, BytesText};
|
||||
|
||||
use crate::context::Context;
|
||||
use crate::dc_loginparam::*;
|
||||
use crate::dc_tools::*;
|
||||
use crate::login_param::LoginParam;
|
||||
use crate::x::*;
|
||||
|
||||
use super::read_autoconf_file;
|
||||
@@ -13,10 +13,10 @@ use super::read_autoconf_file;
|
||||
/* documentation: https://developer.mozilla.org/en-US/docs/Mozilla/Thunderbird/Autoconfiguration */
|
||||
#[repr(C)]
|
||||
struct moz_autoconfigure_t<'a> {
|
||||
pub in_0: &'a dc_loginparam_t,
|
||||
pub in_emaildomain: *mut libc::c_char,
|
||||
pub in_emaillocalpart: *mut libc::c_char,
|
||||
pub out: dc_loginparam_t,
|
||||
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,
|
||||
@@ -26,44 +26,37 @@ struct moz_autoconfigure_t<'a> {
|
||||
pub unsafe fn moz_autoconfigure(
|
||||
context: &Context,
|
||||
url: &str,
|
||||
param_in: &dc_loginparam_t,
|
||||
) -> Option<dc_loginparam_t> {
|
||||
let mut moz_ac = moz_autoconfigure_t {
|
||||
in_0: param_in,
|
||||
in_emaildomain: std::ptr::null_mut(),
|
||||
in_emaillocalpart: std::ptr::null_mut(),
|
||||
out: dc_loginparam_new(),
|
||||
out_imap_set: 0,
|
||||
out_smtp_set: 0,
|
||||
tag_server: 0,
|
||||
tag_config: 0,
|
||||
};
|
||||
|
||||
let url_c = url.strdup();
|
||||
let xml_raw = read_autoconf_file(context, url_c);
|
||||
free(url_c as *mut libc::c_void);
|
||||
param_in: &LoginParam,
|
||||
) -> Option<LoginParam> {
|
||||
let xml_raw = read_autoconf_file(context, url);
|
||||
if xml_raw.is_null() {
|
||||
return None;
|
||||
}
|
||||
|
||||
moz_ac.in_emaillocalpart = param_in.addr.strdup();
|
||||
let p = strchr(moz_ac.in_emaillocalpart, '@' as i32);
|
||||
|
||||
if p.is_null() {
|
||||
// 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);
|
||||
free(moz_ac.in_emaildomain as *mut libc::c_void);
|
||||
free(moz_ac.in_emaillocalpart as *mut libc::c_void);
|
||||
return None;
|
||||
}
|
||||
|
||||
*p = 0 as libc::c_char;
|
||||
moz_ac.in_emaildomain = dc_strdup(p.offset(1isize));
|
||||
let (in_emaillocalpart, in_emaildomain) = param_in.addr.split_at(p.unwrap());
|
||||
let in_emaildomain = &in_emaildomain[1..];
|
||||
|
||||
let mut reader = quick_xml::Reader::from_str(as_str(xml_raw));
|
||||
reader.trim_text(true);
|
||||
|
||||
let mut buf = Vec::new();
|
||||
|
||||
let mut moz_ac = moz_autoconfigure_t {
|
||||
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,
|
||||
};
|
||||
loop {
|
||||
match reader.read_event(&mut buf) {
|
||||
Ok(quick_xml::events::Event::Start(ref e)) => {
|
||||
@@ -93,17 +86,13 @@ pub unsafe fn moz_autoconfigure(
|
||||
|| moz_ac.out.send_server.is_empty()
|
||||
|| moz_ac.out.send_port == 0
|
||||
{
|
||||
let r = dc_loginparam_get_readable(&moz_ac.out);
|
||||
let r = moz_ac.out.to_string();
|
||||
warn!(context, 0, "Bad or incomplete autoconfig: {}", r,);
|
||||
free(xml_raw as *mut libc::c_void);
|
||||
free(moz_ac.in_emaildomain as *mut libc::c_void);
|
||||
free(moz_ac.in_emaillocalpart as *mut libc::c_void);
|
||||
return None;
|
||||
}
|
||||
|
||||
free(xml_raw as *mut libc::c_void);
|
||||
free(moz_ac.in_emaildomain as *mut libc::c_void);
|
||||
free(moz_ac.in_emaillocalpart as *mut libc::c_void);
|
||||
Some(moz_ac.out)
|
||||
}
|
||||
|
||||
@@ -115,8 +104,8 @@ fn moz_autoconfigure_text_cb<B: std::io::BufRead>(
|
||||
let val = event.unescape_and_decode(reader).unwrap_or_default();
|
||||
|
||||
let addr = &moz_ac.in_0.addr;
|
||||
let email_local = as_str(moz_ac.in_emaillocalpart);
|
||||
let email_domain = as_str(moz_ac.in_emaildomain);
|
||||
let email_local = moz_ac.in_emaillocalpart;
|
||||
let email_domain = moz_ac.in_emaildomain;
|
||||
|
||||
let val = val
|
||||
.trim()
|
||||
|
||||
@@ -2,8 +2,8 @@ use quick_xml;
|
||||
use quick_xml::events::{BytesEnd, BytesStart, BytesText};
|
||||
|
||||
use crate::context::Context;
|
||||
use crate::dc_loginparam::*;
|
||||
use crate::dc_tools::*;
|
||||
use crate::login_param::LoginParam;
|
||||
use crate::x::*;
|
||||
use std::ptr;
|
||||
|
||||
@@ -13,8 +13,8 @@ use super::read_autoconf_file;
|
||||
******************************************************************************/
|
||||
#[repr(C)]
|
||||
struct outlk_autodiscover_t<'a> {
|
||||
pub in_0: &'a dc_loginparam_t,
|
||||
pub out: dc_loginparam_t,
|
||||
pub in_0: &'a LoginParam,
|
||||
pub out: LoginParam,
|
||||
pub out_imap_set: libc::c_int,
|
||||
pub out_smtp_set: libc::c_int,
|
||||
pub tag_config: libc::c_int,
|
||||
@@ -25,13 +25,13 @@ struct outlk_autodiscover_t<'a> {
|
||||
pub unsafe fn outlk_autodiscover(
|
||||
context: &Context,
|
||||
url__: &str,
|
||||
param_in: &dc_loginparam_t,
|
||||
) -> Option<dc_loginparam_t> {
|
||||
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: dc_loginparam_new(),
|
||||
out: LoginParam::new(),
|
||||
out_imap_set: 0,
|
||||
out_smtp_set: 0,
|
||||
tag_config: 0,
|
||||
@@ -50,7 +50,7 @@ pub unsafe fn outlk_autodiscover(
|
||||
0,
|
||||
::std::mem::size_of::<outlk_autodiscover_t>(),
|
||||
);
|
||||
xml_raw = read_autoconf_file(context, url);
|
||||
xml_raw = read_autoconf_file(context, as_str(url));
|
||||
if xml_raw.is_null() {
|
||||
ok_to_continue = false;
|
||||
break;
|
||||
@@ -108,7 +108,7 @@ pub unsafe fn outlk_autodiscover(
|
||||
|| outlk_ad.out.send_server.is_empty()
|
||||
|| outlk_ad.out.send_port == 0
|
||||
{
|
||||
let r = dc_loginparam_get_readable(&outlk_ad.out);
|
||||
let r = outlk_ad.out.to_string();
|
||||
warn!(context, 0, "Bad or incomplete autoconfig: {}", r,);
|
||||
free(url as *mut libc::c_void);
|
||||
free(xml_raw as *mut libc::c_void);
|
||||
@@ -126,7 +126,7 @@ pub unsafe fn outlk_autodiscover(
|
||||
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] = 0 as *mut libc::c_char;
|
||||
(*outlk_ad).config[i] = ptr::null_mut();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
1009
src/configure/mod.rs
1009
src/configure/mod.rs
File diff suppressed because it is too large
Load Diff
@@ -18,6 +18,12 @@ pub enum MoveState {
|
||||
Moving = 3,
|
||||
}
|
||||
|
||||
impl Default for MoveState {
|
||||
fn default() -> Self {
|
||||
MoveState::Undefined
|
||||
}
|
||||
}
|
||||
|
||||
// some defaults
|
||||
const DC_E2EE_DEFAULT_ENABLED: i32 = 1;
|
||||
pub const DC_MDNS_DEFAULT_ENABLED: i32 = 1;
|
||||
@@ -42,7 +48,7 @@ impl Default for Blocked {
|
||||
|
||||
pub const DC_IMAP_SEEN: u32 = 0x1;
|
||||
|
||||
const DC_HANDSHAKE_CONTINUE_NORMAL_PROCESSING: i32 = 0x01;
|
||||
pub const DC_HANDSHAKE_CONTINUE_NORMAL_PROCESSING: i32 = 0x01;
|
||||
pub const DC_HANDSHAKE_STOP_NORMAL_PROCESSING: i32 = 0x02;
|
||||
pub const DC_HANDSHAKE_ADD_DELETE_JOB: i32 = 0x04;
|
||||
|
||||
@@ -52,7 +58,7 @@ pub const DC_GCL_ADD_ALLDONE_HINT: usize = 0x04;
|
||||
|
||||
const DC_GCM_ADDDAYMARKER: usize = 0x01;
|
||||
|
||||
const DC_GCL_VERIFIED_ONLY: usize = 0x01;
|
||||
pub const DC_GCL_VERIFIED_ONLY: usize = 0x01;
|
||||
pub const DC_GCL_ADD_SELF: usize = 0x02;
|
||||
|
||||
/// param1 is a directory where the keys are written to
|
||||
@@ -65,19 +71,19 @@ const DC_IMEX_EXPORT_BACKUP: usize = 11;
|
||||
const DC_IMEX_IMPORT_BACKUP: usize = 12;
|
||||
|
||||
/// virtual chat showing all messages belonging to chats flagged with chats.blocked=2
|
||||
pub(crate) const DC_CHAT_ID_DEADDROP: usize = 1;
|
||||
pub(crate) const DC_CHAT_ID_DEADDROP: u32 = 1;
|
||||
/// messages that should be deleted get this chat_id; the messages are deleted from the working thread later then. This is also needed as rfc724_mid should be preset as long as the message is not deleted on the server (otherwise it is downloaded again)
|
||||
pub const DC_CHAT_ID_TRASH: usize = 3;
|
||||
pub const DC_CHAT_ID_TRASH: u32 = 3;
|
||||
/// a message is just in creation but not yet assigned to a chat (eg. we may need the message ID to set up blobs; this avoids unready message to be sent and shown)
|
||||
const DC_CHAT_ID_MSGS_IN_CREATION: usize = 4;
|
||||
const DC_CHAT_ID_MSGS_IN_CREATION: u32 = 4;
|
||||
/// virtual chat showing all messages flagged with msgs.starred=2
|
||||
const DC_CHAT_ID_STARRED: usize = 5;
|
||||
const DC_CHAT_ID_STARRED: u32 = 5;
|
||||
/// only an indicator in a chatlist
|
||||
pub const DC_CHAT_ID_ARCHIVED_LINK: usize = 6;
|
||||
pub const DC_CHAT_ID_ARCHIVED_LINK: u32 = 6;
|
||||
/// only an indicator in a chatlist
|
||||
pub const DC_CHAT_ID_ALLDONE_HINT: usize = 7;
|
||||
pub const DC_CHAT_ID_ALLDONE_HINT: u32 = 7;
|
||||
/// larger chat IDs are "real" chats, their messages are "real" messages.
|
||||
pub const DC_CHAT_ID_LAST_SPECIAL: usize = 9;
|
||||
pub const DC_CHAT_ID_LAST_SPECIAL: u32 = 9;
|
||||
|
||||
#[derive(
|
||||
Debug,
|
||||
@@ -106,19 +112,19 @@ impl Default for Chattype {
|
||||
}
|
||||
}
|
||||
|
||||
pub const DC_MSG_ID_MARKER1: usize = 1;
|
||||
const DC_MSG_ID_DAYMARKER: usize = 9;
|
||||
pub const DC_MSG_ID_LAST_SPECIAL: usize = 9;
|
||||
pub const DC_MSG_ID_MARKER1: u32 = 1;
|
||||
const DC_MSG_ID_DAYMARKER: u32 = 9;
|
||||
pub const DC_MSG_ID_LAST_SPECIAL: u32 = 9;
|
||||
|
||||
/// approx. max. length returned by dc_msg_get_text()
|
||||
const DC_MAX_GET_TEXT_LEN: usize = 30000;
|
||||
/// approx. max. length returned by dc_get_msg_info()
|
||||
const DC_MAX_GET_INFO_LEN: usize = 100000;
|
||||
|
||||
pub const DC_CONTACT_ID_UNDEFINED: usize = 0;
|
||||
pub const DC_CONTACT_ID_SELF: usize = 1;
|
||||
const DC_CONTACT_ID_DEVICE: usize = 2;
|
||||
pub const DC_CONTACT_ID_LAST_SPECIAL: usize = 9;
|
||||
pub const DC_CONTACT_ID_UNDEFINED: u32 = 0;
|
||||
pub const DC_CONTACT_ID_SELF: u32 = 1;
|
||||
const DC_CONTACT_ID_DEVICE: u32 = 2;
|
||||
pub const DC_CONTACT_ID_LAST_SPECIAL: u32 = 9;
|
||||
|
||||
pub const DC_CREATE_MVBOX: usize = 1;
|
||||
|
||||
@@ -169,6 +175,12 @@ const DC_LP_IMAP_SOCKET_FLAGS: usize =
|
||||
const DC_LP_SMTP_SOCKET_FLAGS: usize =
|
||||
(DC_LP_SMTP_SOCKET_STARTTLS | DC_LP_SMTP_SOCKET_SSL | DC_LP_SMTP_SOCKET_PLAIN);
|
||||
|
||||
// QR code scanning (view from Bob, the joiner)
|
||||
pub const DC_VC_AUTH_REQUIRED: i32 = 2;
|
||||
pub const DC_VC_CONTACT_CONFIRM: i32 = 6;
|
||||
pub const DC_BOB_ERROR: i32 = 0;
|
||||
pub const DC_BOB_SUCCESS: i32 = 1;
|
||||
|
||||
#[derive(Debug, Display, Clone, Copy, PartialEq, Eq, FromPrimitive, ToPrimitive, FromSql, ToSql)]
|
||||
#[repr(i32)]
|
||||
pub enum Viewtype {
|
||||
@@ -214,6 +226,12 @@ pub enum Viewtype {
|
||||
File = 60,
|
||||
}
|
||||
|
||||
impl Default for Viewtype {
|
||||
fn default() -> Self {
|
||||
Viewtype::Unknown
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
|
||||
110
src/contact.rs
110
src/contact.rs
@@ -1,25 +1,22 @@
|
||||
use deltachat_derive::*;
|
||||
use itertools::Itertools;
|
||||
use num_traits::{FromPrimitive, ToPrimitive};
|
||||
use rusqlite;
|
||||
use rusqlite::types::*;
|
||||
|
||||
use crate::aheader::EncryptPreference;
|
||||
use crate::config::Config;
|
||||
use crate::constants::*;
|
||||
use crate::context::Context;
|
||||
use crate::dc_e2ee::*;
|
||||
use crate::dc_loginparam::*;
|
||||
use crate::dc_tools::*;
|
||||
use crate::e2ee;
|
||||
use crate::error::Result;
|
||||
use crate::key::*;
|
||||
use crate::login_param::LoginParam;
|
||||
use crate::message::MessageState;
|
||||
use crate::peerstate::*;
|
||||
use crate::sql;
|
||||
use crate::stock::StockMessage;
|
||||
use crate::types::*;
|
||||
|
||||
const DC_GCL_VERIFIED_ONLY: u32 = 0x01;
|
||||
|
||||
/// Contacts with at least this origin value are shown in the contact list.
|
||||
const DC_ORIGIN_MIN_CONTACT_LIST: i32 = 0x100;
|
||||
|
||||
@@ -60,7 +57,9 @@ pub struct Contact<'a> {
|
||||
}
|
||||
|
||||
/// Possible origins of a contact.
|
||||
#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, FromPrimitive, ToPrimitive)]
|
||||
#[derive(
|
||||
Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, FromPrimitive, ToPrimitive, FromSql, ToSql,
|
||||
)]
|
||||
#[repr(i32)]
|
||||
pub enum Origin {
|
||||
Unknown = 0,
|
||||
@@ -98,20 +97,9 @@ pub enum Origin {
|
||||
ManuallyCreated = 0x4000000,
|
||||
}
|
||||
|
||||
impl ToSql for Origin {
|
||||
fn to_sql(&self) -> rusqlite::Result<ToSqlOutput> {
|
||||
let num: i64 = self
|
||||
.to_i64()
|
||||
.expect("impossible: Origin -> i64 conversion failed");
|
||||
|
||||
Ok(ToSqlOutput::Owned(Value::Integer(num)))
|
||||
}
|
||||
}
|
||||
|
||||
impl FromSql for Origin {
|
||||
fn column_result(col: ValueRef) -> FromSqlResult<Self> {
|
||||
let inner = FromSql::column_result(col)?;
|
||||
FromPrimitive::from_i64(inner).ok_or(FromSqlError::InvalidType)
|
||||
impl Default for Origin {
|
||||
fn default() -> Self {
|
||||
Origin::Unknown
|
||||
}
|
||||
}
|
||||
|
||||
@@ -152,7 +140,7 @@ pub enum VerifiedStatus {
|
||||
|
||||
impl<'a> Contact<'a> {
|
||||
pub fn load_from_db(context: &'a Context, contact_id: u32) -> Result<Self> {
|
||||
if contact_id == DC_CONTACT_ID_SELF as u32 {
|
||||
if contact_id == DC_CONTACT_ID_SELF {
|
||||
let contact = Contact {
|
||||
context,
|
||||
id: contact_id,
|
||||
@@ -343,19 +331,22 @@ impl<'a> Contact<'a> {
|
||||
let row_id = row.get(0)?;
|
||||
let row_name: String = row.get(1)?;
|
||||
let row_addr: String = row.get(2)?;
|
||||
let row_origin = row.get(3)?;
|
||||
let row_origin: Origin = row.get(3)?;
|
||||
let row_authname: String = row.get(4)?;
|
||||
|
||||
if !name.as_ref().is_empty() && !row_name.is_empty() {
|
||||
if origin >= row_origin && name.as_ref() != row_name {
|
||||
if !name.as_ref().is_empty() {
|
||||
if !row_name.is_empty() {
|
||||
if origin >= row_origin && name.as_ref() != row_name {
|
||||
update_name = true;
|
||||
}
|
||||
} else {
|
||||
update_name = true;
|
||||
}
|
||||
} else {
|
||||
update_name = true;
|
||||
}
|
||||
if origin == Origin::IncomingUnknownFrom && name.as_ref() != row_authname {
|
||||
update_authname = true;
|
||||
if origin == Origin::IncomingUnknownFrom && name.as_ref() != row_authname {
|
||||
update_authname = true;
|
||||
}
|
||||
}
|
||||
|
||||
Ok((row_id, row_name, row_addr, row_origin, row_authname))
|
||||
},
|
||||
) {
|
||||
@@ -395,7 +386,7 @@ impl<'a> Contact<'a> {
|
||||
context,
|
||||
&context.sql,
|
||||
"UPDATE chats SET name=? WHERE type=? AND id IN(SELECT chat_id FROM chats_contacts WHERE contact_id=?);",
|
||||
params![name.as_ref(), 100, row_id]
|
||||
params![name.as_ref(), Chattype::Single, row_id]
|
||||
).ok();
|
||||
}
|
||||
sth_modified = Modifier::Modified;
|
||||
@@ -433,19 +424,13 @@ impl<'a> Contact<'a> {
|
||||
/// To add a single contact entered by the user, you should prefer `Contact::create`,
|
||||
/// however, for adding a bunch of addresses, this function is _much_ faster.
|
||||
///
|
||||
/// The `adr_book` is a multiline string in the format `Name one\nAddress one\nName two\nAddress two`.
|
||||
/// The `addr_book` is a multiline string in the format `Name one\nAddress one\nName two\nAddress two`.
|
||||
///
|
||||
/// Returns the number of modified contacts.
|
||||
pub fn add_address_book(context: &Context, adr_book: impl AsRef<str>) -> Result<usize> {
|
||||
pub fn add_address_book(context: &Context, addr_book: impl AsRef<str>) -> Result<usize> {
|
||||
let mut modify_cnt = 0;
|
||||
|
||||
for chunk in &adr_book.as_ref().lines().chunks(2) {
|
||||
let chunk = chunk.collect::<Vec<_>>();
|
||||
if chunk.len() < 2 {
|
||||
break;
|
||||
}
|
||||
let name = chunk[0];
|
||||
let addr = chunk[1];
|
||||
for (name, addr) in split_address_book(addr_book.as_ref()).into_iter() {
|
||||
let name = normalize_name(name);
|
||||
let (_, modified) = Contact::add_or_lookup(context, name, addr, Origin::AdressBook)?;
|
||||
if modified != Modifier::None {
|
||||
@@ -479,8 +464,10 @@ impl<'a> Contact<'a> {
|
||||
|
||||
let mut add_self = false;
|
||||
let mut ret = Vec::new();
|
||||
let flag_verified_only = listflags_has(listflags, DC_GCL_VERIFIED_ONLY);
|
||||
let flag_add_self = listflags_has(listflags, DC_GCL_ADD_SELF);
|
||||
|
||||
if (listflags & DC_GCL_VERIFIED_ONLY) > 0 || query.is_some() {
|
||||
if flag_verified_only || query.is_some() {
|
||||
let s3str_like_cmd = format!(
|
||||
"%{}%",
|
||||
query
|
||||
@@ -504,7 +491,7 @@ impl<'a> Contact<'a> {
|
||||
0x100,
|
||||
&s3str_like_cmd,
|
||||
&s3str_like_cmd,
|
||||
if 0 != listflags & 0x1 { 0 } else { 1 },
|
||||
if flag_verified_only { 0 } else { 1 },
|
||||
],
|
||||
|row| row.get::<_, i32>(0),
|
||||
|ids| {
|
||||
@@ -544,8 +531,8 @@ impl<'a> Contact<'a> {
|
||||
)?;
|
||||
}
|
||||
|
||||
if 0 != listflags & DC_GCL_ADD_SELF as u32 && add_self {
|
||||
ret.push(DC_CONTACT_ID_SELF as u32);
|
||||
if flag_add_self && add_self {
|
||||
ret.push(DC_CONTACT_ID_SELF);
|
||||
}
|
||||
|
||||
Ok(ret)
|
||||
@@ -589,7 +576,7 @@ impl<'a> Contact<'a> {
|
||||
|
||||
if let Ok(contact) = Contact::load_from_db(context, contact_id) {
|
||||
let peerstate = Peerstate::from_addr(context, &context.sql, &contact.addr);
|
||||
let loginparam = dc_loginparam_read(context, &context.sql, "configured_");
|
||||
let loginparam = LoginParam::from_database(context, "configured_");
|
||||
|
||||
let mut self_key = Key::from_self_public(context, &loginparam.addr, &context.sql);
|
||||
|
||||
@@ -603,7 +590,7 @@ impl<'a> Contact<'a> {
|
||||
});
|
||||
ret += &p;
|
||||
if self_key.is_none() {
|
||||
dc_ensure_secret_key_exists(context)?;
|
||||
e2ee::ensure_secret_key_exists(context)?;
|
||||
self_key = Key::from_self_public(context, &loginparam.addr, &context.sql);
|
||||
}
|
||||
let p = context.stock_str(StockMessage::FingerPrints);
|
||||
@@ -655,7 +642,7 @@ impl<'a> Contact<'a> {
|
||||
/// May result in a `#DC_EVENT_CONTACTS_CHANGED` event.
|
||||
pub fn delete(context: &Context, contact_id: u32) -> Result<()> {
|
||||
ensure!(
|
||||
contact_id > DC_CONTACT_ID_LAST_SPECIAL as u32,
|
||||
contact_id > DC_CONTACT_ID_LAST_SPECIAL,
|
||||
"Can not delete special contact"
|
||||
);
|
||||
|
||||
@@ -780,7 +767,7 @@ impl<'a> Contact<'a> {
|
||||
/// This is the image set by each remote user on their own
|
||||
/// using dc_set_config(context, "selfavatar", image).
|
||||
pub fn get_profile_image(&self) -> Option<String> {
|
||||
if self.id == DC_CONTACT_ID_SELF as u32 {
|
||||
if self.id == DC_CONTACT_ID_SELF {
|
||||
return self.context.get_config(Config::Selfavatar);
|
||||
}
|
||||
// TODO: else get image_abs from contact param
|
||||
@@ -810,7 +797,7 @@ impl<'a> Contact<'a> {
|
||||
pub fn is_verified_ex(&self, peerstate: Option<&Peerstate<'a>>) -> VerifiedStatus {
|
||||
// We're always sort of secured-verified as we could verify the key on this device any time with the key
|
||||
// on this device
|
||||
if self.id == DC_CONTACT_ID_SELF as u32 {
|
||||
if self.id == DC_CONTACT_ID_SELF {
|
||||
return VerifiedStatus::BidirectVerified;
|
||||
}
|
||||
|
||||
@@ -1043,6 +1030,21 @@ pub fn addr_equals_self(context: &Context, addr: impl AsRef<str>) -> bool {
|
||||
false
|
||||
}
|
||||
|
||||
fn split_address_book(book: &str) -> Vec<(&str, &str)> {
|
||||
book.lines()
|
||||
.chunks(2)
|
||||
.into_iter()
|
||||
.filter_map(|mut chunk| {
|
||||
let name = chunk.next().unwrap();
|
||||
let addr = match chunk.next() {
|
||||
Some(a) => a,
|
||||
None => return None,
|
||||
};
|
||||
Some((name, addr))
|
||||
})
|
||||
.collect()
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
@@ -1078,4 +1080,14 @@ mod tests {
|
||||
fn test_get_first_name() {
|
||||
assert_eq!(get_first_name("John Doe"), "John");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_split_address_book() {
|
||||
let book = "Name one\nAddress one\nName two\nAddress two\nrest name";
|
||||
let list = split_address_book(&book);
|
||||
assert_eq!(
|
||||
list,
|
||||
vec![("Name one", "Address one"), ("Name two", "Address two")]
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
292
src/context.rs
292
src/context.rs
@@ -1,19 +1,15 @@
|
||||
use std::ffi::OsString;
|
||||
use std::path::Path;
|
||||
use std::sync::{Arc, Condvar, Mutex, RwLock};
|
||||
|
||||
use crate::chat::*;
|
||||
use crate::constants::*;
|
||||
use crate::contact::*;
|
||||
use crate::dc_loginparam::*;
|
||||
use crate::dc_move::*;
|
||||
use crate::dc_receive_imf::*;
|
||||
use crate::dc_tools::*;
|
||||
use crate::error::*;
|
||||
use crate::imap::*;
|
||||
use crate::job::*;
|
||||
use crate::job_thread::JobThread;
|
||||
use crate::key::*;
|
||||
use crate::login_param::LoginParam;
|
||||
use crate::lot::Lot;
|
||||
use crate::message::*;
|
||||
use crate::param::Params;
|
||||
@@ -25,15 +21,6 @@ use std::ptr;
|
||||
|
||||
use std::path::PathBuf;
|
||||
|
||||
/// Callback function type for [Context].
|
||||
///
|
||||
/// @param context The context object as returned by dc_context_new().
|
||||
/// @param event one of the @ref DC_EVENT constants
|
||||
/// @param data1 depends on the event parameter
|
||||
/// @param data2 depends on the event parameter
|
||||
/// @return return 0 unless stated otherwise in the event parameter documentation
|
||||
pub type ContextCallback = dyn Fn(&Context, Event, uintptr_t, uintptr_t) -> uintptr_t;
|
||||
|
||||
pub struct Context {
|
||||
pub userdata: *mut libc::c_void,
|
||||
pub dbfile: Arc<RwLock<Option<PathBuf>>>,
|
||||
@@ -47,7 +34,7 @@ pub struct Context {
|
||||
pub smtp: Arc<Mutex<Smtp>>,
|
||||
pub smtp_state: Arc<(Mutex<SmtpState>, Condvar)>,
|
||||
pub oauth2_critical: Arc<Mutex<()>>,
|
||||
pub cb: Box<ContextCallback>,
|
||||
pub cb: Option<dc_callback_t>,
|
||||
pub os_name: Option<String>,
|
||||
pub cmdline_sel_chat_id: Arc<RwLock<u32>>,
|
||||
pub bob: Arc<RwLock<BobStatus>>,
|
||||
@@ -67,76 +54,6 @@ pub struct RunningState {
|
||||
}
|
||||
|
||||
impl Context {
|
||||
pub fn new(cb: Box<ContextCallback>, os_name: String, dbfile: PathBuf) -> Result<Context> {
|
||||
let mut blob_fname = OsString::new();
|
||||
blob_fname.push(dbfile.file_name().unwrap_or_default());
|
||||
blob_fname.push("-blobs");
|
||||
let blobdir = dbfile.with_file_name(blob_fname);
|
||||
if !blobdir.exists() {
|
||||
std::fs::create_dir_all(&blobdir)?;
|
||||
}
|
||||
Context::with_blobdir(cb, os_name, dbfile, &blobdir)
|
||||
}
|
||||
|
||||
pub fn with_blobdir(
|
||||
cb: Box<ContextCallback>,
|
||||
os_name: String,
|
||||
dbfile: PathBuf,
|
||||
blobdir: &Path,
|
||||
) -> Result<Context> {
|
||||
if !blobdir.is_dir() {
|
||||
return Err(format_err!("Blobdir does not exist: {}", blobdir.display()));
|
||||
}
|
||||
let ctx = Context {
|
||||
blobdir: Arc::new(RwLock::new(unsafe { blobdir.strdup() })),
|
||||
dbfile: Arc::new(RwLock::new(Some(dbfile))),
|
||||
inbox: Arc::new(RwLock::new({
|
||||
Imap::new(
|
||||
cb_get_config,
|
||||
cb_set_config,
|
||||
cb_precheck_imf,
|
||||
cb_receive_imf,
|
||||
)
|
||||
})),
|
||||
userdata: std::ptr::null_mut(),
|
||||
cb,
|
||||
os_name: Some(os_name),
|
||||
running_state: Arc::new(RwLock::new(Default::default())),
|
||||
sql: Sql::new(),
|
||||
smtp: Arc::new(Mutex::new(Smtp::new())),
|
||||
smtp_state: Arc::new((Mutex::new(Default::default()), Condvar::new())),
|
||||
oauth2_critical: Arc::new(Mutex::new(())),
|
||||
bob: Arc::new(RwLock::new(Default::default())),
|
||||
last_smeared_timestamp: Arc::new(RwLock::new(0)),
|
||||
cmdline_sel_chat_id: Arc::new(RwLock::new(0)),
|
||||
sentbox_thread: Arc::new(RwLock::new(JobThread::new(
|
||||
"SENTBOX",
|
||||
"configured_sentbox_folder",
|
||||
Imap::new(
|
||||
cb_get_config,
|
||||
cb_set_config,
|
||||
cb_precheck_imf,
|
||||
cb_receive_imf,
|
||||
),
|
||||
))),
|
||||
mvbox_thread: Arc::new(RwLock::new(JobThread::new(
|
||||
"MVBOX",
|
||||
"configured_mvbox_folder",
|
||||
Imap::new(
|
||||
cb_get_config,
|
||||
cb_set_config,
|
||||
cb_precheck_imf,
|
||||
cb_receive_imf,
|
||||
),
|
||||
))),
|
||||
probe_imap_network: Arc::new(RwLock::new(false)),
|
||||
perform_inbox_jobs_needed: Arc::new(RwLock::new(false)),
|
||||
generating_key_mutex: Mutex::new(()),
|
||||
};
|
||||
ctx.sql.open(&ctx, &ctx.dbfile.read().unwrap().as_ref().unwrap(), 0)?;
|
||||
Ok(ctx)
|
||||
}
|
||||
|
||||
pub fn has_dbfile(&self) -> bool {
|
||||
self.get_dbfile().is_some()
|
||||
}
|
||||
@@ -156,7 +73,11 @@ impl Context {
|
||||
}
|
||||
|
||||
pub fn call_cb(&self, event: Event, data1: uintptr_t, data2: uintptr_t) -> uintptr_t {
|
||||
(*self.cb)(self, event, data1, data2)
|
||||
if let Some(cb) = self.cb {
|
||||
unsafe { cb(self, event, data1, data2) }
|
||||
} else {
|
||||
0
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -193,6 +114,60 @@ pub struct SmtpState {
|
||||
pub probe_network: bool,
|
||||
}
|
||||
|
||||
// create/open/config/information
|
||||
pub fn dc_context_new(
|
||||
cb: Option<dc_callback_t>,
|
||||
userdata: *mut libc::c_void,
|
||||
os_name: Option<String>,
|
||||
) -> Context {
|
||||
Context {
|
||||
blobdir: Arc::new(RwLock::new(std::ptr::null_mut())),
|
||||
dbfile: Arc::new(RwLock::new(None)),
|
||||
inbox: Arc::new(RwLock::new({
|
||||
Imap::new(
|
||||
cb_get_config,
|
||||
cb_set_config,
|
||||
cb_precheck_imf,
|
||||
cb_receive_imf,
|
||||
)
|
||||
})),
|
||||
userdata,
|
||||
cb,
|
||||
os_name,
|
||||
running_state: Arc::new(RwLock::new(Default::default())),
|
||||
sql: Sql::new(),
|
||||
smtp: Arc::new(Mutex::new(Smtp::new())),
|
||||
smtp_state: Arc::new((Mutex::new(Default::default()), Condvar::new())),
|
||||
oauth2_critical: Arc::new(Mutex::new(())),
|
||||
bob: Arc::new(RwLock::new(Default::default())),
|
||||
last_smeared_timestamp: Arc::new(RwLock::new(0)),
|
||||
cmdline_sel_chat_id: Arc::new(RwLock::new(0)),
|
||||
sentbox_thread: Arc::new(RwLock::new(JobThread::new(
|
||||
"SENTBOX",
|
||||
"configured_sentbox_folder",
|
||||
Imap::new(
|
||||
cb_get_config,
|
||||
cb_set_config,
|
||||
cb_precheck_imf,
|
||||
cb_receive_imf,
|
||||
),
|
||||
))),
|
||||
mvbox_thread: Arc::new(RwLock::new(JobThread::new(
|
||||
"MVBOX",
|
||||
"configured_mvbox_folder",
|
||||
Imap::new(
|
||||
cb_get_config,
|
||||
cb_set_config,
|
||||
cb_precheck_imf,
|
||||
cb_receive_imf,
|
||||
),
|
||||
))),
|
||||
probe_imap_network: Arc::new(RwLock::new(false)),
|
||||
perform_inbox_jobs_needed: Arc::new(RwLock::new(false)),
|
||||
generating_key_mutex: Mutex::new(()),
|
||||
}
|
||||
}
|
||||
|
||||
unsafe fn cb_receive_imf(
|
||||
context: &Context,
|
||||
imf_raw_not_terminated: *const libc::c_char,
|
||||
@@ -252,7 +227,7 @@ unsafe fn cb_precheck_imf(
|
||||
if as_str(old_server_folder) != server_folder || old_server_uid != server_uid {
|
||||
dc_update_server_uid(context, rfc724_mid, server_folder, server_uid);
|
||||
}
|
||||
dc_do_heuristics_moves(context, server_folder, msg_id);
|
||||
do_heuristics_moves(context, server_folder, msg_id);
|
||||
if 0 != mark_seen {
|
||||
job_add(
|
||||
context,
|
||||
@@ -311,10 +286,41 @@ pub unsafe fn dc_close(context: &Context) {
|
||||
*blobdir = ptr::null_mut();
|
||||
}
|
||||
|
||||
pub unsafe fn dc_is_open(context: &Context) -> libc::c_int {
|
||||
context.sql.is_open() as libc::c_int
|
||||
}
|
||||
|
||||
pub unsafe fn dc_get_userdata(context: &mut Context) -> *mut libc::c_void {
|
||||
context.userdata as *mut _
|
||||
}
|
||||
|
||||
pub unsafe fn dc_open(context: &Context, dbfile: &str, blobdir: Option<&str>) -> bool {
|
||||
let mut success = false;
|
||||
if 0 != dc_is_open(context) {
|
||||
return false;
|
||||
}
|
||||
|
||||
*context.dbfile.write().unwrap() = Some(PathBuf::from(dbfile));
|
||||
let blobdir = blobdir.unwrap_or_default();
|
||||
if !blobdir.is_empty() {
|
||||
let dir = dc_ensure_no_slash_safe(blobdir).strdup();
|
||||
*context.blobdir.write().unwrap() = dir;
|
||||
} else {
|
||||
let dir = dbfile.to_string() + "-blobs";
|
||||
dc_create_folder(context, &dir);
|
||||
*context.blobdir.write().unwrap() = dir.strdup();
|
||||
}
|
||||
// Create/open sqlite database, this may already use the blobdir
|
||||
let dbfile_path = std::path::Path::new(dbfile);
|
||||
if context.sql.open(context, dbfile_path, 0) {
|
||||
success = true
|
||||
}
|
||||
if !success {
|
||||
dc_close(context);
|
||||
}
|
||||
success
|
||||
}
|
||||
|
||||
pub unsafe fn dc_get_blobdir(context: &Context) -> *mut libc::c_char {
|
||||
dc_strdup(*context.blobdir.clone().read().unwrap())
|
||||
}
|
||||
@@ -325,8 +331,8 @@ pub unsafe fn dc_get_blobdir(context: &Context) -> *mut libc::c_char {
|
||||
|
||||
pub unsafe fn dc_get_info(context: &Context) -> *mut libc::c_char {
|
||||
let unset = "0";
|
||||
let l = dc_loginparam_read(context, &context.sql, "");
|
||||
let l2 = dc_loginparam_read(context, &context.sql, "configured_");
|
||||
let l = LoginParam::from_database(context, "");
|
||||
let l2 = LoginParam::from_database(context, "configured_");
|
||||
let displayname = context.sql.get_config(context, "displayname");
|
||||
let chats = get_chat_cnt(context) as usize;
|
||||
let real_msgs = dc_get_real_msg_cnt(context) as usize;
|
||||
@@ -370,8 +376,6 @@ pub unsafe fn dc_get_info(context: &Context) -> *mut libc::c_char {
|
||||
"<Not yet calculated>".into()
|
||||
};
|
||||
|
||||
let l_readable_str = dc_loginparam_get_readable(&l);
|
||||
let l2_readable_str = dc_loginparam_get_readable(&l2);
|
||||
let inbox_watch = context
|
||||
.sql
|
||||
.get_config_int(context, "inbox_watch")
|
||||
@@ -451,8 +455,8 @@ pub unsafe fn dc_get_info(context: &Context) -> *mut libc::c_char {
|
||||
},
|
||||
displayname.unwrap_or_else(|| unset.into()),
|
||||
is_configured,
|
||||
l_readable_str,
|
||||
l2_readable_str,
|
||||
l,
|
||||
l2,
|
||||
inbox_watch,
|
||||
sentbox_watch,
|
||||
mvbox_watch,
|
||||
@@ -568,74 +572,62 @@ pub fn dc_is_mvbox(context: &Context, folder_name: impl AsRef<str>) -> bool {
|
||||
}
|
||||
}
|
||||
|
||||
pub fn do_heuristics_moves(context: &Context, folder: &str, msg_id: u32) {
|
||||
if context
|
||||
.sql
|
||||
.get_config_int(context, "mvbox_move")
|
||||
.unwrap_or_else(|| 1)
|
||||
== 0
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
if !dc_is_inbox(context, folder) && !dc_is_sentbox(context, folder) {
|
||||
return;
|
||||
}
|
||||
|
||||
if let Ok(msg) = dc_msg_new_load(context, msg_id) {
|
||||
if dc_msg_is_setupmessage(&msg) {
|
||||
// do not move setup messages;
|
||||
// there may be a non-delta device that wants to handle it
|
||||
return;
|
||||
}
|
||||
|
||||
if dc_is_mvbox(context, folder) {
|
||||
dc_update_msg_move_state(context, msg.rfc724_mid, MoveState::Stay);
|
||||
}
|
||||
|
||||
// 1 = dc message, 2 = reply to dc message
|
||||
if 0 != msg.is_dc_message {
|
||||
job_add(
|
||||
context,
|
||||
Action::MoveMsg,
|
||||
msg.id as libc::c_int,
|
||||
Params::new(),
|
||||
0,
|
||||
);
|
||||
dc_update_msg_move_state(context, msg.rfc724_mid, MoveState::Moving);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
|
||||
use crate::test_utils::*;
|
||||
|
||||
#[test]
|
||||
fn test_wrong_db() {
|
||||
let tmp = tempfile::tempdir().unwrap();
|
||||
let dbfile = tmp.path().join("db.sqlite");
|
||||
std::fs::write(&dbfile, b"123").unwrap();
|
||||
let res = Context::new(Box::new(|_, _, _, _| 0), "FakeOs".into(), dbfile);
|
||||
assert!(res.is_err());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_blobdir_exists() {
|
||||
let tmp = tempfile::tempdir().unwrap();
|
||||
let dbfile = tmp.path().join("db.sqlite");
|
||||
Context::new(Box::new(|_, _, _, _| 0), "FakeOS".into(), dbfile).unwrap();
|
||||
let blobdir = tmp.path().join("db.sqlite-blobs");
|
||||
assert!(blobdir.is_dir());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_wrong_blogdir() {
|
||||
let tmp = tempfile::tempdir().unwrap();
|
||||
let dbfile = tmp.path().join("db.sqlite");
|
||||
let blobdir = tmp.path().join("db.sqlite-blobs");
|
||||
std::fs::write(&blobdir, b"123").unwrap();
|
||||
let res = Context::new(Box::new(|_, _, _, _| 0), "FakeOS".into(), dbfile);
|
||||
assert!(res.is_err());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_sqlite_parent_not_exists() {
|
||||
let tmp = tempfile::tempdir().unwrap();
|
||||
let subdir = tmp.path().join("subdir");
|
||||
let dbfile = subdir.join("db.sqlite");
|
||||
let dbfile2 = dbfile.clone();
|
||||
Context::new(Box::new(|_, _, _, _| 0), "FakeOS".into(), dbfile).unwrap();
|
||||
assert!(subdir.is_dir());
|
||||
assert!(dbfile2.is_file());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_with_blobdir_not_exists() {
|
||||
let tmp = tempfile::tempdir().unwrap();
|
||||
let dbfile = tmp.path().join("db.sqlite");
|
||||
let blobdir = tmp.path().join("blobs");
|
||||
let res =
|
||||
Context::with_blobdir(Box::new(|_, _, _, _| 0), "FakeOS".into(), dbfile, &blobdir);
|
||||
assert!(res.is_err());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn no_crashes_on_context_deref() {
|
||||
let t = dummy_context();
|
||||
std::mem::drop(t.ctx);
|
||||
let ctx = dc_context_new(None, std::ptr::null_mut(), Some("Test OS".into()));
|
||||
std::mem::drop(ctx);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_context_double_close() {
|
||||
let t = dummy_context();
|
||||
let ctx = dc_context_new(None, std::ptr::null_mut(), None);
|
||||
unsafe {
|
||||
dc_close(&t.ctx);
|
||||
dc_close(&t.ctx);
|
||||
dc_close(&ctx);
|
||||
dc_close(&ctx);
|
||||
}
|
||||
std::mem::drop(t.ctx);
|
||||
std::mem::drop(ctx);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,11 +1,11 @@
|
||||
use crate::dc_location::dc_location;
|
||||
use crate::location::Location;
|
||||
use crate::types::*;
|
||||
|
||||
/* * the structure behind dc_array_t */
|
||||
#[derive(Clone)]
|
||||
#[allow(non_camel_case_types)]
|
||||
pub enum dc_array_t {
|
||||
Locations(Vec<dc_location>),
|
||||
Locations(Vec<Location>),
|
||||
Uint(Vec<u32>),
|
||||
}
|
||||
|
||||
@@ -27,7 +27,7 @@ impl dc_array_t {
|
||||
}
|
||||
}
|
||||
|
||||
pub fn add_location(&mut self, location: dc_location) {
|
||||
pub fn add_location(&mut self, location: Location) {
|
||||
if let Self::Locations(array) = self {
|
||||
array.push(location)
|
||||
} else {
|
||||
@@ -42,7 +42,7 @@ impl dc_array_t {
|
||||
}
|
||||
}
|
||||
|
||||
pub fn get_location(&self, index: usize) -> &dc_location {
|
||||
pub fn get_location(&self, index: usize) -> &Location {
|
||||
if let Self::Locations(array) = self {
|
||||
&array[index]
|
||||
} else {
|
||||
@@ -108,8 +108,8 @@ impl From<Vec<u32>> for dc_array_t {
|
||||
}
|
||||
}
|
||||
|
||||
impl From<Vec<dc_location>> for dc_array_t {
|
||||
fn from(array: Vec<dc_location>) -> Self {
|
||||
impl From<Vec<Location>> for dc_array_t {
|
||||
fn from(array: Vec<Location>) -> Self {
|
||||
dc_array_t::Locations(array)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2,9 +2,6 @@ use lazy_static::lazy_static;
|
||||
use quick_xml;
|
||||
use quick_xml::events::{BytesEnd, BytesStart, BytesText};
|
||||
|
||||
use crate::dc_tools::*;
|
||||
use crate::x::*;
|
||||
|
||||
lazy_static! {
|
||||
static ref LINE_RE: regex::Regex = regex::Regex::new(r"(\r?\n)+").unwrap();
|
||||
}
|
||||
@@ -24,19 +21,20 @@ enum AddText {
|
||||
|
||||
// dc_dehtml() returns way too many lineends; however, an optimisation on this issue is not needed as
|
||||
// the lineends are typically remove in further processing by the caller
|
||||
pub unsafe fn dc_dehtml(buf_terminated: *mut libc::c_char) -> *mut libc::c_char {
|
||||
dc_trim(buf_terminated);
|
||||
if *buf_terminated.offset(0isize) as libc::c_int == 0i32 {
|
||||
return dc_strdup(b"\x00" as *const u8 as *const libc::c_char);
|
||||
pub fn dc_dehtml(buf_terminated: &str) -> String {
|
||||
let buf_terminated = buf_terminated.trim();
|
||||
|
||||
if buf_terminated.is_empty() {
|
||||
return "".into();
|
||||
}
|
||||
|
||||
let mut dehtml = Dehtml {
|
||||
strbuilder: String::with_capacity(strlen(buf_terminated)),
|
||||
strbuilder: String::with_capacity(buf_terminated.len()),
|
||||
add_text: AddText::YesRemoveLineEnds,
|
||||
last_href: None,
|
||||
};
|
||||
|
||||
let mut reader = quick_xml::Reader::from_str(as_str(buf_terminated));
|
||||
let mut reader = quick_xml::Reader::from_str(buf_terminated);
|
||||
|
||||
let mut buf = Vec::new();
|
||||
|
||||
@@ -61,7 +59,7 @@ pub unsafe fn dc_dehtml(buf_terminated: *mut libc::c_char) -> *mut libc::c_char
|
||||
buf.clear();
|
||||
}
|
||||
|
||||
dehtml.strbuilder.strdup()
|
||||
dehtml.strbuilder
|
||||
}
|
||||
|
||||
fn dehtml_text_cb(event: &BytesText, dehtml: &mut Dehtml) {
|
||||
|
||||
164
src/dc_imex.rs
164
src/dc_imex.rs
@@ -11,8 +11,8 @@ use crate::config::Config;
|
||||
use crate::configure::*;
|
||||
use crate::constants::*;
|
||||
use crate::context::Context;
|
||||
use crate::dc_e2ee::*;
|
||||
use crate::dc_tools::*;
|
||||
use crate::e2ee;
|
||||
use crate::error::*;
|
||||
use crate::job::*;
|
||||
use crate::key::*;
|
||||
@@ -74,7 +74,7 @@ pub unsafe fn dc_imex_has_backup(
|
||||
let name = name.to_string_lossy();
|
||||
if name.starts_with("delta-chat") && name.ends_with(".bak") {
|
||||
let sql = Sql::new();
|
||||
if sql.open(context, &path, 0x1).is_ok() {
|
||||
if sql.open(context, &path, 0x1) {
|
||||
let curr_backup_time =
|
||||
sql.get_config_int(context, "backup_time")
|
||||
.unwrap_or_default() as u64;
|
||||
@@ -103,7 +103,7 @@ pub unsafe fn dc_imex_has_backup(
|
||||
pub unsafe fn dc_initiate_key_transfer(context: &Context) -> *mut libc::c_char {
|
||||
let mut setup_file_name: *mut libc::c_char = ptr::null_mut();
|
||||
let mut msg: Message;
|
||||
if dc_alloc_ongoing(context) == 0 {
|
||||
if !dc_alloc_ongoing(context) {
|
||||
return std::ptr::null_mut();
|
||||
}
|
||||
let setup_code = dc_create_setup_code(context);
|
||||
@@ -196,7 +196,7 @@ pub fn dc_render_setup_file(context: &Context, passphrase: &str) -> Result<Strin
|
||||
passphrase.len() >= 2,
|
||||
"Passphrase must be at least 2 chars long."
|
||||
);
|
||||
let self_addr = dc_ensure_secret_key_exists(context)?;
|
||||
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
|
||||
@@ -502,11 +502,9 @@ pub unsafe fn dc_normalize_setup_code(
|
||||
pub unsafe fn dc_job_do_DC_JOB_IMEX_IMAP(context: &Context, job: &Job) {
|
||||
let mut ok_to_continue = true;
|
||||
let mut success: libc::c_int = 0;
|
||||
let mut ongoing_allocated_here: libc::c_int = 0;
|
||||
let what: libc::c_int;
|
||||
|
||||
if !(0 == dc_alloc_ongoing(context)) {
|
||||
ongoing_allocated_here = 1;
|
||||
if dc_alloc_ongoing(context) {
|
||||
what = job.param.get_int(Param::Cmd).unwrap_or_default();
|
||||
let param1_s = job.param.get(Param::Arg).unwrap_or_default();
|
||||
let param1 = CString::yolo(param1_s);
|
||||
@@ -522,7 +520,7 @@ pub unsafe fn dc_job_do_DC_JOB_IMEX_IMAP(context: &Context, job: &Job) {
|
||||
} else {
|
||||
if what == 1 || what == 11 {
|
||||
/* before we export anything, make sure the private key exists */
|
||||
if dc_ensure_secret_key_exists(context).is_err() {
|
||||
if e2ee::ensure_secret_key_exists(context).is_err() {
|
||||
error!(
|
||||
context,
|
||||
0,
|
||||
@@ -564,9 +562,6 @@ pub unsafe fn dc_job_do_DC_JOB_IMEX_IMAP(context: &Context, job: &Job) {
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if 0 != ongoing_allocated_here {
|
||||
dc_free_ongoing(context);
|
||||
}
|
||||
context.call_cb(
|
||||
@@ -617,10 +612,9 @@ unsafe fn import_backup(context: &Context, backup_to_import: *const libc::c_char
|
||||
}
|
||||
/* error already logged */
|
||||
/* re-open copied database file */
|
||||
if context
|
||||
if !context
|
||||
.sql
|
||||
.open(&context, &context.get_dbfile().unwrap(), 0)
|
||||
.is_err()
|
||||
{
|
||||
return 0;
|
||||
}
|
||||
@@ -711,9 +705,9 @@ unsafe fn import_backup(context: &Context, backup_to_import: *const libc::c_char
|
||||
/* 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. */
|
||||
// TODO should return bool /rtn
|
||||
#[allow(non_snake_case, unused_must_use)]
|
||||
#[allow(non_snake_case)]
|
||||
unsafe fn export_backup(context: &Context, dir: *const libc::c_char) -> libc::c_int {
|
||||
let mut current_block: u64;
|
||||
let mut ok_to_continue: bool;
|
||||
let mut success: libc::c_int = 0;
|
||||
|
||||
let mut delete_dest_file: libc::c_int = 0;
|
||||
@@ -758,7 +752,7 @@ unsafe fn export_backup(context: &Context, dir: *const libc::c_char) -> libc::c_
|
||||
/* 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*/
|
||||
let sql = Sql::new();
|
||||
if sql.open(context, as_path(dest_pathNfilename), 0).is_ok() {
|
||||
if sql.open(context, as_path(dest_pathNfilename), 0) {
|
||||
if !sql.table_exists("backup_blobs") {
|
||||
if sql::execute(
|
||||
context,
|
||||
@@ -769,51 +763,30 @@ unsafe fn export_backup(context: &Context, dir: *const libc::c_char) -> libc::c_
|
||||
.is_err()
|
||||
{
|
||||
/* error already logged */
|
||||
current_block = 11487273724841241105;
|
||||
ok_to_continue = false;
|
||||
} else {
|
||||
current_block = 14648156034262866959;
|
||||
ok_to_continue = true;
|
||||
}
|
||||
} else {
|
||||
current_block = 14648156034262866959;
|
||||
ok_to_continue = true;
|
||||
}
|
||||
match current_block {
|
||||
11487273724841241105 => {}
|
||||
_ => {
|
||||
let mut total_files_cnt = 0;
|
||||
let dir = std::path::Path::new(as_str(context.get_blobdir()));
|
||||
let dir_handle = std::fs::read_dir(dir);
|
||||
if dir_handle.is_err() {
|
||||
error!(
|
||||
context,
|
||||
0,
|
||||
"Backup: Cannot get info for blob-directory \"{}\".",
|
||||
as_str(context.get_blobdir()),
|
||||
);
|
||||
} else {
|
||||
let dir_handle = dir_handle.unwrap();
|
||||
total_files_cnt += dir_handle.filter(|r| r.is_ok()).count();
|
||||
if ok_to_continue {
|
||||
let mut total_files_cnt = 0;
|
||||
let dir = std::path::Path::new(as_str(context.get_blobdir()));
|
||||
if let Ok(dir_handle) = std::fs::read_dir(dir) {
|
||||
total_files_cnt += dir_handle.filter(|r| r.is_ok()).count();
|
||||
|
||||
info!(context, 0, "EXPORT: total_files_cnt={}", total_files_cnt);
|
||||
if total_files_cnt > 0 {
|
||||
// scan directory, pass 2: copy files
|
||||
let dir_handle = std::fs::read_dir(dir);
|
||||
if dir_handle.is_err() {
|
||||
error!(
|
||||
context,
|
||||
0,
|
||||
"Backup: Cannot copy from blob-directory \"{}\".",
|
||||
as_str(context.get_blobdir()),
|
||||
);
|
||||
} else {
|
||||
let dir_handle = dir_handle.unwrap();
|
||||
|
||||
sql.prepare(
|
||||
info!(context, 0, "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() {
|
||||
current_block = 2631791190359682872;
|
||||
ok_to_continue = true;
|
||||
break;
|
||||
}
|
||||
let entry = entry.unwrap();
|
||||
@@ -825,7 +798,7 @@ unsafe fn export_backup(context: &Context, dir: *const libc::c_char) -> libc::c_
|
||||
.shall_stop_ongoing
|
||||
{
|
||||
delete_dest_file = 1;
|
||||
current_block = 11487273724841241105;
|
||||
ok_to_continue = false;
|
||||
break;
|
||||
} else {
|
||||
processed_files_cnt += 1;
|
||||
@@ -870,7 +843,7 @@ unsafe fn export_backup(context: &Context, dir: *const libc::c_char) -> libc::c_
|
||||
&curr_pathNfilename,
|
||||
);
|
||||
/* this is not recoverable! writing to the sqlite database should work! */
|
||||
current_block = 11487273724841241105;
|
||||
ok_to_continue = false;
|
||||
break;
|
||||
}
|
||||
} else {
|
||||
@@ -882,29 +855,39 @@ unsafe fn export_backup(context: &Context, dir: *const libc::c_char) -> libc::c_
|
||||
Ok(())
|
||||
}
|
||||
).unwrap();
|
||||
}
|
||||
} else {
|
||||
info!(context, 0, "Backup: No files to copy.",);
|
||||
current_block = 2631791190359682872;
|
||||
error!(
|
||||
context,
|
||||
0,
|
||||
"Backup: Cannot copy from blob-directory \"{}\".",
|
||||
as_str(context.get_blobdir()),
|
||||
);
|
||||
}
|
||||
match current_block {
|
||||
11487273724841241105 => {}
|
||||
_ => {
|
||||
if sql
|
||||
.set_config_int(context, "backup_time", now as i32)
|
||||
.is_ok()
|
||||
{
|
||||
context.call_cb(
|
||||
Event::IMEX_FILE_WRITTEN,
|
||||
dest_pathNfilename as uintptr_t,
|
||||
0,
|
||||
);
|
||||
success = 1;
|
||||
}
|
||||
}
|
||||
} else {
|
||||
info!(context, 0, "Backup: No files to copy.",);
|
||||
ok_to_continue = true;
|
||||
}
|
||||
if ok_to_continue {
|
||||
if sql
|
||||
.set_config_int(context, "backup_time", now as i32)
|
||||
.is_ok()
|
||||
{
|
||||
context.call_cb(
|
||||
Event::IMEX_FILE_WRITTEN,
|
||||
dest_pathNfilename as uintptr_t,
|
||||
0,
|
||||
);
|
||||
success = 1;
|
||||
}
|
||||
}
|
||||
}
|
||||
} else {
|
||||
error!(
|
||||
context,
|
||||
0,
|
||||
"Backup: Cannot get info for blob-directory \"{}\".",
|
||||
as_str(context.get_blobdir())
|
||||
);
|
||||
};
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -944,16 +927,7 @@ unsafe fn import_self_keys(context: &Context, dir_name: *const libc::c_char) ->
|
||||
let mut buf2_headerline: *const libc::c_char = ptr::null_mut();
|
||||
if !dir_name.is_null() {
|
||||
let dir = std::path::Path::new(as_str(dir_name));
|
||||
let dir_handle = std::fs::read_dir(dir);
|
||||
if dir_handle.is_err() {
|
||||
error!(
|
||||
context,
|
||||
0,
|
||||
"Import: Cannot open directory \"{}\".",
|
||||
as_str(dir_name),
|
||||
);
|
||||
} else {
|
||||
let dir_handle = dir_handle.unwrap();
|
||||
if let Ok(dir_handle) = std::fs::read_dir(dir) {
|
||||
for entry in dir_handle {
|
||||
if entry.is_err() {
|
||||
break;
|
||||
@@ -992,9 +966,9 @@ unsafe fn import_self_keys(context: &Context, dir_name: *const libc::c_char) ->
|
||||
if dc_split_armored_data(
|
||||
buf2,
|
||||
&mut buf2_headerline,
|
||||
0 as *mut *const libc::c_char,
|
||||
0 as *mut *const libc::c_char,
|
||||
0 as *mut *const libc::c_char,
|
||||
ptr::null_mut(),
|
||||
ptr::null_mut(),
|
||||
ptr::null_mut(),
|
||||
) && strcmp(
|
||||
buf2_headerline,
|
||||
b"-----BEGIN PGP PUBLIC KEY BLOCK-----\x00" as *const u8 as *const libc::c_char,
|
||||
@@ -1037,6 +1011,13 @@ unsafe fn import_self_keys(context: &Context, dir_name: *const libc::c_char) ->
|
||||
as_str(dir_name),
|
||||
);
|
||||
}
|
||||
} else {
|
||||
error!(
|
||||
context,
|
||||
0,
|
||||
"Import: Cannot open directory \"{}\".",
|
||||
as_str(dir_name),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1160,7 +1141,7 @@ mod tests {
|
||||
|
||||
#[test]
|
||||
fn test_render_setup_file() {
|
||||
let t = test_context(Some(Box::new(logging_cb)));
|
||||
let t = test_context(Some(logging_cb));
|
||||
|
||||
configure_alice_keypair(&t.ctx);
|
||||
let msg = dc_render_setup_file(&t.ctx, "hello").unwrap();
|
||||
@@ -1178,9 +1159,14 @@ mod tests {
|
||||
assert!(msg.contains("-----END PGP MESSAGE-----\n"));
|
||||
}
|
||||
|
||||
fn ac_setup_msg_cb(ctx: &Context, evt: Event, d1: uintptr_t, d2: uintptr_t) -> uintptr_t {
|
||||
unsafe extern "C" fn ac_setup_msg_cb(
|
||||
ctx: &Context,
|
||||
evt: Event,
|
||||
d1: uintptr_t,
|
||||
d2: uintptr_t,
|
||||
) -> uintptr_t {
|
||||
if evt == Event::GET_STRING && d1 == StockMessage::AcSetupMsgBody.to_usize().unwrap() {
|
||||
unsafe { "hello\r\nthere".strdup() as usize }
|
||||
"hello\r\nthere".strdup() as usize
|
||||
} else {
|
||||
logging_cb(ctx, evt, d1, d2)
|
||||
}
|
||||
@@ -1188,7 +1174,7 @@ mod tests {
|
||||
|
||||
#[test]
|
||||
fn test_render_setup_file_newline_replace() {
|
||||
let t = test_context(Some(Box::new(ac_setup_msg_cb)));
|
||||
let t = test_context(Some(ac_setup_msg_cb));
|
||||
configure_alice_keypair(&t.ctx);
|
||||
let msg = dc_render_setup_file(&t.ctx, "pw").unwrap();
|
||||
println!("{}", &msg);
|
||||
|
||||
@@ -1,776 +0,0 @@
|
||||
use quick_xml;
|
||||
use quick_xml::events::{BytesEnd, BytesStart, BytesText};
|
||||
|
||||
use crate::chat;
|
||||
use crate::constants::Event;
|
||||
use crate::constants::*;
|
||||
use crate::context::*;
|
||||
use crate::dc_tools::*;
|
||||
use crate::job::*;
|
||||
use crate::message::*;
|
||||
use crate::param::*;
|
||||
use crate::sql;
|
||||
use crate::stock::StockMessage;
|
||||
use crate::types::*;
|
||||
use crate::x::*;
|
||||
|
||||
// location handling
|
||||
#[derive(Clone, Default)]
|
||||
#[allow(non_camel_case_types)]
|
||||
pub struct dc_location {
|
||||
pub location_id: uint32_t,
|
||||
pub latitude: libc::c_double,
|
||||
pub longitude: libc::c_double,
|
||||
pub accuracy: libc::c_double,
|
||||
pub timestamp: i64,
|
||||
pub contact_id: uint32_t,
|
||||
pub msg_id: uint32_t,
|
||||
pub chat_id: uint32_t,
|
||||
pub marker: Option<String>,
|
||||
pub independent: uint32_t,
|
||||
}
|
||||
|
||||
impl dc_location {
|
||||
pub fn new() -> Self {
|
||||
dc_location {
|
||||
location_id: 0,
|
||||
latitude: 0.0,
|
||||
longitude: 0.0,
|
||||
accuracy: 0.0,
|
||||
timestamp: 0,
|
||||
contact_id: 0,
|
||||
msg_id: 0,
|
||||
chat_id: 0,
|
||||
marker: None,
|
||||
independent: 0,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone)]
|
||||
#[allow(non_camel_case_types)]
|
||||
pub struct dc_kml_t {
|
||||
pub addr: *mut libc::c_char,
|
||||
pub locations: Option<Vec<dc_location>>,
|
||||
pub tag: libc::c_int,
|
||||
pub curr: dc_location,
|
||||
}
|
||||
|
||||
impl dc_kml_t {
|
||||
pub fn new() -> Self {
|
||||
dc_kml_t {
|
||||
addr: std::ptr::null_mut(),
|
||||
locations: None,
|
||||
tag: 0,
|
||||
curr: dc_location::new(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// location streaming
|
||||
pub unsafe fn dc_send_locations_to_chat(context: &Context, chat_id: uint32_t, seconds: i64) {
|
||||
let now = time();
|
||||
let mut msg: Message;
|
||||
let is_sending_locations_before: bool;
|
||||
if !(seconds < 0 || chat_id <= 9i32 as libc::c_uint) {
|
||||
is_sending_locations_before = dc_is_sending_locations_to_chat(context, chat_id);
|
||||
if sql::execute(
|
||||
context,
|
||||
&context.sql,
|
||||
"UPDATE chats \
|
||||
SET locations_send_begin=?, \
|
||||
locations_send_until=? \
|
||||
WHERE id=?",
|
||||
params![
|
||||
if 0 != seconds { now } else { 0 },
|
||||
if 0 != seconds { now + seconds } else { 0 },
|
||||
chat_id as i32,
|
||||
],
|
||||
)
|
||||
.is_ok()
|
||||
{
|
||||
if 0 != seconds && !is_sending_locations_before {
|
||||
msg = dc_msg_new(context, Viewtype::Text);
|
||||
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();
|
||||
} else if 0 == seconds && is_sending_locations_before {
|
||||
let stock_str =
|
||||
context.stock_system_msg(StockMessage::MsgLocationDisabled, "", "", 0);
|
||||
chat::add_device_msg(context, chat_id, stock_str);
|
||||
}
|
||||
context.call_cb(
|
||||
Event::CHAT_MODIFIED,
|
||||
chat_id as uintptr_t,
|
||||
0i32 as uintptr_t,
|
||||
);
|
||||
if 0 != seconds {
|
||||
schedule_MAYBE_SEND_LOCATIONS(context, 0i32);
|
||||
job_add(
|
||||
context,
|
||||
Action::MaybeSendLocationsEnded,
|
||||
chat_id as libc::c_int,
|
||||
Params::new(),
|
||||
seconds + 1,
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/*******************************************************************************
|
||||
* job to send locations out to all chats that want them
|
||||
******************************************************************************/
|
||||
#[allow(non_snake_case)]
|
||||
unsafe fn schedule_MAYBE_SEND_LOCATIONS(context: &Context, flags: libc::c_int) {
|
||||
if 0 != flags & 0x1 || !job_action_exists(context, Action::MaybeSendLocations) {
|
||||
job_add(context, Action::MaybeSendLocations, 0, Params::new(), 60);
|
||||
};
|
||||
}
|
||||
|
||||
pub fn dc_is_sending_locations_to_chat(context: &Context, chat_id: u32) -> bool {
|
||||
context
|
||||
.sql
|
||||
.exists(
|
||||
"SELECT id FROM chats WHERE (? OR id=?) AND locations_send_until>?;",
|
||||
params![if chat_id == 0 { 1 } else { 0 }, chat_id as i32, time()],
|
||||
)
|
||||
.unwrap_or_default()
|
||||
}
|
||||
|
||||
pub fn dc_set_location(
|
||||
context: &Context,
|
||||
latitude: libc::c_double,
|
||||
longitude: libc::c_double,
|
||||
accuracy: libc::c_double,
|
||||
) -> libc::c_int {
|
||||
if latitude == 0.0 && longitude == 0.0 {
|
||||
return 1;
|
||||
}
|
||||
|
||||
context.sql.query_map(
|
||||
"SELECT id FROM chats WHERE locations_send_until>?;",
|
||||
params![time()], |row| row.get::<_, i32>(0),
|
||||
|chats| {
|
||||
let mut continue_streaming = false;
|
||||
|
||||
for chat in chats {
|
||||
let chat_id = chat?;
|
||||
context.sql.execute(
|
||||
"INSERT INTO locations \
|
||||
(latitude, longitude, accuracy, timestamp, chat_id, from_id) VALUES (?,?,?,?,?,?);",
|
||||
params![
|
||||
latitude,
|
||||
longitude,
|
||||
accuracy,
|
||||
time(),
|
||||
chat_id,
|
||||
1,
|
||||
]
|
||||
)?;
|
||||
continue_streaming = true;
|
||||
}
|
||||
if continue_streaming {
|
||||
context.call_cb(Event::LOCATION_CHANGED, 1, 0);
|
||||
};
|
||||
unsafe { schedule_MAYBE_SEND_LOCATIONS(context, 0) };
|
||||
Ok(continue_streaming as libc::c_int)
|
||||
}
|
||||
).unwrap_or_default()
|
||||
}
|
||||
|
||||
pub fn dc_get_locations(
|
||||
context: &Context,
|
||||
chat_id: uint32_t,
|
||||
contact_id: uint32_t,
|
||||
timestamp_from: i64,
|
||||
mut timestamp_to: i64,
|
||||
) -> Vec<dc_location> {
|
||||
if timestamp_to == 0 {
|
||||
timestamp_to = time() + 10;
|
||||
}
|
||||
|
||||
context
|
||||
.sql
|
||||
.query_map(
|
||||
"SELECT l.id, l.latitude, l.longitude, l.accuracy, l.timestamp, l.independent, \
|
||||
m.id, l.from_id, l.chat_id, m.txt \
|
||||
FROM locations l LEFT JOIN msgs m ON l.id=m.location_id WHERE (? OR l.chat_id=?) \
|
||||
AND (? OR l.from_id=?) \
|
||||
AND (l.independent=1 OR (l.timestamp>=? AND l.timestamp<=?)) \
|
||||
ORDER BY l.timestamp DESC, l.id DESC, m.id DESC;",
|
||||
params![
|
||||
if chat_id == 0 { 1 } else { 0 },
|
||||
chat_id as i32,
|
||||
if contact_id == 0 { 1 } else { 0 },
|
||||
contact_id as i32,
|
||||
timestamp_from,
|
||||
timestamp_to,
|
||||
],
|
||||
|row| {
|
||||
let msg_id = row.get(6)?;
|
||||
let txt: String = row.get(9)?;
|
||||
let marker = if msg_id != 0 && is_marker(&txt) {
|
||||
Some(txt)
|
||||
} else {
|
||||
None
|
||||
};
|
||||
|
||||
let loc = dc_location {
|
||||
location_id: row.get(0)?,
|
||||
latitude: row.get(1)?,
|
||||
longitude: row.get(2)?,
|
||||
accuracy: row.get(3)?,
|
||||
timestamp: row.get(4)?,
|
||||
independent: row.get(5)?,
|
||||
msg_id,
|
||||
contact_id: row.get(7)?,
|
||||
chat_id: row.get(8)?,
|
||||
marker,
|
||||
};
|
||||
Ok(loc)
|
||||
},
|
||||
|locations| {
|
||||
let mut ret = Vec::new();
|
||||
|
||||
for location in locations {
|
||||
ret.push(location?);
|
||||
}
|
||||
Ok(ret)
|
||||
},
|
||||
)
|
||||
.unwrap_or_default()
|
||||
}
|
||||
|
||||
fn is_marker(txt: &str) -> bool {
|
||||
txt.len() == 1 && txt.chars().next().unwrap() != ' '
|
||||
}
|
||||
|
||||
pub fn dc_delete_all_locations(context: &Context) -> bool {
|
||||
if sql::execute(context, &context.sql, "DELETE FROM locations;", params![]).is_err() {
|
||||
return false;
|
||||
}
|
||||
context.call_cb(Event::LOCATION_CHANGED, 0, 0);
|
||||
true
|
||||
}
|
||||
|
||||
pub fn dc_get_location_kml(
|
||||
context: &Context,
|
||||
chat_id: uint32_t,
|
||||
last_added_location_id: *mut uint32_t,
|
||||
) -> *mut libc::c_char {
|
||||
let mut success: libc::c_int = 0;
|
||||
let now = time();
|
||||
let mut location_count: libc::c_int = 0;
|
||||
let mut ret = String::new();
|
||||
|
||||
let self_addr = context
|
||||
.sql
|
||||
.get_config(context, "configured_addr")
|
||||
.unwrap_or_default();
|
||||
|
||||
if let Ok((locations_send_begin, locations_send_until, locations_last_sent)) = context.sql.query_row(
|
||||
"SELECT locations_send_begin, locations_send_until, locations_last_sent FROM chats WHERE id=?;",
|
||||
params![chat_id as i32], |row| {
|
||||
let send_begin: i64 = row.get(0)?;
|
||||
let send_until: i64 = row.get(1)?;
|
||||
let last_sent: i64 = row.get(2)?;
|
||||
|
||||
Ok((send_begin, send_until, last_sent))
|
||||
}
|
||||
) {
|
||||
if !(locations_send_begin == 0 || now > locations_send_until) {
|
||||
ret += &format!(
|
||||
"<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<kml xmlns=\"http://www.opengis.net/kml/2.2\">\n<Document addr=\"{}\">\n",
|
||||
self_addr,
|
||||
);
|
||||
|
||||
context.sql.query_map(
|
||||
"SELECT id, latitude, longitude, accuracy, timestamp\
|
||||
FROM locations WHERE from_id=? \
|
||||
AND timestamp>=? \
|
||||
AND (timestamp>=? OR timestamp=(SELECT MAX(timestamp) FROM locations WHERE from_id=?)) \
|
||||
AND independent=0 \
|
||||
GROUP BY timestamp \
|
||||
ORDER BY timestamp;",
|
||||
params![1, locations_send_begin, locations_last_sent, 1],
|
||||
|row| {
|
||||
let location_id: i32 = row.get(0)?;
|
||||
let latitude: f64 = row.get(1)?;
|
||||
let longitude: f64 = row.get(2)?;
|
||||
let accuracy: f64 = row.get(3)?;
|
||||
let timestamp = unsafe { get_kml_timestamp(row.get(4)?) };
|
||||
|
||||
Ok((location_id, latitude, longitude, accuracy, timestamp))
|
||||
},
|
||||
|rows| {
|
||||
for row in rows {
|
||||
let (location_id, latitude, longitude, accuracy, timestamp) = row?;
|
||||
ret += &format!(
|
||||
"<Placemark><Timestamp><when>{}</when></Timestamp><Point><coordinates accuracy=\"{}\">{},{}</coordinates></Point></Placemark>\n\x00",
|
||||
as_str(timestamp),
|
||||
accuracy,
|
||||
longitude,
|
||||
latitude
|
||||
);
|
||||
location_count += 1;
|
||||
if !last_added_location_id.is_null() {
|
||||
unsafe { *last_added_location_id = location_id as u32 };
|
||||
}
|
||||
unsafe { free(timestamp as *mut libc::c_void) };
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
).unwrap(); // TODO: better error handling
|
||||
}
|
||||
}
|
||||
|
||||
if location_count > 0 {
|
||||
ret += "</Document>\n</kml>";
|
||||
success = 1;
|
||||
}
|
||||
|
||||
if 0 != success {
|
||||
unsafe { ret.strdup() }
|
||||
} else {
|
||||
std::ptr::null_mut()
|
||||
}
|
||||
}
|
||||
|
||||
/*******************************************************************************
|
||||
* create kml-files
|
||||
******************************************************************************/
|
||||
unsafe fn get_kml_timestamp(utc: i64) -> *mut libc::c_char {
|
||||
// Returns a string formatted as YYYY-MM-DDTHH:MM:SSZ. The trailing `Z` indicates UTC.
|
||||
let res = chrono::NaiveDateTime::from_timestamp(utc, 0)
|
||||
.format("%Y-%m-%dT%H:%M:%SZ")
|
||||
.to_string();
|
||||
res.strdup()
|
||||
}
|
||||
|
||||
pub unsafe fn dc_get_message_kml(
|
||||
timestamp: i64,
|
||||
latitude: libc::c_double,
|
||||
longitude: libc::c_double,
|
||||
) -> *mut libc::c_char {
|
||||
let timestamp_str = get_kml_timestamp(timestamp);
|
||||
let latitude_str = dc_ftoa(latitude);
|
||||
let longitude_str = dc_ftoa(longitude);
|
||||
|
||||
let ret = dc_mprintf(
|
||||
b"<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n\
|
||||
<kml xmlns=\"http://www.opengis.net/kml/2.2\">\n\
|
||||
<Document>\n\
|
||||
<Placemark>\
|
||||
<Timestamp><when>%s</when></Timestamp>\
|
||||
<Point><coordinates>%s,%s</coordinates></Point>\
|
||||
</Placemark>\n\
|
||||
</Document>\n\
|
||||
</kml>\x00" as *const u8 as *const libc::c_char,
|
||||
timestamp_str,
|
||||
longitude_str, // reverse order!
|
||||
latitude_str,
|
||||
);
|
||||
|
||||
free(latitude_str as *mut libc::c_void);
|
||||
free(longitude_str as *mut libc::c_void);
|
||||
free(timestamp_str as *mut libc::c_void);
|
||||
|
||||
ret
|
||||
}
|
||||
|
||||
pub fn dc_set_kml_sent_timestamp(context: &Context, chat_id: u32, timestamp: i64) -> bool {
|
||||
sql::execute(
|
||||
context,
|
||||
&context.sql,
|
||||
"UPDATE chats SET locations_last_sent=? WHERE id=?;",
|
||||
params![timestamp, chat_id as i32],
|
||||
)
|
||||
.is_ok()
|
||||
}
|
||||
|
||||
pub fn dc_set_msg_location_id(context: &Context, msg_id: u32, location_id: u32) -> bool {
|
||||
sql::execute(
|
||||
context,
|
||||
&context.sql,
|
||||
"UPDATE msgs SET location_id=? WHERE id=?;",
|
||||
params![location_id, msg_id as i32],
|
||||
)
|
||||
.is_ok()
|
||||
}
|
||||
|
||||
pub unsafe fn dc_save_locations(
|
||||
context: &Context,
|
||||
chat_id: u32,
|
||||
contact_id: u32,
|
||||
locations_opt: &Option<Vec<dc_location>>,
|
||||
independent: libc::c_int,
|
||||
) -> u32 {
|
||||
if chat_id <= 9 || locations_opt.is_none() {
|
||||
return 0;
|
||||
}
|
||||
|
||||
let locations = locations_opt.as_ref().unwrap();
|
||||
context
|
||||
.sql
|
||||
.prepare2(
|
||||
"SELECT id FROM locations WHERE timestamp=? AND from_id=?",
|
||||
"INSERT INTO locations\
|
||||
(timestamp, from_id, chat_id, latitude, longitude, accuracy, independent) \
|
||||
VALUES (?,?,?,?,?,?,?);",
|
||||
|mut stmt_test, mut stmt_insert, conn| {
|
||||
let mut newest_timestamp = 0;
|
||||
let mut newest_location_id = 0;
|
||||
|
||||
for location in locations {
|
||||
let exists =
|
||||
stmt_test.exists(params![location.timestamp, contact_id as i32])?;
|
||||
|
||||
if 0 != independent || !exists {
|
||||
stmt_insert.execute(params![
|
||||
location.timestamp,
|
||||
contact_id as i32,
|
||||
chat_id as i32,
|
||||
location.latitude,
|
||||
location.longitude,
|
||||
location.accuracy,
|
||||
independent,
|
||||
])?;
|
||||
|
||||
if location.timestamp > newest_timestamp {
|
||||
newest_timestamp = location.timestamp;
|
||||
newest_location_id = sql::get_rowid2_with_conn(
|
||||
context,
|
||||
conn,
|
||||
"locations",
|
||||
"timestamp",
|
||||
location.timestamp,
|
||||
"from_id",
|
||||
contact_id as i32,
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
Ok(newest_location_id)
|
||||
},
|
||||
)
|
||||
.unwrap_or_default()
|
||||
}
|
||||
|
||||
pub unsafe fn dc_kml_parse(
|
||||
context: &Context,
|
||||
content: *const libc::c_char,
|
||||
content_bytes: size_t,
|
||||
) -> dc_kml_t {
|
||||
let mut kml = dc_kml_t::new();
|
||||
|
||||
if content_bytes > (1 * 1024 * 1024) {
|
||||
warn!(
|
||||
context,
|
||||
0, "A kml-files with {} bytes is larger than reasonably expected.", content_bytes,
|
||||
);
|
||||
return kml;
|
||||
}
|
||||
|
||||
let content_null = dc_null_terminate(content, content_bytes as libc::c_int);
|
||||
if !content_null.is_null() {
|
||||
let mut reader = quick_xml::Reader::from_str(as_str(content_null));
|
||||
reader.trim_text(true);
|
||||
|
||||
kml.locations = Some(Vec::with_capacity(100));
|
||||
|
||||
let mut buf = Vec::new();
|
||||
|
||||
loop {
|
||||
match reader.read_event(&mut buf) {
|
||||
Ok(quick_xml::events::Event::Start(ref e)) => kml_starttag_cb(e, &mut kml, &reader),
|
||||
Ok(quick_xml::events::Event::End(ref e)) => kml_endtag_cb(e, &mut kml),
|
||||
Ok(quick_xml::events::Event::Text(ref e)) => kml_text_cb(e, &mut kml, &reader),
|
||||
Err(e) => {
|
||||
error!(
|
||||
context,
|
||||
0,
|
||||
"Location parsing: Error at position {}: {:?}",
|
||||
reader.buffer_position(),
|
||||
e
|
||||
);
|
||||
}
|
||||
Ok(quick_xml::events::Event::Eof) => break,
|
||||
_ => (),
|
||||
}
|
||||
buf.clear();
|
||||
}
|
||||
}
|
||||
|
||||
free(content_null.cast());
|
||||
|
||||
kml
|
||||
}
|
||||
|
||||
fn kml_text_cb<B: std::io::BufRead>(
|
||||
event: &BytesText,
|
||||
kml: &mut dc_kml_t,
|
||||
reader: &quick_xml::Reader<B>,
|
||||
) {
|
||||
if 0 != kml.tag & (0x4 | 0x10) {
|
||||
let val = event.unescape_and_decode(reader).unwrap_or_default();
|
||||
|
||||
let val = val
|
||||
.replace("\n", "")
|
||||
.replace("\r", "")
|
||||
.replace("\t", "")
|
||||
.replace(" ", "");
|
||||
|
||||
if 0 != kml.tag & 0x4 && val.len() >= 19 {
|
||||
// YYYY-MM-DDTHH:MM:SSZ
|
||||
// 0 4 7 10 13 16 19
|
||||
match chrono::NaiveDateTime::parse_from_str(&val, "%Y-%m-%dT%H:%M:%SZ") {
|
||||
Ok(res) => {
|
||||
kml.curr.timestamp = res.timestamp();
|
||||
if kml.curr.timestamp > time() {
|
||||
kml.curr.timestamp = time();
|
||||
}
|
||||
}
|
||||
Err(_err) => {
|
||||
kml.curr.timestamp = time();
|
||||
}
|
||||
}
|
||||
} else if 0 != kml.tag & 0x10 {
|
||||
let parts = val.splitn(2, ',').collect::<Vec<_>>();
|
||||
if parts.len() == 2 {
|
||||
kml.curr.longitude = parts[0].parse().unwrap_or_default();
|
||||
kml.curr.latitude = parts[1].parse().unwrap_or_default();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn kml_endtag_cb(event: &BytesEnd, kml: &mut dc_kml_t) {
|
||||
let tag = String::from_utf8_lossy(event.name()).trim().to_lowercase();
|
||||
|
||||
if tag == "placemark" {
|
||||
if 0 != kml.tag & 0x1
|
||||
&& 0 != kml.curr.timestamp
|
||||
&& 0. != kml.curr.latitude
|
||||
&& 0. != kml.curr.longitude
|
||||
{
|
||||
if let Some(ref mut locations) = kml.locations {
|
||||
locations.push(std::mem::replace(&mut kml.curr, dc_location::new()));
|
||||
}
|
||||
}
|
||||
kml.tag = 0
|
||||
};
|
||||
}
|
||||
|
||||
fn kml_starttag_cb<B: std::io::BufRead>(
|
||||
event: &BytesStart,
|
||||
kml: &mut dc_kml_t,
|
||||
reader: &quick_xml::Reader<B>,
|
||||
) {
|
||||
let tag = String::from_utf8_lossy(event.name()).trim().to_lowercase();
|
||||
if tag == "document" {
|
||||
if let Some(addr) = event.attributes().find(|attr| {
|
||||
attr.as_ref()
|
||||
.map(|a| String::from_utf8_lossy(a.key).trim().to_lowercase() == "addr")
|
||||
.unwrap_or_default()
|
||||
}) {
|
||||
kml.addr = unsafe {
|
||||
addr.unwrap()
|
||||
.unescape_and_decode_value(reader)
|
||||
.unwrap_or_default()
|
||||
.strdup()
|
||||
};
|
||||
}
|
||||
} else if tag == "placemark" {
|
||||
kml.tag = 0x1;
|
||||
kml.curr.timestamp = 0;
|
||||
kml.curr.latitude = 0 as libc::c_double;
|
||||
kml.curr.longitude = 0.0f64;
|
||||
kml.curr.accuracy = 0.0f64
|
||||
} else if tag == "timestamp" && 0 != kml.tag & 0x1 {
|
||||
kml.tag = 0x1 | 0x2
|
||||
} else if tag == "when" && 0 != kml.tag & 0x2 {
|
||||
kml.tag = 0x1 | 0x2 | 0x4
|
||||
} else if tag == "point" && 0 != kml.tag & 0x1 {
|
||||
kml.tag = 0x1 | 0x8
|
||||
} else if tag == "coordinates" && 0 != kml.tag & 0x8 {
|
||||
kml.tag = 0x1 | 0x8 | 0x10;
|
||||
if let Some(acc) = event.attributes().find(|attr| {
|
||||
attr.as_ref()
|
||||
.map(|a| String::from_utf8_lossy(a.key).trim().to_lowercase() == "accuracy")
|
||||
.unwrap_or_default()
|
||||
}) {
|
||||
let v = acc
|
||||
.unwrap()
|
||||
.unescape_and_decode_value(reader)
|
||||
.unwrap_or_default();
|
||||
|
||||
kml.curr.accuracy = v.trim().parse().unwrap_or_default();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub unsafe fn dc_kml_unref(kml: &mut dc_kml_t) {
|
||||
free(kml.addr as *mut libc::c_void);
|
||||
}
|
||||
|
||||
#[allow(non_snake_case)]
|
||||
pub unsafe fn dc_job_do_DC_JOB_MAYBE_SEND_LOCATIONS(context: &Context, _job: &Job) {
|
||||
let now = time();
|
||||
let mut continue_streaming: libc::c_int = 1;
|
||||
info!(
|
||||
context,
|
||||
0, " ----------------- MAYBE_SEND_LOCATIONS -------------- ",
|
||||
);
|
||||
|
||||
context
|
||||
.sql
|
||||
.query_map(
|
||||
"SELECT id, locations_send_begin, locations_last_sent \
|
||||
FROM chats \
|
||||
WHERE locations_send_until>?;",
|
||||
params![now],
|
||||
|row| {
|
||||
let chat_id: i32 = row.get(0)?;
|
||||
let locations_send_begin: i64 = row.get(1)?;
|
||||
let locations_last_sent: i64 = row.get(2)?;
|
||||
continue_streaming = 1;
|
||||
|
||||
// be a bit tolerant as the timer may not align exactly with time(NULL)
|
||||
if now - locations_last_sent < (60 - 3) {
|
||||
Ok(None)
|
||||
} else {
|
||||
Ok(Some((chat_id, locations_send_begin, locations_last_sent)))
|
||||
}
|
||||
},
|
||||
|rows| {
|
||||
context.sql.prepare(
|
||||
"SELECT id \
|
||||
FROM locations \
|
||||
WHERE from_id=? \
|
||||
AND timestamp>=? \
|
||||
AND timestamp>? \
|
||||
AND independent=0 \
|
||||
ORDER BY timestamp;",
|
||||
|mut stmt_locations, _| {
|
||||
for (chat_id, locations_send_begin, locations_last_sent) in
|
||||
rows.filter_map(|r| match r {
|
||||
Ok(Some(v)) => Some(v),
|
||||
_ => None,
|
||||
})
|
||||
{
|
||||
// TODO: do I need to reset?
|
||||
if !stmt_locations
|
||||
.exists(params![1, locations_send_begin, locations_last_sent,])
|
||||
.unwrap_or_default()
|
||||
{
|
||||
// if there is no new location, there's nothing to send.
|
||||
// however, maybe we want to bypass this test eg. 15 minutes
|
||||
continue;
|
||||
}
|
||||
// pending locations are attached automatically to every message,
|
||||
// so also to this empty text message.
|
||||
// DC_CMD_LOCATION is only needed to create a nicer subject.
|
||||
//
|
||||
// for optimisation and to avoid flooding the sending queue,
|
||||
// we could sending these messages only if we're really online.
|
||||
// the easiest way to determine this, is to check for an empty message queue.
|
||||
// (might not be 100%, however, as positions are sent combined later
|
||||
// and dc_set_location() is typically called periodically, this is ok)
|
||||
let mut msg = dc_msg_new(context, Viewtype::Text);
|
||||
msg.hidden = true;
|
||||
msg.param.set_int(Param::Cmd, 9);
|
||||
// TODO: handle cleanup on error
|
||||
chat::send_msg(context, chat_id as u32, &mut msg).unwrap();
|
||||
}
|
||||
Ok(())
|
||||
},
|
||||
)
|
||||
},
|
||||
)
|
||||
.unwrap(); // TODO: Better error handling
|
||||
|
||||
if 0 != continue_streaming {
|
||||
schedule_MAYBE_SEND_LOCATIONS(context, 0x1);
|
||||
}
|
||||
}
|
||||
|
||||
#[allow(non_snake_case)]
|
||||
pub unsafe fn dc_job_do_DC_JOB_MAYBE_SEND_LOC_ENDED(context: &Context, job: &mut Job) {
|
||||
// this function is called when location-streaming _might_ have ended for a chat.
|
||||
// the function checks, if location-streaming is really ended;
|
||||
// if so, a device-message is added if not yet done.
|
||||
|
||||
let chat_id = (*job).foreign_id;
|
||||
|
||||
if let Ok((send_begin, send_until)) = context.sql.query_row(
|
||||
"SELECT locations_send_begin, locations_send_until FROM chats WHERE id=?",
|
||||
params![chat_id as i32],
|
||||
|row| Ok((row.get::<_, i64>(0)?, row.get::<_, i64>(1)?)),
|
||||
) {
|
||||
if !(send_begin != 0 && time() <= send_until) {
|
||||
// still streaming -
|
||||
// may happen as several calls to dc_send_locations_to_chat()
|
||||
// do not un-schedule pending DC_MAYBE_SEND_LOC_ENDED jobs
|
||||
if !(send_begin == 0 && send_until == 0) {
|
||||
// not streaming, device-message already sent
|
||||
if context.sql.execute(
|
||||
"UPDATE chats SET locations_send_begin=0, locations_send_until=0 WHERE id=?",
|
||||
params![chat_id as i32],
|
||||
).is_ok() {
|
||||
let stock_str = context.stock_system_msg(StockMessage::MsgLocationDisabled, "", "", 0);
|
||||
chat::add_device_msg(context, chat_id, stock_str);
|
||||
context.call_cb(
|
||||
Event::CHAT_MODIFIED,
|
||||
chat_id as usize,
|
||||
0,
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
use crate::test_utils::dummy_context;
|
||||
|
||||
#[test]
|
||||
fn test_dc_kml_parse() {
|
||||
unsafe {
|
||||
let context = dummy_context();
|
||||
|
||||
let xml =
|
||||
b"<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<kml xmlns=\"http://www.opengis.net/kml/2.2\">\n<Document addr=\"user@example.org\">\n<Placemark><Timestamp><when>2019-03-06T21:09:57Z</when></Timestamp><Point><coordinates accuracy=\"32.000000\">9.423110,53.790302</coordinates></Point></Placemark>\n<PlaceMARK>\n<Timestamp><WHEN > \n\t2018-12-13T22:11:12Z\t</WHEN></Timestamp><Point><coordinates aCCuracy=\"2.500000\"> 19.423110 \t , \n 63.790302\n </coordinates></Point></PlaceMARK>\n</Document>\n</kml>\x00"
|
||||
as *const u8 as *const libc::c_char;
|
||||
|
||||
let mut kml = dc_kml_parse(&context.ctx, xml, strlen(xml));
|
||||
|
||||
assert!(!kml.addr.is_null());
|
||||
assert_eq!(as_str(kml.addr as *const libc::c_char), "user@example.org",);
|
||||
|
||||
let locations_ref = &kml.locations.as_ref().unwrap();
|
||||
assert_eq!(locations_ref.len(), 2);
|
||||
|
||||
assert!(locations_ref[0].latitude > 53.6f64);
|
||||
assert!(locations_ref[0].latitude < 53.8f64);
|
||||
assert!(locations_ref[0].longitude > 9.3f64);
|
||||
assert!(locations_ref[0].longitude < 9.5f64);
|
||||
assert!(locations_ref[0].accuracy > 31.9f64);
|
||||
assert!(locations_ref[0].accuracy < 32.1f64);
|
||||
assert_eq!(locations_ref[0].timestamp, 1551906597);
|
||||
|
||||
assert!(locations_ref[1].latitude > 63.6f64);
|
||||
assert!(locations_ref[1].latitude < 63.8f64);
|
||||
assert!(locations_ref[1].longitude > 19.3f64);
|
||||
assert!(locations_ref[1].longitude < 19.5f64);
|
||||
assert!(locations_ref[1].accuracy > 2.4f64);
|
||||
assert!(locations_ref[1].accuracy < 2.6f64);
|
||||
assert_eq!(locations_ref[1].timestamp, 1544739072);
|
||||
|
||||
dc_kml_unref(&mut kml);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,214 +0,0 @@
|
||||
use std::borrow::Cow;
|
||||
|
||||
use crate::context::Context;
|
||||
use crate::sql::Sql;
|
||||
|
||||
#[derive(Default, Debug)]
|
||||
#[allow(non_camel_case_types)]
|
||||
pub struct dc_loginparam_t {
|
||||
pub addr: String,
|
||||
pub mail_server: String,
|
||||
pub mail_user: String,
|
||||
pub mail_pw: String,
|
||||
pub mail_port: i32,
|
||||
pub send_server: String,
|
||||
pub send_user: String,
|
||||
pub send_pw: String,
|
||||
pub send_port: i32,
|
||||
pub server_flags: i32,
|
||||
}
|
||||
|
||||
impl dc_loginparam_t {
|
||||
pub fn addr_str(&self) -> &str {
|
||||
self.addr.as_str()
|
||||
}
|
||||
}
|
||||
|
||||
pub fn dc_loginparam_new() -> dc_loginparam_t {
|
||||
Default::default()
|
||||
}
|
||||
|
||||
pub fn dc_loginparam_read(
|
||||
context: &Context,
|
||||
sql: &Sql,
|
||||
prefix: impl AsRef<str>,
|
||||
) -> dc_loginparam_t {
|
||||
let prefix = prefix.as_ref();
|
||||
|
||||
let key = format!("{}addr", prefix);
|
||||
let addr = sql
|
||||
.get_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 key = format!("{}mail_port", prefix);
|
||||
let mail_port = sql.get_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 key = format!("{}mail_pw", prefix);
|
||||
let mail_pw = sql.get_config(context, key).unwrap_or_default();
|
||||
|
||||
let key = format!("{}send_server", prefix);
|
||||
let send_server = sql.get_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 key = format!("{}send_user", prefix);
|
||||
let send_user = sql.get_config(context, key).unwrap_or_default();
|
||||
|
||||
let key = format!("{}send_pw", prefix);
|
||||
let send_pw = sql.get_config(context, key).unwrap_or_default();
|
||||
|
||||
let key = format!("{}server_flags", prefix);
|
||||
let server_flags = sql.get_config_int(context, key).unwrap_or_default();
|
||||
|
||||
dc_loginparam_t {
|
||||
addr: addr.to_string(),
|
||||
mail_server,
|
||||
mail_user,
|
||||
mail_pw,
|
||||
mail_port,
|
||||
send_server,
|
||||
send_user,
|
||||
send_pw,
|
||||
send_port,
|
||||
server_flags,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn dc_loginparam_write(
|
||||
context: &Context,
|
||||
loginparam: &dc_loginparam_t,
|
||||
sql: &Sql,
|
||||
prefix: impl AsRef<str>,
|
||||
) {
|
||||
let prefix = prefix.as_ref();
|
||||
|
||||
let key = format!("{}addr", prefix);
|
||||
sql.set_config(context, key, Some(&loginparam.addr)).ok();
|
||||
|
||||
let key = format!("{}mail_server", prefix);
|
||||
sql.set_config(context, key, Some(&loginparam.mail_server))
|
||||
.ok();
|
||||
|
||||
let key = format!("{}mail_port", prefix);
|
||||
sql.set_config_int(context, key, loginparam.mail_port).ok();
|
||||
|
||||
let key = format!("{}mail_user", prefix);
|
||||
sql.set_config(context, key, Some(&loginparam.mail_user))
|
||||
.ok();
|
||||
|
||||
let key = format!("{}mail_pw", prefix);
|
||||
sql.set_config(context, key, Some(&loginparam.mail_pw)).ok();
|
||||
|
||||
let key = format!("{}send_server", prefix);
|
||||
sql.set_config(context, key, Some(&loginparam.send_server))
|
||||
.ok();
|
||||
|
||||
let key = format!("{}send_port", prefix);
|
||||
sql.set_config_int(context, key, loginparam.send_port).ok();
|
||||
|
||||
let key = format!("{}send_user", prefix);
|
||||
sql.set_config(context, key, Some(&loginparam.send_user))
|
||||
.ok();
|
||||
|
||||
let key = format!("{}send_pw", prefix);
|
||||
sql.set_config(context, key, Some(&loginparam.send_pw)).ok();
|
||||
|
||||
let key = format!("{}server_flags", prefix);
|
||||
sql.set_config_int(context, key, loginparam.server_flags)
|
||||
.ok();
|
||||
}
|
||||
|
||||
fn unset_empty(s: &String) -> Cow<String> {
|
||||
if s.is_empty() {
|
||||
Cow::Owned("unset".to_string())
|
||||
} else {
|
||||
Cow::Borrowed(s)
|
||||
}
|
||||
}
|
||||
|
||||
pub fn dc_loginparam_get_readable(loginparam: &dc_loginparam_t) -> String {
|
||||
let unset = "0";
|
||||
let pw = "***";
|
||||
|
||||
let flags_readable = get_readable_flags(loginparam.server_flags);
|
||||
|
||||
format!(
|
||||
"{} {}:{}:{}:{} {}:{}:{}:{} {}",
|
||||
unset_empty(&loginparam.addr),
|
||||
unset_empty(&loginparam.mail_user),
|
||||
if !loginparam.mail_pw.is_empty() {
|
||||
pw
|
||||
} else {
|
||||
unset
|
||||
},
|
||||
unset_empty(&loginparam.mail_server),
|
||||
loginparam.mail_port,
|
||||
unset_empty(&loginparam.send_user),
|
||||
if !loginparam.send_pw.is_empty() {
|
||||
pw
|
||||
} else {
|
||||
unset
|
||||
},
|
||||
unset_empty(&loginparam.send_server),
|
||||
loginparam.send_port,
|
||||
flags_readable,
|
||||
)
|
||||
}
|
||||
|
||||
fn get_readable_flags(flags: i32) -> String {
|
||||
let mut res = String::new();
|
||||
for bit in 0..31 {
|
||||
if 0 != flags & 1 << bit {
|
||||
let mut flag_added = 0;
|
||||
if 1 << bit == 0x2 {
|
||||
res += "OAUTH2 ";
|
||||
flag_added = 1;
|
||||
}
|
||||
if 1 << bit == 0x4 {
|
||||
res += "AUTH_NORMAL ";
|
||||
flag_added = 1;
|
||||
}
|
||||
if 1 << bit == 0x100 {
|
||||
res += "IMAP_STARTTLS ";
|
||||
flag_added = 1;
|
||||
}
|
||||
if 1 << bit == 0x200 {
|
||||
res += "IMAP_SSL ";
|
||||
flag_added = 1;
|
||||
}
|
||||
if 1 << bit == 0x400 {
|
||||
res += "IMAP_PLAIN ";
|
||||
flag_added = 1;
|
||||
}
|
||||
if 1 << bit == 0x10000 {
|
||||
res += "SMTP_STARTTLS ";
|
||||
flag_added = 1
|
||||
}
|
||||
if 1 << bit == 0x20000 {
|
||||
res += "SMTP_SSL ";
|
||||
flag_added = 1
|
||||
}
|
||||
if 1 << bit == 0x40000 {
|
||||
res += "SMTP_PLAIN ";
|
||||
flag_added = 1
|
||||
}
|
||||
if 0 == flag_added {
|
||||
res += &format!("{:#0x}", 1 << bit);
|
||||
}
|
||||
}
|
||||
}
|
||||
if res.is_empty() {
|
||||
res += "0";
|
||||
}
|
||||
|
||||
res
|
||||
}
|
||||
@@ -15,11 +15,11 @@ use crate::chat::{self, Chat};
|
||||
use crate::constants::*;
|
||||
use crate::contact::*;
|
||||
use crate::context::{dc_get_version_str, Context};
|
||||
use crate::dc_e2ee::*;
|
||||
use crate::dc_location::*;
|
||||
use crate::dc_strencode::*;
|
||||
use crate::dc_tools::*;
|
||||
use crate::e2ee::*;
|
||||
use crate::error::Error;
|
||||
use crate::location;
|
||||
use crate::message::*;
|
||||
use crate::param::*;
|
||||
use crate::stock::StockMessage;
|
||||
@@ -87,7 +87,7 @@ pub unsafe fn dc_mimefactory_load_msg(
|
||||
context: &Context,
|
||||
msg_id: u32,
|
||||
) -> Result<dc_mimefactory_t, Error> {
|
||||
ensure!(msg_id > DC_CHAT_ID_LAST_SPECIAL as u32, "Invalid chat id");
|
||||
ensure!(msg_id > DC_CHAT_ID_LAST_SPECIAL, "Invalid chat id");
|
||||
|
||||
let msg = dc_msg_load_from_db(context, msg_id)?;
|
||||
let chat = Chat::load_from_db(context, msg.chat_id)?;
|
||||
@@ -148,7 +148,7 @@ pub unsafe fn dc_mimefactory_load_msg(
|
||||
for row in rows {
|
||||
let (authname, addr) = row?;
|
||||
let addr_c = addr.strdup();
|
||||
if clist_search_string_nocase(factory.recipients_addr, addr_c) == 0 {
|
||||
if !clist_search_string_nocase(factory.recipients_addr, addr_c) {
|
||||
clist_insert_after(
|
||||
factory.recipients_names,
|
||||
(*factory.recipients_names).last,
|
||||
@@ -183,11 +183,11 @@ pub unsafe fn dc_mimefactory_load_msg(
|
||||
.unwrap_or_default();
|
||||
|
||||
if !email_to_remove.is_empty() && email_to_remove != self_addr {
|
||||
if clist_search_string_nocase(factory.recipients_addr, email_to_remove_c) == 0 {
|
||||
if !clist_search_string_nocase(factory.recipients_addr, email_to_remove_c) {
|
||||
clist_insert_after(
|
||||
factory.recipients_names,
|
||||
(*factory.recipients_names).last,
|
||||
0 as *mut libc::c_void,
|
||||
ptr::null_mut(),
|
||||
);
|
||||
clist_insert_after(
|
||||
factory.recipients_addr,
|
||||
@@ -308,7 +308,7 @@ pub unsafe fn dc_mimefactory_load_mdn<'a>(
|
||||
// in dc_markseen_msgs()
|
||||
ensure!(!contact.is_blocked(), "Contact blocked");
|
||||
ensure!(
|
||||
factory.msg.chat_id > DC_CHAT_ID_LAST_SPECIAL as u32,
|
||||
factory.msg.chat_id > DC_CHAT_ID_LAST_SPECIAL,
|
||||
"Invalid chat id"
|
||||
);
|
||||
|
||||
@@ -318,7 +318,7 @@ pub unsafe fn dc_mimefactory_load_mdn<'a>(
|
||||
(if !contact.get_authname().is_empty() {
|
||||
contact.get_authname().strdup()
|
||||
} else {
|
||||
0 as *mut libc::c_char
|
||||
ptr::null_mut()
|
||||
}) as *mut libc::c_void,
|
||||
);
|
||||
clist_insert_after(
|
||||
@@ -339,10 +339,10 @@ pub unsafe fn dc_mimefactory_render(factory: &mut dc_mimefactory_t) -> libc::c_i
|
||||
let subject: *mut mailimf_subject;
|
||||
let mut ok_to_continue = true;
|
||||
let imf_fields: *mut mailimf_fields;
|
||||
let mut message: *mut mailmime = 0 as *mut mailmime;
|
||||
let mut message_text: *mut libc::c_char = 0 as *mut libc::c_char;
|
||||
let mut message_text2: *mut libc::c_char = 0 as *mut libc::c_char;
|
||||
let mut subject_str: *mut libc::c_char = 0 as *mut libc::c_char;
|
||||
let mut message: *mut mailmime = ptr::null_mut();
|
||||
let mut message_text: *mut libc::c_char = ptr::null_mut();
|
||||
let mut message_text2: *mut libc::c_char = ptr::null_mut();
|
||||
let mut subject_str: *mut libc::c_char = ptr::null_mut();
|
||||
let mut afwd_email: libc::c_int = 0;
|
||||
let mut col: libc::c_int = 0;
|
||||
let mut success: libc::c_int = 0;
|
||||
@@ -353,13 +353,7 @@ pub unsafe fn dc_mimefactory_render(factory: &mut dc_mimefactory_t) -> libc::c_i
|
||||
let mut force_plaintext: libc::c_int = 0;
|
||||
let mut do_gossip: libc::c_int = 0;
|
||||
let mut grpimage = None;
|
||||
let mut e2ee_helper = dc_e2ee_helper_t {
|
||||
encryption_successfull: 0,
|
||||
cdata_to_free: 0 as *mut libc::c_void,
|
||||
encrypted: 0,
|
||||
signatures: Default::default(),
|
||||
gossipped_addr: Default::default(),
|
||||
};
|
||||
let mut e2ee_helper = E2eeHelper::default();
|
||||
|
||||
if factory.loaded as libc::c_uint == DC_MF_NOTHING_LOADED as libc::c_int as libc::c_uint
|
||||
|| !factory.out.is_null()
|
||||
@@ -377,12 +371,12 @@ pub unsafe fn dc_mimefactory_render(factory: &mut dc_mimefactory_t) -> libc::c_i
|
||||
if !factory.from_displayname.is_null() {
|
||||
dc_encode_header_words(factory.from_displayname)
|
||||
} else {
|
||||
0 as *mut libc::c_char
|
||||
ptr::null_mut()
|
||||
},
|
||||
dc_strdup(factory.from_addr),
|
||||
),
|
||||
);
|
||||
let mut to: *mut mailimf_address_list = 0 as *mut mailimf_address_list;
|
||||
let mut to: *mut mailimf_address_list = ptr::null_mut();
|
||||
if !factory.recipients_names.is_null()
|
||||
&& !factory.recipients_addr.is_null()
|
||||
&& (*factory.recipients_addr).count > 0
|
||||
@@ -396,12 +390,12 @@ pub unsafe fn dc_mimefactory_render(factory: &mut dc_mimefactory_t) -> libc::c_i
|
||||
let name: *const libc::c_char = (if !iter1.is_null() {
|
||||
(*iter1).data
|
||||
} else {
|
||||
0 as *mut libc::c_void
|
||||
ptr::null_mut()
|
||||
}) as *const libc::c_char;
|
||||
let addr: *const libc::c_char = (if !iter2.is_null() {
|
||||
(*iter2).data
|
||||
} else {
|
||||
0 as *mut libc::c_void
|
||||
ptr::null_mut()
|
||||
}) as *const libc::c_char;
|
||||
mailimf_address_list_add(
|
||||
to,
|
||||
@@ -411,33 +405,33 @@ pub unsafe fn dc_mimefactory_render(factory: &mut dc_mimefactory_t) -> libc::c_i
|
||||
if !name.is_null() {
|
||||
dc_encode_header_words(name)
|
||||
} else {
|
||||
0 as *mut libc::c_char
|
||||
ptr::null_mut()
|
||||
},
|
||||
dc_strdup(addr),
|
||||
),
|
||||
0 as *mut mailimf_group,
|
||||
ptr::null_mut(),
|
||||
),
|
||||
);
|
||||
iter1 = if !iter1.is_null() {
|
||||
(*iter1).next
|
||||
} else {
|
||||
0 as *mut clistcell
|
||||
ptr::null_mut()
|
||||
};
|
||||
iter2 = if !iter2.is_null() {
|
||||
(*iter2).next
|
||||
} else {
|
||||
0 as *mut clistcell
|
||||
ptr::null_mut()
|
||||
}
|
||||
}
|
||||
}
|
||||
let mut references_list: *mut clist = 0 as *mut clist;
|
||||
let mut references_list: *mut clist = ptr::null_mut();
|
||||
if !factory.references.is_null() && 0 != *factory.references.offset(0isize) as libc::c_int {
|
||||
references_list = dc_str_to_clist(
|
||||
factory.references,
|
||||
b" \x00" as *const u8 as *const libc::c_char,
|
||||
)
|
||||
}
|
||||
let mut in_reply_to_list: *mut clist = 0 as *mut clist;
|
||||
let mut in_reply_to_list: *mut clist = ptr::null_mut();
|
||||
if !factory.in_reply_to.is_null() && 0 != *factory.in_reply_to.offset(0isize) as libc::c_int
|
||||
{
|
||||
in_reply_to_list = dc_str_to_clist(
|
||||
@@ -448,15 +442,15 @@ pub unsafe fn dc_mimefactory_render(factory: &mut dc_mimefactory_t) -> libc::c_i
|
||||
imf_fields = mailimf_fields_new_with_data_all(
|
||||
mailimf_get_date(factory.timestamp as i64),
|
||||
from,
|
||||
0 as *mut mailimf_mailbox,
|
||||
0 as *mut mailimf_address_list,
|
||||
ptr::null_mut(),
|
||||
ptr::null_mut(),
|
||||
to,
|
||||
0 as *mut mailimf_address_list,
|
||||
0 as *mut mailimf_address_list,
|
||||
ptr::null_mut(),
|
||||
ptr::null_mut(),
|
||||
dc_strdup(factory.rfc724_mid),
|
||||
in_reply_to_list,
|
||||
references_list,
|
||||
0 as *mut libc::c_char,
|
||||
ptr::null_mut(),
|
||||
);
|
||||
|
||||
let os_name = &factory.context.os_name;
|
||||
@@ -503,8 +497,8 @@ pub unsafe fn dc_mimefactory_render(factory: &mut dc_mimefactory_t) -> libc::c_i
|
||||
/* Render a normal message
|
||||
*********************************************************************/
|
||||
let chat = factory.chat.as_ref().unwrap();
|
||||
let mut meta_part: *mut mailmime = 0 as *mut mailmime;
|
||||
let mut placeholdertext: *mut libc::c_char = 0 as *mut libc::c_char;
|
||||
let mut meta_part: *mut mailmime = ptr::null_mut();
|
||||
let mut placeholdertext: *mut libc::c_char = ptr::null_mut();
|
||||
if chat.typ == Chattype::VerifiedGroup {
|
||||
mailimf_fields_add(
|
||||
imf_fields,
|
||||
@@ -736,7 +730,7 @@ pub unsafe fn dc_mimefactory_render(factory: &mut dc_mimefactory_t) -> libc::c_i
|
||||
meta.type_0 = Viewtype::Image;
|
||||
meta.param.set(Param::File, grpimage);
|
||||
|
||||
let mut filename_as_sent = 0 as *mut libc::c_char;
|
||||
let mut filename_as_sent = ptr::null_mut();
|
||||
meta_part = build_body_file(
|
||||
&meta,
|
||||
b"group-image\x00" as *const u8 as *const libc::c_char,
|
||||
@@ -785,7 +779,7 @@ pub unsafe fn dc_mimefactory_render(factory: &mut dc_mimefactory_t) -> libc::c_i
|
||||
}
|
||||
}
|
||||
afwd_email = factory.msg.param.exists(Param::Forwarded) as libc::c_int;
|
||||
let mut fwdhint = 0 as *mut libc::c_char;
|
||||
let mut fwdhint = ptr::null_mut();
|
||||
if 0 != afwd_email {
|
||||
fwdhint = dc_strdup(
|
||||
b"---------- Forwarded message ----------\r\nFrom: Delta Chat\r\n\r\n\x00"
|
||||
@@ -850,11 +844,8 @@ pub unsafe fn dc_mimefactory_render(factory: &mut dc_mimefactory_t) -> libc::c_i
|
||||
free(error as *mut libc::c_void);
|
||||
ok_to_continue = false;
|
||||
} else {
|
||||
let file_part: *mut mailmime = build_body_file(
|
||||
&factory.msg,
|
||||
0 as *const libc::c_char,
|
||||
0 as *mut *mut libc::c_char,
|
||||
);
|
||||
let file_part: *mut mailmime =
|
||||
build_body_file(&factory.msg, ptr::null(), ptr::null_mut());
|
||||
if !file_part.is_null() {
|
||||
mailmime_smart_add_part(message, file_part);
|
||||
parts += 1
|
||||
@@ -883,45 +874,47 @@ pub unsafe fn dc_mimefactory_render(factory: &mut dc_mimefactory_t) -> libc::c_i
|
||||
.param
|
||||
.get_float(Param::SetLongitude)
|
||||
.unwrap_or_default();
|
||||
let kml_file =
|
||||
dc_get_message_kml(factory.msg.timestamp_sort, latitude, longitude);
|
||||
if !kml_file.is_null() {
|
||||
let kml_file = location::get_message_kml(
|
||||
factory.msg.timestamp_sort,
|
||||
latitude,
|
||||
longitude,
|
||||
);
|
||||
let content_type = mailmime_content_new_with_str(
|
||||
b"application/vnd.google-earth.kml+xml\x00" as *const u8
|
||||
as *const libc::c_char,
|
||||
);
|
||||
let mime_fields = mailmime_fields_new_filename(
|
||||
MAILMIME_DISPOSITION_TYPE_ATTACHMENT as libc::c_int,
|
||||
dc_strdup(b"message.kml\x00" as *const u8 as *const libc::c_char),
|
||||
MAILMIME_MECHANISM_8BIT as libc::c_int,
|
||||
);
|
||||
let kml_mime_part = mailmime_new_empty(content_type, mime_fields);
|
||||
mailmime_set_body_text(kml_mime_part, kml_file.strdup(), kml_file.len());
|
||||
mailmime_smart_add_part(message, kml_mime_part);
|
||||
}
|
||||
|
||||
if location::is_sending_locations_to_chat(
|
||||
factory.msg.context,
|
||||
factory.msg.chat_id,
|
||||
) {
|
||||
if let Ok((kml_file, last_added_location_id)) =
|
||||
location::get_kml(factory.msg.context, factory.msg.chat_id)
|
||||
{
|
||||
let content_type = mailmime_content_new_with_str(
|
||||
b"application/vnd.google-earth.kml+xml\x00" as *const u8
|
||||
as *const libc::c_char,
|
||||
);
|
||||
let mime_fields = mailmime_fields_new_filename(
|
||||
MAILMIME_DISPOSITION_TYPE_ATTACHMENT as libc::c_int,
|
||||
dc_strdup(b"message.kml\x00" as *const u8 as *const libc::c_char),
|
||||
MAILMIME_MECHANISM_8BIT as libc::c_int,
|
||||
);
|
||||
let kml_mime_part = mailmime_new_empty(content_type, mime_fields);
|
||||
mailmime_set_body_text(kml_mime_part, kml_file, strlen(kml_file));
|
||||
|
||||
mailmime_smart_add_part(message, kml_mime_part);
|
||||
}
|
||||
}
|
||||
|
||||
if dc_is_sending_locations_to_chat(factory.msg.context, factory.msg.chat_id) {
|
||||
let mut last_added_location_id: uint32_t = 0 as uint32_t;
|
||||
let kml_file: *mut libc::c_char = dc_get_location_kml(
|
||||
factory.msg.context,
|
||||
factory.msg.chat_id,
|
||||
&mut last_added_location_id,
|
||||
);
|
||||
if !kml_file.is_null() {
|
||||
let content_type: *mut mailmime_content = mailmime_content_new_with_str(
|
||||
b"application/vnd.google-earth.kml+xml\x00" as *const u8
|
||||
as *const libc::c_char,
|
||||
);
|
||||
let mime_fields: *mut mailmime_fields = mailmime_fields_new_filename(
|
||||
MAILMIME_DISPOSITION_TYPE_ATTACHMENT as libc::c_int,
|
||||
dc_strdup(b"location.kml\x00" as *const u8 as *const libc::c_char),
|
||||
MAILMIME_MECHANISM_8BIT as libc::c_int,
|
||||
);
|
||||
let kml_mime_part: *mut mailmime =
|
||||
mailmime_new_empty(content_type, mime_fields);
|
||||
mailmime_set_body_text(kml_mime_part, kml_file, strlen(kml_file));
|
||||
let kml_mime_part = mailmime_new_empty(content_type, mime_fields);
|
||||
mailmime_set_body_text(
|
||||
kml_mime_part,
|
||||
kml_file.strdup(),
|
||||
kml_file.len(),
|
||||
);
|
||||
mailmime_smart_add_part(message, kml_mime_part);
|
||||
if !factory.msg.param.exists(Param::SetLatitude) {
|
||||
// otherwise, the independent location is already filed
|
||||
@@ -1009,32 +1002,32 @@ pub unsafe fn dc_mimefactory_render(factory: &mut dc_mimefactory_t) -> libc::c_i
|
||||
imf_fields,
|
||||
mailimf_field_new(
|
||||
MAILIMF_FIELD_SUBJECT as libc::c_int,
|
||||
0 as *mut mailimf_return,
|
||||
0 as *mut mailimf_orig_date,
|
||||
0 as *mut mailimf_from,
|
||||
0 as *mut mailimf_sender,
|
||||
0 as *mut mailimf_to,
|
||||
0 as *mut mailimf_cc,
|
||||
0 as *mut mailimf_bcc,
|
||||
0 as *mut mailimf_message_id,
|
||||
0 as *mut mailimf_orig_date,
|
||||
0 as *mut mailimf_from,
|
||||
0 as *mut mailimf_sender,
|
||||
0 as *mut mailimf_reply_to,
|
||||
0 as *mut mailimf_to,
|
||||
0 as *mut mailimf_cc,
|
||||
0 as *mut mailimf_bcc,
|
||||
0 as *mut mailimf_message_id,
|
||||
0 as *mut mailimf_in_reply_to,
|
||||
0 as *mut mailimf_references,
|
||||
ptr::null_mut(),
|
||||
ptr::null_mut(),
|
||||
ptr::null_mut(),
|
||||
ptr::null_mut(),
|
||||
ptr::null_mut(),
|
||||
ptr::null_mut(),
|
||||
ptr::null_mut(),
|
||||
ptr::null_mut(),
|
||||
ptr::null_mut(),
|
||||
ptr::null_mut(),
|
||||
ptr::null_mut(),
|
||||
ptr::null_mut(),
|
||||
ptr::null_mut(),
|
||||
ptr::null_mut(),
|
||||
ptr::null_mut(),
|
||||
ptr::null_mut(),
|
||||
ptr::null_mut(),
|
||||
ptr::null_mut(),
|
||||
subject,
|
||||
0 as *mut mailimf_comments,
|
||||
0 as *mut mailimf_keywords,
|
||||
0 as *mut mailimf_optional_field,
|
||||
ptr::null_mut(),
|
||||
ptr::null_mut(),
|
||||
ptr::null_mut(),
|
||||
),
|
||||
);
|
||||
if force_plaintext != 2 {
|
||||
dc_e2ee_encrypt(
|
||||
e2ee_helper.encrypt(
|
||||
factory.context,
|
||||
factory.recipients_addr,
|
||||
force_plaintext,
|
||||
@@ -1042,10 +1035,9 @@ pub unsafe fn dc_mimefactory_render(factory: &mut dc_mimefactory_t) -> libc::c_i
|
||||
min_verified,
|
||||
do_gossip,
|
||||
message,
|
||||
&mut e2ee_helper,
|
||||
);
|
||||
}
|
||||
if 0 != e2ee_helper.encryption_successfull {
|
||||
if e2ee_helper.encryption_successfull {
|
||||
factory.out_encrypted = 1;
|
||||
if 0 != do_gossip {
|
||||
factory.out_gossiped = 1
|
||||
@@ -1053,14 +1045,14 @@ pub unsafe fn dc_mimefactory_render(factory: &mut dc_mimefactory_t) -> libc::c_i
|
||||
}
|
||||
factory.out = mmap_string_new(b"\x00" as *const u8 as *const libc::c_char);
|
||||
mailmime_write_mem(factory.out, &mut col, message);
|
||||
success = 1
|
||||
success = 1;
|
||||
}
|
||||
}
|
||||
|
||||
if !message.is_null() {
|
||||
mailmime_free(message);
|
||||
}
|
||||
dc_e2ee_thanks(&mut e2ee_helper);
|
||||
e2ee_helper.thanks();
|
||||
free(message_text as *mut libc::c_void);
|
||||
free(message_text2 as *mut libc::c_void);
|
||||
free(subject_str as *mut libc::c_void);
|
||||
@@ -1149,7 +1141,7 @@ unsafe fn build_body_file(
|
||||
) -> *mut mailmime {
|
||||
let needs_ext: bool;
|
||||
let mime_fields: *mut mailmime_fields;
|
||||
let mut mime_sub: *mut mailmime = 0 as *mut mailmime;
|
||||
let mut mime_sub: *mut mailmime = ptr::null_mut();
|
||||
let content: *mut mailmime_content;
|
||||
let path_filename = msg.param.get(Param::File);
|
||||
|
||||
@@ -1159,8 +1151,8 @@ unsafe fn build_body_file(
|
||||
.map(|s| s.strdup())
|
||||
.unwrap_or_else(|| std::ptr::null_mut());
|
||||
|
||||
let mut filename_to_send = 0 as *mut libc::c_char;
|
||||
let mut filename_encoded = 0 as *mut libc::c_char;
|
||||
let mut filename_to_send = ptr::null_mut();
|
||||
let mut filename_encoded = ptr::null_mut();
|
||||
|
||||
if let Some(ref path_filename) = path_filename {
|
||||
let suffix = dc_get_filesuffix_lc(path_filename);
|
||||
@@ -1226,11 +1218,11 @@ unsafe fn build_body_file(
|
||||
/* create mime part, for Content-Disposition, see RFC 2183.
|
||||
`Content-Disposition: attachment` seems not to make a difference to `Content-Disposition: inline` at least on tested Thunderbird and Gma'l in 2017.
|
||||
But I've heard about problems with inline and outl'k, so we just use the attachment-type until we run into other problems ... */
|
||||
needs_ext = dc_needs_ext_header(filename_to_send);
|
||||
needs_ext = dc_needs_ext_header(as_str(filename_to_send));
|
||||
mime_fields = mailmime_fields_new_filename(
|
||||
MAILMIME_DISPOSITION_TYPE_ATTACHMENT as libc::c_int,
|
||||
if needs_ext {
|
||||
0 as *mut libc::c_char
|
||||
ptr::null_mut()
|
||||
} else {
|
||||
dc_strdup(filename_to_send)
|
||||
},
|
||||
@@ -1242,7 +1234,7 @@ unsafe fn build_body_file(
|
||||
let field: *mut mailmime_field = (if !cur1.is_null() {
|
||||
(*cur1).data
|
||||
} else {
|
||||
0 as *mut libc::c_void
|
||||
ptr::null_mut()
|
||||
}) as *mut mailmime_field;
|
||||
if !field.is_null()
|
||||
&& (*field).fld_type == MAILMIME_FIELD_DISPOSITION as libc::c_int
|
||||
@@ -1254,16 +1246,16 @@ unsafe fn build_body_file(
|
||||
let parm: *mut mailmime_disposition_parm =
|
||||
mailmime_disposition_parm_new(
|
||||
MAILMIME_DISPOSITION_PARM_PARAMETER as libc::c_int,
|
||||
0 as *mut libc::c_char,
|
||||
0 as *mut libc::c_char,
|
||||
0 as *mut libc::c_char,
|
||||
0 as *mut libc::c_char,
|
||||
ptr::null_mut(),
|
||||
ptr::null_mut(),
|
||||
ptr::null_mut(),
|
||||
ptr::null_mut(),
|
||||
0 as size_t,
|
||||
mailmime_parameter_new(
|
||||
strdup(
|
||||
b"filename*\x00" as *const u8 as *const libc::c_char,
|
||||
),
|
||||
dc_encode_ext_header(filename_to_send),
|
||||
dc_encode_ext_header(as_str(filename_to_send)).strdup(),
|
||||
),
|
||||
);
|
||||
if !parm.is_null() {
|
||||
@@ -1279,7 +1271,7 @@ unsafe fn build_body_file(
|
||||
cur1 = if !cur1.is_null() {
|
||||
(*cur1).next
|
||||
} else {
|
||||
0 as *mut clistcell
|
||||
ptr::null_mut()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -1,44 +0,0 @@
|
||||
use crate::constants::*;
|
||||
use crate::context::*;
|
||||
use crate::job::*;
|
||||
use crate::message::*;
|
||||
use crate::param::Params;
|
||||
|
||||
pub unsafe fn dc_do_heuristics_moves(context: &Context, folder: &str, msg_id: u32) {
|
||||
if context
|
||||
.sql
|
||||
.get_config_int(context, "mvbox_move")
|
||||
.unwrap_or_else(|| 1)
|
||||
== 0
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
if !dc_is_inbox(context, folder) && !dc_is_sentbox(context, folder) {
|
||||
return;
|
||||
}
|
||||
|
||||
if let Ok(msg) = dc_msg_new_load(context, msg_id) {
|
||||
if dc_msg_is_setupmessage(&msg) {
|
||||
// do not move setup messages;
|
||||
// there may be a non-delta device that wants to handle it
|
||||
return;
|
||||
}
|
||||
|
||||
if dc_is_mvbox(context, folder) {
|
||||
dc_update_msg_move_state(context, msg.rfc724_mid, MoveState::Stay);
|
||||
}
|
||||
|
||||
// 1 = dc message, 2 = reply to dc message
|
||||
if 0 != msg.is_dc_message {
|
||||
job_add(
|
||||
context,
|
||||
Action::MoveMsg,
|
||||
msg.id as libc::c_int,
|
||||
Params::new(),
|
||||
0,
|
||||
);
|
||||
dc_update_msg_move_state(context, msg.rfc724_mid, MoveState::Moving);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,3 +1,4 @@
|
||||
use std::ffi::CString;
|
||||
use std::ptr;
|
||||
|
||||
use itertools::join;
|
||||
@@ -13,18 +14,17 @@ use sha2::{Digest, Sha256};
|
||||
use crate::chat::{self, Chat};
|
||||
use crate::constants::*;
|
||||
use crate::contact::*;
|
||||
use crate::context::Context;
|
||||
use crate::dc_location::*;
|
||||
use crate::context::{do_heuristics_moves, Context};
|
||||
use crate::dc_mimeparser::*;
|
||||
use crate::dc_move::*;
|
||||
use crate::dc_securejoin::*;
|
||||
use crate::dc_strencode::*;
|
||||
use crate::dc_tools::*;
|
||||
use crate::error::Result;
|
||||
use crate::job::*;
|
||||
use crate::location;
|
||||
use crate::message::*;
|
||||
use crate::param::*;
|
||||
use crate::peerstate::*;
|
||||
use crate::securejoin::handle_securejoin_handshake;
|
||||
use crate::sql;
|
||||
use crate::stock::StockMessage;
|
||||
use crate::types::*;
|
||||
@@ -56,8 +56,8 @@ pub unsafe fn dc_receive_imf(
|
||||
// we use mailmime_parse() through dc_mimeparser (both call mailimf_struct_multiple_parse()
|
||||
// somewhen, I did not found out anything that speaks against this approach yet)
|
||||
|
||||
let mut mime_parser = dc_mimeparser_new(context);
|
||||
dc_mimeparser_parse(&mut mime_parser, imf_raw_not_terminated, imf_raw_bytes);
|
||||
let body = std::slice::from_raw_parts(imf_raw_not_terminated as *const u8, imf_raw_bytes);
|
||||
let mut mime_parser = dc_mimeparser_parse(context, body);
|
||||
|
||||
if mime_parser.header.is_empty() {
|
||||
// Error - even adding an empty record won't help as we do not know the message ID
|
||||
@@ -131,7 +131,7 @@ pub unsafe fn dc_receive_imf(
|
||||
if 0 != check_self {
|
||||
incoming = 0;
|
||||
if 0 != dc_mimeparser_sender_equals_recipient(&mime_parser) {
|
||||
from_id = DC_CONTACT_ID_SELF as u32;
|
||||
from_id = DC_CONTACT_ID_SELF;
|
||||
}
|
||||
} else if from_list.len() >= 1 {
|
||||
// if there is no from given, from_id stays 0 which is just fine. These messages
|
||||
@@ -220,7 +220,7 @@ pub unsafe fn dc_receive_imf(
|
||||
);
|
||||
}
|
||||
|
||||
if !mime_parser.message_kml.is_none() && chat_id > DC_CHAT_ID_LAST_SPECIAL as u32 {
|
||||
if !mime_parser.message_kml.is_none() && chat_id > DC_CHAT_ID_LAST_SPECIAL {
|
||||
save_locations(
|
||||
context,
|
||||
&mime_parser,
|
||||
@@ -362,7 +362,7 @@ unsafe fn add_parts(
|
||||
}
|
||||
|
||||
// 1 or 0 for yes/no
|
||||
msgrmsg = mime_parser.is_send_by_messenger;
|
||||
msgrmsg = mime_parser.is_send_by_messenger as _;
|
||||
if msgrmsg == 0 && 0 != dc_is_reply_to_messenger_message(context, mime_parser) {
|
||||
// 2=no, but is reply to messenger message
|
||||
msgrmsg = 2;
|
||||
@@ -401,7 +401,7 @@ unsafe fn add_parts(
|
||||
msgrmsg = 1;
|
||||
*chat_id = 0;
|
||||
allow_creation = 1;
|
||||
let handshake = dc_handle_securejoin_handshake(context, mime_parser, *from_id);
|
||||
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;
|
||||
@@ -445,7 +445,7 @@ unsafe fn add_parts(
|
||||
|
||||
if *chat_id == 0 {
|
||||
// check if the message belongs to a mailing list
|
||||
if 0 != dc_mimeparser_is_mailinglist_message(mime_parser) {
|
||||
if dc_mimeparser_is_mailinglist_message(mime_parser) {
|
||||
*chat_id = 3;
|
||||
info!(
|
||||
context,
|
||||
@@ -492,7 +492,7 @@ unsafe fn add_parts(
|
||||
}
|
||||
if *chat_id == 0 {
|
||||
// maybe from_id is null or sth. else is suspicious, move message to trash
|
||||
*chat_id = DC_CHAT_ID_TRASH as u32;
|
||||
*chat_id = DC_CHAT_ID_TRASH;
|
||||
}
|
||||
|
||||
// if the chat_id is blocked,
|
||||
@@ -509,7 +509,7 @@ unsafe fn add_parts(
|
||||
// the mail is on the IMAP server, probably it is also delivered.
|
||||
// We cannot recreate other states (read, error).
|
||||
state = MessageState::OutDelivered;
|
||||
*from_id = DC_CONTACT_ID_SELF as u32;
|
||||
*from_id = DC_CONTACT_ID_SELF;
|
||||
if !to_ids.is_empty() {
|
||||
*to_id = to_ids[0];
|
||||
if *chat_id == 0 {
|
||||
@@ -565,7 +565,7 @@ unsafe fn add_parts(
|
||||
}
|
||||
}
|
||||
if *chat_id == 0 {
|
||||
*chat_id = DC_CHAT_ID_TRASH as u32;
|
||||
*chat_id = DC_CHAT_ID_TRASH;
|
||||
}
|
||||
}
|
||||
// correct message_timestamp, it should not be used before,
|
||||
@@ -626,33 +626,34 @@ unsafe fn add_parts(
|
||||
|mut stmt, conn| {
|
||||
for i in 0..icnt {
|
||||
let part = &mut mime_parser.parts[i];
|
||||
if part.is_meta != 0 {
|
||||
if part.is_meta {
|
||||
continue;
|
||||
}
|
||||
|
||||
if !mime_parser.location_kml.is_none()
|
||||
&& icnt == 1
|
||||
&& !part.msg.is_null()
|
||||
&& (strcmp(
|
||||
part.msg,
|
||||
b"-location-\x00" as *const u8 as *const libc::c_char,
|
||||
) == 0
|
||||
|| *part.msg.offset(0) as libc::c_int == 0)
|
||||
{
|
||||
*hidden = 1;
|
||||
if state == MessageState::InFresh {
|
||||
state = MessageState::InNoticed;
|
||||
if let Some(ref msg) = part.msg {
|
||||
if !mime_parser.location_kml.is_none()
|
||||
&& icnt == 1
|
||||
&& (msg == "-location-" || msg.is_empty())
|
||||
{
|
||||
*hidden = 1;
|
||||
if state == MessageState::InFresh {
|
||||
state = MessageState::InNoticed;
|
||||
}
|
||||
}
|
||||
}
|
||||
if part.type_0 == Viewtype::Text as i32 {
|
||||
if part.type_0 == Viewtype::Text {
|
||||
let msg_raw = CString::yolo(part.msg_raw.as_ref().unwrap().clone());
|
||||
let subject_c = CString::yolo(
|
||||
mime_parser
|
||||
.subject
|
||||
.as_ref()
|
||||
.map(|s| s.to_string())
|
||||
.unwrap_or("".into()),
|
||||
);
|
||||
txt_raw = dc_mprintf(
|
||||
b"%s\n\n%s\x00" as *const u8 as *const libc::c_char,
|
||||
if !mime_parser.subject.is_null() {
|
||||
mime_parser.subject
|
||||
} else {
|
||||
b"\x00" as *const u8 as *const libc::c_char
|
||||
},
|
||||
part.msg_raw,
|
||||
subject_c.as_ptr(),
|
||||
msg_raw.as_ptr(),
|
||||
)
|
||||
}
|
||||
if 0 != mime_parser.is_system_message {
|
||||
@@ -673,11 +674,7 @@ unsafe fn add_parts(
|
||||
part.type_0,
|
||||
state,
|
||||
msgrmsg,
|
||||
if !part.msg.is_null() {
|
||||
as_str(part.msg)
|
||||
} else {
|
||||
""
|
||||
},
|
||||
part.msg.as_ref().map_or("", String::as_str),
|
||||
// txt_raw might contain invalid utf8
|
||||
if !txt_raw.is_null() {
|
||||
to_string_lossy(txt_raw)
|
||||
@@ -703,7 +700,7 @@ unsafe fn add_parts(
|
||||
])?;
|
||||
|
||||
free(txt_raw as *mut libc::c_void);
|
||||
txt_raw = 0 as *mut libc::c_char;
|
||||
txt_raw = ptr::null_mut();
|
||||
*insert_msg_id = sql::get_rowid_with_conn(
|
||||
context,
|
||||
conn,
|
||||
@@ -730,7 +727,7 @@ unsafe fn add_parts(
|
||||
);
|
||||
|
||||
// check event to send
|
||||
if *chat_id == DC_CHAT_ID_TRASH as u32 {
|
||||
if *chat_id == DC_CHAT_ID_TRASH {
|
||||
*create_event_to_send = None;
|
||||
} else if 0 != incoming && state == MessageState::InFresh {
|
||||
if 0 != from_id_blocked {
|
||||
@@ -742,7 +739,7 @@ unsafe fn add_parts(
|
||||
}
|
||||
}
|
||||
|
||||
dc_do_heuristics_moves(context, server_folder.as_ref(), *insert_msg_id);
|
||||
do_heuristics_moves(context, server_folder.as_ref(), *insert_msg_id);
|
||||
cleanup(mime_in_reply_to, mime_references, txt_raw);
|
||||
|
||||
Ok(())
|
||||
@@ -804,7 +801,7 @@ unsafe fn handle_reports(
|
||||
{
|
||||
(*(*(*report_root).mm_data.mm_multipart.mm_mp_list).first).next
|
||||
} else {
|
||||
0 as *mut clistcell
|
||||
ptr::null_mut()
|
||||
}
|
||||
.is_null()
|
||||
{
|
||||
@@ -814,11 +811,11 @@ unsafe fn handle_reports(
|
||||
{
|
||||
(*(*(*report_root).mm_data.mm_multipart.mm_mp_list).first).next
|
||||
} else {
|
||||
0 as *mut clistcell
|
||||
ptr::null_mut()
|
||||
})
|
||||
.data
|
||||
} else {
|
||||
0 as *mut libc::c_void
|
||||
ptr::null_mut()
|
||||
}) as *mut mailmime;
|
||||
|
||||
if !report_data.is_null()
|
||||
@@ -908,11 +905,11 @@ unsafe fn handle_reports(
|
||||
}
|
||||
}
|
||||
|
||||
if 0 != mime_parser.is_send_by_messenger || 0 != mdn_consumed {
|
||||
if mime_parser.is_send_by_messenger || 0 != mdn_consumed {
|
||||
let mut param = Params::new();
|
||||
param.set(Param::ServerFolder, server_folder.as_ref());
|
||||
param.set_int(Param::ServerUid, server_uid as i32);
|
||||
if 0 != mime_parser.is_send_by_messenger
|
||||
if mime_parser.is_send_by_messenger
|
||||
&& 0 != context
|
||||
.sql
|
||||
.get_config_int(context, "mvbox_move")
|
||||
@@ -938,36 +935,34 @@ unsafe fn save_locations(
|
||||
let mut send_event = false;
|
||||
|
||||
if !mime_parser.message_kml.is_none() && chat_id > DC_CHAT_ID_LAST_SPECIAL as libc::c_uint {
|
||||
let newest_location_id: uint32_t = dc_save_locations(
|
||||
context,
|
||||
chat_id,
|
||||
from_id,
|
||||
&mime_parser.message_kml.as_ref().unwrap().locations,
|
||||
1,
|
||||
);
|
||||
let locations = &mime_parser.message_kml.as_ref().unwrap().locations;
|
||||
let newest_location_id =
|
||||
location::save(context, chat_id, from_id, locations, 1).unwrap_or_default();
|
||||
if 0 != newest_location_id && 0 == hidden {
|
||||
dc_set_msg_location_id(context, insert_msg_id, newest_location_id);
|
||||
location_id_written = true;
|
||||
send_event = true;
|
||||
if location::set_msg_location_id(context, insert_msg_id, newest_location_id).is_ok() {
|
||||
location_id_written = true;
|
||||
send_event = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if !mime_parser.location_kml.is_none() && chat_id > DC_CHAT_ID_LAST_SPECIAL as libc::c_uint {
|
||||
if !mime_parser.location_kml.as_ref().unwrap().addr.is_null() {
|
||||
if let Some(ref addr) = mime_parser.location_kml.as_ref().unwrap().addr {
|
||||
if let Ok(contact) = Contact::get_by_id(context, from_id) {
|
||||
if !contact.get_addr().is_empty()
|
||||
&& contact.get_addr().to_lowercase()
|
||||
== as_str(mime_parser.location_kml.as_ref().unwrap().addr).to_lowercase()
|
||||
&& contact.get_addr().to_lowercase() == addr.to_lowercase()
|
||||
{
|
||||
let newest_location_id = dc_save_locations(
|
||||
context,
|
||||
chat_id,
|
||||
from_id,
|
||||
&mime_parser.location_kml.as_ref().unwrap().locations,
|
||||
0,
|
||||
);
|
||||
let locations = &mime_parser.location_kml.as_ref().unwrap().locations;
|
||||
let newest_location_id =
|
||||
location::save(context, chat_id, from_id, locations, 0).unwrap_or_default();
|
||||
if newest_location_id != 0 && hidden == 0 && !location_id_written {
|
||||
dc_set_msg_location_id(context, insert_msg_id, newest_location_id);
|
||||
if let Err(err) = location::set_msg_location_id(
|
||||
context,
|
||||
insert_msg_id,
|
||||
newest_location_id,
|
||||
) {
|
||||
error!(context, 0, "Failed to set msg_location_id: {:?}", err);
|
||||
}
|
||||
}
|
||||
send_event = true;
|
||||
}
|
||||
@@ -1044,7 +1039,6 @@ unsafe fn create_or_lookup_group(
|
||||
let group_explicitly_left: bool;
|
||||
let mut chat_id = 0;
|
||||
let mut chat_id_blocked = Blocked::Not;
|
||||
let mut chat_id_verified = 0;
|
||||
let mut grpid = "".to_string();
|
||||
let mut grpname = std::ptr::null_mut();
|
||||
let to_ids_cnt = to_ids.len();
|
||||
@@ -1226,14 +1220,9 @@ unsafe fn create_or_lookup_group(
|
||||
set_better_msg(mime_parser, &better_msg);
|
||||
|
||||
// check, if we have a chat with this group ID
|
||||
chat_id = chat::get_chat_id_by_grpid(
|
||||
context,
|
||||
&grpid,
|
||||
Some(&mut chat_id_blocked),
|
||||
&mut chat_id_verified,
|
||||
);
|
||||
let (mut chat_id, chat_id_verified, _blocked) = chat::get_chat_id_by_grpid(context, &grpid);
|
||||
if chat_id != 0 {
|
||||
if 0 != chat_id_verified
|
||||
if chat_id_verified
|
||||
&& 0 == check_verified_properties(
|
||||
context,
|
||||
mime_parser,
|
||||
@@ -1260,7 +1249,7 @@ unsafe fn create_or_lookup_group(
|
||||
.get_config(context, "configured_addr")
|
||||
.unwrap_or_default();
|
||||
if chat_id == 0
|
||||
&& 0 == dc_mimeparser_is_mailinglist_message(mime_parser)
|
||||
&& !dc_mimeparser_is_mailinglist_message(mime_parser)
|
||||
&& !grpid.is_empty()
|
||||
&& !grpname.is_null()
|
||||
// otherwise, a pending "quit" message may pop up
|
||||
@@ -1299,10 +1288,10 @@ unsafe fn create_or_lookup_group(
|
||||
}
|
||||
|
||||
// again, check chat_id
|
||||
if chat_id <= DC_CHAT_ID_LAST_SPECIAL as u32 {
|
||||
if chat_id <= DC_CHAT_ID_LAST_SPECIAL {
|
||||
chat_id = 0;
|
||||
if group_explicitly_left {
|
||||
chat_id = DC_CHAT_ID_TRASH as u32;
|
||||
chat_id = DC_CHAT_ID_TRASH;
|
||||
} else {
|
||||
create_or_lookup_adhoc_group(
|
||||
context,
|
||||
@@ -1343,7 +1332,7 @@ unsafe fn create_or_lookup_group(
|
||||
}
|
||||
if !X_MrGrpImageChanged.is_null() {
|
||||
let mut ok = 0;
|
||||
let mut grpimage = 0 as *mut libc::c_char;
|
||||
let mut grpimage = ptr::null_mut();
|
||||
if strcmp(
|
||||
X_MrGrpImageChanged,
|
||||
b"0\x00" as *const u8 as *const libc::c_char,
|
||||
@@ -1352,7 +1341,7 @@ unsafe fn create_or_lookup_group(
|
||||
ok = 1
|
||||
} else {
|
||||
for part in &mut mime_parser.parts {
|
||||
if part.type_0 == 20 {
|
||||
if part.type_0 == Viewtype::Image {
|
||||
grpimage = part
|
||||
.param
|
||||
.get(Param::File)
|
||||
@@ -1397,7 +1386,7 @@ unsafe fn create_or_lookup_group(
|
||||
let skip = if !X_MrRemoveFromGrp.is_null() {
|
||||
X_MrRemoveFromGrp
|
||||
} else {
|
||||
0 as *mut libc::c_char
|
||||
ptr::null_mut()
|
||||
};
|
||||
sql::execute(
|
||||
context,
|
||||
@@ -1407,9 +1396,9 @@ unsafe fn create_or_lookup_group(
|
||||
)
|
||||
.ok();
|
||||
if skip.is_null() || !addr_cmp(&self_addr, as_str(skip)) {
|
||||
chat::add_to_chat_contacts_table(context, chat_id, DC_CONTACT_ID_SELF as u32);
|
||||
chat::add_to_chat_contacts_table(context, chat_id, DC_CONTACT_ID_SELF);
|
||||
}
|
||||
if from_id > DC_CHAT_ID_LAST_SPECIAL as u32 {
|
||||
if from_id > DC_CHAT_ID_LAST_SPECIAL {
|
||||
if !Contact::addr_equals_contact(context, &self_addr, from_id as u32)
|
||||
&& (skip.is_null()
|
||||
|| !Contact::addr_equals_contact(context, to_string(skip), from_id as u32))
|
||||
@@ -1435,7 +1424,7 @@ unsafe fn create_or_lookup_group(
|
||||
|
||||
// check the number of receivers -
|
||||
// the only critical situation is if the user hits "Reply" instead of "Reply all" in a non-messenger-client */
|
||||
if to_ids_cnt == 1 && mime_parser.is_send_by_messenger == 0 {
|
||||
if to_ids_cnt == 1 && !mime_parser.is_send_by_messenger {
|
||||
let is_contact_cnt = chat::get_chat_contact_cnt(context, chat_id);
|
||||
if is_contact_cnt > 3 {
|
||||
// to_ids_cnt==1 may be "From: A, To: B, SELF" as SELF is not counted in to_ids_cnt.
|
||||
@@ -1479,7 +1468,7 @@ unsafe fn create_or_lookup_adhoc_group(
|
||||
// group matching the to-list or if we can create one
|
||||
let mut chat_id = 0;
|
||||
let mut chat_id_blocked = Blocked::Not;
|
||||
let mut grpname = 0 as *mut libc::c_char;
|
||||
let mut grpname = ptr::null_mut();
|
||||
|
||||
let cleanup = |grpname: *mut libc::c_char,
|
||||
ret_chat_id: *mut uint32_t,
|
||||
@@ -1495,7 +1484,7 @@ unsafe fn create_or_lookup_adhoc_group(
|
||||
};
|
||||
|
||||
// build member list from the given ids
|
||||
if to_ids.is_empty() || 0 != dc_mimeparser_is_mailinglist_message(mime_parser) {
|
||||
if to_ids.is_empty() || dc_mimeparser_is_mailinglist_message(mime_parser) {
|
||||
// too few contacts or a mailinglist
|
||||
cleanup(
|
||||
grpname,
|
||||
@@ -1584,8 +1573,8 @@ unsafe fn create_or_lookup_adhoc_group(
|
||||
}
|
||||
|
||||
// use subject as initial chat name
|
||||
if !mime_parser.subject.is_null() && 0 != *mime_parser.subject.offset(0isize) as libc::c_int {
|
||||
grpname = dc_strdup(mime_parser.subject)
|
||||
if let Some(subject) = mime_parser.subject.as_ref().filter(|s| !s.is_empty()) {
|
||||
grpname = subject.strdup();
|
||||
} else {
|
||||
grpname = context
|
||||
.stock_string_repl_int(StockMessage::Member, member_ids.len() as libc::c_int)
|
||||
@@ -1771,7 +1760,7 @@ unsafe fn check_verified_properties(
|
||||
}
|
||||
};
|
||||
|
||||
if 0 == mimeparser.e2ee_helper.encrypted {
|
||||
if mimeparser.e2ee_helper.encrypted {
|
||||
verify_fail("This message is not encrypted".into());
|
||||
return 0;
|
||||
}
|
||||
@@ -1862,9 +1851,8 @@ unsafe fn set_better_msg<T: AsRef<str>>(mime_parser: &mut dc_mimeparser_t, bette
|
||||
let msg = better_msg.as_ref();
|
||||
if msg.len() > 0 && !mime_parser.parts.is_empty() {
|
||||
let part = &mut mime_parser.parts[0];
|
||||
if (*part).type_0 == 10 {
|
||||
free(part.msg as *mut libc::c_void);
|
||||
part.msg = msg.strdup();
|
||||
if (*part).type_0 == Viewtype::Text {
|
||||
part.msg = Some(msg.to_string());
|
||||
}
|
||||
};
|
||||
}
|
||||
@@ -1885,10 +1873,7 @@ unsafe fn dc_is_reply_to_known_message(
|
||||
if !field.is_null() && (*field).fld_type == MAILIMF_FIELD_IN_REPLY_TO as libc::c_int {
|
||||
let fld_in_reply_to: *mut mailimf_in_reply_to = (*field).fld_data.fld_in_reply_to;
|
||||
if !fld_in_reply_to.is_null() {
|
||||
if 0 != is_known_rfc724_mid_in_list(
|
||||
context,
|
||||
(*(*field).fld_data.fld_in_reply_to).mid_list,
|
||||
) {
|
||||
if is_known_rfc724_mid_in_list(context, (*(*field).fld_data.fld_in_reply_to).mid_list) {
|
||||
return 1;
|
||||
}
|
||||
}
|
||||
@@ -1897,10 +1882,7 @@ unsafe fn dc_is_reply_to_known_message(
|
||||
if !field.is_null() && (*field).fld_type == MAILIMF_FIELD_REFERENCES as libc::c_int {
|
||||
let fld_references: *mut mailimf_references = (*field).fld_data.fld_references;
|
||||
if !fld_references.is_null() {
|
||||
if 0 != is_known_rfc724_mid_in_list(
|
||||
context,
|
||||
(*(*field).fld_data.fld_references).mid_list,
|
||||
) {
|
||||
if is_known_rfc724_mid_in_list(context, (*(*field).fld_data.fld_references).mid_list) {
|
||||
return 1;
|
||||
}
|
||||
}
|
||||
@@ -1908,29 +1890,18 @@ unsafe fn dc_is_reply_to_known_message(
|
||||
0
|
||||
}
|
||||
|
||||
unsafe fn is_known_rfc724_mid_in_list(context: &Context, mid_list: *const clist) -> libc::c_int {
|
||||
if !mid_list.is_null() {
|
||||
let mut cur: *mut clistiter;
|
||||
cur = (*mid_list).first;
|
||||
while !cur.is_null() {
|
||||
if 0 != is_known_rfc724_mid(
|
||||
context,
|
||||
(if !cur.is_null() {
|
||||
(*cur).data
|
||||
} else {
|
||||
0 as *mut libc::c_void
|
||||
}) as *const libc::c_char,
|
||||
) {
|
||||
return 1;
|
||||
}
|
||||
cur = if !cur.is_null() {
|
||||
(*cur).next
|
||||
} else {
|
||||
ptr::null_mut()
|
||||
}
|
||||
unsafe fn is_known_rfc724_mid_in_list(context: &Context, mid_list: *const clist) -> bool {
|
||||
if mid_list.is_null() {
|
||||
return false;
|
||||
}
|
||||
|
||||
for data in &*mid_list {
|
||||
if 0 != is_known_rfc724_mid(context, data.cast()) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
0
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
/// Check if a message is a reply to a known message (messenger or non-messenger).
|
||||
@@ -1995,7 +1966,7 @@ unsafe fn is_msgrmsg_rfc724_mid_in_list(context: &Context, mid_list: *const clis
|
||||
(if !cur.is_null() {
|
||||
(*cur).data
|
||||
} else {
|
||||
0 as *mut libc::c_void
|
||||
ptr::null_mut()
|
||||
}) as *const libc::c_char,
|
||||
) {
|
||||
return 1;
|
||||
@@ -2039,7 +2010,7 @@ unsafe fn dc_add_or_lookup_contacts_by_address_list(
|
||||
let adr: *mut mailimf_address = (if !cur.is_null() {
|
||||
(*cur).data
|
||||
} else {
|
||||
0 as *mut libc::c_void
|
||||
ptr::null_mut()
|
||||
}) as *mut mailimf_address;
|
||||
if !adr.is_null() {
|
||||
if (*adr).ad_type == MAILIMF_ADDRESS_MAILBOX as libc::c_int {
|
||||
@@ -2070,7 +2041,7 @@ unsafe fn dc_add_or_lookup_contacts_by_address_list(
|
||||
cur = if !cur.is_null() {
|
||||
(*cur).next
|
||||
} else {
|
||||
0 as *mut clistcell
|
||||
ptr::null_mut()
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -2090,7 +2061,7 @@ unsafe fn dc_add_or_lookup_contacts_by_mailbox_list(
|
||||
let mb: *mut mailimf_mailbox = (if !cur.is_null() {
|
||||
(*cur).data
|
||||
} else {
|
||||
0 as *mut libc::c_void
|
||||
ptr::null_mut()
|
||||
}) as *mut mailimf_mailbox;
|
||||
if !mb.is_null() {
|
||||
add_or_lookup_contact_by_addr(
|
||||
@@ -2105,7 +2076,7 @@ unsafe fn dc_add_or_lookup_contacts_by_mailbox_list(
|
||||
cur = if !cur.is_null() {
|
||||
(*cur).next
|
||||
} else {
|
||||
0 as *mut clistcell
|
||||
ptr::null_mut()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
1026
src/dc_securejoin.rs
1026
src/dc_securejoin.rs
File diff suppressed because it is too large
Load Diff
@@ -1,64 +1,49 @@
|
||||
use crate::dc_dehtml::*;
|
||||
use crate::dc_tools::*;
|
||||
use crate::x::*;
|
||||
|
||||
#[derive(Copy, Clone)]
|
||||
pub struct Simplify {
|
||||
pub is_forwarded: bool,
|
||||
pub is_cut_at_begin: bool,
|
||||
pub is_cut_at_end: bool,
|
||||
}
|
||||
|
||||
/// Return index of footer line in vector of message lines, or vector length if
|
||||
/// no footer is found.
|
||||
///
|
||||
/// Also return whether not-standard (rfc3676, §4.3) footer is found.
|
||||
fn find_message_footer(lines: &[&str]) -> (usize, bool) {
|
||||
for ix in 0..lines.len() {
|
||||
let line = lines[ix];
|
||||
|
||||
// quoted-printable may encode `-- ` to `-- =20` which is converted
|
||||
// back to `-- `
|
||||
match line.as_ref() {
|
||||
"-- " | "-- " => return (ix, false),
|
||||
"--" | "---" | "----" => return (ix, true),
|
||||
_ => (),
|
||||
}
|
||||
}
|
||||
return (lines.len(), false);
|
||||
}
|
||||
|
||||
impl Simplify {
|
||||
pub fn new() -> Self {
|
||||
Simplify {
|
||||
is_forwarded: false,
|
||||
is_cut_at_begin: false,
|
||||
is_cut_at_end: false,
|
||||
}
|
||||
}
|
||||
|
||||
/// Simplify and normalise text: Remove quotes, signatures, unnecessary
|
||||
/// lineends etc.
|
||||
/// The data returned from simplify() must be free()'d when no longer used.
|
||||
pub unsafe fn simplify(
|
||||
&mut self,
|
||||
in_unterminated: *const libc::c_char,
|
||||
in_bytes: libc::c_int,
|
||||
is_html: bool,
|
||||
is_msgrmsg: libc::c_int,
|
||||
) -> *mut libc::c_char {
|
||||
if in_bytes <= 0 {
|
||||
return "".strdup();
|
||||
}
|
||||
pub fn simplify(&mut self, input: &str, is_html: bool, is_msgrmsg: bool) -> String {
|
||||
let mut out = if is_html {
|
||||
dc_dehtml(input)
|
||||
} else {
|
||||
input.to_string()
|
||||
};
|
||||
|
||||
/* create a copy of the given buffer */
|
||||
let mut out: *mut libc::c_char;
|
||||
let mut temp: *mut libc::c_char;
|
||||
self.is_forwarded = false;
|
||||
self.is_cut_at_begin = false;
|
||||
self.is_cut_at_end = false;
|
||||
out = strndup(
|
||||
in_unterminated as *mut libc::c_char,
|
||||
in_bytes as libc::c_ulong,
|
||||
);
|
||||
if out.is_null() {
|
||||
return dc_strdup(b"\x00" as *const u8 as *const libc::c_char);
|
||||
}
|
||||
if is_html {
|
||||
temp = dc_dehtml(out);
|
||||
if !temp.is_null() {
|
||||
free(out as *mut libc::c_void);
|
||||
out = temp
|
||||
}
|
||||
}
|
||||
dc_remove_cr_chars(out);
|
||||
temp = self.simplify_plain_text(out, is_msgrmsg);
|
||||
if !temp.is_null() {
|
||||
free(out as *mut libc::c_void);
|
||||
out = temp
|
||||
}
|
||||
dc_remove_cr_chars(out);
|
||||
out.retain(|c| c != '\r');
|
||||
out = self.simplify_plain_text(&out, is_msgrmsg);
|
||||
out.retain(|c| c != '\r');
|
||||
|
||||
out
|
||||
}
|
||||
@@ -67,102 +52,75 @@ impl Simplify {
|
||||
* Simplify Plain Text
|
||||
*/
|
||||
#[allow(non_snake_case)]
|
||||
unsafe fn simplify_plain_text(
|
||||
&mut self,
|
||||
buf_terminated: *const libc::c_char,
|
||||
is_msgrmsg: libc::c_int,
|
||||
) -> *mut libc::c_char {
|
||||
fn simplify_plain_text(&mut self, buf_terminated: &str, is_msgrmsg: bool) -> String {
|
||||
/* This function ...
|
||||
... removes all text after the line `-- ` (footer mark)
|
||||
... removes full quotes at the beginning and at the end of the text -
|
||||
these are all lines starting with the character `>`
|
||||
... remove a non-empty line before the removed quote (contains sth. like "On 2.9.2016, Bjoern wrote:" in different formats and lanugages) */
|
||||
/* split the given buffer into lines */
|
||||
let lines = dc_split_into_lines(buf_terminated);
|
||||
let lines: Vec<_> = buf_terminated.split('\n').collect();
|
||||
let mut l_first: usize = 0;
|
||||
let mut l_last = lines.len();
|
||||
let mut line: *mut libc::c_char;
|
||||
let mut footer_mark: libc::c_int = 0i32;
|
||||
for l in l_first..l_last {
|
||||
line = lines[l];
|
||||
if strcmp(line, b"-- \x00" as *const u8 as *const libc::c_char) == 0i32
|
||||
|| strcmp(line, b"-- \x00" as *const u8 as *const libc::c_char) == 0i32
|
||||
{
|
||||
footer_mark = 1i32
|
||||
}
|
||||
if strcmp(line, b"--\x00" as *const u8 as *const libc::c_char) == 0i32
|
||||
|| strcmp(line, b"---\x00" as *const u8 as *const libc::c_char) == 0i32
|
||||
|| strcmp(line, b"----\x00" as *const u8 as *const libc::c_char) == 0i32
|
||||
{
|
||||
footer_mark = 1i32;
|
||||
self.is_cut_at_end = true
|
||||
}
|
||||
if 0 != footer_mark {
|
||||
l_last = l;
|
||||
/* done */
|
||||
break;
|
||||
}
|
||||
}
|
||||
let mut is_cut_at_begin = false;
|
||||
let (mut l_last, mut is_cut_at_end) = find_message_footer(&lines);
|
||||
|
||||
if l_last > l_first + 2 {
|
||||
let line0: *mut libc::c_char = lines[l_first];
|
||||
let line1: *mut libc::c_char = lines[l_first + 1];
|
||||
let line2: *mut libc::c_char = lines[l_first + 2];
|
||||
if strcmp(
|
||||
line0,
|
||||
b"---------- Forwarded message ----------\x00" as *const u8 as *const libc::c_char,
|
||||
) == 0i32
|
||||
&& strncmp(line1, b"From: \x00" as *const u8 as *const libc::c_char, 6) == 0i32
|
||||
&& *line2.offset(0isize) as libc::c_int == 0i32
|
||||
let line0 = lines[l_first];
|
||||
let line1 = lines[l_first + 1];
|
||||
let line2 = lines[l_first + 2];
|
||||
if line0 == "---------- Forwarded message ----------"
|
||||
&& line1.starts_with("From: ")
|
||||
&& line2.is_empty()
|
||||
{
|
||||
self.is_forwarded = true;
|
||||
l_first += 3
|
||||
}
|
||||
}
|
||||
for l in l_first..l_last {
|
||||
line = lines[l];
|
||||
if strncmp(line, b"-----\x00" as *const u8 as *const libc::c_char, 5) == 0i32
|
||||
|| strncmp(line, b"_____\x00" as *const u8 as *const libc::c_char, 5) == 0i32
|
||||
|| strncmp(line, b"=====\x00" as *const u8 as *const libc::c_char, 5) == 0i32
|
||||
|| strncmp(line, b"*****\x00" as *const u8 as *const libc::c_char, 5) == 0i32
|
||||
|| strncmp(line, b"~~~~~\x00" as *const u8 as *const libc::c_char, 5) == 0i32
|
||||
let line = lines[l];
|
||||
if line == "-----"
|
||||
|| line == "_____"
|
||||
|| line == "====="
|
||||
|| line == "*****"
|
||||
|| line == "~~~~~"
|
||||
{
|
||||
l_last = l;
|
||||
self.is_cut_at_end = true;
|
||||
is_cut_at_end = true;
|
||||
/* done */
|
||||
break;
|
||||
}
|
||||
}
|
||||
if 0 == is_msgrmsg {
|
||||
if !is_msgrmsg {
|
||||
let mut l_lastQuotedLine = None;
|
||||
for l in (l_first..l_last).rev() {
|
||||
line = lines[l];
|
||||
let line = lines[l];
|
||||
if is_plain_quote(line) {
|
||||
l_lastQuotedLine = Some(l)
|
||||
} else if !is_empty_line(line) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
if l_lastQuotedLine.is_some() {
|
||||
l_last = l_lastQuotedLine.unwrap();
|
||||
self.is_cut_at_end = true;
|
||||
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 {
|
||||
line = lines[l_last - 1];
|
||||
let line = lines[l_last - 1];
|
||||
if is_quoted_headline(line) {
|
||||
l_last -= 1
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
if 0 == is_msgrmsg {
|
||||
if !is_msgrmsg {
|
||||
let mut l_lastQuotedLine_0 = None;
|
||||
let mut hasQuotedHeadline = 0;
|
||||
for l in l_first..l_last {
|
||||
line = lines[l];
|
||||
let line = lines[l];
|
||||
if is_plain_quote(line) {
|
||||
l_lastQuotedLine_0 = Some(l)
|
||||
} else if !is_empty_line(line) {
|
||||
@@ -177,21 +135,21 @@ impl Simplify {
|
||||
}
|
||||
}
|
||||
}
|
||||
if l_lastQuotedLine_0.is_some() {
|
||||
l_first = l_lastQuotedLine_0.unwrap() + 1;
|
||||
self.is_cut_at_begin = true
|
||||
if let Some(last_quoted_line) = l_lastQuotedLine_0 {
|
||||
l_first = last_quoted_line + 1;
|
||||
is_cut_at_begin = true
|
||||
}
|
||||
}
|
||||
/* re-create buffer from the remaining lines */
|
||||
let mut ret = String::new();
|
||||
if self.is_cut_at_begin {
|
||||
if is_cut_at_begin {
|
||||
ret += "[...]";
|
||||
}
|
||||
/* we write empty lines only in case and non-empty line follows */
|
||||
let mut pending_linebreaks: libc::c_int = 0i32;
|
||||
let mut content_lines_added: libc::c_int = 0i32;
|
||||
for l in l_first..l_last {
|
||||
line = lines[l];
|
||||
let line = lines[l];
|
||||
if is_empty_line(line) {
|
||||
pending_linebreaks += 1
|
||||
} else {
|
||||
@@ -205,142 +163,105 @@ impl Simplify {
|
||||
}
|
||||
}
|
||||
// the incoming message might contain invalid UTF8
|
||||
ret += &to_string_lossy(line);
|
||||
ret += line;
|
||||
content_lines_added += 1;
|
||||
pending_linebreaks = 1i32
|
||||
}
|
||||
}
|
||||
if self.is_cut_at_end && (!self.is_cut_at_begin || 0 != content_lines_added) {
|
||||
if is_cut_at_end && (!is_cut_at_begin || 0 != content_lines_added) {
|
||||
ret += " [...]";
|
||||
}
|
||||
dc_free_splitted_lines(lines);
|
||||
|
||||
ret.strdup()
|
||||
ret
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Tools
|
||||
*/
|
||||
unsafe fn is_empty_line(buf: *const libc::c_char) -> bool {
|
||||
/* force unsigned - otherwise the `> ' '` comparison will fail */
|
||||
let mut p1: *const libc::c_uchar = buf as *const libc::c_uchar;
|
||||
while 0 != *p1 {
|
||||
if *p1 as libc::c_int > ' ' as i32 {
|
||||
fn is_empty_line(buf: &str) -> bool {
|
||||
// XXX: can it be simplified to buf.chars().all(|c| c.is_whitespace())?
|
||||
//
|
||||
// Strictly speaking, it is not equivalent (^A is not whitespace, but less than ' '),
|
||||
// but having control sequences in email body?!
|
||||
//
|
||||
// See discussion at: https://github.com/deltachat/deltachat-core-rust/pull/402#discussion_r317062392
|
||||
for c in buf.chars() {
|
||||
if c > ' ' {
|
||||
return false;
|
||||
}
|
||||
p1 = p1.offset(1isize)
|
||||
}
|
||||
|
||||
true
|
||||
}
|
||||
|
||||
unsafe fn is_quoted_headline(buf: *const libc::c_char) -> bool {
|
||||
fn is_quoted_headline(buf: &str) -> bool {
|
||||
/* This function may be called for the line _directly_ before a quote.
|
||||
The function checks if the line contains sth. like "On 01.02.2016, xy@z wrote:" in various languages.
|
||||
- Currently, we simply check if the last character is a ':'.
|
||||
- Checking for the existence of an email address may fail (headlines may show the user's name instead of the address) */
|
||||
let buf_len: libc::c_int = strlen(buf) as libc::c_int;
|
||||
if buf_len > 80i32 {
|
||||
return false;
|
||||
}
|
||||
if buf_len > 0i32 && *buf.offset((buf_len - 1i32) as isize) as libc::c_int == ':' as i32 {
|
||||
return true;
|
||||
}
|
||||
|
||||
false
|
||||
buf.len() <= 80 && buf.ends_with(':')
|
||||
}
|
||||
|
||||
unsafe fn is_plain_quote(buf: *const libc::c_char) -> bool {
|
||||
if *buf.offset(0isize) as libc::c_int == '>' as i32 {
|
||||
return true;
|
||||
}
|
||||
|
||||
false
|
||||
fn is_plain_quote(buf: &str) -> bool {
|
||||
buf.starts_with(">")
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
use std::ffi::CStr;
|
||||
|
||||
#[test]
|
||||
fn test_simplify_trim() {
|
||||
unsafe {
|
||||
let mut simplify = Simplify::new();
|
||||
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 plain: *mut libc::c_char =
|
||||
simplify.simplify(html, strlen(html) as libc::c_int, true, 0);
|
||||
let mut simplify = Simplify::new();
|
||||
let html = "\r\r\nline1<br>\r\n\r\n\r\rline2\n\r";
|
||||
let plain = simplify.simplify(html, true, false);
|
||||
|
||||
assert_eq!(
|
||||
CStr::from_ptr(plain as *const libc::c_char)
|
||||
.to_str()
|
||||
.unwrap(),
|
||||
"line1\nline2",
|
||||
);
|
||||
|
||||
free(plain as *mut libc::c_void);
|
||||
}
|
||||
assert_eq!(plain, "line1\nline2");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_simplify_parse_href() {
|
||||
unsafe {
|
||||
let mut simplify = Simplify::new();
|
||||
let html: *const libc::c_char =
|
||||
b"<a href=url>text</a\x00" as *const u8 as *const libc::c_char;
|
||||
let plain: *mut libc::c_char =
|
||||
simplify.simplify(html, strlen(html) as libc::c_int, true, 0);
|
||||
let mut simplify = Simplify::new();
|
||||
let html = "<a href=url>text</a";
|
||||
let plain = simplify.simplify(html, true, false);
|
||||
|
||||
assert_eq!(
|
||||
CStr::from_ptr(plain as *const libc::c_char)
|
||||
.to_str()
|
||||
.unwrap(),
|
||||
"[text](url)",
|
||||
);
|
||||
|
||||
free(plain as *mut libc::c_void);
|
||||
}
|
||||
assert_eq!(plain, "[text](url)");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_simplify_bold_text() {
|
||||
unsafe {
|
||||
let mut simplify = Simplify::new();
|
||||
let html: *const libc::c_char =
|
||||
b"<!DOCTYPE name [<!DOCTYPE ...>]><!-- comment -->text <b><?php echo ... ?>bold</b><![CDATA[<>]]>\x00"
|
||||
as *const u8 as *const libc::c_char;
|
||||
let plain = simplify.simplify(html, strlen(html) as libc::c_int, true, 0);
|
||||
let mut simplify = Simplify::new();
|
||||
let html = "<!DOCTYPE name [<!DOCTYPE ...>]><!-- comment -->text <b><?php echo ... ?>bold</b><![CDATA[<>]]>";
|
||||
let plain = simplify.simplify(html, true, false);
|
||||
|
||||
assert_eq!(
|
||||
CStr::from_ptr(plain as *const libc::c_char)
|
||||
.to_str()
|
||||
.unwrap(),
|
||||
"text *bold*<>",
|
||||
);
|
||||
|
||||
free(plain as *mut libc::c_void);
|
||||
}
|
||||
assert_eq!(plain, "text *bold*<>");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_simplify_html_encoded() {
|
||||
unsafe {
|
||||
let mut simplify = Simplify::new();
|
||||
let html =
|
||||
b"<>"'& äÄöÖüÜß fooÆçÇ ♦‎‏‌&noent;‍\x00"
|
||||
as *const u8 as *const libc::c_char;
|
||||
let plain = simplify.simplify(html, strlen(html) as libc::c_int, true, 0);
|
||||
let mut simplify = Simplify::new();
|
||||
let html =
|
||||
"<>"'& äÄöÖüÜß fooÆçÇ ♦‎‏‌&noent;‍";
|
||||
|
||||
assert_eq!(
|
||||
CStr::from_ptr(plain as *const libc::c_char)
|
||||
.to_str()
|
||||
.unwrap(),
|
||||
"<>\"\'& äÄöÖüÜß fooÆçÇ \u{2666}\u{200e}\u{200f}\u{200c}&noent;\u{200d}"
|
||||
);
|
||||
let plain = simplify.simplify(html, true, false);
|
||||
|
||||
free(plain as *mut libc::c_void);
|
||||
}
|
||||
assert_eq!(
|
||||
plain,
|
||||
"<>\"\'& äÄöÖüÜß fooÆçÇ \u{2666}\u{200e}\u{200f}\u{200c}&noent;\u{200d}"
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_simplify_utilities() {
|
||||
assert!(is_empty_line(" \t"));
|
||||
assert!(is_empty_line(""));
|
||||
assert!(is_empty_line(" \r"));
|
||||
assert!(!is_empty_line(" x"));
|
||||
assert!(is_plain_quote("> hello world"));
|
||||
assert!(is_plain_quote(">>"));
|
||||
assert!(!is_plain_quote("Life is pain"));
|
||||
assert!(!is_plain_quote(""));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,85 +1,20 @@
|
||||
use std::ffi::{CStr, CString};
|
||||
use std::borrow::Cow;
|
||||
use std::ffi::CString;
|
||||
use std::ptr;
|
||||
|
||||
use charset::Charset;
|
||||
use mmime::mailmime_decode::*;
|
||||
use mmime::mmapstring::*;
|
||||
use mmime::other::*;
|
||||
use percent_encoding::{percent_decode, utf8_percent_encode, AsciiSet, CONTROLS};
|
||||
|
||||
use crate::dc_tools::*;
|
||||
use crate::types::*;
|
||||
use crate::x::*;
|
||||
|
||||
#[inline]
|
||||
fn isalnum(c: libc::c_int) -> libc::c_int {
|
||||
if c < std::u8::MAX as libc::c_int {
|
||||
(c as u8 as char).is_ascii_alphanumeric() as libc::c_int
|
||||
} else {
|
||||
0
|
||||
}
|
||||
}
|
||||
|
||||
/* ******************************************************************************
|
||||
* URL encoding and decoding, RFC 3986
|
||||
******************************************************************************/
|
||||
unsafe fn int_2_uppercase_hex(code: libc::c_char) -> libc::c_char {
|
||||
static mut HEX: [libc::c_char; 17] = [
|
||||
48, 49, 50, 51, 52, 53, 54, 55, 56, 57, 65, 66, 67, 68, 69, 70, 0,
|
||||
];
|
||||
|
||||
HEX[(code as libc::c_int & 15i32) as usize]
|
||||
}
|
||||
|
||||
pub unsafe fn dc_urldecode(to_decode: *const libc::c_char) -> *mut libc::c_char {
|
||||
let mut pstr: *const libc::c_char = to_decode;
|
||||
if to_decode.is_null() {
|
||||
return dc_strdup(b"\x00" as *const u8 as *const libc::c_char);
|
||||
}
|
||||
let buf: *mut libc::c_char = malloc(strlen(to_decode).wrapping_add(1)) as *mut libc::c_char;
|
||||
let mut pbuf: *mut libc::c_char = buf;
|
||||
assert!(!buf.is_null());
|
||||
|
||||
while 0 != *pstr {
|
||||
if *pstr as libc::c_int == '%' as i32 {
|
||||
if 0 != *pstr.offset(1isize) as libc::c_int && 0 != *pstr.offset(2isize) as libc::c_int
|
||||
{
|
||||
let fresh5 = pbuf;
|
||||
pbuf = pbuf.offset(1);
|
||||
*fresh5 = ((hex_2_int(*pstr.offset(1isize)) as libc::c_int) << 4i32
|
||||
| hex_2_int(*pstr.offset(2isize)) as libc::c_int)
|
||||
as libc::c_char;
|
||||
pstr = pstr.offset(2isize)
|
||||
}
|
||||
} else if *pstr as libc::c_int == '+' as i32 {
|
||||
let fresh6 = pbuf;
|
||||
pbuf = pbuf.offset(1);
|
||||
*fresh6 = ' ' as i32 as libc::c_char
|
||||
} else {
|
||||
let fresh7 = pbuf;
|
||||
pbuf = pbuf.offset(1);
|
||||
*fresh7 = *pstr
|
||||
}
|
||||
pstr = pstr.offset(1isize)
|
||||
}
|
||||
*pbuf = '\u{0}' as i32 as libc::c_char;
|
||||
|
||||
buf
|
||||
}
|
||||
|
||||
fn hex_2_int(ch: libc::c_char) -> libc::c_char {
|
||||
let ch = ch as u8 as char;
|
||||
if !ch.is_ascii_hexdigit() {
|
||||
return (ch.to_ascii_lowercase() as i32 - 'a' as i32 + 10) as libc::c_char;
|
||||
}
|
||||
|
||||
match ch.to_digit(16) {
|
||||
Some(res) => res as libc::c_char,
|
||||
None => 0,
|
||||
}
|
||||
}
|
||||
|
||||
pub unsafe fn dc_encode_header_words(to_encode: *const libc::c_char) -> *mut libc::c_char {
|
||||
let mut ok_to_continue = true;
|
||||
let mut ret_str: *mut libc::c_char = 0 as *mut libc::c_char;
|
||||
let mut ret_str: *mut libc::c_char = ptr::null_mut();
|
||||
let mut cur: *const libc::c_char = to_encode;
|
||||
let mmapstr: *mut MMAPString = mmap_string_new(b"\x00" as *const u8 as *const libc::c_char);
|
||||
if to_encode.is_null() || mmapstr.is_null() {
|
||||
@@ -270,9 +205,9 @@ unsafe fn to_be_quoted(word: *const libc::c_char, size: size_t) -> bool {
|
||||
|
||||
pub unsafe fn dc_decode_header_words(in_0: *const libc::c_char) -> *mut libc::c_char {
|
||||
if in_0.is_null() {
|
||||
return 0 as *mut libc::c_char;
|
||||
return ptr::null_mut();
|
||||
}
|
||||
let mut out: *mut libc::c_char = 0 as *mut libc::c_char;
|
||||
let mut out: *mut libc::c_char = ptr::null_mut();
|
||||
let mut cur_token: size_t = 0i32 as size_t;
|
||||
let r: libc::c_int = mailmime_encoded_phrase_parse(
|
||||
b"iso-8859-1\x00" as *const u8 as *const libc::c_char,
|
||||
@@ -289,374 +224,63 @@ pub unsafe fn dc_decode_header_words(in_0: *const libc::c_char) -> *mut libc::c_
|
||||
out
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
unsafe fn dc_encode_modified_utf7(
|
||||
mut to_encode: *const libc::c_char,
|
||||
change_spaces: libc::c_int,
|
||||
) -> *mut libc::c_char {
|
||||
let mut utf8pos: libc::c_uint;
|
||||
let mut utf8total: libc::c_uint;
|
||||
let mut c: libc::c_uint;
|
||||
let mut utf7mode: libc::c_uint;
|
||||
let mut bitstogo: libc::c_uint;
|
||||
let mut utf16flag: libc::c_uint;
|
||||
let mut ucs4: libc::c_ulong = 0;
|
||||
let mut bitbuf: libc::c_ulong = 0;
|
||||
let mut dst: *mut libc::c_char;
|
||||
let res: *mut libc::c_char;
|
||||
if to_encode.is_null() {
|
||||
return dc_strdup(b"\x00" as *const u8 as *const libc::c_char);
|
||||
}
|
||||
res = malloc(2usize.wrapping_mul(strlen(to_encode)).wrapping_add(1)) as *mut libc::c_char;
|
||||
dst = res;
|
||||
assert!(!dst.is_null());
|
||||
pub fn dc_needs_ext_header(to_check: impl AsRef<str>) -> bool {
|
||||
let to_check = to_check.as_ref();
|
||||
|
||||
utf7mode = 0i32 as libc::c_uint;
|
||||
utf8total = 0i32 as libc::c_uint;
|
||||
bitstogo = 0i32 as libc::c_uint;
|
||||
utf8pos = 0i32 as libc::c_uint;
|
||||
loop {
|
||||
c = *to_encode as libc::c_uchar as libc::c_uint;
|
||||
if !(c != '\u{0}' as i32 as libc::c_uint) {
|
||||
break;
|
||||
}
|
||||
to_encode = to_encode.offset(1isize);
|
||||
// normal character?
|
||||
if c >= ' ' as i32 as libc::c_uint
|
||||
&& c <= '~' as i32 as libc::c_uint
|
||||
&& (c != '_' as i32 as libc::c_uint || 0 == change_spaces)
|
||||
{
|
||||
if 0 != utf7mode {
|
||||
if 0 != bitstogo {
|
||||
let fresh8 = dst;
|
||||
dst = dst.offset(1);
|
||||
*fresh8 = BASE64CHARS
|
||||
[(bitbuf << (6i32 as libc::c_uint).wrapping_sub(bitstogo) & 0x3f) as usize]
|
||||
}
|
||||
let fresh9 = dst;
|
||||
dst = dst.offset(1);
|
||||
*fresh9 = '-' as i32 as libc::c_char;
|
||||
utf7mode = 0i32 as libc::c_uint;
|
||||
utf8pos = 0i32 as libc::c_uint;
|
||||
bitstogo = 0i32 as libc::c_uint;
|
||||
utf8total = 0i32 as libc::c_uint
|
||||
}
|
||||
if 0 != change_spaces && c == ' ' as i32 as libc::c_uint {
|
||||
let fresh10 = dst;
|
||||
dst = dst.offset(1);
|
||||
*fresh10 = '_' as i32 as libc::c_char
|
||||
} else {
|
||||
let fresh11 = dst;
|
||||
dst = dst.offset(1);
|
||||
*fresh11 = c as libc::c_char
|
||||
}
|
||||
if c == '&' as i32 as libc::c_uint {
|
||||
let fresh12 = dst;
|
||||
dst = dst.offset(1);
|
||||
*fresh12 = '-' as i32 as libc::c_char
|
||||
}
|
||||
} else {
|
||||
if 0 == utf7mode {
|
||||
let fresh13 = dst;
|
||||
dst = dst.offset(1);
|
||||
*fresh13 = '&' as i32 as libc::c_char;
|
||||
utf7mode = 1i32 as libc::c_uint
|
||||
}
|
||||
// encode ascii characters as themselves
|
||||
if c < 0x80i32 as libc::c_uint {
|
||||
ucs4 = c as libc::c_ulong
|
||||
} else if 0 != utf8total {
|
||||
ucs4 = ucs4 << 6i32 | c as libc::c_ulong & 0x3f;
|
||||
utf8pos = utf8pos.wrapping_add(1);
|
||||
if utf8pos < utf8total {
|
||||
continue;
|
||||
}
|
||||
} else {
|
||||
utf8pos = 1i32 as libc::c_uint;
|
||||
if c < 0xe0i32 as libc::c_uint {
|
||||
utf8total = 2i32 as libc::c_uint;
|
||||
ucs4 = (c & 0x1fi32 as libc::c_uint) as libc::c_ulong
|
||||
} else if c < 0xf0i32 as libc::c_uint {
|
||||
utf8total = 3i32 as libc::c_uint;
|
||||
ucs4 = (c & 0xfi32 as libc::c_uint) as libc::c_ulong
|
||||
} else {
|
||||
utf8total = 4i32 as libc::c_uint;
|
||||
ucs4 = (c & 0x3i32 as libc::c_uint) as libc::c_ulong
|
||||
}
|
||||
continue;
|
||||
}
|
||||
utf8total = 0i32 as libc::c_uint;
|
||||
loop {
|
||||
if ucs4 >= 0x10000 {
|
||||
ucs4 = ucs4.wrapping_sub(0x10000);
|
||||
bitbuf = bitbuf << 16 | (ucs4 >> 10).wrapping_add(0xd800);
|
||||
ucs4 = (ucs4 & 0x3ff).wrapping_add(0xdc00);
|
||||
utf16flag = 1i32 as libc::c_uint
|
||||
} else {
|
||||
bitbuf = bitbuf << 16 | ucs4;
|
||||
utf16flag = 0i32 as libc::c_uint
|
||||
}
|
||||
bitstogo = bitstogo.wrapping_add(16i32 as libc::c_uint);
|
||||
while bitstogo >= 6i32 as libc::c_uint {
|
||||
bitstogo = bitstogo.wrapping_sub(6i32 as libc::c_uint);
|
||||
let fresh14 = dst;
|
||||
dst = dst.offset(1);
|
||||
*fresh14 = BASE64CHARS[(if 0 != bitstogo {
|
||||
bitbuf >> bitstogo
|
||||
} else {
|
||||
bitbuf
|
||||
} & 0x3f) as usize]
|
||||
}
|
||||
if !(0 != utf16flag) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
if to_check.is_empty() {
|
||||
return false;
|
||||
}
|
||||
if 0 != utf7mode {
|
||||
if 0 != bitstogo {
|
||||
let fresh15 = dst;
|
||||
dst = dst.offset(1);
|
||||
*fresh15 = BASE64CHARS
|
||||
[(bitbuf << (6i32 as libc::c_uint).wrapping_sub(bitstogo) & 0x3f) as usize]
|
||||
}
|
||||
let fresh16 = dst;
|
||||
dst = dst.offset(1);
|
||||
*fresh16 = '-' as i32 as libc::c_char
|
||||
}
|
||||
*dst = '\u{0}' as i32 as libc::c_char;
|
||||
|
||||
res
|
||||
to_check.chars().any(|c| {
|
||||
!(c.is_ascii_alphanumeric()
|
||||
|| c == '-'
|
||||
|| c == '_'
|
||||
|| c == '_'
|
||||
|| c == '.'
|
||||
|| c == '~'
|
||||
|| c == '%')
|
||||
})
|
||||
}
|
||||
|
||||
/* ******************************************************************************
|
||||
* Encode/decode modified UTF-7 as needed for IMAP, see RFC 2192
|
||||
******************************************************************************/
|
||||
const EXT_ASCII_ST: &AsciiSet = &CONTROLS
|
||||
.add(b' ')
|
||||
.add(b'-')
|
||||
.add(b'_')
|
||||
.add(b'.')
|
||||
.add(b'~')
|
||||
.add(b'%');
|
||||
|
||||
// UTF7 modified base64 alphabet
|
||||
#[cfg(test)]
|
||||
static mut BASE64CHARS: [libc::c_char; 65] = [
|
||||
65, 66, 67, 68, 69, 70, 71, 72, 73, 74, 75, 76, 77, 78, 79, 80, 81, 82, 83, 84, 85, 86, 87, 88,
|
||||
89, 90, 97, 98, 99, 100, 101, 102, 103, 104, 105, 106, 107, 108, 109, 110, 111, 112, 113, 114,
|
||||
115, 116, 117, 118, 119, 120, 121, 122, 48, 49, 50, 51, 52, 53, 54, 55, 56, 57, 43, 44, 0,
|
||||
];
|
||||
|
||||
#[cfg(test)]
|
||||
unsafe fn dc_decode_modified_utf7(
|
||||
to_decode: *const libc::c_char,
|
||||
change_spaces: libc::c_int,
|
||||
) -> *mut libc::c_char {
|
||||
let mut c: libc::c_uint;
|
||||
let mut i: libc::c_uint;
|
||||
let mut bitcount: libc::c_uint;
|
||||
let mut ucs4: libc::c_ulong;
|
||||
let mut utf16: libc::c_ulong;
|
||||
let mut bitbuf: libc::c_ulong;
|
||||
let mut base64: [libc::c_uchar; 256] = [0; 256];
|
||||
let mut src: *const libc::c_char;
|
||||
let mut dst: *mut libc::c_char;
|
||||
let res: *mut libc::c_char;
|
||||
if to_decode.is_null() {
|
||||
return dc_strdup(b"\x00" as *const u8 as *const libc::c_char);
|
||||
}
|
||||
res = malloc(4usize.wrapping_mul(strlen(to_decode)).wrapping_add(1)) as *mut libc::c_char;
|
||||
dst = res;
|
||||
src = to_decode;
|
||||
assert!(!dst.is_null());
|
||||
|
||||
libc::memset(
|
||||
base64.as_mut_ptr() as *mut libc::c_void,
|
||||
64,
|
||||
::std::mem::size_of::<[libc::c_uchar; 256]>(),
|
||||
);
|
||||
i = 0i32 as libc::c_uint;
|
||||
while (i as libc::c_ulong) < ::std::mem::size_of::<[libc::c_char; 65]>() as libc::c_ulong {
|
||||
base64[BASE64CHARS[i as usize] as libc::c_uint as usize] = i as libc::c_uchar;
|
||||
i = i.wrapping_add(1)
|
||||
}
|
||||
while *src as libc::c_int != '\u{0}' as i32 {
|
||||
let fresh17 = src;
|
||||
src = src.offset(1);
|
||||
c = *fresh17 as libc::c_uint;
|
||||
if c != '&' as i32 as libc::c_uint || *src as libc::c_int == '-' as i32 {
|
||||
if 0 != change_spaces && c == '_' as i32 as libc::c_uint {
|
||||
let fresh18 = dst;
|
||||
dst = dst.offset(1);
|
||||
*fresh18 = ' ' as i32 as libc::c_char
|
||||
} else {
|
||||
let fresh19 = dst;
|
||||
dst = dst.offset(1);
|
||||
*fresh19 = c as libc::c_char
|
||||
}
|
||||
if c == '&' as i32 as libc::c_uint {
|
||||
src = src.offset(1isize)
|
||||
}
|
||||
} else {
|
||||
bitbuf = 0;
|
||||
bitcount = 0i32 as libc::c_uint;
|
||||
ucs4 = 0;
|
||||
loop {
|
||||
c = base64[*src as libc::c_uchar as usize] as libc::c_uint;
|
||||
if !(c != 64i32 as libc::c_uint) {
|
||||
break;
|
||||
}
|
||||
src = src.offset(1isize);
|
||||
bitbuf = bitbuf << 6i32 | c as libc::c_ulong;
|
||||
bitcount = bitcount.wrapping_add(6i32 as libc::c_uint);
|
||||
// enough bits for a UTF-16 character?
|
||||
if !(bitcount >= 16i32 as libc::c_uint) {
|
||||
continue;
|
||||
}
|
||||
bitcount = bitcount.wrapping_sub(16i32 as libc::c_uint);
|
||||
utf16 = if 0 != bitcount {
|
||||
bitbuf >> bitcount
|
||||
} else {
|
||||
bitbuf
|
||||
} & 0xffff;
|
||||
|
||||
// convert UTF16 to UCS4
|
||||
if utf16 >= 0xd800 && utf16 <= 0xdbff {
|
||||
ucs4 = utf16.wrapping_sub(0xd800) << 10i32
|
||||
} else {
|
||||
if utf16 >= 0xdc00 && utf16 <= 0xdfff {
|
||||
ucs4 = ucs4.wrapping_add(utf16.wrapping_sub(0xdc00).wrapping_add(0x10000))
|
||||
} else {
|
||||
ucs4 = utf16
|
||||
}
|
||||
if ucs4 <= 0x7f {
|
||||
*dst.offset(0isize) = ucs4 as libc::c_char;
|
||||
dst = dst.offset(1isize)
|
||||
} else if ucs4 <= 0x7ff {
|
||||
*dst.offset(0isize) = (0xc0 | ucs4 >> 6i32) as libc::c_char;
|
||||
*dst.offset(1isize) = (0x80 | ucs4 & 0x3f) as libc::c_char;
|
||||
dst = dst.offset(2isize)
|
||||
} else if ucs4 <= 0xffff {
|
||||
*dst.offset(0isize) = (0xe0 | ucs4 >> 12i32) as libc::c_char;
|
||||
*dst.offset(1isize) = (0x80 | ucs4 >> 6i32 & 0x3f) as libc::c_char;
|
||||
*dst.offset(2isize) = (0x80 | ucs4 & 0x3f) as libc::c_char;
|
||||
dst = dst.offset(3isize)
|
||||
} else {
|
||||
*dst.offset(0isize) = (0xf0 | ucs4 >> 18i32) as libc::c_char;
|
||||
*dst.offset(1isize) = (0x80 | ucs4 >> 12i32 & 0x3f) as libc::c_char;
|
||||
*dst.offset(2isize) = (0x80 | ucs4 >> 6i32 & 0x3f) as libc::c_char;
|
||||
*dst.offset(3isize) = (0x80 | ucs4 & 0x3f) as libc::c_char;
|
||||
dst = dst.offset(4isize)
|
||||
}
|
||||
}
|
||||
}
|
||||
if *src as libc::c_int == '-' as i32 {
|
||||
src = src.offset(1isize)
|
||||
}
|
||||
}
|
||||
}
|
||||
*dst = '\u{0}' as i32 as libc::c_char;
|
||||
|
||||
res
|
||||
/// Encode an UTF-8 string to the extended header format.
|
||||
pub fn dc_encode_ext_header(to_encode: impl AsRef<str>) -> String {
|
||||
let encoded = utf8_percent_encode(to_encode.as_ref(), &EXT_ASCII_ST);
|
||||
format!("utf-8''{}", encoded)
|
||||
}
|
||||
|
||||
pub unsafe fn dc_needs_ext_header(mut to_check: *const libc::c_char) -> bool {
|
||||
if !to_check.is_null() {
|
||||
while 0 != *to_check {
|
||||
if 0 == isalnum(*to_check as libc::c_int)
|
||||
&& *to_check as libc::c_int != '-' as i32
|
||||
&& *to_check as libc::c_int != '_' as i32
|
||||
&& *to_check as libc::c_int != '.' as i32
|
||||
&& *to_check as libc::c_int != '~' as i32
|
||||
{
|
||||
return true;
|
||||
}
|
||||
to_check = to_check.offset(1isize)
|
||||
}
|
||||
}
|
||||
|
||||
false
|
||||
}
|
||||
|
||||
pub unsafe fn dc_encode_ext_header(to_encode: *const libc::c_char) -> *mut libc::c_char {
|
||||
let mut pstr: *const libc::c_char = to_encode;
|
||||
if to_encode.is_null() {
|
||||
return dc_strdup(b"utf-8\'\'\x00" as *const u8 as *const libc::c_char);
|
||||
}
|
||||
let buf: *mut libc::c_char = malloc(
|
||||
strlen(b"utf-8\'\'\x00" as *const u8 as *const libc::c_char)
|
||||
.wrapping_add(strlen(to_encode).wrapping_mul(3))
|
||||
.wrapping_add(1),
|
||||
) as *mut libc::c_char;
|
||||
assert!(!buf.is_null());
|
||||
|
||||
let mut pbuf: *mut libc::c_char = buf;
|
||||
strcpy(pbuf, b"utf-8\'\'\x00" as *const u8 as *const libc::c_char);
|
||||
pbuf = pbuf.offset(strlen(pbuf) as isize);
|
||||
while 0 != *pstr {
|
||||
if 0 != isalnum(*pstr as libc::c_int)
|
||||
|| *pstr as libc::c_int == '-' as i32
|
||||
|| *pstr as libc::c_int == '_' as i32
|
||||
|| *pstr as libc::c_int == '.' as i32
|
||||
|| *pstr as libc::c_int == '~' as i32
|
||||
{
|
||||
let fresh20 = pbuf;
|
||||
pbuf = pbuf.offset(1);
|
||||
*fresh20 = *pstr
|
||||
} else {
|
||||
let fresh21 = pbuf;
|
||||
pbuf = pbuf.offset(1);
|
||||
*fresh21 = '%' as i32 as libc::c_char;
|
||||
let fresh22 = pbuf;
|
||||
pbuf = pbuf.offset(1);
|
||||
*fresh22 = int_2_uppercase_hex((*pstr as libc::c_int >> 4i32) as libc::c_char);
|
||||
let fresh23 = pbuf;
|
||||
pbuf = pbuf.offset(1);
|
||||
*fresh23 = int_2_uppercase_hex((*pstr as libc::c_int & 15i32) as libc::c_char)
|
||||
}
|
||||
pstr = pstr.offset(1isize)
|
||||
}
|
||||
*pbuf = '\u{0}' as i32 as libc::c_char;
|
||||
|
||||
buf
|
||||
}
|
||||
|
||||
pub unsafe fn dc_decode_ext_header(to_decode: *const libc::c_char) -> *mut libc::c_char {
|
||||
let mut decoded: *mut libc::c_char = 0 as *mut libc::c_char;
|
||||
let mut charset: *mut libc::c_char = 0 as *mut libc::c_char;
|
||||
let mut p2: *const libc::c_char;
|
||||
if !to_decode.is_null() {
|
||||
// get char set
|
||||
p2 = strchr(to_decode, '\'' as i32);
|
||||
if !(p2.is_null() || p2 == to_decode) {
|
||||
/*no empty charset allowed*/
|
||||
charset =
|
||||
dc_null_terminate(to_decode, p2.wrapping_offset_from(to_decode) as libc::c_int);
|
||||
p2 = p2.offset(1isize);
|
||||
/// Decode an extended-header-format strings to UTF-8.
|
||||
pub fn dc_decode_ext_header(to_decode: &[u8]) -> Cow<str> {
|
||||
if let Some(index) = bytes!(b'\'').find(to_decode) {
|
||||
let (charset, rest) = to_decode.split_at(index);
|
||||
if !charset.is_empty() {
|
||||
// skip language
|
||||
p2 = strchr(p2, '\'' as i32);
|
||||
if !p2.is_null() {
|
||||
p2 = p2.offset(1isize);
|
||||
decoded = dc_urldecode(p2);
|
||||
if !charset.is_null()
|
||||
&& strcmp(charset, b"utf-8\x00" as *const u8 as *const libc::c_char) != 0i32
|
||||
&& 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())
|
||||
{
|
||||
let data =
|
||||
std::slice::from_raw_parts(decoded as *const u8, strlen(decoded));
|
||||
if let Some(index2) = bytes!(b'\'').find(&rest[1..]) {
|
||||
let decoded = percent_decode(&rest[index2 + 2..]);
|
||||
|
||||
let (res, _, _) = encoding.decode(data);
|
||||
free(decoded as *mut _);
|
||||
let r = std::ffi::CString::new(res.as_bytes()).unwrap();
|
||||
decoded = dc_strdup(r.as_ptr());
|
||||
if charset != b"utf-8" && charset != b"UTF-8" {
|
||||
if let Some(encoding) = Charset::for_label(charset) {
|
||||
let bytes = decoded.collect::<Vec<u8>>();
|
||||
let (res, _, _) = encoding.decode(&bytes);
|
||||
return Cow::Owned(res.into_owned());
|
||||
} else {
|
||||
return decoded.decode_utf8_lossy();
|
||||
}
|
||||
} else {
|
||||
return decoded.decode_utf8_lossy();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
free(charset as *mut libc::c_void);
|
||||
if !decoded.is_null() {
|
||||
decoded
|
||||
} else {
|
||||
dc_strdup(to_decode)
|
||||
}
|
||||
|
||||
String::from_utf8_lossy(to_decode)
|
||||
}
|
||||
|
||||
unsafe fn print_hex(target: *mut libc::c_char, cur: *const libc::c_char) {
|
||||
@@ -671,16 +295,8 @@ unsafe fn print_hex(target: *mut libc::c_char, cur: *const libc::c_char) {
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
use percent_encoding::{percent_encode, NON_ALPHANUMERIC};
|
||||
use std::ffi::CStr;
|
||||
|
||||
#[test]
|
||||
fn test_isalnum() {
|
||||
assert_eq!(isalnum(0), 0);
|
||||
assert_eq!(isalnum('5' as libc::c_int), 1);
|
||||
assert_eq!(isalnum('Q' as libc::c_int), 1);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_dc_decode_header_words() {
|
||||
unsafe {
|
||||
@@ -741,99 +357,43 @@ mod tests {
|
||||
|
||||
#[test]
|
||||
fn test_dc_encode_ext_header() {
|
||||
unsafe {
|
||||
let mut buf1 = dc_encode_ext_header(
|
||||
b"Bj\xc3\xb6rn Petersen\x00" as *const u8 as *const libc::c_char,
|
||||
);
|
||||
assert_eq!(
|
||||
CStr::from_ptr(buf1).to_str().unwrap(),
|
||||
"utf-8\'\'Bj%C3%B6rn%20Petersen"
|
||||
);
|
||||
let buf2 = dc_decode_ext_header(buf1);
|
||||
assert_eq!(
|
||||
strcmp(
|
||||
buf2,
|
||||
b"Bj\xc3\xb6rn Petersen\x00" as *const u8 as *const libc::c_char,
|
||||
),
|
||||
0
|
||||
);
|
||||
free(buf1 as *mut libc::c_void);
|
||||
free(buf2 as *mut libc::c_void);
|
||||
let buf1 = dc_encode_ext_header("Björn Petersen");
|
||||
assert_eq!(&buf1, "utf-8\'\'Bj%C3%B6rn%20Petersen");
|
||||
let buf2 = dc_decode_ext_header(buf1.as_bytes());
|
||||
assert_eq!(&buf2, "Björn Petersen",);
|
||||
|
||||
buf1 = dc_decode_ext_header(
|
||||
b"iso-8859-1\'en\'%A3%20rates\x00" as *const u8 as *const libc::c_char,
|
||||
);
|
||||
assert_eq!(
|
||||
strcmp(
|
||||
buf1,
|
||||
b"\xc2\xa3 rates\x00" as *const u8 as *const libc::c_char,
|
||||
),
|
||||
0
|
||||
);
|
||||
free(buf1 as *mut libc::c_void);
|
||||
let buf1 = dc_decode_ext_header(b"iso-8859-1\'en\'%A3%20rates");
|
||||
assert_eq!(buf1, "£ rates",);
|
||||
|
||||
buf1 = dc_decode_ext_header(b"wrong\'format\x00" as *const u8 as *const libc::c_char);
|
||||
assert_eq!(
|
||||
strcmp(
|
||||
buf1,
|
||||
b"wrong\'format\x00" as *const u8 as *const libc::c_char,
|
||||
),
|
||||
0
|
||||
);
|
||||
free(buf1 as *mut libc::c_void);
|
||||
let buf1 = dc_decode_ext_header(b"wrong\'format");
|
||||
assert_eq!(buf1, "wrong\'format",);
|
||||
|
||||
buf1 = dc_decode_ext_header(b"\'\'\x00" as *const u8 as *const libc::c_char);
|
||||
assert_eq!(
|
||||
strcmp(buf1, b"\'\'\x00" as *const u8 as *const libc::c_char),
|
||||
0
|
||||
);
|
||||
free(buf1 as *mut libc::c_void);
|
||||
let buf1 = dc_decode_ext_header(b"\'\'");
|
||||
assert_eq!(buf1, "\'\'");
|
||||
|
||||
buf1 = dc_decode_ext_header(b"x\'\'\x00" as *const u8 as *const libc::c_char);
|
||||
assert_eq!(strcmp(buf1, b"\x00" as *const u8 as *const libc::c_char), 0);
|
||||
free(buf1 as *mut libc::c_void);
|
||||
let buf1 = dc_decode_ext_header(b"x\'\'");
|
||||
assert_eq!(buf1, "");
|
||||
|
||||
buf1 = dc_decode_ext_header(b"\'\x00" as *const u8 as *const libc::c_char);
|
||||
assert_eq!(
|
||||
strcmp(buf1, b"\'\x00" as *const u8 as *const libc::c_char),
|
||||
0
|
||||
);
|
||||
free(buf1 as *mut libc::c_void);
|
||||
let buf1 = dc_decode_ext_header(b"\'");
|
||||
assert_eq!(buf1, "\'");
|
||||
|
||||
buf1 = dc_decode_ext_header(b"\x00" as *const u8 as *const libc::c_char);
|
||||
assert_eq!(strcmp(buf1, b"\x00" as *const u8 as *const libc::c_char), 0);
|
||||
free(buf1 as *mut libc::c_void);
|
||||
}
|
||||
let buf1 = dc_decode_ext_header(b"");
|
||||
assert_eq!(buf1, "");
|
||||
|
||||
// regressions
|
||||
assert_eq!(
|
||||
dc_decode_ext_header(dc_encode_ext_header("%0A").as_bytes()),
|
||||
"%0A"
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_dc_needs_ext_header() {
|
||||
unsafe {
|
||||
assert_eq!(
|
||||
dc_needs_ext_header(b"Bj\xc3\xb6rn\x00" as *const u8 as *const libc::c_char),
|
||||
true
|
||||
);
|
||||
assert_eq!(
|
||||
dc_needs_ext_header(b"Bjoern\x00" as *const u8 as *const libc::c_char),
|
||||
false
|
||||
);
|
||||
assert_eq!(
|
||||
dc_needs_ext_header(b"\x00" as *const u8 as *const libc::c_char),
|
||||
false
|
||||
);
|
||||
assert_eq!(
|
||||
dc_needs_ext_header(b" \x00" as *const u8 as *const libc::c_char),
|
||||
true
|
||||
);
|
||||
assert_eq!(
|
||||
dc_needs_ext_header(b"a b\x00" as *const u8 as *const libc::c_char),
|
||||
true
|
||||
);
|
||||
assert_eq!(
|
||||
dc_needs_ext_header(0 as *const u8 as *const libc::c_char),
|
||||
false
|
||||
);
|
||||
}
|
||||
assert_eq!(dc_needs_ext_header("Björn"), true);
|
||||
assert_eq!(dc_needs_ext_header("Bjoern"), false);
|
||||
assert_eq!(dc_needs_ext_header(""), false);
|
||||
assert_eq!(dc_needs_ext_header(" "), true);
|
||||
assert_eq!(dc_needs_ext_header("a b"), true);
|
||||
}
|
||||
|
||||
#[test]
|
||||
@@ -848,69 +408,20 @@ mod tests {
|
||||
assert_eq!(to_string(hex.as_ptr() as *const _), "=3A");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_dc_urlencode_urldecode() {
|
||||
unsafe {
|
||||
let buf1 = percent_encode(b"Bj\xc3\xb6rn Petersen", NON_ALPHANUMERIC)
|
||||
.to_string()
|
||||
.strdup();
|
||||
use proptest::prelude::*;
|
||||
|
||||
assert_eq!(
|
||||
CStr::from_ptr(buf1 as *const libc::c_char)
|
||||
.to_str()
|
||||
.unwrap(),
|
||||
"Bj%C3%B6rn%20Petersen"
|
||||
);
|
||||
|
||||
let buf2 = dc_urldecode(buf1);
|
||||
|
||||
assert_eq!(
|
||||
strcmp(
|
||||
buf2,
|
||||
b"Bj\xc3\xb6rn Petersen\x00" as *const u8 as *const libc::c_char
|
||||
),
|
||||
0
|
||||
);
|
||||
|
||||
free(buf1 as *mut libc::c_void);
|
||||
free(buf2 as *mut libc::c_void);
|
||||
proptest! {
|
||||
#[test]
|
||||
fn test_ext_header_roundtrip(buf: String) {
|
||||
let encoded = dc_encode_ext_header(&buf);
|
||||
let decoded = dc_decode_ext_header(encoded.as_bytes());
|
||||
assert_eq!(buf, decoded);
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_dc_encode_decode_modified_utf7() {
|
||||
unsafe {
|
||||
let buf1 = dc_encode_modified_utf7(
|
||||
b"Bj\xc3\xb6rn Petersen\x00" as *const u8 as *const libc::c_char,
|
||||
1,
|
||||
);
|
||||
|
||||
assert_eq!(
|
||||
CStr::from_ptr(buf1 as *const libc::c_char)
|
||||
.to_str()
|
||||
.unwrap(),
|
||||
"Bj&APY-rn_Petersen"
|
||||
);
|
||||
|
||||
let buf2 = dc_decode_modified_utf7(buf1, 1);
|
||||
|
||||
assert_eq!(
|
||||
strcmp(
|
||||
buf2,
|
||||
b"Bj\xc3\xb6rn Petersen\x00" as *const u8 as *const libc::c_char
|
||||
),
|
||||
0
|
||||
);
|
||||
|
||||
free(buf1 as *mut libc::c_void);
|
||||
free(buf2 as *mut libc::c_void);
|
||||
#[test]
|
||||
fn test_ext_header_decode_anything(buf: Vec<u8>) {
|
||||
// make sure this never panics
|
||||
let _decoded = dc_decode_ext_header(&buf);
|
||||
}
|
||||
}
|
||||
#[test]
|
||||
fn test_hex_to_int() {
|
||||
assert_eq!(hex_2_int(b'A' as libc::c_char), 10);
|
||||
assert_eq!(hex_2_int(b'a' as libc::c_char), 10);
|
||||
assert_eq!(hex_2_int(b'4' as libc::c_char), 4);
|
||||
assert_eq!(hex_2_int(b'K' as libc::c_char), 20);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,65 +0,0 @@
|
||||
use crate::context::Context;
|
||||
use crate::dc_tools::*;
|
||||
use crate::sql;
|
||||
|
||||
// Token namespaces
|
||||
#[allow(non_camel_case_types)]
|
||||
type dc_tokennamespc_t = usize;
|
||||
pub const DC_TOKEN_AUTH: dc_tokennamespc_t = 110;
|
||||
pub const DC_TOKEN_INVITENUMBER: dc_tokennamespc_t = 100;
|
||||
|
||||
// Functions to read/write token from/to the database. A token is any string associated with a key.
|
||||
|
||||
pub fn dc_token_save(
|
||||
context: &Context,
|
||||
namespc: dc_tokennamespc_t,
|
||||
foreign_id: u32,
|
||||
token: *const libc::c_char,
|
||||
) -> bool {
|
||||
if token.is_null() {
|
||||
return false;
|
||||
}
|
||||
// foreign_id may be 0
|
||||
sql::execute(
|
||||
context,
|
||||
&context.sql,
|
||||
"INSERT INTO tokens (namespc, foreign_id, token, timestamp) VALUES (?, ?, ?, ?);",
|
||||
params![namespc as i32, foreign_id as i32, as_str(token), time()],
|
||||
)
|
||||
.is_ok()
|
||||
}
|
||||
|
||||
pub fn dc_token_lookup(
|
||||
context: &Context,
|
||||
namespc: dc_tokennamespc_t,
|
||||
foreign_id: u32,
|
||||
) -> *mut libc::c_char {
|
||||
context
|
||||
.sql
|
||||
.query_row_col::<_, String>(
|
||||
context,
|
||||
"SELECT token FROM tokens WHERE namespc=? AND foreign_id=?;",
|
||||
params![namespc as i32, foreign_id as i32],
|
||||
0,
|
||||
)
|
||||
.map(|s| unsafe { s.strdup() })
|
||||
.unwrap_or_else(|| std::ptr::null_mut())
|
||||
}
|
||||
|
||||
pub fn dc_token_exists(
|
||||
context: &Context,
|
||||
namespc: dc_tokennamespc_t,
|
||||
token: *const libc::c_char,
|
||||
) -> bool {
|
||||
if token.is_null() {
|
||||
return false;
|
||||
}
|
||||
|
||||
context
|
||||
.sql
|
||||
.exists(
|
||||
"SELECT id FROM tokens WHERE namespc=? AND token=?;",
|
||||
params![namespc as i32, as_str(token)],
|
||||
)
|
||||
.unwrap_or_default()
|
||||
}
|
||||
227
src/dc_tools.rs
227
src/dc_tools.rs
@@ -8,13 +8,14 @@ use std::{fmt, fs, ptr};
|
||||
use chrono::{Local, TimeZone};
|
||||
use mmime::mailimf_types::*;
|
||||
use rand::{thread_rng, Rng};
|
||||
use itertools::max;
|
||||
|
||||
use crate::context::Context;
|
||||
use crate::error::Error;
|
||||
use crate::types::*;
|
||||
use crate::x::*;
|
||||
|
||||
use itertools::max;
|
||||
|
||||
/* Some tools and enhancements to the used libraries, there should be
|
||||
no references to Context and other "larger" classes here. */
|
||||
/* ** library-private **********************************************************/
|
||||
@@ -82,14 +83,6 @@ pub unsafe fn dc_atoi_null_is_0(s: *const libc::c_char) -> libc::c_int {
|
||||
}
|
||||
}
|
||||
|
||||
pub fn dc_atof(s: *const libc::c_char) -> libc::c_double {
|
||||
if s.is_null() {
|
||||
return 0.;
|
||||
}
|
||||
|
||||
as_str(s).parse().unwrap_or_default()
|
||||
}
|
||||
|
||||
pub unsafe fn dc_str_replace(
|
||||
haystack: *mut *mut libc::c_char,
|
||||
needle: *const libc::c_char,
|
||||
@@ -104,21 +97,6 @@ pub unsafe fn dc_str_replace(
|
||||
*haystack = haystack_s.replace(&needle_s, &replacement_s).strdup();
|
||||
}
|
||||
|
||||
pub unsafe fn dc_ftoa(f: libc::c_double) -> *mut libc::c_char {
|
||||
// hack around printf(%f) that may return `,` as decimal point on mac
|
||||
let test: *mut libc::c_char = dc_mprintf(b"%f\x00" as *const u8 as *const libc::c_char, 1.2f64);
|
||||
*test.offset(2isize) = 0 as libc::c_char;
|
||||
let mut str: *mut libc::c_char = dc_mprintf(b"%f\x00" as *const u8 as *const libc::c_char, f);
|
||||
dc_str_replace(
|
||||
&mut str,
|
||||
test.offset(1isize),
|
||||
b".\x00" as *const u8 as *const libc::c_char,
|
||||
);
|
||||
free(test as *mut libc::c_void);
|
||||
|
||||
str
|
||||
}
|
||||
|
||||
unsafe fn dc_ltrim(buf: *mut libc::c_char) {
|
||||
let mut len: size_t;
|
||||
let mut cur: *const libc::c_uchar;
|
||||
@@ -283,11 +261,18 @@ pub unsafe fn dc_replace_bad_utf8_chars(buf: *mut libc::c_char) {
|
||||
pub fn dc_truncate(buf: &str, approx_chars: usize, do_unwrap: bool) -> Cow<str> {
|
||||
let ellipse = if do_unwrap { "..." } else { "[...]" };
|
||||
|
||||
if approx_chars > 0 && buf.len() > approx_chars + ellipse.len() {
|
||||
if let Some(index) = buf[..approx_chars].rfind(|c| c == ' ' || c == '\n') {
|
||||
let count = buf.chars().count();
|
||||
if approx_chars > 0 && count > approx_chars + ellipse.len() {
|
||||
let end_pos = buf
|
||||
.char_indices()
|
||||
.nth(approx_chars)
|
||||
.map(|(n, _)| n)
|
||||
.unwrap_or_default();
|
||||
|
||||
if let Some(index) = buf[..end_pos].rfind(|c| c == ' ' || c == '\n') {
|
||||
Cow::Owned(format!("{}{}", &buf[..index + 1], ellipse))
|
||||
} else {
|
||||
Cow::Owned(format!("{}{}", &buf[..approx_chars], ellipse))
|
||||
Cow::Owned(format!("{}{}", &buf[..end_pos], ellipse))
|
||||
}
|
||||
} else {
|
||||
Cow::Borrowed(buf)
|
||||
@@ -354,33 +339,6 @@ unsafe fn dc_utf8_strnlen(s: *const libc::c_char, n: size_t) -> size_t {
|
||||
j
|
||||
}
|
||||
|
||||
/* split string into lines*/
|
||||
pub unsafe fn dc_split_into_lines(buf_terminated: *const libc::c_char) -> Vec<*mut libc::c_char> {
|
||||
let mut lines = Vec::new();
|
||||
let mut line_chars = 0;
|
||||
let mut p1: *const libc::c_char = buf_terminated;
|
||||
let mut line_start: *const libc::c_char = p1;
|
||||
while 0 != *p1 {
|
||||
if *p1 as libc::c_int == '\n' as i32 {
|
||||
lines.push(strndup(line_start, line_chars));
|
||||
p1 = p1.offset(1isize);
|
||||
line_start = p1;
|
||||
line_chars = 0;
|
||||
} else {
|
||||
p1 = p1.offset(1isize);
|
||||
line_chars += 1;
|
||||
}
|
||||
}
|
||||
lines.push(strndup(line_start, line_chars));
|
||||
lines
|
||||
}
|
||||
|
||||
pub unsafe fn dc_free_splitted_lines(lines: Vec<*mut libc::c_char>) {
|
||||
for s in lines {
|
||||
free(s as *mut libc::c_void);
|
||||
}
|
||||
}
|
||||
|
||||
pub unsafe fn dc_str_from_clist(
|
||||
list: *const clist,
|
||||
delimiter: *const libc::c_char,
|
||||
@@ -484,21 +442,10 @@ pub unsafe fn clist_free_content(haystack: *const clist) {
|
||||
pub unsafe fn clist_search_string_nocase(
|
||||
haystack: *const clist,
|
||||
needle: *const libc::c_char,
|
||||
) -> libc::c_int {
|
||||
let mut iter = (*haystack).first;
|
||||
|
||||
while !iter.is_null() {
|
||||
if strcasecmp((*iter).data as *const libc::c_char, needle) == 0 {
|
||||
return 1;
|
||||
}
|
||||
iter = if !iter.is_null() {
|
||||
(*iter).next
|
||||
} else {
|
||||
ptr::null_mut()
|
||||
}
|
||||
}
|
||||
|
||||
0
|
||||
) -> bool {
|
||||
(&*haystack)
|
||||
.into_iter()
|
||||
.any(|data| strcasecmp(data.cast(), needle) == 0)
|
||||
}
|
||||
|
||||
/* date/time tools */
|
||||
@@ -543,7 +490,7 @@ pub fn dc_gm2local_offset() -> i64 {
|
||||
}
|
||||
|
||||
/* timesmearing */
|
||||
pub unsafe fn dc_smeared_time(context: &Context) -> i64 {
|
||||
pub fn dc_smeared_time(context: &Context) -> i64 {
|
||||
/* function returns a corrected time(NULL) */
|
||||
let mut now = time();
|
||||
let ts = *context.last_smeared_timestamp.clone().read().unwrap();
|
||||
@@ -679,6 +626,21 @@ pub unsafe fn dc_create_outgoing_rfc724_mid(
|
||||
ret
|
||||
}
|
||||
|
||||
/// Generate globally-unique message-id for a new outgoing message.
|
||||
///
|
||||
/// Note: Do not add a counter or any private data as as this may give
|
||||
/// unneeded information to the receiver
|
||||
pub fn dc_create_outgoing_rfc724_mid_safe(grpid: Option<&str>, from_addr: &str) -> String {
|
||||
let hostname = from_addr
|
||||
.find('@')
|
||||
.map(|k| &from_addr[k..])
|
||||
.unwrap_or("@nohost");
|
||||
match grpid {
|
||||
Some(grpid) => format!("Gr.{}.{}{}", grpid, dc_create_id(), hostname),
|
||||
None => format!("Mr.{}.{}{}", dc_create_id(), dc_create_id(), hostname),
|
||||
}
|
||||
}
|
||||
|
||||
/// Extract the group id (grpid) from a message id (mid)
|
||||
///
|
||||
/// # Arguments
|
||||
@@ -1227,7 +1189,7 @@ impl CStringExt for CString {}
|
||||
/// Rust strings to raw C strings. This can be clumsy to do correctly
|
||||
/// and the compiler sometimes allows it in an unsafe way. These
|
||||
/// methods make it more succinct and help you get it right.
|
||||
pub trait Strdup {
|
||||
pub trait StrExt {
|
||||
/// Allocate a new raw C `*char` version of this string.
|
||||
///
|
||||
/// This allocates a new raw C string which must be freed using
|
||||
@@ -1244,26 +1206,13 @@ pub trait Strdup {
|
||||
unsafe fn strdup(&self) -> *mut libc::c_char;
|
||||
}
|
||||
|
||||
impl<T: AsRef<str>> Strdup for T {
|
||||
impl<T: AsRef<str>> StrExt for T {
|
||||
unsafe fn strdup(&self) -> *mut libc::c_char {
|
||||
let tmp = CString::yolo(self.as_ref());
|
||||
dc_strdup(tmp.as_ptr())
|
||||
}
|
||||
}
|
||||
|
||||
impl Strdup for CStr {
|
||||
unsafe fn strdup(&self) -> *mut libc::c_char {
|
||||
dc_strdup(self.as_ptr())
|
||||
}
|
||||
}
|
||||
|
||||
impl Strdup for std::path::Path {
|
||||
unsafe fn strdup(&self) -> *mut libc::c_char {
|
||||
let tmp = self.to_c_string().unwrap();
|
||||
dc_strdup(tmp.as_ptr())
|
||||
}
|
||||
}
|
||||
|
||||
pub fn to_string(s: *const libc::c_char) -> String {
|
||||
if s.is_null() {
|
||||
return "".into();
|
||||
@@ -1422,6 +1371,32 @@ impl FromStr for EmailAddress {
|
||||
}
|
||||
}
|
||||
|
||||
/// Utility to check if a in the binary represantion of listflags
|
||||
/// the bit at position bitindex is 1.
|
||||
///
|
||||
///
|
||||
/// # Example
|
||||
///
|
||||
/// ```
|
||||
/// use std::convert::TryInto;
|
||||
/// use deltachat::dc_tools::listflags_has;
|
||||
/// use deltachat::constants::{DC_GCL_ADD_SELF, DC_GCL_VERIFIED_ONLY};
|
||||
/// let listflags: u32 = 0x1101;
|
||||
/// assert!(listflags_has(listflags, 0x1) == true);
|
||||
/// assert!(listflags_has(listflags, 0x10) == false);
|
||||
/// assert!(listflags_has(listflags, 0x100) == true);
|
||||
/// assert!(listflags_has(listflags, 0x1000) == true);
|
||||
/// let listflags: u32 = (DC_GCL_ADD_SELF | DC_GCL_VERIFIED_ONLY).try_into().unwrap();
|
||||
/// assert!(listflags_has(listflags, DC_GCL_VERIFIED_ONLY) == true);
|
||||
/// assert!(listflags_has(listflags, DC_GCL_ADD_SELF) == true);
|
||||
/// let listflags: u32 = DC_GCL_VERIFIED_ONLY.try_into().unwrap();
|
||||
/// assert!(listflags_has(listflags, DC_GCL_ADD_SELF) == false);
|
||||
/// ```
|
||||
pub fn listflags_has(listflags: u32, bitindex: usize) -> bool {
|
||||
let listflags = listflags as usize;
|
||||
(listflags & bitindex) == bitindex
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
@@ -1463,7 +1438,7 @@ mod tests {
|
||||
);
|
||||
assert_ne!(str_a, str_a_copy);
|
||||
|
||||
let str_a = 0 as *const u8 as *const libc::c_char;
|
||||
let str_a = ptr::null();
|
||||
let str_a_copy = dc_strdup_keep_null(str_a);
|
||||
assert_eq!(str_a.is_null(), true);
|
||||
assert_eq!(str_a_copy.is_null(), true);
|
||||
@@ -1518,23 +1493,6 @@ mod tests {
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_dc_atof() {
|
||||
let f: libc::c_double = dc_atof(b"1.23\x00" as *const u8 as *const libc::c_char);
|
||||
assert!(f > 1.22f64);
|
||||
assert!(f < 1.24f64);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_dc_ftoa() {
|
||||
unsafe {
|
||||
let s: *mut libc::c_char = dc_ftoa(1.23f64);
|
||||
assert!(dc_atof(s) > 1.22f64);
|
||||
assert!(dc_atof(s) < 1.24f64);
|
||||
free(s as *mut libc::c_void);
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_rust_ftoa() {
|
||||
assert_eq!("1.22", format!("{}", 1.22));
|
||||
@@ -1582,6 +1540,36 @@ mod tests {
|
||||
assert_eq!(dc_truncate("123456", 4, true), "123456");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_dc_truncate_edge() {
|
||||
assert_eq!(dc_truncate("", 4, false), "");
|
||||
assert_eq!(dc_truncate("", 4, true), "");
|
||||
|
||||
assert_eq!(dc_truncate("\n hello \n world", 4, false), "\n [...]");
|
||||
assert_eq!(dc_truncate("\n hello \n world", 4, true), "\n ...");
|
||||
|
||||
assert_eq!(
|
||||
dc_truncate("𐠈0Aᝮa𫝀®!ꫛa¡0A𐢧00𐹠®A 丽ⷐએ", 1, false),
|
||||
"𐠈[...]"
|
||||
);
|
||||
assert_eq!(
|
||||
dc_truncate("𐠈0Aᝮa𫝀®!ꫛa¡0A𐢧00𐹠®A 丽ⷐએ", 0, false),
|
||||
"𐠈0Aᝮa𫝀®!ꫛa¡0A𐢧00𐹠®A 丽ⷐએ"
|
||||
);
|
||||
|
||||
// 9 characters, so no truncation
|
||||
assert_eq!(
|
||||
dc_truncate("𑒀ὐ¢🜀\u{1e01b}A a🟠", 6, false),
|
||||
"𑒀ὐ¢🜀\u{1e01b}A a🟠",
|
||||
);
|
||||
|
||||
// 12 characters, truncation
|
||||
assert_eq!(
|
||||
dc_truncate("𑒀ὐ¢🜀\u{1e01b}A a🟠bcd", 6, false),
|
||||
"𑒀ὐ¢🜀\u{1e01b}A[...]",
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_dc_null_terminate_1() {
|
||||
unsafe {
|
||||
@@ -1919,4 +1907,37 @@ mod tests {
|
||||
assert_eq!(EmailAddress::new("u@.tt").is_ok(), false);
|
||||
assert_eq!(EmailAddress::new("@d.tt").is_ok(), false);
|
||||
}
|
||||
|
||||
use proptest::prelude::*;
|
||||
|
||||
proptest! {
|
||||
#[test]
|
||||
fn test_dc_truncate(
|
||||
buf: String,
|
||||
approx_chars in 0..10000usize,
|
||||
do_unwrap: bool,
|
||||
) {
|
||||
let res = dc_truncate(&buf, approx_chars, do_unwrap);
|
||||
let el_len = if do_unwrap { 3 } else { 5 };
|
||||
let l = res.chars().count();
|
||||
if approx_chars > 0 {
|
||||
assert!(
|
||||
l <= approx_chars + el_len,
|
||||
"buf: '{}' - res: '{}' - len {}, approx {}",
|
||||
&buf, &res, res.len(), approx_chars
|
||||
);
|
||||
} else {
|
||||
assert_eq!(&res, &buf);
|
||||
}
|
||||
|
||||
if approx_chars > 0 && buf.chars().count() > approx_chars + el_len {
|
||||
let l = res.len();
|
||||
if do_unwrap {
|
||||
assert_eq!(&res[l-3..l], "...", "missing ellipsis in {}", &res);
|
||||
} else {
|
||||
assert_eq!(&res[l-5..l], "[...]", "missing ellipsis in {}", &res);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
46
src/imap.rs
46
src/imap.rs
@@ -8,8 +8,8 @@ use std::time::{Duration, SystemTime};
|
||||
|
||||
use crate::constants::*;
|
||||
use crate::context::Context;
|
||||
use crate::dc_loginparam::*;
|
||||
use crate::dc_tools::CStringExt;
|
||||
use crate::login_param::LoginParam;
|
||||
use crate::oauth2::dc_get_oauth2_access_token;
|
||||
use crate::types::*;
|
||||
|
||||
@@ -479,25 +479,19 @@ impl Imap {
|
||||
0, "IMAP unsetup_handle step 1 (closing down stream)."
|
||||
);
|
||||
let stream = self.stream.write().unwrap().take();
|
||||
if stream.is_some() {
|
||||
match stream.unwrap().shutdown(net::Shutdown::Both) {
|
||||
Ok(_) => {}
|
||||
Err(err) => {
|
||||
eprintln!("failed to shutdown connection: {:?}", err);
|
||||
}
|
||||
if let Some(stream) = stream {
|
||||
if let Err(err) = stream.shutdown(net::Shutdown::Both) {
|
||||
eprintln!("failed to shutdown connection: {:?}", err);
|
||||
}
|
||||
}
|
||||
|
||||
info!(
|
||||
context,
|
||||
0, "IMAP unsetup_handle step 2 (acquiring session.lock)"
|
||||
);
|
||||
let session = self.session.lock().unwrap().take();
|
||||
if session.is_some() {
|
||||
match session.unwrap().close() {
|
||||
Ok(_) => {}
|
||||
Err(err) => {
|
||||
eprintln!("failed to close connection: {:?}", err);
|
||||
}
|
||||
if let Some(mut session) = self.session.lock().unwrap().take() {
|
||||
if let Err(err) = session.close() {
|
||||
eprintln!("failed to close connection: {:?}", err);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -522,7 +516,7 @@ impl Imap {
|
||||
cfg.watch_folder = None;
|
||||
}
|
||||
|
||||
pub fn connect(&self, context: &Context, lp: &dc_loginparam_t) -> bool {
|
||||
pub fn connect(&self, context: &Context, lp: &LoginParam) -> bool {
|
||||
if lp.mail_server.is_empty() || lp.mail_user.is_empty() || lp.mail_pw.is_empty() {
|
||||
return false;
|
||||
}
|
||||
@@ -554,19 +548,17 @@ impl Imap {
|
||||
}
|
||||
|
||||
let (teardown, can_idle, has_xlist) = match &mut *self.session.lock().unwrap() {
|
||||
Some(ref mut session) => {
|
||||
if let Ok(caps) = session.capabilities() {
|
||||
Some(ref mut session) => match session.capabilities() {
|
||||
Ok(caps) => {
|
||||
if !context.sql.is_open() {
|
||||
warn!(context, 0, "IMAP-LOGIN as {} ok but ABORTING", lp.mail_user,);
|
||||
(true, false, false)
|
||||
} else {
|
||||
let can_idle = caps.has("IDLE");
|
||||
let has_xlist = caps.has("XLIST");
|
||||
let caps_list = caps.iter().fold(String::new(), |mut s, c| {
|
||||
s += " ";
|
||||
s += c;
|
||||
s
|
||||
});
|
||||
let can_idle = caps.has_str("IDLE");
|
||||
let has_xlist = caps.has_str("XLIST");
|
||||
let caps_list = caps
|
||||
.iter()
|
||||
.fold(String::new(), |s, c| s + &format!(" {:?}", c));
|
||||
log_event!(
|
||||
context,
|
||||
Event::IMAP_CONNECTED,
|
||||
@@ -577,10 +569,12 @@ impl Imap {
|
||||
);
|
||||
(false, can_idle, has_xlist)
|
||||
}
|
||||
} else {
|
||||
}
|
||||
Err(err) => {
|
||||
info!(context, 0, "CAPABILITY command error: {}", err);
|
||||
(true, false, false)
|
||||
}
|
||||
}
|
||||
},
|
||||
None => (true, false, false),
|
||||
};
|
||||
|
||||
|
||||
189
src/job.rs
189
src/job.rs
@@ -10,11 +10,11 @@ use crate::configure::*;
|
||||
use crate::constants::*;
|
||||
use crate::context::Context;
|
||||
use crate::dc_imex::*;
|
||||
use crate::dc_location::*;
|
||||
use crate::dc_loginparam::*;
|
||||
use crate::dc_mimefactory::*;
|
||||
use crate::dc_tools::*;
|
||||
use crate::imap::*;
|
||||
use crate::location;
|
||||
use crate::login_param::LoginParam;
|
||||
use crate::message::*;
|
||||
use crate::param::*;
|
||||
use crate::sql;
|
||||
@@ -25,13 +25,22 @@ use crate::x::*;
|
||||
#[derive(Debug, Display, Copy, Clone, PartialEq, Eq, FromPrimitive, ToPrimitive, FromSql, ToSql)]
|
||||
#[repr(i32)]
|
||||
enum Thread {
|
||||
Unknown = 0,
|
||||
Imap = 100,
|
||||
Smtp = 5000,
|
||||
}
|
||||
|
||||
impl Default for Thread {
|
||||
fn default() -> Self {
|
||||
Thread::Unknown
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Display, Copy, Clone, PartialEq, Eq, FromPrimitive, ToPrimitive, FromSql, ToSql)]
|
||||
#[repr(i32)]
|
||||
pub enum Action {
|
||||
Unknown = 0,
|
||||
|
||||
// Jobs in the INBOX-thread, range from DC_IMAP_THREAD..DC_IMAP_THREAD+999
|
||||
Housekeeping = 105, // low priority ...
|
||||
DeleteMsgOnImap = 110,
|
||||
@@ -50,11 +59,19 @@ pub enum Action {
|
||||
SendMsgToSmtp = 5901, // ... high priority
|
||||
}
|
||||
|
||||
impl Default for Action {
|
||||
fn default() -> Self {
|
||||
Action::Unknown
|
||||
}
|
||||
}
|
||||
|
||||
impl From<Action> for Thread {
|
||||
fn from(action: Action) -> Thread {
|
||||
use Action::*;
|
||||
|
||||
match action {
|
||||
Unknown => Thread::Unknown,
|
||||
|
||||
Housekeeping => Thread::Imap,
|
||||
DeleteMsgOnImap => Thread::Imap,
|
||||
MarkseenMdnOnImap => Thread::Imap,
|
||||
@@ -111,37 +128,21 @@ impl Job {
|
||||
|
||||
#[allow(non_snake_case)]
|
||||
fn do_DC_JOB_SEND(&mut self, context: &Context) {
|
||||
let ok_to_continue;
|
||||
let mut filename = ptr::null_mut();
|
||||
let mut buf = ptr::null_mut();
|
||||
let mut buf_bytes = 0;
|
||||
|
||||
/* connect to SMTP server, if not yet done */
|
||||
if !context.smtp.lock().unwrap().is_connected() {
|
||||
let loginparam = dc_loginparam_read(context, &context.sql, "configured_");
|
||||
let loginparam = LoginParam::from_database(context, "configured_");
|
||||
let connected = context.smtp.lock().unwrap().connect(context, &loginparam);
|
||||
|
||||
if !connected {
|
||||
self.try_again_later(3i32, None);
|
||||
ok_to_continue = false;
|
||||
} else {
|
||||
ok_to_continue = true;
|
||||
return;
|
||||
}
|
||||
} else {
|
||||
ok_to_continue = true;
|
||||
}
|
||||
if ok_to_continue {
|
||||
let filename_s = self.param.get(Param::File).unwrap_or_default();
|
||||
filename = unsafe { filename_s.strdup() };
|
||||
if unsafe { strlen(filename) } == 0 {
|
||||
warn!(context, 0, "Missing file name for job {}", self.job_id,);
|
||||
} else if 0 != unsafe { dc_read_file(context, filename, &mut buf, &mut buf_bytes) } {
|
||||
let recipients = self.param.get(Param::Recipients);
|
||||
if recipients.is_none() {
|
||||
warn!(context, 0, "Missing recipients for job {}", self.job_id,);
|
||||
} else {
|
||||
|
||||
if let Some(filename) = self.param.get(Param::File) {
|
||||
if let Some(body) = dc_read_file_safe(context, filename) {
|
||||
if let Some(recipients) = self.param.get(Param::Recipients) {
|
||||
let recipients_list = recipients
|
||||
.unwrap()
|
||||
.split("\x1e")
|
||||
.filter_map(|addr| match lettre::EmailAddress::new(addr.to_string()) {
|
||||
Ok(addr) => Some(addr),
|
||||
@@ -151,76 +152,63 @@ impl Job {
|
||||
}
|
||||
})
|
||||
.collect::<Vec<_>>();
|
||||
|
||||
/* if there is a msg-id and it does not exist in the db, cancel sending.
|
||||
this happends if dc_delete_msgs() was called
|
||||
before the generated mime was sent out */
|
||||
let ok_to_continue1;
|
||||
if 0 != self.foreign_id {
|
||||
if 0 == unsafe { dc_msg_exists(context, self.foreign_id) } {
|
||||
warn!(
|
||||
context,
|
||||
0,
|
||||
"Message {} for job {} does not exist",
|
||||
self.foreign_id,
|
||||
self.job_id,
|
||||
);
|
||||
ok_to_continue1 = false;
|
||||
} else {
|
||||
ok_to_continue1 = true;
|
||||
}
|
||||
} else {
|
||||
ok_to_continue1 = true;
|
||||
}
|
||||
if ok_to_continue1 {
|
||||
/* send message */
|
||||
let body = unsafe {
|
||||
std::slice::from_raw_parts(buf as *const u8, buf_bytes).to_vec()
|
||||
};
|
||||
if 0 != self.foreign_id
|
||||
&& 0 == unsafe { dc_msg_exists(context, self.foreign_id) }
|
||||
{
|
||||
warn!(
|
||||
context,
|
||||
0, "Message {} for job {} does not exist", self.foreign_id, self.job_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
|
||||
// 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, Some(as_str(sock.error)));
|
||||
} else {
|
||||
dc_delete_file(context, filename_s);
|
||||
if 0 != self.foreign_id {
|
||||
dc_update_msg_state(
|
||||
// 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
|
||||
// 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 {
|
||||
dc_update_msg_state(
|
||||
context,
|
||||
self.foreign_id,
|
||||
MessageState::OutDelivered,
|
||||
);
|
||||
let chat_id: i32 = context
|
||||
.sql
|
||||
.query_row_col(
|
||||
context,
|
||||
self.foreign_id,
|
||||
MessageState::OutDelivered,
|
||||
);
|
||||
let chat_id: i32 = context
|
||||
.sql
|
||||
.query_row_col(
|
||||
context,
|
||||
"SELECT chat_id FROM msgs WHERE id=?",
|
||||
params![self.foreign_id as i32],
|
||||
0,
|
||||
)
|
||||
.unwrap_or_default();
|
||||
context.call_cb(
|
||||
Event::MSG_DELIVERED,
|
||||
chat_id as uintptr_t,
|
||||
self.foreign_id as uintptr_t,
|
||||
);
|
||||
}
|
||||
"SELECT chat_id FROM msgs WHERE id=?",
|
||||
params![self.foreign_id as i32],
|
||||
0,
|
||||
)
|
||||
.unwrap_or_default();
|
||||
context.call_cb(
|
||||
Event::MSG_DELIVERED,
|
||||
chat_id as uintptr_t,
|
||||
self.foreign_id as uintptr_t,
|
||||
);
|
||||
}
|
||||
}
|
||||
} else {
|
||||
warn!(context, 0, "Missing recipients for job {}", self.job_id,);
|
||||
}
|
||||
}
|
||||
}
|
||||
unsafe { free(buf) };
|
||||
unsafe { free(filename.cast()) };
|
||||
}
|
||||
|
||||
// this value does not increase the number of tries
|
||||
fn try_again_later(&mut self, try_again: libc::c_int, pending_error: Option<&str>) {
|
||||
fn try_again_later(&mut self, try_again: libc::c_int, pending_error: Option<String>) {
|
||||
self.try_again = try_again;
|
||||
self.pending_error = pending_error.map(|s| s.to_string());
|
||||
self.pending_error = pending_error;
|
||||
}
|
||||
|
||||
#[allow(non_snake_case)]
|
||||
@@ -721,13 +709,11 @@ pub unsafe fn job_send_msg(context: &Context, msg_id: uint32_t) -> libc::c_int {
|
||||
);
|
||||
} else {
|
||||
/* unrecoverable */
|
||||
if clist_search_string_nocase(mimefactory.recipients_addr, mimefactory.from_addr)
|
||||
== 0i32
|
||||
{
|
||||
if !clist_search_string_nocase(mimefactory.recipients_addr, mimefactory.from_addr) {
|
||||
clist_insert_after(
|
||||
mimefactory.recipients_names,
|
||||
(*mimefactory.recipients_names).last,
|
||||
0 as *mut libc::c_void,
|
||||
ptr::null_mut(),
|
||||
);
|
||||
clist_insert_after(
|
||||
mimefactory.recipients_addr,
|
||||
@@ -739,13 +725,19 @@ pub unsafe fn job_send_msg(context: &Context, msg_id: uint32_t) -> libc::c_int {
|
||||
chat::set_gossiped_timestamp(context, mimefactory.msg.chat_id, time());
|
||||
}
|
||||
if 0 != mimefactory.out_last_added_location_id {
|
||||
dc_set_kml_sent_timestamp(context, mimefactory.msg.chat_id, time());
|
||||
if let Err(err) =
|
||||
location::set_kml_sent_timestamp(context, mimefactory.msg.chat_id, time())
|
||||
{
|
||||
error!(context, 0, "Failed to set kml sent_timestamp: {:?}", err);
|
||||
}
|
||||
if !mimefactory.msg.hidden {
|
||||
dc_set_msg_location_id(
|
||||
if let Err(err) = location::set_msg_location_id(
|
||||
context,
|
||||
mimefactory.msg.id,
|
||||
mimefactory.out_last_added_location_id,
|
||||
);
|
||||
) {
|
||||
error!(context, 0, "Failed to set msg_location_id: {:?}", err);
|
||||
}
|
||||
}
|
||||
}
|
||||
if 0 != mimefactory.out_encrypted
|
||||
@@ -867,6 +859,9 @@ fn job_perform(context: &Context, thread: Thread, probe_network: bool) {
|
||||
job.try_again = 0;
|
||||
|
||||
match job.action {
|
||||
Action::Unknown => {
|
||||
warn!(context, 0, "Unknown job id found");
|
||||
}
|
||||
Action::SendMsgToSmtp => job.do_DC_JOB_SEND(context),
|
||||
Action::DeleteMsgOnImap => job.do_DC_JOB_DELETE_MSG_ON_IMAP(context),
|
||||
Action::MarkseenMsgOnImap => job.do_DC_JOB_MARKSEEN_MSG_ON_IMAP(context),
|
||||
@@ -875,12 +870,12 @@ fn job_perform(context: &Context, thread: Thread, probe_network: bool) {
|
||||
Action::SendMdn => job.do_DC_JOB_SEND(context),
|
||||
Action::ConfigureImap => unsafe { dc_job_do_DC_JOB_CONFIGURE_IMAP(context, &job) },
|
||||
Action::ImexImap => unsafe { dc_job_do_DC_JOB_IMEX_IMAP(context, &job) },
|
||||
Action::MaybeSendLocations => unsafe {
|
||||
dc_job_do_DC_JOB_MAYBE_SEND_LOCATIONS(context, &job)
|
||||
},
|
||||
Action::MaybeSendLocationsEnded => unsafe {
|
||||
dc_job_do_DC_JOB_MAYBE_SEND_LOC_ENDED(context, &mut job)
|
||||
},
|
||||
Action::MaybeSendLocations => {
|
||||
location::job_do_DC_JOB_MAYBE_SEND_LOCATIONS(context, &job)
|
||||
}
|
||||
Action::MaybeSendLocationsEnded => {
|
||||
location::job_do_DC_JOB_MAYBE_SEND_LOC_ENDED(context, &mut job)
|
||||
}
|
||||
Action::Housekeeping => sql::housekeeping(context),
|
||||
Action::SendMdnOld => {}
|
||||
Action::SendMsgToSmtpOld => {}
|
||||
@@ -1014,7 +1009,7 @@ fn send_mdn(context: &Context, msg_id: uint32_t) {
|
||||
fn add_smtp_job(context: &Context, action: Action, mimefactory: &dc_mimefactory_t) -> libc::c_int {
|
||||
let pathNfilename: *mut libc::c_char;
|
||||
let mut success: libc::c_int = 0i32;
|
||||
let mut recipients: *mut libc::c_char = 0 as *mut libc::c_char;
|
||||
let mut recipients: *mut libc::c_char = ptr::null_mut();
|
||||
let mut param = Params::new();
|
||||
pathNfilename = unsafe {
|
||||
dc_get_fine_pathNfilename(
|
||||
@@ -1085,6 +1080,11 @@ pub fn job_add(
|
||||
param: Params,
|
||||
delay_seconds: i64,
|
||||
) {
|
||||
if action == Action::Unknown {
|
||||
error!(context, 0, "Invalid action passed to job_add");
|
||||
return;
|
||||
}
|
||||
|
||||
let timestamp = time();
|
||||
let thread: Thread = action.into();
|
||||
|
||||
@@ -1105,6 +1105,7 @@ pub fn job_add(
|
||||
match thread {
|
||||
Thread::Imap => interrupt_imap_idle(context),
|
||||
Thread::Smtp => interrupt_smtp_idle(context),
|
||||
Thread::Unknown => {}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
13
src/lib.rs
13
src/lib.rs
@@ -16,6 +16,8 @@ extern crate rusqlite;
|
||||
extern crate strum;
|
||||
#[macro_use]
|
||||
extern crate strum_macros;
|
||||
#[macro_use]
|
||||
extern crate jetscii;
|
||||
|
||||
#[macro_use]
|
||||
mod log;
|
||||
@@ -30,11 +32,13 @@ pub mod configure;
|
||||
pub mod constants;
|
||||
pub mod contact;
|
||||
pub mod context;
|
||||
mod e2ee;
|
||||
mod imap;
|
||||
pub mod job;
|
||||
mod job_thread;
|
||||
pub mod key;
|
||||
pub mod keyring;
|
||||
pub mod location;
|
||||
pub mod lot;
|
||||
pub mod message;
|
||||
pub mod oauth2;
|
||||
@@ -50,19 +54,16 @@ pub mod x;
|
||||
|
||||
pub mod dc_array;
|
||||
mod dc_dehtml;
|
||||
mod dc_e2ee;
|
||||
pub mod dc_imex;
|
||||
pub mod dc_location;
|
||||
mod dc_loginparam;
|
||||
mod dc_mimefactory;
|
||||
pub mod dc_mimeparser;
|
||||
mod dc_move;
|
||||
pub mod dc_receive_imf;
|
||||
pub mod dc_securejoin;
|
||||
mod dc_simplify;
|
||||
mod dc_strencode;
|
||||
mod dc_token;
|
||||
pub mod dc_tools;
|
||||
mod login_param;
|
||||
pub mod securejoin;
|
||||
mod token;
|
||||
|
||||
#[cfg(test)]
|
||||
mod test_utils;
|
||||
|
||||
696
src/location.rs
Normal file
696
src/location.rs
Normal file
@@ -0,0 +1,696 @@
|
||||
use bitflags::bitflags;
|
||||
use quick_xml;
|
||||
use quick_xml::events::{BytesEnd, BytesStart, BytesText};
|
||||
|
||||
use crate::chat;
|
||||
use crate::constants::Event;
|
||||
use crate::constants::*;
|
||||
use crate::context::*;
|
||||
use crate::dc_tools::*;
|
||||
use crate::error::Error;
|
||||
use crate::job::*;
|
||||
use crate::message::*;
|
||||
use crate::param::*;
|
||||
use crate::sql;
|
||||
use crate::stock::StockMessage;
|
||||
use crate::types::*;
|
||||
|
||||
// location handling
|
||||
#[derive(Debug, Clone, Default)]
|
||||
pub struct Location {
|
||||
pub location_id: u32,
|
||||
pub latitude: f64,
|
||||
pub longitude: f64,
|
||||
pub accuracy: f64,
|
||||
pub timestamp: i64,
|
||||
pub contact_id: u32,
|
||||
pub msg_id: u32,
|
||||
pub chat_id: u32,
|
||||
pub marker: Option<String>,
|
||||
pub independent: u32,
|
||||
}
|
||||
|
||||
impl Location {
|
||||
pub fn new() -> Self {
|
||||
Default::default()
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Default)]
|
||||
pub struct Kml {
|
||||
pub addr: Option<String>,
|
||||
pub locations: Vec<Location>,
|
||||
tag: KmlTag,
|
||||
pub curr: Location,
|
||||
}
|
||||
|
||||
bitflags! {
|
||||
#[derive(Default)]
|
||||
struct KmlTag: i32 {
|
||||
const UNDEFINED = 0x00;
|
||||
const PLACEMARK = 0x01;
|
||||
const TIMESTAMP = 0x02;
|
||||
const WHEN = 0x04;
|
||||
const POINT = 0x08;
|
||||
const COORDINATES = 0x10;
|
||||
}
|
||||
}
|
||||
|
||||
impl Kml {
|
||||
pub fn new() -> Self {
|
||||
Default::default()
|
||||
}
|
||||
|
||||
pub fn parse(context: &Context, content: impl AsRef<str>) -> Result<Self, Error> {
|
||||
ensure!(
|
||||
content.as_ref().len() <= (1 * 1024 * 1024),
|
||||
"A kml-files with {} bytes is larger than reasonably expected.",
|
||||
content.as_ref().len()
|
||||
);
|
||||
|
||||
let mut reader = quick_xml::Reader::from_str(content.as_ref());
|
||||
reader.trim_text(true);
|
||||
|
||||
let mut kml = Kml::new();
|
||||
kml.locations = Vec::with_capacity(100);
|
||||
|
||||
let mut buf = Vec::new();
|
||||
|
||||
loop {
|
||||
match reader.read_event(&mut buf) {
|
||||
Ok(quick_xml::events::Event::Start(ref e)) => kml.starttag_cb(e, &reader),
|
||||
Ok(quick_xml::events::Event::End(ref e)) => kml.endtag_cb(e),
|
||||
Ok(quick_xml::events::Event::Text(ref e)) => kml.text_cb(e, &reader),
|
||||
Err(e) => {
|
||||
error!(
|
||||
context,
|
||||
0,
|
||||
"Location parsing: Error at position {}: {:?}",
|
||||
reader.buffer_position(),
|
||||
e
|
||||
);
|
||||
}
|
||||
Ok(quick_xml::events::Event::Eof) => break,
|
||||
_ => (),
|
||||
}
|
||||
buf.clear();
|
||||
}
|
||||
|
||||
Ok(kml)
|
||||
}
|
||||
|
||||
fn text_cb<B: std::io::BufRead>(&mut self, event: &BytesText, reader: &quick_xml::Reader<B>) {
|
||||
if self.tag.contains(KmlTag::WHEN) || self.tag.contains(KmlTag::COORDINATES) {
|
||||
let val = event.unescape_and_decode(reader).unwrap_or_default();
|
||||
|
||||
let val = val
|
||||
.replace("\n", "")
|
||||
.replace("\r", "")
|
||||
.replace("\t", "")
|
||||
.replace(" ", "");
|
||||
|
||||
if self.tag.contains(KmlTag::WHEN) && val.len() >= 19 {
|
||||
// YYYY-MM-DDTHH:MM:SSZ
|
||||
// 0 4 7 10 13 16 19
|
||||
match chrono::NaiveDateTime::parse_from_str(&val, "%Y-%m-%dT%H:%M:%SZ") {
|
||||
Ok(res) => {
|
||||
self.curr.timestamp = res.timestamp();
|
||||
if self.curr.timestamp > time() {
|
||||
self.curr.timestamp = time();
|
||||
}
|
||||
}
|
||||
Err(_err) => {
|
||||
self.curr.timestamp = time();
|
||||
}
|
||||
}
|
||||
} else if self.tag.contains(KmlTag::COORDINATES) {
|
||||
let parts = val.splitn(2, ',').collect::<Vec<_>>();
|
||||
if parts.len() == 2 {
|
||||
self.curr.longitude = parts[0].parse().unwrap_or_default();
|
||||
self.curr.latitude = parts[1].parse().unwrap_or_default();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn endtag_cb(&mut self, event: &BytesEnd) {
|
||||
let tag = String::from_utf8_lossy(event.name()).trim().to_lowercase();
|
||||
|
||||
if tag == "placemark" {
|
||||
if self.tag.contains(KmlTag::PLACEMARK)
|
||||
&& 0 != self.curr.timestamp
|
||||
&& 0. != self.curr.latitude
|
||||
&& 0. != self.curr.longitude
|
||||
{
|
||||
self.locations
|
||||
.push(std::mem::replace(&mut self.curr, Location::new()));
|
||||
}
|
||||
self.tag = KmlTag::UNDEFINED;
|
||||
};
|
||||
}
|
||||
|
||||
fn starttag_cb<B: std::io::BufRead>(
|
||||
&mut self,
|
||||
event: &BytesStart,
|
||||
reader: &quick_xml::Reader<B>,
|
||||
) {
|
||||
let tag = String::from_utf8_lossy(event.name()).trim().to_lowercase();
|
||||
if tag == "document" {
|
||||
if let Some(addr) = event.attributes().find(|attr| {
|
||||
attr.as_ref()
|
||||
.map(|a| String::from_utf8_lossy(a.key).trim().to_lowercase() == "addr")
|
||||
.unwrap_or_default()
|
||||
}) {
|
||||
self.addr = addr.unwrap().unescape_and_decode_value(reader).ok();
|
||||
}
|
||||
} else if tag == "placemark" {
|
||||
self.tag = KmlTag::PLACEMARK;
|
||||
self.curr.timestamp = 0;
|
||||
self.curr.latitude = 0.0;
|
||||
self.curr.longitude = 0.0;
|
||||
self.curr.accuracy = 0.0
|
||||
} else if tag == "timestamp" && self.tag.contains(KmlTag::PLACEMARK) {
|
||||
self.tag = KmlTag::PLACEMARK | KmlTag::TIMESTAMP
|
||||
} else if tag == "when" && self.tag.contains(KmlTag::TIMESTAMP) {
|
||||
self.tag = KmlTag::PLACEMARK | KmlTag::TIMESTAMP | KmlTag::WHEN
|
||||
} else if tag == "point" && self.tag.contains(KmlTag::PLACEMARK) {
|
||||
self.tag = KmlTag::PLACEMARK | KmlTag::POINT
|
||||
} else if tag == "coordinates" && self.tag.contains(KmlTag::POINT) {
|
||||
self.tag = KmlTag::PLACEMARK | KmlTag::POINT | KmlTag::COORDINATES;
|
||||
if let Some(acc) = event.attributes().find(|attr| {
|
||||
attr.as_ref()
|
||||
.map(|a| String::from_utf8_lossy(a.key).trim().to_lowercase() == "accuracy")
|
||||
.unwrap_or_default()
|
||||
}) {
|
||||
let v = acc
|
||||
.unwrap()
|
||||
.unescape_and_decode_value(reader)
|
||||
.unwrap_or_default();
|
||||
|
||||
self.curr.accuracy = v.trim().parse().unwrap_or_default();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// location streaming
|
||||
pub fn send_locations_to_chat(context: &Context, chat_id: u32, seconds: i64) {
|
||||
let now = time();
|
||||
let mut msg: Message;
|
||||
let is_sending_locations_before: bool;
|
||||
if !(seconds < 0 || chat_id <= 9i32 as libc::c_uint) {
|
||||
is_sending_locations_before = is_sending_locations_to_chat(context, chat_id);
|
||||
if sql::execute(
|
||||
context,
|
||||
&context.sql,
|
||||
"UPDATE chats \
|
||||
SET locations_send_begin=?, \
|
||||
locations_send_until=? \
|
||||
WHERE id=?",
|
||||
params![
|
||||
if 0 != seconds { now } else { 0 },
|
||||
if 0 != seconds { now + seconds } else { 0 },
|
||||
chat_id as i32,
|
||||
],
|
||||
)
|
||||
.is_ok()
|
||||
{
|
||||
if 0 != seconds && !is_sending_locations_before {
|
||||
msg = dc_msg_new(context, Viewtype::Text);
|
||||
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();
|
||||
} else if 0 == seconds && is_sending_locations_before {
|
||||
let stock_str =
|
||||
context.stock_system_msg(StockMessage::MsgLocationDisabled, "", "", 0);
|
||||
chat::add_device_msg(context, chat_id, stock_str);
|
||||
}
|
||||
context.call_cb(
|
||||
Event::CHAT_MODIFIED,
|
||||
chat_id as uintptr_t,
|
||||
0i32 as uintptr_t,
|
||||
);
|
||||
if 0 != seconds {
|
||||
schedule_MAYBE_SEND_LOCATIONS(context, 0i32);
|
||||
job_add(
|
||||
context,
|
||||
Action::MaybeSendLocationsEnded,
|
||||
chat_id as libc::c_int,
|
||||
Params::new(),
|
||||
seconds + 1,
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[allow(non_snake_case)]
|
||||
fn schedule_MAYBE_SEND_LOCATIONS(context: &Context, flags: i32) {
|
||||
if 0 != flags & 0x1 || !job_action_exists(context, Action::MaybeSendLocations) {
|
||||
job_add(context, Action::MaybeSendLocations, 0, Params::new(), 60);
|
||||
};
|
||||
}
|
||||
|
||||
pub fn is_sending_locations_to_chat(context: &Context, chat_id: u32) -> bool {
|
||||
context
|
||||
.sql
|
||||
.exists(
|
||||
"SELECT id FROM chats WHERE (? OR id=?) AND locations_send_until>?;",
|
||||
params![if chat_id == 0 { 1 } else { 0 }, chat_id as i32, time()],
|
||||
)
|
||||
.unwrap_or_default()
|
||||
}
|
||||
|
||||
pub fn set(context: &Context, latitude: f64, longitude: f64, accuracy: f64) -> libc::c_int {
|
||||
if latitude == 0.0 && longitude == 0.0 {
|
||||
return 1;
|
||||
}
|
||||
|
||||
context.sql.query_map(
|
||||
"SELECT id FROM chats WHERE locations_send_until>?;",
|
||||
params![time()], |row| row.get::<_, i32>(0),
|
||||
|chats| {
|
||||
let mut continue_streaming = false;
|
||||
|
||||
for chat in chats {
|
||||
let chat_id = chat?;
|
||||
context.sql.execute(
|
||||
"INSERT INTO locations \
|
||||
(latitude, longitude, accuracy, timestamp, chat_id, from_id) VALUES (?,?,?,?,?,?);",
|
||||
params![
|
||||
latitude,
|
||||
longitude,
|
||||
accuracy,
|
||||
time(),
|
||||
chat_id,
|
||||
1,
|
||||
]
|
||||
)?;
|
||||
continue_streaming = true;
|
||||
}
|
||||
if continue_streaming {
|
||||
context.call_cb(Event::LOCATION_CHANGED, 1, 0);
|
||||
};
|
||||
schedule_MAYBE_SEND_LOCATIONS(context, 0);
|
||||
Ok(continue_streaming as libc::c_int)
|
||||
}
|
||||
).unwrap_or_default()
|
||||
}
|
||||
|
||||
pub fn get_range(
|
||||
context: &Context,
|
||||
chat_id: u32,
|
||||
contact_id: u32,
|
||||
timestamp_from: i64,
|
||||
mut timestamp_to: i64,
|
||||
) -> Vec<Location> {
|
||||
if timestamp_to == 0 {
|
||||
timestamp_to = time() + 10;
|
||||
}
|
||||
|
||||
context
|
||||
.sql
|
||||
.query_map(
|
||||
"SELECT l.id, l.latitude, l.longitude, l.accuracy, l.timestamp, l.independent, \
|
||||
m.id, l.from_id, l.chat_id, m.txt \
|
||||
FROM locations l LEFT JOIN msgs m ON l.id=m.location_id WHERE (? OR l.chat_id=?) \
|
||||
AND (? OR l.from_id=?) \
|
||||
AND (l.independent=1 OR (l.timestamp>=? AND l.timestamp<=?)) \
|
||||
ORDER BY l.timestamp DESC, l.id DESC, m.id DESC;",
|
||||
params![
|
||||
if chat_id == 0 { 1 } else { 0 },
|
||||
chat_id as i32,
|
||||
if contact_id == 0 { 1 } else { 0 },
|
||||
contact_id as i32,
|
||||
timestamp_from,
|
||||
timestamp_to,
|
||||
],
|
||||
|row| {
|
||||
let msg_id = row.get(6)?;
|
||||
let txt: String = row.get(9)?;
|
||||
let marker = if msg_id != 0 && is_marker(&txt) {
|
||||
Some(txt)
|
||||
} else {
|
||||
None
|
||||
};
|
||||
|
||||
let loc = Location {
|
||||
location_id: row.get(0)?,
|
||||
latitude: row.get(1)?,
|
||||
longitude: row.get(2)?,
|
||||
accuracy: row.get(3)?,
|
||||
timestamp: row.get(4)?,
|
||||
independent: row.get(5)?,
|
||||
msg_id,
|
||||
contact_id: row.get(7)?,
|
||||
chat_id: row.get(8)?,
|
||||
marker,
|
||||
};
|
||||
Ok(loc)
|
||||
},
|
||||
|locations| {
|
||||
let mut ret = Vec::new();
|
||||
|
||||
for location in locations {
|
||||
ret.push(location?);
|
||||
}
|
||||
Ok(ret)
|
||||
},
|
||||
)
|
||||
.unwrap_or_default()
|
||||
}
|
||||
|
||||
fn is_marker(txt: &str) -> bool {
|
||||
txt.len() == 1 && txt.chars().next().unwrap() != ' '
|
||||
}
|
||||
|
||||
pub fn delete_all(context: &Context) -> Result<(), Error> {
|
||||
sql::execute(context, &context.sql, "DELETE FROM locations;", params![])?;
|
||||
context.call_cb(Event::LOCATION_CHANGED, 0, 0);
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub fn get_kml(context: &Context, chat_id: u32) -> Result<(String, u32), Error> {
|
||||
let now = time();
|
||||
let mut location_count = 0;
|
||||
let mut ret = String::new();
|
||||
let mut last_added_location_id = 0;
|
||||
|
||||
let self_addr = context
|
||||
.sql
|
||||
.get_config(context, "configured_addr")
|
||||
.unwrap_or_default();
|
||||
|
||||
let (locations_send_begin, locations_send_until, locations_last_sent) = context.sql.query_row(
|
||||
"SELECT locations_send_begin, locations_send_until, locations_last_sent FROM chats WHERE id=?;",
|
||||
params![chat_id as i32], |row| {
|
||||
let send_begin: i64 = row.get(0)?;
|
||||
let send_until: i64 = row.get(1)?;
|
||||
let last_sent: i64 = row.get(2)?;
|
||||
|
||||
Ok((send_begin, send_until, last_sent))
|
||||
})?;
|
||||
|
||||
if !(locations_send_begin == 0 || now > locations_send_until) {
|
||||
ret += &format!(
|
||||
"<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<kml xmlns=\"http://www.opengis.net/kml/2.2\">\n<Document addr=\"{}\">\n",
|
||||
self_addr,
|
||||
);
|
||||
|
||||
context.sql.query_map(
|
||||
"SELECT id, latitude, longitude, accuracy, timestamp\
|
||||
FROM locations WHERE from_id=? \
|
||||
AND timestamp>=? \
|
||||
AND (timestamp>=? OR timestamp=(SELECT MAX(timestamp) FROM locations WHERE from_id=?)) \
|
||||
AND independent=0 \
|
||||
GROUP BY timestamp \
|
||||
ORDER BY timestamp;",
|
||||
params![1, locations_send_begin, locations_last_sent, 1],
|
||||
|row| {
|
||||
let location_id: i32 = row.get(0)?;
|
||||
let latitude: f64 = row.get(1)?;
|
||||
let longitude: f64 = row.get(2)?;
|
||||
let accuracy: f64 = row.get(3)?;
|
||||
let timestamp = get_kml_timestamp(row.get(4)?);
|
||||
|
||||
Ok((location_id, latitude, longitude, accuracy, timestamp))
|
||||
},
|
||||
|rows| {
|
||||
for row in rows {
|
||||
let (location_id, latitude, longitude, accuracy, timestamp) = row?;
|
||||
ret += &format!(
|
||||
"<Placemark><Timestamp><when>{}</when></Timestamp><Point><coordinates accuracy=\"{}\">{},{}</coordinates></Point></Placemark>\n\x00",
|
||||
timestamp,
|
||||
accuracy,
|
||||
longitude,
|
||||
latitude
|
||||
);
|
||||
location_count += 1;
|
||||
last_added_location_id = location_id as u32;
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
)?;
|
||||
}
|
||||
|
||||
ensure!(location_count > 0, "No locations processed");
|
||||
ret += "</Document>\n</kml>";
|
||||
|
||||
Ok((ret, last_added_location_id))
|
||||
}
|
||||
|
||||
fn get_kml_timestamp(utc: i64) -> String {
|
||||
// Returns a string formatted as YYYY-MM-DDTHH:MM:SSZ. The trailing `Z` indicates UTC.
|
||||
chrono::NaiveDateTime::from_timestamp(utc, 0)
|
||||
.format("%Y-%m-%dT%H:%M:%SZ")
|
||||
.to_string()
|
||||
}
|
||||
|
||||
pub fn get_message_kml(timestamp: i64, latitude: f64, longitude: f64) -> String {
|
||||
format!(
|
||||
"<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n\
|
||||
<kml xmlns=\"http://www.opengis.net/kml/2.2\">\n\
|
||||
<Document>\n\
|
||||
<Placemark>\
|
||||
<Timestamp><when>{}</when></Timestamp>\
|
||||
<Point><coordinates>{:.2},{:.2}</coordinates></Point>\
|
||||
</Placemark>\n\
|
||||
</Document>\n\
|
||||
</kml>",
|
||||
get_kml_timestamp(timestamp),
|
||||
longitude,
|
||||
latitude,
|
||||
)
|
||||
}
|
||||
|
||||
pub fn set_kml_sent_timestamp(
|
||||
context: &Context,
|
||||
chat_id: u32,
|
||||
timestamp: i64,
|
||||
) -> Result<(), Error> {
|
||||
sql::execute(
|
||||
context,
|
||||
&context.sql,
|
||||
"UPDATE chats SET locations_last_sent=? WHERE id=?;",
|
||||
params![timestamp, chat_id as i32],
|
||||
)?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub fn set_msg_location_id(context: &Context, msg_id: u32, location_id: u32) -> Result<(), Error> {
|
||||
sql::execute(
|
||||
context,
|
||||
&context.sql,
|
||||
"UPDATE msgs SET location_id=? WHERE id=?;",
|
||||
params![location_id, msg_id as i32],
|
||||
)?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub fn save(
|
||||
context: &Context,
|
||||
chat_id: u32,
|
||||
contact_id: u32,
|
||||
locations: &[Location],
|
||||
independent: i32,
|
||||
) -> Result<u32, Error> {
|
||||
ensure!(chat_id > 9, "Invalid chat id");
|
||||
context.sql.prepare2(
|
||||
"SELECT id FROM locations WHERE timestamp=? AND from_id=?",
|
||||
"INSERT INTO locations\
|
||||
(timestamp, from_id, chat_id, latitude, longitude, accuracy, independent) \
|
||||
VALUES (?,?,?,?,?,?,?);",
|
||||
|mut stmt_test, mut stmt_insert, conn| {
|
||||
let mut newest_timestamp = 0;
|
||||
let mut newest_location_id = 0;
|
||||
|
||||
for location in locations {
|
||||
let exists = stmt_test.exists(params![location.timestamp, contact_id as i32])?;
|
||||
|
||||
if 0 != independent || !exists {
|
||||
stmt_insert.execute(params![
|
||||
location.timestamp,
|
||||
contact_id as i32,
|
||||
chat_id as i32,
|
||||
location.latitude,
|
||||
location.longitude,
|
||||
location.accuracy,
|
||||
independent,
|
||||
])?;
|
||||
|
||||
if location.timestamp > newest_timestamp {
|
||||
newest_timestamp = location.timestamp;
|
||||
newest_location_id = sql::get_rowid2_with_conn(
|
||||
context,
|
||||
conn,
|
||||
"locations",
|
||||
"timestamp",
|
||||
location.timestamp,
|
||||
"from_id",
|
||||
contact_id as i32,
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
Ok(newest_location_id)
|
||||
},
|
||||
)
|
||||
}
|
||||
|
||||
#[allow(non_snake_case)]
|
||||
pub fn job_do_DC_JOB_MAYBE_SEND_LOCATIONS(context: &Context, _job: &Job) {
|
||||
let now = time();
|
||||
let mut continue_streaming: libc::c_int = 1;
|
||||
info!(
|
||||
context,
|
||||
0, " ----------------- MAYBE_SEND_LOCATIONS -------------- ",
|
||||
);
|
||||
|
||||
context
|
||||
.sql
|
||||
.query_map(
|
||||
"SELECT id, locations_send_begin, locations_last_sent \
|
||||
FROM chats \
|
||||
WHERE locations_send_until>?;",
|
||||
params![now],
|
||||
|row| {
|
||||
let chat_id: i32 = row.get(0)?;
|
||||
let locations_send_begin: i64 = row.get(1)?;
|
||||
let locations_last_sent: i64 = row.get(2)?;
|
||||
continue_streaming = 1;
|
||||
|
||||
// be a bit tolerant as the timer may not align exactly with time(NULL)
|
||||
if now - locations_last_sent < (60 - 3) {
|
||||
Ok(None)
|
||||
} else {
|
||||
Ok(Some((chat_id, locations_send_begin, locations_last_sent)))
|
||||
}
|
||||
},
|
||||
|rows| {
|
||||
context.sql.prepare(
|
||||
"SELECT id \
|
||||
FROM locations \
|
||||
WHERE from_id=? \
|
||||
AND timestamp>=? \
|
||||
AND timestamp>? \
|
||||
AND independent=0 \
|
||||
ORDER BY timestamp;",
|
||||
|mut stmt_locations, _| {
|
||||
for (chat_id, locations_send_begin, locations_last_sent) in
|
||||
rows.filter_map(|r| match r {
|
||||
Ok(Some(v)) => Some(v),
|
||||
_ => None,
|
||||
})
|
||||
{
|
||||
// TODO: do I need to reset?
|
||||
if !stmt_locations
|
||||
.exists(params![1, locations_send_begin, locations_last_sent,])
|
||||
.unwrap_or_default()
|
||||
{
|
||||
// if there is no new location, there's nothing to send.
|
||||
// however, maybe we want to bypass this test eg. 15 minutes
|
||||
continue;
|
||||
}
|
||||
// pending locations are attached automatically to every message,
|
||||
// so also to this empty text message.
|
||||
// DC_CMD_LOCATION is only needed to create a nicer subject.
|
||||
//
|
||||
// for optimisation and to avoid flooding the sending queue,
|
||||
// we could sending these messages only if we're really online.
|
||||
// the easiest way to determine this, is to check for an empty message queue.
|
||||
// (might not be 100%, however, as positions are sent combined later
|
||||
// and dc_set_location() is typically called periodically, this is ok)
|
||||
let mut msg = dc_msg_new(context, Viewtype::Text);
|
||||
msg.hidden = true;
|
||||
msg.param.set_int(Param::Cmd, 9);
|
||||
// TODO: handle cleanup on error
|
||||
chat::send_msg(context, chat_id as u32, &mut msg).unwrap();
|
||||
}
|
||||
Ok(())
|
||||
},
|
||||
)
|
||||
},
|
||||
)
|
||||
.unwrap(); // TODO: Better error handling
|
||||
|
||||
if 0 != continue_streaming {
|
||||
schedule_MAYBE_SEND_LOCATIONS(context, 0x1);
|
||||
}
|
||||
}
|
||||
|
||||
#[allow(non_snake_case)]
|
||||
pub fn job_do_DC_JOB_MAYBE_SEND_LOC_ENDED(context: &Context, job: &mut Job) {
|
||||
// this function is called when location-streaming _might_ have ended for a chat.
|
||||
// the function checks, if location-streaming is really ended;
|
||||
// if so, a device-message is added if not yet done.
|
||||
|
||||
let chat_id = job.foreign_id;
|
||||
|
||||
if let Ok((send_begin, send_until)) = context.sql.query_row(
|
||||
"SELECT locations_send_begin, locations_send_until FROM chats WHERE id=?",
|
||||
params![chat_id as i32],
|
||||
|row| Ok((row.get::<_, i64>(0)?, row.get::<_, i64>(1)?)),
|
||||
) {
|
||||
if !(send_begin != 0 && time() <= send_until) {
|
||||
// still streaming -
|
||||
// may happen as several calls to dc_send_locations_to_chat()
|
||||
// do not un-schedule pending DC_MAYBE_SEND_LOC_ENDED jobs
|
||||
if !(send_begin == 0 && send_until == 0) {
|
||||
// not streaming, device-message already sent
|
||||
if context.sql.execute(
|
||||
"UPDATE chats SET locations_send_begin=0, locations_send_until=0 WHERE id=?",
|
||||
params![chat_id as i32],
|
||||
).is_ok() {
|
||||
let stock_str = context.stock_system_msg(StockMessage::MsgLocationDisabled, "", "", 0);
|
||||
chat::add_device_msg(context, chat_id, stock_str);
|
||||
context.call_cb(
|
||||
Event::CHAT_MODIFIED,
|
||||
chat_id as usize,
|
||||
0,
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
use crate::test_utils::dummy_context;
|
||||
|
||||
#[test]
|
||||
fn test_kml_parse() {
|
||||
let context = dummy_context();
|
||||
|
||||
let xml =
|
||||
"<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<kml xmlns=\"http://www.opengis.net/kml/2.2\">\n<Document addr=\"user@example.org\">\n<Placemark><Timestamp><when>2019-03-06T21:09:57Z</when></Timestamp><Point><coordinates accuracy=\"32.000000\">9.423110,53.790302</coordinates></Point></Placemark>\n<PlaceMARK>\n<Timestamp><WHEN > \n\t2018-12-13T22:11:12Z\t</WHEN></Timestamp><Point><coordinates aCCuracy=\"2.500000\"> 19.423110 \t , \n 63.790302\n </coordinates></Point></PlaceMARK>\n</Document>\n</kml>";
|
||||
|
||||
let kml = Kml::parse(&context.ctx, &xml).expect("parsing failed");
|
||||
|
||||
assert!(kml.addr.is_some());
|
||||
assert_eq!(kml.addr.as_ref().unwrap(), "user@example.org",);
|
||||
|
||||
let locations_ref = &kml.locations;
|
||||
assert_eq!(locations_ref.len(), 2);
|
||||
|
||||
assert!(locations_ref[0].latitude > 53.6f64);
|
||||
assert!(locations_ref[0].latitude < 53.8f64);
|
||||
assert!(locations_ref[0].longitude > 9.3f64);
|
||||
assert!(locations_ref[0].longitude < 9.5f64);
|
||||
assert!(locations_ref[0].accuracy > 31.9f64);
|
||||
assert!(locations_ref[0].accuracy < 32.1f64);
|
||||
assert_eq!(locations_ref[0].timestamp, 1551906597);
|
||||
|
||||
assert!(locations_ref[1].latitude > 63.6f64);
|
||||
assert!(locations_ref[1].latitude < 63.8f64);
|
||||
assert!(locations_ref[1].longitude > 19.3f64);
|
||||
assert!(locations_ref[1].longitude < 19.5f64);
|
||||
assert!(locations_ref[1].accuracy > 2.4f64);
|
||||
assert!(locations_ref[1].accuracy < 2.6f64);
|
||||
assert_eq!(locations_ref[1].timestamp, 1544739072);
|
||||
}
|
||||
}
|
||||
@@ -57,3 +57,10 @@ macro_rules! log_event {
|
||||
formatted_c.as_ptr() as libc::uintptr_t);
|
||||
}};
|
||||
}
|
||||
|
||||
#[macro_export]
|
||||
macro_rules! emit_event {
|
||||
($ctx:expr, $event:expr, $data1:expr, $data2:expr) => {
|
||||
$ctx.call_cb($event, $data1 as libc::uintptr_t, $data2 as libc::uintptr_t);
|
||||
};
|
||||
}
|
||||
|
||||
206
src/login_param.rs
Normal file
206
src/login_param.rs
Normal file
@@ -0,0 +1,206 @@
|
||||
use std::borrow::Cow;
|
||||
use std::fmt;
|
||||
|
||||
use crate::context::Context;
|
||||
use crate::error::Error;
|
||||
|
||||
#[derive(Default, Debug)]
|
||||
pub struct LoginParam {
|
||||
pub addr: String,
|
||||
pub mail_server: String,
|
||||
pub mail_user: String,
|
||||
pub mail_pw: String,
|
||||
pub mail_port: i32,
|
||||
pub send_server: String,
|
||||
pub send_user: String,
|
||||
pub send_pw: String,
|
||||
pub send_port: i32,
|
||||
pub server_flags: i32,
|
||||
}
|
||||
|
||||
impl LoginParam {
|
||||
/// Create a new `LoginParam` with default values.
|
||||
pub fn new() -> Self {
|
||||
Default::default()
|
||||
}
|
||||
|
||||
/// Read the login parameters from the database.
|
||||
pub fn from_database(context: &Context, prefix: impl AsRef<str>) -> Self {
|
||||
let prefix = prefix.as_ref();
|
||||
let sql = &context.sql;
|
||||
|
||||
let key = format!("{}addr", prefix);
|
||||
let addr = sql
|
||||
.get_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 key = format!("{}mail_port", prefix);
|
||||
let mail_port = sql.get_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 key = format!("{}mail_pw", prefix);
|
||||
let mail_pw = sql.get_config(context, key).unwrap_or_default();
|
||||
|
||||
let key = format!("{}send_server", prefix);
|
||||
let send_server = sql.get_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 key = format!("{}send_user", prefix);
|
||||
let send_user = sql.get_config(context, key).unwrap_or_default();
|
||||
|
||||
let key = format!("{}send_pw", prefix);
|
||||
let send_pw = sql.get_config(context, key).unwrap_or_default();
|
||||
|
||||
let key = format!("{}server_flags", prefix);
|
||||
let server_flags = sql.get_config_int(context, key).unwrap_or_default();
|
||||
|
||||
LoginParam {
|
||||
addr: addr.to_string(),
|
||||
mail_server,
|
||||
mail_user,
|
||||
mail_pw,
|
||||
mail_port,
|
||||
send_server,
|
||||
send_user,
|
||||
send_pw,
|
||||
send_port,
|
||||
server_flags,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn addr_str(&self) -> &str {
|
||||
self.addr.as_str()
|
||||
}
|
||||
|
||||
/// Save this loginparam to the database.
|
||||
pub fn save_to_database(
|
||||
&self,
|
||||
context: &Context,
|
||||
prefix: impl AsRef<str>,
|
||||
) -> Result<(), Error> {
|
||||
let prefix = prefix.as_ref();
|
||||
let sql = &context.sql;
|
||||
|
||||
let key = format!("{}addr", prefix);
|
||||
sql.set_config(context, key, Some(&self.addr))?;
|
||||
|
||||
let key = format!("{}mail_server", prefix);
|
||||
sql.set_config(context, key, Some(&self.mail_server))?;
|
||||
|
||||
let key = format!("{}mail_port", prefix);
|
||||
sql.set_config_int(context, key, self.mail_port)?;
|
||||
|
||||
let key = format!("{}mail_user", prefix);
|
||||
sql.set_config(context, key, Some(&self.mail_user))?;
|
||||
|
||||
let key = format!("{}mail_pw", prefix);
|
||||
sql.set_config(context, key, Some(&self.mail_pw))?;
|
||||
|
||||
let key = format!("{}send_server", prefix);
|
||||
sql.set_config(context, key, Some(&self.send_server))?;
|
||||
|
||||
let key = format!("{}send_port", prefix);
|
||||
sql.set_config_int(context, key, self.send_port)?;
|
||||
|
||||
let key = format!("{}send_user", prefix);
|
||||
sql.set_config(context, key, Some(&self.send_user))?;
|
||||
|
||||
let key = format!("{}send_pw", prefix);
|
||||
sql.set_config(context, key, Some(&self.send_pw))?;
|
||||
|
||||
let key = format!("{}server_flags", prefix);
|
||||
sql.set_config_int(context, key, self.server_flags)?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
impl fmt::Display for LoginParam {
|
||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||
let unset = "0";
|
||||
let pw = "***";
|
||||
|
||||
let flags_readable = get_readable_flags(self.server_flags);
|
||||
|
||||
write!(
|
||||
f,
|
||||
"{} {}:{}:{}:{} {}:{}:{}:{} {}",
|
||||
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,
|
||||
unset_empty(&self.send_user),
|
||||
if !self.send_pw.is_empty() { pw } else { unset },
|
||||
unset_empty(&self.send_server),
|
||||
self.send_port,
|
||||
flags_readable,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
fn unset_empty(s: &String) -> Cow<String> {
|
||||
if s.is_empty() {
|
||||
Cow::Owned("unset".to_string())
|
||||
} else {
|
||||
Cow::Borrowed(s)
|
||||
}
|
||||
}
|
||||
|
||||
fn get_readable_flags(flags: i32) -> String {
|
||||
let mut res = String::new();
|
||||
for bit in 0..31 {
|
||||
if 0 != flags & 1 << bit {
|
||||
let mut flag_added = 0;
|
||||
if 1 << bit == 0x2 {
|
||||
res += "OAUTH2 ";
|
||||
flag_added = 1;
|
||||
}
|
||||
if 1 << bit == 0x4 {
|
||||
res += "AUTH_NORMAL ";
|
||||
flag_added = 1;
|
||||
}
|
||||
if 1 << bit == 0x100 {
|
||||
res += "IMAP_STARTTLS ";
|
||||
flag_added = 1;
|
||||
}
|
||||
if 1 << bit == 0x200 {
|
||||
res += "IMAP_SSL ";
|
||||
flag_added = 1;
|
||||
}
|
||||
if 1 << bit == 0x400 {
|
||||
res += "IMAP_PLAIN ";
|
||||
flag_added = 1;
|
||||
}
|
||||
if 1 << bit == 0x10000 {
|
||||
res += "SMTP_STARTTLS ";
|
||||
flag_added = 1
|
||||
}
|
||||
if 1 << bit == 0x20000 {
|
||||
res += "SMTP_SSL ";
|
||||
flag_added = 1
|
||||
}
|
||||
if 1 << bit == 0x40000 {
|
||||
res += "SMTP_PLAIN ";
|
||||
flag_added = 1
|
||||
}
|
||||
if 0 == flag_added {
|
||||
res += &format!("{:#0x}", 1 << bit);
|
||||
}
|
||||
}
|
||||
}
|
||||
if res.is_empty() {
|
||||
res += "0";
|
||||
}
|
||||
|
||||
res
|
||||
}
|
||||
111
src/message.rs
111
src/message.rs
@@ -38,6 +38,12 @@ pub enum MessageState {
|
||||
OutMdnRcvd = 28,
|
||||
}
|
||||
|
||||
impl Default for MessageState {
|
||||
fn default() -> Self {
|
||||
MessageState::Undefined
|
||||
}
|
||||
}
|
||||
|
||||
impl From<MessageState> for LotState {
|
||||
fn from(s: MessageState) -> Self {
|
||||
use MessageState::*;
|
||||
@@ -80,7 +86,7 @@ impl Lot {
|
||||
if msg.state == MessageState::OutDraft {
|
||||
self.text1 = Some(context.stock_str(StockMessage::Draft).to_owned().into());
|
||||
self.text1_meaning = Meaning::Text1Draft;
|
||||
} else if msg.from_id == DC_CONTACT_ID_SELF as u32 {
|
||||
} else if msg.from_id == DC_CONTACT_ID_SELF {
|
||||
if 0 != dc_msg_is_info(msg) || chat.is_self_talk() {
|
||||
self.text1 = None;
|
||||
self.text1_meaning = Meaning::None;
|
||||
@@ -93,7 +99,7 @@ impl Lot {
|
||||
self.text1 = None;
|
||||
self.text1_meaning = Meaning::None;
|
||||
} else {
|
||||
if chat.id == DC_CHAT_ID_DEADDROP as u32 {
|
||||
if chat.id == DC_CHAT_ID_DEADDROP {
|
||||
if let Some(contact) = contact {
|
||||
self.text1 = Some(contact.get_display_name().into());
|
||||
} else {
|
||||
@@ -159,7 +165,6 @@ pub struct Message<'a> {
|
||||
|
||||
// handle messages
|
||||
pub unsafe fn dc_get_msg_info(context: &Context, msg_id: u32) -> *mut libc::c_char {
|
||||
let mut p: *mut libc::c_char;
|
||||
let mut ret = String::new();
|
||||
|
||||
let msg = dc_msg_load_from_db(context, msg_id);
|
||||
@@ -208,33 +213,28 @@ pub unsafe fn dc_get_msg_info(context: &Context, msg_id: u32) -> *mut libc::c_ch
|
||||
return ret.strdup();
|
||||
}
|
||||
|
||||
context
|
||||
.sql
|
||||
.query_map(
|
||||
"SELECT contact_id, timestamp_sent FROM msgs_mdns WHERE msg_id=?;",
|
||||
params![msg_id as i32],
|
||||
|row| {
|
||||
let contact_id: i32 = row.get(0)?;
|
||||
let ts: i64 = row.get(1)?;
|
||||
Ok((contact_id, ts))
|
||||
},
|
||||
|rows| {
|
||||
for row in rows {
|
||||
let (contact_id, ts) = row?;
|
||||
let fts = dc_timestamp_to_str(ts);
|
||||
ret += &format!("Read: {}", fts);
|
||||
if let Ok(rows) = context.sql.query_map(
|
||||
"SELECT contact_id, timestamp_sent FROM msgs_mdns WHERE msg_id=?;",
|
||||
params![msg_id as i32],
|
||||
|row| {
|
||||
let contact_id: i32 = row.get(0)?;
|
||||
let ts: i64 = row.get(1)?;
|
||||
Ok((contact_id, ts))
|
||||
},
|
||||
|rows| rows.collect::<Result<Vec<_>, _>>().map_err(Into::into),
|
||||
) {
|
||||
for (contact_id, ts) in rows {
|
||||
let fts = dc_timestamp_to_str(ts);
|
||||
ret += &format!("Read: {}", fts);
|
||||
|
||||
let name = Contact::load_from_db(context, contact_id as u32)
|
||||
.map(|contact| contact.get_name_n_addr())
|
||||
.unwrap_or_default();
|
||||
let name = Contact::load_from_db(context, contact_id as u32)
|
||||
.map(|contact| contact.get_name_n_addr())
|
||||
.unwrap_or_default();
|
||||
|
||||
ret += &format!(" by {}", name);
|
||||
ret += "\n";
|
||||
}
|
||||
Ok(())
|
||||
},
|
||||
)
|
||||
.unwrap(); // TODO: better error handling
|
||||
ret += &format!(" by {}", name);
|
||||
ret += "\n";
|
||||
}
|
||||
}
|
||||
|
||||
ret += "State: ";
|
||||
use MessageState::*;
|
||||
@@ -270,7 +270,7 @@ pub unsafe fn dc_get_msg_info(context: &Context, msg_id: u32) -> *mut libc::c_ch
|
||||
_ => {}
|
||||
}
|
||||
|
||||
p = dc_msg_get_file(&msg);
|
||||
let p = dc_msg_get_file(&msg);
|
||||
if !p.is_null() && 0 != *p.offset(0isize) as libc::c_int {
|
||||
ret += &format!(
|
||||
"\nFile: {}, {}, bytes\n",
|
||||
@@ -284,9 +284,7 @@ pub unsafe fn dc_get_msg_info(context: &Context, msg_id: u32) -> *mut libc::c_ch
|
||||
ret += "Type: ";
|
||||
ret += &format!("{}", msg.type_0);
|
||||
ret += "\n";
|
||||
p = dc_msg_get_filemime(&msg);
|
||||
ret += &format!("Mimetype: {}\n", as_str(p));
|
||||
free(p as *mut libc::c_void);
|
||||
ret += &format!("Mimetype: {}\n", &dc_msg_get_filemime(&msg));
|
||||
}
|
||||
let w = msg.param.get_int(Param::Width).unwrap_or_default();
|
||||
let h = msg.param.get_int(Param::Height).unwrap_or_default();
|
||||
@@ -352,16 +350,16 @@ impl<'a> Drop for Message<'a> {
|
||||
}
|
||||
}
|
||||
|
||||
pub unsafe fn dc_msg_get_filemime(msg: &Message) -> *mut libc::c_char {
|
||||
pub fn dc_msg_get_filemime(msg: &Message) -> String {
|
||||
if let Some(m) = msg.param.get(Param::MimeType) {
|
||||
return m.strdup();
|
||||
return m.to_string();
|
||||
} else if let Some(file) = msg.param.get(Param::File) {
|
||||
if let Some((_, mime)) = dc_msg_guess_msgtype_from_suffix(Path::new(file)) {
|
||||
return mime.strdup();
|
||||
return mime.to_string();
|
||||
}
|
||||
}
|
||||
|
||||
"application/octet-stream".strdup()
|
||||
"application/octet-stream".to_string()
|
||||
}
|
||||
|
||||
pub fn dc_msg_guess_msgtype_from_suffix(path: &Path) -> Option<(Viewtype, &str)> {
|
||||
@@ -383,7 +381,7 @@ pub fn dc_msg_guess_msgtype_from_suffix(path: &Path) -> Option<(Viewtype, &str)>
|
||||
}
|
||||
|
||||
pub unsafe fn dc_msg_get_file(msg: &Message) -> *mut libc::c_char {
|
||||
let mut file_abs = 0 as *mut libc::c_char;
|
||||
let mut file_abs = ptr::null_mut();
|
||||
|
||||
if let Some(file_rel) = msg.param.get(Param::File) {
|
||||
file_abs = dc_get_abs_path(msg.context, file_rel);
|
||||
@@ -483,8 +481,7 @@ pub fn dc_msg_load_from_db<'a>(context: &'a Context, id: u32) -> Result<Message<
|
||||
text = String::from_utf8_lossy(buf).into_owned();
|
||||
}
|
||||
} else {
|
||||
warn!(context, 0, "dc_msg_load_from_db: could not get text column for id {}", id);
|
||||
text = "[ Could not read from db ]".to_string();
|
||||
text = "".to_string();
|
||||
}
|
||||
msg.text = Some(text);
|
||||
|
||||
@@ -692,7 +689,7 @@ pub unsafe fn dc_msg_get_text(msg: &Message) -> *mut libc::c_char {
|
||||
|
||||
#[allow(non_snake_case)]
|
||||
pub unsafe fn dc_msg_get_filename(msg: &Message) -> *mut libc::c_char {
|
||||
let mut ret = 0 as *mut libc::c_char;
|
||||
let mut ret = ptr::null_mut();
|
||||
|
||||
if let Some(file) = msg.param.get(Param::File) {
|
||||
ret = dc_get_filename(file);
|
||||
@@ -824,19 +821,21 @@ pub fn dc_msg_get_summarytext_by_raw(
|
||||
}
|
||||
}
|
||||
};
|
||||
let ret = if append_text && text.is_some() {
|
||||
let text = text.unwrap();
|
||||
if !prefix.is_empty() {
|
||||
|
||||
if !append_text {
|
||||
return prefix;
|
||||
}
|
||||
|
||||
if let Some(text) = text {
|
||||
if prefix.is_empty() {
|
||||
dc_truncate(text.as_ref(), approx_characters, true).to_string()
|
||||
} else {
|
||||
let tmp = format!("{} – {}", prefix, text.as_ref());
|
||||
dc_truncate(&tmp, approx_characters, true).to_string()
|
||||
} else {
|
||||
dc_truncate(text.as_ref(), approx_characters, true).to_string()
|
||||
}
|
||||
} else {
|
||||
prefix
|
||||
};
|
||||
|
||||
ret
|
||||
}
|
||||
}
|
||||
|
||||
pub unsafe fn dc_msg_has_deviating_timestamp(msg: &Message) -> libc::c_int {
|
||||
@@ -900,14 +899,14 @@ pub fn dc_msg_is_setupmessage(msg: &Message) -> bool {
|
||||
}
|
||||
|
||||
pub unsafe fn dc_msg_get_setupcodebegin(msg: &Message) -> *mut libc::c_char {
|
||||
let mut filename: *mut libc::c_char = 0 as *mut libc::c_char;
|
||||
let mut buf: *mut libc::c_char = 0 as *mut libc::c_char;
|
||||
let mut filename: *mut libc::c_char = ptr::null_mut();
|
||||
let mut buf: *mut libc::c_char = ptr::null_mut();
|
||||
let mut buf_bytes: size_t = 0i32 as size_t;
|
||||
// just a pointer inside buf, MUST NOT be free()'d
|
||||
let mut buf_headerline: *const libc::c_char = 0 as *const libc::c_char;
|
||||
let mut buf_headerline: *const libc::c_char = ptr::null();
|
||||
// just a pointer inside buf, MUST NOT be free()'d
|
||||
let mut buf_setupcodebegin: *const libc::c_char = 0 as *const libc::c_char;
|
||||
let mut ret: *mut libc::c_char = 0 as *mut libc::c_char;
|
||||
let mut buf_setupcodebegin: *const libc::c_char = ptr::null();
|
||||
let mut ret: *mut libc::c_char = ptr::null_mut();
|
||||
if dc_msg_is_setupmessage(msg) {
|
||||
filename = dc_msg_get_file(msg);
|
||||
if !(filename.is_null() || *filename.offset(0isize) as libc::c_int == 0i32) {
|
||||
@@ -925,8 +924,8 @@ pub unsafe fn dc_msg_get_setupcodebegin(msg: &Message) -> *mut libc::c_char {
|
||||
buf,
|
||||
&mut buf_headerline,
|
||||
&mut buf_setupcodebegin,
|
||||
0 as *mut *const libc::c_char,
|
||||
0 as *mut *const libc::c_char,
|
||||
ptr::null_mut(),
|
||||
ptr::null_mut(),
|
||||
) && strcmp(
|
||||
buf_headerline,
|
||||
b"-----BEGIN PGP MESSAGE-----\x00" as *const u8 as *const libc::c_char,
|
||||
@@ -1268,7 +1267,7 @@ pub fn dc_rfc724_mid_exists(
|
||||
Ok(res) => res,
|
||||
Err(_err) => {
|
||||
if !ret_server_folder.is_null() {
|
||||
unsafe { *ret_server_folder = 0 as *mut libc::c_char };
|
||||
unsafe { *ret_server_folder = ptr::null_mut() };
|
||||
}
|
||||
if !ret_server_uid.is_null() {
|
||||
unsafe { *ret_server_uid = 0 };
|
||||
|
||||
@@ -178,8 +178,11 @@ impl<'a> Peerstate<'a> {
|
||||
OR gossip_key_fingerprint=? COLLATE NOCASE \
|
||||
ORDER BY public_key_fingerprint=? DESC;";
|
||||
|
||||
let fp = fingerprint.as_bytes();
|
||||
Self::from_stmt(context, query, params![fp, fp, fp])
|
||||
Self::from_stmt(
|
||||
context,
|
||||
query,
|
||||
params![fingerprint, fingerprint, fingerprint],
|
||||
)
|
||||
}
|
||||
|
||||
fn from_stmt<P>(context: &'a Context, query: &str, params: P) -> Option<Self>
|
||||
@@ -523,6 +526,10 @@ mod tests {
|
||||
// clear to_save, as that is not persissted
|
||||
peerstate.to_save = None;
|
||||
assert_eq!(peerstate, peerstate_new);
|
||||
let peerstate_new2 =
|
||||
Peerstate::from_fingerprint(&ctx.ctx, &ctx.ctx.sql, &pub_key.fingerprint())
|
||||
.expect("failed to load peerstate from db");
|
||||
assert_eq!(peerstate, peerstate_new2);
|
||||
}
|
||||
|
||||
#[test]
|
||||
|
||||
@@ -33,7 +33,7 @@ pub unsafe fn dc_split_armored_data(
|
||||
let mut headerline: *mut libc::c_char = ptr::null_mut();
|
||||
let mut base64: *mut libc::c_char = ptr::null_mut();
|
||||
if !ret_headerline.is_null() {
|
||||
*ret_headerline = 0 as *const libc::c_char
|
||||
*ret_headerline = ptr::null()
|
||||
}
|
||||
if !ret_setupcodebegin.is_null() {
|
||||
*ret_setupcodebegin = ptr::null_mut();
|
||||
|
||||
782
src/securejoin.rs
Normal file
782
src/securejoin.rs
Normal file
@@ -0,0 +1,782 @@
|
||||
use mmime::mailimf_types::*;
|
||||
use percent_encoding::{utf8_percent_encode, AsciiSet, NON_ALPHANUMERIC};
|
||||
use std::ptr;
|
||||
|
||||
use crate::aheader::EncryptPreference;
|
||||
use crate::chat::{self, Chat};
|
||||
use crate::configure::*;
|
||||
use crate::constants::*;
|
||||
use crate::contact::*;
|
||||
use crate::context::Context;
|
||||
use crate::dc_mimeparser::*;
|
||||
use crate::dc_tools::*;
|
||||
use crate::e2ee::*;
|
||||
use crate::error::Error;
|
||||
use crate::key::*;
|
||||
use crate::lot::LotState;
|
||||
use crate::message::*;
|
||||
use crate::param::*;
|
||||
use crate::peerstate::*;
|
||||
use crate::qr::check_qr;
|
||||
use crate::stock::StockMessage;
|
||||
use crate::token;
|
||||
use crate::types::*;
|
||||
|
||||
pub const NON_ALPHANUMERIC_WITHOUT_DOT: &AsciiSet = &NON_ALPHANUMERIC.remove(b'.');
|
||||
|
||||
macro_rules! progress {
|
||||
($context:tt, $event:expr, $contact_id:expr, $progress:expr) => {
|
||||
assert!(
|
||||
$progress >= 0 && $progress <= 1000,
|
||||
"value in range 0..1000 expected with: 0=error, 1..999=progress, 1000=success"
|
||||
);
|
||||
$context.call_cb($event, $contact_id as uintptr_t, $progress as uintptr_t);
|
||||
};
|
||||
}
|
||||
|
||||
macro_rules! joiner_progress {
|
||||
($context:tt, $contact_id:expr, $progress:expr) => {
|
||||
progress!(
|
||||
$context,
|
||||
Event::SECUREJOIN_JOINER_PROGRESS,
|
||||
$contact_id,
|
||||
$progress
|
||||
);
|
||||
};
|
||||
}
|
||||
|
||||
macro_rules! inviter_progress {
|
||||
($context:tt, $contact_id:expr, $progress:expr) => {
|
||||
progress!(
|
||||
$context,
|
||||
Event::SECUREJOIN_INVITER_PROGRESS,
|
||||
$contact_id,
|
||||
$progress
|
||||
);
|
||||
};
|
||||
}
|
||||
|
||||
macro_rules! get_qr_attr {
|
||||
($context:tt, $attr:ident) => {
|
||||
$context
|
||||
.bob
|
||||
.read()
|
||||
.unwrap()
|
||||
.qr_scan
|
||||
.as_ref()
|
||||
.unwrap()
|
||||
.$attr
|
||||
.as_ref()
|
||||
.unwrap()
|
||||
};
|
||||
}
|
||||
|
||||
pub fn dc_get_securejoin_qr(context: &Context, group_chat_id: uint32_t) -> Option<String> {
|
||||
/* =========================================================
|
||||
==== Alice - the inviter side ====
|
||||
==== Step 1 in "Setup verified contact" protocol ====
|
||||
========================================================= */
|
||||
|
||||
let fingerprint: String;
|
||||
|
||||
ensure_secret_key_exists(context).ok();
|
||||
let invitenumber = token::lookup_or_new(context, token::Namespace::InviteNumber, group_chat_id);
|
||||
let auth = token::lookup_or_new(context, token::Namespace::Auth, group_chat_id);
|
||||
let self_addr = match context.sql.get_config(context, "configured_addr") {
|
||||
Some(addr) => addr,
|
||||
None => {
|
||||
error!(context, 0, "Not configured, cannot generate QR code.",);
|
||||
return None;
|
||||
}
|
||||
};
|
||||
|
||||
let self_name = context
|
||||
.sql
|
||||
.get_config(context, "displayname")
|
||||
.unwrap_or_default();
|
||||
|
||||
fingerprint = match get_self_fingerprint(context) {
|
||||
Some(fp) => fp,
|
||||
None => {
|
||||
return None;
|
||||
}
|
||||
};
|
||||
|
||||
let self_addr_urlencoded =
|
||||
utf8_percent_encode(&self_addr, NON_ALPHANUMERIC_WITHOUT_DOT).to_string();
|
||||
let self_name_urlencoded =
|
||||
utf8_percent_encode(&self_name, NON_ALPHANUMERIC_WITHOUT_DOT).to_string();
|
||||
|
||||
let qr = if 0 != group_chat_id {
|
||||
if let Ok(chat) = Chat::load_from_db(context, group_chat_id) {
|
||||
let group_name = chat.get_name();
|
||||
let group_name_urlencoded =
|
||||
utf8_percent_encode(&group_name, NON_ALPHANUMERIC).to_string();
|
||||
|
||||
Some(format!(
|
||||
"OPENPGP4FPR:{}#a={}&g={}&x={}&i={}&s={}",
|
||||
fingerprint,
|
||||
self_addr_urlencoded,
|
||||
&group_name_urlencoded,
|
||||
&chat.grpid,
|
||||
&invitenumber,
|
||||
&auth,
|
||||
))
|
||||
} else {
|
||||
error!(
|
||||
context,
|
||||
0, "Cannot get QR-code for chat-id {}", group_chat_id,
|
||||
);
|
||||
return None;
|
||||
}
|
||||
} else {
|
||||
Some(format!(
|
||||
"OPENPGP4FPR:{}#a={}&n={}&i={}&s={}",
|
||||
fingerprint, self_addr_urlencoded, self_name_urlencoded, &invitenumber, &auth,
|
||||
))
|
||||
};
|
||||
|
||||
info!(context, 0, "Generated QR code: {}", qr.as_ref().unwrap());
|
||||
|
||||
qr
|
||||
}
|
||||
|
||||
fn get_self_fingerprint(context: &Context) -> Option<String> {
|
||||
if let Some(self_addr) = context.sql.get_config(context, "configured_addr") {
|
||||
if let Some(key) = Key::from_self_public(context, self_addr, &context.sql) {
|
||||
return Some(key.fingerprint());
|
||||
}
|
||||
}
|
||||
None
|
||||
}
|
||||
|
||||
pub fn dc_join_securejoin(context: &Context, qr: &str) -> uint32_t {
|
||||
let cleanup =
|
||||
|context: &Context, contact_chat_id: u32, ongoing_allocated: bool, join_vg: bool| {
|
||||
let mut bob = context.bob.write().unwrap();
|
||||
bob.expects = 0;
|
||||
let ret_chat_id = if bob.status == DC_BOB_SUCCESS {
|
||||
if join_vg {
|
||||
chat::get_chat_id_by_grpid(
|
||||
context,
|
||||
bob.qr_scan.as_ref().unwrap().text2.as_ref().unwrap(),
|
||||
)
|
||||
.0
|
||||
} else {
|
||||
contact_chat_id
|
||||
}
|
||||
} else {
|
||||
0
|
||||
};
|
||||
bob.qr_scan = None;
|
||||
|
||||
if ongoing_allocated {
|
||||
dc_free_ongoing(context);
|
||||
}
|
||||
ret_chat_id as uint32_t
|
||||
};
|
||||
/* ==========================================================
|
||||
==== Bob - the joiner's side =====
|
||||
==== Step 2 in "Setup verified contact" protocol =====
|
||||
========================================================== */
|
||||
let mut contact_chat_id: uint32_t = 0;
|
||||
let mut join_vg: bool = false;
|
||||
|
||||
info!(context, 0, "Requesting secure-join ...",);
|
||||
ensure_secret_key_exists(context).ok();
|
||||
if !dc_alloc_ongoing(context) {
|
||||
return cleanup(&context, contact_chat_id, false, join_vg);
|
||||
}
|
||||
let qr_scan = check_qr(context, &qr);
|
||||
if qr_scan.state != LotState::QrAskVerifyContact && qr_scan.state != LotState::QrAskVerifyGroup
|
||||
{
|
||||
error!(context, 0, "Unknown QR code.",);
|
||||
return cleanup(&context, contact_chat_id, true, join_vg);
|
||||
}
|
||||
contact_chat_id = chat::create_by_contact_id(context, qr_scan.id).unwrap_or_default();
|
||||
if contact_chat_id == 0 {
|
||||
error!(context, 0, "Unknown contact.",);
|
||||
return cleanup(&context, contact_chat_id, true, join_vg);
|
||||
}
|
||||
if check_exit(context) {
|
||||
return cleanup(&context, contact_chat_id, true, join_vg);
|
||||
}
|
||||
join_vg = qr_scan.get_state() == LotState::QrAskVerifyGroup;
|
||||
{
|
||||
let mut bob = context.bob.write().unwrap();
|
||||
bob.status = 0;
|
||||
bob.qr_scan = Some(qr_scan);
|
||||
}
|
||||
if fingerprint_equals_sender(
|
||||
context,
|
||||
context
|
||||
.bob
|
||||
.read()
|
||||
.unwrap()
|
||||
.qr_scan
|
||||
.as_ref()
|
||||
.unwrap()
|
||||
.fingerprint
|
||||
.as_ref()
|
||||
.unwrap(),
|
||||
contact_chat_id,
|
||||
) {
|
||||
info!(context, 0, "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();
|
||||
send_handshake_msg(
|
||||
context,
|
||||
contact_chat_id,
|
||||
if join_vg {
|
||||
"vg-request-with-auth"
|
||||
} else {
|
||||
"vc-request-with-auth"
|
||||
},
|
||||
get_qr_attr!(context, auth).to_string(),
|
||||
Some(own_fingerprint),
|
||||
if join_vg {
|
||||
get_qr_attr!(context, text2).to_string()
|
||||
} else {
|
||||
"".to_string()
|
||||
},
|
||||
);
|
||||
} else {
|
||||
context.bob.write().unwrap().expects = DC_VC_AUTH_REQUIRED;
|
||||
send_handshake_msg(
|
||||
context,
|
||||
contact_chat_id,
|
||||
if join_vg { "vg-request" } else { "vc-request" },
|
||||
get_qr_attr!(context, invitenumber),
|
||||
None,
|
||||
"",
|
||||
);
|
||||
}
|
||||
|
||||
// Bob -> Alice
|
||||
while !check_exit(&context) {
|
||||
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: uint32_t,
|
||||
step: &str,
|
||||
param2: impl AsRef<str>,
|
||||
fingerprint: Option<String>,
|
||||
grpid: impl AsRef<str>,
|
||||
) {
|
||||
let mut msg = unsafe { dc_msg_new_untyped(context) };
|
||||
msg.type_0 = Viewtype::Text;
|
||||
msg.text = Some(format!("Secure-Join: {}", step));
|
||||
msg.hidden = true;
|
||||
msg.param.set_int(Param::Cmd, 7);
|
||||
if step.is_empty() {
|
||||
msg.param.remove(Param::Arg);
|
||||
} else {
|
||||
msg.param.set(Param::Arg, step);
|
||||
}
|
||||
if !param2.as_ref().is_empty() {
|
||||
msg.param.set(Param::Arg2, param2);
|
||||
}
|
||||
if let Some(fp) = fingerprint {
|
||||
msg.param.set(Param::Arg3, fp);
|
||||
}
|
||||
if !grpid.as_ref().is_empty() {
|
||||
msg.param.set(Param::Arg4, grpid.as_ref());
|
||||
}
|
||||
if step == "vg-request" || step == "vc-request" {
|
||||
msg.param.set_int(
|
||||
Param::ForcePlaintext,
|
||||
ForcePlaintext::AddAutocryptHeader as i32,
|
||||
);
|
||||
} else {
|
||||
msg.param.set_int(Param::GuranteeE2ee, 1);
|
||||
}
|
||||
// TODO. handle cleanup on error
|
||||
chat::send_msg(context, contact_chat_id, &mut msg).unwrap();
|
||||
}
|
||||
|
||||
fn chat_id_2_contact_id(context: &Context, contact_chat_id: uint32_t) -> uint32_t {
|
||||
let contacts = chat::get_chat_contacts(context, contact_chat_id);
|
||||
if contacts.len() == 1 {
|
||||
contacts[0]
|
||||
} else {
|
||||
0
|
||||
}
|
||||
}
|
||||
|
||||
fn fingerprint_equals_sender(
|
||||
context: &Context,
|
||||
fingerprint: impl AsRef<str>,
|
||||
contact_chat_id: u32,
|
||||
) -> bool {
|
||||
let contacts = chat::get_chat_contacts(context, contact_chat_id);
|
||||
|
||||
if contacts.len() == 1 {
|
||||
if let Ok(contact) = Contact::load_from_db(context, contacts[0]) {
|
||||
if let Some(peerstate) = Peerstate::from_addr(context, &context.sql, contact.get_addr())
|
||||
{
|
||||
let fingerprint_normalized = dc_normalize_fingerprint(fingerprint.as_ref());
|
||||
if peerstate.public_key_fingerprint.is_some()
|
||||
&& &fingerprint_normalized == peerstate.public_key_fingerprint.as_ref().unwrap()
|
||||
{
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
false
|
||||
}
|
||||
|
||||
/* library private: secure-join */
|
||||
pub fn handle_securejoin_handshake(
|
||||
context: &Context,
|
||||
mimeparser: &dc_mimeparser_t,
|
||||
contact_id: uint32_t,
|
||||
) -> libc::c_int {
|
||||
let own_fingerprint: String;
|
||||
|
||||
if contact_id <= DC_CONTACT_ID_LAST_SPECIAL {
|
||||
return 0;
|
||||
}
|
||||
let step = match lookup_field(mimeparser, "Secure-Join") {
|
||||
Some(s) => s,
|
||||
None => {
|
||||
return 0;
|
||||
}
|
||||
};
|
||||
info!(
|
||||
context,
|
||||
0, ">>>>>>>>>>>>>>>>>>>>>>>>> secure-join message \'{}\' received", step,
|
||||
);
|
||||
let (contact_chat_id, contact_chat_id_blocked) =
|
||||
chat::create_or_lookup_by_contact_id(context, contact_id, Blocked::Not).unwrap_or_default();
|
||||
|
||||
if contact_chat_id_blocked != Blocked::Not {
|
||||
chat::unblock(context, contact_chat_id);
|
||||
}
|
||||
let mut ret: libc::c_int = DC_HANDSHAKE_STOP_NORMAL_PROCESSING;
|
||||
let join_vg = step.starts_with("vg-");
|
||||
|
||||
match step.as_str() {
|
||||
"vg-request" | "vc-request" => {
|
||||
/* =========================================================
|
||||
==== Alice - the inviter side ====
|
||||
==== Step 3 in "Setup verified contact" protocol ====
|
||||
========================================================= */
|
||||
// this message may be unencrypted (Bob, the joinder and the sender, might not have Alice's key yet)
|
||||
// it just ensures, we have Bobs key now. If we do _not_ have the key because eg. MitM has removed it,
|
||||
// send_message() will fail with the error "End-to-end-encryption unavailable unexpectedly.", so, there is no additional check needed here.
|
||||
// verify that the `Secure-Join-Invitenumber:`-header matches invitenumber written to the QR code
|
||||
let invitenumber = match lookup_field(mimeparser, "Secure-Join-Invitenumber") {
|
||||
Some(n) => n,
|
||||
None => {
|
||||
warn!(context, 0, "Secure-join denied (invitenumber missing).",);
|
||||
return ret;
|
||||
}
|
||||
};
|
||||
if !token::exists(context, token::Namespace::InviteNumber, &invitenumber) {
|
||||
warn!(context, 0, "Secure-join denied (bad invitenumber).",);
|
||||
return ret;
|
||||
}
|
||||
info!(context, 0, "Secure-join requested.",);
|
||||
|
||||
inviter_progress!(context, contact_id, 300);
|
||||
send_handshake_msg(
|
||||
context,
|
||||
contact_chat_id,
|
||||
&format!("{}-auth-required", &step[..2]),
|
||||
"",
|
||||
None,
|
||||
"",
|
||||
);
|
||||
}
|
||||
"vg-auth-required" | "vc-auth-required" => {
|
||||
let cond = {
|
||||
let bob = context.bob.read().unwrap();
|
||||
let scan = bob.qr_scan.as_ref();
|
||||
scan.is_none()
|
||||
|| bob.expects != DC_VC_AUTH_REQUIRED
|
||||
|| join_vg && scan.unwrap().state != LotState::QrAskVerifyGroup
|
||||
};
|
||||
|
||||
if cond {
|
||||
warn!(context, 0, "auth-required message out of sync.",);
|
||||
// no error, just aborted somehow or a mail from another handshake
|
||||
return ret;
|
||||
}
|
||||
let scanned_fingerprint_of_alice = get_qr_attr!(context, fingerprint).to_string();
|
||||
let auth = get_qr_attr!(context, auth).to_string();
|
||||
|
||||
if !encrypted_and_signed(mimeparser, &scanned_fingerprint_of_alice) {
|
||||
could_not_establish_secure_connection(
|
||||
context,
|
||||
contact_chat_id,
|
||||
if mimeparser.e2ee_helper.encrypted {
|
||||
"No valid signature."
|
||||
} else {
|
||||
"Not encrypted."
|
||||
},
|
||||
);
|
||||
end_bobs_joining(context, DC_BOB_ERROR);
|
||||
return ret;
|
||||
}
|
||||
if !fingerprint_equals_sender(context, &scanned_fingerprint_of_alice, contact_chat_id) {
|
||||
could_not_establish_secure_connection(
|
||||
context,
|
||||
contact_chat_id,
|
||||
"Fingerprint mismatch on joiner-side.",
|
||||
);
|
||||
end_bobs_joining(context, DC_BOB_ERROR);
|
||||
return ret;
|
||||
}
|
||||
info!(context, 0, "Fingerprint verified.",);
|
||||
own_fingerprint = get_self_fingerprint(context).unwrap();
|
||||
joiner_progress!(context, contact_id, 400);
|
||||
context.bob.write().unwrap().expects = DC_VC_CONTACT_CONFIRM;
|
||||
|
||||
send_handshake_msg(
|
||||
context,
|
||||
contact_chat_id,
|
||||
&format!("{}-request-with-auth", &step[..2]),
|
||||
auth,
|
||||
Some(own_fingerprint),
|
||||
if join_vg {
|
||||
get_qr_attr!(context, text2).to_string()
|
||||
} else {
|
||||
"".to_string()
|
||||
},
|
||||
);
|
||||
}
|
||||
"vg-request-with-auth" | "vc-request-with-auth" => {
|
||||
/* ============================================================
|
||||
==== Alice - the inviter side ====
|
||||
==== Steps 5+6 in "Setup verified contact" protocol ====
|
||||
==== Step 6 in "Out-of-band verified groups" protocol ====
|
||||
============================================================ */
|
||||
// verify that Secure-Join-Fingerprint:-header matches the fingerprint of Bob
|
||||
let fingerprint = match lookup_field(mimeparser, "Secure-Join-Fingerprint") {
|
||||
Some(fp) => fp,
|
||||
None => {
|
||||
could_not_establish_secure_connection(
|
||||
context,
|
||||
contact_chat_id,
|
||||
"Fingerprint not provided.",
|
||||
);
|
||||
return ret;
|
||||
}
|
||||
};
|
||||
if !encrypted_and_signed(mimeparser, &fingerprint) {
|
||||
could_not_establish_secure_connection(
|
||||
context,
|
||||
contact_chat_id,
|
||||
"Auth not encrypted.",
|
||||
);
|
||||
return ret;
|
||||
}
|
||||
if !fingerprint_equals_sender(context, &fingerprint, contact_chat_id) {
|
||||
could_not_establish_secure_connection(
|
||||
context,
|
||||
contact_chat_id,
|
||||
"Fingerprint mismatch on inviter-side.",
|
||||
);
|
||||
return ret;
|
||||
}
|
||||
info!(context, 0, "Fingerprint verified.",);
|
||||
// verify that the `Secure-Join-Auth:`-header matches the secret written to the QR code
|
||||
let auth_0 = match lookup_field(mimeparser, "Secure-Join-Auth") {
|
||||
Some(auth) => auth,
|
||||
None => {
|
||||
could_not_establish_secure_connection(
|
||||
context,
|
||||
contact_chat_id,
|
||||
"Auth not provided.",
|
||||
);
|
||||
return ret;
|
||||
}
|
||||
};
|
||||
if !token::exists(context, token::Namespace::Auth, &auth_0) {
|
||||
could_not_establish_secure_connection(context, contact_chat_id, "Auth invalid.");
|
||||
return ret;
|
||||
}
|
||||
if mark_peer_as_verified(context, fingerprint).is_err() {
|
||||
could_not_establish_secure_connection(
|
||||
context,
|
||||
contact_chat_id,
|
||||
"Fingerprint mismatch on inviter-side.",
|
||||
);
|
||||
return ret;
|
||||
}
|
||||
Contact::scaleup_origin_by_id(context, contact_id, Origin::SecurejoinInvited);
|
||||
info!(context, 0, "Auth verified.",);
|
||||
secure_connection_established(context, contact_chat_id);
|
||||
emit_event!(context, Event::CONTACTS_CHANGED, contact_id, 0);
|
||||
inviter_progress!(context, contact_id, 600);
|
||||
if join_vg {
|
||||
let field_grpid = lookup_field(mimeparser, "Secure-Join-Group").unwrap_or_default();
|
||||
let (group_chat_id, _, _) = chat::get_chat_id_by_grpid(context, &field_grpid);
|
||||
if group_chat_id == 0 {
|
||||
error!(context, 0, "Chat {} not found.", &field_grpid);
|
||||
return ret;
|
||||
} else {
|
||||
chat::add_contact_to_chat_ex(context, group_chat_id, contact_id, 0x1i32);
|
||||
}
|
||||
} else {
|
||||
send_handshake_msg(context, contact_chat_id, "vc-contact-confirm", "", None, "");
|
||||
inviter_progress!(context, contact_id, 1000);
|
||||
}
|
||||
}
|
||||
"vg-member-added" | "vc-contact-confirm" => {
|
||||
if join_vg {
|
||||
ret = DC_HANDSHAKE_CONTINUE_NORMAL_PROCESSING;
|
||||
}
|
||||
if context.bob.read().unwrap().expects != DC_VC_CONTACT_CONFIRM {
|
||||
info!(context, 0, "Message belongs to a different handshake.",);
|
||||
return ret;
|
||||
}
|
||||
let cond = {
|
||||
let bob = context.bob.read().unwrap();
|
||||
let scan = bob.qr_scan.as_ref();
|
||||
scan.is_none() || join_vg && scan.unwrap().state != LotState::QrAskVerifyGroup
|
||||
};
|
||||
if cond {
|
||||
warn!(
|
||||
context,
|
||||
0, "Message out of sync or belongs to a different handshake.",
|
||||
);
|
||||
return ret;
|
||||
}
|
||||
let scanned_fingerprint_of_alice = get_qr_attr!(context, fingerprint).to_string();
|
||||
|
||||
let vg_expect_encrypted = if join_vg {
|
||||
let group_id = get_qr_attr!(context, text2).to_string();
|
||||
let (_, is_verified_group, _) = chat::get_chat_id_by_grpid(context, group_id);
|
||||
// when joining a non-verified group
|
||||
// the vg-member-added message may be unencrypted
|
||||
// when not all group members have keys or prefer encryption.
|
||||
// So only expect encryption if this is a verified group
|
||||
is_verified_group
|
||||
} else {
|
||||
// setup contact is always encrypted
|
||||
true
|
||||
};
|
||||
if vg_expect_encrypted
|
||||
&& !encrypted_and_signed(mimeparser, &scanned_fingerprint_of_alice)
|
||||
{
|
||||
could_not_establish_secure_connection(
|
||||
context,
|
||||
contact_chat_id,
|
||||
"Contact confirm message not encrypted.",
|
||||
);
|
||||
end_bobs_joining(context, DC_BOB_ERROR);
|
||||
return ret;
|
||||
}
|
||||
|
||||
if mark_peer_as_verified(context, &scanned_fingerprint_of_alice).is_err() {
|
||||
could_not_establish_secure_connection(
|
||||
context,
|
||||
contact_chat_id,
|
||||
"Fingerprint mismatch on joiner-side.",
|
||||
);
|
||||
return ret;
|
||||
}
|
||||
Contact::scaleup_origin_by_id(context, contact_id, Origin::SecurejoinJoined);
|
||||
emit_event!(context, Event::CONTACTS_CHANGED, 0, 0);
|
||||
let cg_member_added =
|
||||
lookup_field(mimeparser, "Chat-Group-Member-Added").unwrap_or_default();
|
||||
if join_vg && !addr_equals_self(context, cg_member_added) {
|
||||
info!(context, 0, "Message belongs to a different handshake (scaled up contact anyway to allow creation of group).");
|
||||
return ret;
|
||||
}
|
||||
secure_connection_established(context, contact_chat_id);
|
||||
context.bob.write().unwrap().expects = 0;
|
||||
if join_vg {
|
||||
send_handshake_msg(
|
||||
context,
|
||||
contact_chat_id,
|
||||
"vg-member-added-received",
|
||||
"",
|
||||
None,
|
||||
"",
|
||||
);
|
||||
}
|
||||
end_bobs_joining(context, DC_BOB_SUCCESS);
|
||||
}
|
||||
"vg-member-added-received" => {
|
||||
/* ============================================================
|
||||
==== Alice - the inviter side ====
|
||||
==== Step 8 in "Out-of-band verified groups" protocol ====
|
||||
============================================================ */
|
||||
if let Ok(contact) = Contact::get_by_id(context, contact_id) {
|
||||
if contact.is_verified() == VerifiedStatus::Unverified {
|
||||
warn!(context, 0, "vg-member-added-received invalid.",);
|
||||
return ret;
|
||||
}
|
||||
inviter_progress!(context, contact_id, 800);
|
||||
inviter_progress!(context, contact_id, 1000);
|
||||
} else {
|
||||
warn!(context, 0, "vg-member-added-received invalid.",);
|
||||
return ret;
|
||||
}
|
||||
}
|
||||
_ => {
|
||||
warn!(context, 0, "invalid step: {}", step);
|
||||
}
|
||||
}
|
||||
if ret == DC_HANDSHAKE_STOP_NORMAL_PROCESSING {
|
||||
ret |= DC_HANDSHAKE_ADD_DELETE_JOB;
|
||||
}
|
||||
ret
|
||||
}
|
||||
|
||||
fn end_bobs_joining(context: &Context, status: libc::c_int) {
|
||||
context.bob.write().unwrap().status = status;
|
||||
dc_stop_ongoing_process(context);
|
||||
}
|
||||
|
||||
fn secure_connection_established(context: &Context, contact_chat_id: uint32_t) {
|
||||
let contact_id: uint32_t = chat_id_2_contact_id(context, contact_chat_id);
|
||||
let contact = Contact::get_by_id(context, contact_id);
|
||||
let addr = if let Ok(ref contact) = contact {
|
||||
contact.get_addr()
|
||||
} else {
|
||||
"?"
|
||||
};
|
||||
let msg = context.stock_string_repl_str(StockMessage::ContactVerified, addr);
|
||||
chat::add_device_msg(context, contact_chat_id, msg);
|
||||
emit_event!(context, Event::CHAT_MODIFIED, contact_chat_id, 0);
|
||||
}
|
||||
|
||||
fn lookup_field(mimeparser: &dc_mimeparser_t, key: &str) -> Option<String> {
|
||||
let field: *mut mailimf_field = dc_mimeparser_lookup_field(mimeparser, key);
|
||||
unsafe {
|
||||
let mut value: *const libc::c_char = ptr::null();
|
||||
if field.is_null()
|
||||
|| (*field).fld_type != MAILIMF_FIELD_OPTIONAL_FIELD as libc::c_int
|
||||
|| (*field).fld_data.fld_optional_field.is_null()
|
||||
|| {
|
||||
value = (*(*field).fld_data.fld_optional_field).fld_value;
|
||||
value.is_null()
|
||||
}
|
||||
{
|
||||
return None;
|
||||
}
|
||||
Some(as_str(value).to_string())
|
||||
}
|
||||
}
|
||||
|
||||
fn could_not_establish_secure_connection(
|
||||
context: &Context,
|
||||
contact_chat_id: uint32_t,
|
||||
details: &str,
|
||||
) {
|
||||
let contact_id = chat_id_2_contact_id(context, contact_chat_id);
|
||||
let contact = Contact::get_by_id(context, contact_id);
|
||||
let msg = context.stock_string_repl_str(
|
||||
StockMessage::ContactNotVerified,
|
||||
if let Ok(ref contact) = contact {
|
||||
contact.get_addr()
|
||||
} else {
|
||||
"?"
|
||||
},
|
||||
);
|
||||
|
||||
chat::add_device_msg(context, contact_chat_id, &msg);
|
||||
error!(context, 0, "{} ({})", &msg, details);
|
||||
}
|
||||
|
||||
fn mark_peer_as_verified(context: &Context, fingerprint: impl AsRef<str>) -> Result<(), Error> {
|
||||
if let Some(ref mut peerstate) =
|
||||
Peerstate::from_fingerprint(context, &context.sql, fingerprint.as_ref())
|
||||
{
|
||||
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);
|
||||
return Ok(());
|
||||
}
|
||||
}
|
||||
bail!(
|
||||
"could not mark peer as verified for fingerprint {}",
|
||||
fingerprint.as_ref()
|
||||
);
|
||||
}
|
||||
|
||||
/* ******************************************************************************
|
||||
* Tools: Misc.
|
||||
******************************************************************************/
|
||||
|
||||
fn encrypted_and_signed(
|
||||
mimeparser: &dc_mimeparser_t,
|
||||
expected_fingerprint: impl AsRef<str>,
|
||||
) -> bool {
|
||||
if !mimeparser.e2ee_helper.encrypted {
|
||||
warn!(mimeparser.context, 0, "Message not encrypted.",);
|
||||
false
|
||||
} else if mimeparser.e2ee_helper.signatures.len() <= 0 {
|
||||
warn!(mimeparser.context, 0, "Message not signed.",);
|
||||
false
|
||||
} else if expected_fingerprint.as_ref().is_empty() {
|
||||
warn!(mimeparser.context, 0, "Fingerprint for comparison missing.",);
|
||||
false
|
||||
} else if !mimeparser
|
||||
.e2ee_helper
|
||||
.signatures
|
||||
.contains(expected_fingerprint.as_ref())
|
||||
{
|
||||
warn!(
|
||||
mimeparser.context,
|
||||
0,
|
||||
"Message does not match expected fingerprint {}.",
|
||||
expected_fingerprint.as_ref(),
|
||||
);
|
||||
false
|
||||
} else {
|
||||
true
|
||||
}
|
||||
}
|
||||
|
||||
pub fn handle_degrade_event(context: &Context, peerstate: &Peerstate) {
|
||||
// - we do not issue an warning for DC_DE_ENCRYPTION_PAUSED as this is quite normal
|
||||
// - currently, we do not issue an extra warning for DC_DE_VERIFICATION_LOST - this always comes
|
||||
// together with DC_DE_FINGERPRINT_CHANGED which is logged, the idea is not to bother
|
||||
// with things they cannot fix, so the user is just kicked from the verified group
|
||||
// (and he will know this and can fix this)
|
||||
if Some(DegradeEvent::FingerprintChanged) == peerstate.degrade_event {
|
||||
let contact_id: i32 = context
|
||||
.sql
|
||||
.query_row_col(
|
||||
context,
|
||||
"SELECT id FROM contacts WHERE addr=?;",
|
||||
params![&peerstate.addr],
|
||||
0,
|
||||
)
|
||||
.unwrap_or_default();
|
||||
if contact_id > 0 {
|
||||
let (contact_chat_id, _) =
|
||||
chat::create_or_lookup_by_contact_id(context, contact_id as u32, Blocked::Deaddrop)
|
||||
.unwrap_or_default();
|
||||
|
||||
let peeraddr: &str = match peerstate.addr {
|
||||
Some(ref addr) => &addr,
|
||||
None => "",
|
||||
};
|
||||
let msg = context.stock_string_repl_str(StockMessage::ContactSetupChanged, peeraddr);
|
||||
|
||||
chat::add_device_msg(context, contact_chat_id, msg);
|
||||
emit_event!(context, Event::CHAT_MODIFIED, contact_chat_id, 0);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -4,7 +4,7 @@ use lettre::*;
|
||||
use crate::constants::Event;
|
||||
use crate::constants::*;
|
||||
use crate::context::Context;
|
||||
use crate::dc_loginparam::*;
|
||||
use crate::login_param::LoginParam;
|
||||
use crate::oauth2::*;
|
||||
|
||||
pub struct Smtp {
|
||||
@@ -12,7 +12,7 @@ pub struct Smtp {
|
||||
transport_connected: bool,
|
||||
/// Email address we are sending from.
|
||||
from: Option<EmailAddress>,
|
||||
pub error: *mut libc::c_char,
|
||||
pub error: Option<String>,
|
||||
}
|
||||
|
||||
impl Smtp {
|
||||
@@ -22,7 +22,7 @@ impl Smtp {
|
||||
transport: None,
|
||||
transport_connected: false,
|
||||
from: None,
|
||||
error: std::ptr::null_mut(),
|
||||
error: None,
|
||||
}
|
||||
}
|
||||
|
||||
@@ -43,7 +43,7 @@ impl Smtp {
|
||||
}
|
||||
|
||||
/// Connect using the provided login params
|
||||
pub fn connect(&mut self, context: &Context, lp: &dc_loginparam_t) -> bool {
|
||||
pub fn connect(&mut self, context: &Context, lp: &LoginParam) -> bool {
|
||||
if self.is_connected() {
|
||||
warn!(context, 0, "SMTP already connected.");
|
||||
return true;
|
||||
@@ -152,6 +152,7 @@ impl Smtp {
|
||||
}
|
||||
Err(err) => {
|
||||
warn!(context, 0, "SMTP failed to send message: {}", err);
|
||||
self.error = Some(format!("{}", err));
|
||||
0
|
||||
}
|
||||
}
|
||||
|
||||
51
src/sql.rs
51
src/sql.rs
@@ -38,13 +38,16 @@ impl Sql {
|
||||
info!(context, 0, "Database closed.");
|
||||
}
|
||||
|
||||
pub fn open(
|
||||
&self,
|
||||
context: &Context,
|
||||
dbfile: &std::path::Path,
|
||||
flags: libc::c_int,
|
||||
) -> Result<()> {
|
||||
open(context, self, dbfile, flags)
|
||||
// return true on success, false on failure
|
||||
pub fn open(&self, context: &Context, dbfile: &std::path::Path, flags: libc::c_int) -> bool {
|
||||
match open(context, self, dbfile, flags) {
|
||||
Ok(_) => true,
|
||||
Err(Error::SqlAlreadyOpen) => false,
|
||||
Err(_) => {
|
||||
self.close(context);
|
||||
false
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub fn execute<P>(&self, sql: &str, params: P) -> Result<usize>
|
||||
@@ -224,10 +227,7 @@ impl Sql {
|
||||
match res {
|
||||
Ok(_) => Ok(()),
|
||||
Err(err) => {
|
||||
error!(
|
||||
context,
|
||||
0, "set_config(): Cannot change value for {}: {:?}", key, &err
|
||||
);
|
||||
error!(context, 0, "set_config(): Cannot change value. {:?}", &err);
|
||||
Err(err.into())
|
||||
}
|
||||
}
|
||||
@@ -1015,14 +1015,14 @@ pub fn housekeeping(context: &Context) {
|
||||
|
||||
match std::fs::metadata(entry.path()) {
|
||||
Ok(stats) => {
|
||||
let created = stats.created().is_ok()
|
||||
let recently_created = stats.created().is_ok()
|
||||
&& stats.created().unwrap() > keep_files_newer_than;
|
||||
let modified = stats.modified().is_ok()
|
||||
let recently_modified = stats.modified().is_ok()
|
||||
&& stats.modified().unwrap() > keep_files_newer_than;
|
||||
let accessed = stats.accessed().is_ok()
|
||||
let recently_accessed = stats.accessed().is_ok()
|
||||
&& stats.accessed().unwrap() > keep_files_newer_than;
|
||||
|
||||
if created || modified || accessed {
|
||||
if recently_created || recently_modified || recently_accessed {
|
||||
info!(
|
||||
context,
|
||||
0,
|
||||
@@ -1090,13 +1090,20 @@ fn maybe_add_from_param(
|
||||
) {
|
||||
context
|
||||
.sql
|
||||
.query_row(query, NO_PARAMS, |row| {
|
||||
let param: Params = row.get::<_, String>(0)?.parse().unwrap_or_default();
|
||||
if let Some(file) = param.get(param_id) {
|
||||
maybe_add_file(files_in_use, file);
|
||||
}
|
||||
Ok(())
|
||||
})
|
||||
.query_map(
|
||||
query,
|
||||
NO_PARAMS,
|
||||
|row| row.get::<_, String>(0),
|
||||
|rows| {
|
||||
for row in rows {
|
||||
let param: Params = row?.parse().unwrap_or_default();
|
||||
if let Some(file) = param.get(param_id) {
|
||||
maybe_add_file(files_in_use, file);
|
||||
}
|
||||
}
|
||||
Ok(())
|
||||
},
|
||||
)
|
||||
.unwrap_or_else(|err| {
|
||||
warn!(context, 0, "sql: failed to add_from_param: {}", err);
|
||||
});
|
||||
|
||||
42
src/stock.rs
42
src/stock.rs
@@ -232,7 +232,10 @@ mod tests {
|
||||
use super::*;
|
||||
use crate::test_utils::*;
|
||||
|
||||
use std::ffi::CString;
|
||||
|
||||
use crate::constants::DC_CONTACT_ID_SELF;
|
||||
use crate::context::dc_context_new;
|
||||
use crate::types::uintptr_t;
|
||||
|
||||
use num_traits::ToPrimitive;
|
||||
@@ -250,18 +253,19 @@ mod tests {
|
||||
|
||||
#[test]
|
||||
fn test_stock_str() {
|
||||
let t = dummy_context();
|
||||
assert_eq!(t.ctx.stock_str(StockMessage::NoMessages), "No messages.");
|
||||
let ctx = dc_context_new(None, std::ptr::null_mut(), None);
|
||||
assert_eq!(ctx.stock_str(StockMessage::NoMessages), "No messages.");
|
||||
}
|
||||
|
||||
fn test_stock_str_no_fallback_cb(
|
||||
unsafe extern "C" fn test_stock_str_no_fallback_cb(
|
||||
_ctx: &Context,
|
||||
evt: Event,
|
||||
d1: uintptr_t,
|
||||
_d2: uintptr_t,
|
||||
) -> uintptr_t {
|
||||
if evt == Event::GET_STRING && d1 == StockMessage::NoMessages.to_usize().unwrap() {
|
||||
unsafe { "Hello there".strdup() as usize }
|
||||
let tmp = CString::new("Hello there").unwrap();
|
||||
dc_strdup(tmp.as_ptr()) as usize
|
||||
} else {
|
||||
0
|
||||
}
|
||||
@@ -269,16 +273,16 @@ mod tests {
|
||||
|
||||
#[test]
|
||||
fn test_stock_str_no_fallback() {
|
||||
let t = test_context(Some(Box::new(test_stock_str_no_fallback_cb)));
|
||||
let t = test_context(Some(test_stock_str_no_fallback_cb));
|
||||
assert_eq!(t.ctx.stock_str(StockMessage::NoMessages), "Hello there");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_stock_string_repl_str() {
|
||||
let t = dummy_context();
|
||||
let ctx = dc_context_new(None, std::ptr::null_mut(), None);
|
||||
// uses %1$s substitution
|
||||
assert_eq!(
|
||||
t.ctx.stock_string_repl_str(StockMessage::Member, "42"),
|
||||
ctx.stock_string_repl_str(StockMessage::Member, "42"),
|
||||
"42 member(s)"
|
||||
);
|
||||
// We have no string using %1$d to test...
|
||||
@@ -286,42 +290,40 @@ mod tests {
|
||||
|
||||
#[test]
|
||||
fn test_stock_string_repl_int() {
|
||||
let t = dummy_context();
|
||||
let ctx = dc_context_new(None, std::ptr::null_mut(), None);
|
||||
assert_eq!(
|
||||
t.ctx.stock_string_repl_int(StockMessage::Member, 42),
|
||||
ctx.stock_string_repl_int(StockMessage::Member, 42),
|
||||
"42 member(s)"
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_stock_string_repl_str2() {
|
||||
let t = dummy_context();
|
||||
let ctx = dc_context_new(None, std::ptr::null_mut(), None);
|
||||
assert_eq!(
|
||||
t.ctx
|
||||
.stock_string_repl_str2(StockMessage::ServerResponse, "foo", "bar"),
|
||||
ctx.stock_string_repl_str2(StockMessage::ServerResponse, "foo", "bar"),
|
||||
"Response from foo: bar"
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_stock_system_msg_simple() {
|
||||
let t = dummy_context();
|
||||
let ctx = dc_context_new(None, std::ptr::null_mut(), None);
|
||||
assert_eq!(
|
||||
t.ctx
|
||||
.stock_system_msg(StockMessage::MsgLocationEnabled, "", "", 0),
|
||||
ctx.stock_system_msg(StockMessage::MsgLocationEnabled, "", "", 0),
|
||||
"Location streaming enabled."
|
||||
)
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_stock_system_msg_add_member_by_me() {
|
||||
let t = dummy_context();
|
||||
let ctx = dc_context_new(None, std::ptr::null_mut(), None);
|
||||
assert_eq!(
|
||||
t.ctx.stock_system_msg(
|
||||
ctx.stock_system_msg(
|
||||
StockMessage::MsgAddMember,
|
||||
"alice@example.com",
|
||||
"",
|
||||
DC_CONTACT_ID_SELF as u32
|
||||
DC_CONTACT_ID_SELF
|
||||
),
|
||||
"Member alice@example.com added by me."
|
||||
)
|
||||
@@ -336,7 +338,7 @@ mod tests {
|
||||
StockMessage::MsgAddMember,
|
||||
"alice@example.com",
|
||||
"",
|
||||
DC_CONTACT_ID_SELF as u32
|
||||
DC_CONTACT_ID_SELF
|
||||
),
|
||||
"Member Alice (alice@example.com) added by me."
|
||||
);
|
||||
@@ -371,7 +373,7 @@ mod tests {
|
||||
StockMessage::MsgGrpName,
|
||||
"Some chat",
|
||||
"Other chat",
|
||||
DC_CONTACT_ID_SELF as u32
|
||||
DC_CONTACT_ID_SELF
|
||||
),
|
||||
"Group name changed from \"Some chat\" to \"Other chat\" by me."
|
||||
)
|
||||
|
||||
@@ -9,8 +9,9 @@ use tempfile::{tempdir, TempDir};
|
||||
|
||||
use crate::config::Config;
|
||||
use crate::constants::{Event, KeyType};
|
||||
use crate::context::{Context, ContextCallback};
|
||||
use crate::context::{dc_context_new, dc_open, Context};
|
||||
use crate::key;
|
||||
use crate::types::dc_callback_t;
|
||||
|
||||
/// A Context and temporary directory.
|
||||
///
|
||||
@@ -27,15 +28,18 @@ pub struct TestContext {
|
||||
/// "db.sqlite" in the [TestContext.dir] directory.
|
||||
///
|
||||
/// [Context]: crate::context::Context
|
||||
pub fn test_context(callback: Option<Box<ContextCallback>>) -> TestContext {
|
||||
let dir = tempdir().unwrap();
|
||||
let dbfile = dir.path().join("db.sqlite");
|
||||
let cb: Box<ContextCallback> = match callback {
|
||||
Some(cb) => cb,
|
||||
None => Box::new(|_, _, _, _| 0),
|
||||
};
|
||||
let ctx = Context::new(cb, "FakeOs".into(), dbfile).unwrap();
|
||||
TestContext { ctx: ctx, dir: dir }
|
||||
pub fn test_context(cb: Option<dc_callback_t>) -> TestContext {
|
||||
unsafe {
|
||||
let mut ctx = dc_context_new(cb, std::ptr::null_mut(), None);
|
||||
let dir = tempdir().unwrap();
|
||||
let dbfile = dir.path().join("db.sqlite");
|
||||
assert!(
|
||||
dc_open(&mut ctx, dbfile.to_str().unwrap(), None),
|
||||
"Failed to open {}",
|
||||
dbfile.display(),
|
||||
);
|
||||
TestContext { ctx: ctx, dir: dir }
|
||||
}
|
||||
}
|
||||
|
||||
/// Return a dummy [TestContext].
|
||||
@@ -47,8 +51,13 @@ pub fn dummy_context() -> TestContext {
|
||||
test_context(None)
|
||||
}
|
||||
|
||||
pub fn logging_cb(_ctx: &Context, evt: Event, _d1: uintptr_t, d2: uintptr_t) -> uintptr_t {
|
||||
let to_str = unsafe { |x| CStr::from_ptr(x as *const libc::c_char).to_str().unwrap() };
|
||||
pub unsafe extern "C" fn logging_cb(
|
||||
_ctx: &Context,
|
||||
evt: Event,
|
||||
_d1: uintptr_t,
|
||||
d2: uintptr_t,
|
||||
) -> uintptr_t {
|
||||
let to_str = |x| CStr::from_ptr(x as *const libc::c_char).to_str().unwrap();
|
||||
match evt {
|
||||
Event::INFO => println!("I: {}", to_str(d2)),
|
||||
Event::WARNING => println!("W: {}", to_str(d2)),
|
||||
|
||||
58
src/token.rs
Normal file
58
src/token.rs
Normal file
@@ -0,0 +1,58 @@
|
||||
//! Functions to read/write token from/to the database. A token is any string associated with a key.
|
||||
|
||||
use deltachat_derive::*;
|
||||
|
||||
use crate::context::Context;
|
||||
use crate::dc_tools::*;
|
||||
use crate::sql;
|
||||
|
||||
// Token namespaces
|
||||
#[derive(Debug, Display, Clone, Copy, PartialEq, Eq, FromPrimitive, ToPrimitive, ToSql, FromSql)]
|
||||
#[repr(i32)]
|
||||
pub enum Namespace {
|
||||
Unknown = 0,
|
||||
Auth = 110,
|
||||
InviteNumber = 100,
|
||||
}
|
||||
|
||||
impl Default for Namespace {
|
||||
fn default() -> Self {
|
||||
Namespace::Unknown
|
||||
}
|
||||
}
|
||||
|
||||
pub fn save(context: &Context, namespace: Namespace, foreign_id: u32) -> String {
|
||||
// foreign_id may be 0
|
||||
let token = dc_create_id();
|
||||
sql::execute(
|
||||
context,
|
||||
&context.sql,
|
||||
"INSERT INTO tokens (namespc, foreign_id, token, timestamp) VALUES (?, ?, ?, ?);",
|
||||
params![namespace, foreign_id as i32, &token, time()],
|
||||
)
|
||||
.ok();
|
||||
token
|
||||
}
|
||||
|
||||
pub fn lookup(context: &Context, namespace: Namespace, foreign_id: u32) -> Option<String> {
|
||||
context.sql.query_row_col::<_, String>(
|
||||
context,
|
||||
"SELECT token FROM tokens WHERE namespc=? AND foreign_id=?;",
|
||||
params![namespace, foreign_id as i32],
|
||||
0,
|
||||
)
|
||||
}
|
||||
|
||||
pub fn lookup_or_new(context: &Context, namespace: Namespace, foreign_id: u32) -> String {
|
||||
lookup(context, namespace, foreign_id).unwrap_or_else(|| save(context, namespace, foreign_id))
|
||||
}
|
||||
|
||||
pub fn exists(context: &Context, namespace: Namespace, token: &str) -> bool {
|
||||
context
|
||||
.sql
|
||||
.exists(
|
||||
"SELECT id FROM tokens WHERE namespc=? AND token=?;",
|
||||
params![namespace, token],
|
||||
)
|
||||
.unwrap_or_default()
|
||||
}
|
||||
@@ -1,24 +1,24 @@
|
||||
#!/usr/bin/env python3
|
||||
|
||||
|
||||
from pathlib import Path
|
||||
import os
|
||||
import re
|
||||
|
||||
if __name__ == "__main__":
|
||||
filestats = []
|
||||
for fn in os.listdir():
|
||||
if fn.endswith(".rs"):
|
||||
s = open(fn).read()
|
||||
s = re.sub(r'(?m)///.*$', '', s) # remove comments
|
||||
unsafe = s.count("unsafe")
|
||||
free = s.count("free(")
|
||||
gotoblocks = s.count("current_block =")
|
||||
filestats.append((fn, unsafe, free, gotoblocks))
|
||||
for fn in Path(".").glob("**/*.rs"):
|
||||
s = fn.read_text()
|
||||
s = re.sub(r"(?m)///.*$", "", s) # remove comments
|
||||
unsafe = s.count("unsafe")
|
||||
free = s.count("free(")
|
||||
gotoblocks = s.count("ok_to_continue")
|
||||
filestats.append((fn, unsafe, free, gotoblocks))
|
||||
|
||||
sum_unsafe, sum_free, sum_gotoblocks = 0, 0, 0
|
||||
|
||||
for fn, unsafe, free, gotoblocks in reversed(sorted(filestats, key=lambda x: sum(x[1:]))):
|
||||
print("{0: <30} unsafe: {1: >3} free: {2: >3} goto-blocks: {3: >3}".format(fn, unsafe, free, gotoblocks))
|
||||
print("{0: <30} unsafe: {1: >3} free: {2: >3} ok_to_continue: {3: >3}".format(str(fn), unsafe, free, gotoblocks))
|
||||
sum_unsafe += unsafe
|
||||
sum_free += free
|
||||
sum_gotoblocks += gotoblocks
|
||||
@@ -27,5 +27,5 @@ if __name__ == "__main__":
|
||||
print()
|
||||
print("total unsafe:", sum_unsafe)
|
||||
print("total free:", sum_free)
|
||||
print("total gotoblocks:", sum_gotoblocks)
|
||||
print("total ok_to_continue:", sum_gotoblocks)
|
||||
|
||||
|
||||
12
src/types.rs
12
src/types.rs
@@ -1,9 +1,21 @@
|
||||
#![allow(non_camel_case_types)]
|
||||
use crate::constants::Event;
|
||||
use crate::context::Context;
|
||||
|
||||
pub use mmime::clist::*;
|
||||
pub use rusqlite::ffi::*;
|
||||
|
||||
/// Callback function that should be given to dc_context_new().
|
||||
///
|
||||
/// @memberof Context
|
||||
/// @param context The context object as returned by dc_context_new().
|
||||
/// @param event one of the @ref DC_EVENT constants
|
||||
/// @param data1 depends on the event parameter
|
||||
/// @param data2 depends on the event parameter
|
||||
/// @return return 0 unless stated otherwise in the event parameter documentation
|
||||
pub type dc_callback_t =
|
||||
unsafe extern "C" fn(_: &Context, _: Event, _: uintptr_t, _: uintptr_t) -> uintptr_t;
|
||||
|
||||
pub type dc_receive_imf_t = unsafe fn(
|
||||
_: &Context,
|
||||
_: *const libc::c_char,
|
||||
|
||||
312
tests/stress.rs
312
tests/stress.rs
@@ -2,8 +2,8 @@
|
||||
|
||||
use std::collections::HashSet;
|
||||
use std::ffi::CString;
|
||||
use std::ptr;
|
||||
|
||||
use mmime::mailimf_types::*;
|
||||
use tempfile::{tempdir, TempDir};
|
||||
|
||||
use deltachat::chat::{self, Chat};
|
||||
@@ -12,7 +12,6 @@ use deltachat::constants::*;
|
||||
use deltachat::contact::*;
|
||||
use deltachat::context::*;
|
||||
use deltachat::dc_imex::*;
|
||||
use deltachat::dc_mimeparser::*;
|
||||
use deltachat::dc_tools::*;
|
||||
use deltachat::key::*;
|
||||
use deltachat::keyring::*;
|
||||
@@ -33,100 +32,103 @@ static mut S_EM_SETUPFILE: *const libc::c_char =
|
||||
as *const u8 as *const libc::c_char;
|
||||
|
||||
unsafe fn stress_functions(context: &Context) {
|
||||
if dc_file_exist(context, "$BLOBDIR/foobar")
|
||||
|| dc_file_exist(context, "$BLOBDIR/dada")
|
||||
|| dc_file_exist(context, "$BLOBDIR/foobar.dadada")
|
||||
|| dc_file_exist(context, "$BLOBDIR/foobar-folder")
|
||||
{
|
||||
dc_delete_file(context, "$BLOBDIR/foobar");
|
||||
dc_delete_file(context, "$BLOBDIR/dada");
|
||||
dc_delete_file(context, "$BLOBDIR/foobar.dadada");
|
||||
dc_delete_file(context, "$BLOBDIR/foobar-folder");
|
||||
}
|
||||
dc_write_file(
|
||||
context,
|
||||
b"$BLOBDIR/foobar\x00" as *const u8 as *const libc::c_char,
|
||||
b"content\x00" as *const u8 as *const libc::c_char as *const libc::c_void,
|
||||
7i32 as size_t,
|
||||
);
|
||||
assert!(dc_file_exist(context, "$BLOBDIR/foobar",));
|
||||
assert!(!dc_file_exist(context, "$BLOBDIR/foobarx"));
|
||||
assert_eq!(
|
||||
dc_get_filebytes(context, "$BLOBDIR/foobar",),
|
||||
7i32 as libc::c_ulonglong
|
||||
);
|
||||
let abs_path: *mut libc::c_char = dc_mprintf(
|
||||
b"%s/%s\x00" as *const u8 as *const libc::c_char,
|
||||
context.get_blobdir(),
|
||||
b"foobar\x00" as *const u8 as *const libc::c_char,
|
||||
);
|
||||
assert!(dc_is_blobdir_path(context, as_str(abs_path)));
|
||||
assert!(dc_is_blobdir_path(context, "$BLOBDIR/fofo"));
|
||||
assert!(!dc_is_blobdir_path(context, "/BLOBDIR/fofo"));
|
||||
assert!(dc_file_exist(context, as_path(abs_path)));
|
||||
free(abs_path as *mut libc::c_void);
|
||||
assert!(dc_copy_file(context, "$BLOBDIR/foobar", "$BLOBDIR/dada",));
|
||||
assert_eq!(dc_get_filebytes(context, "$BLOBDIR/dada",), 7);
|
||||
|
||||
let mut buf: *mut libc::c_void = 0 as *mut libc::c_void;
|
||||
let mut buf_bytes: size_t = 0;
|
||||
|
||||
assert_eq!(
|
||||
dc_read_file(
|
||||
if 0 != dc_is_open(context) {
|
||||
if dc_file_exist(context, "$BLOBDIR/foobar")
|
||||
|| dc_file_exist(context, "$BLOBDIR/dada")
|
||||
|| dc_file_exist(context, "$BLOBDIR/foobar.dadada")
|
||||
|| dc_file_exist(context, "$BLOBDIR/foobar-folder")
|
||||
{
|
||||
dc_delete_file(context, "$BLOBDIR/foobar");
|
||||
dc_delete_file(context, "$BLOBDIR/dada");
|
||||
dc_delete_file(context, "$BLOBDIR/foobar.dadada");
|
||||
dc_delete_file(context, "$BLOBDIR/foobar-folder");
|
||||
}
|
||||
dc_write_file(
|
||||
context,
|
||||
b"$BLOBDIR/dada\x00" as *const u8 as *const libc::c_char,
|
||||
&mut buf,
|
||||
&mut buf_bytes,
|
||||
),
|
||||
1
|
||||
);
|
||||
assert_eq!(buf_bytes, 7);
|
||||
assert_eq!(
|
||||
std::str::from_utf8(std::slice::from_raw_parts(buf as *const u8, buf_bytes)).unwrap(),
|
||||
"content"
|
||||
);
|
||||
b"$BLOBDIR/foobar\x00" as *const u8 as *const libc::c_char,
|
||||
b"content\x00" as *const u8 as *const libc::c_char as *const libc::c_void,
|
||||
7i32 as size_t,
|
||||
);
|
||||
assert!(dc_file_exist(context, "$BLOBDIR/foobar",));
|
||||
assert!(!dc_file_exist(context, "$BLOBDIR/foobarx"));
|
||||
assert_eq!(
|
||||
dc_get_filebytes(context, "$BLOBDIR/foobar",),
|
||||
7i32 as libc::c_ulonglong
|
||||
);
|
||||
|
||||
free(buf as *mut _);
|
||||
assert!(dc_delete_file(context, "$BLOBDIR/foobar"));
|
||||
assert!(dc_delete_file(context, "$BLOBDIR/dada"));
|
||||
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: *mut libc::c_char = dc_get_fine_pathNfilename(
|
||||
context,
|
||||
b"$BLOBDIR\x00" as *const u8 as *const libc::c_char,
|
||||
b"foobar.dadada\x00" as *const u8 as *const libc::c_char,
|
||||
);
|
||||
assert!(!fn0.is_null());
|
||||
assert_eq!(
|
||||
strcmp(
|
||||
let abs_path: *mut libc::c_char = dc_mprintf(
|
||||
b"%s/%s\x00" as *const u8 as *const libc::c_char,
|
||||
context.get_blobdir(),
|
||||
b"foobar\x00" as *const u8 as *const libc::c_char,
|
||||
);
|
||||
assert!(dc_is_blobdir_path(context, as_str(abs_path)));
|
||||
assert!(dc_is_blobdir_path(context, "$BLOBDIR/fofo",));
|
||||
assert!(!dc_is_blobdir_path(context, "/BLOBDIR/fofo",));
|
||||
assert!(dc_file_exist(context, as_path(abs_path)));
|
||||
free(abs_path as *mut libc::c_void);
|
||||
assert!(dc_copy_file(context, "$BLOBDIR/foobar", "$BLOBDIR/dada",));
|
||||
assert_eq!(dc_get_filebytes(context, "$BLOBDIR/dada",), 7);
|
||||
|
||||
let mut buf: *mut libc::c_void = ptr::null_mut();
|
||||
let mut buf_bytes: size_t = 0;
|
||||
|
||||
assert_eq!(
|
||||
dc_read_file(
|
||||
context,
|
||||
b"$BLOBDIR/dada\x00" as *const u8 as *const libc::c_char,
|
||||
&mut buf,
|
||||
&mut buf_bytes,
|
||||
),
|
||||
1
|
||||
);
|
||||
assert_eq!(buf_bytes, 7);
|
||||
assert_eq!(
|
||||
std::str::from_utf8(std::slice::from_raw_parts(buf as *const u8, buf_bytes)).unwrap(),
|
||||
"content"
|
||||
);
|
||||
|
||||
free(buf as *mut _);
|
||||
assert!(dc_delete_file(context, "$BLOBDIR/foobar"));
|
||||
assert!(dc_delete_file(context, "$BLOBDIR/dada"));
|
||||
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: *mut libc::c_char = dc_get_fine_pathNfilename(
|
||||
context,
|
||||
b"$BLOBDIR\x00" as *const u8 as *const libc::c_char,
|
||||
b"foobar.dadada\x00" as *const u8 as *const libc::c_char,
|
||||
);
|
||||
assert!(!fn0.is_null());
|
||||
assert_eq!(
|
||||
strcmp(
|
||||
fn0,
|
||||
b"$BLOBDIR/foobar.dadada\x00" as *const u8 as *const libc::c_char,
|
||||
),
|
||||
0
|
||||
);
|
||||
dc_write_file(
|
||||
context,
|
||||
fn0,
|
||||
b"$BLOBDIR/foobar.dadada\x00" as *const u8 as *const libc::c_char,
|
||||
),
|
||||
0
|
||||
);
|
||||
dc_write_file(
|
||||
context,
|
||||
fn0,
|
||||
b"content\x00" as *const u8 as *const libc::c_char as *const libc::c_void,
|
||||
7i32 as size_t,
|
||||
);
|
||||
let fn1: *mut libc::c_char = dc_get_fine_pathNfilename(
|
||||
context,
|
||||
b"$BLOBDIR\x00" as *const u8 as *const libc::c_char,
|
||||
b"foobar.dadada\x00" as *const u8 as *const libc::c_char,
|
||||
);
|
||||
assert!(!fn1.is_null());
|
||||
assert_eq!(
|
||||
strcmp(
|
||||
fn1,
|
||||
b"$BLOBDIR/foobar-1.dadada\x00" as *const u8 as *const libc::c_char,
|
||||
),
|
||||
0
|
||||
);
|
||||
assert!(dc_delete_file(context, as_path(fn0)));
|
||||
free(fn0 as *mut libc::c_void);
|
||||
free(fn1 as *mut libc::c_void);
|
||||
b"content\x00" as *const u8 as *const libc::c_char as *const libc::c_void,
|
||||
7i32 as size_t,
|
||||
);
|
||||
let fn1: *mut libc::c_char = dc_get_fine_pathNfilename(
|
||||
context,
|
||||
b"$BLOBDIR\x00" as *const u8 as *const libc::c_char,
|
||||
b"foobar.dadada\x00" as *const u8 as *const libc::c_char,
|
||||
);
|
||||
assert!(!fn1.is_null());
|
||||
assert_eq!(
|
||||
strcmp(
|
||||
fn1,
|
||||
b"$BLOBDIR/foobar-1.dadada\x00" as *const u8 as *const libc::c_char,
|
||||
),
|
||||
0
|
||||
);
|
||||
assert!(dc_delete_file(context, as_path(fn0)));
|
||||
free(fn0 as *mut libc::c_void);
|
||||
free(fn1 as *mut libc::c_void);
|
||||
}
|
||||
|
||||
let res = context.get_config(config::Config::SysConfigKeys).unwrap();
|
||||
|
||||
@@ -160,10 +162,10 @@ unsafe fn stress_functions(context: &Context) {
|
||||
assert!(res.contains(" configured_server_flags "));
|
||||
|
||||
let mut buf_0: *mut libc::c_char;
|
||||
let mut headerline: *const libc::c_char = 0 as *const libc::c_char;
|
||||
let mut setupcodebegin: *const libc::c_char = 0 as *const libc::c_char;
|
||||
let mut preferencrypt: *const libc::c_char = 0 as *const libc::c_char;
|
||||
let mut base64: *const libc::c_char = 0 as *const libc::c_char;
|
||||
let mut headerline: *const libc::c_char = ptr::null();
|
||||
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,
|
||||
@@ -172,7 +174,7 @@ unsafe fn stress_functions(context: &Context) {
|
||||
buf_0,
|
||||
&mut headerline,
|
||||
&mut setupcodebegin,
|
||||
0 as *mut *const libc::c_char,
|
||||
ptr::null_mut(),
|
||||
&mut base64,
|
||||
);
|
||||
assert!(ok);
|
||||
@@ -197,7 +199,7 @@ unsafe fn stress_functions(context: &Context) {
|
||||
buf_0,
|
||||
&mut headerline,
|
||||
&mut setupcodebegin,
|
||||
0 as *mut *const libc::c_char,
|
||||
ptr::null_mut(),
|
||||
&mut base64,
|
||||
);
|
||||
|
||||
@@ -224,7 +226,7 @@ unsafe fn stress_functions(context: &Context) {
|
||||
buf_0,
|
||||
&mut headerline,
|
||||
&mut setupcodebegin,
|
||||
0 as *mut *const libc::c_char,
|
||||
ptr::null_mut(),
|
||||
&mut base64,
|
||||
);
|
||||
|
||||
@@ -249,7 +251,7 @@ unsafe fn stress_functions(context: &Context) {
|
||||
buf_0,
|
||||
&mut headerline,
|
||||
&mut setupcodebegin,
|
||||
0 as *mut *const libc::c_char,
|
||||
ptr::null_mut(),
|
||||
&mut base64,
|
||||
);
|
||||
|
||||
@@ -262,7 +264,7 @@ unsafe fn stress_functions(context: &Context) {
|
||||
buf_0,
|
||||
&mut headerline,
|
||||
&mut setupcodebegin,
|
||||
0 as *mut *const libc::c_char,
|
||||
ptr::null_mut(),
|
||||
&mut base64,
|
||||
);
|
||||
assert!(ok);
|
||||
@@ -295,7 +297,7 @@ unsafe fn stress_functions(context: &Context) {
|
||||
let ok = dc_split_armored_data(
|
||||
buf_0,
|
||||
&mut headerline,
|
||||
0 as *mut *const libc::c_char,
|
||||
ptr::null_mut(),
|
||||
&mut preferencrypt,
|
||||
&mut base64,
|
||||
);
|
||||
@@ -351,16 +353,16 @@ unsafe fn stress_functions(context: &Context) {
|
||||
);
|
||||
free(norm as *mut libc::c_void);
|
||||
let mut buf_1: *mut libc::c_char;
|
||||
let mut headerline_0: *const libc::c_char = 0 as *const libc::c_char;
|
||||
let mut setupcodebegin_0: *const libc::c_char = 0 as *const libc::c_char;
|
||||
let mut preferencrypt_0: *const libc::c_char = 0 as *const libc::c_char;
|
||||
let mut headerline_0: *const libc::c_char = ptr::null();
|
||||
let mut setupcodebegin_0: *const libc::c_char = ptr::null();
|
||||
let mut preferencrypt_0: *const libc::c_char = ptr::null();
|
||||
buf_1 = strdup(S_EM_SETUPFILE);
|
||||
assert!(dc_split_armored_data(
|
||||
buf_1,
|
||||
&mut headerline_0,
|
||||
&mut setupcodebegin_0,
|
||||
&mut preferencrypt_0,
|
||||
0 as *mut *const libc::c_char,
|
||||
ptr::null_mut(),
|
||||
));
|
||||
assert!(!headerline_0.is_null());
|
||||
assert_eq!(
|
||||
@@ -386,7 +388,7 @@ unsafe fn stress_functions(context: &Context) {
|
||||
&mut headerline_0,
|
||||
&mut setupcodebegin_0,
|
||||
&mut preferencrypt_0,
|
||||
0 as *mut *const libc::c_char,
|
||||
ptr::null_mut(),
|
||||
));
|
||||
assert!(!headerline_0.is_null());
|
||||
assert_eq!(
|
||||
@@ -414,16 +416,16 @@ unsafe fn stress_functions(context: &Context) {
|
||||
// let setupcode_c = CString::yolo(setupcode.clone());
|
||||
// let setupfile = dc_render_setup_file(context, &setupcode).unwrap();
|
||||
// let setupfile_c = CString::yolo(setupfile);
|
||||
// let mut headerline_2: *const libc::c_char = 0 as *const libc::c_char;
|
||||
// let mut headerline_2: *const libc::c_char = ptr::null();
|
||||
// let payload = dc_decrypt_setup_file(context, setupcode_c.as_ptr(), setupfile_c.as_ptr());
|
||||
|
||||
// assert!(payload.is_null());
|
||||
// assert!(!dc_split_armored_data(
|
||||
// payload,
|
||||
// &mut headerline_2,
|
||||
// 0 as *mut *const libc::c_char,
|
||||
// 0 as *mut *const libc::c_char,
|
||||
// 0 as *mut *const libc::c_char,
|
||||
// ptr::null_mut(),
|
||||
// ptr::null_mut(),
|
||||
// ptr::null_mut(),
|
||||
// ));
|
||||
// assert!(!headerline_2.is_null());
|
||||
// assert_eq!(
|
||||
@@ -623,7 +625,12 @@ fn test_encryption_decryption() {
|
||||
}
|
||||
}
|
||||
|
||||
fn cb(_context: &Context, _event: Event, _data1: uintptr_t, _data2: uintptr_t) -> uintptr_t {
|
||||
unsafe extern "C" fn cb(
|
||||
_context: &Context,
|
||||
_event: Event,
|
||||
_data1: uintptr_t,
|
||||
_data2: uintptr_t,
|
||||
) -> uintptr_t {
|
||||
0
|
||||
}
|
||||
|
||||
@@ -633,47 +640,21 @@ struct TestContext {
|
||||
dir: TempDir,
|
||||
}
|
||||
|
||||
fn create_test_context() -> TestContext {
|
||||
unsafe fn create_test_context() -> TestContext {
|
||||
let mut ctx = dc_context_new(Some(cb), std::ptr::null_mut(), None);
|
||||
let dir = tempdir().unwrap();
|
||||
let dbfile = dir.path().join("db.sqlite");
|
||||
let ctx = Context::new(Box::new(cb), "FakeOs".into(), dbfile).unwrap();
|
||||
assert!(
|
||||
dc_open(&mut ctx, dbfile.to_str().unwrap(), None),
|
||||
"Failed to open {}",
|
||||
dbfile.display()
|
||||
);
|
||||
TestContext { ctx: ctx, dir: dir }
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_dc_mimeparser_with_context() {
|
||||
unsafe {
|
||||
let context = create_test_context();
|
||||
|
||||
let mut mimeparser = dc_mimeparser_new(&context.ctx);
|
||||
let raw: *const libc::c_char =
|
||||
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"
|
||||
as *const u8 as *const libc::c_char;
|
||||
|
||||
dc_mimeparser_parse(&mut mimeparser, raw, strlen(raw));
|
||||
assert_eq!(
|
||||
as_str(mimeparser.subject as *const libc::c_char),
|
||||
"inner-subject",
|
||||
);
|
||||
|
||||
let mut of: *mut mailimf_optional_field =
|
||||
dc_mimeparser_lookup_optional_field(&mimeparser, "X-Special-A");
|
||||
assert_eq!(as_str((*of).fld_value as *const libc::c_char), "special-a",);
|
||||
|
||||
of = dc_mimeparser_lookup_optional_field(&mimeparser, "Foo");
|
||||
assert_eq!(as_str((*of).fld_value as *const libc::c_char), "Bar",);
|
||||
|
||||
of = dc_mimeparser_lookup_optional_field(&mimeparser, "Chat-Version");
|
||||
assert_eq!(as_str((*of).fld_value as *const libc::c_char), "1.0",);
|
||||
assert_eq!(mimeparser.parts.len(), 1);
|
||||
|
||||
dc_mimeparser_unref(&mut mimeparser);
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_dc_get_oauth2_url() {
|
||||
let ctx = create_test_context();
|
||||
let ctx = unsafe { create_test_context() };
|
||||
let addr = "dignifiedquire@gmail.com";
|
||||
let redirect_uri = "chat.delta:/com.b44t.messenger";
|
||||
let res = dc_get_oauth2_url(&ctx.ctx, addr, redirect_uri);
|
||||
@@ -683,7 +664,7 @@ fn test_dc_get_oauth2_url() {
|
||||
|
||||
#[test]
|
||||
fn test_dc_get_oauth2_addr() {
|
||||
let ctx = create_test_context();
|
||||
let ctx = unsafe { create_test_context() };
|
||||
let addr = "dignifiedquire@gmail.com";
|
||||
let code = "fail";
|
||||
let res = dc_get_oauth2_addr(&ctx.ctx, addr, code);
|
||||
@@ -693,7 +674,7 @@ fn test_dc_get_oauth2_addr() {
|
||||
|
||||
#[test]
|
||||
fn test_dc_get_oauth2_token() {
|
||||
let ctx = create_test_context();
|
||||
let ctx = unsafe { create_test_context() };
|
||||
let addr = "dignifiedquire@gmail.com";
|
||||
let code = "fail";
|
||||
let res = dc_get_oauth2_access_token(&ctx.ctx, addr, code, 0);
|
||||
@@ -711,18 +692,20 @@ fn test_stress_tests() {
|
||||
|
||||
#[test]
|
||||
fn test_get_contacts() {
|
||||
let context = create_test_context();
|
||||
let contacts = Contact::get_all(&context.ctx, 0, Some("some2")).unwrap();
|
||||
assert_eq!(contacts.len(), 0);
|
||||
unsafe {
|
||||
let context = create_test_context();
|
||||
let contacts = Contact::get_all(&context.ctx, 0, Some("some2")).unwrap();
|
||||
assert_eq!(contacts.len(), 0);
|
||||
|
||||
let id = Contact::create(&context.ctx, "bob", "bob@mail.de").unwrap();
|
||||
assert_ne!(id, 0);
|
||||
let id = Contact::create(&context.ctx, "bob", "bob@mail.de").unwrap();
|
||||
assert_ne!(id, 0);
|
||||
|
||||
let contacts = Contact::get_all(&context.ctx, 0, Some("bob")).unwrap();
|
||||
assert_eq!(contacts.len(), 1);
|
||||
let contacts = Contact::get_all(&context.ctx, 0, Some("bob")).unwrap();
|
||||
assert_eq!(contacts.len(), 1);
|
||||
|
||||
let contacts = Contact::get_all(&context.ctx, 0, Some("alice")).unwrap();
|
||||
assert_eq!(contacts.len(), 0);
|
||||
let contacts = Contact::get_all(&context.ctx, 0, Some("alice")).unwrap();
|
||||
assert_eq!(contacts.len(), 0);
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
@@ -743,3 +726,16 @@ fn test_chat() {
|
||||
assert_eq!(chat2.name, chat.name);
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_wrong_db() {
|
||||
unsafe {
|
||||
let mut ctx = dc_context_new(Some(cb), std::ptr::null_mut(), None);
|
||||
let dir = tempdir().unwrap();
|
||||
let dbfile = dir.path().join("db.sqlite");
|
||||
std::fs::write(&dbfile, b"123").unwrap();
|
||||
|
||||
let res = dc_open(&mut ctx, dbfile.to_str().unwrap(), None);
|
||||
assert!(!res);
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user