Compare commits

..

2 Commits

77 changed files with 5122 additions and 4922 deletions

1
.gitignore vendored
View File

@@ -16,7 +16,6 @@ python/.tox
*.egg-info
__pycache__
python/src/deltachat/capi*.so
python/.venv/
python/liveconfig*

143
Cargo.lock generated
View File

@@ -84,7 +84,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
[[package]]
name = "backtrace"
version = "0.3.38"
version = "0.3.37"
source = "registry+https://github.com/rust-lang/crates.io-index"
dependencies = [
"backtrace-sys 0.1.31 (registry+https://github.com/rust-lang/crates.io-index)",
@@ -130,7 +130,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
[[package]]
name = "bitflags"
version = "1.2.0"
version = "1.1.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
[[package]]
@@ -317,7 +317,7 @@ name = "cloudabi"
version = "0.0.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
dependencies = [
"bitflags 1.2.0 (registry+https://github.com/rust-lang/crates.io-index)",
"bitflags 1.1.0 (registry+https://github.com/rust-lang/crates.io-index)",
]
[[package]]
@@ -419,7 +419,7 @@ dependencies = [
[[package]]
name = "ctor"
version = "0.1.11"
version = "0.1.10"
source = "registry+https://github.com/rust-lang/crates.io-index"
dependencies = [
"quote 1.0.2 (registry+https://github.com/rust-lang/crates.io-index)",
@@ -481,11 +481,11 @@ dependencies = [
[[package]]
name = "deltachat"
version = "1.0.0-alpha.5"
version = "1.0.0-alpha.4"
dependencies = [
"backtrace 0.3.38 (registry+https://github.com/rust-lang/crates.io-index)",
"backtrace 0.3.37 (registry+https://github.com/rust-lang/crates.io-index)",
"base64 0.10.1 (registry+https://github.com/rust-lang/crates.io-index)",
"bitflags 1.2.0 (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)",
"charset 0.1.2 (registry+https://github.com/rust-lang/crates.io-index)",
"chrono 0.4.9 (registry+https://github.com/rust-lang/crates.io-index)",
@@ -508,6 +508,7 @@ dependencies = [
"num-traits 0.2.8 (registry+https://github.com/rust-lang/crates.io-index)",
"percent-encoding 2.1.0 (registry+https://github.com/rust-lang/crates.io-index)",
"pgp 0.2.1 (registry+https://github.com/rust-lang/crates.io-index)",
"phf 0.7.24 (git+https://github.com/sfackler/rust-phf?rev=0d00821)",
"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)",
@@ -530,8 +531,8 @@ dependencies = [
]
[[package]]
name = "deltachat-provider-database"
version = "0.2.1"
name = "deltachat-provider-overview"
version = "0.1.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
dependencies = [
"glob 0.3.0 (registry+https://github.com/rust-lang/crates.io-index)",
@@ -550,10 +551,10 @@ dependencies = [
[[package]]
name = "deltachat_ffi"
version = "1.0.0-alpha.5"
version = "1.0.0-alpha.4"
dependencies = [
"deltachat 1.0.0-alpha.5",
"deltachat-provider-database 0.2.1 (registry+https://github.com/rust-lang/crates.io-index)",
"deltachat 1.0.0-alpha.4",
"deltachat-provider-overview 0.1.0 (registry+https://github.com/rust-lang/crates.io-index)",
"human-panic 1.0.1 (registry+https://github.com/rust-lang/crates.io-index)",
"libc 0.2.62 (registry+https://github.com/rust-lang/crates.io-index)",
"num-traits 0.2.8 (registry+https://github.com/rust-lang/crates.io-index)",
@@ -686,7 +687,7 @@ name = "error-chain"
version = "0.12.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
dependencies = [
"backtrace 0.3.38 (registry+https://github.com/rust-lang/crates.io-index)",
"backtrace 0.3.37 (registry+https://github.com/rust-lang/crates.io-index)",
"version_check 0.1.5 (registry+https://github.com/rust-lang/crates.io-index)",
]
@@ -703,7 +704,7 @@ name = "failure"
version = "0.1.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
dependencies = [
"backtrace 0.3.38 (registry+https://github.com/rust-lang/crates.io-index)",
"backtrace 0.3.37 (registry+https://github.com/rust-lang/crates.io-index)",
"failure_derive 0.1.5 (registry+https://github.com/rust-lang/crates.io-index)",
]
@@ -779,7 +780,7 @@ name = "fuchsia-zircon"
version = "0.3.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
dependencies = [
"bitflags 1.2.0 (registry+https://github.com/rust-lang/crates.io-index)",
"bitflags 1.1.0 (registry+https://github.com/rust-lang/crates.io-index)",
"fuchsia-zircon-sys 0.3.3 (registry+https://github.com/rust-lang/crates.io-index)",
]
@@ -900,7 +901,7 @@ name = "human-panic"
version = "1.0.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
dependencies = [
"backtrace 0.3.38 (registry+https://github.com/rust-lang/crates.io-index)",
"backtrace 0.3.37 (registry+https://github.com/rust-lang/crates.io-index)",
"failure 0.1.5 (registry+https://github.com/rust-lang/crates.io-index)",
"os_type 2.2.0 (registry+https://github.com/rust-lang/crates.io-index)",
"serde 1.0.101 (registry+https://github.com/rust-lang/crates.io-index)",
@@ -1088,7 +1089,7 @@ dependencies = [
[[package]]
name = "lexical-core"
version = "0.4.6"
version = "0.4.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
dependencies = [
"arrayvec 0.4.11 (registry+https://github.com/rust-lang/crates.io-index)",
@@ -1299,7 +1300,7 @@ name = "nix"
version = "0.13.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
dependencies = [
"bitflags 1.2.0 (registry+https://github.com/rust-lang/crates.io-index)",
"bitflags 1.1.0 (registry+https://github.com/rust-lang/crates.io-index)",
"cc 1.0.45 (registry+https://github.com/rust-lang/crates.io-index)",
"cfg-if 0.1.9 (registry+https://github.com/rust-lang/crates.io-index)",
"libc 0.2.62 (registry+https://github.com/rust-lang/crates.io-index)",
@@ -1325,7 +1326,7 @@ name = "nom"
version = "5.0.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
dependencies = [
"lexical-core 0.4.6 (registry+https://github.com/rust-lang/crates.io-index)",
"lexical-core 0.4.5 (registry+https://github.com/rust-lang/crates.io-index)",
"memchr 2.2.1 (registry+https://github.com/rust-lang/crates.io-index)",
"version_check 0.1.5 (registry+https://github.com/rust-lang/crates.io-index)",
]
@@ -1408,7 +1409,7 @@ name = "openssl"
version = "0.10.24"
source = "registry+https://github.com/rust-lang/crates.io-index"
dependencies = [
"bitflags 1.2.0 (registry+https://github.com/rust-lang/crates.io-index)",
"bitflags 1.1.0 (registry+https://github.com/rust-lang/crates.io-index)",
"cfg-if 0.1.9 (registry+https://github.com/rust-lang/crates.io-index)",
"foreign-types 0.3.2 (registry+https://github.com/rust-lang/crates.io-index)",
"lazy_static 1.4.0 (registry+https://github.com/rust-lang/crates.io-index)",
@@ -1600,6 +1601,46 @@ dependencies = [
"x25519-dalek 0.5.2 (registry+https://github.com/rust-lang/crates.io-index)",
]
[[package]]
name = "phf"
version = "0.7.24"
source = "git+https://github.com/sfackler/rust-phf?rev=0d00821#0d0082178568036736bb6d51cb91f95ca5a616c3"
dependencies = [
"phf_macros 0.7.24 (git+https://github.com/sfackler/rust-phf?rev=0d00821)",
"phf_shared 0.7.24 (git+https://github.com/sfackler/rust-phf?rev=0d00821)",
"proc-macro-hack 0.5.9 (registry+https://github.com/rust-lang/crates.io-index)",
]
[[package]]
name = "phf_generator"
version = "0.7.24"
source = "git+https://github.com/sfackler/rust-phf?rev=0d00821#0d0082178568036736bb6d51cb91f95ca5a616c3"
dependencies = [
"phf_shared 0.7.24 (git+https://github.com/sfackler/rust-phf?rev=0d00821)",
"rand 0.7.2 (registry+https://github.com/rust-lang/crates.io-index)",
]
[[package]]
name = "phf_macros"
version = "0.7.24"
source = "git+https://github.com/sfackler/rust-phf?rev=0d00821#0d0082178568036736bb6d51cb91f95ca5a616c3"
dependencies = [
"phf_generator 0.7.24 (git+https://github.com/sfackler/rust-phf?rev=0d00821)",
"phf_shared 0.7.24 (git+https://github.com/sfackler/rust-phf?rev=0d00821)",
"proc-macro-hack 0.5.9 (registry+https://github.com/rust-lang/crates.io-index)",
"proc-macro2 0.4.30 (registry+https://github.com/rust-lang/crates.io-index)",
"quote 0.6.13 (registry+https://github.com/rust-lang/crates.io-index)",
"syn 0.15.44 (registry+https://github.com/rust-lang/crates.io-index)",
]
[[package]]
name = "phf_shared"
version = "0.7.24"
source = "git+https://github.com/sfackler/rust-phf?rev=0d00821#0d0082178568036736bb6d51cb91f95ca5a616c3"
dependencies = [
"siphasher 0.3.0 (registry+https://github.com/rust-lang/crates.io-index)",
]
[[package]]
name = "pkg-config"
version = "0.3.16"
@@ -1616,7 +1657,7 @@ version = "0.6.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
dependencies = [
"ansi_term 0.11.0 (registry+https://github.com/rust-lang/crates.io-index)",
"ctor 0.1.11 (registry+https://github.com/rust-lang/crates.io-index)",
"ctor 0.1.10 (registry+https://github.com/rust-lang/crates.io-index)",
"difference 2.0.0 (registry+https://github.com/rust-lang/crates.io-index)",
"output_vt100 0.1.2 (registry+https://github.com/rust-lang/crates.io-index)",
]
@@ -1631,6 +1672,16 @@ dependencies = [
"log 0.4.8 (registry+https://github.com/rust-lang/crates.io-index)",
]
[[package]]
name = "proc-macro-hack"
version = "0.5.9"
source = "registry+https://github.com/rust-lang/crates.io-index"
dependencies = [
"proc-macro2 1.0.3 (registry+https://github.com/rust-lang/crates.io-index)",
"quote 1.0.2 (registry+https://github.com/rust-lang/crates.io-index)",
"syn 1.0.5 (registry+https://github.com/rust-lang/crates.io-index)",
]
[[package]]
name = "proc-macro2"
version = "0.4.30"
@@ -1641,7 +1692,7 @@ dependencies = [
[[package]]
name = "proc-macro2"
version = "1.0.4"
version = "1.0.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
dependencies = [
"unicode-xid 0.2.0 (registry+https://github.com/rust-lang/crates.io-index)",
@@ -1653,7 +1704,7 @@ 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.2.0 (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)",
@@ -1683,7 +1734,7 @@ name = "pulldown-cmark"
version = "0.2.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
dependencies = [
"bitflags 1.2.0 (registry+https://github.com/rust-lang/crates.io-index)",
"bitflags 1.1.0 (registry+https://github.com/rust-lang/crates.io-index)",
]
[[package]]
@@ -1721,7 +1772,7 @@ name = "quote"
version = "1.0.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
dependencies = [
"proc-macro2 1.0.4 (registry+https://github.com/rust-lang/crates.io-index)",
"proc-macro2 1.0.3 (registry+https://github.com/rust-lang/crates.io-index)",
]
[[package]]
@@ -1784,6 +1835,7 @@ dependencies = [
"rand_chacha 0.2.1 (registry+https://github.com/rust-lang/crates.io-index)",
"rand_core 0.5.1 (registry+https://github.com/rust-lang/crates.io-index)",
"rand_hc 0.2.0 (registry+https://github.com/rust-lang/crates.io-index)",
"rand_pcg 0.2.0 (registry+https://github.com/rust-lang/crates.io-index)",
]
[[package]]
@@ -1881,6 +1933,15 @@ dependencies = [
"rand_core 0.4.2 (registry+https://github.com/rust-lang/crates.io-index)",
]
[[package]]
name = "rand_pcg"
version = "0.2.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
dependencies = [
"autocfg 0.1.6 (registry+https://github.com/rust-lang/crates.io-index)",
"rand_core 0.5.1 (registry+https://github.com/rust-lang/crates.io-index)",
]
[[package]]
name = "rand_xorshift"
version = "0.1.1"
@@ -2002,7 +2063,7 @@ name = "rusqlite"
version = "0.20.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
dependencies = [
"bitflags 1.2.0 (registry+https://github.com/rust-lang/crates.io-index)",
"bitflags 1.1.0 (registry+https://github.com/rust-lang/crates.io-index)",
"fallible-iterator 0.2.0 (registry+https://github.com/rust-lang/crates.io-index)",
"fallible-streaming-iterator 0.1.9 (registry+https://github.com/rust-lang/crates.io-index)",
"libsqlite3-sys 0.16.0 (registry+https://github.com/rust-lang/crates.io-index)",
@@ -2152,7 +2213,7 @@ name = "serde_derive"
version = "1.0.101"
source = "registry+https://github.com/rust-lang/crates.io-index"
dependencies = [
"proc-macro2 1.0.4 (registry+https://github.com/rust-lang/crates.io-index)",
"proc-macro2 1.0.3 (registry+https://github.com/rust-lang/crates.io-index)",
"quote 1.0.2 (registry+https://github.com/rust-lang/crates.io-index)",
"syn 1.0.5 (registry+https://github.com/rust-lang/crates.io-index)",
]
@@ -2212,6 +2273,11 @@ dependencies = [
"opaque-debug 0.2.3 (registry+https://github.com/rust-lang/crates.io-index)",
]
[[package]]
name = "siphasher"
version = "0.3.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
[[package]]
name = "skeptic"
version = "0.13.4"
@@ -2289,7 +2355,7 @@ version = "0.16.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
dependencies = [
"heck 0.3.1 (registry+https://github.com/rust-lang/crates.io-index)",
"proc-macro2 1.0.4 (registry+https://github.com/rust-lang/crates.io-index)",
"proc-macro2 1.0.3 (registry+https://github.com/rust-lang/crates.io-index)",
"quote 1.0.2 (registry+https://github.com/rust-lang/crates.io-index)",
"syn 1.0.5 (registry+https://github.com/rust-lang/crates.io-index)",
]
@@ -2334,7 +2400,7 @@ name = "syn"
version = "1.0.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
dependencies = [
"proc-macro2 1.0.4 (registry+https://github.com/rust-lang/crates.io-index)",
"proc-macro2 1.0.3 (registry+https://github.com/rust-lang/crates.io-index)",
"quote 1.0.2 (registry+https://github.com/rust-lang/crates.io-index)",
"unicode-xid 0.2.0 (registry+https://github.com/rust-lang/crates.io-index)",
]
@@ -2860,13 +2926,13 @@ dependencies = [
"checksum ascii_utils 0.9.3 (registry+https://github.com/rust-lang/crates.io-index)" = "71938f30533e4d95a6d17aa530939da3842c2ab6f4f84b9dae68447e4129f74a"
"checksum atty 0.2.13 (registry+https://github.com/rust-lang/crates.io-index)" = "1803c647a3ec87095e7ae7acfca019e98de5ec9a7d01343f611cf3152ed71a90"
"checksum autocfg 0.1.6 (registry+https://github.com/rust-lang/crates.io-index)" = "b671c8fb71b457dd4ae18c4ba1e59aa81793daacc361d82fcd410cef0d491875"
"checksum backtrace 0.3.38 (registry+https://github.com/rust-lang/crates.io-index)" = "690a62be8920ccf773ee00ef0968649b0e724cda8bd5b12286302b4ae955fdf5"
"checksum backtrace 0.3.37 (registry+https://github.com/rust-lang/crates.io-index)" = "5180c5a20655b14a819b652fd2378fa5f1697b6c9ddad3e695c2f9cedf6df4e2"
"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.2.0 (registry+https://github.com/rust-lang/crates.io-index)" = "8a606a02debe2813760609f57a64a2ffd27d9fdf5b2f133eaca0b248dd92cdd2"
"checksum bitflags 1.1.0 (registry+https://github.com/rust-lang/crates.io-index)" = "3d155346769a6855b86399e9bc3814ab343cd3d62c7e985113d46a0ec3c281fd"
"checksum blake2b_simd 0.5.8 (registry+https://github.com/rust-lang/crates.io-index)" = "5850aeee1552f495dd0250014cf64b82b7c8879a89d83b33bbdace2cc4f63182"
"checksum block-buffer 0.7.3 (registry+https://github.com/rust-lang/crates.io-index)" = "c0940dc441f31689269e10ac70eb1002a3a1d3ad1390e030043662eb7fe4688b"
"checksum block-cipher-trait 0.6.2 (registry+https://github.com/rust-lang/crates.io-index)" = "1c924d49bd09e7c06003acda26cd9742e796e34282ec6c1189404dee0c1f4774"
@@ -2901,13 +2967,13 @@ dependencies = [
"checksum crossbeam-epoch 0.7.2 (registry+https://github.com/rust-lang/crates.io-index)" = "fedcd6772e37f3da2a9af9bf12ebe046c0dfe657992377b4df982a2b54cd37a9"
"checksum crossbeam-queue 0.1.2 (registry+https://github.com/rust-lang/crates.io-index)" = "7c979cd6cfe72335896575c6b5688da489e420d36a27a0b9eb0c73db574b4a4b"
"checksum crossbeam-utils 0.6.6 (registry+https://github.com/rust-lang/crates.io-index)" = "04973fa96e96579258a5091af6003abde64af786b860f18622b82e026cca60e6"
"checksum ctor 0.1.11 (registry+https://github.com/rust-lang/crates.io-index)" = "3e061727ebef83bbccac7c27b9a5ff9fd83094d34cb20f4005440a9562a27de7"
"checksum ctor 0.1.10 (registry+https://github.com/rust-lang/crates.io-index)" = "5b6b2f4752cc29efbfd03474c532ce8f916f2d44ec5bb8c21f93bc76e5365528"
"checksum curve25519-dalek 1.2.3 (registry+https://github.com/rust-lang/crates.io-index)" = "8b7dcd30ba50cdf88b55b033456138b7c0ac4afdc436d82e1b79f370f24cc66d"
"checksum darling 0.9.0 (registry+https://github.com/rust-lang/crates.io-index)" = "fcfbcb0c5961907597a7d1148e3af036268f2b773886b8bb3eeb1e1281d3d3d6"
"checksum darling_core 0.9.0 (registry+https://github.com/rust-lang/crates.io-index)" = "6afc018370c3bff3eb51f89256a6bdb18b4fdcda72d577982a14954a7a0b402c"
"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 deltachat-provider-database 0.2.1 (registry+https://github.com/rust-lang/crates.io-index)" = "814dba060d9fdc7a989fccdc4810ada9d1c7a1f09131c78e42412bc6c634b93b"
"checksum deltachat-provider-overview 0.1.0 (registry+https://github.com/rust-lang/crates.io-index)" = "5bef7b3626b0f859878db86ed54e4eef317adbcc3bcc3617eb38dec52e3f40e3"
"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"
@@ -2969,7 +3035,7 @@ dependencies = [
"checksum kernel32-sys 0.2.2 (registry+https://github.com/rust-lang/crates.io-index)" = "7507624b29483431c0ba2d82aece8ca6cdba9382bff4ddd0f7490560c056098d"
"checksum lazy_static 1.4.0 (registry+https://github.com/rust-lang/crates.io-index)" = "e2abad23fbc42b3700f2f279844dc832adb2b2eb069b2df918f455c4e18cc646"
"checksum lettre 0.9.2 (registry+https://github.com/rust-lang/crates.io-index)" = "c66afaa5dfadbb81d4e00fd1d1ab057c7cd4c799c5a44e0009386d553587e728"
"checksum lexical-core 0.4.6 (registry+https://github.com/rust-lang/crates.io-index)" = "2304bccb228c4b020f3a4835d247df0a02a7c4686098d4167762cfbbe4c5cb14"
"checksum lexical-core 0.4.5 (registry+https://github.com/rust-lang/crates.io-index)" = "0d11f3928ffd249baadf9f083cdea16d7cf317b2a8be6227e1169102432a36d2"
"checksum libc 0.2.62 (registry+https://github.com/rust-lang/crates.io-index)" = "34fcd2c08d2f832f376f4173a231990fa5aef4e99fb569867318a227ef4c06ba"
"checksum libsqlite3-sys 0.16.0 (registry+https://github.com/rust-lang/crates.io-index)" = "5e5b95e89c330291768dc840238db7f9e204fd208511ab6319b56193a7f2ae25"
"checksum linked-hash-map 0.5.2 (registry+https://github.com/rust-lang/crates.io-index)" = "ae91b68aebc4ddb91978b11a1b02ddd8602a05ec19002801c5666000e05e0f83"
@@ -3020,12 +3086,17 @@ dependencies = [
"checksum percent-encoding 1.0.1 (registry+https://github.com/rust-lang/crates.io-index)" = "31010dd2e1ac33d5b46a5b413495239882813e0369f8ed8a5e266f173602f831"
"checksum percent-encoding 2.1.0 (registry+https://github.com/rust-lang/crates.io-index)" = "d4fd5641d01c8f18a23da7b6fe29298ff4b55afcccdf78973b24cf3175fee32e"
"checksum pgp 0.2.1 (registry+https://github.com/rust-lang/crates.io-index)" = "bb80b37b7debf9a98dc0caca3ed40ddf1d383691208763d0458df0b91521020f"
"checksum phf 0.7.24 (git+https://github.com/sfackler/rust-phf?rev=0d00821)" = "<none>"
"checksum phf_generator 0.7.24 (git+https://github.com/sfackler/rust-phf?rev=0d00821)" = "<none>"
"checksum phf_macros 0.7.24 (git+https://github.com/sfackler/rust-phf?rev=0d00821)" = "<none>"
"checksum phf_shared 0.7.24 (git+https://github.com/sfackler/rust-phf?rev=0d00821)" = "<none>"
"checksum pkg-config 0.3.16 (registry+https://github.com/rust-lang/crates.io-index)" = "72d5370d90f49f70bd033c3d75e87fc529fbfff9d6f7cccef07d6170079d91ea"
"checksum ppv-lite86 0.2.5 (registry+https://github.com/rust-lang/crates.io-index)" = "e3cbf9f658cdb5000fcf6f362b8ea2ba154b9f146a61c7a20d647034c6b6561b"
"checksum pretty_assertions 0.6.1 (registry+https://github.com/rust-lang/crates.io-index)" = "3f81e1644e1b54f5a68959a29aa86cde704219254669da328ecfdf6a1f09d427"
"checksum pretty_env_logger 0.3.1 (registry+https://github.com/rust-lang/crates.io-index)" = "717ee476b1690853d222af4634056d830b5197ffd747726a9a1eee6da9f49074"
"checksum proc-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.4 (registry+https://github.com/rust-lang/crates.io-index)" = "afdc77cc74ec70ed262262942ebb7dac3d479e9e5cfa2da1841c0806f6cdabcc"
"checksum proc-macro2 1.0.3 (registry+https://github.com/rust-lang/crates.io-index)" = "e98a83a9f9b331f54b924e68a66acb1bb35cb01fb0a23645139967abefb697e8"
"checksum proptest 0.9.4 (registry+https://github.com/rust-lang/crates.io-index)" = "cf147e022eacf0c8a054ab864914a7602618adba841d800a9a9868a5237a529f"
"checksum publicsuffix 1.5.3 (registry+https://github.com/rust-lang/crates.io-index)" = "9bf259a81de2b2eb9850ec990ec78e6a25319715584fd7652b9b26f96fcb1510"
"checksum pulldown-cmark 0.2.0 (registry+https://github.com/rust-lang/crates.io-index)" = "eef52fac62d0ea7b9b4dc7da092aa64ea7ec3d90af6679422d3d7e0e14b6ee15"
@@ -3050,6 +3121,7 @@ dependencies = [
"checksum rand_jitter 0.1.4 (registry+https://github.com/rust-lang/crates.io-index)" = "1166d5c91dc97b88d1decc3285bb0a99ed84b05cfd0bc2341bdf2d43fc41e39b"
"checksum rand_os 0.1.3 (registry+https://github.com/rust-lang/crates.io-index)" = "7b75f676a1e053fc562eafbb47838d67c84801e38fc1ba459e8f180deabd5071"
"checksum rand_pcg 0.1.2 (registry+https://github.com/rust-lang/crates.io-index)" = "abf9b09b01790cfe0364f52bf32995ea3c39f4d2dd011eac241d2914146d0b44"
"checksum rand_pcg 0.2.0 (registry+https://github.com/rust-lang/crates.io-index)" = "3e196346cbbc5c70c77e7b4926147ee8e383a38ee4d15d58a08098b169e492b6"
"checksum rand_xorshift 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)" = "cbf7e9e623549b0e21f6e97cf8ecf247c1a8fd2e8a992ae265314300b2455d5c"
"checksum rdrand 0.4.0 (registry+https://github.com/rust-lang/crates.io-index)" = "678054eb77286b51581ba43620cc911abf02758c91f93f479767aed0f90458b2"
"checksum redox_syscall 0.1.56 (registry+https://github.com/rust-lang/crates.io-index)" = "2439c63f3f6139d1b57529d16bc3b8bb855230c8efcc5d3a896c8bea7c3b1e84"
@@ -3084,6 +3156,7 @@ dependencies = [
"checksum sha-1 0.8.1 (registry+https://github.com/rust-lang/crates.io-index)" = "23962131a91661d643c98940b20fcaffe62d776a823247be80a48fcb8b6fce68"
"checksum sha2 0.8.0 (registry+https://github.com/rust-lang/crates.io-index)" = "7b4d8bfd0e469f417657573d8451fb33d16cfe0989359b93baf3a1ffc639543d"
"checksum sha3 0.8.2 (registry+https://github.com/rust-lang/crates.io-index)" = "dd26bc0e7a2e3a7c959bc494caf58b72ee0c71d67704e9520f736ca7e4853ecf"
"checksum siphasher 0.3.0 (registry+https://github.com/rust-lang/crates.io-index)" = "9913c75df657d84a03fa689c016b0bb2863ff0b497b26a8d6e9703f8d5df03a8"
"checksum skeptic 0.13.4 (registry+https://github.com/rust-lang/crates.io-index)" = "d6fb8ed853fdc19ce09752d63f3a2e5b5158aeb261520cd75eb618bd60305165"
"checksum slab 0.4.2 (registry+https://github.com/rust-lang/crates.io-index)" = "c111b5bd5695e56cffe5129854aa230b39c93a305372fdbb2668ca2394eea9f8"
"checksum slice-deque 0.2.4 (registry+https://github.com/rust-lang/crates.io-index)" = "ffddf594f5f597f63533d897427a570dbaa9feabaaa06595b74b71b7014507d7"

View File

@@ -1,6 +1,6 @@
[package]
name = "deltachat"
version = "1.0.0-alpha.5"
version = "1.0.0-alpha.4"
authors = ["dignifiedquire <dignifiedquire@gmail.com>"]
edition = "2018"
license = "MPL"
@@ -14,6 +14,7 @@ pgp = { version = "0.2", default-features = false }
hex = "0.3.2"
sha2 = "0.8.0"
rand = "0.6.5"
phf = { git = "https://github.com/sfackler/rust-phf", rev = "0d00821", features = ["macros"] }
smallvec = "0.6.9"
reqwest = "0.9.15"
num-derive = "0.2.5"

View File

@@ -1,6 +1,6 @@
[package]
name = "deltachat_ffi"
version = "1.0.0-alpha.5"
version = "1.0.0-alpha.4"
description = "Deltachat FFI"
authors = ["dignifiedquire <dignifiedquire@gmail.com>"]
edition = "2018"
@@ -16,7 +16,7 @@ crate-type = ["cdylib", "staticlib"]
[dependencies]
deltachat = { path = "../", default-features = false }
deltachat-provider-database = "0.2.1"
deltachat-provider-overview = "0.1.0"
libc = "0.2"
human-panic = "1.0.1"
num-traits = "0.2.6"

View File

@@ -405,14 +405,9 @@ char* dc_get_config (dc_context_t* context, const char*
/**
* Get information about the context.
*
* The information is returned by a multi-line string
* and contains information about the current configuration.
*
* If the context is not open or configured only a subset of the information
* will be available. There is no guarantee about which information will be
* included when however.
*
* @memberof dc_context_t
* @param context The context as created by dc_context_new().
* @return String which must be free()'d after usage. Never returns NULL.
@@ -3485,7 +3480,7 @@ int dc_contact_is_verified (dc_contact_t* contact);
* accessor functions. If no provider info is found, NULL will be
* returned.
*/
dc_provider_t* dc_provider_new_from_domain (const char* domain);
dc_provider_t* dc_provider_new_from_domain (char* domain);
/**
@@ -3499,7 +3494,7 @@ dc_provider_t* dc_provider_new_from_domain (const char* domain);
* accessor functions. If no provider info is found, NULL will be
* returned.
*/
dc_provider_t* dc_provider_new_from_email (const char* email);
dc_provider_t* dc_provider_new_from_email (char* email);
/**
@@ -3719,14 +3714,6 @@ int64_t dc_lot_get_timestamp (const dc_lot_t* lot);
#define DC_MSG_GIF 21
/**
* Message containing a sticker, similar to image.
* If possible, the ui should display the image without borders in a transparent way.
* A click on a sticker will offer to install the sticker set in some future.
*/
#define DC_MSG_STICKER 23
/**
* Message containing an Audio file.
* File and duration are set via dc_msg_set_file(), dc_msg_set_duration()
@@ -3904,45 +3891,6 @@ int64_t dc_lot_get_timestamp (const dc_lot_t* lot);
*/
#define DC_EVENT_SMTP_MESSAGE_SENT 103
/**
* Emitted when a message was successfully marked as deleted on the IMAP server.
*
* @param data1 0
* @param data2 (const char*) Info string in english language.
* Must not be free()'d or modified and is valid only until the callback returns.
* @return 0
*/
#define DC_EVENT_IMAP_MESSAGE_DELETED 104
/**
* Emitted when a message was successfully moved on IMAP.
*
* @param data1 0
* @param data2 (const char*) Info string in english language.
* Must not be free()'d or modified and is valid only until the callback returns.
* @return 0
*/
#define DC_EVENT_IMAP_MESSAGE_MOVED 105
/**
* Emitted when a new blob file was successfully written
*
* @param data1 0
* @param data2 (const char*) path name
* Must not be free()'d or modified and is valid only until the callback returns.
* @return 0
*/
#define DC_EVENT_NEW_BLOB_FILE 150
/**
* Emitted when a blob file was successfully deleted
*
* @param data1 0
* @param data2 (const char*) path name
* Must not be free()'d or modified and is valid only until the callback returns.
* @return 0
*/
#define DC_EVENT_DELETED_BLOB_FILE 151
/**
* The library-user should write a warning string to the log.

View File

@@ -24,7 +24,9 @@ use num_traits::{FromPrimitive, ToPrimitive};
use deltachat::contact::Contact;
use deltachat::context::Context;
use deltachat::dc_tools::{as_path, as_str, dc_strdup, to_string_lossy, OsStrExt, StrExt};
use deltachat::dc_tools::{
as_path, as_str, dc_strdup, to_string, to_string_lossy, OsStrExt, StrExt,
};
use deltachat::*;
// as C lacks a good and portable error handling,
@@ -123,15 +125,11 @@ impl ContextWrapper {
| Event::SmtpConnected(msg)
| Event::ImapConnected(msg)
| Event::SmtpMessageSent(msg)
| Event::ImapMessageDeleted(msg)
| Event::ImapMessageMoved(msg)
| Event::NewBlobFile(msg)
| Event::DeletedBlobFile(msg)
| Event::Warning(msg)
| Event::Error(msg)
| Event::ErrorNetwork(msg)
| Event::ErrorSelfNotInGroup(msg) => {
let data2 = CString::new(msg).unwrap_or_default();
let data2 = CString::new(msg).unwrap();
ffi_cb(self, event_id, 0, data2.as_ptr() as uintptr_t)
}
Event::MsgsChanged { chat_id, msg_id }
@@ -150,7 +148,7 @@ impl ContextWrapper {
ffi_cb(self, event_id, progress as uintptr_t, 0)
}
Event::ImexFileWritten(file) => {
let data1 = file.to_c_string().unwrap_or_default();
let data1 = file.to_c_string().unwrap();
ffi_cb(self, event_id, data1.as_ptr() as uintptr_t, 0)
}
Event::SecurejoinInviterProgress {
@@ -227,8 +225,8 @@ pub unsafe extern "C" fn dc_get_userdata(context: *mut dc_context_t) -> *mut lib
#[no_mangle]
pub unsafe extern "C" fn dc_open(
context: *mut dc_context_t,
dbfile: *const libc::c_char,
blobdir: *const libc::c_char,
dbfile: *mut libc::c_char,
blobdir: *mut libc::c_char,
) -> libc::c_int {
if context.is_null() || dbfile.is_null() {
eprintln!("ignoring careless call to dc_open()");
@@ -277,11 +275,11 @@ pub unsafe extern "C" fn dc_is_open(context: *mut dc_context_t) -> libc::c_int {
eprintln!("ignoring careless call to dc_is_open()");
return 0;
}
let ffi_context = &*context;
let ffi_context = &mut *context;
let inner_guard = ffi_context.inner.read().unwrap();
match *inner_guard {
Some(_) => 1,
None => 0,
Some(_) => 0,
None => 1,
}
}
@@ -300,8 +298,8 @@ pub unsafe extern "C" fn dc_get_blobdir(context: *mut dc_context_t) -> *mut libc
#[no_mangle]
pub unsafe extern "C" fn dc_set_config(
context: *mut dc_context_t,
key: *const libc::c_char,
value: *const libc::c_char,
key: *mut libc::c_char,
value: *mut libc::c_char,
) -> libc::c_int {
if context.is_null() || key.is_null() {
eprintln!("ignoring careless call to dc_set_config()");
@@ -324,7 +322,7 @@ pub unsafe extern "C" fn dc_set_config(
#[no_mangle]
pub unsafe extern "C" fn dc_get_config(
context: *mut dc_context_t,
key: *const libc::c_char,
key: *mut libc::c_char,
) -> *mut libc::c_char {
if context.is_null() || key.is_null() {
eprintln!("ignoring careless call to dc_get_config()");
@@ -349,12 +347,9 @@ pub unsafe extern "C" fn dc_get_info(context: *mut dc_context_t) -> *mut libc::c
return dc_strdup(ptr::null());
}
let ffi_context = &*context;
let guard = ffi_context.inner.read().unwrap();
let info = match guard.as_ref() {
Some(ref ctx) => ctx.get_info(),
None => context::get_info(),
};
render_info(info).unwrap_or_default().strdup()
ffi_context
.with_inner(|ctx| render_info(ctx.get_info()).unwrap_or_default().strdup())
.unwrap_or_else(|_| "".strdup())
}
fn render_info(
@@ -371,16 +366,16 @@ fn render_info(
#[no_mangle]
pub unsafe extern "C" fn dc_get_oauth2_url(
context: *mut dc_context_t,
addr: *const libc::c_char,
redirect: *const libc::c_char,
addr: *mut libc::c_char,
redirect: *mut libc::c_char,
) -> *mut libc::c_char {
if context.is_null() {
eprintln!("ignoring careless call to dc_get_oauth2_url()");
return ptr::null_mut(); // NULL explicitly defined as "unknown"
}
let ffi_context = &*context;
let addr = to_string_lossy(addr);
let redirect = to_string_lossy(redirect);
let addr = to_string(addr);
let redirect = to_string(redirect);
ffi_context
.with_inner(|ctx| match oauth2::dc_get_oauth2_url(ctx, addr, redirect) {
Some(res) => res.strdup(),
@@ -614,7 +609,7 @@ pub unsafe extern "C" fn dc_maybe_network(context: *mut dc_context_t) {
pub unsafe extern "C" fn dc_get_chatlist(
context: *mut dc_context_t,
flags: libc::c_int,
query_str: *const libc::c_char,
query_str: *mut libc::c_char,
query_id: u32,
) -> *mut dc_chatlist_t {
if context.is_null() {
@@ -735,7 +730,7 @@ pub unsafe extern "C" fn dc_send_msg(
pub unsafe extern "C" fn dc_send_text_msg(
context: *mut dc_context_t,
chat_id: u32,
text_to_send: *const libc::c_char,
text_to_send: *mut libc::c_char,
) -> u32 {
if context.is_null() || text_to_send.is_null() {
eprintln!("ignoring careless call to dc_send_text_msg()");
@@ -1019,7 +1014,7 @@ pub unsafe extern "C" fn dc_get_chat_contacts(
pub unsafe extern "C" fn dc_search_msgs(
context: *mut dc_context_t,
chat_id: u32,
query: *const libc::c_char,
query: *mut libc::c_char,
) -> *mut dc_array::dc_array_t {
if context.is_null() || query.is_null() {
eprintln!("ignoring careless call to dc_search_msgs()");
@@ -1056,7 +1051,7 @@ pub unsafe extern "C" fn dc_get_chat(context: *mut dc_context_t, chat_id: u32) -
pub unsafe extern "C" fn dc_create_group_chat(
context: *mut dc_context_t,
verified: libc::c_int,
name: *const libc::c_char,
name: *mut libc::c_char,
) -> u32 {
if context.is_null() || name.is_null() {
eprintln!("ignoring careless call to dc_create_group_chat()");
@@ -1133,7 +1128,7 @@ pub unsafe extern "C" fn dc_remove_contact_from_chat(
pub unsafe extern "C" fn dc_set_chat_name(
context: *mut dc_context_t,
chat_id: u32,
name: *const libc::c_char,
name: *mut libc::c_char,
) -> libc::c_int {
if context.is_null() || chat_id <= constants::DC_CHAT_ID_LAST_SPECIAL as u32 || name.is_null() {
eprintln!("ignoring careless call to dc_set_chat_name()");
@@ -1153,7 +1148,7 @@ pub unsafe extern "C" fn dc_set_chat_name(
pub unsafe extern "C" fn dc_set_chat_profile_image(
context: *mut dc_context_t,
chat_id: u32,
image: *const libc::c_char,
image: *mut libc::c_char,
) -> libc::c_int {
if context.is_null() || chat_id <= constants::DC_CHAT_ID_LAST_SPECIAL as u32 {
eprintln!("ignoring careless call to dc_set_chat_profile_image()");
@@ -1247,11 +1242,8 @@ pub unsafe extern "C" fn dc_forward_msgs(
let ffi_context = &*context;
ffi_context
.with_inner(|ctx| {
chat::forward_msgs(ctx, ids, chat_id)
.unwrap_or_log_default(ctx, "Failed to forward message")
})
.unwrap_or_default()
.with_inner(|ctx| chat::forward_msgs(ctx, ids, chat_id))
.unwrap_or(())
}
#[no_mangle]
@@ -1327,7 +1319,7 @@ pub unsafe extern "C" fn dc_get_msg(context: *mut dc_context_t, msg_id: u32) ->
}
#[no_mangle]
pub unsafe extern "C" fn dc_may_be_valid_addr(addr: *const libc::c_char) -> libc::c_int {
pub unsafe extern "C" fn dc_may_be_valid_addr(addr: *mut libc::c_char) -> libc::c_int {
if addr.is_null() {
eprintln!("ignoring careless call to dc_may_be_valid_addr()");
return 0;
@@ -1339,7 +1331,7 @@ pub unsafe extern "C" fn dc_may_be_valid_addr(addr: *const libc::c_char) -> libc
#[no_mangle]
pub unsafe extern "C" fn dc_lookup_contact_id_by_addr(
context: *mut dc_context_t,
addr: *const libc::c_char,
addr: *mut libc::c_char,
) -> u32 {
if context.is_null() || addr.is_null() {
eprintln!("ignoring careless call to dc_lookup_contact_id_by_addr()");
@@ -1354,8 +1346,8 @@ pub unsafe extern "C" fn dc_lookup_contact_id_by_addr(
#[no_mangle]
pub unsafe extern "C" fn dc_create_contact(
context: *mut dc_context_t,
name: *const libc::c_char,
addr: *const libc::c_char,
name: *mut libc::c_char,
addr: *mut libc::c_char,
) -> u32 {
if context.is_null() || addr.is_null() {
eprintln!("ignoring careless call to dc_create_contact()");
@@ -1374,7 +1366,7 @@ pub unsafe extern "C" fn dc_create_contact(
#[no_mangle]
pub unsafe extern "C" fn dc_add_address_book(
context: *mut dc_context_t,
addr_book: *const libc::c_char,
addr_book: *mut libc::c_char,
) -> libc::c_int {
if context.is_null() || addr_book.is_null() {
eprintln!("ignoring careless call to dc_add_address_book()");
@@ -1395,7 +1387,7 @@ pub unsafe extern "C" fn dc_add_address_book(
pub unsafe extern "C" fn dc_get_contacts(
context: *mut dc_context_t,
flags: u32,
query: *const libc::c_char,
query: *mut libc::c_char,
) -> *mut dc_array::dc_array_t {
if context.is_null() {
eprintln!("ignoring careless call to dc_get_contacts()");
@@ -1526,31 +1518,23 @@ pub unsafe extern "C" fn dc_get_contact(
pub unsafe extern "C" fn dc_imex(
context: *mut dc_context_t,
what: libc::c_int,
param1: *const libc::c_char,
_param2: *const libc::c_char,
param1: *mut libc::c_char,
param2: *mut libc::c_char,
) {
if context.is_null() {
eprintln!("ignoring careless call to dc_imex()");
return;
}
let what = match imex::ImexMode::from_i32(what as i32) {
Some(what) => what,
None => {
eprintln!("ignoring invalid argument {} to dc_imex", what);
return;
}
};
let ffi_context = &*context;
ffi_context
.with_inner(|ctx| imex::imex(ctx, what, as_opt_str(param1)))
.with_inner(|ctx| dc_imex::dc_imex(ctx, what, as_opt_str(param1), param2))
.ok();
}
#[no_mangle]
pub unsafe extern "C" fn dc_imex_has_backup(
context: *mut dc_context_t,
dir: *const libc::c_char,
dir: *mut libc::c_char,
) -> *mut libc::c_char {
if context.is_null() || dir.is_null() {
eprintln!("ignoring careless call to dc_imex_has_backup()");
@@ -1558,13 +1542,7 @@ pub unsafe extern "C" fn dc_imex_has_backup(
}
let ffi_context = &*context;
ffi_context
.with_inner(|ctx| match imex::has_backup(ctx, as_str(dir)) {
Ok(res) => res.strdup(),
Err(err) => {
error!(ctx, "dc_imex_has_backup: {}", err);
ptr::null_mut()
}
})
.with_inner(|ctx| dc_imex::dc_imex_has_backup(ctx, as_str(dir)))
.unwrap_or_else(|_| ptr::null_mut())
}
@@ -1576,13 +1554,7 @@ pub unsafe extern "C" fn dc_initiate_key_transfer(context: *mut dc_context_t) ->
}
let ffi_context = &*context;
ffi_context
.with_inner(|ctx| match imex::initiate_key_transfer(ctx) {
Ok(res) => res.strdup(),
Err(err) => {
error!(ctx, "dc_initiate_key_transfer(): {}", err);
ptr::null_mut()
}
})
.with_inner(|ctx| dc_imex::dc_initiate_key_transfer(ctx))
.unwrap_or_else(|_| ptr::null_mut())
}
@@ -1590,7 +1562,7 @@ pub unsafe extern "C" fn dc_initiate_key_transfer(context: *mut dc_context_t) ->
pub unsafe extern "C" fn dc_continue_key_transfer(
context: *mut dc_context_t,
msg_id: u32,
setup_code: *const libc::c_char,
setup_code: *mut libc::c_char,
) -> libc::c_int {
if context.is_null()
|| msg_id <= constants::DC_MSG_ID_LAST_SPECIAL as u32
@@ -1601,15 +1573,7 @@ pub unsafe extern "C" fn dc_continue_key_transfer(
}
let ffi_context = &*context;
ffi_context
.with_inner(
|ctx| match imex::continue_key_transfer(ctx, msg_id, as_str(setup_code)) {
Ok(()) => 1,
Err(err) => {
error!(ctx, "dc_continue_key_transfer: {}", err);
0
}
},
)
.with_inner(|ctx| dc_imex::dc_continue_key_transfer(ctx, msg_id, setup_code) as libc::c_int)
.unwrap_or(0)
}
@@ -1628,7 +1592,7 @@ pub unsafe extern "C" fn dc_stop_ongoing_process(context: *mut dc_context_t) {
#[no_mangle]
pub unsafe extern "C" fn dc_check_qr(
context: *mut dc_context_t,
qr: *const libc::c_char,
qr: *mut libc::c_char,
) -> *mut dc_lot_t {
if context.is_null() || qr.is_null() {
eprintln!("ignoring careless call to dc_check_qr()");
@@ -1665,7 +1629,7 @@ pub unsafe extern "C" fn dc_get_securejoin_qr(
#[no_mangle]
pub unsafe extern "C" fn dc_join_securejoin(
context: *mut dc_context_t,
qr: *const libc::c_char,
qr: *mut libc::c_char,
) -> u32 {
if context.is_null() || qr.is_null() {
eprintln!("ignoring careless call to dc_join_securejoin()");
@@ -2136,7 +2100,7 @@ pub unsafe extern "C" fn dc_chat_get_profile_image(chat: *mut dc_chat_t) -> *mut
let ffi_context = &*ffi_chat.context;
ffi_context
.with_inner(|ctx| match ffi_chat.chat.get_profile_image(ctx) {
Some(p) => p.to_str().unwrap_or_default().to_string().strdup(),
Some(p) => p.to_str().unwrap().to_string().strdup(),
None => ptr::null_mut(),
})
.unwrap_or_else(|_| ptr::null_mut())
@@ -2381,11 +2345,7 @@ pub unsafe extern "C" fn dc_msg_get_filemime(msg: *mut dc_msg_t) -> *mut libc::c
return dc_strdup(ptr::null());
}
let ffi_msg = &*msg;
if let Some(x) = ffi_msg.message.get_filemime() {
x.strdup()
} else {
return dc_strdup(ptr::null());
}
ffi_msg.message.get_filemime().strdup()
}
#[no_mangle]
@@ -2481,7 +2441,7 @@ pub unsafe extern "C" fn dc_msg_get_summarytext(
.with_inner(|ctx| {
ffi_msg
.message
.get_summarytext(ctx, approx_characters.try_into().unwrap_or_default())
.get_summarytext(ctx, approx_characters.try_into().unwrap())
})
.unwrap_or_default()
.strdup()
@@ -2582,7 +2542,7 @@ pub unsafe extern "C" fn dc_msg_get_setupcodebegin(msg: *mut dc_msg_t) -> *mut l
}
#[no_mangle]
pub unsafe extern "C" fn dc_msg_set_text(msg: *mut dc_msg_t, text: *const libc::c_char) {
pub unsafe extern "C" fn dc_msg_set_text(msg: *mut dc_msg_t, text: *mut libc::c_char) {
if msg.is_null() {
eprintln!("ignoring careless call to dc_msg_set_text()");
return;
@@ -2595,8 +2555,8 @@ pub unsafe extern "C" fn dc_msg_set_text(msg: *mut dc_msg_t, text: *const libc::
#[no_mangle]
pub unsafe extern "C" fn dc_msg_set_file(
msg: *mut dc_msg_t,
file: *const libc::c_char,
filemime: *const libc::c_char,
file: *mut libc::c_char,
filemime: *mut libc::c_char,
) {
if msg.is_null() || file.is_null() {
eprintln!("ignoring careless call to dc_msg_set_file()");
@@ -2773,7 +2733,7 @@ pub unsafe extern "C" fn dc_contact_get_profile_image(
ffi_contact
.contact
.get_profile_image(ctx)
.map(|p| p.to_str().unwrap_or_default().to_string().strdup())
.map(|p| p.to_str().unwrap().to_string().strdup())
.unwrap_or_else(|| std::ptr::null_mut())
})
.unwrap_or_else(|_| ptr::null_mut())

View File

@@ -1,18 +1,18 @@
extern crate deltachat_provider_database;
extern crate deltachat_provider_overview;
use std::ptr;
use deltachat::dc_tools::{as_str, StrExt};
use deltachat_provider_database::StatusState;
use deltachat_provider_overview::StatusState;
#[no_mangle]
pub type dc_provider_t = deltachat_provider_database::Provider;
pub type dc_provider_t = deltachat_provider_overview::Provider;
#[no_mangle]
pub unsafe extern "C" fn dc_provider_new_from_domain(
domain: *const libc::c_char,
) -> *const dc_provider_t {
match deltachat_provider_database::get_provider_info(as_str(domain)) {
match deltachat_provider_overview::get_provider_info(as_str(domain)) {
Some(provider) => provider,
None => ptr::null(),
}
@@ -22,8 +22,8 @@ pub unsafe extern "C" fn dc_provider_new_from_domain(
pub unsafe extern "C" fn dc_provider_new_from_email(
email: *const libc::c_char,
) -> *const dc_provider_t {
let domain = deltachat_provider_database::get_domain_from_email(as_str(email));
match deltachat_provider_database::get_provider_info(domain) {
let domain = deltachat_provider_overview::get_domain_from_email(as_str(email));
match deltachat_provider_overview::get_provider_info(domain) {
Some(provider) => provider,
None => ptr::null(),
}
@@ -44,7 +44,7 @@ pub unsafe extern "C" fn dc_provider_get_overview_page(
null_guard!(provider);
format!(
"{}/{}",
deltachat_provider_database::PROVIDER_OVERVIEW_URL,
deltachat_provider_overview::PROVIDER_OVERVIEW_URL,
(*provider).overview_page
)
.strdup()

View File

@@ -1,4 +1,5 @@
use std::path::Path;
use std::ptr;
use std::str::FromStr;
use deltachat::chat::{self, Chat};
@@ -8,10 +9,10 @@ use deltachat::configure::*;
use deltachat::constants::*;
use deltachat::contact::*;
use deltachat::context::*;
use deltachat::dc_imex::*;
use deltachat::dc_receive_imf::*;
use deltachat::dc_tools::*;
use deltachat::error::Error;
use deltachat::imex::*;
use deltachat::job::*;
use deltachat::location;
use deltachat::lot::LotState;
@@ -96,13 +97,22 @@ pub unsafe fn dc_reset_tables(context: &Context, bits: i32) -> i32 {
fn dc_poke_eml_file(context: &Context, filename: impl AsRef<Path>) -> Result<(), Error> {
let data = dc_read_file(context, filename)?;
unsafe { dc_receive_imf(context, &data, "import", 0, 0) };
unsafe {
dc_receive_imf(
context,
data.as_ptr() as *const _,
data.len(),
"import",
0,
0,
)
};
Ok(())
}
/// Import a file to the database.
/// For testing, import a folder with eml-files, a single eml-file, e-mail plus public key and so on.
/// For normal importing, use imex().
/// For normal importing, use dc_imex().
///
/// @private @memberof Context
/// @param context The context as created by dc_context_new().
@@ -119,13 +129,13 @@ fn poke_spec(context: &Context, spec: *const libc::c_char) -> libc::c_int {
/* if `spec` is given, remember it for later usage; if it is not given, try to use the last one */
if !spec.is_null() {
real_spec = to_string_lossy(spec);
real_spec = to_string(spec);
context
.sql
.set_raw_config(context, "import_spec", Some(&real_spec))
.set_config(context, "import_spec", Some(&real_spec))
.unwrap();
} else {
let rs = context.sql.get_raw_config(context, "import_spec");
let rs = context.sql.get_config(context, "import_spec");
if rs.is_none() {
error!(context, "Import: No file or folder given.");
return 0;
@@ -317,8 +327,12 @@ pub unsafe fn dc_cmdline(context: &Context, line: &str) -> Result<(), failure::E
arg1.strdup() as *const _
};
let arg2 = args.next().unwrap_or_default();
let arg2_c = if arg2.is_empty() {
std::ptr::null()
} else {
arg2.strdup() as *const _
};
let blobdir = context.get_blobdir();
match arg0 {
"help" | "?" => match arg1 {
// TODO: reuse commands definition in main.rs.
@@ -405,13 +419,18 @@ pub unsafe fn dc_cmdline(context: &Context, line: &str) -> Result<(), failure::E
============================================="
),
},
"initiate-key-transfer" => match initiate_key_transfer(context) {
Ok(setup_code) => println!(
"Setup code for the transferred setup message: {}",
setup_code,
),
Err(err) => bail!("Failed to generate setup code: {}", err),
},
"initiate-key-transfer" => {
let setup_code = dc_initiate_key_transfer(context);
if !setup_code.is_null() {
println!(
"Setup code for the transferred setup message: {}",
as_str(setup_code),
);
free(setup_code as *mut libc::c_void);
} else {
bail!("Failed to generate setup code");
};
}
"get-setupcodebegin" => {
ensure!(!arg1.is_empty(), "Argument <msg-id> missing.");
let msg_id: u32 = arg1.parse()?;
@@ -432,28 +451,33 @@ pub unsafe fn dc_cmdline(context: &Context, line: &str) -> Result<(), failure::E
!arg1.is_empty() && !arg2.is_empty(),
"Arguments <msg-id> <setup-code> expected"
);
continue_key_transfer(context, arg1.parse()?, &arg2)?;
if !dc_continue_key_transfer(context, arg1.parse()?, arg2_c) {
bail!("Continue key transfer failed");
}
}
"has-backup" => {
has_backup(context, blobdir)?;
let ret = dc_imex_has_backup(context, context.get_blobdir());
if ret.is_null() {
println!("No backup found.");
}
}
"export-backup" => {
imex(context, ImexMode::ExportBackup, Some(blobdir));
dc_imex(context, 11, Some(context.get_blobdir()), ptr::null());
}
"import-backup" => {
ensure!(!arg1.is_empty(), "Argument <backup-file> missing.");
imex(context, ImexMode::ImportBackup, Some(arg1));
dc_imex(context, 12, Some(arg1), ptr::null());
}
"export-keys" => {
imex(context, ImexMode::ExportSelfKeys, Some(blobdir));
dc_imex(context, 1, Some(context.get_blobdir()), ptr::null());
}
"import-keys" => {
imex(context, ImexMode::ImportSelfKeys, Some(blobdir));
dc_imex(context, 2, Some(context.get_blobdir()), ptr::null());
}
"export-setup" => {
let setup_code = create_setup_code(context);
let file_name = blobdir.join("autocrypt-setup-message.html");
let file_content = render_setup_file(context, &setup_code)?;
let setup_code = dc_create_setup_code(context);
let file_name = context.get_blobdir().join("autocrypt-setup-message.html");
let file_content = dc_render_setup_file(context, &setup_code)?;
std::fs::write(&file_name, file_content)?;
println!(
"Setup message written to: {}\nSetup code: {}",
@@ -864,7 +888,7 @@ pub unsafe fn dc_cmdline(context: &Context, line: &str) -> Result<(), failure::E
let mut msg_ids = [0; 1];
let chat_id = arg2.parse()?;
msg_ids[0] = arg1.parse()?;
chat::forward_msgs(context, &msg_ids, chat_id)?;
chat::forward_msgs(context, &msg_ids, chat_id);
}
"markseen" => {
ensure!(!arg1.is_empty(), "Argument <msg-id> missing.");
@@ -977,6 +1001,7 @@ pub unsafe fn dc_cmdline(context: &Context, line: &str) -> Result<(), failure::E
}
free(arg1_c as *mut _);
free(arg2_c as *mut _);
Ok(())
}

View File

@@ -16,16 +16,13 @@ pub unsafe fn charconv(
) -> libc::c_int {
assert!(!fromcode.is_null(), "invalid fromcode");
assert!(!s.is_null(), "invalid input string");
if let Some(encoding) = charset::Charset::for_label(
CStr::from_ptr(fromcode)
.to_str()
.unwrap_or_default()
.as_bytes(),
) {
if let Some(encoding) =
charset::Charset::for_label(CStr::from_ptr(fromcode).to_str().unwrap().as_bytes())
{
let data = std::slice::from_raw_parts(s as *const u8, strlen(s));
let (res, _, _) = encoding.decode(data);
let res_c = CString::new(res.as_bytes()).unwrap_or_default();
let res_c = CString::new(res.as_bytes()).unwrap();
*result = strdup(res_c.as_ptr()) as *mut _;
MAIL_CHARCONV_NO_ERROR as libc::c_int

View File

@@ -1,71 +0,0 @@
pub const MAIL_ERROR_SSL: libc::c_uint = 58;
pub const MAIL_ERROR_FOLDER: libc::c_uint = 57;
pub const MAIL_ERROR_UNABLE: libc::c_uint = 56;
pub const MAIL_ERROR_SYSTEM: libc::c_uint = 55;
pub const MAIL_ERROR_COMMAND: libc::c_uint = 54;
pub const MAIL_ERROR_SEND: libc::c_uint = 53;
pub const MAIL_ERROR_CHAR_ENCODING_FAILED: libc::c_uint = 52;
pub const MAIL_ERROR_SUBJECT_NOT_FOUND: libc::c_uint = 51;
/* 50 */
pub const MAIL_ERROR_PROGRAM_ERROR: libc::c_uint = 50;
pub const MAIL_ERROR_NO_PERMISSION: libc::c_uint = 49;
pub const MAIL_ERROR_COMMAND_NOT_SUPPORTED: libc::c_uint = 48;
pub const MAIL_ERROR_NO_APOP: libc::c_uint = 47;
pub const MAIL_ERROR_READONLY: libc::c_uint = 46;
pub const MAIL_ERROR_FATAL: libc::c_uint = 45;
pub const MAIL_ERROR_CLOSE: libc::c_uint = 44;
pub const MAIL_ERROR_CAPABILITY: libc::c_uint = 43;
pub const MAIL_ERROR_PROTOCOL: libc::c_uint = 42;
/* misc errors */
pub const MAIL_ERROR_MISC: libc::c_uint = 41;
/* 40 */
pub const MAIL_ERROR_EXPUNGE: libc::c_uint = 40;
pub const MAIL_ERROR_NO_TLS: libc::c_uint = 39;
pub const MAIL_ERROR_CACHE_MISS: libc::c_uint = 38;
pub const MAIL_ERROR_STARTTLS: libc::c_uint = 37;
pub const MAIL_ERROR_MOVE: libc::c_uint = 36;
pub const MAIL_ERROR_FOLDER_NOT_FOUND: libc::c_uint = 35;
pub const MAIL_ERROR_REMOVE: libc::c_uint = 34;
pub const MAIL_ERROR_PART_NOT_FOUND: libc::c_uint = 33;
pub const MAIL_ERROR_INVAL: libc::c_uint = 32;
pub const MAIL_ERROR_PARSE: libc::c_uint = 31;
/* 30 */
pub const MAIL_ERROR_MSG_NOT_FOUND: libc::c_uint = 30;
pub const MAIL_ERROR_DISKSPACE: libc::c_uint = 29;
pub const MAIL_ERROR_SEARCH: libc::c_uint = 28;
pub const MAIL_ERROR_STORE: libc::c_uint = 27;
pub const MAIL_ERROR_FETCH: libc::c_uint = 26;
pub const MAIL_ERROR_COPY: libc::c_uint = 25;
pub const MAIL_ERROR_APPEND: libc::c_uint = 24;
pub const MAIL_ERROR_LSUB: libc::c_uint = 23;
pub const MAIL_ERROR_LIST: libc::c_uint = 22;
pub const MAIL_ERROR_UNSUBSCRIBE: libc::c_uint = 21;
/* 20 */
pub const MAIL_ERROR_SUBSCRIBE: libc::c_uint = 20;
pub const MAIL_ERROR_STATUS: libc::c_uint = 19;
pub const MAIL_ERROR_MEMORY: libc::c_uint = 18;
pub const MAIL_ERROR_SELECT: libc::c_uint = 17;
pub const MAIL_ERROR_EXAMINE: libc::c_uint = 16;
pub const MAIL_ERROR_CHECK: libc::c_uint = 15;
pub const MAIL_ERROR_RENAME: libc::c_uint = 14;
pub const MAIL_ERROR_NOOP: libc::c_uint = 13;
pub const MAIL_ERROR_LOGOUT: libc::c_uint = 12;
pub const MAIL_ERROR_DELETE: libc::c_uint = 11;
/* 10 */
pub const MAIL_ERROR_CREATE: libc::c_uint = 10;
pub const MAIL_ERROR_LOGIN: libc::c_uint = 9;
pub const MAIL_ERROR_STREAM: libc::c_uint = 8;
pub const MAIL_ERROR_FILE: libc::c_uint = 7;
pub const MAIL_ERROR_BAD_STATE: libc::c_uint = 6;
pub const MAIL_ERROR_CONNECT: libc::c_uint = 5;
pub const MAIL_ERROR_UNKNOWN: libc::c_uint = 4;
pub const MAIL_ERROR_NOT_IMPLEMENTED: libc::c_uint = 3;
pub const MAIL_NO_ERROR_NON_AUTHENTICATED: libc::c_uint = 2;
pub const MAIL_NO_ERROR_AUTHENTICATED: libc::c_uint = 1;
pub const MAIL_NO_ERROR: libc::c_uint = 0;
pub const MAILIMF_ERROR_FILE: libc::c_uint = 4;
pub const MAILIMF_ERROR_INVAL: libc::c_uint = 3;
pub const MAILIMF_ERROR_MEMORY: libc::c_uint = 2;
pub const MAILIMF_ERROR_PARSE: libc::c_uint = 1;
pub const MAILIMF_NO_ERROR: libc::c_uint = 0;

View File

@@ -1,386 +0,0 @@
use crate::clist::*;
use crate::mailimf::types::*;
use crate::mailmime::types::*;
use std::ffi::CStr;
pub unsafe fn display_mime(mut mime: *mut Mailmime) {
let mut cur: *mut clistiter = 0 as *mut clistiter;
println!("{}", (*mime).mm_type);
match (*mime).mm_type as u32 {
MAILMIME_SINGLE => {
println!("single part");
}
MAILMIME_MULTIPLE => {
println!("multipart");
}
MAILMIME_MESSAGE => println!("message"),
_ => {}
}
if !(*mime).mm_mime_fields.is_null() {
if !(*(*(*mime).mm_mime_fields).fld_list).first.is_null() {
print!("MIME headers begin");
display_mime_fields((*mime).mm_mime_fields);
println!("MIME headers end");
}
}
display_mime_content((*mime).mm_content_type);
match (*mime).mm_type as u32 {
MAILMIME_SINGLE => {
display_mime_data((*mime).mm_data.mm_single);
}
MAILMIME_MULTIPLE => {
cur = (*(*mime).mm_data.mm_multipart.mm_mp_list).first;
while !cur.is_null() {
display_mime(
(if !cur.is_null() {
(*cur).data
} else {
0 as *mut libc::c_void
}) as *mut Mailmime,
);
cur = if !cur.is_null() {
(*cur).next
} else {
0 as *mut clistcell
}
}
}
MAILMIME_MESSAGE => {
if !(*mime).mm_data.mm_message.mm_fields.is_null() {
if !(*(*(*mime).mm_data.mm_message.mm_fields).fld_list)
.first
.is_null()
{
println!("headers begin");
display_fields((*mime).mm_data.mm_message.mm_fields);
println!("headers end");
}
if !(*mime).mm_data.mm_message.mm_msg_mime.is_null() {
display_mime((*mime).mm_data.mm_message.mm_msg_mime);
}
}
}
_ => {}
};
}
unsafe fn display_mime_content(mut content_type: *mut mailmime_content) {
print!("type: ");
display_mime_type((*content_type).ct_type);
println!(
"/{}",
CStr::from_ptr((*content_type).ct_subtype).to_str().unwrap()
);
}
unsafe fn display_mime_type(mut type_0: *mut mailmime_type) {
match (*type_0).tp_type {
1 => {
display_mime_discrete_type((*type_0).tp_data.tp_discrete_type);
}
2 => {
display_mime_composite_type((*type_0).tp_data.tp_composite_type);
}
_ => {}
};
}
unsafe fn display_mime_composite_type(mut ct: *mut mailmime_composite_type) {
match (*ct).ct_type {
1 => {
print!("message");
}
2 => {
print!("multipart");
}
3 => {
print!("{}", CStr::from_ptr((*ct).ct_token).to_str().unwrap());
}
_ => {}
};
}
unsafe fn display_mime_discrete_type(mut discrete_type: *mut mailmime_discrete_type) {
match (*discrete_type).dt_type {
1 => {
print!("text");
}
2 => {
print!("image");
}
3 => {
print!("audio");
}
4 => {
print!("video");
}
5 => {
print!("application");
}
6 => {
print!("{}", (*discrete_type).dt_extension as u8 as char);
}
_ => {}
};
}
pub unsafe fn display_mime_data(mut data: *mut mailmime_data) {
match (*data).dt_type {
0 => {
println!(
"data : {} bytes",
(*data).dt_data.dt_text.dt_length as libc::c_uint,
);
}
1 => {
println!(
"data (file) : {}",
CStr::from_ptr((*data).dt_data.dt_filename)
.to_str()
.unwrap()
);
}
_ => {}
};
}
unsafe fn display_mime_dsp_parm(mut param: *mut mailmime_disposition_parm) {
match (*param).pa_type {
0 => {
println!(
"filename: {}",
CStr::from_ptr((*param).pa_data.pa_filename)
.to_str()
.unwrap()
);
}
_ => {}
};
}
unsafe fn display_mime_disposition(mut disposition: *mut mailmime_disposition) {
let mut cur: *mut clistiter = 0 as *mut clistiter;
cur = (*(*disposition).dsp_parms).first;
while !cur.is_null() {
let mut param: *mut mailmime_disposition_parm = 0 as *mut mailmime_disposition_parm;
param = (if !cur.is_null() {
(*cur).data
} else {
0 as *mut libc::c_void
}) as *mut mailmime_disposition_parm;
display_mime_dsp_parm(param);
cur = if !cur.is_null() {
(*cur).next
} else {
0 as *mut clistcell
}
}
}
unsafe fn display_mime_field(mut field: *mut mailmime_field) {
match (*field).fld_type {
1 => {
print!("content-type: ");
display_mime_content((*field).fld_data.fld_content);
println!("");
}
6 => {
display_mime_disposition((*field).fld_data.fld_disposition);
}
_ => {}
};
}
unsafe fn display_mime_fields(mut fields: *mut mailmime_fields) {
let mut cur: *mut clistiter = 0 as *mut clistiter;
cur = (*(*fields).fld_list).first;
while !cur.is_null() {
let mut field: *mut mailmime_field = 0 as *mut mailmime_field;
field = (if !cur.is_null() {
(*cur).data
} else {
0 as *mut libc::c_void
}) as *mut mailmime_field;
display_mime_field(field);
cur = if !cur.is_null() {
(*cur).next
} else {
0 as *mut clistcell
}
}
}
unsafe fn display_date_time(mut d: *mut mailimf_date_time) {
print!(
"{:02}/{:02}/{:02} {:02}:{:02}:{:02} +{:04}",
(*d).dt_day,
(*d).dt_month,
(*d).dt_year,
(*d).dt_hour,
(*d).dt_min,
(*d).dt_sec,
(*d).dt_zone,
);
}
unsafe fn display_orig_date(mut orig_date: *mut mailimf_orig_date) {
display_date_time((*orig_date).dt_date_time);
}
unsafe fn display_mailbox(mut mb: *mut mailimf_mailbox) {
if !(*mb).mb_display_name.is_null() {
print!(
"{}",
CStr::from_ptr((*mb).mb_display_name).to_str().unwrap()
);
}
print!("<{}>", CStr::from_ptr((*mb).mb_addr_spec).to_str().unwrap());
}
unsafe fn display_mailbox_list(mut mb_list: *mut mailimf_mailbox_list) {
let mut cur: *mut clistiter = 0 as *mut clistiter;
cur = (*(*mb_list).mb_list).first;
while !cur.is_null() {
let mut mb: *mut mailimf_mailbox = 0 as *mut mailimf_mailbox;
mb = (if !cur.is_null() {
(*cur).data
} else {
0 as *mut libc::c_void
}) as *mut mailimf_mailbox;
display_mailbox(mb);
if !if !cur.is_null() {
(*cur).next
} else {
0 as *mut clistcell
}
.is_null()
{
print!(", ");
}
cur = if !cur.is_null() {
(*cur).next
} else {
0 as *mut clistcell
}
}
}
unsafe fn display_group(mut group: *mut mailimf_group) {
let mut cur: *mut clistiter = 0 as *mut clistiter;
print!(
"{}: ",
CStr::from_ptr((*group).grp_display_name).to_str().unwrap()
);
cur = (*(*(*group).grp_mb_list).mb_list).first;
while !cur.is_null() {
let mut mb: *mut mailimf_mailbox = 0 as *mut mailimf_mailbox;
mb = (if !cur.is_null() {
(*cur).data
} else {
0 as *mut libc::c_void
}) as *mut mailimf_mailbox;
display_mailbox(mb);
cur = if !cur.is_null() {
(*cur).next
} else {
0 as *mut clistcell
}
}
print!("; ");
}
unsafe fn display_address(mut a: *mut mailimf_address) {
match (*a).ad_type {
2 => {
display_group((*a).ad_data.ad_group);
}
1 => {
display_mailbox((*a).ad_data.ad_mailbox);
}
_ => {}
};
}
unsafe fn display_address_list(mut addr_list: *mut mailimf_address_list) {
let mut cur: *mut clistiter = 0 as *mut clistiter;
cur = (*(*addr_list).ad_list).first;
while !cur.is_null() {
let mut addr: *mut mailimf_address = 0 as *mut mailimf_address;
addr = (if !cur.is_null() {
(*cur).data
} else {
0 as *mut libc::c_void
}) as *mut mailimf_address;
display_address(addr);
if !if !cur.is_null() {
(*cur).next
} else {
0 as *mut clistcell
}
.is_null()
{
print!(", ");
}
cur = if !cur.is_null() {
(*cur).next
} else {
0 as *mut clistcell
}
}
}
unsafe fn display_from(mut from: *mut mailimf_from) {
display_mailbox_list((*from).frm_mb_list);
}
unsafe fn display_to(mut to: *mut mailimf_to) {
display_address_list((*to).to_addr_list);
}
unsafe fn display_cc(mut cc: *mut mailimf_cc) {
display_address_list((*cc).cc_addr_list);
}
unsafe fn display_subject(mut subject: *mut mailimf_subject) {
print!("{}", CStr::from_ptr((*subject).sbj_value).to_str().unwrap());
}
unsafe fn display_field(mut field: *mut mailimf_field) {
match (*field).fld_type {
9 => {
print!("Date: ");
display_orig_date((*field).fld_data.fld_orig_date);
println!("");
}
10 => {
print!("From: ");
display_from((*field).fld_data.fld_from);
println!("");
}
13 => {
print!("To: ");
display_to((*field).fld_data.fld_to);
println!("");
}
14 => {
print!("Cc: ");
display_cc((*field).fld_data.fld_cc);
println!("");
}
19 => {
print!("Subject: ");
display_subject((*field).fld_data.fld_subject);
println!("");
}
16 => {
println!(
"Message-ID: {}",
CStr::from_ptr((*(*field).fld_data.fld_message_id).mid_value)
.to_str()
.unwrap(),
);
}
_ => {}
};
}
unsafe fn display_fields(mut fields: *mut mailimf_fields) {
let mut cur: *mut clistiter = 0 as *mut clistiter;
cur = (*(*fields).fld_list).first;
while !cur.is_null() {
let mut f: *mut mailimf_field = 0 as *mut mailimf_field;
f = (if !cur.is_null() {
(*cur).data
} else {
0 as *mut libc::c_void
}) as *mut mailimf_field;
display_field(f);
cur = if !cur.is_null() {
(*cur).next
} else {
0 as *mut clistcell
}
}
}

View File

@@ -19,24 +19,44 @@
pub mod charconv;
pub mod chash;
pub mod clist;
pub mod display;
pub mod mailimf;
pub mod mailimf_types;
pub mod mailimf_types_helper;
pub mod mailimf_write_generic;
pub mod mailmime;
pub mod mailmime_content;
pub mod mailmime_decode;
pub mod mailmime_disposition;
pub mod mailmime_types;
pub mod mailmime_types_helper;
pub mod mailmime_write_generic;
pub mod mailmime_write_mem;
pub mod mmapstring;
pub mod other;
pub use self::charconv::*;
pub use self::chash::*;
pub use self::clist::*;
pub use self::display::*;
pub use self::mailimf::*;
pub use self::mailimf_types::*;
pub use self::mailimf_types_helper::*;
pub use self::mailimf_write_generic::*;
pub use self::mailmime::*;
pub use self::mailmime_content::*;
pub use self::mailmime_decode::*;
pub use self::mailmime_disposition::*;
pub use self::mailmime_types::*;
pub use self::mailmime_types_helper::*;
pub use self::mailmime_write_generic::*;
pub use self::mailmime_write_mem::*;
pub use self::mmapstring::*;
pub use self::other::*;
#[cfg(test)]
mod tests {
use super::*;
use crate::mailmime_types::{mailmime, mailmime_content, mailmime_disposition};
use std::ffi::CStr;
#[test]
fn mailmime_parse_test() {
@@ -60,7 +80,7 @@ mod tests {
let mut current_index = 0;
let mut mime = std::ptr::null_mut();
let res = crate::mailmime::content::mailmime_parse(
let res = crate::mailmime_content::mailmime_parse(
c_data.as_ptr(),
data.len() as usize,
&mut current_index,
@@ -72,7 +92,387 @@ mod tests {
display_mime(mime);
mailmime::types::mailmime_free(mime);
mailmime_types::mailmime_free(mime);
}
}
unsafe fn display_mime(mut mime: *mut mailmime) {
let mut cur: *mut clistiter = 0 as *mut clistiter;
println!("{}", (*mime).mm_type);
match (*mime).mm_type {
1 => {
println!("single part");
}
2 => {
println!("multipart");
}
3 => println!("message"),
_ => {}
}
if !(*mime).mm_mime_fields.is_null() {
if !(*(*(*mime).mm_mime_fields).fld_list).first.is_null() {
print!("MIME headers begin");
display_mime_fields((*mime).mm_mime_fields);
println!("MIME headers end");
}
}
display_mime_content((*mime).mm_content_type);
match (*mime).mm_type {
1 => {
display_mime_data((*mime).mm_data.mm_single);
}
2 => {
cur = (*(*mime).mm_data.mm_multipart.mm_mp_list).first;
while !cur.is_null() {
display_mime(
(if !cur.is_null() {
(*cur).data
} else {
0 as *mut libc::c_void
}) as *mut mailmime,
);
cur = if !cur.is_null() {
(*cur).next
} else {
0 as *mut clistcell
}
}
}
3 => {
if !(*mime).mm_data.mm_message.mm_fields.is_null() {
if !(*(*(*mime).mm_data.mm_message.mm_fields).fld_list)
.first
.is_null()
{
println!("headers begin");
display_fields((*mime).mm_data.mm_message.mm_fields);
println!("headers end");
}
if !(*mime).mm_data.mm_message.mm_msg_mime.is_null() {
display_mime((*mime).mm_data.mm_message.mm_msg_mime);
}
}
}
_ => {}
};
}
unsafe fn display_mime_content(mut content_type: *mut mailmime_content) {
print!("type: ");
display_mime_type((*content_type).ct_type);
println!(
"/{}",
CStr::from_ptr((*content_type).ct_subtype).to_str().unwrap()
);
}
unsafe fn display_mime_type(mut type_0: *mut mailmime_type) {
match (*type_0).tp_type {
1 => {
display_mime_discrete_type((*type_0).tp_data.tp_discrete_type);
}
2 => {
display_mime_composite_type((*type_0).tp_data.tp_composite_type);
}
_ => {}
};
}
unsafe fn display_mime_composite_type(mut ct: *mut mailmime_composite_type) {
match (*ct).ct_type {
1 => {
print!("message");
}
2 => {
print!("multipart");
}
3 => {
print!("{}", CStr::from_ptr((*ct).ct_token).to_str().unwrap());
}
_ => {}
};
}
unsafe fn display_mime_discrete_type(mut discrete_type: *mut mailmime_discrete_type) {
match (*discrete_type).dt_type {
1 => {
print!("text");
}
2 => {
print!("image");
}
3 => {
print!("audio");
}
4 => {
print!("video");
}
5 => {
print!("application");
}
6 => {
print!("{}", (*discrete_type).dt_extension as u8 as char);
}
_ => {}
};
}
unsafe fn display_mime_data(mut data: *mut mailmime_data) {
match (*data).dt_type {
0 => {
println!(
"data : {} bytes",
(*data).dt_data.dt_text.dt_length as libc::c_uint,
);
}
1 => {
println!(
"data (file) : {}",
CStr::from_ptr((*data).dt_data.dt_filename)
.to_str()
.unwrap()
);
}
_ => {}
};
}
unsafe fn display_mime_dsp_parm(mut param: *mut mailmime_disposition_parm) {
match (*param).pa_type {
0 => {
println!(
"filename: {}",
CStr::from_ptr((*param).pa_data.pa_filename)
.to_str()
.unwrap()
);
}
_ => {}
};
}
unsafe fn display_mime_disposition(mut disposition: *mut mailmime_disposition) {
let mut cur: *mut clistiter = 0 as *mut clistiter;
cur = (*(*disposition).dsp_parms).first;
while !cur.is_null() {
let mut param: *mut mailmime_disposition_parm = 0 as *mut mailmime_disposition_parm;
param = (if !cur.is_null() {
(*cur).data
} else {
0 as *mut libc::c_void
}) as *mut mailmime_disposition_parm;
display_mime_dsp_parm(param);
cur = if !cur.is_null() {
(*cur).next
} else {
0 as *mut clistcell
}
}
}
unsafe fn display_mime_field(mut field: *mut mailmime_field) {
match (*field).fld_type {
1 => {
print!("content-type: ");
display_mime_content((*field).fld_data.fld_content);
println!("");
}
6 => {
display_mime_disposition((*field).fld_data.fld_disposition);
}
_ => {}
};
}
unsafe fn display_mime_fields(mut fields: *mut mailmime_fields) {
let mut cur: *mut clistiter = 0 as *mut clistiter;
cur = (*(*fields).fld_list).first;
while !cur.is_null() {
let mut field: *mut mailmime_field = 0 as *mut mailmime_field;
field = (if !cur.is_null() {
(*cur).data
} else {
0 as *mut libc::c_void
}) as *mut mailmime_field;
display_mime_field(field);
cur = if !cur.is_null() {
(*cur).next
} else {
0 as *mut clistcell
}
}
}
unsafe fn display_date_time(mut d: *mut mailimf_date_time) {
print!(
"{:02}/{:02}/{:02} {:02}:{:02}:{:02} +{:04}",
(*d).dt_day,
(*d).dt_month,
(*d).dt_year,
(*d).dt_hour,
(*d).dt_min,
(*d).dt_sec,
(*d).dt_zone,
);
}
unsafe fn display_orig_date(mut orig_date: *mut mailimf_orig_date) {
display_date_time((*orig_date).dt_date_time);
}
unsafe fn display_mailbox(mut mb: *mut mailimf_mailbox) {
if !(*mb).mb_display_name.is_null() {
print!(
"{}",
CStr::from_ptr((*mb).mb_display_name).to_str().unwrap()
);
}
print!("<{}>", CStr::from_ptr((*mb).mb_addr_spec).to_str().unwrap());
}
unsafe fn display_mailbox_list(mut mb_list: *mut mailimf_mailbox_list) {
let mut cur: *mut clistiter = 0 as *mut clistiter;
cur = (*(*mb_list).mb_list).first;
while !cur.is_null() {
let mut mb: *mut mailimf_mailbox = 0 as *mut mailimf_mailbox;
mb = (if !cur.is_null() {
(*cur).data
} else {
0 as *mut libc::c_void
}) as *mut mailimf_mailbox;
display_mailbox(mb);
if !if !cur.is_null() {
(*cur).next
} else {
0 as *mut clistcell
}
.is_null()
{
print!(", ");
}
cur = if !cur.is_null() {
(*cur).next
} else {
0 as *mut clistcell
}
}
}
unsafe fn display_group(mut group: *mut mailimf_group) {
let mut cur: *mut clistiter = 0 as *mut clistiter;
print!(
"{}: ",
CStr::from_ptr((*group).grp_display_name).to_str().unwrap()
);
cur = (*(*(*group).grp_mb_list).mb_list).first;
while !cur.is_null() {
let mut mb: *mut mailimf_mailbox = 0 as *mut mailimf_mailbox;
mb = (if !cur.is_null() {
(*cur).data
} else {
0 as *mut libc::c_void
}) as *mut mailimf_mailbox;
display_mailbox(mb);
cur = if !cur.is_null() {
(*cur).next
} else {
0 as *mut clistcell
}
}
print!("; ");
}
unsafe fn display_address(mut a: *mut mailimf_address) {
match (*a).ad_type {
2 => {
display_group((*a).ad_data.ad_group);
}
1 => {
display_mailbox((*a).ad_data.ad_mailbox);
}
_ => {}
};
}
unsafe fn display_address_list(mut addr_list: *mut mailimf_address_list) {
let mut cur: *mut clistiter = 0 as *mut clistiter;
cur = (*(*addr_list).ad_list).first;
while !cur.is_null() {
let mut addr: *mut mailimf_address = 0 as *mut mailimf_address;
addr = (if !cur.is_null() {
(*cur).data
} else {
0 as *mut libc::c_void
}) as *mut mailimf_address;
display_address(addr);
if !if !cur.is_null() {
(*cur).next
} else {
0 as *mut clistcell
}
.is_null()
{
print!(", ");
}
cur = if !cur.is_null() {
(*cur).next
} else {
0 as *mut clistcell
}
}
}
unsafe fn display_from(mut from: *mut mailimf_from) {
display_mailbox_list((*from).frm_mb_list);
}
unsafe fn display_to(mut to: *mut mailimf_to) {
display_address_list((*to).to_addr_list);
}
unsafe fn display_cc(mut cc: *mut mailimf_cc) {
display_address_list((*cc).cc_addr_list);
}
unsafe fn display_subject(mut subject: *mut mailimf_subject) {
print!("{}", CStr::from_ptr((*subject).sbj_value).to_str().unwrap());
}
unsafe fn display_field(mut field: *mut mailimf_field) {
match (*field).fld_type {
9 => {
print!("Date: ");
display_orig_date((*field).fld_data.fld_orig_date);
println!("");
}
10 => {
print!("From: ");
display_from((*field).fld_data.fld_from);
println!("");
}
13 => {
print!("To: ");
display_to((*field).fld_data.fld_to);
println!("");
}
14 => {
print!("Cc: ");
display_cc((*field).fld_data.fld_cc);
println!("");
}
19 => {
print!("Subject: ");
display_subject((*field).fld_data.fld_subject);
println!("");
}
16 => {
println!(
"Message-ID: {}",
CStr::from_ptr((*(*field).fld_data.fld_message_id).mid_value)
.to_str()
.unwrap(),
);
}
_ => {}
};
}
unsafe fn display_fields(mut fields: *mut mailimf_fields) {
let mut cur: *mut clistiter = 0 as *mut clistiter;
cur = (*(*fields).fld_list).first;
while !cur.is_null() {
let mut f: *mut mailimf_field = 0 as *mut mailimf_field;
f = (if !cur.is_null() {
(*cur).data
} else {
0 as *mut libc::c_void
}) as *mut mailimf_field;
display_field(f);
cur = if !cur.is_null() {
(*cur).next
} else {
0 as *mut clistcell
}
}
}
}

View File

@@ -1,13 +1,10 @@
pub mod types;
pub mod types_helper;
pub(crate) mod write_generic;
use libc::{self, toupper};
use libc;
use libc::toupper;
use crate::clist::*;
use crate::mailimf::types::*;
use crate::mailmime::decode::*;
use crate::mailmime::types::*;
use crate::mailimf_types::*;
use crate::mailmime_decode::*;
use crate::mailmime_types::*;
use crate::mmapstring::*;
use crate::other::*;

View File

@@ -43,14 +43,15 @@ pub struct mailimf_date_time {
pub dt_zone: libc::c_int,
}
/* this is the type of address */
pub type unnamed = libc::c_uint;
/* if this is a group
(group_name: address1@domain1,
address2@domain2; ) */
pub const MAILIMF_ADDRESS_GROUP: libc::c_uint = 2;
pub const MAILIMF_ADDRESS_GROUP: unnamed = 2;
/* if this is a mailbox (mailbox@domain) */
pub const MAILIMF_ADDRESS_MAILBOX: libc::c_uint = 1;
pub const MAILIMF_ADDRESS_MAILBOX: unnamed = 1;
/* on parse error */
pub const MAILIMF_ADDRESS_ERROR: libc::c_uint = 0;
pub const MAILIMF_ADDRESS_ERROR: unnamed = 0;
/*
mailimf_address is an address
@@ -888,7 +889,6 @@ pub unsafe fn mailimf_fields_new(mut fld_list: *mut clist) -> *mut mailimf_field
(*fields).fld_list = fld_list;
return fields;
}
#[no_mangle]
pub unsafe fn mailimf_field_new(
mut fld_type: libc::c_int,
@@ -948,20 +948,6 @@ pub unsafe fn mailimf_field_new(
}
return field;
}
#[no_mangle]
pub unsafe fn mailimf_field_new_subject(fld_subject: *mut mailimf_subject) -> *mut mailimf_field {
let mut field: *mut mailimf_field = 0 as *mut mailimf_field;
field = malloc(::std::mem::size_of::<mailimf_field>() as libc::size_t) as *mut mailimf_field;
if field.is_null() {
return 0 as *mut mailimf_field;
}
(*field).fld_type = MAILIMF_FIELD_SUBJECT as libc::c_int;
(*field).fld_data.fld_subject = fld_subject;
field
}
#[no_mangle]
pub unsafe fn mailimf_orig_date_new(
mut dt_date_time: *mut mailimf_date_time,

View File

@@ -1,5 +1,5 @@
use crate::clist::*;
use crate::mailimf::types::*;
use crate::mailimf_types::*;
use crate::other::*;
/*

View File

@@ -1,5 +1,5 @@
use crate::clist::*;
use crate::mailimf::types::*;
use crate::mailimf_types::*;
use crate::other::*;
pub const STATE_WORD: libc::c_uint = 1;
@@ -1598,7 +1598,7 @@ unsafe fn mailimf_date_time_write_driver(
(*date_time).dt_sec,
(*date_time).dt_zone,
);
let date_str_c = std::ffi::CString::new(date_str).unwrap_or_default();
let date_str_c = std::ffi::CString::new(date_str).unwrap();
let r = mailimf_string_write_driver(
do_write,
data,
@@ -1983,3 +1983,43 @@ unsafe fn mailimf_path_write_driver(
}
return MAILIMF_NO_ERROR as libc::c_int;
}
/*
mailimf_envelope_fields_write writes only some fields to a given stream
@param f is the stream
@param col (* col) is the column number where we will start to
write the text, the ending column will be stored in (* col)
@param fields is the fields to write
*/
pub unsafe fn mailimf_envelope_fields_write_driver(
mut do_write: Option<
unsafe fn(_: *mut libc::c_void, _: *const libc::c_char, _: size_t) -> libc::c_int,
>,
mut data: *mut libc::c_void,
mut col: *mut libc::c_int,
mut fields: *mut mailimf_fields,
) -> libc::c_int {
let mut cur: *mut clistiter = 0 as *mut clistiter;
cur = (*(*fields).fld_list).first;
while !cur.is_null() {
let mut r: libc::c_int = 0;
let mut field: *mut mailimf_field = 0 as *mut mailimf_field;
field = (if !cur.is_null() {
(*cur).data
} else {
0 as *mut libc::c_void
}) as *mut mailimf_field;
if (*field).fld_type != MAILIMF_FIELD_OPTIONAL_FIELD as libc::c_int {
r = mailimf_field_write_driver(do_write, data, col, field);
if r != MAILIMF_NO_ERROR as libc::c_int {
return r;
}
}
cur = if !cur.is_null() {
(*cur).next
} else {
0 as *mut clistcell
}
}
return MAILIMF_NO_ERROR as libc::c_int;
}

View File

@@ -1,19 +1,13 @@
pub mod content;
pub mod decode;
pub mod disposition;
pub mod types;
pub mod types_helper;
pub(crate) mod write_generic;
pub mod write_mem;
use libc;
use libc::toupper;
use crate::clist::*;
use crate::mailimf::types::*;
use crate::mailimf::*;
use crate::mailmime::decode::*;
use crate::mailmime::disposition::*;
use crate::mailmime::types::*;
use crate::mailimf_types::*;
use crate::mailmime_decode::*;
use crate::mailmime_disposition::*;
use crate::mailmime_types::*;
use crate::other::*;
pub const MAILMIME_COMPOSITE_TYPE_EXTENSION: libc::c_uint = 3;

View File

@@ -1,9 +1,9 @@
use crate::clist::*;
use crate::mailimf::types::*;
use crate::mailimf::*;
use crate::mailmime::types::*;
use crate::mailmime::types_helper::*;
use crate::mailimf_types::*;
use crate::mailmime::*;
use crate::mailmime_types::*;
use crate::mailmime_types_helper::*;
use crate::mmapstring::*;
use crate::other::*;
@@ -80,9 +80,9 @@ pub unsafe fn mailmime_parse(
mut message: *const libc::c_char,
mut length: size_t,
mut indx: *mut size_t,
mut result: *mut *mut Mailmime,
mut result: *mut *mut mailmime,
) -> libc::c_int {
let mut mime: *mut Mailmime = 0 as *mut Mailmime;
let mut mime: *mut mailmime = 0 as *mut mailmime;
let mut r: libc::c_int = 0;
let mut res: libc::c_int = 0;
let mut content_message: *mut mailmime_content = 0 as *mut mailmime_content;
@@ -174,7 +174,7 @@ unsafe fn mailmime_parse_with_default(
mut default_type: libc::c_int,
mut content_type: *mut mailmime_content,
mut mime_fields: *mut mailmime_fields,
mut result: *mut *mut Mailmime,
mut result: *mut *mut mailmime,
) -> libc::c_int {
let mut current_block: u64;
let mut cur_token: size_t = 0;
@@ -184,8 +184,8 @@ unsafe fn mailmime_parse_with_default(
let mut boundary: *mut libc::c_char = 0 as *mut libc::c_char;
let mut fields: *mut mailimf_fields = 0 as *mut mailimf_fields;
let mut list: *mut clist = 0 as *mut clist;
let mut msg_mime: *mut Mailmime = 0 as *mut Mailmime;
let mut mime: *mut Mailmime = 0 as *mut Mailmime;
let mut msg_mime: *mut mailmime = 0 as *mut mailmime;
let mut mime: *mut mailmime = 0 as *mut mailmime;
let mut r: libc::c_int = 0;
let mut res: libc::c_int = 0;
let mut preamble: *mut mailmime_data = 0 as *mut mailmime_data;
@@ -312,7 +312,7 @@ unsafe fn mailmime_parse_with_default(
res = MAILIMF_ERROR_MEMORY as libc::c_int
} else {
list = 0 as *mut clist;
msg_mime = 0 as *mut Mailmime;
msg_mime = 0 as *mut mailmime;
fields = 0 as *mut mailimf_fields;
match body_type {
3 => {
@@ -361,7 +361,7 @@ unsafe fn mailmime_parse_with_default(
current_block = 12065775993741208975;
} else if r == MAILIMF_ERROR_PARSE as libc::c_int {
mailmime_fields_free(mime_fields);
msg_mime = 0 as *mut Mailmime;
msg_mime = 0 as *mut mailmime;
current_block = 12065775993741208975;
} else {
mailmime_fields_free(mime_fields);
@@ -456,7 +456,7 @@ unsafe fn mailmime_parse_with_default(
clist_foreach(
list,
::std::mem::transmute::<
Option<unsafe fn(_: *mut Mailmime) -> ()>,
Option<unsafe fn(_: *mut mailmime) -> ()>,
clist_func,
>(Some(
mailmime_free,
@@ -605,7 +605,7 @@ unsafe fn mailmime_multipart_body_parse(
break;
}
let mut bp_token: size_t = 0;
let mut mime_bp: *mut Mailmime = 0 as *mut Mailmime;
let mut mime_bp: *mut mailmime = 0 as *mut mailmime;
let mut data_str: *const libc::c_char = 0 as *const libc::c_char;
let mut data_size: size_t = 0;
let mut fields: *mut mailimf_fields = 0 as *mut mailimf_fields;
@@ -821,7 +821,7 @@ unsafe fn mailmime_multipart_body_parse(
clist_foreach(
list,
::std::mem::transmute::<
Option<unsafe fn(_: *mut Mailmime) -> ()>,
Option<unsafe fn(_: *mut mailmime) -> ()>,
clist_func,
>(Some(mailmime_free)),
0 as *mut libc::c_void,
@@ -1344,20 +1344,20 @@ pub unsafe fn mailmime_extract_boundary(
}
pub unsafe fn mailmime_get_section(
mut mime: *mut Mailmime,
mut mime: *mut mailmime,
mut section: *mut mailmime_section,
mut result: *mut *mut Mailmime,
mut result: *mut *mut mailmime,
) -> libc::c_int {
return mailmime_get_section_list(mime, (*(*section).sec_list).first, result);
}
unsafe fn mailmime_get_section_list(
mut mime: *mut Mailmime,
mut mime: *mut mailmime,
mut list: *mut clistiter,
mut result: *mut *mut Mailmime,
mut result: *mut *mut mailmime,
) -> libc::c_int {
let mut id: uint32_t = 0;
let mut data: *mut Mailmime = 0 as *mut Mailmime;
let mut submime: *mut Mailmime = 0 as *mut Mailmime;
let mut data: *mut mailmime = 0 as *mut mailmime;
let mut submime: *mut mailmime = 0 as *mut mailmime;
if list.is_null() {
*result = mime;
return MAILIMF_NO_ERROR as libc::c_int;
@@ -1367,14 +1367,14 @@ unsafe fn mailmime_get_section_list(
} else {
0 as *mut libc::c_void
}) as *mut uint32_t);
data = 0 as *mut Mailmime;
data = 0 as *mut mailmime;
match (*mime).mm_type {
1 => return MAILIMF_ERROR_INVAL as libc::c_int,
2 => {
data = clist_nth_data(
(*mime).mm_data.mm_multipart.mm_mp_list,
id.wrapping_sub(1i32 as libc::c_uint) as libc::c_int,
) as *mut Mailmime;
) as *mut mailmime;
if data.is_null() {
return MAILIMF_ERROR_INVAL as libc::c_int;
}
@@ -1406,7 +1406,7 @@ unsafe fn mailmime_get_section_list(
data = clist_nth_data(
(*submime).mm_data.mm_multipart.mm_mp_list,
id.wrapping_sub(1i32 as libc::c_uint) as libc::c_int,
) as *mut Mailmime;
) as *mut mailmime;
if data.is_null() {
return MAILIMF_ERROR_INVAL as libc::c_int;
}
@@ -2147,7 +2147,7 @@ pub unsafe fn mailmime_part_parse_partial(
}
pub unsafe fn mailmime_get_section_id(
mut mime: *mut Mailmime,
mut mime: *mut mailmime,
mut result: *mut *mut mailmime_section,
) -> libc::c_int {
let mut current_block: u64;
@@ -2173,7 +2173,7 @@ pub unsafe fn mailmime_get_section_id(
let mut id: uint32_t = 0;
let mut p_id: *mut uint32_t = 0 as *mut uint32_t;
let mut cur: *mut clistiter = 0 as *mut clistiter;
let mut parent: *mut Mailmime = 0 as *mut Mailmime;
let mut parent: *mut mailmime = 0 as *mut mailmime;
r = mailmime_get_section_id((*mime).mm_parent, &mut section_id);
if r != MAILIMF_NO_ERROR as libc::c_int {
res = r;

View File

@@ -3,11 +3,14 @@ use libc::toupper;
use crate::charconv::*;
use crate::mailimf::*;
use crate::mailmime::content::*;
use crate::mailmime::types::*;
use crate::mailmime_content::*;
use crate::mailmime_types::*;
use crate::mmapstring::*;
use crate::other::*;
pub const MAIL_CHARCONV_ERROR_CONV: libc::c_uint = 3;
pub const MAIL_CHARCONV_ERROR_UNKNOWN_CHARSET: libc::c_uint = 1;
pub const MAIL_CHARCONV_ERROR_MEMORY: libc::c_uint = 2;
pub const TYPE_WORD: libc::c_uint = 1;
pub const TYPE_ENCODED_WORD: libc::c_uint = 2;
pub const MAILMIME_ENCODING_Q: libc::c_uint = 1;
@@ -847,7 +850,7 @@ unsafe fn mailmime_etoken_parse(
return mailimf_custom_string_parse(message, length, indx, result, Some(is_etoken_char));
}
unsafe fn is_etoken_char(mut ch: libc::c_char) -> libc::c_int {
pub unsafe fn is_etoken_char(mut ch: libc::c_char) -> libc::c_int {
let mut uch: libc::c_uchar = ch as libc::c_uchar;
if (uch as libc::c_int) < 31i32 {
return 0i32;

View File

@@ -1,9 +1,10 @@
use libc::{self, toupper};
use libc;
use libc::toupper;
use crate::clist::*;
use crate::mailimf::*;
use crate::mailmime::types::*;
use crate::mailmime::*;
use crate::mailmime_types::*;
use crate::other::*;
pub const MAILMIME_DISPOSITION_TYPE_EXTENSION: libc::c_uint = 3;

View File

@@ -1,5 +1,7 @@
use libc;
use crate::clist::*;
use crate::mailimf::types::*;
use crate::mailimf_types::*;
use crate::mmapstring::*;
use crate::other::*;
@@ -162,9 +164,9 @@ pub const MAILMIME_NONE: unnamed_7 = 0;
#[derive(Copy, Clone)]
#[repr(C)]
pub struct Mailmime {
pub struct mailmime {
pub mm_parent_type: libc::c_int,
pub mm_parent: *mut Mailmime,
pub mm_parent: *mut mailmime,
pub mm_multipart_pos: *mut clistiter,
pub mm_type: libc::c_int,
pub mm_mime_start: *const libc::c_char,
@@ -186,7 +188,7 @@ pub union unnamed_8 {
#[repr(C)]
pub struct unnamed_9 {
pub mm_fields: *mut mailimf_fields,
pub mm_msg_mime: *mut Mailmime,
pub mm_msg_mime: *mut mailmime,
}
/* multi-part */
#[derive(Copy, Clone)]
@@ -654,15 +656,15 @@ pub unsafe fn mailmime_new(
mut mm_epilogue: *mut mailmime_data,
mut mm_mp_list: *mut clist,
mut mm_fields: *mut mailimf_fields,
mut mm_msg_mime: *mut Mailmime,
) -> *mut Mailmime {
let mut mime: *mut Mailmime = 0 as *mut Mailmime;
mut mm_msg_mime: *mut mailmime,
) -> *mut mailmime {
let mut mime: *mut mailmime = 0 as *mut mailmime;
let mut cur: *mut clistiter = 0 as *mut clistiter;
mime = malloc(::std::mem::size_of::<Mailmime>() as libc::size_t) as *mut Mailmime;
mime = malloc(::std::mem::size_of::<mailmime>() as libc::size_t) as *mut mailmime;
if mime.is_null() {
return 0 as *mut Mailmime;
return 0 as *mut mailmime;
}
(*mime).mm_parent = 0 as *mut Mailmime;
(*mime).mm_parent = 0 as *mut mailmime;
(*mime).mm_parent_type = MAILMIME_NONE as libc::c_int;
(*mime).mm_multipart_pos = 0 as *mut clistiter;
(*mime).mm_type = mm_type;
@@ -679,12 +681,12 @@ pub unsafe fn mailmime_new(
(*mime).mm_data.mm_multipart.mm_mp_list = mm_mp_list;
cur = (*mm_mp_list).first;
while !cur.is_null() {
let mut submime: *mut Mailmime = 0 as *mut Mailmime;
let mut submime: *mut mailmime = 0 as *mut mailmime;
submime = (if !cur.is_null() {
(*cur).data
} else {
0 as *mut libc::c_void
}) as *mut Mailmime;
}) as *mut mailmime;
(*submime).mm_parent = mime;
(*submime).mm_parent_type = MAILMIME_MULTIPLE as libc::c_int;
(*submime).mm_multipart_pos = cur;
@@ -708,29 +710,7 @@ pub unsafe fn mailmime_new(
return mime;
}
pub unsafe fn mailmime_new_simple(
mut mm_type: libc::c_int,
mut mm_mime_fields: *mut mailmime_fields,
mut mm_content_type: *mut mailmime_content,
mut mm_fields: *mut mailimf_fields,
mut mm_msg_mime: *mut Mailmime,
) -> *mut Mailmime {
mailmime_new(
mm_type,
std::ptr::null(),
0,
mm_mime_fields,
mm_content_type,
std::ptr::null_mut(),
std::ptr::null_mut(),
std::ptr::null_mut(),
std::ptr::null_mut(),
mm_fields,
mm_msg_mime,
)
}
pub unsafe fn mailmime_free(mut mime: *mut Mailmime) {
pub unsafe fn mailmime_free(mut mime: *mut mailmime) {
match (*mime).mm_type {
1 => {
if (*mime).mm_body.is_null() && !(*mime).mm_data.mm_single.is_null() {
@@ -747,7 +727,7 @@ pub unsafe fn mailmime_free(mut mime: *mut Mailmime) {
}
clist_foreach(
(*mime).mm_data.mm_multipart.mm_mp_list,
::std::mem::transmute::<Option<unsafe fn(_: *mut Mailmime) -> ()>, clist_func>(
::std::mem::transmute::<Option<unsafe fn(_: *mut mailmime) -> ()>, clist_func>(
Some(mailmime_free),
),
0 as *mut libc::c_void,

View File

@@ -1,9 +1,10 @@
use libc;
use rand::{thread_rng, Rng};
use crate::clist::*;
use crate::mailimf::types::*;
use crate::mailmime::types::*;
use crate::mailimf_types::*;
use crate::mailmime::*;
use crate::mailmime_types::*;
use crate::other::*;
#[derive(Copy, Clone)]
@@ -636,9 +637,9 @@ pub unsafe fn mailmime_data_new_file(
);
}
pub unsafe fn mailmime_new_message_data(mut msg_mime: *mut Mailmime) -> *mut Mailmime {
pub unsafe fn mailmime_new_message_data(mut msg_mime: *mut mailmime) -> *mut mailmime {
let mut content: *mut mailmime_content = 0 as *mut mailmime_content;
let mut build_info: *mut Mailmime = 0 as *mut Mailmime;
let mut build_info: *mut mailmime = 0 as *mut mailmime;
let mut mime_fields: *mut mailmime_fields = 0 as *mut mailmime_fields;
content = mailmime_get_content_message();
if !content.is_null() {
@@ -671,15 +672,15 @@ pub unsafe fn mailmime_new_message_data(mut msg_mime: *mut Mailmime) -> *mut Mai
}
mailmime_content_free(content);
}
return 0 as *mut Mailmime;
return 0 as *mut mailmime;
}
pub unsafe fn mailmime_new_empty(
mut content: *mut mailmime_content,
mut mime_fields: *mut mailmime_fields,
) -> *mut Mailmime {
) -> *mut mailmime {
let mut current_block: u64;
let mut build_info: *mut Mailmime = 0 as *mut Mailmime;
let mut build_info: *mut mailmime = 0 as *mut mailmime;
let mut list: *mut clist = 0 as *mut clist;
let mut r: libc::c_int = 0;
let mut mime_type: libc::c_int = 0;
@@ -822,11 +823,11 @@ pub unsafe fn mailmime_new_empty(
0 as *mut mailmime_data,
list,
0 as *mut mailimf_fields,
0 as *mut Mailmime,
0 as *mut mailmime,
);
if build_info.is_null() {
clist_free(list);
return 0 as *mut Mailmime;
return 0 as *mut mailmime;
}
return build_info;
}
@@ -834,7 +835,7 @@ pub unsafe fn mailmime_new_empty(
}
_ => {}
}
return 0 as *mut Mailmime;
return 0 as *mut mailmime;
}
pub unsafe fn mailmime_generate_boundary() -> *mut libc::c_char {
@@ -848,19 +849,19 @@ pub unsafe fn mailmime_generate_boundary() -> *mut libc::c_char {
hex::encode(&std::process::id().to_le_bytes()[..2])
);
let c = std::ffi::CString::new(raw).unwrap_or_default();
let c = std::ffi::CString::new(raw).unwrap();
strdup(c.as_ptr())
}
pub unsafe fn mailmime_new_with_content(
mut content_type: *const libc::c_char,
mut mime_fields: *mut mailmime_fields,
mut result: *mut *mut Mailmime,
mut result: *mut *mut mailmime,
) -> libc::c_int {
let mut r: libc::c_int = 0;
let mut cur_token: size_t = 0;
let mut content: *mut mailmime_content = 0 as *mut mailmime_content;
let mut build_info: *mut Mailmime = 0 as *mut Mailmime;
let mut build_info: *mut mailmime = 0 as *mut mailmime;
let mut res: libc::c_int = 0;
cur_token = 0i32 as size_t;
r = mailmime_content_parse(
@@ -885,7 +886,7 @@ pub unsafe fn mailmime_new_with_content(
}
pub unsafe fn mailmime_set_preamble_file(
mut build_info: *mut Mailmime,
mut build_info: *mut mailmime,
mut filename: *mut libc::c_char,
) -> libc::c_int {
let mut data: *mut mailmime_data = 0 as *mut mailmime_data;
@@ -905,7 +906,7 @@ pub unsafe fn mailmime_set_preamble_file(
}
pub unsafe fn mailmime_set_epilogue_file(
mut build_info: *mut Mailmime,
mut build_info: *mut mailmime,
mut filename: *mut libc::c_char,
) -> libc::c_int {
let mut data: *mut mailmime_data = 0 as *mut mailmime_data;
@@ -925,7 +926,7 @@ pub unsafe fn mailmime_set_epilogue_file(
}
pub unsafe fn mailmime_set_preamble_text(
mut build_info: *mut Mailmime,
mut build_info: *mut mailmime,
mut data_str: *mut libc::c_char,
mut length: size_t,
) -> libc::c_int {
@@ -946,7 +947,7 @@ pub unsafe fn mailmime_set_preamble_text(
}
pub unsafe fn mailmime_set_epilogue_text(
mut build_info: *mut Mailmime,
mut build_info: *mut mailmime,
mut data_str: *mut libc::c_char,
mut length: size_t,
) -> libc::c_int {
@@ -967,7 +968,7 @@ pub unsafe fn mailmime_set_epilogue_text(
}
pub unsafe fn mailmime_set_body_file(
mut build_info: *mut Mailmime,
mut build_info: *mut mailmime,
mut filename: *mut libc::c_char,
) -> libc::c_int {
let mut encoding: libc::c_int = 0;
@@ -989,7 +990,7 @@ pub unsafe fn mailmime_set_body_file(
}
pub unsafe fn mailmime_set_body_text(
mut build_info: *mut Mailmime,
mut build_info: *mut mailmime,
mut data_str: *mut libc::c_char,
mut length: size_t,
) -> libc::c_int {
@@ -1012,8 +1013,8 @@ pub unsafe fn mailmime_set_body_text(
}
pub unsafe fn mailmime_add_part(
mut build_info: *mut Mailmime,
mut part: *mut Mailmime,
mut build_info: *mut mailmime,
mut part: *mut mailmime,
) -> libc::c_int {
let mut r: libc::c_int = 0;
if (*build_info).mm_type == MAILMIME_MESSAGE as libc::c_int {
@@ -1038,19 +1039,19 @@ pub unsafe fn mailmime_add_part(
return MAILIMF_NO_ERROR as libc::c_int;
}
pub unsafe fn mailmime_remove_part(mut mime: *mut Mailmime) {
let mut parent: *mut Mailmime = 0 as *mut Mailmime;
pub unsafe fn mailmime_remove_part(mut mime: *mut mailmime) {
let mut parent: *mut mailmime = 0 as *mut mailmime;
parent = (*mime).mm_parent;
if parent.is_null() {
return;
}
match (*mime).mm_parent_type {
3 => {
(*mime).mm_parent = 0 as *mut Mailmime;
(*parent).mm_data.mm_message.mm_msg_mime = 0 as *mut Mailmime
(*mime).mm_parent = 0 as *mut mailmime;
(*parent).mm_data.mm_message.mm_msg_mime = 0 as *mut mailmime
}
2 => {
(*mime).mm_parent = 0 as *mut Mailmime;
(*mime).mm_parent = 0 as *mut mailmime;
clist_delete(
(*parent).mm_data.mm_multipart.mm_mp_list,
(*mime).mm_multipart_pos,
@@ -1061,7 +1062,7 @@ pub unsafe fn mailmime_remove_part(mut mime: *mut Mailmime) {
}
pub unsafe fn mailmime_set_imf_fields(
mut build_info: *mut Mailmime,
mut build_info: *mut mailmime,
mut mm_fields: *mut mailimf_fields,
) {
(*build_info).mm_data.mm_message.mm_fields = mm_fields;
@@ -1212,11 +1213,11 @@ pub unsafe fn mailmime_single_fields_free(mut single_fields: *mut mailmime_singl
}
pub unsafe fn mailmime_smart_add_part(
mut mime: *mut Mailmime,
mut mime_sub: *mut Mailmime,
mut mime: *mut mailmime,
mut mime_sub: *mut mailmime,
) -> libc::c_int {
let mut saved_sub: *mut Mailmime = 0 as *mut Mailmime;
let mut mp: *mut Mailmime = 0 as *mut Mailmime;
let mut saved_sub: *mut mailmime = 0 as *mut mailmime;
let mut mp: *mut mailmime = 0 as *mut mailmime;
let mut res: libc::c_int = 0;
let mut r: libc::c_int = 0;
match (*mime).mm_type {
@@ -1277,10 +1278,10 @@ pub unsafe fn mailmime_smart_add_part(
return res;
}
pub unsafe fn mailmime_multiple_new(mut type_0: *const libc::c_char) -> *mut Mailmime {
pub unsafe fn mailmime_multiple_new(mut type_0: *const libc::c_char) -> *mut mailmime {
let mut mime_fields: *mut mailmime_fields = 0 as *mut mailmime_fields;
let mut content: *mut mailmime_content = 0 as *mut mailmime_content;
let mut mp: *mut Mailmime = 0 as *mut Mailmime;
let mut mp: *mut mailmime = 0 as *mut mailmime;
mime_fields = mailmime_fields_new_empty();
if !mime_fields.is_null() {
content = mailmime_content_new_with_str(type_0);
@@ -1294,7 +1295,7 @@ pub unsafe fn mailmime_multiple_new(mut type_0: *const libc::c_char) -> *mut Mai
}
mailmime_fields_free(mime_fields);
}
return 0 as *mut Mailmime;
return 0 as *mut mailmime;
}
pub unsafe fn mailmime_content_new_with_str(mut str: *const libc::c_char) -> *mut mailmime_content {
@@ -1309,8 +1310,8 @@ pub unsafe fn mailmime_content_new_with_str(mut str: *const libc::c_char) -> *mu
return content;
}
pub unsafe fn mailmime_smart_remove_part(mut mime: *mut Mailmime) -> libc::c_int {
let mut parent: *mut Mailmime = 0 as *mut Mailmime;
pub unsafe fn mailmime_smart_remove_part(mut mime: *mut mailmime) -> libc::c_int {
let mut parent: *mut mailmime = 0 as *mut mailmime;
let mut res: libc::c_int = 0;
parent = (*mime).mm_parent;
if parent.is_null() {

View File

@@ -1,11 +1,11 @@
use std::ffi::CStr;
use crate::clist::*;
use crate::mailimf::write_generic::*;
use crate::mailmime::content::*;
use crate::mailmime::types::*;
use crate::mailmime::types_helper::*;
use crate::mailimf_write_generic::*;
use crate::mailmime::*;
use crate::mailmime_content::*;
use crate::mailmime_types::*;
use crate::mailmime_types_helper::*;
use crate::other::*;
pub const STATE_INIT: libc::c_uint = 0;
@@ -338,7 +338,7 @@ unsafe fn mailmime_disposition_param_write_driver(
4 => {
let value = (*param).pa_data.pa_size as u32;
let raw = format!("{}", value);
let raw_c = std::ffi::CString::new(raw).unwrap_or_default();
let raw_c = std::ffi::CString::new(raw).unwrap();
sizestr = strdup(raw_c.as_ptr());
len = strlen(b"size=\x00" as *const u8 as *const libc::c_char)
.wrapping_add(strlen(sizestr))
@@ -542,7 +542,7 @@ unsafe fn mailmime_version_write_driver(
}
let raw = format!("{}.{}", (version >> 16) as i32, (version & 0xffff) as i32);
let raw_c = std::ffi::CString::new(raw).unwrap_or_default();
let raw_c = std::ffi::CString::new(raw).unwrap();
let mut versionstr = strdup(raw_c.as_ptr());
r = mailimf_string_write_driver(do_write, data, col, versionstr, strlen(versionstr));
if r != MAILIMF_NO_ERROR as libc::c_int {
@@ -1017,7 +1017,7 @@ pub unsafe fn mailmime_write_driver(
>,
mut data: *mut libc::c_void,
mut col: *mut libc::c_int,
mut build_info: *mut Mailmime,
mut build_info: *mut mailmime,
) -> libc::c_int {
if !(*build_info).mm_parent.is_null() {
return mailmime_sub_write_driver(do_write, data, col, build_info);
@@ -1038,7 +1038,7 @@ unsafe fn mailmime_part_write_driver(
>,
mut data: *mut libc::c_void,
mut col: *mut libc::c_int,
mut build_info: *mut Mailmime,
mut build_info: *mut mailmime,
) -> libc::c_int {
let mut current_block: u64;
let mut cur: *mut clistiter = 0 as *mut clistiter;
@@ -1152,8 +1152,8 @@ unsafe fn mailmime_part_write_driver(
current_block = 3546145585875536353;
break;
}
let mut subpart: *mut Mailmime = 0 as *mut Mailmime;
subpart = (*cur).data as *mut Mailmime;
let mut subpart: *mut mailmime = 0 as *mut mailmime;
subpart = (*cur).data as *mut mailmime;
if 0 == first {
r = mailimf_string_write_driver(
do_write,
@@ -1421,7 +1421,7 @@ unsafe fn mailmime_sub_write_driver(
>,
mut data: *mut libc::c_void,
mut col: *mut libc::c_int,
mut build_info: *mut Mailmime,
mut build_info: *mut mailmime,
) -> libc::c_int {
let mut r: libc::c_int = 0;
if !(*build_info).mm_content_type.is_null() {
@@ -1516,7 +1516,7 @@ pub unsafe fn mailmime_data_write_driver(
1 => {
let filename = CStr::from_ptr((*mime_data).dt_data.dt_filename)
.to_str()
.unwrap_or_default();
.unwrap();
if let Ok(file) = std::fs::File::open(filename) {
if let Ok(mut text) = memmap::MmapOptions::new().map_copy(&file) {
if 0 != (*mime_data).dt_encoded {
@@ -1797,7 +1797,7 @@ pub unsafe fn mailmime_quoted_printable_write_driver(
start = text.offset(i as isize).offset(1isize);
let raw = format!("={:02X}", (ch as libc::c_int));
let raw_c = std::ffi::CString::new(raw).unwrap_or_default();
let raw_c = std::ffi::CString::new(raw).unwrap();
let mut hexstr = strdup(raw_c.as_ptr());
r = mailimf_string_write_driver(
do_write,
@@ -1822,7 +1822,7 @@ pub unsafe fn mailmime_quoted_printable_write_driver(
}
start = text.offset(i as isize).offset(1isize);
let raw = format!("={:02X}", ch as libc::c_int);
let raw_c = std::ffi::CString::new(raw).unwrap_or_default();
let raw_c = std::ffi::CString::new(raw).unwrap();
let mut hexstr = strdup(raw_c.as_ptr());
r = mailimf_string_write_driver(
do_write,
@@ -1866,7 +1866,7 @@ pub unsafe fn mailmime_quoted_printable_write_driver(
}
start = text.offset(i as isize);
let raw = format!("={:02X}", b'\r' as i32);
let raw_c = std::ffi::CString::new(raw).unwrap_or_default();
let raw_c = std::ffi::CString::new(raw).unwrap();
let mut hexstr = strdup(raw_c.as_ptr());
r = mailimf_string_write_driver(do_write, data, col, hexstr, 3i32 as size_t);
if r != MAILIMF_NO_ERROR as libc::c_int {
@@ -1890,7 +1890,7 @@ pub unsafe fn mailmime_quoted_printable_write_driver(
"={:02X}\r\n",
*text.offset(i.wrapping_sub(1i32 as libc::size_t) as isize) as libc::c_int
);
let raw_c = std::ffi::CString::new(raw).unwrap_or_default();
let raw_c = std::ffi::CString::new(raw).unwrap();
let mut hexstr = strdup(raw_c.as_ptr());
r = mailimf_string_write_driver(do_write, data, col, hexstr, strlen(hexstr));
@@ -1917,7 +1917,7 @@ pub unsafe fn mailmime_quoted_printable_write_driver(
"={:02X}\r\n",
*text.offset(i.wrapping_sub(2i32 as libc::size_t) as isize) as libc::c_int
);
let raw_c = std::ffi::CString::new(raw).unwrap_or_default();
let raw_c = std::ffi::CString::new(raw).unwrap();
let mut hexstr = strdup(raw_c.as_ptr());
r = mailimf_string_write_driver(do_write, data, col, hexstr, strlen(hexstr));
@@ -1938,7 +1938,7 @@ pub unsafe fn mailmime_quoted_printable_write_driver(
(*text.offset(i.wrapping_sub(2i32 as libc::size_t) as isize) as u8 as char),
b'\r' as i32
);
let raw_c = std::ffi::CString::new(raw).unwrap_or_default();
let raw_c = std::ffi::CString::new(raw).unwrap();
let mut hexstr = strdup(raw_c.as_ptr());
r = mailimf_string_write_driver(do_write, data, col, hexstr, strlen(hexstr));

View File

@@ -1,5 +1,5 @@
use crate::mailmime::types::*;
use crate::mailmime::write_generic::*;
use crate::mailmime_types::*;
use crate::mailmime_write_generic::*;
use crate::mmapstring::*;
use crate::other::*;
@@ -41,7 +41,7 @@ pub unsafe fn mailmime_content_type_write_mem(
pub unsafe fn mailmime_write_mem(
mut f: *mut MMAPString,
mut col: *mut libc::c_int,
mut build_info: *mut Mailmime,
mut build_info: *mut mailmime,
) -> libc::c_int {
return mailmime_write_driver(Some(do_write), f as *mut libc::c_void, col, build_info);
}

View File

@@ -1,10 +1,10 @@
use chrono::{Datelike, Local, TimeZone, Timelike};
use crate::clist::*;
use crate::mailimf::types::*;
use crate::mailimf::types_helper::*;
use crate::mailmime::types::*;
use crate::mailmime::types_helper::*;
use crate::mailimf_types::*;
use crate::mailimf_types_helper::*;
use crate::mailmime_types::*;
use crate::mailmime_types_helper::*;
pub(crate) use libc::{
calloc, close, free, isalpha, isdigit, malloc, memcmp, memcpy, memmove, memset, realloc,
@@ -132,7 +132,7 @@ pub const MAILIMF_ERROR_MEMORY: libc::c_uint = 2;
pub const MAILIMF_ERROR_PARSE: libc::c_uint = 1;
pub const MAILIMF_NO_ERROR: libc::c_uint = 0;
pub unsafe fn mailprivacy_prepare_mime(mut mime: *mut Mailmime) {
pub unsafe fn mailprivacy_prepare_mime(mut mime: *mut mailmime) {
let mut cur: *mut clistiter = 0 as *mut clistiter;
match (*mime).mm_type {
1 => {
@@ -143,12 +143,12 @@ pub unsafe fn mailprivacy_prepare_mime(mut mime: *mut Mailmime) {
2 => {
cur = (*(*mime).mm_data.mm_multipart.mm_mp_list).first;
while !cur.is_null() {
let mut child: *mut Mailmime = 0 as *mut Mailmime;
let mut child: *mut mailmime = 0 as *mut mailmime;
child = (if !cur.is_null() {
(*cur).data
} else {
0 as *mut libc::c_void
}) as *mut Mailmime;
}) as *mut mailmime;
mailprivacy_prepare_mime(child);
cur = if !cur.is_null() {
(*cur).next
@@ -166,7 +166,7 @@ pub unsafe fn mailprivacy_prepare_mime(mut mime: *mut Mailmime) {
};
}
unsafe fn prepare_mime_single(mut mime: *mut Mailmime) {
unsafe fn prepare_mime_single(mut mime: *mut mailmime) {
let mut single_fields: mailmime_single_fields = mailmime_single_fields {
fld_content: 0 as *mut mailmime_content,
fld_content_charset: 0 as *mut libc::c_char,
@@ -251,10 +251,10 @@ unsafe fn prepare_mime_single(mut mime: *mut Mailmime) {
}
pub unsafe fn mailmime_substitute(
mut old_mime: *mut Mailmime,
mut new_mime: *mut Mailmime,
mut old_mime: *mut mailmime,
mut new_mime: *mut mailmime,
) -> libc::c_int {
let mut parent: *mut Mailmime = 0 as *mut Mailmime;
let mut parent: *mut mailmime = 0 as *mut mailmime;
parent = (*old_mime).mm_parent;
if parent.is_null() {
return MAIL_ERROR_INVAL as libc::c_int;
@@ -266,7 +266,7 @@ pub unsafe fn mailmime_substitute(
}
(*new_mime).mm_parent = parent;
(*new_mime).mm_parent_type = (*old_mime).mm_parent_type;
(*old_mime).mm_parent = 0 as *mut Mailmime;
(*old_mime).mm_parent = 0 as *mut mailmime;
(*old_mime).mm_parent_type = MAILMIME_NONE as libc::c_int;
return MAIL_NO_ERROR as libc::c_int;
}

View File

@@ -5,4 +5,3 @@
# 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"
cc e34960438edb2426904b44fb4215154e7e2880f2fd1c3183b98bfcc76fec4882 # shrinks to input = " 0"

View File

@@ -97,7 +97,7 @@ If you want to run "liveconfig" functional tests you can set
chat devs.
- or the path of a file that contains two lines, each describing
via "addr=... mail_pw=..." a test account login that will
via "addr=... mail_pwd=..." a test account login that will
be used for the live tests.
With ``DCC_PY_LIVECONFIG`` set pytest invocations will use real

View File

@@ -6,7 +6,7 @@
import os
import subprocess
import sys
import os
if __name__ == "__main__":
os.environ["DCC_RS_TARGET"] = target = "release"
@@ -21,5 +21,5 @@ if __name__ == "__main__":
subprocess.check_call("rm -rf build/ src/deltachat/*.so" , shell=True)
subprocess.check_call([
sys.executable, "-m", "pip", "install", "-e", "."
"pip", "install", "-e", "."
])

View File

@@ -2,6 +2,7 @@
from __future__ import print_function
import threading
import os
import re
import time
from array import array
@@ -22,7 +23,7 @@ class Account(object):
by the underlying deltachat c-library. All public Account methods are
meant to be memory-safe and return memory-safe objects.
"""
def __init__(self, db_path, logid=None, eventlogging=True, debug=True):
def __init__(self, db_path, logid=None, eventlogging=True):
""" initialize account object.
:param db_path: a path to the account database. The database
@@ -30,14 +31,13 @@ class Account(object):
:param logid: an optional logging prefix that should be used with
the default internal logging.
:param eventlogging: if False no eventlogging and no context callback will be configured
:param debug: turn on debug logging for events.
"""
self._dc_context = ffi.gc(
lib.dc_context_new(lib.py_dc_callback, ffi.NULL, ffi.NULL),
_destroy_dc_context,
)
if eventlogging:
self._evlogger = EventLogger(self._dc_context, logid, debug)
self._evlogger = EventLogger(self._dc_context, logid)
deltachat.set_context_callback(self._dc_context, self._process_event)
self._threads = IOThreads(self._dc_context, self._evlogger._log_event)
else:
@@ -48,7 +48,7 @@ class Account(object):
if not lib.dc_open(self._dc_context, db_path, ffi.NULL):
raise ValueError("Could not dc_open: {}".format(db_path))
self._configkeys = self.get_config("sys.config_keys").split()
self._imex_events = Queue()
self._imex_completed = threading.Event()
def __del__(self):
self.shutdown()
@@ -289,64 +289,31 @@ class Account(object):
msg_ids = [msg.id for msg in messages]
lib.dc_delete_msgs(self._dc_context, msg_ids, len(msg_ids))
def export_self_keys(self, path):
""" export public and private keys to the specified directory. """
return self._export(path, imex_cmd=1)
def export_all(self, path):
"""return new file containing a backup of all database state
(chats, contacts, keys, media, ...). The file is created in the
the `path` directory.
def export_to_dir(self, backupdir):
"""return after all delta chat state is exported to a new file in
the specified directory.
"""
export_files = self._export(path, 11)
if len(export_files) != 1:
raise RuntimeError("found more than one new file")
return export_files[0]
def _imex_events_clear(self):
try:
while True:
self._imex_events.get_nowait()
except Empty:
pass
def _export(self, path, imex_cmd):
self._imex_events_clear()
lib.dc_imex(self._dc_context, imex_cmd, as_dc_charpointer(path), ffi.NULL)
snap_files = os.listdir(backupdir)
self._imex_completed.clear()
lib.dc_imex(self._dc_context, 11, as_dc_charpointer(backupdir), ffi.NULL)
if not self._threads.is_started():
lib.dc_perform_imap_jobs(self._dc_context)
files_written = []
while True:
ev = self._imex_events.get()
if isinstance(ev, str):
files_written.append(ev)
elif isinstance(ev, bool):
if not ev:
raise ValueError("export failed, exp-files: {}".format(files_written))
return files_written
self._imex_completed.wait()
for x in os.listdir(backupdir):
if x not in snap_files:
return os.path.join(backupdir, x)
def import_self_keys(self, path):
""" Import private keys found in the `path` directory.
The last imported key is made the default keys unless its name
contains the string legacy. Public keys are not imported.
"""
self._import(path, imex_cmd=2)
def import_all(self, path):
"""import delta chat state from the specified backup `path` (a file).
def import_from_file(self, path):
"""import delta chat state from the specified backup file.
The account must be in unconfigured state for import to attempted.
"""
assert not self.is_configured(), "cannot import into configured account"
self._import(path, imex_cmd=12)
def _import(self, path, imex_cmd):
self._imex_events_clear()
lib.dc_imex(self._dc_context, imex_cmd, as_dc_charpointer(path), ffi.NULL)
self._imex_completed.clear()
lib.dc_imex(self._dc_context, 12, as_dc_charpointer(path), ffi.NULL)
if not self._threads.is_started():
lib.dc_perform_imap_jobs(self._dc_context)
if not self._imex_events.get():
raise ValueError("import from path '{}' failed".format(path))
self._imex_completed.wait()
def initiate_key_transfer(self):
"""return setup code after a Autocrypt setup message
@@ -456,12 +423,7 @@ class Account(object):
def on_dc_event_imex_progress(self, data1, data2):
if data1 == 1000:
self._imex_events.put(True)
elif data1 == 0:
self._imex_events.put(False)
def on_dc_event_imex_file_written(self, data1, data2):
self._imex_events.put(data1)
self._imex_completed.set()
class IOThreads:
@@ -574,11 +536,11 @@ class EventLogger:
else:
assert not rex.match(ev[0]), "event found {}".format(ev)
def get_matching(self, event_name_regex, check_error=True, timeout=None):
def get_matching(self, event_name_regex, check_error=True):
self._log("-- waiting for event with regex: {} --".format(event_name_regex))
rex = re.compile("(?:{}).*".format(event_name_regex))
while 1:
ev = self.get(timeout=timeout, check_error=check_error)
ev = self.get()
if rex.match(ev[0]):
return ev

View File

@@ -8,6 +8,9 @@ from os.path import join as joinpath
# this works well when you in a git-checkout
# run "python deltachat/const.py" to regenerate events
# begin const generated
DC_PROVIDER_STATUS_OK = 1
DC_PROVIDER_STATUS_PREPARATION = 2
DC_PROVIDER_STATUS_BROKEN = 3
DC_GCL_ARCHIVED_ONLY = 0x01
DC_GCL_NO_SPECIALS = 0x02
DC_GCL_ADD_ALLDONE_HINT = 0x04
@@ -52,7 +55,6 @@ DC_CONTACT_ID_LAST_SPECIAL = 9
DC_MSG_TEXT = 10
DC_MSG_IMAGE = 20
DC_MSG_GIF = 21
DC_MSG_STICKER = 23
DC_MSG_AUDIO = 40
DC_MSG_VOICE = 41
DC_MSG_VIDEO = 50
@@ -61,10 +63,6 @@ DC_EVENT_INFO = 100
DC_EVENT_SMTP_CONNECTED = 101
DC_EVENT_IMAP_CONNECTED = 102
DC_EVENT_SMTP_MESSAGE_SENT = 103
DC_EVENT_IMAP_MESSAGE_DELETED = 104
DC_EVENT_IMAP_MESSAGE_MOVED = 105
DC_EVENT_NEW_BLOB_FILE = 150
DC_EVENT_DELETED_BLOB_FILE = 151
DC_EVENT_WARNING = 300
DC_EVENT_ERROR = 400
DC_EVENT_ERROR_NETWORK = 401
@@ -85,9 +83,6 @@ DC_EVENT_SECUREJOIN_JOINER_PROGRESS = 2061
DC_EVENT_GET_STRING = 2091
DC_EVENT_FILE_COPIED = 2055
DC_EVENT_IS_OFFLINE = 2081
DC_PROVIDER_STATUS_OK = 1
DC_PROVIDER_STATUS_PREPARATION = 2
DC_PROVIDER_STATUS_BROKEN = 3
# end const generated

View File

@@ -101,10 +101,6 @@ class Message(object):
""" return True if this message is a setup message. """
return lib.dc_msg_is_setupmessage(self._dc_msg)
def get_setupcodebegin(self):
""" return the first characters of a setup code in a setup message. """
return from_dc_charpointer(lib.dc_msg_get_setupcodebegin(self._dc_msg))
def is_encrypted(self):
""" return True if this message was encrypted. """
return bool(lib.dc_msg_get_showpadlock(self._dc_msg))

View File

@@ -157,11 +157,6 @@ def acfactory(pytestconfig, tmpdir, request, session_liveconfig):
self.live_count += 1
if "e2ee_enabled" not in configdict:
configdict["e2ee_enabled"] = "1"
# Enable strict certificate checks for online accounts
configdict["imap_certificate_checks"] = "1"
configdict["smtp_certificate_checks"] = "1"
tmpdb = tmpdir.join("livedb%d" % self.live_count)
ac = self.make_account(tmpdb.strpath, logid="ac{}".format(self.live_count))
ac._evlogger.init_time = self.init_time

View File

@@ -1,7 +1,6 @@
from __future__ import print_function
import pytest
import os
import queue
from deltachat import const, Account
from deltachat.message import Message
from datetime import datetime, timedelta
@@ -20,7 +19,6 @@ class TestOfflineAccountBasic:
d = ac1.get_info()
assert d["arch"]
assert d["number_of_chats"] == "0"
assert d["bcc_self"] == "1"
def test_is_not_configured(self, acfactory):
ac1 = acfactory.get_unconfigured_account()
@@ -39,11 +37,6 @@ class TestOfflineAccountBasic:
ac1 = acfactory.get_unconfigured_account()
assert "save_mime_headers" in ac1.get_config("sys.config_keys").split()
def test_has_bccself(self, acfactory):
ac1 = acfactory.get_unconfigured_account()
assert "bcc_self" in ac1.get_config("sys.config_keys").split()
assert ac1.get_config("bcc_self") == "1"
def test_selfcontact_if_unconfigured(self, acfactory):
ac1 = acfactory.get_unconfigured_account()
with pytest.raises(ValueError):
@@ -100,9 +93,8 @@ class TestOfflineContact:
ac1 = acfactory.get_configured_offline_account()
contact1 = ac1.create_contact(email="some1@example.com", name="some1")
chat = ac1.create_chat_by_contact(contact1)
msg = chat.send_text("one message")
chat.send_text("one messae")
assert not ac1.delete_contact(contact1)
assert not msg.filemime
class TestOfflineChat:
@@ -229,9 +221,7 @@ class TestOfflineChat:
chat1.send_image(path="notexists")
fn = data.get_path("d.png")
lp.sec("sending image")
chat1.account._evlogger.consume_events()
msg = chat1.send_image(fn)
chat1.account._evlogger.get_matching("DC_EVENT_NEW_BLOB_FILE")
assert msg.is_image()
assert msg
assert msg.id > 0
@@ -303,10 +293,10 @@ class TestOfflineChat:
assert contact == ac1.get_self_contact()
assert not backupdir.listdir()
path = ac1.export_all(backupdir.strpath)
path = ac1.export_to_dir(backupdir.strpath)
assert os.path.exists(path)
ac2 = acfactory.get_unconfigured_account()
ac2.import_all(path)
ac2.import_from_file(path)
contacts = ac2.get_contacts(query="some1")
assert len(contacts) == 1
contact2 = contacts[0]
@@ -350,44 +340,18 @@ class TestOnlineAccount:
assert chat.id > const.DC_CHAT_ID_LAST_SPECIAL
return chat
def test_export_import_self_keys(self, acfactory, tmpdir):
ac1, ac2 = acfactory.get_two_online_accounts()
dir = tmpdir.mkdir("exportdir")
export_files = ac1.export_self_keys(dir.strpath)
assert len(export_files) == 2
for x in export_files:
assert x.startswith(dir.strpath)
ac1._evlogger.consume_events()
ac1.import_self_keys(dir.strpath)
def test_one_account_send_bcc_setting(self, acfactory, lp):
def test_one_account_send(self, acfactory):
ac1 = acfactory.get_online_configuring_account()
c2 = ac1.create_contact(email="notexists@testrun.org")
c2 = ac1.create_contact(email=ac1.get_config("addr"))
chat = ac1.create_chat_by_contact(c2)
assert chat.id > const.DC_CHAT_ID_LAST_SPECIAL
wait_successful_IMAP_SMTP_connection(ac1)
wait_configuration_progress(ac1, 1000)
lp.sec("send out message with bcc to ourselves")
msg_out = chat.send_text("message2")
ev = ac1._evlogger.get_matching("DC_EVENT_MSGS_CHANGED")
assert ev[2] == msg_out.id
# wait for send out (BCC)
assert ac1.get_config("bcc_self") == "1"
self_addr = ac1.get_config("addr")
ev = ac1._evlogger.get_matching("DC_EVENT_SMTP_MESSAGE_SENT")
assert self_addr in ev[2]
ev = ac1._evlogger.get_matching("DC_EVENT_DELETED_BLOB_FILE")
ac1._evlogger.consume_events()
lp.sec("send out message without bcc")
ac1.set_config("bcc_self", "0")
msg_out = chat.send_text("message3")
ev = ac1._evlogger.get_matching("DC_EVENT_MSGS_CHANGED")
assert ev[2] == msg_out.id
ev = ac1._evlogger.get_matching("DC_EVENT_SMTP_MESSAGE_SENT")
assert self_addr not in ev[2]
ev = ac1._evlogger.get_matching("DC_EVENT_DELETED_BLOB_FILE")
# wait for own account to receive
ev = ac1._evlogger.get_matching("DC_EVENT_INCOMING_MSG|DC_EVENT_MSGS_CHANGED")
assert ev[1] == msg_out.id
def test_mvbox_sentbox_threads(self, acfactory):
ac1 = acfactory.get_online_configuring_account(mvbox=True, sentbox=True)
@@ -399,17 +363,6 @@ class TestOnlineAccount:
ev = ac2._evlogger.get_matching("DC_EVENT_INCOMING_MSG|DC_EVENT_MSGS_CHANGED")
assert ev[2] > const.DC_CHAT_ID_LAST_SPECIAL
def test_move_works(self, acfactory):
ac1 = acfactory.get_online_configuring_account()
ac2 = acfactory.get_online_configuring_account(mvbox=True)
wait_configuration_progress(ac2, 1000)
wait_configuration_progress(ac1, 1000)
chat = self.get_chat(ac1, ac2)
chat.send_text("message1")
ev = ac2._evlogger.get_matching("DC_EVENT_INCOMING_MSG|DC_EVENT_MSGS_CHANGED")
assert ev[2] > const.DC_CHAT_ID_LAST_SPECIAL
ev = ac2._evlogger.get_matching("DC_EVENT_IMAP_MESSAGE_MOVED")
def test_forward_messages(self, acfactory):
ac1, ac2 = acfactory.get_two_online_accounts()
chat = self.get_chat(ac1, ac2)
@@ -480,14 +433,6 @@ class TestOnlineAccount:
lp.step("2")
assert msg_out.is_out_mdn_received()
lp.sec("check that a second call to mark_seen does not create change or smtp job")
ac2._evlogger.consume_events()
ac2.mark_seen_messages([msg_in])
try:
ac2._evlogger.get_matching("DC_EVENT_MSG_READ", timeout=0.01)
except queue.Empty:
pass # mark_seen_messages() has generated events before it returns
def test_send_and_receive_will_encrypt_decrypt(self, acfactory, lp):
ac1, ac2 = acfactory.get_two_online_accounts()
@@ -516,14 +461,6 @@ class TestOnlineAccount:
assert msg_back.text == "message-back"
assert msg_back.is_encrypted()
lp.sec("create group chat with two members, one of which has no encrypt state")
chat = ac1.create_group_chat("encryption test")
chat.add_contact(ac1.create_contact(ac2.get_config("addr")))
chat.add_contact(ac1.create_contact("notexisting@testrun.org"))
msg = chat.send_text("test not encrypt")
ev = ac1._evlogger.get_matching("DC_EVENT_SMTP_MESSAGE_SENT")
assert not msg.is_encrypted()
def test_saved_mime_on_received_message(self, acfactory, lp):
ac1, ac2 = acfactory.get_two_online_accounts()
@@ -564,7 +501,7 @@ class TestOnlineAccount:
assert os.path.exists(msg_in.filename)
assert os.stat(msg_in.filename).st_size == os.stat(path).st_size
def test_import_export_online_all(self, acfactory, tmpdir):
def test_import_export_online(self, acfactory, tmpdir):
ac1 = acfactory.get_online_configuring_account()
wait_configuration_progress(ac1, 1000)
@@ -572,11 +509,11 @@ class TestOnlineAccount:
chat = ac1.create_chat_by_contact(contact1)
chat.send_text("msg1")
backupdir = tmpdir.mkdir("backup")
path = ac1.export_all(backupdir.strpath)
path = ac1.export_to_dir(backupdir.strpath)
assert os.path.exists(path)
ac2 = acfactory.get_unconfigured_account()
ac2.import_all(path)
ac2.import_from_file(path)
contacts = ac2.get_contacts(query="some1")
assert len(contacts) == 1
contact2 = contacts[0]
@@ -586,7 +523,7 @@ class TestOnlineAccount:
assert len(messages) == 1
assert messages[0].text == "msg1"
def test_ac_setup_message(self, acfactory, lp):
def test_ac_setup_message(self, acfactory):
# note that the receiving account needs to be configured and running
# before ther setup message is send. DC does not read old messages
# as of Jul2019
@@ -594,18 +531,15 @@ class TestOnlineAccount:
ac2 = acfactory.clone_online_account(ac1)
wait_configuration_progress(ac2, 1000)
wait_configuration_progress(ac1, 1000)
lp.sec("trigger ac setup message and return setupcode")
assert ac1.get_info()["fingerprint"] != ac2.get_info()["fingerprint"]
setup_code = ac1.initiate_key_transfer()
ac2._evlogger.set_timeout(30)
ev = ac2._evlogger.get_matching("DC_EVENT_INCOMING_MSG|DC_EVENT_MSGS_CHANGED")
msg = ac2.get_message_by_id(ev[2])
assert msg.is_setup_message()
assert msg.get_setupcodebegin() == setup_code[:2]
lp.sec("try a bad setup code")
# first try a bad setup code
with pytest.raises(ValueError):
msg.continue_key_transfer(str(reversed(setup_code)))
lp.sec("try a good setup code")
print("*************** Incoming ASM File at: ", msg.filename)
print("*************** Setup Code: ", setup_code)
msg.continue_key_transfer(setup_code)
@@ -628,9 +562,6 @@ class TestOnlineAccount:
lp.sec("ac2: start QR-code based join-group protocol")
ch = ac2.qr_join_chat(qr)
assert ch.id >= 10
# check that at least some of the handshake messages are deleted
ac1._evlogger.get_matching("DC_EVENT_IMAP_MESSAGE_DELETED")
ac2._evlogger.get_matching("DC_EVENT_IMAP_MESSAGE_DELETED")
wait_securejoin_inviter_progress(ac1, 1000)
def test_qr_verified_group_and_chatting(self, acfactory, lp):

View File

@@ -108,43 +108,3 @@ def test_provider_info():
def test_provider_info_none():
assert lib.dc_provider_new_from_email(cutil.as_dc_charpointer("email@unexistent.no")) == ffi.NULL
def test_get_info_closed():
ctx = ffi.gc(
lib.dc_context_new(lib.py_dc_callback, ffi.NULL, ffi.NULL),
lib.dc_context_unref,
)
info = cutil.from_dc_charpointer(lib.dc_get_info(ctx))
assert 'deltachat_core_version' in info
assert 'database_dir' not in info
def test_get_info_open(tmpdir):
ctx = ffi.gc(
lib.dc_context_new(lib.py_dc_callback, ffi.NULL, ffi.NULL),
lib.dc_context_unref,
)
db_fname = tmpdir.join("test.db")
lib.dc_open(ctx, db_fname.strpath.encode("ascii"), ffi.NULL)
info = cutil.from_dc_charpointer(lib.dc_get_info(ctx))
assert 'deltachat_core_version' in info
assert 'database_dir' in info
def test_is_open_closed():
ctx = ffi.gc(
lib.dc_context_new(lib.py_dc_callback, ffi.NULL, ffi.NULL),
lib.dc_context_unref,
)
assert lib.dc_is_open(ctx) == 0
def test_is_open_actually_open(tmpdir):
ctx = ffi.gc(
lib.dc_context_new(lib.py_dc_callback, ffi.NULL, ffi.NULL),
lib.dc_context_unref,
)
db_fname = tmpdir.join("test.db")
lib.dc_open(ctx, db_fname.strpath.encode("ascii"), ffi.NULL)
assert lib.dc_is_open(ctx) == 1

23
spec.md
View File

@@ -117,8 +117,7 @@ The sender plus the recipients are the group members.
To allow different groups with the same members,
groups are identified by a group-id.
The group-id MUST be created only from the characters
`0`-`9`, `A`-`Z`, `a`-`z` `_` and `-`
and MUST have a length of at least 11 characters.
`0`-`9`, `A`-`Z`, `a`-`z` `_` and `-`.
Groups MUST have a group-name.
The group-name is any non-zero-length UTF-8 string.
@@ -145,9 +144,9 @@ The message-id MUST have the format `Gr.<group-id>.<unique data>`.
From: member1@domain
To: member2@domain, member3@domain
Chat-Version: 1.0
Chat-Group-ID: 12345uvwxyZ
Chat-Group-ID: 1234xyZ
Chat-Group-Name: My Group
Message-ID: Gr.12345uvwxyZ.0001@domain
Message-ID: Gr.1234xyZ.0001@domain
Subject: Chat: My Group: Hello group ...
Hello group - this group contains three members
@@ -197,10 +196,10 @@ and the message SHOULD appear as a message or action from the sender.
From: member1@domain
To: member2@domain, member3@domain, member4@domain
Chat-Version: 1.0
Chat-Group-ID: 12345uvwxyZ
Chat-Group-ID: 1234xyZ
Chat-Group-Name: My Group
Chat-Group-Member-Added: member4@domain
Message-ID: Gr.12345uvwxyZ.0002@domain
Message-ID: Gr.1234xyZ.0002@domain
Subject: Chat: My Group: Hello, ...
Hello, I've added member4@domain to our group. Now we have 4 members.
@@ -210,10 +209,10 @@ To remove a member:
From: member1@domain
To: member2@domain, member3@domain
Chat-Version: 1.0
Chat-Group-ID: 12345uvwxyZ
Chat-Group-ID: 1234xyZ
Chat-Group-Name: My Group
Chat-Group-Member-Removed: member4@domain
Message-ID: Gr.12345uvwxyZ.0003@domain
Message-ID: Gr.1234xyZ.0003@domain
Subject: Chat: My Group: Hello, ...
Hello, I've removed member4@domain from our group. Now we have 3 members.
@@ -234,10 +233,10 @@ and the message SHOULD appear as a message or action from the sender.
From: member1@domain
To: member2@domain, member3@domain
Chat-Version: 1.0
Chat-Group-ID: 12345uvwxyZ
Chat-Group-ID: 1234xyZ
Chat-Group-Name: Our Group
Chat-Group-Name-Changed: My Group
Message-ID: Gr.12345uvwxyZ.0004@domain
Message-ID: Gr.1234xyZ.0004@domain
Subject: Chat: Our Group: Hello, ...
Hello, I've changed the group name from "My Group" to "Our Group".
@@ -263,10 +262,10 @@ and the message SHOULD appear as a message or action from the sender.
From: member1@domain
To: member2@domain, member3@domain
Chat-Version: 1.0
Chat-Group-ID: 12345uvwxyZ
Chat-Group-ID: 1234xyZ
Chat-Group-Name: Our Group
Chat-Group-Image: image.jpg
Message-ID: Gr.12345uvwxyZ.0005@domain
Message-ID: Gr.1234xyZ.0005@domain
Subject: Chat: Our Group: Hello, ...
Content-Type: multipart/mixed; boundary="==break=="

View File

@@ -3,7 +3,7 @@ use std::ffi::CStr;
use std::str::FromStr;
use std::{fmt, str};
use mmime::mailimf::types::*;
use mmime::mailimf_types::*;
use crate::constants::*;
use crate::contact::*;
@@ -79,27 +79,27 @@ impl Aheader {
let optional_field = unsafe { (*field).fld_data.fld_optional_field };
if !optional_field.is_null()
&& unsafe { !(*optional_field).fld_name.is_null() }
&& unsafe {
CStr::from_ptr((*optional_field).fld_name)
.to_str()
.unwrap_or_default()
} == "Autocrypt"
&& unsafe { CStr::from_ptr((*optional_field).fld_name).to_str().unwrap() }
== "Autocrypt"
{
let value = unsafe {
CStr::from_ptr((*optional_field).fld_value)
.to_str()
.unwrap_or_default()
.unwrap()
};
if let Ok(test) = Self::from_str(value) {
if addr_cmp(&test.addr, wanted_from) {
if fine_header.is_none() {
fine_header = Some(test);
} else {
// TODO: figure out what kind of error case this is
return None;
match Self::from_str(value) {
Ok(test) => {
if addr_cmp(&test.addr, wanted_from) {
if fine_header.is_none() {
fine_header = Some(test);
} else {
// TODO: figure out what kind of error case this is
return None;
}
}
}
_ => {}
}
}
}
@@ -131,9 +131,9 @@ impl str::FromStr for Aheader {
fn from_str(s: &str) -> Result<Self, Self::Err> {
let mut attributes: BTreeMap<String, String> = s
.split(';')
.split(";")
.filter_map(|a| {
let attribute: Vec<&str> = a.trim().splitn(2, '=').collect();
let attribute: Vec<&str> = a.trim().splitn(2, "=").collect();
if attribute.len() < 2 {
return None;
}
@@ -178,7 +178,7 @@ impl str::FromStr for Aheader {
// Autocrypt-Level0: unknown attributes starting with an underscore can be safely ignored
// Autocrypt-Level0: unknown attribute, treat the header as invalid
if attributes.keys().any(|k| !k.starts_with('_')) {
if attributes.keys().find(|k| !k.starts_with("_")).is_some() {
return Err(());
}

View File

@@ -152,23 +152,21 @@ impl Chat {
return context.stock_str(StockMessage::DeadDrop).into();
}
let cnt = get_chat_contact_cnt(context, self.id);
return context.stock_string_repl_int(StockMessage::Member, cnt as i32);
return context
.stock_string_repl_int(StockMessage::Member, cnt as i32)
.into();
}
"Err".to_string()
return "Err".into();
}
pub fn get_parent_mime_headers(&self, context: &Context) -> Option<(String, String, String)> {
let collect = |row: &rusqlite::Row| Ok((row.get(0)?, row.get(1)?, row.get(2)?));
let params = params![self.id as i32, DC_CONTACT_ID_SELF as i32];
let sql = &context.sql;
// use the last messsage of another user in the group as the parent
let main_query = "SELECT rfc724_mid, mime_in_reply_to, mime_references \
FROM msgs WHERE chat_id=?1 AND timestamp=(SELECT max(timestamp) \
FROM msgs WHERE chat_id=?1 AND from_id!=?2);";
// there are no messages of other users - use the first message if SELF as parent
let fallback_query = "SELECT rfc724_mid, mime_in_reply_to, mime_references \
FROM msgs WHERE chat_id=?1 AND timestamp=(SELECT min(timestamp) \
FROM msgs WHERE chat_id=?1 AND from_id==?2);";
@@ -291,7 +289,7 @@ impl Chat {
if self.typ == Chattype::Group || self.typ == Chattype::VerifiedGroup {
if self.param.get_int(Param::Unpromoted).unwrap_or_default() == 1 {
self.param.remove(Param::Unpromoted);
self.update_param(context)?;
self.update_param(context).unwrap();
}
}
}
@@ -301,15 +299,16 @@ impl Chat {
so that E2EE is no longer available at a later point (reset, changed settings),
we do not send the message out at all */
do_guarantee_e2ee = false;
e2ee_enabled = context.get_config_bool(Config::E2eeEnabled);
e2ee_enabled = context
.sql
.get_config_int(context, "e2ee_enabled")
.unwrap_or_else(|| 1)
== 1;
if e2ee_enabled && msg.param.get_int(Param::ForcePlaintext).unwrap_or_default() == 0 {
let mut can_encrypt = true;
let mut all_mutual = true;
let mut can_encrypt = 1;
let mut all_mutual = 1;
// take care that this statement returns NULL rows
// if there is no peerstates for a chat member!
// for DC_PARAM_SELFTALK this statement does not return any row
let res = context.sql.query_map(
let res = context.sql.query_row(
"SELECT ps.prefer_encrypted, c.addr \
FROM chats_contacts cc \
LEFT JOIN contacts c ON cc.contact_id=c.id \
@@ -317,32 +316,29 @@ impl Chat {
WHERE cc.chat_id=? AND cc.contact_id>9;",
params![self.id],
|row| {
let addr: String = row.get(1)?;
let state: String = row.get(1)?;
if let Some(prefer_encrypted) = row.get::<_, Option<i32>>(0)? {
// the peerstate exist, so we have either public_key or gossip_key
// and can encrypt potentially
if prefer_encrypted != 1 {
info!(
context,
"[autocrypt] peerstate for {} is {}",
addr,
state,
if prefer_encrypted == 0 {
"NOPREFERENCE"
} else {
"RESET"
},
);
all_mutual = false;
all_mutual = 0;
}
} else {
info!(context, "[autocrypt] no peerstate for {}", addr,);
can_encrypt = false;
all_mutual = false;
info!(context, "[autocrypt] no peerstate for {}", state,);
can_encrypt = 0;
all_mutual = 0;
}
Ok(())
},
|rows| rows.collect::<Result<Vec<_>, _>>().map_err(Into::into),
);
match res {
Ok(_) => {}
@@ -351,8 +347,8 @@ impl Chat {
}
}
if can_encrypt {
if all_mutual {
if 0 != can_encrypt {
if 0 != all_mutual {
do_guarantee_e2ee = true;
} else if last_msg_in_chat_encrypted(context, &context.sql, self.id) {
do_guarantee_e2ee = true;
@@ -362,15 +358,7 @@ impl Chat {
if do_guarantee_e2ee {
msg.param.set_int(Param::GuranteeE2ee, 1);
}
// reset eg. for forwarding
msg.param.remove(Param::ErroneousE2ee);
// set "In-Reply-To:" to identify the message to which the composed message is a reply;
// set "References:" to identify the "thread" of the conversation;
// both according to RFC 5322 3.6.4, page 25
//
// as self-talks are mainly used to transfer data between devices,
// we do not set In-Reply-To/References in this case.
if !self.is_self_talk() {
if let Some((parent_rfc724_mid, parent_in_reply_to, parent_references)) =
self.get_parent_mime_headers(context)
@@ -378,9 +366,6 @@ impl Chat {
if !parent_rfc724_mid.is_empty() {
new_in_reply_to = parent_rfc724_mid.clone();
}
// the whole list of messages referenced may be huge;
// only use the oldest and and the parent message
let parent_references = if let Some(n) = parent_references.find(' ') {
&parent_references[0..n]
} else {
@@ -388,7 +373,6 @@ impl Chat {
};
if !parent_references.is_empty() && !parent_rfc724_mid.is_empty() {
// angle brackets are added by the mimefactory later
new_references = format!("{} {}", parent_references, parent_rfc724_mid);
} else if !parent_references.is_empty() {
new_references = parent_references.to_string();
@@ -662,7 +646,6 @@ pub fn msgtype_has_file(msgtype: Viewtype) -> bool {
match msgtype {
Viewtype::Image => true,
Viewtype::Gif => true,
Viewtype::Sticker => true,
Viewtype::Audio => true,
Viewtype::Voice => true,
Viewtype::Video => true,
@@ -800,7 +783,10 @@ pub fn send_msg(context: &Context, chat_id: u32, msg: &mut Message) -> Result<u3
message::update_msg_state(context, msg.id, MessageState::OutPending);
}
job_send_msg(context, msg.id)?;
ensure!(
job_send_msg(context, msg.id) != 0,
"Failed to initiate send job"
);
context.call_cb(Event::MsgsChanged {
chat_id: msg.chat_id,
@@ -819,8 +805,11 @@ pub fn send_msg(context: &Context, chat_id: u32, msg: &mut Message) -> Result<u3
if 0 == id {
// avoid hanging if user tampers with db
break;
} else if let Ok(mut copy) = Message::load_from_db(context, id as u32) {
send_msg(context, 0, &mut copy)?;
} else {
if let Ok(mut copy) = Message::load_from_db(context, id as u32) {
// TODO: handle cleanup and return early instead
send_msg(context, 0, &mut copy).unwrap();
}
}
}
msg.param.remove(Param::PrepForwards);
@@ -936,9 +925,7 @@ fn get_draft_msg_id(context: &Context, chat_id: u32) -> u32 {
}
pub fn get_draft(context: &Context, chat_id: u32) -> Result<Option<Message>, Error> {
if chat_id <= DC_CHAT_ID_LAST_SPECIAL {
return Ok(None);
}
ensure!(chat_id > DC_CHAT_ID_LAST_SPECIAL, "Invalid chat ID");
let draft_msg_id = get_draft_msg_id(context, chat_id);
if draft_msg_id == 0 {
return Ok(None);
@@ -973,7 +960,10 @@ pub fn get_chat_msgs(context: &Context, chat_id: u32, flags: u32, marker1before:
};
let success = if chat_id == 1 {
let show_emails = context.get_config_int(Config::ShowEmails);
let show_emails = context
.sql
.get_config_int(context, "show_emails")
.unwrap_or_default();
context.sql.query_map(
"SELECT m.id, m.timestamp FROM msgs m \
LEFT JOIN chats ON m.chat_id=chats.id \
@@ -1342,7 +1332,7 @@ pub fn add_to_chat_contacts_table(context: &Context, chat_id: u32, contact_id: u
pub fn add_contact_to_chat(context: &Context, chat_id: u32, contact_id: u32) -> bool {
match add_contact_to_chat_ex(context, chat_id, contact_id, false) {
Ok(res) => res,
Ok(res) => res,
Err(err) => {
error!(context, "failed to add contact: {}", err);
false
@@ -1357,10 +1347,7 @@ pub(crate) fn add_contact_to_chat_ex(
contact_id: u32,
from_handshake: bool,
) -> Result<bool, Error> {
ensure!(
chat_id > DC_CHAT_ID_LAST_SPECIAL,
"can not add member to special chats"
);
ensure!(chat_id > DC_CHAT_ID_LAST_SPECIAL, "can not add member to special chats");
let contact = Contact::get_by_id(context, contact_id)?;
let mut msg = Message::default();
@@ -1368,76 +1355,74 @@ pub(crate) fn add_contact_to_chat_ex(
/*this also makes sure, not contacts are added to special or normal chats*/
let mut chat = Chat::load_from_db(context, chat_id)?;
ensure!(
real_group_exists(context, chat_id),
"chat_id {} is not a group where one can add members",
chat_id
);
ensure!(
Contact::real_exists_by_id(context, contact_id) && contact_id != DC_CONTACT_ID_SELF,
"invalid contact_id {} for removal in group",
contact_id
);
ensure!(real_group_exists(context, chat_id),
"chat_id {} is not a group where one can add members", chat_id);
ensure!(Contact::real_exists_by_id(context, contact_id) && contact_id != DC_CONTACT_ID_SELF,
"invalid contact_id {} for removal in group", contact_id);
if !is_contact_in_chat(context, chat_id, DC_CONTACT_ID_SELF as u32) {
/* we should respect this - whatever we send to the group, it gets discarded anyway! */
emit_event!(
context,
Event::ErrorSelfNotInGroup("Cannot add contact to group; self not in group.".into())
);
bail!("can not add contact because our account is not part of it");
}
if from_handshake && chat.param.get_int(Param::Unpromoted).unwrap_or_default() == 1 {
chat.param.remove(Param::Unpromoted);
chat.update_param(context)?;
}
let self_addr = context
.get_config(Config::ConfiguredAddr)
.unwrap_or_default();
if contact.get_addr() == &self_addr {
bail!("invalid attempt to add self e-mail address to group");
}
// ourself is added using DC_CONTACT_ID_SELF, do not add it explicitly.
// if SELF is not in the group, members cannot be added at all.
if !is_contact_in_chat(context, chat_id, DC_CONTACT_ID_SELF as u32) {
/* we should respect this - whatever we send to the group, it gets discarded anyway! */
emit_event!(
context,
Event::ErrorSelfNotInGroup(
"Cannot add contact to group; self not in group.".into()
)
);
bail!("can not add contact because our account is not part of it");
}
if from_handshake && chat.param.get_int(Param::Unpromoted).unwrap_or_default() == 1
{
chat.param.remove(Param::Unpromoted);
chat.update_param(context).unwrap();
}
let self_addr = context
.sql
.get_config(context, "configured_addr")
.unwrap_or_default();
if contact.get_addr() == &self_addr {
bail!("invalid attempt to add self e-mail address to group");
}
// ourself is added using DC_CONTACT_ID_SELF, do not add it explicitly.
// if SELF is not in the group, members cannot be added at all.
if is_contact_in_chat(context, chat_id, contact_id) {
if !from_handshake {
return Ok(true);
}
} else {
// else continue and send status mail
if chat.typ == Chattype::VerifiedGroup
&& contact.is_verified(context) != VerifiedStatus::BidirectVerified
{
error!(
context,
"Only bidirectional verified contacts can be added to verified groups."
);
return Ok(false);
}
if !add_to_chat_contacts_table(context, chat_id, contact_id) {
return Ok(false);
}
}
if chat.param.get_int(Param::Unpromoted).unwrap_or_default() == 0 {
msg.type_0 = Viewtype::Text;
msg.text = Some(context.stock_system_msg(
StockMessage::MsgAddMember,
contact.get_addr(),
"",
DC_CONTACT_ID_SELF as u32,
));
msg.param.set_int(Param::Cmd, 4);
msg.param.set(Param::Arg, contact.get_addr());
msg.param.set_int(Param::Arg2, from_handshake.into());
msg.id = send_msg(context, chat_id, &mut msg)?;
context.call_cb(Event::MsgsChanged {
chat_id,
msg_id: msg.id,
});
}
context.call_cb(Event::MsgsChanged { chat_id, msg_id: 0 });
Ok(true)
if is_contact_in_chat(context, chat_id, contact_id) {
if !from_handshake {
return Ok(true);
}
} else {
// else continue and send status mail
if chat.typ == Chattype::VerifiedGroup {
if contact.is_verified(context) != VerifiedStatus::BidirectVerified {
error!(
context,
"Only bidirectional verified contacts can be added to verified groups."
);
return Ok(false);
}
}
if !add_to_chat_contacts_table(context, chat_id, contact_id) {
return Ok(false);
}
}
if chat.param.get_int(Param::Unpromoted).unwrap_or_default() == 0 {
msg.type_0 = Viewtype::Text;
msg.text = Some(context.stock_system_msg(
StockMessage::MsgAddMember,
contact.get_addr(),
"",
DC_CONTACT_ID_SELF as u32,
));
msg.param.set_int(Param::Cmd, 4);
msg.param.set(Param::Arg, contact.get_addr());
msg.param.set_int(Param::Arg2, from_handshake.into());
msg.id = send_msg(context, chat_id, &mut msg).unwrap_or_default();
context.call_cb(Event::MsgsChanged {
chat_id,
msg_id: msg.id,
});
}
context.call_cb(Event::MsgsChanged { chat_id, msg_id: 0 });
return Ok(true);
}
fn real_group_exists(context: &Context, chat_id: u32) -> bool {
@@ -1524,7 +1509,7 @@ pub fn remove_contact_from_chat(
if chat.is_promoted() {
msg.type_0 = Viewtype::Text;
if contact.id == DC_CONTACT_ID_SELF {
set_group_explicitly_left(context, chat.grpid)?;
set_group_explicitly_left(context, chat.grpid).unwrap();
msg.text = Some(context.stock_system_msg(
StockMessage::MsgGroupLeft,
"",
@@ -1541,7 +1526,7 @@ pub fn remove_contact_from_chat(
}
msg.param.set_int(Param::Cmd, 5);
msg.param.set(Param::Arg, contact.get_addr());
msg.id = send_msg(context, chat_id, &mut msg)?;
msg.id = send_msg(context, chat_id, &mut msg).unwrap_or_default();
context.call_cb(Event::MsgsChanged {
chat_id,
msg_id: msg.id,
@@ -1605,7 +1590,7 @@ pub fn set_chat_name(
let mut msg = Message::default();
if real_group_exists(context, chat_id) {
if chat.name == new_name.as_ref() {
if &chat.name == new_name.as_ref() {
success = true;
} else if !is_contact_in_chat(context, chat_id, 1) {
emit_event!(
@@ -1638,7 +1623,7 @@ pub fn set_chat_name(
if !chat.name.is_empty() {
msg.param.set(Param::Arg, &chat.name);
}
msg.id = send_msg(context, chat_id, &mut msg)?;
msg.id = send_msg(context, chat_id, &mut msg).unwrap_or_default();
context.call_cb(Event::MsgsChanged {
chat_id,
msg_id: msg.id,
@@ -1707,7 +1692,7 @@ pub fn set_chat_profile_image(
"",
DC_CONTACT_ID_SELF,
));
msg.id = send_msg(context, chat_id, &mut msg)?;
msg.id = send_msg(context, chat_id, &mut msg).unwrap_or_default();
emit_event!(
context,
Event::MsgsChanged {
@@ -1723,35 +1708,36 @@ pub fn set_chat_profile_image(
bail!("Failed to set profile image");
}
pub fn forward_msgs(context: &Context, msg_ids: &[u32], chat_id: u32) -> Result<(), Error> {
ensure!(!msg_ids.is_empty(), "empty msgs_ids: no one to forward to");
ensure!(
chat_id > DC_CHAT_ID_LAST_SPECIAL,
"can not forward to special chat"
);
pub fn forward_msgs(context: &Context, msg_ids: &[u32], chat_id: u32) {
if msg_ids.is_empty() || chat_id <= DC_CHAT_ID_LAST_SPECIAL {
return;
}
let mut created_db_entries = Vec::new();
let mut curr_timestamp: i64;
unarchive(context, chat_id)?;
unarchive(context, chat_id).unwrap();
if let Ok(mut chat) = Chat::load_from_db(context, chat_id) {
curr_timestamp = dc_create_smeared_timestamps(context, msg_ids.len());
let idsstr = msg_ids
.iter()
.into_iter()
.enumerate()
.fold(String::with_capacity(2 * msg_ids.len()), |acc, (i, n)| {
(if i == 0 { acc } else { acc + "," }) + &n.to_string()
});
let ids = context.sql.query_map(
format!(
"SELECT id FROM msgs WHERE id IN({}) ORDER BY timestamp,id",
idsstr
),
params![],
|row| row.get::<_, i32>(0),
|ids| ids.collect::<Result<Vec<_>, _>>().map_err(Into::into),
)?;
let ids = context
.sql
.query_map(
format!(
"SELECT id FROM msgs WHERE id IN({}) ORDER BY timestamp,id",
idsstr
),
params![],
|row| row.get::<_, i32>(0),
|ids| ids.collect::<Result<Vec<_>, _>>().map_err(Into::into),
)
.unwrap(); // TODO: better error handling
for id in ids {
let src_msg_id = id;
@@ -1771,7 +1757,7 @@ pub fn forward_msgs(context: &Context, msg_ids: &[u32], chat_id: u32) -> Result<
let new_msg_id: u32;
if msg.state == MessageState::OutPreparing {
let fresh9 = curr_timestamp;
curr_timestamp += 1;
curr_timestamp = curr_timestamp + 1;
new_msg_id = chat
.prepare_msg_raw(context, &mut msg, fresh9)
.unwrap_or_default();
@@ -1791,11 +1777,11 @@ pub fn forward_msgs(context: &Context, msg_ids: &[u32], chat_id: u32) -> Result<
} else {
msg.state = MessageState::OutPending;
let fresh10 = curr_timestamp;
curr_timestamp += 1;
curr_timestamp = curr_timestamp + 1;
new_msg_id = chat
.prepare_msg_raw(context, &mut msg, fresh10)
.unwrap_or_default();
job_send_msg(context, new_msg_id)?;
job_send_msg(context, new_msg_id);
}
created_db_entries.push(chat_id);
created_db_entries.push(new_msg_id);
@@ -1808,8 +1794,6 @@ pub fn forward_msgs(context: &Context, msg_ids: &[u32], chat_id: u32) -> Result<
msg_id: created_db_entries[i + 1],
});
}
Ok(())
}
pub fn get_chat_contact_cnt(context: &Context, chat_id: u32) -> usize {
@@ -1896,8 +1880,8 @@ mod tests {
#[test]
fn test_get_draft_special_chat_id() {
let t = dummy_context();
let draft = get_draft(&t.ctx, DC_CHAT_ID_LAST_SPECIAL).unwrap();
assert!(draft.is_none());
let draft = get_draft(&t.ctx, DC_CHAT_ID_LAST_SPECIAL);
assert!(draft.is_err());
}
#[test]

View File

@@ -182,7 +182,7 @@ impl Chatlist {
if 0 == listflags & DC_GCL_NO_SPECIALS {
let last_deaddrop_fresh_msg_id = get_last_deaddrop_fresh_msg(context);
if last_deaddrop_fresh_msg_id > 0 {
ids.insert(0, (DC_CHAT_ID_DEADDROP, last_deaddrop_fresh_msg_id));
ids.push((1, last_deaddrop_fresh_msg_id));
}
add_archived_link_item = 1;
}
@@ -258,11 +258,13 @@ impl Chatlist {
let chat_loaded: Chat;
let chat = if let Some(chat) = chat {
chat
} else if let Ok(chat) = Chat::load_from_db(context, self.ids[index].0) {
chat_loaded = chat;
&chat_loaded
} else {
return ret;
if let Ok(chat) = Chat::load_from_db(context, self.ids[index].0) {
chat_loaded = chat;
&chat_loaded
} else {
return ret;
}
};
let lastmsg_id = self.ids[index].1;

View File

@@ -19,12 +19,10 @@ pub enum Config {
MailUser,
MailPw,
MailPort,
ImapCertificateChecks,
SendServer,
SendUser,
SendPw,
SendPort,
SmtpCertificateChecks,
ServerFlags,
#[strum(props(default = "INBOX"))]
ImapFolder,
@@ -32,8 +30,6 @@ pub enum Config {
Selfstatus,
Selfavatar,
#[strum(props(default = "1"))]
BccSelf,
#[strum(props(default = "1"))]
E2eeEnabled,
#[strum(props(default = "1"))]
MdnsEnabled,
@@ -54,12 +50,10 @@ pub enum Config {
ConfiguredMailPw,
ConfiguredMailPort,
ConfiguredMailSecurity,
ConfiguredImapCertificateChecks,
ConfiguredSendServer,
ConfiguredSendUser,
ConfiguredSendPw,
ConfiguredSendPort,
ConfiguredSmtpCertificateChecks,
ConfiguredServerFlags,
ConfiguredSendSecurity,
ConfiguredE2EEEnabled,
@@ -78,13 +72,13 @@ impl Context {
pub fn get_config(&self, key: Config) -> Option<String> {
let value = match key {
Config::Selfavatar => {
let rel_path = self.sql.get_raw_config(self, key);
rel_path.map(|p| dc_get_abs_path(self, &p).to_string_lossy().into_owned())
let rel_path = self.sql.get_config(self, key);
rel_path.map(|p| dc_get_abs_path(self, &p).to_str().unwrap().to_string())
}
Config::SysVersion => Some((&*DC_VERSION_STR).clone()),
Config::SysMsgsizeMaxRecommended => Some(format!("{}", 24 * 1024 * 1024 / 4 * 3)),
Config::SysConfigKeys => Some(get_config_keys_string()),
_ => self.sql.get_raw_config(self, key),
_ => self.sql.get_config(self, key),
};
if value.is_some() {
@@ -98,16 +92,6 @@ impl Context {
}
}
pub fn get_config_int(&self, key: Config) -> i32 {
self.get_config(key)
.and_then(|s| s.parse().ok())
.unwrap_or_default()
}
pub fn get_config_bool(&self, key: Config) -> bool {
self.get_config_int(key) != 0
}
/// Set the given config key.
/// If `None` is passed as a value the value is cleared and set to the default if there is one.
pub fn set_config(&self, key: Config, value: Option<&str>) -> Result<(), Error> {
@@ -115,20 +99,20 @@ impl Context {
Config::Selfavatar if value.is_some() => {
let rel_path = std::fs::canonicalize(value.unwrap())?;
self.sql
.set_raw_config(self, key, Some(&rel_path.to_string_lossy()))
.set_config(self, key, Some(&rel_path.to_string_lossy()))
}
Config::InboxWatch => {
let ret = self.sql.set_raw_config(self, key, value);
let ret = self.sql.set_config(self, key, value);
interrupt_imap_idle(self);
ret
}
Config::SentboxWatch => {
let ret = self.sql.set_raw_config(self, key, value);
let ret = self.sql.set_config(self, key, value);
interrupt_sentbox_idle(self);
ret
}
Config::MvboxWatch => {
let ret = self.sql.set_raw_config(self, key, value);
let ret = self.sql.set_config(self, key, value);
interrupt_mvbox_idle(self);
ret
}
@@ -140,9 +124,9 @@ impl Context {
value
};
self.sql.set_raw_config(self, key, val)
self.sql.set_config(self, key, val)
}
_ => self.sql.set_raw_config(self, key, value),
_ => self.sql.set_config(self, key, value),
}
}
}

View File

@@ -1,8 +1,10 @@
use libc::free;
use quick_xml;
use quick_xml::events::{BytesEnd, BytesStart, BytesText};
use crate::constants::*;
use crate::context::Context;
use crate::dc_tools::*;
use crate::login_param::LoginParam;
use super::read_autoconf_file;
@@ -22,28 +24,26 @@ struct moz_autoconfigure_t<'a> {
pub tag_config: libc::c_int,
}
pub fn moz_autoconfigure(
pub unsafe fn moz_autoconfigure(
context: &Context,
url: &str,
param_in: &LoginParam,
) -> Option<LoginParam> {
let xml_raw = match read_autoconf_file(context, url) {
Err(err) => {
info!(context, "can't read file: {}", err);
return None;
}
Ok(content) => content,
};
let xml_raw = read_autoconf_file(context, url);
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() {
free(xml_raw 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..];
let mut reader = quick_xml::Reader::from_str(&xml_raw);
let mut reader = quick_xml::Reader::from_str(as_str(xml_raw));
reader.trim_text(true);
let mut buf = Vec::new();
@@ -88,9 +88,11 @@ pub fn moz_autoconfigure(
{
let r = moz_ac.out.to_string();
warn!(context, "Bad or incomplete autoconfig: {}", r,);
free(xml_raw as *mut libc::c_void);
return None;
}
free(xml_raw as *mut libc::c_void);
Some(moz_ac.out)
}

View File

@@ -1,10 +1,13 @@
use std::collections::HashMap;
use std::ptr;
use libc::free;
use quick_xml;
use quick_xml::events::{BytesEnd, BytesStart, BytesText};
use crate::constants::*;
use crate::context::Context;
use crate::dc_tools::*;
use crate::login_param::LoginParam;
use quick_xml::events::{BytesEnd};
use super::read_autoconf_file;
/* ******************************************************************************
@@ -14,52 +17,62 @@ use super::read_autoconf_file;
struct outlk_autodiscover_t<'a> {
pub in_0: &'a LoginParam,
pub out: LoginParam,
pub out_imap_set: bool,
pub out_smtp_set: bool,
pub config: HashMap<String, String>,
pub out_imap_set: libc::c_int,
pub out_smtp_set: libc::c_int,
pub tag_config: libc::c_int,
pub config: [*mut libc::c_char; 6],
pub redirect: *mut libc::c_char,
}
pub fn outlk_autodiscover(
pub unsafe fn outlk_autodiscover(
context: &Context,
url__: &str,
param_in: &LoginParam,
) -> Option<LoginParam> {
let mut url = url__.to_string();
let mut xml_raw: *mut libc::c_char = ptr::null_mut();
let mut url = url__.strdup();
let mut outlk_ad = outlk_autodiscover_t {
in_0: param_in,
out: LoginParam::new(),
out_imap_set: false,
out_smtp_set: false,
config: HashMap::new(),
out_imap_set: 0,
out_smtp_set: 0,
tag_config: 0,
config: [ptr::null_mut(); 6],
redirect: ptr::null_mut(),
};
for i in 0..10 {
let xml_raw = read_autoconf_file(context, &url);
if xml_raw.is_err() {
return Some(outlk_ad.out);
let ok_to_continue;
let mut i = 0;
loop {
if !(i < 10) {
ok_to_continue = true;
break;
}
libc::memset(
&mut outlk_ad as *mut outlk_autodiscover_t as *mut libc::c_void,
0,
::std::mem::size_of::<outlk_autodiscover_t>(),
);
xml_raw = read_autoconf_file(context, as_str(url));
if xml_raw.is_null() {
ok_to_continue = false;
break;
}
let xml_raw = xml_raw.unwrap();
let mut reader = quick_xml::Reader::from_str(&xml_raw);
let mut reader = quick_xml::Reader::from_str(as_str(xml_raw));
reader.trim_text(true);
let mut buf = Vec::new();
let current_tag: Option<String> = None;
loop {
match reader.read_event(&mut buf) {
Ok(quick_xml::events::Event::Start(ref e)) => {
current_tag = Some(String::from_utf8_lossy(e.name()).trim().to_lowercase());
outlk_autodiscover_starttag_cb(e, &mut outlk_ad)
}
Ok(quick_xml::events::Event::End(ref e)) => {
if "protocol" == String::from_utf8_lossy(e.name()).trim().to_lowercase() {
finish_settings(e, &mut outlk_ad);
}
current_tag = None;
outlk_autodiscover_endtag_cb(e, &mut outlk_ad)
}
Ok(quick_xml::events::Event::Text(ref e)) => {
if let Some(current_tag) = current_tag {
let val = e.unescape_and_decode(&reader).unwrap_or_default();
&outlk_ad.config.insert(current_tag, val);
}
outlk_autodiscover_text_cb(e, &mut outlk_ad, &reader)
}
Err(e) => {
error!(
@@ -68,70 +81,134 @@ pub fn outlk_autodiscover(
reader.buffer_position(),
e
);
break;
}
Ok(quick_xml::events::Event::Eof) => break,
_ => (),
}
buf.clear();
}
if let Some(next_url) = outlk_ad.config.get("redirecturl") {
if !next_url.is_empty() {
url = next_url.to_string();
continue;
}
if !(!outlk_ad.config[5].is_null()
&& 0 != *outlk_ad.config[5usize].offset(0isize) as libc::c_int)
{
ok_to_continue = true;
break;
}
break;
free(url as *mut libc::c_void);
url = dc_strdup(outlk_ad.config[5usize]);
outlk_clean_config(&mut outlk_ad);
free(xml_raw as *mut libc::c_void);
xml_raw = ptr::null_mut();
i += 1;
}
if outlk_ad.out.mail_server.is_empty()
|| outlk_ad.out.mail_port == 0
|| outlk_ad.out.send_server.is_empty()
|| outlk_ad.out.send_port == 0
{
let r = outlk_ad.out.to_string();
warn!(context, "Bad or incomplete autoconfig: {}", r,);
if ok_to_continue {
if outlk_ad.out.mail_server.is_empty()
|| outlk_ad.out.mail_port == 0
|| outlk_ad.out.send_server.is_empty()
|| outlk_ad.out.send_port == 0
{
let r = outlk_ad.out.to_string();
warn!(context, "Bad or incomplete autoconfig: {}", r,);
free(url as *mut libc::c_void);
free(xml_raw as *mut libc::c_void);
outlk_clean_config(&mut outlk_ad);
return None;
return None;
}
}
free(url as *mut libc::c_void);
free(xml_raw as *mut libc::c_void);
outlk_clean_config(&mut outlk_ad);
Some(outlk_ad.out)
}
fn finish_settings(event: &BytesEnd, outlk_ad: &mut outlk_autodiscover_t) {
let ssl_on = false;
let ssl_off = false;
let config = &outlk_ad.config;
if let Some(type_val) = &config.get("type") {
let port = match config.get("port") {
None => 0,
Some(r) => {
r.parse::<i32>().unwrap_or_default()
}
};
if let Some(ssl) = &config.get("ssl") {
ssl_on = *ssl == "on";
ssl_off = *ssl == "off";
}
let type_val = *type_val;
if !outlk_ad.out_imap_set && type_val == "imap" {
outlk_ad.out.mail_server = config.get("server").unwrap_or("".to_string());
outlk_ad.out.mail_port = port;
if ssl_on {
outlk_ad.out.server_flags |= DC_LP_IMAP_SOCKET_SSL as i32;
} else if ssl_off {
outlk_ad.out.server_flags |= DC_LP_IMAP_SOCKET_PLAIN as i32;
}
outlk_ad.out_imap_set = true;
} else if !outlk_ad.out_smtp_set && type_val == "smtp" {
outlk_ad.out.send_server = &config.get("server").unwrap_or_default();
outlk_ad.out.send_port = port;
if ssl_on {
outlk_ad.out.server_flags |= DC_LP_SMTP_SOCKET_SSL as i32
} else if ssl_off {
outlk_ad.out.server_flags |= DC_LP_SMTP_SOCKET_PLAIN as i32
}
outlk_ad.out_smtp_set = true;
}
unsafe fn outlk_clean_config(mut outlk_ad: *mut outlk_autodiscover_t) {
for i in 0..6 {
free((*outlk_ad).config[i] as *mut libc::c_void);
(*outlk_ad).config[i] = ptr::null_mut();
}
}
fn outlk_autodiscover_text_cb<B: std::io::BufRead>(
event: &BytesText,
outlk_ad: &mut outlk_autodiscover_t,
reader: &quick_xml::Reader<B>,
) {
let val = event.unescape_and_decode(reader).unwrap_or_default();
unsafe {
free(outlk_ad.config[outlk_ad.tag_config as usize].cast());
outlk_ad.config[outlk_ad.tag_config as usize] = val.trim().strdup();
}
}
unsafe fn outlk_autodiscover_endtag_cb(event: &BytesEnd, outlk_ad: &mut outlk_autodiscover_t) {
let tag = String::from_utf8_lossy(event.name()).trim().to_lowercase();
if tag == "protocol" {
if !outlk_ad.config[1].is_null() {
let port = dc_atoi_null_is_0(outlk_ad.config[3]);
let ssl_on = (!outlk_ad.config[4].is_null()
&& strcasecmp(
outlk_ad.config[4],
b"on\x00" as *const u8 as *const libc::c_char,
) == 0) as libc::c_int;
let ssl_off = (!outlk_ad.config[4].is_null()
&& strcasecmp(
outlk_ad.config[4],
b"off\x00" as *const u8 as *const libc::c_char,
) == 0) as libc::c_int;
if strcasecmp(
outlk_ad.config[1],
b"imap\x00" as *const u8 as *const libc::c_char,
) == 0
&& outlk_ad.out_imap_set == 0
{
outlk_ad.out.mail_server = to_string(outlk_ad.config[2]);
outlk_ad.out.mail_port = port;
if 0 != ssl_on {
outlk_ad.out.server_flags |= DC_LP_IMAP_SOCKET_SSL as i32
} else if 0 != ssl_off {
outlk_ad.out.server_flags |= DC_LP_IMAP_SOCKET_PLAIN as i32
}
outlk_ad.out_imap_set = 1
} else if strcasecmp(
outlk_ad.config[1usize],
b"smtp\x00" as *const u8 as *const libc::c_char,
) == 0
&& outlk_ad.out_smtp_set == 0
{
outlk_ad.out.send_server = to_string(outlk_ad.config[2]);
outlk_ad.out.send_port = port;
if 0 != ssl_on {
outlk_ad.out.server_flags |= DC_LP_SMTP_SOCKET_SSL as i32
} else if 0 != ssl_off {
outlk_ad.out.server_flags |= DC_LP_SMTP_SOCKET_PLAIN as i32
}
outlk_ad.out_smtp_set = 1
}
}
outlk_clean_config(outlk_ad);
}
outlk_ad.tag_config = 0;
}
fn outlk_autodiscover_starttag_cb(event: &BytesStart, outlk_ad: &mut outlk_autodiscover_t) {
let tag = String::from_utf8_lossy(event.name()).trim().to_lowercase();
if tag == "protocol" {
unsafe { outlk_clean_config(outlk_ad) };
} else if tag == "type" {
outlk_ad.tag_config = 1
} else if tag == "server" {
outlk_ad.tag_config = 2
} else if tag == "port" {
outlk_ad.tag_config = 3
} else if tag == "ssl" {
outlk_ad.tag_config = 4
} else if tag == "redirecturl" {
outlk_ad.tag_config = 5
};
}

View File

@@ -1,11 +1,9 @@
use percent_encoding::{utf8_percent_encode, NON_ALPHANUMERIC};
use crate::config::Config;
use crate::constants::*;
use crate::context::Context;
use crate::dc_tools::*;
use crate::e2ee;
use crate::error::*;
use crate::imap::*;
use crate::job::*;
use crate::login_param::LoginParam;
@@ -28,7 +26,7 @@ macro_rules! progress {
}
// connect
pub fn configure(context: &Context) {
pub unsafe fn configure(context: &Context) {
if dc_has_ongoing(context) {
warn!(context, "There is already another ongoing process running.",);
return;
@@ -39,7 +37,7 @@ pub fn configure(context: &Context) {
/// Check if the context is already configured.
pub fn dc_is_configured(context: &Context) -> bool {
context.sql.get_raw_config_bool(context, "configured")
context.sql.get_config_bool(context, "configured")
}
/*******************************************************************************
@@ -47,7 +45,7 @@ pub fn dc_is_configured(context: &Context) -> bool {
******************************************************************************/
// the other dc_job_do_DC_JOB_*() functions are declared static in the c-file
#[allow(non_snake_case, unused_must_use)]
pub fn dc_job_do_DC_JOB_CONFIGURE_IMAP(context: &Context) {
pub unsafe fn dc_job_do_DC_JOB_CONFIGURE_IMAP(context: &Context) {
let mut success = false;
let mut imap_connected_here = false;
let mut smtp_connected_here = false;
@@ -115,7 +113,7 @@ pub fn dc_job_do_DC_JOB_CONFIGURE_IMAP(context: &Context) {
param.addr = oauth2_addr;
context
.sql
.set_raw_config(context, "addr", Some(param.addr.as_str()))
.set_config(context, "addr", Some(param.addr.as_str()))
.ok();
}
progress!(context, 20);
@@ -354,7 +352,7 @@ pub fn dc_job_do_DC_JOB_CONFIGURE_IMAP(context: &Context) {
ok_to_continue8 = true;
break;
}
if param_autoconfig.is_some() {
if !param_autoconfig.is_none() {
ok_to_continue8 = false;
break;
}
@@ -425,7 +423,7 @@ pub fn dc_job_do_DC_JOB_CONFIGURE_IMAP(context: &Context) {
.unwrap()
.connect(context, &param)
{
if param_autoconfig.is_some() {
if !param_autoconfig.is_none() {
success = false;
} else if s.shall_stop_ongoing {
success = false;
@@ -478,8 +476,15 @@ pub fn dc_job_do_DC_JOB_CONFIGURE_IMAP(context: &Context) {
}
16 => {
progress!(context, 900);
let flags: libc::c_int = if context.get_config_bool(Config::MvboxWatch)
|| context.get_config_bool(Config::MvboxMove)
let flags: libc::c_int = if 0
!= context
.sql
.get_config_int(context, "mvbox_watch")
.unwrap_or_else(|| 1)
|| 0 != context
.sql
.get_config_int(context, "mvbox_move")
.unwrap_or_else(|| 1)
{
DC_CREATE_MVBOX as i32
} else {
@@ -502,7 +507,7 @@ pub fn dc_job_do_DC_JOB_CONFIGURE_IMAP(context: &Context) {
)
.ok();
context.sql.set_raw_config_bool(context, "configured", true);
context.sql.set_config_bool(context, "configured", true);
true
}
18 => {
@@ -603,7 +608,12 @@ pub fn dc_connect_to_configured_imap(context: &Context, imap: &Imap) -> libc::c_
if imap.is_connected() {
ret_connected = 1
} else if !context.sql.get_raw_config_bool(context, "configured") {
} else if context
.sql
.get_config_int(context, "configured")
.unwrap_or_default()
== 0
{
warn!(context, "Not configured, cannot connect.",);
} else {
let param = LoginParam::from_database(context, "configured_");
@@ -634,7 +644,7 @@ pub fn dc_stop_ongoing_process(context: &Context) {
};
}
pub fn read_autoconf_file(context: &Context, url: &str) -> Result<String> {
pub fn read_autoconf_file(context: &Context, url: &str) -> *mut libc::c_char {
info!(context, "Testing {} ...", url);
match reqwest::Client::new()
@@ -642,10 +652,12 @@ pub fn read_autoconf_file(context: &Context, url: &str) -> Result<String> {
.send()
.and_then(|mut res| res.text())
{
Err(err) => {
bail!("{}", err);
Ok(res) => unsafe { res.strdup() },
Err(_err) => {
info!(context, "Can\'t read file.",);
std::ptr::null_mut()
}
Ok(res) => Ok(res.to_string()),
}
}
@@ -663,6 +675,8 @@ mod tests {
.set_config(Config::Addr, Some("probably@unexistant.addr"))
.unwrap();
t.ctx.set_config(Config::MailPw, Some("123456")).unwrap();
dc_job_do_DC_JOB_CONFIGURE_IMAP(&t.ctx);
unsafe {
dc_job_do_DC_JOB_CONFIGURE_IMAP(&t.ctx);
}
}
}

View File

@@ -25,6 +25,7 @@ impl Default for MoveState {
// some defaults
const DC_E2EE_DEFAULT_ENABLED: i32 = 1;
pub const DC_MDNS_DEFAULT_ENABLED: i32 = 1;
const DC_INBOX_WATCH_DEFAULT: i32 = 1;
const DC_SENTBOX_WATCH_DEFAULT: i32 = 1;
const DC_MVBOX_WATCH_DEFAULT: i32 = 1;
@@ -63,6 +64,15 @@ pub const DC_GCL_ADD_SELF: usize = 0x02;
pub(crate) const DC_FP_NO_AUTOCRYPT_HEADER: i32 = 2;
pub(crate) const DC_FP_ADD_AUTOCRYPT_HEADER: i32 = 1;
/// param1 is a directory where the keys are written to
const DC_IMEX_EXPORT_SELF_KEYS: usize = 1;
/// param1 is a directory where the keys are searched in and read from
const DC_IMEX_IMPORT_SELF_KEYS: usize = 2;
/// param1 is a directory where the backup is written to
const DC_IMEX_EXPORT_BACKUP: usize = 11;
/// param1 is the file with the backup to import
const DC_IMEX_IMPORT_BACKUP: usize = 12;
/// virtual chat showing all messages belonging to chats flagged with chats.blocked=2
pub(crate) const DC_CHAT_ID_DEADDROP: u32 = 1;
/// messages that should be deleted get this chat_id; the messages are deleted from the working thread later then. This is also needed as rfc724_mid should be preset as long as the message is not deleted on the server (otherwise it is downloaded again)
@@ -194,11 +204,6 @@ pub enum Viewtype {
/// and retrieved via dc_msg_get_file(), dc_msg_get_width(), dc_msg_get_height().
Gif = 21,
/// Message containing a sticker, similar to image.
/// If possible, the ui should display the image without borders in a transparent way.
/// A click on a sticker will offer to install the sticker set in some future.
Sticker = 23,
/// Message containing an Audio file.
/// File and duration are set via dc_msg_set_file(), dc_msg_set_duration()
/// and retrieved via dc_msg_get_file(), dc_msg_get_duration().
@@ -303,8 +308,7 @@ const DC_STR_MSGACTIONBYME: usize = 63;
const DC_STR_MSGLOCATIONENABLED: usize = 64;
const DC_STR_MSGLOCATIONDISABLED: usize = 65;
const DC_STR_LOCATION: usize = 66;
const DC_STR_STICKER: usize = 67;
const DC_STR_COUNT: usize = 67;
const DC_STR_COUNT: usize = 66;
pub const DC_JOB_DELETE_MSG_ON_IMAP: i32 = 110;

View File

@@ -390,18 +390,20 @@ impl Contact {
}
sth_modified = Modifier::Modified;
}
} else if sql::execute(
context,
&context.sql,
"INSERT INTO contacts (name, addr, origin) VALUES(?, ?, ?);",
params![name.as_ref(), addr, origin,],
)
.is_ok()
{
row_id = sql::get_rowid(context, &context.sql, "contacts", "addr", addr);
sth_modified = Modifier::Created;
} else {
error!(context, "Cannot add contact.");
if sql::execute(
context,
&context.sql,
"INSERT INTO contacts (name, addr, origin) VALUES(?, ?, ?);",
params![name.as_ref(), addr, origin,],
)
.is_ok()
{
row_id = sql::get_rowid(context, &context.sql, "contacts", "addr", addr);
sth_modified = Modifier::Created;
} else {
error!(context, "Cannot add contact.");
}
}
Ok((row_id, sth_modified))
@@ -802,14 +804,14 @@ impl Contact {
}
if let Some(peerstate) = peerstate {
if peerstate.verified_key.is_some() {
if peerstate.verified_key().is_some() {
return VerifiedStatus::BidirectVerified;
}
}
let peerstate = Peerstate::from_addr(context, &context.sql, &self.addr);
if let Some(ps) = peerstate {
if ps.verified_key.is_some() {
if ps.verified_key().is_some() {
return VerifiedStatus::BidirectVerified;
}
}
@@ -825,7 +827,7 @@ impl Contact {
if let Ok(contact) = Contact::load_from_db(context, contact_id) {
if !contact.addr.is_empty() {
let normalized_addr = addr_normalize(addr.as_ref());
if contact.addr == normalized_addr {
if &contact.addr == &normalized_addr {
return true;
}
}
@@ -961,9 +963,9 @@ pub fn normalize_name(full_name: impl AsRef<str>) -> String {
if len > 0 {
let firstchar = full_name.as_bytes()[0];
let lastchar = full_name.as_bytes()[len - 1];
if firstchar == b'\'' && lastchar == b'\''
|| firstchar == b'\"' && lastchar == b'\"'
|| firstchar == b'<' && lastchar == b'>'
if firstchar == '\'' as u8 && lastchar == '\'' as u8
|| firstchar == '\"' as u8 && lastchar == '\"' as u8
|| firstchar == '<' as u8 && lastchar == '>' as u8
{
full_name = &full_name[1..len - 1];
}

View File

@@ -1,17 +1,13 @@
use std::collections::HashMap;
use std::ffi::OsString;
use std::fs;
use std::io::Write;
use std::path::{Path, PathBuf};
use std::sync::{Arc, Condvar, Mutex, RwLock};
use libc::uintptr_t;
use crate::chat::*;
use crate::config::Config;
use crate::constants::*;
use crate::contact::*;
use crate::dc_tools::{dc_copy_file, dc_derive_safe_stem_ext};
use crate::error::*;
use crate::events::Event;
use crate::imap::*;
@@ -24,7 +20,6 @@ use crate::message::{self, Message};
use crate::param::Params;
use crate::smtp::*;
use crate::sql::Sql;
use rand::{thread_rng, Rng};
/// Callback function type for [Context]
///
@@ -71,30 +66,6 @@ pub struct RunningState {
pub shall_stop_ongoing: bool,
}
/// Return some info about deltachat-core
///
/// This contains information mostly about the library itself, the
/// actual keys and their values which will be present are not
/// guaranteed. Calling [Context::get_info] also includes information
/// about the context on top of the information here.
pub fn get_info() -> HashMap<&'static str, String> {
let mut res = HashMap::new();
res.insert("deltachat_core_version", format!("v{}", &*DC_VERSION_STR));
res.insert("sqlite_version", rusqlite::version().to_string());
res.insert(
"sqlite_thread_safe",
unsafe { rusqlite::ffi::sqlite3_threadsafe() }.to_string(),
);
res.insert(
"arch",
(::std::mem::size_of::<*mut libc::c_void>())
.wrapping_mul(8)
.to_string(),
);
res.insert("level", "awesome".into());
res
}
impl Context {
pub fn new(cb: Box<ContextCallback>, os_name: String, dbfile: PathBuf) -> Result<Context> {
let mut blob_fname = OsString::new();
@@ -163,59 +134,6 @@ impl Context {
self.blobdir.as_path()
}
pub fn copy_to_blobdir(&self, orig_filename: impl AsRef<str>) -> Result<String> {
// return a $BLOBDIR/<filename> with the content of orig_filename
// copied into it. The <filename> will be safely derived from
// orig_filename, and will not clash with existing filenames.
let dest = self.new_blob_file(&orig_filename, b"")?;
if dc_copy_file(
&self,
PathBuf::from(orig_filename.as_ref()),
PathBuf::from(&dest),
) {
Ok(dest)
} else {
bail!("could not copy {} to {}", orig_filename.as_ref(), dest);
}
}
pub fn new_blob_file(&self, orig_filename: impl AsRef<str>, data: &[u8]) -> Result<String> {
// return a $BLOBDIR/<FILENAME> string which corresponds to the
// respective file in the blobdir, and which contains the data.
// FILENAME is computed by looking and possibly mangling the
// basename of orig_filename. The resulting filenames are meant
// to be human-readable.
let (stem, ext) = dc_derive_safe_stem_ext(orig_filename.as_ref());
// ext starts with "." or is empty string, so we can always resconstruct
for i in 0..3 {
let candidate_basename = match i {
// first a try to just use the (possibly mangled) original basename
0 => format!("{}{}", stem, ext),
// otherwise extend stem with random numbers
_ => {
let mut rng = thread_rng();
let random_id: u32 = rng.gen();
format!("{}-{}{}", stem, random_id, ext)
}
};
let path = self.get_blobdir().join(&candidate_basename);
if let Ok(mut file) = fs::OpenOptions::new()
.create_new(true)
.write(true)
.open(&path)
{
file.write_all(data)?;
let db_entry = format!("$BLOBDIR/{}", candidate_basename);
self.call_cb(Event::NewBlobFile(db_entry.clone()));
return Ok(db_entry);
}
}
bail!("out of luck to create new blob file");
}
pub fn call_cb(&self, event: Event) -> uintptr_t {
(*self.cb)(self, event)
}
@@ -224,20 +142,27 @@ impl Context {
let unset = "0";
let l = LoginParam::from_database(self, "");
let l2 = LoginParam::from_database(self, "configured_");
let displayname = self.get_config(Config::Displayname);
let displayname = self.sql.get_config(self, "displayname");
let chats = get_chat_cnt(self) as usize;
let real_msgs = message::get_real_msg_cnt(self) as usize;
let deaddrop_msgs = message::get_deaddrop_msg_cnt(self) as usize;
let contacts = Contact::get_real_cnt(self) as usize;
let is_configured = self.get_config_int(Config::Configured);
let is_configured = self
.sql
.get_config_int(self, "configured")
.unwrap_or_default();
let dbversion = self
.sql
.get_raw_config_int(self, "dbversion")
.get_config_int(self, "dbversion")
.unwrap_or_default();
let e2ee_enabled = self.get_config_int(Config::E2eeEnabled);
let mdns_enabled = self.get_config_int(Config::MdnsEnabled);
let bcc_self = self.get_config_int(Config::BccSelf);
let e2ee_enabled = self
.sql
.get_config_int(self, "e2ee_enabled")
.unwrap_or_else(|| 1);
let mdns_enabled = self
.sql
.get_config_int(self, "mdns_enabled")
.unwrap_or_else(|| 1);
let prv_key_cnt: Option<isize> =
self.sql
@@ -255,25 +180,48 @@ impl Context {
"<Not yet calculated>".into()
};
let inbox_watch = self.get_config_int(Config::InboxWatch);
let sentbox_watch = self.get_config_int(Config::SentboxWatch);
let mvbox_watch = self.get_config_int(Config::MvboxWatch);
let mvbox_move = self.get_config_int(Config::MvboxMove);
let inbox_watch = self
.sql
.get_config_int(self, "inbox_watch")
.unwrap_or_else(|| 1);
let sentbox_watch = self
.sql
.get_config_int(self, "sentbox_watch")
.unwrap_or_else(|| 1);
let mvbox_watch = self
.sql
.get_config_int(self, "mvbox_watch")
.unwrap_or_else(|| 1);
let mvbox_move = self
.sql
.get_config_int(self, "mvbox_move")
.unwrap_or_else(|| 1);
let folders_configured = self
.sql
.get_raw_config_int(self, "folders_configured")
.get_config_int(self, "folders_configured")
.unwrap_or_default();
let configured_sentbox_folder = self
.sql
.get_raw_config(self, "configured_sentbox_folder")
.get_config(self, "configured_sentbox_folder")
.unwrap_or_else(|| "<unset>".to_string());
let configured_mvbox_folder = self
.sql
.get_raw_config(self, "configured_mvbox_folder")
.get_config(self, "configured_mvbox_folder")
.unwrap_or_else(|| "<unset>".to_string());
let mut res = get_info();
let mut res = HashMap::new();
res.insert("deltachat_core_version", format!("v{}", &*DC_VERSION_STR));
res.insert("sqlite_version", rusqlite::version().to_string());
res.insert(
"sqlite_thread_safe",
unsafe { rusqlite::ffi::sqlite3_threadsafe() }.to_string(),
);
res.insert(
"arch",
(::std::mem::size_of::<*mut libc::c_void>())
.wrapping_mul(8)
.to_string(),
);
res.insert("number_of_chats", chats.to_string());
res.insert("number_of_chat_messages", real_msgs.to_string());
res.insert("messages_in_contact_requests", deaddrop_msgs.to_string());
@@ -294,7 +242,6 @@ impl Context {
res.insert("configured_mvbox_folder", configured_mvbox_folder);
res.insert("mdns_enabled", mdns_enabled.to_string());
res.insert("e2ee_enabled", e2ee_enabled.to_string());
res.insert("bcc_self", bcc_self.to_string());
res.insert(
"private_key_count",
prv_key_cnt.unwrap_or_default().to_string(),
@@ -304,6 +251,7 @@ impl Context {
pub_key_cnt.unwrap_or_default().to_string(),
);
res.insert("fingerprint", fingerprint_str);
res.insert("level", "awesome".into());
res
}
@@ -330,7 +278,7 @@ impl Context {
Ok(ret)
},
)
.unwrap_or_default()
.unwrap()
}
#[allow(non_snake_case)]
@@ -374,7 +322,7 @@ impl Context {
}
pub fn is_sentbox(&self, folder_name: impl AsRef<str>) -> bool {
let sentbox_name = self.sql.get_raw_config(self, "configured_sentbox_folder");
let sentbox_name = self.sql.get_config(self, "configured_sentbox_folder");
if let Some(name) = sentbox_name {
name == folder_name.as_ref()
} else {
@@ -383,7 +331,7 @@ impl Context {
}
pub fn is_mvbox(&self, folder_name: impl AsRef<str>) -> bool {
let mvbox_name = self.sql.get_raw_config(self, "configured_mvbox_folder");
let mvbox_name = self.sql.get_config(self, "configured_mvbox_folder");
if let Some(name) = mvbox_name {
name == folder_name.as_ref()
@@ -393,7 +341,12 @@ impl Context {
}
pub fn do_heuristics_moves(&self, folder: &str, msg_id: u32) {
if !self.get_config_bool(Config::MvboxMove) {
if self
.sql
.get_config_int(self, "mvbox_move")
.unwrap_or_else(|| 1)
== 0
{
return;
}
@@ -474,7 +427,6 @@ pub fn get_version_str() -> &'static str {
mod tests {
use super::*;
use crate::dc_tools::*;
use crate::test_utils::*;
#[test]
@@ -512,51 +464,6 @@ mod tests {
assert!(res.is_err());
}
#[test]
fn test_new_blob_file() {
let t = dummy_context();
let context = t.ctx;
let x = &context.new_blob_file("hello", b"data").unwrap();
assert!(dc_file_exist(&context, x));
assert!(x.starts_with("$BLOBDIR"));
assert!(dc_read_file(&context, x).unwrap() == b"data");
let y = &context.new_blob_file("hello", b"data").unwrap();
assert!(dc_file_exist(&context, y));
assert!(y.starts_with("$BLOBDIR/hello-"));
let x = &context.new_blob_file("xyz/hello.png", b"data").unwrap();
assert!(dc_file_exist(&context, x));
assert_eq!(x, "$BLOBDIR/hello.png");
let y = &context.new_blob_file("hello\\world.png", b"data").unwrap();
assert!(dc_file_exist(&context, y));
assert_eq!(y, "$BLOBDIR/world.png");
}
#[test]
fn test_new_blob_file_long_names() {
let t = dummy_context();
let context = t.ctx;
let s = "12312312039182039182039812039810293810293810293810293801293801293123123";
let x = &context.new_blob_file(s, b"data").unwrap();
println!("blobfilename '{}'", x);
println!("xxxxfilename '{}'", s);
assert!(x.len() < s.len());
assert!(dc_file_exist(&context, x));
assert!(x.starts_with("$BLOBDIR"));
}
#[test]
fn test_new_blob_file_unicode() {
let t = dummy_context();
let context = t.ctx;
let s = "helloäworld.qwe";
let x = &context.new_blob_file(s, b"data").unwrap();
assert_eq!(x, "$BLOBDIR/hello-world.qwe");
assert_eq!(dc_read_file(&context, x).unwrap(), b"data");
}
#[test]
fn test_sqlite_parent_not_exists() {
let tmp = tempfile::tempdir().unwrap();
@@ -597,14 +504,6 @@ mod tests {
let t = dummy_context();
let info = t.ctx.get_info();
assert!(info.get("database_dir").is_some());
}
#[test]
fn test_get_info_no_context() {
let info = get_info();
assert!(info.get("deltachat_core_version").is_some());
assert!(info.get("database_dir").is_none());
assert_eq!(info.get("level").unwrap(), "awesome");
}
}

1102
src/dc_imex.rs Normal file

File diff suppressed because it is too large Load Diff

View File

@@ -5,12 +5,14 @@ use std::ptr;
use charset::Charset;
use deltachat_derive::{FromSql, ToSql};
use libc::{strcmp, strlen, strncmp};
use mmime::mailimf::types::*;
use mmime::clist::*;
use mmime::mailimf::*;
use mmime::mailmime::content::*;
use mmime::mailmime::disposition::*;
use mmime::mailmime::types::*;
use mmime::mailimf_types::*;
use mmime::mailmime::*;
use mmime::mailmime_content::*;
use mmime::mailmime_disposition::*;
use mmime::mailmime_types::*;
use mmime::mmapstring::*;
use mmime::other::*;
use crate::constants::Viewtype;
@@ -19,29 +21,26 @@ use crate::context::Context;
use crate::dc_simplify::*;
use crate::dc_strencode::*;
use crate::dc_tools::*;
use crate::e2ee;
use crate::e2ee::*;
use crate::error::Error;
use crate::location;
use crate::param::*;
use crate::stock::StockMessage;
use crate::wrapmime;
#[derive(Debug)]
pub struct MimeParser<'a> {
pub context: &'a Context,
pub parts: Vec<Part>,
pub mimeroot: *mut Mailmime,
pub mimeroot: *mut mailmime,
pub header: HashMap<String, *mut mailimf_field>,
pub header_root: *mut mailimf_fields,
pub header_protected: *mut mailimf_fields,
pub subject: Option<String>,
pub is_send_by_messenger: bool,
pub decrypting_failed: bool,
pub encrypted: bool,
pub signatures: HashSet<String>,
pub gossipped_addr: HashSet<String>,
pub e2ee_helper: E2eeHelper,
pub is_forwarded: bool,
pub reports: Vec<*mut Mailmime>,
pub reports: Vec<*mut mailmime>,
pub is_system_message: SystemMessage,
pub location_kml: Option<location::Kml>,
pub message_kml: Option<location::Kml>,
@@ -93,9 +92,7 @@ impl<'a> MimeParser<'a> {
subject: None,
is_send_by_messenger: false,
decrypting_failed: false,
encrypted: false,
signatures: Default::default(),
gossipped_addr: Default::default(),
e2ee_helper: Default::default(),
is_forwarded: false,
context,
reports: Vec::new(),
@@ -105,7 +102,7 @@ impl<'a> MimeParser<'a> {
}
}
pub unsafe fn parse(&mut self, body: &[u8]) -> Result<(), Error> {
pub unsafe fn parse(&mut self, body: &[u8]) {
let mut index = 0;
let r = mailmime_parse(
@@ -116,18 +113,14 @@ impl<'a> MimeParser<'a> {
);
if r == MAILIMF_NO_ERROR as libc::c_int && !self.mimeroot.is_null() {
let (encrypted, signatures, gossipped_addr) =
e2ee::try_decrypt(self.context, self.mimeroot)?;
self.encrypted = encrypted;
self.signatures = signatures;
self.gossipped_addr = gossipped_addr;
self.e2ee_helper.decrypt(self.context, self.mimeroot);
self.parse_mime_recursive(self.mimeroot);
if let Some(field) = self.lookup_field("Subject") {
if (*field).fld_type == MAILIMF_FIELD_SUBJECT as libc::c_int {
let subj = (*(*field).fld_data.fld_subject).sbj_value;
self.subject = as_opt_str(subj).map(dc_decode_header_words);
self.subject = as_opt_str(subj).map(dc_decode_header_words_safe);
}
}
@@ -164,17 +157,21 @@ impl<'a> MimeParser<'a> {
}
}
}
} else if let Some(optional_field) = self.lookup_optional_field("Chat-Content") {
if optional_field == "location-streaming-enabled" {
self.is_system_message = SystemMessage::LocationStreamingEnabled;
} else {
if let Some(optional_field) = self.lookup_optional_field("Chat-Content") {
if optional_field == "location-streaming-enabled" {
self.is_system_message = SystemMessage::LocationStreamingEnabled;
}
}
}
if self.lookup_field("Chat-Group-Image").is_some() && !self.parts.is_empty() {
let textpart = &self.parts[0];
if textpart.typ == Viewtype::Text && self.parts.len() >= 2 {
let imgpart = &mut self.parts[1];
if imgpart.typ == Viewtype::Image {
imgpart.is_meta = true;
if textpart.typ == Viewtype::Text {
if self.parts.len() >= 2 {
let imgpart = &mut self.parts[1];
if imgpart.typ == Viewtype::Image {
imgpart.is_meta = true;
}
}
}
}
@@ -185,7 +182,6 @@ impl<'a> MimeParser<'a> {
textpart.typ == Viewtype::Text
&& (filepart.typ == Viewtype::Image
|| filepart.typ == Viewtype::Gif
|| filepart.typ == Viewtype::Sticker
|| filepart.typ == Viewtype::Audio
|| filepart.typ == Viewtype::Voice
|| filepart.typ == Viewtype::Video
@@ -253,14 +249,6 @@ impl<'a> MimeParser<'a> {
part_mut.typ = Viewtype::Voice;
}
}
if self.parts[0].typ == Viewtype::Image {
if let Some(content_type) = self.lookup_optional_field("Chat-Content") {
if content_type == "sticker" {
let part_mut = &mut self.parts[0];
part_mut.typ = Viewtype::Sticker;
}
}
}
let part = &self.parts[0];
if part.typ == Viewtype::Audio
|| part.typ == Viewtype::Voice
@@ -282,7 +270,7 @@ impl<'a> MimeParser<'a> {
if self.get_last_nonmeta().is_some() {
let mut mb_list: *mut mailimf_mailbox_list = ptr::null_mut();
let mut index_0 = 0;
let dn_field_c = CString::new(dn_field).unwrap_or_default();
let dn_field_c = CString::new(dn_field).unwrap();
if mailimf_mailbox_list_parse(
dn_field_c.as_ptr(),
@@ -292,12 +280,12 @@ impl<'a> MimeParser<'a> {
) == MAILIMF_NO_ERROR as libc::c_int
&& !mb_list.is_null()
{
if let Some(dn_to_addr) = wrapmime::mailimf_find_first_addr(mb_list) {
if let Some(dn_to_addr) = mailimf_find_first_addr(mb_list) {
if let Some(from_field) = self.lookup_field("From") {
if (*from_field).fld_type == MAILIMF_FIELD_FROM as libc::c_int
&& !(*from_field).fld_data.fld_from.is_null()
{
let from_addr = wrapmime::mailimf_find_first_addr(
let from_addr = mailimf_find_first_addr(
(*(*from_field).fld_data.fld_from).frm_mb_list,
);
if let Some(from_addr) = from_addr {
@@ -329,7 +317,6 @@ impl<'a> MimeParser<'a> {
}
self.parts.push(part_5);
}
Ok(())
}
pub fn get_last_nonmeta(&mut self) -> Option<&mut Part> {
@@ -376,7 +363,7 @@ impl<'a> MimeParser<'a> {
}
}
unsafe fn parse_mime_recursive(&mut self, mime: *mut Mailmime) -> bool {
unsafe fn parse_mime_recursive(&mut self, mime: *mut mailmime) -> bool {
if mime.is_null() {
return false;
}
@@ -441,7 +428,7 @@ impl<'a> MimeParser<'a> {
}
}
unsafe fn handle_multiple(&mut self, mime: *mut Mailmime) -> bool {
unsafe fn handle_multiple(&mut self, mime: *mut mailmime) -> bool {
let mut any_part_added = false;
match mailmime_get_mime_type(mime) {
/* Most times, mutlipart/alternative contains true alternatives
@@ -481,7 +468,7 @@ impl<'a> MimeParser<'a> {
however, most times it seems okay. */
let cur = (*(*mime).mm_data.mm_multipart.mm_mp_list).first;
if !cur.is_null() {
any_part_added = self.parse_mime_recursive((*cur).data as *mut Mailmime);
any_part_added = self.parse_mime_recursive((*cur).data as *mut mailmime);
}
}
(DC_MIMETYPE_MP_NOT_DECRYPTABLE, _, _) => {
@@ -551,7 +538,7 @@ impl<'a> MimeParser<'a> {
plain_cnt += 1;
}
(DC_MIMETYPE_TEXT_HTML, _, _) => {
html_part = cur_data as *mut Mailmime;
html_part = cur_data as *mut mailmime;
html_cnt += 1;
}
_ => {}
@@ -578,7 +565,7 @@ impl<'a> MimeParser<'a> {
any_part_added
}
unsafe fn add_single_part_if_known(&mut self, mime: *mut Mailmime) -> bool {
unsafe fn add_single_part_if_known(&mut self, mime: *mut mailmime) -> bool {
// return true if a part was added
if mime.is_null() || (*mime).mm_data.mm_single.is_null() {
return false;
@@ -595,7 +582,7 @@ impl<'a> MimeParser<'a> {
return false;
}
let mut decoded_data = match wrapmime::mailmime_transfer_decode(mime) {
let mut decoded_data = match mailmime_transfer_decode(mime) {
Ok(decoded_data) => decoded_data,
Err(_) => {
// Note that it's now always an error - might be no data
@@ -620,12 +607,9 @@ impl<'a> MimeParser<'a> {
&& 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_or_default()
.as_bytes(),
) {
if let Some(encoding) =
Charset::for_label(CStr::from_ptr(charset).to_str().unwrap().as_bytes())
{
let (res, _, _) = encoding.decode(&decoded_data);
if res.is_empty() {
/* no error - but nothing to add */
@@ -712,7 +696,7 @@ impl<'a> MimeParser<'a> {
// might be a wrongly encoded filename
let s = to_string_lossy((*dsp_param).pa_data.pa_filename);
// this is used only if the parts buffer stays empty
desired_filename = dc_decode_header_words(&s)
desired_filename = dc_decode_header_words_safe(&s)
}
}
}
@@ -784,40 +768,34 @@ impl<'a> MimeParser<'a> {
decoded_data: &[u8],
desired_filename: &str,
) {
/* write decoded data to new blob file */
let bpath = match self.context.new_blob_file(desired_filename, decoded_data) {
Ok(path) => path,
Err(err) => {
error!(
self.context,
"Could not add blob for mime part {}, error {}", desired_filename, err
);
return;
}
};
/* create a free file name to use */
let path_filename = dc_get_fine_path_filename(self.context, "$BLOBDIR", desired_filename);
let mut part = Part::default();
part.typ = msg_type;
part.mimetype = mime_type;
part.bytes = decoded_data.len() as libc::c_int;
part.param.set(Param::File, bpath);
if let Some(raw_mime) = raw_mime {
part.param.set(Param::MimeType, raw_mime);
}
if mime_type == DC_MIMETYPE_IMAGE {
if let Ok((width, height)) = dc_get_filemeta(decoded_data) {
part.param.set_int(Param::Width, width as i32);
part.param.set_int(Param::Height, height as i32);
/* copy data to file */
if dc_write_file(self.context, &path_filename, decoded_data) {
let mut part = Part::default();
part.typ = msg_type;
part.mimetype = mime_type;
part.bytes = decoded_data.len() as libc::c_int;
part.param.set(Param::File, path_filename.to_string_lossy());
if let Some(raw_mime) = raw_mime {
part.param.set(Param::MimeType, raw_mime);
}
if mime_type == DC_MIMETYPE_IMAGE {
if let Ok((width, height)) = dc_get_filemeta(decoded_data) {
part.param.set_int(Param::Width, width as i32);
part.param.set_int(Param::Height, height as i32);
}
}
self.do_add_single_part(part);
}
self.do_add_single_part(part);
}
fn do_add_single_part(&mut self, mut part: Part) {
if self.encrypted && self.signatures.len() > 0 {
if self.e2ee_helper.encrypted && self.e2ee_helper.signatures.len() > 0 {
part.param.set_int(Param::GuranteeE2ee, 1);
} else if self.encrypted {
} else if self.e2ee_helper.encrypted {
part.param.set_int(Param::ErroneousE2ee, 0x2);
}
self.parts.push(part);
@@ -846,7 +824,7 @@ impl<'a> MimeParser<'a> {
let mut fld_from: *const mailimf_from = ptr::null();
/* get From: and check there is exactly one sender */
let fld = wrapmime::mailimf_find_field(self.header_root, MAILIMF_FIELD_FROM as libc::c_int);
let fld = mailimf_find_field(self.header_root, MAILIMF_FIELD_FROM as libc::c_int);
if !(fld.is_null()
|| {
fld_from = (*fld).fld_data.fld_from;
@@ -864,9 +842,11 @@ impl<'a> MimeParser<'a> {
if !mb.is_null() {
let from_addr_norm = addr_normalize(as_str((*mb).mb_addr_spec));
let recipients = wrapmime::mailimf_get_recipients(self.header_root);
if recipients.len() == 1 && recipients.contains(from_addr_norm) {
sender_equals_recipient = true;
let recipients = mailimf_get_recipients(self.header_root);
if recipients.len() == 1 {
if recipients.contains(from_addr_norm) {
sender_equals_recipient = true;
}
}
}
}
@@ -893,7 +873,7 @@ impl<'a> MimeParser<'a> {
unsafe {
let fld_message_id = (*field).fld_data.fld_message_id;
if !fld_message_id.is_null() {
return Some(to_string_lossy((*fld_message_id).mid_value));
return Some(to_string((*fld_message_id).mid_value));
}
}
}
@@ -909,6 +889,7 @@ impl<'a> Drop for MimeParser<'a> {
if !self.mimeroot.is_null() {
unsafe { mailmime_free(self.mimeroot) };
}
unsafe { self.e2ee_helper.thanks() };
}
}
@@ -923,6 +904,22 @@ pub struct Part {
pub param: Params,
}
pub fn mailimf_find_first_addr(mb_list: *const mailimf_mailbox_list) -> Option<String> {
if mb_list.is_null() {
return None;
}
for cur in unsafe { (*(*mb_list).mb_list).into_iter() } {
let mb = cur as *mut mailimf_mailbox;
if !mb.is_null() && !unsafe { (*mb).mb_addr_spec.is_null() } {
let addr = unsafe { as_str((*mb).mb_addr_spec) };
return Some(addr_normalize(addr).to_string());
}
}
None
}
unsafe fn hash_header(out: &mut HashMap<String, *mut mailimf_field>, in_0: *const mailimf_fields) {
if in_0.is_null() {
return;
@@ -972,7 +969,7 @@ unsafe fn hash_header(out: &mut HashMap<String, *mut mailimf_field>, in_0: *cons
}
}
unsafe fn mailmime_get_mime_type(mime: *mut Mailmime) -> (libc::c_int, Viewtype, Option<String>) {
unsafe fn mailmime_get_mime_type(mime: *mut mailmime) -> (libc::c_int, Viewtype, Option<String>) {
let c = (*mime).mm_content_type;
let unknown_type = (0, Viewtype::Unknown, None);
@@ -993,12 +990,14 @@ unsafe fn mailmime_get_mime_type(mime: *mut Mailmime) -> (libc::c_int, Viewtype,
) == 0i32
{
return (DC_MIMETYPE_TEXT_PLAIN, Viewtype::Text, None);
} else if strcmp(
(*c).ct_subtype,
b"html\x00" as *const u8 as *const libc::c_char,
) == 0i32
{
return (DC_MIMETYPE_TEXT_HTML, Viewtype::Text, None);
} else {
if strcmp(
(*c).ct_subtype,
b"html\x00" as *const u8 as *const libc::c_char,
) == 0i32
{
return (DC_MIMETYPE_TEXT_HTML, Viewtype::Text, None);
}
}
}
@@ -1057,8 +1056,7 @@ unsafe fn mailmime_get_mime_type(mime: *mut Mailmime) -> (libc::c_int, Viewtype,
Some("alternative") => DC_MIMETYPE_MP_ALTERNATIVE,
Some("related") => DC_MIMETYPE_MP_RELATED,
Some("encrypted") => {
// maybe try_decrypt failed to decrypt
// or it wasn't in proper Autocrypt format
// decryptable parts are already converted to other mime parts in dc_e2ee_decrypt()
DC_MIMETYPE_MP_NOT_DECRYPTABLE
}
Some("signed") => DC_MIMETYPE_MP_SIGNED,
@@ -1095,7 +1093,7 @@ fn reconcat_mime(typ: Option<&str>, subtype: Option<&str>) -> String {
format!("{}/{}", typ, subtype)
}
unsafe fn mailmime_is_attachment_disposition(mime: *mut Mailmime) -> bool {
unsafe fn mailmime_is_attachment_disposition(mime: *mut mailmime) -> bool {
if (*mime).mm_mime_fields.is_null() {
return false;
}
@@ -1120,7 +1118,7 @@ unsafe fn mailmime_is_attachment_disposition(mime: *mut Mailmime) -> bool {
/* low-level-tools for working with mailmime structures directly */
pub unsafe fn mailmime_find_ct_parameter(
mime: *mut Mailmime,
mime: *mut mailmime,
name: &str,
) -> *mut mailmime_parameter {
if mime.is_null()
@@ -1142,6 +1140,207 @@ pub unsafe fn mailmime_find_ct_parameter(
ptr::null_mut()
}
pub unsafe fn mailmime_transfer_decode(mime: *mut mailmime) -> Result<Vec<u8>, Error> {
ensure!(!mime.is_null(), "invalid inputs");
let mut mime_transfer_encoding = MAILMIME_MECHANISM_BINARY as libc::c_int;
let mime_data = (*mime).mm_data.mm_single;
if !(*mime).mm_mime_fields.is_null() {
for cur in (*(*(*mime).mm_mime_fields).fld_list).into_iter() {
let field = cur as *mut mailmime_field;
if !field.is_null()
&& (*field).fld_type == MAILMIME_FIELD_TRANSFER_ENCODING as libc::c_int
&& !(*field).fld_data.fld_encoding.is_null()
{
mime_transfer_encoding = (*(*field).fld_data.fld_encoding).enc_type;
break;
}
}
}
if mime_transfer_encoding == MAILMIME_MECHANISM_7BIT as libc::c_int
|| mime_transfer_encoding == MAILMIME_MECHANISM_8BIT as libc::c_int
|| mime_transfer_encoding == MAILMIME_MECHANISM_BINARY as libc::c_int
{
let decoded_data = (*mime_data).dt_data.dt_text.dt_data;
let decoded_data_bytes = (*mime_data).dt_data.dt_text.dt_length;
if decoded_data.is_null() || decoded_data_bytes <= 0 {
bail!("No data to decode found");
} else {
let result = std::slice::from_raw_parts(decoded_data as *const u8, decoded_data_bytes);
return Ok(result.to_vec());
}
}
let mut current_index = 0;
let mut transfer_decoding_buffer = ptr::null_mut();
let mut decoded_data_bytes = 0;
let r = mailmime_part_parse(
(*mime_data).dt_data.dt_text.dt_data,
(*mime_data).dt_data.dt_text.dt_length,
&mut current_index,
mime_transfer_encoding,
&mut transfer_decoding_buffer,
&mut decoded_data_bytes,
);
if r == MAILIMF_NO_ERROR as libc::c_int
&& !transfer_decoding_buffer.is_null()
&& decoded_data_bytes > 0
{
let result =
std::slice::from_raw_parts(transfer_decoding_buffer as *const u8, decoded_data_bytes)
.to_vec();
mmap_string_unref(transfer_decoding_buffer);
return Ok(result);
}
Err(format_err!("Failed to to decode"))
}
pub unsafe fn mailimf_get_recipients(imffields: *mut mailimf_fields) -> HashSet<String> {
/* returned addresses are normalized. */
let mut recipients: HashSet<String> = Default::default();
for cur in (*(*imffields).fld_list).into_iter() {
let fld = cur as *mut mailimf_field;
let fld_to: *mut mailimf_to;
let fld_cc: *mut mailimf_cc;
let mut addr_list: *mut mailimf_address_list = ptr::null_mut();
// TODO match on enums /rtn
match (*fld).fld_type {
13 => {
fld_to = (*fld).fld_data.fld_to;
if !fld_to.is_null() {
addr_list = (*fld_to).to_addr_list
}
}
14 => {
fld_cc = (*fld).fld_data.fld_cc;
if !fld_cc.is_null() {
addr_list = (*fld_cc).cc_addr_list
}
}
_ => {}
}
if !addr_list.is_null() {
for cur2 in (*(*addr_list).ad_list).into_iter() {
let adr = cur2 as *mut mailimf_address;
if !adr.is_null() {
if (*adr).ad_type == MAILIMF_ADDRESS_MAILBOX as libc::c_int {
mailimf_get_recipients_add_addr(&mut recipients, (*adr).ad_data.ad_mailbox);
} else if (*adr).ad_type == MAILIMF_ADDRESS_GROUP as libc::c_int {
let group: *mut mailimf_group = (*adr).ad_data.ad_group;
if !group.is_null() && !(*group).grp_mb_list.is_null() {
for cur3 in (*(*(*group).grp_mb_list).mb_list).into_iter() {
mailimf_get_recipients_add_addr(
&mut recipients,
cur3 as *mut mailimf_mailbox,
);
}
}
}
}
}
}
}
recipients
}
fn mailimf_get_recipients_add_addr(recipients: &mut HashSet<String>, mb: *mut mailimf_mailbox) {
if !mb.is_null() {
let addr_norm = addr_normalize(as_str(unsafe { (*mb).mb_addr_spec }));
recipients.insert(addr_norm.into());
}
}
/*the result is a pointer to mime, must not be freed*/
pub unsafe fn mailimf_find_field(
header: *mut mailimf_fields,
wanted_fld_type: libc::c_int,
) -> *mut mailimf_field {
if header.is_null() || (*header).fld_list.is_null() {
return ptr::null_mut();
}
let mut cur1: *mut clistiter = (*(*header).fld_list).first;
while !cur1.is_null() {
let field: *mut mailimf_field = (if !cur1.is_null() {
(*cur1).data
} else {
ptr::null_mut()
}) as *mut mailimf_field;
if !field.is_null() {
if (*field).fld_type == wanted_fld_type {
return field;
}
}
cur1 = if !cur1.is_null() {
(*cur1).next
} else {
ptr::null_mut()
}
}
ptr::null_mut()
}
/*the result is a pointer to mime, must not be freed*/
pub unsafe fn mailmime_find_mailimf_fields(mime: *mut mailmime) -> *mut mailimf_fields {
if mime.is_null() {
return ptr::null_mut();
}
match (*mime).mm_type as _ {
MAILMIME_MULTIPLE => {
for cur_data in (*(*mime).mm_data.mm_multipart.mm_mp_list).into_iter() {
let header = mailmime_find_mailimf_fields(cur_data as *mut _);
if !header.is_null() {
return header;
}
}
}
MAILMIME_MESSAGE => return (*mime).mm_data.mm_message.mm_fields,
_ => {}
}
ptr::null_mut()
}
pub unsafe fn mailimf_find_optional_field(
header: *mut mailimf_fields,
wanted_fld_name: *const libc::c_char,
) -> *mut mailimf_optional_field {
if header.is_null() || (*header).fld_list.is_null() {
return ptr::null_mut();
}
for cur_data in (*(*header).fld_list).into_iter() {
let field: *mut mailimf_field = cur_data as *mut _;
if (*field).fld_type == MAILIMF_FIELD_OPTIONAL_FIELD as libc::c_int {
let optional_field: *mut mailimf_optional_field = (*field).fld_data.fld_optional_field;
if !optional_field.is_null()
&& !(*optional_field).fld_name.is_null()
&& !(*optional_field).fld_value.is_null()
&& strcasecmp((*optional_field).fld_name, wanted_fld_name) == 0i32
{
return optional_field;
}
}
}
ptr::null_mut()
}
#[cfg(test)]
mod tests {
use super::*;
@@ -1154,16 +1353,17 @@ mod tests {
unsafe {
let txt: *const libc::c_char =
b"FieldA: ValueA\nFieldB: ValueB\n\x00" as *const u8 as *const libc::c_char;
let mut mime: *mut Mailmime = ptr::null_mut();
let mut mime: *mut mailmime = ptr::null_mut();
let mut dummy = 0;
let res = mailmime_parse(txt, strlen(txt), &mut dummy, &mut mime);
assert_eq!(res, MAIL_NO_ERROR as libc::c_int);
assert!(!mime.is_null());
let fields: *mut mailimf_fields = wrapmime::mailmime_find_mailimf_fields(mime);
let fields: *mut mailimf_fields = mailmime_find_mailimf_fields(mime);
assert!(!fields.is_null());
let mut of_a: *mut mailimf_optional_field = wrapmime::mailimf_find_optional_field(
let mut of_a: *mut mailimf_optional_field = mailimf_find_optional_field(
fields,
b"fielda\x00" as *const u8 as *const libc::c_char,
);
@@ -1183,7 +1383,7 @@ mod tests {
"ValueA",
);
of_a = wrapmime::mailimf_find_optional_field(
of_a = mailimf_find_optional_field(
fields,
b"FIELDA\x00" as *const u8 as *const libc::c_char,
);
@@ -1203,7 +1403,7 @@ mod tests {
"ValueA",
);
let of_b: *mut mailimf_optional_field = wrapmime::mailimf_find_optional_field(
let of_b: *mut mailimf_optional_field = mailimf_find_optional_field(
fields,
b"FieldB\x00" as *const u8 as *const libc::c_char,
);
@@ -1226,9 +1426,7 @@ mod tests {
let context = dummy_context();
let raw = include_bytes!("../test-data/message/issue_523.txt");
let mut mimeparser = MimeParser::new(&context.ctx);
unsafe {
mimeparser.parse(&raw[..]).unwrap();
};
unsafe { mimeparser.parse(&raw[..]) };
assert_eq!(mimeparser.subject, None);
assert_eq!(mimeparser.parts.len(), 1);
}
@@ -1236,13 +1434,9 @@ mod tests {
proptest! {
#[test]
fn test_dc_mailmime_parse_crash_fuzzy(data in "[!-~\t ]{2000,}") {
// this test doesn't exercise much of dc_mimeparser anymore
// because a missing From-field early aborts parsing
let context = dummy_context();
let mut mimeparser = MimeParser::new(&context.ctx);
unsafe {
assert!(mimeparser.parse(data.as_bytes()).is_err());
}
unsafe { mimeparser.parse(data.as_bytes()) };
}
}
@@ -1251,7 +1445,7 @@ mod tests {
let context = dummy_context();
let raw = include_bytes!("../test-data/message/mail_with_message_id.txt");
let mut mimeparser = MimeParser::new(&context.ctx);
unsafe { mimeparser.parse(&raw[..]).unwrap() };
unsafe { mimeparser.parse(&raw[..]) };
assert_eq!(
mimeparser.get_rfc724_mid(),
Some("2dfdbde7@example.org".into())
@@ -1263,7 +1457,7 @@ mod tests {
let context = dummy_context();
let raw = include_bytes!("../test-data/message/issue_523.txt");
let mut mimeparser = MimeParser::new(&context.ctx);
unsafe { mimeparser.parse(&raw[..]).unwrap() };
unsafe { mimeparser.parse(&raw[..]) };
assert_eq!(mimeparser.get_rfc724_mid(), None);
}
@@ -1271,9 +1465,9 @@ mod tests {
fn test_mimeparser_with_context() {
unsafe {
let context = dummy_context();
let raw = b"From: hello\nContent-Type: multipart/mixed; boundary=\"==break==\";\nSubject: outer-subject\nX-Special-A: special-a\nFoo: Bar\nChat-Version: 0.0\n\n--==break==\nContent-Type: text/plain; protected-headers=\"v1\";\nSubject: inner-subject\nX-Special-B: special-b\nFoo: Xy\nChat-Version: 1.0\n\ntest1\n\n--==break==--\n\n\x00";
let raw = b"Content-Type: multipart/mixed; boundary=\"==break==\";\nSubject: outer-subject\nX-Special-A: special-a\nFoo: Bar\nChat-Version: 0.0\n\n--==break==\nContent-Type: text/plain; protected-headers=\"v1\";\nSubject: inner-subject\nX-Special-B: special-b\nFoo: Xy\nChat-Version: 1.0\n\ntest1\n\n--==break==--\n\n\x00";
let mut mimeparser = MimeParser::new(&context.ctx);
mimeparser.parse(&raw[..]).unwrap();
mimeparser.parse(&raw[..]);
assert_eq!(mimeparser.subject, Some("inner-subject".into()));

View File

@@ -2,17 +2,17 @@ use std::ffi::CString;
use std::ptr;
use itertools::join;
use libc::{free, strcmp};
use libc::{free, strcmp, strlen};
use mmime::clist::*;
use mmime::mailimf::types::*;
use mmime::mailmime::content::*;
use mmime::mailmime::types::*;
use mmime::mailimf::*;
use mmime::mailimf_types::*;
use mmime::mailmime::*;
use mmime::mailmime_content::*;
use mmime::mailmime_types::*;
use mmime::other::*;
use sha2::{Digest, Sha256};
use crate::chat::{self, Chat};
use crate::config::Config;
use crate::constants::*;
use crate::contact::*;
use crate::context::Context;
@@ -29,7 +29,6 @@ use crate::peerstate::*;
use crate::securejoin::handle_securejoin_handshake;
use crate::sql;
use crate::stock::StockMessage;
use crate::wrapmime;
#[derive(Debug, PartialEq, Eq)]
enum CreateEvent {
@@ -40,7 +39,8 @@ enum CreateEvent {
/// Receive a message and add it to the database.
pub unsafe fn dc_receive_imf(
context: &Context,
imf_raw: &[u8],
imf_raw_not_terminated: *const libc::c_char,
imf_raw_bytes: libc::size_t,
server_folder: impl AsRef<str>,
server_uid: u32,
flags: u32,
@@ -61,10 +61,9 @@ pub unsafe fn dc_receive_imf(
// we use mailmime_parse() through dc_mimeparser (both call mailimf_struct_multiple_parse()
// somewhen, I did not found out anything that speaks against this approach yet)
let body = std::slice::from_raw_parts(imf_raw_not_terminated as *const u8, imf_raw_bytes);
let mut mime_parser = MimeParser::new(context);
if let Err(err) = mime_parser.parse(imf_raw) {
error!(context, "dc_receive_imf parse error: {}", err);
};
mime_parser.parse(body);
if mime_parser.header.is_empty() {
// Error - even adding an empty record won't help as we do not know the message ID
@@ -82,7 +81,7 @@ pub unsafe fn dc_receive_imf(
let mut chat_id = 0;
let mut hidden = 0;
let mut needs_delete_job = false;
let mut add_delete_job: libc::c_int = 0;
let mut insert_msg_id = 0;
let mut sent_timestamp = 0;
@@ -148,7 +147,7 @@ pub unsafe fn dc_receive_imf(
if mime_parser.sender_equals_recipient() {
from_id = DC_CONTACT_ID_SELF;
}
} else if !from_list.is_empty() {
} else if from_list.len() >= 1 {
// if there is no from given, from_id stays 0 which is just fine. These messages
// are very rare, however, we have to add them to the database (they go to the
// "deaddrop" chat) to avoid a re-download from the server. See also [**]
@@ -205,7 +204,8 @@ pub unsafe fn dc_receive_imf(
if let Err(err) = add_parts(
context,
&mut mime_parser,
imf_raw,
imf_raw_not_terminated,
imf_raw_bytes,
incoming,
&mut incoming_origin,
server_folder.as_ref(),
@@ -219,7 +219,7 @@ pub unsafe fn dc_receive_imf(
&mut chat_id,
&mut to_id,
flags,
&mut needs_delete_job,
&mut add_delete_job,
to_self,
&mut insert_msg_id,
&mut created_db_entries,
@@ -250,7 +250,7 @@ pub unsafe fn dc_receive_imf(
from_id,
sent_timestamp,
&mut rr_event_to_send,
&server_folder,
server_folder,
server_uid,
);
}
@@ -266,8 +266,7 @@ pub unsafe fn dc_receive_imf(
);
}
// if we delete we don't need to try moving messages
if needs_delete_job && !created_db_entries.is_empty() {
if 0 != add_delete_job && !created_db_entries.is_empty() {
job_add(
context,
Action::DeleteMsgOnImap,
@@ -275,8 +274,6 @@ pub unsafe fn dc_receive_imf(
Params::new(),
0,
);
} else {
context.do_heuristics_moves(server_folder.as_ref(), insert_msg_id);
}
info!(
@@ -295,7 +292,8 @@ pub unsafe fn dc_receive_imf(
unsafe fn add_parts(
context: &Context,
mut mime_parser: &mut MimeParser,
imf_raw: &[u8],
imf_raw_not_terminated: *const libc::c_char,
imf_raw_bytes: libc::size_t,
incoming: i32,
incoming_origin: &mut Origin,
server_folder: impl AsRef<str>,
@@ -309,7 +307,7 @@ unsafe fn add_parts(
chat_id: &mut u32,
to_id: &mut u32,
flags: u32,
needs_delete_job: &mut bool,
add_delete_job: &mut libc::c_int,
to_self: i32,
insert_msg_id: &mut u32,
created_db_entries: &mut Vec<(usize, usize)>,
@@ -374,7 +372,10 @@ unsafe fn add_parts(
// maybe this can be optimized later, by checking the state before the message body is downloaded
let mut allow_creation = 1;
if mime_parser.is_system_message != SystemMessage::AutocryptSetupMessage && msgrmsg == 0 {
let show_emails = context.get_config_int(Config::ShowEmails);
let show_emails = context
.sql
.get_config_int(context, "show_emails")
.unwrap_or_default();
if show_emails == 0 {
*chat_id = 3;
allow_creation = 0
@@ -404,7 +405,7 @@ unsafe fn add_parts(
let handshake = handle_securejoin_handshake(context, mime_parser, *from_id);
if 0 != handshake & DC_HANDSHAKE_STOP_NORMAL_PROCESSING {
*hidden = 1;
*needs_delete_job = 0 != handshake & DC_HANDSHAKE_ADD_DELETE_JOB;
*add_delete_job = handshake & DC_HANDSHAKE_ADD_DELETE_JOB;
state = MessageState::InSeen;
}
}
@@ -495,12 +496,10 @@ unsafe fn add_parts(
// if the chat_id is blocked,
// for unknown senders and non-delta messages set the state to NOTICED
// to not result in a contact request (this would require the state FRESH)
if Blocked::Not != chat_id_blocked
&& state == MessageState::InFresh
&& !incoming_origin.is_verified()
&& msgrmsg == 0
{
state = MessageState::InNoticed;
if Blocked::Not != chat_id_blocked && state == MessageState::InFresh {
if !incoming_origin.is_verified() && msgrmsg == 0 {
state = MessageState::InNoticed;
}
}
} else {
// Outgoing
@@ -581,11 +580,11 @@ unsafe fn add_parts(
);
// unarchive chat
chat::unarchive(context, *chat_id)?;
chat::unarchive(context, *chat_id).unwrap();
// if the mime-headers should be saved, find out its size
// (the mime-header ends with an empty line)
let save_mime_headers = context.get_config_bool(Config::SaveMimeHeaders);
let save_mime_headers = context.sql.get_config_bool(context, "save_mime_headers");
if let Some(field) = mime_parser.lookup_field_typ("In-Reply-To", MAILIMF_FIELD_IN_REPLY_TO) {
let fld_in_reply_to = (*field).fld_data.fld_in_reply_to;
if !fld_in_reply_to.is_null() {
@@ -613,7 +612,7 @@ unsafe fn add_parts(
let icnt = mime_parser.parts.len();
let mut txt_raw = None;
context
let is_ok = context
.sql
.prepare(
"INSERT INTO msgs \
@@ -629,7 +628,7 @@ unsafe fn add_parts(
}
if let Some(ref msg) = part.msg {
if mime_parser.location_kml.is_some()
if !mime_parser.location_kml.is_none()
&& icnt == 1
&& (msg == "-location-" || msg.is_empty())
{
@@ -684,12 +683,15 @@ unsafe fn add_parts(
part.bytes,
*hidden,
if save_mime_headers {
Some(String::from_utf8_lossy(imf_raw))
Some(String::from_utf8_lossy(std::slice::from_raw_parts(
imf_raw_not_terminated as *const u8,
imf_raw_bytes,
)))
} else {
None
},
to_string_lossy(mime_in_reply_to),
to_string_lossy(mime_references),
to_string(mime_in_reply_to),
to_string(mime_references),
])?;
txt_raw = None;
@@ -700,10 +702,13 @@ unsafe fn add_parts(
Ok(())
},
)
.map_err(|err| {
cleanup(mime_in_reply_to, mime_references);
err
})?;
.is_ok();
if !is_ok {
// i/o error - there is nothing more we can do - in other cases, we try to write at least an empty record
cleanup(mime_in_reply_to, mime_references);
bail!("Cannot write DB.");
}
info!(
context,
@@ -723,6 +728,7 @@ unsafe fn add_parts(
}
}
context.do_heuristics_moves(server_folder.as_ref(), *insert_msg_id);
cleanup(mime_in_reply_to, mime_references);
Ok(())
@@ -738,7 +744,10 @@ unsafe fn handle_reports(
server_folder: impl AsRef<str>,
server_uid: u32,
) {
let mdns_enabled = context.get_config_bool(Config::MdnsEnabled);
let mdns_enabled = context
.sql
.get_config_int(context, "mdns_enabled")
.unwrap_or_else(|| DC_MDNS_DEFAULT_ENABLED);
for report_root in &mime_parser.reports {
let report_root = *report_root;
@@ -757,7 +766,7 @@ unsafe fn handle_reports(
&& (*(*report_root).mm_data.mm_multipart.mm_mp_list).count >= 2
{
// to get a clear functionality, do not show incoming MDNs if the options is disabled
if mdns_enabled {
if 0 != mdns_enabled {
let report_data = (if !if !(*(*report_root).mm_data.mm_multipart.mm_mp_list)
.first
.is_null()
@@ -779,7 +788,7 @@ unsafe fn handle_reports(
.data
} else {
ptr::null_mut()
}) as *mut Mailmime;
}) as *mut mailmime;
if !report_data.is_null()
&& (*(*(*report_data).mm_content_type).ct_type).tp_type
@@ -794,7 +803,7 @@ unsafe fn handle_reports(
b"disposition-notification\x00" as *const u8 as *const libc::c_char,
) == 0
{
if let Ok(report_body) = wrapmime::mailmime_transfer_decode(report_data) {
if let Ok(report_body) = mailmime_transfer_decode(report_data) {
let mut report_parsed = std::ptr::null_mut();
let mut dummy = 0;
@@ -806,14 +815,13 @@ unsafe fn handle_reports(
) == MAIL_NO_ERROR as libc::c_int
&& !report_parsed.is_null()
{
let report_fields =
wrapmime::mailmime_find_mailimf_fields(report_parsed);
let report_fields = mailmime_find_mailimf_fields(report_parsed);
if !report_fields.is_null() {
let of_disposition = wrapmime::mailimf_find_optional_field(
let of_disposition = mailimf_find_optional_field(
report_fields,
b"Disposition\x00" as *const u8 as *const libc::c_char,
);
let of_org_msgid = wrapmime::mailimf_find_optional_field(
let of_org_msgid = mailimf_find_optional_field(
report_fields,
b"Original-Message-ID\x00" as *const u8 as *const libc::c_char,
);
@@ -822,16 +830,24 @@ unsafe fn handle_reports(
&& !of_org_msgid.is_null()
&& !(*of_org_msgid).fld_value.is_null()
{
if let Ok(rfc724_mid) = wrapmime::parse_message_id(as_str(
let mut rfc724_mid_0 = std::ptr::null_mut();
dummy = 0;
if mailimf_msg_id_parse(
(*of_org_msgid).fld_value,
)) {
strlen((*of_org_msgid).fld_value),
&mut dummy,
&mut rfc724_mid_0,
) == MAIL_NO_ERROR as libc::c_int
&& !rfc724_mid_0.is_null()
{
let mut chat_id_0 = 0;
let mut msg_id = 0;
if message::mdn_from_ext(
context,
from_id,
&rfc724_mid,
as_str(rfc724_mid_0),
sent_timestamp,
&mut chat_id_0,
&mut msg_id,
@@ -839,6 +855,7 @@ unsafe fn handle_reports(
rr_event_to_send.push((chat_id_0, msg_id));
}
mdn_consumed = (msg_id != 0) as libc::c_int;
free(rfc724_mid_0.cast());
}
}
}
@@ -852,7 +869,12 @@ unsafe fn handle_reports(
let mut param = Params::new();
param.set(Param::ServerFolder, server_folder.as_ref());
param.set_int(Param::ServerUid, server_uid as i32);
if mime_parser.is_send_by_messenger && context.get_config_bool(Config::MvboxMove) {
if mime_parser.is_send_by_messenger
&& 0 != context
.sql
.get_config_int(context, "mvbox_move")
.unwrap_or_else(|| 1)
{
param.set_int(Param::AlsoMove, 1);
}
job_add(context, Action::MarkseenMdnOnImap, 0, param, 0);
@@ -1026,7 +1048,7 @@ unsafe fn create_or_lookup_group(
{
let fld_in_reply_to = (*field).fld_data.fld_in_reply_to;
if !fld_in_reply_to.is_null() {
grpid = to_string_lossy(dc_extract_grpid_from_rfc724_mid_list(
grpid = to_string(dc_extract_grpid_from_rfc724_mid_list(
(*fld_in_reply_to).mid_list,
));
}
@@ -1037,7 +1059,7 @@ unsafe fn create_or_lookup_group(
{
let fld_references = (*field).fld_data.fld_references;
if !fld_references.is_null() {
grpid = to_string_lossy(dc_extract_grpid_from_rfc724_mid_list(
grpid = to_string(dc_extract_grpid_from_rfc724_mid_list(
(*fld_references).mid_list,
));
}
@@ -1062,7 +1084,7 @@ unsafe fn create_or_lookup_group(
}
if let Some(optional_field) = mime_parser.lookup_optional_field("Chat-Group-Name") {
grpname = Some(dc_decode_header_words(&optional_field));
grpname = Some(dc_decode_header_words_safe(&optional_field));
}
if let Some(optional_field) = mime_parser.lookup_optional_field("Chat-Group-Member-Removed") {
X_MrRemoveFromGrp = Some(optional_field);
@@ -1133,15 +1155,21 @@ unsafe fn create_or_lookup_group(
// check, if we have a chat with this group ID
let (mut chat_id, chat_id_verified, _blocked) = chat::get_chat_id_by_grpid(context, &grpid);
if chat_id != 0 {
if chat_id_verified {
if let Err(err) =
check_verified_properties(context, mime_parser, from_id as u32, to_ids)
{
warn!(context, "verification problem: {}", err);
let s = format!("{}. See 'Info' for more details", err);
mime_parser.repl_msg_by_error(s);
}
let mut failure_reason = std::ptr::null_mut();
if chat_id_verified
&& 0 == check_verified_properties(
context,
mime_parser,
from_id as u32,
to_ids,
&mut failure_reason,
)
{
mime_parser.repl_msg_by_error(to_string(failure_reason));
}
free(failure_reason.cast());
}
// check if the sender is a member of the existing group -
@@ -1154,7 +1182,8 @@ unsafe fn create_or_lookup_group(
group_explicitly_left = chat::is_group_explicitly_left(context, &grpid).unwrap_or_default();
let self_addr = context
.get_config(Config::ConfiguredAddr)
.sql
.get_config(context, "configured_addr")
.unwrap_or_default();
if chat_id == 0
&& !mime_parser.is_mailinglist_message()
@@ -1169,14 +1198,18 @@ unsafe fn create_or_lookup_group(
let mut create_verified = VerifiedStatus::Unverified;
if mime_parser.lookup_field("Chat-Verified").is_some() {
create_verified = VerifiedStatus::Verified;
let mut failure_reason = std::ptr::null_mut();
if let Err(err) =
check_verified_properties(context, mime_parser, from_id as u32, to_ids)
{
warn!(context, "verification problem: {}", err);
let s = format!("{}. See 'Info' for more details", err);
mime_parser.repl_msg_by_error(&s);
if 0 == check_verified_properties(
context,
mime_parser,
from_id as u32,
to_ids,
&mut failure_reason,
) {
mime_parser.repl_msg_by_error(to_string(failure_reason));
}
free(failure_reason.cast());
}
if 0 == allow_creation {
cleanup(ret_chat_id, ret_chat_id_blocked, chat_id, chat_id_blocked);
@@ -1264,7 +1297,7 @@ unsafe fn create_or_lookup_group(
} else {
chat.param.set(Param::ProfileImage, grpimage);
}
chat.update_param(context).unwrap_or_default();
chat.update_param(context).unwrap();
send_EVENT_CHAT_MODIFIED = 1;
}
}
@@ -1475,7 +1508,7 @@ fn create_group_record(
sql::get_rowid(context, &context.sql, "chats", "grpid", grpid.as_ref())
}
fn create_adhoc_grp_id(context: &Context, member_ids: &[u32]) -> String {
fn create_adhoc_grp_id(context: &Context, member_ids: &Vec<u32>) -> String {
/* algorithm:
- sort normalized, lowercased, e-mail addresses alphabetically
- put all e-mail addresses into a single string, separate the address by a single comma
@@ -1484,7 +1517,8 @@ fn create_adhoc_grp_id(context: &Context, member_ids: &[u32]) -> String {
*/
let member_ids_str = join(member_ids.iter().map(|x| x.to_string()), ",");
let member_cs = context
.get_config(Config::ConfiguredAddr)
.sql
.get_config(context, "configured_addr")
.unwrap_or_else(|| "no-self".to_string())
.to_lowercase();
@@ -1575,45 +1609,58 @@ fn search_chat_ids_by_contact_ids(context: &Context, unsorted_contact_ids: &Vec<
}
Ok(())
}
).unwrap_or_default(); // TODO: better error handling
).unwrap(); // TODO: better error handling
}
}
chat_ids
}
fn check_verified_properties(
unsafe fn check_verified_properties(
context: &Context,
mimeparser: &MimeParser,
from_id: u32,
to_ids: &[u32],
) -> Result<()> {
let contact = Contact::load_from_db(context, from_id)?;
to_ids: &Vec<u32>,
failure_reason: *mut *mut libc::c_char,
) -> libc::c_int {
let verify_fail = |reason: String| {
*failure_reason = format!("{}. See \"Info\" for details.", reason).strdup();
warn!(context, "{}", reason);
};
ensure!(mimeparser.encrypted, "This message is not encrypted.");
let contact = match Contact::load_from_db(context, from_id) {
Ok(contact) => contact,
Err(_err) => {
verify_fail("Internal Error; cannot load contact".into());
return 0;
}
};
if !mimeparser.e2ee_helper.encrypted {
verify_fail("This message is not encrypted".into());
return 0;
}
// ensure, the contact is verified
// and the message is signed with a verified key of the sender.
// this check is skipped for SELF as there is no proper SELF-peerstate
// and results in group-splits otherwise.
if from_id != DC_CONTACT_ID_SELF {
if from_id != 1 {
let peerstate = Peerstate::from_addr(context, &context.sql, contact.get_addr());
if peerstate.is_none()
|| contact.is_verified_ex(context, peerstate.as_ref())
!= VerifiedStatus::BidirectVerified
{
bail!(
"Sender of this message is not verified: {}",
contact.get_addr()
);
verify_fail("The sender of this message is not verified.".into());
return 0;
}
if let Some(peerstate) = peerstate {
ensure!(
peerstate.has_verified_key(&mimeparser.signatures),
"The message was sent with non-verified encryption."
);
if !peerstate.has_verified_key(&mimeparser.e2ee_helper.signatures) {
verify_fail("The message was sent with non-verified encryption.".into());
return 0;
}
}
}
@@ -1626,19 +1673,19 @@ fn check_verified_properties(
to_ids_str,
),
params![],
|row| Ok((row.get::<_, String>(0)?, row.get::<_, i32>(1).unwrap_or(0))),
|row| Ok((row.get::<_, String>(0)?, row.get::<_, i32>(1)?)),
|rows| {
rows.collect::<std::result::Result<Vec<_>, _>>()
.map_err(Into::into)
},
)?;
);
for (to_addr, _is_verified) in rows.into_iter() {
let mut is_verified = _is_verified != 0;
if rows.is_err() {
return 0;
}
for (to_addr, mut is_verified) in rows.unwrap().into_iter() {
let mut peerstate = Peerstate::from_addr(context, &context.sql, &to_addr);
// mark gossiped keys (if any) as verified
if mimeparser.gossipped_addr.contains(&to_addr) && peerstate.is_some() {
if mimeparser.e2ee_helper.gossipped_addr.contains(&to_addr) && peerstate.is_some() {
let peerstate = peerstate.as_mut().unwrap();
// if we're here, we know the gossip key is verified:
@@ -1646,27 +1693,29 @@ fn check_verified_properties(
// - OR if the verified-key does not match public-key or gossip-key
// (otherwise a verified key can _only_ be updated through QR scan which might be annoying,
// see https://github.com/nextleap-project/countermitm/issues/46 for a discussion about this point)
if !is_verified
if 0 == is_verified
|| peerstate.verified_key_fingerprint != peerstate.public_key_fingerprint
&& peerstate.verified_key_fingerprint != peerstate.gossip_key_fingerprint
{
info!(context, "{} has verified {}.", contact.get_addr(), to_addr,);
info!(context, "{} has verfied {}.", contact.get_addr(), to_addr,);
let fp = peerstate.gossip_key_fingerprint.clone();
if let Some(fp) = fp {
peerstate.set_verified(0, &fp, 2);
peerstate.save_to_db(&context.sql, false)?;
is_verified = true;
peerstate.save_to_db(&context.sql, false);
is_verified = 1;
}
}
}
if !is_verified {
bail!(
if 0 == is_verified {
verify_fail(format!(
"{} is not a member of this verified group",
to_addr.to_string()
);
to_addr
));
return 0;
}
}
Ok(())
1
}
fn set_better_msg(mime_parser: &mut MimeParser, better_msg: impl AsRef<str>) {
@@ -1683,7 +1732,7 @@ unsafe fn dc_is_reply_to_known_message(context: &Context, mime_parser: &MimePars
/* check if the message is a reply to a known message; the replies are identified by the Message-ID from
`In-Reply-To`/`References:` (to support non-Delta-Clients) or from `Chat-Predecessor:` (Delta clients, see comment in dc_chat.c) */
if let Some(optional_field) = mime_parser.lookup_optional_field("Chat-Predecessor") {
let optional_field_c = CString::new(optional_field).unwrap_or_default();
let optional_field_c = CString::new(optional_field).unwrap();
if 0 != is_known_rfc724_mid(context, optional_field_c.as_ptr()) {
return 1;
}
@@ -1706,13 +1755,13 @@ unsafe fn dc_is_reply_to_known_message(context: &Context, mime_parser: &MimePars
if let Some(field) = mime_parser.lookup_field("References") {
if (*field).fld_type == MAILIMF_FIELD_REFERENCES as libc::c_int {
let fld_references = (*field).fld_data.fld_references;
if !fld_references.is_null()
&& is_known_rfc724_mid_in_list(
if !fld_references.is_null() {
if is_known_rfc724_mid_in_list(
context,
(*(*field).fld_data.fld_references).mid_list,
)
{
return 1;
) {
return 1;
}
}
}
}
@@ -1726,12 +1775,12 @@ unsafe fn is_known_rfc724_mid_in_list(context: &Context, mid_list: *const clist)
}
for data in &*mid_list {
if is_known_rfc724_mid(context, data.cast()) != 0 {
if 0 != is_known_rfc724_mid(context, data.cast()) {
return true;
}
}
false
return false;
}
/// Check if a message is a reply to a known message (messenger or non-messenger).
@@ -1935,7 +1984,8 @@ unsafe fn add_or_lookup_contact_by_addr(
}
*check_self = 0;
let self_addr = context
.get_config(Config::ConfiguredAddr)
.sql
.get_config(context, "configured_addr")
.unwrap_or_default();
if addr_cmp(self_addr, as_str(addr_spec)) {
@@ -1948,15 +1998,17 @@ unsafe fn add_or_lookup_contact_by_addr(
/* add addr_spec if missing, update otherwise */
let mut display_name_dec = "".to_string();
if !display_name_enc.is_null() {
let tmp = dc_decode_header_words(as_str(display_name_enc));
let tmp = as_str(dc_decode_header_words(display_name_enc));
display_name_dec = normalize_name(&tmp);
}
/*can be NULL*/
let row_id = Contact::add_or_lookup(context, display_name_dec, as_str(addr_spec), origin)
.map(|(id, _)| id)
.unwrap_or_default();
if 0 != row_id && !ids.contains(&row_id) {
ids.push(row_id);
if 0 != row_id {
if !ids.contains(&row_id) {
ids.push(row_id);
}
};
}

View File

@@ -10,16 +10,18 @@ pub struct Simplify {
///
/// Also return whether not-standard (rfc3676, §4.3) footer is found.
fn find_message_footer(lines: &[&str]) -> (usize, bool) {
for (ix, &line) in lines.iter().enumerate() {
for ix in 0..lines.len() {
let line = lines[ix];
// quoted-printable may encode `-- ` to `-- =20` which is converted
// back to `-- `
match line {
match line.as_ref() {
"-- " | "-- " => return (ix, false),
"--" | "---" | "----" => return (ix, true),
_ => (),
}
}
(lines.len(), false)
return (lines.len(), false);
}
impl Simplify {
@@ -101,8 +103,10 @@ impl Simplify {
if let Some(last_quoted_line) = l_lastQuotedLine {
l_last = last_quoted_line;
is_cut_at_end = true;
if l_last > 1 && is_empty_line(lines[l_last - 1]) {
l_last -= 1
if l_last > 1 {
if is_empty_line(lines[l_last - 1]) {
l_last -= 1
}
}
if l_last > 1 {
let line = lines[l_last - 1];
@@ -201,7 +205,7 @@ fn is_quoted_headline(buf: &str) -> bool {
}
fn is_plain_quote(buf: &str) -> bool {
buf.starts_with('>')
buf.starts_with(">")
}
#[cfg(test)]

View File

@@ -1,11 +1,11 @@
use itertools::Itertools;
use std::borrow::Cow;
use std::ffi::CString;
use std::ptr;
use charset::Charset;
use libc::free;
use mmime::mailmime::decode::mailmime_encoded_phrase_parse;
use libc::{free, strlen};
use mmime::mailmime_decode::*;
use mmime::mmapstring::*;
use mmime::other::*;
use percent_encoding::{percent_decode, utf8_percent_encode, AsciiSet, CONTROLS};
@@ -25,50 +25,217 @@ use crate::dc_tools::*;
* @return Returns the encoded string which must be free()'d when no longed needed.
* On errors, NULL is returned.
*/
pub fn dc_encode_header_words(input: impl AsRef<str>) -> String {
let mut result = String::default();
for (_, group) in &input.as_ref().chars().group_by(|c| c.is_whitespace()) {
let word: String = group.collect();
result.push_str(&quote_word(&word.as_bytes()));
pub unsafe fn dc_encode_header_words(to_encode_r: impl AsRef<str>) -> String {
let to_encode =
CString::new(to_encode_r.as_ref().as_bytes()).expect("invalid cstring to_encode");
let mut ok_to_continue = true;
let mut ret_str: *mut libc::c_char = ptr::null_mut();
let mut cur: *const libc::c_char = to_encode.as_ptr();
let mmapstr: *mut MMAPString = mmap_string_new(b"\x00" as *const u8 as *const libc::c_char);
if mmapstr.is_null() {
ok_to_continue = false;
}
result
}
fn must_encode(byte: u8) -> bool {
static SPECIALS: &[u8] = b",:!\"#$@[\\]^`{|}~=?_";
SPECIALS.into_iter().any(|b| *b == byte)
}
fn quote_word(word: &[u8]) -> String {
let mut result = String::default();
let mut encoded = false;
for byte in word {
let byte = *byte;
if byte >= 128 || must_encode(byte) {
result.push_str(&format!("={:2X}", byte));
encoded = true;
} else if byte == b' ' {
result.push('_');
encoded = true;
loop {
if !ok_to_continue {
if !mmapstr.is_null() {
mmap_string_free(mmapstr);
}
break;
} else {
result.push(byte as _);
if *cur as libc::c_int != '\u{0}' as i32 {
let begin: *const libc::c_char;
let mut end: *const libc::c_char;
let mut do_quote: bool;
let mut quote_words: libc::c_int;
begin = cur;
end = begin;
quote_words = 0i32;
do_quote = true;
while *cur as libc::c_int != '\u{0}' as i32 {
get_word(cur, &mut cur, &mut do_quote);
if !do_quote {
break;
}
quote_words = 1i32;
end = cur;
if *cur as libc::c_int != '\u{0}' as i32 {
cur = cur.offset(1isize)
}
}
if 0 != quote_words {
if !quote_word(
mmapstr,
begin,
end.wrapping_offset_from(begin) as libc::size_t,
) {
ok_to_continue = false;
continue;
}
if *end as libc::c_int == ' ' as i32 || *end as libc::c_int == '\t' as i32 {
if mmap_string_append_c(mmapstr, *end).is_null() {
ok_to_continue = false;
continue;
}
end = end.offset(1isize)
}
if *end as libc::c_int != '\u{0}' as i32 {
if mmap_string_append_len(
mmapstr,
end,
cur.wrapping_offset_from(end) as libc::size_t,
)
.is_null()
{
ok_to_continue = false;
continue;
}
}
} else if mmap_string_append_len(
mmapstr,
begin,
cur.wrapping_offset_from(begin) as libc::size_t,
)
.is_null()
{
ok_to_continue = false;
continue;
}
if !(*cur as libc::c_int == ' ' as i32 || *cur as libc::c_int == '\t' as i32) {
continue;
}
if mmap_string_append_c(mmapstr, *cur).is_null() {
ok_to_continue = false;
continue;
}
cur = cur.offset(1isize);
} else {
ret_str = strdup((*mmapstr).str_0);
ok_to_continue = false;
}
}
}
if encoded {
result = format!("=?utf-8?Q?{}?=", &result);
let s = to_string(ret_str);
free(ret_str.cast());
s
}
unsafe fn quote_word(
mmapstr: *mut MMAPString,
word: *const libc::c_char,
size: libc::size_t,
) -> bool {
let mut cur: *const libc::c_char;
let mut i = 0;
let mut hex: [libc::c_char; 4] = [0; 4];
// let mut col: libc::c_int = 0i32;
if mmap_string_append(mmapstr, b"=?utf-8?Q?\x00".as_ptr().cast()).is_null() {
return false;
}
result
// col = (*mmapstr).len as libc::c_int;
cur = word;
while i < size {
let mut do_quote_char = false;
match *cur as u8 as char {
',' | ':' | '!' | '"' | '#' | '$' | '@' | '[' | '\\' | ']' | '^' | '`' | '{' | '|'
| '}' | '~' | '=' | '?' | '_' => do_quote_char = true,
_ => {
if *cur as u8 >= 128 {
do_quote_char = true;
}
}
}
if do_quote_char {
print_hex(hex.as_mut_ptr(), cur);
if mmap_string_append(mmapstr, hex.as_mut_ptr()).is_null() {
return false;
}
// col += 3i32
} else {
if *cur as libc::c_int == ' ' as i32 {
if mmap_string_append_c(mmapstr, '_' as i32 as libc::c_char).is_null() {
return false;
}
} else if mmap_string_append_c(mmapstr, *cur).is_null() {
return false;
}
// col += 3i32
}
cur = cur.offset(1isize);
i = i.wrapping_add(1)
}
if mmap_string_append(mmapstr, b"?=\x00" as *const u8 as *const libc::c_char).is_null() {
return false;
}
true
}
unsafe fn get_word(
begin: *const libc::c_char,
pend: *mut *const libc::c_char,
pto_be_quoted: *mut bool,
) {
let mut cur: *const libc::c_char = begin;
while *cur as libc::c_int != ' ' as i32
&& *cur as libc::c_int != '\t' as i32
&& *cur as libc::c_int != '\u{0}' as i32
{
cur = cur.offset(1isize)
}
*pto_be_quoted = to_be_quoted(begin, cur.wrapping_offset_from(begin) as libc::size_t);
*pend = cur;
}
/* ******************************************************************************
* Encode/decode header words, RFC 2047
******************************************************************************/
pub(crate) fn dc_decode_header_words(input: &str) -> String {
/* see comment below */
unsafe fn to_be_quoted(word: *const libc::c_char, size: libc::size_t) -> bool {
let mut cur: *const libc::c_char = word;
let mut i = 0;
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
| 61 | 63 | 95 => return true,
_ => {
if *cur as libc::c_uchar as libc::c_int >= 128i32 {
return true;
}
}
}
cur = cur.offset(1isize);
i = i.wrapping_add(1)
}
false
}
pub unsafe fn dc_decode_header_words(in_0: *const libc::c_char) -> *mut libc::c_char {
if in_0.is_null() {
return ptr::null_mut();
}
let mut out: *mut libc::c_char = ptr::null_mut();
let mut cur_token = 0;
let r: libc::c_int = mailmime_encoded_phrase_parse(
b"iso-8859-1\x00" as *const u8 as *const libc::c_char,
in_0,
strlen(in_0),
&mut cur_token,
b"utf-8\x00" as *const u8 as *const libc::c_char,
&mut out,
);
if r != MAILIMF_NO_ERROR as libc::c_int || out.is_null() {
out = dc_strdup(in_0)
}
out
}
pub fn dc_decode_header_words_safe(input: &str) -> String {
static FROM_ENCODING: &[u8] = b"iso-8859-1\x00";
static TO_ENCODING: &[u8] = b"utf-8\x00";
let mut out = ptr::null_mut();
@@ -86,7 +253,7 @@ pub(crate) fn dc_decode_header_words(input: &str) -> String {
if r as u32 != MAILIMF_NO_ERROR || out.is_null() {
input.to_string()
} else {
let res = to_string_lossy(out);
let res = to_string(out);
free(out.cast());
res
}
@@ -152,35 +319,73 @@ pub fn dc_decode_ext_header(to_decode: &[u8]) -> Cow<str> {
String::from_utf8_lossy(to_decode)
}
unsafe fn print_hex(target: *mut libc::c_char, cur: *const libc::c_char) {
assert!(!target.is_null());
assert!(!cur.is_null());
let bytes = std::slice::from_raw_parts(cur as *const _, strlen(cur));
let raw = CString::yolo(format!("={}", &hex::encode_upper(bytes)[..2]));
libc::memcpy(target as *mut _, raw.as_ptr() as *const _, 4);
}
#[cfg(test)]
mod tests {
use super::*;
use libc::strcmp;
use std::ffi::CStr;
#[test]
fn test_dc_decode_header_words() {
assert_eq!(
dc_decode_header_words("=?utf-8?B?dGVzdMOkw7bDvC50eHQ=?="),
std::string::String::from_utf8(b"test\xc3\xa4\xc3\xb6\xc3\xbc.txt".to_vec()).unwrap(),
);
assert_eq!(dc_decode_header_words("just ascii test"), "just ascii test");
assert_eq!(dc_encode_header_words("abcdef"), "abcdef");
let r = dc_encode_header_words(
std::string::String::from_utf8(b"test\xc3\xa4\xc3\xb6\xc3\xbc.txt".to_vec()).unwrap(),
);
assert!(r.starts_with("=?utf-8"));
assert_eq!(
dc_decode_header_words(&r),
std::string::String::from_utf8(b"test\xc3\xa4\xc3\xb6\xc3\xbc.txt".to_vec()).unwrap(),
);
assert_eq!(
dc_decode_header_words("=?ISO-8859-1?Q?attachment=3B=0D=0A_filename=3D?= =?ISO-8859-1?Q?=22test=E4=F6=FC=2Etxt=22=3B=0D=0A_size=3D39?="),
std::string::String::from_utf8(b"attachment;\r\n filename=\"test\xc3\xa4\xc3\xb6\xc3\xbc.txt\";\r\n size=39".to_vec()).unwrap(),
unsafe {
let mut buf1: *mut libc::c_char = dc_decode_header_words(
b"=?utf-8?B?dGVzdMOkw7bDvC50eHQ=?=\x00" as *const u8 as *const libc::c_char,
);
assert_eq!(
strcmp(
buf1,
b"test\xc3\xa4\xc3\xb6\xc3\xbc.txt\x00" as *const u8 as *const libc::c_char
),
0
);
free(buf1 as *mut libc::c_void);
buf1 =
dc_decode_header_words(b"just ascii test\x00" as *const u8 as *const libc::c_char);
assert_eq!(CStr::from_ptr(buf1).to_str().unwrap(), "just ascii test");
free(buf1 as *mut libc::c_void);
assert_eq!(dc_encode_header_words("abcdef"), "abcdef");
let r = dc_encode_header_words(
std::string::String::from_utf8(b"test\xc3\xa4\xc3\xb6\xc3\xbc.txt".to_vec())
.unwrap(),
);
assert!(r.starts_with("=?utf-8"));
buf1 = r.strdup();
let buf2: *mut libc::c_char = dc_decode_header_words(buf1);
assert_eq!(
strcmp(
buf2,
b"test\xc3\xa4\xc3\xb6\xc3\xbc.txt\x00" as *const u8 as *const libc::c_char
),
0
);
free(buf2 as *mut libc::c_void);
buf1 = dc_decode_header_words(
b"=?ISO-8859-1?Q?attachment=3B=0D=0A_filename=3D?= =?ISO-8859-1?Q?=22test=E4=F6=FC=2Etxt=22=3B=0D=0A_size=3D39?=\x00" as *const u8 as *const libc::c_char
);
assert_eq!(
strcmp(
buf1,
b"attachment;\r\n filename=\"test\xc3\xa4\xc3\xb6\xc3\xbc.txt\";\r\n size=39\x00" as *const u8 as *const libc::c_char,
),
0
);
}
}
#[test]
@@ -224,6 +429,18 @@ mod tests {
assert_eq!(dc_needs_ext_header("a b"), true);
}
#[test]
fn test_print_hex() {
let mut hex: [libc::c_char; 4] = [0; 4];
let cur = b"helloworld" as *const u8 as *const libc::c_char;
unsafe { print_hex(hex.as_mut_ptr(), cur) };
assert_eq!(to_string(hex.as_ptr() as *const _), "=68");
let cur = b":" as *const u8 as *const libc::c_char;
unsafe { print_hex(hex.as_mut_ptr(), cur) };
assert_eq!(to_string(hex.as_ptr() as *const _), "=3A");
}
use proptest::prelude::*;
proptest! {
@@ -239,13 +456,5 @@ mod tests {
// make sure this never panics
let _decoded = dc_decode_ext_header(&buf);
}
#[test]
fn test_dc_header_roundtrip(input: String) {
let encoded = dc_encode_header_words(&input);
let decoded = dc_decode_header_words(&encoded);
assert_eq!(input, decoded);
}
}
}

View File

@@ -1,23 +1,21 @@
//! Some tools and enhancements to the used libraries, there should be
//! no references to Context and other "larger" entities here.
use core::cmp::max;
use std::borrow::Cow;
use std::ffi::{CStr, CString};
use std::ffi::{CStr, CString, OsString};
use std::path::{Path, PathBuf};
use std::str::FromStr;
use std::time::SystemTime;
use std::{fmt, fs, ptr};
use chrono::{Local, TimeZone};
use libc::{memcpy, strlen};
use libc::{memcpy, strcpy, strlen, uintptr_t};
use mmime::clist::*;
use mmime::mailimf::types::*;
use mmime::mailimf_types::*;
use rand::{thread_rng, Rng};
use crate::context::Context;
use crate::error::Error;
use crate::events::Event;
pub(crate) fn dc_exactly_one_bit_set(v: libc::c_int) -> bool {
0 != v && 0 == v & (v - 1)
@@ -30,11 +28,11 @@ pub(crate) fn dc_exactly_one_bit_set(v: libc::c_int) -> bool {
/// # Examples
///
/// ```
/// use deltachat::dc_tools::{dc_strdup, to_string_lossy};
/// use deltachat::dc_tools::{dc_strdup, to_string};
/// unsafe {
/// let str_a = b"foobar\x00" as *const u8 as *const libc::c_char;
/// let str_a_copy = dc_strdup(str_a);
/// assert_eq!(to_string_lossy(str_a_copy), "foobar");
/// assert_eq!(to_string(str_a_copy), "foobar");
/// assert_ne!(str_a, str_a_copy);
/// }
/// ```
@@ -142,7 +140,7 @@ pub(crate) fn dc_truncate(buf: &str, approx_chars: usize, do_unwrap: bool) -> Co
.unwrap_or_default();
if let Some(index) = buf[..end_pos].rfind(|c| c == ' ' || c == '\n') {
Cow::Owned(format!("{}{}", &buf[..=index], ellipse))
Cow::Owned(format!("{}{}", &buf[..index + 1], ellipse))
} else {
Cow::Owned(format!("{}{}", &buf[..end_pos], ellipse))
}
@@ -151,6 +149,66 @@ pub(crate) fn dc_truncate(buf: &str, approx_chars: usize, do_unwrap: bool) -> Co
}
}
#[allow(non_snake_case)]
pub(crate) unsafe fn dc_truncate_n_unwrap_str(
buf: *mut libc::c_char,
approx_characters: libc::c_int,
do_unwrap: libc::c_int,
) {
/* Function unwraps the given string and removes unnecessary whitespace.
Function stops processing after approx_characters are processed.
(as we're using UTF-8, for simplicity, we cut the string only at whitespaces). */
/* a single line is truncated `...` instead of `[...]` (the former is typically also used by the UI to fit strings in a rectangle) */
let ellipse_utf8: *const libc::c_char = if 0 != do_unwrap {
b" ...\x00" as *const u8 as *const libc::c_char
} else {
b" [...]\x00" as *const u8 as *const libc::c_char
};
let mut lastIsCharacter: libc::c_int = 0;
/* force unsigned - otherwise the `> ' '` comparison will fail */
let mut p1: *mut libc::c_uchar = buf as *mut libc::c_uchar;
while 0 != *p1 {
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;
if dc_utf8_strnlen(buf, used_bytes) >= approx_characters as usize {
let buf_bytes = strlen(buf);
if buf_bytes.wrapping_sub(used_bytes) >= strlen(ellipse_utf8) {
strcpy(p1 as *mut libc::c_char, ellipse_utf8);
}
break;
} else {
lastIsCharacter = 0;
if 0 != do_unwrap {
*p1 = ' ' as i32 as libc::c_uchar
}
}
} else if 0 != do_unwrap {
*p1 = '\r' as i32 as libc::c_uchar
}
p1 = p1.offset(1isize)
}
if 0 != do_unwrap {
dc_remove_cr_chars(buf);
};
}
unsafe fn dc_utf8_strnlen(s: *const libc::c_char, n: libc::size_t) -> libc::size_t {
if s.is_null() {
return 0;
}
let mut j: libc::size_t = 0;
for i in 0..n {
if *s.add(i) as libc::c_int & 0xc0 != 0x80 {
j = j.wrapping_add(1)
}
}
j
}
pub(crate) unsafe fn dc_str_from_clist(
list: *const clist,
delimiter: *const libc::c_char,
@@ -219,26 +277,26 @@ pub(crate) fn dc_str_to_color(s: impl AsRef<str>) -> u32 {
/* date/time tools */
/* the result is UTC or DC_INVALID_TIMESTAMP */
pub(crate) fn dc_timestamp_from_date(date_time: *mut mailimf_date_time) -> i64 {
assert!(!date_time.is_null());
let dt = unsafe { *date_time };
let sec = dt.dt_sec;
let min = dt.dt_min;
let hour = dt.dt_hour;
let day = dt.dt_day;
let month = dt.dt_month;
let year = dt.dt_year;
pub(crate) unsafe fn dc_timestamp_from_date(date_time: *mut mailimf_date_time) -> i64 {
let sec = (*date_time).dt_sec;
let min = (*date_time).dt_min;
let hour = (*date_time).dt_hour;
let day = (*date_time).dt_day;
let month = (*date_time).dt_month;
let year = (*date_time).dt_year;
let ts = chrono::NaiveDateTime::new(
chrono::NaiveDate::from_ymd(year, month as u32, day as u32),
chrono::NaiveTime::from_hms(hour as u32, min as u32, sec as u32),
);
let (zone_hour, zone_min) = if dt.dt_zone >= 0 {
(dt.dt_zone / 100, dt.dt_zone % 100)
let (zone_hour, zone_min) = if (*date_time).dt_zone >= 0 {
((*date_time).dt_zone / 100, (*date_time).dt_zone % 100)
} else {
(-(-dt.dt_zone / 100), -(-dt.dt_zone % 100))
(
-(-(*date_time).dt_zone / 100),
-(-(*date_time).dt_zone % 100),
)
};
ts.timestamp() - (zone_hour * 3600 + zone_min * 60) as i64
@@ -335,14 +393,14 @@ fn encode_66bits_as_base64(v1: u32, v2: u32, fill: u32) -> String {
enc.write_u8(((fill & 0x3) as u8) << 6).unwrap();
enc.finish().unwrap();
}
assert_eq!(wrapped_writer.pop(), Some(b'A')); // Remove last "A"
assert_eq!(wrapped_writer.pop(), Some('A' as u8)); // Remove last "A"
String::from_utf8(wrapped_writer).unwrap()
}
pub(crate) fn dc_create_incoming_rfc724_mid(
message_timestamp: i64,
contact_id_from: u32,
contact_ids_to: &[u32],
contact_ids_to: &Vec<u32>,
) -> Option<String> {
if contact_ids_to.is_empty() {
return None;
@@ -417,41 +475,12 @@ pub(crate) fn dc_ensure_no_slash_safe(path: &str) -> &str {
path
}
// Function returns a sanitized basename that does not contain
// win/linux path separators and also not any non-ascii chars
fn get_safe_basename(filename: &str) -> String {
// return the (potentially mangled) basename of the input filename
// this might be a path that comes in from another operating system
let mut index: usize = 0;
if let Some(unix_index) = filename.rfind('/') {
index = unix_index + 1;
}
if let Some(win_index) = filename.rfind('\\') {
index = max(index, win_index + 1);
}
if index >= filename.len() {
"nobasename".to_string()
} else {
// we don't allow any non-ascii to be super-safe
filename[index..].replace(|c: char| !c.is_ascii() || c == ':', "-")
}
}
pub fn dc_derive_safe_stem_ext(filename: &str) -> (String, String) {
let basename = get_safe_basename(&filename);
let (mut stem, mut ext) = if let Some(index) = basename.rfind('.') {
(
basename[0..index].to_string(),
basename[index..].to_string(),
)
} else {
(basename, "".to_string())
};
// limit length of stem and ext
stem.truncate(32);
ext.truncate(32);
(stem, ext)
/// Function modifies the given buffer and replaces all characters not valid in filenames by a "-".
fn validate_filename(filename: &str) -> String {
filename
.replace('/', "-")
.replace('\\', "-")
.replace(':', "-")
}
// the returned suffix is lower-case
@@ -501,26 +530,19 @@ pub(crate) fn dc_get_filebytes(context: &Context, path: impl AsRef<std::path::Pa
pub(crate) 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.exists() {
return false;
}
if !path_abs.is_file() {
warn!(
context,
"refusing to delete non-file \"{}\".",
"Will not delete directory \"{}\".",
path.as_ref().display()
);
return false;
}
let dpath = format!("{}", path.as_ref().to_string_lossy());
match fs::remove_file(path_abs) {
Ok(_) => {
context.call_cb(Event::DeletedBlobFile(dpath));
true
}
Ok(_) => true,
Err(_err) => {
warn!(context, "Cannot delete \"{}\".", dpath);
warn!(context, "Cannot delete \"{}\".", path.as_ref().display());
false
}
}
@@ -601,24 +623,49 @@ pub fn dc_read_file<P: AsRef<std::path::Path>>(
}
}
pub(crate) fn dc_get_next_backup_path(
pub(crate) fn dc_get_fine_path_filename(
context: &Context,
folder: impl AsRef<Path>,
backup_time: i64,
) -> Result<PathBuf, Error> {
let folder = PathBuf::from(folder.as_ref());
let stem = chrono::NaiveDateTime::from_timestamp(backup_time, 0)
.format("delta-chat-%Y-%m-%d")
.to_string();
desired_filename_suffix: impl AsRef<str>,
) -> PathBuf {
let now = time();
// 64 backup files per day should be enough for everyone
for i in 0..64 {
let mut path = folder.clone();
path.push(format!("{}-{}.bak", stem, i));
if !path.exists() {
return Ok(path);
let folder = PathBuf::from(folder.as_ref());
// XXX sanitize desired_filename eg using
// https://github.com/kardeiz/sanitize-filename/blob/master/src/lib.rs
let suffix = validate_filename(desired_filename_suffix.as_ref());
let file_name = PathBuf::from(suffix);
let extension = file_name.extension().map(|c| c.clone());
for i in 0..100_000 {
let ret = if i == 0 {
let mut folder = folder.clone();
folder.push(&file_name);
folder
} else {
let idx = if i < 100 { i } else { now + i };
let file_name = if let Some(stem) = file_name.file_stem() {
let mut stem = stem.to_os_string();
stem.push(format!("-{}", idx));
stem
} else {
OsString::from(idx.to_string())
};
let mut folder = folder.clone();
folder.push(file_name);
if let Some(ext) = extension {
folder.set_extension(&ext);
}
folder
};
if !dc_file_exist(context, &ret) {
// fine filename found
return ret;
}
}
bail!("could not create backup file, disk full?");
panic!("Something is really wrong, you need to clean up your disk");
}
pub(crate) fn dc_is_blobdir_path(context: &Context, path: impl AsRef<str>) -> bool {
@@ -637,10 +684,7 @@ fn dc_make_rel_path(context: &Context, path: &mut String) {
.map(|s| path.starts_with(s))
.unwrap_or_default()
{
*path = path.replace(
context.get_blobdir().to_str().unwrap_or_default(),
"$BLOBDIR",
);
*path = path.replace(context.get_blobdir().to_str().unwrap(), "$BLOBDIR");
}
}
@@ -649,10 +693,13 @@ pub(crate) fn dc_make_rel_and_copy(context: &Context, path: &mut String) -> bool
dc_make_rel_path(context, path);
return true;
}
if let Ok(blobdir_path) = context.copy_to_blobdir(&path) {
*path = blobdir_path;
let blobdir_path = dc_get_fine_path_filename(context, "$BLOBDIR", &path);
if dc_copy_file(context, &path, &blobdir_path) {
*path = blobdir_path.to_string_lossy().to_string();
dc_make_rel_path(context, path);
return true;
}
false
}
@@ -801,6 +848,22 @@ impl<T: AsRef<str>> StrExt for T {
}
}
pub fn to_string(s: *const libc::c_char) -> String {
if s.is_null() {
return "".into();
}
let cstr = unsafe { CStr::from_ptr(s) };
cstr.to_str().map(|s| s.to_string()).unwrap_or_else(|err| {
panic!(
"Non utf8 string: '{:?}' ({:?})",
cstr.to_string_lossy(),
err
);
})
}
pub fn to_string_lossy(s: *const libc::c_char) -> String {
if s.is_null() {
return "".into();
@@ -1423,33 +1486,19 @@ mod tests {
unsafe {
let res = strndup(b"helloworld\x00" as *const u8 as *const libc::c_char, 4);
assert_eq!(
to_string_lossy(res),
to_string_lossy(b"hell\x00" as *const u8 as *const libc::c_char)
to_string(res),
to_string(b"hell\x00" as *const u8 as *const libc::c_char)
);
assert_eq!(strlen(res), 4);
free(res as *mut _);
}
}
#[test]
fn test_file_get_safe_basename() {
assert_eq!(get_safe_basename("12312/hello"), "hello");
assert_eq!(get_safe_basename("12312\\hello"), "hello");
assert_eq!(get_safe_basename("//12312\\hello"), "hello");
assert_eq!(get_safe_basename("//123:12\\hello"), "hello");
assert_eq!(get_safe_basename("//123:12/\\\\hello"), "hello");
assert_eq!(get_safe_basename("//123:12//hello"), "hello");
assert_eq!(get_safe_basename("//123:12//"), "nobasename");
assert_eq!(get_safe_basename("//123:12/"), "nobasename");
assert!(get_safe_basename("123\x012.hello").ends_with(".hello"));
}
#[test]
fn test_file_handling() {
let t = dummy_context();
let context = &t.ctx;
assert!(!dc_delete_file(context, "$BLOBDIR/lkqwjelqkwlje"));
if dc_file_exist(context, "$BLOBDIR/foobar")
|| dc_file_exist(context, "$BLOBDIR/dada")
|| dc_file_exist(context, "$BLOBDIR/foobar.dadada")
@@ -1472,13 +1521,11 @@ mod tests {
.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 buf = dc_read_file(context, "$BLOBDIR/dada").unwrap();
@@ -1491,12 +1538,14 @@ mod tests {
assert!(dc_create_folder(context, "$BLOBDIR/foobar-folder"));
assert!(dc_file_exist(context, "$BLOBDIR/foobar-folder",));
assert!(!dc_delete_file(context, "$BLOBDIR/foobar-folder"));
let fn0 = dc_get_fine_path_filename(context, "$BLOBDIR", "foobar.dadada");
assert_eq!(fn0, PathBuf::from("$BLOBDIR/foobar.dadada"));
let fn0 = "$BLOBDIR/data.data";
assert!(dc_write_file(context, &fn0, b"content"));
let fn1 = dc_get_fine_path_filename(context, "$BLOBDIR", "foobar.dadada");
assert_eq!(fn1, PathBuf::from("$BLOBDIR/foobar-1.dadada"));
assert!(dc_delete_file(context, &fn0));
assert!(!dc_file_exist(context, &fn0));
}
#[test]
@@ -1518,12 +1567,12 @@ mod tests {
unsafe {
let input = "foo\r\nbar".strdup();
dc_remove_cr_chars(input);
assert_eq!("foo\nbar", to_string_lossy(input));
assert_eq!("foo\nbar", to_string(input));
free(input.cast());
let input = "\rfoo\r\rbar\r".strdup();
dc_remove_cr_chars(input);
assert_eq!("foobar", to_string_lossy(input));
assert_eq!("foobar", to_string(input));
free(input.cast());
}
}

File diff suppressed because it is too large Load Diff

View File

@@ -26,10 +26,6 @@ pub enum Error {
CStringError(crate::dc_tools::CStringError),
#[fail(display = "PGP: {:?}", _0)]
Pgp(pgp::errors::Error),
#[fail(display = "Base64Decode: {:?}", _0)]
Base64Decode(base64::DecodeError),
#[fail(display = "{:?}", _0)]
FromUtf8(std::string::FromUtf8Error),
}
pub type Result<T> = std::result::Result<T, Error>;
@@ -40,12 +36,6 @@ impl From<rusqlite::Error> for Error {
}
}
impl From<base64::DecodeError> for Error {
fn from(err: base64::DecodeError) -> Error {
Error::Base64Decode(err)
}
}
impl From<failure::Error> for Error {
fn from(err: failure::Error) -> Error {
Error::Failure(err)
@@ -70,12 +60,6 @@ impl From<std::str::Utf8Error> for Error {
}
}
impl From<std::string::FromUtf8Error> for Error {
fn from(err: std::string::FromUtf8Error) -> Error {
Error::FromUtf8(err)
}
}
impl From<image_meta::ImageError> for Error {
fn from(err: image_meta::ImageError) -> Error {
Error::Image(err)

View File

@@ -42,30 +42,6 @@ pub enum Event {
#[strum(props(id = "103"))]
SmtpMessageSent(String),
/// Emitted when an IMAP message has been marked as deleted
///
/// @return 0
#[strum(props(id = "104"))]
ImapMessageDeleted(String),
/// Emitted when an IMAP message has been moved
///
/// @return 0
#[strum(props(id = "105"))]
ImapMessageMoved(String),
/// Emitted when an new file in the $BLOBDIR was created
///
/// @return 0
#[strum(props(id = "150"))]
NewBlobFile(String),
/// Emitted when an new file in the $BLOBDIR was created
///
/// @return 0
#[strum(props(id = "151"))]
DeletedBlobFile(String),
/// The library-user should write a warning string to the log.
/// Passed to the callback given to dc_context_new().
///
@@ -191,7 +167,7 @@ pub enum Event {
#[strum(props(id = "2041"))]
ConfigureProgress(usize),
/// Inform about the import/export progress started by imex().
/// 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
@@ -199,8 +175,8 @@ pub enum Event {
#[strum(props(id = "2051"))]
ImexProgress(usize),
/// A file has been exported. A file has been written by imex().
/// This event may be sent multiple times by a single call to imex().
/// 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.

View File

@@ -8,14 +8,12 @@ use std::time::{Duration, SystemTime};
use crate::constants::*;
use crate::context::Context;
use crate::dc_receive_imf::dc_receive_imf;
use crate::error::Error;
use crate::events::Event;
use crate::job::{connect_to_inbox, job_add, Action};
use crate::login_param::{dc_build_tls, CertificateChecks, LoginParam};
use crate::job::{job_add, Action};
use crate::login_param::LoginParam;
use crate::message::{self, update_msg_move_state, update_server_uid};
use crate::oauth2::dc_get_oauth2_access_token;
use crate::param::Params;
use crate::wrapmime;
const DC_IMAP_SEEN: usize = 0x0001;
@@ -29,6 +27,7 @@ pub enum ImapResult {
const PREFETCH_FLAGS: &str = "(UID ENVELOPE)";
const BODY_FLAGS: &str = "(FLAGS BODY.PEEK[])";
const FETCH_FLAGS: &str = "(FLAGS)";
#[derive(Debug)]
pub struct Imap {
@@ -108,10 +107,12 @@ impl Client {
pub fn connect_secure<A: net::ToSocketAddrs, S: AsRef<str>>(
addr: A,
domain: S,
certificate_checks: CertificateChecks,
) -> imap::error::Result<Self> {
let stream = net::TcpStream::connect(addr)?;
let tls = dc_build_tls(certificate_checks).unwrap();
let tls = native_tls::TlsConnector::builder()
.danger_accept_invalid_hostnames(true)
.build()
.unwrap();
let s = stream.try_clone().expect("cloning the stream failed");
let tls_stream = native_tls::TlsConnector::connect(&tls, domain.as_ref(), s)?;
@@ -131,14 +132,13 @@ impl Client {
Ok(Client::Insecure(client, stream))
}
pub fn secure<S: AsRef<str>>(
self,
domain: S,
certificate_checks: CertificateChecks,
) -> imap::error::Result<Client> {
pub fn secure<S: AsRef<str>>(self, domain: S) -> imap::error::Result<Client> {
match self {
Client::Insecure(client, stream) => {
let tls = dc_build_tls(certificate_checks).unwrap();
let tls = native_tls::TlsConnector::builder()
.danger_accept_invalid_hostnames(true)
.build()
.unwrap();
let client_sec = client.secure(domain, &tls)?;
@@ -318,7 +318,6 @@ struct ImapConfig {
pub imap_port: u16,
pub imap_user: String,
pub imap_pw: String,
pub certificate_checks: CertificateChecks,
pub server_flags: usize,
pub selected_folder: Option<String>,
pub selected_mailbox: Option<imap::types::Mailbox>,
@@ -331,13 +330,12 @@ struct ImapConfig {
impl Default for ImapConfig {
fn default() -> Self {
ImapConfig {
let cfg = ImapConfig {
addr: "".into(),
imap_server: "".into(),
imap_port: 0,
imap_user: "".into(),
imap_pw: "".into(),
certificate_checks: Default::default(),
server_flags: 0,
selected_folder: None,
selected_mailbox: None,
@@ -346,7 +344,9 @@ impl Default for ImapConfig {
has_xlist: false,
imap_delimiter: '.',
watch_folder: None,
}
};
cfg
}
}
@@ -394,7 +394,7 @@ impl Imap {
Client::connect_insecure((imap_server, imap_port)).and_then(|client| {
if (server_flags & DC_LP_IMAP_SOCKET_STARTTLS) != 0 {
client.secure(imap_server, config.certificate_checks)
client.secure(imap_server)
} else {
Ok(client)
}
@@ -404,11 +404,7 @@ impl Imap {
let imap_server: &str = config.imap_server.as_ref();
let imap_port = config.imap_port;
Client::connect_secure(
(imap_server, imap_port),
imap_server,
config.certificate_checks,
)
Client::connect_secure((imap_server, imap_port), imap_server)
};
let login_res = match connection_res {
@@ -535,7 +531,6 @@ impl Imap {
config.imap_port = imap_port;
config.imap_user = imap_user.to_string();
config.imap_pw = imap_pw.to_string();
config.certificate_checks = lp.imap_certificate_checks;
config.server_flags = server_flags;
}
@@ -684,7 +679,7 @@ impl Imap {
}
}
} else {
unreachable!();
return 0;
}
}
@@ -693,20 +688,12 @@ 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_raw_config(context, &key) {
if let Some(entry) = context.sql.get_config(context, &key) {
// the entry has the format `imap.mailbox.<folder>=<uidvalidity>:<lastseenuid>`
let mut parts = entry.split(':');
(
parts
.next()
.unwrap_or_default()
.parse()
.unwrap_or_else(|_| 0),
parts
.next()
.unwrap_or_default()
.parse()
.unwrap_or_else(|_| 0),
parts.next().unwrap().parse().unwrap_or_else(|_| 0),
parts.next().unwrap().parse().unwrap_or_else(|_| 0),
)
} else {
(0, 0)
@@ -750,7 +737,7 @@ impl Imap {
return 0;
}
if mailbox.uid_validity.unwrap_or_default() != uid_validity {
if mailbox.uid_validity.unwrap() != uid_validity {
// first time this folder is selected or UIDVALIDITY has changed, init lastseenuid and save it to config
if mailbox.exists == 0 {
@@ -760,12 +747,7 @@ impl Imap {
// id we do not do this here, we'll miss the first message
// as we will get in here again and fetch from lastseenuid+1 then
self.set_config_last_seen_uid(
context,
&folder,
mailbox.uid_validity.unwrap_or_default(),
0,
);
self.set_config_last_seen_uid(context, &folder, mailbox.uid_validity.unwrap(), 0);
return 0;
}
@@ -796,7 +778,7 @@ impl Imap {
last_seen_uid -= 1;
}
uid_validity = mailbox.uid_validity.unwrap_or_default();
uid_validity = mailbox.uid_validity.unwrap();
self.set_config_last_seen_uid(context, &folder, uid_validity, last_seen_uid);
info!(
context,
@@ -832,7 +814,11 @@ impl Imap {
if cur_uid > last_seen_uid {
read_cnt += 1;
let message_id = prefetch_get_message_id(msg).unwrap_or_default();
let message_id = msg
.envelope()
.expect("missing envelope")
.message_id
.expect("missing message id");
if !precheck_imf(context, &message_id, folder.as_ref(), cur_uid) {
// check passed, go fetch the rest
@@ -897,7 +883,7 @@ impl Imap {
let key = format!("imap.mailbox.{}", folder.as_ref());
let val = format!("{}:{}", uidvalidity, lastseenuid);
context.sql.set_raw_config(context, &key, Some(&val)).ok();
context.sql.set_config(context, &key, Some(&val)).ok();
}
fn fetch_single_msg<S: AsRef<str>>(
@@ -945,22 +931,36 @@ impl Imap {
} else {
let msg = &msgs[0];
// XXX put flags into a set and pass them to dc_receive_imf
let is_deleted = msg.flags().iter().any(|flag| match flag {
imap::types::Flag::Deleted => true,
_ => false,
});
let is_seen = msg.flags().iter().any(|flag| match flag {
imap::types::Flag::Seen => true,
_ => false,
});
let is_deleted = msg
.flags()
.iter()
.find(|flag| match flag {
imap::types::Flag::Deleted => true,
_ => false,
})
.is_some();
let is_seen = msg
.flags()
.iter()
.find(|flag| match flag {
imap::types::Flag::Seen => true,
_ => false,
})
.is_some();
let flags = if is_seen { DC_IMAP_SEEN } else { 0 };
if !is_deleted && msg.body().is_some() {
let body = msg.body().unwrap_or_default();
let body = msg.body().unwrap();
unsafe {
dc_receive_imf(context, &body, folder.as_ref(), server_uid, flags as u32);
dc_receive_imf(
context,
body.as_ptr() as *const libc::c_char,
body.len(),
folder.as_ref(),
server_uid,
flags as u32,
);
}
}
}
@@ -1069,14 +1069,13 @@ impl Imap {
let mut do_fake_idle = true;
while do_fake_idle {
// wait a moment: every 5 seconds in the first 3 minutes after a new message, after that every 60 seconds.
let seconds_to_wait = if fake_idle_start_time.elapsed().unwrap_or_default()
< Duration::new(3 * 60, 0)
&& !wait_long
{
Duration::new(5, 0)
} else {
Duration::new(60, 0)
};
let seconds_to_wait =
if fake_idle_start_time.elapsed().unwrap() < Duration::new(3 * 60, 0) && !wait_long
{
Duration::new(5, 0)
} else {
Duration::new(60, 0)
};
let &(ref lock, ref cvar) = &*self.watch.clone();
let mut watch = lock.lock().unwrap();
@@ -1125,94 +1124,117 @@ impl Imap {
cvar.notify_one();
}
pub fn mv(
pub fn mv<S1: AsRef<str>, S2: AsRef<str>>(
&self,
context: &Context,
folder: &str,
folder: S1,
uid: u32,
dest_folder: &str,
dest_folder: S2,
dest_uid: &mut u32,
) -> ImapResult {
if folder == dest_folder {
let mut res = ImapResult::RetryLater;
let set = format!("{}", uid);
if uid == 0 {
res = ImapResult::Failed;
} else if folder.as_ref() == dest_folder.as_ref() {
info!(
context,
"Skip moving message; message {}/{} is already in {}...", folder, uid, dest_folder,
"Skip moving message; message {}/{} is already in {}...",
folder.as_ref(),
uid,
dest_folder.as_ref()
);
return ImapResult::AlreadyDone;
}
if let Some(imapresult) = self.prepare_imap_operation_on_msg(context, folder, uid) {
return imapresult;
}
// we are connected, and the folder is selected
// XXX Rust-Imap provides no target uid on mv, so just set it to 0
*dest_uid = 0;
let set = format!("{}", uid);
let display_folder_id = format!("{}/{}", folder, uid);
if let Some(ref mut session) = &mut *self.session.lock().unwrap() {
match session.uid_mv(&set, &dest_folder) {
Ok(_) => {
emit_event!(
context,
Event::ImapMessageMoved(format!(
"IMAP Message {} moved to {}",
display_folder_id, dest_folder
))
);
return ImapResult::Success;
}
Err(err) => {
warn!(
context,
"Cannot move message, fallback to COPY/DELETE {}/{} to {}: {}",
folder,
uid,
dest_folder,
err
);
}
}
res = ImapResult::AlreadyDone;
} else {
unreachable!();
};
info!(
context,
"Moving message {}/{} to {}...",
folder.as_ref(),
uid,
dest_folder.as_ref()
);
if let Some(ref mut session) = &mut *self.session.lock().unwrap() {
match session.uid_copy(&set, &dest_folder) {
Ok(_) => {
if !self.add_flag_finalized(context, uid, "\\Deleted") {
warn!(context, "Cannot mark {} as \"Deleted\" after copy.", uid);
ImapResult::Failed
if self.select_folder(context, Some(folder.as_ref())) == 0 {
warn!(
context,
"Cannot select folder {} for moving message.",
folder.as_ref()
);
} else {
let moved = if let Some(ref mut session) = &mut *self.session.lock().unwrap() {
match session.uid_mv(&set, &dest_folder) {
Ok(_) => {
res = ImapResult::Success;
true
}
Err(err) => {
info!(
context,
"Cannot move message, fallback to COPY/DELETE {}/{} to {}: {}",
folder.as_ref(),
uid,
dest_folder.as_ref(),
err
);
false
}
}
} else {
unreachable!();
};
if !moved {
let copied = if let Some(ref mut session) = &mut *self.session.lock().unwrap() {
match session.uid_copy(&set, &dest_folder) {
Ok(_) => true,
Err(err) => {
eprintln!("error copy: {:?}", err);
info!(context, "Cannot copy message.",);
false
}
}
} else {
unreachable!();
};
if copied {
if self.add_flag(context, uid, "\\Deleted") == 0 {
warn!(context, "Cannot mark message as \"Deleted\".",);
}
self.config.write().unwrap().selected_folder_needs_expunge = true;
ImapResult::Success
res = ImapResult::Success;
}
}
Err(err) => {
warn!(context, "Could not copy message: {}", err);
ImapResult::Failed
}
}
}
if res == ImapResult::Success {
// TODO: is this correct?
*dest_uid = uid;
}
if res == ImapResult::RetryLater {
if self.should_reconnect() {
ImapResult::RetryLater
} else {
ImapResult::Failed
}
} else {
unreachable!();
res
}
}
fn add_flag_finalized(&self, context: &Context, server_uid: u32, flag: &str) -> bool {
// return true if we successfully set the flag or we otherwise
// think add_flag should not be retried: Disconnection during setting
// the flag, or other imap-errors, returns true as well.
//
// returning false means that the operation can be retried.
fn add_flag<S: AsRef<str>>(&self, context: &Context, server_uid: u32, flag: S) -> usize {
if server_uid == 0 {
return true; // might be moved but we don't want to have a stuck job
}
if self.should_reconnect() {
return false;
return 0;
}
if let Some(ref mut session) = &mut *self.session.lock().unwrap() {
let set = format!("{}", server_uid);
let query = format!("+FLAGS ({})", flag);
let query = format!("+FLAGS ({})", flag.as_ref());
match session.uid_store(&set, &query) {
Ok(_) => {}
Err(err) => {
@@ -1222,126 +1244,243 @@ impl Imap {
);
}
}
true // we tried once, that's probably enough for setting flag
}
// All non-connection states are treated as success - the mail may
// already be deleted or moved away on the server.
if self.should_reconnect() {
0
} else {
unreachable!();
1
}
}
pub fn prepare_imap_operation_on_msg(
&self,
context: &Context,
folder: &str,
uid: u32,
) -> Option<ImapResult> {
pub fn set_seen<S: AsRef<str>>(&self, context: &Context, folder: S, uid: u32) -> ImapResult {
let mut res = ImapResult::RetryLater;
if uid == 0 {
return Some(ImapResult::Failed);
} else if !self.is_connected() {
connect_to_inbox(context, &self);
if !self.is_connected() {
return Some(ImapResult::RetryLater);
res = ImapResult::Failed
} else if self.is_connected() {
info!(
context,
"Marking message {}/{} as seen...",
folder.as_ref(),
uid,
);
if self.select_folder(context, Some(folder.as_ref())) == 0 {
warn!(
context,
"Cannot select folder {} for setting SEEN flag.",
folder.as_ref(),
);
} else if self.add_flag(context, uid, "\\Seen") == 0 {
warn!(context, "Cannot mark message as seen.",);
} else {
res = ImapResult::Success
}
}
if self.select_folder(context, Some(&folder)) == 0 {
warn!(
context,
"Cannot select folder {} for preparing IMAP operation", folder
);
Some(ImapResult::RetryLater)
if res == ImapResult::RetryLater {
if self.should_reconnect() {
ImapResult::RetryLater
} else {
ImapResult::Failed
}
} else {
None
res
}
}
pub fn set_seen(&self, context: &Context, folder: &str, uid: u32) -> ImapResult {
if let Some(imapresult) = self.prepare_imap_operation_on_msg(context, folder, uid) {
return imapresult;
}
// we are connected, and the folder is selected
info!(context, "Marking message {}/{} as seen...", folder, uid,);
pub fn set_mdnsent<S: AsRef<str>>(&self, context: &Context, folder: S, uid: u32) -> ImapResult {
// returns 0=job should be retried later, 1=job done, 2=job done and flag just set
let mut res = ImapResult::RetryLater;
let set = format!("{}", uid);
if self.add_flag_finalized(context, uid, "\\Seen") {
ImapResult::Success
} else {
warn!(
if uid == 0 {
res = ImapResult::Failed;
} else if self.is_connected() {
info!(
context,
"Cannot mark message {} in folder {} as seen, ignoring.", uid, folder
"Marking message {}/{} as $MDNSent...",
folder.as_ref(),
uid,
);
ImapResult::Failed
if self.select_folder(context, Some(folder.as_ref())) == 0 {
warn!(
context,
"Cannot select folder {} for setting $MDNSent flag.",
folder.as_ref()
);
} else {
// Check if the folder can handle the `$MDNSent` flag (see RFC 3503). If so, and not
// set: set the flags and return this information.
// If the folder cannot handle the `$MDNSent` flag, we risk duplicated MDNs; it's up
// to the receiving MUA to handle this then (eg. Delta Chat has no problem with this).
let can_create_flag = self
.config
.read()
.unwrap()
.selected_mailbox
.as_ref()
.map(|mbox| {
// empty means, everything can be stored
mbox.permanent_flags.is_empty()
|| mbox
.permanent_flags
.iter()
.find(|flag| match flag {
imap::types::Flag::Custom(s) => s == "$MDNSent",
_ => false,
})
.is_some()
})
.expect("just selected folder");
if can_create_flag {
let fetched_msgs =
if let Some(ref mut session) = &mut *self.session.lock().unwrap() {
match session.uid_fetch(set, FETCH_FLAGS) {
Ok(res) => Some(res),
Err(err) => {
eprintln!("fetch error: {:?}", err);
None
}
}
} else {
unreachable!();
};
if let Some(msgs) = fetched_msgs {
let flag_set = msgs
.first()
.map(|msg| {
msg.flags()
.iter()
.find(|flag| match flag {
imap::types::Flag::Custom(s) => s == "$MDNSent",
_ => false,
})
.is_some()
})
.unwrap_or_else(|| false);
res = if flag_set {
ImapResult::AlreadyDone
} else if self.add_flag(context, uid, "$MDNSent") != 0 {
ImapResult::Success
} else {
res
};
if res == ImapResult::Success {
info!(context, "$MDNSent just set and MDN will be sent.");
} else {
info!(context, "$MDNSent already set and MDN already sent.");
}
}
} else {
res = ImapResult::Success;
info!(
context,
"Cannot store $MDNSent flags, risk sending duplicate MDN.",
);
}
}
}
if res == ImapResult::RetryLater {
if self.should_reconnect() {
ImapResult::RetryLater
} else {
ImapResult::Failed
}
} else {
res
}
}
// only returns 0 on connection problems; we should try later again in this case *
pub fn delete_msg(
pub fn delete_msg<S1: AsRef<str>, S2: AsRef<str>>(
&self,
context: &Context,
message_id: &str,
folder: &str,
uid: &mut u32,
) -> ImapResult {
if let Some(imapresult) = self.prepare_imap_operation_on_msg(context, folder, *uid) {
return imapresult;
}
// we are connected, and the folder is selected
message_id: S1,
folder: S2,
server_uid: &mut u32,
) -> usize {
let mut success = false;
if *server_uid == 0 {
success = true
} else {
info!(
context,
"Marking message \"{}\", {}/{} for deletion...",
message_id.as_ref(),
folder.as_ref(),
server_uid,
);
let set = format!("{}", uid);
let display_imap_id = format!("{}/{}", folder, uid);
if self.select_folder(context, Some(&folder)) == 0 {
warn!(
context,
"Cannot select folder {} for deleting message.",
folder.as_ref()
);
} else {
let set = format!("{}", server_uid);
if let Some(ref mut session) = &mut *self.session.lock().unwrap() {
match session.uid_fetch(set, PREFETCH_FLAGS) {
Ok(msgs) => {
if msgs.is_empty()
|| msgs
.first()
.unwrap()
.envelope()
.expect("missing envelope")
.message_id
.expect("missing message id")
!= message_id.as_ref()
{
warn!(
context,
"Cannot delete on IMAP, {}/{} does not match {}.",
folder.as_ref(),
server_uid,
message_id.as_ref(),
);
*server_uid = 0;
}
}
Err(err) => {
eprintln!("fetch error: {:?}", err);
// double-check that we are deleting the correct message-id
// this comes at the expense of another imap query
if let Some(ref mut session) = &mut *self.session.lock().unwrap() {
match session.uid_fetch(set, PREFETCH_FLAGS) {
Ok(msgs) => {
if msgs.is_empty() {
warn!(
context,
"Cannot delete on IMAP, {}: imap entry gone '{}'",
display_imap_id,
message_id,
);
return ImapResult::Failed;
warn!(
context,
"Cannot delete on IMAP, {}/{} not found.",
folder.as_ref(),
server_uid,
);
*server_uid = 0;
}
}
let remote_message_id =
prefetch_get_message_id(msgs.first().unwrap()).unwrap_or_default();
if remote_message_id != message_id {
warn!(
context,
"Cannot delete on IMAP, {}: remote message-id '{}' != '{}'",
display_imap_id,
remote_message_id,
message_id,
);
}
*uid = 0;
}
Err(err) => {
warn!(
context,
"Cannot delete {} on IMAP: {}", display_imap_id, err
);
*uid = 0;
// mark the message for deletion
if self.add_flag(context, *server_uid, "\\Deleted") == 0 {
warn!(context, "Cannot mark message as \"Deleted\".");
} else {
self.config.write().unwrap().selected_folder_needs_expunge = true;
success = true
}
}
}
// mark the message for deletion
if !self.add_flag_finalized(context, *uid, "\\Deleted") {
warn!(
context,
"Cannot mark message {} as \"Deleted\".", display_imap_id
);
ImapResult::Failed
if success {
1
} else {
emit_event!(
context,
Event::ImapMessageDeleted(format!(
"IMAP Message {} marked as deleted [{}]",
display_imap_id, message_id
))
);
self.config.write().unwrap().selected_folder_needs_expunge = true;
ImapResult::Success
self.is_connected() as usize
}
}
@@ -1410,18 +1549,18 @@ impl Imap {
context
.sql
.set_raw_config_int(context, "folders_configured", 3)
.set_config_int(context, "folders_configured", 3)
.ok();
if let Some(ref mvbox_folder) = mvbox_folder {
context
.sql
.set_raw_config(context, "configured_mvbox_folder", Some(mvbox_folder))
.set_config(context, "configured_mvbox_folder", Some(mvbox_folder))
.ok();
}
if let Some(ref sentbox_folder) = sentbox_folder {
context
.sql
.set_raw_config(
.set_config(
context,
"configured_sentbox_folder",
Some(sentbox_folder.name()),
@@ -1501,11 +1640,27 @@ fn get_folder_meaning(folder_name: &imap::types::Name) -> FolderMeaning {
}
fn precheck_imf(context: &Context, rfc724_mid: &str, server_folder: &str, server_uid: u32) -> bool {
let mut rfc724_mid_exists = false;
let mut mark_seen = false;
if let Ok((old_server_folder, old_server_uid, msg_id)) =
message::rfc724_mid_exists(context, &rfc724_mid)
{
rfc724_mid_exists = true;
if old_server_folder.is_empty() && old_server_uid == 0 {
info!(context, "[move] detected bbc-self {}", rfc724_mid,);
mark_seen = true;
} else if old_server_folder != server_folder {
info!(context, "[move] detected moved message {}", rfc724_mid,);
update_msg_move_state(context, &rfc724_mid, MoveState::Stay);
}
if old_server_folder != server_folder || old_server_uid != server_uid {
update_server_uid(context, &rfc724_mid, server_folder, server_uid);
}
context.do_heuristics_moves(server_folder, msg_id);
if mark_seen {
job_add(
context,
Action::MarkseenMsgOnImap,
@@ -1513,21 +1668,8 @@ fn precheck_imf(context: &Context, rfc724_mid: &str, server_folder: &str, server
Params::new(),
0,
);
} else if old_server_folder != server_folder {
info!(context, "[move] detected moved message {}", rfc724_mid,);
update_msg_move_state(context, &rfc724_mid, MoveState::Stay);
}
if old_server_folder != server_folder || old_server_uid != server_uid {
update_server_uid(context, &rfc724_mid, server_folder, server_uid);
}
true
} else {
false
}
}
fn prefetch_get_message_id(prefetch_msg: &imap::types::Fetch) -> Result<String, Error> {
let message_id = prefetch_msg.envelope().unwrap().message_id.unwrap();
wrapmime::parse_message_id(&message_id)
rfc724_mid_exists
}

File diff suppressed because it is too large Load Diff

View File

@@ -4,19 +4,17 @@ use deltachat_derive::{FromSql, ToSql};
use rand::{thread_rng, Rng};
use crate::chat;
use crate::config::Config;
use crate::configure::*;
use crate::constants::*;
use crate::context::Context;
use crate::dc_imex::*;
use crate::dc_tools::*;
use crate::error::Error;
use crate::events::Event;
use crate::imap::*;
use crate::imex::job_do_DC_JOB_IMEX_IMAP;
use crate::location;
use crate::login_param::LoginParam;
use crate::message::{self, Message, MessageState};
use crate::mimefactory::{vec_contains_lowercase, Loaded, MimeFactory};
use crate::mimefactory::*;
use crate::param::*;
use crate::sql;
@@ -142,11 +140,11 @@ impl Job {
if let Ok(body) = dc_read_file(context, filename) {
if let Some(recipients) = self.param.get(Param::Recipients) {
let recipients_list = recipients
.split('\x1e')
.split("\x1e")
.filter_map(|addr| match lettre::EmailAddress::new(addr.to_string()) {
Ok(addr) => Some(addr),
Err(err) => {
warn!(context, "invalid recipient: {} {:?}", addr, err);
eprintln!("WARNING: invalid recipient: {} {:?}", addr, err);
None
}
})
@@ -158,45 +156,39 @@ impl Job {
if 0 != self.foreign_id && !message::exists(context, self.foreign_id) {
warn!(
context,
"Not sending Message {} as it was deleted", self.foreign_id
"Message {} for job {} does not exist", self.foreign_id, self.job_id,
);
return;
};
// hold the smtp lock during sending of a job and
// its ok/error response processing. Note that if a message
// was sent we need to mark it in the database ASAP as we
// was sent we need to mark it in the database as we
// otherwise might send it twice.
let mut sock = context.smtp.lock().unwrap();
match sock.send(context, recipients_list, body) {
Err(err) => {
sock.disconnect();
warn!(context, "smtp failed: {}", err);
self.try_again_later(-1i32, Some(err.to_string()));
}
Ok(()) => {
// smtp success, update db ASAP, then delete smtp file
if 0 != self.foreign_id {
message::update_msg_state(
if 0 == sock.send(context, recipients_list, body) {
sock.disconnect();
self.try_again_later(-1i32, sock.error.clone());
} else {
dc_delete_file(context, filename);
if 0 != self.foreign_id {
message::update_msg_state(
context,
self.foreign_id,
MessageState::OutDelivered,
);
let chat_id: i32 = context
.sql
.query_get_value(
context,
self.foreign_id,
MessageState::OutDelivered,
);
let chat_id: i32 = context
.sql
.query_get_value(
context,
"SELECT chat_id FROM msgs WHERE id=?",
params![self.foreign_id as i32],
)
.unwrap_or_default();
context.call_cb(Event::MsgDelivered {
chat_id: chat_id as u32,
msg_id: self.foreign_id,
});
}
// now also delete the generated file
dc_delete_file(context, filename);
"SELECT chat_id FROM msgs WHERE id=?",
params![self.foreign_id as i32],
)
.unwrap_or_default();
context.call_cb(Event::MsgDelivered {
chat_id: chat_id as u32,
msg_id: self.foreign_id,
});
}
}
} else {
@@ -214,44 +206,57 @@ impl Job {
#[allow(non_snake_case)]
fn do_DC_JOB_MOVE_MSG(&mut self, context: &Context) {
let ok_to_continue;
let mut dest_uid = 0;
let inbox = context.inbox.read().unwrap();
if let Ok(msg) = Message::load_from_db(context, self.foreign_id) {
if context
.sql
.get_raw_config_int(context, "folders_configured")
.unwrap_or_default()
< 3
{
inbox.configure_folders(context, 0x1i32);
if !inbox.is_connected() {
connect_to_inbox(context, &inbox);
if !inbox.is_connected() {
self.try_again_later(3, None);
ok_to_continue = false;
} else {
ok_to_continue = true;
}
let dest_folder = context
.sql
.get_raw_config(context, "configured_mvbox_folder");
} else {
ok_to_continue = true;
}
if ok_to_continue {
if let Ok(msg) = Message::load_from_db(context, self.foreign_id) {
if context
.sql
.get_config_int(context, "folders_configured")
.unwrap_or_default()
< 3
{
inbox.configure_folders(context, 0x1i32);
}
let dest_folder = context.sql.get_config(context, "configured_mvbox_folder");
if let Some(dest_folder) = dest_folder {
let server_folder = msg.server_folder.as_ref().unwrap();
let mut dest_uid = 0;
if let Some(dest_folder) = dest_folder {
let server_folder = msg.server_folder.as_ref().unwrap();
match inbox.mv(
context,
server_folder,
msg.server_uid,
&dest_folder,
&mut dest_uid,
) {
ImapResult::RetryLater => {
self.try_again_later(3i32, None);
match inbox.mv(
context,
server_folder,
msg.server_uid,
&dest_folder,
&mut dest_uid,
) {
ImapResult::RetryLater => {
self.try_again_later(3i32, None);
}
ImapResult::Success => {
message::update_server_uid(
context,
&msg.rfc724_mid,
&dest_folder,
dest_uid,
);
}
ImapResult::Failed | ImapResult::AlreadyDone => {}
}
ImapResult::Success => {
message::update_server_uid(
context,
&msg.rfc724_mid,
&dest_folder,
dest_uid,
);
}
ImapResult::Failed | ImapResult::AlreadyDone => {}
}
}
}
@@ -259,53 +264,99 @@ impl Job {
#[allow(non_snake_case)]
fn do_DC_JOB_DELETE_MSG_ON_IMAP(&mut self, context: &Context) {
let mut delete_from_server = 1;
let inbox = context.inbox.read().unwrap();
if let Ok(mut msg) = Message::load_from_db(context, self.foreign_id) {
if !msg.rfc724_mid.is_empty() {
let ok_to_continue1;
/* eg. device messages have no Message-ID */
if message::rfc724_mid_cnt(context, &msg.rfc724_mid) > 1 {
if message::rfc724_mid_cnt(context, &msg.rfc724_mid) != 1 {
info!(
context,
"The message is deleted from the server when all parts are deleted.",
);
} else {
/* if this is the last existing part of the message,
we delete the message from the server */
let mid = msg.rfc724_mid;
let server_folder = msg.server_folder.as_ref().unwrap();
let res = inbox.delete_msg(context, &mid, server_folder, &mut msg.server_uid);
if res == ImapResult::RetryLater {
self.try_again_later(-1i32, None);
return;
}
delete_from_server = 0i32
}
/* if this is the last existing part of the message, we delete the message from the server */
if 0 != delete_from_server {
let ok_to_continue;
if !inbox.is_connected() {
connect_to_inbox(context, &inbox);
if !inbox.is_connected() {
self.try_again_later(3i32, None);
ok_to_continue = false;
} else {
ok_to_continue = true;
}
} else {
ok_to_continue = true;
}
if ok_to_continue {
let mid = msg.rfc724_mid;
let server_folder = msg.server_folder.as_ref().unwrap();
if 0 == inbox.delete_msg(context, &mid, server_folder, &mut msg.server_uid)
{
self.try_again_later(-1i32, None);
ok_to_continue1 = false;
} else {
ok_to_continue1 = true;
}
} else {
ok_to_continue1 = false;
}
} else {
ok_to_continue1 = true;
}
if ok_to_continue1 {
Message::delete_from_db(context, msg.id);
}
Message::delete_from_db(context, msg.id);
}
}
}
#[allow(non_snake_case)]
fn do_DC_JOB_MARKSEEN_MSG_ON_IMAP(&mut self, context: &Context) {
let ok_to_continue;
let inbox = context.inbox.read().unwrap();
if let Ok(msg) = Message::load_from_db(context, self.foreign_id) {
let folder = msg.server_folder.as_ref().unwrap();
match inbox.set_seen(context, folder, msg.server_uid) {
ImapResult::RetryLater => {
self.try_again_later(3i32, None);
}
ImapResult::AlreadyDone => {}
ImapResult::Success | ImapResult::Failed => {
// XXX the message might just have been moved
// we want to send out an MDN anyway
// The job will not be retried so locally
// there is no risk of double-sending MDNs.
if 0 != msg.param.get_int(Param::WantsMdn).unwrap_or_default()
&& context.get_config_bool(Config::MdnsEnabled)
{
if let Err(err) = send_mdn(context, msg.id) {
warn!(context, "could not send out mdn for {}: {}", msg.id, err);
if !inbox.is_connected() {
connect_to_inbox(context, &inbox);
if !inbox.is_connected() {
self.try_again_later(3i32, None);
ok_to_continue = false;
} else {
ok_to_continue = true;
}
} else {
ok_to_continue = true;
}
if ok_to_continue {
if let Ok(msg) = Message::load_from_db(context, self.foreign_id) {
let server_folder = msg.server_folder.as_ref().unwrap();
match inbox.set_seen(context, server_folder, msg.server_uid) {
ImapResult::Failed => {}
ImapResult::RetryLater => {
self.try_again_later(3i32, None);
}
_ => {
if 0 != msg.param.get_int(Param::WantsMdn).unwrap_or_default()
&& 0 != context
.sql
.get_config_int(context, "mdns_enabled")
.unwrap_or_else(|| 1)
{
let folder = msg.server_folder.as_ref().unwrap();
match inbox.set_mdnsent(context, folder, msg.server_uid) {
ImapResult::RetryLater => {
self.try_again_later(3i32, None);
}
ImapResult::Success => {
send_mdn(context, msg.id);
}
ImapResult::Failed | ImapResult::AlreadyDone => {}
}
}
}
}
@@ -315,35 +366,47 @@ impl Job {
#[allow(non_snake_case)]
fn do_DC_JOB_MARKSEEN_MDN_ON_IMAP(&mut self, context: &Context) {
let ok_to_continue;
let folder = self
.param
.get(Param::ServerFolder)
.unwrap_or_default()
.to_string();
let uid = self.param.get_int(Param::ServerUid).unwrap_or_default() as u32;
let mut dest_uid = 0;
let inbox = context.inbox.read().unwrap();
if inbox.set_seen(context, &folder, uid) == ImapResult::RetryLater {
self.try_again_later(3i32, None);
return;
}
if 0 != self.param.get_int(Param::AlsoMove).unwrap_or_default() {
if context
.sql
.get_raw_config_int(context, "folders_configured")
.unwrap_or_default()
< 3
{
inbox.configure_folders(context, 0x1i32);
if !inbox.is_connected() {
connect_to_inbox(context, &inbox);
if !inbox.is_connected() {
self.try_again_later(3, None);
ok_to_continue = false;
} else {
ok_to_continue = true;
}
let dest_folder = context
.sql
.get_raw_config(context, "configured_mvbox_folder");
if let Some(dest_folder) = dest_folder {
let mut dest_uid = 0;
if ImapResult::RetryLater
== inbox.mv(context, &folder, uid, &dest_folder, &mut dest_uid)
} else {
ok_to_continue = true;
}
if ok_to_continue {
if inbox.set_seen(context, &folder, uid) == ImapResult::Failed {
self.try_again_later(3i32, None);
}
if 0 != self.param.get_int(Param::AlsoMove).unwrap_or_default() {
if context
.sql
.get_config_int(context, "folders_configured")
.unwrap_or_default()
< 3
{
self.try_again_later(3, None);
inbox.configure_folders(context, 0x1i32);
}
let dest_folder = context.sql.get_config(context, "configured_mvbox_folder");
if let Some(dest_folder) = dest_folder {
if ImapResult::RetryLater
== inbox.mv(context, folder, uid, dest_folder, &mut dest_uid)
{
self.try_again_later(3, None);
}
}
}
}
@@ -368,7 +431,12 @@ pub fn perform_imap_fetch(context: &Context) {
if 0 == connect_to_inbox(context, &inbox) {
return;
}
if !context.get_config_bool(Config::InboxWatch) {
if context
.sql
.get_config_int(context, "inbox_watch")
.unwrap_or_else(|| 1)
== 0
{
info!(context, "INBOX-watch disabled.",);
return;
}
@@ -403,23 +471,29 @@ pub fn perform_imap_idle(context: &Context) {
}
pub fn perform_mvbox_fetch(context: &Context) {
let use_network = context.get_config_bool(Config::MvboxWatch);
let use_network = context
.sql
.get_config_int(context, "mvbox_watch")
.unwrap_or_else(|| 1);
context
.mvbox_thread
.write()
.unwrap()
.fetch(context, use_network);
.fetch(context, use_network == 1);
}
pub fn perform_mvbox_idle(context: &Context) {
let use_network = context.get_config_bool(Config::MvboxWatch);
let use_network = context
.sql
.get_config_int(context, "mvbox_watch")
.unwrap_or_else(|| 1);
context
.mvbox_thread
.read()
.unwrap()
.idle(context, use_network);
.idle(context, use_network == 1);
}
pub fn interrupt_mvbox_idle(context: &Context) {
@@ -427,23 +501,29 @@ pub fn interrupt_mvbox_idle(context: &Context) {
}
pub fn perform_sentbox_fetch(context: &Context) {
let use_network = context.get_config_bool(Config::SentboxWatch);
let use_network = context
.sql
.get_config_int(context, "sentbox_watch")
.unwrap_or_else(|| 1);
context
.sentbox_thread
.write()
.unwrap()
.fetch(context, use_network);
.fetch(context, use_network == 1);
}
pub fn perform_sentbox_idle(context: &Context) {
let use_network = context.get_config_bool(Config::SentboxWatch);
let use_network = context
.sql
.get_config_int(context, "sentbox_watch")
.unwrap_or_else(|| 1);
context
.sentbox_thread
.read()
.unwrap()
.idle(context, use_network);
.idle(context, use_network == 1);
}
pub fn interrupt_sentbox_idle(context: &Context) {
@@ -560,108 +640,109 @@ pub fn job_action_exists(context: &Context, action: Action) -> bool {
/* special case for DC_JOB_SEND_MSG_TO_SMTP */
#[allow(non_snake_case)]
pub fn job_send_msg(context: &Context, msg_id: u32) -> Result<(), Error> {
let mut mimefactory = MimeFactory::load_msg(context, msg_id)?;
pub fn job_send_msg(context: &Context, msg_id: u32) -> libc::c_int {
let mut success = 0;
if chat::msgtype_has_file(mimefactory.msg.type_0) {
let file_param = mimefactory
.msg
.param
.get(Param::File)
.map(|s| s.to_string());
if let Some(pathNfilename) = file_param {
if (mimefactory.msg.type_0 == Viewtype::Image
|| mimefactory.msg.type_0 == Viewtype::Gif)
&& !mimefactory.msg.param.exists(Param::Width)
{
mimefactory.msg.param.set_int(Param::Width, 0);
mimefactory.msg.param.set_int(Param::Height, 0);
if let Ok(buf) = dc_read_file(context, pathNfilename) {
if let Ok((width, height)) = dc_get_filemeta(&buf) {
mimefactory.msg.param.set_int(Param::Width, width as i32);
mimefactory.msg.param.set_int(Param::Height, height as i32);
}
}
mimefactory.msg.save_param_to_disk(context);
}
}
}
/* create message */
if let Err(msg) = unsafe { mimefactory.render() } {
let e = msg.to_string();
message::set_msg_failed(context, msg_id, Some(e));
return Err(msg);
}
if 0 != mimefactory
.msg
.param
.get_int(Param::GuranteeE2ee)
.unwrap_or_default()
&& !mimefactory.out_encrypted
{
/* unrecoverable */
message::set_msg_failed(
context,
msg_id,
Some("End-to-end-encryption unavailable unexpectedly."),
);
bail!(
"e2e encryption unavailable {} - {:?}",
msg_id,
mimefactory.msg.param.get_int(Param::GuranteeE2ee),
);
}
if context.get_config_bool(Config::BccSelf)
&& !vec_contains_lowercase(&mimefactory.recipients_addr, &mimefactory.from_addr)
{
mimefactory.recipients_names.push("".to_string());
mimefactory
.recipients_addr
.push(mimefactory.from_addr.to_string());
}
if mimefactory.recipients_addr.is_empty() {
/* load message data */
let mimefactory = MimeFactory::load_msg(context, msg_id);
if mimefactory.is_err() {
warn!(
context,
"message {} has no recipient, skipping smtp-send", msg_id
"Cannot load data to send, maybe the message is deleted in between.",
);
return Ok(());
}
} else {
let mut mimefactory = mimefactory.unwrap();
// no redo, no IMAP. moreover, as the data does not exist, there is no need in calling dc_set_msg_failed()
if chat::msgtype_has_file(mimefactory.msg.type_0) {
let file_param = mimefactory
.msg
.param
.get(Param::File)
.map(|s| s.to_string());
if let Some(pathNfilename) = file_param {
if (mimefactory.msg.type_0 == Viewtype::Image
|| mimefactory.msg.type_0 == Viewtype::Gif)
&& !mimefactory.msg.param.exists(Param::Width)
{
mimefactory.msg.param.set_int(Param::Width, 0);
mimefactory.msg.param.set_int(Param::Height, 0);
if mimefactory.out_gossiped {
chat::set_gossiped_timestamp(context, mimefactory.msg.chat_id, time());
}
if 0 != mimefactory.out_last_added_location_id {
if let Err(err) = location::set_kml_sent_timestamp(context, mimefactory.msg.chat_id, time())
{
error!(context, "Failed to set kml sent_timestamp: {:?}", err);
}
if !mimefactory.msg.hidden {
if let Err(err) = location::set_msg_location_id(
context,
mimefactory.msg.id,
mimefactory.out_last_added_location_id,
) {
error!(context, "Failed to set msg_location_id: {:?}", err);
if let Ok(buf) = dc_read_file(context, pathNfilename) {
if let Ok((width, height)) = dc_get_filemeta(&buf) {
mimefactory.msg.param.set_int(Param::Width, width as i32);
mimefactory.msg.param.set_int(Param::Height, height as i32);
}
}
mimefactory.msg.save_param_to_disk(context);
}
}
}
/* create message */
if let Err(msg) = unsafe { mimefactory.render() } {
let e = msg.to_string();
message::set_msg_failed(context, msg_id, Some(e));
} else if 0
!= mimefactory
.msg
.param
.get_int(Param::GuranteeE2ee)
.unwrap_or_default()
&& !mimefactory.out_encrypted
{
/* unrecoverable */
warn!(
context,
"e2e encryption unavailable {} - {:?}",
msg_id,
mimefactory.msg.param.get_int(Param::GuranteeE2ee),
);
message::set_msg_failed(
context,
msg_id,
Some("End-to-end-encryption unavailable unexpectedly."),
);
} else {
if !vec_contains_lowercase(&mimefactory.recipients_addr, &mimefactory.from_addr) {
mimefactory.recipients_names.push("".to_string());
mimefactory
.recipients_addr
.push(mimefactory.from_addr.to_string());
}
if mimefactory.out_gossiped {
chat::set_gossiped_timestamp(context, mimefactory.msg.chat_id, time());
}
if 0 != mimefactory.out_last_added_location_id {
if let Err(err) =
location::set_kml_sent_timestamp(context, mimefactory.msg.chat_id, time())
{
error!(context, "Failed to set kml sent_timestamp: {:?}", err);
}
if !mimefactory.msg.hidden {
if let Err(err) = location::set_msg_location_id(
context,
mimefactory.msg.id,
mimefactory.out_last_added_location_id,
) {
error!(context, "Failed to set msg_location_id: {:?}", err);
}
}
}
if mimefactory.out_encrypted
&& mimefactory
.msg
.param
.get_int(Param::GuranteeE2ee)
.unwrap_or_default()
== 0
{
mimefactory.msg.param.set_int(Param::GuranteeE2ee, 1);
mimefactory.msg.save_param_to_disk(context);
}
success = add_smtp_job(context, Action::SendMsgToSmtp, &mut mimefactory);
}
}
if mimefactory.out_encrypted
&& mimefactory
.msg
.param
.get_int(Param::GuranteeE2ee)
.unwrap_or_default()
== 0
{
mimefactory.msg.param.set_int(Param::GuranteeE2ee, 1);
mimefactory.msg.save_param_to_disk(context);
}
add_smtp_job(context, Action::SendMsgToSmtp, &mut mimefactory)?;
Ok(())
success
}
pub fn perform_imap_jobs(context: &Context) {
@@ -751,13 +832,13 @@ fn job_perform(context: &Context, thread: Thread, probe_network: bool) {
// - they can be re-executed one time AT_ONCE, but they are not save in the database for later execution
if Action::ConfigureImap == job.action || Action::ImexImap == job.action {
job_kill_action(context, job.action);
context
&context
.sentbox_thread
.clone()
.read()
.unwrap()
.suspend(context);
context
&context
.mvbox_thread
.clone()
.read()
@@ -781,8 +862,8 @@ fn job_perform(context: &Context, thread: Thread, probe_network: bool) {
Action::MarkseenMdnOnImap => job.do_DC_JOB_MARKSEEN_MDN_ON_IMAP(context),
Action::MoveMsg => job.do_DC_JOB_MOVE_MSG(context),
Action::SendMdn => job.do_DC_JOB_SEND(context),
Action::ImexImap => job_do_DC_JOB_IMEX_IMAP(context, &job),
Action::ConfigureImap => dc_job_do_DC_JOB_CONFIGURE_IMAP(context),
Action::ConfigureImap => unsafe { dc_job_do_DC_JOB_CONFIGURE_IMAP(context) },
Action::ImexImap => unsafe { dc_job_do_DC_JOB_IMEX_IMAP(context, &job) },
Action::MaybeSendLocations => {
location::job_do_DC_JOB_MAYBE_SEND_LOCATIONS(context, &job)
}
@@ -877,7 +958,8 @@ fn job_perform(context: &Context, thread: Thread, probe_network: bool) {
#[allow(non_snake_case)]
fn get_backoff_time_offset(c_tries: libc::c_int) -> i64 {
// results in ~3 weeks for the last backoff timespan
let N = 2_i32.pow((c_tries - 1) as u32) * 60;
let mut N = 2_i32.pow((c_tries - 1) as u32);
N = N * 60;
let mut rng = thread_rng();
let n: i32 = rng.gen();
let mut seconds = n % (N + 1);
@@ -899,7 +981,7 @@ fn suspend_smtp_thread(context: &Context, suspend: bool) {
}
}
pub fn connect_to_inbox(context: &Context, inbox: &Imap) -> libc::c_int {
fn connect_to_inbox(context: &Context, inbox: &Imap) -> libc::c_int {
let ret_connected = dc_connect_to_configured_imap(context, inbox);
if 0 != ret_connected {
inbox.set_watch_folder("INBOX".into());
@@ -907,44 +989,51 @@ pub fn connect_to_inbox(context: &Context, inbox: &Imap) -> libc::c_int {
ret_connected
}
fn send_mdn(context: &Context, msg_id: u32) -> Result<(), Error> {
let mut mimefactory = MimeFactory::load_mdn(context, msg_id)?;
unsafe { mimefactory.render()? };
add_smtp_job(context, Action::SendMdn, &mut mimefactory)?;
Ok(())
fn send_mdn(context: &Context, msg_id: u32) {
if let Ok(mut mimefactory) = MimeFactory::load_mdn(context, msg_id) {
if unsafe { mimefactory.render() }.is_ok() {
add_smtp_job(context, Action::SendMdn, &mut mimefactory);
}
}
}
#[allow(non_snake_case)]
fn add_smtp_job(context: &Context, action: Action, mimefactory: &MimeFactory) -> Result<(), Error> {
ensure!(
!mimefactory.recipients_addr.is_empty(),
"no recipients for smtp job set"
);
fn add_smtp_job(context: &Context, action: Action, mimefactory: &MimeFactory) -> libc::c_int {
let mut success: libc::c_int = 0i32;
let mut param = Params::new();
let path_filename = dc_get_fine_path_filename(context, "$BLOBDIR", &mimefactory.rfc724_mid);
let bytes = unsafe {
std::slice::from_raw_parts(
(*mimefactory.out).str_0 as *const u8,
(*mimefactory.out).len,
)
};
let bpath = context.new_blob_file(&mimefactory.rfc724_mid, bytes)?;
let recipients = mimefactory.recipients_addr.join("\x1e");
param.set(Param::File, &bpath);
param.set(Param::Recipients, &recipients);
job_add(
context,
action,
(if mimefactory.loaded == Loaded::Message {
mimefactory.msg.id
} else {
0
}) as libc::c_int,
param,
0,
);
Ok(())
if !dc_write_file(context, &path_filename, bytes) {
error!(
context,
"Could not write message <{}> to \"{}\".",
mimefactory.rfc724_mid,
path_filename.display(),
);
} else {
info!(context, "add_smtp_job file written: {:?}", path_filename);
let recipients = mimefactory.recipients_addr.join("\x1e");
param.set(Param::File, path_filename.to_string_lossy());
param.set(Param::Recipients, &recipients);
job_add(
context,
action,
(if mimefactory.loaded == Loaded::Message {
mimefactory.msg.id
} else {
0
}) as libc::c_int,
param,
0,
);
success = 1;
}
success
}
pub fn job_add(

View File

@@ -116,14 +116,14 @@ impl JobThread {
if ret_connected {
if context
.sql
.get_raw_config_int(context, "folders_configured")
.get_config_int(context, "folders_configured")
.unwrap_or_default()
< 3
{
self.imap.configure_folders(context, 0x1);
}
if let Some(mvbox_name) = context.sql.get_raw_config(context, self.folder_config_name) {
if let Some(mvbox_name) = context.sql.get_config(context, self.folder_config_name) {
self.imap.set_watch_folder(mvbox_name);
} else {
self.imap.disconnect(context);

View File

@@ -2,6 +2,7 @@ use std::collections::BTreeMap;
use std::io::Cursor;
use std::path::Path;
use libc;
use pgp::composed::{Deserializable, SignedPublicKey, SignedSecretKey};
use pgp::ser::Serialize;
use pgp::types::{KeyTrait, SecretKeyTrait};
@@ -163,8 +164,8 @@ impl Key {
pub fn to_bytes(&self) -> Vec<u8> {
match self {
Key::Public(k) => k.to_bytes().unwrap_or_default(),
Key::Secret(k) => k.to_bytes().unwrap_or_default(),
Key::Public(k) => k.to_bytes().unwrap(),
Key::Secret(k) => k.to_bytes().unwrap(),
}
}
@@ -218,10 +219,10 @@ impl Key {
let file_content = self.to_asc(None).into_bytes();
if dc_write_file(context, &file, &file_content) {
true
return true;
} else {
error!(context, "Cannot write key to {}", file.as_ref().display());
false
return false;
}
}
@@ -253,14 +254,14 @@ pub fn dc_key_save_self_keypair(
public_key: &Key,
private_key: &Key,
addr: impl AsRef<str>,
is_default: bool,
is_default: libc::c_int,
sql: &Sql,
) -> bool {
sql::execute(
context,
sql,
"INSERT INTO keypairs (addr, is_default, public_key, private_key, created) VALUES (?,?,?,?,?);",
params![addr.as_ref(), is_default as i32, public_key.to_bytes(), private_key.to_bytes(), time()],
params![addr.as_ref(), is_default, public_key.to_bytes(), private_key.to_bytes(), time()],
).is_ok()
}

View File

@@ -39,36 +39,36 @@ pub mod contact;
pub mod context;
mod e2ee;
mod imap;
pub mod imex;
pub mod job;
mod job_thread;
pub mod key;
pub mod keyring;
pub mod location;
mod login_param;
pub mod lot;
pub mod message;
mod mimefactory;
pub mod oauth2;
mod param;
pub mod peerstate;
pub mod pgp;
pub mod qr;
pub mod securejoin;
mod smtp;
pub mod sql;
mod stock;
mod token;
#[macro_use]
mod wrapmime;
pub mod dc_array;
mod dc_dehtml;
pub mod dc_imex;
pub mod dc_mimeparser;
pub mod dc_receive_imf;
mod dc_simplify;
mod dc_strencode;
pub mod dc_tools;
mod login_param;
mod mimefactory;
pub mod securejoin;
mod token;
#[macro_use]
mod wrapmime;
#[cfg(test)]
mod test_utils;

View File

@@ -3,7 +3,6 @@ use quick_xml;
use quick_xml::events::{BytesEnd, BytesStart, BytesText};
use crate::chat;
use crate::config::Config;
use crate::constants::*;
use crate::context::*;
use crate::dc_tools::*;
@@ -63,7 +62,7 @@ impl Kml {
pub fn parse(context: &Context, content: impl AsRef<str>) -> Result<Self, Error> {
ensure!(
content.as_ref().len() <= (1024 * 1024),
content.as_ref().len() <= (1 * 1024 * 1024),
"A kml-files with {} bytes is larger than reasonably expected.",
content.as_ref().len()
);
@@ -219,7 +218,7 @@ pub fn send_locations_to_chat(context: &Context, chat_id: u32, seconds: i64) {
msg.text =
Some(context.stock_system_msg(StockMessage::MsgLocationEnabled, "", "", 0));
msg.param.set_int(Param::Cmd, 8);
chat::send_msg(context, chat_id, &mut msg).unwrap_or_default();
chat::send_msg(context, chat_id, &mut msg).unwrap();
} else if 0 == seconds && is_sending_locations_before {
let stock_str =
context.stock_system_msg(StockMessage::MsgLocationDisabled, "", "", 0);
@@ -360,7 +359,7 @@ pub fn get_range(
}
fn is_marker(txt: &str) -> bool {
txt.len() == 1 && !txt.starts_with(' ')
txt.len() == 1 && txt.chars().next().unwrap() != ' '
}
pub fn delete_all(context: &Context) -> Result<(), Error> {
@@ -376,7 +375,8 @@ pub fn get_kml(context: &Context, chat_id: u32) -> Result<(String, u32), Error>
let mut last_added_location_id = 0;
let self_addr = context
.get_config(Config::ConfiguredAddr)
.sql
.get_config(context, "configured_addr")
.unwrap_or_default();
let (locations_send_begin, locations_send_until, locations_last_sent) = context.sql.query_row(
@@ -615,7 +615,7 @@ pub fn job_do_DC_JOB_MAYBE_SEND_LOCATIONS(context: &Context, _job: &Job) {
for (chat_id, mut msg) in msgs.into_iter() {
// TODO: better error handling
chat::send_msg(context, chat_id as u32, &mut msg).unwrap_or_default();
chat::send_msg(context, chat_id as u32, &mut msg).unwrap();
}
}
if 0 != continue_streaming {

View File

@@ -4,22 +4,6 @@ use std::fmt;
use crate::context::Context;
use crate::error::Error;
#[derive(Copy, Clone, Debug, Display, FromPrimitive)]
#[repr(i32)]
#[strum(serialize_all = "snake_case")]
pub enum CertificateChecks {
Automatic = 0,
Strict = 1,
AcceptInvalidHostnames = 2,
AcceptInvalidCertificates = 3,
}
impl Default for CertificateChecks {
fn default() -> Self {
Self::Automatic
}
}
#[derive(Default, Debug)]
pub struct LoginParam {
pub addr: String,
@@ -27,14 +11,10 @@ pub struct LoginParam {
pub mail_user: String,
pub mail_pw: String,
pub mail_port: i32,
/// IMAP TLS options: whether to allow invalid certificates and/or invalid hostnames
pub imap_certificate_checks: CertificateChecks,
pub send_server: String,
pub send_user: String,
pub send_pw: String,
pub send_port: i32,
/// SMTP TLS options: whether to allow invalid certificates and/or invalid hostnames
pub smtp_certificate_checks: CertificateChecks,
pub server_flags: i32,
}
@@ -51,53 +31,37 @@ impl LoginParam {
let key = format!("{}addr", prefix);
let addr = sql
.get_raw_config(context, key)
.get_config(context, key)
.unwrap_or_default()
.trim()
.to_string();
let key = format!("{}mail_server", prefix);
let mail_server = sql.get_raw_config(context, key).unwrap_or_default();
let mail_server = sql.get_config(context, key).unwrap_or_default();
let key = format!("{}mail_port", prefix);
let mail_port = sql.get_raw_config_int(context, key).unwrap_or_default();
let mail_port = sql.get_config_int(context, key).unwrap_or_default();
let key = format!("{}mail_user", prefix);
let mail_user = sql.get_raw_config(context, key).unwrap_or_default();
let mail_user = sql.get_config(context, key).unwrap_or_default();
let key = format!("{}mail_pw", prefix);
let mail_pw = sql.get_raw_config(context, key).unwrap_or_default();
let key = format!("{}imap_certificate_checks", prefix);
let imap_certificate_checks =
if let Some(certificate_checks) = sql.get_raw_config_int(context, key) {
num_traits::FromPrimitive::from_i32(certificate_checks).unwrap()
} else {
Default::default()
};
let mail_pw = sql.get_config(context, key).unwrap_or_default();
let key = format!("{}send_server", prefix);
let send_server = sql.get_raw_config(context, key).unwrap_or_default();
let send_server = sql.get_config(context, key).unwrap_or_default();
let key = format!("{}send_port", prefix);
let send_port = sql.get_raw_config_int(context, key).unwrap_or_default();
let send_port = sql.get_config_int(context, key).unwrap_or_default();
let key = format!("{}send_user", prefix);
let send_user = sql.get_raw_config(context, key).unwrap_or_default();
let send_user = sql.get_config(context, key).unwrap_or_default();
let key = format!("{}send_pw", prefix);
let send_pw = sql.get_raw_config(context, key).unwrap_or_default();
let key = format!("{}smtp_certificate_checks", prefix);
let smtp_certificate_checks =
if let Some(certificate_checks) = sql.get_raw_config_int(context, key) {
num_traits::FromPrimitive::from_i32(certificate_checks).unwrap()
} else {
Default::default()
};
let send_pw = sql.get_config(context, key).unwrap_or_default();
let key = format!("{}server_flags", prefix);
let server_flags = sql.get_raw_config_int(context, key).unwrap_or_default();
let server_flags = sql.get_config_int(context, key).unwrap_or_default();
LoginParam {
addr: addr.to_string(),
@@ -105,12 +69,10 @@ impl LoginParam {
mail_user,
mail_pw,
mail_port,
imap_certificate_checks,
send_server,
send_user,
send_pw,
send_port,
smtp_certificate_checks,
server_flags,
}
}
@@ -129,40 +91,34 @@ impl LoginParam {
let sql = &context.sql;
let key = format!("{}addr", prefix);
sql.set_raw_config(context, key, Some(&self.addr))?;
sql.set_config(context, key, Some(&self.addr))?;
let key = format!("{}mail_server", prefix);
sql.set_raw_config(context, key, Some(&self.mail_server))?;
sql.set_config(context, key, Some(&self.mail_server))?;
let key = format!("{}mail_port", prefix);
sql.set_raw_config_int(context, key, self.mail_port)?;
sql.set_config_int(context, key, self.mail_port)?;
let key = format!("{}mail_user", prefix);
sql.set_raw_config(context, key, Some(&self.mail_user))?;
sql.set_config(context, key, Some(&self.mail_user))?;
let key = format!("{}mail_pw", prefix);
sql.set_raw_config(context, key, Some(&self.mail_pw))?;
let key = format!("{}imap_certificate_checks", prefix);
sql.set_raw_config_int(context, key, self.imap_certificate_checks as i32)?;
sql.set_config(context, key, Some(&self.mail_pw))?;
let key = format!("{}send_server", prefix);
sql.set_raw_config(context, key, Some(&self.send_server))?;
sql.set_config(context, key, Some(&self.send_server))?;
let key = format!("{}send_port", prefix);
sql.set_raw_config_int(context, key, self.send_port)?;
sql.set_config_int(context, key, self.send_port)?;
let key = format!("{}send_user", prefix);
sql.set_raw_config(context, key, Some(&self.send_user))?;
sql.set_config(context, key, Some(&self.send_user))?;
let key = format!("{}send_pw", prefix);
sql.set_raw_config(context, key, Some(&self.send_pw))?;
let key = format!("{}smtp_certificate_checks", prefix);
sql.set_raw_config_int(context, key, self.smtp_certificate_checks as i32)?;
sql.set_config(context, key, Some(&self.send_pw))?;
let key = format!("{}server_flags", prefix);
sql.set_raw_config_int(context, key, self.server_flags)?;
sql.set_config_int(context, key, self.server_flags)?;
Ok(())
}
@@ -177,18 +133,16 @@ impl fmt::Display for LoginParam {
write!(
f,
"{} imap:{}:{}:{}:{}:cert_{} smtp:{}:{}:{}:{}:cert_{} {}",
"{} {}:{}:{}:{} {}:{}:{}:{} {}",
unset_empty(&self.addr),
unset_empty(&self.mail_user),
if !self.mail_pw.is_empty() { pw } else { unset },
unset_empty(&self.mail_server),
self.mail_port,
self.imap_certificate_checks,
unset_empty(&self.send_user),
if !self.send_pw.is_empty() { pw } else { unset },
unset_empty(&self.send_server),
self.send_port,
self.smtp_certificate_checks,
flags_readable,
)
}
@@ -250,41 +204,3 @@ fn get_readable_flags(flags: i32) -> String {
res
}
pub fn dc_build_tls(
certificate_checks: CertificateChecks,
) -> Result<native_tls::TlsConnector, native_tls::Error> {
let mut tls_builder = native_tls::TlsConnector::builder();
match certificate_checks {
CertificateChecks::Automatic => {
// Same as AcceptInvalidCertificates for now.
// TODO: use provider database when it becomes available
tls_builder
.danger_accept_invalid_hostnames(true)
.danger_accept_invalid_certs(true)
}
CertificateChecks::Strict => &mut tls_builder,
CertificateChecks::AcceptInvalidHostnames => {
tls_builder.danger_accept_invalid_hostnames(true)
}
CertificateChecks::AcceptInvalidCertificates => tls_builder
.danger_accept_invalid_hostnames(true)
.danger_accept_invalid_certs(true),
}
.build()
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_certificate_checks_display() {
use std::string::ToString;
assert_eq!(
"accept_invalid_hostnames".to_string(),
CertificateChecks::AcceptInvalidHostnames.to_string()
);
}
}

View File

@@ -2,6 +2,8 @@ use std::path::{Path, PathBuf};
use std::ptr;
use deltachat_derive::{FromSql, ToSql};
use libc::{free, strcmp};
use phf::phf_map;
use crate::chat::{self, Chat};
use crate::constants::*;
@@ -107,6 +109,18 @@ impl Message {
msg.hidden = row.get(18)?;
msg.location_id = row.get(19)?;
msg.chat_blocked = row.get::<_, Option<Blocked>>(20)?.unwrap_or_default();
if msg.chat_blocked == Blocked::Deaddrop {
if let Some(ref text) = msg.text {
unsafe {
let ptr = text.strdup();
dc_truncate_n_unwrap_str(ptr, 256, 0);
msg.text = Some(to_string(ptr));
free(ptr.cast());
}
}
};
Ok(msg)
})
}
@@ -130,18 +144,16 @@ impl Message {
}
}
pub fn get_filemime(&self) -> Option<String> {
pub fn get_filemime(&self) -> String {
if let Some(m) = self.param.get(Param::MimeType) {
return Some(m.to_string());
return m.to_string();
} else if let Some(file) = self.param.get(Param::File) {
if let Some((_, mime)) = guess_msgtype_from_suffix(Path::new(file)) {
return Some(mime.to_string());
return mime.to_string();
}
// we have a file but no mimetype, let's use a generic one
return Some("application/octet-stream".to_string());
}
// no mimetype and no file
None
"application/octet-stream".to_string()
}
pub fn get_file(&self, context: &Context) -> Option<PathBuf> {
@@ -334,7 +346,7 @@ impl Message {
}
pub fn get_setupcodebegin(&self, context: &Context) -> Option<String> {
if !self.is_setupmessage() {
if self.is_setupmessage() {
return None;
}
@@ -342,7 +354,7 @@ impl Message {
if let Ok(mut buf) = dc_read_file(context, filename) {
unsafe {
// just a pointer inside buf, MUST NOT be free()'d
let mut buf_headerline = String::default();
let mut buf_headerline = ptr::null();
// just a pointer inside buf, MUST NOT be free()'d
let mut buf_setupcodebegin = ptr::null();
@@ -352,10 +364,13 @@ impl Message {
&mut buf_setupcodebegin,
ptr::null_mut(),
ptr::null_mut(),
) && buf_headerline == "-----BEGIN PGP MESSAGE-----"
) && strcmp(
buf_headerline,
b"-----BEGIN PGP MESSAGE-----\x00" as *const u8 as *const libc::c_char,
) == 0
&& !buf_setupcodebegin.is_null()
{
return Some(to_string_lossy(buf_setupcodebegin));
return Some(to_string(buf_setupcodebegin));
}
}
}
@@ -494,10 +509,12 @@ impl Lot {
} else {
self.text1 = None;
}
} else if let Some(contact) = contact {
self.text1 = Some(contact.get_first_name().into());
} else {
self.text1 = None;
if let Some(contact) = contact {
self.text1 = Some(contact.get_first_name().into());
} else {
self.text1 = None;
}
}
self.text1_meaning = Meaning::Text1Username;
}
@@ -524,7 +541,7 @@ pub fn get_msg_info(context: &Context, msg_id: u32) -> String {
return ret;
}
let msg = msg.unwrap_or_default();
let msg = msg.unwrap();
let rawtxt: Option<String> = context.sql.query_get_value(
context,
@@ -536,7 +553,7 @@ pub fn get_msg_info(context: &Context, msg_id: u32) -> String {
ret += &format!("Cannot load message #{}.", msg_id as usize);
return ret;
}
let rawtxt = rawtxt.unwrap_or_default();
let rawtxt = rawtxt.unwrap();
let rawtxt = dc_truncate(rawtxt.trim(), 100000, false);
let fts = dc_timestamp_to_str(msg.get_timestamp());
@@ -616,8 +633,9 @@ pub fn get_msg_info(context: &Context, msg_id: u32) -> String {
}
ret += "\n";
if let Some(err) = msg.param.get(Param::Error) {
ret += &format!("Error: {}", err)
match msg.param.get(Param::Error) {
Some(err) => ret += &format!("Error: {}", err),
_ => {}
}
if let Some(path) = msg.get_file(context) {
@@ -629,7 +647,7 @@ pub fn get_msg_info(context: &Context, msg_id: u32) -> String {
ret += "Type: ";
ret += &format!("{}", msg.type_0);
ret += "\n";
ret += &format!("Mimetype: {}\n", &msg.get_filemime().unwrap_or_default());
ret += &format!("Mimetype: {}\n", &msg.get_filemime());
}
let w = msg.param.get_int(Param::Width).unwrap_or_default();
let h = msg.param.get_int(Param::Height).unwrap_or_default();
@@ -656,24 +674,22 @@ pub fn get_msg_info(context: &Context, msg_id: u32) -> String {
}
pub fn guess_msgtype_from_suffix(path: &Path) -> Option<(Viewtype, &str)> {
let extension: &str = &path.extension()?.to_str()?.to_lowercase();
let info = match extension {
"mp3" => (Viewtype::Audio, "audio/mpeg"),
"aac" => (Viewtype::Audio, "audio/aac"),
"mp4" => (Viewtype::Video, "video/mp4"),
"jpg" => (Viewtype::Image, "image/jpeg"),
"jpeg" => (Viewtype::Image, "image/jpeg"),
"jpe" => (Viewtype::Image, "image/jpeg"),
"png" => (Viewtype::Image, "image/png"),
"webp" => (Viewtype::Image, "image/webp"),
"gif" => (Viewtype::Gif, "image/gif"),
"vcf" => (Viewtype::File, "text/vcard"),
"vcard" => (Viewtype::File, "text/vcard"),
_ => {
return None;
}
static KNOWN: phf::Map<&'static str, (Viewtype, &'static str)> = phf_map! {
"mp3" => (Viewtype::Audio, "audio/mpeg"),
"aac" => (Viewtype::Audio, "audio/aac"),
"mp4" => (Viewtype::Video, "video/mp4"),
"jpg" => (Viewtype::Image, "image/jpeg"),
"jpeg" => (Viewtype::Image, "image/jpeg"),
"jpe" => (Viewtype::Image, "image/jpeg"),
"png" => (Viewtype::Image, "image/png"),
"webp" => (Viewtype::Image, "image/webp"),
"gif" => (Viewtype::Gif, "image/gif"),
"vcf" => (Viewtype::File, "text/vcard"),
"vcard" => (Viewtype::File, "text/vcard"),
};
Some(info)
let extension: &str = &path.extension()?.to_str()?.to_lowercase();
KNOWN.get(extension).map(|x| *x)
}
pub fn get_mime_headers(context: &Context, msg_id: u32) -> Option<String> {
@@ -685,7 +701,7 @@ pub fn get_mime_headers(context: &Context, msg_id: u32) -> Option<String> {
}
pub fn delete_msgs(context: &Context, msg_ids: &[u32]) {
for msg_id in msg_ids.iter() {
for msg_id in msg_ids.into_iter() {
update_msg_chat_id(context, *msg_id, DC_CHAT_ID_TRASH);
job_add(
context,
@@ -725,7 +741,7 @@ pub fn markseen_msgs(context: &Context, msg_ids: &[u32]) -> bool {
"SELECT m.state, c.blocked FROM msgs m LEFT JOIN chats c ON c.id=m.chat_id WHERE m.id=? AND m.chat_id>9",
|mut stmt, _| {
let mut res = Vec::with_capacity(msg_ids.len());
for id in msg_ids.iter() {
for id in msg_ids.into_iter() {
let query_res = stmt.query_row(params![*id as i32], |row| {
Ok((row.get::<_, MessageState>(0)?, row.get::<_, Option<Blocked>>(1)?.unwrap_or_default()))
});
@@ -745,7 +761,7 @@ pub fn markseen_msgs(context: &Context, msg_ids: &[u32]) -> bool {
return false;
}
let mut send_event = false;
let msgs = msgs.unwrap_or_default();
let msgs = msgs.unwrap();
for (id, curr_state, curr_blocked) in msgs.into_iter() {
if curr_blocked == Blocked::Not {
@@ -795,7 +811,7 @@ pub fn star_msgs(context: &Context, msg_ids: &[u32], star: bool) -> bool {
context
.sql
.prepare("UPDATE msgs SET starred=? WHERE id=?;", |mut stmt, _| {
for msg_id in msg_ids.iter() {
for msg_id in msg_ids.into_iter() {
stmt.execute(params![star as i32, *msg_id as i32])?;
}
Ok(())
@@ -815,7 +831,6 @@ pub fn get_summarytext_by_raw(
let prefix = match viewtype {
Viewtype::Image => context.stock_str(StockMessage::Image).into_owned(),
Viewtype::Gif => context.stock_str(StockMessage::Gif).into_owned(),
Viewtype::Sticker => context.stock_str(StockMessage::Sticker).into_owned(),
Viewtype::Video => context.stock_str(StockMessage::Video).into_owned(),
Viewtype::Voice => context.stock_str(StockMessage::VoiceMessage).into_owned(),
Viewtype::Audio | Viewtype::File => {
@@ -834,7 +849,7 @@ pub fn get_summarytext_by_raw(
} else {
None
}
.unwrap_or_else(|| "ErrFileName".to_string());
.unwrap_or("ErrFileName".to_string());
let label = context.stock_str(if viewtype == Viewtype::Audio {
StockMessage::Audio
@@ -982,7 +997,7 @@ pub fn mdn_from_ext(
context.sql.execute(
"INSERT INTO msgs_mdns (msg_id, contact_id, timestamp_sent) VALUES (?, ?, ?);",
params![*ret_msg_id as i32, from_id as i32, timestamp_sent],
).unwrap_or_default(); // TODO: better error handling
).unwrap(); // TODO: better error handling
}
// Normal chat? that's quite easy.
@@ -1141,6 +1156,5 @@ mod tests {
let msg_id = chat::prepare_msg(ctx, chat, &mut msg).unwrap();
let _msg2 = Message::load_from_db(ctx, msg_id).unwrap();
assert_eq!(_msg2.get_filemime(), None);
}
}

View File

@@ -3,21 +3,20 @@ use std::ptr;
use chrono::TimeZone;
use mmime::clist::*;
use mmime::mailimf::types::*;
use mmime::mailimf::types_helper::*;
use mmime::mailmime::disposition::*;
use mmime::mailmime::types::*;
use mmime::mailmime::types_helper::*;
use mmime::mailmime::write_mem::*;
use mmime::mailimf_types::*;
use mmime::mailimf_types_helper::*;
use mmime::mailmime_disposition::*;
use mmime::mailmime_types::*;
use mmime::mailmime_types_helper::*;
use mmime::mailmime_write_mem::*;
use mmime::mmapstring::*;
use mmime::other::*;
use crate::chat::{self, Chat};
use crate::config::Config;
use crate::constants::*;
use crate::contact::*;
use crate::context::{get_version_str, Context};
use crate::dc_mimeparser::SystemMessage;
use crate::dc_mimeparser::{mailmime_find_mailimf_fields, SystemMessage};
use crate::dc_strencode::*;
use crate::dc_tools::*;
use crate::e2ee::*;
@@ -60,13 +59,11 @@ pub struct MimeFactory<'a> {
impl<'a> MimeFactory<'a> {
fn new(context: &'a Context, msg: Message) -> Self {
let cget = |context: &Context, name: &str| context.sql.get_config(context, name);
MimeFactory {
from_addr: context
.get_config(Config::ConfiguredAddr)
.unwrap_or_default(),
from_displayname: context.get_config(Config::Displayname).unwrap_or_default(),
selfstatus: context
.get_config(Config::Selfstatus)
from_addr: cget(&context, "configured_addr").unwrap_or_default(),
from_displayname: cget(&context, "displayname").unwrap_or_default(),
selfstatus: cget(&context, "selfstatus")
.unwrap_or_else(|| context.stock_str(StockMessage::StatusLine).to_string()),
recipients_names: Vec::with_capacity(5),
recipients_addr: Vec::with_capacity(5),
@@ -89,7 +86,7 @@ impl<'a> MimeFactory<'a> {
pub fn finalize_mime_message(
&mut self,
message: *mut Mailmime,
message: *mut mailmime,
encrypted: bool,
gossiped: bool,
) -> Result<(), Error> {
@@ -109,10 +106,15 @@ impl<'a> MimeFactory<'a> {
}
pub fn load_mdn(context: &'a Context, msg_id: u32) -> Result<MimeFactory, Error> {
if !context.get_config_bool(Config::MdnsEnabled) {
// MDNs not enabled - check this is late, in the job. the
// user may have changed its choice while offline ...
bail!("MDNs meanwhile disabled")
if 0 == context
.sql
.get_config_int(context, "mdns_enabled")
.unwrap_or_else(|| 1)
{
// MDNs not enabled - check this is late, in the job. the use may have changed its
// choice while offline ...
bail!("MDNs disabled ")
}
let msg = Message::load_from_db(context, msg_id)?;
@@ -139,35 +141,52 @@ impl<'a> MimeFactory<'a> {
}
/*******************************************************************************
* Render a basic email
* Render
******************************************************************************/
// XXX restrict unsafe to parts, introduce wrapmime helpers where appropriate
// restrict unsafe to parts, introduce wrapmime helpers where appropriate
pub unsafe fn render(&mut self) -> Result<(), Error> {
if self.loaded == Loaded::Nothing || !self.out.is_null() {
bail!("Invalid use of mimefactory-object.");
}
let context = &self.context;
let from = wrapmime::new_mailbox_list(&self.from_displayname, &self.from_addr);
let to = mailimf_address_list_new_empty();
let name_iter = self.recipients_names.iter();
let addr_iter = self.recipients_addr.iter();
for (name, addr) in name_iter.zip(addr_iter) {
mailimf_address_list_add(
to,
mailimf_address_new(
MAILIMF_ADDRESS_MAILBOX as libc::c_int,
mailimf_mailbox_new(
if !name.is_empty() {
dc_encode_header_words(&name).strdup()
} else {
ptr::null_mut()
},
addr.strdup(),
/* create basic mail
*************************************************************************/
let from: *mut mailimf_mailbox_list = mailimf_mailbox_list_new_empty();
mailimf_mailbox_list_add(
from,
mailimf_mailbox_new(
if !self.from_displayname.is_empty() {
dc_encode_header_words(&self.from_displayname).strdup()
} else {
ptr::null_mut()
},
self.from_addr.strdup(),
),
);
let mut to: *mut mailimf_address_list = ptr::null_mut();
if !self.recipients_names.is_empty() && !self.recipients_addr.is_empty() {
to = mailimf_address_list_new_empty();
let name_iter = self.recipients_names.iter();
let addr_iter = self.recipients_addr.iter();
for (name, addr) in name_iter.zip(addr_iter) {
mailimf_address_list_add(
to,
mailimf_address_new(
MAILIMF_ADDRESS_MAILBOX as libc::c_int,
mailimf_mailbox_new(
if !name.is_empty() {
dc_encode_header_words(&name).strdup()
} else {
ptr::null_mut()
},
addr.strdup(),
),
ptr::null_mut(),
),
ptr::null_mut(),
),
);
);
}
}
let references_list = if !self.references.is_empty() {
dc_str_to_clist(&self.references, " ")
@@ -179,7 +198,6 @@ impl<'a> MimeFactory<'a> {
} else {
ptr::null_mut()
};
let imf_fields = mailimf_fields_new_with_data_all(
mailimf_get_date(self.timestamp as i64),
from,
@@ -218,12 +236,12 @@ impl<'a> MimeFactory<'a> {
);
}
let cleanup = |message: *mut Mailmime| {
let cleanup = |message: *mut mailmime| {
if !message.is_null() {
mailmime_free(message);
}
};
let message = mailmime_new_message_data(0 as *mut Mailmime);
let message = mailmime_new_message_data(0 as *mut mailmime);
ensure!(!message.is_null(), "could not create mime message data");
mailmime_set_imf_fields(message, imf_fields);
@@ -239,7 +257,7 @@ impl<'a> MimeFactory<'a> {
/* Render a normal message
*********************************************************************/
let chat = self.chat.as_ref().unwrap();
let mut meta_part: *mut Mailmime = ptr::null_mut();
let mut meta_part: *mut mailmime = ptr::null_mut();
let mut placeholdertext = None;
if chat.typ == Chattype::VerifiedGroup {
@@ -384,8 +402,15 @@ impl<'a> MimeFactory<'a> {
&fingerprint,
);
}
if let Some(id) = msg.param.get(Param::Arg4) {
wrapmime::new_custom_field(imf_fields, "Secure-Join-Group", &id);
match msg.param.get(Param::Arg4) {
Some(id) => {
wrapmime::new_custom_field(
imf_fields,
"Secure-Join-Group",
&id,
);
}
None => {}
};
}
}
@@ -410,10 +435,6 @@ impl<'a> MimeFactory<'a> {
}
}
if self.msg.type_0 == Viewtype::Sticker {
wrapmime::new_custom_field(imf_fields, "Chat-Content", "sticker");
}
if self.msg.type_0 == Viewtype::Voice
|| self.msg.type_0 == Viewtype::Audio
|| self.msg.type_0 == Viewtype::Video
@@ -568,11 +589,12 @@ impl<'a> MimeFactory<'a> {
wrapmime::new_content_type("message/disposition-notification")?;
let mime_fields_0: *mut mailmime_fields =
mailmime_fields_new_encoding(MAILMIME_MECHANISM_8BIT as libc::c_int);
let mach_mime_part: *mut Mailmime =
let mach_mime_part: *mut mailmime =
mailmime_new_empty(content_type_0, mime_fields_0);
wrapmime::set_body_text(mach_mime_part, &message_text2)?;
mailmime_add_part(multipart, mach_mime_part);
force_plaintext = DC_FP_NO_AUTOCRYPT_HEADER;
info!(context, "sending MDM {:?}", message_text2);
/* currently, we do not send MDNs encrypted:
- in a multi-device-setup that is not set up properly, MDNs would disturb the communication as they
are send automatically which may lead to spreading outdated Autocrypt headers.
@@ -623,7 +645,7 @@ impl<'a> MimeFactory<'a> {
);
/*just a pointer into mailmime structure, must not be freed*/
let imffields_unprotected = wrapmime::mailmime_find_mailimf_fields(message);
let imffields_unprotected = mailmime_find_mailimf_fields(message);
ensure!(
!imffields_unprotected.is_null(),
"could not find mime fields"
@@ -635,18 +657,17 @@ impl<'a> MimeFactory<'a> {
let aheader = encrypt_helper.get_aheader().to_string();
wrapmime::new_custom_field(imffields_unprotected, "Autocrypt", &aheader);
}
let finalized = if force_plaintext == 0 {
encrypt_helper.try_encrypt(
let mut finalized = false;
if force_plaintext == 0 {
finalized = encrypt_helper.try_encrypt(
self,
e2ee_guaranteed,
min_verified,
do_gossip,
message,
imffields_unprotected,
)?
} else {
false
};
)?;
}
if !finalized {
self.finalize_mime_message(message, false, false)?;
}
@@ -695,7 +716,7 @@ impl<'a> MimeFactory<'a> {
Ok(())
},
)
.unwrap_or_default();
.unwrap();
let command = factory.msg.param.get_cmd();
let msg = &factory.msg;
@@ -705,7 +726,8 @@ impl<'a> MimeFactory<'a> {
let email_to_remove = msg.param.get(Param::Arg).unwrap_or_default();
let self_addr = context
.get_config(Config::ConfiguredAddr)
.sql
.get_config(context, "configured_addr")
.unwrap_or_default();
if !email_to_remove.is_empty() && email_to_remove != self_addr {
@@ -717,7 +739,10 @@ impl<'a> MimeFactory<'a> {
}
if command != SystemMessage::AutocryptSetupMessage
&& command != SystemMessage::SecurejoinMessage
&& context.get_config_bool(Config::MdnsEnabled)
&& 0 != context
.sql
.get_config_int(context, "mdns_enabled")
.unwrap_or_else(|| 1)
{
factory.req_mdn = true;
}
@@ -796,7 +821,7 @@ fn build_body_file(
context: &Context,
msg: &Message,
base_name: &str,
) -> Result<(*mut Mailmime, String), Error> {
) -> Result<(*mut mailmime, String), Error> {
let path_filename = match msg.param.get(Param::File) {
None => {
bail!("msg has no filename");
@@ -900,13 +925,13 @@ fn build_body_file(
let mime_sub = mailmime_new_empty(content, mime_fields);
let abs_path = dc_get_abs_path(context, path_filename)
.to_c_string()
.unwrap_or_default();
.unwrap();
mailmime_set_body_file(mime_sub, dc_strdup(abs_path.as_ptr()));
Ok((mime_sub, filename_to_send))
}
}
pub(crate) fn vec_contains_lowercase(vec: &[String], part: &str) -> bool {
pub(crate) fn vec_contains_lowercase(vec: &Vec<String>, part: &str) -> bool {
let partlc = part.to_lowercase();
for cur in vec.iter() {
if cur.to_lowercase() == partlc {

View File

@@ -50,7 +50,7 @@ pub fn dc_get_oauth2_url(
if let Some(oauth2) = Oauth2::from_address(addr) {
if context
.sql
.set_raw_config(
.set_config(
context,
"oauth2_pending_redirect_uri",
Some(redirect_uri.as_ref()),
@@ -82,17 +82,17 @@ pub fn dc_get_oauth2_access_token(
// read generated token
if !regenerate && !is_expired(context) {
let access_token = context.sql.get_raw_config(context, "oauth2_access_token");
let access_token = context.sql.get_config(context, "oauth2_access_token");
if access_token.is_some() {
// success
return access_token;
}
}
let refresh_token = context.sql.get_raw_config(context, "oauth2_refresh_token");
let refresh_token = context.sql.get_config(context, "oauth2_refresh_token");
let refresh_token_for = context
.sql
.get_raw_config(context, "oauth2_refresh_token_for")
.get_config(context, "oauth2_refresh_token_for")
.unwrap_or_else(|| "unset".into());
let (redirect_uri, token_url, update_redirect_uri_on_success) =
@@ -101,7 +101,7 @@ pub fn dc_get_oauth2_access_token(
(
context
.sql
.get_raw_config(context, "oauth2_pending_redirect_uri")
.get_config(context, "oauth2_pending_redirect_uri")
.unwrap_or_else(|| "unset".into()),
oauth2.init_token,
true,
@@ -114,7 +114,7 @@ pub fn dc_get_oauth2_access_token(
(
context
.sql
.get_raw_config(context, "oauth2_redirect_uri")
.get_config(context, "oauth2_redirect_uri")
.unwrap_or_else(|| "unset".into()),
oauth2.refresh_token,
false,
@@ -159,11 +159,11 @@ pub fn dc_get_oauth2_access_token(
if let Some(ref token) = response.refresh_token {
context
.sql
.set_raw_config(context, "oauth2_refresh_token", Some(token))
.set_config(context, "oauth2_refresh_token", Some(token))
.ok();
context
.sql
.set_raw_config(context, "oauth2_refresh_token_for", Some(code.as_ref()))
.set_config(context, "oauth2_refresh_token_for", Some(code.as_ref()))
.ok();
}
@@ -172,7 +172,7 @@ pub fn dc_get_oauth2_access_token(
if let Some(ref token) = response.access_token {
context
.sql
.set_raw_config(context, "oauth2_access_token", Some(token))
.set_config(context, "oauth2_access_token", Some(token))
.ok();
let expires_in = response
.expires_in
@@ -181,13 +181,13 @@ pub fn dc_get_oauth2_access_token(
.unwrap_or_else(|| 0);
context
.sql
.set_raw_config_int64(context, "oauth2_timestamp_expires", expires_in)
.set_config_int64(context, "oauth2_timestamp_expires", expires_in)
.ok();
if update_redirect_uri_on_success {
context
.sql
.set_raw_config(context, "oauth2_redirect_uri", Some(redirect_uri.as_ref()))
.set_config(context, "oauth2_redirect_uri", Some(redirect_uri.as_ref()))
.ok();
}
} else {
@@ -207,8 +207,14 @@ pub fn dc_get_oauth2_addr(
addr: impl AsRef<str>,
code: impl AsRef<str>,
) -> Option<String> {
let oauth2 = Oauth2::from_address(addr.as_ref())?;
oauth2.get_userinfo?;
let oauth2 = Oauth2::from_address(addr.as_ref());
if oauth2.is_none() {
return None;
}
let oauth2 = oauth2.unwrap();
if oauth2.get_userinfo.is_none() {
return None;
}
if let Some(access_token) =
dc_get_oauth2_access_token(context, addr.as_ref(), code.as_ref(), false)
@@ -293,7 +299,7 @@ impl Oauth2 {
fn is_expired(context: &Context) -> bool {
let expire_timestamp = context
.sql
.get_raw_config_int64(context, "oauth2_timestamp_expires")
.get_config_int64(context, "oauth2_timestamp_expires")
.unwrap_or_default();
if expire_timestamp <= 0 {

View File

@@ -12,65 +12,65 @@ use crate::error;
#[repr(u8)]
pub enum Param {
/// For messages and jobs
File = b'f',
File = 'f' as u8,
/// For Messages
Width = b'w',
Width = 'w' as u8,
/// For Messages
Height = b'h',
Height = 'h' as u8,
/// For Messages
Duration = b'd',
Duration = 'd' as u8,
/// For Messages
MimeType = b'm',
MimeType = 'm' as u8,
/// For Messages: message is encryoted, outgoing: guarantee E2EE or the message is not send
GuranteeE2ee = b'c',
GuranteeE2ee = 'c' as u8,
/// For Messages: decrypted with validation errors or without mutual set, if neither
/// 'c' nor 'e' are preset, the messages is only transport encrypted.
ErroneousE2ee = b'e',
ErroneousE2ee = 'e' as u8,
/// For Messages: force unencrypted message, either `ForcePlaintext::AddAutocryptHeader` (1),
/// `ForcePlaintext::NoAutocryptHeader` (2) or 0.
ForcePlaintext = b'u',
ForcePlaintext = 'u' as u8,
/// For Messages
WantsMdn = b'r',
WantsMdn = 'r' as u8,
/// For Messages
Forwarded = b'a',
Forwarded = 'a' as u8,
/// For Messages
Cmd = b'S',
Cmd = 'S' as u8,
/// For Messages
Arg = b'E',
Arg = 'E' as u8,
/// For Messages
Arg2 = b'F',
Arg2 = 'F' as u8,
/// For Messages
Arg3 = b'G',
Arg3 = 'G' as u8,
/// For Messages
Arg4 = b'H',
Arg4 = 'H' as u8,
/// For Messages
Error = b'L',
Error = 'L' as u8,
/// For Messages: space-separated list of messaged IDs of forwarded copies.
PrepForwards = b'P',
PrepForwards = 'P' as u8,
/// For Jobs
SetLatitude = b'l',
SetLatitude = 'l' as u8,
/// For Jobs
SetLongitude = b'n',
SetLongitude = 'n' as u8,
/// For Jobs
ServerFolder = b'Z',
ServerFolder = 'Z' as u8,
/// For Jobs
ServerUid = b'z',
ServerUid = 'z' as u8,
/// For Jobs
AlsoMove = b'M',
AlsoMove = 'M' as u8,
/// For Jobs: space-separated list of message recipients
Recipients = b'R',
Recipients = 'R' as u8,
// For Groups
Unpromoted = b'U',
Unpromoted = 'U' as u8,
// For Groups and Contacts
ProfileImage = b'i',
ProfileImage = 'i' as u8,
// For Chats
Selftalk = b'K',
Selftalk = 'K' as u8,
// For QR
Auth = b's',
Auth = 's' as u8,
// For QR
GroupId = b'x',
GroupId = 'x' as u8,
// For QR
GroupName = b'g',
GroupName = 'g' as u8,
}
/// Possible values for `Param::ForcePlaintext`.
@@ -122,8 +122,8 @@ impl str::FromStr for Params {
ensure!(key.is_some(), "Missing key");
ensure!(value.is_some(), "Missing value");
let key = key.unwrap_or_default().trim();
let value = value.unwrap_or_default().trim();
let key = key.unwrap().trim();
let value = value.unwrap().trim();
if let Some(key) = Param::from_u8(key.as_bytes()[0]) {
inner.insert(key, value.to_string());

View File

@@ -7,7 +7,6 @@ use crate::aheader::*;
use crate::chat::*;
use crate::constants::*;
use crate::context::Context;
use crate::error::*;
use crate::key::*;
use crate::sql::{self, Sql};
@@ -23,7 +22,7 @@ pub struct Peerstate<'a> {
pub gossip_key: Option<Key>,
pub gossip_timestamp: i64,
pub gossip_key_fingerprint: Option<String>,
pub verified_key: Option<Key>,
verified_key: VerifiedKey,
pub verified_key_fingerprint: Option<String>,
pub to_save: Option<ToSave>,
pub degrade_event: Option<DegradeEvent>,
@@ -85,6 +84,32 @@ pub enum DegradeEvent {
FingerprintChanged = 0x02,
}
#[derive(Debug, Copy, Clone, PartialEq, Eq)]
enum VerifiedKey {
Gossip,
Public,
None,
}
impl Default for VerifiedKey {
fn default() -> Self {
VerifiedKey::None
}
}
impl VerifiedKey {
pub fn is_none(&self) -> bool {
match self {
VerifiedKey::None => true,
_ => false,
}
}
pub fn is_some(&self) -> bool {
!self.is_none()
}
}
impl<'a> Peerstate<'a> {
pub fn new(context: &'a Context) -> Self {
Peerstate {
@@ -98,13 +123,21 @@ impl<'a> Peerstate<'a> {
gossip_key: None,
gossip_key_fingerprint: None,
gossip_timestamp: 0,
verified_key: None,
verified_key: Default::default(),
verified_key_fingerprint: None,
to_save: None,
degrade_event: None,
}
}
pub fn verified_key(&self) -> Option<&Key> {
match self.verified_key {
VerifiedKey::Public => self.public_key.as_ref(),
VerifiedKey::Gossip => self.gossip_key.as_ref(),
VerifiedKey::None => None,
}
}
pub fn from_header(context: &'a Context, header: &Aheader, message_time: i64) -> Self {
let mut res = Self::new(context);
@@ -208,11 +241,19 @@ impl<'a> Peerstate<'a> {
.get(6)
.ok()
.and_then(|blob: Vec<u8>| Key::from_slice(&blob, KeyType::Public));
res.verified_key = row
let vk = row
.get(9)
.ok()
.and_then(|blob: Vec<u8>| Key::from_slice(&blob, KeyType::Public));
res.verified_key = if vk == res.gossip_key && res.gossip_key.is_some() {
VerifiedKey::Gossip
} else if vk == res.public_key {
VerifiedKey::Public
} else {
VerifiedKey::None
};
Ok(res)
})
.ok()
@@ -332,7 +373,7 @@ impl<'a> Peerstate<'a> {
}
if 0 != min_verified {
return self.verified_key.as_ref();
return self.verified_key();
}
if self.public_key.is_some() {
return self.public_key.as_ref();
@@ -349,7 +390,7 @@ impl<'a> Peerstate<'a> {
&& self.public_key_fingerprint.as_ref().unwrap() == fingerprint
{
self.to_save = Some(ToSave::All);
self.verified_key = self.public_key.clone();
self.verified_key = VerifiedKey::Public;
self.verified_key_fingerprint = self.public_key_fingerprint.clone();
success = true;
}
@@ -358,7 +399,7 @@ impl<'a> Peerstate<'a> {
&& self.gossip_key_fingerprint.as_ref().unwrap() == fingerprint
{
self.to_save = Some(ToSave::All);
self.verified_key = self.gossip_key.clone();
self.verified_key = VerifiedKey::Gossip;
self.verified_key_fingerprint = self.gossip_key_fingerprint.clone();
success = true;
}
@@ -367,19 +408,28 @@ impl<'a> Peerstate<'a> {
success
}
pub fn save_to_db(&self, sql: &Sql, create: bool) -> Result<()> {
ensure!(!self.addr.is_none(), "self.addr is not configured");
pub fn save_to_db(&self, sql: &Sql, create: bool) -> bool {
let mut success = false;
if self.addr.is_none() {
return success;
}
if create {
sql::execute(
if sql::execute(
self.context,
sql,
"INSERT INTO acpeerstates (addr) VALUES(?);",
params![self.addr.as_ref().unwrap()],
)?;
)
.is_err()
{
return false;
}
}
if self.to_save == Some(ToSave::All) || create {
sql::execute(
success = sql::execute(
self.context,
sql,
"UPDATE acpeerstates \
@@ -396,14 +446,14 @@ impl<'a> Peerstate<'a> {
self.gossip_key.as_ref().map(|k| k.to_bytes()),
&self.public_key_fingerprint,
&self.gossip_key_fingerprint,
self.verified_key.as_ref().map(|k| k.to_bytes()),
self.verified_key().map(|k| k.to_bytes()),
&self.verified_key_fingerprint,
&self.addr,
],
)?;
reset_gossiped_timestamp(self.context, 0);
).is_ok();
assert_eq!(success, true);
} else if self.to_save == Some(ToSave::Timestamps) {
sql::execute(
success = sql::execute(
self.context,
sql,
"UPDATE acpeerstates SET last_seen=?, last_seen_autocrypt=?, gossip_timestamp=? \
@@ -414,10 +464,15 @@ impl<'a> Peerstate<'a> {
self.gossip_timestamp,
&self.addr
],
)?;
)
.is_ok();
}
Ok(())
if self.to_save == Some(ToSave::All) || create {
reset_gossiped_timestamp(self.context, 0);
}
success
}
pub fn has_verified_key(&self, fingerprints: &HashSet<String>) -> bool {
@@ -461,18 +516,15 @@ mod tests {
gossip_key: Some(pub_key.clone()),
gossip_timestamp: 12,
gossip_key_fingerprint: Some(pub_key.fingerprint()),
verified_key: Some(pub_key.clone()),
verified_key: VerifiedKey::Gossip,
verified_key_fingerprint: Some(pub_key.fingerprint()),
to_save: Some(ToSave::All),
degrade_event: None,
};
assert!(
peerstate.save_to_db(&ctx.ctx.sql, true).is_ok(),
"failed to save to db"
);
assert!(peerstate.save_to_db(&ctx.ctx.sql, true), "failed to save");
let peerstate_new = Peerstate::from_addr(&ctx.ctx, &ctx.ctx.sql, addr)
let peerstate_new = Peerstate::from_addr(&ctx.ctx, &ctx.ctx.sql, addr.into())
.expect("failed to load peerstate from db");
// clear to_save, as that is not persissted
@@ -484,44 +536,6 @@ mod tests {
assert_eq!(peerstate, peerstate_new2);
}
#[test]
fn test_peerstate_double_create() {
let ctx = crate::test_utils::dummy_context();
let addr = "hello@mail.com";
let pub_key = crate::key::Key::from_base64(
include_str!("../test-data/key/public.asc"),
KeyType::Public,
)
.unwrap();
let peerstate = Peerstate {
context: &ctx.ctx,
addr: Some(addr.into()),
last_seen: 10,
last_seen_autocrypt: 11,
prefer_encrypt: EncryptPreference::Mutual,
public_key: Some(pub_key.clone()),
public_key_fingerprint: Some(pub_key.fingerprint()),
gossip_key: None,
gossip_timestamp: 12,
gossip_key_fingerprint: None,
verified_key: None,
verified_key_fingerprint: None,
to_save: Some(ToSave::All),
degrade_event: None,
};
assert!(
peerstate.save_to_db(&ctx.ctx.sql, true).is_ok(),
"failed to save"
);
assert!(
peerstate.save_to_db(&ctx.ctx.sql, true).is_ok(),
"double-call with create failed"
);
}
#[test]
fn test_peerstate_with_empty_gossip_key_save_to_db() {
let ctx = crate::test_utils::dummy_context();
@@ -544,18 +558,15 @@ mod tests {
gossip_key: None,
gossip_timestamp: 12,
gossip_key_fingerprint: None,
verified_key: None,
verified_key: VerifiedKey::None,
verified_key_fingerprint: None,
to_save: Some(ToSave::All),
degrade_event: None,
};
assert!(
peerstate.save_to_db(&ctx.ctx.sql, true).is_ok(),
"failed to save"
);
assert!(peerstate.save_to_db(&ctx.ctx.sql, true), "failed to save");
let peerstate_new = Peerstate::from_addr(&ctx.ctx, &ctx.ctx.sql, addr)
let peerstate_new = Peerstate::from_addr(&ctx.ctx, &ctx.ctx.sql, addr.into())
.expect("failed to load peerstate from db");
// clear to_save, as that is not persissted

View File

@@ -19,7 +19,7 @@ use crate::keyring::*;
pub unsafe fn dc_split_armored_data(
buf: *mut libc::c_char,
ret_headerline: *mut String,
ret_headerline: *mut *const libc::c_char,
ret_setupcodebegin: *mut *const libc::c_char,
ret_preferencrypt: *mut *const libc::c_char,
ret_base64: *mut *const libc::c_char,
@@ -31,6 +31,9 @@ pub unsafe fn dc_split_armored_data(
let mut p2: *mut libc::c_char;
let mut headerline: *mut libc::c_char = ptr::null_mut();
let mut base64: *mut libc::c_char = ptr::null_mut();
if !ret_headerline.is_null() {
*ret_headerline = ptr::null()
}
if !ret_setupcodebegin.is_null() {
*ret_setupcodebegin = ptr::null_mut();
}
@@ -40,10 +43,10 @@ pub unsafe fn dc_split_armored_data(
if !ret_base64.is_null() {
*ret_base64 = ptr::null();
}
if !buf.is_null() {
if !(buf.is_null() || ret_headerline.is_null()) {
dc_remove_cr_chars(buf);
while 0 != *p1 {
if i32::from(*p1) == '\n' as i32 {
if *p1 as libc::c_int == '\n' as i32 {
*line.offset(line_chars as isize) = 0i32 as libc::c_char;
if headerline.is_null() {
dc_trim(line);
@@ -59,7 +62,9 @@ pub unsafe fn dc_split_armored_data(
) == 0i32
{
headerline = line;
*ret_headerline = as_str(headerline).to_string();
if !ret_headerline.is_null() {
*ret_headerline = headerline
}
}
} else if strspn(line, b"\t\r\n \x00" as *const u8 as *const libc::c_char)
== strlen(line)
@@ -69,7 +74,7 @@ pub unsafe fn dc_split_armored_data(
} else {
p2 = strchr(line, ':' as i32);
if p2.is_null() {
*line.add(line_chars) = '\n' as i32 as libc::c_char;
*line.offset(line_chars as isize) = '\n' as i32 as libc::c_char;
base64 = line;
break;
} else {
@@ -189,7 +194,7 @@ pub fn dc_pgp_pk_encrypt(
let lit_msg = Message::new_literal_bytes("", plain);
let pkeys: Vec<&SignedPublicKey> = public_keys_for_encryption
.keys()
.iter()
.into_iter()
.filter_map(|key| {
let k: &Key = &key;
k.try_into().ok()

View File

@@ -82,7 +82,10 @@ pub fn dc_get_securejoin_qr(context: &Context, group_chat_id: u32) -> Option<Str
}
};
let self_name = context.get_config(Config::Displayname).unwrap_or_default();
let self_name = context
.sql
.get_config(context, "displayname")
.unwrap_or_default();
fingerprint = match get_self_fingerprint(context) {
Some(fp) => fp,
@@ -210,7 +213,7 @@ pub fn dc_join_securejoin(context: &Context, qr: &str) -> u32 {
info!(context, "Taking protocol shortcut.");
context.bob.write().unwrap().expects = DC_VC_CONTACT_CONFIRM;
joiner_progress!(context, chat_id_2_contact_id(context, contact_chat_id), 400);
let own_fingerprint = get_self_fingerprint(context).unwrap_or_default();
let own_fingerprint = get_self_fingerprint(context).unwrap();
send_handshake_msg(
context,
contact_chat_id,
@@ -291,7 +294,7 @@ fn send_handshake_msg(
msg.param.set_int(Param::GuranteeE2ee, 1);
}
// TODO. handle cleanup on error
chat::send_msg(context, contact_chat_id, &mut msg).unwrap_or_default();
chat::send_msg(context, contact_chat_id, &mut msg).unwrap();
}
fn chat_id_2_contact_id(context: &Context, contact_chat_id: u32) -> u32 {
@@ -410,7 +413,7 @@ pub fn handle_securejoin_handshake(
could_not_establish_secure_connection(
context,
contact_chat_id,
if mimeparser.encrypted {
if mimeparser.e2ee_helper.encrypted {
"No valid signature."
} else {
"Not encrypted."
@@ -519,9 +522,7 @@ pub fn handle_securejoin_handshake(
error!(context, "Chat {} not found.", &field_grpid);
return ret;
} else {
if let Err(err) =
chat::add_contact_to_chat_ex(context, group_chat_id, contact_id, true)
{
if let Err(err) = chat::add_contact_to_chat_ex(context, group_chat_id, contact_id, true) {
error!(context, "failed to add contact: {}", err);
}
}
@@ -675,9 +676,7 @@ fn mark_peer_as_verified(context: &Context, fingerprint: impl AsRef<str>) -> Res
if peerstate.set_verified(1, fingerprint.as_ref(), 2) {
peerstate.prefer_encrypt = EncryptPreference::Mutual;
peerstate.to_save = Some(ToSave::All);
peerstate
.save_to_db(&context.sql, false)
.unwrap_or_default();
peerstate.save_to_db(&context.sql, false);
return Ok(());
}
}
@@ -692,16 +691,17 @@ fn mark_peer_as_verified(context: &Context, fingerprint: impl AsRef<str>) -> Res
******************************************************************************/
fn encrypted_and_signed(mimeparser: &MimeParser, expected_fingerprint: impl AsRef<str>) -> bool {
if !mimeparser.encrypted {
if !mimeparser.e2ee_helper.encrypted {
warn!(mimeparser.context, "Message not encrypted.",);
false
} else if mimeparser.signatures.is_empty() {
} else if mimeparser.e2ee_helper.signatures.len() <= 0 {
warn!(mimeparser.context, "Message not signed.",);
false
} else if expected_fingerprint.as_ref().is_empty() {
warn!(mimeparser.context, "Fingerprint for comparison missing.",);
false
} else if !mimeparser
.e2ee_helper
.signatures
.contains(expected_fingerprint.as_ref())
{
@@ -716,24 +716,21 @@ fn encrypted_and_signed(mimeparser: &MimeParser, expected_fingerprint: impl AsRe
}
}
pub fn handle_degrade_event(context: &Context, peerstate: &Peerstate) -> Result<(), Error> {
pub fn handle_degrade_event(context: &Context, peerstate: &Peerstate) {
// - we do not issue an warning for DC_DE_ENCRYPTION_PAUSED as this is quite normal
// - currently, we do not issue an extra warning for DC_DE_VERIFICATION_LOST - this always comes
// together with DC_DE_FINGERPRINT_CHANGED which is logged, the idea is not to bother
// with things they cannot fix, so the user is just kicked from the verified group
// (and he will know this and can fix this)
if Some(DegradeEvent::FingerprintChanged) == peerstate.degrade_event {
let contact_id: i32 = match context.sql.query_get_value(
context,
"SELECT id FROM contacts WHERE addr=?;",
params![&peerstate.addr],
) {
None => bail!(
"contact with peerstate.addr {:?} not found",
&peerstate.addr
),
Some(contact_id) => contact_id,
};
let contact_id: i32 = context
.sql
.query_get_value(
context,
"SELECT id FROM contacts WHERE addr=?;",
params![&peerstate.addr],
)
.unwrap_or_default();
if contact_id > 0 {
let (contact_chat_id, _) =
chat::create_or_lookup_by_contact_id(context, contact_id as u32, Blocked::Deaddrop)
@@ -749,5 +746,4 @@ pub fn handle_degrade_event(context: &Context, peerstate: &Peerstate) -> Result<
emit_event!(context, Event::ChatModified(contact_chat_id));
}
}
Ok(())
}

View File

@@ -3,9 +3,8 @@ use lettre::*;
use crate::constants::*;
use crate::context::Context;
use crate::error::Error;
use crate::events::Event;
use crate::login_param::{dc_build_tls, LoginParam};
use crate::login_param::LoginParam;
use crate::oauth2::*;
#[derive(DebugStub)]
@@ -15,6 +14,7 @@ pub struct Smtp {
transport_connected: bool,
/// Email address we are sending from.
from: Option<EmailAddress>,
pub error: Option<String>,
}
impl Smtp {
@@ -24,6 +24,7 @@ impl Smtp {
transport: None,
transport_connected: false,
from: None,
error: None,
}
}
@@ -68,7 +69,13 @@ impl Smtp {
let domain = &lp.send_server;
let port = lp.send_port as u16;
let tls = dc_build_tls(lp.smtp_certificate_checks).unwrap();
let tls = native_tls::TlsConnector::builder()
// FIXME: unfortunately this is needed to make things work on macos + testrun.org
.danger_accept_invalid_hostnames(true)
.min_protocol_version(Some(DEFAULT_TLS_PROTOCOLS[0]))
.build()
.unwrap();
let tls_parameters = ClientTlsParameters::new(domain.to_string(), tls);
let creds = if 0 != lp.server_flags & (DC_LP_AUTH_OAUTH2 as i32) {
@@ -81,10 +88,7 @@ impl Smtp {
}
let user = &lp.send_user;
lettre::smtp::authentication::Credentials::new(
user.to_string(),
access_token.unwrap_or_default(),
)
lettre::smtp::authentication::Credentials::new(user.to_string(), access_token.unwrap())
} else {
// plain
let user = lp.send_user.clone();
@@ -120,50 +124,37 @@ impl Smtp {
}
}
/// SMTP-Send a prepared mail to recipients.
/// returns boolean whether send was successful.
pub fn send<'a>(
&mut self,
context: &Context,
recipients: Vec<EmailAddress>,
message: Vec<u8>,
) -> Result<(), Error> {
let message_len = message.len();
let recipients_display = recipients
.iter()
.map(|x| format!("{}", x))
.collect::<Vec<String>>()
.join(",");
body: Vec<u8>,
) -> usize {
if let Some(ref mut transport) = self.transport {
let envelope = Envelope::new(self.from.clone(), recipients);
ensure!(envelope.is_ok(), "internal smtp-message construction fail");
let envelope = envelope.unwrap();
let envelope = Envelope::new(self.from.clone(), recipients).expect("invalid envelope");
let mail = SendableEmail::new(
envelope,
"mail-id".into(), // TODO: random id
message,
body,
);
match transport.send(mail) {
Ok(_) => {
context.call_cb(Event::SmtpMessageSent(format!(
"Message len={} was smtp-sent to {}",
message_len, recipients_display
)));
context.call_cb(Event::SmtpMessageSent(
"Message was sent to SMTP server".into(),
));
self.transport_connected = true;
Ok(())
1
}
Err(err) => {
bail!("SMTP failed len={}: error: {}", message_len, err);
warn!(context, "SMTP failed to send message: {}", err);
self.error = Some(format!("{}", err));
0
}
}
} else {
bail!(
"uh? SMTP has no transport, failed to send to {:?}",
recipients_display
);
// TODO: log error
0
}
}
}

View File

@@ -1,6 +1,5 @@
use std::collections::HashSet;
use std::sync::{Arc, RwLock};
use std::time::Duration;
use rusqlite::{Connection, OpenFlags, Statement, NO_PARAMS};
use thread_local_object::ThreadLocal;
@@ -69,16 +68,6 @@ impl Sql {
let res = match &*self.pool.read().unwrap() {
Some(pool) => {
let conn = pool.get()?;
// Only one process can make changes to the database at one time.
// busy_timeout defines, that if a seconds process wants write access,
// this second process will wait some milliseconds
// and try over until it gets write access or the given timeout is elapsed.
// If the second process does not get write access within the given timeout,
// sqlite3_step() will return the error SQLITE_BUSY.
// (without a busy_timeout, sqlite3_step() would return SQLITE_BUSY _at once_)
conn.busy_timeout(Duration::from_secs(10))?;
g(&conn)
}
None => Err(Error::SqlNoConnection),
@@ -193,14 +182,14 @@ impl Sql {
///
/// Setting `None` deletes the value. On failure an error message
/// will already have been logged.
pub fn set_raw_config(
pub fn set_config(
&self,
context: &Context,
key: impl AsRef<str>,
value: Option<&str>,
) -> Result<()> {
if !self.is_open() {
error!(context, "set_raw_config(): Database not ready.");
error!(context, "set_config(): Database not ready.");
return Err(Error::SqlNoConnection);
}
@@ -234,14 +223,14 @@ impl Sql {
match res {
Ok(_) => Ok(()),
Err(err) => {
error!(context, "set_raw_config(): Cannot change value. {:?}", &err);
Err(err)
error!(context, "set_config(): Cannot change value. {:?}", &err);
Err(err.into())
}
}
}
/// Get configuration options from the database.
pub fn get_raw_config(&self, context: &Context, key: impl AsRef<str>) -> Option<String> {
pub fn get_config(&self, context: &Context, key: impl AsRef<str>) -> Option<String> {
if !self.is_open() || key.as_ref().is_empty() {
return None;
}
@@ -252,46 +241,44 @@ impl Sql {
)
}
pub fn set_raw_config_int(
pub fn set_config_int(
&self,
context: &Context,
key: impl AsRef<str>,
value: i32,
) -> Result<()> {
self.set_raw_config(context, key, Some(&format!("{}", value)))
self.set_config(context, key, Some(&format!("{}", value)))
}
pub fn get_raw_config_int(&self, context: &Context, key: impl AsRef<str>) -> Option<i32> {
self.get_raw_config(context, key)
.and_then(|s| s.parse().ok())
pub fn get_config_int(&self, context: &Context, key: impl AsRef<str>) -> Option<i32> {
self.get_config(context, key).and_then(|s| s.parse().ok())
}
pub fn get_raw_config_bool(&self, context: &Context, key: impl AsRef<str>) -> bool {
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_raw_config_int(context, key).unwrap_or_default() > 0
self.get_config_int(context, key).unwrap_or_default() > 0
}
pub fn set_raw_config_bool<T>(&self, context: &Context, key: T, value: bool) -> Result<()>
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_raw_config(context, key, value)
self.set_config(context, key, value)
}
pub fn set_raw_config_int64(
pub fn set_config_int64(
&self,
context: &Context,
key: impl AsRef<str>,
value: i64,
) -> Result<()> {
self.set_raw_config(context, key, Some(&format!("{}", value)))
self.set_config(context, key, Some(&format!("{}", value)))
}
pub fn get_raw_config_int64(&self, context: &Context, key: impl AsRef<str>) -> Option<i64> {
self.get_raw_config(context, key)
.and_then(|r| r.parse().ok())
pub fn get_config_int64(&self, context: &Context, key: impl AsRef<str>) -> Option<i64> {
self.get_config(context, key).and_then(|r| r.parse().ok())
}
fn start_stmt(&self, stmt: impl AsRef<str>) {
@@ -343,7 +330,7 @@ fn open(
.with_init(|c| c.execute_batch("PRAGMA secure_delete=on;"));
let pool = r2d2::Pool::builder()
.min_idle(Some(2))
.max_size(10)
.max_size(4)
.connection_timeout(std::time::Duration::new(60, 0))
.build(mgr)?;
@@ -479,13 +466,11 @@ fn open(
// cannot create the tables - maybe we cannot write?
return Err(Error::SqlFailedToOpen);
} else {
sql.set_raw_config_int(context, "dbversion", 0)?;
sql.set_config_int(context, "dbversion", 0)?;
}
} else {
exists_before_update = 1;
dbversion_before_update = sql
.get_raw_config_int(context, "dbversion")
.unwrap_or_default();
dbversion_before_update = sql.get_config_int(context, "dbversion").unwrap_or_default();
}
// (1) update low-level database structure.
@@ -497,7 +482,6 @@ fn open(
let mut update_file_paths = 0;
if dbversion < 1 {
info!(context, "[migration] v1");
sql.execute(
"CREATE TABLE leftgrps ( id INTEGER PRIMARY KEY, grpid TEXT DEFAULT '');",
params![],
@@ -507,19 +491,17 @@ fn open(
params![],
)?;
dbversion = 1;
sql.set_raw_config_int(context, "dbversion", 1)?;
sql.set_config_int(context, "dbversion", 1)?;
}
if dbversion < 2 {
info!(context, "[migration] v2");
sql.execute(
"ALTER TABLE contacts ADD COLUMN authname TEXT DEFAULT '';",
params![],
)?;
dbversion = 2;
sql.set_raw_config_int(context, "dbversion", 2)?;
sql.set_config_int(context, "dbversion", 2)?;
}
if dbversion < 7 {
info!(context, "[migration] v7");
sql.execute(
"CREATE TABLE keypairs (\
id INTEGER PRIMARY KEY, \
@@ -531,10 +513,9 @@ fn open(
params![],
)?;
dbversion = 7;
sql.set_raw_config_int(context, "dbversion", 7)?;
sql.set_config_int(context, "dbversion", 7)?;
}
if dbversion < 10 {
info!(context, "[migration] v10");
sql.execute(
"CREATE TABLE acpeerstates (\
id INTEGER PRIMARY KEY, \
@@ -550,10 +531,9 @@ fn open(
params![],
)?;
dbversion = 10;
sql.set_raw_config_int(context, "dbversion", 10)?;
sql.set_config_int(context, "dbversion", 10)?;
}
if dbversion < 12 {
info!(context, "[migration] v12");
sql.execute(
"CREATE TABLE msgs_mdns ( msg_id INTEGER, contact_id INTEGER);",
params![],
@@ -563,10 +543,9 @@ fn open(
params![],
)?;
dbversion = 12;
sql.set_raw_config_int(context, "dbversion", 12)?;
sql.set_config_int(context, "dbversion", 12)?;
}
if dbversion < 17 {
info!(context, "[migration] v17");
sql.execute(
"ALTER TABLE chats ADD COLUMN archived INTEGER DEFAULT 0;",
params![],
@@ -578,20 +557,18 @@ fn open(
)?;
sql.execute("CREATE INDEX msgs_index5 ON msgs (starred);", params![])?;
dbversion = 17;
sql.set_raw_config_int(context, "dbversion", 17)?;
sql.set_config_int(context, "dbversion", 17)?;
}
if dbversion < 18 {
info!(context, "[migration] v18");
sql.execute(
"ALTER TABLE acpeerstates ADD COLUMN gossip_timestamp INTEGER DEFAULT 0;",
params![],
)?;
sql.execute("ALTER TABLE acpeerstates ADD COLUMN gossip_key;", params![])?;
dbversion = 18;
sql.set_raw_config_int(context, "dbversion", 18)?;
sql.set_config_int(context, "dbversion", 18)?;
}
if dbversion < 27 {
info!(context, "[migration] v27");
sql.execute("DELETE FROM msgs WHERE chat_id=1 OR chat_id=2;", params![])?;
sql.execute(
"CREATE INDEX chats_contacts_index2 ON chats_contacts (contact_id);",
@@ -606,10 +583,9 @@ fn open(
params![],
)?;
dbversion = 27;
sql.set_raw_config_int(context, "dbversion", 27)?;
sql.set_config_int(context, "dbversion", 27)?;
}
if dbversion < 34 {
info!(context, "[migration] v34");
sql.execute(
"ALTER TABLE msgs ADD COLUMN hidden INTEGER DEFAULT 0;",
params![],
@@ -636,10 +612,9 @@ fn open(
)?;
recalc_fingerprints = 1;
dbversion = 34;
sql.set_raw_config_int(context, "dbversion", 34)?;
sql.set_config_int(context, "dbversion", 34)?;
}
if dbversion < 39 {
info!(context, "[migration] v39");
sql.execute(
"CREATE TABLE tokens ( id INTEGER PRIMARY KEY, namespc INTEGER DEFAULT 0, foreign_id INTEGER DEFAULT 0, token TEXT DEFAULT '', timestamp INTEGER DEFAULT 0);",
params![]
@@ -667,37 +642,32 @@ fn open(
)?;
}
dbversion = 39;
sql.set_raw_config_int(context, "dbversion", 39)?;
sql.set_config_int(context, "dbversion", 39)?;
}
if dbversion < 40 {
info!(context, "[migration] v40");
sql.execute(
"ALTER TABLE jobs ADD COLUMN thread INTEGER DEFAULT 0;",
params![],
)?;
dbversion = 40;
sql.set_raw_config_int(context, "dbversion", 40)?;
sql.set_config_int(context, "dbversion", 40)?;
}
if dbversion < 41 {
info!(context, "[migration] v41");
update_file_paths = 1;
dbversion = 41;
sql.set_raw_config_int(context, "dbversion", 41)?;
sql.set_config_int(context, "dbversion", 41)?;
}
if dbversion < 42 {
info!(context, "[migration] v42");
sql.execute("UPDATE msgs SET txt='' WHERE type!=10", params![])?;
dbversion = 42;
sql.set_raw_config_int(context, "dbversion", 42)?;
sql.set_config_int(context, "dbversion", 42)?;
}
if dbversion < 44 {
info!(context, "[migration] v44");
sql.execute("ALTER TABLE msgs ADD COLUMN mime_headers TEXT;", params![])?;
dbversion = 44;
sql.set_raw_config_int(context, "dbversion", 44)?;
sql.set_config_int(context, "dbversion", 44)?;
}
if dbversion < 46 {
info!(context, "[migration] v46");
sql.execute(
"ALTER TABLE msgs ADD COLUMN mime_in_reply_to TEXT;",
params![],
@@ -707,7 +677,7 @@ fn open(
params![],
)?;
dbversion = 46;
sql.set_raw_config_int(context, "dbversion", 46)?;
sql.set_config_int(context, "dbversion", 46)?;
}
if dbversion < 47 {
info!(context, "[migration] v47");
@@ -716,7 +686,7 @@ fn open(
params![],
)?;
dbversion = 47;
sql.set_raw_config_int(context, "dbversion", 47)?;
sql.set_config_int(context, "dbversion", 47)?;
}
if dbversion < 48 {
info!(context, "[migration] v48");
@@ -726,7 +696,7 @@ fn open(
)?;
dbversion = 48;
sql.set_raw_config_int(context, "dbversion", 48)?;
sql.set_config_int(context, "dbversion", 48)?;
}
if dbversion < 49 {
info!(context, "[migration] v49");
@@ -735,15 +705,15 @@ fn open(
params![],
)?;
dbversion = 49;
sql.set_raw_config_int(context, "dbversion", 49)?;
sql.set_config_int(context, "dbversion", 49)?;
}
if dbversion < 50 {
info!(context, "[migration] v50");
if 0 != exists_before_update {
sql.set_raw_config_int(context, "show_emails", 2)?;
sql.set_config_int(context, "show_emails", 2)?;
}
dbversion = 50;
sql.set_raw_config_int(context, "dbversion", 50)?;
sql.set_config_int(context, "dbversion", 50)?;
}
if dbversion < 53 {
info!(context, "[migration] v53");
@@ -776,7 +746,7 @@ fn open(
params![],
)?;
dbversion = 53;
sql.set_raw_config_int(context, "dbversion", 53)?;
sql.set_config_int(context, "dbversion", 53)?;
}
if dbversion < 54 {
info!(context, "[migration] v54");
@@ -786,20 +756,18 @@ fn open(
)?;
sql.execute("CREATE INDEX msgs_index6 ON msgs (location_id);", params![])?;
dbversion = 54;
sql.set_raw_config_int(context, "dbversion", 54)?;
sql.set_config_int(context, "dbversion", 54)?;
}
if dbversion < 55 {
info!(context, "[migration] v55");
sql.execute(
"ALTER TABLE locations ADD COLUMN independent INTEGER DEFAULT 0;",
params![],
)?;
sql.set_raw_config_int(context, "dbversion", 55)?;
sql.set_config_int(context, "dbversion", 55)?;
}
if 0 != recalc_fingerprints {
info!(context, "[migration] recalc fingerprints");
sql.query_map(
"SELECT addr FROM acpeerstates;",
params![],
@@ -809,7 +777,7 @@ fn open(
if let Some(ref mut peerstate) = Peerstate::from_addr(context, sql, &addr?)
{
peerstate.recalc_fingerprint();
peerstate.save_to_db(sql, false)?;
peerstate.save_to_db(sql, false);
}
}
Ok(())
@@ -820,9 +788,11 @@ fn open(
// versions before 2018-08 save the absolute paths in the database files at "param.f=";
// for newer versions, we copy files always to the blob directory and store relative paths.
// this snippet converts older databases and can be removed after some time.
info!(context, "[migration] update file paths");
info!(context, "[open] update file paths");
let repl_from = sql
.get_raw_config(context, "backup_for")
.get_config(context, "backup_for")
.unwrap_or_else(|| context.get_blobdir().to_string_lossy().into());
let repl_from = dc_ensure_no_slash_safe(&repl_from);
@@ -842,7 +812,7 @@ fn open(
NO_PARAMS,
)?;
sql.set_raw_config(context, "backup_for", None)?;
sql.set_config(context, "backup_for", None)?;
}
}
@@ -1163,8 +1133,12 @@ mod test {
maybe_add_file(&mut files, "$BLOBDIR/world.txt");
maybe_add_file(&mut files, "world2.txt");
assert!(is_file_in_use(&files, None, "hello"));
assert!(!is_file_in_use(&files, Some(".txt"), "hello"));
assert!(is_file_in_use(&files, Some("-suffix"), "world.txt-suffix"));
assert!(is_file_in_use(&mut files, None, "hello"));
assert!(!is_file_in_use(&mut files, Some(".txt"), "hello"));
assert!(is_file_in_use(
&mut files,
Some("-suffix"),
"world.txt-suffix"
));
}
}

View File

@@ -109,8 +109,6 @@ pub enum StockMessage {
MsgLocationDisabled = 65,
#[strum(props(fallback = "Location"))]
Location = 66,
#[strum(props(fallback = "Sticker"))]
Sticker = 67,
}
impl StockMessage {
@@ -118,7 +116,7 @@ impl StockMessage {
///
/// These could be used in logging calls, so no logging here.
fn fallback(&self) -> &'static str {
self.get_str("fallback").unwrap_or_default()
self.get_str("fallback").unwrap()
}
}
@@ -133,7 +131,7 @@ impl Context {
if ptr.is_null() {
Cow::Borrowed(id.fallback())
} else {
let ret = to_string_lossy(ptr);
let ret = to_string(ptr);
unsafe { libc::free(ptr as *mut libc::c_void) };
Cow::Owned(ret)
}
@@ -351,7 +349,9 @@ mod tests {
let contact_id = {
Contact::create(&t.ctx, "Alice", "alice@example.com")
.expect("Failed to create contact Alice");
Contact::create(&t.ctx, "Bob", "bob@example.com").expect("failed to create bob")
let id =
Contact::create(&t.ctx, "Bob", "bob@example.com").expect("failed to create bob");
id
};
assert_eq!(
t.ctx.stock_system_msg(

View File

@@ -76,7 +76,7 @@ pub fn configure_alice_keypair(ctx: &Context) -> String {
KeyType::Private,
)
.unwrap();
let saved = key::dc_key_save_self_keypair(&ctx, &public, &private, &addr, true, &ctx.sql);
let saved = key::dc_key_save_self_keypair(&ctx, &public, &private, &addr, 1, &ctx.sql);
assert_eq!(saved, true, "Failed to save Alice's key");
addr
}

View File

@@ -21,8 +21,6 @@ if __name__ == "__main__":
sum_unsafe, sum_free, sum_gotoblocks, sum_chars = 0, 0, 0, 0
for fn, unsafe, free, gotoblocks, chars in reversed(sorted(filestats, key=lambda x: sum(x[1:]))):
if unsafe + free + gotoblocks + chars == 0:
continue
print("{0: <25} unsafe: {1: >3} free: {2: >3} ok_to_cont: {3: >3} chars: {4: >3}".format(str(fn), unsafe, free, gotoblocks, chars))
sum_unsafe += unsafe
sum_free += free

View File

@@ -1,22 +1,14 @@
use std::collections::HashSet;
use std::ffi::CString;
use std::ptr;
use crate::contact::addr_normalize;
use crate::dc_strencode::*;
use crate::dc_tools::*;
use crate::error::Error;
use mmime::clist::*;
// use mmime::display::*;
use mmime::mailimf::mailimf_msg_id_parse;
use mmime::mailimf::types::*;
use mmime::mailimf::types_helper::*;
use mmime::mailmime::content::*;
use mmime::mailmime::disposition::*;
use mmime::mailmime::types::*;
use mmime::mailmime::types_helper::*;
use mmime::mailimf_types::*;
use mmime::mailimf_types_helper::*;
use mmime::mailmime::*;
use mmime::mmapstring::*;
use mmime::mailmime_disposition::*;
use mmime::mailmime_types::*;
use mmime::mailmime_types_helper::*;
use mmime::other::*;
#[macro_export]
@@ -33,369 +25,8 @@ macro_rules! clist_append {
};
}
/**************************************
* mime parsing API
**************************************/
pub fn get_ct_subtype(mime: *mut Mailmime) -> Option<String> {
unsafe {
let ct: *mut mailmime_content = (*mime).mm_content_type;
if !ct.is_null() && !(*ct).ct_subtype.is_null() {
Some(to_string_lossy((*ct).ct_subtype))
} else {
None
}
}
}
pub fn parse_message_id(message_id: &str) -> Result<String, Error> {
let mut dummy = 0;
let c_message_id = CString::new(message_id).unwrap_or_default();
let c_ptr = c_message_id.as_ptr();
let mut rfc724_mid_c = std::ptr::null_mut();
if unsafe { mailimf_msg_id_parse(c_ptr, libc::strlen(c_ptr), &mut dummy, &mut rfc724_mid_c) }
== MAIL_NO_ERROR as libc::c_int
&& !rfc724_mid_c.is_null()
{
let res = to_string_lossy(rfc724_mid_c);
unsafe { libc::free(rfc724_mid_c.cast()) };
Ok(res)
} else {
bail!("could not parse message_id: {}", message_id);
}
}
pub fn get_autocrypt_mime(
mime_undetermined: *mut Mailmime,
) -> Result<(*mut Mailmime, *mut Mailmime), Error> {
/* return Result with two mime pointers:
First mime pointer is to the multipart-mime message
(which is replaced with a decrypted version later)
Second one is to the encrypted payload.
For non-autocrypt message an Error is returned.
*/
unsafe {
ensure!(
(*mime_undetermined).mm_type == MAILMIME_MESSAGE as libc::c_int,
"Not a root mime message"
);
let mime = (*mime_undetermined).mm_data.mm_message.mm_msg_mime;
ensure!(
(*mime).mm_type == MAILMIME_MULTIPLE as libc::c_int
&& "encrypted" == get_ct_subtype(mime).unwrap_or_default(),
"Not a multipart/encrypted message"
);
let parts: Vec<_> = (*(*mime).mm_data.mm_multipart.mm_mp_list)
.into_iter()
.map(|c| c as *mut Mailmime)
.collect();
ensure!(parts.len() == 2, "Invalid Autocrypt Level 1 Mime Parts");
// XXX ensure protocol-parameter "application/pgp-encrypted")
// XXX ensure wrapmime::get_content_type(parts[1])) == "application/octetstream"
// a proper OpenPGP multipart/encrypted Autocrypt Level 1 message
// https://tools.ietf.org/html/rfc3156.html#section-4
Ok((mime, parts[1]))
}
}
pub fn has_decryptable_data(mime_data: *mut mailmime_data) -> bool {
/* MAILMIME_DATA_FILE indicates, the data is in a file; AFAIK this is not used on parsing */
unsafe {
(*mime_data).dt_type == MAILMIME_DATA_TEXT as libc::c_int
&& !(*mime_data).dt_data.dt_text.dt_data.is_null()
&& (*mime_data).dt_data.dt_text.dt_length > 0
}
}
pub fn get_field_from(imffields: *mut mailimf_fields) -> Result<String, Error> {
let field = mailimf_find_field(imffields, MAILIMF_FIELD_FROM as libc::c_int);
if !field.is_null() && unsafe { !(*field).fld_data.fld_from.is_null() } {
let mb_list = unsafe { (*(*field).fld_data.fld_from).frm_mb_list };
if let Some(addr) = mailimf_find_first_addr(mb_list) {
return Ok(addr);
}
}
bail!("not From field found");
}
pub fn get_field_date(imffields: *mut mailimf_fields) -> Result<i64, Error> {
let field = mailimf_find_field(imffields, MAILIMF_FIELD_ORIG_DATE as libc::c_int);
let mut message_time = 0;
if !field.is_null() && unsafe { !(*field).fld_data.fld_orig_date.is_null() } {
let orig_date = unsafe { (*field).fld_data.fld_orig_date };
if !orig_date.is_null() {
let dt = unsafe { (*orig_date).dt_date_time };
message_time = dc_timestamp_from_date(dt);
if message_time != 0 && message_time > time() {
message_time = time()
}
}
}
Ok(message_time)
}
fn mailimf_get_recipients_add_addr(recipients: &mut HashSet<String>, mb: *mut mailimf_mailbox) {
if !mb.is_null() {
let addr_norm = addr_normalize(as_str(unsafe { (*mb).mb_addr_spec }));
recipients.insert(addr_norm.into());
}
}
/*the result is a pointer to mime, must not be freed*/
pub fn mailimf_find_field(
header: *mut mailimf_fields,
wanted_fld_type: libc::c_int,
) -> *mut mailimf_field {
if header.is_null() {
return ptr::null_mut();
}
let header = unsafe { (*header) };
if header.fld_list.is_null() {
return ptr::null_mut();
}
for cur in unsafe { &(*header.fld_list) } {
let field = cur as *mut mailimf_field;
if !field.is_null() {
if unsafe { (*field).fld_type } == wanted_fld_type {
return field;
}
}
}
ptr::null_mut()
}
/*the result is a pointer to mime, must not be freed*/
pub unsafe fn mailmime_find_mailimf_fields(mime: *mut Mailmime) -> *mut mailimf_fields {
if mime.is_null() {
return ptr::null_mut();
}
match (*mime).mm_type as _ {
MAILMIME_MULTIPLE => {
for cur_data in (*(*mime).mm_data.mm_multipart.mm_mp_list).into_iter() {
let header = mailmime_find_mailimf_fields(cur_data as *mut _);
if !header.is_null() {
return header;
}
}
}
MAILMIME_MESSAGE => return (*mime).mm_data.mm_message.mm_fields,
_ => {}
}
ptr::null_mut()
}
pub unsafe fn mailimf_find_optional_field(
header: *mut mailimf_fields,
wanted_fld_name: *const libc::c_char,
) -> *mut mailimf_optional_field {
if header.is_null() || (*header).fld_list.is_null() {
return ptr::null_mut();
}
for cur_data in (*(*header).fld_list).into_iter() {
let field: *mut mailimf_field = cur_data as *mut _;
if (*field).fld_type == MAILIMF_FIELD_OPTIONAL_FIELD as libc::c_int {
let optional_field: *mut mailimf_optional_field = (*field).fld_data.fld_optional_field;
if !optional_field.is_null()
&& !(*optional_field).fld_name.is_null()
&& !(*optional_field).fld_value.is_null()
&& strcasecmp((*optional_field).fld_name, wanted_fld_name) == 0i32
{
return optional_field;
}
}
}
ptr::null_mut()
}
pub fn mailimf_get_recipients(imffields: *mut mailimf_fields) -> HashSet<String> {
/* returned addresses are normalized. */
let mut recipients: HashSet<String> = Default::default();
for cur in unsafe { (*(*imffields).fld_list).into_iter() } {
let fld = cur as *mut mailimf_field;
let fld_to: *mut mailimf_to;
let fld_cc: *mut mailimf_cc;
let mut addr_list: *mut mailimf_address_list = ptr::null_mut();
if fld.is_null() {
continue;
}
let fld = unsafe { *fld };
// TODO match on enums /rtn
match fld.fld_type {
13 => {
fld_to = unsafe { fld.fld_data.fld_to };
if !fld_to.is_null() {
addr_list = unsafe { (*fld_to).to_addr_list };
}
}
14 => {
fld_cc = unsafe { fld.fld_data.fld_cc };
if !fld_cc.is_null() {
addr_list = unsafe { (*fld_cc).cc_addr_list };
}
}
_ => {}
}
if !addr_list.is_null() {
for cur2 in unsafe { &(*(*addr_list).ad_list) } {
let adr = cur2 as *mut mailimf_address;
if adr.is_null() {
continue;
}
let adr = unsafe { *adr };
if adr.ad_type == MAILIMF_ADDRESS_MAILBOX as libc::c_int {
mailimf_get_recipients_add_addr(&mut recipients, unsafe {
adr.ad_data.ad_mailbox
});
} else if adr.ad_type == MAILIMF_ADDRESS_GROUP as libc::c_int {
let group = unsafe { adr.ad_data.ad_group };
if !group.is_null() && unsafe { !(*group).grp_mb_list.is_null() } {
for cur3 in unsafe { &(*(*(*group).grp_mb_list).mb_list) } {
mailimf_get_recipients_add_addr(
&mut recipients,
cur3 as *mut mailimf_mailbox,
);
}
}
}
}
}
}
recipients
}
pub fn mailmime_transfer_decode(mime: *mut Mailmime) -> Result<Vec<u8>, Error> {
ensure!(!mime.is_null(), "invalid inputs");
let mime_transfer_encoding =
get_mime_transfer_encoding(mime).unwrap_or(MAILMIME_MECHANISM_BINARY as i32);
let mime_data = unsafe { (*mime).mm_data.mm_single };
decode_dt_data(mime_data, mime_transfer_encoding)
}
pub fn get_mime_transfer_encoding(mime: *mut Mailmime) -> Option<libc::c_int> {
unsafe {
let mm_mime_fields = (*mime).mm_mime_fields;
if !mm_mime_fields.is_null() {
for cur_data in (*(*mm_mime_fields).fld_list).into_iter() {
let field: *mut mailmime_field = cur_data as *mut _;
if (*field).fld_type == MAILMIME_FIELD_TRANSFER_ENCODING as libc::c_int
&& !(*field).fld_data.fld_encoding.is_null()
{
return Some((*(*field).fld_data.fld_encoding).enc_type);
}
}
}
}
None
}
pub fn decode_dt_data(
mime_data: *mut mailmime_data,
mime_transfer_encoding: libc::c_int,
) -> Result<Vec<u8>, Error> {
// Decode data according to mime_transfer_encoding
// returns Ok with a (decoded_data,decoded_data_bytes) pointer
// where the caller must make sure to free it.
// It may return Ok(ptr::null_mut(), 0)
if mime_transfer_encoding == MAILMIME_MECHANISM_7BIT as libc::c_int
|| mime_transfer_encoding == MAILMIME_MECHANISM_8BIT as libc::c_int
|| mime_transfer_encoding == MAILMIME_MECHANISM_BINARY as libc::c_int
{
let decoded_data = unsafe { (*mime_data).dt_data.dt_text.dt_data };
let decoded_data_bytes = unsafe { (*mime_data).dt_data.dt_text.dt_length };
if decoded_data.is_null() || decoded_data_bytes == 0 {
bail!("No data to decode found");
} else {
let result = unsafe {
std::slice::from_raw_parts(decoded_data as *const u8, decoded_data_bytes)
};
return Ok(result.to_vec());
}
}
// unsafe { display_mime_data(mime_data) };
let mut current_index = 0;
let mut transfer_decoding_buffer = ptr::null_mut();
let mut decoded_data_bytes = 0;
let r = unsafe {
mailmime_part_parse(
(*mime_data).dt_data.dt_text.dt_data,
(*mime_data).dt_data.dt_text.dt_length,
&mut current_index,
mime_transfer_encoding,
&mut transfer_decoding_buffer,
&mut decoded_data_bytes,
)
};
if r == MAILIMF_NO_ERROR as libc::c_int
&& !transfer_decoding_buffer.is_null()
&& decoded_data_bytes > 0
{
let result = unsafe {
std::slice::from_raw_parts(transfer_decoding_buffer as *const u8, decoded_data_bytes)
}
.to_vec();
// we return a fresh vec and transfer_decoding_buffer is not used or passed anywhere
// so it's safe to free it right away, as mailman_part_parse has
// allocated it fresh.
unsafe { mmap_string_unref(transfer_decoding_buffer) };
return Ok(result);
}
Err(format_err!("Failed to to decode"))
}
pub fn mailimf_find_first_addr(mb_list: *const mailimf_mailbox_list) -> Option<String> {
if mb_list.is_null() {
return None;
}
for cur in unsafe { (*(*mb_list).mb_list).into_iter() } {
let mb = cur as *mut mailimf_mailbox;
if !mb.is_null() && !unsafe { (*mb).mb_addr_spec.is_null() } {
let addr = unsafe { as_str((*mb).mb_addr_spec) };
return Some(addr_normalize(addr).to_string());
}
}
None
}
/**************************************
* mime creation API
**************************************/
pub fn add_filename_part(
message: *mut Mailmime,
message: *mut mailmime,
basename: &str,
mime_type: &str,
file_content: &str,
@@ -426,9 +57,9 @@ pub fn new_custom_field(fields: *mut mailimf_fields, name: &str, value: &str) {
}
}
pub fn build_body_text(text: &str) -> Result<*mut Mailmime, Error> {
pub fn build_body_text(text: &str) -> Result<*mut mailmime, Error> {
let mime_fields: *mut mailmime_fields;
let message_part: *mut Mailmime;
let message_part: *mut mailmime;
let content = new_content_type("text/plain")?;
append_ct_param(content, "charset", "utf-8")?;
@@ -448,8 +79,8 @@ pub fn append_ct_param(
value: &str,
) -> Result<(), Error> {
unsafe {
let name_c = CString::new(name).unwrap_or_default();
let value_c = CString::new(value).unwrap_or_default();
let name_c = CString::new(name).unwrap();
let value_c = CString::new(value).unwrap();
clist_append!(
(*content).ct_parameters,
@@ -463,7 +94,7 @@ pub fn append_ct_param(
}
pub fn new_content_type(content_type: &str) -> Result<*mut mailmime_content, Error> {
let ct = CString::new(content_type).unwrap_or_default();
let ct = CString::new(content_type).unwrap();
let content: *mut mailmime_content;
// mailmime_content_new_with_str only parses but does not retain/own ct
unsafe {
@@ -473,7 +104,7 @@ pub fn new_content_type(content_type: &str) -> Result<*mut mailmime_content, Err
Ok(content)
}
pub fn set_body_text(part: *mut Mailmime, text: &str) -> Result<(), Error> {
pub fn set_body_text(part: *mut mailmime, text: &str) -> Result<(), Error> {
use libc::strlen;
unsafe {
let text_c = text.strdup();
@@ -499,24 +130,6 @@ pub fn content_type_needs_encoding(content: *const mailmime_content) -> bool {
}
}
pub fn new_mailbox_list(displayname: &str, addr: &str) -> *mut mailimf_mailbox_list {
let mbox: *mut mailimf_mailbox_list = unsafe { mailimf_mailbox_list_new_empty() };
unsafe {
mailimf_mailbox_list_add(
mbox,
mailimf_mailbox_new(
if !displayname.is_empty() {
dc_encode_header_words(&displayname).strdup()
} else {
ptr::null_mut()
},
addr.strdup(),
),
);
}
mbox
}
#[cfg(test)]
mod tests {
use super::*;
@@ -536,16 +149,4 @@ mod tests {
new_content_type("application/pgp-encrypted").unwrap()
));
}
#[test]
fn test_parse_message_id() {
assert_eq!(
parse_message_id("Mr.PRUe8HJBoaO.3whNvLCMFU0@testrun.org").unwrap(),
"Mr.PRUe8HJBoaO.3whNvLCMFU0@testrun.org"
);
assert_eq!(
parse_message_id("<Mr.PRUe8HJBoaO.3whNvLCMFU0@testrun.org>").unwrap(),
"Mr.PRUe8HJBoaO.3whNvLCMFU0@testrun.org"
);
}
}

View File

@@ -1,125 +0,0 @@
-----BEGIN PGP MESSAGE-----
Passphrase-Format: numeric9x4
Passphrase-Begin: 17
wy4ECQMI0jNRBQfVKHVg1+a2Yihd6JAjR9H0kk3oDVeX7nc4Oi+IjEtonUJt
PQpO0tPWASWYuYvjZSuTz9r1yZYV+y4mu9bu9NEQoRlWg2wnbjoUoKk4emFF
FweUj84iI6VWTCSRyMu5d5JS1RfOdX4CG/muLAegyIHezqYOEC0Z3b9Ci9rd
DiSgqqN+/LDkUR/vr7L2CSLN5suBP9Hsz75AtaV8DJ2DYDywYX89yH1CfL1O
WohyrJPdmGJZfdvQX0LI9mzN7MH0W6vUJeCaUpujc+UkLiOM6TDB74rmYF+V
Z7K9BXbaN4V6dyxVZfgpXUoZlaNpvqPJXuLHJ68umkuIgIyQvzmMj3mFgZ8s
akCt6Cf3o5O9n2PJvX89vuNnDGJrO5booEqGaBJfwUk0Rwb0gWsm5U0gceUz
dce8KZK15CzX+bNv5OC+8jjjBw7mBHVt+2q8LI+G9fEy9NIREkp5/v2ZRN0G
R6lpZwW+8TkMvJnriQeABqDpxsJVT6ENYAhkPG3AZCr/whGBU3EbDzPexXkz
qt8Pdu5DrazLSFtjpjkekrjCh43vHjGl8IOiWxKQx0VfBkHJ7O9CsHmb0r1o
F++fMh0bH1/aewmlg5wd0ixwZoP1o79he8Q4kfATZAjvB1xSLyMma+jxW5uu
U3wYUOsUmYmzo46/QzizFCUpaTJ4ZQZY1/4sflidsl/XgZ0fD1NCrdkWBNA1
0tQF949pEAeA4hSfHfQDNKAY8A7fk8lZblqWPkyu/0x8eV537QOhs89ZvhSB
V87KEAwxWt60+Eolf8PvvkvB/AKlfWq4MYShgyldwwCfkED3rv2mvTsdqfvW
WvqZNo4eRkJrnv9Be3LaXoFyY6a3z+ObBIkKI+u5azGJYge97O4E2DrUEKdQ
cScq5upzXity0E+Yhm964jzBzxnA52S4RoXzkjTxH+AHjQ5+MHQxmRfMd2ly
7skM106weVOR0JgOdkvfiOFDTHZLIVCzVyYVlOUJYYwPhmM1426zbegHNkaM
M2WgvjMp5G+X9qfDWKecntQJTziyDFZKfd1UrUCPHrvl1Ac9cuqgcCXLtdUS
jI+e1Y9fXvgyvHiMX0ztSz1yfvnRt34508G9j68fEQFQR/VIepULB5/SqKbq
p2flgJL48kY32hEw2GRPri64Tv3vMPIWa//zvQDhQPmcd3S4TqnTIIKUoTAO
NUo6GS9UAX12fdSFPZINcAkNIaB69+iwGyuJE4FLHKVkqNnNmDwF3fl0Oczo
hbboWzA3GlpR2Ri6kfe0SocfGR0CHT5ZmqI6es8hWx+RN8hpXcsRxGS0BMi2
mcJ7fPY+bKastnEeatP+b0XN/eaJAPZPZSF8PuPeQ0Uc735fylPrrgtWK9Gp
Wq0DPaWV/+O94OB/JvWT5wq7d/EEVbTck5FPl4gdv3HHpaaQ6/8G89wVMEXA
GUxB8WuvNeHAtQ7qXF7TkaZvUpF0rb1aV88uABOOPpsfAyWJo/PExCZacg8R
GOQYI6inV5HcGUw06yDSqArHZmONveqjbDBApenearcskv6Uz7q+Bp60GGSA
lvU3C3RyP/OUc1azOp72MIe0+JvP8S5DN9/Ltc/5ZyZHOjLoG+npIXnThYwV
0kkrlsi/7loCzvhcWOac1vrSaGVCfifkYf+LUFQFrFVbxKLOQ6vTsYZWM0yM
QsMMywW5A6CdROT5UB0UKRh/S1cwCwrN5UFTRt2UpDF3wSBAcChsHyy90RAL
Xd4+ZIyf29GIFuwwQyzGBWnXQ2ytU4kg/D5XSqJbJJTya386UuyQpnFjI19R
uuD0mvEfFvojCKDJDWguUNtWsHSg01NXDSrY26BhlOkMpUrzPfX5r0FQpgDS
zOdY9SIG+y9MKG+4nwmYnFM6V5NxVL+6XZ7BQTvlLIcIIu+BujVNWteDnWNZ
T1UukCGmFd8sNZpCc3wu4o/gLDQxih/545tWMf0dmeUfYhKcjSX9uucMRZHT
1N0FINw04fDdp2LccL+WCGatFGnkZVPw3asid4d1od9RG9DbNRBJEp/QeNhc
/peJCPLGYlA1NjTEq+MVB+DHdGNOuy//be3KhedBr6x4VVaDzL6jyHu/a7PR
BWRVtI1CIVDxyrEXucHdGQoEm7p+0G2zouOe/oxbPFoEYrjaI+0e/FN3u/Y3
aG0dlYWbxeHMqTh2F3lB/CFALReeGqqN6PwRyePWKaVctZYb6ydf9JVl6q1/
aV9C5rf9eFGqqA+OIx/+XuAG1w0rwlznvtajHzCoUeA4QfbmuOV/t5drWN2N
PCk2mJlcSmd7lx53rnOIgme1hggchjezc4TisL4PvSLxjJ7DxzktD2jv2I/Q
OlSxTUaXnGfIVedsI0WjFomz5w9tZjC0B5O5TpSRRz6gfpe/OC3kV7qs1YCS
lJTTxj1mTs6wqt0WjKkN/Ke0Cm5r7NQ79szDNlcC0AViEOQb3U1R88nNdiVx
ymKT5Dl+yM6acv53lNX6O5BH+mpP2/pCpi3x+kYFyr4cUsNgVVGlhmkPWctZ
trHvO7wcLrAsrLNqRxt1G3DLjQt9VY+w5qOPJv6s9qd5JBL/qtH5zqIXiXlM
IWI9LLwHFFXqjk/f6G4LyOeHB9AqccGQ4IztgzTKmYEmFWVIpTO4UN6+E7yQ
gtcYSIUEJo824ht5rL+ODqmCSAWsWIomEoTPvgn9QqO0YRwAEMpsFtE17klS
qjbYyV7Y5A0jpCvqbnGmZPqCgzjjN/p5VKSNjSdM0vdwBRgpXlyooXg/EGoJ
ZTZH8nLSuYMMu7AK8c7DKJ1AocTNYHRe9xFV8RzEiIm3zaezxa0r+Fo3nuTX
UR9DOH0EHaDLrFQcfS5y1iRxY9CHg0N2ECaUzr/H7jck9mLZ7v9xisj3QDuv
i0xQbC4BTxMEBGTK8fOcjHHOABOyhqotOreERqwOV2c1OOGUQE8QK18zJCUd
BTmQZ709ttASD7VWK4TraOGczZXkZsKdZko5T6+6EkFy9H+gwENLUG9zk0x9
2G5zicDr6PDoAGDuoB3B3VA8ertXTX7zEz30N6m+tcAtPWka0owokLy3f0o7
ZdytBPkly8foTMWKF2vsJ8K4Xdn/57jJ2qFku32xmtiPIoa6s8wINO06AVB0
0/AuttvxcPr+ycE+9wRZHx6JBujAqOZztU3zu8WZMaqVKb7gnmkWPiL+1XFp
2+mr0AghScIvjzTDEjigDtLydURJrW01wXjaR0ByBT4z8ZjaNmQAxIPOIRFC
bD0mviaoX61qgQLmSc6mzVlzzNZRCKtSvvGEK5NJ6CB6g2EeFau8+w0Zd+vv
/iv6Img3pUBgvpMaIsxRXvGZwmo2R0tztJt+CqHRvyTWjQL+CjIAWyoHEdVH
k7ne/q9zo3iIMsQUO7tVYtgURpRYc2OM1IVQtrgbmbYGEdOrhMjaWULg9C7o
6oDM0EFlCAId3P8ykXQNMluFKlf9il5nr19B/qf/wh6C7DFLOmnjTWDXrEiP
6wFEWTeUWLchGlbpiJFEu05MWPIRoRd3BHQvVpzLLgeBdxMVW7D6WCK+KJxI
W1rOKhhLVvKU3BrFgr12A4uQm+6w1j33Feh68Y0JB7GLDBBGe11QtLCD6kz5
RzFl+GbgiwpHi3nlCc5yiNwyPq/JRxU3GRb62YJcsSQBg+CD3Mk5FGiDcuvp
kZXOcTE2FAnUDigjEs+oH2qkhD4/5CiHkrfFJTzv+wqw+jwxPor2jkZH2akN
6PssXQYupXJE3NmcyaYT+b5E6qbkIyQj7CknkiqmrqrmxkOQxA+Ab2Vy9zrW
u0+Wvf+C+SebWTo3qfJZQ3KcASZHa5AGoSHetWzH2fNLIHfULXac/T++1DWE
nbeNvhXiFmAJ+BRsZj9p6RcnSamk4bjAbX1lg2G3Sq6MiA1fIRSMlSjuDLrQ
8xfVFrg7gfBIIQPErJWv2GdAsz76sLxuSXQLKYpFnozvMT7xRs84+iRNWWh9
SNibbEjlh0DcJlKw49Eis/bN22sDQWy4awHuRvvQetk/QCgp54epuqWnbxoE
XZDgGBBkMc3or+6Cxr3q9x7J/oHLvPb+Q5yVP9fyz6ZiSVWluMefA9smjJ/A
KMD84s7uO/8/4yug+swXGrcBjHSddTcy05vm+7X6o9IEZKZb5tz7VqAfEcuk
QNPUWCMudhzxSNr4+yVXRVpcjsjKtplJcXC5aIuJwq3C5OdysCGqXWjLuUu1
OFSoPvTsYC2VxYdFUcczeHEFTxXoXz3I0TyLPyxUNsJiKpUGt/SXmV/IyAx+
h6pZ2OUXspC9d78DdiHZtItPjEGiIb678ZyMxWPE59XQd/ad92mlPHU8InXD
yTq6otZ7LwAOLGbDR9bqN7oX8PCHRwuu30hk2b4+WkZn/WLd2KCPddQswZJg
Qgi5ajUaFhZvxF5YNTqIzzYVh7Y8fFMfzH9AO+SJqy+0ECX0GwtHHeVsXYNb
P/NO/ma4MI8301JyipPmdtzvvt9NOD/PJcnZH2KmDquARXMO/vKbn3rNUXog
pTFqqyNTr4L5FK86QPEoE4hDy9ItHGlEuiNVD+5suGVGUgYfV7AvZU46EeqO
rfFj8wNSX1aK/pIwWmh1EkygPSxomWRUANLX1jO6zX9wk2X80Xn9q/8jot1k
Vl54OOd7cvGls2wKkEZi5h3p6KKZHJ+WIDBQupeJbuma1GK8wAiwjDH59Y0X
wXHAk7XA+t4u0dgRpZbUUMqQmvEvfJaCr4qMlpuGdEYbbpIMUB1qCfYU9taL
zbepMIT+XYD5mTyytZhR+zrsfpt1EzbrhuabqPioySoIS/1+bWfxvndq16r0
AdNxR5LiVSVh8QJr3B/HJhVghgSVrrynniG3E94abNWL/GNxPS/dTHSf8ass
vbv7+uznADzHsMiG/ZlLAEkQJ9j0ENJvHmnayeVFIXDV6jPCcQJ+rURDgl7z
/qTLfe3o3zBMG78LcB+xDNXTQrK5Z0LX7h17hLSElpiUghFa9nviCsT0nkcr
nz302P4IOFwJuYMMCEfW+ywTn+CHpKjLHWkZSZ4q6LzNTbbgXZn/vh7njNf0
QHaHmaMNxnDhUw/Bl13uM52qtsfEYK07SEhLFlJbAk0G7q+OabK8dJxCRwS3
X9k4juzLUYhX8XBovg9G3YEVckb6iM8/LF/yvNXbUsPrdhYU9lPA63xD0Pgb
zthZCLIlnF+lS6e41WJv3n1dc4dFWD7F5tmt/7uwLC6oUGYsccSzY+bUkYhL
dp7tlQRd5AG/Xz8XilORk8cUjvi6uZss5LyQpKvGSU+77C8ZV/oS62BdS5TE
osBTrO2/9FGzQtHT+8DJSTPPgR6rcQUWLPemiG09ACKfRQ/g3b9Qj0upOcKL
6dti0lq7Aorc39vV18DPMFBOwzchUEBlBFyuSa4AoD30tsoilAC3qbzBwu3z
QLjmst76HEcWDkxgDAhlBz6/XgiVZsCivn7ygigmc2+hNEzIdDsKKfM9bkoe
3uJzmmsv8Bh5ZEtfGoGNmu/zA7tgvTOCBeotYeHr2O6pLmYb3hK+E/qCBl14
8pK4qYrjAlF+ZMq9BzXcaz5mRfKVfAQtghHOaNqopBczSE1bjFF6HaNhIaGa
N8YdabNQG7mLI/fgBxJfkPl6HdIhEpctp4RURbSFhW+wn0o85VyHM6a+6Vgj
NrYmhxPZ6N1KN0Qy76aNiw7nAToRRcOv87uZnkDIeVH8mP/0hldyiy/Y97cG
QgOeQHOG27QW57nHhqLRqvf0zzQZekuXWFbqajpaabEcdGXyiUpJ8/ZopBPM
AJwfkyA2LkV946IA4JV6sPnu9pYzpXQ4vdQKJ6DoDUyRTQmgmfSFGtfHAozY
V9k0iQeetSkYYtOagTrg3t92v7M00o/NJW/rKX4jj2djD8wtBovOcv4kxg4Z
o58Iv94ROim48XfyesvSYKN1xqqbXH4sfE6b4b9pLUxQVOmWANLK9MK8D+Ci
IvrGbz5U5bZP6vlNbe9bYzjvWTPjaMrjXknRTBcikavqOfDTSIVFtT4qvhvK
42PpOrm0qdiLwExGKQ9FfEfYZRgEcYRGg7rH3oNz6ZNOEXppF3tCl9yVOlFb
ygdIeT3Z3HeOQbAsi8jK7o16DSXL7ZOpFq9Bv9yzusrF7Eht/fSEpAVUO3D1
IuqjZcsQRhMtIvnF0oFujFtooJx9x3dj/RarvEGX/NzwATZkgJ+yWs2etruA
EzMQqED4j7Lb790zEWnt+nuHdCdlPnNy8RG5u5X62p3h5KqUbg9HfmIuuESi
hwr6dKsVQGc5XUB5KTt0dtjWlK5iaetDsZFuF5+aE0Xa6PmiQ2e7ZPFyxXmO
T/PSHzobx0qClKCu+tSWA1HDSL08IeoGZEyyhoaxyn5D9r1Mqg101v/iu59r
lRRs+plAhbuq5aQA3WKtF1N6Zb5+AVRpNUyrxyHoH36ddR4/n7lnIld3STGD
RqZLrOuKHS3dCNW2Pt15lU+loYsWFZwC6T/tAbvwhax+XaBMiKQSDFmG9sBw
TiM1JWXhq2IsjXBvCl6k2AKWLQOvc/Hin+oYs4d7M9mi0vdoEOAMadU/+Pqn
uZzP941mOUV5UeTCCbjpyfI7qtIi3TH1cQmC2kG2HrvQYuM6Momp//JusH1+
9eHgFo25HbitcKJ1sAqxsnYIW5/jIVyIJC7tatxmNfFQQ/LUb2cT+Jowwsf4
bbPinA9S6aQFy9k3vk07V2ouYl+cpMMXmNAUrboFRLxw7QDapWYMKdmnbU5O
HZuDz3iyrm0lMPsRtt/f5WUhZYY4vXT5/dj+8P6Pr5fdc4S84i5qEzf7bX/I
Sc6fpISdYBscfHdv6uXsEVtVPKEuQVYwhyc4kkwVKjZBaqsgjAA7VEhQXzO3
rC7di4UhabWQCQTG1GYZyrj4bm6dg/32uVxMoLS5kuSpi3nMz5JmQahLqRxh
argg13K2/MJ7w2AI23gCvO5bEmD1ZXIi1aGYdZfu7+KqrTumYxj0KgIesgU0
6ekmPh4Zu5lIyKopa89nfQVj3uKbwr9LLHegfzeMhvI5WQWghKcNcXEvJwSA
vEik5aXm2qSKXT+ijXBy5MuNeICoGaQ5WA0OJ30Oh5dN0XpLtFUWHZKThJvR
mngm1QCMMw2v/j8=
=9sJE
-----END PGP MESSAGE-----

File diff suppressed because one or more lines are too long