Compare commits

..

1375 Commits

Author SHA1 Message Date
holger krekel
0dc87e9a0f remove one attr from mimeparser 2019-12-07 22:41:43 +01:00
holger krekel
c011a8cfef run lint without installing the package 2019-12-07 21:44:34 +01:00
holger krekel
2d7d79fc44 allow to use a different buildhost :) 2019-12-07 21:44:34 +01:00
holger krekel
119d4fa689 - 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 21:44:34 +01:00
holger krekel
b7a9f73329 passes test but needs cleanup 2019-12-07 21:44:34 +01:00
holger krekel
a52a3e0d24 try fix incoming text/html in multipart/mixed 2019-12-07 21:44:34 +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
B. Petersen
37ecfa6b67 fix gm2local offset calculations 2019-11-09 20:26:00 +01:00
B. Petersen
99ba2fb358 let dc_timestamp_to_str() print the local time, not UTC times. the function is used for outputs directly shown to the user, eg. dc_get_msg_info() 2019-11-09 20:26:00 +01:00
B. Petersen
85ebde29dc do not resort chatlist on draft changes
resorting the chatlist on changing drafts has some ux issues.
eg. when the chatlist is visible together with the input field,
if may come to flickering resorting during input
or to a resorting just when the user leave the chat
as this might trigger set_draft().

but also on mobiles, the resorting is visible and a bit unexpected.
also it is unclear what happens when a chat with a draft is entered
and left without modifications.

the solution proposed here is to ignore draft on sorting
while still showing them in the chatlist
if they're newer as the last message.

a possible disadvantage is
that the date for the chat with a draft does not follow the ordering
(the ordering is by the last message),
however, the date is not shown as a "primary sort" criterion or so,
so it might be that this is completely okay.
also, of course, it affects only draft :)
2019-11-09 19:32:13 +01:00
björn petersen
0876f45503 Merge pull request #810 from deltachat/sync-config-param
restore config-param if configure() fails
2019-11-08 19:36:43 +01:00
B. Petersen
2fae6890c2 the new MsgId type comes with a formatter that makes special formatting in the repl tool superfluous 2019-11-07 13:19:38 +01:00
B. Petersen
34f9961857 restore config-param if configure() fails 2019-11-06 22:59:14 +01:00
B. Petersen
515f0c5089 correct ffi return value of dc_add_device_msg() 2019-11-06 13:33:30 +01:00
B. Petersen
5a11551b4d block sending to chats that do not support sending (normally, this should already be avoided in the ui) 2019-11-06 13:33:30 +01:00
B. Petersen
49bf99588b show chat-profile-image in repl tool 2019-11-06 13:33:30 +01:00
B. Petersen
231110fb61 add profile-icon for device-chat 2019-11-06 13:33:30 +01:00
B. Petersen
4c30bf80ce target comments of @flub 2019-11-06 13:33:30 +01:00
B. Petersen
f8afefa2c1 get contact- and chat-info for device-messages 2019-11-06 13:33:30 +01:00
B. Petersen
89bb2d0ffe add devicemsg to repl tool 2019-11-06 13:33:30 +01:00
B. Petersen
b5d5d98645 implement add_device_msg() 2019-11-06 13:33:30 +01:00
B. Petersen
89f394ab86 create separate function for preparing a blob 2019-11-06 13:33:30 +01:00
B. Petersen
cbaa4e03b3 basic devicetalk implementation 2019-11-06 13:33:30 +01:00
B. Petersen
50539465b9 prototype a device-chat 2019-11-06 13:33:30 +01:00
B. Petersen
be08bcb22b rename DC_CONTACT_ID_DEVICE to DC_CONTACT_ID_INFO to be in-line with dc_msg_is_info() 2019-11-06 13:33:30 +01:00
holger krekel
dcd92a894e fix export: write backup_time to the destination not the source sql file
and perform slightly cleaner teardown in python
2019-11-06 13:26:32 +01:00
holger krekel
6336eeb568 better error on has_backup() failing 2019-11-06 13:26:32 +01:00
holger krekel
6b18cbda1f refine dc_copy along the lines @flub did for blobstore 2019-11-06 13:26:32 +01:00
Alexander Krotov
cf023ea557 sql: remove unnecessary ? in prepare() and prepare2() 2019-11-06 13:26:03 +01:00
Alexander Krotov
51a804a80f location: use "bool" for "independent" argument 2019-11-06 14:17:59 +03:00
B. Petersen
1a33b1c574 bump version 2019-11-05 17:04:44 +01:00
B. Petersen
67e2e4d415 target comment of @hpk42 2019-11-04 13:20:35 +01:00
B. Petersen
8c2efa707a name special contact-ids where easily possible
the point of this pr is to get an overview
how and where DC_CONTACT_ID_DEVICE is used,
to prepare introducing a device-"chat".

i did not change the sql statements for now
as this would require some more refactoring
and has the potential to introduce bugs.
2019-11-04 13:20:35 +01:00
B. Petersen
87abc6e4a2 adapt wording 2019-11-03 21:36:40 +01:00
B. Petersen
0ea017c53d add repl command for testing interrupt-idle 2019-11-03 21:36:40 +01:00
B. Petersen
b9c7510b58 use boolean for jobs_needed 2019-11-03 21:36:40 +01:00
holger krekel
01e7caf65a use job_id as mail_id for SendableEmail -- it's only an internal id and job_id is unique enough. 2019-11-03 21:36:01 +01:00
holger krekel
1cfeb730c3 try to fix some smtp todos and do better error logging 2019-11-03 21:36:01 +01:00
Floris Bruynooghe
a3b90a08b6 Copy the file contents manually
Before we created an empty file and asked the OS to copy the file.
The OS is very good at this so this is a good idea generally.  However
it seems that in some cases, possibly an Android Dowload folder, we
might be able to create a file but not overwrite it.  Thus refactor
this a bit so we are copying the file ourselves.

There are no new tests here since the behaviour remains identical.
The good news is that the existing tests were good enough to catch
some bugs already.
2019-11-03 20:16:56 +01:00
holger krekel
31571be71e add account.get_chat_by_id API 2019-11-03 20:14:57 +01:00
holger krekel
661fc45106 split "chatting.py" into "contact.py" and "chat.py" to be more reminiscent of the core-rust structure 2019-11-03 20:14:57 +01:00
holger krekel
da64dee3e0 start a changelog for the yet untagged beta.7 2019-11-03 01:32:49 +01:00
holger krekel
cb00f5da79 fix #786 by always succeeding to create a canonical deterministic message-id -- thanks @csb0730 for the analysis of the issue 2019-11-03 01:28:45 +01:00
B. Petersen
e1df41c209 fix tests 2019-11-03 01:19:29 +01:00
B. Petersen
3b64748427 add proptest-regression file 2019-11-03 01:19:29 +01:00
B. Petersen
70cef68eeb display failed messages 2019-11-03 01:19:29 +01:00
holger krekel
c5f64d2988 address @link2xt comments 2019-11-03 00:21:27 +01:00
holger krekel
4eb068613d extend and fix python/test side of location streaming 2019-11-03 00:21:27 +01:00
holger krekel
d774430ec2 simplify location parsing 2019-11-03 00:21:27 +01:00
holger krekel
d24a982757 fix various location-streaming issue, test passes now 2019-11-03 00:21:27 +01:00
holger krekel
d74c70a57c fix and streamline location-outgoing pipeline 2019-11-03 00:21:27 +01:00
holger krekel
a6f0f78588 addresses #757 test and add location streaming python api 2019-11-03 00:21:27 +01:00
B. Petersen
e6d9991581 fix conversion from nanoseconds to milliseconds
1_000_000 nanosecond = 1 millisecond
2019-11-02 15:22:27 +01:00
B. Petersen
ec8dbddcfb tweak ffi documentation wrt certificate checks 2019-11-01 18:06:01 +01:00
B. Petersen
bc699f17d9 avoid usage of get_subtitle() in repl-tool, remove dc_chat_get_subtitle() from documentation. 2019-11-01 13:19:47 +01:00
B. Petersen
832df41130 target comments of @hpk42 and @flub 2019-11-01 00:46:48 +01:00
B. Petersen
5709681076 streamline as_path_unicode(), delete unused as_str() and as_str_safe() 2019-11-01 00:46:48 +01:00
B. Petersen
858baf0c2c prefer to_opt_string_lossy() over as_opt_str() as the latter pancis on non-wellformatted utf-8, delete as_opt_str() 2019-11-01 00:46:48 +01:00
B. Petersen
e4b3e23769 prefer to_string_lossy() over as_str() as the latter pancis on non-wellformatted utf-8 2019-11-01 00:46:48 +01:00
holger krekel
8ce05796da ensure that especially qr tests are rerun 2019-10-31 23:31:09 +01:00
holger krekel
7f8c6d8cca some more display refinements 2019-10-31 23:31:09 +01:00
holger krekel
75ba040531 more error logging with file operations in general 2019-10-31 23:31:09 +01:00
Floris Bruynooghe
faa78e1c04 At least log the error 2019-10-31 22:38:32 +01:00
Floris Bruynooghe
b264d3be3c Be more accepting in creating blobs from existing names
This is an additional fix for #768 aka
commit eac8ad8369
2019-10-31 20:05:09 +01:00
holger krekel
80d7e84e5d prepare 1.0.0-beta.6 2019-10-31 12:34:03 +01:00
B. Petersen
4e37610f21 fix boolean error in chatlist::get_msg_id()
the error led to unusable contact requests,
at least on android and ios (probably also desktop)
because msg_id=dc_chatlist_get_msg_id() always returns 0
and create_chat_by_msg_id(msg_id)
or dc_marknoticed_contact(<get sender from msg_id>)
failed therefore.
2019-10-31 12:31:31 +01:00
Floris Bruynooghe
78030e4a31 Add test for #767
Without a test for this fix, a regression will happen again.

Original PR #767 aka commit 4fed875107
2019-10-30 21:08:25 +01:00
holger krekel
b01c842d7c bump version in toml's and changelog, and trigger deltachat-specific "cargo update". example: python set_core_version.py 1.0.0-beta.6 automates bumping the version and performs some quick sanity checks 2019-10-30 20:05:01 +01:00
holger krekel
c56c10bced remove unneccessary check of is_special() + cleanups 2019-10-30 19:29:13 +01:00
holger krekel
b0ccbc36d9 fix FFI-behaviour: return default empty messages when asked for special ones 2019-10-30 19:29:13 +01:00
holger krekel
9cdfc3409d systematically ignore invalid message ids when passed in through CFFI 2019-10-30 19:29:13 +01:00
björn petersen
fc851f542a Merge pull request #770 from deltachat/tweak-ffi
remove unneeded const attribtute
2019-10-30 19:12:43 +01:00
B. Petersen
7530abd581 remove unneeded const attribtute 2019-10-30 16:36:28 +01:00
holger krekel
a6594a9ae3 add changelog and bump to beta.4 2019-10-30 16:13:34 +01:00
B. Petersen
62019f57e9 fix some doxygen links and overviews 2019-10-30 16:09:04 +01:00
holger krekel
41443bb7f9 fix sending of autocrypt setup message 2019-10-30 15:48:06 +01:00
B. Petersen
8b5f7d98f6 do not escalate attemt to add self to a group to the user, just return false from add_contact_to_chat() 2019-10-30 14:03:54 +01:00
B. Petersen
6fea6f730d fix recognition of mailto-address-qr-codes, add tests 2019-10-30 13:13:02 +01:00
holger krekel
ad42a39a43 amend changelog 2019-10-30 13:12:00 +01:00
B. Petersen
ed9cfedbf3 update changelog 2019-10-30 13:12:00 +01:00
holger krekel
36510d8451 update links / use deltachat/rust-imap master branch 2019-10-30 13:07:44 +01:00
björn petersen
501a6eee69 Merge pull request #765 from deltachat/fix-qr2
fix plus-space-decoding in qr-code
2019-10-30 10:16:57 +01:00
B. Petersen
39cd8465f4 allow plus-space-encoding in qr-code, adapt tests 2019-10-30 00:37:35 +01:00
holger krekel
d3c0d2ebb1 bump version 2019-10-29 22:38:25 +01:00
holger krekel
911c0e45dc expose empty server functionality and test it (also introducing a new DC_EVENT_IMAP_FOLDER_EMPTIED event) 2019-10-29 22:19:13 +01:00
holger krekel
7628ee1e05 rust-part of empty_server 2019-10-29 22:19:13 +01:00
holger krekel
de3e5e1c39 fix deadlock issue with config access 2019-10-29 16:08:24 +01:00
B. Petersen
27627b4f74 show better error message for a simple 'bad credentials' error and give some more hints for other errors 2019-10-29 16:08:24 +01:00
B. Petersen
469f8ac31d make stock_string_repl_str2() public as the other members 2019-10-29 16:08:24 +01:00
Floris Bruynooghe
c8d296ea0e A MsgId newtype
This more strongly types the ubiquitous message id type by no longer
making it an integer.  It keeps the actual ID opaque.  Only for the
generic job API the number keeps being used.  Some locations also need
to create it from an integer and call MsgId::new().
2019-10-29 15:30:53 +01:00
holger krekel
c6adbe939d use latest rust-imap fork commits from @dignifiedquire 2019-10-28 20:51:17 +01:00
holger krekel
b4464ab0a3 address @dignifiedquire comments 2019-10-28 20:51:17 +01:00
holger krekel
bf7d57c560 update rust-imap 2019-10-28 20:51:17 +01:00
holger krekel
0e59819af4 use latest rust-imap fork 2019-10-28 20:51:17 +01:00
holger krekel
1cc4f56025 make imap-idle survive disconnects (during and at the beginning of an app) 2019-10-28 20:51:17 +01:00
holger krekel
1d03e0822e seems to work 2019-10-28 20:51:17 +01:00
björn petersen
b722da642a Merge pull request #750 from deltachat/tweak-summary
in summary, show hyphen only if there is a type and a text
2019-10-27 15:59:59 +01:00
Alexander Krotov
0aa1d1caa0 Merge pull request #749 from deltachat/fix-get-chat-id
let dc_get_chat_id_by_contact_id() returns 0 if no chat is found
2019-10-27 12:42:22 +00:00
B. Petersen
da28e1dd44 address comment of @flub and add some tests 2019-10-27 13:32:06 +01:00
B. Petersen
d223a286c0 in summary, show hyphen only if there is a type and a text; this avoids summaries as 'Voice message -' 2019-10-27 13:31:52 +01:00
Alexander Krotov
7916a7fa07 Fix spelling of Param::GuaranteeE2ee 2019-10-27 11:51:59 +01:00
Alexander Krotov
ee81895e1e Use DC_CONTACT_ID_SELF in do_initiate_key_transfer 2019-10-27 11:51:47 +01:00
Alexander Krotov
6ac4384769 location.rs cleanup
Use constants where possible, move "let" closer to assignments.
2019-10-27 11:51:35 +01:00
Alexander Krotov
99fababf0b to_base64: operate on characters instead of bytes to avoid unsafe code 2019-10-27 11:51:25 +01:00
Alexander Krotov
c85f1b20ca Add constants for certificate checks configuration 2019-10-27 11:51:14 +01:00
B. Petersen
51f43842cf cargo fmt 2019-10-27 11:42:56 +01:00
B. Petersen
8015ba1d64 dc_get_chat_id_by_contact_id() returns 0 if no chat is found.
this is no error;
in fact, the function is used to probe
if there is a chat with a given contact at several places
eg. in the android-ui.
2019-10-26 18:37:33 +02:00
Alexander Krotov
cfa69cf35a Add Params::set_cmd and use SystemMessage constants 2019-10-26 14:04:08 +02:00
B. Petersen
dced1932b3 if show_emails=ALL, show belonging contact-requests directly in the chatlist 2019-10-24 11:12:35 +02:00
B. Petersen
79a08f96c5 make ShowEmails an enum, use constant for trash 2019-10-24 11:12:35 +02:00
björn petersen
f5d98c1db6 Merge pull request #742 from deltachat/add-self-to-group
allow adding SELF to group
2019-10-23 22:13:25 +02:00
B. Petersen
df4273e986 fix logic error: adding a member to a group is okay if a real contact exists or for SELF 2019-10-23 14:03:42 +02:00
Floris Bruynooghe
5d79690260 Add Params::get_file(), ::get_path() and ::get_blob()
Turns out that anyone that uses these either justs wants a file or
wants a blob.  Consolidate those patterns into one place and simplify
all the callers.
2019-10-22 18:54:09 +02:00
Floris Bruynooghe
6c9e16d31a Introduce a BlobObject type for blobs
This creates a specific type for blobs, with well defined conversions
at the borders.  It also introduces a strong type for the Param::File
value since that param is often used used by the public API to set
filenames using absolute paths, but then core changes the param to a
blob before it gets to the database.

This eliminates a few more functions with very mallable C-like
arguments behaviour which combine a number of operations in one.
Because blob filenames are stored so often in arbitrary strings this
does add more code when receiving those, until the storage is fixed.

File name sanitisation is now deletated to the sanitize-filename crate
which should do a slightly better job at this.
2019-10-22 18:54:09 +02:00
B. Petersen
f0fc50d5a9 adapt to reality 2019-10-22 18:37:47 +02:00
björn petersen
7a4a4389fa Merge pull request #739 from deltachat/location
Rustify location.rs
2019-10-22 18:01:35 +02:00
holger krekel
131889cdfb add beta2 changelog, bump version to 1.0.0-beta.2 2019-10-22 17:50:23 +02:00
Alexander Krotov
bed14d5c02 Initialize continue_streaming with false
Otherwise this variable is constant.
2019-10-22 13:24:23 +03:00
Alexander Krotov
d3c831a0a2 Replace continue_streaming int with bool 2019-10-22 13:24:23 +03:00
Alexander Krotov
0007c12dea Replace FORCE_SCHEDULE #define from C core with bool 2019-10-22 13:24:23 +03:00
B. Petersen
049077f13b reconnect on io errors and broken pipes 2019-10-22 09:58:05 +02:00
holger krekel
e17c69f89c actually try connecting, instead of just preparing the connect 2019-10-21 23:17:18 +02:00
holger krekel
4b24f32d6c add tests and API for is_forwarded 2019-10-21 23:00:42 +02:00
Friedel Ziegelmayer
f404e31e30 chore(deps): switch back to rust-imap master (#735)
chore(deps): switch back to rust-imap master
2019-10-21 18:48:50 +02:00
dignifiedquire
7455b26ab2 chore(deps): switch back to rust-imap master 2019-10-21 16:52:43 +02:00
holger krekel
ee3259a74d fix rust-imap dep and remove Xargo.lock -- or is the latter used for anything? 2019-10-21 11:14:38 +02:00
dignifiedquire
391ba67ad5 update to fixed rust-imap for yandex 2019-10-20 13:10:17 +02:00
dignifiedquire
54f8c68151 switch to master 2019-10-20 13:10:17 +02:00
dignifiedquire
4a2e1897a6 fix(smtp): use correct auth mechanisms 2019-10-20 13:10:17 +02:00
dignifiedquire
076616bfb9 fix(imap): read server greeting 2019-10-20 13:10:17 +02:00
Dmitry Bogatov
a9dd78f622 Narrow return type of location::set: cint -> bool 2019-10-19 22:38:43 +02:00
B. Petersen
d16bdafaf0 simplify calling Simplifier, get is_forwarded flag correctly 2019-10-19 22:34:14 +02:00
B. Petersen
4f126c5292 show forwarded-state in repl-tool 2019-10-19 22:34:14 +02:00
B. Petersen
7b958a20fd prefer to_string_lossy() over as_str() as the latter pancis on non-wellformatted utf-8 2019-10-19 22:30:25 +02:00
B. Petersen
4519071718 prefer to_opt_string_lossy() over as_opt_str() as the latter pancis on non-wellformatted utf-8 2019-10-19 22:30:25 +02:00
B. Petersen
0108b4724e add function to convert NULL-able c-string to Option<String> 2019-10-19 22:30:25 +02:00
B. Petersen
bb08b39c71 remove duplicated code 2019-10-19 22:30:25 +02:00
B. Petersen
1908ac428b mark own forwarded messages as such 2019-10-17 19:41:17 +02:00
Alexander Krotov
dfc453c1d1 Merge pull request #727 from deltachat/strndup
Remove unused strndup
2019-10-17 12:33:51 +03:00
Alexander Krotov
9fa6289093 Remove unused strndup 2019-10-17 03:18:08 +03:00
björn petersen
6f92ce0fa8 Merge pull request #722 from deltachat/oauth2-even-better
make oauth2.get_addr work
2019-10-16 10:58:04 +02:00
B. Petersen
cde2c9137f make oauth2.get_addr work
oauth2.get_addr is used for gmail-oauth2
to retrieve the address really authorized in the oauth2 process.
2019-10-16 00:57:58 +02:00
björn petersen
120524ae00 Merge pull request #720 from deltachat/make-oauth2-better
make oauth2 POST successfully
2019-10-15 18:29:27 +02:00
holger krekel
7bb73f45a5 add LP smtp/imap connection flags to const 2019-10-15 13:46:39 +02:00
Dmitry Bogatov
2d0f563dfe Replace magic constant with symbolic name 2019-10-15 13:45:08 +02:00
VP-
cfe3c69f00 Don't use hard-coded email addresses in tests 2019-10-15 13:10:55 +02:00
B. Petersen
c266d2ca0d use POST instead GET to init or refresh oauth2 tokens 2019-10-15 00:22:37 +02:00
B. Petersen
85fc696975 more specific and distinguishable oauth2 error message 2019-10-15 00:20:08 +02:00
Alexander Krotov
9bf8bed0c3 Merge pull request #685 from deltachat/outlook_unit_test
Unit test Outlook autodiscovery
2019-10-14 21:34:43 +00:00
Alexander Krotov
c4d55f6ba4 auto_outlook: convert <Protocol> type to lowercase before comparison 2019-10-14 19:21:29 +03:00
B. Petersen
766d7cbd3a add some missing comments 2019-10-14 15:11:33 +02:00
Alexander Krotov
8e0e1bd58d Add test for Outlook autodiscovery without redirect 2019-10-14 13:42:35 +03:00
Alexander Krotov
a471ccc95a Test Outlook autoconfigure redirect parsing 2019-10-14 01:24:20 +03:00
Alexander Krotov
daac8c4824 rustfmt 2019-10-14 01:24:20 +03:00
Alexander Krotov
59c22a5626 Move Outlook autodiscovery into separate function 2019-10-14 01:24:19 +03:00
Alexander Krotov
5154f27f72 Merge pull request #712 from deltachat/moz_autoconfigure_unit
auto_mozilla: split XML parsing into separate function
2019-10-13 16:34:05 +00:00
Alexander Krotov
5f7279eb85 auto_mozilla: server is only configured if the type matches
This fixes the testcase introduced in previous commit
2019-10-12 17:07:20 +03:00
Alexander Krotov
ce67f593f6 Add XML parsing test for Mozilla autoconfig 2019-10-12 17:07:20 +03:00
Alexander Krotov
556ea57f37 auto_mozilla: split XML parsing into separate function 2019-10-12 17:07:20 +03:00
Dmitry Bogatov
a4257b619a Replace flags argument to Sql.open with single bool
Previously, "flags" argument of Sql.open was of type libc::c_int, but
only one bit was used: whether to open database read-only. This commit
makes it explicit by changing type to bool and renaming argument.
2019-10-12 09:56:18 +02:00
Alexander Krotov
8479c8afbf Do not convert 1 to libc::c_int
It is compared to u32 in this case
2019-10-11 21:52:47 +02:00
Alexander Krotov
eba012b965 Do not use libc::c_int in Simplify 2019-10-11 21:52:47 +02:00
Alexander Krotov
66e53e6804 Return bool from Imap::fetch()
It is unused anyway
2019-10-11 21:52:47 +02:00
Alexander Krotov
c8aa8b55f6 Use i32 instead of libc::c_int for try_again
It is already declared as i32 in the structure
2019-10-11 21:52:47 +02:00
Alexander Krotov
900e3905c0 Count number of libc references in top_evil_rs.py 2019-10-11 21:52:47 +02:00
holger krekel
088490721d bump to 1.0.0-beta.1, add initial Changelog 2019-10-10 19:08:47 +02:00
björn petersen
a40b99aae0 Merge pull request #709 from deltachat/fix/features
fix: disable default features for pgp, by default
2019-10-10 18:32:56 +02:00
dignifiedquire
b9646446f8 fix: disable default features for pgp, by default 2019-10-10 18:05:11 +02:00
holger krekel
4e36b35039 cargo-fmt only 2019-10-10 12:01:01 +02:00
holger krekel
d412ee6042 make dc_str_from_clist safe and return a string instead of c-string -- this allows to remove some unsafe and now unneccessary cleanup code 2019-10-10 12:01:01 +02:00
B. Petersen
67848e3333 use libc::c_char as i8 and u8 are used differently on x86 and arm 2019-10-10 08:44:31 +02:00
björn petersen
4d79c6e235 Merge pull request #705 from deltachat/safer-strn
len-check in strncasecmp()
2019-10-10 01:19:09 +02:00
B. Petersen
bc99d9d196 cargo fmt 2019-10-10 00:39:35 +02:00
B. Petersen
e1fc5863c2 target comments of @hpk42 2019-10-10 00:29:52 +02:00
B. Petersen
f0791149e6 document dc_str_unref() 2019-10-10 00:29:52 +02:00
B. Petersen
297b032bdc fix doxygen, some rewording 2019-10-10 00:29:52 +02:00
holger krekel
98180c175d make sure c-doc upload dirs exist even if branch name has / in it 2019-10-10 00:23:36 +02:00
dignifiedquire
46e8a436cb updates and fixes 2019-10-10 00:23:36 +02:00
dignifiedquire
dc2cf8ecfc unify naming in pgp 2019-10-10 00:23:36 +02:00
dignifiedquire
fd69ebfd1f fix and cleanup tests 2019-10-10 00:23:36 +02:00
dignifiedquire
03979fdc51 wip 2019-10-10 00:23:36 +02:00
B. Petersen
2c98e91276 target comments of @link2xt 2019-10-10 00:22:50 +02:00
B. Petersen
3270120d16 strncasecmp() compares the given number of characters but not after a 0-byte 2019-10-09 19:00:54 +02:00
holger krekel
77a7efc920 fix #694 by forking lettre and avoiding extra NOOP Smtp commands for connection checking.
If this works out and fixes our problem, we can submit upstream.
2019-10-09 18:25:41 +02:00
björn petersen
de604e744e Merge pull request #703 from deltachat/fix-strncasecmp
fix(mmime): correct implementation of strncasecmp
2019-10-09 17:31:41 +02:00
holger krekel
24c0a833bd address @flub comments 2019-10-09 15:59:31 +02:00
holger krekel
45f011c63c expose and test set_stock_translation to Python 2019-10-09 15:59:31 +02:00
holger krekel
bc86201b44 error out if %1 %2 replacements are not contained in default english version 2019-10-09 15:59:31 +02:00
holger krekel
b82af9fff3 introduce set_stock_translation and remove call to DC_EVENT_GET_STRING 2019-10-09 15:59:31 +02:00
dignifiedquire
3a1e74a306 fix(mmime): correct implementation of strncasecmp
Closes #523
2019-10-09 14:27:15 +02:00
Alexander Krotov
e4cca92910 Simplify to_string_lossy()
Call .to_string_lossy() without trying .to_str() first.
2019-10-09 08:56:02 +02:00
holger krekel
102220834c address https://github.com/deltachat/deltachat-ios/issues/328 and don't escalate xml-parsing errors to users through error! 2019-10-08 23:09:03 +02:00
holger krekel
24d744b94c try generating and uploading doxygen docs (#697) 2019-10-08 18:52:13 +02:00
holger krekel
1df6229e99 remove/push down some unsafe-fn 2019-10-08 17:37:37 +02:00
Alexander Krotov
c23e98ff83 Merge pull request #693 from deltachat/mozautoconfig
Use Rust types in auto_mozilla.rs
2019-10-07 23:03:17 +00:00
holger krekel
b7c81f37c0 ok_to_continue is 0, now start counting "unsafe fn" ... 2019-10-07 23:58:32 +02:00
Alexander Krotov
5c3a7e4119 Use Rust types in auto_mozilla.rs 2019-10-07 18:43:53 +03:00
holger krekel
a94acef49b cargo fmt 2019-10-07 13:39:01 +02:00
holger krekel
7f5b362eda remove ok_to_continue and simplify/refactor imap and smtp connection trying
address @r10s review comments and also remove unneccessary "unsafe fn"
2019-10-07 13:39:01 +02:00
holger krekel
ba5b3ad675 fix #690 by avoiding account.__del__ and registering/unregistering with atexit (a module that manages process/interpreter shutdown and calls into registered shutdown. Recommended way for user code still is to call account.shutdown() explcitely. 2019-10-07 12:24:50 +02:00
björn petersen
c1e4d1e7a4 Merge pull request #688 from deltachat/remove-predecessor
remove scanning for old Chat-Predecessor header
2019-10-07 02:02:15 +02:00
B. Petersen
dd8744b74e remove unused import 2019-10-07 01:15:42 +02:00
holger krekel
b775ecca08 fix tests 2019-10-07 00:55:33 +02:00
holger krekel
b8f211a013 cargo fmt 2019-10-07 00:55:33 +02:00
holger krekel
51534b2fae move all alloc/free/stop/has/shall_stop*ongoing* methods to context
introduce stop_ongoing to bindings and test for cancelation of configure
2019-10-07 00:55:33 +02:00
B. Petersen
710db2ba0a remove scanning for old Chat-Predecessor header
the Chat-Predecessor header was dropped about 10 month ago.
as a replacement, the standard headers are used.
2019-10-07 00:43:43 +02:00
holger krekel
32ef0d4dc3 cargo fmt 2019-10-07 00:22:19 +02:00
holger krekel
b3cd80ba6d remove ok_to_continue, shift code to a result-returning add_export_files to avoid if-branches, cleanup and simplify some logging 2019-10-07 00:22:19 +02:00
holger krekel
3f053f899e cargo fmt 2019-10-07 00:15:39 +02:00
holger krekel
22b4d1734c remove last ok_to_continue in mimeparser 2019-10-07 00:15:39 +02:00
björn petersen
46a71e81a0 Merge pull request #683 from deltachat/outlook_none
Fix and refactor Outlook autodiscovery
2019-10-06 15:54:01 +02:00
Alexander Krotov
b4851187ba Safe outlk_autodiscover 2019-10-06 05:46:45 +03:00
Alexander Krotov
0252969f7e Remove unused in_0 from outlk_autodiscover_t 2019-10-06 05:16:17 +03:00
Alexander Krotov
f86cec4844 Mark moz_autoconfigure as safe 2019-10-06 05:16:17 +03:00
Alexander Krotov
cd2e36da92 rustfmt 2019-10-06 05:16:17 +03:00
Alexander Krotov
1b13107181 Return Option<String> from read_autoconf_file 2019-10-06 05:16:17 +03:00
Alexander Krotov
6fbde21995 outlk_autodiscover: store url as String 2019-10-06 05:16:17 +03:00
Alexander Krotov
a6608513ac auto_outlook: restore and add some comments 2019-10-06 05:16:17 +03:00
Alexander Krotov
d43c225be3 Return None from outlk_autodiscover if no XML config was parsed 2019-10-06 05:16:17 +03:00
B. Petersen
1802d7658d add some tests for dc_create_outgoing_rfc724_mid()
as noticed in the last attempts to change the prefix,
this test is missing (as probably many more :)
2019-10-05 22:48:00 +02:00
B. Petersen
275f5d713f cargo fmt 2019-10-05 22:47:39 +02:00
B. Petersen
e40cfeec58 bubble up sql, get_abs_path errors 2019-10-05 22:47:39 +02:00
B. Petersen
275b4b8d36 bubble up sql-errors in reveive_imf() 2019-10-05 22:47:39 +02:00
B. Petersen
77cef632c7 bubble up update_param() error
bubble up the error instead of ignoring it -
but also do not panic as before #673
2019-10-05 22:47:39 +02:00
B. Petersen
db2064de14 use .to_string_lossy() member of Path, CStr etc.
instead of calling to_str().unwrap_or_default().
2019-10-05 22:46:29 +02:00
B. Petersen
e251c7b1c8 redo some c-comments 2019-10-05 15:53:18 +02:00
björn petersen
2fe98775f9 Merge pull request #673 from deltachat/dont-panic
Don't Panic!
2019-10-05 15:08:15 +02:00
björn petersen
187179d87b Merge pull request #674 from deltachat/to-lossy
always expect bad c-strings
2019-10-05 15:04:32 +02:00
holger krekel
dd03f6e8af test and fix that a group with mixed-encryption members (with some we can encrypt, others not) does not break 2019-10-05 15:02:03 +02:00
holger krekel
07b32241bd try fix #677 (this fixes only a test) -- seems like we passed pointers into Rust strings to code that was expecting null-terminated strings. Now do proper "strdup()" and free. The real proper fix is to make dc_split_armored_string into a rust-string function and do away with all c-pointers. 2019-10-05 15:01:28 +02:00
B. Petersen
abfff96cd4 cargo fmt 2019-10-05 12:44:08 +02:00
B. Petersen
735bdd1c20 always use to_string_lossy() for converting c-strings to String
the function to_string() is removed;
c-strings may always be badly formatted and this should never lead to a panic.
2019-10-05 00:12:32 +02:00
B. Petersen
9cae075b6f target detailed checks of @flub 2019-10-04 23:07:34 +02:00
B. Petersen
2317518e5e cargo fmt 2019-10-04 23:07:34 +02:00
B. Petersen
477af413c6 if in doubt, prefer unwrap_or_default()
if the past we had lots of crashes because of unexpected unwrap failures,
mostly related to string.
this commit avoids them eg. for string-conversions that may panic
eg. when encountering a null-byte or by logical programming errors
where an object is assumed to be set but is not under unexpected circumstances.
2019-10-04 23:07:33 +02:00
B. Petersen
93f0f5ccae rename sql-config-setters to set_raw_config*()
the rename is reasonable as the getter is called get_raw_config*()
and to make the functional difference to context.set|get_config() clearer.
2019-10-04 11:18:26 +02:00
B. Petersen
79b92727cc prefer get_config_bool() where appropriate
for db input/output, we still use get_config_int() to convert to/from 0/1.
also for info() we prefer get_config_int() to show the real value.
2019-10-04 11:15:11 +02:00
björn petersen
8dfd04672f Merge pull request #670 from deltachat/tweak-config
tweak config
2019-10-03 21:19:45 +02:00
holger krekel
603761e4b7 add DC_EVENT_DELETED_BLOB_FILE 2019-10-03 18:01:56 +02:00
holger krekel
467c09f491 introduce DC_EVENT_NEW_BLOB_FILE event and test for it 2019-10-03 18:01:56 +02:00
B. Petersen
a953b494cb cargo fmt 2019-10-03 17:41:09 +02:00
B. Petersen
dca9afa10b rename sql.get_config() to sql.get_raw_config() to make clearer that there is not default handling 2019-10-03 17:35:43 +02:00
B. Petersen
23d2d87c24 do not panic on missing or wrong formatted values in the database 2019-10-03 17:15:40 +02:00
B. Petersen
c6b2d640ae prefer context.get_config() over context.sql.get_config() as the latter does not handle default values. 2019-10-03 17:15:40 +02:00
björn petersen
b4b8a1d15b Merge pull request #667 from deltachat/resilient-writes
more resilient database writes
2019-10-03 14:48:21 +02:00
Alexander Krotov
130d485cac Fix some clippy warnings 2019-10-03 14:30:40 +02:00
B. Petersen
d5b92744ed increase number of simultan database-connections, wait for write if another thread writes
- increase the number of simultan database-connections handled by the r2d2 pool.
currently we have already at least 5 threads threads,
but also the ui may call from any thread.
- the busy-timeout for all connections is set to 10 seconds.
this means, if a connection-A wants to write,
but connection B-is already writing,
connection-A waits multiple times a few ms and tries over.
this is repeated until the 10 seconds are accumulated.
2019-10-03 14:09:43 +02:00
Alexander Krotov
a5c4e16405 Merge pull request #647 from deltachat/tls_checks_config
Add certificate check configuration options
2019-10-02 23:21:03 +00:00
Alexander Krotov
216266d7bf Apply imap_certificate_checks config to StartTLS connections 2019-10-03 02:02:51 +03:00
Alexander Krotov
bf1652a1be Move common code for IMAP and SMTP to login_param.rs 2019-10-03 01:26:38 +03:00
Alexander Krotov
f93f3d6012 Do not set minimal TLS version for SMTP manually 2019-10-03 01:26:07 +03:00
Alexander Krotov
41806f86ba Return certificate check information from get_info() 2019-10-03 01:26:07 +03:00
Alexander Krotov
59df97944f Enable strict certificate checks for test online accounts 2019-10-03 01:26:07 +03:00
Alexander Krotov
468651534e Manually specify values for CertificateChecks enum
This is what we are doing in constants.rs
2019-10-03 01:26:07 +03:00
Alexander Krotov
6343ae8161 Rename {mail,send}_certificate_checks into {imap,smtp}_certificate_checks 2019-10-03 01:26:07 +03:00
Alexander Krotov
641bd5eb15 Write configured_{mail,send}_certificate_checks to database 2019-10-03 01:26:07 +03:00
Alexander Krotov
063d989225 Use mail_certificate_checks configuration in imap.rs 2019-10-03 01:26:07 +03:00
Alexander Krotov
b8ca7b1591 Add CertificateChecks::Automatic option and make it default
It is the same as AcceptInvalidCertificates for now,
but can be replaced with better heuristics later,
such as a database of known providers or TOFU.
2019-10-03 01:25:19 +03:00
Alexander Krotov
e222f49c9d Use send_certificate_checks configuration 2019-10-03 01:25:19 +03:00
Alexander Krotov
297bc635e8 Add certificate check configuration options 2019-10-03 01:25:19 +03:00
holger krekel
230c65594c add test for markseen not sending out smtp on second call 2019-10-02 23:51:14 +02:00
holger krekel
509a21ff05 introduce DC_IMAP_MESSAGE_MOVED event and try harder to send out MDNs 2019-10-02 23:51:14 +02:00
holger krekel
96066712bd add and test for DC_IMAP_MESSAGE_DELETED event 2019-10-02 23:51:14 +02:00
holger krekel
d83aa1e898 perform heuristic moves only if a job is not delete 2019-10-02 23:51:14 +02:00
holger krekel
f0a7bdb6d6 properly parse message ids in imap prefetch 2019-10-02 23:51:14 +02:00
holger krekel
9c077c98cd remove MdnSent logic 2019-10-02 23:51:14 +02:00
holger krekel
6dc45642b7 rework API 2019-10-02 23:51:14 +02:00
holger krekel
5c1b9c83f7 fixes #661 and also contains a cleanup of config access (only for e2ee and mdns) 2019-10-02 22:51:09 +02:00
holger krekel
92438737c9 - introduce and test BccSelf config, to prevent bcc-self on outgoing mails
- introduce context.get_config_int() which respects default values
  declared in config.rs (Config)
2019-10-02 22:51:09 +02:00
holger krekel
489cdd1b24 - resultify send-out pipeline for better error reporting
- early ignore sending out smtp messages with no recipients
2019-10-02 22:51:09 +02:00
holger krekel
3f7995a7ea start with some refactoring of the outgoing pipeline 2019-10-02 22:51:09 +02:00
Alexander Krotov
f7ad93229d test_account.py: fix a typo in test message 2019-10-02 13:10:16 +02:00
Alexander Krotov
555b4bc8c7 python/README.rst: s/mail_pwd/mail_pw/
mail_pw is the option that is actually read by the tests
2019-10-02 13:09:45 +02:00
björn petersen
75f41bcb90 Merge pull request #658 from deltachat/fix-contact-requests
move contact-requests to the beginning of chatlists
2019-10-01 13:29:44 +02:00
B. Petersen
97e1fbc198 move contact-requests to beginning of chatlists
contact-requests of non-blocked senders are shown in the chatlist,
so that the user gets aware of them without opening the contact-request-chat.
however, for that, the contact-request virtual-chat-id
must be added to the beginning of the list,
otherwise it won't get noticed by the user.
2019-10-01 12:50:31 +02:00
holger krekel
ee6d16f1b1 on some call sites: peerstate.save_to_db() should bubble up errors instead of crashing.
also write a test that double-creation of an addr-row is fine.
2019-10-01 10:34:36 +02:00
björn petersen
22d2097132 log all database-migration actions (#654) 2019-09-30 20:56:19 +02:00
björn petersen
c376de9b5e add sticker type (#653)
* add sticker type

this pr adds the message type 'sticker'.
stickers are handled as normal images
but tagged with the header `Chat-Content: sticker`
it's up to the ui to render these stickers appropriate.

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

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

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

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

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

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

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

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

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

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

This change replaces "flags" argument with single boolean argument
"regenerate".
2019-09-18 19:29:39 +02:00
dignifiedquire
8667de994e use proper Result instead of Option for encryption and decryption 2019-09-18 19:21:41 +02:00
holger krekel
cee0e22ce7 fix #538 -- don't crash on wrong setup codes for ac-message, don't use "expect(), added test 2019-09-18 19:21:41 +02:00
holger krekel
dc8a2f54e5 use utf8 lossy strings for saved mime headers (as discussed on PR and IRC with @r10s) 2019-09-18 16:47:04 +02:00
dignifiedquire
b3bc5b2520 fix(receive_imf): do not attempt to convert raw body to utf8 2019-09-18 16:47:04 +02:00
B. Petersen
00e929afac reformat: use shorter lines (max. 80 chats) and try to add linebreaks semantically 2019-09-18 15:20:34 +02:00
Dmitry Bogatov
8a3bf6a5d9 Remove use of `dbg!' macro
According to documentation[^1], this macro should not be in version
control, and should only be used for debugging.

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

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

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

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

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

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

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

 * src/message.rs(dc_msg_get_setupcodebegin): ditto

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

Note, that logic is changed slightly: if get(Param::File) returns
Some(""), it no longer triggers "missing filename warnings".
2019-09-04 16:44:51 +02:00
Alexander Krotov
46520edd87 Print error if CAPABILITY command fails. 2019-09-04 15:12:00 +02:00
Simon Laux
14daa99802 remove ok_to_continue7 comment 2019-09-04 15:07:45 +02:00
Simon Laux
4f9f67a477 switch to while instead of loop 2019-09-04 15:07:45 +02:00
Simon Laux
85f182067c typo fixes in comments 2019-09-04 15:07:45 +02:00
Simon Laux
66ab6874f8 reorganize dc_job_do_DC_JOB_CONFIGURE_IMAP 2019-09-04 15:07:45 +02:00
Simon Laux
906b901e3d move function order / filestructure like in c core 2019-09-04 15:07:45 +02:00
Alexander Krotov
65adff4bdd Merge pull request #437 from KAction/spelling
Fix minor spelling errors in python/README.rst
2019-09-04 10:55:58 +00:00
Dmitry Bogatov
e60fc0dc30 Fix minor spelling errors in python/README.rst 2019-09-04 06:19:21 +00:00
Alexander Krotov
ffd719962c Merge pull request #430 from deltachat/latest-imap
Update to the latest rust-imap
2019-09-03 17:12:12 +00:00
dignifiedquire
1a1f0c0a7c refactor(e2ee): rename dc_e2ee -> e2ee 2019-09-03 19:05:21 +02:00
dignifiedquire
3944592c09 refactor(e2ee): restructure types a and method slightly 2019-09-03 19:05:21 +02:00
holger krekel
a5f862a564 remove python2 testing 2019-09-03 19:04:09 +02:00
Alexander Krotov
489f25940f Update to the latest rust-imap
It uses imap-proto 0.8.1 instead of outdated 0.7.0.
2019-09-03 19:14:56 +03:00
Dmitry Bogatov
6288909481 Refactor is_known_rfc724_mid() to use Iterator interface 2019-09-03 16:08:52 +02:00
Dmitry Bogatov
c95f134963 Change return type of is_known_rfc724_mid_in_list() to bool 2019-09-03 16:08:52 +02:00
Simon Laux
024c2883c0 cargo fmt 2019-09-03 15:32:36 +02:00
Simon Laux
d7b0ecad75 transform current_block to ok_to_continue 2019-09-03 15:32:36 +02:00
B. Petersen
50947b81c0 set correct default value for inbox_watch 2019-09-03 15:31:17 +02:00
holger krekel
e5a1b721f1 try use py36 for uploads (#429)
* try use py36 for uploads

* another try

* another
2019-09-03 15:30:28 +02:00
Alexander Krotov
aeb1a88e7a Remove dc_move.rs
and move dc_do_heuristics_moves to context.rs
2019-09-03 13:39:14 +02:00
Alexander Krotov
c406675d6a Mark dc_do_heuristics_moves as safe 2019-09-03 13:39:14 +02:00
Alexander Krotov
1ec193991b Make dc_token.rs safe 2019-09-02 20:57:22 +02:00
Simon Laux
1d09d2f0d1 cargo fmt 2019-09-02 18:00:23 +02:00
Simon Laux
5b993f601f transform current_block to ok_to_continue 2019-09-02 18:00:23 +02:00
Alexander Krotov
77be6c4294 Merge pull request #424 from link2xt/chatlist_safe
Mark Chatlist::get_summary as safe
2019-09-02 11:45:12 +00:00
Alexander Krotov
1497f263dc Mark Chatlist::get_summary as safe 2019-09-02 13:00:04 +03:00
björn petersen
d6e2798f5a Merge pull request #416 from deltachat/as_u32
Convert DC_*_ID_* constants to u32
2019-09-02 10:22:18 +02:00
björn petersen
8e41037eb1 Merge pull request #422 from deltachat/raw_mime_assert
mailmime_get_mime_type: do not dereference null pointer
2019-09-02 10:20:36 +02:00
björn petersen
0bcd3bc94e Merge pull request #423 from KAction/fix-master2
unbreak master
2019-09-02 10:19:57 +02:00
Dmitry Bogatov
fb5d53226d Fix errorneous ffi check in dc_send_msg 2019-09-01 22:45:49 +00:00
Alexander Krotov
b5a9cc1380 Convert DC_*_ID_* constants to u32 2019-09-01 23:41:05 +03:00
Alexander Krotov
1ba7368c6d mailmime_get_mime_type: do not dereference null pointer
In C code there was a check for null pointer in reconcat_mime()
It did nothing if raw_mime was null.
Now the result is stored even when mailmime_get_mime_type is
explicitly called with null pointer.

This commit restores C core logic.

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

Previously, usage pattern was following:

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

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

* Change type of is_msgrmsg parameter to bool

* Narrow type of local variable in simplify_plain_text()

* Export less fields of `Simplify' record

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

* Refactor part of simplify_plain_text()

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

It simplifies code and allows removing one mutable local variable.

* Replace dc_split_into_lines with String.split()

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

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

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

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

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

* Make simplify_plain_text() safe

* Make Simplify.simplify return String, not pointer

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

* Make Simplify.simplify() safe

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

* Add tests for simplify utilities

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

* Update python/src/deltachat/cutil.py

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

* Update cutil.py

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

* use dc_truncate method in all places

* Fix tests and add docs to dc_truncate()

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

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

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

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

which is equivalent to

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

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

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

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

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

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

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


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

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

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

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

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

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

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

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

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

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

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

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

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

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

      Error: invalid digit found in string

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

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

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

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

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

from

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

to

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

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

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

    fix fmt

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

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

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

    left-over error logging

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

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

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

    fix(receive_imf): remove recursive sql call

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

    fix: blocked is an optional value

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

    fix(msg): handle optional in_reply_to

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

    fix(chat): remove recursive sql usage

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

    fix rust fmt

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

    use forked rusqlite

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

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

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

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

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

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

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

    fix linting

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

    fix some test flakyness

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

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

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

    formatting

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

    remove some print statements

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

    Merge branch 'master' into eventlogging

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

    print invalid strings

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

    cleanup

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

    reduce direc usage of CString

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

    audit use of to_cstring and fix ub

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

    fix(imap): body ptr lifetime

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

    remove debug

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

    fix some things, add more debugging statements

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

    fix python lint issues

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

    cargofmt

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

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

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

    remove print statements and fix a crash

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

    fix some merge issues

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

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

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

    Merge branch 'master' into eventlogging

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

    address @dignifiedquire comments

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

    remove spurious print

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

    address @dignifiedquire rustyness comment and fix changelog

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

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

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

    cleanup some parts, add comments

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

    wip-commit which passes all tests with proper finalization

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

    snapshot of my current debugging state

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

    more aggressively skip perform API when threads are closing

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

    intermediate wip commit

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

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

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

    fix std

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

    rustfmt

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

    remove some debugging

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

    fix test for events

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

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

View File

@@ -1,10 +1,12 @@
version: 2.1
executors:
default:
docker:
- image: filecoin/rust:latest
working_directory: /mnt/crate
doxygen:
docker:
- image: hrektts/doxygen
restore-workspace: &restore-workspace
attach_workspace:
@@ -13,7 +15,7 @@ restore-workspace: &restore-workspace
restore-cache: &restore-cache
restore_cache:
keys:
- cargo-v0-{{ checksum "rust-toolchain" }}-{{ checksum "Cargo.toml" }}-{{ checksum "Cargo.lock" }}-{{ arch }}
- cargo-v3-{{ checksum "rust-toolchain" }}-{{ checksum "Cargo.toml" }}-{{ checksum "Cargo.lock" }}-{{ arch }}
- repo-source-{{ .Branch }}-{{ .Revision }}
commands:
@@ -24,20 +26,9 @@ commands:
steps:
- *restore-workspace
- *restore-cache
- setup_remote_docker:
docker_layer_caching: true
# TODO: move into image
- run:
name: Install Docker client
command: |
set -x
VER="18.09.2"
curl -L -o /tmp/docker-$VER.tgz https://download.docker.com/linux/static/stable/x86_64/docker-$VER.tgz
tar -xz -C /tmp -f /tmp/docker-$VER.tgz
mv /tmp/docker/* /usr/bin
- run:
name: Test (<< parameters.target >>)
command: TARGET=<< parameters.target >> ci/run.sh
command: TARGET=<< parameters.target >> ci_scripts/run-rust-test.sh
no_output_timeout: 15m
jobs:
@@ -53,21 +44,23 @@ jobs:
command: cargo generate-lockfile
- restore_cache:
keys:
- cargo-v0-{{ 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
- run: rm -rf .git
# make sure this git repo doesn't grow too big
- run: git gc
- persist_to_workspace:
root: /mnt
paths:
- crate
- save_cache:
key: cargo-v0-{{ 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"
@@ -102,7 +95,7 @@ jobs:
- run: cargo fetch
- run:
name: Test
command: TARGET=x86_64-apple-darwin ci/run.sh
command: TARGET=x86_64-apple-darwin ci_scripts/run-rust-test.sh
test_x86_64-unknown-linux-gnu:
executor: default
@@ -123,42 +116,78 @@ jobs:
target: "aarch64-linux-android"
build_test_docs_wheel:
machine: True
build_doxygen:
executor: doxygen
steps:
- checkout
# - run: docker pull deltachat/doxygen
- run: docker pull deltachat/coredeps
- run:
name: build docs, run tests and build wheels
command: ci_scripts/ci_run.sh
environment:
TESTS: 1
DOCS: 1
- 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
- run: bash ci_scripts/run-doxygen.sh
- run: mkdir -p workspace/c-docs
- run: cp -av deltachat-ffi/{html,xml} workspace/c-docs/
- persist_to_workspace:
root: workspace
paths:
# - c-docs
- py-docs
- wheelhouse
- c-docs
# 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:
- 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
machine: true
steps:
- checkout
- attach_workspace:
at: workspace
- run: pyenv global 3.5.2
- run: ls -laR workspace
- run: ci_scripts/ci_upload.sh workspace/py-docs workspace/wheelhouse
- run: ci_scripts/ci_upload.sh workspace/py-docs workspace/wheelhouse workspace/c-docs
clippy:
executor: default
steps:
- *restore-workspace
- *restore-cache
- run:
name: Run cargo clippy
command: cargo clippy --all
workflows:
@@ -166,19 +195,29 @@ workflows:
test:
jobs:
- build_test_docs_wheel
- upload_docs_wheels:
requires:
- build_test_docs_wheel
- 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
# 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:

6
.gitattributes vendored
View File

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

7
.gitignore vendored
View File

@@ -16,5 +16,12 @@ python/.tox
*.egg-info
__pycache__
python/src/deltachat/capi*.so
python/.venv/
python/liveconfig*
# ignore doxgen generated files
deltachat-ffi/html
deltachat-ffi/xml
.rsynclist

182
CHANGELOG.md Normal file
View File

@@ -0,0 +1,182 @@
# Changelog
## next (pending)
- restructured (but not change) imap idle handling into own file. cc @link2xt
## 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
- fix display of messages that could not be decrypted #785
- fix smtp MAILER-DAEMON bug #786
- fix a logging of durations #783
- add more error logging #779
- do not panic on some bad utf-8 mime #776
## 1.0.0-beta.6
- fix chatlist.get_msg_id to return id, instead of wrongly erroring
## 1.0.0-beta.5
- fix dc_get_msg() to return empty messages when asked for special ones
## 1.0.0-beta.4
- fix more than one sending of autocrypt setup message
- fix recognition of mailto-address-qr-codes, add tests
- tune down error to warning when adding self to chat
## 1.0.0-beta.3
- add back `dc_empty_server()` #682
- if `show_emails` is set to `DC_SHOW_EMAILS_ALL`,
email-based contact requests are added to the chatlist directly
- fix IMAP hangs #717 and cleanups
- several rPGP fixes
- code streamlining and rustifications
## 1.0.0-beta.2
- https://c.delta.chat docs are now regenerated again through our CI
- several rPGP cleanups, security fixes and better multi-platform support
- reconnect on io errors and broken pipes (imap)
- probe SMTP with real connection not just setup
- various imap/smtp related fixes
- use to_string_lossy in most places instead of relying on valid utf-8
encodings
- rework, rustify and test autoconfig-reading and parsing
- some rustifications/boolifications of c-ints
## 1.0.0-beta.1
- first beta of the Delta Chat Rust core library. many fixes of crashes
and other issues compared to 1.0.0-alpha.5.
- Most code is now "rustified" and does not do manual memory allocation anymore.
- The `DC_EVENT_GET_STRING` event is not used anymore, removing the last
event where the core requested a return value from the event callback.
Please now use `dc_set_stock_translation()` API for core messages
to be properly localized.
- Deltachat FFI docs are automatically generated and available here:
https://c.delta.chat
- New events ImapMessageMoved and ImapMessageDeleted
For a full list of changes, please see our closed Pull Requests:
https://github.com/deltachat/deltachat-core-rust/pulls?q=is%3Apr+is%3Aclosed

2709
Cargo.lock generated

File diff suppressed because it is too large Load Diff

View File

@@ -1,31 +1,30 @@
[package]
name = "deltachat"
version = "1.0.0-alpha.3"
authors = ["dignifiedquire <dignifiedquire@gmail.com>"]
version = "1.0.0-beta.12"
authors = ["Delta Chat Developers (ML) <delta@codespeak.net>"]
edition = "2018"
license = "MPL"
[build-dependencies]
cc = "1.0.35"
pkg-config = "0.3"
[dependencies]
deltachat_derive = { path = "./deltachat_derive" }
libc = "0.2.51"
pgp = { version = "0.2", default-features = false }
hex = "0.3.2"
pgp = { git = "https://github.com/rpgp/rpgp", branch = "master", default-features = false }
hex = "0.4.0"
sha2 = "0.8.0"
rand = "0.6.5"
smallvec = "0.6.9"
reqwest = "0.9.15"
reqwest = { version = "0.9.15", default-features = false, features = ["rustls-tls"] }
num-derive = "0.2.5"
num-traits = "0.2.6"
native-tls = "0.2.3"
lettre = "0.9.0"
imap = "1.0.1"
mmime = "0.1.0"
base64 = "0.10"
lettre = { git = "https://github.com/deltachat/lettre", branch = "feat/mail" }
lettre_email = { git = "https://github.com/deltachat/lettre", branch = "feat/mail" }
async-imap = { git = "https://github.com/async-email/async-imap", branch="master" }
async-tls = "0.6"
async-std = { version = "1.0", features = ["unstable"] }
base64 = "0.11"
charset = "0.1"
percent-encoding = "1.0"
percent-encoding = "2.0"
serde = { version = "1.0", features = ["derive"] }
serde_json = "1.0"
chrono = "0.4.6"
@@ -33,23 +32,40 @@ failure = "0.1.5"
failure_derive = "0.1.5"
# TODO: make optional
rustyline = "4.1.0"
lazy_static = "1.3.0"
lazy_static = "1.4.0"
regex = "1.1.6"
rusqlite = { version = "0.19", features = ["bundled"] }
addr = "0.2.0"
r2d2_sqlite = "0.11.0"
rusqlite = { version = "0.20", features = ["bundled"] }
r2d2_sqlite = "0.12.0"
r2d2 = "0.8.5"
strum = "0.15.0"
strum_macros = "0.15.0"
strum = "0.16.0"
strum_macros = "0.16.0"
thread-local-object = "0.1.0"
backtrace = "0.3.33"
byteorder = "1.3.1"
itertools = "0.8.0"
image-meta = "0.1.0"
quick-xml = "0.15.0"
escaper = "0.1.0"
bitflags = "1.1.0"
debug_stub_derive = "0.3.0"
sanitize-filename = "0.2.1"
stop-token = { version = "0.1.1", features = ["unstable"] }
rustls = "0.16.0"
webpki-roots = "0.18.0"
webpki = "0.21.0"
mailparse = "0.10.1"
encoded-words = { git = "https://github.com/async-email/encoded-words", branch="master" }
[dev-dependencies]
tempfile = "3.0"
pretty_assertions = "0.6.1"
pretty_env_logger = "0.3.0"
proptest = "0.9.4"
[workspace]
members = [
"deltachat-ffi"
"deltachat-ffi",
"deltachat_derive",
]
[[example]]
@@ -63,6 +79,6 @@ path = "examples/repl/main.rs"
[features]
default = ["nightly", "ringbuf"]
vendored = ["native-tls/vendored", "reqwest/default-tls-vendored"]
vendored = []
nightly = ["pgp/nightly"]
ringbuf = ["pgp/ringbuf"]

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
@@ -63,6 +61,11 @@ Single#10: yourfriends@email.org [yourfriends@email.org]
Message sent.
```
If `yourfriend@email.org` uses DeltaChat, but does not receive message just
sent, it is advisable to check `Spam` folder. It is known that at least
`gmx.com` treat such test messages as spam, unless told otherwise with web
interface.
List messages when inside a chat:
```
@@ -84,6 +87,23 @@ $ 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
use the `--ignored` argument to the test binary (not to cargo itself):
```sh
$ cargo test -- --ignored
```
## Features
- `vendored`: When using Openssl for TLS, this bundles a vendored version.

View File

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

BIN
assets/icon-device.png Normal file

Binary file not shown.

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

View File

@@ -1,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

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

View File

@@ -7,24 +7,30 @@ fi
set -xe
#DOXYDOCDIR=${1:?directory where doxygen docs to be found}
PYDOCDIR=${1:?directory with python docs}
WHEELHOUSEDIR=${2:?directory with pre-built wheels}
DOXYDOCDIR=${3:?directory where doxygen docs to be found}
export BRANCH=${CIRCLE_BRANCH:?specify branch for uploading purposes}
# python docs to py.delta.chat
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
# 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" \
# "$DOXYDOCDIR/html/" \
# delta@py.delta.chat:build-c/${BRANCH}
# "$PYDOCDIR/html/" \
# delta@py.delta.chat:build/${BRANCH}
# C docs to c.delta.chat
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
@@ -33,15 +39,17 @@ echo -----------------------
# Bundle external shared libraries into the wheels
pushd $WHEELHOUSEDIR
pip install devpi-client
pip3 install devpi-client
devpi use https://m.devpi.net
devpi login dc --password $DEVPI_LOGIN
devpi use dc/$BRANCH || {
devpi index -c $BRANCH
devpi use dc/$BRANCH
N_BRANCH=${BRANCH//[\/]}
devpi use dc/$N_BRANCH || {
devpi index -c $N_BRANCH
devpi use dc/$N_BRANCH
}
devpi index $BRANCH bases=/root/pypi
devpi index $N_BRANCH bases=/root/pypi
devpi upload deltachat*.whl
popd

View File

@@ -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-04-19 -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-04-19-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

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

View File

@@ -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

7
ci_scripts/run-doxygen.sh Executable file
View File

@@ -0,0 +1,7 @@
#!/usr/bin/env bash
set -ex
cd deltachat-ffi
doxygen

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,8 +2,9 @@
set -ex
export RUST_TEST_THREADS=1
#export RUST_TEST_THREADS=1
export RUST_BACKTRACE=1
export RUSTFLAGS='--deny warnings'
export OPT="--target=$TARGET"
export OPT_RELEASE="--release ${OPT}"
export OPT_FFI_RELEASE="--manifest-path=deltachat-ffi/Cargo.toml --release"
@@ -30,16 +31,15 @@ fi
if [[ $NORUN == "1" ]]; then
export CARGO_SUBCMD="build"
else
export CARGO_SUBCMD="test"
export CARGO_SUBCMD="test --all"
export OPT="${OPT} "
export OPT_RELEASE="${OPT_RELEASE} "
export OPT_RELEASE_IGNORED="${OPT_RELEASE} -- --ignored"
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
# Build the ffi lib
$CARGO_CMD $CARGO_SUBCMD $OPT_FFI_RELEASE

View File

@@ -1,8 +1,8 @@
[package]
name = "deltachat_ffi"
version = "1.0.0-alpha.3"
version = "1.0.0-beta.12"
description = "Deltachat FFI"
authors = ["dignifiedquire <dignifiedquire@gmail.com>"]
authors = ["Delta Chat Developers (ML) <delta@codespeak.net>"]
edition = "2018"
readme = "README.md"
license = "MIT OR Apache-2.0"
@@ -16,8 +16,11 @@ crate-type = ["cdylib", "staticlib"]
[dependencies]
deltachat = { path = "../", default-features = false }
deltachat-provider-database = "0.2.1"
libc = "0.2"
human-panic = "1.0.1"
num-traits = "0.2.6"
failure = "0.1.6"
[features]
default = ["vendored", "nightly", "ringbuf"]

2423
deltachat-ffi/Doxyfile Normal file

File diff suppressed because it is too large Load Diff

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.9 KiB

View File

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

View File

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

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

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

File diff suppressed because it is too large Load Diff

View File

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

File diff suppressed because it is too large Load Diff

View File

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

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

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

View File

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

File diff suppressed because it is too large Load Diff

View File

@@ -14,19 +14,19 @@ extern crate lazy_static;
extern crate rusqlite;
use std::borrow::Cow::{self, Borrowed, Owned};
use std::io::{self, Write};
use std::path::Path;
use std::process::Command;
use std::sync::atomic::{AtomicBool, Ordering};
use std::sync::{Arc, Mutex, RwLock};
use deltachat::config;
use deltachat::constants::*;
use deltachat::configure::*;
use deltachat::context::*;
use deltachat::dc_configure::*;
use deltachat::dc_job::*;
use deltachat::dc_securejoin::*;
use deltachat::dc_tools::*;
use deltachat::job::*;
use deltachat::oauth2::*;
use deltachat::types::*;
use deltachat::x::*;
use deltachat::securejoin::*;
use deltachat::Event;
use rustyline::completion::{Completer, FilenameCompleter, Pair};
use rustyline::config::OutputStreamType;
use rustyline::error::ReadlineError;
@@ -41,96 +41,74 @@ use self::cmdline::*;
// Event Handler
unsafe extern "C" fn receive_event(
_context: &Context,
event: Event,
data1: uintptr_t,
data2: uintptr_t,
) -> uintptr_t {
fn receive_event(_context: &Context, event: Event) -> libc::uintptr_t {
match event {
Event::GET_STRING => {}
Event::INFO => {
Event::Info(msg) => {
/* do not show the event as this would fill the screen */
println!("{}", to_string(data2 as *const _),);
println!("{}", msg);
}
Event::SMTP_CONNECTED => {
println!("[DC_EVENT_SMTP_CONNECTED] {}", to_string(data2 as *const _));
Event::SmtpConnected(msg) => {
println!("[DC_EVENT_SMTP_CONNECTED] {}", msg);
}
Event::IMAP_CONNECTED => {
println!("[DC_EVENT_IMAP_CONNECTED] {}", to_string(data2 as *const _),);
Event::ImapConnected(msg) => {
println!("[DC_EVENT_IMAP_CONNECTED] {}", msg);
}
Event::SMTP_MESSAGE_SENT => {
println!(
"[DC_EVENT_SMTP_MESSAGE_SENT] {}",
to_string(data2 as *const _),
);
Event::SmtpMessageSent(msg) => {
println!("[DC_EVENT_SMTP_MESSAGE_SENT] {}", msg);
}
Event::WARNING => {
println!("[Warning] {}", to_string(data2 as *const _),);
Event::Warning(msg) => {
println!("[Warning] {}", msg);
}
Event::ERROR => {
println!(
"\x1b[31m[DC_EVENT_ERROR] {}\x1b[0m",
to_string(data2 as *const _),
);
Event::Error(msg) => {
println!("\x1b[31m[DC_EVENT_ERROR] {}\x1b[0m", msg);
}
Event::ERROR_NETWORK => {
println!(
"\x1b[31m[DC_EVENT_ERROR_NETWORK] first={}, msg={}\x1b[0m",
data1 as usize,
to_string(data2 as *const _),
);
Event::ErrorNetwork(msg) => {
println!("\x1b[31m[DC_EVENT_ERROR_NETWORK] msg={}\x1b[0m", msg);
}
Event::ERROR_SELF_NOT_IN_GROUP => {
println!(
"\x1b[31m[DC_EVENT_ERROR_SELF_NOT_IN_GROUP] {}\x1b[0m",
to_string(data2 as *const _),
);
Event::ErrorSelfNotInGroup(msg) => {
println!("\x1b[31m[DC_EVENT_ERROR_SELF_NOT_IN_GROUP] {}\x1b[0m", msg);
}
Event::MSGS_CHANGED => {
Event::MsgsChanged { chat_id, msg_id } => {
print!(
"\x1b[33m{{Received DC_EVENT_MSGS_CHANGED({}, {})}}\n\x1b[0m",
data1 as usize, data2 as usize,
"\x1b[33m{{Received DC_EVENT_MSGS_CHANGED(chat_id={}, msg_id={})}}\n\x1b[0m",
chat_id, msg_id,
);
}
Event::CONTACTS_CHANGED => {
Event::ContactsChanged(_) => {
print!("\x1b[33m{{Received DC_EVENT_CONTACTS_CHANGED()}}\n\x1b[0m");
}
Event::LOCATION_CHANGED => {
Event::LocationChanged(contact) => {
print!(
"\x1b[33m{{Received DC_EVENT_LOCATION_CHANGED(contact={})}}\n\x1b[0m",
data1 as usize,
"\x1b[33m{{Received DC_EVENT_LOCATION_CHANGED(contact={:?})}}\n\x1b[0m",
contact,
);
}
Event::CONFIGURE_PROGRESS => {
Event::ConfigureProgress(progress) => {
print!(
"\x1b[33m{{Received DC_EVENT_CONFIGURE_PROGRESS({} ‰)}}\n\x1b[0m",
data1 as usize,
progress,
);
}
Event::IMEX_PROGRESS => {
Event::ImexProgress(progress) => {
print!(
"\x1b[33m{{Received DC_EVENT_IMEX_PROGRESS({} ‰)}}\n\x1b[0m",
data1 as usize,
progress,
);
}
Event::IMEX_FILE_WRITTEN => {
Event::ImexFileWritten(file) => {
print!(
"\x1b[33m{{Received DC_EVENT_IMEX_FILE_WRITTEN({})}}\n\x1b[0m",
to_string(data1 as *const _)
file.display()
);
}
Event::CHAT_MODIFIED => {
Event::ChatModified(chat) => {
print!(
"\x1b[33m{{Received DC_EVENT_CHAT_MODIFIED({})}}\n\x1b[0m",
data1 as usize,
chat
);
}
_ => {
print!(
"\x1b[33m{{Received {:?}({}, {})}}\n\x1b[0m",
event, data1 as usize, data2 as usize,
);
print!("\x1b[33m{{Received {:?}}}\n\x1b[0m", event);
}
}
@@ -172,13 +150,11 @@ fn start_threads(c: Arc<RwLock<Context>>) {
let ctx = c.clone();
let handle_imap = std::thread::spawn(move || loop {
while_running!({
unsafe {
dc_perform_imap_jobs(&ctx.read().unwrap());
dc_perform_imap_fetch(&ctx.read().unwrap());
}
perform_inbox_jobs(&ctx.read().unwrap());
perform_inbox_fetch(&ctx.read().unwrap());
while_running!({
let context = ctx.read().unwrap();
dc_perform_imap_idle(&context);
perform_inbox_idle(&context);
});
});
});
@@ -186,9 +162,9 @@ fn start_threads(c: Arc<RwLock<Context>>) {
let ctx = c.clone();
let handle_mvbox = std::thread::spawn(move || loop {
while_running!({
unsafe { dc_perform_mvbox_fetch(&ctx.read().unwrap()) };
perform_mvbox_fetch(&ctx.read().unwrap());
while_running!({
unsafe { dc_perform_mvbox_idle(&ctx.read().unwrap()) };
perform_mvbox_idle(&ctx.read().unwrap());
});
});
});
@@ -196,9 +172,9 @@ fn start_threads(c: Arc<RwLock<Context>>) {
let ctx = c.clone();
let handle_sentbox = std::thread::spawn(move || loop {
while_running!({
unsafe { dc_perform_sentbox_fetch(&ctx.read().unwrap()) };
perform_sentbox_fetch(&ctx.read().unwrap());
while_running!({
unsafe { dc_perform_sentbox_idle(&ctx.read().unwrap()) };
perform_sentbox_idle(&ctx.read().unwrap());
});
});
});
@@ -206,9 +182,9 @@ fn start_threads(c: Arc<RwLock<Context>>) {
let ctx = c;
let handle_smtp = std::thread::spawn(move || loop {
while_running!({
unsafe { dc_perform_smtp_jobs(&ctx.read().unwrap()) };
perform_smtp_jobs(&ctx.read().unwrap());
while_running!({
unsafe { dc_perform_smtp_idle(&ctx.read().unwrap()) };
perform_smtp_idle(&ctx.read().unwrap());
});
});
});
@@ -226,12 +202,10 @@ fn stop_threads(context: &Context) {
println!("Stopping threads");
IS_RUNNING.store(false, Ordering::Relaxed);
unsafe {
dc_interrupt_imap_idle(context);
dc_interrupt_mvbox_idle(context);
dc_interrupt_sentbox_idle(context);
dc_interrupt_smtp_idle(context);
}
interrupt_inbox_idle(context, true);
interrupt_mvbox_idle(context);
interrupt_sentbox_idle(context);
interrupt_smtp_idle(context);
handle.handle_imap.take().unwrap().join().unwrap();
handle.handle_mvbox.take().unwrap().join().unwrap();
@@ -261,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",
@@ -276,7 +250,7 @@ const IMEX_COMMANDS: [&'static str; 12] = [
"stop",
];
const DB_COMMANDS: [&'static str; 11] = [
const DB_COMMANDS: [&str; 11] = [
"info",
"open",
"close",
@@ -290,7 +264,7 @@ const DB_COMMANDS: [&'static str; 11] = [
"housekeeping",
];
const CHAT_COMMANDS: [&'static str; 24] = [
const CHAT_COMMANDS: [&str; 24] = [
"listchats",
"listarchived",
"chat",
@@ -316,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",
@@ -326,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",
@@ -334,8 +308,8 @@ const CONTACT_COMMANDS: [&'static str; 6] = [
"delcontact",
"cleanupcontacts",
];
const MISC_COMMANDS: [&'static str; 8] = [
"getqr", "getbadqr", "checkqr", "event", "fileinfo", "clear", "exit", "help",
const MISC_COMMANDS: [&str; 9] = [
"getqr", "getbadqr", "checkqr", "event", "fileinfo", "clear", "exit", "quit", "help",
];
impl Hinter for DcHelper {
@@ -360,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> {
@@ -388,27 +362,15 @@ impl Highlighter for DcHelper {
impl Helper for DcHelper {}
fn main_0(args: Vec<String>) -> Result<(), failure::Error> {
let mut context = dc_context_new(
Some(receive_event),
0 as *mut libc::c_void,
b"CLI\x00" as *const u8 as *const libc::c_char,
);
unsafe { dc_cmdline_skip_auth() };
if args.len() == 2 {
if 0 == unsafe {
dc_open(
&mut context,
to_cstring(&args[1]).as_ptr(),
0 as *const libc::c_char,
)
} {
println!("Error: Cannot open {}.", args[0],);
}
} else if args.len() != 1 {
if args.len() < 2 {
println!("Error: Bad arguments, expected [db-name].");
return Err(format_err!("No db-name specified"));
}
let context = Context::new(
Box::new(receive_event),
"CLI".into(),
Path::new(&args[1]).to_path_buf(),
)?;
println!("Delta Chat Core is awaiting your commands.");
@@ -441,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),
@@ -461,12 +423,6 @@ fn main_0(args: Vec<String>) -> Result<(), failure::Error> {
println!("history saved");
{
stop_threads(&ctx.read().unwrap());
unsafe {
let mut ctx = ctx.write().unwrap();
dc_close(&mut ctx);
dc_context_unref(&mut ctx);
}
}
Ok(())
@@ -478,16 +434,10 @@ 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();
let arg1_c = to_cstring(arg1);
let arg1_c_ptr = if arg1.is_empty() {
std::ptr::null()
} else {
arg1_c.as_ptr()
};
match arg0 {
"connect" => {
@@ -500,19 +450,19 @@ unsafe fn handle_cmd(line: &str, ctx: Arc<RwLock<Context>>) -> Result<ExitResult
if HANDLE.clone().lock().unwrap().is_some() {
println!("smtp-jobs are already running in a thread.",);
} else {
dc_perform_smtp_jobs(&ctx.read().unwrap());
perform_smtp_jobs(&ctx.read().unwrap());
}
}
"imap-jobs" => {
if HANDLE.clone().lock().unwrap().is_some() {
println!("imap-jobs are already running in a thread.");
println!("inbox-jobs are already running in a thread.");
} else {
dc_perform_imap_jobs(&ctx.read().unwrap());
perform_inbox_jobs(&ctx.read().unwrap());
}
}
"configure" => {
start_threads(ctx.clone());
dc_configure(&ctx.read().unwrap());
configure(&ctx.read().unwrap());
}
"oauth2" => {
if let Some(addr) = ctx.read().unwrap().get_config(config::Config::Addr) {
@@ -536,33 +486,30 @@ unsafe fn handle_cmd(line: &str, ctx: Arc<RwLock<Context>>) -> Result<ExitResult
}
"getqr" | "getbadqr" => {
start_threads(ctx.clone());
let qrstr =
dc_get_securejoin_qr(&ctx.read().unwrap(), arg1.parse().unwrap_or_default());
if !qrstr.is_null() && 0 != *qrstr.offset(0isize) as libc::c_int {
if arg0 == "getbadqr" && strlen(qrstr) > 40 {
let mut i: libc::c_int = 12i32;
while i < 22i32 {
*qrstr.offset(i as isize) = '0' as i32 as libc::c_char;
i += 1
if let Some(mut qr) =
dc_get_securejoin_qr(&ctx.read().unwrap(), arg1.parse().unwrap_or_default())
{
if !qr.is_empty() {
if arg0 == "getbadqr" && qr.len() > 40 {
qr.replace_range(12..22, "0000000000")
}
println!("{}", qr);
let output = Command::new("qrencode")
.args(&["-t", "ansiutf8", qr.as_str(), "-o", "-"])
.output()
.expect("failed to execute process");
io::stdout().write_all(&output.stdout).unwrap();
io::stderr().write_all(&output.stderr).unwrap();
}
println!("{}", to_string(qrstr as *const _));
let syscmd = dc_mprintf(
b"qrencode -t ansiutf8 \"%s\" -o -\x00" as *const u8 as *const libc::c_char,
qrstr,
);
system(syscmd);
free(syscmd as *mut libc::c_void);
}
free(qrstr as *mut libc::c_void);
}
"joinqr" => {
start_threads(ctx.clone());
if !arg0.is_empty() {
dc_join_securejoin(&ctx.read().unwrap(), arg1_c_ptr);
dc_join_securejoin(&ctx.read().unwrap(), arg1);
}
}
"exit" => return Ok(ExitResult::Exit),
"exit" | "quit" => return Ok(ExitResult::Exit),
_ => dc_cmdline(&ctx.read().unwrap(), line)?,
}

View File

@@ -1,150 +1,115 @@
extern crate deltachat;
use std::ffi::{CStr, CString};
use std::sync::{Arc, RwLock};
use std::{thread, time};
use tempfile::tempdir;
use deltachat::chat;
use deltachat::chatlist::*;
use deltachat::config;
use deltachat::constants::Event;
use deltachat::configure::*;
use deltachat::contact::*;
use deltachat::context::*;
use deltachat::dc_chat::*;
use deltachat::dc_chatlist::*;
use deltachat::dc_configure::*;
use deltachat::dc_contact::*;
use deltachat::dc_job::{
dc_perform_imap_fetch, dc_perform_imap_idle, dc_perform_imap_jobs, dc_perform_smtp_idle,
dc_perform_smtp_jobs,
use deltachat::job::{
perform_inbox_fetch, perform_inbox_idle, perform_inbox_jobs, perform_smtp_idle,
perform_smtp_jobs,
};
use deltachat::dc_lot::*;
use deltachat::Event;
extern "C" fn cb(_ctx: &Context, event: Event, data1: usize, data2: usize) -> usize {
println!("[{:?}]", event);
fn cb(_ctx: &Context, event: Event) -> usize {
print!("[{:?}]", event);
match event {
Event::CONFIGURE_PROGRESS => {
println!(" progress: {}", data1);
Event::ConfigureProgress(progress) => {
print!(" progress: {}\n", progress);
0
}
Event::INFO | Event::WARNING | Event::ERROR | Event::ERROR_NETWORK => {
println!(
" {}",
unsafe { CStr::from_ptr(data2 as *const _) }
.to_str()
.unwrap()
);
Event::Info(msg) | Event::Warning(msg) | Event::Error(msg) | Event::ErrorNetwork(msg) => {
print!(" {}\n", msg);
0
}
_ => {
print!("\n");
0
}
_ => 0,
}
}
fn main() {
unsafe {
let ctx = dc_context_new(Some(cb), std::ptr::null_mut(), std::ptr::null_mut());
let running = Arc::new(RwLock::new(true));
let info = dc_get_info(&ctx);
let info_s = CStr::from_ptr(info);
let duration = time::Duration::from_millis(4000);
println!("info: {}", info_s.to_str().unwrap());
let dir = tempdir().unwrap();
let dbfile = dir.path().join("db.sqlite");
println!("creating database {:?}", dbfile);
let ctx =
Context::new(Box::new(cb), "FakeOs".into(), dbfile).expect("Failed to create context");
let running = Arc::new(RwLock::new(true));
let info = ctx.get_info();
let duration = time::Duration::from_millis(4000);
println!("info: {:#?}", info);
let ctx = Arc::new(ctx);
let ctx1 = ctx.clone();
let r1 = running.clone();
let t1 = thread::spawn(move || {
while *r1.read().unwrap() {
perform_inbox_jobs(&ctx1);
if *r1.read().unwrap() {
perform_inbox_fetch(&ctx1);
let ctx = Arc::new(ctx);
let ctx1 = ctx.clone();
let r1 = running.clone();
let t1 = thread::spawn(move || {
while *r1.read().unwrap() {
dc_perform_imap_jobs(&ctx1);
if *r1.read().unwrap() {
dc_perform_imap_fetch(&ctx1);
if *r1.read().unwrap() {
dc_perform_imap_idle(&ctx1);
}
perform_inbox_idle(&ctx1);
}
}
});
let ctx1 = ctx.clone();
let r1 = running.clone();
let t2 = thread::spawn(move || {
while *r1.read().unwrap() {
dc_perform_smtp_jobs(&ctx1);
if *r1.read().unwrap() {
dc_perform_smtp_idle(&ctx1);
}
}
});
let dir = tempdir().unwrap();
let dbfile = CString::new(dir.path().join("db.sqlite").to_str().unwrap()).unwrap();
println!("opening database {:?}", dbfile);
assert_eq!(dc_open(&ctx, dbfile.as_ptr(), std::ptr::null()), 1);
println!("configuring");
let args = std::env::args().collect::<Vec<String>>();
assert_eq!(args.len(), 2, "missing password");
let pw = args[1].clone();
ctx.set_config(config::Config::Addr, Some("d@testrun.org"));
ctx.set_config(config::Config::MailPw, Some(&pw));
dc_configure(&ctx);
thread::sleep(duration);
let email = CString::new("dignifiedquire@gmail.com").unwrap();
println!("sending a message");
let contact_id = dc_create_contact(&ctx, std::ptr::null(), email.as_ptr());
let chat_id = dc_create_chat_by_contact_id(&ctx, contact_id);
let msg_text = CString::new("Hi, here is my first message!").unwrap();
dc_send_text_msg(&ctx, chat_id, msg_text.as_ptr());
println!("fetching chats..");
let chats = dc_get_chatlist(&ctx, 0, std::ptr::null(), 0);
for i in 0..dc_chatlist_get_cnt(chats) {
let summary = dc_chatlist_get_summary(chats, 0, std::ptr::null_mut());
let text1 = dc_lot_get_text1(summary);
let text2 = dc_lot_get_text2(summary);
let text1_s = if !text1.is_null() {
Some(CStr::from_ptr(text1))
} else {
None
};
let text2_s = if !text2.is_null() {
Some(CStr::from_ptr(text2))
} else {
None
};
println!("chat: {} - {:?} - {:?}", i, text1_s, text2_s,);
dc_lot_unref(summary);
}
dc_chatlist_unref(chats);
});
thread::sleep(duration);
let ctx1 = ctx.clone();
let r1 = running.clone();
let t2 = thread::spawn(move || {
while *r1.read().unwrap() {
perform_smtp_jobs(&ctx1);
if *r1.read().unwrap() {
perform_smtp_idle(&ctx1);
}
}
});
// 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!("configuring");
let args = std::env::args().collect::<Vec<String>>();
assert_eq!(args.len(), 2, "missing password");
let pw = args[1].clone();
ctx.set_config(config::Config::Addr, Some("d@testrun.org"))
.unwrap();
ctx.set_config(config::Config::MailPw, Some(&pw)).unwrap();
configure(&ctx);
println!("stopping threads");
thread::sleep(duration);
*running.clone().write().unwrap() = false;
deltachat::dc_job::dc_interrupt_imap_idle(&ctx);
deltachat::dc_job::dc_interrupt_smtp_idle(&ctx);
println!("sending a message");
let contact_id = Contact::create(&ctx, "dignifiedquire", "dignifiedquire@gmail.com").unwrap();
let chat_id = chat::create_by_contact_id(&ctx, contact_id).unwrap();
chat::send_text_msg(&ctx, chat_id, "Hi, here is my first message!".into()).unwrap();
println!("joining");
t1.join().unwrap();
t2.join().unwrap();
println!("fetching chats..");
let chats = Chatlist::try_load(&ctx, 0, None, None).unwrap();
println!("closing");
dc_close(&ctx);
for i in 0..chats.len() {
let summary = chats.get_summary(&ctx, 0, None);
let text1 = summary.get_text1();
let text2 = summary.get_text2();
println!("chat: {} - {:?} - {:?}", i, text1, text2,);
}
thread::sleep(duration);
println!("stopping threads");
*running.write().unwrap() = false;
deltachat::job::interrupt_inbox_idle(&ctx, true);
deltachat::job::interrupt_smtp_idle(&ctx);
println!("joining");
t1.join().unwrap();
t2.join().unwrap();
println!("closing");
}

52
misc.c
View File

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

1
misc.h
View File

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

View File

@@ -0,0 +1,7 @@
# Seeds for failure cases proptest has generated in the past. It is
# automatically read and these particular cases re-run before any
# novel cases are generated.
#
# It is recommended to check this file in to source control so that
# everyone who runs the test benefits from these saved cases.
cc 03cab93c6d1f3a8245f63cf84dacb307944294fe6333c1e38f078a6600659c7a # shrinks to data = "a\t0aA\ta\t0 \ta\t0 \ta a\t\ta A\tAA0a0a 0\t a\t aA \t a\t A0\t AAa\taA0\taAAaA\t0\taa0a\ta Aa Aaaa A0A\t a aA 0\t A\t0\t0\t\t\t\t\t\tA \t\t a\tA Aa aAA0A0AA0aaA A\t\t aa0\ta\t \tAa\taA\t00 AA A a\tA a aAAa \t 00\t0 \t\t a A 0\t\t\t aAA Aa \taAAAA0a A0A\t\t1\\E$\t$R\tc\t^=\t\"\tQ<Uk\t\t>A\t\t&\t}v&\tM^\'|\tW5?dn\t\t+\t\tP\te`\t\t>:Brlq--?\t$#Q\tK=zJ\tb\"9*.\"\t`\tF&T*\tBs,\tg\'*\\\t:\t?l$\t\t|A\"HR:Hk\t\\KkV\t\t{=&!^e%:|_*0wV\t[$`\t:\t$%f\t\t[!\"Y. \tP\t\th\'?\'/?%:++NfQo#@\"+?\t(\\??{\t\'\'$Dzj\t0.?{s4?&?Y=/yj]Z=\t4n\t?Ja\"\t{I\t$\t;I}_8V\t&\t?N\'\tI2/\t9.\tFT%9%`\'\tz\to7Y\t|AXP&@G12g\t\'w\t\t%??\t\"h$?F\"\"6%q\\\\{\tT\t\"]$87$}h\'\t<\t$\tc%U:mT2:<v\t#Rl!;U\t\t\"^D\tRZ(BZ{n\t%[$fgH`\t{B}:*\t\t%%*F`%W\t//B}PQ\t\tsu2\tLz<1*!p-X\tnKv&&0\thm4<n\\.\\/.w\'\t<)E1g3#ood\t`?\t\\({N?,8_?h\ty\t0%\t*$A\t\t*w-ViQUj\tTiH\t%\t%&0p\'\'\tA%r**Fo\'Z\\\tNI*ely4=I?}$.o`\t$\ts\'&lt\t\",:~=Nv/0O%=+<LS\t%P\'\t$r%d.\t{G{/L:\t&I&8-`cy*\"{\t/%fP9.P<\t\t\'/`\t\t`\t\t`!t:\t::\t\tW\'X^\t@.uL&a\tN\t\t\t.\t?0*\tvUK>UJ\\\tQbj%w]P=Js\t\"R\t&s^?</=%\t\'VI:\" kT`{b*<\t\tF&?Y\t\t:/\t!C\'e0`\t\t\tx-\t*\\N\\wwu.I4\tb/y\t\"P:P%\"\t\tQ][\\\t^x\t\t):\t\t&s\t$1-\t\t\tXt`0\t;\t/UeCP*\"G\t\t\':\tk@91Hf&#\t(Uzz&`p2;\t{]\t\"I_%=\\%,XD\"\'06QfCd<;$:q^\t8:\"*\"H\t\to\t&xK/\t\ty0/<}<j<|*`~::b\t=S.=}=Ki\t<Y.\'{\tf\t{Ub*H%(_4*?*\tn2\t/\'\t\t\t/,4]\tt\t<y\t\t\tWi\t\tT&\"\t\t\t\t\t=/1Wu?\t\'A\"W-P\t$?e\\\t`\t6`vD\t8`\t\tccD J\tY&jI//?_\t\\j\t_\tsiq$\t?9\tQ\t.^%&..?%Jm??A%+<\tN&*\t.g\tS$W\"\"\tMpq\t\t:&\\\thTz&<iz%/%T&\'F\t\\]&\t\t}\t\t\tXr=dtp`:+<,\t%60Y*<(&K*kJ\todO\t=<V?&\tMU/\"\t= Y!)<\tV\t9\t)\t&v8q{\t\t&pT\t3\ttB,qcq\'i$;gv%j_%M_{[\"&;\t\t\t.B;6Y\\%\t\"\tY/a\t\\`:?\t<\t?]\taNwD;\\\t%l*74%5T?QS :~yR/E*R\t\t=u\t\\\t\t.Q<;\\\t_S/{&F$^\tw_>\'h=|%\t\t:W!\\<U2\'$\tb&`\t=|=L9\t\t\t\\WZ:! }{\t ;\t;\t\t 0.*\t.%\"\'r%{<Mh_t[[}\t-\tJo\"b/AC*-=`=T\tz$2\tC\t\t/k{47\"\t\t,q%\tZ\tT3\t\tf>%\t\'?%@^tx\t7\"1Bk{b{n\t\"Pj3\tHc\t\tt\tY<\t#?\tSh\\yk/N\\\t8 7=R4*9Cw&m\t\\-\'f\t|\'#t(Etu.Hdu(E\t%&v:\'aqW~A5\t\t w.s{J%lX<\"\t\'*%m<&:/B<&\':U}$&`.{)\t\t6S\t:/$*kQ-Z\t^\'t${/tH*\'v\t3\t=\t\tDyp:B\t`I_R&4SO\t\t&-j=*.\t87&\'e*( \t\t\t\'<$\\DJ<$p?{}\'&\tv\t\\Xk<Y:Y`!$K{\tF&\tzd\t\t*i$\tj\'\t<)R*\t%?\t!.\t=\"@#~:=*\t\tXO=_T,1\"\'.%%\"`{\\:\t\"\tfkeOb/\'$I~\ta\t|&\t[\\KK\"1&Z\t<k\t\t)%\'-~\"2n\tj\tW?*<@w{g%d\ta\\\'\'I\t;:ySR%ke:4\tc\t$=\t&9P]x4\tJ=\t6C6%a\t`0\tF\tm-\tTr\t}\t\tQum\t&@\typ|w2&\t\t3`i&t\t\tT5\"\t.&b&e*/==1.\'*\\[U*\tqPL%?$-0/}~|q`\t\t}\t$\tq==o+T$\'!H\t\ti&um\"?\"%%\t/\'p\tg>?{0{J{\t\t/\t\t{zKZ&>=\t[\"1h<H%z/8,/]s\tv{7\t\t:j*H,M//\t\t\td\'.)\t"

View File

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

View File

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

View File

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

View File

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

View File

@@ -6,7 +6,6 @@
<li><a href="{{ pathto('install') }}">install</a></li>
<li><a href="{{ pathto('api') }}">high level API</a></li>
<li><a href="{{ pathto('lapi') }}">low level API</a></li>
<li><a href="{{ pathto('capi') }}">C deltachat.h</a></li>
</ul>
<b>external links:</b>
<ul>

View File

@@ -8,11 +8,9 @@ high level API reference
- :class:`deltachat.account.Account` (your main entry point, creates the
other classes)
- :class:`deltachat.chatting.Contact`
- :class:`deltachat.chatting.Chat`
- :class:`deltachat.contact.Contact`
- :class:`deltachat.chat.Chat`
- :class:`deltachat.message.Message`
- :class:`deltachat.message.MessageType`
- :class:`deltachat.message.MessageState`
Account
-------
@@ -24,13 +22,13 @@ Account
Contact
-------
.. autoclass:: deltachat.chatting.Contact
.. autoclass:: deltachat.contact.Contact
:members:
Chat
----
.. autoclass:: deltachat.chatting.Chat
.. autoclass:: deltachat.chat.Chat
:members:
Message
@@ -39,16 +37,3 @@ Message
.. autoclass:: deltachat.message.Message
:members:
MessageType
------------
.. autoclass:: deltachat.message.MessageType
:members:
MessageState
------------
.. autoclass:: deltachat.message.MessageState
:members:

View File

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

View File

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

View File

@@ -2,7 +2,13 @@
low level API reference
===================================
for full C-docs, defines and function checkout :doc:`capi`
for full doxygen-generated C-docs, defines and functions please checkout
https://c.delta.chat
Python low-level capi calls
---------------------------
.. automodule:: deltachat.capi.lib

View File

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

View File

@@ -0,0 +1,31 @@
#!/usr/bin/env python
"""
setup a python binding development in-place install with cargo debug symbols.
"""
import os
import subprocess
import sys
if __name__ == "__main__":
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)
if len(sys.argv) <= 1 or sys.argv[1] != "onlybuild":
subprocess.check_call([
sys.executable, "-m", "pip", "install", "-e", "."
])

View File

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

View File

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

View File

@@ -6,10 +6,15 @@ import platform
import os
import cffi
import shutil
from os.path import dirname as dn
from os.path import abspath
def ffibuilder():
projdir = os.environ.get('DCC_RS_DEV')
if not projdir:
p = dn(dn(dn(dn(abspath(__file__)))))
projdir = os.environ["DCC_RS_DEV"] = p
target = os.environ.get('DCC_RS_TARGET', 'release')
if projdir:
if platform.system() == 'Darwin':
@@ -24,12 +29,17 @@ 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:
libs = ['deltachat']
objs = []
incs = []
extra_link_args = []
builder = cffi.FFI()
builder.set_source(
'deltachat.capi',
@@ -69,8 +79,8 @@ def ffibuilder():
distutils.sysconfig.customize_compiler(cc)
tmpdir = tempfile.mkdtemp()
try:
src_name = os.path.join(tmpdir, "prep.h")
dst_name = os.path.join(tmpdir, "prep2.c")
src_name = os.path.join(tmpdir, "include.h")
dst_name = os.path.join(tmpdir, "expanded.h")
with open(src_name, "w") as src_fp:
src_fp.write('#include <deltachat.h>')
cc.preprocess(source=src_name,

View File

@@ -1,49 +1,61 @@
""" Account class implementation. """
from __future__ import print_function
import atexit
import threading
import os
import re
import time
from array import array
try:
from queue import Queue
from queue import Queue, Empty
except ImportError:
from Queue import Queue
from Queue import Queue, Empty
import deltachat
from . import const
from .capi import ffi, lib
from .cutil import as_dc_charpointer, from_dc_charpointer, iter_array
from .chatting import Contact, Chat, Message
from .cutil import as_dc_charpointer, from_dc_charpointer, iter_array, DCLot
from .chat import Chat
from .message import Message
from .contact import Contact
class Account(object):
""" Each account is tied to a sqlite database file which is fully managed
by the underlying deltachat c-library. All public Account methods are
by the underlying deltachat core library. All public Account methods are
meant to be memory-safe and return memory-safe objects.
"""
def __init__(self, db_path, logid=None):
def __init__(self, db_path, logid=None, eventlogging=True, debug=True):
""" initialize account object.
:param db_path: a path to the account database. The database
will be created if it doesn't exist.
:param logid: an optional logging prefix that should be used with
the default internal logging.
:param eventlogging: if False no eventlogging and no context callback will be configured
:param debug: turn on debug logging for events.
"""
self._dc_context = ffi.gc(
lib.dc_context_new(lib.py_dc_callback, ffi.NULL, ffi.NULL),
_destroy_dc_context,
)
if eventlogging:
self._evlogger = EventLogger(self._dc_context, logid, debug)
deltachat.set_context_callback(self._dc_context, self._process_event)
self._threads = IOThreads(self._dc_context, self._evlogger._log_event)
else:
self._threads = IOThreads(self._dc_context)
if hasattr(db_path, "encode"):
db_path = db_path.encode("utf8")
if not lib.dc_open(self._dc_context, db_path, ffi.NULL):
raise ValueError("Could not dc_open: {}".format(db_path))
self._evlogger = EventLogger(self._dc_context, logid)
deltachat.set_context_callback(self._dc_context, self._process_event)
self._threads = IOThreads(self._dc_context)
self._configkeys = self.get_config("sys.config_keys").split()
self._imex_completed = threading.Event()
self._imex_events = Queue()
atexit.register(self.shutdown)
# def __del__(self):
# self.shutdown()
def _check_config_key(self, name):
if name not in self._configkeys:
@@ -61,6 +73,18 @@ class Account(object):
d[key.lower()] = value
return d
def set_stock_translation(self, id, string):
""" set stock translation string.
:param id: id of stock string (const.DC_STR_*)
:param value: string to set as new transalation
:returns: None
"""
string = string.encode("utf8")
res = lib.dc_set_stock_translation(self._dc_context, id, string)
if res == 0:
raise ValueError("could not set translation string")
def set_config(self, name, value):
""" set configuration values.
@@ -113,11 +137,30 @@ class Account(object):
if not self.is_configured():
raise ValueError("need to configure first")
def empty_server_folders(self, inbox=False, mvbox=False):
""" empty server folders. """
flags = 0
if inbox:
flags |= const.DC_EMPTY_INBOX
if mvbox:
flags |= const.DC_EMPTY_MVBOX
if not flags:
raise ValueError("no flags set")
lib.dc_empty_server(self._dc_context, flags)
def get_infostring(self):
""" return info of the configured account. """
self.check_is_configured()
return from_dc_charpointer(lib.dc_get_info(self._dc_context))
def get_latest_backupfile(self, backupdir):
""" return the latest backup file in a given directory.
"""
res = lib.dc_imex_has_backup(self._dc_context, as_dc_charpointer(backupdir))
if res == ffi.NULL:
return None
return from_dc_charpointer(res)
def get_blobdir(self):
""" return the directory for files.
@@ -127,22 +170,13 @@ class Account(object):
return from_dc_charpointer(lib.dc_get_blobdir(self._dc_context))
def get_self_contact(self):
""" return this account's identity as a :class:`deltachat.chatting.Contact`.
""" return this account's identity as a :class:`deltachat.contact.Contact`.
:returns: :class:`deltachat.chatting.Contact`
:returns: :class:`deltachat.contact.Contact`
"""
self.check_is_configured()
return Contact(self._dc_context, const.DC_CONTACT_ID_SELF)
def create_message(self, view_type):
""" create a new non persistent message.
:param view_type: a string specifying "text", "video",
"image", "audio" or "file".
:returns: :class:`deltachat.message.Message` instance.
"""
return Message.new(self._dc_context, view_type)
def create_contact(self, email, name=None):
""" create a (new) Contact. If there already is a Contact
with that e-mail address, it is unblocked and its name is
@@ -150,7 +184,7 @@ class Account(object):
:param email: email-address (text type)
:param name: display name for this contact (optional)
:returns: :class:`deltachat.chatting.Contact` instance.
:returns: :class:`deltachat.contact.Contact` instance.
"""
name = as_dc_charpointer(name)
email = as_dc_charpointer(email)
@@ -158,6 +192,17 @@ class Account(object):
assert contact_id > const.DC_CHAT_ID_LAST_SPECIAL
return Contact(self._dc_context, contact_id)
def delete_contact(self, contact):
""" delete a Contact.
:param contact: contact object obtained
:returns: True if deletion succeeded (contact was deleted)
"""
contact_id = contact.id
assert contact._dc_context == self._dc_context
assert contact_id > const.DC_CHAT_ID_LAST_SPECIAL
return bool(lib.dc_delete_contact(self._dc_context, contact_id))
def get_contacts(self, query=None, with_self=False, only_verified=False):
""" get a (filtered) list of contacts.
@@ -165,7 +210,7 @@ class Account(object):
whose name or e-mail matches query.
:param only_verified: if true only return verified contacts.
:param with_self: if true the self-contact is also returned.
:returns: list of :class:`deltachat.message.Message` objects.
:returns: list of :class:`deltachat.contact.Contact` objects.
"""
flags = 0
query = as_dc_charpointer(query)
@@ -183,7 +228,7 @@ class Account(object):
""" create or get an existing 1:1 chat object for the specified contact or contact id.
:param contact: chat_id (int) or contact object.
:returns: a :class:`deltachat.chatting.Chat` object.
:returns: a :class:`deltachat.chat.Chat` object.
"""
if hasattr(contact, "id"):
if contact._dc_context != self._dc_context:
@@ -193,14 +238,14 @@ class Account(object):
assert isinstance(contact, int)
contact_id = contact
chat_id = lib.dc_create_chat_by_contact_id(self._dc_context, contact_id)
return Chat(self._dc_context, chat_id)
return Chat(self, chat_id)
def create_chat_by_message(self, message):
""" create or get an existing chat object for the
the specified message.
:param message: messsage id or message instance.
:returns: a :class:`deltachat.chatting.Chat` object.
:returns: a :class:`deltachat.chat.Chat` object.
"""
if hasattr(message, "id"):
if self._dc_context != message._dc_context:
@@ -210,7 +255,7 @@ class Account(object):
assert isinstance(message, int)
msg_id = message
chat_id = lib.dc_create_chat_by_msg_id(self._dc_context, msg_id)
return Chat(self._dc_context, chat_id)
return Chat(self, chat_id)
def create_group_chat(self, name, verified=False):
""" create a new group chat object.
@@ -218,16 +263,16 @@ class Account(object):
Chats are unpromoted until the first message is sent.
:param verified: if true only verified contacts can be added.
:returns: a :class:`deltachat.chatting.Chat` object.
:returns: a :class:`deltachat.chat.Chat` object.
"""
bytes_name = name.encode("utf8")
chat_id = lib.dc_create_group_chat(self._dc_context, verified, bytes_name)
return Chat(self._dc_context, chat_id)
chat_id = lib.dc_create_group_chat(self._dc_context, int(verified), bytes_name)
return Chat(self, chat_id)
def get_chats(self):
""" return list of chats.
:returns: a list of :class:`deltachat.chatting.Chat` objects.
:returns: a list of :class:`deltachat.chat.Chat` objects.
"""
dc_chatlist = ffi.gc(
lib.dc_get_chatlist(self._dc_context, 0, ffi.NULL, 0),
@@ -238,15 +283,30 @@ class Account(object):
chatlist = []
for i in range(0, lib.dc_chatlist_get_cnt(dc_chatlist)):
chat_id = lib.dc_chatlist_get_chat_id(dc_chatlist, i)
chatlist.append(Chat(self._dc_context, chat_id))
chatlist.append(Chat(self, chat_id))
return chatlist
def get_deaddrop_chat(self):
return Chat(self._dc_context, const.DC_CHAT_ID_DEADDROP)
return Chat(self, const.DC_CHAT_ID_DEADDROP)
def get_message_by_id(self, msg_id):
""" return Message instance. """
return Message.from_db(self._dc_context, msg_id)
""" return Message instance.
:param msg_id: integer id of this message.
:returns: :class:`deltachat.message.Message` instance.
"""
return Message.from_db(self, msg_id)
def get_chat_by_id(self, chat_id):
""" return Chat instance.
:param chat_id: integer id of this chat.
:returns: :class:`deltachat.chat.Chat` instance.
:raises: ValueError if chat does not exist.
"""
res = lib.dc_get_chat(self._dc_context, chat_id)
if res == ffi.NULL:
raise ValueError("cannot get chat with id={}".format(chat_id))
lib.dc_chat_unref(res)
return Chat(self, chat_id)
def mark_seen_messages(self, messages):
""" mark the given set of messages as seen.
@@ -264,7 +324,7 @@ class Account(object):
""" Forward list of messages to a chat.
:param messages: list of :class:`deltachat.message.Message` object.
:param chat: :class:`deltachat.chatting.Chat` object.
:param chat: :class:`deltachat.chat.Chat` object.
:returns: None
"""
msg_ids = [msg.id for msg in messages]
@@ -279,31 +339,64 @@ class Account(object):
msg_ids = [msg.id for msg in messages]
lib.dc_delete_msgs(self._dc_context, msg_ids, len(msg_ids))
def export_to_dir(self, backupdir):
"""return after all delta chat state is exported to a new file in
the specified directory.
def export_self_keys(self, path):
""" export public and private keys to the specified directory. """
return self._export(path, imex_cmd=1)
def export_all(self, path):
"""return new file containing a backup of all database state
(chats, contacts, keys, media, ...). The file is created in the
the `path` directory.
"""
snap_files = os.listdir(backupdir)
self._imex_completed.clear()
lib.dc_imex(self._dc_context, 11, as_dc_charpointer(backupdir), ffi.NULL)
export_files = self._export(path, 11)
if len(export_files) != 1:
raise RuntimeError("found more than one new file")
return export_files[0]
def _imex_events_clear(self):
try:
while True:
self._imex_events.get_nowait()
except Empty:
pass
def _export(self, path, imex_cmd):
self._imex_events_clear()
lib.dc_imex(self._dc_context, imex_cmd, as_dc_charpointer(path), ffi.NULL)
if not self._threads.is_started():
lib.dc_perform_imap_jobs(self._dc_context)
self._imex_completed.wait()
for x in os.listdir(backupdir):
if x not in snap_files:
return os.path.join(backupdir, x)
files_written = []
while True:
ev = self._imex_events.get()
if isinstance(ev, str):
files_written.append(ev)
elif isinstance(ev, bool):
if not ev:
raise ValueError("export failed, exp-files: {}".format(files_written))
return files_written
def import_from_file(self, path):
"""import delta chat state from the specified backup file.
def import_self_keys(self, path):
""" Import private keys found in the `path` directory.
The last imported key is made the default keys unless its name
contains the string legacy. Public keys are not imported.
"""
self._import(path, imex_cmd=2)
def import_all(self, path):
"""import delta chat state from the specified backup `path` (a file).
The account must be in unconfigured state for import to attempted.
"""
assert not self.is_configured(), "cannot import into configured account"
self._imex_completed.clear()
lib.dc_imex(self._dc_context, 12, as_dc_charpointer(path), ffi.NULL)
self._import(path, imex_cmd=12)
def _import(self, path, imex_cmd):
self._imex_events_clear()
lib.dc_imex(self._dc_context, imex_cmd, as_dc_charpointer(path), ffi.NULL)
if not self._threads.is_started():
lib.dc_perform_imap_jobs(self._dc_context)
self._imex_completed.wait()
if not self._imex_events.get():
raise ValueError("import from path '{}' failed".format(path))
def initiate_key_transfer(self):
"""return setup code after a Autocrypt setup message
@@ -318,7 +411,69 @@ class Account(object):
raise RuntimeError("could not send out autocrypt setup message")
return from_dc_charpointer(res)
def start_threads(self):
def get_setup_contact_qr(self):
""" get/create Setup-Contact QR Code as ascii-string.
this string needs to be transferred to another DC account
in a second channel (typically used by mobiles with QRcode-show + scan UX)
where qr_setup_contact(qr) is called.
"""
res = lib.dc_get_securejoin_qr(self._dc_context, 0)
return from_dc_charpointer(res)
def check_qr(self, qr):
""" check qr code and return :class:`ScannedQRCode` instance representing the result"""
res = ffi.gc(
lib.dc_check_qr(self._dc_context, as_dc_charpointer(qr)),
lib.dc_lot_unref
)
lot = DCLot(res)
if lot.state() == const.DC_QR_ERROR:
raise ValueError("invalid or unknown QR code: {}".format(lot.text1()))
return ScannedQRCode(lot)
def qr_setup_contact(self, qr):
""" setup contact and return a Chat after contact is established.
Note that this function may block for a long time as messages are exchanged
with the emitter of the QR code. On success a :class:`deltachat.chat.Chat` instance
is returned.
:param qr: valid "setup contact" QR code (all other QR codes will result in an exception)
"""
assert self.check_qr(qr).is_ask_verifycontact()
chat_id = lib.dc_join_securejoin(self._dc_context, as_dc_charpointer(qr))
if chat_id == 0:
raise ValueError("could not setup secure contact")
return Chat(self, chat_id)
def qr_join_chat(self, qr):
""" join a chat group through a QR code.
Note that this function may block for a long time as messages are exchanged
with the emitter of the QR code. On success a :class:`deltachat.chat.Chat` instance
is returned which is the chat that we just joined.
:param qr: valid "join-group" QR code (all other QR codes will result in an exception)
"""
assert self.check_qr(qr).is_ask_verifygroup()
chat_id = lib.dc_join_securejoin(self._dc_context, as_dc_charpointer(qr))
if chat_id == 0:
raise ValueError("could not join group")
return Chat(self, chat_id)
def stop_ongoing(self):
lib.dc_stop_ongoing_process(self._dc_context)
#
# meta API for start/stop and event based processing
#
def wait_next_incoming_message(self):
""" wait for and return next incoming message. """
ev = self._evlogger.get_matching("DC_EVENT_INCOMING_MSG")
return self.get_message_by_id(ev[2])
def start_threads(self, mvbox=False, sentbox=False):
""" start IMAP/SMTP threads (and configure account if it hasn't happened).
:raises: ValueError if 'addr' or 'mail_pw' are not configured.
@@ -326,39 +481,76 @@ class Account(object):
"""
if not self.is_configured():
self.configure()
self._threads.start()
self._threads.start(mvbox=mvbox, sentbox=sentbox)
def stop_threads(self, wait=True):
""" stop IMAP/SMTP threads. """
lib.dc_stop_ongoing_process(self._dc_context)
self._threads.stop(wait=wait)
if self._threads.is_started():
self.stop_ongoing()
self._threads.stop(wait=wait)
def shutdown(self, wait=True):
""" stop threads and close and remove underlying dc_context and callbacks. """
if hasattr(self, "_dc_context") and hasattr(self, "_threads"):
# print("SHUTDOWN", self)
self.stop_threads(wait=False)
lib.dc_close(self._dc_context)
self.stop_threads(wait=wait) # to wait for threads
deltachat.clear_context_callback(self._dc_context)
del self._dc_context
atexit.unregister(self.shutdown)
def _process_event(self, ctx, evt_name, data1, data2):
assert ctx == self._dc_context
self._evlogger(evt_name, data1, data2)
method = getattr(self, "on_" + evt_name.lower(), None)
if method is not None:
method(data1, data2)
if hasattr(self, "_evlogger"):
self._evlogger(evt_name, data1, data2)
method = getattr(self, "on_" + evt_name.lower(), None)
if method is not None:
method(data1, data2)
return 0
def on_dc_event_imex_progress(self, data1, data2):
if data1 == 1000:
self._imex_completed.set()
self._imex_events.put(True)
elif data1 == 0:
self._imex_events.put(False)
def on_dc_event_imex_file_written(self, data1, data2):
self._imex_events.put(data1)
def set_location(self, latitude=0.0, longitude=0.0, accuracy=0.0):
"""set a new location. It effects all chats where we currently
have enabled location streaming.
:param latitude: float (use 0.0 if not known)
:param longitude: float (use 0.0 if not known)
:param accuracy: float (use 0.0 if not known)
:raises: ValueError if no chat is currently streaming locations
:returns: None
"""
dc_res = lib.dc_set_location(self._dc_context, latitude, longitude, accuracy)
if dc_res == 0:
raise ValueError("no chat is streaming locations")
class IOThreads:
def __init__(self, dc_context):
def __init__(self, dc_context, log_event=lambda *args: None):
self._dc_context = dc_context
self._thread_quitflag = False
self._name2thread = {}
self._log_event = log_event
def is_started(self):
return len(self._name2thread) > 0
def start(self, imap=True, smtp=True):
def start(self, imap=True, smtp=True, mvbox=False, sentbox=False):
assert not self.is_started()
if imap:
self._start_one_thread("imap", self.imap_thread_run)
self._start_one_thread("inbox", self.imap_thread_run)
if mvbox:
self._start_one_thread("mvbox", self.mvbox_thread_run)
if sentbox:
self._start_one_thread("sentbox", self.sentbox_thread_run)
if smtp:
self._start_one_thread("smtp", self.smtp_thread_run)
@@ -371,20 +563,44 @@ class IOThreads:
self._thread_quitflag = True
lib.dc_interrupt_imap_idle(self._dc_context)
lib.dc_interrupt_smtp_idle(self._dc_context)
lib.dc_interrupt_mvbox_idle(self._dc_context)
lib.dc_interrupt_sentbox_idle(self._dc_context)
if wait:
for name, thread in self._name2thread.items():
thread.join()
def imap_thread_run(self):
self._log_event("py-bindings-info", 0, "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):
self._log_event("py-bindings-info", 0, "MVBOX THREAD START")
while not self._thread_quitflag:
lib.dc_perform_mvbox_jobs(self._dc_context)
lib.dc_perform_mvbox_fetch(self._dc_context)
lib.dc_perform_mvbox_idle(self._dc_context)
self._log_event("py-bindings-info", 0, "MVBOX THREAD FINISHED")
def sentbox_thread_run(self):
self._log_event("py-bindings-info", 0, "SENTBOX THREAD START")
while not self._thread_quitflag:
lib.dc_perform_sentbox_jobs(self._dc_context)
lib.dc_perform_sentbox_fetch(self._dc_context)
lib.dc_perform_sentbox_idle(self._dc_context)
self._log_event("py-bindings-info", 0, "SENTBOX THREAD FINISHED")
def smtp_thread_run(self):
self._log_event("py-bindings-info", 0, "SMTP THREAD START")
while not self._thread_quitflag:
lib.dc_perform_smtp_jobs(self._dc_context)
lib.dc_perform_smtp_idle(self._dc_context)
self._log_event("py-bindings-info", 0, "SMTP THREAD FINISHED")
class EventLogger:
@@ -407,6 +623,10 @@ class EventLogger:
def set_timeout(self, timeout):
self._timeout = timeout
def consume_events(self, check_error=True):
while not self._event_queue.empty():
self.get()
def get(self, timeout=None, check_error=True):
timeout = timeout or self._timeout
ev = self._event_queue.get(timeout=timeout)
@@ -414,11 +634,22 @@ class EventLogger:
raise ValueError("{}({!r},{!r})".format(*ev))
return ev
def get_matching(self, event_name_regex):
def ensure_event_not_queued(self, event_name_regex):
__tracebackhide__ = True
rex = re.compile("(?:{}).*".format(event_name_regex))
while 1:
try:
ev = self._event_queue.get(False)
except Empty:
break
else:
assert not rex.match(ev[0]), "event found {}".format(ev)
def get_matching(self, event_name_regex, check_error=True, timeout=None):
self._log("-- waiting for event with regex: {} --".format(event_name_regex))
rex = re.compile("(?:{}).*".format(event_name_regex))
while 1:
ev = self.get()
ev = self.get(timeout=timeout, check_error=check_error)
if rex.match(ev[0]):
return ev
@@ -455,3 +686,18 @@ def _destroy_dc_context(dc_context, dc_context_unref=lib.dc_context_unref):
# we are deep into Python Interpreter shutdown,
# so no need to clear the callback context mapping.
pass
class ScannedQRCode:
def __init__(self, dc_lot):
self._dc_lot = dc_lot
def is_ask_verifycontact(self):
return self._dc_lot.state() == const.DC_QR_ASK_VERIFYCONTACT
def is_ask_verifygroup(self):
return self._dc_lot.state() == const.DC_QR_ASK_VERIFYGROUP
@property
def contact_id(self):
return self._dc_lot.id()

View File

@@ -0,0 +1,409 @@
""" Chat and Location related API. """
import mimetypes
import calendar
import json
from datetime import datetime
import os
from .cutil import as_dc_charpointer, from_dc_charpointer, iter_array
from .capi import lib, ffi
from . import const
from .message import Message
class Chat(object):
""" Chat object which manages members and through which you can send and retrieve messages.
You obtain instances of it through :class:`deltachat.account.Account`.
"""
def __init__(self, account, id):
self.account = account
self._dc_context = account._dc_context
self.id = id
def __eq__(self, other):
return self.id == getattr(other, "id", None) and \
self._dc_context == getattr(other, "_dc_context", None)
def __ne__(self, other):
return not (self == other)
def __repr__(self):
return "<Chat id={} name={} dc_context={}>".format(self.id, self.get_name(), self._dc_context)
@property
def _dc_chat(self):
return ffi.gc(
lib.dc_get_chat(self._dc_context, self.id),
lib.dc_chat_unref
)
def delete(self):
"""Delete this chat and all its messages.
Note:
- does not delete messages on server
- the chat or contact is not blocked, new message will arrive
"""
lib.dc_delete_chat(self._dc_context, self.id)
# ------ chat status/metadata API ------------------------------
def is_deaddrop(self):
""" return true if this chat is a deaddrop chat.
:returns: True if chat is the deaddrop chat, False otherwise.
"""
return self.id == const.DC_CHAT_ID_DEADDROP
def is_promoted(self):
""" return True if this chat is promoted, i.e.
the member contacts are aware of their membership,
have been sent messages.
:returns: True if chat is promoted, False otherwise.
"""
return not lib.dc_chat_is_unpromoted(self._dc_chat)
def is_verified(self):
""" return True if this chat is a verified group.
:returns: True if chat is verified, False otherwise.
"""
return lib.dc_chat_is_verified(self._dc_chat)
def get_name(self):
""" return name of this chat.
:returns: unicode name
"""
return from_dc_charpointer(lib.dc_chat_get_name(self._dc_chat))
def set_name(self, name):
""" set name of this chat.
:param: name as a unicode string.
:returns: None
"""
name = as_dc_charpointer(name)
return lib.dc_set_chat_name(self._dc_context, self.id, name)
def get_type(self):
""" return type of this chat.
:returns: one of const.DC_CHAT_TYPE_*
"""
return lib.dc_chat_get_type(self._dc_chat)
def get_join_qr(self):
""" get/create Join-Group QR Code as ascii-string.
this string needs to be transferred to another DC account
in a second channel (typically used by mobiles with QRcode-show + scan UX)
where account.join_with_qrcode(qr) needs to be called.
"""
res = lib.dc_get_securejoin_qr(self._dc_context, self.id)
return from_dc_charpointer(res)
# ------ chat messaging API ------------------------------
def send_text(self, text):
""" send a text message and return the resulting Message instance.
:param msg: unicode text
:raises ValueError: if message can not be send/chat does not exist.
:returns: the resulting :class:`deltachat.message.Message` instance
"""
msg = as_dc_charpointer(text)
msg_id = lib.dc_send_text_msg(self._dc_context, self.id, msg)
if msg_id == 0:
raise ValueError("message could not be send, does chat exist?")
return Message.from_db(self.account, msg_id)
def send_file(self, path, mime_type="application/octet-stream"):
""" send a file and return the resulting Message instance.
:param path: path to the file.
:param mime_type: the mime-type of this file, defaults to application/octet-stream.
: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
def send_image(self, path):
""" send an image message and return the resulting Message instance.
:param path: path to an image file.
:raises ValueError: if message can not be send/chat does not exist.
: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
def prepare_message(self, msg):
""" create a new prepared message.
:param msg: the message to be prepared.
:returns: :class:`deltachat.message.Message` instance.
"""
msg_id = lib.dc_prepare_msg(self._dc_context, self.id, msg._dc_msg)
if msg_id == 0:
raise ValueError("message could not be prepared")
# invalidate passed in message which is not safe to use anymore
msg._dc_msg = msg.id = None
return Message.from_db(self.account, msg_id)
def prepare_message_file(self, path, mime_type=None, view_type="file"):
""" prepare a message for sending and return the resulting Message instance.
To actually send the message, call :meth:`send_prepared`.
The file must be inside the blob directory.
:param path: path to the file.
:param mime_type: the mime-type of this file, defaults to auto-detection.
:param view_type: "text", "image", "gif", "audio", "video", "file"
:raises ValueError: if message can not be prepared/chat does not exist.
:returns: the resulting :class:`Message` instance
"""
msg = Message.new_empty(self.account, view_type)
msg.set_file(path, mime_type)
return self.prepare_message(msg)
def send_prepared(self, message):
""" send a previously prepared message.
:param message: a :class:`Message` instance previously returned by
:meth:`prepare_file`.
:raises ValueError: if message can not be sent.
:returns: a :class:`deltachat.message.Message` instance as sent out.
"""
assert message.id != 0 and message.is_out_preparing()
# get a fresh copy of dc_msg, the core needs it
msg = Message.from_db(self.account, message.id)
# pass 0 as chat-id because core-docs say it's ok when out-preparing
sent_id = lib.dc_send_msg(self._dc_context, 0, msg._dc_msg)
if sent_id == 0:
raise ValueError("message could not be sent")
assert sent_id == msg.id
# modify message in place to avoid bad state for the caller
msg._dc_msg = Message.from_db(self.account, sent_id)._dc_msg
def set_draft(self, message):
""" set message as draft.
:param message: a :class:`Message` instance
:returns: None
"""
if message is None:
lib.dc_set_draft(self._dc_context, self.id, ffi.NULL)
else:
lib.dc_set_draft(self._dc_context, self.id, message._dc_msg)
def get_draft(self):
""" get draft message for this chat.
:param message: a :class:`Message` instance
:returns: Message object or None (if no draft available)
"""
x = lib.dc_get_draft(self._dc_context, self.id)
if x == ffi.NULL:
return None
dc_msg = ffi.gc(x, lib.dc_msg_unref)
return Message(self.account, dc_msg)
def get_messages(self):
""" return list of messages in this chat.
:returns: list of :class:`deltachat.message.Message` objects for this chat.
"""
dc_array = ffi.gc(
lib.dc_get_chat_msgs(self._dc_context, self.id, 0, 0),
lib.dc_array_unref
)
return list(iter_array(dc_array, lambda x: Message.from_db(self.account, x)))
def count_fresh_messages(self):
""" return number of fresh messages in this chat.
:returns: number of fresh messages
"""
return lib.dc_get_fresh_msg_cnt(self._dc_context, self.id)
def mark_noticed(self):
""" mark all messages in this chat as noticed.
Noticed messages are no longer fresh.
"""
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):
""" add a contact to this chat.
:params: contact object.
:raises ValueError: if contact could not be added
:returns: None
"""
ret = lib.dc_add_contact_to_chat(self._dc_context, self.id, contact.id)
if ret != 1:
raise ValueError("could not add contact {!r} to chat".format(contact))
def remove_contact(self, contact):
""" remove a contact from this chat.
:params: contact object.
:raises ValueError: if contact could not be removed
:returns: None
"""
ret = lib.dc_remove_contact_from_chat(self._dc_context, self.id, contact.id)
if ret != 1:
raise ValueError("could not remove contact {!r} from chat".format(contact))
def get_contacts(self):
""" get all contacts for this chat.
:params: contact object.
:returns: list of :class:`deltachat.contact.Contact` objects for this chat
"""
from .contact import Contact
dc_array = ffi.gc(
lib.dc_get_chat_contacts(self._dc_context, self.id),
lib.dc_array_unref
)
return list(iter_array(
dc_array, lambda id: Contact(self._dc_context, id))
)
def set_profile_image(self, img_path):
"""Set group profile image.
If the group is already promoted (any message was sent to the group),
all group members are informed by a special status message that is sent
automatically by this function.
:params img_path: path to image object
:raises ValueError: if profile image could not be set
:returns: None
"""
assert os.path.exists(img_path), img_path
p = as_dc_charpointer(img_path)
res = lib.dc_set_chat_profile_image(self._dc_context, self.id, p)
if res != 1:
raise ValueError("Setting Profile Image {!r} failed".format(p))
def remove_profile_image(self):
"""remove group profile image.
If the group is already promoted (any message was sent to the group),
all group members are informed by a special status message that is sent
automatically by this function.
:raises ValueError: if profile image could not be reset
:returns: None
"""
res = lib.dc_set_chat_profile_image(self._dc_context, self.id, ffi.NULL)
if res != 1:
raise ValueError("Removing Profile Image failed")
def get_profile_image(self):
"""Get group profile image.
For groups, this is the image set by any group member using
set_chat_profile_image(). For normal chats, this is the image
set by each remote user on their own using dc_set_config(context,
"selfavatar", image).
:returns: path to profile image, None if no profile image exists.
"""
dc_res = lib.dc_chat_get_profile_image(self._dc_chat)
if dc_res == ffi.NULL:
return None
return from_dc_charpointer(dc_res)
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):
"""return True if this chat has location-sending enabled currently.
:returns: True if location sending is enabled.
"""
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.
all subsequent messages will carry a location with them.
"""
lib.dc_send_locations_to_chat(self._dc_context, self.id, seconds)
def get_locations(self, contact=None, timestamp_from=None, timestamp_to=None):
"""return list of locations for the given contact in the given timespan.
:param contact: the contact for which locations shall be returned.
:param timespan_from: a datetime object or None (indicating "since beginning")
:param timespan_to: a datetime object or None (indicating up till now)
:returns: list of :class:`deltachat.chat.Location` objects.
"""
if timestamp_from is None:
time_from = 0
else:
time_from = calendar.timegm(timestamp_from.utctimetuple())
if timestamp_to is None:
time_to = 0
else:
time_to = calendar.timegm(timestamp_to.utctimetuple())
if contact is None:
contact_id = 0
else:
contact_id = contact.id
dc_array = lib.dc_get_locations(self._dc_context, self.id, contact_id, time_from, time_to)
return [
Location(
latitude=lib.dc_array_get_latitude(dc_array, i),
longitude=lib.dc_array_get_longitude(dc_array, i),
accuracy=lib.dc_array_get_accuracy(dc_array, i),
timestamp=datetime.utcfromtimestamp(lib.dc_array_get_timestamp(dc_array, i)))
for i in range(lib.dc_array_get_cnt(dc_array))
]
class Location:
def __init__(self, latitude, longitude, accuracy, timestamp):
assert isinstance(timestamp, datetime)
self.latitude = latitude
self.longitude = longitude
self.accuracy = accuracy
self.timestamp = timestamp
def __eq__(self, other):
return self.__dict__ == other.__dict__

View File

@@ -1,258 +0,0 @@
""" chatting related objects: Contact, Chat, Message. """
import os
from . import props
from .cutil import as_dc_charpointer, from_dc_charpointer, iter_array
from .capi import lib, ffi
from . import const
import attr
from attr import validators as v
from .message import Message
@attr.s
class Contact(object):
""" Delta-Chat Contact.
You obtain instances of it through :class:`deltachat.account.Account`.
"""
_dc_context = attr.ib(validator=v.instance_of(ffi.CData))
id = attr.ib(validator=v.instance_of(int))
@property
def _dc_contact(self):
return ffi.gc(
lib.dc_get_contact(self._dc_context, self.id),
lib.dc_contact_unref
)
@props.with_doc
def addr(self):
""" normalized e-mail address for this account. """
return from_dc_charpointer(lib.dc_contact_get_addr(self._dc_contact))
@props.with_doc
def display_name(self):
""" display name for this contact. """
return from_dc_charpointer(lib.dc_contact_get_display_name(self._dc_contact))
def is_blocked(self):
""" Return True if the contact is blocked. """
return lib.dc_contact_is_blocked(self._dc_contact)
def is_verified(self):
""" Return True if the contact is verified. """
return lib.dc_contact_is_verified(self._dc_contact)
@attr.s
class Chat(object):
""" Chat object which manages members and through which you can send and retrieve messages.
You obtain instances of it through :class:`deltachat.account.Account`.
"""
_dc_context = attr.ib(validator=v.instance_of(ffi.CData))
id = attr.ib(validator=v.instance_of(int))
@property
def _dc_chat(self):
return ffi.gc(
lib.dc_get_chat(self._dc_context, self.id),
lib.dc_chat_unref
)
def delete(self):
"""Delete this chat and all its messages.
Note:
- does not delete messages on server
- the chat or contact is not blocked, new message will arrive
"""
lib.dc_delete_chat(self._dc_context, self.id)
# ------ chat status/metadata API ------------------------------
def is_deaddrop(self):
""" return true if this chat is a deaddrop chat.
:returns: True if chat is the deaddrop chat, False otherwise.
"""
return self.id == const.DC_CHAT_ID_DEADDROP
def is_promoted(self):
""" return True if this chat is promoted, i.e.
the member contacts are aware of their membership,
have been sent messages.
:returns: True if chat is promoted, False otherwise.
"""
return not lib.dc_chat_is_unpromoted(self._dc_chat)
def get_name(self):
""" return name of this chat.
:returns: unicode name
"""
return from_dc_charpointer(lib.dc_chat_get_name(self._dc_chat))
def set_name(self, name):
""" set name of this chat.
:param: name as a unicode string.
:returns: None
"""
name = as_dc_charpointer(name)
return lib.dc_set_chat_name(self._dc_context, self.id, name)
def get_type(self):
""" return type of this chat.
:returns: one of const.DC_CHAT_TYPE_*
"""
return lib.dc_chat_get_type(self._dc_chat)
# ------ chat messaging API ------------------------------
def send_text(self, text):
""" send a text message and return the resulting Message instance.
:param msg: unicode text
:raises ValueError: if message can not be send/chat does not exist.
:returns: the resulting :class:`deltachat.message.Message` instance
"""
msg = as_dc_charpointer(text)
msg_id = lib.dc_send_text_msg(self._dc_context, self.id, msg)
if msg_id == 0:
raise ValueError("message could not be send, does chat exist?")
return Message.from_db(self._dc_context, msg_id)
def send_file(self, path, mime_type="application/octet-stream"):
""" send a file and return the resulting Message instance.
:param path: path to the file.
:param mime_type: the mime-type of this file, defaults to application/octet-stream.
:raises ValueError: if message can not be send/chat does not exist.
:returns: the resulting :class:`deltachat.message.Message` instance
"""
path = as_dc_charpointer(path)
mtype = as_dc_charpointer(mime_type)
msg = Message.new(self._dc_context, "file")
msg.set_file(path, mtype)
msg_id = lib.dc_send_msg(self._dc_context, self.id, msg._dc_msg)
if msg_id == 0:
raise ValueError("message could not be send, does chat exist?")
return Message.from_db(self._dc_context, msg_id)
def send_image(self, path):
""" send an image message and return the resulting Message instance.
:param path: path to an image file.
:raises ValueError: if message can not be send/chat does not exist.
:returns: the resulting :class:`deltachat.message.Message` instance
"""
if not os.path.exists(path):
raise ValueError("path does not exist: {!r}".format(path))
msg = Message.new(self._dc_context, "image")
msg.set_file(path)
msg_id = lib.dc_send_msg(self._dc_context, self.id, msg._dc_msg)
return Message.from_db(self._dc_context, msg_id)
def prepare_file(self, path, mime_type=None, view_type="file"):
""" prepare a message for sending and return the resulting Message instance.
To actually send the message, call :meth:`send_prepared`.
The file must be inside the blob directory.
:param path: path to the file.
:param mime_type: the mime-type of this file, defaults to auto-detection.
:param view_type: passed to :meth:`MessageType.new`.
:raises ValueError: if message can not be prepared/chat does not exist.
:returns: the resulting :class:`Message` instance
"""
path = as_dc_charpointer(path)
mtype = as_dc_charpointer(mime_type)
msg = Message.new(self._dc_context, view_type)
msg.set_file(path, mtype)
msg_id = lib.dc_prepare_msg(self._dc_context, self.id, msg._dc_msg)
if msg_id == 0:
raise ValueError("message could not be prepared, does chat exist?")
return Message.from_db(self._dc_context, msg_id)
def send_prepared(self, message):
""" send a previously prepared message.
:param message: a :class:`Message` instance previously returned by
:meth:`prepare_file`.
:raises ValueError: if message can not be sent.
:returns: a :class:`deltachat.message.Message` instance with updated state
"""
msg_id = lib.dc_send_msg(self._dc_context, 0, message._dc_msg)
if msg_id == 0:
raise ValueError("message could not be sent")
return Message.from_db(self._dc_context, msg_id)
def get_messages(self):
""" return list of messages in this chat.
:returns: list of :class:`deltachat.message.Message` objects for this chat.
"""
dc_array = ffi.gc(
lib.dc_get_chat_msgs(self._dc_context, self.id, 0, 0),
lib.dc_array_unref
)
return list(iter_array(dc_array, lambda x: Message.from_db(self._dc_context, x)))
def count_fresh_messages(self):
""" return number of fresh messages in this chat.
:returns: number of fresh messages
"""
return lib.dc_get_fresh_msg_cnt(self._dc_context, self.id)
def mark_noticed(self):
""" mark all messages in this chat as noticed.
Noticed messages are no longer fresh.
"""
return lib.dc_marknoticed_chat(self._dc_context, self.id)
# ------ group management API ------------------------------
def add_contact(self, contact):
""" add a contact to this chat.
:params: contact object.
:raises ValueError: if contact could not be added
:returns: None
"""
ret = lib.dc_add_contact_to_chat(self._dc_context, self.id, contact.id)
if ret != 1:
raise ValueError("could not add contact {!r} to chat".format(contact))
def remove_contact(self, contact):
""" remove a contact from this chat.
:params: contact object.
:raises ValueError: if contact could not be removed
:returns: None
"""
ret = lib.dc_remove_contact_from_chat(self._dc_context, self.id, contact.id)
if ret != 1:
raise ValueError("could not remove contact {!r} from chat".format(contact))
def get_contacts(self):
""" get all contacts for this chat.
:params: contact object.
:raises ValueError: if contact could not be added
:returns: list of :class:`deltachat.chatting.Contact` objects for this chat
"""
dc_array = ffi.gc(
lib.dc_get_chat_contacts(self._dc_context, self.id),
lib.dc_array_unref
)
return list(iter_array(
dc_array, lambda id: Contact(self._dc_context, id))
)

View File

@@ -13,6 +13,15 @@ DC_GCL_NO_SPECIALS = 0x02
DC_GCL_ADD_ALLDONE_HINT = 0x04
DC_GCL_VERIFIED_ONLY = 0x01
DC_GCL_ADD_SELF = 0x02
DC_QR_ASK_VERIFYCONTACT = 200
DC_QR_ASK_VERIFYGROUP = 202
DC_QR_FPR_OK = 210
DC_QR_FPR_MISMATCH = 220
DC_QR_FPR_WITHOUT_ADDR = 230
DC_QR_ADDR = 320
DC_QR_TEXT = 330
DC_QR_URL = 332
DC_QR_ERROR = 400
DC_CHAT_ID_DEADDROP = 1
DC_CHAT_ID_TRASH = 3
DC_CHAT_ID_MSGS_IN_CREATION = 4
@@ -38,19 +47,40 @@ DC_STATE_OUT_FAILED = 24
DC_STATE_OUT_DELIVERED = 26
DC_STATE_OUT_MDN_RCVD = 28
DC_CONTACT_ID_SELF = 1
DC_CONTACT_ID_DEVICE = 2
DC_CONTACT_ID_INFO = 2
DC_CONTACT_ID_DEVICE = 5
DC_CONTACT_ID_LAST_SPECIAL = 9
DC_MSG_TEXT = 10
DC_MSG_IMAGE = 20
DC_MSG_GIF = 21
DC_MSG_STICKER = 23
DC_MSG_AUDIO = 40
DC_MSG_VOICE = 41
DC_MSG_VIDEO = 50
DC_MSG_FILE = 60
DC_LP_AUTH_OAUTH2 = 0x2
DC_LP_AUTH_NORMAL = 0x4
DC_LP_IMAP_SOCKET_STARTTLS = 0x100
DC_LP_IMAP_SOCKET_SSL = 0x200
DC_LP_IMAP_SOCKET_PLAIN = 0x400
DC_LP_SMTP_SOCKET_STARTTLS = 0x10000
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
DC_EVENT_INFO = 100
DC_EVENT_SMTP_CONNECTED = 101
DC_EVENT_IMAP_CONNECTED = 102
DC_EVENT_SMTP_MESSAGE_SENT = 103
DC_EVENT_IMAP_MESSAGE_DELETED = 104
DC_EVENT_IMAP_MESSAGE_MOVED = 105
DC_EVENT_IMAP_FOLDER_EMPTIED = 106
DC_EVENT_NEW_BLOB_FILE = 150
DC_EVENT_DELETED_BLOB_FILE = 151
DC_EVENT_WARNING = 300
DC_EVENT_ERROR = 400
DC_EVENT_ERROR_NETWORK = 401
@@ -68,16 +98,67 @@ 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_GET_STRING = 2091
DC_EVENT_HTTP_GET = 2100
DC_EVENT_HTTP_POST = 2110
DC_EVENT_SECUREJOIN_MEMBER_ADDED = 2062
DC_EVENT_FILE_COPIED = 2055
DC_EVENT_IS_OFFLINE = 2081
DC_EVENT_GET_STRING = 2091
DC_STR_SELFNOTINGRP = 21
DC_PROVIDER_STATUS_OK = 1
DC_PROVIDER_STATUS_PREPARATION = 2
DC_PROVIDER_STATUS_BROKEN = 3
DC_STR_NOMESSAGES = 1
DC_STR_SELF = 2
DC_STR_DRAFT = 3
DC_STR_MEMBER = 4
DC_STR_CONTACT = 6
DC_STR_VOICEMESSAGE = 7
DC_STR_DEADDROP = 8
DC_STR_IMAGE = 9
DC_STR_VIDEO = 10
DC_STR_AUDIO = 11
DC_STR_FILE = 12
DC_STR_STATUSLINE = 13
DC_STR_NEWGROUPDRAFT = 14
DC_STR_MSGGRPNAME = 15
DC_STR_MSGGRPIMGCHANGED = 16
DC_STR_MSGADDMEMBER = 17
DC_STR_MSGDELMEMBER = 18
DC_STR_MSGGROUPLEFT = 19
DC_STR_GIF = 23
DC_STR_ENCRYPTEDMSG = 24
DC_STR_E2E_AVAILABLE = 25
DC_STR_ENCR_TRANSP = 27
DC_STR_ENCR_NONE = 28
DC_STR_CANTDECRYPT_MSG_BODY = 29
DC_STR_FINGERPRINTS = 30
DC_STR_READRCPT = 31
DC_STR_READRCPT_MAILBODY = 32
DC_STR_MSGGRPIMGDELETED = 33
DC_STR_E2E_PREFERRED = 34
DC_STR_CONTACT_VERIFIED = 35
DC_STR_CONTACT_NOT_VERIFIED = 36
DC_STR_CONTACT_SETUP_CHANGED = 37
DC_STR_ARCHIVEDCHATS = 40
DC_STR_STARREDMSGS = 41
DC_STR_AC_SETUP_MSG_SUBJECT = 42
DC_STR_AC_SETUP_MSG_BODY = 43
DC_STR_SELFTALK_SUBTITLE = 50
DC_STR_CANNOT_LOGIN = 60
DC_STR_SERVER_RESPONSE = 61
DC_STR_MSGACTIONBYUSER = 62
DC_STR_MSGACTIONBYME = 63
DC_STR_MSGLOCATIONENABLED = 64
DC_STR_MSGLOCATIONDISABLED = 65
DC_STR_LOCATION = 66
DC_STR_STICKER = 67
DC_STR_DEVICE_MESSAGES = 68
DC_STR_COUNT = 68
# end const generated
def read_event_defines(f):
rex = re.compile(r'#define\s+((?:DC_EVENT_|DC_MSG|DC_STATE_|DC_CONTACT_ID_|DC_GCL|DC_CHAT)\S+)\s+([x\d]+).*')
rex = re.compile(r'#define\s+((?:DC_EVENT|DC_QR|DC_MSG|DC_LP|DC_EMPTY|DC_CERTCK|DC_STATE|DC_STR|'
r'DC_CONTACT_ID|DC_GCL|DC_CHAT|DC_PROVIDER)_\S+)\s+([x\d]+).*')
for line in f:
m = rex.match(line)
if m:
@@ -90,7 +171,7 @@ if __name__ == "__main__":
if len(sys.argv) >= 2:
deltah = sys.argv[1]
else:
deltah = joinpath(dirname(dirname(dirname(here_dir))), "src", "deltachat.h")
deltah = joinpath(dirname(dirname(dirname(here_dir))), "deltachat-ffi", "deltachat.h")
assert os.path.exists(deltah)
lines = []

View File

@@ -0,0 +1,49 @@
""" Contact object. """
from . import props
from .cutil import from_dc_charpointer
from .capi import lib, ffi
class Contact(object):
""" Delta-Chat Contact.
You obtain instances of it through :class:`deltachat.account.Account`.
"""
def __init__(self, dc_context, id):
self._dc_context = dc_context
self.id = id
def __eq__(self, other):
return self._dc_context == other._dc_context and self.id == other.id
def __ne__(self, other):
return not (self == other)
def __repr__(self):
return "<Contact id={} addr={} dc_context={}>".format(self.id, self.addr, self._dc_context)
@property
def _dc_contact(self):
return ffi.gc(
lib.dc_get_contact(self._dc_context, self.id),
lib.dc_contact_unref
)
@props.with_doc
def addr(self):
""" normalized e-mail address for this account. """
return from_dc_charpointer(lib.dc_contact_get_addr(self._dc_contact))
@props.with_doc
def display_name(self):
""" display name for this contact. """
return from_dc_charpointer(lib.dc_contact_get_display_name(self._dc_contact))
def is_blocked(self):
""" Return True if the contact is blocked. """
return lib.dc_contact_is_blocked(self._dc_contact)
def is_verified(self):
""" Return True if the contact is verified. """
return lib.dc_contact_is_verified(self._dc_contact)

View File

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

View File

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

View File

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

View File

@@ -1,9 +1,10 @@
from __future__ import print_function
import os
import pytest
import requests
import time
from deltachat import Account
from deltachat import props
from deltachat import const
from deltachat.capi import lib
import tempfile
@@ -16,18 +17,37 @@ def pytest_addoption(parser):
)
def pytest_configure(config):
cfg = config.getoption('--liveconfig')
if not cfg:
cfg = os.getenv('DCC_PY_LIVECONFIG')
if cfg:
config.option.liveconfig = cfg
def pytest_report_header(config, startdir):
summary = []
t = tempfile.mktemp()
try:
ac = Account(t)
ac = Account(t, eventlogging=False)
info = ac.get_info()
del ac
ac.shutdown()
finally:
os.remove(t)
return "Deltachat core={} sqlite={}".format(
info['deltachat_core_version'],
info['sqlite_version'],
)
summary.extend(['Deltachat core={} sqlite={}'.format(
info['deltachat_core_version'],
info['sqlite_version'],
)])
cfg = config.option.liveconfig
if cfg:
if "#" in cfg:
url, token = cfg.split("#", 1)
summary.append('Liveconfig provider: {}#<token ommitted>'.format(url))
else:
summary.append('Liveconfig file: {}'.format(cfg))
return summary
@pytest.fixture(scope="session")
@@ -43,16 +63,62 @@ def data():
return Data()
class SessionLiveConfigFromFile:
def __init__(self, fn):
self.fn = fn
self.configlist = []
for line in open(fn):
if line.strip() and not line.strip().startswith('#'):
d = {}
for part in line.split():
name, value = part.split("=")
d[name] = value
self.configlist.append(d)
def get(self, index):
return self.configlist[index]
def exists(self):
return bool(self.configlist)
class SessionLiveConfigFromURL:
def __init__(self, url, create_token):
self.configlist = []
for i in range(2):
res = requests.post(url, json={"token_create_user": int(create_token)})
if res.status_code != 200:
pytest.skip("creating newtmpuser failed {!r}".format(res))
d = res.json()
config = dict(addr=d["email"], mail_pw=d["password"])
self.configlist.append(config)
def get(self, index):
return self.configlist[index]
def exists(self):
return bool(self.configlist)
@pytest.fixture(scope="session")
def session_liveconfig(request):
liveconfig_opt = request.config.option.liveconfig
if liveconfig_opt:
if liveconfig_opt.startswith("http"):
url, create_token = liveconfig_opt.split("#", 1)
return SessionLiveConfigFromURL(url, create_token)
else:
return SessionLiveConfigFromFile(liveconfig_opt)
@pytest.fixture
def acfactory(pytestconfig, tmpdir, request):
fn = pytestconfig.getoption("--liveconfig")
def acfactory(pytestconfig, tmpdir, request, session_liveconfig):
class AccountMaker:
def __init__(self):
self.live_count = 0
self.offline_count = 0
self._finalizers = []
request.addfinalizer(self.finalize)
self.init_time = time.time()
def finalize(self):
@@ -60,22 +126,15 @@ def acfactory(pytestconfig, tmpdir, request):
fin = self._finalizers.pop()
fin()
@props.cached
def configlist(self):
configlist = []
for line in open(fn):
if line.strip():
d = {}
for part in line.split():
name, value = part.split("=")
d[name] = value
configlist.append(d)
return configlist
def make_account(self, path, logid):
ac = Account(path, logid=logid)
self._finalizers.append(ac.shutdown)
return ac
def get_unconfigured_account(self):
self.offline_count += 1
tmpdb = tmpdir.join("offlinedb%d" % self.offline_count)
ac = Account(tmpdb.strpath, logid="ac{}".format(self.offline_count))
ac = self.make_account(tmpdb.strpath, logid="ac{}".format(self.offline_count))
ac._evlogger.init_time = self.init_time
ac._evlogger.set_timeout(2)
return ac
@@ -92,32 +151,63 @@ def acfactory(pytestconfig, tmpdir, request):
lib.dc_set_config(ac._dc_context, b"configured", b"1")
return ac
def get_online_configuring_account(self):
if not fn:
pytest.skip("specify a --liveconfig file to run tests with real accounts")
def peek_online_config(self):
if not session_liveconfig:
pytest.skip("specify DCC_PY_LIVECONFIG or --liveconfig")
return session_liveconfig.get(self.live_count)
def get_online_config(self):
if not session_liveconfig:
pytest.skip("specify DCC_PY_LIVECONFIG or --liveconfig")
configdict = session_liveconfig.get(self.live_count)
self.live_count += 1
configdict = self.configlist.pop(0)
if "e2ee_enabled" not in configdict:
configdict["e2ee_enabled"] = "1"
# Enable strict certificate checks for online accounts
configdict["imap_certificate_checks"] = str(const.DC_CERTCK_STRICT)
configdict["smtp_certificate_checks"] = str(const.DC_CERTCK_STRICT)
tmpdb = tmpdir.join("livedb%d" % self.live_count)
ac = Account(tmpdb.strpath, logid="ac{}".format(self.live_count))
ac = self.make_account(tmpdb.strpath, logid="ac{}".format(self.live_count))
ac._evlogger.init_time = self.init_time
ac._evlogger.set_timeout(30)
return ac, dict(configdict)
def get_online_configuring_account(self, mvbox=False, sentbox=False):
ac, configdict = self.get_online_config()
ac.configure(**configdict)
ac.start_threads()
self._finalizers.append(lambda: ac.stop_threads(wait=False))
ac.start_threads(mvbox=mvbox, sentbox=sentbox)
return ac
def get_one_online_account(self):
ac1 = self.get_online_configuring_account()
wait_successful_IMAP_SMTP_connection(ac1)
wait_configuration_progress(ac1, 1000)
return ac1
def get_two_online_accounts(self):
ac1 = self.get_online_configuring_account()
ac2 = self.get_online_configuring_account()
wait_successful_IMAP_SMTP_connection(ac1)
wait_configuration_progress(ac1, 1000)
wait_successful_IMAP_SMTP_connection(ac2)
wait_configuration_progress(ac2, 1000)
return ac1, ac2
def clone_online_account(self, account):
self.live_count += 1
tmpdb = tmpdir.join("livedb%d" % self.live_count)
ac = Account(tmpdb.strpath, logid="ac{}".format(self.live_count))
ac = self.make_account(tmpdb.strpath, logid="ac{}".format(self.live_count))
ac._evlogger.init_time = self.init_time
ac._evlogger.set_timeout(30)
ac.configure(addr=account.get_config("addr"), mail_pw=account.get_config("mail_pw"))
ac.start_threads()
self._finalizers.append(lambda: ac.stop_threads(wait=False))
return ac
return AccountMaker()
am = AccountMaker()
request.addfinalizer(am.finalize)
return am
@pytest.fixture
@@ -137,12 +227,22 @@ def lp():
return Printer()
def wait_configuration_progress(account, target):
def wait_configuration_progress(account, min_target, max_target=1001):
min_target = min(min_target, max_target)
while 1:
evt_name, data1, data2 = \
account._evlogger.get_matching("DC_EVENT_CONFIGURE_PROGRESS")
if data1 >= target:
print("** CONFIG PROGRESS {}".format(target), account)
if data1 >= min_target and data1 <= max_target:
print("** CONFIG PROGRESS {}".format(min_target), account)
break
def wait_securejoin_inviter_progress(account, target):
while 1:
evt_name, data1, data2 = \
account._evlogger.get_matching("DC_EVENT_SECUREJOIN_INVITER_PROGRESS")
if data2 >= target:
print("** SECUREJOINT-INVITER PROGRESS {}".format(target), account)
break

File diff suppressed because it is too large Load Diff

View File

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

View File

@@ -1,6 +1,8 @@
from __future__ import print_function
import pytest
from deltachat import capi, Account, const
from deltachat import capi, cutil, const, set_context_callback, clear_context_callback
from deltachat.capi import ffi
from deltachat.capi import lib
from deltachat.account import EventLogger
def test_empty_context():
@@ -8,10 +10,63 @@ def test_empty_context():
capi.lib.dc_close(ctx)
def test_callback_None2int():
ctx = capi.lib.dc_context_new(capi.lib.py_dc_callback, ffi.NULL, ffi.NULL)
set_context_callback(ctx, lambda *args: None)
capi.lib.dc_close(ctx)
clear_context_callback(ctx)
def test_dc_close_events(tmpdir):
ctx = ffi.gc(
capi.lib.dc_context_new(capi.lib.py_dc_callback, ffi.NULL, ffi.NULL),
lib.dc_context_unref,
)
evlog = EventLogger(ctx)
evlog.set_timeout(5)
set_context_callback(
ctx,
lambda ctx, evt_name, data1, data2: evlog(evt_name, data1, data2)
)
p = tmpdir.join("hello.db")
lib.dc_open(ctx, p.strpath.encode("ascii"), ffi.NULL)
capi.lib.dc_close(ctx)
def find(info_string):
while 1:
ev = evlog.get_matching("DC_EVENT_INFO", check_error=False)
data2 = ev[2]
if info_string in data2:
return
else:
print("skipping event", *ev)
find("disconnecting inbox-thread")
find("disconnecting sentbox-thread")
find("disconnecting mvbox-thread")
find("disconnecting SMTP")
find("Database closed")
def test_wrong_db(tmpdir):
tmpdir.join("hello.db").write("123")
with pytest.raises(ValueError):
Account(db_path=tmpdir.strpath)
dc_context = ffi.gc(
lib.dc_context_new(lib.py_dc_callback, ffi.NULL, ffi.NULL),
lib.dc_context_unref,
)
p = tmpdir.join("hello.db")
# write an invalid database file
p.write("x123" * 10)
assert not lib.dc_open(dc_context, p.strpath.encode("ascii"), ffi.NULL)
def test_empty_blobdir(tmpdir):
# Apparently some client code expects this to be the same as passing NULL.
ctx = ffi.gc(
lib.dc_context_new(lib.py_dc_callback, ffi.NULL, ffi.NULL),
lib.dc_context_unref,
)
db_fname = tmpdir.join("hello.db")
assert lib.dc_open(ctx, db_fname.strpath.encode("ascii"), b"")
def test_event_defines():
@@ -27,3 +82,76 @@ def test_sig():
assert sig(const.DC_EVENT_SMTP_CONNECTED) == 2
assert sig(const.DC_EVENT_IMAP_CONNECTED) == 2
assert sig(const.DC_EVENT_SMTP_MESSAGE_SENT) == 2
def test_markseen_invalid_message_ids(acfactory):
ac1 = acfactory.get_configured_offline_account()
contact1 = ac1.create_contact(email="some1@example.com", name="some1")
chat = ac1.create_chat_by_contact(contact1)
chat.send_text("one messae")
ac1._evlogger.get_matching("DC_EVENT_MSGS_CHANGED")
msg_ids = [9]
lib.dc_markseen_msgs(ac1._dc_context, msg_ids, len(msg_ids))
ac1._evlogger.ensure_event_not_queued("DC_EVENT_WARNING|DC_EVENT_ERROR")
def test_get_special_message_id_returns_empty_message(acfactory):
ac1 = acfactory.get_configured_offline_account()
for i in range(1, 10):
msg = ac1.get_message_by_id(i)
assert msg.id == 0
def test_provider_info():
provider = lib.dc_provider_new_from_email(cutil.as_dc_charpointer("ex@example.com"))
assert cutil.from_dc_charpointer(
lib.dc_provider_get_overview_page(provider)
) == "https://providers.delta.chat/example.com"
assert cutil.from_dc_charpointer(lib.dc_provider_get_name(provider)) == "Example"
assert cutil.from_dc_charpointer(lib.dc_provider_get_markdown(provider)) == "\n..."
assert cutil.from_dc_charpointer(lib.dc_provider_get_status_date(provider)) == "2018-09"
assert lib.dc_provider_get_status(provider) == const.DC_PROVIDER_STATUS_PREPARATION
def test_provider_info_none():
assert lib.dc_provider_new_from_email(cutil.as_dc_charpointer("email@unexistent.no")) == ffi.NULL
def test_get_info_closed():
ctx = ffi.gc(
lib.dc_context_new(lib.py_dc_callback, ffi.NULL, ffi.NULL),
lib.dc_context_unref,
)
info = cutil.from_dc_charpointer(lib.dc_get_info(ctx))
assert 'deltachat_core_version' in info
assert 'database_dir' not in info
def test_get_info_open(tmpdir):
ctx = ffi.gc(
lib.dc_context_new(lib.py_dc_callback, ffi.NULL, ffi.NULL),
lib.dc_context_unref,
)
db_fname = tmpdir.join("test.db")
lib.dc_open(ctx, db_fname.strpath.encode("ascii"), ffi.NULL)
info = cutil.from_dc_charpointer(lib.dc_get_info(ctx))
assert 'deltachat_core_version' in info
assert 'database_dir' in info
def test_is_open_closed():
ctx = ffi.gc(
lib.dc_context_new(lib.py_dc_callback, ffi.NULL, ffi.NULL),
lib.dc_context_unref,
)
assert lib.dc_is_open(ctx) == 0
def test_is_open_actually_open(tmpdir):
ctx = ffi.gc(
lib.dc_context_new(lib.py_dc_callback, ffi.NULL, ffi.NULL),
lib.dc_context_unref,
)
db_fname = tmpdir.join("test.db")
lib.dc_open(ctx, db_fname.strpath.encode("ascii"), ffi.NULL)
assert lib.dc_is_open(ctx) == 1

View File

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

View File

@@ -1,32 +1,38 @@
[tox]
# make sure to update environment list in travis.yml and appveyor.yml
envlist =
py27
py35
py37
lint
auditwheels
[testenv]
commands =
pytest -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-faulthandler
pytest-rerunfailures
pytest-timeout
pytest-xdist
pdbpp
requests
[testenv:auditwheels]
skipsdist = True
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
@@ -38,20 +44,33 @@ commands =
rst-lint --encoding 'utf-8' README.rst
[testenv:doc]
basepython = python3.5
changedir=doc
deps =
sphinx==2.0.1
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
python_files = tests/test_*.py
norecursedirs = .tox
xfail_strict=true
timeout = 60
timeout_method = thread
[flake8]
max-line-length = 120

View File

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

View File

@@ -1 +1 @@
nightly-2019-07-10
nightly-2019-11-06

63
set_core_version.py Normal file
View File

@@ -0,0 +1,63 @@
#!/usr/bin/env python
import os
import sys
import re
import pathlib
import subprocess
rex = re.compile(r'version = "(\S+)"')
def read_toml_version(relpath):
p = pathlib.Path(relpath)
assert p.exists()
for line in open(str(p)):
m = rex.match(line)
if m is not None:
return m.group(1)
raise ValueError("no version found in {}".format(relpath))
def replace_toml_version(relpath, newversion):
p = pathlib.Path(relpath)
assert p.exists()
tmp_path = str(p) + "_tmp"
with open(tmp_path, "w") as f:
for line in open(str(p)):
m = rex.match(line)
if m is not None:
f.write('version = "{}"\n'.format(newversion))
else:
f.write(line)
os.rename(tmp_path, str(p))
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:
raise SystemExit("need at least two dots in version")
core_toml = read_toml_version("Cargo.toml")
ffi_toml = read_toml_version("deltachat-ffi/Cargo.toml")
assert core_toml == ffi_toml, (core_toml, ffi_toml)
for line in open("CHANGELOG.md"):
## 1.0.0-beta5
if line.startswith("## "):
if line[2:].strip().startswith(newversion):
break
else:
raise SystemExit("CHANGELOG.md contains no entry for version: {}".format(newversion))
replace_toml_version("Cargo.toml", newversion)
replace_toml_version("deltachat-ffi/Cargo.toml", newversion)
subprocess.call(["cargo", "update", "-p", "deltachat"])
print("after commit make sure to: ")
print("")
print(" git tag {}".format(newversion))
print("")

371
spec.md Normal file
View File

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

View File

@@ -1,13 +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::dc_contact::*;
use crate::dc_tools::as_str;
use crate::contact::*;
use crate::context::Context;
use crate::key::*;
/// Possible values for encryption preference
@@ -41,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,
@@ -56,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,
@@ -64,69 +66,54 @@ impl Aheader {
}
}
pub fn from_imffields(
wanted_from: *const libc::c_char,
header: *const mailimf_fields,
pub fn from_headers(
context: &Context,
wanted_from: &str,
headers: &[mailparse::MailHeader<'_>],
) -> Option<Self> {
if wanted_from.is_null() || header.is_null() {
return None;
}
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_str().unwrap() }
== "Autocrypt"
{
let value = unsafe {
CStr::from_ptr((*optional_field).fld_value)
.to_str()
.unwrap()
};
match Self::from_str(value) {
Ok(test) => {
if dc_addr_cmp(&test.addr, as_str(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)
}
}
@@ -135,9 +122,9 @@ impl str::FromStr for Aheader {
fn from_str(s: &str) -> Result<Self, Self::Err> {
let mut attributes: BTreeMap<String, String> = s
.split(";")
.split(';')
.filter_map(|a| {
let attribute: Vec<&str> = a.trim().splitn(2, "=").collect();
let attribute: Vec<&str> = a.trim().splitn(2, '=').collect();
if attribute.len() < 2 {
return None;
}
@@ -172,17 +159,14 @@ 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
if attributes.keys().find(|k| !k.starts_with("_")).is_some() {
if attributes.keys().any(|k| !k.starts_with('_')) {
return Err(());
}
@@ -198,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");
@@ -215,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");
@@ -228,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");
}
@@ -266,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"));
}
}

595
src/blob.rs Normal file
View File

@@ -0,0 +1,595 @@
//! # Blob directory management
use std::ffi::OsStr;
use std::fmt;
use std::fs;
use std::io::Write;
use std::path::{Path, PathBuf};
use crate::context::Context;
use crate::events::Event;
/// Represents a file in the blob directory.
///
/// The object has a name, which will always be valid UTF-8. Having a
/// blob object does not imply the respective file exists, however
/// when using one of the `create*()` methods a unique file is
/// created.
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct BlobObject<'a> {
blobdir: &'a Path,
name: String,
}
impl<'a> BlobObject<'a> {
/// Creates a new blob object with a unique name.
///
/// Creates a new file in the blob directory. The name will be
/// derived from the platform-agnostic basename of the suggested
/// name, followed by a random number and followed by a possible
/// extension. The `data` will be written into the file without
/// race-conditions.
///
/// # Errors
///
/// [BlobError::CreateFailure] is used when the file could not
/// be created. You can expect [BlobError.cause] to contain an
/// underlying error.
///
/// [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(
context: &'a Context,
suggested_name: impl AsRef<str>,
data: &[u8],
) -> std::result::Result<BlobObject<'a>, BlobError> {
let blobdir = context.get_blobdir();
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::WriteFailure {
blobdir: blobdir.to_path_buf(),
blobname: name.clone(),
cause: err,
backtrace: failure::Backtrace::new(),
})?;
let blob = BlobObject {
blobdir,
name: format!("$BLOBDIR/{}", name),
};
context.call_cb(Event::NewBlobFile(blob.as_name().to_string()));
Ok(blob)
}
// Creates a new file, returning a tuple of the name and the handle.
fn create_new_file(dir: &Path, stem: &str, ext: &str) -> Result<(String, fs::File), BlobError> {
let max_attempt = 15;
let mut name = format!("{}{}", stem, ext);
for attempt in 0..max_attempt {
let path = dir.join(&name);
match fs::OpenOptions::new()
.create_new(true)
.write(true)
.open(&path)
{
Ok(file) => return Ok((name, file)),
Err(err) => {
if attempt == max_attempt {
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);
}
}
}
}
// 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.
///
/// This creates a new blob as described in [BlobObject::create]
/// but also copies an existing file into it. This is done in a
/// in way which avoids race-conditions when multiple files are
/// concurrently created.
///
/// # Errors
///
/// In addition to the errors in [BlobObject::create] the
/// [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::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_for_err);
fs::remove_file(path).ok();
}
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(),
name: format!("$BLOBDIR/{}", name),
};
context.call_cb(Event::NewBlobFile(blob.as_name().to_string()));
Ok(blob)
}
/// Creates a blob from a file, possibly copying it to the blobdir.
///
/// If the source file is not a path to into the blob directory
/// the file will be copied into the blob directory first. If the
/// source file is already in the blobdir it will not be copied
/// and only be created if it is a valid blobname, that is no
/// subdirectory is used and [BlobObject::sanitise_name] does not
/// modify the filename.
///
/// # Errors
///
/// 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(
context: &Context,
src: impl AsRef<Path>,
) -> std::result::Result<BlobObject, BlobError> {
if src.as_ref().starts_with(context.get_blobdir()) {
BlobObject::from_path(context, src)
} else {
BlobObject::create_and_copy(context, src)
}
}
/// Returns a [BlobObject] for an existing blob from a path.
///
/// The path must designate a file directly in the blobdir and
/// must use a valid blob name. That is after sanitisation the
/// name must still be the same, that means it must be valid UTF-8
/// and not have any special characters in it.
///
/// # Errors
///
/// [BlobError::WrongBlobdir] is used if the path is not in
/// the blob directory.
///
/// [BlobError::WrongName] is used if the file name does not
/// remain identical after sanitisation.
pub fn from_path(
context: &Context,
path: impl AsRef<Path>,
) -> std::result::Result<BlobObject, BlobError> {
let rel_path = path
.as_ref()
.strip_prefix(context.get_blobdir())
.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::WrongName {
blobname: path.as_ref().to_path_buf(),
backtrace: failure::Backtrace::new(),
});
}
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())
}
/// Returns a [BlobObject] for an existing blob.
///
/// The `name` may optionally be prefixed with the `$BLOBDIR/`
/// prefixed, as returned by [BlobObject::as_name]. This is how
/// you want to create a [BlobObject] for a filename read from the
/// database.
///
/// # Errors
///
/// [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(
context: &'a Context,
name: String,
) -> std::result::Result<BlobObject<'a>, BlobError> {
let name: String = match name.starts_with("$BLOBDIR/") {
true => name.splitn(2, '/').last().unwrap().to_string(),
false => name,
};
if !BlobObject::is_acceptible_blob_name(&name) {
return Err(BlobError::WrongName {
blobname: PathBuf::from(name),
backtrace: failure::Backtrace::new(),
});
}
Ok(BlobObject {
blobdir: context.get_blobdir(),
name: format!("$BLOBDIR/{}", name),
})
}
/// Returns the absolute path to the blob in the filesystem.
pub fn to_abs_path(&self) -> PathBuf {
let fname = Path::new(&self.name).strip_prefix("$BLOBDIR/").unwrap();
self.blobdir.join(fname)
}
/// Returns the blob name, as stored in the database.
///
/// This returns the blob in the `$BLOBDIR/<name>` format used in
/// the database. Do not use this unless you're about to store
/// this string in the database or [Params]. Eventually even
/// those conversions should be handled by the type system.
///
/// [Params]: crate::param::Params
pub fn as_name(&self) -> &str {
&self.name
}
/// Returns the filename of the blob.
pub fn as_file_name(&self) -> &str {
self.name.rsplitn(2, '/').next().unwrap()
}
/// The path relative in the blob directory.
pub fn as_rel_path(&self) -> &Path {
Path::new(self.as_file_name())
}
/// Returns the extension of the blob.
///
/// If a blob's filename has an extension, it is always guaranteed
/// to be lowercase.
pub fn suffix(&self) -> Option<&str> {
let ext = self.name.rsplitn(2, '.').next();
if ext == Some(&self.name) {
None
} else {
ext
}
}
/// Create a safe name based on a messy input string.
///
/// The safe name will be a valid filename on Unix and Windows and
/// not contain any path separators. The input can contain path
/// segments separated by either Unix or Windows path separators,
/// the rightmost non-empty segment will be used as name,
/// sanitised for special characters.
///
/// The resulting name is returned as a tuple, the first part
/// being the stem or basename and the second being an extension,
/// including the dot. E.g. "foo.txt" is returned as `("foo",
/// ".txt")` while "bar" is returned as `("bar", "")`.
///
/// The extension part will always be lowercased.
fn sanitise_name(name: &str) -> (String, String) {
let mut name = name.to_string();
for part in name.rsplit('/') {
if !part.is_empty() {
name = part.to_string();
break;
}
}
for part in name.rsplit('\\') {
if !part.is_empty() {
name = part.to_string();
break;
}
}
let opts = sanitize_filename::Options {
truncate: true,
windows: true,
replacement: "",
};
let clean = sanitize_filename::sanitize_with_options(name, opts);
let mut iter = clean.splitn(2, '.');
let mut stem = iter.next().unwrap_or_default().to_string();
let mut ext = iter.next().unwrap_or_default().to_string();
stem.truncate(64);
ext.truncate(32);
match ext.len() {
0 => (stem, "".to_string()),
_ => (stem, format!(".{}", ext).to_lowercase()),
}
}
/// Checks whether a name is a valid blob name.
///
/// This is slightly less strict than stanitise_name, presumably
/// someone already created a file with such a name so we just
/// ensure it's not actually a path in disguise is actually utf-8.
fn is_acceptible_blob_name(name: impl AsRef<OsStr>) -> bool {
let uname = match name.as_ref().to_str() {
Some(name) => name,
None => return false,
};
if uname.find('/').is_some() {
return false;
}
if uname.find('\\').is_some() {
return false;
}
if uname.find('\0').is_some() {
return false;
}
true
}
}
impl<'a> fmt::Display for BlobObject<'a> {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "$BLOBDIR/{}", self.name)
}
}
/// Errors for the [BlobObject].
#[derive(Fail, Debug)]
pub enum BlobError {
CreateFailure {
blobdir: PathBuf,
blobname: String,
#[cause]
cause: std::io::Error,
backtrace: failure::Backtrace,
},
WriteFailure {
blobdir: PathBuf,
blobname: String,
#[cause]
cause: std::io::Error,
backtrace: failure::Backtrace,
},
CopyFailure {
blobdir: PathBuf,
blobname: String,
src: PathBuf,
#[cause]
cause: std::io::Error,
backtrace: failure::Backtrace,
},
WrongBlobdir {
blobdir: PathBuf,
src: PathBuf,
backtrace: failure::Backtrace,
},
WrongName {
blobname: PathBuf,
backtrace: failure::Backtrace,
},
}
// 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 {
BlobError::CreateFailure {
blobdir, blobname, ..
} => write!(
f,
"Failed to create blob {} in {}",
blobname,
blobdir.display()
),
BlobError::WriteFailure {
blobdir, blobname, ..
} => write!(
f,
"Failed to write data to blob {} in {}",
blobname,
blobdir.display()
),
BlobError::CopyFailure {
blobdir,
blobname,
src,
..
} => write!(
f,
"Failed to copy data from {} to blob {} in {}",
src.display(),
blobname,
blobdir.display(),
),
BlobError::WrongBlobdir { blobdir, src, .. } => write!(
f,
"File path {} is not in blobdir {}",
src.display(),
blobdir.display(),
),
BlobError::WrongName { blobname, .. } => {
write!(f, "Blob has a bad name: {}", blobname.display(),)
}
}
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::test_utils::*;
#[test]
fn test_create() {
let t = dummy_context();
let blob = BlobObject::create(&t.ctx, "foo", b"hello").unwrap();
let fname = t.ctx.get_blobdir().join("foo");
let data = fs::read(fname).unwrap();
assert_eq!(data, b"hello");
assert_eq!(blob.as_name(), "$BLOBDIR/foo");
assert_eq!(blob.to_abs_path(), t.ctx.get_blobdir().join("foo"));
}
#[test]
fn test_lowercase_ext() {
let t = dummy_context();
let blob = BlobObject::create(&t.ctx, "foo.TXT", b"hello").unwrap();
assert_eq!(blob.as_name(), "$BLOBDIR/foo.txt");
}
#[test]
fn test_as_file_name() {
let t = dummy_context();
let blob = BlobObject::create(&t.ctx, "foo.txt", b"hello").unwrap();
assert_eq!(blob.as_file_name(), "foo.txt");
}
#[test]
fn test_as_rel_path() {
let t = dummy_context();
let blob = BlobObject::create(&t.ctx, "foo.txt", b"hello").unwrap();
assert_eq!(blob.as_rel_path(), Path::new("foo.txt"));
}
#[test]
fn test_suffix() {
let t = dummy_context();
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_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_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"));
assert!(name.ends_with(".txt"));
}
}
}
#[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();
let s = "1".repeat(150);
let blob = BlobObject::create(&t.ctx, &s, b"data").unwrap();
let blobname = blob.as_name().split('/').last().unwrap();
assert!(blobname.len() < 128);
}
#[test]
fn test_create_and_copy() {
let t = dummy_context();
let src = t.dir.path().join("src");
fs::write(&src, b"boo").unwrap();
let blob = BlobObject::create_and_copy(&t.ctx, &src).unwrap();
assert_eq!(blob.as_name(), "$BLOBDIR/src");
let data = fs::read(blob.to_abs_path()).unwrap();
assert_eq!(data, b"boo");
let whoops = t.dir.path().join("whoops");
assert!(BlobObject::create_and_copy(&t.ctx, &whoops).is_err());
let whoops = t.ctx.get_blobdir().join("whoops");
assert!(!whoops.exists());
}
#[test]
fn test_create_from_path() {
let t = dummy_context();
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();
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();
assert_eq!(blob.as_name(), "$BLOBDIR/internal");
let data = fs::read(blob.to_abs_path()).unwrap();
assert_eq!(data, b"boo");
}
#[test]
fn test_create_from_name_long() {
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();
assert_eq!(
blob.as_name(),
"$BLOBDIR/autocrypt-setup-message-4137848473.html"
);
}
#[test]
fn test_is_blob_name() {
assert!(BlobObject::is_acceptible_blob_name("foo"));
assert!(BlobObject::is_acceptible_blob_name("foo.txt"));
assert!(BlobObject::is_acceptible_blob_name("f".repeat(128)));
assert!(!BlobObject::is_acceptible_blob_name("foo/bar"));
assert!(!BlobObject::is_acceptible_blob_name("foo\\bar"));
assert!(!BlobObject::is_acceptible_blob_name("foo\x00bar"));
}
}

2415
src/chat.rs Normal file

File diff suppressed because it is too large Load Diff

387
src/chatlist.rs Normal file
View File

@@ -0,0 +1,387 @@
//! # 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, MessageState, MsgId};
use crate::stock::StockMessage;
/// An object representing a single chatlist in memory.
///
/// Chatlist objects contain chat IDs and, if possible, message IDs belonging to them.
/// The chatlist object is not updated; if you want an update, you have to recreate the object.
///
/// For a **typical chat overview**, the idea is to get the list of all chats via dc_get_chatlist()
/// without any listflags (see below) and to implement a "virtual list" or so
/// (the count of chats is known by chatlist.len()).
///
/// Only for the items that are in view (the list may have several hundreds chats),
/// the UI should call chatlist.get_summary() then.
/// chatlist.get_summary() provides all elements needed for painting the item.
///
/// On a click of such an item, the UI should change to the chat view
/// and get all messages from this view via dc_get_chat_msgs().
/// Again, a "virtual list" is created (the count of messages is known)
/// and for each messages that is scrolled into view, dc_get_msg() is called then.
///
/// Why no listflags?
/// Without listflags, dc_get_chatlist() adds the deaddrop and the archive "link" automatically as needed.
/// The UI can just render these items differently then. Although the deaddrop link is currently always the
/// first entry and only present on new messages, there is the rough idea that it can be optionally always
/// present and sorted into the list by date. Rendering the deaddrop in the described way
/// would not add extra work in the UI then.
#[derive(Debug)]
pub struct Chatlist {
/// Stores pairs of `chat_id, message_id`
ids: Vec<(u32, MsgId)>,
}
impl Chatlist {
/// Get a list of chats.
/// The list can be filtered by query parameters.
///
/// The list is already sorted and starts with the most recent chat in use.
/// The sorting takes care of invalid sending dates, drafts and chats without messages.
/// Clients should not try to re-sort the list as this would be an expensive action
/// and would result in inconsistencies between clients.
///
/// To get information about each entry, use eg. chatlist.get_summary().
///
/// By default, the function adds some special entries to the list.
/// These special entries can be identified by the ID returned by chatlist.get_chat_id():
/// - DC_CHAT_ID_DEADDROP (1) - this special chat is present if there are
/// messages from addresses that have no relationship to the configured account.
/// The last of these messages is represented by DC_CHAT_ID_DEADDROP and you can retrieve details
/// about it with chatlist.get_msg_id(). Typically, the UI asks the user "Do you want to chat with NAME?"
/// and offers the options "Yes" (call dc_create_chat_by_msg_id()), "Never" (call dc_block_contact())
/// or "Not now".
/// The UI can also offer a "Close" button that calls dc_marknoticed_contact() then.
/// - DC_CHAT_ID_ARCHIVED_LINK (6) - this special chat is present if the user has
/// archived *any* chat using dc_archive_chat(). The UI should show a link as
/// "Show archived chats", if the user clicks this item, the UI should show a
/// list of all archived chats that can be created by this function hen using
/// the DC_GCL_ARCHIVED_ONLY flag.
/// - DC_CHAT_ID_ALLDONE_HINT (7) - this special chat is present
/// if DC_GCL_ADD_ALLDONE_HINT is added to listflags
/// and if there are only archived chats.
///
/// The `listflags` is a combination of flags:
/// - if the flag DC_GCL_ARCHIVED_ONLY is set, only archived chats are returned.
/// if DC_GCL_ARCHIVED_ONLY is not set, only unarchived chats are returned and
/// the pseudo-chat DC_CHAT_ID_ARCHIVED_LINK is added if there are *any* archived
/// chats
/// - if the flag DC_GCL_NO_SPECIALS is set, deaddrop and archive link are not added
/// to the list (may be used eg. for selecting chats on forwarding, the flag is
/// not needed when DC_GCL_ARCHIVED_ONLY is already set)
/// - if the flag DC_GCL_ADD_ALLDONE_HINT is set, DC_CHAT_ID_ALLDONE_HINT
/// is added as needed.
/// `query`: An optional query for filtering the list. Only chats matching this query
/// are returned.
/// `query_contact_id`: An optional contact ID for filtering the list. Only chats including this contact ID
/// are returned.
pub fn try_load(
context: &Context,
listflags: usize,
query: Option<&str>,
query_contact_id: Option<u32>,
) -> Result<Self> {
let mut add_archived_link_item = false;
let process_row = |row: &rusqlite::Row| {
let chat_id: u32 = row.get(0)?;
let msg_id: MsgId = row.get(1).unwrap_or_default();
Ok((chat_id, msg_id))
};
let process_rows = |rows: rusqlite::MappedRows<_>| {
rows.collect::<std::result::Result<Vec<_>, _>>()
.map_err(Into::into)
};
// select with left join and minimum:
//
// - the inner select must use `hidden` and _not_ `m.hidden`
// which would refer the outer select and take a lot of time
// - `GROUP BY` is needed several messages may have the same
// timestamp
// - the list starts with the newest chats
//
// nb: the query currently shows messages from blocked
// contacts in groups. however, for normal-groups, this is
// okay as the message is also returned by dc_get_chat_msgs()
// (otherwise it would be hard to follow conversations, wa and
// tg do the same) for the deaddrop, however, they should
// really be hidden, however, _currently_ the deaddrop is not
// shown at all permanent in the chatlist.
let mut ids = if let Some(query_contact_id) = query_contact_id {
// show chats shared with a given contact
context.sql.query_map(
"SELECT c.id, m.id
FROM chats c
LEFT JOIN msgs m
ON c.id=m.chat_id
AND m.timestamp=(
SELECT MAX(timestamp)
FROM msgs
WHERE chat_id=c.id
AND (hidden=0 OR 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(
"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,
)?
} else if let Some(query) = query {
let query = query.trim().to_string();
ensure!(!query.is_empty(), "missing query");
let str_like_cmd = format!("%{}%", query);
context.sql.query_map(
"SELECT c.id, m.id
FROM chats c
LEFT JOIN msgs m
ON c.id=m.chat_id
AND m.timestamp=(
SELECT MAX(timestamp)
FROM msgs
WHERE chat_id=c.id
AND (hidden=0 OR 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(
"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,
)?;
if 0 == listflags & DC_GCL_NO_SPECIALS {
if let Some(last_deaddrop_fresh_msg_id) = get_last_deaddrop_fresh_msg(context) {
ids.insert(0, (DC_CHAT_ID_DEADDROP, last_deaddrop_fresh_msg_id));
}
add_archived_link_item = true;
}
ids
};
if add_archived_link_item && dc_get_archived_cnt(context) > 0 {
if ids.is_empty() && 0 != listflags & DC_GCL_ADD_ALLDONE_HINT {
ids.push((DC_CHAT_ID_ALLDONE_HINT, MsgId::new(0)));
}
ids.push((DC_CHAT_ID_ARCHIVED_LINK, MsgId::new(0)));
}
Ok(Chatlist { ids })
}
/// Find out the number of chats.
pub fn len(&self) -> usize {
self.ids.len()
}
/// Returns true if chatlist is empty.
pub fn is_empty(&self) -> bool {
self.ids.is_empty()
}
/// Get a single chat ID of a chatlist.
///
/// To get the message object from the message ID, use dc_get_chat().
pub fn get_chat_id(&self, index: usize) -> u32 {
if index >= self.ids.len() {
return 0;
}
self.ids[index].0
}
/// Get a single message ID of a chatlist.
///
/// To get the message object from the message ID, use dc_get_msg().
pub fn get_msg_id(&self, index: usize) -> Result<MsgId> {
ensure!(index < self.ids.len(), "Chatlist index out of range");
Ok(self.ids[index].1)
}
/// Get a summary for a chatlist index.
///
/// The summary is returned by a dc_lot_t object with the following fields:
///
/// - dc_lot_t::text1: contains the username or the strings "Me", "Draft" and so on.
/// The string may be colored by having a look at text1_meaning.
/// If there is no such name or it should not be displayed, the element is NULL.
/// - dc_lot_t::text1_meaning: one of DC_TEXT1_USERNAME, DC_TEXT1_SELF or DC_TEXT1_DRAFT.
/// Typically used to show dc_lot_t::text1 with different colors. 0 if not applicable.
/// - dc_lot_t::text2: contains an excerpt of the message text or strings as
/// "No messages". May be NULL of there is no such text (eg. for the archive link)
/// - dc_lot_t::timestamp: the timestamp of the message. 0 if not applicable.
/// - dc_lot_t::state: The state of the message as one of the DC_STATE_* constants (see #dc_msg_get_state()).
// 0 if not applicable.
pub fn get_summary(&self, context: &Context, index: usize, chat: Option<&Chat>) -> Lot {
// The summary is created by the chat, not by the last message.
// This is because we may want to display drafts here or stuff as
// "is typing".
// Also, sth. as "No messages" would not work if the summary comes from a message.
let mut ret = Lot::new();
if index >= self.ids.len() {
ret.text2 = Some("ErrBadChatlistIndex".to_string());
return ret;
}
let chat_loaded: Chat;
let chat = if let Some(chat) = chat {
chat
} else if let Ok(chat) = Chat::load_from_db(context, self.ids[index].0) {
chat_loaded = chat;
&chat_loaded
} else {
return ret;
};
let lastmsg_id = self.ids[index].1;
let mut lastcontact = None;
let lastmsg = if 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)
{
lastcontact = Contact::load_from_db(context, lastmsg.from_id).ok();
}
Some(lastmsg)
} else {
None
};
if chat.id == DC_CHAT_ID_ARCHIVED_LINK {
ret.text2 = None;
} else if lastmsg.is_none() || lastmsg.as_ref().unwrap().from_id == DC_CONTACT_ID_UNDEFINED
{
ret.text2 = Some(context.stock_str(StockMessage::NoMessages).to_string());
} else {
ret.fill(&mut lastmsg.unwrap(), chat, lastcontact.as_ref(), context);
}
ret
}
}
/// Get the number of archived chats
pub fn dc_get_archived_cnt(context: &Context) -> u32 {
context
.sql
.query_get_value(
context,
"SELECT COUNT(*) FROM chats WHERE blocked=0 AND archived=1;",
params![],
)
.unwrap_or_default()
}
fn get_last_deaddrop_fresh_msg(context: &Context) -> Option<MsgId> {
// We have an index over the state-column, this should be
// sufficient as there are typically only few fresh messages.
context.sql.query_get_value(
context,
concat!(
"SELECT m.id",
" FROM msgs m",
" LEFT JOIN chats c",
" ON c.id=m.chat_id",
" WHERE m.state=10",
" AND m.hidden=0",
" AND c.blocked=2",
" ORDER BY m.timestamp DESC, m.id DESC;"
),
params![],
)
}
#[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,13 +1,14 @@
//! # 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_job::*;
use crate::dc_stock::*;
use crate::dc_tools::*;
use crate::error::Error;
use crate::x::*;
use crate::job::*;
use crate::stock::StockMessage;
/// The available configuration keys.
#[derive(
@@ -20,46 +21,69 @@ pub enum Config {
MailUser,
MailPw,
MailPort,
ImapCertificateChecks,
SendServer,
SendUser,
SendPw,
SendPort,
SmtpCertificateChecks,
ServerFlags,
#[strum(props(default = "INBOX"))]
ImapFolder,
Displayname,
Selfstatus,
Selfavatar,
#[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"))]
#[strum(props(default = "0"))] // also change ShowEmails.default() on changes
ShowEmails,
SaveMimeHeaders,
ConfiguredAddr,
ConfiguredMailServer,
ConfiguredMailUser,
ConfiguredMailPw,
ConfiguredMailPort,
ConfiguredMailSecurity,
ConfiguredImapCertificateChecks,
ConfiguredSendServer,
ConfiguredSendUser,
ConfiguredSendPw,
ConfiguredSendPort,
ConfiguredSmtpCertificateChecks,
ConfiguredServerFlags,
ConfiguredSendSecurity,
ConfiguredE2EEEnabled,
Configured,
// Deprecated
#[strum(serialize = "sys.version")]
SysVersion,
#[strum(serialize = "sys.msgsize_max_recommended")]
SysMsgsizeMaxRecommended,
#[strum(serialize = "sys.config_keys")]
SysConfigKeys,
}
@@ -69,18 +93,13 @@ impl Context {
pub fn get_config(&self, key: Config) -> Option<String> {
let value = match key {
Config::Selfavatar => {
let rel_path = self.sql.get_config(self, key);
rel_path.map(|p| {
let v = unsafe { dc_get_abs_path(self, to_cstring(p).as_ptr()) };
let r = to_string(v);
unsafe { free(v as *mut _) };
r
})
let rel_path = self.sql.get_raw_config(self, key);
rel_path.map(|p| dc_get_abs_path(self, &p).to_string_lossy().into_owned())
}
Config::SysVersion => Some(std::str::from_utf8(DC_VERSION_STR).unwrap().into()),
Config::SysVersion => Some((&*DC_VERSION_STR).clone()),
Config::SysMsgsizeMaxRecommended => Some(format!("{}", 24 * 1024 * 1024 / 4 * 3)),
Config::SysConfigKeys => Some(get_config_keys_string()),
_ => self.sql.get_config(self, key),
_ => self.sql.get_raw_config(self, key),
};
if value.is_some() {
@@ -89,53 +108,55 @@ impl Context {
// Default values
match key {
Config::Selfstatus => {
let s = unsafe { dc_stock_str(self, 13) };
let res = to_string(s);
unsafe { free(s as *mut _) };
Some(res)
}
Config::Selfstatus => Some(self.stock_str(StockMessage::StatusLine).into_owned()),
_ => key.get_str("default").map(|s| s.to_string()),
}
}
pub fn get_config_int(&self, key: Config) -> i32 {
self.get_config(key)
.and_then(|s| s.parse().ok())
.unwrap_or_default()
}
pub fn get_config_bool(&self, key: Config) -> bool {
self.get_config_int(key) != 0
}
/// Set the given config key.
/// If `None` is passed as a value the value is cleared and set to the deafult if there is one.
pub fn set_config(&self, key: Config, value: Option<&str>) -> Result<(), Error> {
/// 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>) -> crate::sql::Result<()> {
match key {
Config::Selfavatar if value.is_some() => {
let rel_path = std::fs::canonicalize(value.unwrap())?;
self.sql
.set_config(self, key, Some(&rel_path.to_string_lossy()))
let blob = BlobObject::create_from_path(&self, value.unwrap())?;
self.sql.set_raw_config(self, key, Some(blob.as_name()))
}
Config::InboxWatch => {
let ret = self.sql.set_config(self, key, value);
unsafe { dc_interrupt_imap_idle(self) };
let ret = self.sql.set_raw_config(self, key, value);
interrupt_inbox_idle(self, true);
ret
}
Config::SentboxWatch => {
let ret = self.sql.set_config(self, key, value);
unsafe { dc_interrupt_sentbox_idle(self) };
let ret = self.sql.set_raw_config(self, key, value);
interrupt_sentbox_idle(self);
ret
}
Config::MvboxWatch => {
let ret = self.sql.set_config(self, key, value);
unsafe { dc_interrupt_mvbox_idle(self) };
let ret = self.sql.set_raw_config(self, key, value);
interrupt_mvbox_idle(self);
ret
}
Config::Selfstatus => {
let def = unsafe { dc_stock_str(self, 13) };
let val = if value.is_none() || value.unwrap() == as_str(def) {
let def = self.stock_str(StockMessage::StatusLine);
let val = if value.is_none() || value.unwrap() == def {
None
} else {
value
};
let ret = self.sql.set_config(self, key, val);
unsafe { free(def as *mut libc::c_void) };
ret
self.sql.set_raw_config(self, key, val)
}
_ => self.sql.set_config(self, key, value),
_ => self.sql.set_raw_config(self, key, value),
}
}
}
@@ -158,6 +179,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");
@@ -174,4 +197,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

@@ -0,0 +1,349 @@
//! # 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::login_param::LoginParam;
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,
pub in_emaillocalpart: &'a str,
pub out: LoginParam,
pub out_imap_set: bool,
pub out_smtp_set: bool,
pub tag_server: MozServer,
pub tag_config: MozConfigTag,
}
#[derive(Debug, PartialEq)]
enum MozServer {
Undefined,
Imap,
Smtp,
}
#[derive(Debug)]
enum MozConfigTag {
Undefined,
Hostname,
Port,
Sockettype,
Username,
}
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 = 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..];
let mut moz_ac = MozAutoconfigure {
in_emailaddr,
in_emaildomain,
in_emaillocalpart,
out: LoginParam::new(),
out_imap_set: false,
out_smtp_set: false,
tag_server: MozServer::Undefined,
tag_config: MozConfigTag::Undefined,
};
let mut buf = Vec::new();
loop {
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)
}
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)
}
quick_xml::events::Event::Eof => break,
_ => (),
}
buf.clear();
}
if moz_ac.out.mail_server.is_empty()
|| moz_ac.out.mail_port == 0
|| moz_ac.out.send_server.is_empty()
|| moz_ac.out.send_port == 0
{
Err(Error::IncompleteAutoconfig(moz_ac.out))
} else {
Ok(moz_ac.out)
}
}
pub fn moz_autoconfigure(
context: &Context,
url: &str,
param_in: &LoginParam,
) -> Result<LoginParam> {
let xml_raw = read_url(context, url)?;
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>(
event: &BytesText,
moz_ac: &mut MozAutoconfigure,
reader: &quick_xml::Reader<B>,
) {
let val = event.unescape_and_decode(reader).unwrap_or_default();
let addr = moz_ac.in_emailaddr;
let email_local = moz_ac.in_emaillocalpart;
let email_domain = moz_ac.in_emaildomain;
let val = val
.trim()
.replace("%EMAILADDRESS%", addr)
.replace("%EMAILLOCALPART%", email_local)
.replace("%EMAILDOMAIN%", email_domain);
match moz_ac.tag_server {
MozServer::Imap => match moz_ac.tag_config {
MozConfigTag::Hostname => moz_ac.out.mail_server = val,
MozConfigTag::Port => moz_ac.out.mail_port = val.parse().unwrap_or_default(),
MozConfigTag::Username => moz_ac.out.mail_user = val,
MozConfigTag::Sockettype => {
let val_lower = val.to_lowercase();
if val_lower == "ssl" {
moz_ac.out.server_flags |= DC_LP_IMAP_SOCKET_SSL as i32
}
if val_lower == "starttls" {
moz_ac.out.server_flags |= DC_LP_IMAP_SOCKET_STARTTLS as i32
}
if val_lower == "plain" {
moz_ac.out.server_flags |= DC_LP_IMAP_SOCKET_PLAIN as i32
}
}
_ => {}
},
MozServer::Smtp => match moz_ac.tag_config {
MozConfigTag::Hostname => moz_ac.out.send_server = val,
MozConfigTag::Port => moz_ac.out.send_port = val.parse().unwrap_or_default(),
MozConfigTag::Username => moz_ac.out.send_user = val,
MozConfigTag::Sockettype => {
let val_lower = val.to_lowercase();
if val_lower == "ssl" {
moz_ac.out.server_flags |= DC_LP_SMTP_SOCKET_SSL as i32
}
if val_lower == "starttls" {
moz_ac.out.server_flags |= DC_LP_SMTP_SOCKET_STARTTLS as i32
}
if val_lower == "plain" {
moz_ac.out.server_flags |= DC_LP_SMTP_SOCKET_PLAIN as i32
}
}
_ => {}
},
MozServer::Undefined => {}
}
}
fn moz_autoconfigure_endtag_cb(event: &BytesEnd, moz_ac: &mut MozAutoconfigure) {
let tag = String::from_utf8_lossy(event.name()).trim().to_lowercase();
if tag == "incomingserver" {
if moz_ac.tag_server == MozServer::Imap {
moz_ac.out_imap_set = true;
}
moz_ac.tag_server = MozServer::Undefined;
moz_ac.tag_config = MozConfigTag::Undefined;
} else if tag == "outgoingserver" {
if moz_ac.tag_server == MozServer::Smtp {
moz_ac.out_smtp_set = true;
}
moz_ac.tag_server = MozServer::Undefined;
moz_ac.tag_config = MozConfigTag::Undefined;
} else {
moz_ac.tag_config = MozConfigTag::Undefined;
}
}
fn moz_autoconfigure_starttag_cb<B: std::io::BufRead>(
event: &BytesStart,
moz_ac: &mut MozAutoconfigure,
reader: &quick_xml::Reader<B>,
) {
let tag = String::from_utf8_lossy(event.name()).trim().to_lowercase();
if tag == "incomingserver" {
moz_ac.tag_server = if let Some(typ) = event.attributes().find(|attr| {
attr.as_ref()
.map(|a| String::from_utf8_lossy(a.key).trim().to_lowercase() == "type")
.unwrap_or_default()
}) {
let typ = typ
.unwrap()
.unescape_and_decode_value(reader)
.unwrap_or_default()
.to_lowercase();
if typ == "imap" && !moz_ac.out_imap_set {
MozServer::Imap
} else {
MozServer::Undefined
}
} else {
MozServer::Undefined
};
moz_ac.tag_config = MozConfigTag::Undefined;
} else if tag == "outgoingserver" {
moz_ac.tag_server = if !moz_ac.out_smtp_set {
MozServer::Smtp
} else {
MozServer::Undefined
};
moz_ac.tag_config = MozConfigTag::Undefined;
} else if tag == "hostname" {
moz_ac.tag_config = MozConfigTag::Hostname;
} else if tag == "port" {
moz_ac.tag_config = MozConfigTag::Port;
} else if tag == "sockettype" {
moz_ac.tag_config = MozConfigTag::Sockettype;
} else if tag == "username" {
moz_ac.tag_config = MozConfigTag::Username;
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_parse_outlook_autoconfig() {
// Copied from https://autoconfig.thunderbird.net/v1.1/outlook.com on 2019-10-11
let xml_raw =
"<clientConfig version=\"1.1\">
<emailProvider id=\"outlook.com\">
<domain>hotmail.com</domain>
<domain>hotmail.co.uk</domain>
<domain>hotmail.co.jp</domain>
<domain>hotmail.com.br</domain>
<domain>hotmail.de</domain>
<domain>hotmail.fr</domain>
<domain>hotmail.it</domain>
<domain>hotmail.es</domain>
<domain>live.com</domain>
<domain>live.co.uk</domain>
<domain>live.co.jp</domain>
<domain>live.de</domain>
<domain>live.fr</domain>
<domain>live.it</domain>
<domain>live.jp</domain>
<domain>msn.com</domain>
<domain>outlook.com</domain>
<displayName>Outlook.com (Microsoft)</displayName>
<displayShortName>Outlook</displayShortName>
<incomingServer type=\"exchange\">
<hostname>outlook.office365.com</hostname>
<port>443</port>
<username>%EMAILADDRESS%</username>
<socketType>SSL</socketType>
<authentication>OAuth2</authentication>
<owaURL>https://outlook.office365.com/owa/</owaURL>
<ewsURL>https://outlook.office365.com/ews/exchange.asmx</ewsURL>
<useGlobalPreferredServer>true</useGlobalPreferredServer>
</incomingServer>
<incomingServer type=\"imap\">
<hostname>outlook.office365.com</hostname>
<port>993</port>
<socketType>SSL</socketType>
<authentication>password-cleartext</authentication>
<username>%EMAILADDRESS%</username>
</incomingServer>
<incomingServer type=\"pop3\">
<hostname>outlook.office365.com</hostname>
<port>995</port>
<socketType>SSL</socketType>
<authentication>password-cleartext</authentication>
<username>%EMAILADDRESS%</username>
<pop3>
<leaveMessagesOnServer>true</leaveMessagesOnServer>
<!-- Outlook.com docs specifically mention that POP3 deletes have effect on the main inbox on webmail and IMAP -->
</pop3>
</incomingServer>
<outgoingServer type=\"smtp\">
<hostname>smtp.office365.com</hostname>
<port>587</port>
<socketType>STARTTLS</socketType>
<authentication>password-cleartext</authentication>
<username>%EMAILADDRESS%</username>
</outgoingServer>
<documentation url=\"http://windows.microsoft.com/en-US/windows/outlook/send-receive-from-app\">
<descr lang=\"en\">Set up an email app with Outlook.com</descr>
</documentation>
</emailProvider>
<webMail>
<loginPage url=\"https://www.outlook.com/\"/>
<loginPageInfo url=\"https://www.outlook.com/\">
<username>%EMAILADDRESS%</username>
<usernameField id=\"i0116\" name=\"login\"/>
<passwordField id=\"i0118\" name=\"passwd\"/>
<loginButton id=\"idSIButton9\" name=\"SI\"/>
</loginPageInfo>
</webMail>
</clientConfig>";
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");
assert_eq!(res.send_port, 587);
}
}

View File

@@ -0,0 +1,269 @@
//! Outlook's Autodiscover
use quick_xml;
use quick_xml::events::BytesEnd;
use crate::constants::*;
use crate::context::Context;
use crate::login_param::LoginParam;
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)
}
}
struct OutlookAutodiscover {
pub out: LoginParam,
pub out_imap_set: bool,
pub out_smtp_set: bool,
pub config_type: Option<String>,
pub config_server: String,
pub config_port: i32,
pub config_ssl: String,
pub config_redirecturl: Option<String>,
}
enum ParsingResult {
LoginParam(LoginParam),
RedirectUrl(String),
}
fn parse_xml(xml_raw: &str) -> Result<ParsingResult> {
let mut outlk_ad = OutlookAutodiscover {
out: LoginParam::new(),
out_imap_set: false,
out_smtp_set: false,
config_type: None,
config_server: String::new(),
config_port: 0,
config_ssl: String::new(),
config_redirecturl: None,
};
let mut reader = quick_xml::Reader::from_str(&xml_raw);
reader.trim_text(true);
let mut buf = Vec::new();
let mut current_tag: Option<String> = None;
loop {
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" {
outlk_ad.config_type = None;
outlk_ad.config_server = String::new();
outlk_ad.config_port = 0;
outlk_ad.config_ssl = String::new();
outlk_ad.config_redirecturl = None;
current_tag = None;
} else {
current_tag = Some(tag);
}
}
quick_xml::events::Event::End(ref e) => {
outlk_autodiscover_endtag_cb(e, &mut outlk_ad);
current_tag = None;
}
quick_xml::events::Event::Text(ref e) => {
let val = e.unescape_and_decode(&reader).unwrap_or_default();
if let Some(ref tag) = current_tag {
match tag.as_str() {
"type" => {
outlk_ad.config_type = Some(val.trim().to_lowercase().to_string())
}
"server" => outlk_ad.config_server = val.trim().to_string(),
"port" => outlk_ad.config_port = val.trim().parse().unwrap_or_default(),
"ssl" => outlk_ad.config_ssl = val.trim().to_string(),
"redirecturl" => outlk_ad.config_redirecturl = Some(val.trim().to_string()),
_ => {}
};
}
}
quick_xml::events::Event::Eof => break,
_ => (),
}
buf.clear();
}
// XML redirect via redirecturl
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()
|| outlk_ad.out.mail_port == 0
|| outlk_ad.out.send_server.is_empty()
|| outlk_ad.out.send_port == 0
{
return Err(Error::IncompleteAutoconfig(outlk_ad.out));
}
ParsingResult::LoginParam(outlk_ad.out)
} else {
ParsingResult::RedirectUrl(outlk_ad.config_redirecturl.unwrap())
};
Ok(res)
}
pub fn outlk_autodiscover(
context: &Context,
url: &str,
_param_in: &LoginParam,
) -> Result<LoginParam> {
let mut url = url.to_string();
/* Follow up to 10 xml-redirects (http-redirects are followed in read_url() */
for _i in 0..10 {
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),
}
}
Err(Error::RedirectionError)
}
fn outlk_autodiscover_endtag_cb(event: &BytesEnd, outlk_ad: &mut OutlookAutodiscover) {
let tag = String::from_utf8_lossy(event.name()).trim().to_lowercase();
if tag == "protocol" {
if let Some(type_) = &outlk_ad.config_type {
let port = outlk_ad.config_port;
let ssl_on = outlk_ad.config_ssl == "on";
let ssl_off = outlk_ad.config_ssl == "off";
if type_ == "imap" && !outlk_ad.out_imap_set {
outlk_ad.out.mail_server =
std::mem::replace(&mut outlk_ad.config_server, String::new());
outlk_ad.out.mail_port = port;
if ssl_on {
outlk_ad.out.server_flags |= DC_LP_IMAP_SOCKET_SSL as i32
} else if ssl_off {
outlk_ad.out.server_flags |= DC_LP_IMAP_SOCKET_PLAIN as i32
}
outlk_ad.out_imap_set = true
} else if type_ == "smtp" && !outlk_ad.out_smtp_set {
outlk_ad.out.send_server =
std::mem::replace(&mut outlk_ad.config_server, String::new());
outlk_ad.out.send_port = outlk_ad.config_port;
if ssl_on {
outlk_ad.out.server_flags |= DC_LP_SMTP_SOCKET_SSL as i32
} else if ssl_off {
outlk_ad.out.server_flags |= DC_LP_SMTP_SOCKET_PLAIN as i32
}
outlk_ad.out_smtp_set = true
}
}
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_parse_redirect() {
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\">
<Account>
<AccountType>email</AccountType>
<Action>redirectUrl</Action>
<RedirectUrl>https://mail.example.com/autodiscover/autodiscover.xml</RedirectUrl>
</Account>
</Response>
</Autodiscover>
").expect("XML is not parsed successfully");
match res {
ParsingResult::LoginParam(_lp) => {
panic!("redirecturl is not found");
}
ParsingResult::RedirectUrl(url) => {
assert_eq!(
url,
"https://mail.example.com/autodiscover/autodiscover.xml"
);
}
}
}
#[test]
fn test_parse_loginparam() {
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\">
<Account>
<AccountType>email</AccountType>
<Action>settings</Action>
<Protocol>
<Type>IMAP</Type>
<Server>example.com</Server>
<Port>993</Port>
<SSL>on</SSL>
<AuthRequired>on</AuthRequired>
</Protocol>
<Protocol>
<Type>SMTP</Type>
<Server>smtp.example.com</Server>
<Port>25</Port>
<SSL>off</SSL>
<AuthRequired>on</AuthRequired>
</Protocol>
</Account>
</Response>
</Autodiscover>",
)
.expect("XML is not parsed successfully");
match res {
ParsingResult::LoginParam(lp) => {
assert_eq!(lp.mail_server, "example.com");
assert_eq!(lp.mail_port, 993);
assert_eq!(lp.send_server, "smtp.example.com");
assert_eq!(lp.send_port, 25);
}
ParsingResult::RedirectUrl(_) => {
panic!("RedirectUrl is not expected");
}
}
}
}

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

@@ -0,0 +1,583 @@
//! 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;
use crate::constants::*;
use crate::context::Context;
use crate::dc_tools::*;
use crate::e2ee;
use crate::job::*;
use crate::login_param::LoginParam;
use crate::oauth2::*;
use crate::param::Params;
use auto_mozilla::moz_autoconfigure;
use auto_outlook::outlk_autodiscover;
macro_rules! progress {
($context:tt, $progress:expr) => {
assert!(
$progress <= 1000,
"value in range 0..1000 expected with: 0=error, 1..999=progress, 1000=success"
);
$context.call_cb($crate::events::Event::ConfigureProgress($progress));
};
}
// connect
pub fn configure(context: &Context) {
if context.has_ongoing() {
warn!(context, "There is already another ongoing process running.",);
return;
}
job_kill_action(context, Action::ConfigureImap);
job_add(context, Action::ConfigureImap, 0, Params::new(), 0);
}
/// Check if the context is already configured.
pub fn dc_is_configured(context: &Context) -> bool {
context.sql.get_raw_config_bool(context, "configured")
}
/*******************************************************************************
* Configure JOB
******************************************************************************/
#[allow(non_snake_case, unused_must_use)]
pub fn JobConfigureImap(context: &Context) {
if !context.sql.is_open() {
error!(context, "Cannot configure, database not opened.",);
progress!(context, 0);
return;
}
if !context.alloc_ongoing() {
progress!(context, 0);
return;
}
let mut success = false;
let mut imap_connected_here = false;
let mut smtp_connected_here = false;
let mut param_autoconfig: Option<LoginParam> = None;
context
.inbox_thread
.read()
.unwrap()
.imap
.disconnect(context);
context
.sentbox_thread
.read()
.unwrap()
.imap
.disconnect(context);
context
.mvbox_thread
.read()
.unwrap()
.imap
.disconnect(context);
context.smtp.clone().lock().unwrap().disconnect();
info!(context, "Configure ...",);
// Variables that are shared between steps:
let mut param = LoginParam::from_database(context, "");
// need all vars here to be mutable because rust thinks the same step could be called multiple times
// and also initialize, because otherwise rust thinks it's used while unitilized, even if thats not the case as the loop goes only forward
let mut param_domain = "undefined.undefined".to_owned();
let mut param_addr_urlencoded: String =
"Internal Error: this value should never be used".to_owned();
let mut keep_flags = std::i32::MAX;
const STEP_3_INDEX: u8 = 13;
let mut step_counter: u8 = 0;
while !context.shall_stop_ongoing() {
step_counter += 1;
let success = match step_counter {
// Read login parameters from the database
1 => {
progress!(context, 1);
if param.addr.is_empty() {
error!(context, "Please enter an email address.",);
}
!param.addr.is_empty()
}
// Step 1: Load the parameters and check email-address and password
2 => {
if 0 != param.server_flags & 0x2 {
// the used oauth2 addr may differ, check this.
// if dc_get_oauth2_addr() is not available in the oauth2 implementation,
// just use the given one.
progress!(context, 10);
if let Some(oauth2_addr) =
dc_get_oauth2_addr(context, &param.addr, &param.mail_pw)
.and_then(|e| e.parse().ok())
{
info!(context, "Authorized address is {}", oauth2_addr);
param.addr = oauth2_addr;
context
.sql
.set_raw_config(context, "addr", Some(param.addr.as_str()))
.ok();
}
progress!(context, 20);
}
true // no oauth? - just continue it's no error
}
3 => {
if let Ok(parsed) = param.addr.parse() {
let parsed: EmailAddress = parsed;
param_domain = parsed.domain;
param_addr_urlencoded =
utf8_percent_encode(&param.addr, NON_ALPHANUMERIC).to_string();
true
} else {
error!(context, "Bad email-address.");
false
}
}
// Step 2: Autoconfig
4 => {
progress!(context, 200);
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()
&& param.send_port == 0
&& param.send_user.is_empty()
/*&&param.send_pw.is_empty() -- the password cannot be auto-configured and is no criterion for autoconfig or not */
&& param.server_flags & !0x2 == 0
{
keep_flags = param.server_flags & 0x2;
} else {
// Autoconfig is not needed so skip it.
step_counter = STEP_3_INDEX - 1;
}
true
}
/* A. Search configurations from the domain used in the email-address, prefer encrypted */
5 => {
if param_autoconfig.is_none() {
let url = format!(
"https://autoconfig.{}/mail/config-v1.1.xml?emailaddress={}",
param_domain, param_addr_urlencoded
);
param_autoconfig = moz_autoconfigure(context, &url, &param).ok();
}
true
}
6 => {
progress!(context, 300);
if param_autoconfig.is_none() {
// the doc does not mention `emailaddress=`, however, Thunderbird adds it, see https://releases.mozilla.org/pub/thunderbird/ , which makes some sense
let url = format!(
"https://{}/.well-known/autoconfig/mail/config-v1.1.xml?emailaddress={}",
param_domain, param_addr_urlencoded
);
param_autoconfig = moz_autoconfigure(context, &url, &param).ok();
}
true
}
/* Outlook section start ------------- */
/* Outlook uses always SSL but different domains (this comment describes the next two steps) */
7 => {
progress!(context, 310);
if param_autoconfig.is_none() {
let url = format!(
"https://{}{}/autodiscover/autodiscover.xml",
"", param_domain
);
param_autoconfig = outlk_autodiscover(context, &url, &param).ok();
}
true
}
8 => {
progress!(context, 320);
if param_autoconfig.is_none() {
let url = format!(
"https://{}{}/autodiscover/autodiscover.xml",
"autodiscover.", param_domain
);
param_autoconfig = outlk_autodiscover(context, &url, &param).ok();
}
true
}
/* ----------- Outlook section end */
9 => {
progress!(context, 330);
if param_autoconfig.is_none() {
let url = format!(
"http://autoconfig.{}/mail/config-v1.1.xml?emailaddress={}",
param_domain, param_addr_urlencoded
);
param_autoconfig = moz_autoconfigure(context, &url, &param).ok();
}
true
}
10 => {
progress!(context, 340);
if param_autoconfig.is_none() {
// do not transfer the email-address unencrypted
let url = format!(
"http://{}/.well-known/autoconfig/mail/config-v1.1.xml",
param_domain
);
param_autoconfig = moz_autoconfigure(context, &url, &param).ok();
}
true
}
/* B. If we have no configuration yet, search configuration in Thunderbird's centeral database */
11 => {
progress!(context, 350);
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).ok();
}
true
}
/* C. Do we have any result? */
12 => {
progress!(context, 500);
if let Some(ref cfg) = param_autoconfig {
info!(context, "Got autoconfig: {}", &cfg);
if !cfg.mail_user.is_empty() {
param.mail_user = cfg.mail_user.clone();
}
param.mail_server = cfg.mail_server.clone(); /* all other values are always NULL when entering autoconfig */
param.mail_port = cfg.mail_port;
param.send_server = cfg.send_server.clone();
param.send_port = cfg.send_port;
param.send_user = cfg.send_user.clone();
param.server_flags = cfg.server_flags;
/* 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 */
}
param.server_flags |= keep_flags;
true
}
// Step 3: Fill missing fields with defaults
13 => {
// if you move this, don't forget to update STEP_3_INDEX, too
if param.mail_server.is_empty() {
param.mail_server = format!("imap.{}", param_domain,)
}
if param.mail_port == 0 {
param.mail_port = if 0 != param.server_flags & (0x100 | 0x400) {
143
} else {
993
}
}
if param.mail_user.is_empty() {
param.mail_user = param.addr.clone();
}
if param.send_server.is_empty() && !param.mail_server.is_empty() {
param.send_server = param.mail_server.clone();
if param.send_server.starts_with("imap.") {
param.send_server = param.send_server.replacen("imap", "smtp", 1);
}
}
if param.send_port == 0 {
param.send_port = if 0 != param.server_flags & DC_LP_SMTP_SOCKET_STARTTLS as i32
{
587
} else if 0 != param.server_flags & DC_LP_SMTP_SOCKET_PLAIN as i32 {
25
} else {
465
}
}
if param.send_user.is_empty() && !param.mail_user.is_empty() {
param.send_user = param.mail_user.clone();
}
if param.send_pw.is_empty() && !param.mail_pw.is_empty() {
param.send_pw = param.mail_pw.clone()
}
if !dc_exactly_one_bit_set(param.server_flags & DC_LP_AUTH_FLAGS as i32) {
param.server_flags &= !(DC_LP_AUTH_FLAGS as i32);
param.server_flags |= DC_LP_AUTH_NORMAL as i32
}
if !dc_exactly_one_bit_set(param.server_flags & DC_LP_IMAP_SOCKET_FLAGS as i32) {
param.server_flags &= !(DC_LP_IMAP_SOCKET_FLAGS as i32);
param.server_flags |= if param.send_port == 143 {
DC_LP_IMAP_SOCKET_STARTTLS as i32
} else {
DC_LP_IMAP_SOCKET_SSL as i32
}
}
if !dc_exactly_one_bit_set(param.server_flags & (DC_LP_SMTP_SOCKET_FLAGS as i32)) {
param.server_flags &= !(DC_LP_SMTP_SOCKET_FLAGS as i32);
param.server_flags |= if param.send_port == 587 {
DC_LP_SMTP_SOCKET_STARTTLS as i32
} else if param.send_port == 25 {
DC_LP_SMTP_SOCKET_PLAIN as i32
} else {
DC_LP_SMTP_SOCKET_SSL as i32
}
}
/* do we have a complete configuration? */
if param.mail_server.is_empty()
|| param.mail_port == 0
|| param.mail_user.is_empty()
|| param.mail_pw.is_empty()
|| param.send_server.is_empty()
|| param.send_port == 0
|| param.send_user.is_empty()
|| param.send_pw.is_empty()
|| param.server_flags == 0
{
error!(context, "Account settings incomplete.");
false
} else {
true
}
}
14 => {
progress!(context, 600);
/* try to connect to IMAP - if we did not got an autoconfig,
do some further tries with different settings and username variations */
imap_connected_here =
try_imap_connections(context, &mut param, param_autoconfig.is_some());
imap_connected_here
}
15 => {
progress!(context, 800);
smtp_connected_here =
try_smtp_connections(context, &mut param, param_autoconfig.is_some());
smtp_connected_here
}
16 => {
progress!(context, 900);
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 {
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);
/* configuration success - write back the configured parameters with the "configured_" prefix; also write the "configured"-flag */
param
.save_to_database(
context,
"configured_", /*the trailing underscore is correct*/
)
.ok();
context.sql.set_raw_config_bool(context, "configured", true);
true
}
18 => {
progress!(context, 920);
// we generate the keypair just now - we could also postpone this until the first message is sent, however,
// this may result in a unexpected and annoying delay when the user sends his very first message
// (~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, "key generation completed");
progress!(context, 940);
break; // We are done here
}
_ => {
error!(context, "Internal error: step counter out of bound",);
break;
}
};
if !success {
break;
}
}
if imap_connected_here {
context
.inbox_thread
.read()
.unwrap()
.imap
.disconnect(context);
}
if smtp_connected_here {
context.smtp.clone().lock().unwrap().disconnect();
}
// 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.
if success {
LoginParam::from_database(context, "").save_to_database(context, "configured_raw_");
} else {
LoginParam::from_database(context, "configured_raw_").save_to_database(context, "");
}
context.free_ongoing();
progress!(context, if success { 1000 } else { 0 });
}
fn try_imap_connections(
context: &Context,
mut param: &mut LoginParam,
was_autoconfig: bool,
) -> bool {
// progress 650 and 660
if let Some(res) = try_imap_connection(context, &mut param, was_autoconfig, 0) {
return res;
}
progress!(context, 670);
param.server_flags &= !(DC_LP_IMAP_SOCKET_FLAGS);
param.server_flags |= DC_LP_IMAP_SOCKET_SSL;
param.mail_port = 993;
if let Some(at) = param.mail_user.find('@') {
param.mail_user = param.mail_user.split_at(at).0.to_string();
}
if let Some(at) = param.send_user.find('@') {
param.send_user = param.send_user.split_at(at).0.to_string();
}
// progress 680 and 690
if let Some(res) = try_imap_connection(context, &mut param, was_autoconfig, 1) {
res
} else {
false
}
}
fn try_imap_connection(
context: &Context,
param: &mut LoginParam,
was_autoconfig: bool,
variation: usize,
) -> Option<bool> {
if let Some(res) = try_imap_one_param(context, &param) {
return Some(res);
}
if was_autoconfig {
return Some(false);
}
progress!(context, 650 + variation * 30);
param.server_flags &= !(DC_LP_IMAP_SOCKET_FLAGS);
param.server_flags |= DC_LP_IMAP_SOCKET_STARTTLS;
if let Some(res) = try_imap_one_param(context, &param) {
return Some(res);
}
progress!(context, 660 + variation * 30);
param.mail_port = 143;
try_imap_one_param(context, &param)
}
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
);
info!(context, "Trying: {}", inf);
if context
.inbox_thread
.read()
.unwrap()
.imap
.connect(context, &param)
{
info!(context, "success: {}", inf);
return Some(true);
}
if context.shall_stop_ongoing() {
return Some(false);
}
info!(context, "Could not connect: {}", inf);
None
}
fn try_smtp_connections(
context: &Context,
mut param: &mut LoginParam,
was_autoconfig: bool,
) -> bool {
/* try to connect to SMTP - if we did not got an autoconfig, the first try was SSL-465 and we do a second try with STARTTLS-587 */
if let Some(res) = try_smtp_one_param(context, &param) {
return res;
}
if was_autoconfig {
return false;
}
progress!(context, 850);
param.server_flags &= !(DC_LP_SMTP_SOCKET_FLAGS as i32);
param.server_flags |= DC_LP_SMTP_SOCKET_STARTTLS as i32;
param.send_port = 587;
if let Some(res) = try_smtp_one_param(context, &param) {
return res;
}
progress!(context, 860);
param.server_flags &= !(DC_LP_SMTP_SOCKET_FLAGS as i32);
param.server_flags |= DC_LP_SMTP_SOCKET_STARTTLS as i32;
param.send_port = 25;
if let Some(res) = try_smtp_one_param(context, &param) {
return res;
}
false
}
fn try_smtp_one_param(context: &Context, param: &LoginParam) -> Option<bool> {
let inf = format!(
"smtp: {}@{}:{} flags: 0x{:x}",
param.send_user, param.send_server, param.send_port, param.server_flags
);
info!(context, "Trying: {}", inf);
match context
.smtp
.clone()
.lock()
.unwrap()
.connect(context, &param)
{
Ok(()) => {
info!(context, "success: {}", inf);
Some(true)
}
Err(err) => {
if context.shall_stop_ongoing() {
Some(false)
} else {
warn!(context, "could not connect: {}", err);
None
}
}
}
}
#[cfg(test)]
mod tests {
use crate::config::*;
use crate::configure::JobConfigureImap;
use crate::test_utils::*;
#[test]
fn test_no_panic_on_bad_credentials() {
let t = dummy_context();
t.ctx
.set_config(Config::Addr, Some("probably@unexistant.addr"))
.unwrap();
t.ctx.set_config(Config::MailPw, Some("123456")).unwrap();
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,139 +1,128 @@
//! Constants
//! # Constants
#![allow(non_camel_case_types, dead_code)]
pub const DC_VERSION_STR: &'static [u8; 14] = b"1.0.0-alpha.3\x00";
use deltachat_derive::*;
use lazy_static::lazy_static;
pub const DC_MOVE_STATE_MOVING: u32 = 3;
pub const DC_MOVE_STATE_STAY: u32 = 2;
pub const DC_MOVE_STATE_PENDING: u32 = 1;
pub const DC_MOVE_STATE_UNDEFINED: u32 = 0;
lazy_static! {
pub static ref DC_VERSION_STR: String = env!("CARGO_PKG_VERSION").to_string();
}
// some defaults
const DC_E2EE_DEFAULT_ENABLED: i32 = 1;
const DC_INBOX_WATCH_DEFAULT: i32 = 1;
const DC_SENTBOX_WATCH_DEFAULT: i32 = 1;
const DC_MVBOX_WATCH_DEFAULT: i32 = 1;
const DC_MVBOX_MOVE_DEFAULT: i32 = 1;
#[derive(Debug, Display, Clone, Copy, PartialEq, Eq, FromPrimitive, ToPrimitive, FromSql, ToSql)]
#[repr(u8)]
pub enum Blocked {
Not = 0,
Manually = 1,
Deaddrop = 2,
}
impl Default for Blocked {
fn default() -> Self {
Blocked::Not
}
}
#[derive(Debug, Display, Clone, Copy, PartialEq, Eq, FromPrimitive, ToPrimitive, FromSql, ToSql)]
#[repr(u8)]
pub enum ShowEmails {
Off = 0,
AcceptedContacts = 1,
All = 2,
}
impl Default for ShowEmails {
fn default() -> Self {
ShowEmails::Off // also change Config.ShowEmails props(default) on changes
}
}
pub const DC_IMAP_SEEN: u32 = 0x1;
pub const DC_HANDSHAKE_CONTINUE_NORMAL_PROCESSING: i32 = 0x01;
pub const DC_HANDSHAKE_STOP_NORMAL_PROCESSING: i32 = 0x02;
pub const DC_HANDSHAKE_ADD_DELETE_JOB: i32 = 0x04;
pub const DC_GCL_ARCHIVED_ONLY: usize = 0x01;
pub const DC_GCL_NO_SPECIALS: usize = 0x02;
pub const DC_GCL_ADD_ALLDONE_HINT: usize = 0x04;
pub const DC_GCM_ADDDAYMARKER: usize = 0x01;
pub const DC_GCM_ADDDAYMARKER: u32 = 0x01;
pub const DC_GCL_VERIFIED_ONLY: usize = 0x01;
pub const DC_GCL_ADD_SELF: usize = 0x02;
/// param1 is a directory where the keys are written to
pub const DC_IMEX_EXPORT_SELF_KEYS: usize = 1;
/// param1 is a directory where the keys are searched in and read from
pub const DC_IMEX_IMPORT_SELF_KEYS: usize = 2;
/// param1 is a directory where the backup is written to
pub const DC_IMEX_EXPORT_BACKUP: usize = 11;
/// param1 is the file with the backup to import
pub const DC_IMEX_IMPORT_BACKUP: usize = 12;
/// id=contact
pub const DC_QR_ASK_VERIFYCONTACT: usize = 200;
/// text1=groupname
pub const DC_QR_ASK_VERIFYGROUP: usize = 202;
/// id=contact
pub const DC_QR_FPR_OK: usize = 210;
/// id=contact
pub const DC_QR_FPR_MISMATCH: usize = 220;
/// test1=formatted fingerprint
pub const DC_QR_FPR_WITHOUT_ADDR: usize = 230;
/// id=contact
pub const DC_QR_ADDR: usize = 320;
/// text1=text
pub const DC_QR_TEXT: usize = 330;
/// text1=URL
pub const DC_QR_URL: usize = 332;
/// text1=error string
pub const DC_QR_ERROR: usize = 400;
// values for DC_PARAM_FORCE_PLAINTEXT
pub(crate) const DC_FP_NO_AUTOCRYPT_HEADER: i32 = 2;
pub(crate) const DC_FP_ADD_AUTOCRYPT_HEADER: i32 = 1;
/// virtual chat showing all messages belonging to chats flagged with chats.blocked=2
pub const DC_CHAT_ID_DEADDROP: usize = 1;
pub(crate) const DC_CHAT_ID_DEADDROP: u32 = 1;
/// messages that should be deleted get this chat_id; the messages are deleted from the working thread later then. This is also needed as rfc724_mid should be preset as long as the message is not deleted on the server (otherwise it is downloaded again)
pub const DC_CHAT_ID_TRASH: usize = 3;
pub const DC_CHAT_ID_TRASH: u32 = 3;
/// a message is just in creation but not yet assigned to a chat (eg. we may need the message ID to set up blobs; this avoids unready message to be sent and shown)
pub const DC_CHAT_ID_MSGS_IN_CREATION: usize = 4;
const DC_CHAT_ID_MSGS_IN_CREATION: u32 = 4;
/// virtual chat showing all messages flagged with msgs.starred=2
pub const DC_CHAT_ID_STARRED: usize = 5;
pub const DC_CHAT_ID_STARRED: u32 = 5;
/// only an indicator in a chatlist
pub const DC_CHAT_ID_ARCHIVED_LINK: usize = 6;
pub const DC_CHAT_ID_ARCHIVED_LINK: u32 = 6;
/// only an indicator in a chatlist
pub const DC_CHAT_ID_ALLDONE_HINT: usize = 7;
pub const DC_CHAT_ID_ALLDONE_HINT: u32 = 7;
/// larger chat IDs are "real" chats, their messages are "real" messages.
pub const DC_CHAT_ID_LAST_SPECIAL: usize = 9;
pub const DC_CHAT_ID_LAST_SPECIAL: u32 = 9;
pub const DC_CHAT_TYPE_UNDEFINED: usize = 0;
pub const DC_CHAT_TYPE_SINGLE: usize = 100;
pub const DC_CHAT_TYPE_GROUP: usize = 120;
pub const DC_CHAT_TYPE_VERIFIED_GROUP: usize = 130;
#[derive(
Debug,
Display,
Clone,
Copy,
PartialEq,
Eq,
FromPrimitive,
ToPrimitive,
FromSql,
ToSql,
IntoStaticStr,
)]
#[repr(u32)]
pub enum Chattype {
Undefined = 0,
Single = 100,
Group = 120,
VerifiedGroup = 130,
}
pub const DC_MSG_ID_MARKER1: usize = 1;
pub const DC_MSG_ID_DAYMARKER: usize = 9;
pub const DC_MSG_ID_LAST_SPECIAL: usize = 9;
impl Default for Chattype {
fn default() -> Self {
Chattype::Undefined
}
}
pub const DC_STATE_UNDEFINED: usize = 0;
pub const DC_STATE_IN_FRESH: usize = 10;
pub const DC_STATE_IN_NOTICED: usize = 13;
pub const DC_STATE_IN_SEEN: usize = 16;
pub const DC_STATE_OUT_PREPARING: usize = 18;
pub const DC_STATE_OUT_DRAFT: usize = 19;
pub const DC_STATE_OUT_PENDING: usize = 20;
pub const DC_STATE_OUT_FAILED: usize = 24;
/// to check if a mail was sent, use dc_msg_is_sent()
pub const DC_STATE_OUT_DELIVERED: usize = 26;
pub const DC_STATE_OUT_MDN_RCVD: usize = 28;
pub const DC_MSG_ID_MARKER1: u32 = 1;
pub const DC_MSG_ID_DAYMARKER: u32 = 9;
pub const DC_MSG_ID_LAST_SPECIAL: u32 = 9;
/// approx. max. lenght returned by dc_msg_get_text()
pub const DC_MAX_GET_TEXT_LEN: usize = 30000;
/// approx. max. lenght returned by dc_get_msg_info()
pub const DC_MAX_GET_INFO_LEN: usize = 100000;
/// approx. max. length returned by dc_msg_get_text()
const DC_MAX_GET_TEXT_LEN: usize = 30000;
/// approx. max. length returned by dc_get_msg_info()
const DC_MAX_GET_INFO_LEN: usize = 100000;
pub const DC_CONTACT_ID_SELF: usize = 1;
pub const DC_CONTACT_ID_DEVICE: usize = 2;
pub const DC_CONTACT_ID_LAST_SPECIAL: usize = 9;
pub const DC_CONTACT_ID_UNDEFINED: u32 = 0;
pub const DC_CONTACT_ID_SELF: u32 = 1;
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_TEXT1_DRAFT: usize = 1;
pub const DC_TEXT1_USERNAME: usize = 2;
pub const DC_TEXT1_SELF: usize = 3;
// Flags for empty server job
pub const DC_CREATE_MVBOX: usize = 1;
/// Text message.
/// The text of the message is set using dc_msg_set_text()
/// and retrieved with dc_msg_get_text().
pub const DC_MSG_TEXT: usize = 10;
/// Image message.
/// If the image is an animated GIF, the type DC_MSG_GIF should be used.
/// File, width and height are set via dc_msg_set_file(), dc_msg_set_dimension
/// and retrieved via dc_msg_set_file(), dc_msg_set_dimension().
pub const DC_MSG_IMAGE: usize = 20;
/// Animated GIF message.
/// File, width and height are set via dc_msg_set_file(), dc_msg_set_dimension()
/// and retrieved via dc_msg_get_file(), dc_msg_get_width(), dc_msg_get_height().
pub const DC_MSG_GIF: usize = 21;
/// Message containing an Audio file.
/// File and duration are set via dc_msg_set_file(), dc_msg_set_duration()
/// and retrieved via dc_msg_get_file(), dc_msg_get_duration().
pub const DC_MSG_AUDIO: usize = 40;
/// A voice message that was directly recorded by the user.
/// For all other audio messages, the type #DC_MSG_AUDIO should be used.
/// File and duration are set via dc_msg_set_file(), dc_msg_set_duration()
/// and retieved via dc_msg_get_file(), dc_msg_get_duration()
pub const DC_MSG_VOICE: usize = 41;
/// Video messages.
/// File, width, height and durarion
/// are set via dc_msg_set_file(), dc_msg_set_dimension(), dc_msg_set_duration()
/// and retrieved via
/// dc_msg_get_file(), dc_msg_get_width(),
/// dc_msg_get_height(), dc_msg_get_duration().
pub const DC_MSG_VIDEO: usize = 50;
/// Message containing any file, eg. a PDF.
/// The file is set via dc_msg_set_file()
/// and retrieved via dc_msg_get_file().
pub const DC_MSG_FILE: usize = 60;
pub const DC_EMPTY_MVBOX: u32 = 0x01;
pub const DC_EMPTY_INBOX: u32 = 0x02;
// Flags for configuring IMAP and SMTP servers.
// These flags are optional
@@ -141,25 +130,25 @@ pub const DC_MSG_FILE: usize = 60;
// via dc_set_config() using the key "server_flags".
/// Force OAuth2 authorization. This flag does not skip automatic configuration.
/// Before calling dc_configure() with DC_LP_AUTH_OAUTH2 set,
/// Before calling configure() with DC_LP_AUTH_OAUTH2 set,
/// the user has to confirm access at the URL returned by dc_get_oauth2_url().
pub const DC_LP_AUTH_OAUTH2: usize = 0x2;
pub const DC_LP_AUTH_OAUTH2: i32 = 0x2;
/// Force NORMAL authorization, this is the default.
/// If this flag is set, automatic configuration is skipped.
pub const DC_LP_AUTH_NORMAL: usize = 0x4;
pub const DC_LP_AUTH_NORMAL: i32 = 0x4;
/// Connect to IMAP via STARTTLS.
/// If this flag is set, automatic configuration is skipped.
pub const DC_LP_IMAP_SOCKET_STARTTLS: usize = 0x100;
pub const DC_LP_IMAP_SOCKET_STARTTLS: i32 = 0x100;
/// Connect to IMAP via SSL.
/// If this flag is set, automatic configuration is skipped.
pub const DC_LP_IMAP_SOCKET_SSL: usize = 0x200;
pub const DC_LP_IMAP_SOCKET_SSL: i32 = 0x200;
/// Connect to IMAP unencrypted, this should not be used.
/// If this flag is set, automatic configuration is skipped.
pub const DC_LP_IMAP_SOCKET_PLAIN: usize = 0x400;
pub const DC_LP_IMAP_SOCKET_PLAIN: i32 = 0x400;
/// Connect to SMTP via STARTTLS.
/// If this flag is set, automatic configuration is skipped.
@@ -173,315 +162,147 @@ pub const DC_LP_SMTP_SOCKET_SSL: usize = 0x20000;
/// If this flag is set, automatic configuration is skipped.
pub const DC_LP_SMTP_SOCKET_PLAIN: usize = 0x40000;
/// if none of these flags are set, the default is choosen
pub const DC_LP_AUTH_FLAGS: usize = (DC_LP_AUTH_OAUTH2 | DC_LP_AUTH_NORMAL);
/// if none of these flags are set, the default is choosen
pub const DC_LP_IMAP_SOCKET_FLAGS: usize =
/// if none of these flags are set, the default is chosen
pub const DC_LP_AUTH_FLAGS: i32 = (DC_LP_AUTH_OAUTH2 | DC_LP_AUTH_NORMAL);
/// if none of these flags are set, the default is chosen
pub const DC_LP_IMAP_SOCKET_FLAGS: i32 =
(DC_LP_IMAP_SOCKET_STARTTLS | DC_LP_IMAP_SOCKET_SSL | DC_LP_IMAP_SOCKET_PLAIN);
/// if none of these flags are set, the default is choosen
/// if none of these flags are set, the default is chosen
pub const DC_LP_SMTP_SOCKET_FLAGS: usize =
(DC_LP_SMTP_SOCKET_STARTTLS | DC_LP_SMTP_SOCKET_SSL | DC_LP_SMTP_SOCKET_PLAIN);
// QR code scanning (view from Bob, the joiner)
pub const DC_VC_AUTH_REQUIRED: i32 = 2;
pub const DC_VC_CONTACT_CONFIRM: i32 = 6;
pub const DC_BOB_ERROR: i32 = 0;
pub const DC_BOB_SUCCESS: i32 = 1;
#[derive(Debug, Display, Clone, Copy, PartialEq, Eq, FromPrimitive, ToPrimitive, FromSql, ToSql)]
#[repr(i32)]
pub enum Viewtype {
Unknown = 0,
/// Text message.
/// The text of the message is set using dc_msg_set_text()
/// and retrieved with dc_msg_get_text().
Text = 10,
/// Image message.
/// If the image is an animated GIF, the type DC_MSG_GIF should be used.
/// File, width and height are set via dc_msg_set_file(), dc_msg_set_dimension
/// and retrieved via dc_msg_set_file(), dc_msg_set_dimension().
Image = 20,
/// Animated GIF message.
/// File, width and height are set via dc_msg_set_file(), dc_msg_set_dimension()
/// and retrieved via dc_msg_get_file(), dc_msg_get_width(), dc_msg_get_height().
Gif = 21,
/// Message containing a sticker, similar to image.
/// If possible, the ui should display the image without borders in a transparent way.
/// A click on a sticker will offer to install the sticker set in some future.
Sticker = 23,
/// Message containing an Audio file.
/// File and duration are set via dc_msg_set_file(), dc_msg_set_duration()
/// and retrieved via dc_msg_get_file(), dc_msg_get_duration().
Audio = 40,
/// A voice message that was directly recorded by the user.
/// For all other audio messages, the type #DC_MSG_AUDIO should be used.
/// File and duration are set via dc_msg_set_file(), dc_msg_set_duration()
/// and retrieved via dc_msg_get_file(), dc_msg_get_duration()
Voice = 41,
/// Video messages.
/// File, width, height and durarion
/// are set via dc_msg_set_file(), dc_msg_set_dimension(), dc_msg_set_duration()
/// and retrieved via
/// dc_msg_get_file(), dc_msg_get_width(),
/// dc_msg_get_height(), dc_msg_get_duration().
Video = 50,
/// Message containing any file, eg. a PDF.
/// The file is set via dc_msg_set_file()
/// and retrieved via dc_msg_get_file().
File = 60,
}
impl Default for Viewtype {
fn default() -> Self {
Viewtype::Unknown
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn derive_display_works_as_expected() {
assert_eq!(format!("{}", Viewtype::Audio), "Audio");
}
}
// These constants are used as events
// reported to the callback given to dc_context_new().
// If you do not want to handle an event, it is always safe to return 0,
// so there is no need to add a "case" for every event.
#[derive(Debug, Clone, Copy, PartialEq, Eq, FromPrimitive, ToPrimitive)]
#[repr(u32)]
pub enum Event {
/// The library-user may write an informational string to the log.
/// Passed to the callback given to dc_context_new().
/// This event should not be reported to the end-user using a popup or something like that.
/// @param data1 0
/// @param data2 (const char*) Info string in english language.
/// Must not be free()'d or modified and is valid only until the callback returns.
/// @return 0
INFO = 100,
/// Emitted when SMTP connection is established and login was successful.
///
/// @param data1 0
/// @param data2 (const char*) Info string in english language.
/// Must not be free()'d or modified and is valid only until the callback returns.
/// @return 0
SMTP_CONNECTED = 101,
/// Emitted when IMAP connection is established and login was successful.
///
/// @param data1 0
/// @param data2 (const char*) Info string in english language.
/// Must not be free()'d or modified and is valid only until the callback returns.
/// @return 0
IMAP_CONNECTED = 102,
/// Emitted when a message was successfully sent to the SMTP server.
///
/// @param data1 0
/// @param data2 (const char*) Info string in english language.
/// Must not be free()'d or modified and is valid only until the callback returns.
/// @return 0
SMTP_MESSAGE_SENT = 103,
/// The library-user should write a warning string to the log.
/// Passed to the callback given to dc_context_new().
///
/// This event should not be reported to the end-user using a popup or something like that.
///
/// @param data1 0
/// @param data2 (const char*) Warning string in english language.
/// Must not be free()'d or modified and is valid only until the callback returns.
/// @return 0
WARNING = 300,
/// The library-user should report an error to the end-user.
/// Passed to the callback given to dc_context_new().
///
/// As most things are asynchrounous, things may go wrong at any time and the user
/// should not be disturbed by a dialog or so. Instead, use a bubble or so.
///
/// However, for ongoing processes (eg. dc_configure())
/// or for functions that are expected to fail (eg. dc_continue_key_transfer())
/// it might be better to delay showing these events until the function has really
/// failed (returned false). It should be sufficient to report only the _last_ error
/// in a messasge box then.
///
/// @param data1 0
/// @param data2 (const char*) Error string, always set, never NULL. Frequent error strings are
/// localized using #DC_EVENT_GET_STRING, however, most error strings will be in english language.
/// Must not be free()'d or modified and is valid only until the callback returns.
/// @return 0
ERROR = 400,
/// An action cannot be performed because there is no network available.
///
/// The library will typically try over after a some time
/// and when dc_maybe_network() is called.
///
/// Network errors should be reported to users in a non-disturbing way,
/// however, as network errors may come in a sequence,
/// it is not useful to raise each an every error to the user.
/// For this purpose, data1 is set to 1 if the error is probably worth reporting.
///
/// Moreover, if the UI detects that the device is offline,
/// it is probably more useful to report this to the user
/// instread of the string from data2.
///
/// @param data1 (int) 1=first/new network error, should be reported the user;
/// 0=subsequent network error, should be logged only
/// @param data2 (const char*) Error string, always set, never NULL.
/// Must not be free()'d or modified and is valid only until the callback returns.
/// @return 0
ERROR_NETWORK = 401,
/// An action cannot be performed because the user is not in the group.
/// Reported eg. after a call to
/// dc_set_chat_name(), dc_set_chat_profile_image(),
/// dc_add_contact_to_chat(), dc_remove_contact_from_chat(),
/// dc_send_text_msg() or another sending function.
///
/// @param data1 0
/// @param data2 (const char*) Info string in english language.
/// Must not be free()'d or modified
/// and is valid only until the callback returns.
/// @return 0
ERROR_SELF_NOT_IN_GROUP = 410,
/// Messages or chats changed. One or more messages or chats changed for various
/// reasons in the database:
/// - Messages sent, received or removed
/// - Chats created, deleted or archived
/// - A draft has been set
///
/// @param data1 (int) chat_id for single added messages
/// @param data2 (int) msg_id for single added messages
/// @return 0
MSGS_CHANGED = 2000,
/// There is a fresh message. Typically, the user will show an notification
/// when receiving this message.
///
/// There is no extra #DC_EVENT_MSGS_CHANGED event send together with this event.
///
/// @param data1 (int) chat_id
/// @param data2 (int) msg_id
/// @return 0
INCOMING_MSG = 2005,
/// A single message is sent successfully. State changed from DC_STATE_OUT_PENDING to
/// DC_STATE_OUT_DELIVERED, see dc_msg_get_state().
///
/// @param data1 (int) chat_id
/// @param data2 (int) msg_id
/// @return 0
MSG_DELIVERED = 2010,
/// A single message could not be sent. State changed from DC_STATE_OUT_PENDING or DC_STATE_OUT_DELIVERED to
/// DC_STATE_OUT_FAILED, see dc_msg_get_state().
///
/// @param data1 (int) chat_id
/// @param data2 (int) msg_id
/// @return 0
MSG_FAILED = 2012,
/// A single message is read by the receiver. State changed from DC_STATE_OUT_DELIVERED to
/// DC_STATE_OUT_MDN_RCVD, see dc_msg_get_state().
///
/// @param data1 (int) chat_id
/// @param data2 (int) msg_id
/// @return 0
MSG_READ = 2015,
/// Chat changed. The name or the image of a chat group was changed or members were added or removed.
/// Or the verify state of a chat has changed.
/// See dc_set_chat_name(), dc_set_chat_profile_image(), dc_add_contact_to_chat()
/// and dc_remove_contact_from_chat().
///
/// @param data1 (int) chat_id
/// @param data2 0
/// @return 0
CHAT_MODIFIED = 2020,
/// Contact(s) created, renamed, blocked or deleted.
///
/// @param data1 (int) If not 0, this is the contact_id of an added contact that should be selected.
/// @param data2 0
/// @return 0
CONTACTS_CHANGED = 2030,
/// Location of one or more contact has changed.
///
/// @param data1 (int) contact_id of the contact for which the location has changed.
/// If the locations of several contacts have been changed,
/// eg. after calling dc_delete_all_locations(), this parameter is set to 0.
/// @param data2 0
/// @return 0
LOCATION_CHANGED = 2035,
/// Inform about the configuration progress started by dc_configure().
///
/// @param data1 (int) 0=error, 1-999=progress in permille, 1000=success and done
/// @param data2 0
/// @return 0
CONFIGURE_PROGRESS = 2041,
/// Inform about the import/export progress started by dc_imex().
///
/// @param data1 (int) 0=error, 1-999=progress in permille, 1000=success and done
/// @param data2 0
/// @return 0
IMEX_PROGRESS = 2051,
/// A file has been exported. A file has been written by dc_imex().
/// This event may be sent multiple times by a single call to dc_imex().
///
/// A typical purpose for a handler of this event may be to make the file public to some system
/// services.
///
/// @param data1 (const char*) Path and file name.
/// Must not be free()'d or modified and is valid only until the callback returns.
/// @param data2 0
/// @return 0
IMEX_FILE_WRITTEN = 2052,
/// Progress information of a secure-join handshake from the view of the inviter
/// (Alice, the person who shows the QR code).
///
/// These events are typically sent after a joiner has scanned the QR code
/// generated by dc_get_securejoin_qr().
///
/// @param data1 (int) ID of the contact that wants to join.
/// @param data2 (int) Progress as:
/// 300=vg-/vc-request received, typically shown as "bob@addr joins".
/// 600=vg-/vc-request-with-auth received, vg-member-added/vc-contact-confirm sent, typically shown as "bob@addr verified".
/// 800=vg-member-added-received received, shown as "bob@addr securely joined GROUP", only sent for the verified-group-protocol.
/// 1000=Protocol finished for this contact.
/// @return 0
SECUREJOIN_INVITER_PROGRESS = 2060,
/// Progress information of a secure-join handshake from the view of the joiner
/// (Bob, the person who scans the QR code).
/// The events are typically sent while dc_join_securejoin(), which
/// may take some time, is executed.
/// @param data1 (int) ID of the inviting contact.
/// @param data2 (int) Progress as:
/// 400=vg-/vc-request-with-auth sent, typically shown as "alice@addr verified, introducing myself."
/// (Bob has verified alice and waits until Alice does the same for him)
/// @return 0
SECUREJOIN_JOINER_PROGRESS = 2061,
// the following events are functions that should be provided by the frontends
/// Requeste a localized string from the frontend.
/// @param data1 (int) ID of the string to request, one of the DC_STR_/// constants.
/// @param data2 (int) The count. If the requested string contains a placeholder for a numeric value,
/// the ui may use this value to return different strings on different plural forms.
/// @return (const char*) Null-terminated UTF-8 string.
/// The string will be free()'d by the core,
/// so it must be allocated using malloc() or a compatible function.
/// Return 0 if the ui cannot provide the requested string
/// the core will use a default string in english language then.
GET_STRING = 2091,
}
pub const DC_EVENT_FILE_COPIED: usize = 2055; // deprecated;
pub const DC_EVENT_IS_OFFLINE: usize = 2081; // deprecated;
pub const DC_ERROR_SEE_STRING: usize = 0; // deprecated;
pub const DC_ERROR_SELF_NOT_IN_GROUP: usize = 1; // deprecated;
pub const DC_STR_SELFNOTINGRP: usize = 21; // deprecated;
/// Values for dc_get|set_config("show_emails")
pub const DC_SHOW_EMAILS_OFF: usize = 0;
pub const DC_SHOW_EMAILS_ACCEPTED_CONTACTS: usize = 1;
pub const DC_SHOW_EMAILS_ALL: usize = 2;
const DC_EVENT_FILE_COPIED: usize = 2055; // deprecated;
const DC_EVENT_IS_OFFLINE: usize = 2081; // deprecated;
const DC_ERROR_SEE_STRING: usize = 0; // deprecated;
const DC_ERROR_SELF_NOT_IN_GROUP: usize = 1; // deprecated;
const DC_STR_SELFNOTINGRP: usize = 21; // deprecated;
// TODO: Strings need some doumentation about used placeholders.
// These constants are used to request strings using #DC_EVENT_GET_STRING.
// These constants are used to set stock translation strings
pub const DC_STR_NOMESSAGES: usize = 1;
pub const DC_STR_SELF: usize = 2;
pub const DC_STR_DRAFT: usize = 3;
pub const DC_STR_MEMBER: usize = 4;
pub const DC_STR_CONTACT: usize = 6;
pub const DC_STR_VOICEMESSAGE: usize = 7;
pub const DC_STR_DEADDROP: usize = 8;
pub const DC_STR_IMAGE: usize = 9;
pub const DC_STR_VIDEO: usize = 10;
pub const DC_STR_AUDIO: usize = 11;
pub const DC_STR_FILE: usize = 12;
pub const DC_STR_STATUSLINE: usize = 13;
pub const DC_STR_NEWGROUPDRAFT: usize = 14;
pub const DC_STR_MSGGRPNAME: usize = 15;
pub const DC_STR_MSGGRPIMGCHANGED: usize = 16;
pub const DC_STR_MSGADDMEMBER: usize = 17;
pub const DC_STR_MSGDELMEMBER: usize = 18;
pub const DC_STR_MSGGROUPLEFT: usize = 19;
pub const DC_STR_GIF: usize = 23;
pub const DC_STR_ENCRYPTEDMSG: usize = 24;
pub const DC_STR_E2E_AVAILABLE: usize = 25;
pub const DC_STR_ENCR_TRANSP: usize = 27;
pub const DC_STR_ENCR_NONE: usize = 28;
pub const DC_STR_CANTDECRYPT_MSG_BODY: usize = 29;
pub const DC_STR_FINGERPRINTS: usize = 30;
pub const DC_STR_READRCPT: usize = 31;
pub const DC_STR_READRCPT_MAILBODY: usize = 32;
pub const DC_STR_MSGGRPIMGDELETED: usize = 33;
pub const DC_STR_E2E_PREFERRED: usize = 34;
pub const DC_STR_CONTACT_VERIFIED: usize = 35;
pub const DC_STR_CONTACT_NOT_VERIFIED: usize = 36;
pub const DC_STR_CONTACT_SETUP_CHANGED: usize = 37;
pub const DC_STR_ARCHIVEDCHATS: usize = 40;
pub const DC_STR_STARREDMSGS: usize = 41;
pub const DC_STR_AC_SETUP_MSG_SUBJECT: usize = 42;
pub const DC_STR_AC_SETUP_MSG_BODY: usize = 43;
pub const DC_STR_SELFTALK_SUBTITLE: usize = 50;
pub const DC_STR_CANNOT_LOGIN: usize = 60;
pub const DC_STR_SERVER_RESPONSE: usize = 61;
pub const DC_STR_MSGACTIONBYUSER: usize = 62;
pub const DC_STR_MSGACTIONBYME: usize = 63;
pub const DC_STR_MSGLOCATIONENABLED: usize = 64;
pub const DC_STR_MSGLOCATIONDISABLED: usize = 65;
pub const DC_STR_LOCATION: usize = 66;
pub const DC_STR_COUNT: usize = 66;
const DC_STR_NOMESSAGES: usize = 1;
const DC_STR_SELF: usize = 2;
const DC_STR_DRAFT: usize = 3;
const DC_STR_MEMBER: usize = 4;
const DC_STR_CONTACT: usize = 6;
const DC_STR_VOICEMESSAGE: usize = 7;
const DC_STR_DEADDROP: usize = 8;
const DC_STR_IMAGE: usize = 9;
const DC_STR_VIDEO: usize = 10;
const DC_STR_AUDIO: usize = 11;
const DC_STR_FILE: usize = 12;
const DC_STR_STATUSLINE: usize = 13;
const DC_STR_NEWGROUPDRAFT: usize = 14;
const DC_STR_MSGGRPNAME: usize = 15;
const DC_STR_MSGGRPIMGCHANGED: usize = 16;
const DC_STR_MSGADDMEMBER: usize = 17;
const DC_STR_MSGDELMEMBER: usize = 18;
const DC_STR_MSGGROUPLEFT: usize = 19;
const DC_STR_GIF: usize = 23;
const DC_STR_ENCRYPTEDMSG: usize = 24;
const DC_STR_E2E_AVAILABLE: usize = 25;
const DC_STR_ENCR_TRANSP: usize = 27;
const DC_STR_ENCR_NONE: usize = 28;
const DC_STR_CANTDECRYPT_MSG_BODY: usize = 29;
const DC_STR_FINGERPRINTS: usize = 30;
const DC_STR_READRCPT: usize = 31;
const DC_STR_READRCPT_MAILBODY: usize = 32;
const DC_STR_MSGGRPIMGDELETED: usize = 33;
const DC_STR_E2E_PREFERRED: usize = 34;
const DC_STR_CONTACT_VERIFIED: usize = 35;
const DC_STR_CONTACT_NOT_VERIFIED: usize = 36;
const DC_STR_CONTACT_SETUP_CHANGED: usize = 37;
const DC_STR_ARCHIVEDCHATS: usize = 40;
const DC_STR_STARREDMSGS: usize = 41;
const DC_STR_AC_SETUP_MSG_SUBJECT: usize = 42;
const DC_STR_AC_SETUP_MSG_BODY: usize = 43;
const DC_STR_SELFTALK_SUBTITLE: usize = 50;
const DC_STR_CANNOT_LOGIN: usize = 60;
const DC_STR_SERVER_RESPONSE: usize = 61;
const DC_STR_MSGACTIONBYUSER: usize = 62;
const DC_STR_MSGACTIONBYME: usize = 63;
const DC_STR_MSGLOCATIONENABLED: usize = 64;
const DC_STR_MSGLOCATIONDISABLED: usize = 65;
const DC_STR_LOCATION: usize = 66;
const DC_STR_STICKER: usize = 67;
const DC_STR_COUNT: usize = 67;
pub const DC_JOB_DELETE_MSG_ON_IMAP: i32 = 110;

1218
src/contact.rs Normal file

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

View File

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

File diff suppressed because it is too large Load Diff

View File

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

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

View File

@@ -1,151 +0,0 @@
use lazy_static::lazy_static;
use crate::dc_saxparser::*;
use crate::dc_tools::*;
use crate::x::*;
lazy_static! {
static ref LINE_RE: regex::Regex = regex::Regex::new(r"(\r?\n)+").unwrap();
}
struct Dehtml {
strbuilder: String,
add_text: AddText,
last_href: *mut libc::c_char,
}
#[derive(Debug, PartialEq)]
enum AddText {
No,
YesRemoveLineEnds,
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 unsafe fn dc_dehtml(buf_terminated: *mut libc::c_char) -> *mut libc::c_char {
dc_trim(buf_terminated);
if *buf_terminated.offset(0isize) as libc::c_int == 0i32 {
return dc_strdup(b"\x00" as *const u8 as *const libc::c_char);
}
let mut dehtml = Dehtml {
strbuilder: String::with_capacity(strlen(buf_terminated)),
add_text: AddText::YesRemoveLineEnds,
last_href: 0 as *mut libc::c_char,
};
let mut saxparser = dc_saxparser_t {
starttag_cb: None,
endtag_cb: None,
text_cb: None,
userdata: 0 as *mut libc::c_void,
};
dc_saxparser_init(
&mut saxparser,
&mut dehtml as *mut Dehtml as *mut libc::c_void,
);
dc_saxparser_set_tag_handler(
&mut saxparser,
Some(dehtml_starttag_cb),
Some(dehtml_endtag_cb),
);
dc_saxparser_set_text_handler(&mut saxparser, Some(dehtml_text_cb));
dc_saxparser_parse(&mut saxparser, buf_terminated);
free(dehtml.last_href as *mut libc::c_void);
strdup(to_cstring(dehtml.strbuilder).as_ptr())
}
unsafe fn dehtml_text_cb(
userdata: *mut libc::c_void,
text: *const libc::c_char,
_len: libc::c_int,
) {
let dehtml = &mut *(userdata as *mut Dehtml);
if dehtml.add_text == AddText::YesPreserveLineEnds
|| dehtml.add_text == AddText::YesRemoveLineEnds
{
let last_added = std::ffi::CStr::from_ptr(text).to_string_lossy();
if dehtml.add_text == AddText::YesRemoveLineEnds {
dehtml.strbuilder += LINE_RE.replace_all(last_added.as_ref(), "\r").as_ref();
} else {
dehtml.strbuilder += last_added.as_ref();
}
}
}
unsafe fn dehtml_endtag_cb(userdata: *mut libc::c_void, tag: *const libc::c_char) {
let mut dehtml = &mut *(userdata as *mut Dehtml);
let tag = std::ffi::CStr::from_ptr(tag).to_string_lossy();
match tag.as_ref() {
"p" | "div" | "table" | "td" | "style" | "script" | "title" | "pre" => {
dehtml.strbuilder += "\n\n";
dehtml.add_text = AddText::YesRemoveLineEnds;
}
"a" => {
if !dehtml.last_href.is_null() {
dehtml.strbuilder += "](";
dehtml.strbuilder += std::ffi::CStr::from_ptr((*dehtml).last_href)
.to_string_lossy()
.as_ref();
dehtml.strbuilder += ")";
free(dehtml.last_href as *mut libc::c_void);
dehtml.last_href = 0 as *mut libc::c_char;
}
}
"b" | "strong" => {
dehtml.strbuilder += "*";
}
"i" | "em" => {
dehtml.strbuilder += "_";
}
_ => {}
}
}
unsafe fn dehtml_starttag_cb(
userdata: *mut libc::c_void,
tag: *const libc::c_char,
attr: *mut *mut libc::c_char,
) {
let mut dehtml = &mut *(userdata as *mut Dehtml);
let tag = std::ffi::CStr::from_ptr(tag).to_string_lossy();
match tag.as_ref() {
"p" | "div" | "table" | "td" => {
dehtml.strbuilder += "\n\n";
dehtml.add_text = AddText::YesRemoveLineEnds;
}
"br" => {
dehtml.strbuilder += "\n";
dehtml.add_text = AddText::YesRemoveLineEnds;
}
"style" | "script" | "title" => {
dehtml.add_text = AddText::No;
}
"pre" => {
dehtml.strbuilder += "\n\n";
dehtml.add_text = AddText::YesPreserveLineEnds;
}
"a" => {
free(dehtml.last_href as *mut libc::c_void);
dehtml.last_href = dc_strdup_keep_null(dc_attr_find(
attr,
b"href\x00" as *const u8 as *const libc::c_char,
));
if !dehtml.last_href.is_null() {
dehtml.strbuilder += "[";
}
}
"b" | "strong" => {
dehtml.strbuilder += "*";
}
"i" | "em" => {
dehtml.strbuilder += "_";
}
_ => {}
}
}

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

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