Compare commits

..

63 Commits

Author SHA1 Message Date
Alexander Krotov
580ae5b499 Fix test_encryption_decryption
It is broken since 28cae607a4
2019-09-10 02:41:35 +03:00
holger krekel
2b257e3d0d fix ffi 2019-09-09 19:45:43 +02:00
Alexander Krotov
77c9746be5 Make dc_msg_get_filemime safe 2019-09-09 19:45:43 +02:00
Alexander Krotov
28cae607a4 Pass buffers to pgp.rs as slices 2019-09-09 18:50:47 +02:00
Simon Laux
814281ed7d fixes #463 2019-09-09 18:44:30 +02:00
holger krekel
5b0c8dd9dd address @r10s and @flub review comments, and fix some docstrings/test meta docs 2019-09-09 18:07:32 +02:00
holger krekel
650d8c45ec fix test, and cleanup according profile-image API 2019-09-09 18:07:32 +02:00
holger krekel
383d8980d6 add profile image API to python, tests, Rust fixes/cleanups 2019-09-09 18:07:32 +02:00
Simon Laux
6ea706c646 remove macro 2019-09-09 18:07:32 +02:00
Simon Laux
1ed2af08b8 cargo fmt 2019-09-09 18:07:32 +02:00
Simon Laux
7563a5abe0 remove closure
Co-authored-by: @Jikstra
2019-09-09 18:07:32 +02:00
Simon Laux
0a8b187f80 fix remove chat profile img 2019-09-09 18:07:32 +02:00
björn petersen
275aa853f5 Merge pull request #471 from deltachat/fix417
fix crash when downloading message
2019-09-09 16:18:50 +02:00
Friedel Ziegelmayer
3614d57f9f Merge pull request #469 from deltachat/refctor/strencode
refactor(strencode): rustify some strencode methods
2019-09-09 11:52:30 +02:00
B. Petersen
1367873949 check bounds before accessing Vec 2019-09-09 01:47:50 +02:00
B. Petersen
d933183e0a mark safe functions as such 2019-09-09 01:47:50 +02:00
B. Petersen
7e11def527 make code more readable 2019-09-09 00:01:15 +02:00
dignifiedquire
f3e53a05a6 refactor(loginparam): rename dc_loginparam -> login_param 2019-09-08 18:48:57 +02:00
dignifiedquire
dd381a5c1c refactor(loginparam): simplify and rustify 2019-09-08 18:48:57 +02:00
dignifiedquire
8eee449305 refactor(token): rustify 2019-09-08 18:35:20 +02:00
dignifiedquire
96e02af0da refactor: rename dc_token to token 2019-09-08 18:35:20 +02:00
dignifiedquire
60fb1478c3 refactor(strencode): rustify some strencode methods 2019-09-08 15:50:28 +02:00
Friedel Ziegelmayer
aa7d0679df fix(tools): make sure dc_truncate can handle arbitrary utf8 valu… (#460)
fix(tools): make sure dc_truncate can handle arbitrary utf8 values
2019-09-08 15:23:46 +02:00
dignifiedquire
8e3cc192a5 fix(tools): make sure dc_truncate can handle arbitrary utf8 values
also adds proptests to make sure this is upheld

Should close #433
2019-09-08 14:00:04 +02:00
Simon Laux
0f939995d1 fix(job): "invalid job action" check 2019-09-08 13:39:59 +02:00
björn petersen
2e1bc9b14e Merge pull request #458 from deltachat/fix-unknown
fix reading unknown origin
2019-09-08 12:24:51 +02:00
Friedel Ziegelmayer
fd1ac6ab2d Remove some free() from Mozilla autoconfig (#457)
Remove some free() from Mozilla autoconfig
2019-09-08 11:53:41 +02:00
Friedel Ziegelmayer
d224924dc8 Change type of function from `const char *' to &str (#451)
Change type of function from `const char *' to &str
2019-09-08 11:51:16 +02:00
dignifiedquire
00e5ddd6f0 make enum reading from the db more robust 2019-09-08 11:29:40 +02:00
B. Petersen
c603ca0e7a remove dead code 2019-09-08 07:26:07 +02:00
B. Petersen
d07ef01204 cargo fmt 2019-09-07 23:25:19 +02:00
B. Petersen
d8630b5029 fix reading of unknown/outdated origin 2019-09-07 22:21:38 +02:00
Alexander Krotov
3b397326f8 Store email parts as Rust str's 2019-09-07 20:23:08 +03:00
Alexander Krotov
81cabd08a9 Accept str instead of char* in read_autoconf_file 2019-09-07 20:20:25 +03:00
björn petersen
5663c7dec3 Merge pull request #453 from deltachat/fix-backup
Fix backup and housekeeping
2019-09-07 15:38:01 +02:00
björn petersen
06673b2108 Merge pull request #455 from deltachat/fix-msg-loading
Fix msg loading
2019-09-07 15:37:38 +02:00
dignifiedquire
6b7498a4b1 fix(contact): fix logic for create or add contact
Closes #448
2019-09-07 14:07:56 +02:00
B. Petersen
7f4ef493b9 be tolerant when reading unexpected NULL from the database and treat this as an empty string, compatible to core-c 2019-09-07 13:54:43 +02:00
B. Petersen
d9d0dee0d5 fix: use empty string for messages without text everywhere 2019-09-07 13:40:50 +02:00
B. Petersen
9605370f0b fix cmdline: sendimage and sendfile really accept only a file 2019-09-07 12:34:31 +02:00
Dmitry Bogatov
7d9fc682a0 cargo-fmt 2019-09-07 03:09:01 +00:00
Dmitry Bogatov
c4c08f2552 Remove ok_to_continue pattern from msg_prepare_raw()
This commit will fail CI due incorrect formatting. It is done
deliberately to simplify review process.
2019-09-07 03:06:13 +00:00
Dmitry Bogatov
400740fdba Simplify prepare_msg_raw() using early return
This commit will fail CI due incorrect formatting. It is done
deliberately to simplify review process.
2019-09-07 03:03:35 +00:00
Dmitry Bogatov
42bce7c0bf Remove last C string from prepare_msg_raw() 2019-09-07 02:54:28 +00:00
Dmitry Bogatov
a2281489a6 Create safe version of msgid-generating function 2019-09-07 02:35:13 +00:00
Dmitry Bogatov
9bf7b0bf96 Use more of Rust, less of C strings in prepare_msg_raw() 2019-09-07 01:39:26 +00:00
Dmitry Bogatov
1f82ba74aa Remove redundant checks in prepare_msg_raw() 2019-09-06 23:24:39 +00:00
Dmitry Bogatov
1062ac6ade Drop unsafe version of get_parent_mime_headers function 2019-09-06 23:15:31 +00:00
Dmitry Bogatov
aa5304a4f3 Use safe version of `get_parent_mime_headers()' function 2019-09-06 23:15:31 +00:00
Dmitry Bogatov
3a57ba1142 Implement safe version of `get_parent_mime_headers' function 2019-09-06 23:15:31 +00:00
Dmitry Bogatov
c0e7293360 Change return type of clist_search_string_nocase to `bool' 2019-09-06 23:15:31 +00:00
Dmitry Bogatov
dc1839760c Simplify clist_search_string_nocase using Iterator interface 2019-09-06 23:15:31 +00:00
Dmitry Bogatov
a4e4b0fc17 Rustify type of dc_mimeparser_t.subject 2019-09-06 23:15:31 +00:00
Dmitry Bogatov
743e4deb36 Remove dc_mimepart_unref function
Since there is no longer any manually-managed memory, associated with
`dc_mimepart_t' structure, default Drop instances does everything
automatically.
2019-09-06 23:15:30 +00:00
Dmitry Bogatov
1d75f8478c Rustify type of dc_mimepart_t.msg_raw 2019-09-06 23:15:27 +00:00
B. Petersen
cc0428aa50 really check all rows when searching for referenced files 2019-09-06 15:15:06 +02:00
B. Petersen
4be481275f clearer naming 2019-09-06 14:32:17 +02:00
Dmitry Bogatov
28cfe36f43 Change type of dc_mimeparser_t.decryption_failed to bool 2019-09-06 03:48:41 +00:00
Dmitry Bogatov
e0df78c5f7 Change type of dc_mimepart_t.is_meta to bool 2019-09-06 03:41:18 +00:00
Dmitry Bogatov
4d8b058b65 Change type of dc_mimeparser_t.is_forwarded to bool 2019-09-06 03:36:54 +00:00
Dmitry Bogatov
da25611758 Change type of function from `const char *' to &str 2019-09-06 03:05:12 +00:00
B. Petersen
27732c85af mark actually safe function as such 2019-09-06 00:55:50 +02:00
B. Petersen
5ffc84eb59 remove unused functions 2019-09-06 00:55:50 +02:00
38 changed files with 1290 additions and 1679 deletions

64
Cargo.lock generated
View File

@@ -110,6 +110,19 @@ dependencies = [
"byteorder 1.3.2 (registry+https://github.com/rust-lang/crates.io-index)",
]
[[package]]
name = "bit-set"
version = "0.5.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
dependencies = [
"bit-vec 0.5.1 (registry+https://github.com/rust-lang/crates.io-index)",
]
[[package]]
name = "bit-vec"
version = "0.5.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
[[package]]
name = "bitfield"
version = "0.13.2"
@@ -476,6 +489,7 @@ dependencies = [
"image-meta 0.1.0 (registry+https://github.com/rust-lang/crates.io-index)",
"imap 1.0.2 (git+https://github.com/jonhoo/rust-imap?rev=281d2eb8ab50dc656ceff2ae749ca5045f334e15)",
"itertools 0.8.0 (registry+https://github.com/rust-lang/crates.io-index)",
"jetscii 0.4.4 (registry+https://github.com/rust-lang/crates.io-index)",
"lazy_static 1.4.0 (registry+https://github.com/rust-lang/crates.io-index)",
"lettre 0.9.2 (registry+https://github.com/rust-lang/crates.io-index)",
"libc 0.2.62 (registry+https://github.com/rust-lang/crates.io-index)",
@@ -489,6 +503,7 @@ dependencies = [
"pkg-config 0.3.15 (registry+https://github.com/rust-lang/crates.io-index)",
"pretty_assertions 0.6.1 (registry+https://github.com/rust-lang/crates.io-index)",
"pretty_env_logger 0.3.1 (registry+https://github.com/rust-lang/crates.io-index)",
"proptest 0.9.4 (registry+https://github.com/rust-lang/crates.io-index)",
"quick-xml 0.15.0 (registry+https://github.com/rust-lang/crates.io-index)",
"r2d2 0.8.5 (registry+https://github.com/rust-lang/crates.io-index)",
"r2d2_sqlite 0.12.0 (registry+https://github.com/rust-lang/crates.io-index)",
@@ -996,6 +1011,11 @@ name = "itoa"
version = "0.4.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
[[package]]
name = "jetscii"
version = "0.4.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
[[package]]
name = "keccak"
version = "0.1.0"
@@ -1647,6 +1667,25 @@ dependencies = [
"unicode-xid 0.2.0 (registry+https://github.com/rust-lang/crates.io-index)",
]
[[package]]
name = "proptest"
version = "0.9.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
dependencies = [
"bit-set 0.5.1 (registry+https://github.com/rust-lang/crates.io-index)",
"bitflags 1.1.0 (registry+https://github.com/rust-lang/crates.io-index)",
"byteorder 1.3.2 (registry+https://github.com/rust-lang/crates.io-index)",
"lazy_static 1.4.0 (registry+https://github.com/rust-lang/crates.io-index)",
"num-traits 0.2.8 (registry+https://github.com/rust-lang/crates.io-index)",
"quick-error 1.2.2 (registry+https://github.com/rust-lang/crates.io-index)",
"rand 0.6.5 (registry+https://github.com/rust-lang/crates.io-index)",
"rand_chacha 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)",
"rand_xorshift 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)",
"regex-syntax 0.6.11 (registry+https://github.com/rust-lang/crates.io-index)",
"rusty-fork 0.2.2 (registry+https://github.com/rust-lang/crates.io-index)",
"tempfile 3.1.0 (registry+https://github.com/rust-lang/crates.io-index)",
]
[[package]]
name = "publicsuffix"
version = "1.5.2"
@@ -2020,6 +2059,17 @@ dependencies = [
"semver 0.9.0 (registry+https://github.com/rust-lang/crates.io-index)",
]
[[package]]
name = "rusty-fork"
version = "0.2.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
dependencies = [
"fnv 1.0.6 (registry+https://github.com/rust-lang/crates.io-index)",
"quick-error 1.2.2 (registry+https://github.com/rust-lang/crates.io-index)",
"tempfile 3.1.0 (registry+https://github.com/rust-lang/crates.io-index)",
"wait-timeout 0.2.0 (registry+https://github.com/rust-lang/crates.io-index)",
]
[[package]]
name = "rustyline"
version = "4.1.0"
@@ -2664,6 +2714,14 @@ name = "void"
version = "1.0.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
[[package]]
name = "wait-timeout"
version = "0.2.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
dependencies = [
"libc 0.2.62 (registry+https://github.com/rust-lang/crates.io-index)",
]
[[package]]
name = "walkdir"
version = "2.2.9"
@@ -2811,6 +2869,8 @@ dependencies = [
"checksum backtrace 0.3.34 (registry+https://github.com/rust-lang/crates.io-index)" = "b5164d292487f037ece34ec0de2fcede2faa162f085dd96d2385ab81b12765ba"
"checksum backtrace-sys 0.1.31 (registry+https://github.com/rust-lang/crates.io-index)" = "82a830b4ef2d1124a711c71d263c5abdc710ef8e907bd508c88be475cebc422b"
"checksum base64 0.10.1 (registry+https://github.com/rust-lang/crates.io-index)" = "0b25d992356d2eb0ed82172f5248873db5560c4721f564b13cb5193bda5e668e"
"checksum bit-set 0.5.1 (registry+https://github.com/rust-lang/crates.io-index)" = "e84c238982c4b1e1ee668d136c510c67a13465279c0cb367ea6baf6310620a80"
"checksum bit-vec 0.5.1 (registry+https://github.com/rust-lang/crates.io-index)" = "f59bbe95d4e52a6398ec21238d31577f2b28a9d86807f06ca59d191d8440d0bb"
"checksum bitfield 0.13.2 (registry+https://github.com/rust-lang/crates.io-index)" = "46afbd2983a5d5a7bd740ccb198caf5b82f45c40c09c0eed36052d91cb92e719"
"checksum bitflags 1.1.0 (registry+https://github.com/rust-lang/crates.io-index)" = "3d155346769a6855b86399e9bc3814ab343cd3d62c7e985113d46a0ec3c281fd"
"checksum blake2b_simd 0.5.6 (registry+https://github.com/rust-lang/crates.io-index)" = "461f4b879a8eb70c1debf7d0788a9a5ff15f1ea9d25925fea264ef4258bed6b2"
@@ -2906,6 +2966,7 @@ dependencies = [
"checksum iovec 0.1.2 (registry+https://github.com/rust-lang/crates.io-index)" = "dbe6e417e7d0975db6512b90796e8ce223145ac4e33c377e4a42882a0e88bb08"
"checksum itertools 0.8.0 (registry+https://github.com/rust-lang/crates.io-index)" = "5b8467d9c1cebe26feb08c640139247fac215782d35371ade9a2136ed6085358"
"checksum itoa 0.4.4 (registry+https://github.com/rust-lang/crates.io-index)" = "501266b7edd0174f8530248f87f99c88fbe60ca4ef3dd486835b8d8d53136f7f"
"checksum jetscii 0.4.4 (registry+https://github.com/rust-lang/crates.io-index)" = "5f25cca2463cb19dbb1061eb3bd38a8b5e4ce1cc5a5a9fc0e02de486d92b9b05"
"checksum keccak 0.1.0 (registry+https://github.com/rust-lang/crates.io-index)" = "67c21572b4949434e4fc1e1978b99c5f77064153c59d998bf13ecd96fb5ecba7"
"checksum kernel32-sys 0.2.2 (registry+https://github.com/rust-lang/crates.io-index)" = "7507624b29483431c0ba2d82aece8ca6cdba9382bff4ddd0f7490560c056098d"
"checksum lazy_static 1.4.0 (registry+https://github.com/rust-lang/crates.io-index)" = "e2abad23fbc42b3700f2f279844dc832adb2b2eb069b2df918f455c4e18cc646"
@@ -2973,6 +3034,7 @@ dependencies = [
"checksum proc-macro-hack 0.5.9 (registry+https://github.com/rust-lang/crates.io-index)" = "e688f31d92ffd7c1ddc57a1b4e6d773c0f2a14ee437a4b0a4f5a69c80eb221c8"
"checksum proc-macro2 0.4.30 (registry+https://github.com/rust-lang/crates.io-index)" = "cf3d2011ab5c909338f7887f4fc896d35932e29146c12c8d01da6b22a80ba759"
"checksum proc-macro2 1.0.1 (registry+https://github.com/rust-lang/crates.io-index)" = "4c5c2380ae88876faae57698be9e9775e3544decad214599c3a6266cca6ac802"
"checksum proptest 0.9.4 (registry+https://github.com/rust-lang/crates.io-index)" = "cf147e022eacf0c8a054ab864914a7602618adba841d800a9a9868a5237a529f"
"checksum publicsuffix 1.5.2 (registry+https://github.com/rust-lang/crates.io-index)" = "5afecba86dcf1e4fd610246f89899d1924fe12e1e89f555eb7c7f710f3c5ad1d"
"checksum pulldown-cmark 0.2.0 (registry+https://github.com/rust-lang/crates.io-index)" = "eef52fac62d0ea7b9b4dc7da092aa64ea7ec3d90af6679422d3d7e0e14b6ee15"
"checksum quick-error 1.2.2 (registry+https://github.com/rust-lang/crates.io-index)" = "9274b940887ce9addde99c4eee6b5c44cc494b182b97e73dc8ffdcb3397fd3f0"
@@ -3010,6 +3072,7 @@ dependencies = [
"checksum rust-argon2 0.5.1 (registry+https://github.com/rust-lang/crates.io-index)" = "4ca4eaef519b494d1f2848fc602d18816fed808a981aedf4f1f00ceb7c9d32cf"
"checksum rustc-demangle 0.1.16 (registry+https://github.com/rust-lang/crates.io-index)" = "4c691c0e608126e00913e33f0ccf3727d5fc84573623b8d65b2df340b5201783"
"checksum rustc_version 0.2.3 (registry+https://github.com/rust-lang/crates.io-index)" = "138e3e0acb6c9fb258b19b67cb8abd63c00679d2851805ea151465464fe9030a"
"checksum rusty-fork 0.2.2 (registry+https://github.com/rust-lang/crates.io-index)" = "3dd93264e10c577503e926bd1430193eeb5d21b059148910082245309b424fae"
"checksum rustyline 4.1.0 (registry+https://github.com/rust-lang/crates.io-index)" = "0f47ea1ceb347d2deae482d655dc8eef4bd82363d3329baffa3818bd76fea48b"
"checksum ryu 1.0.0 (registry+https://github.com/rust-lang/crates.io-index)" = "c92464b447c0ee8c4fb3824ecc8383b81717b9f1e74ba2e72540aef7b9f82997"
"checksum safemem 0.3.2 (registry+https://github.com/rust-lang/crates.io-index)" = "d2b08423011dae9a5ca23f07cf57dac3857f5c885d352b76f6d95f4aea9434d0"
@@ -3086,6 +3149,7 @@ dependencies = [
"checksum vcpkg 0.2.7 (registry+https://github.com/rust-lang/crates.io-index)" = "33dd455d0f96e90a75803cfeb7f948768c08d70a6de9a8d2362461935698bf95"
"checksum version_check 0.1.5 (registry+https://github.com/rust-lang/crates.io-index)" = "914b1a6776c4c929a602fafd8bc742e06365d4bcbe48c30f9cca5824f70dc9dd"
"checksum void 1.0.2 (registry+https://github.com/rust-lang/crates.io-index)" = "6a02e4885ed3bc0f2de90ea6dd45ebcbb66dacffe03547fadbb0eeae2770887d"
"checksum wait-timeout 0.2.0 (registry+https://github.com/rust-lang/crates.io-index)" = "9f200f5b12eb75f8c1ed65abd4b2db8a6e1b138a20de009dacee265a2498f3f6"
"checksum walkdir 2.2.9 (registry+https://github.com/rust-lang/crates.io-index)" = "9658c94fa8b940eab2250bd5a457f9c48b748420d71293b165c8cdbe2f55f71e"
"checksum want 0.2.0 (registry+https://github.com/rust-lang/crates.io-index)" = "b6395efa4784b027708f7451087e647ec73cc74f5d9bc2e418404248d679a230"
"checksum wasi 0.5.0 (registry+https://github.com/rust-lang/crates.io-index)" = "fd5442abcac6525a045cc8c795aedb60da7a2e5e89c7bf18a0d5357849bb23c7"

View File

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

View File

@@ -898,9 +898,15 @@ pub unsafe extern "C" fn dc_set_chat_profile_image(
let context = &*context;
chat::set_chat_profile_image(context, chat_id, as_str(image))
.map(|_| 1)
.unwrap_or_log_default(context, "Failed to set profile image")
chat::set_chat_profile_image(context, chat_id, {
if image.is_null() {
""
} else {
as_str(image)
}
})
.map(|_| 1)
.unwrap_or_log_default(context, "Failed to set profile image")
}
#[no_mangle]
@@ -1780,7 +1786,7 @@ pub unsafe extern "C" fn dc_chat_get_profile_image(chat: *mut dc_chat_t) -> *mut
let chat = &*chat;
match chat.get_profile_image() {
Some(i) => i.strdup(),
Some(p) => p.to_str().unwrap().to_string().strdup(),
None => ptr::null_mut(),
}
}
@@ -2019,7 +2025,7 @@ pub unsafe extern "C" fn dc_msg_get_filemime(msg: *mut dc_msg_t) -> *mut libc::c
}
let msg = &*msg;
message::dc_msg_get_filemime(msg)
message::dc_msg_get_filemime(msg).strdup()
}
#[no_mangle]
@@ -2397,7 +2403,7 @@ pub unsafe extern "C" fn dc_contact_get_profile_image(
contact
.get_profile_image()
.map(|s| s.strdup())
.map(|p| p.to_str().unwrap().to_string().strdup())
.unwrap_or_else(|| std::ptr::null_mut())
}

View File

@@ -36,7 +36,7 @@ pub fn from_sql_derive(input: TokenStream) -> TokenStream {
impl rusqlite::types::FromSql for #name {
fn column_result(col: rusqlite::types::ValueRef) -> rusqlite::types::FromSqlResult<Self> {
let inner = rusqlite::types::FromSql::column_result(col)?;
num_traits::FromPrimitive::from_i64(inner).ok_or(rusqlite::types::FromSqlError::InvalidType)
Ok(num_traits::FromPrimitive::from_i64(inner).unwrap_or_default())
}
}
};

View File

@@ -342,12 +342,6 @@ unsafe fn log_contactlist(context: &Context, contacts: &Vec<u32>) {
}
}
static mut S_IS_AUTH: libc::c_int = 0;
pub unsafe fn dc_cmdline_skip_auth() {
S_IS_AUTH = 1;
}
fn chat_prefix(chat: &Chat) -> &'static str {
chat.typ.into()
}
@@ -428,7 +422,7 @@ pub unsafe fn dc_cmdline(context: &Context, line: &str) -> Result<(), failure::E
send <text>\n\
send-garbage\n\
sendimage <file> [<text>]\n\
sendfile <file>\n\
sendfile <file> [<text>]\n\
draft [<text>]\n\
listmedia\n\
archive <chat-id>\n\
@@ -461,20 +455,6 @@ pub unsafe fn dc_cmdline(context: &Context, line: &str) -> Result<(), failure::E
============================================="
),
},
"auth" => {
if 0 == S_IS_AUTH {
let is_pw = context
.get_config(config::Config::MailPw)
.unwrap_or_default();
if arg1 == is_pw {
S_IS_AUTH = 1;
} else {
println!("Bad password.");
}
} else {
println!("Already authorized.");
}
}
"open" => {
ensure!(!arg1.is_empty(), "Argument <file> missing");
dc_close(context);
@@ -862,7 +842,7 @@ pub unsafe fn dc_cmdline(context: &Context, line: &str) -> Result<(), failure::E
}
"sendimage" | "sendfile" => {
ensure!(sel_chat.is_some(), "No chat selected.");
ensure!(!arg1.is_empty() && !arg2.is_empty(), "No file given.");
ensure!(!arg1.is_empty(), "No file given.");
let mut msg = dc_msg_new(
context,
@@ -873,7 +853,9 @@ pub unsafe fn dc_cmdline(context: &Context, line: &str) -> Result<(), failure::E
},
);
dc_msg_set_file(&mut msg, arg1_c, ptr::null());
dc_msg_set_text(&mut msg, arg2_c);
if !arg2.is_empty() {
dc_msg_set_text(&mut msg, arg2_c);
}
chat::send_msg(context, sel_chat.as_ref().unwrap().get_id(), &mut msg)?;
}
"listmsgs" => {

View File

@@ -389,8 +389,6 @@ impl Helper for DcHelper {}
fn main_0(args: Vec<String>) -> Result<(), failure::Error> {
let mut context = dc_context_new(Some(receive_event), ptr::null_mut(), Some("CLI".into()));
unsafe { dc_cmdline_skip_auth() };
if args.len() == 2 {
if unsafe { !dc_open(&mut context, &args[1], None) } {
println!("Error: Cannot open {}.", args[0],);

View File

@@ -0,0 +1,7 @@
# Seeds for failure cases proptest has generated in the past. It is
# automatically read and these particular cases re-run before any
# novel cases are generated.
#
# It is recommended to check this file in to source control so that
# everyone who runs the test benefits from these saved cases.
cc 679506fe9ac59df773f8cfa800fdab5f0a32fe49d2ab370394000a1aa5bc2a72 # shrinks to buf = "%0A"

View File

@@ -0,0 +1,9 @@
# Seeds for failure cases proptest has generated in the past. It is
# automatically read and these particular cases re-run before any
# novel cases are generated.
#
# It is recommended to check this file in to source control so that
# everyone who runs the test benefits from these saved cases.
cc c310754465ee0261807b96fa9bcc4861ff9aa286e94667524b5960c69f9b6620 # shrinks to buf = "", approx_chars = 0, do_unwrap = false
cc 5fd8d730b0a9cdf7308ce58818ca9aefc0255c9ba2a0878944fc48d43a67315b # shrinks to buf = "𑒀ὐ¢🜀\u{1e01b}A a🟠", approx_chars = 0, do_unwrap = false
cc c6a0029a54137a4b9efc9ef2ea6d9a7dd1d60d1c937bb472b66a174618ba8013 # shrinks to buf = "𐠈0Aᝮa𫝀®!ꫛa¡0A𐢧00𐹠®A 丽ⷐએ ", approx_chars = 0, do_unwrap = false

View File

@@ -483,6 +483,10 @@ class EventLogger:
def set_timeout(self, timeout):
self._timeout = timeout
def consume_events(self, check_error=True):
while not self._event_queue.empty():
self.get()
def get(self, timeout=None, check_error=True):
timeout = timeout or self._timeout
ev = self._event_queue.get(timeout=timeout)

View File

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

View File

@@ -147,6 +147,15 @@ class TestOfflineChat:
qr = chat.get_join_qr()
assert ac2.check_qr(qr).is_ask_verifygroup
def test_get_set_profile_image_simple(self, ac1, data):
chat = ac1.create_group_chat(name="title1")
p = data.get_path("d.png")
chat.set_profile_image(p)
p2 = chat.get_profile_image()
assert open(p, "rb").read() == open(p2, "rb").read()
chat.remove_profile_image()
assert chat.get_profile_image() is None
def test_delete_and_send_fails(self, ac1, chat1):
chat1.delete()
ac1._evlogger.get_matching("DC_EVENT_MSGS_CHANGED")
@@ -589,3 +598,59 @@ class TestOnlineAccount:
ch = ac2.qr_join_chat(qr)
assert ch.id >= 10
wait_securejoin_inviter_progress(ac1, 1000)
def test_set_get_profile_image(self, acfactory, data, lp):
ac1 = acfactory.get_online_configuring_account()
ac2 = acfactory.get_online_configuring_account()
wait_configuration_progress(ac2, 1000)
wait_configuration_progress(ac1, 1000)
lp.sec("create unpromoted group chat")
chat = ac1.create_group_chat("hello")
p = data.get_path("d.png")
lp.sec("ac1: set profile image on unpromoted chat")
chat.set_profile_image(p)
ac1._evlogger.get_matching("DC_EVENT_CHAT_MODIFIED")
assert not chat.is_promoted()
lp.sec("ac1: send text to promote chat (XXX without contact added)")
# XXX first promote the chat before adding contact
# because DC does not send out profile images for unpromoted chats
# otherwise
chat.send_text("ac1: initial message to promote chat (workaround)")
assert chat.is_promoted()
lp.sec("ac2: add ac1 to a chat so the message does not land in DEADDROP")
c1 = ac2.create_contact(email=ac1.get_config("addr"))
ac2.create_chat_by_contact(c1)
ev = ac2._evlogger.get_matching("DC_EVENT_MSGS_CHANGED")
lp.sec("ac1: add ac2 to promoted group chat")
c2 = ac1.create_contact(email=ac2.get_config("addr"))
chat.add_contact(c2)
lp.sec("ac1: send a first message to ac2")
chat.send_text("hi")
assert chat.is_promoted()
lp.sec("ac2: wait for receiving message from ac1")
ev = ac2._evlogger.get_matching("DC_EVENT_INCOMING_MSG")
msg_in = ac2.get_message_by_id(ev[2])
assert not msg_in.chat.is_deaddrop()
lp.sec("ac2: create chat and read profile image")
chat2 = ac2.create_chat_by_message(msg_in)
p2 = chat2.get_profile_image()
assert p2 is not None
assert open(p2, "rb").read() == open(p, "rb").read()
ac2._evlogger.consume_events()
ac1._evlogger.consume_events()
lp.sec("ac2: delete profile image from chat")
chat2.remove_profile_image()
ev = ac1._evlogger.get_matching("DC_EVENT_INCOMING_MSG")
assert ev[1] == chat.id
chat1b = ac1.create_chat_by_message(ev[2])
assert chat1b.get_profile_image() is None
assert chat.get_profile_image() is None

View File

@@ -1,5 +1,5 @@
use std::ffi::CString;
use std::path::Path;
use std::path::{Path, PathBuf};
use crate::chatlist::*;
use crate::constants::*;
@@ -72,15 +72,15 @@ impl<'a> Chat<'a> {
},
Ok(mut chat) => {
match chat.id {
1 => {
DC_CHAT_ID_DEADDROP => {
chat.name = chat.context.stock_str(StockMessage::DeadDrop).into();
}
6 => {
DC_CHAT_ID_ARCHIVED_LINK => {
let tempname = chat.context.stock_str(StockMessage::ArchivedChats);
let cnt = dc_get_archived_cnt(chat.context);
chat.name = format!("{} ({})", tempname, cnt);
}
5 => {
DC_CHAT_ID_STARRED => {
chat.name = chat.context.stock_str(StockMessage::StarredMsgs).into();
}
_ => {
@@ -171,57 +171,26 @@ impl<'a> Chat<'a> {
return "Err".into();
}
unsafe fn get_parent_mime_headers(
&self,
parent_rfc724_mid: *mut *mut libc::c_char,
parent_in_reply_to: *mut *mut libc::c_char,
parent_references: *mut *mut libc::c_char,
) -> Result<(), Error> {
if !(parent_rfc724_mid.is_null()
|| parent_in_reply_to.is_null()
|| parent_references.is_null())
{
// prefer a last message that isn't from us
let next = self
.context
.sql
.query_row(
"SELECT rfc724_mid, mime_in_reply_to, mime_references \
FROM msgs WHERE chat_id=?1 AND timestamp=(SELECT max(timestamp) \
FROM msgs WHERE chat_id=?1 AND from_id!=?2);",
params![self.id as i32, DC_CONTACT_ID_SELF as i32],
|row| {
*parent_rfc724_mid = row.get::<_, String>(0)?.strdup();
*parent_in_reply_to = row.get::<_, String>(1)?.strdup();
*parent_references = row.get::<_, String>(2)?.strdup();
Ok(())
},
)
.is_ok();
pub fn get_parent_mime_headers(&self) -> Option<(String, String, String)> {
let collect = |row: &rusqlite::Row| Ok((row.get(0)?, row.get(1)?, row.get(2)?));
let params = params![self.id as i32, DC_CONTACT_ID_SELF as i32];
let sql = &self.context.sql;
let main_query = "SELECT rfc724_mid, mime_in_reply_to, mime_references \
FROM msgs WHERE chat_id=?1 AND timestamp=(SELECT max(timestamp) \
FROM msgs WHERE chat_id=?1 AND from_id!=?2);";
let fallback_query = "SELECT rfc724_mid, mime_in_reply_to, mime_references \
FROM msgs WHERE chat_id=?1 AND timestamp=(SELECT min(timestamp) \
FROM msgs WHERE chat_id=?1 AND from_id==?2);";
if !next {
self.context.sql.query_row(
"SELECT rfc724_mid, mime_in_reply_to, mime_references \
FROM msgs WHERE chat_id=?1 AND timestamp=(SELECT min(timestamp) \
FROM msgs WHERE chat_id=?1 AND from_id==?2);",
params![self.id as i32, DC_CONTACT_ID_SELF as i32],
|row| {
*parent_rfc724_mid = row.get::<_, String>(0)?.strdup();
*parent_in_reply_to = row.get::<_, String>(1)?.strdup();
*parent_references = row.get::<_, String>(2)?.strdup();
Ok(())
},
)?;
}
}
Ok(())
sql.query_row(main_query, params, collect)
.or_else(|_| sql.query_row(fallback_query, params, collect))
.ok()
}
pub unsafe fn get_profile_image(&self) -> Option<String> {
pub fn get_profile_image(&self) -> Option<PathBuf> {
if let Some(image_rel) = self.param.get(Param::ProfileImage) {
if !image_rel.is_empty() {
return Some(to_string(dc_get_abs_path(self.context, image_rel)));
return Some(dc_get_abs_path_safe(self.context, image_rel));
}
} else if self.typ == Chattype::Single {
let contacts = get_chat_contacts(self.context, self.id);
@@ -260,6 +229,10 @@ impl<'a> Chat<'a> {
self.param.get_int(Param::Unpromoted).unwrap_or_default() == 1
}
pub fn is_promoted(&self) -> bool {
!self.is_unpromoted()
}
pub fn is_verified(&self) -> bool {
(self.typ == Chattype::VerifiedGroup)
}
@@ -277,13 +250,8 @@ impl<'a> Chat<'a> {
) -> Result<u32, Error> {
let mut do_guarantee_e2ee: libc::c_int;
let e2ee_enabled: libc::c_int;
let mut OK_TO_CONTINUE = true;
let mut parent_rfc724_mid = ptr::null_mut();
let mut parent_references = ptr::null_mut();
let mut parent_in_reply_to = ptr::null_mut();
let mut new_rfc724_mid = ptr::null_mut();
let mut new_references = ptr::null_mut();
let mut new_in_reply_to = ptr::null_mut();
let mut new_references = "".into();
let mut new_in_reply_to = "".into();
let mut msg_id = 0;
let mut to_id = 0;
let mut location_id = 0;
@@ -293,7 +261,10 @@ impl<'a> Chat<'a> {
|| self.typ == Chattype::VerifiedGroup)
{
error!(context, 0, "Cannot send to chat type #{}.", self.typ,);
} else if (self.typ == Chattype::Group || self.typ == Chattype::VerifiedGroup)
return Ok(0);
}
if (self.typ == Chattype::Group || self.typ == Chattype::VerifiedGroup)
&& 0 == is_contact_in_chat(context, self.id, 1 as u32)
{
log_event!(
@@ -302,211 +273,184 @@ impl<'a> Chat<'a> {
0,
"Cannot send message; self not in group.",
);
} else {
if let Some(from) = context.sql.get_config(context, "configured_addr") {
let from_c = CString::yolo(from);
new_rfc724_mid = dc_create_outgoing_rfc724_mid(
if self.typ == Chattype::Group || self.typ == Chattype::VerifiedGroup {
self.grpid.strdup()
} else {
ptr::null_mut()
},
from_c.as_ptr(),
);
return Ok(0);
}
if self.typ == Chattype::Single {
if let Some(id) = context.sql.query_row_col(
context,
"SELECT contact_id FROM chats_contacts WHERE chat_id=?;",
params![self.id as i32],
0,
) {
to_id = id;
} else {
error!(
context,
0, "Cannot send message, contact for chat #{} not found.", self.id,
);
OK_TO_CONTINUE = false;
}
if let Some(from) = context.sql.get_config(context, "configured_addr") {
let new_rfc724_mid = {
let grpid = match self.typ {
Chattype::Group | Chattype::VerifiedGroup => Some(self.grpid.as_str()),
_ => None,
};
dc_create_outgoing_rfc724_mid_safe(grpid, &from)
};
if self.typ == Chattype::Single {
if let Some(id) = context.sql.query_row_col(
context,
"SELECT contact_id FROM chats_contacts WHERE chat_id=?;",
params![self.id as i32],
0,
) {
to_id = id;
} else {
if self.typ == Chattype::Group || self.typ == Chattype::VerifiedGroup {
if self.param.get_int(Param::Unpromoted).unwrap_or_default() == 1 {
self.param.remove(Param::Unpromoted);
self.update_param().unwrap();
}
error!(
context,
0, "Cannot send message, contact for chat #{} not found.", self.id,
);
return Ok(0);
}
} else {
if self.typ == Chattype::Group || self.typ == Chattype::VerifiedGroup {
if self.param.get_int(Param::Unpromoted).unwrap_or_default() == 1 {
self.param.remove(Param::Unpromoted);
self.update_param().unwrap();
}
}
if OK_TO_CONTINUE {
/* check if we can guarantee E2EE for this message.
if we guarantee E2EE, and circumstances change
so that E2EE is no longer available at a later point (reset, changed settings),
we do not send the message out at all */
do_guarantee_e2ee = 0;
e2ee_enabled = context
.sql
.get_config_int(context, "e2ee_enabled")
.unwrap_or_else(|| 1);
if 0 != e2ee_enabled
&& msg.param.get_int(Param::ForcePlaintext).unwrap_or_default() == 0
{
let mut can_encrypt = 1;
let mut all_mutual = 1;
}
let res = context.sql.query_row(
"SELECT ps.prefer_encrypted, c.addr \
FROM chats_contacts cc \
LEFT JOIN contacts c ON cc.contact_id=c.id \
LEFT JOIN acpeerstates ps ON c.addr=ps.addr \
WHERE cc.chat_id=? AND cc.contact_id>9;",
params![self.id],
|row| {
let state: String = row.get(1)?;
/* check if we can guarantee E2EE for this message.
if we guarantee E2EE, and circumstances change
so that E2EE is no longer available at a later point (reset, changed settings),
we do not send the message out at all */
do_guarantee_e2ee = 0;
e2ee_enabled = context
.sql
.get_config_int(context, "e2ee_enabled")
.unwrap_or_else(|| 1);
if 0 != e2ee_enabled
&& msg.param.get_int(Param::ForcePlaintext).unwrap_or_default() == 0
{
let mut can_encrypt = 1;
let mut all_mutual = 1;
if let Some(prefer_encrypted) = row.get::<_, Option<i32>>(0)? {
if prefer_encrypted != 1 {
info!(
context,
0,
"[autocrypt] peerstate for {} is {}",
state,
if prefer_encrypted == 0 {
"NOPREFERENCE"
} else {
"RESET"
},
);
all_mutual = 0;
}
} else {
info!(context, 0, "[autocrypt] no peerstate for {}", state,);
can_encrypt = 0;
all_mutual = 0;
}
Ok(())
},
);
match res {
Ok(_) => {}
Err(err) => {
warn!(context, 0, "chat: failed to load peerstates: {:?}", err);
}
}
if 0 != can_encrypt {
if 0 != all_mutual {
do_guarantee_e2ee = 1;
} else if last_msg_in_chat_encrypted(context, &context.sql, self.id) {
do_guarantee_e2ee = 1;
let res = context.sql.query_row(
"SELECT ps.prefer_encrypted, c.addr \
FROM chats_contacts cc \
LEFT JOIN contacts c ON cc.contact_id=c.id \
LEFT JOIN acpeerstates ps ON c.addr=ps.addr \
WHERE cc.chat_id=? AND cc.contact_id>9;",
params![self.id],
|row| {
let state: String = row.get(1)?;
if let Some(prefer_encrypted) = row.get::<_, Option<i32>>(0)? {
if prefer_encrypted != 1 {
info!(
context,
0,
"[autocrypt] peerstate for {} is {}",
state,
if prefer_encrypted == 0 {
"NOPREFERENCE"
} else {
"RESET"
},
);
all_mutual = 0;
}
} else {
info!(context, 0, "[autocrypt] no peerstate for {}", state,);
can_encrypt = 0;
all_mutual = 0;
}
Ok(())
},
);
match res {
Ok(_) => {}
Err(err) => {
warn!(context, 0, "chat: failed to load peerstates: {:?}", err);
}
if 0 != do_guarantee_e2ee {
msg.param.set_int(Param::GuranteeE2ee, 1);
}
if 0 != can_encrypt {
if 0 != all_mutual {
do_guarantee_e2ee = 1;
} else if last_msg_in_chat_encrypted(context, &context.sql, self.id) {
do_guarantee_e2ee = 1;
}
msg.param.remove(Param::ErroneousE2ee);
if !self.is_self_talk()
&& self
.get_parent_mime_headers(
&mut parent_rfc724_mid,
&mut parent_in_reply_to,
&mut parent_references,
)
.is_ok()
{
if !parent_rfc724_mid.is_null()
&& 0 != *parent_rfc724_mid.offset(0isize) as libc::c_int
{
new_in_reply_to = dc_strdup(parent_rfc724_mid)
}
if !parent_references.is_null() {
let space: *mut libc::c_char;
space = strchr(parent_references, ' ' as i32);
if !space.is_null() {
*space = 0 as libc::c_char
}
}
if !parent_references.is_null()
&& 0 != *parent_references.offset(0isize) as libc::c_int
&& !parent_rfc724_mid.is_null()
&& 0 != *parent_rfc724_mid.offset(0isize) as libc::c_int
{
new_references = dc_mprintf(
b"%s %s\x00" as *const u8 as *const libc::c_char,
parent_references,
parent_rfc724_mid,
)
} else if !parent_references.is_null()
&& 0 != *parent_references.offset(0isize) as libc::c_int
{
new_references = dc_strdup(parent_references)
} else if !parent_in_reply_to.is_null()
&& 0 != *parent_in_reply_to.offset(0isize) as libc::c_int
&& !parent_rfc724_mid.is_null()
&& 0 != *parent_rfc724_mid.offset(0isize) as libc::c_int
{
new_references = dc_mprintf(
b"%s %s\x00" as *const u8 as *const libc::c_char,
parent_in_reply_to,
parent_rfc724_mid,
)
} else if !parent_in_reply_to.is_null()
&& 0 != *parent_in_reply_to.offset(0isize) as libc::c_int
{
new_references = dc_strdup(parent_in_reply_to)
}
}
}
if 0 != do_guarantee_e2ee {
msg.param.set_int(Param::GuranteeE2ee, 1);
}
msg.param.remove(Param::ErroneousE2ee);
if !self.is_self_talk() {
if let Some((parent_rfc724_mid, parent_in_reply_to, parent_references)) =
self.get_parent_mime_headers()
{
if !parent_rfc724_mid.is_empty() {
new_in_reply_to = parent_rfc724_mid.clone();
}
let parent_references = if let Some(n) = parent_references.find(' ') {
&parent_references[0..n]
} else {
&parent_references
};
// add independent location to database
if msg.param.exists(Param::SetLatitude) {
if sql::execute(
context,
&context.sql,
"INSERT INTO locations \
(timestamp,from_id,chat_id, latitude,longitude,independent)\
VALUES (?,?,?, ?,?,1);",
params![
timestamp,
DC_CONTACT_ID_SELF,
self.id as i32,
msg.param.get_float(Param::SetLatitude).unwrap_or_default(),
msg.param.get_float(Param::SetLongitude).unwrap_or_default(),
],
)
.is_ok()
{
location_id = sql::get_rowid2(
context,
&context.sql,
"locations",
"timestamp",
timestamp,
"from_id",
DC_CONTACT_ID_SELF as i32,
);
}
if !parent_references.is_empty() && !parent_rfc724_mid.is_empty() {
new_references = format!("{} {}", parent_references, parent_rfc724_mid);
} else if !parent_references.is_empty() {
new_references = parent_references.to_string();
} else if !parent_in_reply_to.is_empty() && !parent_rfc724_mid.is_empty() {
new_references = format!("{} {}", parent_in_reply_to, parent_rfc724_mid);
} else if !parent_in_reply_to.is_empty() {
new_references = parent_in_reply_to.clone();
}
}
}
// add message to the database
// add independent location to database
if sql::execute(
if msg.param.exists(Param::SetLatitude) {
if sql::execute(
context,
&context.sql,
"INSERT INTO locations \
(timestamp,from_id,chat_id, latitude,longitude,independent)\
VALUES (?,?,?, ?,?,1);",
params![
timestamp,
DC_CONTACT_ID_SELF,
self.id as i32,
msg.param.get_float(Param::SetLatitude).unwrap_or_default(),
msg.param.get_float(Param::SetLongitude).unwrap_or_default(),
],
)
.is_ok()
{
location_id = sql::get_rowid2(
context,
&context.sql,
"locations",
"timestamp",
timestamp,
"from_id",
DC_CONTACT_ID_SELF as i32,
);
}
}
// add message to the database
if sql::execute(
context,
&context.sql,
"INSERT INTO msgs (rfc724_mid, chat_id, from_id, to_id, timestamp, type, state, txt, param, hidden, mime_in_reply_to, mime_references, location_id) VALUES (?,?,?,?,?, ?,?,?,?,?, ?,?,?);",
params![
as_str(new_rfc724_mid),
new_rfc724_mid,
self.id as i32,
1i32,
to_id as i32,
timestamp,
msg.type_0,
msg.state,
msg.text,
msg.text.as_ref().map_or("", String::as_str),
msg.param.to_string(),
msg.hidden,
to_string(new_in_reply_to),
to_string(new_references),
new_in_reply_to,
new_references,
location_id as i32,
]
).is_ok() {
@@ -515,7 +459,7 @@ impl<'a> Chat<'a> {
&context.sql,
"msgs",
"rfc724_mid",
as_str(new_rfc724_mid),
new_rfc724_mid,
);
} else {
error!(
@@ -525,19 +469,10 @@ impl<'a> Chat<'a> {
self.id,
);
}
}
} else {
error!(context, 0, "Cannot send message, not configured.",);
}
} else {
error!(context, 0, "Cannot send message, not configured.",);
}
free(parent_rfc724_mid as *mut libc::c_void);
free(parent_in_reply_to as *mut libc::c_void);
free(parent_references as *mut libc::c_void);
free(new_rfc724_mid as *mut libc::c_void);
free(new_in_reply_to as *mut libc::c_void);
free(new_references as *mut libc::c_void);
Ok(msg_id)
}
}
@@ -1551,7 +1486,10 @@ pub unsafe fn remove_contact_from_chat(
"bad chat_id = {} <= 9",
chat_id
);
ensure!(contact_id != DC_CONTACT_ID_SELF, "Cannot remove self");
ensure!(
contact_id > DC_CONTACT_ID_LAST_SPECIAL || contact_id == DC_CONTACT_ID_SELF,
"Cannot remove special contact"
);
let mut msg = dc_msg_new_untyped(context);
let mut success = false;
@@ -1570,7 +1508,7 @@ pub unsafe fn remove_contact_from_chat(
} else {
/* we should respect this - whatever we send to the group, it gets discarded anyway! */
if let Ok(contact) = Contact::get_by_id(context, contact_id) {
if chat.param.get_int(Param::Unpromoted).unwrap_or_default() == 0 {
if chat.is_promoted() {
msg.type_0 = Viewtype::Text;
if contact.id == DC_CONTACT_ID_SELF {
set_group_explicitly_left(context, chat.grpid).unwrap();
@@ -1678,7 +1616,7 @@ pub unsafe fn set_chat_name(
)
.is_ok()
{
if chat.param.get_int(Param::Unpromoted).unwrap_or_default() == 0 {
if chat.is_promoted() {
msg.type_0 = Viewtype::Text;
msg.text = Some(context.stock_system_msg(
StockMessage::MsgGrpName,
@@ -1715,83 +1653,62 @@ pub unsafe fn set_chat_name(
}
#[allow(non_snake_case)]
pub unsafe fn set_chat_profile_image(
pub fn set_chat_profile_image(
context: &Context,
chat_id: u32,
new_image: impl AsRef<str>,
new_image: impl AsRef<str>, // XXX use PathBuf
) -> Result<(), Error> {
ensure!(chat_id > DC_CHAT_ID_LAST_SPECIAL, "Invalid chat ID");
let mut OK_TO_CONTINUE = true;
let mut success = false;
let mut chat = Chat::load_from_db(context, chat_id)?;
let mut msg = dc_msg_new_untyped(context);
let mut new_image_rel = None;
if real_group_exists(context, chat_id) {
if !(is_contact_in_chat(context, chat_id, 1i32 as u32) == 1i32) {
/* we should respect this - whatever we send to the group, it gets discarded anyway! */
if !(is_contact_in_chat(context, chat_id, DC_CONTACT_ID_SELF) == 1i32) {
log_event!(
context,
Event::ERROR_SELF_NOT_IN_GROUP,
0,
"Cannot set chat profile image; self not in group.",
);
bail!("Failed to set profile image");
}
let mut new_image_rel: String;
if !new_image.as_ref().is_empty() {
new_image_rel = new_image.as_ref().to_string();
if !dc_make_rel_and_copy(context, &mut new_image_rel) {
bail!("Failed to get relative path for profile image");
}
} else {
/* we should respect this - whatever we send to the group, it gets discarded anyway! */
if !new_image.as_ref().is_empty() {
let mut img = new_image.as_ref().to_string();
if !dc_make_rel_and_copy(context, &mut img) {
OK_TO_CONTINUE = false;
}
new_image_rel = Some(img);
} else {
OK_TO_CONTINUE = false;
}
new_image_rel = "".to_string();
}
if OK_TO_CONTINUE {
if let Some(ref new_image_rel) = new_image_rel {
chat.param.set(Param::ProfileImage, new_image_rel);
}
if chat.update_param().is_ok() {
if chat.param.get_int(Param::Unpromoted).unwrap_or_default() == 0 {
msg.param.set_int(Param::Cmd, 3);
if let Some(ref new_image_rel) = new_image_rel {
msg.param.set(Param::Arg, new_image_rel);
}
msg.type_0 = Viewtype::Text;
msg.text = Some(context.stock_system_msg(
if new_image_rel.is_some() {
StockMessage::MsgGrpImgChanged
} else {
StockMessage::MsgGrpImgDeleted
},
"",
"",
DC_CONTACT_ID_SELF,
));
msg.id = send_msg(context, chat_id, &mut msg).unwrap_or_default();
context.call_cb(
Event::MSGS_CHANGED,
chat_id as uintptr_t,
msg.id as uintptr_t,
);
}
context.call_cb(
Event::CHAT_MODIFIED,
chat_id as uintptr_t,
0i32 as uintptr_t,
);
success = true;
chat.param.set(Param::ProfileImage, &new_image_rel);
if chat.update_param().is_ok() {
if chat.is_promoted() {
let mut msg = unsafe { dc_msg_new_untyped(context) };
msg.param.set_int(Param::Cmd, DC_CMD_GROUPIMAGE_CHANGED);
msg.type_0 = Viewtype::Text;
msg.text = Some(context.stock_system_msg(
if new_image_rel == "" {
msg.param.remove(Param::Arg);
StockMessage::MsgGrpImgDeleted
} else {
msg.param.set(Param::Arg, &new_image_rel);
StockMessage::MsgGrpImgChanged
},
"",
"",
DC_CONTACT_ID_SELF,
));
msg.id = send_msg(context, chat_id, &mut msg).unwrap_or_default();
emit_event!(context, Event::MSGS_CHANGED, chat_id, msg.id);
}
emit_event!(context, Event::CHAT_MODIFIED, chat_id, 0);
return Ok(());
}
}
if !success {
bail!("Failed to set profile image");
}
Ok(())
bail!("Failed to set profile image");
}
pub unsafe fn forward_msgs(

View File

@@ -2,8 +2,8 @@ use quick_xml;
use quick_xml::events::{BytesEnd, BytesStart, BytesText};
use crate::context::Context;
use crate::dc_loginparam::*;
use crate::dc_tools::*;
use crate::login_param::LoginParam;
use crate::x::*;
use super::read_autoconf_file;
@@ -13,10 +13,10 @@ use super::read_autoconf_file;
/* documentation: https://developer.mozilla.org/en-US/docs/Mozilla/Thunderbird/Autoconfiguration */
#[repr(C)]
struct moz_autoconfigure_t<'a> {
pub in_0: &'a dc_loginparam_t,
pub in_emaildomain: *mut libc::c_char,
pub in_emaillocalpart: *mut libc::c_char,
pub out: dc_loginparam_t,
pub in_0: &'a LoginParam,
pub in_emaildomain: &'a str,
pub in_emaillocalpart: &'a str,
pub out: LoginParam,
pub out_imap_set: libc::c_int,
pub out_smtp_set: libc::c_int,
pub tag_server: libc::c_int,
@@ -26,44 +26,37 @@ struct moz_autoconfigure_t<'a> {
pub unsafe fn moz_autoconfigure(
context: &Context,
url: &str,
param_in: &dc_loginparam_t,
) -> Option<dc_loginparam_t> {
let mut moz_ac = moz_autoconfigure_t {
in_0: param_in,
in_emaildomain: std::ptr::null_mut(),
in_emaillocalpart: std::ptr::null_mut(),
out: dc_loginparam_new(),
out_imap_set: 0,
out_smtp_set: 0,
tag_server: 0,
tag_config: 0,
};
let url_c = url.strdup();
let xml_raw = read_autoconf_file(context, url_c);
free(url_c as *mut libc::c_void);
param_in: &LoginParam,
) -> Option<LoginParam> {
let xml_raw = read_autoconf_file(context, url);
if xml_raw.is_null() {
return None;
}
moz_ac.in_emaillocalpart = param_in.addr.strdup();
let p = strchr(moz_ac.in_emaillocalpart, '@' as i32);
if p.is_null() {
// Split address into local part and domain part.
let p = param_in.addr.find("@");
if p.is_none() {
free(xml_raw as *mut libc::c_void);
free(moz_ac.in_emaildomain as *mut libc::c_void);
free(moz_ac.in_emaillocalpart as *mut libc::c_void);
return None;
}
*p = 0 as libc::c_char;
moz_ac.in_emaildomain = dc_strdup(p.offset(1isize));
let (in_emaillocalpart, in_emaildomain) = param_in.addr.split_at(p.unwrap());
let in_emaildomain = &in_emaildomain[1..];
let mut reader = quick_xml::Reader::from_str(as_str(xml_raw));
reader.trim_text(true);
let mut buf = Vec::new();
let mut moz_ac = moz_autoconfigure_t {
in_0: param_in,
in_emaildomain,
in_emaillocalpart,
out: LoginParam::new(),
out_imap_set: 0,
out_smtp_set: 0,
tag_server: 0,
tag_config: 0,
};
loop {
match reader.read_event(&mut buf) {
Ok(quick_xml::events::Event::Start(ref e)) => {
@@ -93,17 +86,13 @@ pub unsafe fn moz_autoconfigure(
|| moz_ac.out.send_server.is_empty()
|| moz_ac.out.send_port == 0
{
let r = dc_loginparam_get_readable(&moz_ac.out);
let r = moz_ac.out.to_string();
warn!(context, 0, "Bad or incomplete autoconfig: {}", r,);
free(xml_raw as *mut libc::c_void);
free(moz_ac.in_emaildomain as *mut libc::c_void);
free(moz_ac.in_emaillocalpart as *mut libc::c_void);
return None;
}
free(xml_raw as *mut libc::c_void);
free(moz_ac.in_emaildomain as *mut libc::c_void);
free(moz_ac.in_emaillocalpart as *mut libc::c_void);
Some(moz_ac.out)
}
@@ -115,8 +104,8 @@ fn moz_autoconfigure_text_cb<B: std::io::BufRead>(
let val = event.unescape_and_decode(reader).unwrap_or_default();
let addr = &moz_ac.in_0.addr;
let email_local = as_str(moz_ac.in_emaillocalpart);
let email_domain = as_str(moz_ac.in_emaildomain);
let email_local = moz_ac.in_emaillocalpart;
let email_domain = moz_ac.in_emaildomain;
let val = val
.trim()

View File

@@ -2,8 +2,8 @@ use quick_xml;
use quick_xml::events::{BytesEnd, BytesStart, BytesText};
use crate::context::Context;
use crate::dc_loginparam::*;
use crate::dc_tools::*;
use crate::login_param::LoginParam;
use crate::x::*;
use std::ptr;
@@ -13,8 +13,8 @@ use super::read_autoconf_file;
******************************************************************************/
#[repr(C)]
struct outlk_autodiscover_t<'a> {
pub in_0: &'a dc_loginparam_t,
pub out: dc_loginparam_t,
pub in_0: &'a LoginParam,
pub out: LoginParam,
pub out_imap_set: libc::c_int,
pub out_smtp_set: libc::c_int,
pub tag_config: libc::c_int,
@@ -25,13 +25,13 @@ struct outlk_autodiscover_t<'a> {
pub unsafe fn outlk_autodiscover(
context: &Context,
url__: &str,
param_in: &dc_loginparam_t,
) -> Option<dc_loginparam_t> {
param_in: &LoginParam,
) -> Option<LoginParam> {
let mut xml_raw: *mut libc::c_char = ptr::null_mut();
let mut url = url__.strdup();
let mut outlk_ad = outlk_autodiscover_t {
in_0: param_in,
out: dc_loginparam_new(),
out: LoginParam::new(),
out_imap_set: 0,
out_smtp_set: 0,
tag_config: 0,
@@ -50,7 +50,7 @@ pub unsafe fn outlk_autodiscover(
0,
::std::mem::size_of::<outlk_autodiscover_t>(),
);
xml_raw = read_autoconf_file(context, url);
xml_raw = read_autoconf_file(context, as_str(url));
if xml_raw.is_null() {
ok_to_continue = false;
break;
@@ -108,7 +108,7 @@ pub unsafe fn outlk_autodiscover(
|| outlk_ad.out.send_server.is_empty()
|| outlk_ad.out.send_port == 0
{
let r = dc_loginparam_get_readable(&outlk_ad.out);
let r = outlk_ad.out.to_string();
warn!(context, 0, "Bad or incomplete autoconfig: {}", r,);
free(url as *mut libc::c_void);
free(xml_raw as *mut libc::c_void);

View File

@@ -3,11 +3,11 @@ use percent_encoding::{utf8_percent_encode, NON_ALPHANUMERIC};
use crate::constants::Event;
use crate::constants::DC_CREATE_MVBOX;
use crate::context::Context;
use crate::dc_loginparam::*;
use crate::dc_tools::*;
use crate::e2ee;
use crate::imap::*;
use crate::job::*;
use crate::login_param::LoginParam;
use crate::oauth2::*;
use crate::param::Params;
use crate::types::*;
@@ -69,7 +69,7 @@ pub unsafe fn dc_job_do_DC_JOB_CONFIGURE_IMAP(context: &Context, _job: &Job) {
let mut smtp_connected_here = false;
let mut ongoing_allocated_here = false;
let mut param_autoconfig: Option<dc_loginparam_t> = None;
let mut param_autoconfig: Option<LoginParam> = None;
if dc_alloc_ongoing(context) {
ongoing_allocated_here = true;
if !context.sql.is_open() {
@@ -95,7 +95,7 @@ pub unsafe fn dc_job_do_DC_JOB_CONFIGURE_IMAP(context: &Context, _job: &Job) {
let s = s_a.read().unwrap();
// Variables that are shared between steps:
let mut param: dc_loginparam_t = dc_loginparam_read(context, &context.sql, "");
let mut param = LoginParam::from_database(context, "");
// need all vars here to be mutable because rust thinks the same step could be called multiple times
// and also initialize, because otherwise rust thinks it's used while unitilized, even if thats not the case as the loop goes only forward
let mut param_domain = "undefined.undefined".to_owned();
@@ -256,8 +256,7 @@ pub unsafe fn dc_job_do_DC_JOB_CONFIGURE_IMAP(context: &Context, _job: &Job) {
12 => {
progress!(context, 500);
if let Some(ref cfg) = param_autoconfig {
let r = dc_loginparam_get_readable(cfg);
info!(context, 0, "Got autoconfig: {}", r);
info!(context, 0, "Got autoconfig: {}", &cfg);
if !cfg.mail_user.is_empty() {
param.mail_user = cfg.mail_user.clone();
}
@@ -358,8 +357,7 @@ pub unsafe fn dc_job_do_DC_JOB_CONFIGURE_IMAP(context: &Context, _job: &Job) {
ok_to_continue8 = true;
break;
}
let r_0 = dc_loginparam_get_readable(&param);
info!(context, 0, "Trying: {}", r_0,);
info!(context, 0, "Trying: {}", &param);
if context.inbox.read().unwrap().connect(context, &param) {
ok_to_continue8 = true;
@@ -377,8 +375,7 @@ pub unsafe fn dc_job_do_DC_JOB_CONFIGURE_IMAP(context: &Context, _job: &Job) {
progress!(context, 650 + username_variation * 30);
param.server_flags &= !(0x100 | 0x200 | 0x400);
param.server_flags |= 0x100;
let r_1 = dc_loginparam_get_readable(&param);
info!(context, 0, "Trying: {}", r_1,);
info!(context, 0, "Trying: {}", &param);
if context.inbox.read().unwrap().connect(context, &param) {
ok_to_continue8 = true;
@@ -391,8 +388,7 @@ pub unsafe fn dc_job_do_DC_JOB_CONFIGURE_IMAP(context: &Context, _job: &Job) {
}
progress!(context, 660 + username_variation * 30);
param.mail_port = 143;
let r_2 = dc_loginparam_get_readable(&param);
info!(context, 0, "Trying: {}", r_2,);
info!(context, 0, "Trying: {}", &param);
if context.inbox.read().unwrap().connect(context, &param) {
ok_to_continue8 = true;
@@ -447,8 +443,7 @@ pub unsafe fn dc_job_do_DC_JOB_CONFIGURE_IMAP(context: &Context, _job: &Job) {
param.server_flags &= !(0x10000 | 0x20000 | 0x40000);
param.server_flags |= 0x10000;
param.send_port = 587;
let r_3 = dc_loginparam_get_readable(&param);
info!(context, 0, "Trying: {}", r_3,);
info!(context, 0, "Trying: {}", &param);
if !context
.smtp
@@ -464,8 +459,7 @@ pub unsafe fn dc_job_do_DC_JOB_CONFIGURE_IMAP(context: &Context, _job: &Job) {
param.server_flags &= !(0x10000 | 0x20000 | 0x40000);
param.server_flags |= 0x10000;
param.send_port = 25;
let r_4 = dc_loginparam_get_readable(&param);
info!(context, 0, "Trying: {}", r_4);
info!(context, 0, "Trying: {}", &param);
if !context
.smtp
@@ -517,12 +511,13 @@ pub unsafe fn dc_job_do_DC_JOB_CONFIGURE_IMAP(context: &Context, _job: &Job) {
17 => {
progress!(context, 910);
/* configuration success - write back the configured parameters with the "configured_" prefix; also write the "configured"-flag */
dc_loginparam_write(
context,
&param,
&context.sql,
"configured_", /*the trailing underscore is correct*/
);
param
.save_to_database(
context,
"configured_", /*the trailing underscore is correct*/
)
.ok();
context.sql.set_config_int(context, "configured", 1).ok();
true
}
@@ -635,7 +630,7 @@ pub fn dc_connect_to_configured_imap(context: &Context, imap: &Imap) -> libc::c_
{
warn!(context, 0, "Not configured, cannot connect.",);
} else {
let param = dc_loginparam_read(context, &context.sql, "configured_");
let param = LoginParam::from_database(context, "configured_");
// the trailing underscore is correct
if imap.connect(context, &param) {
@@ -663,11 +658,11 @@ pub fn dc_stop_ongoing_process(context: &Context) {
};
}
pub fn read_autoconf_file(context: &Context, url: *const libc::c_char) -> *mut libc::c_char {
info!(context, 0, "Testing {} ...", to_string(url));
pub fn read_autoconf_file(context: &Context, url: &str) -> *mut libc::c_char {
info!(context, 0, "Testing {} ...", url);
match reqwest::Client::new()
.get(as_str(url))
.get(url)
.send()
.and_then(|mut res| res.text())
{

View File

@@ -18,6 +18,12 @@ pub enum MoveState {
Moving = 3,
}
impl Default for MoveState {
fn default() -> Self {
MoveState::Undefined
}
}
// some defaults
const DC_E2EE_DEFAULT_ENABLED: i32 = 1;
pub const DC_MDNS_DEFAULT_ENABLED: i32 = 1;
@@ -71,7 +77,7 @@ pub const DC_CHAT_ID_TRASH: u32 = 3;
/// a message is just in creation but not yet assigned to a chat (eg. we may need the message ID to set up blobs; this avoids unready message to be sent and shown)
const DC_CHAT_ID_MSGS_IN_CREATION: u32 = 4;
/// virtual chat showing all messages flagged with msgs.starred=2
const DC_CHAT_ID_STARRED: u32 = 5;
pub const DC_CHAT_ID_STARRED: u32 = 5;
/// only an indicator in a chatlist
pub const DC_CHAT_ID_ARCHIVED_LINK: u32 = 6;
/// only an indicator in a chatlist
@@ -220,6 +226,12 @@ pub enum Viewtype {
File = 60,
}
impl Default for Viewtype {
fn default() -> Self {
Viewtype::Unknown
}
}
#[cfg(test)]
mod tests {
use super::*;
@@ -545,6 +557,6 @@ pub const DC_CMD_GROUPIMAGE_CHANGED: libc::c_int = 3;
pub const DC_CMD_MEMBER_ADDED_TO_GROUP: libc::c_int = 4;
pub const DC_CMD_MEMBER_REMOVED_FROM_GROUP: libc::c_int = 5;
pub const DC_CMD_AUTOCRYPT_SETUP_MESSAGE: libc::c_int = 6;
const DC_CMD_SECUREJOIN_MESSAGE: libc::c_int = 7;
pub const DC_CMD_SECUREJOIN_MESSAGE: libc::c_int = 7;
pub const DC_CMD_LOCATION_STREAMING_ENABLED: libc::c_int = 8;
const DC_CMD_LOCATION_ONLY: libc::c_int = 9;
pub const DC_CMD_LOCATION_ONLY: libc::c_int = 9;

View File

@@ -1,17 +1,17 @@
use deltachat_derive::*;
use itertools::Itertools;
use num_traits::{FromPrimitive, ToPrimitive};
use rusqlite;
use rusqlite::types::*;
use std::path::PathBuf;
use crate::aheader::EncryptPreference;
use crate::config::Config;
use crate::constants::*;
use crate::context::Context;
use crate::dc_loginparam::*;
use crate::dc_tools::*;
use crate::e2ee;
use crate::error::Result;
use crate::key::*;
use crate::login_param::LoginParam;
use crate::message::MessageState;
use crate::peerstate::*;
use crate::sql;
@@ -58,7 +58,9 @@ pub struct Contact<'a> {
}
/// Possible origins of a contact.
#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, FromPrimitive, ToPrimitive)]
#[derive(
Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, FromPrimitive, ToPrimitive, FromSql, ToSql,
)]
#[repr(i32)]
pub enum Origin {
Unknown = 0,
@@ -96,20 +98,9 @@ pub enum Origin {
ManuallyCreated = 0x4000000,
}
impl ToSql for Origin {
fn to_sql(&self) -> rusqlite::Result<ToSqlOutput> {
let num: i64 = self
.to_i64()
.expect("impossible: Origin -> i64 conversion failed");
Ok(ToSqlOutput::Owned(Value::Integer(num)))
}
}
impl FromSql for Origin {
fn column_result(col: ValueRef) -> FromSqlResult<Self> {
let inner = FromSql::column_result(col)?;
FromPrimitive::from_i64(inner).ok_or(FromSqlError::InvalidType)
impl Default for Origin {
fn default() -> Self {
Origin::Unknown
}
}
@@ -341,19 +332,22 @@ impl<'a> Contact<'a> {
let row_id = row.get(0)?;
let row_name: String = row.get(1)?;
let row_addr: String = row.get(2)?;
let row_origin = row.get(3)?;
let row_origin: Origin = row.get(3)?;
let row_authname: String = row.get(4)?;
if !name.as_ref().is_empty() && !row_name.is_empty() {
if origin >= row_origin && name.as_ref() != row_name {
if !name.as_ref().is_empty() {
if !row_name.is_empty() {
if origin >= row_origin && name.as_ref() != row_name {
update_name = true;
}
} else {
update_name = true;
}
} else {
update_name = true;
}
if origin == Origin::IncomingUnknownFrom && name.as_ref() != row_authname {
update_authname = true;
if origin == Origin::IncomingUnknownFrom && name.as_ref() != row_authname {
update_authname = true;
}
}
Ok((row_id, row_name, row_addr, row_origin, row_authname))
},
) {
@@ -393,7 +387,7 @@ impl<'a> Contact<'a> {
context,
&context.sql,
"UPDATE chats SET name=? WHERE type=? AND id IN(SELECT chat_id FROM chats_contacts WHERE contact_id=?);",
params![name.as_ref(), 100, row_id]
params![name.as_ref(), Chattype::Single, row_id]
).ok();
}
sth_modified = Modifier::Modified;
@@ -431,19 +425,13 @@ impl<'a> Contact<'a> {
/// To add a single contact entered by the user, you should prefer `Contact::create`,
/// however, for adding a bunch of addresses, this function is _much_ faster.
///
/// The `adr_book` is a multiline string in the format `Name one\nAddress one\nName two\nAddress two`.
/// The `addr_book` is a multiline string in the format `Name one\nAddress one\nName two\nAddress two`.
///
/// Returns the number of modified contacts.
pub fn add_address_book(context: &Context, adr_book: impl AsRef<str>) -> Result<usize> {
pub fn add_address_book(context: &Context, addr_book: impl AsRef<str>) -> Result<usize> {
let mut modify_cnt = 0;
for chunk in &adr_book.as_ref().lines().chunks(2) {
let chunk = chunk.collect::<Vec<_>>();
if chunk.len() < 2 {
break;
}
let name = chunk[0];
let addr = chunk[1];
for (name, addr) in split_address_book(addr_book.as_ref()).into_iter() {
let name = normalize_name(name);
let (_, modified) = Contact::add_or_lookup(context, name, addr, Origin::AdressBook)?;
if modified != Modifier::None {
@@ -589,7 +577,7 @@ impl<'a> Contact<'a> {
if let Ok(contact) = Contact::load_from_db(context, contact_id) {
let peerstate = Peerstate::from_addr(context, &context.sql, &contact.addr);
let loginparam = dc_loginparam_read(context, &context.sql, "configured_");
let loginparam = LoginParam::from_database(context, "configured_");
let mut self_key = Key::from_self_public(context, &loginparam.addr, &context.sql);
@@ -779,9 +767,11 @@ impl<'a> Contact<'a> {
/// Get the contact's profile image.
/// This is the image set by each remote user on their own
/// using dc_set_config(context, "selfavatar", image).
pub fn get_profile_image(&self) -> Option<String> {
pub fn get_profile_image(&self) -> Option<PathBuf> {
if self.id == DC_CONTACT_ID_SELF {
return self.context.get_config(Config::Selfavatar);
if let Some(p) = self.context.get_config(Config::Selfavatar) {
return Some(PathBuf::from(p));
}
}
// TODO: else get image_abs from contact param
None
@@ -1043,6 +1033,21 @@ pub fn addr_equals_self(context: &Context, addr: impl AsRef<str>) -> bool {
false
}
fn split_address_book(book: &str) -> Vec<(&str, &str)> {
book.lines()
.chunks(2)
.into_iter()
.filter_map(|mut chunk| {
let name = chunk.next().unwrap();
let addr = match chunk.next() {
Some(a) => a,
None => return None,
};
Some((name, addr))
})
.collect()
}
#[cfg(test)]
mod tests {
use super::*;
@@ -1078,4 +1083,14 @@ mod tests {
fn test_get_first_name() {
assert_eq!(get_first_name("John Doe"), "John");
}
#[test]
fn test_split_address_book() {
let book = "Name one\nAddress one\nName two\nAddress two\nrest name";
let list = split_address_book(&book);
assert_eq!(
list,
vec![("Name one", "Address one"), ("Name two", "Address two")]
)
}
}

View File

@@ -3,13 +3,13 @@ use std::sync::{Arc, Condvar, Mutex, RwLock};
use crate::chat::*;
use crate::constants::*;
use crate::contact::*;
use crate::dc_loginparam::*;
use crate::dc_receive_imf::*;
use crate::dc_tools::*;
use crate::imap::*;
use crate::job::*;
use crate::job_thread::JobThread;
use crate::key::*;
use crate::login_param::LoginParam;
use crate::lot::Lot;
use crate::message::*;
use crate::param::Params;
@@ -331,8 +331,8 @@ pub unsafe fn dc_get_blobdir(context: &Context) -> *mut libc::c_char {
pub unsafe fn dc_get_info(context: &Context) -> *mut libc::c_char {
let unset = "0";
let l = dc_loginparam_read(context, &context.sql, "");
let l2 = dc_loginparam_read(context, &context.sql, "configured_");
let l = LoginParam::from_database(context, "");
let l2 = LoginParam::from_database(context, "configured_");
let displayname = context.sql.get_config(context, "displayname");
let chats = get_chat_cnt(context) as usize;
let real_msgs = dc_get_real_msg_cnt(context) as usize;
@@ -376,8 +376,6 @@ pub unsafe fn dc_get_info(context: &Context) -> *mut libc::c_char {
"<Not yet calculated>".into()
};
let l_readable_str = dc_loginparam_get_readable(&l);
let l2_readable_str = dc_loginparam_get_readable(&l2);
let inbox_watch = context
.sql
.get_config_int(context, "inbox_watch")
@@ -457,8 +455,8 @@ pub unsafe fn dc_get_info(context: &Context) -> *mut libc::c_char {
},
displayname.unwrap_or_else(|| unset.into()),
is_configured,
l_readable_str,
l2_readable_str,
l,
l2,
inbox_watch,
sentbox_watch,
mvbox_watch,

View File

@@ -211,12 +211,8 @@ pub fn dc_render_setup_file(context: &Context, passphrase: &str) -> Result<Strin
let encr = {
let private_key_asc_c = CString::yolo(private_key_asc);
let passphrase_c = CString::yolo(passphrase);
dc_pgp_symm_encrypt(
passphrase_c.as_ptr(),
private_key_asc_c.as_ptr() as *const libc::c_void,
private_key_asc_c.as_bytes().len(),
)
.ok_or(format_err!("Failed to encrypt private key."))?
dc_pgp_symm_encrypt(passphrase_c.as_ptr(), private_key_asc_c.as_bytes())
.ok_or(format_err!("Failed to encrypt private key."))?
};
let replacement = format!(
concat!(
@@ -449,9 +445,10 @@ pub unsafe fn dc_decrypt_setup_file(
|| binary_bytes == 0)
{
/* decrypt symmetrically */
if let Some(plain) =
dc_pgp_symm_decrypt(passphrase, binary as *const libc::c_void, binary_bytes)
{
if let Some(plain) = dc_pgp_symm_decrypt(
passphrase,
std::slice::from_raw_parts(binary as *const u8, binary_bytes),
) {
let payload_c = CString::new(plain).unwrap();
payload = strdup(payload_c.as_ptr());
}

View File

@@ -1,214 +0,0 @@
use std::borrow::Cow;
use crate::context::Context;
use crate::sql::Sql;
#[derive(Default, Debug)]
#[allow(non_camel_case_types)]
pub struct dc_loginparam_t {
pub addr: String,
pub mail_server: String,
pub mail_user: String,
pub mail_pw: String,
pub mail_port: i32,
pub send_server: String,
pub send_user: String,
pub send_pw: String,
pub send_port: i32,
pub server_flags: i32,
}
impl dc_loginparam_t {
pub fn addr_str(&self) -> &str {
self.addr.as_str()
}
}
pub fn dc_loginparam_new() -> dc_loginparam_t {
Default::default()
}
pub fn dc_loginparam_read(
context: &Context,
sql: &Sql,
prefix: impl AsRef<str>,
) -> dc_loginparam_t {
let prefix = prefix.as_ref();
let key = format!("{}addr", prefix);
let addr = sql
.get_config(context, key)
.unwrap_or_default()
.trim()
.to_string();
let key = format!("{}mail_server", prefix);
let mail_server = sql.get_config(context, key).unwrap_or_default();
let key = format!("{}mail_port", prefix);
let mail_port = sql.get_config_int(context, key).unwrap_or_default();
let key = format!("{}mail_user", prefix);
let mail_user = sql.get_config(context, key).unwrap_or_default();
let key = format!("{}mail_pw", prefix);
let mail_pw = sql.get_config(context, key).unwrap_or_default();
let key = format!("{}send_server", prefix);
let send_server = sql.get_config(context, key).unwrap_or_default();
let key = format!("{}send_port", prefix);
let send_port = sql.get_config_int(context, key).unwrap_or_default();
let key = format!("{}send_user", prefix);
let send_user = sql.get_config(context, key).unwrap_or_default();
let key = format!("{}send_pw", prefix);
let send_pw = sql.get_config(context, key).unwrap_or_default();
let key = format!("{}server_flags", prefix);
let server_flags = sql.get_config_int(context, key).unwrap_or_default();
dc_loginparam_t {
addr: addr.to_string(),
mail_server,
mail_user,
mail_pw,
mail_port,
send_server,
send_user,
send_pw,
send_port,
server_flags,
}
}
pub fn dc_loginparam_write(
context: &Context,
loginparam: &dc_loginparam_t,
sql: &Sql,
prefix: impl AsRef<str>,
) {
let prefix = prefix.as_ref();
let key = format!("{}addr", prefix);
sql.set_config(context, key, Some(&loginparam.addr)).ok();
let key = format!("{}mail_server", prefix);
sql.set_config(context, key, Some(&loginparam.mail_server))
.ok();
let key = format!("{}mail_port", prefix);
sql.set_config_int(context, key, loginparam.mail_port).ok();
let key = format!("{}mail_user", prefix);
sql.set_config(context, key, Some(&loginparam.mail_user))
.ok();
let key = format!("{}mail_pw", prefix);
sql.set_config(context, key, Some(&loginparam.mail_pw)).ok();
let key = format!("{}send_server", prefix);
sql.set_config(context, key, Some(&loginparam.send_server))
.ok();
let key = format!("{}send_port", prefix);
sql.set_config_int(context, key, loginparam.send_port).ok();
let key = format!("{}send_user", prefix);
sql.set_config(context, key, Some(&loginparam.send_user))
.ok();
let key = format!("{}send_pw", prefix);
sql.set_config(context, key, Some(&loginparam.send_pw)).ok();
let key = format!("{}server_flags", prefix);
sql.set_config_int(context, key, loginparam.server_flags)
.ok();
}
fn unset_empty(s: &String) -> Cow<String> {
if s.is_empty() {
Cow::Owned("unset".to_string())
} else {
Cow::Borrowed(s)
}
}
pub fn dc_loginparam_get_readable(loginparam: &dc_loginparam_t) -> String {
let unset = "0";
let pw = "***";
let flags_readable = get_readable_flags(loginparam.server_flags);
format!(
"{} {}:{}:{}:{} {}:{}:{}:{} {}",
unset_empty(&loginparam.addr),
unset_empty(&loginparam.mail_user),
if !loginparam.mail_pw.is_empty() {
pw
} else {
unset
},
unset_empty(&loginparam.mail_server),
loginparam.mail_port,
unset_empty(&loginparam.send_user),
if !loginparam.send_pw.is_empty() {
pw
} else {
unset
},
unset_empty(&loginparam.send_server),
loginparam.send_port,
flags_readable,
)
}
fn get_readable_flags(flags: i32) -> String {
let mut res = String::new();
for bit in 0..31 {
if 0 != flags & 1 << bit {
let mut flag_added = 0;
if 1 << bit == 0x2 {
res += "OAUTH2 ";
flag_added = 1;
}
if 1 << bit == 0x4 {
res += "AUTH_NORMAL ";
flag_added = 1;
}
if 1 << bit == 0x100 {
res += "IMAP_STARTTLS ";
flag_added = 1;
}
if 1 << bit == 0x200 {
res += "IMAP_SSL ";
flag_added = 1;
}
if 1 << bit == 0x400 {
res += "IMAP_PLAIN ";
flag_added = 1;
}
if 1 << bit == 0x10000 {
res += "SMTP_STARTTLS ";
flag_added = 1
}
if 1 << bit == 0x20000 {
res += "SMTP_SSL ";
flag_added = 1
}
if 1 << bit == 0x40000 {
res += "SMTP_PLAIN ";
flag_added = 1
}
if 0 == flag_added {
res += &format!("{:#0x}", 1 << bit);
}
}
}
if res.is_empty() {
res += "0";
}
res
}

View File

@@ -148,7 +148,7 @@ pub unsafe fn dc_mimefactory_load_msg(
for row in rows {
let (authname, addr) = row?;
let addr_c = addr.strdup();
if clist_search_string_nocase(factory.recipients_addr, addr_c) == 0 {
if !clist_search_string_nocase(factory.recipients_addr, addr_c) {
clist_insert_after(
factory.recipients_names,
(*factory.recipients_names).last,
@@ -183,7 +183,7 @@ pub unsafe fn dc_mimefactory_load_msg(
.unwrap_or_default();
if !email_to_remove.is_empty() && email_to_remove != self_addr {
if clist_search_string_nocase(factory.recipients_addr, email_to_remove_c) == 0 {
if !clist_search_string_nocase(factory.recipients_addr, email_to_remove_c) {
clist_insert_after(
factory.recipients_names,
(*factory.recipients_names).last,
@@ -548,7 +548,7 @@ pub unsafe fn dc_mimefactory_render(factory: &mut dc_mimefactory_t) -> libc::c_i
dc_encode_header_words(name.as_ptr()),
),
);
if command == 5 {
if command == DC_CMD_MEMBER_REMOVED_FROM_GROUP {
let email_to_remove = factory
.msg
.param
@@ -567,7 +567,7 @@ pub unsafe fn dc_mimefactory_render(factory: &mut dc_mimefactory_t) -> libc::c_i
),
);
}
} else if command == 4 {
} else if command == DC_CMD_MEMBER_ADDED_TO_GROUP {
let msg = &factory.msg;
do_gossip = 1;
let email_to_add = msg.param.get(Param::Arg).unwrap_or_default().strdup();
@@ -599,7 +599,7 @@ pub unsafe fn dc_mimefactory_render(factory: &mut dc_mimefactory_t) -> libc::c_i
),
);
}
} else if command == 2 {
} else if command == DC_CMD_GROUPNAME_CHANGED {
let msg = &factory.msg;
let value_to_add = msg.param.get(Param::Arg).unwrap_or_default().strdup();
@@ -612,7 +612,7 @@ pub unsafe fn dc_mimefactory_render(factory: &mut dc_mimefactory_t) -> libc::c_i
value_to_add,
),
);
} else if command == 3 {
} else if command == DC_CMD_GROUPIMAGE_CHANGED {
let msg = &factory.msg;
grpimage = msg.param.get(Param::Arg);
if grpimage.is_none() {
@@ -626,7 +626,7 @@ pub unsafe fn dc_mimefactory_render(factory: &mut dc_mimefactory_t) -> libc::c_i
}
}
}
if command == 8 {
if command == DC_CMD_LOCATION_STREAMING_ENABLED {
mailimf_fields_add(
imf_fields,
mailimf_field_new_custom(
@@ -637,7 +637,7 @@ pub unsafe fn dc_mimefactory_render(factory: &mut dc_mimefactory_t) -> libc::c_i
),
);
}
if command == 6 {
if command == DC_CMD_AUTOCRYPT_SETUP_MESSAGE {
mailimf_fields_add(
imf_fields,
mailimf_field_new_custom(
@@ -650,7 +650,7 @@ pub unsafe fn dc_mimefactory_render(factory: &mut dc_mimefactory_t) -> libc::c_i
.stock_str(StockMessage::AcSetupMsgBody)
.strdup();
}
if command == 7 {
if command == DC_CMD_SECUREJOIN_MESSAGE {
let msg = &factory.msg;
let step = msg.param.get(Param::Arg).unwrap_or_default().strdup();
if strlen(step) > 0 {
@@ -726,6 +726,7 @@ pub unsafe fn dc_mimefactory_render(factory: &mut dc_mimefactory_t) -> libc::c_i
}
}
if let Some(grpimage) = grpimage {
info!(factory.context, 0, "setting group image '{}'", grpimage);
let mut meta = dc_msg_new_untyped(factory.context);
meta.type_0 = Viewtype::Image;
meta.param.set(Param::File, grpimage);
@@ -1083,7 +1084,7 @@ unsafe fn get_subject(
} else {
b"\x00" as *const u8 as *const libc::c_char
};
if msg.param.get_int(Param::Cmd).unwrap_or_default() == 6 {
if msg.param.get_int(Param::Cmd).unwrap_or_default() == DC_CMD_AUTOCRYPT_SETUP_MESSAGE {
ret = context.stock_str(StockMessage::AcSetupMsgSubject).strdup()
} else if chat.typ == Chattype::Group || chat.typ == Chattype::VerifiedGroup {
ret = format!(
@@ -1218,7 +1219,7 @@ unsafe fn build_body_file(
/* create mime part, for Content-Disposition, see RFC 2183.
`Content-Disposition: attachment` seems not to make a difference to `Content-Disposition: inline` at least on tested Thunderbird and Gma'l in 2017.
But I've heard about problems with inline and outl'k, so we just use the attachment-type until we run into other problems ... */
needs_ext = dc_needs_ext_header(filename_to_send);
needs_ext = dc_needs_ext_header(as_str(filename_to_send));
mime_fields = mailmime_fields_new_filename(
MAILMIME_DISPOSITION_TYPE_ATTACHMENT as libc::c_int,
if needs_ext {
@@ -1255,7 +1256,7 @@ unsafe fn build_body_file(
strdup(
b"filename*\x00" as *const u8 as *const libc::c_char,
),
dc_encode_ext_header(filename_to_send),
dc_encode_ext_header(as_str(filename_to_send)).strdup(),
),
);
if !parm.is_null() {

View File

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

View File

@@ -1,8 +1,7 @@
use std::ffi::CString;
use std::ptr;
use itertools::join;
use std::collections::HashMap;
use mmime::mailimf::*;
use mmime::mailimf_types::*;
use mmime::mailmime::*;
@@ -627,7 +626,7 @@ unsafe fn add_parts(
|mut stmt, conn| {
for i in 0..icnt {
let part = &mut mime_parser.parts[i];
if part.is_meta != 0 {
if part.is_meta {
continue;
}
@@ -643,14 +642,18 @@ unsafe fn add_parts(
}
}
if part.type_0 == Viewtype::Text {
let msg_raw = CString::yolo(part.msg_raw.as_ref().unwrap().clone());
let subject_c = CString::yolo(
mime_parser
.subject
.as_ref()
.map(|s| s.to_string())
.unwrap_or("".into()),
);
txt_raw = dc_mprintf(
b"%s\n\n%s\x00" as *const u8 as *const libc::c_char,
if !mime_parser.subject.is_null() {
mime_parser.subject
} else {
b"\x00" as *const u8 as *const libc::c_char
},
part.msg_raw,
subject_c.as_ptr(),
msg_raw.as_ptr(),
)
}
if 0 != mime_parser.is_system_message {
@@ -920,7 +923,7 @@ unsafe fn handle_reports(
}
}
unsafe fn save_locations(
fn save_locations(
context: &Context,
mime_parser: &dc_mimeparser_t,
chat_id: u32,
@@ -1046,7 +1049,7 @@ unsafe fn create_or_lookup_group(
// pointer somewhere into mime_parser, must not be freed
let mut X_MrAddToGrp = std::ptr::null_mut();
let mut X_MrGrpNameChanged = 0;
let mut X_MrGrpImageChanged = std::ptr::null();
let mut X_MrGrpImageChanged = "".to_string();
let mut better_msg: String = From::from("");
let mut failure_reason = std::ptr::null_mut();
@@ -1075,7 +1078,6 @@ unsafe fn create_or_lookup_group(
}
set_better_msg(mime_parser, &better_msg);
// search the grpid in the header
let optional_field = dc_mimeparser_lookup_optional_field(mime_parser, "Chat-Group-ID");
if !optional_field.is_null() {
grpid = to_string((*optional_field).fld_value)
@@ -1170,7 +1172,8 @@ unsafe fn create_or_lookup_group(
let optional_field =
dc_mimeparser_lookup_optional_field(mime_parser, "Chat-Group-Image");
if !optional_field.is_null() {
X_MrGrpImageChanged = (*optional_field).fld_value
// fld_value is a pointer somewhere into mime_parser, must not be freed
X_MrGrpImageChanged = as_str((*optional_field).fld_value).to_string();
}
better_msg = context.stock_system_msg(
StockMessage::MsgAddMember,
@@ -1194,14 +1197,11 @@ unsafe fn create_or_lookup_group(
let optional_field =
dc_mimeparser_lookup_optional_field(mime_parser, "Chat-Group-Image");
if !optional_field.is_null() {
X_MrGrpImageChanged = (*optional_field).fld_value;
// fld_value is a pointer somewhere into mime_parser, must not be freed
X_MrGrpImageChanged = as_str((*optional_field).fld_value).to_string();
mime_parser.is_system_message = DC_CMD_GROUPIMAGE_CHANGED;
better_msg = context.stock_system_msg(
if strcmp(
X_MrGrpImageChanged,
b"0\x00" as *const u8 as *const libc::c_char,
) == 0
{
if X_MrGrpImageChanged == "0" {
StockMessage::MsgGrpImgDeleted
} else {
StockMessage::MsgGrpImgChanged
@@ -1316,6 +1316,7 @@ unsafe fn create_or_lookup_group(
if !X_MrAddToGrp.is_null() || !X_MrRemoveFromGrp.is_null() {
recreate_member_list = 1;
} else if 0 != X_MrGrpNameChanged && !grpname.is_null() && strlen(grpname) < 200 {
info!(context, 0, "updating grpname for chat {}", chat_id);
if sql::execute(
context,
&context.sql,
@@ -1327,49 +1328,39 @@ unsafe fn create_or_lookup_group(
context.call_cb(Event::CHAT_MODIFIED, chat_id as uintptr_t, 0);
}
}
if !X_MrGrpImageChanged.is_null() {
let mut ok = 0;
let mut grpimage = ptr::null_mut();
if strcmp(
X_MrGrpImageChanged,
b"0\x00" as *const u8 as *const libc::c_char,
) == 0
{
ok = 1
if !X_MrGrpImageChanged.is_empty() {
info!(
context,
0, "grp-image-change {} chat {}", X_MrGrpImageChanged, chat_id
);
let mut changed = false;
let mut grpimage = "".to_string();
if X_MrGrpImageChanged == "0" {
changed = true;
} else {
for part in &mut mime_parser.parts {
if part.type_0 == Viewtype::Image {
grpimage = part
.param
.get(Param::File)
.map(|s| s.strdup())
.unwrap_or_else(|| std::ptr::null_mut());
ok = 1
.map(|s| s.to_string())
.unwrap_or_else(|| "".to_string());
info!(context, 0, "found image {:?}", grpimage);
changed = true;
}
}
}
if 0 != ok {
info!(
context,
0,
"New group image set to {}.",
if !grpimage.is_null() {
"DELETED".to_string()
} else {
to_string(grpimage)
},
);
if changed {
info!(context, 0, "New group image set to '{}'.", grpimage);
if let Ok(mut chat) = Chat::load_from_db(context, chat_id) {
if grpimage.is_null() {
if grpimage.is_empty() {
chat.param.remove(Param::ProfileImage);
} else {
chat.param.set(Param::ProfileImage, as_str(grpimage));
chat.param.set(Param::ProfileImage, grpimage);
}
chat.update_param().unwrap();
send_EVENT_CHAT_MODIFIED = 1;
}
free(grpimage as *mut libc::c_void);
}
}
@@ -1570,8 +1561,8 @@ unsafe fn create_or_lookup_adhoc_group(
}
// use subject as initial chat name
if !mime_parser.subject.is_null() && 0 != *mime_parser.subject.offset(0isize) as libc::c_int {
grpname = dc_strdup(mime_parser.subject)
if let Some(subject) = mime_parser.subject.as_ref().filter(|s| !s.is_empty()) {
grpname = subject.strdup();
} else {
grpname = context
.stock_string_repl_int(StockMessage::Member, member_ids.len() as libc::c_int)
@@ -1678,60 +1669,64 @@ fn hex_hash(s: impl AsRef<str>) -> String {
}
#[allow(non_snake_case)]
fn search_chat_ids_by_contact_ids(
context: &Context,
unsorted_contact_ids: &Vec<u32>,
) -> Vec<u32> {
fn search_chat_ids_by_contact_ids(context: &Context, unsorted_contact_ids: &Vec<u32>) -> Vec<u32> {
/* searches chat_id's by the given contact IDs, may return zero, one or more chat_id's */
let mut match_contact_ids = Vec::with_capacity(23);
let mut contact_ids = Vec::with_capacity(23);
let mut chat_ids = Vec::with_capacity(23);
if unsorted_contact_ids.is_empty() {
return chat_ids;
}
/* copy array, remove duplicates and SELF, sort by ID */
for &curr_id in unsorted_contact_ids {
if curr_id != DC_CONTACT_ID_SELF && !match_contact_ids.contains(&curr_id) {
match_contact_ids.push(curr_id);
}
}
if match_contact_ids.is_empty() {
return chat_ids;
}
/* collect all possible chats which contain at least one contact */
context.sql.query_map(
format!(
"SELECT DISTINCT cc.chat_id, cc.contact_id FROM chats_contacts cc LEFT JOIN chats c ON c.id=cc.chat_id WHERE cc.chat_id IN(SELECT chat_id FROM chats_contacts WHERE contact_id IN({})) AND c.type=120 AND cc.contact_id!=1 ORDER BY cc.chat_id, cc.contact_id;",
join(match_contact_ids.iter().map(|x| x.to_string()), ",")
),
params![],
|row| Ok((row.get::<_, u32>(0)?, row.get::<_, u32>(1)?)),
|rows| {
// per-chat count all matching contacts and add chat-id
// if all matching contacts are seen
let mut chat2matches = HashMap::new();
for row in rows {
let (chat_id, contact_id) = row?;
if match_contact_ids.contains(&contact_id) {
let count = if let Some(x) = chat2matches.get_mut(&chat_id) {
*x = *x + 1;
*x
} else {
chat2matches.insert(chat_id, 1);
1
};
if count == match_contact_ids.len() {
chat_ids.push(chat_id)
}
}
if !unsorted_contact_ids.is_empty() {
for &curr_id in unsorted_contact_ids {
if curr_id != 1 && !contact_ids.contains(&curr_id) {
contact_ids.push(curr_id);
}
Ok(())
}
).unwrap(); // TODO: better error handling
if !contact_ids.is_empty() {
contact_ids.sort();
let contact_ids_str = join(contact_ids.iter().map(|x| x.to_string()), ",");
context.sql.query_map(
format!(
"SELECT DISTINCT cc.chat_id, cc.contact_id \
FROM chats_contacts cc \
LEFT JOIN chats c ON c.id=cc.chat_id \
WHERE cc.chat_id IN(SELECT chat_id FROM chats_contacts WHERE contact_id IN({})) \
AND c.type=120 \
AND cc.contact_id!=1 \
ORDER BY cc.chat_id, cc.contact_id;",
contact_ids_str
),
params![],
|row| Ok((row.get::<_, u32>(0)?, row.get::<_, u32>(1)?)),
|rows| {
let mut last_chat_id = 0;
let mut matches = 0;
let mut mismatches = 0;
for row in rows {
let (chat_id, contact_id) = row?;
if chat_id != last_chat_id {
if matches == contact_ids.len() && mismatches == 0 {
chat_ids.push(last_chat_id);
}
last_chat_id = chat_id;
matches = 0;
mismatches = 0;
}
if matches < contact_ids.len() && contact_id == contact_ids[matches] {
matches += 1;
} else {
mismatches += 1;
}
}
if matches == contact_ids.len() && mismatches == 0 {
chat_ids.push(last_chat_id);
}
Ok(())
}
).unwrap(); // TODO: better error handling
}
}
chat_ids
}

View File

@@ -1,83 +1,17 @@
use std::ffi::{CStr, CString};
use std::borrow::Cow;
use std::ffi::CString;
use std::ptr;
use charset::Charset;
use mmime::mailmime_decode::*;
use mmime::mmapstring::*;
use mmime::other::*;
use percent_encoding::{percent_decode, utf8_percent_encode, AsciiSet, CONTROLS};
use crate::dc_tools::*;
use crate::types::*;
use crate::x::*;
#[inline]
fn isalnum(c: libc::c_int) -> libc::c_int {
if c < std::u8::MAX as libc::c_int {
(c as u8 as char).is_ascii_alphanumeric() as libc::c_int
} else {
0
}
}
/* ******************************************************************************
* URL encoding and decoding, RFC 3986
******************************************************************************/
unsafe fn int_2_uppercase_hex(code: libc::c_char) -> libc::c_char {
static mut HEX: [libc::c_char; 17] = [
48, 49, 50, 51, 52, 53, 54, 55, 56, 57, 65, 66, 67, 68, 69, 70, 0,
];
HEX[(code as libc::c_int & 15i32) as usize]
}
pub unsafe fn dc_urldecode(to_decode: *const libc::c_char) -> *mut libc::c_char {
let mut pstr: *const libc::c_char = to_decode;
if to_decode.is_null() {
return dc_strdup(b"\x00" as *const u8 as *const libc::c_char);
}
let buf: *mut libc::c_char = malloc(strlen(to_decode).wrapping_add(1)) as *mut libc::c_char;
let mut pbuf: *mut libc::c_char = buf;
assert!(!buf.is_null());
while 0 != *pstr {
if *pstr as libc::c_int == '%' as i32 {
if 0 != *pstr.offset(1isize) as libc::c_int && 0 != *pstr.offset(2isize) as libc::c_int
{
let fresh5 = pbuf;
pbuf = pbuf.offset(1);
*fresh5 = ((hex_2_int(*pstr.offset(1isize)) as libc::c_int) << 4i32
| hex_2_int(*pstr.offset(2isize)) as libc::c_int)
as libc::c_char;
pstr = pstr.offset(2isize)
}
} else if *pstr as libc::c_int == '+' as i32 {
let fresh6 = pbuf;
pbuf = pbuf.offset(1);
*fresh6 = ' ' as i32 as libc::c_char
} else {
let fresh7 = pbuf;
pbuf = pbuf.offset(1);
*fresh7 = *pstr
}
pstr = pstr.offset(1isize)
}
*pbuf = '\u{0}' as i32 as libc::c_char;
buf
}
fn hex_2_int(ch: libc::c_char) -> libc::c_char {
let ch = ch as u8 as char;
if !ch.is_ascii_hexdigit() {
return (ch.to_ascii_lowercase() as i32 - 'a' as i32 + 10) as libc::c_char;
}
match ch.to_digit(16) {
Some(res) => res as libc::c_char,
None => 0,
}
}
pub unsafe fn dc_encode_header_words(to_encode: *const libc::c_char) -> *mut libc::c_char {
let mut ok_to_continue = true;
let mut ret_str: *mut libc::c_char = ptr::null_mut();
@@ -290,374 +224,63 @@ pub unsafe fn dc_decode_header_words(in_0: *const libc::c_char) -> *mut libc::c_
out
}
#[cfg(test)]
unsafe fn dc_encode_modified_utf7(
mut to_encode: *const libc::c_char,
change_spaces: libc::c_int,
) -> *mut libc::c_char {
let mut utf8pos: libc::c_uint;
let mut utf8total: libc::c_uint;
let mut c: libc::c_uint;
let mut utf7mode: libc::c_uint;
let mut bitstogo: libc::c_uint;
let mut utf16flag: libc::c_uint;
let mut ucs4: libc::c_ulong = 0;
let mut bitbuf: libc::c_ulong = 0;
let mut dst: *mut libc::c_char;
let res: *mut libc::c_char;
if to_encode.is_null() {
return dc_strdup(b"\x00" as *const u8 as *const libc::c_char);
}
res = malloc(2usize.wrapping_mul(strlen(to_encode)).wrapping_add(1)) as *mut libc::c_char;
dst = res;
assert!(!dst.is_null());
pub fn dc_needs_ext_header(to_check: impl AsRef<str>) -> bool {
let to_check = to_check.as_ref();
utf7mode = 0i32 as libc::c_uint;
utf8total = 0i32 as libc::c_uint;
bitstogo = 0i32 as libc::c_uint;
utf8pos = 0i32 as libc::c_uint;
loop {
c = *to_encode as libc::c_uchar as libc::c_uint;
if !(c != '\u{0}' as i32 as libc::c_uint) {
break;
}
to_encode = to_encode.offset(1isize);
// normal character?
if c >= ' ' as i32 as libc::c_uint
&& c <= '~' as i32 as libc::c_uint
&& (c != '_' as i32 as libc::c_uint || 0 == change_spaces)
{
if 0 != utf7mode {
if 0 != bitstogo {
let fresh8 = dst;
dst = dst.offset(1);
*fresh8 = BASE64CHARS
[(bitbuf << (6i32 as libc::c_uint).wrapping_sub(bitstogo) & 0x3f) as usize]
}
let fresh9 = dst;
dst = dst.offset(1);
*fresh9 = '-' as i32 as libc::c_char;
utf7mode = 0i32 as libc::c_uint;
utf8pos = 0i32 as libc::c_uint;
bitstogo = 0i32 as libc::c_uint;
utf8total = 0i32 as libc::c_uint
}
if 0 != change_spaces && c == ' ' as i32 as libc::c_uint {
let fresh10 = dst;
dst = dst.offset(1);
*fresh10 = '_' as i32 as libc::c_char
} else {
let fresh11 = dst;
dst = dst.offset(1);
*fresh11 = c as libc::c_char
}
if c == '&' as i32 as libc::c_uint {
let fresh12 = dst;
dst = dst.offset(1);
*fresh12 = '-' as i32 as libc::c_char
}
} else {
if 0 == utf7mode {
let fresh13 = dst;
dst = dst.offset(1);
*fresh13 = '&' as i32 as libc::c_char;
utf7mode = 1i32 as libc::c_uint
}
// encode ascii characters as themselves
if c < 0x80i32 as libc::c_uint {
ucs4 = c as libc::c_ulong
} else if 0 != utf8total {
ucs4 = ucs4 << 6i32 | c as libc::c_ulong & 0x3f;
utf8pos = utf8pos.wrapping_add(1);
if utf8pos < utf8total {
continue;
}
} else {
utf8pos = 1i32 as libc::c_uint;
if c < 0xe0i32 as libc::c_uint {
utf8total = 2i32 as libc::c_uint;
ucs4 = (c & 0x1fi32 as libc::c_uint) as libc::c_ulong
} else if c < 0xf0i32 as libc::c_uint {
utf8total = 3i32 as libc::c_uint;
ucs4 = (c & 0xfi32 as libc::c_uint) as libc::c_ulong
} else {
utf8total = 4i32 as libc::c_uint;
ucs4 = (c & 0x3i32 as libc::c_uint) as libc::c_ulong
}
continue;
}
utf8total = 0i32 as libc::c_uint;
loop {
if ucs4 >= 0x10000 {
ucs4 = ucs4.wrapping_sub(0x10000);
bitbuf = bitbuf << 16 | (ucs4 >> 10).wrapping_add(0xd800);
ucs4 = (ucs4 & 0x3ff).wrapping_add(0xdc00);
utf16flag = 1i32 as libc::c_uint
} else {
bitbuf = bitbuf << 16 | ucs4;
utf16flag = 0i32 as libc::c_uint
}
bitstogo = bitstogo.wrapping_add(16i32 as libc::c_uint);
while bitstogo >= 6i32 as libc::c_uint {
bitstogo = bitstogo.wrapping_sub(6i32 as libc::c_uint);
let fresh14 = dst;
dst = dst.offset(1);
*fresh14 = BASE64CHARS[(if 0 != bitstogo {
bitbuf >> bitstogo
} else {
bitbuf
} & 0x3f) as usize]
}
if !(0 != utf16flag) {
break;
}
}
}
if to_check.is_empty() {
return false;
}
if 0 != utf7mode {
if 0 != bitstogo {
let fresh15 = dst;
dst = dst.offset(1);
*fresh15 = BASE64CHARS
[(bitbuf << (6i32 as libc::c_uint).wrapping_sub(bitstogo) & 0x3f) as usize]
}
let fresh16 = dst;
dst = dst.offset(1);
*fresh16 = '-' as i32 as libc::c_char
}
*dst = '\u{0}' as i32 as libc::c_char;
res
to_check.chars().any(|c| {
!(c.is_ascii_alphanumeric()
|| c == '-'
|| c == '_'
|| c == '_'
|| c == '.'
|| c == '~'
|| c == '%')
})
}
/* ******************************************************************************
* Encode/decode modified UTF-7 as needed for IMAP, see RFC 2192
******************************************************************************/
const EXT_ASCII_ST: &AsciiSet = &CONTROLS
.add(b' ')
.add(b'-')
.add(b'_')
.add(b'.')
.add(b'~')
.add(b'%');
// UTF7 modified base64 alphabet
#[cfg(test)]
static mut BASE64CHARS: [libc::c_char; 65] = [
65, 66, 67, 68, 69, 70, 71, 72, 73, 74, 75, 76, 77, 78, 79, 80, 81, 82, 83, 84, 85, 86, 87, 88,
89, 90, 97, 98, 99, 100, 101, 102, 103, 104, 105, 106, 107, 108, 109, 110, 111, 112, 113, 114,
115, 116, 117, 118, 119, 120, 121, 122, 48, 49, 50, 51, 52, 53, 54, 55, 56, 57, 43, 44, 0,
];
#[cfg(test)]
unsafe fn dc_decode_modified_utf7(
to_decode: *const libc::c_char,
change_spaces: libc::c_int,
) -> *mut libc::c_char {
let mut c: libc::c_uint;
let mut i: libc::c_uint;
let mut bitcount: libc::c_uint;
let mut ucs4: libc::c_ulong;
let mut utf16: libc::c_ulong;
let mut bitbuf: libc::c_ulong;
let mut base64: [libc::c_uchar; 256] = [0; 256];
let mut src: *const libc::c_char;
let mut dst: *mut libc::c_char;
let res: *mut libc::c_char;
if to_decode.is_null() {
return dc_strdup(b"\x00" as *const u8 as *const libc::c_char);
}
res = malloc(4usize.wrapping_mul(strlen(to_decode)).wrapping_add(1)) as *mut libc::c_char;
dst = res;
src = to_decode;
assert!(!dst.is_null());
libc::memset(
base64.as_mut_ptr() as *mut libc::c_void,
64,
::std::mem::size_of::<[libc::c_uchar; 256]>(),
);
i = 0i32 as libc::c_uint;
while (i as libc::c_ulong) < ::std::mem::size_of::<[libc::c_char; 65]>() as libc::c_ulong {
base64[BASE64CHARS[i as usize] as libc::c_uint as usize] = i as libc::c_uchar;
i = i.wrapping_add(1)
}
while *src as libc::c_int != '\u{0}' as i32 {
let fresh17 = src;
src = src.offset(1);
c = *fresh17 as libc::c_uint;
if c != '&' as i32 as libc::c_uint || *src as libc::c_int == '-' as i32 {
if 0 != change_spaces && c == '_' as i32 as libc::c_uint {
let fresh18 = dst;
dst = dst.offset(1);
*fresh18 = ' ' as i32 as libc::c_char
} else {
let fresh19 = dst;
dst = dst.offset(1);
*fresh19 = c as libc::c_char
}
if c == '&' as i32 as libc::c_uint {
src = src.offset(1isize)
}
} else {
bitbuf = 0;
bitcount = 0i32 as libc::c_uint;
ucs4 = 0;
loop {
c = base64[*src as libc::c_uchar as usize] as libc::c_uint;
if !(c != 64i32 as libc::c_uint) {
break;
}
src = src.offset(1isize);
bitbuf = bitbuf << 6i32 | c as libc::c_ulong;
bitcount = bitcount.wrapping_add(6i32 as libc::c_uint);
// enough bits for a UTF-16 character?
if !(bitcount >= 16i32 as libc::c_uint) {
continue;
}
bitcount = bitcount.wrapping_sub(16i32 as libc::c_uint);
utf16 = if 0 != bitcount {
bitbuf >> bitcount
} else {
bitbuf
} & 0xffff;
// convert UTF16 to UCS4
if utf16 >= 0xd800 && utf16 <= 0xdbff {
ucs4 = utf16.wrapping_sub(0xd800) << 10i32
} else {
if utf16 >= 0xdc00 && utf16 <= 0xdfff {
ucs4 = ucs4.wrapping_add(utf16.wrapping_sub(0xdc00).wrapping_add(0x10000))
} else {
ucs4 = utf16
}
if ucs4 <= 0x7f {
*dst.offset(0isize) = ucs4 as libc::c_char;
dst = dst.offset(1isize)
} else if ucs4 <= 0x7ff {
*dst.offset(0isize) = (0xc0 | ucs4 >> 6i32) as libc::c_char;
*dst.offset(1isize) = (0x80 | ucs4 & 0x3f) as libc::c_char;
dst = dst.offset(2isize)
} else if ucs4 <= 0xffff {
*dst.offset(0isize) = (0xe0 | ucs4 >> 12i32) as libc::c_char;
*dst.offset(1isize) = (0x80 | ucs4 >> 6i32 & 0x3f) as libc::c_char;
*dst.offset(2isize) = (0x80 | ucs4 & 0x3f) as libc::c_char;
dst = dst.offset(3isize)
} else {
*dst.offset(0isize) = (0xf0 | ucs4 >> 18i32) as libc::c_char;
*dst.offset(1isize) = (0x80 | ucs4 >> 12i32 & 0x3f) as libc::c_char;
*dst.offset(2isize) = (0x80 | ucs4 >> 6i32 & 0x3f) as libc::c_char;
*dst.offset(3isize) = (0x80 | ucs4 & 0x3f) as libc::c_char;
dst = dst.offset(4isize)
}
}
}
if *src as libc::c_int == '-' as i32 {
src = src.offset(1isize)
}
}
}
*dst = '\u{0}' as i32 as libc::c_char;
res
/// Encode an UTF-8 string to the extended header format.
pub fn dc_encode_ext_header(to_encode: impl AsRef<str>) -> String {
let encoded = utf8_percent_encode(to_encode.as_ref(), &EXT_ASCII_ST);
format!("utf-8''{}", encoded)
}
pub unsafe fn dc_needs_ext_header(mut to_check: *const libc::c_char) -> bool {
if !to_check.is_null() {
while 0 != *to_check {
if 0 == isalnum(*to_check as libc::c_int)
&& *to_check as libc::c_int != '-' as i32
&& *to_check as libc::c_int != '_' as i32
&& *to_check as libc::c_int != '.' as i32
&& *to_check as libc::c_int != '~' as i32
{
return true;
}
to_check = to_check.offset(1isize)
}
}
false
}
pub unsafe fn dc_encode_ext_header(to_encode: *const libc::c_char) -> *mut libc::c_char {
let mut pstr: *const libc::c_char = to_encode;
if to_encode.is_null() {
return dc_strdup(b"utf-8\'\'\x00" as *const u8 as *const libc::c_char);
}
let buf: *mut libc::c_char = malloc(
strlen(b"utf-8\'\'\x00" as *const u8 as *const libc::c_char)
.wrapping_add(strlen(to_encode).wrapping_mul(3))
.wrapping_add(1),
) as *mut libc::c_char;
assert!(!buf.is_null());
let mut pbuf: *mut libc::c_char = buf;
strcpy(pbuf, b"utf-8\'\'\x00" as *const u8 as *const libc::c_char);
pbuf = pbuf.offset(strlen(pbuf) as isize);
while 0 != *pstr {
if 0 != isalnum(*pstr as libc::c_int)
|| *pstr as libc::c_int == '-' as i32
|| *pstr as libc::c_int == '_' as i32
|| *pstr as libc::c_int == '.' as i32
|| *pstr as libc::c_int == '~' as i32
{
let fresh20 = pbuf;
pbuf = pbuf.offset(1);
*fresh20 = *pstr
} else {
let fresh21 = pbuf;
pbuf = pbuf.offset(1);
*fresh21 = '%' as i32 as libc::c_char;
let fresh22 = pbuf;
pbuf = pbuf.offset(1);
*fresh22 = int_2_uppercase_hex((*pstr as libc::c_int >> 4i32) as libc::c_char);
let fresh23 = pbuf;
pbuf = pbuf.offset(1);
*fresh23 = int_2_uppercase_hex((*pstr as libc::c_int & 15i32) as libc::c_char)
}
pstr = pstr.offset(1isize)
}
*pbuf = '\u{0}' as i32 as libc::c_char;
buf
}
pub unsafe fn dc_decode_ext_header(to_decode: *const libc::c_char) -> *mut libc::c_char {
let mut decoded: *mut libc::c_char = ptr::null_mut();
let mut charset: *mut libc::c_char = ptr::null_mut();
let mut p2: *const libc::c_char;
if !to_decode.is_null() {
// get char set
p2 = strchr(to_decode, '\'' as i32);
if !(p2.is_null() || p2 == to_decode) {
/*no empty charset allowed*/
charset =
dc_null_terminate(to_decode, p2.wrapping_offset_from(to_decode) as libc::c_int);
p2 = p2.offset(1isize);
/// Decode an extended-header-format strings to UTF-8.
pub fn dc_decode_ext_header(to_decode: &[u8]) -> Cow<str> {
if let Some(index) = bytes!(b'\'').find(to_decode) {
let (charset, rest) = to_decode.split_at(index);
if !charset.is_empty() {
// skip language
p2 = strchr(p2, '\'' as i32);
if !p2.is_null() {
p2 = p2.offset(1isize);
decoded = dc_urldecode(p2);
if !charset.is_null()
&& strcmp(charset, b"utf-8\x00" as *const u8 as *const libc::c_char) != 0i32
&& strcmp(charset, b"UTF-8\x00" as *const u8 as *const libc::c_char) != 0i32
{
if let Some(encoding) =
Charset::for_label(CStr::from_ptr(charset).to_str().unwrap().as_bytes())
{
let data =
std::slice::from_raw_parts(decoded as *const u8, strlen(decoded));
if let Some(index2) = bytes!(b'\'').find(&rest[1..]) {
let decoded = percent_decode(&rest[index2 + 2..]);
let (res, _, _) = encoding.decode(data);
free(decoded as *mut _);
let r = std::ffi::CString::new(res.as_bytes()).unwrap();
decoded = dc_strdup(r.as_ptr());
if charset != b"utf-8" && charset != b"UTF-8" {
if let Some(encoding) = Charset::for_label(charset) {
let bytes = decoded.collect::<Vec<u8>>();
let (res, _, _) = encoding.decode(&bytes);
return Cow::Owned(res.into_owned());
} else {
return decoded.decode_utf8_lossy();
}
} else {
return decoded.decode_utf8_lossy();
}
}
}
}
free(charset as *mut libc::c_void);
if !decoded.is_null() {
decoded
} else {
dc_strdup(to_decode)
}
String::from_utf8_lossy(to_decode)
}
unsafe fn print_hex(target: *mut libc::c_char, cur: *const libc::c_char) {
@@ -672,16 +295,8 @@ unsafe fn print_hex(target: *mut libc::c_char, cur: *const libc::c_char) {
#[cfg(test)]
mod tests {
use super::*;
use percent_encoding::{percent_encode, NON_ALPHANUMERIC};
use std::ffi::CStr;
#[test]
fn test_isalnum() {
assert_eq!(isalnum(0), 0);
assert_eq!(isalnum('5' as libc::c_int), 1);
assert_eq!(isalnum('Q' as libc::c_int), 1);
}
#[test]
fn test_dc_decode_header_words() {
unsafe {
@@ -742,99 +357,43 @@ mod tests {
#[test]
fn test_dc_encode_ext_header() {
unsafe {
let mut buf1 = dc_encode_ext_header(
b"Bj\xc3\xb6rn Petersen\x00" as *const u8 as *const libc::c_char,
);
assert_eq!(
CStr::from_ptr(buf1).to_str().unwrap(),
"utf-8\'\'Bj%C3%B6rn%20Petersen"
);
let buf2 = dc_decode_ext_header(buf1);
assert_eq!(
strcmp(
buf2,
b"Bj\xc3\xb6rn Petersen\x00" as *const u8 as *const libc::c_char,
),
0
);
free(buf1 as *mut libc::c_void);
free(buf2 as *mut libc::c_void);
let buf1 = dc_encode_ext_header("Björn Petersen");
assert_eq!(&buf1, "utf-8\'\'Bj%C3%B6rn%20Petersen");
let buf2 = dc_decode_ext_header(buf1.as_bytes());
assert_eq!(&buf2, "Björn Petersen",);
buf1 = dc_decode_ext_header(
b"iso-8859-1\'en\'%A3%20rates\x00" as *const u8 as *const libc::c_char,
);
assert_eq!(
strcmp(
buf1,
b"\xc2\xa3 rates\x00" as *const u8 as *const libc::c_char,
),
0
);
free(buf1 as *mut libc::c_void);
let buf1 = dc_decode_ext_header(b"iso-8859-1\'en\'%A3%20rates");
assert_eq!(buf1, "£ rates",);
buf1 = dc_decode_ext_header(b"wrong\'format\x00" as *const u8 as *const libc::c_char);
assert_eq!(
strcmp(
buf1,
b"wrong\'format\x00" as *const u8 as *const libc::c_char,
),
0
);
free(buf1 as *mut libc::c_void);
let buf1 = dc_decode_ext_header(b"wrong\'format");
assert_eq!(buf1, "wrong\'format",);
buf1 = dc_decode_ext_header(b"\'\'\x00" as *const u8 as *const libc::c_char);
assert_eq!(
strcmp(buf1, b"\'\'\x00" as *const u8 as *const libc::c_char),
0
);
free(buf1 as *mut libc::c_void);
let buf1 = dc_decode_ext_header(b"\'\'");
assert_eq!(buf1, "\'\'");
buf1 = dc_decode_ext_header(b"x\'\'\x00" as *const u8 as *const libc::c_char);
assert_eq!(strcmp(buf1, b"\x00" as *const u8 as *const libc::c_char), 0);
free(buf1 as *mut libc::c_void);
let buf1 = dc_decode_ext_header(b"x\'\'");
assert_eq!(buf1, "");
buf1 = dc_decode_ext_header(b"\'\x00" as *const u8 as *const libc::c_char);
assert_eq!(
strcmp(buf1, b"\'\x00" as *const u8 as *const libc::c_char),
0
);
free(buf1 as *mut libc::c_void);
let buf1 = dc_decode_ext_header(b"\'");
assert_eq!(buf1, "\'");
buf1 = dc_decode_ext_header(b"\x00" as *const u8 as *const libc::c_char);
assert_eq!(strcmp(buf1, b"\x00" as *const u8 as *const libc::c_char), 0);
free(buf1 as *mut libc::c_void);
}
let buf1 = dc_decode_ext_header(b"");
assert_eq!(buf1, "");
// regressions
assert_eq!(
dc_decode_ext_header(dc_encode_ext_header("%0A").as_bytes()),
"%0A"
);
}
#[test]
fn test_dc_needs_ext_header() {
unsafe {
assert_eq!(
dc_needs_ext_header(b"Bj\xc3\xb6rn\x00" as *const u8 as *const libc::c_char),
true
);
assert_eq!(
dc_needs_ext_header(b"Bjoern\x00" as *const u8 as *const libc::c_char),
false
);
assert_eq!(
dc_needs_ext_header(b"\x00" as *const u8 as *const libc::c_char),
false
);
assert_eq!(
dc_needs_ext_header(b" \x00" as *const u8 as *const libc::c_char),
true
);
assert_eq!(
dc_needs_ext_header(b"a b\x00" as *const u8 as *const libc::c_char),
true
);
assert_eq!(
dc_needs_ext_header(0 as *const u8 as *const libc::c_char),
false
);
}
assert_eq!(dc_needs_ext_header("Björn"), true);
assert_eq!(dc_needs_ext_header("Bjoern"), false);
assert_eq!(dc_needs_ext_header(""), false);
assert_eq!(dc_needs_ext_header(" "), true);
assert_eq!(dc_needs_ext_header("a b"), true);
}
#[test]
@@ -849,69 +408,20 @@ mod tests {
assert_eq!(to_string(hex.as_ptr() as *const _), "=3A");
}
#[test]
fn test_dc_urlencode_urldecode() {
unsafe {
let buf1 = percent_encode(b"Bj\xc3\xb6rn Petersen", NON_ALPHANUMERIC)
.to_string()
.strdup();
use proptest::prelude::*;
assert_eq!(
CStr::from_ptr(buf1 as *const libc::c_char)
.to_str()
.unwrap(),
"Bj%C3%B6rn%20Petersen"
);
let buf2 = dc_urldecode(buf1);
assert_eq!(
strcmp(
buf2,
b"Bj\xc3\xb6rn Petersen\x00" as *const u8 as *const libc::c_char
),
0
);
free(buf1 as *mut libc::c_void);
free(buf2 as *mut libc::c_void);
proptest! {
#[test]
fn test_ext_header_roundtrip(buf: String) {
let encoded = dc_encode_ext_header(&buf);
let decoded = dc_decode_ext_header(encoded.as_bytes());
assert_eq!(buf, decoded);
}
}
#[test]
fn test_dc_encode_decode_modified_utf7() {
unsafe {
let buf1 = dc_encode_modified_utf7(
b"Bj\xc3\xb6rn Petersen\x00" as *const u8 as *const libc::c_char,
1,
);
assert_eq!(
CStr::from_ptr(buf1 as *const libc::c_char)
.to_str()
.unwrap(),
"Bj&APY-rn_Petersen"
);
let buf2 = dc_decode_modified_utf7(buf1, 1);
assert_eq!(
strcmp(
buf2,
b"Bj\xc3\xb6rn Petersen\x00" as *const u8 as *const libc::c_char
),
0
);
free(buf1 as *mut libc::c_void);
free(buf2 as *mut libc::c_void);
#[test]
fn test_ext_header_decode_anything(buf: Vec<u8>) {
// make sure this never panics
let _decoded = dc_decode_ext_header(&buf);
}
}
#[test]
fn test_hex_to_int() {
assert_eq!(hex_2_int(b'A' as libc::c_char), 10);
assert_eq!(hex_2_int(b'a' as libc::c_char), 10);
assert_eq!(hex_2_int(b'4' as libc::c_char), 4);
assert_eq!(hex_2_int(b'K' as libc::c_char), 20);
}
}

View File

@@ -1,56 +0,0 @@
use crate::context::Context;
use crate::dc_tools::*;
use crate::sql;
// Token namespaces
#[allow(non_camel_case_types)]
type dc_tokennamespc_t = usize;
pub const DC_TOKEN_AUTH: dc_tokennamespc_t = 110;
pub const DC_TOKEN_INVITENUMBER: dc_tokennamespc_t = 100;
// Functions to read/write token from/to the database. A token is any string associated with a key.
pub fn dc_token_save(context: &Context, namespc: dc_tokennamespc_t, foreign_id: u32) -> String {
// foreign_id may be 0
let token = dc_create_id();
sql::execute(
context,
&context.sql,
"INSERT INTO tokens (namespc, foreign_id, token, timestamp) VALUES (?, ?, ?, ?);",
params![namespc as i32, foreign_id as i32, &token, time()],
)
.ok();
token
}
pub fn dc_token_lookup(
context: &Context,
namespc: dc_tokennamespc_t,
foreign_id: u32,
) -> Option<String> {
context.sql.query_row_col::<_, String>(
context,
"SELECT token FROM tokens WHERE namespc=? AND foreign_id=?;",
params![namespc as i32, foreign_id as i32],
0,
)
}
pub fn dc_token_lookup_or_new(
context: &Context,
namespc: dc_tokennamespc_t,
foreign_id: u32,
) -> String {
dc_token_lookup(context, namespc, foreign_id)
.unwrap_or_else(|| dc_token_save(context, namespc, foreign_id))
}
pub fn dc_token_exists(context: &Context, namespc: dc_tokennamespc_t, token: &str) -> bool {
context
.sql
.exists(
"SELECT id FROM tokens WHERE namespc=? AND token=?;",
params![namespc as i32, token],
)
.unwrap_or_default()
}

View File

@@ -83,14 +83,6 @@ pub unsafe fn dc_atoi_null_is_0(s: *const libc::c_char) -> libc::c_int {
}
}
pub fn dc_atof(s: *const libc::c_char) -> libc::c_double {
if s.is_null() {
return 0.;
}
as_str(s).parse().unwrap_or_default()
}
pub unsafe fn dc_str_replace(
haystack: *mut *mut libc::c_char,
needle: *const libc::c_char,
@@ -105,21 +97,6 @@ pub unsafe fn dc_str_replace(
*haystack = haystack_s.replace(&needle_s, &replacement_s).strdup();
}
pub unsafe fn dc_ftoa(f: libc::c_double) -> *mut libc::c_char {
// hack around printf(%f) that may return `,` as decimal point on mac
let test: *mut libc::c_char = dc_mprintf(b"%f\x00" as *const u8 as *const libc::c_char, 1.2f64);
*test.offset(2isize) = 0 as libc::c_char;
let mut str: *mut libc::c_char = dc_mprintf(b"%f\x00" as *const u8 as *const libc::c_char, f);
dc_str_replace(
&mut str,
test.offset(1isize),
b".\x00" as *const u8 as *const libc::c_char,
);
free(test as *mut libc::c_void);
str
}
unsafe fn dc_ltrim(buf: *mut libc::c_char) {
let mut len: size_t;
let mut cur: *const libc::c_uchar;
@@ -284,11 +261,18 @@ pub unsafe fn dc_replace_bad_utf8_chars(buf: *mut libc::c_char) {
pub fn dc_truncate(buf: &str, approx_chars: usize, do_unwrap: bool) -> Cow<str> {
let ellipse = if do_unwrap { "..." } else { "[...]" };
if approx_chars > 0 && buf.len() > approx_chars + ellipse.len() {
if let Some(index) = buf[..approx_chars].rfind(|c| c == ' ' || c == '\n') {
let count = buf.chars().count();
if approx_chars > 0 && count > approx_chars + ellipse.len() {
let end_pos = buf
.char_indices()
.nth(approx_chars)
.map(|(n, _)| n)
.unwrap_or_default();
if let Some(index) = buf[..end_pos].rfind(|c| c == ' ' || c == '\n') {
Cow::Owned(format!("{}{}", &buf[..index + 1], ellipse))
} else {
Cow::Owned(format!("{}{}", &buf[..approx_chars], ellipse))
Cow::Owned(format!("{}{}", &buf[..end_pos], ellipse))
}
} else {
Cow::Borrowed(buf)
@@ -458,21 +442,10 @@ pub unsafe fn clist_free_content(haystack: *const clist) {
pub unsafe fn clist_search_string_nocase(
haystack: *const clist,
needle: *const libc::c_char,
) -> libc::c_int {
let mut iter = (*haystack).first;
while !iter.is_null() {
if strcasecmp((*iter).data as *const libc::c_char, needle) == 0 {
return 1;
}
iter = if !iter.is_null() {
(*iter).next
} else {
ptr::null_mut()
}
}
0
) -> bool {
(&*haystack)
.into_iter()
.any(|data| strcasecmp(data.cast(), needle) == 0)
}
/* date/time tools */
@@ -517,7 +490,7 @@ pub fn dc_gm2local_offset() -> i64 {
}
/* timesmearing */
pub unsafe fn dc_smeared_time(context: &Context) -> i64 {
pub fn dc_smeared_time(context: &Context) -> i64 {
/* function returns a corrected time(NULL) */
let mut now = time();
let ts = *context.last_smeared_timestamp.clone().read().unwrap();
@@ -653,6 +626,21 @@ pub unsafe fn dc_create_outgoing_rfc724_mid(
ret
}
/// Generate globally-unique message-id for a new outgoing message.
///
/// Note: Do not add a counter or any private data as as this may give
/// unneeded information to the receiver
pub fn dc_create_outgoing_rfc724_mid_safe(grpid: Option<&str>, from_addr: &str) -> String {
let hostname = from_addr
.find('@')
.map(|k| &from_addr[k..])
.unwrap_or("@nohost");
match grpid {
Some(grpid) => format!("Gr.{}.{}{}", grpid, dc_create_id(), hostname),
None => format!("Mr.{}.{}{}", dc_create_id(), dc_create_id(), hostname),
}
}
/// Extract the group id (grpid) from a message id (mid)
///
/// # Arguments
@@ -1505,23 +1493,6 @@ mod tests {
}
}
#[test]
fn test_dc_atof() {
let f: libc::c_double = dc_atof(b"1.23\x00" as *const u8 as *const libc::c_char);
assert!(f > 1.22f64);
assert!(f < 1.24f64);
}
#[test]
fn test_dc_ftoa() {
unsafe {
let s: *mut libc::c_char = dc_ftoa(1.23f64);
assert!(dc_atof(s) > 1.22f64);
assert!(dc_atof(s) < 1.24f64);
free(s as *mut libc::c_void);
}
}
#[test]
fn test_rust_ftoa() {
assert_eq!("1.22", format!("{}", 1.22));
@@ -1569,6 +1540,36 @@ mod tests {
assert_eq!(dc_truncate("123456", 4, true), "123456");
}
#[test]
fn test_dc_truncate_edge() {
assert_eq!(dc_truncate("", 4, false), "");
assert_eq!(dc_truncate("", 4, true), "");
assert_eq!(dc_truncate("\n hello \n world", 4, false), "\n [...]");
assert_eq!(dc_truncate("\n hello \n world", 4, true), "\n ...");
assert_eq!(
dc_truncate("𐠈0Aᝮa𫝀®!ꫛa¡0A𐢧00𐹠®A 丽ⷐએ", 1, false),
"𐠈[...]"
);
assert_eq!(
dc_truncate("𐠈0Aᝮa𫝀®!ꫛa¡0A𐢧00𐹠®A 丽ⷐએ", 0, false),
"𐠈0Aᝮa𫝀®!ꫛa¡0A𐢧00𐹠®A 丽ⷐએ"
);
// 9 characters, so no truncation
assert_eq!(
dc_truncate("𑒀ὐ¢🜀\u{1e01b}A a🟠", 6, false),
"𑒀ὐ¢🜀\u{1e01b}A a🟠",
);
// 12 characters, truncation
assert_eq!(
dc_truncate("𑒀ὐ¢🜀\u{1e01b}A a🟠bcd", 6, false),
"𑒀ὐ¢🜀\u{1e01b}A[...]",
);
}
#[test]
fn test_dc_null_terminate_1() {
unsafe {
@@ -1906,4 +1907,37 @@ mod tests {
assert_eq!(EmailAddress::new("u@.tt").is_ok(), false);
assert_eq!(EmailAddress::new("@d.tt").is_ok(), false);
}
use proptest::prelude::*;
proptest! {
#[test]
fn test_dc_truncate(
buf: String,
approx_chars in 0..10000usize,
do_unwrap: bool,
) {
let res = dc_truncate(&buf, approx_chars, do_unwrap);
let el_len = if do_unwrap { 3 } else { 5 };
let l = res.chars().count();
if approx_chars > 0 {
assert!(
l <= approx_chars + el_len,
"buf: '{}' - res: '{}' - len {}, approx {}",
&buf, &res, res.len(), approx_chars
);
} else {
assert_eq!(&res, &buf);
}
if approx_chars > 0 && buf.chars().count() > approx_chars + el_len {
let l = res.len();
if do_unwrap {
assert_eq!(&res[l-3..l], "...", "missing ellipsis in {}", &res);
} else {
assert_eq!(&res[l-5..l], "[...]", "missing ellipsis in {}", &res);
}
}
}
}
}

View File

@@ -299,8 +299,10 @@ impl E2eeHelper {
ok_to_continue = false;
} else {
if let Some(ctext_v) = dc_pgp_pk_encrypt(
(*plain).str_0 as *const libc::c_void,
(*plain).len,
std::slice::from_raw_parts(
(*plain).str_0 as *const u8,
(*plain).len,
),
&keyring,
sign_key.as_ref(),
) {
@@ -907,8 +909,7 @@ unsafe fn decrypt_part(
/*if we already have fingerprints, do not add more; this ensures, only the fingerprints from the outer-most part are collected */
if let Some(plain) = dc_pgp_pk_decrypt(
decoded_data as *const libc::c_void,
decoded_data_bytes,
std::slice::from_raw_parts(decoded_data as *const u8, decoded_data_bytes),
&private_keyring,
&public_keyring_for_validate,
add_signatures,

View File

@@ -8,8 +8,8 @@ use std::time::{Duration, SystemTime};
use crate::constants::*;
use crate::context::Context;
use crate::dc_loginparam::*;
use crate::dc_tools::CStringExt;
use crate::login_param::LoginParam;
use crate::oauth2::dc_get_oauth2_access_token;
use crate::types::*;
@@ -516,7 +516,7 @@ impl Imap {
cfg.watch_folder = None;
}
pub fn connect(&self, context: &Context, lp: &dc_loginparam_t) -> bool {
pub fn connect(&self, context: &Context, lp: &LoginParam) -> bool {
if lp.mail_server.is_empty() || lp.mail_user.is_empty() || lp.mail_pw.is_empty() {
return false;
}

View File

@@ -10,11 +10,11 @@ use crate::configure::*;
use crate::constants::*;
use crate::context::Context;
use crate::dc_imex::*;
use crate::dc_loginparam::*;
use crate::dc_mimefactory::*;
use crate::dc_tools::*;
use crate::imap::*;
use crate::location;
use crate::login_param::LoginParam;
use crate::message::*;
use crate::param::*;
use crate::sql;
@@ -25,13 +25,22 @@ use crate::x::*;
#[derive(Debug, Display, Copy, Clone, PartialEq, Eq, FromPrimitive, ToPrimitive, FromSql, ToSql)]
#[repr(i32)]
enum Thread {
Unknown = 0,
Imap = 100,
Smtp = 5000,
}
impl Default for Thread {
fn default() -> Self {
Thread::Unknown
}
}
#[derive(Debug, Display, Copy, Clone, PartialEq, Eq, FromPrimitive, ToPrimitive, FromSql, ToSql)]
#[repr(i32)]
pub enum Action {
Unknown = 0,
// Jobs in the INBOX-thread, range from DC_IMAP_THREAD..DC_IMAP_THREAD+999
Housekeeping = 105, // low priority ...
DeleteMsgOnImap = 110,
@@ -50,11 +59,19 @@ pub enum Action {
SendMsgToSmtp = 5901, // ... high priority
}
impl Default for Action {
fn default() -> Self {
Action::Unknown
}
}
impl From<Action> for Thread {
fn from(action: Action) -> Thread {
use Action::*;
match action {
Unknown => Thread::Unknown,
Housekeeping => Thread::Imap,
DeleteMsgOnImap => Thread::Imap,
MarkseenMdnOnImap => Thread::Imap,
@@ -113,7 +130,7 @@ impl Job {
fn do_DC_JOB_SEND(&mut self, context: &Context) {
/* connect to SMTP server, if not yet done */
if !context.smtp.lock().unwrap().is_connected() {
let loginparam = dc_loginparam_read(context, &context.sql, "configured_");
let loginparam = LoginParam::from_database(context, "configured_");
let connected = context.smtp.lock().unwrap().connect(context, &loginparam);
if !connected {
@@ -692,9 +709,7 @@ pub unsafe fn job_send_msg(context: &Context, msg_id: uint32_t) -> libc::c_int {
);
} else {
/* unrecoverable */
if clist_search_string_nocase(mimefactory.recipients_addr, mimefactory.from_addr)
== 0i32
{
if !clist_search_string_nocase(mimefactory.recipients_addr, mimefactory.from_addr) {
clist_insert_after(
mimefactory.recipients_names,
(*mimefactory.recipients_names).last,
@@ -844,6 +859,9 @@ fn job_perform(context: &Context, thread: Thread, probe_network: bool) {
job.try_again = 0;
match job.action {
Action::Unknown => {
warn!(context, 0, "Unknown job id found");
}
Action::SendMsgToSmtp => job.do_DC_JOB_SEND(context),
Action::DeleteMsgOnImap => job.do_DC_JOB_DELETE_MSG_ON_IMAP(context),
Action::MarkseenMsgOnImap => job.do_DC_JOB_MARKSEEN_MSG_ON_IMAP(context),
@@ -1062,6 +1080,11 @@ pub fn job_add(
param: Params,
delay_seconds: i64,
) {
if action == Action::Unknown {
error!(context, 0, "Invalid action passed to job_add");
return;
}
let timestamp = time();
let thread: Thread = action.into();
@@ -1082,6 +1105,7 @@ pub fn job_add(
match thread {
Thread::Imap => interrupt_imap_idle(context),
Thread::Smtp => interrupt_smtp_idle(context),
Thread::Unknown => {}
}
}

View File

@@ -16,6 +16,8 @@ extern crate rusqlite;
extern crate strum;
#[macro_use]
extern crate strum_macros;
#[macro_use]
extern crate jetscii;
#[macro_use]
mod log;
@@ -53,15 +55,15 @@ pub mod x;
pub mod dc_array;
mod dc_dehtml;
pub mod dc_imex;
mod dc_loginparam;
mod dc_mimefactory;
pub mod dc_mimeparser;
pub mod dc_receive_imf;
mod dc_simplify;
mod dc_strencode;
mod dc_token;
pub mod dc_tools;
mod login_param;
pub mod securejoin;
mod token;
#[cfg(test)]
mod test_utils;

206
src/login_param.rs Normal file
View File

@@ -0,0 +1,206 @@
use std::borrow::Cow;
use std::fmt;
use crate::context::Context;
use crate::error::Error;
#[derive(Default, Debug)]
pub struct LoginParam {
pub addr: String,
pub mail_server: String,
pub mail_user: String,
pub mail_pw: String,
pub mail_port: i32,
pub send_server: String,
pub send_user: String,
pub send_pw: String,
pub send_port: i32,
pub server_flags: i32,
}
impl LoginParam {
/// Create a new `LoginParam` with default values.
pub fn new() -> Self {
Default::default()
}
/// Read the login parameters from the database.
pub fn from_database(context: &Context, prefix: impl AsRef<str>) -> Self {
let prefix = prefix.as_ref();
let sql = &context.sql;
let key = format!("{}addr", prefix);
let addr = sql
.get_config(context, key)
.unwrap_or_default()
.trim()
.to_string();
let key = format!("{}mail_server", prefix);
let mail_server = sql.get_config(context, key).unwrap_or_default();
let key = format!("{}mail_port", prefix);
let mail_port = sql.get_config_int(context, key).unwrap_or_default();
let key = format!("{}mail_user", prefix);
let mail_user = sql.get_config(context, key).unwrap_or_default();
let key = format!("{}mail_pw", prefix);
let mail_pw = sql.get_config(context, key).unwrap_or_default();
let key = format!("{}send_server", prefix);
let send_server = sql.get_config(context, key).unwrap_or_default();
let key = format!("{}send_port", prefix);
let send_port = sql.get_config_int(context, key).unwrap_or_default();
let key = format!("{}send_user", prefix);
let send_user = sql.get_config(context, key).unwrap_or_default();
let key = format!("{}send_pw", prefix);
let send_pw = sql.get_config(context, key).unwrap_or_default();
let key = format!("{}server_flags", prefix);
let server_flags = sql.get_config_int(context, key).unwrap_or_default();
LoginParam {
addr: addr.to_string(),
mail_server,
mail_user,
mail_pw,
mail_port,
send_server,
send_user,
send_pw,
send_port,
server_flags,
}
}
pub fn addr_str(&self) -> &str {
self.addr.as_str()
}
/// Save this loginparam to the database.
pub fn save_to_database(
&self,
context: &Context,
prefix: impl AsRef<str>,
) -> Result<(), Error> {
let prefix = prefix.as_ref();
let sql = &context.sql;
let key = format!("{}addr", prefix);
sql.set_config(context, key, Some(&self.addr))?;
let key = format!("{}mail_server", prefix);
sql.set_config(context, key, Some(&self.mail_server))?;
let key = format!("{}mail_port", prefix);
sql.set_config_int(context, key, self.mail_port)?;
let key = format!("{}mail_user", prefix);
sql.set_config(context, key, Some(&self.mail_user))?;
let key = format!("{}mail_pw", prefix);
sql.set_config(context, key, Some(&self.mail_pw))?;
let key = format!("{}send_server", prefix);
sql.set_config(context, key, Some(&self.send_server))?;
let key = format!("{}send_port", prefix);
sql.set_config_int(context, key, self.send_port)?;
let key = format!("{}send_user", prefix);
sql.set_config(context, key, Some(&self.send_user))?;
let key = format!("{}send_pw", prefix);
sql.set_config(context, key, Some(&self.send_pw))?;
let key = format!("{}server_flags", prefix);
sql.set_config_int(context, key, self.server_flags)?;
Ok(())
}
}
impl fmt::Display for LoginParam {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
let unset = "0";
let pw = "***";
let flags_readable = get_readable_flags(self.server_flags);
write!(
f,
"{} {}:{}:{}:{} {}:{}:{}:{} {}",
unset_empty(&self.addr),
unset_empty(&self.mail_user),
if !self.mail_pw.is_empty() { pw } else { unset },
unset_empty(&self.mail_server),
self.mail_port,
unset_empty(&self.send_user),
if !self.send_pw.is_empty() { pw } else { unset },
unset_empty(&self.send_server),
self.send_port,
flags_readable,
)
}
}
fn unset_empty(s: &String) -> Cow<String> {
if s.is_empty() {
Cow::Owned("unset".to_string())
} else {
Cow::Borrowed(s)
}
}
fn get_readable_flags(flags: i32) -> String {
let mut res = String::new();
for bit in 0..31 {
if 0 != flags & 1 << bit {
let mut flag_added = 0;
if 1 << bit == 0x2 {
res += "OAUTH2 ";
flag_added = 1;
}
if 1 << bit == 0x4 {
res += "AUTH_NORMAL ";
flag_added = 1;
}
if 1 << bit == 0x100 {
res += "IMAP_STARTTLS ";
flag_added = 1;
}
if 1 << bit == 0x200 {
res += "IMAP_SSL ";
flag_added = 1;
}
if 1 << bit == 0x400 {
res += "IMAP_PLAIN ";
flag_added = 1;
}
if 1 << bit == 0x10000 {
res += "SMTP_STARTTLS ";
flag_added = 1
}
if 1 << bit == 0x20000 {
res += "SMTP_SSL ";
flag_added = 1
}
if 1 << bit == 0x40000 {
res += "SMTP_PLAIN ";
flag_added = 1
}
if 0 == flag_added {
res += &format!("{:#0x}", 1 << bit);
}
}
}
if res.is_empty() {
res += "0";
}
res
}

View File

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

View File

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

View File

@@ -9,7 +9,6 @@ use crate::constants::*;
use crate::contact::*;
use crate::context::Context;
use crate::dc_mimeparser::*;
use crate::dc_token::*;
use crate::dc_tools::*;
use crate::e2ee::*;
use crate::error::Error;
@@ -20,6 +19,7 @@ use crate::param::*;
use crate::peerstate::*;
use crate::qr::check_qr;
use crate::stock::StockMessage;
use crate::token;
use crate::types::*;
pub const NON_ALPHANUMERIC_WITHOUT_DOT: &AsciiSet = &NON_ALPHANUMERIC.remove(b'.');
@@ -80,8 +80,8 @@ pub fn dc_get_securejoin_qr(context: &Context, group_chat_id: uint32_t) -> Optio
let fingerprint: String;
ensure_secret_key_exists(context).ok();
let invitenumber = dc_token_lookup_or_new(context, DC_TOKEN_INVITENUMBER, group_chat_id);
let auth = dc_token_lookup_or_new(context, DC_TOKEN_AUTH, group_chat_id);
let invitenumber = token::lookup_or_new(context, token::Namespace::InviteNumber, group_chat_id);
let auth = token::lookup_or_new(context, token::Namespace::Auth, group_chat_id);
let self_addr = match context.sql.get_config(context, "configured_addr") {
Some(addr) => addr,
None => {
@@ -387,7 +387,7 @@ pub fn handle_securejoin_handshake(
return ret;
}
};
if !dc_token_exists(context, DC_TOKEN_INVITENUMBER, &invitenumber) {
if !token::exists(context, token::Namespace::InviteNumber, &invitenumber) {
warn!(context, 0, "Secure-join denied (bad invitenumber).",);
return ret;
}
@@ -507,7 +507,7 @@ pub fn handle_securejoin_handshake(
return ret;
}
};
if !dc_token_exists(context, DC_TOKEN_AUTH, &auth_0) {
if !token::exists(context, token::Namespace::Auth, &auth_0) {
could_not_establish_secure_connection(context, contact_chat_id, "Auth invalid.");
return ret;
}

View File

@@ -4,7 +4,7 @@ use lettre::*;
use crate::constants::Event;
use crate::constants::*;
use crate::context::Context;
use crate::dc_loginparam::*;
use crate::login_param::LoginParam;
use crate::oauth2::*;
pub struct Smtp {
@@ -43,7 +43,7 @@ impl Smtp {
}
/// Connect using the provided login params
pub fn connect(&mut self, context: &Context, lp: &dc_loginparam_t) -> bool {
pub fn connect(&mut self, context: &Context, lp: &LoginParam) -> bool {
if self.is_connected() {
warn!(context, 0, "SMTP already connected.");
return true;

View File

@@ -1015,14 +1015,14 @@ pub fn housekeeping(context: &Context) {
match std::fs::metadata(entry.path()) {
Ok(stats) => {
let created = stats.created().is_ok()
let recently_created = stats.created().is_ok()
&& stats.created().unwrap() > keep_files_newer_than;
let modified = stats.modified().is_ok()
let recently_modified = stats.modified().is_ok()
&& stats.modified().unwrap() > keep_files_newer_than;
let accessed = stats.accessed().is_ok()
let recently_accessed = stats.accessed().is_ok()
&& stats.accessed().unwrap() > keep_files_newer_than;
if created || modified || accessed {
if recently_created || recently_modified || recently_accessed {
info!(
context,
0,
@@ -1090,13 +1090,20 @@ fn maybe_add_from_param(
) {
context
.sql
.query_row(query, NO_PARAMS, |row| {
let param: Params = row.get::<_, String>(0)?.parse().unwrap_or_default();
if let Some(file) = param.get(param_id) {
maybe_add_file(files_in_use, file);
}
Ok(())
})
.query_map(
query,
NO_PARAMS,
|row| row.get::<_, String>(0),
|rows| {
for row in rows {
let param: Params = row?.parse().unwrap_or_default();
if let Some(file) = param.get(param_id) {
maybe_add_file(files_in_use, file);
}
}
Ok(())
},
)
.unwrap_or_else(|err| {
warn!(context, 0, "sql: failed to add_from_param: {}", err);
});

58
src/token.rs Normal file
View File

@@ -0,0 +1,58 @@
//! Functions to read/write token from/to the database. A token is any string associated with a key.
use deltachat_derive::*;
use crate::context::Context;
use crate::dc_tools::*;
use crate::sql;
// Token namespaces
#[derive(Debug, Display, Clone, Copy, PartialEq, Eq, FromPrimitive, ToPrimitive, ToSql, FromSql)]
#[repr(i32)]
pub enum Namespace {
Unknown = 0,
Auth = 110,
InviteNumber = 100,
}
impl Default for Namespace {
fn default() -> Self {
Namespace::Unknown
}
}
pub fn save(context: &Context, namespace: Namespace, foreign_id: u32) -> String {
// foreign_id may be 0
let token = dc_create_id();
sql::execute(
context,
&context.sql,
"INSERT INTO tokens (namespc, foreign_id, token, timestamp) VALUES (?, ?, ?, ?);",
params![namespace, foreign_id as i32, &token, time()],
)
.ok();
token
}
pub fn lookup(context: &Context, namespace: Namespace, foreign_id: u32) -> Option<String> {
context.sql.query_row_col::<_, String>(
context,
"SELECT token FROM tokens WHERE namespc=? AND foreign_id=?;",
params![namespace, foreign_id as i32],
0,
)
}
pub fn lookup_or_new(context: &Context, namespace: Namespace, foreign_id: u32) -> String {
lookup(context, namespace, foreign_id).unwrap_or_else(|| save(context, namespace, foreign_id))
}
pub fn exists(context: &Context, namespace: Namespace, token: &str) -> bool {
context
.sql
.exists(
"SELECT id FROM tokens WHERE namespc=? AND token=?;",
params![namespace, token],
)
.unwrap_or_default()
}

View File

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