Compare commits

..

465 Commits

Author SHA1 Message Date
holger krekel
e8158b5fbf fix the merge 2019-09-04 14:52:06 +02:00
holger krekel
580fda26b7 Merge branch 'master' into fix_securejoin_closure 2019-09-03 19:06:40 +02:00
dignifiedquire
1a1f0c0a7c refactor(e2ee): rename dc_e2ee -> e2ee 2019-09-03 19:05:21 +02:00
dignifiedquire
3944592c09 refactor(e2ee): restructure types a and method slightly 2019-09-03 19:05:21 +02:00
holger krekel
a5f862a564 remove python2 testing 2019-09-03 19:04:09 +02:00
Dmitry Bogatov
6288909481 Refactor is_known_rfc724_mid() to use Iterator interface 2019-09-03 16:08:52 +02:00
Dmitry Bogatov
c95f134963 Change return type of is_known_rfc724_mid_in_list() to bool 2019-09-03 16:08:52 +02:00
Simon Laux
024c2883c0 cargo fmt 2019-09-03 15:32:36 +02:00
Simon Laux
d7b0ecad75 transform current_block to ok_to_continue 2019-09-03 15:32:36 +02:00
B. Petersen
50947b81c0 set correct default value for inbox_watch 2019-09-03 15:31:17 +02:00
holger krekel
e5a1b721f1 try use py36 for uploads (#429)
* try use py36 for uploads

* another try

* another
2019-09-03 15:30:28 +02:00
Alexander Krotov
aeb1a88e7a Remove dc_move.rs
and move dc_do_heuristics_moves to context.rs
2019-09-03 13:39:14 +02:00
Alexander Krotov
c406675d6a Mark dc_do_heuristics_moves as safe 2019-09-03 13:39:14 +02:00
Alexander Krotov
1ec193991b Make dc_token.rs safe 2019-09-02 20:57:22 +02:00
jikstra
7b9da77d51 cargo fmt 2019-09-02 18:31:51 +02:00
Simon Laux
1d09d2f0d1 cargo fmt 2019-09-02 18:00:23 +02:00
Simon Laux
5b993f601f transform current_block to ok_to_continue 2019-09-02 18:00:23 +02:00
jikstra
91510e7717 Cleanup closure should be defined as first thing and all variables should get passed as arguments 2019-09-02 17:37:41 +02:00
jikstra
ff1e62a42d Closure shouldn't copy qr variable into it's own scope and we should
never return null pointer
2019-09-02 17:05:31 +02:00
Alexander Krotov
77be6c4294 Merge pull request #424 from link2xt/chatlist_safe
Mark Chatlist::get_summary as safe
2019-09-02 11:45:12 +00:00
Alexander Krotov
1497f263dc Mark Chatlist::get_summary as safe 2019-09-02 13:00:04 +03:00
björn petersen
d6e2798f5a Merge pull request #416 from deltachat/as_u32
Convert DC_*_ID_* constants to u32
2019-09-02 10:22:18 +02:00
björn petersen
8e41037eb1 Merge pull request #422 from deltachat/raw_mime_assert
mailmime_get_mime_type: do not dereference null pointer
2019-09-02 10:20:36 +02:00
björn petersen
0bcd3bc94e Merge pull request #423 from KAction/fix-master2
unbreak master
2019-09-02 10:19:57 +02:00
Dmitry Bogatov
fb5d53226d Fix errorneous ffi check in dc_send_msg 2019-09-01 22:45:49 +00:00
Alexander Krotov
b5a9cc1380 Convert DC_*_ID_* constants to u32 2019-09-01 23:41:05 +03:00
Alexander Krotov
1ba7368c6d mailmime_get_mime_type: do not dereference null pointer
In C code there was a check for null pointer in reconcat_mime()
It did nothing if raw_mime was null.
Now the result is stored even when mailmime_get_mime_type is
explicitly called with null pointer.

This commit restores C core logic.

Fixes #420
2019-09-01 20:11:50 +03:00
Alexander Krotov
c1a78d9c3e Merge remote-tracking branch 'kaction/clippy'
Fix clippy warnings.
2019-08-30 01:49:46 +03:00
björn petersen
ad36671adb Merge pull request #414 from deltachat/forgiving-ffi
handle ffi-failures compatible to core-c
2019-08-29 21:54:23 +02:00
B. Petersen
8902d0843b fix some return values 2019-08-28 18:32:34 +02:00
B. Petersen
010ac6a6ac print message to stderr on careless ffi usage 2019-08-28 18:24:01 +02:00
B. Petersen
c71589a710 handle ffi-failures compatible to core-c 2019-08-28 13:46:47 +02:00
Dmitry Bogatov
9b70ea0595 Fix one clippy::unneeded_unwrap warning 2019-08-28 00:32:01 +00:00
Dmitry Bogatov
a6928c29e8 Fix one clippy::unneeded_unwrap warning 2019-08-28 00:23:15 +00:00
Dmitry Bogatov
3d9129c198 Fix one clippy::unneeded_unwrap warning 2019-08-28 00:17:12 +00:00
Dmitry Bogatov
cf8f37449c Fix one clippy::unneeded_unwrap warning 2019-08-28 00:13:37 +00:00
Dmitry Bogatov
886870964b Fix one clippy::unneeded_unwrap warning 2019-08-28 00:11:48 +00:00
Dmitry Bogatov
6a5e107e64 Fix one clippy::unneeded_unwrap warning 2019-08-28 00:09:40 +00:00
Dmitry Bogatov
723624b3f7 Fix one clippy::unneeded_unwrap warning 2019-08-28 00:06:49 +00:00
Dmitry Bogatov
634dfaa1ca Fix one clippy::unneeded_unwrap warning 2019-08-28 00:03:07 +00:00
Dmitry Bogatov
2d00495d29 Fix one clippy::unneeded_unwrap warning 2019-08-28 00:01:09 +00:00
Dmitry Bogatov
ea964fd733 Fix one clippy::unneeded_unwrap warning 2019-08-27 23:58:50 +00:00
Dmitry Bogatov
dd5803e576 Fix one clippy::unneeded_unwrap warning 2019-08-27 23:18:42 +00:00
Dmitry Bogatov
85e16f6e82 Fix one clippy::unneeded_unwrap warning 2019-08-27 23:06:56 +00:00
Dmitry Bogatov
0a9f61783d Fix one clippy::unneeded_unwrap warning 2019-08-27 23:00:37 +00:00
Dmitry Bogatov
19a0071585 Fix one clippy::unneeded_unwrap warning 2019-08-27 22:56:46 +00:00
Dmitry Bogatov
2fe07e86c7 Replace explicit casts from 0 to pointer with std::ptr::null 2019-08-27 21:22:03 +00:00
Dmitry Bogatov
22c1b34ebf Remove 'unsafe' qualifier of dc_mimeparser_new() 2019-08-27 22:20:45 +02:00
Dmitry Bogatov
eca11a74d7 Change type of dc_mimepart_t.msg to Option<String> 2019-08-27 22:20:45 +02:00
Dmitry Bogatov
b3df24d188 Make dc_mimeparser_is_mailinglist_message() return bool, not int 2019-08-27 22:20:45 +02:00
Dmitry Bogatov
6fcd6419bd Use named MAILMIME_* constants instead of numeric values 2019-08-27 22:20:45 +02:00
Dmitry Bogatov
edb0fa17af Make dc_mimeparser::reconcat_mime() safe 2019-08-27 22:20:45 +02:00
Dmitry Bogatov
86eb9cc058 Change type of `dc_mimeparser_t.type_0' to constants::Viewtype
Adjust use sites accordingly.
2019-08-27 22:20:45 +02:00
Dmitry Bogatov
47c0526026 Avoid separate declaration and definition of `need_drop' variable 2019-08-27 22:20:45 +02:00
Dmitry Bogatov
f5416b1c2c Drop deprecated S_GENERATE_COMPOUND_MSGS flag
There is no code that modifies this flag anyway.
2019-08-27 22:20:45 +02:00
Dmitry Bogatov
a9f42f7a9e dc_mimeparser_parse(): avoid mutability of `has_setup_file' variable 2019-08-27 22:20:45 +02:00
Dmitry Bogatov
7650e1c7df Narrow type of dc_mimeparser_t.is_send_by_messenger to bool 2019-08-27 22:20:45 +02:00
Dmitry Bogatov
0de76d6d3f dc_mimeparser_parse(): simplify conditional expression 2019-08-27 22:20:45 +02:00
Dmitry Bogatov
db4f972315 Move test for mimeparser from stress.rs into src/* 2019-08-27 22:20:45 +02:00
Dmitry Bogatov
8e4a01c98d Make dc_mimeparser_parse() return parser instead of modifying reference
Since dc_mimeparser_parse() called `dc_mimeparser_empty()' on passed reference
anyway, it makes more sense to create new instance of `dc_mimeparser_t' and
return it instead.

Previously, usage pattern was following:

 dc_mimeparser_new()
 dc_mimeparser_empty() // semantically no-op, called inside dc_mimeparser_parse

Now call to dc_mimeparser_empty() is avoided.
2019-08-27 22:20:45 +02:00
Dmitry Bogatov
21976b14a6 dc_mimeparser_parse: accept input as slice, not as pointer + length 2019-08-27 22:20:45 +02:00
björn petersen
d3048aa06f Merge pull request #411 from deltachat/fix-nested-msg-info
fix(message): avoid nested sql statement in dc_get_msg_info
2019-08-27 16:41:54 +02:00
dignifiedquire
a1a85270e6 fix(message): avoid nested sql statement in dc_get_msg_info
Closes #410
2019-08-27 14:27:37 +02:00
Friedel Ziegelmayer
2f6438f75a refactor: save locations (#407)
refactor: save locations
2019-08-27 14:27:04 +02:00
björn petersen
ff0fdd5837 Merge pull request #406 from deltachat/forgiving-ffi
allow passing empty arrays to ffi
2019-08-27 10:20:37 +02:00
dignifiedquire
25b0a26ff9 fix(ffi): handle result from location deletion 2019-08-26 21:50:33 +02:00
dignifiedquire
3f8abd2218 refactor(location): make location on the kml object not optional 2019-08-26 21:49:47 +02:00
dignifiedquire
4ec214b3fc refactor(location): use bitflags for tags 2019-08-26 21:46:57 +02:00
B. Petersen
cefbd64f37 allow passing empty arrays to ffi 2019-08-26 21:38:23 +02:00
dignifiedquire
d25d839d6a refactor(location): more rusty api 2019-08-26 21:29:40 +02:00
dignifiedquire
23d49560bf refactor(location): rename module dc_location -> location 2019-08-26 21:09:40 +02:00
dignifiedquire
bb16849eef refactor(location): remove most unsafe usage 2019-08-26 21:07:53 +02:00
Alexander Krotov
ae17971599 top_evil_rs.py: use glob to walk into subdirs
Otherwise configure/* is not included in the report.
2019-08-26 19:31:30 +02:00
B. Petersen
f13b068479 remove accidentally added function body 2019-08-26 19:17:58 +02:00
B. Petersen
850941bf8b target doxygen warnings 2019-08-26 19:17:58 +02:00
B. Petersen
11dd156594 tweak title 2019-08-26 19:17:58 +02:00
B. Petersen
78587ee6b1 add howto-generate-ffi-documentation 2019-08-26 19:17:58 +02:00
B. Petersen
3eeb184278 ignore doxygen generated files 2019-08-26 19:17:58 +02:00
B. Petersen
7632eb1ce0 make doxygen work 2019-08-26 19:17:58 +02:00
B. Petersen
3eea175d36 ffi docs for dc_lot_t 2019-08-26 19:17:58 +02:00
B. Petersen
8f24ff74dd ffi docs for dc_contact_t 2019-08-26 19:17:58 +02:00
B. Petersen
6fab7d0a27 ffi docs for dc_msg_t 2019-08-26 19:17:58 +02:00
B. Petersen
7cc52e0a55 ffi docs for dc_chat_t 2019-08-26 19:17:58 +02:00
B. Petersen
aa0801014a ffi docs for dc_chatlist_t 2019-08-26 19:17:58 +02:00
B. Petersen
9e379338bc ffi docs for dc_array_t 2019-08-26 19:17:58 +02:00
B. Petersen
ba62d13a14 ffi docs for dc_context_t 2019-08-26 19:17:58 +02:00
Dmitry Bogatov
d7d7147549 refactor: make dc_dehtml() function safe
* Make dc_dehtml() function safe

* Change type of is_msgrmsg parameter to bool

* Narrow type of local variable in simplify_plain_text()

* Export less fields of `Simplify' record

* Demote is_cut_* from fields of `Simplify' to local variables

* Refactor part of simplify_plain_text()

Refactor footer ("-- " and similar) code into separate function,
and re-implement it with standard Rust string methods.

It simplifies code and allows removing one mutable local variable.

* Replace dc_split_into_lines with String.split()

  * src/dc_simplify.rs(find_message_footer): adjust type signature to accept
    slice of &str, not slice of pointers

  * src/dc_simplify.rs(simplify_plain_text): adjust code to use '==' operator
    instead of strcmp(3).

  * src/dc_simplify.rs(is_empty_line, is_quoted_headline, is_plain_quote):
    + adjust type signatures to accept &str, not 'const char *'
    + remove no longer needed 'unsafe' qualifier

  * src/dc_tools(dc_split_into_lines, dc_free_splitted_lines): remove no longer
    used functions.

In addition to additional type-safety, this change reduces number of
allocations: String.split returns iterator of &str.

* Make simplify_plain_text() safe

* Make Simplify.simplify return String, not pointer

* Refactor Simplify.simplify to use String methods, not pointers

* Make Simplify.simplify() safe

* Avoid neeless allocation in Simplify.simplify when input is html

* Add tests for simplify utilities

* Document discussion about is_empty_line() discussion
2019-08-26 17:15:14 +02:00
Alexander Krotov
8a73f84003 Move all unsafe code from dc_array.rs to FFI 2019-08-21 09:30:59 +02:00
Asiel Díaz Benítez
118599b4cc Update account.py
fix doc string
2019-08-20 17:10:39 +02:00
Simon Laux
1d32e010ae Merge pull request #397 from deltachat/move_autoconfig_to_dedicated_file
Move moz- and outlk-autoconfig to dedicated files
2019-08-19 19:42:25 +02:00
Simon Laux
91481caf89 cargo fmt 2019-08-19 19:04:12 +02:00
Simon Laux
491826556b renaming dc_configure to configure
and renaming the autoconfigure modules
2019-08-19 19:03:46 +02:00
Friedel Ziegelmayer
6463019faf Merge pull request #398 from deltachat/refactor-msg
Round 1 for rust message
2019-08-19 19:03:38 +02:00
Simon Laux
e3b2a7a69b remove unused struct 2019-08-19 18:58:47 +02:00
Simon Laux
51b54fce64 move out the autoconfigure functions 2019-08-19 18:58:47 +02:00
Simon Laux
184c58bf36 move main file
making this step by step in hope that git can then better work with this
2019-08-19 18:58:47 +02:00
dignifiedquire
39abb0b0ad refactor(message): rename dc_msg to message 2019-08-19 12:13:11 +02:00
dignifiedquire
cb6c8ac78b refactor(msg): use rust based allocations 2019-08-19 12:10:26 +02:00
Friedel Ziegelmayer
a906faeb35 refactor: a rusty job
* refactor(jobthread): safe and rusty
* refactor(job): rusty and safe
2019-08-19 12:07:13 +02:00
Alexander Krotov
1a8e08e429 Merge pull request #396 from deltachat/chat-array
Remove dc_array_t from chat.rs
2019-08-19 06:55:58 +00:00
dignifiedquire
d47a693611 refactor: rename dc_qr -> qr 2019-08-19 08:06:54 +02:00
dignifiedquire
886262539a refactor: save lot implementation and follow up refactors
rewrote qr code to match the now safe lot
2019-08-19 08:06:54 +02:00
dignifiedquire
401c5a7cb0 refactor(lot): rename dc_lot to lot 2019-08-19 08:06:54 +02:00
dignifiedquire
b5c66dd52a refactor(lot): rust memory management 2019-08-19 08:06:54 +02:00
Alexander Krotov
e7cf5a546a Remove dc_array_t from chat.rs 2019-08-18 23:02:16 +03:00
Friedel Ziegelmayer
8c10aa287c fix(python): free allocated string pointers (#393)
* fix(python): free allocated string pointers

* Update python/src/deltachat/cutil.py

Co-Authored-By: holger krekel  <holger@merlinux.eu>

* Update cutil.py

* probably the proper way to handle ffi.strings
2019-08-18 12:45:38 +02:00
Alexander Krotov
6b2fe03d08 Replace dc_array_t with Vec in context.rs 2019-08-18 12:44:49 +02:00
holger krekel
799d362654 cargo fmt 2019-08-18 12:44:03 +02:00
holger krekel
64beb17fa6 subst current_blocks with ok_to_continue 2019-08-18 12:44:03 +02:00
Friedel Ziegelmayer
2bddb8409c Merge pull request #390 from deltachat/remove-dead-code
remove dead code
2019-08-17 14:02:00 +02:00
B. Petersen
b1a056082a remove dead code 2019-08-17 12:36:26 +02:00
Friedel Ziegelmayer
7dc82ba4d7 Merge pull request #374 from deltachat/refactor-chat
refactor(chat): make it rusty and safe
2019-08-17 12:15:28 +02:00
dignifiedquire
4645a4300e refactor: remove unused methods 2019-08-17 11:40:43 +02:00
dignifiedquire
13d8306a7b refactor(chat): some minor cleanup and api improvements 2019-08-17 11:40:43 +02:00
dignifiedquire
9b1a74cc22 refactor(chat): remove C strings from the public interface 2019-08-17 11:40:43 +02:00
dignifiedquire
25e97df641 refactor(chat): store rust strings in the chat struct 2019-08-17 11:35:02 +02:00
dignifiedquire
6b3245ddfc refactor(ffi): use nicer exentsion traits for results 2019-08-17 11:34:10 +02:00
dignifiedquire
001880e1f0 refactor(chat): first round of method renaming and restructuring 2019-08-17 11:34:10 +02:00
dignifiedquire
ddfd067e97 refactor(chat): rust based memory management 2019-08-17 11:30:26 +02:00
dignifiedquire
64117c2964 refactor(chat): rename dc_chat to chat 2019-08-17 11:30:26 +02:00
dignifiedquire
c8ce099f22 refactor(chat): improve field types by using enums and bools 2019-08-17 11:29:08 +02:00
dignifiedquire
4878192a25 refactor: remove now unused saxparser 2019-08-17 01:32:43 +02:00
dignifiedquire
c901ab844f refactor(dehtml): switch to quick-xml for parsing 2019-08-17 01:32:43 +02:00
dignifiedquire
eb2b4028ab refactor(configure) use rust based xml parsing 2019-08-17 01:32:43 +02:00
dignifiedquire
64a0718032 fixup: dont panic on errors 2019-08-17 01:32:43 +02:00
dignifiedquire
b2a6876a50 refactor(location): switch to rust based xml parsing 2019-08-17 01:32:43 +02:00
holger krekel
5ea2da4245 cargo fmt 2019-08-17 01:28:04 +02:00
holger krekel
ff1f286882 remove goto/current_block logic with ok_to_continue 2019-08-17 01:28:04 +02:00
Alexander Krotov
e013532e27 Remove unused dc_array_get_string 2019-08-17 01:04:37 +02:00
Alexander Krotov
1a42a1e6b1 Replace dc_array with Vec in dc_receive_imf.rs 2019-08-17 01:04:37 +02:00
Jikstra
d946774741 Make dc_msg_get_summarytext_by_raw safe (#316)
* Make dc_msg_get_summarytext_by_raw safe

* use dc_truncate method in all places

* Fix tests and add docs to dc_truncate()

* Make text argument an AsRef<&str> and rename type_0 to viewtype

* Fix too early return in dc_msg_get_summarytext_by_raw. Fixes https://github.com/deltachat/deltachat-core-rust/issues/313
2019-08-16 14:58:20 +02:00
Friedel Ziegelmayer
3d080d2733 Remove dead code (#384)
Remove dead code
2019-08-16 11:13:40 +02:00
Dmitry Bogatov
aa7a149560 Check extension of files in case-insensitive way 2019-08-16 08:56:25 +02:00
Dmitry Bogatov
6beea86df7 Use perfrect hash map for new implementation of dc_msg_guess_msgtype_from_suffix 2019-08-16 08:56:25 +02:00
Dmitry Bogatov
5917d05305 Drop old version of {dc_msg_guess_msgtype_from_suffix} 2019-08-16 08:56:25 +02:00
Dmitry Bogatov
ceb4464a9b Adjust call sites to use new version of dc_msg_guess_msgtype_from_suffix 2019-08-16 08:56:25 +02:00
Dmitry Bogatov
8f0e554bc4 Implement Rust version of {dc_msg_guess_msgtype_from_suffix} 2019-08-16 08:56:25 +02:00
Dmitry Bogatov
7c7a1b64df Remove dead code 2019-08-16 04:46:20 +00:00
Dmitry Bogatov
ea661896a1 Do not export more than strictly necessary 2019-08-16 04:46:20 +00:00
Jikstra
2bb2ef07e9 Merge pull request #381 from deltachat/dc_array_ffi
Move some unsafe dc_array_* functions to FFI
2019-08-16 01:48:16 +02:00
Jikstra
c71934215b Merge pull request #382 from deltachat/correct-void-func
declare void function as such
2019-08-16 01:45:04 +02:00
Dmitry Bogatov
f31884fc15 Remove unused `dc_str_contains' function 2019-08-15 21:57:14 +00:00
Dmitry Bogatov
2b1ffb5fb9 Do not export `dc_strlower' function 2019-08-15 21:56:42 +00:00
Alexander Krotov
fadcd43fee Move some unsafe dc_array_* functions to FFI 2019-08-16 00:53:32 +03:00
B. Petersen
5af800b16a declare void function as such 2019-08-15 23:52:04 +02:00
Alexander Krotov
37622af55a Return Vec<dc_location> from dc_get_locations 2019-08-15 20:43:04 +02:00
jikstra
d4dfc5443c Add missing config keys which caused a panic in desktop 2019-08-15 20:38:22 +02:00
holger krekel
18b70bff0e remove redundant unsafe dc_str_to_color impl by calling safe one, fixes #375 2019-08-15 15:48:11 +02:00
jikstra
d4bd9187d6 Fix panic in dc_set_chat_profile_image because of missing
ok_to_continue=false
2019-08-15 13:36:43 +02:00
dignifiedquire
712f5a9782 refactor(tools): cleanup and make clippy a little happier
Uses a crate now for extracing image meta data, instead of our own hand rolled code.
2019-08-15 11:26:32 +02:00
Alexander Krotov
29d4f6888d Store IDs in dc_array as u32
This changes dc_array_get_raw API
2019-08-15 01:54:51 +02:00
Alexander Krotov
5b47409fb0 Remove unused dc_array_* API 2019-08-15 01:54:51 +02:00
Asiel Díaz Benítez
8077fdeddb Update examples.rst
just replaced `send_text_message` with `send_text`
2019-08-15 01:47:03 +02:00
dignifiedquire
6f290e249f chore: release v1.0.0-alpha.4 2019-08-14 13:58:51 +02:00
Simon Laux
a655f2cbba fix: properly handle chat name when contact has no name
* fix chat name empty when contact has no name
Show contacts display_name instead as defined in:
https://c.delta.chat/classdc__chat__t.html#a0fb1b4850bcd899eaa06d23648a1efaf
"For one-to-one chats, this is the name of the contact"
2019-08-14 13:55:56 +02:00
dignifiedquire
0cb42f840d fix(imap): call create, not subscribe
Closes #324
2019-08-14 11:49:39 +02:00
dignifiedquire
99aabef7f3 feat: load package version during build
This removes the duplication of the version string between the `Cargo.toml` and `constants.rs`
2019-08-14 10:35:32 +02:00
holger krekel
7d51c6e4f4 fix documentation 2019-08-14 10:18:06 +02:00
Dmitry Bogatov
f463fb3759 Change type of Context.dbfile to std::path::PathBuf
This change makes code more type-correct, but introduces copying: compiler
refuses to return reference to object behind Arc<RwLock>.
2019-08-14 09:43:26 +02:00
Dmitry Bogatov
cb0eb0e68e refactor: make encrypted_and_signed() return bool, not libc::int 2019-08-14 09:26:08 +02:00
holger krekel
8009f220fc hold smtp lock during the full send action 2019-08-14 01:50:15 +02:00
dignifiedquire
4f1551b91f chore(ci): enable livetests 2019-08-14 01:50:15 +02:00
dignifiedquire
6ba37a135e fix(imap): reduce lock contention around session and config locks
Hopefully closes #331
2019-08-14 01:50:15 +02:00
dignifiedquire
dab514d8bc feat: switch to simpler email address parsing
This stops the psl insanity and matches more closely what the c-core did

Closes #325
2019-08-14 01:48:41 +02:00
Dmitry Bogatov
8342b29618 Fix some clippy warnings 2019-08-14 01:45:39 +02:00
B. Petersen
e05944c6cb simplify progress! macro 2019-08-14 01:33:57 +02:00
Alexander Krotov
88a81f5737 Return Vec from get_all_blocked
And never return nullptr from dc_get_blocked_contacts.
2019-08-13 23:56:17 +02:00
B. Petersen
9cf6ca045c send CONFIGURE_PROGRESS(0) on failure and only on failure 2019-08-13 23:55:56 +02:00
holger krekel
ba381d0d0b fix failing array asserts #355 2019-08-13 23:07:38 +02:00
Friedel Ziegelmayer
033ebc7ce3 Setup clippy (#349)
Setup clippy
2019-08-13 22:37:16 +02:00
holger krekel
6292219551 fix bug that lead to all liveconfig tests failing 2019-08-13 22:34:27 +02:00
Friedel Ziegelmayer
ed237c8d25 refactor(receive_imf): first pass at some more sanity 2019-08-13 17:58:32 +02:00
Alexander Krotov
d46a5345d2 Use Vec instead of dc_array_t in search_chat_ids_by_contact_ids 2019-08-13 17:54:28 +02:00
dignifiedquire
523141597e chore: remove no longer needed features 2019-08-13 12:20:53 +02:00
dignifiedquire
cfed5c914c chore: update rust nightly version 2019-08-13 12:20:53 +02:00
dignifiedquire
20f9bb3b14 chore: setup clippy 2019-08-13 12:20:53 +02:00
Friedel Ziegelmayer
6067160582 Implement procedural macro to derive {To,From}Sql traits (#322)
Implement procedural macro to derive {To,From}Sql traits
2019-08-13 12:16:24 +02:00
Alexander Krotov
3175c4f7ba Merge pull request #333 from link2xt/dc_array-assert
dc_array: panic on null pointers and out of range indexes
2019-08-13 12:53:46 +03:00
Simon Laux
a29f06a730 cargo fmt 2019-08-13 10:45:27 +02:00
Simon Laux
c713474d1f remove lot magic 2019-08-13 10:45:27 +02:00
Simon Laux
89c874d4a9 remove message magic
there is still a reference to C #[repr(C)] so I'm not sure at this
2019-08-13 10:45:27 +02:00
Simon Laux
5e3cba9b70 remove chat magic 2019-08-13 10:45:27 +02:00
Dmitry Bogatov
a7894fd785 cargo fmt 2019-08-13 10:11:17 +02:00
Dmitry Bogatov
c638a770f9 Remove redundant check
Condition '!imffields.is_null()' is always true, since it is contained in conditional

	  if !(in_out_message.is_null() || imffields.is_null()) {

which is equivalent to

	  if !in_out_message.is_null() && !imffields.is_null() {
2019-08-13 10:11:17 +02:00
Simon Laux
6ced6ac23b macro for progress event like in C core 2019-08-13 10:07:13 +02:00
Simon Laux
d0b77b61eb cargo fmt 2019-08-13 10:07:13 +02:00
Simon Laux
b440c3636b replace gotos with ok_to_continue 2019-08-13 10:07:13 +02:00
Floris Bruynooghe
dfd58961f7 Safe load_or_generate_self_public_key
The function is made safe and now returns Result.  Functionally it now
fails when it can not write the newly generated key to the database
whereas before it still returned the key but logged a warning.  There
is no reason this shouldn't be able to store the key and silently not
storing the key may result in later operations assuming the key is
available, so failing seems like a better choice.

The function now also uses a proper mutex to guard against multiple
threads generating keys.  And this mutex is Context-scoped rather than
fully global (static).
2019-08-13 10:04:38 +02:00
Jikstra
139c9f37b1 Merge pull request #339 from deltachat/rm_goto_dc_job
rm GOTO: dc job
2019-08-13 02:10:08 +02:00
Jikstra
2445b12898 Merge pull request #342 from deltachat/rm_goto_cmdline
rm GOTO: cmdline
2019-08-13 02:09:14 +02:00
Alexander Krotov
4d402f3a06 dc_array: panic on null pointers and out of range indexes 2019-08-13 03:07:13 +03:00
Jikstra
ab022ccc33 Merge pull request #334 from link2xt/dc_get_chat_contacts_vec
Return Vec from dc_get_chat_contacts
2019-08-13 02:03:58 +02:00
Alexander Krotov
fb7bbac524 Return Vec from dc_get_chat_contacts 2019-08-13 02:37:18 +03:00
Jikstra
39fbff5fb6 Merge pull request #347 from link2xt/dc_array_t-new
Implement From<Vec<u32>> for dc_array_t and use it instead of new()
2019-08-13 01:30:10 +02:00
Jikstra
3ac1eaf7d2 Merge pull request #341 from deltachat/add_cmdline_quit_cmd
add quit command as alias to exit in cmdline
2019-08-13 00:49:27 +02:00
Alexander Krotov
6c95d008e0 Implement From<Vec<u32>> for dc_array_t and use it instead of new() 2019-08-13 01:40:47 +03:00
Friedel Ziegelmayer
16f891c290 Merge pull request #337 from deltachat/fix-cmdline
fix 'chats' command in cmdline tool
2019-08-12 10:39:11 +02:00
holger krekel
650bddd54b try fix upload failure with / branches 2019-08-12 08:38:15 +02:00
Simon Laux
9e30df4b43 cargo fmt 2019-08-12 05:42:02 +02:00
Simon Laux
50c592e41f convert current_block to goto and remove UDC*
*Unreachable Duplicated Code (I made that shortcut up)
2019-08-12 05:41:24 +02:00
Simon Laux
bdf8cd2dd5 add quit command as alias to exit in cmdline 2019-08-12 01:52:09 +02:00
B. Petersen
5554df29fd show full chatlist by just entering 'chats' in cmdline 2019-08-12 01:40:04 +02:00
Simon Laux
2dd3088f50 cargo fmt 2019-08-12 01:33:07 +02:00
Simon Laux
b9bd128c7a goto to ok_to_continue 2019-08-12 01:32:34 +02:00
B. Petersen
adb67d1910 off by one: show chats cnt-1..0 instead of cnt-1..1 2019-08-12 01:28:36 +02:00
Jikstra
ce3b815bd8 Merge pull request #319 from deltachat/fix_utf8_text_msg_load_from_db
Fix having a msg object without a text in it because of invalid utf8
2019-08-12 01:16:23 +02:00
holger krekel
b94f9ef496 address @flub comments 2019-08-11 23:09:48 +02:00
holger krekel
77db475663 - rework running of liveconfig tests
- better README reflecting how to use things, don't advertise
  run-integration-tests to only have one documented way
  and use less tools for rust-devs that just want to run
  python tests

- fix test skipping and get circle-ci to play along

- update docker related docs as well
2019-08-11 23:09:48 +02:00
jikstra
a3683be047 cargo fmt 2019-08-11 18:55:23 +02:00
Jikstra
9dca19d6c9 Merge pull request #302 from deltachat/rm_goto_dc_imex
Remove goto in dc imex
2019-08-11 18:52:17 +02:00
jikstra
3ba847ece2 Apply requested changes 2019-08-11 16:57:49 +02:00
Simon Laux
91bf948d1e chat magic to const 2019-08-11 10:45:17 +02:00
Simon Laux
91fec77f4b fix msg info: message-id 2019-08-11 09:05:56 +02:00
Simon Laux
8fb25a6340 Cargo fmt: removed two empty llines 2019-08-11 09:04:47 +02:00
Simon Laux
cf49acff67 part 2 2019-08-11 02:07:51 +02:00
Simon Laux
4f1a25e1bf cargo fmt 2019-08-11 02:07:51 +02:00
Simon Laux
8608daa7dc remove goto 2019-08-11 02:07:51 +02:00
Alexander Krotov
828e6e3fd0 Merge pull request #320 from link2xt/dc_tools_files
dc_tools: rustify interfaces of file-related functions
2019-08-10 23:10:24 +00:00
Alexander Krotov
ff021fed1f dc_tools: rustify interfaces of file-related functions 2019-08-10 21:15:48 +03:00
Dmitry Bogatov
ed66f36cb5 Implement procedural macro to derive {To,From}Sql traits
With this macro it is possible to #[derive(ToSql, FromSql)] for enums, that do
not contain data (C-style).
2019-08-10 17:52:48 +00:00
jikstra
b7ff996b15 Cargo fmt + refactoring 2019-08-10 17:57:53 +02:00
jikstra
faf53fe11e Manually get a lossy utf8 string from the database if other fails 2019-08-10 17:53:05 +02:00
jikstra
b23c4b4da6 Remove debug printlns, refactor a bit 2019-08-10 16:51:57 +02:00
jikstra
966bb2271a Put something into the msg object if we fail to get a valid string out of the db 2019-08-10 16:47:38 +02:00
Floris Bruynooghe
5438be891b Remove dc_context_unref from Rust API
This removes the dc_context_unref function from the Rust API which was
just an alias for dc_close.  It still exists on the C API where it
makes sure to free the memory.

It also implements Drop for the context which just calls dc_close to
make sure all the memory is freed.  Since you can call dc_close as
many times as you like this ensures that at the Rust level you can't
Drop the struct without releasing the memory.

Finally since memory is now freed by dropping the struct this removes
the #[repr(C)] for the struct.  This struct is fully opaque to the C
API.
2019-08-10 12:04:11 +02:00
Floris Bruynooghe
f31f603c8b Turn dc_ensure_secret_key_existy into something more rusty
This marks the function safe and returns Result, it also now returns
the ConfiguredAddr since it has to look this up anyway and it makes
testing more easy.  Turns out it reduces some duplicate SQL query in
some callers too.

More test code has been moved from dc_imex to test_utils as it's
more genrally applicable.
2019-08-10 11:14:40 +02:00
Dmitry Bogatov
ff39fa0fed Remove useless cast of format! argument
Both `c_int' and `u32' format is same way by default formatter `{}'.
2019-08-10 03:00:43 +02:00
Dmitry Bogatov
cdf3809634 Set type of dc_msg_t.server_folder to Option<String> 2019-08-10 03:00:43 +02:00
Simon Laux
b2a2791f6f cargo fmt 2019-08-09 23:49:54 +02:00
Simon Laux
125df968d2 replace goto with ok_to_continue 2019-08-09 23:49:54 +02:00
Dmitry Bogatov
1c5d07a29f Reimplement dc_str_replace() with standard Rust functions
This change removes one more use of unstable `wrapping_offset_of'.
2019-08-09 23:40:44 +02:00
Dmitry Bogatov
24b025f573 Replace DC_MOVE_* constants with enum 2019-08-09 21:51:33 +02:00
jikstra
d323bd3593 Write tests and docs for dc_strdup and dc_strdup_keep_null 2019-08-09 19:30:36 +02:00
Alexander Krotov
b7174783f1 Pass is_html to Simplify.simplify() as bool 2019-08-09 16:57:14 +02:00
Alexander Krotov
e3269616bd Use bool for Simplify members 2019-08-09 16:57:14 +02:00
Alexander Krotov
64051fca10 Rename dc_simplify_t into Simplify 2019-08-09 16:57:14 +02:00
Alexander Krotov
14ce55b1a8 Remove #[repr(C)] from dc_simplify_t 2019-08-09 16:57:14 +02:00
holger krekel
be605d8ea5 fix(peerstate): encryption-not-available
Add a test for failing e2e encryption and some info statement to hunt where the e2e encryption failure comes from, as well as fix the issue.


Closes #233
2019-08-09 13:28:48 +02:00
Friedel Ziegelmayer
4d8d5f4e1e Fix broken string allocations in message handling (#306)
Fix broken string allocations in message handling
2019-08-09 11:32:42 +02:00
holger krekel
750d6e99a8 fix some longer standing nonsense code that sent to misleading MSG_READ events instead of one correct one 2019-08-09 11:32:24 +02:00
holger krekel
a67892d414 (jikstra, hpk) fix a logic bug introduced with the stock-string merge which set the better message only if it was empty 2019-08-09 11:32:24 +02:00
holger krekel
1cd2a62caf fix a failure which blocked correctly sending out messages (dc_job_add_smtp mis-set filename) 2019-08-09 11:32:24 +02:00
dignifiedquire
6772d6f66c fix: improve some string handling in the message recieve path 2019-08-09 11:32:24 +02:00
Simon Laux
89531dfb62 fix(dc_lot): correct test2 2019-08-09 11:31:11 +02:00
Friedel Ziegelmayer
2414a618e2 Merge pull request #305 from link2xt/keyhistory
Remove empty keyhistory module
2019-08-08 16:20:55 +02:00
Alexander Krotov
7c34806125 Remove empty keyhistory module 2019-08-08 13:32:45 +03:00
Friedel Ziegelmayer
c1f19aa9d3 Merge pull request #292 from link2xt/dc_get_abs_path_safe
Implement dc_get_abs_path_safe
2019-08-08 12:28:19 +02:00
Friedel Ziegelmayer
aab2336223 Merge pull request #304 from link2xt/set_self_key_bool
Return bool from set_self_key()
2019-08-08 12:20:15 +02:00
Friedel Ziegelmayer
7863f5719c Merge pull request #303 from deltachat/fix_chat_list_no_messages
fix own messages displayed as noMsgs in summary
2019-08-08 12:19:31 +02:00
Alexander Krotov
8c933f8fa8 Return bool from set_self_key() 2019-08-08 12:27:01 +03:00
Simon Laux
3d83409375 fix own messages displayed as noMsgs in summary 2019-08-08 08:48:58 +02:00
Alexander Krotov
76dbb37609 Implement dc_get_abs_path_safe 2019-08-08 04:20:12 +03:00
Jikstra
e9ff02bdc5 Merge pull request #296 from deltachat/remove_gotos_dc_e2ee
Remove gotos from dc_e2ee
2019-08-08 00:22:51 +02:00
Jikstra
602d145348 Merge pull request #297 from deltachat/remove_gotos_dc_mimefactory
Remove gotos from dc_mimefactory
2019-08-08 00:15:28 +02:00
Jikstra
ee264117d3 Merge pull request #298 from deltachat/remove_gotos_dc_strencode
Remove gotos from dc_strencode
2019-08-07 23:41:33 +02:00
Jikstra
721dcd7ccd Merge pull request #295 from deltachat/remove_gotos_dc_saxparser
Remove gotos from dc_saxparser
2019-08-07 23:37:50 +02:00
jikstra
cb138fdcb7 Remove comment 2019-08-07 23:17:11 +02:00
jikstra
a993c256e2 Remove comment 2019-08-07 23:15:55 +02:00
Friedel Ziegelmayer
ea6972118a refactor: rusty contact
* refactor(contact): rename and rusty memory allocations
* refactor(contact): use enum to indidcate origin
* refactor(contact): safe blocking and unblocking api
* refactor(contact): only safe and no more cstrings
2019-08-07 22:20:48 +02:00
jikstra
70f6aa9d3a run cargo fmt 2019-08-07 21:21:13 +02:00
jikstra
e1d7871754 Remove gotos in dc_strencode 2019-08-07 21:20:35 +02:00
jikstra
655884b559 run cargo fmt 2019-08-07 21:13:54 +02:00
jikstra
e0dbd47185 Remove gotos from dc_mimefactory 2019-08-07 21:13:20 +02:00
jikstra
925759a922 run cargo fmt 2019-08-07 20:57:58 +02:00
jikstra
9034f316ba Remove gotos from dc_e2ee_encrypt function 2019-08-07 20:57:42 +02:00
jikstra
0276f97d96 run cargo fmt 2019-08-07 20:52:38 +02:00
jikstra
ac2f9fdee5 remove gotos from new_data_part function 2019-08-07 20:52:18 +02:00
jikstra
76b3c532b1 run cargo fmt 2019-08-07 20:40:50 +02:00
Dmitry Bogatov
760332262d Add more assertions to `deltachat-ffi' library
Change code to panic! on invalid input (null pointers, out-of-range
identifiers) instead of silently doing nothing.
2019-08-07 20:36:00 +02:00
Dmitry Bogatov
bc2314586c Avoid unstable `inner_deref' feature 2019-08-07 20:31:02 +02:00
Dmitry Bogatov
765ac2005e Change type of dc_msg_t.text to String
Also, remove `send-garbage' command from REPL, since it is not possible to send
non-utf8 string anymore.
2019-08-07 20:31:02 +02:00
Dmitry Bogatov
d4650ba4a9 Add Rust clone of python `prepare_and_send' test
Previous version of patch, that changed type of `dc_msg_t.text' to String had
been breaking `prepare_and_send' test in Python.

This commit adds same regression test, reimplemented in Rust, since running
Rust tests is simplier and faster.
2019-08-07 20:31:02 +02:00
jikstra
b715df9e97 make ok_to_continue variable mutable 2019-08-07 19:16:21 +02:00
jikstra
acb3d14d7d remove gotos in dc_e2ee in dcrypt_part function 2019-08-07 19:14:35 +02:00
jikstra
c0e3c7a9df run cargo fmt 2019-08-07 19:01:24 +02:00
jikstra
164130a83f Replace gotos with ok_to_continue (is_valid) approach 2019-08-07 19:00:28 +02:00
Dmitry Bogatov
4d9bd46c9d Add note about possible cause of lost messages 2019-08-06 22:09:43 +02:00
jikstra
76d3d86a9e import dc_extract... method for rustdoc example 2019-08-06 21:33:01 +02:00
jikstra
c0b1e41820 Improve function documentation 2019-08-06 21:33:01 +02:00
jikstra
10689f45f4 cargo fmt 2019-08-06 21:33:01 +02:00
jikstra
205e2d4e99 Fix rustdoc 2019-08-06 21:33:01 +02:00
jikstra
5b368960c0 Apply requested changes 2019-08-06 21:33:01 +02:00
jikstra
9ecd0f80dc Fix test and simplify if gate 2019-08-06 21:33:01 +02:00
jikstra
45eec35e6e Make dc_extract_grpid_from_rfc724_mid take/return &str instead of String 2019-08-06 21:33:01 +02:00
jikstra
b74a86f617 Run cargo fmt 2019-08-06 21:33:01 +02:00
jikstra
ff95c44d51 Make receive_imf use safe version of dc_extract_grpid_from_rfc724_mid
- Remove unused unsafe wrapper
- Adjust tests to use safe version
2019-08-06 21:33:01 +02:00
jikstra
d5168916df Refactor dc_extract_grpid_from_rfc724_mid_list and rename test 2019-08-06 21:33:01 +02:00
jikstra
282f964f2f Add tests for dc_extract_grpid_from_rfc724_mid and implement safe
version of dc_extract_grpid_from_rfc724_mid
2019-08-06 21:33:01 +02:00
Simon Laux
7b5073b634 ?1 and ?2 2019-08-06 09:02:41 +02:00
Simon Laux
59bb9add2a fix an type error 2019-08-06 09:02:41 +02:00
Simon Laux
ed95752f8f use DC_CONTACT_ID_SELF constant instead of 1 2019-08-06 09:02:41 +02:00
Simon Laux
81719c4b33 add coment that helps with understanding the code 2019-08-06 09:02:41 +02:00
Simon Laux
fc6019e3c9 fix sql statement in order to close #163 2019-08-06 09:02:41 +02:00
Alexander Krotov
5811248bfc Merge pull request #289 from KAction/new-assert
python: assert that underlying dc_msg_t* for Message is not NULL
2019-08-04 12:58:04 +00:00
Dmitry Bogatov
47b76ceb3e python: assert that underlying dc_msg_t* for Message is not NULL 2019-08-04 09:17:54 +00:00
Floris Bruynooghe
0051720d1b Kill to_cstring with fire
I swear I already did this, see #273.
2019-08-04 09:49:41 +02:00
Jikstra
d814dffbb0 Merge pull request #281 from deltachat/new-mmime
chore: update mmime to 0.1.1
2019-08-03 15:31:36 +02:00
dignifiedquire
4e1e32df65 chore: update mmime to 0.1.1 2019-08-03 11:12:21 +02:00
Floris Bruynooghe
d2b23b727b Restore carriage-return handling to how it was in dcc
Some part of the translation seems to have got this wrong somewhere.
Add a test and restore handling to something more sane again.
2019-08-03 01:00:59 +02:00
Floris Bruynooghe
d37dda6f50 Turn the function safe 2019-08-03 01:00:59 +02:00
Floris Bruynooghe
c568d5dcac Safe string usage
Mostly safe string usage, but some other minor cleanups:

- This isn't used from any C code, so no extern C.
- Use Config enums rather than strings.
- Clean up old unused sql statements.
2019-08-03 01:00:59 +02:00
Floris Bruynooghe
227ef1b467 Add unittest
This should give us some confidence in the refactoring.
2019-08-03 01:00:59 +02:00
dignifiedquire
a8cea64f39 feat(deps): update rusqlite to official version again
A fix for the non utf8 strings has been merged and released in `0.20`
2019-08-03 00:11:23 +02:00
Friedel Ziegelmayer
294e855bbe Merge pull request #276 from link2xt/lines
Return Vec instead of carray from dc_split_into_lines
2019-08-02 20:28:23 +02:00
Alexander Krotov
c5eef21645 Remove carray use and comment 2019-08-02 13:12:32 +03:00
Alexander Krotov
d64fcece5b Return Vec instead of carray from dc_split_into_lines 2019-08-02 13:12:25 +03:00
Friedel Ziegelmayer
0a9f3ae160 Use Vec instead of carray in dc_forward_msgs() (#277)
Use Vec instead of carray in dc_forward_msgs()
2019-08-02 11:05:28 +02:00
Friedel Ziegelmayer
acbedbd352 Remove unused function dc_simplify_simplify (#278)
Remove unused function dc_simplify_simplify
2019-08-02 11:05:05 +02:00
Alexander Krotov
e78b879c9e Remove unused function dc_simplify_simplify 2019-08-02 09:29:54 +03:00
Alexander Krotov
1145c3533b Use Vec instead of carray in dc_forward_msgs() 2019-08-02 01:34:15 +03:00
Friedel Ziegelmayer
0d41a78182 Merge pull request #272 from link2xt/parts_vec
Store dc_mimeparser_t::parts as Vec instead of carray
2019-08-01 23:50:53 +02:00
Alexander Krotov
a4f94dbf86 Store dc_mimeparser_t::parts as Vec instead of carray 2019-08-01 23:21:56 +03:00
Floris Bruynooghe
b6b0849bce Remove to_cstring() naming convention ambiguity
Add a trait for str.strdup() to replace to_cstring() which avoid the
signature ambiguity with .to_string().

Also instruduce CString::yolo() as a shortcut to
CString::new().unwrap() and use it whenever the variable does can be
deallocated by going out of scope.  This is less error prone.

Use some Path.to_c_string() functions where possible.
2019-08-01 19:06:39 +02:00
Friedel Ziegelmayer
e7428887d0 Introduce new type: Viewtype (#256)
Introduce new type: Viewtype
2019-08-01 12:17:33 +02:00
Friedel Ziegelmayer
a7269e7096 Merge pull request #274 from KAction/refine-bool
Narrow types of struct fields, that use only two values.
2019-08-01 11:22:55 +02:00
Dmitry Bogatov
7394666266 Refine type of SmtpState.doing_jobs to bool 2019-08-01 06:51:52 +00:00
Dmitry Bogatov
8eb5cec9ce Refine type of SmtpState.suspended to bool 2019-08-01 06:41:50 +00:00
Dmitry Bogatov
b2807429cc Refine type of SmtpState.probe_network to bool 2019-08-01 06:37:06 +00:00
Dmitry Bogatov
07d7316a9f Refine type of Context.perform_inbox_jobs_needed to bool 2019-08-01 06:31:58 +00:00
Dmitry Bogatov
1f9807ccfe Refine type of Context.probe_network to bool 2019-08-01 06:24:50 +00:00
Dmitry Bogatov
3358d09148 Derive and use Display trait for Viewtype 2019-08-01 06:05:56 +00:00
Dmitry Bogatov
c04c8ff103 Introduce new enum: Viewtype
With this change, kind of message is represented by value of enum
`Viewtype' instead of raw libc::c_int, providing more type safety. This
enum replaces DC_MSG_* constants. The only way to create `Viewtype' from
libc::c_int is smart constructor.

With this change, functions `dc_get_chat_media' and `dc_get_next_media' became
less forgiving about invalid message type arguments. Previously, invalid
message types were implicitly interpreted as 0 (Viewtype::Unknown). Now,
function calls with invalid message type arguments are rejected (error code
returned) on FFI boundary.

Additionally, when `Viewtype' is read from database, it is checked to have
sensible value.

No tests assumed forgiving behaviour.
2019-08-01 06:05:56 +00:00
Friedel Ziegelmayer
e7456248a0 Merge pull request #244 from link2xt/dc_location_vec
Replace some dc_array_t with Vec<dc_location>
2019-07-31 22:15:35 +02:00
Friedel Ziegelmayer
3370b9cfcb Merge pull request #268 from KAction/string-os-name
Change Context.os_name field to String
2019-07-31 22:13:18 +02:00
Friedel Ziegelmayer
022c7c2f07 Merge pull request #266 from KAction/dc_msg_is_starred_bool
Make dc_msg_is_starred() return bool
2019-07-31 22:06:48 +02:00
Alexander Krotov
9e50e77031 Simplify dc_initiate_key_transfer() 2019-07-31 22:06:05 +02:00
Friedel Ziegelmayer
39e9cfd908 Merge pull request #269 from link2xt/dc_array_sort_ids
Make dc_array_sort_ids() safe and move it into impl
2019-07-31 22:04:17 +02:00
Friedel Ziegelmayer
c29c893426 Merge pull request #270 from link2xt/get_backoff_time_offset-safe
Mark get_backoff_time_offset as safe
2019-07-31 22:03:29 +02:00
Friedel Ziegelmayer
16028cc5dc Merge pull request #271 from link2xt/reports_vec
Replace dc_mimeparser_t::reports carray with Vec
2019-07-31 22:02:35 +02:00
Dmitry Bogatov
39e530f759 Change Context.os_name field to String 2019-07-31 15:45:35 +00:00
jikstra
8274c6bf17 Allow non_snake_case in dc_replace_bad_utf8_chars function 2019-07-31 16:26:17 +02:00
jikstra
566d255b33 run cargo fmt 2019-07-31 16:26:17 +02:00
jikstra
9fb9fb0fc1 Remove goto/current_block logic with OK_TO_CONTINUE. Keeps identation 2019-07-31 16:26:17 +02:00
Alexander Krotov
164a8fe39a encode_66bits_as_base64: use base64 crate 2019-07-31 16:25:01 +02:00
Alexander Krotov
0b679f8b95 dc_tools: Make dc_create_id() safe 2019-07-31 16:25:01 +02:00
Alexander Krotov
8267ffb8d2 Replace dc_mimeparser_t::reports carray with Vec 2019-07-31 15:57:05 +03:00
Alexander Krotov
7bc338fc72 Mark get_backoff_time_offset as safe 2019-07-31 15:51:50 +03:00
Alexander Krotov
d6dae0a9e8 Make dc_array_sort_ids() safe and move it into impl 2019-07-31 03:21:13 +03:00
Dmitry Bogatov
a41c0614cc Make dc_msg_is_starred() return bool 2019-07-30 22:57:28 +00:00
Dmitry Bogatov
73298c0273 chore: fix and enforce compiler warnings on CI 2019-07-30 09:58:28 +02:00
Friedel Ziegelmayer
b44c7928f2 Merge pull request #265 from link2xt/safe_cb_set_config
Make cb_set_config() safe
2019-07-30 09:53:56 +02:00
Friedel Ziegelmayer
dd9b83dc24 Merge pull request #264 from link2xt/key-to_asc-safe
Make key::to_asc return String
2019-07-30 09:53:42 +02:00
Friedel Ziegelmayer
e79c168279 Merge pull request #263 from link2xt/sql-is_file_in_use-safe
sql.rs: make is_file_in_use() safe
2019-07-30 09:53:13 +02:00
Alexander Krotov
707c8c2830 Make dc_split_armored_data return bool (#251)
* Make dc_split_armored_data return bool

* Remove double negations
2019-07-30 08:46:36 +02:00
Alexander Krotov
f87c98d6ea Make cb_set_config() safe 2019-07-30 02:43:50 +03:00
Alexander Krotov
76e76470e0 Replace range loop with foreach loop 2019-07-30 02:34:05 +03:00
Alexander Krotov
ae6c41a019 dc_kml_t: replace Option<dc_array_t> with Option<Vec<dc_location>> 2019-07-30 02:34:05 +03:00
Alexander Krotov
81a84620eb Store dc_kml_t::locations as Option<dc_array_t> instead of pointer 2019-07-30 02:34:05 +03:00
Alexander Krotov
14e42b48bd dc_get_locations: use from(Vec<dc_location>) instead of add_location 2019-07-30 02:34:05 +03:00
Alexander Krotov
2688a397aa Implement From<Vec<dc_location>> for dc_array_t 2019-07-30 02:32:24 +03:00
Alexander Krotov
3ace4fcc2f Make key::to_asc return String 2019-07-30 02:22:08 +03:00
Friedel Ziegelmayer
9314a5a8fd Merge pull request #252 from link2xt/dc_get_config-safe
Make dc_get_config_t safe
2019-07-30 00:25:29 +02:00
Friedel Ziegelmayer
5bf2d7c5ac Merge pull request #253 from link2xt/is_file_size_okay-bool
Return bool from is_file_size_okay
2019-07-30 00:24:13 +02:00
Friedel Ziegelmayer
1fbd16310a Merge pull request #262 from link2xt/thread-constants
dc_job.rs: add DC_{IMAP,SMTP}_THREAD constants
2019-07-30 00:23:34 +02:00
Friedel Ziegelmayer
7ba1a6f79f Merge pull request #255 from link2xt/dc_open-safer
Make dc_open arguments rusty
2019-07-30 00:21:03 +02:00
Friedel Ziegelmayer
2c66837df4 Merge pull request #261 from link2xt/dc_timestamp_to_str
Remove unsafe version of dc_timestamp_to_str
2019-07-30 00:19:33 +02:00
Alexander Krotov
21b4147d15 sql.rs: make is_file_in_use() safe 2019-07-30 01:15:51 +03:00
KAction
0c082fac7b refactor: remove unused import of qsort() from C library ( 2019-07-30 00:10:22 +02:00
Alexander Krotov
03603a48a0 dc_job.rs: add DC_{IMAP,SMTP}_THREAD constants 2019-07-29 20:20:52 +03:00
Alexander Krotov
27342f50b5 Remove unsafe version of dc_timestamp_to_str 2019-07-29 18:45:12 +03:00
Alexander Krotov
2d5b04148f Make dc_open arguments rusty 2019-07-29 04:05:21 +03:00
Alexander Krotov
dfce34f275 Return bool from is_file_size_okay 2019-07-29 03:14:21 +03:00
Friedel Ziegelmayer
188da2a020 refactor(params): rustify 2019-07-29 01:49:53 +02:00
Alexander Krotov
3f445a3a6c Make dc_get_config_t safe 2019-07-28 23:57:28 +03:00
Dmitry Bogatov
669ed0e0df Override non_camel_case_types warning on per-declaration basis 2019-07-28 22:44:21 +02:00
Dmitry Bogatov
c6ccfd824e Override `non_shake_case' warning on per-function basis
This change removes global override of `non_shake_case' warning and replaces it
with per-function overrrides. This way, compiler will complain about style
guide violation in new code.

It should be noted, that `rustc' is not smart enough to emit warning when
override is no longer needed, it must be checked manually.
2019-07-28 22:44:21 +02:00
Dmitry Bogatov
39cc93240f Fix 'non_upper_case_globals' compiler warnings 2019-07-28 22:44:21 +02:00
Alexander Krotov
6c818c6123 Make dc_tools::dc_exactly_one_bit_set safe and return bool 2019-07-28 22:40:30 +02:00
Alexander Krotov
3eaab07b0b top_evil_rs.py: remove comments before counting
For example, constants.rs is completely safe now, but has "free()" in the comments.
2019-07-28 22:39:39 +02:00
Alexander
0cffbaf1e9 refactor: rename dc_array_t::as_ptr() into dc_array_t::into_raw()
By convention as_* functions do not consume self by taking the reference.
2019-07-28 20:31:57 +02:00
Friedel Ziegelmayer
c34e66adb6 Make dc_create_setup_code() safe (#239)
Make dc_create_setup_code() safe
2019-07-28 19:50:25 +02:00
Alexander
0132106c7e fix(imex): dc_decrypt_setup_file: extend lifetime of CString
payload_c binding makes sure the memory is not freed until strdup exits.

See as_ptr documentation:
https://doc.rust-lang.org/std/ffi/struct.CString.html#method.as_ptr
2019-07-28 19:47:52 +02:00
Alexander
5a8b4748b8 fix(msg): correct return value for dc_msg_is_setupmessage
It is broken since 8a0fc609e6
where 0i32 was replaced with true instead of false.
2019-07-28 18:33:31 +02:00
Alexander Krotov
3033d8100d Test dc_create_setup_code() without to_cstring() 2019-07-28 15:05:28 +03:00
Alexander Krotov
0e6b12d7ae Move to_string out of dc_create_setup_code 2019-07-28 15:05:28 +03:00
Friedel Ziegelmayer
2407604e37 Merge pull request #211 from link2xt/dc_array-cleanup
dc_array_t cleanup
2019-07-28 11:51:08 +02:00
Friedel Ziegelmayer
b23ca26908 refactor(chatlist): rustify 2019-07-28 11:32:48 +02:00
Alexander Krotov
21d94b1d09 Remove misplaced comment 2019-07-27 19:28:39 +03:00
Alexander Krotov
ae1cbc9596 dc_array: store locations as Vec<dc_location> 2019-07-27 19:28:39 +03:00
Alexander Krotov
86d047f618 dc_chat: use get_id instead of accessing array fields directly 2019-07-27 19:28:39 +03:00
Alexander Krotov
1cd7cb541c Rewrite most location array member accessors 2019-07-27 19:28:39 +03:00
Alexander Krotov
f27dda86ff Move dc_array_search_id into dc_array_t implementation 2019-07-27 19:28:39 +03:00
Alexander Krotov
51319f89e8 Create dc_array_t in dc_get_locations without unsafe 2019-07-27 19:28:39 +03:00
Alexander Krotov
8b4acbb63a Move dc_array_get_ptr inside dc_array_t implementation 2019-07-27 19:28:39 +03:00
Alexander Krotov
928361429e Move dc_array_get_{uint,id} inside dc_array_t implementation 2019-07-27 19:28:39 +03:00
Alexander Krotov
c17632188a Avoid using return in dc_array_get_cnt implementation 2019-07-27 19:28:39 +03:00
Alexander Krotov
ea3c89e913 Move dc_array_unref logic inside dc_array_t implementation
This will allow to make dc_array_t members private in the future.
2019-07-27 19:28:39 +03:00
Alexander Krotov
ea84edf13a Implement dc_array_t::len() 2019-07-27 19:28:39 +03:00
Alexander Krotov
c335348f20 Implement dc_array_t::is_empty() 2019-07-27 19:28:39 +03:00
Alexander Krotov
1e91f6a204 Merge dc_array_free_ptr into dc_array_unref 2019-07-27 19:28:39 +03:00
Alexander Krotov
dfd43cbb97 Rename dc_array_new_typed into dc_array_new_locations
dc_array_new_typed is only used internally, so we can change its API.
2019-07-27 19:28:39 +03:00
Alexander Krotov
c7a6b3caae Remove unnecessary check in dc_array_new_typed
Allocating Vec with 0 capacity is correct.
2019-07-27 19:28:39 +03:00
Alexander Krotov
f3eea41914 Reimplement dc_array_new without dc_array_new_typed 2019-07-27 19:28:39 +03:00
Alexander Krotov
8d43ad4809 Construct dc_array_t id arrays using safe methods 2019-07-27 19:28:39 +03:00
Alexander Krotov
1f63753a8b Simplify dc_array_search_id 2019-07-27 19:28:39 +03:00
Alexander Krotov
e796a4c438 Move dc_array_add_{uint,id} implementations into dc_array_t 2019-07-27 19:28:39 +03:00
Alexander Krotov
85dfd65e48 Simplify dc_array_get_string 2019-07-27 19:28:39 +03:00
Alexander Krotov
a323fe68a6 Simplify dc_array_duplicate 2019-07-27 19:28:39 +03:00
Alexander Krotov
05aca2c529 Make dc_array_new and dc_array_new_typed safe
Just like Box::into_raw, these functions are safe,
even though the caller is responsible for the allocated structure.
2019-07-27 19:16:42 +03:00
Alexander Krotov
1dfad65afd dc_array.rs: remove magic field
It was always set to DC_ARRAY_MAGIC, except immediately before freeing the memory.
2019-07-27 19:16:42 +03:00
Alexander Krotov
e15e3a1e84 Use Vec to store dc_array_t data 2019-07-27 18:25:24 +03:00
Alexander Krotov
252697b174 Implement dc_array_t::new() and use Box to allocate dc_array_t 2019-07-27 18:25:24 +03:00
Alexander Krotov
7764ab3ff3 Replace C loop with Rust loop in dc_array_search_id 2019-07-27 18:25:24 +03:00
Alexander Krotov
7585dc49e3 Replace C loop with Rust loop in dc_array_free_ptr 2019-07-27 18:25:24 +03:00
Alexander Krotov
f0ae5fcd7c Add DC_ARRAY_LOCATIONS constant 2019-07-27 18:25:24 +03:00
Alexander Krotov
7cba2b3f66 Remove unused dc_array_sort_strings 2019-07-27 18:25:23 +03:00
Alexander Krotov
a0594338b2 Remove repr(C) from dc_array_t
All members of dc_array_t structure are private, C code does not need to interact with them.
2019-07-27 17:14:09 +03:00
Floris Bruynooghe
4902310138 Make stock strings rusty
This converts the stock strings API to be more safe-rust style.  The
API is kept roughly the same for now but moved to methods on the
context.
2019-07-27 12:37:22 +02:00
Friedel Ziegelmayer
44b8629811 Merge pull request #232 from deltachat/fix_markseen
(jikstra, hpk) fix markseen logic to work like C
2019-07-27 12:31:59 +02:00
Friedel Ziegelmayer
30668aaecf Merge pull request #237 from KAction/repl__do_not_treat_empty_string_as_command_
repl: do not treat empty string as command
2019-07-27 12:31:30 +02:00
Friedel Ziegelmayer
6dfc019163 Merge pull request #236 from KAction/Make_repl_program_more_robust_
Make repl program more robust
2019-07-27 12:31:02 +02:00
Friedel Ziegelmayer
b584b7eb58 Merge pull request #234 from KAction/send-garbage
Send garbage
2019-07-27 12:30:35 +02:00
Dmitry Bogatov
36b5f4da53 Check if input to dc_send_text_msg is valid utf8
With this change, passing invalid utf8 string to `dc_send_text_msg' does not
crash application, it prints warning and returns error code.

It should be admitted that this fix is sub-optimal: if input C string is valid
utf8 (which is likely), result of successful conversion to `&str' is discarded
in `dc_send_text_msg', and the same input C string is converted again with
`as_str' in `prepare_msg_raw'.

It is not clear how to fix it in non-disruptive way, since input C string is
passed down to call stack as part of `dc_msg_t' struct, which is part of C ABI.
2019-07-27 09:04:20 +00:00
Dmitry Bogatov
d1968d8ccb New repl command: send-garbage
This new command attempts to send malformed utf8 string to current chat with
dc_send_text_msg() function. Currently, instead of error code it causes
application crash with following backtrace:

thread 'main' panicked at 'Non utf8 string: '[255]' (Utf8Error { valid_up_to: 0, error_len: Some(1) })', src/dc_t
ools.rs:1571:9
stack backtrace:
   0: backtrace::backtrace::libunwind::trace
             at /cargo/registry/src/github.com-1ecc6299db9ec823/backtrace-0.3.29/src/backtrace/libunwind.rs:88
   1: backtrace::backtrace::trace_unsynchronized
             at /cargo/registry/src/github.com-1ecc6299db9ec823/backtrace-0.3.29/src/backtrace/mod.rs:66
   2: std::sys_common::backtrace::_print
             at src/libstd/sys_common/backtrace.rs:47
   3: std::sys_common::backtrace::print
             at src/libstd/sys_common/backtrace.rs:36
   4: std::panicking::default_hook::{{closure}}
             at src/libstd/panicking.rs:200
   5: std::panicking::default_hook
             at src/libstd/panicking.rs:214
   6: std::panicking::rust_panic_with_hook
             at src/libstd/panicking.rs:477
   7: std::panicking::continue_panic_fmt
             at src/libstd/panicking.rs:384
   8: std::panicking::begin_panic_fmt
             at src/libstd/panicking.rs:339
   9: deltachat::dc_tools::as_str::{{closure}}
             at src/dc_tools.rs:1571
  10: core::result::Result<T,E>::unwrap_or_else
             at /rustc/0b680cfce544ff9a59d720020e397c4bf3346650/src/libcore/result.rs:818
  11: deltachat::dc_tools::as_str
             at src/dc_tools.rs:1570
  12: deltachat::dc_chat::prepare_msg_raw
             at src/dc_chat.rs:726
  13: deltachat::dc_chat::prepare_msg_common
             at src/dc_chat.rs:461
  14: deltachat::dc_chat::dc_send_msg
             at src/dc_chat.rs:905
  15: deltachat::dc_chat::dc_send_text_msg
             at src/dc_chat.rs:981
  16: repl::cmdline::dc_cmdline
             at examples/repl/cmdline.rs:955
  17: repl::handle_cmd
             at examples/repl/main.rs:566
  18: repl::main_0
             at examples/repl/main.rs:445
  19: repl::main
             at examples/repl/main.rs:578
  20: std::rt::lang_start::{{closure}}
             at /rustc/0b680cfce544ff9a59d720020e397c4bf3346650/src/libstd/rt.rs:64
  21: std::rt::lang_start_internal::{{closure}}
             at src/libstd/rt.rs:49
  22: std::panicking::try::do_call
             at src/libstd/panicking.rs:296
  23: __rust_maybe_catch_panic
             at src/libpanic_unwind/lib.rs:82
  24: std::panicking::try
             at src/libstd/panicking.rs:275
  25: std::panic::catch_unwind
             at src/libstd/panic.rs:394
  26: std::rt::lang_start_internal
             at src/libstd/rt.rs:48
  27: std::rt::lang_start
             at /rustc/0b680cfce544ff9a59d720020e397c4bf3346650/src/libstd/rt.rs:64
  28: main
  29: __libc_start_main
  30: _start
2019-07-27 08:16:18 +00:00
Dmitry Bogatov
a9c714748d Make repl program more robust
This change replaces calls to unwrap() function with "?" operator, propagating
error up the call stack. This way "chat foo" (for example) command results in

      Error: invalid digit found in string

message instead of crashing whole repl.
2019-07-27 07:43:45 +00:00
Dmitry Bogatov
7a07629d68 repl: do not treat empty string as command
Previously, just pressing "<Enter>" at prompt resulted in following error
message:

	Error: Unknown command: "" type ? for help.

This message is harmless, but useless.  Also, such behaviour is different from
many (if not all) interactive environments: python, bash, dash, ...

With this change empty input string is silently ignored.
2019-07-27 07:43:44 +00:00
holger krekel
67d9515033 (jikstra, hpk) fix markseen logic to work like C: ignore if we get no rows for a message (eg desktop calls itwith msg_id=9 which is a special id -- and C just ignored it) 2019-07-26 09:02:11 +02:00
holger krekel
f63e79cd6d remove MessageType, remove account.get_blob_dir which is duplicate of get_blobdir() 2019-07-26 08:57:31 +02:00
holger krekel
83346722fd - simplify and clarify dc_msg caching for Message object
- merge state class into Message object proper -- one less intermediate object to worry about for callers
2019-07-26 08:57:31 +02:00
holger krekel
9836e73683 - properly support prepare-msg API and implement get_message_info()
- remove usage of "attr.s" across the code
- make msg.set_file() copy a file into blobdir if it isn't already
- regroup tests
- add set_draft/get_draft API
2019-07-26 08:57:31 +02:00
Alexander Krotov
c7ebf6de09 cargo fmt 2019-07-25 23:32:17 +02:00
Alexander Krotov
2f204fd2aa Simplify dc_simplify_t implementation
- Replace dc_simplify_new and dc_simplify_unref with ::new()
- Move dc_simplify_simplify and dc_simplify_simplify_plain_text into impl
2019-07-25 23:32:17 +02:00
holger krekel
63ed5c4009 re-enable devpi uploads 2019-07-25 22:46:27 +02:00
Alexander Krotov
9f75a5049e dc_location: store marker as Option<String> instead of C string 2019-07-25 22:41:33 +02:00
Alexander Krotov
ec6cc5c355 Allocate dc_kml_t in a rusty way 2019-07-25 22:41:33 +02:00
Alexander Krotov
b0ef825e67 Implement dc_location::new() and dc_kml_t::new() 2019-07-25 22:41:33 +02:00
Alexander Krotov
a791f76e4b Rename _dc_location into dc_location 2019-07-25 22:41:33 +02:00
holger krekel
2cf227571a pure cargo fmt 2019-07-25 21:51:39 +02:00
holger krekel
9a9b49f8f0 - remove current_block logic from dc_chat.rs with the "OK_TO_CONTINUE"
pattern -- re-indentation will come after this commit with a pure application of "cargo fmt"
- bring back comment from C code
- make some path helpers return bool
2019-07-25 21:51:39 +02:00
holger krekel
9d87f2f10b carefully replace msg state and type numbers with DC_MSG_* and DC_STATE_* constants and also declare them as i32 to avoid tons of casts 2019-07-25 09:45:04 +02:00
holger krekel
5de7c35622 disable upload to devpi as it's currently down and unncessarily braks PR testing 2019-07-24 14:10:29 +02:00
Dmitry Bogatov
004cdf6491 Improve punctuation of message, printed by "repl" program
This patch changes output of following command (from README.md)

	$ cargo run --example repl -- /tmp/main.db

from

	First time init: creating tables in ""/tmp/main.db""
	[...]
	Opened ""/tmp/main.db"".

to

	First time init: creating tables in "/tmp/main.db"
	[...]
	Opened "/tmp/main.db".

Note lack of double quotation mark, which was confusing and could have been
interpreted as part of file name.
2019-07-24 11:36:51 +02:00
Dmitry Bogatov
72ad8b5199 Improve error handling in dc_send_text_msg()
Previously, dc_send_text_msg() silently returned 0 in case of incorrect
input. This way "send" command in repl reported "Sending failed" without
any clue what exactly went wrong.
2019-07-24 09:52:17 +02:00
Alexander Krotov
cb75ac3842 Remove dc_location_t
dc_location_t is an incomplete copy of _dc_location

The difference is that it lacks `int independent` field.
As a result, calloc did not allocate memory for this field.
deltachat-core (C version) has only one dc_location_t, that includes the last field.
2019-07-24 09:37:04 +02:00
Alexander Krotov
a5553f98af dc_location.rs: rewrite is_marker in safe Rust 2019-07-24 09:16:44 +02:00
Alexander Krotov
648d3d78aa Remove dc_arr_to_string function that was used only once 2019-07-23 10:09:52 +02:00
holger krekel
afcf48f833 add test, fix and high level python api for dc_delete_contact
the rust-logic was inverted -- you can not delete a contact that still has messages with it.
2019-07-23 09:37:21 +02:00
holger krekel
6f79800824 fix last two warnings 2019-07-22 11:45:41 +02:00
holger krekel
7a19963879 properly fix the QueryReturnedNoRows warning and rustfmt 2019-07-22 11:18:30 +02:00
holger krekel
cd7630360f fix fmt 2019-07-22 02:33:08 +02:00
holger krekel
4a633169e1 Merge branch 'master' into flub-nowarn 2019-07-22 01:40:56 +02:00
Floris Bruynooghe
ea8d6e8ff0 Write a deltachat.pc file at build time
This is writes pkg-config/deltachat.pc file in the target directory,
using the PREFIX environment variable at build time.  If this is
undefined at build time /usr/local is used.
2019-07-22 01:16:34 +02:00
holger krekel
065124b93b Merge branch 'master' into flub-param-names 2019-07-22 01:11:47 +02:00
holger krekel
86d290832b Merge branch 'master' into flub-py-glue-fixes 2019-07-22 01:09:58 +02:00
Alexander Krotov
56f8717a40 Show AutocryptSetupMessage independently of show-emails settings
Fixes #161
2019-07-22 00:45:17 +02:00
Alexander Krotov
4a0b2e68c8 Add DC_CMD_* constants 2019-07-22 00:45:17 +02:00
holger krekel
2576b78126 Merge branch 'master' into flub-py-glue-fixes 2019-07-22 00:39:47 +02:00
holger krekel
6a956b6008 Merge branch 'master' into flub-param-names 2019-07-22 00:36:52 +02:00
Alexander Krotov
33575e7aa3 dc_get_abs_path cleanup 2019-07-21 23:40:29 +02:00
holger krekel
8089559958 Squashed commit of the following:
commit 6bc5d1b90e
Author: holger krekel <holger@merlinux.eu>
Date:   Sun Jul 21 22:56:37 2019 +0200

    fix fmt

commit 197d94ad9d
Merge: 7ce337c 686678c
Author: holger krekel <holger@merlinux.eu>
Date:   Sun Jul 21 22:51:16 2019 +0200

    Merge remote-tracking branch 'origin/master' into eventlogging

commit 7ce337c6d0
Author: holger krekel <holger@merlinux.eu>
Date:   Sun Jul 21 22:44:27 2019 +0200

    left-over error logging

commit 10148d2e43
Author: holger krekel <holger@merlinux.eu>
Date:   Sun Jul 21 22:03:17 2019 +0200

    ignore non-utf8 parts of header fields (add comment why it shouldn't happen)
    don't throw error if no sql rows are returned

commit 69dc237ee3
Author: dignifiedquire <dignifiedquire@users.noreply.github.com>
Date:   Sun Jul 21 12:56:04 2019 +0200

    fix(receive_imf): remove recursive sql call

commit df5464ea80
Author: dignifiedquire <dignifiedquire@users.noreply.github.com>
Date:   Sat Jul 20 17:05:24 2019 +0200

    fix: blocked is an optional value

commit e4bf9956a5
Author: dignifiedquire <dignifiedquire@users.noreply.github.com>
Date:   Sat Jul 20 16:50:56 2019 +0200

    fix(msg): handle optional in_reply_to

commit d353d9d9d8
Author: dignifiedquire <dignifiedquire@users.noreply.github.com>
Date:   Sat Jul 20 16:17:25 2019 +0200

    fix(chat): remove recursive sql usage

commit 1ad45ed4d6
Author: holger krekel <holger@merlinux.eu>
Date:   Sat Jul 20 15:14:11 2019 +0200

    fix rust fmt

commit 496e980a17
Author: dignifiedquire <dignifiedquire@users.noreply.github.com>
Date:   Sat Jul 20 14:34:20 2019 +0200

    use forked rusqlite

commit fa09e46ed9
Author: holger krekel <holger@merlinux.eu>
Date:   Sat Jul 20 12:37:51 2019 +0200

    another pace where we might (and in my case did) get invalid utf8

commit d6de420b9a
Author: holger krekel <holger@merlinux.eu>
Date:   Sat Jul 20 12:30:48 2019 +0200

    fix some string issues, introduce to_string_lossy such that to_string() continues to panic on non-utf8

commit 38eb708db8
Author: holger krekel <holger@merlinux.eu>
Date:   Sat Jul 20 01:17:53 2019 +0200

    for now make to_string() less strict as we often don't want to crash the whole app just because some non-proper utf8 came in (through a message we can't neccesarily congtrol)

commit 7a59da5f8f
Author: holger krekel <holger@merlinux.eu>
Date:   Fri Jul 19 22:48:39 2019 +0200

    fix linting

commit f13a1d4a2f
Author: holger krekel <holger@merlinux.eu>
Date:   Fri Jul 19 22:46:58 2019 +0200

    fix some test flakyness

commit 7b3a450918
Author: holger krekel <holger@merlinux.eu>
Date:   Fri Jul 19 22:35:07 2019 +0200

    - fix saved_mime test which broke to improper conversion of
      imf_raw_not_terminated
    - some cargo.toml updates no clue where they come from
    - log Message-ID for received messages

commit 169923b102
Author: holger krekel <holger@merlinux.eu>
Date:   Fri Jul 19 12:31:22 2019 +0200

    formatting

commit 42688a0622
Author: holger krekel <holger@merlinux.eu>
Date:   Fri Jul 19 12:24:56 2019 +0200

    remove some print statements

commit 35f3c0edd1
Merge: e7a2362 f58b1d6
Author: holger krekel <holger@merlinux.eu>
Date:   Fri Jul 19 10:25:21 2019 +0200

    Merge branch 'master' into eventlogging

commit e7a236264a
Author: dignifiedquire <dignifiedquire@users.noreply.github.com>
Date:   Thu Jul 18 23:20:20 2019 +0200

    print invalid strings

commit aaa5b820d9
Author: dignifiedquire <dignifiedquire@users.noreply.github.com>
Date:   Thu Jul 18 23:12:35 2019 +0200

    cleanup

commit e7f0745010
Author: dignifiedquire <dignifiedquire@users.noreply.github.com>
Date:   Thu Jul 18 23:03:57 2019 +0200

    reduce direc usage of CString

commit c68e7ae14e
Author: dignifiedquire <dignifiedquire@users.noreply.github.com>
Date:   Thu Jul 18 22:47:47 2019 +0200

    audit use of to_cstring and fix ub

commit 618087e5a7
Author: dignifiedquire <dignifiedquire@users.noreply.github.com>
Date:   Thu Jul 18 21:38:52 2019 +0200

    fix(imap): body ptr lifetime

commit 245abb8384
Author: dignifiedquire <dignifiedquire@users.noreply.github.com>
Date:   Thu Jul 18 19:44:10 2019 +0200

    remove debug

commit a3e1042001
Author: dignifiedquire <dignifiedquire@users.noreply.github.com>
Date:   Thu Jul 18 18:30:54 2019 +0200

    fix some things, add more debugging statements

commit 7b7ce9348f
Author: holger krekel <holger@merlinux.eu>
Date:   Thu Jul 18 15:11:57 2019 +0200

    fix python lint issues

commit 7a4808ba0d
Author: holger krekel <holger@merlinux.eu>
Date:   Thu Jul 18 14:35:54 2019 +0200

    cargofmt

commit 8f240f7153
Author: holger krekel <holger@merlinux.eu>
Date:   Thu Jul 18 14:03:57 2019 +0200

    (dig,hpk) pull out job collection from sql query/lock logic

commit 7d0b5d8abb
Author: holger krekel <holger@merlinux.eu>
Date:   Thu Jul 18 12:52:02 2019 +0200

    remove print statements and fix a crash

commit ee317cb1b5
Author: holger krekel <holger@merlinux.eu>
Date:   Thu Jul 18 11:38:10 2019 +0200

    fix some merge issues

commit 7b736fe635
Author: holger krekel <holger@merlinux.eu>
Date:   Thu Jul 18 11:16:38 2019 +0200

    (dig,hpk) add test and fix for wrong dbs

commit c7db15352a
Merge: 0b37167 0c5015d
Author: holger krekel <holger@merlinux.eu>
Date:   Thu Jul 18 09:59:44 2019 +0200

    Merge branch 'master' into eventlogging

commit 0b37167be8
Author: holger krekel <holger@merlinux.eu>
Date:   Thu Jul 18 00:06:05 2019 +0200

    address @dignifiedquire comments

commit 5cac4b5076
Author: holger krekel <holger@merlinux.eu>
Date:   Wed Jul 17 12:47:22 2019 +0200

    remove spurious print

commit 475a41beb3
Author: holger krekel <holger@merlinux.eu>
Date:   Wed Jul 17 12:31:12 2019 +0200

    address @dignifiedquire rustyness comment and fix changelog

commit ad4be80b4e
Author: holger krekel <holger@merlinux.eu>
Date:   Wed Jul 17 10:25:25 2019 +0200

    make smtp/imap connect() return bool instead of c-int

commit 8737c1d142
Author: holger krekel <holger@merlinux.eu>
Date:   Wed Jul 17 09:26:33 2019 +0200

    cleanup some parts, add comments

commit 964fe466cc
Author: holger krekel <holger@merlinux.eu>
Date:   Tue Jul 16 20:05:41 2019 +0200

    wip-commit which passes all tests with proper finalization

commit 43936e7db7
Author: holger krekel <holger@merlinux.eu>
Date:   Tue Jul 16 16:17:42 2019 +0200

    snapshot of my current debugging state

commit 0e80ce9c39
Author: holger krekel <holger@merlinux.eu>
Date:   Tue Jul 16 12:57:19 2019 +0200

    more aggressively skip perform API when threads are closing

commit c652bae68a
Author: holger krekel <holger@merlinux.eu>
Date:   Tue Jul 16 12:06:05 2019 +0200

    intermediate wip commit

commit bc904a495d
Author: holger krekel <holger@merlinux.eu>
Date:   Tue Jul 16 11:18:56 2019 +0200

    add some logging, and a more precise teardown for online python tests

commit 8d99444c6a
Author: holger krekel <holger@merlinux.eu>
Date:   Tue Jul 16 00:22:12 2019 +0200

    fix std

commit 9dab53e0af
Author: holger krekel <holger@merlinux.eu>
Date:   Tue Jul 16 00:20:54 2019 +0200

    rustfmt

commit 360089ac74
Author: holger krekel <holger@merlinux.eu>
Date:   Tue Jul 16 00:03:49 2019 +0200

    remove some debugging

commit e892c5cf4d
Author: holger krekel <holger@merlinux.eu>
Date:   Mon Jul 15 23:31:30 2019 +0200

    fix test for events

commit 9ad4c9a6fe
Author: holger krekel <holger@merlinux.eu>
Date:   Mon Jul 15 22:51:57 2019 +0200

    wip try test that we see INFO events from the core
2019-07-21 23:31:14 +02:00
Alexander Krotov
686678c96c Spellcheck 2019-07-21 21:40:19 +02:00
Alexander Krotov
c116d6f73f Use Rust for instead of C while in dc_array test 2019-07-21 21:27:49 +02:00
Alexander Krotov
a7c8ebc089 Replace DC_ARRAY_MAGIC #define with a constant 2019-07-21 21:26:27 +02:00
Floris Bruynooghe
7774052911 Use DC_PARAM_* constants everywhere
Also document each type they store.  This makes existing code a little
more readable and gives some hints towards refactoring this.
2019-07-21 20:25:52 +02:00
Floris Bruynooghe
68888f6d1f Also silence warnings in test code
We can be a bit more liberal with .unwrap() here.
2019-07-21 12:03:04 +02:00
Alexander Krotov
3dfd623db7 Use constants instead of hardcoded values in dc_mimefactory.rs 2019-07-21 00:37:58 +02:00
Floris Bruynooghe
31d2bc7401 Silence warnings from ignored Result values
For a few of the locations where error handling is done correctly this
does the right thing.  For most other places it gracefully ignores any
issues which is what the original code did as well.  Errors are
already logged by the called functions in those cases.
2019-07-21 00:32:33 +02:00
Floris Bruynooghe
5ee8f8cb59 Several fixes to the intergration tests
- Pass extra_link_args when using an installed libdeltachat
- Allow setting the liveconfig by envvar
- Show lifeconfig path in the pytest summary line
- Pass required envvars through tox
- Fix broken liveconfig passing in run-integration-test.sh
2019-07-20 23:28:23 +02:00
Floris Bruynooghe
d1825956b2 Merge pull request #198 from link2xt/top_evil_rs_shebang
src/top_evil_rs.py: fix shebang to always use Python 3 and make it executable
2019-07-20 19:17:44 +02:00
Alexander Krotov
30ca377586 src/top_evil_rs.py: fix shebang to always use Python 3 and make it executable
PEP-0394 (https://www.python.org/dev/peps/pep-0394/) recommends to use more specific shebangs.
For example, Debian allows /usr/bin/python to be configured to python2 via `update-alternatives python`, but the script does not work with Python 2.
2019-07-20 19:43:05 +03:00
holger krekel
f58b1d66c2 add a little script to compute rust-evilness 2019-07-19 10:15:04 +02:00
dignifiedquire
0c5015d92b chore: release v1.0.0-alpha.3 2019-07-18 00:34:46 +02:00
dignifiedquire
ab2d2e7583 chore: prepare for using cargo-release 2019-07-18 00:33:07 +02:00
dignifiedquire
c11ac46dce chore: add Cargo.lock
Closes #145
2019-07-18 00:29:01 +02:00
Friedel Ziegelmayer
8a0fc609e6 The big sqlite refactor
* refactor: safe sql access

* Clean up the worst rebase mistakes

* Some more progress on the rebase fallout and this branch

* upgrade and compile again

* cleanup from rebase

* example of how to prepare now

* rebase fixes

* add sql.query_map

* less preparation

* more improvements in sql code

* fix string truncation

* more prepare conversions

* most prep done

* fix tests

* fix ffi

* fix last prepares

* fix segfaults and some queries

* use r2d2 pool

* fix dc_job sql call, to reduce contention

* try newer rust

* No more vararg printing (drop dc_log_)

* ignore expected errors

* fix: uses exists instead of execute where needed

* fix: get_contacts logic was broken

* fix: contact creation

* test on 32bit linux

* ci: try running 32bit without cross

* undo 32bit tests

* refactor: rename dc_sqlite3 to sql

* fix: safer string conversions

* more string fixes

* try fixing appveyor build to 64bit

* chore(ci): hardcode target

* chore(ci): appveyor

* some cleanup work

* try fix darwin

* fix and improve sql escaping

* fix various bugs

* fix chat deletion

* refactor: cleanup config values and move to their own file

* refactor: move more methods onto the sql struct

* dont panic on failed state loading

* first round of cr

* one more cr fix

* stop using strange defaults

* remove unused escapes
2019-07-18 00:24:45 +02:00
111 changed files with 28035 additions and 21245 deletions

View File

@@ -1,5 +1,4 @@
version: 2.1
executors:
default:
docker:
@@ -13,7 +12,7 @@ restore-workspace: &restore-workspace
restore-cache: &restore-cache
restore_cache:
keys:
- cargo-v0-{{ checksum "rust-toolchain" }}-{{ checksum "Cargo.toml" }}-{{ checksum "Cargo.lock" }}-{{ arch }}
- cargo-v1-{{ checksum "rust-toolchain" }}-{{ checksum "Cargo.toml" }}-{{ checksum "Cargo.lock" }}-{{ arch }}
- repo-source-{{ .Branch }}-{{ .Revision }}
commands:
@@ -53,10 +52,11 @@ jobs:
command: cargo generate-lockfile
- restore_cache:
keys:
- cargo-v0-{{ checksum "rust-toolchain" }}-{{ checksum "Cargo.toml" }}-{{ checksum "Cargo.lock" }}-{{ arch }}
- cargo-v1-{{ checksum "rust-toolchain" }}-{{ checksum "Cargo.toml" }}-{{ checksum "Cargo.lock" }}-{{ arch }}
- run: rustup install $(cat rust-toolchain)
- run: rustup default $(cat rust-toolchain)
- run: rustup component add --toolchain $(cat rust-toolchain) rustfmt
- run: rustup component add --toolchain $(cat rust-toolchain) clippy-preview
- run: cargo update
- run: cargo fetch
- run: rustc +stable --version
@@ -67,7 +67,7 @@ jobs:
paths:
- crate
- save_cache:
key: cargo-v0-{{ checksum "rust-toolchain" }}-{{ checksum "Cargo.toml" }}-{{ checksum "Cargo.lock" }}-{{ arch }}
key: cargo-v1-{{ checksum "rust-toolchain" }}-{{ checksum "Cargo.toml" }}-{{ checksum "Cargo.lock" }}-{{ arch }}
paths:
- "~/.cargo"
- "~/.rustup"
@@ -152,14 +152,24 @@ jobs:
- wheelhouse
upload_docs_wheels:
machine: True
machine: true
steps:
- checkout
- attach_workspace:
at: workspace
- run: pyenv global 3.5.2
- run: ls -laR workspace
- run: ci_scripts/ci_upload.sh workspace/py-docs workspace/wheelhouse
clippy:
executor: default
steps:
- *restore-workspace
- *restore-cache
- run:
name: Run cargo clippy
command: cargo clippy --all
workflows:
version: 2.1
@@ -169,12 +179,16 @@ workflows:
- build_test_docs_wheel
- upload_docs_wheels:
requires:
- build_test_docs_wheel
- build_test_docs_wheel
- cargo_fetch
- rustfmt:
requires:
- cargo_fetch
- clippy:
requires:
- cargo_fetch
# Linux Desktop 64bit
- test_x86_64-unknown-linux-gnu:
requires:

2
.gitattributes vendored
View File

@@ -2,7 +2,7 @@
# ensures this even if the user has not set core.autocrlf.
* text=auto
# binary files should be detected by git, however, to be sure, you can add them here explictly
# binary files should be detected by git, however, to be sure, you can add them here explicitly
*.png binary
*.jpg binary
*.gif binary

7
.gitignore vendored
View File

@@ -1,6 +1,5 @@
/target
**/*.rs.bk
Cargo.lock
# ignore vi temporaries
*~
@@ -17,3 +16,9 @@ python/.tox
*.egg-info
__pycache__
python/src/deltachat/capi*.so
python/liveconfig*
# ignore doxgen generated files
deltachat-ffi/html
deltachat-ffi/xml

3055
Cargo.lock generated Normal file

File diff suppressed because it is too large Load Diff

View File

@@ -1,6 +1,6 @@
[package]
name = "deltachat"
version = "1.0.0-alpha.2"
version = "1.0.0-alpha.4"
authors = ["dignifiedquire <dignifiedquire@gmail.com>"]
edition = "2018"
license = "MPL"
@@ -10,11 +10,13 @@ cc = "1.0.35"
pkg-config = "0.3"
[dependencies]
deltachat_derive = { path = "./deltachat_derive" }
libc = "0.2.51"
pgp = { version = "0.2", default-features = false }
hex = "0.3.2"
sha2 = "0.8.0"
rand = "0.6.5"
phf = { git = "https://github.com/sfackler/rust-phf", rev = "0d00821", features = ["macros"] }
smallvec = "0.6.9"
reqwest = "0.9.15"
num-derive = "0.2.5"
@@ -22,10 +24,10 @@ num-traits = "0.2.6"
native-tls = "0.2.3"
lettre = "0.9.0"
imap = "1.0.1"
mmime = "0.1.0"
mmime = { git = "https://github.com/dignifiedquire/mmime", rev = "bccd2c2" }
base64 = "0.10"
charset = "0.1"
percent-encoding = "1.0"
percent-encoding = "2.0"
serde = { version = "1.0", features = ["derive"] }
serde_json = "1.0"
chrono = "0.4.6"
@@ -35,12 +37,19 @@ failure_derive = "0.1.5"
rustyline = "4.1.0"
lazy_static = "1.3.0"
regex = "1.1.6"
rusqlite = { version = "0.19", features = ["bundled"] }
addr = "0.2.0"
r2d2_sqlite = "0.11.0"
rusqlite = { version = "0.20", features = ["bundled"] }
r2d2_sqlite = "0.12.0"
r2d2 = "0.8.5"
strum = "0.15.0"
strum_macros = "0.15.0"
thread-local-object = "0.1.0"
backtrace = "0.3.33"
byteorder = "1.3.1"
itertools = "0.8.0"
image-meta = "0.1.0"
quick-xml = "0.15.0"
escaper = "0.1.0"
bitflags = "1.1.0"
[dev-dependencies]
tempfile = "3.0"
@@ -49,7 +58,8 @@ pretty_env_logger = "0.3.0"
[workspace]
members = [
"deltachat-ffi"
"deltachat-ffi",
"deltachat_derive",
]
[[example]]

View File

@@ -63,6 +63,11 @@ Single#10: yourfriends@email.org [yourfriends@email.org]
Message sent.
```
If `yourfriend@email.org` uses DeltaChat, but does not receive message just
sent, it is advisable to check `Spam` folder. It is known that at least
`gmx.com` treat such test messages as spam, unless told otherwise with web
interface.
List messages when inside a chat:
```
@@ -84,6 +89,14 @@ $ cargo test --all
$ cargo build -p deltachat_ffi --release
```
### Expensive tests
Some tests are expensive and marked with `#[ignore]`, to run these
use the `--ignored` argument to the test binary (not to cargo itself):
```sh
$ cargo test -- --ignored
```
## Features
- `vendored`: When using Openssl for TLS, this bundles a vendored version.

6
Xargo.toml Normal file
View File

@@ -0,0 +1,6 @@
[dependencies.std]
features = ["panic-unwind"]
# if using `cargo test`
[dependencies.test]
stage = 1

View File

@@ -4,6 +4,7 @@ set -ex
export RUST_TEST_THREADS=1
export RUST_BACKTRACE=1
export RUSTFLAGS='--deny warnings'
export OPT="--target=$TARGET"
export OPT_RELEASE="--release ${OPT}"
export OPT_FFI_RELEASE="--manifest-path=deltachat-ffi/Cargo.toml --release"

View File

@@ -16,7 +16,7 @@ export BRANCH=${CIRCLE_BRANCH:-test7}
#fi
# run everything else inside docker (TESTS, DOCS, WHEELS)
docker run -e BRANCH -e TESTS -e DOCS \
docker run -e DCC_PY_LIVECONFIG -e BRANCH -e TESTS -e DOCS \
--rm -it -v $(pwd):/mnt -w /mnt \
deltachat/coredeps ci_scripts/run_all.sh

View File

@@ -15,6 +15,7 @@ export BRANCH=${CIRCLE_BRANCH:?specify branch for uploading purposes}
# python docs to py.delta.chat
ssh -o StrictHostKeyChecking=no -o UserKnownHostsFile=/dev/null delta@py.delta.chat mkdir -p build/${BRANCH}
rsync -avz \
-e "ssh -o StrictHostKeyChecking=no -o UserKnownHostsFile=/dev/null" \
"$PYDOCDIR/html/" \
@@ -33,15 +34,17 @@ echo -----------------------
# Bundle external shared libraries into the wheels
pushd $WHEELHOUSEDIR
pip install devpi-client
pip3 install devpi-client
devpi use https://m.devpi.net
devpi login dc --password $DEVPI_LOGIN
devpi use dc/$BRANCH || {
devpi index -c $BRANCH
devpi use dc/$BRANCH
N_BRANCH=${BRANCH//[\/]}
devpi use dc/$N_BRANCH || {
devpi index -c $N_BRANCH
devpi use dc/$N_BRANCH
}
devpi index $BRANCH bases=/root/pypi
devpi index $N_BRANCH bases=/root/pypi
devpi upload deltachat*.whl
popd

View File

@@ -3,9 +3,9 @@
set -e -x
# Install Rust
curl https://sh.rustup.rs -sSf | sh -s -- --default-toolchain nightly-2019-04-19 -y
curl https://sh.rustup.rs -sSf | sh -s -- --default-toolchain nightly-2019-07-10 -y
export PATH=/root/.cargo/bin:$PATH
rustc --version
# remove some 300-400 MB that we don't need for automated builds
rm -rf /root/.rustup/toolchains/nightly-2019-04-19-x86_64-unknown-linux-gnu/share/
rm -rf /root/.rustup/toolchains/nightly-2019-07-10-x86_64-unknown-linux-gnu/share/

View File

@@ -37,7 +37,11 @@ if [ -n "$TESTS" ]; then
export PYTHONDONTWRITEBYTECODE=1
# run tox
tox --workdir "$TOXWORKDIR" -e lint,py27,py35,py36,py37,auditwheels
# XXX we don't run liveconfig tests because they hang sometimes
# see https://github.com/deltachat/deltachat-core-rust/issues/331
# unset DCC_PY_LIVECONFIG
tox --workdir "$TOXWORKDIR" -e lint,py35,py36,py37,auditwheels
popd
fi

View File

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

2423
deltachat-ffi/Doxyfile Normal file

File diff suppressed because it is too large Load Diff

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.9 KiB

View File

@@ -0,0 +1,7 @@
/* the code snippet frame, defaults to white which tends to get badly readable in combination with explaining text around */
div.fragment {
background-color: #e0e0e0;
border: 0;
padding: 1em;
}

View File

@@ -1 +1,10 @@
# Delta Chat C Interface
## Documentation
To generate the C Interface documentation,
call doxygen in the `deltachat-ffi` directory
and browse the `html` subdirectory.
If thinks work,
the documentation is also available online at <https://c.delta.chat>

33
deltachat-ffi/build.rs Normal file
View File

@@ -0,0 +1,33 @@
use std::io::Write;
use std::path::PathBuf;
use std::{env, fs};
fn main() {
let out_path = PathBuf::from(env::var("OUT_DIR").unwrap());
let target_path = out_path.join("../../..");
let target_triple = env::var("TARGET").unwrap();
// macOS or iOS, inherited from rpgp
let libs_priv = if target_triple.contains("apple") || target_triple.contains("darwin") {
// needed for OsRng
"-framework Security -framework Foundation"
} else {
""
};
let pkg_config = format!(
include_str!("deltachat.pc.in"),
name = "deltachat",
description = env::var("CARGO_PKG_DESCRIPTION").unwrap(),
url = env::var("CARGO_PKG_HOMEPAGE").unwrap_or("".to_string()),
version = env::var("CARGO_PKG_VERSION").unwrap(),
libs_priv = libs_priv,
prefix = env::var("PREFIX").unwrap_or("/usr/local".to_string()),
);
fs::create_dir_all(target_path.join("pkgconfig")).unwrap();
fs::File::create(target_path.join("pkgconfig").join("deltachat.pc"))
.unwrap()
.write_all(&pkg_config.as_bytes())
.unwrap();
}

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,11 @@
prefix={prefix}
libdir=${{prefix}}/lib
includedir=${{prefix}}/include
Name: {name}
Description: {description}
URL: {url}
Version: {version}
Cflags: -I${{includedir}}
Libs: -L${{libdir}} -ldeltachat
Libs.private: {libs_priv}

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,12 @@
[package]
name = "deltachat_derive"
version = "0.1.0"
authors = ["Dmitry Bogatov <KAction@debian.org>"]
edition = "2018"
[lib]
proc-macro = true
[dependencies]
syn = "0.14.4"
quote = "0.6.3"

View File

@@ -0,0 +1,44 @@
#![recursion_limit = "128"]
extern crate proc_macro;
use crate::proc_macro::TokenStream;
use quote::quote;
use syn;
// For now, assume (not check) that these macroses are applied to enum without
// data. If this assumption is violated, compiler error will point to
// generated code, which is not very user-friendly.
#[proc_macro_derive(ToSql)]
pub fn to_sql_derive(input: TokenStream) -> TokenStream {
let ast: syn::DeriveInput = syn::parse(input).unwrap();
let name = &ast.ident;
let gen = quote! {
impl rusqlite::types::ToSql for #name {
fn to_sql(&self) -> rusqlite::Result<rusqlite::types::ToSqlOutput> {
let num = *self as i64;
let value = rusqlite::types::Value::Integer(num);
let output = rusqlite::types::ToSqlOutput::Owned(value);
std::result::Result::Ok(output)
}
}
};
gen.into()
}
#[proc_macro_derive(FromSql)]
pub fn from_sql_derive(input: TokenStream) -> TokenStream {
let ast: syn::DeriveInput = syn::parse(input).unwrap();
let name = &ast.ident;
let gen = quote! {
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)
}
}
};
gen.into()
}

File diff suppressed because it is too large Load Diff

View File

@@ -14,16 +14,17 @@ extern crate lazy_static;
extern crate rusqlite;
use std::borrow::Cow::{self, Borrowed, Owned};
use std::ptr;
use std::sync::atomic::{AtomicBool, Ordering};
use std::sync::{Arc, Mutex, RwLock};
use deltachat::config;
use deltachat::configure::*;
use deltachat::constants::*;
use deltachat::context::*;
use deltachat::dc_configure::*;
use deltachat::dc_job::*;
use deltachat::dc_securejoin::*;
use deltachat::dc_tools::*;
use deltachat::job::*;
use deltachat::oauth2::*;
use deltachat::types::*;
use deltachat::x::*;
@@ -172,13 +173,11 @@ fn start_threads(c: Arc<RwLock<Context>>) {
let ctx = c.clone();
let handle_imap = std::thread::spawn(move || loop {
while_running!({
unsafe {
dc_perform_imap_jobs(&ctx.read().unwrap());
dc_perform_imap_fetch(&ctx.read().unwrap());
}
perform_imap_jobs(&ctx.read().unwrap());
perform_imap_fetch(&ctx.read().unwrap());
while_running!({
let context = ctx.read().unwrap();
dc_perform_imap_idle(&context);
perform_imap_idle(&context);
});
});
});
@@ -186,9 +185,9 @@ fn start_threads(c: Arc<RwLock<Context>>) {
let ctx = c.clone();
let handle_mvbox = std::thread::spawn(move || loop {
while_running!({
unsafe { dc_perform_mvbox_fetch(&ctx.read().unwrap()) };
perform_mvbox_fetch(&ctx.read().unwrap());
while_running!({
unsafe { dc_perform_mvbox_idle(&ctx.read().unwrap()) };
perform_mvbox_idle(&ctx.read().unwrap());
});
});
});
@@ -196,9 +195,9 @@ fn start_threads(c: Arc<RwLock<Context>>) {
let ctx = c.clone();
let handle_sentbox = std::thread::spawn(move || loop {
while_running!({
unsafe { dc_perform_sentbox_fetch(&ctx.read().unwrap()) };
perform_sentbox_fetch(&ctx.read().unwrap());
while_running!({
unsafe { dc_perform_sentbox_idle(&ctx.read().unwrap()) };
perform_sentbox_idle(&ctx.read().unwrap());
});
});
});
@@ -206,9 +205,9 @@ fn start_threads(c: Arc<RwLock<Context>>) {
let ctx = c;
let handle_smtp = std::thread::spawn(move || loop {
while_running!({
unsafe { dc_perform_smtp_jobs(&ctx.read().unwrap()) };
perform_smtp_jobs(&ctx.read().unwrap());
while_running!({
unsafe { dc_perform_smtp_idle(&ctx.read().unwrap()) };
perform_smtp_idle(&ctx.read().unwrap());
});
});
});
@@ -226,12 +225,10 @@ fn stop_threads(context: &Context) {
println!("Stopping threads");
IS_RUNNING.store(false, Ordering::Relaxed);
unsafe {
dc_interrupt_imap_idle(context);
dc_interrupt_mvbox_idle(context);
dc_interrupt_sentbox_idle(context);
dc_interrupt_smtp_idle(context);
}
interrupt_imap_idle(context);
interrupt_mvbox_idle(context);
interrupt_sentbox_idle(context);
interrupt_smtp_idle(context);
handle.handle_imap.take().unwrap().join().unwrap();
handle.handle_mvbox.take().unwrap().join().unwrap();
@@ -334,8 +331,8 @@ const CONTACT_COMMANDS: [&'static str; 6] = [
"delcontact",
"cleanupcontacts",
];
const MISC_COMMANDS: [&'static str; 8] = [
"getqr", "getbadqr", "checkqr", "event", "fileinfo", "clear", "exit", "help",
const MISC_COMMANDS: [&'static str; 9] = [
"getqr", "getbadqr", "checkqr", "event", "fileinfo", "clear", "exit", "quit", "help",
];
impl Hinter for DcHelper {
@@ -388,22 +385,12 @@ impl Highlighter for DcHelper {
impl Helper for DcHelper {}
fn main_0(args: Vec<String>) -> Result<(), failure::Error> {
let mut context = dc_context_new(
Some(receive_event),
0 as *mut libc::c_void,
b"CLI\x00" as *const u8 as *const libc::c_char,
);
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 0 == unsafe {
dc_open(
&mut context,
to_cstring(&args[1]).as_ptr(),
0 as *const libc::c_char,
)
} {
if unsafe { !dc_open(&mut context, &args[1], None) } {
println!("Error: Cannot open {}.", args[0],);
}
} else if args.len() != 1 {
@@ -461,12 +448,6 @@ fn main_0(args: Vec<String>) -> Result<(), failure::Error> {
println!("history saved");
{
stop_threads(&ctx.read().unwrap());
unsafe {
let mut ctx = ctx.write().unwrap();
dc_close(&mut ctx);
dc_context_unref(&mut ctx);
}
}
Ok(())
@@ -482,11 +463,10 @@ unsafe fn handle_cmd(line: &str, ctx: Arc<RwLock<Context>>) -> Result<ExitResult
let mut args = line.splitn(2, ' ');
let arg0 = args.next().unwrap_or_default();
let arg1 = args.next().unwrap_or_default();
let arg1_c = to_cstring(arg1);
let arg1_c_ptr = if arg1.is_empty() {
let arg1_c = if arg1.is_empty() {
std::ptr::null()
} else {
arg1_c.as_ptr()
arg1.strdup()
};
match arg0 {
@@ -500,25 +480,22 @@ unsafe fn handle_cmd(line: &str, ctx: Arc<RwLock<Context>>) -> Result<ExitResult
if HANDLE.clone().lock().unwrap().is_some() {
println!("smtp-jobs are already running in a thread.",);
} else {
dc_perform_smtp_jobs(&ctx.read().unwrap());
perform_smtp_jobs(&ctx.read().unwrap());
}
}
"imap-jobs" => {
if HANDLE.clone().lock().unwrap().is_some() {
println!("imap-jobs are already running in a thread.");
} else {
dc_perform_imap_jobs(&ctx.read().unwrap());
perform_imap_jobs(&ctx.read().unwrap());
}
}
"configure" => {
start_threads(ctx.clone());
dc_configure(&ctx.read().unwrap());
configure(&ctx.read().unwrap());
}
"oauth2" => {
let addr = config::get(&ctx.read().unwrap(), "addr");
if addr.is_empty() {
println!("oauth2: set addr first.");
} else {
if let Some(addr) = ctx.read().unwrap().get_config(config::Config::Addr) {
let oauth2_url = dc_get_oauth2_url(
&ctx.read().unwrap(),
&addr,
@@ -529,6 +506,8 @@ unsafe fn handle_cmd(line: &str, ctx: Arc<RwLock<Context>>) -> Result<ExitResult
} else {
println!("Open the following url, set mail_pw to the generated token and server_flags to 2:\n{}", oauth2_url.unwrap());
}
} else {
println!("oauth2: set addr first.");
}
}
"clear" => {
@@ -560,13 +539,15 @@ unsafe fn handle_cmd(line: &str, ctx: Arc<RwLock<Context>>) -> Result<ExitResult
"joinqr" => {
start_threads(ctx.clone());
if !arg0.is_empty() {
dc_join_securejoin(&ctx.read().unwrap(), arg1_c_ptr);
dc_join_securejoin(&ctx.read().unwrap(), arg1_c);
}
}
"exit" => return Ok(ExitResult::Exit),
"exit" | "quit" => return Ok(ExitResult::Exit),
_ => dc_cmdline(&ctx.read().unwrap(), line)?,
}
free(arg1_c as *mut _);
Ok(ExitResult::Continue)
}

View File

@@ -1,22 +1,20 @@
extern crate deltachat;
use std::ffi::{CStr, CString};
use std::ffi::CStr;
use std::sync::{Arc, RwLock};
use std::{thread, time};
use tempfile::tempdir;
use deltachat::chat;
use deltachat::chatlist::*;
use deltachat::config;
use deltachat::configure::*;
use deltachat::constants::Event;
use deltachat::contact::*;
use deltachat::context::*;
use deltachat::dc_chat::*;
use deltachat::dc_chatlist::*;
use deltachat::dc_configure::*;
use deltachat::dc_contact::*;
use deltachat::dc_job::{
dc_perform_imap_fetch, dc_perform_imap_idle, dc_perform_imap_jobs, dc_perform_smtp_idle,
dc_perform_smtp_jobs,
use deltachat::job::{
perform_imap_fetch, perform_imap_idle, perform_imap_jobs, perform_smtp_idle, perform_smtp_jobs,
};
use deltachat::dc_lot::*;
extern "C" fn cb(_ctx: &Context, event: Event, data1: usize, data2: usize) -> usize {
println!("[{:?}]", event);
@@ -41,7 +39,7 @@ extern "C" fn cb(_ctx: &Context, event: Event, data1: usize, data2: usize) -> us
fn main() {
unsafe {
let ctx = dc_context_new(Some(cb), std::ptr::null_mut(), std::ptr::null_mut());
let ctx = dc_context_new(Some(cb), std::ptr::null_mut(), None);
let running = Arc::new(RwLock::new(true));
let info = dc_get_info(&ctx);
let info_s = CStr::from_ptr(info);
@@ -53,12 +51,12 @@ fn main() {
let r1 = running.clone();
let t1 = thread::spawn(move || {
while *r1.read().unwrap() {
dc_perform_imap_jobs(&ctx1);
perform_imap_jobs(&ctx1);
if *r1.read().unwrap() {
dc_perform_imap_fetch(&ctx1);
perform_imap_fetch(&ctx1);
if *r1.read().unwrap() {
dc_perform_imap_idle(&ctx1);
perform_imap_idle(&ctx1);
}
}
}
@@ -68,59 +66,46 @@ fn main() {
let r1 = running.clone();
let t2 = thread::spawn(move || {
while *r1.read().unwrap() {
dc_perform_smtp_jobs(&ctx1);
perform_smtp_jobs(&ctx1);
if *r1.read().unwrap() {
dc_perform_smtp_idle(&ctx1);
perform_smtp_idle(&ctx1);
}
}
});
let dir = tempdir().unwrap();
let dbfile = CString::new(dir.path().join("db.sqlite").to_str().unwrap()).unwrap();
let dbfile = dir.path().join("db.sqlite");
println!("opening database {:?}", dbfile);
assert_eq!(dc_open(&ctx, dbfile.as_ptr(), std::ptr::null()), 1);
assert!(dc_open(&ctx, dbfile.to_str().unwrap(), None));
println!("configuring");
let args = std::env::args().collect::<Vec<String>>();
assert_eq!(args.len(), 2, "missing password");
let pw = args[1].clone();
config::set(&ctx, "addr", Some("d@testrun.org"));
config::set(&ctx, "mail_pw", Some(&pw));
dc_configure(&ctx);
ctx.set_config(config::Config::Addr, Some("d@testrun.org"))
.unwrap();
ctx.set_config(config::Config::MailPw, Some(&pw)).unwrap();
configure(&ctx);
thread::sleep(duration);
let email = CString::new("dignifiedquire@gmail.com").unwrap();
println!("sending a message");
let contact_id = dc_create_contact(&ctx, std::ptr::null(), email.as_ptr());
let chat_id = dc_create_chat_by_contact_id(&ctx, contact_id);
let msg_text = CString::new("Hi, here is my first message!").unwrap();
dc_send_text_msg(&ctx, chat_id, msg_text.as_ptr());
let contact_id =
Contact::create(&ctx, "dignifiedquire", "dignifiedquire@gmail.com").unwrap();
let chat_id = chat::create_by_contact_id(&ctx, contact_id).unwrap();
chat::send_text_msg(&ctx, chat_id, "Hi, here is my first message!".into()).unwrap();
println!("fetching chats..");
let chats = dc_get_chatlist(&ctx, 0, std::ptr::null(), 0);
let chats = Chatlist::try_load(&ctx, 0, None, None).unwrap();
for i in 0..dc_chatlist_get_cnt(chats) {
let summary = dc_chatlist_get_summary(chats, 0, std::ptr::null_mut());
let text1 = dc_lot_get_text1(summary);
let text2 = dc_lot_get_text2(summary);
let text1_s = if !text1.is_null() {
Some(CStr::from_ptr(text1))
} else {
None
};
let text2_s = if !text2.is_null() {
Some(CStr::from_ptr(text2))
} else {
None
};
println!("chat: {} - {:?} - {:?}", i, text1_s, text2_s,);
dc_lot_unref(summary);
for i in 0..chats.len() {
let summary = chats.get_summary(0, None);
let text1 = summary.get_text1();
let text2 = summary.get_text2();
println!("chat: {} - {:?} - {:?}", i, text1, text2,);
}
dc_chatlist_unref(chats);
thread::sleep(duration);
@@ -137,8 +122,8 @@ fn main() {
println!("stopping threads");
*running.clone().write().unwrap() = false;
deltachat::dc_job::dc_interrupt_imap_idle(&ctx);
deltachat::dc_job::dc_interrupt_smtp_idle(&ctx);
deltachat::job::interrupt_imap_idle(&ctx);
deltachat::job::interrupt_smtp_idle(&ctx);
println!("joining");
t1.join().unwrap();

View File

@@ -4,6 +4,12 @@
- introduce automatic versioning via setuptools_scm,
based on py-X.Y.Z tags
- integrate latest DCC core-rust with dc_close() fixes
- provide a account.shutdown() method and improve termination
logic also in tests. also fixes output-clubbering during
test runs.
0.600.0
---------

View File

@@ -15,7 +15,7 @@ without any "build-from-source" steps.
1. `Install virtualenv <https://virtualenv.pypa.io/en/stable/installation/>`_,
then create a fresh python environment and activate it in your shell::
virtualenv -p python3 venv
virtualenv venv # or: python -m venv
source venv/bin/activate
Afterwards, invoking ``python`` or ``pip install`` will only
@@ -39,6 +39,12 @@ and push them to a python package index. To install the latest github ``master``
pip install -i https://m.devpi.net/dc/master deltachat
.. note::
If you can help to automate the building of wheels for Mac or Windows,
that'd be much appreciated! please then get
`in contact with us <https://delta.chat/en/contribute>`_.
Installing bindings from source
===============================
@@ -48,34 +54,55 @@ to core deltachat library::
git clone https://github.com/deltachat/deltachat-core-rust
cd deltachat-core-rust
cargo build -p deltachat_ffi --release
This will result in a ``libdeltachat.so`` and ``libdeltachat.a`` files
in the ``target/release`` directory. These files are needed for
creating the python bindings for deltachat::
cd python
DCC_RS_DEV=`pwd`/.. pip install -e .
Now test if the bindings find the correct library::
If you don't have one active, create and activate a python "virtualenv":
python -c 'import deltachat ; print(deltachat.__version__)'
python virtualenv venv # or python -m venv
source venv/bin/activate
This should print your deltachat bindings version.
Afterwards ``which python`` tells you that it comes out of the "venv"
directory that contains all python install artifacts. Let's first
install test tools::
pip install pytest pytest-timeout requests
then cargo-build and install the deltachat bindings::
python install_python_bindings.py
The bindings will be installed in release mode but with debug symbols.
The release mode is neccessary because some tests generate RSA keys
which is prohibitively slow in debug mode.
After succcessul binding installation you can finally run the tests::
pytest -v tests
.. note::
If you can help to automate the building of wheels for Mac or Windows,
that'd be much appreciated! please then get
`in contact with us <https://delta.chat/en/contribute>`_.
Some tests are sometimes failing/hanging because of
https://github.com/deltachat/deltachat-core-rust/issues/331
and
https://github.com/deltachat/deltachat-core-rust/issues/326
Using a system-installed deltachat-core-rust
--------------------------------------------
When calling ``pip`` without specifying the ``DCC_RS_DEV`` environment
variable cffi will try to use a ``deltachat.h`` from a system location
like ``/usr/local/include`` and will try to dynamically link against a
``libdeltachat.so`` in a similar location (e.g. ``/usr/local/lib``).
running "live" tests (experimental)
-----------------------------------
If you want to run "liveconfig" functional tests you can set
``DCC_PY_LIVECONFIG`` to:
- a particular https-url that you can ask for from the delta
chat devs.
- or the path of a file that contains two lines, each describing
via "addr=... mail_pwd=..." a test account login that will
be used for the live tests.
With ``DCC_PY_LIVECONFIG`` set pytest invocations will use real
e-mail accounts and run through all functional "liveconfig" tests.
Code examples
@@ -84,68 +111,34 @@ Code examples
You may look at `examples <https://py.delta.chat/examples.html>`_.
Running tests
=============
Get a checkout of the `deltachat-core-rust github repository`_ and type::
pip install tox
./run-integration-tests.sh
If you want to run functional tests with real
e-mail test accounts, generate a "liveconfig" file where each
lines contains test account settings, for example::
# 'liveconfig' file specifying imap/smtp accounts
addr=some-email@example.org mail_pw=password
addr=other-email@example.org mail_pw=otherpassword
The "keyword=value" style allows to specify any
`deltachat account config setting <https://c.delta.chat/classdc__context__t.html#aff3b894f6cfca46cab5248fdffdf083d>`_ so you can also specify smtp or imap servers, ports, ssl modes etc.
Typically DC's automatic configuration allows to not specify these settings.
The ``run-integration-tests.sh`` script will automatically use
``python/liveconfig`` if it exists, to manually run tests with this
``liveconfig`` file use::
tox -- --liveconfig liveconfig
.. _`deltachat-core-rust github repository`: https://github.com/deltachat/deltachat-core-rust
.. _`deltachat-core`: https://github.com/deltachat/deltachat-core-rust
Running test using a debug build
--------------------------------
If you need to examine e.g. a coredump you may want to run the tests
using a debug build::
DCC_RS_TARGET=debug ./run-integration-tests.sh -e py37 -- -x -v -k failing_test
Building manylinux1 wheels
==========================
.. note::
This section may not fully work.
Building portable manylinux1 wheels which come with libdeltachat.so
and all it's dependencies is easy using the provided docker tooling.
using docker pull / premade images
------------------------------------
We publish a build environment under the ``deltachat/wheel`` tag so
We publish a build environment under the ``deltachat/coredeps`` tag so
that you can pull it from the ``hub.docker.com`` site's "deltachat"
organization::
$ docker pull deltachat/wheel
$ docker pull deltachat/coredeps
The ``deltachat/wheel`` image can be used to build both libdeltachat.so
and the Python wheels::
This docker image can be used to run tests and build Python wheels for all interpreters::
$ docker run --rm -it -v $(pwd):/io/ deltachat/wheel /io/python/wheelbuilder/build-wheels.sh
$ bash ci_scripts/ci_run.sh
This command runs a script within the image, after mounting ``$(pwd)`` as ``/io`` within
the docker image. The script is specified as a path within the docker image's filesystem.
The resulting wheel files will be in ``python/wheelhouse``.
This command runs tests and build-wheel scripts in a docker container.
Optionally build your own docker image
@@ -154,10 +147,10 @@ Optionally build your own docker image
If you want to build your own custom docker image you can do this::
$ cd deltachat-core # cd to deltachat-core checkout directory
$ docker build -t deltachat/wheel python/wheelbuilder/
$ docker build -t deltachat/coredeps ci_scripts/docker_coredeps
This will use the ``python/wheelbuilder/Dockerfile`` to build
up docker image called ``deltachat/wheel``. You can afterwards
This will use the ``ci_scripts/docker_coredeps/Dockerfile`` to build
up docker image called ``deltachat/coredeps``. You can afterwards
find it with::
$ docker images

View File

@@ -11,8 +11,6 @@ high level API reference
- :class:`deltachat.chatting.Contact`
- :class:`deltachat.chatting.Chat`
- :class:`deltachat.message.Message`
- :class:`deltachat.message.MessageType`
- :class:`deltachat.message.MessageState`
Account
-------
@@ -39,16 +37,3 @@ Message
.. autoclass:: deltachat.message.Message
:members:
MessageType
------------
.. autoclass:: deltachat.message.MessageType
:members:
MessageState
------------
.. autoclass:: deltachat.message.MessageState
:members:

View File

@@ -288,10 +288,6 @@ intersphinx_mapping = {'http://docs.python.org/': None}
autodoc_member_order = "bysource"
# always document __init__ functions
def skip(app, what, name, obj, skip, options):
import attr
if name == "__init__":
if not hasattr(obj.im_class, "__attrs_attrs__"):
return False
return skip
def setup(app):

View File

@@ -8,8 +8,7 @@ Playing around on the commandline
----------------------------------
Once you have :doc:`installed deltachat bindings <install>`
you can start playing from the python interpreter commandline::
you can start playing from the python interpreter commandline.
For example you can type ``python`` and then::
# instantiate and configure deltachat account
@@ -23,7 +22,7 @@ For example you can type ``python`` and then::
# create a contact and send a message
contact = ac.create_contact("someother@email.address")
chat = ac.create_chat_by_contact(contact)
chat.send_text_message("hi from the python interpreter command line")
chat.send_text("hi from the python interpreter command line")
Checkout our :doc:`api` for the various high-level things you can do
to send/receive messages, create contacts and chats.

View File

@@ -1,7 +0,0 @@
#!/usr/bin/env bash
set -ex
cargo build -p deltachat_ffi --release
rm -rf build/ src/deltachat/*.so
DCC_RS_DEV=`pwd`/.. pip install -e .

View File

@@ -0,0 +1,25 @@
#!/usr/bin/env python
"""
setup a python binding development in-place install with cargo debug symbols.
"""
import os
import subprocess
import os
if __name__ == "__main__":
os.environ["DCC_RS_TARGET"] = target = "release"
if "DCC_RS_DEV" not in os.environ:
dn = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))
os.environ["DCC_RS_DEV"] = dn
os.environ["RUSTFLAGS"] = "-g"
subprocess.check_call([
"cargo", "build", "-p", "deltachat_ffi", "--" + target
])
subprocess.check_call("rm -rf build/ src/deltachat/*.so" , shell=True)
subprocess.check_call([
"pip", "install", "-e", "."
])

View File

@@ -1,2 +0,0 @@
addr=digtest1@testrun.org mail_pw=diggydug
addr=digtest2@testrun.org mail_pw=diggydug

View File

@@ -17,7 +17,7 @@ def main():
description='Python bindings for the Delta Chat Core library using CFFI against the Rust-implemented libdeltachat',
long_description=long_description,
author='holger krekel, Floris Bruynooghe, Bjoern Petersen and contributors',
install_requires=['cffi>=1.0.0', 'attrs', 'six'],
install_requires=['cffi>=1.0.0', 'six'],
packages=setuptools.find_packages('src'),
package_dir={'': 'src'},
cffi_modules=['src/deltachat/_build.py:ffibuilder'],

View File

@@ -34,13 +34,14 @@ def py_dc_callback(ctx, evt, data1, data2):
if data1 and event_sig_types & 1:
data1 = ffi.string(ffi.cast('char*', data1)).decode("utf8")
if data2 and event_sig_types & 2:
data2 = ffi.string(ffi.cast('char*', data2)).decode("utf8")
try:
data2 = ffi.string(ffi.cast('char*', data2)).decode("utf8")
if isinstance(data2, bytes):
data2 = data2.decode("utf8")
except UnicodeDecodeError:
# XXX ignoring this error is not quite correct but for now
# XXX ignoring the decode error is not quite correct but for now
# i don't want to hunt down encoding problems in the c lib
data2 = ffi.string(ffi.cast('char*', data2))
pass
try:
ret = callback(ctx, evt_name, data1, data2)
if ret is None:
@@ -61,7 +62,10 @@ def set_context_callback(dc_context, func):
def clear_context_callback(dc_context):
_DC_CALLBACK_MAP.pop(dc_context, None)
try:
_DC_CALLBACK_MAP.pop(dc_context, None)
except AttributeError:
pass
def get_dc_event_name(integer, _DC_EVENTNAME_MAP={}):

View File

@@ -6,10 +6,15 @@ import platform
import os
import cffi
import shutil
from os.path import dirname as dn
from os.path import abspath
def ffibuilder():
projdir = os.environ.get('DCC_RS_DEV')
if not projdir:
p = dn(dn(dn(dn(abspath(__file__)))))
projdir = os.environ["DCC_RS_DEV"] = p
target = os.environ.get('DCC_RS_TARGET', 'release')
if projdir:
if platform.system() == 'Darwin':
@@ -25,11 +30,13 @@ def ffibuilder():
else:
raise NotImplementedError("Compilation not supported yet on Windows, can you help?")
objs = [os.path.join(projdir, 'target', target, 'libdeltachat.a')]
assert os.path.exists(objs[0]), objs
incs = [os.path.join(projdir, 'deltachat-ffi')]
else:
libs = ['deltachat']
objs = []
incs = []
extra_link_args = []
builder = cffi.FFI()
builder.set_source(
'deltachat.capi',
@@ -69,8 +76,8 @@ def ffibuilder():
distutils.sysconfig.customize_compiler(cc)
tmpdir = tempfile.mkdtemp()
try:
src_name = os.path.join(tmpdir, "prep.h")
dst_name = os.path.join(tmpdir, "prep2.c")
src_name = os.path.join(tmpdir, "include.h")
dst_name = os.path.join(tmpdir, "expanded.h")
with open(src_name, "w") as src_fp:
src_fp.write('#include <deltachat.h>')
cc.preprocess(source=src_name,

View File

@@ -7,9 +7,9 @@ import re
import time
from array import array
try:
from queue import Queue
from queue import Queue, Empty
except ImportError:
from Queue import Queue
from Queue import Queue, Empty
import deltachat
from . import const
@@ -36,19 +36,23 @@ class Account(object):
lib.dc_context_new(lib.py_dc_callback, ffi.NULL, ffi.NULL),
_destroy_dc_context,
)
if eventlogging:
self._evlogger = EventLogger(self._dc_context, logid)
deltachat.set_context_callback(self._dc_context, self._process_event)
self._threads = IOThreads(self._dc_context, self._evlogger._log_event)
else:
self._threads = IOThreads(self._dc_context)
if hasattr(db_path, "encode"):
db_path = db_path.encode("utf8")
if not lib.dc_open(self._dc_context, db_path, ffi.NULL):
raise ValueError("Could not dc_open: {}".format(db_path))
if eventlogging:
self._evlogger = EventLogger(self._dc_context, logid)
deltachat.set_context_callback(self._dc_context, self._process_event)
self._threads = IOThreads(self._dc_context)
self._configkeys = self.get_config("sys.config_keys").split()
self._imex_completed = threading.Event()
def __del__(self):
self.shutdown()
# XXX this can cause "illegal instructions" at test ends so we omit it for now
# def __del__(self):
# self.shutdown()
def _check_config_key(self, name):
if name not in self._configkeys:
@@ -139,15 +143,6 @@ class Account(object):
self.check_is_configured()
return Contact(self._dc_context, const.DC_CONTACT_ID_SELF)
def create_message(self, view_type):
""" create a new non persistent message.
:param view_type: a string specifying "text", "video",
"image", "audio" or "file".
:returns: :class:`deltachat.message.Message` instance.
"""
return Message.new(self._dc_context, view_type)
def create_contact(self, email, name=None):
""" create a (new) Contact. If there already is a Contact
with that e-mail address, it is unblocked and its name is
@@ -163,6 +158,17 @@ class Account(object):
assert contact_id > const.DC_CHAT_ID_LAST_SPECIAL
return Contact(self._dc_context, contact_id)
def delete_contact(self, contact):
""" delete a Contact.
:param contact: contact object obtained
:returns: True if deletion succeeded (contact was deleted)
"""
contact_id = contact.id
assert contact._dc_context == self._dc_context
assert contact_id > const.DC_CHAT_ID_LAST_SPECIAL
return bool(lib.dc_delete_contact(self._dc_context, contact_id))
def get_contacts(self, query=None, with_self=False, only_verified=False):
""" get a (filtered) list of contacts.
@@ -170,7 +176,7 @@ class Account(object):
whose name or e-mail matches query.
:param only_verified: if true only return verified contacts.
:param with_self: if true the self-contact is also returned.
:returns: list of :class:`deltachat.message.Message` objects.
:returns: list of :class:`deltachat.chatting.Contact` objects.
"""
flags = 0
query = as_dc_charpointer(query)
@@ -198,7 +204,7 @@ class Account(object):
assert isinstance(contact, int)
contact_id = contact
chat_id = lib.dc_create_chat_by_contact_id(self._dc_context, contact_id)
return Chat(self._dc_context, chat_id)
return Chat(self, chat_id)
def create_chat_by_message(self, message):
""" create or get an existing chat object for the
@@ -215,7 +221,7 @@ class Account(object):
assert isinstance(message, int)
msg_id = message
chat_id = lib.dc_create_chat_by_msg_id(self._dc_context, msg_id)
return Chat(self._dc_context, chat_id)
return Chat(self, chat_id)
def create_group_chat(self, name, verified=False):
""" create a new group chat object.
@@ -227,7 +233,7 @@ class Account(object):
"""
bytes_name = name.encode("utf8")
chat_id = lib.dc_create_group_chat(self._dc_context, verified, bytes_name)
return Chat(self._dc_context, chat_id)
return Chat(self, chat_id)
def get_chats(self):
""" return list of chats.
@@ -243,15 +249,15 @@ class Account(object):
chatlist = []
for i in range(0, lib.dc_chatlist_get_cnt(dc_chatlist)):
chat_id = lib.dc_chatlist_get_chat_id(dc_chatlist, i)
chatlist.append(Chat(self._dc_context, chat_id))
chatlist.append(Chat(self, chat_id))
return chatlist
def get_deaddrop_chat(self):
return Chat(self._dc_context, const.DC_CHAT_ID_DEADDROP)
return Chat(self, const.DC_CHAT_ID_DEADDROP)
def get_message_by_id(self, msg_id):
""" return Message instance. """
return Message.from_db(self._dc_context, msg_id)
return Message.from_db(self, msg_id)
def mark_seen_messages(self, messages):
""" mark the given set of messages as seen.
@@ -340,7 +346,7 @@ class Account(object):
def shutdown(self, wait=True):
""" stop threads and close and remove underlying dc_context and callbacks. """
if hasattr(self, "_dc_context"):
if hasattr(self, "_dc_context") and hasattr(self, "_threads"):
self.stop_threads(wait=False) # to interrupt idle and tell python threads to stop
lib.dc_close(self._dc_context)
self.stop_threads(wait=wait) # to wait for threads
@@ -362,10 +368,11 @@ class Account(object):
class IOThreads:
def __init__(self, dc_context):
def __init__(self, dc_context, log_event=lambda *args: None):
self._dc_context = dc_context
self._thread_quitflag = False
self._name2thread = {}
self._log_event = log_event
def is_started(self):
return len(self._name2thread) > 0
@@ -391,21 +398,19 @@ class IOThreads:
thread.join()
def imap_thread_run(self):
self._log_event("py-bindings-info", 0, "IMAP THREAD START")
while not self._thread_quitflag:
lib.dc_perform_imap_jobs(self._dc_context)
if self._thread_quitflag:
break
lib.dc_perform_imap_fetch(self._dc_context)
if self._thread_quitflag:
break
lib.dc_perform_imap_idle(self._dc_context)
self._log_event("py-bindings-info", 0, "IMAP THREAD FINISHED")
def smtp_thread_run(self):
self._log_event("py-bindings-info", 0, "SMTP THREAD START")
while not self._thread_quitflag:
lib.dc_perform_smtp_jobs(self._dc_context)
if self._thread_quitflag:
break
lib.dc_perform_smtp_idle(self._dc_context)
self._log_event("py-bindings-info", 0, "SMTP THREAD FINISHED")
class EventLogger:
@@ -435,6 +440,17 @@ class EventLogger:
raise ValueError("{}({!r},{!r})".format(*ev))
return ev
def ensure_event_not_queued(self, event_name_regex):
__tracebackhide__ = True
rex = re.compile("(?:{}).*".format(event_name_regex))
while 1:
try:
ev = self._event_queue.get(False)
except Empty:
break
else:
assert not rex.match(ev[0]), "event found {}".format(ev)
def get_matching(self, event_name_regex, check_error=True):
self._log("-- waiting for event with regex: {} --".format(event_name_regex))
rex = re.compile("(?:{}).*".format(event_name_regex))

View File

@@ -1,24 +1,30 @@
""" chatting related objects: Contact, Chat, Message. """
import os
import mimetypes
from . import props
from .cutil import as_dc_charpointer, from_dc_charpointer, iter_array
from .capi import lib, ffi
from . import const
import attr
from attr import validators as v
from .message import Message
@attr.s
class Contact(object):
""" Delta-Chat Contact.
You obtain instances of it through :class:`deltachat.account.Account`.
"""
_dc_context = attr.ib(validator=v.instance_of(ffi.CData))
id = attr.ib(validator=v.instance_of(int))
def __init__(self, dc_context, id):
self._dc_context = dc_context
self.id = id
def __eq__(self, other):
return self._dc_context == other._dc_context and self.id == other.id
def __ne__(self, other):
return not (self == other)
def __repr__(self):
return "<Contact id={} addr={} dc_context={}>".format(self.id, self.addr, self._dc_context)
@property
def _dc_contact(self):
@@ -46,14 +52,26 @@ class Contact(object):
return lib.dc_contact_is_verified(self._dc_contact)
@attr.s
class Chat(object):
""" Chat object which manages members and through which you can send and retrieve messages.
You obtain instances of it through :class:`deltachat.account.Account`.
"""
_dc_context = attr.ib(validator=v.instance_of(ffi.CData))
id = attr.ib(validator=v.instance_of(int))
def __init__(self, account, id):
self.account = account
self._dc_context = account._dc_context
self.id = id
def __eq__(self, other):
return self.id == getattr(other, "id", None) and \
self._dc_context == getattr(other, "_dc_context", None)
def __ne__(self, other):
return not (self == other)
def __repr__(self):
return "<Chat id={} name={} dc_context={}>".format(self.id, self.get_name(), self._dc_context)
@property
def _dc_chat(self):
@@ -126,7 +144,7 @@ class Chat(object):
msg_id = lib.dc_send_text_msg(self._dc_context, self.id, msg)
if msg_id == 0:
raise ValueError("message could not be send, does chat exist?")
return Message.from_db(self._dc_context, msg_id)
return Message.from_db(self.account, msg_id)
def send_file(self, path, mime_type="application/octet-stream"):
""" send a file and return the resulting Message instance.
@@ -136,14 +154,9 @@ class Chat(object):
:raises ValueError: if message can not be send/chat does not exist.
:returns: the resulting :class:`deltachat.message.Message` instance
"""
path = as_dc_charpointer(path)
mtype = as_dc_charpointer(mime_type)
msg = Message.new(self._dc_context, "file")
msg.set_file(path, mtype)
msg_id = lib.dc_send_msg(self._dc_context, self.id, msg._dc_msg)
if msg_id == 0:
raise ValueError("message could not be send, does chat exist?")
return Message.from_db(self._dc_context, msg_id)
msg = self.prepare_message_file(path=path, mime_type=mime_type)
self.send_prepared(msg)
return msg
def send_image(self, path):
""" send an image message and return the resulting Message instance.
@@ -152,14 +165,25 @@ class Chat(object):
:raises ValueError: if message can not be send/chat does not exist.
:returns: the resulting :class:`deltachat.message.Message` instance
"""
if not os.path.exists(path):
raise ValueError("path does not exist: {!r}".format(path))
msg = Message.new(self._dc_context, "image")
msg.set_file(path)
msg_id = lib.dc_send_msg(self._dc_context, self.id, msg._dc_msg)
return Message.from_db(self._dc_context, msg_id)
mime_type = mimetypes.guess_type(path)[0]
msg = self.prepare_message_file(path=path, mime_type=mime_type, view_type="image")
self.send_prepared(msg)
return msg
def prepare_file(self, path, mime_type=None, view_type="file"):
def prepare_message(self, msg):
""" create a new prepared message.
:param msg: the message to be prepared.
:returns: :class:`deltachat.message.Message` instance.
"""
msg_id = lib.dc_prepare_msg(self._dc_context, self.id, msg._dc_msg)
if msg_id == 0:
raise ValueError("message could not be prepared")
# invalidate passed in message which is not safe to use anymore
msg._dc_msg = msg.id = None
return Message.from_db(self.account, msg_id)
def prepare_message_file(self, path, mime_type=None, view_type="file"):
""" prepare a message for sending and return the resulting Message instance.
To actually send the message, call :meth:`send_prepared`.
@@ -167,18 +191,13 @@ class Chat(object):
:param path: path to the file.
:param mime_type: the mime-type of this file, defaults to auto-detection.
:param view_type: passed to :meth:`MessageType.new`.
:param view_type: "text", "image", "gif", "audio", "video", "file"
:raises ValueError: if message can not be prepared/chat does not exist.
:returns: the resulting :class:`Message` instance
"""
path = as_dc_charpointer(path)
mtype = as_dc_charpointer(mime_type)
msg = Message.new(self._dc_context, view_type)
msg.set_file(path, mtype)
msg_id = lib.dc_prepare_msg(self._dc_context, self.id, msg._dc_msg)
if msg_id == 0:
raise ValueError("message could not be prepared, does chat exist?")
return Message.from_db(self._dc_context, msg_id)
msg = Message.new_empty(self.account, view_type)
msg.set_file(path, mime_type)
return self.prepare_message(msg)
def send_prepared(self, message):
""" send a previously prepared message.
@@ -186,12 +205,42 @@ class Chat(object):
:param message: a :class:`Message` instance previously returned by
:meth:`prepare_file`.
:raises ValueError: if message can not be sent.
:returns: a :class:`deltachat.message.Message` instance with updated state
:returns: a :class:`deltachat.message.Message` instance as sent out.
"""
msg_id = lib.dc_send_msg(self._dc_context, 0, message._dc_msg)
if msg_id == 0:
assert message.id != 0 and message.is_out_preparing()
# get a fresh copy of dc_msg, the core needs it
msg = Message.from_db(self.account, message.id)
# pass 0 as chat-id because core-docs say it's ok when out-preparing
sent_id = lib.dc_send_msg(self._dc_context, 0, msg._dc_msg)
if sent_id == 0:
raise ValueError("message could not be sent")
return Message.from_db(self._dc_context, msg_id)
assert sent_id == msg.id
# modify message in place to avoid bad state for the caller
msg._dc_msg = Message.from_db(self.account, sent_id)._dc_msg
def set_draft(self, message):
""" set message as draft.
:param message: a :class:`Message` instance
:returns: None
"""
if message is None:
lib.dc_set_draft(self._dc_context, self.id, ffi.NULL)
else:
lib.dc_set_draft(self._dc_context, self.id, message._dc_msg)
def get_draft(self):
""" get draft message for this chat.
:param message: a :class:`Message` instance
:returns: Message object or None (if no draft available)
"""
x = lib.dc_get_draft(self._dc_context, self.id)
if x == ffi.NULL:
return None
dc_msg = ffi.gc(x, lib.dc_msg_unref)
return Message(self.account, dc_msg)
def get_messages(self):
""" return list of messages in this chat.
@@ -202,7 +251,7 @@ class Chat(object):
lib.dc_get_chat_msgs(self._dc_context, self.id, 0, 0),
lib.dc_array_unref
)
return list(iter_array(dc_array, lambda x: Message.from_db(self._dc_context, x)))
return list(iter_array(dc_array, lambda x: Message.from_db(self.account, x)))
def count_fresh_messages(self):
""" return number of fresh messages in this chat.

View File

@@ -16,4 +16,4 @@ def iter_array(dc_array_t, constructor):
def from_dc_charpointer(obj):
return ffi.string(obj).decode("utf8")
return ffi.string(ffi.gc(obj, lib.dc_str_unref)).decode("utf8")

View File

@@ -1,59 +1,55 @@
""" chatting related objects: Contact, Chat, Message. """
import os
import shutil
from . import props
from .cutil import from_dc_charpointer, as_dc_charpointer
from .capi import lib, ffi
from . import const
from datetime import datetime
import attr
from attr import validators as v
@attr.s
class Message(object):
""" Message object.
You obtain instances of it through :class:`deltachat.account.Account` or
:class:`deltachat.chatting.Chat`.
"""
_dc_context = attr.ib(validator=v.instance_of(ffi.CData))
try:
id = attr.ib(validator=v.instance_of((int, long)))
except NameError: # py35
id = attr.ib(validator=v.instance_of(int))
def __init__(self, account, dc_msg):
self.account = account
self._dc_context = account._dc_context
assert isinstance(self._dc_context, ffi.CData)
assert isinstance(dc_msg, ffi.CData)
assert dc_msg != ffi.NULL
self._dc_msg = dc_msg
self.id = lib.dc_msg_get_id(dc_msg)
assert self.id is not None and self.id >= 0, repr(self.id)
@property
def _dc_msg(self):
if self.id > 0:
return ffi.gc(
lib.dc_get_msg(self._dc_context, self.id),
lib.dc_msg_unref
)
return self._dc_msg_volatile
def __eq__(self, other):
return self.account == other.account and self.id == other.id
def __repr__(self):
return "<Message id={} dc_context={}>".format(self.id, self._dc_context)
@classmethod
def from_db(cls, _dc_context, id):
def from_db(cls, account, id):
assert id > 0
return cls(_dc_context, id)
return cls(account, ffi.gc(
lib.dc_get_msg(account._dc_context, id),
lib.dc_msg_unref
))
@classmethod
def new(cls, dc_context, view_type):
""" create a non-persistent method. """
msg = cls(dc_context, 0)
view_type_code = MessageType.get_typecode(view_type)
msg._dc_msg_volatile = ffi.gc(
lib.dc_msg_new(dc_context, view_type_code),
lib.dc_msg_unref
)
return msg
def new_empty(cls, account, view_type):
""" create a non-persistent message.
def get_state(self):
""" get the message in/out state.
:returns: :class:`deltachat.message.MessageState`
:param: view_type is "text", "audio", "video", "file"
"""
return MessageState(self)
view_type_code = get_viewtype_code_from_name(view_type)
return Message(account, ffi.gc(
lib.dc_msg_new(account._dc_context, view_type_code),
lib.dc_msg_unref
))
@props.with_doc
def text(self):
@@ -62,7 +58,9 @@ class Message(object):
def set_text(self, text):
"""set text of this message. """
return lib.dc_msg_set_text(self._dc_msg, as_dc_charpointer(text))
assert self.id > 0, "message not prepared"
assert self.is_out_preparing()
lib.dc_msg_set_text(self._dc_msg, as_dc_charpointer(text))
@props.with_doc
def filename(self):
@@ -70,9 +68,23 @@ class Message(object):
return from_dc_charpointer(lib.dc_msg_get_file(self._dc_msg))
def set_file(self, path, mime_type=None):
"""set file for this message. """
mtype = ffi.NULL if mime_type is None else mime_type
assert os.path.exists(path)
"""set file for this message from path and mime_type. """
mtype = ffi.NULL if mime_type is None else as_dc_charpointer(mime_type)
if not os.path.exists(path):
raise ValueError("path does not exist: {!r}".format(path))
blobdir = self.account.get_blobdir()
if not path.startswith(blobdir):
for i in range(50):
ext = "" if i == 0 else "-" + str(i)
dest = os.path.join(blobdir, os.path.basename(path) + ext)
if os.path.exists(dest):
continue
shutil.copyfile(path, dest)
break
else:
raise ValueError("could not create blobdir-path for {}".format(path))
path = dest
assert path.startswith(blobdir), path
lib.dc_msg_set_file(self._dc_msg, as_dc_charpointer(path), mtype)
@props.with_doc
@@ -85,18 +97,17 @@ class Message(object):
"""mime type of the file (if it exists)"""
return from_dc_charpointer(lib.dc_msg_get_filemime(self._dc_msg))
@props.with_doc
def view_type(self):
"""the view type of this message.
:returns: a :class:`deltachat.message.MessageType` instance.
"""
return MessageType(lib.dc_msg_get_viewtype(self._dc_msg))
def is_setup_message(self):
""" return True if this message is a setup message. """
return lib.dc_msg_is_setupmessage(self._dc_msg)
def get_message_info(self):
""" Return informational text for a single message.
The text is multiline and may contain eg. the raw text of the message.
"""
return from_dc_charpointer(lib.dc_get_msg_info(self._dc_context, self.id))
def continue_key_transfer(self, setup_code):
""" extract key and use it as primary key for this account. """
lib.dc_continue_key_transfer(self._dc_context, self.id, as_dc_charpointer(setup_code))
@@ -131,7 +142,7 @@ class Message(object):
import email.parser
mime_headers = lib.dc_get_mime_headers(self._dc_context, self.id)
if mime_headers:
s = ffi.string(mime_headers)
s = ffi.string(ffi.gc(mime_headers, lib.dc_str_unref))
if isinstance(s, bytes):
s = s.decode("ascii")
return email.message_from_string(s)
@@ -144,7 +155,7 @@ class Message(object):
"""
from .chatting import Chat
chat_id = lib.dc_msg_get_chat_id(self._dc_msg)
return Chat(self._dc_context, chat_id)
return Chat(self.account, chat_id)
def get_sender_contact(self):
"""return the contact of who wrote the message.
@@ -155,66 +166,20 @@ class Message(object):
contact_id = lib.dc_msg_get_from_id(self._dc_msg)
return Contact(self._dc_context, contact_id)
@attr.s
class MessageType(object):
""" DeltaChat message type, with is_* methods. """
_type = attr.ib(validator=v.instance_of(int))
_mapping = {
const.DC_MSG_TEXT: 'text',
const.DC_MSG_IMAGE: 'image',
const.DC_MSG_GIF: 'gif',
const.DC_MSG_AUDIO: 'audio',
const.DC_MSG_VIDEO: 'video',
const.DC_MSG_FILE: 'file'
}
@classmethod
def get_typecode(cls, view_type):
for code, value in cls._mapping.items():
if value == view_type:
return code
raise ValueError("message typecode not found for {!r}".format(view_type))
@props.with_doc
def name(self):
""" human readable type name. """
return self._mapping.get(self._type, "")
def is_text(self):
""" return True if it's a text message. """
return self._type == const.DC_MSG_TEXT
def is_image(self):
""" return True if it's an image message. """
return self._type == const.DC_MSG_IMAGE
def is_gif(self):
""" return True if it's a gif message. """
return self._type == const.DC_MSG_GIF
def is_audio(self):
""" return True if it's an audio message. """
return self._type == const.DC_MSG_AUDIO
def is_video(self):
""" return True if it's a video message. """
return self._type == const.DC_MSG_VIDEO
def is_file(self):
""" return True if it's a file message. """
return self._type == const.DC_MSG_FILE
@attr.s
class MessageState(object):
""" Current Message In/Out state, updated on each call of is_* methods.
"""
message = attr.ib(validator=v.instance_of(Message))
#
# Message State query methods
#
@property
def _msgstate(self):
return lib.dc_msg_get_state(self.message._dc_msg)
if self.id == 0:
dc_msg = self.message._dc_msg
else:
# load message from db to get a fresh/current state
dc_msg = ffi.gc(
lib.dc_get_msg(self._dc_context, self.id),
lib.dc_msg_unref
)
return lib.dc_msg_get_state(dc_msg)
def is_in_fresh(self):
""" return True if Message is incoming fresh message (un-noticed).
@@ -268,3 +233,56 @@ class MessageState(object):
state, you'll receive the event DC_EVENT_MSG_READ.
"""
return self._msgstate == const.DC_STATE_OUT_MDN_RCVD
#
# Message type query methods
#
@property
def _view_type(self):
assert self.id > 0
return lib.dc_msg_get_viewtype(self._dc_msg)
def is_text(self):
""" return True if it's a text message. """
return self._view_type == const.DC_MSG_TEXT
def is_image(self):
""" return True if it's an image message. """
return self._view_type == const.DC_MSG_IMAGE
def is_gif(self):
""" return True if it's a gif message. """
return self._view_type == const.DC_MSG_GIF
def is_audio(self):
""" return True if it's an audio message. """
return self._view_type == const.DC_MSG_AUDIO
def is_video(self):
""" return True if it's a video message. """
return self._view_type == const.DC_MSG_VIDEO
def is_file(self):
""" return True if it's a file message. """
return self._view_type == const.DC_MSG_FILE
# some code for handling DC_MSG_* view types
_view_type_mapping = {
const.DC_MSG_TEXT: 'text',
const.DC_MSG_IMAGE: 'image',
const.DC_MSG_GIF: 'gif',
const.DC_MSG_AUDIO: 'audio',
const.DC_MSG_VIDEO: 'video',
const.DC_MSG_FILE: 'file'
}
def get_viewtype_code_from_name(view_type_name):
for code, value in _view_type_mapping.items():
if value == view_type_name:
return code
raise ValueError("message typecode not found for {!r}, "
"available {!r}".format(view_type_name, list(_view_type_mapping.values())))

View File

@@ -1,9 +1,9 @@
from __future__ import print_function
import os
import pytest
import requests
import time
from deltachat import Account
from deltachat import props
from deltachat.capi import lib
import tempfile
@@ -16,17 +16,28 @@ def pytest_addoption(parser):
)
def pytest_configure(config):
cfg = config.getoption('--liveconfig')
if not cfg:
cfg = os.getenv('DCC_PY_LIVECONFIG')
if cfg:
config.option.liveconfig = cfg
@pytest.hookimpl(trylast=True)
def pytest_runtest_call(item):
# perform early finalization because we otherwise get cloberred
# output from concurrent threads printing between execution
# of the test function and the teardown phase of that test function
if "acfactory" in item.funcargs:
print("*"*30, "finalizing", "*"*30)
acfactory = item.funcargs["acfactory"]
acfactory.finalize()
def pytest_report_header(config, startdir):
summary = []
t = tempfile.mktemp()
try:
ac = Account(t, eventlogging=False)
@@ -34,10 +45,19 @@ def pytest_report_header(config, startdir):
ac.shutdown()
finally:
os.remove(t)
return "Deltachat core={} sqlite={}".format(
info['deltachat_core_version'],
info['sqlite_version'],
)
summary.extend(['Deltachat core={} sqlite={}'.format(
info['deltachat_core_version'],
info['sqlite_version'],
)])
cfg = config.option.liveconfig
if cfg:
if "#" in cfg:
url, token = cfg.split("#", 1)
summary.append('Liveconfig provider: {}#<token ommitted>'.format(url))
else:
summary.append('Liveconfig file: {}'.format(cfg))
return summary
@pytest.fixture(scope="session")
@@ -53,9 +73,56 @@ def data():
return Data()
class SessionLiveConfigFromFile:
def __init__(self, fn):
self.fn = fn
self.configlist = []
for line in open(fn):
if line.strip() and not line.strip().startswith('#'):
d = {}
for part in line.split():
name, value = part.split("=")
d[name] = value
self.configlist.append(d)
def get(self, index):
return self.configlist[index]
def exists(self):
return bool(self.configlist)
class SessionLiveConfigFromURL:
def __init__(self, url, create_token):
self.configlist = []
for i in range(2):
res = requests.post(url, json={"token_create_user": int(create_token)})
if res.status_code != 200:
pytest.skip("creating newtmpuser failed {!r}".format(res))
d = res.json()
config = dict(addr=d["email"], mail_pw=d["password"])
self.configlist.append(config)
def get(self, index):
return self.configlist[index]
def exists(self):
return bool(self.configlist)
@pytest.fixture(scope="session")
def session_liveconfig(request):
liveconfig_opt = request.config.option.liveconfig
if liveconfig_opt:
if liveconfig_opt.startswith("http"):
url, create_token = liveconfig_opt.split("#", 1)
return SessionLiveConfigFromURL(url, create_token)
else:
return SessionLiveConfigFromFile(liveconfig_opt)
@pytest.fixture
def acfactory(pytestconfig, tmpdir, request):
fn = pytestconfig.getoption("--liveconfig")
def acfactory(pytestconfig, tmpdir, request, session_liveconfig):
class AccountMaker:
def __init__(self):
@@ -69,18 +136,6 @@ def acfactory(pytestconfig, tmpdir, request):
fin = self._finalizers.pop()
fin()
@props.cached
def configlist(self):
configlist = []
for line in open(fn):
if line.strip():
d = {}
for part in line.split():
name, value = part.split("=")
d[name] = value
configlist.append(d)
return configlist
def get_unconfigured_account(self):
self.offline_count += 1
tmpdb = tmpdir.join("offlinedb%d" % self.offline_count)
@@ -103,10 +158,12 @@ def acfactory(pytestconfig, tmpdir, request):
return ac
def get_online_configuring_account(self):
if not fn:
pytest.skip("specify a --liveconfig file to run tests with real accounts")
if not session_liveconfig:
pytest.skip("specify DCC_PY_LIVECONFIG or --liveconfig")
configdict = session_liveconfig.get(self.live_count)
self.live_count += 1
configdict = self.configlist.pop(0)
if "e2ee_enabled" not in configdict:
configdict["e2ee_enabled"] = "1"
tmpdb = tmpdir.join("livedb%d" % self.live_count)
ac = Account(tmpdb.strpath, logid="ac{}".format(self.live_count))
ac._evlogger.init_time = self.init_time

View File

@@ -1,12 +1,19 @@
from __future__ import print_function
import pytest
import os
from deltachat import const
from deltachat import const, Account
from deltachat.message import Message
from datetime import datetime, timedelta
from conftest import wait_configuration_progress, wait_successful_IMAP_SMTP_connection
class TestOfflineAccount:
class TestOfflineAccountBasic:
def test_wrong_db(self, tmpdir):
p = tmpdir.join("hello.db")
p.write("123")
with pytest.raises(ValueError):
Account(p.strpath)
def test_getinfo(self, acfactory):
ac1 = acfactory.get_unconfigured_account()
d = ac1.get_info()
@@ -51,16 +58,22 @@ class TestOfflineAccount:
with pytest.raises(KeyError):
ac1.get_config("123123")
class TestOfflineContact:
def test_contact_attr(self, acfactory):
ac1 = acfactory.get_configured_offline_account()
contact1 = ac1.create_contact(email="some1@hello.com", name="some1")
contact2 = ac1.create_contact(email="some1@hello.com", name="some1")
str(contact1)
repr(contact1)
assert contact1 == contact2
assert contact1.id
assert contact1.addr == "some1@hello.com"
assert contact1.display_name == "some1"
assert not contact1.is_blocked()
assert not contact1.is_verified()
def test_get_contacts(self, acfactory):
def test_get_contacts_and_delete(self, acfactory):
ac1 = acfactory.get_configured_offline_account()
contact1 = ac1.create_contact(email="some1@hello.com", name="some1")
contacts = ac1.get_contacts()
@@ -73,26 +86,48 @@ class TestOfflineAccount:
contacts = ac1.get_contacts(with_self=True)
assert len(contacts) == 2
def test_chat(self, acfactory):
assert ac1.delete_contact(contact1)
assert contact1 not in ac1.get_contacts()
def test_get_contacts_and_delete_fails(self, acfactory):
ac1 = acfactory.get_configured_offline_account()
contact1 = ac1.create_contact(email="some1@example.com", name="some1")
chat = ac1.create_chat_by_contact(contact1)
chat.send_text("one messae")
assert not ac1.delete_contact(contact1)
class TestOfflineChat:
@pytest.fixture
def ac1(self, acfactory):
return acfactory.get_configured_offline_account()
@pytest.fixture
def chat1(self, ac1):
contact1 = ac1.create_contact("some1@hello.com", name="some1")
chat = ac1.create_chat_by_contact(contact1)
assert chat.id >= const.DC_CHAT_ID_LAST_SPECIAL, chat.id
return chat
def test_display(self, chat1):
str(chat1)
repr(chat1)
def test_chat_idempotent(self, chat1, ac1):
contact1 = chat1.get_contacts()[0]
chat2 = ac1.create_chat_by_contact(contact1.id)
assert chat2.id == chat.id
assert chat2.get_name() == chat.get_name()
assert chat == chat2
assert not (chat != chat2)
assert chat2.id == chat1.id
assert chat2.get_name() == chat1.get_name()
assert chat1 == chat2
assert not (chat1 != chat2)
for ichat in ac1.get_chats():
if ichat.id == chat.id:
if ichat.id == chat1.id:
break
else:
pytest.fail("could not find chat")
def test_group_chat_creation(self, acfactory):
ac1 = acfactory.get_configured_offline_account()
def test_group_chat_creation(self, ac1):
contact1 = ac1.create_contact("some1@hello.com", name="some1")
contact2 = ac1.create_contact("some2@hello.com", name="some2")
chat = ac1.create_group_chat(name="title1")
@@ -105,64 +140,73 @@ class TestOfflineAccount:
chat.set_name("title2")
assert chat.get_name() == "title2"
def test_delete_and_send_fails(self, acfactory):
ac1 = acfactory.get_configured_offline_account()
contact1 = ac1.create_contact("some1@hello.com", name="some1")
chat = ac1.create_chat_by_contact(contact1)
chat.delete()
def test_delete_and_send_fails(self, ac1, chat1):
chat1.delete()
ac1._evlogger.get_matching("DC_EVENT_MSGS_CHANGED")
with pytest.raises(ValueError):
chat.send_text("msg1")
chat1.send_text("msg1")
def test_create_message(self, acfactory):
ac1 = acfactory.get_configured_offline_account()
message = ac1.create_message("text")
assert message.id == 0
assert message._dc_msg is message._dc_msg
message.set_text("hello")
assert message.text == "hello"
assert message.id == 0
def test_prepare_message_and_send(self, ac1, chat1):
msg = chat1.prepare_message(Message.new_empty(chat1.account, "text"))
msg.set_text("hello world")
assert msg.text == "hello world"
assert msg.id > 0
chat1.send_prepared(msg)
assert "Sent" in msg.get_message_info()
str(msg)
repr(msg)
assert msg == ac1.get_message_by_id(msg.id)
def test_message(self, acfactory):
ac1 = acfactory.get_configured_offline_account()
contact1 = ac1.create_contact("some1@hello.com", name="some1")
chat = ac1.create_chat_by_contact(contact1)
msg = chat.send_text("msg1")
def test_prepare_file(self, ac1, chat1):
blobdir = ac1.get_blobdir()
p = os.path.join(blobdir, "somedata.txt")
with open(p, "w") as f:
f.write("some data")
message = chat1.prepare_message_file(p)
assert message.id > 0
message.set_text("hello world")
assert message.is_out_preparing()
assert message.text == "hello world"
chat1.send_prepared(message)
assert "Sent" in message.get_message_info()
def test_message_eq_contains(self, chat1):
msg = chat1.send_text("msg1")
assert msg in chat1.get_messages()
assert not (msg not in chat1.get_messages())
str(msg)
repr(msg)
def test_message_send_text(self, chat1):
msg = chat1.send_text("msg1")
assert msg
assert msg.view_type.is_text()
assert msg.view_type.name == "text"
assert not msg.view_type.is_audio()
assert not msg.view_type.is_video()
assert not msg.view_type.is_gif()
assert not msg.view_type.is_file()
assert not msg.view_type.is_image()
msg_state = msg.get_state()
assert not msg_state.is_in_fresh()
assert not msg_state.is_in_noticed()
assert not msg_state.is_in_seen()
assert msg_state.is_out_pending()
assert not msg_state.is_out_failed()
assert not msg_state.is_out_delivered()
assert not msg_state.is_out_mdn_received()
assert msg.is_text()
assert not msg.is_audio()
assert not msg.is_video()
assert not msg.is_gif()
assert not msg.is_file()
assert not msg.is_image()
def test_create_chat_by_mssage_id(self, acfactory):
ac1 = acfactory.get_configured_offline_account()
contact1 = ac1.create_contact("some1@hello.com", name="some1")
chat = ac1.create_chat_by_contact(contact1)
msg = chat.send_text("msg1")
assert chat == ac1.create_chat_by_message(msg)
assert chat == ac1.create_chat_by_message(msg.id)
assert not msg.is_in_fresh()
assert not msg.is_in_noticed()
assert not msg.is_in_seen()
assert msg.is_out_pending()
assert not msg.is_out_failed()
assert not msg.is_out_delivered()
assert not msg.is_out_mdn_received()
def test_message_image(self, acfactory, data, lp):
ac1 = acfactory.get_configured_offline_account()
contact1 = ac1.create_contact("some1@hello.com", name="some1")
chat = ac1.create_chat_by_contact(contact1)
def test_create_chat_by_message_id(self, ac1, chat1):
msg = chat1.send_text("msg1")
assert chat1 == ac1.create_chat_by_message(msg)
assert chat1 == ac1.create_chat_by_message(msg.id)
def test_message_image(self, chat1, data, lp):
with pytest.raises(ValueError):
chat.send_image(path="notexists")
chat1.send_image(path="notexists")
fn = data.get_path("d.png")
lp.sec("sending image")
msg = chat.send_image(fn)
assert msg.view_type.name == "image"
msg = chat1.send_image(fn)
assert msg.is_image()
assert msg
assert msg.id > 0
assert os.path.exists(msg.filename)
@@ -173,20 +217,19 @@ class TestOfflineAccount:
("text/plain", "text/plain"),
("image/png", "image/png"),
])
def test_message_file(self, acfactory, data, lp, typein, typeout):
ac1 = acfactory.get_configured_offline_account()
contact1 = ac1.create_contact("some1@hello.com", name="some1")
chat = ac1.create_chat_by_contact(contact1)
def test_message_file(self, ac1, chat1, data, lp, typein, typeout):
lp.sec("sending file")
fn = data.get_path("r.txt")
msg = chat.send_file(fn, typein)
msg = chat1.send_file(fn, typein)
assert msg
assert msg.id > 0
assert msg.view_type.name == "file"
assert msg.view_type.is_file()
assert msg.is_file()
assert os.path.exists(msg.filename)
assert msg.filename.endswith(msg.basename)
assert msg.filemime == typeout
msg2 = chat1.send_file(fn, typein)
assert msg2 != msg
assert msg2.filename != msg.filename
def test_create_chat_mismatch(self, acfactory):
ac1 = acfactory.get_configured_offline_account()
@@ -199,12 +242,9 @@ class TestOfflineAccount:
with pytest.raises(ValueError):
ac2.create_chat_by_message(msg)
def test_chat_message_distinctions(self, acfactory):
ac1 = acfactory.get_configured_offline_account()
contact1 = ac1.create_contact("some1@hello.com", name="some1")
chat = ac1.create_chat_by_contact(contact1)
def test_chat_message_distinctions(self, ac1, chat1):
past1s = datetime.utcnow() - timedelta(seconds=1)
msg = chat.send_text("msg1")
msg = chat1.send_text("msg1")
ts = msg.time_sent
assert msg.time_received is None
assert ts.strftime("Y")
@@ -212,8 +252,7 @@ class TestOfflineAccount:
contact = msg.get_sender_contact()
assert contact == ac1.get_self_contact()
def test_basic_configure_ok_addr_setting_forbidden(self, acfactory):
ac1 = acfactory.get_configured_offline_account()
def test_basic_configure_ok_addr_setting_forbidden(self, ac1):
assert ac1.get_config("mail_pw")
assert ac1.is_configured()
with pytest.raises(ValueError):
@@ -252,11 +291,21 @@ class TestOfflineAccount:
assert messages[0].text == "msg1"
assert os.path.exists(messages[1].filename)
def test_ac_setup_message_fails(self, acfactory):
ac1 = acfactory.get_configured_offline_account()
def test_ac_setup_message_fails(self, ac1):
with pytest.raises(RuntimeError):
ac1.initiate_key_transfer()
def test_set_get_draft(self, chat1):
msg = Message.new_empty(chat1.account, "text")
msg1 = chat1.prepare_message(msg)
msg1.set_text("hello")
chat1.set_draft(msg1)
msg1.set_text("obsolete")
msg2 = chat1.get_draft()
assert msg2.text == "hello"
chat1.set_draft(None)
assert chat1.get_draft() is None
class TestOnlineAccount:
def test_one_account_init(self, acfactory):
@@ -277,7 +326,7 @@ class TestOnlineAccount:
ev = ac1._evlogger.get_matching("DC_EVENT_INCOMING_MSG|DC_EVENT_MSGS_CHANGED")
assert ev[1] == msg_out.id
def test_two_acocunts_send_receive(self, acfactory):
def test_two_accounts_send_receive(self, acfactory):
ac1 = acfactory.get_online_configuring_account()
ac2 = acfactory.get_online_configuring_account()
c2 = ac1.create_contact(email=ac2.get_config("addr"))
@@ -345,7 +394,7 @@ class TestOnlineAccount:
evt_name, data1, data2 = ev
assert data1 == chat.id
assert data2 == msg_out.id
assert msg_out.get_state().is_out_delivered()
assert msg_out.is_out_delivered()
lp.sec("wait for ac2 to receive message")
ev = ac2._evlogger.get_matching("DC_EVENT_MSGS_CHANGED")
@@ -372,10 +421,42 @@ class TestOnlineAccount:
lp.sec("mark message as seen on ac2, wait for changes on ac1")
ac2.mark_seen_messages([msg_in])
lp.step("1")
ac1._evlogger.get_matching("DC_EVENT_MSG_READ")
ev = ac1._evlogger.get_matching("DC_EVENT_MSG_READ")
assert ev[1] >= const.DC_CHAT_ID_LAST_SPECIAL
assert ev[2] >= const.DC_MSG_ID_LAST_SPECIAL
lp.step("2")
# ac1._evlogger.get_info_matching("Message marked as seen")
assert msg_out.get_state().is_out_mdn_received()
assert msg_out.is_out_mdn_received()
def test_send_and_receive_will_encrypt_decrypt(self, acfactory, lp):
lp.sec("starting accounts, waiting for configuration")
ac1 = acfactory.get_online_configuring_account()
ac2 = acfactory.get_online_configuring_account()
c2 = ac1.create_contact(email=ac2.get_config("addr"))
chat = ac1.create_chat_by_contact(c2)
assert chat.id >= const.DC_CHAT_ID_LAST_SPECIAL
wait_configuration_progress(ac1, 1000)
wait_configuration_progress(ac2, 1000)
lp.sec("sending text message from ac1 to ac2")
msg_out = chat.send_text("message1")
lp.sec("wait for ac2 to receive message")
ev = ac2._evlogger.get_matching("DC_EVENT_MSGS_CHANGED")
assert ev[2] == msg_out.id
msg_in = ac2.get_message_by_id(msg_out.id)
assert msg_in.text == "message1"
lp.sec("create new chat with contact and send back (encrypted) message")
chat2b = ac2.create_chat_by_message(msg_in)
chat2b.send_text("message-back")
lp.sec("wait for ac1 to receive message")
ev = ac1._evlogger.get_matching("DC_EVENT_INCOMING_MSG")
assert ev[1] == chat.id
assert ev[2] > msg_out.id
msg_back = ac1.get_message_by_id(ev[2])
assert msg_back.text == "message-back"
def test_saved_mime_on_received_message(self, acfactory, lp):
lp.sec("starting accounts, waiting for configuration")
@@ -415,13 +496,13 @@ class TestOnlineAccount:
evt_name, data1, data2 = ev
assert data1 == chat.id
assert data2 == msg_out.id
assert msg_out.get_state().is_out_delivered()
assert msg_out.is_out_delivered()
lp.sec("wait for ac2 to receive message")
ev = ac2._evlogger.get_matching("DC_EVENT_MSGS_CHANGED")
assert ev[2] == msg_out.id
msg_in = ac2.get_message_by_id(msg_out.id)
assert msg_in.view_type.is_image()
assert msg_in.is_image()
assert os.path.exists(msg_in.filename)
assert os.stat(msg_in.filename).st_size == os.stat(path).st_size
@@ -457,7 +538,7 @@ class TestOnlineAccount:
wait_configuration_progress(ac1, 1000)
assert ac1.get_info()["fingerprint"] != ac2.get_info()["fingerprint"]
setup_code = ac1.initiate_key_transfer()
ac2._evlogger.set_timeout(10)
ac2._evlogger.set_timeout(30)
ev = ac2._evlogger.get_matching("DC_EVENT_INCOMING_MSG|DC_EVENT_MSGS_CHANGED")
msg = ac2.get_message_by_id(ev[2])
assert msg.is_setup_message()

View File

@@ -1,6 +1,4 @@
from __future__ import print_function
import os
import shutil
from filecmp import cmp
from deltachat import const
from conftest import wait_configuration_progress, wait_msgs_changed
@@ -13,18 +11,15 @@ class TestInCreation:
wait_configuration_progress(ac1, 1000)
wait_configuration_progress(ac2, 1000)
blobdir = ac1.get_blobdir()
c2 = ac1.create_contact(email=ac2.get_config("addr"))
chat = ac1.create_chat_by_contact(c2)
assert chat.id >= const.DC_CHAT_ID_LAST_SPECIAL
wait_msgs_changed(ac1, 0, 0) # why no chat id?
lp.sec("create a message with a file in creation")
path = os.path.join(blobdir, "d.png")
open(path, 'a').close()
prepared_original = chat.prepare_file(path)
assert prepared_original.get_state().is_out_preparing()
path = data.get_path("d.png")
prepared_original = chat.prepare_message_file(path)
assert prepared_original.is_out_preparing()
wait_msgs_changed(ac1, chat.id, prepared_original.id)
lp.sec("forward the message while still in creation")
@@ -32,36 +27,41 @@ class TestInCreation:
chat2.add_contact(c2)
wait_msgs_changed(ac1, 0, 0) # why not chat id?
ac1.forward_messages([prepared_original], chat2)
# XXX there might be two EVENT_MSGS_CHANGED and only one of them
# is the one caused by forwarding
forwarded_id = wait_msgs_changed(ac1, chat2.id)
if forwarded_id == 0:
forwarded_id = wait_msgs_changed(ac1, chat2.id)
assert forwarded_id
forwarded_msg = ac1.get_message_by_id(forwarded_id)
assert forwarded_msg.get_state().is_out_preparing()
assert forwarded_msg.is_out_preparing()
lp.sec("finish creating the file and send it")
shutil.copy(data.get_path("d.png"), path)
sent_original = chat.send_prepared(prepared_original)
assert sent_original.id == prepared_original.id
state = sent_original.get_state()
assert state.is_out_pending() or state.is_out_delivered()
wait_msgs_changed(ac1, chat.id, sent_original.id)
assert prepared_original.is_out_preparing()
chat.send_prepared(prepared_original)
assert prepared_original.is_out_pending() or prepared_original.is_out_delivered()
wait_msgs_changed(ac1, chat.id, prepared_original.id)
lp.sec("expect the forwarded message to be sent now too")
wait_msgs_changed(ac1, chat2.id, forwarded_id)
state = ac1.get_message_by_id(forwarded_id).get_state()
assert state.is_out_pending() or state.is_out_delivered()
fwd_msg = ac1.get_message_by_id(forwarded_id)
assert fwd_msg.is_out_pending() or fwd_msg.is_out_delivered()
lp.sec("wait for the messages to be delivered to SMTP")
ev = ac1._evlogger.get_matching("DC_EVENT_MSG_DELIVERED")
assert ev[1] == chat.id
assert ev[2] == sent_original.id
assert ev[2] == prepared_original.id
ev = ac1._evlogger.get_matching("DC_EVENT_MSG_DELIVERED")
assert ev[1] == chat2.id
assert ev[2] == forwarded_id
lp.sec("wait for both messages to arrive")
lp.sec("wait1 for original or forwarded messages to arrive")
ev1 = ac2._evlogger.get_matching("DC_EVENT_MSGS_CHANGED")
assert ev1[1] >= const.DC_CHAT_ID_LAST_SPECIAL
received_original = ac2.get_message_by_id(ev1[2])
assert cmp(received_original.filename, path, False)
lp.sec("wait2 for original or forwarded messages to arrive")
ev2 = ac2._evlogger.get_matching("DC_EVENT_MSGS_CHANGED")
assert ev2[1] >= const.DC_CHAT_ID_LAST_SPECIAL
assert ev2[1] != ev1[1]

View File

@@ -1,7 +1,7 @@
from __future__ import print_function
import pytest
from deltachat import capi, Account, const, set_context_callback, clear_context_callback
from deltachat import capi, const, set_context_callback, clear_context_callback
from deltachat.capi import ffi
from deltachat.capi import lib
from deltachat.account import EventLogger
@@ -23,17 +23,32 @@ def test_dc_close_events():
evlog.set_timeout(5)
set_context_callback(ctx, lambda ctx, evt_name, data1, data2: evlog(evt_name, data1, data2))
capi.lib.dc_close(ctx)
# test that we get events from dc_close
print(evlog.get_matching("DC_EVENT_INFO", check_error=False))
print(evlog.get_matching("DC_EVENT_INFO", check_error=False))
print(evlog.get_matching("DC_EVENT_INFO", check_error=False))
print(evlog.get_matching("DC_EVENT_INFO", check_error=False))
def find(info_string):
while 1:
ev = evlog.get_matching("DC_EVENT_INFO", check_error=False)
data2 = ev[2]
if info_string in data2:
return
else:
print("skipping event", *ev)
find("disconnecting INBOX-watch")
find("disconnecting sentbox-thread")
find("disconnecting mvbox-thread")
find("disconnecting SMTP")
find("Database closed")
def test_wrong_db(tmpdir):
tmpdir.join("hello.db").write("123")
with pytest.raises(ValueError):
Account(db_path=tmpdir.strpath)
dc_context = ffi.gc(
lib.dc_context_new(lib.py_dc_callback, ffi.NULL, ffi.NULL),
lib.dc_context_unref,
)
p = tmpdir.join("hello.db")
# write an invalid database file
p.write("x123" * 10)
assert not lib.dc_open(dc_context, p.strpath.encode("ascii"), ffi.NULL)
def test_event_defines():
@@ -49,3 +64,14 @@ def test_sig():
assert sig(const.DC_EVENT_SMTP_CONNECTED) == 2
assert sig(const.DC_EVENT_IMAP_CONNECTED) == 2
assert sig(const.DC_EVENT_SMTP_MESSAGE_SENT) == 2
def test_markseen_invalid_message_ids(acfactory):
ac1 = acfactory.get_configured_offline_account()
contact1 = ac1.create_contact(email="some1@example.com", name="some1")
chat = ac1.create_chat_by_contact(contact1)
chat.send_text("one messae")
ac1._evlogger.get_matching("DC_EVENT_MSGS_CHANGED")
msg_ids = [9]
lib.dc_markseen_msgs(ac1._dc_context, msg_ids, len(msg_ids))
ac1._evlogger.ensure_event_not_queued("DC_EVENT_WARNING|DC_EVENT_ERROR")

View File

@@ -1,22 +1,24 @@
[tox]
# make sure to update environment list in travis.yml and appveyor.yml
envlist =
py27
py35
lint
auditwheels
[testenv]
commands =
pytest -rsXx {posargs:tests}
pytest -v -rsXx {posargs:tests}
python tests/package_wheels.py {toxworkdir}/wheelhouse
passenv =
TRAVIS
DCC_RS_DEV
DCC_RS_TARGET
DCC_PY_LIVECONFIG
deps =
pytest
pytest-faulthandler
pdbpp
requests
[testenv:auditwheels]
skipsdist = True
@@ -49,6 +51,7 @@ commands =
[pytest]
addopts = -v -rs
python_files = tests/test_*.py
norecursedirs = .tox
xfail_strict=true

3
release.toml Normal file
View File

@@ -0,0 +1,3 @@
pre-release-commit-message = "chore({{crate_name}}): release {{version}}"
pro-release-commit-message = "chore({{crate_name}}): starting development cycle for {{next_version}}"
no-dev-version = true

View File

@@ -23,11 +23,10 @@ if [ $? != 0 ]; then
fi
pushd python
toxargs="$@"
if [ -e liveconfig ]; then
toxargs="--liveconfig liveconfig $@"
if [ -e "./liveconfig" && -z "$DCC_PY_LIVECONFIG" ]; then
export DCC_PY_LIVECONFIG=liveconfig
fi
tox $toxargs
tox "$@"
ret=$?
popd
exit $ret

View File

@@ -1 +1 @@
nightly-2019-07-10
nightly-2019-08-13

View File

@@ -6,7 +6,7 @@ use std::{fmt, str};
use mmime::mailimf_types::*;
use crate::constants::*;
use crate::dc_contact::*;
use crate::contact::*;
use crate::dc_tools::as_str;
use crate::key::*;
@@ -94,7 +94,7 @@ impl Aheader {
match Self::from_str(value) {
Ok(test) => {
if dc_addr_cmp(&test.addr, as_str(wanted_from)) {
if addr_cmp(&test.addr, as_str(wanted_from)) {
if fine_header.is_none() {
fine_header = Some(test);
} else {

1990
src/chat.rs Normal file

File diff suppressed because it is too large Load Diff

339
src/chatlist.rs Normal file
View File

@@ -0,0 +1,339 @@
use crate::chat::*;
use crate::constants::*;
use crate::contact::*;
use crate::context::*;
use crate::error::Result;
use crate::lot::Lot;
use crate::message::*;
use crate::stock::StockMessage;
/// An object representing a single chatlist in memory.
///
/// Chatlist objects contain chat IDs and, if possible, message IDs belonging to them.
/// The chatlist object is not updated; if you want an update, you have to recreate the object.
///
/// For a **typical chat overview**, the idea is to get the list of all chats via dc_get_chatlist()
/// without any listflags (see below) and to implement a "virtual list" or so
/// (the count of chats is known by chatlist.len()).
///
/// Only for the items that are in view (the list may have several hundreds chats),
/// the UI should call chatlist.get_summary() then.
/// chatlist.get_summary() provides all elements needed for painting the item.
///
/// On a click of such an item, the UI should change to the chat view
/// and get all messages from this view via dc_get_chat_msgs().
/// Again, a "virtual list" is created (the count of messages is known)
/// and for each messages that is scrolled into view, dc_get_msg() is called then.
///
/// Why no listflags?
/// Without listflags, dc_get_chatlist() adds the deaddrop and the archive "link" automatically as needed.
/// The UI can just render these items differently then. Although the deaddrop link is currently always the
/// first entry and only present on new messages, there is the rough idea that it can be optionally always
/// present and sorted into the list by date. Rendering the deaddrop in the described way
/// would not add extra work in the UI then.
pub struct Chatlist<'a> {
context: &'a Context,
/// Stores pairs of `chat_id, message_id`
ids: Vec<(u32, u32)>,
}
impl<'a> Chatlist<'a> {
pub fn get_context(&self) -> &Context {
self.context
}
/// Get a list of chats.
/// The list can be filtered by query parameters.
///
/// The list is already sorted and starts with the most recent chat in use.
/// The sorting takes care of invalid sending dates, drafts and chats without messages.
/// Clients should not try to re-sort the list as this would be an expensive action
/// and would result in inconsistencies between clients.
///
/// To get information about each entry, use eg. chatlist.get_summary().
///
/// By default, the function adds some special entries to the list.
/// These special entries can be identified by the ID returned by chatlist.get_chat_id():
/// - DC_CHAT_ID_DEADDROP (1) - this special chat is present if there are
/// messages from addresses that have no relationship to the configured account.
/// The last of these messages is represented by DC_CHAT_ID_DEADDROP and you can retrieve details
/// about it with chatlist.get_msg_id(). Typically, the UI asks the user "Do you want to chat with NAME?"
/// and offers the options "Yes" (call dc_create_chat_by_msg_id()), "Never" (call dc_block_contact())
/// or "Not now".
/// The UI can also offer a "Close" button that calls dc_marknoticed_contact() then.
/// - DC_CHAT_ID_ARCHIVED_LINK (6) - this special chat is present if the user has
/// archived _any_ chat using dc_archive_chat(). The UI should show a link as
/// "Show archived chats", if the user clicks this item, the UI should show a
/// list of all archived chats that can be created by this function hen using
/// the DC_GCL_ARCHIVED_ONLY flag.
/// - DC_CHAT_ID_ALLDONE_HINT (7) - this special chat is present
/// if DC_GCL_ADD_ALLDONE_HINT is added to listflags
/// and if there are only archived chats.
///
/// The `listflags` is a combination of flags:
/// - if the flag DC_GCL_ARCHIVED_ONLY is set, only archived chats are returned.
/// if DC_GCL_ARCHIVED_ONLY is not set, only unarchived chats are returned and
/// the pseudo-chat DC_CHAT_ID_ARCHIVED_LINK is added if there are _any_ archived
/// chats
/// - if the flag DC_GCL_NO_SPECIALS is set, deaddrop and archive link are not added
/// to the list (may be used eg. for selecting chats on forwarding, the flag is
/// not needed when DC_GCL_ARCHIVED_ONLY is already set)
/// - if the flag DC_GCL_ADD_ALLDONE_HINT is set, DC_CHAT_ID_ALLDONE_HINT
/// is added as needed.
/// `query`: An optional query for filtering the list. Only chats matching this query
/// are returned.
/// `query_contact_id`: An optional contact ID for filtering the list. Only chats including this contact ID
/// are returned.
pub fn try_load(
context: &'a Context,
listflags: usize,
query: Option<&str>,
query_contact_id: Option<u32>,
) -> Result<Self> {
let mut add_archived_link_item = 0;
// select with left join and minimum:
// - the inner select must use `hidden` and _not_ `m.hidden`
// which would refer the outer select and take a lot of time
// - `GROUP BY` is needed several messages may have the same timestamp
// - the list starts with the newest chats
// nb: the query currently shows messages from blocked contacts in groups.
// however, for normal-groups, this is okay as the message is also returned by dc_get_chat_msgs()
// (otherwise it would be hard to follow conversations, wa and tg do the same)
// for the deaddrop, however, they should really be hidden, however, _currently_ the deaddrop is not
// shown at all permanent in the chatlist.
let process_row = |row: &rusqlite::Row| {
let chat_id: i32 = row.get(0)?;
// TODO: verify that it is okay for this to be Null
let msg_id: i32 = row.get(1).unwrap_or_default();
Ok((chat_id as u32, msg_id as u32))
};
let process_rows = |rows: rusqlite::MappedRows<_>| {
rows.collect::<std::result::Result<Vec<_>, _>>()
.map_err(Into::into)
};
// nb: the query currently shows messages from blocked contacts in groups.
// however, for normal-groups, this is okay as the message is also returned by dc_get_chat_msgs()
// (otherwise it would be hard to follow conversations, wa and tg do the same)
// for the deaddrop, however, they should really be hidden, however, _currently_ the deaddrop is not
// shown at all permanent in the chatlist.
let mut ids = if let Some(query_contact_id) = query_contact_id {
// show chats shared with a given contact
context.sql.query_map(
"SELECT c.id, m.id FROM chats c LEFT JOIN msgs m \
ON c.id=m.chat_id \
AND m.timestamp=( SELECT MAX(timestamp) \
FROM msgs WHERE chat_id=c.id \
AND (hidden=0 OR (hidden=1 AND state=19))) WHERE c.id>9 \
AND c.blocked=0 AND c.id IN(SELECT chat_id FROM chats_contacts WHERE contact_id=?) \
GROUP BY c.id ORDER BY IFNULL(m.timestamp,0) DESC, m.id DESC;",
params![query_contact_id as i32],
process_row,
process_rows,
)?
} else if 0 != listflags & DC_GCL_ARCHIVED_ONLY {
// show archived chats
context.sql.query_map(
"SELECT c.id, m.id FROM chats c LEFT JOIN msgs m \
ON c.id=m.chat_id \
AND m.timestamp=( SELECT MAX(timestamp) \
FROM msgs WHERE chat_id=c.id \
AND (hidden=0 OR (hidden=1 AND state=19))) WHERE c.id>9 \
AND c.blocked=0 AND c.archived=1 GROUP BY c.id \
ORDER BY IFNULL(m.timestamp,0) DESC, m.id DESC;",
params![],
process_row,
process_rows,
)?
} else if let Some(query) = query {
let query = query.trim().to_string();
ensure!(!query.is_empty(), "missing query");
let str_like_cmd = format!("%{}%", query);
context.sql.query_map(
"SELECT c.id, m.id FROM chats c LEFT JOIN msgs m \
ON c.id=m.chat_id \
AND m.timestamp=( SELECT MAX(timestamp) \
FROM msgs WHERE chat_id=c.id \
AND (hidden=0 OR (hidden=1 AND state=19))) WHERE c.id>9 \
AND c.blocked=0 AND c.name LIKE ? \
GROUP BY c.id ORDER BY IFNULL(m.timestamp,0) DESC, m.id DESC;",
params![str_like_cmd],
process_row,
process_rows,
)?
} else {
// show normal chatlist
let mut ids = context.sql.query_map(
"SELECT c.id, m.id FROM chats c \
LEFT JOIN msgs m \
ON c.id=m.chat_id \
AND m.timestamp=( SELECT MAX(timestamp) \
FROM msgs WHERE chat_id=c.id \
AND (hidden=0 OR (hidden=1 AND state=19))) WHERE c.id>9 \
AND c.blocked=0 AND c.archived=0 \
GROUP BY c.id \
ORDER BY IFNULL(m.timestamp,0) DESC, m.id DESC;",
params![],
process_row,
process_rows,
)?;
if 0 == listflags & DC_GCL_NO_SPECIALS {
let last_deaddrop_fresh_msg_id = get_last_deaddrop_fresh_msg(context);
if last_deaddrop_fresh_msg_id > 0 {
ids.push((1, last_deaddrop_fresh_msg_id));
}
add_archived_link_item = 1;
}
ids
};
if 0 != add_archived_link_item && dc_get_archived_cnt(context) > 0 {
if ids.is_empty() && 0 != listflags & DC_GCL_ADD_ALLDONE_HINT {
ids.push((DC_CHAT_ID_ALLDONE_HINT, 0));
}
ids.push((DC_CHAT_ID_ARCHIVED_LINK, 0));
}
Ok(Chatlist { context, ids })
}
/// Find out the number of chats.
pub fn len(&self) -> usize {
self.ids.len()
}
pub fn is_empty(&self) -> bool {
self.ids.is_empty()
}
/// Get a single chat ID of a chatlist.
///
/// To get the message object from the message ID, use dc_get_chat().
pub fn get_chat_id(&self, index: usize) -> u32 {
if index >= self.ids.len() {
return 0;
}
self.ids[index].0
}
/// Get a single message ID of a chatlist.
///
/// To get the message object from the message ID, use dc_get_msg().
pub fn get_msg_id(&self, index: usize) -> u32 {
if index >= self.ids.len() {
return 0;
}
self.ids[index].1
}
/// Get a summary for a chatlist index.
///
/// The summary is returned by a dc_lot_t object with the following fields:
///
/// - dc_lot_t::text1: contains the username or the strings "Me", "Draft" and so on.
/// The string may be colored by having a look at text1_meaning.
/// If there is no such name or it should not be displayed, the element is NULL.
/// - dc_lot_t::text1_meaning: one of DC_TEXT1_USERNAME, DC_TEXT1_SELF or DC_TEXT1_DRAFT.
/// Typically used to show dc_lot_t::text1 with different colors. 0 if not applicable.
/// - dc_lot_t::text2: contains an excerpt of the message text or strings as
/// "No messages". May be NULL of there is no such text (eg. for the archive link)
/// - dc_lot_t::timestamp: the timestamp of the message. 0 if not applicable.
/// - dc_lot_t::state: The state of the message as one of the DC_STATE_* constants (see #dc_msg_get_state()).
// 0 if not applicable.
pub fn get_summary(&self, index: usize, chat: Option<&Chat<'a>>) -> Lot {
// The summary is created by the chat, not by the last message.
// This is because we may want to display drafts here or stuff as
// "is typing".
// Also, sth. as "No messages" would not work if the summary comes from a message.
let mut ret = Lot::new();
if index >= self.ids.len() {
ret.text2 = Some("ErrBadChatlistIndex".to_string());
return ret;
}
let chat_loaded: Chat;
let chat = if let Some(chat) = chat {
chat
} else {
if let Ok(chat) = Chat::load_from_db(self.context, self.ids[index].0) {
chat_loaded = chat;
&chat_loaded
} else {
return ret;
}
};
let lastmsg_id = self.ids[index].1;
let mut lastcontact = None;
let lastmsg = if 0 != lastmsg_id {
if let Ok(lastmsg) = dc_msg_load_from_db(self.context, lastmsg_id) {
if lastmsg.from_id != 1 as libc::c_uint
&& (chat.typ == Chattype::Group || chat.typ == Chattype::VerifiedGroup)
{
lastcontact = Contact::load_from_db(self.context, lastmsg.from_id).ok();
}
Some(lastmsg)
} else {
None
}
} else {
None
};
if chat.id == DC_CHAT_ID_ARCHIVED_LINK {
ret.text2 = None;
} else if lastmsg.is_none() || lastmsg.as_ref().unwrap().from_id == DC_CONTACT_ID_UNDEFINED
{
ret.text2 = Some(self.context.stock_str(StockMessage::NoMessages).to_string());
} else {
ret.fill(
&mut lastmsg.unwrap(),
chat,
lastcontact.as_ref(),
self.context,
);
}
ret
}
}
pub fn dc_get_archived_cnt(context: &Context) -> u32 {
context
.sql
.query_row_col(
context,
"SELECT COUNT(*) FROM chats WHERE blocked=0 AND archived=1;",
params![],
0,
)
.unwrap_or_default()
}
fn get_last_deaddrop_fresh_msg(context: &Context) -> u32 {
// We have an index over the state-column, this should be sufficient as there are typically
// only few fresh messages.
context
.sql
.query_row_col(
context,
"SELECT m.id FROM msgs m LEFT JOIN chats c ON c.id=m.chat_id \
WHERE m.state=10 \
AND m.hidden=0 \
AND c.blocked=2 \
ORDER BY m.timestamp DESC, m.id DESC;",
params![],
0,
)
.unwrap_or_default()
}

View File

@@ -1,14 +1,12 @@
use std::str::FromStr;
use strum::{EnumProperty, IntoEnumIterator};
use strum_macros::{AsRefStr, Display, EnumIter, EnumProperty, EnumString};
use crate::constants::DC_VERSION_STR;
use crate::context::Context;
use crate::dc_job::*;
use crate::dc_stock::*;
use crate::dc_tools::*;
use crate::x::*;
use crate::error::Error;
use crate::job::*;
use crate::stock::StockMessage;
/// The available configuration keys.
#[derive(
@@ -35,6 +33,7 @@ pub enum Config {
E2eeEnabled,
#[strum(props(default = "1"))]
MdnsEnabled,
#[strum(props(default = "1"))]
InboxWatch,
#[strum(props(default = "1"))]
SentboxWatch,
@@ -50,139 +49,97 @@ pub enum Config {
ConfiguredMailUser,
ConfiguredMailPw,
ConfiguredMailPort,
ConfiguredMailSecurity,
ConfiguredSendServer,
ConfiguredSendUser,
ConfiguredSendPw,
ConfiguredSendPort,
ConfiguredServerFlags,
ConfiguredSendSecurity,
ConfiguredE2EEEnabled,
Configured,
}
// deprecated
#[derive(Debug, Clone, Copy, PartialEq, Eq, Display, EnumString, AsRefStr, EnumIter)]
pub enum SysConfig {
// Deprecated
#[strum(serialize = "sys.version")]
Version,
SysVersion,
#[strum(serialize = "sys.msgsize_max_recommended")]
MsgsizeMaxRecommended,
SysMsgsizeMaxRecommended,
#[strum(serialize = "sys.config_keys")]
ConfigKeys,
SysConfigKeys,
}
/// Get a configuration key.
/// Returns "" when the key is invalid, or no default was found.
pub fn get(context: &Context, key: impl AsRef<str>) -> String {
let key = key.as_ref();
if key.starts_with("sys.") {
return get_sys_config_str(key);
}
match Config::from_str(key) {
Ok(config_key) => {
let value = match config_key {
Config::Selfavatar => {
let rel_path = context.sql.get_config(context, key, None);
rel_path.map(|p| {
let v = unsafe { dc_get_abs_path(context, to_cstring(p).as_ptr()) };
let r = to_string(v);
unsafe { free(v as *mut _) };
r
})
}
_ => context.sql.get_config(context, key, None),
};
if value.is_some() {
return value.unwrap();
impl Context {
/// Get a configuration key. Returns `None` if no value is set, and no default value found.
pub fn get_config(&self, key: Config) -> Option<String> {
let value = match key {
Config::Selfavatar => {
let rel_path = self.sql.get_config(self, key);
rel_path.map(|p| dc_get_abs_path_safe(self, &p).to_str().unwrap().to_string())
}
Config::SysVersion => Some((&*DC_VERSION_STR).clone()),
Config::SysMsgsizeMaxRecommended => Some(format!("{}", 24 * 1024 * 1024 / 4 * 3)),
Config::SysConfigKeys => Some(get_config_keys_string()),
_ => self.sql.get_config(self, key),
};
// Default values
match config_key {
Config::Selfstatus => {
let s = unsafe { dc_stock_str(context, 13) };
let res = to_string(s);
unsafe { free(s as *mut _) };
res
}
_ => config_key
.get_str("default")
.unwrap_or_default()
.to_string(),
}
if value.is_some() {
return value;
}
// Default values
match key {
Config::Selfstatus => Some(self.stock_str(StockMessage::StatusLine).into_owned()),
_ => key.get_str("default").map(|s| s.to_string()),
}
}
/// Set the given config key.
/// If `None` is passed as a value the value is cleared and set to the default if there is one.
pub fn set_config(&self, key: Config, value: Option<&str>) -> Result<(), Error> {
match key {
Config::Selfavatar if value.is_some() => {
let rel_path = std::fs::canonicalize(value.unwrap())?;
self.sql
.set_config(self, key, Some(&rel_path.to_string_lossy()))
}
Config::InboxWatch => {
let ret = self.sql.set_config(self, key, value);
interrupt_imap_idle(self);
ret
}
Config::SentboxWatch => {
let ret = self.sql.set_config(self, key, value);
interrupt_sentbox_idle(self);
ret
}
Config::MvboxWatch => {
let ret = self.sql.set_config(self, key, value);
interrupt_mvbox_idle(self);
ret
}
Config::Selfstatus => {
let def = self.stock_str(StockMessage::StatusLine);
let val = if value.is_none() || value.unwrap() == def {
None
} else {
value
};
self.sql.set_config(self, key, val)
}
_ => self.sql.set_config(self, key, value),
}
Err(_) => "".into(),
}
}
fn get_sys_config_str(key: impl AsRef<str>) -> String {
match SysConfig::from_str(key.as_ref()) {
Ok(SysConfig::Version) => std::str::from_utf8(DC_VERSION_STR).unwrap().into(),
Ok(SysConfig::MsgsizeMaxRecommended) => format!("{}", 24 * 1024 * 1024 / 4 * 3),
Ok(SysConfig::ConfigKeys) => get_config_keys_str(),
Err(_) => "".into(),
}
}
fn get_config_keys_str() -> String {
/// Returns all available configuration keys concated together.
fn get_config_keys_string() -> String {
let keys = Config::iter().fold(String::new(), |mut acc, key| {
acc += key.as_ref();
acc += " ";
acc
});
let sys_keys = SysConfig::iter().fold(String::new(), |mut acc, key| {
acc += key.as_ref();
acc += " ";
acc
});
format!(" {} {} ", keys, sys_keys)
}
/// Set the given config key.
/// Returns `1` on success and `0` on failure.
pub fn set(context: &Context, key: impl AsRef<str>, value: Option<&str>) -> libc::c_int {
let mut ret = 0;
// regular keys
match Config::from_str(key.as_ref()) {
Ok(Config::Selfavatar) if value.is_some() => {
let mut rel_path = unsafe { dc_strdup(to_cstring(value.unwrap()).as_ptr()) };
if 0 != unsafe { dc_make_rel_and_copy(context, &mut rel_path) } {
ret = context.sql.set_config(context, key, Some(as_str(rel_path)));
}
unsafe { free(rel_path as *mut libc::c_void) };
}
Ok(Config::InboxWatch) => {
ret = context.sql.set_config(context, key, value);
unsafe { dc_interrupt_imap_idle(context) };
}
Ok(Config::SentboxWatch) => {
ret = context.sql.set_config(context, key, value);
unsafe { dc_interrupt_sentbox_idle(context) };
}
Ok(Config::MvboxWatch) => {
ret = context.sql.set_config(context, key, value);
unsafe { dc_interrupt_mvbox_idle(context) };
}
Ok(Config::Selfstatus) => {
let def = unsafe { dc_stock_str(context, 13) };
let val = if value.is_none() || value.unwrap() == as_str(def) {
None
} else {
value
};
ret = context.sql.set_config(context, key, val);
unsafe { free(def as *mut libc::c_void) };
}
Ok(_) => {
ret = context.sql.set_config(context, key, value);
}
Err(_) => {}
}
ret
format!(" {} ", keys)
}
#[cfg(test)]
@@ -197,10 +154,10 @@ mod tests {
assert_eq!(Config::MailServer.to_string(), "mail_server");
assert_eq!(Config::from_str("mail_server"), Ok(Config::MailServer));
assert_eq!(SysConfig::ConfigKeys.to_string(), "sys.config_keys");
assert_eq!(Config::SysConfigKeys.to_string(), "sys.config_keys");
assert_eq!(
SysConfig::from_str("sys.config_keys"),
Ok(SysConfig::ConfigKeys)
Config::from_str("sys.config_keys"),
Ok(Config::SysConfigKeys)
);
}

View File

@@ -0,0 +1,224 @@
use quick_xml;
use quick_xml::events::{BytesEnd, BytesStart, BytesText};
use crate::context::Context;
use crate::dc_loginparam::*;
use crate::dc_tools::*;
use crate::x::*;
use super::read_autoconf_file;
/* ******************************************************************************
* Thunderbird's Autoconfigure
******************************************************************************/
/* 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 out_imap_set: libc::c_int,
pub out_smtp_set: libc::c_int,
pub tag_server: libc::c_int,
pub tag_config: libc::c_int,
}
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);
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() {
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 mut reader = quick_xml::Reader::from_str(as_str(xml_raw));
reader.trim_text(true);
let mut buf = Vec::new();
loop {
match reader.read_event(&mut buf) {
Ok(quick_xml::events::Event::Start(ref e)) => {
moz_autoconfigure_starttag_cb(e, &mut moz_ac, &reader)
}
Ok(quick_xml::events::Event::End(ref e)) => moz_autoconfigure_endtag_cb(e, &mut moz_ac),
Ok(quick_xml::events::Event::Text(ref e)) => {
moz_autoconfigure_text_cb(e, &mut moz_ac, &reader)
}
Err(e) => {
error!(
context,
0,
"Configure xml: Error at position {}: {:?}",
reader.buffer_position(),
e
);
}
Ok(quick_xml::events::Event::Eof) => break,
_ => (),
}
buf.clear();
}
if moz_ac.out.mail_server.is_empty()
|| moz_ac.out.mail_port == 0
|| moz_ac.out.send_server.is_empty()
|| moz_ac.out.send_port == 0
{
let r = dc_loginparam_get_readable(&moz_ac.out);
warn!(context, 0, "Bad or incomplete autoconfig: {}", r,);
free(xml_raw as *mut libc::c_void);
free(moz_ac.in_emaildomain as *mut libc::c_void);
free(moz_ac.in_emaillocalpart as *mut libc::c_void);
return None;
}
free(xml_raw as *mut libc::c_void);
free(moz_ac.in_emaildomain as *mut libc::c_void);
free(moz_ac.in_emaillocalpart as *mut libc::c_void);
Some(moz_ac.out)
}
fn moz_autoconfigure_text_cb<B: std::io::BufRead>(
event: &BytesText,
moz_ac: &mut moz_autoconfigure_t,
reader: &quick_xml::Reader<B>,
) {
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 val = val
.trim()
.replace("%EMAILADDRESS%", addr)
.replace("%EMAILLOCALPART%", email_local)
.replace("%EMAILDOMAIN%", email_domain);
if moz_ac.tag_server == 1 {
match moz_ac.tag_config {
10 => moz_ac.out.mail_server = val,
11 => moz_ac.out.mail_port = val.parse().unwrap_or_default(),
12 => moz_ac.out.mail_user = val,
13 => {
let val_lower = val.to_lowercase();
if val_lower == "ssl" {
moz_ac.out.server_flags |= 0x200
}
if val_lower == "starttls" {
moz_ac.out.server_flags |= 0x100
}
if val_lower == "plain" {
moz_ac.out.server_flags |= 0x400
}
}
_ => {}
}
} else if moz_ac.tag_server == 2 {
match moz_ac.tag_config {
10 => moz_ac.out.send_server = val,
11 => moz_ac.out.send_port = val.parse().unwrap_or_default(),
12 => moz_ac.out.send_user = val,
13 => {
let val_lower = val.to_lowercase();
if val_lower == "ssl" {
moz_ac.out.server_flags |= 0x20000
}
if val_lower == "starttls" {
moz_ac.out.server_flags |= 0x10000
}
if val_lower == "plain" {
moz_ac.out.server_flags |= 0x40000
}
}
_ => {}
}
}
}
fn moz_autoconfigure_endtag_cb(event: &BytesEnd, moz_ac: &mut moz_autoconfigure_t) {
let tag = String::from_utf8_lossy(event.name()).trim().to_lowercase();
if tag == "incomingserver" {
moz_ac.tag_server = 0;
moz_ac.tag_config = 0;
moz_ac.out_imap_set = 1;
} else if tag == "outgoingserver" {
moz_ac.tag_server = 0;
moz_ac.tag_config = 0;
moz_ac.out_smtp_set = 1;
} else {
moz_ac.tag_config = 0;
}
}
fn moz_autoconfigure_starttag_cb<B: std::io::BufRead>(
event: &BytesStart,
moz_ac: &mut moz_autoconfigure_t,
reader: &quick_xml::Reader<B>,
) {
let tag = String::from_utf8_lossy(event.name()).trim().to_lowercase();
if tag == "incomingserver" {
moz_ac.tag_server = if let Some(typ) = event.attributes().find(|attr| {
attr.as_ref()
.map(|a| String::from_utf8_lossy(a.key).trim().to_lowercase() == "type")
.unwrap_or_default()
}) {
let typ = typ
.unwrap()
.unescape_and_decode_value(reader)
.unwrap_or_default()
.to_lowercase();
if typ == "imap" && moz_ac.out_imap_set == 0 {
1
} else {
0
}
} else {
0
};
moz_ac.tag_config = 0;
} else if tag == "outgoingserver" {
moz_ac.tag_server = if moz_ac.out_smtp_set == 0 { 2 } else { 0 };
moz_ac.tag_config = 0;
} else if tag == "hostname" {
moz_ac.tag_config = 10;
} else if tag == "port" {
moz_ac.tag_config = 11;
} else if tag == "sockettype" {
moz_ac.tag_config = 13;
} else if tag == "username" {
moz_ac.tag_config = 12;
}
}

View File

@@ -0,0 +1,213 @@
use quick_xml;
use quick_xml::events::{BytesEnd, BytesStart, BytesText};
use crate::context::Context;
use crate::dc_loginparam::*;
use crate::dc_tools::*;
use crate::x::*;
use std::ptr;
use super::read_autoconf_file;
/* ******************************************************************************
* Outlook's Autodiscover
******************************************************************************/
#[repr(C)]
struct outlk_autodiscover_t<'a> {
pub in_0: &'a dc_loginparam_t,
pub out: dc_loginparam_t,
pub out_imap_set: libc::c_int,
pub out_smtp_set: libc::c_int,
pub tag_config: libc::c_int,
pub config: [*mut libc::c_char; 6],
pub redirect: *mut libc::c_char,
}
pub unsafe fn outlk_autodiscover(
context: &Context,
url__: &str,
param_in: &dc_loginparam_t,
) -> Option<dc_loginparam_t> {
let mut xml_raw: *mut libc::c_char = ptr::null_mut();
let mut url = url__.strdup();
let mut outlk_ad = outlk_autodiscover_t {
in_0: param_in,
out: dc_loginparam_new(),
out_imap_set: 0,
out_smtp_set: 0,
tag_config: 0,
config: [ptr::null_mut(); 6],
redirect: ptr::null_mut(),
};
let ok_to_continue;
let mut i = 0;
loop {
if !(i < 10) {
ok_to_continue = true;
break;
}
memset(
&mut outlk_ad as *mut outlk_autodiscover_t as *mut libc::c_void,
0,
::std::mem::size_of::<outlk_autodiscover_t>(),
);
xml_raw = read_autoconf_file(context, url);
if xml_raw.is_null() {
ok_to_continue = false;
break;
}
let mut reader = quick_xml::Reader::from_str(as_str(xml_raw));
reader.trim_text(true);
let mut buf = Vec::new();
loop {
match reader.read_event(&mut buf) {
Ok(quick_xml::events::Event::Start(ref e)) => {
outlk_autodiscover_starttag_cb(e, &mut outlk_ad)
}
Ok(quick_xml::events::Event::End(ref e)) => {
outlk_autodiscover_endtag_cb(e, &mut outlk_ad)
}
Ok(quick_xml::events::Event::Text(ref e)) => {
outlk_autodiscover_text_cb(e, &mut outlk_ad, &reader)
}
Err(e) => {
error!(
context,
0,
"Configure xml: Error at position {}: {:?}",
reader.buffer_position(),
e
);
}
Ok(quick_xml::events::Event::Eof) => break,
_ => (),
}
buf.clear();
}
if !(!outlk_ad.config[5].is_null()
&& 0 != *outlk_ad.config[5usize].offset(0isize) as libc::c_int)
{
ok_to_continue = true;
break;
}
free(url as *mut libc::c_void);
url = dc_strdup(outlk_ad.config[5usize]);
outlk_clean_config(&mut outlk_ad);
free(xml_raw as *mut libc::c_void);
xml_raw = ptr::null_mut();
i += 1;
}
if ok_to_continue {
if outlk_ad.out.mail_server.is_empty()
|| outlk_ad.out.mail_port == 0
|| outlk_ad.out.send_server.is_empty()
|| outlk_ad.out.send_port == 0
{
let r = dc_loginparam_get_readable(&outlk_ad.out);
warn!(context, 0, "Bad or incomplete autoconfig: {}", r,);
free(url as *mut libc::c_void);
free(xml_raw as *mut libc::c_void);
outlk_clean_config(&mut outlk_ad);
return None;
}
}
free(url as *mut libc::c_void);
free(xml_raw as *mut libc::c_void);
outlk_clean_config(&mut outlk_ad);
Some(outlk_ad.out)
}
unsafe fn outlk_clean_config(mut outlk_ad: *mut outlk_autodiscover_t) {
for i in 0..6 {
free((*outlk_ad).config[i] as *mut libc::c_void);
(*outlk_ad).config[i] = ptr::null_mut();
}
}
fn outlk_autodiscover_text_cb<B: std::io::BufRead>(
event: &BytesText,
outlk_ad: &mut outlk_autodiscover_t,
reader: &quick_xml::Reader<B>,
) {
let val = event.unescape_and_decode(reader).unwrap_or_default();
unsafe {
free(outlk_ad.config[outlk_ad.tag_config as usize].cast());
outlk_ad.config[outlk_ad.tag_config as usize] = val.trim().strdup();
}
}
unsafe fn outlk_autodiscover_endtag_cb(event: &BytesEnd, outlk_ad: &mut outlk_autodiscover_t) {
let tag = String::from_utf8_lossy(event.name()).trim().to_lowercase();
if tag == "protocol" {
if !outlk_ad.config[1].is_null() {
let port = dc_atoi_null_is_0(outlk_ad.config[3]);
let ssl_on = (!outlk_ad.config[4].is_null()
&& strcasecmp(
outlk_ad.config[4],
b"on\x00" as *const u8 as *const libc::c_char,
) == 0) as libc::c_int;
let ssl_off = (!outlk_ad.config[4].is_null()
&& strcasecmp(
outlk_ad.config[4],
b"off\x00" as *const u8 as *const libc::c_char,
) == 0) as libc::c_int;
if strcasecmp(
outlk_ad.config[1],
b"imap\x00" as *const u8 as *const libc::c_char,
) == 0
&& outlk_ad.out_imap_set == 0
{
outlk_ad.out.mail_server = to_string(outlk_ad.config[2]);
outlk_ad.out.mail_port = port;
if 0 != ssl_on {
outlk_ad.out.server_flags |= 0x200
} else if 0 != ssl_off {
outlk_ad.out.server_flags |= 0x400
}
outlk_ad.out_imap_set = 1
} else if strcasecmp(
outlk_ad.config[1usize],
b"smtp\x00" as *const u8 as *const libc::c_char,
) == 0
&& outlk_ad.out_smtp_set == 0
{
outlk_ad.out.send_server = to_string(outlk_ad.config[2]);
outlk_ad.out.send_port = port;
if 0 != ssl_on {
outlk_ad.out.server_flags |= 0x20000
} else if 0 != ssl_off {
outlk_ad.out.server_flags |= 0x40000
}
outlk_ad.out_smtp_set = 1
}
}
outlk_clean_config(outlk_ad);
}
outlk_ad.tag_config = 0;
}
fn outlk_autodiscover_starttag_cb(event: &BytesStart, outlk_ad: &mut outlk_autodiscover_t) {
let tag = String::from_utf8_lossy(event.name()).trim().to_lowercase();
if tag == "protocol" {
unsafe { outlk_clean_config(outlk_ad) };
} else if tag == "type" {
outlk_ad.tag_config = 1
} else if tag == "server" {
outlk_ad.tag_config = 2
} else if tag == "port" {
outlk_ad.tag_config = 3
} else if tag == "ssl" {
outlk_ad.tag_config = 4
} else if tag == "redirecturl" {
outlk_ad.tag_config = 5
};
}

712
src/configure/mod.rs Normal file
View File

@@ -0,0 +1,712 @@
use percent_encoding::{utf8_percent_encode, NON_ALPHANUMERIC};
use crate::constants::Event;
use crate::context::Context;
use crate::dc_loginparam::*;
use crate::dc_tools::*;
use crate::e2ee;
use crate::imap::*;
use crate::job::*;
use crate::oauth2::*;
use crate::param::Params;
use crate::types::*;
mod auto_outlook;
use auto_outlook::outlk_autodiscover;
mod auto_mozilla;
use auto_mozilla::moz_autoconfigure;
macro_rules! progress {
($context:tt, $progress:expr) => {
assert!(
$progress >= 0 && $progress <= 1000,
"value in range 0..1000 expected with: 0=error, 1..999=progress, 1000=success"
);
$context.call_cb(
Event::CONFIGURE_PROGRESS,
$progress as uintptr_t,
0 as uintptr_t,
);
};
}
// connect
pub unsafe fn configure(context: &Context) {
if 0 != dc_has_ongoing(context) {
warn!(
context,
0, "There is already another ongoing process running.",
);
return;
}
job_kill_action(context, Action::ConfigureImap);
job_add(context, Action::ConfigureImap, 0, Params::new(), 0);
}
unsafe fn dc_has_ongoing(context: &Context) -> libc::c_int {
let s_a = context.running_state.clone();
let s = s_a.read().unwrap();
if s.ongoing_running || !s.shall_stop_ongoing {
1
} else {
0
}
}
pub fn dc_is_configured(context: &Context) -> libc::c_int {
if context
.sql
.get_config_int(context, "configured")
.unwrap_or_default()
> 0
{
1
} else {
0
}
}
pub fn dc_stop_ongoing_process(context: &Context) {
let s_a = context.running_state.clone();
let mut s = s_a.write().unwrap();
if s.ongoing_running && !s.shall_stop_ongoing {
info!(context, 0, "Signaling the ongoing process to stop ASAP.",);
s.shall_stop_ongoing = true;
} else {
info!(context, 0, "No ongoing process to stop.",);
};
}
// the other dc_job_do_DC_JOB_*() functions are declared static in the c-file
#[allow(non_snake_case, unused_must_use)]
pub unsafe fn dc_job_do_DC_JOB_CONFIGURE_IMAP(context: &Context, _job: &Job) {
let flags: libc::c_int;
let mut success = false;
let mut imap_connected_here = false;
let mut smtp_connected_here = false;
let mut ongoing_allocated_here = false;
let mut param_autoconfig = None;
if !(0 == dc_alloc_ongoing(context)) {
ongoing_allocated_here = true;
if !context.sql.is_open() {
error!(context, 0, "Cannot configure, database not opened.",);
} else {
context.inbox.read().unwrap().disconnect(context);
context
.sentbox_thread
.read()
.unwrap()
.imap
.disconnect(context);
context
.mvbox_thread
.read()
.unwrap()
.imap
.disconnect(context);
context.smtp.clone().lock().unwrap().disconnect();
info!(context, 0, "Configure ...",);
let s_a = context.running_state.clone();
let s = s_a.read().unwrap();
if !s.shall_stop_ongoing {
progress!(context, 1);
let mut param = dc_loginparam_read(context, &context.sql, "");
if param.addr.is_empty() {
error!(context, 0, "Please enter an email address.",);
} else {
let ok_to_continue0;
if 0 != param.server_flags & 0x2 {
// the used oauth2 addr may differ, check this.
// if dc_get_oauth2_addr() is not available in the oauth2 implementation,
// just use the given one.
if s.shall_stop_ongoing {
ok_to_continue0 = false;
} else {
progress!(context, 10);
if let Some(oauth2_addr) =
dc_get_oauth2_addr(context, &param.addr, &param.mail_pw)
.and_then(|e| e.parse().ok())
{
param.addr = oauth2_addr;
context
.sql
.set_config(context, "addr", Some(param.addr.as_str()))
.ok();
}
if s.shall_stop_ongoing {
ok_to_continue0 = false;
} else {
progress!(context, 20);
ok_to_continue0 = true;
}
}
} else {
ok_to_continue0 = true;
}
if ok_to_continue0 {
let mut ok_to_continue7 = false;
if let Ok(parsed) = param.addr.parse() {
let parsed: EmailAddress = parsed;
let param_domain = parsed.domain;
let param_addr_urlencoded =
utf8_percent_encode(&param.addr, NON_ALPHANUMERIC).to_string();
if !s.shall_stop_ongoing {
progress!(context, 200);
/* 2. Autoconfig
**************************************************************************/
if param.mail_server.is_empty()
&& param.mail_port == 0
&& param.send_server.is_empty()
&& param.send_port == 0
&& param.send_user.is_empty()
&& param.server_flags & !0x2 == 0
{
let ok_to_continue1;
/*&&param->mail_user ==NULL -- the user can enter a loginname which is used by autoconfig then */
/*&&param->send_pw ==NULL -- the password cannot be auto-configured and is no criterion for autoconfig or not */
/* flags but OAuth2 avoid autoconfig */
let keep_flags = param.server_flags & 0x2;
/* A. Search configurations from the domain used in the email-address, prefer encrypted */
if param_autoconfig.is_none() {
let url = format!(
"https://autoconfig.{}/mail/config-v1.1.xml?emailaddress={}",
param_domain,
param_addr_urlencoded
);
param_autoconfig = moz_autoconfigure(context, &url, &param);
if s.shall_stop_ongoing {
ok_to_continue1 = false;
} else {
progress!(context, 300);
ok_to_continue1 = true;
}
} else {
ok_to_continue1 = true;
}
if ok_to_continue1 {
let ok_to_continue2;
if param_autoconfig.is_none() {
// the doc does not mention `emailaddress=`, however, Thunderbird adds it, see https://releases.mozilla.org/pub/thunderbird/ , which makes some sense
let url = format!(
"https://{}/.well-known/autoconfig/mail/config-v1.1.xml?emailaddress={}",
param_domain,
param_addr_urlencoded
);
param_autoconfig =
moz_autoconfigure(context, &url, &param);
if s.shall_stop_ongoing {
ok_to_continue2 = false;
} else {
progress!(context, 310);
ok_to_continue2 = true;
}
} else {
ok_to_continue2 = true;
}
if ok_to_continue2 {
let mut i: libc::c_int = 0;
let ok_to_continue3;
loop {
if !(i <= 1) {
ok_to_continue3 = true;
break;
}
if param_autoconfig.is_none() {
/* Outlook uses always SSL but different domains */
let url = format!(
"https://{}{}/autodiscover/autodiscover.xml",
if i == 0 {
""
} else {
"autodiscover."
},
param_domain
);
param_autoconfig =
outlk_autodiscover(context, &url, &param);
if s.shall_stop_ongoing {
ok_to_continue3 = false;
break;
}
progress!(context, 320 + i * 10);
}
i += 1
}
if ok_to_continue3 {
let ok_to_continue4;
if param_autoconfig.is_none() {
let url = format!(
"http://autoconfig.{}/mail/config-v1.1.xml?emailaddress={}",
param_domain,
param_addr_urlencoded
);
param_autoconfig =
moz_autoconfigure(context, &url, &param);
if s.shall_stop_ongoing {
ok_to_continue4 = false;
} else {
progress!(context, 340);
ok_to_continue4 = true;
}
} else {
ok_to_continue4 = true;
}
if ok_to_continue4 {
let ok_to_continue5;
if param_autoconfig.is_none() {
// do not transfer the email-address unencrypted
let url = format!(
"http://{}/.well-known/autoconfig/mail/config-v1.1.xml",
param_domain
);
param_autoconfig = moz_autoconfigure(
context, &url, &param,
);
if s.shall_stop_ongoing {
ok_to_continue5 = false;
} else {
progress!(context, 350);
ok_to_continue5 = true;
}
} else {
ok_to_continue5 = true;
}
if ok_to_continue5 {
let ok_to_continue6;
/* B. If we have no configuration yet, search configuration in Thunderbird's centeral database */
if param_autoconfig.is_none() {
/* always SSL for Thunderbird's database */
let url =
format!("https://autoconfig.thunderbird.net/v1.1/{}",
param_domain
);
param_autoconfig = moz_autoconfigure(
context, &url, &param,
);
if s.shall_stop_ongoing {
ok_to_continue6 = false;
} else {
progress!(context, 500);
ok_to_continue6 = true;
}
} else {
ok_to_continue6 = true;
}
if ok_to_continue6 {
if let Some(ref cfg) = param_autoconfig
{
let r =
dc_loginparam_get_readable(cfg);
info!(
context,
0, "Got autoconfig: {}", r
);
if !cfg.mail_user.is_empty() {
param.mail_user =
cfg.mail_user.clone();
}
param.mail_server =
cfg.mail_server.clone();
param.mail_port = cfg.mail_port;
param.send_server =
cfg.send_server.clone();
param.send_port = cfg.send_port;
param.send_user =
cfg.send_user.clone();
param.server_flags =
cfg.server_flags;
}
param.server_flags |= keep_flags;
ok_to_continue7 = true;
}
}
}
}
}
}
}
} else {
ok_to_continue7 = true;
}
if ok_to_continue7 {
if param.mail_server.is_empty() {
param.mail_server = format!("imap.{}", param_domain,)
}
if param.mail_port == 0 {
param.mail_port = if 0 != param.server_flags & (0x100 | 0x400) {
143
} else {
993
}
}
if param.mail_user.is_empty() {
param.mail_user = param.addr.clone();
}
if param.send_server.is_empty() && !param.mail_server.is_empty() {
param.send_server = param.mail_server.clone();
if param.send_server.starts_with("imap.") {
param.send_server =
param.send_server.replacen("imap", "smtp", 1);
}
}
if param.send_port == 0 {
param.send_port = if 0 != param.server_flags & 0x10000 {
587
} else if 0 != param.server_flags & 0x40000 {
25
} else {
465
}
}
if param.send_user.is_empty() && !param.mail_user.is_empty() {
param.send_user = param.mail_user.clone();
}
if param.send_pw.is_empty() && !param.mail_pw.is_empty() {
param.send_pw = param.mail_pw.clone()
}
if !dc_exactly_one_bit_set(param.server_flags & (0x2 | 0x4)) {
param.server_flags &= !(0x2 | 0x4);
param.server_flags |= 0x4
}
if !dc_exactly_one_bit_set(
param.server_flags & (0x100 | 0x200 | 0x400),
) {
param.server_flags &= !(0x100 | 0x200 | 0x400);
param.server_flags |=
if param.send_port == 143 { 0x100 } else { 0x200 }
}
if !dc_exactly_one_bit_set(
param.server_flags & (0x10000 | 0x20000 | 0x40000),
) {
param.server_flags &= !(0x10000 | 0x20000 | 0x40000);
param.server_flags |= if param.send_port == 587 {
0x10000
} else if param.send_port == 25 {
0x40000
} else {
0x20000
}
}
/* do we have a complete configuration? */
if param.mail_server.is_empty()
|| param.mail_port == 0
|| param.mail_user.is_empty()
|| param.mail_pw.is_empty()
|| param.send_server.is_empty()
|| param.send_port == 0
|| param.send_user.is_empty()
|| param.send_pw.is_empty()
|| param.server_flags == 0
{
error!(context, 0, "Account settings incomplete.",);
} else if !s.shall_stop_ongoing {
progress!(context, 600);
/* try to connect to IMAP - if we did not got an autoconfig,
do some further tries with different settings and username variations */
let ok_to_continue8;
let mut username_variation = 0;
loop {
if !(username_variation <= 1) {
ok_to_continue8 = true;
break;
}
let r_0 = dc_loginparam_get_readable(&param);
info!(context, 0, "Trying: {}", r_0,);
if context.inbox.read().unwrap().connect(context, &param) {
ok_to_continue8 = true;
break;
}
if !param_autoconfig.is_none() {
ok_to_continue8 = false;
break;
}
// probe STARTTLS/993
if s.shall_stop_ongoing {
ok_to_continue8 = false;
break;
}
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,);
if context.inbox.read().unwrap().connect(context, &param) {
ok_to_continue8 = true;
break;
}
// probe STARTTLS/143
if s.shall_stop_ongoing {
ok_to_continue8 = false;
break;
}
progress!(context, 660 + username_variation * 30);
param.mail_port = 143;
let r_2 = dc_loginparam_get_readable(&param);
info!(context, 0, "Trying: {}", r_2,);
if context.inbox.read().unwrap().connect(context, &param) {
ok_to_continue8 = true;
break;
}
if 0 != username_variation {
ok_to_continue8 = false;
break;
}
// next probe round with only the localpart of the email-address as the loginname
if s.shall_stop_ongoing {
ok_to_continue8 = false;
break;
}
progress!(context, 670 + username_variation * 30);
param.server_flags &= !(0x100 | 0x200 | 0x400);
param.server_flags |= 0x200;
param.mail_port = 993;
if let Some(at) = param.mail_user.find('@') {
param.mail_user =
param.mail_user.split_at(at).0.to_string();
}
if let Some(at) = param.send_user.find('@') {
param.send_user =
param.send_user.split_at(at).0.to_string();
}
username_variation += 1
}
if ok_to_continue8 {
imap_connected_here = true;
if !s.shall_stop_ongoing {
progress!(context, 800);
let ok_to_continue9;
/* try to connect to SMTP - if we did not got an autoconfig, the first try was SSL-465 and we do a second try with STARTTLS-587 */
if !context
.smtp
.clone()
.lock()
.unwrap()
.connect(context, &param)
{
if !param_autoconfig.is_none() {
ok_to_continue9 = false;
} else if s.shall_stop_ongoing {
ok_to_continue9 = false;
} else {
progress!(context, 850);
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,);
if !context
.smtp
.clone()
.lock()
.unwrap()
.connect(context, &param)
{
if s.shall_stop_ongoing {
ok_to_continue9 = false;
} else {
progress!(context, 860);
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);
if !context
.smtp
.clone()
.lock()
.unwrap()
.connect(context, &param)
{
ok_to_continue9 = false;
} else {
ok_to_continue9 = true;
}
}
} else {
ok_to_continue9 = true;
}
}
} else {
ok_to_continue9 = true;
}
if ok_to_continue9 {
smtp_connected_here = true;
if !s.shall_stop_ongoing {
progress!(context, 900);
flags = if 0
!= context
.sql
.get_config_int(context, "mvbox_watch")
.unwrap_or_else(|| 1)
|| 0 != context
.sql
.get_config_int(context, "mvbox_move")
.unwrap_or_else(|| 1)
{
0x1
} else {
0
};
context
.inbox
.read()
.unwrap()
.configure_folders(context, flags);
if !s.shall_stop_ongoing {
progress!(context, 910);
dc_loginparam_write(
context,
&param,
&context.sql,
"configured_",
);
context
.sql
.set_config_int(
context,
"configured",
1,
)
.ok();
if !s.shall_stop_ongoing {
progress!(context, 920);
e2ee::ensure_secret_key_exists(context);
success = true;
info!(
context,
0, "Configure completed."
);
if !s.shall_stop_ongoing {
progress!(context, 940);
}
}
}
}
}
}
}
}
}
} else {
error!(context, 0, "Bad email-address.");
}
}
}
}
}
}
if imap_connected_here {
context.inbox.read().unwrap().disconnect(context);
}
if smtp_connected_here {
context.smtp.clone().lock().unwrap().disconnect();
}
/*
if !success {
// disconnect if configure did not succeed
if imap_connected_here {
// context.inbox.read().unwrap().disconnect(context);
}
if smtp_connected_here {
// context.smtp.clone().lock().unwrap().disconnect();
}
} else {
assert!(imap_connected_here && smtp_connected_here);
info!(
context,
0, "Keeping IMAP/SMTP connections open after successful configuration"
);
}
*/
if ongoing_allocated_here {
dc_free_ongoing(context);
}
progress!(context, (if success { 1000 } else { 0 }));
}
pub unsafe fn dc_free_ongoing(context: &Context) {
let s_a = context.running_state.clone();
let mut s = s_a.write().unwrap();
s.ongoing_running = false;
s.shall_stop_ongoing = true;
}
pub unsafe fn dc_alloc_ongoing(context: &Context) -> libc::c_int {
if 0 != dc_has_ongoing(context) {
warn!(
context,
0, "There is already another ongoing process running.",
);
return 0;
}
let s_a = context.running_state.clone();
let mut s = s_a.write().unwrap();
s.ongoing_running = true;
s.shall_stop_ongoing = false;
1
}
pub fn dc_connect_to_configured_imap(context: &Context, imap: &Imap) -> libc::c_int {
let mut ret_connected = 0;
if imap.is_connected() {
ret_connected = 1
} else if context
.sql
.get_config_int(context, "configured")
.unwrap_or_default()
== 0
{
warn!(context, 0, "Not configured, cannot connect.",);
} else {
let param = dc_loginparam_read(context, &context.sql, "configured_");
// the trailing underscore is correct
if imap.connect(context, &param) {
ret_connected = 2;
}
}
ret_connected
}
pub fn read_autoconf_file(context: &Context, url: *const libc::c_char) -> *mut libc::c_char {
info!(context, 0, "Testing {} ...", to_string(url));
match reqwest::Client::new()
.get(as_str(url))
.send()
.and_then(|mut res| res.text())
{
Ok(res) => unsafe { res.strdup() },
Err(_err) => {
info!(context, 0, "Can\'t read file.",);
std::ptr::null_mut()
}
}
}

View File

@@ -1,153 +1,140 @@
//! Constants
#![allow(non_camel_case_types, dead_code)]
pub const DC_VERSION_STR: &'static [u8; 14] = b"1.0.0-alpha.1\x00";
use lazy_static::lazy_static;
pub const DC_MOVE_STATE_MOVING: u32 = 3;
pub const DC_MOVE_STATE_STAY: u32 = 2;
pub const DC_MOVE_STATE_PENDING: u32 = 1;
pub const DC_MOVE_STATE_UNDEFINED: u32 = 0;
use deltachat_derive::*;
lazy_static! {
pub static ref DC_VERSION_STR: String = env!("CARGO_PKG_VERSION").to_string();
}
#[repr(u8)]
#[derive(Debug, Display, Clone, Copy, PartialEq, Eq, FromPrimitive, ToPrimitive, ToSql, FromSql)]
pub enum MoveState {
Undefined = 0,
Pending = 1,
Stay = 2,
Moving = 3,
}
// some defaults
const DC_E2EE_DEFAULT_ENABLED: i32 = 1;
pub const DC_MDNS_DEFAULT_ENABLED: i32 = 1;
const DC_INBOX_WATCH_DEFAULT: i32 = 1;
const DC_SENTBOX_WATCH_DEFAULT: i32 = 1;
const DC_MVBOX_WATCH_DEFAULT: i32 = 1;
const DC_MVBOX_MOVE_DEFAULT: i32 = 1;
#[derive(Debug, Display, Clone, Copy, PartialEq, Eq, FromPrimitive, ToPrimitive, FromSql, ToSql)]
#[repr(u8)]
pub enum Blocked {
Not = 0,
Manually = 1,
Deaddrop = 2,
}
impl Default for Blocked {
fn default() -> Self {
Blocked::Not
}
}
pub const DC_IMAP_SEEN: u32 = 0x1;
const DC_HANDSHAKE_CONTINUE_NORMAL_PROCESSING: i32 = 0x01;
pub const DC_HANDSHAKE_STOP_NORMAL_PROCESSING: i32 = 0x02;
pub const DC_HANDSHAKE_ADD_DELETE_JOB: i32 = 0x04;
pub const DC_GCL_ARCHIVED_ONLY: usize = 0x01;
pub const DC_GCL_NO_SPECIALS: usize = 0x02;
pub const DC_GCL_ADD_ALLDONE_HINT: usize = 0x04;
pub const DC_GCM_ADDDAYMARKER: usize = 0x01;
const DC_GCM_ADDDAYMARKER: usize = 0x01;
pub const DC_GCL_VERIFIED_ONLY: usize = 0x01;
const DC_GCL_VERIFIED_ONLY: usize = 0x01;
pub const DC_GCL_ADD_SELF: usize = 0x02;
/// param1 is a directory where the keys are written to
pub const DC_IMEX_EXPORT_SELF_KEYS: usize = 1;
const DC_IMEX_EXPORT_SELF_KEYS: usize = 1;
/// param1 is a directory where the keys are searched in and read from
pub const DC_IMEX_IMPORT_SELF_KEYS: usize = 2;
const DC_IMEX_IMPORT_SELF_KEYS: usize = 2;
/// param1 is a directory where the backup is written to
pub const DC_IMEX_EXPORT_BACKUP: usize = 11;
const DC_IMEX_EXPORT_BACKUP: usize = 11;
/// param1 is the file with the backup to import
pub const DC_IMEX_IMPORT_BACKUP: usize = 12;
/// id=contact
pub const DC_QR_ASK_VERIFYCONTACT: usize = 200;
/// text1=groupname
pub const DC_QR_ASK_VERIFYGROUP: usize = 202;
/// id=contact
pub const DC_QR_FPR_OK: usize = 210;
/// id=contact
pub const DC_QR_FPR_MISMATCH: usize = 220;
/// test1=formatted fingerprint
pub const DC_QR_FPR_WITHOUT_ADDR: usize = 230;
/// id=contact
pub const DC_QR_ADDR: usize = 320;
/// text1=text
pub const DC_QR_TEXT: usize = 330;
/// text1=URL
pub const DC_QR_URL: usize = 332;
/// text1=error string
pub const DC_QR_ERROR: usize = 400;
const DC_IMEX_IMPORT_BACKUP: usize = 12;
/// virtual chat showing all messages belonging to chats flagged with chats.blocked=2
pub const DC_CHAT_ID_DEADDROP: usize = 1;
pub(crate) const DC_CHAT_ID_DEADDROP: u32 = 1;
/// messages that should be deleted get this chat_id; the messages are deleted from the working thread later then. This is also needed as rfc724_mid should be preset as long as the message is not deleted on the server (otherwise it is downloaded again)
pub const DC_CHAT_ID_TRASH: usize = 3;
pub const DC_CHAT_ID_TRASH: u32 = 3;
/// a message is just in creation but not yet assigned to a chat (eg. we may need the message ID to set up blobs; this avoids unready message to be sent and shown)
pub const DC_CHAT_ID_MSGS_IN_CREATION: usize = 4;
const DC_CHAT_ID_MSGS_IN_CREATION: u32 = 4;
/// virtual chat showing all messages flagged with msgs.starred=2
pub const DC_CHAT_ID_STARRED: usize = 5;
const DC_CHAT_ID_STARRED: u32 = 5;
/// only an indicator in a chatlist
pub const DC_CHAT_ID_ARCHIVED_LINK: usize = 6;
pub const DC_CHAT_ID_ARCHIVED_LINK: u32 = 6;
/// only an indicator in a chatlist
pub const DC_CHAT_ID_ALLDONE_HINT: usize = 7;
pub const DC_CHAT_ID_ALLDONE_HINT: u32 = 7;
/// larger chat IDs are "real" chats, their messages are "real" messages.
pub const DC_CHAT_ID_LAST_SPECIAL: usize = 9;
pub const DC_CHAT_ID_LAST_SPECIAL: u32 = 9;
pub const DC_CHAT_TYPE_UNDEFINED: usize = 0;
pub const DC_CHAT_TYPE_SINGLE: usize = 100;
pub const DC_CHAT_TYPE_GROUP: usize = 120;
pub const DC_CHAT_TYPE_VERIFIED_GROUP: usize = 130;
#[derive(
Debug,
Display,
Clone,
Copy,
PartialEq,
Eq,
FromPrimitive,
ToPrimitive,
FromSql,
ToSql,
IntoStaticStr,
)]
#[repr(u32)]
pub enum Chattype {
Undefined = 0,
Single = 100,
Group = 120,
VerifiedGroup = 130,
}
pub const DC_MSG_ID_MARKER1: usize = 1;
pub const DC_MSG_ID_DAYMARKER: usize = 9;
pub const DC_MSG_ID_LAST_SPECIAL: usize = 9;
impl Default for Chattype {
fn default() -> Self {
Chattype::Undefined
}
}
pub const DC_STATE_UNDEFINED: usize = 0;
pub const DC_STATE_IN_FRESH: usize = 10;
pub const DC_STATE_IN_NOTICED: usize = 13;
pub const DC_STATE_IN_SEEN: usize = 16;
pub const DC_STATE_OUT_PREPARING: usize = 18;
pub const DC_STATE_OUT_DRAFT: usize = 19;
pub const DC_STATE_OUT_PENDING: usize = 20;
pub const DC_STATE_OUT_FAILED: usize = 24;
/// to check if a mail was sent, use dc_msg_is_sent()
pub const DC_STATE_OUT_DELIVERED: usize = 26;
pub const DC_STATE_OUT_MDN_RCVD: usize = 28;
pub const DC_MSG_ID_MARKER1: u32 = 1;
const DC_MSG_ID_DAYMARKER: u32 = 9;
pub const DC_MSG_ID_LAST_SPECIAL: u32 = 9;
/// approx. max. lenght returned by dc_msg_get_text()
pub const DC_MAX_GET_TEXT_LEN: usize = 30000;
/// approx. max. lenght returned by dc_get_msg_info()
pub const DC_MAX_GET_INFO_LEN: usize = 100000;
/// approx. max. length returned by dc_msg_get_text()
const DC_MAX_GET_TEXT_LEN: usize = 30000;
/// approx. max. length returned by dc_get_msg_info()
const DC_MAX_GET_INFO_LEN: usize = 100000;
pub const DC_CONTACT_ID_SELF: usize = 1;
pub const DC_CONTACT_ID_DEVICE: usize = 2;
pub const DC_CONTACT_ID_LAST_SPECIAL: usize = 9;
pub const DC_TEXT1_DRAFT: usize = 1;
pub const DC_TEXT1_USERNAME: usize = 2;
pub const DC_TEXT1_SELF: usize = 3;
pub const DC_CONTACT_ID_UNDEFINED: u32 = 0;
pub const DC_CONTACT_ID_SELF: u32 = 1;
const DC_CONTACT_ID_DEVICE: u32 = 2;
pub const DC_CONTACT_ID_LAST_SPECIAL: u32 = 9;
pub const DC_CREATE_MVBOX: usize = 1;
/// Text message.
/// The text of the message is set using dc_msg_set_text()
/// and retrieved with dc_msg_get_text().
pub const DC_MSG_TEXT: usize = 10;
/// Image message.
/// If the image is an animated GIF, the type DC_MSG_GIF should be used.
/// File, width and height are set via dc_msg_set_file(), dc_msg_set_dimension
/// and retrieved via dc_msg_set_file(), dc_msg_set_dimension().
pub const DC_MSG_IMAGE: usize = 20;
/// Animated GIF message.
/// File, width and height are set via dc_msg_set_file(), dc_msg_set_dimension()
/// and retrieved via dc_msg_get_file(), dc_msg_get_width(), dc_msg_get_height().
pub const DC_MSG_GIF: usize = 21;
/// Message containing an Audio file.
/// File and duration are set via dc_msg_set_file(), dc_msg_set_duration()
/// and retrieved via dc_msg_get_file(), dc_msg_get_duration().
pub const DC_MSG_AUDIO: usize = 40;
/// A voice message that was directly recorded by the user.
/// For all other audio messages, the type #DC_MSG_AUDIO should be used.
/// File and duration are set via dc_msg_set_file(), dc_msg_set_duration()
/// and retieved via dc_msg_get_file(), dc_msg_get_duration()
pub const DC_MSG_VOICE: usize = 41;
/// Video messages.
/// File, width, height and durarion
/// are set via dc_msg_set_file(), dc_msg_set_dimension(), dc_msg_set_duration()
/// and retrieved via
/// dc_msg_get_file(), dc_msg_get_width(),
/// dc_msg_get_height(), dc_msg_get_duration().
pub const DC_MSG_VIDEO: usize = 50;
/// Message containing any file, eg. a PDF.
/// The file is set via dc_msg_set_file()
/// and retrieved via dc_msg_get_file().
pub const DC_MSG_FILE: usize = 60;
// Flags for configuring IMAP and SMTP servers.
// These flags are optional
// and may be set together with the username, password etc.
// via dc_set_config() using the key "server_flags".
/// Force OAuth2 authorization. This flag does not skip automatic configuration.
/// Before calling dc_configure() with DC_LP_AUTH_OAUTH2 set,
/// Before calling configure() with DC_LP_AUTH_OAUTH2 set,
/// the user has to confirm access at the URL returned by dc_get_oauth2_url().
pub const DC_LP_AUTH_OAUTH2: usize = 0x2;
/// Force NORMAL authorization, this is the default.
/// If this flag is set, automatic configuration is skipped.
pub const DC_LP_AUTH_NORMAL: usize = 0x4;
const DC_LP_AUTH_NORMAL: usize = 0x4;
/// Connect to IMAP via STARTTLS.
/// If this flag is set, automatic configuration is skipped.
@@ -155,7 +142,7 @@ pub const DC_LP_IMAP_SOCKET_STARTTLS: usize = 0x100;
/// Connect to IMAP via SSL.
/// If this flag is set, automatic configuration is skipped.
pub const DC_LP_IMAP_SOCKET_SSL: usize = 0x200;
const DC_LP_IMAP_SOCKET_SSL: usize = 0x200;
/// Connect to IMAP unencrypted, this should not be used.
/// If this flag is set, automatic configuration is skipped.
@@ -167,21 +154,76 @@ pub const DC_LP_SMTP_SOCKET_STARTTLS: usize = 0x10000;
/// Connect to SMTP via SSL.
/// If this flag is set, automatic configuration is skipped.
pub const DC_LP_SMTP_SOCKET_SSL: usize = 0x20000;
const DC_LP_SMTP_SOCKET_SSL: usize = 0x20000;
/// Connect to SMTP unencrypted, this should not be used.
/// If this flag is set, automatic configuration is skipped.
pub const DC_LP_SMTP_SOCKET_PLAIN: usize = 0x40000;
/// if none of these flags are set, the default is choosen
pub const DC_LP_AUTH_FLAGS: usize = (DC_LP_AUTH_OAUTH2 | DC_LP_AUTH_NORMAL);
/// if none of these flags are set, the default is choosen
pub const DC_LP_IMAP_SOCKET_FLAGS: usize =
/// if none of these flags are set, the default is chosen
const DC_LP_AUTH_FLAGS: usize = (DC_LP_AUTH_OAUTH2 | DC_LP_AUTH_NORMAL);
/// if none of these flags are set, the default is chosen
const DC_LP_IMAP_SOCKET_FLAGS: usize =
(DC_LP_IMAP_SOCKET_STARTTLS | DC_LP_IMAP_SOCKET_SSL | DC_LP_IMAP_SOCKET_PLAIN);
/// if none of these flags are set, the default is choosen
pub const DC_LP_SMTP_SOCKET_FLAGS: usize =
/// if none of these flags are set, the default is chosen
const DC_LP_SMTP_SOCKET_FLAGS: usize =
(DC_LP_SMTP_SOCKET_STARTTLS | DC_LP_SMTP_SOCKET_SSL | DC_LP_SMTP_SOCKET_PLAIN);
#[derive(Debug, Display, Clone, Copy, PartialEq, Eq, FromPrimitive, ToPrimitive, FromSql, ToSql)]
#[repr(i32)]
pub enum Viewtype {
Unknown = 0,
/// Text message.
/// The text of the message is set using dc_msg_set_text()
/// and retrieved with dc_msg_get_text().
Text = 10,
/// Image message.
/// If the image is an animated GIF, the type DC_MSG_GIF should be used.
/// File, width and height are set via dc_msg_set_file(), dc_msg_set_dimension
/// and retrieved via dc_msg_set_file(), dc_msg_set_dimension().
Image = 20,
/// Animated GIF message.
/// File, width and height are set via dc_msg_set_file(), dc_msg_set_dimension()
/// and retrieved via dc_msg_get_file(), dc_msg_get_width(), dc_msg_get_height().
Gif = 21,
/// Message containing an Audio file.
/// File and duration are set via dc_msg_set_file(), dc_msg_set_duration()
/// and retrieved via dc_msg_get_file(), dc_msg_get_duration().
Audio = 40,
/// A voice message that was directly recorded by the user.
/// For all other audio messages, the type #DC_MSG_AUDIO should be used.
/// File and duration are set via dc_msg_set_file(), dc_msg_set_duration()
/// and retrieved via dc_msg_get_file(), dc_msg_get_duration()
Voice = 41,
/// Video messages.
/// File, width, height and durarion
/// are set via dc_msg_set_file(), dc_msg_set_dimension(), dc_msg_set_duration()
/// and retrieved via
/// dc_msg_get_file(), dc_msg_get_width(),
/// dc_msg_get_height(), dc_msg_get_duration().
Video = 50,
/// Message containing any file, eg. a PDF.
/// The file is set via dc_msg_set_file()
/// and retrieved via dc_msg_get_file().
File = 60,
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn derive_display_works_as_expected() {
assert_eq!(format!("{}", Viewtype::Audio), "Audio");
}
}
// These constants are used as events
// reported to the callback given to dc_context_new().
// If you do not want to handle an event, it is always safe to return 0,
@@ -237,10 +279,10 @@ pub enum Event {
/// The library-user should report an error to the end-user.
/// Passed to the callback given to dc_context_new().
///
/// As most things are asynchrounous, things may go wrong at any time and the user
/// As most things are asynchronous, things may go wrong at any time and the user
/// should not be disturbed by a dialog or so. Instead, use a bubble or so.
///
/// However, for ongoing processes (eg. dc_configure())
/// However, for ongoing processes (eg. configure())
/// or for functions that are expected to fail (eg. dc_continue_key_transfer())
/// it might be better to delay showing these events until the function has really
/// failed (returned false). It should be sufficient to report only the _last_ error
@@ -265,7 +307,7 @@ pub enum Event {
///
/// Moreover, if the UI detects that the device is offline,
/// it is probably more useful to report this to the user
/// instread of the string from data2.
/// instead of the string from data2.
///
/// @param data1 (int) 1=first/new network error, should be reported the user;
/// 0=subsequent network error, should be logged only
@@ -358,7 +400,7 @@ pub enum Event {
/// @return 0
LOCATION_CHANGED = 2035,
/// Inform about the configuration progress started by dc_configure().
/// Inform about the configuration progress started by configure().
///
/// @param data1 (int) 0=error, 1-999=progress in permille, 1000=success and done
/// @param data2 0
@@ -423,65 +465,65 @@ pub enum Event {
GET_STRING = 2091,
}
pub const DC_EVENT_FILE_COPIED: usize = 2055; // deprecated;
pub const DC_EVENT_IS_OFFLINE: usize = 2081; // deprecated;
pub const DC_ERROR_SEE_STRING: usize = 0; // deprecated;
pub const DC_ERROR_SELF_NOT_IN_GROUP: usize = 1; // deprecated;
pub const DC_STR_SELFNOTINGRP: usize = 21; // deprecated;
const DC_EVENT_FILE_COPIED: usize = 2055; // deprecated;
const DC_EVENT_IS_OFFLINE: usize = 2081; // deprecated;
const DC_ERROR_SEE_STRING: usize = 0; // deprecated;
const DC_ERROR_SELF_NOT_IN_GROUP: usize = 1; // deprecated;
const DC_STR_SELFNOTINGRP: usize = 21; // deprecated;
/// Values for dc_get|set_config("show_emails")
pub const DC_SHOW_EMAILS_OFF: usize = 0;
pub const DC_SHOW_EMAILS_ACCEPTED_CONTACTS: usize = 1;
pub const DC_SHOW_EMAILS_ALL: usize = 2;
const DC_SHOW_EMAILS_OFF: usize = 0;
const DC_SHOW_EMAILS_ACCEPTED_CONTACTS: usize = 1;
const DC_SHOW_EMAILS_ALL: usize = 2;
// TODO: Strings need some doumentation about used placeholders.
// These constants are used to request strings using #DC_EVENT_GET_STRING.
pub const DC_STR_NOMESSAGES: usize = 1;
pub const DC_STR_SELF: usize = 2;
pub const DC_STR_DRAFT: usize = 3;
pub const DC_STR_MEMBER: usize = 4;
pub const DC_STR_CONTACT: usize = 6;
pub const DC_STR_VOICEMESSAGE: usize = 7;
pub const DC_STR_DEADDROP: usize = 8;
pub const DC_STR_IMAGE: usize = 9;
pub const DC_STR_VIDEO: usize = 10;
pub const DC_STR_AUDIO: usize = 11;
pub const DC_STR_FILE: usize = 12;
pub const DC_STR_STATUSLINE: usize = 13;
pub const DC_STR_NEWGROUPDRAFT: usize = 14;
pub const DC_STR_MSGGRPNAME: usize = 15;
pub const DC_STR_MSGGRPIMGCHANGED: usize = 16;
pub const DC_STR_MSGADDMEMBER: usize = 17;
pub const DC_STR_MSGDELMEMBER: usize = 18;
pub const DC_STR_MSGGROUPLEFT: usize = 19;
pub const DC_STR_GIF: usize = 23;
pub const DC_STR_ENCRYPTEDMSG: usize = 24;
pub const DC_STR_E2E_AVAILABLE: usize = 25;
pub const DC_STR_ENCR_TRANSP: usize = 27;
pub const DC_STR_ENCR_NONE: usize = 28;
pub const DC_STR_CANTDECRYPT_MSG_BODY: usize = 29;
pub const DC_STR_FINGERPRINTS: usize = 30;
pub const DC_STR_READRCPT: usize = 31;
pub const DC_STR_READRCPT_MAILBODY: usize = 32;
pub const DC_STR_MSGGRPIMGDELETED: usize = 33;
pub const DC_STR_E2E_PREFERRED: usize = 34;
pub const DC_STR_CONTACT_VERIFIED: usize = 35;
pub const DC_STR_CONTACT_NOT_VERIFIED: usize = 36;
pub const DC_STR_CONTACT_SETUP_CHANGED: usize = 37;
pub const DC_STR_ARCHIVEDCHATS: usize = 40;
pub const DC_STR_STARREDMSGS: usize = 41;
pub const DC_STR_AC_SETUP_MSG_SUBJECT: usize = 42;
pub const DC_STR_AC_SETUP_MSG_BODY: usize = 43;
pub const DC_STR_SELFTALK_SUBTITLE: usize = 50;
pub const DC_STR_CANNOT_LOGIN: usize = 60;
pub const DC_STR_SERVER_RESPONSE: usize = 61;
pub const DC_STR_MSGACTIONBYUSER: usize = 62;
pub const DC_STR_MSGACTIONBYME: usize = 63;
pub const DC_STR_MSGLOCATIONENABLED: usize = 64;
pub const DC_STR_MSGLOCATIONDISABLED: usize = 65;
pub const DC_STR_LOCATION: usize = 66;
pub const DC_STR_COUNT: usize = 66;
const DC_STR_NOMESSAGES: usize = 1;
const DC_STR_SELF: usize = 2;
const DC_STR_DRAFT: usize = 3;
const DC_STR_MEMBER: usize = 4;
const DC_STR_CONTACT: usize = 6;
const DC_STR_VOICEMESSAGE: usize = 7;
const DC_STR_DEADDROP: usize = 8;
const DC_STR_IMAGE: usize = 9;
const DC_STR_VIDEO: usize = 10;
const DC_STR_AUDIO: usize = 11;
const DC_STR_FILE: usize = 12;
const DC_STR_STATUSLINE: usize = 13;
const DC_STR_NEWGROUPDRAFT: usize = 14;
const DC_STR_MSGGRPNAME: usize = 15;
const DC_STR_MSGGRPIMGCHANGED: usize = 16;
const DC_STR_MSGADDMEMBER: usize = 17;
const DC_STR_MSGDELMEMBER: usize = 18;
const DC_STR_MSGGROUPLEFT: usize = 19;
const DC_STR_GIF: usize = 23;
const DC_STR_ENCRYPTEDMSG: usize = 24;
const DC_STR_E2E_AVAILABLE: usize = 25;
const DC_STR_ENCR_TRANSP: usize = 27;
const DC_STR_ENCR_NONE: usize = 28;
const DC_STR_CANTDECRYPT_MSG_BODY: usize = 29;
const DC_STR_FINGERPRINTS: usize = 30;
const DC_STR_READRCPT: usize = 31;
const DC_STR_READRCPT_MAILBODY: usize = 32;
const DC_STR_MSGGRPIMGDELETED: usize = 33;
const DC_STR_E2E_PREFERRED: usize = 34;
const DC_STR_CONTACT_VERIFIED: usize = 35;
const DC_STR_CONTACT_NOT_VERIFIED: usize = 36;
const DC_STR_CONTACT_SETUP_CHANGED: usize = 37;
const DC_STR_ARCHIVEDCHATS: usize = 40;
const DC_STR_STARREDMSGS: usize = 41;
const DC_STR_AC_SETUP_MSG_SUBJECT: usize = 42;
const DC_STR_AC_SETUP_MSG_BODY: usize = 43;
const DC_STR_SELFTALK_SUBTITLE: usize = 50;
const DC_STR_CANNOT_LOGIN: usize = 60;
const DC_STR_SERVER_RESPONSE: usize = 61;
const DC_STR_MSGACTIONBYUSER: usize = 62;
const DC_STR_MSGACTIONBYME: usize = 63;
const DC_STR_MSGLOCATIONENABLED: usize = 64;
const DC_STR_MSGLOCATIONDISABLED: usize = 65;
const DC_STR_LOCATION: usize = 66;
const DC_STR_COUNT: usize = 66;
pub const DC_JOB_DELETE_MSG_ON_IMAP: i32 = 110;
@@ -491,3 +533,12 @@ pub enum KeyType {
Public = 0,
Private = 1,
}
pub const DC_CMD_GROUPNAME_CHANGED: libc::c_int = 2;
pub const DC_CMD_GROUPIMAGE_CHANGED: libc::c_int = 3;
pub const DC_CMD_MEMBER_ADDED_TO_GROUP: libc::c_int = 4;
pub const DC_CMD_MEMBER_REMOVED_FROM_GROUP: libc::c_int = 5;
pub const DC_CMD_AUTOCRYPT_SETUP_MESSAGE: libc::c_int = 6;
const DC_CMD_SECUREJOIN_MESSAGE: libc::c_int = 7;
pub const DC_CMD_LOCATION_STREAMING_ENABLED: libc::c_int = 8;
const DC_CMD_LOCATION_ONLY: libc::c_int = 9;

1081
src/contact.rs Normal file

File diff suppressed because it is too large Load Diff

View File

@@ -1,44 +1,47 @@
use std::sync::{Arc, Condvar, Mutex, RwLock};
use crate::chat::*;
use crate::constants::*;
use crate::dc_array::*;
use crate::dc_chat::*;
use crate::dc_contact::*;
use crate::dc_job::*;
use crate::dc_jobthread::*;
use crate::contact::*;
use crate::dc_loginparam::*;
use crate::dc_lot::dc_lot_t;
use crate::dc_move::*;
use crate::dc_msg::*;
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::lot::Lot;
use crate::message::*;
use crate::param::Params;
use crate::smtp::*;
use crate::sql::Sql;
use crate::types::*;
use crate::x::*;
use std::ptr;
use std::path::PathBuf;
#[repr(C)]
pub struct Context {
pub userdata: *mut libc::c_void,
pub dbfile: Arc<RwLock<*mut libc::c_char>>,
pub dbfile: Arc<RwLock<Option<PathBuf>>>,
pub blobdir: Arc<RwLock<*mut libc::c_char>>,
pub sql: Sql,
pub inbox: Arc<RwLock<Imap>>,
pub perform_inbox_jobs_needed: Arc<RwLock<i32>>,
pub probe_imap_network: Arc<RwLock<i32>>,
pub sentbox_thread: Arc<RwLock<dc_jobthread_t>>,
pub mvbox_thread: Arc<RwLock<dc_jobthread_t>>,
pub perform_inbox_jobs_needed: Arc<RwLock<bool>>,
pub probe_imap_network: Arc<RwLock<bool>>,
pub sentbox_thread: Arc<RwLock<JobThread>>,
pub mvbox_thread: Arc<RwLock<JobThread>>,
pub smtp: Arc<Mutex<Smtp>>,
pub smtp_state: Arc<(Mutex<SmtpState>, Condvar)>,
pub oauth2_critical: Arc<Mutex<()>>,
pub cb: Option<dc_callback_t>,
pub os_name: *mut libc::c_char,
pub os_name: Option<String>,
pub cmdline_sel_chat_id: Arc<RwLock<u32>>,
pub bob: Arc<RwLock<BobStatus>>,
pub last_smeared_timestamp: Arc<RwLock<i64>>,
pub running_state: Arc<RwLock<RunningState>>,
/// Mutex to avoid generating the key for the user more than once.
pub generating_key_mutex: Mutex<()>,
}
unsafe impl std::marker::Send for Context {}
@@ -52,15 +55,17 @@ pub struct RunningState {
impl Context {
pub fn has_dbfile(&self) -> bool {
!self.get_dbfile().is_null()
self.get_dbfile().is_some()
}
pub fn has_blobdir(&self) -> bool {
!self.get_blobdir().is_null()
}
pub fn get_dbfile(&self) -> *const libc::c_char {
*self.dbfile.clone().read().unwrap()
pub fn get_dbfile(&self) -> Option<PathBuf> {
(*self.dbfile.clone().read().unwrap())
.as_ref()
.map(|x| x.clone())
}
pub fn get_blobdir(&self) -> *const libc::c_char {
@@ -76,6 +81,14 @@ impl Context {
}
}
impl Drop for Context {
fn drop(&mut self) {
unsafe {
dc_close(&self);
}
}
}
impl Default for RunningState {
fn default() -> Self {
RunningState {
@@ -85,57 +98,31 @@ impl Default for RunningState {
}
}
#[derive(Debug, PartialEq, Eq)]
#[derive(Default)]
pub struct BobStatus {
pub expects: i32,
pub status: i32,
pub qr_scan: *mut dc_lot_t,
}
impl Default for BobStatus {
fn default() -> Self {
BobStatus {
expects: 0,
status: 0,
qr_scan: std::ptr::null_mut(),
}
}
pub qr_scan: Option<Lot>,
}
#[derive(Default, Debug)]
pub struct SmtpState {
pub idle: bool,
pub suspended: i32,
pub doing_jobs: i32,
pub suspended: bool,
pub doing_jobs: bool,
pub perform_jobs_needed: i32,
pub probe_network: i32,
}
// location handling
#[derive(Copy, Clone)]
#[repr(C)]
pub struct _dc_location {
pub location_id: uint32_t,
pub latitude: libc::c_double,
pub longitude: libc::c_double,
pub accuracy: libc::c_double,
pub timestamp: i64,
pub contact_id: uint32_t,
pub msg_id: uint32_t,
pub chat_id: uint32_t,
pub marker: *mut libc::c_char,
pub independent: uint32_t,
pub probe_network: bool,
}
// create/open/config/information
pub fn dc_context_new(
cb: Option<dc_callback_t>,
userdata: *mut libc::c_void,
os_name: *const libc::c_char,
os_name: Option<String>,
) -> Context {
let context = Context {
Context {
blobdir: Arc::new(RwLock::new(std::ptr::null_mut())),
dbfile: Arc::new(RwLock::new(std::ptr::null_mut())),
dbfile: Arc::new(RwLock::new(None)),
inbox: Arc::new(RwLock::new({
Imap::new(
cb_get_config,
@@ -146,7 +133,7 @@ pub fn dc_context_new(
})),
userdata,
cb,
os_name: unsafe { dc_strdup_keep_null(os_name) },
os_name,
running_state: Arc::new(RwLock::new(Default::default())),
sql: Sql::new(),
smtp: Arc::new(Mutex::new(Smtp::new())),
@@ -155,7 +142,7 @@ pub fn dc_context_new(
bob: Arc::new(RwLock::new(Default::default())),
last_smeared_timestamp: Arc::new(RwLock::new(0)),
cmdline_sel_chat_id: Arc::new(RwLock::new(0)),
sentbox_thread: Arc::new(RwLock::new(dc_jobthread_init(
sentbox_thread: Arc::new(RwLock::new(JobThread::new(
"SENTBOX",
"configured_sentbox_folder",
Imap::new(
@@ -165,7 +152,7 @@ pub fn dc_context_new(
cb_receive_imf,
),
))),
mvbox_thread: Arc::new(RwLock::new(dc_jobthread_init(
mvbox_thread: Arc::new(RwLock::new(JobThread::new(
"MVBOX",
"configured_mvbox_folder",
Imap::new(
@@ -175,10 +162,10 @@ pub fn dc_context_new(
cb_receive_imf,
),
))),
probe_imap_network: Arc::new(RwLock::new(0)),
perform_inbox_jobs_needed: Arc::new(RwLock::new(0)),
};
context
probe_imap_network: Arc::new(RwLock::new(false)),
perform_inbox_jobs_needed: Arc::new(RwLock::new(false)),
generating_key_mutex: Mutex::new(()),
}
}
unsafe fn cb_receive_imf(
@@ -207,7 +194,7 @@ unsafe fn cb_precheck_imf(
) -> libc::c_int {
let mut rfc724_mid_exists: libc::c_int = 0i32;
let msg_id: uint32_t;
let mut old_server_folder: *mut libc::c_char = 0 as *mut libc::c_char;
let mut old_server_folder: *mut libc::c_char = ptr::null_mut();
let mut old_server_uid: uint32_t = 0i32 as uint32_t;
let mut mark_seen: libc::c_int = 0i32;
msg_id = dc_rfc724_mid_exists(
@@ -235,33 +222,28 @@ unsafe fn cb_precheck_imf(
"[move] detected moved message {}",
as_str(rfc724_mid),
);
dc_update_msg_move_state(context, rfc724_mid, DC_MOVE_STATE_STAY);
dc_update_msg_move_state(context, rfc724_mid, MoveState::Stay);
}
if as_str(old_server_folder) != server_folder || old_server_uid != server_uid {
dc_update_server_uid(context, rfc724_mid, server_folder, server_uid);
}
dc_do_heuristics_moves(context, server_folder, msg_id);
do_heuristics_moves(context, server_folder, msg_id);
if 0 != mark_seen {
dc_job_add(
job_add(
context,
130i32,
Action::MarkseenMsgOnImap,
msg_id as libc::c_int,
0 as *const libc::c_char,
0i32,
Params::new(),
0,
);
}
}
free(old_server_folder as *mut libc::c_void);
return rfc724_mid_exists;
rfc724_mid_exists
}
unsafe fn cb_set_config(context: &Context, key: *const libc::c_char, value: *const libc::c_char) {
let v = if value.is_null() {
None
} else {
Some(as_str(value))
};
context.sql.set_config(context, as_str(key), v);
fn cb_set_config(context: &Context, key: &str, value: Option<&str>) {
context.sql.set_config(context, key, value).ok();
}
/* *
@@ -271,34 +253,11 @@ unsafe fn cb_set_config(context: &Context, key: *const libc::c_char, value: *con
*
* @private @memberof Context
*/
unsafe fn cb_get_config(
context: &Context,
key: *const libc::c_char,
def: *const libc::c_char,
) -> *mut libc::c_char {
let d = if def.is_null() {
None
} else {
Some(as_str(def))
};
let res = context.sql.get_config(context, as_str(key), d);
if let Some(res) = res {
strdup(to_cstring(res).as_ptr())
} else {
std::ptr::null_mut()
}
}
pub unsafe fn dc_context_unref(context: &mut Context) {
if 0 != dc_is_open(context) {
dc_close(context);
}
free(context.os_name as *mut libc::c_void);
fn cb_get_config(context: &Context, key: &str) -> Option<String> {
context.sql.get_config(context, key)
}
pub unsafe fn dc_close(context: &Context) {
println!("disconnecting inbox watch yooaa");
info!(context, 0, "disconnecting INBOX-watch",);
context.inbox.read().unwrap().disconnect(context);
info!(context, 0, "disconnecting sentbox-thread",);
@@ -321,50 +280,42 @@ pub unsafe fn dc_close(context: &Context) {
context.sql.close(context);
let mut dbfile = context.dbfile.write().unwrap();
free(*dbfile as *mut libc::c_void);
*dbfile = 0 as *mut libc::c_char;
*dbfile = None;
let mut blobdir = context.blobdir.write().unwrap();
free(*blobdir as *mut libc::c_void);
*blobdir = 0 as *mut libc::c_char;
*blobdir = ptr::null_mut();
}
pub unsafe fn dc_is_open(context: &Context) -> libc::c_int {
match context.sql.is_open() {
true => 1,
false => 0,
}
context.sql.is_open() as libc::c_int
}
pub unsafe fn dc_get_userdata(context: &mut Context) -> *mut libc::c_void {
context.userdata as *mut _
}
pub unsafe fn dc_open(
context: &Context,
dbfile: *const libc::c_char,
blobdir: *const libc::c_char,
) -> libc::c_int {
let mut success = 0;
pub unsafe fn dc_open(context: &Context, dbfile: &str, blobdir: Option<&str>) -> bool {
let mut success = false;
if 0 != dc_is_open(context) {
return 0;
return false;
}
if !dbfile.is_null() {
*context.dbfile.write().unwrap() = dc_strdup(dbfile);
if !blobdir.is_null() && 0 != *blobdir.offset(0isize) as libc::c_int {
let dir = dc_strdup(blobdir);
dc_ensure_no_slash(dir);
*context.blobdir.write().unwrap() = dir;
} else {
let dir = dc_mprintf(b"%s-blobs\x00" as *const u8 as *const libc::c_char, dbfile);
dc_create_folder(context, dir);
*context.blobdir.write().unwrap() = dir;
}
// Create/open sqlite database, this may already use the blobdir
if context.sql.open(context, as_path(dbfile), 0) {
success = 1i32
}
*context.dbfile.write().unwrap() = Some(PathBuf::from(dbfile));
let blobdir = blobdir.unwrap_or_default();
if !blobdir.is_empty() {
let dir = dc_ensure_no_slash_safe(blobdir).strdup();
*context.blobdir.write().unwrap() = dir;
} else {
let dir = dbfile.to_string() + "-blobs";
dc_create_folder(context, &dir);
*context.blobdir.write().unwrap() = dir.strdup();
}
if 0 == success {
// Create/open sqlite database, this may already use the blobdir
let dbfile_path = std::path::Path::new(dbfile);
if context.sql.open(context, dbfile_path, 0) {
success = true
}
if !success {
dc_close(context);
}
success
@@ -382,15 +333,27 @@ 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 displayname = context.sql.get_config(context, "displayname", None);
let chats = dc_get_chat_cnt(context) as usize;
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;
let deaddrop_msgs = dc_get_deaddrop_msg_cnt(context) as usize;
let contacts = dc_get_real_contact_cnt(context) as usize;
let is_configured = context.sql.get_config_int(context, "configured", 0);
let dbversion = context.sql.get_config_int(context, "dbversion", 0);
let e2ee_enabled = context.sql.get_config_int(context, "e2ee_enabled", 1);
let mdns_enabled = context.sql.get_config_int(context, "mdns_enabled", 1);
let contacts = Contact::get_real_cnt(context) as usize;
let is_configured = context
.sql
.get_config_int(context, "configured")
.unwrap_or_default();
let dbversion = context
.sql
.get_config_int(context, "dbversion")
.unwrap_or_default();
let e2ee_enabled = context
.sql
.get_config_int(context, "e2ee_enabled")
.unwrap_or_else(|| 1);
let mdns_enabled = context
.sql
.get_config_int(context, "mdns_enabled")
.unwrap_or_else(|| 1);
let prv_key_cnt: Option<isize> = context.sql.query_row_col(
context,
@@ -415,19 +378,34 @@ pub unsafe fn dc_get_info(context: &Context) -> *mut libc::c_char {
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", 1);
let sentbox_watch = context.sql.get_config_int(context, "sentbox_watch", 1);
let mvbox_watch = context.sql.get_config_int(context, "mvbox_watch", 1);
let mvbox_move = context.sql.get_config_int(context, "mvbox_move", 1);
let folders_configured = context.sql.get_config_int(context, "folders_configured", 0);
let configured_sentbox_folder =
context
.sql
.get_config(context, "configured_sentbox_folder", Some("<unset>"));
let configured_mvbox_folder =
context
.sql
.get_config(context, "configured_mvbox_folder", Some("<unset>"));
let inbox_watch = context
.sql
.get_config_int(context, "inbox_watch")
.unwrap_or_else(|| 1);
let sentbox_watch = context
.sql
.get_config_int(context, "sentbox_watch")
.unwrap_or_else(|| 1);
let mvbox_watch = context
.sql
.get_config_int(context, "mvbox_watch")
.unwrap_or_else(|| 1);
let mvbox_move = context
.sql
.get_config_int(context, "mvbox_move")
.unwrap_or_else(|| 1);
let folders_configured = context
.sql
.get_config_int(context, "folders_configured")
.unwrap_or_default();
let configured_sentbox_folder = context
.sql
.get_config(context, "configured_sentbox_folder")
.unwrap_or_else(|| "<unset>".to_string());
let configured_mvbox_folder = context
.sql
.get_config(context, "configured_mvbox_folder")
.unwrap_or_else(|| "<unset>".to_string());
let res = format!(
"deltachat_core_version=v{}\n\
@@ -458,7 +436,7 @@ pub unsafe fn dc_get_info(context: &Context) -> *mut libc::c_char {
public_key_count={}\n\
fingerprint={}\n\
level=awesome\n",
as_str(DC_VERSION_STR as *const u8 as *const _),
&*DC_VERSION_STR,
rusqlite::version(),
sqlite3_threadsafe(),
// arch
@@ -467,11 +445,10 @@ pub unsafe fn dc_get_info(context: &Context) -> *mut libc::c_char {
real_msgs,
deaddrop_msgs,
contacts,
if context.has_dbfile() {
as_str(context.get_dbfile())
} else {
unset
},
context
.get_dbfile()
.as_ref()
.map_or(unset, |p| p.to_str().unwrap()),
dbversion,
if context.has_blobdir() {
as_str(context.get_blobdir())
@@ -487,8 +464,8 @@ pub unsafe fn dc_get_info(context: &Context) -> *mut libc::c_char {
mvbox_watch,
mvbox_move,
folders_configured,
configured_sentbox_folder.unwrap_or_default(),
configured_mvbox_folder.unwrap_or_default(),
configured_sentbox_folder,
configured_mvbox_folder,
mdns_enabled,
e2ee_enabled,
prv_key_cnt.unwrap_or_default(),
@@ -496,14 +473,14 @@ pub unsafe fn dc_get_info(context: &Context) -> *mut libc::c_char {
fingerprint_str,
);
strdup(to_cstring(res).as_ptr())
res.strdup()
}
pub unsafe fn dc_get_version_str() -> *mut libc::c_char {
dc_strdup(DC_VERSION_STR as *const u8 as *const libc::c_char)
(&*DC_VERSION_STR).strdup()
}
pub fn dc_get_fresh_msgs(context: &Context) -> *mut dc_array_t {
pub fn dc_get_fresh_msgs(context: &Context) -> Vec<u32> {
let show_deaddrop = 0;
context
@@ -518,11 +495,10 @@ pub fn dc_get_fresh_msgs(context: &Context) -> *mut dc_array_t {
&[10, 9, if 0 != show_deaddrop { 2 } else { 0 }],
|row| row.get(0),
|rows| {
let ret = unsafe { dc_array_new(128 as size_t) };
let mut ret = Vec::new();
for row in rows {
let id = row?;
unsafe { dc_array_add_id(ret, id) };
let id: u32 = row?;
ret.push(id);
}
Ok(ret)
},
@@ -530,18 +506,19 @@ pub fn dc_get_fresh_msgs(context: &Context) -> *mut dc_array_t {
.unwrap()
}
#[allow(non_snake_case)]
pub fn dc_search_msgs(
context: &Context,
chat_id: uint32_t,
query: *const libc::c_char,
) -> *mut dc_array_t {
) -> Vec<u32> {
if query.is_null() {
return std::ptr::null_mut();
return Vec::new();
}
let real_query = to_string(query).trim().to_string();
if real_query.is_empty() {
return std::ptr::null_mut();
return Vec::new();
}
let strLikeInText = format!("%{}%", &real_query);
let strLikeBeg = format!("{}%", &real_query);
@@ -557,31 +534,21 @@ pub fn dc_search_msgs(
AND ct.blocked=0 AND (m.txt LIKE ? OR ct.name LIKE ?) ORDER BY m.timestamp DESC,m.id DESC;"
};
let ret = unsafe { dc_array_new(100 as size_t) };
let success = context
context
.sql
.query_map(
query,
params![chat_id as libc::c_int, &strLikeInText, &strLikeBeg],
|row| row.get::<_, i32>(0),
|rows| {
let mut ret = Vec::new();
for id in rows {
unsafe { dc_array_add_id(ret, id? as u32) };
ret.push(id? as u32);
}
Ok(())
Ok(ret)
},
)
.is_ok();
if success {
return ret;
}
if !ret.is_null() {
unsafe { dc_array_unref(ret) };
}
std::ptr::null_mut()
.unwrap_or_default()
}
pub fn dc_is_inbox(_context: &Context, folder_name: impl AsRef<str>) -> bool {
@@ -589,9 +556,7 @@ pub fn dc_is_inbox(_context: &Context, folder_name: impl AsRef<str>) -> bool {
}
pub fn dc_is_sentbox(context: &Context, folder_name: impl AsRef<str>) -> bool {
let sentbox_name = context
.sql
.get_config(context, "configured_sentbox_folder", None);
let sentbox_name = context.sql.get_config(context, "configured_sentbox_folder");
if let Some(name) = sentbox_name {
name == folder_name.as_ref()
} else {
@@ -600,9 +565,7 @@ pub fn dc_is_sentbox(context: &Context, folder_name: impl AsRef<str>) -> bool {
}
pub fn dc_is_mvbox(context: &Context, folder_name: impl AsRef<str>) -> bool {
let mvbox_name = context
.sql
.get_config(context, "configured_mvbox_folder", None);
let mvbox_name = context.sql.get_config(context, "configured_mvbox_folder");
if let Some(name) = mvbox_name {
name == folder_name.as_ref()
@@ -610,3 +573,63 @@ pub fn dc_is_mvbox(context: &Context, folder_name: impl AsRef<str>) -> bool {
false
}
}
pub fn do_heuristics_moves(context: &Context, folder: &str, msg_id: u32) {
if context
.sql
.get_config_int(context, "mvbox_move")
.unwrap_or_else(|| 1)
== 0
{
return;
}
if !dc_is_inbox(context, folder) && !dc_is_sentbox(context, folder) {
return;
}
if let Ok(msg) = dc_msg_new_load(context, msg_id) {
if dc_msg_is_setupmessage(&msg) {
// do not move setup messages;
// there may be a non-delta device that wants to handle it
return;
}
if dc_is_mvbox(context, folder) {
dc_update_msg_move_state(context, msg.rfc724_mid, MoveState::Stay);
}
// 1 = dc message, 2 = reply to dc message
if 0 != msg.is_dc_message {
job_add(
context,
Action::MoveMsg,
msg.id as libc::c_int,
Params::new(),
0,
);
dc_update_msg_move_state(context, msg.rfc724_mid, MoveState::Moving);
}
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn no_crashes_on_context_deref() {
let ctx = dc_context_new(None, std::ptr::null_mut(), Some("Test OS".into()));
std::mem::drop(ctx);
}
#[test]
fn test_context_double_close() {
let ctx = dc_context_new(None, std::ptr::null_mut(), None);
unsafe {
dc_close(&ctx);
dc_close(&ctx);
}
std::mem::drop(ctx);
}
}

View File

@@ -1,495 +1,163 @@
use crate::context::*;
use crate::dc_tools::*;
use crate::location::Location;
use crate::types::*;
use crate::x::*;
/* * the structure behind dc_array_t */
#[derive(Copy, Clone)]
#[repr(C)]
pub struct dc_array_t {
pub magic: uint32_t,
pub allocated: size_t,
pub count: size_t,
pub type_0: libc::c_int,
pub array: *mut uintptr_t,
#[derive(Clone)]
#[allow(non_camel_case_types)]
pub enum dc_array_t {
Locations(Vec<Location>),
Uint(Vec<u32>),
}
/**
* @class dc_array_t
*
* An object containing a simple array.
* This object is used in several places where functions need to return an array.
* The items of the array are typically IDs.
* To free an array object, use dc_array_unref().
*/
pub unsafe fn dc_array_unref(mut array: *mut dc_array_t) {
if array.is_null() || (*array).magic != 0xa11aai32 as libc::c_uint {
return;
impl dc_array_t {
pub fn new(capacity: usize) -> Self {
dc_array_t::Uint(Vec::with_capacity(capacity))
}
if (*array).type_0 == 1i32 {
dc_array_free_ptr(array);
}
free((*array).array as *mut libc::c_void);
(*array).magic = 0i32 as uint32_t;
free(array as *mut libc::c_void);
}
pub unsafe fn dc_array_free_ptr(array: *mut dc_array_t) {
if array.is_null() || (*array).magic != 0xa11aai32 as libc::c_uint {
return;
/// Constructs a new, empty `dc_array_t` holding locations with specified `capacity`.
pub fn new_locations(capacity: usize) -> Self {
dc_array_t::Locations(Vec::with_capacity(capacity))
}
let mut i: size_t = 0i32 as size_t;
while i < (*array).count {
if (*array).type_0 == 1i32 {
free(
(*(*(*array).array.offset(i as isize) as *mut _dc_location)).marker
as *mut libc::c_void,
);
pub fn add_id(&mut self, item: uint32_t) {
if let Self::Uint(array) = self {
array.push(item);
} else {
panic!("Attempt to add id to array of other type");
}
free(*(*array).array.offset(i as isize) as *mut libc::c_void);
*(*array).array.offset(i as isize) = 0i32 as uintptr_t;
i = i.wrapping_add(1)
}
}
pub unsafe fn dc_array_add_uint(mut array: *mut dc_array_t, item: uintptr_t) {
if array.is_null() || (*array).magic != 0xa11aai32 as libc::c_uint {
return;
}
if (*array).count == (*array).allocated {
let newsize = (*array).allocated.wrapping_mul(2).wrapping_add(10);
(*array).array = realloc(
(*array).array as *mut libc::c_void,
(newsize).wrapping_mul(::std::mem::size_of::<uintptr_t>()),
) as *mut uintptr_t;
assert!(!(*array).array.is_null());
(*array).allocated = newsize as size_t
}
*(*array).array.offset((*array).count as isize) = item;
(*array).count = (*array).count.wrapping_add(1);
}
pub unsafe fn dc_array_add_id(array: *mut dc_array_t, item: uint32_t) {
dc_array_add_uint(array, item as uintptr_t);
}
pub unsafe fn dc_array_add_ptr(array: *mut dc_array_t, item: *mut libc::c_void) {
dc_array_add_uint(array, item as uintptr_t);
}
pub unsafe fn dc_array_get_cnt(array: *const dc_array_t) -> size_t {
if array.is_null() || (*array).magic != 0xa11aai32 as libc::c_uint {
return 0i32 as size_t;
}
(*array).count
}
pub unsafe fn dc_array_get_uint(array: *const dc_array_t, index: size_t) -> uintptr_t {
if array.is_null() || (*array).magic != 0xa11aai32 as libc::c_uint || index >= (*array).count {
return 0i32 as uintptr_t;
}
*(*array).array.offset(index as isize)
}
pub unsafe fn dc_array_get_id(array: *const dc_array_t, index: size_t) -> uint32_t {
if array.is_null() || (*array).magic != 0xa11aai32 as libc::c_uint || index >= (*array).count {
return 0i32 as uint32_t;
}
if (*array).type_0 == 1i32 {
return (*(*(*array).array.offset(index as isize) as *mut _dc_location)).location_id;
}
*(*array).array.offset(index as isize) as uint32_t
}
pub unsafe fn dc_array_get_ptr(array: *const dc_array_t, index: size_t) -> *mut libc::c_void {
if array.is_null() || (*array).magic != 0xa11aai32 as libc::c_uint || index >= (*array).count {
return 0 as *mut libc::c_void;
}
*(*array).array.offset(index as isize) as *mut libc::c_void
}
pub unsafe fn dc_array_get_latitude(array: *const dc_array_t, index: size_t) -> libc::c_double {
if array.is_null()
|| (*array).magic != 0xa11aai32 as libc::c_uint
|| index >= (*array).count
|| (*array).type_0 != 1i32
|| *(*array).array.offset(index as isize) == 0
{
return 0i32 as libc::c_double;
}
(*(*(*array).array.offset(index as isize) as *mut _dc_location)).latitude
}
pub unsafe fn dc_array_get_longitude(array: *const dc_array_t, index: size_t) -> libc::c_double {
if array.is_null()
|| (*array).magic != 0xa11aai32 as libc::c_uint
|| index >= (*array).count
|| (*array).type_0 != 1i32
|| *(*array).array.offset(index as isize) == 0
{
return 0i32 as libc::c_double;
}
(*(*(*array).array.offset(index as isize) as *mut _dc_location)).longitude
}
pub unsafe fn dc_array_get_accuracy(array: *const dc_array_t, index: size_t) -> libc::c_double {
if array.is_null()
|| (*array).magic != 0xa11aai32 as libc::c_uint
|| index >= (*array).count
|| (*array).type_0 != 1i32
|| *(*array).array.offset(index as isize) == 0
{
return 0i32 as libc::c_double;
}
(*(*(*array).array.offset(index as isize) as *mut _dc_location)).accuracy
}
pub unsafe fn dc_array_get_timestamp(array: *const dc_array_t, index: size_t) -> i64 {
if array.is_null()
|| (*array).magic != 0xa11aai32 as libc::c_uint
|| index >= (*array).count
|| (*array).type_0 != 1i32
|| *(*array).array.offset(index as isize) == 0
{
return 0;
}
(*(*(*array).array.offset(index as isize) as *mut _dc_location)).timestamp
}
pub unsafe fn dc_array_get_chat_id(array: *const dc_array_t, index: size_t) -> uint32_t {
if array.is_null()
|| (*array).magic != 0xa11aai32 as libc::c_uint
|| index >= (*array).count
|| (*array).type_0 != 1i32
|| *(*array).array.offset(index as isize) == 0
{
return 0i32 as uint32_t;
}
(*(*(*array).array.offset(index as isize) as *mut _dc_location)).chat_id
}
pub unsafe fn dc_array_get_contact_id(array: *const dc_array_t, index: size_t) -> uint32_t {
if array.is_null()
|| (*array).magic != 0xa11aai32 as libc::c_uint
|| index >= (*array).count
|| (*array).type_0 != 1i32
|| *(*array).array.offset(index as isize) == 0
{
return 0i32 as uint32_t;
}
(*(*(*array).array.offset(index as isize) as *mut _dc_location)).contact_id
}
pub unsafe fn dc_array_get_msg_id(array: *const dc_array_t, index: size_t) -> uint32_t {
if array.is_null()
|| (*array).magic != 0xa11aai32 as libc::c_uint
|| index >= (*array).count
|| (*array).type_0 != 1i32
|| *(*array).array.offset(index as isize) == 0
{
return 0i32 as uint32_t;
}
(*(*(*array).array.offset(index as isize) as *mut _dc_location)).msg_id
}
pub unsafe fn dc_array_get_marker(array: *const dc_array_t, index: size_t) -> *mut libc::c_char {
if array.is_null()
|| (*array).magic != 0xa11aai32 as libc::c_uint
|| index >= (*array).count
|| (*array).type_0 != 1i32
|| *(*array).array.offset(index as isize) == 0
{
return 0 as *mut libc::c_char;
}
dc_strdup_keep_null((*(*(*array).array.offset(index as isize) as *mut _dc_location)).marker)
}
/**
* Return the independent-state of the location at the given index.
* Independent locations do not belong to the track of the user.
*
* @memberof dc_array_t
* @param array The array object.
* @param index Index of the item. Must be between 0 and dc_array_get_cnt()-1.
* @return 0=Location belongs to the track of the user,
* 1=Location was reported independently.
*/
pub unsafe fn dc_array_is_independent(array: *const dc_array_t, index: size_t) -> libc::c_int {
if array.is_null()
|| (*array).magic != 0xa11aai32 as libc::c_uint
|| index >= (*array).count
|| (*array).type_0 != 1i32
|| *(*array).array.offset(index as isize) == 0
{
return 0;
}
(*(*(*array).array.offset(index as isize) as *mut _dc_location)).independent as libc::c_int
}
pub unsafe fn dc_array_search_id(
array: *const dc_array_t,
needle: uint32_t,
ret_index: *mut size_t,
) -> bool {
if array.is_null() || (*array).magic != 0xa11aai32 as libc::c_uint {
return false;
}
let data: *mut uintptr_t = (*array).array;
let mut i: size_t = 0;
let cnt: size_t = (*array).count;
while i < cnt {
if *data.offset(i as isize) == needle as size_t {
if !ret_index.is_null() {
*ret_index = i
}
return true;
pub fn add_location(&mut self, location: Location) {
if let Self::Locations(array) = self {
array.push(location)
} else {
panic!("Attempt to add a location to array of other type");
}
i = i.wrapping_add(1)
}
false
}
pub unsafe fn dc_array_get_raw(array: *const dc_array_t) -> *const uintptr_t {
if array.is_null() || (*array).magic != 0xa11aai32 as libc::c_uint {
return 0 as *const uintptr_t;
pub fn get_id(&self, index: usize) -> uint32_t {
match self {
Self::Locations(array) => array[index].location_id,
Self::Uint(array) => array[index] as uint32_t,
}
}
(*array).array
}
pub unsafe fn dc_array_new(initsize: size_t) -> *mut dc_array_t {
dc_array_new_typed(0, initsize)
}
pub unsafe fn dc_array_new_typed(type_0: libc::c_int, initsize: size_t) -> *mut dc_array_t {
let mut array: *mut dc_array_t;
array = calloc(1, ::std::mem::size_of::<dc_array_t>()) as *mut dc_array_t;
assert!(!array.is_null());
(*array).magic = 0xa11aai32 as uint32_t;
(*array).count = 0i32 as size_t;
(*array).allocated = if initsize < 1 { 1 } else { initsize };
(*array).type_0 = type_0;
(*array).array = malloc(
(*array)
.allocated
.wrapping_mul(::std::mem::size_of::<uintptr_t>()),
) as *mut uintptr_t;
if (*array).array.is_null() {
exit(48i32);
pub fn get_location(&self, index: usize) -> &Location {
if let Self::Locations(array) = self {
&array[index]
} else {
panic!("Not an array of locations")
}
}
array
}
pub unsafe fn dc_array_empty(mut array: *mut dc_array_t) {
if array.is_null() || (*array).magic != 0xa11aai32 as libc::c_uint {
return;
pub fn is_empty(&self) -> bool {
match self {
Self::Locations(array) => array.is_empty(),
Self::Uint(array) => array.is_empty(),
}
}
(*array).count = 0i32 as size_t;
}
pub unsafe fn dc_array_duplicate(array: *const dc_array_t) -> *mut dc_array_t {
let mut ret: *mut dc_array_t;
if array.is_null() || (*array).magic != 0xa11aai32 as libc::c_uint {
return 0 as *mut dc_array_t;
/// Returns the number of elements in the array.
pub fn len(&self) -> usize {
match self {
Self::Locations(array) => array.len(),
Self::Uint(array) => array.len(),
}
}
ret = dc_array_new((*array).allocated);
(*ret).count = (*array).count;
memcpy(
(*ret).array as *mut libc::c_void,
(*array).array as *const libc::c_void,
(*array)
.count
.wrapping_mul(::std::mem::size_of::<uintptr_t>()),
);
ret
}
pub unsafe fn dc_array_sort_ids(array: *mut dc_array_t) {
if array.is_null() || (*array).magic != 0xa11aai32 as libc::c_uint || (*array).count <= 1 {
return;
pub fn clear(&mut self) {
match self {
Self::Locations(array) => array.clear(),
Self::Uint(array) => array.clear(),
}
}
qsort(
(*array).array as *mut libc::c_void,
(*array).count,
::std::mem::size_of::<uintptr_t>(),
Some(cmp_intptr_t),
);
}
unsafe extern "C" fn cmp_intptr_t(p1: *const libc::c_void, p2: *const libc::c_void) -> libc::c_int {
let v1: uintptr_t = *(p1 as *mut uintptr_t);
let v2: uintptr_t = *(p2 as *mut uintptr_t);
return if v1 < v2 {
-1i32
} else if v1 > v2 {
1i32
} else {
0i32
};
}
pub unsafe fn dc_array_sort_strings(array: *mut dc_array_t) {
if array.is_null() || (*array).magic != 0xa11aai32 as libc::c_uint || (*array).count <= 1 {
return;
}
qsort(
(*array).array as *mut libc::c_void,
(*array).count,
::std::mem::size_of::<*mut libc::c_char>(),
Some(cmp_strings_t),
);
}
unsafe extern "C" fn cmp_strings_t(
p1: *const libc::c_void,
p2: *const libc::c_void,
) -> libc::c_int {
let v1: *const libc::c_char = *(p1 as *mut *const libc::c_char);
let v2: *const libc::c_char = *(p2 as *mut *const libc::c_char);
strcmp(v1, v2)
}
pub unsafe fn dc_array_get_string(
array: *const dc_array_t,
sep: *const libc::c_char,
) -> *mut libc::c_char {
if array.is_null() || (*array).magic != 0xa11aai32 as libc::c_uint || sep.is_null() {
return dc_strdup(b"\x00" as *const u8 as *const libc::c_char);
}
let cnt = (*array).count as usize;
let slice = std::slice::from_raw_parts((*array).array, cnt);
let sep = as_str(sep);
let res = slice
.iter()
.enumerate()
.fold(String::with_capacity(2 * cnt), |mut res, (i, n)| {
if i == 0 {
res += &n.to_string();
} else {
res += sep;
res += &n.to_string();
pub fn search_id(&self, needle: u32) -> Option<usize> {
if let Self::Uint(array) = self {
for (i, &u) in array.iter().enumerate() {
if u == needle {
return Some(i);
}
}
res
});
strdup(to_cstring(res).as_ptr())
}
/// return comma-separated value-string from integer array
pub unsafe fn dc_arr_to_string(arr: *const uint32_t, cnt: libc::c_int) -> *mut libc::c_char {
if arr.is_null() || cnt == 0 {
return dc_strdup(b"\x00" as *const u8 as *const libc::c_char);
None
} else {
panic!("Attempt to search for id in array of other type");
}
}
let slice = std::slice::from_raw_parts(arr, cnt as usize);
let res = slice.iter().enumerate().fold(
String::with_capacity(2 * cnt as usize),
|mut res, (i, n)| {
if i == 0 {
res += &n.to_string();
} else {
res += ",";
res += &n.to_string();
}
res
},
);
strdup(to_cstring(res).as_ptr())
pub fn sort_ids(&mut self) {
if let dc_array_t::Uint(v) = self {
v.sort();
} else {
panic!("Attempt to sort array of something other than uints");
}
}
pub fn as_ptr(&self) -> *const u32 {
if let dc_array_t::Uint(v) = self {
v.as_ptr()
} else {
panic!("Attempt to convert array of something other than uints to raw");
}
}
}
impl From<Vec<u32>> for dc_array_t {
fn from(array: Vec<u32>) -> Self {
dc_array_t::Uint(array)
}
}
impl From<Vec<Location>> for dc_array_t {
fn from(array: Vec<Location>) -> Self {
dc_array_t::Locations(array)
}
}
#[cfg(test)]
mod tests {
use super::*;
use std::ffi::CStr;
#[test]
fn test_dc_array() {
unsafe {
let arr = dc_array_new(7 as size_t);
assert_eq!(dc_array_get_cnt(arr), 0);
let mut arr = dc_array_t::new(7);
assert!(arr.is_empty());
let mut i: libc::c_int = 0;
while i < 1000 {
dc_array_add_id(arr, (i + 2) as uint32_t);
i += 1
}
assert_eq!(dc_array_get_cnt(arr), 1000);
i = 0;
while i < 1000i32 {
assert_eq!(
dc_array_get_id(arr, i as size_t),
(i + 1i32 * 2i32) as libc::c_uint
);
i += 1
}
assert_eq!(dc_array_get_id(arr, -1i32 as size_t), 0);
assert_eq!(dc_array_get_id(arr, 1000 as size_t), 0);
assert_eq!(dc_array_get_id(arr, 1001 as size_t), 0);
dc_array_empty(arr);
assert_eq!(dc_array_get_cnt(arr), 0);
dc_array_add_id(arr, 13 as uint32_t);
dc_array_add_id(arr, 7 as uint32_t);
dc_array_add_id(arr, 666 as uint32_t);
dc_array_add_id(arr, 0 as uint32_t);
dc_array_add_id(arr, 5000 as uint32_t);
dc_array_sort_ids(arr);
assert_eq!(dc_array_get_id(arr, 0 as size_t), 0);
assert_eq!(dc_array_get_id(arr, 1 as size_t), 7);
assert_eq!(dc_array_get_id(arr, 2 as size_t), 13);
assert_eq!(dc_array_get_id(arr, 3 as size_t), 666);
let str = dc_array_get_string(arr, b"-\x00" as *const u8 as *const libc::c_char);
assert_eq!(
CStr::from_ptr(str as *const libc::c_char).to_str().unwrap(),
"0-7-13-666-5000"
);
free(str as *mut libc::c_void);
dc_array_empty(arr);
dc_array_add_ptr(
arr,
b"XX\x00" as *const u8 as *const libc::c_char as *mut libc::c_void,
);
dc_array_add_ptr(
arr,
b"item1\x00" as *const u8 as *const libc::c_char as *mut libc::c_void,
);
dc_array_add_ptr(
arr,
b"bbb\x00" as *const u8 as *const libc::c_char as *mut libc::c_void,
);
dc_array_add_ptr(
arr,
b"aaa\x00" as *const u8 as *const libc::c_char as *mut libc::c_void,
);
dc_array_sort_strings(arr);
let str = dc_array_get_ptr(arr, 0 as size_t) as *mut libc::c_char;
assert_eq!(CStr::from_ptr(str).to_str().unwrap(), "XX");
let str = dc_array_get_ptr(arr, 1 as size_t) as *mut libc::c_char;
assert_eq!(CStr::from_ptr(str).to_str().unwrap(), "aaa");
let str = dc_array_get_ptr(arr, 2 as size_t) as *mut libc::c_char;
assert_eq!(CStr::from_ptr(str).to_str().unwrap(), "bbb");
let str = dc_array_get_ptr(arr, 3 as size_t) as *mut libc::c_char;
assert_eq!(CStr::from_ptr(str).to_str().unwrap(), "item1");
dc_array_unref(arr);
for i in 0..1000 {
arr.add_id(i + 2);
}
assert_eq!(arr.len(), 1000);
for i in 0..1000 {
assert_eq!(arr.get_id(i), (i + 2) as u32);
}
arr.clear();
assert!(arr.is_empty());
arr.add_id(13);
arr.add_id(7);
arr.add_id(666);
arr.add_id(0);
arr.add_id(5000);
arr.sort_ids();
assert_eq!(arr.get_id(0), 0);
assert_eq!(arr.get_id(1), 7);
assert_eq!(arr.get_id(2), 13);
assert_eq!(arr.get_id(3), 666);
}
#[test]
#[should_panic]
fn test_dc_array_out_of_bounds() {
let mut arr = dc_array_t::new(7);
for i in 0..1000 {
arr.add_id(i + 2);
}
arr.get_id(1000);
}
}

File diff suppressed because it is too large Load Diff

View File

@@ -1,389 +0,0 @@
use crate::context::*;
use crate::dc_array::*;
use crate::dc_chat::*;
use crate::dc_contact::*;
use crate::dc_lot::*;
use crate::dc_msg::*;
use crate::dc_stock::*;
use crate::dc_tools::*;
use crate::types::*;
use crate::x::*;
/* * the structure behind dc_chatlist_t */
#[derive(Copy, Clone)]
#[repr(C)]
pub struct dc_chatlist_t<'a> {
pub magic: uint32_t,
pub context: &'a Context,
pub cnt: size_t,
pub chatNlastmsg_ids: *mut dc_array_t,
}
// handle chatlists
pub unsafe fn dc_get_chatlist<'a>(
context: &'a Context,
listflags: libc::c_int,
query_str: *const libc::c_char,
query_id: uint32_t,
) -> *mut dc_chatlist_t<'a> {
let obj = dc_chatlist_new(context);
if 0 != dc_chatlist_load_from_db(obj, listflags, query_str, query_id) {
return obj;
}
dc_chatlist_unref(obj);
return 0 as *mut dc_chatlist_t;
}
/**
* @class dc_chatlist_t
*
* An object representing a single chatlist in memory.
* Chatlist objects contain chat IDs
* and, if possible, message IDs belonging to them.
* The chatlist object is not updated;
* if you want an update, you have to recreate the object.
*
* For a **typical chat overview**,
* the idea is to get the list of all chats via dc_get_chatlist()
* without any listflags (see below)
* and to implement a "virtual list" or so
* (the count of chats is known by dc_chatlist_get_cnt()).
*
* Only for the items that are in view
* (the list may have several hundreds chats),
* the UI should call dc_chatlist_get_summary() then.
* dc_chatlist_get_summary() provides all elements needed for painting the item.
*
* On a click of such an item,
* the UI should change to the chat view
* and get all messages from this view via dc_get_chat_msgs().
* Again, a "virtual list" is created
* (the count of messages is known)
* and for each messages that is scrolled into view, dc_get_msg() is called then.
*
* Why no listflags?
* Without listflags, dc_get_chatlist() adds the deaddrop
* and the archive "link" automatically as needed.
* The UI can just render these items differently then.
* Although the deaddrop link is currently always the first entry
* and only present on new messages,
* there is the rough idea that it can be optionally always present
* and sorted into the list by date.
* Rendering the deaddrop in the described way
* would not add extra work in the UI then.
*/
pub unsafe fn dc_chatlist_new(context: &Context) -> *mut dc_chatlist_t {
let mut chatlist: *mut dc_chatlist_t;
chatlist = calloc(1, ::std::mem::size_of::<dc_chatlist_t>()) as *mut dc_chatlist_t;
assert!(!chatlist.is_null());
(*chatlist).magic = 0xc4a71157u32;
(*chatlist).context = context;
(*chatlist).chatNlastmsg_ids = dc_array_new(128i32 as size_t);
assert!(!(*chatlist).chatNlastmsg_ids.is_null());
chatlist
}
pub unsafe fn dc_chatlist_unref(mut chatlist: *mut dc_chatlist_t) {
if chatlist.is_null() || (*chatlist).magic != 0xc4a71157u32 {
return;
}
dc_chatlist_empty(chatlist);
dc_array_unref((*chatlist).chatNlastmsg_ids);
(*chatlist).magic = 0i32 as uint32_t;
free(chatlist as *mut libc::c_void);
}
pub unsafe fn dc_chatlist_empty(mut chatlist: *mut dc_chatlist_t) {
if chatlist.is_null() || (*chatlist).magic != 0xc4a71157u32 {
return;
}
(*chatlist).cnt = 0i32 as size_t;
dc_array_empty((*chatlist).chatNlastmsg_ids);
}
/**
* Load a chatlist from the database to the chatlist object.
*
* @private @memberof dc_chatlist_t
*/
// TODO should return bool /rtn
unsafe fn dc_chatlist_load_from_db(
mut chatlist: *mut dc_chatlist_t,
listflags: libc::c_int,
query__: *const libc::c_char,
query_contact_id: u32,
) -> libc::c_int {
if chatlist.is_null() || (*chatlist).magic != 0xc4a71157u32 {
return 0;
}
dc_chatlist_empty(chatlist);
let mut add_archived_link_item = 0;
// select with left join and minimum:
// - the inner select must use `hidden` and _not_ `m.hidden`
// which would refer the outer select and take a lot of time
// - `GROUP BY` is needed several messages may have the same timestamp
// - the list starts with the newest chats
// nb: the query currently shows messages from blocked contacts in groups.
// however, for normal-groups, this is okay as the message is also returned by dc_get_chat_msgs()
// (otherwise it would be hard to follow conversations, wa and tg do the same)
// for the deaddrop, however, they should really be hidden, however, _currently_ the deaddrop is not
// shown at all permanent in the chatlist.
let process_row = |row: &rusqlite::Row| {
let chat_id: i32 = row.get(0)?;
// TODO: verify that it is okay for this to be Null
let msg_id: i32 = row.get(1).unwrap_or_default();
Ok((chat_id, msg_id))
};
let process_rows = |rows: rusqlite::MappedRows<_>| {
for row in rows {
let (id1, id2) = row?;
dc_array_add_id((*chatlist).chatNlastmsg_ids, id1 as u32);
dc_array_add_id((*chatlist).chatNlastmsg_ids, id2 as u32);
}
Ok(())
};
// nb: the query currently shows messages from blocked contacts in groups.
// however, for normal-groups, this is okay as the message is also returned by dc_get_chat_msgs()
// (otherwise it would be hard to follow conversations, wa and tg do the same)
// for the deaddrop, however, they should really be hidden, however, _currently_ the deaddrop is not
// shown at all permanent in the chatlist.
let success = if query_contact_id != 0 {
// show chats shared with a given contact
(*chatlist).context.sql.query_map(
"SELECT c.id, m.id FROM chats c LEFT JOIN msgs m \
ON c.id=m.chat_id \
AND m.timestamp=( SELECT MAX(timestamp) \
FROM msgs WHERE chat_id=c.id \
AND (hidden=0 OR (hidden=1 AND state=19))) WHERE c.id>9 \
AND c.blocked=0 AND c.id IN(SELECT chat_id FROM chats_contacts WHERE contact_id=?) \
GROUP BY c.id ORDER BY IFNULL(m.timestamp,0) DESC, m.id DESC;",
params![query_contact_id as i32],
process_row,
process_rows,
)
} else if 0 != listflags & 0x1 {
// show archived chats
(*chatlist).context.sql.query_map(
"SELECT c.id, m.id FROM chats c LEFT JOIN msgs m \
ON c.id=m.chat_id \
AND m.timestamp=( SELECT MAX(timestamp) \
FROM msgs WHERE chat_id=c.id \
AND (hidden=0 OR (hidden=1 AND state=19))) WHERE c.id>9 \
AND c.blocked=0 AND c.archived=1 GROUP BY c.id \
ORDER BY IFNULL(m.timestamp,0) DESC, m.id DESC;",
params![],
process_row,
process_rows,
)
} else if query__.is_null() {
// show normal chatlist
if 0 == listflags & 0x2 {
let last_deaddrop_fresh_msg_id = get_last_deaddrop_fresh_msg((*chatlist).context);
if last_deaddrop_fresh_msg_id > 0 {
dc_array_add_id((*chatlist).chatNlastmsg_ids, 1);
dc_array_add_id((*chatlist).chatNlastmsg_ids, last_deaddrop_fresh_msg_id);
}
add_archived_link_item = 1;
}
(*chatlist).context.sql.query_map(
"SELECT c.id, m.id FROM chats c \
LEFT JOIN msgs m \
ON c.id=m.chat_id \
AND m.timestamp=( SELECT MAX(timestamp) \
FROM msgs WHERE chat_id=c.id \
AND (hidden=0 OR (hidden=1 AND state=19))) WHERE c.id>9 \
AND c.blocked=0 AND c.archived=0 \
GROUP BY c.id \
ORDER BY IFNULL(m.timestamp,0) DESC, m.id DESC;",
params![],
process_row,
process_rows,
)
} else {
let query = to_string(query__).trim().to_string();
if query.is_empty() {
return 1;
} else {
let strLikeCmd = format!("%{}%", query);
(*chatlist).context.sql.query_map(
"SELECT c.id, m.id FROM chats c LEFT JOIN msgs m \
ON c.id=m.chat_id \
AND m.timestamp=( SELECT MAX(timestamp) \
FROM msgs WHERE chat_id=c.id \
AND (hidden=0 OR (hidden=1 AND state=19))) WHERE c.id>9 \
AND c.blocked=0 AND c.name LIKE ? \
GROUP BY c.id ORDER BY IFNULL(m.timestamp,0) DESC, m.id DESC;",
params![strLikeCmd],
process_row,
process_rows,
)
}
};
if 0 != add_archived_link_item && dc_get_archived_cnt((*chatlist).context) > 0 {
if dc_array_get_cnt((*chatlist).chatNlastmsg_ids) == 0 && 0 != listflags & 0x4 {
dc_array_add_id((*chatlist).chatNlastmsg_ids, 7);
dc_array_add_id((*chatlist).chatNlastmsg_ids, 0);
}
dc_array_add_id((*chatlist).chatNlastmsg_ids, 6);
dc_array_add_id((*chatlist).chatNlastmsg_ids, 0);
}
(*chatlist).cnt = dc_array_get_cnt((*chatlist).chatNlastmsg_ids) / 2;
match success {
Ok(_) => 1,
Err(err) => {
error!(
(*chatlist).context,
0, "chatlist: failed to load from database: {:?}", err
);
0
}
}
}
// Context functions to work with chatlist
pub fn dc_get_archived_cnt(context: &Context) -> libc::c_int {
context
.sql
.query_row_col(
context,
"SELECT COUNT(*) FROM chats WHERE blocked=0 AND archived=1;",
params![],
0,
)
.unwrap_or_default()
}
fn get_last_deaddrop_fresh_msg(context: &Context) -> u32 {
// we have an index over the state-column, this should be sufficient as there are typically only few fresh messages
context
.sql
.query_row_col(
context,
"SELECT m.id FROM msgs m LEFT JOIN chats c ON c.id=m.chat_id \
WHERE m.state=10 \
AND m.hidden=0 \
AND c.blocked=2 \
ORDER BY m.timestamp DESC, m.id DESC;",
params![],
0,
)
.unwrap_or_default()
}
pub unsafe fn dc_chatlist_get_cnt(chatlist: *const dc_chatlist_t) -> size_t {
if chatlist.is_null() || (*chatlist).magic != 0xc4a71157u32 {
return 0i32 as size_t;
}
(*chatlist).cnt
}
pub unsafe fn dc_chatlist_get_chat_id(chatlist: *const dc_chatlist_t, index: size_t) -> uint32_t {
if chatlist.is_null()
|| (*chatlist).magic != 0xc4a71157u32
|| (*chatlist).chatNlastmsg_ids.is_null()
|| index >= (*chatlist).cnt
{
return 0i32 as uint32_t;
}
dc_array_get_id((*chatlist).chatNlastmsg_ids, index.wrapping_mul(2))
}
pub unsafe fn dc_chatlist_get_msg_id(chatlist: *const dc_chatlist_t, index: size_t) -> uint32_t {
if chatlist.is_null()
|| (*chatlist).magic != 0xc4a71157u32
|| (*chatlist).chatNlastmsg_ids.is_null()
|| index >= (*chatlist).cnt
{
return 0i32 as uint32_t;
}
dc_array_get_id(
(*chatlist).chatNlastmsg_ids,
index.wrapping_mul(2).wrapping_add(1),
)
}
pub unsafe fn dc_chatlist_get_summary<'a>(
chatlist: *const dc_chatlist_t<'a>,
index: size_t,
mut chat: *mut Chat<'a>,
) -> *mut dc_lot_t {
let current_block: u64;
/* The summary is created by the chat, not by the last message.
This is because we may want to display drafts here or stuff as
"is typing".
Also, sth. as "No messages" would not work if the summary comes from a
message. */
/* the function never returns NULL */
let mut ret: *mut dc_lot_t = dc_lot_new();
let lastmsg_id: uint32_t;
let mut lastmsg: *mut dc_msg_t = 0 as *mut dc_msg_t;
let mut lastcontact: *mut dc_contact_t = 0 as *mut dc_contact_t;
let mut chat_to_delete: *mut Chat = 0 as *mut Chat;
if chatlist.is_null() || (*chatlist).magic != 0xc4a71157u32 || index >= (*chatlist).cnt {
(*ret).text2 = dc_strdup(b"ErrBadChatlistIndex\x00" as *const u8 as *const libc::c_char)
} else {
lastmsg_id = dc_array_get_id(
(*chatlist).chatNlastmsg_ids,
index.wrapping_mul(2).wrapping_add(1),
);
if chat.is_null() {
chat = dc_chat_new((*chatlist).context);
chat_to_delete = chat;
if !dc_chat_load_from_db(
chat,
dc_array_get_id((*chatlist).chatNlastmsg_ids, index.wrapping_mul(2)),
) {
(*ret).text2 =
dc_strdup(b"ErrCannotReadChat\x00" as *const u8 as *const libc::c_char);
current_block = 3777403817673069519;
} else {
current_block = 7651349459974463963;
}
} else {
current_block = 7651349459974463963;
}
match current_block {
3777403817673069519 => {}
_ => {
if 0 != lastmsg_id {
lastmsg = dc_msg_new_untyped((*chatlist).context);
dc_msg_load_from_db(lastmsg, (*chatlist).context, lastmsg_id);
if (*lastmsg).from_id != 1i32 as libc::c_uint
&& ((*chat).type_0 == 120i32 || (*chat).type_0 == 130i32)
{
lastcontact = dc_contact_new((*chatlist).context);
dc_contact_load_from_db(
lastcontact,
&(*chatlist).context.sql,
(*lastmsg).from_id,
);
}
}
if (*chat).id == 6i32 as libc::c_uint {
(*ret).text2 = dc_strdup(0 as *const libc::c_char)
} else if lastmsg.is_null() || (*lastmsg).from_id == 0i32 as libc::c_uint {
(*ret).text2 = dc_stock_str((*chatlist).context, 1i32)
} else {
dc_lot_fill(ret, lastmsg, chat, lastcontact, (*chatlist).context);
}
}
}
}
dc_msg_unref(lastmsg);
dc_contact_unref(lastcontact);
dc_chat_unref(chat_to_delete);
ret
}

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

View File

@@ -1,8 +1,6 @@
use lazy_static::lazy_static;
use crate::dc_saxparser::*;
use crate::dc_tools::*;
use crate::x::*;
use quick_xml;
use quick_xml::events::{BytesEnd, BytesStart, BytesText};
lazy_static! {
static ref LINE_RE: regex::Regex = regex::Regex::new(r"(\r?\n)+").unwrap();
@@ -11,7 +9,7 @@ lazy_static! {
struct Dehtml {
strbuilder: String,
add_text: AddText,
last_href: *mut libc::c_char,
last_href: Option<String>,
}
#[derive(Debug, PartialEq)]
@@ -23,77 +21,88 @@ enum AddText {
// dc_dehtml() returns way too many lineends; however, an optimisation on this issue is not needed as
// the lineends are typically remove in further processing by the caller
pub unsafe fn dc_dehtml(buf_terminated: *mut libc::c_char) -> *mut libc::c_char {
dc_trim(buf_terminated);
if *buf_terminated.offset(0isize) as libc::c_int == 0i32 {
return dc_strdup(b"\x00" as *const u8 as *const libc::c_char);
pub fn dc_dehtml(buf_terminated: &str) -> String {
let buf_terminated = buf_terminated.trim();
if buf_terminated.is_empty() {
return "".into();
}
let mut dehtml = Dehtml {
strbuilder: String::with_capacity(strlen(buf_terminated)),
strbuilder: String::with_capacity(buf_terminated.len()),
add_text: AddText::YesRemoveLineEnds,
last_href: 0 as *mut libc::c_char,
last_href: None,
};
let mut saxparser = dc_saxparser_t {
starttag_cb: None,
endtag_cb: None,
text_cb: None,
userdata: 0 as *mut libc::c_void,
};
dc_saxparser_init(
&mut saxparser,
&mut dehtml as *mut Dehtml as *mut libc::c_void,
);
dc_saxparser_set_tag_handler(
&mut saxparser,
Some(dehtml_starttag_cb),
Some(dehtml_endtag_cb),
);
dc_saxparser_set_text_handler(&mut saxparser, Some(dehtml_text_cb));
dc_saxparser_parse(&mut saxparser, buf_terminated);
free(dehtml.last_href as *mut libc::c_void);
strdup(to_cstring(dehtml.strbuilder).as_ptr())
let mut reader = quick_xml::Reader::from_str(buf_terminated);
let mut buf = Vec::new();
loop {
match reader.read_event(&mut buf) {
Ok(quick_xml::events::Event::Start(ref e)) => {
dehtml_starttag_cb(e, &mut dehtml, &reader)
}
Ok(quick_xml::events::Event::End(ref e)) => dehtml_endtag_cb(e, &mut dehtml),
Ok(quick_xml::events::Event::Text(ref e)) => dehtml_text_cb(e, &mut dehtml),
Ok(quick_xml::events::Event::CData(ref e)) => dehtml_cdata_cb(e, &mut dehtml),
Err(e) => {
eprintln!(
"Parse html error: Error at position {}: {:?}",
reader.buffer_position(),
e
);
}
Ok(quick_xml::events::Event::Eof) => break,
_ => (),
}
buf.clear();
}
dehtml.strbuilder
}
unsafe fn dehtml_text_cb(
userdata: *mut libc::c_void,
text: *const libc::c_char,
_len: libc::c_int,
) {
let dehtml = &mut *(userdata as *mut Dehtml);
fn dehtml_text_cb(event: &BytesText, dehtml: &mut Dehtml) {
if dehtml.add_text == AddText::YesPreserveLineEnds
|| dehtml.add_text == AddText::YesRemoveLineEnds
{
let last_added = std::ffi::CStr::from_ptr(text).to_string_lossy();
let last_added = escaper::decode_html_buf_sloppy(event.escaped()).unwrap_or_default();
if dehtml.add_text == AddText::YesRemoveLineEnds {
dehtml.strbuilder += LINE_RE.replace_all(last_added.as_ref(), "\r").as_ref();
dehtml.strbuilder += LINE_RE.replace_all(&last_added, "\r").as_ref();
} else {
dehtml.strbuilder += last_added.as_ref();
dehtml.strbuilder += &last_added;
}
}
}
unsafe fn dehtml_endtag_cb(userdata: *mut libc::c_void, tag: *const libc::c_char) {
let mut dehtml = &mut *(userdata as *mut Dehtml);
let tag = std::ffi::CStr::from_ptr(tag).to_string_lossy();
fn dehtml_cdata_cb(event: &BytesText, dehtml: &mut Dehtml) {
if dehtml.add_text == AddText::YesPreserveLineEnds
|| dehtml.add_text == AddText::YesRemoveLineEnds
{
let last_added = escaper::decode_html_buf_sloppy(event.escaped()).unwrap_or_default();
match tag.as_ref() {
if dehtml.add_text == AddText::YesRemoveLineEnds {
dehtml.strbuilder += LINE_RE.replace_all(&last_added, "\r").as_ref();
} else {
dehtml.strbuilder += &last_added;
}
}
}
fn dehtml_endtag_cb(event: &BytesEnd, dehtml: &mut Dehtml) {
let tag = String::from_utf8_lossy(event.name()).trim().to_lowercase();
match tag.as_str() {
"p" | "div" | "table" | "td" | "style" | "script" | "title" | "pre" => {
dehtml.strbuilder += "\n\n";
dehtml.add_text = AddText::YesRemoveLineEnds;
}
"a" => {
if !dehtml.last_href.is_null() {
if let Some(ref last_href) = dehtml.last_href.take() {
dehtml.strbuilder += "](";
dehtml.strbuilder += std::ffi::CStr::from_ptr((*dehtml).last_href)
.to_string_lossy()
.as_ref();
dehtml.strbuilder += last_href;
dehtml.strbuilder += ")";
free(dehtml.last_href as *mut libc::c_void);
dehtml.last_href = 0 as *mut libc::c_char;
}
}
"b" | "strong" => {
@@ -106,15 +115,14 @@ unsafe fn dehtml_endtag_cb(userdata: *mut libc::c_void, tag: *const libc::c_char
}
}
unsafe fn dehtml_starttag_cb(
userdata: *mut libc::c_void,
tag: *const libc::c_char,
attr: *mut *mut libc::c_char,
fn dehtml_starttag_cb<B: std::io::BufRead>(
event: &BytesStart,
dehtml: &mut Dehtml,
reader: &quick_xml::Reader<B>,
) {
let mut dehtml = &mut *(userdata as *mut Dehtml);
let tag = std::ffi::CStr::from_ptr(tag).to_string_lossy();
let tag = String::from_utf8_lossy(event.name()).trim().to_lowercase();
match tag.as_ref() {
match tag.as_str() {
"p" | "div" | "table" | "td" => {
dehtml.strbuilder += "\n\n";
dehtml.add_text = AddText::YesRemoveLineEnds;
@@ -131,13 +139,21 @@ unsafe fn dehtml_starttag_cb(
dehtml.add_text = AddText::YesPreserveLineEnds;
}
"a" => {
free(dehtml.last_href as *mut libc::c_void);
dehtml.last_href = dc_strdup_keep_null(dc_attr_find(
attr,
b"href\x00" as *const u8 as *const libc::c_char,
));
if !dehtml.last_href.is_null() {
dehtml.strbuilder += "[";
if let Some(href) = event.html_attributes().find(|attr| {
attr.as_ref()
.map(|a| String::from_utf8_lossy(a.key).trim().to_lowercase() == "href")
.unwrap_or_default()
}) {
let href = href
.unwrap()
.unescape_and_decode_value(reader)
.unwrap_or_default()
.to_lowercase();
if !href.is_empty() {
dehtml.last_href = Some(href);
dehtml.strbuilder += "[";
}
}
}
"b" | "strong" => {

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

View File

@@ -1,205 +0,0 @@
use std::sync::{Arc, Condvar, Mutex};
use crate::context::Context;
use crate::dc_configure::*;
use crate::imap::Imap;
use crate::x::*;
#[repr(C)]
pub struct dc_jobthread_t {
pub name: &'static str,
pub folder_config_name: &'static str,
pub imap: Imap,
pub state: Arc<(Mutex<JobState>, Condvar)>,
}
pub fn dc_jobthread_init(
name: &'static str,
folder_config_name: &'static str,
imap: Imap,
) -> dc_jobthread_t {
dc_jobthread_t {
name,
folder_config_name,
imap,
state: Arc::new((Mutex::new(Default::default()), Condvar::new())),
}
}
#[derive(Debug, Default)]
pub struct JobState {
idle: bool,
jobs_needed: i32,
suspended: i32,
using_handle: i32,
}
pub unsafe fn dc_jobthread_suspend(
context: &Context,
jobthread: &dc_jobthread_t,
suspend: libc::c_int,
) {
if 0 != suspend {
info!(context, 0, "Suspending {}-thread.", jobthread.name,);
{
jobthread.state.0.lock().unwrap().suspended = 1;
}
dc_jobthread_interrupt_idle(context, jobthread);
loop {
let using_handle = jobthread.state.0.lock().unwrap().using_handle;
if using_handle == 0 {
return;
}
std::thread::sleep(std::time::Duration::from_micros(300 * 1000));
}
} else {
info!(context, 0, "Unsuspending {}-thread.", jobthread.name);
let &(ref lock, ref cvar) = &*jobthread.state.clone();
let mut state = lock.lock().unwrap();
state.suspended = 0;
state.idle = true;
cvar.notify_one();
}
}
pub unsafe fn dc_jobthread_interrupt_idle(context: &Context, jobthread: &dc_jobthread_t) {
{
jobthread.state.0.lock().unwrap().jobs_needed = 1;
}
info!(context, 0, "Interrupting {}-IDLE...", jobthread.name);
jobthread.imap.interrupt_idle();
let &(ref lock, ref cvar) = &*jobthread.state.clone();
let mut state = lock.lock().unwrap();
state.idle = true;
cvar.notify_one();
}
pub unsafe fn dc_jobthread_fetch(
context: &Context,
jobthread: &mut dc_jobthread_t,
use_network: libc::c_int,
) {
let start;
{
let &(ref lock, _) = &*jobthread.state.clone();
let mut state = lock.lock().unwrap();
if 0 != state.suspended {
return;
}
state.using_handle = 1;
}
if 0 != use_network {
start = clock();
if !(0 == connect_to_imap(context, jobthread)) {
info!(context, 0, "{}-fetch started...", jobthread.name);
jobthread.imap.fetch(context);
if jobthread.imap.should_reconnect() {
info!(
context,
0, "{}-fetch aborted, starting over...", jobthread.name,
);
jobthread.imap.fetch(context);
}
info!(
context,
0,
"{}-fetch done in {:.3} ms.",
jobthread.name,
clock().wrapping_sub(start) as f64 / 1000.0,
);
}
}
jobthread.state.0.lock().unwrap().using_handle = 0;
}
/* ******************************************************************************
* the typical fetch, idle, interrupt-idle
******************************************************************************/
unsafe fn connect_to_imap(context: &Context, jobthread: &dc_jobthread_t) -> libc::c_int {
if jobthread.imap.is_connected() {
return 1;
}
let mut ret_connected = dc_connect_to_configured_imap(context, &jobthread.imap);
if !(0 == ret_connected) {
if context.sql.get_config_int(context, "folders_configured", 0) < 3 {
jobthread.imap.configure_folders(context, 0x1);
}
if let Some(mvbox_name) =
context
.sql
.get_config(context, jobthread.folder_config_name, None)
{
jobthread.imap.set_watch_folder(mvbox_name);
} else {
jobthread.imap.disconnect(context);
ret_connected = 0;
}
}
ret_connected
}
pub unsafe fn dc_jobthread_idle(
context: &Context,
jobthread: &dc_jobthread_t,
use_network: libc::c_int,
) {
{
let &(ref lock, ref cvar) = &*jobthread.state.clone();
let mut state = lock.lock().unwrap();
if 0 != state.jobs_needed {
info!(
context,
0,
"{}-IDLE will not be started as it was interrupted while not ideling.",
jobthread.name,
);
state.jobs_needed = 0;
return;
}
if 0 != state.suspended {
while !state.idle {
state = cvar.wait(state).unwrap();
}
state.idle = false;
return;
}
state.using_handle = 1;
if 0 == use_network {
state.using_handle = 0;
while !state.idle {
state = cvar.wait(state).unwrap();
}
state.idle = false;
return;
}
}
connect_to_imap(context, jobthread);
info!(context, 0, "{}-IDLE started...", jobthread.name,);
jobthread.imap.idle(context);
info!(context, 0, "{}-IDLE ended.", jobthread.name);
jobthread.state.0.lock().unwrap().using_handle = 0;
}

View File

@@ -1,747 +0,0 @@
use crate::constants::Event;
use crate::context::*;
use crate::dc_array::*;
use crate::dc_chat::*;
use crate::dc_job::*;
use crate::dc_msg::*;
use crate::dc_param::*;
use crate::dc_saxparser::*;
use crate::dc_stock::*;
use crate::dc_tools::*;
use crate::sql;
use crate::types::*;
use crate::x::*;
// location handling
#[derive(Copy, Clone)]
#[repr(C)]
pub struct dc_location_t {
pub location_id: uint32_t,
pub latitude: libc::c_double,
pub longitude: libc::c_double,
pub accuracy: libc::c_double,
pub timestamp: i64,
pub contact_id: uint32_t,
pub msg_id: uint32_t,
pub chat_id: uint32_t,
pub marker: *mut libc::c_char,
}
#[derive(Copy, Clone)]
#[repr(C)]
pub struct dc_kml_t {
pub addr: *mut libc::c_char,
pub locations: *mut dc_array_t,
pub tag: libc::c_int,
pub curr: dc_location_t,
}
// location streaming
pub unsafe fn dc_send_locations_to_chat(
context: &Context,
chat_id: uint32_t,
seconds: libc::c_int,
) {
let now = time();
let mut msg: *mut dc_msg_t = 0 as *mut dc_msg_t;
let mut stock_str: *mut libc::c_char = 0 as *mut libc::c_char;
let is_sending_locations_before: bool;
if !(seconds < 0i32 || chat_id <= 9i32 as libc::c_uint) {
is_sending_locations_before = dc_is_sending_locations_to_chat(context, chat_id);
if sql::execute(
context,
&context.sql,
"UPDATE chats \
SET locations_send_begin=?, \
locations_send_until=? \
WHERE id=?",
params![
if 0 != seconds { now } else { 0 },
if 0 != seconds {
now + seconds as i64
} else {
0
},
chat_id as i32,
],
) {
if 0 != seconds && !is_sending_locations_before {
msg = dc_msg_new(context, 10i32);
(*msg).text = dc_stock_system_msg(
context,
64,
0 as *const libc::c_char,
0 as *const libc::c_char,
0,
);
dc_param_set_int((*msg).param, 'S' as i32, 8i32);
dc_send_msg(context, chat_id, msg);
} else if 0 == seconds && is_sending_locations_before {
stock_str = dc_stock_system_msg(
context,
65i32,
0 as *const libc::c_char,
0 as *const libc::c_char,
0i32 as uint32_t,
);
dc_add_device_msg(context, chat_id, stock_str);
}
context.call_cb(
Event::CHAT_MODIFIED,
chat_id as uintptr_t,
0i32 as uintptr_t,
);
if 0 != seconds {
schedule_MAYBE_SEND_LOCATIONS(context, 0i32);
dc_job_add(
context,
5007i32,
chat_id as libc::c_int,
0 as *const libc::c_char,
seconds + 1i32,
);
}
}
}
free(stock_str as *mut libc::c_void);
dc_msg_unref(msg);
}
/*******************************************************************************
* job to send locations out to all chats that want them
******************************************************************************/
unsafe fn schedule_MAYBE_SEND_LOCATIONS(context: &Context, flags: libc::c_int) {
if 0 != flags & 0x1 || !dc_job_action_exists(context, 5005) {
dc_job_add(context, 5005, 0, 0 as *const libc::c_char, 60);
};
}
pub fn dc_is_sending_locations_to_chat(context: &Context, chat_id: u32) -> bool {
context
.sql
.exists(
"SELECT id FROM chats WHERE (? OR id=?) AND locations_send_until>?;",
params![if chat_id == 0 { 1 } else { 0 }, chat_id as i32, time()],
)
.unwrap_or_default()
}
pub fn dc_set_location(
context: &Context,
latitude: libc::c_double,
longitude: libc::c_double,
accuracy: libc::c_double,
) -> libc::c_int {
if latitude == 0.0 && longitude == 0.0 {
return 1;
}
context.sql.query_map(
"SELECT id FROM chats WHERE locations_send_until>?;",
params![time()], |row| row.get::<_, i32>(0),
|chats| {
let mut continue_streaming = false;
for chat in chats {
let chat_id = chat?;
context.sql.execute(
"INSERT INTO locations \
(latitude, longitude, accuracy, timestamp, chat_id, from_id) VALUES (?,?,?,?,?,?);",
params![
latitude,
longitude,
accuracy,
time(),
chat_id,
1,
]
)?;
continue_streaming = true;
}
if continue_streaming {
context.call_cb(Event::LOCATION_CHANGED, 1, 0);
};
unsafe { schedule_MAYBE_SEND_LOCATIONS(context, 0) };
Ok(continue_streaming as libc::c_int)
}
).unwrap_or_default()
}
pub fn dc_get_locations(
context: &Context,
chat_id: uint32_t,
contact_id: uint32_t,
timestamp_from: i64,
mut timestamp_to: i64,
) -> *mut dc_array_t {
if timestamp_to == 0 {
timestamp_to = time() + 10;
}
context
.sql
.query_map(
"SELECT l.id, l.latitude, l.longitude, l.accuracy, l.timestamp, l.independent, \
m.id, l.from_id, l.chat_id, m.txt \
FROM locations l LEFT JOIN msgs m ON l.id=m.location_id WHERE (? OR l.chat_id=?) \
AND (? OR l.from_id=?) \
AND (l.independent=1 OR (l.timestamp>=? AND l.timestamp<=?)) \
ORDER BY l.timestamp DESC, l.id DESC, m.id DESC;",
params![
if chat_id == 0 { 1 } else { 0 },
chat_id as i32,
if contact_id == 0 { 1 } else { 0 },
contact_id as i32,
timestamp_from,
timestamp_to,
],
|row| unsafe {
let mut loc: *mut _dc_location =
calloc(1, ::std::mem::size_of::<_dc_location>()) as *mut _dc_location;
assert!(!loc.is_null(), "allocation failed");
(*loc).location_id = row.get(0)?;
(*loc).latitude = row.get(1)?;
(*loc).longitude = row.get(2)?;
(*loc).accuracy = row.get(3)?;
(*loc).timestamp = row.get(4)?;
(*loc).independent = row.get(5)?;
(*loc).msg_id = row.get(6)?;
(*loc).contact_id = row.get(7)?;
(*loc).chat_id = row.get(8)?;
if 0 != (*loc).msg_id {
let txt: String = row.get(9)?;
let txt_c = to_cstring(txt);
if 0 != is_marker(txt_c.as_ptr()) {
(*loc).marker = strdup(txt_c.as_ptr());
}
}
Ok(loc)
},
|locations| {
let ret = unsafe { dc_array_new_typed(1, 500) };
for location in locations {
unsafe { dc_array_add_ptr(ret, location? as *mut libc::c_void) };
}
Ok(ret)
},
)
.unwrap_or_else(|_| std::ptr::null_mut())
}
// TODO should be bool /rtn
unsafe fn is_marker(txt: *const libc::c_char) -> libc::c_int {
if !txt.is_null() {
let len: libc::c_int = dc_utf8_strlen(txt) as libc::c_int;
if len == 1 && *txt.offset(0isize) as libc::c_int != ' ' as i32 {
return 1;
}
}
0
}
pub fn dc_delete_all_locations(context: &Context) -> bool {
if !sql::execute(context, &context.sql, "DELETE FROM locations;", params![]) {
return false;
}
context.call_cb(Event::LOCATION_CHANGED, 0, 0);
true
}
pub fn dc_get_location_kml(
context: &Context,
chat_id: uint32_t,
last_added_location_id: *mut uint32_t,
) -> *mut libc::c_char {
let mut success: libc::c_int = 0;
let now = time();
let mut location_count: libc::c_int = 0;
let mut ret = String::new();
let self_addr = context.sql.get_config(context, "configured_addr", Some(""));
if self_addr.is_none() {
return std::ptr::null_mut();
}
let self_addr = self_addr.unwrap();
if let Ok((locations_send_begin, locations_send_until, locations_last_sent)) = context.sql.query_row(
"SELECT locations_send_begin, locations_send_until, locations_last_sent FROM chats WHERE id=?;",
params![chat_id as i32], |row| {
let send_begin: i64 = row.get(0)?;
let send_until: i64 = row.get(1)?;
let last_sent: i64 = row.get(2)?;
Ok((send_begin, send_until, last_sent))
}
) {
if !(locations_send_begin == 0 || now > locations_send_until) {
ret += &format!(
"<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<kml xmlns=\"http://www.opengis.net/kml/2.2\">\n<Document addr=\"{}\">\n",
self_addr,
);
context.sql.query_map(
"SELECT id, latitude, longitude, accuracy, timestamp\
FROM locations WHERE from_id=? \
AND timestamp>=? \
AND (timestamp>=? OR timestamp=(SELECT MAX(timestamp) FROM locations WHERE from_id=?)) \
AND independent=0 \
GROUP BY timestamp \
ORDER BY timestamp;",
params![1, locations_send_begin, locations_last_sent, 1],
|row| {
let location_id: i32 = row.get(0)?;
let latitude: f64 = row.get(1)?;
let longitude: f64 = row.get(2)?;
let accuracy: f64 = row.get(3)?;
let timestamp = unsafe { get_kml_timestamp(row.get(4)?) };
Ok((location_id, latitude, longitude, accuracy, timestamp))
},
|rows| {
for row in rows {
let (location_id, latitude, longitude, accuracy, timestamp) = row?;
ret += &format!(
"<Placemark><Timestamp><when>{}</when></Timestamp><Point><coordinates accuracy=\"{}\">{},{}</coordinates></Point></Placemark>\n\x00",
as_str(timestamp),
accuracy,
longitude,
latitude
);
location_count += 1;
if !last_added_location_id.is_null() {
unsafe { *last_added_location_id = location_id as u32 };
}
unsafe { free(timestamp as *mut libc::c_void) };
}
Ok(())
}
).unwrap(); // TODO: better error handling
}
}
if location_count > 0 {
ret += "</Document>\n</kml>";
success = 1;
}
if 0 != success {
unsafe { strdup(to_cstring(ret).as_ptr()) }
} else {
0 as *mut libc::c_char
}
}
/*******************************************************************************
* create kml-files
******************************************************************************/
unsafe fn get_kml_timestamp(utc: i64) -> *mut libc::c_char {
// Returns a string formatted as YYYY-MM-DDTHH:MM:SSZ. The trailing `Z` indicates UTC.
let res = chrono::NaiveDateTime::from_timestamp(utc, 0)
.format("%Y-%m-%dT%H:%M:%SZ")
.to_string();
strdup(to_cstring(res).as_ptr())
}
pub unsafe fn dc_get_message_kml(
timestamp: i64,
latitude: libc::c_double,
longitude: libc::c_double,
) -> *mut libc::c_char {
let timestamp_str = get_kml_timestamp(timestamp);
let latitude_str = dc_ftoa(latitude);
let longitude_str = dc_ftoa(longitude);
let ret = dc_mprintf(
b"<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n\
<kml xmlns=\"http://www.opengis.net/kml/2.2\">\n\
<Document>\n\
<Placemark>\
<Timestamp><when>%s</when></Timestamp>\
<Point><coordinates>%s,%s</coordinates></Point>\
</Placemark>\n\
</Document>\n\
</kml>\x00" as *const u8 as *const libc::c_char,
timestamp_str,
longitude_str, // reverse order!
latitude_str,
);
free(latitude_str as *mut libc::c_void);
free(longitude_str as *mut libc::c_void);
free(timestamp_str as *mut libc::c_void);
ret
}
pub fn dc_set_kml_sent_timestamp(context: &Context, chat_id: u32, timestamp: i64) -> bool {
sql::execute(
context,
&context.sql,
"UPDATE chats SET locations_last_sent=? WHERE id=?;",
params![timestamp, chat_id as i32],
)
}
pub fn dc_set_msg_location_id(context: &Context, msg_id: u32, location_id: u32) -> bool {
sql::execute(
context,
&context.sql,
"UPDATE msgs SET location_id=? WHERE id=?;",
params![location_id, msg_id as i32],
)
}
pub unsafe fn dc_save_locations(
context: &Context,
chat_id: u32,
contact_id: u32,
locations: *const dc_array_t,
independent: libc::c_int,
) -> u32 {
if chat_id <= 9 || locations.is_null() {
return 0;
}
context
.sql
.prepare2(
"SELECT id FROM locations WHERE timestamp=? AND from_id=?",
"INSERT INTO locations\
(timestamp, from_id, chat_id, latitude, longitude, accuracy, independent) \
VALUES (?,?,?,?,?,?,?);",
|mut stmt_test, mut stmt_insert, conn| {
let mut newest_timestamp = 0;
let mut newest_location_id = 0;
for i in 0..dc_array_get_cnt(locations) {
let location = dc_array_get_ptr(locations, i as size_t) as *mut dc_location_t;
let exists =
stmt_test.exists(params![(*location).timestamp, contact_id as i32])?;
if 0 != independent || !exists {
stmt_insert.execute(params![
(*location).timestamp,
contact_id as i32,
chat_id as i32,
(*location).latitude,
(*location).longitude,
(*location).accuracy,
independent,
])?;
if (*location).timestamp > newest_timestamp {
newest_timestamp = (*location).timestamp;
newest_location_id = sql::get_rowid2_with_conn(
context,
conn,
"locations",
"timestamp",
(*location).timestamp,
"from_id",
contact_id as i32,
);
}
}
}
Ok(newest_location_id)
},
)
.unwrap_or_default()
}
pub unsafe fn dc_kml_parse(
context: &Context,
content: *const libc::c_char,
content_bytes: size_t,
) -> *mut dc_kml_t {
let mut kml: *mut dc_kml_t = calloc(1, ::std::mem::size_of::<dc_kml_t>()) as *mut dc_kml_t;
let mut content_nullterminated: *mut libc::c_char = 0 as *mut libc::c_char;
let mut saxparser: dc_saxparser_t = dc_saxparser_t {
starttag_cb: None,
endtag_cb: None,
text_cb: None,
userdata: 0 as *mut libc::c_void,
};
if content_bytes > (1 * 1024 * 1024) {
warn!(
context,
0, "A kml-files with {} bytes is larger than reasonably expected.", content_bytes,
);
} else {
content_nullterminated = dc_null_terminate(content, content_bytes as libc::c_int);
if !content_nullterminated.is_null() {
(*kml).locations = dc_array_new_typed(1, 100 as size_t);
dc_saxparser_init(&mut saxparser, kml as *mut libc::c_void);
dc_saxparser_set_tag_handler(
&mut saxparser,
Some(kml_starttag_cb),
Some(kml_endtag_cb),
);
dc_saxparser_set_text_handler(&mut saxparser, Some(kml_text_cb));
dc_saxparser_parse(&mut saxparser, content_nullterminated);
}
}
free(content_nullterminated as *mut libc::c_void);
kml
}
unsafe fn kml_text_cb(userdata: *mut libc::c_void, text: *const libc::c_char, _len: libc::c_int) {
let mut kml: *mut dc_kml_t = userdata as *mut dc_kml_t;
if 0 != (*kml).tag & (0x4 | 0x10) {
let mut val: *mut libc::c_char = dc_strdup(text);
dc_str_replace(
&mut val,
b"\n\x00" as *const u8 as *const libc::c_char,
b"\x00" as *const u8 as *const libc::c_char,
);
dc_str_replace(
&mut val,
b"\r\x00" as *const u8 as *const libc::c_char,
b"\x00" as *const u8 as *const libc::c_char,
);
dc_str_replace(
&mut val,
b"\t\x00" as *const u8 as *const libc::c_char,
b"\x00" as *const u8 as *const libc::c_char,
);
dc_str_replace(
&mut val,
b" \x00" as *const u8 as *const libc::c_char,
b"\x00" as *const u8 as *const libc::c_char,
);
if 0 != (*kml).tag & 0x4 && strlen(val) >= 19 {
// YYYY-MM-DDTHH:MM:SSZ
// 0 4 7 10 13 16 19
let val_r = as_str(val);
match chrono::NaiveDateTime::parse_from_str(val_r, "%Y-%m-%dT%H:%M:%SZ") {
Ok(res) => {
(*kml).curr.timestamp = res.timestamp();
if (*kml).curr.timestamp > time() {
(*kml).curr.timestamp = time();
}
}
Err(_err) => {
(*kml).curr.timestamp = time();
}
}
} else if 0 != (*kml).tag & 0x10 {
let mut comma: *mut libc::c_char = strchr(val, ',' as i32);
if !comma.is_null() {
let longitude: *mut libc::c_char = val;
let latitude: *mut libc::c_char = comma.offset(1isize);
*comma = 0 as libc::c_char;
comma = strchr(latitude, ',' as i32);
if !comma.is_null() {
*comma = 0 as libc::c_char
}
(*kml).curr.latitude = dc_atof(latitude);
(*kml).curr.longitude = dc_atof(longitude)
}
}
free(val as *mut libc::c_void);
};
}
unsafe fn kml_endtag_cb(userdata: *mut libc::c_void, tag: *const libc::c_char) {
let mut kml: *mut dc_kml_t = userdata as *mut dc_kml_t;
if strcmp(tag, b"placemark\x00" as *const u8 as *const libc::c_char) == 0 {
if 0 != (*kml).tag & 0x1
&& 0 != (*kml).curr.timestamp
&& 0. != (*kml).curr.latitude
&& 0. != (*kml).curr.longitude
{
let location: *mut dc_location_t =
calloc(1, ::std::mem::size_of::<dc_location_t>()) as *mut dc_location_t;
*location = (*kml).curr;
dc_array_add_ptr((*kml).locations, location as *mut libc::c_void);
}
(*kml).tag = 0
};
}
/*******************************************************************************
* parse kml-files
******************************************************************************/
unsafe fn kml_starttag_cb(
userdata: *mut libc::c_void,
tag: *const libc::c_char,
attr: *mut *mut libc::c_char,
) {
let mut kml: *mut dc_kml_t = userdata as *mut dc_kml_t;
if strcmp(tag, b"document\x00" as *const u8 as *const libc::c_char) == 0 {
let addr: *const libc::c_char =
dc_attr_find(attr, b"addr\x00" as *const u8 as *const libc::c_char);
if !addr.is_null() {
(*kml).addr = dc_strdup(addr)
}
} else if strcmp(tag, b"placemark\x00" as *const u8 as *const libc::c_char) == 0 {
(*kml).tag = 0x1;
(*kml).curr.timestamp = 0;
(*kml).curr.latitude = 0 as libc::c_double;
(*kml).curr.longitude = 0.0f64;
(*kml).curr.accuracy = 0.0f64
} else if strcmp(tag, b"timestamp\x00" as *const u8 as *const libc::c_char) == 0
&& 0 != (*kml).tag & 0x1
{
(*kml).tag = 0x1 | 0x2
} else if strcmp(tag, b"when\x00" as *const u8 as *const libc::c_char) == 0
&& 0 != (*kml).tag & 0x2
{
(*kml).tag = 0x1 | 0x2 | 0x4
} else if strcmp(tag, b"point\x00" as *const u8 as *const libc::c_char) == 0
&& 0 != (*kml).tag & 0x1
{
(*kml).tag = 0x1 | 0x8
} else if strcmp(tag, b"coordinates\x00" as *const u8 as *const libc::c_char) == 0
&& 0 != (*kml).tag & 0x8
{
(*kml).tag = 0x1 | 0x8 | 0x10;
let accuracy: *const libc::c_char =
dc_attr_find(attr, b"accuracy\x00" as *const u8 as *const libc::c_char);
if !accuracy.is_null() {
(*kml).curr.accuracy = dc_atof(accuracy)
}
};
}
pub unsafe fn dc_kml_unref(kml: *mut dc_kml_t) {
if kml.is_null() {
return;
}
dc_array_unref((*kml).locations);
free((*kml).addr as *mut libc::c_void);
free(kml as *mut libc::c_void);
}
pub unsafe fn dc_job_do_DC_JOB_MAYBE_SEND_LOCATIONS(context: &Context, _job: *mut dc_job_t) {
let now = time();
let mut continue_streaming: libc::c_int = 1;
info!(
context,
0, " ----------------- MAYBE_SEND_LOCATIONS -------------- ",
);
context
.sql
.query_map(
"SELECT id, locations_send_begin, locations_last_sent \
FROM chats \
WHERE locations_send_until>?;",
params![now],
|row| {
let chat_id: i32 = row.get(0)?;
let locations_send_begin: i64 = row.get(1)?;
let locations_last_sent: i64 = row.get(2)?;
continue_streaming = 1;
// be a bit tolerant as the timer may not align exactly with time(NULL)
if now - locations_last_sent < (60 - 3) {
Ok(None)
} else {
Ok(Some((chat_id, locations_send_begin, locations_last_sent)))
}
},
|rows| {
context.sql.prepare(
"SELECT id \
FROM locations \
WHERE from_id=? \
AND timestamp>=? \
AND timestamp>? \
AND independent=0 \
ORDER BY timestamp;",
|mut stmt_locations| {
for (chat_id, locations_send_begin, locations_last_sent) in
rows.filter_map(|r| match r {
Ok(Some(v)) => Some(v),
_ => None,
})
{
// TODO: do I need to reset?
if !stmt_locations
.exists(params![1, locations_send_begin, locations_last_sent,])
.unwrap_or_default()
{
// if there is no new location, there's nothing to send.
// however, maybe we want to bypass this test eg. 15 minutes
continue;
}
// pending locations are attached automatically to every message,
// so also to this empty text message.
// DC_CMD_LOCATION is only needed to create a nicer subject.
//
// for optimisation and to avoid flooding the sending queue,
// we could sending these messages only if we're really online.
// the easiest way to determine this, is to check for an empty message queue.
// (might not be 100%, however, as positions are sent combined later
// and dc_set_location() is typically called periodically, this is ok)
let mut msg = dc_msg_new(context, 10);
(*msg).hidden = 1;
dc_param_set_int((*msg).param, 'S' as i32, 9);
dc_send_msg(context, chat_id as u32, msg);
dc_msg_unref(msg);
}
Ok(())
},
)
},
)
.unwrap(); // TODO: Better error handling
if 0 != continue_streaming {
schedule_MAYBE_SEND_LOCATIONS(context, 0x1);
}
}
pub unsafe fn dc_job_do_DC_JOB_MAYBE_SEND_LOC_ENDED(context: &Context, job: &mut dc_job_t) {
// this function is called when location-streaming _might_ have ended for a chat.
// the function checks, if location-streaming is really ended;
// if so, a device-message is added if not yet done.
let chat_id = (*job).foreign_id;
let mut stock_str = 0 as *mut libc::c_char;
if let Ok((send_begin, send_until)) = context.sql.query_row(
"SELECT locations_send_begin, locations_send_until FROM chats WHERE id=?",
params![chat_id as i32],
|row| Ok((row.get::<_, i64>(0)?, row.get::<_, i64>(1)?)),
) {
if !(send_begin != 0 && time() <= send_until) {
// still streaming -
// may happen as several calls to dc_send_locations_to_chat()
// do not un-schedule pending DC_MAYBE_SEND_LOC_ENDED jobs
if !(send_begin == 0 && send_until == 0) {
// not streaming, device-message already sent
if context.sql.execute(
"UPDATE chats SET locations_send_begin=0, locations_send_until=0 WHERE id=?",
params![chat_id as i32],
).is_ok() {
stock_str = dc_stock_system_msg(
context,
65,
0 as *const libc::c_char,
0 as *const libc::c_char,
0,
);
dc_add_device_msg(context, chat_id, stock_str);
context.call_cb(
Event::CHAT_MODIFIED,
chat_id as usize,
0,
);
}
}
}
}
free(stock_str as *mut libc::c_void);
}

View File

@@ -4,6 +4,7 @@ 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,
@@ -36,37 +37,37 @@ pub fn dc_loginparam_read(
let key = format!("{}addr", prefix);
let addr = sql
.get_config(context, key, None)
.get_config(context, key)
.unwrap_or_default()
.trim()
.to_string();
let key = format!("{}mail_server", prefix);
let mail_server = sql.get_config(context, key, None).unwrap_or_default();
let mail_server = sql.get_config(context, key).unwrap_or_default();
let key = format!("{}mail_port", prefix);
let mail_port = sql.get_config_int(context, key, 0);
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, None).unwrap_or_default();
let mail_user = sql.get_config(context, key).unwrap_or_default();
let key = format!("{}mail_pw", prefix);
let mail_pw = sql.get_config(context, key, None).unwrap_or_default();
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, None).unwrap_or_default();
let send_server = sql.get_config(context, key).unwrap_or_default();
let key = format!("{}send_port", prefix);
let send_port = sql.get_config_int(context, key, 0);
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, None).unwrap_or_default();
let send_user = sql.get_config(context, key).unwrap_or_default();
let key = format!("{}send_pw", prefix);
let send_pw = sql.get_config(context, key, None).unwrap_or_default();
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, 0);
let server_flags = sql.get_config_int(context, key).unwrap_or_default();
dc_loginparam_t {
addr: addr.to_string(),
@@ -91,34 +92,39 @@ pub fn dc_loginparam_write(
let prefix = prefix.as_ref();
let key = format!("{}addr", prefix);
sql.set_config(context, key, Some(&loginparam.addr));
sql.set_config(context, key, Some(&loginparam.addr)).ok();
let key = format!("{}mail_server", prefix);
sql.set_config(context, key, Some(&loginparam.mail_server));
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);
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));
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));
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));
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);
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));
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));
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);
sql.set_config_int(context, key, loginparam.server_flags)
.ok();
}
fn unset_empty(s: &String) -> Cow<String> {

View File

@@ -1,167 +0,0 @@
use crate::context::Context;
use crate::dc_chat::*;
use crate::dc_contact::*;
use crate::dc_msg::*;
use crate::dc_stock::*;
use crate::dc_tools::*;
use crate::types::*;
use crate::x::*;
/* * Structure behind dc_lot_t */
#[derive(Copy, Clone)]
#[repr(C)]
pub struct dc_lot_t {
pub magic: uint32_t,
pub text1_meaning: libc::c_int,
pub text1: *mut libc::c_char,
pub text2: *mut libc::c_char,
pub timestamp: i64,
pub state: libc::c_int,
pub id: uint32_t,
pub fingerprint: *mut libc::c_char,
pub invitenumber: *mut libc::c_char,
pub auth: *mut libc::c_char,
}
/* *
* @class dc_lot_t
*
* An object containing a set of values.
* The meaning of the values is defined by the function returning the object.
* Lot objects are created
* eg. by dc_chatlist_get_summary() or dc_msg_get_summary().
*
* NB: _Lot_ is used in the meaning _heap_ here.
*/
pub unsafe fn dc_lot_new() -> *mut dc_lot_t {
let mut lot: *mut dc_lot_t;
lot = calloc(1, ::std::mem::size_of::<dc_lot_t>()) as *mut dc_lot_t;
assert!(!lot.is_null());
(*lot).magic = 0x107107i32 as uint32_t;
(*lot).text1_meaning = 0i32;
lot
}
pub unsafe fn dc_lot_empty(mut lot: *mut dc_lot_t) {
if lot.is_null() || (*lot).magic != 0x107107i32 as libc::c_uint {
return;
}
free((*lot).text1 as *mut libc::c_void);
(*lot).text1 = 0 as *mut libc::c_char;
(*lot).text1_meaning = 0i32;
free((*lot).text2 as *mut libc::c_void);
(*lot).text2 = 0 as *mut libc::c_char;
free((*lot).fingerprint as *mut libc::c_void);
(*lot).fingerprint = 0 as *mut libc::c_char;
free((*lot).invitenumber as *mut libc::c_void);
(*lot).invitenumber = 0 as *mut libc::c_char;
free((*lot).auth as *mut libc::c_void);
(*lot).auth = 0 as *mut libc::c_char;
(*lot).timestamp = 0;
(*lot).state = 0i32;
(*lot).id = 0i32 as uint32_t;
}
pub unsafe fn dc_lot_unref(mut set: *mut dc_lot_t) {
if set.is_null() || (*set).magic != 0x107107i32 as libc::c_uint {
return;
}
dc_lot_empty(set);
(*set).magic = 0i32 as uint32_t;
free(set as *mut libc::c_void);
}
pub unsafe fn dc_lot_get_text1(lot: *const dc_lot_t) -> *mut libc::c_char {
if lot.is_null() || (*lot).magic != 0x107107i32 as libc::c_uint {
return 0 as *mut libc::c_char;
}
dc_strdup_keep_null((*lot).text1)
}
pub unsafe fn dc_lot_get_text2(lot: *const dc_lot_t) -> *mut libc::c_char {
if lot.is_null() || (*lot).magic != 0x107107i32 as libc::c_uint {
return 0 as *mut libc::c_char;
}
dc_strdup_keep_null((*lot).text2)
}
pub unsafe fn dc_lot_get_text1_meaning(lot: *const dc_lot_t) -> libc::c_int {
if lot.is_null() || (*lot).magic != 0x107107i32 as libc::c_uint {
return 0i32;
}
(*lot).text1_meaning
}
pub unsafe fn dc_lot_get_state(lot: *const dc_lot_t) -> libc::c_int {
if lot.is_null() || (*lot).magic != 0x107107i32 as libc::c_uint {
return 0i32;
}
(*lot).state
}
pub unsafe fn dc_lot_get_id(lot: *const dc_lot_t) -> uint32_t {
if lot.is_null() || (*lot).magic != 0x107107i32 as libc::c_uint {
return 0i32 as uint32_t;
}
(*lot).id
}
pub unsafe fn dc_lot_get_timestamp(lot: *const dc_lot_t) -> i64 {
if lot.is_null() || (*lot).magic != 0x107107i32 as libc::c_uint {
return 0;
}
(*lot).timestamp
}
/* library-internal */
/* in practice, the user additionally cuts the string himself pixel-accurate */
pub unsafe fn dc_lot_fill(
mut lot: *mut dc_lot_t,
msg: *const dc_msg_t,
chat: *const Chat,
contact: *const dc_contact_t,
context: &Context,
) {
if lot.is_null() || (*lot).magic != 0x107107i32 as libc::c_uint || msg.is_null() {
return;
}
if (*msg).state == 19i32 {
(*lot).text1 = dc_stock_str(context, 3i32);
(*lot).text1_meaning = 1i32
} else if (*msg).from_id == 1i32 as libc::c_uint {
if 0 != dc_msg_is_info(msg) || 0 != dc_chat_is_self_talk(chat) {
(*lot).text1 = 0 as *mut libc::c_char;
(*lot).text1_meaning = 0i32
} else {
(*lot).text1 = dc_stock_str(context, 2i32);
(*lot).text1_meaning = 3i32
}
} else if chat.is_null() {
(*lot).text1 = 0 as *mut libc::c_char;
(*lot).text1_meaning = 0i32
} else if (*chat).type_0 == 120i32 || (*chat).type_0 == 130i32 {
if 0 != dc_msg_is_info(msg) || contact.is_null() {
(*lot).text1 = 0 as *mut libc::c_char;
(*lot).text1_meaning = 0i32
} else {
if !chat.is_null() && (*chat).id == 1i32 as libc::c_uint {
(*lot).text1 = dc_contact_get_display_name(contact)
} else {
(*lot).text1 = dc_contact_get_first_name(contact)
}
(*lot).text1_meaning = 2i32
}
}
(*lot).text2 =
dc_msg_get_summarytext_by_raw((*msg).type_0, (*msg).text, (*msg).param, 160i32, context);
(*lot).timestamp = dc_msg_get_timestamp(msg);
(*lot).state = (*msg).state;
}

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

View File

@@ -1,40 +0,0 @@
use crate::constants::*;
use crate::context::*;
use crate::dc_job::*;
use crate::dc_msg::*;
pub unsafe fn dc_do_heuristics_moves(context: &Context, folder: &str, msg_id: u32) {
if context.sql.get_config_int(context, "mvbox_move", 1) == 0 {
return;
}
if !dc_is_inbox(context, folder) && !dc_is_sentbox(context, folder) {
return;
}
let msg = dc_msg_new_load(context, msg_id);
if dc_msg_is_setupmessage(msg) {
// do not move setup messages;
// there may be a non-delta device that wants to handle it
dc_msg_unref(msg);
return;
}
if dc_is_mvbox(context, folder) {
dc_update_msg_move_state(context, (*msg).rfc724_mid, DC_MOVE_STATE_STAY);
}
// 1 = dc message, 2 = reply to dc message
if 0 != (*msg).is_dc_message {
dc_job_add(
context,
200,
(*msg).id as libc::c_int,
0 as *const libc::c_char,
0,
);
dc_update_msg_move_state(context, (*msg).rfc724_mid, DC_MOVE_STATE_MOVING);
}
dc_msg_unref(msg);
}

File diff suppressed because it is too large Load Diff

View File

@@ -1,437 +0,0 @@
use crate::dc_tools::*;
use crate::types::*;
use crate::x::*;
/// for msgs and jobs
pub const DC_PARAM_FILE: char = 'f';
/// for msgs
pub const DC_PARAM_WIDTH: char = 'w';
/// for msgs
pub const DC_PARAM_HEIGHT: char = 'h';
/// for msgs
pub const DC_PARAM_DURATION: char = 'd';
/// for msgs
pub const DC_PARAM_MIMETYPE: char = 'm';
/// for msgs: incoming: message is encryoted, outgoing: guarantee E2EE or the message is not send
pub const DC_PARAM_GUARANTEE_E2EE: char = 'c';
/// for msgs: decrypted with validation errors or without mutual set, if neither 'c' nor 'e' are preset, the messages is only transport encrypted
pub const DC_PARAM_ERRONEOUS_E2EE: char = 'e';
/// for msgs: force unencrypted message, either DC_FP_ADD_AUTOCRYPT_HEADER (1), DC_FP_NO_AUTOCRYPT_HEADER (2) or 0
pub const DC_PARAM_FORCE_PLAINTEXT: char = 'u';
/// for msgs: an incoming message which requestes a MDN (aka read receipt)
pub const DC_PARAM_WANTS_MDN: char = 'r';
/// for msgs
pub const DC_PARAM_FORWARDED: char = 'a';
/// for msgs
pub const DC_PARAM_CMD: char = 'S';
/// for msgs
pub const DC_PARAM_CMD_ARG: char = 'E';
/// for msgs
pub const DC_PARAM_CMD_ARG2: char = 'F';
/// for msgs
pub const DC_PARAM_CMD_ARG3: char = 'G';
/// for msgs
pub const DC_PARAM_CMD_ARG4: char = 'H';
/// for msgs
pub const DC_PARAM_ERROR: char = 'L';
/// for msgs in PREPARING: space-separated list of message IDs of forwarded copies
pub const DC_PARAM_PREP_FORWARDS: char = 'P';
/// for msgs
pub const DC_PARAM_SET_LATITUDE: char = 'l';
/// for msgs
pub const DC_PARAM_SET_LONGITUDE: char = 'n';
/// for jobs
pub const DC_PARAM_SERVER_FOLDER: char = 'Z';
/// for jobs
pub const DC_PARAM_SERVER_UID: char = 'z';
/// for jobs
pub const DC_PARAM_ALSO_MOVE: char = 'M';
/// for jobs: space-separated list of message recipients
pub const DC_PARAM_RECIPIENTS: char = 'R';
/// for groups
pub const DC_PARAM_UNPROMOTED: char = 'U';
/// for groups and contacts
pub const DC_PARAM_PROFILE_IMAGE: char = 'i';
/// for chats
pub const DC_PARAM_SELFTALK: char = 'K';
// values for DC_PARAM_FORCE_PLAINTEXT
pub const DC_FP_ADD_AUTOCRYPT_HEADER: u8 = 1;
pub const DC_FP_NO_AUTOCRYPT_HEADER: u8 = 2;
/// An object for handling key=value parameter lists; for the key, curently only
/// a single character is allowed.
///
/// The object is used eg. by Chat or dc_msg_t, for readable paramter names,
/// these classes define some DC_PARAM_* constantats.
///
/// Only for library-internal use.
#[derive(Copy, Clone)]
#[repr(C)]
pub struct dc_param_t {
pub packed: *mut libc::c_char,
}
// values for DC_PARAM_FORCE_PLAINTEXT
/* user functions */
pub unsafe fn dc_param_exists(param: *mut dc_param_t, key: libc::c_int) -> libc::c_int {
let mut p2: *mut libc::c_char = 0 as *mut libc::c_char;
if param.is_null() || key == 0i32 {
return 0i32;
}
return if !find_param((*param).packed, key, &mut p2).is_null() {
1i32
} else {
0i32
};
}
unsafe extern "C" fn find_param(
haystack: *mut libc::c_char,
key: libc::c_int,
ret_p2: *mut *mut libc::c_char,
) -> *mut libc::c_char {
let mut p1: *mut libc::c_char;
let mut p2: *mut libc::c_char;
p1 = haystack;
loop {
if p1.is_null() || *p1 as libc::c_int == 0i32 {
return 0 as *mut libc::c_char;
} else {
if *p1 as libc::c_int == key && *p1.offset(1isize) as libc::c_int == '=' as i32 {
break;
}
p1 = strchr(p1, '\n' as i32);
if !p1.is_null() {
p1 = p1.offset(1isize)
}
}
}
p2 = strchr(p1, '\n' as i32);
if p2.is_null() {
p2 = &mut *p1.offset(strlen(p1) as isize) as *mut libc::c_char
}
*ret_p2 = p2;
p1
}
/* the value may be an empty string, "def" is returned only if the value unset. The result must be free()'d in any case. */
pub unsafe fn dc_param_get(
param: *const dc_param_t,
key: libc::c_int,
def: *const libc::c_char,
) -> *mut libc::c_char {
let mut p1: *mut libc::c_char;
let mut p2: *mut libc::c_char = 0 as *mut libc::c_char;
let bak: libc::c_char;
let ret: *mut libc::c_char;
if param.is_null() || key == 0i32 {
return if !def.is_null() {
dc_strdup(def)
} else {
0 as *mut libc::c_char
};
}
p1 = find_param((*param).packed, key, &mut p2);
if p1.is_null() {
return if !def.is_null() {
dc_strdup(def)
} else {
0 as *mut libc::c_char
};
}
p1 = p1.offset(2isize);
bak = *p2;
*p2 = 0i32 as libc::c_char;
ret = dc_strdup(p1);
dc_rtrim(ret);
*p2 = bak;
ret
}
pub unsafe fn dc_param_get_int(
param: *const dc_param_t,
key: libc::c_int,
def: int32_t,
) -> int32_t {
if param.is_null() || key == 0i32 {
return def;
}
let s = dc_param_get(param, key, 0 as *const libc::c_char);
if s.is_null() {
return def;
}
let ret = as_str(s).parse().unwrap_or_default();
free(s as *mut libc::c_void);
ret
}
/**
* Get value of a parameter.
*
* @memberof dc_param_t
* @param param Parameter object to query.
* @param key Key of the parameter to get, one of the DC_PARAM_* constants.
* @param def Value to return if the parameter is not set.
* @return The stored value or the default value.
*/
pub unsafe fn dc_param_get_float(
param: *const dc_param_t,
key: libc::c_int,
def: libc::c_double,
) -> libc::c_double {
if param.is_null() || key == 0 {
return def;
}
let str = dc_param_get(param, key, std::ptr::null());
if str.is_null() {
return def;
}
let ret = dc_atof(str) as libc::c_double;
free(str as *mut libc::c_void);
ret
}
pub unsafe fn dc_param_set(
mut param: *mut dc_param_t,
key: libc::c_int,
value: *const libc::c_char,
) {
let mut old1: *mut libc::c_char;
let mut old2: *mut libc::c_char;
let new1: *mut libc::c_char;
if param.is_null() || key == 0i32 {
return;
}
old1 = (*param).packed;
old2 = 0 as *mut libc::c_char;
if !old1.is_null() {
let p1: *mut libc::c_char;
let mut p2: *mut libc::c_char = 0 as *mut libc::c_char;
p1 = find_param(old1, key, &mut p2);
if !p1.is_null() {
*p1 = 0i32 as libc::c_char;
old2 = p2
} else if value.is_null() {
return;
}
}
dc_rtrim(old1);
dc_ltrim(old2);
if !old1.is_null() && *old1.offset(0isize) as libc::c_int == 0i32 {
old1 = 0 as *mut libc::c_char
}
if !old2.is_null() && *old2.offset(0isize) as libc::c_int == 0i32 {
old2 = 0 as *mut libc::c_char
}
if !value.is_null() {
new1 = dc_mprintf(
b"%s%s%c=%s%s%s\x00" as *const u8 as *const libc::c_char,
if !old1.is_null() {
old1
} else {
b"\x00" as *const u8 as *const libc::c_char
},
if !old1.is_null() {
b"\n\x00" as *const u8 as *const libc::c_char
} else {
b"\x00" as *const u8 as *const libc::c_char
},
key,
value,
if !old2.is_null() {
b"\n\x00" as *const u8 as *const libc::c_char
} else {
b"\x00" as *const u8 as *const libc::c_char
},
if !old2.is_null() {
old2
} else {
b"\x00" as *const u8 as *const libc::c_char
},
)
} else {
new1 = dc_mprintf(
b"%s%s%s\x00" as *const u8 as *const libc::c_char,
if !old1.is_null() {
old1
} else {
b"\x00" as *const u8 as *const libc::c_char
},
if !old1.is_null() && !old2.is_null() {
b"\n\x00" as *const u8 as *const libc::c_char
} else {
b"\x00" as *const u8 as *const libc::c_char
},
if !old2.is_null() {
old2
} else {
b"\x00" as *const u8 as *const libc::c_char
},
)
}
free((*param).packed as *mut libc::c_void);
(*param).packed = new1;
}
pub unsafe fn dc_param_set_int(param: *mut dc_param_t, key: libc::c_int, value: int32_t) {
if param.is_null() || key == 0i32 {
return;
}
let value_str: *mut libc::c_char = dc_mprintf(
b"%i\x00" as *const u8 as *const libc::c_char,
value as libc::c_int,
);
if value_str.is_null() {
return;
}
dc_param_set(param, key, value_str);
free(value_str as *mut libc::c_void);
}
/* library-private */
pub unsafe fn dc_param_new() -> *mut dc_param_t {
let mut param: *mut dc_param_t;
param = calloc(1, ::std::mem::size_of::<dc_param_t>()) as *mut dc_param_t;
assert!(!param.is_null());
(*param).packed = calloc(1, 1) as *mut libc::c_char;
param
}
pub unsafe fn dc_param_empty(param: *mut dc_param_t) {
if param.is_null() {
return;
}
*(*param).packed.offset(0isize) = 0i32 as libc::c_char;
}
pub unsafe fn dc_param_unref(param: *mut dc_param_t) {
if param.is_null() {
return;
}
dc_param_empty(param);
free((*param).packed as *mut libc::c_void);
free(param as *mut libc::c_void);
}
pub unsafe fn dc_param_set_packed(mut param: *mut dc_param_t, packed: *const libc::c_char) {
if param.is_null() {
return;
}
dc_param_empty(param);
if !packed.is_null() {
free((*param).packed as *mut libc::c_void);
(*param).packed = dc_strdup(packed)
};
}
pub unsafe fn dc_param_set_urlencoded(mut param: *mut dc_param_t, urlencoded: *const libc::c_char) {
if param.is_null() {
return;
}
dc_param_empty(param);
if !urlencoded.is_null() {
free((*param).packed as *mut libc::c_void);
(*param).packed = dc_strdup(urlencoded);
dc_str_replace(
&mut (*param).packed,
b"&\x00" as *const u8 as *const libc::c_char,
b"\n\x00" as *const u8 as *const libc::c_char,
);
};
}
/**
* Set parameter to a float.
*
* @memberof dc_param_t
* @param param Parameter object to modify.
* @param key Key of the parameter to modify, one of the DC_PARAM_* constants.
* @param value Value to store for key.
* @return None.
*/
pub unsafe fn dc_param_set_float(param: *mut dc_param_t, key: libc::c_int, value: libc::c_double) {
if param.is_null() || key == 0 {
return;
}
let value_str = dc_ftoa(value);
if value_str.is_null() {
return;
}
dc_param_set(param, key, value_str);
free(value_str as *mut libc::c_void);
}
#[cfg(test)]
mod tests {
use super::*;
use std::ffi::CStr;
#[test]
fn test_dc_param() {
unsafe {
let p1: *mut dc_param_t = dc_param_new();
dc_param_set_packed(
p1,
b"\r\n\r\na=1\nb=2\n\nc = 3 \x00" as *const u8 as *const libc::c_char,
);
assert_eq!(dc_param_get_int(p1, 'a' as i32, 0), 1);
assert_eq!(dc_param_get_int(p1, 'b' as i32, 0), 2);
assert_eq!(dc_param_get_int(p1, 'c' as i32, 0), 0);
assert_eq!(dc_param_exists(p1, 'c' as i32), 0);
dc_param_set_int(p1, 'd' as i32, 4i32);
assert_eq!(dc_param_get_int(p1, 'd' as i32, 0), 4);
dc_param_empty(p1);
dc_param_set(
p1,
'a' as i32,
b"foo\x00" as *const u8 as *const libc::c_char,
);
dc_param_set_int(p1, 'b' as i32, 2i32);
dc_param_set(p1, 'c' as i32, 0 as *const libc::c_char);
dc_param_set_int(p1, 'd' as i32, 4i32);
assert_eq!(
CStr::from_ptr((*p1).packed as *const libc::c_char)
.to_str()
.unwrap(),
"a=foo\nb=2\nd=4"
);
dc_param_set(p1, 'b' as i32, 0 as *const libc::c_char);
assert_eq!(
CStr::from_ptr((*p1).packed as *const libc::c_char)
.to_str()
.unwrap(),
"a=foo\nd=4",
);
dc_param_set(p1, 'a' as i32, 0 as *const libc::c_char);
dc_param_set(p1, 'd' as i32, 0 as *const libc::c_char);
assert_eq!(
CStr::from_ptr((*p1).packed as *const libc::c_char)
.to_str()
.unwrap(),
"",
);
dc_param_unref(p1);
}
}
}

View File

@@ -1,340 +0,0 @@
use crate::context::Context;
use crate::dc_chat::*;
use crate::dc_contact::*;
use crate::dc_lot::*;
use crate::dc_param::*;
use crate::dc_strencode::*;
use crate::dc_tools::*;
use crate::key::*;
use crate::peerstate::*;
use crate::types::*;
use crate::x::*;
// out-of-band verification
// id=contact
// text1=groupname
// id=contact
// id=contact
// test1=formatted fingerprint
// id=contact
// text1=text
// text1=URL
// text1=error string
pub unsafe fn dc_check_qr(context: &Context, qr: *const libc::c_char) -> *mut dc_lot_t {
let mut current_block: u64;
let mut payload: *mut libc::c_char = 0 as *mut libc::c_char;
// must be normalized, if set
let mut addr: *mut libc::c_char = 0 as *mut libc::c_char;
// must be normalized, if set
let mut fingerprint: *mut libc::c_char = 0 as *mut libc::c_char;
let mut name: *mut libc::c_char = 0 as *mut libc::c_char;
let mut invitenumber: *mut libc::c_char = 0 as *mut libc::c_char;
let mut auth: *mut libc::c_char = 0 as *mut libc::c_char;
let mut qr_parsed: *mut dc_lot_t = dc_lot_new();
let mut chat_id: uint32_t = 0i32 as uint32_t;
let mut device_msg: *mut libc::c_char = 0 as *mut libc::c_char;
let mut grpid: *mut libc::c_char = 0 as *mut libc::c_char;
let mut grpname: *mut libc::c_char = 0 as *mut libc::c_char;
(*qr_parsed).state = 0i32;
if !qr.is_null() {
info!(context, 0, "Scanned QR code: {}", as_str(qr),);
/* split parameters from the qr code
------------------------------------ */
if strncasecmp(
qr,
b"OPENPGP4FPR:\x00" as *const u8 as *const libc::c_char,
strlen(b"OPENPGP4FPR:\x00" as *const u8 as *const libc::c_char),
) == 0i32
{
payload =
dc_strdup(&*qr.offset(strlen(
b"OPENPGP4FPR:\x00" as *const u8 as *const libc::c_char,
) as isize));
let mut fragment: *mut libc::c_char = strchr(payload, '#' as i32);
if !fragment.is_null() {
*fragment = 0i32 as libc::c_char;
fragment = fragment.offset(1isize);
let param: *mut dc_param_t = dc_param_new();
dc_param_set_urlencoded(param, fragment);
addr = dc_param_get(param, 'a' as i32, 0 as *const libc::c_char);
if !addr.is_null() {
let mut urlencoded: *mut libc::c_char =
dc_param_get(param, 'n' as i32, 0 as *const libc::c_char);
if !urlencoded.is_null() {
name = dc_urldecode(urlencoded);
dc_normalize_name(name);
free(urlencoded as *mut libc::c_void);
}
invitenumber = dc_param_get(param, 'i' as i32, 0 as *const libc::c_char);
auth = dc_param_get(param, 's' as i32, 0 as *const libc::c_char);
grpid = dc_param_get(param, 'x' as i32, 0 as *const libc::c_char);
if !grpid.is_null() {
urlencoded = dc_param_get(param, 'g' as i32, 0 as *const libc::c_char);
if !urlencoded.is_null() {
grpname = dc_urldecode(urlencoded);
free(urlencoded as *mut libc::c_void);
}
}
}
dc_param_unref(param);
}
fingerprint = dc_normalize_fingerprint_c(payload);
current_block = 5023038348526654800;
} else if strncasecmp(
qr,
b"mailto:\x00" as *const u8 as *const libc::c_char,
strlen(b"mailto:\x00" as *const u8 as *const libc::c_char),
) == 0i32
{
payload = dc_strdup(
&*qr.offset(strlen(b"mailto:\x00" as *const u8 as *const libc::c_char) as isize),
);
let query: *mut libc::c_char = strchr(payload, '?' as i32);
if !query.is_null() {
*query = 0i32 as libc::c_char
}
addr = dc_strdup(payload);
current_block = 5023038348526654800;
} else if strncasecmp(
qr,
b"SMTP:\x00" as *const u8 as *const libc::c_char,
strlen(b"SMTP:\x00" as *const u8 as *const libc::c_char),
) == 0i32
{
payload = dc_strdup(
&*qr.offset(strlen(b"SMTP:\x00" as *const u8 as *const libc::c_char) as isize),
);
let colon: *mut libc::c_char = strchr(payload, ':' as i32);
if !colon.is_null() {
*colon = 0i32 as libc::c_char
}
addr = dc_strdup(payload);
current_block = 5023038348526654800;
} else if strncasecmp(
qr,
b"MATMSG:\x00" as *const u8 as *const libc::c_char,
strlen(b"MATMSG:\x00" as *const u8 as *const libc::c_char),
) == 0i32
{
/* scheme: `MATMSG:TO:addr...;SUB:subject...;BODY:body...;` - there may or may not be linebreaks after the fields */
/* does not work when the text `TO:` is used in subject/body _and_ TO: is not the first field. we ignore this case. */
let to: *mut libc::c_char = strstr(qr, b"TO:\x00" as *const u8 as *const libc::c_char);
if !to.is_null() {
addr = dc_strdup(&mut *to.offset(3isize));
let semicolon: *mut libc::c_char = strchr(addr, ';' as i32);
if !semicolon.is_null() {
*semicolon = 0i32 as libc::c_char
}
current_block = 5023038348526654800;
} else {
(*qr_parsed).state = 400i32;
(*qr_parsed).text1 =
dc_strdup(b"Bad e-mail address.\x00" as *const u8 as *const libc::c_char);
current_block = 16562876845594826114;
}
} else {
if strncasecmp(
qr,
b"BEGIN:VCARD\x00" as *const u8 as *const libc::c_char,
strlen(b"BEGIN:VCARD\x00" as *const u8 as *const libc::c_char),
) == 0i32
{
let lines: *mut carray = dc_split_into_lines(qr);
let mut i: libc::c_int = 0i32;
while (i as libc::c_uint) < carray_count(lines) {
let key: *mut libc::c_char =
carray_get(lines, i as libc::c_uint) as *mut libc::c_char;
dc_trim(key);
let mut value: *mut libc::c_char = strchr(key, ':' as i32);
if !value.is_null() {
*value = 0i32 as libc::c_char;
value = value.offset(1isize);
let mut semicolon_0: *mut libc::c_char = strchr(key, ';' as i32);
if !semicolon_0.is_null() {
*semicolon_0 = 0i32 as libc::c_char
}
if strcasecmp(key, b"EMAIL\x00" as *const u8 as *const libc::c_char) == 0i32
{
semicolon_0 = strchr(value, ';' as i32);
if !semicolon_0.is_null() {
*semicolon_0 = 0i32 as libc::c_char
}
addr = dc_strdup(value)
} else if strcasecmp(key, b"N\x00" as *const u8 as *const libc::c_char)
== 0i32
{
semicolon_0 = strchr(value, ';' as i32);
if !semicolon_0.is_null() {
semicolon_0 = strchr(semicolon_0.offset(1isize), ';' as i32);
if !semicolon_0.is_null() {
*semicolon_0 = 0i32 as libc::c_char
}
}
name = dc_strdup(value);
dc_str_replace(
&mut name,
b";\x00" as *const u8 as *const libc::c_char,
b",\x00" as *const u8 as *const libc::c_char,
);
dc_normalize_name(name);
}
}
i += 1
}
dc_free_splitted_lines(lines);
}
current_block = 5023038348526654800;
}
match current_block {
16562876845594826114 => {}
_ => {
/* check the paramters
---------------------- */
if !addr.is_null() {
/* urldecoding is needed at least for OPENPGP4FPR but should not hurt in the other cases */
let mut temp: *mut libc::c_char = dc_urldecode(addr);
free(addr as *mut libc::c_void);
addr = temp;
temp = dc_addr_normalize(addr);
free(addr as *mut libc::c_void);
addr = temp;
if !dc_may_be_valid_addr(addr) {
(*qr_parsed).state = 400i32;
(*qr_parsed).text1 = dc_strdup(
b"Bad e-mail address.\x00" as *const u8 as *const libc::c_char,
);
current_block = 16562876845594826114;
} else {
current_block = 14116432890150942211;
}
} else {
current_block = 14116432890150942211;
}
match current_block {
16562876845594826114 => {}
_ => {
if !fingerprint.is_null() {
if strlen(fingerprint) != 40 {
(*qr_parsed).state = 400i32;
(*qr_parsed).text1 = dc_strdup(
b"Bad fingerprint length in QR code.\x00" as *const u8
as *const libc::c_char,
);
current_block = 16562876845594826114;
} else {
current_block = 5409161009579131794;
}
} else {
current_block = 5409161009579131794;
}
match current_block {
16562876845594826114 => {}
_ => {
if !fingerprint.is_null() {
let peerstate = Peerstate::from_fingerprint(
context,
&context.sql,
as_str(fingerprint),
);
if addr.is_null() || invitenumber.is_null() || auth.is_null() {
if let Some(peerstate) = peerstate {
(*qr_parsed).state = 210i32;
let c_addr = peerstate
.addr
.as_ref()
.map(to_cstring)
.unwrap_or_default();
let addr_ptr = if peerstate.addr.is_some() {
c_addr.as_ptr()
} else {
std::ptr::null()
};
(*qr_parsed).id = dc_add_or_lookup_contact(
context,
0 as *const libc::c_char,
addr_ptr,
0x80i32,
0 as *mut libc::c_int,
);
dc_create_or_lookup_nchat_by_contact_id(
context,
(*qr_parsed).id,
2i32,
&mut chat_id,
0 as *mut libc::c_int,
);
device_msg = dc_mprintf(
b"%s verified.\x00" as *const u8
as *const libc::c_char,
peerstate.addr,
)
} else {
(*qr_parsed).text1 =
dc_format_fingerprint_c(fingerprint);
(*qr_parsed).state = 230i32
}
} else {
if !grpid.is_null() && !grpname.is_null() {
(*qr_parsed).state = 202i32;
(*qr_parsed).text1 = dc_strdup(grpname);
(*qr_parsed).text2 = dc_strdup(grpid)
} else {
(*qr_parsed).state = 200i32
}
(*qr_parsed).id = dc_add_or_lookup_contact(
context,
name,
addr,
0x80i32,
0 as *mut libc::c_int,
);
(*qr_parsed).fingerprint = dc_strdup(fingerprint);
(*qr_parsed).invitenumber = dc_strdup(invitenumber);
(*qr_parsed).auth = dc_strdup(auth)
}
} else if !addr.is_null() {
(*qr_parsed).state = 320i32;
(*qr_parsed).id = dc_add_or_lookup_contact(
context,
name,
addr,
0x80i32,
0 as *mut libc::c_int,
)
} else if strstr(
qr,
b"http://\x00" as *const u8 as *const libc::c_char,
) == qr as *mut libc::c_char
|| strstr(
qr,
b"https://\x00" as *const u8 as *const libc::c_char,
) == qr as *mut libc::c_char
{
(*qr_parsed).state = 332i32;
(*qr_parsed).text1 = dc_strdup(qr)
} else {
(*qr_parsed).state = 330i32;
(*qr_parsed).text1 = dc_strdup(qr)
}
if !device_msg.is_null() {
dc_add_device_msg(context, chat_id, device_msg);
}
}
}
}
}
}
}
}
free(addr as *mut libc::c_void);
free(fingerprint as *mut libc::c_void);
free(payload as *mut libc::c_void);
free(name as *mut libc::c_void);
free(invitenumber as *mut libc::c_void);
free(auth as *mut libc::c_void);
free(device_msg as *mut libc::c_void);
free(grpname as *mut libc::c_void);
free(grpid as *mut libc::c_void);
qr_parsed
}

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

View File

@@ -1,374 +1,267 @@
use crate::dc_dehtml::*;
use crate::dc_tools::*;
use crate::types::*;
use crate::x::*;
#[derive(Copy, Clone)]
#[repr(C)]
pub struct dc_simplify_t {
pub is_forwarded: libc::c_int,
pub is_cut_at_begin: libc::c_int,
pub is_cut_at_end: libc::c_int,
pub struct Simplify {
pub is_forwarded: bool,
}
pub unsafe fn dc_simplify_new() -> *mut dc_simplify_t {
let simplify: *mut dc_simplify_t;
simplify = calloc(1, ::std::mem::size_of::<dc_simplify_t>()) as *mut dc_simplify_t;
assert!(!simplify.is_null());
/// Return index of footer line in vector of message lines, or vector length if
/// no footer is found.
///
/// Also return whether not-standard (rfc3676, §4.3) footer is found.
fn find_message_footer(lines: &[&str]) -> (usize, bool) {
for ix in 0..lines.len() {
let line = lines[ix];
simplify
}
pub unsafe fn dc_simplify_unref(simplify: *mut dc_simplify_t) {
if simplify.is_null() {
return;
}
free(simplify as *mut libc::c_void);
}
/* Simplify and normalise text: Remove quotes, signatures, unnecessary
lineends etc.
The data returned from Simplify() must be free()'d when no longer used, private */
pub unsafe fn dc_simplify_simplify(
mut simplify: *mut dc_simplify_t,
in_unterminated: *const libc::c_char,
in_bytes: libc::c_int,
is_html: libc::c_int,
is_msgrmsg: libc::c_int,
) -> *mut libc::c_char {
/* create a copy of the given buffer */
let mut out: *mut libc::c_char;
let mut temp: *mut libc::c_char;
if simplify.is_null() || in_unterminated.is_null() || in_bytes <= 0i32 {
return dc_strdup(b"\x00" as *const u8 as *const libc::c_char);
}
(*simplify).is_forwarded = 0i32;
(*simplify).is_cut_at_begin = 0i32;
(*simplify).is_cut_at_end = 0i32;
out = strndup(
in_unterminated as *mut libc::c_char,
in_bytes as libc::c_ulong,
);
if out.is_null() {
return dc_strdup(b"\x00" as *const u8 as *const libc::c_char);
}
if 0 != is_html {
temp = dc_dehtml(out);
if !temp.is_null() {
free(out as *mut libc::c_void);
out = temp
// quoted-printable may encode `-- ` to `-- =20` which is converted
// back to `-- `
match line.as_ref() {
"-- " | "-- " => return (ix, false),
"--" | "---" | "----" => return (ix, true),
_ => (),
}
}
dc_remove_cr_chars(out);
temp = dc_simplify_simplify_plain_text(simplify, out, is_msgrmsg);
if !temp.is_null() {
free(out as *mut libc::c_void);
out = temp
}
dc_remove_cr_chars(out);
out
return (lines.len(), false);
}
/**
* Simplify Plain Text
*/
unsafe fn dc_simplify_simplify_plain_text(
mut simplify: *mut dc_simplify_t,
buf_terminated: *const libc::c_char,
is_msgrmsg: libc::c_int,
) -> *mut libc::c_char {
/* This function ...
... removes all text after the line `-- ` (footer mark)
... removes full quotes at the beginning and at the end of the text -
these are all lines starting with the character `>`
... remove a non-empty line before the removed quote (contains sth. like "On 2.9.2016, Bjoern wrote:" in different formats and lanugages) */
/* split the given buffer into lines */
let lines: *mut carray = dc_split_into_lines(buf_terminated);
let mut l: libc::c_int;
let mut l_first: libc::c_int = 0i32;
/* if l_last is -1, there are no lines */
let mut l_last: libc::c_int =
carray_count(lines).wrapping_sub(1i32 as libc::c_uint) as libc::c_int;
let mut line: *mut libc::c_char;
let mut footer_mark: libc::c_int = 0i32;
l = l_first;
while l <= l_last {
line = carray_get(lines, l as libc::c_uint) as *mut libc::c_char;
if strcmp(line, b"-- \x00" as *const u8 as *const libc::c_char) == 0i32
|| strcmp(line, b"-- \x00" as *const u8 as *const libc::c_char) == 0i32
{
footer_mark = 1i32
impl Simplify {
pub fn new() -> Self {
Simplify {
is_forwarded: false,
}
if strcmp(line, b"--\x00" as *const u8 as *const libc::c_char) == 0i32
|| strcmp(line, b"---\x00" as *const u8 as *const libc::c_char) == 0i32
|| strcmp(line, b"----\x00" as *const u8 as *const libc::c_char) == 0i32
{
footer_mark = 1i32;
(*simplify).is_cut_at_end = 1i32
}
if 0 != footer_mark {
l_last = l - 1i32;
/* done */
break;
}
/// Simplify and normalise text: Remove quotes, signatures, unnecessary
/// lineends etc.
/// The data returned from simplify() must be free()'d when no longer used.
pub fn simplify(&mut self, input: &str, is_html: bool, is_msgrmsg: bool) -> String {
let mut out = if is_html {
dc_dehtml(input)
} else {
l += 1
}
input.to_string()
};
out.retain(|c| c != '\r');
out = self.simplify_plain_text(&out, is_msgrmsg);
out.retain(|c| c != '\r');
out
}
if l_last - l_first + 1i32 >= 3i32 {
let line0: *mut libc::c_char =
carray_get(lines, l_first as libc::c_uint) as *mut libc::c_char;
let line1: *mut libc::c_char =
carray_get(lines, (l_first + 1i32) as libc::c_uint) as *mut libc::c_char;
let line2: *mut libc::c_char =
carray_get(lines, (l_first + 2i32) as libc::c_uint) as *mut libc::c_char;
if strcmp(
line0,
b"---------- Forwarded message ----------\x00" as *const u8 as *const libc::c_char,
) == 0i32
&& strncmp(line1, b"From: \x00" as *const u8 as *const libc::c_char, 6) == 0i32
&& *line2.offset(0isize) as libc::c_int == 0i32
{
(*simplify).is_forwarded = 1i32;
l_first += 3i32
/**
* Simplify Plain Text
*/
#[allow(non_snake_case)]
fn simplify_plain_text(&mut self, buf_terminated: &str, is_msgrmsg: bool) -> String {
/* This function ...
... removes all text after the line `-- ` (footer mark)
... removes full quotes at the beginning and at the end of the text -
these are all lines starting with the character `>`
... remove a non-empty line before the removed quote (contains sth. like "On 2.9.2016, Bjoern wrote:" in different formats and lanugages) */
/* split the given buffer into lines */
let lines: Vec<_> = buf_terminated.split('\n').collect();
let mut l_first: usize = 0;
let mut is_cut_at_begin = false;
let (mut l_last, mut is_cut_at_end) = find_message_footer(&lines);
if l_last > l_first + 2 {
let line0 = lines[l_first];
let line1 = lines[l_first + 1];
let line2 = lines[l_first + 2];
if line0 == "---------- Forwarded message ----------"
&& line1.starts_with("From: ")
&& line2.is_empty()
{
self.is_forwarded = true;
l_first += 3
}
}
}
l = l_first;
while l <= l_last {
line = carray_get(lines, l as libc::c_uint) as *mut libc::c_char;
if strncmp(line, b"-----\x00" as *const u8 as *const libc::c_char, 5) == 0i32
|| strncmp(line, b"_____\x00" as *const u8 as *const libc::c_char, 5) == 0i32
|| strncmp(line, b"=====\x00" as *const u8 as *const libc::c_char, 5) == 0i32
|| strncmp(line, b"*****\x00" as *const u8 as *const libc::c_char, 5) == 0i32
|| strncmp(line, b"~~~~~\x00" as *const u8 as *const libc::c_char, 5) == 0i32
{
l_last = l - 1i32;
(*simplify).is_cut_at_end = 1i32;
/* done */
break;
} else {
l += 1
}
}
if 0 == is_msgrmsg {
let mut l_lastQuotedLine: libc::c_int = -1i32;
l = l_last;
while l >= l_first {
line = carray_get(lines, l as libc::c_uint) as *mut libc::c_char;
if is_plain_quote(line) {
l_lastQuotedLine = l
} else if !is_empty_line(line) {
for l in l_first..l_last {
let line = lines[l];
if line == "-----"
|| line == "_____"
|| line == "====="
|| line == "*****"
|| line == "~~~~~"
{
l_last = l;
is_cut_at_end = true;
/* done */
break;
}
l -= 1
}
if l_lastQuotedLine != -1i32 {
l_last = l_lastQuotedLine - 1i32;
(*simplify).is_cut_at_end = 1i32;
if l_last > 0i32 {
if is_empty_line(carray_get(lines, l_last as libc::c_uint) as *mut libc::c_char) {
l_last -= 1
}
}
if l_last > 0i32 {
line = carray_get(lines, l_last as libc::c_uint) as *mut libc::c_char;
if is_quoted_headline(line) {
l_last -= 1
}
}
}
}
if 0 == is_msgrmsg {
let mut l_lastQuotedLine_0: libc::c_int = -1i32;
let mut hasQuotedHeadline: libc::c_int = 0i32;
l = l_first;
while l <= l_last {
line = carray_get(lines, l as libc::c_uint) as *mut libc::c_char;
if is_plain_quote(line) {
l_lastQuotedLine_0 = l
} else if !is_empty_line(line) {
if is_quoted_headline(line) && 0 == hasQuotedHeadline && l_lastQuotedLine_0 == -1i32
{
hasQuotedHeadline = 1i32
} else {
/* non-quoting line found */
if !is_msgrmsg {
let mut l_lastQuotedLine = None;
for l in (l_first..l_last).rev() {
let line = lines[l];
if is_plain_quote(line) {
l_lastQuotedLine = Some(l)
} else if !is_empty_line(line) {
break;
}
}
l += 1
}
if l_lastQuotedLine_0 != -1i32 {
l_first = l_lastQuotedLine_0 + 1i32;
(*simplify).is_cut_at_begin = 1i32
}
}
/* re-create buffer from the remaining lines */
let mut ret = String::new();
if 0 != (*simplify).is_cut_at_begin {
ret += "[...]";
}
/* we write empty lines only in case and non-empty line follows */
let mut pending_linebreaks: libc::c_int = 0i32;
let mut content_lines_added: libc::c_int = 0i32;
l = l_first;
while l <= l_last {
line = carray_get(lines, l as libc::c_uint) as *mut libc::c_char;
if is_empty_line(line) {
pending_linebreaks += 1
} else {
if 0 != content_lines_added {
if pending_linebreaks > 2i32 {
pending_linebreaks = 2i32
if let Some(last_quoted_line) = l_lastQuotedLine {
l_last = last_quoted_line;
is_cut_at_end = true;
if l_last > 1 {
if is_empty_line(lines[l_last - 1]) {
l_last -= 1
}
}
while 0 != pending_linebreaks {
ret += "\n";
pending_linebreaks -= 1
if l_last > 1 {
let line = lines[l_last - 1];
if is_quoted_headline(line) {
l_last -= 1
}
}
}
ret += &to_string(line);
content_lines_added += 1;
pending_linebreaks = 1i32
}
l += 1
}
if 0 != (*simplify).is_cut_at_end
&& (0 == (*simplify).is_cut_at_begin || 0 != content_lines_added)
{
ret += " [...]";
}
dc_free_splitted_lines(lines);
if !is_msgrmsg {
let mut l_lastQuotedLine_0 = None;
let mut hasQuotedHeadline = 0;
for l in l_first..l_last {
let line = lines[l];
if is_plain_quote(line) {
l_lastQuotedLine_0 = Some(l)
} else if !is_empty_line(line) {
if is_quoted_headline(line)
&& 0 == hasQuotedHeadline
&& l_lastQuotedLine_0.is_none()
{
hasQuotedHeadline = 1i32
} else {
/* non-quoting line found */
break;
}
}
}
if let Some(last_quoted_line) = l_lastQuotedLine_0 {
l_first = last_quoted_line + 1;
is_cut_at_begin = true
}
}
/* re-create buffer from the remaining lines */
let mut ret = String::new();
if is_cut_at_begin {
ret += "[...]";
}
/* we write empty lines only in case and non-empty line follows */
let mut pending_linebreaks: libc::c_int = 0i32;
let mut content_lines_added: libc::c_int = 0i32;
for l in l_first..l_last {
let line = lines[l];
if is_empty_line(line) {
pending_linebreaks += 1
} else {
if 0 != content_lines_added {
if pending_linebreaks > 2i32 {
pending_linebreaks = 2i32
}
while 0 != pending_linebreaks {
ret += "\n";
pending_linebreaks -= 1
}
}
// the incoming message might contain invalid UTF8
ret += line;
content_lines_added += 1;
pending_linebreaks = 1i32
}
}
if is_cut_at_end && (!is_cut_at_begin || 0 != content_lines_added) {
ret += " [...]";
}
strdup(to_cstring(ret).as_ptr())
ret
}
}
/**
* Tools
*/
unsafe fn is_empty_line(buf: *const libc::c_char) -> bool {
/* force unsigned - otherwise the `> ' '` comparison will fail */
let mut p1: *const libc::c_uchar = buf as *const libc::c_uchar;
while 0 != *p1 {
if *p1 as libc::c_int > ' ' as i32 {
fn is_empty_line(buf: &str) -> bool {
// XXX: can it be simplified to buf.chars().all(|c| c.is_whitespace())?
//
// Strictly speaking, it is not equivalent (^A is not whitespace, but less than ' '),
// but having control sequences in email body?!
//
// See discussion at: https://github.com/deltachat/deltachat-core-rust/pull/402#discussion_r317062392
for c in buf.chars() {
if c > ' ' {
return false;
}
p1 = p1.offset(1isize)
}
true
}
unsafe fn is_quoted_headline(buf: *const libc::c_char) -> bool {
fn is_quoted_headline(buf: &str) -> bool {
/* This function may be called for the line _directly_ before a quote.
The function checks if the line contains sth. like "On 01.02.2016, xy@z wrote:" in various languages.
- Currently, we simply check if the last character is a ':'.
- Checking for the existance of an email address may fail (headlines may show the user's name instead of the address) */
let buf_len: libc::c_int = strlen(buf) as libc::c_int;
if buf_len > 80i32 {
return false;
}
if buf_len > 0i32 && *buf.offset((buf_len - 1i32) as isize) as libc::c_int == ':' as i32 {
return true;
}
- Checking for the existence of an email address may fail (headlines may show the user's name instead of the address) */
false
buf.len() <= 80 && buf.ends_with(':')
}
unsafe fn is_plain_quote(buf: *const libc::c_char) -> bool {
if *buf.offset(0isize) as libc::c_int == '>' as i32 {
return true;
}
false
fn is_plain_quote(buf: &str) -> bool {
buf.starts_with(">")
}
#[cfg(test)]
mod tests {
use super::*;
use std::ffi::CStr;
#[test]
fn test_simplify_trim() {
unsafe {
let simplify: *mut dc_simplify_t = dc_simplify_new();
let html: *const libc::c_char =
b"\r\r\nline1<br>\r\n\r\n\r\rline2\n\r\x00" as *const u8 as *const libc::c_char;
let plain: *mut libc::c_char =
dc_simplify_simplify(simplify, html, strlen(html) as libc::c_int, 1, 0);
let mut simplify = Simplify::new();
let html = "\r\r\nline1<br>\r\n\r\n\r\rline2\n\r";
let plain = simplify.simplify(html, true, false);
assert_eq!(
CStr::from_ptr(plain as *const libc::c_char)
.to_str()
.unwrap(),
"line1\nline2",
);
free(plain as *mut libc::c_void);
dc_simplify_unref(simplify);
}
assert_eq!(plain, "line1\nline2");
}
#[test]
fn test_simplify_parse_href() {
unsafe {
let simplify: *mut dc_simplify_t = dc_simplify_new();
let html: *const libc::c_char =
b"<a href=url>text</a\x00" as *const u8 as *const libc::c_char;
let plain: *mut libc::c_char =
dc_simplify_simplify(simplify, html, strlen(html) as libc::c_int, 1, 0);
let mut simplify = Simplify::new();
let html = "<a href=url>text</a";
let plain = simplify.simplify(html, true, false);
assert_eq!(
CStr::from_ptr(plain as *const libc::c_char)
.to_str()
.unwrap(),
"[text](url)",
);
free(plain as *mut libc::c_void);
dc_simplify_unref(simplify);
}
assert_eq!(plain, "[text](url)");
}
#[test]
fn test_simplify_bold_text() {
unsafe {
let simplify: *mut dc_simplify_t = dc_simplify_new();
let html: *const libc::c_char =
b"<!DOCTYPE name [<!DOCTYPE ...>]><!-- comment -->text <b><?php echo ... ?>bold</b><![CDATA[<>]]>\x00"
as *const u8 as *const libc::c_char;
let plain: *mut libc::c_char =
dc_simplify_simplify(simplify, html, strlen(html) as libc::c_int, 1, 0);
let mut simplify = Simplify::new();
let html = "<!DOCTYPE name [<!DOCTYPE ...>]><!-- comment -->text <b><?php echo ... ?>bold</b><![CDATA[<>]]>";
let plain = simplify.simplify(html, true, false);
assert_eq!(
CStr::from_ptr(plain as *const libc::c_char)
.to_str()
.unwrap(),
"text *bold*<>",
);
free(plain as *mut libc::c_void);
dc_simplify_unref(simplify);
}
assert_eq!(plain, "text *bold*<>");
}
#[test]
fn test_simplify_html_encoded() {
unsafe {
let simplify: *mut dc_simplify_t = dc_simplify_new();
let html: *const libc::c_char =
b"&lt;&gt;&quot;&apos;&amp; &auml;&Auml;&ouml;&Ouml;&uuml;&Uuml;&szlig; foo&AElig;&ccedil;&Ccedil; &diams;&noent;&lrm;&rlm;&zwnj;&zwj;\x00"
as *const u8 as *const libc::c_char;
let plain: *mut libc::c_char =
dc_simplify_simplify(simplify, html, strlen(html) as libc::c_int, 1, 0);
let mut simplify = Simplify::new();
let html =
"&lt;&gt;&quot;&apos;&amp; &auml;&Auml;&ouml;&Ouml;&uuml;&Uuml;&szlig; foo&AElig;&ccedil;&Ccedil; &diams;&lrm;&rlm;&zwnj;&noent;&zwj;";
assert_eq!(
strcmp(plain,
b"<>\"\'& \xc3\xa4\xc3\x84\xc3\xb6\xc3\x96\xc3\xbc\xc3\x9c\xc3\x9f foo\xc3\x86\xc3\xa7\xc3\x87 \xe2\x99\xa6&noent;\x00"
as *const u8 as *const libc::c_char),
0,
);
let plain = simplify.simplify(html, true, false);
free(plain as *mut libc::c_void);
dc_simplify_unref(simplify);
}
assert_eq!(
plain,
"<>\"\'& äÄöÖüÜß fooÆçÇ \u{2666}\u{200e}\u{200f}\u{200c}&noent;\u{200d}"
);
}
#[test]
fn test_simplify_utilities() {
assert!(is_empty_line(" \t"));
assert!(is_empty_line(""));
assert!(is_empty_line(" \r"));
assert!(!is_empty_line(" x"));
assert!(is_plain_quote("> hello world"));
assert!(is_plain_quote(">>"));
assert!(!is_plain_quote("Life is pain"));
assert!(!is_plain_quote(""));
}
}

View File

@@ -1,338 +0,0 @@
use crate::constants::Event;
use crate::context::Context;
use crate::dc_contact::*;
use crate::dc_tools::*;
use crate::types::*;
use crate::x::*;
/* Return the string with the given ID by calling DC_EVENT_GET_STRING.
The result must be free()'d! */
pub unsafe fn dc_stock_str(context: &Context, id: libc::c_int) -> *mut libc::c_char {
return get_string(context, id, 0i32);
}
unsafe fn get_string(context: &Context, id: libc::c_int, qty: libc::c_int) -> *mut libc::c_char {
let mut ret: *mut libc::c_char;
ret =
context.call_cb(Event::GET_STRING, id as uintptr_t, qty as uintptr_t) as *mut libc::c_char;
if ret.is_null() {
ret = default_string(id)
}
ret
}
/* Add translated strings that are used by the messager backend.
As the logging functions may use these strings, do not log any
errors from here. */
unsafe fn default_string(id: libc::c_int) -> *mut libc::c_char {
// TODO match on enum values /rtn
match id {
1 => {
return dc_strdup(b"No messages.\x00" as *const u8 as
*const libc::c_char)
}
2 => {
return dc_strdup(b"Me\x00" as *const u8 as *const libc::c_char)
}
3 => {
return dc_strdup(b"Draft\x00" as *const u8 as *const libc::c_char)
}
4 => {
return dc_strdup(b"%1$s member(s)\x00" as *const u8 as
*const libc::c_char)
}
6 => {
return dc_strdup(b"%1$s contact(s)\x00" as *const u8 as
*const libc::c_char)
}
7 => {
return dc_strdup(b"Voice message\x00" as *const u8 as
*const libc::c_char)
}
8 => {
return dc_strdup(b"Contact requests\x00" as *const u8 as
*const libc::c_char)
}
9 => {
return dc_strdup(b"Image\x00" as *const u8 as *const libc::c_char)
}
23 => {
return dc_strdup(b"GIF\x00" as *const u8 as *const libc::c_char)
}
10 => {
return dc_strdup(b"Video\x00" as *const u8 as *const libc::c_char)
}
11 => {
return dc_strdup(b"Audio\x00" as *const u8 as *const libc::c_char)
}
12 => {
return dc_strdup(b"File\x00" as *const u8 as *const libc::c_char)
}
66 => {
return dc_strdup(b"Location\x00" as *const u8 as
*const libc::c_char)
}
24 => {
return dc_strdup(b"Encrypted message\x00" as *const u8 as
*const libc::c_char)
}
13 => {
return dc_strdup(b"Sent with my Delta Chat Messenger: https://delta.chat\x00"
as *const u8 as *const libc::c_char)
}
14 => {
return dc_strdup(b"Hello, I\'ve just created the group \"%1$s\" for us.\x00"
as *const u8 as *const libc::c_char)
}
15 => {
return dc_strdup(b"Group name changed from \"%1$s\" to \"%2$s\".\x00"
as *const u8 as *const libc::c_char)
}
16 => {
return dc_strdup(b"Group image changed.\x00" as *const u8 as
*const libc::c_char)
}
17 => {
return dc_strdup(b"Member %1$s added.\x00" as *const u8 as
*const libc::c_char)
}
18 => {
return dc_strdup(b"Member %1$s removed.\x00" as *const u8 as
*const libc::c_char)
}
19 => {
return dc_strdup(b"Group left.\x00" as *const u8 as
*const libc::c_char)
}
64 => {
return dc_strdup(b"Location streaming enabled.\x00" as *const u8
as *const libc::c_char)
}
65 => {
return dc_strdup(b"Location streaming disabled.\x00" as *const u8
as *const libc::c_char)
}
62 => {
return dc_strdup(b"%1$s by %2$s.\x00" as *const u8 as
*const libc::c_char)
}
63 => {
return dc_strdup(b"%1$s by me.\x00" as *const u8 as
*const libc::c_char)
}
25 => {
return dc_strdup(b"End-to-end encryption available.\x00" as
*const u8 as *const libc::c_char)
}
27 => {
return dc_strdup(b"Transport-encryption.\x00" as *const u8 as
*const libc::c_char)
}
28 => {
return dc_strdup(b"No encryption.\x00" as *const u8 as
*const libc::c_char)
}
30 => {
return dc_strdup(b"Fingerprints\x00" as *const u8 as
*const libc::c_char)
}
31 => {
return dc_strdup(b"Return receipt\x00" as *const u8 as
*const libc::c_char)
}
32 => {
return dc_strdup(b"This is a return receipt for the message \"%1$s\".\x00"
as *const u8 as *const libc::c_char)
}
33 => {
return dc_strdup(b"Group image deleted.\x00" as *const u8 as
*const libc::c_char)
}
34 => {
return dc_strdup(b"End-to-end encryption preferred.\x00" as
*const u8 as *const libc::c_char)
}
35 => {
return dc_strdup(b"%1$s verified.\x00" as *const u8 as
*const libc::c_char)
}
36 => {
return dc_strdup(b"Cannot verifiy %1$s\x00" as *const u8 as
*const libc::c_char)
}
37 => {
return dc_strdup(b"Changed setup for %1$s\x00" as *const u8 as
*const libc::c_char)
}
40 => {
return dc_strdup(b"Archived chats\x00" as *const u8 as
*const libc::c_char)
}
41 => {
return dc_strdup(b"Starred messages\x00" as *const u8 as
*const libc::c_char)
}
42 => {
return dc_strdup(b"Autocrypt Setup Message\x00" as *const u8 as
*const libc::c_char)
}
43 => {
return dc_strdup(b"This is the Autocrypt Setup Message used to transfer your key between clients.\n\nTo decrypt and use your key, open the message in an Autocrypt-compliant client and enter the setup code presented on the generating device.\x00"
as *const u8 as *const libc::c_char)
}
50 => {
return dc_strdup(b"Messages I sent to myself\x00" as *const u8 as
*const libc::c_char)
}
29 => {
return dc_strdup(b"This message was encrypted for another setup.\x00"
as *const u8 as *const libc::c_char)
}
60 => {
return dc_strdup(b"Cannot login as %1$s.\x00" as *const u8 as
*const libc::c_char)
}
61 => {
return dc_strdup(b"Response from %1$s: %2$s\x00" as *const u8 as
*const libc::c_char)
}
_ => { }
}
dc_strdup(b"ErrStr\x00" as *const u8 as *const libc::c_char)
}
/* Replaces the first `%1$s` in the given String-ID by the given value.
The result must be free()'d! */
pub unsafe fn dc_stock_str_repl_string(
context: &Context,
id: libc::c_int,
to_insert: *const libc::c_char,
) -> *mut libc::c_char {
let mut ret: *mut libc::c_char = get_string(context, id, 0i32);
dc_str_replace(
&mut ret,
b"%1$s\x00" as *const u8 as *const libc::c_char,
to_insert,
);
dc_str_replace(
&mut ret,
b"%1$d\x00" as *const u8 as *const libc::c_char,
to_insert,
);
ret
}
pub unsafe fn dc_stock_str_repl_int(
context: &Context,
id: libc::c_int,
to_insert_int: libc::c_int,
) -> *mut libc::c_char {
let mut ret: *mut libc::c_char = get_string(context, id, to_insert_int);
let to_insert_str: *mut libc::c_char = dc_mprintf(
b"%i\x00" as *const u8 as *const libc::c_char,
to_insert_int as libc::c_int,
);
dc_str_replace(
&mut ret,
b"%1$s\x00" as *const u8 as *const libc::c_char,
to_insert_str,
);
dc_str_replace(
&mut ret,
b"%1$d\x00" as *const u8 as *const libc::c_char,
to_insert_str,
);
free(to_insert_str as *mut libc::c_void);
ret
}
/* Replaces the first `%1$s` and `%2$s` in the given String-ID by the two given strings.
The result must be free()'d! */
pub unsafe fn dc_stock_str_repl_string2(
context: &Context,
id: libc::c_int,
to_insert: *const libc::c_char,
to_insert2: *const libc::c_char,
) -> *mut libc::c_char {
let mut ret: *mut libc::c_char = get_string(context, id, 0i32);
dc_str_replace(
&mut ret,
b"%1$s\x00" as *const u8 as *const libc::c_char,
to_insert,
);
dc_str_replace(
&mut ret,
b"%1$d\x00" as *const u8 as *const libc::c_char,
to_insert,
);
dc_str_replace(
&mut ret,
b"%2$s\x00" as *const u8 as *const libc::c_char,
to_insert2,
);
dc_str_replace(
&mut ret,
b"%2$d\x00" as *const u8 as *const libc::c_char,
to_insert2,
);
ret
}
/* Misc. */
pub unsafe fn dc_stock_system_msg(
context: &Context,
str_id: libc::c_int,
mut param1: *const libc::c_char,
param2: *const libc::c_char,
from_id: uint32_t,
) -> *mut libc::c_char {
let ret: *mut libc::c_char;
let mut mod_contact: *mut dc_contact_t = 0 as *mut dc_contact_t;
let mut mod_displayname: *mut libc::c_char = 0 as *mut libc::c_char;
let mut from_contact: *mut dc_contact_t = 0 as *mut dc_contact_t;
let mut from_displayname: *mut libc::c_char = 0 as *mut libc::c_char;
if str_id == 17i32 || str_id == 18i32 {
let mod_contact_id: uint32_t = dc_lookup_contact_id_by_addr(context, param1);
if mod_contact_id != 0i32 as libc::c_uint {
mod_contact = dc_get_contact(context, mod_contact_id);
mod_displayname = dc_contact_get_name_n_addr(mod_contact);
param1 = mod_displayname
}
}
let action: *mut libc::c_char = dc_stock_str_repl_string2(context, str_id, param1, param2);
if 0 != from_id {
if 0 != strlen(action)
&& *action.offset(strlen(action).wrapping_sub(1) as isize) as libc::c_int == '.' as i32
{
*action.offset(strlen(action).wrapping_sub(1) as isize) = 0i32 as libc::c_char
}
from_contact = dc_get_contact(context, from_id);
from_displayname = dc_contact_get_display_name(from_contact);
ret = dc_stock_str_repl_string2(
context,
if from_id == 1i32 as libc::c_uint {
63i32
} else {
62i32
},
action,
from_displayname,
)
} else {
ret = dc_strdup(action)
}
free(action as *mut libc::c_void);
free(from_displayname as *mut libc::c_void);
free(mod_displayname as *mut libc::c_void);
dc_contact_unref(from_contact);
dc_contact_unref(mod_contact);
ret
}

View File

@@ -1,4 +1,5 @@
use std::ffi::{CStr, CString};
use std::ptr;
use charset::Charset;
use mmime::mailmime_decode::*;
@@ -10,7 +11,7 @@ use crate::types::*;
use crate::x::*;
#[inline]
pub fn isalnum(c: libc::c_int) -> libc::c_int {
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 {
@@ -18,57 +19,15 @@ pub fn isalnum(c: libc::c_int) -> libc::c_int {
}
}
pub unsafe fn dc_urlencode(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"\x00" as *const u8 as *const libc::c_char);
}
let buf: *mut libc::c_char =
malloc(strlen(to_encode).wrapping_mul(3).wrapping_add(1)) as *mut libc::c_char;
let mut pbuf: *mut libc::c_char = buf;
assert!(!buf.is_null());
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 fresh0 = pbuf;
pbuf = pbuf.offset(1);
*fresh0 = *pstr
} else if *pstr as libc::c_int == ' ' as i32 {
let fresh1 = pbuf;
pbuf = pbuf.offset(1);
*fresh1 = '+' as i32 as libc::c_char
} else {
let fresh2 = pbuf;
pbuf = pbuf.offset(1);
*fresh2 = '%' as i32 as libc::c_char;
let fresh3 = pbuf;
pbuf = pbuf.offset(1);
*fresh3 = int_2_uppercase_hex((*pstr as libc::c_int >> 4i32) as libc::c_char);
let fresh4 = pbuf;
pbuf = pbuf.offset(1);
*fresh4 = 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
}
/* ******************************************************************************
* 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] = [
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]
HEX[(code as libc::c_int & 15i32) as usize]
}
pub unsafe fn dc_urldecode(to_decode: *const libc::c_char) -> *mut libc::c_char {
@@ -120,97 +79,90 @@ fn hex_2_int(ch: libc::c_char) -> libc::c_char {
}
pub unsafe fn dc_encode_header_words(to_encode: *const libc::c_char) -> *mut libc::c_char {
let mut current_block: u64;
let mut ret_str: *mut libc::c_char = 0 as *mut libc::c_char;
let mut ok_to_continue = true;
let mut ret_str: *mut libc::c_char = ptr::null_mut();
let mut cur: *const libc::c_char = to_encode;
let mmapstr: *mut MMAPString = mmap_string_new(b"\x00" as *const u8 as *const libc::c_char);
if to_encode.is_null() || mmapstr.is_null() {
current_block = 8550051112593613029;
} else {
current_block = 4644295000439058019;
ok_to_continue = false;
}
loop {
match current_block {
8550051112593613029 => {
if !mmapstr.is_null() {
mmap_string_free(mmapstr);
}
break;
if !ok_to_continue {
if !mmapstr.is_null() {
mmap_string_free(mmapstr);
}
_ => {
if *cur as libc::c_int != '\u{0}' as i32 {
let begin: *const libc::c_char;
let mut end: *const libc::c_char;
let mut do_quote: bool;
let mut quote_words: libc::c_int;
begin = cur;
end = begin;
quote_words = 0i32;
do_quote = true;
while *cur as libc::c_int != '\u{0}' as i32 {
get_word(cur, &mut cur, &mut do_quote);
if !do_quote {
break;
}
quote_words = 1i32;
end = cur;
if *cur as libc::c_int != '\u{0}' as i32 {
cur = cur.offset(1isize)
}
break;
} else {
if *cur as libc::c_int != '\u{0}' as i32 {
let begin: *const libc::c_char;
let mut end: *const libc::c_char;
let mut do_quote: bool;
let mut quote_words: libc::c_int;
begin = cur;
end = begin;
quote_words = 0i32;
do_quote = true;
while *cur as libc::c_int != '\u{0}' as i32 {
get_word(cur, &mut cur, &mut do_quote);
if !do_quote {
break;
}
if 0 != quote_words {
if !quote_word(
b"utf-8\x00" as *const u8 as *const libc::c_char,
mmapstr,
begin,
end.wrapping_offset_from(begin) as size_t,
) {
current_block = 8550051112593613029;
continue;
}
if *end as libc::c_int == ' ' as i32 || *end as libc::c_int == '\t' as i32 {
if mmap_string_append_c(mmapstr, *end).is_null() {
current_block = 8550051112593613029;
continue;
}
end = end.offset(1isize)
}
if *end as libc::c_int != '\u{0}' as i32 {
if mmap_string_append_len(
mmapstr,
end,
cur.wrapping_offset_from(end) as size_t,
)
.is_null()
{
current_block = 8550051112593613029;
continue;
}
}
} else if mmap_string_append_len(
quote_words = 1i32;
end = cur;
if *cur as libc::c_int != '\u{0}' as i32 {
cur = cur.offset(1isize)
}
}
if 0 != quote_words {
if !quote_word(
b"utf-8\x00" as *const u8 as *const libc::c_char,
mmapstr,
begin,
cur.wrapping_offset_from(begin) as size_t,
)
.is_null()
{
current_block = 8550051112593613029;
end.wrapping_offset_from(begin) as size_t,
) {
ok_to_continue = false;
continue;
}
if !(*cur as libc::c_int == ' ' as i32 || *cur as libc::c_int == '\t' as i32) {
current_block = 4644295000439058019;
continue;
if *end as libc::c_int == ' ' as i32 || *end as libc::c_int == '\t' as i32 {
if mmap_string_append_c(mmapstr, *end).is_null() {
ok_to_continue = false;
continue;
}
end = end.offset(1isize)
}
if mmap_string_append_c(mmapstr, *cur).is_null() {
current_block = 8550051112593613029;
continue;
if *end as libc::c_int != '\u{0}' as i32 {
if mmap_string_append_len(
mmapstr,
end,
cur.wrapping_offset_from(end) as size_t,
)
.is_null()
{
ok_to_continue = false;
continue;
}
}
cur = cur.offset(1isize);
current_block = 4644295000439058019;
} else {
ret_str = strdup((*mmapstr).str_0);
current_block = 8550051112593613029;
} else if mmap_string_append_len(
mmapstr,
begin,
cur.wrapping_offset_from(begin) as size_t,
)
.is_null()
{
ok_to_continue = false;
continue;
}
if !(*cur as libc::c_int == ' ' as i32 || *cur as libc::c_int == '\t' as i32) {
continue;
}
if mmap_string_append_c(mmapstr, *cur).is_null() {
ok_to_continue = false;
continue;
}
cur = cur.offset(1isize);
} else {
ret_str = strdup((*mmapstr).str_0);
ok_to_continue = false;
}
}
}
@@ -319,9 +271,9 @@ unsafe fn to_be_quoted(word: *const libc::c_char, size: size_t) -> bool {
pub unsafe fn dc_decode_header_words(in_0: *const libc::c_char) -> *mut libc::c_char {
if in_0.is_null() {
return 0 as *mut libc::c_char;
return ptr::null_mut();
}
let mut out: *mut libc::c_char = 0 as *mut libc::c_char;
let mut out: *mut libc::c_char = ptr::null_mut();
let mut cur_token: size_t = 0i32 as size_t;
let r: libc::c_int = mailmime_encoded_phrase_parse(
b"iso-8859-1\x00" as *const u8 as *const libc::c_char,
@@ -338,7 +290,8 @@ pub unsafe fn dc_decode_header_words(in_0: *const libc::c_char) -> *mut libc::c_
out
}
pub unsafe fn dc_encode_modified_utf7(
#[cfg(test)]
unsafe fn dc_encode_modified_utf7(
mut to_encode: *const libc::c_char,
change_spaces: libc::c_int,
) -> *mut libc::c_char {
@@ -378,7 +331,7 @@ pub unsafe fn dc_encode_modified_utf7(
if 0 != bitstogo {
let fresh8 = dst;
dst = dst.offset(1);
*fresh8 = base64chars
*fresh8 = BASE64CHARS
[(bitbuf << (6i32 as libc::c_uint).wrapping_sub(bitstogo) & 0x3f) as usize]
}
let fresh9 = dst;
@@ -449,7 +402,7 @@ pub unsafe fn dc_encode_modified_utf7(
bitstogo = bitstogo.wrapping_sub(6i32 as libc::c_uint);
let fresh14 = dst;
dst = dst.offset(1);
*fresh14 = base64chars[(if 0 != bitstogo {
*fresh14 = BASE64CHARS[(if 0 != bitstogo {
bitbuf >> bitstogo
} else {
bitbuf
@@ -465,7 +418,7 @@ pub unsafe fn dc_encode_modified_utf7(
if 0 != bitstogo {
let fresh15 = dst;
dst = dst.offset(1);
*fresh15 = base64chars
*fresh15 = BASE64CHARS
[(bitbuf << (6i32 as libc::c_uint).wrapping_sub(bitstogo) & 0x3f) as usize]
}
let fresh16 = dst;
@@ -482,13 +435,15 @@ pub unsafe fn dc_encode_modified_utf7(
******************************************************************************/
// UTF7 modified base64 alphabet
static mut base64chars: [libc::c_char; 65] = [
#[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,
];
pub unsafe fn dc_decode_modified_utf7(
#[cfg(test)]
unsafe fn dc_decode_modified_utf7(
to_decode: *const libc::c_char,
change_spaces: libc::c_int,
) -> *mut libc::c_char {
@@ -517,7 +472,7 @@ pub unsafe fn dc_decode_modified_utf7(
);
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;
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 {
@@ -662,8 +617,8 @@ pub unsafe fn dc_encode_ext_header(to_encode: *const libc::c_char) -> *mut libc:
}
pub unsafe fn dc_decode_ext_header(to_decode: *const libc::c_char) -> *mut libc::c_char {
let mut decoded: *mut libc::c_char = 0 as *mut libc::c_char;
let mut charset: *mut libc::c_char = 0 as *mut libc::c_char;
let mut 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
@@ -689,21 +644,20 @@ pub unsafe fn dc_decode_ext_header(to_decode: *const libc::c_char) -> *mut libc:
std::slice::from_raw_parts(decoded as *const u8, strlen(decoded));
let (res, _, _) = encoding.decode(data);
free(decoded as *mut libc::c_void);
let res_c = CString::new(res.as_bytes()).unwrap();
decoded = strdup(res_c.as_ptr());
free(decoded as *mut _);
let r = std::ffi::CString::new(res.as_bytes()).unwrap();
decoded = dc_strdup(r.as_ptr());
}
}
}
}
}
free(charset as *mut libc::c_void);
return if !decoded.is_null() {
if !decoded.is_null() {
decoded
} else {
dc_strdup(to_decode)
};
}
}
unsafe fn print_hex(target: *mut libc::c_char, cur: *const libc::c_char) {
@@ -711,13 +665,14 @@ unsafe fn print_hex(target: *mut libc::c_char, cur: *const libc::c_char) {
assert!(!cur.is_null());
let bytes = std::slice::from_raw_parts(cur as *const _, strlen(cur));
let raw = to_cstring(format!("={}", &hex::encode_upper(bytes)[..2]));
let raw = CString::yolo(format!("={}", &hex::encode_upper(bytes)[..2]));
libc::memcpy(target as *mut _, raw.as_ptr() as *const _, 4);
}
#[cfg(test)]
mod tests {
use super::*;
use percent_encoding::{percent_encode, NON_ALPHANUMERIC};
use std::ffi::CStr;
#[test]
@@ -897,14 +852,15 @@ mod tests {
#[test]
fn test_dc_urlencode_urldecode() {
unsafe {
let buf1 =
dc_urlencode(b"Bj\xc3\xb6rn Petersen\x00" as *const u8 as *const libc::c_char);
let buf1 = percent_encode(b"Bj\xc3\xb6rn Petersen", NON_ALPHANUMERIC)
.to_string()
.strdup();
assert_eq!(
CStr::from_ptr(buf1 as *const libc::c_char)
.to_str()
.unwrap(),
"Bj%C3%B6rn+Petersen"
"Bj%C3%B6rn%20Petersen"
);
let buf2 = dc_urldecode(buf1);

View File

@@ -1,10 +1,10 @@
use crate::context::Context;
use crate::dc_tools::*;
use crate::sql;
use crate::x::strdup;
// Token namespaces
pub type dc_tokennamespc_t = usize;
#[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;
@@ -14,51 +14,37 @@ pub fn dc_token_save(
context: &Context,
namespc: dc_tokennamespc_t,
foreign_id: u32,
token: *const libc::c_char,
token: &str,
) -> bool {
if token.is_null() {
return false;
}
// foreign_id may be 0
sql::execute(
context,
&context.sql,
"INSERT INTO tokens (namespc, foreign_id, token, timestamp) VALUES (?, ?, ?, ?);",
params![namespc as i32, foreign_id as i32, as_str(token), time()],
params![namespc as i32, foreign_id as i32, token, time()],
)
.is_ok()
}
pub fn dc_token_lookup(
context: &Context,
namespc: dc_tokennamespc_t,
foreign_id: u32,
) -> *mut libc::c_char {
if let Some(token) = context.sql.query_row_col::<_, String>(
) -> 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,
) {
unsafe { strdup(to_cstring(token).as_ptr()) }
} else {
std::ptr::null_mut()
}
)
}
pub fn dc_token_exists(
context: &Context,
namespc: dc_tokennamespc_t,
token: *const libc::c_char,
) -> bool {
if token.is_null() {
return false;
}
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, as_str(token)],
params![namespc as i32, token],
)
.unwrap_or_default()
}

File diff suppressed because it is too large Load Diff

1165
src/e2ee.rs Normal file

File diff suppressed because it is too large Load Diff

View File

@@ -14,6 +14,14 @@ pub enum Error {
SqlAlreadyOpen,
#[fail(display = "Sqlite: Failed to open")]
SqlFailedToOpen,
#[fail(display = "{:?}", _0)]
Io(std::io::Error),
#[fail(display = "{:?}", _0)]
Message(String),
#[fail(display = "{:?}", _0)]
Image(image_meta::ImageError),
#[fail(display = "{:?}", _0)]
Utf8(std::str::Utf8Error),
}
pub type Result<T> = std::result::Result<T, Error>;
@@ -35,3 +43,85 @@ impl From<r2d2::Error> for Error {
Error::ConnectionPool(err)
}
}
impl From<std::io::Error> for Error {
fn from(err: std::io::Error) -> Error {
Error::Io(err)
}
}
impl From<std::str::Utf8Error> for Error {
fn from(err: std::str::Utf8Error) -> Error {
Error::Utf8(err)
}
}
impl From<image_meta::ImageError> for Error {
fn from(err: image_meta::ImageError) -> Error {
Error::Image(err)
}
}
#[macro_export]
macro_rules! bail {
($e:expr) => {
return Err($crate::error::Error::Message($e.to_string()));
};
($fmt:expr, $($arg:tt)+) => {
return Err($crate::error::Error::Message(format!($fmt, $($arg)+)));
};
}
#[macro_export]
macro_rules! format_err {
($e:expr) => {
$crate::error::Error::Message($e.to_string());
};
($fmt:expr, $($arg:tt)+) => {
$crate::error::Error::Message(format!($fmt, $($arg)+));
};
}
#[macro_export(local_inner_macros)]
macro_rules! ensure {
($cond:expr, $e:expr) => {
if !($cond) {
bail!($e);
}
};
($cond:expr, $fmt:expr, $($arg:tt)+) => {
if !($cond) {
bail!($fmt, $($arg)+);
}
};
}
#[macro_export]
macro_rules! ensure_eq {
($left:expr, $right:expr) => ({
match (&$left, &$right) {
(left_val, right_val) => {
if !(*left_val == *right_val) {
bail!(r#"assertion failed: `(left == right)`
left: `{:?}`,
right: `{:?}`"#, left_val, right_val)
}
}
}
});
($left:expr, $right:expr,) => ({
ensure_eq!($left, $right)
});
($left:expr, $right:expr, $($arg:tt)+) => ({
match (&($left), &($right)) {
(left_val, right_val) => {
if !(*left_val == *right_val) {
bail!(r#"assertion failed: `(left == right)`
left: `{:?}`,
right: `{:?}`: {}"#, left_val, right_val,
format_args!($($arg)+))
}
}
}
});
}

View File

@@ -1,28 +1,30 @@
use std::ffi::CString;
use std::net;
use std::sync::{Arc, Condvar, Mutex, RwLock};
use std::sync::{
atomic::{AtomicBool, Ordering},
Arc, Condvar, Mutex, RwLock,
};
use std::time::{Duration, SystemTime};
use crate::constants::*;
use crate::context::Context;
use crate::dc_loginparam::*;
use crate::dc_tools::as_str;
use crate::dc_tools::CStringExt;
use crate::oauth2::dc_get_oauth2_access_token;
use crate::types::*;
pub const DC_IMAP_SEEN: usize = 0x0001;
pub const DC_REGENERATE: usize = 0x01;
const DC_IMAP_SEEN: usize = 0x0001;
const DC_REGENERATE: usize = 0x01;
pub const DC_SUCCESS: usize = 3;
pub const DC_ALREADY_DONE: usize = 2;
pub const DC_RETRY_LATER: usize = 1;
pub const DC_FAILED: usize = 0;
const DC_SUCCESS: usize = 3;
const DC_ALREADY_DONE: usize = 2;
const DC_RETRY_LATER: usize = 1;
const DC_FAILED: usize = 0;
const PREFETCH_FLAGS: &'static str = "(UID ENVELOPE)";
const BODY_FLAGS: &'static str = "(FLAGS BODY.PEEK[])";
const FETCH_FLAGS: &'static str = "(FLAGS)";
const PREFETCH_FLAGS: &str = "(UID ENVELOPE)";
const BODY_FLAGS: &str = "(FLAGS BODY.PEEK[])";
const FETCH_FLAGS: &str = "(FLAGS)";
#[repr(C)]
pub struct Imap {
config: Arc<RwLock<ImapConfig>>,
watch: Arc<(Mutex<bool>, Condvar)>,
@@ -35,6 +37,8 @@ pub struct Imap {
session: Arc<Mutex<Option<Session>>>,
stream: Arc<RwLock<Option<net::TcpStream>>>,
connected: Arc<Mutex<bool>>,
should_reconnect: AtomicBool,
}
struct OAuth2 {
@@ -55,13 +59,13 @@ impl imap::Authenticator for OAuth2 {
}
#[derive(Debug)]
pub enum FolderMeaning {
enum FolderMeaning {
Unknown,
SentObjects,
Other,
}
pub enum Client {
enum Client {
Secure(
imap::Client<native_tls::TlsStream<net::TcpStream>>,
net::TcpStream,
@@ -69,12 +73,12 @@ pub enum Client {
Insecure(imap::Client<net::TcpStream>, net::TcpStream),
}
pub enum Session {
enum Session {
Secure(imap::Session<native_tls::TlsStream<net::TcpStream>>),
Insecure(imap::Session<net::TcpStream>),
}
pub enum IdleHandle<'a> {
enum IdleHandle<'a> {
Secure(imap::extensions::idle::Handle<'a, native_tls::TlsStream<net::TcpStream>>),
Insecure(imap::extensions::idle::Handle<'a, net::TcpStream>),
}
@@ -199,8 +203,8 @@ impl Session {
pub fn create<S: AsRef<str>>(&mut self, mailbox_name: S) -> imap::error::Result<()> {
match self {
Session::Secure(i) => i.subscribe(mailbox_name),
Session::Insecure(i) => i.subscribe(mailbox_name),
Session::Secure(i) => i.create(mailbox_name),
Session::Insecure(i) => i.create(mailbox_name),
}
}
@@ -260,8 +264,8 @@ impl Session {
pub fn idle(&mut self) -> imap::error::Result<IdleHandle> {
match self {
Session::Secure(i) => i.idle().map(|h| IdleHandle::Secure(h)),
Session::Insecure(i) => i.idle().map(|h| IdleHandle::Insecure(h)),
Session::Secure(i) => i.idle().map(IdleHandle::Secure),
Session::Insecure(i) => i.idle().map(IdleHandle::Insecure),
}
}
@@ -303,7 +307,7 @@ impl Session {
}
}
pub struct ImapConfig {
struct ImapConfig {
pub addr: String,
pub imap_server: String,
pub imap_port: u16,
@@ -313,7 +317,6 @@ pub struct ImapConfig {
pub selected_folder: Option<String>,
pub selected_mailbox: Option<imap::types::Mailbox>,
pub selected_folder_needs_expunge: bool,
pub should_reconnect: bool,
pub can_idle: bool,
pub has_xlist: bool,
pub imap_delimiter: char,
@@ -332,7 +335,6 @@ impl Default for ImapConfig {
selected_folder: None,
selected_mailbox: None,
selected_folder_needs_expunge: false,
should_reconnect: false,
can_idle: false,
has_xlist: false,
imap_delimiter: '.',
@@ -360,6 +362,7 @@ impl Imap {
precheck_imf,
receive_imf,
connected: Arc::new(Mutex::new(false)),
should_reconnect: AtomicBool::new(false),
}
}
@@ -368,12 +371,12 @@ impl Imap {
}
pub fn should_reconnect(&self) -> bool {
self.config.read().unwrap().should_reconnect
self.should_reconnect.load(Ordering::Relaxed)
}
fn setup_handle_if_needed(&self, context: &Context) -> libc::c_int {
fn setup_handle_if_needed(&self, context: &Context) -> bool {
if self.config.read().unwrap().imap_server.is_empty() {
return 0;
return false;
}
if self.should_reconnect() {
@@ -381,8 +384,8 @@ impl Imap {
}
if self.is_connected() && self.stream.read().unwrap().is_some() {
self.config.write().unwrap().should_reconnect = false;
return 1;
self.should_reconnect.store(false, Ordering::Relaxed);
return true;
}
let server_flags = self.config.read().unwrap().server_flags;
@@ -426,7 +429,7 @@ impl Imap {
};
client.authenticate("XOAUTH2", &auth)
} else {
return 0;
return false;
}
} else {
client.login(imap_user, imap_pw)
@@ -447,54 +450,55 @@ impl Imap {
err
);
return 0;
return false;
}
};
self.config.write().unwrap().should_reconnect = false;
self.should_reconnect.store(false, Ordering::Relaxed);
match login_res {
Ok((session, stream)) => {
*self.session.lock().unwrap() = Some(session);
*self.stream.write().unwrap() = Some(stream);
1
true
}
Err((err, _)) => {
log_event!(context, Event::ERROR_NETWORK, 0, "Cannot login ({})", err);
self.unsetup_handle(context);
0
false
}
}
}
fn unsetup_handle(&self, context: &Context) {
info!(context, 0, "IMAP unsetup_handle starts");
let session = self.session.lock().unwrap().take();
if session.is_some() {
match session.unwrap().close() {
Ok(_) => {}
Err(err) => {
eprintln!("failed to close connection: {:?}", err);
}
}
}
info!(context, 0, "IMAP unsetup_handle2.");
info!(
context,
0, "IMAP unsetup_handle step 1 (closing down stream)."
);
let stream = self.stream.write().unwrap().take();
if stream.is_some() {
match stream.unwrap().shutdown(net::Shutdown::Both) {
Ok(_) => {}
Err(err) => {
eprintln!("failed to shutdown connection: {:?}", err);
}
if let Some(stream) = stream {
if let Err(err) = stream.shutdown(net::Shutdown::Both) {
eprintln!("failed to shutdown connection: {:?}", err);
}
}
info!(context, 0, "IMAP unsetup_handle3.");
let mut cfg = self.config.write().unwrap();
cfg.selected_folder = None;
cfg.selected_mailbox = None;
info!(context, 0, "IMAP disconnected.",);
info!(
context,
0, "IMAP unsetup_handle step 2 (acquiring session.lock)"
);
if let Some(mut session) = self.session.lock().unwrap().take() {
if let Err(err) = session.close() {
eprintln!("failed to close connection: {:?}", err);
}
}
info!(context, 0, "IMAP unsetup_handle step 3 (clearing config).");
self.config.write().unwrap().selected_folder = None;
self.config.write().unwrap().selected_mailbox = None;
info!(context, 0, "IMAP unsetup_handle step 4 (disconnected).",);
}
fn free_connect_params(&self) {
@@ -512,13 +516,13 @@ impl Imap {
cfg.watch_folder = None;
}
pub fn connect(&self, context: &Context, lp: &dc_loginparam_t) -> libc::c_int {
pub fn connect(&self, context: &Context, lp: &dc_loginparam_t) -> bool {
if lp.mail_server.is_empty() || lp.mail_user.is_empty() || lp.mail_pw.is_empty() {
return 0;
return false;
}
if self.is_connected() {
return 1;
return true;
}
{
@@ -532,54 +536,57 @@ impl Imap {
let mut config = self.config.write().unwrap();
config.addr = addr.to_string();
config.imap_server = imap_server.to_string();
config.imap_port = imap_port.into();
config.imap_port = imap_port;
config.imap_user = imap_user.to_string();
config.imap_pw = imap_pw.to_string();
config.server_flags = server_flags;
}
if self.setup_handle_if_needed(context) == 0 {
if !self.setup_handle_if_needed(context) {
self.free_connect_params();
return 0;
return false;
}
match &mut *self.session.lock().unwrap() {
let (teardown, can_idle, has_xlist) = match &mut *self.session.lock().unwrap() {
Some(ref mut session) => {
if let Ok(caps) = session.capabilities() {
let can_idle = caps.has("IDLE");
let has_xlist = caps.has("XLIST");
let caps_list = caps.iter().fold(String::new(), |mut s, c| {
s += " ";
s += c;
s
});
log_event!(
context,
Event::IMAP_CONNECTED,
0,
"IMAP-LOGIN as {} ok",
lp.mail_user,
);
info!(context, 0, "IMAP-capabilities:{}", caps_list);
let mut config = self.config.write().unwrap();
config.can_idle = can_idle;
config.has_xlist = has_xlist;
*self.connected.lock().unwrap() = true;
1
if !context.sql.is_open() {
warn!(context, 0, "IMAP-LOGIN as {} ok but ABORTING", lp.mail_user,);
(true, false, false)
} else {
let can_idle = caps.has("IDLE");
let has_xlist = caps.has("XLIST");
let caps_list = caps.iter().fold(String::new(), |mut s, c| {
s += " ";
s += c;
s
});
log_event!(
context,
Event::IMAP_CONNECTED,
0,
"IMAP-LOGIN as {}, capabilities: {}",
lp.mail_user,
caps_list,
);
(false, can_idle, has_xlist)
}
} else {
self.unsetup_handle(context);
self.free_connect_params();
0
(true, false, false)
}
}
None => {
self.unsetup_handle(context);
self.free_connect_params();
0
}
None => (true, false, false),
};
if teardown {
self.unsetup_handle(context);
self.free_connect_params();
false
} else {
self.config.write().unwrap().can_idle = can_idle;
self.config.write().unwrap().has_xlist = has_xlist;
*self.connected.lock().unwrap() = true;
true
}
}
@@ -676,9 +683,8 @@ impl Imap {
err
);
let mut config = self.config.write().unwrap();
config.selected_folder = None;
config.should_reconnect = true;
self.config.write().unwrap().selected_folder = None;
self.should_reconnect.store(true, Ordering::Relaxed);
return 0;
}
}
@@ -692,24 +698,16 @@ impl Imap {
fn get_config_last_seen_uid<S: AsRef<str>>(&self, context: &Context, folder: S) -> (u32, u32) {
let key = format!("imap.mailbox.{}", folder.as_ref());
let val1 = unsafe {
(self.get_config)(
context,
CString::new(key).unwrap().as_ptr(),
0 as *const libc::c_char,
if let Some(entry) = (self.get_config)(context, &key) {
// the entry has the format `imap.mailbox.<folder>=<uidvalidity>:<lastseenuid>`
let mut parts = entry.split(':');
(
parts.next().unwrap().parse().unwrap_or_else(|_| 0),
parts.next().unwrap().parse().unwrap_or_else(|_| 0),
)
};
if val1.is_null() {
return (0, 0);
} else {
(0, 0)
}
let entry = as_str(val1);
// the entry has the format `imap.mailbox.<folder>=<uidvalidity>:<lastseenuid>`
let mut parts = entry.split(':');
(
parts.next().unwrap().parse().unwrap_or_else(|_| 0),
parts.next().unwrap().parse().unwrap_or_else(|_| 0),
)
}
fn fetch_from_single_folder<S: AsRef<str>>(&self, context: &Context, folder: S) -> usize {
@@ -772,7 +770,7 @@ impl Imap {
match session.fetch(set, PREFETCH_FLAGS) {
Ok(list) => list,
Err(_err) => {
self.config.write().unwrap().should_reconnect = true;
self.should_reconnect.store(true, Ordering::Relaxed);
info!(
context,
0,
@@ -817,7 +815,7 @@ impl Imap {
match session.uid_fetch(set, PREFETCH_FLAGS) {
Ok(list) => list,
Err(err) => {
eprintln!("fetch err: {:?}", err);
warn!(context, 0, "failed to fetch uids: {}", err);
return 0;
}
}
@@ -837,8 +835,8 @@ impl Imap {
.message_id
.expect("missing message id");
let message_id_c = CString::new(message_id).unwrap();
if 0 == unsafe {
let message_id_c = CString::yolo(message_id);
(self.precheck_imf)(context, message_id_c.as_ptr(), folder.as_ref(), cur_uid)
} {
// check passed, go fetch the rest
@@ -907,13 +905,7 @@ impl Imap {
let key = format!("imap.mailbox.{}", folder.as_ref());
let val = format!("{}:{}", uidvalidity, lastseenuid);
unsafe {
(self.set_config)(
context,
CString::new(key).unwrap().as_ptr(),
CString::new(val).unwrap().as_ptr(),
)
};
(self.set_config)(context, &key, Some(&val));
}
fn fetch_single_msg<S: AsRef<str>>(
@@ -929,15 +921,13 @@ impl Imap {
return 0;
}
let mut retry_later = false;
let set = format!("{}", server_uid);
let msgs = if let Some(ref mut session) = &mut *self.session.lock().unwrap() {
match session.uid_fetch(set, BODY_FLAGS) {
Ok(msgs) => msgs,
Err(err) => {
self.config.write().unwrap().should_reconnect = true;
self.should_reconnect.store(true, Ordering::Relaxed);
warn!(
context,
0,
@@ -947,17 +937,11 @@ impl Imap {
self.should_reconnect(),
err
);
if self.should_reconnect() {
// maybe we should also retry on other errors, however, we should check this carefully, as this may result in a dead lock!
retry_later = true;
}
return if retry_later { 0 } else { 1 };
return 0;
}
}
} else {
return if retry_later { 0 } else { 1 };
return 1;
};
if msgs.is_empty() {
@@ -991,11 +975,12 @@ impl Imap {
let flags = if is_seen { DC_IMAP_SEEN } else { 0 };
if !is_deleted && msg.body().is_some() {
let body = msg.body().unwrap();
unsafe {
(self.receive_imf)(
context,
msg.body().unwrap().as_ptr() as *const libc::c_char,
msg.body().unwrap().len(),
body.as_ptr() as *const libc::c_char,
body.len(),
folder.as_ref(),
server_uid,
flags as u32,
@@ -1004,11 +989,7 @@ impl Imap {
}
}
if retry_later {
0
} else {
1
}
1
}
pub fn idle(&self, context: &Context) {
@@ -1030,13 +1011,15 @@ impl Imap {
let (sender, receiver) = std::sync::mpsc::channel();
let v = self.watch.clone();
info!(context, 0, "IMAP-IDLE SPAWNING");
std::thread::spawn(move || {
let &(ref lock, ref cvar) = &*v;
if let Some(ref mut session) = &mut *session.lock().unwrap() {
let mut idle = match session.idle() {
Ok(idle) => idle,
Err(err) => {
panic!("failed to setup idle: {:?}", err);
eprintln!("failed to setup idle: {:?}", err);
return;
}
};
@@ -1071,7 +1054,7 @@ impl Imap {
context,
0, "IMAP-IDLE wait cancelled, we will reconnect soon."
);
self.config.write().unwrap().should_reconnect = true;
self.should_reconnect.store(true, Ordering::Relaxed);
}
_ => {
warn!(context, 0, "IMAP-IDLE returns unknown value: {}", err);
@@ -1144,7 +1127,7 @@ impl Imap {
// check for new messages. fetch_from_single_folder() has the side-effect that messages
// are also downloaded, however, typically this would take place in the FETCH command
// following IDLE otherwise, so this seems okay here.
if self.setup_handle_if_needed(context) != 0 {
if self.setup_handle_if_needed(context) {
if let Some(ref watch_folder) = self.config.read().unwrap().watch_folder {
if 0 != self.fetch_from_single_folder(context, watch_folder) {
do_fake_idle = false;
@@ -1574,20 +1557,23 @@ impl Imap {
info!(context, 0, "MVBOX-folder created.",);
}
Err(err) => {
eprintln!("create error: {:?}", err);
warn!(
context,
0, "Cannot create MVBOX-folder, using trying INBOX subfolder."
0,
"Cannot create MVBOX-folder, using trying INBOX subfolder. ({})",
err
);
match session.create(&fallback_folder) {
Ok(_) => {
mvbox_folder = Some(fallback_folder);
info!(context, 0, "MVBOX-folder created as INBOX subfolder.",);
info!(
context,
0, "MVBOX-folder created as INBOX subfolder. ({})", err
);
}
Err(err) => {
eprintln!("create error: {:?}", err);
warn!(context, 0, "Cannot create MVBOX-folder.",);
warn!(context, 0, "Cannot create MVBOX-folder. ({})", err);
}
}
}
@@ -1602,18 +1588,25 @@ impl Imap {
}
}
context.sql.set_config_int(context, "folders_configured", 3);
context
.sql
.set_config_int(context, "folders_configured", 3)
.ok();
if let Some(ref mvbox_folder) = mvbox_folder {
context
.sql
.set_config(context, "configured_mvbox_folder", Some(mvbox_folder));
.set_config(context, "configured_mvbox_folder", Some(mvbox_folder))
.ok();
}
if let Some(ref sentbox_folder) = sentbox_folder {
context.sql.set_config(
context,
"configured_sentbox_folder",
Some(sentbox_folder.name()),
);
context
.sql
.set_config(
context,
"configured_sentbox_folder",
Some(sentbox_folder.name()),
)
.ok();
}
}

1131
src/job.rs Normal file

File diff suppressed because it is too large Load Diff

181
src/job_thread.rs Normal file
View File

@@ -0,0 +1,181 @@
use std::sync::{Arc, Condvar, Mutex};
use crate::configure::*;
use crate::context::Context;
use crate::imap::Imap;
pub struct JobThread {
pub name: &'static str,
pub folder_config_name: &'static str,
pub imap: Imap,
pub state: Arc<(Mutex<JobState>, Condvar)>,
}
#[derive(Clone, Debug, Default)]
pub struct JobState {
idle: bool,
jobs_needed: i32,
suspended: bool,
using_handle: bool,
}
impl JobThread {
pub fn new(name: &'static str, folder_config_name: &'static str, imap: Imap) -> Self {
JobThread {
name,
folder_config_name,
imap,
state: Arc::new((Mutex::new(Default::default()), Condvar::new())),
}
}
pub fn suspend(&self, context: &Context) {
info!(context, 0, "Suspending {}-thread.", self.name,);
{
self.state.0.lock().unwrap().suspended = true;
}
self.interrupt_idle(context);
loop {
let using_handle = self.state.0.lock().unwrap().using_handle;
if !using_handle {
return;
}
std::thread::sleep(std::time::Duration::from_micros(300 * 1000));
}
}
pub fn unsuspend(&self, context: &Context) {
info!(context, 0, "Unsuspending {}-thread.", self.name);
let &(ref lock, ref cvar) = &*self.state.clone();
let mut state = lock.lock().unwrap();
state.suspended = false;
state.idle = true;
cvar.notify_one();
}
pub fn interrupt_idle(&self, context: &Context) {
{
self.state.0.lock().unwrap().jobs_needed = 1;
}
info!(context, 0, "Interrupting {}-IDLE...", self.name);
self.imap.interrupt_idle();
let &(ref lock, ref cvar) = &*self.state.clone();
let mut state = lock.lock().unwrap();
state.idle = true;
cvar.notify_one();
}
pub fn fetch(&mut self, context: &Context, use_network: bool) {
{
let &(ref lock, _) = &*self.state.clone();
let mut state = lock.lock().unwrap();
if state.suspended {
return;
}
state.using_handle = true;
}
if use_network {
let start = std::time::Instant::now();
if self.connect_to_imap(context) {
info!(context, 0, "{}-fetch started...", self.name);
self.imap.fetch(context);
if self.imap.should_reconnect() {
info!(context, 0, "{}-fetch aborted, starting over...", self.name,);
self.imap.fetch(context);
}
info!(
context,
0,
"{}-fetch done in {:.3} ms.",
self.name,
start.elapsed().as_millis(),
);
}
}
self.state.0.lock().unwrap().using_handle = false;
}
fn connect_to_imap(&self, context: &Context) -> bool {
if self.imap.is_connected() {
return true;
}
let mut ret_connected = dc_connect_to_configured_imap(context, &self.imap) != 0;
if ret_connected {
if context
.sql
.get_config_int(context, "folders_configured")
.unwrap_or_default()
< 3
{
self.imap.configure_folders(context, 0x1);
}
if let Some(mvbox_name) = context.sql.get_config(context, self.folder_config_name) {
self.imap.set_watch_folder(mvbox_name);
} else {
self.imap.disconnect(context);
ret_connected = false;
}
}
ret_connected
}
pub fn idle(&self, context: &Context, use_network: bool) {
{
let &(ref lock, ref cvar) = &*self.state.clone();
let mut state = lock.lock().unwrap();
if 0 != state.jobs_needed {
info!(
context,
0,
"{}-IDLE will not be started as it was interrupted while not ideling.",
self.name,
);
state.jobs_needed = 0;
return;
}
if state.suspended {
while !state.idle {
state = cvar.wait(state).unwrap();
}
state.idle = false;
return;
}
state.using_handle = true;
if !use_network {
state.using_handle = false;
while !state.idle {
state = cvar.wait(state).unwrap();
}
state.idle = false;
return;
}
}
self.connect_to_imap(context);
info!(context, 0, "{}-IDLE started...", self.name,);
self.imap.idle(context);
info!(context, 0, "{}-IDLE ended.", self.name);
self.state.0.lock().unwrap().using_handle = false;
}
}

View File

@@ -89,6 +89,9 @@ impl Key {
}
pub fn from_slice(bytes: &[u8], key_type: KeyType) -> Option<Self> {
if bytes.is_empty() {
return None;
}
let res: Result<Key, _> = match key_type {
KeyType::Public => SignedPublicKey::from_bytes(Cursor::new(bytes)).map(Into::into),
KeyType::Private => SignedSecretKey::from_bytes(Cursor::new(bytes)).map(Into::into),
@@ -213,22 +216,16 @@ impl Key {
}
}
/// Each header line must be terminated by `\r\n`, the result must be freed.
pub fn to_asc_c(&self, header: Option<(&str, &str)>) -> *mut libc::c_char {
/// Each header line must be terminated by `\r\n`
pub fn to_asc(&self, header: Option<(&str, &str)>) -> String {
let headers = header.map(|(key, value)| {
let mut m = BTreeMap::new();
m.insert(key.to_string(), value.to_string());
m
});
let buf = self
.to_armored_string(headers.as_ref())
.expect("failed to serialize key");
let buf_c = CString::new(buf).unwrap();
// need to use strdup to allocate the result with malloc
// so it can be `free`d later.
unsafe { strdup(buf_c.as_ptr()) }
self.to_armored_string(headers.as_ref())
.expect("failed to serialize key")
}
pub fn write_asc_to_file(&self, file: *const libc::c_char, context: &Context) -> bool {
@@ -236,15 +233,16 @@ impl Key {
return false;
}
let file_content = self.to_asc_c(None);
let file_content = self.to_asc(None);
let file_content_c = CString::new(file_content).unwrap();
let success = if 0
== unsafe {
dc_write_file(
context,
file,
file_content as *const libc::c_void,
strlen(file_content),
file_content_c.as_ptr() as *const libc::c_void,
file_content_c.as_bytes().len(),
)
} {
error!(context, 0, "Cannot write key to {}", to_string(file));
@@ -253,8 +251,6 @@ impl Key {
true
};
unsafe { free(file_content as *mut libc::c_void) };
success
}
@@ -287,7 +283,7 @@ impl Key {
Key::Public(_) => None,
Key::Secret(k) => {
let pub_key = k.public_key();
pub_key.sign(k, || "".into()).map(|k| Key::Public(k)).ok()
pub_key.sign(k, || "".into()).map(Key::Public).ok()
}
}
}
@@ -306,7 +302,7 @@ pub fn dc_key_save_self_keypair(
sql,
"INSERT INTO keypairs (addr, is_default, public_key, private_key, created) VALUES (?,?,?,?,?);",
params![addr.as_ref(), is_default, public_key.to_bytes(), private_key.to_bytes(), time()],
)
).is_ok()
}
/// Make a fingerprint human-readable, in hex format.

View File

@@ -1,13 +0,0 @@
use crate::context::Context;
/* yes: uppercase */
/* library private: key-history */
pub fn dc_add_to_keyhistory(
_context: &Context,
_rfc724_mid: *const libc::c_char,
_sending_time: u64,
_addr: *const libc::c_char,
_fingerprint: *const libc::c_char,
) {
}

View File

@@ -1,12 +1,9 @@
#![allow(
non_camel_case_types,
non_snake_case,
non_upper_case_globals,
non_upper_case_globals,
non_camel_case_types,
non_snake_case
)]
#![feature(c_variadic, ptr_wrapping_offset_from)]
#![deny(clippy::correctness)]
// TODO: make all of these errors, such that clippy actually passes.
#![warn(clippy::all, clippy::perf, clippy::not_unsafe_ptr_arg_deref)]
// This is nice, but for now just annoying.
#![allow(clippy::unreadable_literal)]
#![feature(ptr_wrapping_offset_from)]
#[macro_use]
extern crate failure_derive;
@@ -16,53 +13,55 @@ extern crate num_derive;
extern crate smallvec;
#[macro_use]
extern crate rusqlite;
extern crate strum;
#[macro_use]
extern crate strum_macros;
#[macro_use]
mod log;
pub mod aheader;
pub mod config;
pub mod constants;
pub mod context;
#[macro_use]
pub mod error;
pub mod imap;
mod aheader;
pub mod chat;
pub mod chatlist;
pub mod config;
pub mod configure;
pub mod constants;
pub mod contact;
pub mod context;
mod e2ee;
mod imap;
pub mod job;
mod job_thread;
pub mod key;
pub mod keyhistory;
pub mod keyring;
pub mod location;
pub mod lot;
pub mod message;
pub mod oauth2;
mod param;
pub mod peerstate;
pub mod pgp;
pub mod smtp;
pub mod qr;
mod smtp;
pub mod sql;
mod stock;
pub mod types;
pub mod x;
pub mod dc_array;
pub mod dc_chat;
pub mod dc_chatlist;
pub mod dc_configure;
pub mod dc_contact;
pub mod dc_dehtml;
pub mod dc_e2ee;
mod dc_dehtml;
pub mod dc_imex;
pub mod dc_job;
pub mod dc_jobthread;
pub mod dc_location;
pub mod dc_loginparam;
pub mod dc_lot;
pub mod dc_mimefactory;
mod dc_loginparam;
mod dc_mimefactory;
pub mod dc_mimeparser;
pub mod dc_move;
pub mod dc_msg;
pub mod dc_param;
pub mod dc_qr;
pub mod dc_receive_imf;
pub mod dc_saxparser;
pub mod dc_securejoin;
pub mod dc_simplify;
pub mod dc_stock;
pub mod dc_strencode;
pub mod dc_token;
mod dc_simplify;
mod dc_strencode;
mod dc_token;
pub mod dc_tools;
pub use self::constants::*;
#[cfg(test)]
mod test_utils;

696
src/location.rs Normal file
View File

@@ -0,0 +1,696 @@
use bitflags::bitflags;
use quick_xml;
use quick_xml::events::{BytesEnd, BytesStart, BytesText};
use crate::chat;
use crate::constants::Event;
use crate::constants::*;
use crate::context::*;
use crate::dc_tools::*;
use crate::error::Error;
use crate::job::*;
use crate::message::*;
use crate::param::*;
use crate::sql;
use crate::stock::StockMessage;
use crate::types::*;
// location handling
#[derive(Debug, Clone, Default)]
pub struct Location {
pub location_id: u32,
pub latitude: f64,
pub longitude: f64,
pub accuracy: f64,
pub timestamp: i64,
pub contact_id: u32,
pub msg_id: u32,
pub chat_id: u32,
pub marker: Option<String>,
pub independent: u32,
}
impl Location {
pub fn new() -> Self {
Default::default()
}
}
#[derive(Debug, Clone, Default)]
pub struct Kml {
pub addr: Option<String>,
pub locations: Vec<Location>,
tag: KmlTag,
pub curr: Location,
}
bitflags! {
#[derive(Default)]
struct KmlTag: i32 {
const UNDEFINED = 0x00;
const PLACEMARK = 0x01;
const TIMESTAMP = 0x02;
const WHEN = 0x04;
const POINT = 0x08;
const COORDINATES = 0x10;
}
}
impl Kml {
pub fn new() -> Self {
Default::default()
}
pub fn parse(context: &Context, content: impl AsRef<str>) -> Result<Self, Error> {
ensure!(
content.as_ref().len() <= (1 * 1024 * 1024),
"A kml-files with {} bytes is larger than reasonably expected.",
content.as_ref().len()
);
let mut reader = quick_xml::Reader::from_str(content.as_ref());
reader.trim_text(true);
let mut kml = Kml::new();
kml.locations = Vec::with_capacity(100);
let mut buf = Vec::new();
loop {
match reader.read_event(&mut buf) {
Ok(quick_xml::events::Event::Start(ref e)) => kml.starttag_cb(e, &reader),
Ok(quick_xml::events::Event::End(ref e)) => kml.endtag_cb(e),
Ok(quick_xml::events::Event::Text(ref e)) => kml.text_cb(e, &reader),
Err(e) => {
error!(
context,
0,
"Location parsing: Error at position {}: {:?}",
reader.buffer_position(),
e
);
}
Ok(quick_xml::events::Event::Eof) => break,
_ => (),
}
buf.clear();
}
Ok(kml)
}
fn text_cb<B: std::io::BufRead>(&mut self, event: &BytesText, reader: &quick_xml::Reader<B>) {
if self.tag.contains(KmlTag::WHEN) || self.tag.contains(KmlTag::COORDINATES) {
let val = event.unescape_and_decode(reader).unwrap_or_default();
let val = val
.replace("\n", "")
.replace("\r", "")
.replace("\t", "")
.replace(" ", "");
if self.tag.contains(KmlTag::WHEN) && val.len() >= 19 {
// YYYY-MM-DDTHH:MM:SSZ
// 0 4 7 10 13 16 19
match chrono::NaiveDateTime::parse_from_str(&val, "%Y-%m-%dT%H:%M:%SZ") {
Ok(res) => {
self.curr.timestamp = res.timestamp();
if self.curr.timestamp > time() {
self.curr.timestamp = time();
}
}
Err(_err) => {
self.curr.timestamp = time();
}
}
} else if self.tag.contains(KmlTag::COORDINATES) {
let parts = val.splitn(2, ',').collect::<Vec<_>>();
if parts.len() == 2 {
self.curr.longitude = parts[0].parse().unwrap_or_default();
self.curr.latitude = parts[1].parse().unwrap_or_default();
}
}
}
}
fn endtag_cb(&mut self, event: &BytesEnd) {
let tag = String::from_utf8_lossy(event.name()).trim().to_lowercase();
if tag == "placemark" {
if self.tag.contains(KmlTag::PLACEMARK)
&& 0 != self.curr.timestamp
&& 0. != self.curr.latitude
&& 0. != self.curr.longitude
{
self.locations
.push(std::mem::replace(&mut self.curr, Location::new()));
}
self.tag = KmlTag::UNDEFINED;
};
}
fn starttag_cb<B: std::io::BufRead>(
&mut self,
event: &BytesStart,
reader: &quick_xml::Reader<B>,
) {
let tag = String::from_utf8_lossy(event.name()).trim().to_lowercase();
if tag == "document" {
if let Some(addr) = event.attributes().find(|attr| {
attr.as_ref()
.map(|a| String::from_utf8_lossy(a.key).trim().to_lowercase() == "addr")
.unwrap_or_default()
}) {
self.addr = addr.unwrap().unescape_and_decode_value(reader).ok();
}
} else if tag == "placemark" {
self.tag = KmlTag::PLACEMARK;
self.curr.timestamp = 0;
self.curr.latitude = 0.0;
self.curr.longitude = 0.0;
self.curr.accuracy = 0.0
} else if tag == "timestamp" && self.tag.contains(KmlTag::PLACEMARK) {
self.tag = KmlTag::PLACEMARK | KmlTag::TIMESTAMP
} else if tag == "when" && self.tag.contains(KmlTag::TIMESTAMP) {
self.tag = KmlTag::PLACEMARK | KmlTag::TIMESTAMP | KmlTag::WHEN
} else if tag == "point" && self.tag.contains(KmlTag::PLACEMARK) {
self.tag = KmlTag::PLACEMARK | KmlTag::POINT
} else if tag == "coordinates" && self.tag.contains(KmlTag::POINT) {
self.tag = KmlTag::PLACEMARK | KmlTag::POINT | KmlTag::COORDINATES;
if let Some(acc) = event.attributes().find(|attr| {
attr.as_ref()
.map(|a| String::from_utf8_lossy(a.key).trim().to_lowercase() == "accuracy")
.unwrap_or_default()
}) {
let v = acc
.unwrap()
.unescape_and_decode_value(reader)
.unwrap_or_default();
self.curr.accuracy = v.trim().parse().unwrap_or_default();
}
}
}
}
// location streaming
pub fn send_locations_to_chat(context: &Context, chat_id: u32, seconds: i64) {
let now = time();
let mut msg: Message;
let is_sending_locations_before: bool;
if !(seconds < 0 || chat_id <= 9i32 as libc::c_uint) {
is_sending_locations_before = is_sending_locations_to_chat(context, chat_id);
if sql::execute(
context,
&context.sql,
"UPDATE chats \
SET locations_send_begin=?, \
locations_send_until=? \
WHERE id=?",
params![
if 0 != seconds { now } else { 0 },
if 0 != seconds { now + seconds } else { 0 },
chat_id as i32,
],
)
.is_ok()
{
if 0 != seconds && !is_sending_locations_before {
msg = dc_msg_new(context, Viewtype::Text);
msg.text =
Some(context.stock_system_msg(StockMessage::MsgLocationEnabled, "", "", 0));
msg.param.set_int(Param::Cmd, 8);
unsafe { chat::send_msg(context, chat_id, &mut msg).unwrap() };
} else if 0 == seconds && is_sending_locations_before {
let stock_str =
context.stock_system_msg(StockMessage::MsgLocationDisabled, "", "", 0);
chat::add_device_msg(context, chat_id, stock_str);
}
context.call_cb(
Event::CHAT_MODIFIED,
chat_id as uintptr_t,
0i32 as uintptr_t,
);
if 0 != seconds {
schedule_MAYBE_SEND_LOCATIONS(context, 0i32);
job_add(
context,
Action::MaybeSendLocationsEnded,
chat_id as libc::c_int,
Params::new(),
seconds + 1,
);
}
}
}
}
#[allow(non_snake_case)]
fn schedule_MAYBE_SEND_LOCATIONS(context: &Context, flags: i32) {
if 0 != flags & 0x1 || !job_action_exists(context, Action::MaybeSendLocations) {
job_add(context, Action::MaybeSendLocations, 0, Params::new(), 60);
};
}
pub fn is_sending_locations_to_chat(context: &Context, chat_id: u32) -> bool {
context
.sql
.exists(
"SELECT id FROM chats WHERE (? OR id=?) AND locations_send_until>?;",
params![if chat_id == 0 { 1 } else { 0 }, chat_id as i32, time()],
)
.unwrap_or_default()
}
pub fn set(context: &Context, latitude: f64, longitude: f64, accuracy: f64) -> libc::c_int {
if latitude == 0.0 && longitude == 0.0 {
return 1;
}
context.sql.query_map(
"SELECT id FROM chats WHERE locations_send_until>?;",
params![time()], |row| row.get::<_, i32>(0),
|chats| {
let mut continue_streaming = false;
for chat in chats {
let chat_id = chat?;
context.sql.execute(
"INSERT INTO locations \
(latitude, longitude, accuracy, timestamp, chat_id, from_id) VALUES (?,?,?,?,?,?);",
params![
latitude,
longitude,
accuracy,
time(),
chat_id,
1,
]
)?;
continue_streaming = true;
}
if continue_streaming {
context.call_cb(Event::LOCATION_CHANGED, 1, 0);
};
schedule_MAYBE_SEND_LOCATIONS(context, 0);
Ok(continue_streaming as libc::c_int)
}
).unwrap_or_default()
}
pub fn get_range(
context: &Context,
chat_id: u32,
contact_id: u32,
timestamp_from: i64,
mut timestamp_to: i64,
) -> Vec<Location> {
if timestamp_to == 0 {
timestamp_to = time() + 10;
}
context
.sql
.query_map(
"SELECT l.id, l.latitude, l.longitude, l.accuracy, l.timestamp, l.independent, \
m.id, l.from_id, l.chat_id, m.txt \
FROM locations l LEFT JOIN msgs m ON l.id=m.location_id WHERE (? OR l.chat_id=?) \
AND (? OR l.from_id=?) \
AND (l.independent=1 OR (l.timestamp>=? AND l.timestamp<=?)) \
ORDER BY l.timestamp DESC, l.id DESC, m.id DESC;",
params![
if chat_id == 0 { 1 } else { 0 },
chat_id as i32,
if contact_id == 0 { 1 } else { 0 },
contact_id as i32,
timestamp_from,
timestamp_to,
],
|row| {
let msg_id = row.get(6)?;
let txt: String = row.get(9)?;
let marker = if msg_id != 0 && is_marker(&txt) {
Some(txt)
} else {
None
};
let loc = Location {
location_id: row.get(0)?,
latitude: row.get(1)?,
longitude: row.get(2)?,
accuracy: row.get(3)?,
timestamp: row.get(4)?,
independent: row.get(5)?,
msg_id,
contact_id: row.get(7)?,
chat_id: row.get(8)?,
marker,
};
Ok(loc)
},
|locations| {
let mut ret = Vec::new();
for location in locations {
ret.push(location?);
}
Ok(ret)
},
)
.unwrap_or_default()
}
fn is_marker(txt: &str) -> bool {
txt.len() == 1 && txt.chars().next().unwrap() != ' '
}
pub fn delete_all(context: &Context) -> Result<(), Error> {
sql::execute(context, &context.sql, "DELETE FROM locations;", params![])?;
context.call_cb(Event::LOCATION_CHANGED, 0, 0);
Ok(())
}
pub fn get_kml(context: &Context, chat_id: u32) -> Result<(String, u32), Error> {
let now = time();
let mut location_count = 0;
let mut ret = String::new();
let mut last_added_location_id = 0;
let self_addr = context
.sql
.get_config(context, "configured_addr")
.unwrap_or_default();
let (locations_send_begin, locations_send_until, locations_last_sent) = context.sql.query_row(
"SELECT locations_send_begin, locations_send_until, locations_last_sent FROM chats WHERE id=?;",
params![chat_id as i32], |row| {
let send_begin: i64 = row.get(0)?;
let send_until: i64 = row.get(1)?;
let last_sent: i64 = row.get(2)?;
Ok((send_begin, send_until, last_sent))
})?;
if !(locations_send_begin == 0 || now > locations_send_until) {
ret += &format!(
"<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<kml xmlns=\"http://www.opengis.net/kml/2.2\">\n<Document addr=\"{}\">\n",
self_addr,
);
context.sql.query_map(
"SELECT id, latitude, longitude, accuracy, timestamp\
FROM locations WHERE from_id=? \
AND timestamp>=? \
AND (timestamp>=? OR timestamp=(SELECT MAX(timestamp) FROM locations WHERE from_id=?)) \
AND independent=0 \
GROUP BY timestamp \
ORDER BY timestamp;",
params![1, locations_send_begin, locations_last_sent, 1],
|row| {
let location_id: i32 = row.get(0)?;
let latitude: f64 = row.get(1)?;
let longitude: f64 = row.get(2)?;
let accuracy: f64 = row.get(3)?;
let timestamp = get_kml_timestamp(row.get(4)?);
Ok((location_id, latitude, longitude, accuracy, timestamp))
},
|rows| {
for row in rows {
let (location_id, latitude, longitude, accuracy, timestamp) = row?;
ret += &format!(
"<Placemark><Timestamp><when>{}</when></Timestamp><Point><coordinates accuracy=\"{}\">{},{}</coordinates></Point></Placemark>\n\x00",
timestamp,
accuracy,
longitude,
latitude
);
location_count += 1;
last_added_location_id = location_id as u32;
}
Ok(())
}
)?;
}
ensure!(location_count > 0, "No locations processed");
ret += "</Document>\n</kml>";
Ok((ret, last_added_location_id))
}
fn get_kml_timestamp(utc: i64) -> String {
// Returns a string formatted as YYYY-MM-DDTHH:MM:SSZ. The trailing `Z` indicates UTC.
chrono::NaiveDateTime::from_timestamp(utc, 0)
.format("%Y-%m-%dT%H:%M:%SZ")
.to_string()
}
pub fn get_message_kml(timestamp: i64, latitude: f64, longitude: f64) -> String {
format!(
"<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n\
<kml xmlns=\"http://www.opengis.net/kml/2.2\">\n\
<Document>\n\
<Placemark>\
<Timestamp><when>{}</when></Timestamp>\
<Point><coordinates>{:.2},{:.2}</coordinates></Point>\
</Placemark>\n\
</Document>\n\
</kml>",
get_kml_timestamp(timestamp),
longitude,
latitude,
)
}
pub fn set_kml_sent_timestamp(
context: &Context,
chat_id: u32,
timestamp: i64,
) -> Result<(), Error> {
sql::execute(
context,
&context.sql,
"UPDATE chats SET locations_last_sent=? WHERE id=?;",
params![timestamp, chat_id as i32],
)?;
Ok(())
}
pub fn set_msg_location_id(context: &Context, msg_id: u32, location_id: u32) -> Result<(), Error> {
sql::execute(
context,
&context.sql,
"UPDATE msgs SET location_id=? WHERE id=?;",
params![location_id, msg_id as i32],
)?;
Ok(())
}
pub fn save(
context: &Context,
chat_id: u32,
contact_id: u32,
locations: &[Location],
independent: i32,
) -> Result<u32, Error> {
ensure!(chat_id > 9, "Invalid chat id");
context.sql.prepare2(
"SELECT id FROM locations WHERE timestamp=? AND from_id=?",
"INSERT INTO locations\
(timestamp, from_id, chat_id, latitude, longitude, accuracy, independent) \
VALUES (?,?,?,?,?,?,?);",
|mut stmt_test, mut stmt_insert, conn| {
let mut newest_timestamp = 0;
let mut newest_location_id = 0;
for location in locations {
let exists = stmt_test.exists(params![location.timestamp, contact_id as i32])?;
if 0 != independent || !exists {
stmt_insert.execute(params![
location.timestamp,
contact_id as i32,
chat_id as i32,
location.latitude,
location.longitude,
location.accuracy,
independent,
])?;
if location.timestamp > newest_timestamp {
newest_timestamp = location.timestamp;
newest_location_id = sql::get_rowid2_with_conn(
context,
conn,
"locations",
"timestamp",
location.timestamp,
"from_id",
contact_id as i32,
);
}
}
}
Ok(newest_location_id)
},
)
}
#[allow(non_snake_case)]
pub fn job_do_DC_JOB_MAYBE_SEND_LOCATIONS(context: &Context, _job: &Job) {
let now = time();
let mut continue_streaming: libc::c_int = 1;
info!(
context,
0, " ----------------- MAYBE_SEND_LOCATIONS -------------- ",
);
context
.sql
.query_map(
"SELECT id, locations_send_begin, locations_last_sent \
FROM chats \
WHERE locations_send_until>?;",
params![now],
|row| {
let chat_id: i32 = row.get(0)?;
let locations_send_begin: i64 = row.get(1)?;
let locations_last_sent: i64 = row.get(2)?;
continue_streaming = 1;
// be a bit tolerant as the timer may not align exactly with time(NULL)
if now - locations_last_sent < (60 - 3) {
Ok(None)
} else {
Ok(Some((chat_id, locations_send_begin, locations_last_sent)))
}
},
|rows| {
context.sql.prepare(
"SELECT id \
FROM locations \
WHERE from_id=? \
AND timestamp>=? \
AND timestamp>? \
AND independent=0 \
ORDER BY timestamp;",
|mut stmt_locations, _| {
for (chat_id, locations_send_begin, locations_last_sent) in
rows.filter_map(|r| match r {
Ok(Some(v)) => Some(v),
_ => None,
})
{
// TODO: do I need to reset?
if !stmt_locations
.exists(params![1, locations_send_begin, locations_last_sent,])
.unwrap_or_default()
{
// if there is no new location, there's nothing to send.
// however, maybe we want to bypass this test eg. 15 minutes
continue;
}
// pending locations are attached automatically to every message,
// so also to this empty text message.
// DC_CMD_LOCATION is only needed to create a nicer subject.
//
// for optimisation and to avoid flooding the sending queue,
// we could sending these messages only if we're really online.
// the easiest way to determine this, is to check for an empty message queue.
// (might not be 100%, however, as positions are sent combined later
// and dc_set_location() is typically called periodically, this is ok)
let mut msg = dc_msg_new(context, Viewtype::Text);
msg.hidden = true;
msg.param.set_int(Param::Cmd, 9);
// TODO: handle cleanup on error
unsafe { chat::send_msg(context, chat_id as u32, &mut msg).unwrap() };
}
Ok(())
},
)
},
)
.unwrap(); // TODO: Better error handling
if 0 != continue_streaming {
schedule_MAYBE_SEND_LOCATIONS(context, 0x1);
}
}
#[allow(non_snake_case)]
pub fn job_do_DC_JOB_MAYBE_SEND_LOC_ENDED(context: &Context, job: &mut Job) {
// this function is called when location-streaming _might_ have ended for a chat.
// the function checks, if location-streaming is really ended;
// if so, a device-message is added if not yet done.
let chat_id = job.foreign_id;
if let Ok((send_begin, send_until)) = context.sql.query_row(
"SELECT locations_send_begin, locations_send_until FROM chats WHERE id=?",
params![chat_id as i32],
|row| Ok((row.get::<_, i64>(0)?, row.get::<_, i64>(1)?)),
) {
if !(send_begin != 0 && time() <= send_until) {
// still streaming -
// may happen as several calls to dc_send_locations_to_chat()
// do not un-schedule pending DC_MAYBE_SEND_LOC_ENDED jobs
if !(send_begin == 0 && send_until == 0) {
// not streaming, device-message already sent
if context.sql.execute(
"UPDATE chats SET locations_send_begin=0, locations_send_until=0 WHERE id=?",
params![chat_id as i32],
).is_ok() {
let stock_str = context.stock_system_msg(StockMessage::MsgLocationDisabled, "", "", 0);
chat::add_device_msg(context, chat_id, stock_str);
context.call_cb(
Event::CHAT_MODIFIED,
chat_id as usize,
0,
);
}
}
}
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::test_utils::dummy_context;
#[test]
fn test_kml_parse() {
let context = dummy_context();
let xml =
"<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<kml xmlns=\"http://www.opengis.net/kml/2.2\">\n<Document addr=\"user@example.org\">\n<Placemark><Timestamp><when>2019-03-06T21:09:57Z</when></Timestamp><Point><coordinates accuracy=\"32.000000\">9.423110,53.790302</coordinates></Point></Placemark>\n<PlaceMARK>\n<Timestamp><WHEN > \n\t2018-12-13T22:11:12Z\t</WHEN></Timestamp><Point><coordinates aCCuracy=\"2.500000\"> 19.423110 \t , \n 63.790302\n </coordinates></Point></PlaceMARK>\n</Document>\n</kml>";
let kml = Kml::parse(&context.ctx, &xml).expect("parsing failed");
assert!(kml.addr.is_some());
assert_eq!(kml.addr.as_ref().unwrap(), "user@example.org",);
let locations_ref = &kml.locations;
assert_eq!(locations_ref.len(), 2);
assert!(locations_ref[0].latitude > 53.6f64);
assert!(locations_ref[0].latitude < 53.8f64);
assert!(locations_ref[0].longitude > 9.3f64);
assert!(locations_ref[0].longitude < 9.5f64);
assert!(locations_ref[0].accuracy > 31.9f64);
assert!(locations_ref[0].accuracy < 32.1f64);
assert_eq!(locations_ref[0].timestamp, 1551906597);
assert!(locations_ref[1].latitude > 63.6f64);
assert!(locations_ref[1].latitude < 63.8f64);
assert!(locations_ref[1].longitude > 19.3f64);
assert!(locations_ref[1].longitude < 19.5f64);
assert!(locations_ref[1].accuracy > 2.4f64);
assert!(locations_ref[1].accuracy < 2.6f64);
assert_eq!(locations_ref[1].timestamp, 1544739072);
}
}

View File

@@ -3,11 +3,13 @@ macro_rules! info {
($ctx:expr, $data1:expr, $msg:expr) => {
info!($ctx, $data1, $msg,)
};
($ctx:expr, $data1:expr, $msg:expr, $($args:expr),* $(,)?) => {{
let formatted = format!($msg, $($args),*);
let formatted_c = $crate::dc_tools::to_cstring(formatted);
$ctx.call_cb($crate::constants::Event::INFO, $data1 as libc::uintptr_t,
formatted_c.as_ptr() as libc::uintptr_t)
($ctx:expr, $data1:expr, $msg:expr, $($args:expr),* $(,)?) => {
#[allow(unused_unsafe)]
unsafe {
let formatted = format!($msg, $($args),*);
let formatted_c = std::ffi::CString::new(formatted).unwrap();
$ctx.call_cb($crate::constants::Event::INFO, $data1 as libc::uintptr_t,
formatted_c.as_ptr() as libc::uintptr_t);
}};
}
@@ -17,11 +19,13 @@ macro_rules! warn {
warn!($ctx, $data1, $msg,)
};
($ctx:expr, $data1:expr, $msg:expr, $($args:expr),* $(,)?) => {
let formatted = format!($msg, $($args),*);
let formatted_c = $crate::dc_tools::to_cstring(formatted);
$ctx.call_cb($crate::constants::Event::WARNING, $data1 as libc::uintptr_t,
formatted_c.as_ptr() as libc::uintptr_t)
};
#[allow(unused_unsafe)]
unsafe {
let formatted = format!($msg, $($args),*);
let formatted_c = std::ffi::CString::new(formatted).unwrap();
$ctx.call_cb($crate::constants::Event::WARNING, $data1 as libc::uintptr_t,
formatted_c.as_ptr() as libc::uintptr_t);
}};
}
#[macro_export]
@@ -30,11 +34,13 @@ macro_rules! error {
error!($ctx, $data1, $msg,)
};
($ctx:expr, $data1:expr, $msg:expr, $($args:expr),* $(,)?) => {
#[allow(unused_unsafe)]
unsafe {
let formatted = format!($msg, $($args),*);
let formatted_c = $crate::dc_tools::to_cstring(formatted);
let formatted_c = std::ffi::CString::new(formatted).unwrap();
$ctx.call_cb($crate::constants::Event::ERROR, $data1 as libc::uintptr_t,
formatted_c.as_ptr() as libc::uintptr_t)
};
formatted_c.as_ptr() as libc::uintptr_t);
}};
}
#[macro_export]
@@ -43,9 +49,11 @@ macro_rules! log_event {
log_event!($ctx, $data1, $msg,)
};
($ctx:expr, $event:expr, $data1:expr, $msg:expr, $($args:expr),* $(,)?) => {
let formatted = format!($msg, $($args),*);
let formatted_c = $crate::dc_tools::to_cstring(formatted);
$ctx.call_cb($event, $data1 as libc::uintptr_t,
formatted_c.as_ptr() as libc::uintptr_t)
};
#[allow(unused_unsafe)]
unsafe {
let formatted = format!($msg, $($args),*);
let formatted_c = std::ffi::CString::new(formatted).unwrap();
$ctx.call_cb($event, $data1 as libc::uintptr_t,
formatted_c.as_ptr() as libc::uintptr_t);
}};
}

109
src/lot.rs Normal file
View File

@@ -0,0 +1,109 @@
use deltachat_derive::{FromSql, ToSql};
/// An object containing a set of values.
/// The meaning of the values is defined by the function returning the object.
/// Lot objects are created
/// eg. by chatlist.get_summary() or dc_msg_get_summary().
///
/// _Lot_ is used in the meaning _heap_ here.
#[derive(Default, Debug, Clone)]
pub struct Lot {
pub(crate) text1_meaning: Meaning,
pub(crate) text1: Option<String>,
pub(crate) text2: Option<String>,
pub(crate) timestamp: i64,
pub(crate) state: LotState,
pub(crate) id: u32,
pub(crate) fingerprint: Option<String>,
pub(crate) invitenumber: Option<String>,
pub(crate) auth: Option<String>,
}
#[repr(u8)]
#[derive(Debug, Display, Clone, Copy, PartialEq, Eq, FromPrimitive, ToPrimitive, ToSql, FromSql)]
pub enum Meaning {
None = 0,
Text1Draft = 1,
Text1Username = 2,
Text1Self = 3,
}
impl Default for Meaning {
fn default() -> Self {
Meaning::None
}
}
impl Lot {
pub fn new() -> Self {
Default::default()
}
pub fn get_text1(&self) -> Option<&str> {
self.text1.as_ref().map(|s| s.as_str())
}
pub fn get_text2(&self) -> Option<&str> {
self.text2.as_ref().map(|s| s.as_str())
}
pub fn get_text1_meaning(&self) -> Meaning {
self.text1_meaning
}
pub fn get_state(&self) -> LotState {
self.state
}
pub fn get_id(&self) -> u32 {
self.id
}
pub fn get_timestamp(&self) -> i64 {
self.timestamp
}
}
#[repr(i32)]
#[derive(Debug, Display, Clone, Copy, PartialEq, Eq, FromPrimitive, ToPrimitive, ToSql, FromSql)]
pub enum LotState {
// Default
Undefined = 0,
// Qr States
/// id=contact
QrAskVerifyContact = 200,
/// text1=groupname
QrAskVerifyGroup = 202,
/// id=contact
QrFprOk = 210,
/// id=contact
QrFprMissmatch = 220,
/// test1=formatted fingerprint
QrFprWithoutAddr = 230,
/// id=contact
QrAddr = 320,
/// text1=text
QrText = 330,
/// text1=URL
QrUrl = 332,
/// text1=error string
QrError = 400,
// Message States
MsgInFresh = 10,
MsgInNoticed = 13,
MsgInSeen = 16,
MsgOutPreparing = 18,
MsgOutDraft = 19,
MsgOutPending = 20,
MsgOutFailed = 24,
MsgOutDelivered = 26,
MsgOutMdnRcvd = 28,
}
impl Default for LotState {
fn default() -> Self {
LotState::Undefined
}
}

1330
src/message.rs Normal file

File diff suppressed because it is too large Load Diff

View File

@@ -1,6 +1,6 @@
use std::collections::HashMap;
use percent_encoding::{utf8_percent_encode, DEFAULT_ENCODE_SET};
use percent_encoding::{utf8_percent_encode, NON_ALPHANUMERIC};
use serde::Deserialize;
use crate::context::Context;
@@ -23,7 +23,7 @@ const OAUTH2_YANDEX: Oauth2 = Oauth2 {
};
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct Oauth2 {
struct Oauth2 {
client_id: &'static str,
get_code: &'static str,
init_token: &'static str,
@@ -48,11 +48,17 @@ pub fn dc_get_oauth2_url(
redirect_uri: impl AsRef<str>,
) -> Option<String> {
if let Some(oauth2) = Oauth2::from_address(addr) {
context.sql.set_config(
context,
"oauth2_pending_redirect_uri",
Some(redirect_uri.as_ref()),
);
if context
.sql
.set_config(
context,
"oauth2_pending_redirect_uri",
Some(redirect_uri.as_ref()),
)
.is_err()
{
return None;
}
let oauth2_url = replace_in_uri(&oauth2.get_code, "$CLIENT_ID", &oauth2.client_id);
let oauth2_url = replace_in_uri(&oauth2_url, "$REDIRECT_URI", redirect_uri.as_ref());
@@ -76,19 +82,17 @@ pub fn dc_get_oauth2_access_token(
// read generated token
if 0 == flags & 0x1 && !is_expired(context) {
let access_token = context.sql.get_config(context, "oauth2_access_token", None);
let access_token = context.sql.get_config(context, "oauth2_access_token");
if access_token.is_some() {
// success
return access_token;
}
}
let refresh_token = context
.sql
.get_config(context, "oauth2_refresh_token", None);
let refresh_token = context.sql.get_config(context, "oauth2_refresh_token");
let refresh_token_for = context
.sql
.get_config(context, "oauth2_refresh_token_for", None)
.get_config(context, "oauth2_refresh_token_for")
.unwrap_or_else(|| "unset".into());
let (redirect_uri, token_url, update_redirect_uri_on_success) =
@@ -100,7 +104,7 @@ pub fn dc_get_oauth2_access_token(
(
context
.sql
.get_config(context, "oauth2_pending_redirect_uri", None)
.get_config(context, "oauth2_pending_redirect_uri")
.unwrap_or_else(|| "unset".into()),
oauth2.init_token,
true,
@@ -113,7 +117,7 @@ pub fn dc_get_oauth2_access_token(
(
context
.sql
.get_config(context, "oauth2_redirect_uri", None)
.get_config(context, "oauth2_redirect_uri")
.unwrap_or_else(|| "unset".into()),
oauth2.refresh_token,
false,
@@ -159,10 +163,12 @@ pub fn dc_get_oauth2_access_token(
if let Some(ref token) = response.refresh_token {
context
.sql
.set_config(context, "oauth2_refresh_token", Some(token));
.set_config(context, "oauth2_refresh_token", Some(token))
.ok();
context
.sql
.set_config(context, "oauth2_refresh_token_for", Some(code.as_ref()));
.set_config(context, "oauth2_refresh_token_for", Some(code.as_ref()))
.ok();
}
// after that, save the access token.
@@ -170,7 +176,8 @@ pub fn dc_get_oauth2_access_token(
if let Some(ref token) = response.access_token {
context
.sql
.set_config(context, "oauth2_access_token", Some(token));
.set_config(context, "oauth2_access_token", Some(token))
.ok();
let expires_in = response
.expires_in
// refresh a bet before
@@ -178,12 +185,14 @@ pub fn dc_get_oauth2_access_token(
.unwrap_or_else(|| 0);
context
.sql
.set_config_int64(context, "oauth2_timestamp_expires", expires_in);
.set_config_int64(context, "oauth2_timestamp_expires", expires_in)
.ok();
if update_redirect_uri_on_success {
context
.sql
.set_config(context, "oauth2_redirect_uri", Some(redirect_uri.as_ref()));
.set_config(context, "oauth2_redirect_uri", Some(redirect_uri.as_ref()))
.ok();
}
} else {
warn!(context, 0, "Failed to find OAuth2 access token");
@@ -296,10 +305,10 @@ impl Oauth2 {
}
fn is_expired(context: &Context) -> bool {
let expire_timestamp =
context
.sql
.get_config_int64(context, "oauth2_timestamp_expires", Some(0));
let expire_timestamp = context
.sql
.get_config_int64(context, "oauth2_timestamp_expires")
.unwrap_or_default();
if expire_timestamp <= 0 {
return false;
@@ -312,7 +321,7 @@ fn is_expired(context: &Context) -> bool {
}
fn replace_in_uri(uri: impl AsRef<str>, key: impl AsRef<str>, value: impl AsRef<str>) -> String {
let value_urlencoded = utf8_percent_encode(value.as_ref(), DEFAULT_ENCODE_SET).to_string();
let value_urlencoded = utf8_percent_encode(value.as_ref(), NON_ALPHANUMERIC).to_string();
uri.as_ref().replace(key.as_ref(), &value_urlencoded)
}
@@ -335,7 +344,7 @@ mod tests {
fn test_replace_in_uri() {
assert_eq!(
replace_in_uri("helloworld", "world", "a-b c"),
"helloa-b%20c"
"helloa%2Db%20c"
);
}

246
src/param.rs Normal file
View File

@@ -0,0 +1,246 @@
use std::collections::BTreeMap;
use std::fmt;
use std::str;
use num_traits::FromPrimitive;
use crate::error;
/// Available param keys.
#[derive(PartialEq, Eq, Debug, Clone, Copy, Hash, PartialOrd, Ord, FromPrimitive)]
#[repr(u8)]
pub enum Param {
/// For messages and jobs
File = 'f' as u8,
/// For Messages
Width = 'w' as u8,
/// For Messages
Height = 'h' as u8,
/// For Messages
Duration = 'd' as u8,
/// For Messages
MimeType = 'm' as u8,
/// For Messages: message is encryoted, outgoing: guarantee E2EE or the message is not send
GuranteeE2ee = 'c' as u8,
/// For Messages: decrypted with validation errors or without mutual set, if neither
/// 'c' nor 'e' are preset, the messages is only transport encrypted.
ErroneousE2ee = 'e' as u8,
/// For Messages: force unencrypted message, either `ForcePlaintext::AddAutocryptHeader` (1),
/// `ForcePlaintext::NoAutocryptHeader` (2) or 0.
ForcePlaintext = 'u' as u8,
/// For Messages
WantsMdn = 'r' as u8,
/// For Messages
Forwarded = 'a' as u8,
/// For Messages
Cmd = 'S' as u8,
/// For Messages
Arg = 'E' as u8,
/// For Messages
Arg2 = 'F' as u8,
/// For Messages
Arg3 = 'G' as u8,
/// For Messages
Arg4 = 'H' as u8,
/// For Messages
Error = 'L' as u8,
/// For Messages: space-separated list of messaged IDs of forwarded copies.
PrepForwards = 'P' as u8,
/// For Jobs
SetLatitude = 'l' as u8,
/// For Jobs
SetLongitude = 'n' as u8,
/// For Jobs
ServerFolder = 'Z' as u8,
/// For Jobs
ServerUid = 'z' as u8,
/// For Jobs
AlsoMove = 'M' as u8,
/// For Jobs: space-separated list of message recipients
Recipients = 'R' as u8,
// For Groups
Unpromoted = 'U' as u8,
// For Groups and Contacts
ProfileImage = 'i' as u8,
// For Chats
Selftalk = 'K' as u8,
// For QR
Auth = 's' as u8,
// For QR
GroupId = 'x' as u8,
// For QR
GroupName = 'g' as u8,
}
/// Possible values for `Param::ForcePlaintext`.
#[derive(PartialEq, Eq, Debug, Clone, Copy, FromPrimitive)]
#[repr(u8)]
pub enum ForcePlaintext {
AddAutocryptHeader = 1,
NoAutocryptHeader = 2,
}
/// An object for handling key=value parameter lists.
///
/// The structure is serialized by calling `to_string()` on it.
///
/// Only for library-internal use.
#[derive(Debug, Clone, PartialEq, Eq, Default)]
pub struct Params {
inner: BTreeMap<Param, String>,
}
impl fmt::Display for Params {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
for (i, (key, value)) in self.inner.iter().enumerate() {
if i > 0 {
write!(f, "\n")?;
}
write!(f, "{}={}", *key as u8 as char, value)?;
}
Ok(())
}
}
impl str::FromStr for Params {
type Err = error::Error;
fn from_str(s: &str) -> std::result::Result<Self, Self::Err> {
let mut inner = BTreeMap::new();
for pair in s.trim().lines() {
let pair = pair.trim();
if pair.is_empty() {
continue;
}
// TODO: probably nicer using a regex
ensure!(pair.len() > 1, "Invalid key pair: '{}'", pair);
let mut split = pair.splitn(2, '=');
let key = split.next();
let value = split.next();
ensure!(key.is_some(), "Missing key");
ensure!(value.is_some(), "Missing value");
let key = key.unwrap().trim();
let value = value.unwrap().trim();
if let Some(key) = Param::from_u8(key.as_bytes()[0]) {
inner.insert(key, value.to_string());
} else {
bail!("Unknown key: {}", key);
}
}
Ok(Params { inner })
}
}
impl Params {
/// Create new empty params.
pub fn new() -> Self {
Default::default()
}
/// Get the value of the given key, return `None` if no value is set.
pub fn get(&self, key: Param) -> Option<&str> {
self.inner.get(&key).map(|s| s.as_str())
}
/// Check if the given key is set.
pub fn exists(&self, key: Param) -> bool {
self.inner.contains_key(&key)
}
/// Set the given key to the passed in value.
pub fn set(&mut self, key: Param, value: impl AsRef<str>) -> &mut Self {
self.inner.insert(key, value.as_ref().to_string());
self
}
/// Removes the given key, if it exists.
pub fn remove(&mut self, key: Param) -> &mut Self {
self.inner.remove(&key);
self
}
/// Check if there are any values in this.
pub fn is_empty(&self) -> bool {
self.inner.is_empty()
}
/// Returns how many key-value pairs are set.
pub fn len(&self) -> usize {
self.inner.len()
}
/// Get the given parameter and parse as `i32`.
pub fn get_int(&self, key: Param) -> Option<i32> {
self.get(key).and_then(|s| s.parse().ok())
}
/// Get the given parameter and parse as `f64`.
pub fn get_float(&self, key: Param) -> Option<f64> {
self.get(key).and_then(|s| s.parse().ok())
}
/// Set the given paramter to the passed in `i32`.
pub fn set_int(&mut self, key: Param, value: i32) -> &mut Self {
self.set(key, format!("{}", value));
self
}
/// Set the given parameter to the passed in `f64` .
pub fn set_float(&mut self, key: Param, value: f64) -> &mut Self {
self.set(key, format!("{}", value));
self
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_dc_param() {
let mut p1: Params = "\r\n\r\na=1\nf=2\n\nc = 3 ".parse().unwrap();
assert_eq!(p1.get_int(Param::Forwarded), Some(1));
assert_eq!(p1.get_int(Param::File), Some(2));
assert_eq!(p1.get_int(Param::Height), None);
assert!(!p1.exists(Param::Height));
p1.set_int(Param::Duration, 4);
assert_eq!(p1.get_int(Param::Duration), Some(4));
let mut p1 = Params::new();
p1.set(Param::Forwarded, "foo")
.set_int(Param::File, 2)
.remove(Param::GuranteeE2ee)
.set_int(Param::Duration, 4);
assert_eq!(p1.to_string(), "a=foo\nd=4\nf=2");
p1.remove(Param::File);
assert_eq!(p1.to_string(), "a=foo\nd=4",);
assert_eq!(p1.len(), 2);
p1.remove(Param::Forwarded);
p1.remove(Param::Duration);
assert_eq!(p1.to_string(), "",);
assert!(p1.is_empty());
assert_eq!(p1.len(), 0)
}
#[test]
fn test_regression() {
let p1: Params = "a=cli%40deltachat.de\nn=\ni=TbnwJ6lSvD5\ns=0ejvbdFSQxB"
.parse()
.unwrap();
assert_eq!(p1.get(Param::Forwarded).unwrap(), "cli%40deltachat.de");
}
}

Some files were not shown because too many files have changed in this diff Show More