Compare commits

..

199 Commits

Author SHA1 Message Date
holger krekel
601b284ed8 fix #538 -- don't crash on wrong setup codes for ac-message, don't use "expect(), added test 2019-09-18 16:31:20 +02:00
Dmitry Bogatov
8a3bf6a5d9 Remove use of `dbg!' macro
According to documentation[^1], this macro should not be in version
control, and should only be used for debugging.

https://doc.rust-lang.org/std/macro.dbg.html
2019-09-18 14:35:49 +02:00
Dmitry Bogatov
d5f361d386 Use include_str! macro instead of embedding key into source
Additionally, this change reduces duplication: now test public key is
stored in only one place, and used in two instead of copy-paste of very
long line.
2019-09-18 14:35:49 +02:00
Dmitry Bogatov
7bb4a27b60 Make Context.cb field private (no code changes) 2019-09-18 14:35:49 +02:00
holger krekel
3bd36feede fix broken rust mimeparser fuzzy tests 2019-09-18 14:15:16 +02:00
holger krekel
fc1f1ce37c streamline fixtures for online accounts, reducing test functions 2019-09-18 14:15:16 +02:00
holger krekel
ee327dc87d remove an ancient hack on context-teardown, refine and use more regular API for finalizing online accounts 2019-09-18 14:15:16 +02:00
Dmitry Bogatov
d644ca5563 Add property test for simplify_plain_text 2019-09-18 07:34:56 +02:00
Dmitry Bogatov
b3b1e37192 Add tests for dc_dehtml 2019-09-18 07:34:56 +02:00
Dmitry Bogatov
38f39c8d32 Add smoke test for Context.get_fresh_msgs 2019-09-18 07:34:56 +02:00
Dmitry Bogatov
a773b7929c Add fuzzy-test for dc_mimeparser_parser 2019-09-18 07:33:22 +02:00
Dmitry Bogatov
7b73103133 Add test for mime parsing reportedly crash-inducing message 2019-09-18 07:33:22 +02:00
holger krekel
b6803191cb Merge branch 'refactor/remove-mprintf' 2019-09-17 16:23:25 +02:00
holger krekel
2b038a34c9 Merge branch 'master' into refactor/remove-mprintf 2019-09-17 15:46:05 +02:00
B. Petersen
8c2c3f8bee move spec.md from separate repo here 2019-09-17 10:49:34 +02:00
dignifiedquire
e710836276 cleanup and fix earlier introduced scoping error 2019-09-16 22:58:10 +02:00
dignifiedquire
6be3c9a48a refactor: improve mime field lookup 2019-09-16 22:58:10 +02:00
dignifiedquire
c0747bf68d refactor: use enum for system messages 2019-09-16 22:58:10 +02:00
dignifiedquire
0346dd15d9 refactor(mimeparser): some more sanity 2019-09-16 22:58:10 +02:00
dignifiedquire
ff0aa8423d refactor: remove now unused build.rs and extern declarations 2019-09-16 07:59:41 +02:00
dignifiedquire
0f718e0d08 refactor(mimefactory): remove dc_mprintf 2019-09-16 07:59:41 +02:00
dignifiedquire
9534a9ad30 refactor(mimeparser): remove dc_mprintf 2019-09-16 07:59:41 +02:00
dignifiedquire
d091857cef refactor(imex): remove dc_mprintf 2019-09-16 07:59:41 +02:00
dignifiedquire
72a9ca0aa5 refactor(receive_imf): remove dc_mprintf 2019-09-16 07:58:08 +02:00
dignifiedquire
ecaae42b80 refactor(examples): remove dc_mprintf 2019-09-16 07:58:08 +02:00
dignifiedquire
84bf1ec6e7 refactor(tools): no more dc_mprintf 2019-09-16 07:58:08 +02:00
dignifiedquire
0bf3d20e07 fix and implement ffi tranlation 2019-09-15 23:40:08 +02:00
dignifiedquire
5486ac5b9f refacor: use an enum for events 2019-09-15 23:40:08 +02:00
Alexander Krotov
ac12b2e643 chore(key): remove unused *fingerprint_c functions 2019-09-15 19:32:16 +02:00
dignifiedquire
16c281a9b7 refactor(context): safe interface 2019-09-15 16:36:31 +02:00
dignifiedquire
413e3eb62d apply more CR 2019-09-15 16:36:31 +02:00
dignifiedquire
f31f341a50 feat: enforce Debug implementations and remove mod types 2019-09-15 16:36:31 +02:00
dignifiedquire
c2501258b6 apply CR feedback 2019-09-15 16:36:31 +02:00
dignifiedquire
de1e3e1d4f refactor(context): remove last unsafe bits from the context struct 2019-09-15 16:36:31 +02:00
Floris Bruynooghe
a3f64d4e95 Handle new Context creation API on the FFI
This adopts the FFI API to use the new Context::new() and
Contest::drop() Rust-style APIs while preserving the semantics of the
existing FFI API.  It introduces a wrapper around the context which
keeps an optional reference to the actual context, changing dc_open()
and dc_close() to create/destroy the actual reference.

This changes the events emitted on closing slightly: they only get
emitted when dc_close() was called while the context was open.  While
dc_close() remains idempotent in that it can still be called safely
multiple times, the close events will only occur if the context is
actually closed.
2019-09-15 02:37:13 +02:00
Floris Bruynooghe
afc9a31080 Remove dc_open call
A new context is now created by calling Context::new and therefore you
always have a valid context.  This is much more in Rust style and will
allow a lot of furture simplifications on the context itself.

The FFI layer has not yet been adjusted in this commit and thus will
fail.
2019-09-15 02:37:13 +02:00
Simon Laux
e5699e8ba9 Merge pull request #517 from deltachat/fix_get_draft
Fix get_draft to just return null and not produce errors if there's no
2019-09-14 23:39:09 +02:00
Floris Bruynooghe
8a7143b791 Return a Result<Option<Message>> for get_draft
The function can fail, so we need to still have an error return as
well as a no-draft return.

Add tests.
2019-09-14 23:00:13 +02:00
björn petersen
79d23909b5 fix 'this message is not encrypted' error in verified groups (#522) 2019-09-14 18:55:19 +02:00
Alexander Krotov
24fe4740d3 Merge pull request #498 from deltachat/return_bool
Resolve "should return bool" TODOs
2019-09-14 14:31:50 +00:00
Alexander Krotov
1e1d3f4aa8 Merge pull request #521 from deltachat/server_flags
Restore DC_LP_* constants from C code
2019-09-14 14:19:02 +00:00
Alexander Krotov
51bf875826 Use DC_LP_* constants in configure module 2019-09-14 13:22:59 +03:00
Alexander Krotov
f9ce6c7c81 Use DC_LP_* constants in auto_outlook.rs 2019-09-14 13:16:21 +03:00
Alexander Krotov
2e91c3d334 Use DC_LP_* constants in auto_mozilla.rs 2019-09-14 13:14:35 +03:00
Alexander Krotov
d1b762af04 Make DC_LP_* constants public 2019-09-14 13:14:18 +03:00
Alexander Krotov
e1e02839d1 Return bool from dc_mimefactory_render 2019-09-14 12:06:33 +03:00
Alexander Krotov
50a812ea5e Return bool from mailmime_transfer_decode 2019-09-14 12:03:10 +03:00
Alexander Krotov
27a4adb9c6 Return bool from export_key_to_asc_file 2019-09-14 12:03:10 +03:00
Alexander Krotov
3932d8f48f Return bool from export_self_keys 2019-09-14 12:03:10 +03:00
Alexander Krotov
d978a8e0a2 Return bool from {import,export}_backup 2019-09-14 12:03:10 +03:00
Alexander Krotov
fad49e08d7 message.rs: resolve "should return bool" TODOs 2019-09-14 12:03:10 +03:00
Alexander Krotov
0f67b16f29 Return bool from add_contact_to_chat_ex 2019-09-14 12:03:10 +03:00
Alexander Krotov
97edd23910 Return bool from add_to_chat_contacts_table 2019-09-14 12:03:10 +03:00
Alexander Krotov
400ab2cdab Return bool from dc_msg_get_showpadlock 2019-09-14 12:03:10 +03:00
jikstra
db20ecf985 Remove println 2019-09-13 22:35:31 +02:00
jikstra
e59f421e79 cargo fmt 2019-09-13 22:25:18 +02:00
jikstra
75b7de712a Fix get_draft to just return null and not produce errors if there's no
draft
2019-09-13 18:19:52 +02:00
Alexander Krotov
fa8192177d Merge pull request #514 from deltachat/in_reply_to
Store In-Reply-To header as Option<String>
2019-09-13 10:21:32 +00:00
Alexander Krotov
48ffef7955 Store In-Reply-To header as Option<String> 2019-09-13 11:43:48 +03:00
Alexander Krotov
055796e6b3 Merge pull request #496 from KAction/safe
Make dc_msg_get_file return PathBuf, not char*
2019-09-13 00:43:28 +00:00
B. Petersen
34433c4962 support additional placeholder in stock-string 2019-09-12 23:44:40 +02:00
Alexander Krotov
6d1076e1f6 Mark dc_imex() as safe 2019-09-12 23:43:25 +02:00
Dmitry Bogatov
bb4081e503 Rename query_row_col to query_get_value
Since function `query_row_col` no longer accept column number argument,
it is misleading to mention column in its name.
2019-09-12 18:42:12 +00:00
Dmitry Bogatov
bef25ad5f6 Avoid excessive allocation in dc_search_msgs 2019-09-12 17:12:26 +00:00
Dmitry Bogatov
f47652e72d Change argument type of write_asc_to_file from const char * to &Path 2019-09-12 17:12:23 +00:00
Dmitry Bogatov
7c4d7fb3dd Make Aheader::from_imffields accept &str, not const char * 2019-09-12 06:32:13 +00:00
Dmitry Bogatov
cdfc7281d0 Simplify Sql.query_row_col function
Function `query_row` executes query and calls callback function to process
row returned. Function query_row_col() is special case, that provides
callback function, which returns value of one particular column of row,
ignoring others.

In all cases, that particular column was 0 (first and only column of
query result), since there is no point to select more than one column
with this function -- they are discarded anyway.

This commit removes that redundancy, removing column number argument of
query_row_col() function and adjusting call sites accordingly.
2019-09-12 05:01:06 +00:00
Dmitry Bogatov
500e80784a Replace some of context.sql.get_config() with context.get_config()
Pattern `context.sql.get_config(context, {foo})` is unnecessary
redundant in Rust: unlike C, Rust has associated functions (methods).
2019-09-12 04:41:06 +00:00
Dmitry Bogatov
c5803d9b4c Remove useless catch-all pattern-matching 2019-09-12 04:17:00 +00:00
Dmitry Bogatov
ebac00fbde Use safe version of dc_write_file
* src/key.rs(write_asc_to_file): simplify code using safe version of
   dc_write_file() function
2019-09-12 04:11:44 +00:00
Dmitry Bogatov
f198ce29b1 Drop unsafe version of dc_get_abs_path 2019-09-12 04:11:43 +00:00
Dmitry Bogatov
df47e0ed63 Use safe version of dc_get_abs_path.
* src/dc_mimefactory.rs(build_body_file): use safe version of
   dc_get_abs_path() function.
2019-09-12 04:10:05 +00:00
Dmitry Bogatov
477470ff70 Make dc_msg_get_file return PathBuf, not char*
Adjust call sites as apporiate:

 * src/dc_imex.rs(dc_continue_key_transfer): use if-let pattern
   with dc_read_file_safe() and dc_msg_get_file()

 * src/message.rs(dc_msg_get_setupcodebegin): ditto

 * src/message.rs(dc_get_msg_info): simplify code to print information
   about file inside a message.

 * src/message.rs(dc_msg_get_file): simplify function using
   dc_get_abs_path_safe()

 * deltachat-ffi/src/lib.rs(dc_msg_get_file): convert PathBuf to `char *`
   to preserve C API
2019-09-12 04:10:05 +00:00
Floris Bruynooghe
aefddf7f5e Remove context refrence from Contact struct
This will allow the Rust Context API to be refactored without breaking
the C API.  Full details in #476 aka
a0b5e32f98
2019-09-11 23:53:48 +02:00
Floris Bruynooghe
5ce27b16f1 Remove context refernce from Chatlist
See #476 aka a0b5e32f98 for full
rationale.  Tl;dr it allows us to evolve the Rust API while keeping
the FFI API identical.
2019-09-11 23:53:48 +02:00
Floris Bruynooghe
8302d6833d Remove context ref from Chat struct
Leaving the C API untouched.  See #476 aka
a0b5e32f98 for more extensive rationale.
2019-09-11 23:53:48 +02:00
holger krekel
649c2eb676 try running qr tests in a new process instead of with all the other tests 2019-09-11 22:42:51 +02:00
Floris Bruynooghe
a0b5e32f98 Remove the context reference from Message struct
The Message struct had a reference to the context which made a few
APIs a little easier.  However it has surprising consequences a long
way down the line as shown in #335: it means any object which has such
a reference needs to keep open a lock if we want to do this refactor
of no longer having a "closed" Context struct on the Rust API (which
has many benefits which will simply that Context struct and is more
the Rust way - RAII etc).

By refactoring away the context reference on the rust API as done in
here however, we push this behaviour of how these references are
handled back to the C-API pointer behaviour: that is unsafe but just
works in a C-like way.  The resulting complexity in the FFI layer is
also notably less than in the #335 alternative.

As a consequence all APIs which require the context, now explicitly
need to get the context passed in as an argument.  It looks like this
is certainly no downside and maybe even beneficial for further API
refactors.

For this strategy to work out the same should be done to
dc_chatlist_t, dc_chat_t and dc_contact_t.  But this working for
dc_msg_t give a reasonable confidence that this is a good approach.
2019-09-11 21:48:40 +02:00
björn petersen
e73671a6ff Merge pull request #485 from deltachat/really-fix-477
do not panic on bad-utf-8
2019-09-11 20:14:48 +02:00
björn petersen
9503aca78d Merge branch 'master' into really-fix-477 2019-09-11 19:52:13 +02:00
björn petersen
dfaa8e4529 Merge pull request #490 from deltachat/fix/location-sql
fix(location): do not sql recurse in location sending
2019-09-11 19:40:46 +02:00
björn petersen
cbc3579e9a Merge pull request #489 from deltachat/no-recursive-delete
do not recursive delete folders
2019-09-11 19:39:15 +02:00
dignifiedquire
dd2e3d35fd fix(location): another nesting 2019-09-11 18:13:21 +02:00
dignifiedquire
ca9dccfcd7 fix(location): another nested sql 2019-09-11 17:57:59 +02:00
dignifiedquire
64b00fce7d fix(location): do not sql recurse in location sending 2019-09-11 17:46:05 +02:00
B. Petersen
177ab0229a do not call fs::remove_dir_all() implicitly on non-files; deleting folders is not needed and calling remove_dir_all() is considered harmful 2019-09-11 17:02:27 +02:00
Alexander Krotov
68b5e34fed Merge pull request #488 from KAction/transfer
Reduce indentation in dc_continue_key_transfer
2019-09-11 05:36:02 +00:00
Dmitry Bogatov
2eda839303 cargo-fmt 2019-09-11 02:21:39 +00:00
Dmitry Bogatov
71a01d3002 Reduce indentation in dc_continue_key_transfer
Replace `if (ok) { }` pattern with `if (!ok) return` to reduce
indentation level.

Note: This commit fails CI due incorrect formatting. It is done
deliberately to simplify review process.
2019-09-11 02:21:39 +00:00
Alexander Krotov
995548002b Merge pull request #484 from link2xt/safe_test_encryption_decryption
Safe test encryption decryption
2019-09-11 00:43:21 +00:00
Alexander Krotov
38ad16887b Merge pull request #487 from KAction/top_evil
Minor refactoring
2019-09-11 00:42:48 +00:00
Dmitry Bogatov
c20e8f7613 Use safe version of dc_decode_header_words on one call site 2019-09-10 23:31:29 +00:00
Dmitry Bogatov
5bd4606854 Implement safe version of `dc_decode_header_words' 2019-09-10 23:26:08 +00:00
Dmitry Bogatov
e7b198849d Copy comment for dc_encode_headers_words from C code 2019-09-10 22:49:53 +00:00
Dmitry Bogatov
3ab0d74af2 Remove unused `unsafe' block in log_event! macro 2019-09-10 22:35:54 +00:00
Dmitry Bogatov
7ed5a8e72f Reimplement logging macros in terms of log_event!
Avoid copy-paste message formatting code in src/log.rs, forwarding all
arguments of info! warn! error! macros to generic log_event! macro.
2019-09-10 22:34:38 +00:00
Dmitry Bogatov
57daa0f7f0 Remove useless argument of logging macros
Previously, logging macros (info! warn! error!) accepted integer
argument (data1), that was passed to callback function verbatim. In all
call sites this argument was 0.

With this change, that data1 argument is no longer part of macro
interface, 0 is always passed to callback in internals of these macros.
2019-09-10 22:26:47 +00:00
Dmitry Bogatov
2dd3f169db Use set_config_bool instead of set_config_int in boolean context 2019-09-10 22:26:47 +00:00
Dmitry Bogatov
b97b618b4b Use sql.get_config_bool to simplify dc_is_configured 2019-09-10 22:26:47 +00:00
Dmitry Bogatov
bb12488200 Add Sql.{set,get}_config_bool methods
Previously, boolean configurations were implemented on top of i32
(get_config_int, set_config_it) at call sites.

Having one canonical location, containing boolean <-> database
conversion may help to avoid serialization issues.
2019-09-10 22:26:47 +00:00
Alexander Krotov
706a97b013 Move part of test_encryption_decryption to key.rs 2019-09-11 00:40:46 +03:00
Dmitry Bogatov
1cdb9c733a Change return type of `dc_is_configured' to bool 2019-09-10 19:08:55 +00:00
holger krekel
ffc525af9e pragmatismatic: run flaky tests three times to see if we can get more "green" CI runs this way ...
thanks to @the-compiler also modernize plugin usage
2019-09-10 20:51:52 +02:00
Dmitry Bogatov
1576dc1d13 top_evil_rs: check for all-caps version of ok_to_continue pattern 2019-09-10 17:34:17 +00:00
Dmitry Bogatov
4fbb5fbb25 Make it easier to run src/top_evil_rs.py from git root
Currently, `src/top_evil_rs.py' script recursively scans current
directory for Rust sources and print statistics about them.

When run from git root, it also scans target/ directory, which is
useless.

This commit add cludge that checks if script is run from git root
directory, and `chdir' into `src/' before performing actions.

Unprincipled, but convenient.
2019-09-10 17:34:16 +00:00
B. Petersen
e9da21a02e cargo fmt 2019-09-10 19:14:51 +02:00
B. Petersen
6c4d7ad8cc do not panic on bad-utf-8 2019-09-10 16:48:54 +02:00
Alexander Krotov
f1c026c5ec Pass passphrase to dc_pgp_symm_{en,de}crypt as &str 2019-09-10 15:58:42 +02:00
björn petersen
3d61c06ea9 Merge pull request #478 from deltachat/fix477
fix crash when msg_raw is None
2019-09-10 15:50:33 +02:00
Alexander Krotov
22c1ee1f55 cargo fmt 2019-09-10 16:29:41 +03:00
Alexander Krotov
971960a242 key.rs: remove unsafe Key.from_binary 2019-09-10 16:29:24 +03:00
Alexander Krotov
69f8973339 test_encryption_decryption: use safe from_slice instead of from_binary 2019-09-10 16:29:24 +03:00
B. Petersen
bf1d9b6d06 fix crash when msg_raw is None 2019-09-10 15:20:38 +02:00
Alexander Krotov
6a2368f83c Return bool from dc_continue_key_transfer 2019-09-10 15:15:48 +02:00
Alexander Krotov
d0960f7f7f Merge pull request #483 from link2xt/dc_msg_exists_safe
Make dc_msg_exists safe and rusty
2019-09-10 15:15:33 +03:00
Alexander Krotov
e5ad697466 Make dc_msg_exists safe and rusty 2019-09-10 14:21:27 +03:00
Alexander Krotov
188eab5faf Fix test_encryption_decryption
It is broken since 28cae607a4
2019-09-10 12:00:59 +02:00
holger krekel
2b257e3d0d fix ffi 2019-09-09 19:45:43 +02:00
Alexander Krotov
77c9746be5 Make dc_msg_get_filemime safe 2019-09-09 19:45:43 +02:00
Alexander Krotov
28cae607a4 Pass buffers to pgp.rs as slices 2019-09-09 18:50:47 +02:00
Simon Laux
814281ed7d fixes #463 2019-09-09 18:44:30 +02:00
holger krekel
5b0c8dd9dd address @r10s and @flub review comments, and fix some docstrings/test meta docs 2019-09-09 18:07:32 +02:00
holger krekel
650d8c45ec fix test, and cleanup according profile-image API 2019-09-09 18:07:32 +02:00
holger krekel
383d8980d6 add profile image API to python, tests, Rust fixes/cleanups 2019-09-09 18:07:32 +02:00
Simon Laux
6ea706c646 remove macro 2019-09-09 18:07:32 +02:00
Simon Laux
1ed2af08b8 cargo fmt 2019-09-09 18:07:32 +02:00
Simon Laux
7563a5abe0 remove closure
Co-authored-by: @Jikstra
2019-09-09 18:07:32 +02:00
Simon Laux
0a8b187f80 fix remove chat profile img 2019-09-09 18:07:32 +02:00
björn petersen
275aa853f5 Merge pull request #471 from deltachat/fix417
fix crash when downloading message
2019-09-09 16:18:50 +02:00
Friedel Ziegelmayer
3614d57f9f Merge pull request #469 from deltachat/refctor/strencode
refactor(strencode): rustify some strencode methods
2019-09-09 11:52:30 +02:00
B. Petersen
1367873949 check bounds before accessing Vec 2019-09-09 01:47:50 +02:00
B. Petersen
d933183e0a mark safe functions as such 2019-09-09 01:47:50 +02:00
B. Petersen
7e11def527 make code more readable 2019-09-09 00:01:15 +02:00
dignifiedquire
f3e53a05a6 refactor(loginparam): rename dc_loginparam -> login_param 2019-09-08 18:48:57 +02:00
dignifiedquire
dd381a5c1c refactor(loginparam): simplify and rustify 2019-09-08 18:48:57 +02:00
dignifiedquire
8eee449305 refactor(token): rustify 2019-09-08 18:35:20 +02:00
dignifiedquire
96e02af0da refactor: rename dc_token to token 2019-09-08 18:35:20 +02:00
dignifiedquire
60fb1478c3 refactor(strencode): rustify some strencode methods 2019-09-08 15:50:28 +02:00
Friedel Ziegelmayer
aa7d0679df fix(tools): make sure dc_truncate can handle arbitrary utf8 valu… (#460)
fix(tools): make sure dc_truncate can handle arbitrary utf8 values
2019-09-08 15:23:46 +02:00
dignifiedquire
8e3cc192a5 fix(tools): make sure dc_truncate can handle arbitrary utf8 values
also adds proptests to make sure this is upheld

Should close #433
2019-09-08 14:00:04 +02:00
Simon Laux
0f939995d1 fix(job): "invalid job action" check 2019-09-08 13:39:59 +02:00
björn petersen
2e1bc9b14e Merge pull request #458 from deltachat/fix-unknown
fix reading unknown origin
2019-09-08 12:24:51 +02:00
Friedel Ziegelmayer
fd1ac6ab2d Remove some free() from Mozilla autoconfig (#457)
Remove some free() from Mozilla autoconfig
2019-09-08 11:53:41 +02:00
Friedel Ziegelmayer
d224924dc8 Change type of function from `const char *' to &str (#451)
Change type of function from `const char *' to &str
2019-09-08 11:51:16 +02:00
dignifiedquire
00e5ddd6f0 make enum reading from the db more robust 2019-09-08 11:29:40 +02:00
B. Petersen
c603ca0e7a remove dead code 2019-09-08 07:26:07 +02:00
B. Petersen
d07ef01204 cargo fmt 2019-09-07 23:25:19 +02:00
B. Petersen
d8630b5029 fix reading of unknown/outdated origin 2019-09-07 22:21:38 +02:00
Alexander Krotov
3b397326f8 Store email parts as Rust str's 2019-09-07 20:23:08 +03:00
Alexander Krotov
81cabd08a9 Accept str instead of char* in read_autoconf_file 2019-09-07 20:20:25 +03:00
björn petersen
5663c7dec3 Merge pull request #453 from deltachat/fix-backup
Fix backup and housekeeping
2019-09-07 15:38:01 +02:00
björn petersen
06673b2108 Merge pull request #455 from deltachat/fix-msg-loading
Fix msg loading
2019-09-07 15:37:38 +02:00
dignifiedquire
6b7498a4b1 fix(contact): fix logic for create or add contact
Closes #448
2019-09-07 14:07:56 +02:00
B. Petersen
7f4ef493b9 be tolerant when reading unexpected NULL from the database and treat this as an empty string, compatible to core-c 2019-09-07 13:54:43 +02:00
B. Petersen
d9d0dee0d5 fix: use empty string for messages without text everywhere 2019-09-07 13:40:50 +02:00
B. Petersen
9605370f0b fix cmdline: sendimage and sendfile really accept only a file 2019-09-07 12:34:31 +02:00
Dmitry Bogatov
7d9fc682a0 cargo-fmt 2019-09-07 03:09:01 +00:00
Dmitry Bogatov
c4c08f2552 Remove ok_to_continue pattern from msg_prepare_raw()
This commit will fail CI due incorrect formatting. It is done
deliberately to simplify review process.
2019-09-07 03:06:13 +00:00
Dmitry Bogatov
400740fdba Simplify prepare_msg_raw() using early return
This commit will fail CI due incorrect formatting. It is done
deliberately to simplify review process.
2019-09-07 03:03:35 +00:00
Dmitry Bogatov
42bce7c0bf Remove last C string from prepare_msg_raw() 2019-09-07 02:54:28 +00:00
Dmitry Bogatov
a2281489a6 Create safe version of msgid-generating function 2019-09-07 02:35:13 +00:00
Dmitry Bogatov
9bf7b0bf96 Use more of Rust, less of C strings in prepare_msg_raw() 2019-09-07 01:39:26 +00:00
Dmitry Bogatov
1f82ba74aa Remove redundant checks in prepare_msg_raw() 2019-09-06 23:24:39 +00:00
Dmitry Bogatov
1062ac6ade Drop unsafe version of get_parent_mime_headers function 2019-09-06 23:15:31 +00:00
Dmitry Bogatov
aa5304a4f3 Use safe version of `get_parent_mime_headers()' function 2019-09-06 23:15:31 +00:00
Dmitry Bogatov
3a57ba1142 Implement safe version of `get_parent_mime_headers' function 2019-09-06 23:15:31 +00:00
Dmitry Bogatov
c0e7293360 Change return type of clist_search_string_nocase to `bool' 2019-09-06 23:15:31 +00:00
Dmitry Bogatov
dc1839760c Simplify clist_search_string_nocase using Iterator interface 2019-09-06 23:15:31 +00:00
Dmitry Bogatov
a4e4b0fc17 Rustify type of dc_mimeparser_t.subject 2019-09-06 23:15:31 +00:00
Dmitry Bogatov
743e4deb36 Remove dc_mimepart_unref function
Since there is no longer any manually-managed memory, associated with
`dc_mimepart_t' structure, default Drop instances does everything
automatically.
2019-09-06 23:15:30 +00:00
Dmitry Bogatov
1d75f8478c Rustify type of dc_mimepart_t.msg_raw 2019-09-06 23:15:27 +00:00
B. Petersen
cc0428aa50 really check all rows when searching for referenced files 2019-09-06 15:15:06 +02:00
B. Petersen
4be481275f clearer naming 2019-09-06 14:32:17 +02:00
Dmitry Bogatov
28cfe36f43 Change type of dc_mimeparser_t.decryption_failed to bool 2019-09-06 03:48:41 +00:00
Dmitry Bogatov
e0df78c5f7 Change type of dc_mimepart_t.is_meta to bool 2019-09-06 03:41:18 +00:00
Dmitry Bogatov
4d8b058b65 Change type of dc_mimeparser_t.is_forwarded to bool 2019-09-06 03:36:54 +00:00
Dmitry Bogatov
da25611758 Change type of function from `const char *' to &str 2019-09-06 03:05:12 +00:00
B. Petersen
27732c85af mark actually safe function as such 2019-09-06 00:55:50 +02:00
B. Petersen
5ffc84eb59 remove unused functions 2019-09-06 00:55:50 +02:00
holger krekel
0a6e540394 rename dc_securejoin to securejoin.rs 2019-09-05 22:55:25 +02:00
holger krekel
9f09c73ec1 make secure_join flow more readable by using and adding a few macros, tiny api changes 2019-09-05 22:55:25 +02:00
holger krekel
4bbab876ae majorly de-indent code structure in secure_join by introducing cleanup function, also majorly reducing unsafety in several places 2019-09-05 22:55:25 +02:00
holger krekel
b2fafeff19 use some BOB and VC constants instead of raw numbers 2019-09-05 22:55:25 +02:00
holger krekel
79510a83de - remove many *libc::char usages, and c-pointer from fingerprint
- rustify get_chat_id_by_grpid and streamline returned chat id handling (thereby apprently fixing the test, don't ask)
2019-09-05 22:55:25 +02:00
holger krekel
dd0afdfeb0 add QR based join-group API, with test and SEGFAULT fix to rust 2019-09-05 22:55:25 +02:00
holger krekel
b6997c4455 regen constants and improve high level API for QR setup contact 2019-09-05 22:55:25 +02:00
holger krekel
2920732435 (dignifiedquire, hpk, jikstra)
- fix and test peerstate::from_fingerprint
- add and test python API for secure-join QR + setup-contact
2019-09-05 22:55:25 +02:00
jikstra
b9cfcce284 fix ffi 2019-09-05 22:55:25 +02:00
jikstra
70d997964b cargo fmt 2019-09-05 22:55:25 +02:00
jikstra
4ffe71e1df Fix encoding for email & name, fix qrencode command in repl 2019-09-05 22:55:25 +02:00
jikstra
cc2339fbe2 Fix closure in dc_securejoin, make sure we return an empty string and
never null, make dc_get_securejoin_qr return an Option<String> and move
the logic to cast it to c_str into the ffi
2019-09-05 22:55:25 +02:00
Alexander Krotov
8fb859c0c4 Merge pull request #445 from deltachat/smtp_error
Avoid panic on SMTP error
2019-09-05 20:22:24 +00:00
jikstra
5ff472dae0 Implement helper method to easily check if a bit flag is inside
listflags. Make Contact::get_all use it

Add method documentation and tests
2019-09-05 18:02:50 +02:00
holger krekel
6be4a6ed00 switch to counting ok_to_continue instead of current_blocks -- this still reflects structural problems or missing-rustification problems 2019-09-05 18:00:18 +02:00
Alexander Krotov
094d46293e Set Smtp.error when SMTP fails to send message 2019-09-05 05:29:37 +03:00
Alexander Krotov
c8d945db56 Store Smtp.error as Option<String>
Without this change, when SMTP password is incorrect,
as_str(sock.error) is called with a null pointer,
and as_str panics.

Now it does not crash when the error is not set.
2019-09-05 05:28:57 +03:00
Alexander Krotov
f78f0079c1 Upgrade to the latest version of imap crate 2019-09-04 20:59:54 +02:00
Dmitry Bogatov
98d6bdb48a Simplify control flow in dc_DC_JOB_SEND function
Replace `ok_to_continue' control flow variables with early return from
function, since there is no longer need to free memory manually.
2019-09-04 16:44:51 +02:00
Dmitry Bogatov
0391aebaeb Remove C pointer manipulation from do_DC_JOB_SEND()
This change removes several `unsafe' blocks by using safe version of
`dc_read_file' function.

Note, that logic is changed slightly: if get(Param::File) returns
Some(""), it no longer triggers "missing filename warnings".
2019-09-04 16:44:51 +02:00
79 changed files with 7616 additions and 8487 deletions

4
.gitattributes vendored
View File

@@ -2,6 +2,10 @@
# ensures this even if the user has not set core.autocrlf.
* text=auto
# This directory contains email messages verbatim, and changing CRLF to
# LF will corrupt them.
test-data/* text=false
# binary files should be detected by git, however, to be sure, you can add them here explicitly
*.png binary
*.jpg binary

635
Cargo.lock generated

File diff suppressed because it is too large Load Diff

View File

@@ -5,10 +5,6 @@ authors = ["dignifiedquire <dignifiedquire@gmail.com>"]
edition = "2018"
license = "MPL"
[build-dependencies]
cc = "1.0.35"
pkg-config = "0.3"
[dependencies]
deltachat_derive = { path = "./deltachat_derive" }
libc = "0.2.51"
@@ -23,7 +19,7 @@ num-derive = "0.2.5"
num-traits = "0.2.6"
native-tls = "0.2.3"
lettre = "0.9.0"
imap = { git = "https://github.com/jonhoo/rust-imap", rev = "31e2490d2203a837d8830736bcb183831ec4894d" }
imap = { git = "https://github.com/jonhoo/rust-imap", rev = "281d2eb8ab50dc656ceff2ae749ca5045f334e15" }
mmime = { git = "https://github.com/dignifiedquire/mmime", rev = "bccd2c2" }
base64 = "0.10"
charset = "0.1"
@@ -35,7 +31,7 @@ failure = "0.1.5"
failure_derive = "0.1.5"
# TODO: make optional
rustyline = "4.1.0"
lazy_static = "1.3.0"
lazy_static = "1.4.0"
regex = "1.1.6"
rusqlite = { version = "0.20", features = ["bundled"] }
r2d2_sqlite = "0.12.0"
@@ -50,11 +46,14 @@ image-meta = "0.1.0"
quick-xml = "0.15.0"
escaper = "0.1.0"
bitflags = "1.1.0"
jetscii = "0.4.4"
debug_stub_derive = "0.3.0"
[dev-dependencies]
tempfile = "3.0"
pretty_assertions = "0.6.1"
pretty_env_logger = "0.3.0"
proptest = "0.9.4"
[workspace]
members = [

View File

@@ -1,38 +0,0 @@
extern crate cc;
fn link_static(lib: &str) {
println!("cargo:rustc-link-lib=static={}", lib);
}
fn link_framework(fw: &str) {
println!("cargo:rustc-link-lib=framework={}", fw);
}
fn add_search_path(p: &str) {
println!("cargo:rustc-link-search={}", p);
}
fn build_tools() {
let mut config = cc::Build::new();
config.file("misc.c").compile("libtools.a");
println!("rerun-if-changed=build.rs");
println!("rerun-if-changed=misc.h");
println!("rerun-if-changed=misc.c");
}
fn main() {
build_tools();
add_search_path("/usr/local/lib");
let target = std::env::var("TARGET").unwrap();
if target.contains("-apple") || target.contains("-darwin") {
link_framework("CoreFoundation");
link_framework("CoreServices");
link_framework("Security");
}
// local tools
link_static("tools");
}

View File

@@ -41,7 +41,8 @@ if [ -n "$TESTS" ]; then
# see https://github.com/deltachat/deltachat-core-rust/issues/331
# unset DCC_PY_LIVECONFIG
tox --workdir "$TOXWORKDIR" -e lint,py35,py36,py37,auditwheels
tox --workdir "$TOXWORKDIR" -e lint,py35,py36,py37,auditwheels -- -k "not qr"
tox --workdir "$TOXWORKDIR" -e py35,py36,py37 -- -k "qr"
popd
fi

View File

@@ -20,9 +20,6 @@ libc = "0.2"
human-panic = "1.0.1"
num-traits = "0.2.6"
deltachat-provider-overview = { git = "https://github.com/deltachat/provider-overview", rev = "e52d915c05232c40284387bfe13862c6a2293060" }
serde_json = "1.0"
[features]
default = ["vendored", "nightly", "ringbuf"]
vendored = ["deltachat/vendored"]

View File

@@ -393,8 +393,9 @@ int dc_set_config (dc_context_t* context, const char*
* @memberof dc_context_t
* @param context The context object as created by dc_context_new(). For querying system values, this can be NULL.
* @param key The key to query.
* @return Returns current value of "key", if "key" is unset, the default value is returned.
* The returned value must be free()'d, NULL is never returned.
* @return Returns current value of "key", if "key" is unset, the default
* value is returned. The returned value must be free()'d, NULL is never
* returned. If there is an error an empty string will be returned.
*/
char* dc_get_config (dc_context_t* context, const char* key);
@@ -441,11 +442,7 @@ char* dc_get_info (dc_context_t* context);
*/
char* dc_get_oauth2_url (dc_context_t* context, const char* addr, const char* redirect_uri);
/**
* Get the provider information from our provider overview as json object.
*/
char* dc_get_json_provider_info_from_email (char* email);
// connect

File diff suppressed because it is too large Load Diff

View File

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

View File

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

View File

@@ -14,20 +14,21 @@ extern crate lazy_static;
extern crate rusqlite;
use std::borrow::Cow::{self, Borrowed, Owned};
use std::ptr;
use std::io::{self, Write};
use std::path::Path;
use std::process::Command;
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_securejoin::*;
use deltachat::dc_tools::*;
use deltachat::job::*;
use deltachat::oauth2::*;
use deltachat::types::*;
use deltachat::securejoin::*;
use deltachat::x::*;
use deltachat::Event;
use rustyline::completion::{Completer, FilenameCompleter, Pair};
use rustyline::config::OutputStreamType;
use rustyline::error::ReadlineError;
@@ -42,96 +43,75 @@ use self::cmdline::*;
// Event Handler
unsafe extern "C" fn receive_event(
_context: &Context,
event: Event,
data1: uintptr_t,
data2: uintptr_t,
) -> uintptr_t {
fn receive_event(_context: &Context, event: Event) -> libc::uintptr_t {
match event {
Event::GET_STRING => {}
Event::INFO => {
Event::GetString { .. } => {}
Event::Info(msg) => {
/* do not show the event as this would fill the screen */
println!("{}", to_string(data2 as *const _),);
println!("{}", msg);
}
Event::SMTP_CONNECTED => {
println!("[DC_EVENT_SMTP_CONNECTED] {}", to_string(data2 as *const _));
Event::SmtpConnected(msg) => {
println!("[DC_EVENT_SMTP_CONNECTED] {}", msg);
}
Event::IMAP_CONNECTED => {
println!("[DC_EVENT_IMAP_CONNECTED] {}", to_string(data2 as *const _),);
Event::ImapConnected(msg) => {
println!("[DC_EVENT_IMAP_CONNECTED] {}", msg);
}
Event::SMTP_MESSAGE_SENT => {
println!(
"[DC_EVENT_SMTP_MESSAGE_SENT] {}",
to_string(data2 as *const _),
);
Event::SmtpMessageSent(msg) => {
println!("[DC_EVENT_SMTP_MESSAGE_SENT] {}", msg);
}
Event::WARNING => {
println!("[Warning] {}", to_string(data2 as *const _),);
Event::Warning(msg) => {
println!("[Warning] {}", msg);
}
Event::ERROR => {
println!(
"\x1b[31m[DC_EVENT_ERROR] {}\x1b[0m",
to_string(data2 as *const _),
);
Event::Error(msg) => {
println!("\x1b[31m[DC_EVENT_ERROR] {}\x1b[0m", msg);
}
Event::ERROR_NETWORK => {
println!(
"\x1b[31m[DC_EVENT_ERROR_NETWORK] first={}, msg={}\x1b[0m",
data1 as usize,
to_string(data2 as *const _),
);
Event::ErrorNetwork(msg) => {
println!("\x1b[31m[DC_EVENT_ERROR_NETWORK] msg={}\x1b[0m", msg);
}
Event::ERROR_SELF_NOT_IN_GROUP => {
println!(
"\x1b[31m[DC_EVENT_ERROR_SELF_NOT_IN_GROUP] {}\x1b[0m",
to_string(data2 as *const _),
);
Event::ErrorSelfNotInGroup(msg) => {
println!("\x1b[31m[DC_EVENT_ERROR_SELF_NOT_IN_GROUP] {}\x1b[0m", msg);
}
Event::MSGS_CHANGED => {
Event::MsgsChanged { chat_id, msg_id } => {
print!(
"\x1b[33m{{Received DC_EVENT_MSGS_CHANGED({}, {})}}\n\x1b[0m",
data1 as usize, data2 as usize,
"\x1b[33m{{Received DC_EVENT_MSGS_CHANGED(chat_id={}, msg_id={})}}\n\x1b[0m",
chat_id, msg_id,
);
}
Event::CONTACTS_CHANGED => {
Event::ContactsChanged(_) => {
print!("\x1b[33m{{Received DC_EVENT_CONTACTS_CHANGED()}}\n\x1b[0m");
}
Event::LOCATION_CHANGED => {
Event::LocationChanged(contact) => {
print!(
"\x1b[33m{{Received DC_EVENT_LOCATION_CHANGED(contact={})}}\n\x1b[0m",
data1 as usize,
"\x1b[33m{{Received DC_EVENT_LOCATION_CHANGED(contact={:?})}}\n\x1b[0m",
contact,
);
}
Event::CONFIGURE_PROGRESS => {
Event::ConfigureProgress(progress) => {
print!(
"\x1b[33m{{Received DC_EVENT_CONFIGURE_PROGRESS({} ‰)}}\n\x1b[0m",
data1 as usize,
progress,
);
}
Event::IMEX_PROGRESS => {
Event::ImexProgress(progress) => {
print!(
"\x1b[33m{{Received DC_EVENT_IMEX_PROGRESS({} ‰)}}\n\x1b[0m",
data1 as usize,
progress,
);
}
Event::IMEX_FILE_WRITTEN => {
Event::ImexFileWritten(file) => {
print!(
"\x1b[33m{{Received DC_EVENT_IMEX_FILE_WRITTEN({})}}\n\x1b[0m",
to_string(data1 as *const _)
file.display()
);
}
Event::CHAT_MODIFIED => {
Event::ChatModified(chat) => {
print!(
"\x1b[33m{{Received DC_EVENT_CHAT_MODIFIED({})}}\n\x1b[0m",
data1 as usize,
chat
);
}
_ => {
print!(
"\x1b[33m{{Received {:?}({}, {})}}\n\x1b[0m",
event, data1 as usize, data2 as usize,
);
print!("\x1b[33m{{Received {:?}}}\n\x1b[0m", event);
}
}
@@ -385,17 +365,15 @@ 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), ptr::null_mut(), Some("CLI".into()));
unsafe { dc_cmdline_skip_auth() };
if args.len() == 2 {
if unsafe { !dc_open(&mut context, &args[1], None) } {
println!("Error: Cannot open {}.", args[0],);
}
} else if args.len() != 1 {
if args.len() < 2 {
println!("Error: Bad arguments, expected [db-name].");
return Err(format_err!("No db-name specified"));
}
let context = Context::new(
Box::new(receive_event),
"CLI".into(),
Path::new(&args[1]).to_path_buf(),
)?;
println!("Delta Chat Core is awaiting your commands.");
@@ -516,30 +494,27 @@ unsafe fn handle_cmd(line: &str, ctx: Arc<RwLock<Context>>) -> Result<ExitResult
}
"getqr" | "getbadqr" => {
start_threads(ctx.clone());
let qrstr =
dc_get_securejoin_qr(&ctx.read().unwrap(), arg1.parse().unwrap_or_default());
if !qrstr.is_null() && 0 != *qrstr.offset(0isize) as libc::c_int {
if arg0 == "getbadqr" && strlen(qrstr) > 40 {
let mut i: libc::c_int = 12i32;
while i < 22i32 {
*qrstr.offset(i as isize) = '0' as i32 as libc::c_char;
i += 1
if let Some(mut qr) =
dc_get_securejoin_qr(&ctx.read().unwrap(), arg1.parse().unwrap_or_default())
{
if !qr.is_empty() {
if arg0 == "getbadqr" && qr.len() > 40 {
qr.replace_range(12..22, "0000000000")
}
println!("{}", qr);
let output = Command::new("qrencode")
.args(&["-t", "ansiutf8", qr.as_str(), "-o", "-"])
.output()
.expect("failed to execute process");
io::stdout().write_all(&output.stdout).unwrap();
io::stderr().write_all(&output.stderr).unwrap();
}
println!("{}", to_string(qrstr as *const _));
let syscmd = dc_mprintf(
b"qrencode -t ansiutf8 \"%s\" -o -\x00" as *const u8 as *const libc::c_char,
qrstr,
);
system(syscmd);
free(syscmd as *mut libc::c_void);
}
free(qrstr as *mut libc::c_void);
}
"joinqr" => {
start_threads(ctx.clone());
if !arg0.is_empty() {
dc_join_securejoin(&ctx.read().unwrap(), arg1_c);
dc_join_securejoin(&ctx.read().unwrap(), arg1);
}
}
"exit" | "quit" => return Ok(ExitResult::Exit),

View File

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

52
misc.c
View File

@@ -1,52 +0,0 @@
#include <stdlib.h>
#include <stdarg.h>
#include <ctype.h>
#include <string.h>
#include <stdio.h>
#include "misc.h"
static char* internal_dc_strdup(const char* s) /* strdup(NULL) is undefined, save_strdup(NULL) returns an empty string in this case */
{
char* ret = NULL;
if (s) {
if ((ret=strdup(s))==NULL) {
exit(16); /* cannot allocate (little) memory, unrecoverable error */
}
}
else {
if ((ret=(char*)calloc(1, 1))==NULL) {
exit(17); /* cannot allocate little memory, unrecoverable error */
}
}
return ret;
}
char* dc_mprintf(const char* format, ...)
{
char testbuf[1];
char* buf = NULL;
int char_cnt_without_zero = 0;
va_list argp;
va_list argp_copy;
va_start(argp, format);
va_copy(argp_copy, argp);
char_cnt_without_zero = vsnprintf(testbuf, 0, format, argp);
va_end(argp);
if (char_cnt_without_zero < 0) {
va_end(argp_copy);
return internal_dc_strdup("ErrFmt");
}
buf = malloc(char_cnt_without_zero+2 /* +1 would be enough, however, protect against off-by-one-errors */);
if (buf==NULL) {
va_end(argp_copy);
return internal_dc_strdup("ErrMem");
}
vsnprintf(buf, char_cnt_without_zero+1, format, argp_copy);
va_end(argp_copy);
return buf;
}

1
misc.h
View File

@@ -1 +0,0 @@
char* dc_mprintf (const char* format, ...); /* The result must be free()'d. */

View File

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

View File

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

View File

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

View File

@@ -14,7 +14,7 @@ except ImportError:
import deltachat
from . import const
from .capi import ffi, lib
from .cutil import as_dc_charpointer, from_dc_charpointer, iter_array
from .cutil import as_dc_charpointer, from_dc_charpointer, iter_array, DCLot
from .chatting import Contact, Chat, Message
@@ -50,9 +50,8 @@ class Account(object):
self._configkeys = self.get_config("sys.config_keys").split()
self._imex_completed = threading.Event()
# XXX this can cause "illegal instructions" at test ends so we omit it for now
# def __del__(self):
# self.shutdown()
def __del__(self):
self.shutdown()
def _check_config_key(self, name):
if name not in self._configkeys:
@@ -329,6 +328,56 @@ class Account(object):
raise RuntimeError("could not send out autocrypt setup message")
return from_dc_charpointer(res)
def get_setup_contact_qr(self):
""" get/create Setup-Contact QR Code as ascii-string.
this string needs to be transferred to another DC account
in a second channel (typically used by mobiles with QRcode-show + scan UX)
where qr_setup_contact(qr) is called.
"""
res = lib.dc_get_securejoin_qr(self._dc_context, 0)
return from_dc_charpointer(res)
def check_qr(self, qr):
""" check qr code and return :class:`ScannedQRCode` instance representing the result"""
res = ffi.gc(
lib.dc_check_qr(self._dc_context, as_dc_charpointer(qr)),
lib.dc_lot_unref
)
lot = DCLot(res)
if lot.state() == const.DC_QR_ERROR:
raise ValueError("invalid or unknown QR code: {}".format(lot.text1()))
return ScannedQRCode(lot)
def qr_setup_contact(self, qr):
""" setup contact and return a Chat after contact is established.
Note that this function may block for a long time as messages are exchanged
with the emitter of the QR code. On success a :class:`deltachat.chatting.Chat` instance
is returned.
:param qr: valid "setup contact" QR code (all other QR codes will result in an exception)
"""
assert self.check_qr(qr).is_ask_verifycontact()
chat_id = lib.dc_join_securejoin(self._dc_context, as_dc_charpointer(qr))
if chat_id == 0:
raise ValueError("could not setup secure contact")
return Chat(self, chat_id)
def qr_join_chat(self, qr):
""" join a chat group through a QR code.
Note that this function may block for a long time as messages are exchanged
with the emitter of the QR code. On success a :class:`deltachat.chatting.Chat` instance
is returned which is the chat that we just joined.
:param qr: valid "join-group" QR code (all other QR codes will result in an exception)
"""
assert self.check_qr(qr).is_ask_verifygroup()
chat_id = lib.dc_join_securejoin(self._dc_context, as_dc_charpointer(qr))
if chat_id == 0:
raise ValueError("could not join group")
return Chat(self, chat_id)
def start_threads(self):
""" start IMAP/SMTP threads (and configure account if it hasn't happened).
@@ -347,7 +396,8 @@ class Account(object):
def shutdown(self, wait=True):
""" stop threads and close and remove underlying dc_context and callbacks. """
if hasattr(self, "_dc_context") and hasattr(self, "_threads"):
self.stop_threads(wait=False) # to interrupt idle and tell python threads to stop
# print("SHUTDOWN", self)
self.stop_threads(wait=False)
lib.dc_close(self._dc_context)
self.stop_threads(wait=wait) # to wait for threads
deltachat.clear_context_callback(self._dc_context)
@@ -433,6 +483,10 @@ class EventLogger:
def set_timeout(self, timeout):
self._timeout = timeout
def consume_events(self, check_error=True):
while not self._event_queue.empty():
self.get()
def get(self, timeout=None, check_error=True):
timeout = timeout or self._timeout
ev = self._event_queue.get(timeout=timeout)
@@ -492,3 +546,18 @@ def _destroy_dc_context(dc_context, dc_context_unref=lib.dc_context_unref):
# we are deep into Python Interpreter shutdown,
# so no need to clear the callback context mapping.
pass
class ScannedQRCode:
def __init__(self, dc_lot):
self._dc_lot = dc_lot
def is_ask_verifycontact(self):
return self._dc_lot.state() == const.DC_QR_ASK_VERIFYCONTACT
def is_ask_verifygroup(self):
return self._dc_lot.state() == const.DC_QR_ASK_VERIFYGROUP
@property
def contact_id(self):
return self._dc_lot.id()

View File

@@ -1,6 +1,7 @@
""" chatting related objects: Contact, Chat, Message. """
import mimetypes
import os
from . import props
from .cutil import as_dc_charpointer, from_dc_charpointer, iter_array
from .capi import lib, ffi
@@ -131,6 +132,16 @@ class Chat(object):
"""
return lib.dc_chat_get_type(self._dc_chat)
def get_join_qr(self):
""" get/create Join-Group QR Code as ascii-string.
this string needs to be transferred to another DC account
in a second channel (typically used by mobiles with QRcode-show + scan UX)
where account.join_with_qrcode(qr) needs to be called.
"""
res = lib.dc_get_securejoin_qr(self._dc_context, self.id)
return from_dc_charpointer(res)
# ------ chat messaging API ------------------------------
def send_text(self, text):
@@ -294,7 +305,6 @@ class Chat(object):
def get_contacts(self):
""" get all contacts for this chat.
:params: contact object.
:raises ValueError: if contact could not be added
:returns: list of :class:`deltachat.chatting.Contact` objects for this chat
"""
@@ -305,3 +315,46 @@ class Chat(object):
return list(iter_array(
dc_array, lambda id: Contact(self._dc_context, id))
)
def set_profile_image(self, img_path):
"""Set group profile image.
If the group is already promoted (any message was sent to the group),
all group members are informed by a special status message that is sent
automatically by this function.
:params img_path: path to image object
:raises ValueError: if profile image could not be set
:returns: None
"""
assert os.path.exists(img_path), img_path
p = as_dc_charpointer(img_path)
res = lib.dc_set_chat_profile_image(self._dc_context, self.id, p)
if res != 1:
raise ValueError("Setting Profile Image {!r} failed".format(p))
def remove_profile_image(self):
"""remove group profile image.
If the group is already promoted (any message was sent to the group),
all group members are informed by a special status message that is sent
automatically by this function.
:raises ValueError: if profile image could not be reset
:returns: None
"""
res = lib.dc_set_chat_profile_image(self._dc_context, self.id, ffi.NULL)
if res != 1:
raise ValueError("Removing Profile Image failed")
def get_profile_image(self):
"""Get group profile image.
For groups, this is the image set by any group member using
set_chat_profile_image(). For normal chats, this is the image
set by each remote user on their own using dc_set_config(context,
"selfavatar", image).
:returns: path to profile image, None if no profile image exists.
"""
dc_res = lib.dc_chat_get_profile_image(self._dc_chat)
if dc_res == ffi.NULL:
return None
return from_dc_charpointer(dc_res)

View File

@@ -13,6 +13,15 @@ DC_GCL_NO_SPECIALS = 0x02
DC_GCL_ADD_ALLDONE_HINT = 0x04
DC_GCL_VERIFIED_ONLY = 0x01
DC_GCL_ADD_SELF = 0x02
DC_QR_ASK_VERIFYCONTACT = 200
DC_QR_ASK_VERIFYGROUP = 202
DC_QR_FPR_OK = 210
DC_QR_FPR_MISMATCH = 220
DC_QR_FPR_WITHOUT_ADDR = 230
DC_QR_ADDR = 320
DC_QR_TEXT = 330
DC_QR_URL = 332
DC_QR_ERROR = 400
DC_CHAT_ID_DEADDROP = 1
DC_CHAT_ID_TRASH = 3
DC_CHAT_ID_MSGS_IN_CREATION = 4
@@ -69,15 +78,13 @@ DC_EVENT_IMEX_FILE_WRITTEN = 2052
DC_EVENT_SECUREJOIN_INVITER_PROGRESS = 2060
DC_EVENT_SECUREJOIN_JOINER_PROGRESS = 2061
DC_EVENT_GET_STRING = 2091
DC_EVENT_HTTP_GET = 2100
DC_EVENT_HTTP_POST = 2110
DC_EVENT_FILE_COPIED = 2055
DC_EVENT_IS_OFFLINE = 2081
# end const generated
def read_event_defines(f):
rex = re.compile(r'#define\s+((?:DC_EVENT_|DC_MSG|DC_STATE_|DC_CONTACT_ID_|DC_GCL|DC_CHAT)\S+)\s+([x\d]+).*')
rex = re.compile(r'#define\s+((?:DC_EVENT_|DC_QR|DC_MSG|DC_STATE_|DC_CONTACT_ID_|DC_GCL|DC_CHAT)\S+)\s+([x\d]+).*')
for line in f:
m = rex.match(line)
if m:
@@ -90,7 +97,7 @@ if __name__ == "__main__":
if len(sys.argv) >= 2:
deltah = sys.argv[1]
else:
deltah = joinpath(dirname(dirname(dirname(here_dir))), "src", "deltachat.h")
deltah = joinpath(dirname(dirname(dirname(here_dir))), "deltachat-ffi", "deltachat.h")
assert os.path.exists(deltah)
lines = []

View File

@@ -1,5 +1,6 @@
from .capi import lib
from .capi import ffi
from datetime import datetime
def as_dc_charpointer(obj):
@@ -17,3 +18,29 @@ def iter_array(dc_array_t, constructor):
def from_dc_charpointer(obj):
return ffi.string(ffi.gc(obj, lib.dc_str_unref)).decode("utf8")
class DCLot:
def __init__(self, dc_lot):
self._dc_lot = dc_lot
def id(self):
return lib.dc_lot_get_id(self._dc_lot)
def state(self):
return lib.dc_lot_get_state(self._dc_lot)
def text1(self):
return from_dc_charpointer(lib.dc_lot_get_text1(self._dc_lot))
def text1_meaning(self):
return lib.dc_lot_get_text1_meaning(self._dc_lot)
def text2(self):
return from_dc_charpointer(lib.dc_lot_get_text2(self._dc_lot))
def timestamp(self):
ts = lib.dc_lot_get_timestamp(self._dc_lot)
if ts == 0:
return None
return datetime.utcfromtimestamp(ts)

View File

@@ -110,7 +110,13 @@ class Message(object):
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))
res = lib.dc_continue_key_transfer(
self._dc_context,
self.id,
as_dc_charpointer(setup_code)
)
if res == 0:
raise ValueError("could not decrypt")
@props.with_doc
def time_sent(self):

View File

@@ -24,17 +24,6 @@ def pytest_configure(config):
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 = []
@@ -136,13 +125,17 @@ def acfactory(pytestconfig, tmpdir, request, session_liveconfig):
fin = self._finalizers.pop()
fin()
def make_account(self, path, logid):
ac = Account(path, logid=logid)
self._finalizers.append(ac.shutdown)
return ac
def get_unconfigured_account(self):
self.offline_count += 1
tmpdb = tmpdir.join("offlinedb%d" % self.offline_count)
ac = Account(tmpdb.strpath, logid="ac{}".format(self.offline_count))
ac = self.make_account(tmpdb.strpath, logid="ac{}".format(self.offline_count))
ac._evlogger.init_time = self.init_time
ac._evlogger.set_timeout(2)
self._finalizers.append(ac.shutdown)
return ac
def get_configured_offline_account(self):
@@ -165,26 +158,35 @@ def acfactory(pytestconfig, tmpdir, request, session_liveconfig):
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 = self.make_account(tmpdb.strpath, logid="ac{}".format(self.live_count))
ac._evlogger.init_time = self.init_time
ac._evlogger.set_timeout(30)
ac.configure(**configdict)
ac.start_threads()
self._finalizers.append(ac.shutdown)
return ac
def get_two_online_accounts(self):
ac1 = self.get_online_configuring_account()
ac2 = self.get_online_configuring_account()
wait_successful_IMAP_SMTP_connection(ac1)
wait_configuration_progress(ac1, 1000)
wait_successful_IMAP_SMTP_connection(ac2)
wait_configuration_progress(ac2, 1000)
return ac1, ac2
def clone_online_account(self, account):
self.live_count += 1
tmpdb = tmpdir.join("livedb%d" % self.live_count)
ac = Account(tmpdb.strpath, logid="ac{}".format(self.live_count))
ac = self.make_account(tmpdb.strpath, logid="ac{}".format(self.live_count))
ac._evlogger.init_time = self.init_time
ac._evlogger.set_timeout(30)
ac.configure(addr=account.get_config("addr"), mail_pw=account.get_config("mail_pw"))
ac.start_threads()
self._finalizers.append(ac.shutdown)
return ac
return AccountMaker()
am = AccountMaker()
request.addfinalizer(am.finalize)
return am
@pytest.fixture
@@ -213,6 +215,15 @@ def wait_configuration_progress(account, target):
break
def wait_securejoin_inviter_progress(account, target):
while 1:
evt_name, data1, data2 = \
account._evlogger.get_matching("DC_EVENT_SECUREJOIN_INVITER_PROGRESS")
if data2 >= target:
print("** SECUREJOINT-INVITER PROGRESS {}".format(target), account)
break
def wait_successful_IMAP_SMTP_connection(account):
imap_ok = smtp_ok = False
while not imap_ok or not smtp_ok:

View File

@@ -4,7 +4,7 @@ import os
from deltachat import const, Account
from deltachat.message import Message
from datetime import datetime, timedelta
from conftest import wait_configuration_progress, wait_successful_IMAP_SMTP_connection
from conftest import wait_configuration_progress, wait_successful_IMAP_SMTP_connection, wait_securejoin_inviter_progress
class TestOfflineAccountBasic:
@@ -140,6 +140,22 @@ class TestOfflineChat:
chat.set_name("title2")
assert chat.get_name() == "title2"
@pytest.mark.parametrize("verified", [True, False])
def test_group_chat_qr(self, acfactory, ac1, verified):
ac2 = acfactory.get_configured_offline_account()
chat = ac1.create_group_chat(name="title1", verified=verified)
qr = chat.get_join_qr()
assert ac2.check_qr(qr).is_ask_verifygroup
def test_get_set_profile_image_simple(self, ac1, data):
chat = ac1.create_group_chat(name="title1")
p = data.get_path("d.png")
chat.set_profile_image(p)
p2 = chat.get_profile_image()
assert open(p, "rb").read() == open(p2, "rb").read()
chat.remove_profile_image()
assert chat.get_profile_image() is None
def test_delete_and_send_fails(self, ac1, chat1):
chat1.delete()
ac1._evlogger.get_matching("DC_EVENT_MSGS_CHANGED")
@@ -306,12 +322,23 @@ class TestOfflineChat:
chat1.set_draft(None)
assert chat1.get_draft() is None
def test_qr_setup_contact(self, acfactory, lp):
ac1 = acfactory.get_configured_offline_account()
ac2 = acfactory.get_configured_offline_account()
qr = ac1.get_setup_contact_qr()
assert qr.startswith("OPENPGP4FPR:")
res = ac2.check_qr(qr)
assert res.is_ask_verifycontact()
assert not res.is_ask_verifygroup()
assert res.contact_id == 10
class TestOnlineAccount:
def test_one_account_init(self, acfactory):
ac1 = acfactory.get_online_configuring_account()
wait_successful_IMAP_SMTP_connection(ac1)
wait_configuration_progress(ac1, 1000)
def get_chat(self, ac1, ac2):
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
return chat
def test_one_account_send(self, acfactory):
ac1 = acfactory.get_online_configuring_account()
@@ -327,15 +354,8 @@ class TestOnlineAccount:
assert ev[1] == msg_out.id
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"))
chat = ac1.create_chat_by_contact(c2)
assert chat.id >= const.DC_CHAT_ID_LAST_SPECIAL
wait_successful_IMAP_SMTP_connection(ac1)
wait_configuration_progress(ac1, 1000)
wait_successful_IMAP_SMTP_connection(ac2)
wait_configuration_progress(ac2, 1000)
ac1, ac2 = acfactory.get_two_online_accounts()
chat = self.get_chat(ac1, ac2)
msg_out = chat.send_text("message1")
@@ -346,15 +366,8 @@ class TestOnlineAccount:
assert msg_in.text == "message1"
def test_forward_messages(self, acfactory):
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_successful_IMAP_SMTP_connection(ac1)
wait_configuration_progress(ac1, 1000)
wait_successful_IMAP_SMTP_connection(ac2)
wait_configuration_progress(ac2, 1000)
ac1, ac2 = acfactory.get_two_online_accounts()
chat = self.get_chat(ac1, ac2)
msg_out = chat.send_text("message2")
@@ -378,15 +391,10 @@ class TestOnlineAccount:
assert not chat3.get_messages()
def test_send_and_receive_message(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
ac1, ac2 = acfactory.get_two_online_accounts()
wait_configuration_progress(ac1, 1000)
wait_configuration_progress(ac2, 1000)
lp.sec("ac1: create chat with ac2")
chat = self.get_chat(ac1, ac2)
lp.sec("sending text message from ac1 to ac2")
msg_out = chat.send_text("message1")
@@ -428,15 +436,10 @@ class TestOnlineAccount:
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
ac1, ac2 = acfactory.get_two_online_accounts()
wait_configuration_progress(ac1, 1000)
wait_configuration_progress(ac2, 1000)
lp.sec("ac1: create chat with ac2")
chat = self.get_chat(ac1, ac2)
lp.sec("sending text message from ac1 to ac2")
msg_out = chat.send_text("message1")
@@ -459,14 +462,12 @@ class TestOnlineAccount:
assert msg_back.text == "message-back"
def test_saved_mime_on_received_message(self, acfactory, lp):
lp.sec("starting accounts, waiting for configuration")
ac1 = acfactory.get_online_configuring_account()
ac2 = acfactory.get_online_configuring_account()
ac1, ac2 = acfactory.get_two_online_accounts()
lp.sec("configure ac2 to save mime headers, create ac1/ac2 chat")
ac2.set_config("save_mime_headers", "1")
c2 = ac1.create_contact(email=ac2.get_config("addr"))
chat = ac1.create_chat_by_contact(c2)
wait_configuration_progress(ac1, 1000)
wait_configuration_progress(ac2, 1000)
chat = self.get_chat(ac1, ac2)
lp.sec("sending text message from ac1 to ac2")
msg_out = chat.send_text("message1")
ac1._evlogger.get_matching("DC_EVENT_MSG_DELIVERED")
@@ -480,14 +481,8 @@ class TestOnlineAccount:
assert mime.get_all("Received")
def test_send_and_receive_image(self, acfactory, lp, data):
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)
wait_configuration_progress(ac1, 1000)
wait_configuration_progress(ac2, 1000)
ac1, ac2 = acfactory.get_two_online_accounts()
chat = self.get_chat(ac1, ac2)
lp.sec("sending image message from ac1 to ac2")
path = data.get_path("d.png")
@@ -507,13 +502,13 @@ class TestOnlineAccount:
assert os.stat(msg_in.filename).st_size == os.stat(path).st_size
def test_import_export_online(self, acfactory, tmpdir):
backupdir = tmpdir.mkdir("backup")
ac1 = acfactory.get_online_configuring_account()
wait_configuration_progress(ac1, 1000)
contact1 = ac1.create_contact("some1@hello.com", name="some1")
chat = ac1.create_chat_by_contact(contact1)
chat.send_text("msg1")
backupdir = tmpdir.mkdir("backup")
path = ac1.export_to_dir(backupdir.strpath)
assert os.path.exists(path)
@@ -542,7 +537,82 @@ class TestOnlineAccount:
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()
# first try a bad setup code
with pytest.raises(ValueError):
msg.continue_key_transfer(str(reversed(setup_code)))
print("*************** Incoming ASM File at: ", msg.filename)
print("*************** Setup Code: ", setup_code)
msg.continue_key_transfer(setup_code)
assert ac1.get_info()["fingerprint"] == ac2.get_info()["fingerprint"]
def test_qr_setup_contact(self, acfactory, lp):
ac1, ac2 = acfactory.get_two_online_accounts()
lp.sec("ac1: create QR code and let ac2 scan it, starting the securejoin")
qr = ac1.get_setup_contact_qr()
lp.sec("ac2: start QR-code based setup contact protocol")
ch = ac2.qr_setup_contact(qr)
assert ch.id >= 10
wait_securejoin_inviter_progress(ac1, 1000)
def test_qr_join_chat(self, acfactory, lp):
ac1, ac2 = acfactory.get_two_online_accounts()
lp.sec("ac1: create QR code and let ac2 scan it, starting the securejoin")
chat = ac1.create_group_chat("hello")
qr = chat.get_join_qr()
lp.sec("ac2: start QR-code based join-group protocol")
ch = ac2.qr_join_chat(qr)
assert ch.id >= 10
wait_securejoin_inviter_progress(ac1, 1000)
def test_set_get_profile_image(self, acfactory, data, lp):
ac1, ac2 = acfactory.get_two_online_accounts()
lp.sec("create unpromoted group chat")
chat = ac1.create_group_chat("hello")
p = data.get_path("d.png")
lp.sec("ac1: set profile image on unpromoted chat")
chat.set_profile_image(p)
ac1._evlogger.get_matching("DC_EVENT_CHAT_MODIFIED")
assert not chat.is_promoted()
lp.sec("ac1: send text to promote chat (XXX without contact added)")
# XXX first promote the chat before adding contact
# because DC does not send out profile images for unpromoted chats
# otherwise
chat.send_text("ac1: initial message to promote chat (workaround)")
assert chat.is_promoted()
lp.sec("ac2: add ac1 to a chat so the message does not land in DEADDROP")
c1 = ac2.create_contact(email=ac1.get_config("addr"))
ac2.create_chat_by_contact(c1)
ev = ac2._evlogger.get_matching("DC_EVENT_MSGS_CHANGED")
lp.sec("ac1: add ac2 to promoted group chat")
c2 = ac1.create_contact(email=ac2.get_config("addr"))
chat.add_contact(c2)
lp.sec("ac1: send a first message to ac2")
chat.send_text("hi")
assert chat.is_promoted()
lp.sec("ac2: wait for receiving message from ac1")
ev = ac2._evlogger.get_matching("DC_EVENT_INCOMING_MSG")
msg_in = ac2.get_message_by_id(ev[2])
assert not msg_in.chat.is_deaddrop()
lp.sec("ac2: create chat and read profile image")
chat2 = ac2.create_chat_by_message(msg_in)
p2 = chat2.get_profile_image()
assert p2 is not None
assert open(p2, "rb").read() == open(p, "rb").read()
ac2._evlogger.consume_events()
ac1._evlogger.consume_events()
lp.sec("ac2: delete profile image from chat")
chat2.remove_profile_image()
ev = ac1._evlogger.get_matching("DC_EVENT_INCOMING_MSG")
assert ev[1] == chat.id
chat1b = ac1.create_chat_by_message(ev[2])
assert chat1b.get_profile_image() is None
assert chat.get_profile_image() is None

View File

@@ -1,5 +1,5 @@
from __future__ import print_function
from deltachat import capi, cutil, 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
@@ -17,11 +17,19 @@ def test_callback_None2int():
clear_context_callback(ctx)
def test_dc_close_events():
ctx = capi.lib.dc_context_new(capi.lib.py_dc_callback, ffi.NULL, ffi.NULL)
def test_dc_close_events(tmpdir):
ctx = ffi.gc(
capi.lib.dc_context_new(capi.lib.py_dc_callback, ffi.NULL, ffi.NULL),
lib.dc_context_unref,
)
evlog = EventLogger(ctx)
evlog.set_timeout(5)
set_context_callback(ctx, lambda ctx, evt_name, data1, data2: evlog(evt_name, data1, data2))
set_context_callback(
ctx,
lambda ctx, evt_name, data1, data2: evlog(evt_name, data1, data2)
)
p = tmpdir.join("hello.db")
lib.dc_open(ctx, p.strpath.encode("ascii"), ffi.NULL)
capi.lib.dc_close(ctx)
def find(info_string):
@@ -75,10 +83,3 @@ def test_markseen_invalid_message_ids(acfactory):
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")
def test_provider_info():
example = cutil.from_dc_charpointer(
lib.dc_get_json_provider_info_from_email(cutil.as_dc_charpointer("ex@example.com"))
)
assert example == "{\"overview_page\":\"example.com\",\"name\":\"Example\",\"status\":{\"state\":\"PREPARATION\",\"date\":\"2018-09\"},\"markdown\":\"\\n...\"}"

View File

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

308
spec.md Normal file
View File

@@ -0,0 +1,308 @@
# Chat-over-Email specification
Version 0.19.0
This document describes how emails can be used to implement typical messenger functions while staying compatible to existing MUAs.
- [Encryption](#encryption)
- [Outgoing messages](#outgoing-messages)
- [Incoming messages](#incoming-messages)
- [Forwarded messages](#forwarded-messages)
- [Groups](#groups)
- [Outgoing group messages](#outgoing-group-messages)
- [Incoming group messages](#incoming-group-messages)
- [Add and remove members](#add-and-remove-members)
- [Change group name](#change-group-name)
- [Set group image](#set-group-image)
- [Set profile image](#set-profile-image)
- [Miscellaneous](#miscellaneous)
# Encryption
Messages SHOULD be encrypted by the [Autocrypt](https://autocrypt.org/level1.html) standard; `prefer-encrypt=mutual` MAY be set by default.
Meta data (at least the subject and all chat-headers) SHOULD be encrypted by the [Memoryhole](https://github.com/autocrypt/memoryhole) standard.
If Memoryhole is not used, the subject of encrypted messages SHOULD be replaced by the string
`Chat: Encrypted message` where the part after the colon MAY be localized.
# Outgoing messages
Messengers MUST add a `Chat-Version: 1.0` header to outgoing messages.
For filtering and smart appearance of the messages in normal MUAs,
the `Subject` header SHOULD start with the characters `Chat:` and SHOULD be an excerpt of the message.
Replies to messages MAY follow the typical `Re:`-format.
The body MAY contain text which MUST have the content type `text/plain`
or `mulipart/alternative` containing `text/plain`.
The text MAY be divided into a user-text-part and a footer-part using the
line `-- ` (minus, minus, space, lineend).
The user-text-part MUST contain only user generated content.
User generated content are eg. texts a user has actually typed or pasted or
forwarded from another user.
Full quotes, footers or sth. like that MUST NOT go to the user-text-part.
From: sender@domain
To: rcpt@domain
Chat-Version: 1.0
Content-Type: text/plain
Subject: Chat: Hello ...
Hello world!
# Incoming messages
The `Chat-Version` header MAY be used to detect if a messages comes from a compatible messenger.
The `Subject` header MUST NOT be used to detect compatible messengers, groups or whatever.
Messenger SHOULD show the `Subject` if the message comes from a normal MUA together with the email-body.
The email-body SHOULD be converted to plain text, full-quotes and similar regions SHOULD be cut.
Attachments SHOULD be shown where possible. If an attachment cannot be shown, a non-distracting warning SHOULD be printed.
# Forwarded messages
Forwarded messages are outgoing messages that contain a forwarded-header
before the user generated content.
The forwarded header MUST contain two lines:
The first line contains the text
`---------- Forwarded message ----------`
(10 minus, space, text `Forwarded message`, space, 10 minus).
The second line starts with `From: ` followed by the original sender
which SHOULD be anonymized or just a placeholder.
From: sender@domain
To: rcpt@domain
Chat-Version: 1.0
Content-Type: text/plain
Subject: Chat: Forwarded message
---------- Forwarded message ----------
From: Messenger
Hello world!
Incoming forwarded messages are detected by the header.
The messenger SHOULD mark these messages in a way that it becomes obvious
that the message is not created by the sender.
Note that most messengers do not show the original sender with forwarded messages
but MUAs typically expose the sender in the UI.
# Groups
Groups are chats with usually more than one recipient, each defined by an email-address.
The sender plus the recipients are the group members.
To allow different groups with the same members, groups are identified by a group-id.
The group-id MUST be created only from the characters `0`-`9`, `A`-`Z`, `a`-`z` `_` and `-`.
Groups MUST have a group-name. The group-name is any non-zero-length UTF-8 string.
Groups MAY have a group-image.
## Outgoing groups messages
All group members MUST be added to the `From`/`To` headers.
The group-id MUST be written to the `Chat-Group-ID` header.
The group-name MUST be written to `Chat-Group-Name` header (the forced presence of this header makes it easier to join a group chat on a second device any time).
The `Subject` header of outgoing group messages SHOULD start with the characters `Chat:` followed by the group-name and a colon followed by an excerpt of the message.
To identify the group-id on replies from normal MUAs, the group-id MUST also be added to
the message-id of outgoing messages. The message-id MUST have the
format `Gr.<group-id>.<unique data>`.
From: member1@domain
To: member2@domain, member3@domain
Chat-Version: 1.0
Chat-Group-ID: 1234xyZ
Chat-Group-Name: My Group
Message-ID: Gr.1234xyZ.0001@domain
Subject: Chat: My Group: Hello group ...
Hello group - this group contains three members
Messengers adding the member list in the form `Name <email-address>` MUST take care only to spread the names authorized by the contacts themselves.
Otherwise, names as _Daddy_ or _Honey_ may be spread (this issue is also true for normal MUAs, however, for more contact- and chat-centralized apps
such situations happen more frequently).
## Incoming group messages
The messenger MUST search incoming messages for the group-id in the following headers: `Chat-Group-ID`,
`Message-ID`, `In-Reply-To` and `References` (in this order).
If the messenger finds a valid and existent group-id, the message SHOULD be assigned to the given group.
If the messenger finds a valid but not existent group-id, the messenger MAY create a new group.
If no group-id is found, the message MAY be assigned to a normal single-user chat with the email-address given in `From`.
## Add and remove members
Messenger clients MUST construct the member list from the `From`/`To` headers only on the first group message or if they see a `Chat-Group-Member-Added` or `Chat-Group-Member-Removed` action header.
Both headers MUST have the email-address of the added or removed member as the value.
Messenger clients MUST NOT construct the member list on other group messages (this is to avoid accidentally altered To-lists in normal MUAs; the user
does not expect adding a user to a _message_ will also add him to the _group_ "forever").
The messenger SHOULD send an explicit mail for each added or removed member.
The body of the message SHOULD contain a localized description about what happened
and the message SHOULD appear as a message or action from the sender.
From: member1@domain
To: member2@domain, member3@domain, member4@domain
Chat-Version: 1.0
Chat-Group-ID: 1234xyZ
Chat-Group-Name: My Group
Chat-Group-Member-Added: member4@domain
Message-ID: Gr.1234xyZ.0002@domain
Subject: Chat: My Group: Hello, ...
Hello, I've added member4@domain to our group. Now we have 4 members.
To remove a member:
From: member1@domain
To: member2@domain, member3@domain
Chat-Version: 1.0
Chat-Group-ID: 1234xyZ
Chat-Group-Name: My Group
Chat-Group-Member-Removed: member4@domain
Message-ID: Gr.1234xyZ.0003@domain
Subject: Chat: My Group: Hello, ...
Hello, I've removed member4@domain from our group. Now we have 3 members.
## Change group name
To change the group-name,
the messenger MUST send the action header `Chat-Group-Name-Changed`
with the value set to the old group name to all group members.
The new group name goes to the header `Chat-Group-Name`.
The messenger SHOULD send an explicit mail for each name change.
The body of the message SHOULD contain a localized description about what happened
and the message SHOULD appear as a message or action from the sender.
From: member1@domain
To: member2@domain, member3@domain
Chat-Version: 1.0
Chat-Group-ID: 1234xyZ
Chat-Group-Name: Our Group
Chat-Group-Name-Changed: My Group
Message-ID: Gr.1234xyZ.0004@domain
Subject: Chat: Our Group: Hello, ...
Hello, I've changed the group name from "My Group" to "Our Group".
## Set group image
A group MAY have a group-image.
To change or set the group-image, the messenger MUST attach an image file to a message and MUST add the header `Chat-Group-Image` with the
value set to the image name.
To remove the group-image, the messenger MUST add the header `Chat-Group-Image: 0`.
The messenger SHOULD send an explicit mail for each group image change.
The body of the message SHOULD contain a localized description about what happened
and the message SHOULD appear as a message or action from the sender.
From: member1@domain
To: member2@domain, member3@domain
Chat-Version: 1.0
Chat-Group-ID: 1234xyZ
Chat-Group-Name: Our Group
Chat-Group-Image: image.jpg
Message-ID: Gr.1234xyZ.0005@domain
Subject: Chat: Our Group: Hello, ...
Content-Type: multipart/mixed; boundary="==break=="
--==break==
Content-Type: text/plain
Hello, I've changed the group image.
--==break==
Content-Type: image/jpeg
Content-Disposition: attachment; filename="image.jpg"
/9j/4AAQSkZJRgABAQAAAQABAAD/2wBDAAYEBQYFBAYGBQYHBw ...
--==break==--
The image format SHOULD be image/jpeg or image/png. To save data, it is RECOMMENDED to add a `Chat-Group-Image` only on image changes.
# Set profile image
A user MAY have a profile-image that MAY be spread to his contacts.
To change or set the profile-image, the messenger MUST attach an image file to a message and MUST add the header `Chat-Profile-Image` with the
value set to the image name.
To remove the profile-image, the messenger MUST add the header `Chat-Profile-Image: 0`.
To spread the image, the messenger MAY send the profile image together with the next mail to a given contact
(to do this only once, the messenger has to keep a `profile_image_update_state` somewhere).
Alternatively, the messenger MAY send an explicit mail for each profile-image change to all contacts using a compatible messenger.
The messenger SHOULD NOT send an explicit mail to normal MUAs.
From: sender@domain
To: rcpt@domain
Chat-Version: 1.0
Chat-Profile-Image: photo.jpg
Subject: Chat: Hello, ...
Content-Type: multipart/mixed; boundary="==break=="
--==break==
Content-Type: text/plain
Hello, I've changed my profile image.
--==break==
Content-Type: image/jpeg
Content-Disposition: attachment; filename="photo.jpg"
AKCgkJi3j4l5kjoldfUAKCgkJi3j4lldfHjgWICwgIEBQYFBA ...
--==break==--
The image format SHOULD be image/jpeg or image/png. Note that `Chat-Profile-Image` may appear together with all other headers, eg. there may be a
`Chat-Profile-Image` and a `Chat-Group-Image` header in the same message. To save data, it is RECOMMENDED to add a `Chat-Profile-Image` header only on image changes.
# Miscellaneous
Messengers SHOULD use the header `Chat-Predecessor` instead of `In-Reply-To` as
the latter one results in infinite threads on typical MUAs.
Messengers SHOULD add a `Chat-Voice-message: 1` header if an attached audio file is a voice message.
Messengers MAY add a `Chat-Duration` header to specify the duration of attached audio or video files.
The value MUST be the duration in milliseconds.
This allows the receiver to show the time without knowing the file format.
Chat-Predecessor: foo123@domain
Chat-Voice-Message: 1
Chat-Duration: 10000
Messengers MAY send and receive Message Disposition Notifications (MDNs, [RFC 8098](https://tools.ietf.org/html/rfc8098), [RFC 3503](https://tools.ietf.org/html/rfc3503))
using the `Chat-Disposition-Notification-To` header instead of the `Disposition-Notification-To` (which unfortunately forces many other MUAs to send weird mails not following any
standard).
## Sync messages
If some action is required by a message header, the action should only be performed if the _effective date_ is newer than the date the last action was performed.
We define the effective date of a message as the sending time of the message as indicated by its Date header,
or the time of first receipt if that date is in the future or unavailable.
Copyright © 2017-2019 Delta Chat contributors.

View File

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

File diff suppressed because it is too large Load Diff

View File

@@ -31,17 +31,13 @@ use crate::stock::StockMessage;
/// first entry and only present on new messages, there is the rough idea that it can be optionally always
/// present and sorted into the list by date. Rendering the deaddrop in the described way
/// would not add extra work in the UI then.
pub struct Chatlist<'a> {
context: &'a Context,
#[derive(Debug)]
pub struct Chatlist {
/// Stores pairs of `chat_id, message_id`
ids: Vec<(u32, u32)>,
}
impl<'a> Chatlist<'a> {
pub fn get_context(&self) -> &Context {
self.context
}
impl Chatlist {
/// Get a list of chats.
/// The list can be filtered by query parameters.
///
@@ -85,7 +81,7 @@ impl<'a> Chatlist<'a> {
/// `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,
context: &Context,
listflags: usize,
query: Option<&str>,
query_contact_id: Option<u32>,
@@ -200,7 +196,7 @@ impl<'a> Chatlist<'a> {
ids.push((DC_CHAT_ID_ARCHIVED_LINK, 0));
}
Ok(Chatlist { context, ids })
Ok(Chatlist { ids })
}
/// Find out the number of chats.
@@ -247,7 +243,7 @@ impl<'a> Chatlist<'a> {
/// - 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 {
pub fn get_summary(&self, context: &Context, index: usize, chat: Option<&Chat>) -> 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".
@@ -263,7 +259,7 @@ impl<'a> Chatlist<'a> {
let chat = if let Some(chat) = chat {
chat
} else {
if let Ok(chat) = Chat::load_from_db(self.context, self.ids[index].0) {
if let Ok(chat) = Chat::load_from_db(context, self.ids[index].0) {
chat_loaded = chat;
&chat_loaded
} else {
@@ -275,11 +271,11 @@ impl<'a> Chatlist<'a> {
let mut lastcontact = None;
let lastmsg = if 0 != lastmsg_id {
if let Ok(lastmsg) = dc_msg_load_from_db(self.context, lastmsg_id) {
if let Ok(lastmsg) = dc_msg_load_from_db(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();
lastcontact = Contact::load_from_db(context, lastmsg.from_id).ok();
}
Some(lastmsg)
@@ -294,14 +290,9 @@ impl<'a> Chatlist<'a> {
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());
ret.text2 = Some(context.stock_str(StockMessage::NoMessages).to_string());
} else {
ret.fill(
&mut lastmsg.unwrap(),
chat,
lastcontact.as_ref(),
self.context,
);
ret.fill(&mut lastmsg.unwrap(), chat, lastcontact.as_ref(), context);
}
ret
@@ -311,11 +302,10 @@ impl<'a> Chatlist<'a> {
pub fn dc_get_archived_cnt(context: &Context) -> u32 {
context
.sql
.query_row_col(
.query_get_value(
context,
"SELECT COUNT(*) FROM chats WHERE blocked=0 AND archived=1;",
params![],
0,
)
.unwrap_or_default()
}
@@ -325,7 +315,7 @@ fn get_last_deaddrop_fresh_msg(context: &Context) -> u32 {
// only few fresh messages.
context
.sql
.query_row_col(
.query_get_value(
context,
"SELECT m.id FROM msgs m LEFT JOIN chats c ON c.id=m.chat_id \
WHERE m.state=10 \
@@ -333,7 +323,6 @@ fn get_last_deaddrop_fresh_msg(context: &Context) -> u32 {
AND c.blocked=2 \
ORDER BY m.timestamp DESC, m.id DESC;",
params![],
0,
)
.unwrap_or_default()
}

View File

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

View File

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

View File

@@ -1,9 +1,10 @@
use quick_xml;
use quick_xml::events::{BytesEnd, BytesStart, BytesText};
use crate::constants::*;
use crate::context::Context;
use crate::dc_loginparam::*;
use crate::dc_tools::*;
use crate::login_param::LoginParam;
use crate::x::*;
use std::ptr;
@@ -13,8 +14,8 @@ use super::read_autoconf_file;
******************************************************************************/
#[repr(C)]
struct outlk_autodiscover_t<'a> {
pub in_0: &'a dc_loginparam_t,
pub out: dc_loginparam_t,
pub in_0: &'a LoginParam,
pub out: LoginParam,
pub out_imap_set: libc::c_int,
pub out_smtp_set: libc::c_int,
pub tag_config: libc::c_int,
@@ -25,13 +26,13 @@ struct outlk_autodiscover_t<'a> {
pub unsafe fn outlk_autodiscover(
context: &Context,
url__: &str,
param_in: &dc_loginparam_t,
) -> Option<dc_loginparam_t> {
param_in: &LoginParam,
) -> Option<LoginParam> {
let mut xml_raw: *mut libc::c_char = ptr::null_mut();
let mut url = url__.strdup();
let mut outlk_ad = outlk_autodiscover_t {
in_0: param_in,
out: dc_loginparam_new(),
out: LoginParam::new(),
out_imap_set: 0,
out_smtp_set: 0,
tag_config: 0,
@@ -50,7 +51,7 @@ pub unsafe fn outlk_autodiscover(
0,
::std::mem::size_of::<outlk_autodiscover_t>(),
);
xml_raw = read_autoconf_file(context, url);
xml_raw = read_autoconf_file(context, as_str(url));
if xml_raw.is_null() {
ok_to_continue = false;
break;
@@ -75,7 +76,6 @@ pub unsafe fn outlk_autodiscover(
Err(e) => {
error!(
context,
0,
"Configure xml: Error at position {}: {:?}",
reader.buffer_position(),
e
@@ -108,8 +108,8 @@ pub unsafe fn outlk_autodiscover(
|| outlk_ad.out.send_server.is_empty()
|| outlk_ad.out.send_port == 0
{
let r = dc_loginparam_get_readable(&outlk_ad.out);
warn!(context, 0, "Bad or incomplete autoconfig: {}", r,);
let r = outlk_ad.out.to_string();
warn!(context, "Bad or incomplete autoconfig: {}", r,);
free(url as *mut libc::c_void);
free(xml_raw as *mut libc::c_void);
outlk_clean_config(&mut outlk_ad);
@@ -168,9 +168,9 @@ unsafe fn outlk_autodiscover_endtag_cb(event: &BytesEnd, outlk_ad: &mut outlk_au
outlk_ad.out.mail_server = to_string(outlk_ad.config[2]);
outlk_ad.out.mail_port = port;
if 0 != ssl_on {
outlk_ad.out.server_flags |= 0x200
outlk_ad.out.server_flags |= DC_LP_IMAP_SOCKET_SSL as i32
} else if 0 != ssl_off {
outlk_ad.out.server_flags |= 0x400
outlk_ad.out.server_flags |= DC_LP_IMAP_SOCKET_PLAIN as i32
}
outlk_ad.out_imap_set = 1
} else if strcasecmp(
@@ -182,9 +182,9 @@ unsafe fn outlk_autodiscover_endtag_cb(event: &BytesEnd, outlk_ad: &mut outlk_au
outlk_ad.out.send_server = to_string(outlk_ad.config[2]);
outlk_ad.out.send_port = port;
if 0 != ssl_on {
outlk_ad.out.server_flags |= 0x20000
outlk_ad.out.server_flags |= DC_LP_SMTP_SOCKET_SSL as i32
} else if 0 != ssl_off {
outlk_ad.out.server_flags |= 0x40000
outlk_ad.out.server_flags |= DC_LP_SMTP_SOCKET_PLAIN as i32
}
outlk_ad.out_smtp_set = 1
}

View File

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

View File

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

View File

@@ -1,24 +1,23 @@
use std::path::PathBuf;
use deltachat_derive::*;
use itertools::Itertools;
use num_traits::{FromPrimitive, ToPrimitive};
use rusqlite;
use rusqlite::types::*;
use crate::aheader::EncryptPreference;
use crate::config::Config;
use crate::constants::*;
use crate::context::Context;
use crate::dc_loginparam::*;
use crate::dc_tools::*;
use crate::e2ee;
use crate::error::Result;
use crate::events::Event;
use crate::key::*;
use crate::login_param::LoginParam;
use crate::message::MessageState;
use crate::peerstate::*;
use crate::sql;
use crate::stock::StockMessage;
use crate::types::*;
const DC_GCL_VERIFIED_ONLY: u32 = 0x01;
/// Contacts with at least this origin value are shown in the contact list.
const DC_ORIGIN_MIN_CONTACT_LIST: i32 = 0x100;
@@ -33,8 +32,8 @@ const DC_ORIGIN_MIN_CONTACT_LIST: i32 = 0x100;
/// For this purpose, internally, two names are tracked -
/// authorized-name and given-name.
/// By default, these names are equal, but functions working with contact names
pub struct Contact<'a> {
context: &'a Context,
#[derive(Debug)]
pub struct Contact {
/// The contact ID.
///
/// Special message IDs:
@@ -60,7 +59,9 @@ pub struct Contact<'a> {
}
/// Possible origins of a contact.
#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, FromPrimitive, ToPrimitive)]
#[derive(
Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, FromPrimitive, ToPrimitive, FromSql, ToSql,
)]
#[repr(i32)]
pub enum Origin {
Unknown = 0,
@@ -98,20 +99,9 @@ pub enum Origin {
ManuallyCreated = 0x4000000,
}
impl ToSql for Origin {
fn to_sql(&self) -> rusqlite::Result<ToSqlOutput> {
let num: i64 = self
.to_i64()
.expect("impossible: Origin -> i64 conversion failed");
Ok(ToSqlOutput::Owned(Value::Integer(num)))
}
}
impl FromSql for Origin {
fn column_result(col: ValueRef) -> FromSqlResult<Self> {
let inner = FromSql::column_result(col)?;
FromPrimitive::from_i64(inner).ok_or(FromSqlError::InvalidType)
impl Default for Origin {
fn default() -> Self {
Origin::Unknown
}
}
@@ -150,11 +140,10 @@ pub enum VerifiedStatus {
BidirectVerified = 2,
}
impl<'a> Contact<'a> {
pub fn load_from_db(context: &'a Context, contact_id: u32) -> Result<Self> {
impl Contact {
pub fn load_from_db(context: &Context, contact_id: u32) -> Result<Self> {
if contact_id == DC_CONTACT_ID_SELF {
let contact = Contact {
context,
id: contact_id,
name: context.stock_str(StockMessage::SelfMsg).into(),
authname: "".into(),
@@ -173,7 +162,6 @@ impl<'a> Contact<'a> {
params![contact_id as i32],
|row| {
let contact = Self {
context,
id: contact_id,
name: row.get::<_, String>(0)?,
authname: row.get::<_, String>(4)?,
@@ -192,7 +180,7 @@ impl<'a> Contact<'a> {
}
/// Check if a contact is blocked.
pub fn is_blocked_load(context: &'a Context, id: u32) -> bool {
pub fn is_blocked_load(context: &Context, id: u32) -> bool {
Self::load_from_db(context, id)
.map(|contact| contact.blocked)
.unwrap_or_default()
@@ -226,15 +214,13 @@ impl<'a> Contact<'a> {
let (contact_id, sth_modified) =
Contact::add_or_lookup(context, name, addr, Origin::ManuallyCreated)?;
let blocked = Contact::is_blocked_load(context, contact_id);
context.call_cb(
Event::CONTACTS_CHANGED,
(if sth_modified == Modifier::Created {
contact_id
context.call_cb(Event::ContactsChanged(
if sth_modified == Modifier::Created {
Some(contact_id)
} else {
0
}) as uintptr_t,
0 as uintptr_t,
);
None
},
));
if blocked {
Contact::unblock(context, contact_id);
}
@@ -255,7 +241,10 @@ impl<'a> Contact<'a> {
)
.is_ok()
{
context.call_cb(Event::MSGS_CHANGED, 0, 0);
context.call_cb(Event::MsgsChanged {
chat_id: 0,
msg_id: 0,
});
}
}
@@ -278,7 +267,7 @@ impl<'a> Contact<'a> {
return 1;
}
context.sql.query_row_col(
context.sql.query_get_value(
context,
"SELECT id FROM contacts WHERE addr=?1 COLLATE NOCASE AND id>?2 AND origin>=?3 AND blocked=0;",
params![
@@ -286,7 +275,6 @@ impl<'a> Contact<'a> {
DC_CONTACT_ID_LAST_SPECIAL as i32,
DC_ORIGIN_MIN_CONTACT_LIST,
],
0
).unwrap_or_default()
}
@@ -319,7 +307,6 @@ impl<'a> Contact<'a> {
if !may_be_valid_addr(&addr) {
warn!(
context,
0,
"Bad address \"{}\" for contact \"{}\".",
addr,
if !name.as_ref().is_empty() {
@@ -343,19 +330,22 @@ impl<'a> Contact<'a> {
let row_id = row.get(0)?;
let row_name: String = row.get(1)?;
let row_addr: String = row.get(2)?;
let row_origin = row.get(3)?;
let row_origin: Origin = row.get(3)?;
let row_authname: String = row.get(4)?;
if !name.as_ref().is_empty() && !row_name.is_empty() {
if origin >= row_origin && name.as_ref() != row_name {
if !name.as_ref().is_empty() {
if !row_name.is_empty() {
if origin >= row_origin && name.as_ref() != row_name {
update_name = true;
}
} else {
update_name = true;
}
} else {
update_name = true;
}
if origin == Origin::IncomingUnknownFrom && name.as_ref() != row_authname {
update_authname = true;
if origin == Origin::IncomingUnknownFrom && name.as_ref() != row_authname {
update_authname = true;
}
}
Ok((row_id, row_name, row_addr, row_origin, row_authname))
},
) {
@@ -395,7 +385,7 @@ impl<'a> Contact<'a> {
context,
&context.sql,
"UPDATE chats SET name=? WHERE type=? AND id IN(SELECT chat_id FROM chats_contacts WHERE contact_id=?);",
params![name.as_ref(), 100, row_id]
params![name.as_ref(), Chattype::Single, row_id]
).ok();
}
sth_modified = Modifier::Modified;
@@ -412,7 +402,7 @@ impl<'a> Contact<'a> {
row_id = sql::get_rowid(context, &context.sql, "contacts", "addr", addr);
sth_modified = Modifier::Created;
} else {
error!(context, 0, "Cannot add contact.");
error!(context, "Cannot add contact.");
}
}
@@ -433,19 +423,13 @@ impl<'a> Contact<'a> {
/// To add a single contact entered by the user, you should prefer `Contact::create`,
/// however, for adding a bunch of addresses, this function is _much_ faster.
///
/// The `adr_book` is a multiline string in the format `Name one\nAddress one\nName two\nAddress two`.
/// The `addr_book` is a multiline string in the format `Name one\nAddress one\nName two\nAddress two`.
///
/// Returns the number of modified contacts.
pub fn add_address_book(context: &Context, adr_book: impl AsRef<str>) -> Result<usize> {
pub fn add_address_book(context: &Context, addr_book: impl AsRef<str>) -> Result<usize> {
let mut modify_cnt = 0;
for chunk in &adr_book.as_ref().lines().chunks(2) {
let chunk = chunk.collect::<Vec<_>>();
if chunk.len() < 2 {
break;
}
let name = chunk[0];
let addr = chunk[1];
for (name, addr) in split_address_book(addr_book.as_ref()).into_iter() {
let name = normalize_name(name);
let (_, modified) = Contact::add_or_lookup(context, name, addr, Origin::AdressBook)?;
if modified != Modifier::None {
@@ -453,7 +437,7 @@ impl<'a> Contact<'a> {
}
}
if modify_cnt > 0 {
context.call_cb(Event::CONTACTS_CHANGED, 0 as uintptr_t, 0 as uintptr_t);
context.call_cb(Event::ContactsChanged(None));
}
Ok(modify_cnt)
@@ -479,8 +463,10 @@ impl<'a> Contact<'a> {
let mut add_self = false;
let mut ret = Vec::new();
let flag_verified_only = listflags_has(listflags, DC_GCL_VERIFIED_ONLY);
let flag_add_self = listflags_has(listflags, DC_GCL_ADD_SELF);
if (listflags & DC_GCL_VERIFIED_ONLY) > 0 || query.is_some() {
if flag_verified_only || query.is_some() {
let s3str_like_cmd = format!(
"%{}%",
query
@@ -504,7 +490,7 @@ impl<'a> Contact<'a> {
0x100,
&s3str_like_cmd,
&s3str_like_cmd,
if 0 != listflags & 0x1 { 0 } else { 1 },
if flag_verified_only { 0 } else { 1 },
],
|row| row.get::<_, i32>(0),
|ids| {
@@ -544,7 +530,7 @@ impl<'a> Contact<'a> {
)?;
}
if 0 != listflags & DC_GCL_ADD_SELF as u32 && add_self {
if flag_add_self && add_self {
ret.push(DC_CONTACT_ID_SELF);
}
@@ -554,11 +540,10 @@ impl<'a> Contact<'a> {
pub fn get_blocked_cnt(context: &Context) -> usize {
context
.sql
.query_row_col::<_, isize>(
.query_get_value::<_, isize>(
context,
"SELECT COUNT(*) FROM contacts WHERE id>? AND blocked!=0",
params![DC_CONTACT_ID_LAST_SPECIAL as i32],
0,
)
.unwrap_or_default() as usize
}
@@ -589,7 +574,7 @@ impl<'a> Contact<'a> {
if let Ok(contact) = Contact::load_from_db(context, contact_id) {
let peerstate = Peerstate::from_addr(context, &context.sql, &contact.addr);
let loginparam = dc_loginparam_read(context, &context.sql, "configured_");
let loginparam = LoginParam::from_database(context, "configured_");
let mut self_key = Key::from_self_public(context, &loginparam.addr, &context.sql);
@@ -661,22 +646,20 @@ impl<'a> Contact<'a> {
let count_contacts: i32 = context
.sql
.query_row_col(
.query_get_value(
context,
"SELECT COUNT(*) FROM chats_contacts WHERE contact_id=?;",
params![contact_id as i32],
0,
)
.unwrap_or_default();
let count_msgs: i32 = if count_contacts > 0 {
context
.sql
.query_row_col(
.query_get_value(
context,
"SELECT COUNT(*) FROM msgs WHERE from_id=? OR to_id=?;",
params![contact_id as i32, contact_id as i32],
0,
)
.unwrap_or_default()
} else {
@@ -691,11 +674,11 @@ impl<'a> Contact<'a> {
params![contact_id as i32],
) {
Ok(_) => {
context.call_cb(Event::CONTACTS_CHANGED, 0, 0);
context.call_cb(Event::ContactsChanged(None));
return Ok(());
}
Err(err) => {
error!(context, 0, "delete_contact {} failed ({})", contact_id, err);
error!(context, "delete_contact {} failed ({})", contact_id, err);
return Err(err);
}
}
@@ -703,7 +686,7 @@ impl<'a> Contact<'a> {
info!(
context,
0, "could not delete contact {}, there are {} messages with it", contact_id, count_msgs
"could not delete contact {}, there are {} messages with it", contact_id, count_msgs
);
bail!("Could not delete contact with messages in it");
}
@@ -779,9 +762,11 @@ impl<'a> Contact<'a> {
/// Get the contact's profile image.
/// This is the image set by each remote user on their own
/// using dc_set_config(context, "selfavatar", image).
pub fn get_profile_image(&self) -> Option<String> {
pub fn get_profile_image(&self, context: &Context) -> Option<PathBuf> {
if self.id == DC_CONTACT_ID_SELF {
return self.context.get_config(Config::Selfavatar);
if let Some(p) = context.get_config(Config::Selfavatar) {
return Some(PathBuf::from(p));
}
}
// TODO: else get image_abs from contact param
None
@@ -800,14 +785,18 @@ impl<'a> Contact<'a> {
///
/// The UI may draw a checkbox or something like that beside verified contacts.
///
pub fn is_verified(&self) -> VerifiedStatus {
self.is_verified_ex(None)
pub fn is_verified(&self, context: &Context) -> VerifiedStatus {
self.is_verified_ex(context, None)
}
/// Same as `Contact::is_verified` but allows speeding up things
/// by adding the peerstate belonging to the contact.
/// If you do not have the peerstate available, it is loaded automatically.
pub fn is_verified_ex(&self, peerstate: Option<&Peerstate<'a>>) -> VerifiedStatus {
pub fn is_verified_ex(
&self,
context: &Context,
peerstate: Option<&Peerstate>,
) -> VerifiedStatus {
// We're always sort of secured-verified as we could verify the key on this device any time with the key
// on this device
if self.id == DC_CONTACT_ID_SELF {
@@ -820,7 +809,7 @@ impl<'a> Contact<'a> {
}
}
let peerstate = Peerstate::from_addr(self.context, &self.context.sql, &self.addr);
let peerstate = Peerstate::from_addr(context, &context.sql, &self.addr);
if let Some(ps) = peerstate {
if ps.verified_key().is_some() {
return VerifiedStatus::BidirectVerified;
@@ -854,11 +843,10 @@ impl<'a> Contact<'a> {
context
.sql
.query_row_col::<_, isize>(
.query_get_value::<_, isize>(
context,
"SELECT COUNT(*) FROM contacts WHERE id>?;",
params![DC_CONTACT_ID_LAST_SPECIAL as i32],
0,
)
.unwrap_or_default() as usize
}
@@ -951,11 +939,7 @@ fn set_block_contact(context: &Context, contact_id: u32, new_blocking: bool) {
params![new_blocking, 100, contact_id as i32],
).is_ok() {
Contact::mark_noticed(context, contact_id);
context.call_cb(
Event::CONTACTS_CHANGED,
0,
0,
);
context.call_cb(Event::ContactsChanged(None));
}
}
}
@@ -1043,6 +1027,21 @@ pub fn addr_equals_self(context: &Context, addr: impl AsRef<str>) -> bool {
false
}
fn split_address_book(book: &str) -> Vec<(&str, &str)> {
book.lines()
.chunks(2)
.into_iter()
.filter_map(|mut chunk| {
let name = chunk.next().unwrap();
let addr = match chunk.next() {
Some(a) => a,
None => return None,
};
Some((name, addr))
})
.collect()
}
#[cfg(test)]
mod tests {
use super::*;
@@ -1078,4 +1077,14 @@ mod tests {
fn test_get_first_name() {
assert_eq!(get_first_name("John Doe"), "John");
}
#[test]
fn test_split_address_book() {
let book = "Name one\nAddress one\nName two\nAddress two\nrest name";
let list = split_address_book(&book);
assert_eq!(
list,
vec![("Name one", "Address one"), ("Name two", "Address two")]
)
}
}

File diff suppressed because it is too large Load Diff

View File

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

View File

@@ -165,3 +165,28 @@ fn dehtml_starttag_cb<B: std::io::BufRead>(
_ => {}
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_dc_dehtml() {
let cases = vec![
(
"<a href='https://example.com'> Foo </a>",
"[ Foo ](https://example.com)",
),
("<img href='/foo.png'>", ""),
("<b> bar </b>", "* bar *"),
("<b> bar <i> foo", "* bar _ foo"),
("&amp; bar", "& bar"),
// Note missing '
("<a href='/foo.png>Hi</a> ", ""),
("", ""),
];
for (input, output) in cases {
assert_eq!(dc_dehtml(input), output);
}
}
}

View File

@@ -1,4 +1,5 @@
use std::ffi::CString;
use std::path::Path;
use std::ptr;
use mmime::mailmime_content::*;
@@ -14,6 +15,7 @@ use crate::context::Context;
use crate::dc_tools::*;
use crate::e2ee;
use crate::error::*;
use crate::events::Event;
use crate::job::*;
use crate::key::*;
use crate::message::*;
@@ -21,7 +23,6 @@ use crate::param::*;
use crate::pgp::*;
use crate::sql::{self, Sql};
use crate::stock::StockMessage;
use crate::types::*;
use crate::x::*;
// import/export and tools
@@ -29,16 +30,16 @@ use crate::x::*;
// param1 is a directory where the keys are searched in and read from
// param1 is a directory where the backup is written to
// param1 is the file with the backup to import
pub unsafe fn dc_imex(
pub fn dc_imex(
context: &Context,
what: libc::c_int,
param1: *const libc::c_char,
param1: Option<impl AsRef<Path>>,
param2: *const libc::c_char,
) {
let mut param = Params::new();
param.set_int(Param::Cmd, what as i32);
if !param1.is_null() {
param.set(Param::Arg, as_str(param1));
if let Some(param1) = param1 {
param.set(Param::Arg, param1.as_ref().to_string_lossy());
}
if !param2.is_null() {
param.set(Param::Arg2, as_str(param2));
@@ -51,14 +52,13 @@ pub unsafe fn dc_imex(
/// Returns the filename of the backup if found, nullptr otherwise.
pub unsafe fn dc_imex_has_backup(
context: &Context,
dir_name: *const libc::c_char,
dir_name: impl AsRef<Path>,
) -> *mut libc::c_char {
let dir_name = as_path(dir_name);
let dir_name = dir_name.as_ref();
let dir_iter = std::fs::read_dir(dir_name);
if dir_iter.is_err() {
info!(
context,
0,
"Backup check: Cannot open directory \"{}\".\x00",
dir_name.display(),
);
@@ -92,7 +92,7 @@ pub unsafe fn dc_imex_has_backup(
Some(path) => match path.to_c_string() {
Ok(cstr) => dc_strdup(cstr.as_ptr()),
Err(err) => {
error!(context, 0, "Invalid backup filename: {}", err);
error!(context, "Invalid backup filename: {}", err);
std::ptr::null_mut()
}
},
@@ -101,9 +101,8 @@ pub unsafe fn dc_imex_has_backup(
}
pub unsafe fn dc_initiate_key_transfer(context: &Context) -> *mut libc::c_char {
let mut setup_file_name: *mut libc::c_char = ptr::null_mut();
let mut msg: Message;
if dc_alloc_ongoing(context) == 0 {
if !dc_alloc_ongoing(context) {
return std::ptr::null_mut();
}
let setup_code = dc_create_setup_code(context);
@@ -115,8 +114,7 @@ pub unsafe fn dc_initiate_key_transfer(context: &Context) -> *mut libc::c_char {
.unwrap()
.shall_stop_ongoing
{
if let Ok(setup_file_content) = dc_render_setup_file(context, &setup_code) {
let setup_file_content_c = CString::yolo(setup_file_content.as_str());
if let Ok(ref setup_file_content) = dc_render_setup_file(context, &setup_code) {
/* encrypting may also take a while ... */
if !context
.running_state
@@ -125,23 +123,14 @@ pub unsafe fn dc_initiate_key_transfer(context: &Context) -> *mut libc::c_char {
.unwrap()
.shall_stop_ongoing
{
setup_file_name = dc_get_fine_pathNfilename(
context,
b"$BLOBDIR\x00" as *const u8 as *const libc::c_char,
b"autocrypt-setup-message.html\x00" as *const u8 as *const libc::c_char,
);
if !(setup_file_name.is_null()
|| 0 == dc_write_file(
context,
setup_file_name,
setup_file_content_c.as_ptr() as *const libc::c_void,
setup_file_content_c.as_bytes().len(),
))
{
let setup_file_name =
dc_get_fine_path_filename(context, "$BLOBDIR", "autocrypt-setup-message.html");
if dc_write_file(context, &setup_file_name, setup_file_content.as_bytes()) {
if let Ok(chat_id) = chat::create_by_contact_id(context, 1) {
msg = dc_msg_new_untyped(context);
msg = dc_msg_new_untyped();
msg.type_0 = Viewtype::File;
msg.param.set(Param::File, as_str(setup_file_name));
msg.param
.set(Param::File, setup_file_name.to_string_lossy());
msg.param
.set(Param::MimeType, "application/autocrypt-setup");
@@ -156,7 +145,7 @@ pub unsafe fn dc_initiate_key_transfer(context: &Context) -> *mut libc::c_char {
.shall_stop_ongoing
{
if let Ok(msg_id) = chat::send_msg(context, chat_id, &mut msg) {
info!(context, 0, "Wait for setup message being sent ...",);
info!(context, "Wait for setup message being sent ...",);
loop {
if context
.running_state
@@ -169,8 +158,8 @@ pub unsafe fn dc_initiate_key_transfer(context: &Context) -> *mut libc::c_char {
}
std::thread::sleep(std::time::Duration::from_secs(1));
if let Ok(msg) = dc_get_msg(context, msg_id) {
if 0 != dc_msg_is_sent(&msg) {
info!(context, 0, "... setup message sent.",);
if dc_msg_is_sent(&msg) {
info!(context, "... setup message sent.",);
break;
}
}
@@ -182,7 +171,6 @@ pub unsafe fn dc_initiate_key_transfer(context: &Context) -> *mut libc::c_char {
}
}
}
free(setup_file_name as *mut libc::c_void);
dc_free_ongoing(context);
setup_code.strdup()
@@ -208,16 +196,8 @@ pub fn dc_render_setup_file(context: &Context, passphrase: &str) -> Result<Strin
_ => Some(("Autocrypt-Prefer-Encrypt", "mutual")),
};
let private_key_asc = private_key.to_asc(ac_headers);
let encr = {
let private_key_asc_c = CString::yolo(private_key_asc);
let passphrase_c = CString::yolo(passphrase);
dc_pgp_symm_encrypt(
passphrase_c.as_ptr(),
private_key_asc_c.as_ptr() as *const libc::c_void,
private_key_asc_c.as_bytes().len(),
)
.ok_or(format_err!("Failed to encrypt private key."))?
};
let encr = dc_pgp_symm_encrypt(&passphrase, private_key_asc.as_bytes())
.ok_or(format_err!("Failed to encrypt private key."))?;
let replacement = format!(
concat!(
"-----BEGIN PGP MESSAGE-----\r\n",
@@ -250,7 +230,7 @@ pub fn dc_render_setup_file(context: &Context, passphrase: &str) -> Result<Strin
}
pub fn dc_create_setup_code(_context: &Context) -> String {
let mut random_val: uint16_t;
let mut random_val: u16;
let mut rng = thread_rng();
let mut ret = String::new();
@@ -261,7 +241,7 @@ pub fn dc_create_setup_code(_context: &Context) -> String {
break;
}
}
random_val = (random_val as libc::c_int % 10000) as uint16_t;
random_val = (random_val as libc::c_int % 10000) as u16;
ret += &format!(
"{}{:04}",
if 0 != i { "-" } else { "" },
@@ -272,59 +252,54 @@ pub fn dc_create_setup_code(_context: &Context) -> String {
ret
}
// TODO should return bool /rtn
pub unsafe fn dc_continue_key_transfer(
context: &Context,
msg_id: uint32_t,
msg_id: u32,
setup_code: *const libc::c_char,
) -> libc::c_int {
let mut success: libc::c_int = 0i32;
let mut filename: *mut libc::c_char = ptr::null_mut();
let mut filecontent: *mut libc::c_char = ptr::null_mut();
let mut filebytes: size_t = 0i32 as size_t;
) -> bool {
let mut success = false;
let mut armored_key: *mut libc::c_char = ptr::null_mut();
let mut norm_sc: *mut libc::c_char = ptr::null_mut();
if !(msg_id <= 9i32 as libc::c_uint || setup_code.is_null()) {
let msg = dc_get_msg(context, msg_id);
if msg.is_err()
|| !dc_msg_is_setupmessage(msg.as_ref().unwrap())
|| {
filename = dc_msg_get_file(msg.as_ref().unwrap());
filename.is_null()
}
|| *filename.offset(0isize) as libc::c_int == 0i32
{
error!(context, 0, "Message is no Autocrypt Setup Message.",);
} else if 0
== dc_read_file(
context,
filename,
&mut filecontent as *mut *mut libc::c_char as *mut *mut libc::c_void,
&mut filebytes,
)
|| filecontent.is_null()
|| filebytes <= 0
{
error!(context, 0, "Cannot read Autocrypt Setup Message file.",);
} else {
let norm_sc;
if msg_id <= 9i32 as libc::c_uint || setup_code.is_null() {
return false;
}
let msg = dc_get_msg(context, msg_id);
if msg.is_err() {
error!(context, "Message is no Autocrypt Setup Message.");
return false;
}
let msg = msg.unwrap();
if !dc_msg_is_setupmessage(&msg) {
error!(context, "Message is no Autocrypt Setup Message.");
return false;
}
if let Some(filename) = dc_msg_get_file(context, &msg) {
if let Some(buf) = dc_read_file_safe(context, filename) {
norm_sc = dc_normalize_setup_code(context, setup_code);
if norm_sc.is_null() {
warn!(context, 0, "Cannot normalize Setup Code.",);
warn!(context, "Cannot normalize Setup Code.",);
} else {
armored_key = dc_decrypt_setup_file(context, norm_sc, filecontent);
armored_key = dc_decrypt_setup_file(context, norm_sc, buf.as_ptr().cast());
if armored_key.is_null() {
warn!(context, 0, "Cannot decrypt Autocrypt Setup Message.",);
warn!(context, "Cannot decrypt Autocrypt Setup Message.",);
} else if set_self_key(context, armored_key, 1) {
/*set default*/
/* error already logged */
success = 1i32
success = true
}
}
} else {
error!(context, "Cannot read Autocrypt Setup Message file.",);
return false;
}
} else {
error!(context, "Message is no Autocrypt Setup Message.");
return false;
}
free(armored_key as *mut libc::c_void);
free(filecontent as *mut libc::c_void);
free(filename as *mut libc::c_void);
free(norm_sc as *mut libc::c_void);
success
@@ -343,7 +318,7 @@ fn set_self_key(
.and_then(|(k, h)| k.split_key().map(|pub_key| (k, pub_key, h)));
if keys.is_none() {
error!(context, 0, "File does not contain a valid private key.",);
error!(context, "File does not contain a valid private key.",);
return false;
}
@@ -373,13 +348,13 @@ fn set_self_key(
return false;
}
} else {
error!(context, 0, "File does not contain a private key.",);
error!(context, "File does not contain a private key.",);
}
let self_addr = context.sql.get_config(context, "configured_addr");
let self_addr = context.get_config(Config::ConfiguredAddr);
if self_addr.is_none() {
error!(context, 0, "Missing self addr");
error!(context, "Missing self addr");
return false;
}
@@ -391,7 +366,7 @@ fn set_self_key(
set_default,
&context.sql,
) {
error!(context, 0, "Cannot save keypair.");
error!(context, "Cannot save keypair.");
return false;
}
@@ -418,8 +393,8 @@ pub unsafe fn dc_decrypt_setup_file(
let mut fc_headerline: *const libc::c_char = ptr::null();
let mut fc_base64: *const libc::c_char = ptr::null();
let mut binary: *mut libc::c_char = ptr::null_mut();
let mut binary_bytes: size_t = 0i32 as size_t;
let mut indx: size_t = 0i32 as size_t;
let mut binary_bytes: libc::size_t = 0;
let mut indx: libc::size_t = 0;
let mut payload: *mut libc::c_char = ptr::null_mut();
fc_buf = dc_strdup(filecontent);
@@ -449,9 +424,10 @@ pub unsafe fn dc_decrypt_setup_file(
|| binary_bytes == 0)
{
/* decrypt symmetrically */
if let Some(plain) =
dc_pgp_symm_decrypt(passphrase, binary as *const libc::c_void, binary_bytes)
{
if let Some(plain) = dc_pgp_symm_decrypt(
as_str(passphrase),
std::slice::from_raw_parts(binary as *const u8, binary_bytes),
) {
let payload_c = CString::new(plain).unwrap();
payload = strdup(payload_c.as_ptr());
}
@@ -502,30 +478,27 @@ pub unsafe fn dc_normalize_setup_code(
pub unsafe fn dc_job_do_DC_JOB_IMEX_IMAP(context: &Context, job: &Job) {
let mut ok_to_continue = true;
let mut success: libc::c_int = 0;
let mut ongoing_allocated_here: libc::c_int = 0;
let what: libc::c_int;
if !(0 == dc_alloc_ongoing(context)) {
ongoing_allocated_here = 1;
what = job.param.get_int(Param::Cmd).unwrap_or_default();
if dc_alloc_ongoing(context) {
let what = job.param.get_int(Param::Cmd).unwrap_or_default();
let param1_s = job.param.get(Param::Arg).unwrap_or_default();
let param1 = CString::yolo(param1_s);
let _param2 = CString::yolo(job.param.get(Param::Arg2).unwrap_or_default());
if strlen(param1.as_ptr()) == 0 {
error!(context, 0, "No Import/export dir/file given.",);
error!(context, "No Import/export dir/file given.",);
} else {
info!(context, 0, "Import/export process started.",);
context.call_cb(Event::IMEX_PROGRESS, 10 as uintptr_t, 0 as uintptr_t);
info!(context, "Import/export process started.",);
context.call_cb(Event::ImexProgress(10));
if !context.sql.is_open() {
error!(context, 0, "Import/export: Database not opened.",);
error!(context, "Import/export: Database not opened.",);
} else {
if what == 1 || what == 11 {
/* before we export anything, make sure the private key exists */
if e2ee::ensure_secret_key_exists(context).is_err() {
error!(
context,
0,
"Import/export: Cannot create private key or private key not available.",
);
ok_to_continue = false;
@@ -536,26 +509,26 @@ pub unsafe fn dc_job_do_DC_JOB_IMEX_IMAP(context: &Context, job: &Job) {
if ok_to_continue {
match what {
1 => {
if 0 != export_self_keys(context, param1.as_ptr()) {
info!(context, 0, "Import/export completed.",);
if export_self_keys(context, param1.as_ptr()) {
info!(context, "Import/export completed.",);
success = 1
}
}
2 => {
if 0 != import_self_keys(context, param1.as_ptr()) {
info!(context, 0, "Import/export completed.",);
info!(context, "Import/export completed.",);
success = 1
}
}
11 => {
if 0 != export_backup(context, param1.as_ptr()) {
info!(context, 0, "Import/export completed.",);
if export_backup(context, param1.as_ptr()) {
info!(context, "Import/export completed.",);
success = 1
}
}
12 => {
if 0 != import_backup(context, param1.as_ptr()) {
info!(context, 0, "Import/export completed.",);
if import_backup(context, param1.as_ptr()) {
info!(context, "Import/export completed.",);
success = 1
}
}
@@ -564,73 +537,54 @@ pub unsafe fn dc_job_do_DC_JOB_IMEX_IMAP(context: &Context, job: &Job) {
}
}
}
}
if 0 != ongoing_allocated_here {
dc_free_ongoing(context);
}
context.call_cb(
Event::IMEX_PROGRESS,
(if 0 != success { 1000 } else { 0 }) as uintptr_t,
0 as uintptr_t,
);
context.call_cb(Event::ImexProgress(if 0 != success { 1000 } else { 0 }));
}
/*******************************************************************************
* Import backup
******************************************************************************/
// TODO should return bool /rtn
#[allow(non_snake_case)]
unsafe fn import_backup(context: &Context, backup_to_import: *const libc::c_char) -> libc::c_int {
unsafe fn import_backup(context: &Context, backup_to_import: *const libc::c_char) -> bool {
info!(
context,
0,
"Import \"{}\" to \"{}\".",
as_str(backup_to_import),
context
.get_dbfile()
.as_ref()
.map_or("<<None>>", |p| p.to_str().unwrap())
context.get_dbfile().display()
);
if 0 != dc_is_configured(context) {
error!(context, 0, "Cannot import backups to accounts in use.");
return 0;
if dc_is_configured(context) {
error!(context, "Cannot import backups to accounts in use.");
return false;
}
&context.sql.close(&context);
dc_delete_file(context, context.get_dbfile().unwrap());
if dc_file_exist(context, context.get_dbfile().unwrap()) {
dc_delete_file(context, context.get_dbfile());
if dc_file_exist(context, context.get_dbfile()) {
error!(
context,
0, "Cannot import backups: Cannot delete the old file.",
"Cannot import backups: Cannot delete the old file.",
);
return 0;
return false;
}
if !dc_copy_file(
context,
as_path(backup_to_import),
context.get_dbfile().unwrap(),
) {
return 0;
if !dc_copy_file(context, as_path(backup_to_import), context.get_dbfile()) {
return false;
}
/* error already logged */
/* re-open copied database file */
if !context
.sql
.open(&context, &context.get_dbfile().unwrap(), 0)
{
return 0;
if !context.sql.open(&context, &context.get_dbfile(), 0) {
return false;
}
let total_files_cnt = context
.sql
.query_row_col::<_, isize>(context, "SELECT COUNT(*) FROM backup_blobs;", params![], 0)
.query_get_value::<_, isize>(context, "SELECT COUNT(*) FROM backup_blobs;", params![])
.unwrap_or_default() as usize;
info!(
context,
0, "***IMPORT-in-progress: total_files_cnt={:?}", total_files_cnt,
"***IMPORT-in-progress: total_files_cnt={:?}", total_files_cnt,
);
let res = context.sql.query_map(
@@ -667,20 +621,19 @@ unsafe fn import_backup(context: &Context, backup_to_import: *const libc::c_char
if permille > 990 {
permille = 990
}
context.call_cb(Event::IMEX_PROGRESS, permille as uintptr_t, 0);
context.call_cb(Event::ImexProgress(permille));
if file_blob.is_empty() {
continue;
}
let pathNfilename = format!("{}/{}", as_str(context.get_blobdir()), file_name);
if dc_write_file_safe(context, &pathNfilename, &file_blob) {
let pathNfilename = context.get_blobdir().join(file_name);
if dc_write_file(context, &pathNfilename, &file_blob) {
continue;
}
error!(
context,
0,
"Storage full? Cannot write file {} with {} bytes.",
&pathNfilename,
pathNfilename.display(),
file_blob.len(),
);
// otherwise the user may believe the stuff is imported correctly, but there are files missing ...
@@ -701,7 +654,7 @@ unsafe fn import_backup(context: &Context, backup_to_import: *const libc::c_char
sql::try_execute(context, &context.sql, "VACUUM;").ok();
Ok(())
})
.is_ok() as libc::c_int
.is_ok()
}
/*******************************************************************************
@@ -709,11 +662,10 @@ unsafe fn import_backup(context: &Context, backup_to_import: *const libc::c_char
******************************************************************************/
/* the FILE_PROGRESS macro calls the callback with the permille of files processed.
The macro avoids weird values of 0% or 100% while still working. */
// TODO should return bool /rtn
#[allow(non_snake_case)]
unsafe fn export_backup(context: &Context, dir: *const libc::c_char) -> libc::c_int {
unsafe fn export_backup(context: &Context, dir: *const libc::c_char) -> bool {
let mut ok_to_continue: bool;
let mut success: libc::c_int = 0;
let mut success = false;
let mut delete_dest_file: libc::c_int = 0;
// get a fine backup file name (the name includes the date so that multiple backup instances are possible)
@@ -722,13 +674,8 @@ unsafe fn export_backup(context: &Context, dir: *const libc::c_char) -> libc::c_
let res = chrono::NaiveDateTime::from_timestamp(now as i64, 0)
.format("delta-chat-%Y-%m-%d.bak")
.to_string();
let buffer = CString::yolo(res);
let dest_pathNfilename = dc_get_fine_pathNfilename(context, dir, buffer.as_ptr());
if dest_pathNfilename.is_null() {
error!(context, 0, "Cannot get backup file name.",);
return success;
}
let dest_path_filename = dc_get_fine_path_filename(context, as_path(dir), res);
sql::housekeeping(context);
@@ -737,27 +684,17 @@ unsafe fn export_backup(context: &Context, dir: *const libc::c_char) -> libc::c_
let mut closed = true;
info!(
context,
0,
"Backup \"{}\" to \"{}\".",
context
.get_dbfile()
.as_ref()
.map_or("<<None>>", |p| p.to_str().unwrap()),
as_str(dest_pathNfilename),
context.get_dbfile().display(),
dest_path_filename.display(),
);
if dc_copy_file(
context,
context.get_dbfile().unwrap(),
as_path(dest_pathNfilename),
) {
context
.sql
.open(&context, &context.get_dbfile().unwrap(), 0);
if dc_copy_file(context, context.get_dbfile(), &dest_path_filename) {
context.sql.open(&context, &context.get_dbfile(), 0);
closed = false;
/* add all files as blobs to the database copy (this does not require the source to be locked, neigher the destination as it is used only here) */
/*for logging only*/
let sql = Sql::new();
if sql.open(context, as_path(dest_pathNfilename), 0) {
if sql.open(context, &dest_path_filename, 0) {
if !sql.table_exists("backup_blobs") {
if sql::execute(
context,
@@ -777,14 +714,14 @@ unsafe fn export_backup(context: &Context, dir: *const libc::c_char) -> libc::c_
}
if ok_to_continue {
let mut total_files_cnt = 0;
let dir = std::path::Path::new(as_str(context.get_blobdir()));
if let Ok(dir_handle) = std::fs::read_dir(dir) {
let dir = context.get_blobdir();
if let Ok(dir_handle) = std::fs::read_dir(&dir) {
total_files_cnt += dir_handle.filter(|r| r.is_ok()).count();
info!(context, 0, "EXPORT: total_files_cnt={}", total_files_cnt);
info!(context, "EXPORT: total_files_cnt={}", total_files_cnt);
if total_files_cnt > 0 {
// scan directory, pass 2: copy files
if let Ok(dir_handle) = std::fs::read_dir(dir) {
if let Ok(dir_handle) = std::fs::read_dir(&dir) {
sql.prepare(
"INSERT INTO backup_blobs (file_name, file_content) VALUES (?, ?);",
move |mut stmt, _| {
@@ -815,11 +752,7 @@ unsafe fn export_backup(context: &Context, dir: *const libc::c_char) -> libc::c_
if permille > 990 {
permille = 990;
}
context.call_cb(
Event::IMEX_PROGRESS,
permille as uintptr_t,
0 as uintptr_t,
);
context.call_cb(Event::ImexProgress(permille));
let name_f = entry.file_name();
let name = name_f.to_string_lossy();
@@ -827,13 +760,8 @@ unsafe fn export_backup(context: &Context, dir: *const libc::c_char) -> libc::c_
{
continue;
} else {
info!(context, 0, "EXPORTing filename={}", name);
let curr_pathNfilename = format!(
"{}/{}",
as_str(context.get_blobdir()),
name
);
info!(context, "EXPORTing filename={}", name);
let curr_pathNfilename = context.get_blobdir().join(entry.file_name());
if let Some(buf) =
dc_read_file_safe(context, &curr_pathNfilename)
{
@@ -843,9 +771,8 @@ unsafe fn export_backup(context: &Context, dir: *const libc::c_char) -> libc::c_
if stmt.execute(params![name, buf]).is_err() {
error!(
context,
0,
"Disk full? Cannot add file \"{}\" to backup.",
&curr_pathNfilename,
curr_pathNfilename.display(),
);
/* this is not recoverable! writing to the sqlite database should work! */
ok_to_continue = false;
@@ -863,13 +790,12 @@ unsafe fn export_backup(context: &Context, dir: *const libc::c_char) -> libc::c_
} else {
error!(
context,
0,
"Backup: Cannot copy from blob-directory \"{}\".",
as_str(context.get_blobdir()),
context.get_blobdir().display(),
);
}
} else {
info!(context, 0, "Backup: No files to copy.",);
info!(context, "Backup: No files to copy.",);
ok_to_continue = true;
}
if ok_to_continue {
@@ -877,34 +803,26 @@ unsafe fn export_backup(context: &Context, dir: *const libc::c_char) -> libc::c_
.set_config_int(context, "backup_time", now as i32)
.is_ok()
{
context.call_cb(
Event::IMEX_FILE_WRITTEN,
dest_pathNfilename as uintptr_t,
0,
);
success = 1;
context.call_cb(Event::ImexFileWritten(dest_path_filename.clone()));
success = true;
}
}
} else {
error!(
context,
0,
"Backup: Cannot get info for blob-directory \"{}\".",
as_str(context.get_blobdir())
context.get_blobdir().display(),
);
};
}
}
}
if closed {
context
.sql
.open(&context, &context.get_dbfile().unwrap(), 0);
context.sql.open(&context, &context.get_dbfile(), 0);
}
if 0 != delete_dest_file {
dc_delete_file(context, as_path(dest_pathNfilename));
dc_delete_file(context, &dest_path_filename);
}
free(dest_pathNfilename as *mut libc::c_void);
success
}
@@ -921,10 +839,8 @@ unsafe fn import_self_keys(context: &Context, dir_name: *const libc::c_char) ->
(currently, the last imported key is the standard key unless it contains the string "legacy" in its name) */
let mut imported_cnt: libc::c_int = 0;
let mut suffix: *mut libc::c_char = ptr::null_mut();
let mut path_plus_name: *mut libc::c_char = ptr::null_mut();
let mut set_default: libc::c_int;
let mut buf: *mut libc::c_char = ptr::null_mut();
let mut buf_bytes: size_t = 0 as size_t;
// a pointer inside buf, MUST NOT be free()'d
let mut private_key: *const libc::c_char;
let mut buf2: *mut libc::c_char = ptr::null_mut();
@@ -947,25 +863,21 @@ unsafe fn import_self_keys(context: &Context, dir_name: *const libc::c_char) ->
{
continue;
}
free(path_plus_name as *mut libc::c_void);
path_plus_name = dc_mprintf(
b"%s/%s\x00" as *const u8 as *const libc::c_char,
dir_name,
name_c.as_ptr(),
);
info!(context, 0, "Checking: {}", as_str(path_plus_name));
free(buf as *mut libc::c_void);
let path_plus_name = dir.join(entry.file_name());
info!(context, "Checking: {}", path_plus_name.display());
free(buf.cast());
buf = ptr::null_mut();
if 0 == dc_read_file(
context,
path_plus_name,
&mut buf as *mut *mut libc::c_char as *mut *mut libc::c_void,
&mut buf_bytes,
) || buf_bytes < 50
{
if let Some(buf_r) = dc_read_file_safe(context, &path_plus_name) {
buf = buf_r.as_ptr() as *mut _;
std::mem::forget(buf_r);
} else {
continue;
}
};
private_key = buf;
free(buf2 as *mut libc::c_void);
buf2 = dc_strdup(buf);
if dc_split_armored_data(
@@ -997,9 +909,8 @@ unsafe fn import_self_keys(context: &Context, dir_name: *const libc::c_char) ->
{
info!(
context,
0,
"Treating \"{}\" as a legacy private key.",
as_str(path_plus_name),
path_plus_name.display(),
);
set_default = 0i32
}
@@ -1011,7 +922,6 @@ unsafe fn import_self_keys(context: &Context, dir_name: *const libc::c_char) ->
if imported_cnt == 0i32 {
error!(
context,
0,
"No private keys found in \"{}\".",
as_str(dir_name),
);
@@ -1019,7 +929,6 @@ unsafe fn import_self_keys(context: &Context, dir_name: *const libc::c_char) ->
} else {
error!(
context,
0,
"Import: Cannot open directory \"{}\".",
as_str(dir_name),
);
@@ -1027,15 +936,13 @@ unsafe fn import_self_keys(context: &Context, dir_name: *const libc::c_char) ->
}
free(suffix as *mut libc::c_void);
free(path_plus_name as *mut libc::c_void);
free(buf as *mut libc::c_void);
free(buf2 as *mut libc::c_void);
imported_cnt
}
// TODO should return bool /rtn
unsafe fn export_self_keys(context: &Context, dir: *const libc::c_char) -> libc::c_int {
unsafe fn export_self_keys(context: &Context, dir: *const libc::c_char) -> bool {
let mut export_errors = 0;
context
@@ -1057,14 +964,14 @@ unsafe fn export_self_keys(context: &Context, dir: *const libc::c_char) -> libc:
for key_pair in keys {
let (id, public_key, private_key, is_default) = key_pair?;
if let Some(key) = public_key {
if 0 == export_key_to_asc_file(context, dir, id, &key, is_default) {
if export_key_to_asc_file(context, dir, id, &key, is_default) {
export_errors += 1;
}
} else {
export_errors += 1;
}
if let Some(key) = private_key {
if 0 == export_key_to_asc_file(context, dir, id, &key, is_default) {
if export_key_to_asc_file(context, dir, id, &key, is_default) {
export_errors += 1;
}
} else {
@@ -1077,61 +984,45 @@ unsafe fn export_self_keys(context: &Context, dir: *const libc::c_char) -> libc:
)
.unwrap();
if export_errors == 0 {
1
} else {
0
}
export_errors == 0
}
/*******************************************************************************
* Classic key export
******************************************************************************/
// TODO should return bool /rtn
unsafe fn export_key_to_asc_file(
context: &Context,
dir: *const libc::c_char,
id: libc::c_int,
key: &Key,
is_default: libc::c_int,
) -> libc::c_int {
let mut success: libc::c_int = 0i32;
let file_name;
if 0 != is_default {
file_name = dc_mprintf(
b"%s/%s-key-default.asc\x00" as *const u8 as *const libc::c_char,
dir,
if key.is_public() {
b"public\x00" as *const u8 as *const libc::c_char
} else {
b"private\x00" as *const u8 as *const libc::c_char
},
)
} else {
file_name = dc_mprintf(
b"%s/%s-key-%i.asc\x00" as *const u8 as *const libc::c_char,
dir,
if key.is_public() {
b"public\x00" as *const u8 as *const libc::c_char
} else {
b"private\x00" as *const u8 as *const libc::c_char
},
id,
)
}
info!(context, 0, "Exporting key {}", as_str(file_name),);
dc_delete_file(context, as_path(file_name));
if !key.write_asc_to_file(file_name, context) {
error!(context, 0, "Cannot write key to {}", as_str(file_name),);
} else {
context.call_cb(
Event::IMEX_FILE_WRITTEN,
file_name as uintptr_t,
0i32 as uintptr_t,
) -> bool {
let mut success = false;
let dir = as_path(dir);
let file_name = if 0 != is_default {
let name = format!(
"{}-key-default.asc",
if key.is_public() { "public" } else { "private" },
);
success = 1i32
dir.join(name)
} else {
let name = format!(
"{}-key-{}.asc",
if key.is_public() { "public" } else { "private" },
id
);
dir.join(name)
};
info!(context, "Exporting key {}", file_name.display());
dc_delete_file(context, &file_name);
if !key.write_asc_to_file(&file_name, context) {
error!(context, "Cannot write key to {}", file_name.display());
} else {
context.call_cb(Event::ImexFileWritten(file_name.clone()));
success = true;
}
free(file_name as *mut libc::c_void);
success
}
@@ -1140,13 +1031,11 @@ unsafe fn export_key_to_asc_file(
mod tests {
use super::*;
use num_traits::ToPrimitive;
use crate::test_utils::*;
#[test]
fn test_render_setup_file() {
let t = test_context(Some(logging_cb));
let t = test_context(Some(Box::new(logging_cb)));
configure_alice_keypair(&t.ctx);
let msg = dc_render_setup_file(&t.ctx, "hello").unwrap();
@@ -1164,22 +1053,19 @@ mod tests {
assert!(msg.contains("-----END PGP MESSAGE-----\n"));
}
unsafe extern "C" fn ac_setup_msg_cb(
ctx: &Context,
evt: Event,
d1: uintptr_t,
d2: uintptr_t,
) -> uintptr_t {
if evt == Event::GET_STRING && d1 == StockMessage::AcSetupMsgBody.to_usize().unwrap() {
"hello\r\nthere".strdup() as usize
} else {
logging_cb(ctx, evt, d1, d2)
fn ac_setup_msg_cb(ctx: &Context, evt: Event) -> libc::uintptr_t {
match evt {
Event::GetString {
id: StockMessage::AcSetupMsgBody,
..
} => unsafe { "hello\r\nthere".strdup() as usize },
_ => logging_cb(ctx, evt),
}
}
#[test]
fn test_render_setup_file_newline_replace() {
let t = test_context(Some(ac_setup_msg_cb));
let t = test_context(Some(Box::new(ac_setup_msg_cb)));
configure_alice_keypair(&t.ctx);
let msg = dc_render_setup_file(&t.ctx, "pw").unwrap();
println!("{}", &msg);

View File

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

View File

@@ -1,7 +1,8 @@
use std::ffi::CString;
use std::path::Path;
use std::ptr;
use chrono::TimeZone;
use mmime::clist::*;
use mmime::mailimf_types::*;
use mmime::mailimf_types_helper::*;
use mmime::mailmime_disposition::*;
@@ -14,7 +15,8 @@ use mmime::other::*;
use crate::chat::{self, Chat};
use crate::constants::*;
use crate::contact::*;
use crate::context::{dc_get_version_str, Context};
use crate::context::{get_version_str, Context};
use crate::dc_mimeparser::SystemMessage;
use crate::dc_strencode::*;
use crate::dc_tools::*;
use crate::e2ee::*;
@@ -23,7 +25,6 @@ use crate::location;
use crate::message::*;
use crate::param::*;
use crate::stock::StockMessage;
use crate::types::*;
use crate::x::*;
#[derive(Clone)]
@@ -31,22 +32,22 @@ use crate::x::*;
pub struct dc_mimefactory_t<'a> {
pub from_addr: *mut libc::c_char,
pub from_displayname: *mut libc::c_char,
pub selfstatus: *mut libc::c_char,
pub selfstatus: Option<String>,
pub recipients_names: *mut clist,
pub recipients_addr: *mut clist,
pub timestamp: i64,
pub rfc724_mid: *mut libc::c_char,
pub loaded: dc_mimefactory_loaded_t,
pub msg: Message<'a>,
pub chat: Option<Chat<'a>>,
pub increation: libc::c_int,
pub msg: Message,
pub chat: Option<Chat>,
pub increation: bool,
pub in_reply_to: *mut libc::c_char,
pub references: *mut libc::c_char,
pub req_mdn: libc::c_int,
pub out: *mut MMAPString,
pub out_encrypted: libc::c_int,
pub out_gossiped: libc::c_int,
pub out_last_added_location_id: uint32_t,
pub out_last_added_location_id: u32,
pub error: *mut libc::c_char,
pub context: &'a Context,
}
@@ -56,7 +57,6 @@ impl<'a> Drop for dc_mimefactory_t<'a> {
unsafe {
free(self.from_addr as *mut libc::c_void);
free(self.from_displayname as *mut libc::c_void);
free(self.selfstatus as *mut libc::c_void);
free(self.rfc724_mid as *mut libc::c_void);
if !self.recipients_names.is_null() {
clist_free_content(self.recipients_names);
@@ -94,7 +94,7 @@ pub unsafe fn dc_mimefactory_load_msg(
let mut factory = dc_mimefactory_t {
from_addr: ptr::null_mut(),
from_displayname: ptr::null_mut(),
selfstatus: ptr::null_mut(),
selfstatus: None,
recipients_names: clist_new(),
recipients_addr: clist_new(),
timestamp: 0,
@@ -102,7 +102,7 @@ pub unsafe fn dc_mimefactory_load_msg(
loaded: DC_MF_NOTHING_LOADED,
msg,
chat: Some(chat),
increation: 0,
increation: false,
in_reply_to: ptr::null_mut(),
references: ptr::null_mut(),
req_mdn: 0,
@@ -148,7 +148,7 @@ pub unsafe fn dc_mimefactory_load_msg(
for row in rows {
let (authname, addr) = row?;
let addr_c = addr.strdup();
if clist_search_string_nocase(factory.recipients_addr, addr_c) == 0 {
if !clist_search_string_nocase(factory.recipients_addr, addr_c) {
clist_insert_after(
factory.recipients_names,
(*factory.recipients_names).last,
@@ -170,10 +170,10 @@ pub unsafe fn dc_mimefactory_load_msg(
)
.unwrap();
let command = factory.msg.param.get_int(Param::Cmd).unwrap_or_default();
let command = factory.msg.param.get_cmd();
let msg = &factory.msg;
if command == 5 {
if command == SystemMessage::MemberRemovedFromGroup {
let email_to_remove = msg.param.get(Param::Arg).unwrap_or_default();
let email_to_remove_c = email_to_remove.strdup();
@@ -183,7 +183,7 @@ pub unsafe fn dc_mimefactory_load_msg(
.unwrap_or_default();
if !email_to_remove.is_empty() && email_to_remove != self_addr {
if clist_search_string_nocase(factory.recipients_addr, email_to_remove_c) == 0 {
if !clist_search_string_nocase(factory.recipients_addr, email_to_remove_c) {
clist_insert_after(
factory.recipients_names,
(*factory.recipients_names).last,
@@ -197,8 +197,8 @@ pub unsafe fn dc_mimefactory_load_msg(
}
}
}
if command != 6
&& command != 7
if command != SystemMessage::AutocryptSetupMessage
&& command != SystemMessage::SecurejoinMessage
&& 0 != context
.sql
.get_config_int(context, "mdns_enabled")
@@ -225,7 +225,7 @@ pub unsafe fn dc_mimefactory_load_msg(
Err(err) => {
error!(
context,
0, "mimefactory: failed to load mime_in_reply_to: {:?}", err
"mimefactory: failed to load mime_in_reply_to: {:?}", err
);
}
}
@@ -252,19 +252,21 @@ unsafe fn load_from(factory: &mut dc_mimefactory_t) {
.unwrap_or_default()
.strdup();
factory.selfstatus = context
.sql
.get_config(context, "selfstatus")
.unwrap_or_default()
.strdup();
if factory.selfstatus.is_null() {
factory.selfstatus = factory.context.stock_str(StockMessage::StatusLine).strdup();
};
factory.selfstatus = context.sql.get_config(context, "selfstatus");
if factory.selfstatus.is_none() {
factory.selfstatus = Some(
factory
.context
.stock_str(StockMessage::StatusLine)
.to_string(),
);
}
}
pub unsafe fn dc_mimefactory_load_mdn<'a>(
context: &'a Context,
msg_id: uint32_t,
msg_id: u32,
) -> Result<dc_mimefactory_t, Error> {
if 0 == context
.sql
@@ -282,7 +284,7 @@ pub unsafe fn dc_mimefactory_load_mdn<'a>(
let mut factory = dc_mimefactory_t {
from_addr: ptr::null_mut(),
from_displayname: ptr::null_mut(),
selfstatus: ptr::null_mut(),
selfstatus: None,
recipients_names: clist_new(),
recipients_addr: clist_new(),
timestamp: 0,
@@ -290,7 +292,7 @@ pub unsafe fn dc_mimefactory_load_mdn<'a>(
loaded: DC_MF_NOTHING_LOADED,
msg,
chat: None,
increation: 0,
increation: false,
in_reply_to: ptr::null_mut(),
references: ptr::null_mut(),
req_mdn: 0,
@@ -334,18 +336,16 @@ pub unsafe fn dc_mimefactory_load_mdn<'a>(
Ok(factory)
}
// TODO should return bool /rtn
pub unsafe fn dc_mimefactory_render(factory: &mut dc_mimefactory_t) -> libc::c_int {
pub unsafe fn dc_mimefactory_render(context: &Context, factory: &mut dc_mimefactory_t) -> bool {
let subject: *mut mailimf_subject;
let mut ok_to_continue = true;
let imf_fields: *mut mailimf_fields;
let mut message: *mut mailmime = ptr::null_mut();
let mut message_text: *mut libc::c_char = ptr::null_mut();
let mut message_text2: *mut libc::c_char = ptr::null_mut();
let mut subject_str: *mut libc::c_char = ptr::null_mut();
let mut afwd_email: libc::c_int = 0;
let mut col: libc::c_int = 0;
let mut success: libc::c_int = 0;
let mut success = false;
let mut parts: libc::c_int = 0;
let mut e2ee_guaranteed: libc::c_int = 0;
let mut min_verified: libc::c_int = 0;
@@ -369,7 +369,7 @@ pub unsafe fn dc_mimefactory_render(factory: &mut dc_mimefactory_t) -> libc::c_i
from,
mailimf_mailbox_new(
if !factory.from_displayname.is_null() {
dc_encode_header_words(factory.from_displayname)
dc_encode_header_words(as_str(factory.from_displayname))
} else {
ptr::null_mut()
},
@@ -403,7 +403,7 @@ pub unsafe fn dc_mimefactory_render(factory: &mut dc_mimefactory_t) -> libc::c_i
MAILIMF_ADDRESS_MAILBOX as libc::c_int,
mailimf_mailbox_new(
if !name.is_null() {
dc_encode_header_words(name)
dc_encode_header_words(as_str(name))
} else {
ptr::null_mut()
},
@@ -458,20 +458,14 @@ pub unsafe fn dc_mimefactory_render(factory: &mut dc_mimefactory_t) -> libc::c_i
.as_ref()
.map(|s| format!("/{}", s))
.unwrap_or_default();
let os_part = CString::new(os_part).expect("String -> CString conversion failed");
let version = dc_get_version_str();
let version = get_version_str();
mailimf_fields_add(
imf_fields,
mailimf_field_new_custom(
strdup(b"X-Mailer\x00" as *const u8 as *const libc::c_char),
dc_mprintf(
b"Delta Chat Core %s%s\x00" as *const u8 as *const libc::c_char,
version,
os_part.as_ptr(),
),
format!("Delta Chat Core {}{}", version, os_part).strdup(),
),
);
free(version.cast());
mailimf_fields_add(
imf_fields,
@@ -498,7 +492,8 @@ pub unsafe fn dc_mimefactory_render(factory: &mut dc_mimefactory_t) -> libc::c_i
*********************************************************************/
let chat = factory.chat.as_ref().unwrap();
let mut meta_part: *mut mailmime = ptr::null_mut();
let mut placeholdertext: *mut libc::c_char = ptr::null_mut();
let mut placeholdertext = None;
if chat.typ == Chattype::VerifiedGroup {
mailimf_fields_add(
imf_fields,
@@ -531,7 +526,7 @@ pub unsafe fn dc_mimefactory_render(factory: &mut dc_mimefactory_t) -> libc::c_i
}
/* build header etc. */
let command = factory.msg.param.get_int(Param::Cmd).unwrap_or_default();
let command = factory.msg.param.get_cmd();
if chat.typ == Chattype::Group || chat.typ == Chattype::VerifiedGroup {
mailimf_fields_add(
imf_fields,
@@ -540,15 +535,15 @@ pub unsafe fn dc_mimefactory_render(factory: &mut dc_mimefactory_t) -> libc::c_i
chat.grpid.strdup(),
),
);
let name = CString::yolo(chat.name.as_bytes());
mailimf_fields_add(
imf_fields,
mailimf_field_new_custom(
strdup(b"Chat-Group-Name\x00" as *const u8 as *const libc::c_char),
dc_encode_header_words(name.as_ptr()),
dc_encode_header_words(&chat.name),
),
);
if command == 5 {
if command == SystemMessage::MemberRemovedFromGroup {
let email_to_remove = factory
.msg
.param
@@ -567,7 +562,7 @@ pub unsafe fn dc_mimefactory_render(factory: &mut dc_mimefactory_t) -> libc::c_i
),
);
}
} else if command == 4 {
} else if command == SystemMessage::MemberAddedToGroup {
let msg = &factory.msg;
do_gossip = 1;
let email_to_add = msg.param.get(Param::Arg).unwrap_or_default().strdup();
@@ -586,8 +581,7 @@ pub unsafe fn dc_mimefactory_render(factory: &mut dc_mimefactory_t) -> libc::c_i
}
if 0 != msg.param.get_int(Param::Arg2).unwrap_or_default() & 0x1 {
info!(
msg.context,
0,
context,
"sending secure-join message \'{}\' >>>>>>>>>>>>>>>>>>>>>>>>>",
"vg-member-added",
);
@@ -599,7 +593,7 @@ pub unsafe fn dc_mimefactory_render(factory: &mut dc_mimefactory_t) -> libc::c_i
),
);
}
} else if command == 2 {
} else if command == SystemMessage::GroupNameChanged {
let msg = &factory.msg;
let value_to_add = msg.param.get(Param::Arg).unwrap_or_default().strdup();
@@ -612,7 +606,7 @@ pub unsafe fn dc_mimefactory_render(factory: &mut dc_mimefactory_t) -> libc::c_i
value_to_add,
),
);
} else if command == 3 {
} else if command == SystemMessage::GroupImageChanged {
let msg = &factory.msg;
grpimage = msg.param.get(Param::Arg);
if grpimage.is_none() {
@@ -626,7 +620,8 @@ pub unsafe fn dc_mimefactory_render(factory: &mut dc_mimefactory_t) -> libc::c_i
}
}
}
if command == 8 {
if command == SystemMessage::LocationStreamingEnabled {
mailimf_fields_add(
imf_fields,
mailimf_field_new_custom(
@@ -637,7 +632,7 @@ pub unsafe fn dc_mimefactory_render(factory: &mut dc_mimefactory_t) -> libc::c_i
),
);
}
if command == 6 {
if command == SystemMessage::AutocryptSetupMessage {
mailimf_fields_add(
imf_fields,
mailimf_field_new_custom(
@@ -645,18 +640,19 @@ pub unsafe fn dc_mimefactory_render(factory: &mut dc_mimefactory_t) -> libc::c_i
strdup(b"v1\x00" as *const u8 as *const libc::c_char),
),
);
placeholdertext = factory
.context
.stock_str(StockMessage::AcSetupMsgBody)
.strdup();
placeholdertext = Some(
factory
.context
.stock_str(StockMessage::AcSetupMsgBody)
.to_string(),
);
}
if command == 7 {
if command == SystemMessage::SecurejoinMessage {
let msg = &factory.msg;
let step = msg.param.get(Param::Arg).unwrap_or_default().strdup();
if strlen(step) > 0 {
info!(
msg.context,
0,
context,
"sending secure-join message \'{}\' >>>>>>>>>>>>>>>>>>>>>>>>>",
as_str(step),
);
@@ -725,13 +721,16 @@ pub unsafe fn dc_mimefactory_render(factory: &mut dc_mimefactory_t) -> libc::c_i
}
}
}
if let Some(grpimage) = grpimage {
let mut meta = dc_msg_new_untyped(factory.context);
info!(factory.context, "setting group image '{}'", grpimage);
let mut meta = dc_msg_new_untyped();
meta.type_0 = Viewtype::Image;
meta.param.set(Param::File, grpimage);
let mut filename_as_sent = ptr::null_mut();
meta_part = build_body_file(
context,
&meta,
b"group-image\x00" as *const u8 as *const libc::c_char,
&mut filename_as_sent,
@@ -770,82 +769,72 @@ pub unsafe fn dc_mimefactory_render(factory: &mut dc_mimefactory_t) -> libc::c_i
imf_fields,
mailimf_field_new_custom(
strdup(b"Chat-Duration\x00" as *const u8 as *const libc::c_char),
dc_mprintf(
b"%i\x00" as *const u8 as *const libc::c_char,
duration_ms as libc::c_int,
),
duration_ms.to_string().strdup(),
),
);
}
}
afwd_email = factory.msg.param.exists(Param::Forwarded) as libc::c_int;
let mut fwdhint = ptr::null_mut();
if 0 != afwd_email {
fwdhint = dc_strdup(
b"---------- Forwarded message ----------\r\nFrom: Delta Chat\r\n\r\n\x00"
as *const u8 as *const libc::c_char,
let fwdhint = if 0 != afwd_email {
Some(
"---------- Forwarded message ----------\r\nFrom: Delta Chat\r\n\r\n"
.to_string(),
)
}
} else {
None
};
let final_text = {
if !placeholdertext.is_null() {
to_string(placeholdertext)
if let Some(ref text) = placeholdertext {
text
} else if let Some(ref text) = factory.msg.text {
text.clone()
text
} else {
"".into()
""
}
};
let final_text = CString::yolo(final_text);
let footer: *mut libc::c_char = factory.selfstatus;
message_text = dc_mprintf(
b"%s%s%s%s%s\x00" as *const u8 as *const libc::c_char,
if !fwdhint.is_null() {
fwdhint
let footer = factory.selfstatus.as_ref();
message_text = format!(
"{}{}{}{}{}",
if let Some(ref hint) = fwdhint {
hint
} else {
b"\x00" as *const u8 as *const libc::c_char
""
},
final_text.as_ptr(),
if final_text != CString::yolo("")
&& !footer.is_null()
&& 0 != *footer.offset(0isize) as libc::c_int
{
b"\r\n\r\n\x00" as *const u8 as *const libc::c_char
&final_text,
if !final_text.is_empty() && footer.is_some() {
"\r\n\r\n"
} else {
b"\x00" as *const u8 as *const libc::c_char
""
},
if !footer.is_null() && 0 != *footer.offset(0isize) as libc::c_int {
b"-- \r\n\x00" as *const u8 as *const libc::c_char
} else {
b"\x00" as *const u8 as *const libc::c_char
},
if !footer.is_null() && 0 != *footer.offset(0isize) as libc::c_int {
if footer.is_some() { "-- \r\n" } else { "" },
if let Some(footer) = footer {
footer
} else {
b"\x00" as *const u8 as *const libc::c_char
""
},
);
)
.strdup();
let text_part: *mut mailmime = build_body_text(message_text);
mailmime_smart_add_part(message, text_part);
parts += 1;
free(fwdhint as *mut libc::c_void);
free(placeholdertext as *mut libc::c_void);
/* add attachment part */
if chat::msgtype_has_file(factory.msg.type_0) {
if !is_file_size_okay(&factory.msg) {
let error: *mut libc::c_char = dc_mprintf(
b"Message exceeds the recommended %i MB.\x00" as *const u8
as *const libc::c_char,
if !is_file_size_okay(context, &factory.msg) {
let error = format!(
"Message exceeds the recommended {} MB.",
24 * 1024 * 1024 / 4 * 3 / 1000 / 1000,
);
)
.strdup();
set_error(factory, error);
free(error as *mut libc::c_void);
free(error.cast());
ok_to_continue = false;
} else {
let file_part: *mut mailmime =
build_body_file(&factory.msg, ptr::null(), ptr::null_mut());
build_body_file(context, &factory.msg, ptr::null(), ptr::null_mut());
if !file_part.is_null() {
mailmime_smart_add_part(message, file_part);
parts += 1
@@ -893,12 +882,9 @@ pub unsafe fn dc_mimefactory_render(factory: &mut dc_mimefactory_t) -> libc::c_i
mailmime_smart_add_part(message, kml_mime_part);
}
if location::is_sending_locations_to_chat(
factory.msg.context,
factory.msg.chat_id,
) {
if location::is_sending_locations_to_chat(context, factory.msg.chat_id) {
if let Ok((kml_file, last_added_location_id)) =
location::get_kml(factory.msg.context, factory.msg.chat_id)
location::get_kml(context, factory.msg.chat_id)
{
let content_type = mailmime_content_new_with_str(
b"application/vnd.google-earth.kml+xml\x00" as *const u8
@@ -951,7 +937,7 @@ pub unsafe fn dc_mimefactory_render(factory: &mut dc_mimefactory_t) -> libc::c_i
.stock_str(StockMessage::EncryptedMsg)
.into_owned()
} else {
to_string(dc_msg_get_summarytext(&mut factory.msg, 32))
to_string(dc_msg_get_summarytext(context, &mut factory.msg, 32))
};
let p2 = factory
.context
@@ -959,16 +945,15 @@ pub unsafe fn dc_mimefactory_render(factory: &mut dc_mimefactory_t) -> libc::c_i
message_text = format!("{}\r\n", p2).strdup();
let human_mime_part: *mut mailmime = build_body_text(message_text);
mailmime_add_part(multipart, human_mime_part);
let version = dc_get_version_str();
message_text2 =
dc_mprintf(
b"Reporting-UA: Delta Chat %s\r\nOriginal-Recipient: rfc822;%s\r\nFinal-Recipient: rfc822;%s\r\nOriginal-Message-ID: <%s>\r\nDisposition: manual-action/MDN-sent-automatically; displayed\r\n\x00"
as *const u8 as *const libc::c_char,
version,
factory.from_addr, factory.from_addr,
factory.msg.rfc724_mid
);
free(version.cast());
let version = get_version_str();
message_text2 = format!(
"Reporting-UA: Delta Chat {}\r\nOriginal-Recipient: rfc822;{}\r\nFinal-Recipient: rfc822;{}\r\nOriginal-Message-ID: <{}>\r\nDisposition: manual-action/MDN-sent-automatically; displayed\r\n",
version,
as_str(factory.from_addr),
as_str(factory.from_addr),
as_str(factory.msg.rfc724_mid)
).strdup();
let content_type_0: *mut mailmime_content = mailmime_content_new_with_str(
b"message/disposition-notification\x00" as *const u8 as *const libc::c_char,
);
@@ -987,16 +972,19 @@ pub unsafe fn dc_mimefactory_render(factory: &mut dc_mimefactory_t) -> libc::c_i
}
if ok_to_continue {
if factory.loaded as libc::c_uint == DC_MF_MDN_LOADED as libc::c_int as libc::c_uint {
let e = CString::new(factory.context.stock_str(StockMessage::ReadRcpt).as_ref())
.unwrap();
subject_str = dc_mprintf(
b"Chat: %s\x00" as *const u8 as *const libc::c_char,
e.as_ptr(),
);
let subject_str = if factory.loaded as libc::c_uint == DC_MF_MDN_LOADED as libc::c_uint
{
let e = factory.context.stock_str(StockMessage::ReadRcpt);
format!("Chat: {}", e)
} else {
subject_str = get_subject(factory.chat.as_ref(), &mut factory.msg, afwd_email)
}
to_string(get_subject(
context,
factory.chat.as_ref(),
&mut factory.msg,
afwd_email,
))
};
subject = mailimf_subject_new(dc_encode_header_words(subject_str));
mailimf_fields_add(
imf_fields,
@@ -1045,7 +1033,7 @@ pub unsafe fn dc_mimefactory_render(factory: &mut dc_mimefactory_t) -> libc::c_i
}
factory.out = mmap_string_new(b"\x00" as *const u8 as *const libc::c_char);
mailmime_write_mem(factory.out, &mut col, message);
success = 1;
success = true;
}
}
@@ -1055,12 +1043,12 @@ pub unsafe fn dc_mimefactory_render(factory: &mut dc_mimefactory_t) -> libc::c_i
e2ee_helper.thanks();
free(message_text as *mut libc::c_void);
free(message_text2 as *mut libc::c_void);
free(subject_str as *mut libc::c_void);
success
}
unsafe fn get_subject(
context: &Context,
chat: Option<&Chat>,
msg: &mut Message,
afwd_email: libc::c_int,
@@ -1070,37 +1058,19 @@ unsafe fn get_subject(
}
let chat = chat.unwrap();
let context = chat.context;
let ret: *mut libc::c_char;
let raw_subject = {
dc_msg_get_summarytext_by_raw(msg.type_0, msg.text.as_ref(), &mut msg.param, 32, context)
.strdup()
};
let raw_subject =
dc_msg_get_summarytext_by_raw(msg.type_0, msg.text.as_ref(), &mut msg.param, 32, context);
let fwd = if 0 != afwd_email {
b"Fwd: \x00" as *const u8 as *const libc::c_char
} else {
b"\x00" as *const u8 as *const libc::c_char
};
if msg.param.get_int(Param::Cmd).unwrap_or_default() == 6 {
let fwd = if 0 != afwd_email { "Fwd: " } else { "" };
if msg.param.get_cmd() == SystemMessage::AutocryptSetupMessage {
ret = context.stock_str(StockMessage::AcSetupMsgSubject).strdup()
} else if chat.typ == Chattype::Group || chat.typ == Chattype::VerifiedGroup {
ret = format!(
"Chat: {}: {}{}",
chat.name,
to_string(fwd),
to_string(raw_subject),
)
.strdup()
ret = format!("Chat: {}: {}{}", chat.name, fwd, raw_subject,).strdup();
} else {
ret = dc_mprintf(
b"Chat: %s%s\x00" as *const u8 as *const libc::c_char,
fwd,
raw_subject,
)
ret = format!("Chat: {}{}", fwd, raw_subject).strdup();
}
free(raw_subject as *mut libc::c_void);
ret
}
@@ -1135,6 +1105,7 @@ unsafe fn build_body_text(text: *mut libc::c_char) -> *mut mailmime {
#[allow(non_snake_case)]
unsafe fn build_body_file(
context: &Context,
msg: &Message,
mut base_name: *const libc::c_char,
ret_file_name_as_sent: *mut *mut libc::c_char,
@@ -1151,13 +1122,12 @@ unsafe fn build_body_file(
.map(|s| s.strdup())
.unwrap_or_else(|| std::ptr::null_mut());
let mut filename_to_send = ptr::null_mut();
let mut filename_encoded = ptr::null_mut();
if let Some(ref path_filename) = path_filename {
let suffix = dc_get_filesuffix_lc(path_filename);
if msg.type_0 == Viewtype::Voice {
let filename_to_send = if msg.type_0 == Viewtype::Voice {
let ts = chrono::Utc.timestamp(msg.timestamp_sort as i64, 0);
let suffix = if !suffix.is_null() {
@@ -1165,37 +1135,42 @@ unsafe fn build_body_file(
} else {
"dat".into()
};
let res = ts
.format(&format!("voice-message_%Y-%m-%d_%H-%M-%S.{}", suffix))
.to_string();
filename_to_send = res.strdup();
ts.format(&format!("voice-message_%Y-%m-%d_%H-%M-%S.{}", suffix))
.to_string()
} else if msg.type_0 == Viewtype::Audio {
filename_to_send = dc_get_filename(path_filename)
Path::new(path_filename)
.file_name()
.map(|c| c.to_string_lossy().to_string())
.unwrap_or_default()
} else if msg.type_0 == Viewtype::Image || msg.type_0 == Viewtype::Gif {
if base_name.is_null() {
base_name = b"image\x00" as *const u8 as *const libc::c_char
}
filename_to_send = dc_mprintf(
b"%s.%s\x00" as *const u8 as *const libc::c_char,
base_name,
format!(
"{}.{}",
as_str(base_name),
if !suffix.is_null() {
suffix
as_str(suffix)
} else {
b"dat\x00" as *const u8 as *const libc::c_char
"dat"
},
)
} else if msg.type_0 == Viewtype::Video {
filename_to_send = dc_mprintf(
b"video.%s\x00" as *const u8 as *const libc::c_char,
format!(
"video.{}",
if !suffix.is_null() {
suffix
as_str(suffix)
} else {
b"dat\x00" as *const u8 as *const libc::c_char
"dat"
},
)
} else {
filename_to_send = dc_get_filename(path_filename)
}
Path::new(path_filename)
.file_name()
.map(|c| c.to_string_lossy().to_string())
.unwrap_or_default()
};
if mimetype.is_null() {
if suffix.is_null() {
mimetype =
@@ -1218,13 +1193,13 @@ unsafe fn build_body_file(
/* create mime part, for Content-Disposition, see RFC 2183.
`Content-Disposition: attachment` seems not to make a difference to `Content-Disposition: inline` at least on tested Thunderbird and Gma'l in 2017.
But I've heard about problems with inline and outl'k, so we just use the attachment-type until we run into other problems ... */
needs_ext = dc_needs_ext_header(filename_to_send);
needs_ext = dc_needs_ext_header(&filename_to_send);
mime_fields = mailmime_fields_new_filename(
MAILMIME_DISPOSITION_TYPE_ATTACHMENT as libc::c_int,
if needs_ext {
ptr::null_mut()
} else {
dc_strdup(filename_to_send)
filename_to_send.strdup()
},
MAILMIME_MECHANISM_BASE64 as libc::c_int,
);
@@ -1250,12 +1225,12 @@ unsafe fn build_body_file(
ptr::null_mut(),
ptr::null_mut(),
ptr::null_mut(),
0 as size_t,
0 as libc::size_t,
mailmime_parameter_new(
strdup(
b"filename*\x00" as *const u8 as *const libc::c_char,
),
dc_encode_ext_header(filename_to_send),
dc_encode_ext_header(&filename_to_send).strdup(),
),
);
if !parm.is_null() {
@@ -1277,7 +1252,7 @@ unsafe fn build_body_file(
}
}
content = mailmime_content_new_with_str(mimetype);
filename_encoded = dc_encode_header_words(filename_to_send);
filename_encoded = dc_encode_header_words(&filename_to_send);
clist_insert_after(
(*content).ct_parameters,
(*(*content).ct_parameters).last,
@@ -1287,15 +1262,17 @@ unsafe fn build_body_file(
) as *mut libc::c_void,
);
mime_sub = mailmime_new_empty(content, mime_fields);
mailmime_set_body_file(mime_sub, dc_get_abs_path(msg.context, path_filename));
let abs_path = dc_get_abs_path(context, path_filename)
.to_c_string()
.unwrap();
mailmime_set_body_file(mime_sub, dc_strdup(abs_path.as_ptr()));
if !ret_file_name_as_sent.is_null() {
*ret_file_name_as_sent = dc_strdup(filename_to_send)
*ret_file_name_as_sent = filename_to_send.strdup();
}
}
}
free(mimetype as *mut libc::c_void);
free(filename_to_send as *mut libc::c_void);
free(filename_encoded as *mut libc::c_void);
mime_sub
@@ -1304,10 +1281,10 @@ unsafe fn build_body_file(
/*******************************************************************************
* Render
******************************************************************************/
unsafe fn is_file_size_okay(msg: &Message) -> bool {
unsafe fn is_file_size_okay(context: &Context, msg: &Message) -> bool {
let mut file_size_okay = true;
let path = msg.param.get(Param::File).unwrap_or_default();
let bytes = dc_get_filebytes(msg.context, &path);
let bytes = dc_get_filebytes(context, &path);
if bytes > (49 * 1024 * 1024 / 4 * 3) {
file_size_okay = false;

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

View File

@@ -211,6 +211,16 @@ fn is_plain_quote(buf: &str) -> bool {
#[cfg(test)]
mod tests {
use super::*;
use proptest::prelude::*;
proptest! {
#[test]
// proptest does not support [[:graphical:][:space:]] regex.
fn test_simplify_plain_text_fuzzy(input in "[!-~\t \n]+") {
let output = Simplify::new().simplify_plain_text(&input, true);
assert!(output.split('\n').all(|s| s != "-- "));
}
}
#[test]
fn test_simplify_trim() {

View File

@@ -1,89 +1,39 @@
use std::ffi::{CStr, CString};
use std::borrow::Cow;
use std::ffi::CString;
use std::ptr;
use charset::Charset;
use mmime::mailmime_decode::*;
use mmime::mmapstring::*;
use mmime::other::*;
use percent_encoding::{percent_decode, utf8_percent_encode, AsciiSet, CONTROLS};
use crate::dc_tools::*;
use crate::types::*;
use crate::x::*;
#[inline]
fn isalnum(c: libc::c_int) -> libc::c_int {
if c < std::u8::MAX as libc::c_int {
(c as u8 as char).is_ascii_alphanumeric() as libc::c_int
} else {
0
}
}
/**
* Encode non-ascii-strings as `=?UTF-8?Q?Bj=c3=b6rn_Petersen?=`.
* Belongs to RFC 2047: https://tools.ietf.org/html/rfc2047
*
* We do not fold at position 72; this would result in empty words as `=?utf-8?Q??=` which are correct,
* but cannot be displayed by some mail programs (eg. Android Stock Mail).
* however, this is not needed, as long as _one_ word is not longer than 72 characters.
* _if_ it is, the display may get weird. This affects the subject only.
* the best solution wor all this would be if libetpan encodes the line as only libetpan knowns when a header line is full.
*
* @param to_encode Null-terminated UTF-8-string to encode.
* @return Returns the encoded string which must be free()'d when no longed needed.
* On errors, NULL is returned.
*/
pub unsafe fn dc_encode_header_words(to_encode_r: impl AsRef<str>) -> *mut libc::c_char {
let to_encode =
CString::new(to_encode_r.as_ref().as_bytes()).expect("invalid cstring to_encode");
/* ******************************************************************************
* URL encoding and decoding, RFC 3986
******************************************************************************/
unsafe fn int_2_uppercase_hex(code: libc::c_char) -> libc::c_char {
static mut HEX: [libc::c_char; 17] = [
48, 49, 50, 51, 52, 53, 54, 55, 56, 57, 65, 66, 67, 68, 69, 70, 0,
];
HEX[(code as libc::c_int & 15i32) as usize]
}
pub unsafe fn dc_urldecode(to_decode: *const libc::c_char) -> *mut libc::c_char {
let mut pstr: *const libc::c_char = to_decode;
if to_decode.is_null() {
return dc_strdup(b"\x00" as *const u8 as *const libc::c_char);
}
let buf: *mut libc::c_char = malloc(strlen(to_decode).wrapping_add(1)) as *mut libc::c_char;
let mut pbuf: *mut libc::c_char = buf;
assert!(!buf.is_null());
while 0 != *pstr {
if *pstr as libc::c_int == '%' as i32 {
if 0 != *pstr.offset(1isize) as libc::c_int && 0 != *pstr.offset(2isize) as libc::c_int
{
let fresh5 = pbuf;
pbuf = pbuf.offset(1);
*fresh5 = ((hex_2_int(*pstr.offset(1isize)) as libc::c_int) << 4i32
| hex_2_int(*pstr.offset(2isize)) as libc::c_int)
as libc::c_char;
pstr = pstr.offset(2isize)
}
} else if *pstr as libc::c_int == '+' as i32 {
let fresh6 = pbuf;
pbuf = pbuf.offset(1);
*fresh6 = ' ' as i32 as libc::c_char
} else {
let fresh7 = pbuf;
pbuf = pbuf.offset(1);
*fresh7 = *pstr
}
pstr = pstr.offset(1isize)
}
*pbuf = '\u{0}' as i32 as libc::c_char;
buf
}
fn hex_2_int(ch: libc::c_char) -> libc::c_char {
let ch = ch as u8 as char;
if !ch.is_ascii_hexdigit() {
return (ch.to_ascii_lowercase() as i32 - 'a' as i32 + 10) as libc::c_char;
}
match ch.to_digit(16) {
Some(res) => res as libc::c_char,
None => 0,
}
}
pub unsafe fn dc_encode_header_words(to_encode: *const libc::c_char) -> *mut libc::c_char {
let mut ok_to_continue = true;
let mut ret_str: *mut libc::c_char = ptr::null_mut();
let mut cur: *const libc::c_char = to_encode;
let mut cur: *const libc::c_char = to_encode.as_ptr();
let mmapstr: *mut MMAPString = mmap_string_new(b"\x00" as *const u8 as *const libc::c_char);
if to_encode.is_null() || mmapstr.is_null() {
if mmapstr.is_null() {
ok_to_continue = false;
}
loop {
@@ -118,7 +68,7 @@ pub unsafe fn dc_encode_header_words(to_encode: *const libc::c_char) -> *mut lib
b"utf-8\x00" as *const u8 as *const libc::c_char,
mmapstr,
begin,
end.wrapping_offset_from(begin) as size_t,
end.wrapping_offset_from(begin) as libc::size_t,
) {
ok_to_continue = false;
continue;
@@ -134,7 +84,7 @@ pub unsafe fn dc_encode_header_words(to_encode: *const libc::c_char) -> *mut lib
if mmap_string_append_len(
mmapstr,
end,
cur.wrapping_offset_from(end) as size_t,
cur.wrapping_offset_from(end) as libc::size_t,
)
.is_null()
{
@@ -145,7 +95,7 @@ pub unsafe fn dc_encode_header_words(to_encode: *const libc::c_char) -> *mut lib
} else if mmap_string_append_len(
mmapstr,
begin,
cur.wrapping_offset_from(begin) as size_t,
cur.wrapping_offset_from(begin) as libc::size_t,
)
.is_null()
{
@@ -174,10 +124,10 @@ unsafe fn quote_word(
display_charset: *const libc::c_char,
mmapstr: *mut MMAPString,
word: *const libc::c_char,
size: size_t,
size: libc::size_t,
) -> bool {
let mut cur: *const libc::c_char;
let mut i: size_t = 0i32 as size_t;
let mut i = 0;
let mut hex: [libc::c_char; 4] = [0; 4];
// let mut col: libc::c_int = 0i32;
if mmap_string_append(mmapstr, b"=?\x00" as *const u8 as *const libc::c_char).is_null() {
@@ -240,7 +190,7 @@ unsafe fn get_word(
{
cur = cur.offset(1isize)
}
*pto_be_quoted = to_be_quoted(begin, cur.wrapping_offset_from(begin) as size_t);
*pto_be_quoted = to_be_quoted(begin, cur.wrapping_offset_from(begin) as libc::size_t);
*pend = cur;
}
@@ -249,9 +199,9 @@ unsafe fn get_word(
******************************************************************************/
/* see comment below */
unsafe fn to_be_quoted(word: *const libc::c_char, size: size_t) -> bool {
unsafe fn to_be_quoted(word: *const libc::c_char, size: libc::size_t) -> bool {
let mut cur: *const libc::c_char = word;
let mut i: size_t = 0i32 as size_t;
let mut i = 0;
while i < size {
match *cur as libc::c_int {
44 | 58 | 33 | 34 | 35 | 36 | 64 | 91 | 92 | 93 | 94 | 96 | 123 | 124 | 125 | 126
@@ -274,7 +224,7 @@ pub unsafe fn dc_decode_header_words(in_0: *const libc::c_char) -> *mut libc::c_
return ptr::null_mut();
}
let mut out: *mut libc::c_char = ptr::null_mut();
let mut cur_token: size_t = 0i32 as size_t;
let mut cur_token = 0;
let r: libc::c_int = mailmime_encoded_phrase_parse(
b"iso-8859-1\x00" as *const u8 as *const libc::c_char,
in_0,
@@ -290,374 +240,88 @@ pub unsafe fn dc_decode_header_words(in_0: *const libc::c_char) -> *mut libc::c_
out
}
#[cfg(test)]
unsafe fn dc_encode_modified_utf7(
mut to_encode: *const libc::c_char,
change_spaces: libc::c_int,
) -> *mut libc::c_char {
let mut utf8pos: libc::c_uint;
let mut utf8total: libc::c_uint;
let mut c: libc::c_uint;
let mut utf7mode: libc::c_uint;
let mut bitstogo: libc::c_uint;
let mut utf16flag: libc::c_uint;
let mut ucs4: libc::c_ulong = 0;
let mut bitbuf: libc::c_ulong = 0;
let mut dst: *mut libc::c_char;
let res: *mut libc::c_char;
if to_encode.is_null() {
return dc_strdup(b"\x00" as *const u8 as *const libc::c_char);
}
res = malloc(2usize.wrapping_mul(strlen(to_encode)).wrapping_add(1)) as *mut libc::c_char;
dst = res;
assert!(!dst.is_null());
utf7mode = 0i32 as libc::c_uint;
utf8total = 0i32 as libc::c_uint;
bitstogo = 0i32 as libc::c_uint;
utf8pos = 0i32 as libc::c_uint;
loop {
c = *to_encode as libc::c_uchar as libc::c_uint;
if !(c != '\u{0}' as i32 as libc::c_uint) {
break;
}
to_encode = to_encode.offset(1isize);
// normal character?
if c >= ' ' as i32 as libc::c_uint
&& c <= '~' as i32 as libc::c_uint
&& (c != '_' as i32 as libc::c_uint || 0 == change_spaces)
{
if 0 != utf7mode {
if 0 != bitstogo {
let fresh8 = dst;
dst = dst.offset(1);
*fresh8 = BASE64CHARS
[(bitbuf << (6i32 as libc::c_uint).wrapping_sub(bitstogo) & 0x3f) as usize]
}
let fresh9 = dst;
dst = dst.offset(1);
*fresh9 = '-' as i32 as libc::c_char;
utf7mode = 0i32 as libc::c_uint;
utf8pos = 0i32 as libc::c_uint;
bitstogo = 0i32 as libc::c_uint;
utf8total = 0i32 as libc::c_uint
}
if 0 != change_spaces && c == ' ' as i32 as libc::c_uint {
let fresh10 = dst;
dst = dst.offset(1);
*fresh10 = '_' as i32 as libc::c_char
} else {
let fresh11 = dst;
dst = dst.offset(1);
*fresh11 = c as libc::c_char
}
if c == '&' as i32 as libc::c_uint {
let fresh12 = dst;
dst = dst.offset(1);
*fresh12 = '-' as i32 as libc::c_char
}
pub fn dc_decode_header_words_safe(input: &str) -> String {
static FROM_ENCODING: &[u8] = b"iso-8859-1\x00";
static TO_ENCODING: &[u8] = b"utf-8\x00";
let mut out = ptr::null_mut();
let mut cur_token = 0;
let input_c = CString::yolo(input);
unsafe {
let r = mailmime_encoded_phrase_parse(
FROM_ENCODING.as_ptr().cast(),
input_c.as_ptr(),
input.len(),
&mut cur_token,
TO_ENCODING.as_ptr().cast(),
&mut out,
);
if r as u32 != MAILIMF_NO_ERROR || out.is_null() {
input.to_string()
} else {
if 0 == utf7mode {
let fresh13 = dst;
dst = dst.offset(1);
*fresh13 = '&' as i32 as libc::c_char;
utf7mode = 1i32 as libc::c_uint
}
// encode ascii characters as themselves
if c < 0x80i32 as libc::c_uint {
ucs4 = c as libc::c_ulong
} else if 0 != utf8total {
ucs4 = ucs4 << 6i32 | c as libc::c_ulong & 0x3f;
utf8pos = utf8pos.wrapping_add(1);
if utf8pos < utf8total {
continue;
}
} else {
utf8pos = 1i32 as libc::c_uint;
if c < 0xe0i32 as libc::c_uint {
utf8total = 2i32 as libc::c_uint;
ucs4 = (c & 0x1fi32 as libc::c_uint) as libc::c_ulong
} else if c < 0xf0i32 as libc::c_uint {
utf8total = 3i32 as libc::c_uint;
ucs4 = (c & 0xfi32 as libc::c_uint) as libc::c_ulong
} else {
utf8total = 4i32 as libc::c_uint;
ucs4 = (c & 0x3i32 as libc::c_uint) as libc::c_ulong
}
continue;
}
utf8total = 0i32 as libc::c_uint;
loop {
if ucs4 >= 0x10000 {
ucs4 = ucs4.wrapping_sub(0x10000);
bitbuf = bitbuf << 16 | (ucs4 >> 10).wrapping_add(0xd800);
ucs4 = (ucs4 & 0x3ff).wrapping_add(0xdc00);
utf16flag = 1i32 as libc::c_uint
} else {
bitbuf = bitbuf << 16 | ucs4;
utf16flag = 0i32 as libc::c_uint
}
bitstogo = bitstogo.wrapping_add(16i32 as libc::c_uint);
while bitstogo >= 6i32 as libc::c_uint {
bitstogo = bitstogo.wrapping_sub(6i32 as libc::c_uint);
let fresh14 = dst;
dst = dst.offset(1);
*fresh14 = BASE64CHARS[(if 0 != bitstogo {
bitbuf >> bitstogo
} else {
bitbuf
} & 0x3f) as usize]
}
if !(0 != utf16flag) {
break;
}
}
let res = to_string(out);
free(out.cast());
res
}
}
if 0 != utf7mode {
if 0 != bitstogo {
let fresh15 = dst;
dst = dst.offset(1);
*fresh15 = BASE64CHARS
[(bitbuf << (6i32 as libc::c_uint).wrapping_sub(bitstogo) & 0x3f) as usize]
}
let fresh16 = dst;
dst = dst.offset(1);
*fresh16 = '-' as i32 as libc::c_char
}
*dst = '\u{0}' as i32 as libc::c_char;
res
}
/* ******************************************************************************
* Encode/decode modified UTF-7 as needed for IMAP, see RFC 2192
******************************************************************************/
pub fn dc_needs_ext_header(to_check: impl AsRef<str>) -> bool {
let to_check = to_check.as_ref();
// UTF7 modified base64 alphabet
#[cfg(test)]
static mut BASE64CHARS: [libc::c_char; 65] = [
65, 66, 67, 68, 69, 70, 71, 72, 73, 74, 75, 76, 77, 78, 79, 80, 81, 82, 83, 84, 85, 86, 87, 88,
89, 90, 97, 98, 99, 100, 101, 102, 103, 104, 105, 106, 107, 108, 109, 110, 111, 112, 113, 114,
115, 116, 117, 118, 119, 120, 121, 122, 48, 49, 50, 51, 52, 53, 54, 55, 56, 57, 43, 44, 0,
];
#[cfg(test)]
unsafe fn dc_decode_modified_utf7(
to_decode: *const libc::c_char,
change_spaces: libc::c_int,
) -> *mut libc::c_char {
let mut c: libc::c_uint;
let mut i: libc::c_uint;
let mut bitcount: libc::c_uint;
let mut ucs4: libc::c_ulong;
let mut utf16: libc::c_ulong;
let mut bitbuf: libc::c_ulong;
let mut base64: [libc::c_uchar; 256] = [0; 256];
let mut src: *const libc::c_char;
let mut dst: *mut libc::c_char;
let res: *mut libc::c_char;
if to_decode.is_null() {
return dc_strdup(b"\x00" as *const u8 as *const libc::c_char);
if to_check.is_empty() {
return false;
}
res = malloc(4usize.wrapping_mul(strlen(to_decode)).wrapping_add(1)) as *mut libc::c_char;
dst = res;
src = to_decode;
assert!(!dst.is_null());
libc::memset(
base64.as_mut_ptr() as *mut libc::c_void,
64,
::std::mem::size_of::<[libc::c_uchar; 256]>(),
);
i = 0i32 as libc::c_uint;
while (i as libc::c_ulong) < ::std::mem::size_of::<[libc::c_char; 65]>() as libc::c_ulong {
base64[BASE64CHARS[i as usize] as libc::c_uint as usize] = i as libc::c_uchar;
i = i.wrapping_add(1)
}
while *src as libc::c_int != '\u{0}' as i32 {
let fresh17 = src;
src = src.offset(1);
c = *fresh17 as libc::c_uint;
if c != '&' as i32 as libc::c_uint || *src as libc::c_int == '-' as i32 {
if 0 != change_spaces && c == '_' as i32 as libc::c_uint {
let fresh18 = dst;
dst = dst.offset(1);
*fresh18 = ' ' as i32 as libc::c_char
} else {
let fresh19 = dst;
dst = dst.offset(1);
*fresh19 = c as libc::c_char
}
if c == '&' as i32 as libc::c_uint {
src = src.offset(1isize)
}
} else {
bitbuf = 0;
bitcount = 0i32 as libc::c_uint;
ucs4 = 0;
loop {
c = base64[*src as libc::c_uchar as usize] as libc::c_uint;
if !(c != 64i32 as libc::c_uint) {
break;
}
src = src.offset(1isize);
bitbuf = bitbuf << 6i32 | c as libc::c_ulong;
bitcount = bitcount.wrapping_add(6i32 as libc::c_uint);
// enough bits for a UTF-16 character?
if !(bitcount >= 16i32 as libc::c_uint) {
continue;
}
bitcount = bitcount.wrapping_sub(16i32 as libc::c_uint);
utf16 = if 0 != bitcount {
bitbuf >> bitcount
} else {
bitbuf
} & 0xffff;
// convert UTF16 to UCS4
if utf16 >= 0xd800 && utf16 <= 0xdbff {
ucs4 = utf16.wrapping_sub(0xd800) << 10i32
} else {
if utf16 >= 0xdc00 && utf16 <= 0xdfff {
ucs4 = ucs4.wrapping_add(utf16.wrapping_sub(0xdc00).wrapping_add(0x10000))
} else {
ucs4 = utf16
}
if ucs4 <= 0x7f {
*dst.offset(0isize) = ucs4 as libc::c_char;
dst = dst.offset(1isize)
} else if ucs4 <= 0x7ff {
*dst.offset(0isize) = (0xc0 | ucs4 >> 6i32) as libc::c_char;
*dst.offset(1isize) = (0x80 | ucs4 & 0x3f) as libc::c_char;
dst = dst.offset(2isize)
} else if ucs4 <= 0xffff {
*dst.offset(0isize) = (0xe0 | ucs4 >> 12i32) as libc::c_char;
*dst.offset(1isize) = (0x80 | ucs4 >> 6i32 & 0x3f) as libc::c_char;
*dst.offset(2isize) = (0x80 | ucs4 & 0x3f) as libc::c_char;
dst = dst.offset(3isize)
} else {
*dst.offset(0isize) = (0xf0 | ucs4 >> 18i32) as libc::c_char;
*dst.offset(1isize) = (0x80 | ucs4 >> 12i32 & 0x3f) as libc::c_char;
*dst.offset(2isize) = (0x80 | ucs4 >> 6i32 & 0x3f) as libc::c_char;
*dst.offset(3isize) = (0x80 | ucs4 & 0x3f) as libc::c_char;
dst = dst.offset(4isize)
}
}
}
if *src as libc::c_int == '-' as i32 {
src = src.offset(1isize)
}
}
}
*dst = '\u{0}' as i32 as libc::c_char;
res
to_check.chars().any(|c| {
!(c.is_ascii_alphanumeric()
|| c == '-'
|| c == '_'
|| c == '_'
|| c == '.'
|| c == '~'
|| c == '%')
})
}
pub unsafe fn dc_needs_ext_header(mut to_check: *const libc::c_char) -> bool {
if !to_check.is_null() {
while 0 != *to_check {
if 0 == isalnum(*to_check as libc::c_int)
&& *to_check as libc::c_int != '-' as i32
&& *to_check as libc::c_int != '_' as i32
&& *to_check as libc::c_int != '.' as i32
&& *to_check as libc::c_int != '~' as i32
{
return true;
}
to_check = to_check.offset(1isize)
}
}
const EXT_ASCII_ST: &AsciiSet = &CONTROLS
.add(b' ')
.add(b'-')
.add(b'_')
.add(b'.')
.add(b'~')
.add(b'%');
false
/// Encode an UTF-8 string to the extended header format.
pub fn dc_encode_ext_header(to_encode: impl AsRef<str>) -> String {
let encoded = utf8_percent_encode(to_encode.as_ref(), &EXT_ASCII_ST);
format!("utf-8''{}", encoded)
}
pub unsafe fn dc_encode_ext_header(to_encode: *const libc::c_char) -> *mut libc::c_char {
let mut pstr: *const libc::c_char = to_encode;
if to_encode.is_null() {
return dc_strdup(b"utf-8\'\'\x00" as *const u8 as *const libc::c_char);
}
let buf: *mut libc::c_char = malloc(
strlen(b"utf-8\'\'\x00" as *const u8 as *const libc::c_char)
.wrapping_add(strlen(to_encode).wrapping_mul(3))
.wrapping_add(1),
) as *mut libc::c_char;
assert!(!buf.is_null());
let mut pbuf: *mut libc::c_char = buf;
strcpy(pbuf, b"utf-8\'\'\x00" as *const u8 as *const libc::c_char);
pbuf = pbuf.offset(strlen(pbuf) as isize);
while 0 != *pstr {
if 0 != isalnum(*pstr as libc::c_int)
|| *pstr as libc::c_int == '-' as i32
|| *pstr as libc::c_int == '_' as i32
|| *pstr as libc::c_int == '.' as i32
|| *pstr as libc::c_int == '~' as i32
{
let fresh20 = pbuf;
pbuf = pbuf.offset(1);
*fresh20 = *pstr
} else {
let fresh21 = pbuf;
pbuf = pbuf.offset(1);
*fresh21 = '%' as i32 as libc::c_char;
let fresh22 = pbuf;
pbuf = pbuf.offset(1);
*fresh22 = int_2_uppercase_hex((*pstr as libc::c_int >> 4i32) as libc::c_char);
let fresh23 = pbuf;
pbuf = pbuf.offset(1);
*fresh23 = int_2_uppercase_hex((*pstr as libc::c_int & 15i32) as libc::c_char)
}
pstr = pstr.offset(1isize)
}
*pbuf = '\u{0}' as i32 as libc::c_char;
buf
}
pub unsafe fn dc_decode_ext_header(to_decode: *const libc::c_char) -> *mut libc::c_char {
let mut decoded: *mut libc::c_char = ptr::null_mut();
let mut charset: *mut libc::c_char = ptr::null_mut();
let mut p2: *const libc::c_char;
if !to_decode.is_null() {
// get char set
p2 = strchr(to_decode, '\'' as i32);
if !(p2.is_null() || p2 == to_decode) {
/*no empty charset allowed*/
charset =
dc_null_terminate(to_decode, p2.wrapping_offset_from(to_decode) as libc::c_int);
p2 = p2.offset(1isize);
/// Decode an extended-header-format strings to UTF-8.
pub fn dc_decode_ext_header(to_decode: &[u8]) -> Cow<str> {
if let Some(index) = bytes!(b'\'').find(to_decode) {
let (charset, rest) = to_decode.split_at(index);
if !charset.is_empty() {
// skip language
p2 = strchr(p2, '\'' as i32);
if !p2.is_null() {
p2 = p2.offset(1isize);
decoded = dc_urldecode(p2);
if !charset.is_null()
&& strcmp(charset, b"utf-8\x00" as *const u8 as *const libc::c_char) != 0i32
&& strcmp(charset, b"UTF-8\x00" as *const u8 as *const libc::c_char) != 0i32
{
if let Some(encoding) =
Charset::for_label(CStr::from_ptr(charset).to_str().unwrap().as_bytes())
{
let data =
std::slice::from_raw_parts(decoded as *const u8, strlen(decoded));
if let Some(index2) = bytes!(b'\'').find(&rest[1..]) {
let decoded = percent_decode(&rest[index2 + 2..]);
let (res, _, _) = encoding.decode(data);
free(decoded as *mut _);
let r = std::ffi::CString::new(res.as_bytes()).unwrap();
decoded = dc_strdup(r.as_ptr());
if charset != b"utf-8" && charset != b"UTF-8" {
if let Some(encoding) = Charset::for_label(charset) {
let bytes = decoded.collect::<Vec<u8>>();
let (res, _, _) = encoding.decode(&bytes);
return Cow::Owned(res.into_owned());
} else {
return decoded.decode_utf8_lossy();
}
} else {
return decoded.decode_utf8_lossy();
}
}
}
}
free(charset as *mut libc::c_void);
if !decoded.is_null() {
decoded
} else {
dc_strdup(to_decode)
}
String::from_utf8_lossy(to_decode)
}
unsafe fn print_hex(target: *mut libc::c_char, cur: *const libc::c_char) {
@@ -672,16 +336,8 @@ unsafe fn print_hex(target: *mut libc::c_char, cur: *const libc::c_char) {
#[cfg(test)]
mod tests {
use super::*;
use percent_encoding::{percent_encode, NON_ALPHANUMERIC};
use std::ffi::CStr;
#[test]
fn test_isalnum() {
assert_eq!(isalnum(0), 0);
assert_eq!(isalnum('5' as libc::c_int), 1);
assert_eq!(isalnum('Q' as libc::c_int), 1);
}
#[test]
fn test_dc_decode_header_words() {
unsafe {
@@ -702,12 +358,13 @@ mod tests {
assert_eq!(CStr::from_ptr(buf1).to_str().unwrap(), "just ascii test");
free(buf1 as *mut libc::c_void);
buf1 = dc_encode_header_words(b"abcdef\x00" as *const u8 as *const libc::c_char);
buf1 = dc_encode_header_words("abcdef");
assert_eq!(CStr::from_ptr(buf1).to_str().unwrap(), "abcdef");
free(buf1 as *mut libc::c_void);
buf1 = dc_encode_header_words(
b"test\xc3\xa4\xc3\xb6\xc3\xbc.txt\x00" as *const u8 as *const libc::c_char,
std::string::String::from_utf8(b"test\xc3\xa4\xc3\xb6\xc3\xbc.txt".to_vec())
.unwrap(),
);
assert_eq!(
strncmp(buf1, b"=?utf-8\x00" as *const u8 as *const libc::c_char, 7),
@@ -742,99 +399,43 @@ mod tests {
#[test]
fn test_dc_encode_ext_header() {
unsafe {
let mut buf1 = dc_encode_ext_header(
b"Bj\xc3\xb6rn Petersen\x00" as *const u8 as *const libc::c_char,
);
assert_eq!(
CStr::from_ptr(buf1).to_str().unwrap(),
"utf-8\'\'Bj%C3%B6rn%20Petersen"
);
let buf2 = dc_decode_ext_header(buf1);
assert_eq!(
strcmp(
buf2,
b"Bj\xc3\xb6rn Petersen\x00" as *const u8 as *const libc::c_char,
),
0
);
free(buf1 as *mut libc::c_void);
free(buf2 as *mut libc::c_void);
let buf1 = dc_encode_ext_header("Björn Petersen");
assert_eq!(&buf1, "utf-8\'\'Bj%C3%B6rn%20Petersen");
let buf2 = dc_decode_ext_header(buf1.as_bytes());
assert_eq!(&buf2, "Björn Petersen",);
buf1 = dc_decode_ext_header(
b"iso-8859-1\'en\'%A3%20rates\x00" as *const u8 as *const libc::c_char,
);
assert_eq!(
strcmp(
buf1,
b"\xc2\xa3 rates\x00" as *const u8 as *const libc::c_char,
),
0
);
free(buf1 as *mut libc::c_void);
let buf1 = dc_decode_ext_header(b"iso-8859-1\'en\'%A3%20rates");
assert_eq!(buf1, "£ rates",);
buf1 = dc_decode_ext_header(b"wrong\'format\x00" as *const u8 as *const libc::c_char);
assert_eq!(
strcmp(
buf1,
b"wrong\'format\x00" as *const u8 as *const libc::c_char,
),
0
);
free(buf1 as *mut libc::c_void);
let buf1 = dc_decode_ext_header(b"wrong\'format");
assert_eq!(buf1, "wrong\'format",);
buf1 = dc_decode_ext_header(b"\'\'\x00" as *const u8 as *const libc::c_char);
assert_eq!(
strcmp(buf1, b"\'\'\x00" as *const u8 as *const libc::c_char),
0
);
free(buf1 as *mut libc::c_void);
let buf1 = dc_decode_ext_header(b"\'\'");
assert_eq!(buf1, "\'\'");
buf1 = dc_decode_ext_header(b"x\'\'\x00" as *const u8 as *const libc::c_char);
assert_eq!(strcmp(buf1, b"\x00" as *const u8 as *const libc::c_char), 0);
free(buf1 as *mut libc::c_void);
let buf1 = dc_decode_ext_header(b"x\'\'");
assert_eq!(buf1, "");
buf1 = dc_decode_ext_header(b"\'\x00" as *const u8 as *const libc::c_char);
assert_eq!(
strcmp(buf1, b"\'\x00" as *const u8 as *const libc::c_char),
0
);
free(buf1 as *mut libc::c_void);
let buf1 = dc_decode_ext_header(b"\'");
assert_eq!(buf1, "\'");
buf1 = dc_decode_ext_header(b"\x00" as *const u8 as *const libc::c_char);
assert_eq!(strcmp(buf1, b"\x00" as *const u8 as *const libc::c_char), 0);
free(buf1 as *mut libc::c_void);
}
let buf1 = dc_decode_ext_header(b"");
assert_eq!(buf1, "");
// regressions
assert_eq!(
dc_decode_ext_header(dc_encode_ext_header("%0A").as_bytes()),
"%0A"
);
}
#[test]
fn test_dc_needs_ext_header() {
unsafe {
assert_eq!(
dc_needs_ext_header(b"Bj\xc3\xb6rn\x00" as *const u8 as *const libc::c_char),
true
);
assert_eq!(
dc_needs_ext_header(b"Bjoern\x00" as *const u8 as *const libc::c_char),
false
);
assert_eq!(
dc_needs_ext_header(b"\x00" as *const u8 as *const libc::c_char),
false
);
assert_eq!(
dc_needs_ext_header(b" \x00" as *const u8 as *const libc::c_char),
true
);
assert_eq!(
dc_needs_ext_header(b"a b\x00" as *const u8 as *const libc::c_char),
true
);
assert_eq!(
dc_needs_ext_header(0 as *const u8 as *const libc::c_char),
false
);
}
assert_eq!(dc_needs_ext_header("Björn"), true);
assert_eq!(dc_needs_ext_header("Bjoern"), false);
assert_eq!(dc_needs_ext_header(""), false);
assert_eq!(dc_needs_ext_header(" "), true);
assert_eq!(dc_needs_ext_header("a b"), true);
}
#[test]
@@ -849,69 +450,20 @@ mod tests {
assert_eq!(to_string(hex.as_ptr() as *const _), "=3A");
}
#[test]
fn test_dc_urlencode_urldecode() {
unsafe {
let buf1 = percent_encode(b"Bj\xc3\xb6rn Petersen", NON_ALPHANUMERIC)
.to_string()
.strdup();
use proptest::prelude::*;
assert_eq!(
CStr::from_ptr(buf1 as *const libc::c_char)
.to_str()
.unwrap(),
"Bj%C3%B6rn%20Petersen"
);
let buf2 = dc_urldecode(buf1);
assert_eq!(
strcmp(
buf2,
b"Bj\xc3\xb6rn Petersen\x00" as *const u8 as *const libc::c_char
),
0
);
free(buf1 as *mut libc::c_void);
free(buf2 as *mut libc::c_void);
proptest! {
#[test]
fn test_ext_header_roundtrip(buf: String) {
let encoded = dc_encode_ext_header(&buf);
let decoded = dc_decode_ext_header(encoded.as_bytes());
assert_eq!(buf, decoded);
}
}
#[test]
fn test_dc_encode_decode_modified_utf7() {
unsafe {
let buf1 = dc_encode_modified_utf7(
b"Bj\xc3\xb6rn Petersen\x00" as *const u8 as *const libc::c_char,
1,
);
assert_eq!(
CStr::from_ptr(buf1 as *const libc::c_char)
.to_str()
.unwrap(),
"Bj&APY-rn_Petersen"
);
let buf2 = dc_decode_modified_utf7(buf1, 1);
assert_eq!(
strcmp(
buf2,
b"Bj\xc3\xb6rn Petersen\x00" as *const u8 as *const libc::c_char
),
0
);
free(buf1 as *mut libc::c_void);
free(buf2 as *mut libc::c_void);
#[test]
fn test_ext_header_decode_anything(buf: Vec<u8>) {
// make sure this never panics
let _decoded = dc_decode_ext_header(&buf);
}
}
#[test]
fn test_hex_to_int() {
assert_eq!(hex_2_int(b'A' as libc::c_char), 10);
assert_eq!(hex_2_int(b'a' as libc::c_char), 10);
assert_eq!(hex_2_int(b'4' as libc::c_char), 4);
assert_eq!(hex_2_int(b'K' as libc::c_char), 20);
}
}

View File

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

View File

@@ -1,25 +1,23 @@
//! Some tools and enhancements to the used libraries, there should be
//! no references to Context and other "larger" entities here.
use std::borrow::Cow;
use std::ffi::{CStr, CString};
use std::path::Path;
use std::ffi::{CStr, CString, OsString};
use std::path::{Path, PathBuf};
use std::str::FromStr;
use std::time::SystemTime;
use std::{fmt, fs, ptr};
use chrono::{Local, TimeZone};
use libc::uintptr_t;
use mmime::clist::*;
use mmime::mailimf_types::*;
use rand::{thread_rng, Rng};
use crate::context::Context;
use crate::error::Error;
use crate::types::*;
use crate::x::*;
use itertools::max;
/* Some tools and enhancements to the used libraries, there should be
no references to Context and other "larger" classes here. */
/* ** library-private **********************************************************/
/* math tools */
pub fn dc_exactly_one_bit_set(v: libc::c_int) -> bool {
0 != v && 0 == v & (v - 1)
}
@@ -83,14 +81,6 @@ pub unsafe fn dc_atoi_null_is_0(s: *const libc::c_char) -> libc::c_int {
}
}
pub fn dc_atof(s: *const libc::c_char) -> libc::c_double {
if s.is_null() {
return 0.;
}
as_str(s).parse().unwrap_or_default()
}
pub unsafe fn dc_str_replace(
haystack: *mut *mut libc::c_char,
needle: *const libc::c_char,
@@ -105,23 +95,8 @@ pub unsafe fn dc_str_replace(
*haystack = haystack_s.replace(&needle_s, &replacement_s).strdup();
}
pub unsafe fn dc_ftoa(f: libc::c_double) -> *mut libc::c_char {
// hack around printf(%f) that may return `,` as decimal point on mac
let test: *mut libc::c_char = dc_mprintf(b"%f\x00" as *const u8 as *const libc::c_char, 1.2f64);
*test.offset(2isize) = 0 as libc::c_char;
let mut str: *mut libc::c_char = dc_mprintf(b"%f\x00" as *const u8 as *const libc::c_char, f);
dc_str_replace(
&mut str,
test.offset(1isize),
b".\x00" as *const u8 as *const libc::c_char,
);
free(test as *mut libc::c_void);
str
}
unsafe fn dc_ltrim(buf: *mut libc::c_char) {
let mut len: size_t;
let mut len: libc::size_t;
let mut cur: *const libc::c_uchar;
if !buf.is_null() && 0 != *buf as libc::c_int {
len = strlen(buf);
@@ -141,7 +116,7 @@ unsafe fn dc_ltrim(buf: *mut libc::c_char) {
}
unsafe fn dc_rtrim(buf: *mut libc::c_char) {
let mut len: size_t;
let mut len: libc::size_t;
let mut cur: *mut libc::c_uchar;
if !buf.is_null() && 0 != *buf as libc::c_int {
len = strlen(buf);
@@ -284,11 +259,18 @@ pub unsafe fn dc_replace_bad_utf8_chars(buf: *mut libc::c_char) {
pub fn dc_truncate(buf: &str, approx_chars: usize, do_unwrap: bool) -> Cow<str> {
let ellipse = if do_unwrap { "..." } else { "[...]" };
if approx_chars > 0 && buf.len() > approx_chars + ellipse.len() {
if let Some(index) = buf[..approx_chars].rfind(|c| c == ' ' || c == '\n') {
let count = buf.chars().count();
if approx_chars > 0 && count > approx_chars + ellipse.len() {
let end_pos = buf
.char_indices()
.nth(approx_chars)
.map(|(n, _)| n)
.unwrap_or_default();
if let Some(index) = buf[..end_pos].rfind(|c| c == ' ' || c == '\n') {
Cow::Owned(format!("{}{}", &buf[..index + 1], ellipse))
} else {
Cow::Owned(format!("{}{}", &buf[..approx_chars], ellipse))
Cow::Owned(format!("{}{}", &buf[..end_pos], ellipse))
}
} else {
Cow::Borrowed(buf)
@@ -317,9 +299,9 @@ pub unsafe fn dc_truncate_n_unwrap_str(
if *p1 as libc::c_int > ' ' as i32 {
lastIsCharacter = 1
} else if 0 != lastIsCharacter {
let used_bytes: size_t = (p1 as uintptr_t).wrapping_sub(buf as uintptr_t) as size_t;
let used_bytes = (p1 as uintptr_t).wrapping_sub(buf as uintptr_t) as libc::size_t;
if dc_utf8_strnlen(buf, used_bytes) >= approx_characters as usize {
let buf_bytes: size_t = strlen(buf);
let buf_bytes = strlen(buf);
if buf_bytes.wrapping_sub(used_bytes) >= strlen(ellipse_utf8) {
strcpy(p1 as *mut libc::c_char, ellipse_utf8);
}
@@ -340,12 +322,12 @@ pub unsafe fn dc_truncate_n_unwrap_str(
};
}
unsafe fn dc_utf8_strnlen(s: *const libc::c_char, n: size_t) -> size_t {
unsafe fn dc_utf8_strnlen(s: *const libc::c_char, n: libc::size_t) -> libc::size_t {
if s.is_null() {
return 0;
}
let mut j: size_t = 0;
let mut j: libc::size_t = 0;
for i in 0..n {
if *s.add(i) as libc::c_int & 0xc0 != 0x80 {
j = j.wrapping_add(1)
@@ -458,21 +440,10 @@ pub unsafe fn clist_free_content(haystack: *const clist) {
pub unsafe fn clist_search_string_nocase(
haystack: *const clist,
needle: *const libc::c_char,
) -> libc::c_int {
let mut iter = (*haystack).first;
while !iter.is_null() {
if strcasecmp((*iter).data as *const libc::c_char, needle) == 0 {
return 1;
}
iter = if !iter.is_null() {
(*iter).next
} else {
ptr::null_mut()
}
}
0
) -> bool {
(&*haystack)
.into_iter()
.any(|data| strcasecmp(data.cast(), needle) == 0)
}
/* date/time tools */
@@ -517,7 +488,7 @@ pub fn dc_gm2local_offset() -> i64 {
}
/* timesmearing */
pub unsafe fn dc_smeared_time(context: &Context) -> i64 {
pub fn dc_smeared_time(context: &Context) -> i64 {
/* function returns a corrected time(NULL) */
let mut now = time();
let ts = *context.last_smeared_timestamp.clone().read().unwrap();
@@ -570,7 +541,7 @@ pub fn dc_create_id() -> String {
- the group-id should be a string with the characters [a-zA-Z0-9\-_] */
let mut rng = thread_rng();
let buf: [uint32_t; 3] = [rng.gen(), rng.gen(), rng.gen()];
let buf: [u32; 3] = [rng.gen(), rng.gen(), rng.gen()];
encode_66bits_as_base64(buf[0usize], buf[1usize], buf[2usize])
}
@@ -597,7 +568,7 @@ fn encode_66bits_as_base64(v1: u32, v2: u32, fill: u32) -> String {
String::from_utf8(wrapped_writer).unwrap()
}
pub unsafe fn dc_create_incoming_rfc724_mid(
pub fn dc_create_incoming_rfc724_mid(
message_timestamp: i64,
contact_id_from: u32,
contact_ids_to: &Vec<u32>,
@@ -606,51 +577,51 @@ pub unsafe fn dc_create_incoming_rfc724_mid(
return ptr::null_mut();
}
/* find out the largest receiver ID (we could also take the smallest, but it should be unique) */
let largest_id_to = max(contact_ids_to.iter());
let largest_id_to = contact_ids_to.iter().max().copied().unwrap_or_default();
dc_mprintf(
b"%lu-%lu-%lu@stub\x00" as *const u8 as *const libc::c_char,
message_timestamp as libc::c_ulong,
contact_id_from as libc::c_ulong,
*largest_id_to.unwrap() as libc::c_ulong,
)
let result = format!(
"{}-{}-{}@stub",
message_timestamp, contact_id_from, largest_id_to
);
unsafe { result.strdup() }
}
/// Function generates a Message-ID that can be used for a new outgoing message.
/// - this function is called for all outgoing messages.
/// - the message ID should be globally unique
/// - do not add a counter or any private data as as this may give unneeded information to the receiver
pub unsafe fn dc_create_outgoing_rfc724_mid(
grpid: *const libc::c_char,
from_addr: *const libc::c_char,
) -> *mut libc::c_char {
/* Function generates a Message-ID that can be used for a new outgoing message.
- this function is called for all outgoing messages.
- the message ID should be globally unique
- do not add a counter or any private data as as this may give unneeded information to the receiver */
let mut rand1: *mut libc::c_char = ptr::null_mut();
let rand2: *mut libc::c_char = dc_create_id().strdup();
let ret: *mut libc::c_char;
let mut at_hostname: *const libc::c_char = strchr(from_addr, '@' as i32);
if at_hostname.is_null() {
at_hostname = b"@nohost\x00" as *const u8 as *const libc::c_char
}
if !grpid.is_null() {
ret = dc_mprintf(
b"Gr.%s.%s%s\x00" as *const u8 as *const libc::c_char,
grpid,
rand2,
at_hostname,
)
} else {
rand1 = dc_create_id().strdup();
ret = dc_mprintf(
b"Mr.%s.%s%s\x00" as *const u8 as *const libc::c_char,
rand1,
rand2,
at_hostname,
)
}
free(rand1 as *mut libc::c_void);
free(rand2 as *mut libc::c_void);
let rand2 = dc_create_id();
ret
let at_hostname = as_opt_str(strchr(from_addr, '@' as i32)).unwrap_or_else(|| "@nohost");
let ret = if !grpid.is_null() {
format!("Gr.{}.{}{}", as_str(grpid), rand2, at_hostname,)
} else {
let rand1 = dc_create_id();
format!("Mr.{}.{}{}", rand1, rand2, at_hostname)
};
ret.strdup()
}
/// Generate globally-unique message-id for a new outgoing message.
///
/// Note: Do not add a counter or any private data as as this may give
/// unneeded information to the receiver
pub fn dc_create_outgoing_rfc724_mid_safe(grpid: Option<&str>, from_addr: &str) -> String {
let hostname = from_addr
.find('@')
.map(|k| &from_addr[k..])
.unwrap_or("@nohost");
match grpid {
Some(grpid) => format!("Gr.{}.{}{}", grpid, dc_create_id(), hostname),
None => format!("Mr.{}.{}{}", dc_create_id(), dc_create_id(), hostname),
}
}
/// Extract the group id (grpid) from a message id (mid)
@@ -708,16 +679,6 @@ pub unsafe fn dc_extract_grpid_from_rfc724_mid_list(list: *const clist) -> *mut
ptr::null_mut()
}
#[allow(non_snake_case)]
unsafe fn dc_ensure_no_slash(pathNfilename: *mut libc::c_char) {
let path_len = strlen(pathNfilename);
if path_len > 0 && *pathNfilename.add(path_len - 1) as libc::c_int == '/' as i32
|| *pathNfilename.add(path_len - 1) as libc::c_int == '\\' as i32
{
*pathNfilename.add(path_len - 1) = 0 as libc::c_char;
}
}
pub fn dc_ensure_no_slash_safe(path: &str) -> &str {
if path.ends_with('/') || path.ends_with('\\') {
return &path[..path.len() - 1];
@@ -725,18 +686,12 @@ pub fn dc_ensure_no_slash_safe(path: &str) -> &str {
path
}
unsafe fn dc_validate_filename(filename: *mut libc::c_char) {
/* function modifies the given buffer and replaces all characters not valid in filenames by a "-" */
let mut p1: *mut libc::c_char = filename;
while 0 != *p1 {
if *p1 as libc::c_int == '/' as i32
|| *p1 as libc::c_int == '\\' as i32
|| *p1 as libc::c_int == ':' as i32
{
*p1 = '-' as i32 as libc::c_char
}
p1 = p1.offset(1isize)
}
/// Function modifies the given buffer and replaces all characters not valid in filenames by a "-".
fn validate_filename(filename: &str) -> String {
filename
.replace('/', "-")
.replace('\\', "-")
.replace(':', "-")
}
pub unsafe fn dc_get_filename(path_filename: impl AsRef<str>) -> *mut libc::c_char {
@@ -747,42 +702,6 @@ pub unsafe fn dc_get_filename(path_filename: impl AsRef<str>) -> *mut libc::c_ch
}
}
// the case of the suffix is preserved
#[allow(non_snake_case)]
unsafe fn dc_split_filename(
pathNfilename: *const libc::c_char,
ret_basename: *mut *mut libc::c_char,
ret_all_suffixes_incl_dot: *mut *mut libc::c_char,
) {
if pathNfilename.is_null() {
return;
}
/* splits a filename into basename and all suffixes, eg. "/path/foo.tar.gz" is split into "foo.tar" and ".gz",
(we use the _last_ dot which allows the usage inside the filename which are very usual;
maybe the detection could be more intelligent, however, for the moment, it is just file)
- if there is no suffix, the returned suffix string is empty, eg. "/path/foobar" is split into "foobar" and ""
- the case of the returned suffix is preserved; this is to allow reconstruction of (similar) names */
let basename: *mut libc::c_char = dc_get_filename(as_str(pathNfilename));
let suffix: *mut libc::c_char;
let p1: *mut libc::c_char = strrchr(basename, '.' as i32);
if !p1.is_null() {
suffix = dc_strdup(p1);
*p1 = 0 as libc::c_char
} else {
suffix = dc_strdup(ptr::null())
}
if !ret_basename.is_null() {
*ret_basename = basename
} else {
free(basename as *mut libc::c_void);
}
if !ret_all_suffixes_incl_dot.is_null() {
*ret_all_suffixes_incl_dot = suffix
} else {
free(suffix as *mut libc::c_void);
};
}
// the returned suffix is lower-case
#[allow(non_snake_case)]
pub unsafe fn dc_get_filesuffix_lc(path_filename: impl AsRef<str>) -> *mut libc::c_char {
@@ -804,67 +723,45 @@ pub fn dc_get_filemeta(buf: &[u8]) -> Result<(u32, u32), Error> {
///
/// If `path` starts with "$BLOBDIR", replaces it with the blobdir path.
/// Otherwise, returns path as is.
pub fn dc_get_abs_path_safe<P: AsRef<std::path::Path>>(
pub fn dc_get_abs_path<P: AsRef<std::path::Path>>(
context: &Context,
path: P,
) -> std::path::PathBuf {
let p: &std::path::Path = path.as_ref();
if let Ok(p) = p.strip_prefix("$BLOBDIR") {
assert!(
context.has_blobdir(),
"Expected context to have blobdir to substitute $BLOBDIR",
);
std::path::PathBuf::from(as_str(context.get_blobdir())).join(p)
context.get_blobdir().join(p)
} else {
p.into()
}
}
pub unsafe fn dc_get_abs_path(
context: &Context,
path_filename: impl AsRef<str>,
) -> *mut libc::c_char {
let starts = path_filename.as_ref().starts_with("$BLOBDIR");
if starts && !context.has_blobdir() {
return ptr::null_mut();
}
let mut path_filename_abs = path_filename.as_ref().strdup();
if starts && context.has_blobdir() {
dc_str_replace(
&mut path_filename_abs,
b"$BLOBDIR\x00" as *const u8 as *const libc::c_char,
context.get_blobdir(),
);
}
path_filename_abs
}
pub fn dc_file_exist(context: &Context, path: impl AsRef<std::path::Path>) -> bool {
dc_get_abs_path_safe(context, &path).exists()
dc_get_abs_path(context, &path).exists()
}
pub fn dc_get_filebytes(context: &Context, path: impl AsRef<std::path::Path>) -> uint64_t {
let path_abs = dc_get_abs_path_safe(context, &path);
pub fn dc_get_filebytes(context: &Context, path: impl AsRef<std::path::Path>) -> u64 {
let path_abs = dc_get_abs_path(context, &path);
match fs::metadata(&path_abs) {
Ok(meta) => meta.len() as uint64_t,
Ok(meta) => meta.len() as u64,
Err(_err) => 0,
}
}
pub fn dc_delete_file(context: &Context, path: impl AsRef<std::path::Path>) -> bool {
let path_abs = dc_get_abs_path_safe(context, &path);
let res = if path_abs.is_file() {
fs::remove_file(path_abs)
} else {
fs::remove_dir_all(path_abs)
};
let path_abs = dc_get_abs_path(context, &path);
if !path_abs.is_file() {
warn!(
context,
"Will not delete directory \"{}\".",
path.as_ref().display()
);
return false;
}
match res {
match fs::remove_file(path_abs) {
Ok(_) => true,
Err(_err) => {
warn!(context, 0, "Cannot delete \"{}\".", path.as_ref().display());
warn!(context, "Cannot delete \"{}\".", path.as_ref().display());
false
}
}
@@ -875,14 +772,13 @@ pub fn dc_copy_file(
src: impl AsRef<std::path::Path>,
dest: impl AsRef<std::path::Path>,
) -> bool {
let src_abs = dc_get_abs_path_safe(context, &src);
let dest_abs = dc_get_abs_path_safe(context, &dest);
let src_abs = dc_get_abs_path(context, &src);
let dest_abs = dc_get_abs_path(context, &dest);
match fs::copy(&src_abs, &dest_abs) {
Ok(_) => true,
Err(_) => {
error!(
context,
0,
"Cannot copy \"{}\" to \"{}\".",
src.as_ref().display(),
dest.as_ref().display(),
@@ -893,14 +789,13 @@ pub fn dc_copy_file(
}
pub fn dc_create_folder(context: &Context, path: impl AsRef<std::path::Path>) -> bool {
let path_abs = dc_get_abs_path_safe(context, &path);
let path_abs = dc_get_abs_path(context, &path);
if !path_abs.exists() {
match fs::create_dir_all(path_abs) {
Ok(_) => true,
Err(_err) => {
warn!(
context,
0,
"Cannot create directory \"{}\".",
path.as_ref().display(),
);
@@ -912,28 +807,12 @@ pub fn dc_create_folder(context: &Context, path: impl AsRef<std::path::Path>) ->
}
}
#[allow(non_snake_case)]
pub unsafe fn dc_write_file(
context: &Context,
pathNfilename: *const libc::c_char,
buf: *const libc::c_void,
buf_bytes: size_t,
) -> libc::c_int {
let bytes = std::slice::from_raw_parts(buf as *const u8, buf_bytes);
dc_write_file_safe(context, as_str(pathNfilename), bytes) as libc::c_int
}
pub fn dc_write_file_safe<P: AsRef<std::path::Path>>(
context: &Context,
path: P,
buf: &[u8],
) -> bool {
let path_abs = dc_get_abs_path_safe(context, &path);
/// Write a the given content to provied file path.
pub fn dc_write_file(context: &Context, path: impl AsRef<Path>, buf: &[u8]) -> bool {
let path_abs = dc_get_abs_path(context, &path);
if let Err(_err) = fs::write(&path_abs, buf) {
warn!(
context,
0,
"Cannot write {} bytes to \"{}\".",
buf.len(),
path.as_ref().display(),
@@ -949,7 +828,7 @@ pub unsafe fn dc_read_file(
context: &Context,
pathNfilename: *const libc::c_char,
buf: *mut *mut libc::c_void,
buf_bytes: *mut size_t,
buf_bytes: *mut libc::size_t,
) -> libc::c_int {
if pathNfilename.is_null() {
return 0;
@@ -965,13 +844,12 @@ pub unsafe fn dc_read_file(
}
pub fn dc_read_file_safe<P: AsRef<std::path::Path>>(context: &Context, path: P) -> Option<Vec<u8>> {
let path_abs = dc_get_abs_path_safe(context, &path);
let path_abs = dc_get_abs_path(context, &path);
match fs::read(&path_abs) {
Ok(bytes) => Some(bytes),
Err(_err) => {
warn!(
context,
0,
"Cannot read \"{}\" or file is empty.",
path.as_ref().display()
);
@@ -980,104 +858,83 @@ pub fn dc_read_file_safe<P: AsRef<std::path::Path>>(context: &Context, path: P)
}
}
#[allow(non_snake_case)]
pub unsafe fn dc_get_fine_pathNfilename(
pub fn dc_get_fine_path_filename(
context: &Context,
pathNfolder: *const libc::c_char,
desired_filenameNsuffix__: *const libc::c_char,
) -> *mut libc::c_char {
let mut ret: *mut libc::c_char = ptr::null_mut();
let pathNfolder_wo_slash: *mut libc::c_char;
let filenameNsuffix: *mut libc::c_char;
let mut basename: *mut libc::c_char = ptr::null_mut();
let mut dotNSuffix: *mut libc::c_char = ptr::null_mut();
folder: impl AsRef<Path>,
desired_filename_suffix: impl AsRef<str>,
) -> PathBuf {
let now = time();
pathNfolder_wo_slash = dc_strdup(pathNfolder);
dc_ensure_no_slash(pathNfolder_wo_slash);
filenameNsuffix = dc_strdup(desired_filenameNsuffix__);
dc_validate_filename(filenameNsuffix);
dc_split_filename(filenameNsuffix, &mut basename, &mut dotNSuffix);
let folder = PathBuf::from(folder.as_ref());
let suffix = validate_filename(desired_filename_suffix.as_ref());
for i in 0..1000i64 {
/*no deadlocks, please*/
if 0 != i {
let idx = if i < 100 { i } else { now + i };
ret = dc_mprintf(
b"%s/%s-%lu%s\x00" as *const u8 as *const libc::c_char,
pathNfolder_wo_slash,
basename,
idx as libc::c_ulong,
dotNSuffix,
)
let file_name = PathBuf::from(suffix);
let extension = file_name.extension().map(|c| c.clone());
for i in 0..100_000 {
let ret = if i == 0 {
let mut folder = folder.clone();
folder.push(&file_name);
folder
} else {
ret = dc_mprintf(
b"%s/%s%s\x00" as *const u8 as *const libc::c_char,
pathNfolder_wo_slash,
basename,
dotNSuffix,
)
let idx = if i < 100 { i } else { now + i };
let file_name = if let Some(stem) = file_name.file_stem() {
let mut stem = stem.to_os_string();
stem.push(format!("-{}", idx));
stem
} else {
OsString::from(idx.to_string())
};
let mut folder = folder.clone();
folder.push(file_name);
if let Some(ext) = extension {
folder.set_extension(&ext);
}
folder
};
if !dc_file_exist(context, &ret) {
// fine filename found
return ret;
}
if !dc_file_exist(context, as_path(ret)) {
/* fine filename found */
break;
}
free(ret as *mut libc::c_void);
ret = ptr::null_mut();
}
free(filenameNsuffix as *mut libc::c_void);
free(basename as *mut libc::c_void);
free(dotNSuffix as *mut libc::c_void);
free(pathNfolder_wo_slash as *mut libc::c_void);
ret
panic!("Something is really wrong, you need to clean up your disk");
}
pub fn dc_is_blobdir_path(context: &Context, path: impl AsRef<str>) -> bool {
path.as_ref().starts_with(as_str(context.get_blobdir()))
context
.get_blobdir()
.to_str()
.map(|s| path.as_ref().starts_with(s))
.unwrap_or_default()
|| path.as_ref().starts_with("$BLOBDIR")
}
fn dc_make_rel_path(context: &Context, path: &mut String) {
if path.starts_with(as_str(context.get_blobdir())) {
*path = path.replace("$BLOBDIR", as_str(context.get_blobdir()));
if context
.get_blobdir()
.to_str()
.map(|s| path.starts_with(s))
.unwrap_or_default()
{
*path = path.replace("$BLOBDIR", context.get_blobdir().to_str().unwrap());
}
}
pub fn dc_make_rel_and_copy(context: &Context, path: &mut String) -> bool {
let mut success = false;
let mut filename = ptr::null_mut();
let mut blobdir_path = ptr::null_mut();
if dc_is_blobdir_path(context, &path) {
dc_make_rel_path(context, path);
success = true;
} else {
filename = unsafe { dc_get_filename(&path) };
if !(filename.is_null()
|| {
blobdir_path = unsafe {
dc_get_fine_pathNfilename(
context,
b"$BLOBDIR\x00" as *const u8 as *const libc::c_char,
filename,
)
};
blobdir_path.is_null()
}
|| !dc_copy_file(context, &path, as_path(blobdir_path)))
{
*path = to_string(blobdir_path);
blobdir_path = ptr::null_mut();
dc_make_rel_path(context, path);
success = true;
}
return true;
}
unsafe {
free(blobdir_path.cast());
free(filename.cast());
let blobdir_path = dc_get_fine_path_filename(context, "$BLOBDIR", &path);
if dc_copy_file(context, &path, &blobdir_path) {
*path = blobdir_path.to_string_lossy().to_string();
dc_make_rel_path(context, path);
return true;
}
success
false
}
/// Error type for the [OsStrExt] trait
@@ -1383,6 +1240,32 @@ impl FromStr for EmailAddress {
}
}
/// Utility to check if a in the binary represantion of listflags
/// the bit at position bitindex is 1.
///
///
/// # Example
///
/// ```
/// use std::convert::TryInto;
/// use deltachat::dc_tools::listflags_has;
/// use deltachat::constants::{DC_GCL_ADD_SELF, DC_GCL_VERIFIED_ONLY};
/// let listflags: u32 = 0x1101;
/// assert!(listflags_has(listflags, 0x1) == true);
/// assert!(listflags_has(listflags, 0x10) == false);
/// assert!(listflags_has(listflags, 0x100) == true);
/// assert!(listflags_has(listflags, 0x1000) == true);
/// let listflags: u32 = (DC_GCL_ADD_SELF | DC_GCL_VERIFIED_ONLY).try_into().unwrap();
/// assert!(listflags_has(listflags, DC_GCL_VERIFIED_ONLY) == true);
/// assert!(listflags_has(listflags, DC_GCL_ADD_SELF) == true);
/// let listflags: u32 = DC_GCL_VERIFIED_ONLY.try_into().unwrap();
/// assert!(listflags_has(listflags, DC_GCL_ADD_SELF) == false);
/// ```
pub fn listflags_has(listflags: u32, bitindex: usize) -> bool {
let listflags = listflags as usize;
(listflags & bitindex) == bitindex
}
#[cfg(test)]
mod tests {
use super::*;
@@ -1479,23 +1362,6 @@ mod tests {
}
}
#[test]
fn test_dc_atof() {
let f: libc::c_double = dc_atof(b"1.23\x00" as *const u8 as *const libc::c_char);
assert!(f > 1.22f64);
assert!(f < 1.24f64);
}
#[test]
fn test_dc_ftoa() {
unsafe {
let s: *mut libc::c_char = dc_ftoa(1.23f64);
assert!(dc_atof(s) > 1.22f64);
assert!(dc_atof(s) < 1.24f64);
free(s as *mut libc::c_void);
}
}
#[test]
fn test_rust_ftoa() {
assert_eq!("1.22", format!("{}", 1.22));
@@ -1543,6 +1409,36 @@ mod tests {
assert_eq!(dc_truncate("123456", 4, true), "123456");
}
#[test]
fn test_dc_truncate_edge() {
assert_eq!(dc_truncate("", 4, false), "");
assert_eq!(dc_truncate("", 4, true), "");
assert_eq!(dc_truncate("\n hello \n world", 4, false), "\n [...]");
assert_eq!(dc_truncate("\n hello \n world", 4, true), "\n ...");
assert_eq!(
dc_truncate("𐠈0Aᝮa𫝀®!ꫛa¡0A𐢧00𐹠®A 丽ⷐએ", 1, false),
"𐠈[...]"
);
assert_eq!(
dc_truncate("𐠈0Aᝮa𫝀®!ꫛa¡0A𐢧00𐹠®A 丽ⷐએ", 0, false),
"𐠈0Aᝮa𫝀®!ꫛa¡0A𐢧00𐹠®A 丽ⷐએ"
);
// 9 characters, so no truncation
assert_eq!(
dc_truncate("𑒀ὐ¢🜀\u{1e01b}A a🟠", 6, false),
"𑒀ὐ¢🜀\u{1e01b}A a🟠",
);
// 12 characters, truncation
assert_eq!(
dc_truncate("𑒀ὐ¢🜀\u{1e01b}A a🟠bcd", 6, false),
"𑒀ὐ¢🜀\u{1e01b}A[...]",
);
}
#[test]
fn test_dc_null_terminate_1() {
unsafe {
@@ -1880,4 +1776,47 @@ mod tests {
assert_eq!(EmailAddress::new("u@.tt").is_ok(), false);
assert_eq!(EmailAddress::new("@d.tt").is_ok(), false);
}
use proptest::prelude::*;
proptest! {
#[test]
fn test_dc_truncate(
buf: String,
approx_chars in 0..10000usize,
do_unwrap: bool,
) {
let res = dc_truncate(&buf, approx_chars, do_unwrap);
let el_len = if do_unwrap { 3 } else { 5 };
let l = res.chars().count();
if approx_chars > 0 {
assert!(
l <= approx_chars + el_len,
"buf: '{}' - res: '{}' - len {}, approx {}",
&buf, &res, res.len(), approx_chars
);
} else {
assert_eq!(&res, &buf);
}
if approx_chars > 0 && buf.chars().count() > approx_chars + el_len {
let l = res.len();
if do_unwrap {
assert_eq!(&res[l-3..l], "...", "missing ellipsis in {}", &res);
} else {
assert_eq!(&res[l-5..l], "[...]", "missing ellipsis in {}", &res);
}
}
}
}
#[test]
fn test_dc_create_incoming_rfc724_mid() {
let res = dc_create_incoming_rfc724_mid(123, 45, &vec![6, 7]);
assert_eq!(as_str(res), "123-45-7@stub");
unsafe {
free(res.cast());
}
}
}

View File

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

View File

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

229
src/events.rs Normal file
View File

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

View File

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

View File

@@ -3,6 +3,7 @@ use std::ptr;
use std::time::Duration;
use deltachat_derive::{FromSql, ToSql};
use mmime::clist::*;
use rand::{thread_rng, Rng};
use crate::chat;
@@ -10,28 +11,37 @@ use crate::configure::*;
use crate::constants::*;
use crate::context::Context;
use crate::dc_imex::*;
use crate::dc_loginparam::*;
use crate::dc_mimefactory::*;
use crate::dc_tools::*;
use crate::events::Event;
use crate::imap::*;
use crate::location;
use crate::login_param::LoginParam;
use crate::message::*;
use crate::param::*;
use crate::sql;
use crate::types::*;
use crate::x::*;
/// Thread IDs
#[derive(Debug, Display, Copy, Clone, PartialEq, Eq, FromPrimitive, ToPrimitive, FromSql, ToSql)]
#[repr(i32)]
enum Thread {
Unknown = 0,
Imap = 100,
Smtp = 5000,
}
impl Default for Thread {
fn default() -> Self {
Thread::Unknown
}
}
#[derive(Debug, Display, Copy, Clone, PartialEq, Eq, FromPrimitive, ToPrimitive, FromSql, ToSql)]
#[repr(i32)]
pub enum Action {
Unknown = 0,
// Jobs in the INBOX-thread, range from DC_IMAP_THREAD..DC_IMAP_THREAD+999
Housekeeping = 105, // low priority ...
DeleteMsgOnImap = 110,
@@ -50,11 +60,19 @@ pub enum Action {
SendMsgToSmtp = 5901, // ... high priority
}
impl Default for Action {
fn default() -> Self {
Action::Unknown
}
}
impl From<Action> for Thread {
fn from(action: Action) -> Thread {
use Action::*;
match action {
Unknown => Thread::Unknown,
Housekeeping => Thread::Imap,
DeleteMsgOnImap => Thread::Imap,
MarkseenMdnOnImap => Thread::Imap,
@@ -111,31 +129,19 @@ impl Job {
#[allow(non_snake_case)]
fn do_DC_JOB_SEND(&mut self, context: &Context) {
let ok_to_continue;
let mut filename = ptr::null_mut();
let mut buf = ptr::null_mut();
let mut buf_bytes = 0;
/* connect to SMTP server, if not yet done */
if !context.smtp.lock().unwrap().is_connected() {
let loginparam = dc_loginparam_read(context, &context.sql, "configured_");
let loginparam = LoginParam::from_database(context, "configured_");
let connected = context.smtp.lock().unwrap().connect(context, &loginparam);
if !connected {
self.try_again_later(3i32, None);
ok_to_continue = false;
} else {
ok_to_continue = true;
return;
}
} else {
ok_to_continue = true;
}
if ok_to_continue {
let filename_s = self.param.get(Param::File).unwrap_or_default();
filename = unsafe { filename_s.strdup() };
if unsafe { strlen(filename) } == 0 {
warn!(context, 0, "Missing file name for job {}", self.job_id,);
} else if 0 != unsafe { dc_read_file(context, filename, &mut buf, &mut buf_bytes) } {
if let Some(filename) = self.param.get(Param::File) {
if let Some(body) = dc_read_file_safe(context, filename) {
if let Some(recipients) = self.param.get(Param::Recipients) {
let recipients_list = recipients
.split("\x1e")
@@ -147,78 +153,59 @@ impl Job {
}
})
.collect::<Vec<_>>();
/* if there is a msg-id and it does not exist in the db, cancel sending.
this happends if dc_delete_msgs() was called
before the generated mime was sent out */
let ok_to_continue1;
if 0 != self.foreign_id {
if 0 == unsafe { dc_msg_exists(context, self.foreign_id) } {
warn!(
context,
0,
"Message {} for job {} does not exist",
self.foreign_id,
self.job_id,
);
ok_to_continue1 = false;
} else {
ok_to_continue1 = true;
}
} else {
ok_to_continue1 = true;
}
if ok_to_continue1 {
/* send message */
let body = unsafe {
std::slice::from_raw_parts(buf as *const u8, buf_bytes).to_vec()
};
if 0 != self.foreign_id && !dc_msg_exists(context, self.foreign_id) {
warn!(
context,
"Message {} for job {} does not exist", self.foreign_id, self.job_id,
);
return;
};
// hold the smtp lock during sending of a job and
// its ok/error response processing. Note that if a message
// was sent we need to mark it in the database as we
// otherwise might send it twice.
let mut sock = context.smtp.lock().unwrap();
if 0 == sock.send(context, recipients_list, body) {
sock.disconnect();
self.try_again_later(-1i32, Some(as_str(sock.error)));
} else {
dc_delete_file(context, filename_s);
if 0 != self.foreign_id {
dc_update_msg_state(
// hold the smtp lock during sending of a job and
// its ok/error response processing. Note that if a message
// was sent we need to mark it in the database as we
// otherwise might send it twice.
let mut sock = context.smtp.lock().unwrap();
if 0 == sock.send(context, recipients_list, body) {
sock.disconnect();
self.try_again_later(-1i32, sock.error.clone());
} else {
dc_delete_file(context, filename);
if 0 != self.foreign_id {
dc_update_msg_state(
context,
self.foreign_id,
MessageState::OutDelivered,
);
let chat_id: i32 = context
.sql
.query_get_value(
context,
self.foreign_id,
MessageState::OutDelivered,
);
let chat_id: i32 = context
.sql
.query_row_col(
context,
"SELECT chat_id FROM msgs WHERE id=?",
params![self.foreign_id as i32],
0,
)
.unwrap_or_default();
context.call_cb(
Event::MSG_DELIVERED,
chat_id as uintptr_t,
self.foreign_id as uintptr_t,
);
}
"SELECT chat_id FROM msgs WHERE id=?",
params![self.foreign_id as i32],
)
.unwrap_or_default();
context.call_cb(Event::MsgDelivered {
chat_id: chat_id as u32,
msg_id: self.foreign_id,
});
}
}
} else {
warn!(context, 0, "Missing recipients for job {}", self.job_id,);
warn!(context, "Missing recipients for job {}", self.job_id,);
}
}
}
unsafe { free(buf) };
unsafe { free(filename.cast()) };
}
// this value does not increase the number of tries
fn try_again_later(&mut self, try_again: libc::c_int, pending_error: Option<&str>) {
fn try_again_later(&mut self, try_again: libc::c_int, pending_error: Option<String>) {
self.try_again = try_again;
self.pending_error = pending_error.map(|s| s.to_string());
self.pending_error = pending_error;
}
#[allow(non_snake_case)]
@@ -289,7 +276,7 @@ impl Job {
if dc_rfc724_mid_cnt(context, msg.rfc724_mid) != 1 {
info!(
context,
0, "The message is deleted from the server when all parts are deleted.",
"The message is deleted from the server when all parts are deleted.",
);
delete_from_server = 0i32
}
@@ -452,18 +439,17 @@ pub fn perform_imap_fetch(context: &Context) {
.unwrap_or_else(|| 1)
== 0
{
info!(context, 0, "INBOX-watch disabled.",);
info!(context, "INBOX-watch disabled.",);
return;
}
info!(context, 0, "INBOX-fetch started...",);
info!(context, "INBOX-fetch started...",);
inbox.fetch(context);
if inbox.should_reconnect() {
info!(context, 0, "INBOX-fetch aborted, starting over...",);
info!(context, "INBOX-fetch aborted, starting over...",);
inbox.fetch(context);
}
info!(
context,
0,
"INBOX-fetch done in {:.4} ms.",
start.elapsed().as_nanos() as f64 / 1000.0,
);
@@ -477,13 +463,13 @@ pub fn perform_imap_idle(context: &Context) {
if *context.perform_inbox_jobs_needed.clone().read().unwrap() {
info!(
context,
0, "INBOX-IDLE will not be started because of waiting jobs."
"INBOX-IDLE will not be started because of waiting jobs."
);
return;
}
info!(context, 0, "INBOX-IDLE started...");
info!(context, "INBOX-IDLE started...");
inbox.idle(context);
info!(context, 0, "INBOX-IDLE ended.");
info!(context, "INBOX-IDLE ended.");
}
pub fn perform_mvbox_fetch(context: &Context) {
@@ -560,16 +546,16 @@ pub fn perform_smtp_jobs(context: &Context) {
state.perform_jobs_needed = 0;
if state.suspended {
info!(context, 0, "SMTP-jobs suspended.",);
info!(context, "SMTP-jobs suspended.",);
return;
}
state.doing_jobs = true;
probe_smtp_network
};
info!(context, 0, "SMTP-jobs started...",);
info!(context, "SMTP-jobs started...",);
job_perform(context, Thread::Smtp, probe_smtp_network);
info!(context, 0, "SMTP-jobs ended.");
info!(context, "SMTP-jobs ended.");
{
let &(ref lock, _) = &*context.smtp_state.clone();
@@ -580,7 +566,7 @@ pub fn perform_smtp_jobs(context: &Context) {
}
pub fn perform_smtp_idle(context: &Context) {
info!(context, 0, "SMTP-idle started...",);
info!(context, "SMTP-idle started...",);
{
let &(ref lock, ref cvar) = &*context.smtp_state.clone();
let mut state = lock.lock().unwrap();
@@ -588,7 +574,7 @@ pub fn perform_smtp_idle(context: &Context) {
if state.perform_jobs_needed == 1 {
info!(
context,
0, "SMTP-idle will not be started because of waiting jobs.",
"SMTP-idle will not be started because of waiting jobs.",
);
} else {
let dur = get_next_wakeup_time(context, Thread::Smtp);
@@ -606,17 +592,16 @@ pub fn perform_smtp_idle(context: &Context) {
}
}
info!(context, 0, "SMTP-idle ended.",);
info!(context, "SMTP-idle ended.",);
}
fn get_next_wakeup_time(context: &Context, thread: Thread) -> Duration {
let t: i64 = context
.sql
.query_row_col(
.query_get_value(
context,
"SELECT MIN(desired_timestamp) FROM jobs WHERE thread=?;",
params![thread],
0,
)
.unwrap_or_default();
@@ -657,7 +642,7 @@ pub fn job_action_exists(context: &Context, action: Action) -> bool {
/* special case for DC_JOB_SEND_MSG_TO_SMTP */
#[allow(non_snake_case)]
pub unsafe fn job_send_msg(context: &Context, msg_id: uint32_t) -> libc::c_int {
pub unsafe fn job_send_msg(context: &Context, msg_id: u32) -> libc::c_int {
let mut success = 0;
/* load message data */
@@ -665,7 +650,7 @@ pub unsafe fn job_send_msg(context: &Context, msg_id: uint32_t) -> libc::c_int {
if mimefactory.is_err() || mimefactory.as_ref().unwrap().from_addr.is_null() {
warn!(
context,
0, "Cannot load data to send, maybe the message is deleted in between.",
"Cannot load data to send, maybe the message is deleted in between.",
);
} else {
let mut mimefactory = mimefactory.unwrap();
@@ -690,12 +675,12 @@ pub unsafe fn job_send_msg(context: &Context, msg_id: uint32_t) -> libc::c_int {
mimefactory.msg.param.set_int(Param::Height, height as i32);
}
}
dc_msg_save_param_to_disk(&mut mimefactory.msg);
dc_msg_save_param_to_disk(context, &mut mimefactory.msg);
}
}
}
/* create message */
if 0 == dc_mimefactory_render(&mut mimefactory) {
if !dc_mimefactory_render(context, &mut mimefactory) {
dc_set_msg_failed(context, msg_id, as_opt_str(mimefactory.error));
} else if 0
!= mimefactory
@@ -707,7 +692,6 @@ pub unsafe fn job_send_msg(context: &Context, msg_id: uint32_t) -> libc::c_int {
{
warn!(
context,
0,
"e2e encryption unavailable {} - {:?}",
msg_id,
mimefactory.msg.param.get_int(Param::GuranteeE2ee),
@@ -719,9 +703,7 @@ pub unsafe fn job_send_msg(context: &Context, msg_id: uint32_t) -> libc::c_int {
);
} else {
/* unrecoverable */
if clist_search_string_nocase(mimefactory.recipients_addr, mimefactory.from_addr)
== 0i32
{
if !clist_search_string_nocase(mimefactory.recipients_addr, mimefactory.from_addr) {
clist_insert_after(
mimefactory.recipients_names,
(*mimefactory.recipients_names).last,
@@ -740,7 +722,7 @@ pub unsafe fn job_send_msg(context: &Context, msg_id: uint32_t) -> libc::c_int {
if let Err(err) =
location::set_kml_sent_timestamp(context, mimefactory.msg.chat_id, time())
{
error!(context, 0, "Failed to set kml sent_timestamp: {:?}", err);
error!(context, "Failed to set kml sent_timestamp: {:?}", err);
}
if !mimefactory.msg.hidden {
if let Err(err) = location::set_msg_location_id(
@@ -748,7 +730,7 @@ pub unsafe fn job_send_msg(context: &Context, msg_id: uint32_t) -> libc::c_int {
mimefactory.msg.id,
mimefactory.out_last_added_location_id,
) {
error!(context, 0, "Failed to set msg_location_id: {:?}", err);
error!(context, "Failed to set msg_location_id: {:?}", err);
}
}
}
@@ -761,7 +743,7 @@ pub unsafe fn job_send_msg(context: &Context, msg_id: uint32_t) -> libc::c_int {
== 0
{
mimefactory.msg.param.set_int(Param::GuranteeE2ee, 1);
dc_msg_save_param_to_disk(&mut mimefactory.msg);
dc_msg_save_param_to_disk(context, &mut mimefactory.msg);
}
success = add_smtp_job(context, Action::SendMsgToSmtp, &mut mimefactory);
}
@@ -771,14 +753,14 @@ pub unsafe fn job_send_msg(context: &Context, msg_id: uint32_t) -> libc::c_int {
}
pub fn perform_imap_jobs(context: &Context) {
info!(context, 0, "dc_perform_imap_jobs starting.",);
info!(context, "dc_perform_imap_jobs starting.",);
let probe_imap_network = *context.probe_imap_network.clone().read().unwrap();
*context.probe_imap_network.write().unwrap() = false;
*context.perform_inbox_jobs_needed.write().unwrap() = false;
job_perform(context, Thread::Imap, probe_imap_network);
info!(context, 0, "dc_perform_imap_jobs ended.",);
info!(context, "dc_perform_imap_jobs ended.",);
}
fn job_perform(context: &Context, thread: Thread, probe_network: bool) {
@@ -826,14 +808,13 @@ fn job_perform(context: &Context, thread: Thread, probe_network: bool) {
match jobs {
Ok(ref _res) => {}
Err(ref err) => {
info!(context, 0, "query failed: {:?}", err);
info!(context, "query failed: {:?}", err);
}
}
for mut job in jobs.unwrap_or_default() {
info!(
context,
0,
"{}-job #{}, action {} started...",
if thread == Thread::Imap {
"INBOX"
@@ -871,6 +852,9 @@ fn job_perform(context: &Context, thread: Thread, probe_network: bool) {
job.try_again = 0;
match job.action {
Action::Unknown => {
warn!(context, "Unknown job id found");
}
Action::SendMsgToSmtp => job.do_DC_JOB_SEND(context),
Action::DeleteMsgOnImap => job.do_DC_JOB_DELETE_MSG_ON_IMAP(context),
Action::MarkseenMsgOnImap => job.do_DC_JOB_MARKSEEN_MSG_ON_IMAP(context),
@@ -913,7 +897,6 @@ fn job_perform(context: &Context, thread: Thread, probe_network: bool) {
// just try over next loop unconditionally, the ui typically interrupts idle when the file (video) is ready
info!(
context,
0,
"{}-job #{} not yet ready and will be delayed.",
if thread == Thread::Imap {
"INBOX"
@@ -931,7 +914,6 @@ fn job_perform(context: &Context, thread: Thread, probe_network: bool) {
job.update(context);
info!(
context,
0,
"{}-job #{} not succeeded on try #{}, retry in ADD_TIME+{} (in {} seconds).",
if thread == Thread::Imap {
"INBOX"
@@ -1006,9 +988,9 @@ fn connect_to_inbox(context: &Context, inbox: &Imap) -> libc::c_int {
ret_connected
}
fn send_mdn(context: &Context, msg_id: uint32_t) {
fn send_mdn(context: &Context, msg_id: u32) {
if let Ok(mut mimefactory) = unsafe { dc_mimefactory_load_mdn(context, msg_id) } {
if 0 != unsafe { dc_mimefactory_render(&mut mimefactory) } {
if unsafe { dc_mimefactory_render(context, &mut mimefactory) } {
add_smtp_job(context, Action::SendMdn, &mut mimefactory);
}
}
@@ -1016,40 +998,23 @@ fn send_mdn(context: &Context, msg_id: uint32_t) {
#[allow(non_snake_case)]
fn add_smtp_job(context: &Context, action: Action, mimefactory: &dc_mimefactory_t) -> libc::c_int {
let pathNfilename: *mut libc::c_char;
let mut success: libc::c_int = 0i32;
let mut recipients: *mut libc::c_char = ptr::null_mut();
let mut param = Params::new();
pathNfilename = unsafe {
dc_get_fine_pathNfilename(
context,
b"$BLOBDIR\x00" as *const u8 as *const libc::c_char,
mimefactory.rfc724_mid,
let path_filename =
dc_get_fine_path_filename(context, "$BLOBDIR", as_str(mimefactory.rfc724_mid));
let bytes = unsafe {
std::slice::from_raw_parts(
(*mimefactory.out).str_0 as *const u8,
(*mimefactory.out).len,
)
};
if pathNfilename.is_null() {
if !dc_write_file(context, &path_filename, bytes) {
error!(
context,
0,
"Could not find free file name for message with ID <{}>.",
to_string(mimefactory.rfc724_mid),
);
} else if 0
== unsafe {
dc_write_file(
context,
pathNfilename,
(*mimefactory.out).str_0 as *const libc::c_void,
(*mimefactory.out).len,
)
}
{
error!(
context,
0,
"Could not write message <{}> to \"{}\".",
to_string(mimefactory.rfc724_mid),
as_str(pathNfilename),
path_filename.display(),
);
} else {
recipients = unsafe {
@@ -1058,7 +1023,7 @@ fn add_smtp_job(context: &Context, action: Action, mimefactory: &dc_mimefactory_
b"\x1e\x00" as *const u8 as *const libc::c_char,
)
};
param.set(Param::File, as_str(pathNfilename));
param.set(Param::File, path_filename.to_string_lossy());
param.set(Param::Recipients, as_str(recipients));
job_add(
context,
@@ -1077,7 +1042,6 @@ fn add_smtp_job(context: &Context, action: Action, mimefactory: &dc_mimefactory_
}
unsafe {
free(recipients.cast());
free(pathNfilename.cast());
}
success
}
@@ -1089,6 +1053,11 @@ pub fn job_add(
param: Params,
delay_seconds: i64,
) {
if action == Action::Unknown {
error!(context, "Invalid action passed to job_add");
return;
}
let timestamp = time();
let thread: Thread = action.into();
@@ -1109,11 +1078,12 @@ pub fn job_add(
match thread {
Thread::Imap => interrupt_imap_idle(context),
Thread::Smtp => interrupt_smtp_idle(context),
Thread::Unknown => {}
}
}
pub fn interrupt_smtp_idle(context: &Context) {
info!(context, 0, "Interrupting SMTP-idle...",);
info!(context, "Interrupting SMTP-idle...",);
let &(ref lock, ref cvar) = &*context.smtp_state.clone();
let mut state = lock.lock().unwrap();
@@ -1124,7 +1094,7 @@ pub fn interrupt_smtp_idle(context: &Context) {
}
pub fn interrupt_imap_idle(context: &Context) {
info!(context, 0, "Interrupting IMAP-IDLE...",);
info!(context, "Interrupting IMAP-IDLE...",);
*context.perform_inbox_jobs_needed.write().unwrap() = true;
context.inbox.read().unwrap().interrupt_idle();

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

206
src/login_param.rs Normal file
View File

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

View File

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

View File

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

View File

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

View File

@@ -178,8 +178,11 @@ impl<'a> Peerstate<'a> {
OR gossip_key_fingerprint=? COLLATE NOCASE \
ORDER BY public_key_fingerprint=? DESC;";
let fp = fingerprint.as_bytes();
Self::from_stmt(context, query, params![fp, fp, fp])
Self::from_stmt(
context,
query,
params![fingerprint, fingerprint, fingerprint],
)
}
fn from_stmt<P>(context: &'a Context, query: &str, params: P) -> Option<Self>
@@ -496,7 +499,11 @@ mod tests {
let ctx = crate::test_utils::dummy_context();
let addr = "hello@mail.com";
let pub_key = crate::key::Key::from_base64("xsBNBFztUVkBCADYaQl/UOUpRPd32nLRzx8eU0eI+jQEnG+g5anjYA+3oct1rROGl5SygjMULDKdaUy27O3o9Srsti0YjA7uxZnavIqhSopJhFidqY1M1wA9JZa/duucZdNwUGbjGIRsS/4Cjr5+3svscK24hVYub1dvDWXpwUTnj3K6xOEnJdoM+MhCqtSD5+zcJhFc9vyZm9ZTGWUxAhKh0iJTcCD8V6CQ3XZ2z9GruwzZT/FTFovWrz7m3TUI2OdSSHh0eZLRGEoxMCT/vzflAFGAr8ijCaRsEIfqP6FW8uQWnFTqkjxEUCZG6XkeFHB84aj5jqYG/1KCLjL5vEKwfl1tz/WnPhY7ABEBAAHNEDxoZWxsb0BtYWlsLmNvbT7CwIkEEAEIADMCGQEFAlztUVoCGwMECwkIBwYVCAkKCwIDFgIBFiEEgMjHGVbvLXe6ioRROg8oKCvye7gACgkQOg8oKCvye7ijAwf+PTsuawUax9cNPn1bN90H+g9qyHZJMEwKXtUnNaXJxPW3iB7ThhpCiCzsZwP7+l7ArS8tmLeNDw2bENtcf1XCv4wovP2fdXOP3QOUUFX/GdakcTwv7DzC7CO0grB1HtaPhGw/6UX2o2cx2i9xiUf4Givq2MfCbgAW5zloH6WXGPb6yLQYJXxqDIphr4+uZDb+bMAyWHN/DUkAjHrV8nnVki7PMHqzzZpwglalxMX8RGeiGZE39ALJKL/Og87DMFah87/yoxQWGoS7Wqv0XDcCPKoTCPrpk8pOe2KEsq/lz215nefHd4aRpfUX5YCYa8HPvvfPQbGF73uvyQw5w7qjis7ATQRc7VFZAQgAt8ONdnX6KEEQ5Jw6ilJ+LBtY44SP5t0I3eK+goKepgIiKhjGDa+Mntyi4jdhH+HO6kvK5SHMh2sPp4rRO/WKHJwWFySyM1OdyiywhyH0J9R5rBY4vPHsJjf6vSKJdWLWT+ho1fNet2IIC+jVCYli91MAMbRvk6EKVj1nCc+67giOahXEkHt6xxkeCGlOvbw8hxGj1A8+AC1BLms/OR3oc4JMi9O3kq6uG0z9tlUEerac9HVwcjoO1XLe+hJhoT5H+TbnGjPuhuURP3pFiIKHpbRYgUfdSAY0dTObO7t4I5y/drPOrCTnWrBUg2wXAECUhpRKow9/ai2YemLv9KqhhwARAQABwsB2BBgBCAAgBQJc7VFaAhsMFiEEgMjHGVbvLXe6ioRROg8oKCvye7gACgkQOg8oKCvye7jmyggAhs4QzCzIbT2OsAReBxkxtm0AI+g1HZ1KFKof5NDHfgv9C/Qu1I8mKEjlZzA4qFyPmLqntgwJ0RuFy6gLbljZBNCFO7vB478AhYtnWjuKZmA40HUPwcB1hEJ31c42akzfUbioY1TLLepngdsJg7Cm8O+rhI9+1WRA66haJDgFs793SVUDyJh8f9NX50l5zR87/bsV30CFSw0q4OSSy9VI/z+2g5khn1LnuuOrCfFnYIPYtJED1BfkXkosxGlgbzy79VvGmI9d23x4atDK7oBPCzIj+lP8sytJ0u3HOguXi9OgDitKy+Pt1r8gH8frdktMJr5Ts6DW+tIn2vR23KR8aA==", KeyType::Public).unwrap();
let pub_key = crate::key::Key::from_base64(
include_str!("../test-data/key/public.asc"),
KeyType::Public,
)
.unwrap();
let mut peerstate = Peerstate {
context: &ctx.ctx,
@@ -523,6 +530,10 @@ mod tests {
// clear to_save, as that is not persissted
peerstate.to_save = None;
assert_eq!(peerstate, peerstate_new);
let peerstate_new2 =
Peerstate::from_fingerprint(&ctx.ctx, &ctx.ctx.sql, &pub_key.fingerprint())
.expect("failed to load peerstate from db");
assert_eq!(peerstate, peerstate_new2);
}
#[test]
@@ -530,7 +541,11 @@ mod tests {
let ctx = crate::test_utils::dummy_context();
let addr = "hello@mail.com";
let pub_key = crate::key::Key::from_base64("xsBNBFztUVkBCADYaQl/UOUpRPd32nLRzx8eU0eI+jQEnG+g5anjYA+3oct1rROGl5SygjMULDKdaUy27O3o9Srsti0YjA7uxZnavIqhSopJhFidqY1M1wA9JZa/duucZdNwUGbjGIRsS/4Cjr5+3svscK24hVYub1dvDWXpwUTnj3K6xOEnJdoM+MhCqtSD5+zcJhFc9vyZm9ZTGWUxAhKh0iJTcCD8V6CQ3XZ2z9GruwzZT/FTFovWrz7m3TUI2OdSSHh0eZLRGEoxMCT/vzflAFGAr8ijCaRsEIfqP6FW8uQWnFTqkjxEUCZG6XkeFHB84aj5jqYG/1KCLjL5vEKwfl1tz/WnPhY7ABEBAAHNEDxoZWxsb0BtYWlsLmNvbT7CwIkEEAEIADMCGQEFAlztUVoCGwMECwkIBwYVCAkKCwIDFgIBFiEEgMjHGVbvLXe6ioRROg8oKCvye7gACgkQOg8oKCvye7ijAwf+PTsuawUax9cNPn1bN90H+g9qyHZJMEwKXtUnNaXJxPW3iB7ThhpCiCzsZwP7+l7ArS8tmLeNDw2bENtcf1XCv4wovP2fdXOP3QOUUFX/GdakcTwv7DzC7CO0grB1HtaPhGw/6UX2o2cx2i9xiUf4Givq2MfCbgAW5zloH6WXGPb6yLQYJXxqDIphr4+uZDb+bMAyWHN/DUkAjHrV8nnVki7PMHqzzZpwglalxMX8RGeiGZE39ALJKL/Og87DMFah87/yoxQWGoS7Wqv0XDcCPKoTCPrpk8pOe2KEsq/lz215nefHd4aRpfUX5YCYa8HPvvfPQbGF73uvyQw5w7qjis7ATQRc7VFZAQgAt8ONdnX6KEEQ5Jw6ilJ+LBtY44SP5t0I3eK+goKepgIiKhjGDa+Mntyi4jdhH+HO6kvK5SHMh2sPp4rRO/WKHJwWFySyM1OdyiywhyH0J9R5rBY4vPHsJjf6vSKJdWLWT+ho1fNet2IIC+jVCYli91MAMbRvk6EKVj1nCc+67giOahXEkHt6xxkeCGlOvbw8hxGj1A8+AC1BLms/OR3oc4JMi9O3kq6uG0z9tlUEerac9HVwcjoO1XLe+hJhoT5H+TbnGjPuhuURP3pFiIKHpbRYgUfdSAY0dTObO7t4I5y/drPOrCTnWrBUg2wXAECUhpRKow9/ai2YemLv9KqhhwARAQABwsB2BBgBCAAgBQJc7VFaAhsMFiEEgMjHGVbvLXe6ioRROg8oKCvye7gACgkQOg8oKCvye7jmyggAhs4QzCzIbT2OsAReBxkxtm0AI+g1HZ1KFKof5NDHfgv9C/Qu1I8mKEjlZzA4qFyPmLqntgwJ0RuFy6gLbljZBNCFO7vB478AhYtnWjuKZmA40HUPwcB1hEJ31c42akzfUbioY1TLLepngdsJg7Cm8O+rhI9+1WRA66haJDgFs793SVUDyJh8f9NX50l5zR87/bsV30CFSw0q4OSSy9VI/z+2g5khn1LnuuOrCfFnYIPYtJED1BfkXkosxGlgbzy79VvGmI9d23x4atDK7oBPCzIj+lP8sytJ0u3HOguXi9OgDitKy+Pt1r8gH8frdktMJr5Ts6DW+tIn2vR23KR8aA==", KeyType::Public).unwrap();
let pub_key = crate::key::Key::from_base64(
include_str!("../test-data/key/public.asc"),
KeyType::Public,
)
.unwrap();
let mut peerstate = Peerstate {
context: &ctx.ctx,

View File

@@ -1,6 +1,5 @@
use std::collections::HashSet;
use std::convert::TryInto;
use std::ffi::CStr;
use std::io::Cursor;
use std::ptr;
@@ -15,7 +14,6 @@ use rand::thread_rng;
use crate::dc_tools::*;
use crate::key::*;
use crate::keyring::*;
use crate::types::*;
use crate::x::*;
pub unsafe fn dc_split_armored_data(
@@ -26,7 +24,7 @@ pub unsafe fn dc_split_armored_data(
ret_base64: *mut *const libc::c_char,
) -> bool {
let mut success = false;
let mut line_chars: size_t = 0i32 as size_t;
let mut line_chars: libc::size_t = 0;
let mut line: *mut libc::c_char = buf;
let mut p1: *mut libc::c_char = buf;
let mut p2: *mut libc::c_char;
@@ -106,7 +104,7 @@ pub unsafe fn dc_split_armored_data(
}
p1 = p1.offset(1isize);
line = p1;
line_chars = 0i32 as size_t
line_chars = 0;
} else {
p1 = p1.offset(1isize);
line_chars = line_chars.wrapping_add(1)
@@ -188,15 +186,11 @@ pub fn dc_pgp_create_keypair(addr: impl AsRef<str>) -> Option<(Key, Key)> {
}
pub fn dc_pgp_pk_encrypt(
plain_text: *const libc::c_void,
plain_bytes: size_t,
plain: &[u8],
public_keys_for_encryption: &Keyring,
private_key_for_signing: Option<&Key>,
) -> Option<String> {
assert!(!plain_text.is_null() && !plain_bytes > 0, "invalid input");
let bytes = unsafe { std::slice::from_raw_parts(plain_text as *const u8, plain_bytes) };
let lit_msg = Message::new_literal_bytes("", bytes);
let lit_msg = Message::new_literal_bytes("", plain);
let pkeys: Vec<&SignedPublicKey> = public_keys_for_encryption
.keys()
.into_iter()
@@ -227,16 +221,11 @@ pub fn dc_pgp_pk_encrypt(
}
pub fn dc_pgp_pk_decrypt(
ctext: *const libc::c_void,
ctext_bytes: size_t,
ctext: &[u8],
private_keys_for_decryption: &Keyring,
public_keys_for_validation: &Keyring,
ret_signature_fingerprints: Option<&mut HashSet<String>>,
) -> Option<Vec<u8>> {
assert!(!ctext.is_null() && ctext_bytes > 0, "invalid input");
let ctext = unsafe { std::slice::from_raw_parts(ctext as *const u8, ctext_bytes) };
// TODO: proper error handling
if let Ok((msg, _)) = Message::from_armor_single(Cursor::new(ctext)) {
let skeys: Vec<&SignedSecretKey> = private_keys_for_decryption
@@ -283,46 +272,28 @@ pub fn dc_pgp_pk_decrypt(
}
/// Symmetric encryption.
pub fn dc_pgp_symm_encrypt(
passphrase: *const libc::c_char,
plain: *const libc::c_void,
plain_bytes: size_t,
) -> Option<String> {
assert!(!passphrase.is_null(), "invalid passphrase");
assert!(!plain.is_null() && !plain_bytes > 0, "invalid input");
let pw = unsafe { CStr::from_ptr(passphrase).to_str().unwrap() };
let bytes = unsafe { std::slice::from_raw_parts(plain as *const u8, plain_bytes) };
pub fn dc_pgp_symm_encrypt(passphrase: &str, plain: &[u8]) -> Option<String> {
let mut rng = thread_rng();
let lit_msg = Message::new_literal_bytes("", bytes);
let lit_msg = Message::new_literal_bytes("", plain);
let s2k = StringToKey::new_default(&mut rng);
let msg = lit_msg.encrypt_with_password(&mut rng, s2k, Default::default(), || pw.into());
let msg =
lit_msg.encrypt_with_password(&mut rng, s2k, Default::default(), || passphrase.into());
msg.and_then(|msg| msg.to_armored_string(None)).ok()
}
/// Symmetric decryption.
pub fn dc_pgp_symm_decrypt(
passphrase: *const libc::c_char,
ctext: *const libc::c_void,
ctext_bytes: size_t,
) -> Option<Vec<u8>> {
assert!(!passphrase.is_null(), "invalid passphrase");
assert!(!ctext.is_null() && !ctext_bytes > 0, "invalid input");
let pw = unsafe { CStr::from_ptr(passphrase).to_str().unwrap() };
let bytes = unsafe { std::slice::from_raw_parts(ctext as *const u8, ctext_bytes) };
let enc_msg = Message::from_bytes(Cursor::new(bytes));
pub fn dc_pgp_symm_decrypt(passphrase: &str, ctext: &[u8]) -> Option<Vec<u8>> {
let enc_msg = Message::from_bytes(Cursor::new(ctext));
enc_msg
.and_then(|msg| {
let mut decryptor = msg
.decrypt_with_password(|| pw.into())
.expect("failed decryption");
decryptor.next().expect("no message")
let mut decryptor = msg.decrypt_with_password(|| passphrase.into())?;
match decryptor.next() {
Some(x) => x,
None => Err(pgp::errors::Error::InvalidInput),
}
})
.and_then(|msg| msg.get_content())
.ok()

View File

@@ -37,7 +37,7 @@ impl Into<Lot> for Error {
pub fn check_qr(context: &Context, qr: impl AsRef<str>) -> Lot {
let qr = qr.as_ref();
info!(context, 0, "Scanned QR code: {}", qr);
info!(context, "Scanned QR code: {}", qr);
if qr.starts_with(OPENPGP4FPR_SCHEME) {
decode_openpgp(context, qr)
@@ -70,19 +70,14 @@ fn decode_openpgp(context: &Context, qr: &str) -> Lot {
None => return format_err!("Invalid OPENPGP4FPR found").into(),
};
dbg!(fingerprint);
dbg!(fragment);
// replace & with \n to match expected param format
let fragment = fragment.replace('&', "\n");
dbg!(&fragment);
// Then parse the parameters
let param: Params = match fragment.parse() {
Ok(params) => params,
Err(err) => return err.into(),
};
dbg!(&param);
let addr = if let Some(addr) = param.get(Param::Forwarded) {
match normalize_address(addr) {

747
src/securejoin.rs Normal file
View File

@@ -0,0 +1,747 @@
use percent_encoding::{utf8_percent_encode, AsciiSet, NON_ALPHANUMERIC};
use crate::aheader::EncryptPreference;
use crate::chat::{self, Chat};
use crate::config::*;
use crate::configure::*;
use crate::constants::*;
use crate::contact::*;
use crate::context::Context;
use crate::dc_mimeparser::*;
use crate::e2ee::*;
use crate::error::Error;
use crate::events::Event;
use crate::key::*;
use crate::lot::LotState;
use crate::message::*;
use crate::param::*;
use crate::peerstate::*;
use crate::qr::check_qr;
use crate::stock::StockMessage;
use crate::token;
pub const NON_ALPHANUMERIC_WITHOUT_DOT: &AsciiSet = &NON_ALPHANUMERIC.remove(b'.');
macro_rules! joiner_progress {
($context:tt, $contact_id:expr, $progress:expr) => {
assert!(
$progress >= 0 && $progress <= 1000,
"value in range 0..1000 expected with: 0=error, 1..999=progress, 1000=success"
);
$context.call_cb($crate::events::Event::SecurejoinJoinerProgress {
contact_id: $contact_id,
progress: $progress,
});
};
}
macro_rules! inviter_progress {
($context:tt, $contact_id:expr, $progress:expr) => {
assert!(
$progress >= 0 && $progress <= 1000,
"value in range 0..1000 expected with: 0=error, 1..999=progress, 1000=success"
);
$context.call_cb($crate::events::Event::SecurejoinInviterProgress {
contact_id: $contact_id,
progress: $progress,
});
};
}
macro_rules! get_qr_attr {
($context:tt, $attr:ident) => {
$context
.bob
.read()
.unwrap()
.qr_scan
.as_ref()
.unwrap()
.$attr
.as_ref()
.unwrap()
};
}
pub fn dc_get_securejoin_qr(context: &Context, group_chat_id: u32) -> Option<String> {
/* =========================================================
==== Alice - the inviter side ====
==== Step 1 in "Setup verified contact" protocol ====
========================================================= */
let fingerprint: String;
ensure_secret_key_exists(context).ok();
let invitenumber = token::lookup_or_new(context, token::Namespace::InviteNumber, group_chat_id);
let auth = token::lookup_or_new(context, token::Namespace::Auth, group_chat_id);
let self_addr = match context.get_config(Config::ConfiguredAddr) {
Some(addr) => addr,
None => {
error!(context, "Not configured, cannot generate QR code.",);
return None;
}
};
let self_name = context
.sql
.get_config(context, "displayname")
.unwrap_or_default();
fingerprint = match get_self_fingerprint(context) {
Some(fp) => fp,
None => {
return None;
}
};
let self_addr_urlencoded =
utf8_percent_encode(&self_addr, NON_ALPHANUMERIC_WITHOUT_DOT).to_string();
let self_name_urlencoded =
utf8_percent_encode(&self_name, NON_ALPHANUMERIC_WITHOUT_DOT).to_string();
let qr = if 0 != group_chat_id {
if let Ok(chat) = Chat::load_from_db(context, group_chat_id) {
let group_name = chat.get_name();
let group_name_urlencoded =
utf8_percent_encode(&group_name, NON_ALPHANUMERIC).to_string();
Some(format!(
"OPENPGP4FPR:{}#a={}&g={}&x={}&i={}&s={}",
fingerprint,
self_addr_urlencoded,
&group_name_urlencoded,
&chat.grpid,
&invitenumber,
&auth,
))
} else {
error!(context, "Cannot get QR-code for chat-id {}", group_chat_id,);
return None;
}
} else {
Some(format!(
"OPENPGP4FPR:{}#a={}&n={}&i={}&s={}",
fingerprint, self_addr_urlencoded, self_name_urlencoded, &invitenumber, &auth,
))
};
info!(context, "Generated QR code: {}", qr.as_ref().unwrap());
qr
}
fn get_self_fingerprint(context: &Context) -> Option<String> {
if let Some(self_addr) = context.get_config(Config::ConfiguredAddr) {
if let Some(key) = Key::from_self_public(context, self_addr, &context.sql) {
return Some(key.fingerprint());
}
}
None
}
pub fn dc_join_securejoin(context: &Context, qr: &str) -> u32 {
let cleanup =
|context: &Context, contact_chat_id: u32, ongoing_allocated: bool, join_vg: bool| {
let mut bob = context.bob.write().unwrap();
bob.expects = 0;
let ret_chat_id = if bob.status == DC_BOB_SUCCESS {
if join_vg {
chat::get_chat_id_by_grpid(
context,
bob.qr_scan.as_ref().unwrap().text2.as_ref().unwrap(),
)
.0
} else {
contact_chat_id
}
} else {
0
};
bob.qr_scan = None;
if ongoing_allocated {
dc_free_ongoing(context);
}
ret_chat_id as u32
};
/* ==========================================================
==== Bob - the joiner's side =====
==== Step 2 in "Setup verified contact" protocol =====
========================================================== */
let mut contact_chat_id: u32 = 0;
let mut join_vg: bool = false;
info!(context, "Requesting secure-join ...",);
ensure_secret_key_exists(context).ok();
if !dc_alloc_ongoing(context) {
return cleanup(&context, contact_chat_id, false, join_vg);
}
let qr_scan = check_qr(context, &qr);
if qr_scan.state != LotState::QrAskVerifyContact && qr_scan.state != LotState::QrAskVerifyGroup
{
error!(context, "Unknown QR code.",);
return cleanup(&context, contact_chat_id, true, join_vg);
}
contact_chat_id = chat::create_by_contact_id(context, qr_scan.id).unwrap_or_default();
if contact_chat_id == 0 {
error!(context, "Unknown contact.",);
return cleanup(&context, contact_chat_id, true, join_vg);
}
if check_exit(context) {
return cleanup(&context, contact_chat_id, true, join_vg);
}
join_vg = qr_scan.get_state() == LotState::QrAskVerifyGroup;
{
let mut bob = context.bob.write().unwrap();
bob.status = 0;
bob.qr_scan = Some(qr_scan);
}
if fingerprint_equals_sender(
context,
context
.bob
.read()
.unwrap()
.qr_scan
.as_ref()
.unwrap()
.fingerprint
.as_ref()
.unwrap(),
contact_chat_id,
) {
info!(context, "Taking protocol shortcut.");
context.bob.write().unwrap().expects = DC_VC_CONTACT_CONFIRM;
joiner_progress!(context, chat_id_2_contact_id(context, contact_chat_id), 400);
let own_fingerprint = get_self_fingerprint(context).unwrap();
send_handshake_msg(
context,
contact_chat_id,
if join_vg {
"vg-request-with-auth"
} else {
"vc-request-with-auth"
},
get_qr_attr!(context, auth).to_string(),
Some(own_fingerprint),
if join_vg {
get_qr_attr!(context, text2).to_string()
} else {
"".to_string()
},
);
} else {
context.bob.write().unwrap().expects = DC_VC_AUTH_REQUIRED;
send_handshake_msg(
context,
contact_chat_id,
if join_vg { "vg-request" } else { "vc-request" },
get_qr_attr!(context, invitenumber),
None,
"",
);
}
// Bob -> Alice
while !check_exit(&context) {
std::thread::sleep(std::time::Duration::new(0, 3_000_000));
}
cleanup(&context, contact_chat_id, true, join_vg)
}
fn check_exit(context: &Context) -> bool {
context
.running_state
.clone()
.read()
.unwrap()
.shall_stop_ongoing
}
fn send_handshake_msg(
context: &Context,
contact_chat_id: u32,
step: &str,
param2: impl AsRef<str>,
fingerprint: Option<String>,
grpid: impl AsRef<str>,
) {
let mut msg = dc_msg_new_untyped();
msg.type_0 = Viewtype::Text;
msg.text = Some(format!("Secure-Join: {}", step));
msg.hidden = true;
msg.param.set_int(Param::Cmd, 7);
if step.is_empty() {
msg.param.remove(Param::Arg);
} else {
msg.param.set(Param::Arg, step);
}
if !param2.as_ref().is_empty() {
msg.param.set(Param::Arg2, param2);
}
if let Some(fp) = fingerprint {
msg.param.set(Param::Arg3, fp);
}
if !grpid.as_ref().is_empty() {
msg.param.set(Param::Arg4, grpid.as_ref());
}
if step == "vg-request" || step == "vc-request" {
msg.param.set_int(
Param::ForcePlaintext,
ForcePlaintext::AddAutocryptHeader as i32,
);
} else {
msg.param.set_int(Param::GuranteeE2ee, 1);
}
// TODO. handle cleanup on error
chat::send_msg(context, contact_chat_id, &mut msg).unwrap();
}
fn chat_id_2_contact_id(context: &Context, contact_chat_id: u32) -> u32 {
let contacts = chat::get_chat_contacts(context, contact_chat_id);
if contacts.len() == 1 {
contacts[0]
} else {
0
}
}
fn fingerprint_equals_sender(
context: &Context,
fingerprint: impl AsRef<str>,
contact_chat_id: u32,
) -> bool {
let contacts = chat::get_chat_contacts(context, contact_chat_id);
if contacts.len() == 1 {
if let Ok(contact) = Contact::load_from_db(context, contacts[0]) {
if let Some(peerstate) = Peerstate::from_addr(context, &context.sql, contact.get_addr())
{
let fingerprint_normalized = dc_normalize_fingerprint(fingerprint.as_ref());
if peerstate.public_key_fingerprint.is_some()
&& &fingerprint_normalized == peerstate.public_key_fingerprint.as_ref().unwrap()
{
return true;
}
}
}
}
false
}
/* library private: secure-join */
pub fn handle_securejoin_handshake(
context: &Context,
mimeparser: &MimeParser,
contact_id: u32,
) -> libc::c_int {
let own_fingerprint: String;
if contact_id <= DC_CONTACT_ID_LAST_SPECIAL {
return 0;
}
let step = match mimeparser.lookup_optional_field("Secure-Join") {
Some(s) => s,
None => {
return 0;
}
};
info!(
context,
">>>>>>>>>>>>>>>>>>>>>>>>> secure-join message \'{}\' received", step,
);
let (contact_chat_id, contact_chat_id_blocked) =
chat::create_or_lookup_by_contact_id(context, contact_id, Blocked::Not).unwrap_or_default();
if contact_chat_id_blocked != Blocked::Not {
chat::unblock(context, contact_chat_id);
}
let mut ret: libc::c_int = DC_HANDSHAKE_STOP_NORMAL_PROCESSING;
let join_vg = step.starts_with("vg-");
match step.as_str() {
"vg-request" | "vc-request" => {
/* =========================================================
==== Alice - the inviter side ====
==== Step 3 in "Setup verified contact" protocol ====
========================================================= */
// this message may be unencrypted (Bob, the joinder and the sender, might not have Alice's key yet)
// it just ensures, we have Bobs key now. If we do _not_ have the key because eg. MitM has removed it,
// send_message() will fail with the error "End-to-end-encryption unavailable unexpectedly.", so, there is no additional check needed here.
// verify that the `Secure-Join-Invitenumber:`-header matches invitenumber written to the QR code
let invitenumber = match mimeparser.lookup_optional_field("Secure-Join-Invitenumber") {
Some(n) => n,
None => {
warn!(context, "Secure-join denied (invitenumber missing).",);
return ret;
}
};
if !token::exists(context, token::Namespace::InviteNumber, &invitenumber) {
warn!(context, "Secure-join denied (bad invitenumber).",);
return ret;
}
info!(context, "Secure-join requested.",);
inviter_progress!(context, contact_id, 300);
send_handshake_msg(
context,
contact_chat_id,
&format!("{}-auth-required", &step[..2]),
"",
None,
"",
);
}
"vg-auth-required" | "vc-auth-required" => {
let cond = {
let bob = context.bob.read().unwrap();
let scan = bob.qr_scan.as_ref();
scan.is_none()
|| bob.expects != DC_VC_AUTH_REQUIRED
|| join_vg && scan.unwrap().state != LotState::QrAskVerifyGroup
};
if cond {
warn!(context, "auth-required message out of sync.",);
// no error, just aborted somehow or a mail from another handshake
return ret;
}
let scanned_fingerprint_of_alice = get_qr_attr!(context, fingerprint).to_string();
let auth = get_qr_attr!(context, auth).to_string();
if !encrypted_and_signed(mimeparser, &scanned_fingerprint_of_alice) {
could_not_establish_secure_connection(
context,
contact_chat_id,
if mimeparser.e2ee_helper.encrypted {
"No valid signature."
} else {
"Not encrypted."
},
);
end_bobs_joining(context, DC_BOB_ERROR);
return ret;
}
if !fingerprint_equals_sender(context, &scanned_fingerprint_of_alice, contact_chat_id) {
could_not_establish_secure_connection(
context,
contact_chat_id,
"Fingerprint mismatch on joiner-side.",
);
end_bobs_joining(context, DC_BOB_ERROR);
return ret;
}
info!(context, "Fingerprint verified.",);
own_fingerprint = get_self_fingerprint(context).unwrap();
joiner_progress!(context, contact_id, 400);
context.bob.write().unwrap().expects = DC_VC_CONTACT_CONFIRM;
send_handshake_msg(
context,
contact_chat_id,
&format!("{}-request-with-auth", &step[..2]),
auth,
Some(own_fingerprint),
if join_vg {
get_qr_attr!(context, text2).to_string()
} else {
"".to_string()
},
);
}
"vg-request-with-auth" | "vc-request-with-auth" => {
/* ============================================================
==== Alice - the inviter side ====
==== Steps 5+6 in "Setup verified contact" protocol ====
==== Step 6 in "Out-of-band verified groups" protocol ====
============================================================ */
// verify that Secure-Join-Fingerprint:-header matches the fingerprint of Bob
let fingerprint = match mimeparser.lookup_optional_field("Secure-Join-Fingerprint") {
Some(fp) => fp,
None => {
could_not_establish_secure_connection(
context,
contact_chat_id,
"Fingerprint not provided.",
);
return ret;
}
};
if !encrypted_and_signed(mimeparser, &fingerprint) {
could_not_establish_secure_connection(
context,
contact_chat_id,
"Auth not encrypted.",
);
return ret;
}
if !fingerprint_equals_sender(context, &fingerprint, contact_chat_id) {
could_not_establish_secure_connection(
context,
contact_chat_id,
"Fingerprint mismatch on inviter-side.",
);
return ret;
}
info!(context, "Fingerprint verified.",);
// verify that the `Secure-Join-Auth:`-header matches the secret written to the QR code
let auth_0 = match mimeparser.lookup_optional_field("Secure-Join-Auth") {
Some(auth) => auth,
None => {
could_not_establish_secure_connection(
context,
contact_chat_id,
"Auth not provided.",
);
return ret;
}
};
if !token::exists(context, token::Namespace::Auth, &auth_0) {
could_not_establish_secure_connection(context, contact_chat_id, "Auth invalid.");
return ret;
}
if mark_peer_as_verified(context, fingerprint).is_err() {
could_not_establish_secure_connection(
context,
contact_chat_id,
"Fingerprint mismatch on inviter-side.",
);
return ret;
}
Contact::scaleup_origin_by_id(context, contact_id, Origin::SecurejoinInvited);
info!(context, "Auth verified.",);
secure_connection_established(context, contact_chat_id);
emit_event!(context, Event::ContactsChanged(Some(contact_id)));
inviter_progress!(context, contact_id, 600);
if join_vg {
let field_grpid = mimeparser
.lookup_optional_field("Secure-Join-Group")
.unwrap_or_default();
let (group_chat_id, _, _) = chat::get_chat_id_by_grpid(context, &field_grpid);
if group_chat_id == 0 {
error!(context, "Chat {} not found.", &field_grpid);
return ret;
} else {
chat::add_contact_to_chat_ex(context, group_chat_id, contact_id, 0x1i32);
}
} else {
send_handshake_msg(context, contact_chat_id, "vc-contact-confirm", "", None, "");
inviter_progress!(context, contact_id, 1000);
}
}
"vg-member-added" | "vc-contact-confirm" => {
if join_vg {
ret = DC_HANDSHAKE_CONTINUE_NORMAL_PROCESSING;
}
if context.bob.read().unwrap().expects != DC_VC_CONTACT_CONFIRM {
info!(context, "Message belongs to a different handshake.",);
return ret;
}
let cond = {
let bob = context.bob.read().unwrap();
let scan = bob.qr_scan.as_ref();
scan.is_none() || join_vg && scan.unwrap().state != LotState::QrAskVerifyGroup
};
if cond {
warn!(
context,
"Message out of sync or belongs to a different handshake.",
);
return ret;
}
let scanned_fingerprint_of_alice = get_qr_attr!(context, fingerprint).to_string();
let vg_expect_encrypted = if join_vg {
let group_id = get_qr_attr!(context, text2).to_string();
let (_, is_verified_group, _) = chat::get_chat_id_by_grpid(context, group_id);
// when joining a non-verified group
// the vg-member-added message may be unencrypted
// when not all group members have keys or prefer encryption.
// So only expect encryption if this is a verified group
is_verified_group
} else {
// setup contact is always encrypted
true
};
if vg_expect_encrypted
&& !encrypted_and_signed(mimeparser, &scanned_fingerprint_of_alice)
{
could_not_establish_secure_connection(
context,
contact_chat_id,
"Contact confirm message not encrypted.",
);
end_bobs_joining(context, DC_BOB_ERROR);
return ret;
}
if mark_peer_as_verified(context, &scanned_fingerprint_of_alice).is_err() {
could_not_establish_secure_connection(
context,
contact_chat_id,
"Fingerprint mismatch on joiner-side.",
);
return ret;
}
Contact::scaleup_origin_by_id(context, contact_id, Origin::SecurejoinJoined);
emit_event!(context, Event::ContactsChanged(None));
let cg_member_added = mimeparser
.lookup_optional_field("Chat-Group-Member-Added")
.unwrap_or_default();
if join_vg && !addr_equals_self(context, cg_member_added) {
info!(context, "Message belongs to a different handshake (scaled up contact anyway to allow creation of group).");
return ret;
}
secure_connection_established(context, contact_chat_id);
context.bob.write().unwrap().expects = 0;
if join_vg {
send_handshake_msg(
context,
contact_chat_id,
"vg-member-added-received",
"",
None,
"",
);
}
end_bobs_joining(context, DC_BOB_SUCCESS);
}
"vg-member-added-received" => {
/* ============================================================
==== Alice - the inviter side ====
==== Step 8 in "Out-of-band verified groups" protocol ====
============================================================ */
if let Ok(contact) = Contact::get_by_id(context, contact_id) {
if contact.is_verified(context) == VerifiedStatus::Unverified {
warn!(context, "vg-member-added-received invalid.",);
return ret;
}
inviter_progress!(context, contact_id, 800);
inviter_progress!(context, contact_id, 1000);
} else {
warn!(context, "vg-member-added-received invalid.",);
return ret;
}
}
_ => {
warn!(context, "invalid step: {}", step);
}
}
if ret == DC_HANDSHAKE_STOP_NORMAL_PROCESSING {
ret |= DC_HANDSHAKE_ADD_DELETE_JOB;
}
ret
}
fn end_bobs_joining(context: &Context, status: libc::c_int) {
context.bob.write().unwrap().status = status;
dc_stop_ongoing_process(context);
}
fn secure_connection_established(context: &Context, contact_chat_id: u32) {
let contact_id: u32 = chat_id_2_contact_id(context, contact_chat_id);
let contact = Contact::get_by_id(context, contact_id);
let addr = if let Ok(ref contact) = contact {
contact.get_addr()
} else {
"?"
};
let msg = context.stock_string_repl_str(StockMessage::ContactVerified, addr);
chat::add_device_msg(context, contact_chat_id, msg);
emit_event!(context, Event::ChatModified(contact_chat_id));
}
fn could_not_establish_secure_connection(context: &Context, contact_chat_id: u32, details: &str) {
let contact_id = chat_id_2_contact_id(context, contact_chat_id);
let contact = Contact::get_by_id(context, contact_id);
let msg = context.stock_string_repl_str(
StockMessage::ContactNotVerified,
if let Ok(ref contact) = contact {
contact.get_addr()
} else {
"?"
},
);
chat::add_device_msg(context, contact_chat_id, &msg);
error!(context, "{} ({})", &msg, details);
}
fn mark_peer_as_verified(context: &Context, fingerprint: impl AsRef<str>) -> Result<(), Error> {
if let Some(ref mut peerstate) =
Peerstate::from_fingerprint(context, &context.sql, fingerprint.as_ref())
{
if peerstate.set_verified(1, fingerprint.as_ref(), 2) {
peerstate.prefer_encrypt = EncryptPreference::Mutual;
peerstate.to_save = Some(ToSave::All);
peerstate.save_to_db(&context.sql, false);
return Ok(());
}
}
bail!(
"could not mark peer as verified for fingerprint {}",
fingerprint.as_ref()
);
}
/* ******************************************************************************
* Tools: Misc.
******************************************************************************/
fn encrypted_and_signed(mimeparser: &MimeParser, expected_fingerprint: impl AsRef<str>) -> bool {
if !mimeparser.e2ee_helper.encrypted {
warn!(mimeparser.context, "Message not encrypted.",);
false
} else if mimeparser.e2ee_helper.signatures.len() <= 0 {
warn!(mimeparser.context, "Message not signed.",);
false
} else if expected_fingerprint.as_ref().is_empty() {
warn!(mimeparser.context, "Fingerprint for comparison missing.",);
false
} else if !mimeparser
.e2ee_helper
.signatures
.contains(expected_fingerprint.as_ref())
{
warn!(
mimeparser.context,
"Message does not match expected fingerprint {}.",
expected_fingerprint.as_ref(),
);
false
} else {
true
}
}
pub fn handle_degrade_event(context: &Context, peerstate: &Peerstate) {
// - we do not issue an warning for DC_DE_ENCRYPTION_PAUSED as this is quite normal
// - currently, we do not issue an extra warning for DC_DE_VERIFICATION_LOST - this always comes
// together with DC_DE_FINGERPRINT_CHANGED which is logged, the idea is not to bother
// with things they cannot fix, so the user is just kicked from the verified group
// (and he will know this and can fix this)
if Some(DegradeEvent::FingerprintChanged) == peerstate.degrade_event {
let contact_id: i32 = context
.sql
.query_get_value(
context,
"SELECT id FROM contacts WHERE addr=?;",
params![&peerstate.addr],
)
.unwrap_or_default();
if contact_id > 0 {
let (contact_chat_id, _) =
chat::create_or_lookup_by_contact_id(context, contact_id as u32, Blocked::Deaddrop)
.unwrap_or_default();
let peeraddr: &str = match peerstate.addr {
Some(ref addr) => &addr,
None => "",
};
let msg = context.stock_string_repl_str(StockMessage::ContactSetupChanged, peeraddr);
chat::add_device_msg(context, contact_chat_id, msg);
emit_event!(context, Event::ChatModified(contact_chat_id));
}
}
}

View File

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

View File

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

View File

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

View File

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

57
src/token.rs Normal file
View File

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

View File

@@ -6,19 +6,21 @@ import os
import re
if __name__ == "__main__":
if Path('src/top_evil_rs.py').exists():
os.chdir('src')
filestats = []
for fn in Path(".").glob("**/*.rs"):
s = fn.read_text()
s = re.sub(r"(?m)///.*$", "", s) # remove comments
unsafe = s.count("unsafe")
free = s.count("free(")
gotoblocks = s.count("current_block =")
gotoblocks = s.count("ok_to_continue") + s.count('OK_TO_CONTINUE')
filestats.append((fn, unsafe, free, gotoblocks))
sum_unsafe, sum_free, sum_gotoblocks = 0, 0, 0
for fn, unsafe, free, gotoblocks in reversed(sorted(filestats, key=lambda x: sum(x[1:]))):
print("{0: <30} unsafe: {1: >3} free: {2: >3} goto-blocks: {3: >3}".format(str(fn), unsafe, free, gotoblocks))
print("{0: <30} unsafe: {1: >3} free: {2: >3} ok_to_continue: {3: >3}".format(str(fn), unsafe, free, gotoblocks))
sum_unsafe += unsafe
sum_free += free
sum_gotoblocks += gotoblocks
@@ -27,5 +29,5 @@ if __name__ == "__main__":
print()
print("total unsafe:", sum_unsafe)
print("total free:", sum_free)
print("total gotoblocks:", sum_gotoblocks)
print("total ok_to_continue:", sum_gotoblocks)

View File

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

View File

@@ -33,13 +33,6 @@ pub fn strndup(s: *const libc::c_char, n: libc::c_ulong) -> *mut libc::c_char {
}
}
extern "C" {
pub fn clock() -> libc::clock_t;
// -- DC Methods
pub fn dc_mprintf(format: *const libc::c_char, _: ...) -> *mut libc::c_char;
}
pub(crate) unsafe fn strcasecmp(s1: *const libc::c_char, s2: *const libc::c_char) -> libc::c_int {
let s1 = std::ffi::CStr::from_ptr(s1)
.to_string_lossy()

1
test-data/key/public.asc Normal file
View File

@@ -0,0 +1 @@
xsBNBF086ewBCACmJKuoxIO6T87yi4Q3MyNpMch3Y8KrtHDQyUszU36eqM3Pmd1lFrbcCd8ZWo2pq6OJSwsM/jjRGn1zo2YOaQeJRRrC+KrKGqSUtRSYQBPrPjE2YjSXAMbu8jBI6VVUhHeghriBkK79PY9O/oRhIJC0p14IJe6CQ6OI2fTmTUHF9i/nJ3G4Wb3/K1bU3yVfyPZjTZQPYPvvh03vxHULKurtYkX5DTEMSWsF4qzLMps+l87VuLV9iQnbN7YMRLHHx2KkX5Ivi7JCefhCa54M0K3bDCCxuVWAM5wjQwNZjzR+WL+dYchwoFvuF8NrlzjM9dSv+2rn+J7f99ijSXarzPC7ABEBAAHNEzxhbGljZUBleGFtcGxlLmNvbT7CwIkEEAEIADMCGQEFAl086fgCGwMECwkIBwYVCAkKCwIDFgIBFiEE+iaix4d0doj87Q0ek6DcNkbrcegACgkQk6DcNkbrcei3ogf/cruUmQ+th52TFHTHdkw9OHUl3MrXtZ7QmHyOAFvbXE/6n5Eeh+eZoF8MWWV72m14Wbs+vTcNQkFVTdOPptkKA8e4cJqwDOHsyAnvQXZ7WNje9+BMzcoipIUawHP4ORFaPDsKLZQ0b4wBbKn8ziea6zjGF0/qljTdoxTtsYpv5wXYuhwbYklrLOqgSa5M7LXUe7E3g9mbg+9iX1GuB8m6GkquJN814Y+xny4xhZzGOfue6SeP12jJMNSjSP7416dRq7794VGnkkW9V/7oFEUKu5wO9FFbgDySOSlEjByGejSGuBmho0iJSjcPjZ7EY/j3M3orq4dpza5C82OeSvxDFc7ATQRdPOnsAQgA5oLxXRLnyugzOmNCy7dxV3JrDZscA6JNlJlDWIShT0YSs+zG9JzDeQql+sYXgUSxOoIayItuXtnFn7tstwGoOnYvadm/e5/7V5fKAQRtCtdN51av62n18Venlm0yNKpROPcZ6M/sc4m6uU6YRZ/a1idal8VGY0wLKlghjIBuIiBoVQ/RnoW+/fhmwIg08dQ5m8hQe3GEOZEeLrTWL/9awPlXK7Y+DmJOoR4qbHWEcRfbzS6q4zW8vk2ztB8ngwbnqYy8zrN1DCICN1gYdlU++uVw6Bb1XfY8Cdldh1VLKpF35mAmjxLZfVDcoObFH3Cv2GB7BEYxv86KC2Y6T74Q/wARAQABwsB2BBgBCAAgBQJdPOn4AhsMFiEE+iaix4d0doj87Q0ek6DcNkbrcegACgkQk6DcNkbrcegLxwf/dXshJnoWqEnRsf6rVb9/Mc66ti+NVQLfNd275dybh/QIJdK3NmSxdnTPIEJRVspJywJoupwXFNrnHG2Ff6QPvHqI+/oNu986r9d7Z1oQibbLHKt8t6kpOfg/xGxotagJuiCQvR9mMjD1DqsdB37SjDxGupZOOJSXWi6KX60IE+uM+QOBfeOZziQwuFmA5wV6RDXIeYJfqrcbeXeR1d0nfNpPHQR1gBiqmxNb6KBbdXD2+EXW60axC7D2b1APmzMlammDliPwsK9U1rc9nuquEBvGDOJf4K+Dzn+mDCqRpP6uAuQ7RKHyim4uyN0wwKObzPqgJCGwjTglkixw+aSTXw==

View File

@@ -0,0 +1,21 @@
Return-Path: <x@testrun.org>
Received: from hq5.merlinux.eu
by hq5.merlinux.eu (Dovecot) with LMTP id yRKOBakcfV1AewAAPzvFDg
; Sat, 14 Sep 2019 19:00:25 +0200
Received: from localhost (unknown 7.165.105.24])
by hq5.merlinux.eu (Postfix) with ESMTPSA id 8D9844E023;
Sat, 14 Sep 2019 19:00:22 +0200 (CEST)
DKIM-Signature: v=1; a=rsa-sha256; c=simple/simple; d=testrun.org;
s=testrun; t=1568480425;
bh=GrziQ9bkbioILHZ1tEn81B53bDgaZK+ENbW4MUBiMUM=;
h=Date:From:To:In-Reply-To:References:Subject:From;
b=eHP/GdBQD1cq601Gaqpz7fd+nQvBJLH7c+bk9KGGC3BKZrRAzehEDOaFlGP2+9oFq
cl+y11nGowenVAw7M5ljjxCpOVy0Aa+atn3e7iZeufgOJPr5Hggg2nPe4qfyP6OmOJ
i9RazyXN1rp4fY7ku9x29g818UL8aWiloJHJ9wqDhMe8OXtIAhJA0gpHDCT1SLgo/a
Kv7/Yj4CA8XS/mRxkbBmpWgcF7SluNjuXU9Npo3aTfjQQ5xSzn72Jsai4n0ppLcQ5k
KobttFy+gkmfQRAe8v7vx51qn82BSsGRlxZWevOB9zit/uk7zb8ZbBqPMeCoWsihRM
LdSqwIpokmYmQ==
Date: Sat, 14 Sep 2019 19:00:13 +0200
From: lmn <x@tux.org>
To: abc <abc@bcd.com>, def <def@def.de>,
jik

View File

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