Compare commits

..

4 Commits

Author SHA1 Message Date
Simon Laux
3f731b0f98 remove macro 2019-09-08 16:57:56 +02:00
Simon Laux
99c5674a10 cargo fmt 2019-09-07 13:26:06 +02:00
Simon Laux
c4fe0316d4 remove closure
Co-authored-by: @Jikstra
2019-09-07 13:23:54 +02:00
Simon Laux
176417dc99 fix remove chat profile img 2019-09-07 12:34:56 +02:00
62 changed files with 6457 additions and 5683 deletions

107
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"
@@ -470,15 +457,6 @@ dependencies = [
"syn 0.15.44 (registry+https://github.com/rust-lang/crates.io-index)",
]
[[package]]
name = "debug_stub_derive"
version = "0.3.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
dependencies = [
"quote 0.3.15 (registry+https://github.com/rust-lang/crates.io-index)",
"syn 0.11.11 (registry+https://github.com/rust-lang/crates.io-index)",
]
[[package]]
name = "deltachat"
version = "1.0.0-alpha.4"
@@ -490,7 +468,6 @@ dependencies = [
"cc 1.0.40 (registry+https://github.com/rust-lang/crates.io-index)",
"charset 0.1.2 (registry+https://github.com/rust-lang/crates.io-index)",
"chrono 0.4.7 (registry+https://github.com/rust-lang/crates.io-index)",
"debug_stub_derive 0.3.0 (registry+https://github.com/rust-lang/crates.io-index)",
"deltachat_derive 0.1.0",
"escaper 0.1.0 (registry+https://github.com/rust-lang/crates.io-index)",
"failure 0.1.5 (registry+https://github.com/rust-lang/crates.io-index)",
@@ -499,7 +476,6 @@ dependencies = [
"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)",
"itertools 0.8.0 (registry+https://github.com/rust-lang/crates.io-index)",
"jetscii 0.4.4 (registry+https://github.com/rust-lang/crates.io-index)",
"lazy_static 1.4.0 (registry+https://github.com/rust-lang/crates.io-index)",
"lettre 0.9.2 (registry+https://github.com/rust-lang/crates.io-index)",
"libc 0.2.62 (registry+https://github.com/rust-lang/crates.io-index)",
@@ -513,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)",
@@ -1021,11 +996,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"
@@ -1677,25 +1647,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"
@@ -1733,11 +1684,6 @@ dependencies = [
"memchr 2.2.1 (registry+https://github.com/rust-lang/crates.io-index)",
]
[[package]]
name = "quote"
version = "0.3.15"
source = "registry+https://github.com/rust-lang/crates.io-index"
[[package]]
name = "quote"
version = "0.6.13"
@@ -2074,17 +2020,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"
@@ -2353,16 +2288,6 @@ name = "subtle"
version = "2.1.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
[[package]]
name = "syn"
version = "0.11.11"
source = "registry+https://github.com/rust-lang/crates.io-index"
dependencies = [
"quote 0.3.15 (registry+https://github.com/rust-lang/crates.io-index)",
"synom 0.11.3 (registry+https://github.com/rust-lang/crates.io-index)",
"unicode-xid 0.0.4 (registry+https://github.com/rust-lang/crates.io-index)",
]
[[package]]
name = "syn"
version = "0.14.9"
@@ -2393,14 +2318,6 @@ dependencies = [
"unicode-xid 0.2.0 (registry+https://github.com/rust-lang/crates.io-index)",
]
[[package]]
name = "synom"
version = "0.11.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
dependencies = [
"unicode-xid 0.0.4 (registry+https://github.com/rust-lang/crates.io-index)",
]
[[package]]
name = "synstructure"
version = "0.10.2"
@@ -2674,11 +2591,6 @@ name = "unicode-width"
version = "0.1.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
[[package]]
name = "unicode-xid"
version = "0.0.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
[[package]]
name = "unicode-xid"
version = "0.1.0"
@@ -2752,14 +2664,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"
@@ -2907,8 +2811,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"
@@ -2950,7 +2852,6 @@ dependencies = [
"checksum darling 0.9.0 (registry+https://github.com/rust-lang/crates.io-index)" = "fcfbcb0c5961907597a7d1148e3af036268f2b773886b8bb3eeb1e1281d3d3d6"
"checksum darling_core 0.9.0 (registry+https://github.com/rust-lang/crates.io-index)" = "6afc018370c3bff3eb51f89256a6bdb18b4fdcda72d577982a14954a7a0b402c"
"checksum darling_macro 0.9.0 (registry+https://github.com/rust-lang/crates.io-index)" = "c6d8dac1c6f1d29a41c4712b4400f878cb4fcc4c7628f298dd75038e024998d1"
"checksum debug_stub_derive 0.3.0 (registry+https://github.com/rust-lang/crates.io-index)" = "496b7f8a2f853313c3ca370641d7ff3e42c32974fdccda8f0684599ed0a3ff6b"
"checksum derive_builder 0.7.2 (registry+https://github.com/rust-lang/crates.io-index)" = "3ac53fa6a3cda160df823a9346442525dcaf1e171999a1cf23e67067e4fd64d4"
"checksum derive_builder_core 0.5.0 (registry+https://github.com/rust-lang/crates.io-index)" = "0288a23da9333c246bb18c143426074a6ae96747995c5819d2947b64cd942b37"
"checksum derive_more 0.14.1 (registry+https://github.com/rust-lang/crates.io-index)" = "6d944ac6003ed268757ef1ee686753b57efc5fcf0ebe7b64c9fc81e7e32ff839"
@@ -3005,7 +2906,6 @@ dependencies = [
"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"
@@ -3073,12 +2973,10 @@ 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"
"checksum quick-xml 0.15.0 (registry+https://github.com/rust-lang/crates.io-index)" = "c2b074258da4f2ccb1c450380c5bd8e77db2508de2161f574c70930d8e880482"
"checksum quote 0.3.15 (registry+https://github.com/rust-lang/crates.io-index)" = "7a6e920b65c65f10b2ae65c831a81a073a89edd28c7cce89475bff467ab4167a"
"checksum quote 0.6.13 (registry+https://github.com/rust-lang/crates.io-index)" = "6ce23b6b870e8f94f81fb0a363d65d86675884b34a09043c81e5562f11c1f8e1"
"checksum quote 1.0.1 (registry+https://github.com/rust-lang/crates.io-index)" = "49d77c41ca8767f2f41394c11a4eebccab83da25e7cc035387a3125f02be90a3"
"checksum r2d2 0.8.5 (registry+https://github.com/rust-lang/crates.io-index)" = "bc42ce75d9f4447fb2a04bbe1ed5d18dd949104572850ec19b164e274919f81b"
@@ -3112,7 +3010,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"
@@ -3146,11 +3043,9 @@ dependencies = [
"checksum strum 0.15.0 (registry+https://github.com/rust-lang/crates.io-index)" = "e5d1c33039533f051704951680f1adfd468fd37ac46816ded0d9ee068e60f05f"
"checksum strum_macros 0.15.0 (registry+https://github.com/rust-lang/crates.io-index)" = "47cd23f5c7dee395a00fa20135e2ec0fffcdfa151c56182966d7a3261343432e"
"checksum subtle 2.1.1 (registry+https://github.com/rust-lang/crates.io-index)" = "01f40907d9ffc762709e4ff3eb4a6f6b41b650375a3f09ac92b641942b7fb082"
"checksum syn 0.11.11 (registry+https://github.com/rust-lang/crates.io-index)" = "d3b891b9015c88c576343b9b3e41c2c11a51c219ef067b264bd9c8aa9b441dad"
"checksum syn 0.14.9 (registry+https://github.com/rust-lang/crates.io-index)" = "261ae9ecaa397c42b960649561949d69311f08eeaea86a65696e6e46517cf741"
"checksum syn 0.15.44 (registry+https://github.com/rust-lang/crates.io-index)" = "9ca4b3b69a77cbe1ffc9e198781b7acb0c7365a883670e8f1c1bc66fba79a5c5"
"checksum syn 1.0.2 (registry+https://github.com/rust-lang/crates.io-index)" = "2ae5cd13590144ea968ba5d5520da7a4c08415861014399b5b349f74591c375f"
"checksum synom 0.11.3 (registry+https://github.com/rust-lang/crates.io-index)" = "a393066ed9010ebaed60b9eafa373d4b1baac186dd7e008555b0f702b51945b6"
"checksum synstructure 0.10.2 (registry+https://github.com/rust-lang/crates.io-index)" = "02353edf96d6e4dc81aea2d8490a7e9db177bf8acb0e951c24940bf866cb313f"
"checksum tempdir 0.3.7 (registry+https://github.com/rust-lang/crates.io-index)" = "15f2b5fb00ccdf689e0149d1b1b3c03fead81c2b37735d812fa8bddbbf41b6d8"
"checksum tempfile 3.1.0 (registry+https://github.com/rust-lang/crates.io-index)" = "7a6e24d9338a0a5be79593e2fa15a648add6138caa803e2d5bc782c371732ca9"
@@ -3180,7 +3075,6 @@ dependencies = [
"checksum unicode-normalization 0.1.8 (registry+https://github.com/rust-lang/crates.io-index)" = "141339a08b982d942be2ca06ff8b076563cbe223d1befd5450716790d44e2426"
"checksum unicode-segmentation 1.3.0 (registry+https://github.com/rust-lang/crates.io-index)" = "1967f4cdfc355b37fd76d2a954fb2ed3871034eb4f26d60537d88795cfc332a9"
"checksum unicode-width 0.1.5 (registry+https://github.com/rust-lang/crates.io-index)" = "882386231c45df4700b275c7ff55b6f3698780a650026380e72dabe76fa46526"
"checksum unicode-xid 0.0.4 (registry+https://github.com/rust-lang/crates.io-index)" = "8c1f860d7d29cf02cb2f3f359fd35991af3d30bac52c57d265a3c461074cb4dc"
"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"
@@ -3192,7 +3086,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

@@ -50,14 +50,11 @@ image-meta = "0.1.0"
quick-xml = "0.15.0"
escaper = "0.1.0"
bitflags = "1.1.0"
jetscii = "0.4.4"
debug_stub_derive = "0.3.0"
[dev-dependencies]
tempfile = "3.0"
pretty_assertions = "0.6.1"
pretty_env_logger = "0.3.0"
proptest = "0.9.4"
[workspace]
members = [

View File

@@ -41,8 +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 -- -k "not qr"
tox --workdir "$TOXWORKDIR" -e py35,py36,py37 -- -k "qr"
tox --workdir "$TOXWORKDIR" -e lint,py35,py36,py37,auditwheels
popd
fi

View File

@@ -393,9 +393,8 @@ int dc_set_config (dc_context_t* context, const char*
* @memberof dc_context_t
* @param context The context object as created by dc_context_new(). For querying system values, this can be NULL.
* @param key The key to query.
* @return Returns current value of "key", if "key" is unset, the default
* value is returned. The returned value must be free()'d, NULL is never
* returned. If there is an error an empty string will be returned.
* @return Returns current value of "key", if "key" is unset, the default value is returned.
* The returned value must be free()'d, NULL is never returned.
*/
char* dc_get_config (dc_context_t* context, const char* key);

File diff suppressed because it is too large Load Diff

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

@@ -20,17 +20,18 @@ use deltachat::message::*;
use deltachat::peerstate::*;
use deltachat::qr::*;
use deltachat::sql;
use deltachat::types::*;
use deltachat::x::*;
use deltachat::Event;
use num_traits::FromPrimitive;
/// Reset database tables. This function is called from Core cmdline.
/// Argument is a bitmask, executing single or multiple actions in one call.
/// e.g. bitmask 7 triggers actions definded with bits 1, 2 and 4.
pub unsafe fn dc_reset_tables(context: &Context, bits: i32) -> i32 {
info!(context, "Resetting tables ({})...", bits);
info!(context, 0, "Resetting tables ({})...", bits);
if 0 != bits & 1 {
sql::execute(context, &context.sql, "DELETE FROM jobs;", params![]).unwrap();
info!(context, "(1) Jobs reset.");
info!(context, 0, "(1) Jobs reset.");
}
if 0 != bits & 2 {
sql::execute(
@@ -40,11 +41,11 @@ pub unsafe fn dc_reset_tables(context: &Context, bits: i32) -> i32 {
params![],
)
.unwrap();
info!(context, "(2) Peerstates reset.");
info!(context, 0, "(2) Peerstates reset.");
}
if 0 != bits & 4 {
sql::execute(context, &context.sql, "DELETE FROM keypairs;", params![]).unwrap();
info!(context, "(4) Private keypairs reset.");
info!(context, 0, "(4) Private keypairs reset.");
}
if 0 != bits & 8 {
sql::execute(
@@ -83,13 +84,10 @@ pub unsafe fn dc_reset_tables(context: &Context, bits: i32) -> i32 {
)
.unwrap();
sql::execute(context, &context.sql, "DELETE FROM leftgrps;", params![]).unwrap();
info!(context, "(8) Rest but server config reset.");
info!(context, 0, "(8) Rest but server config reset.");
}
context.call_cb(Event::MsgsChanged {
chat_id: 0,
msg_id: 0,
});
context.call_cb(Event::MSGS_CHANGED, 0, 0);
1
}
@@ -98,7 +96,7 @@ unsafe fn dc_poke_eml_file(context: &Context, filename: *const libc::c_char) ->
/* mainly for testing, may be called by dc_import_spec() */
let mut success: libc::c_int = 0i32;
let mut data: *mut libc::c_char = ptr::null_mut();
let mut data_bytes = 0;
let mut data_bytes: size_t = 0;
if !(dc_read_file(
context,
filename,
@@ -124,7 +122,7 @@ unsafe fn dc_poke_eml_file(context: &Context, filename: *const libc::c_char) ->
/// @return 1=success, 0=error.
unsafe fn poke_spec(context: &Context, spec: *const libc::c_char) -> libc::c_int {
if !context.sql.is_open() {
error!(context, "Import: Database not opened.");
error!(context, 0, "Import: Database not opened.");
return 0;
}
@@ -145,7 +143,7 @@ unsafe fn poke_spec(context: &Context, spec: *const libc::c_char) -> libc::c_int
} else {
let rs = context.sql.get_config(context, "import_spec");
if rs.is_none() {
error!(context, "Import: No file or folder given.");
error!(context, 0, "Import: No file or folder given.");
ok_to_continue = false;
} else {
ok_to_continue = true;
@@ -168,6 +166,7 @@ unsafe fn poke_spec(context: &Context, spec: *const libc::c_char) -> libc::c_int
if dir.is_err() {
error!(
context,
0,
"Import: Cannot open directory \"{}\".",
as_str(real_spec),
);
@@ -183,7 +182,7 @@ unsafe fn poke_spec(context: &Context, spec: *const libc::c_char) -> libc::c_int
let name = name_f.to_string_lossy();
if name.ends_with(".eml") {
let path_plus_name = format!("{}/{}", as_str(real_spec), name);
info!(context, "Import: {}", path_plus_name);
info!(context, 0, "Import: {}", path_plus_name);
let path_plus_name_c = CString::yolo(path_plus_name);
if 0 != dc_poke_eml_file(context, path_plus_name_c.as_ptr()) {
read_cnt += 1
@@ -196,15 +195,13 @@ unsafe fn poke_spec(context: &Context, spec: *const libc::c_char) -> libc::c_int
if ok_to_continue2 {
info!(
context,
0,
"Import: {} items read from \"{}\".",
read_cnt,
as_str(real_spec)
);
if read_cnt > 0 {
context.call_cb(Event::MsgsChanged {
chat_id: 0,
msg_id: 0,
});
context.call_cb(Event::MSGS_CHANGED, 0 as uintptr_t, 0 as uintptr_t);
}
success = 1
}
@@ -231,10 +228,11 @@ unsafe fn log_msg(context: &Context, prefix: impl AsRef<str>, msg: &Message) {
let msgtext = dc_msg_get_text(msg);
info!(
context,
0,
"{}#{}{}{}: {} (Contact#{}): {} {}{}{}{} [{}]",
prefix.as_ref(),
dc_msg_get_id(msg) as libc::c_int,
if dc_msg_get_showpadlock(msg) {
if 0 != dc_msg_get_showpadlock(msg) {
"🔒"
} else {
""
@@ -253,7 +251,11 @@ unsafe fn log_msg(context: &Context, prefix: impl AsRef<str>, msg: &Message) {
} else {
"[FRESH]"
},
if dc_msg_is_info(msg) { "[INFO]" } else { "" },
if 0 != dc_msg_is_info(msg) {
"[INFO]"
} else {
""
},
statestr,
&temp2,
);
@@ -266,6 +268,7 @@ unsafe fn log_msglist(context: &Context, msglist: &Vec<u32>) -> Result<(), Error
if msg_id == 9 as libc::c_uint {
info!(
context,
0,
"--------------------------------------------------------------------------------"
);
@@ -273,7 +276,7 @@ unsafe fn log_msglist(context: &Context, msglist: &Vec<u32>) -> Result<(), Error
} else if msg_id > 0 {
if lines_out == 0 {
info!(
context,
context, 0,
"--------------------------------------------------------------------------------",
);
lines_out += 1
@@ -285,7 +288,7 @@ unsafe fn log_msglist(context: &Context, msglist: &Vec<u32>) -> Result<(), Error
if lines_out > 0 {
info!(
context,
"--------------------------------------------------------------------------------"
0, "--------------------------------------------------------------------------------"
);
}
Ok(())
@@ -302,7 +305,7 @@ unsafe fn log_contactlist(context: &Context, contacts: &Vec<u32>) {
if let Ok(contact) = Contact::get_by_id(context, contact_id) {
let name = contact.get_name();
let addr = contact.get_addr();
let verified_state = contact.is_verified(context);
let verified_state = contact.is_verified();
let verified_str = if VerifiedStatus::Unverified != verified_state {
if verified_state == VerifiedStatus::BidirectVerified {
" √√"
@@ -334,11 +337,17 @@ unsafe fn log_contactlist(context: &Context, contacts: &Vec<u32>) {
);
}
info!(context, "Contact#{}: {}{}", contact_id, line, line2);
info!(context, 0, "Contact#{}: {}{}", contact_id, line, line2);
}
}
}
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()
}
@@ -419,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\
@@ -452,6 +461,28 @@ pub unsafe fn dc_cmdline(context: &Context, line: &str) -> Result<(), failure::E
============================================="
),
},
"auth" => {
if 0 == S_IS_AUTH {
let is_pw = context
.get_config(config::Config::MailPw)
.unwrap_or_default();
if arg1 == is_pw {
S_IS_AUTH = 1;
} else {
println!("Bad password.");
}
} else {
println!("Already authorized.");
}
}
"open" => {
ensure!(!arg1.is_empty(), "Argument <file> missing");
dc_close(context);
ensure!(dc_open(context, arg1, None), "Open failed");
}
"close" => {
dc_close(context);
}
"initiate-key-transfer" => {
let setup_code = dc_initiate_key_transfer(context);
if !setup_code.is_null() {
@@ -469,7 +500,7 @@ pub unsafe fn dc_cmdline(context: &Context, line: &str) -> Result<(), failure::E
let msg_id: u32 = arg1.parse()?;
let msg = dc_get_msg(context, msg_id)?;
if dc_msg_is_setupmessage(&msg) {
let setupcodebegin = dc_msg_get_setupcodebegin(context, &msg);
let setupcodebegin = dc_msg_get_setupcodebegin(&msg);
println!(
"The setup code for setup message Msg#{} starts with: {}",
msg_id,
@@ -485,7 +516,7 @@ pub unsafe fn dc_cmdline(context: &Context, line: &str) -> Result<(), failure::E
!arg1.is_empty() && !arg2.is_empty(),
"Arguments <msg-id> <setup-code> expected"
);
if !dc_continue_key_transfer(context, arg1.parse()?, arg2_c) {
if 0 == dc_continue_key_transfer(context, arg1.parse()?, arg2_c) {
bail!("Continue key transfer failed");
}
}
@@ -496,17 +527,17 @@ pub unsafe fn dc_cmdline(context: &Context, line: &str) -> Result<(), failure::E
}
}
"export-backup" => {
dc_imex(context, 11, Some(context.get_blobdir()), ptr::null());
dc_imex(context, 11, context.get_blobdir(), ptr::null());
}
"import-backup" => {
ensure!(!arg1.is_empty(), "Argument <backup-file> missing.");
dc_imex(context, 12, Some(arg1), ptr::null());
dc_imex(context, 12, arg1_c, ptr::null());
}
"export-keys" => {
dc_imex(context, 1, Some(context.get_blobdir()), ptr::null());
dc_imex(context, 1, context.get_blobdir(), ptr::null());
}
"import-keys" => {
dc_imex(context, 2, Some(context.get_blobdir()), ptr::null());
dc_imex(context, 2, context.get_blobdir(), ptr::null());
}
"export-setup" => {
let setup_code = dc_create_setup_code(context);
@@ -548,7 +579,7 @@ pub unsafe fn dc_cmdline(context: &Context, line: &str) -> Result<(), failure::E
println!("{}={:?}", key, val);
}
"info" => {
println!("{:#?}", context.get_info());
println!("{}", to_string(dc_get_info(context)));
}
"maybenetwork" => {
maybe_network(context);
@@ -568,16 +599,17 @@ pub unsafe fn dc_cmdline(context: &Context, line: &str) -> Result<(), failure::E
let cnt = chatlist.len();
if cnt > 0 {
info!(
context,
context, 0,
"================================================================================"
);
for i in (0..cnt).rev() {
let chat = Chat::load_from_db(context, chatlist.get_chat_id(i))?;
let temp_subtitle = chat.get_subtitle(context);
let temp_subtitle = chat.get_subtitle();
let temp_name = chat.get_name();
info!(
context,
0,
"{}#{}: {} [{}] [{} fresh]",
chat_prefix(&chat),
chat.get_id(),
@@ -585,7 +617,7 @@ pub unsafe fn dc_cmdline(context: &Context, line: &str) -> Result<(), failure::E
temp_subtitle,
chat::get_fresh_msg_cnt(context, chat.get_id()),
);
let lot = chatlist.get_summary(context, i, Some(&chat));
let lot = chatlist.get_summary(i, Some(&chat));
let statestr = if chat.is_archived() {
" [Archived]"
} else {
@@ -602,6 +634,7 @@ pub unsafe fn dc_cmdline(context: &Context, line: &str) -> Result<(), failure::E
let text2 = lot.get_text2();
info!(
context,
0,
"{}{}{}{} [{}]{}",
text1.unwrap_or(""),
if text1.is_some() { ": " } else { "" },
@@ -615,13 +648,13 @@ pub unsafe fn dc_cmdline(context: &Context, line: &str) -> Result<(), failure::E
},
);
info!(
context,
context, 0,
"================================================================================"
);
}
}
if location::is_sending_locations_to_chat(context, 0) {
info!(context, "Location streaming enabled.");
if location::is_sending_locations_to_chat(context, 0 as uint32_t) {
info!(context, 0, "Location streaming enabled.");
}
println!("{} chats", cnt);
}
@@ -640,10 +673,11 @@ pub unsafe fn dc_cmdline(context: &Context, line: &str) -> Result<(), failure::E
let sel_chat = sel_chat.as_ref().unwrap();
let msglist = chat::get_chat_msgs(context, sel_chat.get_id(), 0x1, 0);
let temp2 = sel_chat.get_subtitle(context);
let temp2 = sel_chat.get_subtitle();
let temp_name = sel_chat.get_name();
info!(
context,
0,
"{}#{}: {} [{}]{}",
chat_prefix(sel_chat),
sel_chat.get_id(),
@@ -656,7 +690,7 @@ pub unsafe fn dc_cmdline(context: &Context, line: &str) -> Result<(), failure::E
},
);
log_msglist(context, &msglist)?;
if let Some(draft) = chat::get_draft(context, sel_chat.get_id())? {
if let Ok(draft) = chat::get_draft(context, sel_chat.get_id()) {
log_msg(context, "Draft", &draft);
}
@@ -669,7 +703,7 @@ pub unsafe fn dc_cmdline(context: &Context, line: &str) -> Result<(), failure::E
"createchat" => {
ensure!(!arg1.is_empty(), "Argument <contact-id> missing.");
let contact_id: libc::c_int = arg1.parse()?;
let chat_id = chat::create_by_contact_id(context, contact_id as u32)?;
let chat_id = chat::create_by_contact_id(context, contact_id as uint32_t)?;
println!("Single#{} created successfully.", chat_id,);
}
@@ -698,10 +732,10 @@ pub unsafe fn dc_cmdline(context: &Context, line: &str) -> Result<(), failure::E
ensure!(!arg1.is_empty(), "Argument <contact-id> missing.");
let contact_id_0: libc::c_int = arg1.parse()?;
if chat::add_contact_to_chat(
if 0 != chat::add_contact_to_chat(
context,
sel_chat.as_ref().unwrap().get_id(),
contact_id_0 as u32,
contact_id_0 as uint32_t,
) {
println!("Contact added to chat.");
} else {
@@ -715,7 +749,7 @@ pub unsafe fn dc_cmdline(context: &Context, line: &str) -> Result<(), failure::E
chat::remove_contact_from_chat(
context,
sel_chat.as_ref().unwrap().get_id(),
contact_id_1 as u32,
contact_id_1 as uint32_t,
)?;
println!("Contact added to chat.");
@@ -739,7 +773,7 @@ pub unsafe fn dc_cmdline(context: &Context, line: &str) -> Result<(), failure::E
ensure!(sel_chat.is_some(), "No chat selected.");
let contacts = chat::get_chat_contacts(context, sel_chat.as_ref().unwrap().get_id());
info!(context, "Memberlist:");
info!(context, 0, "Memberlist:");
log_contactlist(context, &contacts);
println!(
@@ -767,6 +801,7 @@ pub unsafe fn dc_cmdline(context: &Context, line: &str) -> Result<(), failure::E
let marker = location.marker.as_ref().unwrap_or(&default_marker);
info!(
context,
0,
"Loc#{}: {}: lat={} lng={} acc={} Chat#{} Contact#{} Msg#{} {}",
location.location_id,
dc_timestamp_to_str(location.timestamp),
@@ -780,7 +815,7 @@ pub unsafe fn dc_cmdline(context: &Context, line: &str) -> Result<(), failure::E
);
}
if locations.is_empty() {
info!(context, "No locations.");
info!(context, 0, "No locations.");
}
}
"sendlocations" => {
@@ -827,17 +862,18 @@ 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(if arg0 == "sendimage" {
Viewtype::Image
} else {
Viewtype::File
});
let mut msg = dc_msg_new(
context,
if arg0 == "sendimage" {
Viewtype::Image
} else {
Viewtype::File
},
);
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" => {
@@ -849,7 +885,7 @@ pub unsafe fn dc_cmdline(context: &Context, line: &str) -> Result<(), failure::E
0 as libc::c_uint
};
let msglist = context.search_msgs(chat, arg1);
let msglist = dc_search_msgs(context, chat, arg1_c);
log_msglist(context, &msglist)?;
println!("{} messages.", msglist.len());
@@ -858,7 +894,7 @@ pub unsafe fn dc_cmdline(context: &Context, line: &str) -> Result<(), failure::E
ensure!(sel_chat.is_some(), "No chat selected.");
if !arg1.is_empty() {
let mut draft = dc_msg_new(Viewtype::Text);
let mut draft = dc_msg_new(context, Viewtype::Text);
dc_msg_set_text(&mut draft, arg1_c);
chat::set_draft(
context,
@@ -912,7 +948,7 @@ pub unsafe fn dc_cmdline(context: &Context, line: &str) -> Result<(), failure::E
println!("{}", as_str(res));
}
"listfresh" => {
let msglist = context.get_fresh_msgs();
let msglist = dc_get_fresh_msgs(context);
log_msglist(context, &msglist)?;
print!("{} fresh messages.", msglist.len());
@@ -1018,17 +1054,16 @@ pub unsafe fn dc_cmdline(context: &Context, line: &str) -> Result<(), failure::E
res.get_text2()
);
}
// TODO: implement this again, unclear how to match this through though, without writing a parser.
// "event" => {
// ensure!(!arg1.is_empty(), "Argument <id> missing.");
// let event = arg1.parse()?;
// let event = Event::from_u32(event).ok_or(format_err!("Event::from_u32({})", event))?;
// let r = context.call_cb(event, 0 as libc::uintptr_t, 0 as libc::uintptr_t);
// println!(
// "Sending event {:?}({}), received value {}.",
// event, event as usize, r as libc::c_int,
// );
// }
"event" => {
ensure!(!arg1.is_empty(), "Argument <id> missing.");
let event = arg1.parse()?;
let event = Event::from_u32(event).ok_or(format_err!("Event::from_u32({})", event))?;
let r = context.call_cb(event, 0 as uintptr_t, 0 as uintptr_t);
println!(
"Sending event {:?}({}), received value {}.",
event, event as usize, r as libc::c_int,
);
}
"fileinfo" => {
ensure!(!arg1.is_empty(), "Argument <file> missing.");

View File

@@ -15,20 +15,21 @@ extern crate rusqlite;
use std::borrow::Cow::{self, Borrowed, Owned};
use std::io::{self, Write};
use std::path::Path;
use std::process::Command;
use std::ptr;
use std::sync::atomic::{AtomicBool, Ordering};
use std::sync::{Arc, Mutex, RwLock};
use deltachat::config;
use deltachat::configure::*;
use deltachat::constants::*;
use deltachat::context::*;
use deltachat::dc_tools::*;
use deltachat::job::*;
use deltachat::oauth2::*;
use deltachat::securejoin::*;
use deltachat::types::*;
use deltachat::x::*;
use deltachat::Event;
use rustyline::completion::{Completer, FilenameCompleter, Pair};
use rustyline::config::OutputStreamType;
use rustyline::error::ReadlineError;
@@ -43,75 +44,96 @@ use self::cmdline::*;
// Event Handler
fn receive_event(_context: &Context, event: Event) -> libc::uintptr_t {
unsafe extern "C" fn receive_event(
_context: &Context,
event: Event,
data1: uintptr_t,
data2: uintptr_t,
) -> uintptr_t {
match event {
Event::GetString { .. } => {}
Event::Info(msg) => {
Event::GET_STRING => {}
Event::INFO => {
/* do not show the event as this would fill the screen */
println!("{}", msg);
println!("{}", to_string(data2 as *const _),);
}
Event::SmtpConnected(msg) => {
println!("[DC_EVENT_SMTP_CONNECTED] {}", msg);
Event::SMTP_CONNECTED => {
println!("[DC_EVENT_SMTP_CONNECTED] {}", to_string(data2 as *const _));
}
Event::ImapConnected(msg) => {
println!("[DC_EVENT_IMAP_CONNECTED] {}", msg);
Event::IMAP_CONNECTED => {
println!("[DC_EVENT_IMAP_CONNECTED] {}", to_string(data2 as *const _),);
}
Event::SmtpMessageSent(msg) => {
println!("[DC_EVENT_SMTP_MESSAGE_SENT] {}", msg);
}
Event::Warning(msg) => {
println!("[Warning] {}", msg);
}
Event::Error(msg) => {
println!("\x1b[31m[DC_EVENT_ERROR] {}\x1b[0m", msg);
}
Event::ErrorNetwork(msg) => {
println!("\x1b[31m[DC_EVENT_ERROR_NETWORK] msg={}\x1b[0m", msg);
}
Event::ErrorSelfNotInGroup(msg) => {
println!("\x1b[31m[DC_EVENT_ERROR_SELF_NOT_IN_GROUP] {}\x1b[0m", msg);
}
Event::MsgsChanged { chat_id, msg_id } => {
print!(
"\x1b[33m{{Received DC_EVENT_MSGS_CHANGED(chat_id={}, msg_id={})}}\n\x1b[0m",
chat_id, msg_id,
Event::SMTP_MESSAGE_SENT => {
println!(
"[DC_EVENT_SMTP_MESSAGE_SENT] {}",
to_string(data2 as *const _),
);
}
Event::ContactsChanged(_) => {
Event::WARNING => {
println!("[Warning] {}", to_string(data2 as *const _),);
}
Event::ERROR => {
println!(
"\x1b[31m[DC_EVENT_ERROR] {}\x1b[0m",
to_string(data2 as *const _),
);
}
Event::ERROR_NETWORK => {
println!(
"\x1b[31m[DC_EVENT_ERROR_NETWORK] first={}, msg={}\x1b[0m",
data1 as usize,
to_string(data2 as *const _),
);
}
Event::ERROR_SELF_NOT_IN_GROUP => {
println!(
"\x1b[31m[DC_EVENT_ERROR_SELF_NOT_IN_GROUP] {}\x1b[0m",
to_string(data2 as *const _),
);
}
Event::MSGS_CHANGED => {
print!(
"\x1b[33m{{Received DC_EVENT_MSGS_CHANGED({}, {})}}\n\x1b[0m",
data1 as usize, data2 as usize,
);
}
Event::CONTACTS_CHANGED => {
print!("\x1b[33m{{Received DC_EVENT_CONTACTS_CHANGED()}}\n\x1b[0m");
}
Event::LocationChanged(contact) => {
Event::LOCATION_CHANGED => {
print!(
"\x1b[33m{{Received DC_EVENT_LOCATION_CHANGED(contact={:?})}}\n\x1b[0m",
contact,
"\x1b[33m{{Received DC_EVENT_LOCATION_CHANGED(contact={})}}\n\x1b[0m",
data1 as usize,
);
}
Event::ConfigureProgress(progress) => {
Event::CONFIGURE_PROGRESS => {
print!(
"\x1b[33m{{Received DC_EVENT_CONFIGURE_PROGRESS({} ‰)}}\n\x1b[0m",
progress,
data1 as usize,
);
}
Event::ImexProgress(progress) => {
Event::IMEX_PROGRESS => {
print!(
"\x1b[33m{{Received DC_EVENT_IMEX_PROGRESS({} ‰)}}\n\x1b[0m",
progress,
data1 as usize,
);
}
Event::ImexFileWritten(file) => {
Event::IMEX_FILE_WRITTEN => {
print!(
"\x1b[33m{{Received DC_EVENT_IMEX_FILE_WRITTEN({})}}\n\x1b[0m",
file.display()
to_string(data1 as *const _)
);
}
Event::ChatModified(chat) => {
Event::CHAT_MODIFIED => {
print!(
"\x1b[33m{{Received DC_EVENT_CHAT_MODIFIED({})}}\n\x1b[0m",
chat
data1 as usize,
);
}
_ => {
print!("\x1b[33m{{Received {:?}}}\n\x1b[0m", event);
print!(
"\x1b[33m{{Received {:?}({}, {})}}\n\x1b[0m",
event, data1 as usize, data2 as usize,
);
}
}
@@ -365,15 +387,17 @@ impl Highlighter for DcHelper {
impl Helper for DcHelper {}
fn main_0(args: Vec<String>) -> Result<(), failure::Error> {
if args.len() < 2 {
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],);
}
} else if args.len() != 1 {
println!("Error: Bad arguments, expected [db-name].");
return Err(format_err!("No db-name specified"));
}
let context = Context::new(
Box::new(receive_event),
"CLI".into(),
Path::new(&args[1]).to_path_buf(),
)?;
println!("Delta Chat Core is awaiting your commands.");

View File

@@ -1,5 +1,6 @@
extern crate deltachat;
use std::ffi::CStr;
use std::sync::{Arc, RwLock};
use std::{thread, time};
use tempfile::tempdir;
@@ -8,43 +9,42 @@ use deltachat::chat;
use deltachat::chatlist::*;
use deltachat::config;
use deltachat::configure::*;
use deltachat::constants::Event;
use deltachat::contact::*;
use deltachat::context::*;
use deltachat::job::{
perform_imap_fetch, perform_imap_idle, perform_imap_jobs, perform_smtp_idle, perform_smtp_jobs,
};
use deltachat::Event;
fn cb(_ctx: &Context, event: Event) -> usize {
print!("[{:?}]", event);
extern "C" fn cb(_ctx: &Context, event: Event, data1: usize, data2: usize) -> usize {
println!("[{:?}]", event);
match event {
Event::ConfigureProgress(progress) => {
print!(" progress: {}\n", progress);
Event::CONFIGURE_PROGRESS => {
println!(" progress: {}", data1);
0
}
Event::Info(msg) | Event::Warning(msg) | Event::Error(msg) | Event::ErrorNetwork(msg) => {
print!(" {}\n", msg);
0
}
_ => {
print!("\n");
Event::INFO | Event::WARNING | Event::ERROR | Event::ERROR_NETWORK => {
println!(
" {}",
unsafe { CStr::from_ptr(data2 as *const _) }
.to_str()
.unwrap()
);
0
}
_ => 0,
}
}
fn main() {
unsafe {
let dir = tempdir().unwrap();
let dbfile = dir.path().join("db.sqlite");
println!("creating database {:?}", dbfile);
let ctx =
Context::new(Box::new(cb), "FakeOs".into(), dbfile).expect("Failed to create context");
let ctx = dc_context_new(Some(cb), std::ptr::null_mut(), None);
let running = Arc::new(RwLock::new(true));
let info = ctx.get_info();
let info = dc_get_info(&ctx);
let info_s = CStr::from_ptr(info);
let duration = time::Duration::from_millis(4000);
println!("info: {:#?}", info);
println!("info: {}", info_s.to_str().unwrap());
let ctx = Arc::new(ctx);
let ctx1 = ctx.clone();
@@ -73,6 +73,13 @@ fn main() {
}
});
let dir = tempdir().unwrap();
let dbfile = dir.path().join("db.sqlite");
println!("opening database {:?}", dbfile);
assert!(dc_open(&ctx, dbfile.to_str().unwrap(), None));
println!("configuring");
let args = std::env::args().collect::<Vec<String>>();
assert_eq!(args.len(), 2, "missing password");
@@ -94,7 +101,7 @@ fn main() {
let chats = Chatlist::try_load(&ctx, 0, None, None).unwrap();
for i in 0..chats.len() {
let summary = chats.get_summary(&ctx, 0, None);
let summary = chats.get_summary(0, None);
let text1 = summary.get_text1();
let text2 = summary.get_text2();
println!("chat: {} - {:?} - {:?}", i, text1, text2,);
@@ -123,5 +130,6 @@ fn main() {
t2.join().unwrap();
println!("closing");
dc_close(&ctx);
}
}

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

@@ -65,7 +65,7 @@ Afterwards ``which python`` tells you that it comes out of the "venv"
directory that contains all python install artifacts. Let's first
install test tools::
pip install pytest pytest-timeout pytest-rerunfailures requests
pip install pytest pytest-timeout requests
then cargo-build and install the deltachat bindings::

View File

@@ -483,10 +483,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)

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
@@ -316,46 +315,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

@@ -147,15 +147,6 @@ class TestOfflineChat:
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")
@@ -598,59 +589,3 @@ class TestOnlineAccount:
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

@@ -17,19 +17,11 @@ def test_callback_None2int():
clear_context_callback(ctx)
def test_dc_close_events(tmpdir):
ctx = ffi.gc(
capi.lib.dc_context_new(capi.lib.py_dc_callback, ffi.NULL, ffi.NULL),
lib.dc_context_unref,
)
def test_dc_close_events():
ctx = capi.lib.dc_context_new(capi.lib.py_dc_callback, ffi.NULL, ffi.NULL)
evlog = EventLogger(ctx)
evlog.set_timeout(5)
set_context_callback(
ctx,
lambda ctx, evt_name, data1, data2: evlog(evt_name, data1, data2)
)
p = tmpdir.join("hello.db")
lib.dc_open(ctx, p.strpath.encode("ascii"), ffi.NULL)
set_context_callback(ctx, lambda ctx, evt_name, data1, data2: evlog(evt_name, data1, data2))
capi.lib.dc_close(ctx)
def find(info_string):

View File

@@ -16,10 +16,7 @@ passenv =
DCC_PY_LIVECONFIG
deps =
pytest
pytest-rerunfailures
pytest-timeout
pytest-xdist
auditwheel
pytest-faulthandler
pdbpp
requests
@@ -54,12 +51,11 @@ commands =
[pytest]
addopts = -v -rs --reruns 3 --reruns-delay 2
addopts = -v -rs
python_files = tests/test_*.py
norecursedirs = .tox
xfail_strict=true
timeout = 60
timeout_method = thread
timeout = 60
[flake8]
max-line-length = 120

View File

@@ -7,6 +7,7 @@ use mmime::mailimf_types::*;
use crate::constants::*;
use crate::contact::*;
use crate::dc_tools::as_str;
use crate::key::*;
/// Possible values for encryption preference
@@ -63,8 +64,11 @@ impl Aheader {
}
}
pub fn from_imffields(wanted_from: &str, header: *const mailimf_fields) -> Option<Self> {
if header.is_null() {
pub fn from_imffields(
wanted_from: *const libc::c_char,
header: *const mailimf_fields,
) -> Option<Self> {
if wanted_from.is_null() || header.is_null() {
return None;
}
@@ -90,7 +94,7 @@ impl Aheader {
match Self::from_str(value) {
Ok(test) => {
if addr_cmp(&test.addr, wanted_from) {
if addr_cmp(&test.addr, as_str(wanted_from)) {
if fine_header.is_none() {
fine_header = Some(test);
} else {

File diff suppressed because it is too large Load Diff

View File

@@ -31,13 +31,17 @@ use crate::stock::StockMessage;
/// first entry and only present on new messages, there is the rough idea that it can be optionally always
/// present and sorted into the list by date. Rendering the deaddrop in the described way
/// would not add extra work in the UI then.
#[derive(Debug)]
pub struct Chatlist {
pub struct Chatlist<'a> {
context: &'a Context,
/// Stores pairs of `chat_id, message_id`
ids: Vec<(u32, u32)>,
}
impl Chatlist {
impl<'a> Chatlist<'a> {
pub fn get_context(&self) -> &Context {
self.context
}
/// Get a list of chats.
/// The list can be filtered by query parameters.
///
@@ -81,7 +85,7 @@ impl Chatlist {
/// `query_contact_id`: An optional contact ID for filtering the list. Only chats including this contact ID
/// are returned.
pub fn try_load(
context: &Context,
context: &'a Context,
listflags: usize,
query: Option<&str>,
query_contact_id: Option<u32>,
@@ -196,7 +200,7 @@ impl Chatlist {
ids.push((DC_CHAT_ID_ARCHIVED_LINK, 0));
}
Ok(Chatlist { ids })
Ok(Chatlist { context, ids })
}
/// Find out the number of chats.
@@ -243,7 +247,7 @@ impl Chatlist {
/// - dc_lot_t::timestamp: the timestamp of the message. 0 if not applicable.
/// - dc_lot_t::state: The state of the message as one of the DC_STATE_* constants (see #dc_msg_get_state()).
// 0 if not applicable.
pub fn get_summary(&self, context: &Context, index: usize, chat: Option<&Chat>) -> Lot {
pub fn get_summary(&self, index: usize, chat: Option<&Chat<'a>>) -> Lot {
// The summary is created by the chat, not by the last message.
// This is because we may want to display drafts here or stuff as
// "is typing".
@@ -259,7 +263,7 @@ impl Chatlist {
let chat = if let Some(chat) = chat {
chat
} else {
if let Ok(chat) = Chat::load_from_db(context, self.ids[index].0) {
if let Ok(chat) = Chat::load_from_db(self.context, self.ids[index].0) {
chat_loaded = chat;
&chat_loaded
} else {
@@ -271,11 +275,11 @@ impl Chatlist {
let mut lastcontact = None;
let lastmsg = if 0 != lastmsg_id {
if let Ok(lastmsg) = dc_msg_load_from_db(context, lastmsg_id) {
if let Ok(lastmsg) = dc_msg_load_from_db(self.context, lastmsg_id) {
if lastmsg.from_id != 1 as libc::c_uint
&& (chat.typ == Chattype::Group || chat.typ == Chattype::VerifiedGroup)
{
lastcontact = Contact::load_from_db(context, lastmsg.from_id).ok();
lastcontact = Contact::load_from_db(self.context, lastmsg.from_id).ok();
}
Some(lastmsg)
@@ -290,9 +294,14 @@ impl Chatlist {
ret.text2 = None;
} else if lastmsg.is_none() || lastmsg.as_ref().unwrap().from_id == DC_CONTACT_ID_UNDEFINED
{
ret.text2 = Some(context.stock_str(StockMessage::NoMessages).to_string());
ret.text2 = Some(self.context.stock_str(StockMessage::NoMessages).to_string());
} else {
ret.fill(&mut lastmsg.unwrap(), chat, lastcontact.as_ref(), context);
ret.fill(
&mut lastmsg.unwrap(),
chat,
lastcontact.as_ref(),
self.context,
);
}
ret
@@ -302,10 +311,11 @@ impl Chatlist {
pub fn dc_get_archived_cnt(context: &Context) -> u32 {
context
.sql
.query_get_value(
.query_row_col(
context,
"SELECT COUNT(*) FROM chats WHERE blocked=0 AND archived=1;",
params![],
0,
)
.unwrap_or_default()
}
@@ -315,7 +325,7 @@ fn get_last_deaddrop_fresh_msg(context: &Context) -> u32 {
// only few fresh messages.
context
.sql
.query_get_value(
.query_row_col(
context,
"SELECT m.id FROM msgs m LEFT JOIN chats c ON c.id=m.chat_id \
WHERE m.state=10 \
@@ -323,6 +333,7 @@ fn get_last_deaddrop_fresh_msg(context: &Context) -> u32 {
AND c.blocked=2 \
ORDER BY m.timestamp DESC, m.id DESC;",
params![],
0,
)
.unwrap_or_default()
}

View File

@@ -73,7 +73,7 @@ impl Context {
let value = match key {
Config::Selfavatar => {
let rel_path = self.sql.get_config(self, key);
rel_path.map(|p| dc_get_abs_path(self, &p).to_str().unwrap().to_string())
rel_path.map(|p| dc_get_abs_path_safe(self, &p).to_str().unwrap().to_string())
}
Config::SysVersion => Some((&*DC_VERSION_STR).clone()),
Config::SysMsgsizeMaxRecommended => Some(format!("{}", 24 * 1024 * 1024 / 4 * 3)),

View File

@@ -1,10 +1,9 @@
use quick_xml;
use quick_xml::events::{BytesEnd, BytesStart, BytesText};
use crate::constants::*;
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;
@@ -14,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,
@@ -27,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)) => {
@@ -70,6 +76,7 @@ pub unsafe fn moz_autoconfigure(
Err(e) => {
error!(
context,
0,
"Configure xml: Error at position {}: {:?}",
reader.buffer_position(),
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();
warn!(context, "Bad or incomplete autoconfig: {}", r,);
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()
@@ -121,13 +132,13 @@ fn moz_autoconfigure_text_cb<B: std::io::BufRead>(
13 => {
let val_lower = val.to_lowercase();
if val_lower == "ssl" {
moz_ac.out.server_flags |= DC_LP_IMAP_SOCKET_SSL as i32
moz_ac.out.server_flags |= 0x200
}
if val_lower == "starttls" {
moz_ac.out.server_flags |= DC_LP_IMAP_SOCKET_STARTTLS as i32
moz_ac.out.server_flags |= 0x100
}
if val_lower == "plain" {
moz_ac.out.server_flags |= DC_LP_IMAP_SOCKET_PLAIN as i32
moz_ac.out.server_flags |= 0x400
}
}
_ => {}
@@ -140,13 +151,13 @@ fn moz_autoconfigure_text_cb<B: std::io::BufRead>(
13 => {
let val_lower = val.to_lowercase();
if val_lower == "ssl" {
moz_ac.out.server_flags |= DC_LP_SMTP_SOCKET_SSL as i32
moz_ac.out.server_flags |= 0x20000
}
if val_lower == "starttls" {
moz_ac.out.server_flags |= DC_LP_SMTP_SOCKET_STARTTLS as i32
moz_ac.out.server_flags |= 0x10000
}
if val_lower == "plain" {
moz_ac.out.server_flags |= DC_LP_SMTP_SOCKET_PLAIN as i32
moz_ac.out.server_flags |= 0x40000
}
}
_ => {}

View File

@@ -1,10 +1,9 @@
use quick_xml;
use quick_xml::events::{BytesEnd, BytesStart, BytesText};
use crate::constants::*;
use crate::context::Context;
use crate::dc_loginparam::*;
use crate::dc_tools::*;
use crate::login_param::LoginParam;
use crate::x::*;
use std::ptr;
@@ -14,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,
@@ -26,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,
@@ -51,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;
@@ -76,6 +75,7 @@ pub unsafe fn outlk_autodiscover(
Err(e) => {
error!(
context,
0,
"Configure xml: Error at position {}: {:?}",
reader.buffer_position(),
e
@@ -108,8 +108,8 @@ 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();
warn!(context, "Bad or incomplete autoconfig: {}", r,);
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);
outlk_clean_config(&mut outlk_ad);
@@ -168,9 +168,9 @@ unsafe fn outlk_autodiscover_endtag_cb(event: &BytesEnd, outlk_ad: &mut outlk_au
outlk_ad.out.mail_server = to_string(outlk_ad.config[2]);
outlk_ad.out.mail_port = port;
if 0 != ssl_on {
outlk_ad.out.server_flags |= DC_LP_IMAP_SOCKET_SSL as i32
outlk_ad.out.server_flags |= 0x200
} else if 0 != ssl_off {
outlk_ad.out.server_flags |= DC_LP_IMAP_SOCKET_PLAIN as i32
outlk_ad.out.server_flags |= 0x400
}
outlk_ad.out_imap_set = 1
} else if strcasecmp(
@@ -182,9 +182,9 @@ unsafe fn outlk_autodiscover_endtag_cb(event: &BytesEnd, outlk_ad: &mut outlk_au
outlk_ad.out.send_server = to_string(outlk_ad.config[2]);
outlk_ad.out.send_port = port;
if 0 != ssl_on {
outlk_ad.out.server_flags |= DC_LP_SMTP_SOCKET_SSL as i32
outlk_ad.out.server_flags |= 0x20000
} else if 0 != ssl_off {
outlk_ad.out.server_flags |= DC_LP_SMTP_SOCKET_PLAIN as i32
outlk_ad.out.server_flags |= 0x40000
}
outlk_ad.out_smtp_set = 1
}

View File

@@ -1,14 +1,16 @@
use percent_encoding::{utf8_percent_encode, NON_ALPHANUMERIC};
use crate::constants::*;
use crate::constants::Event;
use crate::constants::DC_CREATE_MVBOX;
use crate::context::Context;
use crate::dc_loginparam::*;
use crate::dc_tools::*;
use crate::e2ee;
use crate::imap::*;
use crate::job::*;
use crate::login_param::LoginParam;
use crate::oauth2::*;
use crate::param::Params;
use crate::types::*;
mod auto_outlook;
use auto_outlook::outlk_autodiscover;
@@ -18,17 +20,24 @@ use auto_mozilla::moz_autoconfigure;
macro_rules! progress {
($context:tt, $progress:expr) => {
assert!(
$progress > 0 && $progress <= 1000,
$progress >= 0 && $progress <= 1000,
"value in range 0..1000 expected with: 0=error, 1..999=progress, 1000=success"
);
$context.call_cb($crate::events::Event::ConfigureProgress($progress));
$context.call_cb(
Event::CONFIGURE_PROGRESS,
$progress as uintptr_t,
0 as uintptr_t,
);
};
}
// connect
pub unsafe fn configure(context: &Context) {
if dc_has_ongoing(context) {
warn!(context, "There is already another ongoing process running.",);
warn!(
context,
0, "There is already another ongoing process running.",
);
return;
}
job_kill_action(context, Action::ConfigureImap);
@@ -36,8 +45,17 @@ pub unsafe fn configure(context: &Context) {
}
/// Check if the context is already configured.
pub fn dc_is_configured(context: &Context) -> bool {
context.sql.get_config_bool(context, "configured")
pub fn dc_is_configured(context: &Context) -> libc::c_int {
if context
.sql
.get_config_int(context, "configured")
.unwrap_or_default()
> 0
{
1
} else {
0
}
}
/*******************************************************************************
@@ -51,11 +69,11 @@ pub unsafe fn dc_job_do_DC_JOB_CONFIGURE_IMAP(context: &Context, _job: &Job) {
let mut smtp_connected_here = false;
let mut ongoing_allocated_here = false;
let mut param_autoconfig: Option<LoginParam> = None;
let mut param_autoconfig: Option<dc_loginparam_t> = None;
if dc_alloc_ongoing(context) {
ongoing_allocated_here = true;
if !context.sql.is_open() {
error!(context, "Cannot configure, database not opened.",);
error!(context, 0, "Cannot configure, database not opened.",);
} else {
context.inbox.read().unwrap().disconnect(context);
context
@@ -71,13 +89,13 @@ pub unsafe fn dc_job_do_DC_JOB_CONFIGURE_IMAP(context: &Context, _job: &Job) {
.imap
.disconnect(context);
context.smtp.clone().lock().unwrap().disconnect();
info!(context, "Configure ...",);
info!(context, 0, "Configure ...",);
let s_a = context.running_state.clone();
let s = s_a.read().unwrap();
// Variables that are shared between steps:
let mut param = LoginParam::from_database(context, "");
let mut param: dc_loginparam_t = dc_loginparam_read(context, &context.sql, "");
// need all vars here to be mutable because rust thinks the same step could be called multiple times
// and also initialize, because otherwise rust thinks it's used while unitilized, even if thats not the case as the loop goes only forward
let mut param_domain = "undefined.undefined".to_owned();
@@ -95,7 +113,7 @@ pub unsafe fn dc_job_do_DC_JOB_CONFIGURE_IMAP(context: &Context, _job: &Job) {
1 => {
progress!(context, 1);
if param.addr.is_empty() {
error!(context, "Please enter an email address.",);
error!(context, 0, "Please enter an email address.",);
}
!param.addr.is_empty()
}
@@ -128,7 +146,7 @@ pub unsafe fn dc_job_do_DC_JOB_CONFIGURE_IMAP(context: &Context, _job: &Job) {
utf8_percent_encode(&param.addr, NON_ALPHANUMERIC).to_string();
true
} else {
error!(context, "Bad email-address.");
error!(context, 0, "Bad email-address.");
false
}
}
@@ -238,7 +256,8 @@ pub unsafe fn dc_job_do_DC_JOB_CONFIGURE_IMAP(context: &Context, _job: &Job) {
12 => {
progress!(context, 500);
if let Some(ref cfg) = param_autoconfig {
info!(context, "Got autoconfig: {}", &cfg);
let r = dc_loginparam_get_readable(cfg);
info!(context, 0, "Got autoconfig: {}", r);
if !cfg.mail_user.is_empty() {
param.mail_user = cfg.mail_user.clone();
}
@@ -277,14 +296,13 @@ pub unsafe fn dc_job_do_DC_JOB_CONFIGURE_IMAP(context: &Context, _job: &Job) {
}
}
if param.send_port == 0 {
param.send_port =
if 0 != param.server_flags & DC_LP_SMTP_SOCKET_STARTTLS as i32 {
587
} else if 0 != param.server_flags & DC_LP_SMTP_SOCKET_PLAIN as i32 {
25
} else {
465
}
param.send_port = if 0 != param.server_flags & 0x10000 {
587
} else if 0 != param.server_flags & 0x40000 {
25
} else {
465
}
}
if param.send_user.is_empty() && !param.mail_user.is_empty() {
param.send_user = param.mail_user.clone();
@@ -292,30 +310,24 @@ pub unsafe fn dc_job_do_DC_JOB_CONFIGURE_IMAP(context: &Context, _job: &Job) {
if param.send_pw.is_empty() && !param.mail_pw.is_empty() {
param.send_pw = param.mail_pw.clone()
}
if !dc_exactly_one_bit_set(param.server_flags & DC_LP_AUTH_FLAGS as i32) {
param.server_flags &= !(DC_LP_AUTH_FLAGS as i32);
param.server_flags |= DC_LP_AUTH_NORMAL as i32
if !dc_exactly_one_bit_set(param.server_flags & (0x2 | 0x4)) {
param.server_flags &= !(0x2 | 0x4);
param.server_flags |= 0x4
}
if !dc_exactly_one_bit_set(param.server_flags & (0x100 | 0x200 | 0x400)) {
param.server_flags &= !(0x100 | 0x200 | 0x400);
param.server_flags |= if param.send_port == 143 { 0x100 } else { 0x200 }
}
if !dc_exactly_one_bit_set(
param.server_flags & DC_LP_IMAP_SOCKET_FLAGS as i32,
param.server_flags & (0x10000 | 0x20000 | 0x40000),
) {
param.server_flags &= !(DC_LP_IMAP_SOCKET_FLAGS as i32);
param.server_flags |= if param.send_port == 143 {
DC_LP_IMAP_SOCKET_STARTTLS as i32
} else {
DC_LP_IMAP_SOCKET_SSL as i32
}
}
if !dc_exactly_one_bit_set(
param.server_flags & (DC_LP_SMTP_SOCKET_FLAGS as i32),
) {
param.server_flags &= !(DC_LP_SMTP_SOCKET_FLAGS as i32);
param.server_flags &= !(0x10000 | 0x20000 | 0x40000);
param.server_flags |= if param.send_port == 587 {
DC_LP_SMTP_SOCKET_STARTTLS as i32
0x10000
} else if param.send_port == 25 {
DC_LP_SMTP_SOCKET_PLAIN as i32
0x40000
} else {
DC_LP_SMTP_SOCKET_SSL as i32
0x20000
}
}
/* do we have a complete configuration? */
@@ -329,7 +341,7 @@ pub unsafe fn dc_job_do_DC_JOB_CONFIGURE_IMAP(context: &Context, _job: &Job) {
|| param.send_pw.is_empty()
|| param.server_flags == 0
{
error!(context, "Account settings incomplete.");
error!(context, 0, "Account settings incomplete.");
false
} else {
true
@@ -346,7 +358,8 @@ pub unsafe fn dc_job_do_DC_JOB_CONFIGURE_IMAP(context: &Context, _job: &Job) {
ok_to_continue8 = true;
break;
}
info!(context, "Trying: {}", &param);
let r_0 = dc_loginparam_get_readable(&param);
info!(context, 0, "Trying: {}", r_0,);
if context.inbox.read().unwrap().connect(context, &param) {
ok_to_continue8 = true;
@@ -364,7 +377,8 @@ pub unsafe fn dc_job_do_DC_JOB_CONFIGURE_IMAP(context: &Context, _job: &Job) {
progress!(context, 650 + username_variation * 30);
param.server_flags &= !(0x100 | 0x200 | 0x400);
param.server_flags |= 0x100;
info!(context, "Trying: {}", &param);
let r_1 = dc_loginparam_get_readable(&param);
info!(context, 0, "Trying: {}", r_1,);
if context.inbox.read().unwrap().connect(context, &param) {
ok_to_continue8 = true;
@@ -377,7 +391,8 @@ pub unsafe fn dc_job_do_DC_JOB_CONFIGURE_IMAP(context: &Context, _job: &Job) {
}
progress!(context, 660 + username_variation * 30);
param.mail_port = 143;
info!(context, "Trying: {}", &param);
let r_2 = dc_loginparam_get_readable(&param);
info!(context, 0, "Trying: {}", r_2,);
if context.inbox.read().unwrap().connect(context, &param) {
ok_to_continue8 = true;
@@ -429,10 +444,11 @@ pub unsafe fn dc_job_do_DC_JOB_CONFIGURE_IMAP(context: &Context, _job: &Job) {
success = false;
} else {
progress!(context, 850);
param.server_flags &= !(DC_LP_SMTP_SOCKET_FLAGS as i32);
param.server_flags |= DC_LP_SMTP_SOCKET_STARTTLS as i32;
param.server_flags &= !(0x10000 | 0x20000 | 0x40000);
param.server_flags |= 0x10000;
param.send_port = 587;
info!(context, "Trying: {}", &param);
let r_3 = dc_loginparam_get_readable(&param);
info!(context, 0, "Trying: {}", r_3,);
if !context
.smtp
@@ -445,10 +461,11 @@ pub unsafe fn dc_job_do_DC_JOB_CONFIGURE_IMAP(context: &Context, _job: &Job) {
success = false;
} else {
progress!(context, 860);
param.server_flags &= !(DC_LP_SMTP_SOCKET_FLAGS as i32);
param.server_flags |= DC_LP_SMTP_SOCKET_STARTTLS as i32;
param.server_flags &= !(0x10000 | 0x20000 | 0x40000);
param.server_flags |= 0x10000;
param.send_port = 25;
info!(context, "Trying: {}", &param);
let r_4 = dc_loginparam_get_readable(&param);
info!(context, 0, "Trying: {}", r_4);
if !context
.smtp
@@ -500,14 +517,13 @@ pub unsafe fn dc_job_do_DC_JOB_CONFIGURE_IMAP(context: &Context, _job: &Job) {
17 => {
progress!(context, 910);
/* configuration success - write back the configured parameters with the "configured_" prefix; also write the "configured"-flag */
param
.save_to_database(
context,
"configured_", /*the trailing underscore is correct*/
)
.ok();
context.sql.set_config_bool(context, "configured", true);
dc_loginparam_write(
context,
&param,
&context.sql,
"configured_", /*the trailing underscore is correct*/
);
context.sql.set_config_int(context, "configured", 1).ok();
true
}
18 => {
@@ -517,13 +533,13 @@ pub unsafe fn dc_job_do_DC_JOB_CONFIGURE_IMAP(context: &Context, _job: &Job) {
// (~30 seconds on a Moto G4 play) and might looks as if message sending is always that slow.
e2ee::ensure_secret_key_exists(context);
success = true;
info!(context, "Configure completed.");
info!(context, 0, "Configure completed.");
progress!(context, 940);
break; // We are done here
}
_ => {
error!(context, "Internal error: step counter out of bound",);
error!(context, 0, "Internal error: step counter out of bound",);
break;
}
};
@@ -562,7 +578,7 @@ pub unsafe fn dc_job_do_DC_JOB_CONFIGURE_IMAP(context: &Context, _job: &Job) {
dc_free_ongoing(context);
}
progress!(context, if success { 1000 } else { 0 });
progress!(context, (if success { 1000 } else { 0 }));
}
/*******************************************************************************
@@ -571,7 +587,10 @@ pub unsafe fn dc_job_do_DC_JOB_CONFIGURE_IMAP(context: &Context, _job: &Job) {
pub fn dc_alloc_ongoing(context: &Context) -> bool {
if dc_has_ongoing(context) {
warn!(context, "There is already another ongoing process running.",);
warn!(
context,
0, "There is already another ongoing process running.",
);
false
} else {
@@ -614,9 +633,9 @@ pub fn dc_connect_to_configured_imap(context: &Context, imap: &Imap) -> libc::c_
.unwrap_or_default()
== 0
{
warn!(context, "Not configured, cannot connect.",);
warn!(context, 0, "Not configured, cannot connect.",);
} else {
let param = LoginParam::from_database(context, "configured_");
let param = dc_loginparam_read(context, &context.sql, "configured_");
// the trailing underscore is correct
if imap.connect(context, &param) {
@@ -637,24 +656,24 @@ pub fn dc_stop_ongoing_process(context: &Context) {
let mut s = s_a.write().unwrap();
if s.ongoing_running && !s.shall_stop_ongoing {
info!(context, "Signaling the ongoing process to stop ASAP.",);
info!(context, 0, "Signaling the ongoing process to stop ASAP.",);
s.shall_stop_ongoing = true;
} else {
info!(context, "No ongoing process to stop.",);
info!(context, 0, "No ongoing process to stop.",);
};
}
pub fn read_autoconf_file(context: &Context, url: &str) -> *mut libc::c_char {
info!(context, "Testing {} ...", url);
pub fn read_autoconf_file(context: &Context, url: *const libc::c_char) -> *mut libc::c_char {
info!(context, 0, "Testing {} ...", to_string(url));
match reqwest::Client::new()
.get(url)
.get(as_str(url))
.send()
.and_then(|mut res| res.text())
{
Ok(res) => unsafe { res.strdup() },
Err(_err) => {
info!(context, "Can\'t read file.",);
info!(context, 0, "Can\'t read file.",);
std::ptr::null_mut()
}

View File

@@ -1,9 +1,10 @@
//! Constants
#![allow(non_camel_case_types, dead_code)]
use deltachat_derive::*;
use lazy_static::lazy_static;
use deltachat_derive::*;
lazy_static! {
pub static ref DC_VERSION_STR: String = env!("CARGO_PKG_VERSION").to_string();
}
@@ -17,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;
@@ -76,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
@@ -139,7 +134,7 @@ pub const DC_LP_AUTH_OAUTH2: usize = 0x2;
/// Force NORMAL authorization, this is the default.
/// If this flag is set, automatic configuration is skipped.
pub const DC_LP_AUTH_NORMAL: usize = 0x4;
const DC_LP_AUTH_NORMAL: usize = 0x4;
/// Connect to IMAP via STARTTLS.
/// If this flag is set, automatic configuration is skipped.
@@ -147,7 +142,7 @@ pub const DC_LP_IMAP_SOCKET_STARTTLS: usize = 0x100;
/// Connect to IMAP via SSL.
/// If this flag is set, automatic configuration is skipped.
pub const DC_LP_IMAP_SOCKET_SSL: usize = 0x200;
const DC_LP_IMAP_SOCKET_SSL: usize = 0x200;
/// Connect to IMAP unencrypted, this should not be used.
/// If this flag is set, automatic configuration is skipped.
@@ -159,19 +154,19 @@ pub const DC_LP_SMTP_SOCKET_STARTTLS: usize = 0x10000;
/// Connect to SMTP via SSL.
/// If this flag is set, automatic configuration is skipped.
pub const DC_LP_SMTP_SOCKET_SSL: usize = 0x20000;
const DC_LP_SMTP_SOCKET_SSL: usize = 0x20000;
/// Connect to SMTP unencrypted, this should not be used.
/// If this flag is set, automatic configuration is skipped.
pub const DC_LP_SMTP_SOCKET_PLAIN: usize = 0x40000;
/// if none of these flags are set, the default is chosen
pub const DC_LP_AUTH_FLAGS: usize = (DC_LP_AUTH_OAUTH2 | DC_LP_AUTH_NORMAL);
const DC_LP_AUTH_FLAGS: usize = (DC_LP_AUTH_OAUTH2 | DC_LP_AUTH_NORMAL);
/// if none of these flags are set, the default is chosen
pub const DC_LP_IMAP_SOCKET_FLAGS: usize =
const DC_LP_IMAP_SOCKET_FLAGS: usize =
(DC_LP_IMAP_SOCKET_STARTTLS | DC_LP_IMAP_SOCKET_SSL | DC_LP_IMAP_SOCKET_PLAIN);
/// if none of these flags are set, the default is chosen
pub const DC_LP_SMTP_SOCKET_FLAGS: usize =
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)
@@ -225,12 +220,6 @@ pub enum Viewtype {
File = 60,
}
impl Default for Viewtype {
fn default() -> Self {
Viewtype::Unknown
}
}
#[cfg(test)]
mod tests {
use super::*;
@@ -246,6 +235,242 @@ mod tests {
// If you do not want to handle an event, it is always safe to return 0,
// so there is no need to add a "case" for every event.
#[derive(Debug, Clone, Copy, PartialEq, Eq, FromPrimitive, ToPrimitive)]
#[repr(u32)]
pub enum Event {
/// The library-user may write an informational string to the log.
/// Passed to the callback given to dc_context_new().
/// This event should not be reported to the end-user using a popup or something like that.
/// @param data1 0
/// @param data2 (const char*) Info string in english language.
/// Must not be free()'d or modified and is valid only until the callback returns.
/// @return 0
INFO = 100,
/// Emitted when SMTP connection is established and login was successful.
///
/// @param data1 0
/// @param data2 (const char*) Info string in english language.
/// Must not be free()'d or modified and is valid only until the callback returns.
/// @return 0
SMTP_CONNECTED = 101,
/// Emitted when IMAP connection is established and login was successful.
///
/// @param data1 0
/// @param data2 (const char*) Info string in english language.
/// Must not be free()'d or modified and is valid only until the callback returns.
/// @return 0
IMAP_CONNECTED = 102,
/// Emitted when a message was successfully sent to the SMTP server.
///
/// @param data1 0
/// @param data2 (const char*) Info string in english language.
/// Must not be free()'d or modified and is valid only until the callback returns.
/// @return 0
SMTP_MESSAGE_SENT = 103,
/// The library-user should write a warning string to the log.
/// Passed to the callback given to dc_context_new().
///
/// This event should not be reported to the end-user using a popup or something like that.
///
/// @param data1 0
/// @param data2 (const char*) Warning string in english language.
/// Must not be free()'d or modified and is valid only until the callback returns.
/// @return 0
WARNING = 300,
/// The library-user should report an error to the end-user.
/// Passed to the callback given to dc_context_new().
///
/// As most things are asynchronous, things may go wrong at any time and the user
/// should not be disturbed by a dialog or so. Instead, use a bubble or so.
///
/// However, for ongoing processes (eg. configure())
/// or for functions that are expected to fail (eg. dc_continue_key_transfer())
/// it might be better to delay showing these events until the function has really
/// failed (returned false). It should be sufficient to report only the _last_ error
/// in a messasge box then.
///
/// @param data1 0
/// @param data2 (const char*) Error string, always set, never NULL. Frequent error strings are
/// localized using #DC_EVENT_GET_STRING, however, most error strings will be in english language.
/// Must not be free()'d or modified and is valid only until the callback returns.
/// @return 0
ERROR = 400,
/// An action cannot be performed because there is no network available.
///
/// The library will typically try over after a some time
/// and when dc_maybe_network() is called.
///
/// Network errors should be reported to users in a non-disturbing way,
/// however, as network errors may come in a sequence,
/// it is not useful to raise each an every error to the user.
/// For this purpose, data1 is set to 1 if the error is probably worth reporting.
///
/// Moreover, if the UI detects that the device is offline,
/// it is probably more useful to report this to the user
/// instead of the string from data2.
///
/// @param data1 (int) 1=first/new network error, should be reported the user;
/// 0=subsequent network error, should be logged only
/// @param data2 (const char*) Error string, always set, never NULL.
/// Must not be free()'d or modified and is valid only until the callback returns.
/// @return 0
ERROR_NETWORK = 401,
/// An action cannot be performed because the user is not in the group.
/// Reported eg. after a call to
/// dc_set_chat_name(), dc_set_chat_profile_image(),
/// dc_add_contact_to_chat(), dc_remove_contact_from_chat(),
/// dc_send_text_msg() or another sending function.
///
/// @param data1 0
/// @param data2 (const char*) Info string in english language.
/// Must not be free()'d or modified
/// and is valid only until the callback returns.
/// @return 0
ERROR_SELF_NOT_IN_GROUP = 410,
/// Messages or chats changed. One or more messages or chats changed for various
/// reasons in the database:
/// - Messages sent, received or removed
/// - Chats created, deleted or archived
/// - A draft has been set
///
/// @param data1 (int) chat_id for single added messages
/// @param data2 (int) msg_id for single added messages
/// @return 0
MSGS_CHANGED = 2000,
/// There is a fresh message. Typically, the user will show an notification
/// when receiving this message.
///
/// There is no extra #DC_EVENT_MSGS_CHANGED event send together with this event.
///
/// @param data1 (int) chat_id
/// @param data2 (int) msg_id
/// @return 0
INCOMING_MSG = 2005,
/// A single message is sent successfully. State changed from DC_STATE_OUT_PENDING to
/// DC_STATE_OUT_DELIVERED, see dc_msg_get_state().
///
/// @param data1 (int) chat_id
/// @param data2 (int) msg_id
/// @return 0
MSG_DELIVERED = 2010,
/// A single message could not be sent. State changed from DC_STATE_OUT_PENDING or DC_STATE_OUT_DELIVERED to
/// DC_STATE_OUT_FAILED, see dc_msg_get_state().
///
/// @param data1 (int) chat_id
/// @param data2 (int) msg_id
/// @return 0
MSG_FAILED = 2012,
/// A single message is read by the receiver. State changed from DC_STATE_OUT_DELIVERED to
/// DC_STATE_OUT_MDN_RCVD, see dc_msg_get_state().
///
/// @param data1 (int) chat_id
/// @param data2 (int) msg_id
/// @return 0
MSG_READ = 2015,
/// Chat changed. The name or the image of a chat group was changed or members were added or removed.
/// Or the verify state of a chat has changed.
/// See dc_set_chat_name(), dc_set_chat_profile_image(), dc_add_contact_to_chat()
/// and dc_remove_contact_from_chat().
///
/// @param data1 (int) chat_id
/// @param data2 0
/// @return 0
CHAT_MODIFIED = 2020,
/// Contact(s) created, renamed, blocked or deleted.
///
/// @param data1 (int) If not 0, this is the contact_id of an added contact that should be selected.
/// @param data2 0
/// @return 0
CONTACTS_CHANGED = 2030,
/// Location of one or more contact has changed.
///
/// @param data1 (int) contact_id of the contact for which the location has changed.
/// If the locations of several contacts have been changed,
/// eg. after calling dc_delete_all_locations(), this parameter is set to 0.
/// @param data2 0
/// @return 0
LOCATION_CHANGED = 2035,
/// Inform about the configuration progress started by configure().
///
/// @param data1 (int) 0=error, 1-999=progress in permille, 1000=success and done
/// @param data2 0
/// @return 0
CONFIGURE_PROGRESS = 2041,
/// Inform about the import/export progress started by dc_imex().
///
/// @param data1 (int) 0=error, 1-999=progress in permille, 1000=success and done
/// @param data2 0
/// @return 0
IMEX_PROGRESS = 2051,
/// A file has been exported. A file has been written by dc_imex().
/// This event may be sent multiple times by a single call to dc_imex().
///
/// A typical purpose for a handler of this event may be to make the file public to some system
/// services.
///
/// @param data1 (const char*) Path and file name.
/// Must not be free()'d or modified and is valid only until the callback returns.
/// @param data2 0
/// @return 0
IMEX_FILE_WRITTEN = 2052,
/// Progress information of a secure-join handshake from the view of the inviter
/// (Alice, the person who shows the QR code).
///
/// These events are typically sent after a joiner has scanned the QR code
/// generated by dc_get_securejoin_qr().
///
/// @param data1 (int) ID of the contact that wants to join.
/// @param data2 (int) Progress as:
/// 300=vg-/vc-request received, typically shown as "bob@addr joins".
/// 600=vg-/vc-request-with-auth received, vg-member-added/vc-contact-confirm sent, typically shown as "bob@addr verified".
/// 800=vg-member-added-received received, shown as "bob@addr securely joined GROUP", only sent for the verified-group-protocol.
/// 1000=Protocol finished for this contact.
/// @return 0
SECUREJOIN_INVITER_PROGRESS = 2060,
/// Progress information of a secure-join handshake from the view of the joiner
/// (Bob, the person who scans the QR code).
/// The events are typically sent while dc_join_securejoin(), which
/// may take some time, is executed.
/// @param data1 (int) ID of the inviting contact.
/// @param data2 (int) Progress as:
/// 400=vg-/vc-request-with-auth sent, typically shown as "alice@addr verified, introducing myself."
/// (Bob has verified alice and waits until Alice does the same for him)
/// @return 0
SECUREJOIN_JOINER_PROGRESS = 2061,
// the following events are functions that should be provided by the frontends
/// Requeste a localized string from the frontend.
/// @param data1 (int) ID of the string to request, one of the DC_STR_/// constants.
/// @param data2 (int) The count. If the requested string contains a placeholder for a numeric value,
/// the ui may use this value to return different strings on different plural forms.
/// @return (const char*) Null-terminated UTF-8 string.
/// The string will be free()'d by the core,
/// so it must be allocated using malloc() or a compatible function.
/// Return 0 if the ui cannot provide the requested string
/// the core will use a default string in english language then.
GET_STRING = 2091,
}
const DC_EVENT_FILE_COPIED: usize = 2055; // deprecated;
const DC_EVENT_IS_OFFLINE: usize = 2081; // deprecated;
const DC_ERROR_SEE_STRING: usize = 0; // deprecated;
@@ -314,3 +539,12 @@ pub enum KeyType {
Public = 0,
Private = 1,
}
pub const DC_CMD_GROUPNAME_CHANGED: libc::c_int = 2;
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;
const DC_CMD_SECUREJOIN_MESSAGE: libc::c_int = 7;
pub const DC_CMD_LOCATION_STREAMING_ENABLED: libc::c_int = 8;
const DC_CMD_LOCATION_ONLY: libc::c_int = 9;

View File

@@ -1,23 +1,22 @@
use std::path::PathBuf;
use deltachat_derive::*;
use itertools::Itertools;
use num_traits::{FromPrimitive, ToPrimitive};
use rusqlite;
use rusqlite::types::*;
use crate::aheader::EncryptPreference;
use crate::config::Config;
use crate::constants::*;
use crate::context::Context;
use crate::dc_loginparam::*;
use crate::dc_tools::*;
use crate::e2ee;
use crate::error::Result;
use crate::events::Event;
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::*;
/// Contacts with at least this origin value are shown in the contact list.
const DC_ORIGIN_MIN_CONTACT_LIST: i32 = 0x100;
@@ -32,8 +31,8 @@ const DC_ORIGIN_MIN_CONTACT_LIST: i32 = 0x100;
/// For this purpose, internally, two names are tracked -
/// authorized-name and given-name.
/// By default, these names are equal, but functions working with contact names
#[derive(Debug)]
pub struct Contact {
pub struct Contact<'a> {
context: &'a Context,
/// The contact ID.
///
/// Special message IDs:
@@ -59,9 +58,7 @@ pub struct Contact {
}
/// 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,
@@ -99,9 +96,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)
}
}
@@ -140,10 +148,11 @@ pub enum VerifiedStatus {
BidirectVerified = 2,
}
impl Contact {
pub fn load_from_db(context: &Context, contact_id: u32) -> Result<Self> {
impl<'a> Contact<'a> {
pub fn load_from_db(context: &'a Context, contact_id: u32) -> Result<Self> {
if contact_id == DC_CONTACT_ID_SELF {
let contact = Contact {
context,
id: contact_id,
name: context.stock_str(StockMessage::SelfMsg).into(),
authname: "".into(),
@@ -162,6 +171,7 @@ impl Contact {
params![contact_id as i32],
|row| {
let contact = Self {
context,
id: contact_id,
name: row.get::<_, String>(0)?,
authname: row.get::<_, String>(4)?,
@@ -180,7 +190,7 @@ impl Contact {
}
/// Check if a contact is blocked.
pub fn is_blocked_load(context: &Context, id: u32) -> bool {
pub fn is_blocked_load(context: &'a Context, id: u32) -> bool {
Self::load_from_db(context, id)
.map(|contact| contact.blocked)
.unwrap_or_default()
@@ -214,13 +224,15 @@ impl Contact {
let (contact_id, sth_modified) =
Contact::add_or_lookup(context, name, addr, Origin::ManuallyCreated)?;
let blocked = Contact::is_blocked_load(context, contact_id);
context.call_cb(Event::ContactsChanged(
if sth_modified == Modifier::Created {
Some(contact_id)
context.call_cb(
Event::CONTACTS_CHANGED,
(if sth_modified == Modifier::Created {
contact_id
} else {
None
},
));
0
}) as uintptr_t,
0 as uintptr_t,
);
if blocked {
Contact::unblock(context, contact_id);
}
@@ -241,10 +253,7 @@ impl Contact {
)
.is_ok()
{
context.call_cb(Event::MsgsChanged {
chat_id: 0,
msg_id: 0,
});
context.call_cb(Event::MSGS_CHANGED, 0, 0);
}
}
@@ -267,7 +276,7 @@ impl Contact {
return 1;
}
context.sql.query_get_value(
context.sql.query_row_col(
context,
"SELECT id FROM contacts WHERE addr=?1 COLLATE NOCASE AND id>?2 AND origin>=?3 AND blocked=0;",
params![
@@ -275,6 +284,7 @@ impl Contact {
DC_CONTACT_ID_LAST_SPECIAL as i32,
DC_ORIGIN_MIN_CONTACT_LIST,
],
0
).unwrap_or_default()
}
@@ -307,6 +317,7 @@ impl Contact {
if !may_be_valid_addr(&addr) {
warn!(
context,
0,
"Bad address \"{}\" for contact \"{}\".",
addr,
if !name.as_ref().is_empty() {
@@ -330,22 +341,19 @@ impl Contact {
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))
},
) {
@@ -385,7 +393,7 @@ impl Contact {
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;
@@ -402,7 +410,7 @@ impl Contact {
row_id = sql::get_rowid(context, &context.sql, "contacts", "addr", addr);
sth_modified = Modifier::Created;
} else {
error!(context, "Cannot add contact.");
error!(context, 0, "Cannot add contact.");
}
}
@@ -423,13 +431,19 @@ impl Contact {
/// 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 {
@@ -437,7 +451,7 @@ impl Contact {
}
}
if modify_cnt > 0 {
context.call_cb(Event::ContactsChanged(None));
context.call_cb(Event::CONTACTS_CHANGED, 0 as uintptr_t, 0 as uintptr_t);
}
Ok(modify_cnt)
@@ -540,10 +554,11 @@ impl Contact {
pub fn get_blocked_cnt(context: &Context) -> usize {
context
.sql
.query_get_value::<_, isize>(
.query_row_col::<_, isize>(
context,
"SELECT COUNT(*) FROM contacts WHERE id>? AND blocked!=0",
params![DC_CONTACT_ID_LAST_SPECIAL as i32],
0,
)
.unwrap_or_default() as usize
}
@@ -574,7 +589,7 @@ impl Contact {
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);
@@ -646,20 +661,22 @@ impl Contact {
let count_contacts: i32 = context
.sql
.query_get_value(
.query_row_col(
context,
"SELECT COUNT(*) FROM chats_contacts WHERE contact_id=?;",
params![contact_id as i32],
0,
)
.unwrap_or_default();
let count_msgs: i32 = if count_contacts > 0 {
context
.sql
.query_get_value(
.query_row_col(
context,
"SELECT COUNT(*) FROM msgs WHERE from_id=? OR to_id=?;",
params![contact_id as i32, contact_id as i32],
0,
)
.unwrap_or_default()
} else {
@@ -674,11 +691,11 @@ impl Contact {
params![contact_id as i32],
) {
Ok(_) => {
context.call_cb(Event::ContactsChanged(None));
context.call_cb(Event::CONTACTS_CHANGED, 0, 0);
return Ok(());
}
Err(err) => {
error!(context, "delete_contact {} failed ({})", contact_id, err);
error!(context, 0, "delete_contact {} failed ({})", contact_id, err);
return Err(err);
}
}
@@ -686,7 +703,7 @@ impl Contact {
info!(
context,
"could not delete contact {}, there are {} messages with it", contact_id, count_msgs
0, "could not delete contact {}, there are {} messages with it", contact_id, count_msgs
);
bail!("Could not delete contact with messages in it");
}
@@ -762,11 +779,9 @@ impl Contact {
/// 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, context: &Context) -> Option<PathBuf> {
pub fn get_profile_image(&self) -> Option<String> {
if self.id == DC_CONTACT_ID_SELF {
if let Some(p) = 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
@@ -785,18 +800,14 @@ impl Contact {
///
/// The UI may draw a checkbox or something like that beside verified contacts.
///
pub fn is_verified(&self, context: &Context) -> VerifiedStatus {
self.is_verified_ex(context, None)
pub fn is_verified(&self) -> VerifiedStatus {
self.is_verified_ex(None)
}
/// Same as `Contact::is_verified` but allows speeding up things
/// by adding the peerstate belonging to the contact.
/// If you do not have the peerstate available, it is loaded automatically.
pub fn is_verified_ex(
&self,
context: &Context,
peerstate: Option<&Peerstate>,
) -> VerifiedStatus {
pub fn is_verified_ex(&self, peerstate: Option<&Peerstate<'a>>) -> VerifiedStatus {
// We're always sort of secured-verified as we could verify the key on this device any time with the key
// on this device
if self.id == DC_CONTACT_ID_SELF {
@@ -809,7 +820,7 @@ impl Contact {
}
}
let peerstate = Peerstate::from_addr(context, &context.sql, &self.addr);
let peerstate = Peerstate::from_addr(self.context, &self.context.sql, &self.addr);
if let Some(ps) = peerstate {
if ps.verified_key().is_some() {
return VerifiedStatus::BidirectVerified;
@@ -843,10 +854,11 @@ impl Contact {
context
.sql
.query_get_value::<_, isize>(
.query_row_col::<_, isize>(
context,
"SELECT COUNT(*) FROM contacts WHERE id>?;",
params![DC_CONTACT_ID_LAST_SPECIAL as i32],
0,
)
.unwrap_or_default() as usize
}
@@ -939,7 +951,11 @@ fn set_block_contact(context: &Context, contact_id: u32, new_blocking: bool) {
params![new_blocking, 100, contact_id as i32],
).is_ok() {
Contact::mark_noticed(context, contact_id);
context.call_cb(Event::ContactsChanged(None));
context.call_cb(
Event::CONTACTS_CHANGED,
0,
0,
);
}
}
}
@@ -1027,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::*;
@@ -1077,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")]
)
}
}

File diff suppressed because it is too large Load Diff

View File

@@ -1,7 +1,8 @@
use crate::location::Location;
use crate::types::*;
/* * the structure behind dc_array_t */
#[derive(Debug, Clone)]
#[derive(Clone)]
#[allow(non_camel_case_types)]
pub enum dc_array_t {
Locations(Vec<Location>),
@@ -18,7 +19,7 @@ impl dc_array_t {
dc_array_t::Locations(Vec::with_capacity(capacity))
}
pub fn add_id(&mut self, item: u32) {
pub fn add_id(&mut self, item: uint32_t) {
if let Self::Uint(array) = self {
array.push(item);
} else {
@@ -34,10 +35,10 @@ impl dc_array_t {
}
}
pub fn get_id(&self, index: usize) -> u32 {
pub fn get_id(&self, index: usize) -> uint32_t {
match self {
Self::Locations(array) => array[index].location_id,
Self::Uint(array) => array[index] as u32,
Self::Uint(array) => array[index] as uint32_t,
}
}

View File

@@ -1,5 +1,4 @@
use std::ffi::CString;
use std::path::Path;
use std::ptr;
use mmime::mailmime_content::*;
@@ -15,7 +14,6 @@ use crate::context::Context;
use crate::dc_tools::*;
use crate::e2ee;
use crate::error::*;
use crate::events::Event;
use crate::job::*;
use crate::key::*;
use crate::message::*;
@@ -23,6 +21,7 @@ use crate::param::*;
use crate::pgp::*;
use crate::sql::{self, Sql};
use crate::stock::StockMessage;
use crate::types::*;
use crate::x::*;
// import/export and tools
@@ -30,16 +29,16 @@ use crate::x::*;
// param1 is a directory where the keys are searched in and read from
// param1 is a directory where the backup is written to
// param1 is the file with the backup to import
pub fn dc_imex(
pub unsafe fn dc_imex(
context: &Context,
what: libc::c_int,
param1: Option<impl AsRef<Path>>,
param1: *const libc::c_char,
param2: *const libc::c_char,
) {
let mut param = Params::new();
param.set_int(Param::Cmd, what as i32);
if let Some(param1) = param1 {
param.set(Param::Arg, param1.as_ref().to_string_lossy());
if !param1.is_null() {
param.set(Param::Arg, as_str(param1));
}
if !param2.is_null() {
param.set(Param::Arg2, as_str(param2));
@@ -52,13 +51,14 @@ pub fn dc_imex(
/// Returns the filename of the backup if found, nullptr otherwise.
pub unsafe fn dc_imex_has_backup(
context: &Context,
dir_name: impl AsRef<Path>,
dir_name: *const libc::c_char,
) -> *mut libc::c_char {
let dir_name = dir_name.as_ref();
let dir_name = as_path(dir_name);
let dir_iter = std::fs::read_dir(dir_name);
if dir_iter.is_err() {
info!(
context,
0,
"Backup check: Cannot open directory \"{}\".\x00",
dir_name.display(),
);
@@ -92,7 +92,7 @@ pub unsafe fn dc_imex_has_backup(
Some(path) => match path.to_c_string() {
Ok(cstr) => dc_strdup(cstr.as_ptr()),
Err(err) => {
error!(context, "Invalid backup filename: {}", err);
error!(context, 0, "Invalid backup filename: {}", err);
std::ptr::null_mut()
}
},
@@ -139,7 +139,7 @@ pub unsafe fn dc_initiate_key_transfer(context: &Context) -> *mut libc::c_char {
))
{
if let Ok(chat_id) = chat::create_by_contact_id(context, 1) {
msg = dc_msg_new_untyped();
msg = dc_msg_new_untyped(context);
msg.type_0 = Viewtype::File;
msg.param.set(Param::File, as_str(setup_file_name));
@@ -156,7 +156,7 @@ pub unsafe fn dc_initiate_key_transfer(context: &Context) -> *mut libc::c_char {
.shall_stop_ongoing
{
if let Ok(msg_id) = chat::send_msg(context, chat_id, &mut msg) {
info!(context, "Wait for setup message being sent ...",);
info!(context, 0, "Wait for setup message being sent ...",);
loop {
if context
.running_state
@@ -169,8 +169,8 @@ pub unsafe fn dc_initiate_key_transfer(context: &Context) -> *mut libc::c_char {
}
std::thread::sleep(std::time::Duration::from_secs(1));
if let Ok(msg) = dc_get_msg(context, msg_id) {
if dc_msg_is_sent(&msg) {
info!(context, "... setup message sent.",);
if 0 != dc_msg_is_sent(&msg) {
info!(context, 0, "... setup message sent.",);
break;
}
}
@@ -208,8 +208,16 @@ pub fn dc_render_setup_file(context: &Context, passphrase: &str) -> Result<Strin
_ => Some(("Autocrypt-Prefer-Encrypt", "mutual")),
};
let private_key_asc = private_key.to_asc(ac_headers);
let encr = dc_pgp_symm_encrypt(&passphrase, private_key_asc.as_bytes())
.ok_or(format_err!("Failed to encrypt private key."))?;
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_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!(
"-----BEGIN PGP MESSAGE-----\r\n",
@@ -242,7 +250,7 @@ pub fn dc_render_setup_file(context: &Context, passphrase: &str) -> Result<Strin
}
pub fn dc_create_setup_code(_context: &Context) -> String {
let mut random_val: u16;
let mut random_val: uint16_t;
let mut rng = thread_rng();
let mut ret = String::new();
@@ -253,7 +261,7 @@ pub fn dc_create_setup_code(_context: &Context) -> String {
break;
}
}
random_val = (random_val as libc::c_int % 10000) as u16;
random_val = (random_val as libc::c_int % 10000) as uint16_t;
ret += &format!(
"{}{:04}",
if 0 != i { "-" } else { "" },
@@ -264,54 +272,59 @@ pub fn dc_create_setup_code(_context: &Context) -> String {
ret
}
// TODO should return bool /rtn
pub unsafe fn dc_continue_key_transfer(
context: &Context,
msg_id: u32,
msg_id: uint32_t,
setup_code: *const libc::c_char,
) -> bool {
let mut success = false;
) -> libc::c_int {
let mut success: libc::c_int = 0i32;
let mut filename: *mut libc::c_char = ptr::null_mut();
let mut filecontent: *mut libc::c_char = ptr::null_mut();
let mut filebytes: size_t = 0i32 as size_t;
let mut armored_key: *mut libc::c_char = ptr::null_mut();
let norm_sc;
if msg_id <= 9i32 as libc::c_uint || setup_code.is_null() {
return false;
}
let msg = dc_get_msg(context, msg_id);
if msg.is_err() {
error!(context, "Message is no Autocrypt Setup Message.");
return false;
}
let msg = msg.unwrap();
if !dc_msg_is_setupmessage(&msg) {
error!(context, "Message is no Autocrypt Setup Message.");
return false;
}
if let Some(filename) = dc_msg_get_file(context, &msg) {
if let Some(buf) = dc_read_file_safe(context, filename) {
let mut norm_sc: *mut libc::c_char = ptr::null_mut();
if !(msg_id <= 9i32 as libc::c_uint || setup_code.is_null()) {
let msg = dc_get_msg(context, msg_id);
if msg.is_err()
|| !dc_msg_is_setupmessage(msg.as_ref().unwrap())
|| {
filename = dc_msg_get_file(msg.as_ref().unwrap());
filename.is_null()
}
|| *filename.offset(0isize) as libc::c_int == 0i32
{
error!(context, 0, "Message is no Autocrypt Setup Message.",);
} else if 0
== dc_read_file(
context,
filename,
&mut filecontent as *mut *mut libc::c_char as *mut *mut libc::c_void,
&mut filebytes,
)
|| filecontent.is_null()
|| filebytes <= 0
{
error!(context, 0, "Cannot read Autocrypt Setup Message file.",);
} else {
norm_sc = dc_normalize_setup_code(context, setup_code);
if norm_sc.is_null() {
warn!(context, "Cannot normalize Setup Code.",);
warn!(context, 0, "Cannot normalize Setup Code.",);
} else {
armored_key = dc_decrypt_setup_file(context, norm_sc, buf.as_ptr().cast());
armored_key = dc_decrypt_setup_file(context, norm_sc, filecontent);
if armored_key.is_null() {
warn!(context, "Cannot decrypt Autocrypt Setup Message.",);
warn!(context, 0, "Cannot decrypt Autocrypt Setup Message.",);
} else if set_self_key(context, armored_key, 1) {
/*set default*/
/* error already logged */
success = true
success = 1i32
}
}
} else {
error!(context, "Cannot read Autocrypt Setup Message file.",);
return false;
}
} else {
error!(context, "Message is no Autocrypt Setup Message.");
return false;
}
free(armored_key as *mut libc::c_void);
free(filecontent as *mut libc::c_void);
free(filename as *mut libc::c_void);
free(norm_sc as *mut libc::c_void);
success
@@ -330,7 +343,7 @@ fn set_self_key(
.and_then(|(k, h)| k.split_key().map(|pub_key| (k, pub_key, h)));
if keys.is_none() {
error!(context, "File does not contain a valid private key.",);
error!(context, 0, "File does not contain a valid private key.",);
return false;
}
@@ -360,13 +373,13 @@ fn set_self_key(
return false;
}
} else {
error!(context, "File does not contain a private key.",);
error!(context, 0, "File does not contain a private key.",);
}
let self_addr = context.get_config(Config::ConfiguredAddr);
let self_addr = context.sql.get_config(context, "configured_addr");
if self_addr.is_none() {
error!(context, "Missing self addr");
error!(context, 0, "Missing self addr");
return false;
}
@@ -378,7 +391,7 @@ fn set_self_key(
set_default,
&context.sql,
) {
error!(context, "Cannot save keypair.");
error!(context, 0, "Cannot save keypair.");
return false;
}
@@ -405,8 +418,8 @@ pub unsafe fn dc_decrypt_setup_file(
let mut fc_headerline: *const libc::c_char = ptr::null();
let mut fc_base64: *const libc::c_char = ptr::null();
let mut binary: *mut libc::c_char = ptr::null_mut();
let mut binary_bytes: libc::size_t = 0;
let mut indx: libc::size_t = 0;
let mut binary_bytes: size_t = 0i32 as size_t;
let mut indx: size_t = 0i32 as size_t;
let mut payload: *mut libc::c_char = ptr::null_mut();
fc_buf = dc_strdup(filecontent);
@@ -436,10 +449,9 @@ pub unsafe fn dc_decrypt_setup_file(
|| binary_bytes == 0)
{
/* decrypt symmetrically */
if let Some(plain) = dc_pgp_symm_decrypt(
as_str(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());
}
@@ -490,27 +502,28 @@ 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 what: libc::c_int;
if dc_alloc_ongoing(context) {
let what = job.param.get_int(Param::Cmd).unwrap_or_default();
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);
let _param2 = CString::yolo(job.param.get(Param::Arg2).unwrap_or_default());
if strlen(param1.as_ptr()) == 0 {
error!(context, "No Import/export dir/file given.",);
error!(context, 0, "No Import/export dir/file given.",);
} else {
info!(context, "Import/export process started.",);
context.call_cb(Event::ImexProgress(10));
info!(context, 0, "Import/export process started.",);
context.call_cb(Event::IMEX_PROGRESS, 10 as uintptr_t, 0 as uintptr_t);
if !context.sql.is_open() {
error!(context, "Import/export: Database not opened.",);
error!(context, 0, "Import/export: Database not opened.",);
} 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() {
error!(
context,
0,
"Import/export: Cannot create private key or private key not available.",
);
ok_to_continue = false;
@@ -521,26 +534,26 @@ pub unsafe fn dc_job_do_DC_JOB_IMEX_IMAP(context: &Context, job: &Job) {
if ok_to_continue {
match what {
1 => {
if export_self_keys(context, param1.as_ptr()) {
info!(context, "Import/export completed.",);
if 0 != export_self_keys(context, param1.as_ptr()) {
info!(context, 0, "Import/export completed.",);
success = 1
}
}
2 => {
if 0 != import_self_keys(context, param1.as_ptr()) {
info!(context, "Import/export completed.",);
info!(context, 0, "Import/export completed.",);
success = 1
}
}
11 => {
if export_backup(context, param1.as_ptr()) {
info!(context, "Import/export completed.",);
if 0 != export_backup(context, param1.as_ptr()) {
info!(context, 0, "Import/export completed.",);
success = 1
}
}
12 => {
if import_backup(context, param1.as_ptr()) {
info!(context, "Import/export completed.",);
if 0 != import_backup(context, param1.as_ptr()) {
info!(context, 0, "Import/export completed.",);
success = 1
}
}
@@ -551,52 +564,68 @@ pub unsafe fn dc_job_do_DC_JOB_IMEX_IMAP(context: &Context, job: &Job) {
}
dc_free_ongoing(context);
}
context.call_cb(Event::ImexProgress(if 0 != success { 1000 } else { 0 }));
context.call_cb(
Event::IMEX_PROGRESS,
(if 0 != success { 1000 } else { 0 }) as uintptr_t,
0 as uintptr_t,
);
}
/*******************************************************************************
* Import backup
******************************************************************************/
// TODO should return bool /rtn
#[allow(non_snake_case)]
unsafe fn import_backup(context: &Context, backup_to_import: *const libc::c_char) -> bool {
unsafe fn import_backup(context: &Context, backup_to_import: *const libc::c_char) -> libc::c_int {
info!(
context,
0,
"Import \"{}\" to \"{}\".",
as_str(backup_to_import),
context.get_dbfile().display()
context
.get_dbfile()
.as_ref()
.map_or("<<None>>", |p| p.to_str().unwrap())
);
if dc_is_configured(context) {
error!(context, "Cannot import backups to accounts in use.");
return false;
if 0 != dc_is_configured(context) {
error!(context, 0, "Cannot import backups to accounts in use.");
return 0;
}
&context.sql.close(&context);
dc_delete_file(context, context.get_dbfile());
if dc_file_exist(context, context.get_dbfile()) {
dc_delete_file(context, context.get_dbfile().unwrap());
if dc_file_exist(context, context.get_dbfile().unwrap()) {
error!(
context,
"Cannot import backups: Cannot delete the old file.",
0, "Cannot import backups: Cannot delete the old file.",
);
return false;
return 0;
}
if !dc_copy_file(context, as_path(backup_to_import), context.get_dbfile()) {
return false;
if !dc_copy_file(
context,
as_path(backup_to_import),
context.get_dbfile().unwrap(),
) {
return 0;
}
/* error already logged */
/* re-open copied database file */
if !context.sql.open(&context, &context.get_dbfile(), 0) {
return false;
if !context
.sql
.open(&context, &context.get_dbfile().unwrap(), 0)
{
return 0;
}
let total_files_cnt = context
.sql
.query_get_value::<_, isize>(context, "SELECT COUNT(*) FROM backup_blobs;", params![])
.query_row_col::<_, isize>(context, "SELECT COUNT(*) FROM backup_blobs;", params![], 0)
.unwrap_or_default() as usize;
info!(
context,
"***IMPORT-in-progress: total_files_cnt={:?}", total_files_cnt,
0, "***IMPORT-in-progress: total_files_cnt={:?}", total_files_cnt,
);
let res = context.sql.query_map(
@@ -633,19 +662,20 @@ unsafe fn import_backup(context: &Context, backup_to_import: *const libc::c_char
if permille > 990 {
permille = 990
}
context.call_cb(Event::ImexProgress(permille));
context.call_cb(Event::IMEX_PROGRESS, permille as uintptr_t, 0);
if file_blob.is_empty() {
continue;
}
let pathNfilename = context.get_blobdir().join(file_name);
let pathNfilename = format!("{}/{}", as_str(context.get_blobdir()), file_name);
if dc_write_file_safe(context, &pathNfilename, &file_blob) {
continue;
}
error!(
context,
0,
"Storage full? Cannot write file {} with {} bytes.",
pathNfilename.display(),
&pathNfilename,
file_blob.len(),
);
// otherwise the user may believe the stuff is imported correctly, but there are files missing ...
@@ -666,7 +696,7 @@ unsafe fn import_backup(context: &Context, backup_to_import: *const libc::c_char
sql::try_execute(context, &context.sql, "VACUUM;").ok();
Ok(())
})
.is_ok()
.is_ok() as libc::c_int
}
/*******************************************************************************
@@ -674,10 +704,11 @@ unsafe fn import_backup(context: &Context, backup_to_import: *const libc::c_char
******************************************************************************/
/* the FILE_PROGRESS macro calls the callback with the permille of files processed.
The macro avoids weird values of 0% or 100% while still working. */
// TODO should return bool /rtn
#[allow(non_snake_case)]
unsafe fn export_backup(context: &Context, dir: *const libc::c_char) -> bool {
unsafe fn export_backup(context: &Context, dir: *const libc::c_char) -> libc::c_int {
let mut ok_to_continue: bool;
let mut success = false;
let mut success: libc::c_int = 0;
let mut delete_dest_file: libc::c_int = 0;
// get a fine backup file name (the name includes the date so that multiple backup instances are possible)
@@ -689,7 +720,7 @@ unsafe fn export_backup(context: &Context, dir: *const libc::c_char) -> bool {
let buffer = CString::yolo(res);
let dest_pathNfilename = dc_get_fine_pathNfilename(context, dir, buffer.as_ptr());
if dest_pathNfilename.is_null() {
error!(context, "Cannot get backup file name.",);
error!(context, 0, "Cannot get backup file name.",);
return success;
}
@@ -701,12 +732,22 @@ unsafe fn export_backup(context: &Context, dir: *const libc::c_char) -> bool {
let mut closed = true;
info!(
context,
0,
"Backup \"{}\" to \"{}\".",
context.get_dbfile().display(),
context
.get_dbfile()
.as_ref()
.map_or("<<None>>", |p| p.to_str().unwrap()),
as_str(dest_pathNfilename),
);
if dc_copy_file(context, context.get_dbfile(), as_path(dest_pathNfilename)) {
context.sql.open(&context, &context.get_dbfile(), 0);
if dc_copy_file(
context,
context.get_dbfile().unwrap(),
as_path(dest_pathNfilename),
) {
context
.sql
.open(&context, &context.get_dbfile().unwrap(), 0);
closed = false;
/* add all files as blobs to the database copy (this does not require the source to be locked, neigher the destination as it is used only here) */
/*for logging only*/
@@ -731,14 +772,14 @@ unsafe fn export_backup(context: &Context, dir: *const libc::c_char) -> bool {
}
if ok_to_continue {
let mut total_files_cnt = 0;
let dir = context.get_blobdir();
if let Ok(dir_handle) = std::fs::read_dir(&dir) {
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, "EXPORT: total_files_cnt={}", total_files_cnt);
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) {
if let Ok(dir_handle) = std::fs::read_dir(dir) {
sql.prepare(
"INSERT INTO backup_blobs (file_name, file_content) VALUES (?, ?);",
move |mut stmt, _| {
@@ -769,7 +810,11 @@ unsafe fn export_backup(context: &Context, dir: *const libc::c_char) -> bool {
if permille > 990 {
permille = 990;
}
context.call_cb(Event::ImexProgress(permille));
context.call_cb(
Event::IMEX_PROGRESS,
permille as uintptr_t,
0 as uintptr_t,
);
let name_f = entry.file_name();
let name = name_f.to_string_lossy();
@@ -777,8 +822,13 @@ unsafe fn export_backup(context: &Context, dir: *const libc::c_char) -> bool {
{
continue;
} else {
info!(context, "EXPORTing filename={}", name);
let curr_pathNfilename = context.get_blobdir().join(entry.file_name());
info!(context, 0, "EXPORTing filename={}", name);
let curr_pathNfilename = format!(
"{}/{}",
as_str(context.get_blobdir()),
name
);
if let Some(buf) =
dc_read_file_safe(context, &curr_pathNfilename)
{
@@ -788,8 +838,9 @@ unsafe fn export_backup(context: &Context, dir: *const libc::c_char) -> bool {
if stmt.execute(params![name, buf]).is_err() {
error!(
context,
0,
"Disk full? Cannot add file \"{}\" to backup.",
curr_pathNfilename.display(),
&curr_pathNfilename,
);
/* this is not recoverable! writing to the sqlite database should work! */
ok_to_continue = false;
@@ -807,12 +858,13 @@ unsafe fn export_backup(context: &Context, dir: *const libc::c_char) -> bool {
} else {
error!(
context,
0,
"Backup: Cannot copy from blob-directory \"{}\".",
context.get_blobdir().display(),
as_str(context.get_blobdir()),
);
}
} else {
info!(context, "Backup: No files to copy.",);
info!(context, 0, "Backup: No files to copy.",);
ok_to_continue = true;
}
if ok_to_continue {
@@ -820,24 +872,29 @@ unsafe fn export_backup(context: &Context, dir: *const libc::c_char) -> bool {
.set_config_int(context, "backup_time", now as i32)
.is_ok()
{
context.call_cb(Event::ImexFileWritten(
as_path(dest_pathNfilename).to_path_buf(),
));
success = true;
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 \"{}\".",
context.get_blobdir().display(),
as_str(context.get_blobdir())
);
};
}
}
}
if closed {
context.sql.open(&context, &context.get_dbfile(), 0);
context
.sql
.open(&context, &context.get_dbfile().unwrap(), 0);
}
if 0 != delete_dest_file {
dc_delete_file(context, as_path(dest_pathNfilename));
@@ -862,7 +919,7 @@ unsafe fn import_self_keys(context: &Context, dir_name: *const libc::c_char) ->
let mut path_plus_name: *mut libc::c_char = ptr::null_mut();
let mut set_default: libc::c_int;
let mut buf: *mut libc::c_char = ptr::null_mut();
let mut buf_bytes: libc::size_t = 0;
let mut buf_bytes: size_t = 0 as size_t;
// a pointer inside buf, MUST NOT be free()'d
let mut private_key: *const libc::c_char;
let mut buf2: *mut libc::c_char = ptr::null_mut();
@@ -891,7 +948,7 @@ unsafe fn import_self_keys(context: &Context, dir_name: *const libc::c_char) ->
dir_name,
name_c.as_ptr(),
);
info!(context, "Checking: {}", as_str(path_plus_name));
info!(context, 0, "Checking: {}", as_str(path_plus_name));
free(buf as *mut libc::c_void);
buf = ptr::null_mut();
if 0 == dc_read_file(
@@ -935,6 +992,7 @@ unsafe fn import_self_keys(context: &Context, dir_name: *const libc::c_char) ->
{
info!(
context,
0,
"Treating \"{}\" as a legacy private key.",
as_str(path_plus_name),
);
@@ -948,6 +1006,7 @@ unsafe fn import_self_keys(context: &Context, dir_name: *const libc::c_char) ->
if imported_cnt == 0i32 {
error!(
context,
0,
"No private keys found in \"{}\".",
as_str(dir_name),
);
@@ -955,6 +1014,7 @@ unsafe fn import_self_keys(context: &Context, dir_name: *const libc::c_char) ->
} else {
error!(
context,
0,
"Import: Cannot open directory \"{}\".",
as_str(dir_name),
);
@@ -969,7 +1029,8 @@ unsafe fn import_self_keys(context: &Context, dir_name: *const libc::c_char) ->
imported_cnt
}
unsafe fn export_self_keys(context: &Context, dir: *const libc::c_char) -> bool {
// TODO should return bool /rtn
unsafe fn export_self_keys(context: &Context, dir: *const libc::c_char) -> libc::c_int {
let mut export_errors = 0;
context
@@ -991,14 +1052,14 @@ unsafe fn export_self_keys(context: &Context, dir: *const libc::c_char) -> bool
for key_pair in keys {
let (id, public_key, private_key, is_default) = key_pair?;
if let Some(key) = public_key {
if export_key_to_asc_file(context, dir, id, &key, is_default) {
if 0 == export_key_to_asc_file(context, dir, id, &key, is_default) {
export_errors += 1;
}
} else {
export_errors += 1;
}
if let Some(key) = private_key {
if export_key_to_asc_file(context, dir, id, &key, is_default) {
if 0 == export_key_to_asc_file(context, dir, id, &key, is_default) {
export_errors += 1;
}
} else {
@@ -1011,20 +1072,25 @@ unsafe fn export_self_keys(context: &Context, dir: *const libc::c_char) -> bool
)
.unwrap();
export_errors == 0
if export_errors == 0 {
1
} else {
0
}
}
/*******************************************************************************
* Classic key export
******************************************************************************/
// TODO should return bool /rtn
unsafe fn export_key_to_asc_file(
context: &Context,
dir: *const libc::c_char,
id: libc::c_int,
key: &Key,
is_default: libc::c_int,
) -> bool {
let mut success = false;
) -> libc::c_int {
let mut success: libc::c_int = 0i32;
let file_name;
if 0 != is_default {
file_name = dc_mprintf(
@@ -1048,13 +1114,17 @@ unsafe fn export_key_to_asc_file(
id,
)
}
info!(context, "Exporting key {}", as_str(file_name),);
info!(context, 0, "Exporting key {}", as_str(file_name),);
dc_delete_file(context, as_path(file_name));
if !key.write_asc_to_file(as_path(file_name), context) {
error!(context, "Cannot write key to {}", as_str(file_name),);
if !key.write_asc_to_file(file_name, context) {
error!(context, 0, "Cannot write key to {}", as_str(file_name),);
} else {
context.call_cb(Event::ImexFileWritten(as_path(file_name).to_path_buf()));
success = true;
context.call_cb(
Event::IMEX_FILE_WRITTEN,
file_name as uintptr_t,
0i32 as uintptr_t,
);
success = 1i32
}
free(file_name as *mut libc::c_void);
@@ -1065,11 +1135,13 @@ unsafe fn export_key_to_asc_file(
mod tests {
use super::*;
use num_traits::ToPrimitive;
use crate::test_utils::*;
#[test]
fn test_render_setup_file() {
let t = test_context(Some(Box::new(logging_cb)));
let t = test_context(Some(logging_cb));
configure_alice_keypair(&t.ctx);
let msg = dc_render_setup_file(&t.ctx, "hello").unwrap();
@@ -1087,19 +1159,22 @@ mod tests {
assert!(msg.contains("-----END PGP MESSAGE-----\n"));
}
fn ac_setup_msg_cb(ctx: &Context, evt: Event) -> libc::uintptr_t {
match evt {
Event::GetString {
id: StockMessage::AcSetupMsgBody,
..
} => unsafe { "hello\r\nthere".strdup() as usize },
_ => logging_cb(ctx, evt),
unsafe extern "C" fn ac_setup_msg_cb(
ctx: &Context,
evt: Event,
d1: uintptr_t,
d2: uintptr_t,
) -> uintptr_t {
if evt == Event::GET_STRING && d1 == StockMessage::AcSetupMsgBody.to_usize().unwrap() {
"hello\r\nthere".strdup() as usize
} else {
logging_cb(ctx, evt, d1, d2)
}
}
#[test]
fn test_render_setup_file_newline_replace() {
let t = test_context(Some(Box::new(ac_setup_msg_cb)));
let t = test_context(Some(ac_setup_msg_cb));
configure_alice_keypair(&t.ctx);
let msg = dc_render_setup_file(&t.ctx, "pw").unwrap();
println!("{}", &msg);

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

@@ -2,7 +2,6 @@ use std::ffi::CString;
use std::ptr;
use chrono::TimeZone;
use mmime::clist::*;
use mmime::mailimf_types::*;
use mmime::mailimf_types_helper::*;
use mmime::mailmime_disposition::*;
@@ -15,8 +14,7 @@ use mmime::other::*;
use crate::chat::{self, Chat};
use crate::constants::*;
use crate::contact::*;
use crate::context::{get_version_str, Context};
use crate::dc_mimeparser::SystemMessage;
use crate::context::{dc_get_version_str, Context};
use crate::dc_strencode::*;
use crate::dc_tools::*;
use crate::e2ee::*;
@@ -25,6 +23,7 @@ use crate::location;
use crate::message::*;
use crate::param::*;
use crate::stock::StockMessage;
use crate::types::*;
use crate::x::*;
#[derive(Clone)]
@@ -38,16 +37,16 @@ pub struct dc_mimefactory_t<'a> {
pub timestamp: i64,
pub rfc724_mid: *mut libc::c_char,
pub loaded: dc_mimefactory_loaded_t,
pub msg: Message,
pub chat: Option<Chat>,
pub increation: bool,
pub msg: Message<'a>,
pub chat: Option<Chat<'a>>,
pub increation: libc::c_int,
pub in_reply_to: *mut libc::c_char,
pub references: *mut libc::c_char,
pub req_mdn: libc::c_int,
pub out: *mut MMAPString,
pub out_encrypted: libc::c_int,
pub out_gossiped: libc::c_int,
pub out_last_added_location_id: u32,
pub out_last_added_location_id: uint32_t,
pub error: *mut libc::c_char,
pub context: &'a Context,
}
@@ -103,7 +102,7 @@ pub unsafe fn dc_mimefactory_load_msg(
loaded: DC_MF_NOTHING_LOADED,
msg,
chat: Some(chat),
increation: false,
increation: 0,
in_reply_to: ptr::null_mut(),
references: ptr::null_mut(),
req_mdn: 0,
@@ -149,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,
@@ -171,10 +170,10 @@ pub unsafe fn dc_mimefactory_load_msg(
)
.unwrap();
let command = factory.msg.param.get_cmd();
let command = factory.msg.param.get_int(Param::Cmd).unwrap_or_default();
let msg = &factory.msg;
if command == SystemMessage::MemberRemovedFromGroup {
if command == 5 {
let email_to_remove = msg.param.get(Param::Arg).unwrap_or_default();
let email_to_remove_c = email_to_remove.strdup();
@@ -184,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,
@@ -198,8 +197,8 @@ pub unsafe fn dc_mimefactory_load_msg(
}
}
}
if command != SystemMessage::AutocryptSetupMessage
&& command != SystemMessage::SecurejoinMessage
if command != 6
&& command != 7
&& 0 != context
.sql
.get_config_int(context, "mdns_enabled")
@@ -226,7 +225,7 @@ pub unsafe fn dc_mimefactory_load_msg(
Err(err) => {
error!(
context,
"mimefactory: failed to load mime_in_reply_to: {:?}", err
0, "mimefactory: failed to load mime_in_reply_to: {:?}", err
);
}
}
@@ -265,7 +264,7 @@ unsafe fn load_from(factory: &mut dc_mimefactory_t) {
pub unsafe fn dc_mimefactory_load_mdn<'a>(
context: &'a Context,
msg_id: u32,
msg_id: uint32_t,
) -> Result<dc_mimefactory_t, Error> {
if 0 == context
.sql
@@ -291,7 +290,7 @@ pub unsafe fn dc_mimefactory_load_mdn<'a>(
loaded: DC_MF_NOTHING_LOADED,
msg,
chat: None,
increation: false,
increation: 0,
in_reply_to: ptr::null_mut(),
references: ptr::null_mut(),
req_mdn: 0,
@@ -335,7 +334,8 @@ pub unsafe fn dc_mimefactory_load_mdn<'a>(
Ok(factory)
}
pub unsafe fn dc_mimefactory_render(context: &Context, factory: &mut dc_mimefactory_t) -> bool {
// TODO should return bool /rtn
pub unsafe fn dc_mimefactory_render(factory: &mut dc_mimefactory_t) -> libc::c_int {
let subject: *mut mailimf_subject;
let mut ok_to_continue = true;
let imf_fields: *mut mailimf_fields;
@@ -345,7 +345,7 @@ pub unsafe fn dc_mimefactory_render(context: &Context, factory: &mut dc_mimefact
let mut subject_str: *mut libc::c_char = ptr::null_mut();
let mut afwd_email: libc::c_int = 0;
let mut col: libc::c_int = 0;
let mut success = false;
let mut success: libc::c_int = 0;
let mut parts: libc::c_int = 0;
let mut e2ee_guaranteed: libc::c_int = 0;
let mut min_verified: libc::c_int = 0;
@@ -459,18 +459,19 @@ pub unsafe fn dc_mimefactory_render(context: &Context, factory: &mut dc_mimefact
.map(|s| format!("/{}", s))
.unwrap_or_default();
let os_part = CString::new(os_part).expect("String -> CString conversion failed");
let version = CString::yolo(get_version_str());
let version = dc_get_version_str();
mailimf_fields_add(
imf_fields,
mailimf_field_new_custom(
strdup(b"X-Mailer\x00" as *const u8 as *const libc::c_char),
dc_mprintf(
b"Delta Chat Core %s%s\x00" as *const u8 as *const libc::c_char,
version.as_ptr(),
version,
os_part.as_ptr(),
),
),
);
free(version.cast());
mailimf_fields_add(
imf_fields,
@@ -530,7 +531,7 @@ pub unsafe fn dc_mimefactory_render(context: &Context, factory: &mut dc_mimefact
}
/* build header etc. */
let command = factory.msg.param.get_cmd();
let command = factory.msg.param.get_int(Param::Cmd).unwrap_or_default();
if chat.typ == Chattype::Group || chat.typ == Chattype::VerifiedGroup {
mailimf_fields_add(
imf_fields,
@@ -547,8 +548,7 @@ pub unsafe fn dc_mimefactory_render(context: &Context, factory: &mut dc_mimefact
dc_encode_header_words(name.as_ptr()),
),
);
if command == SystemMessage::MemberRemovedFromGroup {
if command == 5 {
let email_to_remove = factory
.msg
.param
@@ -567,7 +567,7 @@ pub unsafe fn dc_mimefactory_render(context: &Context, factory: &mut dc_mimefact
),
);
}
} else if command == SystemMessage::MemberAddedToGroup {
} else if command == 4 {
let msg = &factory.msg;
do_gossip = 1;
let email_to_add = msg.param.get(Param::Arg).unwrap_or_default().strdup();
@@ -586,7 +586,8 @@ pub unsafe fn dc_mimefactory_render(context: &Context, factory: &mut dc_mimefact
}
if 0 != msg.param.get_int(Param::Arg2).unwrap_or_default() & 0x1 {
info!(
context,
msg.context,
0,
"sending secure-join message \'{}\' >>>>>>>>>>>>>>>>>>>>>>>>>",
"vg-member-added",
);
@@ -598,7 +599,7 @@ pub unsafe fn dc_mimefactory_render(context: &Context, factory: &mut dc_mimefact
),
);
}
} else if command == SystemMessage::GroupNameChanged {
} else if command == 2 {
let msg = &factory.msg;
let value_to_add = msg.param.get(Param::Arg).unwrap_or_default().strdup();
@@ -611,7 +612,7 @@ pub unsafe fn dc_mimefactory_render(context: &Context, factory: &mut dc_mimefact
value_to_add,
),
);
} else if command == SystemMessage::GroupImageChanged {
} else if command == 3 {
let msg = &factory.msg;
grpimage = msg.param.get(Param::Arg);
if grpimage.is_none() {
@@ -625,8 +626,7 @@ pub unsafe fn dc_mimefactory_render(context: &Context, factory: &mut dc_mimefact
}
}
}
if command == SystemMessage::LocationStreamingEnabled {
if command == 8 {
mailimf_fields_add(
imf_fields,
mailimf_field_new_custom(
@@ -637,7 +637,7 @@ pub unsafe fn dc_mimefactory_render(context: &Context, factory: &mut dc_mimefact
),
);
}
if command == SystemMessage::AutocryptSetupMessage {
if command == 6 {
mailimf_fields_add(
imf_fields,
mailimf_field_new_custom(
@@ -650,12 +650,13 @@ pub unsafe fn dc_mimefactory_render(context: &Context, factory: &mut dc_mimefact
.stock_str(StockMessage::AcSetupMsgBody)
.strdup();
}
if command == SystemMessage::SecurejoinMessage {
if command == 7 {
let msg = &factory.msg;
let step = msg.param.get(Param::Arg).unwrap_or_default().strdup();
if strlen(step) > 0 {
info!(
context,
msg.context,
0,
"sending secure-join message \'{}\' >>>>>>>>>>>>>>>>>>>>>>>>>",
as_str(step),
);
@@ -724,16 +725,13 @@ pub unsafe fn dc_mimefactory_render(context: &Context, factory: &mut dc_mimefact
}
}
}
if let Some(grpimage) = grpimage {
info!(factory.context, "setting group image '{}'", grpimage);
let mut meta = dc_msg_new_untyped();
let mut meta = dc_msg_new_untyped(factory.context);
meta.type_0 = Viewtype::Image;
meta.param.set(Param::File, grpimage);
let mut filename_as_sent = ptr::null_mut();
meta_part = build_body_file(
context,
&meta,
b"group-image\x00" as *const u8 as *const libc::c_char,
&mut filename_as_sent,
@@ -836,7 +834,7 @@ pub unsafe fn dc_mimefactory_render(context: &Context, factory: &mut dc_mimefact
/* add attachment part */
if chat::msgtype_has_file(factory.msg.type_0) {
if !is_file_size_okay(context, &factory.msg) {
if !is_file_size_okay(&factory.msg) {
let error: *mut libc::c_char = dc_mprintf(
b"Message exceeds the recommended %i MB.\x00" as *const u8
as *const libc::c_char,
@@ -847,7 +845,7 @@ pub unsafe fn dc_mimefactory_render(context: &Context, factory: &mut dc_mimefact
ok_to_continue = false;
} else {
let file_part: *mut mailmime =
build_body_file(context, &factory.msg, ptr::null(), ptr::null_mut());
build_body_file(&factory.msg, ptr::null(), ptr::null_mut());
if !file_part.is_null() {
mailmime_smart_add_part(message, file_part);
parts += 1
@@ -895,9 +893,12 @@ pub unsafe fn dc_mimefactory_render(context: &Context, factory: &mut dc_mimefact
mailmime_smart_add_part(message, kml_mime_part);
}
if location::is_sending_locations_to_chat(context, factory.msg.chat_id) {
if location::is_sending_locations_to_chat(
factory.msg.context,
factory.msg.chat_id,
) {
if let Ok((kml_file, last_added_location_id)) =
location::get_kml(context, factory.msg.chat_id)
location::get_kml(factory.msg.context, factory.msg.chat_id)
{
let content_type = mailmime_content_new_with_str(
b"application/vnd.google-earth.kml+xml\x00" as *const u8
@@ -950,7 +951,7 @@ pub unsafe fn dc_mimefactory_render(context: &Context, factory: &mut dc_mimefact
.stock_str(StockMessage::EncryptedMsg)
.into_owned()
} else {
to_string(dc_msg_get_summarytext(context, &mut factory.msg, 32))
to_string(dc_msg_get_summarytext(&mut factory.msg, 32))
};
let p2 = factory
.context
@@ -958,16 +959,16 @@ pub unsafe fn dc_mimefactory_render(context: &Context, factory: &mut dc_mimefact
message_text = format!("{}\r\n", p2).strdup();
let human_mime_part: *mut mailmime = build_body_text(message_text);
mailmime_add_part(multipart, human_mime_part);
let version = CString::yolo(get_version_str());
let version = dc_get_version_str();
message_text2 =
dc_mprintf(
b"Reporting-UA: Delta Chat %s\r\nOriginal-Recipient: rfc822;%s\r\nFinal-Recipient: rfc822;%s\r\nOriginal-Message-ID: <%s>\r\nDisposition: manual-action/MDN-sent-automatically; displayed\r\n\x00"
as *const u8 as *const libc::c_char,
version.as_ptr(),
version,
factory.from_addr, factory.from_addr,
factory.msg.rfc724_mid
);
free(version.cast());
let content_type_0: *mut mailmime_content = mailmime_content_new_with_str(
b"message/disposition-notification\x00" as *const u8 as *const libc::c_char,
);
@@ -994,8 +995,7 @@ pub unsafe fn dc_mimefactory_render(context: &Context, factory: &mut dc_mimefact
e.as_ptr(),
);
} else {
subject_str =
get_subject(context, factory.chat.as_ref(), &mut factory.msg, afwd_email)
subject_str = get_subject(factory.chat.as_ref(), &mut factory.msg, afwd_email)
}
subject = mailimf_subject_new(dc_encode_header_words(subject_str));
mailimf_fields_add(
@@ -1045,7 +1045,7 @@ pub unsafe fn dc_mimefactory_render(context: &Context, factory: &mut dc_mimefact
}
factory.out = mmap_string_new(b"\x00" as *const u8 as *const libc::c_char);
mailmime_write_mem(factory.out, &mut col, message);
success = true;
success = 1;
}
}
@@ -1061,7 +1061,6 @@ pub unsafe fn dc_mimefactory_render(context: &Context, factory: &mut dc_mimefact
}
unsafe fn get_subject(
context: &Context,
chat: Option<&Chat>,
msg: &mut Message,
afwd_email: libc::c_int,
@@ -1071,6 +1070,7 @@ unsafe fn get_subject(
}
let chat = chat.unwrap();
let context = chat.context;
let ret: *mut libc::c_char;
let raw_subject = {
@@ -1083,7 +1083,7 @@ unsafe fn get_subject(
} else {
b"\x00" as *const u8 as *const libc::c_char
};
if msg.param.get_cmd() == SystemMessage::AutocryptSetupMessage {
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!(
@@ -1135,7 +1135,6 @@ unsafe fn build_body_text(text: *mut libc::c_char) -> *mut mailmime {
#[allow(non_snake_case)]
unsafe fn build_body_file(
context: &Context,
msg: &Message,
mut base_name: *const libc::c_char,
ret_file_name_as_sent: *mut *mut libc::c_char,
@@ -1219,7 +1218,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 {
@@ -1251,12 +1250,12 @@ unsafe fn build_body_file(
ptr::null_mut(),
ptr::null_mut(),
ptr::null_mut(),
0 as libc::size_t,
0 as size_t,
mailmime_parameter_new(
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() {
@@ -1288,10 +1287,7 @@ unsafe fn build_body_file(
) as *mut libc::c_void,
);
mime_sub = mailmime_new_empty(content, mime_fields);
let abs_path = dc_get_abs_path(context, path_filename)
.to_c_string()
.unwrap();
mailmime_set_body_file(mime_sub, dc_strdup(abs_path.as_ptr()));
mailmime_set_body_file(mime_sub, dc_get_abs_path(msg.context, path_filename));
if !ret_file_name_as_sent.is_null() {
*ret_file_name_as_sent = dc_strdup(filename_to_send)
}
@@ -1308,10 +1304,10 @@ unsafe fn build_body_file(
/*******************************************************************************
* Render
******************************************************************************/
unsafe fn is_file_size_okay(context: &Context, msg: &Message) -> bool {
unsafe fn is_file_size_okay(msg: &Message) -> bool {
let mut file_size_okay = true;
let path = msg.param.get(Param::File).unwrap_or_default();
let bytes = dc_get_filebytes(context, &path);
let bytes = dc_get_filebytes(msg.context, &path);
if bytes > (49 * 1024 * 1024 / 4 * 3) {
file_size_okay = false;

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

View File

@@ -1,30 +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::*;
/**
* Encode non-ascii-strings as `=?UTF-8?Q?Bj=c3=b6rn_Petersen?=`.
* Belongs to RFC 2047: https://tools.ietf.org/html/rfc2047
*
* We do not fold at position 72; this would result in empty words as `=?utf-8?Q??=` which are correct,
* but cannot be displayed by some mail programs (eg. Android Stock Mail).
* however, this is not needed, as long as _one_ word is not longer than 72 characters.
* _if_ it is, the display may get weird. This affects the subject only.
* the best solution wor all this would be if libetpan encodes the line as only libetpan knowns when a header line is full.
*
* @param to_encode Null-terminated UTF-8-string to encode.
* @return Returns the encoded string which must be free()'d when no longed needed.
* On errors, NULL is returned.
*/
#[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();
@@ -65,7 +118,7 @@ pub unsafe fn dc_encode_header_words(to_encode: *const libc::c_char) -> *mut lib
b"utf-8\x00" as *const u8 as *const libc::c_char,
mmapstr,
begin,
end.wrapping_offset_from(begin) as libc::size_t,
end.wrapping_offset_from(begin) as size_t,
) {
ok_to_continue = false;
continue;
@@ -81,7 +134,7 @@ pub unsafe fn dc_encode_header_words(to_encode: *const libc::c_char) -> *mut lib
if mmap_string_append_len(
mmapstr,
end,
cur.wrapping_offset_from(end) as libc::size_t,
cur.wrapping_offset_from(end) as size_t,
)
.is_null()
{
@@ -92,7 +145,7 @@ pub unsafe fn dc_encode_header_words(to_encode: *const libc::c_char) -> *mut lib
} else if mmap_string_append_len(
mmapstr,
begin,
cur.wrapping_offset_from(begin) as libc::size_t,
cur.wrapping_offset_from(begin) as size_t,
)
.is_null()
{
@@ -121,10 +174,10 @@ unsafe fn quote_word(
display_charset: *const libc::c_char,
mmapstr: *mut MMAPString,
word: *const libc::c_char,
size: libc::size_t,
size: size_t,
) -> bool {
let mut cur: *const libc::c_char;
let mut i = 0;
let mut i: size_t = 0i32 as size_t;
let mut hex: [libc::c_char; 4] = [0; 4];
// let mut col: libc::c_int = 0i32;
if mmap_string_append(mmapstr, b"=?\x00" as *const u8 as *const libc::c_char).is_null() {
@@ -187,7 +240,7 @@ unsafe fn get_word(
{
cur = cur.offset(1isize)
}
*pto_be_quoted = to_be_quoted(begin, cur.wrapping_offset_from(begin) as libc::size_t);
*pto_be_quoted = to_be_quoted(begin, cur.wrapping_offset_from(begin) as size_t);
*pend = cur;
}
@@ -196,9 +249,9 @@ unsafe fn get_word(
******************************************************************************/
/* see comment below */
unsafe fn to_be_quoted(word: *const libc::c_char, size: libc::size_t) -> bool {
unsafe fn to_be_quoted(word: *const libc::c_char, size: size_t) -> bool {
let mut cur: *const libc::c_char = word;
let mut i = 0;
let mut i: size_t = 0i32 as size_t;
while i < size {
match *cur as libc::c_int {
44 | 58 | 33 | 34 | 35 | 36 | 64 | 91 | 92 | 93 | 94 | 96 | 123 | 124 | 125 | 126
@@ -221,7 +274,7 @@ pub unsafe fn dc_decode_header_words(in_0: *const libc::c_char) -> *mut libc::c_
return ptr::null_mut();
}
let mut out: *mut libc::c_char = ptr::null_mut();
let mut cur_token = 0;
let mut cur_token: size_t = 0i32 as size_t;
let r: libc::c_int = mailmime_encoded_phrase_parse(
b"iso-8859-1\x00" as *const u8 as *const libc::c_char,
in_0,
@@ -237,88 +290,374 @@ pub unsafe fn dc_decode_header_words(in_0: *const libc::c_char) -> *mut libc::c_
out
}
pub fn dc_decode_header_words_safe(input: &str) -> String {
static FROM_ENCODING: &[u8] = b"iso-8859-1\x00";
static TO_ENCODING: &[u8] = b"utf-8\x00";
let mut out = ptr::null_mut();
let mut cur_token = 0;
let input_c = CString::yolo(input);
unsafe {
let r = mailmime_encoded_phrase_parse(
FROM_ENCODING.as_ptr().cast(),
input_c.as_ptr(),
input.len(),
&mut cur_token,
TO_ENCODING.as_ptr().cast(),
&mut out,
);
if r as u32 != MAILIMF_NO_ERROR || out.is_null() {
input.to_string()
} else {
let res = to_string(out);
free(out.cast());
res
#[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());
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;
}
}
}
pub fn dc_needs_ext_header(to_check: impl AsRef<str>) -> bool {
let to_check = to_check.as_ref();
if to_check.is_empty() {
return false;
}
to_check.chars().any(|c| {
!(c.is_ascii_alphanumeric()
|| c == '-'
|| c == '_'
|| c == '_'
|| c == '.'
|| c == '~'
|| c == '%')
})
}
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();
}
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) {
@@ -333,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 {
@@ -395,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]
@@ -446,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);
}
}

56
src/dc_token.rs Normal file
View File

@@ -0,0 +1,56 @@
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) -> 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![namespc as i32, foreign_id as i32, &token, time()],
)
.ok();
token
}
pub fn dc_token_lookup(
context: &Context,
namespc: dc_tokennamespc_t,
foreign_id: u32,
) -> Option<String> {
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,
)
}
pub fn dc_token_lookup_or_new(
context: &Context,
namespc: dc_tokennamespc_t,
foreign_id: u32,
) -> String {
dc_token_lookup(context, namespc, foreign_id)
.unwrap_or_else(|| dc_token_save(context, namespc, foreign_id))
}
pub fn dc_token_exists(context: &Context, namespc: dc_tokennamespc_t, token: &str) -> bool {
context
.sql
.exists(
"SELECT id FROM tokens WHERE namespc=? AND token=?;",
params![namespc as i32, token],
)
.unwrap_or_default()
}

View File

@@ -6,16 +6,16 @@ use std::time::SystemTime;
use std::{fmt, fs, ptr};
use chrono::{Local, TimeZone};
use itertools::max;
use libc::uintptr_t;
use mmime::clist::*;
use mmime::mailimf_types::*;
use rand::{thread_rng, Rng};
use crate::context::Context;
use crate::error::Error;
use crate::types::*;
use crate::x::*;
use itertools::max;
/* Some tools and enhancements to the used libraries, there should be
no references to Context and other "larger" classes here. */
/* ** library-private **********************************************************/
@@ -98,7 +98,7 @@ pub unsafe fn dc_str_replace(
}
unsafe fn dc_ltrim(buf: *mut libc::c_char) {
let mut len: libc::size_t;
let mut len: size_t;
let mut cur: *const libc::c_uchar;
if !buf.is_null() && 0 != *buf as libc::c_int {
len = strlen(buf);
@@ -118,7 +118,7 @@ unsafe fn dc_ltrim(buf: *mut libc::c_char) {
}
unsafe fn dc_rtrim(buf: *mut libc::c_char) {
let mut len: libc::size_t;
let mut len: size_t;
let mut cur: *mut libc::c_uchar;
if !buf.is_null() && 0 != *buf as libc::c_int {
len = strlen(buf);
@@ -261,18 +261,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)
@@ -301,9 +294,9 @@ pub unsafe fn dc_truncate_n_unwrap_str(
if *p1 as libc::c_int > ' ' as i32 {
lastIsCharacter = 1
} else if 0 != lastIsCharacter {
let used_bytes = (p1 as uintptr_t).wrapping_sub(buf as uintptr_t) as libc::size_t;
let used_bytes: size_t = (p1 as uintptr_t).wrapping_sub(buf as uintptr_t) as size_t;
if dc_utf8_strnlen(buf, used_bytes) >= approx_characters as usize {
let buf_bytes = strlen(buf);
let buf_bytes: size_t = strlen(buf);
if buf_bytes.wrapping_sub(used_bytes) >= strlen(ellipse_utf8) {
strcpy(p1 as *mut libc::c_char, ellipse_utf8);
}
@@ -324,12 +317,12 @@ pub unsafe fn dc_truncate_n_unwrap_str(
};
}
unsafe fn dc_utf8_strnlen(s: *const libc::c_char, n: libc::size_t) -> libc::size_t {
unsafe fn dc_utf8_strnlen(s: *const libc::c_char, n: size_t) -> size_t {
if s.is_null() {
return 0;
}
let mut j: libc::size_t = 0;
let mut j: size_t = 0;
for i in 0..n {
if *s.add(i) as libc::c_int & 0xc0 != 0x80 {
j = j.wrapping_add(1)
@@ -442,10 +435,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 */
@@ -543,7 +547,7 @@ pub fn dc_create_id() -> String {
- the group-id should be a string with the characters [a-zA-Z0-9\-_] */
let mut rng = thread_rng();
let buf: [u32; 3] = [rng.gen(), rng.gen(), rng.gen()];
let buf: [uint32_t; 3] = [rng.gen(), rng.gen(), rng.gen()];
encode_66bits_as_base64(buf[0usize], buf[1usize], buf[2usize])
}
@@ -626,21 +630,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
@@ -792,45 +781,67 @@ pub fn dc_get_filemeta(buf: &[u8]) -> Result<(u32, u32), Error> {
///
/// If `path` starts with "$BLOBDIR", replaces it with the blobdir path.
/// Otherwise, returns path as is.
pub fn dc_get_abs_path<P: AsRef<std::path::Path>>(
pub fn dc_get_abs_path_safe<P: AsRef<std::path::Path>>(
context: &Context,
path: P,
) -> std::path::PathBuf {
let p: &std::path::Path = path.as_ref();
if let Ok(p) = p.strip_prefix("$BLOBDIR") {
context.get_blobdir().join(p)
assert!(
context.has_blobdir(),
"Expected context to have blobdir to substitute $BLOBDIR",
);
std::path::PathBuf::from(as_str(context.get_blobdir())).join(p)
} else {
p.into()
}
}
pub fn dc_file_exist(context: &Context, path: impl AsRef<std::path::Path>) -> bool {
dc_get_abs_path(context, &path).exists()
pub unsafe fn dc_get_abs_path(
context: &Context,
path_filename: impl AsRef<str>,
) -> *mut libc::c_char {
let starts = path_filename.as_ref().starts_with("$BLOBDIR");
if starts && !context.has_blobdir() {
return ptr::null_mut();
}
let mut path_filename_abs = path_filename.as_ref().strdup();
if starts && context.has_blobdir() {
dc_str_replace(
&mut path_filename_abs,
b"$BLOBDIR\x00" as *const u8 as *const libc::c_char,
context.get_blobdir(),
);
}
path_filename_abs
}
pub fn dc_get_filebytes(context: &Context, path: impl AsRef<std::path::Path>) -> u64 {
let path_abs = dc_get_abs_path(context, &path);
pub fn dc_file_exist(context: &Context, path: impl AsRef<std::path::Path>) -> bool {
dc_get_abs_path_safe(context, &path).exists()
}
pub fn dc_get_filebytes(context: &Context, path: impl AsRef<std::path::Path>) -> uint64_t {
let path_abs = dc_get_abs_path_safe(context, &path);
match fs::metadata(&path_abs) {
Ok(meta) => meta.len() as u64,
Ok(meta) => meta.len() as uint64_t,
Err(_err) => 0,
}
}
pub fn dc_delete_file(context: &Context, path: impl AsRef<std::path::Path>) -> bool {
let path_abs = dc_get_abs_path(context, &path);
if !path_abs.is_file() {
warn!(
context,
"Will not delete directory \"{}\".",
path.as_ref().display()
);
return false;
}
let path_abs = dc_get_abs_path_safe(context, &path);
let res = if path_abs.is_file() {
fs::remove_file(path_abs)
} else {
fs::remove_dir_all(path_abs)
};
match fs::remove_file(path_abs) {
match res {
Ok(_) => true,
Err(_err) => {
warn!(context, "Cannot delete \"{}\".", path.as_ref().display());
warn!(context, 0, "Cannot delete \"{}\".", path.as_ref().display());
false
}
}
@@ -841,13 +852,14 @@ pub fn dc_copy_file(
src: impl AsRef<std::path::Path>,
dest: impl AsRef<std::path::Path>,
) -> bool {
let src_abs = dc_get_abs_path(context, &src);
let dest_abs = dc_get_abs_path(context, &dest);
let src_abs = dc_get_abs_path_safe(context, &src);
let dest_abs = dc_get_abs_path_safe(context, &dest);
match fs::copy(&src_abs, &dest_abs) {
Ok(_) => true,
Err(_) => {
error!(
context,
0,
"Cannot copy \"{}\" to \"{}\".",
src.as_ref().display(),
dest.as_ref().display(),
@@ -858,13 +870,14 @@ pub fn dc_copy_file(
}
pub fn dc_create_folder(context: &Context, path: impl AsRef<std::path::Path>) -> bool {
let path_abs = dc_get_abs_path(context, &path);
let path_abs = dc_get_abs_path_safe(context, &path);
if !path_abs.exists() {
match fs::create_dir_all(path_abs) {
Ok(_) => true,
Err(_err) => {
warn!(
context,
0,
"Cannot create directory \"{}\".",
path.as_ref().display(),
);
@@ -881,7 +894,7 @@ pub unsafe fn dc_write_file(
context: &Context,
pathNfilename: *const libc::c_char,
buf: *const libc::c_void,
buf_bytes: libc::size_t,
buf_bytes: size_t,
) -> libc::c_int {
let bytes = std::slice::from_raw_parts(buf as *const u8, buf_bytes);
@@ -893,10 +906,11 @@ pub fn dc_write_file_safe<P: AsRef<std::path::Path>>(
path: P,
buf: &[u8],
) -> bool {
let path_abs = dc_get_abs_path(context, &path);
let path_abs = dc_get_abs_path_safe(context, &path);
if let Err(_err) = fs::write(&path_abs, buf) {
warn!(
context,
0,
"Cannot write {} bytes to \"{}\".",
buf.len(),
path.as_ref().display(),
@@ -912,7 +926,7 @@ pub unsafe fn dc_read_file(
context: &Context,
pathNfilename: *const libc::c_char,
buf: *mut *mut libc::c_void,
buf_bytes: *mut libc::size_t,
buf_bytes: *mut size_t,
) -> libc::c_int {
if pathNfilename.is_null() {
return 0;
@@ -928,12 +942,13 @@ pub unsafe fn dc_read_file(
}
pub fn dc_read_file_safe<P: AsRef<std::path::Path>>(context: &Context, path: P) -> Option<Vec<u8>> {
let path_abs = dc_get_abs_path(context, &path);
let path_abs = dc_get_abs_path_safe(context, &path);
match fs::read(&path_abs) {
Ok(bytes) => Some(bytes),
Err(_err) => {
warn!(
context,
0,
"Cannot read \"{}\" or file is empty.",
path.as_ref().display()
);
@@ -997,22 +1012,13 @@ pub unsafe fn dc_get_fine_pathNfilename(
}
pub fn dc_is_blobdir_path(context: &Context, path: impl AsRef<str>) -> bool {
context
.get_blobdir()
.to_str()
.map(|s| path.as_ref().starts_with(s))
.unwrap_or_default()
path.as_ref().starts_with(as_str(context.get_blobdir()))
|| path.as_ref().starts_with("$BLOBDIR")
}
fn dc_make_rel_path(context: &Context, path: &mut String) {
if context
.get_blobdir()
.to_str()
.map(|s| path.starts_with(s))
.unwrap_or_default()
{
*path = path.replace("$BLOBDIR", context.get_blobdir().to_str().unwrap());
if path.starts_with(as_str(context.get_blobdir())) {
*path = path.replace("$BLOBDIR", as_str(context.get_blobdir()));
}
}
@@ -1523,36 +1529,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 {
@@ -1890,37 +1866,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

@@ -30,9 +30,10 @@ use crate::keyring::*;
use crate::peerstate::*;
use crate::pgp::*;
use crate::securejoin::handle_degrade_event;
use crate::types::*;
use crate::x::*;
#[derive(Debug, Default)]
#[derive(Default)]
pub struct E2eeHelper {
pub encryption_successfull: bool,
cdata_to_free: Option<Box<dyn Any>>,
@@ -86,11 +87,11 @@ impl E2eeHelper {
EncryptPreference::NoPreference
};
let addr = context.get_config(Config::ConfiguredAddr);
let addr = context.sql.get_config(context, "configured_addr");
if let Some(addr) = addr {
let pubkey_ret = load_or_generate_self_public_key(context, &addr).map_err(|err| {
error!(context, "Failed to load public key: {}", err);
error!(context, 0, "Failed to load public key: {}", err);
err
});
if let Ok(public_key) = pubkey_ret {
@@ -112,7 +113,7 @@ impl E2eeHelper {
let peerstate = peerstate.unwrap();
info!(
context,
"dc_e2ee_encrypt {} has peerstate", recipient_addr
0, "dc_e2ee_encrypt {} has peerstate", recipient_addr
);
if let Some(key) = peerstate.peek_key(min_verified as usize) {
keyring.add_owned(key.clone());
@@ -121,6 +122,7 @@ impl E2eeHelper {
} else {
info!(
context,
0,
"dc_e2ee_encrypt {} HAS NO peerstate {}",
recipient_addr,
peerstate.is_some()
@@ -165,7 +167,7 @@ impl E2eeHelper {
let message_to_encrypt: *mut mailmime = mailmime_new(
MAILMIME_MESSAGE as libc::c_int,
ptr::null(),
0 as libc::size_t,
0i32 as size_t,
mailmime_fields_new_empty(),
mailmime_get_content_message(),
ptr::null_mut(),
@@ -297,10 +299,8 @@ impl E2eeHelper {
ok_to_continue = false;
} else {
if let Some(ctext_v) = dc_pgp_pk_encrypt(
std::slice::from_raw_parts(
(*plain).str_0 as *const u8,
(*plain).len,
),
(*plain).str_0 as *const libc::c_void,
(*plain).len,
&keyring,
sign_key.as_ref(),
) {
@@ -311,7 +311,7 @@ impl E2eeHelper {
/* create MIME-structure that will contain the encrypted text */
let mut encrypted_part: *mut mailmime = new_data_part(
ptr::null_mut(),
0 as libc::size_t,
0i32 as size_t,
b"multipart/encrypted\x00" as *const u8
as *const libc::c_char
as *mut libc::c_char,
@@ -386,17 +386,16 @@ impl E2eeHelper {
/*just a pointer into mailmime structure, must not be freed*/
let imffields: *mut mailimf_fields = mailmime_find_mailimf_fields(in_out_message);
let mut message_time = 0;
let mut from = None;
let mut from: *mut libc::c_char = ptr::null_mut();
let mut private_keyring = Keyring::default();
let mut public_keyring_for_validate = Keyring::default();
let mut gossip_headers: *mut mailimf_fields = ptr::null_mut();
if !(in_out_message.is_null() || imffields.is_null()) {
let mut field = mailimf_find_field(imffields, MAILIMF_FIELD_FROM as libc::c_int);
let mut field: *mut mailimf_field =
mailimf_find_field(imffields, MAILIMF_FIELD_FROM as libc::c_int);
if !field.is_null() && !(*field).fld_data.fld_from.is_null() {
from = mailimf_find_first_addr((*(*field).fld_data.fld_from).frm_mb_list)
}
field = mailimf_find_field(imffields, MAILIMF_FIELD_ORIG_DATE as libc::c_int);
if !field.is_null() && !(*field).fld_data.fld_orig_date.is_null() {
let orig_date: *mut mailimf_orig_date = (*field).fld_data.fld_orig_date;
@@ -408,32 +407,28 @@ impl E2eeHelper {
}
}
let mut peerstate = None;
let autocryptheader = from
.as_ref()
.and_then(|from| Aheader::from_imffields(from, imffields));
if message_time > 0 {
if let Some(ref from) = from {
peerstate = Peerstate::from_addr(context, &context.sql, from);
let autocryptheader = Aheader::from_imffields(from, imffields);
if message_time > 0 && !from.is_null() {
peerstate = Peerstate::from_addr(context, &context.sql, as_str(from));
if let Some(ref mut peerstate) = peerstate {
if let Some(ref header) = autocryptheader {
peerstate.apply_header(&header, message_time);
peerstate.save_to_db(&context.sql, false);
} else if message_time > peerstate.last_seen_autocrypt
&& !contains_report(in_out_message)
{
peerstate.degrade_encryption(message_time);
peerstate.save_to_db(&context.sql, false);
}
} else if let Some(ref header) = autocryptheader {
let p = Peerstate::from_header(context, header, message_time);
assert!(p.save_to_db(&context.sql, true));
peerstate = Some(p);
if let Some(ref mut peerstate) = peerstate {
if let Some(ref header) = autocryptheader {
peerstate.apply_header(&header, message_time);
peerstate.save_to_db(&context.sql, false);
} else if message_time > peerstate.last_seen_autocrypt
&& !contains_report(in_out_message)
{
peerstate.degrade_encryption(message_time);
peerstate.save_to_db(&context.sql, false);
}
} else if let Some(ref header) = autocryptheader {
let p = Peerstate::from_header(context, header, message_time);
assert!(p.save_to_db(&context.sql, true));
peerstate = Some(p);
}
}
/* load private key for decryption */
let self_addr = context.get_config(Config::ConfiguredAddr);
let self_addr = context.sql.get_config(context, "configured_addr");
if let Some(self_addr) = self_addr {
if private_keyring.load_self_private_for_decrypting(
context,
@@ -441,8 +436,7 @@ impl E2eeHelper {
&context.sql,
) {
if peerstate.as_ref().map(|p| p.last_seen).unwrap_or_else(|| 0) == 0 {
peerstate =
Peerstate::from_addr(&context, &context.sql, &from.unwrap_or_default());
peerstate = Peerstate::from_addr(&context, &context.sql, as_str(from));
}
if let Some(ref peerstate) = peerstate {
if peerstate.degrade_event.is_some() {
@@ -491,12 +485,14 @@ impl E2eeHelper {
if !gossip_headers.is_null() {
mailimf_fields_free(gossip_headers);
}
free(from as *mut libc::c_void);
}
}
unsafe fn new_data_part(
data: *mut libc::c_void,
data_bytes: libc::size_t,
data_bytes: size_t,
default_content_type: *mut libc::c_char,
default_encoding: libc::c_int,
) -> *mut mailmime {
@@ -610,7 +606,7 @@ fn load_or_generate_self_public_key(context: &Context, self_addr: impl AsRef<str
let start = std::time::Instant::now();
info!(
context,
"Generating keypair with {} bits, e={} ...", 2048, 65537,
0, "Generating keypair with {} bits, e={} ...", 2048, 65537,
);
match dc_pgp_create_keypair(&self_addr) {
Some((public_key, private_key)) => {
@@ -625,6 +621,7 @@ fn load_or_generate_self_public_key(context: &Context, self_addr: impl AsRef<str
true => {
info!(
context,
0,
"Keypair generated in {:.3}s.",
start.elapsed().as_secs()
);
@@ -692,6 +689,7 @@ unsafe fn update_gossip_peerstates(
} else {
info!(
context,
0,
"Ignoring gossipped \"{}\" as the address is not in To/Cc list.",
&header.addr,
);
@@ -747,7 +745,7 @@ unsafe fn decrypt_recursive(
&mut decrypted_mime,
) {
if (*ret_gossip_headers).is_null() && ret_valid_signatures.len() > 0 {
let mut dummy: libc::size_t = 0i32 as libc::size_t;
let mut dummy: size_t = 0i32 as size_t;
let mut test: *mut mailimf_fields = ptr::null_mut();
if mailimf_envelope_and_optional_fields_parse(
(*decrypted_mime).mm_mime_start,
@@ -834,7 +832,7 @@ unsafe fn decrypt_part(
let mut transfer_decoding_buffer: *mut libc::c_char = ptr::null_mut();
/* must not be free()'d */
let mut decoded_data: *const libc::c_char = ptr::null_mut();
let mut decoded_data_bytes: libc::size_t = 0;
let mut decoded_data_bytes: size_t = 0i32 as size_t;
let mut sth_decrypted = false;
*ret_decrypted_mime = ptr::null_mut();
@@ -880,7 +878,7 @@ unsafe fn decrypt_part(
}
} else {
let r: libc::c_int;
let mut current_index: libc::size_t = 0;
let mut current_index: size_t = 0i32 as size_t;
r = mailmime_part_parse(
(*mime_data).dt_data.dt_text.dt_data,
(*mime_data).dt_data.dt_text.dt_length,
@@ -909,7 +907,8 @@ unsafe fn decrypt_part(
/*if we already have fingerprints, do not add more; this ensures, only the fingerprints from the outer-most part are collected */
if let Some(plain) = dc_pgp_pk_decrypt(
std::slice::from_raw_parts(decoded_data as *const u8, decoded_data_bytes),
decoded_data as *const libc::c_void,
decoded_data_bytes,
&private_keyring,
&public_keyring_for_validate,
add_signatures,
@@ -917,7 +916,7 @@ unsafe fn decrypt_part(
let plain_bytes = plain.len();
let plain_buf = plain.as_ptr() as *const libc::c_char;
let mut index: libc::size_t = 0;
let mut index: size_t = 0i32 as size_t;
let mut decrypted_mime: *mut mailmime = ptr::null_mut();
if mailmime_parse(
plain_buf as *const _,
@@ -1095,12 +1094,15 @@ Sent with my Delta Chat Messenger: https://delta.chat";
let mut decoded_data_bytes = 0;
let mut transfer_decoding_buffer: *mut libc::c_char = ptr::null_mut();
assert!(mailmime_transfer_decode(
msg1,
&mut decoded_data,
&mut decoded_data_bytes,
&mut transfer_decoding_buffer,
));
assert_eq!(
mailmime_transfer_decode(
msg1,
&mut decoded_data,
&mut decoded_data_bytes,
&mut transfer_decoding_buffer,
),
1
);
println!(
"{:?}",
String::from_utf8_lossy(std::slice::from_raw_parts(

View File

@@ -22,8 +22,6 @@ pub enum Error {
Image(image_meta::ImageError),
#[fail(display = "{:?}", _0)]
Utf8(std::str::Utf8Error),
#[fail(display = "{:?}", _0)]
CStringError(crate::dc_tools::CStringError),
}
pub type Result<T> = std::result::Result<T, Error>;
@@ -64,12 +62,6 @@ impl From<image_meta::ImageError> for Error {
}
}
impl From<crate::dc_tools::CStringError> for Error {
fn from(err: crate::dc_tools::CStringError) -> Error {
Error::CStringError(err)
}
}
#[macro_export]
macro_rules! bail {
($e:expr) => {

View File

@@ -1,229 +0,0 @@
use std::path::PathBuf;
use strum::EnumProperty;
use crate::stock::StockMessage;
impl Event {
/// Returns the corresponding Event id.
pub fn as_id(&self) -> i32 {
self.get_str("id")
.expect("missing id")
.parse()
.expect("invalid id")
}
}
#[derive(Debug, Clone, PartialEq, Eq, EnumProperty)]
pub enum Event {
/// The library-user may write an informational string to the log.
/// Passed to the callback given to dc_context_new().
/// This event should not be reported to the end-user using a popup or something like that.
///
/// @return 0
#[strum(props(id = "100"))]
Info(String),
/// Emitted when SMTP connection is established and login was successful.
///
/// @return 0
#[strum(props(id = "101"))]
SmtpConnected(String),
/// Emitted when IMAP connection is established and login was successful.
///
/// @return 0
#[strum(props(id = "102"))]
ImapConnected(String),
/// Emitted when a message was successfully sent to the SMTP server.
///
/// @return 0
#[strum(props(id = "103"))]
SmtpMessageSent(String),
/// The library-user should write a warning string to the log.
/// Passed to the callback given to dc_context_new().
///
/// This event should not be reported to the end-user using a popup or something like that.
///
/// @return 0
#[strum(props(id = "300"))]
Warning(String),
/// The library-user should report an error to the end-user.
/// Passed to the callback given to dc_context_new().
///
/// As most things are asynchronous, things may go wrong at any time and the user
/// should not be disturbed by a dialog or so. Instead, use a bubble or so.
///
/// However, for ongoing processes (eg. configure())
/// or for functions that are expected to fail (eg. dc_continue_key_transfer())
/// it might be better to delay showing these events until the function has really
/// failed (returned false). It should be sufficient to report only the _last_ error
/// in a messasge box then.
///
/// @return
#[strum(props(id = "400"))]
Error(String),
/// An action cannot be performed because there is no network available.
///
/// The library will typically try over after a some time
/// and when dc_maybe_network() is called.
///
/// Network errors should be reported to users in a non-disturbing way,
/// however, as network errors may come in a sequence,
/// it is not useful to raise each an every error to the user.
/// For this purpose, data1 is set to 1 if the error is probably worth reporting.
///
/// Moreover, if the UI detects that the device is offline,
/// it is probably more useful to report this to the user
/// instead of the string from data2.
///
/// @return 0
#[strum(props(id = "401"))]
ErrorNetwork(String),
/// An action cannot be performed because the user is not in the group.
/// Reported eg. after a call to
/// dc_set_chat_name(), dc_set_chat_profile_image(),
/// dc_add_contact_to_chat(), dc_remove_contact_from_chat(),
/// dc_send_text_msg() or another sending function.
///
/// @return 0
#[strum(props(id = "410"))]
ErrorSelfNotInGroup(String),
/// Messages or chats changed. One or more messages or chats changed for various
/// reasons in the database:
/// - Messages sent, received or removed
/// - Chats created, deleted or archived
/// - A draft has been set
///
/// @return 0
#[strum(props(id = "2000"))]
MsgsChanged { chat_id: u32, msg_id: u32 },
/// There is a fresh message. Typically, the user will show an notification
/// when receiving this message.
///
/// There is no extra #DC_EVENT_MSGS_CHANGED event send together with this event.
///
/// @return 0
#[strum(props(id = "2005"))]
IncomingMsg { chat_id: u32, msg_id: u32 },
/// A single message is sent successfully. State changed from DC_STATE_OUT_PENDING to
/// DC_STATE_OUT_DELIVERED, see dc_msg_get_state().
///
/// @return 0
#[strum(props(id = "2010"))]
MsgDelivered { chat_id: u32, msg_id: u32 },
/// A single message could not be sent. State changed from DC_STATE_OUT_PENDING or DC_STATE_OUT_DELIVERED to
/// DC_STATE_OUT_FAILED, see dc_msg_get_state().
///
/// @return 0
#[strum(props(id = "2012"))]
MsgFailed { chat_id: u32, msg_id: u32 },
/// A single message is read by the receiver. State changed from DC_STATE_OUT_DELIVERED to
/// DC_STATE_OUT_MDN_RCVD, see dc_msg_get_state().
///
/// @return 0
#[strum(props(id = "2015"))]
MsgRead { chat_id: u32, msg_id: u32 },
/// Chat changed. The name or the image of a chat group was changed or members were added or removed.
/// Or the verify state of a chat has changed.
/// See dc_set_chat_name(), dc_set_chat_profile_image(), dc_add_contact_to_chat()
/// and dc_remove_contact_from_chat().
///
/// @return 0
#[strum(props(id = "2020"))]
ChatModified(u32),
/// Contact(s) created, renamed, blocked or deleted.
///
/// @param data1 (int) If set, this is the contact_id of an added contact that should be selected.
/// @return 0
#[strum(props(id = "2030"))]
ContactsChanged(Option<u32>),
/// Location of one or more contact has changed.
///
/// @param data1 (u32) contact_id of the contact for which the location has changed.
/// If the locations of several contacts have been changed,
/// eg. after calling dc_delete_all_locations(), this parameter is set to `None`.
/// @return 0
#[strum(props(id = "2035"))]
LocationChanged(Option<u32>),
/// Inform about the configuration progress started by configure().
///
/// @param data1 (usize) 0=error, 1-999=progress in permille, 1000=success and done
/// @return 0
#[strum(props(id = "2041"))]
ConfigureProgress(usize),
/// Inform about the import/export progress started by dc_imex().
///
/// @param data1 (usize) 0=error, 1-999=progress in permille, 1000=success and done
/// @param data2 0
/// @return 0
#[strum(props(id = "2051"))]
ImexProgress(usize),
/// A file has been exported. A file has been written by dc_imex().
/// This event may be sent multiple times by a single call to dc_imex().
///
/// A typical purpose for a handler of this event may be to make the file public to some system
/// services.
///
/// @param data2 0
/// @return 0
#[strum(props(id = "2052"))]
ImexFileWritten(PathBuf),
/// Progress information of a secure-join handshake from the view of the inviter
/// (Alice, the person who shows the QR code).
///
/// These events are typically sent after a joiner has scanned the QR code
/// generated by dc_get_securejoin_qr().
///
/// @param data1 (int) ID of the contact that wants to join.
/// @param data2 (int) Progress as:
/// 300=vg-/vc-request received, typically shown as "bob@addr joins".
/// 600=vg-/vc-request-with-auth received, vg-member-added/vc-contact-confirm sent, typically shown as "bob@addr verified".
/// 800=vg-member-added-received received, shown as "bob@addr securely joined GROUP", only sent for the verified-group-protocol.
/// 1000=Protocol finished for this contact.
/// @return 0
#[strum(props(id = "2060"))]
SecurejoinInviterProgress { contact_id: u32, progress: usize },
/// Progress information of a secure-join handshake from the view of the joiner
/// (Bob, the person who scans the QR code).
/// The events are typically sent while dc_join_securejoin(), which
/// may take some time, is executed.
/// @param data1 (int) ID of the inviting contact.
/// @param data2 (int) Progress as:
/// 400=vg-/vc-request-with-auth sent, typically shown as "alice@addr verified, introducing myself."
/// (Bob has verified alice and waits until Alice does the same for him)
/// @return 0
#[strum(props(id = "2061"))]
SecurejoinJoinerProgress { contact_id: u32, progress: usize },
// the following events are functions that should be provided by the frontends
/// Requeste a localized string from the frontend.
/// @param data1 (int) ID of the string to request, one of the DC_STR_/// constants.
/// @param data2 (int) The count. If the requested string contains a placeholder for a numeric value,
/// the ui may use this value to return different strings on different plural forms.
/// @return (const char*) Null-terminated UTF-8 string.
/// The string will be free()'d by the core,
/// so it must be allocated using malloc() or a compatible function.
/// Return 0 if the ui cannot provide the requested string
/// the core will use a default string in english language then.
#[strum(props(id = "2091"))]
GetString { id: StockMessage, count: usize },
}

View File

@@ -1,6 +1,5 @@
use std::ffi::CString;
use std::net;
use std::ptr;
use std::sync::{
atomic::{AtomicBool, Ordering},
Arc, Condvar, Mutex, RwLock,
@@ -9,15 +8,10 @@ use std::time::{Duration, SystemTime};
use crate::constants::*;
use crate::context::Context;
use crate::dc_receive_imf::dc_receive_imf;
use crate::dc_loginparam::*;
use crate::dc_tools::CStringExt;
use crate::dc_tools::*;
use crate::events::Event;
use crate::job::{job_add, Action};
use crate::login_param::LoginParam;
use crate::message::{dc_rfc724_mid_exists, dc_update_msg_move_state, dc_update_server_uid};
use crate::oauth2::dc_get_oauth2_access_token;
use crate::param::Params;
use crate::types::*;
const DC_IMAP_SEEN: usize = 0x0001;
const DC_REGENERATE: usize = 0x01;
@@ -31,11 +25,15 @@ const PREFETCH_FLAGS: &str = "(UID ENVELOPE)";
const BODY_FLAGS: &str = "(FLAGS BODY.PEEK[])";
const FETCH_FLAGS: &str = "(FLAGS)";
#[derive(Debug)]
pub struct Imap {
config: Arc<RwLock<ImapConfig>>,
watch: Arc<(Mutex<bool>, Condvar)>,
get_config: dc_get_config_t,
set_config: dc_set_config_t,
precheck_imf: dc_precheck_imf_t,
receive_imf: dc_receive_imf_t,
session: Arc<Mutex<Option<Session>>>,
stream: Arc<RwLock<Option<net::TcpStream>>>,
connected: Arc<Mutex<bool>>,
@@ -43,7 +41,6 @@ pub struct Imap {
should_reconnect: AtomicBool,
}
#[derive(Debug)]
struct OAuth2 {
user: String,
access_token: String,
@@ -68,7 +65,6 @@ enum FolderMeaning {
Other,
}
#[derive(Debug)]
enum Client {
Secure(
imap::Client<native_tls::TlsStream<net::TcpStream>>,
@@ -77,13 +73,11 @@ enum Client {
Insecure(imap::Client<net::TcpStream>, net::TcpStream),
}
#[derive(Debug)]
enum Session {
Secure(imap::Session<native_tls::TlsStream<net::TcpStream>>),
Insecure(imap::Session<net::TcpStream>),
}
#[derive(Debug)]
enum IdleHandle<'a> {
Secure(imap::extensions::idle::Handle<'a, native_tls::TlsStream<net::TcpStream>>),
Insecure(imap::extensions::idle::Handle<'a, net::TcpStream>),
@@ -313,7 +307,6 @@ impl Session {
}
}
#[derive(Debug)]
struct ImapConfig {
pub addr: String,
pub imap_server: String,
@@ -353,12 +346,21 @@ impl Default for ImapConfig {
}
impl Imap {
pub fn new() -> Self {
pub fn new(
get_config: dc_get_config_t,
set_config: dc_set_config_t,
precheck_imf: dc_precheck_imf_t,
receive_imf: dc_receive_imf_t,
) -> Self {
Imap {
session: Arc::new(Mutex::new(None)),
stream: Arc::new(RwLock::new(None)),
config: Arc::new(RwLock::new(ImapConfig::default())),
watch: Arc::new((Mutex::new(false), Condvar::new())),
get_config,
set_config,
precheck_imf,
receive_imf,
connected: Arc::new(Mutex::new(false)),
should_reconnect: AtomicBool::new(false),
}
@@ -438,12 +440,14 @@ impl Imap {
let imap_server: &str = config.imap_server.as_ref();
let imap_port = config.imap_port;
emit_event!(
log_event!(
context,
Event::ErrorNetwork(format!(
"Could not connect to IMAP-server {}:{}. ({})",
imap_server, imap_port, err
))
Event::ERROR_NETWORK,
0,
"Could not connect to IMAP-server {}:{}. ({})",
imap_server,
imap_port,
err
);
return false;
@@ -459,10 +463,7 @@ impl Imap {
true
}
Err((err, _)) => {
emit_event!(
context,
Event::ErrorNetwork(format!("Cannot login ({})", err))
);
log_event!(context, Event::ERROR_NETWORK, 0, "Cannot login ({})", err);
self.unsetup_handle(context);
false
@@ -471,9 +472,12 @@ impl Imap {
}
fn unsetup_handle(&self, context: &Context) {
info!(context, "IMAP unsetup_handle starts");
info!(context, 0, "IMAP unsetup_handle starts");
info!(context, "IMAP unsetup_handle step 1 (closing down stream).");
info!(
context,
0, "IMAP unsetup_handle step 1 (closing down stream)."
);
let stream = self.stream.write().unwrap().take();
if let Some(stream) = stream {
if let Err(err) = stream.shutdown(net::Shutdown::Both) {
@@ -483,7 +487,7 @@ impl Imap {
info!(
context,
"IMAP unsetup_handle step 2 (acquiring session.lock)"
0, "IMAP unsetup_handle step 2 (acquiring session.lock)"
);
if let Some(mut session) = self.session.lock().unwrap().take() {
if let Err(err) = session.close() {
@@ -491,10 +495,10 @@ impl Imap {
}
}
info!(context, "IMAP unsetup_handle step 3 (clearing config).");
info!(context, 0, "IMAP unsetup_handle step 3 (clearing config).");
self.config.write().unwrap().selected_folder = None;
self.config.write().unwrap().selected_mailbox = None;
info!(context, "IMAP unsetup_handle step 4 (disconnected).",);
info!(context, 0, "IMAP unsetup_handle step 4 (disconnected).",);
}
fn free_connect_params(&self) {
@@ -512,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;
}
@@ -547,7 +551,7 @@ impl Imap {
Some(ref mut session) => match session.capabilities() {
Ok(caps) => {
if !context.sql.is_open() {
warn!(context, "IMAP-LOGIN as {} ok but ABORTING", lp.mail_user,);
warn!(context, 0, "IMAP-LOGIN as {} ok but ABORTING", lp.mail_user,);
(true, false, false)
} else {
let can_idle = caps.has_str("IDLE");
@@ -555,18 +559,19 @@ impl Imap {
let caps_list = caps
.iter()
.fold(String::new(), |s, c| s + &format!(" {:?}", c));
emit_event!(
log_event!(
context,
Event::ImapConnected(format!(
"IMAP-LOGIN as {}, capabilities: {}",
lp.mail_user, caps_list,
))
Event::IMAP_CONNECTED,
0,
"IMAP-LOGIN as {}, capabilities: {}",
lp.mail_user,
caps_list,
);
(false, can_idle, has_xlist)
}
}
Err(err) => {
info!(context, "CAPABILITY command error: {}", err);
info!(context, 0, "CAPABILITY command error: {}", err);
(true, false, false)
}
},
@@ -642,7 +647,7 @@ impl Imap {
// deselect existing folder, if needed (it's also done implicitly by SELECT, however, without EXPUNGE then)
if self.config.read().unwrap().selected_folder_needs_expunge {
if let Some(ref folder) = self.config.read().unwrap().selected_folder {
info!(context, "Expunge messages in \"{}\".", folder);
info!(context, 0, "Expunge messages in \"{}\".", folder);
// A CLOSE-SELECT is considerably faster than an EXPUNGE-SELECT, see
// https://tools.ietf.org/html/rfc3501#section-6.4.2
@@ -672,6 +677,7 @@ impl Imap {
Err(err) => {
info!(
context,
0,
"Cannot select folder: {}; {:?}.",
folder.as_ref(),
err
@@ -692,7 +698,7 @@ impl Imap {
fn get_config_last_seen_uid<S: AsRef<str>>(&self, context: &Context, folder: S) -> (u32, u32) {
let key = format!("imap.mailbox.{}", folder.as_ref());
if let Some(entry) = context.sql.get_config(context, &key) {
if let Some(entry) = (self.get_config)(context, &key) {
// the entry has the format `imap.mailbox.<folder>=<uidvalidity>:<lastseenuid>`
let mut parts = entry.split(':');
(
@@ -708,6 +714,7 @@ impl Imap {
if !self.is_connected() {
info!(
context,
0,
"Cannot fetch from \"{}\" - not connected.",
folder.as_ref()
);
@@ -718,6 +725,7 @@ impl Imap {
if self.select_folder(context, Some(&folder)) == 0 {
info!(
context,
0,
"Cannot select folder \"{}\" for fetching.",
folder.as_ref()
);
@@ -734,6 +742,7 @@ impl Imap {
if mailbox.uid_validity.is_none() {
error!(
context,
0,
"Cannot get UIDVALIDITY for folder \"{}\".",
folder.as_ref(),
);
@@ -745,7 +754,7 @@ impl Imap {
// first time this folder is selected or UIDVALIDITY has changed, init lastseenuid and save it to config
if mailbox.exists == 0 {
info!(context, "Folder \"{}\" is empty.", folder.as_ref());
info!(context, 0, "Folder \"{}\" is empty.", folder.as_ref());
// set lastseenuid=0 for empty folders.
// id we do not do this here, we'll miss the first message
@@ -764,6 +773,7 @@ impl Imap {
self.should_reconnect.store(true, Ordering::Relaxed);
info!(
context,
0,
"No result returned for folder \"{}\".",
folder.as_ref()
);
@@ -786,6 +796,7 @@ impl Imap {
self.set_config_last_seen_uid(context, &folder, uid_validity, last_seen_uid);
info!(
context,
0,
"lastseenuid initialized to {} for {}@{}",
last_seen_uid,
folder.as_ref(),
@@ -804,7 +815,7 @@ impl Imap {
match session.uid_fetch(set, PREFETCH_FLAGS) {
Ok(list) => list,
Err(err) => {
warn!(context, "failed to fetch uids: {}", err);
warn!(context, 0, "failed to fetch uids: {}", err);
return 0;
}
}
@@ -826,12 +837,13 @@ impl Imap {
if 0 == unsafe {
let message_id_c = CString::yolo(message_id);
precheck_imf(context, message_id_c.as_ptr(), folder.as_ref(), cur_uid)
(self.precheck_imf)(context, message_id_c.as_ptr(), folder.as_ref(), cur_uid)
} {
// check passed, go fetch the rest
if self.fetch_single_msg(context, &folder, cur_uid) == 0 {
info!(
context,
0,
"Read error for message {} from \"{}\", trying over later.",
message_id,
folder.as_ref()
@@ -843,6 +855,7 @@ impl Imap {
// check failed
info!(
context,
0,
"Skipping message {} from \"{}\" by precheck.",
message_id,
folder.as_ref(),
@@ -863,6 +876,7 @@ impl Imap {
if read_errors > 0 {
warn!(
context,
0,
"{} mails read from \"{}\" with {} errors.",
read_cnt,
folder.as_ref(),
@@ -871,6 +885,7 @@ impl Imap {
} else {
info!(
context,
0,
"{} mails read from \"{}\".",
read_cnt,
folder.as_ref()
@@ -890,7 +905,7 @@ impl Imap {
let key = format!("imap.mailbox.{}", folder.as_ref());
let val = format!("{}:{}", uidvalidity, lastseenuid);
context.sql.set_config(context, &key, Some(&val)).ok();
(self.set_config)(context, &key, Some(&val));
}
fn fetch_single_msg<S: AsRef<str>>(
@@ -915,6 +930,7 @@ impl Imap {
self.should_reconnect.store(true, Ordering::Relaxed);
warn!(
context,
0,
"Error on fetching message #{} from folder \"{}\"; retry={}; error={}.",
server_uid,
folder.as_ref(),
@@ -931,6 +947,7 @@ impl Imap {
if msgs.is_empty() {
warn!(
context,
0,
"Message #{} does not exist in folder \"{}\".",
server_uid,
folder.as_ref()
@@ -960,7 +977,7 @@ impl Imap {
if !is_deleted && msg.body().is_some() {
let body = msg.body().unwrap();
unsafe {
dc_receive_imf(
(self.receive_imf)(
context,
body.as_ptr() as *const libc::c_char,
body.len(),
@@ -984,7 +1001,7 @@ impl Imap {
let watch_folder = self.config.read().unwrap().watch_folder.clone();
if self.select_folder(context, watch_folder.as_ref()) == 0 {
warn!(context, "IMAP-IDLE not setup.",);
warn!(context, 0, "IMAP-IDLE not setup.",);
return self.fake_idle(context);
}
@@ -994,7 +1011,7 @@ impl Imap {
let (sender, receiver) = std::sync::mpsc::channel();
let v = self.watch.clone();
info!(context, "IMAP-IDLE SPAWNING");
info!(context, 0, "IMAP-IDLE SPAWNING");
std::thread::spawn(move || {
let &(ref lock, ref cvar) = &*v;
if let Some(ref mut session) = &mut *session.lock().unwrap() {
@@ -1029,15 +1046,18 @@ impl Imap {
let handle_res = |res| match res {
Ok(()) => {
info!(context, "IMAP-IDLE has data.");
info!(context, 0, "IMAP-IDLE has data.");
}
Err(err) => match err {
imap::error::Error::ConnectionLost => {
info!(context, "IMAP-IDLE wait cancelled, we will reconnect soon.");
info!(
context,
0, "IMAP-IDLE wait cancelled, we will reconnect soon."
);
self.should_reconnect.store(true, Ordering::Relaxed);
}
_ => {
warn!(context, "IMAP-IDLE returns unknown value: {}", err);
warn!(context, 0, "IMAP-IDLE returns unknown value: {}", err);
}
},
};
@@ -1053,7 +1073,7 @@ impl Imap {
if let Ok(res) = worker.as_ref().unwrap().try_recv() {
handle_res(res);
} else {
info!(context, "IMAP-IDLE interrupted");
info!(context, 0, "IMAP-IDLE interrupted");
}
drop(worker.take());
@@ -1071,7 +1091,7 @@ impl Imap {
let fake_idle_start_time = SystemTime::now();
let mut wait_long = false;
info!(context, "IMAP-fake-IDLEing...");
info!(context, 0, "IMAP-fake-IDLEing...");
let mut do_fake_idle = true;
while do_fake_idle {
@@ -1147,6 +1167,7 @@ impl Imap {
} else if folder.as_ref() == dest_folder.as_ref() {
info!(
context,
0,
"Skip moving message; message {}/{} is already in {}...",
folder.as_ref(),
uid,
@@ -1157,6 +1178,7 @@ impl Imap {
} else {
info!(
context,
0,
"Moving message {}/{} to {}...",
folder.as_ref(),
uid,
@@ -1166,6 +1188,7 @@ impl Imap {
if self.select_folder(context, Some(folder.as_ref())) == 0 {
warn!(
context,
0,
"Cannot select folder {} for moving message.",
folder.as_ref()
);
@@ -1179,6 +1202,7 @@ impl Imap {
Err(err) => {
info!(
context,
0,
"Cannot move message, fallback to COPY/DELETE {}/{} to {}: {}",
folder.as_ref(),
uid,
@@ -1199,7 +1223,7 @@ impl Imap {
Ok(_) => true,
Err(err) => {
eprintln!("error copy: {:?}", err);
info!(context, "Cannot copy message.",);
info!(context, 0, "Cannot copy message.",);
false
}
@@ -1210,7 +1234,7 @@ impl Imap {
if copied {
if self.add_flag(context, uid, "\\Deleted") == 0 {
warn!(context, "Cannot mark message as \"Deleted\".",);
warn!(context, 0, "Cannot mark message as \"Deleted\".",);
}
self.config.write().unwrap().selected_folder_needs_expunge = true;
res = DC_SUCCESS;
@@ -1247,7 +1271,7 @@ impl Imap {
Err(err) => {
warn!(
context,
"IMAP failed to store: ({}, {}) {:?}", set, query, err
0, "IMAP failed to store: ({}, {}) {:?}", set, query, err
);
}
}
@@ -1270,6 +1294,7 @@ impl Imap {
} else if self.is_connected() {
info!(
context,
0,
"Marking message {}/{} as seen...",
folder.as_ref(),
uid,
@@ -1278,11 +1303,12 @@ impl Imap {
if self.select_folder(context, Some(folder.as_ref())) == 0 {
warn!(
context,
0,
"Cannot select folder {} for setting SEEN flag.",
folder.as_ref(),
);
} else if self.add_flag(context, uid, "\\Seen") == 0 {
warn!(context, "Cannot mark message as seen.",);
warn!(context, 0, "Cannot mark message as seen.",);
} else {
res = DC_SUCCESS
}
@@ -1309,6 +1335,7 @@ impl Imap {
} else if self.is_connected() {
info!(
context,
0,
"Marking message {}/{} as $MDNSent...",
folder.as_ref(),
uid,
@@ -1317,6 +1344,7 @@ impl Imap {
if self.select_folder(context, Some(folder.as_ref())) == 0 {
warn!(
context,
0,
"Cannot select folder {} for setting $MDNSent flag.",
folder.as_ref()
);
@@ -1383,16 +1411,16 @@ impl Imap {
};
if res == DC_SUCCESS {
info!(context, "$MDNSent just set and MDN will be sent.");
info!(context, 0, "$MDNSent just set and MDN will be sent.");
} else {
info!(context, "$MDNSent already set and MDN already sent.");
info!(context, 0, "$MDNSent already set and MDN already sent.");
}
}
} else {
res = DC_SUCCESS;
info!(
context,
"Cannot store $MDNSent flags, risk sending duplicate MDN.",
0, "Cannot store $MDNSent flags, risk sending duplicate MDN.",
);
}
}
@@ -1423,6 +1451,7 @@ impl Imap {
} else {
info!(
context,
0,
"Marking message \"{}\", {}/{} for deletion...",
message_id.as_ref(),
folder.as_ref(),
@@ -1432,6 +1461,7 @@ impl Imap {
if self.select_folder(context, Some(&folder)) == 0 {
warn!(
context,
0,
"Cannot select folder {} for deleting message.",
folder.as_ref()
);
@@ -1452,6 +1482,7 @@ impl Imap {
{
warn!(
context,
0,
"Cannot delete on IMAP, {}/{} does not match {}.",
folder.as_ref(),
server_uid,
@@ -1465,6 +1496,7 @@ impl Imap {
warn!(
context,
0,
"Cannot delete on IMAP, {}/{} not found.",
folder.as_ref(),
server_uid,
@@ -1476,7 +1508,7 @@ impl Imap {
// mark the message for deletion
if self.add_flag(context, *server_uid, "\\Deleted") == 0 {
warn!(context, "Cannot mark message as \"Deleted\".");
warn!(context, 0, "Cannot mark message as \"Deleted\".");
} else {
self.config.write().unwrap().selected_folder_needs_expunge = true;
success = true
@@ -1496,7 +1528,7 @@ impl Imap {
return;
}
info!(context, "Configuring IMAP-folders.");
info!(context, 0, "Configuring IMAP-folders.");
let folders = self.list_folders(context).unwrap();
let delimiter = self.config.read().unwrap().imap_delimiter;
@@ -1515,19 +1547,21 @@ impl Imap {
});
if mvbox_folder.is_none() && 0 != (flags as usize & DC_CREATE_MVBOX) {
info!(context, "Creating MVBOX-folder \"DeltaChat\"...",);
info!(context, 0, "Creating MVBOX-folder \"DeltaChat\"...",);
if let Some(ref mut session) = &mut *self.session.lock().unwrap() {
match session.create("DeltaChat") {
Ok(_) => {
mvbox_folder = Some("DeltaChat".into());
info!(context, "MVBOX-folder created.",);
info!(context, 0, "MVBOX-folder created.",);
}
Err(err) => {
warn!(
context,
"Cannot create MVBOX-folder, using trying INBOX subfolder. ({})", err
0,
"Cannot create MVBOX-folder, using trying INBOX subfolder. ({})",
err
);
match session.create(&fallback_folder) {
@@ -1535,11 +1569,11 @@ impl Imap {
mvbox_folder = Some(fallback_folder);
info!(
context,
"MVBOX-folder created as INBOX subfolder. ({})", err
0, "MVBOX-folder created as INBOX subfolder. ({})", err
);
}
Err(err) => {
warn!(context, "Cannot create MVBOX-folder. ({})", err);
warn!(context, 0, "Cannot create MVBOX-folder. ({})", err);
}
}
}
@@ -1585,13 +1619,13 @@ impl Imap {
match session.list(Some(""), Some("*")) {
Ok(list) => {
if list.is_empty() {
warn!(context, "Folder list is empty.",);
warn!(context, 0, "Folder list is empty.",);
}
Some(list)
}
Err(err) => {
eprintln!("list error: {:?}", err);
warn!(context, "Cannot get folder list.",);
warn!(context, 0, "Cannot get folder list.",);
None
}
@@ -1645,53 +1679,3 @@ fn get_folder_meaning(folder_name: &imap::types::Name) -> FolderMeaning {
_ => res,
}
}
unsafe fn precheck_imf(
context: &Context,
rfc724_mid: *const libc::c_char,
server_folder: &str,
server_uid: u32,
) -> libc::c_int {
let mut rfc724_mid_exists: libc::c_int = 0i32;
let msg_id: u32;
let mut old_server_folder: *mut libc::c_char = ptr::null_mut();
let mut old_server_uid: u32 = 0i32 as u32;
let mut mark_seen: libc::c_int = 0i32;
msg_id = dc_rfc724_mid_exists(
context,
rfc724_mid,
&mut old_server_folder,
&mut old_server_uid,
);
if msg_id != 0i32 as libc::c_uint {
rfc724_mid_exists = 1i32;
if *old_server_folder.offset(0isize) as libc::c_int == 0i32
&& old_server_uid == 0i32 as libc::c_uint
{
info!(context, "[move] detected bbc-self {}", as_str(rfc724_mid),);
mark_seen = 1i32
} else if as_str(old_server_folder) != server_folder {
info!(
context,
"[move] detected moved message {}",
as_str(rfc724_mid),
);
dc_update_msg_move_state(context, rfc724_mid, MoveState::Stay);
}
if as_str(old_server_folder) != server_folder || old_server_uid != server_uid {
dc_update_server_uid(context, rfc724_mid, server_folder, server_uid);
}
context.do_heuristics_moves(server_folder, msg_id);
if 0 != mark_seen {
job_add(
context,
Action::MarkseenMsgOnImap,
msg_id as libc::c_int,
Params::new(),
0,
);
}
}
libc::free(old_server_folder as *mut libc::c_void);
rfc724_mid_exists
}

View File

@@ -3,7 +3,6 @@ use std::ptr;
use std::time::Duration;
use deltachat_derive::{FromSql, ToSql};
use mmime::clist::*;
use rand::{thread_rng, Rng};
use crate::chat;
@@ -11,37 +10,28 @@ 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::events::Event;
use crate::imap::*;
use crate::location;
use crate::login_param::LoginParam;
use crate::message::*;
use crate::param::*;
use crate::sql;
use crate::types::*;
use crate::x::*;
/// Thread IDs
#[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,
@@ -60,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,
@@ -131,7 +113,7 @@ impl Job {
fn do_DC_JOB_SEND(&mut self, context: &Context) {
/* 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 {
@@ -157,10 +139,12 @@ impl Job {
/* 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 && !dc_msg_exists(context, self.foreign_id) {
if 0 != self.foreign_id
&& 0 == unsafe { dc_msg_exists(context, self.foreign_id) }
{
warn!(
context,
"Message {} for job {} does not exist", self.foreign_id, self.job_id,
0, "Message {} for job {} does not exist", self.foreign_id, self.job_id,
);
return;
};
@@ -183,20 +167,22 @@ impl Job {
);
let chat_id: i32 = context
.sql
.query_get_value(
.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::MsgDelivered {
chat_id: chat_id as u32,
msg_id: self.foreign_id,
});
context.call_cb(
Event::MSG_DELIVERED,
chat_id as uintptr_t,
self.foreign_id as uintptr_t,
);
}
}
} else {
warn!(context, "Missing recipients for job {}", self.job_id,);
warn!(context, 0, "Missing recipients for job {}", self.job_id,);
}
}
}
@@ -276,7 +262,7 @@ impl Job {
if dc_rfc724_mid_cnt(context, msg.rfc724_mid) != 1 {
info!(
context,
"The message is deleted from the server when all parts are deleted.",
0, "The message is deleted from the server when all parts are deleted.",
);
delete_from_server = 0i32
}
@@ -439,17 +425,18 @@ pub fn perform_imap_fetch(context: &Context) {
.unwrap_or_else(|| 1)
== 0
{
info!(context, "INBOX-watch disabled.",);
info!(context, 0, "INBOX-watch disabled.",);
return;
}
info!(context, "INBOX-fetch started...",);
info!(context, 0, "INBOX-fetch started...",);
inbox.fetch(context);
if inbox.should_reconnect() {
info!(context, "INBOX-fetch aborted, starting over...",);
info!(context, 0, "INBOX-fetch aborted, starting over...",);
inbox.fetch(context);
}
info!(
context,
0,
"INBOX-fetch done in {:.4} ms.",
start.elapsed().as_nanos() as f64 / 1000.0,
);
@@ -463,13 +450,13 @@ pub fn perform_imap_idle(context: &Context) {
if *context.perform_inbox_jobs_needed.clone().read().unwrap() {
info!(
context,
"INBOX-IDLE will not be started because of waiting jobs."
0, "INBOX-IDLE will not be started because of waiting jobs."
);
return;
}
info!(context, "INBOX-IDLE started...");
info!(context, 0, "INBOX-IDLE started...");
inbox.idle(context);
info!(context, "INBOX-IDLE ended.");
info!(context, 0, "INBOX-IDLE ended.");
}
pub fn perform_mvbox_fetch(context: &Context) {
@@ -546,16 +533,16 @@ pub fn perform_smtp_jobs(context: &Context) {
state.perform_jobs_needed = 0;
if state.suspended {
info!(context, "SMTP-jobs suspended.",);
info!(context, 0, "SMTP-jobs suspended.",);
return;
}
state.doing_jobs = true;
probe_smtp_network
};
info!(context, "SMTP-jobs started...",);
info!(context, 0, "SMTP-jobs started...",);
job_perform(context, Thread::Smtp, probe_smtp_network);
info!(context, "SMTP-jobs ended.");
info!(context, 0, "SMTP-jobs ended.");
{
let &(ref lock, _) = &*context.smtp_state.clone();
@@ -566,7 +553,7 @@ pub fn perform_smtp_jobs(context: &Context) {
}
pub fn perform_smtp_idle(context: &Context) {
info!(context, "SMTP-idle started...",);
info!(context, 0, "SMTP-idle started...",);
{
let &(ref lock, ref cvar) = &*context.smtp_state.clone();
let mut state = lock.lock().unwrap();
@@ -574,7 +561,7 @@ pub fn perform_smtp_idle(context: &Context) {
if state.perform_jobs_needed == 1 {
info!(
context,
"SMTP-idle will not be started because of waiting jobs.",
0, "SMTP-idle will not be started because of waiting jobs.",
);
} else {
let dur = get_next_wakeup_time(context, Thread::Smtp);
@@ -592,16 +579,17 @@ pub fn perform_smtp_idle(context: &Context) {
}
}
info!(context, "SMTP-idle ended.",);
info!(context, 0, "SMTP-idle ended.",);
}
fn get_next_wakeup_time(context: &Context, thread: Thread) -> Duration {
let t: i64 = context
.sql
.query_get_value(
.query_row_col(
context,
"SELECT MIN(desired_timestamp) FROM jobs WHERE thread=?;",
params![thread],
0,
)
.unwrap_or_default();
@@ -642,7 +630,7 @@ pub fn job_action_exists(context: &Context, action: Action) -> bool {
/* special case for DC_JOB_SEND_MSG_TO_SMTP */
#[allow(non_snake_case)]
pub unsafe fn job_send_msg(context: &Context, msg_id: u32) -> libc::c_int {
pub unsafe fn job_send_msg(context: &Context, msg_id: uint32_t) -> libc::c_int {
let mut success = 0;
/* load message data */
@@ -650,7 +638,7 @@ pub unsafe fn job_send_msg(context: &Context, msg_id: u32) -> libc::c_int {
if mimefactory.is_err() || mimefactory.as_ref().unwrap().from_addr.is_null() {
warn!(
context,
"Cannot load data to send, maybe the message is deleted in between.",
0, "Cannot load data to send, maybe the message is deleted in between.",
);
} else {
let mut mimefactory = mimefactory.unwrap();
@@ -675,12 +663,12 @@ pub unsafe fn job_send_msg(context: &Context, msg_id: u32) -> libc::c_int {
mimefactory.msg.param.set_int(Param::Height, height as i32);
}
}
dc_msg_save_param_to_disk(context, &mut mimefactory.msg);
dc_msg_save_param_to_disk(&mut mimefactory.msg);
}
}
}
/* create message */
if !dc_mimefactory_render(context, &mut mimefactory) {
if 0 == dc_mimefactory_render(&mut mimefactory) {
dc_set_msg_failed(context, msg_id, as_opt_str(mimefactory.error));
} else if 0
!= mimefactory
@@ -692,6 +680,7 @@ pub unsafe fn job_send_msg(context: &Context, msg_id: u32) -> libc::c_int {
{
warn!(
context,
0,
"e2e encryption unavailable {} - {:?}",
msg_id,
mimefactory.msg.param.get_int(Param::GuranteeE2ee),
@@ -703,7 +692,9 @@ pub unsafe fn job_send_msg(context: &Context, msg_id: u32) -> 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,
@@ -722,7 +713,7 @@ pub unsafe fn job_send_msg(context: &Context, msg_id: u32) -> libc::c_int {
if let Err(err) =
location::set_kml_sent_timestamp(context, mimefactory.msg.chat_id, time())
{
error!(context, "Failed to set kml sent_timestamp: {:?}", err);
error!(context, 0, "Failed to set kml sent_timestamp: {:?}", err);
}
if !mimefactory.msg.hidden {
if let Err(err) = location::set_msg_location_id(
@@ -730,7 +721,7 @@ pub unsafe fn job_send_msg(context: &Context, msg_id: u32) -> libc::c_int {
mimefactory.msg.id,
mimefactory.out_last_added_location_id,
) {
error!(context, "Failed to set msg_location_id: {:?}", err);
error!(context, 0, "Failed to set msg_location_id: {:?}", err);
}
}
}
@@ -743,7 +734,7 @@ pub unsafe fn job_send_msg(context: &Context, msg_id: u32) -> libc::c_int {
== 0
{
mimefactory.msg.param.set_int(Param::GuranteeE2ee, 1);
dc_msg_save_param_to_disk(context, &mut mimefactory.msg);
dc_msg_save_param_to_disk(&mut mimefactory.msg);
}
success = add_smtp_job(context, Action::SendMsgToSmtp, &mut mimefactory);
}
@@ -753,14 +744,14 @@ pub unsafe fn job_send_msg(context: &Context, msg_id: u32) -> libc::c_int {
}
pub fn perform_imap_jobs(context: &Context) {
info!(context, "dc_perform_imap_jobs starting.",);
info!(context, 0, "dc_perform_imap_jobs starting.",);
let probe_imap_network = *context.probe_imap_network.clone().read().unwrap();
*context.probe_imap_network.write().unwrap() = false;
*context.perform_inbox_jobs_needed.write().unwrap() = false;
job_perform(context, Thread::Imap, probe_imap_network);
info!(context, "dc_perform_imap_jobs ended.",);
info!(context, 0, "dc_perform_imap_jobs ended.",);
}
fn job_perform(context: &Context, thread: Thread, probe_network: bool) {
@@ -808,13 +799,14 @@ fn job_perform(context: &Context, thread: Thread, probe_network: bool) {
match jobs {
Ok(ref _res) => {}
Err(ref err) => {
info!(context, "query failed: {:?}", err);
info!(context, 0, "query failed: {:?}", err);
}
}
for mut job in jobs.unwrap_or_default() {
info!(
context,
0,
"{}-job #{}, action {} started...",
if thread == Thread::Imap {
"INBOX"
@@ -852,9 +844,6 @@ fn job_perform(context: &Context, thread: Thread, probe_network: bool) {
job.try_again = 0;
match job.action {
Action::Unknown => {
warn!(context, "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),
@@ -897,6 +886,7 @@ fn job_perform(context: &Context, thread: Thread, probe_network: bool) {
// just try over next loop unconditionally, the ui typically interrupts idle when the file (video) is ready
info!(
context,
0,
"{}-job #{} not yet ready and will be delayed.",
if thread == Thread::Imap {
"INBOX"
@@ -914,6 +904,7 @@ fn job_perform(context: &Context, thread: Thread, probe_network: bool) {
job.update(context);
info!(
context,
0,
"{}-job #{} not succeeded on try #{}, retry in ADD_TIME+{} (in {} seconds).",
if thread == Thread::Imap {
"INBOX"
@@ -988,9 +979,9 @@ fn connect_to_inbox(context: &Context, inbox: &Imap) -> libc::c_int {
ret_connected
}
fn send_mdn(context: &Context, msg_id: u32) {
fn send_mdn(context: &Context, msg_id: uint32_t) {
if let Ok(mut mimefactory) = unsafe { dc_mimefactory_load_mdn(context, msg_id) } {
if unsafe { dc_mimefactory_render(context, &mut mimefactory) } {
if 0 != unsafe { dc_mimefactory_render(&mut mimefactory) } {
add_smtp_job(context, Action::SendMdn, &mut mimefactory);
}
}
@@ -1012,6 +1003,7 @@ fn add_smtp_job(context: &Context, action: Action, mimefactory: &dc_mimefactory_
if pathNfilename.is_null() {
error!(
context,
0,
"Could not find free file name for message with ID <{}>.",
to_string(mimefactory.rfc724_mid),
);
@@ -1027,6 +1019,7 @@ fn add_smtp_job(context: &Context, action: Action, mimefactory: &dc_mimefactory_
{
error!(
context,
0,
"Could not write message <{}> to \"{}\".",
to_string(mimefactory.rfc724_mid),
as_str(pathNfilename),
@@ -1069,11 +1062,6 @@ pub fn job_add(
param: Params,
delay_seconds: i64,
) {
if action == Action::Unknown {
error!(context, "Invalid action passed to job_add");
return;
}
let timestamp = time();
let thread: Thread = action.into();
@@ -1094,12 +1082,11 @@ pub fn job_add(
match thread {
Thread::Imap => interrupt_imap_idle(context),
Thread::Smtp => interrupt_smtp_idle(context),
Thread::Unknown => {}
}
}
pub fn interrupt_smtp_idle(context: &Context) {
info!(context, "Interrupting SMTP-idle...",);
info!(context, 0, "Interrupting SMTP-idle...",);
let &(ref lock, ref cvar) = &*context.smtp_state.clone();
let mut state = lock.lock().unwrap();
@@ -1110,7 +1097,7 @@ pub fn interrupt_smtp_idle(context: &Context) {
}
pub fn interrupt_imap_idle(context: &Context) {
info!(context, "Interrupting IMAP-IDLE...",);
info!(context, 0, "Interrupting IMAP-IDLE...",);
*context.perform_inbox_jobs_needed.write().unwrap() = true;
context.inbox.read().unwrap().interrupt_idle();

View File

@@ -4,7 +4,6 @@ use crate::configure::*;
use crate::context::Context;
use crate::imap::Imap;
#[derive(Debug)]
pub struct JobThread {
pub name: &'static str,
pub folder_config_name: &'static str,
@@ -31,7 +30,7 @@ impl JobThread {
}
pub fn suspend(&self, context: &Context) {
info!(context, "Suspending {}-thread.", self.name,);
info!(context, 0, "Suspending {}-thread.", self.name,);
{
self.state.0.lock().unwrap().suspended = true;
}
@@ -46,7 +45,7 @@ impl JobThread {
}
pub fn unsuspend(&self, context: &Context) {
info!(context, "Unsuspending {}-thread.", self.name);
info!(context, 0, "Unsuspending {}-thread.", self.name);
let &(ref lock, ref cvar) = &*self.state.clone();
let mut state = lock.lock().unwrap();
@@ -61,7 +60,7 @@ impl JobThread {
self.state.0.lock().unwrap().jobs_needed = 1;
}
info!(context, "Interrupting {}-IDLE...", self.name);
info!(context, 0, "Interrupting {}-IDLE...", self.name);
self.imap.interrupt_idle();
@@ -87,15 +86,16 @@ impl JobThread {
if use_network {
let start = std::time::Instant::now();
if self.connect_to_imap(context) {
info!(context, "{}-fetch started...", self.name);
info!(context, 0, "{}-fetch started...", self.name);
self.imap.fetch(context);
if self.imap.should_reconnect() {
info!(context, "{}-fetch aborted, starting over...", self.name,);
info!(context, 0, "{}-fetch aborted, starting over...", self.name,);
self.imap.fetch(context);
}
info!(
context,
0,
"{}-fetch done in {:.3} ms.",
self.name,
start.elapsed().as_millis(),
@@ -142,6 +142,7 @@ impl JobThread {
if 0 != state.jobs_needed {
info!(
context,
0,
"{}-IDLE will not be started as it was interrupted while not ideling.",
self.name,
);
@@ -171,9 +172,9 @@ impl JobThread {
}
self.connect_to_imap(context);
info!(context, "{}-IDLE started...", self.name,);
info!(context, 0, "{}-IDLE started...", self.name,);
self.imap.idle(context);
info!(context, "{}-IDLE ended.", self.name);
info!(context, 0, "{}-IDLE ended.", self.name);
self.state.0.lock().unwrap().using_handle = false;
}

View File

@@ -1,6 +1,7 @@
use std::collections::BTreeMap;
use std::ffi::{CStr, CString};
use std::io::Cursor;
use std::path::Path;
use std::slice;
use libc;
use pgp::composed::{Deserializable, SignedPublicKey, SignedSecretKey};
@@ -11,6 +12,7 @@ use crate::constants::*;
use crate::context::Context;
use crate::dc_tools::*;
use crate::sql::{self, Sql};
use crate::x::*;
#[derive(Debug, PartialEq, Eq, Clone)]
pub enum Key {
@@ -104,6 +106,15 @@ impl Key {
}
}
pub fn from_binary(data: *const u8, len: libc::c_int, key_type: KeyType) -> Option<Self> {
if data.is_null() || len == 0 {
return None;
}
let bytes = unsafe { slice::from_raw_parts(data, len as usize) };
Self::from_slice(bytes, key_type)
}
pub fn from_armored_string(
data: &str,
key_type: KeyType,
@@ -141,10 +152,11 @@ impl Key {
) -> Option<Self> {
let addr = self_addr.as_ref();
sql.query_get_value(
sql.query_row_col(
context,
"SELECT public_key FROM keypairs WHERE addr=? AND is_default=1;",
&[addr],
0,
)
.and_then(|blob: Vec<u8>| Self::from_slice(&blob, KeyType::Public))
}
@@ -154,10 +166,11 @@ impl Key {
self_addr: impl AsRef<str>,
sql: &Sql,
) -> Option<Self> {
sql.query_get_value(
sql.query_row_col(
context,
"SELECT private_key FROM keypairs WHERE addr=? AND is_default=1;",
&[self_addr.as_ref()],
0,
)
.and_then(|blob: Vec<u8>| Self::from_slice(&blob, KeyType::Private))
}
@@ -215,15 +228,30 @@ impl Key {
.expect("failed to serialize key")
}
pub fn write_asc_to_file(&self, file: impl AsRef<Path>, context: &Context) -> bool {
let file_content = self.to_asc(None).into_bytes();
if dc_write_file_safe(context, &file, &file_content) {
return true;
} else {
error!(context, "Cannot write key to {}", file.as_ref().display());
pub fn write_asc_to_file(&self, file: *const libc::c_char, context: &Context) -> bool {
if file.is_null() {
return false;
}
let file_content = self.to_asc(None);
let file_content_c = CString::new(file_content).unwrap();
let success = if 0
== unsafe {
dc_write_file(
context,
file,
file_content_c.as_ptr() as *const libc::c_void,
file_content_c.as_bytes().len(),
)
} {
error!(context, 0, "Cannot write key to {}", to_string(file));
false
} else {
true
};
success
}
pub fn fingerprint(&self) -> String {
@@ -233,11 +261,23 @@ impl Key {
}
}
pub fn fingerprint_c(&self) -> *mut libc::c_char {
let res = CString::new(self.fingerprint()).unwrap();
unsafe { strdup(res.as_ptr()) }
}
pub fn formatted_fingerprint(&self) -> String {
let rawhex = self.fingerprint();
dc_format_fingerprint(&rawhex)
}
pub fn formatted_fingerprint_c(&self) -> *mut libc::c_char {
let res = CString::new(self.formatted_fingerprint()).unwrap();
unsafe { strdup(res.as_ptr()) }
}
pub fn split_key(&self) -> Option<Key> {
match self {
Key::Public(_) => None,
@@ -283,6 +323,14 @@ pub fn dc_format_fingerprint(fingerprint: &str) -> String {
res
}
pub fn dc_format_fingerprint_c(fp: *const libc::c_char) -> *mut libc::c_char {
let input = unsafe { CStr::from_ptr(fp).to_str().unwrap() };
let res = dc_format_fingerprint(input);
let res_c = CString::new(res).unwrap();
unsafe { strdup(res_c.as_ptr()) }
}
/// Bring a human-readable or otherwise formatted fingerprint back to the 40-characters-uppercase-hex format.
pub fn dc_normalize_fingerprint(fp: &str) -> String {
fp.to_uppercase()
@@ -291,6 +339,14 @@ pub fn dc_normalize_fingerprint(fp: &str) -> String {
.collect()
}
pub fn dc_normalize_fingerprint_c(fp: *const libc::c_char) -> *mut libc::c_char {
let input = unsafe { CStr::from_ptr(fp).to_str().unwrap() };
let res = dc_normalize_fingerprint(input);
let res_c = CString::new(res).unwrap();
unsafe { strdup(res_c.as_ptr()) }
}
#[cfg(test)]
mod tests {
use super::*;
@@ -393,27 +449,6 @@ i8pcjGO+IZffvyZJVRWfVooBJmWWbPB1pueo3tx8w3+fcuzpxz+RLFKaPyqXO+dD
assert_eq!(private_key, private_key2);
}
#[test]
fn test_from_slice_bad_data() {
let mut bad_data: [u8; 4096] = [0; 4096];
for i in 0..4096 {
bad_data[i] = (i & 0xff) as u8;
}
for j in 0..(4096 / 40) {
let bad_key = Key::from_slice(
&bad_data[j..j + 4096 / 2 + j],
if 0 != j & 1 {
KeyType::Public
} else {
KeyType::Private
},
);
assert!(bad_key.is_none());
}
}
#[test]
#[ignore] // is too expensive
fn test_ascii_roundtrip() {

View File

@@ -33,10 +33,11 @@ impl<'a> Keyring<'a> {
self_addr: impl AsRef<str>,
sql: &Sql,
) -> bool {
sql.query_get_value(
sql.query_row_col(
context,
"SELECT private_key FROM keypairs ORDER BY addr=? DESC, is_default DESC;",
&[self_addr.as_ref()],
0,
)
.and_then(|blob: Vec<u8>| Key::from_slice(&blob, KeyType::Private))
.map(|key| self.add_owned(key))

View File

@@ -1,4 +1,4 @@
#![deny(clippy::correctness, missing_debug_implementations)]
#![deny(clippy::correctness)]
// TODO: make all of these errors, such that clippy actually passes.
#![warn(clippy::all, clippy::perf, clippy::not_unsafe_ptr_arg_deref)]
// This is nice, but for now just annoying.
@@ -16,19 +16,12 @@ extern crate rusqlite;
extern crate strum;
#[macro_use]
extern crate strum_macros;
#[macro_use]
extern crate jetscii;
#[macro_use]
extern crate debug_stub_derive;
#[macro_use]
mod log;
#[macro_use]
pub mod error;
pub(crate) mod events;
pub use events::*;
mod aheader;
pub mod chat;
pub mod chatlist;
@@ -54,20 +47,21 @@ pub mod qr;
mod smtp;
pub mod sql;
mod stock;
pub mod types;
pub mod x;
pub mod dc_array;
mod dc_dehtml;
pub mod dc_imex;
mod dc_loginparam;
mod dc_mimefactory;
pub mod dc_mimeparser;
pub mod dc_receive_imf;
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

@@ -3,16 +3,17 @@ use quick_xml;
use quick_xml::events::{BytesEnd, BytesStart, BytesText};
use crate::chat;
use crate::constants::Event;
use crate::constants::*;
use crate::context::*;
use crate::dc_tools::*;
use crate::error::Error;
use crate::events::Event;
use crate::job::*;
use crate::message::*;
use crate::param::*;
use crate::sql;
use crate::stock::StockMessage;
use crate::types::*;
// location handling
#[derive(Debug, Clone, Default)]
@@ -83,6 +84,7 @@ impl Kml {
Err(e) => {
error!(
context,
0,
"Location parsing: Error at position {}: {:?}",
reader.buffer_position(),
e
@@ -214,7 +216,7 @@ pub fn send_locations_to_chat(context: &Context, chat_id: u32, seconds: i64) {
.is_ok()
{
if 0 != seconds && !is_sending_locations_before {
msg = dc_msg_new(Viewtype::Text);
msg = dc_msg_new(context, Viewtype::Text);
msg.text =
Some(context.stock_system_msg(StockMessage::MsgLocationEnabled, "", "", 0));
msg.param.set_int(Param::Cmd, 8);
@@ -224,7 +226,11 @@ pub fn send_locations_to_chat(context: &Context, chat_id: u32, seconds: i64) {
context.stock_system_msg(StockMessage::MsgLocationDisabled, "", "", 0);
chat::add_device_msg(context, chat_id, stock_str);
}
context.call_cb(Event::ChatModified(chat_id));
context.call_cb(
Event::CHAT_MODIFIED,
chat_id as uintptr_t,
0i32 as uintptr_t,
);
if 0 != seconds {
schedule_MAYBE_SEND_LOCATIONS(context, 0i32);
job_add(
@@ -260,16 +266,16 @@ pub fn set(context: &Context, latitude: f64, longitude: f64, accuracy: f64) -> l
if latitude == 0.0 && longitude == 0.0 {
return 1;
}
let mut continue_streaming = false;
if let Ok(chats) = context.sql.query_map(
context.sql.query_map(
"SELECT id FROM chats WHERE locations_send_until>?;",
params![time()],
|row| row.get::<_, i32>(0),
|chats| chats.collect::<Result<Vec<_>, _>>().map_err(Into::into),
) {
for chat_id in chats {
if let Err(err) = context.sql.execute(
params![time()], |row| row.get::<_, i32>(0),
|chats| {
let mut continue_streaming = false;
for chat in chats {
let chat_id = chat?;
context.sql.execute(
"INSERT INTO locations \
(latitude, longitude, accuracy, timestamp, chat_id, from_id) VALUES (?,?,?,?,?,?);",
params![
@@ -280,19 +286,16 @@ pub fn set(context: &Context, latitude: f64, longitude: f64, accuracy: f64) -> l
chat_id,
1,
]
) {
warn!(context, "failed to store location {:?}", err);
} else {
)?;
continue_streaming = true;
}
if continue_streaming {
context.call_cb(Event::LOCATION_CHANGED, 1, 0);
};
schedule_MAYBE_SEND_LOCATIONS(context, 0);
Ok(continue_streaming as libc::c_int)
}
if continue_streaming {
context.call_cb(Event::LocationChanged(Some(1)));
};
schedule_MAYBE_SEND_LOCATIONS(context, 0);
}
continue_streaming as libc::c_int
).unwrap_or_default()
}
pub fn get_range(
@@ -364,7 +367,7 @@ fn is_marker(txt: &str) -> bool {
pub fn delete_all(context: &Context) -> Result<(), Error> {
sql::execute(context, &context.sql, "DELETE FROM locations;", params![])?;
context.call_cb(Event::LocationChanged(None));
context.call_cb(Event::LOCATION_CHANGED, 0, 0);
Ok(())
}
@@ -543,81 +546,76 @@ pub fn job_do_DC_JOB_MAYBE_SEND_LOCATIONS(context: &Context, _job: &Job) {
let mut continue_streaming: libc::c_int = 1;
info!(
context,
" ----------------- MAYBE_SEND_LOCATIONS -------------- ",
0, " ----------------- MAYBE_SEND_LOCATIONS -------------- ",
);
if let Ok(rows) = context.sql.query_map(
"SELECT id, locations_send_begin, locations_last_sent \
FROM chats \
WHERE locations_send_until>?;",
params![now],
|row| {
let chat_id: i32 = row.get(0)?;
let locations_send_begin: i64 = row.get(1)?;
let locations_last_sent: i64 = row.get(2)?;
continue_streaming = 1;
context
.sql
.query_map(
"SELECT id, locations_send_begin, locations_last_sent \
FROM chats \
WHERE locations_send_until>?;",
params![now],
|row| {
let chat_id: i32 = row.get(0)?;
let locations_send_begin: i64 = row.get(1)?;
let locations_last_sent: i64 = row.get(2)?;
continue_streaming = 1;
// be a bit tolerant as the timer may not align exactly with time(NULL)
if now - locations_last_sent < (60 - 3) {
Ok(None)
} else {
Ok(Some((chat_id, locations_send_begin, locations_last_sent)))
}
},
|rows| {
rows.filter_map(|v| v.transpose())
.collect::<Result<Vec<_>, _>>()
.map_err(Into::into)
},
) {
let msgs = context
.sql
.prepare(
"SELECT id \
FROM locations \
WHERE from_id=? \
AND timestamp>=? \
AND timestamp>? \
AND independent=0 \
ORDER BY timestamp;",
|mut stmt_locations, _| {
let msgs = rows
.into_iter()
.filter_map(|(chat_id, locations_send_begin, locations_last_sent)| {
// be a bit tolerant as the timer may not align exactly with time(NULL)
if now - locations_last_sent < (60 - 3) {
Ok(None)
} else {
Ok(Some((chat_id, locations_send_begin, locations_last_sent)))
}
},
|rows| {
context.sql.prepare(
"SELECT id \
FROM locations \
WHERE from_id=? \
AND timestamp>=? \
AND timestamp>? \
AND independent=0 \
ORDER BY timestamp;",
|mut stmt_locations, _| {
for (chat_id, locations_send_begin, locations_last_sent) in
rows.filter_map(|r| match r {
Ok(Some(v)) => Some(v),
_ => None,
})
{
// TODO: do I need to reset?
if !stmt_locations
.exists(params![1, locations_send_begin, locations_last_sent,])
.unwrap_or_default()
{
// if there is no new location, there's nothing to send.
// however, maybe we want to bypass this test eg. 15 minutes
None
} else {
// pending locations are attached automatically to every message,
// so also to this empty text message.
// DC_CMD_LOCATION is only needed to create a nicer subject.
//
// for optimisation and to avoid flooding the sending queue,
// we could sending these messages only if we're really online.
// the easiest way to determine this, is to check for an empty message queue.
// (might not be 100%, however, as positions are sent combined later
// and dc_set_location() is typically called periodically, this is ok)
let mut msg = dc_msg_new(Viewtype::Text);
msg.hidden = true;
msg.param.set_int(Param::Cmd, 9);
Some((chat_id, msg))
continue;
}
})
.collect::<Vec<_>>();
Ok(msgs)
},
)
.unwrap_or_default(); // TODO: Better error handling
// pending locations are attached automatically to every message,
// so also to this empty text message.
// DC_CMD_LOCATION is only needed to create a nicer subject.
//
// for optimisation and to avoid flooding the sending queue,
// we could sending these messages only if we're really online.
// the easiest way to determine this, is to check for an empty message queue.
// (might not be 100%, however, as positions are sent combined later
// and dc_set_location() is typically called periodically, this is ok)
let mut msg = dc_msg_new(context, Viewtype::Text);
msg.hidden = true;
msg.param.set_int(Param::Cmd, 9);
// TODO: handle cleanup on error
chat::send_msg(context, chat_id as u32, &mut msg).unwrap();
}
Ok(())
},
)
},
)
.unwrap(); // TODO: Better error handling
for (chat_id, mut msg) in msgs.into_iter() {
// TODO: better error handling
chat::send_msg(context, chat_id as u32, &mut msg).unwrap();
}
}
if 0 != continue_streaming {
schedule_MAYBE_SEND_LOCATIONS(context, 0x1);
}
@@ -648,7 +646,11 @@ pub fn job_do_DC_JOB_MAYBE_SEND_LOC_ENDED(context: &Context, job: &mut Job) {
).is_ok() {
let stock_str = context.stock_system_msg(StockMessage::MsgLocationDisabled, "", "", 0);
chat::add_device_msg(context, chat_id, stock_str);
context.call_cb(Event::ChatModified(chat_id));
context.call_cb(
Event::CHAT_MODIFIED,
chat_id as usize,
0,
);
}
}
}

View File

@@ -1,39 +1,66 @@
#[macro_export]
macro_rules! info {
($ctx:expr, $msg:expr) => {
info!($ctx, $msg,)
};
($ctx:expr, $msg:expr, $($args:expr),* $(,)?) => {
let formatted = format!($msg, $($args),*);
emit_event!($ctx, $crate::Event::Info(formatted));
($ctx:expr, $data1:expr, $msg:expr) => {
info!($ctx, $data1, $msg,)
};
($ctx:expr, $data1:expr, $msg:expr, $($args:expr),* $(,)?) => {
#[allow(unused_unsafe)]
unsafe {
let formatted = format!($msg, $($args),*);
let formatted_c = std::ffi::CString::new(formatted).unwrap();
$ctx.call_cb($crate::constants::Event::INFO, $data1 as libc::uintptr_t,
formatted_c.as_ptr() as libc::uintptr_t);
}};
}
#[macro_export]
macro_rules! warn {
($ctx:expr, $msg:expr) => {
warn!($ctx, $msg,)
};
($ctx:expr, $msg:expr, $($args:expr),* $(,)?) => {
let formatted = format!($msg, $($args),*);
emit_event!($ctx, $crate::Event::Warning(formatted));
($ctx:expr, $data1:expr, $msg:expr) => {
warn!($ctx, $data1, $msg,)
};
($ctx:expr, $data1:expr, $msg:expr, $($args:expr),* $(,)?) => {
#[allow(unused_unsafe)]
unsafe {
let formatted = format!($msg, $($args),*);
let formatted_c = std::ffi::CString::new(formatted).unwrap();
$ctx.call_cb($crate::constants::Event::WARNING, $data1 as libc::uintptr_t,
formatted_c.as_ptr() as libc::uintptr_t);
}};
}
#[macro_export]
macro_rules! error {
($ctx:expr, $msg:expr) => {
error!($ctx, $msg,)
($ctx:expr, $data1:expr, $msg:expr) => {
error!($ctx, $data1, $msg,)
};
($ctx:expr, $msg:expr, $($args:expr),* $(,)?) => {
($ctx:expr, $data1:expr, $msg:expr, $($args:expr),* $(,)?) => {
#[allow(unused_unsafe)]
unsafe {
let formatted = format!($msg, $($args),*);
emit_event!($ctx, $crate::Event::Error(formatted));
let formatted_c = std::ffi::CString::new(formatted).unwrap();
$ctx.call_cb($crate::constants::Event::ERROR, $data1 as libc::uintptr_t,
formatted_c.as_ptr() as libc::uintptr_t);
}};
}
#[macro_export]
macro_rules! log_event {
($ctx:expr, $data1:expr, $msg:expr) => {
log_event!($ctx, $data1, $msg,)
};
($ctx:expr, $event:expr, $data1:expr, $msg:expr, $($args:expr),* $(,)?) => {
#[allow(unused_unsafe)]
unsafe {
let formatted = format!($msg, $($args),*);
let formatted_c = std::ffi::CString::new(formatted).unwrap();
$ctx.call_cb($event, $data1 as libc::uintptr_t,
formatted_c.as_ptr() as libc::uintptr_t);
}};
}
#[macro_export]
macro_rules! emit_event {
($ctx:expr, $event:expr) => {
$ctx.call_cb($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

@@ -1,5 +1,5 @@
use std::ffi::CString;
use std::path::{Path, PathBuf};
use std::path::Path;
use std::ptr;
use deltachat_derive::{FromSql, ToSql};
@@ -9,16 +9,15 @@ use crate::chat::{self, Chat};
use crate::constants::*;
use crate::contact::*;
use crate::context::*;
use crate::dc_mimeparser::SystemMessage;
use crate::dc_tools::*;
use crate::error::Error;
use crate::events::Event;
use crate::job::*;
use crate::lot::{Lot, LotState, Meaning};
use crate::param::*;
use crate::pgp::*;
use crate::sql;
use crate::stock::StockMessage;
use crate::types::*;
use crate::x::*;
/// In practice, the user additionally cuts the string himself pixel-accurate.
@@ -39,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::*;
@@ -88,7 +81,7 @@ impl Lot {
self.text1 = Some(context.stock_str(StockMessage::Draft).to_owned().into());
self.text1_meaning = Meaning::Text1Draft;
} else if msg.from_id == DC_CONTACT_ID_SELF {
if dc_msg_is_info(msg) || chat.is_self_talk() {
if 0 != dc_msg_is_info(msg) || chat.is_self_talk() {
self.text1 = None;
self.text1_meaning = Meaning::None;
} else {
@@ -96,7 +89,7 @@ impl Lot {
self.text1_meaning = Meaning::Text1Self;
}
} else if chat.typ == Chattype::Group || chat.typ == Chattype::VerifiedGroup {
if dc_msg_is_info(msg) || contact.is_none() {
if 0 != dc_msg_is_info(msg) || contact.is_none() {
self.text1 = None;
self.text1_meaning = Meaning::None;
} else {
@@ -137,8 +130,8 @@ impl Lot {
/// to check if a mail was sent, use dc_msg_is_sent()
/// approx. max. length returned by dc_msg_get_text()
/// approx. max. length returned by dc_get_msg_info()
#[derive(Debug, Clone)]
pub struct Message {
#[derive(Clone)]
pub struct Message<'a> {
pub id: u32,
pub from_id: u32,
pub to_id: u32,
@@ -151,8 +144,9 @@ pub struct Message {
pub timestamp_sent: i64,
pub timestamp_rcvd: i64,
pub text: Option<String>,
pub context: &'a Context,
pub rfc724_mid: *mut libc::c_char,
pub in_reply_to: Option<String>,
pub in_reply_to: *mut libc::c_char,
pub server_folder: Option<String>,
pub server_uid: u32,
// TODO: enum
@@ -165,6 +159,7 @@ pub struct Message {
// 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);
@@ -174,10 +169,11 @@ pub unsafe fn dc_get_msg_info(context: &Context, msg_id: u32) -> *mut libc::c_ch
let msg = msg.unwrap();
let rawtxt: Option<String> = context.sql.query_get_value(
let rawtxt: Option<String> = context.sql.query_row_col(
context,
"SELECT txt_raw FROM msgs WHERE id=?;",
params![msg_id as i32],
0,
);
if rawtxt.is_none() {
@@ -269,16 +265,23 @@ pub unsafe fn dc_get_msg_info(context: &Context, msg_id: u32) -> *mut libc::c_ch
_ => {}
}
if let Some(path) = dc_msg_get_file(context, &msg) {
let bytes = dc_get_filebytes(context, &path);
ret += &format!("\nFile: {}, {}, bytes\n", path.display(), bytes);
p = dc_msg_get_file(&msg);
if !p.is_null() && 0 != *p.offset(0isize) as libc::c_int {
ret += &format!(
"\nFile: {}, {}, bytes\n",
as_str(p),
dc_get_filebytes(context, as_path(p)) as libc::c_int,
);
}
free(p as *mut libc::c_void);
if msg.type_0 != Viewtype::Text {
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();
@@ -304,11 +307,11 @@ pub unsafe fn dc_get_msg_info(context: &Context, msg_id: u32) -> *mut libc::c_ch
ret.strdup()
}
pub fn dc_msg_new_untyped() -> Message {
dc_msg_new(Viewtype::Unknown)
pub unsafe fn dc_msg_new_untyped<'a>(context: &'a Context) -> Message<'a> {
dc_msg_new(context, Viewtype::Unknown)
}
pub fn dc_msg_new(viewtype: Viewtype) -> Message {
pub fn dc_msg_new<'a>(context: &'a Context, viewtype: Viewtype) -> Message<'a> {
Message {
id: 0,
from_id: 0,
@@ -322,8 +325,9 @@ pub fn dc_msg_new(viewtype: Viewtype) -> Message {
timestamp_sent: 0,
timestamp_rcvd: 0,
text: None,
context,
rfc724_mid: std::ptr::null_mut(),
in_reply_to: None,
in_reply_to: std::ptr::null_mut(),
server_folder: None,
server_uid: 0,
is_dc_message: 0,
@@ -334,24 +338,25 @@ pub fn dc_msg_new(viewtype: Viewtype) -> Message {
}
}
impl Drop for Message {
impl<'a> Drop for Message<'a> {
fn drop(&mut self) {
unsafe {
free(self.rfc724_mid.cast());
free(self.in_reply_to.cast());
}
}
}
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)> {
@@ -372,10 +377,17 @@ pub fn dc_msg_guess_msgtype_from_suffix(path: &Path) -> Option<(Viewtype, &str)>
KNOWN.get(extension).map(|x| *x)
}
pub unsafe fn dc_msg_get_file(context: &Context, msg: &Message) -> Option<PathBuf> {
msg.param
.get(Param::File)
.map(|f| dc_get_abs_path(context, f))
pub unsafe fn dc_msg_get_file(msg: &Message) -> *mut libc::c_char {
let mut file_abs = ptr::null_mut();
if let Some(file_rel) = msg.param.get(Param::File) {
file_abs = dc_get_abs_path(msg.context, file_rel);
}
if !file_abs.is_null() {
file_abs
} else {
dc_strdup(0 as *const libc::c_char)
}
}
/**
@@ -425,7 +437,7 @@ pub fn dc_msg_get_timestamp(msg: &Message) -> i64 {
}
}
pub fn dc_msg_load_from_db(context: &Context, id: u32) -> Result<Message, Error> {
pub fn dc_msg_load_from_db<'a>(context: &'a Context, id: u32) -> Result<Message<'a>, Error> {
context.sql.query_row(
"SELECT \
m.id,rfc724_mid,m.mime_in_reply_to,m.server_folder,m.server_uid,m.move_state,m.chat_id, \
@@ -436,10 +448,14 @@ pub fn dc_msg_load_from_db(context: &Context, id: u32) -> Result<Message, Error>
params![id as i32],
|row| {
unsafe {
let mut msg = dc_msg_new_untyped();
let mut msg = dc_msg_new_untyped(context);
msg.context = context;
msg.id = row.get::<_, i32>(0)? as u32;
msg.rfc724_mid = row.get::<_, String>(1)?.strdup();
msg.in_reply_to = row.get::<_, Option<String>>(2)?;
msg.in_reply_to = match row.get::<_, Option<String>>(2)? {
Some(s) => s.strdup(),
None => std::ptr::null_mut(),
};
msg.server_folder = row.get::<_, Option<String>>(3)?;
msg.server_uid = row.get(4)?;
msg.move_state = row.get(5)?;
@@ -458,11 +474,12 @@ pub fn dc_msg_load_from_db(context: &Context, id: u32) -> Result<Message, Error>
if let Ok(t) = String::from_utf8(buf.to_vec()) {
text = t;
} else {
warn!(context, "dc_msg_load_from_db: could not get text column as non-lossy utf8 id {}", id);
warn!(context, 0, "dc_msg_load_from_db: could not get text column as non-lossy utf8 id {}", id);
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);
@@ -487,10 +504,11 @@ pub fn dc_msg_load_from_db(context: &Context, id: u32) -> Result<Message, Error>
}
pub unsafe fn dc_get_mime_headers(context: &Context, msg_id: u32) -> *mut libc::c_char {
let headers: Option<String> = context.sql.query_get_value(
let headers: Option<String> = context.sql.query_row_col(
context,
"SELECT mime_headers FROM msgs WHERE id=?;",
params![msg_id as i32],
0,
);
if let Some(headers) = headers {
@@ -519,10 +537,7 @@ pub unsafe fn dc_delete_msgs(context: &Context, msg_ids: *const u32, msg_cnt: li
}
if 0 != msg_cnt {
context.call_cb(Event::MsgsChanged {
chat_id: 0,
msg_id: 0,
});
context.call_cb(Event::MSGS_CHANGED, 0 as uintptr_t, 0 as uintptr_t);
job_kill_action(context, Action::Housekeeping);
job_add(context, Action::Housekeeping, 0, Params::new(), 10);
};
@@ -562,7 +577,7 @@ pub fn dc_markseen_msgs(context: &Context, msg_ids: *const u32, msg_cnt: usize)
);
if msgs.is_err() {
warn!(context, "markseen_msgs failed: {:?}", msgs);
warn!(context, 0, "markseen_msgs failed: {:?}", msgs);
return false;
}
let mut send_event = false;
@@ -572,7 +587,7 @@ pub fn dc_markseen_msgs(context: &Context, msg_ids: *const u32, msg_cnt: usize)
if curr_blocked == Blocked::Not {
if curr_state == MessageState::InFresh || curr_state == MessageState::InNoticed {
dc_update_msg_state(context, id, MessageState::InSeen);
info!(context, "Seen message #{}.", id);
info!(context, 0, "Seen message #{}.", id);
job_add(
context,
@@ -590,10 +605,7 @@ pub fn dc_markseen_msgs(context: &Context, msg_ids: *const u32, msg_cnt: usize)
}
if send_event {
context.call_cb(Event::MsgsChanged {
chat_id: 0,
msg_id: 0,
});
context.call_cb(Event::MSGS_CHANGED, 0, 0);
}
true
@@ -629,7 +641,7 @@ pub fn dc_star_msgs(
.is_ok()
}
pub fn dc_get_msg(context: &Context, msg_id: u32) -> Result<Message, Error> {
pub fn dc_get_msg<'a>(context: &'a Context, msg_id: u32) -> Result<Message<'a>, Error> {
dc_msg_load_from_db(context, msg_id)
}
@@ -687,10 +699,11 @@ pub unsafe fn dc_msg_get_filename(msg: &Message) -> *mut libc::c_char {
}
}
pub fn dc_msg_get_filebytes(context: &Context, msg: &Message) -> u64 {
pub unsafe fn dc_msg_get_filebytes(msg: &Message) -> uint64_t {
if let Some(file) = msg.param.get(Param::File) {
return dc_get_filebytes(context, &file);
return dc_get_filebytes(msg.context, &file);
}
0
}
@@ -706,18 +719,23 @@ pub fn dc_msg_get_duration(msg: &Message) -> libc::c_int {
msg.param.get_int(Param::Duration).unwrap_or_default()
}
pub fn dc_msg_get_showpadlock(msg: &Message) -> bool {
msg.param.get_int(Param::GuranteeE2ee).unwrap_or_default() != 0
// TODO should return bool /rtn
pub fn dc_msg_get_showpadlock(msg: &Message) -> libc::c_int {
if msg.param.get_int(Param::GuranteeE2ee).unwrap_or_default() != 0 {
return 1;
}
0
}
pub fn dc_msg_get_summary(context: &Context, msg: &mut Message, chat: Option<&Chat>) -> Lot {
pub unsafe fn dc_msg_get_summary<'a>(msg: &mut Message<'a>, chat: Option<&Chat<'a>>) -> Lot {
let mut ret = Lot::new();
let chat_loaded: Chat;
let chat = if let Some(chat) = chat {
chat
} else {
if let Ok(chat) = Chat::load_from_db(context, msg.chat_id) {
if let Ok(chat) = Chat::load_from_db(msg.context, msg.chat_id) {
chat_loaded = chat;
&chat_loaded
} else {
@@ -728,18 +746,17 @@ pub fn dc_msg_get_summary(context: &Context, msg: &mut Message, chat: Option<&Ch
let contact = if msg.from_id != DC_CONTACT_ID_SELF as libc::c_uint
&& ((*chat).typ == Chattype::Group || (*chat).typ == Chattype::VerifiedGroup)
{
Contact::get_by_id(context, msg.from_id).ok()
Contact::get_by_id((*chat).context, msg.from_id).ok()
} else {
None
};
ret.fill(msg, chat, contact.as_ref(), context);
ret.fill(msg, chat, contact.as_ref(), msg.context);
ret
}
pub unsafe fn dc_msg_get_summarytext(
context: &Context,
msg: &mut Message,
approx_characters: usize,
) -> *mut libc::c_char {
@@ -748,7 +765,7 @@ pub unsafe fn dc_msg_get_summarytext(
msg.text.as_ref(),
&mut msg.param,
approx_characters,
context,
msg.context,
)
.strdup()
}
@@ -768,7 +785,7 @@ pub fn dc_msg_get_summarytext_by_raw(
Viewtype::Video => context.stock_str(StockMessage::Video).into_owned(),
Viewtype::Voice => context.stock_str(StockMessage::VoiceMessage).into_owned(),
Viewtype::Audio | Viewtype::File => {
if param.get_cmd() == SystemMessage::AutocryptSetupMessage {
if param.get_int(Param::Cmd) == Some(6) {
append_text = false;
context
.stock_str(StockMessage::AcSetupMsgSubject)
@@ -794,7 +811,7 @@ pub fn dc_msg_get_summarytext_by_raw(
}
}
_ => {
if param.get_cmd() != SystemMessage::LocationOnly {
if param.get_int(Param::Cmd) != Some(9) {
"".to_string()
} else {
append_text = false;
@@ -827,27 +844,48 @@ pub unsafe fn dc_msg_has_deviating_timestamp(msg: &Message) -> libc::c_int {
(sort_timestamp / 86400 != send_timestamp / 86400) as libc::c_int
}
pub fn dc_msg_is_sent(msg: &Message) -> bool {
msg.state as i32 >= MessageState::OutDelivered as i32
// TODO should return bool /rtn
pub fn dc_msg_is_sent(msg: &Message) -> libc::c_int {
if msg.state as i32 >= MessageState::OutDelivered as i32 {
1
} else {
0
}
}
pub fn dc_msg_is_starred(msg: &Message) -> bool {
msg.starred
}
pub fn dc_msg_is_forwarded(msg: &Message) -> bool {
0 != msg.param.get_int(Param::Forwarded).unwrap_or_default()
// TODO should return bool /rtn
pub fn dc_msg_is_forwarded(msg: &Message) -> libc::c_int {
if 0 != msg.param.get_int(Param::Forwarded).unwrap_or_default() {
1
} else {
0
}
}
pub fn dc_msg_is_info(msg: &Message) -> bool {
let cmd = msg.param.get_cmd();
msg.from_id == 2i32 as libc::c_uint
// TODO should return bool /rtn
pub fn dc_msg_is_info(msg: &Message) -> libc::c_int {
let cmd = msg.param.get_int(Param::Cmd).unwrap_or_default();
if msg.from_id == 2i32 as libc::c_uint
|| msg.to_id == 2i32 as libc::c_uint
|| cmd != SystemMessage::Unknown && cmd != SystemMessage::AutocryptSetupMessage
|| 0 != cmd && cmd != 6i32
{
return 1;
}
0
}
pub fn dc_msg_is_increation(msg: &Message) -> bool {
chat::msgtype_has_file(msg.type_0) && msg.state == MessageState::OutPreparing
// TODO should return bool /rtn
pub fn dc_msg_is_increation(msg: &Message) -> libc::c_int {
if chat::msgtype_has_file(msg.type_0) && msg.state == MessageState::OutPreparing {
1
} else {
0
}
}
pub fn dc_msg_is_setupmessage(msg: &Message) -> bool {
@@ -855,20 +893,33 @@ pub fn dc_msg_is_setupmessage(msg: &Message) -> bool {
return false;
}
msg.param.get_cmd() == SystemMessage::AutocryptSetupMessage
msg.param.get_int(Param::Cmd) == Some(6)
}
pub unsafe fn dc_msg_get_setupcodebegin(context: &Context, msg: &Message) -> *mut libc::c_char {
pub unsafe fn dc_msg_get_setupcodebegin(msg: &Message) -> *mut libc::c_char {
let mut filename: *mut libc::c_char = ptr::null_mut();
let mut buf: *mut libc::c_char = ptr::null_mut();
let mut buf_bytes: size_t = 0i32 as size_t;
// just a pointer inside buf, MUST NOT be free()'d
let mut buf_headerline: *const libc::c_char = ptr::null();
// just a pointer inside buf, MUST NOT be free()'d
let mut buf_setupcodebegin: *const libc::c_char = ptr::null();
let mut ret: *mut libc::c_char = ptr::null_mut();
if dc_msg_is_setupmessage(msg) {
if let Some(filename) = dc_msg_get_file(context, msg) {
if let Some(mut buf) = dc_read_file_safe(context, filename) {
filename = dc_msg_get_file(msg);
if !(filename.is_null() || *filename.offset(0isize) as libc::c_int == 0i32) {
if !(0
== dc_read_file(
msg.context,
filename,
&mut buf as *mut *mut libc::c_char as *mut *mut libc::c_void,
&mut buf_bytes,
)
|| buf.is_null()
|| buf_bytes <= 0)
{
if dc_split_armored_data(
buf.as_mut_ptr().cast(),
buf,
&mut buf_headerline,
&mut buf_setupcodebegin,
ptr::null_mut(),
@@ -884,6 +935,8 @@ pub unsafe fn dc_msg_get_setupcodebegin(context: &Context, msg: &Message) -> *mu
}
}
}
free(filename as *mut libc::c_void);
free(buf as *mut libc::c_void);
if !ret.is_null() {
ret
} else {
@@ -922,7 +975,6 @@ pub fn dc_msg_set_duration(msg: &mut Message, duration: libc::c_int) {
}
pub fn dc_msg_latefiling_mediasize(
context: &Context,
msg: &mut Message,
width: libc::c_int,
height: libc::c_int,
@@ -935,20 +987,20 @@ pub fn dc_msg_latefiling_mediasize(
if duration > 0 {
msg.param.set_int(Param::Duration, duration);
}
dc_msg_save_param_to_disk(context, msg);
dc_msg_save_param_to_disk(msg);
}
pub fn dc_msg_save_param_to_disk(context: &Context, msg: &mut Message) -> bool {
pub fn dc_msg_save_param_to_disk(msg: &mut Message) -> bool {
sql::execute(
context,
&context.sql,
msg.context,
&msg.context.sql,
"UPDATE msgs SET param=? WHERE id=?;",
params![msg.param.to_string(), msg.id as i32],
)
.is_ok()
}
pub fn dc_msg_new_load(context: &Context, msg_id: u32) -> Result<Message, Error> {
pub fn dc_msg_new_load<'a>(context: &'a Context, msg_id: u32) -> Result<Message<'a>, Error> {
dc_msg_load_from_db(context, msg_id)
}
@@ -978,22 +1030,25 @@ The value is also used for CC:-summaries */
// Context functions to work with messages
pub fn dc_msg_exists(context: &Context, msg_id: u32) -> bool {
if msg_id <= DC_CHAT_ID_LAST_SPECIAL {
return false;
pub unsafe fn dc_msg_exists(context: &Context, msg_id: u32) -> libc::c_int {
if msg_id <= 9 {
return 0;
}
let chat_id: Option<u32> = context.sql.query_get_value(
let chat_id: Option<i32> = context.sql.query_row_col(
context,
"SELECT chat_id FROM msgs WHERE id=?;",
params![msg_id],
params![msg_id as i32],
0,
);
if let Some(chat_id) = chat_id {
chat_id != DC_CHAT_ID_TRASH
} else {
false
if chat_id != 3 {
return 1;
}
}
0
}
pub fn dc_update_msg_move_state(
@@ -1019,7 +1074,7 @@ pub fn dc_set_msg_failed(context: &Context, msg_id: u32, error: Option<impl AsRe
}
if let Some(error) = error {
msg.param.set(Param::Error, error.as_ref());
error!(context, "{}", error.as_ref());
error!(context, 0, "{}", error.as_ref());
}
if sql::execute(
@@ -1030,10 +1085,11 @@ pub fn dc_set_msg_failed(context: &Context, msg_id: u32, error: Option<impl AsRe
)
.is_ok()
{
context.call_cb(Event::MsgFailed {
chat_id: msg.chat_id,
msg_id,
});
context.call_cb(
Event::MSG_FAILED,
msg.chat_id as uintptr_t,
msg_id as uintptr_t,
);
}
}
}
@@ -1104,10 +1160,11 @@ pub unsafe fn dc_mdn_from_ext(
/* send event about new state */
let ist_cnt: i32 = context
.sql
.query_get_value(
.query_row_col(
context,
"SELECT COUNT(*) FROM msgs_mdns WHERE msg_id=?;",
params![*ret_msg_id as i32],
0,
)
.unwrap_or_default();
/*
@@ -1146,13 +1203,13 @@ pub fn dc_get_real_msg_cnt(context: &Context) -> libc::c_int {
) {
Ok(res) => res,
Err(err) => {
error!(context, "dc_get_real_msg_cnt() failed. {}", err);
error!(context, 0, "dc_get_real_msg_cnt() failed. {}", err);
0
}
}
}
pub fn dc_get_deaddrop_msg_cnt(context: &Context) -> libc::size_t {
pub fn dc_get_deaddrop_msg_cnt(context: &Context) -> size_t {
match context.sql.query_row(
"SELECT COUNT(*) \
FROM msgs m LEFT JOIN chats c ON c.id=m.chat_id \
@@ -1160,9 +1217,9 @@ pub fn dc_get_deaddrop_msg_cnt(context: &Context) -> libc::size_t {
rusqlite::NO_PARAMS,
|row| row.get::<_, isize>(0),
) {
Ok(res) => res as libc::size_t,
Ok(res) => res as size_t,
Err(err) => {
error!(context, "dc_get_deaddrop_msg_cnt() failed. {}", err);
error!(context, 0, "dc_get_deaddrop_msg_cnt() failed. {}", err);
0
}
}
@@ -1177,7 +1234,7 @@ pub fn dc_rfc724_mid_cnt(context: &Context, rfc724_mid: *const libc::c_char) ->
) {
Ok(res) => res,
Err(err) => {
error!(context, "dc_get_rfc724_mid_cnt() failed. {}", err);
error!(context, 0, "dc_get_rfc724_mid_cnt() failed. {}", err);
0
}
}
@@ -1231,7 +1288,7 @@ pub fn dc_update_server_uid(
) {
Ok(_) => {}
Err(err) => {
warn!(context, "msg: failed to update server_uid: {}", err);
warn!(context, 0, "msg: failed to update server_uid: {}", err);
}
}
}
@@ -1264,7 +1321,7 @@ mod tests {
let chat = chat::create_by_contact_id(ctx, contact).unwrap();
let mut msg = dc_msg_new(Viewtype::Text);
let mut msg = dc_msg_new(ctx, Viewtype::Text);
let msg_id = chat::prepare_msg(ctx, chat, &mut msg).unwrap();

View File

@@ -97,7 +97,10 @@ pub fn dc_get_oauth2_access_token(
let (redirect_uri, token_url, update_redirect_uri_on_success) =
if refresh_token.is_none() || refresh_token_for != code.as_ref() {
info!(context, "Generate OAuth2 refresh_token and access_token...",);
info!(
context,
0, "Generate OAuth2 refresh_token and access_token...",
);
(
context
.sql
@@ -109,7 +112,7 @@ pub fn dc_get_oauth2_access_token(
} else {
info!(
context,
"Regenerate OAuth2 access_token by refresh_token...",
0, "Regenerate OAuth2 access_token by refresh_token...",
);
(
context
@@ -131,7 +134,7 @@ pub fn dc_get_oauth2_access_token(
if response.is_err() {
warn!(
context,
"Error calling OAuth2 at {}: {:?}", token_url, response
0, "Error calling OAuth2 at {}: {:?}", token_url, response
);
return None;
}
@@ -139,6 +142,7 @@ pub fn dc_get_oauth2_access_token(
if !response.status().is_success() {
warn!(
context,
0,
"Error calling OAuth2 at {}: {:?}",
token_url,
response.status()
@@ -150,7 +154,7 @@ pub fn dc_get_oauth2_access_token(
if parsed.is_err() {
warn!(
context,
"Failed to parse OAuth2 JSON response from {}: error: {:?}", token_url, parsed
0, "Failed to parse OAuth2 JSON response from {}: error: {:?}", token_url, parsed
);
return None;
}
@@ -191,12 +195,12 @@ pub fn dc_get_oauth2_access_token(
.ok();
}
} else {
warn!(context, "Failed to find OAuth2 access token");
warn!(context, 0, "Failed to find OAuth2 access token");
}
response.access_token
} else {
warn!(context, "Internal OAuth2 error: 2");
warn!(context, 0, "Internal OAuth2 error: 2");
None
}
@@ -264,12 +268,17 @@ impl Oauth2 {
// }
let response = reqwest::Client::new().get(&userinfo_url).send();
if response.is_err() {
warn!(context, "Error getting userinfo: {:?}", response);
warn!(context, 0, "Error getting userinfo: {:?}", response);
return None;
}
let mut response = response.unwrap();
if !response.status().is_success() {
warn!(context, "Error getting userinfo: {:?}", response.status());
warn!(
context,
0,
"Error getting userinfo: {:?}",
response.status()
);
return None;
}
@@ -277,19 +286,19 @@ impl Oauth2 {
if parsed.is_err() {
warn!(
context,
"Failed to parse userinfo JSON response: {:?}", parsed
0, "Failed to parse userinfo JSON response: {:?}", parsed
);
return None;
}
if let Ok(response) = parsed {
let addr = response.get("email");
if addr.is_none() {
warn!(context, "E-mail missing in userinfo.");
warn!(context, 0, "E-mail missing in userinfo.");
}
addr.map(|addr| addr.to_string())
} else {
warn!(context, "Failed to parse userinfo.");
warn!(context, 0, "Failed to parse userinfo.");
None
}
}

View File

@@ -4,7 +4,6 @@ use std::str;
use num_traits::FromPrimitive;
use crate::dc_mimeparser::SystemMessage;
use crate::error;
/// Available param keys.
@@ -95,7 +94,7 @@ impl fmt::Display for Params {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
for (i, (key, value)) in self.inner.iter().enumerate() {
if i > 0 {
writeln!(f)?;
write!(f, "\n")?;
}
write!(f, "{}={}", *key as u8 as char, value)?;
}
@@ -179,13 +178,6 @@ impl Params {
self.get(key).and_then(|s| s.parse().ok())
}
/// Get the parameter behind `Param::Cmd` interpreted as `SystemMessage`.
pub fn get_cmd(&self) -> SystemMessage {
self.get_int(Param::Cmd)
.and_then(SystemMessage::from_i32)
.unwrap_or_default()
}
/// Get the given parameter and parse as `f64`.
pub fn get_float(&self, key: Param) -> Option<f64> {
self.get(key).and_then(|s| s.parse().ok())

View File

@@ -1,5 +1,6 @@
use std::collections::HashSet;
use std::convert::TryInto;
use std::ffi::CStr;
use std::io::Cursor;
use std::ptr;
@@ -14,6 +15,7 @@ use rand::thread_rng;
use crate::dc_tools::*;
use crate::key::*;
use crate::keyring::*;
use crate::types::*;
use crate::x::*;
pub unsafe fn dc_split_armored_data(
@@ -24,7 +26,7 @@ pub unsafe fn dc_split_armored_data(
ret_base64: *mut *const libc::c_char,
) -> bool {
let mut success = false;
let mut line_chars: libc::size_t = 0;
let mut line_chars: size_t = 0i32 as size_t;
let mut line: *mut libc::c_char = buf;
let mut p1: *mut libc::c_char = buf;
let mut p2: *mut libc::c_char;
@@ -104,7 +106,7 @@ pub unsafe fn dc_split_armored_data(
}
p1 = p1.offset(1isize);
line = p1;
line_chars = 0;
line_chars = 0i32 as size_t
} else {
p1 = p1.offset(1isize);
line_chars = line_chars.wrapping_add(1)
@@ -186,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()
@@ -221,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
@@ -272,25 +283,44 @@ pub fn dc_pgp_pk_decrypt(
}
/// Symmetric encryption.
pub fn dc_pgp_symm_encrypt(passphrase: &str, 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(), || passphrase.into());
let msg = lit_msg.encrypt_with_password(&mut rng, s2k, Default::default(), || pw.into());
msg.and_then(|msg| msg.to_armored_string(None)).ok()
}
/// Symmetric decryption.
pub fn dc_pgp_symm_decrypt(passphrase: &str, ctext: &[u8]) -> Option<Vec<u8>> {
let enc_msg = Message::from_bytes(Cursor::new(ctext));
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(bytes));
enc_msg
.and_then(|msg| {
let mut decryptor = msg
.decrypt_with_password(|| passphrase.into())
.decrypt_with_password(|| pw.into())
.expect("failed decryption");
decryptor.next().expect("no message")
})

View File

@@ -37,7 +37,7 @@ impl Into<Lot> for Error {
pub fn check_qr(context: &Context, qr: impl AsRef<str>) -> Lot {
let qr = qr.as_ref();
info!(context, "Scanned QR code: {}", qr);
info!(context, 0, "Scanned QR code: {}", qr);
if qr.starts_with(OPENPGP4FPR_SCHEME) {
decode_openpgp(context, qr)

View File

@@ -1,16 +1,18 @@
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::config::*;
use crate::configure::*;
use crate::constants::*;
use crate::contact::*;
use crate::context::Context;
use crate::dc_mimeparser::*;
use crate::dc_token::*;
use crate::dc_tools::*;
use crate::e2ee::*;
use crate::error::Error;
use crate::events::Event;
use crate::key::*;
use crate::lot::LotState;
use crate::message::*;
@@ -18,33 +20,39 @@ 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! joiner_progress {
($context:tt, $contact_id:expr, $progress:expr) => {
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($crate::events::Event::SecurejoinJoinerProgress {
contact_id: $contact_id,
progress: $progress,
});
$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) => {
assert!(
$progress >= 0 && $progress <= 1000,
"value in range 0..1000 expected with: 0=error, 1..999=progress, 1000=success"
progress!(
$context,
Event::SECUREJOIN_INVITER_PROGRESS,
$contact_id,
$progress
);
$context.call_cb($crate::events::Event::SecurejoinInviterProgress {
contact_id: $contact_id,
progress: $progress,
});
};
}
@@ -63,7 +71,7 @@ macro_rules! get_qr_attr {
};
}
pub fn dc_get_securejoin_qr(context: &Context, group_chat_id: u32) -> Option<String> {
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 ====
@@ -72,12 +80,12 @@ pub fn dc_get_securejoin_qr(context: &Context, group_chat_id: u32) -> Option<Str
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.get_config(Config::ConfiguredAddr) {
let invitenumber = dc_token_lookup_or_new(context, DC_TOKEN_INVITENUMBER, group_chat_id);
let auth = dc_token_lookup_or_new(context, DC_TOKEN_AUTH, group_chat_id);
let self_addr = match context.sql.get_config(context, "configured_addr") {
Some(addr) => addr,
None => {
error!(context, "Not configured, cannot generate QR code.",);
error!(context, 0, "Not configured, cannot generate QR code.",);
return None;
}
};
@@ -115,7 +123,10 @@ pub fn dc_get_securejoin_qr(context: &Context, group_chat_id: u32) -> Option<Str
&auth,
))
} else {
error!(context, "Cannot get QR-code for chat-id {}", group_chat_id,);
error!(
context,
0, "Cannot get QR-code for chat-id {}", group_chat_id,
);
return None;
}
} else {
@@ -125,13 +136,13 @@ pub fn dc_get_securejoin_qr(context: &Context, group_chat_id: u32) -> Option<Str
))
};
info!(context, "Generated QR code: {}", qr.as_ref().unwrap());
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.get_config(Config::ConfiguredAddr) {
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());
}
@@ -139,7 +150,7 @@ fn get_self_fingerprint(context: &Context) -> Option<String> {
None
}
pub fn dc_join_securejoin(context: &Context, qr: &str) -> u32 {
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();
@@ -162,16 +173,16 @@ pub fn dc_join_securejoin(context: &Context, qr: &str) -> u32 {
if ongoing_allocated {
dc_free_ongoing(context);
}
ret_chat_id as u32
ret_chat_id as uint32_t
};
/* ==========================================================
==== Bob - the joiner's side =====
==== Step 2 in "Setup verified contact" protocol =====
========================================================== */
let mut contact_chat_id: u32 = 0;
let mut contact_chat_id: uint32_t = 0;
let mut join_vg: bool = false;
info!(context, "Requesting secure-join ...",);
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);
@@ -179,12 +190,12 @@ pub fn dc_join_securejoin(context: &Context, qr: &str) -> u32 {
let qr_scan = check_qr(context, &qr);
if qr_scan.state != LotState::QrAskVerifyContact && qr_scan.state != LotState::QrAskVerifyGroup
{
error!(context, "Unknown QR code.",);
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, "Unknown contact.",);
error!(context, 0, "Unknown contact.",);
return cleanup(&context, contact_chat_id, true, join_vg);
}
if check_exit(context) {
@@ -210,7 +221,7 @@ pub fn dc_join_securejoin(context: &Context, qr: &str) -> u32 {
.unwrap(),
contact_chat_id,
) {
info!(context, "Taking protocol shortcut.");
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();
@@ -260,13 +271,13 @@ fn check_exit(context: &Context) -> bool {
fn send_handshake_msg(
context: &Context,
contact_chat_id: u32,
contact_chat_id: uint32_t,
step: &str,
param2: impl AsRef<str>,
fingerprint: Option<String>,
grpid: impl AsRef<str>,
) {
let mut msg = dc_msg_new_untyped();
let mut msg = unsafe { dc_msg_new_untyped(context) };
msg.type_0 = Viewtype::Text;
msg.text = Some(format!("Secure-Join: {}", step));
msg.hidden = true;
@@ -297,7 +308,7 @@ fn send_handshake_msg(
chat::send_msg(context, contact_chat_id, &mut msg).unwrap();
}
fn chat_id_2_contact_id(context: &Context, contact_chat_id: u32) -> u32 {
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]
@@ -332,15 +343,15 @@ fn fingerprint_equals_sender(
/* library private: secure-join */
pub fn handle_securejoin_handshake(
context: &Context,
mimeparser: &MimeParser,
contact_id: u32,
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 mimeparser.lookup_optional_field("Secure-Join") {
let step = match lookup_field(mimeparser, "Secure-Join") {
Some(s) => s,
None => {
return 0;
@@ -348,7 +359,7 @@ pub fn handle_securejoin_handshake(
};
info!(
context,
">>>>>>>>>>>>>>>>>>>>>>>>> secure-join message \'{}\' received", step,
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();
@@ -369,18 +380,18 @@ pub fn handle_securejoin_handshake(
// 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 mimeparser.lookup_optional_field("Secure-Join-Invitenumber") {
let invitenumber = match lookup_field(mimeparser, "Secure-Join-Invitenumber") {
Some(n) => n,
None => {
warn!(context, "Secure-join denied (invitenumber missing).",);
warn!(context, 0, "Secure-join denied (invitenumber missing).",);
return ret;
}
};
if !token::exists(context, token::Namespace::InviteNumber, &invitenumber) {
warn!(context, "Secure-join denied (bad invitenumber).",);
if !dc_token_exists(context, DC_TOKEN_INVITENUMBER, &invitenumber) {
warn!(context, 0, "Secure-join denied (bad invitenumber).",);
return ret;
}
info!(context, "Secure-join requested.",);
info!(context, 0, "Secure-join requested.",);
inviter_progress!(context, contact_id, 300);
send_handshake_msg(
@@ -402,7 +413,7 @@ pub fn handle_securejoin_handshake(
};
if cond {
warn!(context, "auth-required message out of sync.",);
warn!(context, 0, "auth-required message out of sync.",);
// no error, just aborted somehow or a mail from another handshake
return ret;
}
@@ -431,7 +442,7 @@ pub fn handle_securejoin_handshake(
end_bobs_joining(context, DC_BOB_ERROR);
return ret;
}
info!(context, "Fingerprint verified.",);
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;
@@ -456,7 +467,7 @@ pub fn handle_securejoin_handshake(
==== Step 6 in "Out-of-band verified groups" protocol ====
============================================================ */
// verify that Secure-Join-Fingerprint:-header matches the fingerprint of Bob
let fingerprint = match mimeparser.lookup_optional_field("Secure-Join-Fingerprint") {
let fingerprint = match lookup_field(mimeparser, "Secure-Join-Fingerprint") {
Some(fp) => fp,
None => {
could_not_establish_secure_connection(
@@ -483,9 +494,9 @@ pub fn handle_securejoin_handshake(
);
return ret;
}
info!(context, "Fingerprint verified.",);
info!(context, 0, "Fingerprint verified.",);
// verify that the `Secure-Join-Auth:`-header matches the secret written to the QR code
let auth_0 = match mimeparser.lookup_optional_field("Secure-Join-Auth") {
let auth_0 = match lookup_field(mimeparser, "Secure-Join-Auth") {
Some(auth) => auth,
None => {
could_not_establish_secure_connection(
@@ -496,7 +507,7 @@ pub fn handle_securejoin_handshake(
return ret;
}
};
if !token::exists(context, token::Namespace::Auth, &auth_0) {
if !dc_token_exists(context, DC_TOKEN_AUTH, &auth_0) {
could_not_establish_secure_connection(context, contact_chat_id, "Auth invalid.");
return ret;
}
@@ -509,17 +520,15 @@ pub fn handle_securejoin_handshake(
return ret;
}
Contact::scaleup_origin_by_id(context, contact_id, Origin::SecurejoinInvited);
info!(context, "Auth verified.",);
info!(context, 0, "Auth verified.",);
secure_connection_established(context, contact_chat_id);
emit_event!(context, Event::ContactsChanged(Some(contact_id)));
emit_event!(context, Event::CONTACTS_CHANGED, contact_id, 0);
inviter_progress!(context, contact_id, 600);
if join_vg {
let field_grpid = mimeparser
.lookup_optional_field("Secure-Join-Group")
.unwrap_or_default();
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, "Chat {} not found.", &field_grpid);
error!(context, 0, "Chat {} not found.", &field_grpid);
return ret;
} else {
chat::add_contact_to_chat_ex(context, group_chat_id, contact_id, 0x1i32);
@@ -534,7 +543,7 @@ pub fn handle_securejoin_handshake(
ret = DC_HANDSHAKE_CONTINUE_NORMAL_PROCESSING;
}
if context.bob.read().unwrap().expects != DC_VC_CONTACT_CONFIRM {
info!(context, "Message belongs to a different handshake.",);
info!(context, 0, "Message belongs to a different handshake.",);
return ret;
}
let cond = {
@@ -545,7 +554,7 @@ pub fn handle_securejoin_handshake(
if cond {
warn!(
context,
"Message out of sync or belongs to a different handshake.",
0, "Message out of sync or belongs to a different handshake.",
);
return ret;
}
@@ -584,12 +593,11 @@ pub fn handle_securejoin_handshake(
return ret;
}
Contact::scaleup_origin_by_id(context, contact_id, Origin::SecurejoinJoined);
emit_event!(context, Event::ContactsChanged(None));
let cg_member_added = mimeparser
.lookup_optional_field("Chat-Group-Member-Added")
.unwrap_or_default();
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, "Message belongs to a different handshake (scaled up contact anyway to allow creation of group).");
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);
@@ -612,19 +620,19 @@ pub fn handle_securejoin_handshake(
==== Step 8 in "Out-of-band verified groups" protocol ====
============================================================ */
if let Ok(contact) = Contact::get_by_id(context, contact_id) {
if contact.is_verified(context) == VerifiedStatus::Unverified {
warn!(context, "vg-member-added-received invalid.",);
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, "vg-member-added-received invalid.",);
warn!(context, 0, "vg-member-added-received invalid.",);
return ret;
}
}
_ => {
warn!(context, "invalid step: {}", step);
warn!(context, 0, "invalid step: {}", step);
}
}
if ret == DC_HANDSHAKE_STOP_NORMAL_PROCESSING {
@@ -638,8 +646,8 @@ fn end_bobs_joining(context: &Context, status: libc::c_int) {
dc_stop_ongoing_process(context);
}
fn secure_connection_established(context: &Context, contact_chat_id: u32) {
let contact_id: u32 = chat_id_2_contact_id(context, contact_chat_id);
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()
@@ -648,10 +656,32 @@ fn secure_connection_established(context: &Context, contact_chat_id: u32) {
};
let msg = context.stock_string_repl_str(StockMessage::ContactVerified, addr);
chat::add_device_msg(context, contact_chat_id, msg);
emit_event!(context, Event::ChatModified(contact_chat_id));
emit_event!(context, Event::CHAT_MODIFIED, contact_chat_id, 0);
}
fn could_not_establish_secure_connection(context: &Context, contact_chat_id: u32, details: &str) {
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(
@@ -664,7 +694,7 @@ fn could_not_establish_secure_connection(context: &Context, contact_chat_id: u32
);
chat::add_device_msg(context, contact_chat_id, &msg);
error!(context, "{} ({})", &msg, details);
error!(context, 0, "{} ({})", &msg, details);
}
fn mark_peer_as_verified(context: &Context, fingerprint: impl AsRef<str>) -> Result<(), Error> {
@@ -688,15 +718,18 @@ fn mark_peer_as_verified(context: &Context, fingerprint: impl AsRef<str>) -> Res
* Tools: Misc.
******************************************************************************/
fn encrypted_and_signed(mimeparser: &MimeParser, expected_fingerprint: impl AsRef<str>) -> bool {
fn encrypted_and_signed(
mimeparser: &dc_mimeparser_t,
expected_fingerprint: impl AsRef<str>,
) -> bool {
if !mimeparser.e2ee_helper.encrypted {
warn!(mimeparser.context, "Message not encrypted.",);
warn!(mimeparser.context, 0, "Message not encrypted.",);
false
} else if mimeparser.e2ee_helper.signatures.len() <= 0 {
warn!(mimeparser.context, "Message not signed.",);
warn!(mimeparser.context, 0, "Message not signed.",);
false
} else if expected_fingerprint.as_ref().is_empty() {
warn!(mimeparser.context, "Fingerprint for comparison missing.",);
warn!(mimeparser.context, 0, "Fingerprint for comparison missing.",);
false
} else if !mimeparser
.e2ee_helper
@@ -705,6 +738,7 @@ fn encrypted_and_signed(mimeparser: &MimeParser, expected_fingerprint: impl AsRe
{
warn!(
mimeparser.context,
0,
"Message does not match expected fingerprint {}.",
expected_fingerprint.as_ref(),
);
@@ -723,10 +757,11 @@ pub fn handle_degrade_event(context: &Context, peerstate: &Peerstate) {
if Some(DegradeEvent::FingerprintChanged) == peerstate.degrade_event {
let contact_id: i32 = context
.sql
.query_get_value(
.query_row_col(
context,
"SELECT id FROM contacts WHERE addr=?;",
params![&peerstate.addr],
0,
)
.unwrap_or_default();
if contact_id > 0 {
@@ -741,7 +776,7 @@ pub fn handle_degrade_event(context: &Context, peerstate: &Peerstate) {
let msg = context.stock_string_repl_str(StockMessage::ContactSetupChanged, peeraddr);
chat::add_device_msg(context, contact_chat_id, msg);
emit_event!(context, Event::ChatModified(contact_chat_id));
emit_event!(context, Event::CHAT_MODIFIED, contact_chat_id, 0);
}
}
}

View File

@@ -1,15 +1,13 @@
use lettre::smtp::client::net::*;
use lettre::*;
use crate::constants::Event;
use crate::constants::*;
use crate::context::Context;
use crate::events::Event;
use crate::login_param::LoginParam;
use crate::dc_loginparam::*;
use crate::oauth2::*;
#[derive(DebugStub)]
pub struct Smtp {
#[debug_stub(some = "SmtpTransport")]
transport: Option<lettre::smtp::SmtpTransport>,
transport_connected: bool,
/// Email address we are sending from.
@@ -45,14 +43,14 @@ 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, "SMTP already connected.");
warn!(context, 0, "SMTP already connected.");
return true;
}
if lp.send_server.is_empty() || lp.send_port == 0 {
context.call_cb(Event::ErrorNetwork("SMTP bad parameters.".into()));
log_event!(context, Event::ERROR_NETWORK, 0, "SMTP bad parameters.",);
}
self.from = if let Ok(addr) = EmailAddress::new(lp.addr.clone()) {
@@ -111,14 +109,17 @@ impl Smtp {
.credentials(creds)
.connection_reuse(lettre::smtp::ConnectionReuseParameters::ReuseUnlimited);
self.transport = Some(client.transport());
context.call_cb(Event::SmtpConnected(format!(
log_event!(
context,
Event::SMTP_CONNECTED,
0,
"SMTP-LOGIN as {} ok",
lp.send_user,
)));
);
true
}
Err(err) => {
warn!(context, "SMTP: failed to establish connection {:?}", err);
warn!(context, 0, "SMTP: failed to establish connection {:?}", err);
false
}
}
@@ -140,14 +141,17 @@ impl Smtp {
match transport.send(mail) {
Ok(_) => {
context.call_cb(Event::SmtpMessageSent(
"Message was sent to SMTP server".into(),
));
log_event!(
context,
Event::SMTP_MESSAGE_SENT,
0,
"Message was sent to SMTP server",
);
self.transport_connected = true;
1
}
Err(err) => {
warn!(context, "SMTP failed to send message: {}", err);
warn!(context, 0, "SMTP failed to send message: {}", err);
self.error = Some(format!("{}", err));
0
}

View File

@@ -13,10 +13,8 @@ use crate::peerstate::*;
const DC_OPEN_READONLY: usize = 0x01;
/// A wrapper around the underlying Sqlite3 object.
#[derive(DebugStub)]
pub struct Sql {
pool: RwLock<Option<r2d2::Pool<r2d2_sqlite::SqliteConnectionManager>>>,
#[debug_stub = "ThreadLocal<String>"]
in_use: Arc<ThreadLocal<String>>,
}
@@ -37,7 +35,7 @@ impl Sql {
self.in_use.remove();
// drop closes the connection
info!(context, "Database closed.");
info!(context, 0, "Database closed.");
}
// return true on success, false on failure
@@ -157,13 +155,19 @@ impl Sql {
.unwrap_or_default()
}
pub fn query_get_value<P, T>(&self, context: &Context, query: &str, params: P) -> Option<T>
pub fn query_row_col<P, T>(
&self,
context: &Context,
query: &str,
params: P,
column: usize,
) -> Option<T>
where
P: IntoIterator,
P::Item: rusqlite::ToSql,
T: rusqlite::types::FromSql,
{
match self.query_row(query, params, |row| row.get::<_, T>(0)) {
match self.query_row(query, params, |row| row.get::<_, T>(column)) {
Ok(res) => Some(res),
Err(Error::Sql(rusqlite::Error::QueryReturnedNoRows)) => None,
Err(Error::Sql(rusqlite::Error::InvalidColumnType(
@@ -172,7 +176,7 @@ impl Sql {
rusqlite::types::Type::Null,
))) => None,
Err(err) => {
error!(context, "sql: Failed query_row: {}", err);
error!(context, 0, "sql: Failed query_row: {}", err);
None
}
}
@@ -189,7 +193,7 @@ impl Sql {
value: Option<&str>,
) -> Result<()> {
if !self.is_open() {
error!(context, "set_config(): Database not ready.");
error!(context, 0, "set_config(): Database not ready.");
return Err(Error::SqlNoConnection);
}
@@ -223,7 +227,7 @@ impl Sql {
match res {
Ok(_) => Ok(()),
Err(err) => {
error!(context, "set_config(): Cannot change value. {:?}", &err);
error!(context, 0, "set_config(): Cannot change value. {:?}", &err);
Err(err.into())
}
}
@@ -234,10 +238,11 @@ impl Sql {
if !self.is_open() || key.as_ref().is_empty() {
return None;
}
self.query_get_value(
self.query_row_col(
context,
"SELECT value FROM config WHERE keyname=?;",
params![key.as_ref()],
0,
)
}
@@ -254,20 +259,6 @@ impl Sql {
self.get_config(context, key).and_then(|s| s.parse().ok())
}
pub fn get_config_bool(&self, context: &Context, key: impl AsRef<str>) -> bool {
// Not the most obvious way to encode bool as string, but it is matter
// of backward compatibility.
self.get_config_int(context, key).unwrap_or_default() > 0
}
pub fn set_config_bool<T>(&self, context: &Context, key: T, value: bool) -> Result<()>
where
T: AsRef<str>,
{
let value = if value { Some("1") } else { None };
self.set_config(context, key, value)
}
pub fn set_config_int64(
&self,
context: &Context,
@@ -312,6 +303,7 @@ fn open(
if sql.is_open() {
error!(
context,
0,
"Cannot open, database \"{:?}\" already opened.",
dbfile.as_ref(),
);
@@ -345,6 +337,7 @@ fn open(
if !sql.table_exists("config") {
info!(
context,
0,
"First time init: creating tables in {:?}.",
dbfile.as_ref(),
);
@@ -460,6 +453,7 @@ fn open(
{
error!(
context,
0,
"Cannot create tables in new database \"{:?}\".",
dbfile.as_ref(),
);
@@ -680,7 +674,7 @@ fn open(
sql.set_config_int(context, "dbversion", 46)?;
}
if dbversion < 47 {
info!(context, "[migration] v47");
info!(context, 0, "[migration] v47");
sql.execute(
"ALTER TABLE jobs ADD COLUMN tries INTEGER DEFAULT 0;",
params![],
@@ -689,7 +683,7 @@ fn open(
sql.set_config_int(context, "dbversion", 47)?;
}
if dbversion < 48 {
info!(context, "[migration] v48");
info!(context, 0, "[migration] v48");
sql.execute(
"ALTER TABLE msgs ADD COLUMN move_state INTEGER DEFAULT 1;",
params![],
@@ -699,7 +693,7 @@ fn open(
sql.set_config_int(context, "dbversion", 48)?;
}
if dbversion < 49 {
info!(context, "[migration] v49");
info!(context, 0, "[migration] v49");
sql.execute(
"ALTER TABLE chats ADD COLUMN gossiped_timestamp INTEGER DEFAULT 0;",
params![],
@@ -708,7 +702,7 @@ fn open(
sql.set_config_int(context, "dbversion", 49)?;
}
if dbversion < 50 {
info!(context, "[migration] v50");
info!(context, 0, "[migration] v50");
if 0 != exists_before_update {
sql.set_config_int(context, "show_emails", 2)?;
}
@@ -716,7 +710,7 @@ fn open(
sql.set_config_int(context, "dbversion", 50)?;
}
if dbversion < 53 {
info!(context, "[migration] v53");
info!(context, 0, "[migration] v53");
sql.execute(
"CREATE TABLE locations ( id INTEGER PRIMARY KEY AUTOINCREMENT, latitude REAL DEFAULT 0.0, longitude REAL DEFAULT 0.0, accuracy REAL DEFAULT 0.0, timestamp INTEGER DEFAULT 0, chat_id INTEGER DEFAULT 0, from_id INTEGER DEFAULT 0);",
params![]
@@ -749,7 +743,7 @@ fn open(
sql.set_config_int(context, "dbversion", 53)?;
}
if dbversion < 54 {
info!(context, "[migration] v54");
info!(context, 0, "[migration] v54");
sql.execute(
"ALTER TABLE msgs ADD COLUMN location_id INTEGER DEFAULT 0;",
params![],
@@ -789,11 +783,11 @@ fn open(
// for newer versions, we copy files always to the blob directory and store relative paths.
// this snippet converts older databases and can be removed after some time.
info!(context, "[open] update file paths");
info!(context, 0, "[open] update file paths");
let repl_from = sql
.get_config(context, "backup_for")
.unwrap_or_else(|| context.get_blobdir().to_string_lossy().into());
.unwrap_or_else(|| to_string(context.get_blobdir()));
let repl_from = dc_ensure_no_slash_safe(&repl_from);
sql.execute(
@@ -816,7 +810,7 @@ fn open(
}
}
info!(context, "Opened {:?}.", dbfile.as_ref(),);
info!(context, 0, "Opened {:?}.", dbfile.as_ref(),);
Ok(())
}
@@ -831,6 +825,7 @@ where
Err(err) => {
error!(
context,
0,
"execute failed: {:?} for {}",
&err,
querystr.as_ref()
@@ -847,6 +842,7 @@ pub fn try_execute(context: &Context, sql: &Sql, querystr: impl AsRef<str>) -> R
Err(err) => {
warn!(
context,
0,
"Try-execute for \"{}\" failed: {}",
querystr.as_ref(),
&err,
@@ -890,7 +886,7 @@ pub fn get_rowid_with_conn(
Err(err) => {
error!(
context,
"sql: Failed to retrieve rowid: {} in {}", err, query
0, "sql: Failed to retrieve rowid: {} in {}", err, query
);
0
}
@@ -937,7 +933,7 @@ pub fn get_rowid2_with_conn(
) {
Ok(id) => id,
Err(err) => {
error!(context, "sql: Failed to retrieve rowid2: {}", err);
error!(context, 0, "sql: Failed to retrieve rowid2: {}", err);
0
}
}
@@ -947,7 +943,7 @@ pub fn housekeeping(context: &Context) {
let mut files_in_use = HashSet::new();
let mut unreferenced_count = 0;
info!(context, "Start housekeeping...");
info!(context, 0, "Start housekeeping...");
maybe_add_from_param(
context,
&mut files_in_use,
@@ -987,12 +983,12 @@ pub fn housekeeping(context: &Context) {
},
)
.unwrap_or_else(|err| {
warn!(context, "sql: failed query: {}", err);
warn!(context, 0, "sql: failed query: {}", err);
});
info!(context, "{} files in use.", files_in_use.len(),);
info!(context, 0, "{} files in use.", files_in_use.len(),);
/* go through directory and delete unused files */
let p = context.get_blobdir();
let p = std::path::Path::new(as_str(context.get_blobdir()));
match std::fs::read_dir(p) {
Ok(dir_handle) => {
/* avoid deletion of files that are just created to build a message object */
@@ -1019,16 +1015,17 @@ 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,
"Housekeeping: Keeping new unreferenced file #{}: {:?}",
unreferenced_count,
entry.file_name(),
@@ -1040,6 +1037,7 @@ pub fn housekeeping(context: &Context) {
}
info!(
context,
0,
"Housekeeping: Deleting unreferenced file #{}: {:?}",
unreferenced_count,
entry.file_name()
@@ -1051,14 +1049,15 @@ pub fn housekeeping(context: &Context) {
Err(err) => {
warn!(
context,
0,
"Housekeeping: Cannot open {}. ({})",
context.get_blobdir().display(),
as_str(context.get_blobdir()),
err
);
}
}
info!(context, "Housekeeping done.",);
info!(context, 0, "Housekeeping done.",);
}
fn is_file_in_use(files_in_use: &HashSet<String>, namespc_opt: Option<&str>, name: &str) -> bool {
@@ -1091,22 +1090,15 @@ 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, "sql: failed to add_from_param: {}", err);
warn!(context, 0, "sql: failed to add_from_param: {}", err);
});
}

View File

@@ -3,10 +3,10 @@ use std::borrow::Cow;
use strum::EnumProperty;
use strum_macros::EnumProperty;
use crate::constants::Event;
use crate::contact::*;
use crate::context::Context;
use crate::dc_tools::*;
use crate::events::Event;
use libc::free;
/// Stock strings
@@ -128,7 +128,7 @@ impl Context {
/// translation, then this string will be returned. Otherwise a
/// default (English) string is returned.
pub fn stock_str(&self, id: StockMessage) -> Cow<str> {
let ptr = self.call_cb(Event::GetString { id, count: 0 }) as *mut libc::c_char;
let ptr = self.call_cb(Event::GET_STRING, id as usize, 0) as *mut libc::c_char;
if ptr.is_null() {
Cow::Borrowed(id.fallback())
} else {
@@ -140,14 +140,12 @@ impl Context {
/// Return stock string, replacing placeholders with provided string.
///
/// This replaces both the *first* `%1$s`, `%1$d` and `%1$@`
/// This replaces both the *first* `%1$s` **and** `%1$d`
/// placeholders with the provided string.
/// (the `%1$@` variant is used on iOS, the other are used on Android and Desktop)
pub fn stock_string_repl_str(&self, id: StockMessage, insert: impl AsRef<str>) -> String {
self.stock_str(id)
.replacen("%1$s", insert.as_ref(), 1)
.replacen("%1$d", insert.as_ref(), 1)
.replacen("%1$@", insert.as_ref(), 1)
}
/// Return stock string, replacing placeholders with provided int.
@@ -160,10 +158,9 @@ impl Context {
/// Return stock string, replacing 2 placeholders with provided string.
///
/// This replaces both the *first* `%1$s`, `%1$d` and `%1$@`
/// This replaces both the *first* `%1$s` **and** `%1$d`
/// placeholders with the string in `insert` and does the same for
/// `%2$s`, `%2$d` and `%2$@` for `insert2`.
/// (the `%1$@` variant is used on iOS, the other are used on Android and Desktop)
/// `%2$s` and `%2$d` for `insert2`.
fn stock_string_repl_str2(
&self,
id: StockMessage,
@@ -173,10 +170,8 @@ impl Context {
self.stock_str(id)
.replacen("%1$s", insert.as_ref(), 1)
.replacen("%1$d", insert.as_ref(), 1)
.replacen("%1$@", insert.as_ref(), 1)
.replacen("%2$s", insert2.as_ref(), 1)
.replacen("%2$d", insert2.as_ref(), 1)
.replacen("%2$@", insert2.as_ref(), 1)
}
/// Return some kind of stock message
@@ -237,8 +232,11 @@ mod tests {
use super::*;
use crate::test_utils::*;
use std::ffi::CString;
use crate::constants::DC_CONTACT_ID_SELF;
use libc::uintptr_t;
use crate::context::dc_context_new;
use crate::types::uintptr_t;
use num_traits::ToPrimitive;
@@ -255,32 +253,36 @@ mod tests {
#[test]
fn test_stock_str() {
let t = dummy_context();
assert_eq!(t.ctx.stock_str(StockMessage::NoMessages), "No messages.");
let ctx = dc_context_new(None, std::ptr::null_mut(), None);
assert_eq!(ctx.stock_str(StockMessage::NoMessages), "No messages.");
}
fn test_stock_str_no_fallback_cb(_ctx: &Context, evt: Event) -> uintptr_t {
match evt {
Event::GetString {
id: StockMessage::NoMessages,
..
} => unsafe { "Hello there".strdup() as usize },
_ => 0,
unsafe extern "C" fn test_stock_str_no_fallback_cb(
_ctx: &Context,
evt: Event,
d1: uintptr_t,
_d2: uintptr_t,
) -> uintptr_t {
if evt == Event::GET_STRING && d1 == StockMessage::NoMessages.to_usize().unwrap() {
let tmp = CString::new("Hello there").unwrap();
dc_strdup(tmp.as_ptr()) as usize
} else {
0
}
}
#[test]
fn test_stock_str_no_fallback() {
let t = test_context(Some(Box::new(test_stock_str_no_fallback_cb)));
let t = test_context(Some(test_stock_str_no_fallback_cb));
assert_eq!(t.ctx.stock_str(StockMessage::NoMessages), "Hello there");
}
#[test]
fn test_stock_string_repl_str() {
let t = dummy_context();
let ctx = dc_context_new(None, std::ptr::null_mut(), None);
// uses %1$s substitution
assert_eq!(
t.ctx.stock_string_repl_str(StockMessage::Member, "42"),
ctx.stock_string_repl_str(StockMessage::Member, "42"),
"42 member(s)"
);
// We have no string using %1$d to test...
@@ -288,38 +290,36 @@ mod tests {
#[test]
fn test_stock_string_repl_int() {
let t = dummy_context();
let ctx = dc_context_new(None, std::ptr::null_mut(), None);
assert_eq!(
t.ctx.stock_string_repl_int(StockMessage::Member, 42),
ctx.stock_string_repl_int(StockMessage::Member, 42),
"42 member(s)"
);
}
#[test]
fn test_stock_string_repl_str2() {
let t = dummy_context();
let ctx = dc_context_new(None, std::ptr::null_mut(), None);
assert_eq!(
t.ctx
.stock_string_repl_str2(StockMessage::ServerResponse, "foo", "bar"),
ctx.stock_string_repl_str2(StockMessage::ServerResponse, "foo", "bar"),
"Response from foo: bar"
);
}
#[test]
fn test_stock_system_msg_simple() {
let t = dummy_context();
let ctx = dc_context_new(None, std::ptr::null_mut(), None);
assert_eq!(
t.ctx
.stock_system_msg(StockMessage::MsgLocationEnabled, "", "", 0),
ctx.stock_system_msg(StockMessage::MsgLocationEnabled, "", "", 0),
"Location streaming enabled."
)
}
#[test]
fn test_stock_system_msg_add_member_by_me() {
let t = dummy_context();
let ctx = dc_context_new(None, std::ptr::null_mut(), None);
assert_eq!(
t.ctx.stock_system_msg(
ctx.stock_system_msg(
StockMessage::MsgAddMember,
"alice@example.com",
"",

View File

@@ -2,14 +2,16 @@
//!
//! This module is only compiled for test runs.
use std::ffi::CStr;
use libc::uintptr_t;
use tempfile::{tempdir, TempDir};
use crate::config::Config;
use crate::constants::KeyType;
use crate::context::{Context, ContextCallback};
use crate::events::Event;
use crate::constants::{Event, KeyType};
use crate::context::{dc_context_new, dc_open, Context};
use crate::key;
use crate::types::dc_callback_t;
/// A Context and temporary directory.
///
@@ -26,15 +28,18 @@ pub struct TestContext {
/// "db.sqlite" in the [TestContext.dir] directory.
///
/// [Context]: crate::context::Context
pub fn test_context(callback: Option<Box<ContextCallback>>) -> TestContext {
let dir = tempdir().unwrap();
let dbfile = dir.path().join("db.sqlite");
let cb: Box<ContextCallback> = match callback {
Some(cb) => cb,
None => Box::new(|_, _| 0),
};
let ctx = Context::new(cb, "FakeOs".into(), dbfile).unwrap();
TestContext { ctx: ctx, dir: dir }
pub fn test_context(cb: Option<dc_callback_t>) -> TestContext {
unsafe {
let mut ctx = dc_context_new(cb, std::ptr::null_mut(), None);
let dir = tempdir().unwrap();
let dbfile = dir.path().join("db.sqlite");
assert!(
dc_open(&mut ctx, dbfile.to_str().unwrap(), None),
"Failed to open {}",
dbfile.display(),
);
TestContext { ctx: ctx, dir: dir }
}
}
/// Return a dummy [TestContext].
@@ -46,11 +51,17 @@ pub fn dummy_context() -> TestContext {
test_context(None)
}
pub fn logging_cb(_ctx: &Context, evt: Event) -> uintptr_t {
pub unsafe extern "C" fn logging_cb(
_ctx: &Context,
evt: Event,
_d1: uintptr_t,
d2: uintptr_t,
) -> uintptr_t {
let to_str = |x| CStr::from_ptr(x as *const libc::c_char).to_str().unwrap();
match evt {
Event::Info(msg) => println!("I: {}", msg),
Event::Warning(msg) => println!("W: {}", msg),
Event::Error(msg) => println!("E: {}", msg),
Event::INFO => println!("I: {}", to_str(d2)),
Event::WARNING => println!("W: {}", to_str(d2)),
Event::ERROR => println!("E: {}", to_str(d2)),
_ => (),
}
0

View File

@@ -1,57 +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_get_value::<_, String>(
context,
"SELECT token FROM tokens WHERE namespc=? AND foreign_id=?;",
params![namespace, foreign_id as i32],
)
}
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

@@ -6,15 +6,13 @@ import os
import re
if __name__ == "__main__":
if Path('src/top_evil_rs.py').exists():
os.chdir('src')
filestats = []
for fn in Path(".").glob("**/*.rs"):
s = fn.read_text()
s = re.sub(r"(?m)///.*$", "", s) # remove comments
unsafe = s.count("unsafe")
free = s.count("free(")
gotoblocks = s.count("ok_to_continue") + s.count('OK_TO_CONTINUE')
gotoblocks = s.count("ok_to_continue")
filestats.append((fn, unsafe, free, gotoblocks))
sum_unsafe, sum_free, sum_gotoblocks = 0, 0, 0

43
src/types.rs Normal file
View File

@@ -0,0 +1,43 @@
#![allow(non_camel_case_types)]
use crate::constants::Event;
use crate::context::Context;
pub use mmime::clist::*;
pub use rusqlite::ffi::*;
/// Callback function that should be given to dc_context_new().
///
/// @memberof Context
/// @param context The context object as returned by dc_context_new().
/// @param event one of the @ref DC_EVENT constants
/// @param data1 depends on the event parameter
/// @param data2 depends on the event parameter
/// @return return 0 unless stated otherwise in the event parameter documentation
pub type dc_callback_t =
unsafe extern "C" fn(_: &Context, _: Event, _: uintptr_t, _: uintptr_t) -> uintptr_t;
pub type dc_receive_imf_t = unsafe fn(
_: &Context,
_: *const libc::c_char,
_: size_t,
_: &str,
_: uint32_t,
_: uint32_t,
) -> ();
/* Purpose: Reading from IMAP servers with no dependencies to the database.
Context is only used for logging and to get information about
the online state. */
pub type dc_precheck_imf_t =
unsafe fn(_: &Context, _: *const libc::c_char, _: &str, _: u32) -> libc::c_int;
pub type dc_set_config_t = fn(_: &Context, _: &str, _: Option<&str>) -> ();
pub type dc_get_config_t = fn(_: &Context, _: &str) -> Option<String>;
pub type int32_t = i32;
pub type uintptr_t = libc::uintptr_t;
pub type size_t = libc::size_t;
pub type uint32_t = libc::c_uint;
pub type uint8_t = libc::c_uchar;
pub type uint16_t = libc::c_ushort;
pub type uint64_t = u64;

View File

@@ -1,21 +1,24 @@
//! 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};
use deltachat::chat::{self, Chat};
use deltachat::config;
use deltachat::constants::*;
use deltachat::contact::*;
use deltachat::context::*;
use deltachat::dc_imex::*;
use deltachat::dc_tools::*;
use deltachat::key::*;
use deltachat::keyring::*;
use deltachat::oauth2::*;
use deltachat::pgp::*;
use deltachat::types::*;
use deltachat::x::*;
use deltachat::Event;
use libc;
/* some data used for testing
@@ -29,99 +32,103 @@ static mut S_EM_SETUPFILE: *const libc::c_char =
as *const u8 as *const libc::c_char;
unsafe fn stress_functions(context: &Context) {
if dc_file_exist(context, "$BLOBDIR/foobar")
|| dc_file_exist(context, "$BLOBDIR/dada")
|| dc_file_exist(context, "$BLOBDIR/foobar.dadada")
|| dc_file_exist(context, "$BLOBDIR/foobar-folder")
{
dc_delete_file(context, "$BLOBDIR/foobar");
dc_delete_file(context, "$BLOBDIR/dada");
dc_delete_file(context, "$BLOBDIR/foobar.dadada");
dc_delete_file(context, "$BLOBDIR/foobar-folder");
}
dc_write_file(
context,
b"$BLOBDIR/foobar\x00" as *const u8 as *const libc::c_char,
b"content\x00" as *const u8 as *const libc::c_char as *const libc::c_void,
7,
);
assert!(dc_file_exist(context, "$BLOBDIR/foobar",));
assert!(!dc_file_exist(context, "$BLOBDIR/foobarx"));
assert_eq!(dc_get_filebytes(context, "$BLOBDIR/foobar",), 7);
let abs_path = context
.get_blobdir()
.join("foobar")
.to_string_lossy()
.to_string();
assert!(dc_is_blobdir_path(context, &abs_path));
assert!(dc_is_blobdir_path(context, "$BLOBDIR/fofo",));
assert!(!dc_is_blobdir_path(context, "/BLOBDIR/fofo",));
assert!(dc_file_exist(context, &abs_path));
assert!(dc_copy_file(context, "$BLOBDIR/foobar", "$BLOBDIR/dada",));
assert_eq!(dc_get_filebytes(context, "$BLOBDIR/dada",), 7);
let mut buf: *mut libc::c_void = ptr::null_mut();
let mut buf_bytes: libc::size_t = 0;
assert_eq!(
dc_read_file(
if 0 != dc_is_open(context) {
if dc_file_exist(context, "$BLOBDIR/foobar")
|| dc_file_exist(context, "$BLOBDIR/dada")
|| dc_file_exist(context, "$BLOBDIR/foobar.dadada")
|| dc_file_exist(context, "$BLOBDIR/foobar-folder")
{
dc_delete_file(context, "$BLOBDIR/foobar");
dc_delete_file(context, "$BLOBDIR/dada");
dc_delete_file(context, "$BLOBDIR/foobar.dadada");
dc_delete_file(context, "$BLOBDIR/foobar-folder");
}
dc_write_file(
context,
b"$BLOBDIR/dada\x00" as *const u8 as *const libc::c_char,
&mut buf,
&mut buf_bytes,
),
1
);
assert_eq!(buf_bytes, 7);
assert_eq!(
std::str::from_utf8(std::slice::from_raw_parts(buf as *const u8, buf_bytes)).unwrap(),
"content"
);
b"$BLOBDIR/foobar\x00" as *const u8 as *const libc::c_char,
b"content\x00" as *const u8 as *const libc::c_char as *const libc::c_void,
7i32 as size_t,
);
assert!(dc_file_exist(context, "$BLOBDIR/foobar",));
assert!(!dc_file_exist(context, "$BLOBDIR/foobarx"));
assert_eq!(
dc_get_filebytes(context, "$BLOBDIR/foobar",),
7i32 as libc::c_ulonglong
);
free(buf as *mut _);
assert!(dc_delete_file(context, "$BLOBDIR/foobar"));
assert!(dc_delete_file(context, "$BLOBDIR/dada"));
assert!(dc_create_folder(context, "$BLOBDIR/foobar-folder"));
assert!(dc_file_exist(context, "$BLOBDIR/foobar-folder",));
assert!(!dc_delete_file(context, "$BLOBDIR/foobar-folder"));
let fn0: *mut libc::c_char = dc_get_fine_pathNfilename(
context,
b"$BLOBDIR\x00" as *const u8 as *const libc::c_char,
b"foobar.dadada\x00" as *const u8 as *const libc::c_char,
);
assert!(!fn0.is_null());
assert_eq!(
strcmp(
let abs_path: *mut libc::c_char = dc_mprintf(
b"%s/%s\x00" as *const u8 as *const libc::c_char,
context.get_blobdir(),
b"foobar\x00" as *const u8 as *const libc::c_char,
);
assert!(dc_is_blobdir_path(context, as_str(abs_path)));
assert!(dc_is_blobdir_path(context, "$BLOBDIR/fofo",));
assert!(!dc_is_blobdir_path(context, "/BLOBDIR/fofo",));
assert!(dc_file_exist(context, as_path(abs_path)));
free(abs_path as *mut libc::c_void);
assert!(dc_copy_file(context, "$BLOBDIR/foobar", "$BLOBDIR/dada",));
assert_eq!(dc_get_filebytes(context, "$BLOBDIR/dada",), 7);
let mut buf: *mut libc::c_void = ptr::null_mut();
let mut buf_bytes: size_t = 0;
assert_eq!(
dc_read_file(
context,
b"$BLOBDIR/dada\x00" as *const u8 as *const libc::c_char,
&mut buf,
&mut buf_bytes,
),
1
);
assert_eq!(buf_bytes, 7);
assert_eq!(
std::str::from_utf8(std::slice::from_raw_parts(buf as *const u8, buf_bytes)).unwrap(),
"content"
);
free(buf as *mut _);
assert!(dc_delete_file(context, "$BLOBDIR/foobar"));
assert!(dc_delete_file(context, "$BLOBDIR/dada"));
assert!(dc_create_folder(context, "$BLOBDIR/foobar-folder"));
assert!(dc_file_exist(context, "$BLOBDIR/foobar-folder",));
assert!(dc_delete_file(context, "$BLOBDIR/foobar-folder"));
let fn0: *mut libc::c_char = dc_get_fine_pathNfilename(
context,
b"$BLOBDIR\x00" as *const u8 as *const libc::c_char,
b"foobar.dadada\x00" as *const u8 as *const libc::c_char,
);
assert!(!fn0.is_null());
assert_eq!(
strcmp(
fn0,
b"$BLOBDIR/foobar.dadada\x00" as *const u8 as *const libc::c_char,
),
0
);
dc_write_file(
context,
fn0,
b"$BLOBDIR/foobar.dadada\x00" as *const u8 as *const libc::c_char,
),
0
);
dc_write_file(
context,
fn0,
b"content\x00" as *const u8 as *const libc::c_char as *const libc::c_void,
7,
);
let fn1: *mut libc::c_char = dc_get_fine_pathNfilename(
context,
b"$BLOBDIR\x00" as *const u8 as *const libc::c_char,
b"foobar.dadada\x00" as *const u8 as *const libc::c_char,
);
assert!(!fn1.is_null());
assert_eq!(
strcmp(
fn1,
b"$BLOBDIR/foobar-1.dadada\x00" as *const u8 as *const libc::c_char,
),
0
);
assert!(dc_delete_file(context, as_path(fn0)));
free(fn0 as *mut libc::c_void);
free(fn1 as *mut libc::c_void);
b"content\x00" as *const u8 as *const libc::c_char as *const libc::c_void,
7i32 as size_t,
);
let fn1: *mut libc::c_char = dc_get_fine_pathNfilename(
context,
b"$BLOBDIR\x00" as *const u8 as *const libc::c_char,
b"foobar.dadada\x00" as *const u8 as *const libc::c_char,
);
assert!(!fn1.is_null());
assert_eq!(
strcmp(
fn1,
b"$BLOBDIR/foobar-1.dadada\x00" as *const u8 as *const libc::c_char,
),
0
);
assert!(dc_delete_file(context, as_path(fn0)));
free(fn0 as *mut libc::c_void);
free(fn1 as *mut libc::c_void);
}
let res = context.get_config(config::Config::SysConfigKeys).unwrap();
@@ -455,114 +462,175 @@ unsafe fn stress_functions(context: &Context) {
#[test]
#[ignore] // is too expensive
fn test_encryption_decryption() {
let (public_key, private_key) = dc_pgp_create_keypair("foo@bar.de").unwrap();
unsafe {
let mut bad_data: [libc::c_uchar; 4096] = [0; 4096];
let mut i_0: libc::c_int = 0i32;
while i_0 < 4096i32 {
bad_data[i_0 as usize] = (i_0 & 0xffi32) as libc::c_uchar;
i_0 += 1
}
let mut j: libc::c_int = 0i32;
private_key.split_key().unwrap();
while j < 4096 / 40 {
let bad_key = Key::from_binary(
&mut *bad_data.as_mut_ptr().offset(j as isize) as *const u8,
4096 / 2 + j,
if 0 != j & 1 {
KeyType::Public
} else {
KeyType::Private
},
);
let (public_key2, private_key2) = dc_pgp_create_keypair("two@zwo.de").unwrap();
assert!(bad_key.is_none());
j += 1
}
assert_ne!(public_key, public_key2);
let (public_key, private_key) = dc_pgp_create_keypair("foo@bar.de").unwrap();
let original_text = b"This is a test";
let mut keyring = Keyring::default();
keyring.add_owned(public_key.clone());
keyring.add_ref(&public_key2);
private_key.split_key().unwrap();
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 (public_key2, private_key2) = dc_pgp_create_keypair("two@zwo.de").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_ne!(public_key, public_key2);
let mut keyring = Keyring::default();
keyring.add_owned(private_key);
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 mut public_keyring = Keyring::default();
public_keyring.add_ref(&public_key);
let ctext = dc_pgp_pk_encrypt(
original_text as *const libc::c_void,
strlen(original_text),
&keyring,
Some(&private_key),
)
.unwrap();
let mut public_keyring2 = Keyring::default();
public_keyring2.add_owned(public_key2.clone());
assert!(!ctext.is_empty());
assert!(ctext.starts_with("-----BEGIN PGP MESSAGE-----"));
let mut valid_signatures: HashSet<String> = Default::default();
let ctext_signed_bytes = ctext.len();
let ctext_signed = CString::yolo(ctext);
let plain = dc_pgp_pk_decrypt(
ctext_signed.as_bytes(),
&keyring,
&public_keyring,
Some(&mut valid_signatures),
)
.unwrap();
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-----"));
assert_eq!(plain, original_text,);
assert_eq!(valid_signatures.len(), 1);
let ctext_unsigned_bytes = ctext.len();
let ctext_unsigned = CString::yolo(ctext);
valid_signatures.clear();
let mut keyring = Keyring::default();
keyring.add_owned(private_key);
let empty_keyring = Keyring::default();
let plain = dc_pgp_pk_decrypt(
ctext_signed.as_bytes(),
&keyring,
&empty_keyring,
Some(&mut valid_signatures),
)
.unwrap();
assert_eq!(plain, original_text);
assert_eq!(valid_signatures.len(), 0);
let mut public_keyring = Keyring::default();
public_keyring.add_ref(&public_key);
valid_signatures.clear();
let mut public_keyring2 = Keyring::default();
public_keyring2.add_owned(public_key2.clone());
let plain = dc_pgp_pk_decrypt(
ctext_signed.as_bytes(),
&keyring,
&public_keyring2,
Some(&mut valid_signatures),
)
.unwrap();
assert_eq!(plain, original_text);
assert_eq!(valid_signatures.len(), 0);
let mut valid_signatures: HashSet<String> = Default::default();
valid_signatures.clear();
let plain = dc_pgp_pk_decrypt(
ctext_signed.as_ptr() as *const _,
ctext_signed_bytes,
&keyring,
&public_keyring,
Some(&mut valid_signatures),
)
.unwrap();
public_keyring2.add_ref(&public_key);
assert_eq!(std::str::from_utf8(&plain).unwrap(), as_str(original_text),);
assert_eq!(valid_signatures.len(), 1);
let plain = dc_pgp_pk_decrypt(
ctext_signed.as_bytes(),
&keyring,
&public_keyring2,
Some(&mut valid_signatures),
)
.unwrap();
assert_eq!(plain, original_text);
assert_eq!(valid_signatures.len(), 1);
valid_signatures.clear();
valid_signatures.clear();
let empty_keyring = Keyring::default();
let plain = dc_pgp_pk_decrypt(
ctext_signed.as_ptr() as *const _,
ctext_signed_bytes,
&keyring,
&empty_keyring,
Some(&mut valid_signatures),
)
.unwrap();
assert_eq!(std::str::from_utf8(&plain).unwrap(), as_str(original_text),);
assert_eq!(valid_signatures.len(), 0);
let plain = dc_pgp_pk_decrypt(
ctext_unsigned.as_bytes(),
&keyring,
&public_keyring,
Some(&mut valid_signatures),
)
.unwrap();
valid_signatures.clear();
assert_eq!(plain, original_text);
let plain = dc_pgp_pk_decrypt(
ctext_signed.as_ptr() as *const _,
ctext_signed_bytes,
&keyring,
&public_keyring2,
Some(&mut valid_signatures),
)
.unwrap();
assert_eq!(std::str::from_utf8(&plain).unwrap(), as_str(original_text),);
assert_eq!(valid_signatures.len(), 0);
valid_signatures.clear();
valid_signatures.clear();
let mut keyring = Keyring::default();
keyring.add_ref(&private_key2);
let mut public_keyring = Keyring::default();
public_keyring.add_ref(&public_key);
public_keyring2.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_keyring2,
Some(&mut valid_signatures),
)
.unwrap();
assert_eq!(std::str::from_utf8(&plain).unwrap(), as_str(original_text),);
assert_eq!(valid_signatures.len(), 1);
assert_eq!(plain, original_text);
valid_signatures.clear();
let plain = dc_pgp_pk_decrypt(
ctext_unsigned.as_ptr() as *const _,
ctext_unsigned_bytes,
&keyring,
&public_keyring,
Some(&mut valid_signatures),
)
.unwrap();
assert_eq!(std::str::from_utf8(&plain).unwrap(), as_str(original_text),);
valid_signatures.clear();
let mut keyring = Keyring::default();
keyring.add_ref(&private_key2);
let mut public_keyring = Keyring::default();
public_keyring.add_ref(&public_key);
let plain = dc_pgp_pk_decrypt(
ctext_signed.as_ptr() as *const _,
ctext_signed_bytes,
&keyring,
&public_keyring,
None,
)
.unwrap();
assert_eq!(std::str::from_utf8(&plain).unwrap(), as_str(original_text),);
}
}
fn cb(_context: &Context, _event: Event) -> libc::uintptr_t {
unsafe extern "C" fn cb(
_context: &Context,
_event: Event,
_data1: uintptr_t,
_data2: uintptr_t,
) -> uintptr_t {
0
}
@@ -572,16 +640,21 @@ struct TestContext {
dir: TempDir,
}
fn create_test_context() -> TestContext {
unsafe fn create_test_context() -> TestContext {
let mut ctx = dc_context_new(Some(cb), std::ptr::null_mut(), None);
let dir = tempdir().unwrap();
let dbfile = dir.path().join("db.sqlite");
let ctx = Context::new(Box::new(cb), "FakeOs".into(), dbfile).unwrap();
assert!(
dc_open(&mut ctx, dbfile.to_str().unwrap(), None),
"Failed to open {}",
dbfile.display()
);
TestContext { ctx: ctx, dir: dir }
}
#[test]
fn test_dc_get_oauth2_url() {
let ctx = create_test_context();
let ctx = unsafe { create_test_context() };
let addr = "dignifiedquire@gmail.com";
let redirect_uri = "chat.delta:/com.b44t.messenger";
let res = dc_get_oauth2_url(&ctx.ctx, addr, redirect_uri);
@@ -591,7 +664,7 @@ fn test_dc_get_oauth2_url() {
#[test]
fn test_dc_get_oauth2_addr() {
let ctx = create_test_context();
let ctx = unsafe { create_test_context() };
let addr = "dignifiedquire@gmail.com";
let code = "fail";
let res = dc_get_oauth2_addr(&ctx.ctx, addr, code);
@@ -601,7 +674,7 @@ fn test_dc_get_oauth2_addr() {
#[test]
fn test_dc_get_oauth2_token() {
let ctx = create_test_context();
let ctx = unsafe { create_test_context() };
let addr = "dignifiedquire@gmail.com";
let code = "fail";
let res = dc_get_oauth2_access_token(&ctx.ctx, addr, code, 0);
@@ -619,33 +692,50 @@ fn test_stress_tests() {
#[test]
fn test_get_contacts() {
let context = create_test_context();
let contacts = Contact::get_all(&context.ctx, 0, Some("some2")).unwrap();
assert_eq!(contacts.len(), 0);
unsafe {
let context = create_test_context();
let contacts = Contact::get_all(&context.ctx, 0, Some("some2")).unwrap();
assert_eq!(contacts.len(), 0);
let id = Contact::create(&context.ctx, "bob", "bob@mail.de").unwrap();
assert_ne!(id, 0);
let id = Contact::create(&context.ctx, "bob", "bob@mail.de").unwrap();
assert_ne!(id, 0);
let contacts = Contact::get_all(&context.ctx, 0, Some("bob")).unwrap();
assert_eq!(contacts.len(), 1);
let contacts = Contact::get_all(&context.ctx, 0, Some("bob")).unwrap();
assert_eq!(contacts.len(), 1);
let contacts = Contact::get_all(&context.ctx, 0, Some("alice")).unwrap();
assert_eq!(contacts.len(), 0);
let contacts = Contact::get_all(&context.ctx, 0, Some("alice")).unwrap();
assert_eq!(contacts.len(), 0);
}
}
#[test]
fn test_chat() {
let context = create_test_context();
let contact1 = Contact::create(&context.ctx, "bob", "bob@mail.de").unwrap();
assert_ne!(contact1, 0);
unsafe {
let context = create_test_context();
let contact1 = Contact::create(&context.ctx, "bob", "bob@mail.de").unwrap();
assert_ne!(contact1, 0);
let chat_id = chat::create_by_contact_id(&context.ctx, contact1).unwrap();
assert!(chat_id > 9, "chat_id too small {}", chat_id);
let chat = Chat::load_from_db(&context.ctx, chat_id).unwrap();
let chat_id = chat::create_by_contact_id(&context.ctx, contact1).unwrap();
assert!(chat_id > 9, "chat_id too small {}", chat_id);
let chat = Chat::load_from_db(&context.ctx, chat_id).unwrap();
let chat2_id = chat::create_by_contact_id(&context.ctx, contact1).unwrap();
assert_eq!(chat2_id, chat_id);
let chat2 = Chat::load_from_db(&context.ctx, chat2_id).unwrap();
let chat2_id = chat::create_by_contact_id(&context.ctx, contact1).unwrap();
assert_eq!(chat2_id, chat_id);
let chat2 = Chat::load_from_db(&context.ctx, chat2_id).unwrap();
assert_eq!(chat2.name, chat.name);
assert_eq!(chat2.name, chat.name);
}
}
#[test]
fn test_wrong_db() {
unsafe {
let mut ctx = dc_context_new(Some(cb), std::ptr::null_mut(), None);
let dir = tempdir().unwrap();
let dbfile = dir.path().join("db.sqlite");
std::fs::write(&dbfile, b"123").unwrap();
let res = dc_open(&mut ctx, dbfile.to_str().unwrap(), None);
assert!(!res);
}
}