Compare commits

..

3 Commits

Author SHA1 Message Date
holger krekel
357bce7246 intermediate commit 2019-10-06 01:28:17 +02:00
holger krekel
6bac21b13f some more cleanups but not quite working yet 2019-10-06 01:03:13 +02:00
holger krekel
859e7691d0 first round of configure cleanups 2019-10-06 01:00:11 +02:00
56 changed files with 2552 additions and 2434 deletions

View File

@@ -4,9 +4,6 @@ executors:
docker:
- image: filecoin/rust:latest
working_directory: /mnt/crate
doxygen:
docker:
- image: hrektts/doxygen
restore-workspace: &restore-workspace
attach_workspace:
@@ -116,18 +113,6 @@ 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
@@ -163,7 +148,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 workspace/c-docs
- run: ci_scripts/ci_upload.sh workspace/py-docs workspace/wheelhouse
clippy:
executor: default
@@ -181,21 +166,18 @@ 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:

View File

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

605
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-beta.1"
authors = ["Delta Chat Developers (ML) <delta@codespeak.net>"]
version = "1.0.0-alpha.5"
authors = ["dignifiedquire <dignifiedquire@gmail.com>"]
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.3", default-features = false }
pgp = { version = "0.2", 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 = { git = "https://github.com/deltachat/lettre", branch = "master" }
imap = { git = "https://github.com/jonhoo/rust-imap", branch = "master" }
lettre = "0.9.0"
imap = { git = "https://github.com/jonhoo/rust-imap", rev = "281d2eb8ab50dc656ceff2ae749ca5045f334e15" }
base64 = "0.10"
charset = "0.1"
percent-encoding = "2.0"

6
Xargo.toml Normal file
View File

@@ -0,0 +1,6 @@
[dependencies.std]
features = ["panic-unwind"]
# if using `cargo test`
[dependencies.test]
stage = 1

View File

@@ -7,9 +7,9 @@ fi
set -xe
#DOXYDOCDIR=${1:?directory where doxygen docs to be found}
PYDOCDIR=${1:?directory with python docs}
WHEELHOUSEDIR=${2:?directory with pre-built wheels}
DOXYDOCDIR=${3:?directory where doxygen docs to be found}
export BRANCH=${CIRCLE_BRANCH:?specify branch for uploading purposes}
@@ -22,11 +22,10 @@ rsync -avz \
delta@py.delta.chat:build/${BRANCH}
# C docs to c.delta.chat
ssh -o StrictHostKeyChecking=no -o UserKnownHostsFile=/dev/null delta@c.delta.chat mkdir -p build-c/${BRANCH}
rsync -avz \
-e "ssh -o StrictHostKeyChecking=no -o UserKnownHostsFile=/dev/null" \
"$DOXYDOCDIR/html/" \
delta@c.delta.chat:build-c/${BRANCH}
#rsync -avz \
# -e "ssh -o StrictHostKeyChecking=no -o UserKnownHostsFile=/dev/null" \
# "$DOXYDOCDIR/html/" \
# delta@py.delta.chat:build-c/${BRANCH}
echo -----------------------
echo upload wheels

View File

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

View File

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

View File

@@ -78,7 +78,8 @@ typedef struct _dc_provider dc_provider_t;
*
* The example above uses "pthreads",
* however, you can also use anything else for thread handling.
* All deltachat-core-functions, unless stated otherwise, are thread-safe.
* NB: The deltachat-core library itself does not create any threads on its own,
* however, 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:
@@ -134,7 +135,7 @@ typedef struct _dc_provider dc_provider_t;
*
* printf("Message %i: %s\n", i+1, text);
*
* dc_str_unref(text);
* free(text);
* dc_msg_unref(msg);
* }
* dc_array_unref(msglist);
@@ -320,7 +321,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 released using dc_str_unref().
* The returned string must be free()'d.
*/
char* dc_get_blobdir (const dc_context_t* context);
@@ -396,24 +397,11 @@ 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 released using dc_str_unref(), NULL is never
* value is returned. The returned value must be free()'d, NULL is never
* returned. If there is an error an empty string will be returned.
*/
char* dc_get_config (dc_context_t* context, const char* key);
/**
* 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.
@@ -427,7 +415,7 @@ int dc_set_stock_translation(dc_context_t* context, uint32_t stock_i
*
* @memberof dc_context_t
* @param context The context as created by dc_context_new().
* @return String which must be released using dc_str_unref() after usage. Never returns NULL.
* @return String which must be free()'d after usage. Never returns NULL.
*/
char* dc_get_info (dc_context_t* context);
@@ -458,7 +446,6 @@ 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);
@@ -707,7 +694,6 @@ 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()
@@ -1479,7 +1465,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 released using dc_str_unref() after usage
* @return Text string, must be free()'d after usage
*/
char* dc_get_msg_info (dc_context_t* context, uint32_t msg_id);
@@ -1493,7 +1479,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 released using dc_str_unref() after usage.
* @return Raw headers as a multi-line string, must be free()'d 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.
@@ -1734,7 +1720,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 released using dc_str_unref() after usage.
* @return Multi-line text, must be free()'d after usage.
*/
char* dc_get_contact_encrinfo (dc_context_t* context, uint32_t contact_id);
@@ -1870,8 +1856,7 @@ 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 released using dc_str_unref().
* @return String with the backup file, typically given to dc_imex(), returned strings must be free()'d.
* The function returns NULL if no backup was found.
*/
char* dc_imex_has_backup (dc_context_t* context, const char* dir);
@@ -1919,7 +1904,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 released using dc_str_unref() after usage.
* @return The setup code. Must be free()'d after usage.
* On errors, eg. if the message could not be sent, NULL is returned.
*/
char* dc_initiate_key_transfer (dc_context_t* context);
@@ -2024,7 +2009,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 released using dc_str_unref() after usage.
* The returned string must be free()'d after usage.
*/
char* dc_get_securejoin_qr (dc_context_t* context, uint32_t chat_id);
@@ -2194,21 +2179,6 @@ 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
*
@@ -2349,7 +2319,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 released using dc_str_unref() after usage.
* The returned value must be free()'d after usage.
*/
char* dc_array_get_marker (const dc_array_t* array, size_t index);
@@ -2604,7 +2574,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 released using dc_str_unref() after usage. Never NULL.
* @return Chat name as a string. Must be free()'d after usage. Never NULL.
*/
char* dc_chat_get_name (const dc_chat_t* chat);
@@ -2617,7 +2587,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 released using dc_str_unref() after usage. Never NULL.
* @return Subtitle as a string. Must be free()'d after usage. Never NULL.
*/
char* dc_chat_get_subtitle (const dc_chat_t* chat);
@@ -2633,7 +2603,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 released using dc_str_unref() after usage.
* Must be free()'d after usage.
*/
char* dc_chat_get_profile_image (const dc_chat_t* chat);
@@ -2937,7 +2907,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 released using dc_str_unref(). Never returns NULL.
* @return Message text. The result must be free()'d. Never returns NULL.
*/
char* dc_msg_get_text (const dc_msg_t* msg);
@@ -2951,9 +2921,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 released using dc_str_unref().
* @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.
*/
char* dc_msg_get_file (const dc_msg_t* msg);
@@ -2966,7 +2936,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 released using dc_str_unref().
* value must be free()'d.
*/
char* dc_msg_get_filename (const dc_msg_t* msg);
@@ -2978,8 +2948,7 @@ 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 released using dc_str_unref() after usage. NULL is never returned.
* @return String containing the mime type. Must be free()'d after usage. NULL is never returned.
*/
char* dc_msg_get_filemime (const dc_msg_t* msg);
@@ -3087,8 +3056,7 @@ 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 released using dc_str_unref().
* @return A summary for the given messages. The returned string must be free()'d.
* Returns an empty string on errors, never returns NULL.
*/
char* dc_msg_get_summarytext (const dc_msg_t* msg, int approx_characters);
@@ -3233,7 +3201,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 released using dc_str_unref() when done.
* NULL is never returned. Must be free()'d when done.
*/
char* dc_msg_get_setupcodebegin (const dc_msg_t* msg);
@@ -3390,8 +3358,7 @@ 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 released using dc_str_unref(). Never returns NULL.
* @return String with the email address, must be free()'d. Never returns NULL.
*/
char* dc_contact_get_addr (const dc_contact_t* contact);
@@ -3405,8 +3372,7 @@ 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 released using dc_str_unref().
* Empty string if unset, never returns NULL.
* @return String with the name to display, must be free()'d. Empty string if unset, never returns NULL.
*/
char* dc_contact_get_name (const dc_contact_t* contact);
@@ -3420,8 +3386,7 @@ 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 released using dc_str_unref().
* Never returns NULL.
* @return String with the name to display, must be free()'d. Never returns NULL.
*/
char* dc_contact_get_display_name (const dc_contact_t* contact);
@@ -3437,8 +3402,7 @@ 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 released using dc_str_unref().
* Never returns NULL.
* @return Summary string, must be free()'d. Never returns NULL.
*/
char* dc_contact_get_name_n_addr (const dc_contact_t* contact);
@@ -3450,8 +3414,7 @@ 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 released using dc_str_unref().
* Never returns NULL.
* @return String with the name to display, must be free()'d. Never returns NULL.
*/
char* dc_contact_get_first_name (const dc_contact_t* contact);
@@ -3465,7 +3428,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 released using dc_str_unref() after usage.
* Must be free()'d after usage.
*/
char* dc_contact_get_profile_image (const dc_contact_t* contact);
@@ -3546,7 +3509,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 released using dc_str_unref().
* @return A string which must be free()d.
*/
char* dc_provider_get_overview_page (const dc_provider_t* provider);
@@ -3558,7 +3521,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 released using dc_str_unref().
* @return A string which must be free()d.
*/
char* dc_provider_get_name (const dc_provider_t* provider);
@@ -3571,7 +3534,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 released using dc_str_unref().
* @return A string which must be free()d.
*/
char* dc_provider_get_markdown (const dc_provider_t* provider);
@@ -3583,7 +3546,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 released using dc_str_unref().
* @return A string which must be free()d.
*/
char* dc_provider_get_status_date (const dc_provider_t* provider);
@@ -3645,9 +3608,7 @@ 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 released using dc_str_unref().
* NULL if there is no such string.
* @return A string, the string may be empty and the returned value must be free()'d. NULL if there is no such string.
*/
char* dc_lot_get_text1 (const dc_lot_t* lot);
@@ -3656,10 +3617,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 released using dc_str_unref().
* NULL if there is no such string.
*
* @return A string, the string may be empty and the returned value must be free()'d . NULL if there is no such string.
*/
char* dc_lot_get_text2 (const dc_lot_t* lot);
@@ -3680,7 +3641,9 @@ 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);
@@ -3702,7 +3665,9 @@ 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);
@@ -3902,7 +3867,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 unref'd or modified and is valid only until the callback returns.
* Must not be free()'d or modified and is valid only until the callback returns.
* @return 0
*/
#define DC_EVENT_INFO 100
@@ -3913,7 +3878,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 unref'd or modified and is valid only until the callback returns.
* Must not be free()'d or modified and is valid only until the callback returns.
* @return 0
*/
#define DC_EVENT_SMTP_CONNECTED 101
@@ -3924,7 +3889,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 unref'd or modified and is valid only until the callback returns.
* Must not be free()'d or modified and is valid only until the callback returns.
* @return 0
*/
#define DC_EVENT_IMAP_CONNECTED 102
@@ -3934,7 +3899,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 unref'd or modified and is valid only until the callback returns.
* Must not be free()'d or modified and is valid only until the callback returns.
* @return 0
*/
#define DC_EVENT_SMTP_MESSAGE_SENT 103
@@ -3944,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 unref'd or modified and is valid only until the callback returns.
* Must not be free()'d or modified and is valid only until the callback returns.
* @return 0
*/
#define DC_EVENT_IMAP_MESSAGE_DELETED 104
@@ -3954,7 +3919,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 unref'd or modified and is valid only until the callback returns.
* Must not be free()'d or modified and is valid only until the callback returns.
* @return 0
*/
#define DC_EVENT_IMAP_MESSAGE_MOVED 105
@@ -3964,7 +3929,7 @@ int64_t dc_lot_get_timestamp (const dc_lot_t* lot);
*
* @param data1 0
* @param data2 (const char*) path name
* Must not be unref'd or modified and is valid only until the callback returns.
* Must not be free()'d or modified and is valid only until the callback returns.
* @return 0
*/
#define DC_EVENT_NEW_BLOB_FILE 150
@@ -3974,7 +3939,7 @@ int64_t dc_lot_get_timestamp (const dc_lot_t* lot);
*
* @param data1 0
* @param data2 (const char*) path name
* Must not be unref'd or modified and is valid only until the callback returns.
* Must not be free()'d or modified and is valid only until the callback returns.
* @return 0
*/
#define DC_EVENT_DELETED_BLOB_FILE 151
@@ -3987,7 +3952,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 unref'd or modified and is valid only until the callback returns.
* Must not be free()'d or modified and is valid only until the callback returns.
* @return 0
*/
#define DC_EVENT_WARNING 300
@@ -4007,10 +3972,9 @@ 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.
* 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.
* @param data2 (const char*) Error string, always set, never NULL. Frequent error strings are
* localized using #DC_EVENT_GET_STRING, however, most error strings will be in english language.
* Must not be free()'d or modified and is valid only until the callback returns.
* @return 0
*/
#define DC_EVENT_ERROR 400
@@ -4034,7 +3998,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 unref'd or modified and is valid only until the callback returns.
* Must not be free()'d or modified and is valid only until the callback returns.
* @return 0
*/
#define DC_EVENT_ERROR_NETWORK 401
@@ -4049,7 +4013,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 unref'd or modified
* Must not be free()'d or modified
* and is valid only until the callback returns.
* @return 0
*/
@@ -4180,7 +4144,7 @@ int64_t dc_lot_get_timestamp (const dc_lot_t* lot);
* services.
*
* @param data1 (const char*) Path and file name.
* Must not be unref'd or modified and is valid only until the callback returns.
* Must not be free()'d or modified and is valid only until the callback returns.
* @param data2 0
* @return 0
*/
@@ -4221,21 +4185,36 @@ 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 // 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_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_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) // not used anymore
#define DC_EVENT_RETURNS_STRING(e) ((e)==DC_EVENT_GET_STRING) // not used anymore
#define DC_EVENT_RETURNS_INT(e) ((e)==DC_EVENT_IS_OFFLINE)
#define DC_EVENT_RETURNS_STRING(e) ((e)==DC_EVENT_GET_STRING)
char* dc_get_version_str (void); // deprecated
void dc_array_add_id (dc_array_t*, uint32_t); // deprecated
@@ -4337,8 +4316,10 @@ 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_STICKER 67
#define DC_STR_COUNT 67
#define DC_STR_COUNT 66
void dc_str_unref (char*);
/*
* @}

View File

@@ -24,10 +24,7 @@ use num_traits::{FromPrimitive, ToPrimitive};
use deltachat::contact::Contact;
use deltachat::context::Context;
use deltachat::dc_tools::{
as_path, dc_strdup, to_opt_string_lossy, to_string_lossy, OsStrExt, StrExt,
};
use deltachat::stock::StockMessage;
use deltachat::dc_tools::{as_path, as_str, dc_strdup, to_string_lossy, OsStrExt, StrExt};
use deltachat::*;
// as C lacks a good and portable error handling,
@@ -169,6 +166,12 @@ 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,
@@ -305,14 +308,11 @@ pub unsafe extern "C" fn dc_set_config(
return 0;
}
let ffi_context = &*context;
match config::Config::from_str(&to_string_lossy(key)) {
match config::Config::from_str(as_str(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, to_opt_string_lossy(value).as_ref().map(|x| x.as_str()))
.is_ok() as libc::c_int
})
.with_inner(|ctx| ctx.set_config(key, as_opt_str(value)).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(&to_string_lossy(key)) {
match config::Config::from_str(as_str(key)) {
Ok(key) => ffi_context
.with_inner(|ctx| ctx.get_config(key).unwrap_or_default().strdup())
.unwrap_or_else(|_| "".strdup()),
@@ -342,35 +342,6 @@ 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() {
@@ -651,24 +622,22 @@ pub unsafe extern "C" fn dc_get_chatlist(
return ptr::null_mut();
}
let ffi_context = &*context;
let qs = to_opt_string_lossy(query_str);
let qs = if query_str.is_null() {
None
} else {
Some(as_str(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.as_ref().map(|x| x.as_str()),
qi,
) {
.with_inner(
|ctx| match chatlist::Chatlist::try_load(ctx, flags as usize, qs, 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())
}
@@ -1059,7 +1028,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, to_string_lossy(query)));
let arr = dc_array_t::from(ctx.search_msgs(chat_id, as_str(query)));
Box::into_raw(Box::new(arr))
})
.unwrap_or_else(|_| ptr::null_mut())
@@ -1101,7 +1070,7 @@ pub unsafe extern "C" fn dc_create_group_chat(
};
ffi_context
.with_inner(|ctx| {
chat::create_group_chat(ctx, verified, to_string_lossy(name))
chat::create_group_chat(ctx, verified, as_str(name))
.unwrap_or_log_default(ctx, "Failed to create group chat")
})
.unwrap_or(0)
@@ -1173,7 +1142,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, to_string_lossy(name))
chat::set_chat_name(ctx, chat_id, as_str(name))
.map(|_| 1)
.unwrap_or_log_default(ctx, "Failed to set chat name")
})
@@ -1193,9 +1162,15 @@ 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, to_string_lossy(image))
.map(|_| 1)
.unwrap_or_log_default(ctx, "Failed to set profile image")
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")
})
.unwrap_or(0)
}
@@ -1358,7 +1333,7 @@ pub unsafe extern "C" fn dc_may_be_valid_addr(addr: *const libc::c_char) -> libc
return 0;
}
contact::may_be_valid_addr(&to_string_lossy(addr)) as libc::c_int
contact::may_be_valid_addr(as_str(addr)) as libc::c_int
}
#[no_mangle]
@@ -1372,7 +1347,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, to_string_lossy(addr)))
.with_inner(|ctx| Contact::lookup_id_by_addr(ctx, as_str(addr)))
.unwrap_or(0)
}
@@ -1387,14 +1362,12 @@ pub unsafe extern "C" fn dc_create_contact(
return 0;
}
let ffi_context = &*context;
let name = to_string_lossy(name);
let name = if name.is_null() { "" } else { as_str(name) };
ffi_context
.with_inner(
|ctx| match Contact::create(ctx, name, to_string_lossy(addr)) {
Ok(id) => id,
Err(_) => 0,
},
)
.with_inner(|ctx| match Contact::create(ctx, name, as_str(addr)) {
Ok(id) => id,
Err(_) => 0,
})
.unwrap_or(0)
}
@@ -1410,7 +1383,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, to_string_lossy(addr_book)) {
|ctx| match Contact::add_address_book(ctx, as_str(addr_book)) {
Ok(cnt) => cnt as libc::c_int,
Err(_) => 0,
},
@@ -1429,7 +1402,11 @@ pub unsafe extern "C" fn dc_get_contacts(
return ptr::null_mut();
}
let ffi_context = &*context;
let query = to_opt_string_lossy(query);
let query = if query.is_null() {
None
} else {
Some(as_str(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))),
@@ -1566,7 +1543,7 @@ pub unsafe extern "C" fn dc_imex(
let ffi_context = &*context;
ffi_context
.with_inner(|ctx| imex::imex(ctx, what, to_opt_string_lossy(param1)))
.with_inner(|ctx| imex::imex(ctx, what, as_opt_str(param1)))
.ok();
}
@@ -1581,7 +1558,7 @@ pub unsafe extern "C" fn dc_imex_has_backup(
}
let ffi_context = &*context;
ffi_context
.with_inner(|ctx| match imex::has_backup(ctx, to_string_lossy(dir)) {
.with_inner(|ctx| match imex::has_backup(ctx, as_str(dir)) {
Ok(res) => res.strdup(),
Err(err) => {
error!(ctx, "dc_imex_has_backup: {}", err);
@@ -1624,15 +1601,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, &to_string_lossy(setup_code)) {
.with_inner(
|ctx| match imex::continue_key_transfer(ctx, msg_id, as_str(setup_code)) {
Ok(()) => 1,
Err(err) => {
error!(ctx, "dc_continue_key_transfer: {}", err);
0
}
}
})
},
)
.unwrap_or(0)
}
@@ -1643,7 +1620,9 @@ pub unsafe extern "C" fn dc_stop_ongoing_process(context: *mut dc_context_t) {
return;
}
let ffi_context = &*context;
ffi_context.with_inner(|ctx| ctx.stop_ongoing()).ok();
ffi_context
.with_inner(|ctx| configure::dc_stop_ongoing_process(ctx))
.ok();
}
#[no_mangle]
@@ -1658,7 +1637,7 @@ pub unsafe extern "C" fn dc_check_qr(
let ffi_context = &*context;
ffi_context
.with_inner(|ctx| {
let lot = qr::check_qr(ctx, to_string_lossy(qr));
let lot = qr::check_qr(ctx, as_str(qr));
Box::into_raw(Box::new(lot))
})
.unwrap_or_else(|_| ptr::null_mut())
@@ -1694,7 +1673,7 @@ pub unsafe extern "C" fn dc_join_securejoin(
}
let ffi_context = &*context;
ffi_context
.with_inner(|ctx| securejoin::dc_join_securejoin(ctx, &to_string_lossy(qr)))
.with_inner(|ctx| securejoin::dc_join_securejoin(ctx, as_str(qr)))
.unwrap_or(0)
}
@@ -1743,7 +1722,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(false) as _
.unwrap_or(0)
}
#[no_mangle]
@@ -2157,7 +2136,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_string_lossy().strdup(),
Some(p) => p.to_str().unwrap_or_default().to_string().strdup(),
None => ptr::null_mut(),
})
.unwrap_or_else(|_| ptr::null_mut())
@@ -2609,7 +2588,8 @@ pub unsafe extern "C" fn dc_msg_set_text(msg: *mut dc_msg_t, text: *const libc::
return;
}
let ffi_msg = &mut *msg;
ffi_msg.message.set_text(to_opt_string_lossy(text))
// 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))
}
#[no_mangle]
@@ -2623,10 +2603,7 @@ pub unsafe extern "C" fn dc_msg_set_file(
return;
}
let ffi_msg = &mut *msg;
ffi_msg.message.set_file(
to_string_lossy(file),
to_opt_string_lossy(filemime).as_ref().map(|x| x.as_str()),
)
ffi_msg.message.set_file(as_str(file), as_opt_str(filemime))
}
#[no_mangle]
@@ -2796,7 +2773,7 @@ pub unsafe extern "C" fn dc_contact_get_profile_image(
ffi_contact
.contact
.get_profile_image(ctx)
.map(|p| p.to_string_lossy().strdup())
.map(|p| p.to_str().unwrap_or_default().to_string().strdup())
.unwrap_or_else(|| std::ptr::null_mut())
})
.unwrap_or_else(|_| ptr::null_mut())
@@ -2921,6 +2898,14 @@ 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,6 +4,7 @@ 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::*;
@@ -192,7 +193,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 { "" },
@@ -211,11 +212,6 @@ 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,
);
@@ -475,7 +471,7 @@ pub unsafe fn dc_cmdline(context: &Context, line: &str) -> Result<(), failure::E
ensure!(0 != dc_reset_tables(context, bits), "Reset failed");
}
"stop" => {
context.stop_ongoing();
dc_stop_ongoing_process(context);
}
"set" => {
ensure!(!arg1.is_empty(), "Argument <key> missing.");
@@ -746,7 +742,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 continue_streaming {
if 0 != continue_streaming {
println!("Success, streaming should be continued.");
} else {
println!("Success, streaming can be stoppped.");

View File

@@ -43,6 +43,7 @@ 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,90 +35,93 @@ fn cb(_ctx: &Context, event: Event) -> usize {
}
fn main() {
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);
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 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_idle(&ctx1);
perform_imap_fetch(&ctx1);
if *r1.read().unwrap() {
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);
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,);
}
});
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);
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!("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!("stopping threads");
println!("fetching chats..");
let chats = Chatlist::try_load(&ctx, 0, None, None).unwrap();
*running.clone().write().unwrap() = false;
deltachat::job::interrupt_imap_idle(&ctx);
deltachat::job::interrupt_smtp_idle(&ctx);
for i in 0..chats.len() {
let summary = chats.get_summary(&ctx, 0, None);
let text1 = summary.get_text1();
let text2 = summary.get_text2();
println!("chat: {} - {:?} - {:?}", i, text1, text2,);
println!("joining");
t1.join().unwrap();
t2.join().unwrap();
println!("closing");
}
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,9 +16,12 @@ 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_string_lossy().as_bytes())
{
if let Some(encoding) = charset::Charset::for_label(
CStr::from_ptr(fromcode)
.to_str()
.unwrap_or_default()
.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_string_lossy()
.into_owned();
.to_str()
.unwrap_or_default();
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,14 +8,10 @@ use crate::mailmime::types_helper::*;
pub(crate) use libc::{
calloc, close, free, isalpha, isdigit, malloc, memcmp, memcpy, memmove, memset, realloc,
strcpy, strlen, strncmp, strncpy, strnlen,
strcpy, strlen, strncmp, strncpy,
};
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();
@@ -34,25 +30,16 @@ pub(crate) unsafe fn strncasecmp(
s2: *const libc::c_char,
n: libc::size_t,
) -> libc::c_int {
if s1.is_null() || s2.is_null() {
return 1;
}
// 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)
let s1 = std::ffi::CStr::from_ptr(s1)
.to_string_lossy()
.to_lowercase();
let s2 = std::ffi::CStr::from_bytes_with_nul_unchecked(s2_slice)
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 == s2 {
if s1[..m1] == s2[..m2] {
0
} else {
1
@@ -1695,14 +1682,6 @@ 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,6 +6,7 @@
<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,13 +2,7 @@
low level API reference
===================================
for full doxygen-generated C-docs, defines and functions please checkout
https://c.delta.chat
Python low-level capi calls
---------------------------
for full C-docs, defines and function checkout :doc:`capi`
.. automodule:: deltachat.capi.lib

View File

@@ -1,7 +1,6 @@
""" Account class implementation. """
from __future__ import print_function
import atexit
import threading
import re
import time
@@ -50,10 +49,9 @@ 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:
@@ -71,18 +69,6 @@ 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.
@@ -425,9 +411,6 @@ 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
#
@@ -449,7 +432,7 @@ class Account(object):
def stop_threads(self, wait=True):
""" stop IMAP/SMTP threads. """
self.stop_ongoing()
lib.dc_stop_ongoing_process(self._dc_context)
self._threads.stop(wait=wait)
def shutdown(self, wait=True):
@@ -461,7 +444,6 @@ 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

@@ -57,14 +57,6 @@ 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
@@ -90,64 +82,17 @@ 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_LP|DC_STATE_|DC_STR|'
rex = re.compile(r'#define\s+((?:DC_EVENT_|DC_QR|DC_MSG|DC_STATE_|'
r'DC_CONTACT_ID_|DC_GCL|DC_CHAT|DC_PROVIDER)\S+)\s+([x\d]+).*')
for line in f:
m = rex.match(line)

View File

@@ -109,10 +109,6 @@ class Message(object):
""" return True if this message was encrypted. """
return bool(lib.dc_msg_get_showpadlock(self._dc_msg))
def is_forwarded(self):
""" return True if this message was forwarded. """
return bool(lib.dc_msg_is_forwarded(self._dc_msg))
def get_message_info(self):
""" Return informational text for a single message.

View File

@@ -150,11 +150,6 @@ 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,27 +148,6 @@ 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()
@@ -365,20 +344,12 @@ class TestOfflineChat:
class TestOnlineAccount:
def get_chat(self, ac1, ac2, both_created=False):
def get_chat(self, ac1, ac2):
c2 = ac1.create_contact(email=ac2.get_config("addr"))
chat = ac1.create_chat_by_contact(c2)
assert chat.id > const.DC_CHAT_ID_LAST_SPECIAL
if both_created:
ac2.create_chat_by_contact(ac2.create_contact(email=ac1.get_config("addr")))
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")
@@ -391,8 +362,7 @@ class TestOnlineAccount:
def test_one_account_send_bcc_setting(self, acfactory, lp):
ac1 = acfactory.get_online_configuring_account()
ac2_config = acfactory.peek_online_config()
c2 = ac1.create_contact(email=ac2_config["addr"])
c2 = ac1.create_contact(email="notexists@testrun.org")
chat = ac1.create_chat_by_contact(c2)
assert chat.id > const.DC_CHAT_ID_LAST_SPECIAL
wait_successful_IMAP_SMTP_connection(ac1)
@@ -413,7 +383,6 @@ class TestOnlineAccount:
lp.sec("send out message without bcc")
ac1.set_config("bcc_self", "0")
msg_out = chat.send_text("message3")
assert not msg_out.is_forwarded()
ev = ac1._evlogger.get_matching("DC_EVENT_MSGS_CHANGED")
assert ev[2] == msg_out.id
ev = ac1._evlogger.get_matching("DC_EVENT_SMTP_MESSAGE_SENT")
@@ -456,7 +425,6 @@ class TestOnlineAccount:
# check the message arrived in contact-requests/deaddrop
chat2 = msg_in.chat
assert msg_in in chat2.get_messages()
assert not msg_in.is_forwarded()
assert chat2.is_deaddrop()
assert chat2 == ac2.get_deaddrop_chat()
chat3 = ac2.create_group_chat("newgroup")
@@ -464,35 +432,9 @@ class TestOnlineAccount:
ac2.forward_messages([msg_in], chat3)
assert chat3.is_promoted()
messages = chat3.get_messages()
msg = messages[-1]
assert msg.is_forwarded()
ac2.delete_messages(messages)
assert not chat3.get_messages()
def test_forward_own_message(self, acfactory, lp):
ac1, ac2 = acfactory.get_two_online_accounts()
chat = self.get_chat(ac1, ac2, both_created=True)
lp.sec("sending message")
msg_out = chat.send_text("message2")
lp.sec("receiving message")
ev = ac2._evlogger.get_matching("DC_EVENT_INCOMING_MSG")
msg_in = ac2.get_message_by_id(ev[2])
assert msg_in.text == "message2"
assert not msg_in.is_forwarded()
lp.sec("ac1: creating group chat, and forward own message")
group = ac1.create_group_chat("newgroup2")
group.add_contact(ac1.create_contact(ac2.get_config("addr")))
ac1.forward_messages([msg_out], group)
# wait for other account to receive
ev = ac2._evlogger.get_matching("DC_EVENT_INCOMING_MSG")
msg_in = ac2.get_message_by_id(ev[2])
assert msg_in.text == "message2"
assert msg_in.is_forwarded()
def test_send_and_receive_message_markseen(self, acfactory, lp):
ac1, ac2 = acfactory.get_two_online_accounts()
@@ -512,7 +454,6 @@ class TestOnlineAccount:
assert ev[2] == msg_out.id
msg_in = ac2.get_message_by_id(msg_out.id)
assert msg_in.text == "message1"
assert not msg_in.is_forwarded()
lp.sec("check the message arrived in contact-requets/deaddrop")
chat2 = msg_in.chat

View File

@@ -334,7 +334,9 @@ only on image changes.
# Miscellaneous
Messengers SHOULD use the header `In-Reply-To` as usual.
Messengers SHOULD use the header `Chat-Predecessor`
instead of `In-Reply-To` as the latter one results
in infinite threads on typical MUAs.
Messengers SHOULD add a `Chat-Voice-message: 1` header
if an attached audio file is a voice message.
@@ -344,7 +346,7 @@ to specify the duration of attached audio or video files.
The value MUST be the duration in milliseconds.
This allows the receiver to show the time without knowing the file format.
In-Reply-To: Gr.12345uvwxyZ.0005@domain
Chat-Predecessor: foo123@domain
Chat-Voice-Message: 1
Chat-Duration: 10000

View File

@@ -79,13 +79,19 @@ 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_string_lossy() }
== "Autocrypt"
&& unsafe {
CStr::from_ptr((*optional_field).fld_name)
.to_str()
.unwrap_or_default()
} == "Autocrypt"
{
let value =
unsafe { CStr::from_ptr((*optional_field).fld_value).to_string_lossy() };
let value = unsafe {
CStr::from_ptr((*optional_field).fld_value)
.to_str()
.unwrap_or_default()
};
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,12 +1761,9 @@ pub fn forward_msgs(context: &Context, msg_ids: &[u32], chat_id: u32) -> Result<
}
let mut msg = msg.unwrap();
let original_param = msg.param.clone();
// 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);
if msg.from_id != DC_CONTACT_ID_SELF {
msg.param.set_int(Param::Forwarded, 1);
}
msg.param.remove(Param::GuranteeE2ee);
msg.param.remove(Param::ForcePlaintext);
msg.param.remove(Param::Cmd);

View File

@@ -270,7 +270,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
if lastmsg.from_id != 1 as libc::c_uint
&& (chat.typ == Chattype::Group || chat.typ == Chattype::VerifiedGroup)
{
lastcontact = Contact::load_from_db(context, lastmsg.from_id).ok();

View File

@@ -3,7 +3,6 @@ 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;
@@ -11,56 +10,54 @@ use super::read_autoconf_file;
* Thunderbird's Autoconfigure
******************************************************************************/
/* documentation: https://developer.mozilla.org/en-US/docs/Mozilla/Thunderbird/Autoconfiguration */
struct MozAutoconfigure<'a> {
pub in_emailaddr: &'a str,
#[repr(C)]
struct moz_autoconfigure_t<'a> {
pub in_0: &'a LoginParam,
pub in_emaildomain: &'a str,
pub in_emaillocalpart: &'a str,
pub out: LoginParam,
pub out_imap_set: bool,
pub out_smtp_set: bool,
pub tag_server: MozServer,
pub tag_config: MozConfigTag,
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,
}
#[derive(PartialEq)]
enum MozServer {
Undefined,
Imap,
Smtp,
}
enum MozConfigTag {
Undefined,
Hostname,
Port,
Sockettype,
Username,
}
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);
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,
};
// 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 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..];
let mut moz_ac = MozAutoconfigure {
in_emailaddr,
let mut reader = quick_xml::Reader::from_str(&xml_raw);
reader.trim_text(true);
let mut buf = Vec::new();
let mut moz_ac = moz_autoconfigure_t {
in_0: param_in,
in_emaildomain,
in_emaillocalpart,
out: LoginParam::new(),
out_imap_set: false,
out_smtp_set: false,
tag_server: MozServer::Undefined,
tag_config: MozConfigTag::Undefined,
out_imap_set: 0,
out_smtp_set: 0,
tag_server: 0,
tag_config: 0,
};
let mut buf = Vec::new();
loop {
match reader.read_event(&mut buf) {
Ok(quick_xml::events::Event::Start(ref e)) => {
@@ -71,7 +68,8 @@ pub fn moz_parse_xml(in_emailaddr: &str, xml_raw: &str) -> Result<LoginParam, Er
moz_autoconfigure_text_cb(e, &mut moz_ac, &reader)
}
Err(e) => {
bail!(
error!(
context,
"Configure xml: Error at position {}: {:?}",
reader.buffer_position(),
e
@@ -89,36 +87,21 @@ pub fn moz_parse_xml(in_emailaddr: &str, xml_raw: &str) -> Result<LoginParam, Er
|| moz_ac.out.send_port == 0
{
let r = moz_ac.out.to_string();
bail!("Bad or incomplete autoconfig: {}", r,);
warn!(context, "Bad or incomplete autoconfig: {}", r,);
return None;
}
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),
}
Some(moz_ac.out)
}
fn moz_autoconfigure_text_cb<B: std::io::BufRead>(
event: &BytesText,
moz_ac: &mut MozAutoconfigure,
moz_ac: &mut moz_autoconfigure_t,
reader: &quick_xml::Reader<B>,
) {
let val = event.unescape_and_decode(reader).unwrap_or_default();
let addr = moz_ac.in_emailaddr;
let addr = &moz_ac.in_0.addr;
let email_local = moz_ac.in_emaillocalpart;
let email_domain = moz_ac.in_emaildomain;
@@ -128,12 +111,12 @@ fn moz_autoconfigure_text_cb<B: std::io::BufRead>(
.replace("%EMAILLOCALPART%", email_local)
.replace("%EMAILDOMAIN%", email_domain);
match moz_ac.tag_server {
MozServer::Imap => match moz_ac.tag_config {
MozConfigTag::Hostname => moz_ac.out.mail_server = val,
MozConfigTag::Port => moz_ac.out.mail_port = val.parse().unwrap_or_default(),
MozConfigTag::Username => moz_ac.out.mail_user = val,
MozConfigTag::Sockettype => {
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 => {
let val_lower = val.to_lowercase();
if val_lower == "ssl" {
moz_ac.out.server_flags |= DC_LP_IMAP_SOCKET_SSL as i32
@@ -146,12 +129,13 @@ fn moz_autoconfigure_text_cb<B: std::io::BufRead>(
}
}
_ => {}
},
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 => {
}
} 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 => {
let val_lower = val.to_lowercase();
if val_lower == "ssl" {
moz_ac.out.server_flags |= DC_LP_SMTP_SOCKET_SSL as i32
@@ -164,34 +148,29 @@ fn moz_autoconfigure_text_cb<B: std::io::BufRead>(
}
}
_ => {}
},
MozServer::Undefined => {}
}
}
}
fn moz_autoconfigure_endtag_cb(event: &BytesEnd, moz_ac: &mut MozAutoconfigure) {
fn moz_autoconfigure_endtag_cb(event: &BytesEnd, moz_ac: &mut moz_autoconfigure_t) {
let tag = String::from_utf8_lossy(event.name()).trim().to_lowercase();
if tag == "incomingserver" {
if moz_ac.tag_server == MozServer::Imap {
moz_ac.out_imap_set = true;
}
moz_ac.tag_server = MozServer::Undefined;
moz_ac.tag_config = MozConfigTag::Undefined;
moz_ac.tag_server = 0;
moz_ac.tag_config = 0;
moz_ac.out_imap_set = 1;
} else if tag == "outgoingserver" {
if moz_ac.tag_server == MozServer::Smtp {
moz_ac.out_smtp_set = true;
}
moz_ac.tag_server = MozServer::Undefined;
moz_ac.tag_config = MozConfigTag::Undefined;
moz_ac.tag_server = 0;
moz_ac.tag_config = 0;
moz_ac.out_smtp_set = 1;
} else {
moz_ac.tag_config = MozConfigTag::Undefined;
moz_ac.tag_config = 0;
}
}
fn moz_autoconfigure_starttag_cb<B: std::io::BufRead>(
event: &BytesStart,
moz_ac: &mut MozAutoconfigure,
moz_ac: &mut moz_autoconfigure_t,
reader: &quick_xml::Reader<B>,
) {
let tag = String::from_utf8_lossy(event.name()).trim().to_lowercase();
@@ -208,115 +187,25 @@ fn moz_autoconfigure_starttag_cb<B: std::io::BufRead>(
.unwrap_or_default()
.to_lowercase();
if typ == "imap" && !moz_ac.out_imap_set {
MozServer::Imap
if typ == "imap" && moz_ac.out_imap_set == 0 {
1
} else {
MozServer::Undefined
0
}
} else {
MozServer::Undefined
0
};
moz_ac.tag_config = MozConfigTag::Undefined;
moz_ac.tag_config = 0;
} else if tag == "outgoingserver" {
moz_ac.tag_server = if !moz_ac.out_smtp_set {
MozServer::Smtp
} else {
MozServer::Undefined
};
moz_ac.tag_config = MozConfigTag::Undefined;
moz_ac.tag_server = if moz_ac.out_smtp_set == 0 { 2 } else { 0 };
moz_ac.tag_config = 0;
} else if tag == "hostname" {
moz_ac.tag_config = MozConfigTag::Hostname;
moz_ac.tag_config = 10;
} else if tag == "port" {
moz_ac.tag_config = MozConfigTag::Port;
moz_ac.tag_config = 11;
} else if tag == "sockettype" {
moz_ac.tag_config = MozConfigTag::Sockettype;
moz_ac.tag_config = 13;
} else if tag == "username" {
moz_ac.tag_config = MozConfigTag::Username;
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_parse_outlook_autoconfig() {
// Copied from https://autoconfig.thunderbird.net/v1.1/outlook.com on 2019-10-11
let xml_raw =
"<clientConfig version=\"1.1\">
<emailProvider id=\"outlook.com\">
<domain>hotmail.com</domain>
<domain>hotmail.co.uk</domain>
<domain>hotmail.co.jp</domain>
<domain>hotmail.com.br</domain>
<domain>hotmail.de</domain>
<domain>hotmail.fr</domain>
<domain>hotmail.it</domain>
<domain>hotmail.es</domain>
<domain>live.com</domain>
<domain>live.co.uk</domain>
<domain>live.co.jp</domain>
<domain>live.de</domain>
<domain>live.fr</domain>
<domain>live.it</domain>
<domain>live.jp</domain>
<domain>msn.com</domain>
<domain>outlook.com</domain>
<displayName>Outlook.com (Microsoft)</displayName>
<displayShortName>Outlook</displayShortName>
<incomingServer type=\"exchange\">
<hostname>outlook.office365.com</hostname>
<port>443</port>
<username>%EMAILADDRESS%</username>
<socketType>SSL</socketType>
<authentication>OAuth2</authentication>
<owaURL>https://outlook.office365.com/owa/</owaURL>
<ewsURL>https://outlook.office365.com/ews/exchange.asmx</ewsURL>
<useGlobalPreferredServer>true</useGlobalPreferredServer>
</incomingServer>
<incomingServer type=\"imap\">
<hostname>outlook.office365.com</hostname>
<port>993</port>
<socketType>SSL</socketType>
<authentication>password-cleartext</authentication>
<username>%EMAILADDRESS%</username>
</incomingServer>
<incomingServer type=\"pop3\">
<hostname>outlook.office365.com</hostname>
<port>995</port>
<socketType>SSL</socketType>
<authentication>password-cleartext</authentication>
<username>%EMAILADDRESS%</username>
<pop3>
<leaveMessagesOnServer>true</leaveMessagesOnServer>
<!-- Outlook.com docs specifically mention that POP3 deletes have effect on the main inbox on webmail and IMAP -->
</pop3>
</incomingServer>
<outgoingServer type=\"smtp\">
<hostname>smtp.office365.com</hostname>
<port>587</port>
<socketType>STARTTLS</socketType>
<authentication>password-cleartext</authentication>
<username>%EMAILADDRESS%</username>
</outgoingServer>
<documentation url=\"http://windows.microsoft.com/en-US/windows/outlook/send-receive-from-app\">
<descr lang=\"en\">Set up an email app with Outlook.com</descr>
</documentation>
</emailProvider>
<webMail>
<loginPage url=\"https://www.outlook.com/\"/>
<loginPageInfo url=\"https://www.outlook.com/\">
<username>%EMAILADDRESS%</username>
<usernameField id=\"i0116\" name=\"login\"/>
<passwordField id=\"i0118\" name=\"passwd\"/>
<loginButton id=\"idSIButton9\" name=\"SI\"/>
</loginPageInfo>
</webMail>
</clientConfig>";
let res = 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);
moz_ac.tag_config = 12;
}
}

View File

@@ -1,249 +1,137 @@
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
struct OutlookAutodiscover {
/* ******************************************************************************
* Outlook's Autodiscover
******************************************************************************/
#[repr(C)]
struct outlk_autodiscover_t<'a> {
pub in_0: &'a LoginParam,
pub out: LoginParam,
pub out_imap_set: bool,
pub out_smtp_set: bool,
pub config_type: Option<String>,
pub config_server: String,
pub config_port: i32,
pub config_ssl: String,
pub config_redirecturl: Option<String>,
}
enum ParsingResult {
LoginParam(LoginParam),
RedirectUrl(String),
}
fn 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 config: HashMap<String, String>,
}
pub fn outlk_autodiscover(
context: &Context,
url: &str,
_param_in: &LoginParam,
url__: &str,
param_in: &LoginParam,
) -> Option<LoginParam> {
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(res) => match res {
ParsingResult::RedirectUrl(redirect_url) => url = redirect_url,
ParsingResult::LoginParam(login_param) => return Some(login_param),
},
}
} else {
return None;
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());
}
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,
_ => (),
}
buf.clear();
}
if let Some(next_url) = outlk_ad.config.get("redirecturl") {
if !next_url.is_empty() {
url = next_url.to_string();
continue;
}
}
break;
}
None
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)
}
fn outlk_autodiscover_endtag_cb(event: &BytesEnd, outlk_ad: &mut OutlookAutodiscover) {
let tag = String::from_utf8_lossy(event.name()).trim().to_lowercase();
if tag == "protocol" {
if let Some(type_) = &outlk_ad.config_type {
let port = outlk_ad.config_port;
let ssl_on = outlk_ad.config_ssl == "on";
let ssl_off = outlk_ad.config_ssl == "off";
if type_ == "imap" && !outlk_ad.out_imap_set {
outlk_ad.out.mail_server =
std::mem::replace(&mut outlk_ad.config_server, String::new());
outlk_ad.out.mail_port = port;
if ssl_on {
outlk_ad.out.server_flags |= DC_LP_IMAP_SOCKET_SSL as i32
} else if ssl_off {
outlk_ad.out.server_flags |= DC_LP_IMAP_SOCKET_PLAIN as i32
}
outlk_ad.out_imap_set = true
} else if type_ == "smtp" && !outlk_ad.out_smtp_set {
outlk_ad.out.send_server =
std::mem::replace(&mut outlk_ad.config_server, String::new());
outlk_ad.out.send_port = outlk_ad.config_port;
if ssl_on {
outlk_ad.out.server_flags |= DC_LP_SMTP_SOCKET_SSL as i32
} else if ssl_off {
outlk_ad.out.server_flags |= DC_LP_SMTP_SOCKET_PLAIN as i32
}
outlk_ad.out_smtp_set = true
}
}
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_parse_redirect() {
let res = 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");
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;
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;
}
}
}

File diff suppressed because it is too large Load Diff

View File

@@ -129,23 +129,23 @@ pub const DC_CREATE_MVBOX: usize = 1;
/// Force OAuth2 authorization. This flag does not skip automatic configuration.
/// Before calling configure() with DC_LP_AUTH_OAUTH2 set,
/// the user has to confirm access at the URL returned by dc_get_oauth2_url().
pub const DC_LP_AUTH_OAUTH2: i32 = 0x2;
pub const DC_LP_AUTH_OAUTH2: usize = 0x2;
/// Force NORMAL authorization, this is the default.
/// If this flag is set, automatic configuration is skipped.
pub const DC_LP_AUTH_NORMAL: i32 = 0x4;
pub const DC_LP_AUTH_NORMAL: usize = 0x4;
/// Connect to IMAP via STARTTLS.
/// If this flag is set, automatic configuration is skipped.
pub const DC_LP_IMAP_SOCKET_STARTTLS: i32 = 0x100;
pub const DC_LP_IMAP_SOCKET_STARTTLS: usize = 0x100;
/// Connect to IMAP via SSL.
/// If this flag is set, automatic configuration is skipped.
pub const DC_LP_IMAP_SOCKET_SSL: i32 = 0x200;
pub const DC_LP_IMAP_SOCKET_SSL: usize = 0x200;
/// Connect to IMAP unencrypted, this should not be used.
/// If this flag is set, automatic configuration is skipped.
pub const DC_LP_IMAP_SOCKET_PLAIN: i32 = 0x400;
pub const DC_LP_IMAP_SOCKET_PLAIN: usize = 0x400;
/// Connect to SMTP via STARTTLS.
/// If this flag is set, automatic configuration is skipped.
@@ -160,9 +160,9 @@ pub const DC_LP_SMTP_SOCKET_SSL: usize = 0x20000;
pub const DC_LP_SMTP_SOCKET_PLAIN: usize = 0x40000;
/// if none of these flags are set, the default is chosen
pub const DC_LP_AUTH_FLAGS: i32 = (DC_LP_AUTH_OAUTH2 | DC_LP_AUTH_NORMAL);
pub const DC_LP_AUTH_FLAGS: usize = (DC_LP_AUTH_OAUTH2 | DC_LP_AUTH_NORMAL);
/// if none of these flags are set, the default is chosen
pub const DC_LP_IMAP_SOCKET_FLAGS: i32 =
pub const DC_LP_IMAP_SOCKET_FLAGS: usize =
(DC_LP_IMAP_SOCKET_STARTTLS | DC_LP_IMAP_SOCKET_SSL | DC_LP_IMAP_SOCKET_PLAIN);
/// if none of these flags are set, the default is chosen
pub const DC_LP_SMTP_SOCKET_FLAGS: usize =
@@ -257,7 +257,7 @@ const DC_SHOW_EMAILS_ACCEPTED_CONTACTS: usize = 1;
const DC_SHOW_EMAILS_ALL: usize = 2;
// TODO: Strings need some doumentation about used placeholders.
// These constants are used to set stock translation strings
// These constants are used to request strings using #DC_EVENT_GET_STRING.
const DC_STR_NOMESSAGES: usize = 1;
const DC_STR_SELF: usize = 2;

View File

@@ -63,13 +63,12 @@ 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,
shall_stop_ongoing: bool,
pub shall_stop_ongoing: bool,
}
/// Return some info about deltachat-core
@@ -146,11 +145,10 @@ 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, false),
ctx.sql.open(&ctx, &ctx.dbfile, 0),
"Failed opening sqlite database"
);
@@ -222,66 +220,6 @@ 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,24 +606,33 @@ 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_string_lossy().as_bytes())
{
if let Some(encoding) = Charset::for_label(
CStr::from_ptr(charset)
.to_str()
.unwrap_or_default()
.as_bytes(),
) {
let (res, _, _) = encoding.decode(&decoded_data);
if res.is_empty() {
/* no error - but nothing to add */
return false;
ok_to_continue = false;
} else {
decoded_data = res.as_bytes().to_vec()
}
decoded_data = res.as_bytes().to_vec()
} else {
warn!(
self.context,
@@ -633,30 +642,31 @@ impl<'a> MimeParser<'a> {
);
}
}
/* check header directly as is_send_by_messenger is not yet set up */
let is_msgrmsg = self.lookup_optional_field("Chat-Version").is_some();
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();
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;
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.simplify(&input, is_html, is_msgrmsg)
};
if !simplified_txt.is_empty() {
let mut part = Part::default();
part.typ = Viewtype::Text;
part.mimetype = mime_type;
part.msg = Some(simplified_txt);
part.msg_raw =
Some(std::string::String::from_utf8_lossy(&decoded_data).to_string());
self.do_add_single_part(part);
}
simplifier.unwrap().simplify(&input, is_html, is_msgrmsg)
};
if !simplified_txt.is_empty() {
let mut part = Part::default();
part.typ = Viewtype::Text;
part.mimetype = mime_type;
part.msg = Some(simplified_txt);
part.msg_raw =
Some(std::string::String::from_utf8_lossy(&decoded_data).to_string());
self.do_add_single_part(part);
}
if simplifier.is_forwarded {
self.is_forwarded = true;
if simplifier.unwrap().is_forwarded {
self.is_forwarded = true;
}
}
}
DC_MIMETYPE_IMAGE
@@ -731,29 +741,33 @@ impl<'a> MimeParser<'a> {
desired_filename =
format!("file.{}", as_str((*(*mime).mm_content_type).ct_subtype));
} else {
return false;
ok_to_continue = false;
}
}
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();
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,
);
}
} 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,7 +1,8 @@
use std::ffi::CString;
use std::ptr;
use itertools::join;
use libc::strcmp;
use libc::{free, strcmp};
use mmime::clist::*;
use mmime::mailimf::types::*;
use mmime::mailmime::content::*;
@@ -319,8 +320,13 @@ 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 = String::new();
let mut mime_references = String::new();
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());
};
// 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",
@@ -354,6 +360,7 @@ 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");
}
@@ -429,7 +436,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;
@@ -514,7 +521,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;
@@ -582,14 +589,20 @@ 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, " ")
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,
)
}
}
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, " ")
mime_references = dc_str_from_clist(
(*(*field).fld_data.fld_references).mid_list,
b" \x00" as *const u8 as *const libc::c_char,
)
}
}
@@ -600,91 +613,97 @@ 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);
}
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,
))
);
*/
/*
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,
])?;
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(())
},
)?;
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
})?;
info!(
context,
@@ -704,6 +723,8 @@ unsafe fn add_parts(
}
}
cleanup(mime_in_reply_to, mime_references);
Ok(())
}
@@ -947,7 +968,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;
@@ -1032,9 +1053,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 Ok(());
return;
}
}
}
@@ -1159,7 +1180,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 Ok(());
return;
}
chat_id = create_group_record(
context,
@@ -1187,10 +1208,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 Ok(());
return;
}
// execute group commands
@@ -1243,7 +1264,7 @@ unsafe fn create_or_lookup_group(
} else {
chat.param.set(Param::ProfileImage, grpimage);
}
chat.update_param(context)?;
chat.update_param(context).unwrap_or_default();
send_EVENT_CHAT_MODIFIED = 1;
}
}
@@ -1307,12 +1328,11 @@ 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
@@ -1325,7 +1345,7 @@ unsafe fn create_or_lookup_adhoc_group(
to_ids: &mut Vec<u32>,
ret_chat_id: *mut u32,
ret_chat_id_blocked: &mut Blocked,
) -> Result<()> {
) {
// if we're here, no grpid was found, check there is an existing ad-hoc
// group matching the to-list or if we can create one
let mut chat_id = 0;
@@ -1345,7 +1365,7 @@ unsafe fn create_or_lookup_adhoc_group(
if to_ids.is_empty() || mime_parser.is_mailinglist_message() {
// too few contacts or a mailinglist
cleanup(ret_chat_id, ret_chat_id_blocked, chat_id, chat_id_blocked);
return Ok(());
return;
}
let mut member_ids = to_ids.clone();
@@ -1358,10 +1378,10 @@ unsafe fn create_or_lookup_adhoc_group(
if member_ids.len() < 3 {
// too few contacts given
cleanup(ret_chat_id, ret_chat_id_blocked, chat_id, chat_id_blocked);
return Ok(());
return;
}
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(
@@ -1381,13 +1401,13 @@ unsafe fn create_or_lookup_adhoc_group(
chat_id_blocked = id_blocked;
/* success, chat found */
cleanup(ret_chat_id, ret_chat_id_blocked, chat_id, chat_id_blocked);
return Ok(());
return;
}
}
if 0 == allow_creation {
cleanup(ret_chat_id, ret_chat_id_blocked, chat_id, chat_id_blocked);
return Ok(());
return;
}
// 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 ...
@@ -1397,7 +1417,7 @@ unsafe fn create_or_lookup_adhoc_group(
let grpid = create_adhoc_grp_id(context, &member_ids);
if grpid.is_empty() {
cleanup(ret_chat_id, ret_chat_id_blocked, chat_id, chat_id_blocked);
return Ok(());
return;
}
// use subject as initial chat name
@@ -1423,7 +1443,6 @@ 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(
@@ -1501,10 +1520,7 @@ 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>,
) -> Result<Vec<u32>> {
fn search_chat_ids_by_contact_ids(context: &Context, unsorted_contact_ids: &Vec<u32>) -> 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);
@@ -1559,11 +1575,11 @@ fn search_chat_ids_by_contact_ids(
}
Ok(())
}
)?;
).unwrap_or_default(); // TODO: better error handling
}
}
Ok(chat_ids)
chat_ids
}
fn check_verified_properties(
@@ -1665,7 +1681,13 @@ 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) */
`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;
}
}
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 = 0;
let mut content_lines_added = 0;
let mut pending_linebreaks: libc::c_int = 0i32;
let mut content_lines_added: libc::c_int = 0i32;
for l in l_first..l_last {
let line = lines[l];
if is_empty_line(line) {

View File

@@ -51,6 +51,83 @@ 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> {
@@ -74,18 +151,36 @@ pub(crate) fn dc_truncate(buf: &str, approx_chars: usize, do_unwrap: bool) -> Co
}
}
pub(crate) fn dc_str_from_clist(list: *const clist, delimiter: &str) -> String {
pub(crate) unsafe fn dc_str_from_clist(
list: *const clist,
delimiter: *const libc::c_char,
) -> *mut libc::c_char {
let mut res = String::new();
if !list.is_null() {
for rfc724_mid in unsafe { (*list).into_iter() } {
if !res.is_empty() {
res += delimiter;
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()
}
res += as_str(rfc724_mid as *const libc::c_char);
}
}
res
res.strdup()
}
pub(crate) fn dc_str_to_clist(str: &str, delimiter: &str) -> *mut clist {
@@ -506,25 +601,6 @@ 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,
@@ -732,15 +808,9 @@ pub fn to_string_lossy(s: *const libc::c_char) -> String {
let cstr = unsafe { CStr::from_ptr(s) };
cstr.to_string_lossy().to_string()
}
pub fn to_opt_string_lossy(s: *const libc::c_char) -> Option<String> {
if s.is_null() {
return None;
}
Some(to_string_lossy(s))
cstr.to_str()
.map(|s| s.to_string())
.unwrap_or_else(|_| cstr.to_string_lossy().to_string())
}
pub fn as_str<'a>(s: *const libc::c_char) -> &'a str {
@@ -945,6 +1015,54 @@ 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));
@@ -1020,6 +1138,21 @@ 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 {
@@ -1035,11 +1168,17 @@ mod tests {
unsafe {
let list: *mut clist = dc_str_to_clist("foo bar test", " ");
assert_eq!((*list).count, 3);
let str = dc_str_from_clist(list, " ");
assert_eq!(str, "foo bar test");
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"
);
clist_free_content(list);
clist_free(list);
free(str as *mut libc::c_void);
}
}
@@ -1200,25 +1339,6 @@ 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);
@@ -1298,6 +1418,19 @@ 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");
@@ -1379,4 +1512,19 @@ mod tests {
let listflags: u32 = DC_GCL_VERIFIED_ONLY.try_into().unwrap();
assert!(listflags_has(listflags, DC_GCL_ADD_SELF) == false);
}
#[test]
fn test_dc_remove_cr_chars() {
unsafe {
let input = "foo\r\nbar".strdup();
dc_remove_cr_chars(input);
assert_eq!("foo\nbar", to_string_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());
}
}
}

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 = pgp::pk_encrypt(
let ctext = dc_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 = mailmime_find_mailimf_fields(in_out_message);
let imffields = unsafe { 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 pgp::create_keypair(&self_addr) {
match dc_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 pgp::pk_decrypt(
let plain = match dc_pgp_pk_decrypt(
&data,
&private_keyring,
&public_keyring_for_validate,

View File

@@ -70,6 +70,12 @@ 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)
@@ -88,12 +94,6 @@ 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,6 +2,8 @@ use std::path::PathBuf;
use strum::EnumProperty;
use crate::stock::StockMessage;
impl Event {
/// Returns the corresponding Event id.
pub fn as_id(&self) -> i32 {
@@ -235,4 +237,17 @@ 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 mut client = imap::Client::new(tls_stream);
client.read_greeting()?;
let client = imap::Client::new(tls_stream);
// TODO: 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 mut client = imap::Client::new(stream.try_clone().unwrap());
client.read_greeting()?;
let client = imap::Client::new(stream.try_clone().unwrap());
// TODO: Read greeting
Ok(Client::Insecure(client, stream))
}
@@ -384,7 +384,7 @@ impl Imap {
return true;
}
let server_flags = self.config.read().unwrap().server_flags as i32;
let server_flags = self.config.read().unwrap().server_flags;
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) -> bool {
pub fn fetch(&self, context: &Context) -> libc::c_int {
if !self.is_connected() || !context.sql.is_open() {
return false;
return 0;
}
self.setup_handle_if_needed(context);
@@ -616,9 +616,9 @@ impl Imap {
break;
}
}
true
1
} else {
false
0
}
}
@@ -1025,7 +1025,7 @@ impl Imap {
info!(context, "IMAP-IDLE has data.");
}
Err(err) => match err {
imap::error::Error::Io(_) | imap::error::Error::ConnectionLost => {
imap::error::Error::ConnectionLost => {
info!(context, "IMAP-IDLE wait cancelled, we will reconnect soon.");
self.should_reconnect.store(true, Ordering::Relaxed);
}

View File

@@ -1,5 +1,6 @@
use core::cmp::{max, min};
use std::path::{Path, PathBuf};
use std::ffi::CString;
use std::path::Path;
use std::ptr;
use num_traits::FromPrimitive;
use rand::{thread_rng, Rng};
@@ -17,7 +18,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;
@@ -87,7 +88,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, true) {
if sql.open(context, &path, 0x1) {
let curr_backup_time =
sql.get_raw_config_int(context, "backup_time")
.unwrap_or_default() as u64;
@@ -108,48 +109,78 @@ 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 ... */
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(),
)?;
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);
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);
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);
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;
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;
}
}
}
}
}
}
}
}
}
}
dc_free_ongoing(context);
Ok(setup_code)
}
@@ -169,7 +200,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 = pgp::symm_encrypt(&passphrase, private_key_asc.as_bytes())?;
let encr = dc_pgp_symm_encrypt(&passphrase, private_key_asc.as_bytes())?;
let replacement = format!(
concat!(
@@ -239,12 +270,18 @@ pub fn continue_key_transfer(context: &Context, msg_id: u32, setup_code: &str) -
);
if let Some(filename) = msg.get_file(context) {
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)?;
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.")
}
Ok(())
Ok(())
} else {
bail!("Cannot read Autocrypt Setup Message file.");
}
} else {
bail!("Message is no Autocrypt Setup Message.");
}
@@ -318,15 +355,53 @@ fn set_self_key(
Ok(())
}
fn decrypt_setup_file<T: std::io::Read + std::io::Seek>(
fn decrypt_setup_file(
_context: &Context,
passphrase: &str,
file: T,
passphrase: impl AsRef<str>,
filecontent: &mut [u8],
) -> Result<String> {
let plain_bytes = pgp::symm_decrypt(passphrase, file)?;
let plain_text = std::string::String::from_utf8(plain_bytes)?;
let mut fc_headerline = String::default();
let mut fc_base64: *const libc::c_char = ptr::null();
Ok(plain_text)
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)
}
pub fn normalize_setup_code(s: &str) -> String {
@@ -344,7 +419,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!(context.alloc_ongoing(), "could not allocate ongoing");
ensure!(dc_alloc_ongoing(context), "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();
@@ -356,7 +431,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() {
context.free_ongoing();
dc_free_ongoing(context);
bail!("Cannot create private key or private key not available.");
} else {
dc_create_folder(context, &param);
@@ -372,7 +447,7 @@ pub fn job_do_DC_JOB_IMEX_IMAP(context: &Context, job: &Job) -> Result<()> {
bail!("unknown IMEX type");
}
};
context.free_ongoing();
dc_free_ongoing(context);
match success {
Ok(()) => {
info!(context, "IMEX successfully completed");
@@ -413,7 +488,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(), false),
context.sql.open(&context, &context.get_dbfile(), 0),
"could not re-open db"
);
@@ -438,7 +513,15 @@ 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?;
ensure!(!context.shall_stop_ongoing(), "received stop signal");
if context
.running_state
.clone()
.read()
.unwrap()
.shall_stop_ongoing
{
bail!("received stop signal");
}
let mut permille = processed_files_cnt * 1000 / total_files_cnt;
if permille < 10 {
permille = 10
@@ -479,6 +562,10 @@ 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);
@@ -496,7 +583,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(), false);
context.sql.open(&context, &context.get_dbfile(), 0);
if !copied {
let s = dest_path_filename.to_string_lossy().to_string();
bail!(
@@ -505,79 +592,130 @@ fn export_backup(context: &Context, dir: impl AsRef<Path>) -> Result<()> {
s
);
}
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)
/* 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*/
let sql = Sql::new();
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);
// 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"
);
processed_files_cnt += 1;
let permille = max(min(processed_files_cnt * 1000 / total_files_cnt, 990), 10);
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;
}
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])?;
}
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;
}
Ok(())
},
)?;
}
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();
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(),
);
};
}
}
if 0 != delete_dest_file {
dc_delete_file(context, &dest_path_filename);
}
ensure!(success, "failed");
Ok(())
}
@@ -592,48 +730,81 @@ 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();
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" {
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;
}
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
}
}
None => {
continue;
}
set_default = if name_f.contains("legacy") {
info!(context, "found legacy key '{}'", path_plus_name.display());
false
} else {
true
}
}
None => {
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;
}
}
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;
}
if let Err(err) = set_self_key(context, &key, set_default, false) {
error!(context, "set_self_key: {}", err);
continue;
}
Err(_) => continue,
imported_cnt += 1
}
imported_cnt += 1;
ensure!(
imported_cnt > 0,
"No private keys found in \"{}\".",
dir_name
);
Ok(())
} else {
bail!("Import: Cannot open directory \"{}\".", dir_name);
}
ensure!(
imported_cnt > 0,
"No private keys found in \"{}\".",
dir_name
);
Ok(())
}
fn export_self_keys(context: &Context, dir: impl AsRef<Path>) -> Result<()> {
@@ -676,7 +847,7 @@ fn export_self_keys(context: &Context, dir: impl AsRef<Path>) -> Result<()> {
},
)?;
ensure!(export_errors == 0, "errors while exporting keys");
ensure!(export_errors == 0, "errors while importing");
Ok(())
}
@@ -713,9 +884,7 @@ 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() {
@@ -737,12 +906,19 @@ 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 test_render_setup_file_newline_replace() {
let t = dummy_context();
t.ctx
.set_stock_translation(StockMessage::AcSetupMsgBody, "hello\r\nthere".to_string())
.unwrap();
fn otest_render_setup_file_newline_replace() {
let t = test_context(Some(Box::new(ac_setup_msg_cb)));
configure_alice_keypair(&t.ctx);
let msg = render_setup_file(&t.ctx, "pw").unwrap();
println!("{}", &msg);
@@ -798,26 +974,50 @@ mod tests {
let ctx = dummy_context();
let context = &ctx.ctx;
let buf_1 = S_EM_SETUPFILE.as_bytes().to_vec();
let (typ, headers, base64) = split_armored_data(&buf_1).unwrap();
assert_eq!(typ, BlockType::Message);
assert!(S_EM_SETUPCODE.starts_with(headers.get(HEADER_SETUPCODE).unwrap()));
assert!(headers.get(HEADER_AUTOCRYPT).is_none());
let mut headerline = String::default();
let mut setupcodebegin = ptr::null();
let mut preferencrypt = ptr::null();
assert!(!base64.is_empty());
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());
let setup_file = S_EM_SETUPFILE.to_string();
let decrypted = decrypt_setup_file(
context,
S_EM_SETUPCODE,
std::io::Cursor::new(setup_file.as_bytes()),
)
.unwrap();
// TODO: verify that this is the right check
assert!(S_EM_SETUPCODE.starts_with(as_str(setupcodebegin)));
let (typ, headers, _base64) = split_armored_data(decrypted.as_bytes()).unwrap();
assert!(preferencrypt.is_null());
assert_eq!(typ, BlockType::PrivateKey);
assert_eq!(headers.get(HEADER_AUTOCRYPT), Some(&"mutual".to_string()));
assert!(headers.get(HEADER_SETUPCODE).is_none());
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",);
}
}

View File

@@ -12,7 +12,7 @@ use crate::dc_tools::*;
use crate::error::Error;
use crate::events::Event;
use crate::imap::*;
use crate::imex::*;
use crate::imex::job_do_DC_JOB_IMEX_IMAP;
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(3, None);
self.try_again_later(3i32, None);
return;
}
}
@@ -172,7 +172,7 @@ impl Job {
Err(err) => {
sock.disconnect();
warn!(context, "smtp failed: {}", err);
self.try_again_later(-1, Some(err.to_string()));
self.try_again_later(-1i32, 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: i32, pending_error: Option<String>) {
fn try_again_later(&mut self, try_again: libc::c_int, pending_error: Option<String>) {
self.try_again = try_again;
self.pending_error = pending_error;
}
@@ -781,13 +781,8 @@ 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::create_keypair("hello").unwrap();
let (public_key, private_key) = crate::pgp::dc_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::create_keypair("hello").unwrap();
let (public_key, private_key) = crate::pgp::dc_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;
pub mod stock;
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 <= DC_CHAT_ID_LAST_SPECIAL) {
if !(seconds < 0 || chat_id <= 9i32 as libc::c_uint) {
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) -> bool {
pub fn set(context: &Context, latitude: f64, longitude: f64, accuracy: f64) -> libc::c_int {
if latitude == 0.0 && longitude == 0.0 {
return true;
return 1;
}
let mut continue_streaming = false;
@@ -293,7 +293,7 @@ pub fn set(context: &Context, latitude: f64, longitude: f64, accuracy: f64) -> b
schedule_MAYBE_SEND_LOCATIONS(context, 0);
}
continue_streaming
continue_streaming as libc::c_int
}
pub fn get_range(

View File

@@ -1,4 +1,5 @@
use std::path::{Path, PathBuf};
use std::ptr;
use deltachat_derive::{FromSql, ToSql};
@@ -106,7 +107,6 @@ 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,10 +339,23 @@ impl Message {
}
if let Some(filename) = self.get_file(context) {
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();
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));
}
}
}

View File

@@ -671,28 +671,31 @@ 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(())
},
)?;
Ok(())
},
)
.unwrap_or_default();
let command = factory.msg.param.get_cmd();
let msg = &factory.msg;
@@ -895,7 +898,9 @@ 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()?;
let abs_path = dc_get_abs_path(context, path_filename)
.to_c_string()
.unwrap_or_default();
mailmime_set_body_file(mime_sub, dc_strdup(abs_path.as_ptr()));
Ok((mime_sub, filename_to_send))
}

View File

@@ -7,7 +7,6 @@ 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",
@@ -16,7 +15,6 @@ 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",
@@ -91,7 +89,6 @@ 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
@@ -123,37 +120,14 @@ pub fn dc_get_oauth2_access_token(
false,
)
};
// 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 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);
}
// ... and POST
let response = reqwest::Client::new()
.post(post_url)
.form(&post_param)
.send();
let response = reqwest::Client::new().post(&token_url).send();
if response.is_err() {
warn!(
context,
@@ -165,14 +139,13 @@ pub fn dc_get_oauth2_access_token(
if !response.status().is_success() {
warn!(
context,
"Unsuccessful response when calling OAuth2 at {}: {:?}",
"Error 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!(
@@ -182,8 +155,6 @@ 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
@@ -297,7 +268,7 @@ impl Oauth2 {
return None;
}
let parsed: reqwest::Result<HashMap<String, serde_json::Value>> = response.json();
let parsed: reqwest::Result<HashMap<String, String>> = response.json();
if parsed.is_err() {
warn!(
context,
@@ -306,13 +277,11 @@ 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,8 +1,9 @@
use std::collections::{BTreeMap, HashSet};
use std::collections::HashSet;
use std::convert::TryInto;
use std::io::Cursor;
use std::ptr;
use pgp::armor::BlockType;
use libc::{strchr, strlen, strncmp, strspn, strstr};
use pgp::composed::{
Deserializable, KeyType as PgpKeyType, Message, SecretKeyParamsBuilder, SignedPublicKey,
SignedSecretKey, SubkeyParamsBuilder,
@@ -11,43 +12,126 @@ 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 const HEADER_AUTOCRYPT: &str = "autocrypt-prefer-encrypt";
pub const HEADER_SETUPCODE: &str = "passphrase-begin";
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;
}
}
}
/// 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))
success
}
/// Create a new key pair.
pub fn create_keypair(addr: impl AsRef<str>) -> Option<(Key, Key)> {
pub fn dc_pgp_create_keypair(addr: impl AsRef<str>) -> Option<(Key, Key)> {
let user_id = format!("<{}>", addr.as_ref());
let key_params = SecretKeyParamsBuilder::default()
@@ -97,7 +181,7 @@ pub fn create_keypair(addr: impl AsRef<str>) -> Option<(Key, Key)> {
Some((Key::Public(public_key), Key::Secret(private_key)))
}
pub fn pk_encrypt(
pub fn dc_pgp_pk_encrypt(
plain: &[u8],
public_keys_for_encryption: &Keyring,
private_key_for_signing: Option<&Key>,
@@ -134,7 +218,7 @@ pub fn pk_encrypt(
Ok(encoded_msg)
}
pub fn pk_decrypt(
pub fn dc_pgp_pk_decrypt(
ctext: &[u8],
private_keys_for_decryption: &Keyring,
public_keys_for_validation: &Keyring,
@@ -183,7 +267,7 @@ pub fn pk_decrypt(
}
/// Symmetric encryption.
pub fn symm_encrypt(passphrase: &str, plain: &[u8]) -> Result<String, Error> {
pub fn dc_pgp_symm_encrypt(passphrase: &str, plain: &[u8]) -> Result<String, Error> {
let mut rng = thread_rng();
let lit_msg = Message::new_literal_bytes("", plain);
@@ -197,11 +281,8 @@ pub fn symm_encrypt(passphrase: &str, plain: &[u8]) -> Result<String, Error> {
}
/// Symmetric decryption.
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)?;
pub fn dc_pgp_symm_decrypt(passphrase: &str, ctext: &[u8]) -> Result<Vec<u8>, Error> {
let enc_msg = Message::from_bytes(Cursor::new(ctext))?;
let decryptor = enc_msg.decrypt_with_password(|| passphrase.into())?;
let msgs = decryptor.collect::<Result<Vec<_>, _>>()?;
@@ -212,35 +293,3 @@ pub fn symm_decrypt<T: std::io::Read + std::io::Seek>(
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,6 +3,7 @@ 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;
@@ -156,7 +157,7 @@ pub fn dc_join_securejoin(context: &Context, qr: &str) -> u32 {
bob.qr_scan = None;
if ongoing_allocated {
context.free_ongoing();
dc_free_ongoing(context);
}
ret_chat_id as u32
};
@@ -169,7 +170,7 @@ pub fn dc_join_securejoin(context: &Context, qr: &str) -> u32 {
info!(context, "Requesting secure-join ...",);
ensure_secret_key_exists(context).ok();
if !context.alloc_ongoing() {
if !dc_alloc_ongoing(context) {
return cleanup(&context, contact_chat_id, false, join_vg);
}
let qr_scan = check_qr(context, &qr);
@@ -183,7 +184,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 context.shall_stop_ongoing() {
if check_exit(context) {
return cleanup(&context, contact_chat_id, true, join_vg);
}
join_vg = qr_scan.get_state() == LotState::QrAskVerifyGroup;
@@ -239,12 +240,21 @@ pub fn dc_join_securejoin(context: &Context, qr: &str) -> u32 {
}
// Bob -> Alice
while !context.shall_stop_ongoing() {
while !check_exit(&context) {
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,
@@ -626,7 +636,7 @@ pub fn handle_securejoin_handshake(
fn end_bobs_joining(context: &Context, status: libc::c_int) {
context.bob.write().unwrap().status = status;
context.stop_ongoing();
dc_stop_ongoing_process(context);
}
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, mechanism) = if 0 != lp.server_flags & (DC_LP_AUTH_OAUTH2 as i32) {
let creds = if 0 != lp.server_flags & (DC_LP_AUTH_OAUTH2 as i32) {
// oauth2
let addr = &lp.addr;
let send_pw = &lp.send_pw;
@@ -81,24 +81,15 @@ impl Smtp {
}
let user = &lp.send_user;
(
lettre::smtp::authentication::Credentials::new(
user.to_string(),
access_token.unwrap_or_default(),
),
vec![lettre::smtp::authentication::Mechanism::Xoauth2],
lettre::smtp::authentication::Credentials::new(
user.to_string(),
access_token.unwrap_or_default(),
)
} else {
// plain
let user = lp.send_user.clone();
let pw = lp.send_pw.clone();
(
lettre::smtp::authentication::Credentials::new(user, pw),
vec![
lettre::smtp::authentication::Mechanism::Plain,
lettre::smtp::authentication::Mechanism::Login,
],
)
lettre::smtp::authentication::Credentials::new(user, pw)
};
let security = if 0
@@ -114,29 +105,19 @@ impl Smtp {
let client = client
.smtp_utf8(true)
.credentials(creds)
.authentication_mechanism(mechanism)
.connection_reuse(lettre::smtp::ConnectionReuseParameters::ReuseUnlimited);
let mut trans = client.transport();
match trans.connect() {
Ok(()) => {
self.transport = Some(trans);
self.transport_connected = true;
context.call_cb(Event::SmtpConnected(format!(
"SMTP-LOGIN as {} ok",
lp.send_user,
)));
return true;
}
Err(err) => {
warn!(context, "SMTP: failed to connect {:?}", err);
}
}
self.transport = Some(client.transport());
context.call_cb(Event::SmtpConnected(format!(
"SMTP-LOGIN as {} ok",
lp.send_user,
)));
true
}
Err(err) => {
warn!(context, "SMTP: failed to setup connection {:?}", err);
warn!(context, "SMTP: failed to establish connection {:?}", err);
false
}
}
false
}
/// SMTP-Send a prepared mail to recipients.

View File

@@ -11,6 +11,8 @@ 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 {
@@ -40,8 +42,8 @@ impl Sql {
}
// return true on success, false on failure
pub fn open(&self, context: &Context, dbfile: &std::path::Path, readonly: bool) -> bool {
match open(context, self, dbfile, readonly) {
pub fn open(&self, context: &Context, dbfile: &std::path::Path, flags: libc::c_int) -> bool {
match open(context, self, dbfile, flags) {
Ok(_) => true,
Err(Error::SqlAlreadyOpen) => false,
Err(_) => {
@@ -318,7 +320,7 @@ fn open(
context: &Context,
sql: &Sql,
dbfile: impl AsRef<std::path::Path>,
readonly: bool,
flags: libc::c_int,
) -> Result<()> {
if sql.is_open() {
error!(
@@ -330,7 +332,7 @@ fn open(
}
let mut open_flags = OpenFlags::SQLITE_OPEN_NO_MUTEX;
if readonly {
if 0 != (flags & DC_OPEN_READONLY as i32) {
open_flags.insert(OpenFlags::SQLITE_OPEN_READ_ONLY);
} else {
open_flags.insert(OpenFlags::SQLITE_OPEN_READ_WRITE);
@@ -349,7 +351,7 @@ fn open(
*sql.pool.write().unwrap() = Some(pool);
}
if !readonly {
if 0 == flags & DC_OPEN_READONLY as i32 {
let mut exists_before_update = 0;
let mut dbversion_before_update = 0;
/* Init tables to dbversion=0 */

View File

@@ -5,7 +5,8 @@ use strum_macros::EnumProperty;
use crate::contact::*;
use crate::context::Context;
use crate::error::Error;
use crate::dc_tools::*;
use crate::events::Event;
/// Stock strings
///
@@ -122,47 +123,19 @@ 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].
///
/// Return a translation (if it was set with set_stock_translation before)
/// or a default (English) string.
/// 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.
pub fn stock_str(&self, id: StockMessage) -> Cow<str> {
match self
.translated_stockstrings
.read()
.unwrap()
.get(&(id as usize))
{
Some(ref x) => Cow::Owned(x.to_string()),
None => Cow::Borrowed(id.fallback()),
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)
}
}
@@ -266,6 +239,7 @@ mod tests {
use crate::test_utils::*;
use crate::constants::DC_CONTACT_ID_SELF;
use libc::uintptr_t;
use num_traits::ToPrimitive;
@@ -280,34 +254,28 @@ mod tests {
assert_eq!(StockMessage::NoMessages.fallback(), "No messages.");
}
#[test]
fn test_set_stock_translation() {
let t = dummy_context();
t.ctx
.set_stock_translation(StockMessage::NoMessages, "xyz".to_string())
.unwrap();
assert_eq!(t.ctx.stock_str(StockMessage::NoMessages), "xyz")
}
#[test]
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.");
}
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,
}
}
#[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");
}
#[test]
fn test_stock_string_repl_str() {
let t = dummy_context();

View File

@@ -14,27 +14,24 @@ if __name__ == "__main__":
s = re.sub(r"(?m)///.*$", "", s) # remove comments
unsafe = s.count("unsafe")
free = s.count("free(")
unsafe_fn = s.count("unsafe fn")
gotoblocks = s.count("ok_to_continue") + s.count('OK_TO_CONTINUE')
chars = s.count("c_char") + s.count("CStr")
libc = s.count("libc")
filestats.append((fn, unsafe, free, unsafe_fn, chars, libc))
filestats.append((fn, unsafe, free, gotoblocks, chars))
sum_unsafe, sum_free, sum_unsafe_fn, sum_chars, sum_libc = 0, 0, 0, 0, 0
sum_unsafe, sum_free, sum_gotoblocks, sum_chars = 0, 0, 0, 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:
for fn, unsafe, free, gotoblocks, chars in reversed(sorted(filestats, key=lambda x: sum(x[1:]))):
if unsafe + free + gotoblocks + chars == 0:
continue
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))
print("{0: <25} unsafe: {1: >3} free: {2: >3} ok_to_cont: {3: >3} chars: {4: >3}".format(str(fn), unsafe, free, gotoblocks, chars))
sum_unsafe += unsafe
sum_free += free
sum_unsafe_fn += unsafe_fn
sum_gotoblocks += gotoblocks
sum_chars += chars
sum_libc += libc
print()
print("total unsafe:", sum_unsafe)
print("total free:", sum_free)
print("total unsafe-fn:", sum_unsafe_fn)
print("total ok_to_continue:", sum_gotoblocks)
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 fn mailmime_find_mailimf_fields(mime: *mut Mailmime) -> *mut mailimf_fields {
pub unsafe fn mailmime_find_mailimf_fields(mime: *mut Mailmime) -> *mut mailimf_fields {
if mime.is_null() {
return ptr::null_mut();
}
match unsafe { (*mime).mm_type as _ } {
match (*mime).mm_type as _ {
MAILMIME_MULTIPLE => {
for cur_data in unsafe { (*(*mime).mm_data.mm_multipart.mm_mp_list).into_iter() } {
for cur_data in (*(*mime).mm_data.mm_multipart.mm_mp_list).into_iter() {
let header = mailmime_find_mailimf_fields(cur_data as *mut _);
if !header.is_null() {
return header;
}
}
}
MAILMIME_MESSAGE => return unsafe { (*mime).mm_data.mm_message.mm_fields },
MAILMIME_MESSAGE => return (*mime).mm_data.mm_message.mm_fields,
_ => {}
}

View File

@@ -1,15 +1,18 @@
//! 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
@@ -47,6 +50,135 @@ 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");
@@ -100,11 +232,11 @@ unsafe fn stress_functions(context: &Context) {
#[test]
#[ignore] // is too expensive
fn test_encryption_decryption() {
let (public_key, private_key) = pgp::create_keypair("foo@bar.de").unwrap();
let (public_key, private_key) = dc_pgp_create_keypair("foo@bar.de").unwrap();
private_key.split_key().unwrap();
let (public_key2, private_key2) = pgp::create_keypair("two@zwo.de").unwrap();
let (public_key2, private_key2) = dc_pgp_create_keypair("two@zwo.de").unwrap();
assert_ne!(public_key, public_key2);
@@ -113,11 +245,11 @@ fn test_encryption_decryption() {
keyring.add_owned(public_key.clone());
keyring.add_ref(&public_key2);
let ctext_signed = pgp::pk_encrypt(original_text, &keyring, Some(&private_key)).unwrap();
let ctext_signed = dc_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 = pgp::pk_encrypt(original_text, &keyring, None).unwrap();
let ctext_unsigned = dc_pgp_pk_encrypt(original_text, &keyring, None).unwrap();
assert!(!ctext_unsigned.is_empty());
assert!(ctext_unsigned.starts_with("-----BEGIN PGP MESSAGE-----"));
@@ -132,7 +264,7 @@ fn test_encryption_decryption() {
let mut valid_signatures: HashSet<String> = Default::default();
let plain = pgp::pk_decrypt(
let plain = dc_pgp_pk_decrypt(
ctext_signed.as_bytes(),
&keyring,
&public_keyring,
@@ -146,7 +278,7 @@ fn test_encryption_decryption() {
valid_signatures.clear();
let empty_keyring = Keyring::default();
let plain = pgp::pk_decrypt(
let plain = dc_pgp_pk_decrypt(
ctext_signed.as_bytes(),
&keyring,
&empty_keyring,
@@ -158,7 +290,7 @@ fn test_encryption_decryption() {
valid_signatures.clear();
let plain = pgp::pk_decrypt(
let plain = dc_pgp_pk_decrypt(
ctext_signed.as_bytes(),
&keyring,
&public_keyring2,
@@ -172,7 +304,7 @@ fn test_encryption_decryption() {
public_keyring2.add_ref(&public_key);
let plain = pgp::pk_decrypt(
let plain = dc_pgp_pk_decrypt(
ctext_signed.as_bytes(),
&keyring,
&public_keyring2,
@@ -184,7 +316,7 @@ fn test_encryption_decryption() {
valid_signatures.clear();
let plain = pgp::pk_decrypt(
let plain = dc_pgp_pk_decrypt(
ctext_unsigned.as_bytes(),
&keyring,
&public_keyring,
@@ -201,7 +333,8 @@ fn test_encryption_decryption() {
let mut public_keyring = Keyring::default();
public_keyring.add_ref(&public_key);
let plain = pgp::pk_decrypt(ctext_signed.as_bytes(), &keyring, &public_keyring, None).unwrap();
let plain =
dc_pgp_pk_decrypt(ctext_signed.as_bytes(), &keyring, &public_keyring, None).unwrap();
assert_eq!(plain, original_text);
}