Compare commits

..

101 Commits

Author SHA1 Message Date
B. Petersen
6c7962690f drafts are added to chatlist only on explicit request 2019-10-20 23:38:22 +02:00
B. Petersen
5fd691e01b use boolean for add_archived_link_item 2019-10-20 18:03:19 +02:00
dignifiedquire
391ba67ad5 update to fixed rust-imap for yandex 2019-10-20 13:10:17 +02:00
dignifiedquire
54f8c68151 switch to master 2019-10-20 13:10:17 +02:00
dignifiedquire
4a2e1897a6 fix(smtp): use correct auth mechanisms 2019-10-20 13:10:17 +02:00
dignifiedquire
076616bfb9 fix(imap): read server greeting 2019-10-20 13:10:17 +02:00
Dmitry Bogatov
a9dd78f622 Narrow return type of location::set: cint -> bool 2019-10-19 22:38:43 +02:00
B. Petersen
d16bdafaf0 simplify calling Simplifier, get is_forwarded flag correctly 2019-10-19 22:34:14 +02:00
B. Petersen
4f126c5292 show forwarded-state in repl-tool 2019-10-19 22:34:14 +02:00
B. Petersen
7b958a20fd prefer to_string_lossy() over as_str() as the latter pancis on non-wellformatted utf-8 2019-10-19 22:30:25 +02:00
B. Petersen
4519071718 prefer to_opt_string_lossy() over as_opt_str() as the latter pancis on non-wellformatted utf-8 2019-10-19 22:30:25 +02:00
B. Petersen
0108b4724e add function to convert NULL-able c-string to Option<String> 2019-10-19 22:30:25 +02:00
B. Petersen
bb08b39c71 remove duplicated code 2019-10-19 22:30:25 +02:00
B. Petersen
1908ac428b mark own forwarded messages as such 2019-10-17 19:41:17 +02:00
Alexander Krotov
dfc453c1d1 Merge pull request #727 from deltachat/strndup
Remove unused strndup
2019-10-17 12:33:51 +03:00
Alexander Krotov
9fa6289093 Remove unused strndup 2019-10-17 03:18:08 +03:00
björn petersen
6f92ce0fa8 Merge pull request #722 from deltachat/oauth2-even-better
make oauth2.get_addr work
2019-10-16 10:58:04 +02:00
B. Petersen
cde2c9137f make oauth2.get_addr work
oauth2.get_addr is used for gmail-oauth2
to retrieve the address really authorized in the oauth2 process.
2019-10-16 00:57:58 +02:00
björn petersen
120524ae00 Merge pull request #720 from deltachat/make-oauth2-better
make oauth2 POST successfully
2019-10-15 18:29:27 +02:00
holger krekel
7bb73f45a5 add LP smtp/imap connection flags to const 2019-10-15 13:46:39 +02:00
Dmitry Bogatov
2d0f563dfe Replace magic constant with symbolic name 2019-10-15 13:45:08 +02:00
VP-
cfe3c69f00 Don't use hard-coded email addresses in tests 2019-10-15 13:10:55 +02:00
B. Petersen
c266d2ca0d use POST instead GET to init or refresh oauth2 tokens 2019-10-15 00:22:37 +02:00
B. Petersen
85fc696975 more specific and distinguishable oauth2 error message 2019-10-15 00:20:08 +02:00
Alexander Krotov
9bf8bed0c3 Merge pull request #685 from deltachat/outlook_unit_test
Unit test Outlook autodiscovery
2019-10-14 21:34:43 +00:00
Alexander Krotov
c4d55f6ba4 auto_outlook: convert <Protocol> type to lowercase before comparison 2019-10-14 19:21:29 +03:00
B. Petersen
766d7cbd3a add some missing comments 2019-10-14 15:11:33 +02:00
Alexander Krotov
8e0e1bd58d Add test for Outlook autodiscovery without redirect 2019-10-14 13:42:35 +03:00
Alexander Krotov
a471ccc95a Test Outlook autoconfigure redirect parsing 2019-10-14 01:24:20 +03:00
Alexander Krotov
daac8c4824 rustfmt 2019-10-14 01:24:20 +03:00
Alexander Krotov
59c22a5626 Move Outlook autodiscovery into separate function 2019-10-14 01:24:19 +03:00
Alexander Krotov
5154f27f72 Merge pull request #712 from deltachat/moz_autoconfigure_unit
auto_mozilla: split XML parsing into separate function
2019-10-13 16:34:05 +00:00
Alexander Krotov
5f7279eb85 auto_mozilla: server is only configured if the type matches
This fixes the testcase introduced in previous commit
2019-10-12 17:07:20 +03:00
Alexander Krotov
ce67f593f6 Add XML parsing test for Mozilla autoconfig 2019-10-12 17:07:20 +03:00
Alexander Krotov
556ea57f37 auto_mozilla: split XML parsing into separate function 2019-10-12 17:07:20 +03:00
Dmitry Bogatov
a4257b619a Replace flags argument to Sql.open with single bool
Previously, "flags" argument of Sql.open was of type libc::c_int, but
only one bit was used: whether to open database read-only. This commit
makes it explicit by changing type to bool and renaming argument.
2019-10-12 09:56:18 +02:00
Alexander Krotov
8479c8afbf Do not convert 1 to libc::c_int
It is compared to u32 in this case
2019-10-11 21:52:47 +02:00
Alexander Krotov
eba012b965 Do not use libc::c_int in Simplify 2019-10-11 21:52:47 +02:00
Alexander Krotov
66e53e6804 Return bool from Imap::fetch()
It is unused anyway
2019-10-11 21:52:47 +02:00
Alexander Krotov
c8aa8b55f6 Use i32 instead of libc::c_int for try_again
It is already declared as i32 in the structure
2019-10-11 21:52:47 +02:00
Alexander Krotov
900e3905c0 Count number of libc references in top_evil_rs.py 2019-10-11 21:52:47 +02:00
holger krekel
088490721d bump to 1.0.0-beta.1, add initial Changelog 2019-10-10 19:08:47 +02:00
björn petersen
a40b99aae0 Merge pull request #709 from deltachat/fix/features
fix: disable default features for pgp, by default
2019-10-10 18:32:56 +02:00
dignifiedquire
b9646446f8 fix: disable default features for pgp, by default 2019-10-10 18:05:11 +02:00
holger krekel
4e36b35039 cargo-fmt only 2019-10-10 12:01:01 +02:00
holger krekel
d412ee6042 make dc_str_from_clist safe and return a string instead of c-string -- this allows to remove some unsafe and now unneccessary cleanup code 2019-10-10 12:01:01 +02:00
B. Petersen
67848e3333 use libc::c_char as i8 and u8 are used differently on x86 and arm 2019-10-10 08:44:31 +02:00
björn petersen
4d79c6e235 Merge pull request #705 from deltachat/safer-strn
len-check in strncasecmp()
2019-10-10 01:19:09 +02:00
B. Petersen
bc99d9d196 cargo fmt 2019-10-10 00:39:35 +02:00
B. Petersen
e1fc5863c2 target comments of @hpk42 2019-10-10 00:29:52 +02:00
B. Petersen
f0791149e6 document dc_str_unref() 2019-10-10 00:29:52 +02:00
B. Petersen
297b032bdc fix doxygen, some rewording 2019-10-10 00:29:52 +02:00
holger krekel
98180c175d make sure c-doc upload dirs exist even if branch name has / in it 2019-10-10 00:23:36 +02:00
dignifiedquire
46e8a436cb updates and fixes 2019-10-10 00:23:36 +02:00
dignifiedquire
dc2cf8ecfc unify naming in pgp 2019-10-10 00:23:36 +02:00
dignifiedquire
fd69ebfd1f fix and cleanup tests 2019-10-10 00:23:36 +02:00
dignifiedquire
03979fdc51 wip 2019-10-10 00:23:36 +02:00
B. Petersen
2c98e91276 target comments of @link2xt 2019-10-10 00:22:50 +02:00
B. Petersen
3270120d16 strncasecmp() compares the given number of characters but not after a 0-byte 2019-10-09 19:00:54 +02:00
holger krekel
77a7efc920 fix #694 by forking lettre and avoiding extra NOOP Smtp commands for connection checking.
If this works out and fixes our problem, we can submit upstream.
2019-10-09 18:25:41 +02:00
björn petersen
de604e744e Merge pull request #703 from deltachat/fix-strncasecmp
fix(mmime): correct implementation of strncasecmp
2019-10-09 17:31:41 +02:00
holger krekel
24c0a833bd address @flub comments 2019-10-09 15:59:31 +02:00
holger krekel
45f011c63c expose and test set_stock_translation to Python 2019-10-09 15:59:31 +02:00
holger krekel
bc86201b44 error out if %1 %2 replacements are not contained in default english version 2019-10-09 15:59:31 +02:00
holger krekel
b82af9fff3 introduce set_stock_translation and remove call to DC_EVENT_GET_STRING 2019-10-09 15:59:31 +02:00
dignifiedquire
3a1e74a306 fix(mmime): correct implementation of strncasecmp
Closes #523
2019-10-09 14:27:15 +02:00
Alexander Krotov
e4cca92910 Simplify to_string_lossy()
Call .to_string_lossy() without trying .to_str() first.
2019-10-09 08:56:02 +02:00
holger krekel
102220834c address https://github.com/deltachat/deltachat-ios/issues/328 and don't escalate xml-parsing errors to users through error! 2019-10-08 23:09:03 +02:00
holger krekel
24d744b94c try generating and uploading doxygen docs (#697) 2019-10-08 18:52:13 +02:00
holger krekel
1df6229e99 remove/push down some unsafe-fn 2019-10-08 17:37:37 +02:00
Alexander Krotov
c23e98ff83 Merge pull request #693 from deltachat/mozautoconfig
Use Rust types in auto_mozilla.rs
2019-10-07 23:03:17 +00:00
holger krekel
b7c81f37c0 ok_to_continue is 0, now start counting "unsafe fn" ... 2019-10-07 23:58:32 +02:00
Alexander Krotov
5c3a7e4119 Use Rust types in auto_mozilla.rs 2019-10-07 18:43:53 +03:00
holger krekel
a94acef49b cargo fmt 2019-10-07 13:39:01 +02:00
holger krekel
7f5b362eda remove ok_to_continue and simplify/refactor imap and smtp connection trying
address @r10s review comments and also remove unneccessary "unsafe fn"
2019-10-07 13:39:01 +02:00
holger krekel
ba5b3ad675 fix #690 by avoiding account.__del__ and registering/unregistering with atexit (a module that manages process/interpreter shutdown and calls into registered shutdown. Recommended way for user code still is to call account.shutdown() explcitely. 2019-10-07 12:24:50 +02:00
björn petersen
c1e4d1e7a4 Merge pull request #688 from deltachat/remove-predecessor
remove scanning for old Chat-Predecessor header
2019-10-07 02:02:15 +02:00
B. Petersen
dd8744b74e remove unused import 2019-10-07 01:15:42 +02:00
holger krekel
b775ecca08 fix tests 2019-10-07 00:55:33 +02:00
holger krekel
b8f211a013 cargo fmt 2019-10-07 00:55:33 +02:00
holger krekel
51534b2fae move all alloc/free/stop/has/shall_stop*ongoing* methods to context
introduce stop_ongoing to bindings and test for cancelation of configure
2019-10-07 00:55:33 +02:00
B. Petersen
710db2ba0a remove scanning for old Chat-Predecessor header
the Chat-Predecessor header was dropped about 10 month ago.
as a replacement, the standard headers are used.
2019-10-07 00:43:43 +02:00
holger krekel
32ef0d4dc3 cargo fmt 2019-10-07 00:22:19 +02:00
holger krekel
b3cd80ba6d remove ok_to_continue, shift code to a result-returning add_export_files to avoid if-branches, cleanup and simplify some logging 2019-10-07 00:22:19 +02:00
holger krekel
3f053f899e cargo fmt 2019-10-07 00:15:39 +02:00
holger krekel
22b4d1734c remove last ok_to_continue in mimeparser 2019-10-07 00:15:39 +02:00
björn petersen
46a71e81a0 Merge pull request #683 from deltachat/outlook_none
Fix and refactor Outlook autodiscovery
2019-10-06 15:54:01 +02:00
Alexander Krotov
b4851187ba Safe outlk_autodiscover 2019-10-06 05:46:45 +03:00
Alexander Krotov
0252969f7e Remove unused in_0 from outlk_autodiscover_t 2019-10-06 05:16:17 +03:00
Alexander Krotov
f86cec4844 Mark moz_autoconfigure as safe 2019-10-06 05:16:17 +03:00
Alexander Krotov
cd2e36da92 rustfmt 2019-10-06 05:16:17 +03:00
Alexander Krotov
1b13107181 Return Option<String> from read_autoconf_file 2019-10-06 05:16:17 +03:00
Alexander Krotov
6fbde21995 outlk_autodiscover: store url as String 2019-10-06 05:16:17 +03:00
Alexander Krotov
a6608513ac auto_outlook: restore and add some comments 2019-10-06 05:16:17 +03:00
Alexander Krotov
d43c225be3 Return None from outlk_autodiscover if no XML config was parsed 2019-10-06 05:16:17 +03:00
B. Petersen
1802d7658d add some tests for dc_create_outgoing_rfc724_mid()
as noticed in the last attempts to change the prefix,
this test is missing (as probably many more :)
2019-10-05 22:48:00 +02:00
B. Petersen
275f5d713f cargo fmt 2019-10-05 22:47:39 +02:00
B. Petersen
e40cfeec58 bubble up sql, get_abs_path errors 2019-10-05 22:47:39 +02:00
B. Petersen
275b4b8d36 bubble up sql-errors in reveive_imf() 2019-10-05 22:47:39 +02:00
B. Petersen
77cef632c7 bubble up update_param() error
bubble up the error instead of ignoring it -
but also do not panic as before #673
2019-10-05 22:47:39 +02:00
B. Petersen
db2064de14 use .to_string_lossy() member of Path, CStr etc.
instead of calling to_str().unwrap_or_default().
2019-10-05 22:46:29 +02:00
53 changed files with 2372 additions and 2469 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:

24
CHANGELOG.md Normal file
View File

@@ -0,0 +1,24 @@
# API changes
## 1.0.0-beta1
- first beta of the Delta Chat Rust core library. many fixes of crashes
and other issues compared to 1.0.0-alpha.5.
- Most code is now "rustified" and does not do manual memory allocation anymore.
- The `DC_EVENT_GET_STRING` event is not used anymore, removing the last
event where the core requested a return value from the event callback.
Please now use `dc_set_stock_translation()` API for core messages
to be properly localized.
- Deltachat FFI docs are automatically generated and available here:
https://c.delta.chat
- New events ImapMessageMoved and ImapMessageDeleted
For a full list of changes, please see our closed Pull Requests:
https://github.com/deltachat/deltachat-core-rust/pulls?q=is%3Apr+is%3Aclosed

489
Cargo.lock generated

File diff suppressed because it is too large Load Diff

View File

@@ -1,7 +1,7 @@
[package]
name = "deltachat"
version = "1.0.0-alpha.5"
authors = ["dignifiedquire <dignifiedquire@gmail.com>"]
version = "1.0.0-beta.1"
authors = ["Delta Chat Developers (ML) <delta@codespeak.net>"]
edition = "2018"
license = "MPL"
@@ -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 = { version = "0.2.3", default-features = false }
hex = "0.3.2"
sha2 = "0.8.0"
rand = "0.6.5"
@@ -19,8 +19,8 @@ reqwest = "0.9.15"
num-derive = "0.2.5"
num-traits = "0.2.6"
native-tls = "0.2.3"
lettre = "0.9.0"
imap = { git = "https://github.com/jonhoo/rust-imap", rev = "281d2eb8ab50dc656ceff2ae749ca5045f334e15" }
lettre = { git = "https://github.com/deltachat/lettre", branch = "master" }
imap = { git = "https://github.com/dignifiedquire/rust-imap", branch = "fix/oauth-response" }
base64 = "0.10"
charset = "0.1"
percent-encoding = "2.0"

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

@@ -1,8 +1,8 @@
[package]
name = "deltachat_ffi"
version = "1.0.0-alpha.5"
version = "1.0.0-beta.1"
description = "Deltachat FFI"
authors = ["dignifiedquire <dignifiedquire@gmail.com>"]
authors = ["Delta Chat Developers (ML) <delta@codespeak.net>"]
edition = "2018"
readme = "README.md"
license = "MIT OR Apache-2.0"

View File

@@ -78,8 +78,7 @@ typedef struct _dc_provider dc_provider_t;
*
* The example above uses "pthreads",
* however, you can also use anything else for thread handling.
* NB: The deltachat-core library itself does not create any threads on its own,
* however, functions, unless stated otherwise, are thread-safe.
* All deltachat-core-functions, unless stated otherwise, are thread-safe.
*
* After that you can **define and open a database.**
* The database is a normal sqlite-file and is created as needed:
@@ -135,7 +134,7 @@ typedef struct _dc_provider dc_provider_t;
*
* printf("Message %i: %s\n", i+1, text);
*
* free(text);
* dc_str_unref(text);
* dc_msg_unref(msg);
* }
* dc_array_unref(msglist);
@@ -321,7 +320,7 @@ int dc_is_open (const dc_context_t* context);
* @memberof dc_context_t
* @param context The context object as created by dc_context_new().
* @return Blob directory associated with the context object, empty string if unset or on errors. NULL is never returned.
* The returned string must be free()'d.
* The returned string must be released using dc_str_unref().
*/
char* dc_get_blobdir (const dc_context_t* context);
@@ -397,11 +396,24 @@ int dc_set_config (dc_context_t* context, const char*
* @param context The context object as created by dc_context_new(). For querying system values, this can be NULL.
* @param key The key to query.
* @return Returns current value of "key", if "key" is unset, the default
* value is returned. The returned value must be free()'d, NULL is never
* value is returned. The returned value must be released using dc_str_unref(), NULL is never
* returned. If there is an error an empty string will be returned.
*/
char* dc_get_config (dc_context_t* context, const char* key);
/**
* Set stock string translation.
*
* The function will emit warnings if it returns an error state.
*
* @memberof dc_context_t
* @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 stock_id, const char* stock_msg);
/**
* Get information about the context.
@@ -415,7 +427,7 @@ char* dc_get_config (dc_context_t* context, const char*
*
* @memberof dc_context_t
* @param context The context as created by dc_context_new().
* @return String which must be free()'d after usage. Never returns NULL.
* @return String which must be released using dc_str_unref() after usage. Never returns NULL.
*/
char* dc_get_info (dc_context_t* context);
@@ -446,6 +458,7 @@ char* dc_get_info (dc_context_t* context);
* `https://localhost:PORT/PATH`, `urn:ietf:wg:oauth:2.0:oob`
* (the latter just displays the code the user can copy+paste then)
* @return URL that can be opened in the browser to start OAuth2.
* Returned strings must be released using dc_str_unref().
* If OAuth2 is not possible for the given e-mail-address, NULL is returned.
*/
char* dc_get_oauth2_url (dc_context_t* context, const char* addr, const char* redirect_uri);
@@ -694,6 +707,7 @@ void dc_perform_mvbox_idle (dc_context_t* context);
*/
void dc_interrupt_mvbox_idle (dc_context_t* context);
/**
* Execute pending sentbox-jobs.
* This function and dc_perform_sentbox_fetch() and dc_perform_sentbox_idle()
@@ -847,6 +861,7 @@ void dc_maybe_network (dc_context_t* context);
#define DC_GCL_ARCHIVED_ONLY 0x01
#define DC_GCL_NO_SPECIALS 0x02
#define DC_GCL_ADD_ALLDONE_HINT 0x04
#define DC_GCL_ADD_DRAFTS 0x08
/**
@@ -890,6 +905,12 @@ void dc_maybe_network (dc_context_t* context);
* not needed when DC_GCL_ARCHIVED_ONLY is already set)
* - if the flag DC_GCL_ADD_ALLDONE_HINT is set, DC_CHAT_ID_ALLDONE_HINT
* is added as needed.
* - if the flag DC_GCL_ADD_DRAFTS is set
* and a chat has a recent draft,
* the draft is returned by dc_chatlist_get_msg_id().
* if the chatlist is shown permanently eg. on desktop,
* you may not want to add this flags
* as the resorting on dc_set_draft() is easily confusing.
* @param query_str An optional query for filtering the list. Only chats matching this query
* are returned. Give NULL for no filtering.
* @param query_id An optional contact ID for filtering the list. Only chats including this contact ID
@@ -1465,7 +1486,7 @@ int dc_set_chat_profile_image (dc_context_t* context, uint32_t ch
* @memberof dc_context_t
* @param context The context object as created by dc_context_new().
* @param msg_id The message id for which information should be generated
* @return Text string, must be free()'d after usage
* @return Text string, must be released using dc_str_unref() after usage
*/
char* dc_get_msg_info (dc_context_t* context, uint32_t msg_id);
@@ -1479,7 +1500,7 @@ char* dc_get_msg_info (dc_context_t* context, uint32_t ms
* @memberof dc_context_t
* @param context The context object as created by dc_context_new().
* @param msg_id The message id, must be the id of an incoming message.
* @return Raw headers as a multi-line string, must be free()'d after usage.
* @return Raw headers as a multi-line string, must be released using dc_str_unref() after usage.
* Returns NULL if there are no headers saved for the given message,
* eg. because of save_mime_headers is not set
* or the message is not incoming.
@@ -1720,7 +1741,7 @@ void dc_block_contact (dc_context_t* context, uint32_t co
* @memberof dc_context_t
* @param context The context object as created by dc_context_new().
* @param contact_id ID of the contact to get the encryption info for.
* @return Multi-line text, must be free()'d after usage.
* @return Multi-line text, must be released using dc_str_unref() after usage.
*/
char* dc_get_contact_encrinfo (dc_context_t* context, uint32_t contact_id);
@@ -1856,7 +1877,8 @@ void dc_imex (dc_context_t* context, int what, c
* @memberof dc_context_t
* @param context The context as created by dc_context_new().
* @param dir Directory to search backups in.
* @return String with the backup file, typically given to dc_imex(), returned strings must be free()'d.
* @return String with the backup file, typically given to dc_imex(),
* returned strings must be released using dc_str_unref().
* The function returns NULL if no backup was found.
*/
char* dc_imex_has_backup (dc_context_t* context, const char* dir);
@@ -1904,7 +1926,7 @@ char* dc_imex_has_backup (dc_context_t* context, const char*
*
* @memberof dc_context_t
* @param context The context object.
* @return The setup code. Must be free()'d after usage.
* @return The setup code. Must be released using dc_str_unref() after usage.
* On errors, eg. if the message could not be sent, NULL is returned.
*/
char* dc_initiate_key_transfer (dc_context_t* context);
@@ -2009,7 +2031,7 @@ dc_lot_t* dc_check_qr (dc_context_t* context, const char*
* If set to 0, the setup-Verified-contact-protocol is offered in the QR code.
* @return Text that should go to the QR code,
* On errors, an empty QR code is returned, NULL is never returned.
* The returned string must be free()'d after usage.
* The returned string must be released using dc_str_unref() after usage.
*/
char* dc_get_securejoin_qr (dc_context_t* context, uint32_t chat_id);
@@ -2179,6 +2201,21 @@ dc_array_t* dc_get_locations (dc_context_t* context, uint32_t cha
void dc_delete_all_locations (dc_context_t* context);
/**
* Release a string returned by another deltachat-core function.
* - Strings returned by any deltachat-core-function
* MUST NOT be released by the standard free() function;
* always use dc_str_unref() for this purpose.
* - dc_str_unref() MUST NOT be called for strings not returned by deltachat-core.
* - dc_str_unref() MUST NOT be called for other objectes returned by deltachat-core.
*
* @memberof dc_context_t
* @param str The string to release.
* @return None.
*/
void dc_str_unref (char* str);
/**
* @class dc_array_t
*
@@ -2319,7 +2356,7 @@ uint32_t dc_array_get_msg_id (const dc_array_t* array, size_t in
* @param index Index of the item. Must be between 0 and dc_array_get_cnt()-1.
* @return Marker-character of the item at the given index.
* NULL if there is no marker-character bound to the given item.
* The returned value must be free()'d after usage.
* The returned value must be released using dc_str_unref() after usage.
*/
char* dc_array_get_marker (const dc_array_t* array, size_t index);
@@ -2574,7 +2611,7 @@ int dc_chat_get_type (const dc_chat_t* chat);
*
* @memberof dc_chat_t
* @param chat The chat object.
* @return Chat name as a string. Must be free()'d after usage. Never NULL.
* @return Chat name as a string. Must be released using dc_str_unref() after usage. Never NULL.
*/
char* dc_chat_get_name (const dc_chat_t* chat);
@@ -2587,7 +2624,7 @@ char* dc_chat_get_name (const dc_chat_t* chat);
*
* @memberof dc_chat_t
* @param chat The chat object to calulate the subtitle for.
* @return Subtitle as a string. Must be free()'d after usage. Never NULL.
* @return Subtitle as a string. Must be released using dc_str_unref() after usage. Never NULL.
*/
char* dc_chat_get_subtitle (const dc_chat_t* chat);
@@ -2603,7 +2640,7 @@ char* dc_chat_get_subtitle (const dc_chat_t* chat);
* @param chat The chat object.
* @return Path and file if the profile image, if any.
* NULL otherwise.
* Must be free()'d after usage.
* Must be released using dc_str_unref() after usage.
*/
char* dc_chat_get_profile_image (const dc_chat_t* chat);
@@ -2907,7 +2944,7 @@ int64_t dc_msg_get_sort_timestamp (const dc_msg_t* msg);
*
* @memberof dc_msg_t
* @param msg The message object.
* @return Message text. The result must be free()'d. Never returns NULL.
* @return Message text. The result must be released using dc_str_unref(). Never returns NULL.
*/
char* dc_msg_get_text (const dc_msg_t* msg);
@@ -2921,9 +2958,9 @@ char* dc_msg_get_text (const dc_msg_t* msg);
*
* @memberof dc_msg_t
* @param msg The message object.
* @return Full path, file name and extension of the file associated with the
* message. If there is no file associated with the message, an emtpy
* string is returned. NULL is never returned and the returned value must be free()'d.
* @return Full path, file name and extension of the file associated with the message.
* If there is no file associated with the message, an emtpy string is returned.
* NULL is never returned and the returned value must be released using dc_str_unref().
*/
char* dc_msg_get_file (const dc_msg_t* msg);
@@ -2936,7 +2973,7 @@ char* dc_msg_get_file (const dc_msg_t* msg);
* @param msg The message object.
* @return Base file name plus extension without part. If there is no file
* associated with the message, an empty string is returned. The returned
* value must be free()'d.
* value must be released using dc_str_unref().
*/
char* dc_msg_get_filename (const dc_msg_t* msg);
@@ -2948,7 +2985,8 @@ char* dc_msg_get_filename (const dc_msg_t* msg);
*
* @memberof dc_msg_t
* @param msg The message object.
* @return String containing the mime type. Must be free()'d after usage. NULL is never returned.
* @return String containing the mime type.
* Must be released using dc_str_unref() after usage. NULL is never returned.
*/
char* dc_msg_get_filemime (const dc_msg_t* msg);
@@ -3056,7 +3094,8 @@ dc_lot_t* dc_msg_get_summary (const dc_msg_t* msg, const dc_cha
* @memberof dc_msg_t
* @param msg The message object.
* @param approx_characters Rough length of the expected string.
* @return A summary for the given messages. The returned string must be free()'d.
* @return A summary for the given messages.
* The returned string must be released using dc_str_unref().
* Returns an empty string on errors, never returns NULL.
*/
char* dc_msg_get_summarytext (const dc_msg_t* msg, int approx_characters);
@@ -3201,7 +3240,7 @@ int dc_msg_is_setupmessage (const dc_msg_t* msg);
* @memberof dc_msg_t
* @param msg The message object.
* @return Typically, the first two digits of the setup code or an empty string if unknown.
* NULL is never returned. Must be free()'d when done.
* NULL is never returned. Must be released using dc_str_unref() when done.
*/
char* dc_msg_get_setupcodebegin (const dc_msg_t* msg);
@@ -3358,7 +3397,8 @@ uint32_t dc_contact_get_id (const dc_contact_t* contact);
*
* @memberof dc_contact_t
* @param contact The contact object.
* @return String with the email address, must be free()'d. Never returns NULL.
* @return String with the email address,
* must be released using dc_str_unref(). Never returns NULL.
*/
char* dc_contact_get_addr (const dc_contact_t* contact);
@@ -3372,7 +3412,8 @@ char* dc_contact_get_addr (const dc_contact_t* contact);
*
* @memberof dc_contact_t
* @param contact The contact object.
* @return String with the name to display, must be free()'d. Empty string if unset, never returns NULL.
* @return String with the name to display, must be released using dc_str_unref().
* Empty string if unset, never returns NULL.
*/
char* dc_contact_get_name (const dc_contact_t* contact);
@@ -3386,7 +3427,8 @@ char* dc_contact_get_name (const dc_contact_t* contact);
*
* @memberof dc_contact_t
* @param contact The contact object.
* @return String with the name to display, must be free()'d. Never returns NULL.
* @return String with the name to display, must be released using dc_str_unref().
* Never returns NULL.
*/
char* dc_contact_get_display_name (const dc_contact_t* contact);
@@ -3402,7 +3444,8 @@ char* dc_contact_get_display_name (const dc_contact_t* contact);
*
* @memberof dc_contact_t
* @param contact The contact object.
* @return Summary string, must be free()'d. Never returns NULL.
* @return Summary string, must be released using dc_str_unref().
* Never returns NULL.
*/
char* dc_contact_get_name_n_addr (const dc_contact_t* contact);
@@ -3414,7 +3457,8 @@ char* dc_contact_get_name_n_addr (const dc_contact_t* contact);
*
* @memberof dc_contact_t
* @param contact The contact object.
* @return String with the name to display, must be free()'d. Never returns NULL.
* @return String with the name to display, must be released using dc_str_unref().
* Never returns NULL.
*/
char* dc_contact_get_first_name (const dc_contact_t* contact);
@@ -3428,7 +3472,7 @@ char* dc_contact_get_first_name (const dc_contact_t* contact);
* @param contact The contact object.
* @return Path and file if the profile image, if any.
* NULL otherwise.
* Must be free()'d after usage.
* Must be released using dc_str_unref() after usage.
*/
char* dc_contact_get_profile_image (const dc_contact_t* contact);
@@ -3509,7 +3553,7 @@ dc_provider_t* dc_provider_new_from_email (const char* email);
*
* @memberof dc_provider_t
* @param provider The dc_provider_t struct.
* @return A string which must be free()d.
* @return A string which must be released using dc_str_unref().
*/
char* dc_provider_get_overview_page (const dc_provider_t* provider);
@@ -3521,7 +3565,7 @@ char* dc_provider_get_overview_page (const dc_provider_t* prov
*
* @memberof dc_provider_t
* @param provider The dc_provider_t struct.
* @return A string which must be free()d.
* @return A string which must be released using dc_str_unref().
*/
char* dc_provider_get_name (const dc_provider_t* provider);
@@ -3534,7 +3578,7 @@ char* dc_provider_get_name (const dc_provider_t* prov
*
* @memberof dc_provider_t
* @param provider The dc_provider_t struct.
* @return A string which must be free()d.
* @return A string which must be released using dc_str_unref().
*/
char* dc_provider_get_markdown (const dc_provider_t* provider);
@@ -3546,7 +3590,7 @@ char* dc_provider_get_markdown (const dc_provider_t* prov
*
* @memberof dc_provider_t
* @param provider The dc_provider_t struct.
* @return A string which must be free()d.
* @return A string which must be released using dc_str_unref().
*/
char* dc_provider_get_status_date (const dc_provider_t* provider);
@@ -3608,7 +3652,9 @@ void dc_lot_unref (dc_lot_t* lot);
*
* @memberof dc_lot_t
* @param lot The lot object.
* @return A string, the string may be empty and the returned value must be free()'d. NULL if there is no such string.
* @return A string, the string may be empty
* and the returned value must be released using dc_str_unref().
* NULL if there is no such string.
*/
char* dc_lot_get_text1 (const dc_lot_t* lot);
@@ -3617,10 +3663,10 @@ char* dc_lot_get_text1 (const dc_lot_t* lot);
* Get second string. The meaning of the string is defined by the creator of the object.
*
* @memberof dc_lot_t
*
* @param lot The lot object.
*
* @return A string, the string may be empty and the returned value must be free()'d . NULL if there is no such string.
* @return A string, the string may be empty
* and the returned value must be released using dc_str_unref().
* NULL if there is no such string.
*/
char* dc_lot_get_text2 (const dc_lot_t* lot);
@@ -3641,9 +3687,7 @@ int dc_lot_get_text1_meaning (const dc_lot_t* lot);
* Get the associated state. The meaning of the state is defined by the creator of the object.
*
* @memberof dc_lot_t
*
* @param lot The lot object.
*
* @return The state as defined by the creator of the object. 0 if there is not state or on errors.
*/
int dc_lot_get_state (const dc_lot_t* lot);
@@ -3665,9 +3709,7 @@ uint32_t dc_lot_get_id (const dc_lot_t* lot);
* The meaning of the timestamp is defined by the creator of the object.
*
* @memberof dc_lot_t
*
* @param lot The lot object.
*
* @return The timestamp as defined by the creator of the object. 0 if there is not timestamp or on errors.
*/
int64_t dc_lot_get_timestamp (const dc_lot_t* lot);
@@ -3867,7 +3909,7 @@ int64_t dc_lot_get_timestamp (const dc_lot_t* lot);
*
* @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.
* Must not be unref'd or modified and is valid only until the callback returns.
* @return 0
*/
#define DC_EVENT_INFO 100
@@ -3878,7 +3920,7 @@ int64_t dc_lot_get_timestamp (const dc_lot_t* lot);
*
* @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.
* Must not be unref'd or modified and is valid only until the callback returns.
* @return 0
*/
#define DC_EVENT_SMTP_CONNECTED 101
@@ -3889,7 +3931,7 @@ int64_t dc_lot_get_timestamp (const dc_lot_t* lot);
*
* @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.
* Must not be unref'd or modified and is valid only until the callback returns.
* @return 0
*/
#define DC_EVENT_IMAP_CONNECTED 102
@@ -3899,7 +3941,7 @@ int64_t dc_lot_get_timestamp (const dc_lot_t* lot);
*
* @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.
* Must not be unref'd or modified and is valid only until the callback returns.
* @return 0
*/
#define DC_EVENT_SMTP_MESSAGE_SENT 103
@@ -3909,7 +3951,7 @@ int64_t dc_lot_get_timestamp (const dc_lot_t* lot);
*
* @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.
* Must not be unref'd or modified and is valid only until the callback returns.
* @return 0
*/
#define DC_EVENT_IMAP_MESSAGE_DELETED 104
@@ -3919,7 +3961,7 @@ int64_t dc_lot_get_timestamp (const dc_lot_t* lot);
*
* @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.
* Must not be unref'd or modified and is valid only until the callback returns.
* @return 0
*/
#define DC_EVENT_IMAP_MESSAGE_MOVED 105
@@ -3929,7 +3971,7 @@ int64_t dc_lot_get_timestamp (const dc_lot_t* lot);
*
* @param data1 0
* @param data2 (const char*) path name
* Must not be free()'d or modified and is valid only until the callback returns.
* Must not be unref'd or modified and is valid only until the callback returns.
* @return 0
*/
#define DC_EVENT_NEW_BLOB_FILE 150
@@ -3939,7 +3981,7 @@ int64_t dc_lot_get_timestamp (const dc_lot_t* lot);
*
* @param data1 0
* @param data2 (const char*) path name
* Must not be free()'d or modified and is valid only until the callback returns.
* Must not be unref'd or modified and is valid only until the callback returns.
* @return 0
*/
#define DC_EVENT_DELETED_BLOB_FILE 151
@@ -3952,7 +3994,7 @@ int64_t dc_lot_get_timestamp (const dc_lot_t* lot);
*
* @param data1 0
* @param data2 (const char*) Warning string in english language.
* Must not be free()'d or modified and is valid only until the callback returns.
* Must not be unref'd or modified and is valid only until the callback returns.
* @return 0
*/
#define DC_EVENT_WARNING 300
@@ -3972,9 +4014,10 @@ int64_t dc_lot_get_timestamp (const dc_lot_t* lot);
* in a messasge box then.
*
* @param data1 0
* @param data2 (const char*) Error string, always set, never NULL. Frequent error strings are
* localized using #DC_EVENT_GET_STRING, however, most error strings will be in english language.
* Must not be free()'d or modified and is valid only until the callback returns.
* @param data2 (const char*) Error string, always set, never NULL.
* Some error strings are taken from dc_set_stock_translation(),
* however, most error strings will be in english language.
* Must not be unref'd or modified and is valid only until the callback returns.
* @return 0
*/
#define DC_EVENT_ERROR 400
@@ -3998,7 +4041,7 @@ int64_t dc_lot_get_timestamp (const dc_lot_t* lot);
* @param data1 (int) 1=first/new network error, should be reported the user;
* 0=subsequent network error, should be logged only
* @param data2 (const char*) Error string, always set, never NULL.
* Must not be free()'d or modified and is valid only until the callback returns.
* Must not be unref'd or modified and is valid only until the callback returns.
* @return 0
*/
#define DC_EVENT_ERROR_NETWORK 401
@@ -4013,7 +4056,7 @@ int64_t dc_lot_get_timestamp (const dc_lot_t* lot);
*
* @param data1 0
* @param data2 (const char*) Info string in english language.
* Must not be free()'d or modified
* Must not be unref'd or modified
* and is valid only until the callback returns.
* @return 0
*/
@@ -4144,7 +4187,7 @@ int64_t dc_lot_get_timestamp (const dc_lot_t* lot);
* services.
*
* @param data1 (const char*) Path and file name.
* Must not be free()'d or modified and is valid only until the callback returns.
* Must not be unref'd or modified and is valid only until the callback returns.
* @param data2 0
* @return 0
*/
@@ -4185,36 +4228,21 @@ int64_t dc_lot_get_timestamp (const dc_lot_t* lot);
#define DC_EVENT_SECUREJOIN_JOINER_PROGRESS 2061
// the following events are functions that should be provided by the frontends
/**
* Requeste a localized string from the frontend.
*
* @param data1 (int) ID of the string to request, one of the DC_STR_* constants.
* @param data2 (int) The count. If the requested string contains a placeholder for a numeric value,
* the ui may use this value to return different strings on different plural forms.
* @return (const char*) Null-terminated UTF-8 string.
* The string will be free()'d by the core,
* so it must be allocated using malloc() or a compatible function.
* Return 0 if the ui cannot provide the requested string
* the core will use a default string in english language then.
*/
#define DC_EVENT_GET_STRING 2091
/**
* @}
*/
#define DC_EVENT_FILE_COPIED 2055 // deprecated
#define DC_EVENT_IS_OFFLINE 2081 // deprecated
#define DC_ERROR_SEE_STRING 0 // deprecated
#define DC_ERROR_SELF_NOT_IN_GROUP 1 // deprecated
#define DC_STR_SELFNOTINGRP 21 // deprecated
#define DC_EVENT_FILE_COPIED 2055 // not used anymore
#define DC_EVENT_IS_OFFLINE 2081 // not used anymore
#define DC_EVENT_GET_STRING 2091 // not used anymore, use dc_set_stock_translation()
#define DC_ERROR_SEE_STRING 0 // not used anymore
#define DC_ERROR_SELF_NOT_IN_GROUP 1 // not used anymore
#define DC_STR_SELFNOTINGRP 21 // not used anymore
#define DC_EVENT_DATA1_IS_STRING(e) ((e)==DC_EVENT_IMEX_FILE_WRITTEN || (e)==DC_EVENT_FILE_COPIED)
#define DC_EVENT_DATA2_IS_STRING(e) ((e)>=100 && (e)<=499)
#define DC_EVENT_RETURNS_INT(e) ((e)==DC_EVENT_IS_OFFLINE)
#define DC_EVENT_RETURNS_STRING(e) ((e)==DC_EVENT_GET_STRING)
#define DC_EVENT_RETURNS_INT(e) ((e)==DC_EVENT_IS_OFFLINE) // not used anymore
#define DC_EVENT_RETURNS_STRING(e) ((e)==DC_EVENT_GET_STRING) // not used anymore
char* dc_get_version_str (void); // deprecated
void dc_array_add_id (dc_array_t*, uint32_t); // deprecated
@@ -4316,10 +4344,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
void dc_str_unref (char*);
#define DC_STR_STICKER 67
#define DC_STR_COUNT 67
/*
* @}

View File

@@ -24,7 +24,10 @@ 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_lossy, OsStrExt, StrExt};
use deltachat::dc_tools::{
as_path, dc_strdup, to_opt_string_lossy, to_string_lossy, OsStrExt, StrExt,
};
use deltachat::stock::StockMessage;
use deltachat::*;
// as C lacks a good and portable error handling,
@@ -166,12 +169,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,
@@ -308,11 +305,14 @@ pub unsafe extern "C" fn dc_set_config(
return 0;
}
let ffi_context = &*context;
match config::Config::from_str(as_str(key)) {
match config::Config::from_str(&to_string_lossy(key)) {
// When ctx.set_config() fails it already logged the error.
// TODO: Context::set_config() should not log this
Ok(key) => ffi_context
.with_inner(|ctx| ctx.set_config(key, as_opt_str(value)).is_ok() as libc::c_int)
.with_inner(|ctx| {
ctx.set_config(key, to_opt_string_lossy(value).as_ref().map(|x| x.as_str()))
.is_ok() as libc::c_int
})
.unwrap_or(0),
Err(_) => {
ffi_context.error("dc_set_config(): invalid key");
@@ -331,7 +331,7 @@ pub unsafe extern "C" fn dc_get_config(
return "".strdup();
}
let ffi_context = &*context;
match config::Config::from_str(as_str(key)) {
match config::Config::from_str(&to_string_lossy(key)) {
Ok(key) => ffi_context
.with_inner(|ctx| ctx.get_config(key).unwrap_or_default().strdup())
.unwrap_or_else(|_| "".strdup()),
@@ -342,6 +342,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 = to_string_lossy(stock_msg);
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() {
@@ -622,22 +651,24 @@ pub unsafe extern "C" fn dc_get_chatlist(
return ptr::null_mut();
}
let ffi_context = &*context;
let qs = if query_str.is_null() {
None
} else {
Some(as_str(query_str))
};
let qs = to_opt_string_lossy(query_str);
let qi = if query_id == 0 { None } else { Some(query_id) };
ffi_context
.with_inner(
|ctx| match chatlist::Chatlist::try_load(ctx, flags as usize, qs, qi) {
.with_inner(|ctx| {
match chatlist::Chatlist::try_load(
ctx,
flags as usize,
qs.as_ref().map(|x| x.as_str()),
qi,
) {
Ok(list) => {
let ffi_list = ChatlistWrapper { context, list };
Box::into_raw(Box::new(ffi_list))
}
Err(_) => ptr::null_mut(),
},
)
}
})
.unwrap_or_else(|_| ptr::null_mut())
}
@@ -1028,7 +1059,7 @@ pub unsafe extern "C" fn dc_search_msgs(
let ffi_context = &*context;
ffi_context
.with_inner(|ctx| {
let arr = dc_array_t::from(ctx.search_msgs(chat_id, as_str(query)));
let arr = dc_array_t::from(ctx.search_msgs(chat_id, to_string_lossy(query)));
Box::into_raw(Box::new(arr))
})
.unwrap_or_else(|_| ptr::null_mut())
@@ -1070,7 +1101,7 @@ pub unsafe extern "C" fn dc_create_group_chat(
};
ffi_context
.with_inner(|ctx| {
chat::create_group_chat(ctx, verified, as_str(name))
chat::create_group_chat(ctx, verified, to_string_lossy(name))
.unwrap_or_log_default(ctx, "Failed to create group chat")
})
.unwrap_or(0)
@@ -1142,7 +1173,7 @@ pub unsafe extern "C" fn dc_set_chat_name(
let ffi_context = &*context;
ffi_context
.with_inner(|ctx| {
chat::set_chat_name(ctx, chat_id, as_str(name))
chat::set_chat_name(ctx, chat_id, to_string_lossy(name))
.map(|_| 1)
.unwrap_or_log_default(ctx, "Failed to set chat name")
})
@@ -1162,15 +1193,9 @@ pub unsafe extern "C" fn dc_set_chat_profile_image(
let ffi_context = &*context;
ffi_context
.with_inner(|ctx| {
chat::set_chat_profile_image(ctx, chat_id, {
if image.is_null() {
""
} else {
as_str(image)
}
})
.map(|_| 1)
.unwrap_or_log_default(ctx, "Failed to set profile image")
chat::set_chat_profile_image(ctx, chat_id, to_string_lossy(image))
.map(|_| 1)
.unwrap_or_log_default(ctx, "Failed to set profile image")
})
.unwrap_or(0)
}
@@ -1333,7 +1358,7 @@ pub unsafe extern "C" fn dc_may_be_valid_addr(addr: *const libc::c_char) -> libc
return 0;
}
contact::may_be_valid_addr(as_str(addr)) as libc::c_int
contact::may_be_valid_addr(&to_string_lossy(addr)) as libc::c_int
}
#[no_mangle]
@@ -1347,7 +1372,7 @@ pub unsafe extern "C" fn dc_lookup_contact_id_by_addr(
}
let ffi_context = &*context;
ffi_context
.with_inner(|ctx| Contact::lookup_id_by_addr(ctx, as_str(addr)))
.with_inner(|ctx| Contact::lookup_id_by_addr(ctx, to_string_lossy(addr)))
.unwrap_or(0)
}
@@ -1362,12 +1387,14 @@ pub unsafe extern "C" fn dc_create_contact(
return 0;
}
let ffi_context = &*context;
let name = if name.is_null() { "" } else { as_str(name) };
let name = to_string_lossy(name);
ffi_context
.with_inner(|ctx| match Contact::create(ctx, name, as_str(addr)) {
Ok(id) => id,
Err(_) => 0,
})
.with_inner(
|ctx| match Contact::create(ctx, name, to_string_lossy(addr)) {
Ok(id) => id,
Err(_) => 0,
},
)
.unwrap_or(0)
}
@@ -1383,7 +1410,7 @@ pub unsafe extern "C" fn dc_add_address_book(
let ffi_context = &*context;
ffi_context
.with_inner(
|ctx| match Contact::add_address_book(ctx, as_str(addr_book)) {
|ctx| match Contact::add_address_book(ctx, to_string_lossy(addr_book)) {
Ok(cnt) => cnt as libc::c_int,
Err(_) => 0,
},
@@ -1402,11 +1429,7 @@ pub unsafe extern "C" fn dc_get_contacts(
return ptr::null_mut();
}
let ffi_context = &*context;
let query = if query.is_null() {
None
} else {
Some(as_str(query))
};
let query = to_opt_string_lossy(query);
ffi_context
.with_inner(|ctx| match Contact::get_all(ctx, flags, query) {
Ok(contacts) => Box::into_raw(Box::new(dc_array_t::from(contacts))),
@@ -1543,7 +1566,7 @@ pub unsafe extern "C" fn dc_imex(
let ffi_context = &*context;
ffi_context
.with_inner(|ctx| imex::imex(ctx, what, as_opt_str(param1)))
.with_inner(|ctx| imex::imex(ctx, what, to_opt_string_lossy(param1)))
.ok();
}
@@ -1558,7 +1581,7 @@ pub unsafe extern "C" fn dc_imex_has_backup(
}
let ffi_context = &*context;
ffi_context
.with_inner(|ctx| match imex::has_backup(ctx, as_str(dir)) {
.with_inner(|ctx| match imex::has_backup(ctx, to_string_lossy(dir)) {
Ok(res) => res.strdup(),
Err(err) => {
error!(ctx, "dc_imex_has_backup: {}", err);
@@ -1601,15 +1624,15 @@ pub unsafe extern "C" fn dc_continue_key_transfer(
}
let ffi_context = &*context;
ffi_context
.with_inner(
|ctx| match imex::continue_key_transfer(ctx, msg_id, as_str(setup_code)) {
.with_inner(|ctx| {
match imex::continue_key_transfer(ctx, msg_id, &to_string_lossy(setup_code)) {
Ok(()) => 1,
Err(err) => {
error!(ctx, "dc_continue_key_transfer: {}", err);
0
}
},
)
}
})
.unwrap_or(0)
}
@@ -1620,9 +1643,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]
@@ -1637,7 +1658,7 @@ pub unsafe extern "C" fn dc_check_qr(
let ffi_context = &*context;
ffi_context
.with_inner(|ctx| {
let lot = qr::check_qr(ctx, as_str(qr));
let lot = qr::check_qr(ctx, to_string_lossy(qr));
Box::into_raw(Box::new(lot))
})
.unwrap_or_else(|_| ptr::null_mut())
@@ -1673,7 +1694,7 @@ pub unsafe extern "C" fn dc_join_securejoin(
}
let ffi_context = &*context;
ffi_context
.with_inner(|ctx| securejoin::dc_join_securejoin(ctx, as_str(qr)))
.with_inner(|ctx| securejoin::dc_join_securejoin(ctx, &to_string_lossy(qr)))
.unwrap_or(0)
}
@@ -1722,7 +1743,7 @@ pub unsafe extern "C" fn dc_set_location(
let ffi_context = &*context;
ffi_context
.with_inner(|ctx| location::set(ctx, latitude, longitude, accuracy))
.unwrap_or(0)
.unwrap_or(false) as _
}
#[no_mangle]
@@ -2136,7 +2157,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_or_default().to_string().strdup(),
Some(p) => p.to_string_lossy().strdup(),
None => ptr::null_mut(),
})
.unwrap_or_else(|_| ptr::null_mut())
@@ -2588,8 +2609,7 @@ pub unsafe extern "C" fn dc_msg_set_text(msg: *mut dc_msg_t, text: *const libc::
return;
}
let ffi_msg = &mut *msg;
// TODO: {text} equal to NULL is treated as "", which is strange. Does anyone rely on it?
ffi_msg.message.set_text(as_opt_str(text).map(Into::into))
ffi_msg.message.set_text(to_opt_string_lossy(text))
}
#[no_mangle]
@@ -2603,7 +2623,10 @@ pub unsafe extern "C" fn dc_msg_set_file(
return;
}
let ffi_msg = &mut *msg;
ffi_msg.message.set_file(as_str(file), as_opt_str(filemime))
ffi_msg.message.set_file(
to_string_lossy(file),
to_opt_string_lossy(filemime).as_ref().map(|x| x.as_str()),
)
}
#[no_mangle]
@@ -2773,7 +2796,7 @@ pub unsafe extern "C" fn dc_contact_get_profile_image(
ffi_contact
.contact
.get_profile_image(ctx)
.map(|p| p.to_str().unwrap_or_default().to_string().strdup())
.map(|p| p.to_string_lossy().strdup())
.unwrap_or_else(|| std::ptr::null_mut())
})
.unwrap_or_else(|_| ptr::null_mut())
@@ -2898,14 +2921,6 @@ pub unsafe extern "C" fn dc_str_unref(s: *mut libc::c_char) {
libc::free(s as *mut _)
}
fn as_opt_str<'a>(s: *const libc::c_char) -> Option<&'a str> {
if s.is_null() {
return None;
}
Some(as_str(s))
}
pub mod providers;
pub trait ResultExt<T> {

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::*;
@@ -193,7 +192,7 @@ unsafe fn log_msg(context: &Context, prefix: impl AsRef<str>, msg: &Message) {
let msgtext = msg.get_text();
info!(
context,
"{}#{}{}{}: {} (Contact#{}): {} {}{}{}{} [{}]",
"{}#{}{}{}: {} (Contact#{}): {} {}{}{}{}{} [{}]",
prefix.as_ref(),
msg.get_id() as libc::c_int,
if msg.get_showpadlock() { "🔒" } else { "" },
@@ -212,6 +211,11 @@ unsafe fn log_msg(context: &Context, prefix: impl AsRef<str>, msg: &Message) {
"[FRESH]"
},
if msg.is_info() { "[INFO]" } else { "" },
if msg.is_forwarded() {
"[FORWARDED]"
} else {
""
},
statestr,
&temp2,
);
@@ -471,7 +475,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.");
@@ -742,7 +746,7 @@ pub unsafe fn dc_cmdline(context: &Context, line: &str) -> Result<(), failure::E
let longitude = arg2.parse()?;
let continue_streaming = location::set(context, latitude, longitude, 0.);
if 0 != continue_streaming {
if continue_streaming {
println!("Success, streaming should be continued.");
} else {
println!("Success, streaming can be stoppped.");

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

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

View File

@@ -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_or_default();
.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 {

View File

@@ -8,10 +8,14 @@ use crate::mailmime::types_helper::*;
pub(crate) use libc::{
calloc, close, free, isalpha, isdigit, malloc, memcmp, memcpy, memmove, memset, realloc,
strcpy, strlen, strncmp, strncpy,
strcpy, strlen, strncmp, strncpy, strnlen,
};
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,25 @@ 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, strnlen(s1 as *const libc::c_char, n));
let s2_slice =
std::slice::from_raw_parts(s2 as *const u8, strnlen(s2 as *const libc::c_char, 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
@@ -1682,6 +1695,14 @@ mod tests {
4,
)
});
assert_eq!(0, unsafe {
strncasecmp(
CString::new("hell").unwrap().as_ptr(),
CString::new("Hell").unwrap().as_ptr(),
100_000_000,
)
});
}
#[test]

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

View File

@@ -11,6 +11,7 @@ from os.path import join as joinpath
DC_GCL_ARCHIVED_ONLY = 0x01
DC_GCL_NO_SPECIALS = 0x02
DC_GCL_ADD_ALLDONE_HINT = 0x04
DC_GCL_ADD_DRAFTS = 0x08
DC_GCL_VERIFIED_ONLY = 0x01
DC_GCL_ADD_SELF = 0x02
DC_QR_ASK_VERIFYCONTACT = 200
@@ -57,6 +58,14 @@ DC_MSG_AUDIO = 40
DC_MSG_VOICE = 41
DC_MSG_VIDEO = 50
DC_MSG_FILE = 60
DC_LP_AUTH_OAUTH2 = 0x2
DC_LP_AUTH_NORMAL = 0x4
DC_LP_IMAP_SOCKET_STARTTLS = 0x100
DC_LP_IMAP_SOCKET_SSL = 0x200
DC_LP_IMAP_SOCKET_PLAIN = 0x400
DC_LP_SMTP_SOCKET_STARTTLS = 0x10000
DC_LP_SMTP_SOCKET_SSL = 0x20000
DC_LP_SMTP_SOCKET_PLAIN = 0x40000
DC_EVENT_INFO = 100
DC_EVENT_SMTP_CONNECTED = 101
DC_EVENT_IMAP_CONNECTED = 102
@@ -82,17 +91,64 @@ DC_EVENT_IMEX_PROGRESS = 2051
DC_EVENT_IMEX_FILE_WRITTEN = 2052
DC_EVENT_SECUREJOIN_INVITER_PROGRESS = 2060
DC_EVENT_SECUREJOIN_JOINER_PROGRESS = 2061
DC_EVENT_GET_STRING = 2091
DC_EVENT_FILE_COPIED = 2055
DC_EVENT_IS_OFFLINE = 2081
DC_EVENT_GET_STRING = 2091
DC_STR_SELFNOTINGRP = 21
DC_PROVIDER_STATUS_OK = 1
DC_PROVIDER_STATUS_PREPARATION = 2
DC_PROVIDER_STATUS_BROKEN = 3
DC_STR_NOMESSAGES = 1
DC_STR_SELF = 2
DC_STR_DRAFT = 3
DC_STR_MEMBER = 4
DC_STR_CONTACT = 6
DC_STR_VOICEMESSAGE = 7
DC_STR_DEADDROP = 8
DC_STR_IMAGE = 9
DC_STR_VIDEO = 10
DC_STR_AUDIO = 11
DC_STR_FILE = 12
DC_STR_STATUSLINE = 13
DC_STR_NEWGROUPDRAFT = 14
DC_STR_MSGGRPNAME = 15
DC_STR_MSGGRPIMGCHANGED = 16
DC_STR_MSGADDMEMBER = 17
DC_STR_MSGDELMEMBER = 18
DC_STR_MSGGROUPLEFT = 19
DC_STR_GIF = 23
DC_STR_ENCRYPTEDMSG = 24
DC_STR_E2E_AVAILABLE = 25
DC_STR_ENCR_TRANSP = 27
DC_STR_ENCR_NONE = 28
DC_STR_CANTDECRYPT_MSG_BODY = 29
DC_STR_FINGERPRINTS = 30
DC_STR_READRCPT = 31
DC_STR_READRCPT_MAILBODY = 32
DC_STR_MSGGRPIMGDELETED = 33
DC_STR_E2E_PREFERRED = 34
DC_STR_CONTACT_VERIFIED = 35
DC_STR_CONTACT_NOT_VERIFIED = 36
DC_STR_CONTACT_SETUP_CHANGED = 37
DC_STR_ARCHIVEDCHATS = 40
DC_STR_STARREDMSGS = 41
DC_STR_AC_SETUP_MSG_SUBJECT = 42
DC_STR_AC_SETUP_MSG_BODY = 43
DC_STR_SELFTALK_SUBTITLE = 50
DC_STR_CANNOT_LOGIN = 60
DC_STR_SERVER_RESPONSE = 61
DC_STR_MSGACTIONBYUSER = 62
DC_STR_MSGACTIONBYME = 63
DC_STR_MSGLOCATIONENABLED = 64
DC_STR_MSGLOCATIONDISABLED = 65
DC_STR_LOCATION = 66
DC_STR_STICKER = 67
DC_STR_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_LP|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

@@ -150,6 +150,11 @@ def acfactory(pytestconfig, tmpdir, request, session_liveconfig):
lib.dc_set_config(ac._dc_context, b"configured", b"1")
return ac
def peek_online_config(self):
if not session_liveconfig:
pytest.skip("specify DCC_PY_LIVECONFIG or --liveconfig")
return session_liveconfig.get(self.live_count)
def get_online_config(self):
if not session_liveconfig:
pytest.skip("specify DCC_PY_LIVECONFIG or --liveconfig")

View File

@@ -148,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()
@@ -350,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")
@@ -362,7 +389,8 @@ class TestOnlineAccount:
def test_one_account_send_bcc_setting(self, acfactory, lp):
ac1 = acfactory.get_online_configuring_account()
c2 = ac1.create_contact(email="notexists@testrun.org")
ac2_config = acfactory.peek_online_config()
c2 = ac1.create_contact(email=ac2_config["addr"])
chat = ac1.create_chat_by_contact(c2)
assert chat.id > const.DC_CHAT_ID_LAST_SPECIAL
wait_successful_IMAP_SMTP_connection(ac1)

View File

@@ -79,19 +79,13 @@ 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_or_default()
} == "Autocrypt"
&& 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_or_default()
};
let value =
unsafe { CStr::from_ptr((*optional_field).fld_value).to_string_lossy() };
if let Ok(test) = Self::from_str(value) {
if let Ok(test) = Self::from_str(&value) {
if addr_cmp(&test.addr, wanted_from) {
if fine_header.is_none() {
fine_header = Some(test);

View File

@@ -1761,9 +1761,12 @@ pub fn forward_msgs(context: &Context, msg_ids: &[u32], chat_id: u32) -> Result<
}
let mut msg = msg.unwrap();
let original_param = msg.param.clone();
if msg.from_id != DC_CONTACT_ID_SELF {
msg.param.set_int(Param::Forwarded, 1);
}
// we tested a sort of broadcast
// by not marking own forwarded messages as such,
// however, this turned out to be to confusing and unclear.
msg.param.set_int(Param::Forwarded, 1);
msg.param.remove(Param::GuranteeE2ee);
msg.param.remove(Param::ForcePlaintext);
msg.param.remove(Param::Cmd);

View File

@@ -86,7 +86,17 @@ impl Chatlist {
query: Option<&str>,
query_contact_id: Option<u32>,
) -> Result<Self> {
let mut add_archived_link_item = 0;
let mut add_archived_link_item = false;
// drafts are hidden in the database.
// if add_drafts is set, this will be expanded to `hidden=1 AND state=DC_STATE_OUT_DRAFT`.
// otherwise, this results in `hidden=0 AND state=DC_STATE_OUT_DRAFT` in the sql statement
// and no additional messages will be regarded
let add_drafts = if 0 != listflags & DC_GCL_ADD_DRAFTS {
1
} else {
0
};
// select with left join and minimum:
// - the inner select must use `hidden` and _not_ `m.hidden`
@@ -125,10 +135,10 @@ impl Chatlist {
ON c.id=m.chat_id \
AND m.timestamp=( SELECT MAX(timestamp) \
FROM msgs WHERE chat_id=c.id \
AND (hidden=0 OR (hidden=1 AND state=19))) WHERE c.id>9 \
AND (hidden=0 OR (hidden=? AND state=19))) WHERE c.id>9 \
AND c.blocked=0 AND c.id IN(SELECT chat_id FROM chats_contacts WHERE contact_id=?) \
GROUP BY c.id ORDER BY IFNULL(m.timestamp,0) DESC, m.id DESC;",
params![query_contact_id as i32],
params![add_drafts, query_contact_id as i32],
process_row,
process_rows,
)?
@@ -139,10 +149,10 @@ impl Chatlist {
ON c.id=m.chat_id \
AND m.timestamp=( SELECT MAX(timestamp) \
FROM msgs WHERE chat_id=c.id \
AND (hidden=0 OR (hidden=1 AND state=19))) WHERE c.id>9 \
AND (hidden=0 OR (hidden=? AND state=19))) WHERE c.id>9 \
AND c.blocked=0 AND c.archived=1 GROUP BY c.id \
ORDER BY IFNULL(m.timestamp,0) DESC, m.id DESC;",
params![],
params![add_drafts],
process_row,
process_rows,
)?
@@ -156,10 +166,10 @@ impl Chatlist {
ON c.id=m.chat_id \
AND m.timestamp=( SELECT MAX(timestamp) \
FROM msgs WHERE chat_id=c.id \
AND (hidden=0 OR (hidden=1 AND state=19))) WHERE c.id>9 \
AND (hidden=0 OR (hidden=? AND state=19))) WHERE c.id>9 \
AND c.blocked=0 AND c.name LIKE ? \
GROUP BY c.id ORDER BY IFNULL(m.timestamp,0) DESC, m.id DESC;",
params![str_like_cmd],
params![add_drafts, str_like_cmd],
process_row,
process_rows,
)?
@@ -171,11 +181,11 @@ impl Chatlist {
ON c.id=m.chat_id \
AND m.timestamp=( SELECT MAX(timestamp) \
FROM msgs WHERE chat_id=c.id \
AND (hidden=0 OR (hidden=1 AND state=19))) WHERE c.id>9 \
AND (hidden=0 OR (hidden=? AND state=19))) WHERE c.id>9 \
AND c.blocked=0 AND c.archived=0 \
GROUP BY c.id \
ORDER BY IFNULL(m.timestamp,0) DESC, m.id DESC;",
params![],
params![add_drafts],
process_row,
process_rows,
)?;
@@ -184,12 +194,12 @@ impl Chatlist {
if last_deaddrop_fresh_msg_id > 0 {
ids.insert(0, (DC_CHAT_ID_DEADDROP, last_deaddrop_fresh_msg_id));
}
add_archived_link_item = 1;
add_archived_link_item = true;
}
ids
};
if 0 != add_archived_link_item && dc_get_archived_cnt(context) > 0 {
if add_archived_link_item && dc_get_archived_cnt(context) > 0 {
if ids.is_empty() && 0 != listflags & DC_GCL_ADD_ALLDONE_HINT {
ids.push((DC_CHAT_ID_ALLDONE_HINT, 0));
}
@@ -270,7 +280,7 @@ impl Chatlist {
let lastmsg = if 0 != lastmsg_id {
if let Ok(lastmsg) = Message::load_from_db(context, lastmsg_id) {
if lastmsg.from_id != 1 as libc::c_uint
if lastmsg.from_id != 1
&& (chat.typ == Chattype::Group || chat.typ == Chattype::VerifiedGroup)
{
lastcontact = Contact::load_from_db(context, lastmsg.from_id).ok();

View File

@@ -3,6 +3,7 @@ use quick_xml::events::{BytesEnd, BytesStart, BytesText};
use crate::constants::*;
use crate::context::Context;
use crate::error::Error;
use crate::login_param::LoginParam;
use super::read_autoconf_file;
@@ -10,54 +11,56 @@ 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> {
pub in_0: &'a LoginParam,
struct MozAutoconfigure<'a> {
pub in_emailaddr: &'a str,
pub in_emaildomain: &'a str,
pub in_emaillocalpart: &'a str,
pub out: LoginParam,
pub out_imap_set: 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 fn moz_autoconfigure(
context: &Context,
url: &str,
param_in: &LoginParam,
) -> Option<LoginParam> {
let xml_raw = match read_autoconf_file(context, url) {
Err(err) => {
info!(context, "can't read file: {}", err);
return None;
}
Ok(content) => content,
};
#[derive(PartialEq)]
enum MozServer {
Undefined,
Imap,
Smtp,
}
// Split address into local part and domain part.
let p = param_in.addr.find("@");
if p.is_none() {
return None;
}
let (in_emaillocalpart, in_emaildomain) = param_in.addr.split_at(p.unwrap());
let in_emaildomain = &in_emaildomain[1..];
enum MozConfigTag {
Undefined,
Hostname,
Port,
Sockettype,
Username,
}
let mut reader = quick_xml::Reader::from_str(&xml_raw);
pub fn moz_parse_xml(in_emailaddr: &str, xml_raw: &str) -> Result<LoginParam, Error> {
let mut reader = quick_xml::Reader::from_str(xml_raw);
reader.trim_text(true);
let mut buf = Vec::new();
// Split address into local part and domain part.
let p = match in_emailaddr.find('@') {
Some(i) => i,
None => bail!("Email address {} does not contain @", in_emailaddr),
};
let (in_emaillocalpart, in_emaildomain) = in_emailaddr.split_at(p);
let in_emaildomain = &in_emaildomain[1..];
let mut moz_ac = moz_autoconfigure_t {
in_0: param_in,
let mut moz_ac = MozAutoconfigure {
in_emailaddr,
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,
};
let mut buf = Vec::new();
loop {
match reader.read_event(&mut buf) {
Ok(quick_xml::events::Event::Start(ref e)) => {
@@ -68,8 +71,7 @@ pub fn moz_autoconfigure(
moz_autoconfigure_text_cb(e, &mut moz_ac, &reader)
}
Err(e) => {
error!(
context,
bail!(
"Configure xml: Error at position {}: {:?}",
reader.buffer_position(),
e
@@ -87,21 +89,36 @@ pub fn moz_autoconfigure(
|| moz_ac.out.send_port == 0
{
let r = moz_ac.out.to_string();
warn!(context, "Bad or incomplete autoconfig: {}", r,);
return None;
bail!("Bad or incomplete autoconfig: {}", r,);
}
Some(moz_ac.out)
Ok(moz_ac.out)
}
pub fn moz_autoconfigure(
context: &Context,
url: &str,
param_in: &LoginParam,
) -> Option<LoginParam> {
let xml_raw = read_autoconf_file(context, url)?;
match moz_parse_xml(&param_in.addr, &xml_raw) {
Err(err) => {
warn!(context, "{}", err);
None
}
Ok(lp) => Some(lp),
}
}
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();
let addr = &moz_ac.in_0.addr;
let addr = moz_ac.in_emailaddr;
let email_local = moz_ac.in_emaillocalpart;
let email_domain = moz_ac.in_emaildomain;
@@ -111,12 +128,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
@@ -129,13 +146,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
@@ -148,29 +164,34 @@ 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;
if moz_ac.tag_server == MozServer::Imap {
moz_ac.out_imap_set = true;
}
moz_ac.tag_server = MozServer::Undefined;
moz_ac.tag_config = MozConfigTag::Undefined;
} else if tag == "outgoingserver" {
moz_ac.tag_server = 0;
moz_ac.tag_config = 0;
moz_ac.out_smtp_set = 1;
if moz_ac.tag_server == MozServer::Smtp {
moz_ac.out_smtp_set = true;
}
moz_ac.tag_server = MozServer::Undefined;
moz_ac.tag_config = MozConfigTag::Undefined;
} else {
moz_ac.tag_config = 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();
@@ -187,25 +208,115 @@ 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;
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_parse_outlook_autoconfig() {
// Copied from https://autoconfig.thunderbird.net/v1.1/outlook.com on 2019-10-11
let xml_raw =
"<clientConfig version=\"1.1\">
<emailProvider id=\"outlook.com\">
<domain>hotmail.com</domain>
<domain>hotmail.co.uk</domain>
<domain>hotmail.co.jp</domain>
<domain>hotmail.com.br</domain>
<domain>hotmail.de</domain>
<domain>hotmail.fr</domain>
<domain>hotmail.it</domain>
<domain>hotmail.es</domain>
<domain>live.com</domain>
<domain>live.co.uk</domain>
<domain>live.co.jp</domain>
<domain>live.de</domain>
<domain>live.fr</domain>
<domain>live.it</domain>
<domain>live.jp</domain>
<domain>msn.com</domain>
<domain>outlook.com</domain>
<displayName>Outlook.com (Microsoft)</displayName>
<displayShortName>Outlook</displayShortName>
<incomingServer type=\"exchange\">
<hostname>outlook.office365.com</hostname>
<port>443</port>
<username>%EMAILADDRESS%</username>
<socketType>SSL</socketType>
<authentication>OAuth2</authentication>
<owaURL>https://outlook.office365.com/owa/</owaURL>
<ewsURL>https://outlook.office365.com/ews/exchange.asmx</ewsURL>
<useGlobalPreferredServer>true</useGlobalPreferredServer>
</incomingServer>
<incomingServer type=\"imap\">
<hostname>outlook.office365.com</hostname>
<port>993</port>
<socketType>SSL</socketType>
<authentication>password-cleartext</authentication>
<username>%EMAILADDRESS%</username>
</incomingServer>
<incomingServer type=\"pop3\">
<hostname>outlook.office365.com</hostname>
<port>995</port>
<socketType>SSL</socketType>
<authentication>password-cleartext</authentication>
<username>%EMAILADDRESS%</username>
<pop3>
<leaveMessagesOnServer>true</leaveMessagesOnServer>
<!-- Outlook.com docs specifically mention that POP3 deletes have effect on the main inbox on webmail and IMAP -->
</pop3>
</incomingServer>
<outgoingServer type=\"smtp\">
<hostname>smtp.office365.com</hostname>
<port>587</port>
<socketType>STARTTLS</socketType>
<authentication>password-cleartext</authentication>
<username>%EMAILADDRESS%</username>
</outgoingServer>
<documentation url=\"http://windows.microsoft.com/en-US/windows/outlook/send-receive-from-app\">
<descr lang=\"en\">Set up an email app with Outlook.com</descr>
</documentation>
</emailProvider>
<webMail>
<loginPage url=\"https://www.outlook.com/\"/>
<loginPageInfo url=\"https://www.outlook.com/\">
<username>%EMAILADDRESS%</username>
<usernameField id=\"i0116\" name=\"login\"/>
<passwordField id=\"i0118\" name=\"passwd\"/>
<loginButton id=\"idSIButton9\" name=\"SI\"/>
</loginPageInfo>
</webMail>
</clientConfig>";
let res = moz_parse_xml("example@outlook.com", xml_raw).expect("XML parsing failed");
assert_eq!(res.mail_server, "outlook.office365.com");
assert_eq!(res.mail_port, 993);
assert_eq!(res.send_server, "smtp.office365.com");
assert_eq!(res.send_port, 587);
}
}

View File

@@ -1,137 +1,249 @@
use std::collections::HashMap;
use quick_xml;
use quick_xml::events::BytesEnd;
use crate::constants::*;
use crate::context::Context;
use crate::error::Error;
use crate::login_param::LoginParam;
use quick_xml::events::{BytesEnd};
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: bool,
pub out_smtp_set: bool,
pub config: HashMap<String, String>,
pub config_type: Option<String>,
pub config_server: String,
pub config_port: i32,
pub config_ssl: String,
pub config_redirecturl: Option<String>,
}
enum ParsingResult {
LoginParam(LoginParam),
RedirectUrl(String),
}
fn outlk_parse_xml(xml_raw: &str) -> Result<ParsingResult, Error> {
let mut outlk_ad = OutlookAutodiscover {
out: LoginParam::new(),
out_imap_set: false,
out_smtp_set: false,
config_type: None,
config_server: String::new(),
config_port: 0,
config_ssl: String::new(),
config_redirecturl: None,
};
let mut reader = quick_xml::Reader::from_str(&xml_raw);
reader.trim_text(true);
let mut buf = Vec::new();
let mut current_tag: Option<String> = None;
loop {
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_lowercase().to_string())
}
"server" => outlk_ad.config_server = val.trim().to_string(),
"port" => outlk_ad.config_port = val.trim().parse().unwrap_or_default(),
"ssl" => outlk_ad.config_ssl = val.trim().to_string(),
"redirecturl" => outlk_ad.config_redirecturl = Some(val.trim().to_string()),
_ => {}
};
}
}
Err(e) => {
bail!(
"Configure xml: Error at position {}: {:?}",
reader.buffer_position(),
e
);
}
Ok(quick_xml::events::Event::Eof) => break,
_ => (),
}
buf.clear();
}
// 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();
bail!("Bad or incomplete autoconfig: {}", r,);
}
Ok(ParsingResult::LoginParam(outlk_ad.out))
} else {
Ok(ParsingResult::RedirectUrl(
outlk_ad.config_redirecturl.unwrap(),
))
}
}
pub fn outlk_autodiscover(
context: &Context,
url__: &str,
param_in: &LoginParam,
url: &str,
_param_in: &LoginParam,
) -> Option<LoginParam> {
let mut url = url__.to_string();
let mut outlk_ad = outlk_autodiscover_t {
in_0: param_in,
out: LoginParam::new(),
out_imap_set: false,
out_smtp_set: false,
config: HashMap::new(),
};
for i in 0..10 {
let xml_raw = read_autoconf_file(context, &url);
if xml_raw.is_err() {
return Some(outlk_ad.out);
}
let xml_raw = xml_raw.unwrap();
let mut reader = quick_xml::Reader::from_str(&xml_raw);
reader.trim_text(true);
let mut buf = Vec::new();
let current_tag: Option<String> = None;
loop {
match reader.read_event(&mut buf) {
Ok(quick_xml::events::Event::Start(ref e)) => {
current_tag = Some(String::from_utf8_lossy(e.name()).trim().to_lowercase());
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 {
if let Some(xml_raw) = read_autoconf_file(context, &url) {
match outlk_parse_xml(&xml_raw) {
Err(err) => {
warn!(context, "{}", err);
return None;
}
Ok(quick_xml::events::Event::End(ref e)) => {
if "protocol" == String::from_utf8_lossy(e.name()).trim().to_lowercase() {
finish_settings(e, &mut outlk_ad);
}
current_tag = None;
}
Ok(quick_xml::events::Event::Text(ref e)) => {
if let Some(current_tag) = current_tag {
let val = e.unescape_and_decode(&reader).unwrap_or_default();
&outlk_ad.config.insert(current_tag, val);
}
}
Err(e) => {
error!(
context,
"Configure xml: Error at position {}: {:?}",
reader.buffer_position(),
e
);
break;
}
Ok(quick_xml::events::Event::Eof) => break,
_ => (),
Ok(res) => match res {
ParsingResult::RedirectUrl(redirect_url) => url = redirect_url,
ParsingResult::LoginParam(login_param) => return Some(login_param),
},
}
buf.clear();
} else {
return None;
}
if let Some(next_url) = outlk_ad.config.get("redirecturl") {
if !next_url.is_empty() {
url = next_url.to_string();
continue;
}
}
break;
}
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;
}
Some(outlk_ad.out)
None
}
fn finish_settings(event: &BytesEnd, outlk_ad: &mut outlk_autodiscover_t) {
let ssl_on = false;
let ssl_off = false;
let config = &outlk_ad.config;
if let Some(type_val) = &config.get("type") {
let port = match config.get("port") {
None => 0,
Some(r) => {
r.parse::<i32>().unwrap_or_default()
}
};
if let Some(ssl) = &config.get("ssl") {
ssl_on = *ssl == "on";
ssl_off = *ssl == "off";
}
let type_val = *type_val;
if !outlk_ad.out_imap_set && type_val == "imap" {
outlk_ad.out.mail_server = config.get("server").unwrap_or("".to_string());
outlk_ad.out.mail_port = port;
if ssl_on {
outlk_ad.out.server_flags |= DC_LP_IMAP_SOCKET_SSL as i32;
} else if ssl_off {
outlk_ad.out.server_flags |= DC_LP_IMAP_SOCKET_PLAIN as i32;
}
outlk_ad.out_imap_set = true;
} else if !outlk_ad.out_smtp_set && type_val == "smtp" {
outlk_ad.out.send_server = &config.get("server").unwrap_or_default();
outlk_ad.out.send_port = port;
fn outlk_autodiscover_endtag_cb(event: &BytesEnd, outlk_ad: &mut OutlookAutodiscover) {
let tag = String::from_utf8_lossy(event.name()).trim().to_lowercase();
if ssl_on {
outlk_ad.out.server_flags |= DC_LP_SMTP_SOCKET_SSL as i32
} else if ssl_off {
outlk_ad.out.server_flags |= DC_LP_SMTP_SOCKET_PLAIN as i32
if tag == "protocol" {
if let Some(type_) = &outlk_ad.config_type {
let port = outlk_ad.config_port;
let ssl_on = outlk_ad.config_ssl == "on";
let ssl_off = outlk_ad.config_ssl == "off";
if type_ == "imap" && !outlk_ad.out_imap_set {
outlk_ad.out.mail_server =
std::mem::replace(&mut outlk_ad.config_server, String::new());
outlk_ad.out.mail_port = port;
if ssl_on {
outlk_ad.out.server_flags |= DC_LP_IMAP_SOCKET_SSL as i32
} else if ssl_off {
outlk_ad.out.server_flags |= DC_LP_IMAP_SOCKET_PLAIN as i32
}
outlk_ad.out_imap_set = true
} else if type_ == "smtp" && !outlk_ad.out_smtp_set {
outlk_ad.out.send_server =
std::mem::replace(&mut outlk_ad.config_server, String::new());
outlk_ad.out.send_port = outlk_ad.config_port;
if ssl_on {
outlk_ad.out.server_flags |= DC_LP_SMTP_SOCKET_SSL as i32
} else if ssl_off {
outlk_ad.out.server_flags |= DC_LP_SMTP_SOCKET_PLAIN as i32
}
outlk_ad.out_smtp_set = true
}
}
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_parse_redirect() {
let res = outlk_parse_xml("
<?xml version=\"1.0\" encoding=\"utf-8\"?>
<Autodiscover xmlns=\"http://schemas.microsoft.com/exchange/autodiscover/responseschema/2006\">
<Response xmlns=\"http://schemas.microsoft.com/exchange/autodiscover/outlook/responseschema/2006a\">
<Account>
<AccountType>email</AccountType>
<Action>redirectUrl</Action>
<RedirectUrl>https://mail.example.com/autodiscover/autodiscover.xml</RedirectUrl>
</Account>
</Response>
</Autodiscover>
").expect("XML is not parsed successfully");
match res {
ParsingResult::LoginParam(_lp) => {
panic!("redirecturl is not found");
}
ParsingResult::RedirectUrl(url) => {
assert_eq!(
url,
"https://mail.example.com/autodiscover/autodiscover.xml"
);
}
}
}
#[test]
fn test_parse_loginparam() {
let res = outlk_parse_xml(
"\
<?xml version=\"1.0\" encoding=\"utf-8\"?>
<Autodiscover xmlns=\"http://schemas.microsoft.com/exchange/autodiscover/responseschema/2006\">
<Response xmlns=\"http://schemas.microsoft.com/exchange/autodiscover/outlook/responseschema/2006a\">
<Account>
<AccountType>email</AccountType>
<Action>settings</Action>
<Protocol>
<Type>IMAP</Type>
<Server>example.com</Server>
<Port>993</Port>
<SSL>on</SSL>
<AuthRequired>on</AuthRequired>
</Protocol>
<Protocol>
<Type>SMTP</Type>
<Server>smtp.example.com</Server>
<Port>25</Port>
<SSL>off</SSL>
<AuthRequired>on</AuthRequired>
</Protocol>
</Account>
</Response>
</Autodiscover>",
)
.expect("XML is not parsed successfully");
match res {
ParsingResult::LoginParam(lp) => {
assert_eq!(lp.mail_server, "example.com");
assert_eq!(lp.mail_port, 993);
assert_eq!(lp.send_server, "smtp.example.com");
assert_eq!(lp.send_port, 25);
}
ParsingResult::RedirectUrl(_) => {
panic!("RedirectUrl is not expected");
}
outlk_ad.out_smtp_set = true;
}
}
}

File diff suppressed because it is too large Load Diff

View File

@@ -53,6 +53,7 @@ pub const DC_HANDSHAKE_ADD_DELETE_JOB: i32 = 0x04;
pub const DC_GCL_ARCHIVED_ONLY: usize = 0x01;
pub const DC_GCL_NO_SPECIALS: usize = 0x02;
pub const DC_GCL_ADD_ALLDONE_HINT: usize = 0x04;
pub const DC_GCL_ADD_DRAFTS: usize = 0x08;
const DC_GCM_ADDDAYMARKER: usize = 0x01;
@@ -129,23 +130,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.
@@ -160,9 +161,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 =
@@ -257,7 +258,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;

View File

@@ -63,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
@@ -145,10 +146,11 @@ 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!(
ctx.sql.open(&ctx, &ctx.dbfile, 0),
ctx.sql.open(&ctx, &ctx.dbfile, false),
"Failed opening sqlite database"
);
@@ -220,6 +222,66 @@ impl Context {
(*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, "");

View File

@@ -606,33 +606,24 @@ 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 {
DC_MIMETYPE_TEXT_PLAIN | DC_MIMETYPE_TEXT_HTML => {
if simplifier.is_none() {
simplifier = Some(Simplify::new());
}
/* get from `Content-Type: text/...; charset=utf-8`; must not be free()'d */
let charset = mailmime_content_charset_get((*mime).mm_content_type);
if !charset.is_null()
&& strcmp(charset, b"utf-8\x00" as *const u8 as *const libc::c_char) != 0i32
&& strcmp(charset, b"UTF-8\x00" as *const u8 as *const libc::c_char) != 0i32
{
if let Some(encoding) = Charset::for_label(
CStr::from_ptr(charset)
.to_str()
.unwrap_or_default()
.as_bytes(),
) {
if let Some(encoding) =
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,
@@ -642,31 +633,30 @@ 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 mut simplifier = Simplify::new();
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.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.is_forwarded {
self.is_forwarded = true;
}
}
DC_MIMETYPE_IMAGE
@@ -741,33 +731,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,
);
}
}
_ => {}

View File

@@ -1,8 +1,7 @@
use std::ffi::CString;
use std::ptr;
use itertools::join;
use libc::{free, strcmp};
use libc::strcmp;
use mmime::clist::*;
use mmime::mailimf::types::*;
use mmime::mailmime::content::*;
@@ -320,13 +319,8 @@ unsafe fn add_parts(
let mut chat_id_blocked = Blocked::Not;
let mut sort_timestamp = 0;
let mut rcvd_timestamp = 0;
let mut mime_in_reply_to = std::ptr::null_mut();
let mut mime_references = std::ptr::null_mut();
let cleanup = |mime_in_reply_to: *mut libc::c_char, mime_references: *mut libc::c_char| {
free(mime_in_reply_to.cast());
free(mime_references.cast());
};
let mut mime_in_reply_to = String::new();
let mut mime_references = String::new();
// collect the rest information, CC: is added to the to-list, BCC: is ignored
// (we should not add BCC to groups as this would split groups. We could add them as "known contacts",
@@ -360,7 +354,6 @@ unsafe fn add_parts(
message::update_server_uid(context, &rfc724_mid, server_folder.as_ref(), server_uid);
}
cleanup(mime_in_reply_to, mime_references);
bail!("Message already in DB");
}
@@ -436,7 +429,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;
@@ -521,7 +514,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;
@@ -589,20 +582,14 @@ unsafe fn add_parts(
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() {
mime_in_reply_to = dc_str_from_clist(
(*(*field).fld_data.fld_in_reply_to).mid_list,
b" \x00" as *const u8 as *const libc::c_char,
)
mime_in_reply_to = dc_str_from_clist((*(*field).fld_data.fld_in_reply_to).mid_list, " ")
}
}
if let Some(field) = mime_parser.lookup_field_typ("References", MAILIMF_FIELD_REFERENCES) {
let fld_references = (*field).fld_data.fld_references;
if !fld_references.is_null() {
mime_references = dc_str_from_clist(
(*(*field).fld_data.fld_references).mid_list,
b" \x00" as *const u8 as *const libc::c_char,
)
mime_references = dc_str_from_clist((*(*field).fld_data.fld_references).mid_list, " ")
}
}
@@ -613,97 +600,91 @@ unsafe fn add_parts(
let icnt = mime_parser.parts.len();
let mut txt_raw = None;
context
.sql
.prepare(
"INSERT INTO msgs \
(rfc724_mid, server_folder, server_uid, chat_id, from_id, to_id, timestamp, \
timestamp_sent, timestamp_rcvd, type, state, msgrmsg, txt, txt_raw, param, \
bytes, hidden, mime_headers, mime_in_reply_to, mime_references) \
VALUES (?,?,?,?,?,?, ?,?,?,?,?,?, ?,?,?,?,?,?, ?,?);",
|mut stmt, conn| {
for i in 0..icnt {
let part = &mut mime_parser.parts[i];
if part.is_meta {
continue;
}
context.sql.prepare(
"INSERT INTO msgs \
(rfc724_mid, server_folder, server_uid, chat_id, from_id, to_id, timestamp, \
timestamp_sent, timestamp_rcvd, type, state, msgrmsg, txt, txt_raw, param, \
bytes, hidden, mime_headers, mime_in_reply_to, mime_references) \
VALUES (?,?,?,?,?,?, ?,?,?,?,?,?, ?,?,?,?,?,?, ?,?);",
|mut stmt, conn| {
for i in 0..icnt {
let part = &mut mime_parser.parts[i];
if part.is_meta {
continue;
}
if let Some(ref msg) = part.msg {
if mime_parser.location_kml.is_some()
&& icnt == 1
&& (msg == "-location-" || msg.is_empty())
{
*hidden = 1;
if state == MessageState::InFresh {
state = MessageState::InNoticed;
}
if let Some(ref msg) = part.msg {
if mime_parser.location_kml.is_some()
&& icnt == 1
&& (msg == "-location-" || msg.is_empty())
{
*hidden = 1;
if state == MessageState::InFresh {
state = MessageState::InNoticed;
}
}
if part.typ == Viewtype::Text {
let msg_raw = part.msg_raw.as_ref().cloned().unwrap_or_default();
let subject = mime_parser
.subject
.as_ref()
.map(|s| s.to_string())
.unwrap_or("".into());
txt_raw = Some(format!("{}\n\n{}", subject, msg_raw));
}
if mime_parser.is_system_message != SystemMessage::Unknown {
part.param
.set_int(Param::Cmd, mime_parser.is_system_message as i32);
}
/*
info!(
context,
"received mime message {:?}",
String::from_utf8_lossy(std::slice::from_raw_parts(
imf_raw_not_terminated as *const u8,
imf_raw_bytes,
))
);
*/
stmt.execute(params![
rfc724_mid,
server_folder.as_ref(),
server_uid as libc::c_int,
*chat_id as libc::c_int,
*from_id as libc::c_int,
*to_id as libc::c_int,
sort_timestamp,
*sent_timestamp,
rcvd_timestamp,
part.typ,
state,
msgrmsg,
part.msg.as_ref().map_or("", String::as_str),
// txt_raw might contain invalid utf8
txt_raw.unwrap_or_default(),
part.param.to_string(),
part.bytes,
*hidden,
if save_mime_headers {
Some(String::from_utf8_lossy(imf_raw))
} else {
None
},
to_string_lossy(mime_in_reply_to),
to_string_lossy(mime_references),
])?;
txt_raw = None;
*insert_msg_id =
sql::get_rowid_with_conn(context, conn, "msgs", "rfc724_mid", &rfc724_mid);
created_db_entries.push((*chat_id as usize, *insert_msg_id as usize));
}
Ok(())
},
)
.map_err(|err| {
cleanup(mime_in_reply_to, mime_references);
err
})?;
if part.typ == Viewtype::Text {
let msg_raw = part.msg_raw.as_ref().cloned().unwrap_or_default();
let subject = mime_parser
.subject
.as_ref()
.map(|s| s.to_string())
.unwrap_or("".into());
txt_raw = Some(format!("{}\n\n{}", subject, msg_raw));
}
if mime_parser.is_system_message != SystemMessage::Unknown {
part.param
.set_int(Param::Cmd, mime_parser.is_system_message as i32);
}
/*
info!(
context,
"received mime message {:?}",
String::from_utf8_lossy(std::slice::from_raw_parts(
imf_raw_not_terminated as *const u8,
imf_raw_bytes,
))
);
*/
stmt.execute(params![
rfc724_mid,
server_folder.as_ref(),
server_uid as libc::c_int,
*chat_id as libc::c_int,
*from_id as libc::c_int,
*to_id as libc::c_int,
sort_timestamp,
*sent_timestamp,
rcvd_timestamp,
part.typ,
state,
msgrmsg,
part.msg.as_ref().map_or("", String::as_str),
// txt_raw might contain invalid utf8
txt_raw.unwrap_or_default(),
part.param.to_string(),
part.bytes,
*hidden,
if save_mime_headers {
Some(String::from_utf8_lossy(imf_raw))
} else {
None
},
mime_in_reply_to,
mime_references,
])?;
txt_raw = None;
*insert_msg_id =
sql::get_rowid_with_conn(context, conn, "msgs", "rfc724_mid", &rfc724_mid);
created_db_entries.push((*chat_id as usize, *insert_msg_id as usize));
}
Ok(())
},
)?;
info!(
context,
@@ -723,8 +704,6 @@ unsafe fn add_parts(
}
}
cleanup(mime_in_reply_to, mime_references);
Ok(())
}
@@ -968,7 +947,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;
@@ -1053,9 +1032,9 @@ 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(());
}
}
}
@@ -1180,7 +1159,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,
@@ -1208,10 +1187,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
@@ -1264,7 +1243,7 @@ unsafe fn create_or_lookup_group(
} else {
chat.param.set(Param::ProfileImage, grpimage);
}
chat.update_param(context).unwrap_or_default();
chat.update_param(context)?;
send_EVENT_CHAT_MODIFIED = 1;
}
}
@@ -1328,11 +1307,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
@@ -1345,7 +1325,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;
@@ -1365,7 +1345,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();
@@ -1378,10 +1358,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(
@@ -1401,13 +1381,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 ...
@@ -1417,7 +1397,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
@@ -1443,6 +1423,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(
@@ -1520,7 +1501,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);
@@ -1575,11 +1559,11 @@ fn search_chat_ids_by_contact_ids(context: &Context, unsorted_contact_ids: &Vec<
}
Ok(())
}
).unwrap_or_default(); // TODO: better error handling
)?;
}
}
chat_ids
Ok(chat_ids)
}
fn check_verified_properties(
@@ -1681,13 +1665,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_or_default();
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 {

View File

@@ -142,8 +142,8 @@ impl Simplify {
ret += "[...]";
}
/* we write empty lines only in case and non-empty line follows */
let mut pending_linebreaks: libc::c_int = 0i32;
let mut content_lines_added: libc::c_int = 0i32;
let mut pending_linebreaks = 0;
let mut content_lines_added = 0;
for l in l_first..l_last {
let line = lines[l];
if is_empty_line(line) {

View File

@@ -51,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> {
@@ -151,36 +74,18 @@ pub(crate) fn dc_truncate(buf: &str, approx_chars: usize, do_unwrap: bool) -> Co
}
}
pub(crate) unsafe fn dc_str_from_clist(
list: *const clist,
delimiter: *const libc::c_char,
) -> *mut libc::c_char {
pub(crate) fn dc_str_from_clist(list: *const clist, delimiter: &str) -> String {
let mut res = String::new();
if !list.is_null() {
let mut cur: *mut clistiter = (*list).first;
while !cur.is_null() {
let rfc724_mid = (if !cur.is_null() {
(*cur).data
} else {
ptr::null_mut()
}) as *const libc::c_char;
if !rfc724_mid.is_null() {
if !res.is_empty() && !delimiter.is_null() {
res += as_str(delimiter);
}
res += as_str(rfc724_mid);
}
cur = if !cur.is_null() {
(*cur).next
} else {
ptr::null_mut()
for rfc724_mid in unsafe { (*list).into_iter() } {
if !res.is_empty() {
res += delimiter;
}
res += as_str(rfc724_mid as *const libc::c_char);
}
}
res.strdup()
res
}
pub(crate) fn dc_str_to_clist(str: &str, delimiter: &str) -> *mut clist {
@@ -601,6 +506,25 @@ pub fn dc_read_file<P: AsRef<std::path::Path>>(
}
}
pub fn dc_open_file<P: AsRef<std::path::Path>>(
context: &Context,
path: P,
) -> Result<std::fs::File, Error> {
let path_abs = dc_get_abs_path(context, &path);
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())
}
}
}
pub(crate) fn dc_get_next_backup_path(
folder: impl AsRef<Path>,
backup_time: i64,
@@ -808,9 +732,15 @@ 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 to_opt_string_lossy(s: *const libc::c_char) -> Option<String> {
if s.is_null() {
return None;
}
Some(to_string_lossy(s))
}
pub fn as_str<'a>(s: *const libc::c_char) -> &'a str {
@@ -1015,54 +945,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));
@@ -1138,21 +1020,6 @@ mod tests {
}
}
fn strndup(s: *const libc::c_char, n: libc::c_ulong) -> *mut libc::c_char {
if s.is_null() {
return std::ptr::null_mut();
}
let end = std::cmp::min(n as usize, unsafe { strlen(s) });
unsafe {
let result = libc::malloc(end + 1);
memcpy(result, s as *const _, end);
std::ptr::write_bytes(result.offset(end as isize), b'\x00', 1);
result as *mut _
}
}
#[test]
fn test_dc_str_to_clist_1() {
unsafe {
@@ -1168,17 +1035,11 @@ mod tests {
unsafe {
let list: *mut clist = dc_str_to_clist("foo bar test", " ");
assert_eq!((*list).count, 3);
let str: *mut libc::c_char =
dc_str_from_clist(list, b" \x00" as *const u8 as *const libc::c_char);
assert_eq!(
CStr::from_ptr(str as *const libc::c_char).to_str().unwrap(),
"foo bar test"
);
let str = dc_str_from_clist(list, " ");
assert_eq!(str, "foo bar test");
clist_free_content(list);
clist_free(list);
free(str as *mut libc::c_void);
}
}
@@ -1339,6 +1200,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);
@@ -1418,19 +1298,6 @@ mod tests {
assert_eq!(foo, format!("$BLOBDIR{}foo", std::path::MAIN_SEPARATOR));
}
#[test]
fn test_strndup() {
unsafe {
let res = strndup(b"helloworld\x00" as *const u8 as *const libc::c_char, 4);
assert_eq!(
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");
@@ -1511,20 +1378,9 @@ mod tests {
assert!(listflags_has(listflags, DC_GCL_ADD_SELF) == true);
let listflags: u32 = DC_GCL_VERIFIED_ONLY.try_into().unwrap();
assert!(listflags_has(listflags, DC_GCL_ADD_SELF) == false);
}
#[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_lossy(input));
free(input.cast());
let input = "\rfoo\r\rbar\r".strdup();
dc_remove_cr_chars(input);
assert_eq!("foobar", to_string_lossy(input));
free(input.cast());
}
let listflags: u32 = DC_GCL_ADD_DRAFTS.try_into().unwrap();
assert!(listflags_has(listflags, DC_GCL_ADD_ALLDONE_HINT) == false);
assert!(listflags_has(listflags, 0x8) == true);
}
}

View File

@@ -28,7 +28,7 @@ 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::*;
@@ -211,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(),
@@ -263,7 +263,7 @@ pub fn try_decrypt(
in_out_message: *mut Mailmime,
) -> Result<(bool, HashSet<String>, HashSet<String>)> {
// just a pointer into mailmime structure, must not be freed
let imffields = unsafe { mailmime_find_mailimf_fields(in_out_message) };
let imffields = mailmime_find_mailimf_fields(in_out_message);
ensure!(
!in_out_message.is_null() && !imffields.is_null(),
"corrupt invalid mime inputs"
@@ -398,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,
@@ -581,7 +581,7 @@ fn decrypt_part(
// we should only have one decryption happening
ensure!(ret_valid_signatures.is_empty(), "corrupt signatures");
let plain = match dc_pgp_pk_decrypt(
let plain = match pgp::pk_decrypt(
&data,
&private_keyring,
&public_keyring_for_validate,

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 {
@@ -237,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

@@ -116,8 +116,8 @@ impl Client {
let s = stream.try_clone().expect("cloning the stream failed");
let tls_stream = native_tls::TlsConnector::connect(&tls, domain.as_ref(), s)?;
let client = imap::Client::new(tls_stream);
// TODO: Read greeting
let mut client = imap::Client::new(tls_stream);
client.read_greeting()?;
Ok(Client::Secure(client, stream))
}
@@ -125,8 +125,8 @@ impl Client {
pub fn connect_insecure<A: net::ToSocketAddrs>(addr: A) -> imap::error::Result<Self> {
let stream = net::TcpStream::connect(addr)?;
let client = imap::Client::new(stream.try_clone().unwrap());
// TODO: Read greeting
let mut client = imap::Client::new(stream.try_clone().unwrap());
client.read_greeting()?;
Ok(Client::Insecure(client, stream))
}
@@ -384,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 {
@@ -598,9 +598,9 @@ impl Imap {
self.config.write().unwrap().watch_folder = Some(watch_folder);
}
pub fn fetch(&self, context: &Context) -> libc::c_int {
pub fn fetch(&self, context: &Context) -> bool {
if !self.is_connected() || !context.sql.is_open() {
return 0;
return false;
}
self.setup_handle_if_needed(context);
@@ -616,9 +616,9 @@ impl Imap {
break;
}
}
1
true
} else {
0
false
}
}

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;
@@ -88,7 +87,7 @@ pub fn has_backup(context: &Context, dir_name: impl AsRef<Path>) -> Result<Strin
let name = name.to_string_lossy();
if name.starts_with("delta-chat") && name.ends_with(".bak") {
let sql = Sql::new();
if sql.open(context, &path, 0x1) {
if sql.open(context, &path, true) {
let curr_backup_time =
sql.get_raw_config_int(context, "backup_time")
.unwrap_or_default() as u64;
@@ -109,78 +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 = context.new_blob_file(
"autocrypt-setup-message.html",
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);
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)
}
@@ -200,7 +169,7 @@ pub fn render_setup_file(context: &Context, passphrase: &str) -> Result<String>
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!(
@@ -270,18 +239,12 @@ pub fn continue_key_transfer(context: &Context, msg_id: u32, setup_code: &str) -
);
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.");
}
@@ -355,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 {
@@ -419,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();
@@ -431,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);
@@ -447,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");
@@ -488,7 +413,7 @@ fn import_backup(context: &Context, backup_to_import: impl AsRef<Path>) -> Resul
/* error already logged */
/* re-open copied database file */
ensure!(
context.sql.open(&context, &context.get_dbfile(), 0),
context.sql.open(&context, &context.get_dbfile(), false),
"could not re-open db"
);
@@ -513,15 +438,7 @@ fn import_backup(context: &Context, backup_to_import: impl AsRef<Path>) -> Resul
|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");
}
ensure!(!context.shall_stop_ongoing(), "received stop signal");
let mut permille = processed_files_cnt * 1000 / total_files_cnt;
if permille < 10 {
permille = 10
@@ -562,10 +479,6 @@ 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.
// let dest_path_filename = dc_get_next_backup_file(context, dir, res);
@@ -583,7 +496,7 @@ fn export_backup(context: &Context, dir: impl AsRef<Path>) -> Result<()> {
dest_path_filename.display(),
);
let copied = dc_copy_file(context, context.get_dbfile(), &dest_path_filename);
context.sql.open(&context, &context.get_dbfile(), 0);
context.sql.open(&context, &context.get_dbfile(), false);
if !copied {
let s = dest_path_filename.to_string_lossy().to_string();
bail!(
@@ -592,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, false),
"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_or_default();
} 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_raw_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(())
}
@@ -730,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_or_default()
} 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
);
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<()> {
@@ -847,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(())
}
@@ -884,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() {
@@ -906,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);
@@ -974,50 +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());
unsafe {
let buf_1 = S_EM_SETUPFILE.strdup();
let res = dc_split_armored_data(
buf_1,
&mut headerline,
&mut setupcodebegin,
&mut preferencrypt,
ptr::null_mut(),
);
libc::free(buf_1 as *mut libc::c_void);
assert!(res);
}
assert_eq!(headerline, "-----BEGIN PGP MESSAGE-----");
assert!(!setupcodebegin.is_null());
assert!(!base64.is_empty());
// TODO: verify that this is the right check
assert!(S_EM_SETUPCODE.starts_with(as_str(setupcodebegin)));
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();
assert!(preferencrypt.is_null());
let (typ, headers, _base64) = split_armored_data(decrypted.as_bytes()).unwrap();
let mut setup_file = S_EM_SETUPFILE.to_string();
let decrypted = unsafe {
decrypt_setup_file(context, S_EM_SETUPCODE, setup_file.as_bytes_mut()).unwrap()
};
unsafe {
let buf1 = decrypted.strdup();
assert!(dc_split_armored_data(
buf1,
&mut headerline,
&mut setupcodebegin,
&mut preferencrypt,
ptr::null_mut(),
));
libc::free(buf1 as *mut libc::c_void);
}
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

@@ -12,7 +12,7 @@ use crate::dc_tools::*;
use crate::error::Error;
use crate::events::Event;
use crate::imap::*;
use crate::imex::job_do_DC_JOB_IMEX_IMAP;
use crate::imex::*;
use crate::location;
use crate::login_param::LoginParam;
use crate::message::{self, Message, MessageState};
@@ -133,7 +133,7 @@ impl Job {
let connected = context.smtp.lock().unwrap().connect(context, &loginparam);
if !connected {
self.try_again_later(3i32, None);
self.try_again_later(3, None);
return;
}
}
@@ -172,7 +172,7 @@ impl Job {
Err(err) => {
sock.disconnect();
warn!(context, "smtp failed: {}", err);
self.try_again_later(-1i32, Some(err.to_string()));
self.try_again_later(-1, Some(err.to_string()));
}
Ok(()) => {
// smtp success, update db ASAP, then delete smtp file
@@ -207,7 +207,7 @@ impl Job {
}
// this value does not increase the number of tries
fn try_again_later(&mut self, try_again: libc::c_int, pending_error: Option<String>) {
fn try_again_later(&mut self, try_again: i32, pending_error: Option<String>) {
self.try_again = try_again;
self.pending_error = pending_error;
}
@@ -781,8 +781,13 @@ 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::ImexImap => job_do_DC_JOB_IMEX_IMAP(context, &job),
Action::ConfigureImap => dc_job_do_DC_JOB_CONFIGURE_IMAP(context),
Action::ImexImap => match job_do_DC_JOB_IMEX_IMAP(context, &job) {
Ok(()) => {}
Err(err) => {
error!(context, "{}", err);
}
},
Action::MaybeSendLocations => {
location::job_do_DC_JOB_MAYBE_SEND_LOCATIONS(context, &job)
}

View File

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

@@ -197,7 +197,7 @@ pub fn send_locations_to_chat(context: &Context, chat_id: u32, seconds: i64) {
let now = time();
let mut msg: Message;
let is_sending_locations_before: bool;
if !(seconds < 0 || chat_id <= 9i32 as libc::c_uint) {
if !(seconds < 0 || chat_id <= DC_CHAT_ID_LAST_SPECIAL) {
is_sending_locations_before = is_sending_locations_to_chat(context, chat_id);
if sql::execute(
context,
@@ -257,9 +257,9 @@ pub fn is_sending_locations_to_chat(context: &Context, chat_id: u32) -> bool {
.unwrap_or_default()
}
pub fn set(context: &Context, latitude: f64, longitude: f64, accuracy: f64) -> libc::c_int {
pub fn set(context: &Context, latitude: f64, longitude: f64, accuracy: f64) -> bool {
if latitude == 0.0 && longitude == 0.0 {
return 1;
return true;
}
let mut continue_streaming = false;
@@ -293,7 +293,7 @@ pub fn set(context: &Context, latitude: f64, longitude: f64, accuracy: f64) -> l
schedule_MAYBE_SEND_LOCATIONS(context, 0);
}
continue_streaming as libc::c_int
continue_streaming
}
pub fn get_range(

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_lossy(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();
}
}
}

View File

@@ -671,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_or_default();
}
Ok(())
},
)?;
let command = factory.msg.param.get_cmd();
let msg = &factory.msg;
@@ -898,9 +895,7 @@ 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_or_default();
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))
}

View File

@@ -7,6 +7,7 @@ use crate::context::Context;
use crate::dc_tools::*;
const OAUTH2_GMAIL: Oauth2 = Oauth2 {
// see https://developers.google.com/identity/protocols/OAuth2InstalledApp
client_id: "959970109878-4mvtgf6feshskf7695nfln6002mom908.apps.googleusercontent.com",
get_code: "https://accounts.google.com/o/oauth2/auth?client_id=$CLIENT_ID&redirect_uri=$REDIRECT_URI&response_type=code&scope=https%3A%2F%2Fmail.google.com%2F%20email&access_type=offline",
init_token: "https://accounts.google.com/o/oauth2/token?client_id=$CLIENT_ID&redirect_uri=$REDIRECT_URI&code=$CODE&grant_type=authorization_code",
@@ -15,6 +16,7 @@ const OAUTH2_GMAIL: Oauth2 = Oauth2 {
};
const OAUTH2_YANDEX: Oauth2 = Oauth2 {
// see https://tech.yandex.com/oauth/doc/dg/reference/auto-code-client-docpage/
client_id: "c4d0b6735fc8420a816d7e1303469341",
get_code: "https://oauth.yandex.com/authorize?client_id=$CLIENT_ID&response_type=code&scope=mail%3Aimap_full%20mail%3Asmtp&force_confirm=true",
init_token: "https://oauth.yandex.com/token?grant_type=authorization_code&code=$CODE&client_id=$CLIENT_ID&client_secret=58b8c6e94cf44fbe952da8511955dacf",
@@ -89,6 +91,7 @@ pub fn dc_get_oauth2_access_token(
}
}
// generate new token: build & call auth url
let refresh_token = context.sql.get_raw_config(context, "oauth2_refresh_token");
let refresh_token_for = context
.sql
@@ -120,14 +123,37 @@ pub fn dc_get_oauth2_access_token(
false,
)
};
let mut token_url = replace_in_uri(&token_url, "$CLIENT_ID", oauth2.client_id);
token_url = replace_in_uri(&token_url, "$REDIRECT_URI", &redirect_uri);
token_url = replace_in_uri(&token_url, "$CODE", code.as_ref());
if let Some(ref token) = refresh_token {
token_url = replace_in_uri(&token_url, "$REFRESH_TOKEN", token);
// to allow easier specification of different configurations,
// token_url is in GET-method-format, sth. as https://domain?param1=val1&param2=val2 -
// convert this to POST-format ...
let mut parts = token_url.splitn(2, '?');
let post_url = parts.next().unwrap_or_default();
let post_args = parts.next().unwrap_or_default();
let mut post_param = HashMap::new();
for key_value_pair in post_args.split('&') {
let mut parts = key_value_pair.splitn(2, '=');
let key = parts.next().unwrap_or_default();
let mut value = parts.next().unwrap_or_default();
if value == "$CLIENT_ID" {
value = oauth2.client_id;
} else if value == "$REDIRECT_URI" {
value = &redirect_uri;
} else if value == "$CODE" {
value = code.as_ref();
} else if value == "$REFRESH_TOKEN" && refresh_token.is_some() {
value = refresh_token.as_ref().unwrap();
}
post_param.insert(key, value);
}
let response = reqwest::Client::new().post(&token_url).send();
// ... and POST
let response = reqwest::Client::new()
.post(post_url)
.form(&post_param)
.send();
if response.is_err() {
warn!(
context,
@@ -139,13 +165,14 @@ pub fn dc_get_oauth2_access_token(
if !response.status().is_success() {
warn!(
context,
"Error calling OAuth2 at {}: {:?}",
"Unsuccessful response when calling OAuth2 at {}: {:?}",
token_url,
response.status()
);
return None;
}
// generate new token: parse returned json
let parsed: reqwest::Result<Response> = response.json();
if parsed.is_err() {
warn!(
@@ -155,6 +182,8 @@ pub fn dc_get_oauth2_access_token(
return None;
}
println!("response: {:?}", &parsed);
// update refresh_token if given, typically on the first round, but we update it later as well.
let response = parsed.unwrap();
if let Some(ref token) = response.refresh_token {
context
@@ -268,7 +297,7 @@ impl Oauth2 {
return None;
}
let parsed: reqwest::Result<HashMap<String, String>> = response.json();
let parsed: reqwest::Result<HashMap<String, serde_json::Value>> = response.json();
if parsed.is_err() {
warn!(
context,
@@ -277,11 +306,13 @@ impl Oauth2 {
return None;
}
if let Ok(response) = parsed {
// serde_json::Value.as_str() removes the quotes of json-strings
let addr = response.get("email");
if addr.is_none() {
warn!(context, "E-mail missing in userinfo.");
return None;
}
let addr = addr.unwrap().as_str();
addr.map(|addr| addr.to_string())
} else {
warn!(context, "Failed to parse userinfo.");

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 i32::from(*p1) == '\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.add(line_chars) = '\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>,
@@ -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;
@@ -157,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
};
@@ -170,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);
@@ -184,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;
@@ -240,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,
@@ -636,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) {

View File

@@ -71,7 +71,7 @@ impl Smtp {
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) {
let (creds, mechanism) = if 0 != lp.server_flags & (DC_LP_AUTH_OAUTH2 as i32) {
// oauth2
let addr = &lp.addr;
let send_pw = &lp.send_pw;
@@ -81,15 +81,24 @@ impl Smtp {
}
let user = &lp.send_user;
lettre::smtp::authentication::Credentials::new(
user.to_string(),
access_token.unwrap_or_default(),
(
lettre::smtp::authentication::Credentials::new(
user.to_string(),
access_token.unwrap_or_default(),
),
vec![lettre::smtp::authentication::Mechanism::Xoauth2],
)
} else {
// plain
let user = lp.send_user.clone();
let pw = lp.send_pw.clone();
lettre::smtp::authentication::Credentials::new(user, pw)
(
lettre::smtp::authentication::Credentials::new(user, pw),
vec![
lettre::smtp::authentication::Mechanism::Plain,
lettre::smtp::authentication::Mechanism::Login,
],
)
};
let security = if 0
@@ -105,6 +114,7 @@ impl Smtp {
let client = client
.smtp_utf8(true)
.credentials(creds)
.authentication_mechanism(mechanism)
.connection_reuse(lettre::smtp::ConnectionReuseParameters::ReuseUnlimited);
self.transport = Some(client.transport());
context.call_cb(Event::SmtpConnected(format!(

View File

@@ -11,8 +11,6 @@ use crate::error::{Error, Result};
use crate::param::*;
use crate::peerstate::*;
const DC_OPEN_READONLY: usize = 0x01;
/// A wrapper around the underlying Sqlite3 object.
#[derive(DebugStub)]
pub struct Sql {
@@ -42,8 +40,8 @@ impl Sql {
}
// return true on success, false on failure
pub fn open(&self, context: &Context, dbfile: &std::path::Path, flags: libc::c_int) -> bool {
match open(context, self, dbfile, flags) {
pub fn open(&self, context: &Context, dbfile: &std::path::Path, readonly: bool) -> bool {
match open(context, self, dbfile, readonly) {
Ok(_) => true,
Err(Error::SqlAlreadyOpen) => false,
Err(_) => {
@@ -320,7 +318,7 @@ fn open(
context: &Context,
sql: &Sql,
dbfile: impl AsRef<std::path::Path>,
flags: libc::c_int,
readonly: bool,
) -> Result<()> {
if sql.is_open() {
error!(
@@ -332,7 +330,7 @@ fn open(
}
let mut open_flags = OpenFlags::SQLITE_OPEN_NO_MUTEX;
if 0 != (flags & DC_OPEN_READONLY as i32) {
if readonly {
open_flags.insert(OpenFlags::SQLITE_OPEN_READ_ONLY);
} else {
open_flags.insert(OpenFlags::SQLITE_OPEN_READ_WRITE);
@@ -351,7 +349,7 @@ fn open(
*sql.pool.write().unwrap() = Some(pool);
}
if 0 == flags & DC_OPEN_READONLY as i32 {
if !readonly {
let mut exists_before_update = 0;
let mut dbversion_before_update = 0;
/* Init tables to dbversion=0 */

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
///
@@ -123,19 +122,47 @@ impl StockMessage {
}
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_lossy(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()),
}
}
@@ -239,7 +266,6 @@ mod tests {
use crate::test_utils::*;
use crate::constants::DC_CONTACT_ID_SELF;
use libc::uintptr_t;
use num_traits::ToPrimitive;
@@ -255,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]

View File

@@ -14,24 +14,27 @@ 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))
libc = s.count("libc")
filestats.append((fn, unsafe, free, unsafe_fn, chars, libc))
sum_unsafe, sum_free, sum_gotoblocks, sum_chars = 0, 0, 0, 0
sum_unsafe, sum_free, sum_unsafe_fn, sum_chars, sum_libc = 0, 0, 0, 0, 0
for fn, unsafe, free, gotoblocks, chars in reversed(sorted(filestats, key=lambda x: sum(x[1:]))):
if unsafe + free + gotoblocks + chars == 0:
for fn, unsafe, free, unsafe_fn, chars, libc in reversed(sorted(filestats, key=lambda x: sum(x[1:]))):
if unsafe + free + unsafe_fn + chars + libc == 0:
continue
print("{0: <25} unsafe: {1: >3} free: {2: >3} ok_to_cont: {3: >3} chars: {4: >3}".format(str(fn), unsafe, free, gotoblocks, chars))
print("{0: <25} unsafe: {1: >3} free: {2: >3} unsafe-fn: {3: >3} chars: {4: >3} libc: {5: >3}".format(str(fn), unsafe, free, unsafe_fn, chars, libc))
sum_unsafe += unsafe
sum_free += free
sum_gotoblocks += gotoblocks
sum_unsafe_fn += unsafe_fn
sum_chars += chars
sum_libc += libc
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)
print("total libc:", sum_libc)

View File

@@ -175,21 +175,21 @@ pub fn mailimf_find_field(
}
/*the result is a pointer to mime, must not be freed*/
pub unsafe fn mailmime_find_mailimf_fields(mime: *mut Mailmime) -> *mut mailimf_fields {
pub fn mailmime_find_mailimf_fields(mime: *mut Mailmime) -> *mut mailimf_fields {
if mime.is_null() {
return ptr::null_mut();
}
match (*mime).mm_type as _ {
match unsafe { (*mime).mm_type as _ } {
MAILMIME_MULTIPLE => {
for cur_data in (*(*mime).mm_data.mm_multipart.mm_mp_list).into_iter() {
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 (*mime).mm_data.mm_message.mm_fields,
MAILMIME_MESSAGE => return unsafe { (*mime).mm_data.mm_message.mm_fields },
_ => {}
}

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);
}