mirror of
https://github.com/chatmail/core.git
synced 2026-04-03 05:52:10 +03:00
Compare commits
2 Commits
chatlist-d
...
fix597
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
af7934d26f | ||
|
|
233d72516e |
@@ -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:
|
||||
|
||||
24
CHANGELOG.md
24
CHANGELOG.md
@@ -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
|
||||
|
||||
489
Cargo.lock
generated
489
Cargo.lock
generated
File diff suppressed because it is too large
Load Diff
10
Cargo.toml
10
Cargo.toml
@@ -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/dignifiedquire/rust-imap", branch = "fix/oauth-response" }
|
||||
lettre = "0.9.0"
|
||||
imap = { git = "https://github.com/jonhoo/rust-imap", rev = "281d2eb8ab50dc656ceff2ae749ca5045f334e15" }
|
||||
base64 = "0.10"
|
||||
charset = "0.1"
|
||||
percent-encoding = "2.0"
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -1,7 +0,0 @@
|
||||
#!/usr/bin/env bash
|
||||
|
||||
set -ex
|
||||
|
||||
cd deltachat-ffi
|
||||
doxygen
|
||||
|
||||
@@ -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"
|
||||
|
||||
@@ -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()
|
||||
@@ -861,7 +847,6 @@ void dc_maybe_network (dc_context_t* context);
|
||||
#define DC_GCL_ARCHIVED_ONLY 0x01
|
||||
#define DC_GCL_NO_SPECIALS 0x02
|
||||
#define DC_GCL_ADD_ALLDONE_HINT 0x04
|
||||
#define DC_GCL_ADD_DRAFTS 0x08
|
||||
|
||||
|
||||
/**
|
||||
@@ -905,12 +890,6 @@ void dc_maybe_network (dc_context_t* context);
|
||||
* not needed when DC_GCL_ARCHIVED_ONLY is already set)
|
||||
* - if the flag DC_GCL_ADD_ALLDONE_HINT is set, DC_CHAT_ID_ALLDONE_HINT
|
||||
* is added as needed.
|
||||
* - if the flag DC_GCL_ADD_DRAFTS is set
|
||||
* and a chat has a recent draft,
|
||||
* the draft is returned by dc_chatlist_get_msg_id().
|
||||
* if the chatlist is shown permanently eg. on desktop,
|
||||
* you may not want to add this flags
|
||||
* as the resorting on dc_set_draft() is easily confusing.
|
||||
* @param query_str An optional query for filtering the list. Only chats matching this query
|
||||
* are returned. Give NULL for no filtering.
|
||||
* @param query_id An optional contact ID for filtering the list. Only chats including this contact ID
|
||||
@@ -1486,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);
|
||||
|
||||
@@ -1500,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.
|
||||
@@ -1741,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);
|
||||
|
||||
@@ -1877,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);
|
||||
@@ -1926,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);
|
||||
@@ -2031,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);
|
||||
|
||||
@@ -2201,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
|
||||
*
|
||||
@@ -2356,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);
|
||||
|
||||
@@ -2611,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);
|
||||
|
||||
@@ -2624,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);
|
||||
|
||||
@@ -2640,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);
|
||||
|
||||
@@ -2944,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);
|
||||
|
||||
@@ -2958,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);
|
||||
|
||||
@@ -2973,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);
|
||||
|
||||
@@ -2985,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);
|
||||
|
||||
@@ -3094,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);
|
||||
@@ -3240,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);
|
||||
|
||||
@@ -3397,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);
|
||||
|
||||
@@ -3412,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);
|
||||
|
||||
@@ -3427,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);
|
||||
|
||||
@@ -3444,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);
|
||||
|
||||
@@ -3457,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);
|
||||
|
||||
@@ -3472,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);
|
||||
|
||||
@@ -3553,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);
|
||||
|
||||
@@ -3565,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);
|
||||
|
||||
@@ -3578,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);
|
||||
|
||||
@@ -3590,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);
|
||||
|
||||
@@ -3652,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);
|
||||
|
||||
@@ -3663,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);
|
||||
|
||||
@@ -3687,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);
|
||||
@@ -3709,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);
|
||||
@@ -3761,14 +3719,6 @@ int64_t dc_lot_get_timestamp (const dc_lot_t* lot);
|
||||
#define DC_MSG_GIF 21
|
||||
|
||||
|
||||
/**
|
||||
* Message containing a sticker, similar to image.
|
||||
* If possible, the ui should display the image without borders in a transparent way.
|
||||
* A click on a sticker will offer to install the sticker set in some future.
|
||||
*/
|
||||
#define DC_MSG_STICKER 23
|
||||
|
||||
|
||||
/**
|
||||
* Message containing an Audio file.
|
||||
* File and duration are set via dc_msg_set_file(), dc_msg_set_duration()
|
||||
@@ -3909,7 +3859,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
|
||||
@@ -3920,7 +3870,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
|
||||
@@ -3931,7 +3881,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
|
||||
@@ -3941,50 +3891,11 @@ 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
|
||||
|
||||
/**
|
||||
* Emitted when a message was successfully marked as deleted on the IMAP server.
|
||||
*
|
||||
* @param data1 0
|
||||
* @param data2 (const char*) Info string in english language.
|
||||
* Must not be unref'd or modified and is valid only until the callback returns.
|
||||
* @return 0
|
||||
*/
|
||||
#define DC_EVENT_IMAP_MESSAGE_DELETED 104
|
||||
|
||||
/**
|
||||
* Emitted when a message was successfully moved on IMAP.
|
||||
*
|
||||
* @param data1 0
|
||||
* @param data2 (const char*) Info string in english language.
|
||||
* Must not be unref'd or modified and is valid only until the callback returns.
|
||||
* @return 0
|
||||
*/
|
||||
#define DC_EVENT_IMAP_MESSAGE_MOVED 105
|
||||
|
||||
/**
|
||||
* Emitted when a new blob file was successfully written
|
||||
*
|
||||
* @param data1 0
|
||||
* @param data2 (const char*) path name
|
||||
* Must not be unref'd or modified and is valid only until the callback returns.
|
||||
* @return 0
|
||||
*/
|
||||
#define DC_EVENT_NEW_BLOB_FILE 150
|
||||
|
||||
/**
|
||||
* Emitted when a blob file was successfully deleted
|
||||
*
|
||||
* @param data1 0
|
||||
* @param data2 (const char*) path name
|
||||
* Must not be unref'd or modified and is valid only until the callback returns.
|
||||
* @return 0
|
||||
*/
|
||||
#define DC_EVENT_DELETED_BLOB_FILE 151
|
||||
|
||||
/**
|
||||
* The library-user should write a warning string to the log.
|
||||
@@ -3994,7 +3905,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
|
||||
@@ -4014,10 +3925,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
|
||||
@@ -4041,7 +3951,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
|
||||
@@ -4056,7 +3966,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
|
||||
*/
|
||||
@@ -4187,7 +4097,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
|
||||
*/
|
||||
@@ -4228,21 +4138,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
|
||||
|
||||
@@ -4344,8 +4269,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*);
|
||||
|
||||
|
||||
/*
|
||||
* @}
|
||||
|
||||
@@ -25,9 +25,8 @@ 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,
|
||||
as_path, as_str, dc_strdup, to_string, to_string_lossy, OsStrExt, StrExt,
|
||||
};
|
||||
use deltachat::stock::StockMessage;
|
||||
use deltachat::*;
|
||||
|
||||
// as C lacks a good and portable error handling,
|
||||
@@ -126,15 +125,11 @@ impl ContextWrapper {
|
||||
| Event::SmtpConnected(msg)
|
||||
| Event::ImapConnected(msg)
|
||||
| Event::SmtpMessageSent(msg)
|
||||
| Event::ImapMessageDeleted(msg)
|
||||
| Event::ImapMessageMoved(msg)
|
||||
| Event::NewBlobFile(msg)
|
||||
| Event::DeletedBlobFile(msg)
|
||||
| Event::Warning(msg)
|
||||
| Event::Error(msg)
|
||||
| Event::ErrorNetwork(msg)
|
||||
| Event::ErrorSelfNotInGroup(msg) => {
|
||||
let data2 = CString::new(msg).unwrap_or_default();
|
||||
let data2 = CString::new(msg).unwrap();
|
||||
ffi_cb(self, event_id, 0, data2.as_ptr() as uintptr_t)
|
||||
}
|
||||
Event::MsgsChanged { chat_id, msg_id }
|
||||
@@ -153,7 +148,7 @@ impl ContextWrapper {
|
||||
ffi_cb(self, event_id, progress as uintptr_t, 0)
|
||||
}
|
||||
Event::ImexFileWritten(file) => {
|
||||
let data1 = file.to_c_string().unwrap_or_default();
|
||||
let data1 = file.to_c_string().unwrap();
|
||||
ffi_cb(self, event_id, data1.as_ptr() as uintptr_t, 0)
|
||||
}
|
||||
Event::SecurejoinInviterProgress {
|
||||
@@ -169,6 +164,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 +306,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 +329,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 +340,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() {
|
||||
@@ -408,8 +377,8 @@ pub unsafe extern "C" fn dc_get_oauth2_url(
|
||||
return ptr::null_mut(); // NULL explicitly defined as "unknown"
|
||||
}
|
||||
let ffi_context = &*context;
|
||||
let addr = to_string_lossy(addr);
|
||||
let redirect = to_string_lossy(redirect);
|
||||
let addr = to_string(addr);
|
||||
let redirect = to_string(redirect);
|
||||
ffi_context
|
||||
.with_inner(|ctx| match oauth2::dc_get_oauth2_url(ctx, addr, redirect) {
|
||||
Some(res) => res.strdup(),
|
||||
@@ -651,24 +620,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 +1026,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 +1068,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 +1140,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 +1160,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)
|
||||
}
|
||||
@@ -1272,11 +1245,8 @@ pub unsafe extern "C" fn dc_forward_msgs(
|
||||
|
||||
let ffi_context = &*context;
|
||||
ffi_context
|
||||
.with_inner(|ctx| {
|
||||
chat::forward_msgs(ctx, ids, chat_id)
|
||||
.unwrap_or_log_default(ctx, "Failed to forward message")
|
||||
})
|
||||
.unwrap_or_default()
|
||||
.with_inner(|ctx| chat::forward_msgs(ctx, ids, chat_id))
|
||||
.unwrap_or(())
|
||||
}
|
||||
|
||||
#[no_mangle]
|
||||
@@ -1358,7 +1328,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 +1342,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 +1357,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 +1378,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 +1397,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 +1538,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 +1553,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 +1596,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 +1615,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 +1632,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 +1668,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 +1717,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 +2131,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().to_string().strdup(),
|
||||
None => ptr::null_mut(),
|
||||
})
|
||||
.unwrap_or_else(|_| ptr::null_mut())
|
||||
@@ -2502,7 +2476,7 @@ pub unsafe extern "C" fn dc_msg_get_summarytext(
|
||||
.with_inner(|ctx| {
|
||||
ffi_msg
|
||||
.message
|
||||
.get_summarytext(ctx, approx_characters.try_into().unwrap_or_default())
|
||||
.get_summarytext(ctx, approx_characters.try_into().unwrap())
|
||||
})
|
||||
.unwrap_or_default()
|
||||
.strdup()
|
||||
@@ -2609,7 +2583,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 +2598,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 +2768,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().to_string().strdup())
|
||||
.unwrap_or_else(|| std::ptr::null_mut())
|
||||
})
|
||||
.unwrap_or_else(|_| ptr::null_mut())
|
||||
@@ -2921,6 +2893,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> {
|
||||
|
||||
@@ -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::*;
|
||||
@@ -118,13 +119,13 @@ fn poke_spec(context: &Context, spec: *const libc::c_char) -> libc::c_int {
|
||||
|
||||
/* if `spec` is given, remember it for later usage; if it is not given, try to use the last one */
|
||||
if !spec.is_null() {
|
||||
real_spec = to_string_lossy(spec);
|
||||
real_spec = to_string(spec);
|
||||
context
|
||||
.sql
|
||||
.set_raw_config(context, "import_spec", Some(&real_spec))
|
||||
.set_config(context, "import_spec", Some(&real_spec))
|
||||
.unwrap();
|
||||
} else {
|
||||
let rs = context.sql.get_raw_config(context, "import_spec");
|
||||
let rs = context.sql.get_config(context, "import_spec");
|
||||
if rs.is_none() {
|
||||
error!(context, "Import: No file or folder given.");
|
||||
return 0;
|
||||
@@ -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.");
|
||||
@@ -868,7 +864,7 @@ pub unsafe fn dc_cmdline(context: &Context, line: &str) -> Result<(), failure::E
|
||||
let mut msg_ids = [0; 1];
|
||||
let chat_id = arg2.parse()?;
|
||||
msg_ids[0] = arg1.parse()?;
|
||||
chat::forward_msgs(context, &msg_ids, chat_id)?;
|
||||
chat::forward_msgs(context, &msg_ids, chat_id);
|
||||
}
|
||||
"markseen" => {
|
||||
ensure!(!arg1.is_empty(), "Argument <msg-id> missing.");
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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");
|
||||
}
|
||||
|
||||
@@ -17,12 +17,12 @@ pub unsafe fn charconv(
|
||||
assert!(!fromcode.is_null(), "invalid fromcode");
|
||||
assert!(!s.is_null(), "invalid input string");
|
||||
if let Some(encoding) =
|
||||
charset::Charset::for_label(CStr::from_ptr(fromcode).to_string_lossy().as_bytes())
|
||||
charset::Charset::for_label(CStr::from_ptr(fromcode).to_str().unwrap().as_bytes())
|
||||
{
|
||||
let data = std::slice::from_raw_parts(s as *const u8, strlen(s));
|
||||
|
||||
let (res, _, _) = encoding.decode(data);
|
||||
let res_c = CString::new(res.as_bytes()).unwrap_or_default();
|
||||
let res_c = CString::new(res.as_bytes()).unwrap();
|
||||
*result = strdup(res_c.as_ptr()) as *mut _;
|
||||
|
||||
MAIL_CHARCONV_NO_ERROR as libc::c_int
|
||||
|
||||
@@ -123,7 +123,7 @@ unsafe fn display_mime_discrete_type(mut discrete_type: *mut mailmime_discrete_t
|
||||
_ => {}
|
||||
};
|
||||
}
|
||||
pub unsafe fn display_mime_data(mut data: *mut mailmime_data) {
|
||||
unsafe fn display_mime_data(mut data: *mut mailmime_data) {
|
||||
match (*data).dt_type {
|
||||
0 => {
|
||||
println!(
|
||||
|
||||
@@ -1598,7 +1598,7 @@ unsafe fn mailimf_date_time_write_driver(
|
||||
(*date_time).dt_sec,
|
||||
(*date_time).dt_zone,
|
||||
);
|
||||
let date_str_c = std::ffi::CString::new(date_str).unwrap_or_default();
|
||||
let date_str_c = std::ffi::CString::new(date_str).unwrap();
|
||||
let r = mailimf_string_write_driver(
|
||||
do_write,
|
||||
data,
|
||||
|
||||
@@ -848,7 +848,7 @@ pub unsafe fn mailmime_generate_boundary() -> *mut libc::c_char {
|
||||
hex::encode(&std::process::id().to_le_bytes()[..2])
|
||||
);
|
||||
|
||||
let c = std::ffi::CString::new(raw).unwrap_or_default();
|
||||
let c = std::ffi::CString::new(raw).unwrap();
|
||||
strdup(c.as_ptr())
|
||||
}
|
||||
|
||||
|
||||
@@ -338,7 +338,7 @@ unsafe fn mailmime_disposition_param_write_driver(
|
||||
4 => {
|
||||
let value = (*param).pa_data.pa_size as u32;
|
||||
let raw = format!("{}", value);
|
||||
let raw_c = std::ffi::CString::new(raw).unwrap_or_default();
|
||||
let raw_c = std::ffi::CString::new(raw).unwrap();
|
||||
sizestr = strdup(raw_c.as_ptr());
|
||||
len = strlen(b"size=\x00" as *const u8 as *const libc::c_char)
|
||||
.wrapping_add(strlen(sizestr))
|
||||
@@ -542,7 +542,7 @@ unsafe fn mailmime_version_write_driver(
|
||||
}
|
||||
|
||||
let raw = format!("{}.{}", (version >> 16) as i32, (version & 0xffff) as i32);
|
||||
let raw_c = std::ffi::CString::new(raw).unwrap_or_default();
|
||||
let raw_c = std::ffi::CString::new(raw).unwrap();
|
||||
let mut versionstr = strdup(raw_c.as_ptr());
|
||||
r = mailimf_string_write_driver(do_write, data, col, versionstr, strlen(versionstr));
|
||||
if r != MAILIMF_NO_ERROR as libc::c_int {
|
||||
@@ -1515,8 +1515,8 @@ pub unsafe fn mailmime_data_write_driver(
|
||||
}
|
||||
1 => {
|
||||
let filename = CStr::from_ptr((*mime_data).dt_data.dt_filename)
|
||||
.to_string_lossy()
|
||||
.into_owned();
|
||||
.to_str()
|
||||
.unwrap();
|
||||
if let Ok(file) = std::fs::File::open(filename) {
|
||||
if let Ok(mut text) = memmap::MmapOptions::new().map_copy(&file) {
|
||||
if 0 != (*mime_data).dt_encoded {
|
||||
@@ -1797,7 +1797,7 @@ pub unsafe fn mailmime_quoted_printable_write_driver(
|
||||
start = text.offset(i as isize).offset(1isize);
|
||||
|
||||
let raw = format!("={:02X}", (ch as libc::c_int));
|
||||
let raw_c = std::ffi::CString::new(raw).unwrap_or_default();
|
||||
let raw_c = std::ffi::CString::new(raw).unwrap();
|
||||
let mut hexstr = strdup(raw_c.as_ptr());
|
||||
r = mailimf_string_write_driver(
|
||||
do_write,
|
||||
@@ -1822,7 +1822,7 @@ pub unsafe fn mailmime_quoted_printable_write_driver(
|
||||
}
|
||||
start = text.offset(i as isize).offset(1isize);
|
||||
let raw = format!("={:02X}", ch as libc::c_int);
|
||||
let raw_c = std::ffi::CString::new(raw).unwrap_or_default();
|
||||
let raw_c = std::ffi::CString::new(raw).unwrap();
|
||||
let mut hexstr = strdup(raw_c.as_ptr());
|
||||
r = mailimf_string_write_driver(
|
||||
do_write,
|
||||
@@ -1866,7 +1866,7 @@ pub unsafe fn mailmime_quoted_printable_write_driver(
|
||||
}
|
||||
start = text.offset(i as isize);
|
||||
let raw = format!("={:02X}", b'\r' as i32);
|
||||
let raw_c = std::ffi::CString::new(raw).unwrap_or_default();
|
||||
let raw_c = std::ffi::CString::new(raw).unwrap();
|
||||
let mut hexstr = strdup(raw_c.as_ptr());
|
||||
r = mailimf_string_write_driver(do_write, data, col, hexstr, 3i32 as size_t);
|
||||
if r != MAILIMF_NO_ERROR as libc::c_int {
|
||||
@@ -1890,7 +1890,7 @@ pub unsafe fn mailmime_quoted_printable_write_driver(
|
||||
"={:02X}\r\n",
|
||||
*text.offset(i.wrapping_sub(1i32 as libc::size_t) as isize) as libc::c_int
|
||||
);
|
||||
let raw_c = std::ffi::CString::new(raw).unwrap_or_default();
|
||||
let raw_c = std::ffi::CString::new(raw).unwrap();
|
||||
let mut hexstr = strdup(raw_c.as_ptr());
|
||||
|
||||
r = mailimf_string_write_driver(do_write, data, col, hexstr, strlen(hexstr));
|
||||
@@ -1917,7 +1917,7 @@ pub unsafe fn mailmime_quoted_printable_write_driver(
|
||||
"={:02X}\r\n",
|
||||
*text.offset(i.wrapping_sub(2i32 as libc::size_t) as isize) as libc::c_int
|
||||
);
|
||||
let raw_c = std::ffi::CString::new(raw).unwrap_or_default();
|
||||
let raw_c = std::ffi::CString::new(raw).unwrap();
|
||||
let mut hexstr = strdup(raw_c.as_ptr());
|
||||
|
||||
r = mailimf_string_write_driver(do_write, data, col, hexstr, strlen(hexstr));
|
||||
@@ -1938,7 +1938,7 @@ pub unsafe fn mailmime_quoted_printable_write_driver(
|
||||
(*text.offset(i.wrapping_sub(2i32 as libc::size_t) as isize) as u8 as char),
|
||||
b'\r' as i32
|
||||
);
|
||||
let raw_c = std::ffi::CString::new(raw).unwrap_or_default();
|
||||
let raw_c = std::ffi::CString::new(raw).unwrap();
|
||||
let mut hexstr = strdup(raw_c.as_ptr());
|
||||
|
||||
r = mailimf_string_write_driver(do_write, data, col, hexstr, strlen(hexstr));
|
||||
|
||||
@@ -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]
|
||||
|
||||
@@ -97,7 +97,7 @@ If you want to run "liveconfig" functional tests you can set
|
||||
chat devs.
|
||||
|
||||
- or the path of a file that contains two lines, each describing
|
||||
via "addr=... mail_pw=..." a test account login that will
|
||||
via "addr=... mail_pwd=..." a test account login that will
|
||||
be used for the live tests.
|
||||
|
||||
With ``DCC_PY_LIVECONFIG`` set pytest invocations will use real
|
||||
|
||||
1
python/doc/_templates/globaltoc.html
vendored
1
python/doc/_templates/globaltoc.html
vendored
@@ -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>
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
@@ -592,11 +574,11 @@ class EventLogger:
|
||||
else:
|
||||
assert not rex.match(ev[0]), "event found {}".format(ev)
|
||||
|
||||
def get_matching(self, event_name_regex, check_error=True, timeout=None):
|
||||
def get_matching(self, event_name_regex, check_error=True):
|
||||
self._log("-- waiting for event with regex: {} --".format(event_name_regex))
|
||||
rex = re.compile("(?:{}).*".format(event_name_regex))
|
||||
while 1:
|
||||
ev = self.get(timeout=timeout, check_error=check_error)
|
||||
ev = self.get()
|
||||
if rex.match(ev[0]):
|
||||
return ev
|
||||
|
||||
|
||||
@@ -8,10 +8,12 @@ from os.path import join as joinpath
|
||||
# this works well when you in a git-checkout
|
||||
# run "python deltachat/const.py" to regenerate events
|
||||
# begin const generated
|
||||
DC_PROVIDER_STATUS_OK = 1
|
||||
DC_PROVIDER_STATUS_PREPARATION = 2
|
||||
DC_PROVIDER_STATUS_BROKEN = 3
|
||||
DC_GCL_ARCHIVED_ONLY = 0x01
|
||||
DC_GCL_NO_SPECIALS = 0x02
|
||||
DC_GCL_ADD_ALLDONE_HINT = 0x04
|
||||
DC_GCL_ADD_DRAFTS = 0x08
|
||||
DC_GCL_VERIFIED_ONLY = 0x01
|
||||
DC_GCL_ADD_SELF = 0x02
|
||||
DC_QR_ASK_VERIFYCONTACT = 200
|
||||
@@ -53,27 +55,14 @@ DC_CONTACT_ID_LAST_SPECIAL = 9
|
||||
DC_MSG_TEXT = 10
|
||||
DC_MSG_IMAGE = 20
|
||||
DC_MSG_GIF = 21
|
||||
DC_MSG_STICKER = 23
|
||||
DC_MSG_AUDIO = 40
|
||||
DC_MSG_VOICE = 41
|
||||
DC_MSG_VIDEO = 50
|
||||
DC_MSG_FILE = 60
|
||||
DC_LP_AUTH_OAUTH2 = 0x2
|
||||
DC_LP_AUTH_NORMAL = 0x4
|
||||
DC_LP_IMAP_SOCKET_STARTTLS = 0x100
|
||||
DC_LP_IMAP_SOCKET_SSL = 0x200
|
||||
DC_LP_IMAP_SOCKET_PLAIN = 0x400
|
||||
DC_LP_SMTP_SOCKET_STARTTLS = 0x10000
|
||||
DC_LP_SMTP_SOCKET_SSL = 0x20000
|
||||
DC_LP_SMTP_SOCKET_PLAIN = 0x40000
|
||||
DC_EVENT_INFO = 100
|
||||
DC_EVENT_SMTP_CONNECTED = 101
|
||||
DC_EVENT_IMAP_CONNECTED = 102
|
||||
DC_EVENT_SMTP_MESSAGE_SENT = 103
|
||||
DC_EVENT_IMAP_MESSAGE_DELETED = 104
|
||||
DC_EVENT_IMAP_MESSAGE_MOVED = 105
|
||||
DC_EVENT_NEW_BLOB_FILE = 150
|
||||
DC_EVENT_DELETED_BLOB_FILE = 151
|
||||
DC_EVENT_WARNING = 300
|
||||
DC_EVENT_ERROR = 400
|
||||
DC_EVENT_ERROR_NETWORK = 401
|
||||
@@ -91,64 +80,14 @@ 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)
|
||||
|
||||
@@ -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")
|
||||
@@ -162,11 +157,6 @@ def acfactory(pytestconfig, tmpdir, request, session_liveconfig):
|
||||
self.live_count += 1
|
||||
if "e2ee_enabled" not in configdict:
|
||||
configdict["e2ee_enabled"] = "1"
|
||||
|
||||
# Enable strict certificate checks for online accounts
|
||||
configdict["imap_certificate_checks"] = "1"
|
||||
configdict["smtp_certificate_checks"] = "1"
|
||||
|
||||
tmpdb = tmpdir.join("livedb%d" % self.live_count)
|
||||
ac = self.make_account(tmpdb.strpath, logid="ac{}".format(self.live_count))
|
||||
ac._evlogger.init_time = self.init_time
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
from __future__ import print_function
|
||||
import pytest
|
||||
import os
|
||||
import queue
|
||||
from deltachat import const, Account
|
||||
from deltachat.message import Message
|
||||
from datetime import datetime, timedelta
|
||||
@@ -20,7 +19,6 @@ class TestOfflineAccountBasic:
|
||||
d = ac1.get_info()
|
||||
assert d["arch"]
|
||||
assert d["number_of_chats"] == "0"
|
||||
assert d["bcc_self"] == "1"
|
||||
|
||||
def test_is_not_configured(self, acfactory):
|
||||
ac1 = acfactory.get_unconfigured_account()
|
||||
@@ -39,11 +37,6 @@ class TestOfflineAccountBasic:
|
||||
ac1 = acfactory.get_unconfigured_account()
|
||||
assert "save_mime_headers" in ac1.get_config("sys.config_keys").split()
|
||||
|
||||
def test_has_bccself(self, acfactory):
|
||||
ac1 = acfactory.get_unconfigured_account()
|
||||
assert "bcc_self" in ac1.get_config("sys.config_keys").split()
|
||||
assert ac1.get_config("bcc_self") == "1"
|
||||
|
||||
def test_selfcontact_if_unconfigured(self, acfactory):
|
||||
ac1 = acfactory.get_unconfigured_account()
|
||||
with pytest.raises(ValueError):
|
||||
@@ -100,7 +93,7 @@ class TestOfflineContact:
|
||||
ac1 = acfactory.get_configured_offline_account()
|
||||
contact1 = ac1.create_contact(email="some1@example.com", name="some1")
|
||||
chat = ac1.create_chat_by_contact(contact1)
|
||||
msg = chat.send_text("one message")
|
||||
msg = chat.send_text("one messae")
|
||||
assert not ac1.delete_contact(contact1)
|
||||
assert not msg.filemime
|
||||
|
||||
@@ -148,27 +141,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()
|
||||
@@ -250,9 +222,7 @@ class TestOfflineChat:
|
||||
chat1.send_image(path="notexists")
|
||||
fn = data.get_path("d.png")
|
||||
lp.sec("sending image")
|
||||
chat1.account._evlogger.consume_events()
|
||||
msg = chat1.send_image(fn)
|
||||
chat1.account._evlogger.get_matching("DC_EVENT_NEW_BLOB_FILE")
|
||||
assert msg.is_image()
|
||||
assert msg
|
||||
assert msg.id > 0
|
||||
@@ -371,12 +341,6 @@ class TestOnlineAccount:
|
||||
assert chat.id > const.DC_CHAT_ID_LAST_SPECIAL
|
||||
return chat
|
||||
|
||||
def test_configure_canceled(self, acfactory):
|
||||
ac1 = acfactory.get_online_configuring_account()
|
||||
wait_configuration_progress(ac1, 200)
|
||||
ac1.stop_ongoing()
|
||||
wait_configuration_progress(ac1, 0, 0)
|
||||
|
||||
def test_export_import_self_keys(self, acfactory, tmpdir):
|
||||
ac1, ac2 = acfactory.get_two_online_accounts()
|
||||
dir = tmpdir.mkdir("exportdir")
|
||||
@@ -387,35 +351,18 @@ class TestOnlineAccount:
|
||||
ac1._evlogger.consume_events()
|
||||
ac1.import_self_keys(dir.strpath)
|
||||
|
||||
def test_one_account_send_bcc_setting(self, acfactory, lp):
|
||||
def test_one_account_send(self, acfactory):
|
||||
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=ac1.get_config("addr"))
|
||||
chat = ac1.create_chat_by_contact(c2)
|
||||
assert chat.id > const.DC_CHAT_ID_LAST_SPECIAL
|
||||
wait_successful_IMAP_SMTP_connection(ac1)
|
||||
wait_configuration_progress(ac1, 1000)
|
||||
|
||||
lp.sec("send out message with bcc to ourselves")
|
||||
msg_out = chat.send_text("message2")
|
||||
ev = ac1._evlogger.get_matching("DC_EVENT_MSGS_CHANGED")
|
||||
assert ev[2] == msg_out.id
|
||||
# wait for send out (BCC)
|
||||
assert ac1.get_config("bcc_self") == "1"
|
||||
self_addr = ac1.get_config("addr")
|
||||
ev = ac1._evlogger.get_matching("DC_EVENT_SMTP_MESSAGE_SENT")
|
||||
assert self_addr in ev[2]
|
||||
ev = ac1._evlogger.get_matching("DC_EVENT_DELETED_BLOB_FILE")
|
||||
|
||||
ac1._evlogger.consume_events()
|
||||
lp.sec("send out message without bcc")
|
||||
ac1.set_config("bcc_self", "0")
|
||||
msg_out = chat.send_text("message3")
|
||||
ev = ac1._evlogger.get_matching("DC_EVENT_MSGS_CHANGED")
|
||||
assert ev[2] == msg_out.id
|
||||
ev = ac1._evlogger.get_matching("DC_EVENT_SMTP_MESSAGE_SENT")
|
||||
assert self_addr not in ev[2]
|
||||
ev = ac1._evlogger.get_matching("DC_EVENT_DELETED_BLOB_FILE")
|
||||
# wait for own account to receive
|
||||
ev = ac1._evlogger.get_matching("DC_EVENT_INCOMING_MSG|DC_EVENT_MSGS_CHANGED")
|
||||
assert ev[1] == msg_out.id
|
||||
|
||||
def test_mvbox_sentbox_threads(self, acfactory):
|
||||
ac1 = acfactory.get_online_configuring_account(mvbox=True, sentbox=True)
|
||||
@@ -427,17 +374,6 @@ class TestOnlineAccount:
|
||||
ev = ac2._evlogger.get_matching("DC_EVENT_INCOMING_MSG|DC_EVENT_MSGS_CHANGED")
|
||||
assert ev[2] > const.DC_CHAT_ID_LAST_SPECIAL
|
||||
|
||||
def test_move_works(self, acfactory):
|
||||
ac1 = acfactory.get_online_configuring_account()
|
||||
ac2 = acfactory.get_online_configuring_account(mvbox=True)
|
||||
wait_configuration_progress(ac2, 1000)
|
||||
wait_configuration_progress(ac1, 1000)
|
||||
chat = self.get_chat(ac1, ac2)
|
||||
chat.send_text("message1")
|
||||
ev = ac2._evlogger.get_matching("DC_EVENT_INCOMING_MSG|DC_EVENT_MSGS_CHANGED")
|
||||
assert ev[2] > const.DC_CHAT_ID_LAST_SPECIAL
|
||||
ev = ac2._evlogger.get_matching("DC_EVENT_IMAP_MESSAGE_MOVED")
|
||||
|
||||
def test_forward_messages(self, acfactory):
|
||||
ac1, ac2 = acfactory.get_two_online_accounts()
|
||||
chat = self.get_chat(ac1, ac2)
|
||||
@@ -508,14 +444,6 @@ class TestOnlineAccount:
|
||||
lp.step("2")
|
||||
assert msg_out.is_out_mdn_received()
|
||||
|
||||
lp.sec("check that a second call to mark_seen does not create change or smtp job")
|
||||
ac2._evlogger.consume_events()
|
||||
ac2.mark_seen_messages([msg_in])
|
||||
try:
|
||||
ac2._evlogger.get_matching("DC_EVENT_MSG_READ", timeout=0.01)
|
||||
except queue.Empty:
|
||||
pass # mark_seen_messages() has generated events before it returns
|
||||
|
||||
def test_send_and_receive_will_encrypt_decrypt(self, acfactory, lp):
|
||||
ac1, ac2 = acfactory.get_two_online_accounts()
|
||||
|
||||
@@ -544,14 +472,6 @@ class TestOnlineAccount:
|
||||
assert msg_back.text == "message-back"
|
||||
assert msg_back.is_encrypted()
|
||||
|
||||
lp.sec("create group chat with two members, one of which has no encrypt state")
|
||||
chat = ac1.create_group_chat("encryption test")
|
||||
chat.add_contact(ac1.create_contact(ac2.get_config("addr")))
|
||||
chat.add_contact(ac1.create_contact("notexisting@testrun.org"))
|
||||
msg = chat.send_text("test not encrypt")
|
||||
ev = ac1._evlogger.get_matching("DC_EVENT_SMTP_MESSAGE_SENT")
|
||||
assert not msg.is_encrypted()
|
||||
|
||||
def test_saved_mime_on_received_message(self, acfactory, lp):
|
||||
ac1, ac2 = acfactory.get_two_online_accounts()
|
||||
|
||||
@@ -656,9 +576,6 @@ class TestOnlineAccount:
|
||||
lp.sec("ac2: start QR-code based join-group protocol")
|
||||
ch = ac2.qr_join_chat(qr)
|
||||
assert ch.id >= 10
|
||||
# check that at least some of the handshake messages are deleted
|
||||
ac1._evlogger.get_matching("DC_EVENT_IMAP_MESSAGE_DELETED")
|
||||
ac2._evlogger.get_matching("DC_EVENT_IMAP_MESSAGE_DELETED")
|
||||
wait_securejoin_inviter_progress(ac1, 1000)
|
||||
|
||||
def test_qr_verified_group_and_chatting(self, acfactory, lp):
|
||||
|
||||
@@ -79,21 +79,27 @@ 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() }
|
||||
&& unsafe { CStr::from_ptr((*optional_field).fld_name).to_str().unwrap() }
|
||||
== "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()
|
||||
};
|
||||
|
||||
if let Ok(test) = Self::from_str(&value) {
|
||||
if addr_cmp(&test.addr, wanted_from) {
|
||||
if fine_header.is_none() {
|
||||
fine_header = Some(test);
|
||||
} else {
|
||||
// TODO: figure out what kind of error case this is
|
||||
return None;
|
||||
match Self::from_str(value) {
|
||||
Ok(test) => {
|
||||
if addr_cmp(&test.addr, wanted_from) {
|
||||
if fine_header.is_none() {
|
||||
fine_header = Some(test);
|
||||
} else {
|
||||
// TODO: figure out what kind of error case this is
|
||||
return None;
|
||||
}
|
||||
}
|
||||
}
|
||||
_ => {}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -125,9 +131,9 @@ impl str::FromStr for Aheader {
|
||||
|
||||
fn from_str(s: &str) -> Result<Self, Self::Err> {
|
||||
let mut attributes: BTreeMap<String, String> = s
|
||||
.split(';')
|
||||
.split(";")
|
||||
.filter_map(|a| {
|
||||
let attribute: Vec<&str> = a.trim().splitn(2, '=').collect();
|
||||
let attribute: Vec<&str> = a.trim().splitn(2, "=").collect();
|
||||
if attribute.len() < 2 {
|
||||
return None;
|
||||
}
|
||||
@@ -172,7 +178,7 @@ impl str::FromStr for Aheader {
|
||||
|
||||
// Autocrypt-Level0: unknown attributes starting with an underscore can be safely ignored
|
||||
// Autocrypt-Level0: unknown attribute, treat the header as invalid
|
||||
if attributes.keys().any(|k| !k.starts_with('_')) {
|
||||
if attributes.keys().find(|k| !k.starts_with("_")).is_some() {
|
||||
return Err(());
|
||||
}
|
||||
|
||||
|
||||
163
src/chat.rs
163
src/chat.rs
@@ -152,23 +152,21 @@ impl Chat {
|
||||
return context.stock_str(StockMessage::DeadDrop).into();
|
||||
}
|
||||
let cnt = get_chat_contact_cnt(context, self.id);
|
||||
return context.stock_string_repl_int(StockMessage::Member, cnt as i32);
|
||||
return context
|
||||
.stock_string_repl_int(StockMessage::Member, cnt as i32)
|
||||
.into();
|
||||
}
|
||||
|
||||
"Err".to_string()
|
||||
return "Err".into();
|
||||
}
|
||||
|
||||
pub fn get_parent_mime_headers(&self, context: &Context) -> Option<(String, String, String)> {
|
||||
let collect = |row: &rusqlite::Row| Ok((row.get(0)?, row.get(1)?, row.get(2)?));
|
||||
let params = params![self.id as i32, DC_CONTACT_ID_SELF as i32];
|
||||
let sql = &context.sql;
|
||||
|
||||
// use the last messsage of another user in the group as the parent
|
||||
let main_query = "SELECT rfc724_mid, mime_in_reply_to, mime_references \
|
||||
FROM msgs WHERE chat_id=?1 AND timestamp=(SELECT max(timestamp) \
|
||||
FROM msgs WHERE chat_id=?1 AND from_id!=?2);";
|
||||
|
||||
// there are no messages of other users - use the first message if SELF as parent
|
||||
let fallback_query = "SELECT rfc724_mid, mime_in_reply_to, mime_references \
|
||||
FROM msgs WHERE chat_id=?1 AND timestamp=(SELECT min(timestamp) \
|
||||
FROM msgs WHERE chat_id=?1 AND from_id==?2);";
|
||||
@@ -291,7 +289,7 @@ impl Chat {
|
||||
if self.typ == Chattype::Group || self.typ == Chattype::VerifiedGroup {
|
||||
if self.param.get_int(Param::Unpromoted).unwrap_or_default() == 1 {
|
||||
self.param.remove(Param::Unpromoted);
|
||||
self.update_param(context)?;
|
||||
self.update_param(context).unwrap();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -301,15 +299,16 @@ impl Chat {
|
||||
so that E2EE is no longer available at a later point (reset, changed settings),
|
||||
we do not send the message out at all */
|
||||
do_guarantee_e2ee = false;
|
||||
e2ee_enabled = context.get_config_bool(Config::E2eeEnabled);
|
||||
e2ee_enabled = context
|
||||
.sql
|
||||
.get_config_int(context, "e2ee_enabled")
|
||||
.unwrap_or_else(|| 1)
|
||||
== 1;
|
||||
if e2ee_enabled && msg.param.get_int(Param::ForcePlaintext).unwrap_or_default() == 0 {
|
||||
let mut can_encrypt = true;
|
||||
let mut all_mutual = true;
|
||||
let mut can_encrypt = 1;
|
||||
let mut all_mutual = 1;
|
||||
|
||||
// take care that this statement returns NULL rows
|
||||
// if there is no peerstates for a chat member!
|
||||
// for DC_PARAM_SELFTALK this statement does not return any row
|
||||
let res = context.sql.query_map(
|
||||
let res = context.sql.query_row(
|
||||
"SELECT ps.prefer_encrypted, c.addr \
|
||||
FROM chats_contacts cc \
|
||||
LEFT JOIN contacts c ON cc.contact_id=c.id \
|
||||
@@ -317,32 +316,29 @@ impl Chat {
|
||||
WHERE cc.chat_id=? AND cc.contact_id>9;",
|
||||
params![self.id],
|
||||
|row| {
|
||||
let addr: String = row.get(1)?;
|
||||
let state: String = row.get(1)?;
|
||||
|
||||
if let Some(prefer_encrypted) = row.get::<_, Option<i32>>(0)? {
|
||||
// the peerstate exist, so we have either public_key or gossip_key
|
||||
// and can encrypt potentially
|
||||
if prefer_encrypted != 1 {
|
||||
info!(
|
||||
context,
|
||||
"[autocrypt] peerstate for {} is {}",
|
||||
addr,
|
||||
state,
|
||||
if prefer_encrypted == 0 {
|
||||
"NOPREFERENCE"
|
||||
} else {
|
||||
"RESET"
|
||||
},
|
||||
);
|
||||
all_mutual = false;
|
||||
all_mutual = 0;
|
||||
}
|
||||
} else {
|
||||
info!(context, "[autocrypt] no peerstate for {}", addr,);
|
||||
can_encrypt = false;
|
||||
all_mutual = false;
|
||||
info!(context, "[autocrypt] no peerstate for {}", state,);
|
||||
can_encrypt = 0;
|
||||
all_mutual = 0;
|
||||
}
|
||||
Ok(())
|
||||
},
|
||||
|rows| rows.collect::<Result<Vec<_>, _>>().map_err(Into::into),
|
||||
);
|
||||
match res {
|
||||
Ok(_) => {}
|
||||
@@ -351,8 +347,8 @@ impl Chat {
|
||||
}
|
||||
}
|
||||
|
||||
if can_encrypt {
|
||||
if all_mutual {
|
||||
if 0 != can_encrypt {
|
||||
if 0 != all_mutual {
|
||||
do_guarantee_e2ee = true;
|
||||
} else if last_msg_in_chat_encrypted(context, &context.sql, self.id) {
|
||||
do_guarantee_e2ee = true;
|
||||
@@ -362,15 +358,7 @@ impl Chat {
|
||||
if do_guarantee_e2ee {
|
||||
msg.param.set_int(Param::GuranteeE2ee, 1);
|
||||
}
|
||||
// reset eg. for forwarding
|
||||
msg.param.remove(Param::ErroneousE2ee);
|
||||
|
||||
// set "In-Reply-To:" to identify the message to which the composed message is a reply;
|
||||
// set "References:" to identify the "thread" of the conversation;
|
||||
// both according to RFC 5322 3.6.4, page 25
|
||||
//
|
||||
// as self-talks are mainly used to transfer data between devices,
|
||||
// we do not set In-Reply-To/References in this case.
|
||||
if !self.is_self_talk() {
|
||||
if let Some((parent_rfc724_mid, parent_in_reply_to, parent_references)) =
|
||||
self.get_parent_mime_headers(context)
|
||||
@@ -378,9 +366,6 @@ impl Chat {
|
||||
if !parent_rfc724_mid.is_empty() {
|
||||
new_in_reply_to = parent_rfc724_mid.clone();
|
||||
}
|
||||
|
||||
// the whole list of messages referenced may be huge;
|
||||
// only use the oldest and and the parent message
|
||||
let parent_references = if let Some(n) = parent_references.find(' ') {
|
||||
&parent_references[0..n]
|
||||
} else {
|
||||
@@ -388,7 +373,6 @@ impl Chat {
|
||||
};
|
||||
|
||||
if !parent_references.is_empty() && !parent_rfc724_mid.is_empty() {
|
||||
// angle brackets are added by the mimefactory later
|
||||
new_references = format!("{} {}", parent_references, parent_rfc724_mid);
|
||||
} else if !parent_references.is_empty() {
|
||||
new_references = parent_references.to_string();
|
||||
@@ -662,7 +646,6 @@ pub fn msgtype_has_file(msgtype: Viewtype) -> bool {
|
||||
match msgtype {
|
||||
Viewtype::Image => true,
|
||||
Viewtype::Gif => true,
|
||||
Viewtype::Sticker => true,
|
||||
Viewtype::Audio => true,
|
||||
Viewtype::Voice => true,
|
||||
Viewtype::Video => true,
|
||||
@@ -800,7 +783,10 @@ pub fn send_msg(context: &Context, chat_id: u32, msg: &mut Message) -> Result<u3
|
||||
message::update_msg_state(context, msg.id, MessageState::OutPending);
|
||||
}
|
||||
|
||||
job_send_msg(context, msg.id)?;
|
||||
ensure!(
|
||||
job_send_msg(context, msg.id) != 0,
|
||||
"Failed to initiate send job"
|
||||
);
|
||||
|
||||
context.call_cb(Event::MsgsChanged {
|
||||
chat_id: msg.chat_id,
|
||||
@@ -819,8 +805,11 @@ pub fn send_msg(context: &Context, chat_id: u32, msg: &mut Message) -> Result<u3
|
||||
if 0 == id {
|
||||
// avoid hanging if user tampers with db
|
||||
break;
|
||||
} else if let Ok(mut copy) = Message::load_from_db(context, id as u32) {
|
||||
send_msg(context, 0, &mut copy)?;
|
||||
} else {
|
||||
if let Ok(mut copy) = Message::load_from_db(context, id as u32) {
|
||||
// TODO: handle cleanup and return early instead
|
||||
send_msg(context, 0, &mut copy).unwrap();
|
||||
}
|
||||
}
|
||||
}
|
||||
msg.param.remove(Param::PrepForwards);
|
||||
@@ -973,7 +962,10 @@ pub fn get_chat_msgs(context: &Context, chat_id: u32, flags: u32, marker1before:
|
||||
};
|
||||
|
||||
let success = if chat_id == 1 {
|
||||
let show_emails = context.get_config_int(Config::ShowEmails);
|
||||
let show_emails = context
|
||||
.sql
|
||||
.get_config_int(context, "show_emails")
|
||||
.unwrap_or_default();
|
||||
context.sql.query_map(
|
||||
"SELECT m.id, m.timestamp FROM msgs m \
|
||||
LEFT JOIN chats ON m.chat_id=chats.id \
|
||||
@@ -1389,10 +1381,11 @@ pub(crate) fn add_contact_to_chat_ex(
|
||||
}
|
||||
if from_handshake && chat.param.get_int(Param::Unpromoted).unwrap_or_default() == 1 {
|
||||
chat.param.remove(Param::Unpromoted);
|
||||
chat.update_param(context)?;
|
||||
chat.update_param(context).unwrap();
|
||||
}
|
||||
let self_addr = context
|
||||
.get_config(Config::ConfiguredAddr)
|
||||
.sql
|
||||
.get_config(context, "configured_addr")
|
||||
.unwrap_or_default();
|
||||
if contact.get_addr() == &self_addr {
|
||||
bail!("invalid attempt to add self e-mail address to group");
|
||||
@@ -1406,14 +1399,14 @@ pub(crate) fn add_contact_to_chat_ex(
|
||||
}
|
||||
} else {
|
||||
// else continue and send status mail
|
||||
if chat.typ == Chattype::VerifiedGroup
|
||||
&& contact.is_verified(context) != VerifiedStatus::BidirectVerified
|
||||
{
|
||||
error!(
|
||||
context,
|
||||
"Only bidirectional verified contacts can be added to verified groups."
|
||||
);
|
||||
return Ok(false);
|
||||
if chat.typ == Chattype::VerifiedGroup {
|
||||
if contact.is_verified(context) != VerifiedStatus::BidirectVerified {
|
||||
error!(
|
||||
context,
|
||||
"Only bidirectional verified contacts can be added to verified groups."
|
||||
);
|
||||
return Ok(false);
|
||||
}
|
||||
}
|
||||
if !add_to_chat_contacts_table(context, chat_id, contact_id) {
|
||||
return Ok(false);
|
||||
@@ -1430,14 +1423,14 @@ pub(crate) fn add_contact_to_chat_ex(
|
||||
msg.param.set_int(Param::Cmd, 4);
|
||||
msg.param.set(Param::Arg, contact.get_addr());
|
||||
msg.param.set_int(Param::Arg2, from_handshake.into());
|
||||
msg.id = send_msg(context, chat_id, &mut msg)?;
|
||||
msg.id = send_msg(context, chat_id, &mut msg).unwrap_or_default();
|
||||
context.call_cb(Event::MsgsChanged {
|
||||
chat_id,
|
||||
msg_id: msg.id,
|
||||
});
|
||||
}
|
||||
context.call_cb(Event::MsgsChanged { chat_id, msg_id: 0 });
|
||||
Ok(true)
|
||||
return Ok(true);
|
||||
}
|
||||
|
||||
fn real_group_exists(context: &Context, chat_id: u32) -> bool {
|
||||
@@ -1524,7 +1517,7 @@ pub fn remove_contact_from_chat(
|
||||
if chat.is_promoted() {
|
||||
msg.type_0 = Viewtype::Text;
|
||||
if contact.id == DC_CONTACT_ID_SELF {
|
||||
set_group_explicitly_left(context, chat.grpid)?;
|
||||
set_group_explicitly_left(context, chat.grpid).unwrap();
|
||||
msg.text = Some(context.stock_system_msg(
|
||||
StockMessage::MsgGroupLeft,
|
||||
"",
|
||||
@@ -1541,7 +1534,7 @@ pub fn remove_contact_from_chat(
|
||||
}
|
||||
msg.param.set_int(Param::Cmd, 5);
|
||||
msg.param.set(Param::Arg, contact.get_addr());
|
||||
msg.id = send_msg(context, chat_id, &mut msg)?;
|
||||
msg.id = send_msg(context, chat_id, &mut msg).unwrap_or_default();
|
||||
context.call_cb(Event::MsgsChanged {
|
||||
chat_id,
|
||||
msg_id: msg.id,
|
||||
@@ -1605,7 +1598,7 @@ pub fn set_chat_name(
|
||||
let mut msg = Message::default();
|
||||
|
||||
if real_group_exists(context, chat_id) {
|
||||
if chat.name == new_name.as_ref() {
|
||||
if &chat.name == new_name.as_ref() {
|
||||
success = true;
|
||||
} else if !is_contact_in_chat(context, chat_id, 1) {
|
||||
emit_event!(
|
||||
@@ -1638,7 +1631,7 @@ pub fn set_chat_name(
|
||||
if !chat.name.is_empty() {
|
||||
msg.param.set(Param::Arg, &chat.name);
|
||||
}
|
||||
msg.id = send_msg(context, chat_id, &mut msg)?;
|
||||
msg.id = send_msg(context, chat_id, &mut msg).unwrap_or_default();
|
||||
context.call_cb(Event::MsgsChanged {
|
||||
chat_id,
|
||||
msg_id: msg.id,
|
||||
@@ -1707,7 +1700,7 @@ pub fn set_chat_profile_image(
|
||||
"",
|
||||
DC_CONTACT_ID_SELF,
|
||||
));
|
||||
msg.id = send_msg(context, chat_id, &mut msg)?;
|
||||
msg.id = send_msg(context, chat_id, &mut msg).unwrap_or_default();
|
||||
emit_event!(
|
||||
context,
|
||||
Event::MsgsChanged {
|
||||
@@ -1723,35 +1716,36 @@ pub fn set_chat_profile_image(
|
||||
bail!("Failed to set profile image");
|
||||
}
|
||||
|
||||
pub fn forward_msgs(context: &Context, msg_ids: &[u32], chat_id: u32) -> Result<(), Error> {
|
||||
ensure!(!msg_ids.is_empty(), "empty msgs_ids: no one to forward to");
|
||||
ensure!(
|
||||
chat_id > DC_CHAT_ID_LAST_SPECIAL,
|
||||
"can not forward to special chat"
|
||||
);
|
||||
pub fn forward_msgs(context: &Context, msg_ids: &[u32], chat_id: u32) {
|
||||
if msg_ids.is_empty() || chat_id <= DC_CHAT_ID_LAST_SPECIAL {
|
||||
return;
|
||||
}
|
||||
|
||||
let mut created_db_entries = Vec::new();
|
||||
let mut curr_timestamp: i64;
|
||||
|
||||
unarchive(context, chat_id)?;
|
||||
unarchive(context, chat_id).unwrap();
|
||||
if let Ok(mut chat) = Chat::load_from_db(context, chat_id) {
|
||||
curr_timestamp = dc_create_smeared_timestamps(context, msg_ids.len());
|
||||
let idsstr = msg_ids
|
||||
.iter()
|
||||
.into_iter()
|
||||
.enumerate()
|
||||
.fold(String::with_capacity(2 * msg_ids.len()), |acc, (i, n)| {
|
||||
(if i == 0 { acc } else { acc + "," }) + &n.to_string()
|
||||
});
|
||||
|
||||
let ids = context.sql.query_map(
|
||||
format!(
|
||||
"SELECT id FROM msgs WHERE id IN({}) ORDER BY timestamp,id",
|
||||
idsstr
|
||||
),
|
||||
params![],
|
||||
|row| row.get::<_, i32>(0),
|
||||
|ids| ids.collect::<Result<Vec<_>, _>>().map_err(Into::into),
|
||||
)?;
|
||||
let ids = context
|
||||
.sql
|
||||
.query_map(
|
||||
format!(
|
||||
"SELECT id FROM msgs WHERE id IN({}) ORDER BY timestamp,id",
|
||||
idsstr
|
||||
),
|
||||
params![],
|
||||
|row| row.get::<_, i32>(0),
|
||||
|ids| ids.collect::<Result<Vec<_>, _>>().map_err(Into::into),
|
||||
)
|
||||
.unwrap(); // TODO: better error handling
|
||||
|
||||
for id in ids {
|
||||
let src_msg_id = id;
|
||||
@@ -1761,12 +1755,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);
|
||||
@@ -1774,7 +1765,7 @@ pub fn forward_msgs(context: &Context, msg_ids: &[u32], chat_id: u32) -> Result<
|
||||
let new_msg_id: u32;
|
||||
if msg.state == MessageState::OutPreparing {
|
||||
let fresh9 = curr_timestamp;
|
||||
curr_timestamp += 1;
|
||||
curr_timestamp = curr_timestamp + 1;
|
||||
new_msg_id = chat
|
||||
.prepare_msg_raw(context, &mut msg, fresh9)
|
||||
.unwrap_or_default();
|
||||
@@ -1794,11 +1785,11 @@ pub fn forward_msgs(context: &Context, msg_ids: &[u32], chat_id: u32) -> Result<
|
||||
} else {
|
||||
msg.state = MessageState::OutPending;
|
||||
let fresh10 = curr_timestamp;
|
||||
curr_timestamp += 1;
|
||||
curr_timestamp = curr_timestamp + 1;
|
||||
new_msg_id = chat
|
||||
.prepare_msg_raw(context, &mut msg, fresh10)
|
||||
.unwrap_or_default();
|
||||
job_send_msg(context, new_msg_id)?;
|
||||
job_send_msg(context, new_msg_id);
|
||||
}
|
||||
created_db_entries.push(chat_id);
|
||||
created_db_entries.push(new_msg_id);
|
||||
@@ -1811,8 +1802,6 @@ pub fn forward_msgs(context: &Context, msg_ids: &[u32], chat_id: u32) -> Result<
|
||||
msg_id: created_db_entries[i + 1],
|
||||
});
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub fn get_chat_contact_cnt(context: &Context, chat_id: u32) -> usize {
|
||||
|
||||
@@ -86,17 +86,7 @@ impl Chatlist {
|
||||
query: Option<&str>,
|
||||
query_contact_id: Option<u32>,
|
||||
) -> Result<Self> {
|
||||
let mut add_archived_link_item = false;
|
||||
|
||||
// drafts are hidden in the database.
|
||||
// if add_drafts is set, this will be expanded to `hidden=1 AND state=DC_STATE_OUT_DRAFT`.
|
||||
// otherwise, this results in `hidden=0 AND state=DC_STATE_OUT_DRAFT` in the sql statement
|
||||
// and no additional messages will be regarded
|
||||
let add_drafts = if 0 != listflags & DC_GCL_ADD_DRAFTS {
|
||||
1
|
||||
} else {
|
||||
0
|
||||
};
|
||||
let mut add_archived_link_item = 0;
|
||||
|
||||
// select with left join and minimum:
|
||||
// - the inner select must use `hidden` and _not_ `m.hidden`
|
||||
@@ -135,10 +125,10 @@ impl Chatlist {
|
||||
ON c.id=m.chat_id \
|
||||
AND m.timestamp=( SELECT MAX(timestamp) \
|
||||
FROM msgs WHERE chat_id=c.id \
|
||||
AND (hidden=0 OR (hidden=? AND state=19))) WHERE c.id>9 \
|
||||
AND (hidden=0 OR (hidden=1 AND state=19))) WHERE c.id>9 \
|
||||
AND c.blocked=0 AND c.id IN(SELECT chat_id FROM chats_contacts WHERE contact_id=?) \
|
||||
GROUP BY c.id ORDER BY IFNULL(m.timestamp,0) DESC, m.id DESC;",
|
||||
params![add_drafts, query_contact_id as i32],
|
||||
params![query_contact_id as i32],
|
||||
process_row,
|
||||
process_rows,
|
||||
)?
|
||||
@@ -149,10 +139,10 @@ impl Chatlist {
|
||||
ON c.id=m.chat_id \
|
||||
AND m.timestamp=( SELECT MAX(timestamp) \
|
||||
FROM msgs WHERE chat_id=c.id \
|
||||
AND (hidden=0 OR (hidden=? AND state=19))) WHERE c.id>9 \
|
||||
AND (hidden=0 OR (hidden=1 AND state=19))) WHERE c.id>9 \
|
||||
AND c.blocked=0 AND c.archived=1 GROUP BY c.id \
|
||||
ORDER BY IFNULL(m.timestamp,0) DESC, m.id DESC;",
|
||||
params![add_drafts],
|
||||
params![],
|
||||
process_row,
|
||||
process_rows,
|
||||
)?
|
||||
@@ -166,10 +156,10 @@ impl Chatlist {
|
||||
ON c.id=m.chat_id \
|
||||
AND m.timestamp=( SELECT MAX(timestamp) \
|
||||
FROM msgs WHERE chat_id=c.id \
|
||||
AND (hidden=0 OR (hidden=? AND state=19))) WHERE c.id>9 \
|
||||
AND (hidden=0 OR (hidden=1 AND state=19))) WHERE c.id>9 \
|
||||
AND c.blocked=0 AND c.name LIKE ? \
|
||||
GROUP BY c.id ORDER BY IFNULL(m.timestamp,0) DESC, m.id DESC;",
|
||||
params![add_drafts, str_like_cmd],
|
||||
params![str_like_cmd],
|
||||
process_row,
|
||||
process_rows,
|
||||
)?
|
||||
@@ -181,25 +171,25 @@ impl Chatlist {
|
||||
ON c.id=m.chat_id \
|
||||
AND m.timestamp=( SELECT MAX(timestamp) \
|
||||
FROM msgs WHERE chat_id=c.id \
|
||||
AND (hidden=0 OR (hidden=? AND state=19))) WHERE c.id>9 \
|
||||
AND (hidden=0 OR (hidden=1 AND state=19))) WHERE c.id>9 \
|
||||
AND c.blocked=0 AND c.archived=0 \
|
||||
GROUP BY c.id \
|
||||
ORDER BY IFNULL(m.timestamp,0) DESC, m.id DESC;",
|
||||
params![add_drafts],
|
||||
params![],
|
||||
process_row,
|
||||
process_rows,
|
||||
)?;
|
||||
if 0 == listflags & DC_GCL_NO_SPECIALS {
|
||||
let last_deaddrop_fresh_msg_id = get_last_deaddrop_fresh_msg(context);
|
||||
if last_deaddrop_fresh_msg_id > 0 {
|
||||
ids.insert(0, (DC_CHAT_ID_DEADDROP, last_deaddrop_fresh_msg_id));
|
||||
ids.push((1, last_deaddrop_fresh_msg_id));
|
||||
}
|
||||
add_archived_link_item = true;
|
||||
add_archived_link_item = 1;
|
||||
}
|
||||
ids
|
||||
};
|
||||
|
||||
if add_archived_link_item && dc_get_archived_cnt(context) > 0 {
|
||||
if 0 != add_archived_link_item && dc_get_archived_cnt(context) > 0 {
|
||||
if ids.is_empty() && 0 != listflags & DC_GCL_ADD_ALLDONE_HINT {
|
||||
ids.push((DC_CHAT_ID_ALLDONE_HINT, 0));
|
||||
}
|
||||
@@ -268,11 +258,13 @@ impl Chatlist {
|
||||
let chat_loaded: Chat;
|
||||
let chat = if let Some(chat) = chat {
|
||||
chat
|
||||
} else if let Ok(chat) = Chat::load_from_db(context, self.ids[index].0) {
|
||||
chat_loaded = chat;
|
||||
&chat_loaded
|
||||
} else {
|
||||
return ret;
|
||||
if let Ok(chat) = Chat::load_from_db(context, self.ids[index].0) {
|
||||
chat_loaded = chat;
|
||||
&chat_loaded
|
||||
} else {
|
||||
return ret;
|
||||
}
|
||||
};
|
||||
|
||||
let lastmsg_id = self.ids[index].1;
|
||||
@@ -280,7 +272,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();
|
||||
|
||||
@@ -19,12 +19,10 @@ pub enum Config {
|
||||
MailUser,
|
||||
MailPw,
|
||||
MailPort,
|
||||
ImapCertificateChecks,
|
||||
SendServer,
|
||||
SendUser,
|
||||
SendPw,
|
||||
SendPort,
|
||||
SmtpCertificateChecks,
|
||||
ServerFlags,
|
||||
#[strum(props(default = "INBOX"))]
|
||||
ImapFolder,
|
||||
@@ -32,8 +30,6 @@ pub enum Config {
|
||||
Selfstatus,
|
||||
Selfavatar,
|
||||
#[strum(props(default = "1"))]
|
||||
BccSelf,
|
||||
#[strum(props(default = "1"))]
|
||||
E2eeEnabled,
|
||||
#[strum(props(default = "1"))]
|
||||
MdnsEnabled,
|
||||
@@ -54,12 +50,10 @@ pub enum Config {
|
||||
ConfiguredMailPw,
|
||||
ConfiguredMailPort,
|
||||
ConfiguredMailSecurity,
|
||||
ConfiguredImapCertificateChecks,
|
||||
ConfiguredSendServer,
|
||||
ConfiguredSendUser,
|
||||
ConfiguredSendPw,
|
||||
ConfiguredSendPort,
|
||||
ConfiguredSmtpCertificateChecks,
|
||||
ConfiguredServerFlags,
|
||||
ConfiguredSendSecurity,
|
||||
ConfiguredE2EEEnabled,
|
||||
@@ -78,13 +72,13 @@ impl Context {
|
||||
pub fn get_config(&self, key: Config) -> Option<String> {
|
||||
let value = match key {
|
||||
Config::Selfavatar => {
|
||||
let rel_path = self.sql.get_raw_config(self, key);
|
||||
rel_path.map(|p| dc_get_abs_path(self, &p).to_string_lossy().into_owned())
|
||||
let rel_path = self.sql.get_config(self, key);
|
||||
rel_path.map(|p| dc_get_abs_path(self, &p).to_str().unwrap().to_string())
|
||||
}
|
||||
Config::SysVersion => Some((&*DC_VERSION_STR).clone()),
|
||||
Config::SysMsgsizeMaxRecommended => Some(format!("{}", 24 * 1024 * 1024 / 4 * 3)),
|
||||
Config::SysConfigKeys => Some(get_config_keys_string()),
|
||||
_ => self.sql.get_raw_config(self, key),
|
||||
_ => self.sql.get_config(self, key),
|
||||
};
|
||||
|
||||
if value.is_some() {
|
||||
@@ -98,16 +92,6 @@ impl Context {
|
||||
}
|
||||
}
|
||||
|
||||
pub fn get_config_int(&self, key: Config) -> i32 {
|
||||
self.get_config(key)
|
||||
.and_then(|s| s.parse().ok())
|
||||
.unwrap_or_default()
|
||||
}
|
||||
|
||||
pub fn get_config_bool(&self, key: Config) -> bool {
|
||||
self.get_config_int(key) != 0
|
||||
}
|
||||
|
||||
/// Set the given config key.
|
||||
/// If `None` is passed as a value the value is cleared and set to the default if there is one.
|
||||
pub fn set_config(&self, key: Config, value: Option<&str>) -> Result<(), Error> {
|
||||
@@ -115,20 +99,20 @@ impl Context {
|
||||
Config::Selfavatar if value.is_some() => {
|
||||
let rel_path = std::fs::canonicalize(value.unwrap())?;
|
||||
self.sql
|
||||
.set_raw_config(self, key, Some(&rel_path.to_string_lossy()))
|
||||
.set_config(self, key, Some(&rel_path.to_string_lossy()))
|
||||
}
|
||||
Config::InboxWatch => {
|
||||
let ret = self.sql.set_raw_config(self, key, value);
|
||||
let ret = self.sql.set_config(self, key, value);
|
||||
interrupt_imap_idle(self);
|
||||
ret
|
||||
}
|
||||
Config::SentboxWatch => {
|
||||
let ret = self.sql.set_raw_config(self, key, value);
|
||||
let ret = self.sql.set_config(self, key, value);
|
||||
interrupt_sentbox_idle(self);
|
||||
ret
|
||||
}
|
||||
Config::MvboxWatch => {
|
||||
let ret = self.sql.set_raw_config(self, key, value);
|
||||
let ret = self.sql.set_config(self, key, value);
|
||||
interrupt_mvbox_idle(self);
|
||||
ret
|
||||
}
|
||||
@@ -140,9 +124,9 @@ impl Context {
|
||||
value
|
||||
};
|
||||
|
||||
self.sql.set_raw_config(self, key, val)
|
||||
self.sql.set_config(self, key, val)
|
||||
}
|
||||
_ => self.sql.set_raw_config(self, key, value),
|
||||
_ => self.sql.set_config(self, key, value),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,9 +1,10 @@
|
||||
use libc::free;
|
||||
use quick_xml;
|
||||
use quick_xml::events::{BytesEnd, BytesStart, BytesText};
|
||||
|
||||
use crate::constants::*;
|
||||
use crate::context::Context;
|
||||
use crate::error::Error;
|
||||
use crate::dc_tools::*;
|
||||
use crate::login_param::LoginParam;
|
||||
|
||||
use super::read_autoconf_file;
|
||||
@@ -11,56 +12,52 @@ 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 unsafe fn moz_autoconfigure(
|
||||
context: &Context,
|
||||
url: &str,
|
||||
param_in: &LoginParam,
|
||||
) -> Option<LoginParam> {
|
||||
let xml_raw = read_autoconf_file(context, url);
|
||||
if xml_raw.is_null() {
|
||||
return None;
|
||||
}
|
||||
|
||||
// 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() {
|
||||
free(xml_raw as *mut libc::c_void);
|
||||
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(as_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,23 @@ 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,);
|
||||
free(xml_raw as *mut libc::c_void);
|
||||
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(¶m_in.addr, &xml_raw) {
|
||||
Err(err) => {
|
||||
warn!(context, "{}", err);
|
||||
None
|
||||
}
|
||||
Ok(lp) => Some(lp),
|
||||
}
|
||||
free(xml_raw as *mut libc::c_void);
|
||||
Some(moz_ac.out)
|
||||
}
|
||||
|
||||
fn moz_autoconfigure_text_cb<B: std::io::BufRead>(
|
||||
event: &BytesText,
|
||||
moz_ac: &mut 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 +113,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 +131,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 +150,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 +189,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;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,249 +1,214 @@
|
||||
use std::ptr;
|
||||
|
||||
use libc::free;
|
||||
use quick_xml;
|
||||
use quick_xml::events::BytesEnd;
|
||||
use quick_xml::events::{BytesEnd, BytesStart, BytesText};
|
||||
|
||||
use crate::constants::*;
|
||||
use crate::context::Context;
|
||||
use crate::error::Error;
|
||||
use crate::dc_tools::*;
|
||||
use crate::login_param::LoginParam;
|
||||
|
||||
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>,
|
||||
pub out_imap_set: libc::c_int,
|
||||
pub out_smtp_set: libc::c_int,
|
||||
pub tag_config: libc::c_int,
|
||||
pub config: [*mut libc::c_char; 6],
|
||||
pub redirect: *mut libc::c_char,
|
||||
}
|
||||
|
||||
enum ParsingResult {
|
||||
LoginParam(LoginParam),
|
||||
RedirectUrl(String),
|
||||
}
|
||||
|
||||
fn outlk_parse_xml(xml_raw: &str) -> Result<ParsingResult, Error> {
|
||||
let mut outlk_ad = OutlookAutodiscover {
|
||||
pub unsafe fn outlk_autodiscover(
|
||||
context: &Context,
|
||||
url__: &str,
|
||||
param_in: &LoginParam,
|
||||
) -> Option<LoginParam> {
|
||||
let mut xml_raw: *mut libc::c_char = ptr::null_mut();
|
||||
let mut url = url__.strdup();
|
||||
let mut outlk_ad = outlk_autodiscover_t {
|
||||
in_0: param_in,
|
||||
out: LoginParam::new(),
|
||||
out_imap_set: false,
|
||||
out_smtp_set: false,
|
||||
config_type: None,
|
||||
config_server: String::new(),
|
||||
config_port: 0,
|
||||
config_ssl: String::new(),
|
||||
config_redirecturl: None,
|
||||
out_imap_set: 0,
|
||||
out_smtp_set: 0,
|
||||
tag_config: 0,
|
||||
config: [ptr::null_mut(); 6],
|
||||
redirect: ptr::null_mut(),
|
||||
};
|
||||
|
||||
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;
|
||||
|
||||
let ok_to_continue;
|
||||
let mut i = 0;
|
||||
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,
|
||||
_ => (),
|
||||
if !(i < 10) {
|
||||
ok_to_continue = true;
|
||||
break;
|
||||
}
|
||||
buf.clear();
|
||||
libc::memset(
|
||||
&mut outlk_ad as *mut outlk_autodiscover_t as *mut libc::c_void,
|
||||
0,
|
||||
::std::mem::size_of::<outlk_autodiscover_t>(),
|
||||
);
|
||||
xml_raw = read_autoconf_file(context, as_str(url));
|
||||
if xml_raw.is_null() {
|
||||
ok_to_continue = false;
|
||||
break;
|
||||
}
|
||||
|
||||
let mut reader = quick_xml::Reader::from_str(as_str(xml_raw));
|
||||
reader.trim_text(true);
|
||||
|
||||
let mut buf = Vec::new();
|
||||
|
||||
loop {
|
||||
match reader.read_event(&mut buf) {
|
||||
Ok(quick_xml::events::Event::Start(ref e)) => {
|
||||
outlk_autodiscover_starttag_cb(e, &mut outlk_ad)
|
||||
}
|
||||
Ok(quick_xml::events::Event::End(ref e)) => {
|
||||
outlk_autodiscover_endtag_cb(e, &mut outlk_ad)
|
||||
}
|
||||
Ok(quick_xml::events::Event::Text(ref e)) => {
|
||||
outlk_autodiscover_text_cb(e, &mut outlk_ad, &reader)
|
||||
}
|
||||
Err(e) => {
|
||||
error!(
|
||||
context,
|
||||
"Configure xml: Error at position {}: {:?}",
|
||||
reader.buffer_position(),
|
||||
e
|
||||
);
|
||||
}
|
||||
Ok(quick_xml::events::Event::Eof) => break,
|
||||
_ => (),
|
||||
}
|
||||
buf.clear();
|
||||
}
|
||||
|
||||
if !(!outlk_ad.config[5].is_null()
|
||||
&& 0 != *outlk_ad.config[5usize].offset(0isize) as libc::c_int)
|
||||
{
|
||||
ok_to_continue = true;
|
||||
break;
|
||||
}
|
||||
free(url as *mut libc::c_void);
|
||||
url = dc_strdup(outlk_ad.config[5usize]);
|
||||
|
||||
outlk_clean_config(&mut outlk_ad);
|
||||
free(xml_raw as *mut libc::c_void);
|
||||
xml_raw = ptr::null_mut();
|
||||
i += 1;
|
||||
}
|
||||
|
||||
// XML redirect via redirecturl
|
||||
if outlk_ad.config_redirecturl.is_none()
|
||||
|| outlk_ad.config_redirecturl.as_ref().unwrap().is_empty()
|
||||
{
|
||||
if ok_to_continue {
|
||||
if outlk_ad.out.mail_server.is_empty()
|
||||
|| outlk_ad.out.mail_port == 0
|
||||
|| outlk_ad.out.send_server.is_empty()
|
||||
|| outlk_ad.out.send_port == 0
|
||||
{
|
||||
let r = outlk_ad.out.to_string();
|
||||
bail!("Bad or incomplete autoconfig: {}", r,);
|
||||
}
|
||||
Ok(ParsingResult::LoginParam(outlk_ad.out))
|
||||
} else {
|
||||
Ok(ParsingResult::RedirectUrl(
|
||||
outlk_ad.config_redirecturl.unwrap(),
|
||||
))
|
||||
}
|
||||
}
|
||||
warn!(context, "Bad or incomplete autoconfig: {}", r,);
|
||||
free(url as *mut libc::c_void);
|
||||
free(xml_raw as *mut libc::c_void);
|
||||
outlk_clean_config(&mut outlk_ad);
|
||||
|
||||
pub fn outlk_autodiscover(
|
||||
context: &Context,
|
||||
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;
|
||||
}
|
||||
}
|
||||
None
|
||||
free(url as *mut libc::c_void);
|
||||
free(xml_raw as *mut libc::c_void);
|
||||
outlk_clean_config(&mut outlk_ad);
|
||||
Some(outlk_ad.out)
|
||||
}
|
||||
|
||||
fn outlk_autodiscover_endtag_cb(event: &BytesEnd, outlk_ad: &mut OutlookAutodiscover) {
|
||||
unsafe fn outlk_clean_config(mut outlk_ad: *mut outlk_autodiscover_t) {
|
||||
for i in 0..6 {
|
||||
free((*outlk_ad).config[i] as *mut libc::c_void);
|
||||
(*outlk_ad).config[i] = ptr::null_mut();
|
||||
}
|
||||
}
|
||||
|
||||
fn outlk_autodiscover_text_cb<B: std::io::BufRead>(
|
||||
event: &BytesText,
|
||||
outlk_ad: &mut outlk_autodiscover_t,
|
||||
reader: &quick_xml::Reader<B>,
|
||||
) {
|
||||
let val = event.unescape_and_decode(reader).unwrap_or_default();
|
||||
|
||||
unsafe {
|
||||
free(outlk_ad.config[outlk_ad.tag_config as usize].cast());
|
||||
outlk_ad.config[outlk_ad.tag_config as usize] = val.trim().strdup();
|
||||
}
|
||||
}
|
||||
|
||||
unsafe fn outlk_autodiscover_endtag_cb(event: &BytesEnd, outlk_ad: &mut outlk_autodiscover_t) {
|
||||
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());
|
||||
if !outlk_ad.config[1].is_null() {
|
||||
let port = dc_atoi_null_is_0(outlk_ad.config[3]);
|
||||
let ssl_on = (!outlk_ad.config[4].is_null()
|
||||
&& strcasecmp(
|
||||
outlk_ad.config[4],
|
||||
b"on\x00" as *const u8 as *const libc::c_char,
|
||||
) == 0) as libc::c_int;
|
||||
let ssl_off = (!outlk_ad.config[4].is_null()
|
||||
&& strcasecmp(
|
||||
outlk_ad.config[4],
|
||||
b"off\x00" as *const u8 as *const libc::c_char,
|
||||
) == 0) as libc::c_int;
|
||||
if strcasecmp(
|
||||
outlk_ad.config[1],
|
||||
b"imap\x00" as *const u8 as *const libc::c_char,
|
||||
) == 0
|
||||
&& outlk_ad.out_imap_set == 0
|
||||
{
|
||||
outlk_ad.out.mail_server = to_string(outlk_ad.config[2]);
|
||||
outlk_ad.out.mail_port = port;
|
||||
if ssl_on {
|
||||
if 0 != ssl_on {
|
||||
outlk_ad.out.server_flags |= DC_LP_IMAP_SOCKET_SSL as i32
|
||||
} else if ssl_off {
|
||||
} else if 0 != 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_imap_set = 1
|
||||
} else if strcasecmp(
|
||||
outlk_ad.config[1usize],
|
||||
b"smtp\x00" as *const u8 as *const libc::c_char,
|
||||
) == 0
|
||||
&& outlk_ad.out_smtp_set == 0
|
||||
{
|
||||
outlk_ad.out.send_server = to_string(outlk_ad.config[2]);
|
||||
outlk_ad.out.send_port = port;
|
||||
if 0 != ssl_on {
|
||||
outlk_ad.out.server_flags |= DC_LP_SMTP_SOCKET_SSL as i32
|
||||
} else if ssl_off {
|
||||
} else if 0 != ssl_off {
|
||||
outlk_ad.out.server_flags |= DC_LP_SMTP_SOCKET_PLAIN as i32
|
||||
}
|
||||
outlk_ad.out_smtp_set = true
|
||||
outlk_ad.out_smtp_set = 1
|
||||
}
|
||||
}
|
||||
outlk_clean_config(outlk_ad);
|
||||
}
|
||||
outlk_ad.tag_config = 0;
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
fn outlk_autodiscover_starttag_cb(event: &BytesStart, outlk_ad: &mut outlk_autodiscover_t) {
|
||||
let tag = String::from_utf8_lossy(event.name()).trim().to_lowercase();
|
||||
|
||||
#[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");
|
||||
}
|
||||
}
|
||||
}
|
||||
if tag == "protocol" {
|
||||
unsafe { outlk_clean_config(outlk_ad) };
|
||||
} else if tag == "type" {
|
||||
outlk_ad.tag_config = 1
|
||||
} else if tag == "server" {
|
||||
outlk_ad.tag_config = 2
|
||||
} else if tag == "port" {
|
||||
outlk_ad.tag_config = 3
|
||||
} else if tag == "ssl" {
|
||||
outlk_ad.tag_config = 4
|
||||
} else if tag == "redirecturl" {
|
||||
outlk_ad.tag_config = 5
|
||||
};
|
||||
}
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -25,6 +25,7 @@ impl Default for MoveState {
|
||||
|
||||
// some defaults
|
||||
const DC_E2EE_DEFAULT_ENABLED: i32 = 1;
|
||||
pub const DC_MDNS_DEFAULT_ENABLED: i32 = 1;
|
||||
const DC_INBOX_WATCH_DEFAULT: i32 = 1;
|
||||
const DC_SENTBOX_WATCH_DEFAULT: i32 = 1;
|
||||
const DC_MVBOX_WATCH_DEFAULT: i32 = 1;
|
||||
@@ -53,7 +54,6 @@ pub const DC_HANDSHAKE_ADD_DELETE_JOB: i32 = 0x04;
|
||||
pub const DC_GCL_ARCHIVED_ONLY: usize = 0x01;
|
||||
pub const DC_GCL_NO_SPECIALS: usize = 0x02;
|
||||
pub const DC_GCL_ADD_ALLDONE_HINT: usize = 0x04;
|
||||
pub const DC_GCL_ADD_DRAFTS: usize = 0x08;
|
||||
|
||||
const DC_GCM_ADDDAYMARKER: usize = 0x01;
|
||||
|
||||
@@ -130,23 +130,23 @@ pub const DC_CREATE_MVBOX: usize = 1;
|
||||
/// Force OAuth2 authorization. This flag does not skip automatic configuration.
|
||||
/// Before calling configure() with DC_LP_AUTH_OAUTH2 set,
|
||||
/// the user has to confirm access at the URL returned by dc_get_oauth2_url().
|
||||
pub const DC_LP_AUTH_OAUTH2: 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.
|
||||
@@ -161,9 +161,9 @@ pub const DC_LP_SMTP_SOCKET_SSL: usize = 0x20000;
|
||||
pub const DC_LP_SMTP_SOCKET_PLAIN: usize = 0x40000;
|
||||
|
||||
/// if none of these flags are set, the default is chosen
|
||||
pub const DC_LP_AUTH_FLAGS: 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 =
|
||||
@@ -195,11 +195,6 @@ pub enum Viewtype {
|
||||
/// and retrieved via dc_msg_get_file(), dc_msg_get_width(), dc_msg_get_height().
|
||||
Gif = 21,
|
||||
|
||||
/// Message containing a sticker, similar to image.
|
||||
/// If possible, the ui should display the image without borders in a transparent way.
|
||||
/// A click on a sticker will offer to install the sticker set in some future.
|
||||
Sticker = 23,
|
||||
|
||||
/// Message containing an Audio file.
|
||||
/// File and duration are set via dc_msg_set_file(), dc_msg_set_duration()
|
||||
/// and retrieved via dc_msg_get_file(), dc_msg_get_duration().
|
||||
@@ -258,7 +253,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;
|
||||
@@ -304,8 +299,7 @@ const DC_STR_MSGACTIONBYME: usize = 63;
|
||||
const DC_STR_MSGLOCATIONENABLED: usize = 64;
|
||||
const DC_STR_MSGLOCATIONDISABLED: usize = 65;
|
||||
const DC_STR_LOCATION: usize = 66;
|
||||
const DC_STR_STICKER: usize = 67;
|
||||
const DC_STR_COUNT: usize = 67;
|
||||
const DC_STR_COUNT: usize = 66;
|
||||
|
||||
pub const DC_JOB_DELETE_MSG_ON_IMAP: i32 = 110;
|
||||
|
||||
|
||||
@@ -390,18 +390,20 @@ impl Contact {
|
||||
}
|
||||
sth_modified = Modifier::Modified;
|
||||
}
|
||||
} else if sql::execute(
|
||||
context,
|
||||
&context.sql,
|
||||
"INSERT INTO contacts (name, addr, origin) VALUES(?, ?, ?);",
|
||||
params![name.as_ref(), addr, origin,],
|
||||
)
|
||||
.is_ok()
|
||||
{
|
||||
row_id = sql::get_rowid(context, &context.sql, "contacts", "addr", addr);
|
||||
sth_modified = Modifier::Created;
|
||||
} else {
|
||||
error!(context, "Cannot add contact.");
|
||||
if sql::execute(
|
||||
context,
|
||||
&context.sql,
|
||||
"INSERT INTO contacts (name, addr, origin) VALUES(?, ?, ?);",
|
||||
params![name.as_ref(), addr, origin,],
|
||||
)
|
||||
.is_ok()
|
||||
{
|
||||
row_id = sql::get_rowid(context, &context.sql, "contacts", "addr", addr);
|
||||
sth_modified = Modifier::Created;
|
||||
} else {
|
||||
error!(context, "Cannot add contact.");
|
||||
}
|
||||
}
|
||||
|
||||
Ok((row_id, sth_modified))
|
||||
@@ -825,7 +827,7 @@ impl Contact {
|
||||
if let Ok(contact) = Contact::load_from_db(context, contact_id) {
|
||||
if !contact.addr.is_empty() {
|
||||
let normalized_addr = addr_normalize(addr.as_ref());
|
||||
if contact.addr == normalized_addr {
|
||||
if &contact.addr == &normalized_addr {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
@@ -961,9 +963,9 @@ pub fn normalize_name(full_name: impl AsRef<str>) -> String {
|
||||
if len > 0 {
|
||||
let firstchar = full_name.as_bytes()[0];
|
||||
let lastchar = full_name.as_bytes()[len - 1];
|
||||
if firstchar == b'\'' && lastchar == b'\''
|
||||
|| firstchar == b'\"' && lastchar == b'\"'
|
||||
|| firstchar == b'<' && lastchar == b'>'
|
||||
if firstchar == '\'' as u8 && lastchar == '\'' as u8
|
||||
|| firstchar == '\"' as u8 && lastchar == '\"' as u8
|
||||
|| firstchar == '<' as u8 && lastchar == '>' as u8
|
||||
{
|
||||
full_name = &full_name[1..len - 1];
|
||||
}
|
||||
|
||||
133
src/context.rs
133
src/context.rs
@@ -8,7 +8,6 @@ use std::sync::{Arc, Condvar, Mutex, RwLock};
|
||||
use libc::uintptr_t;
|
||||
|
||||
use crate::chat::*;
|
||||
use crate::config::Config;
|
||||
use crate::constants::*;
|
||||
use crate::contact::*;
|
||||
use crate::dc_tools::{dc_copy_file, dc_derive_safe_stem_ext};
|
||||
@@ -63,13 +62,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 +144,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"
|
||||
);
|
||||
|
||||
@@ -210,9 +207,7 @@ impl Context {
|
||||
.open(&path)
|
||||
{
|
||||
file.write_all(data)?;
|
||||
let db_entry = format!("$BLOBDIR/{}", candidate_basename);
|
||||
self.call_cb(Event::NewBlobFile(db_entry.clone()));
|
||||
return Ok(db_entry);
|
||||
return Ok(format!("$BLOBDIR/{}", candidate_basename));
|
||||
}
|
||||
}
|
||||
bail!("out of luck to create new blob file");
|
||||
@@ -222,84 +217,31 @@ 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, "");
|
||||
let l2 = LoginParam::from_database(self, "configured_");
|
||||
let displayname = self.get_config(Config::Displayname);
|
||||
let displayname = self.sql.get_config(self, "displayname");
|
||||
let chats = get_chat_cnt(self) as usize;
|
||||
let real_msgs = message::get_real_msg_cnt(self) as usize;
|
||||
let deaddrop_msgs = message::get_deaddrop_msg_cnt(self) as usize;
|
||||
let contacts = Contact::get_real_cnt(self) as usize;
|
||||
let is_configured = self.get_config_int(Config::Configured);
|
||||
let is_configured = self
|
||||
.sql
|
||||
.get_config_int(self, "configured")
|
||||
.unwrap_or_default();
|
||||
let dbversion = self
|
||||
.sql
|
||||
.get_raw_config_int(self, "dbversion")
|
||||
.get_config_int(self, "dbversion")
|
||||
.unwrap_or_default();
|
||||
|
||||
let e2ee_enabled = self.get_config_int(Config::E2eeEnabled);
|
||||
let mdns_enabled = self.get_config_int(Config::MdnsEnabled);
|
||||
let bcc_self = self.get_config_int(Config::BccSelf);
|
||||
let e2ee_enabled = self
|
||||
.sql
|
||||
.get_config_int(self, "e2ee_enabled")
|
||||
.unwrap_or_else(|| 1);
|
||||
let mdns_enabled = self
|
||||
.sql
|
||||
.get_config_int(self, "mdns_enabled")
|
||||
.unwrap_or_else(|| 1);
|
||||
|
||||
let prv_key_cnt: Option<isize> =
|
||||
self.sql
|
||||
@@ -317,22 +259,33 @@ impl Context {
|
||||
"<Not yet calculated>".into()
|
||||
};
|
||||
|
||||
let inbox_watch = self.get_config_int(Config::InboxWatch);
|
||||
let sentbox_watch = self.get_config_int(Config::SentboxWatch);
|
||||
let mvbox_watch = self.get_config_int(Config::MvboxWatch);
|
||||
let mvbox_move = self.get_config_int(Config::MvboxMove);
|
||||
let inbox_watch = self
|
||||
.sql
|
||||
.get_config_int(self, "inbox_watch")
|
||||
.unwrap_or_else(|| 1);
|
||||
let sentbox_watch = self
|
||||
.sql
|
||||
.get_config_int(self, "sentbox_watch")
|
||||
.unwrap_or_else(|| 1);
|
||||
let mvbox_watch = self
|
||||
.sql
|
||||
.get_config_int(self, "mvbox_watch")
|
||||
.unwrap_or_else(|| 1);
|
||||
let mvbox_move = self
|
||||
.sql
|
||||
.get_config_int(self, "mvbox_move")
|
||||
.unwrap_or_else(|| 1);
|
||||
let folders_configured = self
|
||||
.sql
|
||||
.get_raw_config_int(self, "folders_configured")
|
||||
.get_config_int(self, "folders_configured")
|
||||
.unwrap_or_default();
|
||||
|
||||
let configured_sentbox_folder = self
|
||||
.sql
|
||||
.get_raw_config(self, "configured_sentbox_folder")
|
||||
.get_config(self, "configured_sentbox_folder")
|
||||
.unwrap_or_else(|| "<unset>".to_string());
|
||||
let configured_mvbox_folder = self
|
||||
.sql
|
||||
.get_raw_config(self, "configured_mvbox_folder")
|
||||
.get_config(self, "configured_mvbox_folder")
|
||||
.unwrap_or_else(|| "<unset>".to_string());
|
||||
|
||||
let mut res = get_info();
|
||||
@@ -356,7 +309,6 @@ impl Context {
|
||||
res.insert("configured_mvbox_folder", configured_mvbox_folder);
|
||||
res.insert("mdns_enabled", mdns_enabled.to_string());
|
||||
res.insert("e2ee_enabled", e2ee_enabled.to_string());
|
||||
res.insert("bcc_self", bcc_self.to_string());
|
||||
res.insert(
|
||||
"private_key_count",
|
||||
prv_key_cnt.unwrap_or_default().to_string(),
|
||||
@@ -392,7 +344,7 @@ impl Context {
|
||||
Ok(ret)
|
||||
},
|
||||
)
|
||||
.unwrap_or_default()
|
||||
.unwrap()
|
||||
}
|
||||
|
||||
#[allow(non_snake_case)]
|
||||
@@ -436,7 +388,7 @@ impl Context {
|
||||
}
|
||||
|
||||
pub fn is_sentbox(&self, folder_name: impl AsRef<str>) -> bool {
|
||||
let sentbox_name = self.sql.get_raw_config(self, "configured_sentbox_folder");
|
||||
let sentbox_name = self.sql.get_config(self, "configured_sentbox_folder");
|
||||
if let Some(name) = sentbox_name {
|
||||
name == folder_name.as_ref()
|
||||
} else {
|
||||
@@ -445,7 +397,7 @@ impl Context {
|
||||
}
|
||||
|
||||
pub fn is_mvbox(&self, folder_name: impl AsRef<str>) -> bool {
|
||||
let mvbox_name = self.sql.get_raw_config(self, "configured_mvbox_folder");
|
||||
let mvbox_name = self.sql.get_config(self, "configured_mvbox_folder");
|
||||
|
||||
if let Some(name) = mvbox_name {
|
||||
name == folder_name.as_ref()
|
||||
@@ -455,7 +407,12 @@ impl Context {
|
||||
}
|
||||
|
||||
pub fn do_heuristics_moves(&self, folder: &str, msg_id: u32) {
|
||||
if !self.get_config_bool(Config::MvboxMove) {
|
||||
if self
|
||||
.sql
|
||||
.get_config_int(self, "mvbox_move")
|
||||
.unwrap_or_else(|| 1)
|
||||
== 0
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
|
||||
@@ -11,6 +11,7 @@ use mmime::mailmime::content::*;
|
||||
use mmime::mailmime::disposition::*;
|
||||
use mmime::mailmime::types::*;
|
||||
use mmime::mailmime::*;
|
||||
use mmime::mmapstring::*;
|
||||
use mmime::other::*;
|
||||
|
||||
use crate::constants::Viewtype;
|
||||
@@ -24,7 +25,6 @@ use crate::error::Error;
|
||||
use crate::location;
|
||||
use crate::param::*;
|
||||
use crate::stock::StockMessage;
|
||||
use crate::wrapmime;
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct MimeParser<'a> {
|
||||
@@ -127,7 +127,7 @@ impl<'a> MimeParser<'a> {
|
||||
if (*field).fld_type == MAILIMF_FIELD_SUBJECT as libc::c_int {
|
||||
let subj = (*(*field).fld_data.fld_subject).sbj_value;
|
||||
|
||||
self.subject = as_opt_str(subj).map(dc_decode_header_words);
|
||||
self.subject = as_opt_str(subj).map(dc_decode_header_words_safe);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -164,17 +164,21 @@ impl<'a> MimeParser<'a> {
|
||||
}
|
||||
}
|
||||
}
|
||||
} else if let Some(optional_field) = self.lookup_optional_field("Chat-Content") {
|
||||
if optional_field == "location-streaming-enabled" {
|
||||
self.is_system_message = SystemMessage::LocationStreamingEnabled;
|
||||
} else {
|
||||
if let Some(optional_field) = self.lookup_optional_field("Chat-Content") {
|
||||
if optional_field == "location-streaming-enabled" {
|
||||
self.is_system_message = SystemMessage::LocationStreamingEnabled;
|
||||
}
|
||||
}
|
||||
}
|
||||
if self.lookup_field("Chat-Group-Image").is_some() && !self.parts.is_empty() {
|
||||
let textpart = &self.parts[0];
|
||||
if textpart.typ == Viewtype::Text && self.parts.len() >= 2 {
|
||||
let imgpart = &mut self.parts[1];
|
||||
if imgpart.typ == Viewtype::Image {
|
||||
imgpart.is_meta = true;
|
||||
if textpart.typ == Viewtype::Text {
|
||||
if self.parts.len() >= 2 {
|
||||
let imgpart = &mut self.parts[1];
|
||||
if imgpart.typ == Viewtype::Image {
|
||||
imgpart.is_meta = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -185,7 +189,6 @@ impl<'a> MimeParser<'a> {
|
||||
textpart.typ == Viewtype::Text
|
||||
&& (filepart.typ == Viewtype::Image
|
||||
|| filepart.typ == Viewtype::Gif
|
||||
|| filepart.typ == Viewtype::Sticker
|
||||
|| filepart.typ == Viewtype::Audio
|
||||
|| filepart.typ == Viewtype::Voice
|
||||
|| filepart.typ == Viewtype::Video
|
||||
@@ -253,14 +256,6 @@ impl<'a> MimeParser<'a> {
|
||||
part_mut.typ = Viewtype::Voice;
|
||||
}
|
||||
}
|
||||
if self.parts[0].typ == Viewtype::Image {
|
||||
if let Some(content_type) = self.lookup_optional_field("Chat-Content") {
|
||||
if content_type == "sticker" {
|
||||
let part_mut = &mut self.parts[0];
|
||||
part_mut.typ = Viewtype::Sticker;
|
||||
}
|
||||
}
|
||||
}
|
||||
let part = &self.parts[0];
|
||||
if part.typ == Viewtype::Audio
|
||||
|| part.typ == Viewtype::Voice
|
||||
@@ -282,7 +277,7 @@ impl<'a> MimeParser<'a> {
|
||||
if self.get_last_nonmeta().is_some() {
|
||||
let mut mb_list: *mut mailimf_mailbox_list = ptr::null_mut();
|
||||
let mut index_0 = 0;
|
||||
let dn_field_c = CString::new(dn_field).unwrap_or_default();
|
||||
let dn_field_c = CString::new(dn_field).unwrap();
|
||||
|
||||
if mailimf_mailbox_list_parse(
|
||||
dn_field_c.as_ptr(),
|
||||
@@ -292,12 +287,12 @@ impl<'a> MimeParser<'a> {
|
||||
) == MAILIMF_NO_ERROR as libc::c_int
|
||||
&& !mb_list.is_null()
|
||||
{
|
||||
if let Some(dn_to_addr) = wrapmime::mailimf_find_first_addr(mb_list) {
|
||||
if let Some(dn_to_addr) = mailimf_find_first_addr(mb_list) {
|
||||
if let Some(from_field) = self.lookup_field("From") {
|
||||
if (*from_field).fld_type == MAILIMF_FIELD_FROM as libc::c_int
|
||||
&& !(*from_field).fld_data.fld_from.is_null()
|
||||
{
|
||||
let from_addr = wrapmime::mailimf_find_first_addr(
|
||||
let from_addr = mailimf_find_first_addr(
|
||||
(*(*from_field).fld_data.fld_from).frm_mb_list,
|
||||
);
|
||||
if let Some(from_addr) = from_addr {
|
||||
@@ -595,7 +590,7 @@ impl<'a> MimeParser<'a> {
|
||||
return false;
|
||||
}
|
||||
|
||||
let mut decoded_data = match wrapmime::mailmime_transfer_decode(mime) {
|
||||
let mut decoded_data = match mailmime_transfer_decode(mime) {
|
||||
Ok(decoded_data) => decoded_data,
|
||||
Err(_) => {
|
||||
// Note that it's now always an error - might be no data
|
||||
@@ -606,9 +601,14 @@ 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()
|
||||
@@ -616,14 +616,15 @@ impl<'a> MimeParser<'a> {
|
||||
&& strcmp(charset, b"UTF-8\x00" as *const u8 as *const libc::c_char) != 0i32
|
||||
{
|
||||
if let Some(encoding) =
|
||||
Charset::for_label(CStr::from_ptr(charset).to_string_lossy().as_bytes())
|
||||
Charset::for_label(CStr::from_ptr(charset).to_str().unwrap().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 +634,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
|
||||
@@ -702,7 +704,7 @@ impl<'a> MimeParser<'a> {
|
||||
// might be a wrongly encoded filename
|
||||
let s = to_string_lossy((*dsp_param).pa_data.pa_filename);
|
||||
// this is used only if the parts buffer stays empty
|
||||
desired_filename = dc_decode_header_words(&s)
|
||||
desired_filename = dc_decode_header_words_safe(&s)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -731,29 +733,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,
|
||||
);
|
||||
}
|
||||
}
|
||||
_ => {}
|
||||
@@ -832,7 +838,7 @@ impl<'a> MimeParser<'a> {
|
||||
let mut fld_from: *const mailimf_from = ptr::null();
|
||||
|
||||
/* get From: and check there is exactly one sender */
|
||||
let fld = wrapmime::mailimf_find_field(self.header_root, MAILIMF_FIELD_FROM as libc::c_int);
|
||||
let fld = mailimf_find_field(self.header_root, MAILIMF_FIELD_FROM as libc::c_int);
|
||||
if !(fld.is_null()
|
||||
|| {
|
||||
fld_from = (*fld).fld_data.fld_from;
|
||||
@@ -850,9 +856,11 @@ impl<'a> MimeParser<'a> {
|
||||
|
||||
if !mb.is_null() {
|
||||
let from_addr_norm = addr_normalize(as_str((*mb).mb_addr_spec));
|
||||
let recipients = wrapmime::mailimf_get_recipients(self.header_root);
|
||||
if recipients.len() == 1 && recipients.contains(from_addr_norm) {
|
||||
sender_equals_recipient = true;
|
||||
let recipients = mailimf_get_recipients(self.header_root);
|
||||
if recipients.len() == 1 {
|
||||
if recipients.contains(from_addr_norm) {
|
||||
sender_equals_recipient = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -879,7 +887,7 @@ impl<'a> MimeParser<'a> {
|
||||
unsafe {
|
||||
let fld_message_id = (*field).fld_data.fld_message_id;
|
||||
if !fld_message_id.is_null() {
|
||||
return Some(to_string_lossy((*fld_message_id).mid_value));
|
||||
return Some(to_string((*fld_message_id).mid_value));
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -909,6 +917,22 @@ pub struct Part {
|
||||
pub param: Params,
|
||||
}
|
||||
|
||||
pub fn mailimf_find_first_addr(mb_list: *const mailimf_mailbox_list) -> Option<String> {
|
||||
if mb_list.is_null() {
|
||||
return None;
|
||||
}
|
||||
|
||||
for cur in unsafe { (*(*mb_list).mb_list).into_iter() } {
|
||||
let mb = cur as *mut mailimf_mailbox;
|
||||
if !mb.is_null() && !unsafe { (*mb).mb_addr_spec.is_null() } {
|
||||
let addr = unsafe { as_str((*mb).mb_addr_spec) };
|
||||
return Some(addr_normalize(addr).to_string());
|
||||
}
|
||||
}
|
||||
|
||||
None
|
||||
}
|
||||
|
||||
unsafe fn hash_header(out: &mut HashMap<String, *mut mailimf_field>, in_0: *const mailimf_fields) {
|
||||
if in_0.is_null() {
|
||||
return;
|
||||
@@ -979,12 +1003,14 @@ unsafe fn mailmime_get_mime_type(mime: *mut Mailmime) -> (libc::c_int, Viewtype,
|
||||
) == 0i32
|
||||
{
|
||||
return (DC_MIMETYPE_TEXT_PLAIN, Viewtype::Text, None);
|
||||
} else if strcmp(
|
||||
(*c).ct_subtype,
|
||||
b"html\x00" as *const u8 as *const libc::c_char,
|
||||
) == 0i32
|
||||
{
|
||||
return (DC_MIMETYPE_TEXT_HTML, Viewtype::Text, None);
|
||||
} else {
|
||||
if strcmp(
|
||||
(*c).ct_subtype,
|
||||
b"html\x00" as *const u8 as *const libc::c_char,
|
||||
) == 0i32
|
||||
{
|
||||
return (DC_MIMETYPE_TEXT_HTML, Viewtype::Text, None);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1128,6 +1154,214 @@ pub unsafe fn mailmime_find_ct_parameter(
|
||||
ptr::null_mut()
|
||||
}
|
||||
|
||||
pub unsafe fn mailmime_transfer_decode(mime: *mut Mailmime) -> Result<Vec<u8>, Error> {
|
||||
ensure!(!mime.is_null(), "invalid inputs");
|
||||
|
||||
let mut mime_transfer_encoding = MAILMIME_MECHANISM_BINARY as libc::c_int;
|
||||
|
||||
let mime_data = (*mime).mm_data.mm_single;
|
||||
if !(*mime).mm_mime_fields.is_null() {
|
||||
for cur in (*(*(*mime).mm_mime_fields).fld_list).into_iter() {
|
||||
let field = cur as *mut mailmime_field;
|
||||
|
||||
if !field.is_null()
|
||||
&& (*field).fld_type == MAILMIME_FIELD_TRANSFER_ENCODING as libc::c_int
|
||||
&& !(*field).fld_data.fld_encoding.is_null()
|
||||
{
|
||||
mime_transfer_encoding = (*(*field).fld_data.fld_encoding).enc_type;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if mime_transfer_encoding == MAILMIME_MECHANISM_7BIT as libc::c_int
|
||||
|| mime_transfer_encoding == MAILMIME_MECHANISM_8BIT as libc::c_int
|
||||
|| mime_transfer_encoding == MAILMIME_MECHANISM_BINARY as libc::c_int
|
||||
{
|
||||
let decoded_data = (*mime_data).dt_data.dt_text.dt_data;
|
||||
let decoded_data_bytes = (*mime_data).dt_data.dt_text.dt_length;
|
||||
|
||||
if decoded_data.is_null() || decoded_data_bytes <= 0 {
|
||||
bail!("No data to decode found");
|
||||
} else {
|
||||
let result = std::slice::from_raw_parts(decoded_data as *const u8, decoded_data_bytes);
|
||||
return Ok(result.to_vec());
|
||||
}
|
||||
}
|
||||
|
||||
let mut current_index = 0;
|
||||
let mut transfer_decoding_buffer = ptr::null_mut();
|
||||
let mut decoded_data_bytes = 0;
|
||||
|
||||
let r = mailmime_part_parse(
|
||||
(*mime_data).dt_data.dt_text.dt_data,
|
||||
(*mime_data).dt_data.dt_text.dt_length,
|
||||
&mut current_index,
|
||||
mime_transfer_encoding,
|
||||
&mut transfer_decoding_buffer,
|
||||
&mut decoded_data_bytes,
|
||||
);
|
||||
|
||||
if r == MAILIMF_NO_ERROR as libc::c_int
|
||||
&& !transfer_decoding_buffer.is_null()
|
||||
&& decoded_data_bytes > 0
|
||||
{
|
||||
let result =
|
||||
std::slice::from_raw_parts(transfer_decoding_buffer as *const u8, decoded_data_bytes)
|
||||
.to_vec();
|
||||
mmap_string_unref(transfer_decoding_buffer);
|
||||
|
||||
return Ok(result);
|
||||
}
|
||||
|
||||
Err(format_err!("Failed to to decode"))
|
||||
}
|
||||
|
||||
pub fn mailimf_get_recipients(imffields: *mut mailimf_fields) -> HashSet<String> {
|
||||
/* returned addresses are normalized. */
|
||||
let mut recipients: HashSet<String> = Default::default();
|
||||
|
||||
for cur in unsafe { (*(*imffields).fld_list).into_iter() } {
|
||||
let fld = cur as *mut mailimf_field;
|
||||
|
||||
let fld_to: *mut mailimf_to;
|
||||
let fld_cc: *mut mailimf_cc;
|
||||
|
||||
let mut addr_list: *mut mailimf_address_list = ptr::null_mut();
|
||||
if fld.is_null() {
|
||||
continue;
|
||||
}
|
||||
|
||||
let fld = unsafe { *fld };
|
||||
|
||||
// TODO match on enums /rtn
|
||||
match fld.fld_type {
|
||||
13 => {
|
||||
fld_to = unsafe { fld.fld_data.fld_to };
|
||||
if !fld_to.is_null() {
|
||||
addr_list = unsafe { (*fld_to).to_addr_list };
|
||||
}
|
||||
}
|
||||
14 => {
|
||||
fld_cc = unsafe { fld.fld_data.fld_cc };
|
||||
if !fld_cc.is_null() {
|
||||
addr_list = unsafe { (*fld_cc).cc_addr_list };
|
||||
}
|
||||
}
|
||||
_ => {}
|
||||
}
|
||||
|
||||
if !addr_list.is_null() {
|
||||
for cur2 in unsafe { &(*(*addr_list).ad_list) } {
|
||||
let adr = cur2 as *mut mailimf_address;
|
||||
|
||||
if adr.is_null() {
|
||||
continue;
|
||||
}
|
||||
let adr = unsafe { *adr };
|
||||
|
||||
if adr.ad_type == MAILIMF_ADDRESS_MAILBOX as libc::c_int {
|
||||
mailimf_get_recipients_add_addr(&mut recipients, unsafe {
|
||||
adr.ad_data.ad_mailbox
|
||||
});
|
||||
} else if adr.ad_type == MAILIMF_ADDRESS_GROUP as libc::c_int {
|
||||
let group = unsafe { adr.ad_data.ad_group };
|
||||
if !group.is_null() && unsafe { !(*group).grp_mb_list.is_null() } {
|
||||
for cur3 in unsafe { &(*(*(*group).grp_mb_list).mb_list) } {
|
||||
mailimf_get_recipients_add_addr(
|
||||
&mut recipients,
|
||||
cur3 as *mut mailimf_mailbox,
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
recipients
|
||||
}
|
||||
|
||||
fn mailimf_get_recipients_add_addr(recipients: &mut HashSet<String>, mb: *mut mailimf_mailbox) {
|
||||
if !mb.is_null() {
|
||||
let addr_norm = addr_normalize(as_str(unsafe { (*mb).mb_addr_spec }));
|
||||
recipients.insert(addr_norm.into());
|
||||
}
|
||||
}
|
||||
|
||||
/*the result is a pointer to mime, must not be freed*/
|
||||
pub fn mailimf_find_field(
|
||||
header: *mut mailimf_fields,
|
||||
wanted_fld_type: libc::c_int,
|
||||
) -> *mut mailimf_field {
|
||||
if header.is_null() {
|
||||
return ptr::null_mut();
|
||||
}
|
||||
|
||||
let header = unsafe { (*header) };
|
||||
if header.fld_list.is_null() {
|
||||
return ptr::null_mut();
|
||||
}
|
||||
|
||||
for cur in unsafe { &(*header.fld_list) } {
|
||||
let field = cur as *mut mailimf_field;
|
||||
if !field.is_null() {
|
||||
if unsafe { (*field).fld_type } == wanted_fld_type {
|
||||
return field;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
ptr::null_mut()
|
||||
}
|
||||
|
||||
/*the result is a pointer to mime, must not be freed*/
|
||||
pub unsafe fn mailmime_find_mailimf_fields(mime: *mut Mailmime) -> *mut mailimf_fields {
|
||||
if mime.is_null() {
|
||||
return ptr::null_mut();
|
||||
}
|
||||
|
||||
match (*mime).mm_type as _ {
|
||||
MAILMIME_MULTIPLE => {
|
||||
for cur_data in (*(*mime).mm_data.mm_multipart.mm_mp_list).into_iter() {
|
||||
let header = mailmime_find_mailimf_fields(cur_data as *mut _);
|
||||
if !header.is_null() {
|
||||
return header;
|
||||
}
|
||||
}
|
||||
}
|
||||
MAILMIME_MESSAGE => return (*mime).mm_data.mm_message.mm_fields,
|
||||
_ => {}
|
||||
}
|
||||
|
||||
ptr::null_mut()
|
||||
}
|
||||
|
||||
pub unsafe fn mailimf_find_optional_field(
|
||||
header: *mut mailimf_fields,
|
||||
wanted_fld_name: *const libc::c_char,
|
||||
) -> *mut mailimf_optional_field {
|
||||
if header.is_null() || (*header).fld_list.is_null() {
|
||||
return ptr::null_mut();
|
||||
}
|
||||
for cur_data in (*(*header).fld_list).into_iter() {
|
||||
let field: *mut mailimf_field = cur_data as *mut _;
|
||||
|
||||
if (*field).fld_type == MAILIMF_FIELD_OPTIONAL_FIELD as libc::c_int {
|
||||
let optional_field: *mut mailimf_optional_field = (*field).fld_data.fld_optional_field;
|
||||
if !optional_field.is_null()
|
||||
&& !(*optional_field).fld_name.is_null()
|
||||
&& !(*optional_field).fld_value.is_null()
|
||||
&& strcasecmp((*optional_field).fld_name, wanted_fld_name) == 0i32
|
||||
{
|
||||
return optional_field;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
ptr::null_mut()
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
@@ -1143,13 +1377,14 @@ mod tests {
|
||||
let mut mime: *mut Mailmime = ptr::null_mut();
|
||||
let mut dummy = 0;
|
||||
let res = mailmime_parse(txt, strlen(txt), &mut dummy, &mut mime);
|
||||
|
||||
assert_eq!(res, MAIL_NO_ERROR as libc::c_int);
|
||||
assert!(!mime.is_null());
|
||||
|
||||
let fields: *mut mailimf_fields = wrapmime::mailmime_find_mailimf_fields(mime);
|
||||
let fields: *mut mailimf_fields = mailmime_find_mailimf_fields(mime);
|
||||
assert!(!fields.is_null());
|
||||
|
||||
let mut of_a: *mut mailimf_optional_field = wrapmime::mailimf_find_optional_field(
|
||||
let mut of_a: *mut mailimf_optional_field = mailimf_find_optional_field(
|
||||
fields,
|
||||
b"fielda\x00" as *const u8 as *const libc::c_char,
|
||||
);
|
||||
@@ -1169,7 +1404,7 @@ mod tests {
|
||||
"ValueA",
|
||||
);
|
||||
|
||||
of_a = wrapmime::mailimf_find_optional_field(
|
||||
of_a = mailimf_find_optional_field(
|
||||
fields,
|
||||
b"FIELDA\x00" as *const u8 as *const libc::c_char,
|
||||
);
|
||||
@@ -1189,7 +1424,7 @@ mod tests {
|
||||
"ValueA",
|
||||
);
|
||||
|
||||
let of_b: *mut mailimf_optional_field = wrapmime::mailimf_find_optional_field(
|
||||
let of_b: *mut mailimf_optional_field = mailimf_find_optional_field(
|
||||
fields,
|
||||
b"FieldB\x00" as *const u8 as *const libc::c_char,
|
||||
);
|
||||
@@ -1212,9 +1447,7 @@ mod tests {
|
||||
let context = dummy_context();
|
||||
let raw = include_bytes!("../test-data/message/issue_523.txt");
|
||||
let mut mimeparser = MimeParser::new(&context.ctx);
|
||||
unsafe {
|
||||
mimeparser.parse(&raw[..]).unwrap();
|
||||
};
|
||||
unsafe { mimeparser.parse(&raw[..]).unwrap() };
|
||||
assert_eq!(mimeparser.subject, None);
|
||||
assert_eq!(mimeparser.parts.len(), 1);
|
||||
}
|
||||
@@ -1222,13 +1455,9 @@ mod tests {
|
||||
proptest! {
|
||||
#[test]
|
||||
fn test_dc_mailmime_parse_crash_fuzzy(data in "[!-~\t ]{2000,}") {
|
||||
// this test doesn't exercise much of dc_mimeparser anymore
|
||||
// because a missing From-field early aborts parsing
|
||||
let context = dummy_context();
|
||||
let mut mimeparser = MimeParser::new(&context.ctx);
|
||||
unsafe {
|
||||
assert!(mimeparser.parse(data.as_bytes()).is_err());
|
||||
}
|
||||
unsafe { mimeparser.parse(data.as_bytes()).unwrap() };
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1257,7 +1486,7 @@ mod tests {
|
||||
fn test_mimeparser_with_context() {
|
||||
unsafe {
|
||||
let context = dummy_context();
|
||||
let raw = b"From: hello\nContent-Type: multipart/mixed; boundary=\"==break==\";\nSubject: outer-subject\nX-Special-A: special-a\nFoo: Bar\nChat-Version: 0.0\n\n--==break==\nContent-Type: text/plain; protected-headers=\"v1\";\nSubject: inner-subject\nX-Special-B: special-b\nFoo: Xy\nChat-Version: 1.0\n\ntest1\n\n--==break==--\n\n\x00";
|
||||
let raw = b"Content-Type: multipart/mixed; boundary=\"==break==\";\nSubject: outer-subject\nX-Special-A: special-a\nFoo: Bar\nChat-Version: 0.0\n\n--==break==\nContent-Type: text/plain; protected-headers=\"v1\";\nSubject: inner-subject\nX-Special-B: special-b\nFoo: Xy\nChat-Version: 1.0\n\ntest1\n\n--==break==--\n\n\x00";
|
||||
let mut mimeparser = MimeParser::new(&context.ctx);
|
||||
mimeparser.parse(&raw[..]).unwrap();
|
||||
|
||||
|
||||
@@ -1,9 +1,11 @@
|
||||
use std::ffi::CString;
|
||||
use std::ptr;
|
||||
|
||||
use itertools::join;
|
||||
use libc::strcmp;
|
||||
use libc::{free, strcmp, strlen};
|
||||
use mmime::clist::*;
|
||||
use mmime::mailimf::types::*;
|
||||
use mmime::mailimf::*;
|
||||
use mmime::mailmime::content::*;
|
||||
use mmime::mailmime::types::*;
|
||||
use mmime::mailmime::*;
|
||||
@@ -11,7 +13,6 @@ use mmime::other::*;
|
||||
use sha2::{Digest, Sha256};
|
||||
|
||||
use crate::chat::{self, Chat};
|
||||
use crate::config::Config;
|
||||
use crate::constants::*;
|
||||
use crate::contact::*;
|
||||
use crate::context::Context;
|
||||
@@ -28,7 +29,6 @@ use crate::peerstate::*;
|
||||
use crate::securejoin::handle_securejoin_handshake;
|
||||
use crate::sql;
|
||||
use crate::stock::StockMessage;
|
||||
use crate::wrapmime;
|
||||
|
||||
#[derive(Debug, PartialEq, Eq)]
|
||||
enum CreateEvent {
|
||||
@@ -81,7 +81,7 @@ pub unsafe fn dc_receive_imf(
|
||||
let mut chat_id = 0;
|
||||
let mut hidden = 0;
|
||||
|
||||
let mut needs_delete_job = false;
|
||||
let mut add_delete_job: libc::c_int = 0;
|
||||
let mut insert_msg_id = 0;
|
||||
|
||||
let mut sent_timestamp = 0;
|
||||
@@ -147,7 +147,7 @@ pub unsafe fn dc_receive_imf(
|
||||
if mime_parser.sender_equals_recipient() {
|
||||
from_id = DC_CONTACT_ID_SELF;
|
||||
}
|
||||
} else if !from_list.is_empty() {
|
||||
} else if from_list.len() >= 1 {
|
||||
// if there is no from given, from_id stays 0 which is just fine. These messages
|
||||
// are very rare, however, we have to add them to the database (they go to the
|
||||
// "deaddrop" chat) to avoid a re-download from the server. See also [**]
|
||||
@@ -218,7 +218,7 @@ pub unsafe fn dc_receive_imf(
|
||||
&mut chat_id,
|
||||
&mut to_id,
|
||||
flags,
|
||||
&mut needs_delete_job,
|
||||
&mut add_delete_job,
|
||||
to_self,
|
||||
&mut insert_msg_id,
|
||||
&mut created_db_entries,
|
||||
@@ -249,7 +249,7 @@ pub unsafe fn dc_receive_imf(
|
||||
from_id,
|
||||
sent_timestamp,
|
||||
&mut rr_event_to_send,
|
||||
&server_folder,
|
||||
server_folder,
|
||||
server_uid,
|
||||
);
|
||||
}
|
||||
@@ -265,8 +265,7 @@ pub unsafe fn dc_receive_imf(
|
||||
);
|
||||
}
|
||||
|
||||
// if we delete we don't need to try moving messages
|
||||
if needs_delete_job && !created_db_entries.is_empty() {
|
||||
if 0 != add_delete_job && !created_db_entries.is_empty() {
|
||||
job_add(
|
||||
context,
|
||||
Action::DeleteMsgOnImap,
|
||||
@@ -274,8 +273,6 @@ pub unsafe fn dc_receive_imf(
|
||||
Params::new(),
|
||||
0,
|
||||
);
|
||||
} else {
|
||||
context.do_heuristics_moves(server_folder.as_ref(), insert_msg_id);
|
||||
}
|
||||
|
||||
info!(
|
||||
@@ -308,7 +305,7 @@ unsafe fn add_parts(
|
||||
chat_id: &mut u32,
|
||||
to_id: &mut u32,
|
||||
flags: u32,
|
||||
needs_delete_job: &mut bool,
|
||||
add_delete_job: &mut libc::c_int,
|
||||
to_self: i32,
|
||||
insert_msg_id: &mut u32,
|
||||
created_db_entries: &mut Vec<(usize, usize)>,
|
||||
@@ -319,8 +316,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 +356,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");
|
||||
}
|
||||
|
||||
@@ -367,7 +370,10 @@ unsafe fn add_parts(
|
||||
// maybe this can be optimized later, by checking the state before the message body is downloaded
|
||||
let mut allow_creation = 1;
|
||||
if mime_parser.is_system_message != SystemMessage::AutocryptSetupMessage && msgrmsg == 0 {
|
||||
let show_emails = context.get_config_int(Config::ShowEmails);
|
||||
let show_emails = context
|
||||
.sql
|
||||
.get_config_int(context, "show_emails")
|
||||
.unwrap_or_default();
|
||||
if show_emails == 0 {
|
||||
*chat_id = 3;
|
||||
allow_creation = 0
|
||||
@@ -397,7 +403,7 @@ unsafe fn add_parts(
|
||||
let handshake = handle_securejoin_handshake(context, mime_parser, *from_id);
|
||||
if 0 != handshake & DC_HANDSHAKE_STOP_NORMAL_PROCESSING {
|
||||
*hidden = 1;
|
||||
*needs_delete_job = 0 != handshake & DC_HANDSHAKE_ADD_DELETE_JOB;
|
||||
*add_delete_job = handshake & DC_HANDSHAKE_ADD_DELETE_JOB;
|
||||
state = MessageState::InSeen;
|
||||
}
|
||||
}
|
||||
@@ -429,7 +435,7 @@ unsafe fn add_parts(
|
||||
to_ids,
|
||||
chat_id,
|
||||
&mut chat_id_blocked,
|
||||
)?;
|
||||
);
|
||||
if 0 != *chat_id && Blocked::Not != chat_id_blocked && create_blocked == Blocked::Not {
|
||||
chat::unblock(context, *chat_id);
|
||||
chat_id_blocked = Blocked::Not;
|
||||
@@ -488,12 +494,10 @@ unsafe fn add_parts(
|
||||
// if the chat_id is blocked,
|
||||
// for unknown senders and non-delta messages set the state to NOTICED
|
||||
// to not result in a contact request (this would require the state FRESH)
|
||||
if Blocked::Not != chat_id_blocked
|
||||
&& state == MessageState::InFresh
|
||||
&& !incoming_origin.is_verified()
|
||||
&& msgrmsg == 0
|
||||
{
|
||||
state = MessageState::InNoticed;
|
||||
if Blocked::Not != chat_id_blocked && state == MessageState::InFresh {
|
||||
if !incoming_origin.is_verified() && msgrmsg == 0 {
|
||||
state = MessageState::InNoticed;
|
||||
}
|
||||
}
|
||||
} else {
|
||||
// Outgoing
|
||||
@@ -514,7 +518,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;
|
||||
@@ -574,22 +578,28 @@ unsafe fn add_parts(
|
||||
);
|
||||
|
||||
// unarchive chat
|
||||
chat::unarchive(context, *chat_id)?;
|
||||
chat::unarchive(context, *chat_id).unwrap();
|
||||
|
||||
// if the mime-headers should be saved, find out its size
|
||||
// (the mime-header ends with an empty line)
|
||||
let save_mime_headers = context.get_config_bool(Config::SaveMimeHeaders);
|
||||
let save_mime_headers = context.sql.get_config_bool(context, "save_mime_headers");
|
||||
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 +610,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_none()
|
||||
&& 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(mime_in_reply_to),
|
||||
to_string(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 +720,9 @@ unsafe fn add_parts(
|
||||
}
|
||||
}
|
||||
|
||||
context.do_heuristics_moves(server_folder.as_ref(), *insert_msg_id);
|
||||
cleanup(mime_in_reply_to, mime_references);
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
@@ -717,7 +736,10 @@ unsafe fn handle_reports(
|
||||
server_folder: impl AsRef<str>,
|
||||
server_uid: u32,
|
||||
) {
|
||||
let mdns_enabled = context.get_config_bool(Config::MdnsEnabled);
|
||||
let mdns_enabled = context
|
||||
.sql
|
||||
.get_config_int(context, "mdns_enabled")
|
||||
.unwrap_or_else(|| DC_MDNS_DEFAULT_ENABLED);
|
||||
|
||||
for report_root in &mime_parser.reports {
|
||||
let report_root = *report_root;
|
||||
@@ -736,7 +758,7 @@ unsafe fn handle_reports(
|
||||
&& (*(*report_root).mm_data.mm_multipart.mm_mp_list).count >= 2
|
||||
{
|
||||
// to get a clear functionality, do not show incoming MDNs if the options is disabled
|
||||
if mdns_enabled {
|
||||
if 0 != mdns_enabled {
|
||||
let report_data = (if !if !(*(*report_root).mm_data.mm_multipart.mm_mp_list)
|
||||
.first
|
||||
.is_null()
|
||||
@@ -773,7 +795,7 @@ unsafe fn handle_reports(
|
||||
b"disposition-notification\x00" as *const u8 as *const libc::c_char,
|
||||
) == 0
|
||||
{
|
||||
if let Ok(report_body) = wrapmime::mailmime_transfer_decode(report_data) {
|
||||
if let Ok(report_body) = mailmime_transfer_decode(report_data) {
|
||||
let mut report_parsed = std::ptr::null_mut();
|
||||
let mut dummy = 0;
|
||||
|
||||
@@ -785,14 +807,13 @@ unsafe fn handle_reports(
|
||||
) == MAIL_NO_ERROR as libc::c_int
|
||||
&& !report_parsed.is_null()
|
||||
{
|
||||
let report_fields =
|
||||
wrapmime::mailmime_find_mailimf_fields(report_parsed);
|
||||
let report_fields = mailmime_find_mailimf_fields(report_parsed);
|
||||
if !report_fields.is_null() {
|
||||
let of_disposition = wrapmime::mailimf_find_optional_field(
|
||||
let of_disposition = mailimf_find_optional_field(
|
||||
report_fields,
|
||||
b"Disposition\x00" as *const u8 as *const libc::c_char,
|
||||
);
|
||||
let of_org_msgid = wrapmime::mailimf_find_optional_field(
|
||||
let of_org_msgid = mailimf_find_optional_field(
|
||||
report_fields,
|
||||
b"Original-Message-ID\x00" as *const u8 as *const libc::c_char,
|
||||
);
|
||||
@@ -801,16 +822,24 @@ unsafe fn handle_reports(
|
||||
&& !of_org_msgid.is_null()
|
||||
&& !(*of_org_msgid).fld_value.is_null()
|
||||
{
|
||||
if let Ok(rfc724_mid) = wrapmime::parse_message_id(as_str(
|
||||
let mut rfc724_mid_0 = std::ptr::null_mut();
|
||||
dummy = 0;
|
||||
|
||||
if mailimf_msg_id_parse(
|
||||
(*of_org_msgid).fld_value,
|
||||
)) {
|
||||
strlen((*of_org_msgid).fld_value),
|
||||
&mut dummy,
|
||||
&mut rfc724_mid_0,
|
||||
) == MAIL_NO_ERROR as libc::c_int
|
||||
&& !rfc724_mid_0.is_null()
|
||||
{
|
||||
let mut chat_id_0 = 0;
|
||||
let mut msg_id = 0;
|
||||
|
||||
if message::mdn_from_ext(
|
||||
context,
|
||||
from_id,
|
||||
&rfc724_mid,
|
||||
as_str(rfc724_mid_0),
|
||||
sent_timestamp,
|
||||
&mut chat_id_0,
|
||||
&mut msg_id,
|
||||
@@ -818,6 +847,7 @@ unsafe fn handle_reports(
|
||||
rr_event_to_send.push((chat_id_0, msg_id));
|
||||
}
|
||||
mdn_consumed = (msg_id != 0) as libc::c_int;
|
||||
free(rfc724_mid_0.cast());
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -831,7 +861,12 @@ unsafe fn handle_reports(
|
||||
let mut param = Params::new();
|
||||
param.set(Param::ServerFolder, server_folder.as_ref());
|
||||
param.set_int(Param::ServerUid, server_uid as i32);
|
||||
if mime_parser.is_send_by_messenger && context.get_config_bool(Config::MvboxMove) {
|
||||
if mime_parser.is_send_by_messenger
|
||||
&& 0 != context
|
||||
.sql
|
||||
.get_config_int(context, "mvbox_move")
|
||||
.unwrap_or_else(|| 1)
|
||||
{
|
||||
param.set_int(Param::AlsoMove, 1);
|
||||
}
|
||||
job_add(context, Action::MarkseenMdnOnImap, 0, param, 0);
|
||||
@@ -947,7 +982,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;
|
||||
@@ -1005,7 +1040,7 @@ unsafe fn create_or_lookup_group(
|
||||
{
|
||||
let fld_in_reply_to = (*field).fld_data.fld_in_reply_to;
|
||||
if !fld_in_reply_to.is_null() {
|
||||
grpid = to_string_lossy(dc_extract_grpid_from_rfc724_mid_list(
|
||||
grpid = to_string(dc_extract_grpid_from_rfc724_mid_list(
|
||||
(*fld_in_reply_to).mid_list,
|
||||
));
|
||||
}
|
||||
@@ -1016,7 +1051,7 @@ unsafe fn create_or_lookup_group(
|
||||
{
|
||||
let fld_references = (*field).fld_data.fld_references;
|
||||
if !fld_references.is_null() {
|
||||
grpid = to_string_lossy(dc_extract_grpid_from_rfc724_mid_list(
|
||||
grpid = to_string(dc_extract_grpid_from_rfc724_mid_list(
|
||||
(*fld_references).mid_list,
|
||||
));
|
||||
}
|
||||
@@ -1032,16 +1067,16 @@ unsafe fn create_or_lookup_group(
|
||||
to_ids,
|
||||
&mut chat_id,
|
||||
&mut chat_id_blocked,
|
||||
)?;
|
||||
);
|
||||
cleanup(ret_chat_id, ret_chat_id_blocked, chat_id, chat_id_blocked);
|
||||
return Ok(());
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if let Some(optional_field) = mime_parser.lookup_optional_field("Chat-Group-Name") {
|
||||
grpname = Some(dc_decode_header_words(&optional_field));
|
||||
grpname = Some(dc_decode_header_words_safe(&optional_field));
|
||||
}
|
||||
if let Some(optional_field) = mime_parser.lookup_optional_field("Chat-Group-Member-Removed") {
|
||||
X_MrRemoveFromGrp = Some(optional_field);
|
||||
@@ -1133,7 +1168,8 @@ unsafe fn create_or_lookup_group(
|
||||
group_explicitly_left = chat::is_group_explicitly_left(context, &grpid).unwrap_or_default();
|
||||
|
||||
let self_addr = context
|
||||
.get_config(Config::ConfiguredAddr)
|
||||
.sql
|
||||
.get_config(context, "configured_addr")
|
||||
.unwrap_or_default();
|
||||
if chat_id == 0
|
||||
&& !mime_parser.is_mailinglist_message()
|
||||
@@ -1159,7 +1195,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 +1223,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 +1279,7 @@ unsafe fn create_or_lookup_group(
|
||||
} else {
|
||||
chat.param.set(Param::ProfileImage, grpimage);
|
||||
}
|
||||
chat.update_param(context)?;
|
||||
chat.update_param(context).unwrap();
|
||||
send_EVENT_CHAT_MODIFIED = 1;
|
||||
}
|
||||
}
|
||||
@@ -1307,12 +1343,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 +1360,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 +1380,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 +1393,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 +1416,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 +1432,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 +1458,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(
|
||||
@@ -1456,7 +1490,7 @@ fn create_group_record(
|
||||
sql::get_rowid(context, &context.sql, "chats", "grpid", grpid.as_ref())
|
||||
}
|
||||
|
||||
fn create_adhoc_grp_id(context: &Context, member_ids: &[u32]) -> String {
|
||||
fn create_adhoc_grp_id(context: &Context, member_ids: &Vec<u32>) -> String {
|
||||
/* algorithm:
|
||||
- sort normalized, lowercased, e-mail addresses alphabetically
|
||||
- put all e-mail addresses into a single string, separate the address by a single comma
|
||||
@@ -1465,7 +1499,8 @@ fn create_adhoc_grp_id(context: &Context, member_ids: &[u32]) -> String {
|
||||
*/
|
||||
let member_ids_str = join(member_ids.iter().map(|x| x.to_string()), ",");
|
||||
let member_cs = context
|
||||
.get_config(Config::ConfiguredAddr)
|
||||
.sql
|
||||
.get_config(context, "configured_addr")
|
||||
.unwrap_or_else(|| "no-self".to_string())
|
||||
.to_lowercase();
|
||||
|
||||
@@ -1501,10 +1536,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,18 +1591,18 @@ fn search_chat_ids_by_contact_ids(
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
)?;
|
||||
).unwrap(); // TODO: better error handling
|
||||
}
|
||||
}
|
||||
|
||||
Ok(chat_ids)
|
||||
chat_ids
|
||||
}
|
||||
|
||||
fn check_verified_properties(
|
||||
context: &Context,
|
||||
mimeparser: &MimeParser,
|
||||
from_id: u32,
|
||||
to_ids: &[u32],
|
||||
to_ids: &Vec<u32>,
|
||||
) -> Result<()> {
|
||||
let contact = Contact::load_from_db(context, from_id)?;
|
||||
|
||||
@@ -1638,7 +1670,7 @@ fn check_verified_properties(
|
||||
let fp = peerstate.gossip_key_fingerprint.clone();
|
||||
if let Some(fp) = fp {
|
||||
peerstate.set_verified(0, &fp, 2);
|
||||
peerstate.save_to_db(&context.sql, false)?;
|
||||
peerstate.save_to_db(&context.sql, false).unwrap();
|
||||
is_verified = true;
|
||||
}
|
||||
}
|
||||
@@ -1665,7 +1697,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();
|
||||
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 {
|
||||
@@ -1684,13 +1722,13 @@ unsafe fn dc_is_reply_to_known_message(context: &Context, mime_parser: &MimePars
|
||||
if let Some(field) = mime_parser.lookup_field("References") {
|
||||
if (*field).fld_type == MAILIMF_FIELD_REFERENCES as libc::c_int {
|
||||
let fld_references = (*field).fld_data.fld_references;
|
||||
if !fld_references.is_null()
|
||||
&& is_known_rfc724_mid_in_list(
|
||||
if !fld_references.is_null() {
|
||||
if is_known_rfc724_mid_in_list(
|
||||
context,
|
||||
(*(*field).fld_data.fld_references).mid_list,
|
||||
)
|
||||
{
|
||||
return 1;
|
||||
) {
|
||||
return 1;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1704,12 +1742,12 @@ unsafe fn is_known_rfc724_mid_in_list(context: &Context, mid_list: *const clist)
|
||||
}
|
||||
|
||||
for data in &*mid_list {
|
||||
if is_known_rfc724_mid(context, data.cast()) != 0 {
|
||||
if 0 != is_known_rfc724_mid(context, data.cast()) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
false
|
||||
return false;
|
||||
}
|
||||
|
||||
/// Check if a message is a reply to a known message (messenger or non-messenger).
|
||||
@@ -1913,7 +1951,8 @@ unsafe fn add_or_lookup_contact_by_addr(
|
||||
}
|
||||
*check_self = 0;
|
||||
let self_addr = context
|
||||
.get_config(Config::ConfiguredAddr)
|
||||
.sql
|
||||
.get_config(context, "configured_addr")
|
||||
.unwrap_or_default();
|
||||
|
||||
if addr_cmp(self_addr, as_str(addr_spec)) {
|
||||
@@ -1926,15 +1965,17 @@ unsafe fn add_or_lookup_contact_by_addr(
|
||||
/* add addr_spec if missing, update otherwise */
|
||||
let mut display_name_dec = "".to_string();
|
||||
if !display_name_enc.is_null() {
|
||||
let tmp = dc_decode_header_words(as_str(display_name_enc));
|
||||
let tmp = as_str(dc_decode_header_words(display_name_enc));
|
||||
display_name_dec = normalize_name(&tmp);
|
||||
}
|
||||
/*can be NULL*/
|
||||
let row_id = Contact::add_or_lookup(context, display_name_dec, as_str(addr_spec), origin)
|
||||
.map(|(id, _)| id)
|
||||
.unwrap_or_default();
|
||||
if 0 != row_id && !ids.contains(&row_id) {
|
||||
ids.push(row_id);
|
||||
if 0 != row_id {
|
||||
if !ids.contains(&row_id) {
|
||||
ids.push(row_id);
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
@@ -10,16 +10,18 @@ pub struct Simplify {
|
||||
///
|
||||
/// Also return whether not-standard (rfc3676, §4.3) footer is found.
|
||||
fn find_message_footer(lines: &[&str]) -> (usize, bool) {
|
||||
for (ix, &line) in lines.iter().enumerate() {
|
||||
for ix in 0..lines.len() {
|
||||
let line = lines[ix];
|
||||
|
||||
// quoted-printable may encode `-- ` to `-- =20` which is converted
|
||||
// back to `-- `
|
||||
match line {
|
||||
match line.as_ref() {
|
||||
"-- " | "-- " => return (ix, false),
|
||||
"--" | "---" | "----" => return (ix, true),
|
||||
_ => (),
|
||||
}
|
||||
}
|
||||
(lines.len(), false)
|
||||
return (lines.len(), false);
|
||||
}
|
||||
|
||||
impl Simplify {
|
||||
@@ -101,8 +103,10 @@ impl Simplify {
|
||||
if let Some(last_quoted_line) = l_lastQuotedLine {
|
||||
l_last = last_quoted_line;
|
||||
is_cut_at_end = true;
|
||||
if l_last > 1 && is_empty_line(lines[l_last - 1]) {
|
||||
l_last -= 1
|
||||
if l_last > 1 {
|
||||
if is_empty_line(lines[l_last - 1]) {
|
||||
l_last -= 1
|
||||
}
|
||||
}
|
||||
if l_last > 1 {
|
||||
let line = lines[l_last - 1];
|
||||
@@ -142,8 +146,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) {
|
||||
@@ -201,7 +205,7 @@ fn is_quoted_headline(buf: &str) -> bool {
|
||||
}
|
||||
|
||||
fn is_plain_quote(buf: &str) -> bool {
|
||||
buf.starts_with('>')
|
||||
buf.starts_with(">")
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
|
||||
@@ -4,7 +4,7 @@ use std::ffi::CString;
|
||||
use std::ptr;
|
||||
|
||||
use charset::Charset;
|
||||
use libc::free;
|
||||
use libc::{free, strlen};
|
||||
use mmime::mailmime::decode::mailmime_encoded_phrase_parse;
|
||||
use mmime::other::*;
|
||||
use percent_encoding::{percent_decode, utf8_percent_encode, AsciiSet, CONTROLS};
|
||||
@@ -68,7 +68,28 @@ fn quote_word(word: &[u8]) -> String {
|
||||
* Encode/decode header words, RFC 2047
|
||||
******************************************************************************/
|
||||
|
||||
pub(crate) fn dc_decode_header_words(input: &str) -> String {
|
||||
pub unsafe fn dc_decode_header_words(in_0: *const libc::c_char) -> *mut libc::c_char {
|
||||
if in_0.is_null() {
|
||||
return ptr::null_mut();
|
||||
}
|
||||
let mut out: *mut libc::c_char = ptr::null_mut();
|
||||
let mut cur_token = 0;
|
||||
let r: libc::c_int = mailmime_encoded_phrase_parse(
|
||||
b"iso-8859-1\x00" as *const u8 as *const libc::c_char,
|
||||
in_0,
|
||||
strlen(in_0),
|
||||
&mut cur_token,
|
||||
b"utf-8\x00" as *const u8 as *const libc::c_char,
|
||||
&mut out,
|
||||
);
|
||||
if r != MAILIMF_NO_ERROR as libc::c_int || out.is_null() {
|
||||
out = dc_strdup(in_0)
|
||||
}
|
||||
|
||||
out
|
||||
}
|
||||
|
||||
pub fn dc_decode_header_words_safe(input: &str) -> String {
|
||||
static FROM_ENCODING: &[u8] = b"iso-8859-1\x00";
|
||||
static TO_ENCODING: &[u8] = b"utf-8\x00";
|
||||
let mut out = ptr::null_mut();
|
||||
@@ -86,7 +107,7 @@ pub(crate) fn dc_decode_header_words(input: &str) -> String {
|
||||
if r as u32 != MAILIMF_NO_ERROR || out.is_null() {
|
||||
input.to_string()
|
||||
} else {
|
||||
let res = to_string_lossy(out);
|
||||
let res = to_string(out);
|
||||
free(out.cast());
|
||||
res
|
||||
}
|
||||
@@ -156,31 +177,60 @@ pub fn dc_decode_ext_header(to_decode: &[u8]) -> Cow<str> {
|
||||
mod tests {
|
||||
use super::*;
|
||||
|
||||
use libc::strcmp;
|
||||
use std::ffi::CStr;
|
||||
|
||||
#[test]
|
||||
fn test_dc_decode_header_words() {
|
||||
assert_eq!(
|
||||
dc_decode_header_words("=?utf-8?B?dGVzdMOkw7bDvC50eHQ=?="),
|
||||
std::string::String::from_utf8(b"test\xc3\xa4\xc3\xb6\xc3\xbc.txt".to_vec()).unwrap(),
|
||||
);
|
||||
|
||||
assert_eq!(dc_decode_header_words("just ascii test"), "just ascii test");
|
||||
|
||||
assert_eq!(dc_encode_header_words("abcdef"), "abcdef");
|
||||
|
||||
let r = dc_encode_header_words(
|
||||
std::string::String::from_utf8(b"test\xc3\xa4\xc3\xb6\xc3\xbc.txt".to_vec()).unwrap(),
|
||||
);
|
||||
assert!(r.starts_with("=?utf-8"));
|
||||
|
||||
assert_eq!(
|
||||
dc_decode_header_words(&r),
|
||||
std::string::String::from_utf8(b"test\xc3\xa4\xc3\xb6\xc3\xbc.txt".to_vec()).unwrap(),
|
||||
);
|
||||
|
||||
assert_eq!(
|
||||
dc_decode_header_words("=?ISO-8859-1?Q?attachment=3B=0D=0A_filename=3D?= =?ISO-8859-1?Q?=22test=E4=F6=FC=2Etxt=22=3B=0D=0A_size=3D39?="),
|
||||
std::string::String::from_utf8(b"attachment;\r\n filename=\"test\xc3\xa4\xc3\xb6\xc3\xbc.txt\";\r\n size=39".to_vec()).unwrap(),
|
||||
unsafe {
|
||||
let mut buf1: *mut libc::c_char = dc_decode_header_words(
|
||||
b"=?utf-8?B?dGVzdMOkw7bDvC50eHQ=?=\x00" as *const u8 as *const libc::c_char,
|
||||
);
|
||||
assert_eq!(
|
||||
strcmp(
|
||||
buf1,
|
||||
b"test\xc3\xa4\xc3\xb6\xc3\xbc.txt\x00" as *const u8 as *const libc::c_char
|
||||
),
|
||||
0
|
||||
);
|
||||
free(buf1 as *mut libc::c_void);
|
||||
|
||||
buf1 =
|
||||
dc_decode_header_words(b"just ascii test\x00" as *const u8 as *const libc::c_char);
|
||||
assert_eq!(CStr::from_ptr(buf1).to_str().unwrap(), "just ascii test");
|
||||
free(buf1 as *mut libc::c_void);
|
||||
|
||||
assert_eq!(dc_encode_header_words("abcdef"), "abcdef");
|
||||
|
||||
let r = dc_encode_header_words(
|
||||
std::string::String::from_utf8(b"test\xc3\xa4\xc3\xb6\xc3\xbc.txt".to_vec())
|
||||
.unwrap(),
|
||||
);
|
||||
assert!(r.starts_with("=?utf-8"));
|
||||
|
||||
buf1 = r.strdup();
|
||||
let buf2: *mut libc::c_char = dc_decode_header_words(buf1);
|
||||
assert_eq!(
|
||||
strcmp(
|
||||
buf2,
|
||||
b"test\xc3\xa4\xc3\xb6\xc3\xbc.txt\x00" as *const u8 as *const libc::c_char
|
||||
),
|
||||
0
|
||||
);
|
||||
free(buf2 as *mut libc::c_void);
|
||||
|
||||
buf1 = dc_decode_header_words(
|
||||
b"=?ISO-8859-1?Q?attachment=3B=0D=0A_filename=3D?= =?ISO-8859-1?Q?=22test=E4=F6=FC=2Etxt=22=3B=0D=0A_size=3D39?=\x00" as *const u8 as *const libc::c_char
|
||||
);
|
||||
assert_eq!(
|
||||
strcmp(
|
||||
buf1,
|
||||
b"attachment;\r\n filename=\"test\xc3\xa4\xc3\xb6\xc3\xbc.txt\";\r\n size=39\x00" as *const u8 as *const libc::c_char,
|
||||
|
||||
),
|
||||
0
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
@@ -243,7 +293,7 @@ mod tests {
|
||||
#[test]
|
||||
fn test_dc_header_roundtrip(input: String) {
|
||||
let encoded = dc_encode_header_words(&input);
|
||||
let decoded = dc_decode_header_words(&encoded);
|
||||
let decoded = dc_decode_header_words_safe(&encoded);
|
||||
|
||||
assert_eq!(input, decoded);
|
||||
}
|
||||
|
||||
306
src/dc_tools.rs
306
src/dc_tools.rs
@@ -17,7 +17,6 @@ use rand::{thread_rng, Rng};
|
||||
|
||||
use crate::context::Context;
|
||||
use crate::error::Error;
|
||||
use crate::events::Event;
|
||||
|
||||
pub(crate) fn dc_exactly_one_bit_set(v: libc::c_int) -> bool {
|
||||
0 != v && 0 == v & (v - 1)
|
||||
@@ -30,11 +29,11 @@ pub(crate) fn dc_exactly_one_bit_set(v: libc::c_int) -> bool {
|
||||
/// # Examples
|
||||
///
|
||||
/// ```
|
||||
/// use deltachat::dc_tools::{dc_strdup, to_string_lossy};
|
||||
/// use deltachat::dc_tools::{dc_strdup, to_string};
|
||||
/// unsafe {
|
||||
/// let str_a = b"foobar\x00" as *const u8 as *const libc::c_char;
|
||||
/// let str_a_copy = dc_strdup(str_a);
|
||||
/// assert_eq!(to_string_lossy(str_a_copy), "foobar");
|
||||
/// assert_eq!(to_string(str_a_copy), "foobar");
|
||||
/// assert_ne!(str_a, str_a_copy);
|
||||
/// }
|
||||
/// ```
|
||||
@@ -51,6 +50,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> {
|
||||
@@ -65,7 +141,7 @@ pub(crate) fn dc_truncate(buf: &str, approx_chars: usize, do_unwrap: bool) -> Co
|
||||
.unwrap_or_default();
|
||||
|
||||
if let Some(index) = buf[..end_pos].rfind(|c| c == ' ' || c == '\n') {
|
||||
Cow::Owned(format!("{}{}", &buf[..=index], ellipse))
|
||||
Cow::Owned(format!("{}{}", &buf[..index + 1], ellipse))
|
||||
} else {
|
||||
Cow::Owned(format!("{}{}", &buf[..end_pos], ellipse))
|
||||
}
|
||||
@@ -74,18 +150,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 {
|
||||
@@ -240,14 +334,14 @@ fn encode_66bits_as_base64(v1: u32, v2: u32, fill: u32) -> String {
|
||||
enc.write_u8(((fill & 0x3) as u8) << 6).unwrap();
|
||||
enc.finish().unwrap();
|
||||
}
|
||||
assert_eq!(wrapped_writer.pop(), Some(b'A')); // Remove last "A"
|
||||
assert_eq!(wrapped_writer.pop(), Some('A' as u8)); // Remove last "A"
|
||||
String::from_utf8(wrapped_writer).unwrap()
|
||||
}
|
||||
|
||||
pub(crate) fn dc_create_incoming_rfc724_mid(
|
||||
message_timestamp: i64,
|
||||
contact_id_from: u32,
|
||||
contact_ids_to: &[u32],
|
||||
contact_ids_to: &Vec<u32>,
|
||||
) -> Option<String> {
|
||||
if contact_ids_to.is_empty() {
|
||||
return None;
|
||||
@@ -329,10 +423,10 @@ fn get_safe_basename(filename: &str) -> String {
|
||||
// this might be a path that comes in from another operating system
|
||||
let mut index: usize = 0;
|
||||
|
||||
if let Some(unix_index) = filename.rfind('/') {
|
||||
if let Some(unix_index) = filename.rfind("/") {
|
||||
index = unix_index + 1;
|
||||
}
|
||||
if let Some(win_index) = filename.rfind('\\') {
|
||||
if let Some(win_index) = filename.rfind("\\") {
|
||||
index = max(index, win_index + 1);
|
||||
}
|
||||
if index >= filename.len() {
|
||||
@@ -345,7 +439,7 @@ fn get_safe_basename(filename: &str) -> String {
|
||||
|
||||
pub fn dc_derive_safe_stem_ext(filename: &str) -> (String, String) {
|
||||
let basename = get_safe_basename(&filename);
|
||||
let (mut stem, mut ext) = if let Some(index) = basename.rfind('.') {
|
||||
let (mut stem, mut ext) = if let Some(index) = basename.rfind(".") {
|
||||
(
|
||||
basename[0..index].to_string(),
|
||||
basename[index..].to_string(),
|
||||
@@ -418,14 +512,10 @@ pub(crate) fn dc_delete_file(context: &Context, path: impl AsRef<std::path::Path
|
||||
return false;
|
||||
}
|
||||
|
||||
let dpath = format!("{}", path.as_ref().to_string_lossy());
|
||||
match fs::remove_file(path_abs) {
|
||||
Ok(_) => {
|
||||
context.call_cb(Event::DeletedBlobFile(dpath));
|
||||
true
|
||||
}
|
||||
Ok(_) => true,
|
||||
Err(_err) => {
|
||||
warn!(context, "Cannot delete \"{}\".", dpath);
|
||||
warn!(context, "Cannot delete \"{}\".", path.as_ref().display());
|
||||
false
|
||||
}
|
||||
}
|
||||
@@ -506,25 +596,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,
|
||||
@@ -561,10 +632,7 @@ fn dc_make_rel_path(context: &Context, path: &mut String) {
|
||||
.map(|s| path.starts_with(s))
|
||||
.unwrap_or_default()
|
||||
{
|
||||
*path = path.replace(
|
||||
context.get_blobdir().to_str().unwrap_or_default(),
|
||||
"$BLOBDIR",
|
||||
);
|
||||
*path = path.replace(context.get_blobdir().to_str().unwrap(), "$BLOBDIR");
|
||||
}
|
||||
}
|
||||
|
||||
@@ -725,6 +793,22 @@ impl<T: AsRef<str>> StrExt for T {
|
||||
}
|
||||
}
|
||||
|
||||
pub fn to_string(s: *const libc::c_char) -> String {
|
||||
if s.is_null() {
|
||||
return "".into();
|
||||
}
|
||||
|
||||
let cstr = unsafe { CStr::from_ptr(s) };
|
||||
|
||||
cstr.to_str().map(|s| s.to_string()).unwrap_or_else(|err| {
|
||||
panic!(
|
||||
"Non utf8 string: '{:?}' ({:?})",
|
||||
cstr.to_string_lossy(),
|
||||
err
|
||||
);
|
||||
})
|
||||
}
|
||||
|
||||
pub fn to_string_lossy(s: *const libc::c_char) -> String {
|
||||
if s.is_null() {
|
||||
return "".into();
|
||||
@@ -732,15 +816,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 +1023,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 +1146,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 +1176,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 +1347,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 +1426,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(res),
|
||||
to_string(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");
|
||||
@@ -1378,9 +1519,20 @@ mod tests {
|
||||
assert!(listflags_has(listflags, DC_GCL_ADD_SELF) == true);
|
||||
let listflags: u32 = DC_GCL_VERIFIED_ONLY.try_into().unwrap();
|
||||
assert!(listflags_has(listflags, DC_GCL_ADD_SELF) == false);
|
||||
}
|
||||
|
||||
let listflags: u32 = DC_GCL_ADD_DRAFTS.try_into().unwrap();
|
||||
assert!(listflags_has(listflags, DC_GCL_ADD_ALLDONE_HINT) == false);
|
||||
assert!(listflags_has(listflags, 0x8) == true);
|
||||
#[test]
|
||||
fn test_dc_remove_cr_chars() {
|
||||
unsafe {
|
||||
let input = "foo\r\nbar".strdup();
|
||||
dc_remove_cr_chars(input);
|
||||
assert_eq!("foo\nbar", to_string(input));
|
||||
free(input.cast());
|
||||
|
||||
let input = "\rfoo\r\rbar\r".strdup();
|
||||
dc_remove_cr_chars(input);
|
||||
assert_eq!("foobar", to_string(input));
|
||||
free(input.cast());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
195
src/e2ee.rs
195
src/e2ee.rs
@@ -22,13 +22,14 @@ use num_traits::FromPrimitive;
|
||||
use crate::aheader::*;
|
||||
use crate::config::Config;
|
||||
use crate::context::Context;
|
||||
use crate::dc_mimeparser::*;
|
||||
use crate::dc_tools::*;
|
||||
use crate::error::*;
|
||||
use crate::key::*;
|
||||
use crate::keyring::*;
|
||||
use crate::mimefactory::MimeFactory;
|
||||
use crate::peerstate::*;
|
||||
use crate::pgp;
|
||||
use crate::pgp::*;
|
||||
use crate::securejoin::handle_degrade_event;
|
||||
use crate::wrapmime;
|
||||
use crate::wrapmime::*;
|
||||
@@ -46,9 +47,12 @@ pub struct EncryptHelper {
|
||||
|
||||
impl EncryptHelper {
|
||||
pub fn new(context: &Context) -> Result<EncryptHelper> {
|
||||
let prefer_encrypt =
|
||||
EncryptPreference::from_i32(context.get_config_int(Config::E2eeEnabled))
|
||||
.unwrap_or_default();
|
||||
let prefer_encrypt = context
|
||||
.sql
|
||||
.get_config_int(&context, "e2ee_enabled")
|
||||
.and_then(EncryptPreference::from_i32)
|
||||
.unwrap_or_default();
|
||||
|
||||
let addr = match context.get_config(Config::ConfiguredAddr) {
|
||||
None => {
|
||||
bail!("addr not configured!");
|
||||
@@ -211,7 +215,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(),
|
||||
@@ -262,80 +266,103 @@ pub fn try_decrypt(
|
||||
context: &Context,
|
||||
in_out_message: *mut Mailmime,
|
||||
) -> Result<(bool, HashSet<String>, HashSet<String>)> {
|
||||
// just a pointer into mailmime structure, must not be freed
|
||||
let imffields = mailmime_find_mailimf_fields(in_out_message);
|
||||
ensure!(
|
||||
!in_out_message.is_null() && !imffields.is_null(),
|
||||
"corrupt invalid mime inputs"
|
||||
);
|
||||
|
||||
let from = wrapmime::get_field_from(imffields)?;
|
||||
let message_time = wrapmime::get_field_date(imffields)?;
|
||||
|
||||
let mut peerstate = None;
|
||||
let autocryptheader = Aheader::from_imffields(&from, imffields);
|
||||
|
||||
if message_time > 0 {
|
||||
peerstate = Peerstate::from_addr(context, &context.sql, &from);
|
||||
|
||||
if let Some(ref mut peerstate) = peerstate {
|
||||
if let Some(ref header) = autocryptheader {
|
||||
peerstate.apply_header(&header, message_time);
|
||||
peerstate.save_to_db(&context.sql, false)?;
|
||||
} else if message_time > peerstate.last_seen_autocrypt
|
||||
&& !contains_report(in_out_message)
|
||||
{
|
||||
peerstate.degrade_encryption(message_time);
|
||||
peerstate.save_to_db(&context.sql, false)?;
|
||||
}
|
||||
} else if let Some(ref header) = autocryptheader {
|
||||
let p = Peerstate::from_header(context, header, message_time);
|
||||
p.save_to_db(&context.sql, true)?;
|
||||
peerstate = Some(p);
|
||||
}
|
||||
}
|
||||
/* possibly perform decryption */
|
||||
let mut private_keyring = Keyring::default();
|
||||
let mut public_keyring_for_validate = Keyring::default();
|
||||
let mut encrypted = false;
|
||||
let mut signatures = HashSet::default();
|
||||
let mut gossipped_addr = HashSet::default();
|
||||
|
||||
let self_addr = context.get_config(Config::ConfiguredAddr);
|
||||
// just a pointer into mailmime structure, must not be freed
|
||||
let imffields = unsafe { mailmime_find_mailimf_fields(in_out_message) };
|
||||
let mut message_time = 0;
|
||||
let mut from = None;
|
||||
let mut private_keyring = Keyring::default();
|
||||
let mut public_keyring_for_validate = Keyring::default();
|
||||
let mut gossip_headers = ptr::null_mut();
|
||||
|
||||
if let Some(self_addr) = self_addr {
|
||||
if private_keyring.load_self_private_for_decrypting(context, self_addr, &context.sql) {
|
||||
if peerstate.as_ref().map(|p| p.last_seen).unwrap_or_else(|| 0) == 0 {
|
||||
peerstate = Peerstate::from_addr(&context, &context.sql, &from);
|
||||
}
|
||||
if let Some(ref peerstate) = peerstate {
|
||||
if peerstate.degrade_event.is_some() {
|
||||
handle_degrade_event(context, &peerstate)?;
|
||||
}
|
||||
if let Some(ref key) = peerstate.gossip_key {
|
||||
public_keyring_for_validate.add_ref(key);
|
||||
}
|
||||
if let Some(ref key) = peerstate.public_key {
|
||||
public_keyring_for_validate.add_ref(key);
|
||||
// XXX do wrapmime:: helper for the next block
|
||||
if !(in_out_message.is_null() || imffields.is_null()) {
|
||||
let mut field = mailimf_find_field(imffields, MAILIMF_FIELD_FROM as libc::c_int);
|
||||
|
||||
if !field.is_null() && unsafe { !(*field).fld_data.fld_from.is_null() } {
|
||||
let mb_list = unsafe { (*(*field).fld_data.fld_from).frm_mb_list };
|
||||
from = mailimf_find_first_addr(mb_list);
|
||||
}
|
||||
|
||||
field = mailimf_find_field(imffields, MAILIMF_FIELD_ORIG_DATE as libc::c_int);
|
||||
if !field.is_null() && unsafe { !(*field).fld_data.fld_orig_date.is_null() } {
|
||||
let orig_date = unsafe { (*field).fld_data.fld_orig_date };
|
||||
|
||||
if !orig_date.is_null() {
|
||||
let dt = unsafe { (*orig_date).dt_date_time };
|
||||
message_time = dc_timestamp_from_date(dt);
|
||||
if message_time != 0 && message_time > time() {
|
||||
message_time = time()
|
||||
}
|
||||
}
|
||||
}
|
||||
let mut peerstate = None;
|
||||
let autocryptheader = from
|
||||
.as_ref()
|
||||
.and_then(|from| Aheader::from_imffields(from, imffields));
|
||||
if message_time > 0 {
|
||||
if let Some(ref from) = from {
|
||||
peerstate = Peerstate::from_addr(context, &context.sql, from);
|
||||
|
||||
let mut gossip_headers = ptr::null_mut();
|
||||
encrypted = decrypt_if_autocrypt_message(
|
||||
context,
|
||||
in_out_message,
|
||||
&private_keyring,
|
||||
&public_keyring_for_validate,
|
||||
&mut signatures,
|
||||
&mut gossip_headers,
|
||||
)?;
|
||||
if !gossip_headers.is_null() {
|
||||
gossipped_addr =
|
||||
update_gossip_peerstates(context, message_time, imffields, gossip_headers)?;
|
||||
unsafe { mailimf_fields_free(gossip_headers) };
|
||||
if let Some(ref mut peerstate) = peerstate {
|
||||
if let Some(ref header) = autocryptheader {
|
||||
peerstate.apply_header(&header, message_time);
|
||||
peerstate.save_to_db(&context.sql, false).unwrap();
|
||||
} else if message_time > peerstate.last_seen_autocrypt
|
||||
&& !contains_report(in_out_message)
|
||||
{
|
||||
peerstate.degrade_encryption(message_time);
|
||||
peerstate.save_to_db(&context.sql, false).unwrap();
|
||||
}
|
||||
} else if let Some(ref header) = autocryptheader {
|
||||
let p = Peerstate::from_header(context, header, message_time);
|
||||
p.save_to_db(&context.sql, true).unwrap();
|
||||
peerstate = Some(p);
|
||||
}
|
||||
}
|
||||
}
|
||||
/* load private key for decryption */
|
||||
let self_addr = context.get_config(Config::ConfiguredAddr);
|
||||
if let Some(self_addr) = self_addr {
|
||||
if private_keyring.load_self_private_for_decrypting(context, self_addr, &context.sql) {
|
||||
if peerstate.as_ref().map(|p| p.last_seen).unwrap_or_else(|| 0) == 0 {
|
||||
peerstate =
|
||||
Peerstate::from_addr(&context, &context.sql, &from.unwrap_or_default());
|
||||
}
|
||||
if let Some(ref peerstate) = peerstate {
|
||||
if peerstate.degrade_event.is_some() {
|
||||
handle_degrade_event(context, &peerstate)?;
|
||||
}
|
||||
if let Some(ref key) = peerstate.gossip_key {
|
||||
public_keyring_for_validate.add_ref(key);
|
||||
}
|
||||
if let Some(ref key) = peerstate.public_key {
|
||||
public_keyring_for_validate.add_ref(key);
|
||||
}
|
||||
}
|
||||
|
||||
encrypted = decrypt_if_autocrypt_message(
|
||||
context,
|
||||
in_out_message,
|
||||
&private_keyring,
|
||||
&public_keyring_for_validate,
|
||||
&mut signatures,
|
||||
&mut gossip_headers,
|
||||
)?;
|
||||
if !gossip_headers.is_null() {
|
||||
gossipped_addr =
|
||||
update_gossip_peerstates(context, message_time, imffields, gossip_headers)?;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
if !gossip_headers.is_null() {
|
||||
unsafe { mailimf_fields_free(gossip_headers) };
|
||||
}
|
||||
|
||||
Ok((encrypted, signatures, gossipped_addr))
|
||||
}
|
||||
|
||||
@@ -398,7 +425,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,
|
||||
@@ -533,7 +560,7 @@ fn decrypt_if_autocrypt_message(
|
||||
// XXX better return parsed headers so that upstream
|
||||
// does not need to dive into mmime-stuff again.
|
||||
unsafe {
|
||||
if (*ret_gossip_headers).is_null() && !ret_valid_signatures.is_empty() {
|
||||
if (*ret_gossip_headers).is_null() && ret_valid_signatures.len() > 0 {
|
||||
let mut dummy: libc::size_t = 0;
|
||||
let mut test: *mut mailimf_fields = ptr::null_mut();
|
||||
if mailimf_envelope_and_optional_fields_parse(
|
||||
@@ -573,16 +600,22 @@ fn decrypt_part(
|
||||
mime_transfer_encoding = enc;
|
||||
}
|
||||
|
||||
let data: Vec<u8> = wrapmime::decode_dt_data(mime_data, mime_transfer_encoding)?;
|
||||
let (decoded_data, decoded_data_bytes) =
|
||||
wrapmime::decode_dt_data(mime_data, mime_transfer_encoding)?;
|
||||
|
||||
// encrypted, non-NULL decoded data in decoded_data now ...
|
||||
// Note that we need to take care of freeing decoded_data ourself,
|
||||
// after encryption has been attempted.
|
||||
let mut ret_decrypted_mime = ptr::null_mut();
|
||||
|
||||
if has_decrypted_pgp_armor(&data) {
|
||||
ensure!(!decoded_data.is_null(), "Missing data");
|
||||
let data = unsafe { std::slice::from_raw_parts(decoded_data as *const u8, decoded_data_bytes) };
|
||||
if has_decrypted_pgp_armor(data) {
|
||||
// we should only have one decryption happening
|
||||
ensure!(ret_valid_signatures.is_empty(), "corrupt signatures");
|
||||
|
||||
let plain = match pgp::pk_decrypt(
|
||||
&data,
|
||||
let plain = match dc_pgp_pk_decrypt(
|
||||
data,
|
||||
&private_keyring,
|
||||
&public_keyring_for_validate,
|
||||
Some(ret_valid_signatures),
|
||||
@@ -591,7 +624,10 @@ fn decrypt_part(
|
||||
ensure!(!ret_valid_signatures.is_empty(), "no valid signatures");
|
||||
plain
|
||||
}
|
||||
Err(err) => bail!("could not decrypt: {}", err),
|
||||
Err(err) => {
|
||||
unsafe { mmap_string_unref(decoded_data) };
|
||||
bail!("could not decrypt: {}", err)
|
||||
}
|
||||
};
|
||||
let plain_bytes = plain.len();
|
||||
let plain_buf = plain.as_ptr() as *const libc::c_char;
|
||||
@@ -619,6 +655,7 @@ fn decrypt_part(
|
||||
ret_decrypted_mime = decrypted_mime;
|
||||
}
|
||||
}
|
||||
unsafe { mmap_string_unref(decoded_data) };
|
||||
|
||||
Ok(ret_decrypted_mime)
|
||||
}
|
||||
@@ -686,12 +723,12 @@ fn contains_report(mime: *mut Mailmime) -> bool {
|
||||
/// If this succeeds you are also guaranteed that the
|
||||
/// [Config::ConfiguredAddr] is configured, this address is returned.
|
||||
pub fn ensure_secret_key_exists(context: &Context) -> Result<String> {
|
||||
let self_addr = context.get_config(Config::ConfiguredAddr).ok_or_else(|| {
|
||||
format_err!(concat!(
|
||||
let self_addr = context
|
||||
.get_config(Config::ConfiguredAddr)
|
||||
.ok_or(format_err!(concat!(
|
||||
"Failed to get self address, ",
|
||||
"cannot ensure secret key if not configured."
|
||||
))
|
||||
})?;
|
||||
)))?;
|
||||
load_or_generate_self_public_key(context, &self_addr)?;
|
||||
Ok(self_addr)
|
||||
}
|
||||
|
||||
12
src/error.rs
12
src/error.rs
@@ -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) => {
|
||||
|
||||
@@ -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 {
|
||||
@@ -40,30 +42,6 @@ pub enum Event {
|
||||
#[strum(props(id = "103"))]
|
||||
SmtpMessageSent(String),
|
||||
|
||||
/// Emitted when an IMAP message has been marked as deleted
|
||||
///
|
||||
/// @return 0
|
||||
#[strum(props(id = "104"))]
|
||||
ImapMessageDeleted(String),
|
||||
|
||||
/// Emitted when an IMAP message has been moved
|
||||
///
|
||||
/// @return 0
|
||||
#[strum(props(id = "105"))]
|
||||
ImapMessageMoved(String),
|
||||
|
||||
/// Emitted when an new file in the $BLOBDIR was created
|
||||
///
|
||||
/// @return 0
|
||||
#[strum(props(id = "150"))]
|
||||
NewBlobFile(String),
|
||||
|
||||
/// Emitted when an new file in the $BLOBDIR was created
|
||||
///
|
||||
/// @return 0
|
||||
#[strum(props(id = "151"))]
|
||||
DeletedBlobFile(String),
|
||||
|
||||
/// The library-user should write a warning string to the log.
|
||||
/// Passed to the callback given to dc_context_new().
|
||||
///
|
||||
@@ -235,4 +213,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 },
|
||||
}
|
||||
|
||||
625
src/imap.rs
625
src/imap.rs
@@ -8,14 +8,12 @@ use std::time::{Duration, SystemTime};
|
||||
use crate::constants::*;
|
||||
use crate::context::Context;
|
||||
use crate::dc_receive_imf::dc_receive_imf;
|
||||
use crate::error::Error;
|
||||
use crate::events::Event;
|
||||
use crate::job::{connect_to_inbox, job_add, Action};
|
||||
use crate::login_param::{dc_build_tls, CertificateChecks, LoginParam};
|
||||
use crate::job::{job_add, Action};
|
||||
use crate::login_param::LoginParam;
|
||||
use crate::message::{self, update_msg_move_state, update_server_uid};
|
||||
use crate::oauth2::dc_get_oauth2_access_token;
|
||||
use crate::param::Params;
|
||||
use crate::wrapmime;
|
||||
|
||||
const DC_IMAP_SEEN: usize = 0x0001;
|
||||
|
||||
@@ -29,6 +27,7 @@ pub enum ImapResult {
|
||||
|
||||
const PREFETCH_FLAGS: &str = "(UID ENVELOPE)";
|
||||
const BODY_FLAGS: &str = "(FLAGS BODY.PEEK[])";
|
||||
const FETCH_FLAGS: &str = "(FLAGS)";
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct Imap {
|
||||
@@ -108,16 +107,20 @@ impl Client {
|
||||
pub fn connect_secure<A: net::ToSocketAddrs, S: AsRef<str>>(
|
||||
addr: A,
|
||||
domain: S,
|
||||
certificate_checks: CertificateChecks,
|
||||
) -> imap::error::Result<Self> {
|
||||
let stream = net::TcpStream::connect(addr)?;
|
||||
let tls = dc_build_tls(certificate_checks).unwrap();
|
||||
let tls = native_tls::TlsConnector::builder()
|
||||
// see also: https://github.com/deltachat/deltachat-core-rust/issues/203
|
||||
.danger_accept_invalid_certs(true)
|
||||
.danger_accept_invalid_hostnames(true)
|
||||
.build()
|
||||
.unwrap();
|
||||
|
||||
let 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,20 +128,19 @@ 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))
|
||||
}
|
||||
|
||||
pub fn secure<S: AsRef<str>>(
|
||||
self,
|
||||
domain: S,
|
||||
certificate_checks: CertificateChecks,
|
||||
) -> imap::error::Result<Client> {
|
||||
pub fn secure<S: AsRef<str>>(self, domain: S) -> imap::error::Result<Client> {
|
||||
match self {
|
||||
Client::Insecure(client, stream) => {
|
||||
let tls = dc_build_tls(certificate_checks).unwrap();
|
||||
let tls = native_tls::TlsConnector::builder()
|
||||
.danger_accept_invalid_hostnames(true)
|
||||
.build()
|
||||
.unwrap();
|
||||
|
||||
let client_sec = client.secure(domain, &tls)?;
|
||||
|
||||
@@ -318,7 +320,6 @@ struct ImapConfig {
|
||||
pub imap_port: u16,
|
||||
pub imap_user: String,
|
||||
pub imap_pw: String,
|
||||
pub certificate_checks: CertificateChecks,
|
||||
pub server_flags: usize,
|
||||
pub selected_folder: Option<String>,
|
||||
pub selected_mailbox: Option<imap::types::Mailbox>,
|
||||
@@ -331,13 +332,12 @@ struct ImapConfig {
|
||||
|
||||
impl Default for ImapConfig {
|
||||
fn default() -> Self {
|
||||
ImapConfig {
|
||||
let cfg = ImapConfig {
|
||||
addr: "".into(),
|
||||
imap_server: "".into(),
|
||||
imap_port: 0,
|
||||
imap_user: "".into(),
|
||||
imap_pw: "".into(),
|
||||
certificate_checks: Default::default(),
|
||||
server_flags: 0,
|
||||
selected_folder: None,
|
||||
selected_mailbox: None,
|
||||
@@ -346,7 +346,9 @@ impl Default for ImapConfig {
|
||||
has_xlist: false,
|
||||
imap_delimiter: '.',
|
||||
watch_folder: None,
|
||||
}
|
||||
};
|
||||
|
||||
cfg
|
||||
}
|
||||
}
|
||||
|
||||
@@ -384,7 +386,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 {
|
||||
@@ -394,7 +396,7 @@ impl Imap {
|
||||
|
||||
Client::connect_insecure((imap_server, imap_port)).and_then(|client| {
|
||||
if (server_flags & DC_LP_IMAP_SOCKET_STARTTLS) != 0 {
|
||||
client.secure(imap_server, config.certificate_checks)
|
||||
client.secure(imap_server)
|
||||
} else {
|
||||
Ok(client)
|
||||
}
|
||||
@@ -404,11 +406,7 @@ impl Imap {
|
||||
let imap_server: &str = config.imap_server.as_ref();
|
||||
let imap_port = config.imap_port;
|
||||
|
||||
Client::connect_secure(
|
||||
(imap_server, imap_port),
|
||||
imap_server,
|
||||
config.certificate_checks,
|
||||
)
|
||||
Client::connect_secure((imap_server, imap_port), imap_server)
|
||||
};
|
||||
|
||||
let login_res = match connection_res {
|
||||
@@ -535,7 +533,6 @@ impl Imap {
|
||||
config.imap_port = imap_port;
|
||||
config.imap_user = imap_user.to_string();
|
||||
config.imap_pw = imap_pw.to_string();
|
||||
config.certificate_checks = lp.imap_certificate_checks;
|
||||
config.server_flags = server_flags;
|
||||
}
|
||||
|
||||
@@ -598,9 +595,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 +613,9 @@ impl Imap {
|
||||
break;
|
||||
}
|
||||
}
|
||||
true
|
||||
1
|
||||
} else {
|
||||
false
|
||||
0
|
||||
}
|
||||
}
|
||||
|
||||
@@ -684,7 +681,7 @@ impl Imap {
|
||||
}
|
||||
}
|
||||
} else {
|
||||
unreachable!();
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -693,20 +690,12 @@ impl Imap {
|
||||
|
||||
fn get_config_last_seen_uid<S: AsRef<str>>(&self, context: &Context, folder: S) -> (u32, u32) {
|
||||
let key = format!("imap.mailbox.{}", folder.as_ref());
|
||||
if let Some(entry) = context.sql.get_raw_config(context, &key) {
|
||||
if let Some(entry) = context.sql.get_config(context, &key) {
|
||||
// the entry has the format `imap.mailbox.<folder>=<uidvalidity>:<lastseenuid>`
|
||||
let mut parts = entry.split(':');
|
||||
(
|
||||
parts
|
||||
.next()
|
||||
.unwrap_or_default()
|
||||
.parse()
|
||||
.unwrap_or_else(|_| 0),
|
||||
parts
|
||||
.next()
|
||||
.unwrap_or_default()
|
||||
.parse()
|
||||
.unwrap_or_else(|_| 0),
|
||||
parts.next().unwrap().parse().unwrap_or_else(|_| 0),
|
||||
parts.next().unwrap().parse().unwrap_or_else(|_| 0),
|
||||
)
|
||||
} else {
|
||||
(0, 0)
|
||||
@@ -750,7 +739,7 @@ impl Imap {
|
||||
return 0;
|
||||
}
|
||||
|
||||
if mailbox.uid_validity.unwrap_or_default() != uid_validity {
|
||||
if mailbox.uid_validity.unwrap() != uid_validity {
|
||||
// first time this folder is selected or UIDVALIDITY has changed, init lastseenuid and save it to config
|
||||
|
||||
if mailbox.exists == 0 {
|
||||
@@ -760,12 +749,7 @@ impl Imap {
|
||||
// id we do not do this here, we'll miss the first message
|
||||
// as we will get in here again and fetch from lastseenuid+1 then
|
||||
|
||||
self.set_config_last_seen_uid(
|
||||
context,
|
||||
&folder,
|
||||
mailbox.uid_validity.unwrap_or_default(),
|
||||
0,
|
||||
);
|
||||
self.set_config_last_seen_uid(context, &folder, mailbox.uid_validity.unwrap(), 0);
|
||||
return 0;
|
||||
}
|
||||
|
||||
@@ -796,7 +780,7 @@ impl Imap {
|
||||
last_seen_uid -= 1;
|
||||
}
|
||||
|
||||
uid_validity = mailbox.uid_validity.unwrap_or_default();
|
||||
uid_validity = mailbox.uid_validity.unwrap();
|
||||
self.set_config_last_seen_uid(context, &folder, uid_validity, last_seen_uid);
|
||||
info!(
|
||||
context,
|
||||
@@ -832,7 +816,11 @@ impl Imap {
|
||||
if cur_uid > last_seen_uid {
|
||||
read_cnt += 1;
|
||||
|
||||
let message_id = prefetch_get_message_id(msg).unwrap_or_default();
|
||||
let message_id = msg
|
||||
.envelope()
|
||||
.expect("missing envelope")
|
||||
.message_id
|
||||
.expect("missing message id");
|
||||
|
||||
if !precheck_imf(context, &message_id, folder.as_ref(), cur_uid) {
|
||||
// check passed, go fetch the rest
|
||||
@@ -897,7 +885,7 @@ impl Imap {
|
||||
let key = format!("imap.mailbox.{}", folder.as_ref());
|
||||
let val = format!("{}:{}", uidvalidity, lastseenuid);
|
||||
|
||||
context.sql.set_raw_config(context, &key, Some(&val)).ok();
|
||||
context.sql.set_config(context, &key, Some(&val)).ok();
|
||||
}
|
||||
|
||||
fn fetch_single_msg<S: AsRef<str>>(
|
||||
@@ -945,20 +933,27 @@ impl Imap {
|
||||
} else {
|
||||
let msg = &msgs[0];
|
||||
|
||||
// XXX put flags into a set and pass them to dc_receive_imf
|
||||
let is_deleted = msg.flags().iter().any(|flag| match flag {
|
||||
imap::types::Flag::Deleted => true,
|
||||
_ => false,
|
||||
});
|
||||
let is_seen = msg.flags().iter().any(|flag| match flag {
|
||||
imap::types::Flag::Seen => true,
|
||||
_ => false,
|
||||
});
|
||||
let is_deleted = msg
|
||||
.flags()
|
||||
.iter()
|
||||
.find(|flag| match flag {
|
||||
imap::types::Flag::Deleted => true,
|
||||
_ => false,
|
||||
})
|
||||
.is_some();
|
||||
let is_seen = msg
|
||||
.flags()
|
||||
.iter()
|
||||
.find(|flag| match flag {
|
||||
imap::types::Flag::Seen => true,
|
||||
_ => false,
|
||||
})
|
||||
.is_some();
|
||||
|
||||
let flags = if is_seen { DC_IMAP_SEEN } else { 0 };
|
||||
|
||||
if !is_deleted && msg.body().is_some() {
|
||||
let body = msg.body().unwrap_or_default();
|
||||
let body = msg.body().unwrap();
|
||||
unsafe {
|
||||
dc_receive_imf(context, &body, folder.as_ref(), server_uid, flags as u32);
|
||||
}
|
||||
@@ -1069,14 +1064,13 @@ impl Imap {
|
||||
let mut do_fake_idle = true;
|
||||
while do_fake_idle {
|
||||
// wait a moment: every 5 seconds in the first 3 minutes after a new message, after that every 60 seconds.
|
||||
let seconds_to_wait = if fake_idle_start_time.elapsed().unwrap_or_default()
|
||||
< Duration::new(3 * 60, 0)
|
||||
&& !wait_long
|
||||
{
|
||||
Duration::new(5, 0)
|
||||
} else {
|
||||
Duration::new(60, 0)
|
||||
};
|
||||
let seconds_to_wait =
|
||||
if fake_idle_start_time.elapsed().unwrap() < Duration::new(3 * 60, 0) && !wait_long
|
||||
{
|
||||
Duration::new(5, 0)
|
||||
} else {
|
||||
Duration::new(60, 0)
|
||||
};
|
||||
|
||||
let &(ref lock, ref cvar) = &*self.watch.clone();
|
||||
let mut watch = lock.lock().unwrap();
|
||||
@@ -1125,94 +1119,117 @@ impl Imap {
|
||||
cvar.notify_one();
|
||||
}
|
||||
|
||||
pub fn mv(
|
||||
pub fn mv<S1: AsRef<str>, S2: AsRef<str>>(
|
||||
&self,
|
||||
context: &Context,
|
||||
folder: &str,
|
||||
folder: S1,
|
||||
uid: u32,
|
||||
dest_folder: &str,
|
||||
dest_folder: S2,
|
||||
dest_uid: &mut u32,
|
||||
) -> ImapResult {
|
||||
if folder == dest_folder {
|
||||
let mut res = ImapResult::RetryLater;
|
||||
let set = format!("{}", uid);
|
||||
|
||||
if uid == 0 {
|
||||
res = ImapResult::Failed;
|
||||
} else if folder.as_ref() == dest_folder.as_ref() {
|
||||
info!(
|
||||
context,
|
||||
"Skip moving message; message {}/{} is already in {}...", folder, uid, dest_folder,
|
||||
"Skip moving message; message {}/{} is already in {}...",
|
||||
folder.as_ref(),
|
||||
uid,
|
||||
dest_folder.as_ref()
|
||||
);
|
||||
return ImapResult::AlreadyDone;
|
||||
}
|
||||
if let Some(imapresult) = self.prepare_imap_operation_on_msg(context, folder, uid) {
|
||||
return imapresult;
|
||||
}
|
||||
// we are connected, and the folder is selected
|
||||
|
||||
// XXX Rust-Imap provides no target uid on mv, so just set it to 0
|
||||
*dest_uid = 0;
|
||||
|
||||
let set = format!("{}", uid);
|
||||
let display_folder_id = format!("{}/{}", folder, uid);
|
||||
if let Some(ref mut session) = &mut *self.session.lock().unwrap() {
|
||||
match session.uid_mv(&set, &dest_folder) {
|
||||
Ok(_) => {
|
||||
emit_event!(
|
||||
context,
|
||||
Event::ImapMessageMoved(format!(
|
||||
"IMAP Message {} moved to {}",
|
||||
display_folder_id, dest_folder
|
||||
))
|
||||
);
|
||||
return ImapResult::Success;
|
||||
}
|
||||
Err(err) => {
|
||||
warn!(
|
||||
context,
|
||||
"Cannot move message, fallback to COPY/DELETE {}/{} to {}: {}",
|
||||
folder,
|
||||
uid,
|
||||
dest_folder,
|
||||
err
|
||||
);
|
||||
}
|
||||
}
|
||||
res = ImapResult::AlreadyDone;
|
||||
} else {
|
||||
unreachable!();
|
||||
};
|
||||
info!(
|
||||
context,
|
||||
"Moving message {}/{} to {}...",
|
||||
folder.as_ref(),
|
||||
uid,
|
||||
dest_folder.as_ref()
|
||||
);
|
||||
|
||||
if let Some(ref mut session) = &mut *self.session.lock().unwrap() {
|
||||
match session.uid_copy(&set, &dest_folder) {
|
||||
Ok(_) => {
|
||||
if !self.add_flag_finalized(context, uid, "\\Deleted") {
|
||||
warn!(context, "Cannot mark {} as \"Deleted\" after copy.", uid);
|
||||
ImapResult::Failed
|
||||
if self.select_folder(context, Some(folder.as_ref())) == 0 {
|
||||
warn!(
|
||||
context,
|
||||
"Cannot select folder {} for moving message.",
|
||||
folder.as_ref()
|
||||
);
|
||||
} else {
|
||||
let moved = if let Some(ref mut session) = &mut *self.session.lock().unwrap() {
|
||||
match session.uid_mv(&set, &dest_folder) {
|
||||
Ok(_) => {
|
||||
res = ImapResult::Success;
|
||||
true
|
||||
}
|
||||
Err(err) => {
|
||||
info!(
|
||||
context,
|
||||
"Cannot move message, fallback to COPY/DELETE {}/{} to {}: {}",
|
||||
folder.as_ref(),
|
||||
uid,
|
||||
dest_folder.as_ref(),
|
||||
err
|
||||
);
|
||||
|
||||
false
|
||||
}
|
||||
}
|
||||
} else {
|
||||
unreachable!();
|
||||
};
|
||||
|
||||
if !moved {
|
||||
let copied = if let Some(ref mut session) = &mut *self.session.lock().unwrap() {
|
||||
match session.uid_copy(&set, &dest_folder) {
|
||||
Ok(_) => true,
|
||||
Err(err) => {
|
||||
eprintln!("error copy: {:?}", err);
|
||||
info!(context, "Cannot copy message.",);
|
||||
|
||||
false
|
||||
}
|
||||
}
|
||||
} else {
|
||||
unreachable!();
|
||||
};
|
||||
|
||||
if copied {
|
||||
if self.add_flag(context, uid, "\\Deleted") == 0 {
|
||||
warn!(context, "Cannot mark message as \"Deleted\".",);
|
||||
}
|
||||
self.config.write().unwrap().selected_folder_needs_expunge = true;
|
||||
ImapResult::Success
|
||||
res = ImapResult::Success;
|
||||
}
|
||||
}
|
||||
Err(err) => {
|
||||
warn!(context, "Could not copy message: {}", err);
|
||||
ImapResult::Failed
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if res == ImapResult::Success {
|
||||
// TODO: is this correct?
|
||||
*dest_uid = uid;
|
||||
}
|
||||
|
||||
if res == ImapResult::RetryLater {
|
||||
if self.should_reconnect() {
|
||||
ImapResult::RetryLater
|
||||
} else {
|
||||
ImapResult::Failed
|
||||
}
|
||||
} else {
|
||||
unreachable!();
|
||||
res
|
||||
}
|
||||
}
|
||||
|
||||
fn add_flag_finalized(&self, context: &Context, server_uid: u32, flag: &str) -> bool {
|
||||
// return true if we successfully set the flag or we otherwise
|
||||
// think add_flag should not be retried: Disconnection during setting
|
||||
// the flag, or other imap-errors, returns true as well.
|
||||
//
|
||||
// returning false means that the operation can be retried.
|
||||
fn add_flag<S: AsRef<str>>(&self, context: &Context, server_uid: u32, flag: S) -> usize {
|
||||
if server_uid == 0 {
|
||||
return true; // might be moved but we don't want to have a stuck job
|
||||
}
|
||||
if self.should_reconnect() {
|
||||
return false;
|
||||
return 0;
|
||||
}
|
||||
if let Some(ref mut session) = &mut *self.session.lock().unwrap() {
|
||||
let set = format!("{}", server_uid);
|
||||
let query = format!("+FLAGS ({})", flag);
|
||||
let query = format!("+FLAGS ({})", flag.as_ref());
|
||||
match session.uid_store(&set, &query) {
|
||||
Ok(_) => {}
|
||||
Err(err) => {
|
||||
@@ -1222,126 +1239,243 @@ impl Imap {
|
||||
);
|
||||
}
|
||||
}
|
||||
true // we tried once, that's probably enough for setting flag
|
||||
}
|
||||
|
||||
// All non-connection states are treated as success - the mail may
|
||||
// already be deleted or moved away on the server.
|
||||
if self.should_reconnect() {
|
||||
0
|
||||
} else {
|
||||
unreachable!();
|
||||
1
|
||||
}
|
||||
}
|
||||
|
||||
pub fn prepare_imap_operation_on_msg(
|
||||
&self,
|
||||
context: &Context,
|
||||
folder: &str,
|
||||
uid: u32,
|
||||
) -> Option<ImapResult> {
|
||||
pub fn set_seen<S: AsRef<str>>(&self, context: &Context, folder: S, uid: u32) -> ImapResult {
|
||||
let mut res = ImapResult::RetryLater;
|
||||
|
||||
if uid == 0 {
|
||||
return Some(ImapResult::Failed);
|
||||
} else if !self.is_connected() {
|
||||
connect_to_inbox(context, &self);
|
||||
if !self.is_connected() {
|
||||
return Some(ImapResult::RetryLater);
|
||||
res = ImapResult::Failed
|
||||
} else if self.is_connected() {
|
||||
info!(
|
||||
context,
|
||||
"Marking message {}/{} as seen...",
|
||||
folder.as_ref(),
|
||||
uid,
|
||||
);
|
||||
|
||||
if self.select_folder(context, Some(folder.as_ref())) == 0 {
|
||||
warn!(
|
||||
context,
|
||||
"Cannot select folder {} for setting SEEN flag.",
|
||||
folder.as_ref(),
|
||||
);
|
||||
} else if self.add_flag(context, uid, "\\Seen") == 0 {
|
||||
warn!(context, "Cannot mark message as seen.",);
|
||||
} else {
|
||||
res = ImapResult::Success
|
||||
}
|
||||
}
|
||||
if self.select_folder(context, Some(&folder)) == 0 {
|
||||
warn!(
|
||||
context,
|
||||
"Cannot select folder {} for preparing IMAP operation", folder
|
||||
);
|
||||
Some(ImapResult::RetryLater)
|
||||
|
||||
if res == ImapResult::RetryLater {
|
||||
if self.should_reconnect() {
|
||||
ImapResult::RetryLater
|
||||
} else {
|
||||
ImapResult::Failed
|
||||
}
|
||||
} else {
|
||||
None
|
||||
res
|
||||
}
|
||||
}
|
||||
|
||||
pub fn set_seen(&self, context: &Context, folder: &str, uid: u32) -> ImapResult {
|
||||
if let Some(imapresult) = self.prepare_imap_operation_on_msg(context, folder, uid) {
|
||||
return imapresult;
|
||||
}
|
||||
// we are connected, and the folder is selected
|
||||
info!(context, "Marking message {}/{} as seen...", folder, uid,);
|
||||
pub fn set_mdnsent<S: AsRef<str>>(&self, context: &Context, folder: S, uid: u32) -> ImapResult {
|
||||
// returns 0=job should be retried later, 1=job done, 2=job done and flag just set
|
||||
let mut res = ImapResult::RetryLater;
|
||||
let set = format!("{}", uid);
|
||||
|
||||
if self.add_flag_finalized(context, uid, "\\Seen") {
|
||||
ImapResult::Success
|
||||
} else {
|
||||
warn!(
|
||||
if uid == 0 {
|
||||
res = ImapResult::Failed;
|
||||
} else if self.is_connected() {
|
||||
info!(
|
||||
context,
|
||||
"Cannot mark message {} in folder {} as seen, ignoring.", uid, folder
|
||||
"Marking message {}/{} as $MDNSent...",
|
||||
folder.as_ref(),
|
||||
uid,
|
||||
);
|
||||
ImapResult::Failed
|
||||
|
||||
if self.select_folder(context, Some(folder.as_ref())) == 0 {
|
||||
warn!(
|
||||
context,
|
||||
"Cannot select folder {} for setting $MDNSent flag.",
|
||||
folder.as_ref()
|
||||
);
|
||||
} else {
|
||||
// Check if the folder can handle the `$MDNSent` flag (see RFC 3503). If so, and not
|
||||
// set: set the flags and return this information.
|
||||
// If the folder cannot handle the `$MDNSent` flag, we risk duplicated MDNs; it's up
|
||||
// to the receiving MUA to handle this then (eg. Delta Chat has no problem with this).
|
||||
|
||||
let can_create_flag = self
|
||||
.config
|
||||
.read()
|
||||
.unwrap()
|
||||
.selected_mailbox
|
||||
.as_ref()
|
||||
.map(|mbox| {
|
||||
// empty means, everything can be stored
|
||||
mbox.permanent_flags.is_empty()
|
||||
|| mbox
|
||||
.permanent_flags
|
||||
.iter()
|
||||
.find(|flag| match flag {
|
||||
imap::types::Flag::Custom(s) => s == "$MDNSent",
|
||||
_ => false,
|
||||
})
|
||||
.is_some()
|
||||
})
|
||||
.expect("just selected folder");
|
||||
|
||||
if can_create_flag {
|
||||
let fetched_msgs =
|
||||
if let Some(ref mut session) = &mut *self.session.lock().unwrap() {
|
||||
match session.uid_fetch(set, FETCH_FLAGS) {
|
||||
Ok(res) => Some(res),
|
||||
Err(err) => {
|
||||
eprintln!("fetch error: {:?}", err);
|
||||
None
|
||||
}
|
||||
}
|
||||
} else {
|
||||
unreachable!();
|
||||
};
|
||||
|
||||
if let Some(msgs) = fetched_msgs {
|
||||
let flag_set = msgs
|
||||
.first()
|
||||
.map(|msg| {
|
||||
msg.flags()
|
||||
.iter()
|
||||
.find(|flag| match flag {
|
||||
imap::types::Flag::Custom(s) => s == "$MDNSent",
|
||||
_ => false,
|
||||
})
|
||||
.is_some()
|
||||
})
|
||||
.unwrap_or_else(|| false);
|
||||
|
||||
res = if flag_set {
|
||||
ImapResult::AlreadyDone
|
||||
} else if self.add_flag(context, uid, "$MDNSent") != 0 {
|
||||
ImapResult::Success
|
||||
} else {
|
||||
res
|
||||
};
|
||||
|
||||
if res == ImapResult::Success {
|
||||
info!(context, "$MDNSent just set and MDN will be sent.");
|
||||
} else {
|
||||
info!(context, "$MDNSent already set and MDN already sent.");
|
||||
}
|
||||
}
|
||||
} else {
|
||||
res = ImapResult::Success;
|
||||
info!(
|
||||
context,
|
||||
"Cannot store $MDNSent flags, risk sending duplicate MDN.",
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if res == ImapResult::RetryLater {
|
||||
if self.should_reconnect() {
|
||||
ImapResult::RetryLater
|
||||
} else {
|
||||
ImapResult::Failed
|
||||
}
|
||||
} else {
|
||||
res
|
||||
}
|
||||
}
|
||||
|
||||
// only returns 0 on connection problems; we should try later again in this case *
|
||||
pub fn delete_msg(
|
||||
pub fn delete_msg<S1: AsRef<str>, S2: AsRef<str>>(
|
||||
&self,
|
||||
context: &Context,
|
||||
message_id: &str,
|
||||
folder: &str,
|
||||
uid: &mut u32,
|
||||
) -> ImapResult {
|
||||
if let Some(imapresult) = self.prepare_imap_operation_on_msg(context, folder, *uid) {
|
||||
return imapresult;
|
||||
}
|
||||
// we are connected, and the folder is selected
|
||||
message_id: S1,
|
||||
folder: S2,
|
||||
server_uid: &mut u32,
|
||||
) -> usize {
|
||||
let mut success = false;
|
||||
if *server_uid == 0 {
|
||||
success = true
|
||||
} else {
|
||||
info!(
|
||||
context,
|
||||
"Marking message \"{}\", {}/{} for deletion...",
|
||||
message_id.as_ref(),
|
||||
folder.as_ref(),
|
||||
server_uid,
|
||||
);
|
||||
|
||||
let set = format!("{}", uid);
|
||||
let display_imap_id = format!("{}/{}", folder, uid);
|
||||
if self.select_folder(context, Some(&folder)) == 0 {
|
||||
warn!(
|
||||
context,
|
||||
"Cannot select folder {} for deleting message.",
|
||||
folder.as_ref()
|
||||
);
|
||||
} else {
|
||||
let set = format!("{}", server_uid);
|
||||
if let Some(ref mut session) = &mut *self.session.lock().unwrap() {
|
||||
match session.uid_fetch(set, PREFETCH_FLAGS) {
|
||||
Ok(msgs) => {
|
||||
if msgs.is_empty()
|
||||
|| msgs
|
||||
.first()
|
||||
.unwrap()
|
||||
.envelope()
|
||||
.expect("missing envelope")
|
||||
.message_id
|
||||
.expect("missing message id")
|
||||
!= message_id.as_ref()
|
||||
{
|
||||
warn!(
|
||||
context,
|
||||
"Cannot delete on IMAP, {}/{} does not match {}.",
|
||||
folder.as_ref(),
|
||||
server_uid,
|
||||
message_id.as_ref(),
|
||||
);
|
||||
*server_uid = 0;
|
||||
}
|
||||
}
|
||||
Err(err) => {
|
||||
eprintln!("fetch error: {:?}", err);
|
||||
|
||||
// double-check that we are deleting the correct message-id
|
||||
// this comes at the expense of another imap query
|
||||
if let Some(ref mut session) = &mut *self.session.lock().unwrap() {
|
||||
match session.uid_fetch(set, PREFETCH_FLAGS) {
|
||||
Ok(msgs) => {
|
||||
if msgs.is_empty() {
|
||||
warn!(
|
||||
context,
|
||||
"Cannot delete on IMAP, {}: imap entry gone '{}'",
|
||||
display_imap_id,
|
||||
message_id,
|
||||
);
|
||||
return ImapResult::Failed;
|
||||
warn!(
|
||||
context,
|
||||
"Cannot delete on IMAP, {}/{} not found.",
|
||||
folder.as_ref(),
|
||||
server_uid,
|
||||
);
|
||||
*server_uid = 0;
|
||||
}
|
||||
}
|
||||
let remote_message_id =
|
||||
prefetch_get_message_id(msgs.first().unwrap()).unwrap_or_default();
|
||||
|
||||
if remote_message_id != message_id {
|
||||
warn!(
|
||||
context,
|
||||
"Cannot delete on IMAP, {}: remote message-id '{}' != '{}'",
|
||||
display_imap_id,
|
||||
remote_message_id,
|
||||
message_id,
|
||||
);
|
||||
}
|
||||
*uid = 0;
|
||||
}
|
||||
Err(err) => {
|
||||
warn!(
|
||||
context,
|
||||
"Cannot delete {} on IMAP: {}", display_imap_id, err
|
||||
);
|
||||
*uid = 0;
|
||||
|
||||
// mark the message for deletion
|
||||
if self.add_flag(context, *server_uid, "\\Deleted") == 0 {
|
||||
warn!(context, "Cannot mark message as \"Deleted\".");
|
||||
} else {
|
||||
self.config.write().unwrap().selected_folder_needs_expunge = true;
|
||||
success = true
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// mark the message for deletion
|
||||
if !self.add_flag_finalized(context, *uid, "\\Deleted") {
|
||||
warn!(
|
||||
context,
|
||||
"Cannot mark message {} as \"Deleted\".", display_imap_id
|
||||
);
|
||||
ImapResult::Failed
|
||||
if success {
|
||||
1
|
||||
} else {
|
||||
emit_event!(
|
||||
context,
|
||||
Event::ImapMessageDeleted(format!(
|
||||
"IMAP Message {} marked as deleted [{}]",
|
||||
display_imap_id, message_id
|
||||
))
|
||||
);
|
||||
self.config.write().unwrap().selected_folder_needs_expunge = true;
|
||||
ImapResult::Success
|
||||
self.is_connected() as usize
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1410,18 +1544,18 @@ impl Imap {
|
||||
|
||||
context
|
||||
.sql
|
||||
.set_raw_config_int(context, "folders_configured", 3)
|
||||
.set_config_int(context, "folders_configured", 3)
|
||||
.ok();
|
||||
if let Some(ref mvbox_folder) = mvbox_folder {
|
||||
context
|
||||
.sql
|
||||
.set_raw_config(context, "configured_mvbox_folder", Some(mvbox_folder))
|
||||
.set_config(context, "configured_mvbox_folder", Some(mvbox_folder))
|
||||
.ok();
|
||||
}
|
||||
if let Some(ref sentbox_folder) = sentbox_folder {
|
||||
context
|
||||
.sql
|
||||
.set_raw_config(
|
||||
.set_config(
|
||||
context,
|
||||
"configured_sentbox_folder",
|
||||
Some(sentbox_folder.name()),
|
||||
@@ -1501,11 +1635,27 @@ fn get_folder_meaning(folder_name: &imap::types::Name) -> FolderMeaning {
|
||||
}
|
||||
|
||||
fn precheck_imf(context: &Context, rfc724_mid: &str, server_folder: &str, server_uid: u32) -> bool {
|
||||
let mut rfc724_mid_exists = false;
|
||||
let mut mark_seen = false;
|
||||
|
||||
if let Ok((old_server_folder, old_server_uid, msg_id)) =
|
||||
message::rfc724_mid_exists(context, &rfc724_mid)
|
||||
{
|
||||
rfc724_mid_exists = true;
|
||||
|
||||
if old_server_folder.is_empty() && old_server_uid == 0 {
|
||||
info!(context, "[move] detected bbc-self {}", rfc724_mid,);
|
||||
mark_seen = true;
|
||||
} else if old_server_folder != server_folder {
|
||||
info!(context, "[move] detected moved message {}", rfc724_mid,);
|
||||
update_msg_move_state(context, &rfc724_mid, MoveState::Stay);
|
||||
}
|
||||
if old_server_folder != server_folder || old_server_uid != server_uid {
|
||||
update_server_uid(context, &rfc724_mid, server_folder, server_uid);
|
||||
}
|
||||
context.do_heuristics_moves(server_folder, msg_id);
|
||||
|
||||
if mark_seen {
|
||||
job_add(
|
||||
context,
|
||||
Action::MarkseenMsgOnImap,
|
||||
@@ -1513,21 +1663,8 @@ fn precheck_imf(context: &Context, rfc724_mid: &str, server_folder: &str, server
|
||||
Params::new(),
|
||||
0,
|
||||
);
|
||||
} else if old_server_folder != server_folder {
|
||||
info!(context, "[move] detected moved message {}", rfc724_mid,);
|
||||
update_msg_move_state(context, &rfc724_mid, MoveState::Stay);
|
||||
}
|
||||
|
||||
if old_server_folder != server_folder || old_server_uid != server_uid {
|
||||
update_server_uid(context, &rfc724_mid, server_folder, server_uid);
|
||||
}
|
||||
true
|
||||
} else {
|
||||
false
|
||||
}
|
||||
}
|
||||
|
||||
fn prefetch_get_message_id(prefetch_msg: &imap::types::Fetch) -> Result<String, Error> {
|
||||
let message_id = prefetch_msg.envelope().unwrap().message_id.unwrap();
|
||||
wrapmime::parse_message_id(&message_id)
|
||||
rfc724_mid_exists
|
||||
}
|
||||
|
||||
588
src/imex.rs
588
src/imex.rs
@@ -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,9 +88,9 @@ 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")
|
||||
sql.get_config_int(context, "backup_time")
|
||||
.unwrap_or_default() as u64;
|
||||
if curr_backup_time > newest_backup_time {
|
||||
newest_backup_path = Some(path);
|
||||
@@ -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)
|
||||
}
|
||||
|
||||
@@ -164,12 +195,16 @@ pub fn render_setup_file(context: &Context, passphrase: &str) -> Result<String>
|
||||
let self_addr = e2ee::ensure_secret_key_exists(context)?;
|
||||
let private_key = Key::from_self_private(context, self_addr, &context.sql)
|
||||
.ok_or(format_err!("Failed to get private key."))?;
|
||||
let ac_headers = match context.get_config_bool(Config::E2eeEnabled) {
|
||||
false => None,
|
||||
true => Some(("Autocrypt-Prefer-Encrypt", "mutual")),
|
||||
let ac_headers = match context
|
||||
.sql
|
||||
.get_config_int(context, Config::E2eeEnabled)
|
||||
.unwrap_or(1)
|
||||
{
|
||||
0 => None,
|
||||
_ => 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!(
|
||||
@@ -232,19 +267,25 @@ pub fn continue_key_transfer(context: &Context, msg_id: u32, setup_code: &str) -
|
||||
if msg.is_err() {
|
||||
bail!("Message is no Autocrypt Setup Message.");
|
||||
}
|
||||
let msg = msg.unwrap_or_default();
|
||||
let msg = msg.unwrap();
|
||||
ensure!(
|
||||
msg.is_setupmessage(),
|
||||
"Message is no Autocrypt Setup Message."
|
||||
);
|
||||
|
||||
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.");
|
||||
}
|
||||
@@ -276,7 +317,7 @@ fn set_self_key(
|
||||
};
|
||||
context
|
||||
.sql
|
||||
.set_raw_config_int(context, "e2ee_enabled", e2ee_enabled)?;
|
||||
.set_config_int(context, "e2ee_enabled", e2ee_enabled)?;
|
||||
}
|
||||
None => {
|
||||
if prefer_encrypt_required {
|
||||
@@ -309,7 +350,7 @@ fn set_self_key(
|
||||
context,
|
||||
&public_key,
|
||||
&private_key,
|
||||
self_addr.unwrap_or_default(),
|
||||
self_addr.unwrap(),
|
||||
set_default,
|
||||
&context.sql,
|
||||
) {
|
||||
@@ -318,15 +359,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 +423,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 +435,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, ¶m);
|
||||
@@ -372,7 +451,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");
|
||||
@@ -399,7 +478,7 @@ fn import_backup(context: &Context, backup_to_import: impl AsRef<Path>) -> Resul
|
||||
!dc_is_configured(context),
|
||||
"Cannot import backups to accounts in use."
|
||||
);
|
||||
context.sql.close(&context);
|
||||
&context.sql.close(&context);
|
||||
dc_delete_file(context, context.get_dbfile());
|
||||
ensure!(
|
||||
!dc_file_exist(context, context.get_dbfile()),
|
||||
@@ -413,7 +492,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"
|
||||
);
|
||||
|
||||
@@ -436,9 +515,20 @@ fn import_backup(context: &Context, backup_to_import: impl AsRef<Path>) -> Resul
|
||||
Ok((name, blob))
|
||||
},
|
||||
|files| {
|
||||
for (processed_files_cnt, file) in files.enumerate() {
|
||||
let mut processed_files_cnt = 0;
|
||||
|
||||
for file in files {
|
||||
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");
|
||||
}
|
||||
processed_files_cnt += 1;
|
||||
let mut permille = processed_files_cnt * 1000 / total_files_cnt;
|
||||
if permille < 10 {
|
||||
permille = 10
|
||||
@@ -479,6 +569,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 +590,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 +599,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();
|
||||
} else {
|
||||
error!(
|
||||
context,
|
||||
"Backup: Cannot copy from blob-directory \"{}\".",
|
||||
context.get_blobdir().display(),
|
||||
);
|
||||
}
|
||||
} else {
|
||||
info!(context, "Backup: No files to copy.",);
|
||||
}
|
||||
if ok_to_continue {
|
||||
if sql
|
||||
.set_config_int(context, "backup_time", now as i32)
|
||||
.is_ok()
|
||||
{
|
||||
context.call_cb(Event::ImexFileWritten(dest_path_filename.clone()));
|
||||
success = true;
|
||||
}
|
||||
}
|
||||
} else {
|
||||
error!(
|
||||
context,
|
||||
"Backup: Cannot get info for blob-directory \"{}\".",
|
||||
context.get_blobdir().display(),
|
||||
);
|
||||
};
|
||||
}
|
||||
}
|
||||
if 0 != delete_dest_file {
|
||||
dc_delete_file(context, &dest_path_filename);
|
||||
}
|
||||
|
||||
ensure!(success, "failed");
|
||||
Ok(())
|
||||
}
|
||||
|
||||
@@ -592,48 +737,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()
|
||||
} 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
|
||||
);
|
||||
return 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 +854,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 +891,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 +913,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 +981,47 @@ 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());
|
||||
let mut buf_1 = S_EM_SETUPFILE.to_string();
|
||||
|
||||
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();
|
||||
unsafe {
|
||||
assert!(dc_split_armored_data(
|
||||
buf_1.as_mut_ptr().cast(),
|
||||
&mut headerline,
|
||||
&mut setupcodebegin,
|
||||
&mut preferencrypt,
|
||||
ptr::null_mut(),
|
||||
));
|
||||
}
|
||||
assert_eq!(headerline, "-----BEGIN PGP MESSAGE-----");
|
||||
assert!(!setupcodebegin.is_null());
|
||||
|
||||
let (typ, headers, _base64) = split_armored_data(decrypted.as_bytes()).unwrap();
|
||||
// TODO: verify that this is the right check
|
||||
assert!(S_EM_SETUPCODE.starts_with(as_str(setupcodebegin)));
|
||||
|
||||
assert_eq!(typ, BlockType::PrivateKey);
|
||||
assert_eq!(headers.get(HEADER_AUTOCRYPT), Some(&"mutual".to_string()));
|
||||
assert!(headers.get(HEADER_SETUPCODE).is_none());
|
||||
assert!(preferencrypt.is_null());
|
||||
|
||||
let mut setup_file = S_EM_SETUPFILE.to_string();
|
||||
let mut decrypted = unsafe {
|
||||
decrypt_setup_file(context, S_EM_SETUPCODE, setup_file.as_bytes_mut()).unwrap()
|
||||
};
|
||||
|
||||
unsafe {
|
||||
assert!(dc_split_armored_data(
|
||||
decrypted.as_mut_ptr().cast(),
|
||||
&mut headerline,
|
||||
&mut setupcodebegin,
|
||||
&mut preferencrypt,
|
||||
ptr::null_mut(),
|
||||
));
|
||||
}
|
||||
|
||||
assert_eq!(headerline, "-----BEGIN PGP PRIVATE KEY BLOCK-----");
|
||||
assert!(setupcodebegin.is_null());
|
||||
assert!(!preferencrypt.is_null());
|
||||
assert_eq!(as_str(preferencrypt), "mutual",);
|
||||
}
|
||||
}
|
||||
|
||||
418
src/job.rs
418
src/job.rs
@@ -4,12 +4,10 @@ use deltachat_derive::{FromSql, ToSql};
|
||||
use rand::{thread_rng, Rng};
|
||||
|
||||
use crate::chat;
|
||||
use crate::config::Config;
|
||||
use crate::configure::*;
|
||||
use crate::constants::*;
|
||||
use crate::context::Context;
|
||||
use crate::dc_tools::*;
|
||||
use crate::error::Error;
|
||||
use crate::events::Event;
|
||||
use crate::imap::*;
|
||||
use crate::imex::*;
|
||||
@@ -133,7 +131,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;
|
||||
}
|
||||
}
|
||||
@@ -142,11 +140,11 @@ impl Job {
|
||||
if let Ok(body) = dc_read_file(context, filename) {
|
||||
if let Some(recipients) = self.param.get(Param::Recipients) {
|
||||
let recipients_list = recipients
|
||||
.split('\x1e')
|
||||
.split("\x1e")
|
||||
.filter_map(|addr| match lettre::EmailAddress::new(addr.to_string()) {
|
||||
Ok(addr) => Some(addr),
|
||||
Err(err) => {
|
||||
warn!(context, "invalid recipient: {} {:?}", addr, err);
|
||||
eprintln!("WARNING: invalid recipient: {} {:?}", addr, err);
|
||||
None
|
||||
}
|
||||
})
|
||||
@@ -158,45 +156,39 @@ impl Job {
|
||||
if 0 != self.foreign_id && !message::exists(context, self.foreign_id) {
|
||||
warn!(
|
||||
context,
|
||||
"Not sending Message {} as it was deleted", self.foreign_id
|
||||
"Message {} for job {} does not exist", self.foreign_id, self.job_id,
|
||||
);
|
||||
return;
|
||||
};
|
||||
|
||||
// hold the smtp lock during sending of a job and
|
||||
// its ok/error response processing. Note that if a message
|
||||
// was sent we need to mark it in the database ASAP as we
|
||||
// was sent we need to mark it in the database as we
|
||||
// otherwise might send it twice.
|
||||
let mut sock = context.smtp.lock().unwrap();
|
||||
match sock.send(context, recipients_list, body) {
|
||||
Err(err) => {
|
||||
sock.disconnect();
|
||||
warn!(context, "smtp failed: {}", err);
|
||||
self.try_again_later(-1, Some(err.to_string()));
|
||||
}
|
||||
Ok(()) => {
|
||||
// smtp success, update db ASAP, then delete smtp file
|
||||
if 0 != self.foreign_id {
|
||||
message::update_msg_state(
|
||||
if 0 == sock.send(context, recipients_list, body) {
|
||||
sock.disconnect();
|
||||
self.try_again_later(-1i32, sock.error.clone());
|
||||
} else {
|
||||
dc_delete_file(context, filename);
|
||||
if 0 != self.foreign_id {
|
||||
message::update_msg_state(
|
||||
context,
|
||||
self.foreign_id,
|
||||
MessageState::OutDelivered,
|
||||
);
|
||||
let chat_id: i32 = context
|
||||
.sql
|
||||
.query_get_value(
|
||||
context,
|
||||
self.foreign_id,
|
||||
MessageState::OutDelivered,
|
||||
);
|
||||
let chat_id: i32 = context
|
||||
.sql
|
||||
.query_get_value(
|
||||
context,
|
||||
"SELECT chat_id FROM msgs WHERE id=?",
|
||||
params![self.foreign_id as i32],
|
||||
)
|
||||
.unwrap_or_default();
|
||||
context.call_cb(Event::MsgDelivered {
|
||||
chat_id: chat_id as u32,
|
||||
msg_id: self.foreign_id,
|
||||
});
|
||||
}
|
||||
// now also delete the generated file
|
||||
dc_delete_file(context, filename);
|
||||
"SELECT chat_id FROM msgs WHERE id=?",
|
||||
params![self.foreign_id as i32],
|
||||
)
|
||||
.unwrap_or_default();
|
||||
context.call_cb(Event::MsgDelivered {
|
||||
chat_id: chat_id as u32,
|
||||
msg_id: self.foreign_id,
|
||||
});
|
||||
}
|
||||
}
|
||||
} else {
|
||||
@@ -207,7 +199,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;
|
||||
}
|
||||
@@ -216,18 +208,23 @@ impl Job {
|
||||
fn do_DC_JOB_MOVE_MSG(&mut self, context: &Context) {
|
||||
let inbox = context.inbox.read().unwrap();
|
||||
|
||||
if !inbox.is_connected() {
|
||||
connect_to_inbox(context, &inbox);
|
||||
if !inbox.is_connected() {
|
||||
self.try_again_later(3, None);
|
||||
return;
|
||||
}
|
||||
}
|
||||
if let Ok(msg) = Message::load_from_db(context, self.foreign_id) {
|
||||
if context
|
||||
.sql
|
||||
.get_raw_config_int(context, "folders_configured")
|
||||
.get_config_int(context, "folders_configured")
|
||||
.unwrap_or_default()
|
||||
< 3
|
||||
{
|
||||
inbox.configure_folders(context, 0x1i32);
|
||||
}
|
||||
let dest_folder = context
|
||||
.sql
|
||||
.get_raw_config(context, "configured_mvbox_folder");
|
||||
let dest_folder = context.sql.get_config(context, "configured_mvbox_folder");
|
||||
|
||||
if let Some(dest_folder) = dest_folder {
|
||||
let server_folder = msg.server_folder.as_ref().unwrap();
|
||||
@@ -264,18 +261,26 @@ impl Job {
|
||||
if let Ok(mut msg) = Message::load_from_db(context, self.foreign_id) {
|
||||
if !msg.rfc724_mid.is_empty() {
|
||||
/* eg. device messages have no Message-ID */
|
||||
if message::rfc724_mid_cnt(context, &msg.rfc724_mid) > 1 {
|
||||
let mut delete_from_server = true;
|
||||
if message::rfc724_mid_cnt(context, &msg.rfc724_mid) != 1 {
|
||||
info!(
|
||||
context,
|
||||
"The message is deleted from the server when all parts are deleted.",
|
||||
);
|
||||
} else {
|
||||
/* if this is the last existing part of the message,
|
||||
we delete the message from the server */
|
||||
delete_from_server = false;
|
||||
}
|
||||
/* if this is the last existing part of the message, we delete the message from the server */
|
||||
if delete_from_server {
|
||||
if !inbox.is_connected() {
|
||||
connect_to_inbox(context, &inbox);
|
||||
if !inbox.is_connected() {
|
||||
self.try_again_later(3i32, None);
|
||||
return;
|
||||
}
|
||||
}
|
||||
let mid = msg.rfc724_mid;
|
||||
let server_folder = msg.server_folder.as_ref().unwrap();
|
||||
let res = inbox.delete_msg(context, &mid, server_folder, &mut msg.server_uid);
|
||||
if res == ImapResult::RetryLater {
|
||||
if 0 == inbox.delete_msg(context, &mid, server_folder, &mut msg.server_uid) {
|
||||
self.try_again_later(-1i32, None);
|
||||
return;
|
||||
}
|
||||
@@ -289,23 +294,37 @@ impl Job {
|
||||
fn do_DC_JOB_MARKSEEN_MSG_ON_IMAP(&mut self, context: &Context) {
|
||||
let inbox = context.inbox.read().unwrap();
|
||||
|
||||
if !inbox.is_connected() {
|
||||
connect_to_inbox(context, &inbox);
|
||||
if !inbox.is_connected() {
|
||||
self.try_again_later(3i32, None);
|
||||
return;
|
||||
}
|
||||
}
|
||||
if let Ok(msg) = Message::load_from_db(context, self.foreign_id) {
|
||||
let folder = msg.server_folder.as_ref().unwrap();
|
||||
match inbox.set_seen(context, folder, msg.server_uid) {
|
||||
let server_folder = msg.server_folder.as_ref().unwrap();
|
||||
match inbox.set_seen(context, server_folder, msg.server_uid) {
|
||||
ImapResult::Failed => {}
|
||||
ImapResult::RetryLater => {
|
||||
self.try_again_later(3i32, None);
|
||||
}
|
||||
ImapResult::AlreadyDone => {}
|
||||
ImapResult::Success | ImapResult::Failed => {
|
||||
// XXX the message might just have been moved
|
||||
// we want to send out an MDN anyway
|
||||
// The job will not be retried so locally
|
||||
// there is no risk of double-sending MDNs.
|
||||
_ => {
|
||||
if 0 != msg.param.get_int(Param::WantsMdn).unwrap_or_default()
|
||||
&& context.get_config_bool(Config::MdnsEnabled)
|
||||
&& 0 != context
|
||||
.sql
|
||||
.get_config_int(context, "mdns_enabled")
|
||||
.unwrap_or_else(|| 1)
|
||||
{
|
||||
if let Err(err) = send_mdn(context, msg.id) {
|
||||
warn!(context, "could not send out mdn for {}: {}", msg.id, err);
|
||||
let folder = msg.server_folder.as_ref().unwrap();
|
||||
|
||||
match inbox.set_mdnsent(context, folder, msg.server_uid) {
|
||||
ImapResult::RetryLater => {
|
||||
self.try_again_later(3i32, None);
|
||||
}
|
||||
ImapResult::Success => {
|
||||
send_mdn(context, msg.id);
|
||||
}
|
||||
ImapResult::Failed | ImapResult::AlreadyDone => {}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -322,26 +341,31 @@ impl Job {
|
||||
.to_string();
|
||||
let uid = self.param.get_int(Param::ServerUid).unwrap_or_default() as u32;
|
||||
let inbox = context.inbox.read().unwrap();
|
||||
if inbox.set_seen(context, &folder, uid) == ImapResult::RetryLater {
|
||||
|
||||
if !inbox.is_connected() {
|
||||
connect_to_inbox(context, &inbox);
|
||||
if !inbox.is_connected() {
|
||||
self.try_again_later(3, None);
|
||||
return;
|
||||
}
|
||||
}
|
||||
if inbox.set_seen(context, &folder, uid) == ImapResult::Failed {
|
||||
self.try_again_later(3i32, None);
|
||||
return;
|
||||
}
|
||||
if 0 != self.param.get_int(Param::AlsoMove).unwrap_or_default() {
|
||||
if context
|
||||
.sql
|
||||
.get_raw_config_int(context, "folders_configured")
|
||||
.get_config_int(context, "folders_configured")
|
||||
.unwrap_or_default()
|
||||
< 3
|
||||
{
|
||||
inbox.configure_folders(context, 0x1i32);
|
||||
}
|
||||
let dest_folder = context
|
||||
.sql
|
||||
.get_raw_config(context, "configured_mvbox_folder");
|
||||
let dest_folder = context.sql.get_config(context, "configured_mvbox_folder");
|
||||
if let Some(dest_folder) = dest_folder {
|
||||
let mut dest_uid = 0;
|
||||
if ImapResult::RetryLater
|
||||
== inbox.mv(context, &folder, uid, &dest_folder, &mut dest_uid)
|
||||
== inbox.mv(context, folder, uid, dest_folder, &mut dest_uid)
|
||||
{
|
||||
self.try_again_later(3, None);
|
||||
}
|
||||
@@ -368,7 +392,12 @@ pub fn perform_imap_fetch(context: &Context) {
|
||||
if 0 == connect_to_inbox(context, &inbox) {
|
||||
return;
|
||||
}
|
||||
if !context.get_config_bool(Config::InboxWatch) {
|
||||
if context
|
||||
.sql
|
||||
.get_config_int(context, "inbox_watch")
|
||||
.unwrap_or_else(|| 1)
|
||||
== 0
|
||||
{
|
||||
info!(context, "INBOX-watch disabled.",);
|
||||
return;
|
||||
}
|
||||
@@ -403,23 +432,29 @@ pub fn perform_imap_idle(context: &Context) {
|
||||
}
|
||||
|
||||
pub fn perform_mvbox_fetch(context: &Context) {
|
||||
let use_network = context.get_config_bool(Config::MvboxWatch);
|
||||
let use_network = context
|
||||
.sql
|
||||
.get_config_int(context, "mvbox_watch")
|
||||
.unwrap_or_else(|| 1);
|
||||
|
||||
context
|
||||
.mvbox_thread
|
||||
.write()
|
||||
.unwrap()
|
||||
.fetch(context, use_network);
|
||||
.fetch(context, use_network == 1);
|
||||
}
|
||||
|
||||
pub fn perform_mvbox_idle(context: &Context) {
|
||||
let use_network = context.get_config_bool(Config::MvboxWatch);
|
||||
let use_network = context
|
||||
.sql
|
||||
.get_config_int(context, "mvbox_watch")
|
||||
.unwrap_or_else(|| 1);
|
||||
|
||||
context
|
||||
.mvbox_thread
|
||||
.read()
|
||||
.unwrap()
|
||||
.idle(context, use_network);
|
||||
.idle(context, use_network == 1);
|
||||
}
|
||||
|
||||
pub fn interrupt_mvbox_idle(context: &Context) {
|
||||
@@ -427,23 +462,29 @@ pub fn interrupt_mvbox_idle(context: &Context) {
|
||||
}
|
||||
|
||||
pub fn perform_sentbox_fetch(context: &Context) {
|
||||
let use_network = context.get_config_bool(Config::SentboxWatch);
|
||||
let use_network = context
|
||||
.sql
|
||||
.get_config_int(context, "sentbox_watch")
|
||||
.unwrap_or_else(|| 1);
|
||||
|
||||
context
|
||||
.sentbox_thread
|
||||
.write()
|
||||
.unwrap()
|
||||
.fetch(context, use_network);
|
||||
.fetch(context, use_network == 1);
|
||||
}
|
||||
|
||||
pub fn perform_sentbox_idle(context: &Context) {
|
||||
let use_network = context.get_config_bool(Config::SentboxWatch);
|
||||
let use_network = context
|
||||
.sql
|
||||
.get_config_int(context, "sentbox_watch")
|
||||
.unwrap_or_else(|| 1);
|
||||
|
||||
context
|
||||
.sentbox_thread
|
||||
.read()
|
||||
.unwrap()
|
||||
.idle(context, use_network);
|
||||
.idle(context, use_network == 1);
|
||||
}
|
||||
|
||||
pub fn interrupt_sentbox_idle(context: &Context) {
|
||||
@@ -560,108 +601,109 @@ pub fn job_action_exists(context: &Context, action: Action) -> bool {
|
||||
|
||||
/* special case for DC_JOB_SEND_MSG_TO_SMTP */
|
||||
#[allow(non_snake_case)]
|
||||
pub fn job_send_msg(context: &Context, msg_id: u32) -> Result<(), Error> {
|
||||
let mut mimefactory = MimeFactory::load_msg(context, msg_id)?;
|
||||
pub fn job_send_msg(context: &Context, msg_id: u32) -> libc::c_int {
|
||||
let mut success = 0;
|
||||
|
||||
if chat::msgtype_has_file(mimefactory.msg.type_0) {
|
||||
let file_param = mimefactory
|
||||
.msg
|
||||
.param
|
||||
.get(Param::File)
|
||||
.map(|s| s.to_string());
|
||||
if let Some(pathNfilename) = file_param {
|
||||
if (mimefactory.msg.type_0 == Viewtype::Image
|
||||
|| mimefactory.msg.type_0 == Viewtype::Gif)
|
||||
&& !mimefactory.msg.param.exists(Param::Width)
|
||||
{
|
||||
mimefactory.msg.param.set_int(Param::Width, 0);
|
||||
mimefactory.msg.param.set_int(Param::Height, 0);
|
||||
|
||||
if let Ok(buf) = dc_read_file(context, pathNfilename) {
|
||||
if let Ok((width, height)) = dc_get_filemeta(&buf) {
|
||||
mimefactory.msg.param.set_int(Param::Width, width as i32);
|
||||
mimefactory.msg.param.set_int(Param::Height, height as i32);
|
||||
}
|
||||
}
|
||||
mimefactory.msg.save_param_to_disk(context);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/* create message */
|
||||
if let Err(msg) = unsafe { mimefactory.render() } {
|
||||
let e = msg.to_string();
|
||||
message::set_msg_failed(context, msg_id, Some(e));
|
||||
return Err(msg);
|
||||
}
|
||||
if 0 != mimefactory
|
||||
.msg
|
||||
.param
|
||||
.get_int(Param::GuranteeE2ee)
|
||||
.unwrap_or_default()
|
||||
&& !mimefactory.out_encrypted
|
||||
{
|
||||
/* unrecoverable */
|
||||
message::set_msg_failed(
|
||||
context,
|
||||
msg_id,
|
||||
Some("End-to-end-encryption unavailable unexpectedly."),
|
||||
);
|
||||
bail!(
|
||||
"e2e encryption unavailable {} - {:?}",
|
||||
msg_id,
|
||||
mimefactory.msg.param.get_int(Param::GuranteeE2ee),
|
||||
);
|
||||
}
|
||||
if context.get_config_bool(Config::BccSelf)
|
||||
&& !vec_contains_lowercase(&mimefactory.recipients_addr, &mimefactory.from_addr)
|
||||
{
|
||||
mimefactory.recipients_names.push("".to_string());
|
||||
mimefactory
|
||||
.recipients_addr
|
||||
.push(mimefactory.from_addr.to_string());
|
||||
}
|
||||
|
||||
if mimefactory.recipients_addr.is_empty() {
|
||||
/* load message data */
|
||||
let mimefactory = MimeFactory::load_msg(context, msg_id);
|
||||
if mimefactory.is_err() {
|
||||
warn!(
|
||||
context,
|
||||
"message {} has no recipient, skipping smtp-send", msg_id
|
||||
"Cannot load data to send, maybe the message is deleted in between.",
|
||||
);
|
||||
return Ok(());
|
||||
}
|
||||
} else {
|
||||
let mut mimefactory = mimefactory.unwrap();
|
||||
// no redo, no IMAP. moreover, as the data does not exist, there is no need in calling dc_set_msg_failed()
|
||||
if chat::msgtype_has_file(mimefactory.msg.type_0) {
|
||||
let file_param = mimefactory
|
||||
.msg
|
||||
.param
|
||||
.get(Param::File)
|
||||
.map(|s| s.to_string());
|
||||
if let Some(pathNfilename) = file_param {
|
||||
if (mimefactory.msg.type_0 == Viewtype::Image
|
||||
|| mimefactory.msg.type_0 == Viewtype::Gif)
|
||||
&& !mimefactory.msg.param.exists(Param::Width)
|
||||
{
|
||||
mimefactory.msg.param.set_int(Param::Width, 0);
|
||||
mimefactory.msg.param.set_int(Param::Height, 0);
|
||||
|
||||
if mimefactory.out_gossiped {
|
||||
chat::set_gossiped_timestamp(context, mimefactory.msg.chat_id, time());
|
||||
}
|
||||
if 0 != mimefactory.out_last_added_location_id {
|
||||
if let Err(err) = location::set_kml_sent_timestamp(context, mimefactory.msg.chat_id, time())
|
||||
{
|
||||
error!(context, "Failed to set kml sent_timestamp: {:?}", err);
|
||||
}
|
||||
if !mimefactory.msg.hidden {
|
||||
if let Err(err) = location::set_msg_location_id(
|
||||
context,
|
||||
mimefactory.msg.id,
|
||||
mimefactory.out_last_added_location_id,
|
||||
) {
|
||||
error!(context, "Failed to set msg_location_id: {:?}", err);
|
||||
if let Ok(buf) = dc_read_file(context, pathNfilename) {
|
||||
if let Ok((width, height)) = dc_get_filemeta(&buf) {
|
||||
mimefactory.msg.param.set_int(Param::Width, width as i32);
|
||||
mimefactory.msg.param.set_int(Param::Height, height as i32);
|
||||
}
|
||||
}
|
||||
mimefactory.msg.save_param_to_disk(context);
|
||||
}
|
||||
}
|
||||
}
|
||||
/* create message */
|
||||
if let Err(msg) = unsafe { mimefactory.render() } {
|
||||
let e = msg.to_string();
|
||||
message::set_msg_failed(context, msg_id, Some(e));
|
||||
} else if 0
|
||||
!= mimefactory
|
||||
.msg
|
||||
.param
|
||||
.get_int(Param::GuranteeE2ee)
|
||||
.unwrap_or_default()
|
||||
&& !mimefactory.out_encrypted
|
||||
{
|
||||
/* unrecoverable */
|
||||
warn!(
|
||||
context,
|
||||
"e2e encryption unavailable {} - {:?}",
|
||||
msg_id,
|
||||
mimefactory.msg.param.get_int(Param::GuranteeE2ee),
|
||||
);
|
||||
message::set_msg_failed(
|
||||
context,
|
||||
msg_id,
|
||||
Some("End-to-end-encryption unavailable unexpectedly."),
|
||||
);
|
||||
} else {
|
||||
if !vec_contains_lowercase(&mimefactory.recipients_addr, &mimefactory.from_addr) {
|
||||
mimefactory.recipients_names.push("".to_string());
|
||||
mimefactory
|
||||
.recipients_addr
|
||||
.push(mimefactory.from_addr.to_string());
|
||||
}
|
||||
if mimefactory.out_gossiped {
|
||||
chat::set_gossiped_timestamp(context, mimefactory.msg.chat_id, time());
|
||||
}
|
||||
if 0 != mimefactory.out_last_added_location_id {
|
||||
if let Err(err) =
|
||||
location::set_kml_sent_timestamp(context, mimefactory.msg.chat_id, time())
|
||||
{
|
||||
error!(context, "Failed to set kml sent_timestamp: {:?}", err);
|
||||
}
|
||||
if !mimefactory.msg.hidden {
|
||||
if let Err(err) = location::set_msg_location_id(
|
||||
context,
|
||||
mimefactory.msg.id,
|
||||
mimefactory.out_last_added_location_id,
|
||||
) {
|
||||
error!(context, "Failed to set msg_location_id: {:?}", err);
|
||||
}
|
||||
}
|
||||
}
|
||||
if mimefactory.out_encrypted
|
||||
&& mimefactory
|
||||
.msg
|
||||
.param
|
||||
.get_int(Param::GuranteeE2ee)
|
||||
.unwrap_or_default()
|
||||
== 0
|
||||
{
|
||||
mimefactory.msg.param.set_int(Param::GuranteeE2ee, 1);
|
||||
mimefactory.msg.save_param_to_disk(context);
|
||||
}
|
||||
success = add_smtp_job(context, Action::SendMsgToSmtp, &mut mimefactory);
|
||||
}
|
||||
}
|
||||
if mimefactory.out_encrypted
|
||||
&& mimefactory
|
||||
.msg
|
||||
.param
|
||||
.get_int(Param::GuranteeE2ee)
|
||||
.unwrap_or_default()
|
||||
== 0
|
||||
{
|
||||
mimefactory.msg.param.set_int(Param::GuranteeE2ee, 1);
|
||||
mimefactory.msg.save_param_to_disk(context);
|
||||
}
|
||||
add_smtp_job(context, Action::SendMsgToSmtp, &mut mimefactory)?;
|
||||
|
||||
Ok(())
|
||||
success
|
||||
}
|
||||
|
||||
pub fn perform_imap_jobs(context: &Context) {
|
||||
@@ -751,13 +793,13 @@ fn job_perform(context: &Context, thread: Thread, probe_network: bool) {
|
||||
// - they can be re-executed one time AT_ONCE, but they are not save in the database for later execution
|
||||
if Action::ConfigureImap == job.action || Action::ImexImap == job.action {
|
||||
job_kill_action(context, job.action);
|
||||
context
|
||||
&context
|
||||
.sentbox_thread
|
||||
.clone()
|
||||
.read()
|
||||
.unwrap()
|
||||
.suspend(context);
|
||||
context
|
||||
&context
|
||||
.mvbox_thread
|
||||
.clone()
|
||||
.read()
|
||||
@@ -781,7 +823,7 @@ fn job_perform(context: &Context, thread: Thread, probe_network: bool) {
|
||||
Action::MarkseenMdnOnImap => job.do_DC_JOB_MARKSEEN_MDN_ON_IMAP(context),
|
||||
Action::MoveMsg => job.do_DC_JOB_MOVE_MSG(context),
|
||||
Action::SendMdn => job.do_DC_JOB_SEND(context),
|
||||
Action::ConfigureImap => dc_job_do_DC_JOB_CONFIGURE_IMAP(context),
|
||||
Action::ConfigureImap => unsafe { dc_job_do_DC_JOB_CONFIGURE_IMAP(context) },
|
||||
Action::ImexImap => match job_do_DC_JOB_IMEX_IMAP(context, &job) {
|
||||
Ok(()) => {}
|
||||
Err(err) => {
|
||||
@@ -882,7 +924,8 @@ fn job_perform(context: &Context, thread: Thread, probe_network: bool) {
|
||||
#[allow(non_snake_case)]
|
||||
fn get_backoff_time_offset(c_tries: libc::c_int) -> i64 {
|
||||
// results in ~3 weeks for the last backoff timespan
|
||||
let N = 2_i32.pow((c_tries - 1) as u32) * 60;
|
||||
let mut N = 2_i32.pow((c_tries - 1) as u32);
|
||||
N = N * 60;
|
||||
let mut rng = thread_rng();
|
||||
let n: i32 = rng.gen();
|
||||
let mut seconds = n % (N + 1);
|
||||
@@ -904,7 +947,7 @@ fn suspend_smtp_thread(context: &Context, suspend: bool) {
|
||||
}
|
||||
}
|
||||
|
||||
pub fn connect_to_inbox(context: &Context, inbox: &Imap) -> libc::c_int {
|
||||
fn connect_to_inbox(context: &Context, inbox: &Imap) -> libc::c_int {
|
||||
let ret_connected = dc_connect_to_configured_imap(context, inbox);
|
||||
if 0 != ret_connected {
|
||||
inbox.set_watch_folder("INBOX".into());
|
||||
@@ -912,20 +955,16 @@ pub fn connect_to_inbox(context: &Context, inbox: &Imap) -> libc::c_int {
|
||||
ret_connected
|
||||
}
|
||||
|
||||
fn send_mdn(context: &Context, msg_id: u32) -> Result<(), Error> {
|
||||
let mut mimefactory = MimeFactory::load_mdn(context, msg_id)?;
|
||||
unsafe { mimefactory.render()? };
|
||||
add_smtp_job(context, Action::SendMdn, &mut mimefactory)?;
|
||||
|
||||
Ok(())
|
||||
fn send_mdn(context: &Context, msg_id: u32) {
|
||||
if let Ok(mut mimefactory) = MimeFactory::load_mdn(context, msg_id) {
|
||||
if unsafe { mimefactory.render() }.is_ok() {
|
||||
add_smtp_job(context, Action::SendMdn, &mut mimefactory);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[allow(non_snake_case)]
|
||||
fn add_smtp_job(context: &Context, action: Action, mimefactory: &MimeFactory) -> Result<(), Error> {
|
||||
ensure!(
|
||||
!mimefactory.recipients_addr.is_empty(),
|
||||
"no recipients for smtp job set"
|
||||
);
|
||||
fn add_smtp_job(context: &Context, action: Action, mimefactory: &MimeFactory) -> libc::c_int {
|
||||
let mut param = Params::new();
|
||||
let bytes = unsafe {
|
||||
std::slice::from_raw_parts(
|
||||
@@ -933,7 +972,17 @@ fn add_smtp_job(context: &Context, action: Action, mimefactory: &MimeFactory) ->
|
||||
(*mimefactory.out).len,
|
||||
)
|
||||
};
|
||||
let bpath = context.new_blob_file(&mimefactory.rfc724_mid, bytes)?;
|
||||
let bpath = match context.new_blob_file(&mimefactory.rfc724_mid, bytes) {
|
||||
Ok(path) => path,
|
||||
Err(err) => {
|
||||
error!(
|
||||
context,
|
||||
"Could not write {} smtp-message, error {}", mimefactory.rfc724_mid, err
|
||||
);
|
||||
return 0;
|
||||
}
|
||||
};
|
||||
info!(context, "add_smtp_job file written: {:?}", bpath);
|
||||
let recipients = mimefactory.recipients_addr.join("\x1e");
|
||||
param.set(Param::File, &bpath);
|
||||
param.set(Param::Recipients, &recipients);
|
||||
@@ -948,8 +997,7 @@ fn add_smtp_job(context: &Context, action: Action, mimefactory: &MimeFactory) ->
|
||||
param,
|
||||
0,
|
||||
);
|
||||
|
||||
Ok(())
|
||||
1
|
||||
}
|
||||
|
||||
pub fn job_add(
|
||||
|
||||
@@ -116,14 +116,14 @@ impl JobThread {
|
||||
if ret_connected {
|
||||
if context
|
||||
.sql
|
||||
.get_raw_config_int(context, "folders_configured")
|
||||
.get_config_int(context, "folders_configured")
|
||||
.unwrap_or_default()
|
||||
< 3
|
||||
{
|
||||
self.imap.configure_folders(context, 0x1);
|
||||
}
|
||||
|
||||
if let Some(mvbox_name) = context.sql.get_raw_config(context, self.folder_config_name) {
|
||||
if let Some(mvbox_name) = context.sql.get_config(context, self.folder_config_name) {
|
||||
self.imap.set_watch_folder(mvbox_name);
|
||||
} else {
|
||||
self.imap.disconnect(context);
|
||||
|
||||
12
src/key.rs
12
src/key.rs
@@ -163,8 +163,8 @@ impl Key {
|
||||
|
||||
pub fn to_bytes(&self) -> Vec<u8> {
|
||||
match self {
|
||||
Key::Public(k) => k.to_bytes().unwrap_or_default(),
|
||||
Key::Secret(k) => k.to_bytes().unwrap_or_default(),
|
||||
Key::Public(k) => k.to_bytes().unwrap(),
|
||||
Key::Secret(k) => k.to_bytes().unwrap(),
|
||||
}
|
||||
}
|
||||
|
||||
@@ -218,10 +218,10 @@ impl Key {
|
||||
let file_content = self.to_asc(None).into_bytes();
|
||||
|
||||
if dc_write_file(context, &file, &file_content) {
|
||||
true
|
||||
return true;
|
||||
} else {
|
||||
error!(context, "Cannot write key to {}", file.as_ref().display());
|
||||
false
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -381,7 +381,7 @@ i8pcjGO+IZffvyZJVRWfVooBJmWWbPB1pueo3tx8w3+fcuzpxz+RLFKaPyqXO+dD
|
||||
#[test]
|
||||
#[ignore] // is too expensive
|
||||
fn test_from_slice_roundtrip() {
|
||||
let (public_key, private_key) = crate::pgp::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, _) =
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -3,7 +3,6 @@ use quick_xml;
|
||||
use quick_xml::events::{BytesEnd, BytesStart, BytesText};
|
||||
|
||||
use crate::chat;
|
||||
use crate::config::Config;
|
||||
use crate::constants::*;
|
||||
use crate::context::*;
|
||||
use crate::dc_tools::*;
|
||||
@@ -63,7 +62,7 @@ impl Kml {
|
||||
|
||||
pub fn parse(context: &Context, content: impl AsRef<str>) -> Result<Self, Error> {
|
||||
ensure!(
|
||||
content.as_ref().len() <= (1024 * 1024),
|
||||
content.as_ref().len() <= (1 * 1024 * 1024),
|
||||
"A kml-files with {} bytes is larger than reasonably expected.",
|
||||
content.as_ref().len()
|
||||
);
|
||||
@@ -197,7 +196,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,
|
||||
@@ -219,7 +218,7 @@ pub fn send_locations_to_chat(context: &Context, chat_id: u32, seconds: i64) {
|
||||
msg.text =
|
||||
Some(context.stock_system_msg(StockMessage::MsgLocationEnabled, "", "", 0));
|
||||
msg.param.set_int(Param::Cmd, 8);
|
||||
chat::send_msg(context, chat_id, &mut msg).unwrap_or_default();
|
||||
chat::send_msg(context, chat_id, &mut msg).unwrap();
|
||||
} else if 0 == seconds && is_sending_locations_before {
|
||||
let stock_str =
|
||||
context.stock_system_msg(StockMessage::MsgLocationDisabled, "", "", 0);
|
||||
@@ -257,9 +256,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 +292,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(
|
||||
@@ -360,7 +359,7 @@ pub fn get_range(
|
||||
}
|
||||
|
||||
fn is_marker(txt: &str) -> bool {
|
||||
txt.len() == 1 && !txt.starts_with(' ')
|
||||
txt.len() == 1 && txt.chars().next().unwrap() != ' '
|
||||
}
|
||||
|
||||
pub fn delete_all(context: &Context) -> Result<(), Error> {
|
||||
@@ -376,7 +375,8 @@ pub fn get_kml(context: &Context, chat_id: u32) -> Result<(String, u32), Error>
|
||||
let mut last_added_location_id = 0;
|
||||
|
||||
let self_addr = context
|
||||
.get_config(Config::ConfiguredAddr)
|
||||
.sql
|
||||
.get_config(context, "configured_addr")
|
||||
.unwrap_or_default();
|
||||
|
||||
let (locations_send_begin, locations_send_until, locations_last_sent) = context.sql.query_row(
|
||||
@@ -615,7 +615,7 @@ pub fn job_do_DC_JOB_MAYBE_SEND_LOCATIONS(context: &Context, _job: &Job) {
|
||||
|
||||
for (chat_id, mut msg) in msgs.into_iter() {
|
||||
// TODO: better error handling
|
||||
chat::send_msg(context, chat_id as u32, &mut msg).unwrap_or_default();
|
||||
chat::send_msg(context, chat_id as u32, &mut msg).unwrap();
|
||||
}
|
||||
}
|
||||
if 0 != continue_streaming {
|
||||
|
||||
@@ -4,22 +4,6 @@ use std::fmt;
|
||||
use crate::context::Context;
|
||||
use crate::error::Error;
|
||||
|
||||
#[derive(Copy, Clone, Debug, Display, FromPrimitive)]
|
||||
#[repr(i32)]
|
||||
#[strum(serialize_all = "snake_case")]
|
||||
pub enum CertificateChecks {
|
||||
Automatic = 0,
|
||||
Strict = 1,
|
||||
AcceptInvalidHostnames = 2,
|
||||
AcceptInvalidCertificates = 3,
|
||||
}
|
||||
|
||||
impl Default for CertificateChecks {
|
||||
fn default() -> Self {
|
||||
Self::Automatic
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Default, Debug)]
|
||||
pub struct LoginParam {
|
||||
pub addr: String,
|
||||
@@ -27,14 +11,10 @@ pub struct LoginParam {
|
||||
pub mail_user: String,
|
||||
pub mail_pw: String,
|
||||
pub mail_port: i32,
|
||||
/// IMAP TLS options: whether to allow invalid certificates and/or invalid hostnames
|
||||
pub imap_certificate_checks: CertificateChecks,
|
||||
pub send_server: String,
|
||||
pub send_user: String,
|
||||
pub send_pw: String,
|
||||
pub send_port: i32,
|
||||
/// SMTP TLS options: whether to allow invalid certificates and/or invalid hostnames
|
||||
pub smtp_certificate_checks: CertificateChecks,
|
||||
pub server_flags: i32,
|
||||
}
|
||||
|
||||
@@ -51,53 +31,37 @@ impl LoginParam {
|
||||
|
||||
let key = format!("{}addr", prefix);
|
||||
let addr = sql
|
||||
.get_raw_config(context, key)
|
||||
.get_config(context, key)
|
||||
.unwrap_or_default()
|
||||
.trim()
|
||||
.to_string();
|
||||
|
||||
let key = format!("{}mail_server", prefix);
|
||||
let mail_server = sql.get_raw_config(context, key).unwrap_or_default();
|
||||
let mail_server = sql.get_config(context, key).unwrap_or_default();
|
||||
|
||||
let key = format!("{}mail_port", prefix);
|
||||
let mail_port = sql.get_raw_config_int(context, key).unwrap_or_default();
|
||||
let mail_port = sql.get_config_int(context, key).unwrap_or_default();
|
||||
|
||||
let key = format!("{}mail_user", prefix);
|
||||
let mail_user = sql.get_raw_config(context, key).unwrap_or_default();
|
||||
let mail_user = sql.get_config(context, key).unwrap_or_default();
|
||||
|
||||
let key = format!("{}mail_pw", prefix);
|
||||
let mail_pw = sql.get_raw_config(context, key).unwrap_or_default();
|
||||
|
||||
let key = format!("{}imap_certificate_checks", prefix);
|
||||
let imap_certificate_checks =
|
||||
if let Some(certificate_checks) = sql.get_raw_config_int(context, key) {
|
||||
num_traits::FromPrimitive::from_i32(certificate_checks).unwrap()
|
||||
} else {
|
||||
Default::default()
|
||||
};
|
||||
let mail_pw = sql.get_config(context, key).unwrap_or_default();
|
||||
|
||||
let key = format!("{}send_server", prefix);
|
||||
let send_server = sql.get_raw_config(context, key).unwrap_or_default();
|
||||
let send_server = sql.get_config(context, key).unwrap_or_default();
|
||||
|
||||
let key = format!("{}send_port", prefix);
|
||||
let send_port = sql.get_raw_config_int(context, key).unwrap_or_default();
|
||||
let send_port = sql.get_config_int(context, key).unwrap_or_default();
|
||||
|
||||
let key = format!("{}send_user", prefix);
|
||||
let send_user = sql.get_raw_config(context, key).unwrap_or_default();
|
||||
let send_user = sql.get_config(context, key).unwrap_or_default();
|
||||
|
||||
let key = format!("{}send_pw", prefix);
|
||||
let send_pw = sql.get_raw_config(context, key).unwrap_or_default();
|
||||
|
||||
let key = format!("{}smtp_certificate_checks", prefix);
|
||||
let smtp_certificate_checks =
|
||||
if let Some(certificate_checks) = sql.get_raw_config_int(context, key) {
|
||||
num_traits::FromPrimitive::from_i32(certificate_checks).unwrap()
|
||||
} else {
|
||||
Default::default()
|
||||
};
|
||||
let send_pw = sql.get_config(context, key).unwrap_or_default();
|
||||
|
||||
let key = format!("{}server_flags", prefix);
|
||||
let server_flags = sql.get_raw_config_int(context, key).unwrap_or_default();
|
||||
let server_flags = sql.get_config_int(context, key).unwrap_or_default();
|
||||
|
||||
LoginParam {
|
||||
addr: addr.to_string(),
|
||||
@@ -105,12 +69,10 @@ impl LoginParam {
|
||||
mail_user,
|
||||
mail_pw,
|
||||
mail_port,
|
||||
imap_certificate_checks,
|
||||
send_server,
|
||||
send_user,
|
||||
send_pw,
|
||||
send_port,
|
||||
smtp_certificate_checks,
|
||||
server_flags,
|
||||
}
|
||||
}
|
||||
@@ -129,40 +91,34 @@ impl LoginParam {
|
||||
let sql = &context.sql;
|
||||
|
||||
let key = format!("{}addr", prefix);
|
||||
sql.set_raw_config(context, key, Some(&self.addr))?;
|
||||
sql.set_config(context, key, Some(&self.addr))?;
|
||||
|
||||
let key = format!("{}mail_server", prefix);
|
||||
sql.set_raw_config(context, key, Some(&self.mail_server))?;
|
||||
sql.set_config(context, key, Some(&self.mail_server))?;
|
||||
|
||||
let key = format!("{}mail_port", prefix);
|
||||
sql.set_raw_config_int(context, key, self.mail_port)?;
|
||||
sql.set_config_int(context, key, self.mail_port)?;
|
||||
|
||||
let key = format!("{}mail_user", prefix);
|
||||
sql.set_raw_config(context, key, Some(&self.mail_user))?;
|
||||
sql.set_config(context, key, Some(&self.mail_user))?;
|
||||
|
||||
let key = format!("{}mail_pw", prefix);
|
||||
sql.set_raw_config(context, key, Some(&self.mail_pw))?;
|
||||
|
||||
let key = format!("{}imap_certificate_checks", prefix);
|
||||
sql.set_raw_config_int(context, key, self.imap_certificate_checks as i32)?;
|
||||
sql.set_config(context, key, Some(&self.mail_pw))?;
|
||||
|
||||
let key = format!("{}send_server", prefix);
|
||||
sql.set_raw_config(context, key, Some(&self.send_server))?;
|
||||
sql.set_config(context, key, Some(&self.send_server))?;
|
||||
|
||||
let key = format!("{}send_port", prefix);
|
||||
sql.set_raw_config_int(context, key, self.send_port)?;
|
||||
sql.set_config_int(context, key, self.send_port)?;
|
||||
|
||||
let key = format!("{}send_user", prefix);
|
||||
sql.set_raw_config(context, key, Some(&self.send_user))?;
|
||||
sql.set_config(context, key, Some(&self.send_user))?;
|
||||
|
||||
let key = format!("{}send_pw", prefix);
|
||||
sql.set_raw_config(context, key, Some(&self.send_pw))?;
|
||||
|
||||
let key = format!("{}smtp_certificate_checks", prefix);
|
||||
sql.set_raw_config_int(context, key, self.smtp_certificate_checks as i32)?;
|
||||
sql.set_config(context, key, Some(&self.send_pw))?;
|
||||
|
||||
let key = format!("{}server_flags", prefix);
|
||||
sql.set_raw_config_int(context, key, self.server_flags)?;
|
||||
sql.set_config_int(context, key, self.server_flags)?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
@@ -177,18 +133,16 @@ impl fmt::Display for LoginParam {
|
||||
|
||||
write!(
|
||||
f,
|
||||
"{} imap:{}:{}:{}:{}:cert_{} smtp:{}:{}:{}:{}:cert_{} {}",
|
||||
"{} {}:{}:{}:{} {}:{}:{}:{} {}",
|
||||
unset_empty(&self.addr),
|
||||
unset_empty(&self.mail_user),
|
||||
if !self.mail_pw.is_empty() { pw } else { unset },
|
||||
unset_empty(&self.mail_server),
|
||||
self.mail_port,
|
||||
self.imap_certificate_checks,
|
||||
unset_empty(&self.send_user),
|
||||
if !self.send_pw.is_empty() { pw } else { unset },
|
||||
unset_empty(&self.send_server),
|
||||
self.send_port,
|
||||
self.smtp_certificate_checks,
|
||||
flags_readable,
|
||||
)
|
||||
}
|
||||
@@ -250,41 +204,3 @@ fn get_readable_flags(flags: i32) -> String {
|
||||
|
||||
res
|
||||
}
|
||||
|
||||
pub fn dc_build_tls(
|
||||
certificate_checks: CertificateChecks,
|
||||
) -> Result<native_tls::TlsConnector, native_tls::Error> {
|
||||
let mut tls_builder = native_tls::TlsConnector::builder();
|
||||
match certificate_checks {
|
||||
CertificateChecks::Automatic => {
|
||||
// Same as AcceptInvalidCertificates for now.
|
||||
// TODO: use provider database when it becomes available
|
||||
tls_builder
|
||||
.danger_accept_invalid_hostnames(true)
|
||||
.danger_accept_invalid_certs(true)
|
||||
}
|
||||
CertificateChecks::Strict => &mut tls_builder,
|
||||
CertificateChecks::AcceptInvalidHostnames => {
|
||||
tls_builder.danger_accept_invalid_hostnames(true)
|
||||
}
|
||||
CertificateChecks::AcceptInvalidCertificates => tls_builder
|
||||
.danger_accept_invalid_hostnames(true)
|
||||
.danger_accept_invalid_certs(true),
|
||||
}
|
||||
.build()
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
|
||||
#[test]
|
||||
fn test_certificate_checks_display() {
|
||||
use std::string::ToString;
|
||||
|
||||
assert_eq!(
|
||||
"accept_invalid_hostnames".to_string(),
|
||||
CertificateChecks::AcceptInvalidHostnames.to_string()
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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(buf_setupcodebegin));
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -481,10 +494,12 @@ impl Lot {
|
||||
} else {
|
||||
self.text1 = None;
|
||||
}
|
||||
} else if let Some(contact) = contact {
|
||||
self.text1 = Some(contact.get_first_name().into());
|
||||
} else {
|
||||
self.text1 = None;
|
||||
if let Some(contact) = contact {
|
||||
self.text1 = Some(contact.get_first_name().into());
|
||||
} else {
|
||||
self.text1 = None;
|
||||
}
|
||||
}
|
||||
self.text1_meaning = Meaning::Text1Username;
|
||||
}
|
||||
@@ -511,7 +526,7 @@ pub fn get_msg_info(context: &Context, msg_id: u32) -> String {
|
||||
return ret;
|
||||
}
|
||||
|
||||
let msg = msg.unwrap_or_default();
|
||||
let msg = msg.unwrap();
|
||||
|
||||
let rawtxt: Option<String> = context.sql.query_get_value(
|
||||
context,
|
||||
@@ -523,7 +538,7 @@ pub fn get_msg_info(context: &Context, msg_id: u32) -> String {
|
||||
ret += &format!("Cannot load message #{}.", msg_id as usize);
|
||||
return ret;
|
||||
}
|
||||
let rawtxt = rawtxt.unwrap_or_default();
|
||||
let rawtxt = rawtxt.unwrap();
|
||||
let rawtxt = dc_truncate(rawtxt.trim(), 100000, false);
|
||||
|
||||
let fts = dc_timestamp_to_str(msg.get_timestamp());
|
||||
@@ -603,8 +618,9 @@ pub fn get_msg_info(context: &Context, msg_id: u32) -> String {
|
||||
}
|
||||
|
||||
ret += "\n";
|
||||
if let Some(err) = msg.param.get(Param::Error) {
|
||||
ret += &format!("Error: {}", err)
|
||||
match msg.param.get(Param::Error) {
|
||||
Some(err) => ret += &format!("Error: {}", err),
|
||||
_ => {}
|
||||
}
|
||||
|
||||
if let Some(path) = msg.get_file(context) {
|
||||
@@ -672,7 +688,7 @@ pub fn get_mime_headers(context: &Context, msg_id: u32) -> Option<String> {
|
||||
}
|
||||
|
||||
pub fn delete_msgs(context: &Context, msg_ids: &[u32]) {
|
||||
for msg_id in msg_ids.iter() {
|
||||
for msg_id in msg_ids.into_iter() {
|
||||
update_msg_chat_id(context, *msg_id, DC_CHAT_ID_TRASH);
|
||||
job_add(
|
||||
context,
|
||||
@@ -712,7 +728,7 @@ pub fn markseen_msgs(context: &Context, msg_ids: &[u32]) -> bool {
|
||||
"SELECT m.state, c.blocked FROM msgs m LEFT JOIN chats c ON c.id=m.chat_id WHERE m.id=? AND m.chat_id>9",
|
||||
|mut stmt, _| {
|
||||
let mut res = Vec::with_capacity(msg_ids.len());
|
||||
for id in msg_ids.iter() {
|
||||
for id in msg_ids.into_iter() {
|
||||
let query_res = stmt.query_row(params![*id as i32], |row| {
|
||||
Ok((row.get::<_, MessageState>(0)?, row.get::<_, Option<Blocked>>(1)?.unwrap_or_default()))
|
||||
});
|
||||
@@ -732,7 +748,7 @@ pub fn markseen_msgs(context: &Context, msg_ids: &[u32]) -> bool {
|
||||
return false;
|
||||
}
|
||||
let mut send_event = false;
|
||||
let msgs = msgs.unwrap_or_default();
|
||||
let msgs = msgs.unwrap();
|
||||
|
||||
for (id, curr_state, curr_blocked) in msgs.into_iter() {
|
||||
if curr_blocked == Blocked::Not {
|
||||
@@ -782,7 +798,7 @@ pub fn star_msgs(context: &Context, msg_ids: &[u32], star: bool) -> bool {
|
||||
context
|
||||
.sql
|
||||
.prepare("UPDATE msgs SET starred=? WHERE id=?;", |mut stmt, _| {
|
||||
for msg_id in msg_ids.iter() {
|
||||
for msg_id in msg_ids.into_iter() {
|
||||
stmt.execute(params![star as i32, *msg_id as i32])?;
|
||||
}
|
||||
Ok(())
|
||||
@@ -802,7 +818,6 @@ pub fn get_summarytext_by_raw(
|
||||
let prefix = match viewtype {
|
||||
Viewtype::Image => context.stock_str(StockMessage::Image).into_owned(),
|
||||
Viewtype::Gif => context.stock_str(StockMessage::Gif).into_owned(),
|
||||
Viewtype::Sticker => context.stock_str(StockMessage::Sticker).into_owned(),
|
||||
Viewtype::Video => context.stock_str(StockMessage::Video).into_owned(),
|
||||
Viewtype::Voice => context.stock_str(StockMessage::VoiceMessage).into_owned(),
|
||||
Viewtype::Audio | Viewtype::File => {
|
||||
@@ -821,7 +836,7 @@ pub fn get_summarytext_by_raw(
|
||||
} else {
|
||||
None
|
||||
}
|
||||
.unwrap_or_else(|| "ErrFileName".to_string());
|
||||
.unwrap_or("ErrFileName".to_string());
|
||||
|
||||
let label = context.stock_str(if viewtype == Viewtype::Audio {
|
||||
StockMessage::Audio
|
||||
@@ -969,7 +984,7 @@ pub fn mdn_from_ext(
|
||||
context.sql.execute(
|
||||
"INSERT INTO msgs_mdns (msg_id, contact_id, timestamp_sent) VALUES (?, ?, ?);",
|
||||
params![*ret_msg_id as i32, from_id as i32, timestamp_sent],
|
||||
).unwrap_or_default(); // TODO: better error handling
|
||||
).unwrap(); // TODO: better error handling
|
||||
}
|
||||
|
||||
// Normal chat? that's quite easy.
|
||||
|
||||
@@ -13,11 +13,10 @@ use mmime::mmapstring::*;
|
||||
use mmime::other::*;
|
||||
|
||||
use crate::chat::{self, Chat};
|
||||
use crate::config::Config;
|
||||
use crate::constants::*;
|
||||
use crate::contact::*;
|
||||
use crate::context::{get_version_str, Context};
|
||||
use crate::dc_mimeparser::SystemMessage;
|
||||
use crate::dc_mimeparser::{mailmime_find_mailimf_fields, SystemMessage};
|
||||
use crate::dc_strencode::*;
|
||||
use crate::dc_tools::*;
|
||||
use crate::e2ee::*;
|
||||
@@ -60,13 +59,11 @@ pub struct MimeFactory<'a> {
|
||||
|
||||
impl<'a> MimeFactory<'a> {
|
||||
fn new(context: &'a Context, msg: Message) -> Self {
|
||||
let cget = |context: &Context, name: &str| context.sql.get_config(context, name);
|
||||
MimeFactory {
|
||||
from_addr: context
|
||||
.get_config(Config::ConfiguredAddr)
|
||||
.unwrap_or_default(),
|
||||
from_displayname: context.get_config(Config::Displayname).unwrap_or_default(),
|
||||
selfstatus: context
|
||||
.get_config(Config::Selfstatus)
|
||||
from_addr: cget(&context, "configured_addr").unwrap_or_default(),
|
||||
from_displayname: cget(&context, "displayname").unwrap_or_default(),
|
||||
selfstatus: cget(&context, "selfstatus")
|
||||
.unwrap_or_else(|| context.stock_str(StockMessage::StatusLine).to_string()),
|
||||
recipients_names: Vec::with_capacity(5),
|
||||
recipients_addr: Vec::with_capacity(5),
|
||||
@@ -109,10 +106,15 @@ impl<'a> MimeFactory<'a> {
|
||||
}
|
||||
|
||||
pub fn load_mdn(context: &'a Context, msg_id: u32) -> Result<MimeFactory, Error> {
|
||||
if !context.get_config_bool(Config::MdnsEnabled) {
|
||||
// MDNs not enabled - check this is late, in the job. the
|
||||
// user may have changed its choice while offline ...
|
||||
bail!("MDNs meanwhile disabled")
|
||||
if 0 == context
|
||||
.sql
|
||||
.get_config_int(context, "mdns_enabled")
|
||||
.unwrap_or_else(|| 1)
|
||||
{
|
||||
// MDNs not enabled - check this is late, in the job. the use may have changed its
|
||||
// choice while offline ...
|
||||
|
||||
bail!("MDNs disabled ")
|
||||
}
|
||||
|
||||
let msg = Message::load_from_db(context, msg_id)?;
|
||||
@@ -139,35 +141,52 @@ impl<'a> MimeFactory<'a> {
|
||||
}
|
||||
|
||||
/*******************************************************************************
|
||||
* Render a basic email
|
||||
* Render
|
||||
******************************************************************************/
|
||||
// XXX restrict unsafe to parts, introduce wrapmime helpers where appropriate
|
||||
// restrict unsafe to parts, introduce wrapmime helpers where appropriate
|
||||
pub unsafe fn render(&mut self) -> Result<(), Error> {
|
||||
if self.loaded == Loaded::Nothing || !self.out.is_null() {
|
||||
bail!("Invalid use of mimefactory-object.");
|
||||
}
|
||||
let context = &self.context;
|
||||
let from = wrapmime::new_mailbox_list(&self.from_displayname, &self.from_addr);
|
||||
|
||||
let to = mailimf_address_list_new_empty();
|
||||
let name_iter = self.recipients_names.iter();
|
||||
let addr_iter = self.recipients_addr.iter();
|
||||
for (name, addr) in name_iter.zip(addr_iter) {
|
||||
mailimf_address_list_add(
|
||||
to,
|
||||
mailimf_address_new(
|
||||
MAILIMF_ADDRESS_MAILBOX as libc::c_int,
|
||||
mailimf_mailbox_new(
|
||||
if !name.is_empty() {
|
||||
dc_encode_header_words(&name).strdup()
|
||||
} else {
|
||||
ptr::null_mut()
|
||||
},
|
||||
addr.strdup(),
|
||||
/* create basic mail
|
||||
*************************************************************************/
|
||||
|
||||
let from: *mut mailimf_mailbox_list = mailimf_mailbox_list_new_empty();
|
||||
mailimf_mailbox_list_add(
|
||||
from,
|
||||
mailimf_mailbox_new(
|
||||
if !self.from_displayname.is_empty() {
|
||||
dc_encode_header_words(&self.from_displayname).strdup()
|
||||
} else {
|
||||
ptr::null_mut()
|
||||
},
|
||||
self.from_addr.strdup(),
|
||||
),
|
||||
);
|
||||
let mut to: *mut mailimf_address_list = ptr::null_mut();
|
||||
if !self.recipients_names.is_empty() && !self.recipients_addr.is_empty() {
|
||||
to = mailimf_address_list_new_empty();
|
||||
let name_iter = self.recipients_names.iter();
|
||||
let addr_iter = self.recipients_addr.iter();
|
||||
for (name, addr) in name_iter.zip(addr_iter) {
|
||||
mailimf_address_list_add(
|
||||
to,
|
||||
mailimf_address_new(
|
||||
MAILIMF_ADDRESS_MAILBOX as libc::c_int,
|
||||
mailimf_mailbox_new(
|
||||
if !name.is_empty() {
|
||||
dc_encode_header_words(&name).strdup()
|
||||
} else {
|
||||
ptr::null_mut()
|
||||
},
|
||||
addr.strdup(),
|
||||
),
|
||||
ptr::null_mut(),
|
||||
),
|
||||
ptr::null_mut(),
|
||||
),
|
||||
);
|
||||
);
|
||||
}
|
||||
}
|
||||
let references_list = if !self.references.is_empty() {
|
||||
dc_str_to_clist(&self.references, " ")
|
||||
@@ -179,7 +198,6 @@ impl<'a> MimeFactory<'a> {
|
||||
} else {
|
||||
ptr::null_mut()
|
||||
};
|
||||
|
||||
let imf_fields = mailimf_fields_new_with_data_all(
|
||||
mailimf_get_date(self.timestamp as i64),
|
||||
from,
|
||||
@@ -384,8 +402,15 @@ impl<'a> MimeFactory<'a> {
|
||||
&fingerprint,
|
||||
);
|
||||
}
|
||||
if let Some(id) = msg.param.get(Param::Arg4) {
|
||||
wrapmime::new_custom_field(imf_fields, "Secure-Join-Group", &id);
|
||||
match msg.param.get(Param::Arg4) {
|
||||
Some(id) => {
|
||||
wrapmime::new_custom_field(
|
||||
imf_fields,
|
||||
"Secure-Join-Group",
|
||||
&id,
|
||||
);
|
||||
}
|
||||
None => {}
|
||||
};
|
||||
}
|
||||
}
|
||||
@@ -410,10 +435,6 @@ impl<'a> MimeFactory<'a> {
|
||||
}
|
||||
}
|
||||
|
||||
if self.msg.type_0 == Viewtype::Sticker {
|
||||
wrapmime::new_custom_field(imf_fields, "Chat-Content", "sticker");
|
||||
}
|
||||
|
||||
if self.msg.type_0 == Viewtype::Voice
|
||||
|| self.msg.type_0 == Viewtype::Audio
|
||||
|| self.msg.type_0 == Viewtype::Video
|
||||
@@ -573,6 +594,7 @@ impl<'a> MimeFactory<'a> {
|
||||
wrapmime::set_body_text(mach_mime_part, &message_text2)?;
|
||||
mailmime_add_part(multipart, mach_mime_part);
|
||||
force_plaintext = DC_FP_NO_AUTOCRYPT_HEADER;
|
||||
info!(context, "sending MDM {:?}", message_text2);
|
||||
/* currently, we do not send MDNs encrypted:
|
||||
- in a multi-device-setup that is not set up properly, MDNs would disturb the communication as they
|
||||
are send automatically which may lead to spreading outdated Autocrypt headers.
|
||||
@@ -623,7 +645,7 @@ impl<'a> MimeFactory<'a> {
|
||||
);
|
||||
|
||||
/*just a pointer into mailmime structure, must not be freed*/
|
||||
let imffields_unprotected = wrapmime::mailmime_find_mailimf_fields(message);
|
||||
let imffields_unprotected = mailmime_find_mailimf_fields(message);
|
||||
ensure!(
|
||||
!imffields_unprotected.is_null(),
|
||||
"could not find mime fields"
|
||||
@@ -635,18 +657,17 @@ impl<'a> MimeFactory<'a> {
|
||||
let aheader = encrypt_helper.get_aheader().to_string();
|
||||
wrapmime::new_custom_field(imffields_unprotected, "Autocrypt", &aheader);
|
||||
}
|
||||
let finalized = if force_plaintext == 0 {
|
||||
encrypt_helper.try_encrypt(
|
||||
let mut finalized = false;
|
||||
if force_plaintext == 0 {
|
||||
finalized = encrypt_helper.try_encrypt(
|
||||
self,
|
||||
e2ee_guaranteed,
|
||||
min_verified,
|
||||
do_gossip,
|
||||
message,
|
||||
imffields_unprotected,
|
||||
)?
|
||||
} else {
|
||||
false
|
||||
};
|
||||
)?;
|
||||
}
|
||||
if !finalized {
|
||||
self.finalize_mime_message(message, false, false)?;
|
||||
}
|
||||
@@ -671,28 +692,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();
|
||||
|
||||
let command = factory.msg.param.get_cmd();
|
||||
let msg = &factory.msg;
|
||||
@@ -702,7 +726,8 @@ impl<'a> MimeFactory<'a> {
|
||||
let email_to_remove = msg.param.get(Param::Arg).unwrap_or_default();
|
||||
|
||||
let self_addr = context
|
||||
.get_config(Config::ConfiguredAddr)
|
||||
.sql
|
||||
.get_config(context, "configured_addr")
|
||||
.unwrap_or_default();
|
||||
|
||||
if !email_to_remove.is_empty() && email_to_remove != self_addr {
|
||||
@@ -714,7 +739,10 @@ impl<'a> MimeFactory<'a> {
|
||||
}
|
||||
if command != SystemMessage::AutocryptSetupMessage
|
||||
&& command != SystemMessage::SecurejoinMessage
|
||||
&& context.get_config_bool(Config::MdnsEnabled)
|
||||
&& 0 != context
|
||||
.sql
|
||||
.get_config_int(context, "mdns_enabled")
|
||||
.unwrap_or_else(|| 1)
|
||||
{
|
||||
factory.req_mdn = true;
|
||||
}
|
||||
@@ -895,13 +923,15 @@ 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();
|
||||
mailmime_set_body_file(mime_sub, dc_strdup(abs_path.as_ptr()));
|
||||
Ok((mime_sub, filename_to_send))
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) fn vec_contains_lowercase(vec: &[String], part: &str) -> bool {
|
||||
pub(crate) fn vec_contains_lowercase(vec: &Vec<String>, part: &str) -> bool {
|
||||
let partlc = part.to_lowercase();
|
||||
for cur in vec.iter() {
|
||||
if cur.to_lowercase() == partlc {
|
||||
|
||||
@@ -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",
|
||||
@@ -52,7 +50,7 @@ pub fn dc_get_oauth2_url(
|
||||
if let Some(oauth2) = Oauth2::from_address(addr) {
|
||||
if context
|
||||
.sql
|
||||
.set_raw_config(
|
||||
.set_config(
|
||||
context,
|
||||
"oauth2_pending_redirect_uri",
|
||||
Some(redirect_uri.as_ref()),
|
||||
@@ -84,18 +82,17 @@ pub fn dc_get_oauth2_access_token(
|
||||
|
||||
// read generated token
|
||||
if !regenerate && !is_expired(context) {
|
||||
let access_token = context.sql.get_raw_config(context, "oauth2_access_token");
|
||||
let access_token = context.sql.get_config(context, "oauth2_access_token");
|
||||
if access_token.is_some() {
|
||||
// success
|
||||
return access_token;
|
||||
}
|
||||
}
|
||||
|
||||
// generate new token: build & call auth url
|
||||
let refresh_token = context.sql.get_raw_config(context, "oauth2_refresh_token");
|
||||
let refresh_token = context.sql.get_config(context, "oauth2_refresh_token");
|
||||
let refresh_token_for = context
|
||||
.sql
|
||||
.get_raw_config(context, "oauth2_refresh_token_for")
|
||||
.get_config(context, "oauth2_refresh_token_for")
|
||||
.unwrap_or_else(|| "unset".into());
|
||||
|
||||
let (redirect_uri, token_url, update_redirect_uri_on_success) =
|
||||
@@ -104,7 +101,7 @@ pub fn dc_get_oauth2_access_token(
|
||||
(
|
||||
context
|
||||
.sql
|
||||
.get_raw_config(context, "oauth2_pending_redirect_uri")
|
||||
.get_config(context, "oauth2_pending_redirect_uri")
|
||||
.unwrap_or_else(|| "unset".into()),
|
||||
oauth2.init_token,
|
||||
true,
|
||||
@@ -117,43 +114,20 @@ pub fn dc_get_oauth2_access_token(
|
||||
(
|
||||
context
|
||||
.sql
|
||||
.get_raw_config(context, "oauth2_redirect_uri")
|
||||
.get_config(context, "oauth2_redirect_uri")
|
||||
.unwrap_or_else(|| "unset".into()),
|
||||
oauth2.refresh_token,
|
||||
false,
|
||||
)
|
||||
};
|
||||
|
||||
// to allow easier specification of different configurations,
|
||||
// token_url is in GET-method-format, sth. as https://domain?param1=val1¶m2=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,17 +155,15 @@ 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
|
||||
.sql
|
||||
.set_raw_config(context, "oauth2_refresh_token", Some(token))
|
||||
.set_config(context, "oauth2_refresh_token", Some(token))
|
||||
.ok();
|
||||
context
|
||||
.sql
|
||||
.set_raw_config(context, "oauth2_refresh_token_for", Some(code.as_ref()))
|
||||
.set_config(context, "oauth2_refresh_token_for", Some(code.as_ref()))
|
||||
.ok();
|
||||
}
|
||||
|
||||
@@ -201,7 +172,7 @@ pub fn dc_get_oauth2_access_token(
|
||||
if let Some(ref token) = response.access_token {
|
||||
context
|
||||
.sql
|
||||
.set_raw_config(context, "oauth2_access_token", Some(token))
|
||||
.set_config(context, "oauth2_access_token", Some(token))
|
||||
.ok();
|
||||
let expires_in = response
|
||||
.expires_in
|
||||
@@ -210,13 +181,13 @@ pub fn dc_get_oauth2_access_token(
|
||||
.unwrap_or_else(|| 0);
|
||||
context
|
||||
.sql
|
||||
.set_raw_config_int64(context, "oauth2_timestamp_expires", expires_in)
|
||||
.set_config_int64(context, "oauth2_timestamp_expires", expires_in)
|
||||
.ok();
|
||||
|
||||
if update_redirect_uri_on_success {
|
||||
context
|
||||
.sql
|
||||
.set_raw_config(context, "oauth2_redirect_uri", Some(redirect_uri.as_ref()))
|
||||
.set_config(context, "oauth2_redirect_uri", Some(redirect_uri.as_ref()))
|
||||
.ok();
|
||||
}
|
||||
} else {
|
||||
@@ -236,8 +207,14 @@ pub fn dc_get_oauth2_addr(
|
||||
addr: impl AsRef<str>,
|
||||
code: impl AsRef<str>,
|
||||
) -> Option<String> {
|
||||
let oauth2 = Oauth2::from_address(addr.as_ref())?;
|
||||
oauth2.get_userinfo?;
|
||||
let oauth2 = Oauth2::from_address(addr.as_ref());
|
||||
if oauth2.is_none() {
|
||||
return None;
|
||||
}
|
||||
let oauth2 = oauth2.unwrap();
|
||||
if oauth2.get_userinfo.is_none() {
|
||||
return None;
|
||||
}
|
||||
|
||||
if let Some(access_token) =
|
||||
dc_get_oauth2_access_token(context, addr.as_ref(), code.as_ref(), false)
|
||||
@@ -297,7 +274,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 +283,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.");
|
||||
@@ -324,7 +299,7 @@ impl Oauth2 {
|
||||
fn is_expired(context: &Context) -> bool {
|
||||
let expire_timestamp = context
|
||||
.sql
|
||||
.get_raw_config_int64(context, "oauth2_timestamp_expires")
|
||||
.get_config_int64(context, "oauth2_timestamp_expires")
|
||||
.unwrap_or_default();
|
||||
|
||||
if expire_timestamp <= 0 {
|
||||
|
||||
62
src/param.rs
62
src/param.rs
@@ -12,65 +12,65 @@ use crate::error;
|
||||
#[repr(u8)]
|
||||
pub enum Param {
|
||||
/// For messages and jobs
|
||||
File = b'f',
|
||||
File = 'f' as u8,
|
||||
/// For Messages
|
||||
Width = b'w',
|
||||
Width = 'w' as u8,
|
||||
/// For Messages
|
||||
Height = b'h',
|
||||
Height = 'h' as u8,
|
||||
/// For Messages
|
||||
Duration = b'd',
|
||||
Duration = 'd' as u8,
|
||||
/// For Messages
|
||||
MimeType = b'm',
|
||||
MimeType = 'm' as u8,
|
||||
/// For Messages: message is encryoted, outgoing: guarantee E2EE or the message is not send
|
||||
GuranteeE2ee = b'c',
|
||||
GuranteeE2ee = 'c' as u8,
|
||||
/// For Messages: decrypted with validation errors or without mutual set, if neither
|
||||
/// 'c' nor 'e' are preset, the messages is only transport encrypted.
|
||||
ErroneousE2ee = b'e',
|
||||
ErroneousE2ee = 'e' as u8,
|
||||
/// For Messages: force unencrypted message, either `ForcePlaintext::AddAutocryptHeader` (1),
|
||||
/// `ForcePlaintext::NoAutocryptHeader` (2) or 0.
|
||||
ForcePlaintext = b'u',
|
||||
ForcePlaintext = 'u' as u8,
|
||||
/// For Messages
|
||||
WantsMdn = b'r',
|
||||
WantsMdn = 'r' as u8,
|
||||
/// For Messages
|
||||
Forwarded = b'a',
|
||||
Forwarded = 'a' as u8,
|
||||
/// For Messages
|
||||
Cmd = b'S',
|
||||
Cmd = 'S' as u8,
|
||||
/// For Messages
|
||||
Arg = b'E',
|
||||
Arg = 'E' as u8,
|
||||
/// For Messages
|
||||
Arg2 = b'F',
|
||||
Arg2 = 'F' as u8,
|
||||
/// For Messages
|
||||
Arg3 = b'G',
|
||||
Arg3 = 'G' as u8,
|
||||
/// For Messages
|
||||
Arg4 = b'H',
|
||||
Arg4 = 'H' as u8,
|
||||
/// For Messages
|
||||
Error = b'L',
|
||||
Error = 'L' as u8,
|
||||
/// For Messages: space-separated list of messaged IDs of forwarded copies.
|
||||
PrepForwards = b'P',
|
||||
PrepForwards = 'P' as u8,
|
||||
/// For Jobs
|
||||
SetLatitude = b'l',
|
||||
SetLatitude = 'l' as u8,
|
||||
/// For Jobs
|
||||
SetLongitude = b'n',
|
||||
SetLongitude = 'n' as u8,
|
||||
/// For Jobs
|
||||
ServerFolder = b'Z',
|
||||
ServerFolder = 'Z' as u8,
|
||||
/// For Jobs
|
||||
ServerUid = b'z',
|
||||
ServerUid = 'z' as u8,
|
||||
/// For Jobs
|
||||
AlsoMove = b'M',
|
||||
AlsoMove = 'M' as u8,
|
||||
/// For Jobs: space-separated list of message recipients
|
||||
Recipients = b'R',
|
||||
Recipients = 'R' as u8,
|
||||
// For Groups
|
||||
Unpromoted = b'U',
|
||||
Unpromoted = 'U' as u8,
|
||||
// For Groups and Contacts
|
||||
ProfileImage = b'i',
|
||||
ProfileImage = 'i' as u8,
|
||||
// For Chats
|
||||
Selftalk = b'K',
|
||||
Selftalk = 'K' as u8,
|
||||
// For QR
|
||||
Auth = b's',
|
||||
Auth = 's' as u8,
|
||||
// For QR
|
||||
GroupId = b'x',
|
||||
GroupId = 'x' as u8,
|
||||
// For QR
|
||||
GroupName = b'g',
|
||||
GroupName = 'g' as u8,
|
||||
}
|
||||
|
||||
/// Possible values for `Param::ForcePlaintext`.
|
||||
@@ -122,8 +122,8 @@ impl str::FromStr for Params {
|
||||
ensure!(key.is_some(), "Missing key");
|
||||
ensure!(value.is_some(), "Missing value");
|
||||
|
||||
let key = key.unwrap_or_default().trim();
|
||||
let value = value.unwrap_or_default().trim();
|
||||
let key = key.unwrap().trim();
|
||||
let value = value.unwrap().trim();
|
||||
|
||||
if let Some(key) = Param::from_u8(key.as_bytes()[0]) {
|
||||
inner.insert(key, value.to_string());
|
||||
|
||||
@@ -400,8 +400,7 @@ impl<'a> Peerstate<'a> {
|
||||
&self.verified_key_fingerprint,
|
||||
&self.addr,
|
||||
],
|
||||
)?;
|
||||
reset_gossiped_timestamp(self.context, 0);
|
||||
)?
|
||||
} else if self.to_save == Some(ToSave::Timestamps) {
|
||||
sql::execute(
|
||||
self.context,
|
||||
@@ -417,6 +416,10 @@ impl<'a> Peerstate<'a> {
|
||||
)?;
|
||||
}
|
||||
|
||||
if self.to_save == Some(ToSave::All) || create {
|
||||
reset_gossiped_timestamp(self.context, 0);
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
@@ -472,7 +475,7 @@ mod tests {
|
||||
"failed to save to db"
|
||||
);
|
||||
|
||||
let peerstate_new = Peerstate::from_addr(&ctx.ctx, &ctx.ctx.sql, addr)
|
||||
let peerstate_new = Peerstate::from_addr(&ctx.ctx, &ctx.ctx.sql, addr.into())
|
||||
.expect("failed to load peerstate from db");
|
||||
|
||||
// clear to_save, as that is not persissted
|
||||
@@ -484,44 +487,6 @@ mod tests {
|
||||
assert_eq!(peerstate, peerstate_new2);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_peerstate_double_create() {
|
||||
let ctx = crate::test_utils::dummy_context();
|
||||
let addr = "hello@mail.com";
|
||||
|
||||
let pub_key = crate::key::Key::from_base64(
|
||||
include_str!("../test-data/key/public.asc"),
|
||||
KeyType::Public,
|
||||
)
|
||||
.unwrap();
|
||||
|
||||
let peerstate = Peerstate {
|
||||
context: &ctx.ctx,
|
||||
addr: Some(addr.into()),
|
||||
last_seen: 10,
|
||||
last_seen_autocrypt: 11,
|
||||
prefer_encrypt: EncryptPreference::Mutual,
|
||||
public_key: Some(pub_key.clone()),
|
||||
public_key_fingerprint: Some(pub_key.fingerprint()),
|
||||
gossip_key: None,
|
||||
gossip_timestamp: 12,
|
||||
gossip_key_fingerprint: None,
|
||||
verified_key: None,
|
||||
verified_key_fingerprint: None,
|
||||
to_save: Some(ToSave::All),
|
||||
degrade_event: None,
|
||||
};
|
||||
|
||||
assert!(
|
||||
peerstate.save_to_db(&ctx.ctx.sql, true).is_ok(),
|
||||
"failed to save"
|
||||
);
|
||||
assert!(
|
||||
peerstate.save_to_db(&ctx.ctx.sql, true).is_ok(),
|
||||
"double-call with create failed"
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_peerstate_with_empty_gossip_key_save_to_db() {
|
||||
let ctx = crate::test_utils::dummy_context();
|
||||
@@ -555,7 +520,7 @@ mod tests {
|
||||
"failed to save"
|
||||
);
|
||||
|
||||
let peerstate_new = Peerstate::from_addr(&ctx.ctx, &ctx.ctx.sql, addr)
|
||||
let peerstate_new = Peerstate::from_addr(&ctx.ctx, &ctx.ctx.sql, addr.into())
|
||||
.expect("failed to load peerstate from db");
|
||||
|
||||
// clear to_save, as that is not persissted
|
||||
|
||||
193
src/pgp.rs
193
src/pgp.rs
@@ -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 *p1 as libc::c_int == '\n' as i32 {
|
||||
*line.offset(line_chars as isize) = 0i32 as libc::c_char;
|
||||
if headerline.is_null() {
|
||||
dc_trim(line);
|
||||
if strncmp(
|
||||
line,
|
||||
b"-----BEGIN \x00" as *const u8 as *const libc::c_char,
|
||||
1,
|
||||
) == 0i32
|
||||
&& strncmp(
|
||||
&mut *line.offset(strlen(line).wrapping_sub(5) as isize),
|
||||
b"-----\x00" as *const u8 as *const libc::c_char,
|
||||
5,
|
||||
) == 0i32
|
||||
{
|
||||
headerline = line;
|
||||
*ret_headerline = as_str(headerline).to_string();
|
||||
}
|
||||
} else if strspn(line, b"\t\r\n \x00" as *const u8 as *const libc::c_char)
|
||||
== strlen(line)
|
||||
{
|
||||
base64 = p1.offset(1isize);
|
||||
break;
|
||||
} else {
|
||||
p2 = strchr(line, ':' as i32);
|
||||
if p2.is_null() {
|
||||
*line.offset(line_chars as isize) = '\n' as i32 as libc::c_char;
|
||||
base64 = line;
|
||||
break;
|
||||
} else {
|
||||
*p2 = 0i32 as libc::c_char;
|
||||
dc_trim(line);
|
||||
if strcasecmp(
|
||||
line,
|
||||
b"Passphrase-Begin\x00" as *const u8 as *const libc::c_char,
|
||||
) == 0i32
|
||||
{
|
||||
p2 = p2.offset(1isize);
|
||||
dc_trim(p2);
|
||||
if !ret_setupcodebegin.is_null() {
|
||||
*ret_setupcodebegin = p2
|
||||
}
|
||||
} else if strcasecmp(
|
||||
line,
|
||||
b"Autocrypt-Prefer-Encrypt\x00" as *const u8 as *const libc::c_char,
|
||||
) == 0i32
|
||||
{
|
||||
p2 = p2.offset(1isize);
|
||||
dc_trim(p2);
|
||||
if !ret_preferencrypt.is_null() {
|
||||
*ret_preferencrypt = p2
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
p1 = p1.offset(1isize);
|
||||
line = p1;
|
||||
line_chars = 0;
|
||||
} else {
|
||||
p1 = p1.offset(1isize);
|
||||
line_chars = line_chars.wrapping_add(1)
|
||||
}
|
||||
}
|
||||
if !(headerline.is_null() || base64.is_null()) {
|
||||
/* now, line points to beginning of base64 data, search end */
|
||||
/*the trailing space makes sure, this is not a normal base64 sequence*/
|
||||
p1 = strstr(base64, b"-----END \x00" as *const u8 as *const libc::c_char);
|
||||
if !(p1.is_null()
|
||||
|| strncmp(
|
||||
p1.offset(9isize),
|
||||
headerline.offset(11isize),
|
||||
strlen(headerline.offset(11isize)),
|
||||
) != 0i32)
|
||||
{
|
||||
*p1 = 0i32 as libc::c_char;
|
||||
dc_trim(base64);
|
||||
if !ret_base64.is_null() {
|
||||
*ret_base64 = base64
|
||||
}
|
||||
success = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// 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>,
|
||||
@@ -105,7 +189,7 @@ pub fn pk_encrypt(
|
||||
let lit_msg = Message::new_literal_bytes("", plain);
|
||||
let pkeys: Vec<&SignedPublicKey> = public_keys_for_encryption
|
||||
.keys()
|
||||
.iter()
|
||||
.into_iter()
|
||||
.filter_map(|key| {
|
||||
let k: &Key = &key;
|
||||
k.try_into().ok()
|
||||
@@ -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()));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
@@ -81,7 +82,10 @@ pub fn dc_get_securejoin_qr(context: &Context, group_chat_id: u32) -> Option<Str
|
||||
}
|
||||
};
|
||||
|
||||
let self_name = context.get_config(Config::Displayname).unwrap_or_default();
|
||||
let self_name = context
|
||||
.sql
|
||||
.get_config(context, "displayname")
|
||||
.unwrap_or_default();
|
||||
|
||||
fingerprint = match get_self_fingerprint(context) {
|
||||
Some(fp) => fp,
|
||||
@@ -156,7 +160,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 +173,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 +187,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;
|
||||
@@ -209,7 +213,7 @@ pub fn dc_join_securejoin(context: &Context, qr: &str) -> u32 {
|
||||
info!(context, "Taking protocol shortcut.");
|
||||
context.bob.write().unwrap().expects = DC_VC_CONTACT_CONFIRM;
|
||||
joiner_progress!(context, chat_id_2_contact_id(context, contact_chat_id), 400);
|
||||
let own_fingerprint = get_self_fingerprint(context).unwrap_or_default();
|
||||
let own_fingerprint = get_self_fingerprint(context).unwrap();
|
||||
send_handshake_msg(
|
||||
context,
|
||||
contact_chat_id,
|
||||
@@ -239,12 +243,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,
|
||||
@@ -281,7 +294,7 @@ fn send_handshake_msg(
|
||||
msg.param.set_int(Param::GuranteeE2ee, 1);
|
||||
}
|
||||
// TODO. handle cleanup on error
|
||||
chat::send_msg(context, contact_chat_id, &mut msg).unwrap_or_default();
|
||||
chat::send_msg(context, contact_chat_id, &mut msg).unwrap();
|
||||
}
|
||||
|
||||
fn chat_id_2_contact_id(context: &Context, contact_chat_id: u32) -> u32 {
|
||||
@@ -626,7 +639,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) {
|
||||
@@ -665,9 +678,7 @@ fn mark_peer_as_verified(context: &Context, fingerprint: impl AsRef<str>) -> Res
|
||||
if peerstate.set_verified(1, fingerprint.as_ref(), 2) {
|
||||
peerstate.prefer_encrypt = EncryptPreference::Mutual;
|
||||
peerstate.to_save = Some(ToSave::All);
|
||||
peerstate
|
||||
.save_to_db(&context.sql, false)
|
||||
.unwrap_or_default();
|
||||
peerstate.save_to_db(&context.sql, false).unwrap();
|
||||
return Ok(());
|
||||
}
|
||||
}
|
||||
@@ -685,7 +696,7 @@ fn encrypted_and_signed(mimeparser: &MimeParser, expected_fingerprint: impl AsRe
|
||||
if !mimeparser.encrypted {
|
||||
warn!(mimeparser.context, "Message not encrypted.",);
|
||||
false
|
||||
} else if mimeparser.signatures.is_empty() {
|
||||
} else if mimeparser.signatures.len() <= 0 {
|
||||
warn!(mimeparser.context, "Message not signed.",);
|
||||
false
|
||||
} else if expected_fingerprint.as_ref().is_empty() {
|
||||
|
||||
72
src/smtp.rs
72
src/smtp.rs
@@ -3,9 +3,8 @@ use lettre::*;
|
||||
|
||||
use crate::constants::*;
|
||||
use crate::context::Context;
|
||||
use crate::error::Error;
|
||||
use crate::events::Event;
|
||||
use crate::login_param::{dc_build_tls, LoginParam};
|
||||
use crate::login_param::LoginParam;
|
||||
use crate::oauth2::*;
|
||||
|
||||
#[derive(DebugStub)]
|
||||
@@ -15,6 +14,7 @@ pub struct Smtp {
|
||||
transport_connected: bool,
|
||||
/// Email address we are sending from.
|
||||
from: Option<EmailAddress>,
|
||||
pub error: Option<String>,
|
||||
}
|
||||
|
||||
impl Smtp {
|
||||
@@ -24,6 +24,7 @@ impl Smtp {
|
||||
transport: None,
|
||||
transport_connected: false,
|
||||
from: None,
|
||||
error: None,
|
||||
}
|
||||
}
|
||||
|
||||
@@ -68,10 +69,17 @@ impl Smtp {
|
||||
let domain = &lp.send_server;
|
||||
let port = lp.send_port as u16;
|
||||
|
||||
let tls = dc_build_tls(lp.smtp_certificate_checks).unwrap();
|
||||
let tls = native_tls::TlsConnector::builder()
|
||||
// see also: https://github.com/deltachat/deltachat-core-rust/issues/203
|
||||
.danger_accept_invalid_hostnames(true)
|
||||
.danger_accept_invalid_certs(true)
|
||||
.min_protocol_version(Some(DEFAULT_TLS_PROTOCOLS[0]))
|
||||
.build()
|
||||
.unwrap();
|
||||
|
||||
let tls_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 +89,12 @@ 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())
|
||||
} 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,7 +110,6 @@ impl Smtp {
|
||||
let client = client
|
||||
.smtp_utf8(true)
|
||||
.credentials(creds)
|
||||
.authentication_mechanism(mechanism)
|
||||
.connection_reuse(lettre::smtp::ConnectionReuseParameters::ReuseUnlimited);
|
||||
self.transport = Some(client.transport());
|
||||
context.call_cb(Event::SmtpConnected(format!(
|
||||
@@ -130,50 +125,37 @@ impl Smtp {
|
||||
}
|
||||
}
|
||||
|
||||
/// SMTP-Send a prepared mail to recipients.
|
||||
/// returns boolean whether send was successful.
|
||||
pub fn send<'a>(
|
||||
&mut self,
|
||||
context: &Context,
|
||||
recipients: Vec<EmailAddress>,
|
||||
message: Vec<u8>,
|
||||
) -> Result<(), Error> {
|
||||
let message_len = message.len();
|
||||
|
||||
let recipients_display = recipients
|
||||
.iter()
|
||||
.map(|x| format!("{}", x))
|
||||
.collect::<Vec<String>>()
|
||||
.join(",");
|
||||
|
||||
body: Vec<u8>,
|
||||
) -> usize {
|
||||
if let Some(ref mut transport) = self.transport {
|
||||
let envelope = Envelope::new(self.from.clone(), recipients);
|
||||
ensure!(envelope.is_ok(), "internal smtp-message construction fail");
|
||||
let envelope = envelope.unwrap();
|
||||
let envelope = Envelope::new(self.from.clone(), recipients).expect("invalid envelope");
|
||||
let mail = SendableEmail::new(
|
||||
envelope,
|
||||
"mail-id".into(), // TODO: random id
|
||||
message,
|
||||
body,
|
||||
);
|
||||
|
||||
match transport.send(mail) {
|
||||
Ok(_) => {
|
||||
context.call_cb(Event::SmtpMessageSent(format!(
|
||||
"Message len={} was smtp-sent to {}",
|
||||
message_len, recipients_display
|
||||
)));
|
||||
context.call_cb(Event::SmtpMessageSent(
|
||||
"Message was sent to SMTP server".into(),
|
||||
));
|
||||
self.transport_connected = true;
|
||||
Ok(())
|
||||
1
|
||||
}
|
||||
Err(err) => {
|
||||
bail!("SMTP failed len={}: error: {}", message_len, err);
|
||||
warn!(context, "SMTP failed to send message: {}", err);
|
||||
self.error = Some(format!("{}", err));
|
||||
0
|
||||
}
|
||||
}
|
||||
} else {
|
||||
bail!(
|
||||
"uh? SMTP has no transport, failed to send to {:?}",
|
||||
recipients_display
|
||||
);
|
||||
// TODO: log error
|
||||
0
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
150
src/sql.rs
150
src/sql.rs
@@ -1,6 +1,5 @@
|
||||
use std::collections::HashSet;
|
||||
use std::sync::{Arc, RwLock};
|
||||
use std::time::Duration;
|
||||
|
||||
use rusqlite::{Connection, OpenFlags, Statement, NO_PARAMS};
|
||||
use thread_local_object::ThreadLocal;
|
||||
@@ -11,6 +10,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 +41,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(_) => {
|
||||
@@ -67,16 +68,6 @@ impl Sql {
|
||||
let res = match &*self.pool.read().unwrap() {
|
||||
Some(pool) => {
|
||||
let conn = pool.get()?;
|
||||
|
||||
// Only one process can make changes to the database at one time.
|
||||
// busy_timeout defines, that if a seconds process wants write access,
|
||||
// this second process will wait some milliseconds
|
||||
// and try over until it gets write access or the given timeout is elapsed.
|
||||
// If the second process does not get write access within the given timeout,
|
||||
// sqlite3_step() will return the error SQLITE_BUSY.
|
||||
// (without a busy_timeout, sqlite3_step() would return SQLITE_BUSY _at once_)
|
||||
conn.busy_timeout(Duration::from_secs(10))?;
|
||||
|
||||
g(&conn)
|
||||
}
|
||||
None => Err(Error::SqlNoConnection),
|
||||
@@ -191,14 +182,14 @@ impl Sql {
|
||||
///
|
||||
/// Setting `None` deletes the value. On failure an error message
|
||||
/// will already have been logged.
|
||||
pub fn set_raw_config(
|
||||
pub fn set_config(
|
||||
&self,
|
||||
context: &Context,
|
||||
key: impl AsRef<str>,
|
||||
value: Option<&str>,
|
||||
) -> Result<()> {
|
||||
if !self.is_open() {
|
||||
error!(context, "set_raw_config(): Database not ready.");
|
||||
error!(context, "set_config(): Database not ready.");
|
||||
return Err(Error::SqlNoConnection);
|
||||
}
|
||||
|
||||
@@ -232,14 +223,14 @@ impl Sql {
|
||||
match res {
|
||||
Ok(_) => Ok(()),
|
||||
Err(err) => {
|
||||
error!(context, "set_raw_config(): Cannot change value. {:?}", &err);
|
||||
Err(err)
|
||||
error!(context, "set_config(): Cannot change value. {:?}", &err);
|
||||
Err(err.into())
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Get configuration options from the database.
|
||||
pub fn get_raw_config(&self, context: &Context, key: impl AsRef<str>) -> Option<String> {
|
||||
pub fn get_config(&self, context: &Context, key: impl AsRef<str>) -> Option<String> {
|
||||
if !self.is_open() || key.as_ref().is_empty() {
|
||||
return None;
|
||||
}
|
||||
@@ -250,46 +241,44 @@ impl Sql {
|
||||
)
|
||||
}
|
||||
|
||||
pub fn set_raw_config_int(
|
||||
pub fn set_config_int(
|
||||
&self,
|
||||
context: &Context,
|
||||
key: impl AsRef<str>,
|
||||
value: i32,
|
||||
) -> Result<()> {
|
||||
self.set_raw_config(context, key, Some(&format!("{}", value)))
|
||||
self.set_config(context, key, Some(&format!("{}", value)))
|
||||
}
|
||||
|
||||
pub fn get_raw_config_int(&self, context: &Context, key: impl AsRef<str>) -> Option<i32> {
|
||||
self.get_raw_config(context, key)
|
||||
.and_then(|s| s.parse().ok())
|
||||
pub fn get_config_int(&self, context: &Context, key: impl AsRef<str>) -> Option<i32> {
|
||||
self.get_config(context, key).and_then(|s| s.parse().ok())
|
||||
}
|
||||
|
||||
pub fn get_raw_config_bool(&self, context: &Context, key: impl AsRef<str>) -> bool {
|
||||
pub fn get_config_bool(&self, context: &Context, key: impl AsRef<str>) -> bool {
|
||||
// Not the most obvious way to encode bool as string, but it is matter
|
||||
// of backward compatibility.
|
||||
self.get_raw_config_int(context, key).unwrap_or_default() > 0
|
||||
self.get_config_int(context, key).unwrap_or_default() > 0
|
||||
}
|
||||
|
||||
pub fn set_raw_config_bool<T>(&self, context: &Context, key: T, value: bool) -> Result<()>
|
||||
pub fn set_config_bool<T>(&self, context: &Context, key: T, value: bool) -> Result<()>
|
||||
where
|
||||
T: AsRef<str>,
|
||||
{
|
||||
let value = if value { Some("1") } else { None };
|
||||
self.set_raw_config(context, key, value)
|
||||
self.set_config(context, key, value)
|
||||
}
|
||||
|
||||
pub fn set_raw_config_int64(
|
||||
pub fn set_config_int64(
|
||||
&self,
|
||||
context: &Context,
|
||||
key: impl AsRef<str>,
|
||||
value: i64,
|
||||
) -> Result<()> {
|
||||
self.set_raw_config(context, key, Some(&format!("{}", value)))
|
||||
self.set_config(context, key, Some(&format!("{}", value)))
|
||||
}
|
||||
|
||||
pub fn get_raw_config_int64(&self, context: &Context, key: impl AsRef<str>) -> Option<i64> {
|
||||
self.get_raw_config(context, key)
|
||||
.and_then(|r| r.parse().ok())
|
||||
pub fn get_config_int64(&self, context: &Context, key: impl AsRef<str>) -> Option<i64> {
|
||||
self.get_config(context, key).and_then(|r| r.parse().ok())
|
||||
}
|
||||
|
||||
fn start_stmt(&self, stmt: impl AsRef<str>) {
|
||||
@@ -318,7 +307,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 +319,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);
|
||||
@@ -341,7 +330,7 @@ fn open(
|
||||
.with_init(|c| c.execute_batch("PRAGMA secure_delete=on;"));
|
||||
let pool = r2d2::Pool::builder()
|
||||
.min_idle(Some(2))
|
||||
.max_size(10)
|
||||
.max_size(4)
|
||||
.connection_timeout(std::time::Duration::new(60, 0))
|
||||
.build(mgr)?;
|
||||
|
||||
@@ -349,7 +338,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 */
|
||||
@@ -477,13 +466,11 @@ fn open(
|
||||
// cannot create the tables - maybe we cannot write?
|
||||
return Err(Error::SqlFailedToOpen);
|
||||
} else {
|
||||
sql.set_raw_config_int(context, "dbversion", 0)?;
|
||||
sql.set_config_int(context, "dbversion", 0)?;
|
||||
}
|
||||
} else {
|
||||
exists_before_update = 1;
|
||||
dbversion_before_update = sql
|
||||
.get_raw_config_int(context, "dbversion")
|
||||
.unwrap_or_default();
|
||||
dbversion_before_update = sql.get_config_int(context, "dbversion").unwrap_or_default();
|
||||
}
|
||||
|
||||
// (1) update low-level database structure.
|
||||
@@ -495,7 +482,6 @@ fn open(
|
||||
let mut update_file_paths = 0;
|
||||
|
||||
if dbversion < 1 {
|
||||
info!(context, "[migration] v1");
|
||||
sql.execute(
|
||||
"CREATE TABLE leftgrps ( id INTEGER PRIMARY KEY, grpid TEXT DEFAULT '');",
|
||||
params![],
|
||||
@@ -505,19 +491,17 @@ fn open(
|
||||
params![],
|
||||
)?;
|
||||
dbversion = 1;
|
||||
sql.set_raw_config_int(context, "dbversion", 1)?;
|
||||
sql.set_config_int(context, "dbversion", 1)?;
|
||||
}
|
||||
if dbversion < 2 {
|
||||
info!(context, "[migration] v2");
|
||||
sql.execute(
|
||||
"ALTER TABLE contacts ADD COLUMN authname TEXT DEFAULT '';",
|
||||
params![],
|
||||
)?;
|
||||
dbversion = 2;
|
||||
sql.set_raw_config_int(context, "dbversion", 2)?;
|
||||
sql.set_config_int(context, "dbversion", 2)?;
|
||||
}
|
||||
if dbversion < 7 {
|
||||
info!(context, "[migration] v7");
|
||||
sql.execute(
|
||||
"CREATE TABLE keypairs (\
|
||||
id INTEGER PRIMARY KEY, \
|
||||
@@ -529,10 +513,9 @@ fn open(
|
||||
params![],
|
||||
)?;
|
||||
dbversion = 7;
|
||||
sql.set_raw_config_int(context, "dbversion", 7)?;
|
||||
sql.set_config_int(context, "dbversion", 7)?;
|
||||
}
|
||||
if dbversion < 10 {
|
||||
info!(context, "[migration] v10");
|
||||
sql.execute(
|
||||
"CREATE TABLE acpeerstates (\
|
||||
id INTEGER PRIMARY KEY, \
|
||||
@@ -548,10 +531,9 @@ fn open(
|
||||
params![],
|
||||
)?;
|
||||
dbversion = 10;
|
||||
sql.set_raw_config_int(context, "dbversion", 10)?;
|
||||
sql.set_config_int(context, "dbversion", 10)?;
|
||||
}
|
||||
if dbversion < 12 {
|
||||
info!(context, "[migration] v12");
|
||||
sql.execute(
|
||||
"CREATE TABLE msgs_mdns ( msg_id INTEGER, contact_id INTEGER);",
|
||||
params![],
|
||||
@@ -561,10 +543,9 @@ fn open(
|
||||
params![],
|
||||
)?;
|
||||
dbversion = 12;
|
||||
sql.set_raw_config_int(context, "dbversion", 12)?;
|
||||
sql.set_config_int(context, "dbversion", 12)?;
|
||||
}
|
||||
if dbversion < 17 {
|
||||
info!(context, "[migration] v17");
|
||||
sql.execute(
|
||||
"ALTER TABLE chats ADD COLUMN archived INTEGER DEFAULT 0;",
|
||||
params![],
|
||||
@@ -576,20 +557,18 @@ fn open(
|
||||
)?;
|
||||
sql.execute("CREATE INDEX msgs_index5 ON msgs (starred);", params![])?;
|
||||
dbversion = 17;
|
||||
sql.set_raw_config_int(context, "dbversion", 17)?;
|
||||
sql.set_config_int(context, "dbversion", 17)?;
|
||||
}
|
||||
if dbversion < 18 {
|
||||
info!(context, "[migration] v18");
|
||||
sql.execute(
|
||||
"ALTER TABLE acpeerstates ADD COLUMN gossip_timestamp INTEGER DEFAULT 0;",
|
||||
params![],
|
||||
)?;
|
||||
sql.execute("ALTER TABLE acpeerstates ADD COLUMN gossip_key;", params![])?;
|
||||
dbversion = 18;
|
||||
sql.set_raw_config_int(context, "dbversion", 18)?;
|
||||
sql.set_config_int(context, "dbversion", 18)?;
|
||||
}
|
||||
if dbversion < 27 {
|
||||
info!(context, "[migration] v27");
|
||||
sql.execute("DELETE FROM msgs WHERE chat_id=1 OR chat_id=2;", params![])?;
|
||||
sql.execute(
|
||||
"CREATE INDEX chats_contacts_index2 ON chats_contacts (contact_id);",
|
||||
@@ -604,10 +583,9 @@ fn open(
|
||||
params![],
|
||||
)?;
|
||||
dbversion = 27;
|
||||
sql.set_raw_config_int(context, "dbversion", 27)?;
|
||||
sql.set_config_int(context, "dbversion", 27)?;
|
||||
}
|
||||
if dbversion < 34 {
|
||||
info!(context, "[migration] v34");
|
||||
sql.execute(
|
||||
"ALTER TABLE msgs ADD COLUMN hidden INTEGER DEFAULT 0;",
|
||||
params![],
|
||||
@@ -634,10 +612,9 @@ fn open(
|
||||
)?;
|
||||
recalc_fingerprints = 1;
|
||||
dbversion = 34;
|
||||
sql.set_raw_config_int(context, "dbversion", 34)?;
|
||||
sql.set_config_int(context, "dbversion", 34)?;
|
||||
}
|
||||
if dbversion < 39 {
|
||||
info!(context, "[migration] v39");
|
||||
sql.execute(
|
||||
"CREATE TABLE tokens ( id INTEGER PRIMARY KEY, namespc INTEGER DEFAULT 0, foreign_id INTEGER DEFAULT 0, token TEXT DEFAULT '', timestamp INTEGER DEFAULT 0);",
|
||||
params![]
|
||||
@@ -665,37 +642,32 @@ fn open(
|
||||
)?;
|
||||
}
|
||||
dbversion = 39;
|
||||
sql.set_raw_config_int(context, "dbversion", 39)?;
|
||||
sql.set_config_int(context, "dbversion", 39)?;
|
||||
}
|
||||
if dbversion < 40 {
|
||||
info!(context, "[migration] v40");
|
||||
sql.execute(
|
||||
"ALTER TABLE jobs ADD COLUMN thread INTEGER DEFAULT 0;",
|
||||
params![],
|
||||
)?;
|
||||
dbversion = 40;
|
||||
sql.set_raw_config_int(context, "dbversion", 40)?;
|
||||
sql.set_config_int(context, "dbversion", 40)?;
|
||||
}
|
||||
if dbversion < 41 {
|
||||
info!(context, "[migration] v41");
|
||||
update_file_paths = 1;
|
||||
dbversion = 41;
|
||||
sql.set_raw_config_int(context, "dbversion", 41)?;
|
||||
sql.set_config_int(context, "dbversion", 41)?;
|
||||
}
|
||||
if dbversion < 42 {
|
||||
info!(context, "[migration] v42");
|
||||
sql.execute("UPDATE msgs SET txt='' WHERE type!=10", params![])?;
|
||||
dbversion = 42;
|
||||
sql.set_raw_config_int(context, "dbversion", 42)?;
|
||||
sql.set_config_int(context, "dbversion", 42)?;
|
||||
}
|
||||
if dbversion < 44 {
|
||||
info!(context, "[migration] v44");
|
||||
sql.execute("ALTER TABLE msgs ADD COLUMN mime_headers TEXT;", params![])?;
|
||||
dbversion = 44;
|
||||
sql.set_raw_config_int(context, "dbversion", 44)?;
|
||||
sql.set_config_int(context, "dbversion", 44)?;
|
||||
}
|
||||
if dbversion < 46 {
|
||||
info!(context, "[migration] v46");
|
||||
sql.execute(
|
||||
"ALTER TABLE msgs ADD COLUMN mime_in_reply_to TEXT;",
|
||||
params![],
|
||||
@@ -705,7 +677,7 @@ fn open(
|
||||
params![],
|
||||
)?;
|
||||
dbversion = 46;
|
||||
sql.set_raw_config_int(context, "dbversion", 46)?;
|
||||
sql.set_config_int(context, "dbversion", 46)?;
|
||||
}
|
||||
if dbversion < 47 {
|
||||
info!(context, "[migration] v47");
|
||||
@@ -714,7 +686,7 @@ fn open(
|
||||
params![],
|
||||
)?;
|
||||
dbversion = 47;
|
||||
sql.set_raw_config_int(context, "dbversion", 47)?;
|
||||
sql.set_config_int(context, "dbversion", 47)?;
|
||||
}
|
||||
if dbversion < 48 {
|
||||
info!(context, "[migration] v48");
|
||||
@@ -724,7 +696,7 @@ fn open(
|
||||
)?;
|
||||
|
||||
dbversion = 48;
|
||||
sql.set_raw_config_int(context, "dbversion", 48)?;
|
||||
sql.set_config_int(context, "dbversion", 48)?;
|
||||
}
|
||||
if dbversion < 49 {
|
||||
info!(context, "[migration] v49");
|
||||
@@ -733,15 +705,15 @@ fn open(
|
||||
params![],
|
||||
)?;
|
||||
dbversion = 49;
|
||||
sql.set_raw_config_int(context, "dbversion", 49)?;
|
||||
sql.set_config_int(context, "dbversion", 49)?;
|
||||
}
|
||||
if dbversion < 50 {
|
||||
info!(context, "[migration] v50");
|
||||
if 0 != exists_before_update {
|
||||
sql.set_raw_config_int(context, "show_emails", 2)?;
|
||||
sql.set_config_int(context, "show_emails", 2)?;
|
||||
}
|
||||
dbversion = 50;
|
||||
sql.set_raw_config_int(context, "dbversion", 50)?;
|
||||
sql.set_config_int(context, "dbversion", 50)?;
|
||||
}
|
||||
if dbversion < 53 {
|
||||
info!(context, "[migration] v53");
|
||||
@@ -774,7 +746,7 @@ fn open(
|
||||
params![],
|
||||
)?;
|
||||
dbversion = 53;
|
||||
sql.set_raw_config_int(context, "dbversion", 53)?;
|
||||
sql.set_config_int(context, "dbversion", 53)?;
|
||||
}
|
||||
if dbversion < 54 {
|
||||
info!(context, "[migration] v54");
|
||||
@@ -784,20 +756,18 @@ fn open(
|
||||
)?;
|
||||
sql.execute("CREATE INDEX msgs_index6 ON msgs (location_id);", params![])?;
|
||||
dbversion = 54;
|
||||
sql.set_raw_config_int(context, "dbversion", 54)?;
|
||||
sql.set_config_int(context, "dbversion", 54)?;
|
||||
}
|
||||
if dbversion < 55 {
|
||||
info!(context, "[migration] v55");
|
||||
sql.execute(
|
||||
"ALTER TABLE locations ADD COLUMN independent INTEGER DEFAULT 0;",
|
||||
params![],
|
||||
)?;
|
||||
|
||||
sql.set_raw_config_int(context, "dbversion", 55)?;
|
||||
sql.set_config_int(context, "dbversion", 55)?;
|
||||
}
|
||||
|
||||
if 0 != recalc_fingerprints {
|
||||
info!(context, "[migration] recalc fingerprints");
|
||||
sql.query_map(
|
||||
"SELECT addr FROM acpeerstates;",
|
||||
params![],
|
||||
@@ -807,7 +777,7 @@ fn open(
|
||||
if let Some(ref mut peerstate) = Peerstate::from_addr(context, sql, &addr?)
|
||||
{
|
||||
peerstate.recalc_fingerprint();
|
||||
peerstate.save_to_db(sql, false)?;
|
||||
peerstate.save_to_db(sql, false).unwrap();
|
||||
}
|
||||
}
|
||||
Ok(())
|
||||
@@ -818,9 +788,11 @@ fn open(
|
||||
// versions before 2018-08 save the absolute paths in the database files at "param.f=";
|
||||
// for newer versions, we copy files always to the blob directory and store relative paths.
|
||||
// this snippet converts older databases and can be removed after some time.
|
||||
info!(context, "[migration] update file paths");
|
||||
|
||||
info!(context, "[open] update file paths");
|
||||
|
||||
let repl_from = sql
|
||||
.get_raw_config(context, "backup_for")
|
||||
.get_config(context, "backup_for")
|
||||
.unwrap_or_else(|| context.get_blobdir().to_string_lossy().into());
|
||||
|
||||
let repl_from = dc_ensure_no_slash_safe(&repl_from);
|
||||
@@ -840,7 +812,7 @@ fn open(
|
||||
NO_PARAMS,
|
||||
)?;
|
||||
|
||||
sql.set_raw_config(context, "backup_for", None)?;
|
||||
sql.set_config(context, "backup_for", None)?;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1161,8 +1133,12 @@ mod test {
|
||||
maybe_add_file(&mut files, "$BLOBDIR/world.txt");
|
||||
maybe_add_file(&mut files, "world2.txt");
|
||||
|
||||
assert!(is_file_in_use(&files, None, "hello"));
|
||||
assert!(!is_file_in_use(&files, Some(".txt"), "hello"));
|
||||
assert!(is_file_in_use(&files, Some("-suffix"), "world.txt-suffix"));
|
||||
assert!(is_file_in_use(&mut files, None, "hello"));
|
||||
assert!(!is_file_in_use(&mut files, Some(".txt"), "hello"));
|
||||
assert!(is_file_in_use(
|
||||
&mut files,
|
||||
Some("-suffix"),
|
||||
"world.txt-suffix"
|
||||
));
|
||||
}
|
||||
}
|
||||
|
||||
98
src/stock.rs
98
src/stock.rs
@@ -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
|
||||
///
|
||||
@@ -108,8 +109,6 @@ pub enum StockMessage {
|
||||
MsgLocationDisabled = 65,
|
||||
#[strum(props(fallback = "Location"))]
|
||||
Location = 66,
|
||||
#[strum(props(fallback = "Sticker"))]
|
||||
Sticker = 67,
|
||||
}
|
||||
|
||||
impl StockMessage {
|
||||
@@ -117,52 +116,24 @@ impl StockMessage {
|
||||
///
|
||||
/// These could be used in logging calls, so no logging here.
|
||||
fn fallback(&self) -> &'static str {
|
||||
self.get_str("fallback").unwrap_or_default()
|
||||
self.get_str("fallback").unwrap()
|
||||
}
|
||||
}
|
||||
|
||||
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(ptr);
|
||||
unsafe { libc::free(ptr as *mut libc::c_void) };
|
||||
Cow::Owned(ret)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -266,6 +237,7 @@ mod tests {
|
||||
use crate::test_utils::*;
|
||||
|
||||
use crate::constants::DC_CONTACT_ID_SELF;
|
||||
use libc::uintptr_t;
|
||||
|
||||
use num_traits::ToPrimitive;
|
||||
|
||||
@@ -280,34 +252,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();
|
||||
@@ -383,7 +349,9 @@ mod tests {
|
||||
let contact_id = {
|
||||
Contact::create(&t.ctx, "Alice", "alice@example.com")
|
||||
.expect("Failed to create contact Alice");
|
||||
Contact::create(&t.ctx, "Bob", "bob@example.com").expect("failed to create bob")
|
||||
let id =
|
||||
Contact::create(&t.ctx, "Bob", "bob@example.com").expect("failed to create bob");
|
||||
id
|
||||
};
|
||||
assert_eq!(
|
||||
t.ctx.stock_system_msg(
|
||||
|
||||
@@ -14,27 +14,22 @@ 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:
|
||||
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))
|
||||
for fn, unsafe, free, gotoblocks, chars in reversed(sorted(filestats, key=lambda x: sum(x[1:]))):
|
||||
print("{0: <25} unsafe: {1: >3} free: {2: >3} ok_to_cont: {3: >3} chars: {4: >3}".format(str(fn), unsafe, free, gotoblocks, chars))
|
||||
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)
|
||||
|
||||
340
src/wrapmime.rs
340
src/wrapmime.rs
@@ -1,14 +1,9 @@
|
||||
use std::collections::HashSet;
|
||||
use std::ffi::CString;
|
||||
use std::ptr;
|
||||
|
||||
use crate::contact::addr_normalize;
|
||||
use crate::dc_strencode::*;
|
||||
use crate::dc_tools::*;
|
||||
use crate::error::Error;
|
||||
use mmime::clist::*;
|
||||
// use mmime::display::*;
|
||||
use mmime::mailimf::mailimf_msg_id_parse;
|
||||
use mmime::mailimf::types::*;
|
||||
use mmime::mailimf::types_helper::*;
|
||||
use mmime::mailmime::content::*;
|
||||
@@ -16,7 +11,6 @@ use mmime::mailmime::disposition::*;
|
||||
use mmime::mailmime::types::*;
|
||||
use mmime::mailmime::types_helper::*;
|
||||
use mmime::mailmime::*;
|
||||
use mmime::mmapstring::*;
|
||||
use mmime::other::*;
|
||||
|
||||
#[macro_export]
|
||||
@@ -42,30 +36,13 @@ pub fn get_ct_subtype(mime: *mut Mailmime) -> Option<String> {
|
||||
let ct: *mut mailmime_content = (*mime).mm_content_type;
|
||||
|
||||
if !ct.is_null() && !(*ct).ct_subtype.is_null() {
|
||||
Some(to_string_lossy((*ct).ct_subtype))
|
||||
Some(to_string((*ct).ct_subtype))
|
||||
} else {
|
||||
None
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub fn parse_message_id(message_id: &str) -> Result<String, Error> {
|
||||
let mut dummy = 0;
|
||||
let c_message_id = CString::new(message_id).unwrap_or_default();
|
||||
let c_ptr = c_message_id.as_ptr();
|
||||
let mut rfc724_mid_c = std::ptr::null_mut();
|
||||
if unsafe { mailimf_msg_id_parse(c_ptr, libc::strlen(c_ptr), &mut dummy, &mut rfc724_mid_c) }
|
||||
== MAIL_NO_ERROR as libc::c_int
|
||||
&& !rfc724_mid_c.is_null()
|
||||
{
|
||||
let res = to_string_lossy(rfc724_mid_c);
|
||||
unsafe { libc::free(rfc724_mid_c.cast()) };
|
||||
Ok(res)
|
||||
} else {
|
||||
bail!("could not parse message_id: {}", message_id);
|
||||
}
|
||||
}
|
||||
|
||||
pub fn get_autocrypt_mime(
|
||||
mime_undetermined: *mut Mailmime,
|
||||
) -> Result<(*mut Mailmime, *mut Mailmime), Error> {
|
||||
@@ -111,192 +88,6 @@ pub fn has_decryptable_data(mime_data: *mut mailmime_data) -> bool {
|
||||
}
|
||||
}
|
||||
|
||||
pub fn get_field_from(imffields: *mut mailimf_fields) -> Result<String, Error> {
|
||||
let field = mailimf_find_field(imffields, MAILIMF_FIELD_FROM as libc::c_int);
|
||||
if !field.is_null() && unsafe { !(*field).fld_data.fld_from.is_null() } {
|
||||
let mb_list = unsafe { (*(*field).fld_data.fld_from).frm_mb_list };
|
||||
if let Some(addr) = mailimf_find_first_addr(mb_list) {
|
||||
return Ok(addr);
|
||||
}
|
||||
}
|
||||
bail!("not From field found");
|
||||
}
|
||||
|
||||
pub fn get_field_date(imffields: *mut mailimf_fields) -> Result<i64, Error> {
|
||||
let field = mailimf_find_field(imffields, MAILIMF_FIELD_ORIG_DATE as libc::c_int);
|
||||
let mut message_time = 0;
|
||||
|
||||
if !field.is_null() && unsafe { !(*field).fld_data.fld_orig_date.is_null() } {
|
||||
let orig_date = unsafe { (*field).fld_data.fld_orig_date };
|
||||
|
||||
if !orig_date.is_null() {
|
||||
let dt = unsafe { (*orig_date).dt_date_time };
|
||||
message_time = dc_timestamp_from_date(dt);
|
||||
if message_time != 0 && message_time > time() {
|
||||
message_time = time()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Ok(message_time)
|
||||
}
|
||||
|
||||
fn mailimf_get_recipients_add_addr(recipients: &mut HashSet<String>, mb: *mut mailimf_mailbox) {
|
||||
if !mb.is_null() {
|
||||
let addr_norm = addr_normalize(as_str(unsafe { (*mb).mb_addr_spec }));
|
||||
recipients.insert(addr_norm.into());
|
||||
}
|
||||
}
|
||||
|
||||
/*the result is a pointer to mime, must not be freed*/
|
||||
pub fn mailimf_find_field(
|
||||
header: *mut mailimf_fields,
|
||||
wanted_fld_type: libc::c_int,
|
||||
) -> *mut mailimf_field {
|
||||
if header.is_null() {
|
||||
return ptr::null_mut();
|
||||
}
|
||||
|
||||
let header = unsafe { (*header) };
|
||||
if header.fld_list.is_null() {
|
||||
return ptr::null_mut();
|
||||
}
|
||||
|
||||
for cur in unsafe { &(*header.fld_list) } {
|
||||
let field = cur as *mut mailimf_field;
|
||||
if !field.is_null() {
|
||||
if unsafe { (*field).fld_type } == wanted_fld_type {
|
||||
return field;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
ptr::null_mut()
|
||||
}
|
||||
|
||||
/*the result is a pointer to mime, must not be freed*/
|
||||
pub fn mailmime_find_mailimf_fields(mime: *mut Mailmime) -> *mut mailimf_fields {
|
||||
if mime.is_null() {
|
||||
return ptr::null_mut();
|
||||
}
|
||||
|
||||
match unsafe { (*mime).mm_type as _ } {
|
||||
MAILMIME_MULTIPLE => {
|
||||
for cur_data in unsafe { (*(*mime).mm_data.mm_multipart.mm_mp_list).into_iter() } {
|
||||
let header = mailmime_find_mailimf_fields(cur_data as *mut _);
|
||||
if !header.is_null() {
|
||||
return header;
|
||||
}
|
||||
}
|
||||
}
|
||||
MAILMIME_MESSAGE => return unsafe { (*mime).mm_data.mm_message.mm_fields },
|
||||
_ => {}
|
||||
}
|
||||
|
||||
ptr::null_mut()
|
||||
}
|
||||
|
||||
pub unsafe fn mailimf_find_optional_field(
|
||||
header: *mut mailimf_fields,
|
||||
wanted_fld_name: *const libc::c_char,
|
||||
) -> *mut mailimf_optional_field {
|
||||
if header.is_null() || (*header).fld_list.is_null() {
|
||||
return ptr::null_mut();
|
||||
}
|
||||
for cur_data in (*(*header).fld_list).into_iter() {
|
||||
let field: *mut mailimf_field = cur_data as *mut _;
|
||||
|
||||
if (*field).fld_type == MAILIMF_FIELD_OPTIONAL_FIELD as libc::c_int {
|
||||
let optional_field: *mut mailimf_optional_field = (*field).fld_data.fld_optional_field;
|
||||
if !optional_field.is_null()
|
||||
&& !(*optional_field).fld_name.is_null()
|
||||
&& !(*optional_field).fld_value.is_null()
|
||||
&& strcasecmp((*optional_field).fld_name, wanted_fld_name) == 0i32
|
||||
{
|
||||
return optional_field;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
ptr::null_mut()
|
||||
}
|
||||
|
||||
pub fn mailimf_get_recipients(imffields: *mut mailimf_fields) -> HashSet<String> {
|
||||
/* returned addresses are normalized. */
|
||||
let mut recipients: HashSet<String> = Default::default();
|
||||
|
||||
for cur in unsafe { (*(*imffields).fld_list).into_iter() } {
|
||||
let fld = cur as *mut mailimf_field;
|
||||
|
||||
let fld_to: *mut mailimf_to;
|
||||
let fld_cc: *mut mailimf_cc;
|
||||
|
||||
let mut addr_list: *mut mailimf_address_list = ptr::null_mut();
|
||||
if fld.is_null() {
|
||||
continue;
|
||||
}
|
||||
|
||||
let fld = unsafe { *fld };
|
||||
|
||||
// TODO match on enums /rtn
|
||||
match fld.fld_type {
|
||||
13 => {
|
||||
fld_to = unsafe { fld.fld_data.fld_to };
|
||||
if !fld_to.is_null() {
|
||||
addr_list = unsafe { (*fld_to).to_addr_list };
|
||||
}
|
||||
}
|
||||
14 => {
|
||||
fld_cc = unsafe { fld.fld_data.fld_cc };
|
||||
if !fld_cc.is_null() {
|
||||
addr_list = unsafe { (*fld_cc).cc_addr_list };
|
||||
}
|
||||
}
|
||||
_ => {}
|
||||
}
|
||||
|
||||
if !addr_list.is_null() {
|
||||
for cur2 in unsafe { &(*(*addr_list).ad_list) } {
|
||||
let adr = cur2 as *mut mailimf_address;
|
||||
|
||||
if adr.is_null() {
|
||||
continue;
|
||||
}
|
||||
let adr = unsafe { *adr };
|
||||
|
||||
if adr.ad_type == MAILIMF_ADDRESS_MAILBOX as libc::c_int {
|
||||
mailimf_get_recipients_add_addr(&mut recipients, unsafe {
|
||||
adr.ad_data.ad_mailbox
|
||||
});
|
||||
} else if adr.ad_type == MAILIMF_ADDRESS_GROUP as libc::c_int {
|
||||
let group = unsafe { adr.ad_data.ad_group };
|
||||
if !group.is_null() && unsafe { !(*group).grp_mb_list.is_null() } {
|
||||
for cur3 in unsafe { &(*(*(*group).grp_mb_list).mb_list) } {
|
||||
mailimf_get_recipients_add_addr(
|
||||
&mut recipients,
|
||||
cur3 as *mut mailimf_mailbox,
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
recipients
|
||||
}
|
||||
|
||||
pub fn mailmime_transfer_decode(mime: *mut Mailmime) -> Result<Vec<u8>, Error> {
|
||||
ensure!(!mime.is_null(), "invalid inputs");
|
||||
|
||||
let mime_transfer_encoding =
|
||||
get_mime_transfer_encoding(mime).unwrap_or(MAILMIME_MECHANISM_BINARY as i32);
|
||||
|
||||
let mime_data = unsafe { (*mime).mm_data.mm_single };
|
||||
|
||||
decode_dt_data(mime_data, mime_transfer_encoding)
|
||||
}
|
||||
|
||||
pub fn get_mime_transfer_encoding(mime: *mut Mailmime) -> Option<libc::c_int> {
|
||||
unsafe {
|
||||
let mm_mime_fields = (*mime).mm_mime_fields;
|
||||
@@ -317,77 +108,48 @@ pub fn get_mime_transfer_encoding(mime: *mut Mailmime) -> Option<libc::c_int> {
|
||||
pub fn decode_dt_data(
|
||||
mime_data: *mut mailmime_data,
|
||||
mime_transfer_encoding: libc::c_int,
|
||||
) -> Result<Vec<u8>, Error> {
|
||||
) -> Result<(*mut libc::c_char, libc::size_t), Error> {
|
||||
// Decode data according to mime_transfer_encoding
|
||||
// returns Ok with a (decoded_data,decoded_data_bytes) pointer
|
||||
// where the caller must make sure to free it.
|
||||
// It may return Ok(ptr::null_mut(), 0)
|
||||
|
||||
let mut transfer_decoding_buffer: *mut libc::c_char = ptr::null_mut();
|
||||
let decoded_data: *mut libc::c_char;
|
||||
let mut decoded_data_bytes: libc::size_t = 0;
|
||||
if mime_transfer_encoding == MAILMIME_MECHANISM_7BIT as libc::c_int
|
||||
|| mime_transfer_encoding == MAILMIME_MECHANISM_8BIT as libc::c_int
|
||||
|| mime_transfer_encoding == MAILMIME_MECHANISM_BINARY as libc::c_int
|
||||
{
|
||||
let decoded_data = unsafe { (*mime_data).dt_data.dt_text.dt_data };
|
||||
let decoded_data_bytes = unsafe { (*mime_data).dt_data.dt_text.dt_length };
|
||||
|
||||
if decoded_data.is_null() || decoded_data_bytes == 0 {
|
||||
bail!("No data to decode found");
|
||||
} else {
|
||||
let result = unsafe {
|
||||
std::slice::from_raw_parts(decoded_data as *const u8, decoded_data_bytes)
|
||||
};
|
||||
return Ok(result.to_vec());
|
||||
unsafe {
|
||||
decoded_data = (*mime_data).dt_data.dt_text.dt_data as *mut _;
|
||||
decoded_data_bytes = (*mime_data).dt_data.dt_text.dt_length;
|
||||
}
|
||||
ensure!(
|
||||
!decoded_data.is_null() && decoded_data_bytes > 0,
|
||||
"could not decode mime message"
|
||||
);
|
||||
} else {
|
||||
let mut current_index: libc::size_t = 0;
|
||||
unsafe {
|
||||
let r = mailmime_part_parse(
|
||||
(*mime_data).dt_data.dt_text.dt_data,
|
||||
(*mime_data).dt_data.dt_text.dt_length,
|
||||
&mut current_index,
|
||||
mime_transfer_encoding,
|
||||
&mut transfer_decoding_buffer,
|
||||
&mut decoded_data_bytes,
|
||||
);
|
||||
if r != MAILIMF_NO_ERROR as libc::c_int
|
||||
|| transfer_decoding_buffer.is_null()
|
||||
|| decoded_data_bytes <= 0
|
||||
{
|
||||
bail!("mailmime_part_parse returned error or invalid data");
|
||||
}
|
||||
decoded_data = transfer_decoding_buffer;
|
||||
}
|
||||
}
|
||||
// unsafe { display_mime_data(mime_data) };
|
||||
|
||||
let mut current_index = 0;
|
||||
let mut transfer_decoding_buffer = ptr::null_mut();
|
||||
let mut decoded_data_bytes = 0;
|
||||
|
||||
let r = unsafe {
|
||||
mailmime_part_parse(
|
||||
(*mime_data).dt_data.dt_text.dt_data,
|
||||
(*mime_data).dt_data.dt_text.dt_length,
|
||||
&mut current_index,
|
||||
mime_transfer_encoding,
|
||||
&mut transfer_decoding_buffer,
|
||||
&mut decoded_data_bytes,
|
||||
)
|
||||
};
|
||||
|
||||
if r == MAILIMF_NO_ERROR as libc::c_int
|
||||
&& !transfer_decoding_buffer.is_null()
|
||||
&& decoded_data_bytes > 0
|
||||
{
|
||||
let result = unsafe {
|
||||
std::slice::from_raw_parts(transfer_decoding_buffer as *const u8, decoded_data_bytes)
|
||||
}
|
||||
.to_vec();
|
||||
// we return a fresh vec and transfer_decoding_buffer is not used or passed anywhere
|
||||
// so it's safe to free it right away, as mailman_part_parse has
|
||||
// allocated it fresh.
|
||||
unsafe { mmap_string_unref(transfer_decoding_buffer) };
|
||||
|
||||
return Ok(result);
|
||||
}
|
||||
|
||||
Err(format_err!("Failed to to decode"))
|
||||
}
|
||||
|
||||
pub fn mailimf_find_first_addr(mb_list: *const mailimf_mailbox_list) -> Option<String> {
|
||||
if mb_list.is_null() {
|
||||
return None;
|
||||
}
|
||||
|
||||
for cur in unsafe { (*(*mb_list).mb_list).into_iter() } {
|
||||
let mb = cur as *mut mailimf_mailbox;
|
||||
if !mb.is_null() && !unsafe { (*mb).mb_addr_spec.is_null() } {
|
||||
let addr = unsafe { as_str((*mb).mb_addr_spec) };
|
||||
return Some(addr_normalize(addr).to_string());
|
||||
}
|
||||
}
|
||||
|
||||
None
|
||||
Ok((decoded_data, decoded_data_bytes))
|
||||
}
|
||||
|
||||
/**************************************
|
||||
@@ -448,8 +210,8 @@ pub fn append_ct_param(
|
||||
value: &str,
|
||||
) -> Result<(), Error> {
|
||||
unsafe {
|
||||
let name_c = CString::new(name).unwrap_or_default();
|
||||
let value_c = CString::new(value).unwrap_or_default();
|
||||
let name_c = CString::new(name).unwrap();
|
||||
let value_c = CString::new(value).unwrap();
|
||||
|
||||
clist_append!(
|
||||
(*content).ct_parameters,
|
||||
@@ -463,7 +225,7 @@ pub fn append_ct_param(
|
||||
}
|
||||
|
||||
pub fn new_content_type(content_type: &str) -> Result<*mut mailmime_content, Error> {
|
||||
let ct = CString::new(content_type).unwrap_or_default();
|
||||
let ct = CString::new(content_type).unwrap();
|
||||
let content: *mut mailmime_content;
|
||||
// mailmime_content_new_with_str only parses but does not retain/own ct
|
||||
unsafe {
|
||||
@@ -499,24 +261,6 @@ pub fn content_type_needs_encoding(content: *const mailmime_content) -> bool {
|
||||
}
|
||||
}
|
||||
|
||||
pub fn new_mailbox_list(displayname: &str, addr: &str) -> *mut mailimf_mailbox_list {
|
||||
let mbox: *mut mailimf_mailbox_list = unsafe { mailimf_mailbox_list_new_empty() };
|
||||
unsafe {
|
||||
mailimf_mailbox_list_add(
|
||||
mbox,
|
||||
mailimf_mailbox_new(
|
||||
if !displayname.is_empty() {
|
||||
dc_encode_header_words(&displayname).strdup()
|
||||
} else {
|
||||
ptr::null_mut()
|
||||
},
|
||||
addr.strdup(),
|
||||
),
|
||||
);
|
||||
}
|
||||
mbox
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
@@ -536,16 +280,4 @@ mod tests {
|
||||
new_content_type("application/pgp-encrypted").unwrap()
|
||||
));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_parse_message_id() {
|
||||
assert_eq!(
|
||||
parse_message_id("Mr.PRUe8HJBoaO.3whNvLCMFU0@testrun.org").unwrap(),
|
||||
"Mr.PRUe8HJBoaO.3whNvLCMFU0@testrun.org"
|
||||
);
|
||||
assert_eq!(
|
||||
parse_message_id("<Mr.PRUe8HJBoaO.3whNvLCMFU0@testrun.org>").unwrap(),
|
||||
"Mr.PRUe8HJBoaO.3whNvLCMFU0@testrun.org"
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
155
tests/stress.rs
155
tests/stress.rs
@@ -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);
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user