Compare commits

..

375 Commits

Author SHA1 Message Date
dignifiedquire
2ba25f9f77 bring back memory leak 2019-09-28 12:42:53 -06:00
dignifiedquire
e23704486a fix argument passing in mmime 2019-09-28 12:10:14 -06:00
holger krekel
feee340f4d better bubble up DB errors and guard one unwrap() where a crash occured during integration test run
(and possibly also related to #633)
2019-09-28 12:48:41 +02:00
holger krekel
a5cde0d137 fix a merge-issue, and a double-if, and a wrong guard 2019-09-28 12:16:03 +02:00
dignifiedquire
b08a2b4d2c Merge remote-tracking branch 'origin/master' into safe-e2ee 2019-09-27 20:10:03 -06:00
dignifiedquire
3b6e1b0aae refactor(e2ee): reduce unsafe spread 2019-09-27 20:01:47 -06:00
holger krekel
ca76cac314 address @flub's review comment from https://github.com/deltachat/deltachat-core-rust/pull/622 2019-09-28 03:01:52 +02:00
B. Petersen
3a16ad89bd make ffi-string-parameter const
typically, nearly all string-parameters in the ffi should be const.
one of the few exceptions is dc_str_unref() that really modifies the given data.
2019-09-28 02:56:27 +02:00
dignifiedquire
fb9369f333 refactor(imex): almost all unsafe gone here 2019-09-28 02:55:42 +02:00
B. Petersen
66897611d9 fix tests according to the changed verification structure 2019-09-28 00:55:36 +02:00
B. Petersen
6888554e9d use independent verification key
there are 3 key blobs in the database, gossip_key, public_key and verified_key.
the verification_key should not change if, for any reasons,
the public_key or the gossip_key changes.
2019-09-28 00:55:36 +02:00
holger krekel
f28a971b96 several fixes and streamlinings, probably verified-group encryption is fixed, or at least we should see better errors 2019-09-27 23:24:57 +02:00
holger krekel
18808d0a61 majorly rustify and simplify the incoming decryption pipeline 2019-09-27 23:24:57 +02:00
holger krekel
86369148ee fix #616 -- allow invalid certs for smtp and imap connections -- this is the behaviour of C-core. 2019-09-27 21:44:21 +02:00
holger krekel
f45ee2ab4d fix #615 -- like with c-core Chat-Version is left in unprotected headers because
it's eg used in server-filters for detecting DC messages
2019-09-27 18:28:47 +02:00
holger krekel
2b73fab913 cargo fmt 2019-09-27 18:28:29 +02:00
holger krekel
e0d750ac64 little cleanup dc_imex 2019-09-27 18:28:29 +02:00
Alexander Krotov
bb57c6e7b7 Merge pull request #627 from deltachat/dc_receive_imf-slice
Pass slice to dc_receive_imf
2019-09-27 16:27:14 +00:00
Alexander Krotov
f346a052c1 Return Result from dc_initiate_key_transfer 2019-09-27 17:57:45 +02:00
Alexander Krotov
3933353b5f Pass slice to dc_receive_imf
instead of pointer and length
2019-09-27 17:53:41 +03:00
Dmitry Bogatov
6c9c21c135 quote_word: avoid dependency on phf crate 2019-09-27 04:11:50 +02:00
Dmitry Bogatov
d02a721eed Reimplement dc_encode_header_words in safe Rust
This change fixes proptest, introduced in [THIS~2] commit.
2019-09-27 04:11:50 +02:00
Dmitry Bogatov
8ffb4ae127 Add proptest seed that reveals strencoding bug 2019-09-27 04:11:50 +02:00
Dmitry Bogatov
96fbeb583b Add proptest to check dc_header_{encode,decode} 2019-09-27 04:11:50 +02:00
Dmitry Bogatov
33b98a15d3 Remove unused "print_hex" function 2019-09-27 04:11:50 +02:00
Dmitry Bogatov
e523ebe3c1 Implement safe version of quote_word 2019-09-27 04:11:50 +02:00
Dmitry Bogatov
e17c671b7c Rename local variables to not misleadingly refer to MMAPString 2019-09-27 04:11:50 +02:00
Dmitry Bogatov
e7565e1a2a Use rust strings instead of MMapString in src/dc_strencode.rs
Since Rust strings operations are assumed to never fail, this commit
removes a lot of checking, whether appending to mmapstring fails. Now it
is Rust runtime burden.
2019-09-27 04:11:50 +02:00
B. Petersen
b73d6377fc do not truncate messages in contact requests
core-c has truncated messages in the contact requests.
this is questionable in general, as
- all messages, including contact requests,
  are already truncated, unquoted, simplified etc.
- the ui should be capable of showing the full text anyway
  (when the contact request is accepted, the whole messase is shown)
- also, all current ui show the contact requests by name only in the
  chatlist; the user often does not even come to the contact request view.
- if the ui wants to show the contact request is a special way,
  it is probably better to leave this truncation up to the ui
2019-09-27 02:55:23 +02:00
holger krekel
31f5fffc45 cargo fmt 2019-09-26 20:45:03 +02:00
holger krekel
64c518c2f2 remove ok_to_continue 2019-09-26 20:45:03 +02:00
B. Petersen
1ed543b0e8 adapt group-id length to reality 2019-09-26 20:10:33 +02:00
Floris Bruynooghe
8b7cd2dd1a Revert back to only ffi-level checking of open context
The Rust context is always open, the return value of this function was
simply the wrong way around.
2019-09-26 20:08:36 +02:00
Florian Bruhin
8520b5211a python: Add .venv to .gitignore 2019-09-26 19:20:56 +02:00
Florian Bruhin
03661e2a71 python: Allow to configure debug logging via account 2019-09-26 19:20:56 +02:00
jikstra
20b82b3638 Fix ffi actually calling context.sql.is_open() 2019-09-26 18:36:31 +02:00
Alexander Krotov
cb499ae502 Return Result<String> from dc_decrypt_setup_file 2019-09-26 18:05:29 +02:00
holger krekel
02b73207f9 fixup this PR with tests, and returning None from get_filemime 2019-09-26 17:12:06 +02:00
jikstra
53b5cbc12a get_filemime() should return an empty string if no mimetype is present
and not default to `applicatopm/octet-stream`
2019-09-26 17:12:06 +02:00
Friedel Ziegelmayer
f4c6decd2d refactor(mmime): split up into modules (#609)
refactor(mmime): split up into modules
2019-09-26 15:28:36 +02:00
Floris Bruynooghe
69f1497986 Make dc_get_info() work on a closed context
There is very little API guarantees about this, but clients seem to
expect *something* to work on a closed context.  So split this up into
static info an more dynamic context-related info, but let's not
provide any guarantees about what keys are available when.

Fixes #599
2019-09-26 14:22:03 +02:00
Florian Bruhin
2b46f01fe3 Use sys.executable in install_python_bindings.py
When calling pip this way, the virtualenv is used even if not activated.
2019-09-26 13:09:08 +02:00
dignifiedquire
dd4adb57cf refactor(mmime): remove some duplication 2019-09-26 12:36:23 +02:00
dignifiedquire
452bce07e1 refactor(mmime): split up into modules 2019-09-26 12:36:23 +02:00
Simon Laux
8d702d0b77 rename and update providers crate 2019-09-26 01:42:39 +02:00
holger krekel
e1dc4b69f5 address all @dignifiedquire review comments 2019-09-25 23:46:44 +02:00
holger krekel
6cd3580263 rustifying dc_continue_key_transfer and fix master-conflict 2019-09-25 23:46:44 +02:00
holger krekel
d5383aecc9 finish dc_imex refactoring, fix linting, rustify some things 2019-09-25 23:46:44 +02:00
holger krekel
71cbbab2c9 fix #596 and some cleanups 2019-09-25 23:46:44 +02:00
holger krekel
8518d8f456 rustify imex and friends 2019-09-25 23:46:44 +02:00
holger krekel
adc0db04bc failing test 2019-09-25 23:46:44 +02:00
björn petersen
c61fc59003 Merge pull request #608 from deltachat/fix-get-setupcodebegin
fix boolean error that makes get_setupcodebegin() failing
2019-09-25 21:39:48 +02:00
B. Petersen
40f9072250 add get_setupcodebegin to python bindings, test the function 2019-09-25 21:13:05 +02:00
B. Petersen
ea30bb351e fix boolean error that makes get_setupcodebegin() failing 2019-09-25 20:37:36 +02:00
Alexander Krotov
b93550f6c8 Use DC_MSG_ID_LAST_SPECIAL in dc_continue_key_transfer 2019-09-25 03:29:37 +02:00
Alexander Krotov
60bd053095 Pass setup_code to dc_continue_key_transfer as &str 2019-09-25 03:29:37 +02:00
Alexander Krotov
8165b76001 Make dc_normalize_setup_code safe 2019-09-25 03:29:37 +02:00
jikstra
efc563f5ff fix test 2019-09-25 01:06:42 +02:00
jikstra
e52acc994c Make get_draft() return Ok(None) when called for a special chat id 2019-09-25 01:06:42 +02:00
holger krekel
646833d3ec remove phf crate macro usage: it introduced 7 deps and is really an optimization if you very large (100K+) tables -- we have 10 entries or so and it's called once per message. Let's not introduce crates just because we can -- it increases compile time and in the phf case also introduced a github dependency (for whatever reason -- don't want to know ;) 2019-09-25 00:19:47 +02:00
dignifiedquire
fd72c27afe chore(release): release 1.0.0-alpha.5 2019-09-24 17:26:18 +02:00
dignifiedquire
c13bcc25c6 chore(deps): update lock file 2019-09-24 17:25:03 +02:00
holger krekel
21c9ff6c85 cargo fmt 2019-09-23 23:13:41 +02:00
holger krekel
4d6b367654 remove ok_to_continue from job 2019-09-23 23:13:41 +02:00
holger krekel
e2fd22a78e cargo fmt 2019-09-23 21:23:55 +02:00
holger krekel
0759bdde01 cleanup chat.rs: remove ok_to_continue and return result from add_contact methods 2019-09-23 21:23:55 +02:00
holger krekel
faa03e0e14 no functional code change: rename dc_mimefactory to mimefactory and move some functions to become MimeFactory methods 2019-09-23 20:20:34 +02:00
holger krekel
f70897a6d3 rustify new_data_part() and related sanitizations 2019-09-23 18:43:04 +02:00
holger krekel
ba231d2c5f address @dignifiedquire comments 2019-09-23 17:10:21 +02:00
holger krekel
095cb759ed avoid cdata_to_free trick and some more cleanups 2019-09-23 17:10:21 +02:00
holger krekel
5cbcb76039 introduce safety and a particular EncryptHelper 2019-09-23 17:10:21 +02:00
holger krekel
3388b42f20 another rustification of encrypt() 2019-09-23 17:10:21 +02:00
holger krekel
e1d541b02e create wrapmime module and simplify some mailmime code 2019-09-23 17:10:21 +02:00
Friedel Ziegelmayer
cb784615ee feat: import mmime crate 2019-09-23 13:20:30 +02:00
B. Petersen
321c5e049b re-add some comments from core-c 2019-09-22 23:39:16 +02:00
holger krekel
ed7cf218f8 address three comments from @dignifiedquire 2019-09-22 23:39:16 +02:00
holger krekel
74d8368525 rustify references, in_reply_to, mimefactory's recipients_{addr,names} 2019-09-22 23:39:16 +02:00
holger krekel
dcbfa272f9 rustify parts of MimeFactory struct 2019-09-22 23:39:16 +02:00
holger krekel
42dd600e0c more rustifications 2019-09-22 23:39:16 +02:00
holger krekel
9689df601f streamline mimetype guessing and build_body_file 2019-09-22 23:39:16 +02:00
holger krekel
f6019583b7 better looping on some clists 2019-09-22 23:39:16 +02:00
holger krekel
2d50a3335d - add mailimf_field_add helper to reduce number of strdup()s
- make build_body_text avoid char*
2019-09-22 23:39:16 +02:00
holger krekel
202bfa987d dc_mimefactory+friends: simplify used strings, convert message_text and message_text2 to String, convert ints to bools 2019-09-22 23:39:16 +02:00
holger krekel
93f9c7cfbd e2ee_guaranteed -> bool, rustify set_error 2019-09-22 23:39:16 +02:00
Dmitry Bogatov
8f6a0bbf09 Remove unused argument of DC_JOB_CONFIGURE_IMAP 2019-09-22 22:22:00 +02:00
björn petersen
1a47c148e5 Merge pull request #574 from KAction/test-dc_remove_cr
Add test for `dc_remove_cr_chars`
2019-09-22 19:47:22 +02:00
björn petersen
2435ba1ea0 Merge pull request #576 from KAction/quote_word
Remove useless argument to `quote_word`
2019-09-22 19:46:48 +02:00
Dmitry Bogatov
9ba57a923b Remove useless argument to quote_word
Function "quote_word" accepts display charset argument, but on only call
site it is string constant. Change function to always use "utf-8"
display charset, and remove useless argument.
2019-09-21 22:20:56 +00:00
Dmitry Bogatov
90e2b6f26b Add test for dc_remove_cr_chars 2019-09-21 21:26:47 +00:00
dignifiedquire
05f9f454c3 refactor: remove x module and delete deadcode 2019-09-21 17:56:49 +02:00
Friedel Ziegelmayer
b85f59798c refactor(chat): remove c types and unsafe (#572)
refactor(chat): remove c types and unsafe
2019-09-21 17:14:52 +02:00
dignifiedquire
e80345a05b refactor(chat): remove c types and unsafe 2019-09-21 16:59:59 +02:00
dignifiedquire
0bdcc3d616 refactor(message): remove remaining unsafe and c types 2019-09-21 16:37:19 +02:00
dignifiedquire
987f12740e refactor(message): remove unsafe and c types from the Message api 2019-09-21 16:37:19 +02:00
dignifiedquire
1265016a55 refactor(message): rustiy api 2019-09-21 16:37:19 +02:00
Friedel Ziegelmayer
48d1de3678 Avoid ok-to-continue pattern in set_draft_raw (#570)
Avoid ok-to-continue pattern in `set_draft_raw`
2019-09-21 14:32:20 +02:00
Dmitry Bogatov
43f6db3252 cargo-fmt 2019-09-21 06:19:55 +00:00
Dmitry Bogatov
5b917e7d10 Remove last mutable variable from do_set_draft 2019-09-21 06:19:21 +00:00
Dmitry Bogatov
0523868a88 Avoid ok-to-continue pattern in "do_set_draft" function
Note: I strongly suggest reviewing this commit in side-by-side mode.

Note: This commit fails CI due incorrect formatting. It is done
deliberately to simplify review process.
2019-09-21 06:16:15 +00:00
Dmitry Bogatov
26f176eb7e Factor another part of set_draft_raw into separate function 2019-09-21 06:04:55 +00:00
Dmitry Bogatov
ad32b5ca8f cargo-fmt 2019-09-21 05:44:31 +00:00
Dmitry Bogatov
dbf14179dc Make message argument to set_draft_raw() no longer optional
Previously, "set_draft_raw" function was used with "msg = None" only for
side-effect of removing current draft message in chat.

After draft removal functionality was factored into separate
"maybe_delete_draft" function, it is used directly in only call site,
which used to invoke "set_draft_raw" with optional message.

This function is private, and this refactoring does not change FFI
interface.

Note: This commit fails CI due incorrect formatting. It is done
deliberately to simplify review process.
2019-09-21 05:34:48 +00:00
Dmitry Bogatov
45b0a4ec27 Factor part of set_draft_raw into new function maybe_delete_draft 2019-09-21 05:24:03 +00:00
dignifiedquire
1969ee02a5 refactor(mimeparser): rustify mailmime_get_type 2019-09-21 00:51:36 +02:00
dignifiedquire
266b205c75 refactor: rustify interface of maimlime_transfer_decode 2019-09-21 00:51:36 +02:00
Dmitry Bogatov
7dd3bad8bd Add factory method MimeFactory::new
* src/dc_mimefactory.rs(new): add factory method to have verbose
   initialization of all (more than 10) MimeFactory fields only in one place.
 * src/dc_mimefactory.rc(dc_mimefactory_load_msg, dc_mimefactory_load_mdn):
   simplify code (and reduce linecount) using Mimefactory::new
2019-09-20 22:49:33 +02:00
holger krekel
4b45be7cda cargo fmt only 2019-09-20 22:43:20 +02:00
holger krekel
497ffd86fa make logic and comments more like C (early returns instead of nestedness)
next commit: cargo fmt
2019-09-20 22:43:20 +02:00
dignifiedquire
0bdcc4269f refactor(mimeparser): split and cleanup parse_mime_recursive 2019-09-20 21:42:23 +02:00
Friedel Ziegelmayer
e583c99f94 Make return type of Image::mv an enum (#548)
Make return type of Image::mv an enum
2019-09-20 20:56:27 +02:00
Friedel Ziegelmayer
e22e50c3fa Rename dc_mimefactory_t -> MimeFactory (#563)
Rename dc_mimefactory_t -> MimeFactory
2019-09-20 20:56:12 +02:00
Dmitry Bogatov
e9c9a3e1ce Change type of MimeFactory.loaded to enum
* src/dc_mimefactory.rs(MimeFactory): change type of `loaded` field
 * src/dc_mimefactory.rs(Loaded): new enum, describing possible
   values of `loaded` field of `MimeFactory` structure
 * src/dc_mimefactory.rs(dc_mimefactory_loaded_t): remove unused type alias
 * src/job.rs(add_smtp_job): adjust call site by removing multiple casts
 * src/dc_mimefactory.rs(dc_mimefactory_render): ditto
2019-09-20 18:02:57 +00:00
Dmitry Bogatov
fa7bb71f3f Change type of MimeFactory.out_{gossip,encrypted} to bool 2019-09-20 18:02:57 +00:00
Dmitry Bogatov
34a3ad82e0 Rename dc_mimefactory_t -> MimeFactory
CamelCase naming convention is more natural for Rust.

https://rust-lang-nursery.github.io/api-guidelines/naming.html
2019-09-20 18:02:57 +00:00
Dmitry Bogatov
2f5d74dbf4 Remove unused constants from src/imap.rs 2019-09-20 17:35:26 +00:00
Dmitry Bogatov
4bf5ba594c Make Imap::set_mdnseen return enum, not int 2019-09-20 17:35:26 +00:00
Dmitry Bogatov
6e2da27f45 Change return type of Imap::set_seen to enum 2019-09-20 17:35:26 +00:00
Dmitry Bogatov
6ee9465d43 Make return type of Image::mv an enum
Replace named constants with enum to improve type-safety and make
exhausiveness checks possible.

Note, that since this enum never pass FFI border, its numeric values
does not need to be specified explicitly and can be left on compiler's
discretion.
2019-09-20 17:35:25 +00:00
Dmitry Bogatov
391a6bf422 Replace numbers with named constants 2019-09-20 17:35:25 +00:00
Floris Bruynooghe
5001a0e37d Fix dc_make_rel_path
This was not substituting $BLOBDIR correctly.
2019-09-20 18:38:39 +02:00
holger krekel
fd8d16a7db replace weird pointer-loops with nice for-loops (thanks @dignifiedquire for guiding) 2019-09-20 17:52:07 +02:00
holger krekel
f3ac9306f3 use bool instead of int 2019-09-20 17:52:07 +02:00
holger krekel
59740d0b56 remove unused var, numbers to const-names 2019-09-20 17:52:07 +02:00
holger krekel
fb05a6c26f transfer docs to and cleanup some parts of e2ee::decrypt() 2019-09-20 17:52:07 +02:00
holger krekel
7943b708d2 dc_mimeparser: do a round of renames on numbers to constants and add comments from the C code 2019-09-20 15:23:34 +02:00
dignifiedquire
04e37d1eca chore: update mmime to released version and other deps 2019-09-20 12:02:08 +02:00
holger krekel
91b98e8c6d as discussed during camp and otherwise ... add dc_perform_{mvbox,sentbox}_jobs hooks which, however, for now have an empty implementation. They can already be called from UIs, though. Next step is refactoring imap-job handling to only execute jobs belonging to the respective imap folder. 2019-09-20 01:03:25 +02:00
Floris Bruynooghe
70234e5b19 Add a test for fix in #541
A fixed bug should have a test.  This is an easy test to write.
2019-09-20 00:46:54 +02:00
holger krekel
ceff85d892 add test and python API for verified group handling/chatting
add msg.is_encrypted() API and check for it from some tests
2019-09-20 00:33:33 +02:00
holger krekel
9f914dd42e fix test-state modification bug (online tests running after OnlineConfigureFails tests would break) 2019-09-19 23:01:51 +02:00
holger krekel
24f5d68fef add configure-failure tests 2019-09-19 23:01:51 +02:00
Dmitry Bogatov
735fc325b1 Add test for export_key_to_asc_file 2019-09-19 21:43:54 +02:00
Dmitry Bogatov
178b216e48 Reduce number of arguments of export_key_to_asc_file
Previously, this function accepted both key id (integer) and is_default
(boolean). If `is_default` flag was set, value `id` parameter wasn't
used. This commit compresses two argument into single `id: Option<i64>`.
2019-09-19 21:43:54 +02:00
Simon Laux
5d0481f7a2 use provider overview crate instead of git 2019-09-19 20:07:34 +02:00
holger krekel
711bc69750 address @dignifiedquire comment 2019-09-19 20:03:16 +02:00
holger krekel
7263c9490d refactor rfc724_mid parsing and creation to avoid char*, add tests 2019-09-19 20:03:16 +02:00
holger krekel
0c88bc6ac7 more rfc724_mid cleanup 2019-09-19 20:03:16 +02:00
holger krekel
fda8d0a2e2 factory.rfc724_mid is a String now (instead of C-Char*) 2019-09-19 20:03:16 +02:00
holger krekel
14bdf7fae8 make dc_create_outgoing_rfc724_mid safe and simplify call sites 2019-09-19 20:03:16 +02:00
holger krekel
d4ff7ecbaa split qr tests 2019-09-19 20:00:27 +02:00
Dmitry Bogatov
030aec7373 Read example GPG keys from files using include_str!
For consistency with other tests that use example public and private
keys, replace use of `concat!` macro with `include_str!`. This way, key
data embedded into source with single line of code.
2019-09-19 18:57:39 +02:00
B. Petersen
6a351de4f9 cargo fmt 2019-09-19 18:42:29 +02:00
B. Petersen
bbff1c9c3e test that bad credentials do not panic 2019-09-19 18:42:29 +02:00
B. Petersen
bbb8144129 do not panic when 0 (=failure) is passed to the progress! macro 2019-09-19 17:00:01 +02:00
Friedel Ziegelmayer
83f3e23297 improve python caching (#468)
(@dignifiedquire and @hpk42) 
- introduce rust-caching to python test runs 
- skip release and ffi runs, they are check using python bindings
- shuffle files such that ci_scripts/ contains all the ci scripts
- partly parallelize python tox runs
2019-09-19 13:10:19 +02:00
B. Petersen
4f880932ae change oder of typedefs, function-definitions and defines
as c is top-down, we start with the typedefs
so that functions can use them freely.
after that, the functions are declared
and finally the #defines (for less noise in the function sections).
grouping is done by classes.
2019-09-19 12:58:26 +02:00
B. Petersen
62e8c2497c add missing doxygen commands, fix typo 2019-09-19 12:58:26 +02:00
Simon Laux
37f854be3e Add provider info functions to the FFI API
A lot of work from @Hocuri and @Simon-Laux mostly.

This exposes the API of the deltachat-provider-overview crate on the
deltachat FFI API, allowing clients to use it to help users set up
their accounts.
2019-09-19 07:17:44 +02:00
Dmitry Bogatov
e0e82e1877 Fix ffi interoperability issue
Clients expect empty "dbfile" argument to be treated as NULL value.
This change fulfills their expectations.

Closes: #530
2019-09-19 07:15:35 +02:00
Dmitry Bogatov
95d8665dbe Refine signature of dc_get_oauth2_access_token()
Previously, `dc_get_oauth2_access_token` accepted "flags" argument,
that actually had only one possible field: 0x1 == DC_REGENERATE.

This change replaces "flags" argument with single boolean argument
"regenerate".
2019-09-18 19:29:39 +02:00
dignifiedquire
8667de994e use proper Result instead of Option for encryption and decryption 2019-09-18 19:21:41 +02:00
holger krekel
cee0e22ce7 fix #538 -- don't crash on wrong setup codes for ac-message, don't use "expect(), added test 2019-09-18 19:21:41 +02:00
holger krekel
dc8a2f54e5 use utf8 lossy strings for saved mime headers (as discussed on PR and IRC with @r10s) 2019-09-18 16:47:04 +02:00
dignifiedquire
b3bc5b2520 fix(receive_imf): do not attempt to convert raw body to utf8 2019-09-18 16:47:04 +02:00
B. Petersen
00e929afac reformat: use shorter lines (max. 80 chats) and try to add linebreaks semantically 2019-09-18 15:20:34 +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
Alexander Krotov
46520edd87 Print error if CAPABILITY command fails. 2019-09-04 15:12:00 +02:00
Simon Laux
14daa99802 remove ok_to_continue7 comment 2019-09-04 15:07:45 +02:00
Simon Laux
4f9f67a477 switch to while instead of loop 2019-09-04 15:07:45 +02:00
Simon Laux
85f182067c typo fixes in comments 2019-09-04 15:07:45 +02:00
Simon Laux
66ab6874f8 reorganize dc_job_do_DC_JOB_CONFIGURE_IMAP 2019-09-04 15:07:45 +02:00
Simon Laux
906b901e3d move function order / filestructure like in c core 2019-09-04 15:07:45 +02:00
Alexander Krotov
65adff4bdd Merge pull request #437 from KAction/spelling
Fix minor spelling errors in python/README.rst
2019-09-04 10:55:58 +00:00
Dmitry Bogatov
e60fc0dc30 Fix minor spelling errors in python/README.rst 2019-09-04 06:19:21 +00:00
Alexander Krotov
ffd719962c Merge pull request #430 from deltachat/latest-imap
Update to the latest rust-imap
2019-09-03 17:12:12 +00:00
dignifiedquire
1a1f0c0a7c refactor(e2ee): rename dc_e2ee -> e2ee 2019-09-03 19:05:21 +02:00
dignifiedquire
3944592c09 refactor(e2ee): restructure types a and method slightly 2019-09-03 19:05:21 +02:00
holger krekel
a5f862a564 remove python2 testing 2019-09-03 19:04:09 +02:00
Alexander Krotov
489f25940f Update to the latest rust-imap
It uses imap-proto 0.8.1 instead of outdated 0.7.0.
2019-09-03 19:14:56 +03:00
Dmitry Bogatov
6288909481 Refactor is_known_rfc724_mid() to use Iterator interface 2019-09-03 16:08:52 +02:00
Dmitry Bogatov
c95f134963 Change return type of is_known_rfc724_mid_in_list() to bool 2019-09-03 16:08:52 +02:00
Simon Laux
024c2883c0 cargo fmt 2019-09-03 15:32:36 +02:00
Simon Laux
d7b0ecad75 transform current_block to ok_to_continue 2019-09-03 15:32:36 +02:00
B. Petersen
50947b81c0 set correct default value for inbox_watch 2019-09-03 15:31:17 +02:00
holger krekel
e5a1b721f1 try use py36 for uploads (#429)
* try use py36 for uploads

* another try

* another
2019-09-03 15:30:28 +02:00
Alexander Krotov
aeb1a88e7a Remove dc_move.rs
and move dc_do_heuristics_moves to context.rs
2019-09-03 13:39:14 +02:00
Alexander Krotov
c406675d6a Mark dc_do_heuristics_moves as safe 2019-09-03 13:39:14 +02:00
Alexander Krotov
1ec193991b Make dc_token.rs safe 2019-09-02 20:57:22 +02:00
124 changed files with 35432 additions and 15078 deletions

View File

@@ -12,7 +12,7 @@ restore-workspace: &restore-workspace
restore-cache: &restore-cache
restore_cache:
keys:
- cargo-v1-{{ checksum "rust-toolchain" }}-{{ checksum "Cargo.toml" }}-{{ checksum "Cargo.lock" }}-{{ arch }}
- cargo-v2-{{ checksum "rust-toolchain" }}-{{ checksum "Cargo.toml" }}-{{ checksum "Cargo.lock" }}-{{ arch }}
- repo-source-{{ .Branch }}-{{ .Revision }}
commands:
@@ -23,20 +23,9 @@ commands:
steps:
- *restore-workspace
- *restore-cache
- setup_remote_docker:
docker_layer_caching: true
# TODO: move into image
- run:
name: Install Docker client
command: |
set -x
VER="18.09.2"
curl -L -o /tmp/docker-$VER.tgz https://download.docker.com/linux/static/stable/x86_64/docker-$VER.tgz
tar -xz -C /tmp -f /tmp/docker-$VER.tgz
mv /tmp/docker/* /usr/bin
- run:
name: Test (<< parameters.target >>)
command: TARGET=<< parameters.target >> ci/run.sh
command: TARGET=<< parameters.target >> ci_scripts/run-rust-test.sh
no_output_timeout: 15m
jobs:
@@ -52,7 +41,7 @@ jobs:
command: cargo generate-lockfile
- restore_cache:
keys:
- cargo-v1-{{ checksum "rust-toolchain" }}-{{ checksum "Cargo.toml" }}-{{ checksum "Cargo.lock" }}-{{ arch }}
- cargo-v2-{{ checksum "rust-toolchain" }}-{{ checksum "Cargo.toml" }}-{{ checksum "Cargo.lock" }}-{{ arch }}
- run: rustup install $(cat rust-toolchain)
- run: rustup default $(cat rust-toolchain)
- run: rustup component add --toolchain $(cat rust-toolchain) rustfmt
@@ -61,13 +50,14 @@ jobs:
- run: cargo fetch
- run: rustc +stable --version
- run: rustc +$(cat rust-toolchain) --version
- run: rm -rf .git
# make sure this git repo doesn't grow too big
- run: git gc
- persist_to_workspace:
root: /mnt
paths:
- crate
- save_cache:
key: cargo-v1-{{ checksum "rust-toolchain" }}-{{ checksum "Cargo.toml" }}-{{ checksum "Cargo.lock" }}-{{ arch }}
key: cargo-v2-{{ checksum "rust-toolchain" }}-{{ checksum "Cargo.toml" }}-{{ checksum "Cargo.lock" }}-{{ arch }}
paths:
- "~/.cargo"
- "~/.rustup"
@@ -102,7 +92,7 @@ jobs:
- run: cargo fetch
- run:
name: Test
command: TARGET=x86_64-apple-darwin ci/run.sh
command: TARGET=x86_64-apple-darwin ci_scripts/run-rust-test.sh
test_x86_64-unknown-linux-gnu:
executor: default
@@ -124,18 +114,18 @@ jobs:
build_test_docs_wheel:
machine: True
steps:
- checkout
# - run: docker pull deltachat/doxygen
- run: docker pull deltachat/coredeps
- run:
name: build docs, run tests and build wheels
command: ci_scripts/ci_run.sh
environment:
docker:
- image: deltachat/coredeps
environment:
TESTS: 1
DOCS: 1
working_directory: /mnt/crate
steps:
- *restore-workspace
- *restore-cache
- run:
name: build docs, run tests and build wheels
command: ci_scripts/run-python.sh
- run:
name: copying docs and wheels to workspace
command: |
@@ -143,7 +133,6 @@ jobs:
# cp -av docs workspace/c-docs
cp -av python/.docker-tox/wheelhouse workspace/
cp -av python/doc/_build/ workspace/py-docs
- persist_to_workspace:
root: workspace
paths:
@@ -176,15 +165,16 @@ workflows:
test:
jobs:
- build_test_docs_wheel
- cargo_fetch
- build_test_docs_wheel:
requires:
- cargo_fetch
- upload_docs_wheels:
requires:
- build_test_docs_wheel
- cargo_fetch
- rustfmt:
requires:
- cargo_fetch
- clippy:
requires:
- cargo_fetch

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

1
.gitignore vendored
View File

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

762
Cargo.lock generated

File diff suppressed because it is too large Load Diff

View File

@@ -1,30 +1,26 @@
[package]
name = "deltachat"
version = "1.0.0-alpha.4"
version = "1.0.0-alpha.5"
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" }
mmime = { version = "0.1.2", path = "./mmime" }
libc = "0.2.51"
pgp = { version = "0.2", default-features = false }
hex = "0.3.2"
sha2 = "0.8.0"
rand = "0.6.5"
phf = { git = "https://github.com/sfackler/rust-phf", rev = "0d00821", features = ["macros"] }
smallvec = "0.6.9"
reqwest = "0.9.15"
num-derive = "0.2.5"
num-traits = "0.2.6"
native-tls = "0.2.3"
lettre = "0.9.0"
imap = "1.0.1"
mmime = "0.1.0"
imap = { git = "https://github.com/jonhoo/rust-imap", rev = "281d2eb8ab50dc656ceff2ae749ca5045f334e15" }
base64 = "0.10"
charset = "0.1"
percent-encoding = "2.0"
@@ -35,13 +31,13 @@ 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"
r2d2 = "0.8.5"
strum = "0.15.0"
strum_macros = "0.15.0"
strum = "0.16.0"
strum_macros = "0.16.0"
thread-local-object = "0.1.0"
backtrace = "0.3.33"
byteorder = "1.3.1"
@@ -50,16 +46,20 @@ 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 = [
"deltachat-ffi",
"deltachat_derive",
"mmime",
]
[[example]]

View File

@@ -13,7 +13,7 @@ install:
build: false
test_script:
- cargo test --release
- cargo test --release --all
cache:
- target

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

@@ -1,22 +0,0 @@
# perform CI jobs on PRs and after merges to master.
# triggered from .circleci/config.yml
set -e -x
export BRANCH=${CIRCLE_BRANCH:-test7}
# run doxygen on c-source (needed by later doc-generation steps).
# XXX modifies the host filesystem docs/xml and docs/html directories
# XXX which you can then only remove with sudo as they belong to root
# XXX we don't do doxygen doc generation with Rust anymore, needs to be
# substituted with rust-docs
#if [ -n "$DOCS" ] ; then
# docker run --rm -it -v $PWD:/mnt -w /mnt/docs deltachat/doxygen doxygen
#fi
# run everything else inside docker (TESTS, DOCS, WHEELS)
docker run -e DCC_PY_LIVECONFIG -e BRANCH -e TESTS -e DOCS \
--rm -it -v $(pwd):/mnt -w /mnt \
deltachat/coredeps ci_scripts/run_all.sh

View File

@@ -34,7 +34,6 @@ echo -----------------------
# Bundle external shared libraries into the wheels
pushd $WHEELHOUSEDIR
pip3 install -U setuptools
pip3 install devpi-client
devpi use https://m.devpi.net
devpi login dc --password $DEVPI_LOGIN

View File

@@ -36,19 +36,25 @@ if [ -n "$TESTS" ]; then
rm -rf src/deltachat/__pycache__
export PYTHONDONTWRITEBYTECODE=1
# run tox
# XXX we don't run liveconfig tests because they hang sometimes
# see https://github.com/deltachat/deltachat-core-rust/issues/331
# unset DCC_PY_LIVECONFIG
tox --workdir "$TOXWORKDIR" -e lint,py27,py35,py36,py37,auditwheels
# run tox. The circle-ci project env-var-setting DCC_PY_LIVECONFIG
# allows running of "liveconfig" tests but for speed reasons
# we run them only for the highest python version we support
# we split out qr-tests run to minimize likelyness of flaky tests
# (some qr tests are pretty heavy in terms of send/received
# messages and rust's imap code likely has concurrency problems)
tox --workdir "$TOXWORKDIR" -e py37 -- -k "not qr"
tox --workdir "$TOXWORKDIR" -e py37 -- -k "qr"
unset DCC_PY_LIVECONFIG
tox --workdir "$TOXWORKDIR" -p4 -e lint,py35,py36,doc
tox --workdir "$TOXWORKDIR" -e auditwheels
popd
fi
if [ -n "$DOCS" ]; then
echo -----------------------
echo generating python docs
echo -----------------------
(cd python && tox --workdir "$TOXWORKDIR" -e doc)
fi
# if [ -n "$DOCS" ]; then
# echo -----------------------
# echo generating python docs
# echo -----------------------
# (cd python && tox --workdir "$TOXWORKDIR" -e doc)
# fi

View File

@@ -31,7 +31,7 @@ fi
if [[ $NORUN == "1" ]]; then
export CARGO_SUBCMD="build"
else
export CARGO_SUBCMD="test"
export CARGO_SUBCMD="test --all"
export OPT="${OPT} "
export OPT_RELEASE="${OPT_RELEASE} "
export OPT_RELEASE_IGNORED="${OPT_RELEASE} -- --ignored"
@@ -41,6 +41,3 @@ fi
$CARGO_CMD $CARGO_SUBCMD $OPT
$CARGO_CMD $CARGO_SUBCMD $OPT_RELEASE
$CARGO_CMD $CARGO_SUBCMD $OPT_RELEASE_IGNORED
# Build the ffi lib
$CARGO_CMD $CARGO_SUBCMD $OPT_FFI_RELEASE

View File

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

View File

@@ -11,6 +11,16 @@ extern "C" {
#endif
typedef struct _dc_context dc_context_t;
typedef struct _dc_array dc_array_t;
typedef struct _dc_chatlist dc_chatlist_t;
typedef struct _dc_chat dc_chat_t;
typedef struct _dc_msg dc_msg_t;
typedef struct _dc_contact dc_contact_t;
typedef struct _dc_lot dc_lot_t;
typedef struct _dc_provider dc_provider_t;
/**
* @mainpage Getting started
*
@@ -189,13 +199,6 @@ extern "C" {
* SQLite database for offline functionality and for account-related
* settings.
*/
typedef struct _dc_context dc_context_t;
typedef struct _dc_array dc_array_t;
typedef struct _dc_chatlist dc_chatlist_t;
typedef struct _dc_chat dc_chat_t;
typedef struct _dc_msg dc_msg_t;
typedef struct _dc_contact dc_contact_t;
typedef struct _dc_lot dc_lot_t;
/**
@@ -393,17 +396,23 @@ 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);
/**
* Get information about the context.
*
* The information is returned by a multi-line string
* and contains information about the current configuration.
*
* If the context is not open or configured only a subset of the information
* will be available. There is no guarantee about which information will be
* included when however.
*
* @memberof dc_context_t
* @param context The context as created by dc_context_new().
* @return String which must be free()'d after usage. Never returns NULL.
@@ -442,7 +451,6 @@ char* dc_get_info (dc_context_t* context);
char* dc_get_oauth2_url (dc_context_t* context, const char* addr, const char* redirect_uri);
// connect
/**
@@ -607,12 +615,8 @@ void dc_interrupt_imap_idle (dc_context_t* context);
/**
* Fetch new messages from the MVBOX, if any.
* The MVBOX is a folder on the account where chat messages are moved to.
* The moving is done to not disturb shared accounts that are used by both,
* Delta Chat and a classical MUA.
*
* This function and dc_perform_mvbox_idle()
* Execute pending mvbox-jobs.
* This function and dc_perform_mvbox_fetch() and dc_perform_mvbox_idle()
* must be called from the same thread, typically in a loop.
*
* Example:
@@ -620,6 +624,7 @@ void dc_interrupt_imap_idle (dc_context_t* context);
* void* mvbox_thread_func(void* context)
* {
* while (true) {
* dc_perform_mvbox_jobs(context);
* dc_perform_mvbox_fetch(context);
* dc_perform_mvbox_idle(context);
* }
@@ -633,13 +638,26 @@ void dc_interrupt_imap_idle (dc_context_t* context);
*
* // network becomes available again -
* // the interrupt causes dc_perform_mvbox_idle() in the thread above
* // to return so that and messages are fetched.
* // to return so that jobs are executed and messages are fetched.
* dc_maybe_network(context);
*
* @memberof dc_context_t
* @param context The context as created by dc_context_new().
* @return None.
*/
void dc_perform_mvbox_jobs (dc_context_t* context);
/**
* Fetch new messages from the MVBOX, if any.
* The MVBOX is a folder on the account where chat messages are moved to.
* The moving is done to not disturb shared accounts that are used by both,
* Delta Chat and a classical MUA.
*
* @memberof dc_context_t
* @param context The context as created by dc_context_new().
* @return None.
*/
void dc_perform_mvbox_fetch (dc_context_t* context);
@@ -676,6 +694,39 @@ void dc_perform_mvbox_idle (dc_context_t* context);
*/
void dc_interrupt_mvbox_idle (dc_context_t* context);
/**
* Execute pending sentbox-jobs.
* This function and dc_perform_sentbox_fetch() and dc_perform_sentbox_idle()
* must be called from the same thread, typically in a loop.
*
* Example:
*
* void* sentbox_thread_func(void* context)
* {
* while (true) {
* dc_perform_sentbox_jobs(context);
* dc_perform_sentbox_fetch(context);
* dc_perform_sentbox_idle(context);
* }
* }
*
* // start sentbox-thread that runs forever
* pthread_t sentbox_thread;
* pthread_create(&sentbox_thread, NULL, sentbox_thread_func, context);
*
* ... program runs ...
*
* // network becomes available again -
* // the interrupt causes dc_perform_sentbox_idle() in the thread above
* // to return so that jobs are executed and messages are fetched.
* dc_maybe_network(context);
*
* @memberof dc_context_t
* @param context The context as created by dc_context_new().
* @return None.
*/
void dc_perform_sentbox_jobs (dc_context_t* context);
/**
* Fetch new messages from the Sent folder, if any.
@@ -3418,6 +3469,110 @@ int dc_contact_is_blocked (const dc_contact_t* contact);
int dc_contact_is_verified (dc_contact_t* contact);
/**
* @class dc_provider_t
*
* Opaque object containing information about one single email provider.
*/
/**
* Create a provider struct for the given domain.
*
* @memberof dc_provider_t
* @param domain The domain to get provider info for.
* @return a dc_provider_t struct which can be used with the dc_provider_get_*
* accessor functions. If no provider info is found, NULL will be
* returned.
*/
dc_provider_t* dc_provider_new_from_domain (const char* domain);
/**
* Create a provider struct for the given email address.
*
* The provider is extracted from the email address and it's information is returned.
*
* @memberof dc_provider_t
* @param email The user's email address to extract the provider info form.
* @return a dc_provider_t struct which can be used with the dc_provider_get_*
* accessor functions. If no provider info is found, NULL will be
* returned.
*/
dc_provider_t* dc_provider_new_from_email (const char* email);
/**
* URL of the overview page.
*
* This URL allows linking to the providers page on providers.delta.chat.
*
* @memberof dc_provider_t
* @param provider The dc_provider_t struct.
* @return A string which must be free()d.
*/
char* dc_provider_get_overview_page (const dc_provider_t* provider);
/**
* The provider's name.
*
* The name of the provider, e.g. "POSTEO".
*
* @memberof dc_provider_t
* @param provider The dc_provider_t struct.
* @return A string which must be free()d.
*/
char* dc_provider_get_name (const dc_provider_t* provider);
/**
* The markdown content of the providers page.
*
* This contains the preparation steps or additional information if the status
* is @ref DC_PROVIDER_STATUS_BROKEN.
*
* @memberof dc_provider_t
* @param provider The dc_provider_t struct.
* @return A string which must be free()d.
*/
char* dc_provider_get_markdown (const dc_provider_t* provider);
/**
* Date of when the state was last checked/updated.
*
* This is returned as a string.
*
* @memberof dc_provider_t
* @param provider The dc_provider_t struct.
* @return A string which must be free()d.
*/
char* dc_provider_get_status_date (const dc_provider_t* provider);
/**
* Whether DC works with this provider.
*
* Can be one of @ref DC_PROVIDER_STATUS_OK, @ref
* DC_PROVIDER_STATUS_PREPARATION and @ref DC_PROVIDER_STATUS_BROKEN.
*
* @memberof dc_provider_t
* @param provider The dc_provider_t struct.
* @return The status as a constant number.
*/
int dc_provider_get_status (const dc_provider_t* provider);
/**
* Free the provider info struct.
*
* @memberof dc_provider_t
* @param provider The dc_provider_t struct.
*/
void dc_provider_unref (const dc_provider_t* provider);
/**
* @class dc_lot_t
*
@@ -4025,6 +4180,41 @@ void dc_array_add_id (dc_array_t*, uint32_t); // depreca
#define DC_SHOW_EMAILS_ALL 2
/**
* @defgroup DC_PROVIDER_STATUS DC_PROVIDER_STATUS
*
* These constants are used as return values for dc_provider_get_status().
*
* @addtogroup DC_PROVIDER_STATUS
* @{
*/
/**
* Provider status returned by dc_provider_get_status().
*
* Works right out of the box without any preperation steps needed
*/
#define DC_PROVIDER_STATUS_OK 1
/**
* Provider status returned by dc_provider_get_status().
*
* Works, but preparation steps are needed
*/
#define DC_PROVIDER_STATUS_PREPARATION 2
/**
* Provider status returned by dc_provider_get_status().
*
* Doesn't work (too unstable to use falls also in this category)
*/
#define DC_PROVIDER_STATUS_BROKEN 3
/**
* @}
*/
/*
* TODO: Strings need some doumentation about used placeholders.
*

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,92 @@
extern crate deltachat_provider_database;
use std::ptr;
use deltachat::dc_tools::{as_str, StrExt};
use deltachat_provider_database::StatusState;
#[no_mangle]
pub type dc_provider_t = deltachat_provider_database::Provider;
#[no_mangle]
pub unsafe extern "C" fn dc_provider_new_from_domain(
domain: *const libc::c_char,
) -> *const dc_provider_t {
match deltachat_provider_database::get_provider_info(as_str(domain)) {
Some(provider) => provider,
None => ptr::null(),
}
}
#[no_mangle]
pub unsafe extern "C" fn dc_provider_new_from_email(
email: *const libc::c_char,
) -> *const dc_provider_t {
let domain = deltachat_provider_database::get_domain_from_email(as_str(email));
match deltachat_provider_database::get_provider_info(domain) {
Some(provider) => provider,
None => ptr::null(),
}
}
macro_rules! null_guard {
($context:tt) => {
if $context.is_null() {
return ptr::null_mut() as *mut libc::c_char;
}
};
}
#[no_mangle]
pub unsafe extern "C" fn dc_provider_get_overview_page(
provider: *const dc_provider_t,
) -> *mut libc::c_char {
null_guard!(provider);
format!(
"{}/{}",
deltachat_provider_database::PROVIDER_OVERVIEW_URL,
(*provider).overview_page
)
.strdup()
}
#[no_mangle]
pub unsafe extern "C" fn dc_provider_get_name(provider: *const dc_provider_t) -> *mut libc::c_char {
null_guard!(provider);
(*provider).name.strdup()
}
#[no_mangle]
pub unsafe extern "C" fn dc_provider_get_markdown(
provider: *const dc_provider_t,
) -> *mut libc::c_char {
null_guard!(provider);
(*provider).markdown.strdup()
}
#[no_mangle]
pub unsafe extern "C" fn dc_provider_get_status_date(
provider: *const dc_provider_t,
) -> *mut libc::c_char {
null_guard!(provider);
(*provider).status.date.strdup()
}
#[no_mangle]
pub unsafe extern "C" fn dc_provider_get_status(provider: *const dc_provider_t) -> u32 {
if provider.is_null() {
return 0;
}
match (*provider).status.state {
StatusState::OK => 1,
StatusState::PREPARATION => 2,
StatusState::BROKEN => 3,
}
}
#[no_mangle]
pub unsafe extern "C" fn dc_provider_unref(_provider: *const dc_provider_t) {
()
}
// TODO expose general provider overview url?

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

@@ -1,5 +1,4 @@
use std::ffi::CString;
use std::ptr;
use std::path::Path;
use std::str::FromStr;
use deltachat::chat::{self, Chat};
@@ -9,29 +8,28 @@ use deltachat::configure::*;
use deltachat::constants::*;
use deltachat::contact::*;
use deltachat::context::*;
use deltachat::dc_imex::*;
use deltachat::dc_receive_imf::*;
use deltachat::dc_tools::*;
use deltachat::error::Error;
use deltachat::imex::*;
use deltachat::job::*;
use deltachat::location;
use deltachat::lot::LotState;
use deltachat::message::*;
use deltachat::message::{self, Message, MessageState};
use deltachat::peerstate::*;
use deltachat::qr::*;
use deltachat::sql;
use deltachat::types::*;
use deltachat::x::*;
use num_traits::FromPrimitive;
use deltachat::Event;
use libc::free;
/// 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 +39,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,182 +82,139 @@ 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
}
unsafe fn dc_poke_eml_file(context: &Context, filename: *const libc::c_char) -> libc::c_int {
/* 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;
if !(dc_read_file(
context,
filename,
&mut data as *mut *mut libc::c_char as *mut *mut libc::c_void,
&mut data_bytes,
) == 0i32)
{
dc_receive_imf(context, data, data_bytes, "import", 0, 0);
success = 1;
}
free(data as *mut libc::c_void);
fn dc_poke_eml_file(context: &Context, filename: impl AsRef<Path>) -> Result<(), Error> {
let data = dc_read_file(context, filename)?;
success
unsafe { dc_receive_imf(context, &data, "import", 0, 0) };
Ok(())
}
/// Import a file to the database.
/// For testing, import a folder with eml-files, a single eml-file, e-mail plus public key and so on.
/// For normal importing, use dc_imex().
/// For normal importing, use imex().
///
/// @private @memberof Context
/// @param context The context as created by dc_context_new().
/// @param spec The file or directory to import. NULL for the last command.
/// @return 1=success, 0=error.
unsafe fn poke_spec(context: &Context, spec: *const libc::c_char) -> libc::c_int {
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;
}
let ok_to_continue;
let mut success: libc::c_int = 0;
let real_spec: *mut libc::c_char;
let mut suffix: *mut libc::c_char = ptr::null_mut();
let mut read_cnt: libc::c_int = 0;
let real_spec: String;
let mut read_cnt = 0;
/* if `spec` is given, remember it for later usage; if it is not given, try to use the last one */
if !spec.is_null() {
real_spec = dc_strdup(spec);
real_spec = to_string(spec);
context
.sql
.set_config(context, "import_spec", Some(as_str(real_spec)))
.set_config(context, "import_spec", Some(&real_spec))
.unwrap();
ok_to_continue = true;
} else {
let rs = context.sql.get_config(context, "import_spec");
if rs.is_none() {
error!(context, 0, "Import: No file or folder given.");
ok_to_continue = false;
} else {
ok_to_continue = true;
error!(context, "Import: No file or folder given.");
return 0;
}
real_spec = rs.unwrap_or_default().strdup();
real_spec = rs.unwrap();
}
if ok_to_continue {
let ok_to_continue2;
suffix = dc_get_filesuffix_lc(as_str(real_spec));
if !suffix.is_null() && strcmp(suffix, b"eml\x00" as *const u8 as *const libc::c_char) == 0
{
if 0 != dc_poke_eml_file(context, real_spec) {
if let Some(suffix) = dc_get_filesuffix_lc(&real_spec) {
if suffix == "eml" {
if dc_poke_eml_file(context, &real_spec).is_ok() {
read_cnt += 1
}
ok_to_continue2 = true;
}
} else {
/* import a directory */
let dir_name = std::path::Path::new(&real_spec);
let dir = std::fs::read_dir(dir_name);
if dir.is_err() {
error!(context, "Import: Cannot open directory \"{}\".", &real_spec,);
return 0;
} else {
/* import a directory */
let dir_name = std::path::Path::new(as_str(real_spec));
let dir = std::fs::read_dir(dir_name);
if dir.is_err() {
error!(
context,
0,
"Import: Cannot open directory \"{}\".",
as_str(real_spec),
);
ok_to_continue2 = false;
} else {
let dir = dir.unwrap();
for entry in dir {
if entry.is_err() {
break;
}
let entry = entry.unwrap();
let name_f = entry.file_name();
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);
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
}
let dir = dir.unwrap();
for entry in dir {
if entry.is_err() {
break;
}
let entry = entry.unwrap();
let name_f = entry.file_name();
let name = name_f.to_string_lossy();
if name.ends_with(".eml") {
let path_plus_name = format!("{}/{}", &real_spec, name);
info!(context, "Import: {}", path_plus_name);
if dc_poke_eml_file(context, path_plus_name).is_ok() {
read_cnt += 1
}
}
ok_to_continue2 = true;
}
}
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);
}
success = 1
}
}
free(real_spec as *mut libc::c_void);
free(suffix as *mut libc::c_void);
success
info!(
context,
"Import: {} items read from \"{}\".", read_cnt, &real_spec
);
if read_cnt > 0 {
context.call_cb(Event::MsgsChanged {
chat_id: 0,
msg_id: 0,
});
}
1
}
unsafe fn log_msg(context: &Context, prefix: impl AsRef<str>, msg: &Message) {
let contact = Contact::get_by_id(context, dc_msg_get_from_id(msg)).expect("invalid contact");
let contact = Contact::get_by_id(context, msg.get_from_id()).expect("invalid contact");
let contact_name = contact.get_name();
let contact_id = contact.get_id();
let statestr = match dc_msg_get_state(msg) {
let statestr = match msg.get_state() {
MessageState::OutPending => " o",
MessageState::OutDelivered => "",
MessageState::OutMdnRcvd => " √√",
MessageState::OutFailed => " !!",
_ => "",
};
let temp2 = dc_timestamp_to_str(dc_msg_get_timestamp(msg));
let msgtext = dc_msg_get_text(msg);
let temp2 = dc_timestamp_to_str(msg.get_timestamp());
let msgtext = msg.get_text();
info!(
context,
0,
"{}#{}{}{}: {} (Contact#{}): {} {}{}{}{} [{}]",
prefix.as_ref(),
dc_msg_get_id(msg) as libc::c_int,
if 0 != dc_msg_get_showpadlock(msg) {
"🔒"
} else {
""
},
if dc_msg_has_location(msg) { "📍" } else { "" },
msg.get_id() as libc::c_int,
if msg.get_showpadlock() { "🔒" } else { "" },
if msg.has_location() { "📍" } else { "" },
&contact_name,
contact_id,
as_str(msgtext),
if dc_msg_is_starred(msg) { "" } else { "" },
if dc_msg_get_from_id(msg) == 1 as libc::c_uint {
msgtext.unwrap_or_default(),
if msg.is_starred() { "" } else { "" },
if msg.get_from_id() == 1 as libc::c_uint {
""
} else if dc_msg_get_state(msg) == MessageState::InSeen {
} else if msg.get_state() == MessageState::InSeen {
"[SEEN]"
} else if dc_msg_get_state(msg) == MessageState::InNoticed {
} else if msg.get_state() == MessageState::InNoticed {
"[NOTICED]"
} else {
"[FRESH]"
},
if 0 != dc_msg_is_info(msg) {
"[INFO]"
} else {
""
},
if msg.is_info() { "[INFO]" } else { "" },
statestr,
&temp2,
);
free(msgtext as *mut libc::c_void);
}
unsafe fn log_msglist(context: &Context, msglist: &Vec<u32>) -> Result<(), Error> {
@@ -268,7 +223,6 @@ unsafe fn log_msglist(context: &Context, msglist: &Vec<u32>) -> Result<(), Error
if msg_id == 9 as libc::c_uint {
info!(
context,
0,
"--------------------------------------------------------------------------------"
);
@@ -276,19 +230,19 @@ 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
}
let msg = dc_get_msg(context, msg_id)?;
let msg = Message::load_from_db(context, msg_id)?;
log_msg(context, "Msg", &msg);
}
}
if lines_out > 0 {
info!(
context,
0, "--------------------------------------------------------------------------------"
"--------------------------------------------------------------------------------"
);
}
Ok(())
@@ -305,7 +259,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 +291,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()
}
@@ -369,12 +317,8 @@ pub unsafe fn dc_cmdline(context: &Context, line: &str) -> Result<(), failure::E
arg1.strdup() as *const _
};
let arg2 = args.next().unwrap_or_default();
let arg2_c = if arg2.is_empty() {
std::ptr::null()
} else {
arg2.strdup() as *const _
};
let blobdir = context.get_blobdir();
match arg0 {
"help" | "?" => match arg1 {
// TODO: reuse commands definition in main.rs.
@@ -428,7 +372,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,52 +405,24 @@ 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() {
println!(
"Setup code for the transferred setup message: {}",
as_str(setup_code),
);
free(setup_code as *mut libc::c_void);
} else {
bail!("Failed to generate setup code");
};
}
"initiate-key-transfer" => match initiate_key_transfer(context) {
Ok(setup_code) => println!(
"Setup code for the transferred setup message: {}",
setup_code,
),
Err(err) => bail!("Failed to generate setup code: {}", err),
},
"get-setupcodebegin" => {
ensure!(!arg1.is_empty(), "Argument <msg-id> missing.");
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 msg = Message::load_from_db(context, msg_id)?;
if msg.is_setupmessage() {
let setupcodebegin = msg.get_setupcodebegin(context);
println!(
"The setup code for setup message Msg#{} starts with: {}",
msg_id,
as_str(setupcodebegin),
setupcodebegin.unwrap_or_default(),
);
free(setupcodebegin as *mut libc::c_void);
} else {
bail!("Msg#{} is no setup message.", msg_id,);
}
@@ -516,43 +432,34 @@ 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) {
bail!("Continue key transfer failed");
}
continue_key_transfer(context, arg1.parse()?, &arg2)?;
}
"has-backup" => {
let ret = dc_imex_has_backup(context, context.get_blobdir());
if ret.is_null() {
println!("No backup found.");
}
has_backup(context, blobdir)?;
}
"export-backup" => {
dc_imex(context, 11, context.get_blobdir(), ptr::null());
imex(context, ImexMode::ExportBackup, Some(blobdir));
}
"import-backup" => {
ensure!(!arg1.is_empty(), "Argument <backup-file> missing.");
dc_imex(context, 12, arg1_c, ptr::null());
imex(context, ImexMode::ImportBackup, Some(arg1));
}
"export-keys" => {
dc_imex(context, 1, context.get_blobdir(), ptr::null());
imex(context, ImexMode::ExportSelfKeys, Some(blobdir));
}
"import-keys" => {
dc_imex(context, 2, context.get_blobdir(), ptr::null());
imex(context, ImexMode::ImportSelfKeys, Some(blobdir));
}
"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_content = dc_render_setup_file(context, &setup_code)?;
std::fs::write(as_str(file_name), file_content)?;
let setup_code = create_setup_code(context);
let file_name = blobdir.join("autocrypt-setup-message.html");
let file_content = render_setup_file(context, &setup_code)?;
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 +486,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 +506,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 +523,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 +540,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 +553,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 +578,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 +594,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 +607,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 +636,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 +653,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 +677,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 +705,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 +718,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 +765,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
},
);
dc_msg_set_file(&mut msg, arg1_c, ptr::null());
dc_msg_set_text(&mut msg, arg2_c);
let mut msg = Message::new(if arg0 == "sendimage" {
Viewtype::Image
} else {
Viewtype::File
});
msg.set_file(arg1, None);
if !arg2.is_empty() {
msg.set_text(Some(arg2.to_string()));
}
chat::send_msg(context, sel_chat.as_ref().unwrap().get_id(), &mut msg)?;
}
"listmsgs" => {
@@ -885,7 +787,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,8 +796,8 @@ 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);
dc_msg_set_text(&mut draft, arg1_c);
let mut draft = Message::new(Viewtype::Text);
draft.set_text(Some(arg1.to_string()));
chat::set_draft(
context,
sel_chat.as_ref().unwrap().get_id(),
@@ -944,11 +846,11 @@ pub unsafe fn dc_cmdline(context: &Context, line: &str) -> Result<(), failure::E
"msginfo" => {
ensure!(!arg1.is_empty(), "Argument <msg-id> missing.");
let id = arg1.parse()?;
let res = dc_get_msg_info(context, id);
println!("{}", as_str(res));
let res = message::get_msg_info(context, id);
println!("{}", res);
}
"listfresh" => {
let msglist = dc_get_fresh_msgs(context);
let msglist = context.get_fresh_msgs();
log_msglist(context, &msglist)?;
print!("{} fresh messages.", msglist.len());
@@ -962,30 +864,25 @@ pub unsafe fn dc_cmdline(context: &Context, line: &str) -> Result<(), failure::E
let mut msg_ids = [0; 1];
let chat_id = arg2.parse()?;
msg_ids[0] = arg1.parse()?;
chat::forward_msgs(context, msg_ids.as_mut_ptr(), 1, chat_id);
chat::forward_msgs(context, &msg_ids, chat_id);
}
"markseen" => {
ensure!(!arg1.is_empty(), "Argument <msg-id> missing.");
let mut msg_ids = [0; 1];
msg_ids[0] = arg1.parse()?;
dc_markseen_msgs(context, msg_ids.as_mut_ptr(), 1);
message::markseen_msgs(context, &msg_ids);
}
"star" | "unstar" => {
ensure!(!arg1.is_empty(), "Argument <msg-id> missing.");
let mut msg_ids = [0; 1];
msg_ids[0] = arg1.parse()?;
dc_star_msgs(
context,
msg_ids.as_mut_ptr(),
1,
if arg0 == "star" { 1 } else { 0 },
);
message::star_msgs(context, &msg_ids, arg0 == "star");
}
"delmsg" => {
ensure!(!arg1.is_empty(), "Argument <msg-id> missing.");
let mut ids = [0; 1];
ids[0] = arg1.parse()?;
dc_delete_msgs(context, ids.as_mut_ptr(), 1);
message::delete_msgs(context, &ids);
}
"listcontacts" | "contacts" | "listverified" => {
let contacts = Contact::get_all(
@@ -1054,20 +951,21 @@ 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.");
if let Some(buf) = dc_read_file_safe(context, &arg1) {
if let Ok(buf) = dc_read_file(context, &arg1) {
let (width, height) = dc_get_filemeta(&buf)?;
println!("width={}, height={}", width, height);
} else {
@@ -1079,7 +977,6 @@ pub unsafe fn dc_cmdline(context: &Context, line: &str) -> Result<(), failure::E
}
free(arg1_c as *mut _);
free(arg2_c as *mut _);
Ok(())
}

View File

@@ -14,20 +14,19 @@ 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::x::*;
use deltachat::securejoin::*;
use deltachat::Event;
use rustyline::completion::{Completer, FilenameCompleter, Pair};
use rustyline::config::OutputStreamType;
use rustyline::error::ReadlineError;
@@ -42,96 +41,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 +363,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.");
@@ -463,11 +439,6 @@ unsafe fn handle_cmd(line: &str, ctx: Arc<RwLock<Context>>) -> Result<ExitResult
let mut args = line.splitn(2, ' ');
let arg0 = args.next().unwrap_or_default();
let arg1 = args.next().unwrap_or_default();
let arg1_c = if arg1.is_empty() {
std::ptr::null()
} else {
arg1.strdup()
};
match arg0 {
"connect" => {
@@ -516,38 +487,33 @@ 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),
_ => dc_cmdline(&ctx.read().unwrap(), line)?,
}
free(arg1_c as *mut _);
Ok(ExitResult::Continue)
}

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,42 @@
# copied from http://koushiro.me/2019/04/30/Building-and-Testing-Rust-projects-on-CircleCI/
version: 2.1
jobs:
build:
docker:
- image: ubuntu:18.04
working_directory: ~/deltachat-core-rust
steps:
- checkout
- run:
name: Setup build environment
command: |
apt update
apt install -y curl build-essential autoconf libtool git python pkg-config
# this will pick default toolchain from `rust-toolchain` file
curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh -s -- --no-modify-path --default-toolchain none -y;
source $HOME/.cargo/env
no_output_timeout: 1800s
- run:
name: Format
command: |
export PATH=~/.cargo/bin:$PATH
rustup component add rustfmt
cargo fmt -- --check
- run:
name: Test
command: |
export PATH=~/.cargo/bin:$PATH
export RUST_BACKTRACE=1
cargo test
workflows:
version: 2.1
build:
jobs:
- build

23
mmime/Cargo.toml Normal file
View File

@@ -0,0 +1,23 @@
[package]
name = "mmime"
version = "0.1.2"
authors = ["dignifiedquire <dignifiedquire@users.noreply.github.com>"]
edition = "2018"
license = "MIT OR Apache-2.0"
homepage = "https://github.com/deltachat/deltachat-core-rust"
repository = "https://github.com/deltachat/deltachat-core-rust"
readme = "README.md"
description = "Mime parsing for email"
keywords = ["mail", "mim", "email", "imap", "smtp"]
categories = ["std", "email"]
[dependencies]
libc = "0.2.54"
charset = "0.1.2"
memmap = "0.7.0"
lazy_static = "1.3.0"
rand = "0.6.5"
chrono = "0.4.6"
hex = "0.3.2"

201
mmime/LICENSE-APACHE Normal file
View File

@@ -0,0 +1,201 @@
Apache License
Version 2.0, January 2004
http://www.apache.org/licenses/
TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
1. Definitions.
"License" shall mean the terms and conditions for use, reproduction,
and distribution as defined by Sections 1 through 9 of this document.
"Licensor" shall mean the copyright owner or entity authorized by
the copyright owner that is granting the License.
"Legal Entity" shall mean the union of the acting entity and all
other entities that control, are controlled by, or are under common
control with that entity. For the purposes of this definition,
"control" means (i) the power, direct or indirect, to cause the
direction or management of such entity, whether by contract or
otherwise, or (ii) ownership of fifty percent (50%) or more of the
outstanding shares, or (iii) beneficial ownership of such entity.
"You" (or "Your") shall mean an individual or Legal Entity
exercising permissions granted by this License.
"Source" form shall mean the preferred form for making modifications,
including but not limited to software source code, documentation
source, and configuration files.
"Object" form shall mean any form resulting from mechanical
transformation or translation of a Source form, including but
not limited to compiled object code, generated documentation,
and conversions to other media types.
"Work" shall mean the work of authorship, whether in Source or
Object form, made available under the License, as indicated by a
copyright notice that is included in or attached to the work
(an example is provided in the Appendix below).
"Derivative Works" shall mean any work, whether in Source or Object
form, that is based on (or derived from) the Work and for which the
editorial revisions, annotations, elaborations, or other modifications
represent, as a whole, an original work of authorship. For the purposes
of this License, Derivative Works shall not include works that remain
separable from, or merely link (or bind by name) to the interfaces of,
the Work and Derivative Works thereof.
"Contribution" shall mean any work of authorship, including
the original version of the Work and any modifications or additions
to that Work or Derivative Works thereof, that is intentionally
submitted to Licensor for inclusion in the Work by the copyright owner
or by an individual or Legal Entity authorized to submit on behalf of
the copyright owner. For the purposes of this definition, "submitted"
means any form of electronic, verbal, or written communication sent
to the Licensor or its representatives, including but not limited to
communication on electronic mailing lists, source code control systems,
and issue tracking systems that are managed by, or on behalf of, the
Licensor for the purpose of discussing and improving the Work, but
excluding communication that is conspicuously marked or otherwise
designated in writing by the copyright owner as "Not a Contribution."
"Contributor" shall mean Licensor and any individual or Legal Entity
on behalf of whom a Contribution has been received by Licensor and
subsequently incorporated within the Work.
2. Grant of Copyright License. Subject to the terms and conditions of
this License, each Contributor hereby grants to You a perpetual,
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
copyright license to reproduce, prepare Derivative Works of,
publicly display, publicly perform, sublicense, and distribute the
Work and such Derivative Works in Source or Object form.
3. Grant of Patent License. Subject to the terms and conditions of
this License, each Contributor hereby grants to You a perpetual,
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
(except as stated in this section) patent license to make, have made,
use, offer to sell, sell, import, and otherwise transfer the Work,
where such license applies only to those patent claims licensable
by such Contributor that are necessarily infringed by their
Contribution(s) alone or by combination of their Contribution(s)
with the Work to which such Contribution(s) was submitted. If You
institute patent litigation against any entity (including a
cross-claim or counterclaim in a lawsuit) alleging that the Work
or a Contribution incorporated within the Work constitutes direct
or contributory patent infringement, then any patent licenses
granted to You under this License for that Work shall terminate
as of the date such litigation is filed.
4. Redistribution. You may reproduce and distribute copies of the
Work or Derivative Works thereof in any medium, with or without
modifications, and in Source or Object form, provided that You
meet the following conditions:
(a) You must give any other recipients of the Work or
Derivative Works a copy of this License; and
(b) You must cause any modified files to carry prominent notices
stating that You changed the files; and
(c) You must retain, in the Source form of any Derivative Works
that You distribute, all copyright, patent, trademark, and
attribution notices from the Source form of the Work,
excluding those notices that do not pertain to any part of
the Derivative Works; and
(d) If the Work includes a "NOTICE" text file as part of its
distribution, then any Derivative Works that You distribute must
include a readable copy of the attribution notices contained
within such NOTICE file, excluding those notices that do not
pertain to any part of the Derivative Works, in at least one
of the following places: within a NOTICE text file distributed
as part of the Derivative Works; within the Source form or
documentation, if provided along with the Derivative Works; or,
within a display generated by the Derivative Works, if and
wherever such third-party notices normally appear. The contents
of the NOTICE file are for informational purposes only and
do not modify the License. You may add Your own attribution
notices within Derivative Works that You distribute, alongside
or as an addendum to the NOTICE text from the Work, provided
that such additional attribution notices cannot be construed
as modifying the License.
You may add Your own copyright statement to Your modifications and
may provide additional or different license terms and conditions
for use, reproduction, or distribution of Your modifications, or
for any such Derivative Works as a whole, provided Your use,
reproduction, and distribution of the Work otherwise complies with
the conditions stated in this License.
5. Submission of Contributions. Unless You explicitly state otherwise,
any Contribution intentionally submitted for inclusion in the Work
by You to the Licensor shall be under the terms and conditions of
this License, without any additional terms or conditions.
Notwithstanding the above, nothing herein shall supersede or modify
the terms of any separate license agreement you may have executed
with Licensor regarding such Contributions.
6. Trademarks. This License does not grant permission to use the trade
names, trademarks, service marks, or product names of the Licensor,
except as required for reasonable and customary use in describing the
origin of the Work and reproducing the content of the NOTICE file.
7. Disclaimer of Warranty. Unless required by applicable law or
agreed to in writing, Licensor provides the Work (and each
Contributor provides its Contributions) on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
implied, including, without limitation, any warranties or conditions
of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
PARTICULAR PURPOSE. You are solely responsible for determining the
appropriateness of using or redistributing the Work and assume any
risks associated with Your exercise of permissions under this License.
8. Limitation of Liability. In no event and under no legal theory,
whether in tort (including negligence), contract, or otherwise,
unless required by applicable law (such as deliberate and grossly
negligent acts) or agreed to in writing, shall any Contributor be
liable to You for damages, including any direct, indirect, special,
incidental, or consequential damages of any character arising as a
result of this License or out of the use or inability to use the
Work (including but not limited to damages for loss of goodwill,
work stoppage, computer failure or malfunction, or any and all
other commercial damages or losses), even if such Contributor
has been advised of the possibility of such damages.
9. Accepting Warranty or Additional Liability. While redistributing
the Work or Derivative Works thereof, You may choose to offer,
and charge a fee for, acceptance of support, warranty, indemnity,
or other liability obligations and/or rights consistent with this
License. However, in accepting such obligations, You may act only
on Your own behalf and on Your sole responsibility, not on behalf
of any other Contributor, and only if You agree to indemnify,
defend, and hold each Contributor harmless for any liability
incurred by, or claims asserted against, such Contributor by reason
of your accepting any such warranty or additional liability.
END OF TERMS AND CONDITIONS
APPENDIX: How to apply the Apache License to your work.
To apply the Apache License to your work, attach the following
boilerplate notice, with the fields enclosed by brackets "[]"
replaced with your own identifying information. (Don't include
the brackets!) The text should be enclosed in the appropriate
comment syntax for the file format. We also recommend that a
file or class name and description of purpose be included on the
same "printed page" as the copyright notice for easier
identification within third-party archives.
Copyright [yyyy] [name of copyright owner]
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.

23
mmime/LICENSE-MIT Normal file
View File

@@ -0,0 +1,23 @@
Permission is hereby granted, free of charge, to any
person obtaining a copy of this software and associated
documentation files (the "Software"), to deal in the
Software without restriction, including without
limitation the rights to use, copy, modify, merge,
publish, distribute, sublicense, and/or sell copies of
the Software, and to permit persons to whom the Software
is furnished to do so, subject to the following
conditions:
The above copyright notice and this permission notice
shall be included in all copies or substantial portions
of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF
ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED
TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A
PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT
SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR
IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
DEALINGS IN THE SOFTWARE.

4
mmime/LICENSE.md Normal file
View File

@@ -0,0 +1,4 @@
This library is primarly distributed under the terms of both the MIT license and
the Apache License (Version 2.0).
See LICENSE-MIT and LICENSE-APACHE for details.

16
mmime/README.md Normal file
View File

@@ -0,0 +1,16 @@
# mmime
[![CircleCI build status][circle-shield]][circle] [![Appveyor build status][appveyor-shield]][appveyor] [![License][license-shield]][license]
> mmmmmmime parsing
Base code was compiled using c2rust from libetpan.
[circle-shield]: https://img.shields.io/circleci/project/github/dignifiedquire/mmime/master.svg?style=flat-square
[circle]: https://circleci.com/gh/dignifiedquire/mmime/
[appveyor-shield]: https://ci.appveyor.com/api/projects/status/l26co5rba32knrlu/branch/master?style=flat-square
[appveyor]: https://ci.appveyor.com/project/dignifiedquire/mmime/branch/master
[license-shield]: https://img.shields.io/badge/License-MIT%2FApache2.0-green.svg?style=flat-square
[license]: https://github.com/rpgp/rpgp/blob/master/LICENSE.md

32
mmime/src/charconv.rs Normal file
View File

@@ -0,0 +1,32 @@
use crate::other::*;
use libc;
use std::ffi::{CStr, CString};
pub const MAIL_CHARCONV_ERROR_CONV: libc::c_uint = 3;
pub const MAIL_CHARCONV_ERROR_MEMORY: libc::c_uint = 2;
pub const MAIL_CHARCONV_ERROR_UNKNOWN_CHARSET: libc::c_uint = 1;
pub const MAIL_CHARCONV_NO_ERROR: libc::c_uint = 0;
pub unsafe fn charconv(
tocode: *const libc::c_char,
fromcode: *const libc::c_char,
s: *const libc::c_char,
length: size_t,
result: *mut *mut libc::c_char,
) -> libc::c_int {
assert!(!fromcode.is_null(), "invalid fromcode");
assert!(!s.is_null(), "invalid input string");
if let Some(encoding) =
charset::Charset::for_label(CStr::from_ptr(fromcode).to_str().unwrap().as_bytes())
{
let data = std::slice::from_raw_parts(s as *const u8, strlen(s));
let (res, _, _) = encoding.decode(data);
let res_c = CString::new(res.as_bytes()).unwrap();
*result = strdup(res_c.as_ptr()) as *mut _;
MAIL_CHARCONV_NO_ERROR as libc::c_int
} else {
MAIL_CHARCONV_ERROR_UNKNOWN_CHARSET as libc::c_int
}
}

427
mmime/src/chash.rs Normal file
View File

@@ -0,0 +1,427 @@
use libc;
use crate::other::*;
#[derive(Copy, Clone)]
#[repr(C)]
pub struct chashdatum {
pub data: *mut libc::c_void,
pub len: libc::c_uint,
}
#[derive(Copy, Clone)]
#[repr(C)]
pub struct chash {
pub size: libc::c_uint,
pub count: libc::c_uint,
pub copyvalue: libc::c_int,
pub copykey: libc::c_int,
pub cells: *mut *mut chashcell,
}
#[derive(Copy, Clone)]
#[repr(C)]
pub struct chashcell {
pub func: libc::c_uint,
pub key: chashdatum,
pub value: chashdatum,
pub next: *mut chashcell,
}
pub type chashiter = chashcell;
/* Allocates a new (empty) hash using this initial size and the given flags,
specifying which data should be copied in the hash.
CHASH_COPYNONE : Keys/Values are not copied.
CHASH_COPYKEY : Keys are dupped and freed as needed in the hash.
CHASH_COPYVALUE : Values are dupped and freed as needed in the hash.
CHASH_COPYALL : Both keys and values are dupped in the hash.
*/
pub unsafe fn chash_new(mut size: libc::c_uint, mut flags: libc::c_int) -> *mut chash {
let mut h: *mut chash = 0 as *mut chash;
h = malloc(::std::mem::size_of::<chash>() as libc::size_t) as *mut chash;
if h.is_null() {
return 0 as *mut chash;
}
if size < 13i32 as libc::c_uint {
size = 13i32 as libc::c_uint
}
(*h).count = 0i32 as libc::c_uint;
(*h).cells = calloc(
size as libc::size_t,
::std::mem::size_of::<*mut chashcell>() as libc::size_t,
) as *mut *mut chashcell;
if (*h).cells.is_null() {
free(h as *mut libc::c_void);
return 0 as *mut chash;
}
(*h).size = size;
(*h).copykey = flags & 1i32;
(*h).copyvalue = flags & 2i32;
return h;
}
/* Frees a hash */
pub unsafe fn chash_free(mut hash: *mut chash) {
let mut indx: libc::c_uint = 0;
let mut iter: *mut chashiter = 0 as *mut chashiter;
let mut next: *mut chashiter = 0 as *mut chashiter;
indx = 0i32 as libc::c_uint;
while indx < (*hash).size {
iter = *(*hash).cells.offset(indx as isize);
while !iter.is_null() {
next = (*iter).next;
if 0 != (*hash).copykey {
free((*iter).key.data);
}
if 0 != (*hash).copyvalue {
free((*iter).value.data);
}
free(iter as *mut libc::c_void);
iter = next
}
indx = indx.wrapping_add(1)
}
free((*hash).cells as *mut libc::c_void);
free(hash as *mut libc::c_void);
}
/* Removes all elements from a hash */
pub unsafe fn chash_clear(mut hash: *mut chash) {
let mut indx: libc::c_uint = 0;
let mut iter: *mut chashiter = 0 as *mut chashiter;
let mut next: *mut chashiter = 0 as *mut chashiter;
indx = 0i32 as libc::c_uint;
while indx < (*hash).size {
iter = *(*hash).cells.offset(indx as isize);
while !iter.is_null() {
next = (*iter).next;
if 0 != (*hash).copykey {
free((*iter).key.data);
}
if 0 != (*hash).copyvalue {
free((*iter).value.data);
}
free(iter as *mut libc::c_void);
iter = next
}
indx = indx.wrapping_add(1)
}
memset(
(*hash).cells as *mut libc::c_void,
0i32,
((*hash).size as libc::size_t)
.wrapping_mul(::std::mem::size_of::<*mut chashcell>() as libc::size_t),
);
(*hash).count = 0i32 as libc::c_uint;
}
/* Adds an entry in the hash table.
Length can be 0 if key/value are strings.
If an entry already exists for this key, it is replaced, and its value
is returned. Otherwise, the data pointer will be NULL and the length
field be set to TRUE or FALSe to indicate success or failure. */
pub unsafe fn chash_set(
mut hash: *mut chash,
mut key: *mut chashdatum,
mut value: *mut chashdatum,
mut oldvalue: *mut chashdatum,
) -> libc::c_int {
let mut current_block: u64;
let mut func: libc::c_uint = 0;
let mut indx: libc::c_uint = 0;
let mut iter: *mut chashiter = 0 as *mut chashiter;
let mut cell: *mut chashiter = 0 as *mut chashiter;
let mut r: libc::c_int = 0;
if (*hash).count > (*hash).size.wrapping_mul(3i32 as libc::c_uint) {
r = chash_resize(
hash,
(*hash)
.count
.wrapping_div(3i32 as libc::c_uint)
.wrapping_mul(2i32 as libc::c_uint)
.wrapping_add(1i32 as libc::c_uint),
);
if r < 0i32 {
current_block = 17701753836843438419;
} else {
current_block = 7095457783677275021;
}
} else {
current_block = 7095457783677275021;
}
match current_block {
7095457783677275021 => {
func = chash_func((*key).data as *const libc::c_char, (*key).len);
indx = func.wrapping_rem((*hash).size);
iter = *(*hash).cells.offset(indx as isize);
loop {
if iter.is_null() {
current_block = 17788412896529399552;
break;
}
if (*iter).key.len == (*key).len
&& (*iter).func == func
&& 0 == memcmp((*iter).key.data, (*key).data, (*key).len as libc::size_t)
{
/* found, replacing entry */
if 0 != (*hash).copyvalue {
let mut data: *mut libc::c_char = 0 as *mut libc::c_char;
data = chash_dup((*value).data, (*value).len);
if data.is_null() {
current_block = 17701753836843438419;
break;
}
free((*iter).value.data);
(*iter).value.data = data as *mut libc::c_void;
(*iter).value.len = (*value).len
} else {
if !oldvalue.is_null() {
(*oldvalue).data = (*iter).value.data;
(*oldvalue).len = (*iter).value.len
}
(*iter).value.data = (*value).data;
(*iter).value.len = (*value).len
}
if 0 == (*hash).copykey {
(*iter).key.data = (*key).data
}
if !oldvalue.is_null() {
(*oldvalue).data = (*value).data;
(*oldvalue).len = (*value).len
}
return 0i32;
} else {
iter = (*iter).next
}
}
match current_block {
17701753836843438419 => {}
_ => {
if !oldvalue.is_null() {
(*oldvalue).data = 0 as *mut libc::c_void;
(*oldvalue).len = 0i32 as libc::c_uint
}
cell = malloc(::std::mem::size_of::<chashcell>() as libc::size_t)
as *mut chashcell;
if !cell.is_null() {
if 0 != (*hash).copykey {
(*cell).key.data =
chash_dup((*key).data, (*key).len) as *mut libc::c_void;
if (*cell).key.data.is_null() {
current_block = 4267898785354516004;
} else {
current_block = 7226443171521532240;
}
} else {
(*cell).key.data = (*key).data;
current_block = 7226443171521532240;
}
match current_block {
7226443171521532240 => {
(*cell).key.len = (*key).len;
if 0 != (*hash).copyvalue {
(*cell).value.data =
chash_dup((*value).data, (*value).len) as *mut libc::c_void;
if (*cell).value.data.is_null() {
if 0 != (*hash).copykey {
free((*cell).key.data);
}
current_block = 4267898785354516004;
} else {
current_block = 6717214610478484138;
}
} else {
(*cell).value.data = (*value).data;
current_block = 6717214610478484138;
}
match current_block {
4267898785354516004 => {}
_ => {
(*cell).value.len = (*value).len;
(*cell).func = func;
(*cell).next = *(*hash).cells.offset(indx as isize);
let ref mut fresh0 = *(*hash).cells.offset(indx as isize);
*fresh0 = cell;
(*hash).count = (*hash).count.wrapping_add(1);
return 0i32;
}
}
}
_ => {}
}
free(cell as *mut libc::c_void);
}
}
}
}
_ => {}
}
return -1i32;
}
#[inline]
unsafe fn chash_dup(mut data: *const libc::c_void, mut len: libc::c_uint) -> *mut libc::c_char {
let mut r: *mut libc::c_void = 0 as *mut libc::c_void;
r = malloc(len as libc::size_t) as *mut libc::c_char as *mut libc::c_void;
if r.is_null() {
return 0 as *mut libc::c_char;
}
memcpy(r, data, len as libc::size_t);
return r as *mut libc::c_char;
}
#[inline]
unsafe fn chash_func(mut key: *const libc::c_char, mut len: libc::c_uint) -> libc::c_uint {
let mut c: libc::c_uint = 5381i32 as libc::c_uint;
let mut k: *const libc::c_char = key;
loop {
let fresh1 = len;
len = len.wrapping_sub(1);
if !(0 != fresh1) {
break;
}
let fresh2 = k;
k = k.offset(1);
c = (c << 5i32)
.wrapping_add(c)
.wrapping_add(*fresh2 as libc::c_uint)
}
return c;
}
/* Resizes the hash table to the passed size. */
pub unsafe fn chash_resize(mut hash: *mut chash, mut size: libc::c_uint) -> libc::c_int {
let mut cells: *mut *mut chashcell = 0 as *mut *mut chashcell;
let mut indx: libc::c_uint = 0;
let mut nindx: libc::c_uint = 0;
let mut iter: *mut chashiter = 0 as *mut chashiter;
let mut next: *mut chashiter = 0 as *mut chashiter;
if (*hash).size == size {
return 0i32;
}
cells = calloc(
size as libc::size_t,
::std::mem::size_of::<*mut chashcell>() as libc::size_t,
) as *mut *mut chashcell;
if cells.is_null() {
return -1i32;
}
indx = 0i32 as libc::c_uint;
while indx < (*hash).size {
iter = *(*hash).cells.offset(indx as isize);
while !iter.is_null() {
next = (*iter).next;
nindx = (*iter).func.wrapping_rem(size);
(*iter).next = *cells.offset(nindx as isize);
let ref mut fresh3 = *cells.offset(nindx as isize);
*fresh3 = iter;
iter = next
}
indx = indx.wrapping_add(1)
}
free((*hash).cells as *mut libc::c_void);
(*hash).size = size;
(*hash).cells = cells;
return 0i32;
}
/* Retrieves the data associated to the key if it is found in the hash table.
The data pointer and the length will be NULL if not found*/
pub unsafe fn chash_get(
mut hash: *mut chash,
mut key: *mut chashdatum,
mut result: *mut chashdatum,
) -> libc::c_int {
let mut func: libc::c_uint = 0;
let mut iter: *mut chashiter = 0 as *mut chashiter;
func = chash_func((*key).data as *const libc::c_char, (*key).len);
iter = *(*hash)
.cells
.offset(func.wrapping_rem((*hash).size) as isize);
while !iter.is_null() {
if (*iter).key.len == (*key).len
&& (*iter).func == func
&& 0 == memcmp((*iter).key.data, (*key).data, (*key).len as libc::size_t)
{
*result = (*iter).value;
return 0i32;
}
iter = (*iter).next
}
return -1i32;
}
/* Removes the entry associated to this key if it is found in the hash table,
and returns its contents if not dupped (otherwise, pointer will be NULL
and len TRUE). If entry is not found both pointer and len will be NULL. */
pub unsafe fn chash_delete(
mut hash: *mut chash,
mut key: *mut chashdatum,
mut oldvalue: *mut chashdatum,
) -> libc::c_int {
/* chashdatum result = { NULL, TRUE }; */
let mut func: libc::c_uint = 0;
let mut indx: libc::c_uint = 0;
let mut iter: *mut chashiter = 0 as *mut chashiter;
let mut old: *mut chashiter = 0 as *mut chashiter;
func = chash_func((*key).data as *const libc::c_char, (*key).len);
indx = func.wrapping_rem((*hash).size);
old = 0 as *mut chashiter;
iter = *(*hash).cells.offset(indx as isize);
while !iter.is_null() {
if (*iter).key.len == (*key).len
&& (*iter).func == func
&& 0 == memcmp((*iter).key.data, (*key).data, (*key).len as libc::size_t)
{
if !old.is_null() {
(*old).next = (*iter).next
} else {
let ref mut fresh4 = *(*hash).cells.offset(indx as isize);
*fresh4 = (*iter).next
}
if 0 != (*hash).copykey {
free((*iter).key.data);
}
if 0 != (*hash).copyvalue {
free((*iter).value.data);
} else if !oldvalue.is_null() {
(*oldvalue).data = (*iter).value.data;
(*oldvalue).len = (*iter).value.len
}
free(iter as *mut libc::c_void);
(*hash).count = (*hash).count.wrapping_sub(1);
return 0i32;
}
old = iter;
iter = (*iter).next
}
return -1i32;
}
/* Returns an iterator to the first non-empty entry of the hash table */
pub unsafe fn chash_begin(mut hash: *mut chash) -> *mut chashiter {
let mut iter: *mut chashiter = 0 as *mut chashiter;
let mut indx: libc::c_uint = 0i32 as libc::c_uint;
iter = *(*hash).cells.offset(0isize);
while iter.is_null() {
indx = indx.wrapping_add(1);
if indx >= (*hash).size {
return 0 as *mut chashiter;
}
iter = *(*hash).cells.offset(indx as isize)
}
return iter;
}
/* Returns the next non-empty entry of the hash table */
pub unsafe fn chash_next(mut hash: *mut chash, mut iter: *mut chashiter) -> *mut chashiter {
let mut indx: libc::c_uint = 0;
if iter.is_null() {
return 0 as *mut chashiter;
}
indx = (*iter).func.wrapping_rem((*hash).size);
iter = (*iter).next;
while iter.is_null() {
indx = indx.wrapping_add(1);
if indx >= (*hash).size {
return 0 as *mut chashiter;
}
iter = *(*hash).cells.offset(indx as isize)
}
return iter;
}

202
mmime/src/clist.rs Normal file
View File

@@ -0,0 +1,202 @@
use libc;
use crate::other::*;
#[derive(Copy, Clone)]
#[repr(C)]
pub struct clistcell {
pub data: *mut libc::c_void,
pub previous: *mut clistcell,
pub next: *mut clistcell,
}
#[derive(Clone)]
#[repr(C)]
pub struct clist {
pub first: *mut clistcell,
pub last: *mut clistcell,
pub count: libc::c_int,
}
impl Default for clist {
fn default() -> Self {
Self {
first: std::ptr::null_mut(),
last: std::ptr::null_mut(),
count: 0,
}
}
}
impl Drop for clist {
fn drop(&mut self) {
unsafe {
let mut l1 = self.first;
while !l1.is_null() {
let l2 = (*l1).next;
free(l1 as *mut libc::c_void);
l1 = l2
}
}
}
}
pub type clistiter = clistcell;
pub struct CListIterator {
cur: *mut clistiter,
}
impl Iterator for CListIterator {
type Item = *mut libc::c_void;
fn next(&mut self) -> Option<Self::Item> {
unsafe {
if self.cur.is_null() {
None
} else {
let data = (*self.cur).data;
self.cur = (*self.cur).next;
Some(data)
}
}
}
}
impl IntoIterator for &clist {
type Item = *mut libc::c_void;
type IntoIter = CListIterator;
fn into_iter(self) -> Self::IntoIter {
return CListIterator { cur: self.first };
}
}
pub type clist_func =
Option<unsafe extern "C" fn(_: *mut libc::c_void, _: *mut libc::c_void) -> ()>;
/* Allocate a new pointer list */
pub fn clist_new() -> *mut clist {
Box::into_raw(Box::new(Default::default()))
}
/* Destroys a list. Data pointed by data pointers is NOT freed. */
pub unsafe fn clist_free(mut lst: *mut clist) {
Box::from_raw(lst);
}
/* Inserts this data pointer after the element pointed by the iterator */
pub unsafe fn clist_insert_after(
mut lst: *mut clist,
mut iter: *mut clistiter,
mut data: *mut libc::c_void,
) -> libc::c_int {
let mut c: *mut clistcell = 0 as *mut clistcell;
c = malloc(::std::mem::size_of::<clistcell>() as libc::size_t) as *mut clistcell;
if c.is_null() {
return -1i32;
}
(*c).data = data;
(*lst).count += 1;
if (*lst).first == (*lst).last && (*lst).last.is_null() {
(*c).next = 0 as *mut clistcell;
(*c).previous = (*c).next;
(*lst).last = c;
(*lst).first = (*lst).last;
return 0i32;
}
if iter.is_null() {
(*c).previous = (*lst).last;
(*(*c).previous).next = c;
(*c).next = 0 as *mut clistcell;
(*lst).last = c;
return 0i32;
}
(*c).previous = iter;
(*c).next = (*iter).next;
if !(*c).next.is_null() {
(*(*c).next).previous = c
} else {
(*lst).last = c
}
(*(*c).previous).next = c;
return 0i32;
}
/* Deletes the element pointed by the iterator.
Returns an iterator to the next element. */
pub unsafe fn clist_delete(mut lst: *mut clist, mut iter: *mut clistiter) -> *mut clistiter {
let mut ret: *mut clistiter = 0 as *mut clistiter;
if iter.is_null() {
return 0 as *mut clistiter;
}
if !(*iter).previous.is_null() {
(*(*iter).previous).next = (*iter).next
} else {
(*lst).first = (*iter).next
}
if !(*iter).next.is_null() {
(*(*iter).next).previous = (*iter).previous;
ret = (*iter).next
} else {
(*lst).last = (*iter).previous;
ret = 0 as *mut clistiter
}
free(iter as *mut libc::c_void);
(*lst).count -= 1;
return ret;
}
pub unsafe fn clist_foreach(
mut lst: *mut clist,
mut func: clist_func,
mut data: *mut libc::c_void,
) {
let mut cur: *mut clistiter = 0 as *mut clistiter;
cur = (*lst).first;
while !cur.is_null() {
func.expect("non-null function pointer")((*cur).data, data);
cur = (*cur).next
}
}
pub unsafe fn clist_nth_data(mut lst: *mut clist, mut indx: libc::c_int) -> *mut libc::c_void {
let mut cur: *mut clistiter = 0 as *mut clistiter;
cur = internal_clist_nth(lst, indx);
if cur.is_null() {
return 0 as *mut libc::c_void;
}
return (*cur).data;
}
#[inline]
unsafe fn internal_clist_nth(mut lst: *mut clist, mut indx: libc::c_int) -> *mut clistiter {
let mut cur: *mut clistiter = 0 as *mut clistiter;
cur = (*lst).first;
while indx > 0i32 && !cur.is_null() {
cur = (*cur).next;
indx -= 1
}
if cur.is_null() {
return 0 as *mut clistiter;
}
return cur;
}
pub unsafe fn clist_nth(mut lst: *mut clist, mut indx: libc::c_int) -> *mut clistiter {
return internal_clist_nth(lst, indx);
}
#[cfg(test)]
mod tests {
use super::*;
use std::ptr;
#[test]
fn test_clist_iterator() {
unsafe {
let mut c = clist_new();
assert!(!c.is_null());
clist_insert_after(c, ptr::null_mut(), clist_nth as _);
assert_eq!((*c).count, 1);
/* Only one iteration */
for data in &*c {
assert_eq!(data, clist_nth as _);
}
assert_eq!((*c).count, 1);
clist_free(c);
}
}
}

71
mmime/src/constants.rs Normal file
View File

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

386
mmime/src/display.rs Normal file
View File

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

78
mmime/src/lib.rs Normal file
View File

@@ -0,0 +1,78 @@
#![deny(clippy::correctness)]
// TODO: make all of these errors, such that clippy actually passes.
#![warn(clippy::all, clippy::perf, clippy::not_unsafe_ptr_arg_deref)]
// This is nice, but for now just annoying.
#![allow(clippy::unreadable_literal)]
#![feature(ptr_wrapping_offset_from)]
#![allow(unused_attributes)]
#![allow(unused_variables)]
#![allow(mutable_transmutes)]
#![allow(non_camel_case_types)]
#![allow(non_snake_case)]
#![allow(non_upper_case_globals)]
#![allow(unused_assignments)]
#![allow(unused_mut)]
#![allow(unused_must_use)]
#![feature(extern_types)]
#![feature(const_raw_ptr_to_usize_cast)]
pub mod charconv;
pub mod chash;
pub mod clist;
pub mod display;
pub mod mailimf;
pub mod mailmime;
pub mod mmapstring;
pub mod other;
pub use self::charconv::*;
pub use self::chash::*;
pub use self::clist::*;
pub use self::display::*;
pub use self::mailimf::*;
pub use self::mailmime::*;
pub use self::mmapstring::*;
pub use self::other::*;
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn mailmime_parse_test() {
unsafe {
let data = "MIME-Version: 1.0\
Content-Type: multipart/mixed; boundary=frontier\
\
This is a message with multiple parts in MIME format.\
--frontier\
Content-Type: text/plain\
\
This is the body of the message.\
--frontier\
Content-Type: application/octet-stream\
Content-Transfer-Encoding: base64\
\
PGh0bWw+CiAgPGhlYWQ+CiAgPC9oZWFkPgogIDxib2R5PgogICAgPHA+VGhpcyBpcyB0aGUg\
Ym9keSBvZiB0aGUgbWVzc2FnZS48L3A+CiAgPC9ib2R5Pgo8L2h0bWw+Cg==\
--frontier--";
let c_data = std::ffi::CString::new(data).unwrap();
let mut current_index = 0;
let mut mime = std::ptr::null_mut();
let res = crate::mailmime::content::mailmime_parse(
c_data.as_ptr(),
data.len() as usize,
&mut current_index,
&mut mime,
);
assert_eq!(res, MAIL_NO_ERROR as libc::c_int);
assert!(!mime.is_null());
display_mime(mime);
mailmime::types::mailmime_free(mime);
}
}
}

5921
mmime/src/mailimf/mod.rs Normal file

File diff suppressed because it is too large Load Diff

1196
mmime/src/mailimf/types.rs Normal file

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,89 @@
use crate::clist::*;
use crate::mailimf::types::*;
use crate::other::*;
/*
this function creates a new mailimf_fields structure with no fields
*/
pub unsafe fn mailimf_fields_new_empty() -> *mut mailimf_fields {
let mut list: *mut clist = 0 as *mut clist;
let mut fields_list: *mut mailimf_fields = 0 as *mut mailimf_fields;
list = clist_new();
if list.is_null() {
return 0 as *mut mailimf_fields;
}
fields_list = mailimf_fields_new(list);
if fields_list.is_null() {
return 0 as *mut mailimf_fields;
}
return fields_list;
}
/*
this function adds a field to the mailimf_fields structure
@return MAILIMF_NO_ERROR will be returned on success,
other code will be returned otherwise
*/
pub unsafe fn mailimf_fields_add(
mut fields: *mut mailimf_fields,
mut field: *mut mailimf_field,
) -> libc::c_int {
let mut r: libc::c_int = 0;
r = clist_insert_after(
(*fields).fld_list,
(*(*fields).fld_list).last,
field as *mut libc::c_void,
);
if r < 0i32 {
return MAILIMF_ERROR_MEMORY as libc::c_int;
}
return MAILIMF_NO_ERROR as libc::c_int;
}
/*
mailimf_field_new_custom creates a new field of type optional
@param name should be allocated with malloc()
@param value should be allocated with malloc()
*/
pub unsafe fn mailimf_field_new_custom(
mut name: *mut libc::c_char,
mut value: *mut libc::c_char,
) -> *mut mailimf_field {
let mut opt_field: *mut mailimf_optional_field = 0 as *mut mailimf_optional_field;
let mut field: *mut mailimf_field = 0 as *mut mailimf_field;
opt_field = mailimf_optional_field_new(name, value);
if !opt_field.is_null() {
field = mailimf_field_new(
MAILIMF_FIELD_OPTIONAL_FIELD as libc::c_int,
0 as *mut mailimf_return,
0 as *mut mailimf_orig_date,
0 as *mut mailimf_from,
0 as *mut mailimf_sender,
0 as *mut mailimf_to,
0 as *mut mailimf_cc,
0 as *mut mailimf_bcc,
0 as *mut mailimf_message_id,
0 as *mut mailimf_orig_date,
0 as *mut mailimf_from,
0 as *mut mailimf_sender,
0 as *mut mailimf_reply_to,
0 as *mut mailimf_to,
0 as *mut mailimf_cc,
0 as *mut mailimf_bcc,
0 as *mut mailimf_message_id,
0 as *mut mailimf_in_reply_to,
0 as *mut mailimf_references,
0 as *mut mailimf_subject,
0 as *mut mailimf_comments,
0 as *mut mailimf_keywords,
opt_field,
);
if field.is_null() {
mailimf_optional_field_free(opt_field);
} else {
return field;
}
}
return 0 as *mut mailimf_field;
}

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,860 @@
use libc;
use libc::toupper;
use crate::charconv::*;
use crate::mailimf::*;
use crate::mailmime::content::*;
use crate::mailmime::types::*;
use crate::mmapstring::*;
use crate::other::*;
pub const TYPE_WORD: libc::c_uint = 1;
pub const TYPE_ENCODED_WORD: libc::c_uint = 2;
pub const MAILMIME_ENCODING_Q: libc::c_uint = 1;
pub const MAILMIME_ENCODING_B: libc::c_uint = 0;
pub const TYPE_ERROR: libc::c_uint = 0;
pub unsafe fn mailmime_encoded_phrase_parse(
mut default_fromcode: *const libc::c_char,
mut message: *const libc::c_char,
mut length: size_t,
mut indx: *mut size_t,
mut tocode: *const libc::c_char,
mut result: *mut *mut libc::c_char,
) -> libc::c_int {
let mut current_block: u64;
let mut gphrase: *mut MMAPString = 0 as *mut MMAPString;
let mut word: *mut mailmime_encoded_word = 0 as *mut mailmime_encoded_word;
let mut first: libc::c_int = 0;
let mut cur_token: size_t = 0;
let mut r: libc::c_int = 0;
let mut res: libc::c_int = 0;
let mut str: *mut libc::c_char = 0 as *mut libc::c_char;
let mut wordutf8: *mut libc::c_char = 0 as *mut libc::c_char;
let mut type_0: libc::c_int = 0;
let mut missing_closing_quote: libc::c_int = 0;
cur_token = *indx;
gphrase = mmap_string_new(b"\x00" as *const u8 as *const libc::c_char);
if gphrase.is_null() {
res = MAILIMF_ERROR_MEMORY as libc::c_int
} else {
first = 1i32;
type_0 = TYPE_ERROR as libc::c_int;
loop {
let mut has_fwd: libc::c_int = 0;
word = 0 as *mut mailmime_encoded_word;
r = mailmime_encoded_word_parse(
message,
length,
&mut cur_token,
&mut word,
&mut has_fwd,
&mut missing_closing_quote,
);
if r == MAILIMF_NO_ERROR as libc::c_int {
if 0 == first && 0 != has_fwd {
if type_0 != TYPE_ENCODED_WORD as libc::c_int {
if mmap_string_append_c(gphrase, ' ' as i32 as libc::c_char).is_null() {
mailmime_encoded_word_free(word);
res = MAILIMF_ERROR_MEMORY as libc::c_int;
current_block = 13246848547199022064;
break;
}
}
}
type_0 = TYPE_ENCODED_WORD as libc::c_int;
wordutf8 = 0 as *mut libc::c_char;
r = charconv(
tocode,
(*word).wd_charset,
(*word).wd_text,
strlen((*word).wd_text),
&mut wordutf8,
);
match r {
2 => {
mailmime_encoded_word_free(word);
res = MAILIMF_ERROR_MEMORY as libc::c_int;
current_block = 13246848547199022064;
break;
}
1 => {
r = charconv(
tocode,
b"iso-8859-1\x00" as *const u8 as *const libc::c_char,
(*word).wd_text,
strlen((*word).wd_text),
&mut wordutf8,
)
}
3 => {
mailmime_encoded_word_free(word);
res = MAILIMF_ERROR_PARSE as libc::c_int;
current_block = 13246848547199022064;
break;
}
_ => {}
}
match r {
2 => {
mailmime_encoded_word_free(word);
res = MAILIMF_ERROR_MEMORY as libc::c_int;
current_block = 13246848547199022064;
break;
}
3 => {
mailmime_encoded_word_free(word);
res = MAILIMF_ERROR_PARSE as libc::c_int;
current_block = 13246848547199022064;
break;
}
_ => {
if !wordutf8.is_null() {
if mmap_string_append(gphrase, wordutf8).is_null() {
mailmime_encoded_word_free(word);
free(wordutf8 as *mut libc::c_void);
res = MAILIMF_ERROR_MEMORY as libc::c_int;
current_block = 13246848547199022064;
break;
} else {
free(wordutf8 as *mut libc::c_void);
}
}
mailmime_encoded_word_free(word);
first = 0i32
}
}
} else if !(r == MAILIMF_ERROR_PARSE as libc::c_int) {
/* do nothing */
res = r;
current_block = 13246848547199022064;
break;
}
if !(r == MAILIMF_ERROR_PARSE as libc::c_int) {
continue;
}
let mut raw_word: *mut libc::c_char = 0 as *mut libc::c_char;
raw_word = 0 as *mut libc::c_char;
r = mailmime_non_encoded_word_parse(
message,
length,
&mut cur_token,
&mut raw_word,
&mut has_fwd,
);
if r == MAILIMF_NO_ERROR as libc::c_int {
if 0 == first && 0 != has_fwd {
if mmap_string_append_c(gphrase, ' ' as i32 as libc::c_char).is_null() {
free(raw_word as *mut libc::c_void);
res = MAILIMF_ERROR_MEMORY as libc::c_int;
current_block = 13246848547199022064;
break;
}
}
type_0 = TYPE_WORD as libc::c_int;
wordutf8 = 0 as *mut libc::c_char;
r = charconv(
tocode,
default_fromcode,
raw_word,
strlen(raw_word),
&mut wordutf8,
);
match r {
2 => {
free(raw_word as *mut libc::c_void);
res = MAILIMF_ERROR_MEMORY as libc::c_int;
current_block = 13246848547199022064;
break;
}
1 | 3 => {
free(raw_word as *mut libc::c_void);
res = MAILIMF_ERROR_PARSE as libc::c_int;
current_block = 13246848547199022064;
break;
}
_ => {
if mmap_string_append(gphrase, wordutf8).is_null() {
free(wordutf8 as *mut libc::c_void);
free(raw_word as *mut libc::c_void);
res = MAILIMF_ERROR_MEMORY as libc::c_int;
current_block = 13246848547199022064;
break;
} else {
free(wordutf8 as *mut libc::c_void);
free(raw_word as *mut libc::c_void);
first = 0i32
}
}
}
} else if r == MAILIMF_ERROR_PARSE as libc::c_int {
r = mailimf_fws_parse(message, length, &mut cur_token);
if r != MAILIMF_NO_ERROR as libc::c_int {
current_block = 5005389895767293342;
break;
}
if mmap_string_append_c(gphrase, ' ' as i32 as libc::c_char).is_null() {
res = MAILIMF_ERROR_MEMORY as libc::c_int;
current_block = 13246848547199022064;
break;
} else {
first = 0i32;
current_block = 5005389895767293342;
break;
}
} else {
res = r;
current_block = 13246848547199022064;
break;
}
}
match current_block {
5005389895767293342 => {
if 0 != first {
if cur_token != length {
res = MAILIMF_ERROR_PARSE as libc::c_int;
current_block = 13246848547199022064;
} else {
current_block = 7072655752890836508;
}
} else {
current_block = 7072655752890836508;
}
match current_block {
13246848547199022064 => {}
_ => {
str = strdup((*gphrase).str_0);
if str.is_null() {
res = MAILIMF_ERROR_MEMORY as libc::c_int
} else {
mmap_string_free(gphrase);
*result = str;
*indx = cur_token;
return MAILIMF_NO_ERROR as libc::c_int;
}
}
}
}
_ => {}
}
mmap_string_free(gphrase);
}
return res;
}
unsafe fn mailmime_non_encoded_word_parse(
mut message: *const libc::c_char,
mut length: size_t,
mut indx: *mut size_t,
mut result: *mut *mut libc::c_char,
mut p_has_fwd: *mut libc::c_int,
) -> libc::c_int {
let mut end: libc::c_int = 0;
let mut cur_token: size_t = 0;
let mut res: libc::c_int = 0;
let mut text: *mut libc::c_char = 0 as *mut libc::c_char;
let mut r: libc::c_int = 0;
let mut begin: size_t = 0;
let mut state: libc::c_int = 0;
let mut has_fwd: libc::c_int = 0;
cur_token = *indx;
has_fwd = 0i32;
r = mailimf_fws_parse(message, length, &mut cur_token);
if r == MAILIMF_NO_ERROR as libc::c_int {
has_fwd = 1i32
}
if r != MAILIMF_NO_ERROR as libc::c_int && r != MAILIMF_ERROR_PARSE as libc::c_int {
res = r
} else {
begin = cur_token;
state = 0i32;
end = 0i32;
while !(cur_token >= length) {
let mut current_block_17: u64;
match *message.offset(cur_token as isize) as libc::c_int {
32 | 9 | 13 | 10 => {
state = 0i32;
end = 1i32;
current_block_17 = 16924917904204750491;
}
61 => {
state = 1i32;
current_block_17 = 16924917904204750491;
}
63 => {
if state == 1i32 {
cur_token = cur_token.wrapping_sub(1);
end = 1i32
}
current_block_17 = 10192508258555769664;
}
_ => {
current_block_17 = 10192508258555769664;
}
}
match current_block_17 {
10192508258555769664 => state = 0i32,
_ => {}
}
if 0 != end {
break;
}
cur_token = cur_token.wrapping_add(1)
}
if cur_token.wrapping_sub(begin) == 0i32 as libc::size_t {
res = MAILIMF_ERROR_PARSE as libc::c_int
} else {
text = malloc(
cur_token
.wrapping_sub(begin)
.wrapping_add(1i32 as libc::size_t),
) as *mut libc::c_char;
if text.is_null() {
res = MAILIMF_ERROR_MEMORY as libc::c_int
} else {
memcpy(
text as *mut libc::c_void,
message.offset(begin as isize) as *const libc::c_void,
cur_token.wrapping_sub(begin),
);
*text.offset(cur_token.wrapping_sub(begin) as isize) =
'\u{0}' as i32 as libc::c_char;
*indx = cur_token;
*result = text;
*p_has_fwd = has_fwd;
return MAILIMF_NO_ERROR as libc::c_int;
}
}
}
return res;
}
pub unsafe fn mailmime_encoded_word_parse(
mut message: *const libc::c_char,
mut length: size_t,
mut indx: *mut size_t,
mut result: *mut *mut mailmime_encoded_word,
mut p_has_fwd: *mut libc::c_int,
mut p_missing_closing_quote: *mut libc::c_int,
) -> libc::c_int {
let mut current_block: u64;
/*
Parse the following, when a unicode character encoding is split.
=?UTF-8?B?4Lij4Liw4LmA4Lia4Li04LiU4LiE4Lin4Liy4Lih4Lih4Lix4LiZ4Liq4LmM?=
=?UTF-8?B?4LmA4LiV4LmH4Lih4Lie4Li04LiB4Lix4LiUIFRSQU5TRk9STUVSUyA0IOC4?=
=?UTF-8?B?oeC4seC4meC4quC5jOC4hOC4o+C4muC4l+C4uOC4geC4o+C4sOC4muC4miDg?=
=?UTF-8?B?uJfguLXguYjguYDguJTguLXguKLguKfguYPguJnguYDguKHguLfguK3guIfg?=
=?UTF-8?B?uYTguJfguKI=?=
Expected result:
ระเบิดความมันส์เต็มพิกัด TRANSFORMERS 4 มันส์ครบทุกระบบ ที่เดียวในเมืองไทย
libetpan result:
ระเบิดความมันส์เต็มพิกัด TRANSFORMERS 4 ?ันส์ครบทุกระบบ ??ี่เดียวในเมือง??ทย
See https://github.com/dinhviethoa/libetpan/pull/211
*/
let mut cur_token: size_t = 0;
let mut charset: *mut libc::c_char = 0 as *mut libc::c_char;
let mut encoding: libc::c_int = 0;
let mut body: *mut libc::c_char = 0 as *mut libc::c_char;
let mut old_body_len: size_t = 0;
let mut text: *mut libc::c_char = 0 as *mut libc::c_char;
let mut end_encoding: size_t = 0;
let mut lookfwd_cur_token: size_t = 0;
let mut lookfwd_charset: *mut libc::c_char = 0 as *mut libc::c_char;
let mut lookfwd_encoding: libc::c_int = 0;
let mut copy_len: size_t = 0;
let mut decoded_token: size_t = 0;
let mut decoded: *mut libc::c_char = 0 as *mut libc::c_char;
let mut decoded_len: size_t = 0;
let mut ew: *mut mailmime_encoded_word = 0 as *mut mailmime_encoded_word;
let mut r: libc::c_int = 0;
let mut res: libc::c_int = 0;
let mut opening_quote: libc::c_int = 0;
let mut end: libc::c_int = 0;
let mut has_fwd: libc::c_int = 0;
let mut missing_closing_quote: libc::c_int = 0;
cur_token = *indx;
text = 0 as *mut libc::c_char;
lookfwd_charset = 0 as *mut libc::c_char;
missing_closing_quote = 0i32;
has_fwd = 0i32;
r = mailimf_fws_parse(message, length, &mut cur_token);
if r == MAILIMF_NO_ERROR as libc::c_int {
has_fwd = 1i32
}
if r != MAILIMF_NO_ERROR as libc::c_int && r != MAILIMF_ERROR_PARSE as libc::c_int {
res = r
} else {
opening_quote = 0i32;
r = mailimf_char_parse(message, length, &mut cur_token, '\"' as i32 as libc::c_char);
if r == MAILIMF_NO_ERROR as libc::c_int {
opening_quote = 1i32;
current_block = 17788412896529399552;
} else if r == MAILIMF_ERROR_PARSE as libc::c_int {
current_block = 17788412896529399552;
} else {
/* do nothing */
res = r;
current_block = 7995813543095296079;
}
match current_block {
7995813543095296079 => {}
_ => {
r = mailimf_token_case_insensitive_len_parse(
message,
length,
&mut cur_token,
b"=?\x00" as *const u8 as *const libc::c_char as *mut libc::c_char,
strlen(b"=?\x00" as *const u8 as *const libc::c_char),
);
if r != MAILIMF_NO_ERROR as libc::c_int {
res = r
} else {
r = mailmime_charset_parse(message, length, &mut cur_token, &mut charset);
if r != MAILIMF_NO_ERROR as libc::c_int {
res = r
} else {
r = mailimf_char_parse(
message,
length,
&mut cur_token,
'?' as i32 as libc::c_char,
);
if r != MAILIMF_NO_ERROR as libc::c_int {
res = r
} else {
r = mailmime_encoding_parse(
message,
length,
&mut cur_token,
&mut encoding,
);
if r != MAILIMF_NO_ERROR as libc::c_int {
res = r
} else {
r = mailimf_char_parse(
message,
length,
&mut cur_token,
'?' as i32 as libc::c_char,
);
if r != MAILIMF_NO_ERROR as libc::c_int {
res = r
} else {
lookfwd_cur_token = cur_token;
body = 0 as *mut libc::c_char;
old_body_len = 0i32 as size_t;
loop {
let mut has_base64_padding: libc::c_int = 0;
end = 0i32;
has_base64_padding = 0i32;
end_encoding = cur_token;
while !(end_encoding >= length) {
if end_encoding.wrapping_add(1i32 as libc::size_t)
< length
{
if *message.offset(end_encoding as isize)
as libc::c_int
== '?' as i32
&& *message.offset(
end_encoding
.wrapping_add(1i32 as libc::size_t)
as isize,
)
as libc::c_int
== '=' as i32
{
end = 1i32
}
}
if 0 != end {
break;
}
end_encoding = end_encoding.wrapping_add(1)
}
copy_len = end_encoding.wrapping_sub(lookfwd_cur_token);
if copy_len > 0i32 as libc::size_t {
if encoding == MAILMIME_ENCODING_B as libc::c_int {
if end_encoding >= 1i32 as libc::size_t {
if *message.offset(
end_encoding
.wrapping_sub(1i32 as libc::size_t)
as isize,
)
as libc::c_int
== '=' as i32
{
has_base64_padding = 1i32
}
}
}
body = realloc(
body as *mut libc::c_void,
old_body_len
.wrapping_add(copy_len)
.wrapping_add(1i32 as libc::size_t),
)
as *mut libc::c_char;
if body.is_null() {
res = MAILIMF_ERROR_MEMORY as libc::c_int;
current_block = 13900684162107791171;
break;
} else {
memcpy(
body.offset(old_body_len as isize)
as *mut libc::c_void,
&*message.offset(cur_token as isize)
as *const libc::c_char
as *const libc::c_void,
copy_len,
);
*body
.offset(old_body_len.wrapping_add(copy_len)
as isize) = '\u{0}' as i32 as libc::c_char;
old_body_len = (old_body_len as libc::size_t)
.wrapping_add(copy_len)
as size_t
as size_t
}
}
cur_token = end_encoding;
r = mailimf_token_case_insensitive_len_parse(
message,
length,
&mut cur_token,
b"?=\x00" as *const u8 as *const libc::c_char
as *mut libc::c_char,
strlen(b"?=\x00" as *const u8 as *const libc::c_char),
);
if r != MAILIMF_NO_ERROR as libc::c_int {
current_block = 2652804691515851435;
break;
}
if 0 != has_base64_padding {
current_block = 2652804691515851435;
break;
}
lookfwd_cur_token = cur_token;
r = mailimf_fws_parse(
message,
length,
&mut lookfwd_cur_token,
);
if r != MAILIMF_NO_ERROR as libc::c_int
&& r != MAILIMF_ERROR_PARSE as libc::c_int
{
current_block = 2652804691515851435;
break;
}
r = mailimf_token_case_insensitive_len_parse(
message,
length,
&mut lookfwd_cur_token,
b"=?\x00" as *const u8 as *const libc::c_char
as *mut libc::c_char,
strlen(b"=?\x00" as *const u8 as *const libc::c_char),
);
if r != MAILIMF_NO_ERROR as libc::c_int {
current_block = 2652804691515851435;
break;
}
r = mailmime_charset_parse(
message,
length,
&mut lookfwd_cur_token,
&mut lookfwd_charset,
);
if r != MAILIMF_NO_ERROR as libc::c_int {
current_block = 2652804691515851435;
break;
}
r = mailimf_char_parse(
message,
length,
&mut lookfwd_cur_token,
'?' as i32 as libc::c_char,
);
if r != MAILIMF_NO_ERROR as libc::c_int {
current_block = 2652804691515851435;
break;
}
r = mailmime_encoding_parse(
message,
length,
&mut lookfwd_cur_token,
&mut lookfwd_encoding,
);
if r != MAILIMF_NO_ERROR as libc::c_int {
current_block = 2652804691515851435;
break;
}
r = mailimf_char_parse(
message,
length,
&mut lookfwd_cur_token,
'?' as i32 as libc::c_char,
);
if r != MAILIMF_NO_ERROR as libc::c_int {
current_block = 2652804691515851435;
break;
}
if strcasecmp(charset, lookfwd_charset) == 0i32
&& encoding == lookfwd_encoding
{
cur_token = lookfwd_cur_token;
mailmime_charset_free(lookfwd_charset);
lookfwd_charset = 0 as *mut libc::c_char
} else {
/* the next charset is not matched with the current one,
therefore exit the loop to decode the body appended so far */
current_block = 2652804691515851435;
break;
}
}
match current_block {
2652804691515851435 => {
if !lookfwd_charset.is_null() {
mailmime_charset_free(lookfwd_charset);
lookfwd_charset = 0 as *mut libc::c_char
}
if body.is_null() {
body = strdup(
b"\x00" as *const u8 as *const libc::c_char,
);
if body.is_null() {
res = MAILIMF_ERROR_MEMORY as libc::c_int;
current_block = 13900684162107791171;
} else {
current_block = 16778110326724371720;
}
} else {
current_block = 16778110326724371720;
}
match current_block {
13900684162107791171 => {}
_ => {
decoded_token = 0i32 as size_t;
decoded_len = 0i32 as size_t;
decoded = 0 as *mut libc::c_char;
match encoding {
0 => {
r = mailmime_base64_body_parse(
body,
strlen(body),
&mut decoded_token,
&mut decoded,
&mut decoded_len,
);
if r != MAILIMF_NO_ERROR as libc::c_int
{
res = r;
current_block =
13900684162107791171;
} else {
current_block = 7337917895049117968;
}
}
1 => {
r =
mailmime_quoted_printable_body_parse(body,
strlen(body),
&mut decoded_token,
&mut decoded,
&mut decoded_len,
1i32);
if r != MAILIMF_NO_ERROR as libc::c_int
{
res = r;
current_block =
13900684162107791171;
} else {
current_block = 7337917895049117968;
}
}
_ => {
current_block = 7337917895049117968;
}
}
match current_block {
13900684162107791171 => {}
_ => {
text =
malloc(decoded_len.wrapping_add(
1i32 as libc::size_t,
))
as *mut libc::c_char;
if text.is_null() {
res = MAILIMF_ERROR_MEMORY
as libc::c_int
} else {
if decoded_len
> 0i32 as libc::size_t
{
memcpy(
text as *mut libc::c_void,
decoded
as *const libc::c_void,
decoded_len,
);
}
*text
.offset(decoded_len as isize) =
'\u{0}' as i32 as libc::c_char;
if 0 != opening_quote {
r = mailimf_char_parse(
message,
length,
&mut cur_token,
'\"' as i32 as libc::c_char,
);
if r == MAILIMF_ERROR_PARSE
as libc::c_int
{
missing_closing_quote = 1i32
}
}
if strcasecmp(
charset,
b"utf8\x00" as *const u8
as *const libc::c_char,
) == 0i32
{
free(
charset
as *mut libc::c_void,
);
charset = strdup(
b"utf-8\x00" as *const u8
as *const libc::c_char,
)
}
ew = mailmime_encoded_word_new(
charset, text,
);
if ew.is_null() {
res = MAILIMF_ERROR_MEMORY
as libc::c_int
} else {
*result = ew;
*indx = cur_token;
*p_has_fwd = has_fwd;
*p_missing_closing_quote =
missing_closing_quote;
mailmime_decoded_part_free(
decoded,
);
free(body as *mut libc::c_void);
return MAILIMF_NO_ERROR
as libc::c_int;
}
}
mailmime_decoded_part_free(decoded);
}
}
}
}
}
_ => {}
}
free(body as *mut libc::c_void);
mailmime_encoded_text_free(text);
}
}
}
mailmime_charset_free(charset);
}
}
}
}
}
return res;
}
unsafe fn mailmime_encoding_parse(
mut message: *const libc::c_char,
mut length: size_t,
mut indx: *mut size_t,
mut result: *mut libc::c_int,
) -> libc::c_int {
let mut cur_token: size_t = 0;
let mut encoding: libc::c_int = 0;
cur_token = *indx;
if cur_token >= length {
return MAILIMF_ERROR_PARSE as libc::c_int;
}
match toupper(*message.offset(cur_token as isize) as libc::c_uchar as libc::c_int)
as libc::c_char as libc::c_int
{
81 => encoding = MAILMIME_ENCODING_Q as libc::c_int,
66 => encoding = MAILMIME_ENCODING_B as libc::c_int,
_ => return MAILIMF_ERROR_INVAL as libc::c_int,
}
cur_token = cur_token.wrapping_add(1);
*result = encoding;
*indx = cur_token;
return MAILIMF_NO_ERROR as libc::c_int;
}
/*
* libEtPan! -- a mail stuff library
*
* Copyright (C) 2001, 2005 - DINH Viet Hoa
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions
* are met:
* 1. Redistributions of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer.
* 2. Redistributions in binary form must reproduce the above copyright
* notice, this list of conditions and the following disclaimer in the
* documentation and/or other materials provided with the distribution.
* 3. Neither the name of the libEtPan! project nor the names of its
* contributors may be used to endorse or promote products derived
* from this software without specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE AUTHORS AND CONTRIBUTORS ``AS IS'' AND
* ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
* ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHORS OR CONTRIBUTORS BE LIABLE
* FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
* DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
* OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
* HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
* LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
* OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
* SUCH DAMAGE.
*/
/*
* $Id: mailmime_decode.c,v 1.37 2010/11/16 20:52:28 hoa Exp $
*/
/*
RFC 2047 : MIME (Multipurpose Internet Mail Extensions) Part Three:
Message Header Extensions for Non-ASCII Text
*/
unsafe fn mailmime_charset_parse(
mut message: *const libc::c_char,
mut length: size_t,
mut indx: *mut size_t,
mut charset: *mut *mut libc::c_char,
) -> libc::c_int {
return mailmime_etoken_parse(message, length, indx, charset);
}
unsafe fn mailmime_etoken_parse(
mut message: *const libc::c_char,
mut length: size_t,
mut indx: *mut size_t,
mut result: *mut *mut libc::c_char,
) -> libc::c_int {
return mailimf_custom_string_parse(message, length, indx, result, Some(is_etoken_char));
}
unsafe fn is_etoken_char(mut ch: libc::c_char) -> libc::c_int {
let mut uch: libc::c_uchar = ch as libc::c_uchar;
if (uch as libc::c_int) < 31i32 {
return 0i32;
}
match uch as libc::c_int {
32 | 40 | 41 | 60 | 62 | 64 | 44 | 59 | 58 | 34 | 47 | 91 | 93 | 63 | 61 => return 0i32,
_ => {}
}
return 1i32;
}

View File

@@ -0,0 +1,583 @@
use libc::{self, toupper};
use crate::clist::*;
use crate::mailimf::*;
use crate::mailmime::types::*;
use crate::mailmime::*;
use crate::other::*;
pub const MAILMIME_DISPOSITION_TYPE_EXTENSION: libc::c_uint = 3;
pub const MAILMIME_DISPOSITION_TYPE_ATTACHMENT: libc::c_uint = 2;
pub const MAILMIME_DISPOSITION_TYPE_INLINE: libc::c_uint = 1;
pub const MAILMIME_DISPOSITION_TYPE_ERROR: libc::c_uint = 0;
pub unsafe fn mailmime_disposition_parse(
mut message: *const libc::c_char,
mut length: size_t,
mut indx: *mut size_t,
mut result: *mut *mut mailmime_disposition,
) -> libc::c_int {
let mut current_block: u64;
let mut final_token: size_t = 0;
let mut cur_token: size_t = 0;
let mut dsp_type: *mut mailmime_disposition_type = 0 as *mut mailmime_disposition_type;
let mut list: *mut clist = 0 as *mut clist;
let mut dsp: *mut mailmime_disposition = 0 as *mut mailmime_disposition;
let mut r: libc::c_int = 0;
let mut res: libc::c_int = 0;
cur_token = *indx;
r = mailmime_disposition_type_parse(message, length, &mut cur_token, &mut dsp_type);
if r != MAILIMF_NO_ERROR as libc::c_int {
res = r
} else {
list = clist_new();
if list.is_null() {
res = MAILIMF_ERROR_MEMORY as libc::c_int
} else {
loop {
let mut param: *mut mailmime_disposition_parm = 0 as *mut mailmime_disposition_parm;
final_token = cur_token;
r = mailimf_unstrict_char_parse(
message,
length,
&mut cur_token,
';' as i32 as libc::c_char,
);
if r == MAILIMF_NO_ERROR as libc::c_int {
param = 0 as *mut mailmime_disposition_parm;
r = mailmime_disposition_parm_parse(
message,
length,
&mut cur_token,
&mut param,
);
if r == MAILIMF_NO_ERROR as libc::c_int {
r = clist_insert_after(list, (*list).last, param as *mut libc::c_void);
if !(r < 0i32) {
continue;
}
res = MAILIMF_ERROR_MEMORY as libc::c_int;
current_block = 18290070879695007868;
break;
} else if r == MAILIMF_ERROR_PARSE as libc::c_int {
cur_token = final_token;
current_block = 652864300344834934;
break;
} else {
res = r;
current_block = 18290070879695007868;
break;
}
} else {
/* do nothing */
if r == MAILIMF_ERROR_PARSE as libc::c_int {
current_block = 652864300344834934;
break;
}
res = r;
current_block = 18290070879695007868;
break;
}
}
match current_block {
652864300344834934 => {
dsp = mailmime_disposition_new(dsp_type, list);
if dsp.is_null() {
res = MAILIMF_ERROR_MEMORY as libc::c_int
} else {
*result = dsp;
*indx = cur_token;
return MAILIMF_NO_ERROR as libc::c_int;
}
}
_ => {}
}
clist_foreach(
list,
::std::mem::transmute::<
Option<unsafe fn(_: *mut mailmime_disposition_parm) -> ()>,
clist_func,
>(Some(mailmime_disposition_parm_free)),
0 as *mut libc::c_void,
);
clist_free(list);
}
mailmime_disposition_type_free(dsp_type);
}
return res;
}
/*
* libEtPan! -- a mail stuff library
*
* Copyright (C) 2001, 2005 - DINH Viet Hoa
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions
* are met:
* 1. Redistributions of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer.
* 2. Redistributions in binary form must reproduce the above copyright
* notice, this list of conditions and the following disclaimer in the
* documentation and/or other materials provided with the distribution.
* 3. Neither the name of the libEtPan! project nor the names of its
* contributors may be used to endorse or promote products derived
* from this software without specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE AUTHORS AND CONTRIBUTORS ``AS IS'' AND
* ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
* ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHORS OR CONTRIBUTORS BE LIABLE
* FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
* DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
* OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
* HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
* LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
* OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
* SUCH DAMAGE.
*/
/*
* $Id: mailmime_disposition.c,v 1.17 2011/05/03 16:30:22 hoa Exp $
*/
unsafe fn mailmime_disposition_parm_parse(
mut message: *const libc::c_char,
mut length: size_t,
mut indx: *mut size_t,
mut result: *mut *mut mailmime_disposition_parm,
) -> libc::c_int {
let mut current_block: u64;
let mut filename: *mut libc::c_char = 0 as *mut libc::c_char;
let mut creation_date: *mut libc::c_char = 0 as *mut libc::c_char;
let mut modification_date: *mut libc::c_char = 0 as *mut libc::c_char;
let mut read_date: *mut libc::c_char = 0 as *mut libc::c_char;
let mut size: size_t = 0;
let mut parameter: *mut mailmime_parameter = 0 as *mut mailmime_parameter;
let mut cur_token: size_t = 0;
let mut dsp_parm: *mut mailmime_disposition_parm = 0 as *mut mailmime_disposition_parm;
let mut type_0: libc::c_int = 0;
let mut guessed_type: libc::c_int = 0;
let mut r: libc::c_int = 0;
let mut res: libc::c_int = 0;
cur_token = *indx;
filename = 0 as *mut libc::c_char;
creation_date = 0 as *mut libc::c_char;
modification_date = 0 as *mut libc::c_char;
read_date = 0 as *mut libc::c_char;
size = 0i32 as size_t;
parameter = 0 as *mut mailmime_parameter;
r = mailimf_cfws_parse(message, length, &mut cur_token);
if r != MAILIMF_NO_ERROR as libc::c_int && r != MAILIMF_ERROR_PARSE as libc::c_int {
res = r
} else {
guessed_type = mailmime_disposition_guess_type(message, length, cur_token);
type_0 = MAILMIME_DISPOSITION_PARM_PARAMETER as libc::c_int;
match guessed_type {
0 => {
r = mailmime_filename_parm_parse(message, length, &mut cur_token, &mut filename);
if r == MAILIMF_NO_ERROR as libc::c_int {
type_0 = guessed_type;
current_block = 13826291924415791078;
} else if r == MAILIMF_ERROR_PARSE as libc::c_int {
current_block = 13826291924415791078;
} else {
/* do nothing */
res = r;
current_block = 9120900589700563584;
}
}
1 => {
r = mailmime_creation_date_parm_parse(
message,
length,
&mut cur_token,
&mut creation_date,
);
if r == MAILIMF_NO_ERROR as libc::c_int {
type_0 = guessed_type;
current_block = 13826291924415791078;
} else if r == MAILIMF_ERROR_PARSE as libc::c_int {
current_block = 13826291924415791078;
} else {
/* do nothing */
res = r;
current_block = 9120900589700563584;
}
}
2 => {
r = mailmime_modification_date_parm_parse(
message,
length,
&mut cur_token,
&mut modification_date,
);
if r == MAILIMF_NO_ERROR as libc::c_int {
type_0 = guessed_type;
current_block = 13826291924415791078;
} else if r == MAILIMF_ERROR_PARSE as libc::c_int {
current_block = 13826291924415791078;
} else {
/* do nothing */
res = r;
current_block = 9120900589700563584;
}
}
3 => {
r = mailmime_read_date_parm_parse(message, length, &mut cur_token, &mut read_date);
if r == MAILIMF_NO_ERROR as libc::c_int {
type_0 = guessed_type;
current_block = 13826291924415791078;
} else if r == MAILIMF_ERROR_PARSE as libc::c_int {
current_block = 13826291924415791078;
} else {
/* do nothing */
res = r;
current_block = 9120900589700563584;
}
}
4 => {
r = mailmime_size_parm_parse(message, length, &mut cur_token, &mut size);
if r == MAILIMF_NO_ERROR as libc::c_int {
type_0 = guessed_type;
current_block = 13826291924415791078;
} else if r == MAILIMF_ERROR_PARSE as libc::c_int {
current_block = 13826291924415791078;
} else {
/* do nothing */
res = r;
current_block = 9120900589700563584;
}
}
_ => {
current_block = 13826291924415791078;
}
}
match current_block {
9120900589700563584 => {}
_ => {
if type_0 == MAILMIME_DISPOSITION_PARM_PARAMETER as libc::c_int {
r = mailmime_parameter_parse(message, length, &mut cur_token, &mut parameter);
if r != MAILIMF_NO_ERROR as libc::c_int {
type_0 = guessed_type;
res = r;
current_block = 9120900589700563584;
} else {
current_block = 6721012065216013753;
}
} else {
current_block = 6721012065216013753;
}
match current_block {
9120900589700563584 => {}
_ => {
dsp_parm = mailmime_disposition_parm_new(
type_0,
filename,
creation_date,
modification_date,
read_date,
size,
parameter,
);
if dsp_parm.is_null() {
res = MAILIMF_ERROR_MEMORY as libc::c_int;
if !filename.is_null() {
mailmime_filename_parm_free(filename);
}
if !creation_date.is_null() {
mailmime_creation_date_parm_free(creation_date);
}
if !modification_date.is_null() {
mailmime_modification_date_parm_free(modification_date);
}
if !read_date.is_null() {
mailmime_read_date_parm_free(read_date);
}
if !parameter.is_null() {
mailmime_parameter_free(parameter);
}
} else {
*result = dsp_parm;
*indx = cur_token;
return MAILIMF_NO_ERROR as libc::c_int;
}
}
}
}
}
}
return res;
}
unsafe fn mailmime_size_parm_parse(
mut message: *const libc::c_char,
mut length: size_t,
mut indx: *mut size_t,
mut result: *mut size_t,
) -> libc::c_int {
let mut value: uint32_t = 0;
let mut cur_token: size_t = 0;
let mut r: libc::c_int = 0;
cur_token = *indx;
r = mailimf_token_case_insensitive_len_parse(
message,
length,
&mut cur_token,
b"size\x00" as *const u8 as *const libc::c_char as *mut libc::c_char,
strlen(b"size\x00" as *const u8 as *const libc::c_char),
);
if r != MAILIMF_NO_ERROR as libc::c_int {
return r;
}
r = mailimf_unstrict_char_parse(message, length, &mut cur_token, '=' as i32 as libc::c_char);
if r != MAILIMF_NO_ERROR as libc::c_int {
return r;
}
r = mailimf_cfws_parse(message, length, &mut cur_token);
if r != MAILIMF_NO_ERROR as libc::c_int && r != MAILIMF_ERROR_PARSE as libc::c_int {
return r;
}
r = mailimf_number_parse(message, length, &mut cur_token, &mut value);
if r != MAILIMF_NO_ERROR as libc::c_int {
return r;
}
*indx = cur_token;
*result = value as size_t;
return MAILIMF_NO_ERROR as libc::c_int;
}
unsafe fn mailmime_read_date_parm_parse(
mut message: *const libc::c_char,
mut length: size_t,
mut indx: *mut size_t,
mut result: *mut *mut libc::c_char,
) -> libc::c_int {
let mut value: *mut libc::c_char = 0 as *mut libc::c_char;
let mut cur_token: size_t = 0;
let mut r: libc::c_int = 0;
cur_token = *indx;
r = mailimf_token_case_insensitive_len_parse(
message,
length,
&mut cur_token,
b"read-date\x00" as *const u8 as *const libc::c_char as *mut libc::c_char,
strlen(b"read-date\x00" as *const u8 as *const libc::c_char),
);
if r != MAILIMF_NO_ERROR as libc::c_int {
return r;
}
r = mailimf_unstrict_char_parse(message, length, &mut cur_token, '=' as i32 as libc::c_char);
if r != MAILIMF_NO_ERROR as libc::c_int {
return r;
}
r = mailimf_cfws_parse(message, length, &mut cur_token);
if r != MAILIMF_NO_ERROR as libc::c_int && r != MAILIMF_ERROR_PARSE as libc::c_int {
return r;
}
r = mailmime_quoted_date_time_parse(message, length, &mut cur_token, &mut value);
if r != MAILIMF_NO_ERROR as libc::c_int {
return r;
}
*indx = cur_token;
*result = value;
return MAILIMF_NO_ERROR as libc::c_int;
}
unsafe fn mailmime_quoted_date_time_parse(
mut message: *const libc::c_char,
mut length: size_t,
mut indx: *mut size_t,
mut result: *mut *mut libc::c_char,
) -> libc::c_int {
return mailimf_quoted_string_parse(message, length, indx, result);
}
unsafe fn mailmime_modification_date_parm_parse(
mut message: *const libc::c_char,
mut length: size_t,
mut indx: *mut size_t,
mut result: *mut *mut libc::c_char,
) -> libc::c_int {
let mut value: *mut libc::c_char = 0 as *mut libc::c_char;
let mut cur_token: size_t = 0;
let mut r: libc::c_int = 0;
cur_token = *indx;
r = mailimf_token_case_insensitive_len_parse(
message,
length,
&mut cur_token,
b"modification-date\x00" as *const u8 as *const libc::c_char as *mut libc::c_char,
strlen(b"modification-date\x00" as *const u8 as *const libc::c_char),
);
if r != MAILIMF_NO_ERROR as libc::c_int {
return r;
}
r = mailimf_unstrict_char_parse(message, length, &mut cur_token, '=' as i32 as libc::c_char);
if r != MAILIMF_NO_ERROR as libc::c_int {
return r;
}
r = mailimf_cfws_parse(message, length, &mut cur_token);
if r != MAILIMF_NO_ERROR as libc::c_int && r != MAILIMF_ERROR_PARSE as libc::c_int {
return r;
}
r = mailmime_quoted_date_time_parse(message, length, &mut cur_token, &mut value);
if r != MAILIMF_NO_ERROR as libc::c_int {
return r;
}
*indx = cur_token;
*result = value;
return MAILIMF_NO_ERROR as libc::c_int;
}
unsafe fn mailmime_creation_date_parm_parse(
mut message: *const libc::c_char,
mut length: size_t,
mut indx: *mut size_t,
mut result: *mut *mut libc::c_char,
) -> libc::c_int {
let mut value: *mut libc::c_char = 0 as *mut libc::c_char;
let mut r: libc::c_int = 0;
let mut cur_token: size_t = 0;
cur_token = *indx;
r = mailimf_token_case_insensitive_len_parse(
message,
length,
&mut cur_token,
b"creation-date\x00" as *const u8 as *const libc::c_char as *mut libc::c_char,
strlen(b"creation-date\x00" as *const u8 as *const libc::c_char),
);
if r != MAILIMF_NO_ERROR as libc::c_int {
return r;
}
r = mailimf_unstrict_char_parse(message, length, &mut cur_token, '=' as i32 as libc::c_char);
if r != MAILIMF_NO_ERROR as libc::c_int {
return r;
}
r = mailimf_cfws_parse(message, length, &mut cur_token);
if r != MAILIMF_NO_ERROR as libc::c_int && r != MAILIMF_ERROR_PARSE as libc::c_int {
return r;
}
r = mailmime_quoted_date_time_parse(message, length, &mut cur_token, &mut value);
if r != MAILIMF_NO_ERROR as libc::c_int {
return r;
}
*indx = cur_token;
*result = value;
return MAILIMF_NO_ERROR as libc::c_int;
}
unsafe fn mailmime_filename_parm_parse(
mut message: *const libc::c_char,
mut length: size_t,
mut indx: *mut size_t,
mut result: *mut *mut libc::c_char,
) -> libc::c_int {
let mut value: *mut libc::c_char = 0 as *mut libc::c_char;
let mut r: libc::c_int = 0;
let mut cur_token: size_t = 0;
cur_token = *indx;
r = mailimf_token_case_insensitive_len_parse(
message,
length,
&mut cur_token,
b"filename\x00" as *const u8 as *const libc::c_char as *mut libc::c_char,
strlen(b"filename\x00" as *const u8 as *const libc::c_char),
);
if r != MAILIMF_NO_ERROR as libc::c_int {
return r;
}
r = mailimf_unstrict_char_parse(message, length, &mut cur_token, '=' as i32 as libc::c_char);
if r != MAILIMF_NO_ERROR as libc::c_int {
return r;
}
r = mailimf_cfws_parse(message, length, &mut cur_token);
if r != MAILIMF_NO_ERROR as libc::c_int && r != MAILIMF_ERROR_PARSE as libc::c_int {
return r;
}
r = mailmime_value_parse(message, length, &mut cur_token, &mut value);
if r != MAILIMF_NO_ERROR as libc::c_int {
return r;
}
*indx = cur_token;
*result = value;
return MAILIMF_NO_ERROR as libc::c_int;
}
pub unsafe fn mailmime_disposition_guess_type(
mut message: *const libc::c_char,
mut length: size_t,
mut indx: size_t,
) -> libc::c_int {
if indx >= length {
return MAILMIME_DISPOSITION_PARM_PARAMETER as libc::c_int;
}
match toupper(*message.offset(indx as isize) as libc::c_uchar as libc::c_int) as libc::c_char
as libc::c_int
{
70 => return MAILMIME_DISPOSITION_PARM_FILENAME as libc::c_int,
67 => return MAILMIME_DISPOSITION_PARM_CREATION_DATE as libc::c_int,
77 => return MAILMIME_DISPOSITION_PARM_MODIFICATION_DATE as libc::c_int,
82 => return MAILMIME_DISPOSITION_PARM_READ_DATE as libc::c_int,
83 => return MAILMIME_DISPOSITION_PARM_SIZE as libc::c_int,
_ => return MAILMIME_DISPOSITION_PARM_PARAMETER as libc::c_int,
};
}
pub unsafe fn mailmime_disposition_type_parse(
mut message: *const libc::c_char,
mut length: size_t,
mut indx: *mut size_t,
mut result: *mut *mut mailmime_disposition_type,
) -> libc::c_int {
let mut cur_token: size_t = 0;
let mut type_0: libc::c_int = 0;
let mut extension: *mut libc::c_char = 0 as *mut libc::c_char;
let mut dsp_type: *mut mailmime_disposition_type = 0 as *mut mailmime_disposition_type;
let mut r: libc::c_int = 0;
let mut res: libc::c_int = 0;
cur_token = *indx;
r = mailimf_cfws_parse(message, length, &mut cur_token);
if r != MAILIMF_NO_ERROR as libc::c_int && r != MAILIMF_ERROR_PARSE as libc::c_int {
res = r
} else {
type_0 = MAILMIME_DISPOSITION_TYPE_ERROR as libc::c_int;
extension = 0 as *mut libc::c_char;
r = mailimf_token_case_insensitive_len_parse(
message,
length,
&mut cur_token,
b"inline\x00" as *const u8 as *const libc::c_char as *mut libc::c_char,
strlen(b"inline\x00" as *const u8 as *const libc::c_char),
);
if r == MAILIMF_NO_ERROR as libc::c_int {
type_0 = MAILMIME_DISPOSITION_TYPE_INLINE as libc::c_int
}
if r == MAILIMF_ERROR_PARSE as libc::c_int {
r = mailimf_token_case_insensitive_len_parse(
message,
length,
&mut cur_token,
b"attachment\x00" as *const u8 as *const libc::c_char as *mut libc::c_char,
strlen(b"attachment\x00" as *const u8 as *const libc::c_char),
);
if r == MAILIMF_NO_ERROR as libc::c_int {
type_0 = MAILMIME_DISPOSITION_TYPE_ATTACHMENT as libc::c_int
}
}
if r == MAILIMF_ERROR_PARSE as libc::c_int {
r = mailmime_extension_token_parse(message, length, &mut cur_token, &mut extension);
if r == MAILIMF_NO_ERROR as libc::c_int {
type_0 = MAILMIME_DISPOSITION_TYPE_EXTENSION as libc::c_int
}
}
if r != MAILIMF_NO_ERROR as libc::c_int {
res = r
} else {
dsp_type = mailmime_disposition_type_new(type_0, extension);
if dsp_type.is_null() {
res = MAILIMF_ERROR_MEMORY as libc::c_int;
if !extension.is_null() {
free(extension as *mut libc::c_void);
}
} else {
*result = dsp_type;
*indx = cur_token;
return MAILIMF_NO_ERROR as libc::c_int;
}
}
}
return res;
}

1143
mmime/src/mailmime/mod.rs Normal file

File diff suppressed because it is too large Load Diff

891
mmime/src/mailmime/types.rs Normal file
View File

@@ -0,0 +1,891 @@
use crate::clist::*;
use crate::mailimf::types::*;
use crate::mmapstring::*;
use crate::other::*;
pub const MAILMIME_MECHANISM_TOKEN: libc::c_uint = 6;
pub const MAILMIME_MECHANISM_BASE64: libc::c_uint = 5;
pub const MAILMIME_MECHANISM_QUOTED_PRINTABLE: libc::c_uint = 4;
pub const MAILMIME_MECHANISM_BINARY: libc::c_uint = 3;
pub const MAILMIME_MECHANISM_8BIT: libc::c_uint = 2;
pub const MAILMIME_MECHANISM_7BIT: libc::c_uint = 1;
pub const MAILMIME_MECHANISM_ERROR: libc::c_uint = 0;
#[derive(Copy, Clone)]
#[repr(C)]
pub struct mailmime_composite_type {
pub ct_type: libc::c_int,
pub ct_token: *mut libc::c_char,
}
#[derive(Copy, Clone)]
#[repr(C)]
pub struct mailmime_content {
pub ct_type: *mut mailmime_type,
pub ct_subtype: *mut libc::c_char,
pub ct_parameters: *mut clist,
}
#[derive(Copy, Clone)]
#[repr(C)]
pub struct mailmime_type {
pub tp_type: libc::c_int,
pub tp_data: unnamed,
}
#[derive(Copy, Clone)]
#[repr(C)]
pub union unnamed {
pub tp_discrete_type: *mut mailmime_discrete_type,
pub tp_composite_type: *mut mailmime_composite_type,
}
#[derive(Copy, Clone)]
#[repr(C)]
pub struct mailmime_discrete_type {
pub dt_type: libc::c_int,
pub dt_extension: *mut libc::c_char,
}
pub type unnamed_0 = libc::c_uint;
pub const MAILMIME_FIELD_LOCATION: unnamed_0 = 8;
pub const MAILMIME_FIELD_LANGUAGE: unnamed_0 = 7;
pub const MAILMIME_FIELD_DISPOSITION: unnamed_0 = 6;
pub const MAILMIME_FIELD_VERSION: unnamed_0 = 5;
pub const MAILMIME_FIELD_DESCRIPTION: unnamed_0 = 4;
pub const MAILMIME_FIELD_ID: unnamed_0 = 3;
pub const MAILMIME_FIELD_TRANSFER_ENCODING: unnamed_0 = 2;
pub const MAILMIME_FIELD_TYPE: unnamed_0 = 1;
pub const MAILMIME_FIELD_NONE: unnamed_0 = 0;
#[derive(Copy, Clone)]
#[repr(C)]
pub struct mailmime_field {
pub fld_type: libc::c_int,
pub fld_data: unnamed_1,
}
#[derive(Copy, Clone)]
#[repr(C)]
pub union unnamed_1 {
pub fld_content: *mut mailmime_content,
pub fld_encoding: *mut mailmime_mechanism,
pub fld_id: *mut libc::c_char,
pub fld_description: *mut libc::c_char,
pub fld_version: uint32_t,
pub fld_disposition: *mut mailmime_disposition,
pub fld_language: *mut mailmime_language,
pub fld_location: *mut libc::c_char,
}
#[derive(Copy, Clone)]
#[repr(C)]
pub struct mailmime_language {
pub lg_list: *mut clist,
}
#[derive(Copy, Clone)]
#[repr(C)]
pub struct mailmime_disposition {
pub dsp_type: *mut mailmime_disposition_type,
pub dsp_parms: *mut clist,
}
#[derive(Copy, Clone)]
#[repr(C)]
pub struct mailmime_disposition_type {
pub dsp_type: libc::c_int,
pub dsp_extension: *mut libc::c_char,
}
#[derive(Copy, Clone)]
#[repr(C)]
pub struct mailmime_mechanism {
pub enc_type: libc::c_int,
pub enc_token: *mut libc::c_char,
}
#[derive(Copy, Clone)]
#[repr(C)]
pub struct mailmime_fields {
pub fld_list: *mut clist,
}
#[derive(Copy, Clone)]
#[repr(C)]
pub struct mailmime_parameter {
pub pa_name: *mut libc::c_char,
pub pa_value: *mut libc::c_char,
}
#[derive(Copy, Clone)]
#[repr(C)]
pub struct mailmime_disposition_parm {
pub pa_type: libc::c_int,
pub pa_data: unnamed_3,
}
#[derive(Copy, Clone)]
#[repr(C)]
pub union unnamed_3 {
pub pa_filename: *mut libc::c_char,
pub pa_creation_date: *mut libc::c_char,
pub pa_modification_date: *mut libc::c_char,
pub pa_read_date: *mut libc::c_char,
pub pa_size: size_t,
pub pa_parameter: *mut mailmime_parameter,
}
pub const MAILMIME_DISPOSITION_PARM_PARAMETER: unnamed_11 = 5;
pub const MAILMIME_DISPOSITION_PARM_READ_DATE: unnamed_11 = 3;
pub const MAILMIME_DISPOSITION_PARM_MODIFICATION_DATE: unnamed_11 = 2;
pub const MAILMIME_DISPOSITION_PARM_CREATION_DATE: unnamed_11 = 1;
pub const MAILMIME_DISPOSITION_PARM_FILENAME: unnamed_11 = 0;
#[derive(Copy, Clone)]
#[repr(C)]
pub struct mailmime_multipart_body {
pub bd_list: *mut clist,
}
pub type unnamed_4 = libc::c_uint;
pub const MAILMIME_DATA_FILE: unnamed_4 = 1;
pub const MAILMIME_DATA_TEXT: unnamed_4 = 0;
#[derive(Copy, Clone)]
#[repr(C)]
pub struct mailmime_data {
pub dt_type: libc::c_int,
pub dt_encoding: libc::c_int,
pub dt_encoded: libc::c_int,
pub dt_data: unnamed_5,
}
#[derive(Copy, Clone)]
#[repr(C)]
pub union unnamed_5 {
pub dt_text: unnamed_6,
pub dt_filename: *mut libc::c_char,
}
#[derive(Copy, Clone)]
#[repr(C)]
pub struct unnamed_6 {
pub dt_data: *const libc::c_char,
pub dt_length: size_t,
}
pub type unnamed_7 = libc::c_uint;
pub const MAILMIME_MESSAGE: unnamed_7 = 3;
pub const MAILMIME_MULTIPLE: unnamed_7 = 2;
pub const MAILMIME_SINGLE: unnamed_7 = 1;
pub const MAILMIME_NONE: unnamed_7 = 0;
#[derive(Copy, Clone)]
#[repr(C)]
pub struct Mailmime {
pub mm_parent_type: libc::c_int,
pub mm_parent: *mut Mailmime,
pub mm_multipart_pos: *mut clistiter,
pub mm_type: libc::c_int,
pub mm_mime_start: *const libc::c_char,
pub mm_length: size_t,
pub mm_mime_fields: *mut mailmime_fields,
pub mm_content_type: *mut mailmime_content,
pub mm_body: *mut mailmime_data,
pub mm_data: unnamed_8,
}
#[derive(Copy, Clone)]
#[repr(C)]
pub union unnamed_8 {
pub mm_single: *mut mailmime_data,
pub mm_multipart: unnamed_10,
pub mm_message: unnamed_9,
}
/* message */
#[derive(Copy, Clone)]
#[repr(C)]
pub struct unnamed_9 {
pub mm_fields: *mut mailimf_fields,
pub mm_msg_mime: *mut Mailmime,
}
/* multi-part */
#[derive(Copy, Clone)]
#[repr(C)]
pub struct unnamed_10 {
pub mm_preamble: *mut mailmime_data,
pub mm_epilogue: *mut mailmime_data,
pub mm_mp_list: *mut clist,
}
#[derive(Copy, Clone)]
#[repr(C)]
pub struct mailmime_encoded_word {
pub wd_charset: *mut libc::c_char,
pub wd_text: *mut libc::c_char,
}
pub type unnamed_11 = libc::c_uint;
pub const MAILMIME_DISPOSITION_PARM_SIZE: unnamed_11 = 4;
#[derive(Copy, Clone)]
#[repr(C)]
pub struct mailmime_section {
pub sec_list: *mut clist,
}
pub unsafe fn mailmime_attribute_free(mut attribute: *mut libc::c_char) {
mailmime_token_free(attribute);
}
pub unsafe fn mailmime_token_free(mut token: *mut libc::c_char) {
free(token as *mut libc::c_void);
}
pub unsafe fn mailmime_composite_type_new(
mut ct_type: libc::c_int,
mut ct_token: *mut libc::c_char,
) -> *mut mailmime_composite_type {
let mut ct: *mut mailmime_composite_type = 0 as *mut mailmime_composite_type;
ct = malloc(::std::mem::size_of::<mailmime_composite_type>() as libc::size_t)
as *mut mailmime_composite_type;
if ct.is_null() {
return 0 as *mut mailmime_composite_type;
}
(*ct).ct_type = ct_type;
(*ct).ct_token = ct_token;
return ct;
}
pub unsafe fn mailmime_composite_type_free(mut ct: *mut mailmime_composite_type) {
if !(*ct).ct_token.is_null() {
mailmime_extension_token_free((*ct).ct_token);
}
free(ct as *mut libc::c_void);
}
pub unsafe fn mailmime_extension_token_free(mut extension: *mut libc::c_char) {
mailmime_token_free(extension);
}
pub unsafe fn mailmime_content_new(
mut ct_type: *mut mailmime_type,
mut ct_subtype: *mut libc::c_char,
mut ct_parameters: *mut clist,
) -> *mut mailmime_content {
let mut content: *mut mailmime_content = 0 as *mut mailmime_content;
content =
malloc(::std::mem::size_of::<mailmime_content>() as libc::size_t) as *mut mailmime_content;
if content.is_null() {
return 0 as *mut mailmime_content;
}
(*content).ct_type = ct_type;
(*content).ct_subtype = ct_subtype;
(*content).ct_parameters = ct_parameters;
return content;
}
pub unsafe fn mailmime_content_free(mut content: *mut mailmime_content) {
mailmime_type_free((*content).ct_type);
mailmime_subtype_free((*content).ct_subtype);
if !(*content).ct_parameters.is_null() {
clist_foreach(
(*content).ct_parameters,
::std::mem::transmute::<Option<unsafe fn(_: *mut mailmime_parameter) -> ()>, clist_func>(
Some(mailmime_parameter_free),
),
0 as *mut libc::c_void,
);
clist_free((*content).ct_parameters);
}
free(content as *mut libc::c_void);
}
pub unsafe fn mailmime_parameter_free(mut parameter: *mut mailmime_parameter) {
mailmime_attribute_free((*parameter).pa_name);
mailmime_value_free((*parameter).pa_value);
free(parameter as *mut libc::c_void);
}
pub unsafe fn mailmime_value_free(mut value: *mut libc::c_char) {
free(value as *mut libc::c_void);
}
pub unsafe fn mailmime_subtype_free(mut subtype: *mut libc::c_char) {
mailmime_extension_token_free(subtype);
}
pub unsafe fn mailmime_type_free(mut type_0: *mut mailmime_type) {
match (*type_0).tp_type {
1 => {
mailmime_discrete_type_free((*type_0).tp_data.tp_discrete_type);
}
2 => {
mailmime_composite_type_free((*type_0).tp_data.tp_composite_type);
}
_ => {}
}
free(type_0 as *mut libc::c_void);
}
pub unsafe fn mailmime_discrete_type_free(mut discrete_type: *mut mailmime_discrete_type) {
if !(*discrete_type).dt_extension.is_null() {
mailmime_extension_token_free((*discrete_type).dt_extension);
}
free(discrete_type as *mut libc::c_void);
}
pub unsafe fn mailmime_description_free(mut description: *mut libc::c_char) {
free(description as *mut libc::c_void);
}
pub unsafe fn mailmime_location_free(mut location: *mut libc::c_char) {
free(location as *mut libc::c_void);
}
pub unsafe fn mailmime_discrete_type_new(
mut dt_type: libc::c_int,
mut dt_extension: *mut libc::c_char,
) -> *mut mailmime_discrete_type {
let mut discrete_type: *mut mailmime_discrete_type = 0 as *mut mailmime_discrete_type;
discrete_type = malloc(::std::mem::size_of::<mailmime_discrete_type>() as libc::size_t)
as *mut mailmime_discrete_type;
if discrete_type.is_null() {
return 0 as *mut mailmime_discrete_type;
}
(*discrete_type).dt_type = dt_type;
(*discrete_type).dt_extension = dt_extension;
return discrete_type;
}
pub unsafe fn mailmime_encoding_free(mut encoding: *mut mailmime_mechanism) {
mailmime_mechanism_free(encoding);
}
pub unsafe fn mailmime_mechanism_free(mut mechanism: *mut mailmime_mechanism) {
if !(*mechanism).enc_token.is_null() {
mailmime_token_free((*mechanism).enc_token);
}
free(mechanism as *mut libc::c_void);
}
pub unsafe fn mailmime_id_free(mut id: *mut libc::c_char) {
mailimf_msg_id_free(id);
}
pub unsafe fn mailmime_mechanism_new(
mut enc_type: libc::c_int,
mut enc_token: *mut libc::c_char,
) -> *mut mailmime_mechanism {
let mut mechanism: *mut mailmime_mechanism = 0 as *mut mailmime_mechanism;
mechanism = malloc(::std::mem::size_of::<mailmime_mechanism>() as libc::size_t)
as *mut mailmime_mechanism;
if mechanism.is_null() {
return 0 as *mut mailmime_mechanism;
}
(*mechanism).enc_type = enc_type;
(*mechanism).enc_token = enc_token;
return mechanism;
}
pub unsafe fn mailmime_parameter_new(
mut pa_name: *mut libc::c_char,
mut pa_value: *mut libc::c_char,
) -> *mut mailmime_parameter {
let mut parameter: *mut mailmime_parameter = 0 as *mut mailmime_parameter;
parameter = malloc(::std::mem::size_of::<mailmime_parameter>() as libc::size_t)
as *mut mailmime_parameter;
if parameter.is_null() {
return 0 as *mut mailmime_parameter;
}
(*parameter).pa_name = pa_name;
(*parameter).pa_value = pa_value;
return parameter;
}
pub unsafe fn mailmime_type_new(
mut tp_type: libc::c_int,
mut tp_discrete_type: *mut mailmime_discrete_type,
mut tp_composite_type: *mut mailmime_composite_type,
) -> *mut mailmime_type {
let mut mime_type: *mut mailmime_type = 0 as *mut mailmime_type;
mime_type =
malloc(::std::mem::size_of::<mailmime_type>() as libc::size_t) as *mut mailmime_type;
if mime_type.is_null() {
return 0 as *mut mailmime_type;
}
(*mime_type).tp_type = tp_type;
match tp_type {
1 => (*mime_type).tp_data.tp_discrete_type = tp_discrete_type,
2 => (*mime_type).tp_data.tp_composite_type = tp_composite_type,
_ => {}
}
return mime_type;
}
pub unsafe fn mailmime_language_new(mut lg_list: *mut clist) -> *mut mailmime_language {
let mut lang: *mut mailmime_language = 0 as *mut mailmime_language;
lang = malloc(::std::mem::size_of::<mailmime_language>() as libc::size_t)
as *mut mailmime_language;
if lang.is_null() {
return 0 as *mut mailmime_language;
}
(*lang).lg_list = lg_list;
return lang;
}
pub unsafe fn mailmime_language_free(mut lang: *mut mailmime_language) {
clist_foreach(
(*lang).lg_list,
::std::mem::transmute::<Option<unsafe fn(_: *mut libc::c_char) -> ()>, clist_func>(Some(
mailimf_atom_free,
)),
0 as *mut libc::c_void,
);
clist_free((*lang).lg_list);
free(lang as *mut libc::c_void);
}
/*
void mailmime_x_token_free(gchar * x_token);
*/
pub unsafe fn mailmime_field_new(
mut fld_type: libc::c_int,
mut fld_content: *mut mailmime_content,
mut fld_encoding: *mut mailmime_mechanism,
mut fld_id: *mut libc::c_char,
mut fld_description: *mut libc::c_char,
mut fld_version: uint32_t,
mut fld_disposition: *mut mailmime_disposition,
mut fld_language: *mut mailmime_language,
mut fld_location: *mut libc::c_char,
) -> *mut mailmime_field {
let mut field: *mut mailmime_field = 0 as *mut mailmime_field;
field = malloc(::std::mem::size_of::<mailmime_field>() as libc::size_t) as *mut mailmime_field;
if field.is_null() {
return 0 as *mut mailmime_field;
}
(*field).fld_type = fld_type;
match fld_type {
1 => (*field).fld_data.fld_content = fld_content,
2 => (*field).fld_data.fld_encoding = fld_encoding,
3 => (*field).fld_data.fld_id = fld_id,
4 => (*field).fld_data.fld_description = fld_description,
5 => (*field).fld_data.fld_version = fld_version,
6 => (*field).fld_data.fld_disposition = fld_disposition,
7 => (*field).fld_data.fld_language = fld_language,
8 => (*field).fld_data.fld_location = fld_location,
_ => {}
}
return field;
}
pub unsafe fn mailmime_field_free(mut field: *mut mailmime_field) {
match (*field).fld_type {
1 => {
if !(*field).fld_data.fld_content.is_null() {
mailmime_content_free((*field).fld_data.fld_content);
}
}
2 => {
if !(*field).fld_data.fld_encoding.is_null() {
mailmime_encoding_free((*field).fld_data.fld_encoding);
}
}
3 => {
if !(*field).fld_data.fld_id.is_null() {
mailmime_id_free((*field).fld_data.fld_id);
}
}
4 => {
if !(*field).fld_data.fld_description.is_null() {
mailmime_description_free((*field).fld_data.fld_description);
}
}
6 => {
if !(*field).fld_data.fld_disposition.is_null() {
mailmime_disposition_free((*field).fld_data.fld_disposition);
}
}
7 => {
if !(*field).fld_data.fld_language.is_null() {
mailmime_language_free((*field).fld_data.fld_language);
}
}
8 => {
if !(*field).fld_data.fld_location.is_null() {
mailmime_location_free((*field).fld_data.fld_location);
}
}
_ => {}
}
free(field as *mut libc::c_void);
}
pub unsafe fn mailmime_disposition_free(mut dsp: *mut mailmime_disposition) {
mailmime_disposition_type_free((*dsp).dsp_type);
clist_foreach(
(*dsp).dsp_parms,
::std::mem::transmute::<
Option<unsafe fn(_: *mut mailmime_disposition_parm) -> ()>,
clist_func,
>(Some(mailmime_disposition_parm_free)),
0 as *mut libc::c_void,
);
clist_free((*dsp).dsp_parms);
free(dsp as *mut libc::c_void);
}
pub unsafe fn mailmime_disposition_parm_free(mut dsp_parm: *mut mailmime_disposition_parm) {
match (*dsp_parm).pa_type {
0 => {
mailmime_filename_parm_free((*dsp_parm).pa_data.pa_filename);
}
1 => {
mailmime_creation_date_parm_free((*dsp_parm).pa_data.pa_creation_date);
}
2 => {
mailmime_modification_date_parm_free((*dsp_parm).pa_data.pa_modification_date);
}
3 => {
mailmime_read_date_parm_free((*dsp_parm).pa_data.pa_read_date);
}
5 => {
mailmime_parameter_free((*dsp_parm).pa_data.pa_parameter);
}
_ => {}
}
free(dsp_parm as *mut libc::c_void);
}
pub unsafe fn mailmime_read_date_parm_free(mut date: *mut libc::c_char) {
mailmime_quoted_date_time_free(date);
}
pub unsafe fn mailmime_quoted_date_time_free(mut date: *mut libc::c_char) {
mailimf_quoted_string_free(date);
}
pub unsafe fn mailmime_modification_date_parm_free(mut date: *mut libc::c_char) {
mailmime_quoted_date_time_free(date);
}
pub unsafe fn mailmime_creation_date_parm_free(mut date: *mut libc::c_char) {
mailmime_quoted_date_time_free(date);
}
pub unsafe fn mailmime_filename_parm_free(mut filename: *mut libc::c_char) {
mailmime_value_free(filename);
}
pub unsafe fn mailmime_disposition_type_free(mut dsp_type: *mut mailmime_disposition_type) {
if !(*dsp_type).dsp_extension.is_null() {
free((*dsp_type).dsp_extension as *mut libc::c_void);
}
free(dsp_type as *mut libc::c_void);
}
pub unsafe fn mailmime_fields_new(mut fld_list: *mut clist) -> *mut mailmime_fields {
let mut fields: *mut mailmime_fields = 0 as *mut mailmime_fields;
fields =
malloc(::std::mem::size_of::<mailmime_fields>() as libc::size_t) as *mut mailmime_fields;
if fields.is_null() {
return 0 as *mut mailmime_fields;
}
(*fields).fld_list = fld_list;
return fields;
}
pub unsafe fn mailmime_fields_free(mut fields: *mut mailmime_fields) {
clist_foreach(
(*fields).fld_list,
::std::mem::transmute::<Option<unsafe fn(_: *mut mailmime_field) -> ()>, clist_func>(Some(
mailmime_field_free,
)),
0 as *mut libc::c_void,
);
clist_free((*fields).fld_list);
free(fields as *mut libc::c_void);
}
pub unsafe fn mailmime_multipart_body_new(mut bd_list: *mut clist) -> *mut mailmime_multipart_body {
let mut mp_body: *mut mailmime_multipart_body = 0 as *mut mailmime_multipart_body;
mp_body = malloc(::std::mem::size_of::<mailmime_multipart_body>() as libc::size_t)
as *mut mailmime_multipart_body;
if mp_body.is_null() {
return 0 as *mut mailmime_multipart_body;
}
(*mp_body).bd_list = bd_list;
return mp_body;
}
pub unsafe fn mailmime_multipart_body_free(mut mp_body: *mut mailmime_multipart_body) {
clist_foreach(
(*mp_body).bd_list,
::std::mem::transmute::<Option<unsafe fn(_: *mut mailimf_body) -> ()>, clist_func>(Some(
mailimf_body_free,
)),
0 as *mut libc::c_void,
);
clist_free((*mp_body).bd_list);
free(mp_body as *mut libc::c_void);
}
pub unsafe fn mailmime_data_new(
mut dt_type: libc::c_int,
mut dt_encoding: libc::c_int,
mut dt_encoded: libc::c_int,
mut dt_data: *const libc::c_char,
mut dt_length: size_t,
mut dt_filename: *mut libc::c_char,
) -> *mut mailmime_data {
let mut mime_data: *mut mailmime_data = 0 as *mut mailmime_data;
mime_data =
malloc(::std::mem::size_of::<mailmime_data>() as libc::size_t) as *mut mailmime_data;
if mime_data.is_null() {
return 0 as *mut mailmime_data;
}
(*mime_data).dt_type = dt_type;
(*mime_data).dt_encoding = dt_encoding;
(*mime_data).dt_encoded = dt_encoded;
match dt_type {
0 => {
(*mime_data).dt_data.dt_text.dt_data = dt_data;
(*mime_data).dt_data.dt_text.dt_length = dt_length
}
1 => (*mime_data).dt_data.dt_filename = dt_filename,
_ => {}
}
return mime_data;
}
pub unsafe fn mailmime_data_free(mut mime_data: *mut mailmime_data) {
match (*mime_data).dt_type {
1 => {
free((*mime_data).dt_data.dt_filename as *mut libc::c_void);
}
_ => {}
}
free(mime_data as *mut libc::c_void);
}
pub unsafe fn mailmime_new(
mut mm_type: libc::c_int,
mut mm_mime_start: *const libc::c_char,
mut mm_length: size_t,
mut mm_mime_fields: *mut mailmime_fields,
mut mm_content_type: *mut mailmime_content,
mut mm_body: *mut mailmime_data,
mut mm_preamble: *mut mailmime_data,
mut mm_epilogue: *mut mailmime_data,
mut mm_mp_list: *mut clist,
mut mm_fields: *mut mailimf_fields,
mut mm_msg_mime: *mut Mailmime,
) -> *mut Mailmime {
let mut mime: *mut Mailmime = 0 as *mut Mailmime;
let mut cur: *mut clistiter = 0 as *mut clistiter;
mime = malloc(::std::mem::size_of::<Mailmime>() as libc::size_t) as *mut Mailmime;
if mime.is_null() {
return 0 as *mut Mailmime;
}
(*mime).mm_parent = 0 as *mut Mailmime;
(*mime).mm_parent_type = MAILMIME_NONE as libc::c_int;
(*mime).mm_multipart_pos = 0 as *mut clistiter;
(*mime).mm_type = mm_type;
(*mime).mm_mime_start = mm_mime_start;
(*mime).mm_length = mm_length;
(*mime).mm_mime_fields = mm_mime_fields;
(*mime).mm_content_type = mm_content_type;
(*mime).mm_body = mm_body;
match mm_type {
1 => (*mime).mm_data.mm_single = mm_body,
2 => {
(*mime).mm_data.mm_multipart.mm_preamble = mm_preamble;
(*mime).mm_data.mm_multipart.mm_epilogue = mm_epilogue;
(*mime).mm_data.mm_multipart.mm_mp_list = mm_mp_list;
cur = (*mm_mp_list).first;
while !cur.is_null() {
let mut submime: *mut Mailmime = 0 as *mut Mailmime;
submime = (if !cur.is_null() {
(*cur).data
} else {
0 as *mut libc::c_void
}) as *mut Mailmime;
(*submime).mm_parent = mime;
(*submime).mm_parent_type = MAILMIME_MULTIPLE as libc::c_int;
(*submime).mm_multipart_pos = cur;
cur = if !cur.is_null() {
(*cur).next
} else {
0 as *mut clistcell
}
}
}
3 => {
(*mime).mm_data.mm_message.mm_fields = mm_fields;
(*mime).mm_data.mm_message.mm_msg_mime = mm_msg_mime;
if !mm_msg_mime.is_null() {
(*mm_msg_mime).mm_parent = mime;
(*mm_msg_mime).mm_parent_type = MAILMIME_MESSAGE as libc::c_int
}
}
_ => {}
}
return mime;
}
pub unsafe fn mailmime_new_simple(
mut mm_type: libc::c_int,
mut mm_mime_fields: *mut mailmime_fields,
mut mm_content_type: *mut mailmime_content,
mut mm_fields: *mut mailimf_fields,
mut mm_msg_mime: *mut Mailmime,
) -> *mut Mailmime {
mailmime_new(
mm_type,
std::ptr::null(),
0,
mm_mime_fields,
mm_content_type,
std::ptr::null_mut(),
std::ptr::null_mut(),
std::ptr::null_mut(),
std::ptr::null_mut(),
mm_fields,
mm_msg_mime,
)
}
pub unsafe fn mailmime_free(mut mime: *mut Mailmime) {
match (*mime).mm_type {
1 => {
if (*mime).mm_body.is_null() && !(*mime).mm_data.mm_single.is_null() {
mailmime_data_free((*mime).mm_data.mm_single);
}
}
2 => {
/* do nothing */
if !(*mime).mm_data.mm_multipart.mm_preamble.is_null() {
mailmime_data_free((*mime).mm_data.mm_multipart.mm_preamble);
}
if !(*mime).mm_data.mm_multipart.mm_epilogue.is_null() {
mailmime_data_free((*mime).mm_data.mm_multipart.mm_epilogue);
}
clist_foreach(
(*mime).mm_data.mm_multipart.mm_mp_list,
::std::mem::transmute::<Option<unsafe fn(_: *mut Mailmime) -> ()>, clist_func>(
Some(mailmime_free),
),
0 as *mut libc::c_void,
);
clist_free((*mime).mm_data.mm_multipart.mm_mp_list);
}
3 => {
if !(*mime).mm_data.mm_message.mm_fields.is_null() {
mailimf_fields_free((*mime).mm_data.mm_message.mm_fields);
}
if !(*mime).mm_data.mm_message.mm_msg_mime.is_null() {
mailmime_free((*mime).mm_data.mm_message.mm_msg_mime);
}
}
_ => {}
}
if !(*mime).mm_body.is_null() {
mailmime_data_free((*mime).mm_body);
}
if !(*mime).mm_mime_fields.is_null() {
mailmime_fields_free((*mime).mm_mime_fields);
}
if !(*mime).mm_content_type.is_null() {
mailmime_content_free((*mime).mm_content_type);
}
free(mime as *mut libc::c_void);
}
pub unsafe fn mailmime_encoded_word_new(
mut wd_charset: *mut libc::c_char,
mut wd_text: *mut libc::c_char,
) -> *mut mailmime_encoded_word {
let mut ew: *mut mailmime_encoded_word = 0 as *mut mailmime_encoded_word;
ew = malloc(::std::mem::size_of::<mailmime_encoded_word>() as libc::size_t)
as *mut mailmime_encoded_word;
if ew.is_null() {
return 0 as *mut mailmime_encoded_word;
}
(*ew).wd_charset = wd_charset;
(*ew).wd_text = wd_text;
return ew;
}
pub unsafe fn mailmime_encoded_word_free(mut ew: *mut mailmime_encoded_word) {
mailmime_charset_free((*ew).wd_charset);
mailmime_encoded_text_free((*ew).wd_text);
free(ew as *mut libc::c_void);
}
pub unsafe fn mailmime_encoded_text_free(mut text: *mut libc::c_char) {
free(text as *mut libc::c_void);
}
pub unsafe fn mailmime_charset_free(mut charset: *mut libc::c_char) {
free(charset as *mut libc::c_void);
}
pub unsafe fn mailmime_disposition_new(
mut dsp_type: *mut mailmime_disposition_type,
mut dsp_parms: *mut clist,
) -> *mut mailmime_disposition {
let mut dsp: *mut mailmime_disposition = 0 as *mut mailmime_disposition;
dsp = malloc(::std::mem::size_of::<mailmime_disposition>() as libc::size_t)
as *mut mailmime_disposition;
if dsp.is_null() {
return 0 as *mut mailmime_disposition;
}
(*dsp).dsp_type = dsp_type;
(*dsp).dsp_parms = dsp_parms;
return dsp;
}
pub unsafe fn mailmime_disposition_type_new(
mut dsp_type: libc::c_int,
mut dsp_extension: *mut libc::c_char,
) -> *mut mailmime_disposition_type {
let mut m_dsp_type: *mut mailmime_disposition_type = 0 as *mut mailmime_disposition_type;
m_dsp_type = malloc(::std::mem::size_of::<mailmime_disposition_type>() as libc::size_t)
as *mut mailmime_disposition_type;
if m_dsp_type.is_null() {
return 0 as *mut mailmime_disposition_type;
}
(*m_dsp_type).dsp_type = dsp_type;
(*m_dsp_type).dsp_extension = dsp_extension;
return m_dsp_type;
}
pub unsafe fn mailmime_disposition_parm_new(
mut pa_type: libc::c_int,
mut pa_filename: *mut libc::c_char,
mut pa_creation_date: *mut libc::c_char,
mut pa_modification_date: *mut libc::c_char,
mut pa_read_date: *mut libc::c_char,
mut pa_size: size_t,
mut pa_parameter: *mut mailmime_parameter,
) -> *mut mailmime_disposition_parm {
let mut dsp_parm: *mut mailmime_disposition_parm = 0 as *mut mailmime_disposition_parm;
dsp_parm = malloc(::std::mem::size_of::<mailmime_disposition_parm>() as libc::size_t)
as *mut mailmime_disposition_parm;
if dsp_parm.is_null() {
return 0 as *mut mailmime_disposition_parm;
}
(*dsp_parm).pa_type = pa_type;
match pa_type {
0 => (*dsp_parm).pa_data.pa_filename = pa_filename,
1 => (*dsp_parm).pa_data.pa_creation_date = pa_creation_date,
2 => (*dsp_parm).pa_data.pa_modification_date = pa_modification_date,
3 => (*dsp_parm).pa_data.pa_read_date = pa_read_date,
4 => (*dsp_parm).pa_data.pa_size = pa_size,
5 => (*dsp_parm).pa_data.pa_parameter = pa_parameter,
_ => {}
}
return dsp_parm;
}
pub unsafe fn mailmime_section_new(mut sec_list: *mut clist) -> *mut mailmime_section {
let mut section: *mut mailmime_section = 0 as *mut mailmime_section;
section =
malloc(::std::mem::size_of::<mailmime_section>() as libc::size_t) as *mut mailmime_section;
if section.is_null() {
return 0 as *mut mailmime_section;
}
(*section).sec_list = sec_list;
return section;
}
pub unsafe fn mailmime_section_free(mut section: *mut mailmime_section) {
clist_foreach(
(*section).sec_list,
::std::mem::transmute::<Option<unsafe extern "C" fn(_: *mut libc::c_void) -> ()>, clist_func>(
Some(free),
),
0 as *mut libc::c_void,
);
clist_free((*section).sec_list);
free(section as *mut libc::c_void);
}
pub unsafe fn mailmime_decoded_part_free(mut part: *mut libc::c_char) {
mmap_string_unref(part);
}

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,82 @@
use crate::mailmime::types::*;
use crate::mailmime::write_generic::*;
use crate::mmapstring::*;
use crate::other::*;
unsafe fn do_write(
mut data: *mut libc::c_void,
mut str: *const libc::c_char,
mut length: size_t,
) -> libc::c_int {
let mut f: *mut MMAPString = 0 as *mut MMAPString;
f = data as *mut MMAPString;
if mmap_string_append_len(f, str, length).is_null() {
return 0i32;
} else {
return length as libc::c_int;
};
}
pub unsafe fn mailmime_content_write_mem(
mut f: *mut MMAPString,
mut col: *mut libc::c_int,
mut content: *mut mailmime_content,
) -> libc::c_int {
return mailmime_content_write_driver(Some(do_write), f as *mut libc::c_void, col, content);
}
pub unsafe fn mailmime_content_type_write_mem(
mut f: *mut MMAPString,
mut col: *mut libc::c_int,
mut content: *mut mailmime_content,
) -> libc::c_int {
return mailmime_content_type_write_driver(
Some(do_write),
f as *mut libc::c_void,
col,
content,
);
}
pub unsafe fn mailmime_write_mem(
mut f: *mut MMAPString,
mut col: *mut libc::c_int,
mut build_info: *mut Mailmime,
) -> libc::c_int {
return mailmime_write_driver(Some(do_write), f as *mut libc::c_void, col, build_info);
}
pub unsafe fn mailmime_quoted_printable_write_mem(
mut f: *mut MMAPString,
mut col: *mut libc::c_int,
mut istext: libc::c_int,
mut text: *const libc::c_char,
mut size: size_t,
) -> libc::c_int {
return mailmime_quoted_printable_write_driver(
Some(do_write),
f as *mut libc::c_void,
col,
istext,
text,
size,
);
}
pub unsafe fn mailmime_base64_write_mem(
mut f: *mut MMAPString,
mut col: *mut libc::c_int,
mut text: *const libc::c_char,
mut size: size_t,
) -> libc::c_int {
return mailmime_base64_write_driver(Some(do_write), f as *mut libc::c_void, col, text, size);
}
pub unsafe fn mailmime_data_write_mem(
mut f: *mut MMAPString,
mut col: *mut libc::c_int,
mut data: *mut mailmime_data,
mut istext: libc::c_int,
) -> libc::c_int {
return mailmime_data_write_driver(Some(do_write), f as *mut libc::c_void, col, data, istext);
}

397
mmime/src/mmapstring.rs Normal file
View File

@@ -0,0 +1,397 @@
use std::sync::Mutex;
use lazy_static::lazy_static;
use libc;
use crate::chash::*;
use crate::other::*;
lazy_static! {
static ref mmapstring_lock: Mutex<()> = Mutex::new(());
}
#[derive(Copy, Clone)]
#[repr(C)]
pub struct MMAPString {
pub str_0: *mut libc::c_char,
pub len: size_t,
pub allocated_len: size_t,
pub fd: libc::c_int,
pub mmapped_size: size_t,
}
pub const TMPDIR: &'static str = "/tmp";
pub unsafe fn mmap_string_new(mut init: *const libc::c_char) -> *mut MMAPString {
let mut string: *mut MMAPString = 0 as *mut MMAPString;
string = mmap_string_sized_new(if !init.is_null() {
strlen(init).wrapping_add(2i32 as libc::size_t)
} else {
2i32 as libc::size_t
});
if string.is_null() {
return 0 as *mut MMAPString;
}
if !init.is_null() {
mmap_string_append(string, init);
}
return string;
}
pub unsafe fn mmap_string_append(
mut string: *mut MMAPString,
mut val: *const libc::c_char,
) -> *mut MMAPString {
return mmap_string_insert_len(string, (*string).len, val, strlen(val));
}
pub unsafe fn mmap_string_insert_len(
mut string: *mut MMAPString,
mut pos: size_t,
mut val: *const libc::c_char,
mut len: size_t,
) -> *mut MMAPString {
if mmap_string_maybe_expand(string, len).is_null() {
return 0 as *mut MMAPString;
}
if pos < (*string).len {
memmove(
(*string).str_0.offset(pos as isize).offset(len as isize) as *mut libc::c_void,
(*string).str_0.offset(pos as isize) as *const libc::c_void,
(*string).len.wrapping_sub(pos),
);
}
memmove(
(*string).str_0.offset(pos as isize) as *mut libc::c_void,
val as *const libc::c_void,
len,
);
(*string).len = ((*string).len as libc::size_t).wrapping_add(len) as size_t as size_t;
*(*string).str_0.offset((*string).len as isize) = 0i32 as libc::c_char;
return string;
}
unsafe fn mmap_string_maybe_expand(
mut string: *mut MMAPString,
mut len: size_t,
) -> *mut MMAPString {
if (*string).len.wrapping_add(len) >= (*string).allocated_len {
let mut old_size: size_t = 0;
let mut newstring: *mut MMAPString = 0 as *mut MMAPString;
old_size = (*string).allocated_len;
(*string).allocated_len = nearest_power(
1i32 as size_t,
(*string)
.len
.wrapping_add(len)
.wrapping_add(1i32 as libc::size_t),
);
newstring = mmap_string_realloc_memory(string);
if newstring.is_null() {
(*string).allocated_len = old_size
}
return newstring;
}
return string;
}
/* Strings.
*/
/* SEB */
unsafe fn mmap_string_realloc_memory(mut string: *mut MMAPString) -> *mut MMAPString {
let mut tmp: *mut libc::c_char = 0 as *mut libc::c_char;
tmp = realloc(
(*string).str_0 as *mut libc::c_void,
(*string).allocated_len,
) as *mut libc::c_char;
if tmp.is_null() {
string = 0 as *mut MMAPString
} else {
(*string).str_0 = tmp
}
return string;
}
/* MMAPString */
#[inline]
unsafe fn nearest_power(mut base: size_t, mut num: size_t) -> size_t {
if num > (-1i32 as size_t).wrapping_div(2i32 as libc::size_t) {
return -1i32 as size_t;
} else {
let mut n: size_t = base;
while n < num {
n <<= 1i32
}
return n;
};
}
pub unsafe fn mmap_string_sized_new(mut dfl_size: size_t) -> *mut MMAPString {
let mut string: *mut MMAPString = 0 as *mut MMAPString;
string = malloc(::std::mem::size_of::<MMAPString>() as libc::size_t) as *mut MMAPString;
if string.is_null() {
return 0 as *mut MMAPString;
}
(*string).allocated_len = 0i32 as size_t;
(*string).len = 0i32 as size_t;
(*string).str_0 = 0 as *mut libc::c_char;
(*string).fd = -1i32;
(*string).mmapped_size = 0i32 as size_t;
if mmap_string_maybe_expand(
string,
if dfl_size > 2i32 as libc::size_t {
dfl_size
} else {
2i32 as libc::size_t
},
)
.is_null()
{
free(string as *mut libc::c_void);
return 0 as *mut MMAPString;
}
*(*string).str_0.offset(0isize) = 0i32 as libc::c_char;
return string;
}
pub unsafe fn mmap_string_new_len(
mut init: *const libc::c_char,
mut len: size_t,
) -> *mut MMAPString {
let mut string: *mut MMAPString = 0 as *mut MMAPString;
if len <= 0i32 as libc::size_t {
return mmap_string_new(b"\x00" as *const u8 as *const libc::c_char);
} else {
string = mmap_string_sized_new(len);
if string.is_null() {
return string;
}
if !init.is_null() {
mmap_string_append_len(string, init, len);
}
return string;
};
}
pub unsafe fn mmap_string_append_len(
mut string: *mut MMAPString,
mut val: *const libc::c_char,
mut len: size_t,
) -> *mut MMAPString {
return mmap_string_insert_len(string, (*string).len, val, len);
}
pub unsafe fn mmap_string_free(mut string: *mut MMAPString) {
if string.is_null() {
return;
}
free((*string).str_0 as *mut libc::c_void);
free(string as *mut libc::c_void);
}
pub unsafe fn mmap_string_assign(
mut string: *mut MMAPString,
mut rval: *const libc::c_char,
) -> *mut MMAPString {
mmap_string_truncate(string, 0i32 as size_t);
if mmap_string_append(string, rval).is_null() {
return 0 as *mut MMAPString;
}
return string;
}
pub unsafe fn mmap_string_truncate(
mut string: *mut MMAPString,
mut len: size_t,
) -> *mut MMAPString {
(*string).len = if len < (*string).len {
len
} else {
(*string).len
};
*(*string).str_0.offset((*string).len as isize) = 0i32 as libc::c_char;
return string;
}
pub unsafe fn mmap_string_set_size(
mut string: *mut MMAPString,
mut len: size_t,
) -> *mut MMAPString {
if len >= (*string).allocated_len {
if mmap_string_maybe_expand(string, len.wrapping_sub((*string).len)).is_null() {
return 0 as *mut MMAPString;
}
}
(*string).len = len;
*(*string).str_0.offset(len as isize) = 0i32 as libc::c_char;
return string;
}
pub unsafe fn mmap_string_append_c(
mut string: *mut MMAPString,
mut c: libc::c_char,
) -> *mut MMAPString {
return mmap_string_insert_c(string, (*string).len, c);
}
pub unsafe fn mmap_string_insert_c(
mut string: *mut MMAPString,
mut pos: size_t,
mut c: libc::c_char,
) -> *mut MMAPString {
if mmap_string_maybe_expand(string, 1i32 as size_t).is_null() {
return 0 as *mut MMAPString;
}
if pos < (*string).len {
memmove(
(*string).str_0.offset(pos as isize).offset(1isize) as *mut libc::c_void,
(*string).str_0.offset(pos as isize) as *const libc::c_void,
(*string).len.wrapping_sub(pos),
);
}
*(*string).str_0.offset(pos as isize) = c;
(*string).len =
((*string).len as libc::size_t).wrapping_add(1i32 as libc::size_t) as size_t as size_t;
*(*string).str_0.offset((*string).len as isize) = 0i32 as libc::c_char;
return string;
}
pub unsafe fn mmap_string_prepend(
mut string: *mut MMAPString,
mut val: *const libc::c_char,
) -> *mut MMAPString {
return mmap_string_insert_len(string, 0i32 as size_t, val, strlen(val));
}
pub unsafe fn mmap_string_prepend_c(
mut string: *mut MMAPString,
mut c: libc::c_char,
) -> *mut MMAPString {
return mmap_string_insert_c(string, 0i32 as size_t, c);
}
pub unsafe fn mmap_string_prepend_len(
mut string: *mut MMAPString,
mut val: *const libc::c_char,
mut len: size_t,
) -> *mut MMAPString {
return mmap_string_insert_len(string, 0i32 as size_t, val, len);
}
pub unsafe fn mmap_string_insert(
mut string: *mut MMAPString,
mut pos: size_t,
mut val: *const libc::c_char,
) -> *mut MMAPString {
return mmap_string_insert_len(string, pos, val, strlen(val));
}
pub unsafe fn mmap_string_erase(
mut string: *mut MMAPString,
mut pos: size_t,
mut len: size_t,
) -> *mut MMAPString {
if pos.wrapping_add(len) < (*string).len {
memmove(
(*string).str_0.offset(pos as isize) as *mut libc::c_void,
(*string).str_0.offset(pos as isize).offset(len as isize) as *const libc::c_void,
(*string).len.wrapping_sub(pos.wrapping_add(len)),
);
}
(*string).len = ((*string).len as libc::size_t).wrapping_sub(len) as size_t as size_t;
*(*string).str_0.offset((*string).len as isize) = 0i32 as libc::c_char;
return string;
}
pub unsafe fn mmap_string_set_ceil(mut ceil: size_t) {
mmap_string_ceil = ceil;
}
static mut mmap_string_ceil: size_t = (8i32 * 1024i32 * 1024i32) as size_t;
pub unsafe fn mmap_string_ref(mut string: *mut MMAPString) -> libc::c_int {
let mut ht: *mut chash = 0 as *mut chash;
let mut r: libc::c_int = 0;
let mut key: chashdatum = chashdatum {
data: 0 as *mut libc::c_void,
len: 0,
};
let mut data: chashdatum = chashdatum {
data: 0 as *mut libc::c_void,
len: 0,
};
mmapstring_lock.lock().unwrap();
if mmapstring_hashtable.is_null() {
mmapstring_hashtable_init();
}
ht = mmapstring_hashtable;
if ht.is_null() {
return -1i32;
}
key.data = &mut (*string).str_0 as *mut *mut libc::c_char as *mut libc::c_void;
key.len = ::std::mem::size_of::<*mut libc::c_char>() as libc::size_t as libc::c_uint;
data.data = string as *mut libc::c_void;
data.len = 0i32 as libc::c_uint;
r = chash_set(
mmapstring_hashtable,
&mut key,
&mut data,
0 as *mut chashdatum,
);
if r < 0i32 {
return r;
}
return 0i32;
}
static mut mmapstring_hashtable: *mut chash = 0 as *const chash as *mut chash;
unsafe fn mmapstring_hashtable_init() {
mmapstring_hashtable = chash_new(13i32 as libc::c_uint, 1i32);
}
pub unsafe fn mmap_string_unref(mut str: *mut libc::c_char) -> libc::c_int {
let mut string: *mut MMAPString = 0 as *mut MMAPString;
let mut ht: *mut chash = 0 as *mut chash;
let mut key: chashdatum = chashdatum {
data: 0 as *mut libc::c_void,
len: 0,
};
let mut data: chashdatum = chashdatum {
data: 0 as *mut libc::c_void,
len: 0,
};
let mut r: libc::c_int = 0;
if str.is_null() {
return -1i32;
}
mmapstring_lock.lock().unwrap();
ht = mmapstring_hashtable;
if ht.is_null() {
return -1i32;
}
key.data = &mut str as *mut *mut libc::c_char as *mut libc::c_void;
key.len = ::std::mem::size_of::<*mut libc::c_char>() as libc::size_t as libc::c_uint;
r = chash_get(ht, &mut key, &mut data);
if r < 0i32 {
string = 0 as *mut MMAPString
} else {
string = data.data as *mut MMAPString
}
if !string.is_null() {
chash_delete(ht, &mut key, 0 as *mut chashdatum);
if chash_count(ht) == 0i32 as libc::c_uint {
chash_free(ht);
mmapstring_hashtable = 0 as *mut chash
}
}
if !string.is_null() {
mmap_string_free(string);
return 0i32;
} else {
return -1i32;
};
}
#[inline]
unsafe fn chash_count(mut hash: *mut chash) -> libc::c_uint {
return (*hash).count;
}
pub unsafe fn mmapstring_init_lock() {}
pub unsafe fn mmapstring_uninit_lock() {}

1707
mmime/src/other.rs Normal file

File diff suppressed because it is too large Load Diff

View File

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

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,17 +65,17 @@ 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::
python install_python_bindings.py
The bindings will be installed in release mode but with debug symbols.
The release mode is neccessary because some tests generate RSA keys
The release mode is necessary because some tests generate RSA keys
which is prohibitively slow in debug mode.
After succcessul binding installation you can finally run the tests::
After successful binding installation you can finally run the tests::
pytest -v tests

View File

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

View File

@@ -2,7 +2,6 @@
from __future__ import print_function
import threading
import os
import re
import time
from array import array
@@ -14,7 +13,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
@@ -23,7 +22,7 @@ class Account(object):
by the underlying deltachat c-library. All public Account methods are
meant to be memory-safe and return memory-safe objects.
"""
def __init__(self, db_path, logid=None, eventlogging=True):
def __init__(self, db_path, logid=None, eventlogging=True, debug=True):
""" initialize account object.
:param db_path: a path to the account database. The database
@@ -31,13 +30,14 @@ class Account(object):
:param logid: an optional logging prefix that should be used with
the default internal logging.
:param eventlogging: if False no eventlogging and no context callback will be configured
:param debug: turn on debug logging for events.
"""
self._dc_context = ffi.gc(
lib.dc_context_new(lib.py_dc_callback, ffi.NULL, ffi.NULL),
_destroy_dc_context,
)
if eventlogging:
self._evlogger = EventLogger(self._dc_context, logid)
self._evlogger = EventLogger(self._dc_context, logid, debug)
deltachat.set_context_callback(self._dc_context, self._process_event)
self._threads = IOThreads(self._dc_context, self._evlogger._log_event)
else:
@@ -48,11 +48,10 @@ class Account(object):
if not lib.dc_open(self._dc_context, db_path, ffi.NULL):
raise ValueError("Could not dc_open: {}".format(db_path))
self._configkeys = self.get_config("sys.config_keys").split()
self._imex_completed = threading.Event()
self._imex_events = Queue()
# 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:
@@ -232,7 +231,7 @@ class Account(object):
:returns: a :class:`deltachat.chatting.Chat` object.
"""
bytes_name = name.encode("utf8")
chat_id = lib.dc_create_group_chat(self._dc_context, verified, bytes_name)
chat_id = lib.dc_create_group_chat(self._dc_context, int(verified), bytes_name)
return Chat(self, chat_id)
def get_chats(self):
@@ -290,31 +289,64 @@ class Account(object):
msg_ids = [msg.id for msg in messages]
lib.dc_delete_msgs(self._dc_context, msg_ids, len(msg_ids))
def export_to_dir(self, backupdir):
"""return after all delta chat state is exported to a new file in
the specified directory.
def export_self_keys(self, path):
""" export public and private keys to the specified directory. """
return self._export(path, imex_cmd=1)
def export_all(self, path):
"""return new file containing a backup of all database state
(chats, contacts, keys, media, ...). The file is created in the
the `path` directory.
"""
snap_files = os.listdir(backupdir)
self._imex_completed.clear()
lib.dc_imex(self._dc_context, 11, as_dc_charpointer(backupdir), ffi.NULL)
export_files = self._export(path, 11)
if len(export_files) != 1:
raise RuntimeError("found more than one new file")
return export_files[0]
def _imex_events_clear(self):
try:
while True:
self._imex_events.get_nowait()
except Empty:
pass
def _export(self, path, imex_cmd):
self._imex_events_clear()
lib.dc_imex(self._dc_context, imex_cmd, as_dc_charpointer(path), ffi.NULL)
if not self._threads.is_started():
lib.dc_perform_imap_jobs(self._dc_context)
self._imex_completed.wait()
for x in os.listdir(backupdir):
if x not in snap_files:
return os.path.join(backupdir, x)
files_written = []
while True:
ev = self._imex_events.get()
if isinstance(ev, str):
files_written.append(ev)
elif isinstance(ev, bool):
if not ev:
raise ValueError("export failed, exp-files: {}".format(files_written))
return files_written
def import_from_file(self, path):
"""import delta chat state from the specified backup file.
def import_self_keys(self, path):
""" Import private keys found in the `path` directory.
The last imported key is made the default keys unless its name
contains the string legacy. Public keys are not imported.
"""
self._import(path, imex_cmd=2)
def import_all(self, path):
"""import delta chat state from the specified backup `path` (a file).
The account must be in unconfigured state for import to attempted.
"""
assert not self.is_configured(), "cannot import into configured account"
self._imex_completed.clear()
lib.dc_imex(self._dc_context, 12, as_dc_charpointer(path), ffi.NULL)
self._import(path, imex_cmd=12)
def _import(self, path, imex_cmd):
self._imex_events_clear()
lib.dc_imex(self._dc_context, imex_cmd, as_dc_charpointer(path), ffi.NULL)
if not self._threads.is_started():
lib.dc_perform_imap_jobs(self._dc_context)
self._imex_completed.wait()
if not self._imex_events.get():
raise ValueError("import from path '{}' failed".format(path))
def initiate_key_transfer(self):
"""return setup code after a Autocrypt setup message
@@ -329,7 +361,66 @@ class Account(object):
raise RuntimeError("could not send out autocrypt setup message")
return from_dc_charpointer(res)
def start_threads(self):
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)
#
# meta API for start/stop and event based processing
#
def wait_next_incoming_message(self):
""" wait for and return next incoming message. """
ev = self._evlogger.get_matching("DC_EVENT_INCOMING_MSG")
return self.get_message_by_id(ev[2])
def start_threads(self, mvbox=False, sentbox=False):
""" start IMAP/SMTP threads (and configure account if it hasn't happened).
:raises: ValueError if 'addr' or 'mail_pw' are not configured.
@@ -337,7 +428,7 @@ class Account(object):
"""
if not self.is_configured():
self.configure()
self._threads.start()
self._threads.start(mvbox=mvbox, sentbox=sentbox)
def stop_threads(self, wait=True):
""" stop IMAP/SMTP threads. """
@@ -347,7 +438,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)
@@ -364,7 +456,12 @@ class Account(object):
def on_dc_event_imex_progress(self, data1, data2):
if data1 == 1000:
self._imex_completed.set()
self._imex_events.put(True)
elif data1 == 0:
self._imex_events.put(False)
def on_dc_event_imex_file_written(self, data1, data2):
self._imex_events.put(data1)
class IOThreads:
@@ -377,10 +474,14 @@ class IOThreads:
def is_started(self):
return len(self._name2thread) > 0
def start(self, imap=True, smtp=True):
def start(self, imap=True, smtp=True, mvbox=False, sentbox=False):
assert not self.is_started()
if imap:
self._start_one_thread("imap", self.imap_thread_run)
self._start_one_thread("inbox", self.imap_thread_run)
if mvbox:
self._start_one_thread("mvbox", self.mvbox_thread_run)
if sentbox:
self._start_one_thread("sentbox", self.sentbox_thread_run)
if smtp:
self._start_one_thread("smtp", self.smtp_thread_run)
@@ -393,17 +494,35 @@ class IOThreads:
self._thread_quitflag = True
lib.dc_interrupt_imap_idle(self._dc_context)
lib.dc_interrupt_smtp_idle(self._dc_context)
lib.dc_interrupt_mvbox_idle(self._dc_context)
lib.dc_interrupt_sentbox_idle(self._dc_context)
if wait:
for name, thread in self._name2thread.items():
thread.join()
def imap_thread_run(self):
self._log_event("py-bindings-info", 0, "IMAP THREAD START")
self._log_event("py-bindings-info", 0, "INBOX THREAD START")
while not self._thread_quitflag:
lib.dc_perform_imap_jobs(self._dc_context)
lib.dc_perform_imap_fetch(self._dc_context)
lib.dc_perform_imap_idle(self._dc_context)
self._log_event("py-bindings-info", 0, "IMAP THREAD FINISHED")
self._log_event("py-bindings-info", 0, "INBOX THREAD FINISHED")
def mvbox_thread_run(self):
self._log_event("py-bindings-info", 0, "MVBOX THREAD START")
while not self._thread_quitflag:
lib.dc_perform_mvbox_jobs(self._dc_context)
lib.dc_perform_mvbox_fetch(self._dc_context)
lib.dc_perform_mvbox_idle(self._dc_context)
self._log_event("py-bindings-info", 0, "MVBOX THREAD FINISHED")
def sentbox_thread_run(self):
self._log_event("py-bindings-info", 0, "SENTBOX THREAD START")
while not self._thread_quitflag:
lib.dc_perform_sentbox_jobs(self._dc_context)
lib.dc_perform_sentbox_fetch(self._dc_context)
lib.dc_perform_sentbox_idle(self._dc_context)
self._log_event("py-bindings-info", 0, "SENTBOX THREAD FINISHED")
def smtp_thread_run(self):
self._log_event("py-bindings-info", 0, "SMTP THREAD START")
@@ -433,6 +552,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 +615,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
@@ -108,6 +109,13 @@ class Chat(object):
"""
return not lib.dc_chat_is_unpromoted(self._dc_chat)
def is_verified(self):
""" return True if this chat is a verified group.
:returns: True if chat is verified, False otherwise.
"""
return lib.dc_chat_is_verified(self._dc_chat)
def get_name(self):
""" return name of this chat.
@@ -131,6 +139,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 +312,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 +322,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

@@ -8,11 +8,23 @@ from os.path import join as joinpath
# this works well when you in a git-checkout
# run "python deltachat/const.py" to regenerate events
# begin const generated
DC_PROVIDER_STATUS_OK = 1
DC_PROVIDER_STATUS_PREPARATION = 2
DC_PROVIDER_STATUS_BROKEN = 3
DC_GCL_ARCHIVED_ONLY = 0x01
DC_GCL_NO_SPECIALS = 0x02
DC_GCL_ADD_ALLDONE_HINT = 0x04
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 +81,14 @@ 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_|'
r'DC_CONTACT_ID_|DC_GCL|DC_CHAT|DC_PROVIDER)\S+)\s+([x\d]+).*')
for line in f:
m = rex.match(line)
if m:
@@ -90,7 +101,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

@@ -101,6 +101,14 @@ class Message(object):
""" return True if this message is a setup message. """
return lib.dc_msg_is_setupmessage(self._dc_msg)
def get_setupcodebegin(self):
""" return the first characters of a setup code in a setup message. """
return from_dc_charpointer(lib.dc_msg_get_setupcodebegin(self._dc_msg))
def is_encrypted(self):
""" return True if this message was encrypted. """
return bool(lib.dc_msg_get_showpadlock(self._dc_msg))
def get_message_info(self):
""" Return informational text for a single message.
@@ -110,7 +118,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

@@ -0,0 +1,67 @@
"""Provider info class."""
from .capi import ffi, lib
from .cutil import as_dc_charpointer, from_dc_charpointer
class ProviderNotFoundError(Exception):
"""The provider information was not found."""
class Provider(object):
"""Provider information.
:param domain: The domain to get the provider info for, this is
normally the part following the `@` of the domain.
"""
def __init__(self, domain):
provider = ffi.gc(
lib.dc_provider_new_from_domain(as_dc_charpointer(domain)),
lib.dc_provider_unref,
)
if provider == ffi.NULL:
raise ProviderNotFoundError("Provider not found")
self._provider = provider
@classmethod
def from_email(cls, email):
"""Create provider info from an email address.
:param email: Email address to get provider info for.
"""
return cls(email.split('@')[-1])
@property
def overview_page(self):
"""URL to the overview page of the provider on providers.delta.chat."""
return from_dc_charpointer(
lib.dc_provider_get_overview_page(self._provider))
@property
def name(self):
"""The name of the provider."""
return from_dc_charpointer(lib.dc_provider_get_name(self._provider))
@property
def markdown(self):
"""Content of the information page, formatted as markdown."""
return from_dc_charpointer(
lib.dc_provider_get_markdown(self._provider))
@property
def status_date(self):
"""The date the provider info was last updated, as a string."""
return from_dc_charpointer(
lib.dc_provider_get_status_date(self._provider))
@property
def status(self):
"""The status of the provider information.
This is one of the
:attr:`deltachat.const.DC_PROVIDER_STATUS_OK`,
:attr:`deltachat.const.DC_PROVIDER_STATUS_PREPARATION` or
:attr:`deltachat.const.DC_PROVIDER_STATUS_BROKEN` constants.
"""
return lib.dc_provider_get_status(self._provider)

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):
@@ -157,7 +150,7 @@ def acfactory(pytestconfig, tmpdir, request, session_liveconfig):
lib.dc_set_config(ac._dc_context, b"configured", b"1")
return ac
def get_online_configuring_account(self):
def get_online_config(self):
if not session_liveconfig:
pytest.skip("specify DCC_PY_LIVECONFIG or --liveconfig")
configdict = session_liveconfig.get(self.live_count)
@@ -165,26 +158,39 @@ 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)
return ac, dict(configdict)
def get_online_configuring_account(self, mvbox=False, sentbox=False):
ac, configdict = self.get_online_config()
ac.configure(**configdict)
ac.start_threads()
self._finalizers.append(ac.shutdown)
ac.start_threads(mvbox=mvbox, sentbox=sentbox)
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
@@ -204,12 +210,22 @@ def lp():
return Printer()
def wait_configuration_progress(account, target):
def wait_configuration_progress(account, min_target, max_target=1001):
min_target = min(min_target, max_target)
while 1:
evt_name, data1, data2 = \
account._evlogger.get_matching("DC_EVENT_CONFIGURE_PROGRESS")
if data1 >= target:
print("** CONFIG PROGRESS {}".format(target), account)
if data1 >= min_target and data1 <= max_target:
print("** CONFIG PROGRESS {}".format(min_target), account)
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

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:
@@ -93,8 +93,9 @@ class TestOfflineContact:
ac1 = acfactory.get_configured_offline_account()
contact1 = ac1.create_contact(email="some1@example.com", name="some1")
chat = ac1.create_chat_by_contact(contact1)
chat.send_text("one messae")
msg = chat.send_text("one messae")
assert not ac1.delete_contact(contact1)
assert not msg.filemime
class TestOfflineChat:
@@ -106,7 +107,7 @@ class TestOfflineChat:
def chat1(self, ac1):
contact1 = ac1.create_contact("some1@hello.com", name="some1")
chat = ac1.create_chat_by_contact(contact1)
assert chat.id >= const.DC_CHAT_ID_LAST_SPECIAL, chat.id
assert chat.id > const.DC_CHAT_ID_LAST_SPECIAL, chat.id
return chat
def test_display(self, chat1):
@@ -140,6 +141,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")
@@ -277,10 +294,10 @@ class TestOfflineChat:
assert contact == ac1.get_self_contact()
assert not backupdir.listdir()
path = ac1.export_to_dir(backupdir.strpath)
path = ac1.export_all(backupdir.strpath)
assert os.path.exists(path)
ac2 = acfactory.get_unconfigured_account()
ac2.import_from_file(path)
ac2.import_all(path)
contacts = ac2.get_contacts(query="some1")
assert len(contacts) == 1
contact2 = contacts[0]
@@ -306,18 +323,39 @@ 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_export_import_self_keys(self, acfactory, tmpdir):
ac1, ac2 = acfactory.get_two_online_accounts()
dir = tmpdir.mkdir("exportdir")
export_files = ac1.export_self_keys(dir.strpath)
assert len(export_files) == 2
for x in export_files:
assert x.startswith(dir.strpath)
ac1._evlogger.consume_events()
ac1.import_self_keys(dir.strpath)
def test_one_account_send(self, acfactory):
ac1 = acfactory.get_online_configuring_account()
c2 = ac1.create_contact(email=ac1.get_config("addr"))
chat = ac1.create_chat_by_contact(c2)
assert chat.id >= const.DC_CHAT_ID_LAST_SPECIAL
assert chat.id > const.DC_CHAT_ID_LAST_SPECIAL
wait_successful_IMAP_SMTP_connection(ac1)
wait_configuration_progress(ac1, 1000)
@@ -326,35 +364,19 @@ class TestOnlineAccount:
ev = ac1._evlogger.get_matching("DC_EVENT_INCOMING_MSG|DC_EVENT_MSGS_CHANGED")
assert ev[1] == msg_out.id
def test_two_accounts_send_receive(self, acfactory):
ac1 = acfactory.get_online_configuring_account()
def test_mvbox_sentbox_threads(self, acfactory):
ac1 = acfactory.get_online_configuring_account(mvbox=True, sentbox=True)
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)
msg_out = chat.send_text("message1")
# wait for other account to receive
wait_configuration_progress(ac1, 1000)
chat = self.get_chat(ac1, ac2)
chat.send_text("message1")
ev = ac2._evlogger.get_matching("DC_EVENT_INCOMING_MSG|DC_EVENT_MSGS_CHANGED")
assert ev[2] == msg_out.id
msg_in = ac2.get_message_by_id(msg_out.id)
assert msg_in.text == "message1"
assert ev[2] > const.DC_CHAT_ID_LAST_SPECIAL
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")
@@ -377,16 +399,11 @@ class TestOnlineAccount:
ac2.delete_messages(messages)
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
def test_send_and_receive_message_markseen(self, acfactory, lp):
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")
@@ -422,24 +439,20 @@ class TestOnlineAccount:
ac2.mark_seen_messages([msg_in])
lp.step("1")
ev = ac1._evlogger.get_matching("DC_EVENT_MSG_READ")
assert ev[1] >= const.DC_CHAT_ID_LAST_SPECIAL
assert ev[2] >= const.DC_MSG_ID_LAST_SPECIAL
assert ev[1] > const.DC_CHAT_ID_LAST_SPECIAL
assert ev[2] > const.DC_MSG_ID_LAST_SPECIAL
lp.step("2")
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")
assert not msg_out.is_encrypted()
lp.sec("wait for ac2 to receive message")
ev = ac2._evlogger.get_matching("DC_EVENT_MSGS_CHANGED")
@@ -457,16 +470,15 @@ class TestOnlineAccount:
assert ev[2] > msg_out.id
msg_back = ac1.get_message_by_id(ev[2])
assert msg_back.text == "message-back"
assert msg_back.is_encrypted()
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 +492,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")
@@ -506,19 +512,19 @@ class TestOnlineAccount:
assert os.path.exists(msg_in.filename)
assert os.stat(msg_in.filename).st_size == os.stat(path).st_size
def test_import_export_online(self, acfactory, tmpdir):
backupdir = tmpdir.mkdir("backup")
def test_import_export_online_all(self, acfactory, tmpdir):
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")
path = ac1.export_to_dir(backupdir.strpath)
backupdir = tmpdir.mkdir("backup")
path = ac1.export_all(backupdir.strpath)
assert os.path.exists(path)
ac2 = acfactory.get_unconfigured_account()
ac2.import_from_file(path)
ac2.import_all(path)
contacts = ac2.get_contacts(query="some1")
assert len(contacts) == 1
contact2 = contacts[0]
@@ -528,7 +534,7 @@ class TestOnlineAccount:
assert len(messages) == 1
assert messages[0].text == "msg1"
def test_ac_setup_message(self, acfactory):
def test_ac_setup_message(self, acfactory, lp):
# note that the receiving account needs to be configured and running
# before ther setup message is send. DC does not read old messages
# as of Jul2019
@@ -536,13 +542,152 @@ class TestOnlineAccount:
ac2 = acfactory.clone_online_account(ac1)
wait_configuration_progress(ac2, 1000)
wait_configuration_progress(ac1, 1000)
lp.sec("trigger ac setup message and return setupcode")
assert ac1.get_info()["fingerprint"] != ac2.get_info()["fingerprint"]
setup_code = ac1.initiate_key_transfer()
ac2._evlogger.set_timeout(30)
ev = ac2._evlogger.get_matching("DC_EVENT_INCOMING_MSG|DC_EVENT_MSGS_CHANGED")
msg = ac2.get_message_by_id(ev[2])
assert msg.is_setup_message()
assert msg.get_setupcodebegin() == setup_code[:2]
lp.sec("try a bad setup code")
with pytest.raises(ValueError):
msg.continue_key_transfer(str(reversed(setup_code)))
lp.sec("try a good setup code")
print("*************** Incoming ASM File at: ", msg.filename)
print("*************** Setup Code: ", setup_code)
msg.continue_key_transfer(setup_code)
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_qr_verified_group_and_chatting(self, acfactory, lp):
ac1, ac2 = acfactory.get_two_online_accounts()
lp.sec("ac1: create verified-group QR, ac2 scans and joins")
chat1 = ac1.create_group_chat("hello", verified=True)
assert chat1.is_verified()
qr = chat1.get_join_qr()
lp.sec("ac2: start QR-code based join-group protocol")
chat2 = ac2.qr_join_chat(qr)
assert chat2.id >= 10
wait_securejoin_inviter_progress(ac1, 1000)
lp.sec("ac2: read member added message")
msg = ac2.wait_next_incoming_message()
assert msg.is_encrypted()
assert "added" in msg.text.lower()
lp.sec("ac1: send message")
msg_out = chat1.send_text("hello")
assert msg_out.is_encrypted()
lp.sec("ac2: read message and check it's verified chat")
msg = ac2.wait_next_incoming_message()
assert msg.text == "hello"
assert msg.chat.is_verified()
assert msg.is_encrypted()
lp.sec("ac2: send message and let ac1 read it")
chat2.send_text("world")
msg = ac1.wait_next_incoming_message()
assert msg.text == "world"
assert msg.is_encrypted()
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
class TestOnlineConfigureFails:
def test_invalid_password(self, acfactory):
ac1, configdict = acfactory.get_online_config()
ac1.configure(addr=configdict["addr"], mail_pw="123")
ac1.start_threads()
wait_configuration_progress(ac1, 500)
ev1 = ac1._evlogger.get_matching("DC_EVENT_ERROR_NETWORK")
assert "authentication failed" in ev1[2].lower()
wait_configuration_progress(ac1, 0, 0)
def test_invalid_user(self, acfactory):
ac1, configdict = acfactory.get_online_config()
ac1.configure(addr="x" + configdict["addr"], mail_pw=configdict["mail_pw"])
ac1.start_threads()
wait_configuration_progress(ac1, 500)
ev1 = ac1._evlogger.get_matching("DC_EVENT_ERROR_NETWORK")
assert "authentication failed" in ev1[2].lower()
wait_configuration_progress(ac1, 0, 0)
def test_invalid_domain(self, acfactory):
ac1, configdict = acfactory.get_online_config()
ac1.configure(addr=configdict["addr"] + "x", mail_pw=configdict["mail_pw"])
ac1.start_threads()
wait_configuration_progress(ac1, 500)
ev1 = ac1._evlogger.get_matching("DC_EVENT_ERROR_NETWORK")
assert "could not connect" in ev1[2].lower()
wait_configuration_progress(ac1, 0, 0)

View File

@@ -4,7 +4,7 @@ from deltachat import const
from conftest import wait_configuration_progress, wait_msgs_changed
class TestInCreation:
class TestOnlineInCreation:
def test_forward_increation(self, acfactory, data, lp):
ac1 = acfactory.get_online_configuring_account()
ac2 = acfactory.get_online_configuring_account()

View File

@@ -1,5 +1,5 @@
from __future__ import print_function
from deltachat import capi, const, set_context_callback, clear_context_callback
from deltachat import capi, cutil, 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):
@@ -51,6 +59,16 @@ def test_wrong_db(tmpdir):
assert not lib.dc_open(dc_context, p.strpath.encode("ascii"), ffi.NULL)
def test_empty_blobdir(tmpdir):
# Apparently some client code expects this to be the same as passing NULL.
ctx = ffi.gc(
lib.dc_context_new(lib.py_dc_callback, ffi.NULL, ffi.NULL),
lib.dc_context_unref,
)
db_fname = tmpdir.join("hello.db")
assert lib.dc_open(ctx, db_fname.strpath.encode("ascii"), b"")
def test_event_defines():
assert const.DC_EVENT_INFO == 100
assert const.DC_CONTACT_ID_SELF
@@ -75,3 +93,58 @@ 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():
provider = lib.dc_provider_new_from_email(cutil.as_dc_charpointer("ex@example.com"))
assert cutil.from_dc_charpointer(
lib.dc_provider_get_overview_page(provider)
) == "https://providers.delta.chat/example.com"
assert cutil.from_dc_charpointer(lib.dc_provider_get_name(provider)) == "Example"
assert cutil.from_dc_charpointer(lib.dc_provider_get_markdown(provider)) == "\n..."
assert cutil.from_dc_charpointer(lib.dc_provider_get_status_date(provider)) == "2018-09"
assert lib.dc_provider_get_status(provider) == const.DC_PROVIDER_STATUS_PREPARATION
def test_provider_info_none():
assert lib.dc_provider_new_from_email(cutil.as_dc_charpointer("email@unexistent.no")) == ffi.NULL
def test_get_info_closed():
ctx = ffi.gc(
lib.dc_context_new(lib.py_dc_callback, ffi.NULL, ffi.NULL),
lib.dc_context_unref,
)
info = cutil.from_dc_charpointer(lib.dc_get_info(ctx))
assert 'deltachat_core_version' in info
assert 'database_dir' not in info
def test_get_info_open(tmpdir):
ctx = ffi.gc(
lib.dc_context_new(lib.py_dc_callback, ffi.NULL, ffi.NULL),
lib.dc_context_unref,
)
db_fname = tmpdir.join("test.db")
lib.dc_open(ctx, db_fname.strpath.encode("ascii"), ffi.NULL)
info = cutil.from_dc_charpointer(lib.dc_get_info(ctx))
assert 'deltachat_core_version' in info
assert 'database_dir' in info
def test_is_open_closed():
ctx = ffi.gc(
lib.dc_context_new(lib.py_dc_callback, ffi.NULL, ffi.NULL),
lib.dc_context_unref,
)
assert lib.dc_is_open(ctx) == 0
def test_is_open_actually_open(tmpdir):
ctx = ffi.gc(
lib.dc_context_new(lib.py_dc_callback, ffi.NULL, ffi.NULL),
lib.dc_context_unref,
)
db_fname = tmpdir.join("test.db")
lib.dc_open(ctx, db_fname.strpath.encode("ascii"), ffi.NULL)
assert lib.dc_is_open(ctx) == 1

View File

@@ -0,0 +1,27 @@
import pytest
from deltachat import const
from deltachat import provider
def test_provider_info_from_email():
example = provider.Provider.from_email("email@example.com")
assert example.overview_page == "https://providers.delta.chat/example.com"
assert example.name == "Example"
assert example.markdown == "\n..."
assert example.status_date == "2018-09"
assert example.status == const.DC_PROVIDER_STATUS_PREPARATION
def test_provider_info_from_domain():
example = provider.Provider("example.com")
assert example.overview_page == "https://providers.delta.chat/example.com"
assert example.name == "Example"
assert example.markdown == "\n..."
assert example.status_date == "2018-09"
assert example.status == const.DC_PROVIDER_STATUS_PREPARATION
def test_provider_info_none():
with pytest.raises(provider.ProviderNotFoundError):
provider.Provider.from_email("email@unexistent.no")

View File

@@ -1,7 +1,6 @@
[tox]
# make sure to update environment list in travis.yml and appveyor.yml
envlist =
py27
py35
lint
auditwheels
@@ -17,12 +16,15 @@ passenv =
DCC_PY_LIVECONFIG
deps =
pytest
pytest-faulthandler
pytest-rerunfailures
pytest-timeout
pytest-xdist
pdbpp
requests
[testenv:auditwheels]
skipsdist = True
deps = auditwheel
commands =
python tests/auditwheels.py {toxworkdir}/wheelhouse
@@ -43,7 +45,7 @@ commands =
[testenv:doc]
basepython = python3.5
deps =
sphinx==2.0.1
sphinx==2.2.0
breathe
changedir = doc
@@ -52,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

373
spec.md Normal file
View File

@@ -0,0 +1,373 @@
# 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 of 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 `-`
and MUST have a length of at least 11 characters.
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: 12345uvwxyZ
Chat-Group-Name: My Group
Message-ID: Gr.12345uvwxyZ.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: 12345uvwxyZ
Chat-Group-Name: My Group
Chat-Group-Member-Added: member4@domain
Message-ID: Gr.12345uvwxyZ.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: 12345uvwxyZ
Chat-Group-Name: My Group
Chat-Group-Member-Removed: member4@domain
Message-ID: Gr.12345uvwxyZ.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: 12345uvwxyZ
Chat-Group-Name: Our Group
Chat-Group-Name-Changed: My Group
Message-ID: Gr.12345uvwxyZ.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: 12345uvwxyZ
Chat-Group-Name: Our Group
Chat-Group-Image: image.jpg
Message-ID: Gr.12345uvwxyZ.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

@@ -3,11 +3,10 @@ use std::ffi::CStr;
use std::str::FromStr;
use std::{fmt, str};
use mmime::mailimf_types::*;
use mmime::mailimf::types::*;
use crate::constants::*;
use crate::contact::*;
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

@@ -4,7 +4,7 @@ use crate::contact::*;
use crate::context::*;
use crate::error::Result;
use crate::lot::Lot;
use crate::message::*;
use crate::message::Message;
use crate::stock::StockMessage;
/// An object representing a single chatlist in memory.
@@ -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) = Message::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

@@ -33,6 +33,7 @@ pub enum Config {
E2eeEnabled,
#[strum(props(default = "1"))]
MdnsEnabled,
#[strum(props(default = "1"))]
InboxWatch,
#[strum(props(default = "1"))]
SentboxWatch,
@@ -72,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,10 +1,11 @@
use libc::free;
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::x::*;
use crate::login_param::LoginParam;
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,11 +1,13 @@
use std::ptr;
use libc::free;
use quick_xml;
use quick_xml::events::{BytesEnd, BytesStart, BytesText};
use crate::constants::*;
use crate::context::Context;
use crate::dc_loginparam::*;
use crate::dc_tools::*;
use crate::x::*;
use std::ptr;
use crate::login_param::LoginParam;
use super::read_autoconf_file;
/* ******************************************************************************
@@ -13,8 +15,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 +27,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,
@@ -45,12 +47,12 @@ pub unsafe fn outlk_autodiscover(
ok_to_continue = true;
break;
}
memset(
libc::memset(
&mut outlk_ad as *mut outlk_autodiscover_t as *mut libc::c_void,
0,
::std::mem::size_of::<outlk_autodiscover_t>(),
);
xml_raw = read_autoconf_file(context, url);
xml_raw = read_autoconf_file(context, as_str(url));
if xml_raw.is_null() {
ok_to_continue = false;
break;
@@ -75,7 +77,6 @@ pub unsafe fn outlk_autodiscover(
Err(e) => {
error!(
context,
0,
"Configure xml: Error at position {}: {:?}",
reader.buffer_position(),
e
@@ -108,8 +109,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 +169,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 +183,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
}

File diff suppressed because it is too large Load Diff

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,17 +57,12 @@ 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
const DC_IMEX_EXPORT_SELF_KEYS: usize = 1;
/// param1 is a directory where the keys are searched in and read from
const DC_IMEX_IMPORT_SELF_KEYS: usize = 2;
/// param1 is a directory where the backup is written to
const DC_IMEX_EXPORT_BACKUP: usize = 11;
/// param1 is the file with the backup to import
const DC_IMEX_IMPORT_BACKUP: usize = 12;
// values for DC_PARAM_FORCE_PLAINTEXT
pub(crate) const DC_FP_NO_AUTOCRYPT_HEADER: i32 = 2;
pub(crate) const DC_FP_ADD_AUTOCRYPT_HEADER: i32 = 1;
/// virtual chat showing all messages belonging to chats flagged with chats.blocked=2
pub(crate) const DC_CHAT_ID_DEADDROP: u32 = 1;
@@ -71,7 +71,7 @@ pub const DC_CHAT_ID_TRASH: u32 = 3;
/// a message is just in creation but not yet assigned to a chat (eg. we may need the message ID to set up blobs; this avoids unready message to be sent and shown)
const DC_CHAT_ID_MSGS_IN_CREATION: u32 = 4;
/// virtual chat showing all messages flagged with msgs.starred=2
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
@@ -117,7 +117,7 @@ const DC_MAX_GET_INFO_LEN: usize = 100000;
pub const DC_CONTACT_ID_UNDEFINED: u32 = 0;
pub const DC_CONTACT_ID_SELF: u32 = 1;
const DC_CONTACT_ID_DEVICE: u32 = 2;
pub const DC_CONTACT_ID_DEVICE: u32 = 2;
pub const DC_CONTACT_ID_LAST_SPECIAL: u32 = 9;
pub const DC_CREATE_MVBOX: usize = 1;
@@ -134,7 +134,7 @@ pub const DC_LP_AUTH_OAUTH2: usize = 0x2;
/// Force NORMAL authorization, this is the default.
/// If this flag is set, automatic configuration is skipped.
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 +142,7 @@ pub const DC_LP_IMAP_SOCKET_STARTTLS: usize = 0x100;
/// Connect to IMAP via SSL.
/// If this flag is set, automatic configuration is skipped.
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 +154,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 +220,12 @@ pub enum Viewtype {
File = 60,
}
impl Default for Viewtype {
fn default() -> Self {
Viewtype::Unknown
}
}
#[cfg(test)]
mod tests {
use super::*;
@@ -229,242 +241,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 +309,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_e2ee::*;
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);
@@ -603,7 +588,7 @@ impl<'a> Contact<'a> {
});
ret += &p;
if self_key.is_none() {
dc_ensure_secret_key_exists(context)?;
e2ee::ensure_secret_key_exists(context)?;
self_key = Key::from_self_public(context, &loginparam.addr, &context.sql);
}
let p = context.stock_str(StockMessage::FingerPrints);
@@ -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 {
@@ -815,14 +804,14 @@ impl<'a> Contact<'a> {
}
if let Some(peerstate) = peerstate {
if peerstate.verified_key().is_some() {
if peerstate.verified_key.is_some() {
return VerifiedStatus::BidirectVerified;
}
}
let peerstate = Peerstate::from_addr(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() {
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);
}
}
}

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

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
}

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

View File

@@ -1,44 +0,0 @@
use crate::constants::*;
use crate::context::*;
use crate::job::*;
use crate::message::*;
use crate::param::Params;
pub unsafe fn dc_do_heuristics_moves(context: &Context, folder: &str, msg_id: u32) {
if context
.sql
.get_config_int(context, "mvbox_move")
.unwrap_or_else(|| 1)
== 0
{
return;
}
if !dc_is_inbox(context, folder) && !dc_is_sentbox(context, folder) {
return;
}
if let Ok(msg) = dc_msg_new_load(context, msg_id) {
if dc_msg_is_setupmessage(&msg) {
// do not move setup messages;
// there may be a non-delta device that wants to handle it
return;
}
if dc_is_mvbox(context, folder) {
dc_update_msg_move_state(context, msg.rfc724_mid, MoveState::Stay);
}
// 1 = dc message, 2 = reply to dc message
if 0 != msg.is_dc_message {
job_add(
context,
Action::MoveMsg,
msg.id as libc::c_int,
Params::new(),
0,
);
dc_update_msg_move_state(context, msg.rfc724_mid, MoveState::Moving);
}
}
}

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() {

File diff suppressed because it is too large Load Diff

View File

@@ -1,65 +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: *const libc::c_char,
) -> bool {
if token.is_null() {
return false;
}
// foreign_id may be 0
sql::execute(
context,
&context.sql,
"INSERT INTO tokens (namespc, foreign_id, token, timestamp) VALUES (?, ?, ?, ?);",
params![namespc as i32, foreign_id as i32, as_str(token), time()],
)
.is_ok()
}
pub fn dc_token_lookup(
context: &Context,
namespc: dc_tokennamespc_t,
foreign_id: u32,
) -> *mut libc::c_char {
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,
)
.map(|s| unsafe { s.strdup() })
.unwrap_or_else(|| std::ptr::null_mut())
}
pub fn dc_token_exists(
context: &Context,
namespc: dc_tokennamespc_t,
token: *const libc::c_char,
) -> bool {
if token.is_null() {
return false;
}
context
.sql
.exists(
"SELECT id FROM tokens WHERE namespc=? AND token=?;",
params![namespc as i32, as_str(token)],
)
.unwrap_or_default()
}

File diff suppressed because it is too large Load Diff

861
src/e2ee.rs Normal file
View File

@@ -0,0 +1,861 @@
//! End-to-end encryption support.
use std::collections::HashSet;
use std::ptr;
use std::str::FromStr;
use libc::strlen;
use mmime::clist::*;
use mmime::mailimf::types::*;
use mmime::mailimf::types_helper::*;
use mmime::mailimf::*;
use mmime::mailmime::content::*;
use mmime::mailmime::types::*;
use mmime::mailmime::types_helper::*;
use mmime::mailmime::write_mem::*;
use mmime::mailmime::*;
use mmime::mailprivacy_prepare_mime;
use mmime::mmapstring::*;
use mmime::{mailmime_substitute, MAILIMF_NO_ERROR, MAIL_NO_ERROR};
use num_traits::FromPrimitive;
use crate::aheader::*;
use crate::config::Config;
use crate::context::Context;
use crate::dc_mimeparser::*;
use crate::dc_tools::*;
use crate::error::*;
use crate::key::*;
use crate::keyring::*;
use crate::mimefactory::MimeFactory;
use crate::peerstate::*;
use crate::pgp::*;
use crate::securejoin::handle_degrade_event;
use crate::wrapmime;
use crate::wrapmime::*;
// standard mime-version header aka b"Version: 1\r\n\x00"
static mut VERSION_CONTENT: [libc::c_char; 13] =
[86, 101, 114, 115, 105, 111, 110, 58, 32, 49, 13, 10, 0];
#[derive(Debug)]
pub struct EncryptHelper {
pub prefer_encrypt: EncryptPreference,
pub addr: String,
pub public_key: Key,
}
impl EncryptHelper {
pub fn new(context: &Context) -> Result<EncryptHelper> {
let prefer_encrypt = context
.sql
.get_config_int(&context, "e2ee_enabled")
.and_then(EncryptPreference::from_i32)
.unwrap_or_default();
let addr = match context.get_config(Config::ConfiguredAddr) {
None => {
bail!("addr not configured!");
}
Some(addr) => addr,
};
let public_key = load_or_generate_self_public_key(context, &addr)?;
Ok(EncryptHelper {
prefer_encrypt,
addr,
public_key,
})
}
pub fn get_aheader(&self) -> Aheader {
let pk = self.public_key.clone();
let addr = self.addr.to_string();
Aheader::new(addr, pk, self.prefer_encrypt)
}
pub fn try_encrypt(
&mut self,
factory: &mut MimeFactory,
e2ee_guaranteed: bool,
min_verified: libc::c_int,
do_gossip: bool,
mut in_out_message: *mut Mailmime,
imffields_unprotected: *mut mailimf_fields,
) -> Result<bool> {
// libEtPan's pgp_encrypt_mime() takes the parent as the new root.
// We just expect the root as being given to this function.
ensure!(
!in_out_message.is_null() && unsafe { (*in_out_message).mm_parent.is_null() },
"corrupted inputs"
);
if !(self.prefer_encrypt == EncryptPreference::Mutual || e2ee_guaranteed) {
return Ok(false);
}
let context = &factory.context;
let mut keyring = Keyring::default();
let mut gossip_headers: Vec<String> = Vec::with_capacity(factory.recipients_addr.len());
// determine if we can and should encrypt
for recipient_addr in factory.recipients_addr.iter() {
if recipient_addr == &self.addr {
continue;
}
let peerstate = match Peerstate::from_addr(context, &context.sql, recipient_addr) {
Some(peerstate) => peerstate,
None => {
let msg = format!("peerstate for {} missing, cannot encrypt", recipient_addr);
if e2ee_guaranteed {
return Err(format_err!("{}", msg));
} else {
info!(context, "{}", msg);
return Ok(false);
}
}
};
if peerstate.prefer_encrypt != EncryptPreference::Mutual && !e2ee_guaranteed {
info!(context, "peerstate for {} is no-encrypt", recipient_addr);
return Ok(false);
}
if let Some(key) = peerstate.peek_key(min_verified as usize) {
keyring.add_owned(key.clone());
if do_gossip {
if let Some(header) = peerstate.render_gossip_header(min_verified as usize) {
gossip_headers.push(header.to_string());
}
}
} else {
bail!(
"proper enc-key for {} missing, cannot encrypt",
recipient_addr
);
}
}
let sign_key = {
keyring.add_ref(&self.public_key);
let key = Key::from_self_private(context, self.addr.clone(), &context.sql);
ensure!(key.is_some(), "no own private key found");
key
};
// encrypt message
unsafe {
mailprivacy_prepare_mime(in_out_message);
let mut part_to_encrypt = (*in_out_message).mm_data.mm_message.mm_msg_mime;
(*part_to_encrypt).mm_parent = ptr::null_mut();
let imffields_encrypted = mailimf_fields_new_empty();
// mailmime_new_message_data() calls mailmime_fields_new_with_version()
// which would add the unwanted MIME-Version:-header
let message_to_encrypt = mailmime_new_simple(
MAILMIME_MESSAGE as libc::c_int,
mailmime_fields_new_empty(),
mailmime_get_content_message(),
imffields_encrypted,
part_to_encrypt,
);
for header in &gossip_headers {
wrapmime::new_custom_field(imffields_encrypted, "Autocrypt-Gossip", &header)
}
// memoryhole headers: move some headers into encrypted part
// XXX note we can't use clist's into_iter() because the loop body also removes items
let mut cur = (*(*imffields_unprotected).fld_list).first;
while !cur.is_null() {
let field = (*cur).data as *mut mailimf_field;
let mut move_to_encrypted = false;
if !field.is_null() {
if (*field).fld_type == MAILIMF_FIELD_SUBJECT as libc::c_int {
move_to_encrypted = true;
} else if (*field).fld_type == MAILIMF_FIELD_OPTIONAL_FIELD as libc::c_int {
let opt_field = (*field).fld_data.fld_optional_field;
if !opt_field.is_null() && !(*opt_field).fld_name.is_null() {
let fld_name = to_string_lossy((*opt_field).fld_name);
if fld_name.starts_with("Secure-Join")
|| (fld_name.starts_with("Chat-") && fld_name != "Chat-Version")
{
move_to_encrypted = true;
}
}
}
}
if move_to_encrypted {
mailimf_fields_add(imffields_encrypted, field);
cur = clist_delete((*imffields_unprotected).fld_list, cur);
} else {
cur = (*cur).next;
}
}
let subject = mailimf_subject_new("...".strdup());
mailimf_fields_add(imffields_unprotected, mailimf_field_new_subject(subject));
wrapmime::append_ct_param(
(*part_to_encrypt).mm_content_type,
"protected-headers",
"v1",
)?;
let plain = mmap_string_new(b"\x00" as *const u8 as *const libc::c_char);
let mut col = 0;
mailmime_write_mem(plain, &mut col, message_to_encrypt);
mailmime_free(message_to_encrypt);
ensure!(
!(*plain).str_0.is_null() && (*plain).len > 0,
"could not write/allocate"
);
let ctext = dc_pgp_pk_encrypt(
std::slice::from_raw_parts((*plain).str_0 as *const u8, (*plain).len),
&keyring,
sign_key.as_ref(),
);
mmap_string_free(plain);
let ctext_v = ctext?;
// create MIME-structure that will contain the encrypted text
let mut encrypted_part = new_data_part(
ptr::null_mut(),
0 as libc::size_t,
"multipart/encrypted",
MAILMIME_MECHANISM_BASE64,
)?;
let content = (*encrypted_part).mm_content_type;
wrapmime::append_ct_param(content, "protocol", "application/pgp-encrypted")?;
let version_mime = new_data_part(
VERSION_CONTENT.as_mut_ptr() as *mut libc::c_void,
strlen(VERSION_CONTENT.as_mut_ptr()),
"application/pgp-encrypted",
MAILMIME_MECHANISM_7BIT,
)?;
mailmime_smart_add_part(encrypted_part, version_mime);
// we assume that ctext_v is not dropped until the end
// of this if-scope
let ctext_part = new_data_part(
ctext_v.as_ptr() as *mut libc::c_void,
ctext_v.len(),
"application/octet-stream",
MAILMIME_MECHANISM_7BIT,
)?;
mailmime_smart_add_part(encrypted_part, ctext_part);
(*in_out_message).mm_data.mm_message.mm_msg_mime = encrypted_part;
(*encrypted_part).mm_parent = in_out_message;
let gossiped = !&gossip_headers.is_empty();
factory.finalize_mime_message(in_out_message, true, gossiped)?;
Ok(true)
}
}
}
pub fn try_decrypt(
context: &Context,
in_out_message: *mut Mailmime,
) -> Result<(bool, HashSet<String>, HashSet<String>)> {
let mut encrypted = false;
let mut signatures = HashSet::default();
let mut gossipped_addr = HashSet::default();
// just a pointer into mailmime structure, must not be freed
let imffields = unsafe { mailmime_find_mailimf_fields(in_out_message) };
let mut message_time = 0;
let mut from = None;
let mut private_keyring = Keyring::default();
let mut public_keyring_for_validate = Keyring::default();
let mut gossip_headers = ptr::null_mut();
// XXX do wrapmime:: helper for the next block
if !(in_out_message.is_null() || imffields.is_null()) {
let mut field = mailimf_find_field(imffields, MAILIMF_FIELD_FROM as libc::c_int);
if !field.is_null() && unsafe { !(*field).fld_data.fld_from.is_null() } {
let mb_list = unsafe { (*(*field).fld_data.fld_from).frm_mb_list };
from = mailimf_find_first_addr(mb_list);
}
field = mailimf_find_field(imffields, MAILIMF_FIELD_ORIG_DATE as libc::c_int);
if !field.is_null() && unsafe { !(*field).fld_data.fld_orig_date.is_null() } {
let orig_date = unsafe { (*field).fld_data.fld_orig_date };
if !orig_date.is_null() {
let dt = unsafe { (*orig_date).dt_date_time };
message_time = dc_timestamp_from_date(dt);
if message_time != 0 && message_time > time() {
message_time = time()
}
}
}
let mut peerstate = None;
let autocryptheader = from
.as_ref()
.and_then(|from| Aheader::from_imffields(from, imffields));
if message_time > 0 {
if let Some(ref from) = from {
peerstate = Peerstate::from_addr(context, &context.sql, from);
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).unwrap();
} 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).unwrap();
}
} else if let Some(ref header) = autocryptheader {
let p = Peerstate::from_header(context, header, message_time);
p.save_to_db(&context.sql, true).unwrap();
peerstate = Some(p);
}
}
}
/* load private key for decryption */
let self_addr = context.get_config(Config::ConfiguredAddr);
if let Some(self_addr) = self_addr {
if private_keyring.load_self_private_for_decrypting(context, self_addr, &context.sql) {
if peerstate.as_ref().map(|p| p.last_seen).unwrap_or_else(|| 0) == 0 {
peerstate =
Peerstate::from_addr(&context, &context.sql, &from.unwrap_or_default());
}
if let Some(ref peerstate) = peerstate {
if peerstate.degrade_event.is_some() {
handle_degrade_event(context, &peerstate)?;
}
if let Some(ref key) = peerstate.gossip_key {
public_keyring_for_validate.add_ref(key);
}
if let Some(ref key) = peerstate.public_key {
public_keyring_for_validate.add_ref(key);
}
}
encrypted = decrypt_if_autocrypt_message(
context,
in_out_message,
&private_keyring,
&public_keyring_for_validate,
&mut signatures,
&mut gossip_headers,
)?;
if !gossip_headers.is_null() {
gossipped_addr =
update_gossip_peerstates(context, message_time, imffields, gossip_headers)?;
}
}
}
}
if !gossip_headers.is_null() {
unsafe { mailimf_fields_free(gossip_headers) };
}
Ok((encrypted, signatures, gossipped_addr))
}
fn new_data_part(
data: *mut libc::c_void,
data_bytes: libc::size_t,
content_type: &str,
default_encoding: u32,
) -> Result<*mut Mailmime> {
let content = new_content_type(&content_type)?;
let mut encoding = ptr::null_mut();
if wrapmime::content_type_needs_encoding(content) {
encoding = unsafe { mailmime_mechanism_new(default_encoding as i32, ptr::null_mut()) };
ensure!(!encoding.is_null(), "failed to create encoding");
}
let mime_fields = {
unsafe {
mailmime_fields_new_with_data(
encoding,
ptr::null_mut(),
ptr::null_mut(),
ptr::null_mut(),
ptr::null_mut(),
)
}
};
ensure!(!mime_fields.is_null(), "internal mime error");
let mime = unsafe { mailmime_new_empty(content, mime_fields) };
ensure!(!mime.is_null(), "internal mime error");
if unsafe { (*mime).mm_type } == MAILMIME_SINGLE as libc::c_int {
if !data.is_null() && data_bytes > 0 {
unsafe { mailmime_set_body_text(mime, data as *mut libc::c_char, data_bytes) };
}
}
Ok(mime)
}
/// Load public key from database or generate a new one.
///
/// This will load a public key from the database, generating and
/// storing a new one when one doesn't exist yet. Care is taken to
/// only generate one key per context even when multiple threads call
/// this function concurrently.
fn load_or_generate_self_public_key(context: &Context, self_addr: impl AsRef<str>) -> Result<Key> {
if let Some(key) = Key::from_self_public(context, &self_addr, &context.sql) {
return Ok(key);
}
let _guard = context.generating_key_mutex.lock().unwrap();
// Check again in case the key was generated while we were waiting for the lock.
if let Some(key) = Key::from_self_public(context, &self_addr, &context.sql) {
return Ok(key);
}
let start = std::time::Instant::now();
info!(
context,
"Generating keypair with {} bits, e={} ...", 2048, 65537,
);
match dc_pgp_create_keypair(&self_addr) {
Some((public_key, private_key)) => {
match dc_key_save_self_keypair(
context,
&public_key,
&private_key,
&self_addr,
true,
&context.sql,
) {
true => {
info!(
context,
"Keypair generated in {:.3}s.",
start.elapsed().as_secs()
);
Ok(public_key)
}
false => Err(format_err!("Failed to save keypair")),
}
}
None => Err(format_err!("Failed to generate keypair")),
}
}
fn update_gossip_peerstates(
context: &Context,
message_time: i64,
imffields: *mut mailimf_fields,
gossip_headers: *const mailimf_fields,
) -> Result<HashSet<String>> {
// XXX split the parsing from the modification part
let mut recipients: Option<HashSet<String>> = None;
let mut gossipped_addr: HashSet<String> = Default::default();
for cur_data in unsafe { (*(*gossip_headers).fld_list).into_iter() } {
let field = cur_data as *mut mailimf_field;
if field.is_null() {
continue;
}
let field = unsafe { *field };
if field.fld_type == MAILIMF_FIELD_OPTIONAL_FIELD as libc::c_int {
let optional_field = unsafe { field.fld_data.fld_optional_field };
if optional_field.is_null() {
continue;
}
let optional_field = unsafe { *optional_field };
if !optional_field.fld_name.is_null()
&& as_str(optional_field.fld_name) == "Autocrypt-Gossip"
{
let value = to_string_lossy(optional_field.fld_value);
let gossip_header = Aheader::from_str(&value);
if let Ok(ref header) = gossip_header {
if recipients.is_none() {
recipients = Some(mailimf_get_recipients(imffields));
}
if recipients.as_ref().unwrap().contains(&header.addr) {
let mut peerstate =
Peerstate::from_addr(context, &context.sql, &header.addr);
if let Some(ref mut peerstate) = peerstate {
peerstate.apply_gossip(header, message_time);
peerstate.save_to_db(&context.sql, false)?;
} else {
let p = Peerstate::from_gossip(context, header, message_time);
p.save_to_db(&context.sql, true)?;
peerstate = Some(p);
}
if let Some(peerstate) = peerstate {
if peerstate.degrade_event.is_some() {
handle_degrade_event(context, &peerstate)?;
}
}
gossipped_addr.insert(header.addr.clone());
} else {
info!(
context,
"Ignoring gossipped \"{}\" as the address is not in To/Cc list.",
&header.addr,
);
}
}
}
}
}
Ok(gossipped_addr)
}
fn decrypt_if_autocrypt_message(
context: &Context,
mime_undetermined: *mut Mailmime,
private_keyring: &Keyring,
public_keyring_for_validate: &Keyring,
ret_valid_signatures: &mut HashSet<String>,
ret_gossip_headers: *mut *mut mailimf_fields,
) -> Result<(bool)> {
/* The returned bool is true if we detected an Autocrypt-encrypted
message and successfully decrypted it. Decryption then modifies the
passed in mime structure in place. The returned bool is false
if it was not an Autocrypt message.
Errors are returned for failures related to decryption of AC-messages.
*/
ensure!(!mime_undetermined.is_null(), "Invalid mime reference");
let (mime, encrypted_data_part) = match wrapmime::get_autocrypt_mime(mime_undetermined) {
Err(_) => {
// not a proper autocrypt message, abort and ignore
return Ok(false);
}
Ok(res) => res,
};
let decrypted_mime = decrypt_part(
context,
encrypted_data_part,
private_keyring,
public_keyring_for_validate,
ret_valid_signatures,
)?;
// decrypted_mime is a dangling pointer which we now put into mailmime's Ownership
unsafe {
mailmime_substitute(mime, decrypted_mime);
mailmime_free(mime);
}
// finally, let's also return gossip headers
// XXX better return parsed headers so that upstream
// does not need to dive into mmime-stuff again.
unsafe {
if (*ret_gossip_headers).is_null() && ret_valid_signatures.len() > 0 {
let mut dummy: libc::size_t = 0;
let mut test: *mut mailimf_fields = ptr::null_mut();
if mailimf_envelope_and_optional_fields_parse(
(*decrypted_mime).mm_mime_start,
(*decrypted_mime).mm_length,
&mut dummy,
&mut test,
) == MAILIMF_NO_ERROR as libc::c_int
&& !test.is_null()
{
*ret_gossip_headers = test;
}
}
}
Ok(true)
}
fn decrypt_part(
_context: &Context,
mime: *mut Mailmime,
private_keyring: &Keyring,
public_keyring_for_validate: &Keyring,
ret_valid_signatures: &mut HashSet<String>,
) -> Result<*mut Mailmime> {
let mime_data: *mut mailmime_data;
let mut mime_transfer_encoding = MAILMIME_MECHANISM_BINARY as libc::c_int;
unsafe {
mime_data = (*mime).mm_data.mm_single;
}
if !wrapmime::has_decryptable_data(mime_data) {
return Ok(ptr::null_mut());
}
if let Some(enc) = wrapmime::get_mime_transfer_encoding(mime) {
mime_transfer_encoding = enc;
}
let (decoded_data, decoded_data_bytes) =
wrapmime::decode_dt_data(mime_data, mime_transfer_encoding)?;
// encrypted, non-NULL decoded data in decoded_data now ...
// Note that we need to take care of freeing decoded_data ourself,
// after encryption has been attempted.
let mut ret_decrypted_mime = ptr::null_mut();
ensure!(!decoded_data.is_null(), "Missing data");
let data = unsafe { std::slice::from_raw_parts(decoded_data as *const u8, decoded_data_bytes) };
if has_decrypted_pgp_armor(data) {
// we should only have one decryption happening
ensure!(ret_valid_signatures.is_empty(), "corrupt signatures");
let plain = match dc_pgp_pk_decrypt(
data,
&private_keyring,
&public_keyring_for_validate,
Some(ret_valid_signatures),
) {
Ok(plain) => {
ensure!(!ret_valid_signatures.is_empty(), "no valid signatures");
plain
}
Err(err) => {
unsafe { mmap_string_unref(decoded_data) };
bail!("could not decrypt: {}", err)
}
};
let plain_bytes = plain.len();
let plain_buf = plain.as_ptr() as *const libc::c_char;
let mut index = 0;
let mut decrypted_mime = ptr::null_mut();
if unsafe {
mailmime_parse(
plain_buf as *const _,
plain_bytes,
&mut index,
&mut decrypted_mime,
)
} != MAIL_NO_ERROR as libc::c_int
|| decrypted_mime.is_null()
{
if !decrypted_mime.is_null() {
unsafe { mailmime_free(decrypted_mime) };
}
} else {
// decrypted_mime points into `plain`.
// FIXME(@dignifiedquire): this still leaks memory I believe, as mailmime_free
// does not free the underlying buffer. But for now we have to live with it
std::mem::forget(plain);
ret_decrypted_mime = decrypted_mime;
}
}
unsafe { mmap_string_unref(decoded_data) };
Ok(ret_decrypted_mime)
}
fn has_decrypted_pgp_armor(input: &[u8]) -> bool {
if let Some(index) = input.iter().position(|b| *b > b' ') {
if input.len() - index > 26 {
let start = index;
let end = start + 27;
return &input[start..end] == b"-----BEGIN PGP MESSAGE-----";
}
}
false
}
/// Check if a MIME structure contains a multipart/report part.
///
/// As reports are often unencrypted, we do not reset the Autocrypt header in
/// this case.
///
/// However, Delta Chat itself has no problem with encrypted multipart/report
/// parts and MUAs should be encouraged to encrpyt multipart/reports as well so
/// that we could use the normal Autocrypt processing.
fn contains_report(mime: *mut Mailmime) -> bool {
assert!(!mime.is_null());
let mime = unsafe { *mime };
if mime.mm_type == MAILMIME_MULTIPLE as libc::c_int {
let tp_type = unsafe { (*(*mime.mm_content_type).ct_type).tp_type };
let ct_type =
unsafe { (*(*(*mime.mm_content_type).ct_type).tp_data.tp_composite_type).ct_type };
if tp_type == MAILMIME_TYPE_COMPOSITE_TYPE as libc::c_int
&& ct_type == MAILMIME_COMPOSITE_TYPE_MULTIPART as libc::c_int
&& as_str(unsafe { (*mime.mm_content_type).ct_subtype }) == "report"
{
return true;
}
for cur_data in unsafe { (*(*mime.mm_mime_fields).fld_list).into_iter() } {
if contains_report(cur_data as *mut Mailmime) {
return true;
}
}
} else if mime.mm_type == MAILMIME_MESSAGE as libc::c_int {
let m = unsafe { mime.mm_data.mm_message.mm_msg_mime };
if contains_report(m) {
return true;
}
}
false
}
/// Ensures a private key exists for the configured user.
///
/// Normally the private key is generated when the first message is
/// sent but in a few locations there are no such guarantees,
/// e.g. when exporting keys, and calling this function ensures a
/// private key will be present.
///
/// If this succeeds you are also guaranteed that the
/// [Config::ConfiguredAddr] is configured, this address is returned.
pub fn ensure_secret_key_exists(context: &Context) -> Result<String> {
let self_addr = context
.get_config(Config::ConfiguredAddr)
.ok_or(format_err!(concat!(
"Failed to get self address, ",
"cannot ensure secret key if not configured."
)))?;
load_or_generate_self_public_key(context, &self_addr)?;
Ok(self_addr)
}
#[cfg(test)]
mod tests {
use super::*;
use libc::free;
use crate::test_utils::*;
mod ensure_secret_key_exists {
use super::*;
#[test]
fn test_prexisting() {
let t = dummy_context();
let test_addr = configure_alice_keypair(&t.ctx);
assert_eq!(ensure_secret_key_exists(&t.ctx).unwrap(), test_addr);
}
#[test]
fn test_not_configured() {
let t = dummy_context();
assert!(ensure_secret_key_exists(&t.ctx).is_err());
}
}
#[test]
fn test_mailmime_parse() {
let plain = b"Chat-Disposition-Notification-To: holger@deltachat.de
Chat-Group-ID: CovhGgau8M-
Chat-Group-Name: Delta Chat Dev
Subject: =?utf-8?Q?Chat=3A?= Delta Chat =?utf-8?Q?Dev=3A?= sidenote for
=?utf-8?Q?all=3A?= rust core master ...
Content-Type: text/plain; charset=\"utf-8\"; protected-headers=\"v1\"
Content-Transfer-Encoding: quoted-printable
sidenote for all: rust core master is broken currently ... so dont recomm=
end to try to run with desktop or ios unless you are ready to hunt bugs
-- =20
Sent with my Delta Chat Messenger: https://delta.chat";
let plain_bytes = plain.len();
let plain_buf = plain.as_ptr() as *const libc::c_char;
let mut index = 0;
let mut decrypted_mime = std::ptr::null_mut();
let res = unsafe {
mailmime_parse(
plain_buf as *const _,
plain_bytes,
&mut index,
&mut decrypted_mime,
)
};
unsafe {
let msg1 = (*decrypted_mime).mm_data.mm_message.mm_msg_mime;
let data = mailmime_transfer_decode(msg1).unwrap();
println!("{:?}", String::from_utf8_lossy(&data));
}
assert_eq!(res, 0);
assert!(!decrypted_mime.is_null());
unsafe { free(decrypted_mime as *mut _) };
}
mod load_or_generate_self_public_key {
use super::*;
#[test]
fn test_existing() {
let t = dummy_context();
let addr = configure_alice_keypair(&t.ctx);
let key = load_or_generate_self_public_key(&t.ctx, addr);
assert!(key.is_ok());
}
#[test]
#[ignore] // generating keys is expensive
fn test_generate() {
let t = dummy_context();
let addr = "alice@example.org";
let key0 = load_or_generate_self_public_key(&t.ctx, addr);
assert!(key0.is_ok());
let key1 = load_or_generate_self_public_key(&t.ctx, addr);
assert!(key1.is_ok());
assert_eq!(key0.unwrap(), key1.unwrap());
}
#[test]
#[ignore]
fn test_generate_concurrent() {
use std::sync::Arc;
use std::thread;
let t = dummy_context();
let ctx = Arc::new(t.ctx);
let ctx0 = Arc::clone(&ctx);
let thr0 =
thread::spawn(move || load_or_generate_self_public_key(&ctx0, "alice@example.org"));
let ctx1 = Arc::clone(&ctx);
let thr1 =
thread::spawn(move || load_or_generate_self_public_key(&ctx1, "alice@example.org"));
let res0 = thr0.join().unwrap();
let res1 = thr1.join().unwrap();
assert_eq!(res0.unwrap(), res1.unwrap());
}
}
#[test]
fn test_has_decrypted_pgp_armor() {
let data = b" -----BEGIN PGP MESSAGE-----";
assert_eq!(has_decrypted_pgp_armor(data), true);
let data = b" \n-----BEGIN PGP MESSAGE-----";
assert_eq!(has_decrypted_pgp_armor(data), true);
let data = b" -----BEGIN PGP MESSAGE---";
assert_eq!(has_decrypted_pgp_armor(data), false);
let data = b" -----BEGIN PGP MESSAGE-----";
assert_eq!(has_decrypted_pgp_armor(data), true);
let data = b"blas";
assert_eq!(has_decrypted_pgp_armor(data), false);
}
}

View File

@@ -22,6 +22,14 @@ pub enum Error {
Image(image_meta::ImageError),
#[fail(display = "{:?}", _0)]
Utf8(std::str::Utf8Error),
#[fail(display = "{:?}", _0)]
CStringError(crate::dc_tools::CStringError),
#[fail(display = "PGP: {:?}", _0)]
Pgp(pgp::errors::Error),
#[fail(display = "Base64Decode: {:?}", _0)]
Base64Decode(base64::DecodeError),
#[fail(display = "{:?}", _0)]
FromUtf8(std::string::FromUtf8Error),
}
pub type Result<T> = std::result::Result<T, Error>;
@@ -32,6 +40,12 @@ impl From<rusqlite::Error> for Error {
}
}
impl From<base64::DecodeError> for Error {
fn from(err: base64::DecodeError) -> Error {
Error::Base64Decode(err)
}
}
impl From<failure::Error> for Error {
fn from(err: failure::Error) -> Error {
Error::Failure(err)
@@ -56,12 +70,30 @@ impl From<std::str::Utf8Error> for Error {
}
}
impl From<std::string::FromUtf8Error> for Error {
fn from(err: std::string::FromUtf8Error) -> Error {
Error::FromUtf8(err)
}
}
impl From<image_meta::ImageError> for Error {
fn from(err: image_meta::ImageError) -> Error {
Error::Image(err)
}
}
impl From<crate::dc_tools::CStringError> for Error {
fn from(err: crate::dc_tools::CStringError) -> Error {
Error::CStringError(err)
}
}
impl From<pgp::errors::Error> for Error {
fn from(err: pgp::errors::Error) -> Error {
Error::Pgp(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 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 imex().
/// This event may be sent multiple times by a single call to 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,4 +1,3 @@
use std::ffi::CString;
use std::net;
use std::sync::{
atomic::{AtomicBool, Ordering},
@@ -8,32 +7,33 @@ use std::time::{Duration, SystemTime};
use crate::constants::*;
use crate::context::Context;
use crate::dc_loginparam::*;
use crate::dc_tools::CStringExt;
use crate::dc_receive_imf::dc_receive_imf;
use crate::events::Event;
use crate::job::{job_add, Action};
use crate::login_param::LoginParam;
use crate::message::{self, update_msg_move_state, update_server_uid};
use crate::oauth2::dc_get_oauth2_access_token;
use crate::types::*;
use crate::param::Params;
const DC_IMAP_SEEN: usize = 0x0001;
const DC_REGENERATE: usize = 0x01;
const DC_SUCCESS: usize = 3;
const DC_ALREADY_DONE: usize = 2;
const DC_RETRY_LATER: usize = 1;
const DC_FAILED: usize = 0;
#[derive(Debug, Display, Clone, Copy, PartialEq, Eq)]
pub enum ImapResult {
Failed,
RetryLater,
AlreadyDone,
Success,
}
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 +41,7 @@ pub struct Imap {
should_reconnect: AtomicBool,
}
#[derive(Debug)]
struct OAuth2 {
user: String,
access_token: String,
@@ -65,6 +66,7 @@ enum FolderMeaning {
Other,
}
#[derive(Debug)]
enum Client {
Secure(
imap::Client<native_tls::TlsStream<net::TcpStream>>,
@@ -73,11 +75,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>),
@@ -106,6 +110,8 @@ impl Client {
) -> imap::error::Result<Self> {
let stream = net::TcpStream::connect(addr)?;
let tls = native_tls::TlsConnector::builder()
// see also: https://github.com/deltachat/deltachat-core-rust/issues/203
.danger_accept_invalid_certs(true)
.danger_accept_invalid_hostnames(true)
.build()
.unwrap();
@@ -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),
}
@@ -420,9 +418,7 @@ impl Imap {
if (server_flags & DC_LP_AUTH_OAUTH2) != 0 {
let addr: &str = config.addr.as_ref();
if let Some(token) =
dc_get_oauth2_access_token(context, addr, imap_pw, DC_REGENERATE as usize)
{
if let Some(token) = dc_get_oauth2_access_token(context, addr, imap_pw, true) {
let auth = OAuth2 {
user: imap_user.into(),
access_token: token,
@@ -440,14 +436,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 +457,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 +469,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 +481,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 +489,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 +510,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;
}
@@ -548,33 +542,32 @@ impl Imap {
}
let (teardown, can_idle, has_xlist) = match &mut *self.session.lock().unwrap() {
Some(ref mut session) => {
if let Ok(caps) = session.capabilities() {
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("IDLE");
let has_xlist = caps.has("XLIST");
let caps_list = caps.iter().fold(String::new(), |mut s, c| {
s += " ";
s += c;
s
});
log_event!(
let can_idle = caps.has_str("IDLE");
let has_xlist = caps.has_str("XLIST");
let caps_list = caps
.iter()
.fold(String::new(), |s, c| s + &format!(" {:?}", c));
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)
}
} else {
}
Err(err) => {
info!(context, "CAPABILITY command error: {}", err);
(true, false, false)
}
}
},
None => (true, false, false),
};
@@ -647,7 +640,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 +670,6 @@ impl Imap {
Err(err) => {
info!(
context,
0,
"Cannot select folder: {}; {:?}.",
folder.as_ref(),
err
@@ -698,7 +690,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 +706,6 @@ impl Imap {
if !self.is_connected() {
info!(
context,
0,
"Cannot fetch from \"{}\" - not connected.",
folder.as_ref()
);
@@ -725,7 +716,6 @@ impl Imap {
if self.select_folder(context, Some(&folder)) == 0 {
info!(
context,
0,
"Cannot select folder \"{}\" for fetching.",
folder.as_ref()
);
@@ -742,7 +732,6 @@ impl Imap {
if mailbox.uid_validity.is_none() {
error!(
context,
0,
"Cannot get UIDVALIDITY for folder \"{}\".",
folder.as_ref(),
);
@@ -754,7 +743,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 +762,6 @@ impl Imap {
self.should_reconnect.store(true, Ordering::Relaxed);
info!(
context,
0,
"No result returned for folder \"{}\".",
folder.as_ref()
);
@@ -796,7 +784,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 +802,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;
}
}
@@ -835,15 +822,11 @@ impl Imap {
.message_id
.expect("missing message id");
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)
} {
if !precheck_imf(context, &message_id, 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 +838,6 @@ impl Imap {
// check failed
info!(
context,
0,
"Skipping message {} from \"{}\" by precheck.",
message_id,
folder.as_ref(),
@@ -876,7 +858,6 @@ impl Imap {
if read_errors > 0 {
warn!(
context,
0,
"{} mails read from \"{}\" with {} errors.",
read_cnt,
folder.as_ref(),
@@ -885,7 +866,6 @@ impl Imap {
} else {
info!(
context,
0,
"{} mails read from \"{}\".",
read_cnt,
folder.as_ref()
@@ -905,7 +885,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 +910,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 +926,6 @@ impl Imap {
if msgs.is_empty() {
warn!(
context,
0,
"Message #{} does not exist in folder \"{}\".",
server_uid,
folder.as_ref()
@@ -977,14 +955,7 @@ impl Imap {
if !is_deleted && msg.body().is_some() {
let body = msg.body().unwrap();
unsafe {
(self.receive_imf)(
context,
body.as_ptr() as *const libc::c_char,
body.len(),
folder.as_ref(),
server_uid,
flags as u32,
);
dc_receive_imf(context, &body, folder.as_ref(), server_uid, flags as u32);
}
}
}
@@ -1001,7 +972,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 +982,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 +1017,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 +1041,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 +1059,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 {
@@ -1158,27 +1126,25 @@ impl Imap {
uid: u32,
dest_folder: S2,
dest_uid: &mut u32,
) -> usize {
let mut res = DC_RETRY_LATER;
) -> ImapResult {
let mut res = ImapResult::RetryLater;
let set = format!("{}", uid);
if uid == 0 {
res = DC_FAILED;
res = ImapResult::Failed;
} else if folder.as_ref() == dest_folder.as_ref() {
info!(
context,
0,
"Skip moving message; message {}/{} is already in {}...",
folder.as_ref(),
uid,
dest_folder.as_ref()
);
res = DC_ALREADY_DONE;
res = ImapResult::AlreadyDone;
} else {
info!(
context,
0,
"Moving message {}/{} to {}...",
folder.as_ref(),
uid,
@@ -1188,7 +1154,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()
);
@@ -1196,13 +1161,12 @@ impl Imap {
let moved = if let Some(ref mut session) = &mut *self.session.lock().unwrap() {
match session.uid_mv(&set, &dest_folder) {
Ok(_) => {
res = DC_SUCCESS;
res = ImapResult::Success;
true
}
Err(err) => {
info!(
context,
0,
"Cannot move message, fallback to COPY/DELETE {}/{} to {}: {}",
folder.as_ref(),
uid,
@@ -1223,7 +1187,7 @@ impl Imap {
Ok(_) => true,
Err(err) => {
eprintln!("error copy: {:?}", err);
info!(context, 0, "Cannot copy message.",);
info!(context, "Cannot copy message.",);
false
}
@@ -1234,25 +1198,25 @@ 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;
res = ImapResult::Success;
}
}
}
}
if res == DC_SUCCESS {
if res == ImapResult::Success {
// TODO: is this correct?
*dest_uid = uid;
}
if res == DC_RETRY_LATER {
if res == ImapResult::RetryLater {
if self.should_reconnect() {
DC_RETRY_LATER
ImapResult::RetryLater
} else {
DC_FAILED
ImapResult::Failed
}
} else {
res
@@ -1271,7 +1235,7 @@ impl Imap {
Err(err) => {
warn!(
context,
0, "IMAP failed to store: ({}, {}) {:?}", set, query, err
"IMAP failed to store: ({}, {}) {:?}", set, query, err
);
}
}
@@ -1286,15 +1250,14 @@ impl Imap {
}
}
pub fn set_seen<S: AsRef<str>>(&self, context: &Context, folder: S, uid: u32) -> usize {
let mut res = DC_RETRY_LATER;
pub fn set_seen<S: AsRef<str>>(&self, context: &Context, folder: S, uid: u32) -> ImapResult {
let mut res = ImapResult::RetryLater;
if uid == 0 {
res = DC_FAILED
res = ImapResult::Failed
} else if self.is_connected() {
info!(
context,
0,
"Marking message {}/{} as seen...",
folder.as_ref(),
uid,
@@ -1303,39 +1266,37 @@ 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
res = ImapResult::Success
}
}
if res == DC_RETRY_LATER {
if res == ImapResult::RetryLater {
if self.should_reconnect() {
DC_RETRY_LATER
ImapResult::RetryLater
} else {
DC_FAILED
ImapResult::Failed
}
} else {
res
}
}
pub fn set_mdnsent<S: AsRef<str>>(&self, context: &Context, folder: S, uid: u32) -> usize {
pub fn set_mdnsent<S: AsRef<str>>(&self, context: &Context, folder: S, uid: u32) -> ImapResult {
// returns 0=job should be retried later, 1=job done, 2=job done and flag just set
let mut res = DC_RETRY_LATER;
let mut res = ImapResult::RetryLater;
let set = format!("{}", uid);
if uid == 0 {
res = DC_FAILED;
res = ImapResult::Failed;
} else if self.is_connected() {
info!(
context,
0,
"Marking message {}/{} as $MDNSent...",
folder.as_ref(),
uid,
@@ -1344,7 +1305,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()
);
@@ -1403,34 +1363,34 @@ impl Imap {
.unwrap_or_else(|| false);
res = if flag_set {
DC_ALREADY_DONE
ImapResult::AlreadyDone
} else if self.add_flag(context, uid, "$MDNSent") != 0 {
DC_SUCCESS
ImapResult::Success
} else {
res
};
if res == DC_SUCCESS {
info!(context, 0, "$MDNSent just set and MDN will be sent.");
if res == ImapResult::Success {
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;
res = ImapResult::Success;
info!(
context,
0, "Cannot store $MDNSent flags, risk sending duplicate MDN.",
"Cannot store $MDNSent flags, risk sending duplicate MDN.",
);
}
}
}
if res == DC_RETRY_LATER {
if res == ImapResult::RetryLater {
if self.should_reconnect() {
DC_RETRY_LATER
ImapResult::RetryLater
} else {
DC_FAILED
ImapResult::Failed
}
} else {
res
@@ -1451,7 +1411,6 @@ impl Imap {
} else {
info!(
context,
0,
"Marking message \"{}\", {}/{} for deletion...",
message_id.as_ref(),
folder.as_ref(),
@@ -1461,7 +1420,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 +1440,6 @@ impl Imap {
{
warn!(
context,
0,
"Cannot delete on IMAP, {}/{} does not match {}.",
folder.as_ref(),
server_uid,
@@ -1496,7 +1453,6 @@ impl Imap {
warn!(
context,
0,
"Cannot delete on IMAP, {}/{} not found.",
folder.as_ref(),
server_uid,
@@ -1508,7 +1464,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 +1484,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 +1503,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 +1523,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 +1573,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 +1633,38 @@ fn get_folder_meaning(folder_name: &imap::types::Name) -> FolderMeaning {
_ => res,
}
}
fn precheck_imf(context: &Context, rfc724_mid: &str, server_folder: &str, server_uid: u32) -> bool {
let mut rfc724_mid_exists = false;
let mut mark_seen = false;
if let Ok((old_server_folder, old_server_uid, msg_id)) =
message::rfc724_mid_exists(context, &rfc724_mid)
{
rfc724_mid_exists = true;
if old_server_folder.is_empty() && old_server_uid == 0 {
info!(context, "[move] detected bbc-self {}", rfc724_mid,);
mark_seen = true;
} else if old_server_folder != server_folder {
info!(context, "[move] detected moved message {}", rfc724_mid,);
update_msg_move_state(context, &rfc724_mid, MoveState::Stay);
}
if old_server_folder != server_folder || old_server_uid != server_uid {
update_server_uid(context, &rfc724_mid, server_folder, server_uid);
}
context.do_heuristics_moves(server_folder, msg_id);
if mark_seen {
job_add(
context,
Action::MarkseenMsgOnImap,
msg_id as libc::c_int,
Params::new(),
0,
);
}
}
rfc724_mid_exists
}

1029
src/imex.rs Normal file

File diff suppressed because it is too large Load Diff

View File

@@ -1,5 +1,3 @@
use std::ffi::CStr;
use std::ptr;
use std::time::Duration;
use deltachat_derive::{FromSql, ToSql};
@@ -9,29 +7,37 @@ use crate::chat;
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::imex::*;
use crate::location;
use crate::message::*;
use crate::login_param::LoginParam;
use crate::message::{self, Message, MessageState};
use crate::mimefactory::{vec_contains_lowercase, Loaded, MimeFactory};
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 +56,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 +125,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 Ok(body) = dc_read_file(context, filename) {
if let Some(recipients) = self.param.get(Param::Recipients) {
let recipients_list = recipients
.split("\x1e")
@@ -147,129 +149,106 @@ 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 && !message::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 {
message::update_msg_state(
context,
self.foreign_id,
MessageState::OutDelivered,
);
let chat_id: i32 = context
.sql
.query_get_value(
context,
self.foreign_id,
MessageState::OutDelivered,
);
let chat_id: i32 = context
.sql
.query_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)]
fn do_DC_JOB_MOVE_MSG(&mut self, context: &Context) {
let ok_to_continue;
let mut dest_uid = 0;
let inbox = context.inbox.read().unwrap();
if !inbox.is_connected() {
connect_to_inbox(context, &inbox);
if !inbox.is_connected() {
self.try_again_later(3, None);
ok_to_continue = false;
} else {
ok_to_continue = true;
return;
}
} else {
ok_to_continue = true;
}
if ok_to_continue {
if let Ok(msg) = dc_msg_load_from_db(context, self.foreign_id) {
if context
.sql
.get_config_int(context, "folders_configured")
.unwrap_or_default()
< 3
{
inbox.configure_folders(context, 0x1i32);
}
let dest_folder = context.sql.get_config(context, "configured_mvbox_folder");
if let Ok(msg) = Message::load_from_db(context, self.foreign_id) {
if context
.sql
.get_config_int(context, "folders_configured")
.unwrap_or_default()
< 3
{
inbox.configure_folders(context, 0x1i32);
}
let dest_folder = context.sql.get_config(context, "configured_mvbox_folder");
if let Some(dest_folder) = dest_folder {
let server_folder = msg.server_folder.as_ref().unwrap();
if let Some(dest_folder) = dest_folder {
let server_folder = msg.server_folder.as_ref().unwrap();
let mut dest_uid = 0;
match inbox.mv(
context,
server_folder,
msg.server_uid,
&dest_folder,
&mut dest_uid,
) as libc::c_uint
{
1 => {
self.try_again_later(3i32, None);
}
3 => {
dc_update_server_uid(context, msg.rfc724_mid, &dest_folder, dest_uid);
}
0 | 2 | _ => {}
match inbox.mv(
context,
server_folder,
msg.server_uid,
&dest_folder,
&mut dest_uid,
) {
ImapResult::RetryLater => {
self.try_again_later(3i32, None);
}
ImapResult::Success => {
message::update_server_uid(
context,
&msg.rfc724_mid,
&dest_folder,
dest_uid,
);
}
ImapResult::Failed | ImapResult::AlreadyDone => {}
}
}
}
@@ -277,101 +256,75 @@ impl Job {
#[allow(non_snake_case)]
fn do_DC_JOB_DELETE_MSG_ON_IMAP(&mut self, context: &Context) {
let mut delete_from_server = 1;
let inbox = context.inbox.read().unwrap();
if let Ok(mut msg) = dc_msg_load_from_db(context, self.foreign_id) {
if !(msg.rfc724_mid.is_null()
|| unsafe { *msg.rfc724_mid.offset(0isize) as libc::c_int == 0 })
{
let ok_to_continue1;
if let Ok(mut msg) = Message::load_from_db(context, self.foreign_id) {
if !msg.rfc724_mid.is_empty() {
/* eg. device messages have no Message-ID */
if dc_rfc724_mid_cnt(context, msg.rfc724_mid) != 1 {
let mut delete_from_server = true;
if message::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
delete_from_server = false;
}
/* if this is the last existing part of the message, we delete the message from the server */
if 0 != delete_from_server {
let ok_to_continue;
if delete_from_server {
if !inbox.is_connected() {
connect_to_inbox(context, &inbox);
if !inbox.is_connected() {
self.try_again_later(3i32, None);
ok_to_continue = false;
} else {
ok_to_continue = true;
return;
}
} else {
ok_to_continue = true;
}
if ok_to_continue {
let mid = unsafe { CStr::from_ptr(msg.rfc724_mid).to_str().unwrap() };
let server_folder = msg.server_folder.as_ref().unwrap();
if 0 == inbox.delete_msg(context, mid, server_folder, &mut msg.server_uid) {
self.try_again_later(-1i32, None);
ok_to_continue1 = false;
} else {
ok_to_continue1 = true;
}
} else {
ok_to_continue1 = false;
let mid = msg.rfc724_mid;
let server_folder = msg.server_folder.as_ref().unwrap();
if 0 == inbox.delete_msg(context, &mid, server_folder, &mut msg.server_uid) {
self.try_again_later(-1i32, None);
return;
}
} else {
ok_to_continue1 = true;
}
if ok_to_continue1 {
dc_delete_msg_from_db(context, msg.id);
}
Message::delete_from_db(context, msg.id);
}
}
}
#[allow(non_snake_case)]
fn do_DC_JOB_MARKSEEN_MSG_ON_IMAP(&mut self, context: &Context) {
let ok_to_continue;
let inbox = context.inbox.read().unwrap();
if !inbox.is_connected() {
connect_to_inbox(context, &inbox);
if !inbox.is_connected() {
self.try_again_later(3i32, None);
ok_to_continue = false;
} else {
ok_to_continue = true;
return;
}
} else {
ok_to_continue = true;
}
if ok_to_continue {
if let Ok(msg) = dc_msg_load_from_db(context, self.foreign_id) {
let server_folder = msg.server_folder.as_ref().unwrap();
match inbox.set_seen(context, server_folder, msg.server_uid) as libc::c_uint {
0 => {}
1 => {
self.try_again_later(3i32, None);
}
_ => {
if 0 != msg.param.get_int(Param::WantsMdn).unwrap_or_default()
&& 0 != context
.sql
.get_config_int(context, "mdns_enabled")
.unwrap_or_else(|| 1)
{
let folder = msg.server_folder.as_ref().unwrap();
if let Ok(msg) = Message::load_from_db(context, self.foreign_id) {
let server_folder = msg.server_folder.as_ref().unwrap();
match inbox.set_seen(context, server_folder, msg.server_uid) {
ImapResult::Failed => {}
ImapResult::RetryLater => {
self.try_again_later(3i32, None);
}
_ => {
if 0 != msg.param.get_int(Param::WantsMdn).unwrap_or_default()
&& 0 != context
.sql
.get_config_int(context, "mdns_enabled")
.unwrap_or_else(|| 1)
{
let folder = msg.server_folder.as_ref().unwrap();
match inbox.set_mdnsent(context, folder, msg.server_uid) as libc::c_uint
{
1 => {
self.try_again_later(3i32, None);
}
3 => {
send_mdn(context, msg.id);
}
0 | 2 | _ => {}
match inbox.set_mdnsent(context, folder, msg.server_uid) {
ImapResult::RetryLater => {
self.try_again_later(3i32, None);
}
ImapResult::Success => {
send_mdn(context, msg.id);
}
ImapResult::Failed | ImapResult::AlreadyDone => {}
}
}
}
@@ -381,47 +334,40 @@ impl Job {
#[allow(non_snake_case)]
fn do_DC_JOB_MARKSEEN_MDN_ON_IMAP(&mut self, context: &Context) {
let ok_to_continue;
let folder = self
.param
.get(Param::ServerFolder)
.unwrap_or_default()
.to_string();
let uid = self.param.get_int(Param::ServerUid).unwrap_or_default() as u32;
let mut dest_uid = 0;
let inbox = context.inbox.read().unwrap();
if !inbox.is_connected() {
connect_to_inbox(context, &inbox);
if !inbox.is_connected() {
self.try_again_later(3, None);
ok_to_continue = false;
} else {
ok_to_continue = true;
return;
}
} else {
ok_to_continue = true;
}
if ok_to_continue {
if inbox.set_seen(context, &folder, uid) == 0 {
self.try_again_later(3i32, None);
if inbox.set_seen(context, &folder, uid) == ImapResult::Failed {
self.try_again_later(3i32, None);
}
if 0 != self.param.get_int(Param::AlsoMove).unwrap_or_default() {
if context
.sql
.get_config_int(context, "folders_configured")
.unwrap_or_default()
< 3
{
inbox.configure_folders(context, 0x1i32);
}
if 0 != self.param.get_int(Param::AlsoMove).unwrap_or_default() {
if context
.sql
.get_config_int(context, "folders_configured")
.unwrap_or_default()
< 3
let dest_folder = context.sql.get_config(context, "configured_mvbox_folder");
if let Some(dest_folder) = dest_folder {
let mut dest_uid = 0;
if ImapResult::RetryLater
== inbox.mv(context, folder, uid, dest_folder, &mut dest_uid)
{
inbox.configure_folders(context, 0x1i32);
}
let dest_folder = context.sql.get_config(context, "configured_mvbox_folder");
if let Some(dest_folder) = dest_folder {
if 1 == inbox.mv(context, folder, uid, dest_folder, &mut dest_uid)
as libc::c_uint
{
self.try_again_later(3, None);
}
self.try_again_later(3, None);
}
}
}
@@ -452,18 +398,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 +422,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 +505,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 +525,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 +533,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 +551,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,15 +601,15 @@ 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 fn job_send_msg(context: &Context, msg_id: u32) -> libc::c_int {
let mut success = 0;
/* load message data */
let mimefactory = dc_mimefactory_load_msg(context, msg_id);
if mimefactory.is_err() || mimefactory.as_ref().unwrap().from_addr.is_null() {
let mimefactory = MimeFactory::load_msg(context, msg_id);
if mimefactory.is_err() {
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();
@@ -684,63 +628,55 @@ pub unsafe fn job_send_msg(context: &Context, msg_id: uint32_t) -> libc::c_int {
mimefactory.msg.param.set_int(Param::Width, 0);
mimefactory.msg.param.set_int(Param::Height, 0);
if let Some(buf) = dc_read_file_safe(context, pathNfilename) {
if let Ok(buf) = dc_read_file(context, pathNfilename) {
if let Ok((width, height)) = dc_get_filemeta(&buf) {
mimefactory.msg.param.set_int(Param::Width, width as i32);
mimefactory.msg.param.set_int(Param::Height, height as i32);
}
}
dc_msg_save_param_to_disk(&mut mimefactory.msg);
mimefactory.msg.save_param_to_disk(context);
}
}
}
/* create message */
if 0 == dc_mimefactory_render(&mut mimefactory) {
dc_set_msg_failed(context, msg_id, as_opt_str(mimefactory.error));
if let Err(msg) = unsafe { mimefactory.render() } {
let e = msg.to_string();
message::set_msg_failed(context, msg_id, Some(e));
} else if 0
!= mimefactory
.msg
.param
.get_int(Param::GuranteeE2ee)
.unwrap_or_default()
&& 0 == mimefactory.out_encrypted
&& !mimefactory.out_encrypted
{
/* unrecoverable */
warn!(
context,
0,
"e2e encryption unavailable {} - {:?}",
msg_id,
mimefactory.msg.param.get_int(Param::GuranteeE2ee),
);
dc_set_msg_failed(
message::set_msg_failed(
context,
msg_id,
Some("End-to-end-encryption unavailable unexpectedly."),
);
} else {
/* unrecoverable */
if clist_search_string_nocase(mimefactory.recipients_addr, mimefactory.from_addr)
== 0i32
{
clist_insert_after(
mimefactory.recipients_names,
(*mimefactory.recipients_names).last,
ptr::null_mut(),
);
clist_insert_after(
mimefactory.recipients_addr,
(*mimefactory.recipients_addr).last,
dc_strdup(mimefactory.from_addr) as *mut libc::c_void,
);
if !vec_contains_lowercase(&mimefactory.recipients_addr, &mimefactory.from_addr) {
mimefactory.recipients_names.push("".to_string());
mimefactory
.recipients_addr
.push(mimefactory.from_addr.to_string());
}
if 0 != mimefactory.out_gossiped {
if mimefactory.out_gossiped {
chat::set_gossiped_timestamp(context, mimefactory.msg.chat_id, time());
}
if 0 != mimefactory.out_last_added_location_id {
if let Err(err) =
location::set_kml_sent_timestamp(context, mimefactory.msg.chat_id, time())
{
error!(context, 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,11 +684,11 @@ 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);
}
}
}
if 0 != mimefactory.out_encrypted
if mimefactory.out_encrypted
&& mimefactory
.msg
.param
@@ -761,7 +697,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);
mimefactory.msg.save_param_to_disk(context);
}
success = add_smtp_job(context, Action::SendMsgToSmtp, &mut mimefactory);
}
@@ -771,14 +707,22 @@ 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.",);
}
pub fn perform_mvbox_jobs(context: &Context) {
info!(context, "dc_perform_mbox_jobs EMPTY (for now).",);
}
pub fn perform_sentbox_jobs(context: &Context) {
info!(context, "dc_perform_sentbox_jobs EMPTY (for now).",);
}
fn job_perform(context: &Context, thread: Thread, probe_network: bool) {
@@ -826,14 +770,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,14 +814,22 @@ 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),
Action::MarkseenMdnOnImap => job.do_DC_JOB_MARKSEEN_MDN_ON_IMAP(context),
Action::MoveMsg => job.do_DC_JOB_MOVE_MSG(context),
Action::SendMdn => job.do_DC_JOB_SEND(context),
Action::ConfigureImap => unsafe { dc_job_do_DC_JOB_CONFIGURE_IMAP(context, &job) },
Action::ImexImap => unsafe { dc_job_do_DC_JOB_IMEX_IMAP(context, &job) },
Action::ConfigureImap => unsafe { dc_job_do_DC_JOB_CONFIGURE_IMAP(context) },
Action::ImexImap => match job_do_DC_JOB_IMEX_IMAP(context, &job) {
Ok(()) => {}
Err(err) => {
error!(context, "{}", err);
}
},
Action::MaybeSendLocations => {
location::job_do_DC_JOB_MAYBE_SEND_LOCATIONS(context, &job)
}
@@ -913,7 +864,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 +881,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"
@@ -954,7 +903,7 @@ fn job_perform(context: &Context, thread: Thread, probe_network: bool) {
}
} else {
if job.action == Action::SendMsgToSmtp {
dc_set_msg_failed(context, job.foreign_id, job.pending_error.as_ref());
message::set_msg_failed(context, job.foreign_id, job.pending_error.as_ref());
}
job.delete(context);
}
@@ -1006,66 +955,41 @@ fn connect_to_inbox(context: &Context, inbox: &Imap) -> libc::c_int {
ret_connected
}
fn send_mdn(context: &Context, msg_id: uint32_t) {
if let Ok(mut mimefactory) = unsafe { dc_mimefactory_load_mdn(context, msg_id) } {
if 0 != unsafe { dc_mimefactory_render(&mut mimefactory) } {
fn send_mdn(context: &Context, msg_id: u32) {
if let Ok(mut mimefactory) = MimeFactory::load_mdn(context, msg_id) {
if unsafe { mimefactory.render() }.is_ok() {
add_smtp_job(context, Action::SendMdn, &mut mimefactory);
}
}
}
#[allow(non_snake_case)]
fn add_smtp_job(context: &Context, action: Action, mimefactory: &dc_mimefactory_t) -> libc::c_int {
let pathNfilename: *mut libc::c_char;
fn add_smtp_job(context: &Context, action: Action, mimefactory: &MimeFactory) -> libc::c_int {
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", &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),
mimefactory.rfc724_mid,
path_filename.display(),
);
} else {
recipients = unsafe {
dc_str_from_clist(
mimefactory.recipients_addr,
b"\x1e\x00" as *const u8 as *const libc::c_char,
)
};
param.set(Param::File, as_str(pathNfilename));
param.set(Param::Recipients, as_str(recipients));
info!(context, "add_smtp_job file written: {:?}", path_filename);
let recipients = mimefactory.recipients_addr.join("\x1e");
param.set(Param::File, path_filename.to_string_lossy());
param.set(Param::Recipients, &recipients);
job_add(
context,
action,
(if mimefactory.loaded as libc::c_uint
== DC_MF_MSG_LOADED as libc::c_int as libc::c_uint
{
(if mimefactory.loaded == Loaded::Message {
mimefactory.msg.id
} else {
0
@@ -1075,10 +999,6 @@ fn add_smtp_job(context: &Context, action: Action, mimefactory: &dc_mimefactory_
);
success = 1;
}
unsafe {
free(recipients.cast());
free(pathNfilename.cast());
}
success
}
@@ -1089,6 +1009,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 +1034,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 +1050,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,9 +1,7 @@
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};
use pgp::ser::Serialize;
use pgp::types::{KeyTrait, SecretKeyTrait};
@@ -12,7 +10,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 +103,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 +140,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 +153,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 +214,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 +232,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,
@@ -294,14 +253,14 @@ pub fn dc_key_save_self_keypair(
public_key: &Key,
private_key: &Key,
addr: impl AsRef<str>,
is_default: libc::c_int,
is_default: bool,
sql: &Sql,
) -> bool {
sql::execute(
context,
sql,
"INSERT INTO keypairs (addr, is_default, public_key, private_key, created) VALUES (?,?,?,?,?);",
params![addr.as_ref(), is_default, public_key.to_bytes(), private_key.to_bytes(), time()],
params![addr.as_ref(), is_default as i32, public_key.to_bytes(), private_key.to_bytes(), time()],
).is_ok()
}
@@ -323,14 +282,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 +290,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 +392,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;
@@ -30,38 +37,37 @@ pub mod configure;
pub mod constants;
pub mod contact;
pub mod context;
mod e2ee;
mod imap;
pub mod imex;
pub mod job;
mod job_thread;
pub mod key;
pub mod keyring;
pub mod location;
mod login_param;
pub mod lot;
pub mod message;
mod mimefactory;
pub mod oauth2;
mod param;
pub mod peerstate;
pub mod pgp;
pub mod qr;
pub mod securejoin;
mod smtp;
pub mod sql;
mod stock;
pub mod types;
pub mod x;
mod token;
#[macro_use]
mod wrapmime;
pub mod dc_array;
mod dc_dehtml;
mod dc_e2ee;
pub mod dc_imex;
mod dc_loginparam;
mod dc_mimefactory;
pub mod dc_mimeparser;
mod dc_move;
pub mod dc_receive_imf;
pub mod dc_securejoin;
mod dc_simplify;
mod dc_strencode;
mod dc_token;
pub mod dc_tools;
#[cfg(test)]

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::message::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 = Message::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 = Message::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);
}};
}

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