Compare commits

..

4 Commits

Author SHA1 Message Date
holger krekel
ea28ba6fbd try fix upload 2020-05-05 18:16:28 +02:00
holger krekel
3bfaaf10e3 another 2019-09-03 14:54:32 +02:00
holger krekel
73da9b21a3 another try 2019-09-03 13:40:00 +02:00
holger krekel
5f33a8c54c try use py36 for uploads 2019-09-02 20:54:47 +02:00
52 changed files with 4037 additions and 3529 deletions

170
Cargo.lock generated
View File

@@ -110,19 +110,6 @@ 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"
@@ -234,7 +221,7 @@ name = "c2-chacha"
version = "0.2.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
dependencies = [
"lazy_static 1.4.0 (registry+https://github.com/rust-lang/crates.io-index)",
"lazy_static 1.3.0 (registry+https://github.com/rust-lang/crates.io-index)",
"ppv-lite86 0.2.5 (registry+https://github.com/rust-lang/crates.io-index)",
]
@@ -395,7 +382,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.4.0 (registry+https://github.com/rust-lang/crates.io-index)",
"lazy_static 1.3.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)",
]
@@ -414,7 +401,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.4.0 (registry+https://github.com/rust-lang/crates.io-index)",
"lazy_static 1.3.0 (registry+https://github.com/rust-lang/crates.io-index)",
]
[[package]]
@@ -487,13 +474,12 @@ 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 (git+https://github.com/jonhoo/rust-imap?rev=281d2eb8ab50dc656ceff2ae749ca5045f334e15)",
"imap 1.0.2 (registry+https://github.com/rust-lang/crates.io-index)",
"itertools 0.8.0 (registry+https://github.com/rust-lang/crates.io-index)",
"jetscii 0.4.4 (registry+https://github.com/rust-lang/crates.io-index)",
"lazy_static 1.4.0 (registry+https://github.com/rust-lang/crates.io-index)",
"lazy_static 1.3.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.2-alpha.0 (git+https://github.com/dignifiedquire/mmime?rev=bccd2c2)",
"mmime 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)",
"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)",
@@ -503,7 +489,6 @@ 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)",
@@ -964,24 +949,23 @@ dependencies = [
[[package]]
name = "imap"
version = "1.0.2"
source = "git+https://github.com/jonhoo/rust-imap?rev=281d2eb8ab50dc656ceff2ae749ca5045f334e15#281d2eb8ab50dc656ceff2ae749ca5045f334e15"
source = "registry+https://github.com/rust-lang/crates.io-index"
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.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)",
"imap-proto 0.7.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 5.0.1 (registry+https://github.com/rust-lang/crates.io-index)",
"nom 4.2.3 (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.9.0"
version = "0.7.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
dependencies = [
"nom 5.0.1 (registry+https://github.com/rust-lang/crates.io-index)",
"nom 4.2.3 (registry+https://github.com/rust-lang/crates.io-index)",
]
[[package]]
@@ -1011,11 +995,6 @@ 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"
@@ -1032,7 +1011,7 @@ dependencies = [
[[package]]
name = "lazy_static"
version = "1.4.0"
version = "1.3.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
[[package]]
@@ -1052,18 +1031,6 @@ 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"
@@ -1225,13 +1192,13 @@ dependencies = [
[[package]]
name = "mmime"
version = "0.1.2-alpha.0"
source = "git+https://github.com/dignifiedquire/mmime?rev=bccd2c2#bccd2c2c89e9241e05f321c963f638affdccad96"
version = "0.1.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
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.4.0 (registry+https://github.com/rust-lang/crates.io-index)",
"lazy_static 1.3.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)",
@@ -1242,7 +1209,7 @@ name = "native-tls"
version = "0.2.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
dependencies = [
"lazy_static 1.4.0 (registry+https://github.com/rust-lang/crates.io-index)",
"lazy_static 1.3.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)",
@@ -1290,23 +1257,13 @@ 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.4.0 (registry+https://github.com/rust-lang/crates.io-index)",
"lazy_static 1.3.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)",
@@ -1381,7 +1338,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.4.0 (registry+https://github.com/rust-lang/crates.io-index)",
"lazy_static 1.3.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)",
]
@@ -1551,7 +1508,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.4.0 (registry+https://github.com/rust-lang/crates.io-index)",
"lazy_static 1.3.0 (registry+https://github.com/rust-lang/crates.io-index)",
"log 0.4.8 (registry+https://github.com/rust-lang/crates.io-index)",
"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)",
@@ -1667,25 +1624,6 @@ 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"
@@ -1693,7 +1631,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.4.0 (registry+https://github.com/rust-lang/crates.io-index)",
"lazy_static 1.3.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)",
]
@@ -2012,7 +1950,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.4.0 (registry+https://github.com/rust-lang/crates.io-index)",
"lazy_static 1.3.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)",
@@ -2059,17 +1997,6 @@ 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"
@@ -2109,7 +2036,7 @@ name = "schannel"
version = "0.1.15"
source = "registry+https://github.com/rust-lang/crates.io-index"
dependencies = [
"lazy_static 1.4.0 (registry+https://github.com/rust-lang/crates.io-index)",
"lazy_static 1.3.0 (registry+https://github.com/rust-lang/crates.io-index)",
"winapi 0.3.7 (registry+https://github.com/rust-lang/crates.io-index)",
]
@@ -2282,20 +2209,6 @@ 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"
@@ -2430,7 +2343,7 @@ name = "thread_local"
version = "0.3.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
dependencies = [
"lazy_static 1.4.0 (registry+https://github.com/rust-lang/crates.io-index)",
"lazy_static 1.3.0 (registry+https://github.com/rust-lang/crates.io-index)",
]
[[package]]
@@ -2506,7 +2419,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.4.0 (registry+https://github.com/rust-lang/crates.io-index)",
"lazy_static 1.3.0 (registry+https://github.com/rust-lang/crates.io-index)",
"log 0.4.8 (registry+https://github.com/rust-lang/crates.io-index)",
"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)",
@@ -2651,14 +2564,6 @@ 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"
@@ -2714,14 +2619,6 @@ 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"
@@ -2869,8 +2766,6 @@ 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"
@@ -2960,18 +2855,16 @@ 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 (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 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 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.4.0 (registry+https://github.com/rust-lang/crates.io-index)" = "e2abad23fbc42b3700f2f279844dc832adb2b2eb069b2df918f455c4e18cc646"
"checksum lazy_static 1.3.0 (registry+https://github.com/rust-lang/crates.io-index)" = "bc5729f27f159ddd61f4df6228e827e86643d4d3e7c32183cb30a1c08f604a14"
"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"
@@ -2991,13 +2884,12 @@ 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.2-alpha.0 (git+https://github.com/dignifiedquire/mmime?rev=bccd2c2)" = "<none>"
"checksum mmime 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)" = "a1246fa340840c36f1fca1507db82463fbc4c2f7763fe84bfde666c7381e0593"
"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"
@@ -3034,7 +2926,6 @@ 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"
@@ -3072,7 +2963,6 @@ 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"
@@ -3098,8 +2988,6 @@ 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"
@@ -3140,7 +3028,6 @@ 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"
@@ -3149,7 +3036,6 @@ 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"

View File

@@ -23,8 +23,8 @@ num-derive = "0.2.5"
num-traits = "0.2.6"
native-tls = "0.2.3"
lettre = "0.9.0"
imap = { git = "https://github.com/jonhoo/rust-imap", rev = "281d2eb8ab50dc656ceff2ae749ca5045f334e15" }
mmime = { git = "https://github.com/dignifiedquire/mmime", rev = "bccd2c2" }
imap = "1.0.1"
mmime = "0.1.0"
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.4.0"
lazy_static = "1.3.0"
regex = "1.1.6"
rusqlite = { version = "0.20", features = ["bundled"] }
r2d2_sqlite = "0.12.0"
@@ -50,13 +50,11 @@ 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 = [

View File

@@ -34,6 +34,7 @@ echo -----------------------
# Bundle external shared libraries into the wheels
pushd $WHEELHOUSEDIR
pip3 install -U setuptools
pip3 install devpi-client
devpi use https://m.devpi.net
devpi login dc --password $DEVPI_LOGIN

View File

@@ -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,py35,py36,py37,auditwheels
tox --workdir "$TOXWORKDIR" -e lint,py27,py35,py36,py37,auditwheels
popd
fi

View File

@@ -898,15 +898,9 @@ pub unsafe extern "C" fn dc_set_chat_profile_image(
let context = &*context;
chat::set_chat_profile_image(context, chat_id, {
if image.is_null() {
""
} else {
as_str(image)
}
})
.map(|_| 1)
.unwrap_or_log_default(context, "Failed to set profile image")
chat::set_chat_profile_image(context, chat_id, as_str(image))
.map(|_| 1)
.unwrap_or_log_default(context, "Failed to set profile image")
}
#[no_mangle]
@@ -1326,13 +1320,12 @@ pub unsafe extern "C" fn dc_get_securejoin_qr(
) -> *mut libc::c_char {
if context.is_null() {
eprintln!("ignoring careless call to dc_get_securejoin_qr()");
return "".strdup();
return dc_strdup(ptr::null());
}
let context = &*context;
securejoin::dc_get_securejoin_qr(context, chat_id)
.unwrap_or("".to_string())
.strdup()
dc_securejoin::dc_get_securejoin_qr(context, chat_id)
}
#[no_mangle]
@@ -1347,7 +1340,7 @@ pub unsafe extern "C" fn dc_join_securejoin(
let context = &*context;
securejoin::dc_join_securejoin(context, as_str(qr))
dc_securejoin::dc_join_securejoin(context, qr)
}
#[no_mangle]
@@ -1786,7 +1779,7 @@ pub unsafe extern "C" fn dc_chat_get_profile_image(chat: *mut dc_chat_t) -> *mut
let chat = &*chat;
match chat.get_profile_image() {
Some(p) => p.to_str().unwrap().to_string().strdup(),
Some(i) => i.strdup(),
None => ptr::null_mut(),
}
}
@@ -2025,7 +2018,7 @@ pub unsafe extern "C" fn dc_msg_get_filemime(msg: *mut dc_msg_t) -> *mut libc::c
}
let msg = &*msg;
message::dc_msg_get_filemime(msg).strdup()
message::dc_msg_get_filemime(msg)
}
#[no_mangle]
@@ -2403,7 +2396,7 @@ pub unsafe extern "C" fn dc_contact_get_profile_image(
contact
.get_profile_image()
.map(|p| p.to_str().unwrap().to_string().strdup())
.map(|s| s.strdup())
.unwrap_or_else(|| std::ptr::null_mut())
}

View File

@@ -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)?;
Ok(num_traits::FromPrimitive::from_i64(inner).unwrap_or_default())
num_traits::FromPrimitive::from_i64(inner).ok_or(rusqlite::types::FromSqlError::InvalidType)
}
}
};

View File

@@ -342,6 +342,12 @@ 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()
}
@@ -422,7 +428,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> [<text>]\n\
sendfile <file>\n\
draft [<text>]\n\
listmedia\n\
archive <chat-id>\n\
@@ -455,6 +461,20 @@ 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);
@@ -842,7 +862,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(), "No file given.");
ensure!(!arg1.is_empty() && !arg2.is_empty(), "No file given.");
let mut msg = dc_msg_new(
context,
@@ -853,9 +873,7 @@ pub unsafe fn dc_cmdline(context: &Context, line: &str) -> Result<(), failure::E
},
);
dc_msg_set_file(&mut msg, arg1_c, ptr::null());
if !arg2.is_empty() {
dc_msg_set_text(&mut msg, arg2_c);
}
dc_msg_set_text(&mut msg, arg2_c);
chat::send_msg(context, sel_chat.as_ref().unwrap().get_id(), &mut msg)?;
}
"listmsgs" => {

View File

@@ -14,8 +14,6 @@ extern crate lazy_static;
extern crate rusqlite;
use std::borrow::Cow::{self, Borrowed, Owned};
use std::io::{self, Write};
use std::process::Command;
use std::ptr;
use std::sync::atomic::{AtomicBool, Ordering};
use std::sync::{Arc, Mutex, RwLock};
@@ -24,10 +22,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};
@@ -389,6 +387,8 @@ impl Helper for DcHelper {}
fn main_0(args: Vec<String>) -> Result<(), failure::Error> {
let mut context = dc_context_new(Some(receive_event), ptr::null_mut(), Some("CLI".into()));
unsafe { dc_cmdline_skip_auth() };
if args.len() == 2 {
if unsafe { !dc_open(&mut context, &args[1], None) } {
println!("Error: Cannot open {}.", args[0],);
@@ -516,27 +516,30 @@ unsafe fn handle_cmd(line: &str, ctx: Arc<RwLock<Context>>) -> Result<ExitResult
}
"getqr" | "getbadqr" => {
start_threads(ctx.clone());
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")
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
}
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);
dc_join_securejoin(&ctx.read().unwrap(), arg1_c);
}
}
"exit" | "quit" => return Ok(ExitResult::Exit),

View File

@@ -1,7 +0,0 @@
# 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"

View File

@@ -1,9 +0,0 @@
# 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

View File

@@ -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 necessary because some tests generate RSA keys
The release mode is neccessary because some tests generate RSA keys
which is prohibitively slow in debug mode.
After successful binding installation you can finally run the tests::
After succcessul binding installation you can finally run the tests::
pytest -v tests

View File

@@ -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, DCLot
from .cutil import as_dc_charpointer, from_dc_charpointer, iter_array
from .chatting import Contact, Chat, Message
@@ -329,56 +329,6 @@ 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).
@@ -483,10 +433,6 @@ class EventLogger:
def set_timeout(self, timeout):
self._timeout = timeout
def consume_events(self, check_error=True):
while not self._event_queue.empty():
self.get()
def get(self, timeout=None, check_error=True):
timeout = timeout or self._timeout
ev = self._event_queue.get(timeout=timeout)
@@ -546,18 +492,3 @@ 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()

View File

@@ -1,7 +1,6 @@
""" chatting related objects: Contact, Chat, Message. """
import mimetypes
import os
from . import props
from .cutil import as_dc_charpointer, from_dc_charpointer, iter_array
from .capi import lib, ffi
@@ -132,16 +131,6 @@ 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):
@@ -316,46 +305,3 @@ class Chat(object):
return list(iter_array(
dc_array, lambda id: Contact(self._dc_context, id))
)
def set_profile_image(self, img_path):
"""Set group profile image.
If the group is already promoted (any message was sent to the group),
all group members are informed by a special status message that is sent
automatically by this function.
:params img_path: path to image object
:raises ValueError: if profile image could not be set
:returns: None
"""
assert os.path.exists(img_path), img_path
p = as_dc_charpointer(img_path)
res = lib.dc_set_chat_profile_image(self._dc_context, self.id, p)
if res != 1:
raise ValueError("Setting Profile Image {!r} failed".format(p))
def remove_profile_image(self):
"""remove group profile image.
If the group is already promoted (any message was sent to the group),
all group members are informed by a special status message that is sent
automatically by this function.
:raises ValueError: if profile image could not be reset
:returns: None
"""
res = lib.dc_set_chat_profile_image(self._dc_context, self.id, ffi.NULL)
if res != 1:
raise ValueError("Removing Profile Image failed")
def get_profile_image(self):
"""Get group profile image.
For groups, this is the image set by any group member using
set_chat_profile_image(). For normal chats, this is the image
set by each remote user on their own using dc_set_config(context,
"selfavatar", image).
:returns: path to profile image, None if no profile image exists.
"""
dc_res = lib.dc_chat_get_profile_image(self._dc_chat)
if dc_res == ffi.NULL:
return None
return from_dc_charpointer(dc_res)

View File

@@ -13,15 +13,6 @@ 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
@@ -78,13 +69,15 @@ 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_QR|DC_MSG|DC_STATE_|DC_CONTACT_ID_|DC_GCL|DC_CHAT)\S+)\s+([x\d]+).*')
rex = re.compile(r'#define\s+((?:DC_EVENT_|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:
@@ -97,7 +90,7 @@ if __name__ == "__main__":
if len(sys.argv) >= 2:
deltah = sys.argv[1]
else:
deltah = joinpath(dirname(dirname(dirname(here_dir))), "deltachat-ffi", "deltachat.h")
deltah = joinpath(dirname(dirname(dirname(here_dir))), "src", "deltachat.h")
assert os.path.exists(deltah)
lines = []

View File

@@ -1,6 +1,5 @@
from .capi import lib
from .capi import ffi
from datetime import datetime
def as_dc_charpointer(obj):
@@ -18,29 +17,3 @@ 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)

View File

@@ -213,15 +213,6 @@ 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:

View File

@@ -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, wait_securejoin_inviter_progress
from conftest import wait_configuration_progress, wait_successful_IMAP_SMTP_connection
class TestOfflineAccountBasic:
@@ -140,22 +140,6 @@ 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_get_set_profile_image_simple(self, ac1, data):
chat = ac1.create_group_chat(name="title1")
p = data.get_path("d.png")
chat.set_profile_image(p)
p2 = chat.get_profile_image()
assert open(p, "rb").read() == open(p2, "rb").read()
chat.remove_profile_image()
assert chat.get_profile_image() is None
def test_delete_and_send_fails(self, ac1, chat1):
chat1.delete()
ac1._evlogger.get_matching("DC_EVENT_MSGS_CHANGED")
@@ -322,16 +306,6 @@ 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):
@@ -572,85 +546,3 @@ 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)
def test_set_get_profile_image(self, acfactory, data, 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("create unpromoted group chat")
chat = ac1.create_group_chat("hello")
p = data.get_path("d.png")
lp.sec("ac1: set profile image on unpromoted chat")
chat.set_profile_image(p)
ac1._evlogger.get_matching("DC_EVENT_CHAT_MODIFIED")
assert not chat.is_promoted()
lp.sec("ac1: send text to promote chat (XXX without contact added)")
# XXX first promote the chat before adding contact
# because DC does not send out profile images for unpromoted chats
# otherwise
chat.send_text("ac1: initial message to promote chat (workaround)")
assert chat.is_promoted()
lp.sec("ac2: add ac1 to a chat so the message does not land in DEADDROP")
c1 = ac2.create_contact(email=ac1.get_config("addr"))
ac2.create_chat_by_contact(c1)
ev = ac2._evlogger.get_matching("DC_EVENT_MSGS_CHANGED")
lp.sec("ac1: add ac2 to promoted group chat")
c2 = ac1.create_contact(email=ac2.get_config("addr"))
chat.add_contact(c2)
lp.sec("ac1: send a first message to ac2")
chat.send_text("hi")
assert chat.is_promoted()
lp.sec("ac2: wait for receiving message from ac1")
ev = ac2._evlogger.get_matching("DC_EVENT_INCOMING_MSG")
msg_in = ac2.get_message_by_id(ev[2])
assert not msg_in.chat.is_deaddrop()
lp.sec("ac2: create chat and read profile image")
chat2 = ac2.create_chat_by_message(msg_in)
p2 = chat2.get_profile_image()
assert p2 is not None
assert open(p2, "rb").read() == open(p, "rb").read()
ac2._evlogger.consume_events()
ac1._evlogger.consume_events()
lp.sec("ac2: delete profile image from chat")
chat2.remove_profile_image()
ev = ac1._evlogger.get_matching("DC_EVENT_INCOMING_MSG")
assert ev[1] == chat.id
chat1b = ac1.create_chat_by_message(ev[2])
assert chat1b.get_profile_image() is None
assert chat.get_profile_image() is None

View File

@@ -1,6 +1,7 @@
[tox]
# make sure to update environment list in travis.yml and appveyor.yml
envlist =
py27
py35
lint
auditwheels

View File

@@ -1,5 +1,5 @@
use std::ffi::CString;
use std::path::{Path, PathBuf};
use std::path::Path;
use crate::chatlist::*;
use crate::constants::*;
@@ -72,15 +72,15 @@ impl<'a> Chat<'a> {
},
Ok(mut chat) => {
match chat.id {
DC_CHAT_ID_DEADDROP => {
1 => {
chat.name = chat.context.stock_str(StockMessage::DeadDrop).into();
}
DC_CHAT_ID_ARCHIVED_LINK => {
6 => {
let tempname = chat.context.stock_str(StockMessage::ArchivedChats);
let cnt = dc_get_archived_cnt(chat.context);
chat.name = format!("{} ({})", tempname, cnt);
}
DC_CHAT_ID_STARRED => {
5 => {
chat.name = chat.context.stock_str(StockMessage::StarredMsgs).into();
}
_ => {
@@ -171,26 +171,57 @@ impl<'a> Chat<'a> {
return "Err".into();
}
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);";
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();
sql.query_row(main_query, params, collect)
.or_else(|_| sql.query_row(fallback_query, params, collect))
.ok()
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(())
}
pub fn get_profile_image(&self) -> Option<PathBuf> {
pub unsafe fn get_profile_image(&self) -> Option<String> {
if let Some(image_rel) = self.param.get(Param::ProfileImage) {
if !image_rel.is_empty() {
return Some(dc_get_abs_path_safe(self.context, image_rel));
return Some(to_string(dc_get_abs_path(self.context, image_rel)));
}
} else if self.typ == Chattype::Single {
let contacts = get_chat_contacts(self.context, self.id);
@@ -229,10 +260,6 @@ impl<'a> Chat<'a> {
self.param.get_int(Param::Unpromoted).unwrap_or_default() == 1
}
pub fn is_promoted(&self) -> bool {
!self.is_unpromoted()
}
pub fn is_verified(&self) -> bool {
(self.typ == Chattype::VerifiedGroup)
}
@@ -250,8 +277,13 @@ impl<'a> Chat<'a> {
) -> Result<u32, Error> {
let mut do_guarantee_e2ee: libc::c_int;
let e2ee_enabled: libc::c_int;
let mut new_references = "".into();
let mut new_in_reply_to = "".into();
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 msg_id = 0;
let mut to_id = 0;
let mut location_id = 0;
@@ -261,10 +293,7 @@ impl<'a> Chat<'a> {
|| self.typ == Chattype::VerifiedGroup)
{
error!(context, 0, "Cannot send to chat type #{}.", self.typ,);
return Ok(0);
}
if (self.typ == Chattype::Group || self.typ == Chattype::VerifiedGroup)
} else if (self.typ == Chattype::Group || self.typ == Chattype::VerifiedGroup)
&& 0 == is_contact_in_chat(context, self.id, 1 as u32)
{
log_event!(
@@ -273,184 +302,211 @@ impl<'a> Chat<'a> {
0,
"Cannot send message; self not in group.",
);
return Ok(0);
}
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 {
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();
}
}
}
/* 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)?;
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;
}
}
}
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 {
if let Some(from) = context.sql.get_config(context, "configured_addr") {
let from_c = CString::yolo(from);
new_rfc724_mid = dc_create_outgoing_rfc724_mid(
if self.typ == Chattype::Group || self.typ == Chattype::VerifiedGroup {
self.grpid.strdup()
} else {
&parent_references
};
ptr::null_mut()
},
from_c.as_ptr(),
);
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();
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;
}
} 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;
// add independent location to database
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 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,
);
}
}
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);
}
}
// add message to the database
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;
}
}
}
if 0 != do_guarantee_e2ee {
msg.param.set_int(Param::GuranteeE2ee, 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 sql::execute(
// 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,
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![
new_rfc724_mid,
as_str(new_rfc724_mid),
self.id as i32,
1i32,
to_id as i32,
timestamp,
msg.type_0,
msg.state,
msg.text.as_ref().map_or("", String::as_str),
msg.text,
msg.param.to_string(),
msg.hidden,
new_in_reply_to,
new_references,
to_string(new_in_reply_to),
to_string(new_references),
location_id as i32,
]
).is_ok() {
@@ -459,7 +515,7 @@ impl<'a> Chat<'a> {
&context.sql,
"msgs",
"rfc724_mid",
new_rfc724_mid,
as_str(new_rfc724_mid),
);
} else {
error!(
@@ -469,10 +525,19 @@ impl<'a> Chat<'a> {
self.id,
);
}
} else {
error!(context, 0, "Cannot send message, not configured.",);
}
} 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)
}
}
@@ -789,7 +854,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 fn send_msg<'a>(
pub unsafe fn send_msg<'a>(
context: &'a Context,
chat_id: u32,
msg: &mut Message<'a>,
@@ -807,7 +872,7 @@ pub fn send_msg<'a>(
}
ensure!(
unsafe { job_send_msg(context, msg.id) } != 0,
job_send_msg(context, msg.id) != 0,
"Failed to initiate send job"
);
@@ -1329,7 +1394,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 fn add_contact_to_chat_ex(
pub unsafe fn add_contact_to_chat_ex(
context: &Context,
chat_id: u32,
contact_id: u32,
@@ -1342,7 +1407,7 @@ pub fn add_contact_to_chat_ex(
if contact.is_err() || chat_id <= DC_CHAT_ID_LAST_SPECIAL {
return 0;
}
let mut msg = unsafe { dc_msg_new_untyped(context) };
let mut msg = dc_msg_new_untyped(context);
reset_gossiped_timestamp(context, chat_id);
let contact = contact.unwrap();
@@ -1486,10 +1551,7 @@ pub unsafe fn remove_contact_from_chat(
"bad chat_id = {} <= 9",
chat_id
);
ensure!(
contact_id > DC_CONTACT_ID_LAST_SPECIAL || contact_id == DC_CONTACT_ID_SELF,
"Cannot remove special contact"
);
ensure!(contact_id != DC_CONTACT_ID_SELF, "Cannot remove self");
let mut msg = dc_msg_new_untyped(context);
let mut success = false;
@@ -1508,7 +1570,7 @@ pub unsafe fn remove_contact_from_chat(
} 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.is_promoted() {
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();
@@ -1616,7 +1678,7 @@ pub unsafe fn set_chat_name(
)
.is_ok()
{
if chat.is_promoted() {
if chat.param.get_int(Param::Unpromoted).unwrap_or_default() == 0 {
msg.type_0 = Viewtype::Text;
msg.text = Some(context.stock_system_msg(
StockMessage::MsgGrpName,
@@ -1653,62 +1715,83 @@ pub unsafe fn set_chat_name(
}
#[allow(non_snake_case)]
pub fn set_chat_profile_image(
pub unsafe fn set_chat_profile_image(
context: &Context,
chat_id: u32,
new_image: impl AsRef<str>, // XXX use PathBuf
new_image: impl AsRef<str>,
) -> Result<(), Error> {
ensure!(chat_id > DC_CHAT_ID_LAST_SPECIAL, "Invalid chat ID");
let mut OK_TO_CONTINUE = true;
let mut success = false;
let mut chat = Chat::load_from_db(context, chat_id)?;
let mut msg = dc_msg_new_untyped(context);
let mut new_image_rel = None;
if real_group_exists(context, chat_id) {
/* we should respect this - whatever we send to the group, it gets discarded anyway! */
if !(is_contact_in_chat(context, chat_id, DC_CONTACT_ID_SELF) == 1i32) {
if !(is_contact_in_chat(context, chat_id, 1i32 as u32) == 1i32) {
log_event!(
context,
Event::ERROR_SELF_NOT_IN_GROUP,
0,
"Cannot set chat profile image; self not in group.",
);
bail!("Failed to set profile image");
}
let mut new_image_rel: String;
if !new_image.as_ref().is_empty() {
new_image_rel = new_image.as_ref().to_string();
if !dc_make_rel_and_copy(context, &mut new_image_rel) {
bail!("Failed to get relative path for profile image");
}
} else {
new_image_rel = "".to_string();
}
chat.param.set(Param::ProfileImage, &new_image_rel);
if chat.update_param().is_ok() {
if chat.is_promoted() {
let mut msg = unsafe { dc_msg_new_untyped(context) };
msg.param.set_int(Param::Cmd, DC_CMD_GROUPIMAGE_CHANGED);
msg.type_0 = Viewtype::Text;
msg.text = Some(context.stock_system_msg(
if new_image_rel == "" {
msg.param.remove(Param::Arg);
StockMessage::MsgGrpImgDeleted
} else {
msg.param.set(Param::Arg, &new_image_rel);
StockMessage::MsgGrpImgChanged
},
"",
"",
DC_CONTACT_ID_SELF,
));
msg.id = send_msg(context, chat_id, &mut msg).unwrap_or_default();
emit_event!(context, Event::MSGS_CHANGED, chat_id, msg.id);
/* we should respect this - whatever we send to the group, it gets discarded anyway! */
if !new_image.as_ref().is_empty() {
let mut img = new_image.as_ref().to_string();
if !dc_make_rel_and_copy(context, &mut img) {
OK_TO_CONTINUE = false;
}
new_image_rel = Some(img);
} else {
OK_TO_CONTINUE = false;
}
}
if OK_TO_CONTINUE {
if let Some(ref new_image_rel) = new_image_rel {
chat.param.set(Param::ProfileImage, new_image_rel);
}
if chat.update_param().is_ok() {
if chat.param.get_int(Param::Unpromoted).unwrap_or_default() == 0 {
msg.param.set_int(Param::Cmd, 3);
if let Some(ref new_image_rel) = new_image_rel {
msg.param.set(Param::Arg, new_image_rel);
}
msg.type_0 = Viewtype::Text;
msg.text = Some(context.stock_system_msg(
if new_image_rel.is_some() {
StockMessage::MsgGrpImgChanged
} else {
StockMessage::MsgGrpImgDeleted
},
"",
"",
DC_CONTACT_ID_SELF,
));
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::CHAT_MODIFIED,
chat_id as uintptr_t,
0i32 as uintptr_t,
);
success = true;
}
emit_event!(context, Event::CHAT_MODIFIED, chat_id, 0);
return Ok(());
}
}
bail!("Failed to set profile image");
if !success {
bail!("Failed to set profile image");
}
Ok(())
}
pub unsafe fn forward_msgs(
@@ -1835,7 +1918,16 @@ pub fn get_chat_cnt(context: &Context) -> usize {
}
}
pub fn get_chat_id_by_grpid(context: &Context, grpid: impl AsRef<str>) -> (u32, bool, Blocked) {
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;
}
context
.sql
.query_row(
@@ -1844,12 +1936,17 @@ pub fn get_chat_id_by_grpid(context: &Context, grpid: impl AsRef<str>) -> (u32,
|row| {
let chat_id = row.get(0)?;
let b = row.get::<_, Option<Blocked>>(1)?.unwrap_or_default();
if let Some(b) = ret_blocked {
*b = row.get::<_, Option<Blocked>>(1)?.unwrap_or_default();
}
let v = row.get::<_, Option<Chattype>>(2)?.unwrap_or_default();
Ok((chat_id, v == Chattype::VerifiedGroup, b))
*ret_verified = (v == Chattype::VerifiedGroup) as libc::c_int;
Ok(chat_id)
},
)
.unwrap_or((0, false, Blocked::Not))
.unwrap_or_default()
}
pub fn add_device_msg(context: &Context, chat_id: u32, text: impl AsRef<str>) {

View File

@@ -33,7 +33,6 @@ pub enum Config {
E2eeEnabled,
#[strum(props(default = "1"))]
MdnsEnabled,
#[strum(props(default = "1"))]
InboxWatch,
#[strum(props(default = "1"))]
SentboxWatch,

View File

@@ -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 LoginParam,
pub in_emaildomain: &'a str,
pub in_emaillocalpart: &'a str,
pub out: LoginParam,
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 out_imap_set: libc::c_int,
pub out_smtp_set: libc::c_int,
pub tag_server: libc::c_int,
@@ -26,37 +26,44 @@ struct moz_autoconfigure_t<'a> {
pub unsafe fn moz_autoconfigure(
context: &Context,
url: &str,
param_in: &LoginParam,
) -> Option<LoginParam> {
let xml_raw = read_autoconf_file(context, url);
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);
if xml_raw.is_null() {
return None;
}
// Split address into local part and domain part.
let p = param_in.addr.find("@");
if p.is_none() {
moz_ac.in_emaillocalpart = param_in.addr.strdup();
let p = strchr(moz_ac.in_emaillocalpart, '@' as i32);
if p.is_null() {
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;
}
let (in_emaillocalpart, in_emaildomain) = param_in.addr.split_at(p.unwrap());
let in_emaildomain = &in_emaildomain[1..];
*p = 0 as libc::c_char;
moz_ac.in_emaildomain = dc_strdup(p.offset(1isize));
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)) => {
@@ -86,13 +93,17 @@ pub unsafe fn moz_autoconfigure(
|| moz_ac.out.send_server.is_empty()
|| moz_ac.out.send_port == 0
{
let r = moz_ac.out.to_string();
let r = dc_loginparam_get_readable(&moz_ac.out);
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)
}
@@ -104,8 +115,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 = moz_ac.in_emaillocalpart;
let email_domain = moz_ac.in_emaildomain;
let email_local = as_str(moz_ac.in_emaillocalpart);
let email_domain = as_str(moz_ac.in_emaildomain);
let val = val
.trim()

View File

@@ -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 LoginParam,
pub out: LoginParam,
pub in_0: &'a dc_loginparam_t,
pub out: dc_loginparam_t,
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: &LoginParam,
) -> Option<LoginParam> {
param_in: &dc_loginparam_t,
) -> Option<dc_loginparam_t> {
let mut xml_raw: *mut libc::c_char = ptr::null_mut();
let mut url = url__.strdup();
let mut outlk_ad = outlk_autodiscover_t {
in_0: param_in,
out: LoginParam::new(),
out: dc_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, as_str(url));
xml_raw = read_autoconf_file(context, 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 = outlk_ad.out.to_string();
let r = dc_loginparam_get_readable(&outlk_ad.out);
warn!(context, 0, "Bad or incomplete autoconfig: {}", r,);
free(url as *mut libc::c_void);
free(xml_raw as *mut libc::c_void);

File diff suppressed because it is too large Load Diff

View File

@@ -18,12 +18,6 @@ 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;
@@ -48,7 +42,7 @@ impl Default for Blocked {
pub const DC_IMAP_SEEN: u32 = 0x1;
pub const DC_HANDSHAKE_CONTINUE_NORMAL_PROCESSING: i32 = 0x01;
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;
@@ -58,7 +52,7 @@ pub const DC_GCL_ADD_ALLDONE_HINT: usize = 0x04;
const DC_GCM_ADDDAYMARKER: usize = 0x01;
pub const DC_GCL_VERIFIED_ONLY: usize = 0x01;
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
@@ -77,7 +71,7 @@ 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: u32 = 4;
/// virtual chat showing all messages flagged with msgs.starred=2
pub const DC_CHAT_ID_STARRED: u32 = 5;
const DC_CHAT_ID_STARRED: u32 = 5;
/// only an indicator in a chatlist
pub const DC_CHAT_ID_ARCHIVED_LINK: u32 = 6;
/// only an indicator in a chatlist
@@ -175,12 +169,6 @@ 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 {
@@ -226,12 +214,6 @@ pub enum Viewtype {
File = 60,
}
impl Default for Viewtype {
fn default() -> Self {
Viewtype::Unknown
}
}
#[cfg(test)]
mod tests {
use super::*;
@@ -557,6 +539,6 @@ pub const DC_CMD_GROUPIMAGE_CHANGED: libc::c_int = 3;
pub const DC_CMD_MEMBER_ADDED_TO_GROUP: libc::c_int = 4;
pub const DC_CMD_MEMBER_REMOVED_FROM_GROUP: libc::c_int = 5;
pub const DC_CMD_AUTOCRYPT_SETUP_MESSAGE: libc::c_int = 6;
pub const DC_CMD_SECUREJOIN_MESSAGE: libc::c_int = 7;
const DC_CMD_SECUREJOIN_MESSAGE: libc::c_int = 7;
pub const DC_CMD_LOCATION_STREAMING_ENABLED: libc::c_int = 8;
pub const DC_CMD_LOCATION_ONLY: libc::c_int = 9;
const DC_CMD_LOCATION_ONLY: libc::c_int = 9;

View File

@@ -1,23 +1,25 @@
use deltachat_derive::*;
use itertools::Itertools;
use num_traits::{FromPrimitive, ToPrimitive};
use rusqlite;
use std::path::PathBuf;
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;
@@ -58,9 +60,7 @@ pub struct Contact<'a> {
}
/// Possible origins of a contact.
#[derive(
Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, FromPrimitive, ToPrimitive, FromSql, ToSql,
)]
#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, FromPrimitive, ToPrimitive)]
#[repr(i32)]
pub enum Origin {
Unknown = 0,
@@ -98,9 +98,20 @@ pub enum Origin {
ManuallyCreated = 0x4000000,
}
impl Default for Origin {
fn default() -> Self {
Origin::Unknown
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)
}
}
@@ -332,22 +343,19 @@ 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: Origin = row.get(3)?;
let row_origin = row.get(3)?;
let row_authname: String = row.get(4)?;
if !name.as_ref().is_empty() {
if !row_name.is_empty() {
if origin >= row_origin && name.as_ref() != row_name {
update_name = true;
}
} else {
if !name.as_ref().is_empty() && !row_name.is_empty() {
if origin >= row_origin && name.as_ref() != row_name {
update_name = true;
}
if origin == Origin::IncomingUnknownFrom && name.as_ref() != row_authname {
update_authname = true;
}
} else {
update_name = true;
}
if origin == Origin::IncomingUnknownFrom && name.as_ref() != row_authname {
update_authname = true;
}
Ok((row_id, row_name, row_addr, row_origin, row_authname))
},
) {
@@ -387,7 +395,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(), Chattype::Single, row_id]
params![name.as_ref(), 100, row_id]
).ok();
}
sth_modified = Modifier::Modified;
@@ -425,13 +433,19 @@ 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 `addr_book` is a multiline string in the format `Name one\nAddress one\nName two\nAddress two`.
/// The `adr_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, addr_book: impl AsRef<str>) -> Result<usize> {
pub fn add_address_book(context: &Context, adr_book: impl AsRef<str>) -> Result<usize> {
let mut modify_cnt = 0;
for (name, addr) in split_address_book(addr_book.as_ref()).into_iter() {
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];
let name = normalize_name(name);
let (_, modified) = Contact::add_or_lookup(context, name, addr, Origin::AdressBook)?;
if modified != Modifier::None {
@@ -465,10 +479,8 @@ 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 flag_verified_only || query.is_some() {
if (listflags & DC_GCL_VERIFIED_ONLY) > 0 || query.is_some() {
let s3str_like_cmd = format!(
"%{}%",
query
@@ -492,7 +504,7 @@ impl<'a> Contact<'a> {
0x100,
&s3str_like_cmd,
&s3str_like_cmd,
if flag_verified_only { 0 } else { 1 },
if 0 != listflags & 0x1 { 0 } else { 1 },
],
|row| row.get::<_, i32>(0),
|ids| {
@@ -532,7 +544,7 @@ impl<'a> Contact<'a> {
)?;
}
if flag_add_self && add_self {
if 0 != listflags & DC_GCL_ADD_SELF as u32 && add_self {
ret.push(DC_CONTACT_ID_SELF);
}
@@ -577,7 +589,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 = LoginParam::from_database(context, "configured_");
let loginparam = dc_loginparam_read(context, &context.sql, "configured_");
let mut self_key = Key::from_self_public(context, &loginparam.addr, &context.sql);
@@ -591,7 +603,7 @@ impl<'a> Contact<'a> {
});
ret += &p;
if self_key.is_none() {
e2ee::ensure_secret_key_exists(context)?;
dc_ensure_secret_key_exists(context)?;
self_key = Key::from_self_public(context, &loginparam.addr, &context.sql);
}
let p = context.stock_str(StockMessage::FingerPrints);
@@ -767,11 +779,9 @@ impl<'a> Contact<'a> {
/// Get the contact's profile image.
/// 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<PathBuf> {
pub fn get_profile_image(&self) -> Option<String> {
if self.id == DC_CONTACT_ID_SELF {
if let Some(p) = self.context.get_config(Config::Selfavatar) {
return Some(PathBuf::from(p));
}
return self.context.get_config(Config::Selfavatar);
}
// TODO: else get image_abs from contact param
None
@@ -1033,21 +1043,6 @@ 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::*;
@@ -1083,14 +1078,4 @@ 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")]
)
}
}

View File

@@ -3,13 +3,14 @@ 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::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;
@@ -227,7 +228,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);
}
do_heuristics_moves(context, server_folder, msg_id);
dc_do_heuristics_moves(context, server_folder, msg_id);
if 0 != mark_seen {
job_add(
context,
@@ -331,8 +332,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 = LoginParam::from_database(context, "");
let l2 = LoginParam::from_database(context, "configured_");
let l = dc_loginparam_read(context, &context.sql, "");
let l2 = dc_loginparam_read(context, &context.sql, "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;
@@ -376,6 +377,8 @@ 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")
@@ -455,8 +458,8 @@ pub unsafe fn dc_get_info(context: &Context) -> *mut libc::c_char {
},
displayname.unwrap_or_else(|| unset.into()),
is_configured,
l,
l2,
l_readable_str,
l2_readable_str,
inbox_watch,
sentbox_watch,
mvbox_watch,
@@ -572,45 +575,6 @@ 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::*;

File diff suppressed because it is too large Load Diff

View File

@@ -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::*;
@@ -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) {
if dc_alloc_ongoing(context) == 0 {
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 = e2ee::ensure_secret_key_exists(context)?;
let self_addr = dc_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
@@ -211,8 +211,12 @@ pub fn dc_render_setup_file(context: &Context, passphrase: &str) -> Result<Strin
let encr = {
let private_key_asc_c = CString::yolo(private_key_asc);
let passphrase_c = CString::yolo(passphrase);
dc_pgp_symm_encrypt(passphrase_c.as_ptr(), private_key_asc_c.as_bytes())
.ok_or(format_err!("Failed to encrypt private key."))?
dc_pgp_symm_encrypt(
passphrase_c.as_ptr(),
private_key_asc_c.as_ptr() as *const libc::c_void,
private_key_asc_c.as_bytes().len(),
)
.ok_or(format_err!("Failed to encrypt private key."))?
};
let replacement = format!(
concat!(
@@ -445,10 +449,9 @@ pub unsafe fn dc_decrypt_setup_file(
|| binary_bytes == 0)
{
/* decrypt symmetrically */
if let Some(plain) = dc_pgp_symm_decrypt(
passphrase,
std::slice::from_raw_parts(binary as *const u8, binary_bytes),
) {
if let Some(plain) =
dc_pgp_symm_decrypt(passphrase, binary as *const libc::c_void, binary_bytes)
{
let payload_c = CString::new(plain).unwrap();
payload = strdup(payload_c.as_ptr());
}
@@ -499,9 +502,11 @@ 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 dc_alloc_ongoing(context) {
if !(0 == dc_alloc_ongoing(context)) {
ongoing_allocated_here = 1;
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);
@@ -517,7 +522,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 e2ee::ensure_secret_key_exists(context).is_err() {
if dc_ensure_secret_key_exists(context).is_err() {
error!(
context,
0,
@@ -559,6 +564,9 @@ 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(
@@ -704,7 +712,7 @@ The macro avoids weird values of 0% or 100% while still working. */
// TODO should return bool /rtn
#[allow(non_snake_case)]
unsafe fn export_backup(context: &Context, dir: *const libc::c_char) -> libc::c_int {
let mut ok_to_continue: bool;
let mut current_block: u64;
let mut success: libc::c_int = 0;
let mut delete_dest_file: libc::c_int = 0;
@@ -760,30 +768,32 @@ unsafe fn export_backup(context: &Context, dir: *const libc::c_char) -> libc::c_
.is_err()
{
/* error already logged */
ok_to_continue = false;
current_block = 11487273724841241105;
} else {
ok_to_continue = true;
current_block = 14648156034262866959;
}
} else {
ok_to_continue = true;
current_block = 14648156034262866959;
}
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();
match current_block {
11487273724841241105 => {}
_ => {
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
if let Ok(dir_handle) = std::fs::read_dir(dir) {
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() {
ok_to_continue = true;
current_block = 2631791190359682872;
break;
}
let entry = entry.unwrap();
@@ -795,7 +805,7 @@ unsafe fn export_backup(context: &Context, dir: *const libc::c_char) -> libc::c_
.shall_stop_ongoing
{
delete_dest_file = 1;
ok_to_continue = false;
current_block = 11487273724841241105;
break;
} else {
processed_files_cnt += 1;
@@ -840,7 +850,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! */
ok_to_continue = false;
current_block = 11487273724841241105;
break;
}
} else {
@@ -852,39 +862,43 @@ unsafe fn export_backup(context: &Context, dir: *const libc::c_char) -> libc::c_
Ok(())
}
).unwrap();
} else {
error!(
context,
0,
"Backup: Cannot copy from blob-directory \"{}\".",
as_str(context.get_blobdir()),
);
}
} else {
error!(
context,
0,
"Backup: Cannot copy from blob-directory \"{}\".",
as_str(context.get_blobdir()),
);
info!(context, 0, "Backup: No files to copy.",);
current_block = 2631791190359682872;
}
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())
);
};
error!(
context,
0,
"Backup: Cannot get info for blob-directory \"{}\".",
as_str(context.get_blobdir())
);
};
}
}
}
}

214
src/dc_loginparam.rs Normal file
View File

@@ -0,0 +1,214 @@
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
}

View File

@@ -15,9 +15,9 @@ 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_strencode::*;
use crate::dc_tools::*;
use crate::e2ee::*;
use crate::error::Error;
use crate::location;
use crate::message::*;
@@ -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) {
if clist_search_string_nocase(factory.recipients_addr, addr_c) == 0 {
clist_insert_after(
factory.recipients_names,
(*factory.recipients_names).last,
@@ -183,7 +183,7 @@ 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) {
if clist_search_string_nocase(factory.recipients_addr, email_to_remove_c) == 0 {
clist_insert_after(
factory.recipients_names,
(*factory.recipients_names).last,
@@ -353,7 +353,13 @@ 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 = E2eeHelper::default();
let mut e2ee_helper = dc_e2ee_helper_t {
encryption_successfull: 0,
cdata_to_free: ptr::null_mut(),
encrypted: 0,
signatures: Default::default(),
gossipped_addr: Default::default(),
};
if factory.loaded as libc::c_uint == DC_MF_NOTHING_LOADED as libc::c_int as libc::c_uint
|| !factory.out.is_null()
@@ -548,7 +554,7 @@ pub unsafe fn dc_mimefactory_render(factory: &mut dc_mimefactory_t) -> libc::c_i
dc_encode_header_words(name.as_ptr()),
),
);
if command == DC_CMD_MEMBER_REMOVED_FROM_GROUP {
if command == 5 {
let email_to_remove = factory
.msg
.param
@@ -567,7 +573,7 @@ pub unsafe fn dc_mimefactory_render(factory: &mut dc_mimefactory_t) -> libc::c_i
),
);
}
} else if command == DC_CMD_MEMBER_ADDED_TO_GROUP {
} else if command == 4 {
let msg = &factory.msg;
do_gossip = 1;
let email_to_add = msg.param.get(Param::Arg).unwrap_or_default().strdup();
@@ -599,7 +605,7 @@ pub unsafe fn dc_mimefactory_render(factory: &mut dc_mimefactory_t) -> libc::c_i
),
);
}
} else if command == DC_CMD_GROUPNAME_CHANGED {
} else if command == 2 {
let msg = &factory.msg;
let value_to_add = msg.param.get(Param::Arg).unwrap_or_default().strdup();
@@ -612,7 +618,7 @@ pub unsafe fn dc_mimefactory_render(factory: &mut dc_mimefactory_t) -> libc::c_i
value_to_add,
),
);
} else if command == DC_CMD_GROUPIMAGE_CHANGED {
} else if command == 3 {
let msg = &factory.msg;
grpimage = msg.param.get(Param::Arg);
if grpimage.is_none() {
@@ -626,7 +632,7 @@ pub unsafe fn dc_mimefactory_render(factory: &mut dc_mimefactory_t) -> libc::c_i
}
}
}
if command == DC_CMD_LOCATION_STREAMING_ENABLED {
if command == 8 {
mailimf_fields_add(
imf_fields,
mailimf_field_new_custom(
@@ -637,7 +643,7 @@ pub unsafe fn dc_mimefactory_render(factory: &mut dc_mimefactory_t) -> libc::c_i
),
);
}
if command == DC_CMD_AUTOCRYPT_SETUP_MESSAGE {
if command == 6 {
mailimf_fields_add(
imf_fields,
mailimf_field_new_custom(
@@ -650,7 +656,7 @@ pub unsafe fn dc_mimefactory_render(factory: &mut dc_mimefactory_t) -> libc::c_i
.stock_str(StockMessage::AcSetupMsgBody)
.strdup();
}
if command == DC_CMD_SECUREJOIN_MESSAGE {
if command == 7 {
let msg = &factory.msg;
let step = msg.param.get(Param::Arg).unwrap_or_default().strdup();
if strlen(step) > 0 {
@@ -726,7 +732,6 @@ pub unsafe fn dc_mimefactory_render(factory: &mut dc_mimefactory_t) -> libc::c_i
}
}
if let Some(grpimage) = grpimage {
info!(factory.context, 0, "setting group image '{}'", grpimage);
let mut meta = dc_msg_new_untyped(factory.context);
meta.type_0 = Viewtype::Image;
meta.param.set(Param::File, grpimage);
@@ -1028,7 +1033,7 @@ pub unsafe fn dc_mimefactory_render(factory: &mut dc_mimefactory_t) -> libc::c_i
),
);
if force_plaintext != 2 {
e2ee_helper.encrypt(
dc_e2ee_encrypt(
factory.context,
factory.recipients_addr,
force_plaintext,
@@ -1036,9 +1041,10 @@ pub unsafe fn dc_mimefactory_render(factory: &mut dc_mimefactory_t) -> libc::c_i
min_verified,
do_gossip,
message,
&mut e2ee_helper,
);
}
if e2ee_helper.encryption_successfull {
if 0 != e2ee_helper.encryption_successfull {
factory.out_encrypted = 1;
if 0 != do_gossip {
factory.out_gossiped = 1
@@ -1046,14 +1052,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);
}
e2ee_helper.thanks();
dc_e2ee_thanks(&mut e2ee_helper);
free(message_text as *mut libc::c_void);
free(message_text2 as *mut libc::c_void);
free(subject_str as *mut libc::c_void);
@@ -1084,7 +1090,7 @@ unsafe fn get_subject(
} else {
b"\x00" as *const u8 as *const libc::c_char
};
if msg.param.get_int(Param::Cmd).unwrap_or_default() == DC_CMD_AUTOCRYPT_SETUP_MESSAGE {
if msg.param.get_int(Param::Cmd).unwrap_or_default() == 6 {
ret = context.stock_str(StockMessage::AcSetupMsgSubject).strdup()
} else if chat.typ == Chattype::Group || chat.typ == Chattype::VerifiedGroup {
ret = format!(
@@ -1219,7 +1225,7 @@ 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(as_str(filename_to_send));
needs_ext = dc_needs_ext_header(filename_to_send);
mime_fields = mailmime_fields_new_filename(
MAILMIME_DISPOSITION_TYPE_ATTACHMENT as libc::c_int,
if needs_ext {
@@ -1256,7 +1262,7 @@ unsafe fn build_body_file(
strdup(
b"filename*\x00" as *const u8 as *const libc::c_char,
),
dc_encode_ext_header(as_str(filename_to_send)).strdup(),
dc_encode_ext_header(filename_to_send),
),
);
if !parm.is_null() {

View File

@@ -15,10 +15,10 @@ use mmime::other::*;
use crate::constants::Viewtype;
use crate::contact::*;
use crate::context::Context;
use crate::dc_e2ee::*;
use crate::dc_simplify::*;
use crate::dc_strencode::*;
use crate::dc_tools::*;
use crate::e2ee::*;
use crate::location;
use crate::param::*;
use crate::stock::StockMessage;
@@ -32,10 +32,10 @@ dc_mimeparser_t has no deep dependencies to Context or to the database
#[repr(C)]
pub struct dc_mimepart_t {
pub type_0: Viewtype,
pub is_meta: bool,
pub is_meta: libc::c_int,
pub int_mimetype: libc::c_int,
pub msg: Option<String>,
pub msg_raw: Option<String>,
pub msg_raw: *mut libc::c_char,
pub bytes: libc::c_int,
pub param: Params,
}
@@ -43,6 +43,7 @@ pub struct dc_mimepart_t {
/* *
* @class dc_mimeparser_t
*/
#[derive(Clone)]
#[allow(non_camel_case_types)]
pub struct dc_mimeparser_t<'a> {
pub parts: Vec<dc_mimepart_t>,
@@ -50,11 +51,11 @@ pub struct dc_mimeparser_t<'a> {
pub header: HashMap<String, *mut mailimf_field>,
pub header_root: *mut mailimf_fields,
pub header_protected: *mut mailimf_fields,
pub subject: Option<String>,
pub subject: *mut libc::c_char,
pub is_send_by_messenger: bool,
pub decrypting_failed: bool,
pub e2ee_helper: E2eeHelper,
pub is_forwarded: bool,
pub decrypting_failed: libc::c_int,
pub e2ee_helper: dc_e2ee_helper_t,
pub is_forwarded: libc::c_int,
pub context: &'a Context,
pub reports: Vec<*mut mailmime>,
pub is_system_message: libc::c_int,
@@ -69,11 +70,11 @@ pub fn dc_mimeparser_new(context: &Context) -> dc_mimeparser_t {
header: Default::default(),
header_root: std::ptr::null_mut(),
header_protected: std::ptr::null_mut(),
subject: None,
subject: std::ptr::null_mut(),
is_send_by_messenger: false,
decrypting_failed: false,
decrypting_failed: 0,
e2ee_helper: Default::default(),
is_forwarded: false,
is_forwarded: 0,
context,
reports: Vec::new(),
is_system_message: 0,
@@ -87,7 +88,10 @@ pub unsafe fn dc_mimeparser_unref(mimeparser: &mut dc_mimeparser_t) {
}
unsafe fn dc_mimeparser_empty(mimeparser: &mut dc_mimeparser_t) {
mimeparser.parts = vec![];
for part in mimeparser.parts.drain(..) {
dc_mimepart_unref(part);
}
assert!(mimeparser.parts.is_empty());
mimeparser.header_root = ptr::null_mut();
mimeparser.header.clear();
if !mimeparser.header_protected.is_null() {
@@ -96,20 +100,26 @@ unsafe fn dc_mimeparser_empty(mimeparser: &mut dc_mimeparser_t) {
}
mimeparser.is_send_by_messenger = false;
mimeparser.is_system_message = 0i32;
mimeparser.subject = None;
free(mimeparser.subject as *mut libc::c_void);
mimeparser.subject = ptr::null_mut();
if !mimeparser.mimeroot.is_null() {
mailmime_free(mimeparser.mimeroot);
mimeparser.mimeroot = ptr::null_mut()
}
mimeparser.is_forwarded = false;
mimeparser.is_forwarded = 0i32;
mimeparser.reports.clear();
mimeparser.decrypting_failed = false;
mimeparser.e2ee_helper.thanks();
mimeparser.decrypting_failed = 0i32;
dc_e2ee_thanks(&mut mimeparser.e2ee_helper);
mimeparser.location_kml = None;
mimeparser.message_kml = None;
}
unsafe fn dc_mimepart_unref(mut mimepart: dc_mimepart_t) {
mimepart.msg = None;
free(mimepart.msg_raw as *mut libc::c_void);
mimepart.msg_raw = ptr::null_mut();
}
const DC_MIMETYPE_AC_SETUP_FILE: i32 = 111;
pub unsafe fn dc_mimeparser_parse<'a>(context: &'a Context, body: &[u8]) -> dc_mimeparser_t<'a> {
@@ -125,22 +135,16 @@ pub unsafe fn dc_mimeparser_parse<'a>(context: &'a Context, body: &[u8]) -> dc_m
&mut mimeparser.mimeroot,
);
if r == MAILIMF_NO_ERROR as libc::c_int && !mimeparser.mimeroot.is_null() {
mimeparser
.e2ee_helper
.decrypt(mimeparser.context, mimeparser.mimeroot);
dc_e2ee_decrypt(
mimeparser.context,
mimeparser.mimeroot,
&mut mimeparser.e2ee_helper,
);
let mimeparser_ref = &mut mimeparser;
dc_mimeparser_parse_mime_recursive(mimeparser_ref, mimeparser_ref.mimeroot);
let field: *mut mailimf_field = dc_mimeparser_lookup_field(&mimeparser, "Subject");
let field: *mut mailimf_field = dc_mimeparser_lookup_field(&mut mimeparser, "Subject");
if !field.is_null() && (*field).fld_type == MAILIMF_FIELD_SUBJECT as libc::c_int {
let decoded = dc_decode_header_words((*(*field).fld_data.fld_subject).sbj_value);
if decoded.is_null()
/* XXX: can it happen? */
{
mimeparser.subject = None
} else {
mimeparser.subject = Some(to_string(decoded));
free(decoded.cast());
}
mimeparser.subject = dc_decode_header_words((*(*field).fld_data.fld_subject).sbj_value)
}
if !dc_mimeparser_lookup_optional_field(&mut mimeparser, "Chat-Version").is_null() {
mimeparser.is_send_by_messenger = true
@@ -168,7 +172,8 @@ pub unsafe fn dc_mimeparser_parse<'a>(context: &'a Context, body: &[u8]) -> dc_m
let mut i = 0;
while i != mimeparser.parts.len() {
if mimeparser.parts[i].int_mimetype != 111 {
mimeparser.parts.remove(i);
let part = mimeparser.parts.remove(i);
dc_mimepart_unref(part);
} else {
i += 1;
}
@@ -194,7 +199,7 @@ pub unsafe fn dc_mimeparser_parse<'a>(context: &'a Context, body: &[u8]) -> dc_m
if mimeparser.parts.len() >= 2 {
let imgpart = &mut mimeparser.parts[1];
if imgpart.type_0 == Viewtype::Image {
imgpart.is_meta = true;
imgpart.is_meta = 1i32
}
}
}
@@ -210,7 +215,7 @@ pub unsafe fn dc_mimeparser_parse<'a>(context: &'a Context, body: &[u8]) -> dc_m
|| filepart.type_0 == Viewtype::Voice
|| filepart.type_0 == Viewtype::Video
|| filepart.type_0 == Viewtype::File)
&& !filepart.is_meta
&& 0 == filepart.is_meta
};
if need_drop {
@@ -223,37 +228,42 @@ pub unsafe fn dc_mimeparser_parse<'a>(context: &'a Context, body: &[u8]) -> dc_m
mimeparser.parts[0].msg = None;
// swap new with old
std::mem::replace(&mut mimeparser.parts[0], filepart);
let old = std::mem::replace(&mut mimeparser.parts[0], filepart);
// unref old one
dc_mimepart_unref(old);
}
}
if let Some(ref subject) = mimeparser.subject {
if !mimeparser.subject.is_null() {
let mut prepend_subject: libc::c_int = 1i32;
if !mimeparser.decrypting_failed {
let colon = subject.find(':');
if colon == Some(2)
|| colon == Some(3)
if 0 == mimeparser.decrypting_failed {
let p: *mut libc::c_char = strchr(mimeparser.subject, ':' as i32);
if p.wrapping_offset_from(mimeparser.subject) == 2
|| p.wrapping_offset_from(mimeparser.subject) == 3
|| mimeparser.is_send_by_messenger
|| subject.contains("Chat:")
|| !strstr(
mimeparser.subject,
b"Chat:\x00" as *const u8 as *const libc::c_char,
)
.is_null()
{
prepend_subject = 0i32
}
}
if 0 != prepend_subject {
let subj = if let Some(n) = subject.find('[') {
&subject[0..n]
} else {
subject
let subj: *mut libc::c_char = dc_strdup(mimeparser.subject);
let p_0: *mut libc::c_char = strchr(subj, '[' as i32);
if !p_0.is_null() {
*p_0 = 0i32 as libc::c_char
}
.trim();
if !subj.is_empty() {
let subj_c = CString::yolo(subj);
dc_trim(subj);
if 0 != *subj.offset(0isize) {
for part in mimeparser.parts.iter_mut() {
if part.type_0 == Viewtype::Text {
let msg_c = part.msg.as_ref().unwrap().strdup();
let new_txt: *mut libc::c_char = dc_mprintf(
b"%s \xe2\x80\x93 %s\x00" as *const u8 as *const libc::c_char,
subj_c.as_ptr(),
subj,
msg_c,
);
free(msg_c.cast());
@@ -263,9 +273,10 @@ pub unsafe fn dc_mimeparser_parse<'a>(context: &'a Context, body: &[u8]) -> dc_m
}
}
}
free(subj as *mut libc::c_void);
}
}
if mimeparser.is_forwarded {
if 0 != mimeparser.is_forwarded {
for part in mimeparser.parts.iter_mut() {
part.param.set_int(Param::Forwarded, 1);
}
@@ -293,7 +304,7 @@ pub unsafe fn dc_mimeparser_parse<'a>(context: &'a Context, body: &[u8]) -> dc_m
}
}
}
if !mimeparser.decrypting_failed {
if 0 == mimeparser.decrypting_failed {
let dn_field: *const mailimf_optional_field = dc_mimeparser_lookup_optional_field(
&mimeparser,
"Chat-Disposition-Notification-To",
@@ -342,12 +353,10 @@ pub unsafe fn dc_mimeparser_parse<'a>(context: &'a Context, body: &[u8]) -> dc_m
if dc_mimeparser_get_last_nonmeta(&mut mimeparser).is_none() && mimeparser.reports.is_empty() {
let mut part_5 = dc_mimepart_new();
part_5.type_0 = Viewtype::Text;
part_5.msg = Some("".into());
if let Some(ref subject) = mimeparser.subject {
if !mimeparser.is_send_by_messenger {
part_5.msg = Some(subject.to_string())
}
if !mimeparser.subject.is_null() && !mimeparser.is_send_by_messenger {
part_5.msg = Some(to_string(mimeparser.subject));
} else {
part_5.msg = Some("".into());
}
mimeparser.parts.push(part_5);
};
@@ -360,10 +369,10 @@ pub unsafe fn dc_mimeparser_parse<'a>(context: &'a Context, body: &[u8]) -> dc_m
unsafe fn dc_mimepart_new() -> dc_mimepart_t {
dc_mimepart_t {
type_0: Viewtype::Unknown,
is_meta: false,
is_meta: 0,
int_mimetype: 0,
msg: None,
msg_raw: None,
msg_raw: std::ptr::null_mut(),
bytes: 0,
param: Params::new(),
}
@@ -372,7 +381,11 @@ unsafe fn dc_mimepart_new() -> dc_mimepart_t {
pub fn dc_mimeparser_get_last_nonmeta<'a>(
mimeparser: &'a mut dc_mimeparser_t,
) -> Option<&'a mut dc_mimepart_t> {
mimeparser.parts.iter_mut().rev().find(|part| !part.is_meta)
mimeparser
.parts
.iter_mut()
.rev()
.find(|part| part.is_meta == 0)
}
/*the result must be freed*/
@@ -586,12 +599,12 @@ unsafe fn dc_mimeparser_parse_mime_recursive(
.stock_str(StockMessage::CantDecryptMsgBody);
let txt = format!("[{}]", msg_body);
part.msg_raw = Some(txt.clone());
part.msg_raw = txt.strdup();
part.msg = Some(txt);
mimeparser.parts.push(part);
any_part_added = 1i32;
mimeparser.decrypting_failed = true;
mimeparser.decrypting_failed = 1i32
}
46 => {
cur = (*(*mime).mm_data.mm_multipart.mm_mp_list).first;
@@ -1114,18 +1127,13 @@ unsafe fn dc_mimeparser_add_single_part_if_known(
part.type_0 = Viewtype::Text;
part.int_mimetype = mime_type;
part.msg = Some(simplified_txt);
part.msg_raw = {
let raw_c =
strndup(decoded_data, decoded_data_bytes as libc::c_ulong);
let raw = to_string(raw_c);
free(raw_c.cast());
Some(raw)
};
part.msg_raw =
strndup(decoded_data, decoded_data_bytes as libc::c_ulong);
do_add_single_part(mimeparser, part);
}
if simplifier.unwrap().is_forwarded {
mimeparser.is_forwarded = true;
mimeparser.is_forwarded = 1i32
}
}
}
@@ -1204,8 +1212,8 @@ unsafe fn dc_mimeparser_add_single_part_if_known(
}
if !filename_parts.is_empty() {
free(desired_filename as *mut libc::c_void);
desired_filename =
dc_decode_ext_header(filename_parts.as_bytes()).strdup();
let parts_c = CString::yolo(filename_parts);
desired_filename = dc_decode_ext_header(parts_c.as_ptr());
}
if desired_filename.is_null() {
let param = mailmime_find_ct_parameter(
@@ -1279,7 +1287,7 @@ unsafe fn dc_mimeparser_add_single_part_if_known(
mimeparser,
msg_type,
mime_type,
as_str(raw_mime),
raw_mime,
decoded_data,
decoded_data_bytes,
desired_filename,
@@ -1307,7 +1315,7 @@ unsafe fn do_add_single_file_part(
parser: &mut dc_mimeparser_t,
msg_type: Viewtype,
mime_type: libc::c_int,
raw_mime: &str,
raw_mime: *const libc::c_char,
decoded_data: *const libc::c_char,
decoded_data_bytes: size_t,
desired_filename: *const libc::c_char,
@@ -1333,7 +1341,7 @@ unsafe fn do_add_single_file_part(
part.int_mimetype = mime_type;
part.bytes = decoded_data_bytes as libc::c_int;
part.param.set(Param::File, as_str(pathNfilename));
part.param.set(Param::MimeType, raw_mime);
part.param.set(Param::MimeType, as_str(raw_mime));
if mime_type == 80 {
assert!(!decoded_data.is_null(), "invalid image data");
let data = std::slice::from_raw_parts(
@@ -1353,9 +1361,9 @@ unsafe fn do_add_single_file_part(
}
unsafe fn do_add_single_part(parser: &mut dc_mimeparser_t, mut part: dc_mimepart_t) {
if (*parser).e2ee_helper.encrypted && (*parser).e2ee_helper.signatures.len() > 0 {
if 0 != (*parser).e2ee_helper.encrypted && (*parser).e2ee_helper.signatures.len() > 0 {
part.param.set_int(Param::GuranteeE2ee, 1);
} else if (*parser).e2ee_helper.encrypted {
} else if 0 != (*parser).e2ee_helper.encrypted {
part.param.set_int(Param::ErroneousE2ee, 0x2);
}
parser.parts.push(part);
@@ -1650,7 +1658,9 @@ pub unsafe fn dc_mimeparser_repl_msg_by_error(
let part = &mut mimeparser.parts[0];
part.type_0 = Viewtype::Text;
part.msg = Some(format!("[{}]", to_string(error_msg)));
mimeparser.parts.truncate(1);
for part in mimeparser.parts.drain(1..) {
dc_mimepart_unref(part);
}
assert_eq!(mimeparser.parts.len(), 1);
}
@@ -1807,20 +1817,20 @@ mod tests {
let raw = b"Content-Type: multipart/mixed; boundary=\"==break==\";\nSubject: outer-subject\nX-Special-A: special-a\nFoo: Bar\nChat-Version: 0.0\n\n--==break==\nContent-Type: text/plain; protected-headers=\"v1\";\nSubject: inner-subject\nX-Special-B: special-b\nFoo: Xy\nChat-Version: 1.0\n\ntest1\n\n--==break==--\n\n\x00";
let mut mimeparser = dc_mimeparser_parse(&context.ctx, &raw[..]);
assert_eq!(mimeparser.subject, Some("inner-subject".into()));
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!(
&to_string((*of).fld_value as *const libc::c_char),
"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!(&to_string((*of).fld_value as *const libc::c_char), "Bar",);
assert_eq!(as_str((*of).fld_value as *const libc::c_char), "Bar",);
of = dc_mimeparser_lookup_optional_field(&mimeparser, "Chat-Version");
assert_eq!(&to_string((*of).fld_value as *const libc::c_char), "1.0",);
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);

44
src/dc_move.rs Normal file
View File

@@ -0,0 +1,44 @@
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);
}
}
}

View File

@@ -1,4 +1,3 @@
use std::ffi::CString;
use std::ptr;
use itertools::join;
@@ -14,8 +13,10 @@ use sha2::{Digest, Sha256};
use crate::chat::{self, Chat};
use crate::constants::*;
use crate::contact::*;
use crate::context::{do_heuristics_moves, Context};
use crate::context::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;
@@ -24,7 +25,6 @@ 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::*;
@@ -401,7 +401,7 @@ unsafe fn add_parts(
msgrmsg = 1;
*chat_id = 0;
allow_creation = 1;
let handshake = handle_securejoin_handshake(context, mime_parser, *from_id);
let handshake = dc_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;
@@ -626,7 +626,7 @@ unsafe fn add_parts(
|mut stmt, conn| {
for i in 0..icnt {
let part = &mut mime_parser.parts[i];
if part.is_meta {
if part.is_meta != 0 {
continue;
}
@@ -642,18 +642,14 @@ unsafe fn add_parts(
}
}
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,
subject_c.as_ptr(),
msg_raw.as_ptr(),
if !mime_parser.subject.is_null() {
mime_parser.subject
} else {
b"\x00" as *const u8 as *const libc::c_char
},
part.msg_raw,
)
}
if 0 != mime_parser.is_system_message {
@@ -739,7 +735,7 @@ unsafe fn add_parts(
}
}
do_heuristics_moves(context, server_folder.as_ref(), *insert_msg_id);
dc_do_heuristics_moves(context, server_folder.as_ref(), *insert_msg_id);
cleanup(mime_in_reply_to, mime_references, txt_raw);
Ok(())
@@ -923,7 +919,7 @@ unsafe fn handle_reports(
}
}
fn save_locations(
unsafe fn save_locations(
context: &Context,
mime_parser: &dc_mimeparser_t,
chat_id: u32,
@@ -1039,6 +1035,7 @@ 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();
@@ -1049,7 +1046,7 @@ unsafe fn create_or_lookup_group(
// pointer somewhere into mime_parser, must not be freed
let mut X_MrAddToGrp = std::ptr::null_mut();
let mut X_MrGrpNameChanged = 0;
let mut X_MrGrpImageChanged = "".to_string();
let mut X_MrGrpImageChanged = std::ptr::null();
let mut better_msg: String = From::from("");
let mut failure_reason = std::ptr::null_mut();
@@ -1078,6 +1075,7 @@ unsafe fn create_or_lookup_group(
}
set_better_msg(mime_parser, &better_msg);
// search the grpid in the header
let optional_field = dc_mimeparser_lookup_optional_field(mime_parser, "Chat-Group-ID");
if !optional_field.is_null() {
grpid = to_string((*optional_field).fld_value)
@@ -1172,8 +1170,7 @@ unsafe fn create_or_lookup_group(
let optional_field =
dc_mimeparser_lookup_optional_field(mime_parser, "Chat-Group-Image");
if !optional_field.is_null() {
// fld_value is a pointer somewhere into mime_parser, must not be freed
X_MrGrpImageChanged = as_str((*optional_field).fld_value).to_string();
X_MrGrpImageChanged = (*optional_field).fld_value
}
better_msg = context.stock_system_msg(
StockMessage::MsgAddMember,
@@ -1197,11 +1194,14 @@ unsafe fn create_or_lookup_group(
let optional_field =
dc_mimeparser_lookup_optional_field(mime_parser, "Chat-Group-Image");
if !optional_field.is_null() {
// fld_value is a pointer somewhere into mime_parser, must not be freed
X_MrGrpImageChanged = as_str((*optional_field).fld_value).to_string();
X_MrGrpImageChanged = (*optional_field).fld_value;
mime_parser.is_system_message = DC_CMD_GROUPIMAGE_CHANGED;
better_msg = context.stock_system_msg(
if X_MrGrpImageChanged == "0" {
if strcmp(
X_MrGrpImageChanged,
b"0\x00" as *const u8 as *const libc::c_char,
) == 0
{
StockMessage::MsgGrpImgDeleted
} else {
StockMessage::MsgGrpImgChanged
@@ -1217,9 +1217,14 @@ unsafe fn create_or_lookup_group(
set_better_msg(mime_parser, &better_msg);
// check, if we have a chat with this group ID
let (mut chat_id, chat_id_verified, _blocked) = chat::get_chat_id_by_grpid(context, &grpid);
chat_id = chat::get_chat_id_by_grpid(
context,
&grpid,
Some(&mut chat_id_blocked),
&mut chat_id_verified,
);
if chat_id != 0 {
if chat_id_verified
if 0 != chat_id_verified
&& 0 == check_verified_properties(
context,
mime_parser,
@@ -1316,7 +1321,6 @@ unsafe fn create_or_lookup_group(
if !X_MrAddToGrp.is_null() || !X_MrRemoveFromGrp.is_null() {
recreate_member_list = 1;
} else if 0 != X_MrGrpNameChanged && !grpname.is_null() && strlen(grpname) < 200 {
info!(context, 0, "updating grpname for chat {}", chat_id);
if sql::execute(
context,
&context.sql,
@@ -1328,39 +1332,49 @@ unsafe fn create_or_lookup_group(
context.call_cb(Event::CHAT_MODIFIED, chat_id as uintptr_t, 0);
}
}
if !X_MrGrpImageChanged.is_empty() {
info!(
context,
0, "grp-image-change {} chat {}", X_MrGrpImageChanged, chat_id
);
let mut changed = false;
let mut grpimage = "".to_string();
if X_MrGrpImageChanged == "0" {
changed = true;
if !X_MrGrpImageChanged.is_null() {
let mut ok = 0;
let mut grpimage = ptr::null_mut();
if strcmp(
X_MrGrpImageChanged,
b"0\x00" as *const u8 as *const libc::c_char,
) == 0
{
ok = 1
} else {
for part in &mut mime_parser.parts {
if part.type_0 == Viewtype::Image {
grpimage = part
.param
.get(Param::File)
.map(|s| s.to_string())
.unwrap_or_else(|| "".to_string());
info!(context, 0, "found image {:?}", grpimage);
changed = true;
.map(|s| s.strdup())
.unwrap_or_else(|| std::ptr::null_mut());
ok = 1
}
}
}
if changed {
info!(context, 0, "New group image set to '{}'.", grpimage);
if 0 != ok {
info!(
context,
0,
"New group image set to {}.",
if !grpimage.is_null() {
"DELETED".to_string()
} else {
to_string(grpimage)
},
);
if let Ok(mut chat) = Chat::load_from_db(context, chat_id) {
if grpimage.is_empty() {
if grpimage.is_null() {
chat.param.remove(Param::ProfileImage);
} else {
chat.param.set(Param::ProfileImage, grpimage);
chat.param.set(Param::ProfileImage, as_str(grpimage));
}
chat.update_param().unwrap();
send_EVENT_CHAT_MODIFIED = 1;
}
free(grpimage as *mut libc::c_void);
}
}
@@ -1561,8 +1575,8 @@ unsafe fn create_or_lookup_adhoc_group(
}
// use subject as initial chat name
if let Some(subject) = mime_parser.subject.as_ref().filter(|s| !s.is_empty()) {
grpname = subject.strdup();
if !mime_parser.subject.is_null() && 0 != *mime_parser.subject.offset(0isize) as libc::c_int {
grpname = dc_strdup(mime_parser.subject)
} else {
grpname = context
.stock_string_repl_int(StockMessage::Member, member_ids.len() as libc::c_int)
@@ -1669,7 +1683,10 @@ fn hex_hash(s: impl AsRef<str>) -> String {
}
#[allow(non_snake_case)]
fn search_chat_ids_by_contact_ids(context: &Context, unsorted_contact_ids: &Vec<u32>) -> Vec<u32> {
unsafe fn search_chat_ids_by_contact_ids(
context: &Context,
unsorted_contact_ids: &Vec<u32>,
) -> Vec<u32> {
/* searches chat_id's by the given contact IDs, may return zero, one or more chat_id's */
let mut contact_ids = Vec::with_capacity(23);
let mut chat_ids = Vec::with_capacity(23);
@@ -1686,13 +1703,7 @@ fn search_chat_ids_by_contact_ids(context: &Context, unsorted_contact_ids: &Vec<
let contact_ids_str = join(contact_ids.iter().map(|x| x.to_string()), ",");
context.sql.query_map(
format!(
"SELECT DISTINCT cc.chat_id, cc.contact_id \
FROM chats_contacts cc \
LEFT JOIN chats c ON c.id=cc.chat_id \
WHERE cc.chat_id IN(SELECT chat_id FROM chats_contacts WHERE contact_id IN({})) \
AND c.type=120 \
AND cc.contact_id!=1 \
ORDER BY cc.chat_id, cc.contact_id;",
"SELECT DISTINCT cc.chat_id, cc.contact_id FROM chats_contacts cc LEFT JOIN chats c ON c.id=cc.chat_id WHERE cc.chat_id IN(SELECT chat_id FROM chats_contacts WHERE contact_id IN({})) AND c.type=120 AND cc.contact_id!=1 ORDER BY cc.chat_id, cc.contact_id;",
contact_ids_str
),
params![],
@@ -1704,15 +1715,15 @@ fn search_chat_ids_by_contact_ids(context: &Context, unsorted_contact_ids: &Vec<
for row in rows {
let (chat_id, contact_id) = row?;
if chat_id != last_chat_id {
if chat_id as u32 != last_chat_id {
if matches == contact_ids.len() && mismatches == 0 {
chat_ids.push(last_chat_id);
}
last_chat_id = chat_id;
last_chat_id = chat_id as u32;
matches = 0;
mismatches = 0;
}
if matches < contact_ids.len() && contact_id == contact_ids[matches] {
if contact_id == contact_ids[matches] {
matches += 1;
} else {
mismatches += 1;
@@ -1751,7 +1762,7 @@ unsafe fn check_verified_properties(
}
};
if mimeparser.e2ee_helper.encrypted {
if 0 == mimeparser.e2ee_helper.encrypted {
verify_fail("This message is not encrypted".into());
return 0;
}
@@ -1864,7 +1875,10 @@ 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 is_known_rfc724_mid_in_list(context, (*(*field).fld_data.fld_in_reply_to).mid_list) {
if 0 != is_known_rfc724_mid_in_list(
context,
(*(*field).fld_data.fld_in_reply_to).mid_list,
) {
return 1;
}
}
@@ -1873,7 +1887,10 @@ 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 is_known_rfc724_mid_in_list(context, (*(*field).fld_data.fld_references).mid_list) {
if 0 != is_known_rfc724_mid_in_list(
context,
(*(*field).fld_data.fld_references).mid_list,
) {
return 1;
}
}
@@ -1881,18 +1898,29 @@ unsafe fn dc_is_reply_to_known_message(
0
}
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;
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 {
ptr::null_mut()
}) as *const libc::c_char,
) {
return 1;
}
cur = if !cur.is_null() {
(*cur).next
} else {
ptr::null_mut()
}
}
}
return false;
0
}
/// Check if a message is a reply to a known message (messenger or non-messenger).

1008
src/dc_securejoin.rs Normal file

File diff suppressed because it is too large Load Diff

View File

@@ -1,17 +1,83 @@
use std::borrow::Cow;
use std::ffi::CString;
use std::ffi::{CStr, 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 = ptr::null_mut();
@@ -224,63 +290,374 @@ pub unsafe fn dc_decode_header_words(in_0: *const libc::c_char) -> *mut libc::c_
out
}
pub fn dc_needs_ext_header(to_check: impl AsRef<str>) -> bool {
let to_check = to_check.as_ref();
if to_check.is_empty() {
return false;
#[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());
to_check.chars().any(|c| {
!(c.is_ascii_alphanumeric()
|| c == '-'
|| c == '_'
|| c == '_'
|| c == '.'
|| c == '~'
|| c == '%')
})
}
const EXT_ASCII_ST: &AsciiSet = &CONTROLS
.add(b' ')
.add(b'-')
.add(b'_')
.add(b'.')
.add(b'~')
.add(b'%');
/// 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)
}
/// 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
if let Some(index2) = bytes!(b'\'').find(&rest[1..]) {
let decoded = percent_decode(&rest[index2 + 2..]);
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();
}
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 {
return decoded.decode_utf8_lossy();
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 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;
String::from_utf8_lossy(to_decode)
res
}
/* ******************************************************************************
* Encode/decode modified UTF-7 as needed for IMAP, see RFC 2192
******************************************************************************/
// 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
}
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 = ptr::null_mut();
let mut charset: *mut libc::c_char = ptr::null_mut();
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);
// 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));
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());
}
}
}
}
}
free(charset as *mut libc::c_void);
if !decoded.is_null() {
decoded
} else {
dc_strdup(to_decode)
}
}
unsafe fn print_hex(target: *mut libc::c_char, cur: *const libc::c_char) {
@@ -295,8 +672,16 @@ 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 {
@@ -357,43 +742,99 @@ mod tests {
#[test]
fn test_dc_encode_ext_header() {
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",);
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_decode_ext_header(b"iso-8859-1\'en\'%A3%20rates");
assert_eq!(buf1, "£ rates",);
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"wrong\'format");
assert_eq!(buf1, "wrong\'format",);
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"\'\'");
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"x\'\'");
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"\'");
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"
);
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);
}
}
#[test]
fn test_dc_needs_ext_header() {
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);
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
);
}
}
#[test]
@@ -408,20 +849,69 @@ mod tests {
assert_eq!(to_string(hex.as_ptr() as *const _), "=3A");
}
use proptest::prelude::*;
#[test]
fn test_dc_urlencode_urldecode() {
unsafe {
let buf1 = percent_encode(b"Bj\xc3\xb6rn Petersen", NON_ALPHANUMERIC)
.to_string()
.strdup();
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);
}
assert_eq!(
CStr::from_ptr(buf1 as *const libc::c_char)
.to_str()
.unwrap(),
"Bj%C3%B6rn%20Petersen"
);
#[test]
fn test_ext_header_decode_anything(buf: Vec<u8>) {
// make sure this never panics
let _decoded = dc_decode_ext_header(&buf);
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);
}
}
#[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_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);
}
}

65
src/dc_token.rs Normal file
View File

@@ -0,0 +1,65 @@
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()
}

View File

@@ -83,6 +83,14 @@ 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,
@@ -97,6 +105,21 @@ 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;
@@ -261,18 +284,11 @@ 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 { "[...]" };
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') {
if approx_chars > 0 && buf.len() > approx_chars + ellipse.len() {
if let Some(index) = buf[..approx_chars].rfind(|c| c == ' ' || c == '\n') {
Cow::Owned(format!("{}{}", &buf[..index + 1], ellipse))
} else {
Cow::Owned(format!("{}{}", &buf[..end_pos], ellipse))
Cow::Owned(format!("{}{}", &buf[..approx_chars], ellipse))
}
} else {
Cow::Borrowed(buf)
@@ -442,10 +458,21 @@ pub unsafe fn clist_free_content(haystack: *const clist) {
pub unsafe fn clist_search_string_nocase(
haystack: *const clist,
needle: *const libc::c_char,
) -> bool {
(&*haystack)
.into_iter()
.any(|data| strcasecmp(data.cast(), needle) == 0)
) -> 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
}
/* date/time tools */
@@ -490,7 +517,7 @@ pub fn dc_gm2local_offset() -> i64 {
}
/* timesmearing */
pub fn dc_smeared_time(context: &Context) -> i64 {
pub unsafe 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();
@@ -626,21 +653,6 @@ 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
@@ -1371,32 +1383,6 @@ 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::*;
@@ -1493,6 +1479,23 @@ 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));
@@ -1540,36 +1543,6 @@ 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 {
@@ -1907,37 +1880,4 @@ 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);
}
}
}
}
}

View File

@@ -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::*;
@@ -516,7 +516,7 @@ impl Imap {
cfg.watch_folder = None;
}
pub fn connect(&self, context: &Context, lp: &LoginParam) -> bool {
pub fn connect(&self, context: &Context, lp: &dc_loginparam_t) -> bool {
if lp.mail_server.is_empty() || lp.mail_user.is_empty() || lp.mail_pw.is_empty() {
return false;
}
@@ -548,17 +548,19 @@ impl Imap {
}
let (teardown, can_idle, has_xlist) = match &mut *self.session.lock().unwrap() {
Some(ref mut session) => match session.capabilities() {
Ok(caps) => {
Some(ref mut session) => {
if let Ok(caps) = session.capabilities() {
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_str("IDLE");
let has_xlist = caps.has_str("XLIST");
let caps_list = caps
.iter()
.fold(String::new(), |s, c| s + &format!(" {:?}", c));
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
});
log_event!(
context,
Event::IMAP_CONNECTED,
@@ -569,12 +571,10 @@ impl Imap {
);
(false, can_idle, has_xlist)
}
}
Err(err) => {
info!(context, 0, "CAPABILITY command error: {}", err);
} else {
(true, false, false)
}
},
}
None => (true, false, false),
};

View File

@@ -10,11 +10,11 @@ use crate::configure::*;
use crate::constants::*;
use crate::context::Context;
use crate::dc_imex::*;
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,22 +25,13 @@ 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,
@@ -59,19 +50,11 @@ 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,
@@ -128,19 +111,31 @@ 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 = LoginParam::from_database(context, "configured_");
let loginparam = dc_loginparam_read(context, &context.sql, "configured_");
let connected = context.smtp.lock().unwrap().connect(context, &loginparam);
if !connected {
self.try_again_later(3i32, None);
return;
ok_to_continue = false;
} else {
ok_to_continue = true;
}
} else {
ok_to_continue = true;
}
if let Some(filename) = self.param.get(Param::File) {
if let Some(body) = dc_read_file_safe(context, filename) {
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) } {
if let Some(recipients) = self.param.get(Param::Recipients) {
let recipients_list = recipients
.split("\x1e")
@@ -152,50 +147,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 */
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, sock.error.clone());
} else {
dc_delete_file(context, filename);
if 0 != self.foreign_id {
dc_update_msg_state(
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,
MessageState::OutDelivered,
self.job_id,
);
let chat_id: i32 = context
.sql
.query_row_col(
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()
};
// 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(
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,
);
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,
);
}
}
}
} else {
@@ -203,12 +211,14 @@ impl Job {
}
}
}
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<String>) {
fn try_again_later(&mut self, try_again: libc::c_int, pending_error: Option<&str>) {
self.try_again = try_again;
self.pending_error = pending_error;
self.pending_error = pending_error.map(|s| s.to_string());
}
#[allow(non_snake_case)]
@@ -709,7 +719,9 @@ 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) {
if clist_search_string_nocase(mimefactory.recipients_addr, mimefactory.from_addr)
== 0i32
{
clist_insert_after(
mimefactory.recipients_names,
(*mimefactory.recipients_names).last,
@@ -859,9 +871,6 @@ 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),
@@ -1080,11 +1089,6 @@ 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,7 +1109,6 @@ pub fn job_add(
match thread {
Thread::Imap => interrupt_imap_idle(context),
Thread::Smtp => interrupt_smtp_idle(context),
Thread::Unknown => {}
}
}

View File

@@ -16,8 +16,6 @@ extern crate rusqlite;
extern crate strum;
#[macro_use]
extern crate strum_macros;
#[macro_use]
extern crate jetscii;
#[macro_use]
mod log;
@@ -32,7 +30,6 @@ pub mod configure;
pub mod constants;
pub mod contact;
pub mod context;
mod e2ee;
mod imap;
pub mod job;
mod job_thread;
@@ -54,16 +51,18 @@ pub mod x;
pub mod dc_array;
mod dc_dehtml;
mod dc_e2ee;
pub mod dc_imex;
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;

View File

@@ -220,7 +220,7 @@ pub fn send_locations_to_chat(context: &Context, chat_id: u32, seconds: i64) {
msg.text =
Some(context.stock_system_msg(StockMessage::MsgLocationEnabled, "", "", 0));
msg.param.set_int(Param::Cmd, 8);
chat::send_msg(context, chat_id, &mut msg).unwrap();
unsafe { 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);
@@ -607,7 +607,7 @@ pub fn job_do_DC_JOB_MAYBE_SEND_LOCATIONS(context: &Context, _job: &Job) {
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();
unsafe { chat::send_msg(context, chat_id as u32, &mut msg).unwrap() };
}
Ok(())
},

View File

@@ -57,10 +57,3 @@ 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);
};
}

View File

@@ -1,206 +0,0 @@
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
}

View File

@@ -38,12 +38,6 @@ 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::*;
@@ -165,6 +159,7 @@ 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);
@@ -270,7 +265,7 @@ pub unsafe fn dc_get_msg_info(context: &Context, msg_id: u32) -> *mut libc::c_ch
_ => {}
}
let p = dc_msg_get_file(&msg);
p = dc_msg_get_file(&msg);
if !p.is_null() && 0 != *p.offset(0isize) as libc::c_int {
ret += &format!(
"\nFile: {}, {}, bytes\n",
@@ -284,7 +279,9 @@ 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";
ret += &format!("Mimetype: {}\n", &dc_msg_get_filemime(&msg));
p = dc_msg_get_filemime(&msg);
ret += &format!("Mimetype: {}\n", as_str(p));
free(p as *mut libc::c_void);
}
let w = msg.param.get_int(Param::Width).unwrap_or_default();
let h = msg.param.get_int(Param::Height).unwrap_or_default();
@@ -350,16 +347,16 @@ impl<'a> Drop for Message<'a> {
}
}
pub fn dc_msg_get_filemime(msg: &Message) -> String {
pub unsafe fn dc_msg_get_filemime(msg: &Message) -> *mut libc::c_char {
if let Some(m) = msg.param.get(Param::MimeType) {
return m.to_string();
return m.strdup();
} 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.to_string();
return mime.strdup();
}
}
"application/octet-stream".to_string()
"application/octet-stream".strdup()
}
pub fn dc_msg_guess_msgtype_from_suffix(path: &Path) -> Option<(Viewtype, &str)> {
@@ -481,7 +478,8 @@ pub fn dc_msg_load_from_db<'a>(context: &'a Context, id: u32) -> Result<Message<
text = String::from_utf8_lossy(buf).into_owned();
}
} else {
text = "".to_string();
warn!(context, 0, "dc_msg_load_from_db: could not get text column for id {}", id);
text = "[ Could not read from db ]".to_string();
}
msg.text = Some(text);

View File

@@ -178,11 +178,8 @@ impl<'a> Peerstate<'a> {
OR gossip_key_fingerprint=? COLLATE NOCASE \
ORDER BY public_key_fingerprint=? DESC;";
Self::from_stmt(
context,
query,
params![fingerprint, fingerprint, fingerprint],
)
let fp = fingerprint.as_bytes();
Self::from_stmt(context, query, params![fp, fp, fp])
}
fn from_stmt<P>(context: &'a Context, query: &str, params: P) -> Option<Self>
@@ -526,10 +523,6 @@ 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]

View File

@@ -188,11 +188,15 @@ pub fn dc_pgp_create_keypair(addr: impl AsRef<str>) -> Option<(Key, Key)> {
}
pub fn dc_pgp_pk_encrypt(
plain: &[u8],
plain_text: *const libc::c_void,
plain_bytes: size_t,
public_keys_for_encryption: &Keyring,
private_key_for_signing: Option<&Key>,
) -> Option<String> {
let lit_msg = Message::new_literal_bytes("", plain);
assert!(!plain_text.is_null() && !plain_bytes > 0, "invalid input");
let bytes = unsafe { std::slice::from_raw_parts(plain_text as *const u8, plain_bytes) };
let lit_msg = Message::new_literal_bytes("", bytes);
let pkeys: Vec<&SignedPublicKey> = public_keys_for_encryption
.keys()
.into_iter()
@@ -223,11 +227,16 @@ pub fn dc_pgp_pk_encrypt(
}
pub fn dc_pgp_pk_decrypt(
ctext: &[u8],
ctext: *const libc::c_void,
ctext_bytes: size_t,
private_keys_for_decryption: &Keyring,
public_keys_for_validation: &Keyring,
ret_signature_fingerprints: Option<&mut HashSet<String>>,
) -> Option<Vec<u8>> {
assert!(!ctext.is_null() && ctext_bytes > 0, "invalid input");
let ctext = unsafe { std::slice::from_raw_parts(ctext as *const u8, ctext_bytes) };
// TODO: proper error handling
if let Ok((msg, _)) = Message::from_armor_single(Cursor::new(ctext)) {
let skeys: Vec<&SignedSecretKey> = private_keys_for_decryption
@@ -274,13 +283,19 @@ pub fn dc_pgp_pk_decrypt(
}
/// Symmetric encryption.
pub fn dc_pgp_symm_encrypt(passphrase: *const libc::c_char, plain: &[u8]) -> Option<String> {
pub fn dc_pgp_symm_encrypt(
passphrase: *const libc::c_char,
plain: *const libc::c_void,
plain_bytes: size_t,
) -> Option<String> {
assert!(!passphrase.is_null(), "invalid passphrase");
assert!(!plain.is_null() && !plain_bytes > 0, "invalid input");
let pw = unsafe { CStr::from_ptr(passphrase).to_str().unwrap() };
let bytes = unsafe { std::slice::from_raw_parts(plain as *const u8, plain_bytes) };
let mut rng = thread_rng();
let lit_msg = Message::new_literal_bytes("", plain);
let lit_msg = Message::new_literal_bytes("", bytes);
let s2k = StringToKey::new_default(&mut rng);
let msg = lit_msg.encrypt_with_password(&mut rng, s2k, Default::default(), || pw.into());
@@ -289,12 +304,18 @@ pub fn dc_pgp_symm_encrypt(passphrase: *const libc::c_char, plain: &[u8]) -> Opt
}
/// Symmetric decryption.
pub fn dc_pgp_symm_decrypt(passphrase: *const libc::c_char, ctext: &[u8]) -> Option<Vec<u8>> {
pub fn dc_pgp_symm_decrypt(
passphrase: *const libc::c_char,
ctext: *const libc::c_void,
ctext_bytes: size_t,
) -> Option<Vec<u8>> {
assert!(!passphrase.is_null(), "invalid passphrase");
assert!(!ctext.is_null() && !ctext_bytes > 0, "invalid input");
let pw = unsafe { CStr::from_ptr(passphrase).to_str().unwrap() };
let bytes = unsafe { std::slice::from_raw_parts(ctext as *const u8, ctext_bytes) };
let enc_msg = Message::from_bytes(Cursor::new(ctext));
let enc_msg = Message::from_bytes(Cursor::new(bytes));
enc_msg
.and_then(|msg| {

View File

@@ -1,782 +0,0 @@
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);
}
}
}

View File

@@ -4,7 +4,7 @@ use lettre::*;
use crate::constants::Event;
use crate::constants::*;
use crate::context::Context;
use crate::login_param::LoginParam;
use crate::dc_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: Option<String>,
pub error: *mut libc::c_char,
}
impl Smtp {
@@ -22,7 +22,7 @@ impl Smtp {
transport: None,
transport_connected: false,
from: None,
error: None,
error: std::ptr::null_mut(),
}
}
@@ -43,7 +43,7 @@ impl Smtp {
}
/// Connect using the provided login params
pub fn connect(&mut self, context: &Context, lp: &LoginParam) -> bool {
pub fn connect(&mut self, context: &Context, lp: &dc_loginparam_t) -> bool {
if self.is_connected() {
warn!(context, 0, "SMTP already connected.");
return true;
@@ -152,7 +152,6 @@ impl Smtp {
}
Err(err) => {
warn!(context, 0, "SMTP failed to send message: {}", err);
self.error = Some(format!("{}", err));
0
}
}

View File

@@ -1015,14 +1015,14 @@ pub fn housekeeping(context: &Context) {
match std::fs::metadata(entry.path()) {
Ok(stats) => {
let recently_created = stats.created().is_ok()
let created = stats.created().is_ok()
&& stats.created().unwrap() > keep_files_newer_than;
let recently_modified = stats.modified().is_ok()
let modified = stats.modified().is_ok()
&& stats.modified().unwrap() > keep_files_newer_than;
let recently_accessed = stats.accessed().is_ok()
let accessed = stats.accessed().is_ok()
&& stats.accessed().unwrap() > keep_files_newer_than;
if recently_created || recently_modified || recently_accessed {
if created || modified || accessed {
info!(
context,
0,
@@ -1090,20 +1090,13 @@ fn maybe_add_from_param(
) {
context
.sql
.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(())
},
)
.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(())
})
.unwrap_or_else(|err| {
warn!(context, 0, "sql: failed to add_from_param: {}", err);
});

View File

@@ -1,58 +0,0 @@
//! 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()
}

View File

@@ -12,13 +12,13 @@ if __name__ == "__main__":
s = re.sub(r"(?m)///.*$", "", s) # remove comments
unsafe = s.count("unsafe")
free = s.count("free(")
gotoblocks = s.count("ok_to_continue")
gotoblocks = s.count("current_block =")
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} ok_to_continue: {3: >3}".format(str(fn), unsafe, free, gotoblocks))
print("{0: <30} unsafe: {1: >3} free: {2: >3} goto-blocks: {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 ok_to_continue:", sum_gotoblocks)
print("total gotoblocks:", sum_gotoblocks)

View File

@@ -1,6 +1,7 @@
//! Stress some functions for testing; if used as a lib, this file is obsolete.
use std::collections::HashSet;
use std::ffi::CString;
use std::ptr;
use tempfile::{tempdir, TempDir};
@@ -493,18 +494,38 @@ fn test_encryption_decryption() {
assert_ne!(public_key, public_key2);
let original_text = b"This is a test";
let original_text: *const libc::c_char =
b"This is a test\x00" as *const u8 as *const libc::c_char;
let mut keyring = Keyring::default();
keyring.add_owned(public_key.clone());
keyring.add_ref(&public_key2);
let ctext_signed = dc_pgp_pk_encrypt(original_text, &keyring, Some(&private_key)).unwrap();
assert!(!ctext_signed.is_empty());
assert!(ctext_signed.starts_with("-----BEGIN PGP MESSAGE-----"));
let ctext = dc_pgp_pk_encrypt(
original_text as *const libc::c_void,
strlen(original_text),
&keyring,
Some(&private_key),
)
.unwrap();
let ctext_unsigned = dc_pgp_pk_encrypt(original_text, &keyring, None).unwrap();
assert!(!ctext_unsigned.is_empty());
assert!(ctext_unsigned.starts_with("-----BEGIN PGP MESSAGE-----"));
assert!(!ctext.is_empty());
assert!(ctext.starts_with("-----BEGIN PGP MESSAGE-----"));
let ctext_signed_bytes = ctext.len();
let ctext_signed = CString::yolo(ctext);
let ctext = dc_pgp_pk_encrypt(
original_text as *const libc::c_void,
strlen(original_text),
&keyring,
None,
)
.unwrap();
assert!(!ctext.is_empty());
assert!(ctext.starts_with("-----BEGIN PGP MESSAGE-----"));
let ctext_unsigned_bytes = ctext.len();
let ctext_unsigned = CString::yolo(ctext);
let mut keyring = Keyring::default();
keyring.add_owned(private_key);
@@ -518,39 +539,42 @@ fn test_encryption_decryption() {
let mut valid_signatures: HashSet<String> = Default::default();
let plain = dc_pgp_pk_decrypt(
ctext_signed.as_bytes(),
ctext_signed.as_ptr() as *const _,
ctext_signed_bytes,
&keyring,
&public_keyring,
Some(&mut valid_signatures),
)
.unwrap();
assert_eq!(plain, original_text,);
assert_eq!(std::str::from_utf8(&plain).unwrap(), as_str(original_text),);
assert_eq!(valid_signatures.len(), 1);
valid_signatures.clear();
let empty_keyring = Keyring::default();
let plain = dc_pgp_pk_decrypt(
ctext_signed.as_bytes(),
ctext_signed.as_ptr() as *const _,
ctext_signed_bytes,
&keyring,
&empty_keyring,
Some(&mut valid_signatures),
)
.unwrap();
assert_eq!(plain, original_text);
assert_eq!(std::str::from_utf8(&plain).unwrap(), as_str(original_text),);
assert_eq!(valid_signatures.len(), 0);
valid_signatures.clear();
let plain = dc_pgp_pk_decrypt(
ctext_signed.as_bytes(),
ctext_signed.as_ptr() as *const _,
ctext_signed_bytes,
&keyring,
&public_keyring2,
Some(&mut valid_signatures),
)
.unwrap();
assert_eq!(plain, original_text);
assert_eq!(std::str::from_utf8(&plain).unwrap(), as_str(original_text),);
assert_eq!(valid_signatures.len(), 0);
valid_signatures.clear();
@@ -558,26 +582,28 @@ fn test_encryption_decryption() {
public_keyring2.add_ref(&public_key);
let plain = dc_pgp_pk_decrypt(
ctext_signed.as_bytes(),
ctext_signed.as_ptr() as *const _,
ctext_signed_bytes,
&keyring,
&public_keyring2,
Some(&mut valid_signatures),
)
.unwrap();
assert_eq!(plain, original_text);
assert_eq!(std::str::from_utf8(&plain).unwrap(), as_str(original_text),);
assert_eq!(valid_signatures.len(), 1);
valid_signatures.clear();
let plain = dc_pgp_pk_decrypt(
ctext_unsigned.as_bytes(),
ctext_unsigned.as_ptr() as *const _,
ctext_unsigned_bytes,
&keyring,
&public_keyring,
Some(&mut valid_signatures),
)
.unwrap();
assert_eq!(plain, original_text);
assert_eq!(std::str::from_utf8(&plain).unwrap(), as_str(original_text),);
valid_signatures.clear();
@@ -586,10 +612,16 @@ fn test_encryption_decryption() {
let mut public_keyring = Keyring::default();
public_keyring.add_ref(&public_key);
let plain =
dc_pgp_pk_decrypt(ctext_signed.as_bytes(), &keyring, &public_keyring, None).unwrap();
let plain = dc_pgp_pk_decrypt(
ctext_signed.as_ptr() as *const _,
ctext_signed_bytes,
&keyring,
&public_keyring,
None,
)
.unwrap();
assert_eq!(plain, original_text);
assert_eq!(std::str::from_utf8(&plain).unwrap(), as_str(original_text),);
}
}