Compare commits

..

362 Commits

Author SHA1 Message Date
holger krekel
e9c0492fbd fix off-by-one error and fix wrong flag corrections 2019-12-13 11:08:29 +01:00
holger krekel
72e128db2e (experimental) special-case nauta provider to try make it work 2019-12-13 11:08:29 +01:00
holger krekel
4d1357568b use async-smtp master where @link2xt's branch has been merged 2019-12-13 11:03:46 +01:00
Alexander Krotov
ccc190f991 Switch to native_tls 2019-12-13 11:03:46 +01:00
B. Petersen
0a9f880fb4 remove another superfluous return value 2019-12-13 01:17:48 +01:00
holger krekel
b8faf54f0b remove superflous expression values 2019-12-13 01:17:48 +01:00
B. Petersen
888507f7ba test group- and user-avatars in the same mail 2019-12-13 01:17:48 +01:00
B. Petersen
29866092de adapt the protected-header test; protected-headers really require encryption now and parts of the tests are know to fail now. however, protected-headers are also tested in python implicitly in several places 2019-12-13 01:17:48 +01:00
B. Petersen
36fc2c83f2 return selfavatar from dc_get_info() 2019-12-13 01:17:48 +01:00
holger krekel
5e777b3c51 fix test also for avatar deletion, use that drain filter while we in this code area :) 2019-12-13 01:17:48 +01:00
holger krekel
5690c48863 fix merging of protected headers 2019-12-13 01:17:48 +01:00
holger krekel
8ab3363097 add a test that runs until an avatar is send encrypted 2019-12-13 01:17:48 +01:00
B. Petersen
f6861ca5f5 target comments from @flub and @dignifiedquire 2019-12-13 01:17:48 +01:00
B. Petersen
3bb58be2b5 add missing delete header 2019-12-13 01:17:48 +01:00
B. Petersen
409c96e571 add compatiblity headers 2019-12-13 01:17:48 +01:00
B. Petersen
d681fa6cba use Chat-User-Avatar and Chat-Group-Avatar
we change the name on the wire as the old Chat-Group-Image header
could not be used on random mails, it was the marker for a "Changed" message,
if we would keep this names, things will fail for exising installations
as messages are dropped and a "Group image changed" message is shown instead.
2019-12-13 01:17:48 +01:00
B. Petersen
7c3d8356c4 always handle incoming group-avatars 2019-12-13 01:17:48 +01:00
B. Petersen
a8842da50a && has a higher precedence than || - fix a logical error when resetting Unpromoted 2019-12-13 01:17:48 +01:00
B. Petersen
ff29b84146 share group image on group creation, fixes #602 2019-12-13 01:17:48 +01:00
B. Petersen
ab12a4eb39 move ImageAction parsing to a reusable function 2019-12-13 01:17:48 +01:00
B. Petersen
c3fd0889e2 use tri-state ImageAction instead of Option<Option> 2019-12-13 01:17:48 +01:00
B. Petersen
c62532a665 adapt to new HeaderDef api 2019-12-13 01:17:48 +01:00
B. Petersen
ca63d6ba1c handle incoming profile-images 2019-12-13 01:17:48 +01:00
B. Petersen
a1f496b019 let mimeparser handle profile-images 2019-12-13 01:17:48 +01:00
B. Petersen
da421438cd do not send selfavatar on existing installation without resetting 2019-12-13 01:17:48 +01:00
B. Petersen
7f723ef2bf attach selfavatar 2019-12-13 01:17:48 +01:00
B. Petersen
3cf39dace0 print profile-image in repl tool 2019-12-13 01:17:48 +01:00
B. Petersen
021ad4f12c set correct icon for DC_CONTACT_ID_DEVICE 2019-12-13 01:17:48 +01:00
B. Petersen
36edf447e7 load param incl. user-profile-image from db 2019-12-13 01:17:48 +01:00
B. Petersen
ea2273aef4 reset selfavatar_sent column when the selfavatar is updated 2019-12-13 01:17:48 +01:00
B. Petersen
251aa22c4c add column for selfavatar_sent timestamp 2019-12-13 01:17:48 +01:00
Alexander Krotov
541710147a Copy correct dc_contact_is_verified documentation from C core
instead of dc_contact_is_verified_ex documentation.
2019-12-11 17:54:28 +01:00
dignifiedquire
a197ca4cf7 feat: update dependencies, including rand@0.7 and rpgp@0.4 2019-12-11 15:33:17 +01:00
Simon Laux
775c36bb65 wip move some ci tasks to gh-actions (#998)
* add-action-rs-clippy-action

just to try this action out

* add --all argument

* rm --all argument again

* add cargo check and fmt

* disable circle ci counterparts(fetch, clippy, fmt)
2019-12-10 23:26:42 +01:00
holger krekel
1953b95c57 commit missing Cargo.lock 2019-12-10 21:42:52 +01:00
holger krekel
542a33952f prepare 1.0.0-beta.15 2019-12-10 21:35:20 +01:00
holger krekel
1819712667 make sure we send out <> around message-id's in references and in-reply-to 2019-12-10 21:25:36 +01:00
holger krekel
69a596fdff fix #994 -- avoid double-attachment messages in local chats 2019-12-10 21:25:36 +01:00
björn petersen
4f88b93495 Merge pull request #1003 from deltachat/flub-spec-their
Use 3rd person singular
2019-12-10 19:10:59 +01:00
Floris Bruynooghe
a388052e2a Use 3rd person singular 2019-12-10 19:07:22 +01:00
holger krekel
8b81ea3b6f actually update to 1.0.0-beta.14 2019-12-10 15:39:09 +01:00
holger krekel
b913de5928 prepare a 1.0.0-beta.14 with fixed deps 2019-12-10 14:42:55 +01:00
holger krekel
4e551cde66 update tags and rust-email dep 2019-12-10 14:17:16 +01:00
holger krekel
b681cbd47f prepare 1.0.0-beta.13 2019-12-10 14:02:21 +01:00
holger krekel
551253b4e0 use async-smtp with hostname fixed to version 0.1.5 2019-12-10 14:00:43 +01:00
Alexander Krotov
4ad9166b5a Move one chat test from stress.rs to chat module 2019-12-10 13:54:05 +01:00
holger krekel
8487255c33 prepare changelog for 1.0.0-beta.13 2019-12-10 13:52:57 +01:00
Alexander Krotov
2792d4ea1e Stop using create::error in login_param.rs 2019-12-10 12:39:58 +01:00
björn petersen
95180a850f Merge pull request #993 from deltachat/group_add_remove
Fix various issues around group add/remove
2019-12-10 12:14:52 +01:00
holger krekel
56ee7a0abd try to remove updates to lock file 2019-12-10 02:33:13 +01:00
holger krekel
d0a04be825 remove hack for decoding, and add a test that encoding/decoding works now 2019-12-10 01:35:09 +01:00
holger krekel
2cbf287998 don't cargo-update on CI jobs but use the Cargo.lock file we manually maintain/update 2019-12-10 00:40:18 +01:00
holger krekel
054cf98754 try to work around mailparser not decoding rfc2047 displaynames
this pulls in changes in our fork of rust-email  to also correctly
generate rfc2047 encoding
2019-12-10 00:26:46 +01:00
holger krekel
a95fbfe271 add a test 2019-12-09 22:28:29 +01:00
holger krekel
17ce02a87c add some comment and remove some code after quick a/v with @r10s 2019-12-09 21:45:01 +01:00
holger krekel
4dc5e0378f fix final problem, tests pass now 2019-12-09 20:25:46 +01:00
holger krekel
c33797ff84 remove reverse "check_self" return 2019-12-09 19:41:58 +01:00
holger krekel
f242b40d0a nicer print of imap capabilities 2019-12-09 18:36:26 +01:00
holger krekel
5f916f5a9c - create and use a ContactIds type as an ordered set instead of "Vec<u32>".
- recreate the group list more carefully, fixes #985

- resultify a few functions in the dc_receive pipeline

- don't quote displaynames in email-addresses, use utf8, preliminrarily addresses #976
2019-12-09 16:29:24 +01:00
holger krekel
6edb525540 snap 2019-12-09 14:16:46 +01:00
holger krekel
0c04d5b2ab add a failing test that creates 4 accounts and a group chat with remove/add
see also #985
2019-12-09 14:16:46 +01:00
Alexander Krotov
301852fd87 Add test for kebab_case serialization of From header 2019-12-09 15:43:06 +03:00
holger krekel
0e8df7d633 use automatic serialization, thanks @link2xt for the tip 2019-12-09 15:43:06 +03:00
holger krekel
e4155e0e16 remove lookup_field in favor of get(HeaderDef::...) with all headers defined in headerdef.rs 2019-12-09 15:43:06 +03:00
Floris Bruynooghe
2c4dbe6e68 Re-work some in-creation file handling
This effectively reverts
https://github.com/deltachat/deltachat-core-rust/pull/964 for chat.rs,
which in that PR was thought to fix something.  So maybe something is
still broken?  But after improving tests the previous code seems to be
correct.

- Update Python bindings to not always use dc_prepare_msg path when
  sending messages with attachements.  When using dc_prepare_msg the
  blobs need to be created in the blobdir since they will not get
  copied and many tests where not doing this.

- Add a test that ensures that calling dc_prepare_msg with a
  file **not** in the blobdir fails.

- Add a test that ensures that calling dc_send_msg directly with a
  file **not** in the blobdir copies the file to the blobdir.  This
  test cheats a little by knowing what the filename in the blobdir
  will be which is implementation-dependent and thus a bit brittle.
  But for now it proves correct behaviour so let's go with this.

- Improve the test_forward_increation test to ensure that the
  in-creation file only has it's final state before calling
  dc_send_msg.  This checks the correct file data is sent out and not
  the preparing data, this fails with the chat.rs changes in
  #964 (reverted here to make this work again).  Also fix the test to
  actually create the in-creation file in the blobdir.

- Fix test_send_file_twice_unicode_filename_mangling to not use
  in-creation.  It was not creating it's files in the blobdir and that
  is an error when using in-creation and it didn't seem it was trying
  to test something about the in-creation logic (which is tested in
  test_increation.py already).

- Fix Message._msgtate code which presumably was not used before?

- Rename `BlobObject::create_from_path` to
  `BlobObject::new_from_path`.  All the `BlobObject::create*` calls
  now always create new files which is much more consistent.  APIs
  should do what is obious.
2019-12-09 10:30:57 +01:00
Alexander Krotov
a99b96e36e Use bool for from_id_blocked flag 2019-12-09 09:54:46 +01:00
Alexander Krotov
0889467c7b Use bool for hidden flag 2019-12-09 09:54:46 +01:00
Alexander Krotov
d141e228de Use bool for incoming flag 2019-12-09 09:54:46 +01:00
Alexander Krotov
9b15c42801 Use bool for allow_creation flag 2019-12-09 09:54:46 +01:00
Alexander Krotov
a781b631e1 Return bool from is_reply_to_{known_message,messenger_message} 2019-12-09 09:54:46 +01:00
Alexander Krotov
08af5c8e09 Replace some integers with bools in dc_receive_imf.rs 2019-12-09 09:54:46 +01:00
holger krekel
93e8cca02f remove redundant state from smtp 2019-12-08 23:04:08 +01:00
Alexander Krotov
a8e9a1fbe5 Make Smtp.send async 2019-12-08 21:00:03 +01:00
Alexander Krotov
54eb30f3db Switch from lettre to async-smtp 2019-12-08 21:00:03 +01:00
Alexander Krotov
c08a1adc9b Deprecate AcceptInvalidHostnames option
Rustls does not offer a documented way to accept valid certificates with
invalid hostnames. Implementation of certificate verification in Rustls
does not have a public API and reimplementing it is error-prone.
2019-12-08 20:54:04 +01:00
Alexander Krotov
cd951ad396 Improve IMAP idle timeout error handling
Return proper cause and avoid matching on nested Result
2019-12-08 20:50:27 +01:00
holger krekel
33793d878b address @link2xt comment on done/async 2019-12-08 01:13:00 +01:00
holger krekel
357955015d [wip] initial detection of idle done() not working 2019-12-08 01:13:00 +01:00
holger krekel
d6d94adab0 remove encrypted attr from mimeparser. 2019-12-08 00:47:46 +01:00
holger krekel
099cc9f727 remove subject attribute 2019-12-08 00:47:46 +01:00
holger krekel
61f8d6f171 remove is_send_by_messenger attr from mimeparser 2019-12-08 00:47:46 +01:00
Alexander Krotov
a7af4685f1 Log IMAP certificate checks configuration 2019-12-08 00:31:22 +01:00
Alexander Krotov
2cebed4f77 Move dc_array.rs to deltachat-ffi package 2019-12-08 00:30:43 +01:00
holger krekel
3f2a371599 run lint without installing the package 2019-12-07 22:56:54 +01:00
holger krekel
a3ca3d9179 allow to use a different buildhost :) 2019-12-07 22:56:54 +01:00
holger krekel
86ace1a4af - test and fix receiving text/html attachment in multipart/mixed situations
They are now preserved as attachment, instead of diving into parsing-html
  and simplifying.

- adapt mime-debugging
2019-12-07 22:56:54 +01:00
holger krekel
1abdf62045 passes test but needs cleanup 2019-12-07 22:56:54 +01:00
holger krekel
9e2a96675d try fix incoming text/html in multipart/mixed 2019-12-07 22:56:54 +01:00
Alexander Krotov
f32876708a Update outdated comment
MIME wrapping is now done by the email crate, which implements RFC5322
2019-12-07 21:35:42 +01:00
Alexander Krotov
84e0e7e020 aheader.rs: do not generate lines longer than 78 characters 2019-12-07 21:35:42 +01:00
Alexander Krotov
38cddff6e9 Move keydata= wrapping from key.rs to aheader.rs 2019-12-07 21:35:42 +01:00
Alexander Krotov
f1aba8115b Use Option<EncryptPreference>.unwrap_or_default in aheader.rs 2019-12-07 07:58:53 +01:00
B. Petersen
0db5ff55fd unshape icons, used indexed colors 2019-12-06 20:10:19 +01:00
B. Petersen
7421b0de6c adapt the ffi-examples to reality, free stuff 2019-12-06 20:08:57 +01:00
Alexander Krotov
6c275c30a7 Do not include "prefer-encrypt=nopreference;" in Autocrypt header
Specification
https://autocrypt.org/level1.html#the-autocrypt-header
says that prefer-encrypt attribute can only occur with the value `mutual`.
2019-12-06 20:01:34 +03:00
Alexander Krotov
91f1d793e9 Test that internal "prefer-encrypt=reset" value is not parsed from header
This is a test for bugfix e8f2f7b24e
2019-12-06 14:23:45 +01:00
Alexander Krotov
cddfd04a7f Turn rawkey from function into a constant 2019-12-06 14:23:45 +01:00
Alexander Krotov
7fa94c33bf cargo fmt 2019-12-06 10:35:02 +01:00
Alexander Krotov
30dd20dc7b Make Imap.fetch() async 2019-12-06 10:35:02 +01:00
Alexander Krotov
339c0d3dc7 Remove unnecessary use of failure::Fail 2019-12-06 10:34:34 +01:00
Alexander Krotov
2304d63bb3 Simplify dc_get_filesuffix_lc 2019-12-06 09:45:24 +01:00
holger krekel
36014f9fe5 add next changelog entry 2019-12-06 09:00:23 +01:00
Alexander Krotov
351383dfa4 Split IMAP idle functions into imap::idle submodule
Also introduce Imap.can_idle() to avoid having to match on IdleAbilityMissing error
2019-12-06 08:59:22 +01:00
holger krekel
defad94f5a bump to beta12 2019-12-06 00:55:24 +01:00
holger krekel
a0517478e3 adding auto-copy-blob logic when preparing a message 2019-12-06 00:27:22 +01:00
holger krekel
7f4e68f21c failing test for sending out a file twice 2019-12-06 00:27:22 +01:00
holger krekel
c0a425c26d bump to beta11 2019-12-05 21:53:03 +01:00
holger krekel
e756859b16 don't split qr tests out anymore now 2019-12-05 21:50:17 +01:00
holger krekel
174bc017ad make setup_handle_if_needed async, call it ahead of select_folder and fetch_new_messages (renamed from fetch_from_single_folder), and be more eager triggering reconnect on error conditions 2019-12-05 21:47:32 +01:00
holger krekel
4a9fb0212f complete changelog and bump version 2019-12-05 19:32:16 +01:00
holger krekel
212848409f use encoded-words crate, which friedel ported from python 2019-12-05 19:29:12 +01:00
holger krekel
e45ee0eb81 try fix filename encoding bug -- fails in one test 2019-12-05 19:29:12 +01:00
dignifiedquire
3b8e37de58 switch to quoted-printable, which is already used by mailparse 2019-12-05 19:29:12 +01:00
holger krekel
c2e8cc9bd6 use rfc2047 crate from @valodim and remove dc_strencode.rs completely 2019-12-05 19:29:12 +01:00
holger krekel
ec81d29580 fix multi-line subject encoding and introduce MIME debugging env var 2019-12-05 19:29:12 +01:00
jikstra
c4de0f3b17 Apply remaining requested changes 2019-12-05 18:57:19 +01:00
jikstra
965d41990e change return to "" for errors 2019-12-05 18:57:19 +01:00
holger krekel
d96dba336b Update deltachat-ffi/deltachat.h
fix doc
2019-12-05 18:57:19 +01:00
jikstra
a7e1b4653e Apply requested changes 2019-12-05 18:57:19 +01:00
jikstra
e38b42bc21 Add api docu 2019-12-05 18:57:19 +01:00
jikstra
36bd502292 cargo fmt 2019-12-05 18:57:19 +01:00
jikstra
594bf3dfc8 fixup! Move to json_serde, add tests and implement missing python api 2019-12-05 18:57:19 +01:00
jikstra
6d30ccfc63 Move to json_serde, add tests and implement missing python api 2019-12-05 18:57:19 +01:00
jikstra
1b79f513a3 Implement more json key/value pairs 2019-12-05 18:57:19 +01:00
holger krekel
74825a0f57 working example 2019-12-05 18:57:19 +01:00
holger krekel
4a23d12df2 add a ffi-definiton for a new get-chat summary function that returns json 2019-12-05 18:57:19 +01:00
holger krekel
7f117574ab test and fix #956 2019-12-05 02:15:54 +01:00
holger krekel
f91474c2f8 add preliminary changelog for beta10 2019-12-05 01:22:50 +01:00
holger krekel
2a081aac2b fix grpid extraction from In-Reply-To and References headers 2019-12-05 01:18:53 +01:00
holger krekel
9b10f31fb3 more cleanups 2019-12-05 00:56:09 +01:00
holger krekel
63ad7b8d34 make to_ids const in some places, and simplify returns from create_or_lookup_adhoc_group 2019-12-05 00:56:09 +01:00
holger krekel
86baaab2e9 get rid of unsafe and indirect return values for create_or_lookup.*group 2019-12-05 00:56:09 +01:00
holger krekel
3e66d23367 make set_core_version return the versions if no args are specified 2019-12-04 22:32:56 +01:00
dignifiedquire
609b5588fa fix(mimefactory): only send Autocrypt-Gossip headers on encrypted messages 2019-12-04 13:58:33 +01:00
pabzm
798072b8ba Fix new event name in Changelog. 2019-12-04 13:52:02 +01:00
Alexander Krotov
d950a58613 Improve documentation 2019-12-04 10:15:40 +01:00
holger krekel
4c68e6fe41 update deps 2019-12-04 09:11:21 +01:00
holger krekel
914ce77b50 adapt changelog 2019-12-04 08:58:48 +01:00
holger krekel
d09989a4ed fix implementation of Autocrypt to encrypt if answering to an encrypted message.
Previously, if any of a chat's peers set prefer_encrypt to false
(i.e. "e2ee_enabled=0" in the config, a misnomer btw) then a
previously encrypted chat would drop to cleartext easily.
2019-12-04 08:57:42 +01:00
holger krekel
6999a4e3a9 streamline decrypt/encrypt logging 2019-12-04 08:57:42 +01:00
Alexander Krotov
069541f374 Create select_folder::Error 2019-12-04 06:13:24 +01:00
Alexander Krotov
49b9b28c99 Move select_folder to a separate submodule 2019-12-04 06:13:24 +01:00
B. Petersen
b274482125 add welcome-image created by @Simon-Laux 2019-12-04 06:11:42 +01:00
B. Petersen
b766a55b0a in set_chat_name(), bind value to sql using ?-operator, add a test to check the name is really changed 2019-12-04 06:11:01 +01:00
B. Petersen
b8057b7ddb remove >1y old file path migration which is now uneeded and has potential issues 2019-12-04 06:11:01 +01:00
B. Petersen
98266f47d6 bind value to sql using ?-operator 2019-12-04 06:11:01 +01:00
B. Petersen
7b83979e10 test that the device-chat is a one-to-one-chat 2019-12-04 06:01:37 +01:00
B. Petersen
79cdcca2d2 check that sending/preparing/forwarding on a chat that cannot send really fails 2019-12-04 06:01:37 +01:00
B. Petersen
4c0d00640b fix repl forward command 2019-12-04 06:01:37 +01:00
Alexander Krotov
ad87b7c4a5 Fix clippy warnings 2019-12-03 20:20:52 +03:00
holger krekel
74f36b264b fix get_recipients with test 2019-12-03 17:51:44 +01:00
holger krekel
2df43b6c5d avoids double-semicolon problem leading to empty messages in pre-master releases 2019-12-03 16:55:53 +01:00
holger krekel
73f80624b2 adding a draft changelog entry for beta.9 2019-12-03 14:48:57 +01:00
björn petersen
fe2a4b7a4a Merge pull request #904 from deltachat/mailparse
Switch mailparsing to mailparse
2019-12-03 14:16:07 +01:00
dignifiedquire
affdf7241f fixup: remove double mimefactory 2019-12-03 12:26:27 +01:00
holger krekel
1c5a3e6698 remove debug logging and fix update_gossip_headers location 2019-12-03 12:25:22 +01:00
dignifiedquire
db88212a64 refactor: unsafe, CStr and libc moved out 2019-12-03 12:25:22 +01:00
dignifiedquire
43074464ac cleanup mimeparser and use mime 2019-12-03 12:25:22 +01:00
dignifiedquire
1e7afa9da0 fix chat-verified 2019-12-03 12:25:22 +01:00
dignifiedquire
e985887739 fix content-type setting and some encryption 2019-12-03 12:25:22 +01:00
dignifiedquire
d5287256e0 fix various parsing and sending issues 2019-12-03 12:25:22 +01:00
dignifiedquire
6bb2adaf45 some fixes for mdn handling 2019-12-03 12:25:22 +01:00
dignifiedquire
64fcd56998 fix mailparsing test 2019-12-03 12:25:22 +01:00
dignifiedquire
7d3a56a870 switch to forked rust-email 2019-12-03 12:25:22 +01:00
dignifiedquire
48dd3b8506 less dead code, and update test 2019-12-03 12:25:22 +01:00
dignifiedquire
734bbff04d cleanup and finish missing parts 2019-12-03 12:25:22 +01:00
dignifiedquire
5adde12dff implement mdn rendering 2019-12-03 12:25:22 +01:00
dignifiedquire
5079e15401 restructure mimefactory 2019-12-03 12:25:22 +01:00
dignifiedquire
9196ed1e9f remove dead code 2019-12-03 12:22:55 +01:00
dignifiedquire
c69a2406ad integrate encryption when sending 2019-12-03 12:22:55 +01:00
dignifiedquire
47197aa495 Implement message generation using lettre_email 2019-12-03 12:22:55 +01:00
dignifiedquire
dbd6303829 remove mmime 2019-12-03 12:22:55 +01:00
dignifiedquire
1f8d2531cc some things 2019-12-03 12:22:55 +01:00
dignifiedquire
d2de2aef07 first compile 2019-12-03 12:22:55 +01:00
Alexander Krotov
e22b4e8430 Use map_err in Smtp.connect() instead of match 2019-12-03 13:35:55 +03:00
björn petersen
af0b7e4701 Merge pull request #941 from deltachat/calc-device-msg-dimensions
calculate image-size in device-messages
2019-12-03 11:10:02 +01:00
Alexander Krotov
acef386403 Remove useless comment from the configure module 2019-12-03 06:22:05 +00:00
Alexander Krotov
15ba9b6295 Resultify outlk_autodiscover 2019-12-03 06:22:05 +00:00
Alexander Krotov
2f47cf0be5 Resultify moz_autoconfigure 2019-12-03 06:22:05 +00:00
Alexander Krotov
774106fc26 Move read_autoconf_file to its own module 2019-12-03 06:22:05 +00:00
Alexander Krotov
1b0ff9f5be Make parse_xml in auto_mozilla.rs private 2019-12-03 06:22:05 +00:00
Alexander Krotov
dfaa6895ae Add an error type to configure::auto_outlook module 2019-12-03 06:22:05 +00:00
Alexander Krotov
cb52a299cc Add an error type to configure::auto_mozilla module 2019-12-03 06:22:05 +00:00
B. Petersen
0ac0851ef3 add empty lines to stock-strings-enum 2019-12-03 06:21:37 +00:00
B. Petersen
c0f177548a add empty lines to configure-enum 2019-12-03 06:21:37 +00:00
B. Petersen
8923c9e5af calculate image-size in device-messages
eg. android relies on that and fallsback to wrong aspect ratios otherwise.
also, this makes job.rs a little more readable
as some things are moved to message.rs :)
2019-12-03 00:58:04 +01:00
björn petersen
e609ebe3f9 Merge pull request #939 from deltachat/update-device-chats
and a function to create & update device chats
2019-12-03 00:52:52 +01:00
B. Petersen
8617c3d359 target comments of @hpk42, format strum manually as skipped by 'cargo fmt' for some reason 2019-12-02 18:52:07 +01:00
B. Petersen
61e97e5e6f add updatedevicechats command to repl tool 2019-12-02 16:33:24 +01:00
B. Petersen
521e44bd54 add ffi for update_device_chats() 2019-12-02 16:33:24 +01:00
B. Petersen
18e91c480b add tests for update_device_chats() 2019-12-02 16:33:24 +01:00
B. Petersen
9d2e6e1c31 add update_device_chats() 2019-12-02 16:33:24 +01:00
dignifiedquire
eb02100d68 fix(imap): merge error conversions properly 2019-12-01 21:40:56 +01:00
Alexander Krotov
ee18d60644 cargo fmt 2019-12-01 21:01:03 +01:00
Alexander Krotov
612600278a Move SQL errors into their own module 2019-12-01 21:01:03 +01:00
holger krekel
ea8adf39c2 address @link2xt comment 2019-12-01 20:50:31 +01:00
holger krekel
f7f61e0f85 move imap errors into imap module 2019-12-01 20:50:31 +01:00
Alexander Krotov
a7bb249070 Move another OAuth2 test from stress.rs to oauth2.rs 2019-12-01 20:46:35 +01:00
björn petersen
dfe7dfcfd3 Merge pull request #935 from deltachat/address_idle_race
address last(?) idle race condition heuristically
2019-12-01 19:26:40 +01:00
Alexander Krotov
7223a36a71 SMTP error refactoring 2019-12-01 18:24:26 +01:00
holger krekel
2423d197cd better logging, changed timeout 2019-12-01 18:12:10 +01:00
holger krekel
2582791501 address #925 heuristically 2019-12-01 18:12:10 +01:00
Alexander Krotov
3a08c92433 Replace u32 with a PeerstateKeyType enum 2019-12-01 17:07:33 +01:00
Alexander Krotov
694d8fd6fb Move dc_dehtml to dehtml and remove unnecessary is_empty check 2019-12-01 13:37:37 +01:00
Asiel Díaz Benítez
369bb9166e Update message.py
use `email.message_from_bytes()` instead of decoding the bytes to then call `message_from_string`
2019-12-01 12:08:34 +01:00
björn petersen
5f1e5ef206 Merge pull request #933 from deltachat/refactor-icons
Refactor icons
2019-12-01 12:06:20 +01:00
jikstra
5d04fd0d97 color tweaking 2019-12-01 02:46:04 +01:00
jikstra
d05b5e40df add new icon-device icons 2019-12-01 02:31:12 +01:00
jikstra
0dd436ac54 Add new icon-saved-messages 2019-12-01 02:20:01 +01:00
Alexander Krotov
d5359fb9ba Ensure that Peerstate has an address set on the type level 2019-12-01 01:52:54 +01:00
B. Petersen
72c29aca6a do not create device-chat on skipped messages 2019-12-01 01:38:50 +01:00
B. Petersen
32216a334d add a test that checks the device-chat is not recreated for messages that won't be added 2019-12-01 01:38:50 +01:00
holger krekel
603d55114b address @link2xt error comment 2019-11-30 23:53:35 +01:00
holger krekel
9d18db9cae introduce a select_with_uidvalidity function that helps us during configuration to know about last_seen_uid 2019-11-30 23:53:35 +01:00
holger krekel
d14c6ea202 refine uid_next handling and rename and resultify configure_folder to ensure_configurer_folders 2019-11-30 23:53:35 +01:00
holger krekel
7be5fe925a revert logic to get last_seen_uid 2019-11-30 23:53:35 +01:00
holger krekel
8f43d7fa34 remove commented errors and fix fmt 2019-11-30 23:53:35 +01:00
holger krekel
b6e9bcee3c when first looking at a folder, look at "uid_next" as returned from server
and otherwise properly fetch the last messages to determine the last seen uid.
also add some tracing.
2019-11-30 23:53:35 +01:00
holger krekel
2dbef704e9 rework select_folder error handling (thanks @flub and @link2xt for helping along) 2019-11-30 23:53:35 +01:00
Floris Bruynooghe
74a4691f29 Convert BlobError into an enum
This deletes a lot of code and complexity.  Though comes at some cost:

- The type no longer fits in a register and will always be on the
  stack.

- Constructing the errors is more verbose, no more auto Into casting.
2019-11-30 23:51:30 +01:00
B. Petersen
084a87ed61 add chatlist tests 2019-11-30 19:38:22 +01:00
B. Petersen
657b53ae0b sort drafts again to the top of the chatlist
this reverts the logical changes done in #811
but keeps the improvements done later eg. in #911.
the reason for the revert is that it is too hard to
find a started draft in a larget chatlist.
also the shown date would not be just descending.
2019-11-30 19:38:22 +01:00
Alexander Krotov
17cb1226c6 Move OAuth 2 stress tests to oauth2 module 2019-11-30 19:19:05 +01:00
Alexander Krotov
02e281e465 Resultify dc_write_file and related functions 2019-11-30 01:54:42 +01:00
B. Petersen
4e6d0c9c69 fix places where string-cmp was used instead of addr_cmp() for email-address-comparison 2019-11-30 01:49:45 +01:00
B. Petersen
81d069209c add some tests for addr_cmp() 2019-11-30 01:49:45 +01:00
B. Petersen
63e3c82f9d compare email-addresses case-insesitive, use this comparison also to check for SELF 2019-11-30 01:49:45 +01:00
Alexander Krotov
e8f2f7b24e Do not accept prefer-encrypt=reset value from emails
Reset is an internal value that received messages should not be able to set.

Also return an error on any value other than "mutual" and "nopreference", errors are converted to NoPreference by the caller.
2019-11-30 01:40:33 +01:00
Alexander Krotov
b7f7e607c1 Use map_err 2019-11-30 01:37:56 +01:00
Alexander Krotov
ac4108b05b Mark error cause as such
See failure crate documentation.
2019-11-30 01:37:56 +01:00
Alexander Krotov
14287b12ae Resultify Smtp::send 2019-11-30 01:37:56 +01:00
Alexander Krotov
20ce5f6967 Ignore .rsynclist 2019-11-30 01:32:51 +01:00
Floris Bruynooghe
1a296cbd4e Don't let the user wait for so long 2019-11-30 01:11:15 +01:00
Floris Bruynooghe
642276c90c Attempt to fix race in securejoin handling
The ongoing process of dc_join_securejoin() was stopped before the
corresponding chat was created.  This resulted in a race-condition
between the sqlite threads executing the creation and query
statements, thus sometimes the query would not find the group and
mysteriously fail.

Tripple-programming with hpk & r10s.
2019-11-30 00:48:14 +01:00
björn petersen
e4b2fd87de Merge pull request #911 from deltachat/draft-sorting
sort newly created chats atop of chatlist
2019-11-29 15:11:06 +01:00
Alexander Krotov
dacde72456 Respect CertificateChecks in IMAP Client::secure 2019-11-29 00:40:50 +01:00
Alexander Krotov
7e66af05ff Calculate job backoff relative to the current time
Otherwise it is possible that desired_timestamp is in the past.
2019-11-29 00:26:25 +01:00
B. Petersen
b6bb5b79af target comments of @flub 2019-11-28 23:56:12 +01:00
Floris Bruynooghe
f0486eb820 Skip bad jobs in the database
Be more defensive: if somehow we got corrupt jobs in the database skip
over them rather than fail to do anything.

This only modifies the query_map() call, the rest is only split off
into it's own function to make it testable.  Smaller functions are
good anyway.
2019-11-28 23:41:47 +01:00
B. Petersen
cdc2847b96 use created_timestamp as secondary sort criterion for chatlists 2019-11-28 22:38:53 +01:00
B. Petersen
1d996d9ed9 track created_timetamp for chats 2019-11-28 22:38:48 +01:00
Alexander Krotov
e06ac87c0d Convert the number of retries to u32 2019-11-28 21:44:16 +01:00
Alexander Krotov
0c19fcd79f Use Rust for instead of while 2019-11-28 21:44:16 +01:00
Alexander Krotov
02fe3d1b99 Process PerformJobsNeeded::AvoidDos case explicitly 2019-11-28 21:44:16 +01:00
Alexander Krotov
95b90a59dc Add PerformJobsNeeded enum (DC_JOBS_NEEDED_* in core) 2019-11-28 21:44:16 +01:00
Alexander Krotov
fe7852b64e Restore JOB_RETRIES constant 2019-11-28 21:44:16 +01:00
holger krekel
f87cb4231c rename jobs to make "rg Markseen" and other searches for the job enum produce all places dealing with the enum 2019-11-28 19:42:24 +01:00
B. Petersen
7484fb6120 remove boilderplate from sql-statements, see #852 2019-11-28 19:29:44 +01:00
Alexander Krotov
430d4e5f6e Replace DC_CREATE_MVBOX with boolean 2019-11-28 18:24:29 +01:00
holger krekel
6f6791c1b5 (flub/r10s/hpk42) remove MoveState and related functions because we don't actually need it anymore 2019-11-28 15:57:26 +01:00
holger krekel
bc11ae7245 add a test for bcc-self sent messages getting moved 2019-11-28 15:57:26 +01:00
B. Petersen
de228bdb4b also move messages sent to ourselves via bcc_self to the DeltaChat folder (other messages are moved in receive_imf()) 2019-11-28 15:57:26 +01:00
holger krekel
25fb199ba0 update to current async-imap master which should fix a crash 2019-11-28 13:04:37 +01:00
Alexander Krotov
d0795f5770 Automatically fix some clippy warnings with "cargo fix" 2019-11-27 21:43:18 +01:00
holger krekel
fbb8c8e80c be less verbose when generating python docs 2019-11-27 13:41:08 +01:00
holger krekel
76610f1e72 rename to more specific name, also not using the difficult to type "succeeded" word :) 2019-11-27 13:02:07 +01:00
holger krekel
618d74cd67 safer interruptability of fake-idle -- reusing the same skip_next_idle pattern as with idle 2019-11-27 05:52:14 +01:00
Alexander Krotov
59700cb477 Restore peerstate constants from C core 2019-11-27 05:41:54 +01:00
Floris Bruynooghe
fc1a136448 Remove unused (async)Arc
This identical naming of sync and async arcs is not confusing at all
btw.
2019-11-27 05:39:36 +01:00
holger krekel
42ef43bdf6 test the new event 2019-11-27 00:43:50 +01:00
B. Petersen
f53b3c2e7b add DC_EVENT_SECUREJOIN_SUCCEEDED 2019-11-27 00:43:50 +01:00
Alexander Krotov
22a0e3fe9c job: try_again refactoring
Introduce TryAgain type with C core constants
Make try_again field and method private.
Remove try_again == 2 processing, it was never used.
2019-11-26 22:14:34 +01:00
Floris Bruynooghe
e0601bab3d Leave the avatar in place if it already is in the blobdir 2019-11-25 12:59:55 +01:00
Floris Bruynooghe
69369b02ea Copy Selfavatar to blobdir and store as proper blob
When a new Selfavatar is set the file needs to be copied into the
blobdir and as a proper blob it should be stored in the database as
$BLOBDIR/$filename.

This should fix #868.
2019-11-25 12:59:55 +01:00
B. Petersen
966b9fac49 fix cast to libc::c_char, signed i8 fails eg. on android 2019-11-24 18:12:42 +01:00
holger krekel
e2a86dd803 prepare beta8 2019-11-24 14:41:25 +01:00
holger krekel
1b88cfd053 as recent fixes are merged to async-imap, let's use that cc @link2xt 2019-11-24 16:30:49 +03:00
Alexander Krotov
6412adcfa7 Use forked version of async-imap and imap-proto
This attempts to fix fetching when the inbox contains mail with non-utf8 subjects.
2019-11-24 12:05:30 +01:00
holger krekel
76de8f55f2 make ssh fail directly if a password is asked 2019-11-24 01:05:33 +01:00
holger krekel
ae43b83162 random change to test PR 2019-11-24 01:05:33 +01:00
Alexander Krotov
8d81c8c1e0 Do not crash on messages without message-id 2019-11-23 22:52:16 +01:00
Alexander Krotov
1e173524b5 Upgrade to hex 0.4
rpgp depends on hex 0.4. This prevents building two versions of the same library.
2019-11-23 22:51:27 +01:00
B. Petersen
490418d359 add icon source files 2019-11-22 16:27:54 +01:00
B. Petersen
51ef4d82f9 use flatter design and higher resolution for icons 2019-11-22 16:27:54 +01:00
holger krekel
e522920d49 fix the check for not installing bindings (used from CI), and refined rerun policy 2019-11-22 13:54:30 +01:00
holger krekel
47be554aff adapt README, comment previous python tests out 2019-11-22 12:28:19 +01:00
holger krekel
f44b2a63b0 revert back to split doc and lint 2019-11-22 12:28:19 +01:00
holger krekel
8d4b893658 fix missing renames and tox dep 2019-11-22 12:28:19 +01:00
holger krekel
7e3c61eb41 address @dignifiedquire err.to_string() comment and avoid extra virtualenv building 2019-11-22 12:28:19 +01:00
holger krekel
bb396685ab some comments fix imap->inbox naming in example 2019-11-22 12:28:19 +01:00
holger krekel
8ef0ea8aea simplify double-fetching 2019-11-22 12:28:19 +01:00
holger krekel
34c766dc2b merge JobThread::connect_to_imap with Imap::connect_configured for simplicity 2019-11-22 12:28:19 +01:00
holger krekel
a30fa710ad resultify fetch and simplify fake_idle 2019-11-22 12:28:19 +01:00
holger krekel
fa01884350 proper handling of IdleResponse codes 2019-11-22 12:28:19 +01:00
holger krekel
eae9ad6f8b remove context.inbox in favour of a context.inbox_thread following the mvbox_thread and sentbox_thread patterns. Also some streamlining of shutdown logic. 2019-11-22 12:28:19 +01:00
holger krekel
be533fa66a resultify some imap operations 2019-11-22 12:28:19 +01:00
holger krekel
254b061921 update docs and add a simple manual script to run python/rust tests 2019-11-22 12:28:19 +01:00
holger krekel
cefa03f45b run doxygen for core docs again 2019-11-22 12:28:19 +01:00
holger krekel
b67203b421 use the reponame in dir builddir to we can distinguish from desktop/android etc 2019-11-22 12:28:19 +01:00
holger krekel
e14c4d0683 re-enable cross, some streamlining of docs 2019-11-22 12:28:19 +01:00
holger krekel
4ed96b16f4 simplify/speedup python tests 2019-11-22 12:28:19 +01:00
holger krekel
932c86bb3b various cleanups, better parallelism and build-dir structure 2019-11-22 12:28:19 +01:00
holger krekel
590fd53dd4 don't invoke py36/py35 anymore here. 2019-11-22 12:28:19 +01:00
holger krekel
8e7dc5e86f reconfigure running of rust and python tests 2019-11-22 12:28:19 +01:00
holger krekel
e13ce3140b introduce a trigger_reconnect helper 2019-11-22 12:28:19 +01:00
holger krekel
2ebb43b613 update deps, in particular async-imap to 0.1.1 which contains
the fix by @dignifiedquire that is expected to fix #829
2019-11-22 12:28:19 +01:00
holger krekel
863a70b8fc new clean try to get circle-ci to work, disable and move gh actions to ci_scripts folder 2019-11-22 12:28:19 +01:00
dignifiedquire
89a4b6fee5 update github actions 2019-11-22 12:28:19 +01:00
holger krekel
0405c945e2 shortcut fetch/idle on mvbox/sentbox if we don't know the folder and prevent busy-looping 2019-11-22 12:28:19 +01:00
holger krekel
5293ea70ae steramline some teardown decision code, and add webpki_roots for cert-checking 2019-11-22 12:28:19 +01:00
holger krekel
b5cbc97333 also make smtp respect CertificateChecks setting roughly 2019-11-22 12:28:19 +01:00
holger krekel
a867452927 rough integration of async-tls CertChecks (strict and automatic but not more finegrained work) 2019-11-22 12:28:19 +01:00
dignifiedquire
b13ca0d077 update to released versions 2019-11-22 12:28:19 +01:00
holger krekel
c6f4d6d8bd * fix interrupt_idle by signalling "skip_next_idle_wait" to the potentially concurrently "fn idle" function
* fixes double-export issue
2019-11-22 12:28:19 +01:00
holger krekel
8723aa097e make select_folder return ImapActionResult's and early-return from idle if there is no selected folder 2019-11-22 12:28:19 +01:00
dignifiedquire
97e6bc2be3 bust ci cache, update deps, use a different rust version, remove rustup install 2019-11-22 12:28:19 +01:00
dignifiedquire
2c2555fad9 refactor: drop native-tls 2019-11-22 12:28:19 +01:00
dignifiedquire
f4f69a030a update docker image 2019-11-22 12:28:19 +01:00
dignifiedquire
86f66f4d78 cleanup imap impl 2019-11-22 12:28:19 +01:00
dignifiedquire
b4e2b69086 update async-imap 2019-11-22 12:28:19 +01:00
dignifiedquire
1a1a59a14e implement idle again 2019-11-22 12:28:19 +01:00
dignifiedquire
1687e8d26f it compiles with async-imap, remove local dependency 2019-11-22 12:28:19 +01:00
Alexander Krotov
4b8252e001 Implement public key selection
First, try to use subkeys, because they are usually
short-term encryption keys. If none of the subkeys
are encryption keys, try to use the primary key.

rPGP is updated to the master branch because the
latest release does not have .is_encryption_key() yet.
2019-11-22 10:20:40 +01:00
Floris Bruynooghe
f505ff03e4 Do not break double file extensions
Double extensions are sometimes used to identify files correctly,
e.g. .tar.gz or .html.zip.  Breaking those extensions is not very
nice.

This fixes #865.
2019-11-21 22:18:31 +01:00
björn petersen
ff8e282c43 Merge pull request #862 from deltachat/fix-bcc-self
mark messages as sent
2019-11-20 15:28:40 +01:00
B. Petersen
e39cb160f9 make messages as sent in groups with only SELF and bcc_self disabled 2019-11-20 02:48:43 +01:00
björn petersen
76ce0c3540 Merge pull request #856 from deltachat/fix-time-smearing
fix time smearing
2019-11-19 23:25:11 +01:00
B. Petersen
f7047bbf51 add detailed comments about time-smearing 2019-11-19 22:52:24 +01:00
B. Petersen
c3a53cefb0 fix time smearing
if two messages have the same time,
this results i all kinds of sorting failures,
esp. in non-delta-muas and on forwarding messages.
so, no two messages sent out from delta have the same timestamp.
normally, this is no problem, but when things are sent too fast,
eg. on forwarding, we lend us some time from the future.

however, all this did not work
because we forgot to write back the modified time,
this is fixed by this commit, as well as some cleanup.
2019-11-19 22:23:01 +01:00
björn petersen
a88153954e Merge pull request #854 from deltachat/tweak-device-msg
tweak device messages
2019-11-19 22:19:42 +01:00
Alexander Krotov
4732085421 Fix some clippy warnings 2019-11-19 19:52:13 +01:00
B. Petersen
e4e6a00fe4 rename has_device_msg() to was_device_msg_ever_added() what describes better what the function is doing 2019-11-19 13:21:02 +01:00
B. Petersen
700e10bc0e use dc_add_device_msg() also to add labels-only, this is clearer as having a function with 'skip' in the name 2019-11-19 13:05:01 +01:00
B. Petersen
2a4c193601 simplify check for existing device-message 2019-11-19 11:56:35 +01:00
B. Petersen
d3fa289f27 streamline dc_add_device_msg_once|unlabelled() to one function 2019-11-19 11:49:02 +01:00
B. Petersen
1534a07ded add dc_has_device_msg() 2019-11-18 17:31:03 +01:00
B. Petersen
a4e92694ba name unlabelled device-messages as such 2019-11-18 16:47:18 +01:00
B. Petersen
035da9e5d7 add dc_skip_device_msg() and belonging tests 2019-11-18 16:47:18 +01:00
B. Petersen
fed6f4ab8a do not re-add device-messages deleted by the user 2019-11-18 16:47:18 +01:00
Nico de Haen
d7c42f3c98 Use coalesce for optional columns
resolves #851
2019-11-17 20:57:50 +01:00
B. Petersen
ad852161f3 higher resolution for device-icon 2019-11-17 20:56:42 +01:00
B. Petersen
e9b2d6a594 adapt tests to saved-messages chat 2019-11-17 20:56:07 +01:00
B. Petersen
f9d2fe710d rename me-chat to saved-messages, use an appropriate icon 2019-11-17 20:56:07 +01:00
björn petersen
a4a1cd42db Merge pull request #844 from deltachat/move-location-to-trash
Delete msg related POI when message is deleted
2019-11-16 17:40:53 +01:00
Alexander Krotov
69e14dcb2d Replace some magic numbers with constants 2019-11-16 16:08:08 +00:00
Nico de Haen
e2673894b4 Delete message only if its a POI 2019-11-16 16:55:13 +01:00
B. Petersen
852b16c972 target comment of @flub 2019-11-16 13:55:59 +01:00
B. Petersen
5796c28391 test contact adding and modifying 2019-11-16 13:55:59 +01:00
holger krekel
b6095e29d7 Update src/pgp.rs 2019-11-16 13:53:47 +01:00
Alexander Krotov
f778957caf Improve documentation and comments 2019-11-16 13:53:47 +01:00
Alexander Krotov
47f8da6532 Expand tabs into spaces 2019-11-16 12:32:32 +01:00
Alexander Krotov
0b3f2a55df Do not use wildcard match in msgtype_has_file
This way there will be a compiler error notifying that the function needs to be updated when a new message type is added.
2019-11-16 12:31:13 +01:00
Nico de Haen
13ff2bd8c4 Fix wrong indents 2019-11-16 11:28:43 +01:00
Nico de Haen
c690a64462 Update msg related location when message is deleted
resolves #843
2019-11-16 11:04:56 +01:00
B. Petersen
d808bfe400 add some tests for me-chat and deaddrop 2019-11-13 22:31:18 +01:00
B. Petersen
1b30078c09 test archive functions 2019-11-13 10:16:57 +01:00
B. Petersen
0278875e03 add device-chat tests 2019-11-13 00:13:32 +01:00
B. Petersen
d0ccf28678 do not add random label for unlabelled device-messages 2019-11-13 00:13:32 +01:00
B. Petersen
5023255ebc fix accidentally called maybe_add_bcc_self_device_msg() 2019-11-13 00:13:32 +01:00
holger krekel
545376875a fix python tests 2019-11-12 16:10:48 +01:00
B. Petersen
a9fe77b62e use separate column for device-msg-labels 2019-11-12 16:10:48 +01:00
B. Petersen
b42d8799b4 add dc_add_device_msg_once() to fii 2019-11-12 16:10:48 +01:00
B. Petersen
a42e197634 add device-message only once 2019-11-12 16:10:48 +01:00
B. Petersen
fc32c85608 add device-msg 'consider enabling bcc_self' if bcc_self is disabled and an autocrypt-setup-message is received 2019-11-12 16:10:48 +01:00
B. Petersen
dabd431b1f change default for bcc_self, preserve values for existing installations 2019-11-12 16:10:48 +01:00
B. Petersen
85b4817a1e restore some comments 2019-11-12 16:10:48 +01:00
B. Petersen
84c6113271 use enum for show_emails 2019-11-12 16:10:48 +01:00
B. Petersen
9506f8c38e use bool for exists_before_update 2019-11-12 16:10:48 +01:00
Friedel Ziegelmayer
d330d890c0 setup minimal github action
Just so we can experiment with it on branches
2019-11-12 14:31:38 +01:00
Alexander Krotov
c6369b1c5a Implement TryFrom instead of TryInto
TryInto is derived automatically and its documentation recommends implementing TryFrom.
2019-11-10 15:38:58 +00:00
Alexander Krotov
bfa0f9d911 Use the first subkey for encryption instead of the primary key 2019-11-10 16:32:09 +01:00
B. Petersen
154cb2db83 add missing DC_STR_DEVICE_MESSAGES 2019-11-10 16:31:12 +01:00
135 changed files with 11476 additions and 31269 deletions

View File

@@ -15,7 +15,7 @@ restore-workspace: &restore-workspace
restore-cache: &restore-cache
restore_cache:
keys:
- cargo-v2-{{ checksum "rust-toolchain" }}-{{ checksum "Cargo.toml" }}-{{ checksum "Cargo.lock" }}-{{ arch }}
- cargo-v3-{{ checksum "rust-toolchain" }}-{{ checksum "Cargo.toml" }}-{{ checksum "Cargo.lock" }}-{{ arch }}
- repo-source-{{ .Branch }}-{{ .Revision }}
commands:
@@ -36,20 +36,13 @@ jobs:
executor: default
steps:
- checkout
- run:
name: Update submodules
command: git submodule update --init --recursive
- run:
name: Calculate dependencies
command: cargo generate-lockfile
- restore_cache:
keys:
- cargo-v2-{{ checksum "rust-toolchain" }}-{{ checksum "Cargo.toml" }}-{{ checksum "Cargo.lock" }}-{{ arch }}
- cargo-v3-{{ checksum "rust-toolchain" }}-{{ checksum "Cargo.toml" }}-{{ checksum "Cargo.lock" }}-{{ arch }}
- run: rustup install $(cat rust-toolchain)
- run: rustup default $(cat rust-toolchain)
- run: rustup component add --toolchain $(cat rust-toolchain) rustfmt
- run: rustup component add --toolchain $(cat rust-toolchain) clippy-preview
- run: cargo update
- run: cargo fetch
- run: rustc +stable --version
- run: rustc +$(cat rust-toolchain) --version
@@ -60,7 +53,7 @@ jobs:
paths:
- crate
- save_cache:
key: cargo-v2-{{ checksum "rust-toolchain" }}-{{ checksum "Cargo.toml" }}-{{ checksum "Cargo.lock" }}-{{ arch }}
key: cargo-v3-{{ checksum "rust-toolchain" }}-{{ checksum "Cargo.toml" }}-{{ checksum "Cargo.lock" }}-{{ arch }}
paths:
- "~/.cargo"
- "~/.rustup"
@@ -91,7 +84,6 @@ jobs:
curl https://sh.rustup.rs -sSf | sh -s -- -y
- run: rustup install $(cat rust-toolchain)
- run: rustup default $(cat rust-toolchain)
- run: cargo update
- run: cargo fetch
- run:
name: Test
@@ -121,39 +113,54 @@ jobs:
steps:
- checkout
- run: bash ci_scripts/run-doxygen.sh
- run: mkdir -p workspace/c-docs
- run: mkdir -p workspace/c-docs
- run: cp -av deltachat-ffi/{html,xml} workspace/c-docs/
- persist_to_workspace:
root: workspace
paths:
- c-docs
build_test_docs_wheel:
docker:
- image: deltachat/coredeps
environment:
TESTS: 1
DOCS: 1
working_directory: /mnt/crate
# build_test_docs_wheel:
# 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: |
# mkdir -p workspace/python
# # 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:
# # - c-docs
# - py-docs
# - wheelhouse
remote_tests_rust:
machine: true
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: |
mkdir -p workspace/python
# 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:
# - c-docs
- py-docs
- wheelhouse
- checkout
- run: ci_scripts/remote_tests_rust.sh
remote_tests_python:
machine: true
steps:
- checkout
#- attach_workspace:
# at: workspace
- run: ci_scripts/remote_tests_python.sh
# workspace/py-docs workspace/wheelhouse workspace/c-docs
upload_docs_wheels:
machine: true
@@ -172,7 +179,7 @@ jobs:
- *restore-cache
- run:
name: Run cargo clippy
command: cargo clippy --all
command: cargo clippy
workflows:
@@ -180,27 +187,29 @@ workflows:
test:
jobs:
- cargo_fetch
# - cargo_fetch
- remote_tests_rust
- remote_tests_python
# - upload_docs_wheels:
# requires:
# - build_test_docs_wheel
# - build_doxygen
# - rustfmt:
# requires:
# - cargo_fetch
# - clippy:
# requires:
# - cargo_fetch
- build_doxygen
- build_test_docs_wheel:
requires:
- cargo_fetch
- upload_docs_wheels:
requires:
- build_test_docs_wheel
- build_doxygen
- rustfmt:
requires:
- cargo_fetch
- clippy:
requires:
- cargo_fetch
# Linux Desktop 64bit
- test_x86_64-unknown-linux-gnu:
requires:
- cargo_fetch
# - test_x86_64-unknown-linux-gnu:
# requires:
# - cargo_fetch
# Linux Desktop 32bit
# - test_i686-unknown-linux-gnu:

47
.github/workflows/code-quality.yml vendored Normal file
View File

@@ -0,0 +1,47 @@
on: push
name: Code Quality
jobs:
check:
name: Check
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v1
- uses: actions-rs/toolchain@v1
with:
profile: minimal
toolchain: nightly-2019-11-06
override: true
- uses: actions-rs/cargo@v1
with:
command: check
fmt:
name: Rustfmt
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v1
- uses: actions-rs/toolchain@v1
with:
profile: minimal
toolchain: nightly-2019-11-06
override: true
- run: rustup component add rustfmt
- uses: actions-rs/cargo@v1
with:
command: fmt
args: --all -- --check
run_clippy:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v1
- uses: actions-rs/toolchain@v1
with:
toolchain: nightly-2019-11-06
components: clippy
override: true
- uses: actions-rs/clippy-check@v1
with:
token: ${{ secrets.GITHUB_TOKEN }}
args: --all-features

2
.gitignore vendored
View File

@@ -23,3 +23,5 @@ python/liveconfig*
# ignore doxgen generated files
deltachat-ffi/html
deltachat-ffi/xml
.rsynclist

View File

@@ -1,5 +1,133 @@
# Changelog
## 1.0.0-beta.15
- fix #994 attachment appeared doubled in chats (and where actually
downloaded after smtp-send). @hpk42
## 1.0.0-beta.14
- fix packaging issue with our rust-email fork, now we are tracking
master again there. hpk42
## 1.0.0-beta.13
- fix #976 -- unicode-issues in display-name of email addresses. @hpk42
- fix #985 group add/remove member bugs resulting in broken groups. @hpk42
- fix hanging IMAP connections -- we now detect with a 15second timeout
if we cannot terminate the IDLE IMAP protocol. @hpk42 @link2xt
- fix incoming multipart/mixed containing html, to show up as
attachments again. Fixes usage for simplebot which sends html
files for users to interact with the bot. @adbenitez @hpk42
- refinements to internal autocrypt-handling code, do not send
prefer-encrypt=nopreference as it is the default if no attribute
is present. @linkxt
- simplify, modularize and rustify several parts
of dc-core (general WIP). @link2xt @flub @hpk42 @r10s
- use async-email/async-smtp to handle SMTP connections, might
fix connection/reconnection issues. @link2xt
- more tests and refinements for dealing with blobstorage @flub @hpk42
- use a dedicated build-server for CI testing of core PRs
## 1.0.0-beta.12
- fix python bindings to use core for copying attachments to blobdir
and fix core to actually do it. @hpk42
## 1.0.0-beta.11
- trigger reconnect more often on imap error states. Should fix an
issue observed when trying to empty a folder. @hpk42
- un-split qr tests: we fixed qr-securejoin protocol flakyness
last weeks. @hpk42
## 1.0.0-beta.10
- fix grpid-determination from in-reply-to and references headers. @hpk42
- only send Autocrypt-gossip headers on encrypted messages. @dignifiedquire
- fix reply-to-encrypted message to also be encrypted. @hpk42
- remove last unsafe code from dc_receive_imf :) @hpk42
- add experimental new dc_chat_get_info_json FFI/API so that desktop devs
can play with using it. @jikstra
- fix encoding of subjects and attachment-filenames @hpk42
@dignifiedquire .
## 1.0.0-beta.9
- historic: we now use the mailparse crate and lettre-email to generate mime
messages. This got rid of mmime completely, the C2rust generated port of the libetpan
mime-parse -- IOW 22KLocs of cumbersome code removed! see
https://github.com/deltachat/deltachat-core-rust/pull/904#issuecomment-561163330
many thanks @dignifiedquire for making everybody's life easier
and @jonhoo (from rust-imap fame) for suggesting to use the mailparse crate :)
- lots of improvements and better error handling in many rust modules
thanks @link2xt @flub @r10s, @hpk42 and @dignifiedquire
- @r10s introduced a new device chat which has an initial
welcome message. See
https://c.delta.chat/classdc__context__t.html#a1a2aad98bd23c1d21ee42374e241f389
for the main new FFI-API.
- fix moving self-sent messages, thanks @r10s, @flub, @hpk42
- fix flakyness/sometimes-failing verified/join-protocols,
thanks @flub, @r10s, @hpk42
- fix reply-to-encrypted message to keep encryption
- new DC_EVENT_SECUREJOIN_MEMBER_ADDED event
- many little fixes and rustifications (@link2xt, @flub, @hpk42)
## 1.0.0-beta.8
- now uses async-email/async-imap as the new base
which makes imap-idle interruptible and thus fixes
several issues around the imap thread being in zombie state .
thanks @dignifiedquire, @hpk42 and @link2xt.
- fixes imap-protocol parsing bugs that lead to infinitely
repeated crashing while trying to receive messages with
a subjec that contained non-utf8. thanks @link2xt
- fixed logic to find encryption subkey -- previously
delta chat would use the primary key for encryption
(which works with RSA but not ECC). thanks @link2xt
- introduce a new device chat where core and UIs can
add "device" messages. Android uses it for an initial
welcome message. thanks @r10s
- fix time smearing (when two message are virtually send
in the same second, there would be misbehaviour because
we didn't persist smeared time). thanks @r10s
- fix double-dotted extensions like .html.zip or .tar.gz
to not mangle them when creating blobfiles. thanks @flub
- fix backup/exports where the wrong sql file would be modified,
leading to problems when exporting twice. thanks @hpk42
- several other little fixes and improvements
## 1.0.0-beta.7
- fix location-streaming #782

1604
Cargo.lock generated

File diff suppressed because it is too large Load Diff

View File

@@ -1,27 +1,29 @@
[package]
name = "deltachat"
version = "1.0.0-beta.7"
version = "1.0.0-beta.15"
authors = ["Delta Chat Developers (ML) <delta@codespeak.net>"]
edition = "2018"
license = "MPL"
[dependencies]
deltachat_derive = { path = "./deltachat_derive" }
mmime = { version = "0.1.2", path = "./mmime" }
libc = "0.2.51"
pgp = { version = "0.2.3", default-features = false }
hex = "0.3.2"
pgp = { version = "0.4.0", default-features = false }
hex = "0.4.0"
sha2 = "0.8.0"
rand = "0.6.5"
smallvec = "0.6.9"
reqwest = "0.9.15"
num-derive = "0.2.5"
rand = "0.7.0"
smallvec = "1.0.0"
reqwest = { version = "0.9.15" }
num-derive = "0.3.0"
num-traits = "0.2.6"
native-tls = "0.2.3"
lettre = { git = "https://github.com/deltachat/lettre", branch = "master" }
imap = { git = "https://github.com/deltachat/rust-imap", branch = "master" }
base64 = "0.10"
async-smtp = { git = "https://github.com/async-email/async-smtp", branch = "master" }
email = { git = "https://github.com/deltachat/rust-email", branch = "master" }
lettre_email = { git = "https://github.com/deltachat/lettre", branch = "feat/mail" }
async-imap = { git = "https://github.com/async-email/async-imap", branch="native_tls", default-features = false, features = ["tls_native"] }
async-native-tls = "0.1.1"
async-std = { version = "1.0", features = ["unstable"] }
base64 = "0.11"
charset = "0.1"
percent-encoding = "2.0"
serde = { version = "1.0", features = ["derive"] }
@@ -29,6 +31,7 @@ serde_json = "1.0"
chrono = "0.4.6"
failure = "0.1.5"
failure_derive = "0.1.5"
indexmap = "1.3.0"
# TODO: make optional
rustyline = "4.1.0"
lazy_static = "1.4.0"
@@ -43,12 +46,15 @@ backtrace = "0.3.33"
byteorder = "1.3.1"
itertools = "0.8.0"
image-meta = "0.1.0"
quick-xml = "0.15.0"
quick-xml = "0.17.1"
escaper = "0.1.0"
bitflags = "1.1.0"
jetscii = "0.4.4"
debug_stub_derive = "0.3.0"
sanitize-filename = "0.2.1"
stop-token = { version = "0.1.1", features = ["unstable"] }
mailparse = "0.10.1"
encoded-words = { git = "https://github.com/async-email/encoded-words", branch="master" }
native-tls = "0.2.3"
[dev-dependencies]
tempfile = "3.0"
@@ -60,7 +66,6 @@ proptest = "0.9.4"
members = [
"deltachat-ffi",
"deltachat_derive",
"mmime",
]
[[example]]

View File

@@ -1,11 +1,9 @@
# Delta Chat Rust
> Project porting deltachat-core to rust
> Deltachat-core written in Rust
[![CircleCI build status][circle-shield]][circle] [![Appveyor build status][appveyor-shield]][appveyor]
Current commit on deltachat/deltachat-core: `12ef73c8e76185f9b78e844ea673025f56a959ab`.
## Installing Rust and Cargo
To download and install the official compiler for the Rust programming language, and the Cargo package manager, run the command in your user environment:
@@ -16,7 +14,7 @@ curl https://sh.rustup.rs -sSf | sh
## Using the CLI client
Compile and run Delta Chat Core using `cargo`:
Compile and run Delta Chat Core command line utility, using `cargo`:
```
cargo run --example repl -- /path/to/db
@@ -89,6 +87,15 @@ $ cargo test --all
$ cargo build -p deltachat_ffi --release
```
## Debugging environment variables
- `DCC_IMAP_DEBUG`: if set IMAP protocol commands and responses will be
printed
- `DCC_MIME_DEBUG`: if set outgoing and incoming message will be printed
### Expensive tests
Some tests are expensive and marked with `#[ignore]`, to run these

View File

@@ -8,7 +8,6 @@ install:
- set PATH=%PATH%;%USERPROFILE%\.cargo\bin
- rustc -vV
- cargo -vV
- cargo update
build: false

Binary file not shown.

Before

Width:  |  Height:  |  Size: 3.6 KiB

After

Width:  |  Height:  |  Size: 4.8 KiB

83
assets/icon-device.svg Normal file
View File

@@ -0,0 +1,83 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<svg
xmlns:dc="http://purl.org/dc/elements/1.1/"
xmlns:cc="http://creativecommons.org/ns#"
xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
xmlns:svg="http://www.w3.org/2000/svg"
xmlns="http://www.w3.org/2000/svg"
xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
inkscape:export-ydpi="409.60001"
inkscape:export-xdpi="409.60001"
inkscape:export-filename="/Users/bpetersen/projects/deltachat-core-rust/assets/icon-device.png"
version="1.0"
width="60"
height="60"
viewBox="0 0 45 45"
preserveAspectRatio="xMidYMid meet"
id="svg4344"
sodipodi:docname="icon-device.svg"
inkscape:version="1.0beta1 (32d4812, 2019-09-19)">
<defs
id="defs4348" />
<sodipodi:namedview
inkscape:snap-global="false"
pagecolor="#ffffff"
bordercolor="#666666"
inkscape:document-rotation="0"
borderopacity="1"
objecttolerance="10"
gridtolerance="10"
guidetolerance="10"
inkscape:pageopacity="0"
inkscape:pageshadow="2"
inkscape:window-width="1600"
inkscape:window-height="1035"
id="namedview4346"
showgrid="false"
units="px"
inkscape:zoom="3.959798"
inkscape:cx="28.322498"
inkscape:cy="24.898474"
inkscape:window-x="45"
inkscape:window-y="23"
inkscape:window-maximized="0"
inkscape:current-layer="svg4344" />
<metadata
id="metadata4336">
Created by potrace 1.15, written by Peter Selinger 2001-2017
<rdf:RDF>
<cc:Work
rdf:about="">
<dc:format>image/svg+xml</dc:format>
<dc:type
rdf:resource="http://purl.org/dc/dcmitype/StillImage" />
<dc:title />
</cc:Work>
</rdf:RDF>
</metadata>
<rect
y="-4.4408921e-16"
x="0"
height="45"
width="45"
id="rect860"
style="opacity:1;fill:#76868b;fill-opacity:1;stroke-width:0.819271" />
<g
fill="#000000"
stroke="none"
style="fill:#ffffff;fill-opacity:1"
transform="matrix(0.00255113,0,0,-0.00255113,5.586152,38.200477)"
id="g4342">
<path
style="fill:#ffffff;fill-opacity:1"
d="m 8175,12765 c -703,-114 -1248,-608 -1387,-1258 -17,-82 -21,-136 -22,-277 0,-202 15,-307 70,-470 149,-446 499,-733 1009,-828 142,-26 465,-23 619,6 691,131 1201,609 1328,1244 31,158 31,417 0,565 -114,533 -482,889 -1038,1004 -133,27 -448,35 -579,14 z"
id="path4338"
inkscape:connector-curvature="0" />
<path
style="fill:#ffffff;fill-opacity:1"
d="m 7070,9203 c -212,-20 -275,-27 -397,-48 -691,-117 -1400,-444 -2038,-940 -182,-142 -328,-270 -585,-517 -595,-571 -911,-974 -927,-1181 -6,-76 11,-120 69,-184 75,-80 159,-108 245,-79 109,37 263,181 632,595 539,606 774,826 1035,969 135,75 231,105 341,106 82,1 94,-2 138,-27 116,-68 161,-209 122,-376 -9,-36 -349,-868 -757,-1850 -407,-982 -785,-1892 -838,-2021 -287,-694 -513,-1389 -615,-1889 -70,-342 -90,-683 -52,-874 88,-440 381,-703 882,-792 124,-23 401,-30 562,-16 783,69 1674,461 2561,1125 796,596 1492,1354 1607,1751 43,146 -33,308 -168,360 -61,23 -100,15 -173,-36 -105,-74 -202,-170 -539,-529 -515,-551 -762,-783 -982,-927 -251,-164 -437,-186 -543,-65 -56,64 -74,131 -67,247 13,179 91,434 249,815 135,324 1588,4102 1646,4280 106,325 151,561 159,826 9,281 -22,463 -112,652 -58,122 -114,199 -211,292 -245,233 -582,343 -1044,338 -91,-1 -181,-3 -200,-5 z"
id="path4340"
inkscape:connector-curvature="0" />
</g>
</svg>

After

Width:  |  Height:  |  Size: 3.4 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.9 KiB

View File

@@ -0,0 +1,71 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<svg
xmlns:dc="http://purl.org/dc/elements/1.1/"
xmlns:cc="http://creativecommons.org/ns#"
xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
xmlns:svg="http://www.w3.org/2000/svg"
xmlns="http://www.w3.org/2000/svg"
xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
inkscape:export-ydpi="409.60001"
inkscape:export-xdpi="409.60001"
inkscape:export-filename="/home/kerle/test-icon.png"
version="1.0"
width="60"
height="60"
viewBox="0 0 45 45"
preserveAspectRatio="xMidYMid meet"
id="svg4344"
sodipodi:docname="icon-saved-messages.svg"
inkscape:version="1.0beta1 (32d4812, 2019-09-19)">
<defs
id="defs4348" />
<sodipodi:namedview
pagecolor="#ffffff"
bordercolor="#666666"
inkscape:document-rotation="0"
borderopacity="1"
objecttolerance="10"
gridtolerance="10"
guidetolerance="10"
inkscape:pageopacity="0"
inkscape:pageshadow="2"
inkscape:window-width="1395"
inkscape:window-height="855"
id="namedview4346"
showgrid="false"
units="px"
inkscape:zoom="4"
inkscape:cx="29.308676"
inkscape:cy="49.03624"
inkscape:window-x="89"
inkscape:window-y="108"
inkscape:window-maximized="0"
inkscape:current-layer="svg4344"
inkscape:lockguides="false" />
<metadata
id="metadata4336">
Created by potrace 1.15, written by Peter Selinger 2001-2017
<rdf:RDF>
<cc:Work
rdf:about="">
<dc:format>image/svg+xml</dc:format>
<dc:type
rdf:resource="http://purl.org/dc/dcmitype/StillImage" />
<dc:title />
</cc:Work>
</rdf:RDF>
</metadata>
<rect
y="0"
x="0"
height="45"
width="45"
id="rect1420"
style="fill:#87aade;fill-opacity:1;stroke:none;stroke-width:0.968078" />
<path
id="rect846"
style="fill:#ffffff;stroke-width:0.58409804"
d="M 13.5,7.5 V 39 h 0.08654 L 22.533801,29.370239 31.482419,39 h 0.01758 V 7.5 Z m 9.004056,4.108698 1.879508,4.876388 5.039514,0.359779 -3.879358,3.363728 1.227764,5.095749 -4.276893,-2.796643 -4.280949,2.788618 1.237229,-5.093073 -3.873949,-3.371754 5.040866,-0.350417 z"
inkscape:connector-curvature="0" />
</svg>

After

Width:  |  Height:  |  Size: 2.3 KiB

BIN
assets/welcome-image.jpg Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 113 KiB

View File

@@ -1,52 +1,46 @@
# Continuous Integration Scripts for Delta Chat
Continuous Integration is run through CircleCI
but is largely independent of it.
Continuous Integration, run through CircleCI and an own build machine.
## Description of scripts
- `../.circleci/config.yml` describing the build jobs that are run
by Circle-CI
- `remote_tests_python.sh` rsyncs to a build machine and runs
`run-python-test.sh` remotely on the build machine.
- `remote_tests_rust.sh` rsyncs to the build machine and runs
`run-rust-test.sh` remotely on the build machine.
- `doxygen/Dockerfile` specifies an image that contains
the doxygen tool which is used by `run-doxygen.sh`
to generate C-docs which are then uploaded
via `ci_upload.sh` to `https://c.delta.chat/_unofficial_unreleased_docs/<BRANCH>`
(and the master branch is linked to https://c.delta.chat proper).
## Generating docker containers for performing build step work
## Triggering runs on the build machine locally (fast!)
All tests, docs and wheel building is run in docker containers:
There is experimental support for triggering a remote Python or Rust test run
from your local checkout/branch. You will need to be authorized to login to
the build machine (ask your friendly sysadmin on #deltachat freenode) to type::
- **coredeps/Dockerfile** specifies an image that contains all
of Delta Chat's core dependencies as linkable libraries.
It also serves to run python tests and build wheels
(binary packages for Python).
ci_scripts/manual_remote_tests.sh rust
ci_scripts/manual_remote_tests.sh python
- **doxygen/Dockerfile** specifies an image that contains
the doxygen tool which is used to generate C-docs.
This will **rsync** your current checkout to the remote build machine
(no need to commit before) and then run either rust or python tests.
To run tests locally you can pull existing images from "docker.io",
the hub for sharing Docker images::
# Outdated files (for later re-use)
docker pull deltachat/coredeps
docker pull deltachat/doxygen
`coredeps/Dockerfile` specifies an image that contains all
of Delta Chat's core dependencies. It used to run
python tests and build wheels (binary packages for Python)
or you can build the docker images yourself locally
You can build the docker images yourself locally
to avoid the relatively large download::
cd ci_scripts # where all CI things are
docker build -t deltachat/coredeps docker-coredeps
docker build -t deltachat/doxygen docker-doxygen
## ci_run.sh (main entrypoint called by circle-ci)
Once you have the docker images available
you can run python testing, documentation generation
and building binary wheels::
sh DOCS=1 TESTS=1 ci_scripts/ci_run.sh
## ci_upload.sh (uploading artifacts on success)
- python docs to `https://py.delta.chat/_unofficial_unreleased_docs/<BRANCH>`
- doxygen docs to `https://c.delta.chat/_unofficial_unreleased_docs/<BRANCH>`
- python wheels to `https://m.devpi.net/dc/<BRANCH>`
so that you install fully self-contained wheels like this:
`pip install -U -i https://m.devpi.net/dc/<BRANCH> deltachat`

View File

@@ -14,20 +14,24 @@ DOXYDOCDIR=${3:?directory where doxygen docs to be found}
export BRANCH=${CIRCLE_BRANCH:?specify branch for uploading purposes}
# python docs to py.delta.chat
ssh -o StrictHostKeyChecking=no -o UserKnownHostsFile=/dev/null delta@py.delta.chat mkdir -p build/${BRANCH}
rsync -avz \
-e "ssh -o StrictHostKeyChecking=no -o UserKnownHostsFile=/dev/null" \
"$PYDOCDIR/html/" \
delta@py.delta.chat:build/${BRANCH}
# DISABLED: python docs to py.delta.chat
#ssh -o StrictHostKeyChecking=no -o UserKnownHostsFile=/dev/null delta@py.delta.chat mkdir -p build/${BRANCH}
#rsync -avz \
# -e "ssh -o StrictHostKeyChecking=no -o UserKnownHostsFile=/dev/null" \
# "$PYDOCDIR/html/" \
# delta@py.delta.chat:build/${BRANCH}
# C docs to c.delta.chat
ssh -o StrictHostKeyChecking=no -o UserKnownHostsFile=/dev/null delta@c.delta.chat mkdir -p build-c/${BRANCH}
ssh -o BatchMode=yes -o StrictHostKeyChecking=no -o UserKnownHostsFile=/dev/null delta@c.delta.chat mkdir -p build-c/${BRANCH}
rsync -avz \
-e "ssh -o StrictHostKeyChecking=no -o UserKnownHostsFile=/dev/null" \
"$DOXYDOCDIR/html/" \
delta@c.delta.chat:build-c/${BRANCH}
exit 0
# OUTDATED -- for re-use from python release-scripts
echo -----------------------
echo upload wheels
echo -----------------------

View File

@@ -5,16 +5,6 @@ RUN echo /usr/local/lib64 > /etc/ld.so.conf.d/local.conf && \
echo /usr/local/lib >> /etc/ld.so.conf.d/local.conf
ENV PKG_CONFIG_PATH /usr/local/lib64/pkgconfig:/usr/local/lib/pkgconfig
ENV PIP_DISABLE_PIP_VERSION_CHECK 1
# Install python tools (auditwheels,tox, ...)
ADD deps/build_python.sh /builder/build_python.sh
RUN mkdir tmp1 && cd tmp1 && bash /builder/build_python.sh && cd .. && rm -r tmp1
# Install Rust nightly
ADD deps/build_rust.sh /builder/build_rust.sh
RUN mkdir tmp1 && cd tmp1 && bash /builder/build_rust.sh && cd .. && rm -r tmp1
# Install a recent Perl, needed to install OpenSSL
ADD deps/build_perl.sh /builder/build_perl.sh
RUN mkdir tmp1 && cd tmp1 && bash /builder/build_perl.sh && cd .. && rm -r tmp1
@@ -23,3 +13,12 @@ RUN mkdir tmp1 && cd tmp1 && bash /builder/build_perl.sh && cd .. && rm -r tmp1
ADD deps/build_openssl.sh /builder/build_openssl.sh
RUN mkdir tmp1 && cd tmp1 && bash /builder/build_openssl.sh && cd .. && rm -r tmp1
ENV PIP_DISABLE_PIP_VERSION_CHECK 1
# Install python tools (auditwheels,tox, ...)
ADD deps/build_python.sh /builder/build_python.sh
RUN mkdir tmp1 && cd tmp1 && bash /builder/build_python.sh && cd .. && rm -r tmp1
# Install Rust nightly
ADD deps/build_rust.sh /builder/build_rust.sh
RUN mkdir tmp1 && cd tmp1 && bash /builder/build_rust.sh && cd .. && rm -r tmp1

View File

@@ -1,11 +1,11 @@
#!/bin/bash
PERL_VERSION=5.28.0
PERL_SHA256=7e929f64d4cb0e9d1159d4a59fc89394e27fa1f7004d0836ca0d514685406ea8
PERL_VERSION=5.30.0
# PERL_SHA256=7e929f64d4cb0e9d1159d4a59fc89394e27fa1f7004d0836ca0d514685406ea8
curl -O https://www.cpan.org/src/5.0/perl-${PERL_VERSION}.tar.gz
echo "${PERL_SHA256} perl-${PERL_VERSION}.tar.gz" | sha256sum -c -
tar xzf perl-${PERL_VERSION}.tar.gz
cd perl-${PERL_VERSION}
# echo "${PERL_SHA256} perl-${PERL_VERSION}.tar.gz" | sha256sum -c -
tar -xzf perl-${PERL_VERSION}.tar.gz
cd perl-${PERL_VERSION}
./Configure -de
make

View File

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

View File

@@ -0,0 +1,9 @@
#!/bin/bash
set -xe
export CIRCLE_JOB=remote_tests_${1:?need to specify 'rust' or 'python'}
export CIRCLE_BUILD_NUM=$USER
export CIRCLE_BRANCH=`git branch | grep \* | cut -d ' ' -f2`
export CIRCLE_PROJECT_REPONAME=$(basename `git rev-parse --show-toplevel`)
time bash ci_scripts/$CIRCLE_JOB.sh

View File

@@ -0,0 +1,77 @@
name: CI
on:
pull_request:
push:
env:
RUSTFLAGS: -Dwarnings
jobs:
build_and_test:
name: Build and test
runs-on: ${{ matrix.os }}
strategy:
matrix:
os: [ubuntu-latest, windows-latest, macOS-latest]
rust: [nightly]
steps:
- uses: actions/checkout@master
- name: Install ${{ matrix.rust }}
uses: actions-rs/toolchain@v1
with:
toolchain: ${{ matrix.rust }}
override: true
- name: check
uses: actions-rs/cargo@v1
if: matrix.rust == 'nightly'
with:
command: check
args: --all --bins --examples --tests
- name: tests
uses: actions-rs/cargo@v1
with:
command: test
args: --all
- name: tests ignored
uses: actions-rs/cargo@v1
with:
command: test
args: --all --release -- --ignored
check_fmt:
name: Checking fmt and docs
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@master
- uses: actions-rs/toolchain@v1
with:
profile: minimal
toolchain: nightly
override: true
components: rustfmt
- name: fmt
run: cargo fmt --all -- --check
# clippy_check:
# name: Clippy check
# runs-on: ubuntu-latest
#
# steps:
# - uses: actions/checkout@v1
# - uses: actions-rs/toolchain@v1
# with:
# profile: minimal
# toolchain: nightly
# override: true
# components: clippy
#
# - name: clippy
# run: cargo clippy --all

View File

@@ -0,0 +1,46 @@
#!/bin/bash
export BRANCH=${CIRCLE_BRANCH:?branch to build}
export REPONAME=${CIRCLE_PROJECT_REPONAME:?repository name}
export SSHTARGET=${SSHTARGET-ci@b1.delta.chat}
# we construct the BUILDDIR such that we can easily share the
# CARGO_TARGET_DIR between runs ("..")
export BUILDDIR=ci_builds/$REPONAME/$BRANCH/${CIRCLE_JOB:?jobname}/${CIRCLE_BUILD_NUM:?circle-build-number}
echo "--- Copying files to $SSHTARGET:$BUILDDIR"
set -xe
ssh -oBatchMode=yes -oStrictHostKeyChecking=no $SSHTARGET mkdir -p "$BUILDDIR"
git ls-files >.rsynclist
# we seem to need .git for setuptools_scm versioning
find .git >>.rsynclist
rsync --delete --files-from=.rsynclist -az ./ "$SSHTARGET:$BUILDDIR"
set +x
echo "--- Running $CIRCLE_JOB remotely"
ssh $SSHTARGET <<_HERE
set +x -e
cd $BUILDDIR
# let's share the target dir with our last run on this branch/job-type
# cargo will make sure to block/unblock us properly
export CARGO_TARGET_DIR=\`pwd\`/../target
export TARGET=release
export DCC_PY_LIVECONFIG=$DCC_PY_LIVECONFIG
#we rely on tox/virtualenv being available in the host
#rm -rf virtualenv venv
#virtualenv -q -p python3.7 venv
#source venv/bin/activate
#pip install -q tox virtualenv
set -x
which python
source \$HOME/venv/bin/activate
which python
bash ci_scripts/run-python-test.sh
_HERE

32
ci_scripts/remote_tests_rust.sh Executable file
View File

@@ -0,0 +1,32 @@
#!/bin/bash
export BRANCH=${CIRCLE_BRANCH:?branch to build}
export REPONAME=${CIRCLE_PROJECT_REPONAME:?repository name}
export SSHTARGET=${SSHTARGET-ci@b1.delta.chat}
# we construct the BUILDDIR such that we can easily share the
# CARGO_TARGET_DIR between runs ("..")
export BUILDDIR=ci_builds/$REPONAME/$BRANCH/${CIRCLE_JOB:?jobname}/${CIRCLE_BUILD_NUM:?circle-build-number}
set -e
echo "--- Copying files to $SSHTARGET:$BUILDDIR"
ssh -oBatchMode=yes -oStrictHostKeyChecking=no $SSHTARGET mkdir -p "$BUILDDIR"
git ls-files >.rsynclist
rsync --delete --files-from=.rsynclist -az ./ "$SSHTARGET:$BUILDDIR"
echo "--- Running $CIRCLE_JOB remotely"
ssh $SSHTARGET <<_HERE
set +x -e
cd $BUILDDIR
# let's share the target dir with our last run on this branch/job-type
# cargo will make sure to block/unblock us properly
export CARGO_TARGET_DIR=\`pwd\`/../target
export TARGET=x86_64-unknown-linux-gnu
export RUSTC_WRAPPER=sccache
bash ci_scripts/run-rust-test.sh
_HERE

24
ci_scripts/run-python-test.sh Executable file
View File

@@ -0,0 +1,24 @@
#!/bin/bash
#
# Run functional tests for Delta Chat core using the python bindings
# and tox/pytest.
set -e -x
# for core-building and python install step
export DCC_RS_TARGET=release
export DCC_RS_DEV=`pwd`
cd python
python install_python_bindings.py onlybuild
# remove and inhibit writing PYC files
rm -rf tests/__pycache__
rm -rf src/deltachat/__pycache__
export PYTHONDONTWRITEBYTECODE=1
# run python tests (tox invokes pytest to run tests in python/tests)
#TOX_PARALLEL_NO_SPINNER=1 tox -e lint,doc
tox -e lint
tox -e doc,py37

View File

@@ -2,7 +2,7 @@
set -ex
export RUST_TEST_THREADS=1
#export RUST_TEST_THREADS=1
export RUST_BACKTRACE=1
export RUSTFLAGS='--deny warnings'
export OPT="--target=$TARGET"
@@ -37,7 +37,9 @@ else
export OPT_RELEASE_IGNORED="${OPT_RELEASE} -- --ignored"
fi
# Run all the test configurations:
# Run all the test configurations
# RUSTC_WRAPPER=SCCACHE seems to destroy parallelism / prolong the test
unset RUSTC_WRAPPER
$CARGO_CMD $CARGO_SUBCMD $OPT
$CARGO_CMD $CARGO_SUBCMD $OPT_RELEASE
$CARGO_CMD $CARGO_SUBCMD $OPT_RELEASE_IGNORED

View File

@@ -1,6 +1,6 @@
[package]
name = "deltachat_ffi"
version = "1.0.0-beta.7"
version = "1.0.0-beta.15"
description = "Deltachat FFI"
authors = ["Delta Chat Developers (ML) <delta@codespeak.net>"]
edition = "2018"
@@ -20,6 +20,7 @@ deltachat-provider-database = "0.2.1"
libc = "0.2"
human-panic = "1.0.1"
num-traits = "0.2.6"
failure = "0.1.6"
[features]
default = ["vendored", "nightly", "ringbuf"]

View File

@@ -994,11 +994,20 @@ uint32_t dc_get_chat_id_by_contact_id (dc_context_t* context, uint32_t co
*
* Example:
* ~~~
* char* blobdir = dc_get_blobdir(context);
* char* file_to_send = mprintf("%s/%s", blobdir, "send.mp4")
*
* dc_msg_t* msg = dc_msg_new(context, DC_MSG_VIDEO);
* dc_msg_set_file(msg, "/file/to/send.mp4", NULL);
* dc_msg_set_file(msg, file_to_send, NULL);
* dc_prepare_msg(context, chat_id, msg);
* // ... after /file/to/send.mp4 is ready:
*
* // ... create the file ...
*
* dc_send_msg(context, chat_id, msg);
*
* dc_msg_unref(msg);
* free(file_to_send);
* dc_str_unref(file_to_send);
* ~~~
*
* @memberof dc_context_t
@@ -1024,8 +1033,11 @@ uint32_t dc_prepare_msg (dc_context_t* context, uint32_t ch
* Example:
* ~~~
* dc_msg_t* msg = dc_msg_new(context, DC_MSG_IMAGE);
*
* dc_msg_set_file(msg, "/file/to/send.jpg", NULL);
* dc_send_msg(context, chat_id, msg);
*
* dc_msg_unref(msg);
* ~~~
*
* @memberof dc_context_t
@@ -1096,9 +1108,9 @@ void dc_set_draft (dc_context_t* context, uint32_t ch
* Add a message to the device-chat.
* Device-messages usually contain update information
* and some hints that are added during the program runs, multi-device etc.
*
* Device-messages may be added from the core,
* however, with this function, this can be done from the ui as well.
* The device-message may be defined by a label;
* if a message with the same label was added or skipped before,
* the message is not added again, even if the message was deleted in between.
* If needed, the device-chat is created before.
*
* Sends the event #DC_EVENT_MSGS_CHANGED on success.
@@ -1106,11 +1118,71 @@ void dc_set_draft (dc_context_t* context, uint32_t ch
*
* @memberof dc_context_t
* @param context The context as created by dc_context_new().
* @param label A unique name for the message to add.
* The label is typically not displayed to the user and
* must be created from the characters `A-Z`, `a-z`, `0-9`, `_` or `-`.
* If you pass NULL here, the message is added unconditionally.
* @param msg Message to be added to the device-chat.
* The message appears to the user as an incoming message.
* @return The ID of the added message.
* If you pass NULL here, only the given label will be added
* and block adding messages with that label in the future.
* @return The ID of the just added message,
* if the message was already added or no message to add is given, 0 is returned.
*
* Example:
* ~~~
* dc_msg_t* welcome_msg = dc_msg_new(DC_MSG_TEXT);
* dc_msg_set_text(welcome_msg, "great that you give this app a try!");
*
* dc_msg_t* changelog_msg = dc_msg_new(DC_MSG_TEXT);
* dc_msg_set_text(changelog_msg, "we have added 3 new emojis :)");
*
* if (dc_add_device_msg(context, "welcome", welcome_msg)) {
* // do not add the changelog on a new installations -
* // not now and not when this code is executed again
* dc_add_device_msg(context, "update-123", NULL);
* } else {
* // welcome message was not added now, this is an oder installation,
* // add a changelog
* dc_add_device_msg(context, "update-123", changelog_msg);
* }
*
* dc_msg_unref(changelog_msg);
* dc_msg_unref(welome_msg);
* ~~~
*/
uint32_t dc_add_device_msg (dc_context_t* context, dc_msg_t* msg);
uint32_t dc_add_device_msg (dc_context_t* context, const char* label, dc_msg_t* msg);
/**
* Init device-messages and saved-messages chat.
* This function adds the device-chat and saved-messages chat
* and adds one or more welcome or update-messages.
* The ui can add messages on its own using dc_add_device_msg() -
* for ordering, either before or after or even without calling this function.
*
* Chat and message creation is done only once.
* So if the user has manually deleted things, they won't be re-created
* (however, not seen device messages are added and may re-create the device-chat).
*
* @memberof dc_context_t
* @param context The context as created by dc_context_new().
* @return None.
*/
void dc_update_device_chats (dc_context_t* context);
/**
* Check if a device-message with a given label was ever added.
* Device-messages can be added dc_add_device_msg().
*
* @memberof dc_context_t
* @param context The context as created by dc_context_new().
* @param label Label of the message to check.
* @return 1=A message with this label was added at some point,
* 0=A message with this label was never added.
*/
int dc_was_device_msg_ever_added (dc_context_t* context, const char* label);
/**
@@ -1890,7 +1962,7 @@ void dc_imex (dc_context_t* context, int what, c
* }
* while (!configure_succeeded())
* }
* free(file);
* dc_str_unref(file);
* }
* ~~~
*
@@ -2545,6 +2617,25 @@ dc_lot_t* dc_chatlist_get_summary (const dc_chatlist_t* chatlist, siz
dc_context_t* dc_chatlist_get_context (dc_chatlist_t* chatlist);
/**
* Get info summary for a chat, in json format.
*
* The returned json string has the following key/values:
*
* id: chat id
* name: chat/group name
* color: color of this chat
* last-message-from: who sent the last message
* last-message-text: message (truncated)
* last-message-state: DC_STATE* constant
* last-message-date:
* avatar-path: path-to-blobfile
* is_verified: yes/no
* @return a utf8-encoded json string containing all requested info. Must be freed using dc_str_unref(). NULL is never returned.
*/
char* dc_chat_get_info_json (dc_context_t* context, size_t chat_id);
/**
* @class dc_chat_t
*
@@ -2738,9 +2829,7 @@ int dc_chat_is_self_talk (const dc_chat_t* chat);
* From the ui view, device-talks are not very special,
* the user can delete and forward messages, archive the chat, set notifications etc.
*
* Messages may be added from the core to the device chat,
* so the chat just pops up as usual.
* However, if needed the ui can also add messages using dc_add_device_msg()
* Messages can be added to the device-talk using dc_add_device_msg()
*
* @memberof dc_chat_t
* @param chat The chat object.
@@ -3556,11 +3645,15 @@ int dc_contact_is_blocked (const dc_contact_t* contact);
/**
* Same as dc_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.
* Check if a contact was verified. E.g. by a secure-join QR code scan
* and if the key has not changed since this verification.
*
* @private @memberof dc_context_t
* The UI may draw a checkbox or something like that beside verified contacts.
*
* @memberof dc_contact_t
* @param contact The contact object.
* @return 0: contact is not verified.
* 2: SELF and contact have verified their fingerprints in both directions; in the UI typically checkmarks are shown.
*/
int dc_contact_is_verified (dc_contact_t* contact);
@@ -3961,11 +4054,6 @@ int64_t dc_lot_get_timestamp (const dc_lot_t* lot);
*/
#define DC_CERTCK_STRICT 1
/**
* Accept invalid hostnames, but not invalid certificates.
*/
#define DC_CERTCK_ACCEPT_INVALID_HOSTNAMES 2
/**
* Accept invalid certificates, including self-signed ones
* or having incorrect hostname.
@@ -4350,6 +4438,16 @@ int64_t dc_lot_get_timestamp (const dc_lot_t* lot);
#define DC_EVENT_SECUREJOIN_JOINER_PROGRESS 2061
/**
* This event is sent out to the inviter when a joiner successfully joined a group.
*
* @param data1 (int) chat_id
* @param data2 (int) contact_id
* @return 0
*/
#define DC_EVENT_SECUREJOIN_MEMBER_ADDED 2062
/**
* @}
*/
@@ -4467,7 +4565,8 @@ void dc_array_add_id (dc_array_t*, uint32_t); // depreca
#define DC_STR_MSGLOCATIONDISABLED 65
#define DC_STR_LOCATION 66
#define DC_STR_STICKER 67
#define DC_STR_COUNT 67
#define DC_STR_DEVICE_MESSAGES 68
#define DC_STR_COUNT 68
/*
* @}

View File

@@ -25,13 +25,15 @@ use num_traits::{FromPrimitive, ToPrimitive};
use deltachat::constants::DC_MSG_ID_LAST_SPECIAL;
use deltachat::contact::Contact;
use deltachat::context::Context;
use deltachat::dc_tools::{
as_path, dc_strdup, to_opt_string_lossy, to_string_lossy, OsStrExt, StrExt,
};
use deltachat::message::MsgId;
use deltachat::stock::StockMessage;
use deltachat::*;
mod dc_array;
mod string;
use self::string::*;
// as C lacks a good and portable error handling,
// in general, the C Interface is forgiving wrt to bad parameters.
// - objects returned by some functions
@@ -175,6 +177,15 @@ impl ContextWrapper {
contact_id as uintptr_t,
progress as uintptr_t,
),
Event::SecurejoinMemberAdded {
chat_id,
contact_id,
} => ffi_cb(
self,
event_id,
chat_id as uintptr_t,
contact_id as uintptr_t,
),
}
}
None => 0,
@@ -461,7 +472,7 @@ pub unsafe extern "C" fn dc_perform_imap_jobs(context: *mut dc_context_t) {
}
let ffi_context = &*context;
ffi_context
.with_inner(|ctx| job::perform_imap_jobs(ctx))
.with_inner(|ctx| job::perform_inbox_jobs(ctx))
.unwrap_or(())
}
@@ -473,19 +484,20 @@ pub unsafe extern "C" fn dc_perform_imap_fetch(context: *mut dc_context_t) {
}
let ffi_context = &*context;
ffi_context
.with_inner(|ctx| job::perform_imap_fetch(ctx))
.with_inner(|ctx| job::perform_inbox_fetch(ctx))
.unwrap_or(())
}
#[no_mangle]
pub unsafe extern "C" fn dc_perform_imap_idle(context: *mut dc_context_t) {
// TODO rename function in co-ordination with UIs
if context.is_null() {
eprintln!("ignoring careless call to dc_perform_imap_idle()");
return;
}
let ffi_context = &*context;
ffi_context
.with_inner(|ctx| job::perform_imap_idle(ctx))
.with_inner(|ctx| job::perform_inbox_idle(ctx))
.unwrap_or(())
}
@@ -497,7 +509,7 @@ pub unsafe extern "C" fn dc_interrupt_imap_idle(context: *mut dc_context_t) {
}
let ffi_context = &*context;
ffi_context
.with_inner(|ctx| job::interrupt_imap_idle(ctx))
.with_inner(|ctx| job::interrupt_inbox_idle(ctx, true))
.unwrap_or(())
}
@@ -812,22 +824,68 @@ pub unsafe extern "C" fn dc_set_draft(
}
#[no_mangle]
pub unsafe extern "C" fn dc_add_device_msg(context: *mut dc_context_t, msg: *mut dc_msg_t) -> u32 {
if context.is_null() || msg.is_null() {
pub unsafe extern "C" fn dc_add_device_msg(
context: *mut dc_context_t,
label: *const libc::c_char,
msg: *mut dc_msg_t,
) -> u32 {
if context.is_null() || (label.is_null() && msg.is_null()) {
eprintln!("ignoring careless call to dc_add_device_msg()");
return 0;
}
let ffi_context = &mut *context;
let ffi_msg = &mut *msg;
let msg = if msg.is_null() {
None
} else {
let ffi_msg: &mut MessageWrapper = &mut *msg;
Some(&mut ffi_msg.message)
};
ffi_context
.with_inner(|ctx| {
chat::add_device_msg(ctx, &mut ffi_msg.message)
.unwrap_or_log_default(ctx, "Failed to add device message")
chat::add_device_msg(
ctx,
to_opt_string_lossy(label).as_ref().map(|x| x.as_str()),
msg,
)
.unwrap_or_log_default(ctx, "Failed to add device message")
})
.map(|msg_id| msg_id.to_u32())
.unwrap_or(0)
}
#[no_mangle]
pub unsafe extern "C" fn dc_update_device_chats(context: *mut dc_context_t) {
if context.is_null() {
eprintln!("ignoring careless call to dc_update_device_chats()");
return;
}
let ffi_context = &mut *context;
ffi_context
.with_inner(|ctx| {
ctx.update_device_chats()
.unwrap_or_log_default(ctx, "Failed to add device message")
})
.unwrap_or(())
}
#[no_mangle]
pub unsafe extern "C" fn dc_was_device_msg_ever_added(
context: *mut dc_context_t,
label: *const libc::c_char,
) -> libc::c_int {
if context.is_null() || label.is_null() {
eprintln!("ignoring careless call to dc_was_device_msg_ever_added()");
return 0;
}
let ffi_context = &mut *context;
ffi_context
.with_inner(|ctx| {
chat::was_device_msg_ever_added(ctx, &to_string_lossy(label)).unwrap_or(false)
as libc::c_int
})
.unwrap_or(0)
}
#[no_mangle]
pub unsafe extern "C" fn dc_get_draft(context: *mut dc_context_t, chat_id: u32) -> *mut dc_msg_t {
if context.is_null() {
@@ -2321,6 +2379,27 @@ pub unsafe extern "C" fn dc_chat_is_sending_locations(chat: *mut dc_chat_t) -> l
ffi_chat.chat.is_sending_locations() as libc::c_int
}
#[no_mangle]
pub unsafe extern "C" fn dc_chat_get_info_json(
context: *mut dc_context_t,
chat_id: u32,
) -> *mut libc::c_char {
if context.is_null() {
eprintln!("ignoring careless call to dc_chat_get_info_json()");
return "".strdup();
}
let ffi_context = &*context;
ffi_context
.with_inner(|ctx| match chat::get_info_json(ctx, chat_id) {
Ok(s) => s.strdup(),
Err(err) => {
error!(ctx, "get_info_json({}) returned: {}", chat_id, err);
return "".strdup();
}
})
.unwrap_or_else(|_| "".strdup())
}
// dc_msg_t
/// FFI struct for [dc_msg_t]

View File

@@ -2,7 +2,7 @@ extern crate deltachat_provider_database;
use std::ptr;
use deltachat::dc_tools::{to_string_lossy, StrExt};
use crate::string::{to_string_lossy, StrExt};
use deltachat_provider_database::StatusState;
#[no_mangle]

350
deltachat-ffi/src/string.rs Normal file
View File

@@ -0,0 +1,350 @@
use failure::Fail;
use std::ffi::{CStr, CString};
/// Duplicates a string
///
/// returns an empty string if NULL is given, never returns NULL (exits on errors)
///
/// # Examples
///
/// ```rust,norun
/// use deltachat::dc_tools::{dc_strdup, to_string_lossy};
/// unsafe {
/// let str_a = b"foobar\x00" as *const u8 as *const libc::c_char;
/// let str_a_copy = dc_strdup(str_a);
/// assert_eq!(to_string_lossy(str_a_copy), "foobar");
/// assert_ne!(str_a, str_a_copy);
/// }
/// ```
pub unsafe fn dc_strdup(s: *const libc::c_char) -> *mut libc::c_char {
let ret: *mut libc::c_char;
if !s.is_null() {
ret = libc::strdup(s);
assert!(!ret.is_null());
} else {
ret = libc::calloc(1, 1) as *mut libc::c_char;
assert!(!ret.is_null());
}
ret
}
/// Error type for the [OsStrExt] trait
#[derive(Debug, Fail, PartialEq)]
pub enum CStringError {
/// The string contains an interior null byte
#[fail(display = "String contains an interior null byte")]
InteriorNullByte,
/// The string is not valid Unicode
#[fail(display = "String is not valid unicode")]
NotUnicode,
}
/// Extra convenience methods on [std::ffi::OsStr] to work with `*libc::c_char`.
///
/// The primary function of this trait is to more easily convert
/// [OsStr], [OsString] or [Path] into pointers to C strings. This always
/// allocates a new string since it is very common for the source
/// string not to have the required terminal null byte.
///
/// It is implemented for `AsRef<std::ffi::OsStr>>` trait, which
/// allows any type which implements this trait to transparently use
/// this. This is how the conversion for [Path] works.
///
/// [OsStr]: std::ffi::OsStr
/// [OsString]: std::ffi::OsString
/// [Path]: std::path::Path
///
/// # Example
///
/// ```
/// use deltachat::dc_tools::{dc_strdup, OsStrExt};
/// let path = std::path::Path::new("/some/path");
/// let path_c = path.to_c_string().unwrap();
/// unsafe {
/// let mut c_ptr: *mut libc::c_char = dc_strdup(path_c.as_ptr());
/// }
/// ```
pub trait OsStrExt {
/// Convert a [std::ffi::OsStr] to an [std::ffi::CString]
///
/// This is useful to convert e.g. a [std::path::Path] to
/// [*libc::c_char] by using
/// [Path::as_os_str()](std::path::Path::as_os_str) and
/// [CStr::as_ptr()](std::ffi::CStr::as_ptr).
///
/// This returns [CString] and not [&CStr] because not all [OsStr]
/// slices end with a null byte, particularly those coming from
/// [Path] do not have a null byte and having to handle this as
/// the caller would defeat the point of this function.
///
/// On Windows this requires that the [OsStr] contains valid
/// unicode, which should normally be the case for a [Path].
///
/// [CString]: std::ffi::CString
/// [CStr]: std::ffi::CStr
/// [OsStr]: std::ffi::OsStr
/// [Path]: std::path::Path
///
/// # Errors
///
/// Since a C `*char` is terminated by a NULL byte this conversion
/// will fail, when the [OsStr] has an interior null byte. The
/// function will return
/// `[Err]([CStringError::InteriorNullByte])`. When converting
/// from a [Path] it should be safe to
/// [`.unwrap()`](std::result::Result::unwrap) this anyway since a
/// [Path] should not contain interior null bytes.
///
/// On windows when the string contains invalid Unicode
/// `[Err]([CStringError::NotUnicode])` is returned.
fn to_c_string(&self) -> Result<CString, CStringError>;
}
impl<T: AsRef<std::ffi::OsStr>> OsStrExt for T {
#[cfg(not(target_os = "windows"))]
fn to_c_string(&self) -> Result<CString, CStringError> {
use std::os::unix::ffi::OsStrExt;
CString::new(self.as_ref().as_bytes()).map_err(|err| match err {
std::ffi::NulError { .. } => CStringError::InteriorNullByte,
})
}
#[cfg(target_os = "windows")]
fn to_c_string(&self) -> Result<CString, CStringError> {
os_str_to_c_string_unicode(&self)
}
}
// Implementation for os_str_to_c_string on windows.
#[allow(dead_code)]
fn os_str_to_c_string_unicode(
os_str: &dyn AsRef<std::ffi::OsStr>,
) -> Result<CString, CStringError> {
match os_str.as_ref().to_str() {
Some(val) => CString::new(val.as_bytes()).map_err(|err| match err {
std::ffi::NulError { .. } => CStringError::InteriorNullByte,
}),
None => Err(CStringError::NotUnicode),
}
}
/// Convenience methods/associated functions for working with [CString]
///
/// This is helps transitioning from unsafe code.
pub trait CStringExt {
/// Create a new [CString], yolo style
///
/// This unwrap the result, panicking when there are embedded NULL
/// bytes.
fn yolo<T: Into<Vec<u8>>>(t: T) -> CString {
CString::new(t).expect("String contains null byte, can not be CString")
}
}
impl CStringExt for CString {}
/// Convenience methods to make transitioning from raw C strings easier.
///
/// To interact with (legacy) C APIs we often need to convert from
/// Rust strings to raw C strings. This can be clumsy to do correctly
/// and the compiler sometimes allows it in an unsafe way. These
/// methods make it more succinct and help you get it right.
pub trait StrExt {
/// Allocate a new raw C `*char` version of this string.
///
/// This allocates a new raw C string which must be freed using
/// `free`. It takes care of some common pitfalls with using
/// [CString.as_ptr].
///
/// [CString.as_ptr]: std::ffi::CString.as_ptr
///
/// # Panics
///
/// This function will panic when the original string contains an
/// interior null byte as this can not be represented in raw C
/// strings.
unsafe fn strdup(&self) -> *mut libc::c_char;
}
impl<T: AsRef<str>> StrExt for T {
unsafe fn strdup(&self) -> *mut libc::c_char {
let tmp = CString::yolo(self.as_ref());
dc_strdup(tmp.as_ptr())
}
}
pub fn to_string_lossy(s: *const libc::c_char) -> String {
if s.is_null() {
return "".into();
}
let cstr = unsafe { CStr::from_ptr(s) };
cstr.to_string_lossy().to_string()
}
pub fn to_opt_string_lossy(s: *const libc::c_char) -> Option<String> {
if s.is_null() {
return None;
}
Some(to_string_lossy(s))
}
/// Convert a C `*char` pointer to a [std::path::Path] slice.
///
/// This converts a `*libc::c_char` pointer to a [Path] slice. This
/// essentially has to convert the pointer to [std::ffi::OsStr] to do
/// so and thus is the inverse of [OsStrExt::to_c_string]. Just like
/// [OsStrExt::to_c_string] requires valid Unicode on Windows, this
/// requires that the pointer contains valid UTF-8 on Windows.
///
/// Because this returns a reference the [Path] silce can not outlive
/// the original pointer.
///
/// [Path]: std::path::Path
#[cfg(not(target_os = "windows"))]
pub fn as_path<'a>(s: *const libc::c_char) -> &'a std::path::Path {
assert!(!s.is_null(), "cannot be used on null pointers");
use std::os::unix::ffi::OsStrExt;
unsafe {
let c_str = std::ffi::CStr::from_ptr(s).to_bytes();
let os_str = std::ffi::OsStr::from_bytes(c_str);
std::path::Path::new(os_str)
}
}
// as_path() implementation for windows, documented above.
#[cfg(target_os = "windows")]
pub fn as_path<'a>(s: *const libc::c_char) -> &'a std::path::Path {
as_path_unicode(s)
}
// Implementation for as_path() on Windows.
//
// Having this as a separate function means it can be tested on unix
// too.
#[allow(dead_code)]
fn as_path_unicode<'a>(s: *const libc::c_char) -> &'a std::path::Path {
assert!(!s.is_null(), "cannot be used on null pointers");
let cstr = unsafe { CStr::from_ptr(s) };
let str = cstr.to_str().unwrap_or_else(|err| panic!("{}", err));
std::path::Path::new(str)
}
#[cfg(test)]
mod tests {
use super::*;
use libc::{free, strcmp};
#[test]
fn test_os_str_to_c_string_cwd() {
let some_dir = std::env::current_dir().unwrap();
some_dir.as_os_str().to_c_string().unwrap();
}
#[test]
fn test_os_str_to_c_string_unicode() {
let some_str = String::from("/some/valid/utf8");
let some_dir = std::path::Path::new(&some_str);
assert_eq!(
some_dir.as_os_str().to_c_string().unwrap(),
CString::new("/some/valid/utf8").unwrap()
);
}
#[test]
fn test_os_str_to_c_string_nul() {
let some_str = std::ffi::OsString::from("foo\x00bar");
assert_eq!(
some_str.to_c_string().err().unwrap(),
CStringError::InteriorNullByte
)
}
#[test]
fn test_path_to_c_string_cwd() {
let some_dir = std::env::current_dir().unwrap();
some_dir.to_c_string().unwrap();
}
#[test]
fn test_path_to_c_string_unicode() {
let some_str = String::from("/some/valid/utf8");
let some_dir = std::path::Path::new(&some_str);
assert_eq!(
some_dir.as_os_str().to_c_string().unwrap(),
CString::new("/some/valid/utf8").unwrap()
);
}
#[test]
fn test_os_str_to_c_string_unicode_fn() {
let some_str = std::ffi::OsString::from("foo");
assert_eq!(
os_str_to_c_string_unicode(&some_str).unwrap(),
CString::new("foo").unwrap()
);
}
#[test]
fn test_path_to_c_string_unicode_fn() {
let some_str = String::from("/some/path");
let some_path = std::path::Path::new(&some_str);
assert_eq!(
os_str_to_c_string_unicode(&some_path).unwrap(),
CString::new("/some/path").unwrap()
);
}
#[test]
fn test_os_str_to_c_string_unicode_fn_nul() {
let some_str = std::ffi::OsString::from("fooz\x00bar");
assert_eq!(
os_str_to_c_string_unicode(&some_str).err().unwrap(),
CStringError::InteriorNullByte
);
}
#[test]
fn test_as_path() {
let some_path = CString::new("/some/path").unwrap();
let ptr = some_path.as_ptr();
assert_eq!(as_path(ptr), std::ffi::OsString::from("/some/path"))
}
#[test]
fn test_as_path_unicode_fn() {
let some_path = CString::new("/some/path").unwrap();
let ptr = some_path.as_ptr();
assert_eq!(as_path_unicode(ptr), std::ffi::OsString::from("/some/path"));
}
#[test]
fn test_cstring_yolo() {
assert_eq!(CString::new("hello").unwrap(), CString::yolo("hello"));
}
#[test]
fn test_strdup_str() {
unsafe {
let s = "hello".strdup();
let cmp = strcmp(s, b"hello\x00" as *const u8 as *const libc::c_char);
free(s as *mut libc::c_void);
assert_eq!(cmp, 0);
}
}
#[test]
fn test_strdup_string() {
unsafe {
let s = String::from("hello").strdup();
let cmp = strcmp(s, b"hello\x00" as *const u8 as *const libc::c_char);
free(s as *mut libc::c_void);
assert_eq!(cmp, 0);
}
}
}

View File

@@ -19,12 +19,11 @@ use deltachat::peerstate::*;
use deltachat::qr::*;
use deltachat::sql;
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 {
pub fn dc_reset_tables(context: &Context, bits: i32) -> i32 {
info!(context, "Resetting tables ({})...", bits);
if 0 != bits & 1 {
sql::execute(context, &context.sql, "DELETE FROM jobs;", params![]).unwrap();
@@ -95,7 +94,9 @@ pub unsafe fn dc_reset_tables(context: &Context, bits: i32) -> i32 {
fn dc_poke_eml_file(context: &Context, filename: impl AsRef<Path>) -> Result<(), Error> {
let data = dc_read_file(context, filename)?;
unsafe { dc_receive_imf(context, &data, "import", 0, 0) };
if let Err(err) = dc_receive_imf(context, &data, "import", 0, 0) {
println!("dc_receive_imf errored: {:?}", err);
}
Ok(())
}
@@ -107,18 +108,19 @@ fn dc_poke_eml_file(context: &Context, filename: impl AsRef<Path>) -> Result<(),
/// @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.
fn poke_spec(context: &Context, spec: *const libc::c_char) -> libc::c_int {
fn poke_spec(context: &Context, spec: Option<&str>) -> libc::c_int {
if !context.sql.is_open() {
error!(context, "Import: Database not opened.");
return 0;
}
let real_spec: String;
let mut read_cnt = 0;
let real_spec: String;
/* if `spec` is given, remember it for later usage; if it is not given, try to use the last one */
if !spec.is_null() {
real_spec = to_string_lossy(spec);
if let Some(spec) = spec {
real_spec = spec.to_string();
context
.sql
.set_raw_config(context, "import_spec", Some(&real_spec))
@@ -132,10 +134,8 @@ fn poke_spec(context: &Context, spec: *const libc::c_char) -> libc::c_int {
real_spec = rs.unwrap();
}
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
}
if suffix == "eml" && dc_poke_eml_file(context, &real_spec).is_ok() {
read_cnt += 1
}
} else {
/* import a directory */
@@ -176,7 +176,7 @@ fn poke_spec(context: &Context, spec: *const libc::c_char) -> libc::c_int {
1
}
unsafe fn log_msg(context: &Context, prefix: impl AsRef<str>, msg: &Message) {
fn log_msg(context: &Context, prefix: impl AsRef<str>, msg: &Message) {
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();
@@ -221,7 +221,7 @@ unsafe fn log_msg(context: &Context, prefix: impl AsRef<str>, msg: &Message) {
);
}
unsafe fn log_msglist(context: &Context, msglist: &Vec<MsgId>) -> Result<(), Error> {
fn log_msglist(context: &Context, msglist: &Vec<MsgId>) -> Result<(), Error> {
let mut lines_out = 0;
for &msg_id in msglist {
if msg_id.is_daymarker() {
@@ -252,7 +252,7 @@ unsafe fn log_msglist(context: &Context, msglist: &Vec<MsgId>) -> Result<(), Err
Ok(())
}
unsafe fn log_contactlist(context: &Context, contacts: &Vec<u32>) {
fn log_contactlist(context: &Context, contacts: &Vec<u32>) {
let mut contacts = contacts.clone();
if !contacts.contains(&1) {
contacts.push(1);
@@ -304,7 +304,7 @@ fn chat_prefix(chat: &Chat) -> &'static str {
chat.typ.into()
}
pub unsafe fn dc_cmdline(context: &Context, line: &str) -> Result<(), failure::Error> {
pub fn dc_cmdline(context: &Context, line: &str) -> Result<(), failure::Error> {
let chat_id = *context.cmdline_sel_chat_id.read().unwrap();
let mut sel_chat = if chat_id > 0 {
Chat::load_from_db(context, chat_id).ok()
@@ -315,11 +315,6 @@ pub unsafe fn dc_cmdline(context: &Context, line: &str) -> Result<(), failure::E
let mut args = line.splitn(3, ' ');
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() as *const _
};
let arg2 = args.next().unwrap_or_default();
let blobdir = context.get_blobdir();
@@ -469,7 +464,7 @@ pub unsafe fn dc_cmdline(context: &Context, line: &str) -> Result<(), failure::E
);
}
"poke" => {
ensure!(0 != poke_spec(context, arg1_c), "Poke failed");
ensure!(0 != poke_spec(context, Some(arg1)), "Poke failed");
}
"reset" => {
ensure!(!arg1.is_empty(), "Argument <bits> missing: 1=jobs, 2=peerstates, 4=private keys, 8=rest but server config");
@@ -496,7 +491,7 @@ pub unsafe fn dc_cmdline(context: &Context, line: &str) -> Result<(), failure::E
println!("{:#?}", context.get_info());
}
"interrupt" => {
interrupt_imap_idle(context);
interrupt_inbox_idle(context, true);
}
"maybenetwork" => {
maybe_network(context);
@@ -588,7 +583,7 @@ pub unsafe fn dc_cmdline(context: &Context, line: &str) -> Result<(), failure::E
let members = chat::get_chat_contacts(context, sel_chat.id);
let subtitle = if sel_chat.is_device_talk() {
"device-talk".to_string()
} else if sel_chat.get_type() == Chattype::Single && members.len() >= 1 {
} else if sel_chat.get_type() == Chattype::Single && !members.is_empty() {
let contact = Contact::get_by_id(context, members[0])?;
contact.get_addr().to_string()
} else {
@@ -837,7 +832,10 @@ pub unsafe fn dc_cmdline(context: &Context, line: &str) -> Result<(), failure::E
);
let mut msg = Message::new(Viewtype::Text);
msg.set_text(Some(arg1.to_string()));
chat::add_device_msg(context, &mut msg)?;
chat::add_device_msg(context, None, Some(&mut msg))?;
}
"updatedevicechats" => {
context.update_device_chats()?;
}
"listmedia" => {
ensure!(sel_chat.is_some(), "No chat selected.");
@@ -862,11 +860,7 @@ pub unsafe fn dc_cmdline(context: &Context, line: &str) -> Result<(), failure::E
"archive" | "unarchive" => {
ensure!(!arg1.is_empty(), "Argument <chat-id> missing.");
let chat_id = arg1.parse()?;
chat::archive(
context,
chat_id,
if arg0 == "archive" { true } else { false },
)?;
chat::archive(context, chat_id, arg0 == "archive")?;
}
"delchat" => {
ensure!(!arg1.is_empty(), "Argument <chat-id> missing.");
@@ -887,7 +881,7 @@ pub unsafe fn dc_cmdline(context: &Context, line: &str) -> Result<(), failure::E
}
"forward" => {
ensure!(
!arg1.is_empty() && arg2.is_empty(),
!arg1.is_empty() && !arg2.is_empty(),
"Arguments <msg-id> <chat-id> expected"
);
@@ -944,7 +938,14 @@ pub unsafe fn dc_cmdline(context: &Context, line: &str) -> Result<(), failure::E
let contact = Contact::get_by_id(context, contact_id)?;
let name_n_addr = contact.get_name_n_addr();
let mut res = format!("Contact info for: {}:\n\n", name_n_addr);
let mut res = format!(
"Contact info for: {}:\nIcon: {}\n",
name_n_addr,
match contact.get_profile_image(context) {
Some(image) => image.to_str().unwrap().to_string(),
None => "NoIcon".to_string(),
}
);
res += &Contact::get_encrinfo(context, contact_id)?;
@@ -1011,7 +1012,5 @@ pub unsafe fn dc_cmdline(context: &Context, line: &str) -> Result<(), failure::E
_ => bail!("Unknown command: \"{}\" type ? for help.", arg0),
}
free(arg1_c as *mut _);
Ok(())
}

View File

@@ -150,11 +150,11 @@ fn start_threads(c: Arc<RwLock<Context>>) {
let ctx = c.clone();
let handle_imap = std::thread::spawn(move || loop {
while_running!({
perform_imap_jobs(&ctx.read().unwrap());
perform_imap_fetch(&ctx.read().unwrap());
perform_inbox_jobs(&ctx.read().unwrap());
perform_inbox_fetch(&ctx.read().unwrap());
while_running!({
let context = ctx.read().unwrap();
perform_imap_idle(&context);
perform_inbox_idle(&context);
});
});
});
@@ -202,7 +202,7 @@ fn stop_threads(context: &Context) {
println!("Stopping threads");
IS_RUNNING.store(false, Ordering::Relaxed);
interrupt_imap_idle(context);
interrupt_inbox_idle(context, true);
interrupt_mvbox_idle(context);
interrupt_sentbox_idle(context);
interrupt_smtp_idle(context);
@@ -235,7 +235,7 @@ impl Completer for DcHelper {
}
}
const IMEX_COMMANDS: [&'static str; 12] = [
const IMEX_COMMANDS: [&str; 12] = [
"initiate-key-transfer",
"get-setupcodebegin",
"continue-key-transfer",
@@ -250,7 +250,7 @@ const IMEX_COMMANDS: [&'static str; 12] = [
"stop",
];
const DB_COMMANDS: [&'static str; 11] = [
const DB_COMMANDS: [&str; 11] = [
"info",
"open",
"close",
@@ -264,7 +264,7 @@ const DB_COMMANDS: [&'static str; 11] = [
"housekeeping",
];
const CHAT_COMMANDS: [&'static str; 24] = [
const CHAT_COMMANDS: [&str; 24] = [
"listchats",
"listarchived",
"chat",
@@ -290,7 +290,7 @@ const CHAT_COMMANDS: [&'static str; 24] = [
"unarchive",
"delchat",
];
const MESSAGE_COMMANDS: [&'static str; 8] = [
const MESSAGE_COMMANDS: [&str; 8] = [
"listmsgs",
"msginfo",
"listfresh",
@@ -300,7 +300,7 @@ const MESSAGE_COMMANDS: [&'static str; 8] = [
"unstar",
"delmsg",
];
const CONTACT_COMMANDS: [&'static str; 6] = [
const CONTACT_COMMANDS: [&str; 6] = [
"listcontacts",
"listverified",
"addcontact",
@@ -308,7 +308,7 @@ const CONTACT_COMMANDS: [&'static str; 6] = [
"delcontact",
"cleanupcontacts",
];
const MISC_COMMANDS: [&'static str; 9] = [
const MISC_COMMANDS: [&str; 9] = [
"getqr", "getbadqr", "checkqr", "event", "fileinfo", "clear", "exit", "quit", "help",
];
@@ -334,8 +334,8 @@ impl Hinter for DcHelper {
}
}
static COLORED_PROMPT: &'static str = "\x1b[1;32m> \x1b[0m";
static PROMPT: &'static str = "> ";
static COLORED_PROMPT: &str = "\x1b[1;32m> \x1b[0m";
static PROMPT: &str = "> ";
impl Highlighter for DcHelper {
fn highlight_prompt<'p>(&self, prompt: &'p str) -> Cow<'p, str> {
@@ -403,7 +403,7 @@ fn main_0(args: Vec<String>) -> Result<(), failure::Error> {
// TODO: ignore "set mail_pw"
rl.add_history_entry(line.as_str());
let ctx = ctx.clone();
match unsafe { handle_cmd(line.trim(), ctx) } {
match handle_cmd(line.trim(), ctx) {
Ok(ExitResult::Continue) => {}
Ok(ExitResult::Exit) => break,
Err(err) => println!("Error: {}", err),
@@ -434,7 +434,7 @@ enum ExitResult {
Exit,
}
unsafe fn handle_cmd(line: &str, ctx: Arc<RwLock<Context>>) -> Result<ExitResult, failure::Error> {
fn handle_cmd(line: &str, ctx: Arc<RwLock<Context>>) -> Result<ExitResult, failure::Error> {
let mut args = line.splitn(2, ' ');
let arg0 = args.next().unwrap_or_default();
let arg1 = args.next().unwrap_or_default();
@@ -455,9 +455,9 @@ unsafe fn handle_cmd(line: &str, ctx: Arc<RwLock<Context>>) -> Result<ExitResult
}
"imap-jobs" => {
if HANDLE.clone().lock().unwrap().is_some() {
println!("imap-jobs are already running in a thread.");
println!("inbox-jobs are already running in a thread.");
} else {
perform_imap_jobs(&ctx.read().unwrap());
perform_inbox_jobs(&ctx.read().unwrap());
}
}
"configure" => {

View File

@@ -11,7 +11,8 @@ use deltachat::configure::*;
use deltachat::contact::*;
use deltachat::context::*;
use deltachat::job::{
perform_imap_fetch, perform_imap_idle, perform_imap_jobs, perform_smtp_idle, perform_smtp_jobs,
perform_inbox_fetch, perform_inbox_idle, perform_inbox_jobs, perform_smtp_idle,
perform_smtp_jobs,
};
use deltachat::Event;
@@ -50,12 +51,12 @@ fn main() {
let r1 = running.clone();
let t1 = thread::spawn(move || {
while *r1.read().unwrap() {
perform_imap_jobs(&ctx1);
perform_inbox_jobs(&ctx1);
if *r1.read().unwrap() {
perform_imap_fetch(&ctx1);
perform_inbox_fetch(&ctx1);
if *r1.read().unwrap() {
perform_imap_idle(&ctx1);
perform_inbox_idle(&ctx1);
}
}
}
@@ -100,20 +101,10 @@ fn main() {
thread::sleep(duration);
// let msglist = dc_get_chat_msgs(&ctx, chat_id, 0, 0);
// for i in 0..dc_array_get_cnt(msglist) {
// let msg_id = dc_array_get_id(msglist, i);
// let msg = dc_get_msg(context, msg_id);
// let text = CStr::from_ptr(dc_msg_get_text(msg)).unwrap();
// println!("Message {}: {}\n", i + 1, text.to_str().unwrap());
// dc_msg_unref(msg);
// }
// dc_array_unref(msglist);
println!("stopping threads");
*running.clone().write().unwrap() = false;
deltachat::job::interrupt_imap_idle(&ctx);
*running.write().unwrap() = false;
deltachat::job::interrupt_inbox_idle(&ctx, true);
deltachat::job::interrupt_smtp_idle(&ctx);
println!("joining");

View File

@@ -1,42 +0,0 @@
# 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

View File

@@ -1,23 +0,0 @@
[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"

View File

@@ -1,201 +0,0 @@
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.

View File

@@ -1,23 +0,0 @@
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.

View File

@@ -1,4 +0,0 @@
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.

View File

@@ -1,16 +0,0 @@
# 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

View File

@@ -1,32 +0,0 @@
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_string_lossy().as_bytes())
{
let data = std::slice::from_raw_parts(s as *const u8, strlen(s));
let (res, _, _) = encoding.decode(data);
let res_c = CString::new(res.as_bytes()).unwrap_or_default();
*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
}
}

View File

@@ -1,427 +0,0 @@
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;
}

View File

@@ -1,202 +0,0 @@
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);
}
}
}

View File

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

View File

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

View File

@@ -1,78 +0,0 @@
#![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);
}
}
}

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

View File

@@ -1,89 +0,0 @@
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

@@ -1,860 +0,0 @@
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

@@ -1,583 +0,0 @@
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;
}

File diff suppressed because it is too large Load Diff

View File

@@ -1,891 +0,0 @@
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

@@ -1,82 +0,0 @@
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);
}

View File

@@ -1,397 +0,0 @@
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() {}

File diff suppressed because it is too large Load Diff

View File

@@ -9,17 +9,23 @@ import subprocess
import sys
if __name__ == "__main__":
os.environ["DCC_RS_TARGET"] = target = "release"
target = os.environ.get("DCC_RS_TARGET")
if target is None:
os.environ["DCC_RS_TARGET"] = target = "release"
if "DCC_RS_DEV" not in os.environ:
dn = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))
os.environ["DCC_RS_DEV"] = dn
# build the core library in release + debug mode because
# as of Nov 2019 rPGP generates RSA keys which take
# prohibitively long for non-release installs
os.environ["RUSTFLAGS"] = "-g"
subprocess.check_call([
"cargo", "build", "-p", "deltachat_ffi", "--" + target
])
subprocess.check_call("rm -rf build/ src/deltachat/*.so" , shell=True)
subprocess.check_call([
sys.executable, "-m", "pip", "install", "-e", "."
])
if len(sys.argv) <= 1 or sys.argv[1] != "onlybuild":
subprocess.check_call([
sys.executable, "-m", "pip", "install", "-e", "."
])

View File

@@ -29,7 +29,10 @@ def ffibuilder():
extra_link_args = []
else:
raise NotImplementedError("Compilation not supported yet on Windows, can you help?")
objs = [os.path.join(projdir, 'target', target, 'libdeltachat.a')]
target_dir = os.environ.get("CARGO_TARGET_DIR")
if target_dir is None:
target_dir = os.path.join(projdir, 'target')
objs = [os.path.join(target_dir, target, 'libdeltachat.a')]
assert os.path.exists(objs[0]), objs
incs = [os.path.join(projdir, 'deltachat-ffi')]
else:

View File

@@ -3,6 +3,7 @@
from __future__ import print_function
import atexit
import threading
import os
import re
import time
from array import array
@@ -94,9 +95,12 @@ class Account(object):
"""
self._check_config_key(name)
name = name.encode("utf8")
value = value.encode("utf8")
if name == b"addr" and self.is_configured():
raise ValueError("can not change 'addr' after account is configured.")
if value is not None:
value = value.encode("utf8")
else:
value = ffi.NULL
lib.dc_set_config(self._dc_context, name, value)
def get_config(self, name):
@@ -132,6 +136,18 @@ class Account(object):
"""
return lib.dc_is_configured(self._dc_context)
def set_avatar(self, img_path):
"""Set self avatar.
:raises ValueError: if profile image could not be set
:returns: None
"""
if img_path is None:
self.set_config("selfavatar", None)
else:
assert os.path.exists(img_path), img_path
self.set_config("selfavatar", img_path)
def check_is_configured(self):
""" Raise ValueError if this account is not configured. """
if not self.is_configured():
@@ -573,8 +589,10 @@ class IOThreads:
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)
if not self._thread_quitflag:
lib.dc_perform_imap_fetch(self._dc_context)
if not self._thread_quitflag:
lib.dc_perform_imap_idle(self._dc_context)
self._log_event("py-bindings-info", 0, "INBOX THREAD FINISHED")
def mvbox_thread_run(self):

View File

@@ -2,6 +2,7 @@
import mimetypes
import calendar
import json
from datetime import datetime
import os
from .cutil import as_dc_charpointer, from_dc_charpointer, iter_array
@@ -108,6 +109,30 @@ class Chat(object):
# ------ chat messaging API ------------------------------
def send_msg(self, msg):
"""send a message by using a ready Message object.
:param msg: a :class:`deltachat.message.Message` instance
previously returned by
e.g. :meth:`deltachat.message.Message.new_empty` or
:meth:`prepare_file`.
:raises ValueError: if message can not be sent.
:returns: a :class:`deltachat.message.Message` instance as
sent out. This is the same object as was passed in, which
has been modified with the new state of the core.
"""
if msg.is_out_preparing():
assert msg.id != 0
# get a fresh copy of dc_msg, the core needs it
msg = Message.from_db(self.account, msg.id)
sent_id = lib.dc_send_msg(self._dc_context, self.id, msg._dc_msg)
if sent_id == 0:
raise ValueError("message could not be sent")
# modify message in place to avoid bad state for the caller
msg._dc_msg = Message.from_db(self.account, sent_id)._dc_msg
return msg
def send_text(self, text):
""" send a text message and return the resulting Message instance.
@@ -129,9 +154,12 @@ class Chat(object):
:raises ValueError: if message can not be send/chat does not exist.
:returns: the resulting :class:`deltachat.message.Message` instance
"""
msg = self.prepare_message_file(path=path, mime_type=mime_type)
self.send_prepared(msg)
return msg
msg = Message.new_empty(self.account, view_type="file")
msg.set_file(path, mime_type)
sent_id = lib.dc_send_msg(self._dc_context, self.id, msg._dc_msg)
if sent_id == 0:
raise ValueError("message could not be sent")
return Message.from_db(self.account, sent_id)
def send_image(self, path):
""" send an image message and return the resulting Message instance.
@@ -141,9 +169,12 @@ class Chat(object):
:returns: the resulting :class:`deltachat.message.Message` instance
"""
mime_type = mimetypes.guess_type(path)[0]
msg = self.prepare_message_file(path=path, mime_type=mime_type, view_type="image")
self.send_prepared(msg)
return msg
msg = Message.new_empty(self.account, view_type="image")
msg.set_file(path, mime_type)
sent_id = lib.dc_send_msg(self._dc_context, self.id, msg._dc_msg)
if sent_id == 0:
raise ValueError("message could not be sent")
return Message.from_db(self.account, sent_id)
def prepare_message(self, msg):
""" create a new prepared message.
@@ -242,6 +273,12 @@ class Chat(object):
"""
return lib.dc_marknoticed_chat(self._dc_context, self.id)
def get_summary(self):
""" return dictionary with summary information. """
dc_res = lib.dc_chat_get_info_json(self._dc_context, self.id)
s = from_dc_charpointer(dc_res)
return json.loads(s)
# ------ group management API ------------------------------
def add_contact(self, contact):
@@ -324,6 +361,18 @@ class Chat(object):
return None
return from_dc_charpointer(dc_res)
def get_color(self):
"""return the color of the chat.
:returns: color as 0x00rrggbb
"""
return lib.dc_chat_get_color(self._dc_chat)
def get_subtitle(self):
"""return the subtitle of the chat
:returns: the subtitle
"""
return from_dc_charpointer(lib.dc_chat_get_subtitle(self._dc_chat))
# ------ location streaming API ------------------------------
def is_sending_locations(self):
@@ -332,6 +381,12 @@ class Chat(object):
"""
return lib.dc_is_sending_locations_to_chat(self._dc_context, self.id)
def is_archived(self):
"""return True if this chat is archived.
:returns: True if archived.
"""
return lib.dc_chat_get_archived(self._dc_chat)
def enable_sending_locations(self, seconds):
"""enable sending locations for this chat.

View File

@@ -68,7 +68,6 @@ DC_LP_SMTP_SOCKET_SSL = 0x20000
DC_LP_SMTP_SOCKET_PLAIN = 0x40000
DC_CERTCK_AUTO = 0
DC_CERTCK_STRICT = 1
DC_CERTCK_ACCEPT_INVALID_HOSTNAMES = 2
DC_CERTCK_ACCEPT_INVALID_CERTIFICATES = 3
DC_EMPTY_MVBOX = 0x01
DC_EMPTY_INBOX = 0x02
@@ -98,6 +97,7 @@ DC_EVENT_IMEX_PROGRESS = 2051
DC_EVENT_IMEX_FILE_WRITTEN = 2052
DC_EVENT_SECUREJOIN_INVITER_PROGRESS = 2060
DC_EVENT_SECUREJOIN_JOINER_PROGRESS = 2061
DC_EVENT_SECUREJOIN_MEMBER_ADDED = 2062
DC_EVENT_FILE_COPIED = 2055
DC_EVENT_IS_OFFLINE = 2081
DC_EVENT_GET_STRING = 2091
@@ -150,7 +150,8 @@ DC_STR_MSGLOCATIONENABLED = 64
DC_STR_MSGLOCATIONDISABLED = 65
DC_STR_LOCATION = 66
DC_STR_STICKER = 67
DC_STR_COUNT = 67
DC_STR_DEVICE_MESSAGES = 68
DC_STR_COUNT = 68
# end const generated

View File

@@ -47,3 +47,13 @@ class Contact(object):
def is_verified(self):
""" Return True if the contact is verified. """
return lib.dc_contact_is_verified(self._dc_contact)
def get_profile_image(self):
"""Get contact profile image.
:returns: path to profile image, None if no profile image exists.
"""
dc_res = lib.dc_contact_get_profile_image(self._dc_contact)
if dc_res == ffi.NULL:
return None
return from_dc_charpointer(dc_res)

View File

@@ -1,7 +1,6 @@
""" The Message object. """
import os
import shutil
from . import props
from .cutil import from_dc_charpointer, as_dc_charpointer
from .capi import lib, ffi
@@ -58,8 +57,6 @@ class Message(object):
def set_text(self, text):
"""set text of this message. """
assert self.id > 0, "message not prepared"
assert self.is_out_preparing()
lib.dc_msg_set_text(self._dc_msg, as_dc_charpointer(text))
@props.with_doc
@@ -72,19 +69,6 @@ class Message(object):
mtype = ffi.NULL if mime_type is None else as_dc_charpointer(mime_type)
if not os.path.exists(path):
raise ValueError("path does not exist: {!r}".format(path))
blobdir = self.account.get_blobdir()
if not path.startswith(blobdir):
for i in range(50):
ext = "" if i == 0 else "-" + str(i)
dest = os.path.join(blobdir, os.path.basename(path) + ext)
if os.path.exists(dest):
continue
shutil.copyfile(path, dest)
break
else:
raise ValueError("could not create blobdir-path for {}".format(path))
path = dest
assert path.startswith(blobdir), path
lib.dc_msg_set_file(self._dc_msg, as_dc_charpointer(path), mtype)
@props.with_doc
@@ -162,7 +146,7 @@ class Message(object):
if mime_headers:
s = ffi.string(ffi.gc(mime_headers, lib.dc_str_unref))
if isinstance(s, bytes):
s = s.decode("ascii")
return email.message_from_bytes(s)
return email.message_from_string(s)
@property
@@ -190,7 +174,7 @@ class Message(object):
@property
def _msgstate(self):
if self.id == 0:
dc_msg = self.message._dc_msg
dc_msg = self._dc_msg
else:
# load message from db to get a fresh/current state
dc_msg = ffi.gc(

View File

@@ -85,16 +85,21 @@ class SessionLiveConfigFromFile:
class SessionLiveConfigFromURL:
def __init__(self, url, create_token):
self.configlist = []
for i in range(2):
res = requests.post(url, json={"token_create_user": int(create_token)})
self.url = url
self.create_token = create_token
def get(self, index):
try:
return self.configlist[index]
except IndexError:
assert index == len(self.configlist), index
res = requests.post(self.url, json={"token_create_user": int(self.create_token)})
if res.status_code != 200:
pytest.skip("creating newtmpuser failed {!r}".format(res))
d = res.json()
config = dict(addr=d["email"], mail_pw=d["password"])
self.configlist.append(config)
def get(self, index):
return self.configlist[index]
return config
def exists(self):
return bool(self.configlist)

View File

@@ -21,7 +21,7 @@ class TestOfflineAccountBasic:
d = ac1.get_info()
assert d["arch"]
assert d["number_of_chats"] == "0"
assert d["bcc_self"] == "1"
assert d["bcc_self"] == "0"
def test_is_not_configured(self, acfactory):
ac1 = acfactory.get_unconfigured_account()
@@ -43,7 +43,7 @@ class TestOfflineAccountBasic:
def test_has_bccself(self, acfactory):
ac1 = acfactory.get_unconfigured_account()
assert "bcc_self" in ac1.get_config("sys.config_keys").split()
assert ac1.get_config("bcc_self") == "1"
assert ac1.get_config("bcc_self") == "0"
def test_selfcontact_if_unconfigured(self, acfactory):
ac1 = acfactory.get_unconfigured_account()
@@ -155,6 +155,18 @@ class TestOfflineChat:
chat.set_name("title2")
assert chat.get_name() == "title2"
d = chat.get_summary()
print(d)
assert d["id"] == chat.id
assert d["type"] == chat.get_type()
assert d["name"] == chat.get_name()
assert d["archived"] == chat.is_archived()
# assert d["param"] == chat.param
assert d["color"] == chat.get_color()
assert d["profile_image"] == "" if chat.get_profile_image() is None else chat.get_profile_image()
assert d["subtitle"] == chat.get_subtitle()
assert d["draft"] == "" if chat.get_draft() is None else chat.get_draft()
def test_group_chat_creation_with_translation(self, ac1):
ac1.set_stock_translation(const.DC_STR_NEWGROUPDRAFT, "xyz %1$s")
ac1._evlogger.consume_events()
@@ -370,6 +382,23 @@ class TestOfflineChat:
assert not res.is_ask_verifygroup()
assert res.contact_id == 10
def test_group_chat_many_members_add_remove(self, ac1, lp):
lp.sec("ac1: creating group chat with 10 other members")
chat = ac1.create_group_chat(name="title1")
contacts = []
for i in range(10):
contact = ac1.create_contact("some{}@example.org".format(i))
contacts.append(contact)
chat.add_contact(contact)
num_contacts = len(chat.get_contacts())
assert num_contacts == 11
lp.sec("ac1: removing two contacts and checking things are right")
chat.remove_contact(contacts[9])
chat.remove_contact(contacts[3])
assert len(chat.get_contacts()) == 9
class TestOnlineAccount:
def get_chat(self, ac1, ac2, both_created=False):
@@ -405,6 +434,9 @@ class TestOnlineAccount:
wait_successful_IMAP_SMTP_connection(ac1)
wait_configuration_progress(ac1, 1000)
lp.sec("ac1: setting bcc_self=1")
ac1.set_config("bcc_self", "1")
lp.sec("send out message with bcc to ourselves")
msg_out = chat.send_text("message2")
ev = ac1._evlogger.get_matching("DC_EVENT_MSGS_CHANGED")
@@ -427,15 +459,81 @@ class TestOnlineAccount:
assert self_addr not in ev[2]
ev = ac1._evlogger.get_matching("DC_EVENT_DELETED_BLOB_FILE")
def test_mvbox_sentbox_threads(self, acfactory):
def test_send_file_twice_unicode_filename_mangling(self, tmpdir, acfactory, lp):
ac1, ac2 = acfactory.get_two_online_accounts()
chat = self.get_chat(ac1, ac2)
basename = "somedäüta.html.zip"
p = os.path.join(tmpdir.strpath, basename)
with open(p, "w") as f:
f.write("some data")
def send_and_receive_message():
lp.sec("ac1: prepare and send attachment + text to ac2")
msg1 = Message.new_empty(ac1, "file")
msg1.set_text("withfile")
msg1.set_file(p)
chat.send_msg(msg1)
lp.sec("ac2: receive message")
ev = ac2._evlogger.get_matching("DC_EVENT_INCOMING_MSG|DC_EVENT_MSGS_CHANGED")
assert ev[2] > const.DC_CHAT_ID_LAST_SPECIAL
return ac2.get_message_by_id(ev[2])
msg = send_and_receive_message()
assert msg.text == "withfile"
assert open(msg.filename).read() == "some data"
assert msg.filename.endswith(basename)
msg2 = send_and_receive_message()
assert msg2.text == "withfile"
assert open(msg2.filename).read() == "some data"
assert msg2.filename.endswith("html.zip")
assert msg.filename != msg2.filename
def test_send_file_html_attachment(self, tmpdir, acfactory, lp):
ac1, ac2 = acfactory.get_two_online_accounts()
chat = self.get_chat(ac1, ac2)
basename = "test.html"
content = "<html><body>text</body>data"
p = os.path.join(tmpdir.strpath, basename)
with open(p, "w") as f:
# write wrong html to see if core tries to parse it
# (it shouldn't as it's a file attachment)
f.write(content)
lp.sec("ac1: prepare and send attachment + text to ac2")
chat.send_file(p, mime_type="text/html")
lp.sec("ac2: receive message")
ev = ac2._evlogger.get_matching("DC_EVENT_INCOMING_MSG|DC_EVENT_MSGS_CHANGED")
assert ev[2] > const.DC_CHAT_ID_LAST_SPECIAL
msg = ac2.get_message_by_id(ev[2])
assert open(msg.filename).read() == content
assert msg.filename.endswith(basename)
def test_mvbox_sentbox_threads(self, acfactory, lp):
lp.sec("ac1: start with mvbox thread")
ac1 = acfactory.get_online_configuring_account(mvbox=True, sentbox=True)
lp.sec("ac2: start without mvbox/sentbox threads")
ac2 = acfactory.get_online_configuring_account()
lp.sec("ac2: waiting for configuration")
wait_configuration_progress(ac2, 1000)
lp.sec("ac1: waiting for configuration")
wait_configuration_progress(ac1, 1000)
lp.sec("ac1: send message and wait for ac2 to receive it")
chat = self.get_chat(ac1, ac2)
chat.send_text("message1")
ev = ac2._evlogger.get_matching("DC_EVENT_INCOMING_MSG|DC_EVENT_MSGS_CHANGED")
assert ev[2] > const.DC_CHAT_ID_LAST_SPECIAL
lp.sec("test finished")
def test_move_works(self, acfactory):
ac1 = acfactory.get_online_configuring_account()
@@ -448,27 +546,46 @@ class TestOnlineAccount:
assert ev[2] > const.DC_CHAT_ID_LAST_SPECIAL
ev = ac2._evlogger.get_matching("DC_EVENT_IMAP_MESSAGE_MOVED")
def test_forward_messages(self, acfactory):
def test_move_works_on_self_sent(self, acfactory):
ac1 = acfactory.get_online_configuring_account(mvbox=True)
ac1.set_config("bcc_self", "1")
ac2 = acfactory.get_online_configuring_account()
wait_configuration_progress(ac2, 1000)
wait_configuration_progress(ac1, 1000)
chat = self.get_chat(ac1, ac2)
chat.send_text("message1")
chat.send_text("message2")
chat.send_text("message3")
ac1._evlogger.get_matching("DC_EVENT_IMAP_MESSAGE_MOVED")
ac1._evlogger.get_matching("DC_EVENT_IMAP_MESSAGE_MOVED")
ac1._evlogger.get_matching("DC_EVENT_IMAP_MESSAGE_MOVED")
def test_forward_messages(self, acfactory, lp):
ac1, ac2 = acfactory.get_two_online_accounts()
chat = self.get_chat(ac1, ac2)
lp.sec("ac1: send message to ac2")
msg_out = chat.send_text("message2")
# wait for other account to receive
lp.sec("ac2: wait for receive")
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 == "message2"
# check the message arrived in contact-requests/deaddrop
lp.sec("ac2: check that the message arrive in deaddrop")
chat2 = msg_in.chat
assert msg_in in chat2.get_messages()
assert not msg_in.is_forwarded()
assert chat2.is_deaddrop()
assert chat2 == ac2.get_deaddrop_chat()
lp.sec("ac2: create new chat and forward message to it")
chat3 = ac2.create_group_chat("newgroup")
assert not chat3.is_promoted()
ac2.forward_messages([msg_in], chat3)
lp.sec("ac2: check new chat has a forwarded message")
assert chat3.is_promoted()
messages = chat3.get_messages()
msg = messages[-1]
@@ -515,6 +632,9 @@ class TestOnlineAccount:
def test_send_and_receive_message_markseen(self, acfactory, lp):
ac1, ac2 = acfactory.get_two_online_accounts()
# make DC's life harder wrt to encodings
ac1.set_config("displayname", "ä name")
lp.sec("ac1: create chat with ac2")
chat = self.get_chat(ac1, ac2)
@@ -532,6 +652,7 @@ class TestOnlineAccount:
msg_in = ac2.get_message_by_id(msg_out.id)
assert msg_in.text == "message1"
assert not msg_in.is_forwarded()
assert msg_in.get_sender_contact().display_name == ac1.get_config("displayname")
lp.sec("check the message arrived in contact-requets/deaddrop")
chat2 = msg_in.chat
@@ -602,6 +723,71 @@ class TestOnlineAccount:
ev = ac1._evlogger.get_matching("DC_EVENT_SMTP_MESSAGE_SENT")
assert not msg.is_encrypted()
def test_send_first_message_as_long_unicode_with_cr(self, acfactory, lp):
ac1, ac2 = acfactory.get_two_online_accounts()
ac2.set_config("save_mime_headers", "1")
lp.sec("ac1: create chat with ac2")
chat = self.get_chat(ac1, ac2, both_created=True)
lp.sec("sending multi-line non-unicode message from ac1 to ac2")
text1 = "hello\nworld"
msg_out = chat.send_text(text1)
assert not msg_out.is_encrypted()
lp.sec("sending multi-line unicode text message from ac1 to ac2")
text2 = "äalis\nthis is ßßÄ"
msg_out = chat.send_text(text2)
assert not msg_out.is_encrypted()
lp.sec("wait for ac2 to receive multi-line non-unicode message")
msg_in = ac2.wait_next_incoming_message()
assert msg_in.text == text1
lp.sec("wait for ac2 to receive multi-line unicode message")
msg_in = ac2.wait_next_incoming_message()
assert msg_in.text == text2
assert ac1.get_config("addr") in msg_in.chat.get_name()
def test_reply_encrypted(self, acfactory, lp):
ac1, ac2 = acfactory.get_two_online_accounts()
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")
msg_in = ac2.get_message_by_id(msg_out.id)
assert msg_in.text == "message1"
assert not msg_in.is_encrypted()
lp.sec("create new chat with contact and send back (encrypted) message")
chat2b = ac2.create_chat_by_message(msg_in)
chat2b.send_text("message-back")
lp.sec("wait for ac1 to receive message")
ev = ac1._evlogger.get_matching("DC_EVENT_INCOMING_MSG")
assert ev[1] == chat.id
msg_back = ac1.get_message_by_id(ev[2])
assert msg_back.text == "message-back"
assert msg_back.is_encrypted()
lp.sec("ac1: e2ee_enabled=0 and see if reply is encrypted")
print("ac1: e2ee_enabled={}".format(ac1.get_config("e2ee_enabled")))
print("ac2: e2ee_enabled={}".format(ac2.get_config("e2ee_enabled")))
ac1.set_config("e2ee_enabled", "0")
chat.send_text("message2 -- should be encrypted")
lp.sec("wait for ac2 to receive message")
ev = ac2._evlogger.get_matching("DC_EVENT_INCOMING_MSG")
msg_in = ac2.get_message_by_id(ev[2])
assert msg_in.text == "message2 -- should be encrypted"
assert msg_in.is_encrypted()
def test_saved_mime_on_received_message(self, acfactory, lp):
ac1, ac2 = acfactory.get_two_online_accounts()
@@ -675,7 +861,6 @@ class TestOnlineAccount:
assert len(messages) == 1
assert messages[0].text == "msg1"
pytest.xfail("cannot export twice yet, probably due to interrupt_idle failing")
# wait until a second passed since last backup
# because get_latest_backupfile() shall return the latest backup
# from a UI it's unlikely anyone manages to export two
@@ -718,6 +903,7 @@ class TestOnlineAccount:
ac2._evlogger.set_timeout(30)
wait_configuration_progress(ac2, 1000)
wait_configuration_progress(ac1, 1000)
lp.sec("trigger ac setup message but ignore")
assert ac1.get_info()["fingerprint"] != ac2.get_info()["fingerprint"]
ac1.initiate_key_transfer()
@@ -729,6 +915,7 @@ class TestOnlineAccount:
msg = ac2.get_message_by_id(ev[2])
assert msg.is_setup_message()
assert msg.get_setupcodebegin() == setup_code2[:2]
lp.sec("process second setup message")
msg.continue_key_transfer(setup_code2)
assert ac1.get_info()["fingerprint"] == ac2.get_info()["fingerprint"]
@@ -754,6 +941,7 @@ class TestOnlineAccount:
ac1._evlogger.get_matching("DC_EVENT_IMAP_MESSAGE_DELETED")
ac2._evlogger.get_matching("DC_EVENT_IMAP_MESSAGE_DELETED")
wait_securejoin_inviter_progress(ac1, 1000)
ac1._evlogger.get_matching("DC_EVENT_SECUREJOIN_MEMBER_ADDED")
def test_qr_verified_group_and_chatting(self, acfactory, lp):
ac1, ac2 = acfactory.get_two_online_accounts()
@@ -765,6 +953,7 @@ class TestOnlineAccount:
chat2 = ac2.qr_join_chat(qr)
assert chat2.id >= 10
wait_securejoin_inviter_progress(ac1, 1000)
ac1._evlogger.get_matching("DC_EVENT_SECUREJOIN_MEMBER_ADDED")
lp.sec("ac2: read member added message")
msg = ac2.wait_next_incoming_message()
@@ -787,7 +976,54 @@ class TestOnlineAccount:
assert msg.text == "world"
assert msg.is_encrypted()
def test_set_get_profile_image(self, acfactory, data, lp):
def test_set_get_contact_avatar(self, acfactory, data, lp):
lp.sec("configuring ac1 and ac2")
ac1, ac2 = acfactory.get_two_online_accounts()
lp.sec("ac1: set own profile image")
p = data.get_path("d.png")
ac1.set_avatar(p)
lp.sec("ac1: create 1:1 chat with ac2")
chat = self.get_chat(ac1, ac2, both_created=True)
msg = chat.send_text("hi -- do you see my brand new avatar?")
assert not msg.is_encrypted()
lp.sec("ac2: wait for receiving message and avatar from ac1")
msg1 = ac2.wait_next_incoming_message()
assert not msg1.chat.is_deaddrop()
received_path = msg1.get_sender_contact().get_profile_image()
assert open(received_path, "rb").read() == open(p, "rb").read()
lp.sec("ac2: set own profile image")
p = data.get_path("d.png")
ac2.set_avatar(p)
lp.sec("ac2: send back message")
m = msg1.chat.send_text("yes, i received your avatar -- how do you like mine?")
assert m.is_encrypted()
lp.sec("ac1: wait for receiving message and avatar from ac2")
msg2 = ac1.wait_next_incoming_message()
received_path = msg2.get_sender_contact().get_profile_image()
assert received_path is not None, "did not get avatar through encrypted message"
assert open(received_path, "rb").read() == open(p, "rb").read()
ac2._evlogger.consume_events()
ac1._evlogger.consume_events()
# XXX not sure if the following is correct / possible. you may remove it
lp.sec("ac1: delete profile image from chat, and send message to ac2")
ac1.set_avatar(None)
m = msg2.chat.send_text("i don't like my avatar anymore and removed it")
assert m.is_encrypted()
lp.sec("ac2: wait for message along with avatar deletion of ac1")
msg3 = ac2.wait_next_incoming_message()
assert msg3.get_sender_contact().get_profile_image() is None
def test_set_get_group_image(self, acfactory, data, lp):
ac1, ac2 = acfactory.get_two_online_accounts()
lp.sec("create unpromoted group chat")
@@ -889,6 +1125,52 @@ class TestOnlineAccount:
assert not locations3
class TestGroupStressTests:
def test_group_many_members_add_leave_remove(self, acfactory, lp):
lp.sec("creating and configuring five accounts")
ac1 = acfactory.get_online_configuring_account()
accounts = [acfactory.get_online_configuring_account() for i in range(3)]
wait_configuration_progress(ac1, 1000)
for acc in accounts:
wait_configuration_progress(acc, 1000)
lp.sec("ac1: creating group chat with 3 other members")
chat = ac1.create_group_chat("title1")
contacts = []
chars = list("äöüsr")
for acc in accounts:
contact = ac1.create_contact(acc.get_config("addr"), name=chars.pop())
contacts.append(contact)
chat.add_contact(contact)
# make sure the other side accepts our messages
c1 = acc.create_contact(ac1.get_config("addr"), "ä member")
acc.create_chat_by_contact(c1)
assert not chat.is_promoted()
lp.sec("ac1: send mesage to new group chat")
chat.send_text("hello")
assert chat.is_promoted()
num_contacts = len(chat.get_contacts())
assert num_contacts == 3 + 1
lp.sec("ac2: checking that the chat arrived correctly")
ac2 = accounts[0]
msg = ac2.wait_next_incoming_message()
assert msg.text == "hello"
print("chat is", msg.chat)
assert len(msg.chat.get_contacts()) == 4
lp.sec("ac1: removing one contacts and checking things are right")
to_remove = msg.chat.get_contacts()[-1]
msg.chat.remove_contact(to_remove)
sysmsg = ac1.wait_next_incoming_message()
assert to_remove.addr in sysmsg.text
assert len(sysmsg.chat.get_contacts()) == 3
class TestOnlineConfigureFails:
def test_invalid_password(self, acfactory):
ac1, configdict = acfactory.get_online_config()
@@ -896,7 +1178,7 @@ class TestOnlineConfigureFails:
ac1.start_threads()
wait_configuration_progress(ac1, 500)
ev1 = ac1._evlogger.get_matching("DC_EVENT_ERROR_NETWORK")
assert "authentication failed" in ev1[2].lower()
assert "cannot login" in ev1[2].lower()
wait_configuration_progress(ac1, 0, 0)
def test_invalid_user(self, acfactory):
@@ -905,7 +1187,7 @@ class TestOnlineConfigureFails:
ac1.start_threads()
wait_configuration_progress(ac1, 500)
ev1 = ac1._evlogger.get_matching("DC_EVENT_ERROR_NETWORK")
assert "authentication failed" in ev1[2].lower()
assert "cannot login" in ev1[2].lower()
wait_configuration_progress(ac1, 0, 0)
def test_invalid_domain(self, acfactory):

View File

@@ -1,10 +1,49 @@
from __future__ import print_function
import os.path
import shutil
import pytest
from filecmp import cmp
from deltachat import const
from conftest import wait_configuration_progress, wait_msgs_changed
from deltachat import const
class TestOnlineInCreation:
def test_increation_not_blobdir(self, tmpdir, acfactory, lp):
ac1 = acfactory.get_online_configuring_account()
ac2 = acfactory.get_online_configuring_account()
wait_configuration_progress(ac1, 1000)
wait_configuration_progress(ac2, 1000)
c2 = ac1.create_contact(email=ac2.get_config("addr"))
chat = ac1.create_chat_by_contact(c2)
lp.sec("Creating in-creation file outside of blobdir")
assert tmpdir.strpath != ac1.get_blobdir()
src = tmpdir.join('file.txt').ensure(file=1)
with pytest.raises(Exception):
chat.prepare_message_file(src.strpath)
def test_no_increation_copies_to_blobdir(self, tmpdir, acfactory, lp):
ac1 = acfactory.get_online_configuring_account()
ac2 = acfactory.get_online_configuring_account()
wait_configuration_progress(ac1, 1000)
wait_configuration_progress(ac2, 1000)
c2 = ac1.create_contact(email=ac2.get_config("addr"))
chat = ac1.create_chat_by_contact(c2)
lp.sec("Creating file outside of blobdir")
assert tmpdir.strpath != ac1.get_blobdir()
src = tmpdir.join('file.txt')
src.write("hello there\n")
chat.send_file(src.strpath)
blob_src = os.path.join(ac1.get_blobdir(), 'file.txt')
assert os.path.exists(blob_src), "file.txt not copied to blobdir"
def test_forward_increation(self, acfactory, data, lp):
ac1 = acfactory.get_online_configuring_account()
ac2 = acfactory.get_online_configuring_account()
@@ -17,7 +56,10 @@ class TestOnlineInCreation:
wait_msgs_changed(ac1, 0, 0) # why no chat id?
lp.sec("create a message with a file in creation")
path = data.get_path("d.png")
orig = data.get_path("d.png")
path = os.path.join(ac1.get_blobdir(), 'd.png')
with open(path, "x") as fp:
fp.write("preparing")
prepared_original = chat.prepare_message_file(path)
assert prepared_original.is_out_preparing()
wait_msgs_changed(ac1, chat.id, prepared_original.id)
@@ -38,6 +80,7 @@ class TestOnlineInCreation:
lp.sec("finish creating the file and send it")
assert prepared_original.is_out_preparing()
shutil.copyfile(orig, path)
chat.send_prepared(prepared_original)
assert prepared_original.is_out_pending() or prepared_original.is_out_delivered()
wait_msgs_changed(ac1, chat.id, prepared_original.id)
@@ -57,13 +100,13 @@ class TestOnlineInCreation:
lp.sec("wait1 for original or forwarded messages to arrive")
ev1 = ac2._evlogger.get_matching("DC_EVENT_MSGS_CHANGED")
assert ev1[1] >= const.DC_CHAT_ID_LAST_SPECIAL
assert ev1[1] > const.DC_CHAT_ID_LAST_SPECIAL
received_original = ac2.get_message_by_id(ev1[2])
assert cmp(received_original.filename, path, False)
assert cmp(received_original.filename, orig, shallow=False)
lp.sec("wait2 for original or forwarded messages to arrive")
ev2 = ac2._evlogger.get_matching("DC_EVENT_MSGS_CHANGED")
assert ev2[1] >= const.DC_CHAT_ID_LAST_SPECIAL
assert ev2[1] > const.DC_CHAT_ID_LAST_SPECIAL
assert ev2[1] != ev1[1]
received_copy = ac2.get_message_by_id(ev2[2])
assert cmp(received_copy.filename, path, False)
assert cmp(received_copy.filename, orig, shallow=False)

View File

@@ -41,7 +41,7 @@ def test_dc_close_events(tmpdir):
else:
print("skipping event", *ev)
find("disconnecting INBOX-watch")
find("disconnecting inbox-thread")
find("disconnecting sentbox-thread")
find("disconnecting mvbox-thread")
find("disconnecting SMTP")

View File

@@ -1,19 +1,21 @@
[tox]
# make sure to update environment list in travis.yml and appveyor.yml
envlist =
py35
py37
lint
auditwheels
[testenv]
commands =
pytest -v -rsXx {posargs:tests}
python tests/package_wheels.py {toxworkdir}/wheelhouse
pytest -n6 --reruns 2 --reruns-delay 5 -v -rsXx {posargs:tests}
# python tests/package_wheels.py {toxworkdir}/wheelhouse
passenv =
TRAVIS
DCC_RS_DEV
DCC_RS_TARGET
DCC_PY_LIVECONFIG
CARGO_TARGET_DIR
RUSTC_WRAPPER
deps =
pytest
pytest-rerunfailures
@@ -28,10 +30,9 @@ deps = auditwheel
commands =
python tests/auditwheels.py {toxworkdir}/wheelhouse
[testenv:lint]
skipsdist = True
usedevelop = True
skip_install = True
deps =
flake8
# pygments required by rst-lint
@@ -43,19 +44,29 @@ commands =
rst-lint --encoding 'utf-8' README.rst
[testenv:doc]
basepython = python3.5
changedir=doc
deps =
sphinx==2.2.0
breathe
changedir = doc
commands =
sphinx-build -w docker-toxdoc-warnings.log -b html . _build/html
sphinx-build -Q -w toxdoc-warnings.log -b html . _build/html
[testenv:lintdoc]
skipsdist = True
usedevelop = True
deps =
{[testenv:lint]deps}
{[testenv:doc]deps}
commands =
{[testenv:lint]commands}
{[testenv:doc]commands}
[pytest]
addopts = -v -rs --reruns 3 --reruns-delay 2
python_files = tests/test_*.py
addopts = -v -ra
python_files = tests/test_*.py
norecursedirs = .tox
xfail_strict=true
timeout = 60

View File

@@ -1 +1 @@
nightly-2019-08-13
nightly-2019-11-06

View File

@@ -33,6 +33,8 @@ def replace_toml_version(relpath, newversion):
if __name__ == "__main__":
if len(sys.argv) < 2:
for x in ("Cargo.toml", "deltachat-ffi/Cargo.toml"):
print("{}: {}".format(x, read_toml_version(x)))
raise SystemExit("need argument: new version, example 1.0.0-beta.27")
newversion = sys.argv[1]
if newversion.count(".") < 2:
@@ -53,7 +55,8 @@ if __name__ == "__main__":
replace_toml_version("Cargo.toml", newversion)
replace_toml_version("deltachat-ffi/Cargo.toml", newversion)
subprocess.call(["cargo", "update", "-p", "deltachat"])
subprocess.call(["git", "add", "-u"])
# subprocess.call(["cargo", "update", "-p", "deltachat"])
print("after commit make sure to: ")
print("")

26
spec.md
View File

@@ -1,6 +1,6 @@
# Chat-over-Email specification
Version 0.19.0
Version 0.20.0
This document describes how emails can be used
to implement typical messenger functions
@@ -248,11 +248,11 @@ and the message SHOULD appear as a message or action from the sender.
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`
and MUST add the header `Chat-Group-Avatar`
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 MUST add the header `Chat-Group-Avatar: 0`.
The messenger SHOULD send an explicit mail for each group image change.
The body of the message SHOULD contain
@@ -265,7 +265,7 @@ and the message SHOULD appear as a message or action from the sender.
Chat-Version: 1.0
Chat-Group-ID: 12345uvwxyZ
Chat-Group-Name: Our Group
Chat-Group-Image: image.jpg
Chat-Group-Avatar: image.jpg
Message-ID: Gr.12345uvwxyZ.0005@domain
Subject: Chat: Our Group: Hello, ...
Content-Type: multipart/mixed; boundary="==break=="
@@ -283,25 +283,25 @@ and the message SHOULD appear as a message or action from the sender.
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.
to add a `Chat-Group-Avatar` only on image changes.
# Set profile image
A user MAY have a profile-image that MAY be spread to his contacts.
A user MAY have a profile-image that MAY be spread to their 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`
and MUST add the header `Chat-User-Avatar`
with the value set to the image name.
To remove the profile-image,
the messenger MUST add the header `Chat-Profile-Image: 0`.
the messenger MUST add the header `Chat-User-Avatar: 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).
the messenger has to keep a `user_avatar_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.
@@ -309,7 +309,7 @@ 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
Chat-User-Avatar: photo.jpg
Subject: Chat: Hello, ...
Content-Type: multipart/mixed; boundary="==break=="
@@ -325,10 +325,10 @@ The messenger SHOULD NOT send an explicit mail to normal MUAs.
--==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
Note that `Chat-User-Avatar` may appear together with all other headers,
eg. there may be a `Chat-User-Avatar` and a `Chat-Group-Avatar` header
in the same message.
To save data, it is RECOMMENDED to add a `Chat-Profile-Image` header
To save data, it is RECOMMENDED to add a `Chat-User-Avatar` header
only on image changes.

View File

@@ -1,12 +1,14 @@
//! # Autocrypt header module
//!
//! Parse and create [Autocrypt-headers](https://autocrypt.org/en/latest/level1.html#the-autocrypt-header).
use std::collections::BTreeMap;
use std::ffi::CStr;
use std::str::FromStr;
use std::{fmt, str};
use mmime::mailimf::types::*;
use crate::constants::*;
use crate::contact::*;
use crate::context::Context;
use crate::key::*;
/// Possible values for encryption preference
@@ -40,13 +42,13 @@ impl str::FromStr for EncryptPreference {
fn from_str(s: &str) -> Result<Self, Self::Err> {
match s {
"mutual" => Ok(EncryptPreference::Mutual),
"reset" => Ok(EncryptPreference::Reset),
_ => Ok(EncryptPreference::NoPreference),
"nopreference" => Ok(EncryptPreference::NoPreference),
_ => Err(()),
}
}
}
/// Parse and create [Autocrypt-headers](https://autocrypt.org/en/latest/level1.html#the-autocrypt-header).
/// Autocrypt header
#[derive(Debug)]
pub struct Aheader {
pub addr: String,
@@ -55,6 +57,7 @@ pub struct Aheader {
}
impl Aheader {
/// Creates new autocrypt header
pub fn new(addr: String, public_key: Key, prefer_encrypt: EncryptPreference) -> Self {
Aheader {
addr,
@@ -63,60 +66,54 @@ impl Aheader {
}
}
pub fn from_imffields(wanted_from: &str, header: *const mailimf_fields) -> Option<Self> {
if header.is_null() {
return None;
}
pub fn from_headers(
context: &Context,
wanted_from: &str,
headers: &[mailparse::MailHeader<'_>],
) -> Option<Self> {
use mailparse::MailHeaderMap;
let mut fine_header = None;
let mut cur = unsafe { (*(*header).fld_list).first };
while !cur.is_null() {
let field = unsafe { (*cur).data as *mut mailimf_field };
if !field.is_null()
&& unsafe { (*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()
&& unsafe { !(*optional_field).fld_name.is_null() }
&& unsafe { CStr::from_ptr((*optional_field).fld_name).to_string_lossy() }
== "Autocrypt"
{
let value =
unsafe { CStr::from_ptr((*optional_field).fld_value).to_string_lossy() };
if let Ok(test) = Self::from_str(&value) {
if addr_cmp(&test.addr, wanted_from) {
if fine_header.is_none() {
fine_header = Some(test);
} else {
// TODO: figure out what kind of error case this is
return None;
}
}
if let Ok(Some(value)) = headers.get_first_value("Autocrypt") {
match Self::from_str(&value) {
Ok(header) => {
if addr_cmp(&header.addr, wanted_from) {
return Some(header);
}
}
Err(err) => {
warn!(
context,
"found invalid autocrypt header {}: {:?}", value, err
);
}
}
cur = unsafe { (*cur).next };
}
fine_header
None
}
}
impl fmt::Display for Aheader {
fn fmt(&self, fmt: &mut fmt::Formatter) -> fmt::Result {
// TODO replace 78 with enum /rtn
// adds a whitespace every 78 characters, this allows libEtPan to
// wrap the lines according to RFC 5322
write!(fmt, "addr={};", self.addr)?;
if self.prefer_encrypt == EncryptPreference::Mutual {
write!(fmt, " prefer-encrypt=mutual;")?;
}
// adds a whitespace every 78 characters, this allows
// email crate to wrap the lines according to RFC 5322
// (which may insert a linebreak before every whitespace)
let keydata = self.public_key.to_base64(78);
write!(
fmt,
"addr={}; prefer-encrypt={}; keydata={}",
self.addr, self.prefer_encrypt, keydata
)
let keydata = self.public_key.to_base64().chars().enumerate().fold(
String::new(),
|mut res, (i, c)| {
if i % 78 == 78 - "keydata=".len() {
res.push(' ')
}
res.push(c);
res
},
);
write!(fmt, " keydata={}", keydata)
}
}
@@ -162,13 +159,10 @@ impl str::FromStr for Aheader {
}
};
let prefer_encrypt = match attributes
let prefer_encrypt = attributes
.remove("prefer-encrypt")
.and_then(|raw| raw.parse().ok())
{
Some(pref) => pref,
None => EncryptPreference::NoPreference,
};
.unwrap_or_default();
// Autocrypt-Level0: unknown attributes starting with an underscore can be safely ignored
// Autocrypt-Level0: unknown attribute, treat the header as invalid
@@ -188,15 +182,13 @@ impl str::FromStr for Aheader {
mod tests {
use super::*;
fn rawkey() -> String {
"xsBNBFzG3j0BCAC6iNhT8zydvCXi8LI/gFnkadMbfmSE/rTJskRRra/utGbLyDta/yTrJgWL7O3y/g4HdDW/dN2z26Y6W13IMzx9gLInn1KQZChtqWAcr/ReUucXcymwcfg1mdkBGk3TSLeLihN6CJx8Wsv8ig+kgAzte4f5rqEEAJVQ9WZHuti7UiYs6oRzqTo06CRe9owVXxzdMf0VDQtf7ZFm9dpzKKbhH7Lu8880iiotQ9/yRCkDGp9fNThsrLdZiK6OIAcIBAqi2rI89aS1dAmnRbktQieCx5izzyYkR1KvVL3gTTllHOzfKVEC2asmtWu2e4se/+O4WMIS1eGrn7GeWVb0Vwc5ABEBAAHNETxhQEBiLmV4YW1wbGUuZGU+wsCJBBABCAAzAhkBBQJcxt5FAhsDBAsJCAcGFQgJCgsCAxYCARYhBI4xxYKBgH3ANh5cufaKrc9mtiMLAAoJEPaKrc9mtiML938H/18F+3Wf9/JaAy/8hCO1v4S2PVBhxaKCokaNFtkfaMRne2l087LscCFPiFNyb4mv6Z3YeK8Xpxlp2sI0ecvdiqLUOGfnxS6tQrj+83EjtIrZ/hXOk1h121QFWH9Zg2VNHtODXjAgdLDC0NWUrclR0ZOqEDQHeo0ibTILdokVfXFN25wakPmGaYJP2y729cb1ve7RzvIvwn+Dddfxo3ao72rBfLi7l4NQ4S0KsY4cw+/6l5bRCKYCP77wZtvCwUvfVVosLdT43agtSiBI49+ayqvZ8OCvSJa61i+v81brTiEy9GBod4eAp45Ibsuemkw+gon4ZOvUXHTjwFB+h63MrozOwE0EXMbePQEIAL/vauf1zK8JgCu3V+G+SOX0iWw5xUlCPX+ERpBbWfwu3uAqn4wYXD3JDE/fVAF668xiV4eTPtlSUd5h0mn+G7uXMMOtkb+20SoEt50f8zw8TrL9t+ZsV11GKZWJpCar5AhXWsn6EEi8I2hLL5vn55ZZmHuGgN4jjmkRl3ToKCLhaXwTBjCJem7N5EH7F75wErEITa55v4Lb4Nfca7vnvtYrI1OA446xa8gHra0SINelTD09/JM/Fw4sWVPBaRZmJK/Tnu79N23No9XBUubmFPv1pNexZsQclicnTpt/BEWhiun7d6lfGB63K1aoHRTR1pcrWvBuALuuz0gqar2zlI0AEQEAAcLAdgQYAQgAIAUCXMbeRQIbDBYhBI4xxYKBgH3ANh5cufaKrc9mtiMLAAoJEPaKrc9mtiMLKSEIAIyLCRO2OyZ0IYRvRPpMn4p7E+7Pfcz/0mSkOy+1hshgJnqivXurm8zwGrwdMqeV4eslKR9H1RUdWGUQJNbtwmmjrt5DHpIhYHl5t3FpCBaGbV20Omo00Q38lBl9MtrmZkZw+ktEk6X+0xCKssMF+2MADkSOIufbR5HrDVB89VZOHCO9DeXvCUUAw2hyJiL/LHmLzJ40zYoTmb+F//f0k0j+tRdbkefyRoCmwG7YGiT+2hnCdgcezswnzah5J3ZKlrg7jOGo1LxtbvNUzxNBbC6S/aNgwm6qxo7xegRhmEl5uZ16zwyj4qz+xkjGy25Of5mWfUDoNw7OT7sjUbHOOMc=".into()
}
const RAWKEY: &str = "xsBNBFzG3j0BCAC6iNhT8zydvCXi8LI/gFnkadMbfmSE/rTJskRRra/utGbLyDta/yTrJgWL7O3y/g4HdDW/dN2z26Y6W13IMzx9gLInn1KQZChtqWAcr/ReUucXcymwcfg1mdkBGk3TSLeLihN6CJx8Wsv8ig+kgAzte4f5rqEEAJVQ9WZHuti7UiYs6oRzqTo06CRe9owVXxzdMf0VDQtf7ZFm9dpzKKbhH7Lu8880iiotQ9/yRCkDGp9fNThsrLdZiK6OIAcIBAqi2rI89aS1dAmnRbktQieCx5izzyYkR1KvVL3gTTllHOzfKVEC2asmtWu2e4se/+O4WMIS1eGrn7GeWVb0Vwc5ABEBAAHNETxhQEBiLmV4YW1wbGUuZGU+wsCJBBABCAAzAhkBBQJcxt5FAhsDBAsJCAcGFQgJCgsCAxYCARYhBI4xxYKBgH3ANh5cufaKrc9mtiMLAAoJEPaKrc9mtiML938H/18F+3Wf9/JaAy/8hCO1v4S2PVBhxaKCokaNFtkfaMRne2l087LscCFPiFNyb4mv6Z3YeK8Xpxlp2sI0ecvdiqLUOGfnxS6tQrj+83EjtIrZ/hXOk1h121QFWH9Zg2VNHtODXjAgdLDC0NWUrclR0ZOqEDQHeo0ibTILdokVfXFN25wakPmGaYJP2y729cb1ve7RzvIvwn+Dddfxo3ao72rBfLi7l4NQ4S0KsY4cw+/6l5bRCKYCP77wZtvCwUvfVVosLdT43agtSiBI49+ayqvZ8OCvSJa61i+v81brTiEy9GBod4eAp45Ibsuemkw+gon4ZOvUXHTjwFB+h63MrozOwE0EXMbePQEIAL/vauf1zK8JgCu3V+G+SOX0iWw5xUlCPX+ERpBbWfwu3uAqn4wYXD3JDE/fVAF668xiV4eTPtlSUd5h0mn+G7uXMMOtkb+20SoEt50f8zw8TrL9t+ZsV11GKZWJpCar5AhXWsn6EEi8I2hLL5vn55ZZmHuGgN4jjmkRl3ToKCLhaXwTBjCJem7N5EH7F75wErEITa55v4Lb4Nfca7vnvtYrI1OA446xa8gHra0SINelTD09/JM/Fw4sWVPBaRZmJK/Tnu79N23No9XBUubmFPv1pNexZsQclicnTpt/BEWhiun7d6lfGB63K1aoHRTR1pcrWvBuALuuz0gqar2zlI0AEQEAAcLAdgQYAQgAIAUCXMbeRQIbDBYhBI4xxYKBgH3ANh5cufaKrc9mtiMLAAoJEPaKrc9mtiMLKSEIAIyLCRO2OyZ0IYRvRPpMn4p7E+7Pfcz/0mSkOy+1hshgJnqivXurm8zwGrwdMqeV4eslKR9H1RUdWGUQJNbtwmmjrt5DHpIhYHl5t3FpCBaGbV20Omo00Q38lBl9MtrmZkZw+ktEk6X+0xCKssMF+2MADkSOIufbR5HrDVB89VZOHCO9DeXvCUUAw2hyJiL/LHmLzJ40zYoTmb+F//f0k0j+tRdbkefyRoCmwG7YGiT+2hnCdgcezswnzah5J3ZKlrg7jOGo1LxtbvNUzxNBbC6S/aNgwm6qxo7xegRhmEl5uZ16zwyj4qz+xkjGy25Of5mWfUDoNw7OT7sjUbHOOMc=";
#[test]
fn test_from_str() {
let h: Aheader = format!(
"addr=me@mail.com; prefer-encrypt=mutual; keydata={}",
rawkey()
RAWKEY
)
.parse()
.expect("failed to parse");
@@ -205,9 +197,22 @@ mod tests {
assert_eq!(h.prefer_encrypt, EncryptPreference::Mutual);
}
// EncryptPreference::Reset is an internal value, parser should never return it
#[test]
fn test_from_str_reset() {
let raw = format!(
"addr=reset@example.com; prefer-encrypt=reset; keydata={}",
RAWKEY
);
let h: Aheader = raw.parse().expect("failed to parse");
assert_eq!(h.addr, "reset@example.com");
assert_eq!(h.prefer_encrypt, EncryptPreference::NoPreference);
}
#[test]
fn test_from_str_non_critical() {
let raw = format!("addr=me@mail.com; _foo=one; _bar=two; keydata={}", rawkey());
let raw = format!("addr=me@mail.com; _foo=one; _bar=two; keydata={}", RAWKEY);
let h: Aheader = raw.parse().expect("failed to parse");
assert_eq!(h.addr, "me@mail.com");
@@ -218,33 +223,57 @@ mod tests {
fn test_from_str_superflous_critical() {
let raw = format!(
"addr=me@mail.com; _foo=one; _bar=two; other=me; keydata={}",
rawkey()
RAWKEY
);
assert!(raw.parse::<Aheader>().is_err());
}
#[test]
fn test_good_headers() {
let fixed_header = "addr=a@b.example.org; prefer-encrypt=mutual; keydata=xsBNBFzG3j0BCAC6iNhT8zydvCXi8LI/gFnkadMbfmSE/rTJskRRra/utGbLyDta/yTrJgWL7O3y/g 4HdDW/dN2z26Y6W13IMzx9gLInn1KQZChtqWAcr/ReUucXcymwcfg1mdkBGk3TSLeLihN6CJx8Wsv8 ig+kgAzte4f5rqEEAJVQ9WZHuti7UiYs6oRzqTo06CRe9owVXxzdMf0VDQtf7ZFm9dpzKKbhH7Lu88 80iiotQ9/yRCkDGp9fNThsrLdZiK6OIAcIBAqi2rI89aS1dAmnRbktQieCx5izzyYkR1KvVL3gTTll HOzfKVEC2asmtWu2e4se/+O4WMIS1eGrn7GeWVb0Vwc5ABEBAAHNETxhQEBiLmV4YW1wbGUuZGU+ws CJBBABCAAzAhkBBQJcxt5FAhsDBAsJCAcGFQgJCgsCAxYCARYhBI4xxYKBgH3ANh5cufaKrc9mtiML AAoJEPaKrc9mtiML938H/18F+3Wf9/JaAy/8hCO1v4S2PVBhxaKCokaNFtkfaMRne2l087LscCFPiF Nyb4mv6Z3YeK8Xpxlp2sI0ecvdiqLUOGfnxS6tQrj+83EjtIrZ/hXOk1h121QFWH9Zg2VNHtODXjAg dLDC0NWUrclR0ZOqEDQHeo0ibTILdokVfXFN25wakPmGaYJP2y729cb1ve7RzvIvwn+Dddfxo3ao72 rBfLi7l4NQ4S0KsY4cw+/6l5bRCKYCP77wZtvCwUvfVVosLdT43agtSiBI49+ayqvZ8OCvSJa61i+v 81brTiEy9GBod4eAp45Ibsuemkw+gon4ZOvUXHTjwFB+h63MrozOwE0EXMbePQEIAL/vauf1zK8JgC u3V+G+SOX0iWw5xUlCPX+ERpBbWfwu3uAqn4wYXD3JDE/fVAF668xiV4eTPtlSUd5h0mn+G7uXMMOt kb+20SoEt50f8zw8TrL9t+ZsV11GKZWJpCar5AhXWsn6EEi8I2hLL5vn55ZZmHuGgN4jjmkRl3ToKC LhaXwTBjCJem7N5EH7F75wErEITa55v4Lb4Nfca7vnvtYrI1OA446xa8gHra0SINelTD09/JM/Fw4s WVPBaRZmJK/Tnu79N23No9XBUubmFPv1pNexZsQclicnTpt/BEWhiun7d6lfGB63K1aoHRTR1pcrWv BuALuuz0gqar2zlI0AEQEAAcLAdgQYAQgAIAUCXMbeRQIbDBYhBI4xxYKBgH3ANh5cufaKrc9mtiML AAoJEPaKrc9mtiMLKSEIAIyLCRO2OyZ0IYRvRPpMn4p7E+7Pfcz/0mSkOy+1hshgJnqivXurm8zwGr wdMqeV4eslKR9H1RUdWGUQJNbtwmmjrt5DHpIhYHl5t3FpCBaGbV20Omo00Q38lBl9MtrmZkZw+ktE k6X+0xCKssMF+2MADkSOIufbR5HrDVB89VZOHCO9DeXvCUUAw2hyJiL/LHmLzJ40zYoTmb+F//f0k0 j+tRdbkefyRoCmwG7YGiT+2hnCdgcezswnzah5J3ZKlrg7jOGo1LxtbvNUzxNBbC6S/aNgwm6qxo7x egRhmEl5uZ16zwyj4qz+xkjGy25Of5mWfUDoNw7OT7sjUbHOOMc=";
let fixed_header = concat!(
"addr=a@b.example.org; prefer-encrypt=mutual; ",
"keydata=xsBNBFzG3j0BCAC6iNhT8zydvCXi8LI/gFnkadMbfmSE/rTJskRRra/utGbLyDta/yTrJg",
" WL7O3y/g4HdDW/dN2z26Y6W13IMzx9gLInn1KQZChtqWAcr/ReUucXcymwcfg1mdkBGk3TSLeLihN6",
" CJx8Wsv8ig+kgAzte4f5rqEEAJVQ9WZHuti7UiYs6oRzqTo06CRe9owVXxzdMf0VDQtf7ZFm9dpzKK",
" bhH7Lu8880iiotQ9/yRCkDGp9fNThsrLdZiK6OIAcIBAqi2rI89aS1dAmnRbktQieCx5izzyYkR1Kv",
" VL3gTTllHOzfKVEC2asmtWu2e4se/+O4WMIS1eGrn7GeWVb0Vwc5ABEBAAHNETxhQEBiLmV4YW1wbG",
" UuZGU+wsCJBBABCAAzAhkBBQJcxt5FAhsDBAsJCAcGFQgJCgsCAxYCARYhBI4xxYKBgH3ANh5cufaK",
" rc9mtiMLAAoJEPaKrc9mtiML938H/18F+3Wf9/JaAy/8hCO1v4S2PVBhxaKCokaNFtkfaMRne2l087",
" LscCFPiFNyb4mv6Z3YeK8Xpxlp2sI0ecvdiqLUOGfnxS6tQrj+83EjtIrZ/hXOk1h121QFWH9Zg2VN",
" HtODXjAgdLDC0NWUrclR0ZOqEDQHeo0ibTILdokVfXFN25wakPmGaYJP2y729cb1ve7RzvIvwn+Ddd",
" fxo3ao72rBfLi7l4NQ4S0KsY4cw+/6l5bRCKYCP77wZtvCwUvfVVosLdT43agtSiBI49+ayqvZ8OCv",
" SJa61i+v81brTiEy9GBod4eAp45Ibsuemkw+gon4ZOvUXHTjwFB+h63MrozOwE0EXMbePQEIAL/vau",
" f1zK8JgCu3V+G+SOX0iWw5xUlCPX+ERpBbWfwu3uAqn4wYXD3JDE/fVAF668xiV4eTPtlSUd5h0mn+",
" G7uXMMOtkb+20SoEt50f8zw8TrL9t+ZsV11GKZWJpCar5AhXWsn6EEi8I2hLL5vn55ZZmHuGgN4jjm",
" kRl3ToKCLhaXwTBjCJem7N5EH7F75wErEITa55v4Lb4Nfca7vnvtYrI1OA446xa8gHra0SINelTD09",
" /JM/Fw4sWVPBaRZmJK/Tnu79N23No9XBUubmFPv1pNexZsQclicnTpt/BEWhiun7d6lfGB63K1aoHR",
" TR1pcrWvBuALuuz0gqar2zlI0AEQEAAcLAdgQYAQgAIAUCXMbeRQIbDBYhBI4xxYKBgH3ANh5cufaK",
" rc9mtiMLAAoJEPaKrc9mtiMLKSEIAIyLCRO2OyZ0IYRvRPpMn4p7E+7Pfcz/0mSkOy+1hshgJnqivX",
" urm8zwGrwdMqeV4eslKR9H1RUdWGUQJNbtwmmjrt5DHpIhYHl5t3FpCBaGbV20Omo00Q38lBl9Mtrm",
" ZkZw+ktEk6X+0xCKssMF+2MADkSOIufbR5HrDVB89VZOHCO9DeXvCUUAw2hyJiL/LHmLzJ40zYoTmb",
" +F//f0k0j+tRdbkefyRoCmwG7YGiT+2hnCdgcezswnzah5J3ZKlrg7jOGo1LxtbvNUzxNBbC6S/aNg",
" wm6qxo7xegRhmEl5uZ16zwyj4qz+xkjGy25Of5mWfUDoNw7OT7sjUbHOOMc="
);
let ah = Aheader::from_str(fixed_header).expect("failed to parse");
assert_eq!(ah.addr, "a@b.example.org");
assert_eq!(ah.prefer_encrypt, EncryptPreference::Mutual);
assert_eq!(format!("{}", ah), fixed_header);
let rendered = ah.to_string();
assert_eq!(rendered, fixed_header);
let ah = Aheader::from_str(&format!(" _foo; __FOO=BAR ;;; addr = a@b.example.org ;\r\n prefer-encrypt = mutual ; keydata = {}", rawkey())).expect("failed to parse");
let ah = Aheader::from_str(&format!(" _foo; __FOO=BAR ;;; addr = a@b.example.org ;\r\n prefer-encrypt = mutual ; keydata = {}", RAWKEY)).expect("failed to parse");
assert_eq!(ah.addr, "a@b.example.org");
assert_eq!(ah.prefer_encrypt, EncryptPreference::Mutual);
Aheader::from_str(&format!(
"addr=a@b.example.org; prefer-encrypt=ignoreUnknownValues; keydata={}",
rawkey()
RAWKEY
))
.expect("failed to parse");
Aheader::from_str(&format!("addr=a@b.example.org; keydata={}", rawkey()))
Aheader::from_str(&format!("addr=a@b.example.org; keydata={}", RAWKEY))
.expect("failed to parse");
}
@@ -256,4 +285,30 @@ mod tests {
assert!(Aheader::from_str(" ;;").is_err());
assert!(Aheader::from_str("addr=a@t.de; unknwon=1; keydata=jau").is_err());
}
#[test]
fn test_display_aheader() {
assert!(format!(
"{}",
Aheader::new(
"test@example.com".to_string(),
Key::from_base64(RAWKEY, KeyType::Public).unwrap(),
EncryptPreference::Mutual
)
)
.contains("prefer-encrypt=mutual;"));
// According to Autocrypt Level 1 specification,
// only "prefer-encrypt=mutual;" can be used.
// If the setting is nopreference, the whole attribute is omitted.
assert!(!format!(
"{}",
Aheader::new(
"test@example.com".to_string(),
Key::from_base64(RAWKEY, KeyType::Public).unwrap(),
EncryptPreference::NoPreference
)
)
.contains("prefer-encrypt"));
}
}

View File

@@ -1,3 +1,5 @@
//! # Blob directory management
use std::ffi::OsStr;
use std::fmt;
use std::fs;
@@ -30,11 +32,11 @@ impl<'a> BlobObject<'a> {
///
/// # Errors
///
/// [BlobErrorKind::CreateFailure] is used when the file could not
/// [BlobError::CreateFailure] is used when the file could not
/// be created. You can expect [BlobError.cause] to contain an
/// underlying error.
///
/// [BlobErrorKind::WriteFailure] is used when the file could not
/// [BlobError::WriteFailure] is used when the file could not
/// be written to. You can expect [BlobError.cause] to contain an
/// underlying error.
pub fn create(
@@ -46,7 +48,12 @@ impl<'a> BlobObject<'a> {
let (stem, ext) = BlobObject::sanitise_name(suggested_name.as_ref());
let (name, mut file) = BlobObject::create_new_file(&blobdir, &stem, &ext)?;
file.write_all(data)
.map_err(|err| BlobError::new_write_failure(blobdir, &name, err))?;
.map_err(|err| BlobError::WriteFailure {
blobdir: blobdir.to_path_buf(),
blobname: name.clone(),
cause: err,
backtrace: failure::Backtrace::new(),
})?;
let blob = BlobObject {
blobdir,
name: format!("$BLOBDIR/{}", name),
@@ -69,18 +76,25 @@ impl<'a> BlobObject<'a> {
Ok(file) => return Ok((name, file)),
Err(err) => {
if attempt == max_attempt {
return Err(BlobError::new_create_failure(dir, &name, err));
return Err(BlobError::CreateFailure {
blobdir: dir.to_path_buf(),
blobname: name,
cause: err,
backtrace: failure::Backtrace::new(),
});
} else {
name = format!("{}-{}{}", stem, rand::random::<u32>(), ext);
}
}
}
}
Err(BlobError::new_create_failure(
dir,
&name,
format_err!("Unreachable code - supposedly"),
))
// This is supposed to be unreachable, but the compiler doesn't know.
Err(BlobError::CreateFailure {
blobdir: dir.to_path_buf(),
blobname: name,
cause: std::io::Error::new(std::io::ErrorKind::Other, "supposedly unreachable"),
backtrace: failure::Backtrace::new(),
})
}
/// Creates a new blob object with unique name by copying an existing file.
@@ -93,24 +107,35 @@ impl<'a> BlobObject<'a> {
/// # Errors
///
/// In addition to the errors in [BlobObject::create] the
/// [BlobErrorKind::CopyFailure] is used when the data can not be
/// [BlobError::CopyFailure] is used when the data can not be
/// copied.
pub fn create_and_copy(
context: &'a Context,
src: impl AsRef<Path>,
) -> std::result::Result<BlobObject<'a>, BlobError> {
let mut src_file = fs::File::open(src.as_ref()).map_err(|err| {
BlobError::new_copy_failure(context.get_blobdir(), "", src.as_ref(), err)
let mut src_file = fs::File::open(src.as_ref()).map_err(|err| BlobError::CopyFailure {
blobdir: context.get_blobdir().to_path_buf(),
blobname: String::from(""),
src: src.as_ref().to_path_buf(),
cause: err,
backtrace: failure::Backtrace::new(),
})?;
let (stem, ext) = BlobObject::sanitise_name(&src.as_ref().to_string_lossy());
let (name, mut dst_file) = BlobObject::create_new_file(context.get_blobdir(), &stem, &ext)?;
let name_for_err = name.clone();
std::io::copy(&mut src_file, &mut dst_file).map_err(|err| {
{
// Attempt to remove the failed file, swallow errors resulting from that.
let path = context.get_blobdir().join(&name);
let path = context.get_blobdir().join(&name_for_err);
fs::remove_file(path).ok();
}
BlobError::new_copy_failure(context.get_blobdir(), &name, src.as_ref(), err)
BlobError::CopyFailure {
blobdir: context.get_blobdir().to_path_buf(),
blobname: name_for_err,
src: src.as_ref().to_path_buf(),
cause: err,
backtrace: failure::Backtrace::new(),
}
})?;
let blob = BlobObject {
blobdir: context.get_blobdir(),
@@ -134,13 +159,14 @@ impl<'a> BlobObject<'a> {
/// This merely delegates to the [BlobObject::create_and_copy] and
/// the [BlobObject::from_path] methods. See those for possible
/// errors.
pub fn create_from_path(
pub fn new_from_path(
context: &Context,
src: impl AsRef<Path>,
) -> std::result::Result<BlobObject, BlobError> {
match src.as_ref().starts_with(context.get_blobdir()) {
true => BlobObject::from_path(context, src),
false => BlobObject::create_and_copy(context, src),
if src.as_ref().starts_with(context.get_blobdir()) {
BlobObject::from_path(context, src)
} else {
BlobObject::create_and_copy(context, src)
}
}
@@ -153,10 +179,10 @@ impl<'a> BlobObject<'a> {
///
/// # Errors
///
/// [BlobErrorKind::WrongBlobdir] is used if the path is not in
/// [BlobError::WrongBlobdir] is used if the path is not in
/// the blob directory.
///
/// [BlobErrorKind::WrongName] is used if the file name does not
/// [BlobError::WrongName] is used if the file name does not
/// remain identical after sanitisation.
pub fn from_path(
context: &Context,
@@ -165,13 +191,21 @@ impl<'a> BlobObject<'a> {
let rel_path = path
.as_ref()
.strip_prefix(context.get_blobdir())
.map_err(|_| BlobError::new_wrong_blobdir(context.get_blobdir(), path.as_ref()))?;
.map_err(|_| BlobError::WrongBlobdir {
blobdir: context.get_blobdir().to_path_buf(),
src: path.as_ref().to_path_buf(),
backtrace: failure::Backtrace::new(),
})?;
if !BlobObject::is_acceptible_blob_name(&rel_path) {
return Err(BlobError::new_wrong_name(path.as_ref()));
return Err(BlobError::WrongName {
blobname: path.as_ref().to_path_buf(),
backtrace: failure::Backtrace::new(),
});
}
let name = rel_path
.to_str()
.ok_or_else(|| BlobError::new_wrong_name(path.as_ref()))?;
let name = rel_path.to_str().ok_or_else(|| BlobError::WrongName {
blobname: path.as_ref().to_path_buf(),
backtrace: failure::Backtrace::new(),
})?;
BlobObject::from_name(context, name.to_string())
}
@@ -184,7 +218,7 @@ impl<'a> BlobObject<'a> {
///
/// # Errors
///
/// [BlobErrorKind::WrongName] is used if the name is not a valid
/// [BlobError::WrongName] is used if the name is not a valid
/// blobname, i.e. if [BlobObject::sanitise_name] does modify the
/// provided name.
pub fn from_name(
@@ -196,7 +230,10 @@ impl<'a> BlobObject<'a> {
false => name,
};
if !BlobObject::is_acceptible_blob_name(&name) {
return Err(BlobError::new_wrong_name(name));
return Err(BlobError::WrongName {
blobname: PathBuf::from(name),
backtrace: failure::Backtrace::new(),
});
}
Ok(BlobObject {
blobdir: context.get_blobdir(),
@@ -262,13 +299,13 @@ impl<'a> BlobObject<'a> {
fn sanitise_name(name: &str) -> (String, String) {
let mut name = name.to_string();
for part in name.rsplit('/') {
if part.len() > 0 {
if !part.is_empty() {
name = part.to_string();
break;
}
}
for part in name.rsplit('\\') {
if part.len() > 0 {
if !part.is_empty() {
name = part.to_string();
break;
}
@@ -280,13 +317,13 @@ impl<'a> BlobObject<'a> {
};
let clean = sanitize_filename::sanitize_with_options(name, opts);
let mut iter = clean.rsplitn(2, '.');
let mut ext = iter.next().unwrap_or_default().to_string();
let mut iter = clean.splitn(2, '.');
let mut stem = iter.next().unwrap_or_default().to_string();
ext.truncate(32);
let mut ext = iter.next().unwrap_or_default().to_string();
stem.truncate(64);
match stem.len() {
0 => (ext, "".to_string()),
ext.truncate(32);
match ext.len() {
0 => (stem, "".to_string()),
_ => (stem, format!(".{}", ext).to_lowercase()),
}
}
@@ -321,171 +358,50 @@ impl<'a> fmt::Display for BlobObject<'a> {
}
/// Errors for the [BlobObject].
///
/// To keep the return type small and thus the happy path fast this
/// stores everything on the heap.
#[derive(Debug)]
pub struct BlobError {
inner: Box<BlobErrorInner>,
}
#[derive(Debug)]
struct BlobErrorInner {
kind: BlobErrorKind,
data: BlobErrorData,
backtrace: failure::Backtrace,
}
/// Error kind for [BlobError].
///
/// Each error kind has associated data in the [BlobErrorData].
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum BlobErrorKind {
/// Failed to create the blob.
CreateFailure,
/// Failed to write data to blob.
WriteFailure,
/// Failed to copy data to blob.
CopyFailure,
/// Blob is not in the blobdir.
WrongBlobdir,
/// Blob has a bad name.
///
/// E.g. the name is not sanitised correctly or contains a
/// sub-directory.
WrongName,
}
/// Associated data for each [BlobError] error kind.
///
/// This is not stored directly on the [BlobErrorKind] so that the
/// kind can stay trivially Copy and Eq. It is however possible to
/// create a [BlobError] with mismatching [BlobErrorKind] and
/// [BlobErrorData], don't do that.
///
/// Any blobname stored here is the bare name, without the `$BLOBDIR`
/// prefix. All data is owned so that errors do not need to be tied
/// to any lifetimes.
#[derive(Debug)]
enum BlobErrorData {
#[derive(Fail, Debug)]
pub enum BlobError {
CreateFailure {
blobdir: PathBuf,
blobname: String,
cause: failure::Error,
#[cause]
cause: std::io::Error,
backtrace: failure::Backtrace,
},
WriteFailure {
blobdir: PathBuf,
blobname: String,
cause: failure::Error,
#[cause]
cause: std::io::Error,
backtrace: failure::Backtrace,
},
CopyFailure {
blobdir: PathBuf,
blobname: String,
src: PathBuf,
cause: failure::Error,
#[cause]
cause: std::io::Error,
backtrace: failure::Backtrace,
},
WrongBlobdir {
blobdir: PathBuf,
src: PathBuf,
backtrace: failure::Backtrace,
},
WrongName {
blobname: PathBuf,
backtrace: failure::Backtrace,
},
}
impl BlobError {
pub fn kind(&self) -> BlobErrorKind {
self.inner.kind
}
fn new_create_failure(
blobdir: impl Into<PathBuf>,
blobname: impl Into<String>,
cause: impl Into<failure::Error>,
) -> BlobError {
BlobError {
inner: Box::new(BlobErrorInner {
kind: BlobErrorKind::CreateFailure,
data: BlobErrorData::CreateFailure {
blobdir: blobdir.into(),
blobname: blobname.into(),
cause: cause.into(),
},
backtrace: failure::Backtrace::new(),
}),
}
}
fn new_write_failure(
blobdir: impl Into<PathBuf>,
blobname: impl Into<String>,
cause: impl Into<failure::Error>,
) -> BlobError {
BlobError {
inner: Box::new(BlobErrorInner {
kind: BlobErrorKind::WriteFailure,
data: BlobErrorData::WriteFailure {
blobdir: blobdir.into(),
blobname: blobname.into(),
cause: cause.into(),
},
backtrace: failure::Backtrace::new(),
}),
}
}
fn new_copy_failure(
blobdir: impl Into<PathBuf>,
blobname: impl Into<String>,
src: impl Into<PathBuf>,
cause: impl Into<failure::Error>,
) -> BlobError {
BlobError {
inner: Box::new(BlobErrorInner {
kind: BlobErrorKind::CopyFailure,
data: BlobErrorData::CopyFailure {
blobdir: blobdir.into(),
blobname: blobname.into(),
src: src.into(),
cause: cause.into(),
},
backtrace: failure::Backtrace::new(),
}),
}
}
fn new_wrong_blobdir(blobdir: impl Into<PathBuf>, src: impl Into<PathBuf>) -> BlobError {
BlobError {
inner: Box::new(BlobErrorInner {
kind: BlobErrorKind::WrongBlobdir,
data: BlobErrorData::WrongBlobdir {
blobdir: blobdir.into(),
src: src.into(),
},
backtrace: failure::Backtrace::new(),
}),
}
}
fn new_wrong_name(blobname: impl Into<PathBuf>) -> BlobError {
BlobError {
inner: Box::new(BlobErrorInner {
kind: BlobErrorKind::WrongName,
data: BlobErrorData::WrongName {
blobname: blobname.into(),
},
backtrace: failure::Backtrace::new(),
}),
}
}
}
// Implementing Display is done by hand because the failure
// #[fail(display = "...")] syntax does not allow using
// `blobdir.display()`.
impl fmt::Display for BlobError {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
// Match on the data rather than kind, they are equivalent for
// identifying purposes but contain the actual data we need.
match &self.inner.data {
BlobErrorData::CreateFailure {
match &self {
BlobError::CreateFailure {
blobdir, blobname, ..
} => write!(
f,
@@ -493,7 +409,7 @@ impl fmt::Display for BlobError {
blobname,
blobdir.display()
),
BlobErrorData::WriteFailure {
BlobError::WriteFailure {
blobdir, blobname, ..
} => write!(
f,
@@ -501,7 +417,7 @@ impl fmt::Display for BlobError {
blobname,
blobdir.display()
),
BlobErrorData::CopyFailure {
BlobError::CopyFailure {
blobdir,
blobname,
src,
@@ -513,34 +429,19 @@ impl fmt::Display for BlobError {
blobname,
blobdir.display(),
),
BlobErrorData::WrongBlobdir { blobdir, src } => write!(
BlobError::WrongBlobdir { blobdir, src, .. } => write!(
f,
"File path {} is not in blobdir {}",
src.display(),
blobdir.display(),
),
BlobErrorData::WrongName { blobname } => {
BlobError::WrongName { blobname, .. } => {
write!(f, "Blob has a bad name: {}", blobname.display(),)
}
}
}
}
impl failure::Fail for BlobError {
fn cause(&self) -> Option<&dyn failure::Fail> {
match &self.inner.data {
BlobErrorData::CreateFailure { cause, .. }
| BlobErrorData::WriteFailure { cause, .. }
| BlobErrorData::CopyFailure { cause, .. } => Some(cause.as_fail()),
_ => None,
}
}
fn backtrace(&self) -> Option<&failure::Backtrace> {
Some(&self.inner.backtrace)
}
}
#[cfg(test)]
mod tests {
use super::*;
@@ -582,23 +483,23 @@ mod tests {
#[test]
fn test_suffix() {
let t = dummy_context();
let foo = BlobObject::create(&t.ctx, "foo.txt", b"hello").unwrap();
assert_eq!(foo.suffix(), Some("txt"));
let bar = BlobObject::create(&t.ctx, "bar", b"world").unwrap();
assert_eq!(bar.suffix(), None);
let blob = BlobObject::create(&t.ctx, "foo.txt", b"hello").unwrap();
assert_eq!(blob.suffix(), Some("txt"));
let blob = BlobObject::create(&t.ctx, "bar", b"world").unwrap();
assert_eq!(blob.suffix(), None);
}
#[test]
fn test_create_dup() {
let t = dummy_context();
BlobObject::create(&t.ctx, "foo.txt", b"hello").unwrap();
let foo = t.ctx.get_blobdir().join("foo.txt");
assert!(foo.exists());
let foo_path = t.ctx.get_blobdir().join("foo.txt");
assert!(foo_path.exists());
BlobObject::create(&t.ctx, "foo.txt", b"world").unwrap();
for dirent in fs::read_dir(t.ctx.get_blobdir()).unwrap() {
let fname = dirent.unwrap().file_name();
if fname == foo.file_name().unwrap() {
assert_eq!(fs::read(&foo).unwrap(), b"hello");
if fname == foo_path.file_name().unwrap() {
assert_eq!(fs::read(&foo_path).unwrap(), b"hello");
} else {
let name = fname.to_str().unwrap();
assert!(name.starts_with("foo"));
@@ -607,6 +508,26 @@ mod tests {
}
}
#[test]
fn test_double_ext_preserved() {
let t = dummy_context();
BlobObject::create(&t.ctx, "foo.tar.gz", b"hello").unwrap();
let foo_path = t.ctx.get_blobdir().join("foo.tar.gz");
assert!(foo_path.exists());
BlobObject::create(&t.ctx, "foo.tar.gz", b"world").unwrap();
for dirent in fs::read_dir(t.ctx.get_blobdir()).unwrap() {
let fname = dirent.unwrap().file_name();
if fname == foo_path.file_name().unwrap() {
assert_eq!(fs::read(&foo_path).unwrap(), b"hello");
} else {
let name = fname.to_str().unwrap();
println!("{}", name);
assert!(name.starts_with("foo"));
assert!(name.ends_with(".tar.gz"));
}
}
}
#[test]
fn test_create_long_names() {
let t = dummy_context();
@@ -638,14 +559,14 @@ mod tests {
let src_ext = t.dir.path().join("external");
fs::write(&src_ext, b"boo").unwrap();
let blob = BlobObject::create_from_path(&t.ctx, &src_ext).unwrap();
let blob = BlobObject::new_from_path(&t.ctx, &src_ext).unwrap();
assert_eq!(blob.as_name(), "$BLOBDIR/external");
let data = fs::read(blob.to_abs_path()).unwrap();
assert_eq!(data, b"boo");
let src_int = t.ctx.get_blobdir().join("internal");
fs::write(&src_int, b"boo").unwrap();
let blob = BlobObject::create_from_path(&t.ctx, &src_int).unwrap();
let blob = BlobObject::new_from_path(&t.ctx, &src_int).unwrap();
assert_eq!(blob.as_name(), "$BLOBDIR/internal");
let data = fs::read(blob.to_abs_path()).unwrap();
assert_eq!(data, b"boo");
@@ -655,7 +576,7 @@ mod tests {
let t = dummy_context();
let src_ext = t.dir.path().join("autocrypt-setup-message-4137848473.html");
fs::write(&src_ext, b"boo").unwrap();
let blob = BlobObject::create_from_path(&t.ctx, &src_ext).unwrap();
let blob = BlobObject::new_from_path(&t.ctx, &src_ext).unwrap();
assert_eq!(
blob.as_name(),
"$BLOBDIR/autocrypt-setup-message-4137848473.html"

File diff suppressed because it is too large Load Diff

View File

@@ -1,10 +1,12 @@
//! # Chat list module
use crate::chat::*;
use crate::constants::*;
use crate::contact::*;
use crate::context::*;
use crate::error::Result;
use crate::lot::Lot;
use crate::message::{Message, MsgId};
use crate::message::{Message, MessageState, MsgId};
use crate::stock::StockMessage;
/// An object representing a single chatlist in memory.
@@ -58,7 +60,7 @@ impl Chatlist {
/// or "Not now".
/// The UI can also offer a "Close" button that calls dc_marknoticed_contact() then.
/// - DC_CHAT_ID_ARCHIVED_LINK (6) - this special chat is present if the user has
/// archived _any_ chat using dc_archive_chat(). The UI should show a link as
/// archived *any* chat using dc_archive_chat(). The UI should show a link as
/// "Show archived chats", if the user clicks this item, the UI should show a
/// list of all archived chats that can be created by this function hen using
/// the DC_GCL_ARCHIVED_ONLY flag.
@@ -69,7 +71,7 @@ impl Chatlist {
/// The `listflags` is a combination of flags:
/// - if the flag DC_GCL_ARCHIVED_ONLY is set, only archived chats are returned.
/// if DC_GCL_ARCHIVED_ONLY is not set, only unarchived chats are returned and
/// the pseudo-chat DC_CHAT_ID_ARCHIVED_LINK is added if there are _any_ archived
/// the pseudo-chat DC_CHAT_ID_ARCHIVED_LINK is added if there are *any* archived
/// chats
/// - if the flag DC_GCL_NO_SPECIALS is set, deaddrop and archive link are not added
/// to the list (may be used eg. for selecting chats on forwarding, the flag is
@@ -117,46 +119,42 @@ impl Chatlist {
let mut ids = if let Some(query_contact_id) = query_contact_id {
// show chats shared with a given contact
context.sql.query_map(
concat!(
"SELECT c.id, m.id",
" FROM chats c",
" LEFT JOIN msgs m",
" ON c.id=m.chat_id",
" AND m.timestamp=(",
" SELECT MAX(timestamp)",
" FROM msgs",
" WHERE chat_id=c.id",
" AND hidden=0)",
" WHERE c.id>9",
" AND c.blocked=0",
" AND c.id IN(SELECT chat_id FROM chats_contacts WHERE contact_id=?)",
" GROUP BY c.id",
" ORDER BY IFNULL(m.timestamp,0) DESC, m.id DESC;"
),
params![query_contact_id as i32],
"SELECT c.id, m.id
FROM chats c
LEFT JOIN msgs m
ON c.id=m.chat_id
AND m.timestamp=(
SELECT MAX(timestamp)
FROM msgs
WHERE chat_id=c.id
AND (hidden=0 OR state=?))
WHERE c.id>9
AND c.blocked=0
AND c.id IN(SELECT chat_id FROM chats_contacts WHERE contact_id=?)
GROUP BY c.id
ORDER BY IFNULL(m.timestamp,c.created_timestamp) DESC, m.id DESC;",
params![MessageState::OutDraft, query_contact_id as i32],
process_row,
process_rows,
)?
} else if 0 != listflags & DC_GCL_ARCHIVED_ONLY {
// show archived chats
context.sql.query_map(
concat!(
"SELECT c.id, m.id",
" FROM chats c",
" LEFT JOIN msgs m",
" ON c.id=m.chat_id",
" AND m.timestamp=(",
" SELECT MAX(timestamp)",
" FROM msgs",
" WHERE chat_id=c.id",
" AND hidden=0)",
" WHERE c.id>9",
" AND c.blocked=0",
" AND c.archived=1",
" GROUP BY c.id",
" ORDER BY IFNULL(m.timestamp,0) DESC, m.id DESC;"
),
params![],
"SELECT c.id, m.id
FROM chats c
LEFT JOIN msgs m
ON c.id=m.chat_id
AND m.timestamp=(
SELECT MAX(timestamp)
FROM msgs
WHERE chat_id=c.id
AND (hidden=0 OR state=?))
WHERE c.id>9
AND c.blocked=0
AND c.archived=1
GROUP BY c.id
ORDER BY IFNULL(m.timestamp,c.created_timestamp) DESC, m.id DESC;",
params![MessageState::OutDraft],
process_row,
process_rows,
)?
@@ -166,46 +164,42 @@ impl Chatlist {
let str_like_cmd = format!("%{}%", query);
context.sql.query_map(
concat!(
"SELECT c.id, m.id",
" FROM chats c",
" LEFT JOIN msgs m",
" ON c.id=m.chat_id",
" AND m.timestamp=(",
" SELECT MAX(timestamp)",
" FROM msgs",
" WHERE chat_id=c.id",
" AND hidden=0)",
" WHERE c.id>9",
" AND c.blocked=0",
" AND c.name LIKE ?",
" GROUP BY c.id",
" ORDER BY IFNULL(m.timestamp,0) DESC, m.id DESC;"
),
params![str_like_cmd],
"SELECT c.id, m.id
FROM chats c
LEFT JOIN msgs m
ON c.id=m.chat_id
AND m.timestamp=(
SELECT MAX(timestamp)
FROM msgs
WHERE chat_id=c.id
AND (hidden=0 OR state=?))
WHERE c.id>9
AND c.blocked=0
AND c.name LIKE ?
GROUP BY c.id
ORDER BY IFNULL(m.timestamp,c.created_timestamp) DESC, m.id DESC;",
params![MessageState::OutDraft, str_like_cmd],
process_row,
process_rows,
)?
} else {
// show normal chatlist
let mut ids = context.sql.query_map(
concat!(
"SELECT c.id, m.id",
" FROM chats c",
" LEFT JOIN msgs m",
" ON c.id=m.chat_id",
" AND m.timestamp=(",
" SELECT MAX(timestamp)",
" FROM msgs",
" WHERE chat_id=c.id",
" AND hidden=0)",
" WHERE c.id>9",
" AND c.blocked=0",
" AND c.archived=0",
" GROUP BY c.id",
" ORDER BY IFNULL(m.timestamp,0) DESC, m.id DESC;"
),
params![],
"SELECT c.id, m.id
FROM chats c
LEFT JOIN msgs m
ON c.id=m.chat_id
AND m.timestamp=(
SELECT MAX(timestamp)
FROM msgs
WHERE chat_id=c.id
AND (hidden=0 OR state=?))
WHERE c.id>9
AND c.blocked=0
AND c.archived=0
GROUP BY c.id
ORDER BY IFNULL(m.timestamp,c.created_timestamp) DESC, m.id DESC;",
params![MessageState::OutDraft],
process_row,
process_rows,
)?;
@@ -233,6 +227,7 @@ impl Chatlist {
self.ids.len()
}
/// Returns true if chatlist is empty.
pub fn is_empty(&self) -> bool {
self.ids.is_empty()
}
@@ -294,7 +289,7 @@ impl Chatlist {
let lastmsg_id = self.ids[index].1;
let mut lastcontact = None;
let mut lastmsg = if let Ok(lastmsg) = Message::load_from_db(context, lastmsg_id) {
let lastmsg = if let Ok(lastmsg) = Message::load_from_db(context, lastmsg_id) {
if lastmsg.from_id != DC_CONTACT_ID_SELF
&& (chat.typ == Chattype::Group || chat.typ == Chattype::VerifiedGroup)
{
@@ -306,16 +301,6 @@ impl Chatlist {
None
};
if let Ok(draft) = get_draft(context, chat.id) {
if draft.is_some()
&& (lastmsg.is_none()
|| draft.as_ref().unwrap().timestamp_sort
> lastmsg.as_ref().unwrap().timestamp_sort)
{
lastmsg = draft;
}
}
if chat.id == DC_CHAT_ID_ARCHIVED_LINK {
ret.text2 = None;
} else if lastmsg.is_none() || lastmsg.as_ref().unwrap().from_id == DC_CONTACT_ID_UNDEFINED
@@ -329,6 +314,7 @@ impl Chatlist {
}
}
/// Get the number of archived chats
pub fn dc_get_archived_cnt(context: &Context) -> u32 {
context
.sql
@@ -358,3 +344,44 @@ fn get_last_deaddrop_fresh_msg(context: &Context) -> Option<MsgId> {
params![],
)
}
#[cfg(test)]
mod tests {
use super::*;
use crate::chat;
use crate::test_utils::*;
#[test]
fn test_try_load() {
let t = dummy_context();
let chat_id1 = create_group_chat(&t.ctx, VerifiedStatus::Unverified, "a chat").unwrap();
let chat_id2 = create_group_chat(&t.ctx, VerifiedStatus::Unverified, "b chat").unwrap();
let chat_id3 = create_group_chat(&t.ctx, VerifiedStatus::Unverified, "c chat").unwrap();
// check that the chatlist starts with the most recent message
let chats = Chatlist::try_load(&t.ctx, 0, None, None).unwrap();
assert_eq!(chats.len(), 3);
assert_eq!(chats.get_chat_id(0), chat_id3);
assert_eq!(chats.get_chat_id(1), chat_id2);
assert_eq!(chats.get_chat_id(2), chat_id1);
// drafts are sorted to the top
let mut msg = Message::new(Viewtype::Text);
msg.set_text(Some("hello".to_string()));
set_draft(&t.ctx, chat_id2, Some(&mut msg));
let chats = Chatlist::try_load(&t.ctx, 0, None, None).unwrap();
assert_eq!(chats.get_chat_id(0), chat_id2);
// check chatlist query and archive functionality
let chats = Chatlist::try_load(&t.ctx, 0, Some("b"), None).unwrap();
assert_eq!(chats.len(), 1);
let chats = Chatlist::try_load(&t.ctx, DC_GCL_ARCHIVED_ONLY, None, None).unwrap();
assert_eq!(chats.len(), 0);
chat::archive(&t.ctx, chat_id1, true).ok();
let chats = Chatlist::try_load(&t.ctx, DC_GCL_ARCHIVED_ONLY, None, None).unwrap();
assert_eq!(chats.len(), 1);
}
}

View File

@@ -1,12 +1,15 @@
//! # Key-value configuration management
use strum::{EnumProperty, IntoEnumIterator};
use strum_macros::{AsRefStr, Display, EnumIter, EnumProperty, EnumString};
use crate::blob::BlobObject;
use crate::constants::DC_VERSION_STR;
use crate::context::Context;
use crate::dc_tools::*;
use crate::error::Error;
use crate::job::*;
use crate::stock::StockMessage;
use rusqlite::NO_PARAMS;
/// The available configuration keys.
#[derive(
@@ -26,27 +29,38 @@ pub enum Config {
SendPort,
SmtpCertificateChecks,
ServerFlags,
#[strum(props(default = "INBOX"))]
ImapFolder,
Displayname,
Selfstatus,
Selfavatar,
#[strum(props(default = "1"))]
#[strum(props(default = "0"))]
BccSelf,
#[strum(props(default = "1"))]
E2eeEnabled,
#[strum(props(default = "1"))]
MdnsEnabled,
#[strum(props(default = "1"))]
InboxWatch,
#[strum(props(default = "1"))]
SentboxWatch,
#[strum(props(default = "1"))]
MvboxWatch,
#[strum(props(default = "1"))]
MvboxMove,
#[strum(props(default = "0"))] // also change ShowEmails.default() on changes
ShowEmails,
SaveMimeHeaders,
ConfiguredAddr,
ConfiguredMailServer,
@@ -64,11 +78,13 @@ pub enum Config {
ConfiguredSendSecurity,
ConfiguredE2EEEnabled,
Configured,
// Deprecated
#[strum(serialize = "sys.version")]
SysVersion,
#[strum(serialize = "sys.msgsize_max_recommended")]
SysMsgsizeMaxRecommended,
#[strum(serialize = "sys.config_keys")]
SysConfigKeys,
}
@@ -110,16 +126,24 @@ impl Context {
/// Set the given config key.
/// If `None` is passed as a value the value is cleared and set to the default if there is one.
pub fn set_config(&self, key: Config, value: Option<&str>) -> Result<(), Error> {
pub fn set_config(&self, key: Config, value: Option<&str>) -> crate::sql::Result<()> {
match key {
Config::Selfavatar if value.is_some() => {
let rel_path = std::fs::canonicalize(value.unwrap())?;
Config::Selfavatar => {
self.sql
.set_raw_config(self, key, Some(&rel_path.to_string_lossy()))
.execute("UPDATE contacts SET selfavatar_sent=0;", NO_PARAMS)?;
self.sql
.set_raw_config_bool(self, "attach_selfavatar", true)?;
match value {
Some(value) => {
let blob = BlobObject::new_from_path(&self, value)?;
self.sql.set_raw_config(self, key, Some(blob.as_name()))
}
None => self.sql.set_raw_config(self, key, None),
}
}
Config::InboxWatch => {
let ret = self.sql.set_raw_config(self, key, value);
interrupt_imap_idle(self);
interrupt_inbox_idle(self, true);
ret
}
Config::SentboxWatch => {
@@ -165,6 +189,8 @@ mod tests {
use std::str::FromStr;
use std::string::ToString;
use crate::test_utils::*;
#[test]
fn test_to_string() {
assert_eq!(Config::MailServer.to_string(), "mail_server");
@@ -181,4 +207,32 @@ mod tests {
fn test_default_prop() {
assert_eq!(Config::ImapFolder.get_str("default"), Some("INBOX"));
}
#[test]
fn test_selfavatar() -> failure::Fallible<()> {
let t = dummy_context();
let avatar_src = t.dir.path().join("avatar.jpg");
std::fs::write(&avatar_src, b"avatar")?;
let avatar_blob = t.ctx.get_blobdir().join("avatar.jpg");
assert!(!avatar_blob.exists());
t.ctx
.set_config(Config::Selfavatar, Some(&avatar_src.to_str().unwrap()))?;
assert!(avatar_blob.exists());
assert_eq!(std::fs::read(&avatar_blob)?, b"avatar");
let avatar_cfg = t.ctx.get_config(Config::Selfavatar);
assert_eq!(avatar_cfg, avatar_blob.to_str().map(|s| s.to_string()));
Ok(())
}
#[test]
fn test_selfavatar_in_blobdir() -> failure::Fallible<()> {
let t = dummy_context();
let avatar_src = t.ctx.get_blobdir().join("avatar.jpg");
std::fs::write(&avatar_src, b"avatar")?;
t.ctx
.set_config(Config::Selfavatar, Some(&avatar_src.to_str().unwrap()))?;
let avatar_cfg = t.ctx.get_config(Config::Selfavatar);
assert_eq!(avatar_cfg, avatar_src.to_str().map(|s| s.to_string()));
Ok(())
}
}

View File

@@ -1,16 +1,43 @@
//! # Thunderbird's Autoconfiguration implementation
//!
//! Documentation: https://developer.mozilla.org/en-US/docs/Mozilla/Thunderbird/Autoconfiguration */
use quick_xml;
use quick_xml::events::{BytesEnd, BytesStart, BytesText};
use crate::constants::*;
use crate::context::Context;
use crate::error::Error;
use crate::login_param::LoginParam;
use super::read_autoconf_file;
/* ******************************************************************************
* Thunderbird's Autoconfigure
******************************************************************************/
/* documentation: https://developer.mozilla.org/en-US/docs/Mozilla/Thunderbird/Autoconfiguration */
use super::read_url::read_url;
#[derive(Debug, Fail)]
pub enum Error {
#[fail(display = "Invalid email address: {:?}", _0)]
InvalidEmailAddress(String),
#[fail(display = "XML error at position {}", position)]
InvalidXml {
position: usize,
#[cause]
error: quick_xml::Error,
},
#[fail(display = "Bad or incomplete autoconfig")]
IncompleteAutoconfig(LoginParam),
#[fail(display = "Failed to get URL {}", _0)]
ReadUrlError(#[cause] super::read_url::Error),
}
pub type Result<T> = std::result::Result<T, Error>;
impl From<super::read_url::Error> for Error {
fn from(err: super::read_url::Error) -> Error {
Error::ReadUrlError(err)
}
}
#[derive(Debug)]
struct MozAutoconfigure<'a> {
pub in_emailaddr: &'a str,
pub in_emaildomain: &'a str,
@@ -22,13 +49,14 @@ struct MozAutoconfigure<'a> {
pub tag_config: MozConfigTag,
}
#[derive(PartialEq)]
#[derive(Debug, PartialEq)]
enum MozServer {
Undefined,
Imap,
Smtp,
}
#[derive(Debug)]
enum MozConfigTag {
Undefined,
Hostname,
@@ -37,15 +65,14 @@ enum MozConfigTag {
Username,
}
pub fn moz_parse_xml(in_emailaddr: &str, xml_raw: &str) -> Result<LoginParam, Error> {
fn parse_xml(in_emailaddr: &str, xml_raw: &str) -> Result<LoginParam> {
let mut reader = quick_xml::Reader::from_str(xml_raw);
reader.trim_text(true);
// Split address into local part and domain part.
let p = match in_emailaddr.find('@') {
Some(i) => i,
None => bail!("Email address {} does not contain @", in_emailaddr),
};
let p = in_emailaddr
.find('@')
.ok_or_else(|| Error::InvalidEmailAddress(in_emailaddr.to_string()))?;
let (in_emaillocalpart, in_emaildomain) = in_emailaddr.split_at(p);
let in_emaildomain = &in_emaildomain[1..];
@@ -62,22 +89,22 @@ pub fn moz_parse_xml(in_emailaddr: &str, xml_raw: &str) -> Result<LoginParam, Er
let mut buf = Vec::new();
loop {
match reader.read_event(&mut buf) {
Ok(quick_xml::events::Event::Start(ref e)) => {
let event = reader
.read_event(&mut buf)
.map_err(|error| Error::InvalidXml {
position: reader.buffer_position(),
error,
})?;
match event {
quick_xml::events::Event::Start(ref e) => {
moz_autoconfigure_starttag_cb(e, &mut moz_ac, &reader)
}
Ok(quick_xml::events::Event::End(ref e)) => moz_autoconfigure_endtag_cb(e, &mut moz_ac),
Ok(quick_xml::events::Event::Text(ref e)) => {
quick_xml::events::Event::End(ref e) => moz_autoconfigure_endtag_cb(e, &mut moz_ac),
quick_xml::events::Event::Text(ref e) => {
moz_autoconfigure_text_cb(e, &mut moz_ac, &reader)
}
Err(e) => {
bail!(
"Configure xml: Error at position {}: {:?}",
reader.buffer_position(),
e
);
}
Ok(quick_xml::events::Event::Eof) => break,
quick_xml::events::Event::Eof => break,
_ => (),
}
buf.clear();
@@ -88,27 +115,27 @@ pub fn moz_parse_xml(in_emailaddr: &str, xml_raw: &str) -> Result<LoginParam, Er
|| moz_ac.out.send_server.is_empty()
|| moz_ac.out.send_port == 0
{
let r = moz_ac.out.to_string();
bail!("Bad or incomplete autoconfig: {}", r,);
Err(Error::IncompleteAutoconfig(moz_ac.out))
} else {
Ok(moz_ac.out)
}
Ok(moz_ac.out)
}
pub fn moz_autoconfigure(
context: &Context,
url: &str,
param_in: &LoginParam,
) -> Option<LoginParam> {
let xml_raw = read_autoconf_file(context, url)?;
) -> Result<LoginParam> {
let xml_raw = read_url(context, url)?;
match moz_parse_xml(&param_in.addr, &xml_raw) {
Err(err) => {
warn!(context, "{}", err);
None
}
Ok(lp) => Some(lp),
let res = parse_xml(&param_in.addr, &xml_raw);
if let Err(err) = &res {
warn!(
context,
"Failed to parse Thunderbird autoconfiguration XML: {}", err
);
}
res
}
fn moz_autoconfigure_text_cb<B: std::io::BufRead>(
@@ -313,7 +340,7 @@ mod tests {
</loginPageInfo>
</webMail>
</clientConfig>";
let res = moz_parse_xml("example@outlook.com", xml_raw).expect("XML parsing failed");
let res = parse_xml("example@outlook.com", xml_raw).expect("XML parsing failed");
assert_eq!(res.mail_server, "outlook.office365.com");
assert_eq!(res.mail_port, 993);
assert_eq!(res.send_server, "smtp.office365.com");

View File

@@ -1,14 +1,41 @@
//! Outlook's Autodiscover
use quick_xml;
use quick_xml::events::BytesEnd;
use crate::constants::*;
use crate::context::Context;
use crate::error::Error;
use crate::login_param::LoginParam;
use super::read_autoconf_file;
use super::read_url::read_url;
#[derive(Debug, Fail)]
pub enum Error {
#[fail(display = "XML error at position {}", position)]
InvalidXml {
position: usize,
#[cause]
error: quick_xml::Error,
},
#[fail(display = "Bad or incomplete autoconfig")]
IncompleteAutoconfig(LoginParam),
#[fail(display = "Failed to get URL {}", _0)]
ReadUrlError(#[cause] super::read_url::Error),
#[fail(display = "Number of redirection is exceeded")]
RedirectionError,
}
pub type Result<T> = std::result::Result<T, Error>;
impl From<super::read_url::Error> for Error {
fn from(err: super::read_url::Error) -> Error {
Error::ReadUrlError(err)
}
}
/// Outlook's Autodiscover
struct OutlookAutodiscover {
pub out: LoginParam,
pub out_imap_set: bool,
@@ -25,7 +52,7 @@ enum ParsingResult {
RedirectUrl(String),
}
fn outlk_parse_xml(xml_raw: &str) -> Result<ParsingResult, Error> {
fn parse_xml(xml_raw: &str) -> Result<ParsingResult> {
let mut outlk_ad = OutlookAutodiscover {
out: LoginParam::new(),
out_imap_set: false,
@@ -45,8 +72,15 @@ fn outlk_parse_xml(xml_raw: &str) -> Result<ParsingResult, Error> {
let mut current_tag: Option<String> = None;
loop {
match reader.read_event(&mut buf) {
Ok(quick_xml::events::Event::Start(ref e)) => {
let event = reader
.read_event(&mut buf)
.map_err(|error| Error::InvalidXml {
position: reader.buffer_position(),
error,
})?;
match event {
quick_xml::events::Event::Start(ref e) => {
let tag = String::from_utf8_lossy(e.name()).trim().to_lowercase();
if tag == "protocol" {
@@ -61,11 +95,11 @@ fn outlk_parse_xml(xml_raw: &str) -> Result<ParsingResult, Error> {
current_tag = Some(tag);
}
}
Ok(quick_xml::events::Event::End(ref e)) => {
quick_xml::events::Event::End(ref e) => {
outlk_autodiscover_endtag_cb(e, &mut outlk_ad);
current_tag = None;
}
Ok(quick_xml::events::Event::Text(ref e)) => {
quick_xml::events::Event::Text(ref e) => {
let val = e.unescape_and_decode(&reader).unwrap_or_default();
if let Some(ref tag) = current_tag {
@@ -81,21 +115,14 @@ fn outlk_parse_xml(xml_raw: &str) -> Result<ParsingResult, Error> {
};
}
}
Err(e) => {
bail!(
"Configure xml: Error at position {}: {:?}",
reader.buffer_position(),
e
);
}
Ok(quick_xml::events::Event::Eof) => break,
quick_xml::events::Event::Eof => break,
_ => (),
}
buf.clear();
}
// XML redirect via redirecturl
if outlk_ad.config_redirecturl.is_none()
let res = if outlk_ad.config_redirecturl.is_none()
|| outlk_ad.config_redirecturl.as_ref().unwrap().is_empty()
{
if outlk_ad.out.mail_server.is_empty()
@@ -103,41 +130,34 @@ fn outlk_parse_xml(xml_raw: &str) -> Result<ParsingResult, Error> {
|| outlk_ad.out.send_server.is_empty()
|| outlk_ad.out.send_port == 0
{
let r = outlk_ad.out.to_string();
bail!("Bad or incomplete autoconfig: {}", r,);
return Err(Error::IncompleteAutoconfig(outlk_ad.out));
}
Ok(ParsingResult::LoginParam(outlk_ad.out))
ParsingResult::LoginParam(outlk_ad.out)
} else {
Ok(ParsingResult::RedirectUrl(
outlk_ad.config_redirecturl.unwrap(),
))
}
ParsingResult::RedirectUrl(outlk_ad.config_redirecturl.unwrap())
};
Ok(res)
}
pub fn outlk_autodiscover(
context: &Context,
url: &str,
_param_in: &LoginParam,
) -> Option<LoginParam> {
) -> Result<LoginParam> {
let mut url = url.to_string();
/* Follow up to 10 xml-redirects (http-redirects are followed in read_autoconf_file() */
/* Follow up to 10 xml-redirects (http-redirects are followed in read_url() */
for _i in 0..10 {
if let Some(xml_raw) = read_autoconf_file(context, &url) {
match outlk_parse_xml(&xml_raw) {
Err(err) => {
warn!(context, "{}", err);
return None;
}
Ok(res) => match res {
ParsingResult::RedirectUrl(redirect_url) => url = redirect_url,
ParsingResult::LoginParam(login_param) => return Some(login_param),
},
}
} else {
return None;
let xml_raw = read_url(context, &url)?;
let res = parse_xml(&xml_raw);
if let Err(err) = &res {
warn!(context, "{}", err);
}
match res? {
ParsingResult::RedirectUrl(redirect_url) => url = redirect_url,
ParsingResult::LoginParam(login_param) => return Ok(login_param),
}
}
None
Err(Error::RedirectionError)
}
fn outlk_autodiscover_endtag_cb(event: &BytesEnd, outlk_ad: &mut OutlookAutodiscover) {
@@ -179,7 +199,7 @@ mod tests {
#[test]
fn test_parse_redirect() {
let res = outlk_parse_xml("
let res = parse_xml("
<?xml version=\"1.0\" encoding=\"utf-8\"?>
<Autodiscover xmlns=\"http://schemas.microsoft.com/exchange/autodiscover/responseschema/2006\">
<Response xmlns=\"http://schemas.microsoft.com/exchange/autodiscover/outlook/responseschema/2006a\">
@@ -206,7 +226,7 @@ mod tests {
#[test]
fn test_parse_loginparam() {
let res = outlk_parse_xml(
let res = parse_xml(
"\
<?xml version=\"1.0\" encoding=\"utf-8\"?>
<Autodiscover xmlns=\"http://schemas.microsoft.com/exchange/autodiscover/responseschema/2006\">

View File

@@ -1,3 +1,9 @@
//! Email accounts autoconfiguration process module
mod auto_mozilla;
mod auto_outlook;
mod read_url;
use percent_encoding::{utf8_percent_encode, NON_ALPHANUMERIC};
use crate::config::Config;
@@ -5,16 +11,13 @@ use crate::constants::*;
use crate::context::Context;
use crate::dc_tools::*;
use crate::e2ee;
use crate::imap::*;
use crate::job::*;
use crate::login_param::LoginParam;
use crate::login_param::{CertificateChecks, LoginParam};
use crate::oauth2::*;
use crate::param::Params;
mod auto_outlook;
use auto_outlook::outlk_autodiscover;
mod auto_mozilla;
use auto_mozilla::moz_autoconfigure;
use auto_outlook::outlk_autodiscover;
macro_rules! progress {
($context:tt, $progress:expr) => {
@@ -44,9 +47,8 @@ pub fn dc_is_configured(context: &Context) -> bool {
/*******************************************************************************
* Configure JOB
******************************************************************************/
// the other dc_job_do_DC_JOB_*() functions are declared static in the c-file
#[allow(non_snake_case, unused_must_use)]
pub fn dc_job_do_DC_JOB_CONFIGURE_IMAP(context: &Context) {
pub fn JobConfigureImap(context: &Context) {
if !context.sql.is_open() {
error!(context, "Cannot configure, database not opened.",);
progress!(context, 0);
@@ -62,7 +64,12 @@ pub fn dc_job_do_DC_JOB_CONFIGURE_IMAP(context: &Context) {
let mut param_autoconfig: Option<LoginParam> = None;
context.inbox.read().unwrap().disconnect(context);
context
.inbox_thread
.read()
.unwrap()
.imap
.disconnect(context);
context
.sentbox_thread
.read()
@@ -90,7 +97,7 @@ pub fn dc_job_do_DC_JOB_CONFIGURE_IMAP(context: &Context) {
const STEP_3_INDEX: u8 = 13;
let mut step_counter: u8 = 0;
while !context.shall_stop_ongoing() {
step_counter = step_counter + 1;
step_counter += 1;
let success = match step_counter {
// Read login parameters from the database
@@ -138,7 +145,32 @@ pub fn dc_job_do_DC_JOB_CONFIGURE_IMAP(context: &Context) {
// Step 2: Autoconfig
4 => {
progress!(context, 200);
if param.mail_server.is_empty()
// special case nauta.cu (this should eventually come
// from our provider-db): enforce non-tls cleartext
// settings and and skip all autoconfig which this
// provider does not offer anyway.
if param.addr.ends_with("@nauta.cu") {
let mut p = LoginParam::new();
p.addr = param.addr.clone();
p.mail_user = param.addr.clone();
p.mail_pw = param.mail_pw.clone();
p.mail_port = 143;
p.imap_certificate_checks = CertificateChecks::AcceptInvalidCertificates;
p.send_user = param.addr.clone();
p.send_pw = param.mail_pw.clone();
p.send_port = 25;
p.smtp_certificate_checks = CertificateChecks::AcceptInvalidCertificates;
p.server_flags = DC_LP_AUTH_NORMAL as i32
| DC_LP_IMAP_SOCKET_PLAIN as i32
| DC_LP_SMTP_SOCKET_PLAIN as i32;
// pretend we did autoconfig, to prevent further tries
param_autoconfig = Some(p);
step_counter = 12 - 1;
} else if param.mail_server.is_empty()
&& param.mail_port == 0
/*&&param.mail_user.is_empty() -- the user can enter a loginname which is used by autoconfig then */
&& param.send_server.is_empty()
@@ -161,7 +193,7 @@ pub fn dc_job_do_DC_JOB_CONFIGURE_IMAP(context: &Context) {
"https://autoconfig.{}/mail/config-v1.1.xml?emailaddress={}",
param_domain, param_addr_urlencoded
);
param_autoconfig = moz_autoconfigure(context, &url, &param);
param_autoconfig = moz_autoconfigure(context, &url, &param).ok();
}
true
}
@@ -173,7 +205,7 @@ pub fn dc_job_do_DC_JOB_CONFIGURE_IMAP(context: &Context) {
"https://{}/.well-known/autoconfig/mail/config-v1.1.xml?emailaddress={}",
param_domain, param_addr_urlencoded
);
param_autoconfig = moz_autoconfigure(context, &url, &param);
param_autoconfig = moz_autoconfigure(context, &url, &param).ok();
}
true
}
@@ -186,7 +218,7 @@ pub fn dc_job_do_DC_JOB_CONFIGURE_IMAP(context: &Context) {
"https://{}{}/autodiscover/autodiscover.xml",
"", param_domain
);
param_autoconfig = outlk_autodiscover(context, &url, &param);
param_autoconfig = outlk_autodiscover(context, &url, &param).ok();
}
true
}
@@ -197,7 +229,7 @@ pub fn dc_job_do_DC_JOB_CONFIGURE_IMAP(context: &Context) {
"https://{}{}/autodiscover/autodiscover.xml",
"autodiscover.", param_domain
);
param_autoconfig = outlk_autodiscover(context, &url, &param);
param_autoconfig = outlk_autodiscover(context, &url, &param).ok();
}
true
}
@@ -209,7 +241,7 @@ pub fn dc_job_do_DC_JOB_CONFIGURE_IMAP(context: &Context) {
"http://autoconfig.{}/mail/config-v1.1.xml?emailaddress={}",
param_domain, param_addr_urlencoded
);
param_autoconfig = moz_autoconfigure(context, &url, &param);
param_autoconfig = moz_autoconfigure(context, &url, &param).ok();
}
true
}
@@ -221,7 +253,7 @@ pub fn dc_job_do_DC_JOB_CONFIGURE_IMAP(context: &Context) {
"http://{}/.well-known/autoconfig/mail/config-v1.1.xml",
param_domain
);
param_autoconfig = moz_autoconfigure(context, &url, &param);
param_autoconfig = moz_autoconfigure(context, &url, &param).ok();
}
true
}
@@ -231,7 +263,7 @@ pub fn dc_job_do_DC_JOB_CONFIGURE_IMAP(context: &Context) {
if param_autoconfig.is_none() {
/* always SSL for Thunderbird's database */
let url = format!("https://autoconfig.thunderbird.net/v1.1/{}", param_domain);
param_autoconfig = moz_autoconfigure(context, &url, &param);
param_autoconfig = moz_autoconfigure(context, &url, &param).ok();
}
true
}
@@ -249,8 +281,8 @@ pub fn dc_job_do_DC_JOB_CONFIGURE_IMAP(context: &Context) {
param.send_port = cfg.send_port;
param.send_user = cfg.send_user.clone();
param.server_flags = cfg.server_flags;
/* although param_autoconfig's data are no longer needed from, it is important to keep the object as
we may enter "deep guessing" if we could not read a configuration */
/* although param_autoconfig's data are no longer needed from,
it is used to later to prevent trying variations of port/server/logins */
}
param.server_flags |= keep_flags;
true
@@ -348,19 +380,21 @@ pub fn dc_job_do_DC_JOB_CONFIGURE_IMAP(context: &Context) {
}
16 => {
progress!(context, 900);
let flags: libc::c_int = if context.get_config_bool(Config::MvboxWatch)
|| context.get_config_bool(Config::MvboxMove)
{
DC_CREATE_MVBOX as i32
let create_mvbox = context.get_config_bool(Config::MvboxWatch)
|| context.get_config_bool(Config::MvboxMove);
let imap = &context.inbox_thread.read().unwrap().imap;
if let Err(err) = imap.ensure_configured_folders(context, create_mvbox) {
warn!(context, "configuring folders failed: {:?}", err);
false
} else {
0
};
context
.inbox
.read()
.unwrap()
.configure_folders(context, flags);
true
let res = imap.select_with_uidvalidity(context, "INBOX");
if let Err(err) = res {
error!(context, "could not read INBOX status: {:?}", err);
false
} else {
true
}
}
}
17 => {
progress!(context, 910);
@@ -382,11 +416,10 @@ pub fn dc_job_do_DC_JOB_CONFIGURE_IMAP(context: &Context) {
// (~30 seconds on a Moto G4 play) and might looks as if message sending is always that slow.
e2ee::ensure_secret_key_exists(context);
success = true;
info!(context, "Configure completed.");
info!(context, "key generation completed");
progress!(context, 940);
break; // We are done here
}
_ => {
error!(context, "Internal error: step counter out of bound",);
break;
@@ -398,30 +431,17 @@ pub fn dc_job_do_DC_JOB_CONFIGURE_IMAP(context: &Context) {
}
}
if imap_connected_here {
context.inbox.read().unwrap().disconnect(context);
context
.inbox_thread
.read()
.unwrap()
.imap
.disconnect(context);
}
if smtp_connected_here {
context.smtp.clone().lock().unwrap().disconnect();
}
/*
if !success {
// disconnect if configure did not succeed
if imap_connected_here {
// context.inbox.read().unwrap().disconnect(context);
}
if smtp_connected_here {
// context.smtp.clone().lock().unwrap().disconnect();
}
} else {
assert!(imap_connected_here && smtp_connected_here);
info!(
context,
0, "Keeping IMAP/SMTP connections open after successful configuration"
);
}
*/
// remember the entered parameters on success
// and restore to last-entered on failure.
// this way, the parameters visible to the ui are always in-sync with the current configuration.
@@ -490,11 +510,21 @@ fn try_imap_connection(
fn try_imap_one_param(context: &Context, param: &LoginParam) -> Option<bool> {
let inf = format!(
"imap: {}@{}:{} flags=0x{:x}",
param.mail_user, param.mail_server, param.mail_port, param.server_flags
"imap: {}@{}:{} flags=0x{:x} certificate_checks={}",
param.mail_user,
param.mail_server,
param.mail_port,
param.server_flags,
param.imap_certificate_checks
);
info!(context, "Trying: {}", inf);
if context.inbox.read().unwrap().connect(context, &param) {
if context
.inbox_thread
.read()
.unwrap()
.imap
.connect(context, &param)
{
info!(context, "success: {}", inf);
return Some(true);
}
@@ -563,54 +593,11 @@ fn try_smtp_one_param(context: &Context, param: &LoginParam) -> Option<bool> {
}
}
/*******************************************************************************
* Connect to configured account
******************************************************************************/
pub fn dc_connect_to_configured_imap(context: &Context, imap: &Imap) -> libc::c_int {
let mut ret_connected = 0;
if imap.is_connected() {
ret_connected = 1
} else if !context.sql.get_raw_config_bool(context, "configured") {
warn!(context, "Not configured, cannot connect.",);
} else {
let param = LoginParam::from_database(context, "configured_");
// the trailing underscore is correct
if imap.connect(context, &param) {
ret_connected = 2;
}
}
ret_connected
}
/*******************************************************************************
* Configure a Context
******************************************************************************/
pub fn read_autoconf_file(context: &Context, url: &str) -> Option<String> {
info!(context, "Testing {} ...", url);
match reqwest::Client::new()
.get(url)
.send()
.and_then(|mut res| res.text())
{
Ok(res) => Some(res),
Err(_err) => {
info!(context, "Can\'t read file.",);
None
}
}
}
#[cfg(test)]
mod tests {
use crate::config::*;
use crate::configure::dc_job_do_DC_JOB_CONFIGURE_IMAP;
use crate::configure::JobConfigureImap;
use crate::test_utils::*;
#[test]
@@ -620,6 +607,6 @@ mod tests {
.set_config(Config::Addr, Some("probably@unexistant.addr"))
.unwrap();
t.ctx.set_config(Config::MailPw, Some("123456")).unwrap();
dc_job_do_DC_JOB_CONFIGURE_IMAP(&t.ctx);
JobConfigureImap(&t.ctx);
}
}

26
src/configure/read_url.rs Normal file
View File

@@ -0,0 +1,26 @@
use crate::context::Context;
#[derive(Debug, Fail)]
pub enum Error {
#[fail(display = "URL request error")]
GetError(#[cause] reqwest::Error),
}
pub type Result<T> = std::result::Result<T, Error>;
pub fn read_url(context: &Context, url: &str) -> Result<String> {
info!(context, "Requesting URL {}", url);
match reqwest::Client::new()
.get(url)
.send()
.and_then(|mut res| res.text())
{
Ok(res) => Ok(res),
Err(err) => {
info!(context, "Can\'t read URL {}", url);
Err(Error::GetError(err))
}
}
}

View File

@@ -1,4 +1,4 @@
//! Constants
//! # Constants
#![allow(non_camel_case_types, dead_code)]
use deltachat_derive::*;
@@ -8,21 +8,6 @@ lazy_static! {
pub static ref DC_VERSION_STR: String = env!("CARGO_PKG_VERSION").to_string();
}
#[repr(u8)]
#[derive(Debug, Display, Clone, Copy, PartialEq, Eq, FromPrimitive, ToPrimitive, ToSql, FromSql)]
pub enum MoveState {
Undefined = 0,
Pending = 1,
Stay = 2,
Moving = 3,
}
impl Default for MoveState {
fn default() -> Self {
MoveState::Undefined
}
}
// some defaults
const DC_E2EE_DEFAULT_ENABLED: i32 = 1;
const DC_INBOX_WATCH_DEFAULT: i32 = 1;
@@ -73,6 +58,9 @@ pub const DC_GCM_ADDDAYMARKER: u32 = 0x01;
pub const DC_GCL_VERIFIED_ONLY: usize = 0x01;
pub const DC_GCL_ADD_SELF: usize = 0x02;
// unchanged user avatars are resent to the recipients every some days
pub const DC_RESEND_USER_AVATAR_DAYS: i64 = 14;
// 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;
@@ -134,7 +122,9 @@ pub const DC_CONTACT_ID_INFO: u32 = 2;
pub const DC_CONTACT_ID_DEVICE: u32 = 5;
pub const DC_CONTACT_ID_LAST_SPECIAL: u32 = 9;
pub const DC_CREATE_MVBOX: usize = 1;
// decorative address that is used for DC_CONTACT_ID_DEVICE
// when an api that returns an email is called.
pub const DC_CONTACT_ID_DEVICE_ADDR: &str = "device@localhost";
// Flags for empty server job

View File

@@ -10,11 +10,13 @@ use crate::constants::*;
use crate::context::Context;
use crate::dc_tools::*;
use crate::e2ee;
use crate::error::Result;
use crate::error::{Error, Result};
use crate::events::Event;
use crate::key::*;
use crate::login_param::LoginParam;
use crate::message::{MessageState, MsgId};
use crate::mimeparser::AvatarAction;
use crate::param::*;
use crate::peerstate::*;
use crate::sql;
use crate::stock::StockMessage;
@@ -23,15 +25,17 @@ use crate::stock::StockMessage;
const DC_ORIGIN_MIN_CONTACT_LIST: i32 = 0x100;
/// An object representing a single contact in memory.
///
/// The contact object is not updated.
/// If you want an update, you have to recreate the object.
///
/// The library makes sure
/// only to use names _authorized_ by the contact in `To:` or `Cc:`.
/// _Given-names _as "Daddy" or "Honey" are not used there.
/// *Given-names* as "Daddy" or "Honey" are not used there.
/// For this purpose, internally, two names are tracked -
/// authorized-name and given-name.
/// authorized name and given name.
/// By default, these names are equal, but functions working with contact names
/// only affect the given name.
#[derive(Debug)]
pub struct Contact {
/// The contact ID.
@@ -47,8 +51,8 @@ pub struct Contact {
/// May be empty, initially set to `authname`.
name: String,
/// Name authorized by the contact himself. Only this name may be spread to others,
/// e.g. in To:-lists. May be empty. It is recommended to use `Contact::get_name`,
/// `Contact::get_display_name` or `Contact::get_name_n_addr` to access this field.
/// e.g. in To:-lists. May be empty. It is recommended to use `Contact::get_authname`,
/// to access this field.
authname: String,
/// E-Mail-Address of the contact. It is recommended to use `Contact::get_addr`` to access this field.
addr: String,
@@ -56,6 +60,8 @@ pub struct Contact {
blocked: bool,
/// The origin/source of the contact.
pub origin: Origin,
/// Parameters as Param::ProfileImage
pub param: Params,
}
/// Possible origins of a contact.
@@ -106,11 +112,6 @@ impl Default for Origin {
}
impl Origin {
/// Contacts that start a new "normal" chat, defaults to off.
pub fn is_start_new_chat(self) -> bool {
self as i32 >= 0x7FFFFFFF
}
/// Contacts that are verified and known not to be spam.
pub fn is_verified(self) -> bool {
self as i32 >= 0x100
@@ -141,33 +142,11 @@ pub enum VerifiedStatus {
}
impl Contact {
pub fn load_from_db(context: &Context, contact_id: u32) -> Result<Self> {
if contact_id == DC_CONTACT_ID_SELF {
let contact = Contact {
id: contact_id,
name: context.stock_str(StockMessage::SelfMsg).into(),
authname: "".into(),
addr: context
.get_config(Config::ConfiguredAddr)
.unwrap_or_default(),
blocked: false,
origin: Origin::Unknown,
};
return Ok(contact);
} else if contact_id == DC_CONTACT_ID_DEVICE {
let contact = Contact {
id: contact_id,
name: context.stock_str(StockMessage::DeviceMessages).into(),
authname: "".into(),
addr: "device@localhost".into(),
blocked: false,
origin: Origin::Unknown,
};
return Ok(contact);
}
context.sql.query_row(
"SELECT c.name, c.addr, c.origin, c.blocked, c.authname FROM contacts c WHERE c.id=?;",
pub fn load_from_db(context: &Context, contact_id: u32) -> crate::sql::Result<Self> {
let mut res = context.sql.query_row(
"SELECT c.name, c.addr, c.origin, c.blocked, c.authname, c.param
FROM contacts c
WHERE c.id=?;",
params![contact_id as i32],
|row| {
let contact = Self {
@@ -177,10 +156,21 @@ impl Contact {
addr: row.get::<_, String>(1)?,
blocked: row.get::<_, Option<i32>>(3)?.unwrap_or_default() != 0,
origin: row.get(2)?,
param: row.get::<_, String>(5)?.parse().unwrap_or_default(),
};
Ok(contact)
}
)
},
)?;
if contact_id == DC_CONTACT_ID_SELF {
res.name = context.stock_str(StockMessage::SelfMsg).to_string();
res.addr = context
.get_config(Config::ConfiguredAddr)
.unwrap_or_default();
} else if contact_id == DC_CONTACT_ID_DEVICE {
res.name = context.stock_str(StockMessage::DeviceMessages).to_string();
res.addr = DC_CONTACT_ID_DEVICE_ADDR.to_string();
}
Ok(res)
}
/// Returns `true` if this contact is blocked.
@@ -208,7 +198,7 @@ impl Contact {
/// Add a single contact as a result of an _explicit_ user action.
///
/// We assume, the contact name, if any, is entered by the user and is used "as is" therefore,
/// normalize() is _not_ called for the name. If the contact is blocked, it is unblocked.
/// normalize() is *not* called for the name. If the contact is blocked, it is unblocked.
///
/// To add a number of contacts, see `dc_add_address_book()` which is much faster for adding
/// a bunch of addresses.
@@ -238,7 +228,7 @@ impl Contact {
}
/// Mark all messages sent by the given contact
/// as _noticed_. See also dc_marknoticed_chat() and dc_markseen_msgs()
/// as *noticed*. See also dc_marknoticed_chat() and dc_markseen_msgs()
///
/// Calling this function usually results in the event `#DC_EVENT_MSGS_CHANGED`.
pub fn mark_noticed(context: &Context, id: u32) {
@@ -272,7 +262,7 @@ impl Contact {
.get_config(Config::ConfiguredAddr)
.unwrap_or_default();
if addr_normalized == addr_self {
if addr_cmp(addr_normalized, addr_self) {
return DC_CONTACT_ID_SELF;
}
@@ -309,7 +299,7 @@ impl Contact {
.get_config(Config::ConfiguredAddr)
.unwrap_or_default();
if addr == addr_self {
if addr_cmp(addr, addr_self) {
return Ok((DC_CONTACT_ID_SELF, sth_modified));
}
@@ -428,7 +418,7 @@ impl Contact {
/// the event `DC_EVENT_CONTACTS_CHANGED` is sent.
///
/// 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.
/// however, for adding a bunch of addresses, this function is much faster.
///
/// The `addr_book` is a multiline string in the format `Name one\nAddress one\nName two\nAddress two`.
///
@@ -585,7 +575,12 @@ impl Contact {
let mut self_key = Key::from_self_public(context, &loginparam.addr, &context.sql);
if peerstate.is_some() && peerstate.as_ref().and_then(|p| p.peek_key(0)).is_some() {
if peerstate.is_some()
&& peerstate
.as_ref()
.and_then(|p| p.peek_key(PeerstateVerifiedStatus::Unverified))
.is_some()
{
let peerstate = peerstate.as_ref().unwrap();
let p =
context.stock_str(if peerstate.prefer_encrypt == EncryptPreference::Mutual {
@@ -605,25 +600,25 @@ impl Contact {
.map(|k| k.formatted_fingerprint())
.unwrap_or_default();
let fingerprint_other_verified = peerstate
.peek_key(2)
.peek_key(PeerstateVerifiedStatus::BidirectVerified)
.map(|k| k.formatted_fingerprint())
.unwrap_or_default();
let fingerprint_other_unverified = peerstate
.peek_key(0)
.peek_key(PeerstateVerifiedStatus::Unverified)
.map(|k| k.formatted_fingerprint())
.unwrap_or_default();
if peerstate.addr.is_some() && &loginparam.addr < peerstate.addr.as_ref().unwrap() {
if loginparam.addr < peerstate.addr {
cat_fingerprint(&mut ret, &loginparam.addr, &fingerprint_self, "");
cat_fingerprint(
&mut ret,
peerstate.addr.as_ref().unwrap(),
peerstate.addr.clone(),
&fingerprint_other_verified,
&fingerprint_other_unverified,
);
} else {
cat_fingerprint(
&mut ret,
peerstate.addr.as_ref().unwrap(),
peerstate.addr.clone(),
&fingerprint_other_verified,
&fingerprint_other_unverified,
);
@@ -686,7 +681,7 @@ impl Contact {
}
Err(err) => {
error!(context, "delete_contact {} failed ({})", contact_id, err);
return Err(err);
return Err(err.into());
}
}
}
@@ -704,7 +699,17 @@ impl Contact {
/// like "Me" in the selected language and the email address
/// defined by dc_set_config().
pub fn get_by_id(context: &Context, contact_id: u32) -> Result<Contact> {
Contact::load_from_db(context, contact_id)
Ok(Contact::load_from_db(context, contact_id)?)
}
pub fn update_param(&mut self, context: &Context) -> Result<()> {
sql::execute(
context,
&context.sql,
"UPDATE contacts SET param=? WHERE id=?",
params![self.param.to_string(), self.id as i32],
)?;
Ok(())
}
/// Get the ID of the contact.
@@ -717,6 +722,7 @@ impl Contact {
&self.addr
}
/// Get name authorized by the contact.
pub fn get_authname(&self) -> &str {
&self.authname
}
@@ -774,8 +780,11 @@ impl Contact {
if let Some(p) = context.get_config(Config::Selfavatar) {
return Some(PathBuf::from(p));
}
} else if let Some(image_rel) = self.param.get(Param::ProfileImage) {
if !image_rel.is_empty() {
return Some(dc_get_abs_path(context, image_rel));
}
}
// TODO: else get image_abs from contact param
None
}
@@ -858,14 +867,14 @@ impl Contact {
.unwrap_or_default() as usize
}
pub fn get_origin_by_id(context: &Context, contact_id: u32, ret_blocked: &mut i32) -> Origin {
pub fn get_origin_by_id(context: &Context, contact_id: u32, ret_blocked: &mut bool) -> Origin {
let mut ret = Origin::Unknown;
*ret_blocked = 0;
*ret_blocked = false;
if let Ok(contact) = Contact::load_from_db(context, contact_id) {
/* we could optimize this by loading only the needed fields */
if contact.blocked {
*ret_blocked = 1;
*ret_blocked = true;
} else {
ret = contact.origin;
}
@@ -875,7 +884,7 @@ impl Contact {
}
pub fn real_exists_by_id(context: &Context, contact_id: u32) -> bool {
if !context.sql.is_open() || contact_id <= 9 {
if !context.sql.is_open() || contact_id <= DC_CONTACT_ID_LAST_SPECIAL {
return false;
}
@@ -899,7 +908,8 @@ impl Contact {
}
}
fn get_first_name<'a>(full_name: &'a str) -> &'a str {
/// Extracts first name from full name.
fn get_first_name(full_name: &str) -> &str {
full_name.splitn(2, ' ').next().unwrap_or_default()
}
@@ -909,6 +919,7 @@ pub fn may_be_valid_addr(addr: &str) -> bool {
res.is_ok()
}
/// Returns address with whitespace trimmed and `mailto:` prefix removed.
pub fn addr_normalize(addr: &str) -> &str {
let norm = addr.trim();
@@ -920,26 +931,26 @@ pub fn addr_normalize(addr: &str) -> &str {
}
fn set_block_contact(context: &Context, contact_id: u32, new_blocking: bool) {
if contact_id <= 9 {
if contact_id <= DC_CONTACT_ID_LAST_SPECIAL {
return;
}
if let Ok(contact) = Contact::load_from_db(context, contact_id) {
if contact.blocked != new_blocking {
if sql::execute(
if contact.blocked != new_blocking
&& sql::execute(
context,
&context.sql,
"UPDATE contacts SET blocked=? WHERE id=?;",
params![new_blocking as i32, contact_id as i32],
)
.is_ok()
{
// also (un)block all chats with _only_ this contact - we do not delete them to allow a
// non-destructive blocking->unblocking.
// (Maybe, beside normal chats (type=100) we should also block group chats with only this user.
// However, I'm not sure about this point; it may be confusing if the user wants to add other people;
// this would result in recreating the same group...)
if sql::execute(
{
// also (un)block all chats with _only_ this contact - we do not delete them to allow a
// non-destructive blocking->unblocking.
// (Maybe, beside normal chats (type=100) we should also block group chats with only this user.
// However, I'm not sure about this point; it may be confusing if the user wants to add other people;
// this would result in recreating the same group...)
if sql::execute(
context,
&context.sql,
"UPDATE chats SET blocked=? WHERE type=? AND id IN (SELECT chat_id FROM chats_contacts WHERE contact_id=?);",
@@ -948,11 +959,36 @@ fn set_block_contact(context: &Context, contact_id: u32, new_blocking: bool) {
Contact::mark_noticed(context, contact_id);
context.call_cb(Event::ContactsChanged(None));
}
}
}
}
}
pub fn set_profile_image(
context: &Context,
contact_id: u32,
profile_image: AvatarAction,
) -> Result<()> {
// the given profile image is expected to be already in the blob directory
// as profile images can be set only by receiving messages, this should be always the case, however.
let mut contact = Contact::load_from_db(context, contact_id)?;
let changed = match profile_image {
AvatarAction::Change(profile_image) => {
contact.param.set(Param::ProfileImage, profile_image);
true
}
AvatarAction::Delete => {
contact.param.remove(Param::ProfileImage);
true
}
AvatarAction::None => false,
};
if changed {
contact.update_param(context)?;
context.call_cb(Event::ContactsChanged(Some(contact_id)));
}
Ok(())
}
/// Normalize a name.
///
/// - Remove quotes (come from some bad MUA implementations)
@@ -1017,21 +1053,23 @@ fn cat_fingerprint(
}
}
pub fn addr_cmp(addr1: impl AsRef<str>, addr2: impl AsRef<str>) -> bool {
let norm1 = addr_normalize(addr1.as_ref());
let norm2 = addr_normalize(addr2.as_ref());
impl Context {
/// determine whether the specified addr maps to the/a self addr
pub fn is_self_addr(&self, addr: &str) -> Result<bool> {
let self_addr = match self.get_config(Config::ConfiguredAddr) {
Some(s) => s,
None => return Err(Error::NotConfigured),
};
norm1 == norm2
Ok(addr_cmp(self_addr, addr))
}
}
pub fn addr_equals_self(context: &Context, addr: impl AsRef<str>) -> bool {
if !addr.as_ref().is_empty() {
let normalized_addr = addr_normalize(addr.as_ref());
if let Some(self_addr) = context.get_config(Config::ConfiguredAddr) {
return normalized_addr == self_addr;
}
}
false
pub fn addr_cmp(addr1: impl AsRef<str>, addr2: impl AsRef<str>) -> bool {
let norm1 = addr_normalize(addr1.as_ref()).to_lowercase();
let norm2 = addr_normalize(addr2.as_ref()).to_lowercase();
norm1 == norm2
}
fn split_address_book(book: &str) -> Vec<(&str, &str)> {
@@ -1053,6 +1091,8 @@ fn split_address_book(book: &str) -> Vec<(&str, &str)> {
mod tests {
use super::*;
use crate::test_utils::*;
#[test]
fn test_may_be_valid_addr() {
assert_eq!(may_be_valid_addr(""), false);
@@ -1078,6 +1118,10 @@ mod tests {
fn test_normalize_addr() {
assert_eq!(addr_normalize("mailto:john@doe.com"), "john@doe.com");
assert_eq!(addr_normalize(" hello@world.com "), "hello@world.com");
// normalisation preserves case to allow user-defined spelling.
// however, case is ignored on addr_cmp()
assert_ne!(addr_normalize("John@Doe.com"), "john@doe.com");
}
#[test]
@@ -1094,4 +1138,128 @@ mod tests {
vec![("Name one", "Address one"), ("Name two", "Address two")]
)
}
#[test]
fn test_get_contacts() {
let context = dummy_context();
let contacts = Contact::get_all(&context.ctx, 0, Some("some2")).unwrap();
assert_eq!(contacts.len(), 0);
let id = Contact::create(&context.ctx, "bob", "bob@mail.de").unwrap();
assert_ne!(id, 0);
let contacts = Contact::get_all(&context.ctx, 0, Some("bob")).unwrap();
assert_eq!(contacts.len(), 1);
let contacts = Contact::get_all(&context.ctx, 0, Some("alice")).unwrap();
assert_eq!(contacts.len(), 0);
}
#[test]
fn test_is_self_addr() -> Result<()> {
let t = test_context(None);
assert!(t.ctx.is_self_addr("me@me.org").is_err());
let addr = configure_alice_keypair(&t.ctx);
assert_eq!(t.ctx.is_self_addr("me@me.org")?, false);
assert_eq!(t.ctx.is_self_addr(&addr)?, true);
Ok(())
}
#[test]
fn test_add_or_lookup() {
// add some contacts, this also tests add_address_book()
let t = dummy_context();
let book = concat!(
" Name one \n one@eins.org \n",
"Name two\ntwo@deux.net\n",
"\nthree@drei.sam\n",
"Name two\ntwo@deux.net\n" // should not be added again
);
assert_eq!(Contact::add_address_book(&t.ctx, book).unwrap(), 3);
// check first added contact, this does not modify because of lower origin
let (contact_id, sth_modified) =
Contact::add_or_lookup(&t.ctx, "bla foo", "one@eins.org", Origin::IncomingUnknownTo)
.unwrap();
assert!(contact_id > DC_CONTACT_ID_LAST_SPECIAL);
assert_eq!(sth_modified, Modifier::None);
let contact = Contact::load_from_db(&t.ctx, contact_id).unwrap();
assert_eq!(contact.get_id(), contact_id);
assert_eq!(contact.get_name(), "Name one");
assert_eq!(contact.get_display_name(), "Name one");
assert_eq!(contact.get_addr(), "one@eins.org");
assert_eq!(contact.get_name_n_addr(), "Name one (one@eins.org)");
// modify first added contact
let (contact_id_test, sth_modified) = Contact::add_or_lookup(
&t.ctx,
"Real one",
" one@eins.org ",
Origin::ManuallyCreated,
)
.unwrap();
assert_eq!(contact_id, contact_id_test);
assert_eq!(sth_modified, Modifier::Modified);
let contact = Contact::load_from_db(&t.ctx, contact_id).unwrap();
assert_eq!(contact.get_name(), "Real one");
assert_eq!(contact.get_addr(), "one@eins.org");
assert!(!contact.is_blocked());
// check third added contact (contact without name)
let (contact_id, sth_modified) =
Contact::add_or_lookup(&t.ctx, "", "three@drei.sam", Origin::IncomingUnknownTo)
.unwrap();
assert!(contact_id > DC_CONTACT_ID_LAST_SPECIAL);
assert_eq!(sth_modified, Modifier::None);
let contact = Contact::load_from_db(&t.ctx, contact_id).unwrap();
assert_eq!(contact.get_name(), "");
assert_eq!(contact.get_display_name(), "three@drei.sam");
assert_eq!(contact.get_addr(), "three@drei.sam");
assert_eq!(contact.get_name_n_addr(), "three@drei.sam");
// add name to third contact from incoming message (this becomes authorized name)
let (contact_id_test, sth_modified) = Contact::add_or_lookup(
&t.ctx,
"m. serious",
"three@drei.sam",
Origin::IncomingUnknownFrom,
)
.unwrap();
assert_eq!(contact_id, contact_id_test);
assert_eq!(sth_modified, Modifier::Modified);
let contact = Contact::load_from_db(&t.ctx, contact_id).unwrap();
assert_eq!(contact.get_name_n_addr(), "m. serious (three@drei.sam)");
assert!(!contact.is_blocked());
// manually edit name of third contact (does not changed authorized name)
let (contact_id_test, sth_modified) = Contact::add_or_lookup(
&t.ctx,
"schnucki",
"three@drei.sam",
Origin::ManuallyCreated,
)
.unwrap();
assert_eq!(contact_id, contact_id_test);
assert_eq!(sth_modified, Modifier::Modified);
let contact = Contact::load_from_db(&t.ctx, contact_id).unwrap();
assert_eq!(contact.get_authname(), "m. serious");
assert_eq!(contact.get_name_n_addr(), "schnucki (three@drei.sam)");
assert!(!contact.is_blocked());
// check SELF
let contact = Contact::load_from_db(&t.ctx, DC_CONTACT_ID_SELF).unwrap();
assert_eq!(DC_CONTACT_ID_SELF, 1);
assert_eq!(contact.get_name(), t.ctx.stock_str(StockMessage::SelfMsg));
assert_eq!(contact.get_addr(), ""); // we're not configured
assert!(!contact.is_blocked());
}
#[test]
fn test_addr_cmp() {
assert!(addr_cmp("AA@AA.ORG", "aa@aa.ORG"));
assert!(addr_cmp(" aa@aa.ORG ", "AA@AA.ORG"));
assert!(addr_cmp(" mailto:AA@AA.ORG", "Aa@Aa.orG"));
}
}

View File

@@ -1,3 +1,5 @@
//! Contacts module
use std::collections::HashMap;
use std::ffi::OsString;
use std::path::{Path, PathBuf};
@@ -19,7 +21,7 @@ use crate::login_param::LoginParam;
use crate::lot::Lot;
use crate::message::{self, Message, MsgId};
use crate::param::Params;
use crate::smtp::*;
use crate::smtp::Smtp;
use crate::sql::Sql;
/// Callback function type for [Context]
@@ -39,12 +41,14 @@ pub type ContextCallback = dyn Fn(&Context, Event) -> uintptr_t + Send + Sync;
#[derive(DebugStub)]
pub struct Context {
/// Database file path
dbfile: PathBuf,
/// Blob directory path
blobdir: PathBuf,
pub sql: Sql,
pub inbox: Arc<RwLock<Imap>>,
pub perform_inbox_jobs_needed: Arc<RwLock<bool>>,
pub probe_imap_network: Arc<RwLock<bool>>,
pub inbox_thread: Arc<RwLock<JobThread>>,
pub sentbox_thread: Arc<RwLock<JobThread>>,
pub mvbox_thread: Arc<RwLock<JobThread>>,
pub smtp: Arc<Mutex<Smtp>>,
@@ -55,7 +59,7 @@ pub struct Context {
pub os_name: Option<String>,
pub cmdline_sel_chat_id: Arc<RwLock<u32>>,
pub bob: Arc<RwLock<BobStatus>>,
pub last_smeared_timestamp: Arc<RwLock<i64>>,
pub last_smeared_timestamp: RwLock<i64>,
pub running_state: Arc<RwLock<RunningState>>,
/// Mutex to avoid generating the key for the user more than once.
pub generating_key_mutex: Mutex<()>,
@@ -84,7 +88,7 @@ pub fn get_info() -> HashMap<&'static str, String> {
);
res.insert(
"arch",
(::std::mem::size_of::<*mut libc::c_void>())
(std::mem::size_of::<*mut libc::c_void>())
.wrapping_mul(8)
.to_string(),
);
@@ -93,6 +97,7 @@ pub fn get_info() -> HashMap<&'static str, String> {
}
impl Context {
/// Creates new context.
pub fn new(cb: Box<ContextCallback>, os_name: String, dbfile: PathBuf) -> Result<Context> {
let mut blob_fname = OsString::new();
blob_fname.push(dbfile.file_name().unwrap_or_default());
@@ -118,7 +123,6 @@ impl Context {
let ctx = Context {
blobdir,
dbfile,
inbox: Arc::new(RwLock::new(Imap::new())),
cb,
os_name: Some(os_name),
running_state: Arc::new(RwLock::new(Default::default())),
@@ -127,8 +131,13 @@ impl Context {
smtp_state: Arc::new((Mutex::new(Default::default()), Condvar::new())),
oauth2_critical: Arc::new(Mutex::new(())),
bob: Arc::new(RwLock::new(Default::default())),
last_smeared_timestamp: Arc::new(RwLock::new(0)),
last_smeared_timestamp: RwLock::new(0),
cmdline_sel_chat_id: Arc::new(RwLock::new(0)),
inbox_thread: Arc::new(RwLock::new(JobThread::new(
"INBOX",
"configured_inbox_folder",
Imap::new(),
))),
sentbox_thread: Arc::new(RwLock::new(JobThread::new(
"SENTBOX",
"configured_sentbox_folder",
@@ -153,10 +162,12 @@ impl Context {
Ok(ctx)
}
/// Returns database file path.
pub fn get_dbfile(&self) -> &Path {
self.dbfile.as_path()
}
/// Returns blob directory path.
pub fn get_blobdir(&self) -> &Path {
self.blobdir.as_path()
}
@@ -287,6 +298,11 @@ impl Context {
res.insert("database_version", dbversion.to_string());
res.insert("blobdir", self.get_blobdir().display().to_string());
res.insert("display_name", displayname.unwrap_or_else(|| unset.into()));
res.insert(
"selfavatar",
self.get_config(Config::Selfavatar)
.unwrap_or_else(|| "<unset>".to_string()),
);
res.insert("is_configured", is_configured.to_string());
res.insert("entered_account_settings", l.to_string());
res.insert("used_account_settings", l2.to_string());
@@ -426,10 +442,9 @@ impl Context {
return;
}
if !self.is_inbox(folder) && !self.is_sentbox(folder) {
if self.is_mvbox(folder) {
return;
}
if let Ok(msg) = Message::load_from_db(self, msg_id) {
if msg.is_setupmessage() {
// do not move setup messages;
@@ -437,10 +452,6 @@ impl Context {
return;
}
if self.is_mvbox(folder) {
message::update_msg_move_state(self, &msg.rfc724_mid, MoveState::Stay);
}
// 1 = dc message, 2 = reply to dc message
if 0 != msg.is_dc_message {
job_add(
@@ -450,7 +461,6 @@ impl Context {
Params::new(),
0,
);
message::update_msg_move_state(self, &msg.rfc724_mid, MoveState::Moving);
}
}
}
@@ -458,8 +468,8 @@ impl Context {
impl Drop for Context {
fn drop(&mut self) {
info!(self, "disconnecting INBOX-watch",);
self.inbox.read().unwrap().disconnect(self);
info!(self, "disconnecting inbox-thread",);
self.inbox_thread.read().unwrap().imap.disconnect(self);
info!(self, "disconnecting sentbox-thread",);
self.sentbox_thread.read().unwrap().imap.disconnect(self);
info!(self, "disconnecting mvbox-thread",);
@@ -486,12 +496,25 @@ pub struct BobStatus {
pub qr_scan: Option<Lot>,
}
#[derive(Debug, PartialEq)]
pub enum PerformJobsNeeded {
Not,
AtOnce,
AvoidDos,
}
impl Default for PerformJobsNeeded {
fn default() -> Self {
Self::Not
}
}
#[derive(Default, Debug)]
pub struct SmtpState {
pub idle: bool,
pub suspended: bool,
pub doing_jobs: bool,
pub perform_jobs_needed: i32,
pub perform_jobs_needed: PerformJobsNeeded,
pub probe_network: bool,
}

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

View File

@@ -1,4 +1,4 @@
use crate::dc_dehtml::*;
use crate::dehtml::*;
#[derive(Copy, Clone)]
pub struct Simplify {
@@ -34,7 +34,7 @@ impl Simplify {
/// The data returned from simplify() must be free()'d when no longer used.
pub fn simplify(&mut self, input: &str, is_html: bool, is_msgrmsg: bool) -> String {
let mut out = if is_html {
dc_dehtml(input)
dehtml(input)
} else {
input.to_string()
};
@@ -49,7 +49,7 @@ impl Simplify {
/**
* Simplify Plain Text
*/
#[allow(non_snake_case)]
#[allow(non_snake_case, clippy::mut_range_bound)]
fn simplify_plain_text(&mut self, buf_terminated: &str, is_msgrmsg: bool) -> String {
/* This function ...
... removes all text after the line `-- ` (footer mark)

View File

@@ -1,251 +0,0 @@
use itertools::Itertools;
use std::borrow::Cow;
use std::ffi::CString;
use std::ptr;
use charset::Charset;
use libc::free;
use mmime::mailmime::decode::mailmime_encoded_phrase_parse;
use mmime::other::*;
use percent_encoding::{percent_decode, utf8_percent_encode, AsciiSet, CONTROLS};
use crate::dc_tools::*;
/**
* Encode non-ascii-strings as `=?UTF-8?Q?Bj=c3=b6rn_Petersen?=`.
* Belongs to RFC 2047: https://tools.ietf.org/html/rfc2047
*
* We do not fold at position 72; this would result in empty words as `=?utf-8?Q??=` which are correct,
* but cannot be displayed by some mail programs (eg. Android Stock Mail).
* however, this is not needed, as long as _one_ word is not longer than 72 characters.
* _if_ it is, the display may get weird. This affects the subject only.
* the best solution wor all this would be if libetpan encodes the line as only libetpan knowns when a header line is full.
*
* @param to_encode Null-terminated UTF-8-string to encode.
* @return Returns the encoded string which must be free()'d when no longed needed.
* On errors, NULL is returned.
*/
pub fn dc_encode_header_words(input: impl AsRef<str>) -> String {
let mut result = String::default();
for (_, group) in &input.as_ref().chars().group_by(|c| c.is_whitespace()) {
let word: String = group.collect();
result.push_str(&quote_word(&word.as_bytes()));
}
result
}
fn must_encode(byte: u8) -> bool {
static SPECIALS: &[u8] = b",:!\"#$@[\\]^`{|}~=?_";
SPECIALS.into_iter().any(|b| *b == byte)
}
fn quote_word(word: &[u8]) -> String {
let mut result = String::default();
let mut encoded = false;
for byte in word {
let byte = *byte;
if byte >= 128 || must_encode(byte) {
result.push_str(&format!("={:2X}", byte));
encoded = true;
} else if byte == b' ' {
result.push('_');
encoded = true;
} else {
result.push(byte as _);
}
}
if encoded {
result = format!("=?utf-8?Q?{}?=", &result);
}
result
}
/* ******************************************************************************
* Encode/decode header words, RFC 2047
******************************************************************************/
pub(crate) fn dc_decode_header_words(input: &str) -> String {
static FROM_ENCODING: &[u8] = b"iso-8859-1\x00";
static TO_ENCODING: &[u8] = b"utf-8\x00";
let mut out = ptr::null_mut();
let mut cur_token = 0;
let input_c = CString::yolo(input);
unsafe {
let r = mailmime_encoded_phrase_parse(
FROM_ENCODING.as_ptr().cast(),
input_c.as_ptr(),
input.len(),
&mut cur_token,
TO_ENCODING.as_ptr().cast(),
&mut out,
);
if r as u32 != MAILIMF_NO_ERROR || out.is_null() {
input.to_string()
} else {
let res = to_string_lossy(out);
free(out.cast());
res
}
}
}
pub fn dc_needs_ext_header(to_check: impl AsRef<str>) -> bool {
let to_check = to_check.as_ref();
if to_check.is_empty() {
return false;
}
to_check.chars().any(|c| {
!(c.is_ascii_alphanumeric()
|| c == '-'
|| c == '_'
|| c == '_'
|| c == '.'
|| c == '~'
|| c == '%')
})
}
const EXT_ASCII_ST: &AsciiSet = &CONTROLS
.add(b' ')
.add(b'-')
.add(b'_')
.add(b'.')
.add(b'~')
.add(b'%');
/// Encode an UTF-8 string to the extended header format.
pub fn dc_encode_ext_header(to_encode: impl AsRef<str>) -> String {
let encoded = utf8_percent_encode(to_encode.as_ref(), &EXT_ASCII_ST);
format!("utf-8''{}", encoded)
}
/// Decode an extended-header-format strings to UTF-8.
pub fn dc_decode_ext_header(to_decode: &[u8]) -> Cow<str> {
if let Some(index) = bytes!(b'\'').find(to_decode) {
let (charset, rest) = to_decode.split_at(index);
if !charset.is_empty() {
// skip language
if let Some(index2) = bytes!(b'\'').find(&rest[1..]) {
let decoded = percent_decode(&rest[index2 + 2..]);
if charset != b"utf-8" && charset != b"UTF-8" {
if let Some(encoding) = Charset::for_label(charset) {
let bytes = decoded.collect::<Vec<u8>>();
let (res, _, _) = encoding.decode(&bytes);
return Cow::Owned(res.into_owned());
} else {
return decoded.decode_utf8_lossy();
}
} else {
return decoded.decode_utf8_lossy();
}
}
}
}
String::from_utf8_lossy(to_decode)
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_dc_decode_header_words() {
assert_eq!(
dc_decode_header_words("=?utf-8?B?dGVzdMOkw7bDvC50eHQ=?="),
std::string::String::from_utf8(b"test\xc3\xa4\xc3\xb6\xc3\xbc.txt".to_vec()).unwrap(),
);
assert_eq!(dc_decode_header_words("just ascii test"), "just ascii test");
assert_eq!(dc_encode_header_words("abcdef"), "abcdef");
let r = dc_encode_header_words(
std::string::String::from_utf8(b"test\xc3\xa4\xc3\xb6\xc3\xbc.txt".to_vec()).unwrap(),
);
assert!(r.starts_with("=?utf-8"));
assert_eq!(
dc_decode_header_words(&r),
std::string::String::from_utf8(b"test\xc3\xa4\xc3\xb6\xc3\xbc.txt".to_vec()).unwrap(),
);
assert_eq!(
dc_decode_header_words("=?ISO-8859-1?Q?attachment=3B=0D=0A_filename=3D?= =?ISO-8859-1?Q?=22test=E4=F6=FC=2Etxt=22=3B=0D=0A_size=3D39?="),
std::string::String::from_utf8(b"attachment;\r\n filename=\"test\xc3\xa4\xc3\xb6\xc3\xbc.txt\";\r\n size=39".to_vec()).unwrap(),
);
}
#[test]
fn test_dc_encode_ext_header() {
let buf1 = dc_encode_ext_header("Björn Petersen");
assert_eq!(&buf1, "utf-8\'\'Bj%C3%B6rn%20Petersen");
let buf2 = dc_decode_ext_header(buf1.as_bytes());
assert_eq!(&buf2, "Björn Petersen",);
let buf1 = dc_decode_ext_header(b"iso-8859-1\'en\'%A3%20rates");
assert_eq!(buf1, "£ rates",);
let buf1 = dc_decode_ext_header(b"wrong\'format");
assert_eq!(buf1, "wrong\'format",);
let buf1 = dc_decode_ext_header(b"\'\'");
assert_eq!(buf1, "\'\'");
let buf1 = dc_decode_ext_header(b"x\'\'");
assert_eq!(buf1, "");
let buf1 = dc_decode_ext_header(b"\'");
assert_eq!(buf1, "\'");
let buf1 = dc_decode_ext_header(b"");
assert_eq!(buf1, "");
// regressions
assert_eq!(
dc_decode_ext_header(dc_encode_ext_header("%0A").as_bytes()),
"%0A"
);
}
#[test]
fn test_dc_needs_ext_header() {
assert_eq!(dc_needs_ext_header("Björn"), true);
assert_eq!(dc_needs_ext_header("Bjoern"), false);
assert_eq!(dc_needs_ext_header(""), false);
assert_eq!(dc_needs_ext_header(" "), true);
assert_eq!(dc_needs_ext_header("a b"), true);
}
use proptest::prelude::*;
proptest! {
#[test]
fn test_ext_header_roundtrip(buf: String) {
let encoded = dc_encode_ext_header(&buf);
let decoded = dc_decode_ext_header(encoded.as_bytes());
assert_eq!(buf, decoded);
}
#[test]
fn test_ext_header_decode_anything(buf: Vec<u8>) {
// make sure this never panics
let _decoded = dc_decode_ext_header(&buf);
}
#[test]
fn test_dc_header_roundtrip(input: String) {
let encoded = dc_encode_header_words(&input);
let decoded = dc_decode_header_words(&encoded);
assert_eq!(input, decoded);
}
}
}

View File

@@ -1,56 +1,24 @@
//! Some tools and enhancements to the used libraries, there should be
//! no references to Context and other "larger" entities here.
use core::cmp::max;
use core::cmp::{max, min};
use std::borrow::Cow;
use std::ffi::{CStr, CString};
use std::path::{Path, PathBuf};
use std::str::FromStr;
use std::time::SystemTime;
use std::{fmt, fs, ptr};
use std::{fmt, fs};
use chrono::{Local, TimeZone};
use libc::{memcpy, strlen};
use mmime::clist::*;
use mmime::mailimf::types::*;
use rand::{thread_rng, Rng};
use crate::context::Context;
use crate::error::Error;
use crate::events::Event;
pub(crate) fn dc_exactly_one_bit_set(v: libc::c_int) -> bool {
pub(crate) fn dc_exactly_one_bit_set(v: i32) -> bool {
0 != v && 0 == v & (v - 1)
}
/// Duplicates a string
///
/// returns an empty string if NULL is given, never returns NULL (exits on errors)
///
/// # Examples
///
/// ```
/// use deltachat::dc_tools::{dc_strdup, to_string_lossy};
/// unsafe {
/// let str_a = b"foobar\x00" as *const u8 as *const libc::c_char;
/// let str_a_copy = dc_strdup(str_a);
/// assert_eq!(to_string_lossy(str_a_copy), "foobar");
/// assert_ne!(str_a, str_a_copy);
/// }
/// ```
pub unsafe fn dc_strdup(s: *const libc::c_char) -> *mut libc::c_char {
let ret: *mut libc::c_char;
if !s.is_null() {
ret = strdup(s);
assert!(!ret.is_null());
} else {
ret = libc::calloc(1, 1) as *mut libc::c_char;
assert!(!ret.is_null());
}
ret
}
/// Shortens a string to a specified length and adds "..." or "[...]" to the end of
/// the shortened string.
pub(crate) fn dc_truncate(buf: &str, approx_chars: usize, do_unwrap: bool) -> Cow<str> {
@@ -74,36 +42,12 @@ pub(crate) fn dc_truncate(buf: &str, approx_chars: usize, do_unwrap: bool) -> Co
}
}
pub(crate) fn dc_str_from_clist(list: *const clist, delimiter: &str) -> String {
let mut res = String::new();
if !list.is_null() {
for rfc724_mid in unsafe { (*list).into_iter() } {
if !res.is_empty() {
res += delimiter;
}
res += &to_string_lossy(rfc724_mid as *const libc::c_char);
}
}
res
}
pub(crate) fn dc_str_to_clist(str: &str, delimiter: &str) -> *mut clist {
unsafe {
let list: *mut clist = clist_new();
for cur in str.split(&delimiter) {
clist_insert_after(list, (*list).last, cur.strdup().cast());
}
list
}
}
/* the colors must fulfill some criterions as:
- contrast to black and to white
- work as a text-color
- being noticeable on a typical map
- harmonize together while being different enough
(therefore, we cannot just use random rgb colors :) */
/// the colors must fulfill some criterions as:
/// - contrast to black and to white
/// - work as a text-color
/// - being noticeable on a typical map
/// - harmonize together while being different enough
/// (therefore, we cannot just use random rgb colors :)
const COLORS: [u32; 16] = [
0xe56555, 0xf28c48, 0x8e85ee, 0x76c84d, 0x5bb6cc, 0x549cdd, 0xd25c99, 0xb37800, 0xf23030,
0x39b249, 0xbb243b, 0x964078, 0x66874f, 0x308ab9, 0x127ed0, 0xbe450c,
@@ -122,33 +66,6 @@ pub(crate) fn dc_str_to_color(s: impl AsRef<str>) -> u32 {
COLORS[color_index]
}
/* date/time tools */
/* the result is UTC or DC_INVALID_TIMESTAMP */
pub(crate) fn dc_timestamp_from_date(date_time: *mut mailimf_date_time) -> i64 {
assert!(!date_time.is_null());
let dt = unsafe { *date_time };
let sec = dt.dt_sec;
let min = dt.dt_min;
let hour = dt.dt_hour;
let day = dt.dt_day;
let month = dt.dt_month;
let year = dt.dt_year;
let ts = chrono::NaiveDateTime::new(
chrono::NaiveDate::from_ymd(year, month as u32, day as u32),
chrono::NaiveTime::from_hms(hour as u32, min as u32, sec as u32),
);
let (zone_hour, zone_min) = if dt.dt_zone >= 0 {
(dt.dt_zone / 100, dt.dt_zone % 100)
} else {
(-(-dt.dt_zone / 100), -(-dt.dt_zone % 100))
};
ts.timestamp() - (zone_hour * 3600 + zone_min * 60) as i64
}
/* ******************************************************************************
* date/time tools
******************************************************************************/
@@ -165,11 +82,26 @@ pub(crate) fn dc_gm2local_offset() -> i64 {
lt.offset().local_minus_utc() as i64
}
/* timesmearing */
// timesmearing
// - as e-mails typically only use a second-based-resolution for timestamps,
// the order of two mails sent withing one second is unclear.
// this is bad eg. when forwarding some messages from a chat -
// these messages will appear at the recipient easily out of order.
// - we work around this issue by not sending out two mails with the same timestamp.
// - for this purpose, in short, we track the last timestamp used in `last_smeared_timestamp`
// when another timestamp is needed in the same second, we use `last_smeared_timestamp+1`
// - after some moments without messages sent out,
// `last_smeared_timestamp` is again in sync with the normal time.
// - however, we do not do all this for the far future,
// but at max `MAX_SECONDS_TO_LEND_FROM_FUTURE`
const MAX_SECONDS_TO_LEND_FROM_FUTURE: i64 = 5;
// returns the currently smeared timestamp,
// may be used to check if call to dc_create_smeared_timestamp() is needed or not.
// the returned timestamp MUST NOT be used to be sent out or saved in the database!
pub(crate) fn dc_smeared_time(context: &Context) -> i64 {
/* function returns a corrected time(NULL) */
let mut now = time();
let ts = *context.last_smeared_timestamp.clone().read().unwrap();
let ts = *context.last_smeared_timestamp.read().unwrap();
if ts >= now {
now = ts + 1;
}
@@ -177,32 +109,36 @@ pub(crate) fn dc_smeared_time(context: &Context) -> i64 {
now
}
// returns a timestamp that is guaranteed to be unique.
pub(crate) fn dc_create_smeared_timestamp(context: &Context) -> i64 {
let now = time();
let mut ret = now;
let ts = *context.last_smeared_timestamp.clone().write().unwrap();
if ret <= ts {
ret = ts + 1;
if ret - now > 5 {
ret = now + 5
let mut last_smeared_timestamp = context.last_smeared_timestamp.write().unwrap();
if ret <= *last_smeared_timestamp {
ret = *last_smeared_timestamp + 1;
if ret - now > MAX_SECONDS_TO_LEND_FROM_FUTURE {
ret = now + MAX_SECONDS_TO_LEND_FROM_FUTURE
}
}
*last_smeared_timestamp = ret;
ret
}
// creates `count` timestamps that are guaranteed to be unique.
// the frist created timestamps is returned directly,
// get the other timestamps just by adding 1..count-1
pub(crate) fn dc_create_smeared_timestamps(context: &Context, count: usize) -> i64 {
/* get a range to timestamps that can be used uniquely */
let now = time();
let start = now + (if count < 5 { count } else { 5 }) as i64 - count as i64;
let count = count as i64;
let mut start = now + min(count, MAX_SECONDS_TO_LEND_FROM_FUTURE) - count;
let ts = *context.last_smeared_timestamp.clone().write().unwrap();
if ts + 1 > start {
ts + 1
} else {
start
}
let mut last_smeared_timestamp = context.last_smeared_timestamp.write().unwrap();
start = max(*last_smeared_timestamp + 1, start);
*last_smeared_timestamp = start + count - 1;
start
}
/* Message-ID tools */
@@ -246,22 +182,6 @@ fn encode_66bits_as_base64(v1: u32, v2: u32, fill: u32) -> String {
String::from_utf8(wrapped_writer).unwrap()
}
pub(crate) fn dc_create_incoming_rfc724_mid(
message_timestamp: i64,
contact_id_from: u32,
contact_ids_to: &[u32],
) -> Option<String> {
/* create a deterministic rfc724_mid from input such that
repeatedly calling it with the same input results in the same Message-id */
let largest_id_to = contact_ids_to.iter().max().copied().unwrap_or_default();
let result = format!(
"{}-{}-{}@stub",
message_timestamp, contact_id_from, largest_id_to
);
Some(result)
}
/// Function generates a Message-ID that can be used for a new outgoing message.
/// - this function is called for all outgoing messages.
/// - the message ID should be globally unique
@@ -281,8 +201,11 @@ pub(crate) fn dc_create_outgoing_rfc724_mid(grpid: Option<&str>, from_addr: &str
///
/// # Arguments
///
/// * `mid` - A string that holds the message id
/// * `mid` - A string that holds the message id. Leading/Trailing <>
/// characters are automatically stripped.
pub(crate) fn dc_extract_grpid_from_rfc724_mid(mid: &str) -> Option<&str> {
let mid = mid.trim_start_matches('<').trim_end_matches('>');
if mid.len() < 9 || !mid.starts_with("Gr.") {
return None;
}
@@ -299,29 +222,6 @@ pub(crate) fn dc_extract_grpid_from_rfc724_mid(mid: &str) -> Option<&str> {
None
}
pub(crate) fn dc_extract_grpid_from_rfc724_mid_list(list: *const clist) -> *mut libc::c_char {
if !list.is_null() {
unsafe {
for cur in (*list).into_iter() {
let mid = to_string_lossy(cur as *const libc::c_char);
if let Some(grpid) = dc_extract_grpid_from_rfc724_mid(&mid) {
return grpid.strdup();
}
}
}
}
ptr::null_mut()
}
pub(crate) fn dc_ensure_no_slash_safe(path: &str) -> &str {
if path.ends_with('/') || path.ends_with('\\') {
return &path[..path.len() - 1];
}
path
}
// Function returns a sanitized basename that does not contain
// win/linux path separators and also not any non-ascii chars
fn get_safe_basename(filename: &str) -> String {
@@ -362,11 +262,9 @@ pub fn dc_derive_safe_stem_ext(filename: &str) -> (String, String) {
// the returned suffix is lower-case
#[allow(non_snake_case)]
pub fn dc_get_filesuffix_lc(path_filename: impl AsRef<str>) -> Option<String> {
if let Some(p) = Path::new(path_filename.as_ref()).extension() {
Some(p.to_string_lossy().to_lowercase())
} else {
None
}
Path::new(path_filename.as_ref())
.extension()
.map(|p| p.to_string_lossy().to_lowercase())
}
/// Returns the `(width, height)` of the given image buffer.
@@ -504,9 +402,13 @@ pub(crate) fn dc_create_folder(context: &Context, path: impl AsRef<std::path::Pa
}
/// Write a the given content to provied file path.
pub(crate) fn dc_write_file(context: &Context, path: impl AsRef<Path>, buf: &[u8]) -> bool {
pub(crate) fn dc_write_file(
context: &Context,
path: impl AsRef<Path>,
buf: &[u8],
) -> Result<(), std::io::Error> {
let path_abs = dc_get_abs_path(context, &path);
if let Err(err) = fs::write(&path_abs, buf) {
fs::write(&path_abs, buf).map_err(|err| {
warn!(
context,
"Cannot write {} bytes to \"{}\": {}",
@@ -514,10 +416,8 @@ pub(crate) fn dc_write_file(context: &Context, path: impl AsRef<Path>, buf: &[u8
path.as_ref().display(),
err
);
false
} else {
true
}
err
})
}
pub fn dc_read_file<P: AsRef<std::path::Path>>(
@@ -580,212 +480,6 @@ pub(crate) fn dc_get_next_backup_path(
bail!("could not create backup file, disk full?");
}
/// Error type for the [OsStrExt] trait
#[derive(Debug, Fail, PartialEq)]
pub enum CStringError {
/// The string contains an interior null byte
#[fail(display = "String contains an interior null byte")]
InteriorNullByte,
/// The string is not valid Unicode
#[fail(display = "String is not valid unicode")]
NotUnicode,
}
/// Extra convenience methods on [std::ffi::OsStr] to work with `*libc::c_char`.
///
/// The primary function of this trait is to more easily convert
/// [OsStr], [OsString] or [Path] into pointers to C strings. This always
/// allocates a new string since it is very common for the source
/// string not to have the required terminal null byte.
///
/// It is implemented for `AsRef<std::ffi::OsStr>>` trait, which
/// allows any type which implements this trait to transparently use
/// this. This is how the conversion for [Path] works.
///
/// [OsStr]: std::ffi::OsStr
/// [OsString]: std::ffi::OsString
/// [Path]: std::path::Path
///
/// # Example
///
/// ```
/// use deltachat::dc_tools::{dc_strdup, OsStrExt};
/// let path = std::path::Path::new("/some/path");
/// let path_c = path.to_c_string().unwrap();
/// unsafe {
/// let mut c_ptr: *mut libc::c_char = dc_strdup(path_c.as_ptr());
/// }
/// ```
pub trait OsStrExt {
/// Convert a [std::ffi::OsStr] to an [std::ffi::CString]
///
/// This is useful to convert e.g. a [std::path::Path] to
/// [*libc::c_char] by using
/// [Path::as_os_str()](std::path::Path::as_os_str) and
/// [CStr::as_ptr()](std::ffi::CStr::as_ptr).
///
/// This returns [CString] and not [&CStr] because not all [OsStr]
/// slices end with a null byte, particularly those coming from
/// [Path] do not have a null byte and having to handle this as
/// the caller would defeat the point of this function.
///
/// On Windows this requires that the [OsStr] contains valid
/// unicode, which should normally be the case for a [Path].
///
/// [CString]: std::ffi::CString
/// [CStr]: std::ffi::CStr
/// [OsStr]: std::ffi::OsStr
/// [Path]: std::path::Path
///
/// # Errors
///
/// Since a C `*char` is terminated by a NULL byte this conversion
/// will fail, when the [OsStr] has an interior null byte. The
/// function will return
/// `[Err]([CStringError::InteriorNullByte])`. When converting
/// from a [Path] it should be safe to
/// [`.unwrap()`](std::result::Result::unwrap) this anyway since a
/// [Path] should not contain interior null bytes.
///
/// On windows when the string contains invalid Unicode
/// `[Err]([CStringError::NotUnicode])` is returned.
fn to_c_string(&self) -> Result<CString, CStringError>;
}
impl<T: AsRef<std::ffi::OsStr>> OsStrExt for T {
#[cfg(not(target_os = "windows"))]
fn to_c_string(&self) -> Result<CString, CStringError> {
use std::os::unix::ffi::OsStrExt;
CString::new(self.as_ref().as_bytes()).map_err(|err| match err {
std::ffi::NulError { .. } => CStringError::InteriorNullByte,
})
}
#[cfg(target_os = "windows")]
fn to_c_string(&self) -> Result<CString, CStringError> {
os_str_to_c_string_unicode(&self)
}
}
// Implementation for os_str_to_c_string on windows.
#[allow(dead_code)]
fn os_str_to_c_string_unicode(
os_str: &dyn AsRef<std::ffi::OsStr>,
) -> Result<CString, CStringError> {
match os_str.as_ref().to_str() {
Some(val) => CString::new(val.as_bytes()).map_err(|err| match err {
std::ffi::NulError { .. } => CStringError::InteriorNullByte,
}),
None => Err(CStringError::NotUnicode),
}
}
/// Convenience methods/associated functions for working with [CString]
///
/// This is helps transitioning from unsafe code.
pub trait CStringExt {
/// Create a new [CString], yolo style
///
/// This unwrap the result, panicking when there are embedded NULL
/// bytes.
fn yolo<T: Into<Vec<u8>>>(t: T) -> CString {
CString::new(t).expect("String contains null byte, can not be CString")
}
}
impl CStringExt for CString {}
/// Convenience methods to make transitioning from raw C strings easier.
///
/// To interact with (legacy) C APIs we often need to convert from
/// Rust strings to raw C strings. This can be clumsy to do correctly
/// and the compiler sometimes allows it in an unsafe way. These
/// methods make it more succinct and help you get it right.
pub trait StrExt {
/// Allocate a new raw C `*char` version of this string.
///
/// This allocates a new raw C string which must be freed using
/// `free`. It takes care of some common pitfalls with using
/// [CString.as_ptr].
///
/// [CString.as_ptr]: std::ffi::CString.as_ptr
///
/// # Panics
///
/// This function will panic when the original string contains an
/// interior null byte as this can not be represented in raw C
/// strings.
unsafe fn strdup(&self) -> *mut libc::c_char;
}
impl<T: AsRef<str>> StrExt for T {
unsafe fn strdup(&self) -> *mut libc::c_char {
let tmp = CString::yolo(self.as_ref());
dc_strdup(tmp.as_ptr())
}
}
pub fn to_string_lossy(s: *const libc::c_char) -> String {
if s.is_null() {
return "".into();
}
let cstr = unsafe { CStr::from_ptr(s) };
cstr.to_string_lossy().to_string()
}
pub fn to_opt_string_lossy(s: *const libc::c_char) -> Option<String> {
if s.is_null() {
return None;
}
Some(to_string_lossy(s))
}
/// Convert a C `*char` pointer to a [std::path::Path] slice.
///
/// This converts a `*libc::c_char` pointer to a [Path] slice. This
/// essentially has to convert the pointer to [std::ffi::OsStr] to do
/// so and thus is the inverse of [OsStrExt::to_c_string]. Just like
/// [OsStrExt::to_c_string] requires valid Unicode on Windows, this
/// requires that the pointer contains valid UTF-8 on Windows.
///
/// Because this returns a reference the [Path] silce can not outlive
/// the original pointer.
///
/// [Path]: std::path::Path
#[cfg(not(target_os = "windows"))]
pub fn as_path<'a>(s: *const libc::c_char) -> &'a std::path::Path {
assert!(!s.is_null(), "cannot be used on null pointers");
use std::os::unix::ffi::OsStrExt;
unsafe {
let c_str = std::ffi::CStr::from_ptr(s).to_bytes();
let os_str = std::ffi::OsStr::from_bytes(c_str);
std::path::Path::new(os_str)
}
}
// as_path() implementation for windows, documented above.
#[cfg(target_os = "windows")]
pub fn as_path<'a>(s: *const libc::c_char) -> &'a std::path::Path {
as_path_unicode(s)
}
// Implementation for as_path() on Windows.
//
// Having this as a separate function means it can be tested on unix
// too.
#[allow(dead_code)]
fn as_path_unicode<'a>(s: *const libc::c_char) -> &'a std::path::Path {
assert!(!s.is_null(), "cannot be used on null pointers");
let cstr = unsafe { CStr::from_ptr(s) };
let str = cstr.to_str().unwrap_or_else(|err| panic!("{}", err));
std::path::Path::new(str)
}
pub(crate) fn time() -> i64 {
SystemTime::now()
.duration_since(SystemTime::UNIX_EPOCH)
@@ -863,71 +557,14 @@ pub(crate) fn listflags_has(listflags: u32, bitindex: usize) -> bool {
(listflags & bitindex) == bitindex
}
pub(crate) unsafe fn strdup(s: *const libc::c_char) -> *mut libc::c_char {
if s.is_null() {
return std::ptr::null_mut();
}
let slen = strlen(s);
let result = libc::malloc(slen + 1);
if result.is_null() {
return std::ptr::null_mut();
}
memcpy(result, s as *const _, slen + 1);
result as *mut _
}
pub(crate) unsafe fn strcasecmp(s1: *const libc::c_char, s2: *const libc::c_char) -> libc::c_int {
let s1 = std::ffi::CStr::from_ptr(s1)
.to_string_lossy()
.to_lowercase();
let s2 = std::ffi::CStr::from_ptr(s2)
.to_string_lossy()
.to_lowercase();
if s1 == s2 {
0
} else {
1
}
}
#[cfg(test)]
mod tests {
use super::*;
use libc::{free, strcmp};
use std::convert::TryInto;
use std::ffi::CStr;
use crate::constants::*;
use crate::test_utils::*;
#[test]
fn test_dc_strdup() {
unsafe {
let str_a = b"foobar\x00" as *const u8 as *const libc::c_char;
let str_a_copy = dc_strdup(str_a);
// Value of str_a_copy should equal foobar
assert_eq!(
CStr::from_ptr(str_a_copy),
CString::new("foobar").unwrap().as_c_str()
);
// Address of str_a should be different from str_a_copy
assert_ne!(str_a, str_a_copy);
let str_a = std::ptr::null() as *const libc::c_char;
let str_a_copy = dc_strdup(str_a);
// Value of str_a_copy should equal ""
assert_eq!(
CStr::from_ptr(str_a_copy),
CString::new("").unwrap().as_c_str()
);
assert_ne!(str_a, str_a_copy);
}
}
#[test]
fn test_rust_ftoa() {
assert_eq!("1.22", format!("{}", 1.22));
@@ -988,44 +625,6 @@ mod tests {
);
}
/* calls free() for each item content */
unsafe fn clist_free_content(haystack: *const clist) {
let mut iter = (*haystack).first;
while !iter.is_null() {
free((*iter).data);
(*iter).data = ptr::null_mut();
iter = if !iter.is_null() {
(*iter).next
} else {
ptr::null_mut()
}
}
}
#[test]
fn test_dc_str_to_clist_1() {
unsafe {
let list = dc_str_to_clist("", " ");
assert_eq!((*list).count, 1);
clist_free_content(list);
clist_free(list);
}
}
#[test]
fn test_dc_str_to_clist_4() {
unsafe {
let list: *mut clist = dc_str_to_clist("foo bar test", " ");
assert_eq!((*list).count, 3);
let str = dc_str_from_clist(list, " ");
assert_eq!(str, "foo bar test");
clist_free_content(list);
clist_free(list);
}
}
#[test]
fn test_dc_create_id() {
let buf = dc_create_id();
@@ -1053,113 +652,6 @@ mod tests {
}
#[test]
fn test_os_str_to_c_string_cwd() {
let some_dir = std::env::current_dir().unwrap();
some_dir.as_os_str().to_c_string().unwrap();
}
#[test]
fn test_os_str_to_c_string_unicode() {
let some_str = String::from("/some/valid/utf8");
let some_dir = std::path::Path::new(&some_str);
assert_eq!(
some_dir.as_os_str().to_c_string().unwrap(),
CString::new("/some/valid/utf8").unwrap()
);
}
#[test]
fn test_os_str_to_c_string_nul() {
let some_str = std::ffi::OsString::from("foo\x00bar");
assert_eq!(
some_str.to_c_string().err().unwrap(),
CStringError::InteriorNullByte
)
}
#[test]
fn test_path_to_c_string_cwd() {
let some_dir = std::env::current_dir().unwrap();
some_dir.to_c_string().unwrap();
}
#[test]
fn test_path_to_c_string_unicode() {
let some_str = String::from("/some/valid/utf8");
let some_dir = std::path::Path::new(&some_str);
assert_eq!(
some_dir.as_os_str().to_c_string().unwrap(),
CString::new("/some/valid/utf8").unwrap()
);
}
#[test]
fn test_os_str_to_c_string_unicode_fn() {
let some_str = std::ffi::OsString::from("foo");
assert_eq!(
os_str_to_c_string_unicode(&some_str).unwrap(),
CString::new("foo").unwrap()
);
}
#[test]
fn test_path_to_c_string_unicode_fn() {
let some_str = String::from("/some/path");
let some_path = std::path::Path::new(&some_str);
assert_eq!(
os_str_to_c_string_unicode(&some_path).unwrap(),
CString::new("/some/path").unwrap()
);
}
#[test]
fn test_os_str_to_c_string_unicode_fn_nul() {
let some_str = std::ffi::OsString::from("fooz\x00bar");
assert_eq!(
os_str_to_c_string_unicode(&some_str).err().unwrap(),
CStringError::InteriorNullByte
);
}
#[test]
fn test_as_path() {
let some_path = CString::new("/some/path").unwrap();
let ptr = some_path.as_ptr();
assert_eq!(as_path(ptr), std::ffi::OsString::from("/some/path"))
}
#[test]
fn test_as_path_unicode_fn() {
let some_path = CString::new("/some/path").unwrap();
let ptr = some_path.as_ptr();
assert_eq!(as_path_unicode(ptr), std::ffi::OsString::from("/some/path"));
}
#[test]
fn test_cstring_yolo() {
assert_eq!(CString::new("hello").unwrap(), CString::yolo("hello"));
}
#[test]
fn test_strdup_str() {
unsafe {
let s = "hello".strdup();
let cmp = strcmp(s, b"hello\x00" as *const u8 as *const libc::c_char);
free(s as *mut libc::c_void);
assert_eq!(cmp, 0);
}
}
#[test]
fn test_strdup_string() {
unsafe {
let s = String::from("hello").strdup();
let cmp = strcmp(s, b"hello\x00" as *const u8 as *const libc::c_char);
free(s as *mut libc::c_void);
assert_eq!(cmp, 0);
}
}
#[test]
fn test_dc_extract_grpid_from_rfc724_mid() {
// Should return None if we pass invalid mid
@@ -1181,6 +673,16 @@ mod tests {
let mid = "Gr.1234567890123456.morerandom@domain.de";
let grpid = dc_extract_grpid_from_rfc724_mid(mid);
assert_eq!(grpid, Some("1234567890123456"));
// Should return extracted grpid for grpid with length of 11
let mid = "<Gr.12345678901.morerandom@domain.de>";
let grpid = dc_extract_grpid_from_rfc724_mid(mid);
assert_eq!(grpid, Some("12345678901"));
// Should return extracted grpid for grpid with length of 11
let mid = "<Gr.1234567890123456.morerandom@domain.de>";
let grpid = dc_extract_grpid_from_rfc724_mid(mid);
assert_eq!(grpid, Some("1234567890123456"));
}
#[test]
@@ -1262,14 +764,6 @@ mod tests {
}
}
#[test]
fn test_dc_create_incoming_rfc724_mid() {
let res = dc_create_incoming_rfc724_mid(123, 45, &vec![6, 7]);
assert_eq!(res, Some("123-45-7@stub".into()));
let res = dc_create_incoming_rfc724_mid(123, 45, &vec![]);
assert_eq!(res, Some("123-45-0@stub".into()));
}
#[test]
fn test_file_get_safe_basename() {
assert_eq!(get_safe_basename("12312/hello"), "hello");
@@ -1304,7 +798,7 @@ mod tests {
dc_delete_file(context, "$BLOBDIR/foobar.dadada");
dc_delete_file(context, "$BLOBDIR/foobar-folder");
}
assert!(dc_write_file(context, "$BLOBDIR/foobar", b"content"));
assert!(dc_write_file(context, "$BLOBDIR/foobar", b"content").is_ok());
assert!(dc_file_exist(context, "$BLOBDIR/foobar",));
assert!(!dc_file_exist(context, "$BLOBDIR/foobarx"));
assert_eq!(dc_get_filebytes(context, "$BLOBDIR/foobar"), 7);
@@ -1336,7 +830,7 @@ mod tests {
assert!(!dc_delete_file(context, "$BLOBDIR/foobar-folder"));
let fn0 = "$BLOBDIR/data.data";
assert!(dc_write_file(context, &fn0, b"content"));
assert!(dc_write_file(context, &fn0, b"content").is_ok());
assert!(dc_delete_file(context, &fn0));
assert!(!dc_file_exist(context, &fn0));
@@ -1345,14 +839,44 @@ mod tests {
#[test]
fn test_listflags_has() {
let listflags: u32 = 0x1101;
assert!(listflags_has(listflags, 0x1) == true);
assert!(listflags_has(listflags, 0x10) == false);
assert!(listflags_has(listflags, 0x100) == true);
assert!(listflags_has(listflags, 0x1000) == true);
assert!(listflags_has(listflags, 0x1));
assert!(!listflags_has(listflags, 0x10));
assert!(listflags_has(listflags, 0x100));
assert!(listflags_has(listflags, 0x1000));
let listflags: u32 = (DC_GCL_ADD_SELF | DC_GCL_VERIFIED_ONLY).try_into().unwrap();
assert!(listflags_has(listflags, DC_GCL_VERIFIED_ONLY) == true);
assert!(listflags_has(listflags, DC_GCL_ADD_SELF) == true);
assert!(listflags_has(listflags, DC_GCL_VERIFIED_ONLY));
assert!(listflags_has(listflags, DC_GCL_ADD_SELF));
let listflags: u32 = DC_GCL_VERIFIED_ONLY.try_into().unwrap();
assert!(listflags_has(listflags, DC_GCL_ADD_SELF) == false);
assert!(!listflags_has(listflags, DC_GCL_ADD_SELF));
}
#[test]
fn test_create_smeared_timestamp() {
let t = dummy_context();
assert_ne!(
dc_create_smeared_timestamp(&t.ctx),
dc_create_smeared_timestamp(&t.ctx)
);
assert!(
dc_create_smeared_timestamp(&t.ctx)
>= SystemTime::now()
.duration_since(SystemTime::UNIX_EPOCH)
.unwrap()
.as_secs() as i64
);
}
#[test]
fn test_create_smeared_timestamps() {
let t = dummy_context();
let count = MAX_SECONDS_TO_LEND_FROM_FUTURE - 1;
let start = dc_create_smeared_timestamps(&t.ctx, count as usize);
let next = dc_smeared_time(&t.ctx);
assert!((start + count - 1) < next);
let count = MAX_SECONDS_TO_LEND_FROM_FUTURE + 30;
let start = dc_create_smeared_timestamps(&t.ctx, count as usize);
let next = dc_smeared_time(&t.ctx);
assert!((start + count - 1) < next);
}
}

View File

@@ -1,3 +1,7 @@
//! De-HTML
//!
//! A module to remove HTML tags from the email text
use lazy_static::lazy_static;
use quick_xml;
use quick_xml::events::{BytesEnd, BytesStart, BytesText};
@@ -19,22 +23,18 @@ enum AddText {
YesPreserveLineEnds,
}
// dc_dehtml() returns way too many lineends; however, an optimisation on this issue is not needed as
// the lineends are typically remove in further processing by the caller
pub fn dc_dehtml(buf_terminated: &str) -> String {
let buf_terminated = buf_terminated.trim();
if buf_terminated.is_empty() {
return "".into();
}
// dehtml() returns way too many newlines; however, an optimisation on this issue is not needed as
// the newlines are typically removed in further processing by the caller
pub fn dehtml(buf: &str) -> String {
let buf = buf.trim();
let mut dehtml = Dehtml {
strbuilder: String::with_capacity(buf_terminated.len()),
strbuilder: String::with_capacity(buf.len()),
add_text: AddText::YesRemoveLineEnds,
last_href: None,
};
let mut reader = quick_xml::Reader::from_str(buf_terminated);
let mut reader = quick_xml::Reader::from_str(buf);
let mut buf = Vec::new();
@@ -171,7 +171,7 @@ mod tests {
use super::*;
#[test]
fn test_dc_dehtml() {
fn test_dehtml() {
let cases = vec![
(
"<a href='https://example.com'> Foo </a>",
@@ -186,7 +186,7 @@ mod tests {
("", ""),
];
for (input, output) in cases {
assert_eq!(dc_dehtml(input), output);
assert_eq!(dehtml(input), output);
}
}
}

View File

@@ -1,41 +1,20 @@
//! 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 mailparse::MailHeaderMap;
use num_traits::FromPrimitive;
use crate::aheader::*;
use crate::config::Config;
use crate::context::Context;
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 {
@@ -71,39 +50,27 @@ impl EncryptHelper {
Aheader::new(addr, pk, self.prefer_encrypt)
}
pub fn try_encrypt(
&mut self,
factory: &mut MimeFactory,
/// Determines if we can and should encrypt.
pub fn should_encrypt(
&self,
context: &Context,
e2ee_guaranteed: bool,
min_verified: libc::c_int,
do_gossip: bool,
mut in_out_message: *mut Mailmime,
imffields_unprotected: *mut mailimf_fields,
peerstates: &[(Option<Peerstate>, &str)],
) -> 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,
for (peerstate, addr) in peerstates {
match peerstate {
Some(peerstate) => {
if peerstate.prefer_encrypt != EncryptPreference::Mutual && !e2ee_guaranteed {
info!(context, "peerstate for {:?} is no-encrypt", addr);
return Ok(false);
}
}
None => {
let msg = format!("peerstate for {} missing, cannot encrypt", recipient_addr);
let msg = format!("peerstate for {:?} missing, cannot encrypt", addr);
if e2ee_guaranteed {
return Err(format_err!("{}", msg));
} else {
@@ -111,169 +78,59 @@ impl EncryptHelper {
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");
Ok(true)
}
key
};
/// Tries to encrypt the passed in `mail`.
pub fn encrypt(
&mut self,
context: &Context,
min_verified: PeerstateVerifiedStatus,
mail_to_encrypt: lettre_email::PartBuilder,
peerstates: &[(Option<Peerstate>, &str)],
) -> Result<String> {
let mut keyring = Keyring::default();
// 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 = 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)
for (peerstate, addr) in peerstates
.iter()
.filter_map(|(state, addr)| state.as_ref().map(|s| (s, addr)))
{
let key = peerstate.peek_key(min_verified).ok_or_else(|| {
format_err!("proper enc-key for {} missing, cannot encrypt", addr)
})?;
keyring.add_ref(key);
}
keyring.add_ref(&self.public_key);
let sign_key = Key::from_self_private(context, self.addr.clone(), &context.sql)
.ok_or_else(|| format_err!("missing own private key"))?;
let raw_message = mail_to_encrypt.build().as_string().into_bytes();
let ctext = pgp::pk_encrypt(&raw_message, &keyring, Some(&sign_key))?;
Ok(ctext)
}
}
pub fn try_decrypt(
context: &Context,
in_out_message: *mut Mailmime,
) -> Result<(bool, HashSet<String>, HashSet<String>)> {
// just a pointer into mailmime structure, must not be freed
let imffields = mailmime_find_mailimf_fields(in_out_message);
ensure!(
!in_out_message.is_null() && !imffields.is_null(),
"corrupt invalid mime inputs"
);
let from = wrapmime::get_field_from(imffields)?;
let message_time = wrapmime::get_field_date(imffields)?;
mail: &mailparse::ParsedMail<'_>,
message_time: i64,
) -> Result<(Option<Vec<u8>>, HashSet<String>)> {
let from = mail
.headers
.get_first_value("From")?
.and_then(|from_addr| mailparse::addrparse(&from_addr).ok())
.and_then(|from| from.extract_single_info())
.map(|from| from.addr)
.unwrap_or_default();
let mut peerstate = None;
let autocryptheader = Aheader::from_imffields(&from, imffields);
let autocryptheader = Aheader::from_headers(context, &from, &mail.headers);
if message_time > 0 {
peerstate = Peerstate::from_addr(context, &context.sql, &from);
@@ -282,9 +139,7 @@ pub fn try_decrypt(
if let Some(ref header) = autocryptheader {
peerstate.apply_header(&header, message_time);
peerstate.save_to_db(&context.sql, false)?;
} else if message_time > peerstate.last_seen_autocrypt
&& !contains_report(in_out_message)
{
} else if message_time > peerstate.last_seen_autocrypt && !contains_report(mail) {
peerstate.degrade_encryption(message_time);
peerstate.save_to_db(&context.sql, false)?;
}
@@ -294,13 +149,12 @@ pub fn try_decrypt(
peerstate = Some(p);
}
}
/* possibly perform decryption */
let mut private_keyring = Keyring::default();
let mut public_keyring_for_validate = Keyring::default();
let mut encrypted = false;
let mut out_mail = None;
let mut signatures = HashSet::default();
let mut gossipped_addr = HashSet::default();
let self_addr = context.get_config(Config::ConfiguredAddr);
if let Some(self_addr) = self_addr {
@@ -320,60 +174,16 @@ pub fn try_decrypt(
}
}
let mut gossip_headers = ptr::null_mut();
encrypted = decrypt_if_autocrypt_message(
out_mail = decrypt_if_autocrypt_message(
context,
in_out_message,
mail,
&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)?;
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)
Ok((out_mail, signatures))
}
/// Load public key from database or generate a new one.
@@ -400,7 +210,7 @@ fn load_or_generate_self_public_key(context: &Context, self_addr: impl AsRef<str
);
match pgp::create_keypair(&self_addr) {
Some((public_key, private_key)) => {
match dc_key_save_self_keypair(
if dc_key_save_self_keypair(
context,
&public_key,
&private_key,
@@ -408,219 +218,78 @@ fn load_or_generate_self_public_key(context: &Context, self_addr: impl AsRef<str
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")),
info!(
context,
"Keypair generated in {:.3}s.",
start.elapsed().as_secs()
);
Ok(public_key)
} else {
Err(format_err!("Failed to save keypair"))
}
}
None => Err(format_err!("Failed to generate keypair")),
}
}
fn update_gossip_peerstates(
fn decrypt_if_autocrypt_message<'a>(
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()
&& to_string_lossy(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,
mail: &mailparse::ParsedMail<'a>,
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.
) -> Result<Option<Vec<u8>>> {
// 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.
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) {
let encrypted_data_part = match wrapmime::get_autocrypt_mime(mail) {
Err(_) => {
// not a proper autocrypt message, abort and ignore
return Ok(false);
// not an autocrypt mime message, abort and ignore
return Ok(None);
}
Ok(res) => res,
};
info!(context, "Detected Autocrypt-mime message");
let decrypted_mime = decrypt_part(
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.is_empty() {
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)
)
}
/// Returns Ok(None) if nothing encrypted was found.
fn decrypt_part(
_context: &Context,
mime: *mut Mailmime,
mail: &mailparse::ParsedMail<'_>,
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 data: Vec<u8> = wrapmime::decode_dt_data(mime_data, mime_transfer_encoding)?;
let mut ret_decrypted_mime = ptr::null_mut();
) -> Result<Option<Vec<u8>>> {
let data = mail.get_body_raw()?;
if has_decrypted_pgp_armor(&data) {
// we should only have one decryption happening
ensure!(ret_valid_signatures.is_empty(), "corrupt signatures");
let plain = match pgp::pk_decrypt(
let plain = 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) => 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;
}
ensure!(!ret_valid_signatures.is_empty(), "no valid signatures");
return Ok(Some(plain));
}
Ok(ret_decrypted_mime)
Ok(None)
}
fn has_decrypted_pgp_armor(input: &[u8]) -> bool {
@@ -644,36 +313,8 @@ fn has_decrypted_pgp_armor(input: &[u8]) -> bool {
/// 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
&& to_string_lossy(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
fn contains_report(mail: &mailparse::ParsedMail<'_>) -> bool {
mail.ctype.mimetype == "multipart/report"
}
/// Ensures a private key exists for the configured user.
@@ -699,7 +340,6 @@ pub fn ensure_secret_key_exists(context: &Context) -> Result<String> {
#[cfg(test)]
mod tests {
use super::*;
use libc::free;
use crate::test_utils::*;
@@ -722,7 +362,7 @@ mod tests {
#[test]
fn test_mailmime_parse() {
let plain = b"Chat-Disposition-Notification-To: holger@deltachat.de
let plain = b"Chat-Disposition-Notification-To: hello@world.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
@@ -730,35 +370,18 @@ Subject: =?utf-8?Q?Chat=3A?= Delta Chat =?utf-8?Q?Dev=3A?= sidenote for
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
sidenote for all: things are trick atm recomm=
end not 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 mail = mailparse::parse_mail(plain).expect("failed to parse valid message");
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 _) };
assert_eq!(mail.headers.len(), 6);
assert!(
mail.get_body().unwrap().starts_with(
"sidenote for all: things are trick atm recommend not to try to run with desktop or ios unless you are ready to hunt bugs")
);
}
mod load_or_generate_self_public_key {

View File

@@ -1,19 +1,13 @@
use failure::Fail;
//! # Error handling
use lettre_email::mime;
#[derive(Debug, Fail)]
pub enum Error {
#[fail(display = "Sqlite Error: {:?}", _0)]
Sql(rusqlite::Error),
#[fail(display = "Sqlite Connection Pool Error: {:?}", _0)]
ConnectionPool(r2d2::Error),
#[fail(display = "{:?}", _0)]
Failure(failure::Error),
#[fail(display = "Sqlite: Connection closed")]
SqlNoConnection,
#[fail(display = "Sqlite: Already open")]
SqlAlreadyOpen,
#[fail(display = "Sqlite: Failed to open")]
SqlFailedToOpen,
#[fail(display = "SQL error: {:?}", _0)]
SqlError(#[cause] crate::sql::Error),
#[fail(display = "{:?}", _0)]
Io(std::io::Error),
#[fail(display = "{:?}", _0)]
@@ -22,8 +16,6 @@ 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)]
@@ -34,13 +26,23 @@ pub enum Error {
BlobError(#[cause] crate::blob::BlobError),
#[fail(display = "Invalid Message ID.")]
InvalidMsgId,
#[fail(display = "Watch folder not found {:?}", _0)]
WatchFolderNotFound(String),
#[fail(display = "Invalid Email: {:?}", _0)]
MailParseError(#[cause] mailparse::MailParseError),
#[fail(display = "Building invalid Email: {:?}", _0)]
LettreError(#[cause] lettre_email::error::Error),
#[fail(display = "FromStr error: {:?}", _0)]
FromStr(#[cause] mime::FromStrError),
#[fail(display = "Not Configured")]
NotConfigured,
}
pub type Result<T> = std::result::Result<T, Error>;
impl From<rusqlite::Error> for Error {
fn from(err: rusqlite::Error) -> Error {
Error::Sql(err)
impl From<crate::sql::Error> for Error {
fn from(err: crate::sql::Error) -> Error {
Error::SqlError(err)
}
}
@@ -56,12 +58,6 @@ impl From<failure::Error> for Error {
}
}
impl From<r2d2::Error> for Error {
fn from(err: r2d2::Error) -> Error {
Error::ConnectionPool(err)
}
}
impl From<std::io::Error> for Error {
fn from(err: std::io::Error) -> Error {
Error::Io(err)
@@ -80,12 +76,6 @@ impl From<image_meta::ImageError> for Error {
}
}
impl From<crate::dc_tools::CStringError> for Error {
fn from(err: crate::dc_tools::CStringError) -> Error {
Error::CStringError(err)
}
}
impl From<pgp::errors::Error> for Error {
fn from(err: pgp::errors::Error) -> Error {
Error::Pgp(err)
@@ -110,6 +100,24 @@ impl From<crate::message::InvalidMsgId> for Error {
}
}
impl From<mailparse::MailParseError> for Error {
fn from(err: mailparse::MailParseError) -> Error {
Error::MailParseError(err)
}
}
impl From<lettre_email::error::Error> for Error {
fn from(err: lettre_email::error::Error) -> Error {
Error::LettreError(err)
}
}
impl From<mime::FromStrError> for Error {
fn from(err: mime::FromStrError) -> Error {
Error::FromStr(err)
}
}
#[macro_export]
macro_rules! bail {
($e:expr) => {

View File

@@ -1,3 +1,5 @@
//! # Events specification
use std::path::PathBuf;
use strum::EnumProperty;
@@ -90,7 +92,7 @@ pub enum Event {
/// 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
/// failed (returned false). It should be sufficient to report only the *last* error
/// in a messasge box then.
///
/// @return
@@ -243,4 +245,11 @@ pub enum Event {
/// @return 0
#[strum(props(id = "2061"))]
SecurejoinJoinerProgress { contact_id: u32, progress: usize },
/// This event is sent out to the inviter when a joiner successfully joined a group.
/// @param data1 (int) chat_id
/// @param data2 (int) contact_id
/// @return 0
#[strum(props(id = "2062"))]
SecurejoinMemberAdded { chat_id: u32, contact_id: u32 },
}

58
src/headerdef.rs Normal file
View File

@@ -0,0 +1,58 @@
#[derive(Debug, Display, Clone, PartialEq, Eq, EnumVariantNames)]
#[strum(serialize_all = "kebab_case")]
#[allow(dead_code)]
pub enum HeaderDef {
MessageId,
Subject,
Date,
From_,
To,
Cc,
Disposition,
OriginalMessageId,
ListId,
References,
InReplyTo,
Precedence,
ChatVersion,
ChatGroupId,
ChatGroupName,
ChatGroupNameChanged,
ChatVerified,
ChatGroupImage, // deprecated
ChatGroupAvatar,
ChatUserAvatar,
ChatVoiceMessage,
ChatGroupMemberRemoved,
ChatGroupMemberAdded,
ChatContent,
ChatDuration,
ChatDispositionNotificationTo,
AutocryptSetupMessage,
SecureJoin,
SecureJoinGroup,
SecureJoinFingerprint,
SecureJoinInvitenumber,
SecureJoinAuth,
_TestHeader,
}
impl HeaderDef {
/// Returns the corresponding Event id.
pub fn get_headername(&self) -> String {
self.to_string()
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
/// Test that kebab_case serialization works as expected
fn kebab_test() {
assert_eq!(HeaderDef::From_.to_string(), "from");
assert_eq!(HeaderDef::_TestHeader.to_string(), "test-header");
}
}

File diff suppressed because it is too large Load Diff

273
src/imap/idle.rs Normal file
View File

@@ -0,0 +1,273 @@
use super::Imap;
use async_imap::extensions::idle::IdleResponse;
use async_std::prelude::*;
use async_std::task;
use std::sync::atomic::Ordering;
use std::time::{Duration, SystemTime};
use crate::context::Context;
use crate::imap_client::*;
use super::select_folder;
type Result<T> = std::result::Result<T, Error>;
#[derive(Debug, Fail)]
pub enum Error {
#[fail(display = "IMAP IDLE protocol failed to init/complete")]
IdleProtocolFailed(#[cause] async_imap::error::Error),
#[fail(display = "IMAP IDLE protocol timed out")]
IdleTimeout(#[cause] async_std::future::TimeoutError),
#[fail(display = "IMAP server does not have IDLE capability")]
IdleAbilityMissing,
#[fail(display = "IMAP select folder error")]
SelectFolderError(#[cause] select_folder::Error),
#[fail(display = "IMAP error")]
ImapError(#[cause] async_imap::error::Error),
#[fail(display = "Setup handle error")]
SetupHandleError(#[cause] super::Error),
}
impl From<select_folder::Error> for Error {
fn from(err: select_folder::Error) -> Error {
Error::SelectFolderError(err)
}
}
impl Imap {
pub fn can_idle(&self) -> bool {
task::block_on(async move { self.config.read().await.can_idle })
}
pub fn idle(&self, context: &Context, watch_folder: Option<String>) -> Result<()> {
task::block_on(async move {
if !self.can_idle() {
return Err(Error::IdleAbilityMissing);
}
self.setup_handle_if_needed(context)
.await
.map_err(Error::SetupHandleError)?;
self.select_folder(context, watch_folder.clone()).await?;
let session = self.session.lock().await.take();
let timeout = Duration::from_secs(23 * 60);
if let Some(session) = session {
match session.idle() {
// BEWARE: If you change the Secure branch you
// typically also need to change the Insecure branch.
IdleHandle::Secure(mut handle) => {
if let Err(err) = handle.init().await {
return Err(Error::IdleProtocolFailed(err));
}
let (idle_wait, interrupt) = handle.wait_with_timeout(timeout);
*self.interrupt.lock().await = Some(interrupt);
if self.skip_next_idle_wait.load(Ordering::SeqCst) {
// interrupt_idle has happened before we
// provided self.interrupt
self.skip_next_idle_wait.store(false, Ordering::SeqCst);
std::mem::drop(idle_wait);
info!(context, "Idle wait was skipped");
} else {
info!(context, "Idle entering wait-on-remote state");
match idle_wait.await {
IdleResponse::NewData(_) => {
info!(context, "Idle has NewData");
}
// TODO: idle_wait does not distinguish manual interrupts
// from Timeouts if we would know it's a Timeout we could bail
// directly and reconnect .
IdleResponse::Timeout => {
info!(context, "Idle-wait timeout or interruption");
}
IdleResponse::ManualInterrupt => {
info!(context, "Idle wait was interrupted");
}
}
}
// if we can't properly terminate the idle
// protocol let's break the connection.
let res =
async_std::future::timeout(Duration::from_secs(15), handle.done())
.await
.map_err(|err| {
self.trigger_reconnect();
Error::IdleTimeout(err)
})?;
match res {
Ok(session) => {
*self.session.lock().await = Some(Session::Secure(session));
}
Err(err) => {
// if we cannot terminate IDLE it probably
// means that we waited long (with idle_wait)
// but the network went away/changed
self.trigger_reconnect();
return Err(Error::IdleProtocolFailed(err));
}
}
}
IdleHandle::Insecure(mut handle) => {
if let Err(err) = handle.init().await {
return Err(Error::IdleProtocolFailed(err));
}
let (idle_wait, interrupt) = handle.wait_with_timeout(timeout);
*self.interrupt.lock().await = Some(interrupt);
if self.skip_next_idle_wait.load(Ordering::SeqCst) {
// interrupt_idle has happened before we
// provided self.interrupt
self.skip_next_idle_wait.store(false, Ordering::SeqCst);
std::mem::drop(idle_wait);
info!(context, "Idle wait was skipped");
} else {
info!(context, "Idle entering wait-on-remote state");
match idle_wait.await {
IdleResponse::NewData(_) => {
info!(context, "Idle has NewData");
}
// TODO: idle_wait does not distinguish manual interrupts
// from Timeouts if we would know it's a Timeout we could bail
// directly and reconnect .
IdleResponse::Timeout => {
info!(context, "Idle-wait timeout or interruption");
}
IdleResponse::ManualInterrupt => {
info!(context, "Idle wait was interrupted");
}
}
}
// if we can't properly terminate the idle
// protocol let's break the connection.
let res =
async_std::future::timeout(Duration::from_secs(15), handle.done())
.await
.map_err(|err| {
self.trigger_reconnect();
Error::IdleTimeout(err)
})?;
match res {
Ok(session) => {
*self.session.lock().await = Some(Session::Insecure(session));
}
Err(err) => {
// if we cannot terminate IDLE it probably
// means that we waited long (with idle_wait)
// but the network went away/changed
self.trigger_reconnect();
return Err(Error::IdleProtocolFailed(err));
}
}
}
}
}
Ok(())
})
}
pub(crate) fn fake_idle(&self, context: &Context, watch_folder: Option<String>) {
// Idle using polling. This is also needed if we're not yet configured -
// in this case, we're waiting for a configure job (and an interrupt).
task::block_on(async move {
let fake_idle_start_time = SystemTime::now();
info!(context, "IMAP-fake-IDLEing...");
let interrupt = stop_token::StopSource::new();
// check every minute if there are new messages
// TODO: grow sleep durations / make them more flexible
let interval = async_std::stream::interval(Duration::from_secs(60));
let mut interrupt_interval = interrupt.stop_token().stop_stream(interval);
*self.interrupt.lock().await = Some(interrupt);
if self.skip_next_idle_wait.load(Ordering::SeqCst) {
// interrupt_idle has happened before we
// provided self.interrupt
self.skip_next_idle_wait.store(false, Ordering::SeqCst);
info!(context, "fake-idle wait was skipped");
} else {
// loop until we are interrupted or if we fetched something
while let Some(_) = interrupt_interval.next().await {
// try to connect with proper login params
// (setup_handle_if_needed might not know about them if we
// never successfully connected)
if let Err(err) = self.connect_configured(context) {
warn!(context, "fake_idle: could not connect: {}", err);
continue;
}
if self.config.read().await.can_idle {
// we only fake-idled because network was gone during IDLE, probably
break;
}
info!(context, "fake_idle is connected");
// we are connected, let's see if fetching messages results
// in anything. If so, we behave as if IDLE had data but
// will have already fetched the messages so perform_*_fetch
// will not find any new.
if let Some(ref watch_folder) = watch_folder {
match self.fetch_new_messages(context, watch_folder).await {
Ok(res) => {
info!(context, "fetch_new_messages returned {:?}", res);
if res {
break;
}
}
Err(err) => {
error!(context, "could not fetch from folder: {}", err);
self.trigger_reconnect()
}
}
}
}
}
self.interrupt.lock().await.take();
info!(
context,
"IMAP-fake-IDLE done after {:.4}s",
SystemTime::now()
.duration_since(fake_idle_start_time)
.unwrap()
.as_millis() as f64
/ 1000.,
);
})
}
pub fn interrupt_idle(&self, context: &Context) {
task::block_on(async move {
let mut interrupt: Option<stop_token::StopSource> = self.interrupt.lock().await.take();
if interrupt.is_none() {
// idle wait is not running, signal it needs to skip
self.skip_next_idle_wait.store(true, Ordering::SeqCst);
// meanwhile idle-wait may have produced the StopSource
interrupt = self.interrupt.lock().await.take();
}
// let's manually drop the StopSource
if interrupt.is_some() {
// the imap thread provided us a stop token but might
// not have entered idle_wait yet, give it some time
// for that to happen. XXX handle this without extra wait
// https://github.com/deltachat/deltachat-core-rust/issues/925
std::thread::sleep(Duration::from_millis(200));
info!(context, "low-level: dropping stop-source to interrupt idle");
std::mem::drop(interrupt)
}
});
}
}

1243
src/imap/mod.rs Normal file

File diff suppressed because it is too large Load Diff

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