Compare commits

..

111 Commits

Author SHA1 Message Date
holger krekel
e571750f96 make sure c-doc upload dirs exist even if branch name has / in it 2019-10-09 23:23:33 +02:00
dignifiedquire
0f22b75207 updates and fixes 2019-10-09 23:00:15 +02:00
dignifiedquire
13991d8dc5 unify naming in pgp 2019-10-09 22:55:16 +02:00
dignifiedquire
75d9eb08f7 fix and cleanup tests 2019-10-09 22:55:16 +02:00
dignifiedquire
8cf571c78a wip 2019-10-09 22:55:16 +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
62 changed files with 3499 additions and 3835 deletions

View File

@@ -4,6 +4,9 @@ executors:
docker:
- image: filecoin/rust:latest
working_directory: /mnt/crate
doxygen:
docker:
- image: hrektts/doxygen
restore-workspace: &restore-workspace
attach_workspace:
@@ -113,6 +116,18 @@ jobs:
target: "aarch64-linux-android"
build_doxygen:
executor: doxygen
steps:
- checkout
- 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
build_test_docs_wheel:
docker:
- image: deltachat/coredeps
@@ -148,7 +163,7 @@ jobs:
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
@@ -166,18 +181,21 @@ workflows:
test:
jobs:
- cargo_fetch
- build_doxygen
- build_test_docs_wheel:
requires:
- cargo_fetch
- upload_docs_wheels:
requires:
- build_test_docs_wheel
- build_doxygen
- rustfmt:
requires:
- cargo_fetch
- clippy:
requires:
- cargo_fetch
- cargo_fetch
# Linux Desktop 64bit
- test_x86_64-unknown-linux-gnu:

259
Cargo.lock generated
View File

@@ -57,10 +57,10 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
[[package]]
name = "arrayvec"
version = "0.4.11"
version = "0.4.12"
source = "registry+https://github.com/rust-lang/crates.io-index"
dependencies = [
"nodrop 0.1.13 (registry+https://github.com/rust-lang/crates.io-index)",
"nodrop 0.1.14 (registry+https://github.com/rust-lang/crates.io-index)",
]
[[package]]
@@ -88,7 +88,7 @@ version = "0.3.38"
source = "registry+https://github.com/rust-lang/crates.io-index"
dependencies = [
"backtrace-sys 0.1.31 (registry+https://github.com/rust-lang/crates.io-index)",
"cfg-if 0.1.9 (registry+https://github.com/rust-lang/crates.io-index)",
"cfg-if 0.1.10 (registry+https://github.com/rust-lang/crates.io-index)",
"libc 0.2.62 (registry+https://github.com/rust-lang/crates.io-index)",
"rustc-demangle 0.1.16 (registry+https://github.com/rust-lang/crates.io-index)",
]
@@ -139,7 +139,7 @@ version = "0.5.8"
source = "registry+https://github.com/rust-lang/crates.io-index"
dependencies = [
"arrayref 0.3.5 (registry+https://github.com/rust-lang/crates.io-index)",
"arrayvec 0.4.11 (registry+https://github.com/rust-lang/crates.io-index)",
"arrayvec 0.4.12 (registry+https://github.com/rust-lang/crates.io-index)",
"constant_time_eq 0.1.4 (registry+https://github.com/rust-lang/crates.io-index)",
]
@@ -226,7 +226,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
dependencies = [
"byteorder 1.3.2 (registry+https://github.com/rust-lang/crates.io-index)",
"either 1.5.3 (registry+https://github.com/rust-lang/crates.io-index)",
"iovec 0.1.2 (registry+https://github.com/rust-lang/crates.io-index)",
"iovec 0.1.4 (registry+https://github.com/rust-lang/crates.io-index)",
]
[[package]]
@@ -247,7 +247,7 @@ dependencies = [
"semver 0.9.0 (registry+https://github.com/rust-lang/crates.io-index)",
"serde 1.0.101 (registry+https://github.com/rust-lang/crates.io-index)",
"serde_derive 1.0.101 (registry+https://github.com/rust-lang/crates.io-index)",
"serde_json 1.0.40 (registry+https://github.com/rust-lang/crates.io-index)",
"serde_json 1.0.41 (registry+https://github.com/rust-lang/crates.io-index)",
]
[[package]]
@@ -276,7 +276,7 @@ dependencies = [
[[package]]
name = "cfg-if"
version = "0.1.9"
version = "0.1.10"
source = "registry+https://github.com/rust-lang/crates.io-index"
[[package]]
@@ -301,7 +301,7 @@ dependencies = [
[[package]]
name = "circular"
version = "0.2.0"
version = "0.3.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
[[package]]
@@ -345,7 +345,7 @@ dependencies = [
"log 0.4.8 (registry+https://github.com/rust-lang/crates.io-index)",
"publicsuffix 1.5.3 (registry+https://github.com/rust-lang/crates.io-index)",
"serde 1.0.101 (registry+https://github.com/rust-lang/crates.io-index)",
"serde_json 1.0.40 (registry+https://github.com/rust-lang/crates.io-index)",
"serde_json 1.0.41 (registry+https://github.com/rust-lang/crates.io-index)",
"time 0.1.42 (registry+https://github.com/rust-lang/crates.io-index)",
"try_from 0.3.2 (registry+https://github.com/rust-lang/crates.io-index)",
"url 1.7.2 (registry+https://github.com/rust-lang/crates.io-index)",
@@ -375,7 +375,7 @@ name = "crc32fast"
version = "1.2.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
dependencies = [
"cfg-if 0.1.9 (registry+https://github.com/rust-lang/crates.io-index)",
"cfg-if 0.1.10 (registry+https://github.com/rust-lang/crates.io-index)",
]
[[package]]
@@ -392,8 +392,8 @@ name = "crossbeam-epoch"
version = "0.7.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
dependencies = [
"arrayvec 0.4.11 (registry+https://github.com/rust-lang/crates.io-index)",
"cfg-if 0.1.9 (registry+https://github.com/rust-lang/crates.io-index)",
"arrayvec 0.4.12 (registry+https://github.com/rust-lang/crates.io-index)",
"cfg-if 0.1.10 (registry+https://github.com/rust-lang/crates.io-index)",
"crossbeam-utils 0.6.6 (registry+https://github.com/rust-lang/crates.io-index)",
"lazy_static 1.4.0 (registry+https://github.com/rust-lang/crates.io-index)",
"memoffset 0.5.1 (registry+https://github.com/rust-lang/crates.io-index)",
@@ -413,13 +413,13 @@ name = "crossbeam-utils"
version = "0.6.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
dependencies = [
"cfg-if 0.1.9 (registry+https://github.com/rust-lang/crates.io-index)",
"cfg-if 0.1.10 (registry+https://github.com/rust-lang/crates.io-index)",
"lazy_static 1.4.0 (registry+https://github.com/rust-lang/crates.io-index)",
]
[[package]]
name = "ctor"
version = "0.1.11"
version = "0.1.12"
source = "registry+https://github.com/rust-lang/crates.io-index"
dependencies = [
"quote 1.0.2 (registry+https://github.com/rust-lang/crates.io-index)",
@@ -435,7 +435,7 @@ dependencies = [
"clear_on_drop 0.2.3 (registry+https://github.com/rust-lang/crates.io-index)",
"digest 0.8.1 (registry+https://github.com/rust-lang/crates.io-index)",
"rand_core 0.3.1 (registry+https://github.com/rust-lang/crates.io-index)",
"subtle 2.1.1 (registry+https://github.com/rust-lang/crates.io-index)",
"subtle 2.2.1 (registry+https://github.com/rust-lang/crates.io-index)",
]
[[package]]
@@ -500,14 +500,14 @@ dependencies = [
"itertools 0.8.0 (registry+https://github.com/rust-lang/crates.io-index)",
"jetscii 0.4.4 (registry+https://github.com/rust-lang/crates.io-index)",
"lazy_static 1.4.0 (registry+https://github.com/rust-lang/crates.io-index)",
"lettre 0.9.2 (registry+https://github.com/rust-lang/crates.io-index)",
"lettre 0.9.2 (git+https://github.com/deltachat/lettre)",
"libc 0.2.62 (registry+https://github.com/rust-lang/crates.io-index)",
"mmime 0.1.2",
"native-tls 0.2.3 (registry+https://github.com/rust-lang/crates.io-index)",
"num-derive 0.2.5 (registry+https://github.com/rust-lang/crates.io-index)",
"num-traits 0.2.8 (registry+https://github.com/rust-lang/crates.io-index)",
"percent-encoding 2.1.0 (registry+https://github.com/rust-lang/crates.io-index)",
"pgp 0.2.1 (registry+https://github.com/rust-lang/crates.io-index)",
"pgp 0.2.3 (registry+https://github.com/rust-lang/crates.io-index)",
"pretty_assertions 0.6.1 (registry+https://github.com/rust-lang/crates.io-index)",
"pretty_env_logger 0.3.1 (registry+https://github.com/rust-lang/crates.io-index)",
"proptest 0.9.4 (registry+https://github.com/rust-lang/crates.io-index)",
@@ -516,11 +516,11 @@ dependencies = [
"r2d2_sqlite 0.12.0 (registry+https://github.com/rust-lang/crates.io-index)",
"rand 0.6.5 (registry+https://github.com/rust-lang/crates.io-index)",
"regex 1.3.1 (registry+https://github.com/rust-lang/crates.io-index)",
"reqwest 0.9.20 (registry+https://github.com/rust-lang/crates.io-index)",
"reqwest 0.9.21 (registry+https://github.com/rust-lang/crates.io-index)",
"rusqlite 0.20.0 (registry+https://github.com/rust-lang/crates.io-index)",
"rustyline 4.1.0 (registry+https://github.com/rust-lang/crates.io-index)",
"serde 1.0.101 (registry+https://github.com/rust-lang/crates.io-index)",
"serde_json 1.0.40 (registry+https://github.com/rust-lang/crates.io-index)",
"serde_json 1.0.41 (registry+https://github.com/rust-lang/crates.io-index)",
"sha2 0.8.0 (registry+https://github.com/rust-lang/crates.io-index)",
"smallvec 0.6.10 (registry+https://github.com/rust-lang/crates.io-index)",
"strum 0.16.0 (registry+https://github.com/rust-lang/crates.io-index)",
@@ -633,13 +633,14 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
[[package]]
name = "ed25519-dalek"
version = "1.0.0-pre.1"
version = "1.0.0-pre.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
dependencies = [
"clear_on_drop 0.2.3 (registry+https://github.com/rust-lang/crates.io-index)",
"curve25519-dalek 1.2.3 (registry+https://github.com/rust-lang/crates.io-index)",
"failure 0.1.5 (registry+https://github.com/rust-lang/crates.io-index)",
"rand 0.6.5 (registry+https://github.com/rust-lang/crates.io-index)",
"rand_core 0.3.1 (registry+https://github.com/rust-lang/crates.io-index)",
"rand_os 0.1.3 (registry+https://github.com/rust-lang/crates.io-index)",
"sha2 0.8.0 (registry+https://github.com/rust-lang/crates.io-index)",
]
@@ -653,7 +654,7 @@ name = "encoding_rs"
version = "0.8.20"
source = "registry+https://github.com/rust-lang/crates.io-index"
dependencies = [
"cfg-if 0.1.9 (registry+https://github.com/rust-lang/crates.io-index)",
"cfg-if 0.1.10 (registry+https://github.com/rust-lang/crates.io-index)",
]
[[package]]
@@ -743,12 +744,13 @@ dependencies = [
[[package]]
name = "flate2"
version = "1.0.11"
version = "1.0.12"
source = "registry+https://github.com/rust-lang/crates.io-index"
dependencies = [
"cfg-if 0.1.10 (registry+https://github.com/rust-lang/crates.io-index)",
"crc32fast 1.2.0 (registry+https://github.com/rust-lang/crates.io-index)",
"libc 0.2.62 (registry+https://github.com/rust-lang/crates.io-index)",
"miniz_oxide 0.3.2 (registry+https://github.com/rust-lang/crates.io-index)",
"miniz_oxide 0.3.3 (registry+https://github.com/rust-lang/crates.io-index)",
]
[[package]]
@@ -815,7 +817,7 @@ name = "getrandom"
version = "0.1.12"
source = "registry+https://github.com/rust-lang/crates.io-index"
dependencies = [
"cfg-if 0.1.9 (registry+https://github.com/rust-lang/crates.io-index)",
"cfg-if 0.1.10 (registry+https://github.com/rust-lang/crates.io-index)",
"libc 0.2.62 (registry+https://github.com/rust-lang/crates.io-index)",
"wasi 0.7.0 (registry+https://github.com/rust-lang/crates.io-index)",
]
@@ -860,6 +862,11 @@ name = "hex"
version = "0.3.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
[[package]]
name = "hex"
version = "0.4.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
[[package]]
name = "hostname"
version = "0.1.5"
@@ -931,7 +938,7 @@ dependencies = [
"http 0.1.18 (registry+https://github.com/rust-lang/crates.io-index)",
"http-body 0.1.0 (registry+https://github.com/rust-lang/crates.io-index)",
"httparse 1.3.4 (registry+https://github.com/rust-lang/crates.io-index)",
"iovec 0.1.2 (registry+https://github.com/rust-lang/crates.io-index)",
"iovec 0.1.4 (registry+https://github.com/rust-lang/crates.io-index)",
"itoa 0.4.4 (registry+https://github.com/rust-lang/crates.io-index)",
"log 0.4.8 (registry+https://github.com/rust-lang/crates.io-index)",
"net2 0.2.33 (registry+https://github.com/rust-lang/crates.io-index)",
@@ -941,9 +948,9 @@ dependencies = [
"tokio-buf 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)",
"tokio-executor 0.1.8 (registry+https://github.com/rust-lang/crates.io-index)",
"tokio-io 0.1.12 (registry+https://github.com/rust-lang/crates.io-index)",
"tokio-reactor 0.1.9 (registry+https://github.com/rust-lang/crates.io-index)",
"tokio-reactor 0.1.10 (registry+https://github.com/rust-lang/crates.io-index)",
"tokio-tcp 0.1.3 (registry+https://github.com/rust-lang/crates.io-index)",
"tokio-threadpool 0.1.15 (registry+https://github.com/rust-lang/crates.io-index)",
"tokio-threadpool 0.1.16 (registry+https://github.com/rust-lang/crates.io-index)",
"tokio-timer 0.2.11 (registry+https://github.com/rust-lang/crates.io-index)",
"want 0.2.0 (registry+https://github.com/rust-lang/crates.io-index)",
]
@@ -1025,11 +1032,10 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
[[package]]
name = "iovec"
version = "0.1.2"
version = "0.1.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
dependencies = [
"libc 0.2.62 (registry+https://github.com/rust-lang/crates.io-index)",
"winapi 0.2.8 (registry+https://github.com/rust-lang/crates.io-index)",
]
[[package]]
@@ -1072,7 +1078,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
[[package]]
name = "lettre"
version = "0.9.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
source = "git+https://github.com/deltachat/lettre#aeec999c27a70870e4b7b149e45f24188c1ff351"
dependencies = [
"base64 0.10.1 (registry+https://github.com/rust-lang/crates.io-index)",
"bufstream 0.1.4 (registry+https://github.com/rust-lang/crates.io-index)",
@@ -1080,10 +1086,10 @@ dependencies = [
"hostname 0.1.5 (registry+https://github.com/rust-lang/crates.io-index)",
"log 0.4.8 (registry+https://github.com/rust-lang/crates.io-index)",
"native-tls 0.2.3 (registry+https://github.com/rust-lang/crates.io-index)",
"nom 4.2.3 (registry+https://github.com/rust-lang/crates.io-index)",
"nom 5.0.1 (registry+https://github.com/rust-lang/crates.io-index)",
"serde 1.0.101 (registry+https://github.com/rust-lang/crates.io-index)",
"serde_derive 1.0.101 (registry+https://github.com/rust-lang/crates.io-index)",
"serde_json 1.0.40 (registry+https://github.com/rust-lang/crates.io-index)",
"serde_json 1.0.41 (registry+https://github.com/rust-lang/crates.io-index)",
]
[[package]]
@@ -1091,8 +1097,8 @@ name = "lexical-core"
version = "0.4.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
dependencies = [
"arrayvec 0.4.11 (registry+https://github.com/rust-lang/crates.io-index)",
"cfg-if 0.1.9 (registry+https://github.com/rust-lang/crates.io-index)",
"arrayvec 0.4.12 (registry+https://github.com/rust-lang/crates.io-index)",
"cfg-if 0.1.10 (registry+https://github.com/rust-lang/crates.io-index)",
"rustc_version 0.2.3 (registry+https://github.com/rust-lang/crates.io-index)",
"ryu 1.0.0 (registry+https://github.com/rust-lang/crates.io-index)",
"static_assertions 0.3.4 (registry+https://github.com/rust-lang/crates.io-index)",
@@ -1118,15 +1124,6 @@ name = "linked-hash-map"
version = "0.5.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
[[package]]
name = "lock_api"
version = "0.1.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
dependencies = [
"owning_ref 0.4.0 (registry+https://github.com/rust-lang/crates.io-index)",
"scopeguard 0.3.3 (registry+https://github.com/rust-lang/crates.io-index)",
]
[[package]]
name = "lock_api"
version = "0.2.0"
@@ -1148,7 +1145,7 @@ name = "log"
version = "0.4.8"
source = "registry+https://github.com/rust-lang/crates.io-index"
dependencies = [
"cfg-if 0.1.9 (registry+https://github.com/rust-lang/crates.io-index)",
"cfg-if 0.1.10 (registry+https://github.com/rust-lang/crates.io-index)",
]
[[package]]
@@ -1220,7 +1217,7 @@ dependencies = [
[[package]]
name = "miniz_oxide"
version = "0.3.2"
version = "0.3.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
dependencies = [
"adler32 1.0.4 (registry+https://github.com/rust-lang/crates.io-index)",
@@ -1233,7 +1230,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
dependencies = [
"fuchsia-zircon 0.3.3 (registry+https://github.com/rust-lang/crates.io-index)",
"fuchsia-zircon-sys 0.3.3 (registry+https://github.com/rust-lang/crates.io-index)",
"iovec 0.1.2 (registry+https://github.com/rust-lang/crates.io-index)",
"iovec 0.1.4 (registry+https://github.com/rust-lang/crates.io-index)",
"kernel32-sys 0.2.2 (registry+https://github.com/rust-lang/crates.io-index)",
"libc 0.2.62 (registry+https://github.com/rust-lang/crates.io-index)",
"log 0.4.8 (registry+https://github.com/rust-lang/crates.io-index)",
@@ -1275,9 +1272,9 @@ dependencies = [
"lazy_static 1.4.0 (registry+https://github.com/rust-lang/crates.io-index)",
"libc 0.2.62 (registry+https://github.com/rust-lang/crates.io-index)",
"log 0.4.8 (registry+https://github.com/rust-lang/crates.io-index)",
"openssl 0.10.24 (registry+https://github.com/rust-lang/crates.io-index)",
"openssl 0.10.25 (registry+https://github.com/rust-lang/crates.io-index)",
"openssl-probe 0.1.2 (registry+https://github.com/rust-lang/crates.io-index)",
"openssl-sys 0.9.49 (registry+https://github.com/rust-lang/crates.io-index)",
"openssl-sys 0.9.50 (registry+https://github.com/rust-lang/crates.io-index)",
"schannel 0.1.16 (registry+https://github.com/rust-lang/crates.io-index)",
"security-framework 0.3.1 (registry+https://github.com/rust-lang/crates.io-index)",
"security-framework-sys 0.3.1 (registry+https://github.com/rust-lang/crates.io-index)",
@@ -1289,7 +1286,7 @@ name = "net2"
version = "0.2.33"
source = "registry+https://github.com/rust-lang/crates.io-index"
dependencies = [
"cfg-if 0.1.9 (registry+https://github.com/rust-lang/crates.io-index)",
"cfg-if 0.1.10 (registry+https://github.com/rust-lang/crates.io-index)",
"libc 0.2.62 (registry+https://github.com/rust-lang/crates.io-index)",
"winapi 0.3.8 (registry+https://github.com/rust-lang/crates.io-index)",
]
@@ -1301,14 +1298,14 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
dependencies = [
"bitflags 1.2.0 (registry+https://github.com/rust-lang/crates.io-index)",
"cc 1.0.45 (registry+https://github.com/rust-lang/crates.io-index)",
"cfg-if 0.1.9 (registry+https://github.com/rust-lang/crates.io-index)",
"cfg-if 0.1.10 (registry+https://github.com/rust-lang/crates.io-index)",
"libc 0.2.62 (registry+https://github.com/rust-lang/crates.io-index)",
"void 1.0.2 (registry+https://github.com/rust-lang/crates.io-index)",
]
[[package]]
name = "nodrop"
version = "0.1.13"
version = "0.1.14"
source = "registry+https://github.com/rust-lang/crates.io-index"
[[package]]
@@ -1405,15 +1402,15 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
[[package]]
name = "openssl"
version = "0.10.24"
version = "0.10.25"
source = "registry+https://github.com/rust-lang/crates.io-index"
dependencies = [
"bitflags 1.2.0 (registry+https://github.com/rust-lang/crates.io-index)",
"cfg-if 0.1.9 (registry+https://github.com/rust-lang/crates.io-index)",
"cfg-if 0.1.10 (registry+https://github.com/rust-lang/crates.io-index)",
"foreign-types 0.3.2 (registry+https://github.com/rust-lang/crates.io-index)",
"lazy_static 1.4.0 (registry+https://github.com/rust-lang/crates.io-index)",
"libc 0.2.62 (registry+https://github.com/rust-lang/crates.io-index)",
"openssl-sys 0.9.49 (registry+https://github.com/rust-lang/crates.io-index)",
"openssl-sys 0.9.50 (registry+https://github.com/rust-lang/crates.io-index)",
]
[[package]]
@@ -1431,7 +1428,7 @@ dependencies = [
[[package]]
name = "openssl-sys"
version = "0.9.49"
version = "0.9.50"
source = "registry+https://github.com/rust-lang/crates.io-index"
dependencies = [
"autocfg 0.1.6 (registry+https://github.com/rust-lang/crates.io-index)",
@@ -1458,29 +1455,12 @@ dependencies = [
"winapi 0.3.8 (registry+https://github.com/rust-lang/crates.io-index)",
]
[[package]]
name = "owning_ref"
version = "0.4.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
dependencies = [
"stable_deref_trait 1.1.1 (registry+https://github.com/rust-lang/crates.io-index)",
]
[[package]]
name = "packed_simd"
version = "0.3.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
dependencies = [
"cfg-if 0.1.9 (registry+https://github.com/rust-lang/crates.io-index)",
]
[[package]]
name = "parking_lot"
version = "0.7.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
dependencies = [
"lock_api 0.1.5 (registry+https://github.com/rust-lang/crates.io-index)",
"parking_lot_core 0.4.0 (registry+https://github.com/rust-lang/crates.io-index)",
"cfg-if 0.1.10 (registry+https://github.com/rust-lang/crates.io-index)",
]
[[package]]
@@ -1503,24 +1483,12 @@ dependencies = [
"rustc_version 0.2.3 (registry+https://github.com/rust-lang/crates.io-index)",
]
[[package]]
name = "parking_lot_core"
version = "0.4.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
dependencies = [
"libc 0.2.62 (registry+https://github.com/rust-lang/crates.io-index)",
"rand 0.6.5 (registry+https://github.com/rust-lang/crates.io-index)",
"rustc_version 0.2.3 (registry+https://github.com/rust-lang/crates.io-index)",
"smallvec 0.6.10 (registry+https://github.com/rust-lang/crates.io-index)",
"winapi 0.3.8 (registry+https://github.com/rust-lang/crates.io-index)",
]
[[package]]
name = "parking_lot_core"
version = "0.5.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
dependencies = [
"cfg-if 0.1.9 (registry+https://github.com/rust-lang/crates.io-index)",
"cfg-if 0.1.10 (registry+https://github.com/rust-lang/crates.io-index)",
"cloudabi 0.0.3 (registry+https://github.com/rust-lang/crates.io-index)",
"libc 0.2.62 (registry+https://github.com/rust-lang/crates.io-index)",
"rand 0.6.5 (registry+https://github.com/rust-lang/crates.io-index)",
@@ -1535,7 +1503,7 @@ name = "parking_lot_core"
version = "0.6.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
dependencies = [
"cfg-if 0.1.9 (registry+https://github.com/rust-lang/crates.io-index)",
"cfg-if 0.1.10 (registry+https://github.com/rust-lang/crates.io-index)",
"cloudabi 0.0.3 (registry+https://github.com/rust-lang/crates.io-index)",
"libc 0.2.62 (registry+https://github.com/rust-lang/crates.io-index)",
"redox_syscall 0.1.56 (registry+https://github.com/rust-lang/crates.io-index)",
@@ -1556,7 +1524,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
[[package]]
name = "pgp"
version = "0.2.1"
version = "0.2.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
dependencies = [
"aes 0.3.2 (registry+https://github.com/rust-lang/crates.io-index)",
@@ -1570,17 +1538,17 @@ dependencies = [
"cast5 0.6.0 (registry+https://github.com/rust-lang/crates.io-index)",
"cfb-mode 0.3.2 (registry+https://github.com/rust-lang/crates.io-index)",
"chrono 0.4.9 (registry+https://github.com/rust-lang/crates.io-index)",
"circular 0.2.0 (registry+https://github.com/rust-lang/crates.io-index)",
"circular 0.3.0 (registry+https://github.com/rust-lang/crates.io-index)",
"crc24 0.1.6 (registry+https://github.com/rust-lang/crates.io-index)",
"derive_builder 0.7.2 (registry+https://github.com/rust-lang/crates.io-index)",
"des 0.3.0 (registry+https://github.com/rust-lang/crates.io-index)",
"digest 0.8.1 (registry+https://github.com/rust-lang/crates.io-index)",
"ed25519-dalek 1.0.0-pre.1 (registry+https://github.com/rust-lang/crates.io-index)",
"ed25519-dalek 1.0.0-pre.2 (registry+https://github.com/rust-lang/crates.io-index)",
"enum_primitive 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)",
"failure 0.1.5 (registry+https://github.com/rust-lang/crates.io-index)",
"flate2 1.0.11 (registry+https://github.com/rust-lang/crates.io-index)",
"flate2 1.0.12 (registry+https://github.com/rust-lang/crates.io-index)",
"generic-array 0.12.3 (registry+https://github.com/rust-lang/crates.io-index)",
"hex 0.3.2 (registry+https://github.com/rust-lang/crates.io-index)",
"hex 0.4.0 (registry+https://github.com/rust-lang/crates.io-index)",
"lazy_static 1.4.0 (registry+https://github.com/rust-lang/crates.io-index)",
"log 0.4.8 (registry+https://github.com/rust-lang/crates.io-index)",
"md-5 0.8.0 (registry+https://github.com/rust-lang/crates.io-index)",
@@ -1616,7 +1584,7 @@ version = "0.6.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
dependencies = [
"ansi_term 0.11.0 (registry+https://github.com/rust-lang/crates.io-index)",
"ctor 0.1.11 (registry+https://github.com/rust-lang/crates.io-index)",
"ctor 0.1.12 (registry+https://github.com/rust-lang/crates.io-index)",
"difference 2.0.0 (registry+https://github.com/rust-lang/crates.io-index)",
"output_vt100 0.1.2 (registry+https://github.com/rust-lang/crates.io-index)",
]
@@ -1641,7 +1609,7 @@ dependencies = [
[[package]]
name = "proc-macro2"
version = "1.0.4"
version = "1.0.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
dependencies = [
"unicode-xid 0.2.0 (registry+https://github.com/rust-lang/crates.io-index)",
@@ -1721,7 +1689,7 @@ name = "quote"
version = "1.0.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
dependencies = [
"proc-macro2 1.0.4 (registry+https://github.com/rust-lang/crates.io-index)",
"proc-macro2 1.0.5 (registry+https://github.com/rust-lang/crates.io-index)",
]
[[package]]
@@ -1939,7 +1907,7 @@ dependencies = [
[[package]]
name = "reqwest"
version = "0.9.20"
version = "0.9.21"
source = "registry+https://github.com/rust-lang/crates.io-index"
dependencies = [
"base64 0.10.1 (registry+https://github.com/rust-lang/crates.io-index)",
@@ -1947,7 +1915,7 @@ dependencies = [
"cookie 0.12.0 (registry+https://github.com/rust-lang/crates.io-index)",
"cookie_store 0.7.0 (registry+https://github.com/rust-lang/crates.io-index)",
"encoding_rs 0.8.20 (registry+https://github.com/rust-lang/crates.io-index)",
"flate2 1.0.11 (registry+https://github.com/rust-lang/crates.io-index)",
"flate2 1.0.12 (registry+https://github.com/rust-lang/crates.io-index)",
"futures 0.1.29 (registry+https://github.com/rust-lang/crates.io-index)",
"http 0.1.18 (registry+https://github.com/rust-lang/crates.io-index)",
"hyper 0.12.35 (registry+https://github.com/rust-lang/crates.io-index)",
@@ -1957,13 +1925,13 @@ dependencies = [
"mime_guess 2.0.1 (registry+https://github.com/rust-lang/crates.io-index)",
"native-tls 0.2.3 (registry+https://github.com/rust-lang/crates.io-index)",
"serde 1.0.101 (registry+https://github.com/rust-lang/crates.io-index)",
"serde_json 1.0.40 (registry+https://github.com/rust-lang/crates.io-index)",
"serde_json 1.0.41 (registry+https://github.com/rust-lang/crates.io-index)",
"serde_urlencoded 0.5.5 (registry+https://github.com/rust-lang/crates.io-index)",
"time 0.1.42 (registry+https://github.com/rust-lang/crates.io-index)",
"tokio 0.1.22 (registry+https://github.com/rust-lang/crates.io-index)",
"tokio-executor 0.1.8 (registry+https://github.com/rust-lang/crates.io-index)",
"tokio-io 0.1.12 (registry+https://github.com/rust-lang/crates.io-index)",
"tokio-threadpool 0.1.15 (registry+https://github.com/rust-lang/crates.io-index)",
"tokio-threadpool 0.1.16 (registry+https://github.com/rust-lang/crates.io-index)",
"tokio-timer 0.2.11 (registry+https://github.com/rust-lang/crates.io-index)",
"url 1.7.2 (registry+https://github.com/rust-lang/crates.io-index)",
"uuid 0.7.4 (registry+https://github.com/rust-lang/crates.io-index)",
@@ -1993,7 +1961,7 @@ dependencies = [
"num-iter 0.1.39 (registry+https://github.com/rust-lang/crates.io-index)",
"num-traits 0.2.8 (registry+https://github.com/rust-lang/crates.io-index)",
"rand 0.6.5 (registry+https://github.com/rust-lang/crates.io-index)",
"subtle 2.1.1 (registry+https://github.com/rust-lang/crates.io-index)",
"subtle 2.2.1 (registry+https://github.com/rust-lang/crates.io-index)",
"zeroize 0.6.0 (registry+https://github.com/rust-lang/crates.io-index)",
]
@@ -2096,11 +2064,6 @@ dependencies = [
"parking_lot 0.9.0 (registry+https://github.com/rust-lang/crates.io-index)",
]
[[package]]
name = "scopeguard"
version = "0.3.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
[[package]]
name = "scopeguard"
version = "1.0.0"
@@ -2152,14 +2115,14 @@ name = "serde_derive"
version = "1.0.101"
source = "registry+https://github.com/rust-lang/crates.io-index"
dependencies = [
"proc-macro2 1.0.4 (registry+https://github.com/rust-lang/crates.io-index)",
"proc-macro2 1.0.5 (registry+https://github.com/rust-lang/crates.io-index)",
"quote 1.0.2 (registry+https://github.com/rust-lang/crates.io-index)",
"syn 1.0.5 (registry+https://github.com/rust-lang/crates.io-index)",
]
[[package]]
name = "serde_json"
version = "1.0.40"
version = "1.0.41"
source = "registry+https://github.com/rust-lang/crates.io-index"
dependencies = [
"itoa 0.4.4 (registry+https://github.com/rust-lang/crates.io-index)",
@@ -2222,7 +2185,7 @@ dependencies = [
"error-chain 0.12.1 (registry+https://github.com/rust-lang/crates.io-index)",
"glob 0.2.11 (registry+https://github.com/rust-lang/crates.io-index)",
"pulldown-cmark 0.2.0 (registry+https://github.com/rust-lang/crates.io-index)",
"serde_json 1.0.40 (registry+https://github.com/rust-lang/crates.io-index)",
"serde_json 1.0.41 (registry+https://github.com/rust-lang/crates.io-index)",
"tempdir 0.3.7 (registry+https://github.com/rust-lang/crates.io-index)",
"walkdir 2.2.9 (registry+https://github.com/rust-lang/crates.io-index)",
]
@@ -2247,11 +2210,6 @@ name = "smallvec"
version = "0.6.10"
source = "registry+https://github.com/rust-lang/crates.io-index"
[[package]]
name = "stable_deref_trait"
version = "1.1.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
[[package]]
name = "static_assertions"
version = "0.3.4"
@@ -2289,14 +2247,14 @@ version = "0.16.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
dependencies = [
"heck 0.3.1 (registry+https://github.com/rust-lang/crates.io-index)",
"proc-macro2 1.0.4 (registry+https://github.com/rust-lang/crates.io-index)",
"proc-macro2 1.0.5 (registry+https://github.com/rust-lang/crates.io-index)",
"quote 1.0.2 (registry+https://github.com/rust-lang/crates.io-index)",
"syn 1.0.5 (registry+https://github.com/rust-lang/crates.io-index)",
]
[[package]]
name = "subtle"
version = "2.1.1"
version = "2.2.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
[[package]]
@@ -2334,7 +2292,7 @@ name = "syn"
version = "1.0.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
dependencies = [
"proc-macro2 1.0.4 (registry+https://github.com/rust-lang/crates.io-index)",
"proc-macro2 1.0.5 (registry+https://github.com/rust-lang/crates.io-index)",
"quote 1.0.2 (registry+https://github.com/rust-lang/crates.io-index)",
"unicode-xid 0.2.0 (registry+https://github.com/rust-lang/crates.io-index)",
]
@@ -2372,7 +2330,7 @@ name = "tempfile"
version = "3.1.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
dependencies = [
"cfg-if 0.1.9 (registry+https://github.com/rust-lang/crates.io-index)",
"cfg-if 0.1.10 (registry+https://github.com/rust-lang/crates.io-index)",
"libc 0.2.62 (registry+https://github.com/rust-lang/crates.io-index)",
"rand 0.7.2 (registry+https://github.com/rust-lang/crates.io-index)",
"redox_syscall 0.1.56 (registry+https://github.com/rust-lang/crates.io-index)",
@@ -2434,9 +2392,9 @@ dependencies = [
"tokio-current-thread 0.1.6 (registry+https://github.com/rust-lang/crates.io-index)",
"tokio-executor 0.1.8 (registry+https://github.com/rust-lang/crates.io-index)",
"tokio-io 0.1.12 (registry+https://github.com/rust-lang/crates.io-index)",
"tokio-reactor 0.1.9 (registry+https://github.com/rust-lang/crates.io-index)",
"tokio-reactor 0.1.10 (registry+https://github.com/rust-lang/crates.io-index)",
"tokio-tcp 0.1.3 (registry+https://github.com/rust-lang/crates.io-index)",
"tokio-threadpool 0.1.15 (registry+https://github.com/rust-lang/crates.io-index)",
"tokio-threadpool 0.1.16 (registry+https://github.com/rust-lang/crates.io-index)",
"tokio-timer 0.2.11 (registry+https://github.com/rust-lang/crates.io-index)",
]
@@ -2480,7 +2438,7 @@ dependencies = [
[[package]]
name = "tokio-reactor"
version = "0.1.9"
version = "0.1.10"
source = "registry+https://github.com/rust-lang/crates.io-index"
dependencies = [
"crossbeam-utils 0.6.6 (registry+https://github.com/rust-lang/crates.io-index)",
@@ -2489,7 +2447,7 @@ dependencies = [
"log 0.4.8 (registry+https://github.com/rust-lang/crates.io-index)",
"mio 0.6.19 (registry+https://github.com/rust-lang/crates.io-index)",
"num_cpus 1.10.1 (registry+https://github.com/rust-lang/crates.io-index)",
"parking_lot 0.7.1 (registry+https://github.com/rust-lang/crates.io-index)",
"parking_lot 0.9.0 (registry+https://github.com/rust-lang/crates.io-index)",
"slab 0.4.2 (registry+https://github.com/rust-lang/crates.io-index)",
"tokio-executor 0.1.8 (registry+https://github.com/rust-lang/crates.io-index)",
"tokio-io 0.1.12 (registry+https://github.com/rust-lang/crates.io-index)",
@@ -2512,24 +2470,24 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
dependencies = [
"bytes 0.4.12 (registry+https://github.com/rust-lang/crates.io-index)",
"futures 0.1.29 (registry+https://github.com/rust-lang/crates.io-index)",
"iovec 0.1.2 (registry+https://github.com/rust-lang/crates.io-index)",
"iovec 0.1.4 (registry+https://github.com/rust-lang/crates.io-index)",
"mio 0.6.19 (registry+https://github.com/rust-lang/crates.io-index)",
"tokio-io 0.1.12 (registry+https://github.com/rust-lang/crates.io-index)",
"tokio-reactor 0.1.9 (registry+https://github.com/rust-lang/crates.io-index)",
"tokio-reactor 0.1.10 (registry+https://github.com/rust-lang/crates.io-index)",
]
[[package]]
name = "tokio-threadpool"
version = "0.1.15"
version = "0.1.16"
source = "registry+https://github.com/rust-lang/crates.io-index"
dependencies = [
"crossbeam-deque 0.7.1 (registry+https://github.com/rust-lang/crates.io-index)",
"crossbeam-queue 0.1.2 (registry+https://github.com/rust-lang/crates.io-index)",
"crossbeam-utils 0.6.6 (registry+https://github.com/rust-lang/crates.io-index)",
"futures 0.1.29 (registry+https://github.com/rust-lang/crates.io-index)",
"lazy_static 1.4.0 (registry+https://github.com/rust-lang/crates.io-index)",
"log 0.4.8 (registry+https://github.com/rust-lang/crates.io-index)",
"num_cpus 1.10.1 (registry+https://github.com/rust-lang/crates.io-index)",
"rand 0.6.5 (registry+https://github.com/rust-lang/crates.io-index)",
"slab 0.4.2 (registry+https://github.com/rust-lang/crates.io-index)",
"tokio-executor 0.1.8 (registry+https://github.com/rust-lang/crates.io-index)",
]
@@ -2568,7 +2526,7 @@ name = "try_from"
version = "0.3.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
dependencies = [
"cfg-if 0.1.9 (registry+https://github.com/rust-lang/crates.io-index)",
"cfg-if 0.1.10 (registry+https://github.com/rust-lang/crates.io-index)",
]
[[package]]
@@ -2673,7 +2631,7 @@ name = "uuid"
version = "0.6.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
dependencies = [
"cfg-if 0.1.9 (registry+https://github.com/rust-lang/crates.io-index)",
"cfg-if 0.1.10 (registry+https://github.com/rust-lang/crates.io-index)",
"rand 0.4.6 (registry+https://github.com/rust-lang/crates.io-index)",
]
@@ -2856,7 +2814,7 @@ dependencies = [
"checksum aho-corasick 0.7.6 (registry+https://github.com/rust-lang/crates.io-index)" = "58fb5e95d83b38284460a5fda7d6470aa0b8844d283a0b614b8535e880800d2d"
"checksum ansi_term 0.11.0 (registry+https://github.com/rust-lang/crates.io-index)" = "ee49baf6cb617b853aa8d93bf420db2383fab46d314482ca2803b40d5fde979b"
"checksum arrayref 0.3.5 (registry+https://github.com/rust-lang/crates.io-index)" = "0d382e583f07208808f6b1249e60848879ba3543f57c32277bf52d69c2f0f0ee"
"checksum arrayvec 0.4.11 (registry+https://github.com/rust-lang/crates.io-index)" = "b8d73f9beda665eaa98ab9e4f7442bd4e7de6652587de55b2525e52e29c1b0ba"
"checksum arrayvec 0.4.12 (registry+https://github.com/rust-lang/crates.io-index)" = "cd9fd44efafa8690358b7408d253adf110036b88f55672a933f01d616ad9b1b9"
"checksum ascii_utils 0.9.3 (registry+https://github.com/rust-lang/crates.io-index)" = "71938f30533e4d95a6d17aa530939da3842c2ab6f4f84b9dae68447e4129f74a"
"checksum atty 0.2.13 (registry+https://github.com/rust-lang/crates.io-index)" = "1803c647a3ec87095e7ae7acfca019e98de5ec9a7d01343f611cf3152ed71a90"
"checksum autocfg 0.1.6 (registry+https://github.com/rust-lang/crates.io-index)" = "b671c8fb71b457dd4ae18c4ba1e59aa81793daacc361d82fcd410cef0d491875"
@@ -2884,10 +2842,10 @@ dependencies = [
"checksum cast5 0.6.0 (registry+https://github.com/rust-lang/crates.io-index)" = "4ce5759b4c52ca74f9a98421817c882f1fd9b0071ae41cd61ab9f9d059c04fd6"
"checksum cc 1.0.45 (registry+https://github.com/rust-lang/crates.io-index)" = "4fc9a35e1f4290eb9e5fc54ba6cf40671ed2a2514c3eeb2b2a908dda2ea5a1be"
"checksum cfb-mode 0.3.2 (registry+https://github.com/rust-lang/crates.io-index)" = "190e7b55d3a27cf8879becf61035a141cbc783f3258a41d16d1706719f991345"
"checksum cfg-if 0.1.9 (registry+https://github.com/rust-lang/crates.io-index)" = "b486ce3ccf7ffd79fdeb678eac06a9e6c09fc88d33836340becb8fffe87c5e33"
"checksum cfg-if 0.1.10 (registry+https://github.com/rust-lang/crates.io-index)" = "4785bdd1c96b2a846b2bd7cc02e86b6b3dbf14e7e53446c4f54c92a361040822"
"checksum charset 0.1.2 (registry+https://github.com/rust-lang/crates.io-index)" = "4f426e64df1c3de26cbf44593c6ffff5dbfd43bbf9de0d075058558126b3fc73"
"checksum chrono 0.4.9 (registry+https://github.com/rust-lang/crates.io-index)" = "e8493056968583b0193c1bb04d6f7684586f3726992d6c573261941a895dbd68"
"checksum circular 0.2.0 (registry+https://github.com/rust-lang/crates.io-index)" = "82945ea9ff134eba321833377d0c485f5c6fb4c8e26cfec199174e2970d5385a"
"checksum circular 0.3.0 (registry+https://github.com/rust-lang/crates.io-index)" = "b0fc239e0f6cb375d2402d48afb92f76f5404fd1df208a41930ec81eda078bea"
"checksum clear_on_drop 0.2.3 (registry+https://github.com/rust-lang/crates.io-index)" = "97276801e127ffb46b66ce23f35cc96bd454fa311294bced4bbace7baa8b1d17"
"checksum cloudabi 0.0.3 (registry+https://github.com/rust-lang/crates.io-index)" = "ddfc5b9aa5d4507acaf872de71051dfd0e309860e88966e1051e462a077aac4f"
"checksum constant_time_eq 0.1.4 (registry+https://github.com/rust-lang/crates.io-index)" = "995a44c877f9212528ccc74b21a232f66ad69001e40ede5bcee2ac9ef2657120"
@@ -2901,7 +2859,7 @@ dependencies = [
"checksum crossbeam-epoch 0.7.2 (registry+https://github.com/rust-lang/crates.io-index)" = "fedcd6772e37f3da2a9af9bf12ebe046c0dfe657992377b4df982a2b54cd37a9"
"checksum crossbeam-queue 0.1.2 (registry+https://github.com/rust-lang/crates.io-index)" = "7c979cd6cfe72335896575c6b5688da489e420d36a27a0b9eb0c73db574b4a4b"
"checksum crossbeam-utils 0.6.6 (registry+https://github.com/rust-lang/crates.io-index)" = "04973fa96e96579258a5091af6003abde64af786b860f18622b82e026cca60e6"
"checksum ctor 0.1.11 (registry+https://github.com/rust-lang/crates.io-index)" = "3e061727ebef83bbccac7c27b9a5ff9fd83094d34cb20f4005440a9562a27de7"
"checksum ctor 0.1.12 (registry+https://github.com/rust-lang/crates.io-index)" = "cd8ce37ad4184ab2ce004c33bf6379185d3b1c95801cab51026bd271bf68eedc"
"checksum curve25519-dalek 1.2.3 (registry+https://github.com/rust-lang/crates.io-index)" = "8b7dcd30ba50cdf88b55b033456138b7c0ac4afdc436d82e1b79f370f24cc66d"
"checksum darling 0.9.0 (registry+https://github.com/rust-lang/crates.io-index)" = "fcfbcb0c5961907597a7d1148e3af036268f2b773886b8bb3eeb1e1281d3d3d6"
"checksum darling_core 0.9.0 (registry+https://github.com/rust-lang/crates.io-index)" = "6afc018370c3bff3eb51f89256a6bdb18b4fdcda72d577982a14954a7a0b402c"
@@ -2916,7 +2874,7 @@ dependencies = [
"checksum digest 0.8.1 (registry+https://github.com/rust-lang/crates.io-index)" = "f3d0c8c8752312f9713efd397ff63acb9f85585afbf179282e720e7704954dd5"
"checksum dirs 1.0.5 (registry+https://github.com/rust-lang/crates.io-index)" = "3fd78930633bd1c6e35c4b42b1df7b0cbc6bc191146e512bb3bedf243fcc3901"
"checksum dtoa 0.4.4 (registry+https://github.com/rust-lang/crates.io-index)" = "ea57b42383d091c85abcc2706240b94ab2a8fa1fc81c10ff23c4de06e2a90b5e"
"checksum ed25519-dalek 1.0.0-pre.1 (registry+https://github.com/rust-lang/crates.io-index)" = "81956bcf7ef761fb4e1d88de3fa181358a0d26cbcb9755b587a08f9119824b86"
"checksum ed25519-dalek 1.0.0-pre.2 (registry+https://github.com/rust-lang/crates.io-index)" = "845aaacc16f01178f33349e7c992ecd0cee095aa5e577f0f4dee35971bd36455"
"checksum either 1.5.3 (registry+https://github.com/rust-lang/crates.io-index)" = "bb1f6b1ce1c140482ea30ddd3335fc0024ac7ee112895426e0a629a6c20adfe3"
"checksum encoding_rs 0.8.20 (registry+https://github.com/rust-lang/crates.io-index)" = "87240518927716f79692c2ed85bfe6e98196d18c6401ec75355760233a7e12e9"
"checksum entities 1.0.1 (registry+https://github.com/rust-lang/crates.io-index)" = "b5320ae4c3782150d900b79807611a59a99fc9a1d61d686faafc24b93fc8d7ca"
@@ -2930,7 +2888,7 @@ dependencies = [
"checksum fallible-iterator 0.2.0 (registry+https://github.com/rust-lang/crates.io-index)" = "4443176a9f2c162692bd3d352d745ef9413eec5782a80d8fd6f8a1ac692a07f7"
"checksum fallible-streaming-iterator 0.1.9 (registry+https://github.com/rust-lang/crates.io-index)" = "7360491ce676a36bf9bb3c56c1aa791658183a54d2744120f27285738d90465a"
"checksum fast_chemail 0.9.6 (registry+https://github.com/rust-lang/crates.io-index)" = "495a39d30d624c2caabe6312bfead73e7717692b44e0b32df168c275a2e8e9e4"
"checksum flate2 1.0.11 (registry+https://github.com/rust-lang/crates.io-index)" = "2adaffba6388640136149e18ed080b77a78611c1e1d6de75aedcdf78df5d4682"
"checksum flate2 1.0.12 (registry+https://github.com/rust-lang/crates.io-index)" = "ad3c5233c9a940c8719031b423d7e6c16af66e031cb0420b0896f5245bf181d3"
"checksum fnv 1.0.6 (registry+https://github.com/rust-lang/crates.io-index)" = "2fad85553e09a6f881f739c29f0b00b0f01357c743266d478b68951ce23285f3"
"checksum foreign-types 0.3.2 (registry+https://github.com/rust-lang/crates.io-index)" = "f6f339eb8adc052cd2ca78910fda869aefa38d22d5cb648e6485e4d3fc06f3b1"
"checksum foreign-types-shared 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)" = "00b0228411908ca8685dba7fc2cdd70ec9990a6e753e89b6ac91a84c40fbaf4b"
@@ -2946,6 +2904,7 @@ dependencies = [
"checksum h2 0.1.26 (registry+https://github.com/rust-lang/crates.io-index)" = "a5b34c246847f938a410a03c5458c7fee2274436675e76d8b903c08efc29c462"
"checksum heck 0.3.1 (registry+https://github.com/rust-lang/crates.io-index)" = "20564e78d53d2bb135c343b3f47714a56af2061f1c928fdb541dc7b9fdd94205"
"checksum hex 0.3.2 (registry+https://github.com/rust-lang/crates.io-index)" = "805026a5d0141ffc30abb3be3173848ad46a1b1664fe632428479619a3644d77"
"checksum hex 0.4.0 (registry+https://github.com/rust-lang/crates.io-index)" = "023b39be39e3a2da62a94feb433e91e8bcd37676fbc8bea371daf52b7a769a3e"
"checksum hostname 0.1.5 (registry+https://github.com/rust-lang/crates.io-index)" = "21ceb46a83a85e824ef93669c8b390009623863b5c195d1ba747292c0c72f94e"
"checksum http 0.1.18 (registry+https://github.com/rust-lang/crates.io-index)" = "372bcb56f939e449117fb0869c2e8fd8753a8223d92a172c6e808cf123a5b6e4"
"checksum http-body 0.1.0 (registry+https://github.com/rust-lang/crates.io-index)" = "6741c859c1b2463a423a1dbce98d418e6c3c3fc720fb0d45528657320920292d"
@@ -2961,19 +2920,18 @@ dependencies = [
"checksum imap 1.0.2 (git+https://github.com/jonhoo/rust-imap?rev=281d2eb8ab50dc656ceff2ae749ca5045f334e15)" = "<none>"
"checksum imap-proto 0.9.0 (registry+https://github.com/rust-lang/crates.io-index)" = "1b92ca529b24c5f80a950abe993d3883df6fe6791d4a46b1fda1eb339796c589"
"checksum indexmap 1.2.0 (registry+https://github.com/rust-lang/crates.io-index)" = "a61202fbe46c4a951e9404a720a0180bcf3212c750d735cb5c4ba4dc551299f3"
"checksum iovec 0.1.2 (registry+https://github.com/rust-lang/crates.io-index)" = "dbe6e417e7d0975db6512b90796e8ce223145ac4e33c377e4a42882a0e88bb08"
"checksum iovec 0.1.4 (registry+https://github.com/rust-lang/crates.io-index)" = "b2b3ea6ff95e175473f8ffe6a7eb7c00d054240321b84c57051175fe3c1e075e"
"checksum itertools 0.8.0 (registry+https://github.com/rust-lang/crates.io-index)" = "5b8467d9c1cebe26feb08c640139247fac215782d35371ade9a2136ed6085358"
"checksum itoa 0.4.4 (registry+https://github.com/rust-lang/crates.io-index)" = "501266b7edd0174f8530248f87f99c88fbe60ca4ef3dd486835b8d8d53136f7f"
"checksum jetscii 0.4.4 (registry+https://github.com/rust-lang/crates.io-index)" = "5f25cca2463cb19dbb1061eb3bd38a8b5e4ce1cc5a5a9fc0e02de486d92b9b05"
"checksum keccak 0.1.0 (registry+https://github.com/rust-lang/crates.io-index)" = "67c21572b4949434e4fc1e1978b99c5f77064153c59d998bf13ecd96fb5ecba7"
"checksum kernel32-sys 0.2.2 (registry+https://github.com/rust-lang/crates.io-index)" = "7507624b29483431c0ba2d82aece8ca6cdba9382bff4ddd0f7490560c056098d"
"checksum lazy_static 1.4.0 (registry+https://github.com/rust-lang/crates.io-index)" = "e2abad23fbc42b3700f2f279844dc832adb2b2eb069b2df918f455c4e18cc646"
"checksum lettre 0.9.2 (registry+https://github.com/rust-lang/crates.io-index)" = "c66afaa5dfadbb81d4e00fd1d1ab057c7cd4c799c5a44e0009386d553587e728"
"checksum lettre 0.9.2 (git+https://github.com/deltachat/lettre)" = "<none>"
"checksum lexical-core 0.4.6 (registry+https://github.com/rust-lang/crates.io-index)" = "2304bccb228c4b020f3a4835d247df0a02a7c4686098d4167762cfbbe4c5cb14"
"checksum libc 0.2.62 (registry+https://github.com/rust-lang/crates.io-index)" = "34fcd2c08d2f832f376f4173a231990fa5aef4e99fb569867318a227ef4c06ba"
"checksum libsqlite3-sys 0.16.0 (registry+https://github.com/rust-lang/crates.io-index)" = "5e5b95e89c330291768dc840238db7f9e204fd208511ab6319b56193a7f2ae25"
"checksum linked-hash-map 0.5.2 (registry+https://github.com/rust-lang/crates.io-index)" = "ae91b68aebc4ddb91978b11a1b02ddd8602a05ec19002801c5666000e05e0f83"
"checksum lock_api 0.1.5 (registry+https://github.com/rust-lang/crates.io-index)" = "62ebf1391f6acad60e5c8b43706dde4582df75c06698ab44511d15016bc2442c"
"checksum lock_api 0.2.0 (registry+https://github.com/rust-lang/crates.io-index)" = "ed946d4529956a20f2d63ebe1b69996d5a2137c91913fe3ebbeff957f5bca7ff"
"checksum lock_api 0.3.1 (registry+https://github.com/rust-lang/crates.io-index)" = "f8912e782533a93a167888781b836336a6ca5da6175c05944c86cf28c31104dc"
"checksum log 0.4.8 (registry+https://github.com/rust-lang/crates.io-index)" = "14b6052be84e6b71ab17edffc2eeabf5c2c3ae1fdb464aae35ac50c67a44e1f7"
@@ -2986,13 +2944,13 @@ dependencies = [
"checksum memoffset 0.5.1 (registry+https://github.com/rust-lang/crates.io-index)" = "ce6075db033bbbb7ee5a0bbd3a3186bbae616f57fb001c485c7ff77955f8177f"
"checksum mime 0.3.14 (registry+https://github.com/rust-lang/crates.io-index)" = "dd1d63acd1b78403cc0c325605908475dd9b9a3acbf65ed8bcab97e27014afcf"
"checksum mime_guess 2.0.1 (registry+https://github.com/rust-lang/crates.io-index)" = "1a0ed03949aef72dbdf3116a383d7b38b4768e6f960528cd6a6044aa9ed68599"
"checksum miniz_oxide 0.3.2 (registry+https://github.com/rust-lang/crates.io-index)" = "7108aff85b876d06f22503dcce091e29f76733b2bfdd91eebce81f5e68203a10"
"checksum miniz_oxide 0.3.3 (registry+https://github.com/rust-lang/crates.io-index)" = "304f66c19be2afa56530fa7c39796192eef38618da8d19df725ad7c6d6b2aaae"
"checksum mio 0.6.19 (registry+https://github.com/rust-lang/crates.io-index)" = "83f51996a3ed004ef184e16818edc51fadffe8e7ca68be67f9dee67d84d0ff23"
"checksum miow 0.2.1 (registry+https://github.com/rust-lang/crates.io-index)" = "8c1f2f3b1cf331de6896aabf6e9d55dca90356cc9960cca7eaaf408a355ae919"
"checksum native-tls 0.2.3 (registry+https://github.com/rust-lang/crates.io-index)" = "4b2df1a4c22fd44a62147fd8f13dd0f95c9d8ca7b2610299b2a2f9cf8964274e"
"checksum net2 0.2.33 (registry+https://github.com/rust-lang/crates.io-index)" = "42550d9fb7b6684a6d404d9fa7250c2eb2646df731d1c06afc06dcee9e1bcf88"
"checksum nix 0.13.1 (registry+https://github.com/rust-lang/crates.io-index)" = "4dbdc256eaac2e3bd236d93ad999d3479ef775c863dbda3068c4006a92eec51b"
"checksum nodrop 0.1.13 (registry+https://github.com/rust-lang/crates.io-index)" = "2f9667ddcc6cc8a43afc9b7917599d7216aa09c463919ea32c59ed6cac8bc945"
"checksum nodrop 0.1.14 (registry+https://github.com/rust-lang/crates.io-index)" = "72ef4a56884ca558e5ddb05a1d1e7e1bfd9a68d9ed024c21704cc98872dae1bb"
"checksum nom 4.2.3 (registry+https://github.com/rust-lang/crates.io-index)" = "2ad2a91a8e869eeb30b9cb3119ae87773a8f4ae617f41b1eb9c154b2905f7bd6"
"checksum nom 5.0.1 (registry+https://github.com/rust-lang/crates.io-index)" = "c618b63422da4401283884e6668d39f819a106ef51f5f59b81add00075da35ca"
"checksum num-bigint-dig 0.4.0 (registry+https://github.com/rust-lang/crates.io-index)" = "3cd60678022301da54082fcc383647fc895cba2795f868c871d58d29c8922595"
@@ -3003,29 +2961,26 @@ dependencies = [
"checksum num-traits 0.2.8 (registry+https://github.com/rust-lang/crates.io-index)" = "6ba9a427cfca2be13aa6f6403b0b7e7368fe982bfa16fccc450ce74c46cd9b32"
"checksum num_cpus 1.10.1 (registry+https://github.com/rust-lang/crates.io-index)" = "bcef43580c035376c0705c42792c294b66974abbfd2789b511784023f71f3273"
"checksum opaque-debug 0.2.3 (registry+https://github.com/rust-lang/crates.io-index)" = "2839e79665f131bdb5782e51f2c6c9599c133c6098982a54c794358bf432529c"
"checksum openssl 0.10.24 (registry+https://github.com/rust-lang/crates.io-index)" = "8152bb5a9b5b721538462336e3bef9a539f892715e5037fda0f984577311af15"
"checksum openssl 0.10.25 (registry+https://github.com/rust-lang/crates.io-index)" = "2f372b2b53ce10fb823a337aaa674e3a7d072b957c6264d0f4ff0bd86e657449"
"checksum openssl-probe 0.1.2 (registry+https://github.com/rust-lang/crates.io-index)" = "77af24da69f9d9341038eba93a073b1fdaaa1b788221b00a69bce9e762cb32de"
"checksum openssl-src 111.6.0+1.1.1d (registry+https://github.com/rust-lang/crates.io-index)" = "b9c2da1de8a7a3f860919c01540b03a6db16de042405a8a07a5e9d0b4b825d9c"
"checksum openssl-sys 0.9.49 (registry+https://github.com/rust-lang/crates.io-index)" = "f4fad9e54bd23bd4cbbe48fdc08a1b8091707ac869ef8508edea2fec77dcc884"
"checksum openssl-sys 0.9.50 (registry+https://github.com/rust-lang/crates.io-index)" = "2c42dcccb832556b5926bc9ae61e8775f2a61e725ab07ab3d1e7fcf8ae62c3b6"
"checksum os_type 2.2.0 (registry+https://github.com/rust-lang/crates.io-index)" = "7edc011af0ae98b7f88cf7e4a83b70a54a75d2b8cb013d6efd02e5956207e9eb"
"checksum output_vt100 0.1.2 (registry+https://github.com/rust-lang/crates.io-index)" = "53cdc5b785b7a58c5aad8216b3dfa114df64b0b06ae6e1501cef91df2fbdf8f9"
"checksum owning_ref 0.4.0 (registry+https://github.com/rust-lang/crates.io-index)" = "49a4b8ea2179e6a2e27411d3bca09ca6dd630821cf6894c6c7c8467a8ee7ef13"
"checksum packed_simd 0.3.3 (registry+https://github.com/rust-lang/crates.io-index)" = "a85ea9fc0d4ac0deb6fe7911d38786b32fc11119afd9e9d38b84ff691ce64220"
"checksum parking_lot 0.7.1 (registry+https://github.com/rust-lang/crates.io-index)" = "ab41b4aed082705d1056416ae4468b6ea99d52599ecf3169b00088d43113e337"
"checksum parking_lot 0.8.0 (registry+https://github.com/rust-lang/crates.io-index)" = "fa7767817701cce701d5585b9c4db3cdd02086398322c1d7e8bf5094a96a2ce7"
"checksum parking_lot 0.9.0 (registry+https://github.com/rust-lang/crates.io-index)" = "f842b1982eb6c2fe34036a4fbfb06dd185a3f5c8edfaacdf7d1ea10b07de6252"
"checksum parking_lot_core 0.4.0 (registry+https://github.com/rust-lang/crates.io-index)" = "94c8c7923936b28d546dfd14d4472eaf34c99b14e1c973a32b3e6d4eb04298c9"
"checksum parking_lot_core 0.5.0 (registry+https://github.com/rust-lang/crates.io-index)" = "cb88cb1cb3790baa6776844f968fea3be44956cf184fa1be5a03341f5491278c"
"checksum parking_lot_core 0.6.2 (registry+https://github.com/rust-lang/crates.io-index)" = "b876b1b9e7ac6e1a74a6da34d25c42e17e8862aa409cbbbdcfc8d86c6f3bc62b"
"checksum percent-encoding 1.0.1 (registry+https://github.com/rust-lang/crates.io-index)" = "31010dd2e1ac33d5b46a5b413495239882813e0369f8ed8a5e266f173602f831"
"checksum percent-encoding 2.1.0 (registry+https://github.com/rust-lang/crates.io-index)" = "d4fd5641d01c8f18a23da7b6fe29298ff4b55afcccdf78973b24cf3175fee32e"
"checksum pgp 0.2.1 (registry+https://github.com/rust-lang/crates.io-index)" = "bb80b37b7debf9a98dc0caca3ed40ddf1d383691208763d0458df0b91521020f"
"checksum pgp 0.2.3 (registry+https://github.com/rust-lang/crates.io-index)" = "1c4b0aed94774465356a8cb00525c7312c465072293ff53ed7b39f4f9cbefb41"
"checksum pkg-config 0.3.16 (registry+https://github.com/rust-lang/crates.io-index)" = "72d5370d90f49f70bd033c3d75e87fc529fbfff9d6f7cccef07d6170079d91ea"
"checksum ppv-lite86 0.2.5 (registry+https://github.com/rust-lang/crates.io-index)" = "e3cbf9f658cdb5000fcf6f362b8ea2ba154b9f146a61c7a20d647034c6b6561b"
"checksum pretty_assertions 0.6.1 (registry+https://github.com/rust-lang/crates.io-index)" = "3f81e1644e1b54f5a68959a29aa86cde704219254669da328ecfdf6a1f09d427"
"checksum pretty_env_logger 0.3.1 (registry+https://github.com/rust-lang/crates.io-index)" = "717ee476b1690853d222af4634056d830b5197ffd747726a9a1eee6da9f49074"
"checksum proc-macro2 0.4.30 (registry+https://github.com/rust-lang/crates.io-index)" = "cf3d2011ab5c909338f7887f4fc896d35932e29146c12c8d01da6b22a80ba759"
"checksum proc-macro2 1.0.4 (registry+https://github.com/rust-lang/crates.io-index)" = "afdc77cc74ec70ed262262942ebb7dac3d479e9e5cfa2da1841c0806f6cdabcc"
"checksum proc-macro2 1.0.5 (registry+https://github.com/rust-lang/crates.io-index)" = "90cf5f418035b98e655e9cdb225047638296b862b42411c4e45bb88d700f7fc0"
"checksum proptest 0.9.4 (registry+https://github.com/rust-lang/crates.io-index)" = "cf147e022eacf0c8a054ab864914a7602618adba841d800a9a9868a5237a529f"
"checksum publicsuffix 1.5.3 (registry+https://github.com/rust-lang/crates.io-index)" = "9bf259a81de2b2eb9850ec990ec78e6a25319715584fd7652b9b26f96fcb1510"
"checksum pulldown-cmark 0.2.0 (registry+https://github.com/rust-lang/crates.io-index)" = "eef52fac62d0ea7b9b4dc7da092aa64ea7ec3d90af6679422d3d7e0e14b6ee15"
@@ -3057,7 +3012,7 @@ dependencies = [
"checksum regex 1.3.1 (registry+https://github.com/rust-lang/crates.io-index)" = "dc220bd33bdce8f093101afe22a037b8eb0e5af33592e6a9caafff0d4cb81cbd"
"checksum regex-syntax 0.6.12 (registry+https://github.com/rust-lang/crates.io-index)" = "11a7e20d1cce64ef2fed88b66d347f88bd9babb82845b2b858f3edbf59a4f716"
"checksum remove_dir_all 0.5.2 (registry+https://github.com/rust-lang/crates.io-index)" = "4a83fa3702a688b9359eccba92d153ac33fd2e8462f9e0e3fdf155239ea7792e"
"checksum reqwest 0.9.20 (registry+https://github.com/rust-lang/crates.io-index)" = "0f6d896143a583047512e59ac54a215cb203c29cc941917343edea3be8df9c78"
"checksum reqwest 0.9.21 (registry+https://github.com/rust-lang/crates.io-index)" = "02b7e953e14c6f3102b7e8d1f1ee3abf5ecee80b427f5565c9389835cecae95c"
"checksum ripemd160 0.8.0 (registry+https://github.com/rust-lang/crates.io-index)" = "ad5112e0dbbb87577bfbc56c42450235e3012ce336e29c5befd7807bd626da4a"
"checksum rsa 0.1.3 (registry+https://github.com/rust-lang/crates.io-index)" = "6ad8d3632f6745bb671c8637e2aa44015537c5e384789d2ea3235739301ed1e0"
"checksum rusqlite 0.20.0 (registry+https://github.com/rust-lang/crates.io-index)" = "2a194373ef527035645a1bc21b10dc2125f73497e6e155771233eb187aedd051"
@@ -3071,7 +3026,6 @@ dependencies = [
"checksum same-file 1.0.5 (registry+https://github.com/rust-lang/crates.io-index)" = "585e8ddcedc187886a30fa705c47985c3fa88d06624095856b36ca0b82ff4421"
"checksum schannel 0.1.16 (registry+https://github.com/rust-lang/crates.io-index)" = "87f550b06b6cba9c8b8be3ee73f391990116bf527450d2556e9b9ce263b9a021"
"checksum scheduled-thread-pool 0.2.2 (registry+https://github.com/rust-lang/crates.io-index)" = "bd07742e081ff6c077f5f6b283f12f32b9e7cc765b316160d66289b74546fbb3"
"checksum scopeguard 0.3.3 (registry+https://github.com/rust-lang/crates.io-index)" = "94258f53601af11e6a49f722422f6e3425c52b06245a5cf9bc09908b174f5e27"
"checksum scopeguard 1.0.0 (registry+https://github.com/rust-lang/crates.io-index)" = "b42e15e59b18a828bbf5c58ea01debb36b9b096346de35d941dcb89009f24a0d"
"checksum security-framework 0.3.1 (registry+https://github.com/rust-lang/crates.io-index)" = "eee63d0f4a9ec776eeb30e220f0bc1e092c3ad744b2a379e3993070364d3adc2"
"checksum security-framework-sys 0.3.1 (registry+https://github.com/rust-lang/crates.io-index)" = "9636f8989cbf61385ae4824b98c1aaa54c994d7d8b41f11c601ed799f0549a56"
@@ -3079,7 +3033,7 @@ dependencies = [
"checksum semver-parser 0.7.0 (registry+https://github.com/rust-lang/crates.io-index)" = "388a1df253eca08550bef6c72392cfe7c30914bf41df5269b68cbd6ff8f570a3"
"checksum serde 1.0.101 (registry+https://github.com/rust-lang/crates.io-index)" = "9796c9b7ba2ffe7a9ce53c2287dfc48080f4b2b362fcc245a259b3a7201119dd"
"checksum serde_derive 1.0.101 (registry+https://github.com/rust-lang/crates.io-index)" = "4b133a43a1ecd55d4086bd5b4dc6c1751c68b1bfbeba7a5040442022c7e7c02e"
"checksum serde_json 1.0.40 (registry+https://github.com/rust-lang/crates.io-index)" = "051c49229f282f7c6f3813f8286cc1e3323e8051823fce42c7ea80fe13521704"
"checksum serde_json 1.0.41 (registry+https://github.com/rust-lang/crates.io-index)" = "2f72eb2a68a7dc3f9a691bfda9305a1c017a6215e5a4545c258500d2099a37c2"
"checksum serde_urlencoded 0.5.5 (registry+https://github.com/rust-lang/crates.io-index)" = "642dd69105886af2efd227f75a520ec9b44a820d65bc133a9131f7d229fd165a"
"checksum sha-1 0.8.1 (registry+https://github.com/rust-lang/crates.io-index)" = "23962131a91661d643c98940b20fcaffe62d776a823247be80a48fcb8b6fce68"
"checksum sha2 0.8.0 (registry+https://github.com/rust-lang/crates.io-index)" = "7b4d8bfd0e469f417657573d8451fb33d16cfe0989359b93baf3a1ffc639543d"
@@ -3088,14 +3042,13 @@ dependencies = [
"checksum slab 0.4.2 (registry+https://github.com/rust-lang/crates.io-index)" = "c111b5bd5695e56cffe5129854aa230b39c93a305372fdbb2668ca2394eea9f8"
"checksum slice-deque 0.2.4 (registry+https://github.com/rust-lang/crates.io-index)" = "ffddf594f5f597f63533d897427a570dbaa9feabaaa06595b74b71b7014507d7"
"checksum smallvec 0.6.10 (registry+https://github.com/rust-lang/crates.io-index)" = "ab606a9c5e214920bb66c458cd7be8ef094f813f20fe77a54cc7dbfff220d4b7"
"checksum stable_deref_trait 1.1.1 (registry+https://github.com/rust-lang/crates.io-index)" = "dba1a27d3efae4351c8051072d619e3ade2820635c3958d826bfea39d59b54c8"
"checksum static_assertions 0.3.4 (registry+https://github.com/rust-lang/crates.io-index)" = "7f3eb36b47e512f8f1c9e3d10c2c1965bc992bd9cdb024fa581e2194501c83d3"
"checksum stream-cipher 0.3.2 (registry+https://github.com/rust-lang/crates.io-index)" = "8131256a5896cabcf5eb04f4d6dacbe1aefda854b0d9896e09cb58829ec5638c"
"checksum string 0.2.1 (registry+https://github.com/rust-lang/crates.io-index)" = "d24114bfcceb867ca7f71a0d3fe45d45619ec47a6fbfa98cb14e14250bfa5d6d"
"checksum strsim 0.7.0 (registry+https://github.com/rust-lang/crates.io-index)" = "bb4f380125926a99e52bc279241539c018323fab05ad6368b56f93d9369ff550"
"checksum strum 0.16.0 (registry+https://github.com/rust-lang/crates.io-index)" = "6138f8f88a16d90134763314e3fc76fa3ed6a7db4725d6acf9a3ef95a3188d22"
"checksum strum_macros 0.16.0 (registry+https://github.com/rust-lang/crates.io-index)" = "0054a7df764039a6cd8592b9de84be4bec368ff081d203a7d5371cbfa8e65c81"
"checksum subtle 2.1.1 (registry+https://github.com/rust-lang/crates.io-index)" = "01f40907d9ffc762709e4ff3eb4a6f6b41b650375a3f09ac92b641942b7fb082"
"checksum subtle 2.2.1 (registry+https://github.com/rust-lang/crates.io-index)" = "ab3af2eb31c42e8f0ccf43548232556c42737e01a96db6e1777b0be108e79799"
"checksum syn 0.11.11 (registry+https://github.com/rust-lang/crates.io-index)" = "d3b891b9015c88c576343b9b3e41c2c11a51c219ef067b264bd9c8aa9b441dad"
"checksum syn 0.14.9 (registry+https://github.com/rust-lang/crates.io-index)" = "261ae9ecaa397c42b960649561949d69311f08eeaea86a65696e6e46517cf741"
"checksum syn 0.15.44 (registry+https://github.com/rust-lang/crates.io-index)" = "9ca4b3b69a77cbe1ffc9e198781b7acb0c7365a883670e8f1c1bc66fba79a5c5"
@@ -3114,10 +3067,10 @@ dependencies = [
"checksum tokio-current-thread 0.1.6 (registry+https://github.com/rust-lang/crates.io-index)" = "d16217cad7f1b840c5a97dfb3c43b0c871fef423a6e8d2118c604e843662a443"
"checksum tokio-executor 0.1.8 (registry+https://github.com/rust-lang/crates.io-index)" = "0f27ee0e6db01c5f0b2973824547ce7e637b2ed79b891a9677b0de9bd532b6ac"
"checksum tokio-io 0.1.12 (registry+https://github.com/rust-lang/crates.io-index)" = "5090db468dad16e1a7a54c8c67280c5e4b544f3d3e018f0b913b400261f85926"
"checksum tokio-reactor 0.1.9 (registry+https://github.com/rust-lang/crates.io-index)" = "6af16bfac7e112bea8b0442542161bfc41cbfa4466b580bdda7d18cb88b911ce"
"checksum tokio-reactor 0.1.10 (registry+https://github.com/rust-lang/crates.io-index)" = "c56391be9805bc80163151c0b9e5164ee64f4b0200962c346fea12773158f22d"
"checksum tokio-sync 0.1.6 (registry+https://github.com/rust-lang/crates.io-index)" = "2162248ff317e2bc713b261f242b69dbb838b85248ed20bb21df56d60ea4cae7"
"checksum tokio-tcp 0.1.3 (registry+https://github.com/rust-lang/crates.io-index)" = "1d14b10654be682ac43efee27401d792507e30fd8d26389e1da3b185de2e4119"
"checksum tokio-threadpool 0.1.15 (registry+https://github.com/rust-lang/crates.io-index)" = "90ca01319dea1e376a001e8dc192d42ebde6dd532532a5bad988ac37db365b19"
"checksum tokio-threadpool 0.1.16 (registry+https://github.com/rust-lang/crates.io-index)" = "2bd2c6a3885302581f4401c82af70d792bb9df1700e7437b0aeb4ada94d5388c"
"checksum tokio-timer 0.2.11 (registry+https://github.com/rust-lang/crates.io-index)" = "f2106812d500ed25a4f38235b9cae8f78a09edf43203e16e59c3b769a342a60e"
"checksum toml 0.4.10 (registry+https://github.com/rust-lang/crates.io-index)" = "758664fc71a3a69038656bee8b6be6477d2a6c315a6b81f7081f591bffa4111f"
"checksum traitobject 0.1.0 (registry+https://github.com/rust-lang/crates.io-index)" = "efd1f82c56340fdf16f2a953d7bda4f8fdffba13d93b00844c25572110b26079"

View File

@@ -10,7 +10,7 @@ deltachat_derive = { path = "./deltachat_derive" }
mmime = { version = "0.1.2", path = "./mmime" }
libc = "0.2.51"
pgp = { version = "0.2", default-features = false }
pgp = "0.2.3"
hex = "0.3.2"
sha2 = "0.8.0"
rand = "0.6.5"
@@ -19,7 +19,7 @@ reqwest = "0.9.15"
num-derive = "0.2.5"
num-traits = "0.2.6"
native-tls = "0.2.3"
lettre = "0.9.0"
lettre = { git = "https://github.com/deltachat/lettre" }
imap = { git = "https://github.com/jonhoo/rust-imap", rev = "281d2eb8ab50dc656ceff2ae749ca5045f334e15" }
base64 = "0.10"
charset = "0.1"

View File

@@ -7,9 +7,9 @@ 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}
@@ -22,10 +22,11 @@ rsync -avz \
delta@py.delta.chat:build/${BRANCH}
# C docs to c.delta.chat
#rsync -avz \
# -e "ssh -o StrictHostKeyChecking=no -o UserKnownHostsFile=/dev/null" \
# "$DOXYDOCDIR/html/" \
# delta@py.delta.chat:build-c/${BRANCH}
ssh -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}
echo -----------------------
echo upload wheels

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

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

View File

@@ -402,6 +402,18 @@ int dc_set_config (dc_context_t* context, const char*
*/
char* dc_get_config (dc_context_t* context, const char* key);
/**
* Set stock string translation.
*
* The function will emit warnings if it returns an error state.
*
* @param context The context object
* @param stock_id the integer id of the stock message (DC_STR_*)
* @param stock_msg the message to be used
* @return int (==0 on error, 1 on success)
*/
int dc_set_stock_translation(dc_context_t* context, uint32_t, const char* value);
/**
* Get information about the context.
@@ -3719,6 +3731,14 @@ int64_t dc_lot_get_timestamp (const dc_lot_t* lot);
#define DC_MSG_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.
*/
#define DC_MSG_STICKER 23
/**
* Message containing an Audio file.
* File and duration are set via dc_msg_set_file(), dc_msg_set_duration()
@@ -3896,6 +3916,45 @@ int64_t dc_lot_get_timestamp (const dc_lot_t* lot);
*/
#define DC_EVENT_SMTP_MESSAGE_SENT 103
/**
* Emitted when a message was successfully marked as deleted on the IMAP 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
*/
#define DC_EVENT_IMAP_MESSAGE_DELETED 104
/**
* Emitted when a message was successfully moved on IMAP.
*
* @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
*/
#define DC_EVENT_IMAP_MESSAGE_MOVED 105
/**
* Emitted when a new blob file was successfully written
*
* @param data1 0
* @param data2 (const char*) path name
* Must not be free()'d or modified and is valid only until the callback returns.
* @return 0
*/
#define DC_EVENT_NEW_BLOB_FILE 150
/**
* Emitted when a blob file was successfully deleted
*
* @param data1 0
* @param data2 (const char*) path name
* Must not be free()'d or modified and is valid only until the callback returns.
* @return 0
*/
#define DC_EVENT_DELETED_BLOB_FILE 151
/**
* The library-user should write a warning string to the log.
@@ -4142,16 +4201,10 @@ int64_t dc_lot_get_timestamp (const dc_lot_t* lot);
/**
* Requeste a localized string from the frontend.
* (DEPRECATED, DC_EVENT_GET_STRING is not emmitted anymore. Use
* dc_set_stock_translation() to pre-fill translations for stock
* messages.
*
* @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.
*/
#define DC_EVENT_GET_STRING 2091
@@ -4269,7 +4322,8 @@ void dc_array_add_id (dc_array_t*, uint32_t); // depreca
#define DC_STR_MSGLOCATIONENABLED 64
#define DC_STR_MSGLOCATIONDISABLED 65
#define DC_STR_LOCATION 66
#define DC_STR_COUNT 66
#define DC_STR_STICKER 67
#define DC_STR_COUNT 67
void dc_str_unref (char*);

View File

@@ -24,9 +24,8 @@ use num_traits::{FromPrimitive, ToPrimitive};
use deltachat::contact::Contact;
use deltachat::context::Context;
use deltachat::dc_tools::{
as_path, as_str, dc_strdup, to_string, to_string_lossy, OsStrExt, StrExt,
};
use deltachat::dc_tools::{as_path, as_str, dc_strdup, to_string_lossy, OsStrExt, StrExt};
use deltachat::stock::StockMessage;
use deltachat::*;
// as C lacks a good and portable error handling,
@@ -125,11 +124,15 @@ impl ContextWrapper {
| Event::SmtpConnected(msg)
| Event::ImapConnected(msg)
| Event::SmtpMessageSent(msg)
| Event::ImapMessageDeleted(msg)
| Event::ImapMessageMoved(msg)
| Event::NewBlobFile(msg)
| Event::DeletedBlobFile(msg)
| Event::Warning(msg)
| Event::Error(msg)
| Event::ErrorNetwork(msg)
| Event::ErrorSelfNotInGroup(msg) => {
let data2 = CString::new(msg).unwrap();
let data2 = CString::new(msg).unwrap_or_default();
ffi_cb(self, event_id, 0, data2.as_ptr() as uintptr_t)
}
Event::MsgsChanged { chat_id, msg_id }
@@ -148,7 +151,7 @@ impl ContextWrapper {
ffi_cb(self, event_id, progress as uintptr_t, 0)
}
Event::ImexFileWritten(file) => {
let data1 = file.to_c_string().unwrap();
let data1 = file.to_c_string().unwrap_or_default();
ffi_cb(self, event_id, data1.as_ptr() as uintptr_t, 0)
}
Event::SecurejoinInviterProgress {
@@ -164,12 +167,6 @@ impl ContextWrapper {
contact_id as uintptr_t,
progress as uintptr_t,
),
Event::GetString { id, count } => ffi_cb(
self,
event_id,
id.to_u32().unwrap_or_default() as uintptr_t,
count as uintptr_t,
),
}
}
None => 0,
@@ -340,6 +337,35 @@ pub unsafe extern "C" fn dc_get_config(
}
}
#[no_mangle]
pub unsafe extern "C" fn dc_set_stock_translation(
context: *mut dc_context_t,
stock_id: u32,
stock_msg: *mut libc::c_char,
) -> libc::c_int {
if context.is_null() || stock_msg.is_null() {
eprintln!("ignoring careless call to dc_set_stock_string");
return 0;
}
let msg = as_str(stock_msg).to_string();
let ffi_context = &*context;
ffi_context
.with_inner(|ctx| match StockMessage::from_u32(stock_id) {
Some(id) => match ctx.set_stock_translation(id, msg) {
Ok(()) => 1,
Err(err) => {
warn!(ctx, "set_stock_translation failed: {}", err);
0
}
},
None => {
warn!(ctx, "invalid stock message id {}", stock_id);
0
}
})
.unwrap_or(0)
}
#[no_mangle]
pub unsafe extern "C" fn dc_get_info(context: *mut dc_context_t) -> *mut libc::c_char {
if context.is_null() {
@@ -377,8 +403,8 @@ pub unsafe extern "C" fn dc_get_oauth2_url(
return ptr::null_mut(); // NULL explicitly defined as "unknown"
}
let ffi_context = &*context;
let addr = to_string(addr);
let redirect = to_string(redirect);
let addr = to_string_lossy(addr);
let redirect = to_string_lossy(redirect);
ffi_context
.with_inner(|ctx| match oauth2::dc_get_oauth2_url(ctx, addr, redirect) {
Some(res) => res.strdup(),
@@ -1245,8 +1271,11 @@ pub unsafe extern "C" fn dc_forward_msgs(
let ffi_context = &*context;
ffi_context
.with_inner(|ctx| chat::forward_msgs(ctx, ids, chat_id))
.unwrap_or(())
.with_inner(|ctx| {
chat::forward_msgs(ctx, ids, chat_id)
.unwrap_or_log_default(ctx, "Failed to forward message")
})
.unwrap_or_default()
}
#[no_mangle]
@@ -1615,9 +1644,7 @@ pub unsafe extern "C" fn dc_stop_ongoing_process(context: *mut dc_context_t) {
return;
}
let ffi_context = &*context;
ffi_context
.with_inner(|ctx| configure::dc_stop_ongoing_process(ctx))
.ok();
ffi_context.with_inner(|ctx| ctx.stop_ongoing()).ok();
}
#[no_mangle]
@@ -2131,7 +2158,7 @@ pub unsafe extern "C" fn dc_chat_get_profile_image(chat: *mut dc_chat_t) -> *mut
let ffi_context = &*ffi_chat.context;
ffi_context
.with_inner(|ctx| match ffi_chat.chat.get_profile_image(ctx) {
Some(p) => p.to_str().unwrap().to_string().strdup(),
Some(p) => p.to_string_lossy().strdup(),
None => ptr::null_mut(),
})
.unwrap_or_else(|_| ptr::null_mut())
@@ -2476,7 +2503,7 @@ pub unsafe extern "C" fn dc_msg_get_summarytext(
.with_inner(|ctx| {
ffi_msg
.message
.get_summarytext(ctx, approx_characters.try_into().unwrap())
.get_summarytext(ctx, approx_characters.try_into().unwrap_or_default())
})
.unwrap_or_default()
.strdup()
@@ -2768,7 +2795,7 @@ pub unsafe extern "C" fn dc_contact_get_profile_image(
ffi_contact
.contact
.get_profile_image(ctx)
.map(|p| p.to_str().unwrap().to_string().strdup())
.map(|p| p.to_string_lossy().strdup())
.unwrap_or_else(|| std::ptr::null_mut())
})
.unwrap_or_else(|_| ptr::null_mut())

View File

@@ -4,7 +4,6 @@ use std::str::FromStr;
use deltachat::chat::{self, Chat};
use deltachat::chatlist::*;
use deltachat::config;
use deltachat::configure::*;
use deltachat::constants::*;
use deltachat::contact::*;
use deltachat::context::*;
@@ -119,13 +118,13 @@ fn poke_spec(context: &Context, spec: *const libc::c_char) -> libc::c_int {
/* if `spec` is given, remember it for later usage; if it is not given, try to use the last one */
if !spec.is_null() {
real_spec = to_string(spec);
real_spec = to_string_lossy(spec);
context
.sql
.set_config(context, "import_spec", Some(&real_spec))
.set_raw_config(context, "import_spec", Some(&real_spec))
.unwrap();
} else {
let rs = context.sql.get_config(context, "import_spec");
let rs = context.sql.get_raw_config(context, "import_spec");
if rs.is_none() {
error!(context, "Import: No file or folder given.");
return 0;
@@ -471,7 +470,7 @@ pub unsafe fn dc_cmdline(context: &Context, line: &str) -> Result<(), failure::E
ensure!(0 != dc_reset_tables(context, bits), "Reset failed");
}
"stop" => {
dc_stop_ongoing_process(context);
context.stop_ongoing();
}
"set" => {
ensure!(!arg1.is_empty(), "Argument <key> missing.");
@@ -864,7 +863,7 @@ pub unsafe fn dc_cmdline(context: &Context, line: &str) -> Result<(), failure::E
let mut msg_ids = [0; 1];
let chat_id = arg2.parse()?;
msg_ids[0] = arg1.parse()?;
chat::forward_msgs(context, &msg_ids, chat_id);
chat::forward_msgs(context, &msg_ids, chat_id)?;
}
"markseen" => {
ensure!(!arg1.is_empty(), "Argument <msg-id> missing.");

View File

@@ -43,7 +43,6 @@ use self::cmdline::*;
fn receive_event(_context: &Context, event: Event) -> libc::uintptr_t {
match event {
Event::GetString { .. } => {}
Event::Info(msg) => {
/* do not show the event as this would fill the screen */
println!("{}", msg);

View File

@@ -35,93 +35,90 @@ fn cb(_ctx: &Context, event: Event) -> usize {
}
fn main() {
unsafe {
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 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_imap_jobs(&ctx1);
if *r1.read().unwrap() {
perform_imap_fetch(&ctx1);
let ctx = Arc::new(ctx);
let ctx1 = ctx.clone();
let r1 = running.clone();
let t1 = thread::spawn(move || {
while *r1.read().unwrap() {
perform_imap_jobs(&ctx1);
if *r1.read().unwrap() {
perform_imap_fetch(&ctx1);
if *r1.read().unwrap() {
perform_imap_idle(&ctx1);
}
perform_imap_idle(&ctx1);
}
}
});
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);
}
}
});
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);
thread::sleep(duration);
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!("fetching chats..");
let chats = Chatlist::try_load(&ctx, 0, None, None).unwrap();
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);
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::job::interrupt_imap_idle(&ctx);
deltachat::job::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");
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);
// let msglist = dc_get_chat_msgs(&ctx, chat_id, 0, 0);
// for i in 0..dc_array_get_cnt(msglist) {
// let msg_id = dc_array_get_id(msglist, i);
// let msg = dc_get_msg(context, msg_id);
// let text = CStr::from_ptr(dc_msg_get_text(msg)).unwrap();
// println!("Message {}: {}\n", i + 1, text.to_str().unwrap());
// dc_msg_unref(msg);
// }
// dc_array_unref(msglist);
println!("stopping threads");
*running.clone().write().unwrap() = false;
deltachat::job::interrupt_imap_idle(&ctx);
deltachat::job::interrupt_smtp_idle(&ctx);
println!("joining");
t1.join().unwrap();
t2.join().unwrap();
println!("closing");
}

View File

@@ -17,12 +17,12 @@ pub unsafe fn charconv(
assert!(!fromcode.is_null(), "invalid fromcode");
assert!(!s.is_null(), "invalid input string");
if let Some(encoding) =
charset::Charset::for_label(CStr::from_ptr(fromcode).to_str().unwrap().as_bytes())
charset::Charset::for_label(CStr::from_ptr(fromcode).to_string_lossy().as_bytes())
{
let data = std::slice::from_raw_parts(s as *const u8, strlen(s));
let (res, _, _) = encoding.decode(data);
let res_c = CString::new(res.as_bytes()).unwrap();
let res_c = CString::new(res.as_bytes()).unwrap_or_default();
*result = strdup(res_c.as_ptr()) as *mut _;
MAIL_CHARCONV_NO_ERROR as libc::c_int

View File

@@ -123,7 +123,7 @@ unsafe fn display_mime_discrete_type(mut discrete_type: *mut mailmime_discrete_t
_ => {}
};
}
unsafe fn display_mime_data(mut data: *mut mailmime_data) {
pub unsafe fn display_mime_data(mut data: *mut mailmime_data) {
match (*data).dt_type {
0 => {
println!(

View File

@@ -1598,7 +1598,7 @@ unsafe fn mailimf_date_time_write_driver(
(*date_time).dt_sec,
(*date_time).dt_zone,
);
let date_str_c = std::ffi::CString::new(date_str).unwrap();
let date_str_c = std::ffi::CString::new(date_str).unwrap_or_default();
let r = mailimf_string_write_driver(
do_write,
data,

View File

@@ -848,7 +848,7 @@ pub unsafe fn mailmime_generate_boundary() -> *mut libc::c_char {
hex::encode(&std::process::id().to_le_bytes()[..2])
);
let c = std::ffi::CString::new(raw).unwrap();
let c = std::ffi::CString::new(raw).unwrap_or_default();
strdup(c.as_ptr())
}

View File

@@ -338,7 +338,7 @@ unsafe fn mailmime_disposition_param_write_driver(
4 => {
let value = (*param).pa_data.pa_size as u32;
let raw = format!("{}", value);
let raw_c = std::ffi::CString::new(raw).unwrap();
let raw_c = std::ffi::CString::new(raw).unwrap_or_default();
sizestr = strdup(raw_c.as_ptr());
len = strlen(b"size=\x00" as *const u8 as *const libc::c_char)
.wrapping_add(strlen(sizestr))
@@ -542,7 +542,7 @@ unsafe fn mailmime_version_write_driver(
}
let raw = format!("{}.{}", (version >> 16) as i32, (version & 0xffff) as i32);
let raw_c = std::ffi::CString::new(raw).unwrap();
let raw_c = std::ffi::CString::new(raw).unwrap_or_default();
let mut versionstr = strdup(raw_c.as_ptr());
r = mailimf_string_write_driver(do_write, data, col, versionstr, strlen(versionstr));
if r != MAILIMF_NO_ERROR as libc::c_int {
@@ -1515,8 +1515,8 @@ pub unsafe fn mailmime_data_write_driver(
}
1 => {
let filename = CStr::from_ptr((*mime_data).dt_data.dt_filename)
.to_str()
.unwrap();
.to_string_lossy()
.into_owned();
if let Ok(file) = std::fs::File::open(filename) {
if let Ok(mut text) = memmap::MmapOptions::new().map_copy(&file) {
if 0 != (*mime_data).dt_encoded {
@@ -1797,7 +1797,7 @@ pub unsafe fn mailmime_quoted_printable_write_driver(
start = text.offset(i as isize).offset(1isize);
let raw = format!("={:02X}", (ch as libc::c_int));
let raw_c = std::ffi::CString::new(raw).unwrap();
let raw_c = std::ffi::CString::new(raw).unwrap_or_default();
let mut hexstr = strdup(raw_c.as_ptr());
r = mailimf_string_write_driver(
do_write,
@@ -1822,7 +1822,7 @@ pub unsafe fn mailmime_quoted_printable_write_driver(
}
start = text.offset(i as isize).offset(1isize);
let raw = format!("={:02X}", ch as libc::c_int);
let raw_c = std::ffi::CString::new(raw).unwrap();
let raw_c = std::ffi::CString::new(raw).unwrap_or_default();
let mut hexstr = strdup(raw_c.as_ptr());
r = mailimf_string_write_driver(
do_write,
@@ -1866,7 +1866,7 @@ pub unsafe fn mailmime_quoted_printable_write_driver(
}
start = text.offset(i as isize);
let raw = format!("={:02X}", b'\r' as i32);
let raw_c = std::ffi::CString::new(raw).unwrap();
let raw_c = std::ffi::CString::new(raw).unwrap_or_default();
let mut hexstr = strdup(raw_c.as_ptr());
r = mailimf_string_write_driver(do_write, data, col, hexstr, 3i32 as size_t);
if r != MAILIMF_NO_ERROR as libc::c_int {
@@ -1890,7 +1890,7 @@ pub unsafe fn mailmime_quoted_printable_write_driver(
"={:02X}\r\n",
*text.offset(i.wrapping_sub(1i32 as libc::size_t) as isize) as libc::c_int
);
let raw_c = std::ffi::CString::new(raw).unwrap();
let raw_c = std::ffi::CString::new(raw).unwrap_or_default();
let mut hexstr = strdup(raw_c.as_ptr());
r = mailimf_string_write_driver(do_write, data, col, hexstr, strlen(hexstr));
@@ -1917,7 +1917,7 @@ pub unsafe fn mailmime_quoted_printable_write_driver(
"={:02X}\r\n",
*text.offset(i.wrapping_sub(2i32 as libc::size_t) as isize) as libc::c_int
);
let raw_c = std::ffi::CString::new(raw).unwrap();
let raw_c = std::ffi::CString::new(raw).unwrap_or_default();
let mut hexstr = strdup(raw_c.as_ptr());
r = mailimf_string_write_driver(do_write, data, col, hexstr, strlen(hexstr));
@@ -1938,7 +1938,7 @@ pub unsafe fn mailmime_quoted_printable_write_driver(
(*text.offset(i.wrapping_sub(2i32 as libc::size_t) as isize) as u8 as char),
b'\r' as i32
);
let raw_c = std::ffi::CString::new(raw).unwrap();
let raw_c = std::ffi::CString::new(raw).unwrap_or_default();
let mut hexstr = strdup(raw_c.as_ptr());
r = mailimf_string_write_driver(do_write, data, col, hexstr, strlen(hexstr));

View File

@@ -12,6 +12,10 @@ pub(crate) use libc::{
};
pub(crate) unsafe fn strcasecmp(s1: *const libc::c_char, s2: *const libc::c_char) -> libc::c_int {
if s1.is_null() || s2.is_null() {
return 1;
}
let s1 = std::ffi::CStr::from_ptr(s1)
.to_string_lossy()
.to_lowercase();
@@ -30,16 +34,23 @@ pub(crate) unsafe fn strncasecmp(
s2: *const libc::c_char,
n: libc::size_t,
) -> libc::c_int {
let s1 = std::ffi::CStr::from_ptr(s1)
.to_string_lossy()
.to_lowercase();
let s2 = std::ffi::CStr::from_ptr(s2)
.to_string_lossy()
.to_lowercase();
let m1 = std::cmp::min(n, s1.len());
let m2 = std::cmp::min(n, s2.len());
if s1.is_null() || s2.is_null() {
return 1;
}
if s1[..m1] == s2[..m2] {
// s1 and s2 might not be null terminated.
let s1_slice = std::slice::from_raw_parts(s1 as *const u8, n);
let s2_slice = std::slice::from_raw_parts(s2 as *const u8, n);
let s1 = std::ffi::CStr::from_bytes_with_nul_unchecked(s1_slice)
.to_string_lossy()
.to_lowercase();
let s2 = std::ffi::CStr::from_bytes_with_nul_unchecked(s2_slice)
.to_string_lossy()
.to_lowercase();
if s1 == s2 {
0
} else {
1

View File

@@ -97,7 +97,7 @@ If you want to run "liveconfig" functional tests you can set
chat devs.
- or the path of a file that contains two lines, each describing
via "addr=... mail_pwd=..." a test account login that will
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

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

@@ -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,6 +1,7 @@
""" Account class implementation. """
from __future__ import print_function
import atexit
import threading
import re
import time
@@ -49,9 +50,10 @@ class Account(object):
raise ValueError("Could not dc_open: {}".format(db_path))
self._configkeys = self.get_config("sys.config_keys").split()
self._imex_events = Queue()
atexit.register(self.shutdown)
def __del__(self):
self.shutdown()
# def __del__(self):
# self.shutdown()
def _check_config_key(self, name):
if name not in self._configkeys:
@@ -69,6 +71,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.
@@ -411,6 +425,9 @@ class Account(object):
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
#
@@ -432,7 +449,7 @@ class Account(object):
def stop_threads(self, wait=True):
""" stop IMAP/SMTP threads. """
lib.dc_stop_ongoing_process(self._dc_context)
self.stop_ongoing()
self._threads.stop(wait=wait)
def shutdown(self, wait=True):
@@ -444,6 +461,7 @@ class Account(object):
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
@@ -574,11 +592,11 @@ class EventLogger:
else:
assert not rex.match(ev[0]), "event found {}".format(ev)
def get_matching(self, event_name_regex, check_error=True):
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

View File

@@ -8,9 +8,6 @@ from os.path import join as joinpath
# this works well when you in a git-checkout
# run "python deltachat/const.py" to regenerate events
# begin const generated
DC_PROVIDER_STATUS_OK = 1
DC_PROVIDER_STATUS_PREPARATION = 2
DC_PROVIDER_STATUS_BROKEN = 3
DC_GCL_ARCHIVED_ONLY = 0x01
DC_GCL_NO_SPECIALS = 0x02
DC_GCL_ADD_ALLDONE_HINT = 0x04
@@ -55,6 +52,7 @@ 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
@@ -63,6 +61,10 @@ 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_NEW_BLOB_FILE = 150
DC_EVENT_DELETED_BLOB_FILE = 151
DC_EVENT_WARNING = 300
DC_EVENT_ERROR = 400
DC_EVENT_ERROR_NETWORK = 401
@@ -83,11 +85,61 @@ DC_EVENT_SECUREJOIN_JOINER_PROGRESS = 2061
DC_EVENT_GET_STRING = 2091
DC_EVENT_FILE_COPIED = 2055
DC_EVENT_IS_OFFLINE = 2081
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_COUNT = 67
# end const generated
def read_event_defines(f):
rex = re.compile(r'#define\s+((?:DC_EVENT_|DC_QR|DC_MSG|DC_STATE_|'
rex = re.compile(r'#define\s+((?:DC_EVENT_|DC_QR|DC_MSG|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)

View File

@@ -157,6 +157,11 @@ def acfactory(pytestconfig, tmpdir, request, session_liveconfig):
self.live_count += 1
if "e2ee_enabled" not in configdict:
configdict["e2ee_enabled"] = "1"
# Enable strict certificate checks for online accounts
configdict["imap_certificate_checks"] = "1"
configdict["smtp_certificate_checks"] = "1"
tmpdb = tmpdir.join("livedb%d" % self.live_count)
ac = self.make_account(tmpdb.strpath, logid="ac{}".format(self.live_count))
ac._evlogger.init_time = self.init_time

View File

@@ -1,6 +1,7 @@
from __future__ import print_function
import pytest
import os
import queue
from deltachat import const, Account
from deltachat.message import Message
from datetime import datetime, timedelta
@@ -19,6 +20,7 @@ class TestOfflineAccountBasic:
d = ac1.get_info()
assert d["arch"]
assert d["number_of_chats"] == "0"
assert d["bcc_self"] == "1"
def test_is_not_configured(self, acfactory):
ac1 = acfactory.get_unconfigured_account()
@@ -37,6 +39,11 @@ class TestOfflineAccountBasic:
ac1 = acfactory.get_unconfigured_account()
assert "save_mime_headers" in ac1.get_config("sys.config_keys").split()
def test_has_bccself(self, acfactory):
ac1 = acfactory.get_unconfigured_account()
assert "bcc_self" in ac1.get_config("sys.config_keys").split()
assert ac1.get_config("bcc_self") == "1"
def test_selfcontact_if_unconfigured(self, acfactory):
ac1 = acfactory.get_unconfigured_account()
with pytest.raises(ValueError):
@@ -93,7 +100,7 @@ class TestOfflineContact:
ac1 = acfactory.get_configured_offline_account()
contact1 = ac1.create_contact(email="some1@example.com", name="some1")
chat = ac1.create_chat_by_contact(contact1)
msg = chat.send_text("one messae")
msg = chat.send_text("one message")
assert not ac1.delete_contact(contact1)
assert not msg.filemime
@@ -141,6 +148,27 @@ class TestOfflineChat:
chat.set_name("title2")
assert chat.get_name() == "title2"
def test_group_chat_creation_with_translation(self, ac1):
ac1.set_stock_translation(const.DC_STR_NEWGROUPDRAFT, "xyz %1$s")
ac1._evlogger.consume_events()
with pytest.raises(ValueError):
ac1.set_stock_translation(const.DC_STR_NEWGROUPDRAFT, "xyz %2$s")
ac1._evlogger.get_matching("DC_EVENT_WARNING")
with pytest.raises(ValueError):
ac1.set_stock_translation(500, "xyz %1$s")
ac1._evlogger.get_matching("DC_EVENT_WARNING")
contact1 = ac1.create_contact("some1@hello.com", name="some1")
contact2 = ac1.create_contact("some2@hello.com", name="some2")
chat = ac1.create_group_chat(name="title1")
chat.add_contact(contact1)
chat.add_contact(contact2)
assert chat.get_name() == "title1"
assert contact1 in chat.get_contacts()
assert contact2 in chat.get_contacts()
assert not chat.is_promoted()
msg = chat.get_draft()
assert msg.text == "xyz title1"
@pytest.mark.parametrize("verified", [True, False])
def test_group_chat_qr(self, acfactory, ac1, verified):
ac2 = acfactory.get_configured_offline_account()
@@ -222,7 +250,9 @@ class TestOfflineChat:
chat1.send_image(path="notexists")
fn = data.get_path("d.png")
lp.sec("sending image")
chat1.account._evlogger.consume_events()
msg = chat1.send_image(fn)
chat1.account._evlogger.get_matching("DC_EVENT_NEW_BLOB_FILE")
assert msg.is_image()
assert msg
assert msg.id > 0
@@ -341,6 +371,12 @@ class TestOnlineAccount:
assert chat.id > const.DC_CHAT_ID_LAST_SPECIAL
return chat
def test_configure_canceled(self, acfactory):
ac1 = acfactory.get_online_configuring_account()
wait_configuration_progress(ac1, 200)
ac1.stop_ongoing()
wait_configuration_progress(ac1, 0, 0)
def test_export_import_self_keys(self, acfactory, tmpdir):
ac1, ac2 = acfactory.get_two_online_accounts()
dir = tmpdir.mkdir("exportdir")
@@ -351,18 +387,34 @@ class TestOnlineAccount:
ac1._evlogger.consume_events()
ac1.import_self_keys(dir.strpath)
def test_one_account_send(self, acfactory):
def test_one_account_send_bcc_setting(self, acfactory, lp):
ac1 = acfactory.get_online_configuring_account()
c2 = ac1.create_contact(email=ac1.get_config("addr"))
c2 = ac1.create_contact(email="notexists@testrun.org")
chat = ac1.create_chat_by_contact(c2)
assert chat.id > const.DC_CHAT_ID_LAST_SPECIAL
wait_successful_IMAP_SMTP_connection(ac1)
wait_configuration_progress(ac1, 1000)
lp.sec("send out message with bcc to ourselves")
msg_out = chat.send_text("message2")
# wait for own account to receive
ev = ac1._evlogger.get_matching("DC_EVENT_INCOMING_MSG|DC_EVENT_MSGS_CHANGED")
assert ev[1] == msg_out.id
ev = ac1._evlogger.get_matching("DC_EVENT_MSGS_CHANGED")
assert ev[2] == msg_out.id
# wait for send out (BCC)
assert ac1.get_config("bcc_self") == "1"
self_addr = ac1.get_config("addr")
ev = ac1._evlogger.get_matching("DC_EVENT_SMTP_MESSAGE_SENT")
assert self_addr in ev[2]
ev = ac1._evlogger.get_matching("DC_EVENT_DELETED_BLOB_FILE")
ac1._evlogger.consume_events()
lp.sec("send out message without bcc")
ac1.set_config("bcc_self", "0")
msg_out = chat.send_text("message3")
ev = ac1._evlogger.get_matching("DC_EVENT_MSGS_CHANGED")
assert ev[2] == msg_out.id
ev = ac1._evlogger.get_matching("DC_EVENT_SMTP_MESSAGE_SENT")
assert self_addr not in ev[2]
ev = ac1._evlogger.get_matching("DC_EVENT_DELETED_BLOB_FILE")
def test_mvbox_sentbox_threads(self, acfactory):
ac1 = acfactory.get_online_configuring_account(mvbox=True, sentbox=True)
@@ -374,6 +426,17 @@ class TestOnlineAccount:
ev = ac2._evlogger.get_matching("DC_EVENT_INCOMING_MSG|DC_EVENT_MSGS_CHANGED")
assert ev[2] > const.DC_CHAT_ID_LAST_SPECIAL
def test_move_works(self, acfactory):
ac1 = acfactory.get_online_configuring_account()
ac2 = acfactory.get_online_configuring_account(mvbox=True)
wait_configuration_progress(ac2, 1000)
wait_configuration_progress(ac1, 1000)
chat = self.get_chat(ac1, ac2)
chat.send_text("message1")
ev = ac2._evlogger.get_matching("DC_EVENT_INCOMING_MSG|DC_EVENT_MSGS_CHANGED")
assert ev[2] > const.DC_CHAT_ID_LAST_SPECIAL
ev = ac2._evlogger.get_matching("DC_EVENT_IMAP_MESSAGE_MOVED")
def test_forward_messages(self, acfactory):
ac1, ac2 = acfactory.get_two_online_accounts()
chat = self.get_chat(ac1, ac2)
@@ -444,6 +507,14 @@ class TestOnlineAccount:
lp.step("2")
assert msg_out.is_out_mdn_received()
lp.sec("check that a second call to mark_seen does not create change or smtp job")
ac2._evlogger.consume_events()
ac2.mark_seen_messages([msg_in])
try:
ac2._evlogger.get_matching("DC_EVENT_MSG_READ", timeout=0.01)
except queue.Empty:
pass # mark_seen_messages() has generated events before it returns
def test_send_and_receive_will_encrypt_decrypt(self, acfactory, lp):
ac1, ac2 = acfactory.get_two_online_accounts()
@@ -472,6 +543,14 @@ class TestOnlineAccount:
assert msg_back.text == "message-back"
assert msg_back.is_encrypted()
lp.sec("create group chat with two members, one of which has no encrypt state")
chat = ac1.create_group_chat("encryption test")
chat.add_contact(ac1.create_contact(ac2.get_config("addr")))
chat.add_contact(ac1.create_contact("notexisting@testrun.org"))
msg = chat.send_text("test not encrypt")
ev = ac1._evlogger.get_matching("DC_EVENT_SMTP_MESSAGE_SENT")
assert not msg.is_encrypted()
def test_saved_mime_on_received_message(self, acfactory, lp):
ac1, ac2 = acfactory.get_two_online_accounts()
@@ -576,6 +655,9 @@ class TestOnlineAccount:
lp.sec("ac2: start QR-code based join-group protocol")
ch = ac2.qr_join_chat(qr)
assert ch.id >= 10
# check that at least some of the handshake messages are deleted
ac1._evlogger.get_matching("DC_EVENT_IMAP_MESSAGE_DELETED")
ac2._evlogger.get_matching("DC_EVENT_IMAP_MESSAGE_DELETED")
wait_securejoin_inviter_progress(ac1, 1000)
def test_qr_verified_group_and_chatting(self, acfactory, lp):

View File

@@ -79,27 +79,21 @@ impl Aheader {
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() }
&& unsafe { CStr::from_ptr((*optional_field).fld_name).to_string_lossy() }
== "Autocrypt"
{
let value = unsafe {
CStr::from_ptr((*optional_field).fld_value)
.to_str()
.unwrap()
};
let value =
unsafe { CStr::from_ptr((*optional_field).fld_value).to_string_lossy() };
match Self::from_str(value) {
Ok(test) => {
if addr_cmp(&test.addr, wanted_from) {
if fine_header.is_none() {
fine_header = Some(test);
} else {
// TODO: figure out what kind of error case this is
return None;
}
if let Ok(test) = Self::from_str(&value) {
if addr_cmp(&test.addr, wanted_from) {
if fine_header.is_none() {
fine_header = Some(test);
} else {
// TODO: figure out what kind of error case this is
return None;
}
}
_ => {}
}
}
}
@@ -131,9 +125,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;
}
@@ -178,7 +172,7 @@ impl str::FromStr for Aheader {
// 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(());
}

View File

@@ -152,21 +152,23 @@ impl Chat {
return context.stock_str(StockMessage::DeadDrop).into();
}
let cnt = get_chat_contact_cnt(context, self.id);
return context
.stock_string_repl_int(StockMessage::Member, cnt as i32)
.into();
return context.stock_string_repl_int(StockMessage::Member, cnt as i32);
}
return "Err".into();
"Err".to_string()
}
pub fn get_parent_mime_headers(&self, context: &Context) -> Option<(String, String, String)> {
let collect = |row: &rusqlite::Row| Ok((row.get(0)?, row.get(1)?, row.get(2)?));
let params = params![self.id as i32, DC_CONTACT_ID_SELF as i32];
let sql = &context.sql;
// use the last messsage of another user in the group as the parent
let main_query = "SELECT rfc724_mid, mime_in_reply_to, mime_references \
FROM msgs WHERE chat_id=?1 AND timestamp=(SELECT max(timestamp) \
FROM msgs WHERE chat_id=?1 AND from_id!=?2);";
// there are no messages of other users - use the first message if SELF as parent
let fallback_query = "SELECT rfc724_mid, mime_in_reply_to, mime_references \
FROM msgs WHERE chat_id=?1 AND timestamp=(SELECT min(timestamp) \
FROM msgs WHERE chat_id=?1 AND from_id==?2);";
@@ -289,7 +291,7 @@ impl Chat {
if self.typ == Chattype::Group || self.typ == Chattype::VerifiedGroup {
if self.param.get_int(Param::Unpromoted).unwrap_or_default() == 1 {
self.param.remove(Param::Unpromoted);
self.update_param(context).unwrap();
self.update_param(context)?;
}
}
}
@@ -299,16 +301,15 @@ impl Chat {
so that E2EE is no longer available at a later point (reset, changed settings),
we do not send the message out at all */
do_guarantee_e2ee = false;
e2ee_enabled = context
.sql
.get_config_int(context, "e2ee_enabled")
.unwrap_or_else(|| 1)
== 1;
e2ee_enabled = context.get_config_bool(Config::E2eeEnabled);
if e2ee_enabled && msg.param.get_int(Param::ForcePlaintext).unwrap_or_default() == 0 {
let mut can_encrypt = 1;
let mut all_mutual = 1;
let mut can_encrypt = true;
let mut all_mutual = true;
let res = context.sql.query_row(
// take care that this statement returns NULL rows
// if there is no peerstates for a chat member!
// for DC_PARAM_SELFTALK this statement does not return any row
let res = context.sql.query_map(
"SELECT ps.prefer_encrypted, c.addr \
FROM chats_contacts cc \
LEFT JOIN contacts c ON cc.contact_id=c.id \
@@ -316,29 +317,32 @@ impl Chat {
WHERE cc.chat_id=? AND cc.contact_id>9;",
params![self.id],
|row| {
let state: String = row.get(1)?;
let addr: String = row.get(1)?;
if let Some(prefer_encrypted) = row.get::<_, Option<i32>>(0)? {
// the peerstate exist, so we have either public_key or gossip_key
// and can encrypt potentially
if prefer_encrypted != 1 {
info!(
context,
"[autocrypt] peerstate for {} is {}",
state,
addr,
if prefer_encrypted == 0 {
"NOPREFERENCE"
} else {
"RESET"
},
);
all_mutual = 0;
all_mutual = false;
}
} else {
info!(context, "[autocrypt] no peerstate for {}", state,);
can_encrypt = 0;
all_mutual = 0;
info!(context, "[autocrypt] no peerstate for {}", addr,);
can_encrypt = false;
all_mutual = false;
}
Ok(())
},
|rows| rows.collect::<Result<Vec<_>, _>>().map_err(Into::into),
);
match res {
Ok(_) => {}
@@ -347,8 +351,8 @@ impl Chat {
}
}
if 0 != can_encrypt {
if 0 != all_mutual {
if can_encrypt {
if all_mutual {
do_guarantee_e2ee = true;
} else if last_msg_in_chat_encrypted(context, &context.sql, self.id) {
do_guarantee_e2ee = true;
@@ -358,7 +362,15 @@ impl Chat {
if do_guarantee_e2ee {
msg.param.set_int(Param::GuranteeE2ee, 1);
}
// reset eg. for forwarding
msg.param.remove(Param::ErroneousE2ee);
// set "In-Reply-To:" to identify the message to which the composed message is a reply;
// set "References:" to identify the "thread" of the conversation;
// both according to RFC 5322 3.6.4, page 25
//
// as self-talks are mainly used to transfer data between devices,
// we do not set In-Reply-To/References in this case.
if !self.is_self_talk() {
if let Some((parent_rfc724_mid, parent_in_reply_to, parent_references)) =
self.get_parent_mime_headers(context)
@@ -366,6 +378,9 @@ impl Chat {
if !parent_rfc724_mid.is_empty() {
new_in_reply_to = parent_rfc724_mid.clone();
}
// the whole list of messages referenced may be huge;
// only use the oldest and and the parent message
let parent_references = if let Some(n) = parent_references.find(' ') {
&parent_references[0..n]
} else {
@@ -373,6 +388,7 @@ impl Chat {
};
if !parent_references.is_empty() && !parent_rfc724_mid.is_empty() {
// angle brackets are added by the mimefactory later
new_references = format!("{} {}", parent_references, parent_rfc724_mid);
} else if !parent_references.is_empty() {
new_references = parent_references.to_string();
@@ -646,6 +662,7 @@ pub fn msgtype_has_file(msgtype: Viewtype) -> bool {
match msgtype {
Viewtype::Image => true,
Viewtype::Gif => true,
Viewtype::Sticker => true,
Viewtype::Audio => true,
Viewtype::Voice => true,
Viewtype::Video => true,
@@ -783,10 +800,7 @@ pub fn send_msg(context: &Context, chat_id: u32, msg: &mut Message) -> Result<u3
message::update_msg_state(context, msg.id, MessageState::OutPending);
}
ensure!(
job_send_msg(context, msg.id) != 0,
"Failed to initiate send job"
);
job_send_msg(context, msg.id)?;
context.call_cb(Event::MsgsChanged {
chat_id: msg.chat_id,
@@ -805,11 +819,8 @@ pub fn send_msg(context: &Context, chat_id: u32, msg: &mut Message) -> Result<u3
if 0 == id {
// avoid hanging if user tampers with db
break;
} else {
if let Ok(mut copy) = Message::load_from_db(context, id as u32) {
// TODO: handle cleanup and return early instead
send_msg(context, 0, &mut copy).unwrap();
}
} else if let Ok(mut copy) = Message::load_from_db(context, id as u32) {
send_msg(context, 0, &mut copy)?;
}
}
msg.param.remove(Param::PrepForwards);
@@ -962,10 +973,7 @@ pub fn get_chat_msgs(context: &Context, chat_id: u32, flags: u32, marker1before:
};
let success = if chat_id == 1 {
let show_emails = context
.sql
.get_config_int(context, "show_emails")
.unwrap_or_default();
let show_emails = context.get_config_int(Config::ShowEmails);
context.sql.query_map(
"SELECT m.id, m.timestamp FROM msgs m \
LEFT JOIN chats ON m.chat_id=chats.id \
@@ -1381,11 +1389,10 @@ pub(crate) fn add_contact_to_chat_ex(
}
if from_handshake && chat.param.get_int(Param::Unpromoted).unwrap_or_default() == 1 {
chat.param.remove(Param::Unpromoted);
chat.update_param(context).unwrap();
chat.update_param(context)?;
}
let self_addr = context
.sql
.get_config(context, "configured_addr")
.get_config(Config::ConfiguredAddr)
.unwrap_or_default();
if contact.get_addr() == &self_addr {
bail!("invalid attempt to add self e-mail address to group");
@@ -1399,14 +1406,14 @@ pub(crate) fn add_contact_to_chat_ex(
}
} else {
// else continue and send status mail
if chat.typ == Chattype::VerifiedGroup {
if contact.is_verified(context) != VerifiedStatus::BidirectVerified {
error!(
context,
"Only bidirectional verified contacts can be added to verified groups."
);
return Ok(false);
}
if chat.typ == Chattype::VerifiedGroup
&& contact.is_verified(context) != VerifiedStatus::BidirectVerified
{
error!(
context,
"Only bidirectional verified contacts can be added to verified groups."
);
return Ok(false);
}
if !add_to_chat_contacts_table(context, chat_id, contact_id) {
return Ok(false);
@@ -1423,14 +1430,14 @@ pub(crate) fn add_contact_to_chat_ex(
msg.param.set_int(Param::Cmd, 4);
msg.param.set(Param::Arg, contact.get_addr());
msg.param.set_int(Param::Arg2, from_handshake.into());
msg.id = send_msg(context, chat_id, &mut msg).unwrap_or_default();
msg.id = send_msg(context, chat_id, &mut msg)?;
context.call_cb(Event::MsgsChanged {
chat_id,
msg_id: msg.id,
});
}
context.call_cb(Event::MsgsChanged { chat_id, msg_id: 0 });
return Ok(true);
Ok(true)
}
fn real_group_exists(context: &Context, chat_id: u32) -> bool {
@@ -1517,7 +1524,7 @@ pub fn remove_contact_from_chat(
if chat.is_promoted() {
msg.type_0 = Viewtype::Text;
if contact.id == DC_CONTACT_ID_SELF {
set_group_explicitly_left(context, chat.grpid).unwrap();
set_group_explicitly_left(context, chat.grpid)?;
msg.text = Some(context.stock_system_msg(
StockMessage::MsgGroupLeft,
"",
@@ -1534,7 +1541,7 @@ pub fn remove_contact_from_chat(
}
msg.param.set_int(Param::Cmd, 5);
msg.param.set(Param::Arg, contact.get_addr());
msg.id = send_msg(context, chat_id, &mut msg).unwrap_or_default();
msg.id = send_msg(context, chat_id, &mut msg)?;
context.call_cb(Event::MsgsChanged {
chat_id,
msg_id: msg.id,
@@ -1598,7 +1605,7 @@ pub fn set_chat_name(
let mut msg = Message::default();
if real_group_exists(context, chat_id) {
if &chat.name == new_name.as_ref() {
if chat.name == new_name.as_ref() {
success = true;
} else if !is_contact_in_chat(context, chat_id, 1) {
emit_event!(
@@ -1631,7 +1638,7 @@ pub fn set_chat_name(
if !chat.name.is_empty() {
msg.param.set(Param::Arg, &chat.name);
}
msg.id = send_msg(context, chat_id, &mut msg).unwrap_or_default();
msg.id = send_msg(context, chat_id, &mut msg)?;
context.call_cb(Event::MsgsChanged {
chat_id,
msg_id: msg.id,
@@ -1700,7 +1707,7 @@ pub fn set_chat_profile_image(
"",
DC_CONTACT_ID_SELF,
));
msg.id = send_msg(context, chat_id, &mut msg).unwrap_or_default();
msg.id = send_msg(context, chat_id, &mut msg)?;
emit_event!(
context,
Event::MsgsChanged {
@@ -1716,36 +1723,35 @@ pub fn set_chat_profile_image(
bail!("Failed to set profile image");
}
pub fn forward_msgs(context: &Context, msg_ids: &[u32], chat_id: u32) {
if msg_ids.is_empty() || chat_id <= DC_CHAT_ID_LAST_SPECIAL {
return;
}
pub fn forward_msgs(context: &Context, msg_ids: &[u32], chat_id: u32) -> Result<(), Error> {
ensure!(!msg_ids.is_empty(), "empty msgs_ids: no one to forward to");
ensure!(
chat_id > DC_CHAT_ID_LAST_SPECIAL,
"can not forward to special chat"
);
let mut created_db_entries = Vec::new();
let mut curr_timestamp: i64;
unarchive(context, chat_id).unwrap();
unarchive(context, chat_id)?;
if let Ok(mut chat) = Chat::load_from_db(context, chat_id) {
curr_timestamp = dc_create_smeared_timestamps(context, msg_ids.len());
let idsstr = msg_ids
.into_iter()
.iter()
.enumerate()
.fold(String::with_capacity(2 * msg_ids.len()), |acc, (i, n)| {
(if i == 0 { acc } else { acc + "," }) + &n.to_string()
});
let ids = context
.sql
.query_map(
format!(
"SELECT id FROM msgs WHERE id IN({}) ORDER BY timestamp,id",
idsstr
),
params![],
|row| row.get::<_, i32>(0),
|ids| ids.collect::<Result<Vec<_>, _>>().map_err(Into::into),
)
.unwrap(); // TODO: better error handling
let ids = context.sql.query_map(
format!(
"SELECT id FROM msgs WHERE id IN({}) ORDER BY timestamp,id",
idsstr
),
params![],
|row| row.get::<_, i32>(0),
|ids| ids.collect::<Result<Vec<_>, _>>().map_err(Into::into),
)?;
for id in ids {
let src_msg_id = id;
@@ -1765,7 +1771,7 @@ pub fn forward_msgs(context: &Context, msg_ids: &[u32], chat_id: u32) {
let new_msg_id: u32;
if msg.state == MessageState::OutPreparing {
let fresh9 = curr_timestamp;
curr_timestamp = curr_timestamp + 1;
curr_timestamp += 1;
new_msg_id = chat
.prepare_msg_raw(context, &mut msg, fresh9)
.unwrap_or_default();
@@ -1785,11 +1791,11 @@ pub fn forward_msgs(context: &Context, msg_ids: &[u32], chat_id: u32) {
} else {
msg.state = MessageState::OutPending;
let fresh10 = curr_timestamp;
curr_timestamp = curr_timestamp + 1;
curr_timestamp += 1;
new_msg_id = chat
.prepare_msg_raw(context, &mut msg, fresh10)
.unwrap_or_default();
job_send_msg(context, new_msg_id);
job_send_msg(context, new_msg_id)?;
}
created_db_entries.push(chat_id);
created_db_entries.push(new_msg_id);
@@ -1802,6 +1808,8 @@ pub fn forward_msgs(context: &Context, msg_ids: &[u32], chat_id: u32) {
msg_id: created_db_entries[i + 1],
});
}
Ok(())
}
pub fn get_chat_contact_cnt(context: &Context, chat_id: u32) -> usize {

View File

@@ -182,7 +182,7 @@ impl Chatlist {
if 0 == listflags & DC_GCL_NO_SPECIALS {
let last_deaddrop_fresh_msg_id = get_last_deaddrop_fresh_msg(context);
if last_deaddrop_fresh_msg_id > 0 {
ids.push((1, last_deaddrop_fresh_msg_id));
ids.insert(0, (DC_CHAT_ID_DEADDROP, last_deaddrop_fresh_msg_id));
}
add_archived_link_item = 1;
}
@@ -258,13 +258,11 @@ impl Chatlist {
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 {
if let Ok(chat) = Chat::load_from_db(context, self.ids[index].0) {
chat_loaded = chat;
&chat_loaded
} else {
return ret;
}
return ret;
};
let lastmsg_id = self.ids[index].1;

View File

@@ -19,10 +19,12 @@ pub enum Config {
MailUser,
MailPw,
MailPort,
ImapCertificateChecks,
SendServer,
SendUser,
SendPw,
SendPort,
SmtpCertificateChecks,
ServerFlags,
#[strum(props(default = "INBOX"))]
ImapFolder,
@@ -30,6 +32,8 @@ pub enum Config {
Selfstatus,
Selfavatar,
#[strum(props(default = "1"))]
BccSelf,
#[strum(props(default = "1"))]
E2eeEnabled,
#[strum(props(default = "1"))]
MdnsEnabled,
@@ -50,10 +54,12 @@ pub enum Config {
ConfiguredMailPw,
ConfiguredMailPort,
ConfiguredMailSecurity,
ConfiguredImapCertificateChecks,
ConfiguredSendServer,
ConfiguredSendUser,
ConfiguredSendPw,
ConfiguredSendPort,
ConfiguredSmtpCertificateChecks,
ConfiguredServerFlags,
ConfiguredSendSecurity,
ConfiguredE2EEEnabled,
@@ -72,13 +78,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| dc_get_abs_path(self, &p).to_str().unwrap().to_string())
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((&*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() {
@@ -92,6 +98,16 @@ impl Context {
}
}
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 default if there is one.
pub fn set_config(&self, key: Config, value: Option<&str>) -> Result<(), Error> {
@@ -99,20 +115,20 @@ impl Context {
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()))
.set_raw_config(self, key, Some(&rel_path.to_string_lossy()))
}
Config::InboxWatch => {
let ret = self.sql.set_config(self, key, value);
let ret = self.sql.set_raw_config(self, key, value);
interrupt_imap_idle(self);
ret
}
Config::SentboxWatch => {
let ret = self.sql.set_config(self, key, value);
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);
let ret = self.sql.set_raw_config(self, key, value);
interrupt_mvbox_idle(self);
ret
}
@@ -124,9 +140,9 @@ impl Context {
value
};
self.sql.set_config(self, key, val)
self.sql.set_raw_config(self, key, val)
}
_ => self.sql.set_config(self, key, value),
_ => self.sql.set_raw_config(self, key, value),
}
}
}

View File

@@ -1,10 +1,8 @@
use libc::free;
use quick_xml;
use quick_xml::events::{BytesEnd, BytesStart, BytesText};
use crate::constants::*;
use crate::context::Context;
use crate::dc_tools::*;
use crate::login_param::LoginParam;
use super::read_autoconf_file;
@@ -12,51 +10,57 @@ use super::read_autoconf_file;
* Thunderbird's Autoconfigure
******************************************************************************/
/* documentation: https://developer.mozilla.org/en-US/docs/Mozilla/Thunderbird/Autoconfiguration */
#[repr(C)]
struct moz_autoconfigure_t<'a> {
struct MozAutoconfigure<'a> {
pub in_0: &'a LoginParam,
pub in_emaildomain: &'a str,
pub in_emaillocalpart: &'a str,
pub out: LoginParam,
pub out_imap_set: libc::c_int,
pub out_smtp_set: libc::c_int,
pub tag_server: libc::c_int,
pub tag_config: libc::c_int,
pub out_imap_set: bool,
pub out_smtp_set: bool,
pub tag_server: MozServer,
pub tag_config: MozConfigTag,
}
pub unsafe fn moz_autoconfigure(
enum MozServer {
Undefined,
Imap,
Smtp,
}
enum MozConfigTag {
Undefined,
Hostname,
Port,
Sockettype,
Username,
}
pub fn moz_autoconfigure(
context: &Context,
url: &str,
param_in: &LoginParam,
) -> Option<LoginParam> {
let xml_raw = read_autoconf_file(context, url);
if xml_raw.is_null() {
return None;
}
let xml_raw = read_autoconf_file(context, url)?;
// Split address into local part and domain part.
let p = param_in.addr.find("@");
if p.is_none() {
free(xml_raw as *mut libc::c_void);
return None;
}
let (in_emaillocalpart, in_emaildomain) = param_in.addr.split_at(p.unwrap());
let p = param_in.addr.find('@')?;
let (in_emaillocalpart, in_emaildomain) = param_in.addr.split_at(p);
let in_emaildomain = &in_emaildomain[1..];
let mut reader = quick_xml::Reader::from_str(as_str(xml_raw));
let mut reader = quick_xml::Reader::from_str(&xml_raw);
reader.trim_text(true);
let mut buf = Vec::new();
let mut moz_ac = moz_autoconfigure_t {
let mut moz_ac = MozAutoconfigure {
in_0: param_in,
in_emaildomain,
in_emaillocalpart,
out: LoginParam::new(),
out_imap_set: 0,
out_smtp_set: 0,
tag_server: 0,
tag_config: 0,
out_imap_set: false,
out_smtp_set: false,
tag_server: MozServer::Undefined,
tag_config: MozConfigTag::Undefined,
};
loop {
match reader.read_event(&mut buf) {
@@ -68,7 +72,7 @@ pub unsafe fn moz_autoconfigure(
moz_autoconfigure_text_cb(e, &mut moz_ac, &reader)
}
Err(e) => {
error!(
warn!(
context,
"Configure xml: Error at position {}: {:?}",
reader.buffer_position(),
@@ -88,17 +92,15 @@ pub unsafe fn moz_autoconfigure(
{
let r = moz_ac.out.to_string();
warn!(context, "Bad or incomplete autoconfig: {}", r,);
free(xml_raw as *mut libc::c_void);
return None;
}
free(xml_raw as *mut libc::c_void);
Some(moz_ac.out)
}
fn moz_autoconfigure_text_cb<B: std::io::BufRead>(
event: &BytesText,
moz_ac: &mut moz_autoconfigure_t,
moz_ac: &mut MozAutoconfigure,
reader: &quick_xml::Reader<B>,
) {
let val = event.unescape_and_decode(reader).unwrap_or_default();
@@ -113,12 +115,12 @@ fn moz_autoconfigure_text_cb<B: std::io::BufRead>(
.replace("%EMAILLOCALPART%", email_local)
.replace("%EMAILDOMAIN%", email_domain);
if moz_ac.tag_server == 1 {
match moz_ac.tag_config {
10 => moz_ac.out.mail_server = val,
11 => moz_ac.out.mail_port = val.parse().unwrap_or_default(),
12 => moz_ac.out.mail_user = val,
13 => {
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
@@ -131,13 +133,12 @@ fn moz_autoconfigure_text_cb<B: std::io::BufRead>(
}
}
_ => {}
}
} else if moz_ac.tag_server == 2 {
match moz_ac.tag_config {
10 => moz_ac.out.send_server = val,
11 => moz_ac.out.send_port = val.parse().unwrap_or_default(),
12 => moz_ac.out.send_user = val,
13 => {
},
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
@@ -150,29 +151,30 @@ fn moz_autoconfigure_text_cb<B: std::io::BufRead>(
}
}
_ => {}
}
},
MozServer::Undefined => {}
}
}
fn moz_autoconfigure_endtag_cb(event: &BytesEnd, moz_ac: &mut moz_autoconfigure_t) {
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" {
moz_ac.tag_server = 0;
moz_ac.tag_config = 0;
moz_ac.out_imap_set = 1;
moz_ac.tag_server = MozServer::Undefined;
moz_ac.tag_config = MozConfigTag::Undefined;
moz_ac.out_imap_set = true;
} else if tag == "outgoingserver" {
moz_ac.tag_server = 0;
moz_ac.tag_config = 0;
moz_ac.out_smtp_set = 1;
moz_ac.tag_server = MozServer::Undefined;
moz_ac.tag_config = MozConfigTag::Undefined;
moz_ac.out_smtp_set = true;
} else {
moz_ac.tag_config = 0;
moz_ac.tag_config = MozConfigTag::Undefined;
}
}
fn moz_autoconfigure_starttag_cb<B: std::io::BufRead>(
event: &BytesStart,
moz_ac: &mut moz_autoconfigure_t,
moz_ac: &mut MozAutoconfigure,
reader: &quick_xml::Reader<B>,
) {
let tag = String::from_utf8_lossy(event.name()).trim().to_lowercase();
@@ -189,25 +191,29 @@ fn moz_autoconfigure_starttag_cb<B: std::io::BufRead>(
.unwrap_or_default()
.to_lowercase();
if typ == "imap" && moz_ac.out_imap_set == 0 {
1
if typ == "imap" && !moz_ac.out_imap_set {
MozServer::Imap
} else {
0
MozServer::Undefined
}
} else {
0
MozServer::Undefined
};
moz_ac.tag_config = 0;
moz_ac.tag_config = MozConfigTag::Undefined;
} else if tag == "outgoingserver" {
moz_ac.tag_server = if moz_ac.out_smtp_set == 0 { 2 } else { 0 };
moz_ac.tag_config = 0;
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 = 10;
moz_ac.tag_config = MozConfigTag::Hostname;
} else if tag == "port" {
moz_ac.tag_config = 11;
moz_ac.tag_config = MozConfigTag::Port;
} else if tag == "sockettype" {
moz_ac.tag_config = 13;
moz_ac.tag_config = MozConfigTag::Sockettype;
} else if tag == "username" {
moz_ac.tag_config = 12;
moz_ac.tag_config = MozConfigTag::Username;
}
}

View File

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

File diff suppressed because it is too large Load Diff

View File

@@ -25,7 +25,6 @@ impl Default for MoveState {
// some defaults
const DC_E2EE_DEFAULT_ENABLED: i32 = 1;
pub const DC_MDNS_DEFAULT_ENABLED: i32 = 1;
const DC_INBOX_WATCH_DEFAULT: i32 = 1;
const DC_SENTBOX_WATCH_DEFAULT: i32 = 1;
const DC_MVBOX_WATCH_DEFAULT: i32 = 1;
@@ -130,23 +129,23 @@ pub const DC_CREATE_MVBOX: usize = 1;
/// Force OAuth2 authorization. This flag does not skip automatic configuration.
/// 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.
@@ -161,9 +160,9 @@ pub const DC_LP_SMTP_SOCKET_SSL: usize = 0x20000;
pub const DC_LP_SMTP_SOCKET_PLAIN: usize = 0x40000;
/// if none of these flags are set, the default is chosen
pub const DC_LP_AUTH_FLAGS: usize = (DC_LP_AUTH_OAUTH2 | DC_LP_AUTH_NORMAL);
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: usize =
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 chosen
pub const DC_LP_SMTP_SOCKET_FLAGS: usize =
@@ -195,6 +194,11 @@ pub enum Viewtype {
/// 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().
@@ -253,7 +257,7 @@ const DC_SHOW_EMAILS_ACCEPTED_CONTACTS: usize = 1;
const DC_SHOW_EMAILS_ALL: usize = 2;
// TODO: Strings need some doumentation about used placeholders.
// These constants are used to request strings using #DC_EVENT_GET_STRING.
// These constants are used to set stock translation strings
const DC_STR_NOMESSAGES: usize = 1;
const DC_STR_SELF: usize = 2;
@@ -299,7 +303,8 @@ const DC_STR_MSGACTIONBYME: usize = 63;
const DC_STR_MSGLOCATIONENABLED: usize = 64;
const DC_STR_MSGLOCATIONDISABLED: usize = 65;
const DC_STR_LOCATION: usize = 66;
const DC_STR_COUNT: usize = 66;
const DC_STR_STICKER: usize = 67;
const DC_STR_COUNT: usize = 67;
pub const DC_JOB_DELETE_MSG_ON_IMAP: i32 = 110;

View File

@@ -390,20 +390,18 @@ impl Contact {
}
sth_modified = Modifier::Modified;
}
} else if sql::execute(
context,
&context.sql,
"INSERT INTO contacts (name, addr, origin) VALUES(?, ?, ?);",
params![name.as_ref(), addr, origin,],
)
.is_ok()
{
row_id = sql::get_rowid(context, &context.sql, "contacts", "addr", addr);
sth_modified = Modifier::Created;
} else {
if sql::execute(
context,
&context.sql,
"INSERT INTO contacts (name, addr, origin) VALUES(?, ?, ?);",
params![name.as_ref(), addr, origin,],
)
.is_ok()
{
row_id = sql::get_rowid(context, &context.sql, "contacts", "addr", addr);
sth_modified = Modifier::Created;
} else {
error!(context, "Cannot add contact.");
}
error!(context, "Cannot add contact.");
}
Ok((row_id, sth_modified))
@@ -827,7 +825,7 @@ impl Contact {
if let Ok(contact) = Contact::load_from_db(context, contact_id) {
if !contact.addr.is_empty() {
let normalized_addr = addr_normalize(addr.as_ref());
if &contact.addr == &normalized_addr {
if contact.addr == normalized_addr {
return true;
}
}
@@ -963,9 +961,9 @@ pub fn normalize_name(full_name: impl AsRef<str>) -> String {
if len > 0 {
let firstchar = full_name.as_bytes()[0];
let lastchar = full_name.as_bytes()[len - 1];
if firstchar == '\'' as u8 && lastchar == '\'' as u8
|| firstchar == '\"' as u8 && lastchar == '\"' as u8
|| firstchar == '<' as u8 && lastchar == '>' as u8
if firstchar == b'\'' && lastchar == b'\''
|| firstchar == b'\"' && lastchar == b'\"'
|| firstchar == b'<' && lastchar == b'>'
{
full_name = &full_name[1..len - 1];
}

View File

@@ -1,13 +1,17 @@
use std::collections::HashMap;
use std::ffi::OsString;
use std::fs;
use std::io::Write;
use std::path::{Path, PathBuf};
use std::sync::{Arc, Condvar, Mutex, RwLock};
use libc::uintptr_t;
use crate::chat::*;
use crate::config::Config;
use crate::constants::*;
use crate::contact::*;
use crate::dc_tools::{dc_copy_file, dc_derive_safe_stem_ext};
use crate::error::*;
use crate::events::Event;
use crate::imap::*;
@@ -20,6 +24,7 @@ use crate::message::{self, Message};
use crate::param::Params;
use crate::smtp::*;
use crate::sql::Sql;
use rand::{thread_rng, Rng};
/// Callback function type for [Context]
///
@@ -58,12 +63,13 @@ pub struct Context {
pub running_state: Arc<RwLock<RunningState>>,
/// Mutex to avoid generating the key for the user more than once.
pub generating_key_mutex: Mutex<()>,
pub translated_stockstrings: RwLock<HashMap<usize, String>>,
}
#[derive(Debug, PartialEq, Eq)]
pub struct RunningState {
pub ongoing_running: bool,
pub shall_stop_ongoing: bool,
shall_stop_ongoing: bool,
}
/// Return some info about deltachat-core
@@ -140,6 +146,7 @@ impl Context {
probe_imap_network: Arc::new(RwLock::new(false)),
perform_inbox_jobs_needed: Arc::new(RwLock::new(false)),
generating_key_mutex: Mutex::new(()),
translated_stockstrings: RwLock::new(HashMap::new()),
};
ensure!(
@@ -158,35 +165,141 @@ impl Context {
self.blobdir.as_path()
}
pub fn copy_to_blobdir(&self, orig_filename: impl AsRef<str>) -> Result<String> {
// return a $BLOBDIR/<filename> with the content of orig_filename
// copied into it. The <filename> will be safely derived from
// orig_filename, and will not clash with existing filenames.
let dest = self.new_blob_file(&orig_filename, b"")?;
if dc_copy_file(
&self,
PathBuf::from(orig_filename.as_ref()),
PathBuf::from(&dest),
) {
Ok(dest)
} else {
bail!("could not copy {} to {}", orig_filename.as_ref(), dest);
}
}
pub fn new_blob_file(&self, orig_filename: impl AsRef<str>, data: &[u8]) -> Result<String> {
// return a $BLOBDIR/<FILENAME> string which corresponds to the
// respective file in the blobdir, and which contains the data.
// FILENAME is computed by looking and possibly mangling the
// basename of orig_filename. The resulting filenames are meant
// to be human-readable.
let (stem, ext) = dc_derive_safe_stem_ext(orig_filename.as_ref());
// ext starts with "." or is empty string, so we can always resconstruct
for i in 0..3 {
let candidate_basename = match i {
// first a try to just use the (possibly mangled) original basename
0 => format!("{}{}", stem, ext),
// otherwise extend stem with random numbers
_ => {
let mut rng = thread_rng();
let random_id: u32 = rng.gen();
format!("{}-{}{}", stem, random_id, ext)
}
};
let path = self.get_blobdir().join(&candidate_basename);
if let Ok(mut file) = fs::OpenOptions::new()
.create_new(true)
.write(true)
.open(&path)
{
file.write_all(data)?;
let db_entry = format!("$BLOBDIR/{}", candidate_basename);
self.call_cb(Event::NewBlobFile(db_entry.clone()));
return Ok(db_entry);
}
}
bail!("out of luck to create new blob file");
}
pub fn call_cb(&self, event: Event) -> uintptr_t {
(*self.cb)(self, event)
}
/*******************************************************************************
* Ongoing process allocation/free/check
******************************************************************************/
pub fn alloc_ongoing(&self) -> bool {
if self.has_ongoing() {
warn!(self, "There is already another ongoing process running.",);
false
} else {
let s_a = self.running_state.clone();
let mut s = s_a.write().unwrap();
s.ongoing_running = true;
s.shall_stop_ongoing = false;
true
}
}
pub fn free_ongoing(&self) {
let s_a = self.running_state.clone();
let mut s = s_a.write().unwrap();
s.ongoing_running = false;
s.shall_stop_ongoing = true;
}
pub fn has_ongoing(&self) -> bool {
let s_a = self.running_state.clone();
let s = s_a.read().unwrap();
s.ongoing_running || !s.shall_stop_ongoing
}
/// Signal an ongoing process to stop.
pub fn stop_ongoing(&self) {
let s_a = self.running_state.clone();
let mut s = s_a.write().unwrap();
if s.ongoing_running && !s.shall_stop_ongoing {
info!(self, "Signaling the ongoing process to stop ASAP.",);
s.shall_stop_ongoing = true;
} else {
info!(self, "No ongoing process to stop.",);
};
}
pub fn shall_stop_ongoing(&self) -> bool {
self.running_state
.clone()
.read()
.unwrap()
.shall_stop_ongoing
}
/*******************************************************************************
* UI chat/message related API
******************************************************************************/
pub fn get_info(&self) -> HashMap<&'static str, String> {
let unset = "0";
let l = LoginParam::from_database(self, "");
let l2 = LoginParam::from_database(self, "configured_");
let displayname = self.sql.get_config(self, "displayname");
let displayname = self.get_config(Config::Displayname);
let chats = get_chat_cnt(self) as usize;
let real_msgs = message::get_real_msg_cnt(self) as usize;
let deaddrop_msgs = message::get_deaddrop_msg_cnt(self) as usize;
let contacts = Contact::get_real_cnt(self) as usize;
let is_configured = self
.sql
.get_config_int(self, "configured")
.unwrap_or_default();
let is_configured = self.get_config_int(Config::Configured);
let dbversion = self
.sql
.get_config_int(self, "dbversion")
.get_raw_config_int(self, "dbversion")
.unwrap_or_default();
let e2ee_enabled = self
.sql
.get_config_int(self, "e2ee_enabled")
.unwrap_or_else(|| 1);
let mdns_enabled = self
.sql
.get_config_int(self, "mdns_enabled")
.unwrap_or_else(|| 1);
let e2ee_enabled = self.get_config_int(Config::E2eeEnabled);
let mdns_enabled = self.get_config_int(Config::MdnsEnabled);
let bcc_self = self.get_config_int(Config::BccSelf);
let prv_key_cnt: Option<isize> =
self.sql
@@ -204,33 +317,22 @@ impl Context {
"<Not yet calculated>".into()
};
let inbox_watch = self
.sql
.get_config_int(self, "inbox_watch")
.unwrap_or_else(|| 1);
let sentbox_watch = self
.sql
.get_config_int(self, "sentbox_watch")
.unwrap_or_else(|| 1);
let mvbox_watch = self
.sql
.get_config_int(self, "mvbox_watch")
.unwrap_or_else(|| 1);
let mvbox_move = self
.sql
.get_config_int(self, "mvbox_move")
.unwrap_or_else(|| 1);
let inbox_watch = self.get_config_int(Config::InboxWatch);
let sentbox_watch = self.get_config_int(Config::SentboxWatch);
let mvbox_watch = self.get_config_int(Config::MvboxWatch);
let mvbox_move = self.get_config_int(Config::MvboxMove);
let folders_configured = self
.sql
.get_config_int(self, "folders_configured")
.get_raw_config_int(self, "folders_configured")
.unwrap_or_default();
let configured_sentbox_folder = self
.sql
.get_config(self, "configured_sentbox_folder")
.get_raw_config(self, "configured_sentbox_folder")
.unwrap_or_else(|| "<unset>".to_string());
let configured_mvbox_folder = self
.sql
.get_config(self, "configured_mvbox_folder")
.get_raw_config(self, "configured_mvbox_folder")
.unwrap_or_else(|| "<unset>".to_string());
let mut res = get_info();
@@ -254,6 +356,7 @@ impl Context {
res.insert("configured_mvbox_folder", configured_mvbox_folder);
res.insert("mdns_enabled", mdns_enabled.to_string());
res.insert("e2ee_enabled", e2ee_enabled.to_string());
res.insert("bcc_self", bcc_self.to_string());
res.insert(
"private_key_count",
prv_key_cnt.unwrap_or_default().to_string(),
@@ -289,7 +392,7 @@ impl Context {
Ok(ret)
},
)
.unwrap()
.unwrap_or_default()
}
#[allow(non_snake_case)]
@@ -333,7 +436,7 @@ impl Context {
}
pub fn is_sentbox(&self, folder_name: impl AsRef<str>) -> bool {
let sentbox_name = self.sql.get_config(self, "configured_sentbox_folder");
let sentbox_name = self.sql.get_raw_config(self, "configured_sentbox_folder");
if let Some(name) = sentbox_name {
name == folder_name.as_ref()
} else {
@@ -342,7 +445,7 @@ impl Context {
}
pub fn is_mvbox(&self, folder_name: impl AsRef<str>) -> bool {
let mvbox_name = self.sql.get_config(self, "configured_mvbox_folder");
let mvbox_name = self.sql.get_raw_config(self, "configured_mvbox_folder");
if let Some(name) = mvbox_name {
name == folder_name.as_ref()
@@ -352,12 +455,7 @@ impl Context {
}
pub fn do_heuristics_moves(&self, folder: &str, msg_id: u32) {
if self
.sql
.get_config_int(self, "mvbox_move")
.unwrap_or_else(|| 1)
== 0
{
if !self.get_config_bool(Config::MvboxMove) {
return;
}
@@ -438,6 +536,7 @@ pub fn get_version_str() -> &'static str {
mod tests {
use super::*;
use crate::dc_tools::*;
use crate::test_utils::*;
#[test]
@@ -475,6 +574,51 @@ mod tests {
assert!(res.is_err());
}
#[test]
fn test_new_blob_file() {
let t = dummy_context();
let context = t.ctx;
let x = &context.new_blob_file("hello", b"data").unwrap();
assert!(dc_file_exist(&context, x));
assert!(x.starts_with("$BLOBDIR"));
assert!(dc_read_file(&context, x).unwrap() == b"data");
let y = &context.new_blob_file("hello", b"data").unwrap();
assert!(dc_file_exist(&context, y));
assert!(y.starts_with("$BLOBDIR/hello-"));
let x = &context.new_blob_file("xyz/hello.png", b"data").unwrap();
assert!(dc_file_exist(&context, x));
assert_eq!(x, "$BLOBDIR/hello.png");
let y = &context.new_blob_file("hello\\world.png", b"data").unwrap();
assert!(dc_file_exist(&context, y));
assert_eq!(y, "$BLOBDIR/world.png");
}
#[test]
fn test_new_blob_file_long_names() {
let t = dummy_context();
let context = t.ctx;
let s = "12312312039182039182039812039810293810293810293810293801293801293123123";
let x = &context.new_blob_file(s, b"data").unwrap();
println!("blobfilename '{}'", x);
println!("xxxxfilename '{}'", s);
assert!(x.len() < s.len());
assert!(dc_file_exist(&context, x));
assert!(x.starts_with("$BLOBDIR"));
}
#[test]
fn test_new_blob_file_unicode() {
let t = dummy_context();
let context = t.ctx;
let s = "helloäworld.qwe";
let x = &context.new_blob_file(s, b"data").unwrap();
assert_eq!(x, "$BLOBDIR/hello-world.qwe");
assert_eq!(dc_read_file(&context, x).unwrap(), b"data");
}
#[test]
fn test_sqlite_parent_not_exists() {
let tmp = tempfile::tempdir().unwrap();

View File

@@ -11,7 +11,6 @@ use mmime::mailmime::content::*;
use mmime::mailmime::disposition::*;
use mmime::mailmime::types::*;
use mmime::mailmime::*;
use mmime::mmapstring::*;
use mmime::other::*;
use crate::constants::Viewtype;
@@ -25,6 +24,7 @@ use crate::error::Error;
use crate::location;
use crate::param::*;
use crate::stock::StockMessage;
use crate::wrapmime;
#[derive(Debug)]
pub struct MimeParser<'a> {
@@ -127,7 +127,7 @@ impl<'a> MimeParser<'a> {
if (*field).fld_type == MAILIMF_FIELD_SUBJECT as libc::c_int {
let subj = (*(*field).fld_data.fld_subject).sbj_value;
self.subject = as_opt_str(subj).map(dc_decode_header_words_safe);
self.subject = as_opt_str(subj).map(dc_decode_header_words);
}
}
@@ -164,21 +164,17 @@ impl<'a> MimeParser<'a> {
}
}
}
} else {
if let Some(optional_field) = self.lookup_optional_field("Chat-Content") {
if optional_field == "location-streaming-enabled" {
self.is_system_message = SystemMessage::LocationStreamingEnabled;
}
} else if let Some(optional_field) = self.lookup_optional_field("Chat-Content") {
if optional_field == "location-streaming-enabled" {
self.is_system_message = SystemMessage::LocationStreamingEnabled;
}
}
if self.lookup_field("Chat-Group-Image").is_some() && !self.parts.is_empty() {
let textpart = &self.parts[0];
if textpart.typ == Viewtype::Text {
if self.parts.len() >= 2 {
let imgpart = &mut self.parts[1];
if imgpart.typ == Viewtype::Image {
imgpart.is_meta = true;
}
if textpart.typ == Viewtype::Text && self.parts.len() >= 2 {
let imgpart = &mut self.parts[1];
if imgpart.typ == Viewtype::Image {
imgpart.is_meta = true;
}
}
}
@@ -189,6 +185,7 @@ impl<'a> MimeParser<'a> {
textpart.typ == Viewtype::Text
&& (filepart.typ == Viewtype::Image
|| filepart.typ == Viewtype::Gif
|| filepart.typ == Viewtype::Sticker
|| filepart.typ == Viewtype::Audio
|| filepart.typ == Viewtype::Voice
|| filepart.typ == Viewtype::Video
@@ -256,6 +253,14 @@ impl<'a> MimeParser<'a> {
part_mut.typ = Viewtype::Voice;
}
}
if self.parts[0].typ == Viewtype::Image {
if let Some(content_type) = self.lookup_optional_field("Chat-Content") {
if content_type == "sticker" {
let part_mut = &mut self.parts[0];
part_mut.typ = Viewtype::Sticker;
}
}
}
let part = &self.parts[0];
if part.typ == Viewtype::Audio
|| part.typ == Viewtype::Voice
@@ -277,7 +282,7 @@ impl<'a> MimeParser<'a> {
if self.get_last_nonmeta().is_some() {
let mut mb_list: *mut mailimf_mailbox_list = ptr::null_mut();
let mut index_0 = 0;
let dn_field_c = CString::new(dn_field).unwrap();
let dn_field_c = CString::new(dn_field).unwrap_or_default();
if mailimf_mailbox_list_parse(
dn_field_c.as_ptr(),
@@ -287,12 +292,12 @@ impl<'a> MimeParser<'a> {
) == MAILIMF_NO_ERROR as libc::c_int
&& !mb_list.is_null()
{
if let Some(dn_to_addr) = mailimf_find_first_addr(mb_list) {
if let Some(dn_to_addr) = wrapmime::mailimf_find_first_addr(mb_list) {
if let Some(from_field) = self.lookup_field("From") {
if (*from_field).fld_type == MAILIMF_FIELD_FROM as libc::c_int
&& !(*from_field).fld_data.fld_from.is_null()
{
let from_addr = mailimf_find_first_addr(
let from_addr = wrapmime::mailimf_find_first_addr(
(*(*from_field).fld_data.fld_from).frm_mb_list,
);
if let Some(from_addr) = from_addr {
@@ -590,7 +595,7 @@ impl<'a> MimeParser<'a> {
return false;
}
let mut decoded_data = match mailmime_transfer_decode(mime) {
let mut decoded_data = match wrapmime::mailmime_transfer_decode(mime) {
Ok(decoded_data) => decoded_data,
Err(_) => {
// Note that it's now always an error - might be no data
@@ -601,7 +606,6 @@ impl<'a> MimeParser<'a> {
let old_part_count = self.parts.len();
/* regard `Content-Transfer-Encoding:` */
let mut ok_to_continue = true;
let mut desired_filename = String::default();
let mut simplifier: Option<Simplify> = None;
match mime_type {
@@ -616,15 +620,14 @@ impl<'a> MimeParser<'a> {
&& strcmp(charset, b"UTF-8\x00" as *const u8 as *const libc::c_char) != 0i32
{
if let Some(encoding) =
Charset::for_label(CStr::from_ptr(charset).to_str().unwrap().as_bytes())
Charset::for_label(CStr::from_ptr(charset).to_string_lossy().as_bytes())
{
let (res, _, _) = encoding.decode(&decoded_data);
if res.is_empty() {
/* no error - but nothing to add */
ok_to_continue = false;
} else {
decoded_data = res.as_bytes().to_vec()
return false;
}
decoded_data = res.as_bytes().to_vec()
} else {
warn!(
self.context,
@@ -634,31 +637,29 @@ impl<'a> MimeParser<'a> {
);
}
}
if ok_to_continue {
/* check header directly as is_send_by_messenger is not yet set up */
let is_msgrmsg = self.lookup_optional_field("Chat-Version").is_some();
/* check header directly as is_send_by_messenger is not yet set up */
let is_msgrmsg = self.lookup_optional_field("Chat-Version").is_some();
let simplified_txt = if decoded_data.is_empty() {
"".into()
} else {
let input = std::string::String::from_utf8_lossy(&decoded_data);
let is_html = mime_type == 70;
let simplified_txt = if decoded_data.is_empty() {
"".into()
} else {
let input = std::string::String::from_utf8_lossy(&decoded_data);
let is_html = mime_type == 70;
simplifier.unwrap().simplify(&input, is_html, is_msgrmsg)
};
if !simplified_txt.is_empty() {
let mut part = Part::default();
part.typ = Viewtype::Text;
part.mimetype = mime_type;
part.msg = Some(simplified_txt);
part.msg_raw =
Some(std::string::String::from_utf8_lossy(&decoded_data).to_string());
self.do_add_single_part(part);
}
simplifier.unwrap().simplify(&input, is_html, is_msgrmsg)
};
if !simplified_txt.is_empty() {
let mut part = Part::default();
part.typ = Viewtype::Text;
part.mimetype = mime_type;
part.msg = Some(simplified_txt);
part.msg_raw =
Some(std::string::String::from_utf8_lossy(&decoded_data).to_string());
self.do_add_single_part(part);
}
if simplifier.unwrap().is_forwarded {
self.is_forwarded = true;
}
if simplifier.unwrap().is_forwarded {
self.is_forwarded = true;
}
}
DC_MIMETYPE_IMAGE
@@ -704,7 +705,7 @@ impl<'a> MimeParser<'a> {
// might be a wrongly encoded filename
let s = to_string_lossy((*dsp_param).pa_data.pa_filename);
// this is used only if the parts buffer stays empty
desired_filename = dc_decode_header_words_safe(&s)
desired_filename = dc_decode_header_words(&s)
}
}
}
@@ -733,33 +734,29 @@ impl<'a> MimeParser<'a> {
desired_filename =
format!("file.{}", as_str((*(*mime).mm_content_type).ct_subtype));
} else {
ok_to_continue = false;
return false;
}
}
if ok_to_continue {
if desired_filename.starts_with("location")
&& desired_filename.ends_with(".kml")
{
if !decoded_data.is_empty() {
let d = std::string::String::from_utf8_lossy(&decoded_data);
self.location_kml = location::Kml::parse(self.context, &d).ok();
}
} else if desired_filename.starts_with("message")
&& desired_filename.ends_with(".kml")
{
if !decoded_data.is_empty() {
let d = std::string::String::from_utf8_lossy(&decoded_data);
self.message_kml = location::Kml::parse(self.context, &d).ok();
}
} else if !decoded_data.is_empty() {
self.do_add_single_file_part(
msg_type,
mime_type,
raw_mime.as_ref(),
&decoded_data,
&desired_filename,
);
if desired_filename.starts_with("location") && desired_filename.ends_with(".kml") {
if !decoded_data.is_empty() {
let d = std::string::String::from_utf8_lossy(&decoded_data);
self.location_kml = location::Kml::parse(self.context, &d).ok();
}
} else if desired_filename.starts_with("message")
&& desired_filename.ends_with(".kml")
{
if !decoded_data.is_empty() {
let d = std::string::String::from_utf8_lossy(&decoded_data);
self.message_kml = location::Kml::parse(self.context, &d).ok();
}
} else if !decoded_data.is_empty() {
self.do_add_single_file_part(
msg_type,
mime_type,
raw_mime.as_ref(),
&decoded_data,
&desired_filename,
);
}
}
_ => {}
@@ -776,28 +773,34 @@ impl<'a> MimeParser<'a> {
decoded_data: &[u8],
desired_filename: &str,
) {
/* create a free file name to use */
let path_filename = dc_get_fine_path_filename(self.context, "$BLOBDIR", desired_filename);
/* copy data to file */
if dc_write_file(self.context, &path_filename, decoded_data) {
let mut part = Part::default();
part.typ = msg_type;
part.mimetype = mime_type;
part.bytes = decoded_data.len() as libc::c_int;
part.param.set(Param::File, path_filename.to_string_lossy());
if let Some(raw_mime) = raw_mime {
part.param.set(Param::MimeType, raw_mime);
/* write decoded data to new blob file */
let bpath = match self.context.new_blob_file(desired_filename, decoded_data) {
Ok(path) => path,
Err(err) => {
error!(
self.context,
"Could not add blob for mime part {}, error {}", desired_filename, err
);
return;
}
};
if mime_type == DC_MIMETYPE_IMAGE {
if let Ok((width, height)) = dc_get_filemeta(decoded_data) {
part.param.set_int(Param::Width, width as i32);
part.param.set_int(Param::Height, height as i32);
}
}
self.do_add_single_part(part);
let mut part = Part::default();
part.typ = msg_type;
part.mimetype = mime_type;
part.bytes = decoded_data.len() as libc::c_int;
part.param.set(Param::File, bpath);
if let Some(raw_mime) = raw_mime {
part.param.set(Param::MimeType, raw_mime);
}
if mime_type == DC_MIMETYPE_IMAGE {
if let Ok((width, height)) = dc_get_filemeta(decoded_data) {
part.param.set_int(Param::Width, width as i32);
part.param.set_int(Param::Height, height as i32);
}
}
self.do_add_single_part(part);
}
fn do_add_single_part(&mut self, mut part: Part) {
@@ -832,7 +835,7 @@ impl<'a> MimeParser<'a> {
let mut fld_from: *const mailimf_from = ptr::null();
/* get From: and check there is exactly one sender */
let fld = mailimf_find_field(self.header_root, MAILIMF_FIELD_FROM as libc::c_int);
let fld = wrapmime::mailimf_find_field(self.header_root, MAILIMF_FIELD_FROM as libc::c_int);
if !(fld.is_null()
|| {
fld_from = (*fld).fld_data.fld_from;
@@ -850,11 +853,9 @@ impl<'a> MimeParser<'a> {
if !mb.is_null() {
let from_addr_norm = addr_normalize(as_str((*mb).mb_addr_spec));
let recipients = mailimf_get_recipients(self.header_root);
if recipients.len() == 1 {
if recipients.contains(from_addr_norm) {
sender_equals_recipient = true;
}
let recipients = wrapmime::mailimf_get_recipients(self.header_root);
if recipients.len() == 1 && recipients.contains(from_addr_norm) {
sender_equals_recipient = true;
}
}
}
@@ -881,7 +882,7 @@ impl<'a> MimeParser<'a> {
unsafe {
let fld_message_id = (*field).fld_data.fld_message_id;
if !fld_message_id.is_null() {
return Some(to_string((*fld_message_id).mid_value));
return Some(to_string_lossy((*fld_message_id).mid_value));
}
}
}
@@ -911,22 +912,6 @@ pub struct Part {
pub param: Params,
}
pub fn mailimf_find_first_addr(mb_list: *const mailimf_mailbox_list) -> Option<String> {
if mb_list.is_null() {
return None;
}
for cur in unsafe { (*(*mb_list).mb_list).into_iter() } {
let mb = cur as *mut mailimf_mailbox;
if !mb.is_null() && !unsafe { (*mb).mb_addr_spec.is_null() } {
let addr = unsafe { as_str((*mb).mb_addr_spec) };
return Some(addr_normalize(addr).to_string());
}
}
None
}
unsafe fn hash_header(out: &mut HashMap<String, *mut mailimf_field>, in_0: *const mailimf_fields) {
if in_0.is_null() {
return;
@@ -997,14 +982,12 @@ unsafe fn mailmime_get_mime_type(mime: *mut Mailmime) -> (libc::c_int, Viewtype,
) == 0i32
{
return (DC_MIMETYPE_TEXT_PLAIN, Viewtype::Text, None);
} else {
if strcmp(
(*c).ct_subtype,
b"html\x00" as *const u8 as *const libc::c_char,
) == 0i32
{
return (DC_MIMETYPE_TEXT_HTML, Viewtype::Text, None);
}
} else if strcmp(
(*c).ct_subtype,
b"html\x00" as *const u8 as *const libc::c_char,
) == 0i32
{
return (DC_MIMETYPE_TEXT_HTML, Viewtype::Text, None);
}
}
@@ -1148,214 +1131,6 @@ pub unsafe fn mailmime_find_ct_parameter(
ptr::null_mut()
}
pub unsafe fn mailmime_transfer_decode(mime: *mut Mailmime) -> Result<Vec<u8>, Error> {
ensure!(!mime.is_null(), "invalid inputs");
let mut mime_transfer_encoding = MAILMIME_MECHANISM_BINARY as libc::c_int;
let mime_data = (*mime).mm_data.mm_single;
if !(*mime).mm_mime_fields.is_null() {
for cur in (*(*(*mime).mm_mime_fields).fld_list).into_iter() {
let field = cur as *mut mailmime_field;
if !field.is_null()
&& (*field).fld_type == MAILMIME_FIELD_TRANSFER_ENCODING as libc::c_int
&& !(*field).fld_data.fld_encoding.is_null()
{
mime_transfer_encoding = (*(*field).fld_data.fld_encoding).enc_type;
break;
}
}
}
if mime_transfer_encoding == MAILMIME_MECHANISM_7BIT as libc::c_int
|| mime_transfer_encoding == MAILMIME_MECHANISM_8BIT as libc::c_int
|| mime_transfer_encoding == MAILMIME_MECHANISM_BINARY as libc::c_int
{
let decoded_data = (*mime_data).dt_data.dt_text.dt_data;
let decoded_data_bytes = (*mime_data).dt_data.dt_text.dt_length;
if decoded_data.is_null() || decoded_data_bytes <= 0 {
bail!("No data to decode found");
} else {
let result = std::slice::from_raw_parts(decoded_data as *const u8, decoded_data_bytes);
return Ok(result.to_vec());
}
}
let mut current_index = 0;
let mut transfer_decoding_buffer = ptr::null_mut();
let mut decoded_data_bytes = 0;
let r = mailmime_part_parse(
(*mime_data).dt_data.dt_text.dt_data,
(*mime_data).dt_data.dt_text.dt_length,
&mut current_index,
mime_transfer_encoding,
&mut transfer_decoding_buffer,
&mut decoded_data_bytes,
);
if r == MAILIMF_NO_ERROR as libc::c_int
&& !transfer_decoding_buffer.is_null()
&& decoded_data_bytes > 0
{
let result =
std::slice::from_raw_parts(transfer_decoding_buffer as *const u8, decoded_data_bytes)
.to_vec();
mmap_string_unref(transfer_decoding_buffer);
return Ok(result);
}
Err(format_err!("Failed to to decode"))
}
pub fn mailimf_get_recipients(imffields: *mut mailimf_fields) -> HashSet<String> {
/* returned addresses are normalized. */
let mut recipients: HashSet<String> = Default::default();
for cur in unsafe { (*(*imffields).fld_list).into_iter() } {
let fld = cur as *mut mailimf_field;
let fld_to: *mut mailimf_to;
let fld_cc: *mut mailimf_cc;
let mut addr_list: *mut mailimf_address_list = ptr::null_mut();
if fld.is_null() {
continue;
}
let fld = unsafe { *fld };
// TODO match on enums /rtn
match fld.fld_type {
13 => {
fld_to = unsafe { fld.fld_data.fld_to };
if !fld_to.is_null() {
addr_list = unsafe { (*fld_to).to_addr_list };
}
}
14 => {
fld_cc = unsafe { fld.fld_data.fld_cc };
if !fld_cc.is_null() {
addr_list = unsafe { (*fld_cc).cc_addr_list };
}
}
_ => {}
}
if !addr_list.is_null() {
for cur2 in unsafe { &(*(*addr_list).ad_list) } {
let adr = cur2 as *mut mailimf_address;
if adr.is_null() {
continue;
}
let adr = unsafe { *adr };
if adr.ad_type == MAILIMF_ADDRESS_MAILBOX as libc::c_int {
mailimf_get_recipients_add_addr(&mut recipients, unsafe {
adr.ad_data.ad_mailbox
});
} else if adr.ad_type == MAILIMF_ADDRESS_GROUP as libc::c_int {
let group = unsafe { adr.ad_data.ad_group };
if !group.is_null() && unsafe { !(*group).grp_mb_list.is_null() } {
for cur3 in unsafe { &(*(*(*group).grp_mb_list).mb_list) } {
mailimf_get_recipients_add_addr(
&mut recipients,
cur3 as *mut mailimf_mailbox,
);
}
}
}
}
}
}
recipients
}
fn mailimf_get_recipients_add_addr(recipients: &mut HashSet<String>, mb: *mut mailimf_mailbox) {
if !mb.is_null() {
let addr_norm = addr_normalize(as_str(unsafe { (*mb).mb_addr_spec }));
recipients.insert(addr_norm.into());
}
}
/*the result is a pointer to mime, must not be freed*/
pub fn mailimf_find_field(
header: *mut mailimf_fields,
wanted_fld_type: libc::c_int,
) -> *mut mailimf_field {
if header.is_null() {
return ptr::null_mut();
}
let header = unsafe { (*header) };
if header.fld_list.is_null() {
return ptr::null_mut();
}
for cur in unsafe { &(*header.fld_list) } {
let field = cur as *mut mailimf_field;
if !field.is_null() {
if unsafe { (*field).fld_type } == wanted_fld_type {
return field;
}
}
}
ptr::null_mut()
}
/*the result is a pointer to mime, must not be freed*/
pub unsafe fn mailmime_find_mailimf_fields(mime: *mut Mailmime) -> *mut mailimf_fields {
if mime.is_null() {
return ptr::null_mut();
}
match (*mime).mm_type as _ {
MAILMIME_MULTIPLE => {
for cur_data in (*(*mime).mm_data.mm_multipart.mm_mp_list).into_iter() {
let header = mailmime_find_mailimf_fields(cur_data as *mut _);
if !header.is_null() {
return header;
}
}
}
MAILMIME_MESSAGE => return (*mime).mm_data.mm_message.mm_fields,
_ => {}
}
ptr::null_mut()
}
pub unsafe fn mailimf_find_optional_field(
header: *mut mailimf_fields,
wanted_fld_name: *const libc::c_char,
) -> *mut mailimf_optional_field {
if header.is_null() || (*header).fld_list.is_null() {
return ptr::null_mut();
}
for cur_data in (*(*header).fld_list).into_iter() {
let field: *mut mailimf_field = cur_data as *mut _;
if (*field).fld_type == MAILIMF_FIELD_OPTIONAL_FIELD as libc::c_int {
let optional_field: *mut mailimf_optional_field = (*field).fld_data.fld_optional_field;
if !optional_field.is_null()
&& !(*optional_field).fld_name.is_null()
&& !(*optional_field).fld_value.is_null()
&& strcasecmp((*optional_field).fld_name, wanted_fld_name) == 0i32
{
return optional_field;
}
}
}
ptr::null_mut()
}
#[cfg(test)]
mod tests {
use super::*;
@@ -1371,14 +1146,13 @@ mod tests {
let mut mime: *mut Mailmime = ptr::null_mut();
let mut dummy = 0;
let res = mailmime_parse(txt, strlen(txt), &mut dummy, &mut mime);
assert_eq!(res, MAIL_NO_ERROR as libc::c_int);
assert!(!mime.is_null());
let fields: *mut mailimf_fields = mailmime_find_mailimf_fields(mime);
let fields: *mut mailimf_fields = wrapmime::mailmime_find_mailimf_fields(mime);
assert!(!fields.is_null());
let mut of_a: *mut mailimf_optional_field = mailimf_find_optional_field(
let mut of_a: *mut mailimf_optional_field = wrapmime::mailimf_find_optional_field(
fields,
b"fielda\x00" as *const u8 as *const libc::c_char,
);
@@ -1398,7 +1172,7 @@ mod tests {
"ValueA",
);
of_a = mailimf_find_optional_field(
of_a = wrapmime::mailimf_find_optional_field(
fields,
b"FIELDA\x00" as *const u8 as *const libc::c_char,
);
@@ -1418,7 +1192,7 @@ mod tests {
"ValueA",
);
let of_b: *mut mailimf_optional_field = mailimf_find_optional_field(
let of_b: *mut mailimf_optional_field = wrapmime::mailimf_find_optional_field(
fields,
b"FieldB\x00" as *const u8 as *const libc::c_char,
);
@@ -1441,7 +1215,9 @@ mod tests {
let context = dummy_context();
let raw = include_bytes!("../test-data/message/issue_523.txt");
let mut mimeparser = MimeParser::new(&context.ctx);
unsafe { mimeparser.parse(&raw[..]).unwrap() };
unsafe {
mimeparser.parse(&raw[..]).unwrap();
};
assert_eq!(mimeparser.subject, None);
assert_eq!(mimeparser.parts.len(), 1);
}
@@ -1449,9 +1225,13 @@ mod tests {
proptest! {
#[test]
fn test_dc_mailmime_parse_crash_fuzzy(data in "[!-~\t ]{2000,}") {
// this test doesn't exercise much of dc_mimeparser anymore
// because a missing From-field early aborts parsing
let context = dummy_context();
let mut mimeparser = MimeParser::new(&context.ctx);
unsafe { mimeparser.parse(data.as_bytes()).unwrap() };
unsafe {
assert!(mimeparser.parse(data.as_bytes()).is_err());
}
}
}
@@ -1480,7 +1260,7 @@ mod tests {
fn test_mimeparser_with_context() {
unsafe {
let context = dummy_context();
let raw = b"Content-Type: multipart/mixed; boundary=\"==break==\";\nSubject: outer-subject\nX-Special-A: special-a\nFoo: Bar\nChat-Version: 0.0\n\n--==break==\nContent-Type: text/plain; protected-headers=\"v1\";\nSubject: inner-subject\nX-Special-B: special-b\nFoo: Xy\nChat-Version: 1.0\n\ntest1\n\n--==break==--\n\n\x00";
let raw = b"From: hello\nContent-Type: multipart/mixed; boundary=\"==break==\";\nSubject: outer-subject\nX-Special-A: special-a\nFoo: Bar\nChat-Version: 0.0\n\n--==break==\nContent-Type: text/plain; protected-headers=\"v1\";\nSubject: inner-subject\nX-Special-B: special-b\nFoo: Xy\nChat-Version: 1.0\n\ntest1\n\n--==break==--\n\n\x00";
let mut mimeparser = MimeParser::new(&context.ctx);
mimeparser.parse(&raw[..]).unwrap();

View File

@@ -1,11 +1,9 @@
use std::ffi::CString;
use std::ptr;
use itertools::join;
use libc::{free, strcmp, strlen};
use libc::{free, strcmp};
use mmime::clist::*;
use mmime::mailimf::types::*;
use mmime::mailimf::*;
use mmime::mailmime::content::*;
use mmime::mailmime::types::*;
use mmime::mailmime::*;
@@ -13,6 +11,7 @@ use mmime::other::*;
use sha2::{Digest, Sha256};
use crate::chat::{self, Chat};
use crate::config::Config;
use crate::constants::*;
use crate::contact::*;
use crate::context::Context;
@@ -29,6 +28,7 @@ use crate::peerstate::*;
use crate::securejoin::handle_securejoin_handshake;
use crate::sql;
use crate::stock::StockMessage;
use crate::wrapmime;
#[derive(Debug, PartialEq, Eq)]
enum CreateEvent {
@@ -81,7 +81,7 @@ pub unsafe fn dc_receive_imf(
let mut chat_id = 0;
let mut hidden = 0;
let mut add_delete_job: libc::c_int = 0;
let mut needs_delete_job = false;
let mut insert_msg_id = 0;
let mut sent_timestamp = 0;
@@ -147,7 +147,7 @@ pub unsafe fn dc_receive_imf(
if mime_parser.sender_equals_recipient() {
from_id = DC_CONTACT_ID_SELF;
}
} else if from_list.len() >= 1 {
} else if !from_list.is_empty() {
// if there is no from given, from_id stays 0 which is just fine. These messages
// are very rare, however, we have to add them to the database (they go to the
// "deaddrop" chat) to avoid a re-download from the server. See also [**]
@@ -218,7 +218,7 @@ pub unsafe fn dc_receive_imf(
&mut chat_id,
&mut to_id,
flags,
&mut add_delete_job,
&mut needs_delete_job,
to_self,
&mut insert_msg_id,
&mut created_db_entries,
@@ -249,7 +249,7 @@ pub unsafe fn dc_receive_imf(
from_id,
sent_timestamp,
&mut rr_event_to_send,
server_folder,
&server_folder,
server_uid,
);
}
@@ -265,7 +265,8 @@ pub unsafe fn dc_receive_imf(
);
}
if 0 != add_delete_job && !created_db_entries.is_empty() {
// if we delete we don't need to try moving messages
if needs_delete_job && !created_db_entries.is_empty() {
job_add(
context,
Action::DeleteMsgOnImap,
@@ -273,6 +274,8 @@ pub unsafe fn dc_receive_imf(
Params::new(),
0,
);
} else {
context.do_heuristics_moves(server_folder.as_ref(), insert_msg_id);
}
info!(
@@ -305,7 +308,7 @@ unsafe fn add_parts(
chat_id: &mut u32,
to_id: &mut u32,
flags: u32,
add_delete_job: &mut libc::c_int,
needs_delete_job: &mut bool,
to_self: i32,
insert_msg_id: &mut u32,
created_db_entries: &mut Vec<(usize, usize)>,
@@ -370,10 +373,7 @@ unsafe fn add_parts(
// maybe this can be optimized later, by checking the state before the message body is downloaded
let mut allow_creation = 1;
if mime_parser.is_system_message != SystemMessage::AutocryptSetupMessage && msgrmsg == 0 {
let show_emails = context
.sql
.get_config_int(context, "show_emails")
.unwrap_or_default();
let show_emails = context.get_config_int(Config::ShowEmails);
if show_emails == 0 {
*chat_id = 3;
allow_creation = 0
@@ -403,7 +403,7 @@ unsafe fn add_parts(
let handshake = handle_securejoin_handshake(context, mime_parser, *from_id);
if 0 != handshake & DC_HANDSHAKE_STOP_NORMAL_PROCESSING {
*hidden = 1;
*add_delete_job = handshake & DC_HANDSHAKE_ADD_DELETE_JOB;
*needs_delete_job = 0 != handshake & DC_HANDSHAKE_ADD_DELETE_JOB;
state = MessageState::InSeen;
}
}
@@ -435,7 +435,7 @@ unsafe fn add_parts(
to_ids,
chat_id,
&mut chat_id_blocked,
);
)?;
if 0 != *chat_id && Blocked::Not != chat_id_blocked && create_blocked == Blocked::Not {
chat::unblock(context, *chat_id);
chat_id_blocked = Blocked::Not;
@@ -494,10 +494,12 @@ unsafe fn add_parts(
// if the chat_id is blocked,
// for unknown senders and non-delta messages set the state to NOTICED
// to not result in a contact request (this would require the state FRESH)
if Blocked::Not != chat_id_blocked && state == MessageState::InFresh {
if !incoming_origin.is_verified() && msgrmsg == 0 {
state = MessageState::InNoticed;
}
if Blocked::Not != chat_id_blocked
&& state == MessageState::InFresh
&& !incoming_origin.is_verified()
&& msgrmsg == 0
{
state = MessageState::InNoticed;
}
} else {
// Outgoing
@@ -518,7 +520,7 @@ unsafe fn add_parts(
to_ids,
chat_id,
&mut chat_id_blocked,
);
)?;
if 0 != *chat_id && Blocked::Not != chat_id_blocked {
chat::unblock(context, *chat_id);
chat_id_blocked = Blocked::Not;
@@ -578,11 +580,11 @@ unsafe fn add_parts(
);
// unarchive chat
chat::unarchive(context, *chat_id).unwrap();
chat::unarchive(context, *chat_id)?;
// if the mime-headers should be saved, find out its size
// (the mime-header ends with an empty line)
let save_mime_headers = context.sql.get_config_bool(context, "save_mime_headers");
let save_mime_headers = context.get_config_bool(Config::SaveMimeHeaders);
if let Some(field) = mime_parser.lookup_field_typ("In-Reply-To", MAILIMF_FIELD_IN_REPLY_TO) {
let fld_in_reply_to = (*field).fld_data.fld_in_reply_to;
if !fld_in_reply_to.is_null() {
@@ -626,7 +628,7 @@ unsafe fn add_parts(
}
if let Some(ref msg) = part.msg {
if !mime_parser.location_kml.is_none()
if mime_parser.location_kml.is_some()
&& icnt == 1
&& (msg == "-location-" || msg.is_empty())
{
@@ -685,8 +687,8 @@ unsafe fn add_parts(
} else {
None
},
to_string(mime_in_reply_to),
to_string(mime_references),
to_string_lossy(mime_in_reply_to),
to_string_lossy(mime_references),
])?;
txt_raw = None;
@@ -720,7 +722,6 @@ unsafe fn add_parts(
}
}
context.do_heuristics_moves(server_folder.as_ref(), *insert_msg_id);
cleanup(mime_in_reply_to, mime_references);
Ok(())
@@ -736,10 +737,7 @@ unsafe fn handle_reports(
server_folder: impl AsRef<str>,
server_uid: u32,
) {
let mdns_enabled = context
.sql
.get_config_int(context, "mdns_enabled")
.unwrap_or_else(|| DC_MDNS_DEFAULT_ENABLED);
let mdns_enabled = context.get_config_bool(Config::MdnsEnabled);
for report_root in &mime_parser.reports {
let report_root = *report_root;
@@ -758,7 +756,7 @@ unsafe fn handle_reports(
&& (*(*report_root).mm_data.mm_multipart.mm_mp_list).count >= 2
{
// to get a clear functionality, do not show incoming MDNs if the options is disabled
if 0 != mdns_enabled {
if mdns_enabled {
let report_data = (if !if !(*(*report_root).mm_data.mm_multipart.mm_mp_list)
.first
.is_null()
@@ -795,7 +793,7 @@ unsafe fn handle_reports(
b"disposition-notification\x00" as *const u8 as *const libc::c_char,
) == 0
{
if let Ok(report_body) = mailmime_transfer_decode(report_data) {
if let Ok(report_body) = wrapmime::mailmime_transfer_decode(report_data) {
let mut report_parsed = std::ptr::null_mut();
let mut dummy = 0;
@@ -807,13 +805,14 @@ unsafe fn handle_reports(
) == MAIL_NO_ERROR as libc::c_int
&& !report_parsed.is_null()
{
let report_fields = mailmime_find_mailimf_fields(report_parsed);
let report_fields =
wrapmime::mailmime_find_mailimf_fields(report_parsed);
if !report_fields.is_null() {
let of_disposition = mailimf_find_optional_field(
let of_disposition = wrapmime::mailimf_find_optional_field(
report_fields,
b"Disposition\x00" as *const u8 as *const libc::c_char,
);
let of_org_msgid = mailimf_find_optional_field(
let of_org_msgid = wrapmime::mailimf_find_optional_field(
report_fields,
b"Original-Message-ID\x00" as *const u8 as *const libc::c_char,
);
@@ -822,24 +821,16 @@ unsafe fn handle_reports(
&& !of_org_msgid.is_null()
&& !(*of_org_msgid).fld_value.is_null()
{
let mut rfc724_mid_0 = std::ptr::null_mut();
dummy = 0;
if mailimf_msg_id_parse(
if let Ok(rfc724_mid) = wrapmime::parse_message_id(as_str(
(*of_org_msgid).fld_value,
strlen((*of_org_msgid).fld_value),
&mut dummy,
&mut rfc724_mid_0,
) == MAIL_NO_ERROR as libc::c_int
&& !rfc724_mid_0.is_null()
{
)) {
let mut chat_id_0 = 0;
let mut msg_id = 0;
if message::mdn_from_ext(
context,
from_id,
as_str(rfc724_mid_0),
&rfc724_mid,
sent_timestamp,
&mut chat_id_0,
&mut msg_id,
@@ -847,7 +838,6 @@ unsafe fn handle_reports(
rr_event_to_send.push((chat_id_0, msg_id));
}
mdn_consumed = (msg_id != 0) as libc::c_int;
free(rfc724_mid_0.cast());
}
}
}
@@ -861,12 +851,7 @@ unsafe fn handle_reports(
let mut param = Params::new();
param.set(Param::ServerFolder, server_folder.as_ref());
param.set_int(Param::ServerUid, server_uid as i32);
if mime_parser.is_send_by_messenger
&& 0 != context
.sql
.get_config_int(context, "mvbox_move")
.unwrap_or_else(|| 1)
{
if mime_parser.is_send_by_messenger && context.get_config_bool(Config::MvboxMove) {
param.set_int(Param::AlsoMove, 1);
}
job_add(context, Action::MarkseenMdnOnImap, 0, param, 0);
@@ -982,7 +967,7 @@ unsafe fn create_or_lookup_group(
to_ids: &mut Vec<u32>,
ret_chat_id: *mut u32,
ret_chat_id_blocked: &mut Blocked,
) {
) -> Result<()> {
let group_explicitly_left: bool;
let mut chat_id = 0;
let mut chat_id_blocked = Blocked::Not;
@@ -1040,7 +1025,7 @@ unsafe fn create_or_lookup_group(
{
let fld_in_reply_to = (*field).fld_data.fld_in_reply_to;
if !fld_in_reply_to.is_null() {
grpid = to_string(dc_extract_grpid_from_rfc724_mid_list(
grpid = to_string_lossy(dc_extract_grpid_from_rfc724_mid_list(
(*fld_in_reply_to).mid_list,
));
}
@@ -1051,7 +1036,7 @@ unsafe fn create_or_lookup_group(
{
let fld_references = (*field).fld_data.fld_references;
if !fld_references.is_null() {
grpid = to_string(dc_extract_grpid_from_rfc724_mid_list(
grpid = to_string_lossy(dc_extract_grpid_from_rfc724_mid_list(
(*fld_references).mid_list,
));
}
@@ -1067,16 +1052,16 @@ unsafe fn create_or_lookup_group(
to_ids,
&mut chat_id,
&mut chat_id_blocked,
);
)?;
cleanup(ret_chat_id, ret_chat_id_blocked, chat_id, chat_id_blocked);
return;
return Ok(());
}
}
}
}
if let Some(optional_field) = mime_parser.lookup_optional_field("Chat-Group-Name") {
grpname = Some(dc_decode_header_words_safe(&optional_field));
grpname = Some(dc_decode_header_words(&optional_field));
}
if let Some(optional_field) = mime_parser.lookup_optional_field("Chat-Group-Member-Removed") {
X_MrRemoveFromGrp = Some(optional_field);
@@ -1168,8 +1153,7 @@ unsafe fn create_or_lookup_group(
group_explicitly_left = chat::is_group_explicitly_left(context, &grpid).unwrap_or_default();
let self_addr = context
.sql
.get_config(context, "configured_addr")
.get_config(Config::ConfiguredAddr)
.unwrap_or_default();
if chat_id == 0
&& !mime_parser.is_mailinglist_message()
@@ -1195,7 +1179,7 @@ unsafe fn create_or_lookup_group(
}
if 0 == allow_creation {
cleanup(ret_chat_id, ret_chat_id_blocked, chat_id, chat_id_blocked);
return;
return Ok(());
}
chat_id = create_group_record(
context,
@@ -1223,10 +1207,10 @@ unsafe fn create_or_lookup_group(
to_ids,
&mut chat_id,
&mut chat_id_blocked,
);
)?;
}
cleanup(ret_chat_id, ret_chat_id_blocked, chat_id, chat_id_blocked);
return;
return Ok(());
}
// execute group commands
@@ -1279,7 +1263,7 @@ unsafe fn create_or_lookup_group(
} else {
chat.param.set(Param::ProfileImage, grpimage);
}
chat.update_param(context).unwrap();
chat.update_param(context)?;
send_EVENT_CHAT_MODIFIED = 1;
}
}
@@ -1343,11 +1327,12 @@ unsafe fn create_or_lookup_group(
to_ids,
&mut chat_id,
&mut chat_id_blocked,
);
)?;
}
}
cleanup(ret_chat_id, ret_chat_id_blocked, chat_id, chat_id_blocked);
return Ok(());
}
/// Handle groups for received messages
@@ -1360,7 +1345,7 @@ unsafe fn create_or_lookup_adhoc_group(
to_ids: &mut Vec<u32>,
ret_chat_id: *mut u32,
ret_chat_id_blocked: &mut Blocked,
) {
) -> Result<()> {
// if we're here, no grpid was found, check there is an existing ad-hoc
// group matching the to-list or if we can create one
let mut chat_id = 0;
@@ -1380,7 +1365,7 @@ unsafe fn create_or_lookup_adhoc_group(
if to_ids.is_empty() || mime_parser.is_mailinglist_message() {
// too few contacts or a mailinglist
cleanup(ret_chat_id, ret_chat_id_blocked, chat_id, chat_id_blocked);
return;
return Ok(());
}
let mut member_ids = to_ids.clone();
@@ -1393,10 +1378,10 @@ unsafe fn create_or_lookup_adhoc_group(
if member_ids.len() < 3 {
// too few contacts given
cleanup(ret_chat_id, ret_chat_id_blocked, chat_id, chat_id_blocked);
return;
return Ok(());
}
let chat_ids = search_chat_ids_by_contact_ids(context, &member_ids);
let chat_ids = search_chat_ids_by_contact_ids(context, &member_ids)?;
if !chat_ids.is_empty() {
let chat_ids_str = join(chat_ids.iter().map(|x| x.to_string()), ",");
let res = context.sql.query_row(
@@ -1416,13 +1401,13 @@ unsafe fn create_or_lookup_adhoc_group(
chat_id_blocked = id_blocked;
/* success, chat found */
cleanup(ret_chat_id, ret_chat_id_blocked, chat_id, chat_id_blocked);
return;
return Ok(());
}
}
if 0 == allow_creation {
cleanup(ret_chat_id, ret_chat_id_blocked, chat_id, chat_id_blocked);
return;
return Ok(());
}
// we do not check if the message is a reply to another group, this may result in
// chats with unclear member list. instead we create a new group in the following lines ...
@@ -1432,7 +1417,7 @@ unsafe fn create_or_lookup_adhoc_group(
let grpid = create_adhoc_grp_id(context, &member_ids);
if grpid.is_empty() {
cleanup(ret_chat_id, ret_chat_id_blocked, chat_id, chat_id_blocked);
return;
return Ok(());
}
// use subject as initial chat name
@@ -1458,6 +1443,7 @@ unsafe fn create_or_lookup_adhoc_group(
context.call_cb(Event::ChatModified(chat_id));
cleanup(ret_chat_id, ret_chat_id_blocked, chat_id, chat_id_blocked);
return Ok(());
}
fn create_group_record(
@@ -1490,7 +1476,7 @@ fn create_group_record(
sql::get_rowid(context, &context.sql, "chats", "grpid", grpid.as_ref())
}
fn create_adhoc_grp_id(context: &Context, member_ids: &Vec<u32>) -> String {
fn create_adhoc_grp_id(context: &Context, member_ids: &[u32]) -> String {
/* algorithm:
- sort normalized, lowercased, e-mail addresses alphabetically
- put all e-mail addresses into a single string, separate the address by a single comma
@@ -1499,8 +1485,7 @@ fn create_adhoc_grp_id(context: &Context, member_ids: &Vec<u32>) -> String {
*/
let member_ids_str = join(member_ids.iter().map(|x| x.to_string()), ",");
let member_cs = context
.sql
.get_config(context, "configured_addr")
.get_config(Config::ConfiguredAddr)
.unwrap_or_else(|| "no-self".to_string())
.to_lowercase();
@@ -1536,7 +1521,10 @@ fn hex_hash(s: impl AsRef<str>) -> String {
}
#[allow(non_snake_case)]
fn search_chat_ids_by_contact_ids(context: &Context, unsorted_contact_ids: &Vec<u32>) -> Vec<u32> {
fn search_chat_ids_by_contact_ids(
context: &Context,
unsorted_contact_ids: &Vec<u32>,
) -> Result<Vec<u32>> {
/* searches chat_id's by the given contact IDs, may return zero, one or more chat_id's */
let mut contact_ids = Vec::with_capacity(23);
let mut chat_ids = Vec::with_capacity(23);
@@ -1591,18 +1579,18 @@ fn search_chat_ids_by_contact_ids(context: &Context, unsorted_contact_ids: &Vec<
}
Ok(())
}
).unwrap(); // TODO: better error handling
)?;
}
}
chat_ids
Ok(chat_ids)
}
fn check_verified_properties(
context: &Context,
mimeparser: &MimeParser,
from_id: u32,
to_ids: &Vec<u32>,
to_ids: &[u32],
) -> Result<()> {
let contact = Contact::load_from_db(context, from_id)?;
@@ -1670,7 +1658,7 @@ fn check_verified_properties(
let fp = peerstate.gossip_key_fingerprint.clone();
if let Some(fp) = fp {
peerstate.set_verified(0, &fp, 2);
peerstate.save_to_db(&context.sql, false).unwrap();
peerstate.save_to_db(&context.sql, false)?;
is_verified = true;
}
}
@@ -1697,13 +1685,7 @@ fn set_better_msg(mime_parser: &mut MimeParser, better_msg: impl AsRef<str>) {
unsafe fn dc_is_reply_to_known_message(context: &Context, mime_parser: &MimeParser) -> libc::c_int {
/* check if the message is a reply to a known message; the replies are identified by the Message-ID from
`In-Reply-To`/`References:` (to support non-Delta-Clients) or from `Chat-Predecessor:` (Delta clients, see comment in dc_chat.c) */
if let Some(optional_field) = mime_parser.lookup_optional_field("Chat-Predecessor") {
let optional_field_c = CString::new(optional_field).unwrap();
if 0 != is_known_rfc724_mid(context, optional_field_c.as_ptr()) {
return 1;
}
}
`In-Reply-To`/`References:` (to support non-Delta-Clients) */
if let Some(field) = mime_parser.lookup_field("In-Reply-To") {
if (*field).fld_type == MAILIMF_FIELD_IN_REPLY_TO as libc::c_int {
@@ -1722,13 +1704,13 @@ unsafe fn dc_is_reply_to_known_message(context: &Context, mime_parser: &MimePars
if let Some(field) = mime_parser.lookup_field("References") {
if (*field).fld_type == MAILIMF_FIELD_REFERENCES as libc::c_int {
let fld_references = (*field).fld_data.fld_references;
if !fld_references.is_null() {
if is_known_rfc724_mid_in_list(
if !fld_references.is_null()
&& is_known_rfc724_mid_in_list(
context,
(*(*field).fld_data.fld_references).mid_list,
) {
return 1;
}
)
{
return 1;
}
}
}
@@ -1742,12 +1724,12 @@ unsafe fn is_known_rfc724_mid_in_list(context: &Context, mid_list: *const clist)
}
for data in &*mid_list {
if 0 != is_known_rfc724_mid(context, data.cast()) {
if is_known_rfc724_mid(context, data.cast()) != 0 {
return true;
}
}
return false;
false
}
/// Check if a message is a reply to a known message (messenger or non-messenger).
@@ -1951,8 +1933,7 @@ unsafe fn add_or_lookup_contact_by_addr(
}
*check_self = 0;
let self_addr = context
.sql
.get_config(context, "configured_addr")
.get_config(Config::ConfiguredAddr)
.unwrap_or_default();
if addr_cmp(self_addr, as_str(addr_spec)) {
@@ -1965,17 +1946,15 @@ unsafe fn add_or_lookup_contact_by_addr(
/* add addr_spec if missing, update otherwise */
let mut display_name_dec = "".to_string();
if !display_name_enc.is_null() {
let tmp = as_str(dc_decode_header_words(display_name_enc));
let tmp = dc_decode_header_words(as_str(display_name_enc));
display_name_dec = normalize_name(&tmp);
}
/*can be NULL*/
let row_id = Contact::add_or_lookup(context, display_name_dec, as_str(addr_spec), origin)
.map(|(id, _)| id)
.unwrap_or_default();
if 0 != row_id {
if !ids.contains(&row_id) {
ids.push(row_id);
}
if 0 != row_id && !ids.contains(&row_id) {
ids.push(row_id);
};
}

View File

@@ -10,18 +10,16 @@ pub struct Simplify {
///
/// Also return whether not-standard (rfc3676, §4.3) footer is found.
fn find_message_footer(lines: &[&str]) -> (usize, bool) {
for ix in 0..lines.len() {
let line = lines[ix];
for (ix, &line) in lines.iter().enumerate() {
// quoted-printable may encode `-- ` to `-- =20` which is converted
// back to `-- `
match line.as_ref() {
match line {
"-- " | "-- " => return (ix, false),
"--" | "---" | "----" => return (ix, true),
_ => (),
}
}
return (lines.len(), false);
(lines.len(), false)
}
impl Simplify {
@@ -103,10 +101,8 @@ impl Simplify {
if let Some(last_quoted_line) = l_lastQuotedLine {
l_last = last_quoted_line;
is_cut_at_end = true;
if l_last > 1 {
if is_empty_line(lines[l_last - 1]) {
l_last -= 1
}
if l_last > 1 && is_empty_line(lines[l_last - 1]) {
l_last -= 1
}
if l_last > 1 {
let line = lines[l_last - 1];
@@ -205,7 +201,7 @@ fn is_quoted_headline(buf: &str) -> bool {
}
fn is_plain_quote(buf: &str) -> bool {
buf.starts_with(">")
buf.starts_with('>')
}
#[cfg(test)]

View File

@@ -4,7 +4,7 @@ use std::ffi::CString;
use std::ptr;
use charset::Charset;
use libc::{free, strlen};
use libc::free;
use mmime::mailmime::decode::mailmime_encoded_phrase_parse;
use mmime::other::*;
use percent_encoding::{percent_decode, utf8_percent_encode, AsciiSet, CONTROLS};
@@ -68,28 +68,7 @@ fn quote_word(word: &[u8]) -> String {
* Encode/decode header words, RFC 2047
******************************************************************************/
pub unsafe fn dc_decode_header_words(in_0: *const libc::c_char) -> *mut libc::c_char {
if in_0.is_null() {
return ptr::null_mut();
}
let mut out: *mut libc::c_char = ptr::null_mut();
let mut cur_token = 0;
let r: libc::c_int = mailmime_encoded_phrase_parse(
b"iso-8859-1\x00" as *const u8 as *const libc::c_char,
in_0,
strlen(in_0),
&mut cur_token,
b"utf-8\x00" as *const u8 as *const libc::c_char,
&mut out,
);
if r != MAILIMF_NO_ERROR as libc::c_int || out.is_null() {
out = dc_strdup(in_0)
}
out
}
pub fn dc_decode_header_words_safe(input: &str) -> String {
pub(crate) fn dc_decode_header_words(input: &str) -> String {
static FROM_ENCODING: &[u8] = b"iso-8859-1\x00";
static TO_ENCODING: &[u8] = b"utf-8\x00";
let mut out = ptr::null_mut();
@@ -107,7 +86,7 @@ pub fn dc_decode_header_words_safe(input: &str) -> String {
if r as u32 != MAILIMF_NO_ERROR || out.is_null() {
input.to_string()
} else {
let res = to_string(out);
let res = to_string_lossy(out);
free(out.cast());
res
}
@@ -177,60 +156,31 @@ pub fn dc_decode_ext_header(to_decode: &[u8]) -> Cow<str> {
mod tests {
use super::*;
use libc::strcmp;
use std::ffi::CStr;
#[test]
fn test_dc_decode_header_words() {
unsafe {
let mut buf1: *mut libc::c_char = dc_decode_header_words(
b"=?utf-8?B?dGVzdMOkw7bDvC50eHQ=?=\x00" as *const u8 as *const libc::c_char,
);
assert_eq!(
strcmp(
buf1,
b"test\xc3\xa4\xc3\xb6\xc3\xbc.txt\x00" as *const u8 as *const libc::c_char
),
0
);
free(buf1 as *mut libc::c_void);
assert_eq!(
dc_decode_header_words("=?utf-8?B?dGVzdMOkw7bDvC50eHQ=?="),
std::string::String::from_utf8(b"test\xc3\xa4\xc3\xb6\xc3\xbc.txt".to_vec()).unwrap(),
);
buf1 =
dc_decode_header_words(b"just ascii test\x00" as *const u8 as *const libc::c_char);
assert_eq!(CStr::from_ptr(buf1).to_str().unwrap(), "just ascii test");
free(buf1 as *mut libc::c_void);
assert_eq!(dc_decode_header_words("just ascii test"), "just ascii test");
assert_eq!(dc_encode_header_words("abcdef"), "abcdef");
assert_eq!(dc_encode_header_words("abcdef"), "abcdef");
let r = dc_encode_header_words(
std::string::String::from_utf8(b"test\xc3\xa4\xc3\xb6\xc3\xbc.txt".to_vec())
.unwrap(),
let r = dc_encode_header_words(
std::string::String::from_utf8(b"test\xc3\xa4\xc3\xb6\xc3\xbc.txt".to_vec()).unwrap(),
);
assert!(r.starts_with("=?utf-8"));
assert_eq!(
dc_decode_header_words(&r),
std::string::String::from_utf8(b"test\xc3\xa4\xc3\xb6\xc3\xbc.txt".to_vec()).unwrap(),
);
assert_eq!(
dc_decode_header_words("=?ISO-8859-1?Q?attachment=3B=0D=0A_filename=3D?= =?ISO-8859-1?Q?=22test=E4=F6=FC=2Etxt=22=3B=0D=0A_size=3D39?="),
std::string::String::from_utf8(b"attachment;\r\n filename=\"test\xc3\xa4\xc3\xb6\xc3\xbc.txt\";\r\n size=39".to_vec()).unwrap(),
);
assert!(r.starts_with("=?utf-8"));
buf1 = r.strdup();
let buf2: *mut libc::c_char = dc_decode_header_words(buf1);
assert_eq!(
strcmp(
buf2,
b"test\xc3\xa4\xc3\xb6\xc3\xbc.txt\x00" as *const u8 as *const libc::c_char
),
0
);
free(buf2 as *mut libc::c_void);
buf1 = dc_decode_header_words(
b"=?ISO-8859-1?Q?attachment=3B=0D=0A_filename=3D?= =?ISO-8859-1?Q?=22test=E4=F6=FC=2Etxt=22=3B=0D=0A_size=3D39?=\x00" as *const u8 as *const libc::c_char
);
assert_eq!(
strcmp(
buf1,
b"attachment;\r\n filename=\"test\xc3\xa4\xc3\xb6\xc3\xbc.txt\";\r\n size=39\x00" as *const u8 as *const libc::c_char,
),
0
);
}
}
#[test]
@@ -293,7 +243,7 @@ mod tests {
#[test]
fn test_dc_header_roundtrip(input: String) {
let encoded = dc_encode_header_words(&input);
let decoded = dc_decode_header_words_safe(&encoded);
let decoded = dc_decode_header_words(&encoded);
assert_eq!(input, decoded);
}

View File

@@ -1,8 +1,9 @@
//! Some tools and enhancements to the used libraries, there should be
//! no references to Context and other "larger" entities here.
use core::cmp::max;
use std::borrow::Cow;
use std::ffi::{CStr, CString, OsString};
use std::ffi::{CStr, CString};
use std::path::{Path, PathBuf};
use std::str::FromStr;
use std::time::SystemTime;
@@ -16,6 +17,7 @@ use rand::{thread_rng, Rng};
use crate::context::Context;
use crate::error::Error;
use crate::events::Event;
pub(crate) fn dc_exactly_one_bit_set(v: libc::c_int) -> bool {
0 != v && 0 == v & (v - 1)
@@ -28,11 +30,11 @@ pub(crate) fn dc_exactly_one_bit_set(v: libc::c_int) -> bool {
/// # Examples
///
/// ```
/// use deltachat::dc_tools::{dc_strdup, to_string};
/// 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(str_a_copy), "foobar");
/// assert_eq!(to_string_lossy(str_a_copy), "foobar");
/// assert_ne!(str_a, str_a_copy);
/// }
/// ```
@@ -49,83 +51,6 @@ pub unsafe fn dc_strdup(s: *const libc::c_char) -> *mut libc::c_char {
ret
}
pub(crate) fn dc_atoi_null_is_0(s: *const libc::c_char) -> libc::c_int {
if !s.is_null() {
as_str(s).parse().unwrap_or_default()
} else {
0
}
}
unsafe fn dc_ltrim(buf: *mut libc::c_char) {
let mut len: libc::size_t;
let mut cur: *const libc::c_uchar;
if !buf.is_null() && 0 != *buf as libc::c_int {
len = strlen(buf);
cur = buf as *const libc::c_uchar;
while 0 != *cur as libc::c_int && 0 != libc::isspace(*cur as libc::c_int) {
cur = cur.offset(1isize);
len = len.wrapping_sub(1)
}
if buf as *const libc::c_uchar != cur {
libc::memmove(
buf as *mut libc::c_void,
cur as *const libc::c_void,
len.wrapping_add(1),
);
}
};
}
unsafe fn dc_rtrim(buf: *mut libc::c_char) {
let mut len: libc::size_t;
let mut cur: *mut libc::c_uchar;
if !buf.is_null() && 0 != *buf as libc::c_int {
len = strlen(buf);
cur = (buf as *mut libc::c_uchar)
.offset(len as isize)
.offset(-1isize);
while cur != buf as *mut libc::c_uchar && 0 != libc::isspace(*cur as libc::c_int) {
cur = cur.offset(-1isize);
len = len.wrapping_sub(1)
}
*cur.offset(
(if 0 != libc::isspace(*cur as libc::c_int) {
0
} else {
1
}) as isize,
) = '\u{0}' as i32 as libc::c_uchar
};
}
pub(crate) unsafe fn dc_trim(buf: *mut libc::c_char) {
dc_ltrim(buf);
dc_rtrim(buf);
}
/* remove all \r characters from string */
pub(crate) unsafe fn dc_remove_cr_chars(buf: *mut libc::c_char) {
/* search for first `\r` */
let mut p1: *const libc::c_char = buf;
while 0 != *p1 {
if *p1 as libc::c_int == '\r' as i32 {
break;
}
p1 = p1.offset(1isize)
}
/* p1 is `\r` or null-byte; start removing `\r` */
let mut p2: *mut libc::c_char = p1 as *mut libc::c_char;
while 0 != *p1 {
if *p1 as libc::c_int != '\r' as i32 {
*p2 = *p1;
p2 = p2.offset(1isize)
}
p1 = p1.offset(1isize)
}
*p2 = 0 as libc::c_char;
}
/// Shortens a string to a specified length and adds "..." or "[...]" to the end of
/// the shortened string.
pub(crate) fn dc_truncate(buf: &str, approx_chars: usize, do_unwrap: bool) -> Cow<str> {
@@ -140,7 +65,7 @@ pub(crate) fn dc_truncate(buf: &str, approx_chars: usize, do_unwrap: bool) -> Co
.unwrap_or_default();
if let Some(index) = buf[..end_pos].rfind(|c| c == ' ' || c == '\n') {
Cow::Owned(format!("{}{}", &buf[..index + 1], ellipse))
Cow::Owned(format!("{}{}", &buf[..=index], ellipse))
} else {
Cow::Owned(format!("{}{}", &buf[..end_pos], ellipse))
}
@@ -333,14 +258,14 @@ fn encode_66bits_as_base64(v1: u32, v2: u32, fill: u32) -> String {
enc.write_u8(((fill & 0x3) as u8) << 6).unwrap();
enc.finish().unwrap();
}
assert_eq!(wrapped_writer.pop(), Some('A' as u8)); // Remove last "A"
assert_eq!(wrapped_writer.pop(), Some(b'A')); // Remove last "A"
String::from_utf8(wrapped_writer).unwrap()
}
pub(crate) fn dc_create_incoming_rfc724_mid(
message_timestamp: i64,
contact_id_from: u32,
contact_ids_to: &Vec<u32>,
contact_ids_to: &[u32],
) -> Option<String> {
if contact_ids_to.is_empty() {
return None;
@@ -415,12 +340,41 @@ pub(crate) fn dc_ensure_no_slash_safe(path: &str) -> &str {
path
}
/// Function modifies the given buffer and replaces all characters not valid in filenames by a "-".
fn validate_filename(filename: &str) -> String {
filename
.replace('/', "-")
.replace('\\', "-")
.replace(':', "-")
// Function returns a sanitized basename that does not contain
// win/linux path separators and also not any non-ascii chars
fn get_safe_basename(filename: &str) -> String {
// return the (potentially mangled) basename of the input filename
// this might be a path that comes in from another operating system
let mut index: usize = 0;
if let Some(unix_index) = filename.rfind('/') {
index = unix_index + 1;
}
if let Some(win_index) = filename.rfind('\\') {
index = max(index, win_index + 1);
}
if index >= filename.len() {
"nobasename".to_string()
} else {
// we don't allow any non-ascii to be super-safe
filename[index..].replace(|c: char| !c.is_ascii() || c == ':', "-")
}
}
pub fn dc_derive_safe_stem_ext(filename: &str) -> (String, String) {
let basename = get_safe_basename(&filename);
let (mut stem, mut ext) = if let Some(index) = basename.rfind('.') {
(
basename[0..index].to_string(),
basename[index..].to_string(),
)
} else {
(basename, "".to_string())
};
// limit length of stem and ext
stem.truncate(32);
ext.truncate(32);
(stem, ext)
}
// the returned suffix is lower-case
@@ -482,10 +436,14 @@ pub(crate) fn dc_delete_file(context: &Context, path: impl AsRef<std::path::Path
return false;
}
let dpath = format!("{}", path.as_ref().to_string_lossy());
match fs::remove_file(path_abs) {
Ok(_) => true,
Ok(_) => {
context.call_cb(Event::DeletedBlobFile(dpath));
true
}
Err(_err) => {
warn!(context, "Cannot delete \"{}\".", path.as_ref().display());
warn!(context, "Cannot delete \"{}\".", dpath);
false
}
}
@@ -566,49 +524,43 @@ pub fn dc_read_file<P: AsRef<std::path::Path>>(
}
}
pub(crate) fn dc_get_fine_path_filename(
pub fn dc_open_file<P: AsRef<std::path::Path>>(
context: &Context,
folder: impl AsRef<Path>,
desired_filename_suffix: impl AsRef<str>,
) -> PathBuf {
let now = time();
path: P,
) -> Result<std::fs::File, Error> {
let path_abs = dc_get_abs_path(context, &path);
let folder = PathBuf::from(folder.as_ref());
// XXX sanitize desired_filename eg using
// https://github.com/kardeiz/sanitize-filename/blob/master/src/lib.rs
let suffix = validate_filename(desired_filename_suffix.as_ref());
let file_name = PathBuf::from(suffix);
let extension = file_name.extension().map(|c| c.clone());
for i in 0..100_000 {
let ret = if i == 0 {
let mut folder = folder.clone();
folder.push(&file_name);
folder
} else {
let idx = if i < 100 { i } else { now + i };
let file_name = if let Some(stem) = file_name.file_stem() {
let mut stem = stem.to_os_string();
stem.push(format!("-{}", idx));
stem
} else {
OsString::from(idx.to_string())
};
let mut folder = folder.clone();
folder.push(file_name);
if let Some(ext) = extension {
folder.set_extension(&ext);
}
folder
};
if !dc_file_exist(context, &ret) {
// fine filename found
return ret;
match fs::File::open(&path_abs) {
Ok(bytes) => Ok(bytes),
Err(err) => {
warn!(
context,
"Cannot read \"{}\" or file is empty.",
path.as_ref().display()
);
Err(err.into())
}
}
}
panic!("Something is really wrong, you need to clean up your disk");
pub(crate) fn dc_get_next_backup_path(
folder: impl AsRef<Path>,
backup_time: i64,
) -> Result<PathBuf, Error> {
let folder = PathBuf::from(folder.as_ref());
let stem = chrono::NaiveDateTime::from_timestamp(backup_time, 0)
.format("delta-chat-%Y-%m-%d")
.to_string();
// 64 backup files per day should be enough for everyone
for i in 0..64 {
let mut path = folder.clone();
path.push(format!("{}-{}.bak", stem, i));
if !path.exists() {
return Ok(path);
}
}
bail!("could not create backup file, disk full?");
}
pub(crate) fn dc_is_blobdir_path(context: &Context, path: impl AsRef<str>) -> bool {
@@ -627,7 +579,10 @@ fn dc_make_rel_path(context: &Context, path: &mut String) {
.map(|s| path.starts_with(s))
.unwrap_or_default()
{
*path = path.replace(context.get_blobdir().to_str().unwrap(), "$BLOBDIR");
*path = path.replace(
context.get_blobdir().to_str().unwrap_or_default(),
"$BLOBDIR",
);
}
}
@@ -636,13 +591,10 @@ pub(crate) fn dc_make_rel_and_copy(context: &Context, path: &mut String) -> bool
dc_make_rel_path(context, path);
return true;
}
let blobdir_path = dc_get_fine_path_filename(context, "$BLOBDIR", &path);
if dc_copy_file(context, &path, &blobdir_path) {
*path = blobdir_path.to_string_lossy().to_string();
dc_make_rel_path(context, path);
if let Ok(blobdir_path) = context.copy_to_blobdir(&path) {
*path = blobdir_path;
return true;
}
false
}
@@ -791,22 +743,6 @@ impl<T: AsRef<str>> StrExt for T {
}
}
pub fn to_string(s: *const libc::c_char) -> String {
if s.is_null() {
return "".into();
}
let cstr = unsafe { CStr::from_ptr(s) };
cstr.to_str().map(|s| s.to_string()).unwrap_or_else(|err| {
panic!(
"Non utf8 string: '{:?}' ({:?})",
cstr.to_string_lossy(),
err
);
})
}
pub fn to_string_lossy(s: *const libc::c_char) -> String {
if s.is_null() {
return "".into();
@@ -814,9 +750,7 @@ pub fn to_string_lossy(s: *const libc::c_char) -> String {
let cstr = unsafe { CStr::from_ptr(s) };
cstr.to_str()
.map(|s| s.to_string())
.unwrap_or_else(|_| cstr.to_string_lossy().to_string())
cstr.to_string_lossy().to_string()
}
pub fn as_str<'a>(s: *const libc::c_char) -> &'a str {
@@ -1021,54 +955,6 @@ mod tests {
}
}
#[test]
fn test_dc_ltrim() {
unsafe {
let html: *const libc::c_char =
b"\r\r\nline1<br>\r\n\r\n\r\rline2\n\r\x00" as *const u8 as *const libc::c_char;
let out: *mut libc::c_char = strndup(html, strlen(html) as libc::c_ulong);
dc_ltrim(out);
assert_eq!(
CStr::from_ptr(out as *const libc::c_char).to_str().unwrap(),
"line1<br>\r\n\r\n\r\rline2\n\r"
);
}
}
#[test]
fn test_dc_rtrim() {
unsafe {
let html: *const libc::c_char =
b"\r\r\nline1<br>\r\n\r\n\r\rline2\n\r\x00" as *const u8 as *const libc::c_char;
let out: *mut libc::c_char = strndup(html, strlen(html) as libc::c_ulong);
dc_rtrim(out);
assert_eq!(
CStr::from_ptr(out as *const libc::c_char).to_str().unwrap(),
"\r\r\nline1<br>\r\n\r\n\r\rline2"
);
}
}
#[test]
fn test_dc_trim() {
unsafe {
let html: *const libc::c_char =
b"\r\r\nline1<br>\r\n\r\n\r\rline2\n\r\x00" as *const u8 as *const libc::c_char;
let out: *mut libc::c_char = strndup(html, strlen(html) as libc::c_ulong);
dc_trim(out);
assert_eq!(
CStr::from_ptr(out as *const libc::c_char).to_str().unwrap(),
"line1<br>\r\n\r\n\r\rline2"
);
}
}
#[test]
fn test_rust_ftoa() {
assert_eq!("1.22", format!("{}", 1.22));
@@ -1345,6 +1231,25 @@ mod tests {
assert_eq!(grpid, Some("1234567890123456"));
}
#[test]
fn test_dc_create_outgoing_rfc724_mid() {
// create a normal message-id
let mid = dc_create_outgoing_rfc724_mid(None, "foo@bar.de");
assert!(mid.starts_with("Mr."));
assert!(mid.ends_with("bar.de"));
assert!(dc_extract_grpid_from_rfc724_mid(mid.as_str()).is_none());
// create a message-id containing a group-id
let grpid = dc_create_id();
let mid = dc_create_outgoing_rfc724_mid(Some(&grpid), "foo@bar.de");
assert!(mid.starts_with("Gr."));
assert!(mid.ends_with("bar.de"));
assert_eq!(
dc_extract_grpid_from_rfc724_mid(mid.as_str()),
Some(grpid.as_str())
);
}
#[test]
fn test_emailaddress_parse() {
assert_eq!(EmailAddress::new("").is_ok(), false);
@@ -1429,14 +1334,27 @@ mod tests {
unsafe {
let res = strndup(b"helloworld\x00" as *const u8 as *const libc::c_char, 4);
assert_eq!(
to_string(res),
to_string(b"hell\x00" as *const u8 as *const libc::c_char)
to_string_lossy(res),
to_string_lossy(b"hell\x00" as *const u8 as *const libc::c_char)
);
assert_eq!(strlen(res), 4);
free(res as *mut _);
}
}
#[test]
fn test_file_get_safe_basename() {
assert_eq!(get_safe_basename("12312/hello"), "hello");
assert_eq!(get_safe_basename("12312\\hello"), "hello");
assert_eq!(get_safe_basename("//12312\\hello"), "hello");
assert_eq!(get_safe_basename("//123:12\\hello"), "hello");
assert_eq!(get_safe_basename("//123:12/\\\\hello"), "hello");
assert_eq!(get_safe_basename("//123:12//hello"), "hello");
assert_eq!(get_safe_basename("//123:12//"), "nobasename");
assert_eq!(get_safe_basename("//123:12/"), "nobasename");
assert!(get_safe_basename("123\x012.hello").ends_with(".hello"));
}
#[test]
fn test_file_handling() {
let t = dummy_context();
@@ -1471,6 +1389,7 @@ mod tests {
assert!(dc_file_exist(context, &abs_path));
assert!(dc_copy_file(context, "$BLOBDIR/foobar", "$BLOBDIR/dada",));
assert_eq!(dc_get_filebytes(context, "$BLOBDIR/dada",), 7);
let buf = dc_read_file(context, "$BLOBDIR/dada").unwrap();
@@ -1483,14 +1402,12 @@ mod tests {
assert!(dc_create_folder(context, "$BLOBDIR/foobar-folder"));
assert!(dc_file_exist(context, "$BLOBDIR/foobar-folder",));
assert!(!dc_delete_file(context, "$BLOBDIR/foobar-folder"));
let fn0 = dc_get_fine_path_filename(context, "$BLOBDIR", "foobar.dadada");
assert_eq!(fn0, PathBuf::from("$BLOBDIR/foobar.dadada"));
let fn0 = "$BLOBDIR/data.data";
assert!(dc_write_file(context, &fn0, b"content"));
let fn1 = dc_get_fine_path_filename(context, "$BLOBDIR", "foobar.dadada");
assert_eq!(fn1, PathBuf::from("$BLOBDIR/foobar-1.dadada"));
assert!(dc_delete_file(context, &fn0));
assert!(!dc_file_exist(context, &fn0));
}
#[test]
@@ -1506,19 +1423,4 @@ mod tests {
let listflags: u32 = DC_GCL_VERIFIED_ONLY.try_into().unwrap();
assert!(listflags_has(listflags, DC_GCL_ADD_SELF) == false);
}
#[test]
fn test_dc_remove_cr_chars() {
unsafe {
let input = "foo\r\nbar".strdup();
dc_remove_cr_chars(input);
assert_eq!("foo\nbar", to_string(input));
free(input.cast());
let input = "\rfoo\r\rbar\r".strdup();
dc_remove_cr_chars(input);
assert_eq!("foobar", to_string(input));
free(input.cast());
}
}
}

View File

@@ -22,14 +22,13 @@ use num_traits::FromPrimitive;
use crate::aheader::*;
use crate::config::Config;
use crate::context::Context;
use crate::dc_mimeparser::*;
use crate::dc_tools::*;
use crate::error::*;
use crate::key::*;
use crate::keyring::*;
use crate::mimefactory::MimeFactory;
use crate::peerstate::*;
use crate::pgp::*;
use crate::pgp;
use crate::securejoin::handle_degrade_event;
use crate::wrapmime;
use crate::wrapmime::*;
@@ -47,12 +46,9 @@ pub struct EncryptHelper {
impl EncryptHelper {
pub fn new(context: &Context) -> Result<EncryptHelper> {
let prefer_encrypt = context
.sql
.get_config_int(&context, "e2ee_enabled")
.and_then(EncryptPreference::from_i32)
.unwrap_or_default();
let prefer_encrypt =
EncryptPreference::from_i32(context.get_config_int(Config::E2eeEnabled))
.unwrap_or_default();
let addr = match context.get_config(Config::ConfiguredAddr) {
None => {
bail!("addr not configured!");
@@ -215,7 +211,7 @@ impl EncryptHelper {
"could not write/allocate"
);
let ctext = dc_pgp_pk_encrypt(
let ctext = pgp::pk_encrypt(
std::slice::from_raw_parts((*plain).str_0 as *const u8, (*plain).len),
&keyring,
sign_key.as_ref(),
@@ -266,103 +262,80 @@ pub fn try_decrypt(
context: &Context,
in_out_message: *mut Mailmime,
) -> Result<(bool, HashSet<String>, HashSet<String>)> {
// just a pointer into mailmime structure, must not be freed
let imffields = mailmime_find_mailimf_fields(in_out_message);
ensure!(
!in_out_message.is_null() && !imffields.is_null(),
"corrupt invalid mime inputs"
);
let from = wrapmime::get_field_from(imffields)?;
let message_time = wrapmime::get_field_date(imffields)?;
let mut peerstate = None;
let autocryptheader = Aheader::from_imffields(&from, imffields);
if message_time > 0 {
peerstate = Peerstate::from_addr(context, &context.sql, &from);
if let Some(ref mut peerstate) = peerstate {
if let Some(ref header) = autocryptheader {
peerstate.apply_header(&header, message_time);
peerstate.save_to_db(&context.sql, false)?;
} else if message_time > peerstate.last_seen_autocrypt
&& !contains_report(in_out_message)
{
peerstate.degrade_encryption(message_time);
peerstate.save_to_db(&context.sql, false)?;
}
} else if let Some(ref header) = autocryptheader {
let p = Peerstate::from_header(context, header, message_time);
p.save_to_db(&context.sql, true)?;
peerstate = Some(p);
}
}
/* possibly perform decryption */
let mut private_keyring = Keyring::default();
let mut public_keyring_for_validate = Keyring::default();
let mut encrypted = false;
let mut signatures = HashSet::default();
let mut gossipped_addr = HashSet::default();
// just a pointer into mailmime structure, must not be freed
let imffields = unsafe { mailmime_find_mailimf_fields(in_out_message) };
let mut message_time = 0;
let mut from = None;
let mut private_keyring = Keyring::default();
let mut public_keyring_for_validate = Keyring::default();
let mut gossip_headers = ptr::null_mut();
let self_addr = context.get_config(Config::ConfiguredAddr);
// XXX do wrapmime:: helper for the next block
if !(in_out_message.is_null() || imffields.is_null()) {
let mut field = mailimf_find_field(imffields, MAILIMF_FIELD_FROM as libc::c_int);
if !field.is_null() && unsafe { !(*field).fld_data.fld_from.is_null() } {
let mb_list = unsafe { (*(*field).fld_data.fld_from).frm_mb_list };
from = mailimf_find_first_addr(mb_list);
}
field = mailimf_find_field(imffields, MAILIMF_FIELD_ORIG_DATE as libc::c_int);
if !field.is_null() && unsafe { !(*field).fld_data.fld_orig_date.is_null() } {
let orig_date = unsafe { (*field).fld_data.fld_orig_date };
if !orig_date.is_null() {
let dt = unsafe { (*orig_date).dt_date_time };
message_time = dc_timestamp_from_date(dt);
if message_time != 0 && message_time > time() {
message_time = time()
if let Some(self_addr) = self_addr {
if private_keyring.load_self_private_for_decrypting(context, self_addr, &context.sql) {
if peerstate.as_ref().map(|p| p.last_seen).unwrap_or_else(|| 0) == 0 {
peerstate = Peerstate::from_addr(&context, &context.sql, &from);
}
if let Some(ref peerstate) = peerstate {
if peerstate.degrade_event.is_some() {
handle_degrade_event(context, &peerstate)?;
}
if let Some(ref key) = peerstate.gossip_key {
public_keyring_for_validate.add_ref(key);
}
if let Some(ref key) = peerstate.public_key {
public_keyring_for_validate.add_ref(key);
}
}
}
let mut peerstate = None;
let autocryptheader = from
.as_ref()
.and_then(|from| Aheader::from_imffields(from, imffields));
if message_time > 0 {
if let Some(ref from) = from {
peerstate = Peerstate::from_addr(context, &context.sql, from);
if let Some(ref mut peerstate) = peerstate {
if let Some(ref header) = autocryptheader {
peerstate.apply_header(&header, message_time);
peerstate.save_to_db(&context.sql, false).unwrap();
} else if message_time > peerstate.last_seen_autocrypt
&& !contains_report(in_out_message)
{
peerstate.degrade_encryption(message_time);
peerstate.save_to_db(&context.sql, false).unwrap();
}
} else if let Some(ref header) = autocryptheader {
let p = Peerstate::from_header(context, header, message_time);
p.save_to_db(&context.sql, true).unwrap();
peerstate = Some(p);
}
}
}
/* load private key for decryption */
let self_addr = context.get_config(Config::ConfiguredAddr);
if let Some(self_addr) = self_addr {
if private_keyring.load_self_private_for_decrypting(context, self_addr, &context.sql) {
if peerstate.as_ref().map(|p| p.last_seen).unwrap_or_else(|| 0) == 0 {
peerstate =
Peerstate::from_addr(&context, &context.sql, &from.unwrap_or_default());
}
if let Some(ref peerstate) = peerstate {
if peerstate.degrade_event.is_some() {
handle_degrade_event(context, &peerstate)?;
}
if let Some(ref key) = peerstate.gossip_key {
public_keyring_for_validate.add_ref(key);
}
if let Some(ref key) = peerstate.public_key {
public_keyring_for_validate.add_ref(key);
}
}
encrypted = decrypt_if_autocrypt_message(
context,
in_out_message,
&private_keyring,
&public_keyring_for_validate,
&mut signatures,
&mut gossip_headers,
)?;
if !gossip_headers.is_null() {
gossipped_addr =
update_gossip_peerstates(context, message_time, imffields, gossip_headers)?;
}
let mut gossip_headers = ptr::null_mut();
encrypted = decrypt_if_autocrypt_message(
context,
in_out_message,
&private_keyring,
&public_keyring_for_validate,
&mut signatures,
&mut gossip_headers,
)?;
if !gossip_headers.is_null() {
gossipped_addr =
update_gossip_peerstates(context, message_time, imffields, gossip_headers)?;
unsafe { mailimf_fields_free(gossip_headers) };
}
}
}
if !gossip_headers.is_null() {
unsafe { mailimf_fields_free(gossip_headers) };
}
Ok((encrypted, signatures, gossipped_addr))
}
@@ -425,7 +398,7 @@ fn load_or_generate_self_public_key(context: &Context, self_addr: impl AsRef<str
context,
"Generating keypair with {} bits, e={} ...", 2048, 65537,
);
match dc_pgp_create_keypair(&self_addr) {
match pgp::create_keypair(&self_addr) {
Some((public_key, private_key)) => {
match dc_key_save_self_keypair(
context,
@@ -560,7 +533,7 @@ fn decrypt_if_autocrypt_message(
// XXX better return parsed headers so that upstream
// does not need to dive into mmime-stuff again.
unsafe {
if (*ret_gossip_headers).is_null() && ret_valid_signatures.len() > 0 {
if (*ret_gossip_headers).is_null() && !ret_valid_signatures.is_empty() {
let mut dummy: libc::size_t = 0;
let mut test: *mut mailimf_fields = ptr::null_mut();
if mailimf_envelope_and_optional_fields_parse(
@@ -600,22 +573,16 @@ fn decrypt_part(
mime_transfer_encoding = enc;
}
let (decoded_data, decoded_data_bytes) =
wrapmime::decode_dt_data(mime_data, mime_transfer_encoding)?;
let data: Vec<u8> = wrapmime::decode_dt_data(mime_data, mime_transfer_encoding)?;
// encrypted, non-NULL decoded data in decoded_data now ...
// Note that we need to take care of freeing decoded_data ourself,
// after encryption has been attempted.
let mut ret_decrypted_mime = ptr::null_mut();
ensure!(!decoded_data.is_null(), "Missing data");
let data = unsafe { std::slice::from_raw_parts(decoded_data as *const u8, decoded_data_bytes) };
if has_decrypted_pgp_armor(data) {
if has_decrypted_pgp_armor(&data) {
// we should only have one decryption happening
ensure!(ret_valid_signatures.is_empty(), "corrupt signatures");
let plain = match dc_pgp_pk_decrypt(
data,
let plain = match pgp::pk_decrypt(
&data,
&private_keyring,
&public_keyring_for_validate,
Some(ret_valid_signatures),
@@ -624,10 +591,7 @@ fn decrypt_part(
ensure!(!ret_valid_signatures.is_empty(), "no valid signatures");
plain
}
Err(err) => {
unsafe { mmap_string_unref(decoded_data) };
bail!("could not decrypt: {}", err)
}
Err(err) => bail!("could not decrypt: {}", err),
};
let plain_bytes = plain.len();
let plain_buf = plain.as_ptr() as *const libc::c_char;
@@ -655,7 +619,6 @@ fn decrypt_part(
ret_decrypted_mime = decrypted_mime;
}
}
unsafe { mmap_string_unref(decoded_data) };
Ok(ret_decrypted_mime)
}
@@ -723,12 +686,12 @@ fn contains_report(mime: *mut Mailmime) -> bool {
/// If this succeeds you are also guaranteed that the
/// [Config::ConfiguredAddr] is configured, this address is returned.
pub fn ensure_secret_key_exists(context: &Context) -> Result<String> {
let self_addr = context
.get_config(Config::ConfiguredAddr)
.ok_or(format_err!(concat!(
let self_addr = context.get_config(Config::ConfiguredAddr).ok_or_else(|| {
format_err!(concat!(
"Failed to get self address, ",
"cannot ensure secret key if not configured."
)))?;
))
})?;
load_or_generate_self_public_key(context, &self_addr)?;
Ok(self_addr)
}

View File

@@ -70,12 +70,6 @@ impl From<std::str::Utf8Error> for Error {
}
}
impl From<std::string::FromUtf8Error> for Error {
fn from(err: std::string::FromUtf8Error) -> Error {
Error::FromUtf8(err)
}
}
impl From<image_meta::ImageError> for Error {
fn from(err: image_meta::ImageError) -> Error {
Error::Image(err)
@@ -94,6 +88,12 @@ impl From<pgp::errors::Error> for Error {
}
}
impl From<std::string::FromUtf8Error> for Error {
fn from(err: std::string::FromUtf8Error) -> Error {
Error::FromUtf8(err)
}
}
#[macro_export]
macro_rules! bail {
($e:expr) => {

View File

@@ -2,8 +2,6 @@ use std::path::PathBuf;
use strum::EnumProperty;
use crate::stock::StockMessage;
impl Event {
/// Returns the corresponding Event id.
pub fn as_id(&self) -> i32 {
@@ -42,6 +40,30 @@ pub enum Event {
#[strum(props(id = "103"))]
SmtpMessageSent(String),
/// Emitted when an IMAP message has been marked as deleted
///
/// @return 0
#[strum(props(id = "104"))]
ImapMessageDeleted(String),
/// Emitted when an IMAP message has been moved
///
/// @return 0
#[strum(props(id = "105"))]
ImapMessageMoved(String),
/// Emitted when an new file in the $BLOBDIR was created
///
/// @return 0
#[strum(props(id = "150"))]
NewBlobFile(String),
/// Emitted when an new file in the $BLOBDIR was created
///
/// @return 0
#[strum(props(id = "151"))]
DeletedBlobFile(String),
/// The library-user should write a warning string to the log.
/// Passed to the callback given to dc_context_new().
///
@@ -213,17 +235,4 @@ pub enum Event {
/// @return 0
#[strum(props(id = "2061"))]
SecurejoinJoinerProgress { contact_id: u32, progress: usize },
// the following events are functions that should be provided by the frontends
/// Requeste a localized string from the frontend.
/// @param data1 (int) ID of the string to request, one of the DC_STR_/// constants.
/// @param data2 (int) The count. If the requested string contains a placeholder for a numeric value,
/// the ui may use this value to return different strings on different plural forms.
/// @return (const char*) Null-terminated UTF-8 string.
/// The string will be free()'d by the core,
/// so it must be allocated using malloc() or a compatible function.
/// Return 0 if the ui cannot provide the requested string
/// the core will use a default string in english language then.
#[strum(props(id = "2091"))]
GetString { id: StockMessage, count: usize },
}

View File

@@ -8,12 +8,14 @@ use std::time::{Duration, SystemTime};
use crate::constants::*;
use crate::context::Context;
use crate::dc_receive_imf::dc_receive_imf;
use crate::error::Error;
use crate::events::Event;
use crate::job::{job_add, Action};
use crate::login_param::LoginParam;
use crate::job::{connect_to_inbox, job_add, Action};
use crate::login_param::{dc_build_tls, CertificateChecks, LoginParam};
use crate::message::{self, update_msg_move_state, update_server_uid};
use crate::oauth2::dc_get_oauth2_access_token;
use crate::param::Params;
use crate::wrapmime;
const DC_IMAP_SEEN: usize = 0x0001;
@@ -27,7 +29,6 @@ pub enum ImapResult {
const PREFETCH_FLAGS: &str = "(UID ENVELOPE)";
const BODY_FLAGS: &str = "(FLAGS BODY.PEEK[])";
const FETCH_FLAGS: &str = "(FLAGS)";
#[derive(Debug)]
pub struct Imap {
@@ -107,14 +108,10 @@ impl Client {
pub fn connect_secure<A: net::ToSocketAddrs, S: AsRef<str>>(
addr: A,
domain: S,
certificate_checks: CertificateChecks,
) -> imap::error::Result<Self> {
let stream = net::TcpStream::connect(addr)?;
let tls = native_tls::TlsConnector::builder()
// see also: https://github.com/deltachat/deltachat-core-rust/issues/203
.danger_accept_invalid_certs(true)
.danger_accept_invalid_hostnames(true)
.build()
.unwrap();
let tls = dc_build_tls(certificate_checks).unwrap();
let s = stream.try_clone().expect("cloning the stream failed");
let tls_stream = native_tls::TlsConnector::connect(&tls, domain.as_ref(), s)?;
@@ -134,13 +131,14 @@ impl Client {
Ok(Client::Insecure(client, stream))
}
pub fn secure<S: AsRef<str>>(self, domain: S) -> imap::error::Result<Client> {
pub fn secure<S: AsRef<str>>(
self,
domain: S,
certificate_checks: CertificateChecks,
) -> imap::error::Result<Client> {
match self {
Client::Insecure(client, stream) => {
let tls = native_tls::TlsConnector::builder()
.danger_accept_invalid_hostnames(true)
.build()
.unwrap();
let tls = dc_build_tls(certificate_checks).unwrap();
let client_sec = client.secure(domain, &tls)?;
@@ -320,6 +318,7 @@ struct ImapConfig {
pub imap_port: u16,
pub imap_user: String,
pub imap_pw: String,
pub certificate_checks: CertificateChecks,
pub server_flags: usize,
pub selected_folder: Option<String>,
pub selected_mailbox: Option<imap::types::Mailbox>,
@@ -332,12 +331,13 @@ struct ImapConfig {
impl Default for ImapConfig {
fn default() -> Self {
let cfg = ImapConfig {
ImapConfig {
addr: "".into(),
imap_server: "".into(),
imap_port: 0,
imap_user: "".into(),
imap_pw: "".into(),
certificate_checks: Default::default(),
server_flags: 0,
selected_folder: None,
selected_mailbox: None,
@@ -346,9 +346,7 @@ impl Default for ImapConfig {
has_xlist: false,
imap_delimiter: '.',
watch_folder: None,
};
cfg
}
}
}
@@ -386,7 +384,7 @@ impl Imap {
return true;
}
let server_flags = self.config.read().unwrap().server_flags;
let server_flags = self.config.read().unwrap().server_flags as i32;
let connection_res: imap::error::Result<Client> =
if (server_flags & (DC_LP_IMAP_SOCKET_STARTTLS | DC_LP_IMAP_SOCKET_PLAIN)) != 0 {
@@ -396,7 +394,7 @@ impl Imap {
Client::connect_insecure((imap_server, imap_port)).and_then(|client| {
if (server_flags & DC_LP_IMAP_SOCKET_STARTTLS) != 0 {
client.secure(imap_server)
client.secure(imap_server, config.certificate_checks)
} else {
Ok(client)
}
@@ -406,7 +404,11 @@ impl Imap {
let imap_server: &str = config.imap_server.as_ref();
let imap_port = config.imap_port;
Client::connect_secure((imap_server, imap_port), imap_server)
Client::connect_secure(
(imap_server, imap_port),
imap_server,
config.certificate_checks,
)
};
let login_res = match connection_res {
@@ -533,6 +535,7 @@ impl Imap {
config.imap_port = imap_port;
config.imap_user = imap_user.to_string();
config.imap_pw = imap_pw.to_string();
config.certificate_checks = lp.imap_certificate_checks;
config.server_flags = server_flags;
}
@@ -681,7 +684,7 @@ impl Imap {
}
}
} else {
return 0;
unreachable!();
}
}
@@ -690,12 +693,20 @@ impl Imap {
fn get_config_last_seen_uid<S: AsRef<str>>(&self, context: &Context, folder: S) -> (u32, u32) {
let key = format!("imap.mailbox.{}", folder.as_ref());
if let Some(entry) = context.sql.get_config(context, &key) {
if let Some(entry) = context.sql.get_raw_config(context, &key) {
// the entry has the format `imap.mailbox.<folder>=<uidvalidity>:<lastseenuid>`
let mut parts = entry.split(':');
(
parts.next().unwrap().parse().unwrap_or_else(|_| 0),
parts.next().unwrap().parse().unwrap_or_else(|_| 0),
parts
.next()
.unwrap_or_default()
.parse()
.unwrap_or_else(|_| 0),
parts
.next()
.unwrap_or_default()
.parse()
.unwrap_or_else(|_| 0),
)
} else {
(0, 0)
@@ -739,7 +750,7 @@ impl Imap {
return 0;
}
if mailbox.uid_validity.unwrap() != uid_validity {
if mailbox.uid_validity.unwrap_or_default() != uid_validity {
// first time this folder is selected or UIDVALIDITY has changed, init lastseenuid and save it to config
if mailbox.exists == 0 {
@@ -749,7 +760,12 @@ impl Imap {
// id we do not do this here, we'll miss the first message
// as we will get in here again and fetch from lastseenuid+1 then
self.set_config_last_seen_uid(context, &folder, mailbox.uid_validity.unwrap(), 0);
self.set_config_last_seen_uid(
context,
&folder,
mailbox.uid_validity.unwrap_or_default(),
0,
);
return 0;
}
@@ -780,7 +796,7 @@ impl Imap {
last_seen_uid -= 1;
}
uid_validity = mailbox.uid_validity.unwrap();
uid_validity = mailbox.uid_validity.unwrap_or_default();
self.set_config_last_seen_uid(context, &folder, uid_validity, last_seen_uid);
info!(
context,
@@ -816,11 +832,7 @@ impl Imap {
if cur_uid > last_seen_uid {
read_cnt += 1;
let message_id = msg
.envelope()
.expect("missing envelope")
.message_id
.expect("missing message id");
let message_id = prefetch_get_message_id(msg).unwrap_or_default();
if !precheck_imf(context, &message_id, folder.as_ref(), cur_uid) {
// check passed, go fetch the rest
@@ -885,7 +897,7 @@ impl Imap {
let key = format!("imap.mailbox.{}", folder.as_ref());
let val = format!("{}:{}", uidvalidity, lastseenuid);
context.sql.set_config(context, &key, Some(&val)).ok();
context.sql.set_raw_config(context, &key, Some(&val)).ok();
}
fn fetch_single_msg<S: AsRef<str>>(
@@ -933,27 +945,20 @@ impl Imap {
} else {
let msg = &msgs[0];
let is_deleted = msg
.flags()
.iter()
.find(|flag| match flag {
imap::types::Flag::Deleted => true,
_ => false,
})
.is_some();
let is_seen = msg
.flags()
.iter()
.find(|flag| match flag {
imap::types::Flag::Seen => true,
_ => false,
})
.is_some();
// XXX put flags into a set and pass them to dc_receive_imf
let is_deleted = msg.flags().iter().any(|flag| match flag {
imap::types::Flag::Deleted => true,
_ => false,
});
let is_seen = msg.flags().iter().any(|flag| match flag {
imap::types::Flag::Seen => true,
_ => false,
});
let flags = if is_seen { DC_IMAP_SEEN } else { 0 };
if !is_deleted && msg.body().is_some() {
let body = msg.body().unwrap();
let body = msg.body().unwrap_or_default();
unsafe {
dc_receive_imf(context, &body, folder.as_ref(), server_uid, flags as u32);
}
@@ -1064,13 +1069,14 @@ impl Imap {
let mut do_fake_idle = true;
while do_fake_idle {
// wait a moment: every 5 seconds in the first 3 minutes after a new message, after that every 60 seconds.
let seconds_to_wait =
if fake_idle_start_time.elapsed().unwrap() < Duration::new(3 * 60, 0) && !wait_long
{
Duration::new(5, 0)
} else {
Duration::new(60, 0)
};
let seconds_to_wait = if fake_idle_start_time.elapsed().unwrap_or_default()
< Duration::new(3 * 60, 0)
&& !wait_long
{
Duration::new(5, 0)
} else {
Duration::new(60, 0)
};
let &(ref lock, ref cvar) = &*self.watch.clone();
let mut watch = lock.lock().unwrap();
@@ -1119,117 +1125,94 @@ impl Imap {
cvar.notify_one();
}
pub fn mv<S1: AsRef<str>, S2: AsRef<str>>(
pub fn mv(
&self,
context: &Context,
folder: S1,
folder: &str,
uid: u32,
dest_folder: S2,
dest_folder: &str,
dest_uid: &mut u32,
) -> ImapResult {
let mut res = ImapResult::RetryLater;
if folder == dest_folder {
info!(
context,
"Skip moving message; message {}/{} is already in {}...", folder, uid, dest_folder,
);
return ImapResult::AlreadyDone;
}
if let Some(imapresult) = self.prepare_imap_operation_on_msg(context, folder, uid) {
return imapresult;
}
// we are connected, and the folder is selected
// XXX Rust-Imap provides no target uid on mv, so just set it to 0
*dest_uid = 0;
let set = format!("{}", uid);
if uid == 0 {
res = ImapResult::Failed;
} else if folder.as_ref() == dest_folder.as_ref() {
info!(
context,
"Skip moving message; message {}/{} is already in {}...",
folder.as_ref(),
uid,
dest_folder.as_ref()
);
res = ImapResult::AlreadyDone;
} else {
info!(
context,
"Moving message {}/{} to {}...",
folder.as_ref(),
uid,
dest_folder.as_ref()
);
if self.select_folder(context, Some(folder.as_ref())) == 0 {
warn!(
context,
"Cannot select folder {} for moving message.",
folder.as_ref()
);
} else {
let moved = if let Some(ref mut session) = &mut *self.session.lock().unwrap() {
match session.uid_mv(&set, &dest_folder) {
Ok(_) => {
res = ImapResult::Success;
true
}
Err(err) => {
info!(
context,
"Cannot move message, fallback to COPY/DELETE {}/{} to {}: {}",
folder.as_ref(),
uid,
dest_folder.as_ref(),
err
);
false
}
}
} else {
unreachable!();
};
if !moved {
let copied = if let Some(ref mut session) = &mut *self.session.lock().unwrap() {
match session.uid_copy(&set, &dest_folder) {
Ok(_) => true,
Err(err) => {
eprintln!("error copy: {:?}", err);
info!(context, "Cannot copy message.",);
false
}
}
} else {
unreachable!();
};
if copied {
if self.add_flag(context, uid, "\\Deleted") == 0 {
warn!(context, "Cannot mark message as \"Deleted\".",);
}
self.config.write().unwrap().selected_folder_needs_expunge = true;
res = ImapResult::Success;
}
let display_folder_id = format!("{}/{}", folder, uid);
if let Some(ref mut session) = &mut *self.session.lock().unwrap() {
match session.uid_mv(&set, &dest_folder) {
Ok(_) => {
emit_event!(
context,
Event::ImapMessageMoved(format!(
"IMAP Message {} moved to {}",
display_folder_id, dest_folder
))
);
return ImapResult::Success;
}
Err(err) => {
warn!(
context,
"Cannot move message, fallback to COPY/DELETE {}/{} to {}: {}",
folder,
uid,
dest_folder,
err
);
}
}
}
} else {
unreachable!();
};
if res == ImapResult::Success {
// TODO: is this correct?
*dest_uid = uid;
}
if res == ImapResult::RetryLater {
if self.should_reconnect() {
ImapResult::RetryLater
} else {
ImapResult::Failed
if let Some(ref mut session) = &mut *self.session.lock().unwrap() {
match session.uid_copy(&set, &dest_folder) {
Ok(_) => {
if !self.add_flag_finalized(context, uid, "\\Deleted") {
warn!(context, "Cannot mark {} as \"Deleted\" after copy.", uid);
ImapResult::Failed
} else {
self.config.write().unwrap().selected_folder_needs_expunge = true;
ImapResult::Success
}
}
Err(err) => {
warn!(context, "Could not copy message: {}", err);
ImapResult::Failed
}
}
} else {
res
unreachable!();
}
}
fn add_flag<S: AsRef<str>>(&self, context: &Context, server_uid: u32, flag: S) -> usize {
fn add_flag_finalized(&self, context: &Context, server_uid: u32, flag: &str) -> bool {
// return true if we successfully set the flag or we otherwise
// think add_flag should not be retried: Disconnection during setting
// the flag, or other imap-errors, returns true as well.
//
// returning false means that the operation can be retried.
if server_uid == 0 {
return 0;
return true; // might be moved but we don't want to have a stuck job
}
if self.should_reconnect() {
return false;
}
if let Some(ref mut session) = &mut *self.session.lock().unwrap() {
let set = format!("{}", server_uid);
let query = format!("+FLAGS ({})", flag.as_ref());
let query = format!("+FLAGS ({})", flag);
match session.uid_store(&set, &query) {
Ok(_) => {}
Err(err) => {
@@ -1239,243 +1222,126 @@ impl Imap {
);
}
}
}
// All non-connection states are treated as success - the mail may
// already be deleted or moved away on the server.
if self.should_reconnect() {
0
true // we tried once, that's probably enough for setting flag
} else {
1
unreachable!();
}
}
pub fn set_seen<S: AsRef<str>>(&self, context: &Context, folder: S, uid: u32) -> ImapResult {
let mut res = ImapResult::RetryLater;
pub fn prepare_imap_operation_on_msg(
&self,
context: &Context,
folder: &str,
uid: u32,
) -> Option<ImapResult> {
if uid == 0 {
res = ImapResult::Failed
} else if self.is_connected() {
info!(
context,
"Marking message {}/{} as seen...",
folder.as_ref(),
uid,
);
if self.select_folder(context, Some(folder.as_ref())) == 0 {
warn!(
context,
"Cannot select folder {} for setting SEEN flag.",
folder.as_ref(),
);
} else if self.add_flag(context, uid, "\\Seen") == 0 {
warn!(context, "Cannot mark message as seen.",);
} else {
res = ImapResult::Success
return Some(ImapResult::Failed);
} else if !self.is_connected() {
connect_to_inbox(context, &self);
if !self.is_connected() {
return Some(ImapResult::RetryLater);
}
}
if res == ImapResult::RetryLater {
if self.should_reconnect() {
ImapResult::RetryLater
} else {
ImapResult::Failed
}
if self.select_folder(context, Some(&folder)) == 0 {
warn!(
context,
"Cannot select folder {} for preparing IMAP operation", folder
);
Some(ImapResult::RetryLater)
} else {
res
None
}
}
pub fn set_mdnsent<S: AsRef<str>>(&self, context: &Context, folder: S, uid: u32) -> ImapResult {
// returns 0=job should be retried later, 1=job done, 2=job done and flag just set
let mut res = ImapResult::RetryLater;
let set = format!("{}", uid);
if uid == 0 {
res = ImapResult::Failed;
} else if self.is_connected() {
info!(
context,
"Marking message {}/{} as $MDNSent...",
folder.as_ref(),
uid,
);
if self.select_folder(context, Some(folder.as_ref())) == 0 {
warn!(
context,
"Cannot select folder {} for setting $MDNSent flag.",
folder.as_ref()
);
} else {
// Check if the folder can handle the `$MDNSent` flag (see RFC 3503). If so, and not
// set: set the flags and return this information.
// If the folder cannot handle the `$MDNSent` flag, we risk duplicated MDNs; it's up
// to the receiving MUA to handle this then (eg. Delta Chat has no problem with this).
let can_create_flag = self
.config
.read()
.unwrap()
.selected_mailbox
.as_ref()
.map(|mbox| {
// empty means, everything can be stored
mbox.permanent_flags.is_empty()
|| mbox
.permanent_flags
.iter()
.find(|flag| match flag {
imap::types::Flag::Custom(s) => s == "$MDNSent",
_ => false,
})
.is_some()
})
.expect("just selected folder");
if can_create_flag {
let fetched_msgs =
if let Some(ref mut session) = &mut *self.session.lock().unwrap() {
match session.uid_fetch(set, FETCH_FLAGS) {
Ok(res) => Some(res),
Err(err) => {
eprintln!("fetch error: {:?}", err);
None
}
}
} else {
unreachable!();
};
if let Some(msgs) = fetched_msgs {
let flag_set = msgs
.first()
.map(|msg| {
msg.flags()
.iter()
.find(|flag| match flag {
imap::types::Flag::Custom(s) => s == "$MDNSent",
_ => false,
})
.is_some()
})
.unwrap_or_else(|| false);
res = if flag_set {
ImapResult::AlreadyDone
} else if self.add_flag(context, uid, "$MDNSent") != 0 {
ImapResult::Success
} else {
res
};
if res == ImapResult::Success {
info!(context, "$MDNSent just set and MDN will be sent.");
} else {
info!(context, "$MDNSent already set and MDN already sent.");
}
}
} else {
res = ImapResult::Success;
info!(
context,
"Cannot store $MDNSent flags, risk sending duplicate MDN.",
);
}
}
pub fn set_seen(&self, context: &Context, folder: &str, uid: u32) -> ImapResult {
if let Some(imapresult) = self.prepare_imap_operation_on_msg(context, folder, uid) {
return imapresult;
}
// we are connected, and the folder is selected
info!(context, "Marking message {}/{} as seen...", folder, uid,);
if res == ImapResult::RetryLater {
if self.should_reconnect() {
ImapResult::RetryLater
} else {
ImapResult::Failed
}
if self.add_flag_finalized(context, uid, "\\Seen") {
ImapResult::Success
} else {
res
warn!(
context,
"Cannot mark message {} in folder {} as seen, ignoring.", uid, folder
);
ImapResult::Failed
}
}
// only returns 0 on connection problems; we should try later again in this case *
pub fn delete_msg<S1: AsRef<str>, S2: AsRef<str>>(
pub fn delete_msg(
&self,
context: &Context,
message_id: S1,
folder: S2,
server_uid: &mut u32,
) -> usize {
let mut success = false;
if *server_uid == 0 {
success = true
} else {
info!(
context,
"Marking message \"{}\", {}/{} for deletion...",
message_id.as_ref(),
folder.as_ref(),
server_uid,
);
message_id: &str,
folder: &str,
uid: &mut u32,
) -> ImapResult {
if let Some(imapresult) = self.prepare_imap_operation_on_msg(context, folder, *uid) {
return imapresult;
}
// we are connected, and the folder is selected
if self.select_folder(context, Some(&folder)) == 0 {
warn!(
context,
"Cannot select folder {} for deleting message.",
folder.as_ref()
);
} else {
let set = format!("{}", server_uid);
if let Some(ref mut session) = &mut *self.session.lock().unwrap() {
match session.uid_fetch(set, PREFETCH_FLAGS) {
Ok(msgs) => {
if msgs.is_empty()
|| msgs
.first()
.unwrap()
.envelope()
.expect("missing envelope")
.message_id
.expect("missing message id")
!= message_id.as_ref()
{
warn!(
context,
"Cannot delete on IMAP, {}/{} does not match {}.",
folder.as_ref(),
server_uid,
message_id.as_ref(),
);
*server_uid = 0;
}
}
Err(err) => {
eprintln!("fetch error: {:?}", err);
let set = format!("{}", uid);
let display_imap_id = format!("{}/{}", folder, uid);
warn!(
context,
"Cannot delete on IMAP, {}/{} not found.",
folder.as_ref(),
server_uid,
);
*server_uid = 0;
}
// double-check that we are deleting the correct message-id
// this comes at the expense of another imap query
if let Some(ref mut session) = &mut *self.session.lock().unwrap() {
match session.uid_fetch(set, PREFETCH_FLAGS) {
Ok(msgs) => {
if msgs.is_empty() {
warn!(
context,
"Cannot delete on IMAP, {}: imap entry gone '{}'",
display_imap_id,
message_id,
);
return ImapResult::Failed;
}
}
let remote_message_id =
prefetch_get_message_id(msgs.first().unwrap()).unwrap_or_default();
// mark the message for deletion
if self.add_flag(context, *server_uid, "\\Deleted") == 0 {
warn!(context, "Cannot mark message as \"Deleted\".");
} else {
self.config.write().unwrap().selected_folder_needs_expunge = true;
success = true
if remote_message_id != message_id {
warn!(
context,
"Cannot delete on IMAP, {}: remote message-id '{}' != '{}'",
display_imap_id,
remote_message_id,
message_id,
);
}
*uid = 0;
}
Err(err) => {
warn!(
context,
"Cannot delete {} on IMAP: {}", display_imap_id, err
);
*uid = 0;
}
}
}
if success {
1
// mark the message for deletion
if !self.add_flag_finalized(context, *uid, "\\Deleted") {
warn!(
context,
"Cannot mark message {} as \"Deleted\".", display_imap_id
);
ImapResult::Failed
} else {
self.is_connected() as usize
emit_event!(
context,
Event::ImapMessageDeleted(format!(
"IMAP Message {} marked as deleted [{}]",
display_imap_id, message_id
))
);
self.config.write().unwrap().selected_folder_needs_expunge = true;
ImapResult::Success
}
}
@@ -1544,18 +1410,18 @@ impl Imap {
context
.sql
.set_config_int(context, "folders_configured", 3)
.set_raw_config_int(context, "folders_configured", 3)
.ok();
if let Some(ref mvbox_folder) = mvbox_folder {
context
.sql
.set_config(context, "configured_mvbox_folder", Some(mvbox_folder))
.set_raw_config(context, "configured_mvbox_folder", Some(mvbox_folder))
.ok();
}
if let Some(ref sentbox_folder) = sentbox_folder {
context
.sql
.set_config(
.set_raw_config(
context,
"configured_sentbox_folder",
Some(sentbox_folder.name()),
@@ -1635,27 +1501,11 @@ fn get_folder_meaning(folder_name: &imap::types::Name) -> FolderMeaning {
}
fn precheck_imf(context: &Context, rfc724_mid: &str, server_folder: &str, server_uid: u32) -> bool {
let mut rfc724_mid_exists = false;
let mut mark_seen = false;
if let Ok((old_server_folder, old_server_uid, msg_id)) =
message::rfc724_mid_exists(context, &rfc724_mid)
{
rfc724_mid_exists = true;
if old_server_folder.is_empty() && old_server_uid == 0 {
info!(context, "[move] detected bbc-self {}", rfc724_mid,);
mark_seen = true;
} else if old_server_folder != server_folder {
info!(context, "[move] detected moved message {}", rfc724_mid,);
update_msg_move_state(context, &rfc724_mid, MoveState::Stay);
}
if old_server_folder != server_folder || old_server_uid != server_uid {
update_server_uid(context, &rfc724_mid, server_folder, server_uid);
}
context.do_heuristics_moves(server_folder, msg_id);
if mark_seen {
job_add(
context,
Action::MarkseenMsgOnImap,
@@ -1663,8 +1513,21 @@ fn precheck_imf(context: &Context, rfc724_mid: &str, server_folder: &str, server
Params::new(),
0,
);
} else if old_server_folder != server_folder {
info!(context, "[move] detected moved message {}", rfc724_mid,);
update_msg_move_state(context, &rfc724_mid, MoveState::Stay);
}
}
rfc724_mid_exists
if old_server_folder != server_folder || old_server_uid != server_uid {
update_server_uid(context, &rfc724_mid, server_folder, server_uid);
}
true
} else {
false
}
}
fn prefetch_get_message_id(prefetch_msg: &imap::types::Fetch) -> Result<String, Error> {
let message_id = prefetch_msg.envelope().unwrap().message_id.unwrap();
wrapmime::parse_message_id(&message_id)
}

View File

@@ -1,6 +1,5 @@
use std::ffi::CString;
use std::path::Path;
use std::ptr;
use core::cmp::{max, min};
use std::path::{Path, PathBuf};
use num_traits::FromPrimitive;
use rand::{thread_rng, Rng};
@@ -18,7 +17,7 @@ use crate::job::*;
use crate::key::*;
use crate::message::Message;
use crate::param::*;
use crate::pgp::*;
use crate::pgp;
use crate::sql::{self, Sql};
use crate::stock::StockMessage;
@@ -90,7 +89,7 @@ pub fn has_backup(context: &Context, dir_name: impl AsRef<Path>) -> Result<Strin
let sql = Sql::new();
if sql.open(context, &path, 0x1) {
let curr_backup_time =
sql.get_config_int(context, "backup_time")
sql.get_raw_config_int(context, "backup_time")
.unwrap_or_default() as u64;
if curr_backup_time > newest_backup_time {
newest_backup_path = Some(path);
@@ -109,77 +108,48 @@ pub fn has_backup(context: &Context, dir_name: impl AsRef<Path>) -> Result<Strin
}
pub fn initiate_key_transfer(context: &Context) -> Result<String> {
ensure!(context.alloc_ongoing(), "could not allocate ongoing");
let res = do_initiate_key_transfer(context);
context.free_ongoing();
res
}
fn do_initiate_key_transfer(context: &Context) -> Result<String> {
let mut msg: Message;
ensure!(dc_alloc_ongoing(context), "could not allocate ongoing");
let setup_code = create_setup_code(context);
/* this may require a keypair to be created. this may take a second ... */
if !context
.running_state
.clone()
.read()
.unwrap()
.shall_stop_ongoing
{
if let Ok(ref setup_file_content) = render_setup_file(context, &setup_code) {
/* encrypting may also take a while ... */
if !context
.running_state
.clone()
.read()
.unwrap()
.shall_stop_ongoing
{
let setup_file_name =
dc_get_fine_path_filename(context, "$BLOBDIR", "autocrypt-setup-message.html");
if dc_write_file(context, &setup_file_name, setup_file_content.as_bytes()) {
if let Ok(chat_id) = chat::create_by_contact_id(context, 1) {
msg = Message::default();
msg.type_0 = Viewtype::File;
msg.param
.set(Param::File, setup_file_name.to_string_lossy());
ensure!(!context.shall_stop_ongoing(), "canceled");
let setup_file_content = render_setup_file(context, &setup_code)?;
/* encrypting may also take a while ... */
ensure!(!context.shall_stop_ongoing(), "canceled");
let setup_file_name = context.new_blob_file(
"autocrypt-setup-message.html",
setup_file_content.as_bytes(),
)?;
msg.param
.set(Param::MimeType, "application/autocrypt-setup");
msg.param.set_int(Param::Cmd, 6);
msg.param
.set_int(Param::ForcePlaintext, DC_FP_NO_AUTOCRYPT_HEADER);
let chat_id = chat::create_by_contact_id(context, 1)?;
msg = Message::default();
msg.type_0 = Viewtype::File;
msg.param.set(Param::File, setup_file_name);
if !context
.running_state
.clone()
.read()
.unwrap()
.shall_stop_ongoing
{
if let Ok(msg_id) = chat::send_msg(context, chat_id, &mut msg) {
info!(context, "Wait for setup message being sent ...",);
loop {
if context
.running_state
.clone()
.read()
.unwrap()
.shall_stop_ongoing
{
break;
}
std::thread::sleep(std::time::Duration::from_secs(1));
if let Ok(msg) = Message::load_from_db(context, msg_id) {
if msg.is_sent() {
info!(context, "... setup message sent.",);
break;
}
}
}
}
}
}
}
msg.param
.set(Param::MimeType, "application/autocrypt-setup");
msg.param.set_int(Param::Cmd, 6);
msg.param
.set_int(Param::ForcePlaintext, DC_FP_NO_AUTOCRYPT_HEADER);
ensure!(!context.shall_stop_ongoing(), "canceled");
let msg_id = chat::send_msg(context, chat_id, &mut msg)?;
info!(context, "Wait for setup message being sent ...",);
while !context.shall_stop_ongoing() {
std::thread::sleep(std::time::Duration::from_secs(1));
if let Ok(msg) = Message::load_from_db(context, msg_id) {
if msg.is_sent() {
info!(context, "... setup message sent.",);
break;
}
}
}
dc_free_ongoing(context);
Ok(setup_code)
}
@@ -194,16 +164,12 @@ pub fn render_setup_file(context: &Context, passphrase: &str) -> Result<String>
let self_addr = e2ee::ensure_secret_key_exists(context)?;
let private_key = Key::from_self_private(context, self_addr, &context.sql)
.ok_or(format_err!("Failed to get private key."))?;
let ac_headers = match context
.sql
.get_config_int(context, Config::E2eeEnabled)
.unwrap_or(1)
{
0 => None,
_ => Some(("Autocrypt-Prefer-Encrypt", "mutual")),
let ac_headers = match context.get_config_bool(Config::E2eeEnabled) {
false => None,
true => Some(("Autocrypt-Prefer-Encrypt", "mutual")),
};
let private_key_asc = private_key.to_asc(ac_headers);
let encr = dc_pgp_symm_encrypt(&passphrase, private_key_asc.as_bytes())?;
let encr = pgp::symm_encrypt(&passphrase, private_key_asc.as_bytes())?;
let replacement = format!(
concat!(
@@ -266,25 +232,19 @@ pub fn continue_key_transfer(context: &Context, msg_id: u32, setup_code: &str) -
if msg.is_err() {
bail!("Message is no Autocrypt Setup Message.");
}
let msg = msg.unwrap();
let msg = msg.unwrap_or_default();
ensure!(
msg.is_setupmessage(),
"Message is no Autocrypt Setup Message."
);
if let Some(filename) = msg.get_file(context) {
if let Ok(ref mut buf) = dc_read_file(context, filename) {
let sc = normalize_setup_code(setup_code);
if let Ok(armored_key) = decrypt_setup_file(context, sc, buf) {
set_self_key(context, &armored_key, true, true)?;
} else {
bail!("Bad setup code.")
}
let file = dc_open_file(context, filename)?;
let sc = normalize_setup_code(setup_code);
let armored_key = decrypt_setup_file(context, &sc, file)?;
set_self_key(context, &armored_key, true, true)?;
Ok(())
} else {
bail!("Cannot read Autocrypt Setup Message file.");
}
Ok(())
} else {
bail!("Message is no Autocrypt Setup Message.");
}
@@ -316,7 +276,7 @@ fn set_self_key(
};
context
.sql
.set_config_int(context, "e2ee_enabled", e2ee_enabled)?;
.set_raw_config_int(context, "e2ee_enabled", e2ee_enabled)?;
}
None => {
if prefer_encrypt_required {
@@ -349,7 +309,7 @@ fn set_self_key(
context,
&public_key,
&private_key,
self_addr.unwrap(),
self_addr.unwrap_or_default(),
set_default,
&context.sql,
) {
@@ -358,53 +318,15 @@ fn set_self_key(
Ok(())
}
fn decrypt_setup_file(
fn decrypt_setup_file<T: std::io::Read + std::io::Seek>(
_context: &Context,
passphrase: impl AsRef<str>,
filecontent: &mut [u8],
passphrase: &str,
file: T,
) -> Result<String> {
let mut fc_headerline = String::default();
let mut fc_base64: *const libc::c_char = ptr::null();
let plain_bytes = pgp::symm_decrypt(passphrase, file)?;
let plain_text = std::string::String::from_utf8(plain_bytes)?;
let split_result = unsafe {
dc_split_armored_data(
filecontent.as_mut_ptr().cast(),
&mut fc_headerline,
ptr::null_mut(),
ptr::null_mut(),
&mut fc_base64,
)
};
if !split_result || fc_headerline != "-----BEGIN PGP MESSAGE-----" || fc_base64.is_null() {
bail!("Invalid armored data");
}
// convert base64 to binary
let base64_encoded =
unsafe { std::slice::from_raw_parts(fc_base64 as *const u8, libc::strlen(fc_base64)) };
let data = base64_decode(&base64_encoded)?;
// decrypt symmetrically
let payload = dc_pgp_symm_decrypt(passphrase.as_ref(), &data)?;
let payload_str = String::from_utf8(payload)?;
Ok(payload_str)
}
/// Decode the base64 encoded slice. Handles line breaks.
fn base64_decode(input: &[u8]) -> Result<Vec<u8>> {
use std::io::Read;
let c = std::io::Cursor::new(input);
let lr = pgp::line_reader::LineReader::new(c);
let br = pgp::base64_reader::Base64Reader::new(lr);
let mut reader = pgp::base64_decoder::Base64Decoder::new(br);
let mut data = Vec::new();
reader.read_to_end(&mut data)?;
Ok(data)
Ok(plain_text)
}
pub fn normalize_setup_code(s: &str) -> String {
@@ -422,7 +344,7 @@ pub fn normalize_setup_code(s: &str) -> String {
#[allow(non_snake_case)]
pub fn job_do_DC_JOB_IMEX_IMAP(context: &Context, job: &Job) -> Result<()> {
ensure!(dc_alloc_ongoing(context), "could not allocate ongoing");
ensure!(context.alloc_ongoing(), "could not allocate ongoing");
let what: Option<ImexMode> = job.param.get_int(Param::Cmd).and_then(ImexMode::from_i32);
let param = job.param.get(Param::Arg).unwrap_or_default();
@@ -434,7 +356,7 @@ pub fn job_do_DC_JOB_IMEX_IMAP(context: &Context, job: &Job) -> Result<()> {
if what == Some(ImexMode::ExportBackup) || what == Some(ImexMode::ExportSelfKeys) {
// before we export anything, make sure the private key exists
if e2ee::ensure_secret_key_exists(context).is_err() {
dc_free_ongoing(context);
context.free_ongoing();
bail!("Cannot create private key or private key not available.");
} else {
dc_create_folder(context, &param);
@@ -450,7 +372,7 @@ pub fn job_do_DC_JOB_IMEX_IMAP(context: &Context, job: &Job) -> Result<()> {
bail!("unknown IMEX type");
}
};
dc_free_ongoing(context);
context.free_ongoing();
match success {
Ok(()) => {
info!(context, "IMEX successfully completed");
@@ -477,7 +399,7 @@ fn import_backup(context: &Context, backup_to_import: impl AsRef<Path>) -> Resul
!dc_is_configured(context),
"Cannot import backups to accounts in use."
);
&context.sql.close(&context);
context.sql.close(&context);
dc_delete_file(context, context.get_dbfile());
ensure!(
!dc_file_exist(context, context.get_dbfile()),
@@ -514,20 +436,9 @@ fn import_backup(context: &Context, backup_to_import: impl AsRef<Path>) -> Resul
Ok((name, blob))
},
|files| {
let mut processed_files_cnt = 0;
for file in files {
for (processed_files_cnt, file) in files.enumerate() {
let (file_name, file_blob) = file?;
if context
.running_state
.clone()
.read()
.unwrap()
.shall_stop_ongoing
{
bail!("received stop signal");
}
processed_files_cnt += 1;
ensure!(!context.shall_stop_ongoing(), "received stop signal");
let mut permille = processed_files_cnt * 1000 / total_files_cnt;
if permille < 10 {
permille = 10
@@ -568,18 +479,11 @@ fn import_backup(context: &Context, backup_to_import: impl AsRef<Path>) -> Resul
/* the FILE_PROGRESS macro calls the callback with the permille of files processed.
The macro avoids weird values of 0% or 100% while still working. */
fn export_backup(context: &Context, dir: impl AsRef<Path>) -> Result<()> {
let mut ok_to_continue = true;
let mut success = false;
let mut delete_dest_file: libc::c_int = 0;
// get a fine backup file name (the name includes the date so that multiple backup instances are possible)
// FIXME: we should write to a temporary file first and rename it on success. this would guarantee the backup is complete. however, currently it is not clear it the import exists in the long run (may be replaced by a restore-from-imap)
// FIXME: we should write to a temporary file first and rename it on success. this would guarantee the backup is complete.
// let dest_path_filename = dc_get_next_backup_file(context, dir, res);
let now = time();
let res = chrono::NaiveDateTime::from_timestamp(now as i64, 0)
.format("delta-chat-%Y-%m-%d.bak")
.to_string();
let dest_path_filename = dc_get_fine_path_filename(context, dir, res);
let dest_path_filename = dc_get_next_backup_path(dir, now)?;
sql::housekeeping(context);
@@ -601,130 +505,79 @@ fn export_backup(context: &Context, dir: impl AsRef<Path>) -> Result<()> {
s
);
}
/* add all files as blobs to the database copy (this does not require the source to be locked, neigher the destination as it is used only here) */
/*for logging only*/
match add_files_to_export(context, &dest_path_filename) {
Err(err) => {
dc_delete_file(context, &dest_path_filename);
error!(context, "backup failed: {}", err);
Err(err)
}
Ok(()) => {
context
.sql
.set_raw_config_int(context, "backup_time", now as i32)?;
context.call_cb(Event::ImexFileWritten(dest_path_filename.clone()));
Ok(())
}
}
}
fn add_files_to_export(context: &Context, dest_path_filename: &PathBuf) -> Result<()> {
// add all files as blobs to the database copy (this does not require
// the source to be locked, neigher the destination as it is used only here)
let sql = Sql::new();
if sql.open(context, &dest_path_filename, 0) {
if !sql.table_exists("backup_blobs") {
if sql::execute(
context,
&sql,
"CREATE TABLE backup_blobs (id INTEGER PRIMARY KEY, file_name, file_content);",
params![],
)
.is_err()
{
/* error already logged */
ok_to_continue = false;
}
}
if ok_to_continue {
let mut total_files_cnt = 0;
let dir = context.get_blobdir();
if let Ok(dir_handle) = std::fs::read_dir(&dir) {
total_files_cnt += dir_handle.filter(|r| r.is_ok()).count();
ensure!(
sql.open(context, &dest_path_filename, 0),
"could not open db"
);
if !sql.table_exists("backup_blobs") {
sql::execute(
context,
&sql,
"CREATE TABLE backup_blobs (id INTEGER PRIMARY KEY, file_name, file_content);",
params![],
)?
}
// copy all files from BLOBDIR into backup-db
let mut total_files_cnt = 0;
let dir = context.get_blobdir();
let dir_handle = std::fs::read_dir(&dir)?;
total_files_cnt += dir_handle.filter(|r| r.is_ok()).count();
info!(context, "EXPORT: total_files_cnt={}", total_files_cnt);
if total_files_cnt > 0 {
// scan directory, pass 2: copy files
if let Ok(dir_handle) = std::fs::read_dir(&dir) {
sql.prepare(
"INSERT INTO backup_blobs (file_name, file_content) VALUES (?, ?);",
move |mut stmt, _| {
let mut processed_files_cnt = 0;
for entry in dir_handle {
if entry.is_err() {
break;
}
let entry = entry.unwrap();
if context
.running_state
.clone()
.read()
.unwrap()
.shall_stop_ongoing
{
delete_dest_file = 1;
ok_to_continue = false;
break;
} else {
processed_files_cnt += 1;
let mut permille =
processed_files_cnt * 1000 / total_files_cnt;
if permille < 10 {
permille = 10;
}
if permille > 990 {
permille = 990;
}
context.call_cb(Event::ImexProgress(permille));
let name_f = entry.file_name();
let name = name_f.to_string_lossy();
if name.starts_with("delta-chat") && name.ends_with(".bak")
{
continue;
} else {
info!(context, "EXPORTing filename={}", name);
let curr_path_filename = context.get_blobdir().join(entry.file_name());
if let Ok(buf) =
dc_read_file(context, &curr_path_filename)
{
if buf.is_empty() {
continue;
}
if stmt.execute(params![name, buf]).is_err() {
error!(
context,
"Disk full? Cannot add file \"{}\" to backup.",
curr_path_filename.display(),
);
/* this is not recoverable! writing to the sqlite database should work! */
ok_to_continue = false;
break;
}
} else {
continue;
}
}
}
}
Ok(())
}
).unwrap();
} else {
error!(
context,
"Backup: Cannot copy from blob-directory \"{}\".",
context.get_blobdir().display(),
);
}
} else {
info!(context, "Backup: No files to copy.",);
}
if ok_to_continue {
if sql
.set_config_int(context, "backup_time", now as i32)
.is_ok()
{
context.call_cb(Event::ImexFileWritten(dest_path_filename.clone()));
success = true;
}
}
} else {
error!(
context,
"Backup: Cannot get info for blob-directory \"{}\".",
context.get_blobdir().display(),
info!(context, "EXPORT: total_files_cnt={}", total_files_cnt);
// scan directory, pass 2: copy files
let dir_handle = std::fs::read_dir(&dir)?;
sql.prepare(
"INSERT INTO backup_blobs (file_name, file_content) VALUES (?, ?);",
|mut stmt, _| {
let mut processed_files_cnt = 0;
for entry in dir_handle {
let entry = entry?;
ensure!(
!context.shall_stop_ongoing(),
"canceled during export-files"
);
};
}
}
if 0 != delete_dest_file {
dc_delete_file(context, &dest_path_filename);
}
processed_files_cnt += 1;
let permille = max(min(processed_files_cnt * 1000 / total_files_cnt, 990), 10);
context.call_cb(Event::ImexProgress(permille));
ensure!(success, "failed");
let name_f = entry.file_name();
let name = name_f.to_string_lossy();
if name.starts_with("delta-chat") && name.ends_with(".bak") {
continue;
}
info!(context, "EXPORT: copying filename={}", name);
let curr_path_filename = context.get_blobdir().join(entry.file_name());
if let Ok(buf) = dc_read_file(context, &curr_path_filename) {
if buf.is_empty() {
continue;
}
// bail out if we can't insert
stmt.execute(params![name, buf])?;
}
}
Ok(())
},
)?;
Ok(())
}
@@ -739,81 +592,48 @@ fn import_self_keys(context: &Context, dir: impl AsRef<Path>) -> Result<()> {
Maybe we should make the "default" key handlong also a little bit smarter
(currently, the last imported key is the standard key unless it contains the string "legacy" in its name) */
let mut set_default: bool;
let mut key: String;
let mut imported_cnt = 0;
let dir_name = dir.as_ref().to_string_lossy();
if let Ok(dir_handle) = std::fs::read_dir(&dir) {
for entry in dir_handle {
let entry_fn = match entry {
Err(err) => {
info!(context, "file-dir error: {}", err);
break;
let dir_handle = std::fs::read_dir(&dir)?;
for entry in dir_handle {
let entry_fn = entry?.file_name();
let name_f = entry_fn.to_string_lossy();
let path_plus_name = dir.as_ref().join(&entry_fn);
match dc_get_filesuffix_lc(&name_f) {
Some(suffix) => {
if suffix != "asc" {
continue;
}
Ok(res) => res.file_name(),
};
let name_f = entry_fn.to_string_lossy();
let path_plus_name = dir.as_ref().join(&entry_fn);
match dc_get_filesuffix_lc(&name_f) {
Some(suffix) => {
if suffix != "asc" {
continue;
}
set_default = if name_f.contains("legacy") {
info!(context, "found legacy key '{}'", path_plus_name.display());
false
} else {
true
}
set_default = if name_f.contains("legacy") {
info!(context, "found legacy key '{}'", path_plus_name.display());
false
} else {
true
}
None => {
}
None => {
continue;
}
}
match dc_read_file(context, &path_plus_name) {
Ok(buf) => {
let armored = std::string::String::from_utf8_lossy(&buf);
if let Err(err) = set_self_key(context, &armored, set_default, false) {
error!(context, "set_self_key: {}", err);
continue;
}
}
let ccontent = if let Ok(content) = dc_read_file(context, &path_plus_name) {
key = String::from_utf8_lossy(&content).to_string();
CString::new(content).unwrap()
} else {
continue;
};
/* only import if we have a private key */
let mut buf2_headerline = String::default();
let split_res: bool;
unsafe {
let buf2 = dc_strdup(ccontent.as_ptr());
split_res = dc_split_armored_data(
buf2,
&mut buf2_headerline,
ptr::null_mut(),
ptr::null_mut(),
ptr::null_mut(),
);
libc::free(buf2 as *mut libc::c_void);
}
if split_res
&& buf2_headerline.contains("-----BEGIN PGP PUBLIC KEY BLOCK-----")
&& !key.contains("-----BEGIN PGP PRIVATE KEY BLOCK")
{
info!(context, "ignoring public key file '{}", name_f);
// it's fine: DC exports public with private
continue;
}
if let Err(err) = set_self_key(context, &key, set_default, false) {
error!(context, "set_self_key: {}", err);
continue;
}
imported_cnt += 1
Err(_) => continue,
}
ensure!(
imported_cnt > 0,
"No private keys found in \"{}\".",
dir_name
);
return Ok(());
} else {
bail!("Import: Cannot open directory \"{}\".", dir_name);
imported_cnt += 1;
}
ensure!(
imported_cnt > 0,
"No private keys found in \"{}\".",
dir_name
);
Ok(())
}
fn export_self_keys(context: &Context, dir: impl AsRef<Path>) -> Result<()> {
@@ -856,7 +676,7 @@ fn export_self_keys(context: &Context, dir: impl AsRef<Path>) -> Result<()> {
},
)?;
ensure!(export_errors == 0, "errors while importing");
ensure!(export_errors == 0, "errors while exporting keys");
Ok(())
}
@@ -893,7 +713,9 @@ fn export_key_to_asc_file(
mod tests {
use super::*;
use crate::pgp::{split_armored_data, HEADER_AUTOCRYPT, HEADER_SETUPCODE};
use crate::test_utils::*;
use ::pgp::armor::BlockType;
#[test]
fn test_render_setup_file() {
@@ -915,19 +737,12 @@ mod tests {
assert!(msg.contains("-----END PGP MESSAGE-----\n"));
}
fn ac_setup_msg_cb(ctx: &Context, evt: Event) -> libc::uintptr_t {
match evt {
Event::GetString {
id: StockMessage::AcSetupMsgBody,
..
} => unsafe { "hello\r\nthere".strdup() as usize },
_ => logging_cb(ctx, evt),
}
}
#[test]
fn otest_render_setup_file_newline_replace() {
let t = test_context(Some(Box::new(ac_setup_msg_cb)));
fn test_render_setup_file_newline_replace() {
let t = dummy_context();
t.ctx
.set_stock_translation(StockMessage::AcSetupMsgBody, "hello\r\nthere".to_string())
.unwrap();
configure_alice_keypair(&t.ctx);
let msg = render_setup_file(&t.ctx, "pw").unwrap();
println!("{}", &msg);
@@ -983,47 +798,26 @@ mod tests {
let ctx = dummy_context();
let context = &ctx.ctx;
let mut headerline = String::default();
let mut setupcodebegin = ptr::null();
let mut preferencrypt = ptr::null();
let buf_1 = S_EM_SETUPFILE.as_bytes().to_vec();
let (typ, headers, base64) = split_armored_data(&buf_1).unwrap();
assert_eq!(typ, BlockType::Message);
assert!(S_EM_SETUPCODE.starts_with(headers.get(HEADER_SETUPCODE).unwrap()));
assert!(headers.get(HEADER_AUTOCRYPT).is_none());
let mut buf_1 = S_EM_SETUPFILE.to_string();
assert!(!base64.is_empty());
unsafe {
assert!(dc_split_armored_data(
buf_1.as_mut_ptr().cast(),
&mut headerline,
&mut setupcodebegin,
&mut preferencrypt,
ptr::null_mut(),
));
}
assert_eq!(headerline, "-----BEGIN PGP MESSAGE-----");
assert!(!setupcodebegin.is_null());
let setup_file = S_EM_SETUPFILE.to_string();
let decrypted = decrypt_setup_file(
context,
S_EM_SETUPCODE,
std::io::Cursor::new(setup_file.as_bytes()),
)
.unwrap();
// TODO: verify that this is the right check
assert!(S_EM_SETUPCODE.starts_with(as_str(setupcodebegin)));
let (typ, headers, _base64) = split_armored_data(decrypted.as_bytes()).unwrap();
assert!(preferencrypt.is_null());
let mut setup_file = S_EM_SETUPFILE.to_string();
let mut decrypted = unsafe {
decrypt_setup_file(context, S_EM_SETUPCODE, setup_file.as_bytes_mut()).unwrap()
};
unsafe {
assert!(dc_split_armored_data(
decrypted.as_mut_ptr().cast(),
&mut headerline,
&mut setupcodebegin,
&mut preferencrypt,
ptr::null_mut(),
));
}
assert_eq!(headerline, "-----BEGIN PGP PRIVATE KEY BLOCK-----");
assert!(setupcodebegin.is_null());
assert!(!preferencrypt.is_null());
assert_eq!(as_str(preferencrypt), "mutual",);
assert_eq!(typ, BlockType::PrivateKey);
assert_eq!(headers.get(HEADER_AUTOCRYPT), Some(&"mutual".to_string()));
assert!(headers.get(HEADER_SETUPCODE).is_none());
}
}

View File

@@ -4,10 +4,12 @@ use deltachat_derive::{FromSql, ToSql};
use rand::{thread_rng, Rng};
use crate::chat;
use crate::config::Config;
use crate::configure::*;
use crate::constants::*;
use crate::context::Context;
use crate::dc_tools::*;
use crate::error::Error;
use crate::events::Event;
use crate::imap::*;
use crate::imex::*;
@@ -140,11 +142,11 @@ impl Job {
if let Ok(body) = dc_read_file(context, filename) {
if let Some(recipients) = self.param.get(Param::Recipients) {
let recipients_list = recipients
.split("\x1e")
.split('\x1e')
.filter_map(|addr| match lettre::EmailAddress::new(addr.to_string()) {
Ok(addr) => Some(addr),
Err(err) => {
eprintln!("WARNING: invalid recipient: {} {:?}", addr, err);
warn!(context, "invalid recipient: {} {:?}", addr, err);
None
}
})
@@ -156,39 +158,45 @@ impl Job {
if 0 != self.foreign_id && !message::exists(context, self.foreign_id) {
warn!(
context,
"Message {} for job {} does not exist", self.foreign_id, self.job_id,
"Not sending Message {} as it was deleted", self.foreign_id
);
return;
};
// hold the smtp lock during sending of a job and
// its ok/error response processing. Note that if a message
// was sent we need to mark it in the database as we
// was sent we need to mark it in the database ASAP as we
// otherwise might send it twice.
let mut sock = context.smtp.lock().unwrap();
if 0 == sock.send(context, recipients_list, body) {
sock.disconnect();
self.try_again_later(-1i32, sock.error.clone());
} else {
dc_delete_file(context, filename);
if 0 != self.foreign_id {
message::update_msg_state(
context,
self.foreign_id,
MessageState::OutDelivered,
);
let chat_id: i32 = context
.sql
.query_get_value(
match sock.send(context, recipients_list, body) {
Err(err) => {
sock.disconnect();
warn!(context, "smtp failed: {}", err);
self.try_again_later(-1i32, Some(err.to_string()));
}
Ok(()) => {
// smtp success, update db ASAP, then delete smtp file
if 0 != self.foreign_id {
message::update_msg_state(
context,
"SELECT chat_id FROM msgs WHERE id=?",
params![self.foreign_id as i32],
)
.unwrap_or_default();
context.call_cb(Event::MsgDelivered {
chat_id: chat_id as u32,
msg_id: self.foreign_id,
});
self.foreign_id,
MessageState::OutDelivered,
);
let chat_id: i32 = context
.sql
.query_get_value(
context,
"SELECT chat_id FROM msgs WHERE id=?",
params![self.foreign_id as i32],
)
.unwrap_or_default();
context.call_cb(Event::MsgDelivered {
chat_id: chat_id as u32,
msg_id: self.foreign_id,
});
}
// now also delete the generated file
dc_delete_file(context, filename);
}
}
} else {
@@ -208,23 +216,18 @@ impl Job {
fn do_DC_JOB_MOVE_MSG(&mut self, context: &Context) {
let inbox = context.inbox.read().unwrap();
if !inbox.is_connected() {
connect_to_inbox(context, &inbox);
if !inbox.is_connected() {
self.try_again_later(3, None);
return;
}
}
if let Ok(msg) = Message::load_from_db(context, self.foreign_id) {
if context
.sql
.get_config_int(context, "folders_configured")
.get_raw_config_int(context, "folders_configured")
.unwrap_or_default()
< 3
{
inbox.configure_folders(context, 0x1i32);
}
let dest_folder = context.sql.get_config(context, "configured_mvbox_folder");
let dest_folder = context
.sql
.get_raw_config(context, "configured_mvbox_folder");
if let Some(dest_folder) = dest_folder {
let server_folder = msg.server_folder.as_ref().unwrap();
@@ -261,26 +264,18 @@ impl Job {
if let Ok(mut msg) = Message::load_from_db(context, self.foreign_id) {
if !msg.rfc724_mid.is_empty() {
/* eg. device messages have no Message-ID */
let mut delete_from_server = true;
if message::rfc724_mid_cnt(context, &msg.rfc724_mid) != 1 {
if message::rfc724_mid_cnt(context, &msg.rfc724_mid) > 1 {
info!(
context,
"The message is deleted from the server when all parts are deleted.",
);
delete_from_server = false;
}
/* if this is the last existing part of the message, we delete the message from the server */
if delete_from_server {
if !inbox.is_connected() {
connect_to_inbox(context, &inbox);
if !inbox.is_connected() {
self.try_again_later(3i32, None);
return;
}
}
} else {
/* if this is the last existing part of the message,
we delete the message from the server */
let mid = msg.rfc724_mid;
let server_folder = msg.server_folder.as_ref().unwrap();
if 0 == inbox.delete_msg(context, &mid, server_folder, &mut msg.server_uid) {
let res = inbox.delete_msg(context, &mid, server_folder, &mut msg.server_uid);
if res == ImapResult::RetryLater {
self.try_again_later(-1i32, None);
return;
}
@@ -294,37 +289,23 @@ impl Job {
fn do_DC_JOB_MARKSEEN_MSG_ON_IMAP(&mut self, context: &Context) {
let inbox = context.inbox.read().unwrap();
if !inbox.is_connected() {
connect_to_inbox(context, &inbox);
if !inbox.is_connected() {
self.try_again_later(3i32, None);
return;
}
}
if let Ok(msg) = Message::load_from_db(context, self.foreign_id) {
let server_folder = msg.server_folder.as_ref().unwrap();
match inbox.set_seen(context, server_folder, msg.server_uid) {
ImapResult::Failed => {}
let folder = msg.server_folder.as_ref().unwrap();
match inbox.set_seen(context, folder, msg.server_uid) {
ImapResult::RetryLater => {
self.try_again_later(3i32, None);
}
_ => {
ImapResult::AlreadyDone => {}
ImapResult::Success | ImapResult::Failed => {
// XXX the message might just have been moved
// we want to send out an MDN anyway
// The job will not be retried so locally
// there is no risk of double-sending MDNs.
if 0 != msg.param.get_int(Param::WantsMdn).unwrap_or_default()
&& 0 != context
.sql
.get_config_int(context, "mdns_enabled")
.unwrap_or_else(|| 1)
&& context.get_config_bool(Config::MdnsEnabled)
{
let folder = msg.server_folder.as_ref().unwrap();
match inbox.set_mdnsent(context, folder, msg.server_uid) {
ImapResult::RetryLater => {
self.try_again_later(3i32, None);
}
ImapResult::Success => {
send_mdn(context, msg.id);
}
ImapResult::Failed | ImapResult::AlreadyDone => {}
if let Err(err) = send_mdn(context, msg.id) {
warn!(context, "could not send out mdn for {}: {}", msg.id, err);
}
}
}
@@ -341,31 +322,26 @@ impl Job {
.to_string();
let uid = self.param.get_int(Param::ServerUid).unwrap_or_default() as u32;
let inbox = context.inbox.read().unwrap();
if !inbox.is_connected() {
connect_to_inbox(context, &inbox);
if !inbox.is_connected() {
self.try_again_later(3, None);
return;
}
}
if inbox.set_seen(context, &folder, uid) == ImapResult::Failed {
if inbox.set_seen(context, &folder, uid) == ImapResult::RetryLater {
self.try_again_later(3i32, None);
return;
}
if 0 != self.param.get_int(Param::AlsoMove).unwrap_or_default() {
if context
.sql
.get_config_int(context, "folders_configured")
.get_raw_config_int(context, "folders_configured")
.unwrap_or_default()
< 3
{
inbox.configure_folders(context, 0x1i32);
}
let dest_folder = context.sql.get_config(context, "configured_mvbox_folder");
let dest_folder = context
.sql
.get_raw_config(context, "configured_mvbox_folder");
if let Some(dest_folder) = dest_folder {
let mut dest_uid = 0;
if ImapResult::RetryLater
== inbox.mv(context, folder, uid, dest_folder, &mut dest_uid)
== inbox.mv(context, &folder, uid, &dest_folder, &mut dest_uid)
{
self.try_again_later(3, None);
}
@@ -392,12 +368,7 @@ pub fn perform_imap_fetch(context: &Context) {
if 0 == connect_to_inbox(context, &inbox) {
return;
}
if context
.sql
.get_config_int(context, "inbox_watch")
.unwrap_or_else(|| 1)
== 0
{
if !context.get_config_bool(Config::InboxWatch) {
info!(context, "INBOX-watch disabled.",);
return;
}
@@ -432,29 +403,23 @@ pub fn perform_imap_idle(context: &Context) {
}
pub fn perform_mvbox_fetch(context: &Context) {
let use_network = context
.sql
.get_config_int(context, "mvbox_watch")
.unwrap_or_else(|| 1);
let use_network = context.get_config_bool(Config::MvboxWatch);
context
.mvbox_thread
.write()
.unwrap()
.fetch(context, use_network == 1);
.fetch(context, use_network);
}
pub fn perform_mvbox_idle(context: &Context) {
let use_network = context
.sql
.get_config_int(context, "mvbox_watch")
.unwrap_or_else(|| 1);
let use_network = context.get_config_bool(Config::MvboxWatch);
context
.mvbox_thread
.read()
.unwrap()
.idle(context, use_network == 1);
.idle(context, use_network);
}
pub fn interrupt_mvbox_idle(context: &Context) {
@@ -462,29 +427,23 @@ pub fn interrupt_mvbox_idle(context: &Context) {
}
pub fn perform_sentbox_fetch(context: &Context) {
let use_network = context
.sql
.get_config_int(context, "sentbox_watch")
.unwrap_or_else(|| 1);
let use_network = context.get_config_bool(Config::SentboxWatch);
context
.sentbox_thread
.write()
.unwrap()
.fetch(context, use_network == 1);
.fetch(context, use_network);
}
pub fn perform_sentbox_idle(context: &Context) {
let use_network = context
.sql
.get_config_int(context, "sentbox_watch")
.unwrap_or_else(|| 1);
let use_network = context.get_config_bool(Config::SentboxWatch);
context
.sentbox_thread
.read()
.unwrap()
.idle(context, use_network == 1);
.idle(context, use_network);
}
pub fn interrupt_sentbox_idle(context: &Context) {
@@ -601,109 +560,108 @@ pub fn job_action_exists(context: &Context, action: Action) -> bool {
/* special case for DC_JOB_SEND_MSG_TO_SMTP */
#[allow(non_snake_case)]
pub fn job_send_msg(context: &Context, msg_id: u32) -> libc::c_int {
let mut success = 0;
pub fn job_send_msg(context: &Context, msg_id: u32) -> Result<(), Error> {
let mut mimefactory = MimeFactory::load_msg(context, msg_id)?;
/* load message data */
let mimefactory = MimeFactory::load_msg(context, msg_id);
if mimefactory.is_err() {
warn!(
context,
"Cannot load data to send, maybe the message is deleted in between.",
);
} else {
let mut mimefactory = mimefactory.unwrap();
// no redo, no IMAP. moreover, as the data does not exist, there is no need in calling dc_set_msg_failed()
if chat::msgtype_has_file(mimefactory.msg.type_0) {
let file_param = mimefactory
.msg
.param
.get(Param::File)
.map(|s| s.to_string());
if let Some(pathNfilename) = file_param {
if (mimefactory.msg.type_0 == Viewtype::Image
|| mimefactory.msg.type_0 == Viewtype::Gif)
&& !mimefactory.msg.param.exists(Param::Width)
{
mimefactory.msg.param.set_int(Param::Width, 0);
mimefactory.msg.param.set_int(Param::Height, 0);
if let Ok(buf) = dc_read_file(context, pathNfilename) {
if let Ok((width, height)) = dc_get_filemeta(&buf) {
mimefactory.msg.param.set_int(Param::Width, width as i32);
mimefactory.msg.param.set_int(Param::Height, height as i32);
}
}
mimefactory.msg.save_param_to_disk(context);
}
}
}
/* create message */
if let Err(msg) = unsafe { mimefactory.render() } {
let e = msg.to_string();
message::set_msg_failed(context, msg_id, Some(e));
} else if 0
!= mimefactory
.msg
.param
.get_int(Param::GuranteeE2ee)
.unwrap_or_default()
&& !mimefactory.out_encrypted
{
/* unrecoverable */
warn!(
context,
"e2e encryption unavailable {} - {:?}",
msg_id,
mimefactory.msg.param.get_int(Param::GuranteeE2ee),
);
message::set_msg_failed(
context,
msg_id,
Some("End-to-end-encryption unavailable unexpectedly."),
);
} else {
if !vec_contains_lowercase(&mimefactory.recipients_addr, &mimefactory.from_addr) {
mimefactory.recipients_names.push("".to_string());
mimefactory
.recipients_addr
.push(mimefactory.from_addr.to_string());
}
if mimefactory.out_gossiped {
chat::set_gossiped_timestamp(context, mimefactory.msg.chat_id, time());
}
if 0 != mimefactory.out_last_added_location_id {
if let Err(err) =
location::set_kml_sent_timestamp(context, mimefactory.msg.chat_id, time())
{
error!(context, "Failed to set kml sent_timestamp: {:?}", err);
}
if !mimefactory.msg.hidden {
if let Err(err) = location::set_msg_location_id(
context,
mimefactory.msg.id,
mimefactory.out_last_added_location_id,
) {
error!(context, "Failed to set msg_location_id: {:?}", err);
}
}
}
if mimefactory.out_encrypted
&& mimefactory
.msg
.param
.get_int(Param::GuranteeE2ee)
.unwrap_or_default()
== 0
if chat::msgtype_has_file(mimefactory.msg.type_0) {
let file_param = mimefactory
.msg
.param
.get(Param::File)
.map(|s| s.to_string());
if let Some(pathNfilename) = file_param {
if (mimefactory.msg.type_0 == Viewtype::Image
|| mimefactory.msg.type_0 == Viewtype::Gif)
&& !mimefactory.msg.param.exists(Param::Width)
{
mimefactory.msg.param.set_int(Param::GuranteeE2ee, 1);
mimefactory.msg.param.set_int(Param::Width, 0);
mimefactory.msg.param.set_int(Param::Height, 0);
if let Ok(buf) = dc_read_file(context, pathNfilename) {
if let Ok((width, height)) = dc_get_filemeta(&buf) {
mimefactory.msg.param.set_int(Param::Width, width as i32);
mimefactory.msg.param.set_int(Param::Height, height as i32);
}
}
mimefactory.msg.save_param_to_disk(context);
}
success = add_smtp_job(context, Action::SendMsgToSmtp, &mut mimefactory);
}
}
success
/* create message */
if let Err(msg) = unsafe { mimefactory.render() } {
let e = msg.to_string();
message::set_msg_failed(context, msg_id, Some(e));
return Err(msg);
}
if 0 != mimefactory
.msg
.param
.get_int(Param::GuranteeE2ee)
.unwrap_or_default()
&& !mimefactory.out_encrypted
{
/* unrecoverable */
message::set_msg_failed(
context,
msg_id,
Some("End-to-end-encryption unavailable unexpectedly."),
);
bail!(
"e2e encryption unavailable {} - {:?}",
msg_id,
mimefactory.msg.param.get_int(Param::GuranteeE2ee),
);
}
if context.get_config_bool(Config::BccSelf)
&& !vec_contains_lowercase(&mimefactory.recipients_addr, &mimefactory.from_addr)
{
mimefactory.recipients_names.push("".to_string());
mimefactory
.recipients_addr
.push(mimefactory.from_addr.to_string());
}
if mimefactory.recipients_addr.is_empty() {
warn!(
context,
"message {} has no recipient, skipping smtp-send", msg_id
);
return Ok(());
}
if mimefactory.out_gossiped {
chat::set_gossiped_timestamp(context, mimefactory.msg.chat_id, time());
}
if 0 != mimefactory.out_last_added_location_id {
if let Err(err) = location::set_kml_sent_timestamp(context, mimefactory.msg.chat_id, time())
{
error!(context, "Failed to set kml sent_timestamp: {:?}", err);
}
if !mimefactory.msg.hidden {
if let Err(err) = location::set_msg_location_id(
context,
mimefactory.msg.id,
mimefactory.out_last_added_location_id,
) {
error!(context, "Failed to set msg_location_id: {:?}", err);
}
}
}
if mimefactory.out_encrypted
&& mimefactory
.msg
.param
.get_int(Param::GuranteeE2ee)
.unwrap_or_default()
== 0
{
mimefactory.msg.param.set_int(Param::GuranteeE2ee, 1);
mimefactory.msg.save_param_to_disk(context);
}
add_smtp_job(context, Action::SendMsgToSmtp, &mut mimefactory)?;
Ok(())
}
pub fn perform_imap_jobs(context: &Context) {
@@ -793,13 +751,13 @@ fn job_perform(context: &Context, thread: Thread, probe_network: bool) {
// - they can be re-executed one time AT_ONCE, but they are not save in the database for later execution
if Action::ConfigureImap == job.action || Action::ImexImap == job.action {
job_kill_action(context, job.action);
&context
context
.sentbox_thread
.clone()
.read()
.unwrap()
.suspend(context);
&context
context
.mvbox_thread
.clone()
.read()
@@ -823,7 +781,7 @@ fn job_perform(context: &Context, thread: Thread, probe_network: bool) {
Action::MarkseenMdnOnImap => job.do_DC_JOB_MARKSEEN_MDN_ON_IMAP(context),
Action::MoveMsg => job.do_DC_JOB_MOVE_MSG(context),
Action::SendMdn => job.do_DC_JOB_SEND(context),
Action::ConfigureImap => unsafe { dc_job_do_DC_JOB_CONFIGURE_IMAP(context) },
Action::ConfigureImap => dc_job_do_DC_JOB_CONFIGURE_IMAP(context),
Action::ImexImap => match job_do_DC_JOB_IMEX_IMAP(context, &job) {
Ok(()) => {}
Err(err) => {
@@ -924,8 +882,7 @@ fn job_perform(context: &Context, thread: Thread, probe_network: bool) {
#[allow(non_snake_case)]
fn get_backoff_time_offset(c_tries: libc::c_int) -> i64 {
// results in ~3 weeks for the last backoff timespan
let mut N = 2_i32.pow((c_tries - 1) as u32);
N = N * 60;
let N = 2_i32.pow((c_tries - 1) as u32) * 60;
let mut rng = thread_rng();
let n: i32 = rng.gen();
let mut seconds = n % (N + 1);
@@ -947,7 +904,7 @@ fn suspend_smtp_thread(context: &Context, suspend: bool) {
}
}
fn connect_to_inbox(context: &Context, inbox: &Imap) -> libc::c_int {
pub fn connect_to_inbox(context: &Context, inbox: &Imap) -> libc::c_int {
let ret_connected = dc_connect_to_configured_imap(context, inbox);
if 0 != ret_connected {
inbox.set_watch_folder("INBOX".into());
@@ -955,51 +912,44 @@ fn connect_to_inbox(context: &Context, inbox: &Imap) -> libc::c_int {
ret_connected
}
fn send_mdn(context: &Context, msg_id: u32) {
if let Ok(mut mimefactory) = MimeFactory::load_mdn(context, msg_id) {
if unsafe { mimefactory.render() }.is_ok() {
add_smtp_job(context, Action::SendMdn, &mut mimefactory);
}
}
fn send_mdn(context: &Context, msg_id: u32) -> Result<(), Error> {
let mut mimefactory = MimeFactory::load_mdn(context, msg_id)?;
unsafe { mimefactory.render()? };
add_smtp_job(context, Action::SendMdn, &mut mimefactory)?;
Ok(())
}
#[allow(non_snake_case)]
fn add_smtp_job(context: &Context, action: Action, mimefactory: &MimeFactory) -> libc::c_int {
let mut success: libc::c_int = 0i32;
fn add_smtp_job(context: &Context, action: Action, mimefactory: &MimeFactory) -> Result<(), Error> {
ensure!(
!mimefactory.recipients_addr.is_empty(),
"no recipients for smtp job set"
);
let mut param = Params::new();
let path_filename = dc_get_fine_path_filename(context, "$BLOBDIR", &mimefactory.rfc724_mid);
let bytes = unsafe {
std::slice::from_raw_parts(
(*mimefactory.out).str_0 as *const u8,
(*mimefactory.out).len,
)
};
if !dc_write_file(context, &path_filename, bytes) {
error!(
context,
"Could not write message <{}> to \"{}\".",
mimefactory.rfc724_mid,
path_filename.display(),
);
} else {
info!(context, "add_smtp_job file written: {:?}", path_filename);
let recipients = mimefactory.recipients_addr.join("\x1e");
param.set(Param::File, path_filename.to_string_lossy());
param.set(Param::Recipients, &recipients);
job_add(
context,
action,
(if mimefactory.loaded == Loaded::Message {
mimefactory.msg.id
} else {
0
}) as libc::c_int,
param,
0,
);
success = 1;
}
success
let bpath = context.new_blob_file(&mimefactory.rfc724_mid, bytes)?;
let recipients = mimefactory.recipients_addr.join("\x1e");
param.set(Param::File, &bpath);
param.set(Param::Recipients, &recipients);
job_add(
context,
action,
(if mimefactory.loaded == Loaded::Message {
mimefactory.msg.id
} else {
0
}) as libc::c_int,
param,
0,
);
Ok(())
}
pub fn job_add(

View File

@@ -116,14 +116,14 @@ impl JobThread {
if ret_connected {
if context
.sql
.get_config_int(context, "folders_configured")
.get_raw_config_int(context, "folders_configured")
.unwrap_or_default()
< 3
{
self.imap.configure_folders(context, 0x1);
}
if let Some(mvbox_name) = context.sql.get_config(context, self.folder_config_name) {
if let Some(mvbox_name) = context.sql.get_raw_config(context, self.folder_config_name) {
self.imap.set_watch_folder(mvbox_name);
} else {
self.imap.disconnect(context);

View File

@@ -163,8 +163,8 @@ impl Key {
pub fn to_bytes(&self) -> Vec<u8> {
match self {
Key::Public(k) => k.to_bytes().unwrap(),
Key::Secret(k) => k.to_bytes().unwrap(),
Key::Public(k) => k.to_bytes().unwrap_or_default(),
Key::Secret(k) => k.to_bytes().unwrap_or_default(),
}
}
@@ -218,10 +218,10 @@ impl Key {
let file_content = self.to_asc(None).into_bytes();
if dc_write_file(context, &file, &file_content) {
return true;
true
} else {
error!(context, "Cannot write key to {}", file.as_ref().display());
return false;
false
}
}
@@ -381,7 +381,7 @@ i8pcjGO+IZffvyZJVRWfVooBJmWWbPB1pueo3tx8w3+fcuzpxz+RLFKaPyqXO+dD
#[test]
#[ignore] // is too expensive
fn test_from_slice_roundtrip() {
let (public_key, private_key) = crate::pgp::dc_pgp_create_keypair("hello").unwrap();
let (public_key, private_key) = crate::pgp::create_keypair("hello").unwrap();
let binary = public_key.to_bytes();
let public_key2 = Key::from_slice(&binary, KeyType::Public).expect("invalid public key");
@@ -416,7 +416,7 @@ i8pcjGO+IZffvyZJVRWfVooBJmWWbPB1pueo3tx8w3+fcuzpxz+RLFKaPyqXO+dD
#[test]
#[ignore] // is too expensive
fn test_ascii_roundtrip() {
let (public_key, private_key) = crate::pgp::dc_pgp_create_keypair("hello").unwrap();
let (public_key, private_key) = crate::pgp::create_keypair("hello").unwrap();
let s = public_key.to_armored_string(None).unwrap();
let (public_key2, _) =

View File

@@ -57,7 +57,7 @@ pub mod qr;
pub mod securejoin;
mod smtp;
pub mod sql;
mod stock;
pub mod stock;
mod token;
#[macro_use]
mod wrapmime;

View File

@@ -3,6 +3,7 @@ use quick_xml;
use quick_xml::events::{BytesEnd, BytesStart, BytesText};
use crate::chat;
use crate::config::Config;
use crate::constants::*;
use crate::context::*;
use crate::dc_tools::*;
@@ -62,7 +63,7 @@ impl Kml {
pub fn parse(context: &Context, content: impl AsRef<str>) -> Result<Self, Error> {
ensure!(
content.as_ref().len() <= (1 * 1024 * 1024),
content.as_ref().len() <= (1024 * 1024),
"A kml-files with {} bytes is larger than reasonably expected.",
content.as_ref().len()
);
@@ -218,7 +219,7 @@ pub fn send_locations_to_chat(context: &Context, chat_id: u32, seconds: i64) {
msg.text =
Some(context.stock_system_msg(StockMessage::MsgLocationEnabled, "", "", 0));
msg.param.set_int(Param::Cmd, 8);
chat::send_msg(context, chat_id, &mut msg).unwrap();
chat::send_msg(context, chat_id, &mut msg).unwrap_or_default();
} else if 0 == seconds && is_sending_locations_before {
let stock_str =
context.stock_system_msg(StockMessage::MsgLocationDisabled, "", "", 0);
@@ -359,7 +360,7 @@ pub fn get_range(
}
fn is_marker(txt: &str) -> bool {
txt.len() == 1 && txt.chars().next().unwrap() != ' '
txt.len() == 1 && !txt.starts_with(' ')
}
pub fn delete_all(context: &Context) -> Result<(), Error> {
@@ -375,8 +376,7 @@ pub fn get_kml(context: &Context, chat_id: u32) -> Result<(String, u32), Error>
let mut last_added_location_id = 0;
let self_addr = context
.sql
.get_config(context, "configured_addr")
.get_config(Config::ConfiguredAddr)
.unwrap_or_default();
let (locations_send_begin, locations_send_until, locations_last_sent) = context.sql.query_row(
@@ -615,7 +615,7 @@ pub fn job_do_DC_JOB_MAYBE_SEND_LOCATIONS(context: &Context, _job: &Job) {
for (chat_id, mut msg) in msgs.into_iter() {
// TODO: better error handling
chat::send_msg(context, chat_id as u32, &mut msg).unwrap();
chat::send_msg(context, chat_id as u32, &mut msg).unwrap_or_default();
}
}
if 0 != continue_streaming {

View File

@@ -4,6 +4,22 @@ use std::fmt;
use crate::context::Context;
use crate::error::Error;
#[derive(Copy, Clone, Debug, Display, FromPrimitive)]
#[repr(i32)]
#[strum(serialize_all = "snake_case")]
pub enum CertificateChecks {
Automatic = 0,
Strict = 1,
AcceptInvalidHostnames = 2,
AcceptInvalidCertificates = 3,
}
impl Default for CertificateChecks {
fn default() -> Self {
Self::Automatic
}
}
#[derive(Default, Debug)]
pub struct LoginParam {
pub addr: String,
@@ -11,10 +27,14 @@ pub struct LoginParam {
pub mail_user: String,
pub mail_pw: String,
pub mail_port: i32,
/// IMAP TLS options: whether to allow invalid certificates and/or invalid hostnames
pub imap_certificate_checks: CertificateChecks,
pub send_server: String,
pub send_user: String,
pub send_pw: String,
pub send_port: i32,
/// SMTP TLS options: whether to allow invalid certificates and/or invalid hostnames
pub smtp_certificate_checks: CertificateChecks,
pub server_flags: i32,
}
@@ -31,37 +51,53 @@ impl LoginParam {
let key = format!("{}addr", prefix);
let addr = sql
.get_config(context, key)
.get_raw_config(context, key)
.unwrap_or_default()
.trim()
.to_string();
let key = format!("{}mail_server", prefix);
let mail_server = sql.get_config(context, key).unwrap_or_default();
let mail_server = sql.get_raw_config(context, key).unwrap_or_default();
let key = format!("{}mail_port", prefix);
let mail_port = sql.get_config_int(context, key).unwrap_or_default();
let mail_port = sql.get_raw_config_int(context, key).unwrap_or_default();
let key = format!("{}mail_user", prefix);
let mail_user = sql.get_config(context, key).unwrap_or_default();
let mail_user = sql.get_raw_config(context, key).unwrap_or_default();
let key = format!("{}mail_pw", prefix);
let mail_pw = sql.get_config(context, key).unwrap_or_default();
let mail_pw = sql.get_raw_config(context, key).unwrap_or_default();
let key = format!("{}imap_certificate_checks", prefix);
let imap_certificate_checks =
if let Some(certificate_checks) = sql.get_raw_config_int(context, key) {
num_traits::FromPrimitive::from_i32(certificate_checks).unwrap()
} else {
Default::default()
};
let key = format!("{}send_server", prefix);
let send_server = sql.get_config(context, key).unwrap_or_default();
let send_server = sql.get_raw_config(context, key).unwrap_or_default();
let key = format!("{}send_port", prefix);
let send_port = sql.get_config_int(context, key).unwrap_or_default();
let send_port = sql.get_raw_config_int(context, key).unwrap_or_default();
let key = format!("{}send_user", prefix);
let send_user = sql.get_config(context, key).unwrap_or_default();
let send_user = sql.get_raw_config(context, key).unwrap_or_default();
let key = format!("{}send_pw", prefix);
let send_pw = sql.get_config(context, key).unwrap_or_default();
let send_pw = sql.get_raw_config(context, key).unwrap_or_default();
let key = format!("{}smtp_certificate_checks", prefix);
let smtp_certificate_checks =
if let Some(certificate_checks) = sql.get_raw_config_int(context, key) {
num_traits::FromPrimitive::from_i32(certificate_checks).unwrap()
} else {
Default::default()
};
let key = format!("{}server_flags", prefix);
let server_flags = sql.get_config_int(context, key).unwrap_or_default();
let server_flags = sql.get_raw_config_int(context, key).unwrap_or_default();
LoginParam {
addr: addr.to_string(),
@@ -69,10 +105,12 @@ impl LoginParam {
mail_user,
mail_pw,
mail_port,
imap_certificate_checks,
send_server,
send_user,
send_pw,
send_port,
smtp_certificate_checks,
server_flags,
}
}
@@ -91,34 +129,40 @@ impl LoginParam {
let sql = &context.sql;
let key = format!("{}addr", prefix);
sql.set_config(context, key, Some(&self.addr))?;
sql.set_raw_config(context, key, Some(&self.addr))?;
let key = format!("{}mail_server", prefix);
sql.set_config(context, key, Some(&self.mail_server))?;
sql.set_raw_config(context, key, Some(&self.mail_server))?;
let key = format!("{}mail_port", prefix);
sql.set_config_int(context, key, self.mail_port)?;
sql.set_raw_config_int(context, key, self.mail_port)?;
let key = format!("{}mail_user", prefix);
sql.set_config(context, key, Some(&self.mail_user))?;
sql.set_raw_config(context, key, Some(&self.mail_user))?;
let key = format!("{}mail_pw", prefix);
sql.set_config(context, key, Some(&self.mail_pw))?;
sql.set_raw_config(context, key, Some(&self.mail_pw))?;
let key = format!("{}imap_certificate_checks", prefix);
sql.set_raw_config_int(context, key, self.imap_certificate_checks as i32)?;
let key = format!("{}send_server", prefix);
sql.set_config(context, key, Some(&self.send_server))?;
sql.set_raw_config(context, key, Some(&self.send_server))?;
let key = format!("{}send_port", prefix);
sql.set_config_int(context, key, self.send_port)?;
sql.set_raw_config_int(context, key, self.send_port)?;
let key = format!("{}send_user", prefix);
sql.set_config(context, key, Some(&self.send_user))?;
sql.set_raw_config(context, key, Some(&self.send_user))?;
let key = format!("{}send_pw", prefix);
sql.set_config(context, key, Some(&self.send_pw))?;
sql.set_raw_config(context, key, Some(&self.send_pw))?;
let key = format!("{}smtp_certificate_checks", prefix);
sql.set_raw_config_int(context, key, self.smtp_certificate_checks as i32)?;
let key = format!("{}server_flags", prefix);
sql.set_config_int(context, key, self.server_flags)?;
sql.set_raw_config_int(context, key, self.server_flags)?;
Ok(())
}
@@ -133,16 +177,18 @@ impl fmt::Display for LoginParam {
write!(
f,
"{} {}:{}:{}:{} {}:{}:{}:{} {}",
"{} imap:{}:{}:{}:{}:cert_{} smtp:{}:{}:{}:{}:cert_{} {}",
unset_empty(&self.addr),
unset_empty(&self.mail_user),
if !self.mail_pw.is_empty() { pw } else { unset },
unset_empty(&self.mail_server),
self.mail_port,
self.imap_certificate_checks,
unset_empty(&self.send_user),
if !self.send_pw.is_empty() { pw } else { unset },
unset_empty(&self.send_server),
self.send_port,
self.smtp_certificate_checks,
flags_readable,
)
}
@@ -204,3 +250,41 @@ fn get_readable_flags(flags: i32) -> String {
res
}
pub fn dc_build_tls(
certificate_checks: CertificateChecks,
) -> Result<native_tls::TlsConnector, native_tls::Error> {
let mut tls_builder = native_tls::TlsConnector::builder();
match certificate_checks {
CertificateChecks::Automatic => {
// Same as AcceptInvalidCertificates for now.
// TODO: use provider database when it becomes available
tls_builder
.danger_accept_invalid_hostnames(true)
.danger_accept_invalid_certs(true)
}
CertificateChecks::Strict => &mut tls_builder,
CertificateChecks::AcceptInvalidHostnames => {
tls_builder.danger_accept_invalid_hostnames(true)
}
CertificateChecks::AcceptInvalidCertificates => tls_builder
.danger_accept_invalid_hostnames(true)
.danger_accept_invalid_certs(true),
}
.build()
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_certificate_checks_display() {
use std::string::ToString;
assert_eq!(
"accept_invalid_hostnames".to_string(),
CertificateChecks::AcceptInvalidHostnames.to_string()
);
}
}

View File

@@ -1,5 +1,4 @@
use std::path::{Path, PathBuf};
use std::ptr;
use deltachat_derive::{FromSql, ToSql};
@@ -107,6 +106,7 @@ impl Message {
msg.hidden = row.get(18)?;
msg.location_id = row.get(19)?;
msg.chat_blocked = row.get::<_, Option<Blocked>>(20)?.unwrap_or_default();
Ok(msg)
})
}
@@ -339,23 +339,10 @@ impl Message {
}
if let Some(filename) = self.get_file(context) {
if let Ok(mut buf) = dc_read_file(context, filename) {
unsafe {
// just a pointer inside buf, MUST NOT be free()'d
let mut buf_headerline = String::default();
// just a pointer inside buf, MUST NOT be free()'d
let mut buf_setupcodebegin = ptr::null();
if dc_split_armored_data(
buf.as_mut_ptr().cast(),
&mut buf_headerline,
&mut buf_setupcodebegin,
ptr::null_mut(),
ptr::null_mut(),
) && buf_headerline == "-----BEGIN PGP MESSAGE-----"
&& !buf_setupcodebegin.is_null()
{
return Some(to_string(buf_setupcodebegin));
if let Ok(ref buf) = dc_read_file(context, filename) {
if let Ok((typ, headers, _)) = split_armored_data(buf) {
if typ == pgp::armor::BlockType::Message {
return headers.get(crate::pgp::HEADER_SETUPCODE).cloned();
}
}
}
@@ -494,12 +481,10 @@ impl Lot {
} else {
self.text1 = None;
}
} else if let Some(contact) = contact {
self.text1 = Some(contact.get_first_name().into());
} else {
if let Some(contact) = contact {
self.text1 = Some(contact.get_first_name().into());
} else {
self.text1 = None;
}
self.text1 = None;
}
self.text1_meaning = Meaning::Text1Username;
}
@@ -526,7 +511,7 @@ pub fn get_msg_info(context: &Context, msg_id: u32) -> String {
return ret;
}
let msg = msg.unwrap();
let msg = msg.unwrap_or_default();
let rawtxt: Option<String> = context.sql.query_get_value(
context,
@@ -538,7 +523,7 @@ pub fn get_msg_info(context: &Context, msg_id: u32) -> String {
ret += &format!("Cannot load message #{}.", msg_id as usize);
return ret;
}
let rawtxt = rawtxt.unwrap();
let rawtxt = rawtxt.unwrap_or_default();
let rawtxt = dc_truncate(rawtxt.trim(), 100000, false);
let fts = dc_timestamp_to_str(msg.get_timestamp());
@@ -618,9 +603,8 @@ pub fn get_msg_info(context: &Context, msg_id: u32) -> String {
}
ret += "\n";
match msg.param.get(Param::Error) {
Some(err) => ret += &format!("Error: {}", err),
_ => {}
if let Some(err) = msg.param.get(Param::Error) {
ret += &format!("Error: {}", err)
}
if let Some(path) = msg.get_file(context) {
@@ -688,7 +672,7 @@ pub fn get_mime_headers(context: &Context, msg_id: u32) -> Option<String> {
}
pub fn delete_msgs(context: &Context, msg_ids: &[u32]) {
for msg_id in msg_ids.into_iter() {
for msg_id in msg_ids.iter() {
update_msg_chat_id(context, *msg_id, DC_CHAT_ID_TRASH);
job_add(
context,
@@ -728,7 +712,7 @@ pub fn markseen_msgs(context: &Context, msg_ids: &[u32]) -> bool {
"SELECT m.state, c.blocked FROM msgs m LEFT JOIN chats c ON c.id=m.chat_id WHERE m.id=? AND m.chat_id>9",
|mut stmt, _| {
let mut res = Vec::with_capacity(msg_ids.len());
for id in msg_ids.into_iter() {
for id in msg_ids.iter() {
let query_res = stmt.query_row(params![*id as i32], |row| {
Ok((row.get::<_, MessageState>(0)?, row.get::<_, Option<Blocked>>(1)?.unwrap_or_default()))
});
@@ -748,7 +732,7 @@ pub fn markseen_msgs(context: &Context, msg_ids: &[u32]) -> bool {
return false;
}
let mut send_event = false;
let msgs = msgs.unwrap();
let msgs = msgs.unwrap_or_default();
for (id, curr_state, curr_blocked) in msgs.into_iter() {
if curr_blocked == Blocked::Not {
@@ -798,7 +782,7 @@ pub fn star_msgs(context: &Context, msg_ids: &[u32], star: bool) -> bool {
context
.sql
.prepare("UPDATE msgs SET starred=? WHERE id=?;", |mut stmt, _| {
for msg_id in msg_ids.into_iter() {
for msg_id in msg_ids.iter() {
stmt.execute(params![star as i32, *msg_id as i32])?;
}
Ok(())
@@ -818,6 +802,7 @@ pub fn get_summarytext_by_raw(
let prefix = match viewtype {
Viewtype::Image => context.stock_str(StockMessage::Image).into_owned(),
Viewtype::Gif => context.stock_str(StockMessage::Gif).into_owned(),
Viewtype::Sticker => context.stock_str(StockMessage::Sticker).into_owned(),
Viewtype::Video => context.stock_str(StockMessage::Video).into_owned(),
Viewtype::Voice => context.stock_str(StockMessage::VoiceMessage).into_owned(),
Viewtype::Audio | Viewtype::File => {
@@ -836,7 +821,7 @@ pub fn get_summarytext_by_raw(
} else {
None
}
.unwrap_or("ErrFileName".to_string());
.unwrap_or_else(|| "ErrFileName".to_string());
let label = context.stock_str(if viewtype == Viewtype::Audio {
StockMessage::Audio
@@ -984,7 +969,7 @@ pub fn mdn_from_ext(
context.sql.execute(
"INSERT INTO msgs_mdns (msg_id, contact_id, timestamp_sent) VALUES (?, ?, ?);",
params![*ret_msg_id as i32, from_id as i32, timestamp_sent],
).unwrap(); // TODO: better error handling
).unwrap_or_default(); // TODO: better error handling
}
// Normal chat? that's quite easy.

View File

@@ -13,10 +13,11 @@ use mmime::mmapstring::*;
use mmime::other::*;
use crate::chat::{self, Chat};
use crate::config::Config;
use crate::constants::*;
use crate::contact::*;
use crate::context::{get_version_str, Context};
use crate::dc_mimeparser::{mailmime_find_mailimf_fields, SystemMessage};
use crate::dc_mimeparser::SystemMessage;
use crate::dc_strencode::*;
use crate::dc_tools::*;
use crate::e2ee::*;
@@ -59,11 +60,13 @@ pub struct MimeFactory<'a> {
impl<'a> MimeFactory<'a> {
fn new(context: &'a Context, msg: Message) -> Self {
let cget = |context: &Context, name: &str| context.sql.get_config(context, name);
MimeFactory {
from_addr: cget(&context, "configured_addr").unwrap_or_default(),
from_displayname: cget(&context, "displayname").unwrap_or_default(),
selfstatus: cget(&context, "selfstatus")
from_addr: context
.get_config(Config::ConfiguredAddr)
.unwrap_or_default(),
from_displayname: context.get_config(Config::Displayname).unwrap_or_default(),
selfstatus: context
.get_config(Config::Selfstatus)
.unwrap_or_else(|| context.stock_str(StockMessage::StatusLine).to_string()),
recipients_names: Vec::with_capacity(5),
recipients_addr: Vec::with_capacity(5),
@@ -106,15 +109,10 @@ impl<'a> MimeFactory<'a> {
}
pub fn load_mdn(context: &'a Context, msg_id: u32) -> Result<MimeFactory, Error> {
if 0 == context
.sql
.get_config_int(context, "mdns_enabled")
.unwrap_or_else(|| 1)
{
// MDNs not enabled - check this is late, in the job. the use may have changed its
// choice while offline ...
bail!("MDNs disabled ")
if !context.get_config_bool(Config::MdnsEnabled) {
// MDNs not enabled - check this is late, in the job. the
// user may have changed its choice while offline ...
bail!("MDNs meanwhile disabled")
}
let msg = Message::load_from_db(context, msg_id)?;
@@ -141,52 +139,35 @@ impl<'a> MimeFactory<'a> {
}
/*******************************************************************************
* Render
* Render a basic email
******************************************************************************/
// restrict unsafe to parts, introduce wrapmime helpers where appropriate
// XXX restrict unsafe to parts, introduce wrapmime helpers where appropriate
pub unsafe fn render(&mut self) -> Result<(), Error> {
if self.loaded == Loaded::Nothing || !self.out.is_null() {
bail!("Invalid use of mimefactory-object.");
}
let context = &self.context;
let from = wrapmime::new_mailbox_list(&self.from_displayname, &self.from_addr);
/* create basic mail
*************************************************************************/
let from: *mut mailimf_mailbox_list = mailimf_mailbox_list_new_empty();
mailimf_mailbox_list_add(
from,
mailimf_mailbox_new(
if !self.from_displayname.is_empty() {
dc_encode_header_words(&self.from_displayname).strdup()
} else {
ptr::null_mut()
},
self.from_addr.strdup(),
),
);
let mut to: *mut mailimf_address_list = ptr::null_mut();
if !self.recipients_names.is_empty() && !self.recipients_addr.is_empty() {
to = mailimf_address_list_new_empty();
let name_iter = self.recipients_names.iter();
let addr_iter = self.recipients_addr.iter();
for (name, addr) in name_iter.zip(addr_iter) {
mailimf_address_list_add(
to,
mailimf_address_new(
MAILIMF_ADDRESS_MAILBOX as libc::c_int,
mailimf_mailbox_new(
if !name.is_empty() {
dc_encode_header_words(&name).strdup()
} else {
ptr::null_mut()
},
addr.strdup(),
),
ptr::null_mut(),
let to = mailimf_address_list_new_empty();
let name_iter = self.recipients_names.iter();
let addr_iter = self.recipients_addr.iter();
for (name, addr) in name_iter.zip(addr_iter) {
mailimf_address_list_add(
to,
mailimf_address_new(
MAILIMF_ADDRESS_MAILBOX as libc::c_int,
mailimf_mailbox_new(
if !name.is_empty() {
dc_encode_header_words(&name).strdup()
} else {
ptr::null_mut()
},
addr.strdup(),
),
);
}
ptr::null_mut(),
),
);
}
let references_list = if !self.references.is_empty() {
dc_str_to_clist(&self.references, " ")
@@ -198,6 +179,7 @@ impl<'a> MimeFactory<'a> {
} else {
ptr::null_mut()
};
let imf_fields = mailimf_fields_new_with_data_all(
mailimf_get_date(self.timestamp as i64),
from,
@@ -402,15 +384,8 @@ impl<'a> MimeFactory<'a> {
&fingerprint,
);
}
match msg.param.get(Param::Arg4) {
Some(id) => {
wrapmime::new_custom_field(
imf_fields,
"Secure-Join-Group",
&id,
);
}
None => {}
if let Some(id) = msg.param.get(Param::Arg4) {
wrapmime::new_custom_field(imf_fields, "Secure-Join-Group", &id);
};
}
}
@@ -435,6 +410,10 @@ impl<'a> MimeFactory<'a> {
}
}
if self.msg.type_0 == Viewtype::Sticker {
wrapmime::new_custom_field(imf_fields, "Chat-Content", "sticker");
}
if self.msg.type_0 == Viewtype::Voice
|| self.msg.type_0 == Viewtype::Audio
|| self.msg.type_0 == Viewtype::Video
@@ -594,7 +573,6 @@ impl<'a> MimeFactory<'a> {
wrapmime::set_body_text(mach_mime_part, &message_text2)?;
mailmime_add_part(multipart, mach_mime_part);
force_plaintext = DC_FP_NO_AUTOCRYPT_HEADER;
info!(context, "sending MDM {:?}", message_text2);
/* currently, we do not send MDNs encrypted:
- in a multi-device-setup that is not set up properly, MDNs would disturb the communication as they
are send automatically which may lead to spreading outdated Autocrypt headers.
@@ -645,7 +623,7 @@ impl<'a> MimeFactory<'a> {
);
/*just a pointer into mailmime structure, must not be freed*/
let imffields_unprotected = mailmime_find_mailimf_fields(message);
let imffields_unprotected = wrapmime::mailmime_find_mailimf_fields(message);
ensure!(
!imffields_unprotected.is_null(),
"could not find mime fields"
@@ -657,17 +635,18 @@ impl<'a> MimeFactory<'a> {
let aheader = encrypt_helper.get_aheader().to_string();
wrapmime::new_custom_field(imffields_unprotected, "Autocrypt", &aheader);
}
let mut finalized = false;
if force_plaintext == 0 {
finalized = encrypt_helper.try_encrypt(
let finalized = if force_plaintext == 0 {
encrypt_helper.try_encrypt(
self,
e2ee_guaranteed,
min_verified,
do_gossip,
message,
imffields_unprotected,
)?;
}
)?
} else {
false
};
if !finalized {
self.finalize_mime_message(message, false, false)?;
}
@@ -692,31 +671,28 @@ impl<'a> MimeFactory<'a> {
.push(factory.from_displayname.to_string());
factory.recipients_addr.push(factory.from_addr.to_string());
} else {
context
.sql
.query_map(
"SELECT c.authname, c.addr \
FROM chats_contacts cc \
LEFT JOIN contacts c ON cc.contact_id=c.id \
WHERE cc.chat_id=? AND cc.contact_id>9;",
params![factory.msg.chat_id as i32],
|row| {
let authname: String = row.get(0)?;
let addr: String = row.get(1)?;
Ok((authname, addr))
},
|rows| {
for row in rows {
let (authname, addr) = row?;
if !vec_contains_lowercase(&factory.recipients_addr, &addr) {
factory.recipients_addr.push(addr);
factory.recipients_names.push(authname);
}
context.sql.query_map(
"SELECT c.authname, c.addr \
FROM chats_contacts cc \
LEFT JOIN contacts c ON cc.contact_id=c.id \
WHERE cc.chat_id=? AND cc.contact_id>9;",
params![factory.msg.chat_id as i32],
|row| {
let authname: String = row.get(0)?;
let addr: String = row.get(1)?;
Ok((authname, addr))
},
|rows| {
for row in rows {
let (authname, addr) = row?;
if !vec_contains_lowercase(&factory.recipients_addr, &addr) {
factory.recipients_addr.push(addr);
factory.recipients_names.push(authname);
}
Ok(())
},
)
.unwrap();
}
Ok(())
},
)?;
let command = factory.msg.param.get_cmd();
let msg = &factory.msg;
@@ -726,8 +702,7 @@ impl<'a> MimeFactory<'a> {
let email_to_remove = msg.param.get(Param::Arg).unwrap_or_default();
let self_addr = context
.sql
.get_config(context, "configured_addr")
.get_config(Config::ConfiguredAddr)
.unwrap_or_default();
if !email_to_remove.is_empty() && email_to_remove != self_addr {
@@ -739,10 +714,7 @@ impl<'a> MimeFactory<'a> {
}
if command != SystemMessage::AutocryptSetupMessage
&& command != SystemMessage::SecurejoinMessage
&& 0 != context
.sql
.get_config_int(context, "mdns_enabled")
.unwrap_or_else(|| 1)
&& context.get_config_bool(Config::MdnsEnabled)
{
factory.req_mdn = true;
}
@@ -923,15 +895,13 @@ fn build_body_file(
wrapmime::append_ct_param(content, "name", &filename_encoded)?;
let mime_sub = mailmime_new_empty(content, mime_fields);
let abs_path = dc_get_abs_path(context, path_filename)
.to_c_string()
.unwrap();
let abs_path = dc_get_abs_path(context, path_filename).to_c_string()?;
mailmime_set_body_file(mime_sub, dc_strdup(abs_path.as_ptr()));
Ok((mime_sub, filename_to_send))
}
}
pub(crate) fn vec_contains_lowercase(vec: &Vec<String>, part: &str) -> bool {
pub(crate) fn vec_contains_lowercase(vec: &[String], part: &str) -> bool {
let partlc = part.to_lowercase();
for cur in vec.iter() {
if cur.to_lowercase() == partlc {

View File

@@ -50,7 +50,7 @@ pub fn dc_get_oauth2_url(
if let Some(oauth2) = Oauth2::from_address(addr) {
if context
.sql
.set_config(
.set_raw_config(
context,
"oauth2_pending_redirect_uri",
Some(redirect_uri.as_ref()),
@@ -82,17 +82,17 @@ pub fn dc_get_oauth2_access_token(
// read generated token
if !regenerate && !is_expired(context) {
let access_token = context.sql.get_config(context, "oauth2_access_token");
let access_token = context.sql.get_raw_config(context, "oauth2_access_token");
if access_token.is_some() {
// success
return access_token;
}
}
let refresh_token = context.sql.get_config(context, "oauth2_refresh_token");
let refresh_token = context.sql.get_raw_config(context, "oauth2_refresh_token");
let refresh_token_for = context
.sql
.get_config(context, "oauth2_refresh_token_for")
.get_raw_config(context, "oauth2_refresh_token_for")
.unwrap_or_else(|| "unset".into());
let (redirect_uri, token_url, update_redirect_uri_on_success) =
@@ -101,7 +101,7 @@ pub fn dc_get_oauth2_access_token(
(
context
.sql
.get_config(context, "oauth2_pending_redirect_uri")
.get_raw_config(context, "oauth2_pending_redirect_uri")
.unwrap_or_else(|| "unset".into()),
oauth2.init_token,
true,
@@ -114,7 +114,7 @@ pub fn dc_get_oauth2_access_token(
(
context
.sql
.get_config(context, "oauth2_redirect_uri")
.get_raw_config(context, "oauth2_redirect_uri")
.unwrap_or_else(|| "unset".into()),
oauth2.refresh_token,
false,
@@ -159,11 +159,11 @@ pub fn dc_get_oauth2_access_token(
if let Some(ref token) = response.refresh_token {
context
.sql
.set_config(context, "oauth2_refresh_token", Some(token))
.set_raw_config(context, "oauth2_refresh_token", Some(token))
.ok();
context
.sql
.set_config(context, "oauth2_refresh_token_for", Some(code.as_ref()))
.set_raw_config(context, "oauth2_refresh_token_for", Some(code.as_ref()))
.ok();
}
@@ -172,7 +172,7 @@ pub fn dc_get_oauth2_access_token(
if let Some(ref token) = response.access_token {
context
.sql
.set_config(context, "oauth2_access_token", Some(token))
.set_raw_config(context, "oauth2_access_token", Some(token))
.ok();
let expires_in = response
.expires_in
@@ -181,13 +181,13 @@ pub fn dc_get_oauth2_access_token(
.unwrap_or_else(|| 0);
context
.sql
.set_config_int64(context, "oauth2_timestamp_expires", expires_in)
.set_raw_config_int64(context, "oauth2_timestamp_expires", expires_in)
.ok();
if update_redirect_uri_on_success {
context
.sql
.set_config(context, "oauth2_redirect_uri", Some(redirect_uri.as_ref()))
.set_raw_config(context, "oauth2_redirect_uri", Some(redirect_uri.as_ref()))
.ok();
}
} else {
@@ -207,14 +207,8 @@ pub fn dc_get_oauth2_addr(
addr: impl AsRef<str>,
code: impl AsRef<str>,
) -> Option<String> {
let oauth2 = Oauth2::from_address(addr.as_ref());
if oauth2.is_none() {
return None;
}
let oauth2 = oauth2.unwrap();
if oauth2.get_userinfo.is_none() {
return None;
}
let oauth2 = Oauth2::from_address(addr.as_ref())?;
oauth2.get_userinfo?;
if let Some(access_token) =
dc_get_oauth2_access_token(context, addr.as_ref(), code.as_ref(), false)
@@ -299,7 +293,7 @@ impl Oauth2 {
fn is_expired(context: &Context) -> bool {
let expire_timestamp = context
.sql
.get_config_int64(context, "oauth2_timestamp_expires")
.get_raw_config_int64(context, "oauth2_timestamp_expires")
.unwrap_or_default();
if expire_timestamp <= 0 {

View File

@@ -12,65 +12,65 @@ use crate::error;
#[repr(u8)]
pub enum Param {
/// For messages and jobs
File = 'f' as u8,
File = b'f',
/// For Messages
Width = 'w' as u8,
Width = b'w',
/// For Messages
Height = 'h' as u8,
Height = b'h',
/// For Messages
Duration = 'd' as u8,
Duration = b'd',
/// For Messages
MimeType = 'm' as u8,
MimeType = b'm',
/// For Messages: message is encryoted, outgoing: guarantee E2EE or the message is not send
GuranteeE2ee = 'c' as u8,
GuranteeE2ee = b'c',
/// For Messages: decrypted with validation errors or without mutual set, if neither
/// 'c' nor 'e' are preset, the messages is only transport encrypted.
ErroneousE2ee = 'e' as u8,
ErroneousE2ee = b'e',
/// For Messages: force unencrypted message, either `ForcePlaintext::AddAutocryptHeader` (1),
/// `ForcePlaintext::NoAutocryptHeader` (2) or 0.
ForcePlaintext = 'u' as u8,
ForcePlaintext = b'u',
/// For Messages
WantsMdn = 'r' as u8,
WantsMdn = b'r',
/// For Messages
Forwarded = 'a' as u8,
Forwarded = b'a',
/// For Messages
Cmd = 'S' as u8,
Cmd = b'S',
/// For Messages
Arg = 'E' as u8,
Arg = b'E',
/// For Messages
Arg2 = 'F' as u8,
Arg2 = b'F',
/// For Messages
Arg3 = 'G' as u8,
Arg3 = b'G',
/// For Messages
Arg4 = 'H' as u8,
Arg4 = b'H',
/// For Messages
Error = 'L' as u8,
Error = b'L',
/// For Messages: space-separated list of messaged IDs of forwarded copies.
PrepForwards = 'P' as u8,
PrepForwards = b'P',
/// For Jobs
SetLatitude = 'l' as u8,
SetLatitude = b'l',
/// For Jobs
SetLongitude = 'n' as u8,
SetLongitude = b'n',
/// For Jobs
ServerFolder = 'Z' as u8,
ServerFolder = b'Z',
/// For Jobs
ServerUid = 'z' as u8,
ServerUid = b'z',
/// For Jobs
AlsoMove = 'M' as u8,
AlsoMove = b'M',
/// For Jobs: space-separated list of message recipients
Recipients = 'R' as u8,
Recipients = b'R',
// For Groups
Unpromoted = 'U' as u8,
Unpromoted = b'U',
// For Groups and Contacts
ProfileImage = 'i' as u8,
ProfileImage = b'i',
// For Chats
Selftalk = 'K' as u8,
Selftalk = b'K',
// For QR
Auth = 's' as u8,
Auth = b's',
// For QR
GroupId = 'x' as u8,
GroupId = b'x',
// For QR
GroupName = 'g' as u8,
GroupName = b'g',
}
/// Possible values for `Param::ForcePlaintext`.
@@ -122,8 +122,8 @@ impl str::FromStr for Params {
ensure!(key.is_some(), "Missing key");
ensure!(value.is_some(), "Missing value");
let key = key.unwrap().trim();
let value = value.unwrap().trim();
let key = key.unwrap_or_default().trim();
let value = value.unwrap_or_default().trim();
if let Some(key) = Param::from_u8(key.as_bytes()[0]) {
inner.insert(key, value.to_string());

View File

@@ -400,7 +400,8 @@ impl<'a> Peerstate<'a> {
&self.verified_key_fingerprint,
&self.addr,
],
)?
)?;
reset_gossiped_timestamp(self.context, 0);
} else if self.to_save == Some(ToSave::Timestamps) {
sql::execute(
self.context,
@@ -416,10 +417,6 @@ impl<'a> Peerstate<'a> {
)?;
}
if self.to_save == Some(ToSave::All) || create {
reset_gossiped_timestamp(self.context, 0);
}
Ok(())
}
@@ -475,7 +472,7 @@ mod tests {
"failed to save to db"
);
let peerstate_new = Peerstate::from_addr(&ctx.ctx, &ctx.ctx.sql, addr.into())
let peerstate_new = Peerstate::from_addr(&ctx.ctx, &ctx.ctx.sql, addr)
.expect("failed to load peerstate from db");
// clear to_save, as that is not persissted
@@ -487,6 +484,44 @@ mod tests {
assert_eq!(peerstate, peerstate_new2);
}
#[test]
fn test_peerstate_double_create() {
let ctx = crate::test_utils::dummy_context();
let addr = "hello@mail.com";
let pub_key = crate::key::Key::from_base64(
include_str!("../test-data/key/public.asc"),
KeyType::Public,
)
.unwrap();
let peerstate = Peerstate {
context: &ctx.ctx,
addr: Some(addr.into()),
last_seen: 10,
last_seen_autocrypt: 11,
prefer_encrypt: EncryptPreference::Mutual,
public_key: Some(pub_key.clone()),
public_key_fingerprint: Some(pub_key.fingerprint()),
gossip_key: None,
gossip_timestamp: 12,
gossip_key_fingerprint: None,
verified_key: None,
verified_key_fingerprint: None,
to_save: Some(ToSave::All),
degrade_event: None,
};
assert!(
peerstate.save_to_db(&ctx.ctx.sql, true).is_ok(),
"failed to save"
);
assert!(
peerstate.save_to_db(&ctx.ctx.sql, true).is_ok(),
"double-call with create failed"
);
}
#[test]
fn test_peerstate_with_empty_gossip_key_save_to_db() {
let ctx = crate::test_utils::dummy_context();
@@ -520,7 +555,7 @@ mod tests {
"failed to save"
);
let peerstate_new = Peerstate::from_addr(&ctx.ctx, &ctx.ctx.sql, addr.into())
let peerstate_new = Peerstate::from_addr(&ctx.ctx, &ctx.ctx.sql, addr)
.expect("failed to load peerstate from db");
// clear to_save, as that is not persissted

View File

@@ -1,9 +1,8 @@
use std::collections::HashSet;
use std::collections::{BTreeMap, HashSet};
use std::convert::TryInto;
use std::io::Cursor;
use std::ptr;
use libc::{strchr, strlen, strncmp, strspn, strstr};
use pgp::armor::BlockType;
use pgp::composed::{
Deserializable, KeyType as PgpKeyType, Message, SecretKeyParamsBuilder, SignedPublicKey,
SignedSecretKey, SubkeyParamsBuilder,
@@ -12,126 +11,43 @@ use pgp::crypto::{HashAlgorithm, SymmetricKeyAlgorithm};
use pgp::types::{CompressionAlgorithm, KeyTrait, SecretKeyTrait, StringToKey};
use rand::thread_rng;
use crate::dc_tools::*;
use crate::error::Error;
use crate::key::*;
use crate::keyring::*;
pub unsafe fn dc_split_armored_data(
buf: *mut libc::c_char,
ret_headerline: *mut String,
ret_setupcodebegin: *mut *const libc::c_char,
ret_preferencrypt: *mut *const libc::c_char,
ret_base64: *mut *const libc::c_char,
) -> bool {
let mut success = false;
let mut line_chars: libc::size_t = 0;
let mut line: *mut libc::c_char = buf;
let mut p1: *mut libc::c_char = buf;
let mut p2: *mut libc::c_char;
let mut headerline: *mut libc::c_char = ptr::null_mut();
let mut base64: *mut libc::c_char = ptr::null_mut();
if !ret_setupcodebegin.is_null() {
*ret_setupcodebegin = ptr::null_mut();
}
if !ret_preferencrypt.is_null() {
*ret_preferencrypt = ptr::null();
}
if !ret_base64.is_null() {
*ret_base64 = ptr::null();
}
if !buf.is_null() {
dc_remove_cr_chars(buf);
while 0 != *p1 {
if *p1 as libc::c_int == '\n' as i32 {
*line.offset(line_chars as isize) = 0i32 as libc::c_char;
if headerline.is_null() {
dc_trim(line);
if strncmp(
line,
b"-----BEGIN \x00" as *const u8 as *const libc::c_char,
1,
) == 0i32
&& strncmp(
&mut *line.offset(strlen(line).wrapping_sub(5) as isize),
b"-----\x00" as *const u8 as *const libc::c_char,
5,
) == 0i32
{
headerline = line;
*ret_headerline = as_str(headerline).to_string();
}
} else if strspn(line, b"\t\r\n \x00" as *const u8 as *const libc::c_char)
== strlen(line)
{
base64 = p1.offset(1isize);
break;
} else {
p2 = strchr(line, ':' as i32);
if p2.is_null() {
*line.offset(line_chars as isize) = '\n' as i32 as libc::c_char;
base64 = line;
break;
} else {
*p2 = 0i32 as libc::c_char;
dc_trim(line);
if strcasecmp(
line,
b"Passphrase-Begin\x00" as *const u8 as *const libc::c_char,
) == 0i32
{
p2 = p2.offset(1isize);
dc_trim(p2);
if !ret_setupcodebegin.is_null() {
*ret_setupcodebegin = p2
}
} else if strcasecmp(
line,
b"Autocrypt-Prefer-Encrypt\x00" as *const u8 as *const libc::c_char,
) == 0i32
{
p2 = p2.offset(1isize);
dc_trim(p2);
if !ret_preferencrypt.is_null() {
*ret_preferencrypt = p2
}
}
}
}
p1 = p1.offset(1isize);
line = p1;
line_chars = 0;
} else {
p1 = p1.offset(1isize);
line_chars = line_chars.wrapping_add(1)
}
}
if !(headerline.is_null() || base64.is_null()) {
/* now, line points to beginning of base64 data, search end */
/*the trailing space makes sure, this is not a normal base64 sequence*/
p1 = strstr(base64, b"-----END \x00" as *const u8 as *const libc::c_char);
if !(p1.is_null()
|| strncmp(
p1.offset(9isize),
headerline.offset(11isize),
strlen(headerline.offset(11isize)),
) != 0i32)
{
*p1 = 0i32 as libc::c_char;
dc_trim(base64);
if !ret_base64.is_null() {
*ret_base64 = base64
}
success = true;
}
}
}
pub const HEADER_AUTOCRYPT: &str = "autocrypt-prefer-encrypt";
pub const HEADER_SETUPCODE: &str = "passphrase-begin";
success
/// Split data from PGP Armored Data as defined in https://tools.ietf.org/html/rfc4880#section-6.2.
///
/// Returns (type, headers, base64 encoded body).
pub fn split_armored_data(
buf: &[u8],
) -> Result<(BlockType, BTreeMap<String, String>, Vec<u8>), Error> {
use std::io::Read;
let cursor = Cursor::new(buf);
let mut dearmor = pgp::armor::Dearmor::new(cursor);
let mut bytes = Vec::with_capacity(buf.len());
dearmor.read_to_end(&mut bytes)?;
ensure!(dearmor.typ.is_some(), "Failed to parse type");
let typ = dearmor.typ.unwrap();
// normalize headers
let headers = dearmor
.headers
.into_iter()
.map(|(key, value)| (key.trim().to_lowercase(), value.trim().to_string()))
.collect();
Ok((typ, headers, bytes))
}
/// Create a new key pair.
pub fn dc_pgp_create_keypair(addr: impl AsRef<str>) -> Option<(Key, Key)> {
pub fn create_keypair(addr: impl AsRef<str>) -> Option<(Key, Key)> {
let user_id = format!("<{}>", addr.as_ref());
let key_params = SecretKeyParamsBuilder::default()
@@ -181,7 +97,7 @@ pub fn dc_pgp_create_keypair(addr: impl AsRef<str>) -> Option<(Key, Key)> {
Some((Key::Public(public_key), Key::Secret(private_key)))
}
pub fn dc_pgp_pk_encrypt(
pub fn pk_encrypt(
plain: &[u8],
public_keys_for_encryption: &Keyring,
private_key_for_signing: Option<&Key>,
@@ -189,7 +105,7 @@ pub fn dc_pgp_pk_encrypt(
let lit_msg = Message::new_literal_bytes("", plain);
let pkeys: Vec<&SignedPublicKey> = public_keys_for_encryption
.keys()
.into_iter()
.iter()
.filter_map(|key| {
let k: &Key = &key;
k.try_into().ok()
@@ -218,7 +134,7 @@ pub fn dc_pgp_pk_encrypt(
Ok(encoded_msg)
}
pub fn dc_pgp_pk_decrypt(
pub fn pk_decrypt(
ctext: &[u8],
private_keys_for_decryption: &Keyring,
public_keys_for_validation: &Keyring,
@@ -267,7 +183,7 @@ pub fn dc_pgp_pk_decrypt(
}
/// Symmetric encryption.
pub fn dc_pgp_symm_encrypt(passphrase: &str, plain: &[u8]) -> Result<String, Error> {
pub fn symm_encrypt(passphrase: &str, plain: &[u8]) -> Result<String, Error> {
let mut rng = thread_rng();
let lit_msg = Message::new_literal_bytes("", plain);
@@ -281,8 +197,11 @@ pub fn dc_pgp_symm_encrypt(passphrase: &str, plain: &[u8]) -> Result<String, Err
}
/// Symmetric decryption.
pub fn dc_pgp_symm_decrypt(passphrase: &str, ctext: &[u8]) -> Result<Vec<u8>, Error> {
let enc_msg = Message::from_bytes(Cursor::new(ctext))?;
pub fn symm_decrypt<T: std::io::Read + std::io::Seek>(
passphrase: &str,
ctext: T,
) -> Result<Vec<u8>, Error> {
let (enc_msg, _) = Message::from_armor_single(ctext)?;
let decryptor = enc_msg.decrypt_with_password(|| passphrase.into())?;
let msgs = decryptor.collect::<Result<Vec<_>, _>>()?;
@@ -293,3 +212,35 @@ pub fn dc_pgp_symm_decrypt(passphrase: &str, ctext: &[u8]) -> Result<Vec<u8>, Er
None => bail!("Decrypted message is empty"),
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_split_armored_data_1() {
let (typ, _headers, base64) = split_armored_data(
b"-----BEGIN PGP MESSAGE-----\nNoVal:\n\naGVsbG8gd29ybGQ=\n-----END PGP MESSAGE----",
)
.unwrap();
assert_eq!(typ, BlockType::Message);
assert!(!base64.is_empty());
assert_eq!(
std::string::String::from_utf8(base64).unwrap(),
"hello world"
);
}
#[test]
fn test_split_armored_data_2() {
let (typ, headers, base64) = split_armored_data(
b"-----BEGIN PGP PRIVATE KEY BLOCK-----\nAutocrypt-Prefer-Encrypt: mutual \n\naGVsbG8gd29ybGQ=\n-----END PGP PRIVATE KEY BLOCK-----"
)
.unwrap();
assert_eq!(typ, BlockType::PrivateKey);
assert!(!base64.is_empty());
assert_eq!(headers.get(HEADER_AUTOCRYPT), Some(&"mutual".to_string()));
}
}

View File

@@ -3,7 +3,6 @@ use percent_encoding::{utf8_percent_encode, AsciiSet, NON_ALPHANUMERIC};
use crate::aheader::EncryptPreference;
use crate::chat::{self, Chat};
use crate::config::*;
use crate::configure::*;
use crate::constants::*;
use crate::contact::*;
use crate::context::Context;
@@ -82,10 +81,7 @@ pub fn dc_get_securejoin_qr(context: &Context, group_chat_id: u32) -> Option<Str
}
};
let self_name = context
.sql
.get_config(context, "displayname")
.unwrap_or_default();
let self_name = context.get_config(Config::Displayname).unwrap_or_default();
fingerprint = match get_self_fingerprint(context) {
Some(fp) => fp,
@@ -160,7 +156,7 @@ pub fn dc_join_securejoin(context: &Context, qr: &str) -> u32 {
bob.qr_scan = None;
if ongoing_allocated {
dc_free_ongoing(context);
context.free_ongoing();
}
ret_chat_id as u32
};
@@ -173,7 +169,7 @@ pub fn dc_join_securejoin(context: &Context, qr: &str) -> u32 {
info!(context, "Requesting secure-join ...",);
ensure_secret_key_exists(context).ok();
if !dc_alloc_ongoing(context) {
if !context.alloc_ongoing() {
return cleanup(&context, contact_chat_id, false, join_vg);
}
let qr_scan = check_qr(context, &qr);
@@ -187,7 +183,7 @@ pub fn dc_join_securejoin(context: &Context, qr: &str) -> u32 {
error!(context, "Unknown contact.",);
return cleanup(&context, contact_chat_id, true, join_vg);
}
if check_exit(context) {
if context.shall_stop_ongoing() {
return cleanup(&context, contact_chat_id, true, join_vg);
}
join_vg = qr_scan.get_state() == LotState::QrAskVerifyGroup;
@@ -213,7 +209,7 @@ pub fn dc_join_securejoin(context: &Context, qr: &str) -> u32 {
info!(context, "Taking protocol shortcut.");
context.bob.write().unwrap().expects = DC_VC_CONTACT_CONFIRM;
joiner_progress!(context, chat_id_2_contact_id(context, contact_chat_id), 400);
let own_fingerprint = get_self_fingerprint(context).unwrap();
let own_fingerprint = get_self_fingerprint(context).unwrap_or_default();
send_handshake_msg(
context,
contact_chat_id,
@@ -243,21 +239,12 @@ pub fn dc_join_securejoin(context: &Context, qr: &str) -> u32 {
}
// Bob -> Alice
while !check_exit(&context) {
while !context.shall_stop_ongoing() {
std::thread::sleep(std::time::Duration::new(0, 3_000_000));
}
cleanup(&context, contact_chat_id, true, join_vg)
}
fn check_exit(context: &Context) -> bool {
context
.running_state
.clone()
.read()
.unwrap()
.shall_stop_ongoing
}
fn send_handshake_msg(
context: &Context,
contact_chat_id: u32,
@@ -294,7 +281,7 @@ fn send_handshake_msg(
msg.param.set_int(Param::GuranteeE2ee, 1);
}
// TODO. handle cleanup on error
chat::send_msg(context, contact_chat_id, &mut msg).unwrap();
chat::send_msg(context, contact_chat_id, &mut msg).unwrap_or_default();
}
fn chat_id_2_contact_id(context: &Context, contact_chat_id: u32) -> u32 {
@@ -639,7 +626,7 @@ pub fn handle_securejoin_handshake(
fn end_bobs_joining(context: &Context, status: libc::c_int) {
context.bob.write().unwrap().status = status;
dc_stop_ongoing_process(context);
context.stop_ongoing();
}
fn secure_connection_established(context: &Context, contact_chat_id: u32) {
@@ -678,7 +665,9 @@ fn mark_peer_as_verified(context: &Context, fingerprint: impl AsRef<str>) -> Res
if peerstate.set_verified(1, fingerprint.as_ref(), 2) {
peerstate.prefer_encrypt = EncryptPreference::Mutual;
peerstate.to_save = Some(ToSave::All);
peerstate.save_to_db(&context.sql, false).unwrap();
peerstate
.save_to_db(&context.sql, false)
.unwrap_or_default();
return Ok(());
}
}
@@ -696,7 +685,7 @@ fn encrypted_and_signed(mimeparser: &MimeParser, expected_fingerprint: impl AsRe
if !mimeparser.encrypted {
warn!(mimeparser.context, "Message not encrypted.",);
false
} else if mimeparser.signatures.len() <= 0 {
} else if mimeparser.signatures.is_empty() {
warn!(mimeparser.context, "Message not signed.",);
false
} else if expected_fingerprint.as_ref().is_empty() {

View File

@@ -3,8 +3,9 @@ use lettre::*;
use crate::constants::*;
use crate::context::Context;
use crate::error::Error;
use crate::events::Event;
use crate::login_param::LoginParam;
use crate::login_param::{dc_build_tls, LoginParam};
use crate::oauth2::*;
#[derive(DebugStub)]
@@ -14,7 +15,6 @@ pub struct Smtp {
transport_connected: bool,
/// Email address we are sending from.
from: Option<EmailAddress>,
pub error: Option<String>,
}
impl Smtp {
@@ -24,7 +24,6 @@ impl Smtp {
transport: None,
transport_connected: false,
from: None,
error: None,
}
}
@@ -69,14 +68,7 @@ impl Smtp {
let domain = &lp.send_server;
let port = lp.send_port as u16;
let tls = native_tls::TlsConnector::builder()
// see also: https://github.com/deltachat/deltachat-core-rust/issues/203
.danger_accept_invalid_hostnames(true)
.danger_accept_invalid_certs(true)
.min_protocol_version(Some(DEFAULT_TLS_PROTOCOLS[0]))
.build()
.unwrap();
let tls = dc_build_tls(lp.smtp_certificate_checks).unwrap();
let tls_parameters = ClientTlsParameters::new(domain.to_string(), tls);
let creds = if 0 != lp.server_flags & (DC_LP_AUTH_OAUTH2 as i32) {
@@ -89,7 +81,10 @@ impl Smtp {
}
let user = &lp.send_user;
lettre::smtp::authentication::Credentials::new(user.to_string(), access_token.unwrap())
lettre::smtp::authentication::Credentials::new(
user.to_string(),
access_token.unwrap_or_default(),
)
} else {
// plain
let user = lp.send_user.clone();
@@ -125,37 +120,50 @@ impl Smtp {
}
}
/// SMTP-Send a prepared mail to recipients.
/// returns boolean whether send was successful.
pub fn send<'a>(
&mut self,
context: &Context,
recipients: Vec<EmailAddress>,
body: Vec<u8>,
) -> usize {
message: Vec<u8>,
) -> Result<(), Error> {
let message_len = message.len();
let recipients_display = recipients
.iter()
.map(|x| format!("{}", x))
.collect::<Vec<String>>()
.join(",");
if let Some(ref mut transport) = self.transport {
let envelope = Envelope::new(self.from.clone(), recipients).expect("invalid envelope");
let envelope = Envelope::new(self.from.clone(), recipients);
ensure!(envelope.is_ok(), "internal smtp-message construction fail");
let envelope = envelope.unwrap();
let mail = SendableEmail::new(
envelope,
"mail-id".into(), // TODO: random id
body,
message,
);
match transport.send(mail) {
Ok(_) => {
context.call_cb(Event::SmtpMessageSent(
"Message was sent to SMTP server".into(),
));
context.call_cb(Event::SmtpMessageSent(format!(
"Message len={} was smtp-sent to {}",
message_len, recipients_display
)));
self.transport_connected = true;
1
Ok(())
}
Err(err) => {
warn!(context, "SMTP failed to send message: {}", err);
self.error = Some(format!("{}", err));
0
bail!("SMTP failed len={}: error: {}", message_len, err);
}
}
} else {
// TODO: log error
0
bail!(
"uh? SMTP has no transport, failed to send to {:?}",
recipients_display
);
}
}
}

View File

@@ -1,5 +1,6 @@
use std::collections::HashSet;
use std::sync::{Arc, RwLock};
use std::time::Duration;
use rusqlite::{Connection, OpenFlags, Statement, NO_PARAMS};
use thread_local_object::ThreadLocal;
@@ -68,6 +69,16 @@ impl Sql {
let res = match &*self.pool.read().unwrap() {
Some(pool) => {
let conn = pool.get()?;
// Only one process can make changes to the database at one time.
// busy_timeout defines, that if a seconds process wants write access,
// this second process will wait some milliseconds
// and try over until it gets write access or the given timeout is elapsed.
// If the second process does not get write access within the given timeout,
// sqlite3_step() will return the error SQLITE_BUSY.
// (without a busy_timeout, sqlite3_step() would return SQLITE_BUSY _at once_)
conn.busy_timeout(Duration::from_secs(10))?;
g(&conn)
}
None => Err(Error::SqlNoConnection),
@@ -182,14 +193,14 @@ impl Sql {
///
/// Setting `None` deletes the value. On failure an error message
/// will already have been logged.
pub fn set_config(
pub fn set_raw_config(
&self,
context: &Context,
key: impl AsRef<str>,
value: Option<&str>,
) -> Result<()> {
if !self.is_open() {
error!(context, "set_config(): Database not ready.");
error!(context, "set_raw_config(): Database not ready.");
return Err(Error::SqlNoConnection);
}
@@ -223,14 +234,14 @@ impl Sql {
match res {
Ok(_) => Ok(()),
Err(err) => {
error!(context, "set_config(): Cannot change value. {:?}", &err);
Err(err.into())
error!(context, "set_raw_config(): Cannot change value. {:?}", &err);
Err(err)
}
}
}
/// Get configuration options from the database.
pub fn get_config(&self, context: &Context, key: impl AsRef<str>) -> Option<String> {
pub fn get_raw_config(&self, context: &Context, key: impl AsRef<str>) -> Option<String> {
if !self.is_open() || key.as_ref().is_empty() {
return None;
}
@@ -241,44 +252,46 @@ impl Sql {
)
}
pub fn set_config_int(
pub fn set_raw_config_int(
&self,
context: &Context,
key: impl AsRef<str>,
value: i32,
) -> Result<()> {
self.set_config(context, key, Some(&format!("{}", value)))
self.set_raw_config(context, key, Some(&format!("{}", value)))
}
pub fn get_config_int(&self, context: &Context, key: impl AsRef<str>) -> Option<i32> {
self.get_config(context, key).and_then(|s| s.parse().ok())
pub fn get_raw_config_int(&self, context: &Context, key: impl AsRef<str>) -> Option<i32> {
self.get_raw_config(context, key)
.and_then(|s| s.parse().ok())
}
pub fn get_config_bool(&self, context: &Context, key: impl AsRef<str>) -> bool {
pub fn get_raw_config_bool(&self, context: &Context, key: impl AsRef<str>) -> bool {
// Not the most obvious way to encode bool as string, but it is matter
// of backward compatibility.
self.get_config_int(context, key).unwrap_or_default() > 0
self.get_raw_config_int(context, key).unwrap_or_default() > 0
}
pub fn set_config_bool<T>(&self, context: &Context, key: T, value: bool) -> Result<()>
pub fn set_raw_config_bool<T>(&self, context: &Context, key: T, value: bool) -> Result<()>
where
T: AsRef<str>,
{
let value = if value { Some("1") } else { None };
self.set_config(context, key, value)
self.set_raw_config(context, key, value)
}
pub fn set_config_int64(
pub fn set_raw_config_int64(
&self,
context: &Context,
key: impl AsRef<str>,
value: i64,
) -> Result<()> {
self.set_config(context, key, Some(&format!("{}", value)))
self.set_raw_config(context, key, Some(&format!("{}", value)))
}
pub fn get_config_int64(&self, context: &Context, key: impl AsRef<str>) -> Option<i64> {
self.get_config(context, key).and_then(|r| r.parse().ok())
pub fn get_raw_config_int64(&self, context: &Context, key: impl AsRef<str>) -> Option<i64> {
self.get_raw_config(context, key)
.and_then(|r| r.parse().ok())
}
fn start_stmt(&self, stmt: impl AsRef<str>) {
@@ -330,7 +343,7 @@ fn open(
.with_init(|c| c.execute_batch("PRAGMA secure_delete=on;"));
let pool = r2d2::Pool::builder()
.min_idle(Some(2))
.max_size(4)
.max_size(10)
.connection_timeout(std::time::Duration::new(60, 0))
.build(mgr)?;
@@ -466,11 +479,13 @@ fn open(
// cannot create the tables - maybe we cannot write?
return Err(Error::SqlFailedToOpen);
} else {
sql.set_config_int(context, "dbversion", 0)?;
sql.set_raw_config_int(context, "dbversion", 0)?;
}
} else {
exists_before_update = 1;
dbversion_before_update = sql.get_config_int(context, "dbversion").unwrap_or_default();
dbversion_before_update = sql
.get_raw_config_int(context, "dbversion")
.unwrap_or_default();
}
// (1) update low-level database structure.
@@ -482,6 +497,7 @@ fn open(
let mut update_file_paths = 0;
if dbversion < 1 {
info!(context, "[migration] v1");
sql.execute(
"CREATE TABLE leftgrps ( id INTEGER PRIMARY KEY, grpid TEXT DEFAULT '');",
params![],
@@ -491,17 +507,19 @@ fn open(
params![],
)?;
dbversion = 1;
sql.set_config_int(context, "dbversion", 1)?;
sql.set_raw_config_int(context, "dbversion", 1)?;
}
if dbversion < 2 {
info!(context, "[migration] v2");
sql.execute(
"ALTER TABLE contacts ADD COLUMN authname TEXT DEFAULT '';",
params![],
)?;
dbversion = 2;
sql.set_config_int(context, "dbversion", 2)?;
sql.set_raw_config_int(context, "dbversion", 2)?;
}
if dbversion < 7 {
info!(context, "[migration] v7");
sql.execute(
"CREATE TABLE keypairs (\
id INTEGER PRIMARY KEY, \
@@ -513,9 +531,10 @@ fn open(
params![],
)?;
dbversion = 7;
sql.set_config_int(context, "dbversion", 7)?;
sql.set_raw_config_int(context, "dbversion", 7)?;
}
if dbversion < 10 {
info!(context, "[migration] v10");
sql.execute(
"CREATE TABLE acpeerstates (\
id INTEGER PRIMARY KEY, \
@@ -531,9 +550,10 @@ fn open(
params![],
)?;
dbversion = 10;
sql.set_config_int(context, "dbversion", 10)?;
sql.set_raw_config_int(context, "dbversion", 10)?;
}
if dbversion < 12 {
info!(context, "[migration] v12");
sql.execute(
"CREATE TABLE msgs_mdns ( msg_id INTEGER, contact_id INTEGER);",
params![],
@@ -543,9 +563,10 @@ fn open(
params![],
)?;
dbversion = 12;
sql.set_config_int(context, "dbversion", 12)?;
sql.set_raw_config_int(context, "dbversion", 12)?;
}
if dbversion < 17 {
info!(context, "[migration] v17");
sql.execute(
"ALTER TABLE chats ADD COLUMN archived INTEGER DEFAULT 0;",
params![],
@@ -557,18 +578,20 @@ fn open(
)?;
sql.execute("CREATE INDEX msgs_index5 ON msgs (starred);", params![])?;
dbversion = 17;
sql.set_config_int(context, "dbversion", 17)?;
sql.set_raw_config_int(context, "dbversion", 17)?;
}
if dbversion < 18 {
info!(context, "[migration] v18");
sql.execute(
"ALTER TABLE acpeerstates ADD COLUMN gossip_timestamp INTEGER DEFAULT 0;",
params![],
)?;
sql.execute("ALTER TABLE acpeerstates ADD COLUMN gossip_key;", params![])?;
dbversion = 18;
sql.set_config_int(context, "dbversion", 18)?;
sql.set_raw_config_int(context, "dbversion", 18)?;
}
if dbversion < 27 {
info!(context, "[migration] v27");
sql.execute("DELETE FROM msgs WHERE chat_id=1 OR chat_id=2;", params![])?;
sql.execute(
"CREATE INDEX chats_contacts_index2 ON chats_contacts (contact_id);",
@@ -583,9 +606,10 @@ fn open(
params![],
)?;
dbversion = 27;
sql.set_config_int(context, "dbversion", 27)?;
sql.set_raw_config_int(context, "dbversion", 27)?;
}
if dbversion < 34 {
info!(context, "[migration] v34");
sql.execute(
"ALTER TABLE msgs ADD COLUMN hidden INTEGER DEFAULT 0;",
params![],
@@ -612,9 +636,10 @@ fn open(
)?;
recalc_fingerprints = 1;
dbversion = 34;
sql.set_config_int(context, "dbversion", 34)?;
sql.set_raw_config_int(context, "dbversion", 34)?;
}
if dbversion < 39 {
info!(context, "[migration] v39");
sql.execute(
"CREATE TABLE tokens ( id INTEGER PRIMARY KEY, namespc INTEGER DEFAULT 0, foreign_id INTEGER DEFAULT 0, token TEXT DEFAULT '', timestamp INTEGER DEFAULT 0);",
params![]
@@ -642,32 +667,37 @@ fn open(
)?;
}
dbversion = 39;
sql.set_config_int(context, "dbversion", 39)?;
sql.set_raw_config_int(context, "dbversion", 39)?;
}
if dbversion < 40 {
info!(context, "[migration] v40");
sql.execute(
"ALTER TABLE jobs ADD COLUMN thread INTEGER DEFAULT 0;",
params![],
)?;
dbversion = 40;
sql.set_config_int(context, "dbversion", 40)?;
sql.set_raw_config_int(context, "dbversion", 40)?;
}
if dbversion < 41 {
info!(context, "[migration] v41");
update_file_paths = 1;
dbversion = 41;
sql.set_config_int(context, "dbversion", 41)?;
sql.set_raw_config_int(context, "dbversion", 41)?;
}
if dbversion < 42 {
info!(context, "[migration] v42");
sql.execute("UPDATE msgs SET txt='' WHERE type!=10", params![])?;
dbversion = 42;
sql.set_config_int(context, "dbversion", 42)?;
sql.set_raw_config_int(context, "dbversion", 42)?;
}
if dbversion < 44 {
info!(context, "[migration] v44");
sql.execute("ALTER TABLE msgs ADD COLUMN mime_headers TEXT;", params![])?;
dbversion = 44;
sql.set_config_int(context, "dbversion", 44)?;
sql.set_raw_config_int(context, "dbversion", 44)?;
}
if dbversion < 46 {
info!(context, "[migration] v46");
sql.execute(
"ALTER TABLE msgs ADD COLUMN mime_in_reply_to TEXT;",
params![],
@@ -677,7 +707,7 @@ fn open(
params![],
)?;
dbversion = 46;
sql.set_config_int(context, "dbversion", 46)?;
sql.set_raw_config_int(context, "dbversion", 46)?;
}
if dbversion < 47 {
info!(context, "[migration] v47");
@@ -686,7 +716,7 @@ fn open(
params![],
)?;
dbversion = 47;
sql.set_config_int(context, "dbversion", 47)?;
sql.set_raw_config_int(context, "dbversion", 47)?;
}
if dbversion < 48 {
info!(context, "[migration] v48");
@@ -696,7 +726,7 @@ fn open(
)?;
dbversion = 48;
sql.set_config_int(context, "dbversion", 48)?;
sql.set_raw_config_int(context, "dbversion", 48)?;
}
if dbversion < 49 {
info!(context, "[migration] v49");
@@ -705,15 +735,15 @@ fn open(
params![],
)?;
dbversion = 49;
sql.set_config_int(context, "dbversion", 49)?;
sql.set_raw_config_int(context, "dbversion", 49)?;
}
if dbversion < 50 {
info!(context, "[migration] v50");
if 0 != exists_before_update {
sql.set_config_int(context, "show_emails", 2)?;
sql.set_raw_config_int(context, "show_emails", 2)?;
}
dbversion = 50;
sql.set_config_int(context, "dbversion", 50)?;
sql.set_raw_config_int(context, "dbversion", 50)?;
}
if dbversion < 53 {
info!(context, "[migration] v53");
@@ -746,7 +776,7 @@ fn open(
params![],
)?;
dbversion = 53;
sql.set_config_int(context, "dbversion", 53)?;
sql.set_raw_config_int(context, "dbversion", 53)?;
}
if dbversion < 54 {
info!(context, "[migration] v54");
@@ -756,18 +786,20 @@ fn open(
)?;
sql.execute("CREATE INDEX msgs_index6 ON msgs (location_id);", params![])?;
dbversion = 54;
sql.set_config_int(context, "dbversion", 54)?;
sql.set_raw_config_int(context, "dbversion", 54)?;
}
if dbversion < 55 {
info!(context, "[migration] v55");
sql.execute(
"ALTER TABLE locations ADD COLUMN independent INTEGER DEFAULT 0;",
params![],
)?;
sql.set_config_int(context, "dbversion", 55)?;
sql.set_raw_config_int(context, "dbversion", 55)?;
}
if 0 != recalc_fingerprints {
info!(context, "[migration] recalc fingerprints");
sql.query_map(
"SELECT addr FROM acpeerstates;",
params![],
@@ -777,7 +809,7 @@ fn open(
if let Some(ref mut peerstate) = Peerstate::from_addr(context, sql, &addr?)
{
peerstate.recalc_fingerprint();
peerstate.save_to_db(sql, false).unwrap();
peerstate.save_to_db(sql, false)?;
}
}
Ok(())
@@ -788,11 +820,9 @@ fn open(
// versions before 2018-08 save the absolute paths in the database files at "param.f=";
// for newer versions, we copy files always to the blob directory and store relative paths.
// this snippet converts older databases and can be removed after some time.
info!(context, "[open] update file paths");
info!(context, "[migration] update file paths");
let repl_from = sql
.get_config(context, "backup_for")
.get_raw_config(context, "backup_for")
.unwrap_or_else(|| context.get_blobdir().to_string_lossy().into());
let repl_from = dc_ensure_no_slash_safe(&repl_from);
@@ -812,7 +842,7 @@ fn open(
NO_PARAMS,
)?;
sql.set_config(context, "backup_for", None)?;
sql.set_raw_config(context, "backup_for", None)?;
}
}
@@ -1133,12 +1163,8 @@ mod test {
maybe_add_file(&mut files, "$BLOBDIR/world.txt");
maybe_add_file(&mut files, "world2.txt");
assert!(is_file_in_use(&mut files, None, "hello"));
assert!(!is_file_in_use(&mut files, Some(".txt"), "hello"));
assert!(is_file_in_use(
&mut files,
Some("-suffix"),
"world.txt-suffix"
));
assert!(is_file_in_use(&files, None, "hello"));
assert!(!is_file_in_use(&files, Some(".txt"), "hello"));
assert!(is_file_in_use(&files, Some("-suffix"), "world.txt-suffix"));
}
}

View File

@@ -5,8 +5,7 @@ use strum_macros::EnumProperty;
use crate::contact::*;
use crate::context::Context;
use crate::dc_tools::*;
use crate::events::Event;
use crate::error::Error;
/// Stock strings
///
@@ -109,6 +108,8 @@ pub enum StockMessage {
MsgLocationDisabled = 65,
#[strum(props(fallback = "Location"))]
Location = 66,
#[strum(props(fallback = "Sticker"))]
Sticker = 67,
}
impl StockMessage {
@@ -116,24 +117,52 @@ impl StockMessage {
///
/// These could be used in logging calls, so no logging here.
fn fallback(&self) -> &'static str {
self.get_str("fallback").unwrap()
self.get_str("fallback").unwrap_or_default()
}
}
impl Context {
/// Set the stock string for the [StockMessage].
///
pub fn set_stock_translation(
&self,
id: StockMessage,
stockstring: String,
) -> Result<(), Error> {
if stockstring.contains("%1") && !id.fallback().contains("%1") {
bail!(
"translation {} contains invalid %1 placeholder, default is {}",
stockstring,
id.fallback()
);
}
if stockstring.contains("%2") && !id.fallback().contains("%2") {
bail!(
"translation {} contains invalid %2 placeholder, default is {}",
stockstring,
id.fallback()
);
}
self.translated_stockstrings
.write()
.unwrap()
.insert(id as usize, stockstring);
Ok(())
}
/// Return the stock string for the [StockMessage].
///
/// If the context callback responds with a string to use, e.g. a
/// translation, then this string will be returned. Otherwise a
/// default (English) string is returned.
/// Return a translation (if it was set with set_stock_translation before)
/// or a default (English) string.
pub fn stock_str(&self, id: StockMessage) -> Cow<str> {
let ptr = self.call_cb(Event::GetString { id, count: 0 }) as *mut libc::c_char;
if ptr.is_null() {
Cow::Borrowed(id.fallback())
} else {
let ret = to_string(ptr);
unsafe { libc::free(ptr as *mut libc::c_void) };
Cow::Owned(ret)
match self
.translated_stockstrings
.read()
.unwrap()
.get(&(id as usize))
{
Some(ref x) => Cow::Owned(x.to_string()),
None => Cow::Borrowed(id.fallback()),
}
}
@@ -237,7 +266,6 @@ mod tests {
use crate::test_utils::*;
use crate::constants::DC_CONTACT_ID_SELF;
use libc::uintptr_t;
use num_traits::ToPrimitive;
@@ -253,25 +281,31 @@ mod tests {
}
#[test]
fn test_stock_str() {
fn test_set_stock_translation() {
let t = dummy_context();
assert_eq!(t.ctx.stock_str(StockMessage::NoMessages), "No messages.");
}
fn test_stock_str_no_fallback_cb(_ctx: &Context, evt: Event) -> uintptr_t {
match evt {
Event::GetString {
id: StockMessage::NoMessages,
..
} => unsafe { "Hello there".strdup() as usize },
_ => 0,
}
t.ctx
.set_stock_translation(StockMessage::NoMessages, "xyz".to_string())
.unwrap();
assert_eq!(t.ctx.stock_str(StockMessage::NoMessages), "xyz")
}
#[test]
fn test_stock_str_no_fallback() {
let t = test_context(Some(Box::new(test_stock_str_no_fallback_cb)));
assert_eq!(t.ctx.stock_str(StockMessage::NoMessages), "Hello there");
fn test_set_stock_translation_wrong_replacements() {
let t = dummy_context();
assert!(t
.ctx
.set_stock_translation(StockMessage::NoMessages, "xyz %1$s ".to_string())
.is_err());
assert!(t
.ctx
.set_stock_translation(StockMessage::NoMessages, "xyz %2$s ".to_string())
.is_err());
}
#[test]
fn test_stock_str() {
let t = dummy_context();
assert_eq!(t.ctx.stock_str(StockMessage::NoMessages), "No messages.");
}
#[test]
@@ -349,9 +383,7 @@ mod tests {
let contact_id = {
Contact::create(&t.ctx, "Alice", "alice@example.com")
.expect("Failed to create contact Alice");
let id =
Contact::create(&t.ctx, "Bob", "bob@example.com").expect("failed to create bob");
id
Contact::create(&t.ctx, "Bob", "bob@example.com").expect("failed to create bob")
};
assert_eq!(
t.ctx.stock_system_msg(

View File

@@ -14,22 +14,24 @@ if __name__ == "__main__":
s = re.sub(r"(?m)///.*$", "", s) # remove comments
unsafe = s.count("unsafe")
free = s.count("free(")
gotoblocks = s.count("ok_to_continue") + s.count('OK_TO_CONTINUE')
unsafe_fn = s.count("unsafe fn")
chars = s.count("c_char") + s.count("CStr")
filestats.append((fn, unsafe, free, gotoblocks, chars))
filestats.append((fn, unsafe, free, unsafe_fn, chars))
sum_unsafe, sum_free, sum_gotoblocks, sum_chars = 0, 0, 0, 0
sum_unsafe, sum_free, sum_unsafe_fn, sum_chars = 0, 0, 0, 0
for fn, unsafe, free, gotoblocks, chars in reversed(sorted(filestats, key=lambda x: sum(x[1:]))):
print("{0: <25} unsafe: {1: >3} free: {2: >3} ok_to_cont: {3: >3} chars: {4: >3}".format(str(fn), unsafe, free, gotoblocks, chars))
for fn, unsafe, free, unsafe_fn, chars in reversed(sorted(filestats, key=lambda x: sum(x[1:]))):
if unsafe + free + unsafe_fn + chars == 0:
continue
print("{0: <25} unsafe: {1: >3} free: {2: >3} unsafe-fn: {3: >3} chars: {4: >3}".format(str(fn), unsafe, free, unsafe_fn, chars))
sum_unsafe += unsafe
sum_free += free
sum_gotoblocks += gotoblocks
sum_unsafe_fn += unsafe_fn
sum_chars += chars
print()
print("total unsafe:", sum_unsafe)
print("total free:", sum_free)
print("total ok_to_continue:", sum_gotoblocks)
print("total unsafe-fn:", sum_unsafe_fn)
print("total c_chars:", sum_chars)

View File

@@ -1,9 +1,14 @@
use std::collections::HashSet;
use std::ffi::CString;
use std::ptr;
use crate::contact::addr_normalize;
use crate::dc_strencode::*;
use crate::dc_tools::*;
use crate::error::Error;
use mmime::clist::*;
// use mmime::display::*;
use mmime::mailimf::mailimf_msg_id_parse;
use mmime::mailimf::types::*;
use mmime::mailimf::types_helper::*;
use mmime::mailmime::content::*;
@@ -11,6 +16,7 @@ use mmime::mailmime::disposition::*;
use mmime::mailmime::types::*;
use mmime::mailmime::types_helper::*;
use mmime::mailmime::*;
use mmime::mmapstring::*;
use mmime::other::*;
#[macro_export]
@@ -36,13 +42,30 @@ pub fn get_ct_subtype(mime: *mut Mailmime) -> Option<String> {
let ct: *mut mailmime_content = (*mime).mm_content_type;
if !ct.is_null() && !(*ct).ct_subtype.is_null() {
Some(to_string((*ct).ct_subtype))
Some(to_string_lossy((*ct).ct_subtype))
} else {
None
}
}
}
pub fn parse_message_id(message_id: &str) -> Result<String, Error> {
let mut dummy = 0;
let c_message_id = CString::new(message_id).unwrap_or_default();
let c_ptr = c_message_id.as_ptr();
let mut rfc724_mid_c = std::ptr::null_mut();
if unsafe { mailimf_msg_id_parse(c_ptr, libc::strlen(c_ptr), &mut dummy, &mut rfc724_mid_c) }
== MAIL_NO_ERROR as libc::c_int
&& !rfc724_mid_c.is_null()
{
let res = to_string_lossy(rfc724_mid_c);
unsafe { libc::free(rfc724_mid_c.cast()) };
Ok(res)
} else {
bail!("could not parse message_id: {}", message_id);
}
}
pub fn get_autocrypt_mime(
mime_undetermined: *mut Mailmime,
) -> Result<(*mut Mailmime, *mut Mailmime), Error> {
@@ -88,6 +111,192 @@ pub fn has_decryptable_data(mime_data: *mut mailmime_data) -> bool {
}
}
pub fn get_field_from(imffields: *mut mailimf_fields) -> Result<String, Error> {
let field = mailimf_find_field(imffields, MAILIMF_FIELD_FROM as libc::c_int);
if !field.is_null() && unsafe { !(*field).fld_data.fld_from.is_null() } {
let mb_list = unsafe { (*(*field).fld_data.fld_from).frm_mb_list };
if let Some(addr) = mailimf_find_first_addr(mb_list) {
return Ok(addr);
}
}
bail!("not From field found");
}
pub fn get_field_date(imffields: *mut mailimf_fields) -> Result<i64, Error> {
let field = mailimf_find_field(imffields, MAILIMF_FIELD_ORIG_DATE as libc::c_int);
let mut message_time = 0;
if !field.is_null() && unsafe { !(*field).fld_data.fld_orig_date.is_null() } {
let orig_date = unsafe { (*field).fld_data.fld_orig_date };
if !orig_date.is_null() {
let dt = unsafe { (*orig_date).dt_date_time };
message_time = dc_timestamp_from_date(dt);
if message_time != 0 && message_time > time() {
message_time = time()
}
}
}
Ok(message_time)
}
fn mailimf_get_recipients_add_addr(recipients: &mut HashSet<String>, mb: *mut mailimf_mailbox) {
if !mb.is_null() {
let addr_norm = addr_normalize(as_str(unsafe { (*mb).mb_addr_spec }));
recipients.insert(addr_norm.into());
}
}
/*the result is a pointer to mime, must not be freed*/
pub fn mailimf_find_field(
header: *mut mailimf_fields,
wanted_fld_type: libc::c_int,
) -> *mut mailimf_field {
if header.is_null() {
return ptr::null_mut();
}
let header = unsafe { (*header) };
if header.fld_list.is_null() {
return ptr::null_mut();
}
for cur in unsafe { &(*header.fld_list) } {
let field = cur as *mut mailimf_field;
if !field.is_null() {
if unsafe { (*field).fld_type } == wanted_fld_type {
return field;
}
}
}
ptr::null_mut()
}
/*the result is a pointer to mime, must not be freed*/
pub fn mailmime_find_mailimf_fields(mime: *mut Mailmime) -> *mut mailimf_fields {
if mime.is_null() {
return ptr::null_mut();
}
match unsafe { (*mime).mm_type as _ } {
MAILMIME_MULTIPLE => {
for cur_data in unsafe { (*(*mime).mm_data.mm_multipart.mm_mp_list).into_iter() } {
let header = mailmime_find_mailimf_fields(cur_data as *mut _);
if !header.is_null() {
return header;
}
}
}
MAILMIME_MESSAGE => return unsafe { (*mime).mm_data.mm_message.mm_fields },
_ => {}
}
ptr::null_mut()
}
pub unsafe fn mailimf_find_optional_field(
header: *mut mailimf_fields,
wanted_fld_name: *const libc::c_char,
) -> *mut mailimf_optional_field {
if header.is_null() || (*header).fld_list.is_null() {
return ptr::null_mut();
}
for cur_data in (*(*header).fld_list).into_iter() {
let field: *mut mailimf_field = cur_data as *mut _;
if (*field).fld_type == MAILIMF_FIELD_OPTIONAL_FIELD as libc::c_int {
let optional_field: *mut mailimf_optional_field = (*field).fld_data.fld_optional_field;
if !optional_field.is_null()
&& !(*optional_field).fld_name.is_null()
&& !(*optional_field).fld_value.is_null()
&& strcasecmp((*optional_field).fld_name, wanted_fld_name) == 0i32
{
return optional_field;
}
}
}
ptr::null_mut()
}
pub fn mailimf_get_recipients(imffields: *mut mailimf_fields) -> HashSet<String> {
/* returned addresses are normalized. */
let mut recipients: HashSet<String> = Default::default();
for cur in unsafe { (*(*imffields).fld_list).into_iter() } {
let fld = cur as *mut mailimf_field;
let fld_to: *mut mailimf_to;
let fld_cc: *mut mailimf_cc;
let mut addr_list: *mut mailimf_address_list = ptr::null_mut();
if fld.is_null() {
continue;
}
let fld = unsafe { *fld };
// TODO match on enums /rtn
match fld.fld_type {
13 => {
fld_to = unsafe { fld.fld_data.fld_to };
if !fld_to.is_null() {
addr_list = unsafe { (*fld_to).to_addr_list };
}
}
14 => {
fld_cc = unsafe { fld.fld_data.fld_cc };
if !fld_cc.is_null() {
addr_list = unsafe { (*fld_cc).cc_addr_list };
}
}
_ => {}
}
if !addr_list.is_null() {
for cur2 in unsafe { &(*(*addr_list).ad_list) } {
let adr = cur2 as *mut mailimf_address;
if adr.is_null() {
continue;
}
let adr = unsafe { *adr };
if adr.ad_type == MAILIMF_ADDRESS_MAILBOX as libc::c_int {
mailimf_get_recipients_add_addr(&mut recipients, unsafe {
adr.ad_data.ad_mailbox
});
} else if adr.ad_type == MAILIMF_ADDRESS_GROUP as libc::c_int {
let group = unsafe { adr.ad_data.ad_group };
if !group.is_null() && unsafe { !(*group).grp_mb_list.is_null() } {
for cur3 in unsafe { &(*(*(*group).grp_mb_list).mb_list) } {
mailimf_get_recipients_add_addr(
&mut recipients,
cur3 as *mut mailimf_mailbox,
);
}
}
}
}
}
}
recipients
}
pub fn mailmime_transfer_decode(mime: *mut Mailmime) -> Result<Vec<u8>, Error> {
ensure!(!mime.is_null(), "invalid inputs");
let mime_transfer_encoding =
get_mime_transfer_encoding(mime).unwrap_or(MAILMIME_MECHANISM_BINARY as i32);
let mime_data = unsafe { (*mime).mm_data.mm_single };
decode_dt_data(mime_data, mime_transfer_encoding)
}
pub fn get_mime_transfer_encoding(mime: *mut Mailmime) -> Option<libc::c_int> {
unsafe {
let mm_mime_fields = (*mime).mm_mime_fields;
@@ -108,48 +317,77 @@ pub fn get_mime_transfer_encoding(mime: *mut Mailmime) -> Option<libc::c_int> {
pub fn decode_dt_data(
mime_data: *mut mailmime_data,
mime_transfer_encoding: libc::c_int,
) -> Result<(*mut libc::c_char, libc::size_t), Error> {
) -> Result<Vec<u8>, Error> {
// Decode data according to mime_transfer_encoding
// returns Ok with a (decoded_data,decoded_data_bytes) pointer
// where the caller must make sure to free it.
// It may return Ok(ptr::null_mut(), 0)
let mut transfer_decoding_buffer: *mut libc::c_char = ptr::null_mut();
let decoded_data: *mut libc::c_char;
let mut decoded_data_bytes: libc::size_t = 0;
if mime_transfer_encoding == MAILMIME_MECHANISM_7BIT as libc::c_int
|| mime_transfer_encoding == MAILMIME_MECHANISM_8BIT as libc::c_int
|| mime_transfer_encoding == MAILMIME_MECHANISM_BINARY as libc::c_int
{
unsafe {
decoded_data = (*mime_data).dt_data.dt_text.dt_data as *mut _;
decoded_data_bytes = (*mime_data).dt_data.dt_text.dt_length;
}
ensure!(
!decoded_data.is_null() && decoded_data_bytes > 0,
"could not decode mime message"
);
} else {
let mut current_index: libc::size_t = 0;
unsafe {
let r = mailmime_part_parse(
(*mime_data).dt_data.dt_text.dt_data,
(*mime_data).dt_data.dt_text.dt_length,
&mut current_index,
mime_transfer_encoding,
&mut transfer_decoding_buffer,
&mut decoded_data_bytes,
);
if r != MAILIMF_NO_ERROR as libc::c_int
|| transfer_decoding_buffer.is_null()
|| decoded_data_bytes <= 0
{
bail!("mailmime_part_parse returned error or invalid data");
}
decoded_data = transfer_decoding_buffer;
let decoded_data = unsafe { (*mime_data).dt_data.dt_text.dt_data };
let decoded_data_bytes = unsafe { (*mime_data).dt_data.dt_text.dt_length };
if decoded_data.is_null() || decoded_data_bytes == 0 {
bail!("No data to decode found");
} else {
let result = unsafe {
std::slice::from_raw_parts(decoded_data as *const u8, decoded_data_bytes)
};
return Ok(result.to_vec());
}
}
Ok((decoded_data, decoded_data_bytes))
// unsafe { display_mime_data(mime_data) };
let mut current_index = 0;
let mut transfer_decoding_buffer = ptr::null_mut();
let mut decoded_data_bytes = 0;
let r = unsafe {
mailmime_part_parse(
(*mime_data).dt_data.dt_text.dt_data,
(*mime_data).dt_data.dt_text.dt_length,
&mut current_index,
mime_transfer_encoding,
&mut transfer_decoding_buffer,
&mut decoded_data_bytes,
)
};
if r == MAILIMF_NO_ERROR as libc::c_int
&& !transfer_decoding_buffer.is_null()
&& decoded_data_bytes > 0
{
let result = unsafe {
std::slice::from_raw_parts(transfer_decoding_buffer as *const u8, decoded_data_bytes)
}
.to_vec();
// we return a fresh vec and transfer_decoding_buffer is not used or passed anywhere
// so it's safe to free it right away, as mailman_part_parse has
// allocated it fresh.
unsafe { mmap_string_unref(transfer_decoding_buffer) };
return Ok(result);
}
Err(format_err!("Failed to to decode"))
}
pub fn mailimf_find_first_addr(mb_list: *const mailimf_mailbox_list) -> Option<String> {
if mb_list.is_null() {
return None;
}
for cur in unsafe { (*(*mb_list).mb_list).into_iter() } {
let mb = cur as *mut mailimf_mailbox;
if !mb.is_null() && !unsafe { (*mb).mb_addr_spec.is_null() } {
let addr = unsafe { as_str((*mb).mb_addr_spec) };
return Some(addr_normalize(addr).to_string());
}
}
None
}
/**************************************
@@ -210,8 +448,8 @@ pub fn append_ct_param(
value: &str,
) -> Result<(), Error> {
unsafe {
let name_c = CString::new(name).unwrap();
let value_c = CString::new(value).unwrap();
let name_c = CString::new(name).unwrap_or_default();
let value_c = CString::new(value).unwrap_or_default();
clist_append!(
(*content).ct_parameters,
@@ -225,7 +463,7 @@ pub fn append_ct_param(
}
pub fn new_content_type(content_type: &str) -> Result<*mut mailmime_content, Error> {
let ct = CString::new(content_type).unwrap();
let ct = CString::new(content_type).unwrap_or_default();
let content: *mut mailmime_content;
// mailmime_content_new_with_str only parses but does not retain/own ct
unsafe {
@@ -261,6 +499,24 @@ pub fn content_type_needs_encoding(content: *const mailmime_content) -> bool {
}
}
pub fn new_mailbox_list(displayname: &str, addr: &str) -> *mut mailimf_mailbox_list {
let mbox: *mut mailimf_mailbox_list = unsafe { mailimf_mailbox_list_new_empty() };
unsafe {
mailimf_mailbox_list_add(
mbox,
mailimf_mailbox_new(
if !displayname.is_empty() {
dc_encode_header_words(&displayname).strdup()
} else {
ptr::null_mut()
},
addr.strdup(),
),
);
}
mbox
}
#[cfg(test)]
mod tests {
use super::*;
@@ -280,4 +536,16 @@ mod tests {
new_content_type("application/pgp-encrypted").unwrap()
));
}
#[test]
fn test_parse_message_id() {
assert_eq!(
parse_message_id("Mr.PRUe8HJBoaO.3whNvLCMFU0@testrun.org").unwrap(),
"Mr.PRUe8HJBoaO.3whNvLCMFU0@testrun.org"
);
assert_eq!(
parse_message_id("<Mr.PRUe8HJBoaO.3whNvLCMFU0@testrun.org>").unwrap(),
"Mr.PRUe8HJBoaO.3whNvLCMFU0@testrun.org"
);
}
}

View File

@@ -1,18 +1,15 @@
//! Stress some functions for testing; if used as a lib, this file is obsolete.
use std::collections::HashSet;
use std::ptr;
use deltachat::chat::{self, Chat};
use deltachat::config;
use deltachat::contact::*;
use deltachat::context::*;
use deltachat::dc_tools::*;
use deltachat::keyring::*;
use deltachat::oauth2::*;
use deltachat::pgp::*;
use deltachat::pgp;
use deltachat::Event;
use libc::{free, strcmp, strdup};
use tempfile::{tempdir, TempDir};
/* some data used for testing
@@ -50,135 +47,6 @@ unsafe fn stress_functions(context: &Context) {
assert!(res.contains(" configured_send_port "));
assert!(res.contains(" configured_server_flags "));
let mut buf_0: *mut libc::c_char;
let mut headerline = String::default();
let mut setupcodebegin: *const libc::c_char = ptr::null();
let mut preferencrypt: *const libc::c_char = ptr::null();
let mut base64: *const libc::c_char = ptr::null();
buf_0 = strdup(
b"-----BEGIN PGP MESSAGE-----\nNoVal:\n\ndata\n-----END PGP MESSAGE-----\x00" as *const u8
as *const libc::c_char,
);
let ok = dc_split_armored_data(
buf_0,
&mut headerline,
&mut setupcodebegin,
ptr::null_mut(),
&mut base64,
);
assert!(ok);
assert!(!headerline.is_empty());
assert_eq!(headerline, "-----BEGIN PGP MESSAGE-----");
assert!(!base64.is_null());
assert_eq!(as_str(base64 as *const libc::c_char), "data",);
free(buf_0 as *mut libc::c_void);
buf_0 =
strdup(b"-----BEGIN PGP MESSAGE-----\n\ndat1\n-----END PGP MESSAGE-----\n-----BEGIN PGP MESSAGE-----\n\ndat2\n-----END PGP MESSAGE-----\x00"
as *const u8 as *const libc::c_char);
let ok = dc_split_armored_data(
buf_0,
&mut headerline,
&mut setupcodebegin,
ptr::null_mut(),
&mut base64,
);
assert!(ok);
assert_eq!(headerline, "-----BEGIN PGP MESSAGE-----");
assert!(!base64.is_null());
assert_eq!(as_str(base64 as *const libc::c_char), "dat1",);
free(buf_0 as *mut libc::c_void);
buf_0 = strdup(
b"foo \n -----BEGIN PGP MESSAGE----- \n base64-123 \n -----END PGP MESSAGE-----\x00"
as *const u8 as *const libc::c_char,
);
let ok = dc_split_armored_data(
buf_0,
&mut headerline,
&mut setupcodebegin,
ptr::null_mut(),
&mut base64,
);
assert!(ok);
assert_eq!(headerline, "-----BEGIN PGP MESSAGE-----");
assert!(setupcodebegin.is_null());
assert!(!base64.is_null());
assert_eq!(as_str(base64 as *const libc::c_char), "base64-123",);
free(buf_0 as *mut libc::c_void);
buf_0 = strdup(b"foo-----BEGIN PGP MESSAGE-----\x00" as *const u8 as *const libc::c_char);
let ok = dc_split_armored_data(
buf_0,
&mut headerline,
&mut setupcodebegin,
ptr::null_mut(),
&mut base64,
);
assert!(!ok);
free(buf_0 as *mut libc::c_void);
buf_0 =
strdup(b"foo \n -----BEGIN PGP MESSAGE-----\n Passphrase-BeGIN : 23 \n \n base64-567 \r\n abc \n -----END PGP MESSAGE-----\n\n\n\x00"
as *const u8 as *const libc::c_char);
let ok = dc_split_armored_data(
buf_0,
&mut headerline,
&mut setupcodebegin,
ptr::null_mut(),
&mut base64,
);
assert!(ok);
assert_eq!(headerline, "-----BEGIN PGP MESSAGE-----");
assert!(!setupcodebegin.is_null());
assert_eq!(
strcmp(
setupcodebegin,
b"23\x00" as *const u8 as *const libc::c_char,
),
0
);
assert!(!base64.is_null());
assert_eq!(as_str(base64 as *const libc::c_char), "base64-567 \n abc",);
free(buf_0 as *mut libc::c_void);
buf_0 =
strdup(b"-----BEGIN PGP PRIVATE KEY BLOCK-----\n Autocrypt-Prefer-Encrypt : mutual \n\nbase64\n-----END PGP PRIVATE KEY BLOCK-----\x00"
as *const u8 as *const libc::c_char);
let ok = dc_split_armored_data(
buf_0,
&mut headerline,
ptr::null_mut(),
&mut preferencrypt,
&mut base64,
);
assert!(ok);
assert_eq!(headerline, "-----BEGIN PGP PRIVATE KEY BLOCK-----");
assert!(!preferencrypt.is_null());
assert_eq!(
strcmp(
preferencrypt,
b"mutual\x00" as *const u8 as *const libc::c_char,
),
0
);
assert!(!base64.is_null());
assert_eq!(as_str(base64 as *const libc::c_char), "base64",);
free(buf_0 as *mut libc::c_void);
// Cant check, no configured context
// assert!(dc_is_configured(context) != 0, "Missing configured context");
@@ -232,11 +100,11 @@ unsafe fn stress_functions(context: &Context) {
#[test]
#[ignore] // is too expensive
fn test_encryption_decryption() {
let (public_key, private_key) = dc_pgp_create_keypair("foo@bar.de").unwrap();
let (public_key, private_key) = pgp::create_keypair("foo@bar.de").unwrap();
private_key.split_key().unwrap();
let (public_key2, private_key2) = dc_pgp_create_keypair("two@zwo.de").unwrap();
let (public_key2, private_key2) = pgp::create_keypair("two@zwo.de").unwrap();
assert_ne!(public_key, public_key2);
@@ -245,11 +113,11 @@ fn test_encryption_decryption() {
keyring.add_owned(public_key.clone());
keyring.add_ref(&public_key2);
let ctext_signed = dc_pgp_pk_encrypt(original_text, &keyring, Some(&private_key)).unwrap();
let ctext_signed = pgp::pk_encrypt(original_text, &keyring, Some(&private_key)).unwrap();
assert!(!ctext_signed.is_empty());
assert!(ctext_signed.starts_with("-----BEGIN PGP MESSAGE-----"));
let ctext_unsigned = dc_pgp_pk_encrypt(original_text, &keyring, None).unwrap();
let ctext_unsigned = pgp::pk_encrypt(original_text, &keyring, None).unwrap();
assert!(!ctext_unsigned.is_empty());
assert!(ctext_unsigned.starts_with("-----BEGIN PGP MESSAGE-----"));
@@ -264,7 +132,7 @@ fn test_encryption_decryption() {
let mut valid_signatures: HashSet<String> = Default::default();
let plain = dc_pgp_pk_decrypt(
let plain = pgp::pk_decrypt(
ctext_signed.as_bytes(),
&keyring,
&public_keyring,
@@ -278,7 +146,7 @@ fn test_encryption_decryption() {
valid_signatures.clear();
let empty_keyring = Keyring::default();
let plain = dc_pgp_pk_decrypt(
let plain = pgp::pk_decrypt(
ctext_signed.as_bytes(),
&keyring,
&empty_keyring,
@@ -290,7 +158,7 @@ fn test_encryption_decryption() {
valid_signatures.clear();
let plain = dc_pgp_pk_decrypt(
let plain = pgp::pk_decrypt(
ctext_signed.as_bytes(),
&keyring,
&public_keyring2,
@@ -304,7 +172,7 @@ fn test_encryption_decryption() {
public_keyring2.add_ref(&public_key);
let plain = dc_pgp_pk_decrypt(
let plain = pgp::pk_decrypt(
ctext_signed.as_bytes(),
&keyring,
&public_keyring2,
@@ -316,7 +184,7 @@ fn test_encryption_decryption() {
valid_signatures.clear();
let plain = dc_pgp_pk_decrypt(
let plain = pgp::pk_decrypt(
ctext_unsigned.as_bytes(),
&keyring,
&public_keyring,
@@ -333,8 +201,7 @@ fn test_encryption_decryption() {
let mut public_keyring = Keyring::default();
public_keyring.add_ref(&public_key);
let plain =
dc_pgp_pk_decrypt(ctext_signed.as_bytes(), &keyring, &public_keyring, None).unwrap();
let plain = pgp::pk_decrypt(ctext_signed.as_bytes(), &keyring, &public_keyring, None).unwrap();
assert_eq!(plain, original_text);
}