Compare commits

..

5 Commits

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

View File

@@ -15,7 +15,7 @@ restore-workspace: &restore-workspace
restore-cache: &restore-cache
restore_cache:
keys:
- cargo-v3-{{ checksum "rust-toolchain" }}-{{ checksum "Cargo.toml" }}-{{ checksum "Cargo.lock" }}-{{ arch }}
- cargo-v2-{{ checksum "rust-toolchain" }}-{{ checksum "Cargo.toml" }}-{{ checksum "Cargo.lock" }}-{{ arch }}
- repo-source-{{ .Branch }}-{{ .Revision }}
commands:
@@ -44,7 +44,7 @@ jobs:
command: cargo generate-lockfile
- restore_cache:
keys:
- cargo-v3-{{ checksum "rust-toolchain" }}-{{ checksum "Cargo.toml" }}-{{ checksum "Cargo.lock" }}-{{ arch }}
- cargo-v2-{{ checksum "rust-toolchain" }}-{{ checksum "Cargo.toml" }}-{{ checksum "Cargo.lock" }}-{{ arch }}
- run: rustup install $(cat rust-toolchain)
- run: rustup default $(cat rust-toolchain)
- run: rustup component add --toolchain $(cat rust-toolchain) rustfmt
@@ -60,7 +60,7 @@ jobs:
paths:
- crate
- save_cache:
key: cargo-v3-{{ checksum "rust-toolchain" }}-{{ checksum "Cargo.toml" }}-{{ checksum "Cargo.lock" }}-{{ arch }}
key: cargo-v2-{{ checksum "rust-toolchain" }}-{{ checksum "Cargo.toml" }}-{{ checksum "Cargo.lock" }}-{{ arch }}
paths:
- "~/.cargo"
- "~/.rustup"
@@ -121,7 +121,7 @@ jobs:
steps:
- checkout
- run: bash ci_scripts/run-doxygen.sh
- run: mkdir -p workspace/c-docs
- run: mkdir -p workspace/c-docs
- run: cp -av deltachat-ffi/{html,xml} workspace/c-docs/
- persist_to_workspace:
root: workspace
@@ -189,7 +189,7 @@ workflows:
- upload_docs_wheels:
requires:
- build_test_docs_wheel
- build_doxygen
- build_doxygen
- rustfmt:
requires:
- cargo_fetch

View File

@@ -1,30 +0,0 @@
name: CI
on:
pull_request:
push:
env:
RUSTFLAGS: -Dwarnings
jobs:
build:
name: 3.7 python tests against core
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@master
- uses: actions-rs/toolchain@v1
with:
profile: minimal
toolchain: nightly
override: true
components: rustfmt
- name: Setup python
uses: actions/setup-python@v1
with:
python-version: 3.x
architecture: x64
- run: bash ci_scripts/run-python.sh

View File

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

1343
Cargo.lock generated

File diff suppressed because it is too large Load Diff

View File

@@ -1,7 +1,7 @@
[package]
name = "deltachat"
version = "1.0.0-beta.7"
authors = ["Delta Chat Developers (ML) <delta@codespeak.net>"]
version = "1.0.0-alpha.5"
authors = ["dignifiedquire <dignifiedquire@gmail.com>"]
edition = "2018"
license = "MPL"
@@ -10,18 +10,17 @@ 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 = "0.2.3"
hex = "0.3.2"
sha2 = "0.8.0"
rand = "0.6.5"
smallvec = "0.6.9"
reqwest = { version = "0.9.15", default-features = false, features = ["rustls-tls"] }
reqwest = "0.9.15"
num-derive = "0.2.5"
num-traits = "0.2.6"
lettre = { git = "https://github.com/deltachat/lettre", branch = "feat/rustls" }
async-imap = "0.1"
async-tls = "0.6"
async-std = { version = "1.0", features = ["unstable"] }
native-tls = "0.2.3"
lettre = { git = "https://github.com/deltachat/lettre" }
imap = { git = "https://github.com/jonhoo/rust-imap", rev = "281d2eb8ab50dc656ceff2ae749ca5045f334e15" }
base64 = "0.10"
charset = "0.1"
percent-encoding = "2.0"
@@ -49,11 +48,6 @@ escaper = "0.1.0"
bitflags = "1.1.0"
jetscii = "0.4.4"
debug_stub_derive = "0.3.0"
sanitize-filename = "0.2.1"
stop-token = { version = "0.1.1", features = ["unstable"] }
rustls = "0.16.0"
webpki-roots = "0.18.0"
webpki = "0.21.0"
[dev-dependencies]
tempfile = "3.0"
@@ -79,6 +73,6 @@ path = "examples/repl/main.rs"
[features]
default = ["nightly", "ringbuf"]
vendored = []
vendored = ["native-tls/vendored", "reqwest/default-tls-vendored"]
nightly = ["pgp/nightly"]
ringbuf = ["pgp/ringbuf"]

6
Xargo.toml Normal file
View File

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

Binary file not shown.

Before

Width:  |  Height:  |  Size: 3.6 KiB

View File

@@ -5,6 +5,16 @@ RUN echo /usr/local/lib64 > /etc/ld.so.conf.d/local.conf && \
echo /usr/local/lib >> /etc/ld.so.conf.d/local.conf
ENV PKG_CONFIG_PATH /usr/local/lib64/pkgconfig:/usr/local/lib/pkgconfig
ENV PIP_DISABLE_PIP_VERSION_CHECK 1
# Install python tools (auditwheels,tox, ...)
ADD deps/build_python.sh /builder/build_python.sh
RUN mkdir tmp1 && cd tmp1 && bash /builder/build_python.sh && cd .. && rm -r tmp1
# Install Rust nightly
ADD deps/build_rust.sh /builder/build_rust.sh
RUN mkdir tmp1 && cd tmp1 && bash /builder/build_rust.sh && cd .. && rm -r tmp1
# Install a recent Perl, needed to install OpenSSL
ADD deps/build_perl.sh /builder/build_perl.sh
RUN mkdir tmp1 && cd tmp1 && bash /builder/build_perl.sh && cd .. && rm -r tmp1
@@ -13,12 +23,3 @@ RUN mkdir tmp1 && cd tmp1 && bash /builder/build_perl.sh && cd .. && rm -r tmp1
ADD deps/build_openssl.sh /builder/build_openssl.sh
RUN mkdir tmp1 && cd tmp1 && bash /builder/build_openssl.sh && cd .. && rm -r tmp1
ENV PIP_DISABLE_PIP_VERSION_CHECK 1
# Install python tools (auditwheels,tox, ...)
ADD deps/build_python.sh /builder/build_python.sh
RUN mkdir tmp1 && cd tmp1 && bash /builder/build_python.sh && cd .. && rm -r tmp1
# Install Rust nightly
ADD deps/build_rust.sh /builder/build_rust.sh
RUN mkdir tmp1 && cd tmp1 && bash /builder/build_rust.sh && cd .. && rm -r tmp1

View File

@@ -1,11 +1,11 @@
#!/bin/bash
PERL_VERSION=5.30.0
# PERL_SHA256=7e929f64d4cb0e9d1159d4a59fc89394e27fa1f7004d0836ca0d514685406ea8
PERL_VERSION=5.28.0
PERL_SHA256=7e929f64d4cb0e9d1159d4a59fc89394e27fa1f7004d0836ca0d514685406ea8
curl -O https://www.cpan.org/src/5.0/perl-${PERL_VERSION}.tar.gz
# echo "${PERL_SHA256} perl-${PERL_VERSION}.tar.gz" | sha256sum -c -
tar -xzf perl-${PERL_VERSION}.tar.gz
cd perl-${PERL_VERSION}
echo "${PERL_SHA256} perl-${PERL_VERSION}.tar.gz" | sha256sum -c -
tar xzf perl-${PERL_VERSION}.tar.gz
cd perl-${PERL_VERSION}
./Configure -de
make

View File

@@ -1,8 +1,11 @@
#!/bin/bash
set -e -x
set -e -x
# Install Rust
curl https://sh.rustup.rs -sSf | sh -s -- --default-toolchain nightly-2019-09-12 -y
# Install Rust
curl https://sh.rustup.rs -sSf | sh -s -- --default-toolchain nightly-2019-07-10 -y
export PATH=/root/.cargo/bin:$PATH
rustc --version
# remove some 300-400 MB that we don't need for automated builds
rm -rf /root/.rustup/toolchains/nightly-2019-07-10-x86_64-unknown-linux-gnu/share/

View File

@@ -13,6 +13,7 @@ export TOXWORKDIR=.docker-tox
export PATH=/root/.cargo/bin:$PATH
cargo build --release -p deltachat_ffi
# cargo test --all --all-features
# Statically link against libdeltachat.a.
export DCC_RS_DEV=$(pwd)
@@ -21,27 +22,36 @@ export DCC_RS_DEV=$(pwd)
# needed by tox below.
export PATH=$PATH:/opt/python/cp35-cp35m/bin
export PYTHONDONTWRITEBYTECODE=1
pushd python
# prepare a clean tox run
rm -rf tests/__pycache__
rm -rf src/deltachat/__pycache__
export PYTHONDONTWRITEBYTECODE=1
# run tox. The circle-ci project env-var-setting DCC_PY_LIVECONFIG
# allows running of "liveconfig" tests but for speed reasons
# we run them only for the highest python version we support
# we split out qr-tests run to minimize likelyness of flaky tests
# (some qr tests are pretty heavy in terms of send/received
# messages and rust's imap code likely has concurrency problems)
tox --workdir "$TOXWORKDIR" -e py37 -- --reruns 3 -k "not qr"
tox --workdir "$TOXWORKDIR" -e py37 -- --reruns 3 -k "qr"
unset DCC_PY_LIVECONFIG
#tox --workdir "$TOXWORKDIR" -p4 -e lint,py35,py36,doc
#tox --workdir "$TOXWORKDIR" -e auditwheels
pushd /bin
ln -s /opt/python/cp27-cp27m/bin/python2.7
ln -s /opt/python/cp36-cp36m/bin/python3.6
ln -s /opt/python/cp37-cp37m/bin/python3.7
popd
if [ -n "$TESTS" ]; then
pushd python
# prepare a clean tox run
rm -rf tests/__pycache__
rm -rf src/deltachat/__pycache__
export PYTHONDONTWRITEBYTECODE=1
# run tox. The circle-ci project env-var-setting DCC_PY_LIVECONFIG
# allows running of "liveconfig" tests but for speed reasons
# we run them only for the highest python version we support
# we split out qr-tests run to minimize likelyness of flaky tests
# (some qr tests are pretty heavy in terms of send/received
# messages and rust's imap code likely has concurrency problems)
tox --workdir "$TOXWORKDIR" -e py37 -- -k "not qr"
tox --workdir "$TOXWORKDIR" -e py37 -- -k "qr"
unset DCC_PY_LIVECONFIG
tox --workdir "$TOXWORKDIR" -p4 -e lint,py35,py36,doc
tox --workdir "$TOXWORKDIR" -e auditwheels
popd
fi
# if [ -n "$DOCS" ]; then
# echo -----------------------
# echo generating python docs

View File

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

View File

@@ -78,7 +78,8 @@ typedef struct _dc_provider dc_provider_t;
*
* The example above uses "pthreads",
* however, you can also use anything else for thread handling.
* All deltachat-core-functions, unless stated otherwise, are thread-safe.
* NB: The deltachat-core library itself does not create any threads on its own,
* however, functions, unless stated otherwise, are thread-safe.
*
* After that you can **define and open a database.**
* The database is a normal sqlite-file and is created as needed:
@@ -134,7 +135,7 @@ typedef struct _dc_provider dc_provider_t;
*
* printf("Message %i: %s\n", i+1, text);
*
* dc_str_unref(text);
* free(text);
* dc_msg_unref(msg);
* }
* dc_array_unref(msglist);
@@ -320,7 +321,7 @@ int dc_is_open (const dc_context_t* context);
* @memberof dc_context_t
* @param context The context object as created by dc_context_new().
* @return Blob directory associated with the context object, empty string if unset or on errors. NULL is never returned.
* The returned string must be released using dc_str_unref().
* The returned string must be free()'d.
*/
char* dc_get_blobdir (const dc_context_t* context);
@@ -338,8 +339,6 @@ char* dc_get_blobdir (const dc_context_t* context);
* - `send_pw` = SMTP-password, guessed if left out
* - `send_port` = SMTP-port, guessed if left out
* - `server_flags` = IMAP-/SMTP-flags as a combination of @ref DC_LP flags, guessed if left out
* - `imap_certificate_checks` = how to check IMAP certificates, one of the @ref DC_CERTCK flags, defaults to #DC_CERTCK_AUTO (0)
* - `smtp_certificate_checks` = how to check SMTP certificates, one of the @ref DC_CERTCK flags, defaults to #DC_CERTCK_AUTO (0)
* - `displayname` = Own name to use when sending messages. MUAs are allowed to spread this way eg. using CC, defaults to empty
* - `selfstatus` = Own status to display eg. in email footers, defaults to a standard text
* - `selfavatar` = File containing avatar. Will be copied to blob directory.
@@ -398,7 +397,7 @@ 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);
@@ -408,13 +407,12 @@ char* dc_get_config (dc_context_t* context, const char*
*
* 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);
int dc_set_stock_translation(dc_context_t* context, uint32_t, const char* value);
/**
@@ -429,7 +427,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);
@@ -460,7 +458,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);
@@ -501,9 +498,15 @@ char* dc_get_oauth2_url (dc_context_t* context, const char*
* To interrupt a configuration prematurely, use dc_stop_ongoing_process();
* this is not needed if #DC_EVENT_CONFIGURE_PROGRESS reports success.
*
* If #DC_EVENT_CONFIGURE_PROGRESS reports failure,
* the core continues to use the last working configuration
* and parameters as `addr`, `mail_pw` etc. are set to that.
* On a successfull configuration,
* the core makes a copy of the parameters mentioned above:
* the original parameters as are never modified by the core.
*
* UI-implementors should keep this in mind -
* eg. if the UI wants to prefill a configure-edit-dialog with these parameters,
* the UI should reset them if the user cancels the dialog
* after a configure-attempts has failed.
* Otherwise the parameters may not reflect the current configuation.
*
* @memberof dc_context_t
* @param context The context object as created by dc_context_new().
@@ -703,7 +706,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()
@@ -1092,27 +1094,6 @@ uint32_t dc_send_text_msg (dc_context_t* context, uint32_t ch
void dc_set_draft (dc_context_t* context, uint32_t chat_id, dc_msg_t* msg);
/**
* Add a message to the device-chat.
* Device-messages usually contain update information
* and some hints that are added during the program runs, multi-device etc.
*
* Device-messages may be added from the core,
* however, with this function, this can be done from the ui as well.
* If needed, the device-chat is created before.
*
* Sends the event #DC_EVENT_MSGS_CHANGED on success.
* To check, if a given chat is a device-chat, see dc_chat_is_device_talk()
*
* @memberof dc_context_t
* @param context The context as created by dc_context_new().
* @param msg Message to be added to the device-chat.
* The message appears to the user as an incoming message.
* @return The ID of the added message.
*/
uint32_t dc_add_device_msg (dc_context_t* context, dc_msg_t* msg);
/**
* Get draft for a chat, if any.
* See dc_set_draft() for more details about drafts.
@@ -1228,7 +1209,7 @@ void dc_marknoticed_all_chats (dc_context_t* context);
* @memberof dc_context_t
* @param context The context object as returned from dc_context_new().
* @param chat_id The chat ID to get all messages with media from.
* @param msg_type Specify a message type to query here, one of the @ref DC_MSG constants.
* @param msg_type Specify a message type to query here, one of the DC_MSG_* constats.
* @param msg_type2 Alternative message type to search for. 0 to skip.
* @param msg_type3 Alternative message type to search for. 0 to skip.
* @return An array with messages from the given chat ID that have the wanted message types.
@@ -1496,7 +1477,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);
@@ -1510,7 +1491,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.
@@ -1530,16 +1511,6 @@ char* dc_get_mime_headers (dc_context_t* context, uint32_t ms
*/
void dc_delete_msgs (dc_context_t* context, const uint32_t* msg_ids, int msg_cnt);
/**
* Empty IMAP server folder: delete all messages.
*
* @memberof dc_context_t
* @param context The context object as created by dc_context_new()
* @param flags What to delete, a combination of the @ref DC_EMPTY flags
* @return None.
*/
void dc_empty_server (dc_context_t* context, uint32_t flags);
/**
* Forward messages to another chat.
@@ -1761,7 +1732,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);
@@ -1897,8 +1868,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);
@@ -1946,7 +1916,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);
@@ -2051,7 +2021,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);
@@ -2221,21 +2191,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
*
@@ -2376,7 +2331,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);
@@ -2627,22 +2582,24 @@ int dc_chat_get_type (const dc_chat_t* chat);
*
* To change the name, use dc_set_chat_name()
*
* See also: dc_chat_get_subtitle()
*
* @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);
/*
/**
* Get a subtitle for a chat. The subtitle is eg. the email-address or the
* number of group members.
*
* Deprecated function. Subtitles should be created in the ui
* where plural forms and other specials can be handled more gracefully.
* See also: dc_chat_get_name()
*
* @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);
@@ -2658,7 +2615,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);
@@ -2730,39 +2687,6 @@ int dc_chat_is_unpromoted (const dc_chat_t* chat);
int dc_chat_is_self_talk (const dc_chat_t* chat);
/**
* Check if a chat is a device-talk.
* Device-talks contain update information
* and some hints that are added during the program runs, multi-device etc.
*
* From the ui view, device-talks are not very special,
* the user can delete and forward messages, archive the chat, set notifications etc.
*
* Messages may be added from the core to the device chat,
* so the chat just pops up as usual.
* However, if needed the ui can also add messages using dc_add_device_msg()
*
* @memberof dc_chat_t
* @param chat The chat object.
* @return 1=chat is device-talk, 0=chat is no device-talk
*/
int dc_chat_is_device_talk (const dc_chat_t* chat);
/**
* Check if messages can be sent to a give chat.
* This is not true eg. for the deaddrop or for the device-talk, cmp. dc_chat_is_device_talk().
*
* Calling dc_send_msg() for these chats will fail
* and the ui may decide to hide input controls therefore.
*
* @memberof dc_chat_t
* @param chat The chat object.
* @return 1=chat is writable, 0=chat is not writable
*/
int dc_chat_can_send (const dc_chat_t* chat);
/**
* Check if a chat is verified. Verified chats contain only verified members
* and encryption is alwasy enabled. Verified chats are created using
@@ -2995,7 +2919,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);
@@ -3009,9 +2933,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);
@@ -3024,7 +2948,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);
@@ -3036,8 +2960,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);
@@ -3145,8 +3068,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);
@@ -3291,7 +3213,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);
@@ -3418,8 +3340,7 @@ void dc_msg_latefiling_mediasize (dc_msg_t* msg, int width, int hei
#define DC_CONTACT_ID_SELF 1
#define DC_CONTACT_ID_INFO 2 // centered messages as "member added", used in all chats
#define DC_CONTACT_ID_DEVICE 5 // messages "update info" in the device-chat
#define DC_CONTACT_ID_DEVICE 2
#define DC_CONTACT_ID_LAST_SPECIAL 9
@@ -3449,8 +3370,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);
@@ -3464,8 +3384,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);
@@ -3479,8 +3398,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);
@@ -3496,8 +3414,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);
@@ -3509,8 +3426,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);
@@ -3524,7 +3440,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);
@@ -3605,7 +3521,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);
@@ -3617,7 +3533,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);
@@ -3630,7 +3546,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);
@@ -3642,7 +3558,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);
@@ -3704,9 +3620,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);
@@ -3715,10 +3629,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);
@@ -3739,7 +3653,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);
@@ -3761,7 +3677,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);
@@ -3938,67 +3856,6 @@ int64_t dc_lot_get_timestamp (const dc_lot_t* lot);
#define DC_LP_IMAP_SOCKET_FLAGS (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
#define DC_LP_SMTP_SOCKET_FLAGS (DC_LP_SMTP_SOCKET_STARTTLS|DC_LP_SMTP_SOCKET_SSL|DC_LP_SMTP_SOCKET_PLAIN) // if none of these flags are set, the default is chosen
/**
* @defgroup DC_CERTCK DC_CERTCK
*
* These constants configure TLS certificate checks for IMAP and SMTP connections.
*
* These constants are set via dc_set_config()
* using keys "imap_certificate_checks" and "smtp_certificate_checks".
*
* @addtogroup DC_CERTCK
* @{
*/
/**
* Configure certificate checks automatically.
*/
#define DC_CERTCK_AUTO 0
/**
* Strictly check TLS certificates;
* require that both the certificate and hostname are valid.
*/
#define DC_CERTCK_STRICT 1
/**
* Accept invalid hostnames, but not invalid certificates.
*/
#define DC_CERTCK_ACCEPT_INVALID_HOSTNAMES 2
/**
* Accept invalid certificates, including self-signed ones
* or having incorrect hostname.
*/
#define DC_CERTCK_ACCEPT_INVALID_CERTIFICATES 3
/**
* @}
*/
/**
* @defgroup DC_EMPTY DC_EMPTY
*
* These constants configure emptying imap folders with dc_empty_server()
*
* @addtogroup DC_EMPTY
* @{
*/
/**
* Clear all mvbox messages.
*/
#define DC_EMPTY_MVBOX 0x01
/**
* Clear all INBOX messages.
*/
#define DC_EMPTY_INBOX 0x02
/**
* @}
*/
/**
@@ -4013,6 +3870,7 @@ int64_t dc_lot_get_timestamp (const dc_lot_t* lot);
* @{
*/
/**
* The library-user may write an informational string to the log.
* Passed to the callback given to dc_context_new().
@@ -4021,7 +3879,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
@@ -4032,7 +3890,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
@@ -4043,7 +3901,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
@@ -4053,7 +3911,7 @@ int64_t dc_lot_get_timestamp (const dc_lot_t* lot);
*
* @param data1 0
* @param data2 (const char*) Info string in english language.
* Must not be unref'd or modified and is valid only until the callback returns.
* Must not be free()'d or modified and is valid only until the callback returns.
* @return 0
*/
#define DC_EVENT_SMTP_MESSAGE_SENT 103
@@ -4063,7 +3921,7 @@ int64_t dc_lot_get_timestamp (const dc_lot_t* lot);
*
* @param data1 0
* @param data2 (const char*) Info string in english language.
* Must not be unref'd or modified and is valid only until the callback returns.
* Must not be free()'d or modified and is valid only until the callback returns.
* @return 0
*/
#define DC_EVENT_IMAP_MESSAGE_DELETED 104
@@ -4073,27 +3931,17 @@ int64_t dc_lot_get_timestamp (const dc_lot_t* lot);
*
* @param data1 0
* @param data2 (const char*) Info string in english language.
* Must not be unref'd or modified and is valid only until the callback returns.
* Must not be free()'d or modified and is valid only until the callback returns.
* @return 0
*/
#define DC_EVENT_IMAP_MESSAGE_MOVED 105
/**
* Emitted when an IMAP folder was emptied.
*
* @param data1 0
* @param data2 (const char*) folder name.
* Must not be unref'd or modified and is valid only until the callback returns.
* @return 0
*/
#define DC_EVENT_IMAP_FOLDER_EMPTIED 106
/**
* 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.
* Must not be free()'d or modified and is valid only until the callback returns.
* @return 0
*/
#define DC_EVENT_NEW_BLOB_FILE 150
@@ -4103,7 +3951,7 @@ int64_t dc_lot_get_timestamp (const dc_lot_t* lot);
*
* @param data1 0
* @param data2 (const char*) path name
* Must not be unref'd or modified and is valid only until the callback returns.
* Must not be free()'d or modified and is valid only until the callback returns.
* @return 0
*/
#define DC_EVENT_DELETED_BLOB_FILE 151
@@ -4116,7 +3964,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
@@ -4136,10 +3984,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
@@ -4163,7 +4010,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
@@ -4178,7 +4025,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
*/
@@ -4309,7 +4156,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
*/
@@ -4350,21 +4197,30 @@ 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
/**
* (DEPRECATED, DC_EVENT_GET_STRING is not emmitted anymore. Use
* dc_set_stock_translation() to pre-fill translations for stock
* messages.
*
*/
#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
@@ -4467,8 +4323,10 @@ void dc_array_add_id (dc_array_t*, uint32_t); // depreca
#define DC_STR_MSGLOCATIONDISABLED 65
#define DC_STR_LOCATION 66
#define DC_STR_STICKER 67
#define DC_STR_DEVICE_MESSAGES 68
#define DC_STR_COUNT 68
#define DC_STR_COUNT 67
void dc_str_unref (char*);
/*
* @}

View File

@@ -22,13 +22,9 @@ use std::sync::RwLock;
use libc::uintptr_t;
use num_traits::{FromPrimitive, ToPrimitive};
use deltachat::constants::DC_MSG_ID_LAST_SPECIAL;
use deltachat::contact::Contact;
use deltachat::context::Context;
use deltachat::dc_tools::{
as_path, dc_strdup, to_opt_string_lossy, to_string_lossy, OsStrExt, StrExt,
};
use deltachat::message::MsgId;
use deltachat::dc_tools::{as_path, as_str, dc_strdup, to_string_lossy, OsStrExt, StrExt};
use deltachat::stock::StockMessage;
use deltachat::*;
@@ -130,7 +126,6 @@ impl ContextWrapper {
| Event::SmtpMessageSent(msg)
| Event::ImapMessageDeleted(msg)
| Event::ImapMessageMoved(msg)
| Event::ImapFolderEmptied(msg)
| Event::NewBlobFile(msg)
| Event::DeletedBlobFile(msg)
| Event::Warning(msg)
@@ -144,12 +139,9 @@ impl ContextWrapper {
| Event::IncomingMsg { chat_id, msg_id }
| Event::MsgDelivered { chat_id, msg_id }
| Event::MsgFailed { chat_id, msg_id }
| Event::MsgRead { chat_id, msg_id } => ffi_cb(
self,
event_id,
chat_id as uintptr_t,
msg_id.to_u32() as uintptr_t,
),
| Event::MsgRead { chat_id, msg_id } => {
ffi_cb(self, event_id, chat_id as uintptr_t, msg_id as uintptr_t)
}
Event::ChatModified(chat_id) => ffi_cb(self, event_id, chat_id as uintptr_t, 0),
Event::ContactsChanged(id) | Event::LocationChanged(id) => {
let id = id.unwrap_or_default();
@@ -311,14 +303,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");
@@ -337,7 +326,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()),
@@ -358,7 +347,7 @@ pub unsafe extern "C" fn dc_set_stock_translation(
eprintln!("ignoring careless call to dc_set_stock_string");
return 0;
}
let msg = to_string_lossy(stock_msg);
let msg = as_str(stock_msg).to_string();
let ffi_context = &*context;
ffi_context
.with_inner(|ctx| match StockMessage::from_u32(stock_id) {
@@ -657,24 +646,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())
}
@@ -687,8 +674,7 @@ pub unsafe extern "C" fn dc_create_chat_by_msg_id(context: *mut dc_context_t, ms
let ffi_context = &*context;
ffi_context
.with_inner(|ctx| {
chat::create_by_msg_id(ctx, MsgId::new(msg_id))
.unwrap_or_log_default(ctx, "Failed to create chat")
chat::create_by_msg_id(ctx, msg_id).unwrap_or_log_default(ctx, "Failed to create chat")
})
.unwrap_or(0)
}
@@ -722,7 +708,10 @@ pub unsafe extern "C" fn dc_get_chat_id_by_contact_id(
}
let ffi_context = &*context;
ffi_context
.with_inner(|ctx| chat::get_by_contact_id(ctx, contact_id).unwrap_or(0))
.with_inner(|ctx| {
chat::get_by_contact_id(ctx, contact_id)
.unwrap_or_log_default(ctx, "Failed to get chat")
})
.unwrap_or(0)
}
@@ -743,7 +732,6 @@ pub unsafe extern "C" fn dc_prepare_msg(
chat::prepare_msg(ctx, chat_id, &mut ffi_msg.message)
.unwrap_or_log_default(ctx, "Failed to prepare message")
})
.map(|msg_id| msg_id.to_u32())
.unwrap_or(0)
}
@@ -764,7 +752,6 @@ pub unsafe extern "C" fn dc_send_msg(
chat::send_msg(ctx, chat_id, &mut ffi_msg.message)
.unwrap_or_log_default(ctx, "Failed to send message")
})
.map(|msg_id| msg_id.to_u32())
.unwrap_or(0)
}
@@ -783,7 +770,6 @@ pub unsafe extern "C" fn dc_send_text_msg(
ffi_context
.with_inner(|ctx| {
chat::send_text_msg(ctx, chat_id, text_to_send)
.map(|msg_id| msg_id.to_u32())
.unwrap_or_log_default(ctx, "Failed to send text message")
})
.unwrap_or(0)
@@ -811,23 +797,6 @@ pub unsafe extern "C" fn dc_set_draft(
.unwrap_or(())
}
#[no_mangle]
pub unsafe extern "C" fn dc_add_device_msg(context: *mut dc_context_t, msg: *mut dc_msg_t) -> u32 {
if context.is_null() || msg.is_null() {
eprintln!("ignoring careless call to dc_add_device_msg()");
return 0;
}
let ffi_context = &mut *context;
let ffi_msg = &mut *msg;
ffi_context
.with_inner(|ctx| {
chat::add_device_msg(ctx, &mut ffi_msg.message)
.unwrap_or_log_default(ctx, "Failed to add device message")
})
.map(|msg_id| msg_id.to_u32())
.unwrap_or(0)
}
#[no_mangle]
pub unsafe extern "C" fn dc_get_draft(context: *mut dc_context_t, chat_id: u32) -> *mut dc_msg_t {
if context.is_null() {
@@ -865,19 +834,9 @@ pub unsafe extern "C" fn dc_get_chat_msgs(
return ptr::null_mut();
}
let ffi_context = &*context;
let marker_flag = if marker1before <= DC_MSG_ID_LAST_SPECIAL {
None
} else {
Some(MsgId::new(marker1before))
};
ffi_context
.with_inner(|ctx| {
let arr = dc_array_t::from(
chat::get_chat_msgs(ctx, chat_id, flags, marker_flag)
.iter()
.map(|msg_id| msg_id.to_u32())
.collect::<Vec<u32>>(),
);
let arr = dc_array_t::from(chat::get_chat_msgs(ctx, chat_id, flags, marker1before));
Box::into_raw(Box::new(arr))
})
.unwrap_or_else(|_| ptr::null_mut())
@@ -921,12 +880,7 @@ pub unsafe extern "C" fn dc_get_fresh_msgs(
let ffi_context = &*context;
ffi_context
.with_inner(|ctx| {
let arr = dc_array_t::from(
ctx.get_fresh_msgs()
.iter()
.map(|msg_id| msg_id.to_u32())
.collect::<Vec<u32>>(),
);
let arr = dc_array_t::from(ctx.get_fresh_msgs());
Box::into_raw(Box::new(arr))
})
.unwrap_or_else(|_| ptr::null_mut())
@@ -988,12 +942,13 @@ pub unsafe extern "C" fn dc_get_chat_media(
from_prim(or_msg_type3).expect(&format!("incorrect or_msg_type3 = {}", or_msg_type3));
ffi_context
.with_inner(|ctx| {
let arr = dc_array_t::from(
chat::get_chat_media(ctx, chat_id, msg_type, or_msg_type2, or_msg_type3)
.iter()
.map(|msg_id| msg_id.to_u32())
.collect::<Vec<u32>>(),
);
let arr = dc_array_t::from(chat::get_chat_media(
ctx,
chat_id,
msg_type,
or_msg_type2,
or_msg_type3,
));
Box::into_raw(Box::new(arr))
})
.unwrap_or_else(|_| ptr::null_mut())
@@ -1026,16 +981,7 @@ pub unsafe extern "C" fn dc_get_next_media(
from_prim(or_msg_type3).expect(&format!("incorrect or_msg_type3 = {}", or_msg_type3));
ffi_context
.with_inner(|ctx| {
chat::get_next_media(
ctx,
MsgId::new(msg_id),
direction,
msg_type,
or_msg_type2,
or_msg_type3,
)
.map(|msg_id| msg_id.to_u32())
.unwrap_or(0)
chat::get_next_media(ctx, msg_id, direction, msg_type, or_msg_type2, or_msg_type3)
})
.unwrap_or(0)
}
@@ -1106,12 +1052,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))
.iter()
.map(|msg_id| msg_id.to_u32())
.collect::<Vec<u32>>(),
);
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())
@@ -1153,7 +1094,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)
@@ -1225,7 +1166,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")
})
@@ -1245,9 +1186,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)
}
@@ -1263,7 +1210,7 @@ pub unsafe extern "C" fn dc_get_msg_info(
}
let ffi_context = &*context;
ffi_context
.with_inner(|ctx| message::get_msg_info(ctx, MsgId::new(msg_id)).strdup())
.with_inner(|ctx| message::get_msg_info(ctx, msg_id).strdup())
.unwrap_or_else(|_| ptr::null_mut())
}
@@ -1279,7 +1226,7 @@ pub unsafe extern "C" fn dc_get_mime_headers(
let ffi_context = &*context;
ffi_context
.with_inner(|ctx| {
message::get_mime_headers(ctx, MsgId::new(msg_id))
message::get_mime_headers(ctx, msg_id)
.map(|s| s.strdup())
.unwrap_or_else(|| ptr::null_mut())
})
@@ -1297,21 +1244,11 @@ pub unsafe extern "C" fn dc_delete_msgs(
return;
}
let ffi_context = &*context;
let msg_ids = convert_and_prune_message_ids(msg_ids, msg_cnt);
ffi_context
.with_inner(|ctx| message::delete_msgs(ctx, &msg_ids[..]))
.unwrap_or(())
}
#[no_mangle]
pub unsafe extern "C" fn dc_empty_server(context: *mut dc_context_t, flags: u32) {
if context.is_null() || flags == 0 {
eprintln!("ignoring careless call to dc_empty_server()");
return;
}
let ffi_context = &*context;
let ids = std::slice::from_raw_parts(msg_ids, msg_cnt as usize);
ffi_context
.with_inner(|ctx| message::dc_empty_server(ctx, flags))
.with_inner(|ctx| message::delete_msgs(ctx, ids))
.unwrap_or(())
}
@@ -1330,11 +1267,12 @@ pub unsafe extern "C" fn dc_forward_msgs(
eprintln!("ignoring careless call to dc_forward_msgs()");
return;
}
let msg_ids = convert_and_prune_message_ids(msg_ids, msg_cnt);
let ids = std::slice::from_raw_parts(msg_ids, msg_cnt as usize);
let ffi_context = &*context;
ffi_context
.with_inner(|ctx| {
chat::forward_msgs(ctx, &msg_ids[..], chat_id)
chat::forward_msgs(ctx, ids, chat_id)
.unwrap_or_log_default(ctx, "Failed to forward message")
})
.unwrap_or_default()
@@ -1362,10 +1300,11 @@ pub unsafe extern "C" fn dc_markseen_msgs(
eprintln!("ignoring careless call to dc_markseen_msgs()");
return;
}
let msg_ids = convert_and_prune_message_ids(msg_ids, msg_cnt);
let ids = std::slice::from_raw_parts(msg_ids, msg_cnt as usize);
let ffi_context = &*context;
ffi_context
.with_inner(|ctx| message::markseen_msgs(ctx, &msg_ids[..]))
.with_inner(|ctx| message::markseen_msgs(ctx, ids))
.ok();
}
@@ -1380,10 +1319,12 @@ pub unsafe extern "C" fn dc_star_msgs(
eprintln!("ignoring careless call to dc_star_msgs()");
return;
}
let msg_ids = convert_and_prune_message_ids(msg_ids, msg_cnt);
let ids = std::slice::from_raw_parts(msg_ids, msg_cnt as usize);
let ffi_context = &*context;
ffi_context
.with_inner(|ctx| message::star_msgs(ctx, &msg_ids[..], star == 1))
.with_inner(|ctx| message::star_msgs(ctx, ids, star == 1))
.ok();
}
@@ -1396,23 +1337,11 @@ pub unsafe extern "C" fn dc_get_msg(context: *mut dc_context_t, msg_id: u32) ->
let ffi_context = &*context;
ffi_context
.with_inner(|ctx| {
let message = match message::Message::load_from_db(ctx, MsgId::new(msg_id)) {
let message = match message::Message::load_from_db(ctx, msg_id) {
Ok(msg) => msg,
Err(e) => {
if msg_id <= constants::DC_MSG_ID_LAST_SPECIAL {
// C-core API returns empty messages, do the same
warn!(
ctx,
"dc_get_msg called with special msg_id={}, returning empty msg", msg_id
);
message::Message::default()
} else {
error!(
ctx,
"dc_get_msg could not retrieve msg_id {}: {}", msg_id, e
);
return ptr::null_mut();
}
error!(ctx, "Error getting msg #{}: {}", msg_id, e);
return ptr::null_mut();
}
};
let ffi_msg = MessageWrapper { context, message };
@@ -1428,7 +1357,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]
@@ -1442,7 +1371,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)
}
@@ -1457,14 +1386,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)
}
@@ -1480,7 +1407,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,
},
@@ -1499,7 +1426,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))),
@@ -1636,7 +1567,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();
}
@@ -1651,7 +1582,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);
@@ -1694,16 +1625,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, MsgId::new(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)
}
@@ -1729,7 +1659,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())
@@ -1765,7 +1695,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)
}
@@ -1814,7 +1744,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]
@@ -2107,11 +2037,7 @@ pub unsafe extern "C" fn dc_chatlist_get_msg_id(
return 0;
}
let ffi_list = &*chatlist;
ffi_list
.list
.get_msg_id(index as usize)
.map(|msg_id| msg_id.to_u32())
.unwrap_or(0)
ffi_list.list.get_msg_id(index as usize)
}
#[no_mangle]
@@ -2281,26 +2207,6 @@ pub unsafe extern "C" fn dc_chat_is_self_talk(chat: *mut dc_chat_t) -> libc::c_i
ffi_chat.chat.is_self_talk() as libc::c_int
}
#[no_mangle]
pub unsafe extern "C" fn dc_chat_is_device_talk(chat: *mut dc_chat_t) -> libc::c_int {
if chat.is_null() {
eprintln!("ignoring careless call to dc_chat_is_device_talk()");
return 0;
}
let ffi_chat = &*chat;
ffi_chat.chat.is_device_talk() as libc::c_int
}
#[no_mangle]
pub unsafe extern "C" fn dc_chat_can_send(chat: *mut dc_chat_t) -> libc::c_int {
if chat.is_null() {
eprintln!("ignoring careless call to dc_chat_can_send()");
return 0;
}
let ffi_chat = &*chat;
ffi_chat.chat.can_send() as libc::c_int
}
#[no_mangle]
pub unsafe extern "C" fn dc_chat_is_verified(chat: *mut dc_chat_t) -> libc::c_int {
if chat.is_null() {
@@ -2373,7 +2279,7 @@ pub unsafe extern "C" fn dc_msg_get_id(msg: *mut dc_msg_t) -> u32 {
return 0;
}
let ffi_msg = &*msg;
ffi_msg.message.get_id().to_u32()
ffi_msg.message.get_id()
}
#[no_mangle]
@@ -2704,7 +2610,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]
@@ -2718,10 +2625,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]
@@ -3016,6 +2920,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> {
@@ -3060,14 +2972,3 @@ impl<T, E> ResultNullableExt<T> for Result<T, E> {
}
}
}
fn convert_and_prune_message_ids(msg_ids: *const u32, msg_cnt: libc::c_int) -> Vec<MsgId> {
let ids = unsafe { std::slice::from_raw_parts(msg_ids, msg_cnt as usize) };
let msg_ids: Vec<MsgId> = ids
.iter()
.filter(|id| **id > DC_MSG_ID_LAST_SPECIAL)
.map(|id| MsgId::new(*id))
.collect();
msg_ids
}

View File

@@ -2,7 +2,7 @@ extern crate deltachat_provider_database;
use std::ptr;
use deltachat::dc_tools::{to_string_lossy, StrExt};
use deltachat::dc_tools::{as_str, StrExt};
use deltachat_provider_database::StatusState;
#[no_mangle]
@@ -12,7 +12,7 @@ pub type dc_provider_t = deltachat_provider_database::Provider;
pub unsafe extern "C" fn dc_provider_new_from_domain(
domain: *const libc::c_char,
) -> *const dc_provider_t {
match deltachat_provider_database::get_provider_info(&to_string_lossy(domain)) {
match deltachat_provider_database::get_provider_info(as_str(domain)) {
Some(provider) => provider,
None => ptr::null(),
}
@@ -22,8 +22,7 @@ pub unsafe extern "C" fn dc_provider_new_from_domain(
pub unsafe extern "C" fn dc_provider_new_from_email(
email: *const libc::c_char,
) -> *const dc_provider_t {
let email = to_string_lossy(email);
let domain = deltachat_provider_database::get_domain_from_email(&email);
let domain = deltachat_provider_database::get_domain_from_email(as_str(email));
match deltachat_provider_database::get_provider_info(domain) {
Some(provider) => provider,
None => ptr::null(),

View File

@@ -14,7 +14,7 @@ use deltachat::imex::*;
use deltachat::job::*;
use deltachat::location;
use deltachat::lot::LotState;
use deltachat::message::{self, Message, MessageState, MsgId};
use deltachat::message::{self, Message, MessageState};
use deltachat::peerstate::*;
use deltachat::qr::*;
use deltachat::sql;
@@ -86,7 +86,7 @@ pub unsafe fn dc_reset_tables(context: &Context, bits: i32) -> i32 {
context.call_cb(Event::MsgsChanged {
chat_id: 0,
msg_id: MsgId::new(0),
msg_id: 0,
});
1
@@ -170,7 +170,7 @@ fn poke_spec(context: &Context, spec: *const libc::c_char) -> libc::c_int {
if read_cnt > 0 {
context.call_cb(Event::MsgsChanged {
chat_id: 0,
msg_id: MsgId::new(0),
msg_id: 0,
});
}
1
@@ -192,9 +192,9 @@ 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(),
msg.get_id() as libc::c_int,
if msg.get_showpadlock() { "🔒" } else { "" },
if msg.has_location() { "📍" } else { "" },
&contact_name,
@@ -211,27 +211,22 @@ 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,
);
}
unsafe fn log_msglist(context: &Context, msglist: &Vec<MsgId>) -> Result<(), Error> {
unsafe fn log_msglist(context: &Context, msglist: &Vec<u32>) -> Result<(), Error> {
let mut lines_out = 0;
for &msg_id in msglist {
if msg_id.is_daymarker() {
if msg_id == 9 as libc::c_uint {
info!(
context,
"--------------------------------------------------------------------------------"
);
lines_out += 1
} else if !msg_id.is_special() {
} else if msg_id > 0 {
if lines_out == 0 {
info!(
context,
@@ -240,7 +235,7 @@ unsafe fn log_msglist(context: &Context, msglist: &Vec<MsgId>) -> Result<(), Err
lines_out += 1
}
let msg = Message::load_from_db(context, msg_id)?;
log_msg(context, "", &msg);
log_msg(context, "Msg", &msg);
}
}
if lines_out > 0 {
@@ -353,7 +348,6 @@ pub unsafe fn dc_cmdline(context: &Context, line: &str) -> Result<(), failure::E
configure\n\
connect\n\
disconnect\n\
interrupt\n\
maybenetwork\n\
housekeeping\n\
help imex (Import/Export)\n\
@@ -379,7 +373,6 @@ pub unsafe fn dc_cmdline(context: &Context, line: &str) -> Result<(), failure::E
sendimage <file> [<text>]\n\
sendfile <file> [<text>]\n\
draft [<text>]\n\
devicemsg <text>\n\
listmedia\n\
archive <chat-id>\n\
unarchive <chat-id>\n\
@@ -406,7 +399,6 @@ pub unsafe fn dc_cmdline(context: &Context, line: &str) -> Result<(), failure::E
checkqr <qr-content>\n\
event <event-id to test>\n\
fileinfo <file>\n\
emptyserver <flags> (1=MVBOX 2=INBOX)\n\
clear -- clear screen\n\
exit or quit\n\
============================================="
@@ -421,17 +413,17 @@ pub unsafe fn dc_cmdline(context: &Context, line: &str) -> Result<(), failure::E
},
"get-setupcodebegin" => {
ensure!(!arg1.is_empty(), "Argument <msg-id> missing.");
let msg_id: MsgId = MsgId::new(arg1.parse()?);
let msg_id: u32 = arg1.parse()?;
let msg = Message::load_from_db(context, msg_id)?;
if msg.is_setupmessage() {
let setupcodebegin = msg.get_setupcodebegin(context);
println!(
"The setup code for setup message {} starts with: {}",
"The setup code for setup message Msg#{} starts with: {}",
msg_id,
setupcodebegin.unwrap_or_default(),
);
} else {
bail!("{} is no setup message.", msg_id,);
bail!("Msg#{} is no setup message.", msg_id,);
}
}
"continue-key-transfer" => {
@@ -439,7 +431,7 @@ pub unsafe fn dc_cmdline(context: &Context, line: &str) -> Result<(), failure::E
!arg1.is_empty() && !arg2.is_empty(),
"Arguments <msg-id> <setup-code> expected"
);
continue_key_transfer(context, MsgId::new(arg1.parse()?), &arg2)?;
continue_key_transfer(context, arg1.parse()?, &arg2)?;
}
"has-backup" => {
has_backup(context, blobdir)?;
@@ -495,9 +487,6 @@ pub unsafe fn dc_cmdline(context: &Context, line: &str) -> Result<(), failure::E
"info" => {
println!("{:#?}", context.get_info());
}
"interrupt" => {
interrupt_imap_idle(context);
}
"maybenetwork" => {
maybe_network(context);
}
@@ -522,12 +511,15 @@ pub unsafe fn dc_cmdline(context: &Context, line: &str) -> Result<(), failure::E
for i in (0..cnt).rev() {
let chat = Chat::load_from_db(context, chatlist.get_chat_id(i))?;
let temp_subtitle = chat.get_subtitle(context);
let temp_name = chat.get_name();
info!(
context,
"{}#{}: {} [{} fresh]",
"{}#{}: {} [{}] [{} fresh]",
chat_prefix(&chat),
chat.get_id(),
chat.get_name(),
temp_name,
temp_subtitle,
chat::get_fresh_msg_cnt(context, chat.get_id()),
);
let lot = chatlist.get_summary(context, i, Some(&chat));
@@ -584,35 +576,21 @@ pub unsafe fn dc_cmdline(context: &Context, line: &str) -> Result<(), failure::E
ensure!(sel_chat.is_some(), "Failed to select chat");
let sel_chat = sel_chat.as_ref().unwrap();
let msglist = chat::get_chat_msgs(context, sel_chat.get_id(), 0x1, None);
let members = chat::get_chat_contacts(context, sel_chat.id);
let subtitle = if sel_chat.is_device_talk() {
"device-talk".to_string()
} else if sel_chat.get_type() == Chattype::Single && members.len() >= 1 {
let contact = Contact::get_by_id(context, members[0])?;
contact.get_addr().to_string()
} else {
format!("{} member(s)", members.len())
};
let msglist = chat::get_chat_msgs(context, sel_chat.get_id(), 0x1, 0);
let temp2 = sel_chat.get_subtitle(context);
let temp_name = sel_chat.get_name();
info!(
context,
"{}#{}: {} [{}]{}{}",
"{}#{}: {} [{}]{}",
chat_prefix(sel_chat),
sel_chat.get_id(),
sel_chat.get_name(),
subtitle,
temp_name,
temp2,
if sel_chat.is_sending_locations() {
"📍"
} else {
""
},
match sel_chat.get_profile_image(context) {
Some(icon) => match icon.to_str() {
Some(icon) => format!(" Icon: {}", icon),
_ => " Icon: Err".to_string(),
},
_ => "".to_string(),
},
);
log_msglist(context, &msglist)?;
if let Some(draft) = chat::get_draft(context, sel_chat.get_id())? {
@@ -634,7 +612,7 @@ pub unsafe fn dc_cmdline(context: &Context, line: &str) -> Result<(), failure::E
}
"createchatbymsg" => {
ensure!(!arg1.is_empty(), "Argument <msg-id> missing");
let msg_id = MsgId::new(arg1.parse()?);
let msg_id: u32 = arg1.parse()?;
let chat_id = chat::create_by_msg_id(context, msg_id)?;
let chat = Chat::load_from_db(context, chat_id)?;
@@ -726,7 +704,7 @@ pub unsafe fn dc_cmdline(context: &Context, line: &str) -> Result<(), failure::E
let marker = location.marker.as_ref().unwrap_or(&default_marker);
info!(
context,
"Loc#{}: {}: lat={} lng={} acc={} Chat#{} Contact#{} {} {}",
"Loc#{}: {}: lat={} lng={} acc={} Chat#{} Contact#{} Msg#{} {}",
location.location_id,
dc_timestamp_to_str(location.timestamp),
location.latitude,
@@ -763,7 +741,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.");
@@ -830,15 +808,6 @@ pub unsafe fn dc_cmdline(context: &Context, line: &str) -> Result<(), failure::E
println!("Draft deleted.");
}
}
"devicemsg" => {
ensure!(
!arg1.is_empty(),
"Please specify text to add as device message."
);
let mut msg = Message::new(Viewtype::Text);
msg.set_text(Some(arg1.to_string()));
chat::add_device_msg(context, &mut msg)?;
}
"listmedia" => {
ensure!(sel_chat.is_some(), "No chat selected.");
@@ -852,9 +821,9 @@ pub unsafe fn dc_cmdline(context: &Context, line: &str) -> Result<(), failure::E
println!("{} images or videos: ", images.len());
for (i, data) in images.iter().enumerate() {
if 0 == i {
print!("{}", data);
print!("Msg#{}", data);
} else {
print!(", {}", data);
print!(", Msg#{}", data);
}
}
print!("\n");
@@ -875,7 +844,7 @@ pub unsafe fn dc_cmdline(context: &Context, line: &str) -> Result<(), failure::E
}
"msginfo" => {
ensure!(!arg1.is_empty(), "Argument <msg-id> missing.");
let id = MsgId::new(arg1.parse()?);
let id = arg1.parse()?;
let res = message::get_msg_info(context, id);
println!("{}", res);
}
@@ -891,27 +860,27 @@ pub unsafe fn dc_cmdline(context: &Context, line: &str) -> Result<(), failure::E
"Arguments <msg-id> <chat-id> expected"
);
let mut msg_ids = [MsgId::new(0); 1];
let mut msg_ids = [0; 1];
let chat_id = arg2.parse()?;
msg_ids[0] = MsgId::new(arg1.parse()?);
msg_ids[0] = arg1.parse()?;
chat::forward_msgs(context, &msg_ids, chat_id)?;
}
"markseen" => {
ensure!(!arg1.is_empty(), "Argument <msg-id> missing.");
let mut msg_ids = [MsgId::new(0); 1];
msg_ids[0] = MsgId::new(arg1.parse()?);
let mut msg_ids = [0; 1];
msg_ids[0] = arg1.parse()?;
message::markseen_msgs(context, &msg_ids);
}
"star" | "unstar" => {
ensure!(!arg1.is_empty(), "Argument <msg-id> missing.");
let mut msg_ids = [MsgId::new(0); 1];
msg_ids[0] = MsgId::new(arg1.parse()?);
let mut msg_ids = [0; 1];
msg_ids[0] = arg1.parse()?;
message::star_msgs(context, &msg_ids, arg0 == "star");
}
"delmsg" => {
ensure!(!arg1.is_empty(), "Argument <msg-id> missing.");
let mut ids = [MsgId::new(0); 1];
ids[0] = MsgId::new(arg1.parse()?);
let mut ids = [0; 1];
ids[0] = arg1.parse()?;
message::delete_msgs(context, &ids);
}
"listcontacts" | "contacts" | "listverified" => {
@@ -1002,11 +971,6 @@ pub unsafe fn dc_cmdline(context: &Context, line: &str) -> Result<(), failure::E
bail!("Command failed.");
}
}
"emptyserver" => {
ensure!(!arg1.is_empty(), "Argument <flags> missing");
message::dc_empty_server(context, arg1.parse()?);
}
"" => (),
_ => bail!("Unknown command: \"{}\" type ? for help.", arg0),
}

View File

@@ -8,7 +8,7 @@ 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 {
@@ -40,10 +40,8 @@ pub(crate) unsafe fn strncasecmp(
// 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_slice = std::slice::from_raw_parts(s1 as *const u8, n);
let s2_slice = std::slice::from_raw_parts(s2 as *const u8, n);
let s1 = std::ffi::CStr::from_bytes_with_nul_unchecked(s1_slice)
.to_string_lossy()
@@ -1695,14 +1693,6 @@ mod tests {
4,
)
});
assert_eq!(0, unsafe {
strncasecmp(
CString::new("hell").unwrap().as_ptr(),
CString::new("Hell").unwrap().as_ptr(),
100_000_000,
)
});
}
#[test]

View File

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

View File

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

View File

@@ -15,14 +15,12 @@ import deltachat
from . import const
from .capi import ffi, lib
from .cutil import as_dc_charpointer, from_dc_charpointer, iter_array, DCLot
from .chat import Chat
from .message import Message
from .contact import Contact
from .chatting import Contact, Chat, Message
class Account(object):
""" Each account is tied to a sqlite database file which is fully managed
by the underlying deltachat core library. All public Account methods are
by the underlying deltachat c-library. All public Account methods are
meant to be memory-safe and return memory-safe objects.
"""
def __init__(self, db_path, logid=None, eventlogging=True, debug=True):
@@ -137,30 +135,11 @@ class Account(object):
if not self.is_configured():
raise ValueError("need to configure first")
def empty_server_folders(self, inbox=False, mvbox=False):
""" empty server folders. """
flags = 0
if inbox:
flags |= const.DC_EMPTY_INBOX
if mvbox:
flags |= const.DC_EMPTY_MVBOX
if not flags:
raise ValueError("no flags set")
lib.dc_empty_server(self._dc_context, flags)
def get_infostring(self):
""" return info of the configured account. """
self.check_is_configured()
return from_dc_charpointer(lib.dc_get_info(self._dc_context))
def get_latest_backupfile(self, backupdir):
""" return the latest backup file in a given directory.
"""
res = lib.dc_imex_has_backup(self._dc_context, as_dc_charpointer(backupdir))
if res == ffi.NULL:
return None
return from_dc_charpointer(res)
def get_blobdir(self):
""" return the directory for files.
@@ -170,9 +149,9 @@ class Account(object):
return from_dc_charpointer(lib.dc_get_blobdir(self._dc_context))
def get_self_contact(self):
""" return this account's identity as a :class:`deltachat.contact.Contact`.
""" return this account's identity as a :class:`deltachat.chatting.Contact`.
:returns: :class:`deltachat.contact.Contact`
:returns: :class:`deltachat.chatting.Contact`
"""
self.check_is_configured()
return Contact(self._dc_context, const.DC_CONTACT_ID_SELF)
@@ -184,7 +163,7 @@ class Account(object):
:param email: email-address (text type)
:param name: display name for this contact (optional)
:returns: :class:`deltachat.contact.Contact` instance.
:returns: :class:`deltachat.chatting.Contact` instance.
"""
name = as_dc_charpointer(name)
email = as_dc_charpointer(email)
@@ -210,7 +189,7 @@ class Account(object):
whose name or e-mail matches query.
:param only_verified: if true only return verified contacts.
:param with_self: if true the self-contact is also returned.
:returns: list of :class:`deltachat.contact.Contact` objects.
:returns: list of :class:`deltachat.chatting.Contact` objects.
"""
flags = 0
query = as_dc_charpointer(query)
@@ -228,7 +207,7 @@ class Account(object):
""" create or get an existing 1:1 chat object for the specified contact or contact id.
:param contact: chat_id (int) or contact object.
:returns: a :class:`deltachat.chat.Chat` object.
:returns: a :class:`deltachat.chatting.Chat` object.
"""
if hasattr(contact, "id"):
if contact._dc_context != self._dc_context:
@@ -245,7 +224,7 @@ class Account(object):
the specified message.
:param message: messsage id or message instance.
:returns: a :class:`deltachat.chat.Chat` object.
:returns: a :class:`deltachat.chatting.Chat` object.
"""
if hasattr(message, "id"):
if self._dc_context != message._dc_context:
@@ -263,7 +242,7 @@ class Account(object):
Chats are unpromoted until the first message is sent.
:param verified: if true only verified contacts can be added.
:returns: a :class:`deltachat.chat.Chat` object.
:returns: a :class:`deltachat.chatting.Chat` object.
"""
bytes_name = name.encode("utf8")
chat_id = lib.dc_create_group_chat(self._dc_context, int(verified), bytes_name)
@@ -272,7 +251,7 @@ class Account(object):
def get_chats(self):
""" return list of chats.
:returns: a list of :class:`deltachat.chat.Chat` objects.
:returns: a list of :class:`deltachat.chatting.Chat` objects.
"""
dc_chatlist = ffi.gc(
lib.dc_get_chatlist(self._dc_context, 0, ffi.NULL, 0),
@@ -290,24 +269,9 @@ class Account(object):
return Chat(self, const.DC_CHAT_ID_DEADDROP)
def get_message_by_id(self, msg_id):
""" return Message instance.
:param msg_id: integer id of this message.
:returns: :class:`deltachat.message.Message` instance.
"""
""" return Message instance. """
return Message.from_db(self, msg_id)
def get_chat_by_id(self, chat_id):
""" return Chat instance.
:param chat_id: integer id of this chat.
:returns: :class:`deltachat.chat.Chat` instance.
:raises: ValueError if chat does not exist.
"""
res = lib.dc_get_chat(self._dc_context, chat_id)
if res == ffi.NULL:
raise ValueError("cannot get chat with id={}".format(chat_id))
lib.dc_chat_unref(res)
return Chat(self, chat_id)
def mark_seen_messages(self, messages):
""" mark the given set of messages as seen.
@@ -324,7 +288,7 @@ class Account(object):
""" Forward list of messages to a chat.
:param messages: list of :class:`deltachat.message.Message` object.
:param chat: :class:`deltachat.chat.Chat` object.
:param chat: :class:`deltachat.chatting.Chat` object.
:returns: None
"""
msg_ids = [msg.id for msg in messages]
@@ -436,7 +400,7 @@ class Account(object):
""" setup contact and return a Chat after contact is established.
Note that this function may block for a long time as messages are exchanged
with the emitter of the QR code. On success a :class:`deltachat.chat.Chat` instance
with the emitter of the QR code. On success a :class:`deltachat.chatting.Chat` instance
is returned.
:param qr: valid "setup contact" QR code (all other QR codes will result in an exception)
"""
@@ -450,7 +414,7 @@ class Account(object):
""" join a chat group through a QR code.
Note that this function may block for a long time as messages are exchanged
with the emitter of the QR code. On success a :class:`deltachat.chat.Chat` instance
with the emitter of the QR code. On success a :class:`deltachat.chatting.Chat` instance
is returned which is the chat that we just joined.
:param qr: valid "join-group" QR code (all other QR codes will result in an exception)
@@ -485,9 +449,8 @@ class Account(object):
def stop_threads(self, wait=True):
""" stop IMAP/SMTP threads. """
if self._threads.is_started():
self.stop_ongoing()
self._threads.stop(wait=wait)
self.stop_ongoing()
self._threads.stop(wait=wait)
def shutdown(self, wait=True):
""" stop threads and close and remove underlying dc_context and callbacks. """
@@ -518,20 +481,6 @@ class Account(object):
def on_dc_event_imex_file_written(self, data1, data2):
self._imex_events.put(data1)
def set_location(self, latitude=0.0, longitude=0.0, accuracy=0.0):
"""set a new location. It effects all chats where we currently
have enabled location streaming.
:param latitude: float (use 0.0 if not known)
:param longitude: float (use 0.0 if not known)
:param accuracy: float (use 0.0 if not known)
:raises: ValueError if no chat is currently streaming locations
:returns: None
"""
dc_res = lib.dc_set_location(self._dc_context, latitude, longitude, accuracy)
if dc_res == 0:
raise ValueError("no chat is streaming locations")
class IOThreads:
def __init__(self, dc_context, log_event=lambda *args: None):

View File

@@ -1,15 +1,58 @@
""" Chat and Location related API. """
""" chatting related objects: Contact, Chat, Message. """
import mimetypes
import calendar
from datetime import datetime
import os
from . import props
from .cutil import as_dc_charpointer, from_dc_charpointer, iter_array
from .capi import lib, ffi
from . import const
from .message import Message
class Contact(object):
""" Delta-Chat Contact.
You obtain instances of it through :class:`deltachat.account.Account`.
"""
def __init__(self, dc_context, id):
self._dc_context = dc_context
self.id = id
def __eq__(self, other):
return self._dc_context == other._dc_context and self.id == other.id
def __ne__(self, other):
return not (self == other)
def __repr__(self):
return "<Contact id={} addr={} dc_context={}>".format(self.id, self.addr, self._dc_context)
@property
def _dc_contact(self):
return ffi.gc(
lib.dc_get_contact(self._dc_context, self.id),
lib.dc_contact_unref
)
@props.with_doc
def addr(self):
""" normalized e-mail address for this account. """
return from_dc_charpointer(lib.dc_contact_get_addr(self._dc_contact))
@props.with_doc
def display_name(self):
""" display name for this contact. """
return from_dc_charpointer(lib.dc_contact_get_display_name(self._dc_contact))
def is_blocked(self):
""" Return True if the contact is blocked. """
return lib.dc_contact_is_blocked(self._dc_contact)
def is_verified(self):
""" Return True if the contact is verified. """
return lib.dc_contact_is_verified(self._dc_contact)
class Chat(object):
""" Chat object which manages members and through which you can send and retrieve messages.
@@ -269,10 +312,9 @@ class Chat(object):
def get_contacts(self):
""" get all contacts for this chat.
:params: contact object.
:returns: list of :class:`deltachat.contact.Contact` objects for this chat
:returns: list of :class:`deltachat.chatting.Contact` objects for this chat
"""
from .contact import Contact
dc_array = ffi.gc(
lib.dc_get_chat_contacts(self._dc_context, self.id),
lib.dc_array_unref
@@ -323,62 +365,3 @@ class Chat(object):
if dc_res == ffi.NULL:
return None
return from_dc_charpointer(dc_res)
# ------ location streaming API ------------------------------
def is_sending_locations(self):
"""return True if this chat has location-sending enabled currently.
:returns: True if location sending is enabled.
"""
return lib.dc_is_sending_locations_to_chat(self._dc_context, self.id)
def enable_sending_locations(self, seconds):
"""enable sending locations for this chat.
all subsequent messages will carry a location with them.
"""
lib.dc_send_locations_to_chat(self._dc_context, self.id, seconds)
def get_locations(self, contact=None, timestamp_from=None, timestamp_to=None):
"""return list of locations for the given contact in the given timespan.
:param contact: the contact for which locations shall be returned.
:param timespan_from: a datetime object or None (indicating "since beginning")
:param timespan_to: a datetime object or None (indicating up till now)
:returns: list of :class:`deltachat.chat.Location` objects.
"""
if timestamp_from is None:
time_from = 0
else:
time_from = calendar.timegm(timestamp_from.utctimetuple())
if timestamp_to is None:
time_to = 0
else:
time_to = calendar.timegm(timestamp_to.utctimetuple())
if contact is None:
contact_id = 0
else:
contact_id = contact.id
dc_array = lib.dc_get_locations(self._dc_context, self.id, contact_id, time_from, time_to)
return [
Location(
latitude=lib.dc_array_get_latitude(dc_array, i),
longitude=lib.dc_array_get_longitude(dc_array, i),
accuracy=lib.dc_array_get_accuracy(dc_array, i),
timestamp=datetime.utcfromtimestamp(lib.dc_array_get_timestamp(dc_array, i)))
for i in range(lib.dc_array_get_cnt(dc_array))
]
class Location:
def __init__(self, latitude, longitude, accuracy, timestamp):
assert isinstance(timestamp, datetime)
self.latitude = latitude
self.longitude = longitude
self.accuracy = accuracy
self.timestamp = timestamp
def __eq__(self, other):
return self.__dict__ == other.__dict__

View File

@@ -47,8 +47,7 @@ DC_STATE_OUT_FAILED = 24
DC_STATE_OUT_DELIVERED = 26
DC_STATE_OUT_MDN_RCVD = 28
DC_CONTACT_ID_SELF = 1
DC_CONTACT_ID_INFO = 2
DC_CONTACT_ID_DEVICE = 5
DC_CONTACT_ID_DEVICE = 2
DC_CONTACT_ID_LAST_SPECIAL = 9
DC_MSG_TEXT = 10
DC_MSG_IMAGE = 20
@@ -58,27 +57,12 @@ DC_MSG_AUDIO = 40
DC_MSG_VOICE = 41
DC_MSG_VIDEO = 50
DC_MSG_FILE = 60
DC_LP_AUTH_OAUTH2 = 0x2
DC_LP_AUTH_NORMAL = 0x4
DC_LP_IMAP_SOCKET_STARTTLS = 0x100
DC_LP_IMAP_SOCKET_SSL = 0x200
DC_LP_IMAP_SOCKET_PLAIN = 0x400
DC_LP_SMTP_SOCKET_STARTTLS = 0x10000
DC_LP_SMTP_SOCKET_SSL = 0x20000
DC_LP_SMTP_SOCKET_PLAIN = 0x40000
DC_CERTCK_AUTO = 0
DC_CERTCK_STRICT = 1
DC_CERTCK_ACCEPT_INVALID_HOSTNAMES = 2
DC_CERTCK_ACCEPT_INVALID_CERTIFICATES = 3
DC_EMPTY_MVBOX = 0x01
DC_EMPTY_INBOX = 0x02
DC_EVENT_INFO = 100
DC_EVENT_SMTP_CONNECTED = 101
DC_EVENT_IMAP_CONNECTED = 102
DC_EVENT_SMTP_MESSAGE_SENT = 103
DC_EVENT_IMAP_MESSAGE_DELETED = 104
DC_EVENT_IMAP_MESSAGE_MOVED = 105
DC_EVENT_IMAP_FOLDER_EMPTIED = 106
DC_EVENT_NEW_BLOB_FILE = 150
DC_EVENT_DELETED_BLOB_FILE = 151
DC_EVENT_WARNING = 300
@@ -98,9 +82,9 @@ 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
@@ -155,8 +139,8 @@ DC_STR_COUNT = 67
def read_event_defines(f):
rex = re.compile(r'#define\s+((?:DC_EVENT|DC_QR|DC_MSG|DC_LP|DC_EMPTY|DC_CERTCK|DC_STATE|DC_STR|'
r'DC_CONTACT_ID|DC_GCL|DC_CHAT|DC_PROVIDER)_\S+)\s+([x\d]+).*')
rex = re.compile(r'#define\s+((?:DC_EVENT_|DC_QR|DC_MSG|DC_STATE_|DC_STR|'
r'DC_CONTACT_ID_|DC_GCL|DC_CHAT|DC_PROVIDER)\S+)\s+([x\d]+).*')
for line in f:
m = rex.match(line)
if m:

View File

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

View File

@@ -1,4 +1,4 @@
""" The Message object. """
""" chatting related objects: Contact, Chat, Message. """
import os
import shutil
@@ -13,7 +13,7 @@ class Message(object):
""" Message object.
You obtain instances of it through :class:`deltachat.account.Account` or
:class:`deltachat.chat.Chat`.
:class:`deltachat.chatting.Chat`.
"""
def __init__(self, account, dc_msg):
self.account = account
@@ -109,10 +109,6 @@ class Message(object):
""" return True if this message was encrypted. """
return bool(lib.dc_msg_get_showpadlock(self._dc_msg))
def is_forwarded(self):
""" return True if this message was forwarded. """
return bool(lib.dc_msg_is_forwarded(self._dc_msg))
def get_message_info(self):
""" Return informational text for a single message.
@@ -169,18 +165,18 @@ class Message(object):
def chat(self):
"""chat this message was posted in.
:returns: :class:`deltachat.chat.Chat` object
:returns: :class:`deltachat.chatting.Chat` object
"""
from .chat import Chat
from .chatting import Chat
chat_id = lib.dc_msg_get_chat_id(self._dc_msg)
return Chat(self.account, chat_id)
def get_sender_contact(self):
"""return the contact of who wrote the message.
:returns: :class:`deltachat.chat.Contact` instance
:returns: :class:`deltachat.chatting.Contact` instance
"""
from .contact import Contact
from .chatting import Contact
contact_id = lib.dc_msg_get_from_id(self._dc_msg)
return Contact(self._dc_context, contact_id)

View File

@@ -4,7 +4,6 @@ import pytest
import requests
import time
from deltachat import Account
from deltachat import const
from deltachat.capi import lib
import tempfile
@@ -151,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")
@@ -165,8 +159,8 @@ def acfactory(pytestconfig, tmpdir, request, session_liveconfig):
configdict["e2ee_enabled"] = "1"
# Enable strict certificate checks for online accounts
configdict["imap_certificate_checks"] = str(const.DC_CERTCK_STRICT)
configdict["smtp_certificate_checks"] = str(const.DC_CERTCK_STRICT)
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))
@@ -180,12 +174,6 @@ def acfactory(pytestconfig, tmpdir, request, session_liveconfig):
ac.start_threads(mvbox=mvbox, sentbox=sentbox)
return ac
def get_one_online_account(self):
ac1 = self.get_online_configuring_account()
wait_successful_IMAP_SMTP_connection(ac1)
wait_configuration_progress(ac1, 1000)
return ac1
def get_two_online_accounts(self):
ac1 = self.get_online_configuring_account()
ac2 = self.get_online_configuring_account()

View File

@@ -2,7 +2,6 @@ from __future__ import print_function
import pytest
import os
import queue
import time
from deltachat import const, Account
from deltachat.message import Message
from datetime import datetime, timedelta
@@ -122,12 +121,6 @@ class TestOfflineChat:
str(chat1)
repr(chat1)
def test_chat_by_id(self, chat1):
chat2 = chat1.account.get_chat_by_id(chat1.id)
assert chat2 == chat1
with pytest.raises(ValueError):
chat1.account.get_chat_by_id(123123)
def test_chat_idempotent(self, chat1, ac1):
contact1 = chat1.get_contacts()[0]
chat2 = ac1.create_chat_by_contact(contact1.id)
@@ -372,12 +365,10 @@ class TestOfflineChat:
class TestOnlineAccount:
def get_chat(self, ac1, ac2, both_created=False):
def get_chat(self, ac1, ac2):
c2 = ac1.create_contact(email=ac2.get_config("addr"))
chat = ac1.create_chat_by_contact(c2)
assert chat.id > const.DC_CHAT_ID_LAST_SPECIAL
if both_created:
ac2.create_chat_by_contact(ac2.create_contact(email=ac1.get_config("addr")))
return chat
def test_configure_canceled(self, acfactory):
@@ -398,8 +389,7 @@ class TestOnlineAccount:
def test_one_account_send_bcc_setting(self, acfactory, lp):
ac1 = acfactory.get_online_configuring_account()
ac2_config = acfactory.peek_online_config()
c2 = ac1.create_contact(email=ac2_config["addr"])
c2 = ac1.create_contact(email="notexists@testrun.org")
chat = ac1.create_chat_by_contact(c2)
assert chat.id > const.DC_CHAT_ID_LAST_SPECIAL
wait_successful_IMAP_SMTP_connection(ac1)
@@ -420,7 +410,6 @@ class TestOnlineAccount:
lp.sec("send out message without bcc")
ac1.set_config("bcc_self", "0")
msg_out = chat.send_text("message3")
assert not msg_out.is_forwarded()
ev = ac1._evlogger.get_matching("DC_EVENT_MSGS_CHANGED")
assert ev[2] == msg_out.id
ev = ac1._evlogger.get_matching("DC_EVENT_SMTP_MESSAGE_SENT")
@@ -463,7 +452,6 @@ class TestOnlineAccount:
# check the message arrived in contact-requests/deaddrop
chat2 = msg_in.chat
assert msg_in in chat2.get_messages()
assert not msg_in.is_forwarded()
assert chat2.is_deaddrop()
assert chat2 == ac2.get_deaddrop_chat()
chat3 = ac2.create_group_chat("newgroup")
@@ -471,47 +459,9 @@ class TestOnlineAccount:
ac2.forward_messages([msg_in], chat3)
assert chat3.is_promoted()
messages = chat3.get_messages()
msg = messages[-1]
assert msg.is_forwarded()
ac2.delete_messages(messages)
assert not chat3.get_messages()
def test_forward_own_message(self, acfactory, lp):
ac1, ac2 = acfactory.get_two_online_accounts()
chat = self.get_chat(ac1, ac2, both_created=True)
lp.sec("sending message")
msg_out = chat.send_text("message2")
lp.sec("receiving message")
ev = ac2._evlogger.get_matching("DC_EVENT_INCOMING_MSG")
msg_in = ac2.get_message_by_id(ev[2])
assert msg_in.text == "message2"
assert not msg_in.is_forwarded()
lp.sec("ac1: creating group chat, and forward own message")
group = ac1.create_group_chat("newgroup2")
group.add_contact(ac1.create_contact(ac2.get_config("addr")))
ac1.forward_messages([msg_out], group)
# wait for other account to receive
ev = ac2._evlogger.get_matching("DC_EVENT_INCOMING_MSG")
msg_in = ac2.get_message_by_id(ev[2])
assert msg_in.text == "message2"
assert msg_in.is_forwarded()
def test_send_self_message_and_empty_folder(self, acfactory, lp):
ac1 = acfactory.get_one_online_account()
lp.sec("ac1: create self chat")
chat = ac1.create_chat_by_contact(ac1.get_self_contact())
chat.send_text("hello")
ac1._evlogger.get_matching("DC_EVENT_SMTP_MESSAGE_SENT")
ac1.empty_server_folders(inbox=True, mvbox=True)
ev = ac1._evlogger.get_matching("DC_EVENT_IMAP_FOLDER_EMPTIED")
assert ev[2] == "DeltaChat"
ev = ac1._evlogger.get_matching("DC_EVENT_IMAP_FOLDER_EMPTIED")
assert ev[2] == "INBOX"
def test_send_and_receive_message_markseen(self, acfactory, lp):
ac1, ac2 = acfactory.get_two_online_accounts()
@@ -531,7 +481,6 @@ class TestOnlineAccount:
assert ev[2] == msg_out.id
msg_in = ac2.get_message_by_id(msg_out.id)
assert msg_in.text == "message1"
assert not msg_in.is_forwarded()
lp.sec("check the message arrived in contact-requets/deaddrop")
chat2 = msg_in.chat
@@ -642,29 +591,18 @@ class TestOnlineAccount:
assert os.path.exists(msg_in.filename)
assert os.stat(msg_in.filename).st_size == os.stat(path).st_size
def test_import_export_online_all(self, acfactory, tmpdir, lp):
def test_import_export_online_all(self, acfactory, tmpdir):
ac1 = acfactory.get_online_configuring_account()
wait_configuration_progress(ac1, 1000)
lp.sec("create some chat content")
contact1 = ac1.create_contact("some1@hello.com", name="some1")
chat = ac1.create_chat_by_contact(contact1)
chat.send_text("msg1")
backupdir = tmpdir.mkdir("backup")
lp.sec("export all to {}".format(backupdir))
path = ac1.export_all(backupdir.strpath)
assert os.path.exists(path)
t = time.time()
lp.sec("get fresh empty account")
ac2 = acfactory.get_unconfigured_account()
lp.sec("get latest backup file")
path2 = ac2.get_latest_backupfile(backupdir.strpath)
assert path2 == path
lp.sec("import backup and check it's proper")
ac2.import_all(path)
contacts = ac2.get_contacts(query="some1")
assert len(contacts) == 1
@@ -675,17 +613,6 @@ class TestOnlineAccount:
assert len(messages) == 1
assert messages[0].text == "msg1"
# wait until a second passed since last backup
# because get_latest_backupfile() shall return the latest backup
# from a UI it's unlikely anyone manages to export two
# backups in one second.
time.sleep(max(0, 1 - (time.time() - t)))
lp.sec("Second-time export all to {}".format(backupdir))
path2 = ac1.export_all(backupdir.strpath)
assert os.path.exists(path2)
assert path2 != path
assert ac2.get_latest_backupfile(backupdir.strpath) == path2
def test_ac_setup_message(self, acfactory, lp):
# note that the receiving account needs to be configured and running
# before ther setup message is send. DC does not read old messages
@@ -711,27 +638,6 @@ class TestOnlineAccount:
msg.continue_key_transfer(setup_code)
assert ac1.get_info()["fingerprint"] == ac2.get_info()["fingerprint"]
def test_ac_setup_message_twice(self, acfactory, lp):
ac1 = acfactory.get_online_configuring_account()
ac2 = acfactory.clone_online_account(ac1)
ac2._evlogger.set_timeout(30)
wait_configuration_progress(ac2, 1000)
wait_configuration_progress(ac1, 1000)
lp.sec("trigger ac setup message but ignore")
assert ac1.get_info()["fingerprint"] != ac2.get_info()["fingerprint"]
ac1.initiate_key_transfer()
ac2._evlogger.get_matching("DC_EVENT_INCOMING_MSG|DC_EVENT_MSGS_CHANGED")
lp.sec("trigger second ac setup message, wait for receive ")
setup_code2 = ac1.initiate_key_transfer()
ev = ac2._evlogger.get_matching("DC_EVENT_INCOMING_MSG|DC_EVENT_MSGS_CHANGED")
msg = ac2.get_message_by_id(ev[2])
assert msg.is_setup_message()
assert msg.get_setupcodebegin() == setup_code2[:2]
lp.sec("process second setup message")
msg.continue_key_transfer(setup_code2)
assert ac1.get_info()["fingerprint"] == ac2.get_info()["fingerprint"]
def test_qr_setup_contact(self, acfactory, lp):
ac1, ac2 = acfactory.get_two_online_accounts()
lp.sec("ac1: create QR code and let ac2 scan it, starting the securejoin")
@@ -839,54 +745,6 @@ class TestOnlineAccount:
assert chat1b.get_profile_image() is None
assert chat.get_profile_image() is None
def test_send_receive_locations(self, acfactory, lp):
now = datetime.utcnow()
ac1, ac2 = acfactory.get_two_online_accounts()
lp.sec("ac1: create chat with ac2")
chat1 = self.get_chat(ac1, ac2)
chat2 = self.get_chat(ac2, ac1)
assert not chat1.is_sending_locations()
with pytest.raises(ValueError):
ac1.set_location(latitude=0.0, longitude=10.0)
ac1._evlogger.consume_events()
ac2._evlogger.consume_events()
lp.sec("ac1: enable location sending in chat")
chat1.enable_sending_locations(seconds=100)
assert chat1.is_sending_locations()
ac1._evlogger.get_matching("DC_EVENT_SMTP_MESSAGE_SENT")
ac1.set_location(latitude=2.0, longitude=3.0, accuracy=0.5)
ac1._evlogger.get_matching("DC_EVENT_LOCATION_CHANGED")
chat1.send_text("hello")
ac1._evlogger.get_matching("DC_EVENT_SMTP_MESSAGE_SENT")
lp.sec("ac2: wait for incoming location message")
ac2._evlogger.get_matching("DC_EVENT_INCOMING_MSG") # "enabled-location streaming"
# currently core emits location changed before event_incoming message
ac2._evlogger.get_matching("DC_EVENT_LOCATION_CHANGED")
ac2._evlogger.get_matching("DC_EVENT_INCOMING_MSG") # text message with location
locations = chat2.get_locations()
assert len(locations) == 1
assert locations[0].latitude == 2.0
assert locations[0].longitude == 3.0
assert locations[0].accuracy == 0.5
assert locations[0].timestamp > now
contact = ac2.create_contact(ac1.get_config("addr"))
locations2 = chat2.get_locations(contact=contact)
assert len(locations2) == 1
assert locations2 == locations
contact = ac2.create_contact("nonexisting@example.org")
locations3 = chat2.get_locations(contact=contact)
assert not locations3
class TestOnlineConfigureFails:
def test_invalid_password(self, acfactory):
@@ -895,7 +753,7 @@ class TestOnlineConfigureFails:
ac1.start_threads()
wait_configuration_progress(ac1, 500)
ev1 = ac1._evlogger.get_matching("DC_EVENT_ERROR_NETWORK")
assert "cannot login" in ev1[2].lower()
assert "authentication failed" in ev1[2].lower()
wait_configuration_progress(ac1, 0, 0)
def test_invalid_user(self, acfactory):
@@ -904,7 +762,7 @@ class TestOnlineConfigureFails:
ac1.start_threads()
wait_configuration_progress(ac1, 500)
ev1 = ac1._evlogger.get_matching("DC_EVENT_ERROR_NETWORK")
assert "cannot login" in ev1[2].lower()
assert "authentication failed" in ev1[2].lower()
wait_configuration_progress(ac1, 0, 0)
def test_invalid_domain(self, acfactory):

View File

@@ -95,13 +95,6 @@ def test_markseen_invalid_message_ids(acfactory):
ac1._evlogger.ensure_event_not_queued("DC_EVENT_WARNING|DC_EVENT_ERROR")
def test_get_special_message_id_returns_empty_message(acfactory):
ac1 = acfactory.get_configured_offline_account()
for i in range(1, 10):
msg = ac1.get_message_by_id(i)
assert msg.id == 0
def test_provider_info():
provider = lib.dc_provider_new_from_email(cutil.as_dc_charpointer("ex@example.com"))
assert cutil.from_dc_charpointer(

View File

@@ -23,7 +23,7 @@ if [ $? != 0 ]; then
fi
pushd python
if [ -e "./liveconfig" -a -z "$DCC_PY_LIVECONFIG" ]; then
if [ -e "./liveconfig" && -z "$DCC_PY_LIVECONFIG" ]; then
export DCC_PY_LIVECONFIG=liveconfig
fi
tox "$@"

View File

@@ -1 +1 @@
nightly-2019-11-06
nightly-2019-08-13

View File

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

View File

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

View File

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

File diff suppressed because it is too large Load Diff

View File

@@ -4,7 +4,7 @@ use crate::contact::*;
use crate::context::*;
use crate::error::Result;
use crate::lot::Lot;
use crate::message::{Message, MsgId};
use crate::message::Message;
use crate::stock::StockMessage;
/// An object representing a single chatlist in memory.
@@ -34,7 +34,7 @@ use crate::stock::StockMessage;
#[derive(Debug)]
pub struct Chatlist {
/// Stores pairs of `chat_id, message_id`
ids: Vec<(u32, MsgId)>,
ids: Vec<(u32, u32)>,
}
impl Chatlist {
@@ -86,12 +86,25 @@ impl Chatlist {
query: Option<&str>,
query_contact_id: Option<u32>,
) -> Result<Self> {
let mut add_archived_link_item = false;
let mut add_archived_link_item = 0;
// select with left join and minimum:
// - the inner select must use `hidden` and _not_ `m.hidden`
// which would refer the outer select and take a lot of time
// - `GROUP BY` is needed several messages may have the same timestamp
// - the list starts with the newest chats
// nb: the query currently shows messages from blocked contacts in groups.
// however, for normal-groups, this is okay as the message is also returned by dc_get_chat_msgs()
// (otherwise it would be hard to follow conversations, wa and tg do the same)
// for the deaddrop, however, they should really be hidden, however, _currently_ the deaddrop is not
// shown at all permanent in the chatlist.
let process_row = |row: &rusqlite::Row| {
let chat_id: u32 = row.get(0)?;
let msg_id: MsgId = row.get(1).unwrap_or_default();
Ok((chat_id, msg_id))
let chat_id: i32 = row.get(0)?;
// TODO: verify that it is okay for this to be Null
let msg_id: i32 = row.get(1).unwrap_or_default();
Ok((chat_id as u32, msg_id as u32))
};
let process_rows = |rows: rusqlite::MappedRows<_>| {
@@ -99,63 +112,36 @@ impl Chatlist {
.map_err(Into::into)
};
// select with left join and minimum:
//
// - the inner select must use `hidden` and _not_ `m.hidden`
// which would refer the outer select and take a lot of time
// - `GROUP BY` is needed several messages may have the same
// timestamp
// - the list starts with the newest chats
//
// nb: the query currently shows messages from blocked
// contacts in groups. however, for normal-groups, this is
// okay as the message is also returned by dc_get_chat_msgs()
// (otherwise it would be hard to follow conversations, wa and
// tg do the same) for the deaddrop, however, they should
// really be hidden, however, _currently_ the deaddrop is not
// nb: the query currently shows messages from blocked contacts in groups.
// however, for normal-groups, this is okay as the message is also returned by dc_get_chat_msgs()
// (otherwise it would be hard to follow conversations, wa and tg do the same)
// for the deaddrop, however, they should really be hidden, however, _currently_ the deaddrop is not
// shown at all permanent in the chatlist.
let mut ids = if let Some(query_contact_id) = query_contact_id {
// show chats shared with a given contact
context.sql.query_map(
concat!(
"SELECT c.id, m.id",
" FROM chats c",
" LEFT JOIN msgs m",
" ON c.id=m.chat_id",
" AND m.timestamp=(",
" SELECT MAX(timestamp)",
" FROM msgs",
" WHERE chat_id=c.id",
" AND hidden=0)",
" WHERE c.id>9",
" AND c.blocked=0",
" AND c.id IN(SELECT chat_id FROM chats_contacts WHERE contact_id=?)",
" GROUP BY c.id",
" ORDER BY IFNULL(m.timestamp,0) DESC, m.id DESC;"
),
params![query_contact_id as i32],
process_row,
process_rows,
)?
"SELECT c.id, m.id FROM chats c LEFT JOIN msgs m \
ON c.id=m.chat_id \
AND m.timestamp=( SELECT MAX(timestamp) \
FROM msgs WHERE chat_id=c.id \
AND (hidden=0 OR (hidden=1 AND state=19))) WHERE c.id>9 \
AND c.blocked=0 AND c.id IN(SELECT chat_id FROM chats_contacts WHERE contact_id=?) \
GROUP BY c.id ORDER BY IFNULL(m.timestamp,0) DESC, m.id DESC;",
params![query_contact_id as i32],
process_row,
process_rows,
)?
} else if 0 != listflags & DC_GCL_ARCHIVED_ONLY {
// show archived chats
context.sql.query_map(
concat!(
"SELECT c.id, m.id",
" FROM chats c",
" LEFT JOIN msgs m",
" ON c.id=m.chat_id",
" AND m.timestamp=(",
" SELECT MAX(timestamp)",
" FROM msgs",
" WHERE chat_id=c.id",
" AND hidden=0)",
" 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;"
),
"SELECT c.id, m.id FROM chats c LEFT JOIN msgs m \
ON c.id=m.chat_id \
AND m.timestamp=( SELECT MAX(timestamp) \
FROM msgs WHERE chat_id=c.id \
AND (hidden=0 OR (hidden=1 AND state=19))) WHERE c.id>9 \
AND c.blocked=0 AND c.archived=1 GROUP BY c.id \
ORDER BY IFNULL(m.timestamp,0) DESC, m.id DESC;",
params![],
process_row,
process_rows,
@@ -166,22 +152,13 @@ impl Chatlist {
let str_like_cmd = format!("%{}%", query);
context.sql.query_map(
concat!(
"SELECT c.id, m.id",
" FROM chats c",
" LEFT JOIN msgs m",
" ON c.id=m.chat_id",
" AND m.timestamp=(",
" SELECT MAX(timestamp)",
" FROM msgs",
" WHERE chat_id=c.id",
" AND hidden=0)",
" 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;"
),
"SELECT c.id, m.id FROM chats c LEFT JOIN msgs m \
ON c.id=m.chat_id \
AND m.timestamp=( SELECT MAX(timestamp) \
FROM msgs WHERE chat_id=c.id \
AND (hidden=0 OR (hidden=1 AND state=19))) WHERE c.id>9 \
AND c.blocked=0 AND c.name LIKE ? \
GROUP BY c.id ORDER BY IFNULL(m.timestamp,0) DESC, m.id DESC;",
params![str_like_cmd],
process_row,
process_rows,
@@ -189,40 +166,34 @@ impl Chatlist {
} else {
// show normal chatlist
let mut ids = context.sql.query_map(
concat!(
"SELECT c.id, m.id",
" FROM chats c",
" LEFT JOIN msgs m",
" ON c.id=m.chat_id",
" AND m.timestamp=(",
" SELECT MAX(timestamp)",
" FROM msgs",
" WHERE chat_id=c.id",
" AND hidden=0)",
" 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;"
),
"SELECT c.id, m.id FROM chats c \
LEFT JOIN msgs m \
ON c.id=m.chat_id \
AND m.timestamp=( SELECT MAX(timestamp) \
FROM msgs WHERE chat_id=c.id \
AND (hidden=0 OR (hidden=1 AND state=19))) WHERE c.id>9 \
AND c.blocked=0 AND c.archived=0 \
GROUP BY c.id \
ORDER BY IFNULL(m.timestamp,0) DESC, m.id DESC;",
params![],
process_row,
process_rows,
)?;
if 0 == listflags & DC_GCL_NO_SPECIALS {
if let Some(last_deaddrop_fresh_msg_id) = get_last_deaddrop_fresh_msg(context) {
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));
}
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, MsgId::new(0)));
ids.push((DC_CHAT_ID_ALLDONE_HINT, 0));
}
ids.push((DC_CHAT_ID_ARCHIVED_LINK, MsgId::new(0)));
ids.push((DC_CHAT_ID_ARCHIVED_LINK, 0));
}
Ok(Chatlist { ids })
@@ -250,9 +221,12 @@ impl Chatlist {
/// Get a single message ID of a chatlist.
///
/// To get the message object from the message ID, use dc_get_msg().
pub fn get_msg_id(&self, index: usize) -> Result<MsgId> {
ensure!(index < self.ids.len(), "Chatlist index out of range");
Ok(self.ids[index].1)
pub fn get_msg_id(&self, index: usize) -> u32 {
if index >= self.ids.len() {
return 0;
}
self.ids[index].1
}
/// Get a summary for a chatlist index.
@@ -294,28 +268,22 @@ impl Chatlist {
let lastmsg_id = self.ids[index].1;
let mut lastcontact = None;
let mut lastmsg = if let Ok(lastmsg) = Message::load_from_db(context, lastmsg_id) {
if lastmsg.from_id != DC_CONTACT_ID_SELF
&& (chat.typ == Chattype::Group || chat.typ == Chattype::VerifiedGroup)
{
lastcontact = Contact::load_from_db(context, lastmsg.from_id).ok();
}
let lastmsg = if 0 != lastmsg_id {
if let Ok(lastmsg) = Message::load_from_db(context, lastmsg_id) {
if lastmsg.from_id != 1 as libc::c_uint
&& (chat.typ == Chattype::Group || chat.typ == Chattype::VerifiedGroup)
{
lastcontact = Contact::load_from_db(context, lastmsg.from_id).ok();
}
Some(lastmsg)
Some(lastmsg)
} else {
None
}
} else {
None
};
if let Ok(draft) = get_draft(context, chat.id) {
if draft.is_some()
&& (lastmsg.is_none()
|| draft.as_ref().unwrap().timestamp_sort
> lastmsg.as_ref().unwrap().timestamp_sort)
{
lastmsg = draft;
}
}
if chat.id == DC_CHAT_ID_ARCHIVED_LINK {
ret.text2 = None;
} else if lastmsg.is_none() || lastmsg.as_ref().unwrap().from_id == DC_CONTACT_ID_UNDEFINED
@@ -340,21 +308,19 @@ pub fn dc_get_archived_cnt(context: &Context) -> u32 {
.unwrap_or_default()
}
fn get_last_deaddrop_fresh_msg(context: &Context) -> Option<MsgId> {
// We have an index over the state-column, this should be
// sufficient as there are typically only few fresh messages.
context.sql.query_get_value(
context,
concat!(
"SELECT m.id",
" FROM msgs m",
" LEFT JOIN chats c",
" ON c.id=m.chat_id",
" WHERE m.state=10",
" AND m.hidden=0",
" AND c.blocked=2",
" ORDER BY m.timestamp DESC, m.id DESC;"
),
params![],
)
fn get_last_deaddrop_fresh_msg(context: &Context) -> u32 {
// We have an index over the state-column, this should be sufficient as there are typically
// only few fresh messages.
context
.sql
.query_get_value(
context,
"SELECT m.id FROM msgs m LEFT JOIN chats c ON c.id=m.chat_id \
WHERE m.state=10 \
AND m.hidden=0 \
AND c.blocked=2 \
ORDER BY m.timestamp DESC, m.id DESC;",
params![],
)
.unwrap_or_default()
}

View File

@@ -45,7 +45,7 @@ pub enum Config {
MvboxWatch,
#[strum(props(default = "1"))]
MvboxMove,
#[strum(props(default = "0"))] // also change ShowEmails.default() on changes
#[strum(props(default = "0"))]
ShowEmails,
SaveMimeHeaders,
ConfiguredAddr,

View File

@@ -3,7 +3,6 @@ use quick_xml::events::{BytesEnd, BytesStart, BytesText};
use crate::constants::*;
use crate::context::Context;
use crate::error::Error;
use crate::login_param::LoginParam;
use super::read_autoconf_file;
@@ -12,7 +11,7 @@ use super::read_autoconf_file;
******************************************************************************/
/* documentation: https://developer.mozilla.org/en-US/docs/Mozilla/Thunderbird/Autoconfiguration */
struct MozAutoconfigure<'a> {
pub in_emailaddr: &'a str,
pub in_0: &'a LoginParam,
pub in_emaildomain: &'a str,
pub in_emaillocalpart: &'a str,
pub out: LoginParam,
@@ -22,7 +21,6 @@ struct MozAutoconfigure<'a> {
pub tag_config: MozConfigTag,
}
#[derive(PartialEq)]
enum MozServer {
Undefined,
Imap,
@@ -37,20 +35,25 @@ enum MozConfigTag {
Username,
}
pub fn moz_parse_xml(in_emailaddr: &str, xml_raw: &str) -> Result<LoginParam, Error> {
let mut reader = quick_xml::Reader::from_str(xml_raw);
reader.trim_text(true);
pub fn moz_autoconfigure(
context: &Context,
url: &str,
param_in: &LoginParam,
) -> Option<LoginParam> {
let xml_raw = read_autoconf_file(context, url)?;
// 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('@')?;
let (in_emaillocalpart, in_emaildomain) = param_in.addr.split_at(p);
let in_emaildomain = &in_emaildomain[1..];
let mut reader = quick_xml::Reader::from_str(&xml_raw);
reader.trim_text(true);
let mut buf = Vec::new();
let mut moz_ac = MozAutoconfigure {
in_emailaddr,
in_0: param_in,
in_emaildomain,
in_emaillocalpart,
out: LoginParam::new(),
@@ -59,8 +62,6 @@ pub fn moz_parse_xml(in_emailaddr: &str, xml_raw: &str) -> Result<LoginParam, Er
tag_server: MozServer::Undefined,
tag_config: MozConfigTag::Undefined,
};
let mut buf = Vec::new();
loop {
match reader.read_event(&mut buf) {
Ok(quick_xml::events::Event::Start(ref e)) => {
@@ -71,7 +72,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!(
warn!(
context,
"Configure xml: Error at position {}: {:?}",
reader.buffer_position(),
e
@@ -89,26 +91,11 @@ pub fn moz_parse_xml(in_emailaddr: &str, xml_raw: &str) -> Result<LoginParam, Er
|| moz_ac.out.send_port == 0
{
let r = moz_ac.out.to_string();
bail!("Bad or incomplete autoconfig: {}", r,);
warn!(context, "Bad or incomplete autoconfig: {}", r,);
return None;
}
Ok(moz_ac.out)
}
pub fn moz_autoconfigure(
context: &Context,
url: &str,
param_in: &LoginParam,
) -> Option<LoginParam> {
let xml_raw = read_autoconf_file(context, url)?;
match moz_parse_xml(&param_in.addr, &xml_raw) {
Err(err) => {
warn!(context, "{}", err);
None
}
Ok(lp) => Some(lp),
}
Some(moz_ac.out)
}
fn moz_autoconfigure_text_cb<B: std::io::BufRead>(
@@ -118,7 +105,7 @@ fn moz_autoconfigure_text_cb<B: std::io::BufRead>(
) {
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;
@@ -173,17 +160,13 @@ fn moz_autoconfigure_endtag_cb(event: &BytesEnd, moz_ac: &mut MozAutoconfigure)
let tag = String::from_utf8_lossy(event.name()).trim().to_lowercase();
if tag == "incomingserver" {
if moz_ac.tag_server == MozServer::Imap {
moz_ac.out_imap_set = true;
}
moz_ac.tag_server = MozServer::Undefined;
moz_ac.tag_config = MozConfigTag::Undefined;
moz_ac.out_imap_set = true;
} 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.out_smtp_set = true;
} else {
moz_ac.tag_config = MozConfigTag::Undefined;
}
@@ -234,89 +217,3 @@ fn moz_autoconfigure_starttag_cb<B: std::io::BufRead>(
moz_ac.tag_config = MozConfigTag::Username;
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_parse_outlook_autoconfig() {
// Copied from https://autoconfig.thunderbird.net/v1.1/outlook.com on 2019-10-11
let xml_raw =
"<clientConfig version=\"1.1\">
<emailProvider id=\"outlook.com\">
<domain>hotmail.com</domain>
<domain>hotmail.co.uk</domain>
<domain>hotmail.co.jp</domain>
<domain>hotmail.com.br</domain>
<domain>hotmail.de</domain>
<domain>hotmail.fr</domain>
<domain>hotmail.it</domain>
<domain>hotmail.es</domain>
<domain>live.com</domain>
<domain>live.co.uk</domain>
<domain>live.co.jp</domain>
<domain>live.de</domain>
<domain>live.fr</domain>
<domain>live.it</domain>
<domain>live.jp</domain>
<domain>msn.com</domain>
<domain>outlook.com</domain>
<displayName>Outlook.com (Microsoft)</displayName>
<displayShortName>Outlook</displayShortName>
<incomingServer type=\"exchange\">
<hostname>outlook.office365.com</hostname>
<port>443</port>
<username>%EMAILADDRESS%</username>
<socketType>SSL</socketType>
<authentication>OAuth2</authentication>
<owaURL>https://outlook.office365.com/owa/</owaURL>
<ewsURL>https://outlook.office365.com/ews/exchange.asmx</ewsURL>
<useGlobalPreferredServer>true</useGlobalPreferredServer>
</incomingServer>
<incomingServer type=\"imap\">
<hostname>outlook.office365.com</hostname>
<port>993</port>
<socketType>SSL</socketType>
<authentication>password-cleartext</authentication>
<username>%EMAILADDRESS%</username>
</incomingServer>
<incomingServer type=\"pop3\">
<hostname>outlook.office365.com</hostname>
<port>995</port>
<socketType>SSL</socketType>
<authentication>password-cleartext</authentication>
<username>%EMAILADDRESS%</username>
<pop3>
<leaveMessagesOnServer>true</leaveMessagesOnServer>
<!-- Outlook.com docs specifically mention that POP3 deletes have effect on the main inbox on webmail and IMAP -->
</pop3>
</incomingServer>
<outgoingServer type=\"smtp\">
<hostname>smtp.office365.com</hostname>
<port>587</port>
<socketType>STARTTLS</socketType>
<authentication>password-cleartext</authentication>
<username>%EMAILADDRESS%</username>
</outgoingServer>
<documentation url=\"http://windows.microsoft.com/en-US/windows/outlook/send-receive-from-app\">
<descr lang=\"en\">Set up an email app with Outlook.com</descr>
</documentation>
</emailProvider>
<webMail>
<loginPage url=\"https://www.outlook.com/\"/>
<loginPageInfo url=\"https://www.outlook.com/\">
<username>%EMAILADDRESS%</username>
<usernameField id=\"i0116\" name=\"login\"/>
<passwordField id=\"i0118\" name=\"passwd\"/>
<loginButton id=\"idSIButton9\" name=\"SI\"/>
</loginPageInfo>
</webMail>
</clientConfig>";
let res = moz_parse_xml("example@outlook.com", xml_raw).expect("XML parsing failed");
assert_eq!(res.mail_server, "outlook.office365.com");
assert_eq!(res.mail_port, 993);
assert_eq!(res.send_server, "smtp.office365.com");
assert_eq!(res.send_port, 587);
}
}

View File

@@ -3,7 +3,6 @@ use quick_xml::events::BytesEnd;
use crate::constants::*;
use crate::context::Context;
use crate::error::Error;
use crate::login_param::LoginParam;
use super::read_autoconf_file;
@@ -20,100 +19,6 @@ struct OutlookAutodiscover {
pub config_redirecturl: Option<String>,
}
enum ParsingResult {
LoginParam(LoginParam),
RedirectUrl(String),
}
fn outlk_parse_xml(xml_raw: &str) -> Result<ParsingResult, Error> {
let mut outlk_ad = OutlookAutodiscover {
out: LoginParam::new(),
out_imap_set: false,
out_smtp_set: false,
config_type: None,
config_server: String::new(),
config_port: 0,
config_ssl: String::new(),
config_redirecturl: None,
};
let mut reader = quick_xml::Reader::from_str(&xml_raw);
reader.trim_text(true);
let mut buf = Vec::new();
let mut current_tag: Option<String> = None;
loop {
match reader.read_event(&mut buf) {
Ok(quick_xml::events::Event::Start(ref e)) => {
let tag = String::from_utf8_lossy(e.name()).trim().to_lowercase();
if tag == "protocol" {
outlk_ad.config_type = None;
outlk_ad.config_server = String::new();
outlk_ad.config_port = 0;
outlk_ad.config_ssl = String::new();
outlk_ad.config_redirecturl = None;
current_tag = None;
} else {
current_tag = Some(tag);
}
}
Ok(quick_xml::events::Event::End(ref e)) => {
outlk_autodiscover_endtag_cb(e, &mut outlk_ad);
current_tag = None;
}
Ok(quick_xml::events::Event::Text(ref e)) => {
let val = e.unescape_and_decode(&reader).unwrap_or_default();
if let Some(ref tag) = current_tag {
match tag.as_str() {
"type" => {
outlk_ad.config_type = Some(val.trim().to_lowercase().to_string())
}
"server" => outlk_ad.config_server = val.trim().to_string(),
"port" => outlk_ad.config_port = val.trim().parse().unwrap_or_default(),
"ssl" => outlk_ad.config_ssl = val.trim().to_string(),
"redirecturl" => outlk_ad.config_redirecturl = Some(val.trim().to_string()),
_ => {}
};
}
}
Err(e) => {
bail!(
"Configure xml: Error at position {}: {:?}",
reader.buffer_position(),
e
);
}
Ok(quick_xml::events::Event::Eof) => break,
_ => (),
}
buf.clear();
}
// XML redirect via redirecturl
if outlk_ad.config_redirecturl.is_none()
|| outlk_ad.config_redirecturl.as_ref().unwrap().is_empty()
{
if outlk_ad.out.mail_server.is_empty()
|| outlk_ad.out.mail_port == 0
|| outlk_ad.out.send_server.is_empty()
|| outlk_ad.out.send_port == 0
{
let r = outlk_ad.out.to_string();
bail!("Bad or incomplete autoconfig: {}", r,);
}
Ok(ParsingResult::LoginParam(outlk_ad.out))
} else {
Ok(ParsingResult::RedirectUrl(
outlk_ad.config_redirecturl.unwrap(),
))
}
}
pub fn outlk_autodiscover(
context: &Context,
url: &str,
@@ -122,16 +27,94 @@ pub fn outlk_autodiscover(
let mut url = url.to_string();
/* Follow up to 10 xml-redirects (http-redirects are followed in read_autoconf_file() */
for _i in 0..10 {
let mut outlk_ad = OutlookAutodiscover {
out: LoginParam::new(),
out_imap_set: false,
out_smtp_set: false,
config_type: None,
config_server: String::new(),
config_port: 0,
config_ssl: String::new(),
config_redirecturl: None,
};
if let Some(xml_raw) = read_autoconf_file(context, &url) {
match outlk_parse_xml(&xml_raw) {
Err(err) => {
warn!(context, "{}", err);
let mut reader = quick_xml::Reader::from_str(&xml_raw);
reader.trim_text(true);
let mut buf = Vec::new();
let mut current_tag: Option<String> = None;
loop {
match reader.read_event(&mut buf) {
Ok(quick_xml::events::Event::Start(ref e)) => {
let tag = String::from_utf8_lossy(e.name()).trim().to_lowercase();
if tag == "protocol" {
outlk_ad.config_type = None;
outlk_ad.config_server = String::new();
outlk_ad.config_port = 0;
outlk_ad.config_ssl = String::new();
outlk_ad.config_redirecturl = None;
current_tag = None;
} else {
current_tag = Some(tag);
}
}
Ok(quick_xml::events::Event::End(ref e)) => {
outlk_autodiscover_endtag_cb(e, &mut outlk_ad);
current_tag = None;
}
Ok(quick_xml::events::Event::Text(ref e)) => {
let val = e.unescape_and_decode(&reader).unwrap_or_default();
if let Some(ref tag) = current_tag {
match tag.as_str() {
"type" => outlk_ad.config_type = Some(val.trim().to_string()),
"server" => outlk_ad.config_server = val.trim().to_string(),
"port" => {
outlk_ad.config_port = val.trim().parse().unwrap_or_default()
}
"ssl" => outlk_ad.config_ssl = val.trim().to_string(),
"redirecturl" => {
outlk_ad.config_redirecturl = Some(val.trim().to_string())
}
_ => {}
};
}
}
Err(e) => {
error!(
context,
"Configure xml: Error at position {}: {:?}",
reader.buffer_position(),
e
);
}
Ok(quick_xml::events::Event::Eof) => break,
_ => (),
}
buf.clear();
}
// XML redirect via redirecturl
if outlk_ad.config_redirecturl.is_none()
|| outlk_ad.config_redirecturl.as_ref().unwrap().is_empty()
{
if outlk_ad.out.mail_server.is_empty()
|| outlk_ad.out.mail_port == 0
|| outlk_ad.out.send_server.is_empty()
|| outlk_ad.out.send_port == 0
{
let r = outlk_ad.out.to_string();
warn!(context, "Bad or incomplete autoconfig: {}", r,);
return None;
}
Ok(res) => match res {
ParsingResult::RedirectUrl(redirect_url) => url = redirect_url,
ParsingResult::LoginParam(login_param) => return Some(login_param),
},
return Some(outlk_ad.out);
} else {
url = outlk_ad.config_redirecturl.unwrap();
}
} else {
return None;
@@ -172,78 +155,3 @@ fn outlk_autodiscover_endtag_cb(event: &BytesEnd, outlk_ad: &mut OutlookAutodisc
}
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_parse_redirect() {
let res = outlk_parse_xml("
<?xml version=\"1.0\" encoding=\"utf-8\"?>
<Autodiscover xmlns=\"http://schemas.microsoft.com/exchange/autodiscover/responseschema/2006\">
<Response xmlns=\"http://schemas.microsoft.com/exchange/autodiscover/outlook/responseschema/2006a\">
<Account>
<AccountType>email</AccountType>
<Action>redirectUrl</Action>
<RedirectUrl>https://mail.example.com/autodiscover/autodiscover.xml</RedirectUrl>
</Account>
</Response>
</Autodiscover>
").expect("XML is not parsed successfully");
match res {
ParsingResult::LoginParam(_lp) => {
panic!("redirecturl is not found");
}
ParsingResult::RedirectUrl(url) => {
assert_eq!(
url,
"https://mail.example.com/autodiscover/autodiscover.xml"
);
}
}
}
#[test]
fn test_parse_loginparam() {
let res = outlk_parse_xml(
"\
<?xml version=\"1.0\" encoding=\"utf-8\"?>
<Autodiscover xmlns=\"http://schemas.microsoft.com/exchange/autodiscover/responseschema/2006\">
<Response xmlns=\"http://schemas.microsoft.com/exchange/autodiscover/outlook/responseschema/2006a\">
<Account>
<AccountType>email</AccountType>
<Action>settings</Action>
<Protocol>
<Type>IMAP</Type>
<Server>example.com</Server>
<Port>993</Port>
<SSL>on</SSL>
<AuthRequired>on</AuthRequired>
</Protocol>
<Protocol>
<Type>SMTP</Type>
<Server>smtp.example.com</Server>
<Port>25</Port>
<SSL>off</SSL>
<AuthRequired>on</AuthRequired>
</Protocol>
</Account>
</Response>
</Autodiscover>",
)
.expect("XML is not parsed successfully");
match res {
ParsingResult::LoginParam(lp) => {
assert_eq!(lp.mail_server, "example.com");
assert_eq!(lp.mail_port, 993);
assert_eq!(lp.send_server, "smtp.example.com");
assert_eq!(lp.send_port, 25);
}
ParsingResult::RedirectUrl(_) => {
panic!("RedirectUrl is not expected");
}
}
}
}

View File

@@ -112,7 +112,6 @@ pub fn dc_job_do_DC_JOB_CONFIGURE_IMAP(context: &Context) {
dc_get_oauth2_addr(context, &param.addr, &param.mail_pw)
.and_then(|e| e.parse().ok())
{
info!(context, "Authorized address is {}", oauth2_addr);
param.addr = oauth2_addr;
context
.sql
@@ -421,16 +420,6 @@ pub fn dc_job_do_DC_JOB_CONFIGURE_IMAP(context: &Context) {
);
}
*/
// remember the entered parameters on success
// and restore to last-entered on failure.
// this way, the parameters visible to the ui are always in-sync with the current configuration.
if success {
LoginParam::from_database(context, "").save_to_database(context, "configured_raw_");
} else {
LoginParam::from_database(context, "configured_raw_").save_to_database(context, "");
}
context.free_ongoing();
progress!(context, if success { 1000 } else { 0 });
}
@@ -541,26 +530,21 @@ fn try_smtp_one_param(context: &Context, param: &LoginParam) -> Option<bool> {
param.send_user, param.send_server, param.send_port, param.server_flags
);
info!(context, "Trying: {}", inf);
match context
if context
.smtp
.clone()
.lock()
.unwrap()
.connect(context, &param)
{
Ok(()) => {
info!(context, "success: {}", inf);
Some(true)
}
Err(err) => {
if context.shall_stop_ongoing() {
Some(false)
} else {
warn!(context, "could not connect: {}", err);
None
}
}
info!(context, "success: {}", inf);
return Some(true);
}
if context.shall_stop_ongoing() {
return Some(false);
}
info!(context, "could not connect: {}", inf);
None
}
/*******************************************************************************
@@ -569,7 +553,7 @@ fn try_smtp_one_param(context: &Context, param: &LoginParam) -> Option<bool> {
pub fn dc_connect_to_configured_imap(context: &Context, imap: &Imap) -> libc::c_int {
let mut ret_connected = 0;
if async_std::task::block_on(async move { imap.is_connected().await }) {
if imap.is_connected() {
ret_connected = 1
} else if !context.sql.get_raw_config_bool(context, "configured") {
warn!(context, "Not configured, cannot connect.",);

View File

@@ -44,20 +44,6 @@ impl Default for Blocked {
}
}
#[derive(Debug, Display, Clone, Copy, PartialEq, Eq, FromPrimitive, ToPrimitive, FromSql, ToSql)]
#[repr(u8)]
pub enum ShowEmails {
Off = 0,
AcceptedContacts = 1,
All = 2,
}
impl Default for ShowEmails {
fn default() -> Self {
ShowEmails::Off // also change Config.ShowEmails props(default) on changes
}
}
pub const DC_IMAP_SEEN: u32 = 0x1;
pub const DC_HANDSHAKE_CONTINUE_NORMAL_PROCESSING: i32 = 0x01;
@@ -68,7 +54,7 @@ pub const DC_GCL_ARCHIVED_ONLY: usize = 0x01;
pub const DC_GCL_NO_SPECIALS: usize = 0x02;
pub const DC_GCL_ADD_ALLDONE_HINT: usize = 0x04;
pub const DC_GCM_ADDDAYMARKER: u32 = 0x01;
const DC_GCM_ADDDAYMARKER: usize = 0x01;
pub const DC_GCL_VERIFIED_ONLY: usize = 0x01;
pub const DC_GCL_ADD_SELF: usize = 0x02;
@@ -120,7 +106,7 @@ impl Default for Chattype {
}
pub const DC_MSG_ID_MARKER1: u32 = 1;
pub const DC_MSG_ID_DAYMARKER: u32 = 9;
const DC_MSG_ID_DAYMARKER: u32 = 9;
pub const DC_MSG_ID_LAST_SPECIAL: u32 = 9;
/// approx. max. length returned by dc_msg_get_text()
@@ -130,17 +116,11 @@ const DC_MAX_GET_INFO_LEN: usize = 100000;
pub const DC_CONTACT_ID_UNDEFINED: u32 = 0;
pub const DC_CONTACT_ID_SELF: u32 = 1;
pub const DC_CONTACT_ID_INFO: u32 = 2;
pub const DC_CONTACT_ID_DEVICE: u32 = 5;
pub const DC_CONTACT_ID_DEVICE: u32 = 2;
pub const DC_CONTACT_ID_LAST_SPECIAL: u32 = 9;
pub const DC_CREATE_MVBOX: usize = 1;
// Flags for empty server job
pub const DC_EMPTY_MVBOX: u32 = 0x01;
pub const DC_EMPTY_INBOX: u32 = 0x02;
// Flags for configuring IMAP and SMTP servers.
// These flags are optional
// and may be set together with the username, password etc.
@@ -271,6 +251,11 @@ const DC_ERROR_SEE_STRING: usize = 0; // deprecated;
const DC_ERROR_SELF_NOT_IN_GROUP: usize = 1; // deprecated;
const DC_STR_SELFNOTINGRP: usize = 21; // deprecated;
/// Values for dc_get|set_config("show_emails")
const DC_SHOW_EMAILS_OFF: usize = 0;
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

View File

@@ -14,7 +14,7 @@ use crate::error::Result;
use crate::events::Event;
use crate::key::*;
use crate::login_param::LoginParam;
use crate::message::{MessageState, MsgId};
use crate::message::MessageState;
use crate::peerstate::*;
use crate::sql;
use crate::stock::StockMessage;
@@ -153,16 +153,7 @@ impl Contact {
blocked: false,
origin: Origin::Unknown,
};
return Ok(contact);
} else if contact_id == DC_CONTACT_ID_DEVICE {
let contact = Contact {
id: contact_id,
name: context.stock_str(StockMessage::DeviceMessages).into(),
authname: "".into(),
addr: "device@localhost".into(),
blocked: false,
origin: Origin::Unknown,
};
return Ok(contact);
}
@@ -252,7 +243,7 @@ impl Contact {
{
context.call_cb(Event::MsgsChanged {
chat_id: 0,
msg_id: MsgId::new(0),
msg_id: 0,
});
}
}
@@ -273,7 +264,7 @@ impl Contact {
.unwrap_or_default();
if addr_normalized == addr_self {
return DC_CONTACT_ID_SELF;
return 1;
}
context.sql.query_get_value(
@@ -310,7 +301,7 @@ impl Contact {
.unwrap_or_default();
if addr == addr_self {
return Ok((DC_CONTACT_ID_SELF, sth_modified));
return Ok((1, sth_modified));
}
if !may_be_valid_addr(&addr) {

View File

@@ -1,5 +1,7 @@
use std::collections::HashMap;
use std::ffi::OsString;
use std::fs;
use std::io::Write;
use std::path::{Path, PathBuf};
use std::sync::{Arc, Condvar, Mutex, RwLock};
@@ -9,6 +11,7 @@ use crate::chat::*;
use crate::config::Config;
use crate::constants::*;
use crate::contact::*;
use crate::dc_tools::{dc_copy_file, dc_derive_safe_stem_ext};
use crate::error::*;
use crate::events::Event;
use crate::imap::*;
@@ -17,10 +20,11 @@ use crate::job_thread::JobThread;
use crate::key::*;
use crate::login_param::LoginParam;
use crate::lot::Lot;
use crate::message::{self, Message, MsgId};
use crate::message::{self, Message};
use crate::param::Params;
use crate::smtp::*;
use crate::sql::Sql;
use rand::{thread_rng, Rng};
/// Callback function type for [Context]
///
@@ -146,7 +150,7 @@ impl Context {
};
ensure!(
ctx.sql.open(&ctx, &ctx.dbfile, false),
ctx.sql.open(&ctx, &ctx.dbfile, 0),
"Failed opening sqlite database"
);
@@ -161,6 +165,59 @@ impl Context {
self.blobdir.as_path()
}
pub fn copy_to_blobdir(&self, orig_filename: impl AsRef<str>) -> Result<String> {
// return a $BLOBDIR/<filename> with the content of orig_filename
// copied into it. The <filename> will be safely derived from
// orig_filename, and will not clash with existing filenames.
let dest = self.new_blob_file(&orig_filename, b"")?;
if dc_copy_file(
&self,
PathBuf::from(orig_filename.as_ref()),
PathBuf::from(&dest),
) {
Ok(dest)
} else {
bail!("could not copy {} to {}", orig_filename.as_ref(), dest);
}
}
pub fn new_blob_file(&self, orig_filename: impl AsRef<str>, data: &[u8]) -> Result<String> {
// return a $BLOBDIR/<FILENAME> string which corresponds to the
// respective file in the blobdir, and which contains the data.
// FILENAME is computed by looking and possibly mangling the
// basename of orig_filename. The resulting filenames are meant
// to be human-readable.
let (stem, ext) = dc_derive_safe_stem_ext(orig_filename.as_ref());
// ext starts with "." or is empty string, so we can always resconstruct
for i in 0..3 {
let candidate_basename = match i {
// first a try to just use the (possibly mangled) original basename
0 => format!("{}{}", stem, ext),
// otherwise extend stem with random numbers
_ => {
let mut rng = thread_rng();
let random_id: u32 = rng.gen();
format!("{}-{}{}", stem, random_id, ext)
}
};
let path = self.get_blobdir().join(&candidate_basename);
if let Ok(mut file) = fs::OpenOptions::new()
.create_new(true)
.write(true)
.open(&path)
{
file.write_all(data)?;
let db_entry = format!("$BLOBDIR/{}", candidate_basename);
self.call_cb(Event::NewBlobFile(db_entry.clone()));
return Ok(db_entry);
}
}
bail!("out of luck to create new blob file");
}
pub fn call_cb(&self, event: Event) -> uintptr_t {
(*self.cb)(self, event)
}
@@ -313,30 +370,24 @@ impl Context {
res
}
pub fn get_fresh_msgs(&self) -> Vec<MsgId> {
pub fn get_fresh_msgs(&self) -> Vec<u32> {
let show_deaddrop = 0;
self.sql
.query_map(
concat!(
"SELECT m.id",
" FROM msgs m",
" LEFT JOIN contacts ct",
" ON m.from_id=ct.id",
" LEFT JOIN chats c",
" ON m.chat_id=c.id",
" WHERE m.state=?",
" AND m.hidden=0",
" AND m.chat_id>?",
" AND ct.blocked=0",
" AND (c.blocked=0 OR c.blocked=?)",
" ORDER BY m.timestamp DESC,m.id DESC;"
),
"SELECT m.id FROM msgs m LEFT JOIN contacts ct \
ON m.from_id=ct.id LEFT JOIN chats c ON m.chat_id=c.id WHERE m.state=? \
AND m.hidden=0 \
AND m.chat_id>? \
AND ct.blocked=0 \
AND (c.blocked=0 OR c.blocked=?) ORDER BY m.timestamp DESC,m.id DESC;",
&[10, 9, if 0 != show_deaddrop { 2 } else { 0 }],
|row| row.get::<_, MsgId>(0),
|row| row.get(0),
|rows| {
let mut ret = Vec::new();
for row in rows {
ret.push(row?);
let id: u32 = row?;
ret.push(id);
}
Ok(ret)
},
@@ -345,7 +396,7 @@ impl Context {
}
#[allow(non_snake_case)]
pub fn search_msgs(&self, chat_id: u32, query: impl AsRef<str>) -> Vec<MsgId> {
pub fn search_msgs(&self, chat_id: u32, query: impl AsRef<str>) -> Vec<u32> {
let real_query = query.as_ref().trim();
if real_query.is_empty() {
return Vec::new();
@@ -354,43 +405,25 @@ impl Context {
let strLikeBeg = format!("{}%", real_query);
let query = if 0 != chat_id {
concat!(
"SELECT m.id AS id, m.timestamp AS timestamp",
" FROM msgs m",
" LEFT JOIN contacts ct",
" ON m.from_id=ct.id",
" WHERE m.chat_id=?",
" AND m.hidden=0",
" AND ct.blocked=0",
" AND (txt LIKE ? OR ct.name LIKE ?)",
" ORDER BY m.timestamp,m.id;"
)
"SELECT m.id, m.timestamp FROM msgs m LEFT JOIN contacts ct ON m.from_id=ct.id WHERE m.chat_id=? \
AND m.hidden=0 \
AND ct.blocked=0 AND (txt LIKE ? OR ct.name LIKE ?) ORDER BY m.timestamp,m.id;"
} else {
concat!(
"SELECT m.id AS id, m.timestamp AS timestamp",
" FROM msgs m",
" LEFT JOIN contacts ct",
" ON m.from_id=ct.id",
" LEFT JOIN chats c",
" ON m.chat_id=c.id",
" WHERE m.chat_id>9",
" AND m.hidden=0",
" AND (c.blocked=0 OR c.blocked=?)",
" AND ct.blocked=0",
" AND (m.txt LIKE ? OR ct.name LIKE ?)",
" ORDER BY m.timestamp DESC,m.id DESC;"
)
"SELECT m.id, m.timestamp FROM msgs m LEFT JOIN contacts ct ON m.from_id=ct.id \
LEFT JOIN chats c ON m.chat_id=c.id WHERE m.chat_id>9 AND m.hidden=0 \
AND (c.blocked=0 OR c.blocked=?) \
AND ct.blocked=0 AND (m.txt LIKE ? OR ct.name LIKE ?) ORDER BY m.timestamp DESC,m.id DESC;"
};
self.sql
.query_map(
query,
params![chat_id as i32, &strLikeInText, &strLikeBeg],
|row| row.get::<_, MsgId>("id"),
|row| row.get::<_, i32>(0),
|rows| {
let mut ret = Vec::new();
for id in rows {
ret.push(id?);
ret.push(id? as u32);
}
Ok(ret)
},
@@ -421,7 +454,7 @@ impl Context {
}
}
pub fn do_heuristics_moves(&self, folder: &str, msg_id: MsgId) {
pub fn do_heuristics_moves(&self, folder: &str, msg_id: u32) {
if !self.get_config_bool(Config::MvboxMove) {
return;
}
@@ -446,7 +479,7 @@ impl Context {
job_add(
self,
Action::MoveMsg,
msg.id.to_u32() as i32,
msg.id as libc::c_int,
Params::new(),
0,
);
@@ -503,6 +536,7 @@ pub fn get_version_str() -> &'static str {
mod tests {
use super::*;
use crate::dc_tools::*;
use crate::test_utils::*;
#[test]
@@ -540,6 +574,51 @@ mod tests {
assert!(res.is_err());
}
#[test]
fn test_new_blob_file() {
let t = dummy_context();
let context = t.ctx;
let x = &context.new_blob_file("hello", b"data").unwrap();
assert!(dc_file_exist(&context, x));
assert!(x.starts_with("$BLOBDIR"));
assert!(dc_read_file(&context, x).unwrap() == b"data");
let y = &context.new_blob_file("hello", b"data").unwrap();
assert!(dc_file_exist(&context, y));
assert!(y.starts_with("$BLOBDIR/hello-"));
let x = &context.new_blob_file("xyz/hello.png", b"data").unwrap();
assert!(dc_file_exist(&context, x));
assert_eq!(x, "$BLOBDIR/hello.png");
let y = &context.new_blob_file("hello\\world.png", b"data").unwrap();
assert!(dc_file_exist(&context, y));
assert_eq!(y, "$BLOBDIR/world.png");
}
#[test]
fn test_new_blob_file_long_names() {
let t = dummy_context();
let context = t.ctx;
let s = "12312312039182039182039812039810293810293810293810293801293801293123123";
let x = &context.new_blob_file(s, b"data").unwrap();
println!("blobfilename '{}'", x);
println!("xxxxfilename '{}'", s);
assert!(x.len() < s.len());
assert!(dc_file_exist(&context, x));
assert!(x.starts_with("$BLOBDIR"));
}
#[test]
fn test_new_blob_file_unicode() {
let t = dummy_context();
let context = t.ctx;
let s = "helloäworld.qwe";
let x = &context.new_blob_file(s, b"data").unwrap();
assert_eq!(x, "$BLOBDIR/hello-world.qwe");
assert_eq!(dc_read_file(&context, x).unwrap(), b"data");
}
#[test]
fn test_sqlite_parent_not_exists() {
let tmp = tempfile::tempdir().unwrap();

View File

@@ -13,7 +13,6 @@ use mmime::mailmime::types::*;
use mmime::mailmime::*;
use mmime::other::*;
use crate::blob::BlobObject;
use crate::constants::Viewtype;
use crate::contact::*;
use crate::context::Context;
@@ -117,30 +116,18 @@ impl<'a> MimeParser<'a> {
);
if r == MAILIMF_NO_ERROR as libc::c_int && !self.mimeroot.is_null() {
match e2ee::try_decrypt(self.context, self.mimeroot) {
Ok((encrypted, signatures, gossipped_addr)) => {
self.encrypted = encrypted;
self.signatures = signatures;
self.gossipped_addr = gossipped_addr;
}
Err(err) => {
// continue with the current, still encrypted, mime tree.
// unencrypted parts will be replaced by an error message
// that is added as "the message" to the chat then.
//
// if we just return here, the header is missing
// and the caller cannot display the message
// and try to assign the message to a chat
warn!(self.context, "decryption failed: {}", err);
}
}
let (encrypted, signatures, gossipped_addr) =
e2ee::try_decrypt(self.context, self.mimeroot)?;
self.encrypted = encrypted;
self.signatures = signatures;
self.gossipped_addr = gossipped_addr;
self.parse_mime_recursive(self.mimeroot);
if let Some(field) = self.lookup_field("Subject") {
if (*field).fld_type == MAILIMF_FIELD_SUBJECT as libc::c_int {
let subj = (*(*field).fld_data.fld_subject).sbj_value;
self.subject = to_opt_string_lossy(subj).map(|x| dc_decode_header_words(&x));
self.subject = as_opt_str(subj).map(dc_decode_header_words);
}
}
@@ -611,7 +598,7 @@ impl<'a> MimeParser<'a> {
let mut decoded_data = match wrapmime::mailmime_transfer_decode(mime) {
Ok(decoded_data) => decoded_data,
Err(_) => {
// Note that it's not always an error - might be no data
// Note that it's now always an error - might be no data
return false;
}
};
@@ -620,8 +607,12 @@ impl<'a> MimeParser<'a> {
/* regard `Content-Transfer-Encoding:` */
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()
@@ -642,21 +633,20 @@ impl<'a> MimeParser<'a> {
self.context,
"Cannot convert {} bytes from \"{}\" to \"utf-8\".",
decoded_data.len(),
to_string_lossy(charset),
as_str(charset),
);
}
}
/* 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;
simplifier.simplify(&input, is_html, is_msgrmsg)
simplifier.unwrap().simplify(&input, is_html, is_msgrmsg)
};
if !simplified_txt.is_empty() {
let mut part = Part::default();
@@ -668,7 +658,7 @@ impl<'a> MimeParser<'a> {
self.do_add_single_part(part);
}
if simplifier.is_forwarded {
if simplifier.unwrap().is_forwarded {
self.is_forwarded = true;
}
}
@@ -741,21 +731,33 @@ impl<'a> MimeParser<'a> {
if !(*mime).mm_content_type.is_null()
&& !(*(*mime).mm_content_type).ct_subtype.is_null()
{
desired_filename = format!(
"file.{}",
to_string_lossy((*(*mime).mm_content_type).ct_subtype)
);
desired_filename =
format!("file.{}", as_str((*(*mime).mm_content_type).ct_subtype));
} else {
return false;
}
}
self.do_add_single_file_part(
msg_type,
mime_type,
raw_mime.as_ref(),
&decoded_data,
&desired_filename,
);
if desired_filename.starts_with("location") && desired_filename.ends_with(".kml") {
if !decoded_data.is_empty() {
let d = std::string::String::from_utf8_lossy(&decoded_data);
self.location_kml = location::Kml::parse(self.context, &d).ok();
}
} else if desired_filename.starts_with("message")
&& desired_filename.ends_with(".kml")
{
if !decoded_data.is_empty() {
let d = std::string::String::from_utf8_lossy(&decoded_data);
self.message_kml = location::Kml::parse(self.context, &d).ok();
}
} else if !decoded_data.is_empty() {
self.do_add_single_file_part(
msg_type,
mime_type,
raw_mime.as_ref(),
&decoded_data,
&desired_filename,
);
}
}
_ => {}
}
@@ -769,49 +771,25 @@ impl<'a> MimeParser<'a> {
mime_type: libc::c_int,
raw_mime: Option<&String>,
decoded_data: &[u8],
filename: &str,
desired_filename: &str,
) {
if decoded_data.is_empty() {
return;
}
// treat location/message kml file attachments specially
if filename.ends_with(".kml") {
// XXX what if somebody sends eg an "location-highlights.kml"
// attachment unrelated to location streaming?
if filename.starts_with("location") || filename.starts_with("message") {
let parsed = location::Kml::parse(self.context, decoded_data)
.map_err(|err| {
warn!(self.context, "failed to parse kml part: {}", err);
})
.ok();
if filename.starts_with("location") {
self.location_kml = parsed;
} else {
self.message_kml = parsed;
}
return;
}
}
/* we have a regular file attachment,
write decoded data to new blob object */
let blob = match BlobObject::create(self.context, filename, decoded_data) {
Ok(blob) => blob,
/* write decoded data to new blob file */
let bpath = match self.context.new_blob_file(desired_filename, decoded_data) {
Ok(path) => path,
Err(err) => {
error!(
self.context,
"Could not add blob for mime part {}, error {}", filename, err
"Could not add blob for mime part {}, error {}", desired_filename, err
);
return;
}
};
/* create and register Mime part referencing the new Blob object */
let mut part = Part::default();
part.typ = msg_type;
part.mimetype = mime_type;
part.bytes = decoded_data.len() as libc::c_int;
part.param.set(Param::File, blob.as_name());
part.param.set(Param::File, bpath);
if let Some(raw_mime) = raw_mime {
part.param.set(Param::MimeType, raw_mime);
}
@@ -826,15 +804,10 @@ impl<'a> MimeParser<'a> {
}
fn do_add_single_part(&mut self, mut part: Part) {
if self.encrypted {
if self.signatures.len() > 0 {
part.param.set_int(Param::GuaranteeE2ee, 1);
} else {
// XXX if the message was encrypted but not signed
// it's not neccessarily an error we need to signal.
// we could just treat it as if it was not encrypted.
part.param.set_int(Param::ErroneousE2ee, 0x2);
}
if self.encrypted && self.signatures.len() > 0 {
part.param.set_int(Param::GuranteeE2ee, 1);
} else if self.encrypted {
part.param.set_int(Param::ErroneousE2ee, 0x2);
}
self.parts.push(part);
}
@@ -879,8 +852,7 @@ impl<'a> MimeParser<'a> {
}) as *mut mailimf_mailbox;
if !mb.is_null() {
let from_addr = to_string_lossy((*mb).mb_addr_spec);
let from_addr_norm = addr_normalize(&from_addr);
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;
@@ -1019,16 +991,15 @@ unsafe fn mailmime_get_mime_type(mime: *mut Mailmime) -> (libc::c_int, Viewtype,
}
}
let raw_mime = reconcat_mime(Some("text"), to_opt_string_lossy((*c).ct_subtype));
let raw_mime = reconcat_mime(Some("text"), as_opt_str((*c).ct_subtype));
(DC_MIMETYPE_FILE, Viewtype::File, Some(raw_mime))
}
MAILMIME_DISCRETE_TYPE_IMAGE => {
let subtype = to_opt_string_lossy((*c).ct_subtype);
let msg_type = match subtype.as_ref().map(|x| x.as_str()) {
let subtype = as_opt_str((*c).ct_subtype);
let msg_type = match subtype {
Some("gif") => Viewtype::Gif,
Some("svg+xml") => {
let raw_mime =
reconcat_mime(Some("image"), to_opt_string_lossy((*c).ct_subtype));
let raw_mime = reconcat_mime(Some("image"), as_opt_str((*c).ct_subtype));
return (DC_MIMETYPE_FILE, Viewtype::File, Some(raw_mime));
}
_ => Viewtype::Image,
@@ -1038,11 +1009,11 @@ unsafe fn mailmime_get_mime_type(mime: *mut Mailmime) -> (libc::c_int, Viewtype,
(DC_MIMETYPE_IMAGE, msg_type, Some(raw_mime))
}
MAILMIME_DISCRETE_TYPE_AUDIO => {
let raw_mime = reconcat_mime(Some("audio"), to_opt_string_lossy((*c).ct_subtype));
let raw_mime = reconcat_mime(Some("audio"), as_opt_str((*c).ct_subtype));
(DC_MIMETYPE_AUDIO, Viewtype::Audio, Some(raw_mime))
}
MAILMIME_DISCRETE_TYPE_VIDEO => {
let raw_mime = reconcat_mime(Some("video"), to_opt_string_lossy((*c).ct_subtype));
let raw_mime = reconcat_mime(Some("video"), as_opt_str((*c).ct_subtype));
(DC_MIMETYPE_VIDEO, Viewtype::Video, Some(raw_mime))
}
_ => {
@@ -1053,15 +1024,13 @@ unsafe fn mailmime_get_mime_type(mime: *mut Mailmime) -> (libc::c_int, Viewtype,
b"autocrypt-setup\x00" as *const u8 as *const libc::c_char,
) == 0i32
{
let raw_mime = reconcat_mime(None, to_opt_string_lossy((*c).ct_subtype));
let raw_mime = reconcat_mime(None, as_opt_str((*c).ct_subtype));
return (DC_MIMETYPE_AC_SETUP_FILE, Viewtype::File, Some(raw_mime));
}
let raw_mime = reconcat_mime(
to_opt_string_lossy((*(*(*c).ct_type).tp_data.tp_discrete_type).dt_extension)
.as_ref()
.map(|x| x.as_str()),
to_opt_string_lossy((*c).ct_subtype),
as_opt_str((*(*(*c).ct_type).tp_data.tp_discrete_type).dt_extension),
as_opt_str((*c).ct_subtype),
);
(DC_MIMETYPE_FILE, Viewtype::File, Some(raw_mime))
@@ -1071,9 +1040,9 @@ unsafe fn mailmime_get_mime_type(mime: *mut Mailmime) -> (libc::c_int, Viewtype,
if (*(*(*c).ct_type).tp_data.tp_composite_type).ct_type
== MAILMIME_COMPOSITE_TYPE_MULTIPART as libc::c_int
{
let subtype = to_opt_string_lossy((*c).ct_subtype);
let subtype = as_opt_str((*c).ct_subtype);
let mime_type = match subtype.as_ref().map(|x| x.as_str()) {
let mime_type = match subtype {
Some("alternative") => DC_MIMETYPE_MP_ALTERNATIVE,
Some("related") => DC_MIMETYPE_MP_RELATED,
Some("encrypted") => {
@@ -1108,9 +1077,9 @@ unsafe fn mailmime_get_mime_type(mime: *mut Mailmime) -> (libc::c_int, Viewtype,
}
}
fn reconcat_mime(typ: Option<&str>, subtype: Option<String>) -> String {
fn reconcat_mime(typ: Option<&str>, subtype: Option<&str>) -> String {
let typ = typ.unwrap_or("application");
let subtype = subtype.unwrap_or("octet-stream".to_string());
let subtype = subtype.unwrap_or("octet-stream");
format!("{}/{}", typ, subtype)
}
@@ -1254,16 +1223,15 @@ mod tests {
}
proptest! {
#[ignore]
#[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);
// parsing should always succeed
// but the returned header will normally be empty on random data
assert!(unsafe {mimeparser.parse(data.as_bytes()).is_ok()});
assert!(mimeparser.header.is_empty());
unsafe {
assert!(mimeparser.parse(data.as_bytes()).is_err());
}
}
}

View File

@@ -1,7 +1,7 @@
use std::ptr;
use itertools::join;
use libc::strcmp;
use libc::{free, strcmp};
use mmime::clist::*;
use mmime::mailimf::types::*;
use mmime::mailmime::content::*;
@@ -10,9 +10,6 @@ use mmime::mailmime::*;
use mmime::other::*;
use sha2::{Digest, Sha256};
use num_traits::FromPrimitive;
use crate::blob::BlobObject;
use crate::chat::{self, Chat};
use crate::config::Config;
use crate::constants::*;
@@ -25,7 +22,7 @@ use crate::error::Result;
use crate::events::Event;
use crate::job::*;
use crate::location;
use crate::message::{self, MessageState, MsgId};
use crate::message::{self, MessageState};
use crate::param::*;
use crate::peerstate::*;
use crate::securejoin::handle_securejoin_handshake;
@@ -65,12 +62,12 @@ pub unsafe fn dc_receive_imf(
let mut mime_parser = MimeParser::new(context);
if let Err(err) = mime_parser.parse(imf_raw) {
warn!(context, "dc_receive_imf parse error: {}", err);
error!(context, "dc_receive_imf parse error: {}", err);
};
if mime_parser.header.is_empty() {
// Error - even adding an empty record won't help as we do not know the message ID
warn!(context, "No header.");
info!(context, "No header.");
return;
}
@@ -85,7 +82,7 @@ pub unsafe fn dc_receive_imf(
let mut hidden = 0;
let mut needs_delete_job = false;
let mut insert_msg_id = MsgId::new_unset();
let mut insert_msg_id = 0;
let mut sent_timestamp = 0;
let mut created_db_entries = Vec::new();
@@ -97,17 +94,17 @@ pub unsafe fn dc_receive_imf(
// helper method to handle early exit and memory cleanup
let cleanup = |context: &Context,
create_event_to_send: &Option<CreateEvent>,
created_db_entries: &Vec<(usize, MsgId)>,
rr_event_to_send: &Vec<(u32, MsgId)>| {
created_db_entries: &Vec<(usize, usize)>,
rr_event_to_send: &Vec<(u32, u32)>| {
if let Some(create_event_to_send) = create_event_to_send {
for (chat_id, msg_id) in created_db_entries {
let event = match create_event_to_send {
CreateEvent::MsgsChanged => Event::MsgsChanged {
msg_id: *msg_id,
msg_id: *msg_id as u32,
chat_id: *chat_id as u32,
},
CreateEvent::IncomingMsg => Event::IncomingMsg {
msg_id: *msg_id,
msg_id: *msg_id as u32,
chat_id: *chat_id as u32,
},
};
@@ -227,7 +224,7 @@ pub unsafe fn dc_receive_imf(
&mut created_db_entries,
&mut create_event_to_send,
) {
warn!(context, "{}", err);
info!(context, "{}", err);
cleanup(
context,
@@ -257,7 +254,7 @@ pub unsafe fn dc_receive_imf(
);
}
if mime_parser.location_kml.is_some() || mime_parser.message_kml.is_some() {
if !mime_parser.message_kml.is_none() && chat_id > DC_CHAT_ID_LAST_SPECIAL {
save_locations(
context,
&mime_parser,
@@ -273,7 +270,7 @@ pub unsafe fn dc_receive_imf(
job_add(
context,
Action::DeleteMsgOnImap,
created_db_entries[0].1.to_u32() as i32,
created_db_entries[0].1 as i32,
Params::new(),
0,
);
@@ -313,8 +310,8 @@ unsafe fn add_parts(
flags: u32,
needs_delete_job: &mut bool,
to_self: i32,
insert_msg_id: &mut MsgId,
created_db_entries: &mut Vec<(usize, MsgId)>,
insert_msg_id: &mut u32,
created_db_entries: &mut Vec<(usize, usize)>,
create_event_to_send: &mut Option<CreateEvent>,
) -> Result<()> {
let mut state: MessageState;
@@ -322,8 +319,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",
@@ -357,6 +359,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");
}
@@ -369,14 +372,12 @@ unsafe fn add_parts(
// incoming non-chat messages may be discarded;
// maybe this can be optimized later, by checking the state before the message body is downloaded
let mut allow_creation = 1;
let show_emails =
ShowEmails::from_i32(context.get_config_int(Config::ShowEmails)).unwrap_or_default();
if mime_parser.is_system_message != SystemMessage::AutocryptSetupMessage && msgrmsg == 0 {
// this message is a classic email not a chat-message nor a reply to one
if show_emails == ShowEmails::Off {
*chat_id = DC_CHAT_ID_TRASH;
let show_emails = context.get_config_int(Config::ShowEmails);
if show_emails == 0 {
*chat_id = 3;
allow_creation = 0
} else if show_emails == ShowEmails::AcceptedContacts {
} else if show_emails == 1 {
allow_creation = 0
}
}
@@ -391,7 +392,7 @@ unsafe fn add_parts(
} else {
MessageState::InFresh
};
*to_id = DC_CONTACT_ID_SELF;
*to_id = 1;
// handshake messages must be processed _before_ chats are created
// (eg. contacs may be marked as verified)
if mime_parser.lookup_field("Secure-Join").is_some() {
@@ -444,7 +445,7 @@ unsafe fn add_parts(
if *chat_id == 0 {
// check if the message belongs to a mailing list
if mime_parser.is_mailinglist_message() {
*chat_id = DC_CHAT_ID_TRASH;
*chat_id = 3;
info!(context, "Message belongs to a mailing list and is ignored.",);
}
}
@@ -491,13 +492,12 @@ unsafe fn add_parts(
}
// if the chat_id is blocked,
// for unknown senders and non-delta-messages set the state to NOTICED
// to not result in a chatlist-contact-request (this would require the state FRESH)
// 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
&& show_emails != ShowEmails::All
{
state = MessageState::InNoticed;
}
@@ -551,9 +551,8 @@ unsafe fn add_parts(
if to_ids.is_empty() && 0 != to_self {
// from_id==to_id==DC_CONTACT_ID_SELF - this is a self-sent messages,
// maybe an Autocrypt Setup Messag
let (id, bl) =
chat::create_or_lookup_by_contact_id(context, DC_CONTACT_ID_SELF, Blocked::Not)
.unwrap_or_default();
let (id, bl) = chat::create_or_lookup_by_contact_id(context, 1, Blocked::Not)
.unwrap_or_default();
*chat_id = id;
chat_id_blocked = bl;
@@ -589,14 +588,20 @@ unsafe fn add_parts(
if let Some(field) = mime_parser.lookup_field_typ("In-Reply-To", MAILIMF_FIELD_IN_REPLY_TO) {
let fld_in_reply_to = (*field).fld_data.fld_in_reply_to;
if !fld_in_reply_to.is_null() {
mime_in_reply_to = dc_str_from_clist((*(*field).fld_data.fld_in_reply_to).mid_list, " ")
mime_in_reply_to = dc_str_from_clist(
(*(*field).fld_data.fld_in_reply_to).mid_list,
b" \x00" as *const u8 as *const libc::c_char,
)
}
}
if let Some(field) = mime_parser.lookup_field_typ("References", MAILIMF_FIELD_REFERENCES) {
let fld_references = (*field).fld_data.fld_references;
if !fld_references.is_null() {
mime_references = dc_str_from_clist((*(*field).fld_data.fld_references).mid_list, " ")
mime_references = dc_str_from_clist(
(*(*field).fld_data.fld_references).mid_list,
b" \x00" as *const u8 as *const libc::c_char,
)
}
}
@@ -605,84 +610,99 @@ unsafe fn add_parts(
// into only one message; mails sent by other clients may result in several messages
// (eg. one per attachment))
let icnt = mime_parser.parts.len();
let mut txt_raw = None;
context.sql.prepare(
"INSERT INTO msgs \
(rfc724_mid, server_folder, server_uid, chat_id, from_id, to_id, timestamp, \
timestamp_sent, timestamp_rcvd, type, state, msgrmsg, txt, txt_raw, param, \
bytes, hidden, mime_headers, mime_in_reply_to, mime_references) \
VALUES (?,?,?,?,?,?, ?,?,?,?,?,?, ?,?,?,?,?,?, ?,?);",
|mut stmt, conn| {
for i in 0..icnt {
let part = &mut mime_parser.parts[i];
if part.is_meta {
continue;
}
context
.sql
.prepare(
"INSERT INTO msgs \
(rfc724_mid, server_folder, server_uid, chat_id, from_id, to_id, timestamp, \
timestamp_sent, timestamp_rcvd, type, state, msgrmsg, txt, txt_raw, param, \
bytes, hidden, mime_headers, mime_in_reply_to, mime_references) \
VALUES (?,?,?,?,?,?, ?,?,?,?,?,?, ?,?,?,?,?,?, ?,?);",
|mut stmt, conn| {
for i in 0..icnt {
let part = &mut mime_parser.parts[i];
if part.is_meta {
continue;
}
if let Some(ref msg) = part.msg {
if mime_parser.location_kml.is_some()
&& icnt == 1
&& (msg == "-location-" || msg.is_empty())
{
*hidden = 1;
if state == MessageState::InFresh {
state = MessageState::InNoticed;
if let Some(ref msg) = part.msg {
if mime_parser.location_kml.is_some()
&& icnt == 1
&& (msg == "-location-" || msg.is_empty())
{
*hidden = 1;
if state == MessageState::InFresh {
state = MessageState::InNoticed;
}
}
}
}
if part.typ == Viewtype::Text {
let msg_raw = part.msg_raw.as_ref().cloned().unwrap_or_default();
let subject = mime_parser
.subject
.as_ref()
.map(|s| s.to_string())
.unwrap_or("".into());
txt_raw = Some(format!("{}\n\n{}", subject, msg_raw));
}
if mime_parser.is_system_message != SystemMessage::Unknown {
part.param
.set_int(Param::Cmd, mime_parser.is_system_message as i32);
}
if part.typ == Viewtype::Text {
let msg_raw = part.msg_raw.as_ref().cloned().unwrap_or_default();
let subject = mime_parser
.subject
.as_ref()
.map(|s| s.to_string())
.unwrap_or("".into());
txt_raw = Some(format!("{}\n\n{}", subject, msg_raw));
}
if mime_parser.is_system_message != SystemMessage::Unknown {
part.param
.set_int(Param::Cmd, mime_parser.is_system_message as i32);
}
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,
])?;
/*
info!(
context,
"received mime message {:?}",
String::from_utf8_lossy(std::slice::from_raw_parts(
imf_raw_not_terminated as *const u8,
imf_raw_bytes,
))
);
*/
txt_raw = None;
let row_id =
sql::get_rowid_with_conn(context, conn, "msgs", "rfc724_mid", &rfc724_mid);
*insert_msg_id = MsgId::new(row_id);
created_db_entries.push((*chat_id as usize, *insert_msg_id));
}
Ok(())
},
)?;
stmt.execute(params![
rfc724_mid,
server_folder.as_ref(),
server_uid as libc::c_int,
*chat_id as libc::c_int,
*from_id as libc::c_int,
*to_id as libc::c_int,
sort_timestamp,
*sent_timestamp,
rcvd_timestamp,
part.typ,
state,
msgrmsg,
part.msg.as_ref().map_or("", String::as_str),
// txt_raw might contain invalid utf8
txt_raw.unwrap_or_default(),
part.param.to_string(),
part.bytes,
*hidden,
if save_mime_headers {
Some(String::from_utf8_lossy(imf_raw))
} else {
None
},
to_string_lossy(mime_in_reply_to),
to_string_lossy(mime_references),
])?;
txt_raw = None;
*insert_msg_id =
sql::get_rowid_with_conn(context, conn, "msgs", "rfc724_mid", &rfc724_mid);
created_db_entries.push((*chat_id as usize, *insert_msg_id as usize));
}
Ok(())
},
)
.map_err(|err| {
cleanup(mime_in_reply_to, mime_references);
err
})?;
info!(
context,
@@ -702,6 +722,8 @@ unsafe fn add_parts(
}
}
cleanup(mime_in_reply_to, mime_references);
Ok(())
}
@@ -711,7 +733,7 @@ unsafe fn handle_reports(
mime_parser: &MimeParser,
from_id: u32,
sent_timestamp: i64,
rr_event_to_send: &mut Vec<(u32, MsgId)>,
rr_event_to_send: &mut Vec<(u32, u32)>,
server_folder: impl AsRef<str>,
server_uid: u32,
) {
@@ -799,18 +821,23 @@ 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(
&to_string_lossy((*of_org_msgid).fld_value),
) {
if let Some((chat_id, msg_id)) = message::mdn_from_ext(
if let Ok(rfc724_mid) = wrapmime::parse_message_id(as_str(
(*of_org_msgid).fld_value,
)) {
let mut chat_id_0 = 0;
let mut msg_id = 0;
if message::mdn_from_ext(
context,
from_id,
&rfc724_mid,
sent_timestamp,
&mut chat_id_0,
&mut msg_id,
) {
rr_event_to_send.push((chat_id, msg_id));
mdn_consumed = 1;
rr_event_to_send.push((chat_id_0, msg_id));
}
mdn_consumed = (msg_id != 0) as libc::c_int;
}
}
}
@@ -838,19 +865,16 @@ fn save_locations(
mime_parser: &MimeParser,
chat_id: u32,
from_id: u32,
insert_msg_id: MsgId,
insert_msg_id: u32,
hidden: i32,
) {
if chat_id <= DC_CHAT_ID_LAST_SPECIAL {
return ();
}
let mut location_id_written = false;
let mut send_event = false;
if mime_parser.message_kml.is_some() {
if !mime_parser.message_kml.is_none() && chat_id > DC_CHAT_ID_LAST_SPECIAL as libc::c_uint {
let locations = &mime_parser.message_kml.as_ref().unwrap().locations;
let newest_location_id =
location::save(context, chat_id, from_id, locations, true).unwrap_or_default();
location::save(context, chat_id, from_id, locations, 1).unwrap_or_default();
if 0 != newest_location_id && 0 == hidden {
if location::set_msg_location_id(context, insert_msg_id, newest_location_id).is_ok() {
location_id_written = true;
@@ -859,14 +883,15 @@ fn save_locations(
}
}
if mime_parser.location_kml.is_some() {
if !mime_parser.location_kml.is_none() && chat_id > DC_CHAT_ID_LAST_SPECIAL as libc::c_uint {
if let Some(ref addr) = mime_parser.location_kml.as_ref().unwrap().addr {
if let Ok(contact) = Contact::get_by_id(context, from_id) {
if contact.get_addr().to_lowercase() == addr.to_lowercase() {
if !contact.get_addr().is_empty()
&& contact.get_addr().to_lowercase() == addr.to_lowercase()
{
let locations = &mime_parser.location_kml.as_ref().unwrap().locations;
let newest_location_id =
location::save(context, chat_id, from_id, locations, false)
.unwrap_or_default();
location::save(context, chat_id, from_id, locations, 0).unwrap_or_default();
if newest_location_id != 0 && hidden == 0 && !location_id_written {
if let Err(err) = location::set_msg_location_id(
context,
@@ -986,7 +1011,7 @@ unsafe fn create_or_lookup_group(
let fld_message_id = (*field).fld_data.fld_message_id;
if !fld_message_id.is_null() {
if let Some(extracted_grpid) =
dc_extract_grpid_from_rfc724_mid(&to_string_lossy((*fld_message_id).mid_value))
dc_extract_grpid_from_rfc724_mid(as_str((*fld_message_id).mid_value))
{
grpid = extracted_grpid.to_string();
} else {
@@ -1214,7 +1239,7 @@ unsafe fn create_or_lookup_group(
"grp-image-change {} chat {}", X_MrGrpImageChanged, chat_id
);
let mut changed = false;
let mut grpimage: Option<BlobObject> = None;
let mut grpimage = "".to_string();
if X_MrGrpImageChanged == "0" {
changed = true;
} else {
@@ -1222,27 +1247,22 @@ unsafe fn create_or_lookup_group(
if part.typ == Viewtype::Image {
grpimage = part
.param
.get_blob(Param::File, context, true)
.unwrap_or(None);
.get(Param::File)
.map(|s| s.to_string())
.unwrap_or_else(|| "".to_string());
info!(context, "found image {:?}", grpimage);
changed = true;
}
}
}
if changed {
info!(
context,
"New group image set to '{}'.",
grpimage
.as_ref()
.map(|blob| blob.as_name().to_string())
.unwrap_or_default()
);
info!(context, "New group image set to '{}'.", grpimage);
if let Ok(mut chat) = Chat::load_from_db(context, chat_id) {
match grpimage {
Some(blob) => chat.param.set(Param::ProfileImage, blob.as_name()),
None => chat.param.remove(Param::ProfileImage),
};
if grpimage.is_empty() {
chat.param.remove(Param::ProfileImage);
} else {
chat.param.set(Param::ProfileImage, grpimage);
}
chat.update_param(context)?;
send_EVENT_CHAT_MODIFIED = 1;
}
@@ -1352,8 +1372,8 @@ unsafe fn create_or_lookup_adhoc_group(
if !member_ids.contains(&from_id) {
member_ids.push(from_id);
}
if !member_ids.contains(&DC_CONTACT_ID_SELF) {
member_ids.push(DC_CONTACT_ID_SELF);
if !member_ids.contains(&1) {
member_ids.push(1);
}
if member_ids.len() < 3 {
// too few contacts given
@@ -1473,7 +1493,7 @@ fn create_adhoc_grp_id(context: &Context, member_ids: &[u32]) -> String {
.sql
.query_map(
format!(
"SELECT addr FROM contacts WHERE id IN({}) AND id!=1", // 1=DC_CONTACT_ID_SELF
"SELECT addr FROM contacts WHERE id IN({}) AND id!=1",
member_ids_str
),
params![],
@@ -1527,7 +1547,7 @@ fn search_chat_ids_by_contact_ids(
WHERE cc.chat_id IN(SELECT chat_id FROM chats_contacts WHERE contact_id IN({})) \
AND c.type=120 \
AND cc.contact_id!=1 \
ORDER BY cc.chat_id, cc.contact_id;", // 1=DC_CONTACT_ID_SELF
ORDER BY cc.chat_id, cc.contact_id;",
contact_ids_str
),
params![],
@@ -1724,7 +1744,7 @@ fn is_known_rfc724_mid(context: &Context, rfc724_mid: *const libc::c_char) -> li
LEFT JOIN chats c ON m.chat_id=c.id \
WHERE m.rfc724_mid=? \
AND m.chat_id>9 AND c.blocked=0;",
params![to_string_lossy(rfc724_mid)],
params![as_str(rfc724_mid)],
)
.unwrap_or_default() as libc::c_int
}
@@ -1776,7 +1796,11 @@ unsafe fn is_msgrmsg_rfc724_mid_in_list(context: &Context, mid_list: *const clis
while !cur.is_null() {
if 0 != is_msgrmsg_rfc724_mid(
context,
&to_string_lossy((*cur).data as *const libc::c_char),
if !cur.is_null() {
as_str((*cur).data as *const libc::c_char)
} else {
""
},
) {
return 1;
}
@@ -1912,7 +1936,7 @@ unsafe fn add_or_lookup_contact_by_addr(
.get_config(Config::ConfiguredAddr)
.unwrap_or_default();
if addr_cmp(self_addr, to_string_lossy(addr_spec)) {
if addr_cmp(self_addr, as_str(addr_spec)) {
*check_self = 1;
}
@@ -1922,18 +1946,13 @@ 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(&to_string_lossy(display_name_enc));
let tmp = dc_decode_header_words(as_str(display_name_enc));
display_name_dec = normalize_name(&tmp);
}
/*can be NULL*/
let row_id = Contact::add_or_lookup(
context,
display_name_dec,
to_string_lossy(addr_spec),
origin,
)
.map(|(id, _)| id)
.unwrap_or_default();
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);
};

View File

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

View File

@@ -74,18 +74,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 += &to_string_lossy(rfc724_mid as *const libc::c_char);
}
}
res
res.strdup()
}
pub(crate) fn dc_str_to_clist(str: &str, delimiter: &str) -> *mut clist {
@@ -154,15 +172,13 @@ pub(crate) fn dc_timestamp_from_date(date_time: *mut mailimf_date_time) -> i64 {
******************************************************************************/
pub fn dc_timestamp_to_str(wanted: i64) -> String {
let ts = Local.timestamp(wanted, 0);
let ts = chrono::Utc.timestamp(wanted, 0);
ts.format("%Y.%m.%d %H:%M:%S").to_string()
}
pub(crate) fn dc_gm2local_offset() -> i64 {
/* returns the offset that must be _added_ to an UTC/GMT-time to create the localtime.
the function may return negative values. */
let lt = Local::now();
lt.offset().local_minus_utc() as i64
((lt.offset().local_minus_utc() / (60 * 60)) * 100) as i64
}
/* timesmearing */
@@ -251,10 +267,12 @@ pub(crate) fn dc_create_incoming_rfc724_mid(
contact_id_from: u32,
contact_ids_to: &[u32],
) -> Option<String> {
/* create a deterministic rfc724_mid from input such that
repeatedly calling it with the same input results in the same Message-id */
if contact_ids_to.is_empty() {
return None;
}
/* find out the largest receiver ID (we could also take the smallest, but it should be unique) */
let largest_id_to = contact_ids_to.iter().max().copied().unwrap_or_default();
let result = format!(
"{}-{}-{}@stub",
message_timestamp, contact_id_from, largest_id_to
@@ -303,9 +321,9 @@ pub(crate) fn dc_extract_grpid_from_rfc724_mid_list(list: *const clist) -> *mut
if !list.is_null() {
unsafe {
for cur in (*list).into_iter() {
let mid = to_string_lossy(cur as *const libc::c_char);
let mid = as_str(cur as *const libc::c_char);
if let Some(grpid) = dc_extract_grpid_from_rfc724_mid(&mid) {
if let Some(grpid) = dc_extract_grpid_from_rfc724_mid(mid) {
return grpid.strdup();
}
}
@@ -392,6 +410,10 @@ pub(crate) fn dc_get_abs_path<P: AsRef<std::path::Path>>(
}
}
pub(crate) fn dc_file_exist(context: &Context, path: impl AsRef<std::path::Path>) -> bool {
dc_get_abs_path(context, &path).exists()
}
pub(crate) fn dc_get_filebytes(context: &Context, path: impl AsRef<std::path::Path>) -> u64 {
let path_abs = dc_get_abs_path(context, &path);
match fs::metadata(&path_abs) {
@@ -420,8 +442,8 @@ pub(crate) fn dc_delete_file(context: &Context, path: impl AsRef<std::path::Path
context.call_cb(Event::DeletedBlobFile(dpath));
true
}
Err(err) => {
warn!(context, "Cannot delete \"{}\": {}", dpath, err);
Err(_err) => {
warn!(context, "Cannot delete \"{}\".", dpath);
false
}
}
@@ -429,55 +451,20 @@ pub(crate) fn dc_delete_file(context: &Context, path: impl AsRef<std::path::Path
pub(crate) fn dc_copy_file(
context: &Context,
src_path: impl AsRef<std::path::Path>,
dest_path: impl AsRef<std::path::Path>,
src: impl AsRef<std::path::Path>,
dest: impl AsRef<std::path::Path>,
) -> bool {
let src_abs = dc_get_abs_path(context, &src_path);
let mut src_file = match fs::File::open(&src_abs) {
Ok(file) => file,
Err(err) => {
warn!(
context,
"failed to open for read '{}': {}",
src_abs.display(),
err
);
return false;
}
};
let dest_abs = dc_get_abs_path(context, &dest_path);
let mut dest_file = match fs::OpenOptions::new()
.create_new(true)
.write(true)
.open(&dest_abs)
{
Ok(file) => file,
Err(err) => {
warn!(
context,
"failed to open for write '{}': {}",
dest_abs.display(),
err
);
return false;
}
};
match std::io::copy(&mut src_file, &mut dest_file) {
let src_abs = dc_get_abs_path(context, &src);
let dest_abs = dc_get_abs_path(context, &dest);
match fs::copy(&src_abs, &dest_abs) {
Ok(_) => true,
Err(err) => {
Err(_) => {
error!(
context,
"Cannot copy \"{}\" to \"{}\": {}",
src_abs.display(),
dest_abs.display(),
err
"Cannot copy \"{}\" to \"{}\".",
src.as_ref().display(),
dest.as_ref().display(),
);
{
// Attempt to remove the failed file, swallow errors resulting from that.
fs::remove_file(dest_abs).ok();
}
false
}
}
@@ -488,12 +475,11 @@ pub(crate) fn dc_create_folder(context: &Context, path: impl AsRef<std::path::Pa
if !path_abs.exists() {
match fs::create_dir_all(path_abs) {
Ok(_) => true,
Err(err) => {
Err(_err) => {
warn!(
context,
"Cannot create directory \"{}\": {}",
"Cannot create directory \"{}\".",
path.as_ref().display(),
err
);
false
}
@@ -506,13 +492,12 @@ pub(crate) fn dc_create_folder(context: &Context, path: impl AsRef<std::path::Pa
/// Write a the given content to provied file path.
pub(crate) fn dc_write_file(context: &Context, path: impl AsRef<Path>, buf: &[u8]) -> bool {
let path_abs = dc_get_abs_path(context, &path);
if let Err(err) = fs::write(&path_abs, buf) {
if let Err(_err) = fs::write(&path_abs, buf) {
warn!(
context,
"Cannot write {} bytes to \"{}\": {}",
"Cannot write {} bytes to \"{}\".",
buf.len(),
path.as_ref().display(),
err
);
false
} else {
@@ -531,9 +516,8 @@ pub fn dc_read_file<P: AsRef<std::path::Path>>(
Err(err) => {
warn!(
context,
"Cannot read \"{}\" or file is empty: {}",
path.as_ref().display(),
err
"Cannot read \"{}\" or file is empty.",
path.as_ref().display()
);
Err(err.into())
}
@@ -551,9 +535,8 @@ pub fn dc_open_file<P: AsRef<std::path::Path>>(
Err(err) => {
warn!(
context,
"Cannot read \"{}\" or file is empty: {}",
path.as_ref().display(),
err
"Cannot read \"{}\" or file is empty.",
path.as_ref().display()
);
Err(err.into())
}
@@ -580,6 +563,41 @@ pub(crate) fn dc_get_next_backup_path(
bail!("could not create backup file, disk full?");
}
pub(crate) fn dc_is_blobdir_path(context: &Context, path: impl AsRef<str>) -> bool {
context
.get_blobdir()
.to_str()
.map(|s| path.as_ref().starts_with(s))
.unwrap_or_default()
|| path.as_ref().starts_with("$BLOBDIR")
}
fn dc_make_rel_path(context: &Context, path: &mut String) {
if context
.get_blobdir()
.to_str()
.map(|s| path.starts_with(s))
.unwrap_or_default()
{
*path = path.replace(
context.get_blobdir().to_str().unwrap_or_default(),
"$BLOBDIR",
);
}
}
pub(crate) fn dc_make_rel_and_copy(context: &Context, path: &mut String) -> bool {
if dc_is_blobdir_path(context, &path) {
dc_make_rel_path(context, path);
return true;
}
if let Ok(blobdir_path) = context.copy_to_blobdir(&path) {
*path = blobdir_path;
return true;
}
false
}
/// Error type for the [OsStrExt] trait
#[derive(Debug, Fail, PartialEq)]
pub enum CStringError {
@@ -735,12 +753,25 @@ pub fn to_string_lossy(s: *const libc::c_char) -> String {
cstr.to_string_lossy().to_string()
}
pub fn to_opt_string_lossy(s: *const libc::c_char) -> Option<String> {
pub fn as_str<'a>(s: *const libc::c_char) -> &'a str {
as_str_safe(s).unwrap_or_else(|err| panic!("{}", err))
}
/// Converts a C string to either a Rust `&str` or `None` if it is a null pointer.
pub fn as_opt_str<'a>(s: *const libc::c_char) -> Option<&'a str> {
if s.is_null() {
return None;
}
Some(as_str(s))
}
Some(to_string_lossy(s))
fn as_str_safe<'a>(s: *const libc::c_char) -> Result<&'a str, Error> {
assert!(!s.is_null(), "cannot be used on null pointers");
let cstr = unsafe { CStr::from_ptr(s) };
cstr.to_str()
.map_err(|err| format_err!("Non utf8 string: '{:?}' ({:?})", cstr.to_bytes(), err))
}
/// Convert a C `*char` pointer to a [std::path::Path] slice.
@@ -779,11 +810,7 @@ pub fn as_path<'a>(s: *const libc::c_char) -> &'a std::path::Path {
#[allow(dead_code)]
fn as_path_unicode<'a>(s: *const libc::c_char) -> &'a std::path::Path {
assert!(!s.is_null(), "cannot be used on null pointers");
let cstr = unsafe { CStr::from_ptr(s) };
let str = cstr.to_str().unwrap_or_else(|err| panic!("{}", err));
std::path::Path::new(str)
std::path::Path::new(as_str(s))
}
pub(crate) fn time() -> i64 {
@@ -1003,6 +1030,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 {
@@ -1018,11 +1060,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);
}
}
@@ -1266,8 +1314,32 @@ mod tests {
fn test_dc_create_incoming_rfc724_mid() {
let res = dc_create_incoming_rfc724_mid(123, 45, &vec![6, 7]);
assert_eq!(res, Some("123-45-7@stub".into()));
let res = dc_create_incoming_rfc724_mid(123, 45, &vec![]);
assert_eq!(res, Some("123-45-0@stub".into()));
}
#[test]
fn test_dc_make_rel_path() {
let t = dummy_context();
let mut foo: String = t
.ctx
.get_blobdir()
.join("foo")
.to_string_lossy()
.into_owned();
dc_make_rel_path(&t.ctx, &mut foo);
assert_eq!(foo, format!("$BLOBDIR{}foo", std::path::MAIN_SEPARATOR));
}
#[test]
fn test_strndup() {
unsafe {
let res = strndup(b"helloworld\x00" as *const u8 as *const libc::c_char, 4);
assert_eq!(
to_string_lossy(res),
to_string_lossy(b"hell\x00" as *const u8 as *const libc::c_char)
);
assert_eq!(strlen(res), 4);
free(res as *mut _);
}
}
#[test]
@@ -1287,11 +1359,6 @@ mod tests {
fn test_file_handling() {
let t = dummy_context();
let context = &t.ctx;
let dc_file_exist = |ctx: &Context, fname: &str| {
ctx.get_blobdir()
.join(Path::new(fname).file_name().unwrap())
.exists()
};
assert!(!dc_delete_file(context, "$BLOBDIR/lkqwjelqkwlje"));
if dc_file_exist(context, "$BLOBDIR/foobar")
@@ -1315,13 +1382,14 @@ mod tests {
.to_string_lossy()
.to_string();
assert!(dc_is_blobdir_path(context, &abs_path));
assert!(dc_is_blobdir_path(context, "$BLOBDIR/fofo",));
assert!(!dc_is_blobdir_path(context, "/BLOBDIR/fofo",));
assert!(dc_file_exist(context, &abs_path));
assert!(dc_copy_file(context, "$BLOBDIR/foobar", "$BLOBDIR/dada",));
// attempting to copy a second time should fail
assert!(!dc_copy_file(context, "$BLOBDIR/foobar", "$BLOBDIR/dada",));
assert_eq!(dc_get_filebytes(context, "$BLOBDIR/dada",), 7);
let buf = dc_read_file(context, "$BLOBDIR/dada").unwrap();

View File

@@ -449,7 +449,7 @@ fn update_gossip_peerstates(
let optional_field = unsafe { *optional_field };
if !optional_field.fld_name.is_null()
&& to_string_lossy(optional_field.fld_name) == "Autocrypt-Gossip"
&& as_str(optional_field.fld_name) == "Autocrypt-Gossip"
{
let value = to_string_lossy(optional_field.fld_value);
let gossip_header = Aheader::from_str(&value);
@@ -498,7 +498,7 @@ fn decrypt_if_autocrypt_message(
public_keyring_for_validate: &Keyring,
ret_valid_signatures: &mut HashSet<String>,
ret_gossip_headers: *mut *mut mailimf_fields,
) -> Result<bool> {
) -> Result<(bool)> {
/* The returned bool is true if we detected an Autocrypt-encrypted
message and successfully decrypted it. Decryption then modifies the
passed in mime structure in place. The returned bool is false
@@ -655,7 +655,7 @@ fn contains_report(mime: *mut Mailmime) -> bool {
if tp_type == MAILMIME_TYPE_COMPOSITE_TYPE as libc::c_int
&& ct_type == MAILMIME_COMPOSITE_TYPE_MULTIPART as libc::c_int
&& to_string_lossy(unsafe { (*mime.mm_content_type).ct_subtype }) == "report"
&& as_str(unsafe { (*mime.mm_content_type).ct_subtype }) == "report"
{
return true;
}

View File

@@ -30,10 +30,6 @@ pub enum Error {
Base64Decode(base64::DecodeError),
#[fail(display = "{:?}", _0)]
FromUtf8(std::string::FromUtf8Error),
#[fail(display = "{}", _0)]
BlobError(#[cause] crate::blob::BlobError),
#[fail(display = "Invalid Message ID.")]
InvalidMsgId,
}
pub type Result<T> = std::result::Result<T, Error>;
@@ -98,18 +94,6 @@ impl From<std::string::FromUtf8Error> for Error {
}
}
impl From<crate::blob::BlobError> for Error {
fn from(err: crate::blob::BlobError) -> Error {
Error::BlobError(err)
}
}
impl From<crate::message::InvalidMsgId> for Error {
fn from(_err: crate::message::InvalidMsgId) -> Error {
Error::InvalidMsgId
}
}
#[macro_export]
macro_rules! bail {
($e:expr) => {

View File

@@ -2,8 +2,6 @@ use std::path::PathBuf;
use strum::EnumProperty;
use crate::message::MsgId;
impl Event {
/// Returns the corresponding Event id.
pub fn as_id(&self) -> i32 {
@@ -54,12 +52,6 @@ pub enum Event {
#[strum(props(id = "105"))]
ImapMessageMoved(String),
/// Emitted when an IMAP folder was emptied
///
/// @return 0
#[strum(props(id = "106"))]
ImapFolderEmptied(String),
/// Emitted when an new file in the $BLOBDIR was created
///
/// @return 0
@@ -133,7 +125,7 @@ pub enum Event {
///
/// @return 0
#[strum(props(id = "2000"))]
MsgsChanged { chat_id: u32, msg_id: MsgId },
MsgsChanged { chat_id: u32, msg_id: u32 },
/// There is a fresh message. Typically, the user will show an notification
/// when receiving this message.
@@ -142,28 +134,28 @@ pub enum Event {
///
/// @return 0
#[strum(props(id = "2005"))]
IncomingMsg { chat_id: u32, msg_id: MsgId },
IncomingMsg { chat_id: u32, msg_id: u32 },
/// A single message is sent successfully. State changed from DC_STATE_OUT_PENDING to
/// DC_STATE_OUT_DELIVERED, see dc_msg_get_state().
///
/// @return 0
#[strum(props(id = "2010"))]
MsgDelivered { chat_id: u32, msg_id: MsgId },
MsgDelivered { chat_id: u32, msg_id: u32 },
/// A single message could not be sent. State changed from DC_STATE_OUT_PENDING or DC_STATE_OUT_DELIVERED to
/// DC_STATE_OUT_FAILED, see dc_msg_get_state().
///
/// @return 0
#[strum(props(id = "2012"))]
MsgFailed { chat_id: u32, msg_id: MsgId },
MsgFailed { chat_id: u32, msg_id: u32 },
/// A single message is read by the receiver. State changed from DC_STATE_OUT_DELIVERED to
/// DC_STATE_OUT_MDN_RCVD, see dc_msg_get_state().
///
/// @return 0
#[strum(props(id = "2015"))]
MsgRead { chat_id: u32, msg_id: MsgId },
MsgRead { chat_id: u32, msg_id: u32 },
/// Chat changed. The name or the image of a chat group was changed or members were added or removed.
/// Or the verify state of a chat has changed.

File diff suppressed because it is too large Load Diff

View File

@@ -1,294 +0,0 @@
use async_imap::{
error::{Error as ImapError, Result as ImapResult},
extensions::idle::Handle as ImapIdleHandle,
types::{Capabilities, Fetch, Mailbox, Name},
Client as ImapClient, Session as ImapSession,
};
use async_std::net::{self, TcpStream};
use async_std::prelude::*;
use async_std::sync::Arc;
use async_tls::client::TlsStream;
use crate::login_param::{dc_build_tls_config, CertificateChecks};
const DCC_IMAP_DEBUG: &str = "DCC_IMAP_DEBUG";
#[derive(Debug)]
pub(crate) enum Client {
Secure(ImapClient<TlsStream<TcpStream>>),
Insecure(ImapClient<TcpStream>),
}
#[derive(Debug)]
pub(crate) enum Session {
Secure(ImapSession<TlsStream<TcpStream>>),
Insecure(ImapSession<TcpStream>),
}
#[derive(Debug)]
pub(crate) enum IdleHandle {
Secure(ImapIdleHandle<TlsStream<TcpStream>>),
Insecure(ImapIdleHandle<TcpStream>),
}
impl Client {
pub async fn connect_secure<A: net::ToSocketAddrs, S: AsRef<str>>(
addr: A,
domain: S,
certificate_checks: CertificateChecks,
) -> ImapResult<Self> {
let stream = TcpStream::connect(addr).await?;
let tls_config = dc_build_tls_config(certificate_checks);
let tls_connector: async_tls::TlsConnector = Arc::new(tls_config).into();
let tls_stream = tls_connector.connect(domain.as_ref(), stream)?.await?;
let mut client = ImapClient::new(tls_stream);
if std::env::var(DCC_IMAP_DEBUG).is_ok() {
client.debug = true;
}
let _greeting = client
.read_response()
.await
.expect("failed to read greeting");
Ok(Client::Secure(client))
}
pub async fn connect_insecure<A: net::ToSocketAddrs>(addr: A) -> ImapResult<Self> {
let stream = TcpStream::connect(addr).await?;
let mut client = ImapClient::new(stream);
if std::env::var(DCC_IMAP_DEBUG).is_ok() {
client.debug = true;
}
let _greeting = client
.read_response()
.await
.expect("failed to read greeting");
Ok(Client::Insecure(client))
}
pub async fn secure<S: AsRef<str>>(
self,
domain: S,
_certificate_checks: CertificateChecks,
) -> ImapResult<Client> {
match self {
Client::Insecure(client) => {
let tls = async_tls::TlsConnector::new();
let client_sec = client.secure(domain, &tls).await?;
Ok(Client::Secure(client_sec))
}
// Nothing to do
Client::Secure(_) => Ok(self),
}
}
pub async fn authenticate<A: async_imap::Authenticator, S: AsRef<str>>(
self,
auth_type: S,
authenticator: &A,
) -> Result<Session, (ImapError, Client)> {
match self {
Client::Secure(i) => match i.authenticate(auth_type, authenticator).await {
Ok(session) => Ok(Session::Secure(session)),
Err((err, c)) => Err((err, Client::Secure(c))),
},
Client::Insecure(i) => match i.authenticate(auth_type, authenticator).await {
Ok(session) => Ok(Session::Insecure(session)),
Err((err, c)) => Err((err, Client::Insecure(c))),
},
}
}
pub async fn login<U: AsRef<str>, P: AsRef<str>>(
self,
username: U,
password: P,
) -> Result<Session, (ImapError, Client)> {
match self {
Client::Secure(i) => match i.login(username, password).await {
Ok(session) => Ok(Session::Secure(session)),
Err((err, c)) => Err((err, Client::Secure(c))),
},
Client::Insecure(i) => match i.login(username, password).await {
Ok(session) => Ok(Session::Insecure(session)),
Err((err, c)) => Err((err, Client::Insecure(c))),
},
}
}
}
impl Session {
pub async fn capabilities(&mut self) -> ImapResult<Capabilities> {
let res = match self {
Session::Secure(i) => i.capabilities().await?,
Session::Insecure(i) => i.capabilities().await?,
};
Ok(res)
}
pub async fn list(
&mut self,
reference_name: Option<&str>,
mailbox_pattern: Option<&str>,
) -> ImapResult<Vec<Name>> {
let res = match self {
Session::Secure(i) => {
i.list(reference_name, mailbox_pattern)
.await?
.collect::<ImapResult<_>>()
.await?
}
Session::Insecure(i) => {
i.list(reference_name, mailbox_pattern)
.await?
.collect::<ImapResult<_>>()
.await?
}
};
Ok(res)
}
pub async fn create<S: AsRef<str>>(&mut self, mailbox_name: S) -> ImapResult<()> {
match self {
Session::Secure(i) => i.create(mailbox_name).await?,
Session::Insecure(i) => i.create(mailbox_name).await?,
}
Ok(())
}
pub async fn subscribe<S: AsRef<str>>(&mut self, mailbox: S) -> ImapResult<()> {
match self {
Session::Secure(i) => i.subscribe(mailbox).await?,
Session::Insecure(i) => i.subscribe(mailbox).await?,
}
Ok(())
}
pub async fn close(&mut self) -> ImapResult<()> {
match self {
Session::Secure(i) => i.close().await?,
Session::Insecure(i) => i.close().await?,
}
Ok(())
}
pub async fn select<S: AsRef<str>>(&mut self, mailbox_name: S) -> ImapResult<Mailbox> {
let mbox = match self {
Session::Secure(i) => i.select(mailbox_name).await?,
Session::Insecure(i) => i.select(mailbox_name).await?,
};
Ok(mbox)
}
pub async fn fetch<S1, S2>(&mut self, sequence_set: S1, query: S2) -> ImapResult<Vec<Fetch>>
where
S1: AsRef<str>,
S2: AsRef<str>,
{
let res = match self {
Session::Secure(i) => {
i.fetch(sequence_set, query)
.await?
.collect::<ImapResult<_>>()
.await?
}
Session::Insecure(i) => {
i.fetch(sequence_set, query)
.await?
.collect::<ImapResult<_>>()
.await?
}
};
Ok(res)
}
pub async fn uid_fetch<S1, S2>(&mut self, uid_set: S1, query: S2) -> ImapResult<Vec<Fetch>>
where
S1: AsRef<str>,
S2: AsRef<str>,
{
let res = match self {
Session::Secure(i) => {
i.uid_fetch(uid_set, query)
.await?
.collect::<ImapResult<_>>()
.await?
}
Session::Insecure(i) => {
i.uid_fetch(uid_set, query)
.await?
.collect::<ImapResult<_>>()
.await?
}
};
Ok(res)
}
pub fn idle(self) -> IdleHandle {
match self {
Session::Secure(i) => {
let h = i.idle();
IdleHandle::Secure(h)
}
Session::Insecure(i) => {
let h = i.idle();
IdleHandle::Insecure(h)
}
}
}
pub async fn uid_store<S1, S2>(&mut self, uid_set: S1, query: S2) -> ImapResult<Vec<Fetch>>
where
S1: AsRef<str>,
S2: AsRef<str>,
{
let res = match self {
Session::Secure(i) => {
i.uid_store(uid_set, query)
.await?
.collect::<ImapResult<_>>()
.await?
}
Session::Insecure(i) => {
i.uid_store(uid_set, query)
.await?
.collect::<ImapResult<_>>()
.await?
}
};
Ok(res)
}
pub async fn uid_mv<S1: AsRef<str>, S2: AsRef<str>>(
&mut self,
uid_set: S1,
mailbox_name: S2,
) -> ImapResult<()> {
match self {
Session::Secure(i) => i.uid_mv(uid_set, mailbox_name).await?,
Session::Insecure(i) => i.uid_mv(uid_set, mailbox_name).await?,
}
Ok(())
}
pub async fn uid_copy<S1: AsRef<str>, S2: AsRef<str>>(
&mut self,
uid_set: S1,
mailbox_name: S2,
) -> ImapResult<()> {
match self {
Session::Secure(i) => i.uid_copy(uid_set, mailbox_name).await?,
Session::Insecure(i) => i.uid_copy(uid_set, mailbox_name).await?,
}
Ok(())
}
}

View File

@@ -1,23 +1,21 @@
use core::cmp::{max, min};
use std::path::Path;
use std::path::{Path, PathBuf};
use num_traits::FromPrimitive;
use rand::{thread_rng, Rng};
use crate::blob::BlobObject;
use crate::chat;
use crate::config::Config;
use crate::configure::*;
use crate::constants::*;
use crate::context::Context;
use crate::dc_mimeparser::SystemMessage;
use crate::dc_tools::*;
use crate::e2ee;
use crate::error::*;
use crate::events::Event;
use crate::job::*;
use crate::key::*;
use crate::message::{Message, MsgId};
use crate::message::Message;
use crate::param::*;
use crate::pgp;
use crate::sql::{self, Sql};
@@ -75,7 +73,7 @@ pub fn imex(context: &Context, what: ImexMode, param1: Option<impl AsRef<Path>>)
job_add(context, Action::ImexImap, 0, param, 0);
}
/// Returns the filename of the backup found (otherwise an error)
/// Returns the filename of the backup if found, nullptr otherwise.
pub fn has_backup(context: &Context, dir_name: impl AsRef<Path>) -> Result<String> {
let dir_name = dir_name.as_ref();
let dir_iter = std::fs::read_dir(dir_name)?;
@@ -89,16 +87,14 @@ 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) {
let curr_backup_time = sql
.get_raw_config_int(context, "backup_time")
.unwrap_or_default();
if sql.open(context, &path, 0x1) {
let curr_backup_time =
sql.get_raw_config_int(context, "backup_time")
.unwrap_or_default() as u64;
if curr_backup_time > newest_backup_time {
newest_backup_path = Some(path);
newest_backup_time = curr_backup_time;
}
info!(context, "backup_time of {} is {}", name, curr_backup_time);
sql.close(&context);
}
}
}
@@ -107,7 +103,7 @@ pub fn has_backup(context: &Context, dir_name: impl AsRef<Path>) -> Result<Strin
}
match newest_backup_path {
Some(path) => Ok(path.to_string_lossy().into_owned()),
None => bail!("no backup found in {}", dir_name.display()),
None => bail!("no backup found"),
}
}
@@ -126,20 +122,19 @@ fn do_initiate_key_transfer(context: &Context) -> Result<String> {
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_blob = BlobObject::create(
context,
let setup_file_name = context.new_blob_file(
"autocrypt-setup-message.html",
setup_file_content.as_bytes(),
)?;
let chat_id = chat::create_by_contact_id(context, DC_CONTACT_ID_SELF)?;
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_blob.as_name());
msg.param.set(Param::File, setup_file_name);
msg.param
.set(Param::MimeType, "application/autocrypt-setup");
msg.param.set_cmd(SystemMessage::AutocryptSetupMessage);
msg.param.set_int(Param::Cmd, 6);
msg.param
.set_int(Param::ForcePlaintext, DC_FP_NO_AUTOCRYPT_HEADER);
@@ -230,10 +225,14 @@ pub fn create_setup_code(_context: &Context) -> String {
ret
}
pub fn continue_key_transfer(context: &Context, msg_id: MsgId, setup_code: &str) -> Result<()> {
ensure!(!msg_id.is_special(), "wrong id");
pub fn continue_key_transfer(context: &Context, msg_id: u32, setup_code: &str) -> Result<()> {
ensure!(msg_id > DC_MSG_ID_LAST_SPECIAL, "wrong id");
let msg = Message::load_from_db(context, msg_id)?;
let msg = Message::load_from_db(context, msg_id);
if msg.is_err() {
bail!("Message is no Autocrypt Setup Message.");
}
let msg = msg.unwrap_or_default();
ensure!(
msg.is_setupmessage(),
"Message is no Autocrypt Setup Message."
@@ -403,7 +402,7 @@ fn import_backup(context: &Context, backup_to_import: impl AsRef<Path>) -> Resul
context.sql.close(&context);
dc_delete_file(context, context.get_dbfile());
ensure!(
!context.get_dbfile().exists(),
!dc_file_exist(context, context.get_dbfile()),
"Cannot delete old database."
);
@@ -414,7 +413,7 @@ fn import_backup(context: &Context, backup_to_import: impl AsRef<Path>) -> Resul
/* error already logged */
/* re-open copied database file */
ensure!(
context.sql.open(&context, &context.get_dbfile(), false),
context.sql.open(&context, &context.get_dbfile(), 0),
"could not re-open db"
);
@@ -485,56 +484,51 @@ fn export_backup(context: &Context, dir: impl AsRef<Path>) -> Result<()> {
// let dest_path_filename = dc_get_next_backup_file(context, dir, res);
let now = time();
let dest_path_filename = dc_get_next_backup_path(dir, now)?;
let dest_path_string = dest_path_filename.to_string_lossy().to_string();
sql::housekeeping(context);
sql::try_execute(context, &context.sql, "VACUUM;").ok();
// we close the database during the copy of the dbfile
context.sql.close(context);
info!(
context,
"Backup '{}' to '{}'.",
"Backup \"{}\" to \"{}\".",
context.get_dbfile().display(),
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!(
"could not copy file from '{}' to '{}'",
context.get_dbfile().display(),
dest_path_string
"could not copy file from {:?} to {:?}",
context.get_dbfile(),
s
);
}
let dest_sql = Sql::new();
ensure!(
dest_sql.open(context, &dest_path_filename, false),
"could not open exported database {}",
dest_path_string
);
let res = match add_files_to_export(context, &dest_sql) {
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(()) => {
dest_sql.set_raw_config_int(context, "backup_time", now as i32)?;
context
.sql
.set_raw_config_int(context, "backup_time", now as i32)?;
context.call_cb(Event::ImexFileWritten(dest_path_filename.clone()));
Ok(())
}
};
dest_sql.close(context);
res
}
}
fn add_files_to_export(context: &Context, sql: &Sql) -> Result<()> {
fn add_files_to_export(context: &Context, dest_path_filename: &PathBuf) -> Result<()> {
// add all files as blobs to the database copy (this does not require
// the source to be locked, neigher the destination as it is used only here)
let sql = Sql::new();
ensure!(
sql.open(context, &dest_path_filename, 0),
"could not open db"
);
if !sql.table_exists("backup_blobs") {
sql::execute(
context,

View File

@@ -3,7 +3,6 @@ use std::time::Duration;
use deltachat_derive::{FromSql, ToSql};
use rand::{thread_rng, Rng};
use crate::blob::BlobObject;
use crate::chat;
use crate::config::Config;
use crate::configure::*;
@@ -16,7 +15,6 @@ use crate::imap::*;
use crate::imex::*;
use crate::location;
use crate::login_param::LoginParam;
use crate::message::MsgId;
use crate::message::{self, Message, MessageState};
use crate::mimefactory::{vec_contains_lowercase, Loaded, MimeFactory};
use crate::param::*;
@@ -44,7 +42,6 @@ pub enum Action {
// Jobs in the INBOX-thread, range from DC_IMAP_THREAD..DC_IMAP_THREAD+999
Housekeeping = 105, // low priority ...
EmptyServer = 107,
DeleteMsgOnImap = 110,
MarkseenMdnOnImap = 120,
MarkseenMsgOnImap = 130,
@@ -76,7 +73,6 @@ impl From<Action> for Thread {
Housekeeping => Thread::Imap,
DeleteMsgOnImap => Thread::Imap,
EmptyServer => Thread::Imap,
MarkseenMdnOnImap => Thread::Imap,
MarkseenMsgOnImap => Thread::Imap,
MoveMsg => Thread::Imap,
@@ -135,14 +131,15 @@ impl Job {
if !context.smtp.lock().unwrap().is_connected() {
let loginparam = LoginParam::from_database(context, "configured_");
let connected = context.smtp.lock().unwrap().connect(context, &loginparam);
if connected.is_err() {
self.try_again_later(3, None);
if !connected {
self.try_again_later(3i32, None);
return;
}
}
if let Some(filename) = self.param.get_path(Param::File, context).unwrap_or(None) {
if let Ok(body) = dc_read_file(context, &filename) {
if let Some(filename) = self.param.get(Param::File) {
if let Ok(body) = dc_read_file(context, filename) {
if let Some(recipients) = self.param.get(Param::Recipients) {
let recipients_list = recipients
.split('\x1e')
@@ -158,9 +155,7 @@ impl Job {
/* if there is a msg-id and it does not exist in the db, cancel sending.
this happends if dc_delete_msgs() was called
before the generated mime was sent out */
if 0 != self.foreign_id
&& !message::exists(context, MsgId::new(self.foreign_id))
{
if 0 != self.foreign_id && !message::exists(context, self.foreign_id) {
warn!(
context,
"Not sending Message {} as it was deleted", self.foreign_id
@@ -172,19 +167,19 @@ impl Job {
// its ok/error response processing. Note that if a message
// was sent we need to mark it in the database ASAP as we
// otherwise might send it twice.
let mut smtp = context.smtp.lock().unwrap();
match smtp.send(context, recipients_list, body, self.job_id) {
let mut sock = context.smtp.lock().unwrap();
match sock.send(context, recipients_list, body) {
Err(err) => {
smtp.disconnect();
sock.disconnect();
warn!(context, "smtp failed: {}", err);
self.try_again_later(-1, Some(err.to_string()));
self.try_again_later(-1i32, Some(err.to_string()));
}
Ok(()) => {
// smtp success, update db ASAP, then delete smtp file
if 0 != self.foreign_id {
message::update_msg_state(
context,
MsgId::new(self.foreign_id),
self.foreign_id,
MessageState::OutDelivered,
);
let chat_id: i32 = context
@@ -197,7 +192,7 @@ impl Job {
.unwrap_or_default();
context.call_cb(Event::MsgDelivered {
chat_id: chat_id as u32,
msg_id: MsgId::new(self.foreign_id),
msg_id: self.foreign_id,
});
}
// now also delete the generated file
@@ -212,7 +207,7 @@ impl Job {
}
// this value does not increase the number of tries
fn try_again_later(&mut self, try_again: i32, pending_error: Option<String>) {
fn try_again_later(&mut self, try_again: libc::c_int, pending_error: Option<String>) {
self.try_again = try_again;
self.pending_error = pending_error;
}
@@ -221,7 +216,7 @@ impl Job {
fn do_DC_JOB_MOVE_MSG(&mut self, context: &Context) {
let inbox = context.inbox.read().unwrap();
if let Ok(msg) = Message::load_from_db(context, MsgId::new(self.foreign_id)) {
if let Ok(msg) = Message::load_from_db(context, self.foreign_id) {
if context
.sql
.get_raw_config_int(context, "folders_configured")
@@ -245,10 +240,10 @@ impl Job {
&dest_folder,
&mut dest_uid,
) {
ImapActionResult::RetryLater => {
ImapResult::RetryLater => {
self.try_again_later(3i32, None);
}
ImapActionResult::Success => {
ImapResult::Success => {
message::update_server_uid(
context,
&msg.rfc724_mid,
@@ -256,7 +251,7 @@ impl Job {
dest_uid,
);
}
ImapActionResult::Failed | ImapActionResult::AlreadyDone => {}
ImapResult::Failed | ImapResult::AlreadyDone => {}
}
}
}
@@ -266,7 +261,7 @@ impl Job {
fn do_DC_JOB_DELETE_MSG_ON_IMAP(&mut self, context: &Context) {
let inbox = context.inbox.read().unwrap();
if let Ok(mut msg) = Message::load_from_db(context, MsgId::new(self.foreign_id)) {
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 {
@@ -280,7 +275,7 @@ impl Job {
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 == ImapActionResult::RetryLater {
if res == ImapResult::RetryLater {
self.try_again_later(-1i32, None);
return;
}
@@ -290,34 +285,18 @@ impl Job {
}
}
#[allow(non_snake_case)]
fn do_DC_JOB_EMPTY_SERVER(&mut self, context: &Context) {
let inbox = context.inbox.read().unwrap();
if self.foreign_id & DC_EMPTY_MVBOX > 0 {
if let Some(mvbox_folder) = context
.sql
.get_raw_config(context, "configured_mvbox_folder")
{
inbox.empty_folder(context, &mvbox_folder);
}
}
if self.foreign_id & DC_EMPTY_INBOX > 0 {
inbox.empty_folder(context, "INBOX");
}
}
#[allow(non_snake_case)]
fn do_DC_JOB_MARKSEEN_MSG_ON_IMAP(&mut self, context: &Context) {
let inbox = context.inbox.read().unwrap();
if let Ok(msg) = Message::load_from_db(context, MsgId::new(self.foreign_id)) {
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) {
ImapActionResult::RetryLater => {
ImapResult::RetryLater => {
self.try_again_later(3i32, None);
}
ImapActionResult::AlreadyDone => {}
ImapActionResult::Success | ImapActionResult::Failed => {
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
@@ -343,7 +322,7 @@ 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) == ImapActionResult::RetryLater {
if inbox.set_seen(context, &folder, uid) == ImapResult::RetryLater {
self.try_again_later(3i32, None);
return;
}
@@ -361,7 +340,7 @@ impl Job {
.get_raw_config(context, "configured_mvbox_folder");
if let Some(dest_folder) = dest_folder {
let mut dest_uid = 0;
if ImapActionResult::RetryLater
if ImapResult::RetryLater
== inbox.mv(context, &folder, uid, &dest_folder, &mut dest_uid)
{
self.try_again_later(3, None);
@@ -402,7 +381,7 @@ pub fn perform_imap_fetch(context: &Context) {
info!(
context,
"INBOX-fetch done in {:.4} ms.",
start.elapsed().as_nanos() as f64 / 1_000_000.0,
start.elapsed().as_nanos() as f64 / 1000.0,
);
}
@@ -581,11 +560,15 @@ 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: MsgId) -> Result<(), Error> {
pub fn job_send_msg(context: &Context, msg_id: u32) -> Result<(), Error> {
let mut mimefactory = MimeFactory::load_msg(context, msg_id)?;
if chat::msgtype_has_file(mimefactory.msg.type_0) {
let file_param = mimefactory.msg.param.get_path(Param::File, context)?;
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)
@@ -614,7 +597,7 @@ pub fn job_send_msg(context: &Context, msg_id: MsgId) -> Result<(), Error> {
if 0 != mimefactory
.msg
.param
.get_int(Param::GuaranteeE2ee)
.get_int(Param::GuranteeE2ee)
.unwrap_or_default()
&& !mimefactory.out_encrypted
{
@@ -627,7 +610,7 @@ pub fn job_send_msg(context: &Context, msg_id: MsgId) -> Result<(), Error> {
bail!(
"e2e encryption unavailable {} - {:?}",
msg_id,
mimefactory.msg.param.get_int(Param::GuaranteeE2ee),
mimefactory.msg.param.get_int(Param::GuranteeE2ee),
);
}
if context.get_config_bool(Config::BccSelf)
@@ -669,11 +652,11 @@ pub fn job_send_msg(context: &Context, msg_id: MsgId) -> Result<(), Error> {
&& mimefactory
.msg
.param
.get_int(Param::GuaranteeE2ee)
.get_int(Param::GuranteeE2ee)
.unwrap_or_default()
== 0
{
mimefactory.msg.param.set_int(Param::GuaranteeE2ee, 1);
mimefactory.msg.param.set_int(Param::GuranteeE2ee, 1);
mimefactory.msg.save_param_to_disk(context);
}
add_smtp_job(context, Action::SendMsgToSmtp, &mut mimefactory)?;
@@ -793,7 +776,6 @@ fn job_perform(context: &Context, thread: Thread, probe_network: bool) {
warn!(context, "Unknown job id found");
}
Action::SendMsgToSmtp => job.do_DC_JOB_SEND(context),
Action::EmptyServer => job.do_DC_JOB_EMPTY_SERVER(context),
Action::DeleteMsgOnImap => job.do_DC_JOB_DELETE_MSG_ON_IMAP(context),
Action::MarkseenMsgOnImap => job.do_DC_JOB_MARKSEEN_MSG_ON_IMAP(context),
Action::MarkseenMdnOnImap => job.do_DC_JOB_MARKSEEN_MDN_ON_IMAP(context),
@@ -879,11 +861,7 @@ fn job_perform(context: &Context, thread: Thread, probe_network: bool) {
}
} else {
if job.action == Action::SendMsgToSmtp {
message::set_msg_failed(
context,
MsgId::new(job.foreign_id),
job.pending_error.as_ref(),
);
message::set_msg_failed(context, job.foreign_id, job.pending_error.as_ref());
}
job.delete(context);
}
@@ -934,7 +912,7 @@ pub fn connect_to_inbox(context: &Context, inbox: &Imap) -> libc::c_int {
ret_connected
}
fn send_mdn(context: &Context, msg_id: MsgId) -> Result<(), Error> {
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)?;
@@ -955,15 +933,15 @@ fn add_smtp_job(context: &Context, action: Action, mimefactory: &MimeFactory) ->
(*mimefactory.out).len,
)
};
let blob = BlobObject::create(context, &mimefactory.rfc724_mid, bytes)?;
let bpath = context.new_blob_file(&mimefactory.rfc724_mid, bytes)?;
let recipients = mimefactory.recipients_addr.join("\x1e");
param.set(Param::File, blob.as_name());
param.set(Param::File, &bpath);
param.set(Param::Recipients, &recipients);
job_add(
context,
action,
(if mimefactory.loaded == Loaded::Message {
mimefactory.msg.id.to_u32() as i32
mimefactory.msg.id
} else {
0
}) as libc::c_int,
@@ -1022,9 +1000,8 @@ pub fn interrupt_smtp_idle(context: &Context) {
}
pub fn interrupt_imap_idle(context: &Context) {
info!(context, "Interrupting INBOX-IDLE...",);
info!(context, "Interrupting IMAP-IDLE...",);
*context.perform_inbox_jobs_needed.write().unwrap() = true;
context.inbox.read().unwrap().interrupt_idle();
}

View File

@@ -15,7 +15,7 @@ pub struct JobThread {
#[derive(Clone, Debug, Default)]
pub struct JobState {
idle: bool,
jobs_needed: bool,
jobs_needed: i32,
suspended: bool,
using_handle: bool,
}
@@ -58,7 +58,7 @@ impl JobThread {
pub fn interrupt_idle(&self, context: &Context) {
{
self.state.0.lock().unwrap().jobs_needed = true;
self.state.0.lock().unwrap().jobs_needed = 1;
}
info!(context, "Interrupting {}-IDLE...", self.name);
@@ -107,17 +107,12 @@ impl JobThread {
}
fn connect_to_imap(&self, context: &Context) -> bool {
if async_std::task::block_on(async move { self.imap.is_connected().await }) {
if self.imap.is_connected() {
return true;
}
let watch_folder_name = match context.sql.get_raw_config(context, self.folder_config_name) {
Some(name) => name,
None => {
return false;
}
};
let ret_connected = dc_connect_to_configured_imap(context, &self.imap) != 0;
let mut ret_connected = dc_connect_to_configured_imap(context, &self.imap) != 0;
if ret_connected {
if context
.sql
@@ -128,7 +123,12 @@ impl JobThread {
self.imap.configure_folders(context, 0x1);
}
self.imap.set_watch_folder(watch_folder_name);
if let Some(mvbox_name) = context.sql.get_raw_config(context, self.folder_config_name) {
self.imap.set_watch_folder(mvbox_name);
} else {
self.imap.disconnect(context);
ret_connected = false;
}
}
ret_connected
@@ -139,13 +139,13 @@ impl JobThread {
let &(ref lock, ref cvar) = &*self.state.clone();
let mut state = lock.lock().unwrap();
if state.jobs_needed {
if 0 != state.jobs_needed {
info!(
context,
"{}-IDLE will not be started as it was interrupted while not ideling.",
self.name,
);
state.jobs_needed = false;
state.jobs_needed = 0;
return;
}
@@ -170,18 +170,10 @@ impl JobThread {
}
}
if self.connect_to_imap(context) {
info!(context, "{}-IDLE started...", self.name,);
self.imap.idle(context);
info!(context, "{}-IDLE ended.", self.name);
} else {
// It's probably wrong that the thread even runs
// but let's call fake_idle and tell it to not try network at all.
// (once we move to rust-managed threads this problem goes away)
info!(context, "{}-IDLE not connected, fake-idling", self.name);
async_std::task::block_on(async move { self.imap.fake_idle(context, false).await });
info!(context, "{}-IDLE fake-idling finished", self.name);
}
self.connect_to_imap(context);
info!(context, "{}-IDLE started...", self.name,);
self.imap.idle(context);
info!(context, "{}-IDLE ended.", self.name);
self.state.0.lock().unwrap().using_handle = false;
}

View File

@@ -29,44 +29,44 @@ impl From<SignedSecretKey> for Key {
}
}
impl std::convert::TryFrom<Key> for SignedSecretKey {
impl std::convert::TryInto<SignedSecretKey> for Key {
type Error = ();
fn try_from(value: Key) -> Result<Self, Self::Error> {
match value {
fn try_into(self) -> Result<SignedSecretKey, Self::Error> {
match self {
Key::Public(_) => Err(()),
Key::Secret(key) => Ok(key),
}
}
}
impl<'a> std::convert::TryFrom<&'a Key> for &'a SignedSecretKey {
impl<'a> std::convert::TryInto<&'a SignedSecretKey> for &'a Key {
type Error = ();
fn try_from(value: &'a Key) -> Result<Self, Self::Error> {
match value {
fn try_into(self) -> Result<&'a SignedSecretKey, Self::Error> {
match self {
Key::Public(_) => Err(()),
Key::Secret(key) => Ok(key),
}
}
}
impl std::convert::TryFrom<Key> for SignedPublicKey {
impl std::convert::TryInto<SignedPublicKey> for Key {
type Error = ();
fn try_from(value: Key) -> Result<Self, Self::Error> {
match value {
fn try_into(self) -> Result<SignedPublicKey, Self::Error> {
match self {
Key::Public(key) => Ok(key),
Key::Secret(_) => Err(()),
}
}
}
impl<'a> std::convert::TryFrom<&'a Key> for &'a SignedPublicKey {
impl<'a> std::convert::TryInto<&'a SignedPublicKey> for &'a Key {
type Error = ();
fn try_from(value: &'a Key) -> Result<Self, Self::Error> {
match value {
fn try_into(self) -> Result<&'a SignedPublicKey, Self::Error> {
match self {
Key::Public(key) => Ok(key),
Key::Secret(_) => Err(()),
}
@@ -180,15 +180,15 @@ impl Key {
let encoded = base64::encode(&buf);
encoded
.chars()
.enumerate()
.fold(String::new(), |mut res, (i, c)| {
if i > 0 && i % break_every == 0 {
res.push(' ')
}
res.push(c);
.as_bytes()
.chunks(break_every)
.fold(String::new(), |mut res, buf| {
// safe because we are using a base64 encoded string
res += unsafe { std::str::from_utf8_unchecked(buf) };
res += " ";
res
})
.trim()
.to_string()
}

View File

@@ -30,7 +30,6 @@ pub(crate) mod events;
pub use events::*;
mod aheader;
pub mod blob;
pub mod chat;
pub mod chatlist;
pub mod config;
@@ -40,7 +39,6 @@ pub mod contact;
pub mod context;
mod e2ee;
mod imap;
mod imap_client;
pub mod imex;
pub mod job;
mod job_thread;

View File

@@ -6,12 +6,11 @@ use crate::chat;
use crate::config::Config;
use crate::constants::*;
use crate::context::*;
use crate::dc_mimeparser::SystemMessage;
use crate::dc_tools::*;
use crate::error::Error;
use crate::events::Event;
use crate::job::*;
use crate::message::{Message, MsgId};
use crate::message::Message;
use crate::param::*;
use crate::sql;
use crate::stock::StockMessage;
@@ -62,11 +61,14 @@ impl Kml {
Default::default()
}
pub fn parse(context: &Context, content: &[u8]) -> Result<Self, Error> {
ensure!(content.len() <= 1024 * 1024, "kml-file is too large");
pub fn parse(context: &Context, content: impl AsRef<str>) -> Result<Self, Error> {
ensure!(
content.as_ref().len() <= (1024 * 1024),
"A kml-files with {} bytes is larger than reasonably expected.",
content.as_ref().len()
);
let to_parse = String::from_utf8_lossy(content);
let mut reader = quick_xml::Reader::from_str(&to_parse);
let mut reader = quick_xml::Reader::from_str(content.as_ref());
reader.trim_text(true);
let mut kml = Kml::new();
@@ -193,8 +195,10 @@ impl Kml {
// location streaming
pub fn send_locations_to_chat(context: &Context, chat_id: u32, seconds: i64) {
let now = time();
if !(seconds < 0 || chat_id <= DC_CHAT_ID_LAST_SPECIAL) {
let is_sending_locations_before = is_sending_locations_to_chat(context, chat_id);
let mut msg: Message;
let is_sending_locations_before: bool;
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,
&context.sql,
@@ -211,19 +215,19 @@ pub fn send_locations_to_chat(context: &Context, chat_id: u32, seconds: i64) {
.is_ok()
{
if 0 != seconds && !is_sending_locations_before {
let mut msg = Message::new(Viewtype::Text);
msg = Message::new(Viewtype::Text);
msg.text =
Some(context.stock_system_msg(StockMessage::MsgLocationEnabled, "", "", 0));
msg.param.set_cmd(SystemMessage::LocationStreamingEnabled);
msg.param.set_int(Param::Cmd, 8);
chat::send_msg(context, chat_id, &mut msg).unwrap_or_default();
} else if 0 == seconds && is_sending_locations_before {
let stock_str =
context.stock_system_msg(StockMessage::MsgLocationDisabled, "", "", 0);
chat::add_info_msg(context, chat_id, stock_str);
chat::add_device_msg(context, chat_id, stock_str);
}
context.call_cb(Event::ChatModified(chat_id));
if 0 != seconds {
schedule_MAYBE_SEND_LOCATIONS(context, false);
schedule_MAYBE_SEND_LOCATIONS(context, 0i32);
job_add(
context,
Action::MaybeSendLocationsEnded,
@@ -237,8 +241,8 @@ pub fn send_locations_to_chat(context: &Context, chat_id: u32, seconds: i64) {
}
#[allow(non_snake_case)]
fn schedule_MAYBE_SEND_LOCATIONS(context: &Context, force_schedule: bool) {
if force_schedule || !job_action_exists(context, Action::MaybeSendLocations) {
fn schedule_MAYBE_SEND_LOCATIONS(context: &Context, flags: i32) {
if 0 != flags & 0x1 || !job_action_exists(context, Action::MaybeSendLocations) {
job_add(context, Action::MaybeSendLocations, 0, Params::new(), 60);
};
}
@@ -253,9 +257,9 @@ pub fn is_sending_locations_to_chat(context: &Context, chat_id: u32) -> bool {
.unwrap_or_default()
}
pub fn set(context: &Context, latitude: f64, longitude: f64, accuracy: f64) -> bool {
pub fn set(context: &Context, latitude: f64, longitude: f64, accuracy: f64) -> libc::c_int {
if latitude == 0.0 && longitude == 0.0 {
return true;
return 1;
}
let mut continue_streaming = false;
@@ -275,7 +279,7 @@ pub fn set(context: &Context, latitude: f64, longitude: f64, accuracy: f64) -> b
accuracy,
time(),
chat_id,
DC_CONTACT_ID_SELF,
1,
]
) {
warn!(context, "failed to store location {:?}", err);
@@ -284,12 +288,12 @@ pub fn set(context: &Context, latitude: f64, longitude: f64, accuracy: f64) -> b
}
}
if continue_streaming {
context.call_cb(Event::LocationChanged(Some(DC_CONTACT_ID_SELF)));
context.call_cb(Event::LocationChanged(Some(1)));
};
schedule_MAYBE_SEND_LOCATIONS(context, false);
schedule_MAYBE_SEND_LOCATIONS(context, 0);
}
continue_streaming
continue_streaming as libc::c_int
}
pub fn get_range(
@@ -366,6 +370,9 @@ pub fn delete_all(context: &Context) -> Result<(), Error> {
}
pub fn get_kml(context: &Context, chat_id: u32) -> Result<(String, u32), Error> {
let now = time();
let mut location_count = 0;
let mut ret = String::new();
let mut last_added_location_id = 0;
let self_addr = context
@@ -382,24 +389,21 @@ pub fn get_kml(context: &Context, chat_id: u32) -> Result<(String, u32), Error>
Ok((send_begin, send_until, last_sent))
})?;
let now = time();
let mut location_count = 0;
let mut ret = String::new();
if locations_send_begin != 0 && now <= locations_send_until {
if !(locations_send_begin == 0 || now > locations_send_until) {
ret += &format!(
"<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<kml xmlns=\"http://www.opengis.net/kml/2.2\">\n<Document addr=\"{}\">\n",
self_addr,
);
context.sql.query_map(
"SELECT id, latitude, longitude, accuracy, timestamp \
"SELECT id, latitude, longitude, accuracy, timestamp\
FROM locations WHERE from_id=? \
AND timestamp>=? \
AND (timestamp>=? OR timestamp=(SELECT MAX(timestamp) FROM locations WHERE from_id=?)) \
AND independent=0 \
GROUP BY timestamp \
ORDER BY timestamp;",
params![DC_CONTACT_ID_SELF, locations_send_begin, locations_last_sent, DC_CONTACT_ID_SELF],
params![1, locations_send_begin, locations_last_sent, 1],
|row| {
let location_id: i32 = row.get(0)?;
let latitude: f64 = row.get(1)?;
@@ -413,7 +417,7 @@ pub fn get_kml(context: &Context, chat_id: u32) -> Result<(String, u32), Error>
for row in rows {
let (location_id, latitude, longitude, accuracy, timestamp) = row?;
ret += &format!(
"<Placemark><Timestamp><when>{}</when></Timestamp><Point><coordinates accuracy=\"{}\">{},{}</coordinates></Point></Placemark>\n",
"<Placemark><Timestamp><when>{}</when></Timestamp><Point><coordinates accuracy=\"{}\">{},{}</coordinates></Point></Placemark>\n\x00",
timestamp,
accuracy,
longitude,
@@ -425,10 +429,10 @@ pub fn get_kml(context: &Context, chat_id: u32) -> Result<(String, u32), Error>
Ok(())
}
)?;
ret += "</Document>\n</kml>";
}
ensure!(location_count > 0, "No locations processed");
ret += "</Document>\n</kml>";
Ok((ret, last_added_location_id))
}
@@ -472,16 +476,12 @@ pub fn set_kml_sent_timestamp(
Ok(())
}
pub fn set_msg_location_id(
context: &Context,
msg_id: MsgId,
location_id: u32,
) -> Result<(), Error> {
pub fn set_msg_location_id(context: &Context, msg_id: u32, location_id: u32) -> Result<(), Error> {
sql::execute(
context,
&context.sql,
"UPDATE msgs SET location_id=? WHERE id=?;",
params![location_id, msg_id],
params![location_id, msg_id as i32],
)?;
Ok(())
@@ -492,9 +492,9 @@ pub fn save(
chat_id: u32,
contact_id: u32,
locations: &[Location],
independent: bool,
independent: i32,
) -> Result<u32, Error> {
ensure!(chat_id > DC_CHAT_ID_LAST_SPECIAL, "Invalid chat id");
ensure!(chat_id > 9, "Invalid chat id");
context.sql.prepare2(
"SELECT id FROM locations WHERE timestamp=? AND from_id=?",
"INSERT INTO locations\
@@ -507,7 +507,7 @@ pub fn save(
for location in locations {
let exists = stmt_test.exists(params![location.timestamp, contact_id as i32])?;
if independent || !exists {
if 0 != independent || !exists {
stmt_insert.execute(params![
location.timestamp,
contact_id as i32,
@@ -540,7 +540,7 @@ pub fn save(
#[allow(non_snake_case)]
pub fn job_do_DC_JOB_MAYBE_SEND_LOCATIONS(context: &Context, _job: &Job) {
let now = time();
let mut continue_streaming = false;
let mut continue_streaming: libc::c_int = 1;
info!(
context,
" ----------------- MAYBE_SEND_LOCATIONS -------------- ",
@@ -555,7 +555,7 @@ pub fn job_do_DC_JOB_MAYBE_SEND_LOCATIONS(context: &Context, _job: &Job) {
let chat_id: i32 = row.get(0)?;
let locations_send_begin: i64 = row.get(1)?;
let locations_last_sent: i64 = row.get(2)?;
continue_streaming = true;
continue_streaming = 1;
// be a bit tolerant as the timer may not align exactly with time(NULL)
if now - locations_last_sent < (60 - 3) {
@@ -585,11 +585,7 @@ pub fn job_do_DC_JOB_MAYBE_SEND_LOCATIONS(context: &Context, _job: &Job) {
.into_iter()
.filter_map(|(chat_id, locations_send_begin, locations_last_sent)| {
if !stmt_locations
.exists(params![
DC_CONTACT_ID_SELF,
locations_send_begin,
locations_last_sent,
])
.exists(params![1, locations_send_begin, locations_last_sent,])
.unwrap_or_default()
{
// if there is no new location, there's nothing to send.
@@ -607,7 +603,7 @@ pub fn job_do_DC_JOB_MAYBE_SEND_LOCATIONS(context: &Context, _job: &Job) {
// and dc_set_location() is typically called periodically, this is ok)
let mut msg = Message::new(Viewtype::Text);
msg.hidden = true;
msg.param.set_cmd(SystemMessage::LocationOnly);
msg.param.set_int(Param::Cmd, 9);
Some((chat_id, msg))
}
})
@@ -622,8 +618,8 @@ pub fn job_do_DC_JOB_MAYBE_SEND_LOCATIONS(context: &Context, _job: &Job) {
chat::send_msg(context, chat_id as u32, &mut msg).unwrap_or_default();
}
}
if continue_streaming {
schedule_MAYBE_SEND_LOCATIONS(context, true);
if 0 != continue_streaming {
schedule_MAYBE_SEND_LOCATIONS(context, 0x1);
}
}
@@ -651,7 +647,7 @@ pub fn job_do_DC_JOB_MAYBE_SEND_LOC_ENDED(context: &Context, job: &mut Job) {
params![chat_id as i32],
).is_ok() {
let stock_str = context.stock_system_msg(StockMessage::MsgLocationDisabled, "", "", 0);
chat::add_info_msg(context, chat_id, stock_str);
chat::add_device_msg(context, chat_id, stock_str);
context.call_cb(Event::ChatModified(chat_id));
}
}
@@ -669,9 +665,9 @@ mod tests {
let context = dummy_context();
let xml =
b"<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<kml xmlns=\"http://www.opengis.net/kml/2.2\">\n<Document addr=\"user@example.org\">\n<Placemark><Timestamp><when>2019-03-06T21:09:57Z</when></Timestamp><Point><coordinates accuracy=\"32.000000\">9.423110,53.790302</coordinates></Point></Placemark>\n<PlaceMARK>\n<Timestamp><WHEN > \n\t2018-12-13T22:11:12Z\t</WHEN></Timestamp><Point><coordinates aCCuracy=\"2.500000\"> 19.423110 \t , \n 63.790302\n </coordinates></Point></PlaceMARK>\n</Document>\n</kml>";
"<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<kml xmlns=\"http://www.opengis.net/kml/2.2\">\n<Document addr=\"user@example.org\">\n<Placemark><Timestamp><when>2019-03-06T21:09:57Z</when></Timestamp><Point><coordinates accuracy=\"32.000000\">9.423110,53.790302</coordinates></Point></Placemark>\n<PlaceMARK>\n<Timestamp><WHEN > \n\t2018-12-13T22:11:12Z\t</WHEN></Timestamp><Point><coordinates aCCuracy=\"2.500000\"> 19.423110 \t , \n 63.790302\n </coordinates></Point></PlaceMARK>\n</Document>\n</kml>";
let kml = Kml::parse(&context.ctx, xml).expect("parsing failed");
let kml = Kml::parse(&context.ctx, &xml).expect("parsing failed");
assert!(kml.addr.is_some());
assert_eq!(kml.addr.as_ref().unwrap(), "user@example.org",);

View File

@@ -3,10 +3,6 @@ use std::fmt;
use crate::context::Context;
use crate::error::Error;
use async_std::sync::Arc;
use rustls;
use webpki;
use webpki_roots;
#[derive(Copy, Clone, Debug, Display, FromPrimitive)]
#[repr(i32)]
@@ -255,49 +251,27 @@ fn get_readable_flags(flags: i32) -> String {
res
}
pub struct NoCertificateVerification {}
impl rustls::ServerCertVerifier for NoCertificateVerification {
fn verify_server_cert(
&self,
_roots: &rustls::RootCertStore,
_presented_certs: &[rustls::Certificate],
_dns_name: webpki::DNSNameRef<'_>,
_ocsp: &[u8],
) -> Result<rustls::ServerCertVerified, rustls::TLSError> {
Ok(rustls::ServerCertVerified::assertion())
}
}
pub fn dc_build_tls_config(certificate_checks: CertificateChecks) -> rustls::ClientConfig {
let mut config = rustls::ClientConfig::new();
config
.root_store
.add_server_trust_anchors(&webpki_roots::TLS_SERVER_ROOTS);
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::Strict => {}
CertificateChecks::Automatic => {
// Same as AcceptInvalidCertificates for now.
// TODO: use provider database when it becomes available
config
.dangerous()
.set_certificate_verifier(Arc::new(NoCertificateVerification {}));
}
CertificateChecks::AcceptInvalidCertificates => {
// TODO: only accept invalid certs
config
.dangerous()
.set_certificate_verifier(Arc::new(NoCertificateVerification {}));
tls_builder
.danger_accept_invalid_hostnames(true)
.danger_accept_invalid_certs(true)
}
CertificateChecks::Strict => &mut tls_builder,
CertificateChecks::AcceptInvalidHostnames => {
// TODO: only accept invalid hostnames
config
.dangerous()
.set_certificate_verifier(Arc::new(NoCertificateVerification {}));
tls_builder.danger_accept_invalid_hostnames(true)
}
CertificateChecks::AcceptInvalidCertificates => tls_builder
.danger_accept_invalid_hostnames(true)
.danger_accept_invalid_certs(true),
}
config
.build()
}
#[cfg(test)]

View File

@@ -1,7 +1,6 @@
use std::path::{Path, PathBuf};
use deltachat_derive::{FromSql, ToSql};
use failure::Fail;
use crate::chat::{self, Chat};
use crate::constants::*;
@@ -18,134 +17,9 @@ use crate::pgp::*;
use crate::sql;
use crate::stock::StockMessage;
// In practice, the user additionally cuts the string themselves
// pixel-accurate.
/// In practice, the user additionally cuts the string himself pixel-accurate.
const SUMMARY_CHARACTERS: usize = 160;
/// Message ID, including reserved IDs.
///
/// Some message IDs are reserved to identify special message types.
/// This type can represent both the special as well as normal
/// messages.
#[derive(Debug, Copy, Clone, Default, PartialEq, Eq)]
pub struct MsgId(u32);
impl MsgId {
/// Create a new [MsgId].
pub fn new(id: u32) -> MsgId {
MsgId(id)
}
/// Create a new unset [MsgId].
pub fn new_unset() -> MsgId {
MsgId(0)
}
/// Whether the message ID signifies a special message.
///
/// This kind of message ID can not be used for real messages.
pub fn is_special(&self) -> bool {
match self.0 {
0..=DC_MSG_ID_LAST_SPECIAL => true,
_ => false,
}
}
/// Whether the message ID is unset.
///
/// When a message is created it initially has a ID of `0`, which
/// is filled in by a real message ID once the message is saved in
/// the database. This returns true while the message has not
/// been saved and thus not yet been given an actual message ID.
///
/// When this is `true`, [MsgId::is_special] will also always be
/// `true`.
pub fn is_unset(&self) -> bool {
self.0 == 0
}
/// Whether the message ID is the special marker1 marker.
///
/// See the docs of the `dc_get_chat_msgs` C API for details.
pub fn is_marker1(&self) -> bool {
self.0 == DC_MSG_ID_MARKER1
}
/// Whether the message ID is the special day marker.
///
/// See the docs of the `dc_get_chat_msgs` C API for details.
pub fn is_daymarker(&self) -> bool {
self.0 == DC_MSG_ID_DAYMARKER
}
/// Bad evil escape hatch.
///
/// Avoid using this, eventually types should be cleaned up enough
/// that it is no longer necessary.
pub fn to_u32(&self) -> u32 {
self.0
}
}
impl std::fmt::Display for MsgId {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
// Would be nice if we could use match here, but no computed values in ranges.
if self.0 == DC_MSG_ID_MARKER1 {
write!(f, "Msg#Marker1")
} else if self.0 == DC_MSG_ID_DAYMARKER {
write!(f, "Msg#DayMarker")
} else if self.0 <= DC_MSG_ID_LAST_SPECIAL {
write!(f, "Msg#UnknownSpecial")
} else {
write!(f, "Msg#{}", self.0)
}
}
}
/// Allow converting [MsgId] to an SQLite type.
///
/// This allows you to directly store [MsgId] into the database.
///
/// # Errors
///
/// This **does** ensure that no special message IDs are written into
/// the database and the conversion will fail if this is not the case.
impl rusqlite::types::ToSql for MsgId {
fn to_sql(&self) -> rusqlite::Result<rusqlite::types::ToSqlOutput> {
if self.0 <= DC_MSG_ID_LAST_SPECIAL {
return Err(rusqlite::Error::ToSqlConversionFailure(Box::new(
InvalidMsgId.compat(),
)));
}
let val = rusqlite::types::Value::Integer(self.0 as i64);
let out = rusqlite::types::ToSqlOutput::Owned(val);
Ok(out)
}
}
/// Allow converting an SQLite integer directly into [MsgId].
impl rusqlite::types::FromSql for MsgId {
fn column_result(value: rusqlite::types::ValueRef) -> rusqlite::types::FromSqlResult<Self> {
// Would be nice if we could use match here, but alas.
i64::column_result(value).and_then(|val| {
if 0 <= val && val <= std::u32::MAX as i64 {
Ok(MsgId::new(val as u32))
} else {
Err(rusqlite::types::FromSqlError::OutOfRange(val))
}
})
}
}
/// Message ID was invalid.
///
/// This usually occurs when trying to use a message ID of
/// [DC_MSG_ID_LAST_SPECIAL] or below in a situation where this is not
/// possible.
#[derive(Debug, Fail)]
#[fail(display = "Invalid Message ID.")]
pub struct InvalidMsgId;
/// An object representing a single message in memory.
/// The message object is not updated.
/// If you want an update, you have to recreate the object.
@@ -155,7 +29,7 @@ pub struct InvalidMsgId;
/// approx. max. length returned by dc_get_msg_info()
#[derive(Debug, Clone, Default)]
pub struct Message {
pub(crate) id: MsgId,
pub(crate) id: u32,
pub(crate) from_id: u32,
pub(crate) to_id: u32,
pub(crate) chat_id: u32,
@@ -187,105 +61,70 @@ impl Message {
msg
}
pub fn load_from_db(context: &Context, id: MsgId) -> Result<Message, Error> {
ensure!(
!id.is_special(),
"Can not load special message IDs from DB."
);
pub fn load_from_db(context: &Context, id: u32) -> Result<Message, Error> {
context.sql.query_row(
concat!(
"SELECT",
" m.id AS id,",
" rfc724_mid AS rfc724mid,",
" m.mime_in_reply_to AS mime_in_reply_to,",
" m.server_folder AS server_folder,",
" m.server_uid AS server_uid,",
" m.move_state as move_state,",
" m.chat_id AS chat_id,",
" m.from_id AS from_id,",
" m.to_id AS to_id,",
" m.timestamp AS timestamp,",
" m.timestamp_sent AS timestamp_sent,",
" m.timestamp_rcvd AS timestamp_rcvd,",
" m.type AS type,",
" m.state AS state,",
" m.msgrmsg AS msgrmsg,",
" m.txt AS txt,",
" m.param AS param,",
" m.starred AS starred,",
" m.hidden AS hidden,",
" m.location_id AS location,",
" c.blocked AS blocked",
" FROM msgs m LEFT JOIN chats c ON c.id=m.chat_id",
" WHERE m.id=?;"
),
params![id],
|row| {
let mut msg = Message::default();
// msg.id = row.get::<_, AnyMsgId>("id")?;
msg.id = row.get("id")?;
msg.rfc724_mid = row.get::<_, String>("rfc724mid")?;
msg.in_reply_to = row.get::<_, Option<String>>("mime_in_reply_to")?;
msg.server_folder = row.get::<_, Option<String>>("server_folder")?;
msg.server_uid = row.get("server_uid")?;
msg.move_state = row.get("move_state")?;
msg.chat_id = row.get("chat_id")?;
msg.from_id = row.get("from_id")?;
msg.to_id = row.get("to_id")?;
msg.timestamp_sort = row.get("timestamp")?;
msg.timestamp_sent = row.get("timestamp_sent")?;
msg.timestamp_rcvd = row.get("timestamp_rcvd")?;
msg.type_0 = row.get("type")?;
msg.state = row.get("state")?;
msg.is_dc_message = row.get("msgrmsg")?;
"SELECT \
m.id,rfc724_mid,m.mime_in_reply_to,m.server_folder,m.server_uid,m.move_state,m.chat_id, \
m.from_id,m.to_id,m.timestamp,m.timestamp_sent,m.timestamp_rcvd, m.type,m.state,m.msgrmsg,m.txt, \
m.param,m.starred,m.hidden,m.location_id, c.blocked \
FROM msgs m \
LEFT JOIN chats c ON c.id=m.chat_id WHERE m.id=?;",
params![id as i32],
|row| {
let mut msg = Message::default();
msg.id = row.get::<_, i32>(0)? as u32;
msg.rfc724_mid = row.get::<_, String>(1)?;
msg.in_reply_to = row.get::<_, Option<String>>(2)?;
msg.server_folder = row.get::<_, Option<String>>(3)?;
msg.server_uid = row.get(4)?;
msg.move_state = row.get(5)?;
msg.chat_id = row.get(6)?;
msg.from_id = row.get(7)?;
msg.to_id = row.get(8)?;
msg.timestamp_sort = row.get(9)?;
msg.timestamp_sent = row.get(10)?;
msg.timestamp_rcvd = row.get(11)?;
msg.type_0 = row.get(12)?;
msg.state = row.get(13)?;
msg.is_dc_message = row.get(14)?;
let text;
if let rusqlite::types::ValueRef::Text(buf) = row.get_raw("txt") {
if let Ok(t) = String::from_utf8(buf.to_vec()) {
text = t;
} else {
warn!(
context,
concat!(
"dc_msg_load_from_db: could not get ",
"text column as non-lossy utf8 id {}"
),
id
);
text = String::from_utf8_lossy(buf).into_owned();
}
let text;
if let rusqlite::types::ValueRef::Text(buf) = row.get_raw(15) {
if let Ok(t) = String::from_utf8(buf.to_vec()) {
text = t;
} else {
text = "".to_string();
warn!(context, "dc_msg_load_from_db: could not get text column as non-lossy utf8 id {}", id);
text = String::from_utf8_lossy(buf).into_owned();
}
msg.text = Some(text);
} else {
text = "".to_string();
}
msg.text = Some(text);
msg.param = row.get::<_, String>("param")?.parse().unwrap_or_default();
msg.starred = row.get("starred")?;
msg.hidden = row.get("hidden")?;
msg.location_id = row.get("location")?;
msg.chat_blocked = row
.get::<_, Option<Blocked>>("blocked")?
.unwrap_or_default();
msg.param = row.get::<_, String>(16)?.parse().unwrap_or_default();
msg.starred = row.get(17)?;
msg.hidden = row.get(18)?;
msg.location_id = row.get(19)?;
msg.chat_blocked = row.get::<_, Option<Blocked>>(20)?.unwrap_or_default();
Ok(msg)
},
)
Ok(msg)
})
}
pub fn delete_from_db(context: &Context, msg_id: MsgId) {
pub fn delete_from_db(context: &Context, msg_id: u32) {
if let Ok(msg) = Message::load_from_db(context, msg_id) {
sql::execute(
context,
&context.sql,
"DELETE FROM msgs WHERE id=?;",
params![msg.id],
params![msg.id as i32],
)
.ok();
sql::execute(
context,
&context.sql,
"DELETE FROM msgs_mdns WHERE msg_id=?;",
params![msg.id],
params![msg.id as i32],
)
.ok();
}
@@ -306,7 +145,9 @@ impl Message {
}
pub fn get_file(&self, context: &Context) -> Option<PathBuf> {
self.param.get_path(Param::File, context).unwrap_or(None)
self.param
.get(Param::File)
.map(|f| dc_get_abs_path(context, f))
}
/// Check if a message has a location bound to it.
@@ -349,7 +190,7 @@ impl Message {
}
}
pub fn get_id(&self) -> MsgId {
pub fn get_id(&self) -> u32 {
self.id
}
@@ -396,9 +237,8 @@ impl Message {
pub fn get_filebytes(&self, context: &Context) -> u64 {
self.param
.get_path(Param::File, context)
.unwrap_or(None)
.map(|path| dc_get_filebytes(context, &path))
.get(Param::File)
.map(|file| dc_get_filebytes(context, &file))
.unwrap_or_default()
}
@@ -415,7 +255,7 @@ impl Message {
}
pub fn get_showpadlock(&self) -> bool {
self.param.get_int(Param::GuaranteeE2ee).unwrap_or_default() != 0
self.param.get_int(Param::GuranteeE2ee).unwrap_or_default() != 0
}
pub fn get_summary(&mut self, context: &Context, chat: Option<&Chat>) -> Lot {
@@ -476,19 +316,11 @@ impl Message {
pub fn is_info(&self) -> bool {
let cmd = self.param.get_cmd();
self.from_id == DC_CONTACT_ID_INFO as libc::c_uint
|| self.to_id == DC_CONTACT_ID_INFO as libc::c_uint
self.from_id == DC_CONTACT_ID_DEVICE as libc::c_uint
|| self.to_id == DC_CONTACT_ID_DEVICE as libc::c_uint
|| cmd != SystemMessage::Unknown && cmd != SystemMessage::AutocryptSetupMessage
}
/// Whether the message is still being created.
///
/// Messages with attachments might be created before the
/// attachment is ready. In this case some more restrictions on
/// the attachment apply, e.g. if the file to be attached is still
/// being written to or otherwise will still change it can not be
/// copied to the blobdir. Thus those attachments need to be
/// created immediately in the blobdir with a valid filename.
pub fn is_increation(&self) -> bool {
chat::msgtype_has_file(self.type_0) && self.state == MessageState::OutPreparing
}
@@ -561,7 +393,7 @@ impl Message {
context,
&context.sql,
"UPDATE msgs SET param=? WHERE id=?;",
params![self.param.to_string(), self.id],
params![self.param.to_string(), self.id as i32],
)
.is_ok()
}
@@ -671,7 +503,7 @@ impl Lot {
}
}
pub fn get_msg_info(context: &Context, msg_id: MsgId) -> String {
pub fn get_msg_info(context: &Context, msg_id: u32) -> String {
let mut ret = String::new();
let msg = Message::load_from_db(context, msg_id);
@@ -684,11 +516,11 @@ pub fn get_msg_info(context: &Context, msg_id: MsgId) -> String {
let rawtxt: Option<String> = context.sql.query_get_value(
context,
"SELECT txt_raw FROM msgs WHERE id=?;",
params![msg_id],
params![msg_id as i32],
);
if rawtxt.is_none() {
ret += &format!("Cannot load message {}.", msg_id);
ret += &format!("Cannot load message #{}.", msg_id as usize);
return ret;
}
let rawtxt = rawtxt.unwrap_or_default();
@@ -714,14 +546,14 @@ pub fn get_msg_info(context: &Context, msg_id: MsgId) -> String {
ret += "\n";
}
if msg.from_id == DC_CONTACT_ID_INFO || msg.to_id == DC_CONTACT_ID_INFO {
if msg.from_id == 2 || msg.to_id == 2 {
// device-internal message, no further details needed
return ret;
}
if let Ok(rows) = context.sql.query_map(
"SELECT contact_id, timestamp_sent FROM msgs_mdns WHERE msg_id=?;",
params![msg_id],
params![msg_id as i32],
|row| {
let contact_id: i32 = row.get(0)?;
let ts: i64 = row.get(1)?;
@@ -766,7 +598,7 @@ pub fn get_msg_info(context: &Context, msg_id: MsgId) -> String {
if 0 != e2ee_errors & 0x2 {
ret += ", Encrypted, no valid signature";
}
} else if 0 != msg.param.get_int(Param::GuaranteeE2ee).unwrap_or_default() {
} else if 0 != msg.param.get_int(Param::GuranteeE2ee).unwrap_or_default() {
ret += ", Encrypted";
}
@@ -831,21 +663,21 @@ pub fn guess_msgtype_from_suffix(path: &Path) -> Option<(Viewtype, &str)> {
Some(info)
}
pub fn get_mime_headers(context: &Context, msg_id: MsgId) -> Option<String> {
pub fn get_mime_headers(context: &Context, msg_id: u32) -> Option<String> {
context.sql.query_get_value(
context,
"SELECT mime_headers FROM msgs WHERE id=?;",
params![msg_id],
params![msg_id as i32],
)
}
pub fn delete_msgs(context: &Context, msg_ids: &[MsgId]) {
pub fn delete_msgs(context: &Context, msg_ids: &[u32]) {
for msg_id in msg_ids.iter() {
update_msg_chat_id(context, *msg_id, DC_CHAT_ID_TRASH);
job_add(
context,
Action::DeleteMsgOnImap,
msg_id.to_u32() as i32,
*msg_id as libc::c_int,
Params::new(),
0,
);
@@ -854,45 +686,35 @@ pub fn delete_msgs(context: &Context, msg_ids: &[MsgId]) {
if !msg_ids.is_empty() {
context.call_cb(Event::MsgsChanged {
chat_id: 0,
msg_id: MsgId::new(0),
msg_id: 0,
});
job_kill_action(context, Action::Housekeeping);
job_add(context, Action::Housekeeping, 0, Params::new(), 10);
};
}
fn update_msg_chat_id(context: &Context, msg_id: MsgId, chat_id: u32) -> bool {
fn update_msg_chat_id(context: &Context, msg_id: u32, chat_id: u32) -> bool {
sql::execute(
context,
&context.sql,
"UPDATE msgs SET chat_id=? WHERE id=?;",
params![chat_id as i32, msg_id],
params![chat_id as i32, msg_id as i32],
)
.is_ok()
}
pub fn markseen_msgs(context: &Context, msg_ids: &[MsgId]) -> bool {
pub fn markseen_msgs(context: &Context, msg_ids: &[u32]) -> bool {
if msg_ids.is_empty() {
return false;
}
let msgs = context.sql.prepare(
concat!(
"SELECT",
" m.state AS state,",
" c.blocked AS blocked",
" FROM msgs m LEFT JOIN chats c ON c.id=m.chat_id",
" WHERE m.id=? AND m.chat_id>9"
),
"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() {
let query_res = stmt.query_row(params![*id], |row| {
Ok((
row.get::<_, MessageState>("state")?,
row.get::<_, Option<Blocked>>("blocked")?
.unwrap_or_default(),
))
let query_res = stmt.query_row(params![*id as i32], |row| {
Ok((row.get::<_, MessageState>(0)?, row.get::<_, Option<Blocked>>(1)?.unwrap_or_default()))
});
if let Err(rusqlite::Error::QueryReturnedNoRows) = query_res {
continue;
@@ -902,7 +724,7 @@ pub fn markseen_msgs(context: &Context, msg_ids: &[MsgId]) -> bool {
}
Ok(res)
},
}
);
if msgs.is_err() {
@@ -916,12 +738,12 @@ pub fn markseen_msgs(context: &Context, msg_ids: &[MsgId]) -> bool {
if curr_blocked == Blocked::Not {
if curr_state == MessageState::InFresh || curr_state == MessageState::InNoticed {
update_msg_state(context, *id, MessageState::InSeen);
info!(context, "Seen message {}.", id);
info!(context, "Seen message #{}.", id);
job_add(
context,
Action::MarkseenMsgOnImap,
id.to_u32() as i32,
*id as i32,
Params::new(),
0,
);
@@ -936,24 +758,24 @@ pub fn markseen_msgs(context: &Context, msg_ids: &[MsgId]) -> bool {
if send_event {
context.call_cb(Event::MsgsChanged {
chat_id: 0,
msg_id: MsgId::new(0),
msg_id: 0,
});
}
true
}
pub fn update_msg_state(context: &Context, msg_id: MsgId, state: MessageState) -> bool {
pub fn update_msg_state(context: &Context, msg_id: u32, state: MessageState) -> bool {
sql::execute(
context,
&context.sql,
"UPDATE msgs SET state=? WHERE id=?;",
params![state, msg_id],
params![state, msg_id as i32],
)
.is_ok()
}
pub fn star_msgs(context: &Context, msg_ids: &[MsgId], star: bool) -> bool {
pub fn star_msgs(context: &Context, msg_ids: &[u32], star: bool) -> bool {
if msg_ids.is_empty() {
return false;
}
@@ -961,7 +783,7 @@ pub fn star_msgs(context: &Context, msg_ids: &[MsgId], star: bool) -> bool {
.sql
.prepare("UPDATE msgs SET starred=? WHERE id=?;", |mut stmt, _| {
for msg_id in msg_ids.iter() {
stmt.execute(params![star as i32, *msg_id])?;
stmt.execute(params![star as i32, *msg_id as i32])?;
}
Ok(())
})
@@ -990,14 +812,17 @@ pub fn get_summarytext_by_raw(
.stock_str(StockMessage::AcSetupMsgSubject)
.to_string()
} else {
let file_name: String = param
.get_path(Param::File, context)
.unwrap_or(None)
.and_then(|path| {
path.file_name()
.map(|fname| fname.to_string_lossy().into_owned())
})
.unwrap_or_else(|| String::from("ErrFileName"));
let file_name: String = if let Some(file_path) = param.get(Param::File) {
if let Some(file_name) = Path::new(file_path).file_name() {
Some(file_name.to_string_lossy().into_owned())
} else {
None
}
} else {
None
}
.unwrap_or_else(|| "ErrFileName".to_string());
let label = context.stock_str(if viewtype == Viewtype::Audio {
StockMessage::Audio
} else {
@@ -1021,9 +846,7 @@ pub fn get_summarytext_by_raw(
}
if let Some(text) = text {
if text.as_ref().is_empty() {
prefix
} else if prefix.is_empty() {
if prefix.is_empty() {
dc_truncate(text.as_ref(), approx_characters, true).to_string()
} else {
let tmp = format!("{} {}", prefix, text.as_ref());
@@ -1041,8 +864,8 @@ pub fn get_summarytext_by_raw(
// Context functions to work with messages
pub fn exists(context: &Context, msg_id: MsgId) -> bool {
if msg_id.is_special() {
pub fn exists(context: &Context, msg_id: u32) -> bool {
if msg_id <= DC_CHAT_ID_LAST_SPECIAL {
return false;
}
@@ -1071,7 +894,7 @@ pub fn update_msg_move_state(context: &Context, rfc724_mid: &str, state: MoveSta
.is_ok()
}
pub fn set_msg_failed(context: &Context, msg_id: MsgId, error: Option<impl AsRef<str>>) {
pub fn set_msg_failed(context: &Context, msg_id: u32, error: Option<impl AsRef<str>>) {
if let Ok(mut msg) = Message::load_from_db(context, msg_id) {
if msg.state.can_fail() {
msg.state = MessageState::OutFailed;
@@ -1085,7 +908,7 @@ pub fn set_msg_failed(context: &Context, msg_id: MsgId, error: Option<impl AsRef
context,
&context.sql,
"UPDATE msgs SET state=?, param=? WHERE id=?;",
params![msg.state, msg.param.to_string(), msg_id],
params![msg.state, msg.param.to_string(), msg_id as i32],
)
.is_ok()
{
@@ -1097,39 +920,38 @@ pub fn set_msg_failed(context: &Context, msg_id: MsgId, error: Option<impl AsRef
}
}
/// returns Some if an event should be send
/// returns true if an event should be send
pub fn mdn_from_ext(
context: &Context,
from_id: u32,
rfc724_mid: &str,
timestamp_sent: i64,
) -> Option<(u32, MsgId)> {
if from_id <= 9 || rfc724_mid.is_empty() {
return None;
ret_chat_id: &mut u32,
ret_msg_id: &mut u32,
) -> bool {
if from_id <= 9 || rfc724_mid.is_empty() || *ret_chat_id != 0 || *ret_msg_id != 0 {
return false;
}
let mut read_by_all = false;
if let Ok((msg_id, chat_id, chat_type, msg_state)) = context.sql.query_row(
concat!(
"SELECT",
" m.id AS msg_id,",
" c.id AS chat_id,",
" c.type AS type,",
" m.state AS state",
" FROM msgs m LEFT JOIN chats c ON m.chat_id=c.id",
" WHERE rfc724_mid=? AND from_id=1",
" ORDER BY m.id;"
),
"SELECT m.id, c.id, c.type, m.state FROM msgs m \
LEFT JOIN chats c ON m.chat_id=c.id \
WHERE rfc724_mid=? AND from_id=1 \
ORDER BY m.id;",
params![rfc724_mid],
|row| {
Ok((
row.get::<_, MsgId>("msg_id")?,
row.get::<_, u32>("chat_id")?,
row.get::<_, Chattype>("type")?,
row.get::<_, MessageState>("state")?,
row.get::<_, i32>(0)?,
row.get::<_, i32>(1)?,
row.get::<_, Chattype>(2)?,
row.get::<_, MessageState>(3)?,
))
},
) {
let mut read_by_all = false;
*ret_msg_id = msg_id as u32;
*ret_chat_id = chat_id as u32;
// if already marked as MDNS_RCVD msgstate_can_fail() returns false.
// however, it is important, that ret_msg_id is set above as this
@@ -1139,20 +961,20 @@ pub fn mdn_from_ext(
.sql
.exists(
"SELECT contact_id FROM msgs_mdns WHERE msg_id=? AND contact_id=?;",
params![msg_id, from_id as i32,],
params![*ret_msg_id as i32, from_id as i32,],
)
.unwrap_or_default();
if !mdn_already_in_table {
context.sql.execute(
"INSERT INTO msgs_mdns (msg_id, contact_id, timestamp_sent) VALUES (?, ?, ?);",
params![msg_id, from_id as i32, timestamp_sent],
params![*ret_msg_id as i32, from_id as i32, timestamp_sent],
).unwrap_or_default(); // TODO: better error handling
}
// Normal chat? that's quite easy.
if chat_type == Chattype::Single {
update_msg_state(context, msg_id, MessageState::OutMdnRcvd);
update_msg_state(context, *ret_msg_id, MessageState::OutMdnRcvd);
read_by_all = true;
} else {
// send event about new state
@@ -1161,7 +983,7 @@ pub fn mdn_from_ext(
.query_get_value::<_, isize>(
context,
"SELECT COUNT(*) FROM msgs_mdns WHERE msg_id=?;",
params![msg_id],
params![*ret_msg_id as i32],
)
.unwrap_or_default() as usize;
/*
@@ -1177,19 +999,16 @@ pub fn mdn_from_ext(
(S=Sender, R=Recipient)
*/
// for rounding, SELF is already included!
let soll_cnt = (chat::get_chat_contact_cnt(context, chat_id) + 1) / 2;
let soll_cnt = (chat::get_chat_contact_cnt(context, *ret_chat_id) + 1) / 2;
if ist_cnt >= soll_cnt {
update_msg_state(context, msg_id, MessageState::OutMdnRcvd);
update_msg_state(context, *ret_msg_id, MessageState::OutMdnRcvd);
read_by_all = true;
} // else wait for more receipts
}
}
return match read_by_all {
true => Some((chat_id, msg_id)),
false => None,
};
}
None
read_by_all
}
/// The number of messages assigned to real chat (!=deaddrop, !=trash)
@@ -1243,7 +1062,7 @@ pub fn rfc724_mid_cnt(context: &Context, rfc724_mid: &str) -> libc::c_int {
pub(crate) fn rfc724_mid_exists(
context: &Context,
rfc724_mid: &str,
) -> Result<(String, u32, MsgId), Error> {
) -> Result<(String, u32, u32), Error> {
ensure!(!rfc724_mid.is_empty(), "empty rfc724_mid");
context.sql.query_row(
@@ -1252,7 +1071,7 @@ pub(crate) fn rfc724_mid_exists(
|row| {
let server_folder = row.get::<_, Option<String>>(0)?.unwrap_or_default();
let server_uid = row.get(1)?;
let msg_id: MsgId = row.get(2)?;
let msg_id = row.get(2)?;
Ok((server_folder, server_uid, msg_id))
},
@@ -1276,12 +1095,6 @@ pub fn update_server_uid(
}
}
#[allow(dead_code)]
pub fn dc_empty_server(context: &Context, flags: u32) {
job_kill_action(context, Action::EmptyServer);
job_add(context, Action::EmptyServer, flags as i32, Params::new(), 0);
}
#[cfg(test)]
mod tests {
use super::*;
@@ -1317,121 +1130,4 @@ mod tests {
let _msg2 = Message::load_from_db(ctx, msg_id).unwrap();
assert_eq!(_msg2.get_filemime(), None);
}
#[test]
pub fn test_get_summarytext_by_raw() {
let d = test::dummy_context();
let ctx = &d.ctx;
let some_text = Some("bla bla".to_string());
let empty_text = Some("".to_string());
let no_text: Option<String> = None;
let mut some_file = Params::new();
some_file.set(Param::File, "foo.bar");
assert_eq!(
get_summarytext_by_raw(
Viewtype::Text,
some_text.as_ref(),
&mut Params::new(),
50,
&ctx
),
"bla bla" // for simple text, the type is not added to the summary
);
assert_eq!(
get_summarytext_by_raw(Viewtype::Image, no_text.as_ref(), &mut some_file, 50, &ctx,),
"Image" // file names are not added for images
);
assert_eq!(
get_summarytext_by_raw(Viewtype::Video, no_text.as_ref(), &mut some_file, 50, &ctx,),
"Video" // file names are not added for videos
);
assert_eq!(
get_summarytext_by_raw(Viewtype::Gif, no_text.as_ref(), &mut some_file, 50, &ctx,),
"GIF" // file names are not added for GIFs
);
assert_eq!(
get_summarytext_by_raw(
Viewtype::Sticker,
no_text.as_ref(),
&mut some_file,
50,
&ctx,
),
"Sticker" // file names are not added for stickers
);
assert_eq!(
get_summarytext_by_raw(
Viewtype::Voice,
empty_text.as_ref(),
&mut some_file,
50,
&ctx,
),
"Voice message" // file names are not added for voice messages, empty text is skipped
);
assert_eq!(
get_summarytext_by_raw(Viewtype::Voice, no_text.as_ref(), &mut some_file, 50, &ctx),
"Voice message" // file names are not added for voice messages
);
assert_eq!(
get_summarytext_by_raw(
Viewtype::Voice,
some_text.as_ref(),
&mut some_file,
50,
&ctx
),
"Voice message \u{2013} bla bla" // `\u{2013}` explicitly checks for "EN DASH"
);
assert_eq!(
get_summarytext_by_raw(Viewtype::Audio, no_text.as_ref(), &mut some_file, 50, &ctx),
"Audio \u{2013} foo.bar" // file name is added for audio
);
assert_eq!(
get_summarytext_by_raw(
Viewtype::Audio,
empty_text.as_ref(),
&mut some_file,
50,
&ctx,
),
"Audio \u{2013} foo.bar" // file name is added for audio, empty text is not added
);
assert_eq!(
get_summarytext_by_raw(
Viewtype::Audio,
some_text.as_ref(),
&mut some_file,
50,
&ctx
),
"Audio \u{2013} foo.bar \u{2013} bla bla" // file name and text added for audio
);
assert_eq!(
get_summarytext_by_raw(Viewtype::File, some_text.as_ref(), &mut some_file, 50, &ctx),
"File \u{2013} foo.bar \u{2013} bla bla" // file name is added for files
);
let mut asm_file = Params::new();
asm_file.set(Param::File, "foo.bar");
asm_file.set_cmd(SystemMessage::AutocryptSetupMessage);
assert_eq!(
get_summarytext_by_raw(Viewtype::File, no_text.as_ref(), &mut asm_file, 50, &ctx),
"Autocrypt Setup Message" // file name is not added for autocrypt setup messages
);
}
}

View File

@@ -1,3 +1,4 @@
use std::path::Path;
use std::ptr;
use chrono::TimeZone;
@@ -22,7 +23,6 @@ use crate::dc_tools::*;
use crate::e2ee::*;
use crate::error::Error;
use crate::location;
use crate::message::MsgId;
use crate::message::{self, Message};
use crate::param::*;
use crate::stock::StockMessage;
@@ -108,7 +108,7 @@ impl<'a> MimeFactory<'a> {
Ok(())
}
pub fn load_mdn(context: &'a Context, msg_id: MsgId) -> Result<MimeFactory, Error> {
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 ...
@@ -257,7 +257,7 @@ impl<'a> MimeFactory<'a> {
e2ee_guaranteed = self
.msg
.param
.get_int(Param::GuaranteeE2ee)
.get_int(Param::GuranteeE2ee)
.unwrap_or_default()
!= 0;
}
@@ -484,7 +484,6 @@ impl<'a> MimeFactory<'a> {
if !meta_part.is_null() {
mailmime_smart_add_part(message, meta_part);
}
if self.msg.param.exists(Param::SetLatitude) {
let param = &self.msg.param;
let kml_file = location::get_message_kml(
@@ -501,21 +500,18 @@ impl<'a> MimeFactory<'a> {
}
if location::is_sending_locations_to_chat(context, self.msg.chat_id) {
match location::get_kml(context, self.msg.chat_id) {
Ok((kml_content, last_added_location_id)) => {
wrapmime::add_filename_part(
message,
"location.kml",
"application/vnd.google-earth.kml+xml",
&kml_content,
)?;
if !self.msg.param.exists(Param::SetLatitude) {
// otherwise, the independent location is already filed
self.out_last_added_location_id = last_added_location_id;
}
}
Err(err) => {
warn!(context, "mimefactory: could not get location: {}", err);
if let Ok((kml_file, last_added_location_id)) =
location::get_kml(context, self.msg.chat_id)
{
wrapmime::add_filename_part(
message,
"location.kml",
"application/vnd.google-earth.kml+xml",
&kml_file,
)?;
if !self.msg.param.exists(Param::SetLatitude) {
// otherwise, the independent location is already filed
self.out_last_added_location_id = last_added_location_id;
}
}
}
@@ -542,7 +538,7 @@ impl<'a> MimeFactory<'a> {
!= self
.msg
.param
.get_int(Param::GuaranteeE2ee)
.get_int(Param::GuranteeE2ee)
.unwrap_or_default()
{
self.context
@@ -658,7 +654,9 @@ impl<'a> MimeFactory<'a> {
Ok(())
}
pub fn load_msg(context: &Context, msg_id: MsgId) -> Result<MimeFactory, Error> {
pub fn load_msg(context: &Context, msg_id: u32) -> Result<MimeFactory, Error> {
ensure!(msg_id > DC_CHAT_ID_LAST_SPECIAL, "Invalid chat id");
let msg = Message::load_from_db(context, msg_id)?;
let chat = Chat::load_from_db(context, msg.chat_id)?;
let mut factory = MimeFactory::new(context, msg);
@@ -723,7 +721,7 @@ impl<'a> MimeFactory<'a> {
}
let row = context.sql.query_row(
"SELECT mime_in_reply_to, mime_references FROM msgs WHERE id=?",
params![factory.msg.id],
params![factory.msg.id as i32],
|row| {
let in_reply_to: String = row.get(0)?;
let references: String = row.get(1)?;
@@ -796,21 +794,26 @@ fn build_body_file(
msg: &Message,
base_name: &str,
) -> Result<(*mut Mailmime, String), Error> {
let blob = msg
.param
.get_blob(Param::File, context, true)?
.ok_or_else(|| format_err!("msg has no filename"))?;
let suffix = blob.suffix().unwrap_or("dat");
let path_filename = match msg.param.get(Param::File) {
None => {
bail!("msg has no filename");
}
Some(path) => path,
};
let suffix = dc_get_filesuffix_lc(path_filename).unwrap_or_else(|| "dat".into());
// Get file name to use for sending. For privacy purposes, we do
// not transfer the original filenames eg. for images; these names
// are normally not needed and contain timestamps, running numbers
// etc.
let filename_to_send: String = match msg.type_0 {
/* get file name to use for sending
(for privacy purposes, we do not transfer the original filenames eg. for images;
these names are normally not needed and contain timestamps, running numbers etc.) */
let filename_to_send = match msg.type_0 {
Viewtype::Voice => chrono::Utc
.timestamp(msg.timestamp_sort as i64, 0)
.format(&format!("voice-message_%Y-%m-%d_%H-%M-%S.{}", &suffix))
.format(&format!("voice-message_%Y-%m-%d_%H-%M-%S.{}", suffix))
.to_string(),
Viewtype::Audio => Path::new(path_filename)
.file_name()
.map(|c| c.to_string_lossy().to_string())
.unwrap_or_default(),
Viewtype::Image | Viewtype::Gif => format!(
"{}.{}",
if base_name.is_empty() {
@@ -821,14 +824,18 @@ fn build_body_file(
&suffix,
),
Viewtype::Video => format!("video.{}", &suffix),
_ => blob.as_file_name().to_string(),
_ => Path::new(path_filename)
.file_name()
.map(|c| c.to_string_lossy().to_string())
.unwrap_or_default(),
};
/* check mimetype */
let mimetype = match msg.param.get(Param::MimeType) {
Some(mtype) => mtype,
None => {
if let Some(res) = message::guess_msgtype_from_suffix(blob.as_rel_path()) {
let path = Path::new(path_filename);
if let Some(res) = message::guess_msgtype_from_suffix(&path) {
res.1
} else {
"application/octet-stream"
@@ -888,7 +895,7 @@ fn build_body_file(
wrapmime::append_ct_param(content, "name", &filename_encoded)?;
let mime_sub = mailmime_new_empty(content, mime_fields);
let abs_path = blob.to_abs_path().to_c_string()?;
let abs_path = dc_get_abs_path(context, path_filename).to_c_string()?;
mailmime_set_body_file(mime_sub, dc_strdup(abs_path.as_ptr()));
Ok((mime_sub, filename_to_send))
}
@@ -905,11 +912,13 @@ pub(crate) fn vec_contains_lowercase(vec: &[String], part: &str) -> bool {
}
fn is_file_size_okay(context: &Context, msg: &Message) -> bool {
match msg.param.get_path(Param::File, context).unwrap_or(None) {
Some(path) => {
let bytes = dc_get_filebytes(context, &path);
bytes <= (49 * 1024 * 1024 / 4 * 3)
}
None => false,
let mut file_size_okay = true;
let path = msg.param.get(Param::File).unwrap_or_default();
let bytes = dc_get_filebytes(context, &path);
if bytes > (49 * 1024 * 1024 / 4 * 3) {
file_size_okay = false;
}
file_size_okay
}

View File

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

View File

@@ -1,12 +1,9 @@
use std::collections::BTreeMap;
use std::fmt;
use std::path::PathBuf;
use std::str;
use num_traits::FromPrimitive;
use crate::blob::{BlobError, BlobObject};
use crate::context::Context;
use crate::dc_mimeparser::SystemMessage;
use crate::error;
@@ -25,7 +22,7 @@ pub enum Param {
/// For Messages
MimeType = b'm',
/// For Messages: message is encryoted, outgoing: guarantee E2EE or the message is not send
GuaranteeE2ee = b'c',
GuranteeE2ee = b'c',
/// For Messages: decrypted with validation errors or without mutual set, if neither
/// 'c' nor 'e' are preset, the messages is only transport encrypted.
ErroneousE2ee = b'e',
@@ -49,14 +46,6 @@ pub enum Param {
/// For Messages
Error = b'L',
/// For Messages: space-separated list of messaged IDs of forwarded copies.
///
/// This is used when a [Message] is in the
/// [MessageState::OutPending] state but is already forwarded.
/// In this case the forwarded messages are written to the
/// database and their message IDs are added to this parameter of
/// the original message, which is also saved in the database.
/// When the original message is then finally sent this parameter
/// is used to also send all the forwarded messages.
PrepForwards = b'P',
/// For Jobs
SetLatitude = b'l',
@@ -76,8 +65,6 @@ pub enum Param {
ProfileImage = b'i',
// For Chats
Selftalk = b'K',
// For Chats
Devicetalk = b'D',
// For QR
Auth = b's',
// For QR
@@ -199,82 +186,11 @@ impl Params {
.unwrap_or_default()
}
/// Set the parameter behind `Param::Cmd`.
pub fn set_cmd(&mut self, value: SystemMessage) {
self.set_int(Param::Cmd, value as i32);
}
/// Get the given parameter and parse as `f64`.
pub fn get_float(&self, key: Param) -> Option<f64> {
self.get(key).and_then(|s| s.parse().ok())
}
/// Gets the given parameter and parse as [ParamsFile].
///
/// See also [Params::get_blob] and [Params::get_path] which may
/// be more convenient.
pub fn get_file<'a>(
&self,
key: Param,
context: &'a Context,
) -> Result<Option<ParamsFile<'a>>, BlobError> {
let val = match self.get(key) {
Some(val) => val,
None => return Ok(None),
};
ParamsFile::from_param(context, val).map(|file| Some(file))
}
/// Gets the parameter and returns a [BlobObject] for it.
///
/// This parses the parameter value as a [ParamsFile] and than
/// tries to return a [BlobObject] for that file. If the file is
/// not yet a valid blob, one will be created by copying the file
/// only if `create` is set to `true`, otherwise the a [BlobError]
/// will result.
///
/// Note that in the [ParamsFile::FsPath] case the blob can be
/// created without copying if the path already referes to a valid
/// blob. If so a [BlobObject] will be returned regardless of the
/// `create` argument.
pub fn get_blob<'a>(
&self,
key: Param,
context: &'a Context,
create: bool,
) -> Result<Option<BlobObject<'a>>, BlobError> {
let val = match self.get(key) {
Some(val) => val,
None => return Ok(None),
};
let file = ParamsFile::from_param(context, val)?;
let blob = match file {
ParamsFile::FsPath(path) => match create {
true => BlobObject::create_from_path(context, path)?,
false => BlobObject::from_path(context, path)?,
},
ParamsFile::Blob(blob) => blob,
};
Ok(Some(blob))
}
/// Gets the parameter and returns a [PathBuf] for it.
///
/// This parses the parameter value as a [ParamsFile] and returns
/// a [PathBuf] to the file.
pub fn get_path(&self, key: Param, context: &Context) -> Result<Option<PathBuf>, BlobError> {
let val = match self.get(key) {
Some(val) => val,
None => return Ok(None),
};
let file = ParamsFile::from_param(context, val)?;
let path = match file {
ParamsFile::FsPath(path) => path,
ParamsFile::Blob(blob) => blob.to_abs_path(),
};
Ok(Some(path))
}
/// Set the given paramter to the passed in `i32`.
pub fn set_int(&mut self, key: Param, value: i32) -> &mut Self {
self.set(key, format!("{}", value));
@@ -288,42 +204,10 @@ impl Params {
}
}
/// The value contained in [Param::File].
///
/// Because the only way to construct this object is from a valid
/// UTF-8 string it is always safe to convert the value contained
/// within the [ParamsFile::FsPath] back to a [String] or [&str].
/// Despite the type itself does not guarantee this.
#[derive(Debug, Clone, PartialEq, Eq)]
pub enum ParamsFile<'a> {
FsPath(PathBuf),
Blob(BlobObject<'a>),
}
impl<'a> ParamsFile<'a> {
/// Parse the [Param::File] value into an object.
///
/// If the value was stored into the [Params] correctly this
/// should not fail.
pub fn from_param(context: &'a Context, src: &str) -> Result<ParamsFile<'a>, BlobError> {
let param = match src.starts_with("$BLOBDIR/") {
true => ParamsFile::Blob(BlobObject::from_name(context, src.to_string())?),
false => ParamsFile::FsPath(PathBuf::from(src)),
};
Ok(param)
}
}
#[cfg(test)]
mod tests {
use super::*;
use std::fs;
use std::path::Path;
use crate::blob::BlobErrorKind;
use crate::test_utils::*;
#[test]
fn test_dc_param() {
let mut p1: Params = "\r\n\r\na=1\nf=2\n\nc = 3 ".parse().unwrap();
@@ -341,7 +225,7 @@ mod tests {
p1.set(Param::Forwarded, "foo")
.set_int(Param::File, 2)
.remove(Param::GuaranteeE2ee)
.remove(Param::GuranteeE2ee)
.set_int(Param::Duration, 4);
assert_eq!(p1.to_string(), "a=foo\nd=4\nf=2");
@@ -367,64 +251,4 @@ mod tests {
.unwrap();
assert_eq!(p1.get(Param::Forwarded).unwrap(), "cli%40deltachat.de");
}
#[test]
fn test_params_file_fs_path() {
let t = dummy_context();
if let ParamsFile::FsPath(p) = ParamsFile::from_param(&t.ctx, "/foo/bar/baz").unwrap() {
assert_eq!(p, Path::new("/foo/bar/baz"));
} else {
assert!(false, "Wrong enum variant");
}
}
#[test]
fn test_params_file_blob() {
let t = dummy_context();
if let ParamsFile::Blob(b) = ParamsFile::from_param(&t.ctx, "$BLOBDIR/foo").unwrap() {
assert_eq!(b.as_name(), "$BLOBDIR/foo");
} else {
assert!(false, "Wrong enum variant");
}
}
// Tests for Params::get_file(), Params::get_path() and Params::get_blob().
#[test]
fn test_params_get_fileparam() {
let t = dummy_context();
let fname = t.dir.path().join("foo");
let mut p = Params::new();
p.set(Param::File, fname.to_str().unwrap());
let file = p.get_file(Param::File, &t.ctx).unwrap().unwrap();
assert_eq!(file, ParamsFile::FsPath(fname.clone()));
let path = p.get_path(Param::File, &t.ctx).unwrap().unwrap();
assert_eq!(path, fname);
// Blob does not exist yet, expect BlobError.
let err = p.get_blob(Param::File, &t.ctx, false).unwrap_err();
assert_eq!(err.kind(), BlobErrorKind::WrongBlobdir);
fs::write(fname, b"boo").unwrap();
let blob = p.get_blob(Param::File, &t.ctx, true).unwrap().unwrap();
assert_eq!(
blob,
BlobObject::from_name(&t.ctx, "foo".to_string()).unwrap()
);
// Blob in blobdir, expect blob.
let bar = t.ctx.get_blobdir().join("bar");
p.set(Param::File, bar.to_str().unwrap());
let blob = p.get_blob(Param::File, &t.ctx, false).unwrap().unwrap();
assert_eq!(
blob,
BlobObject::from_name(&t.ctx, "bar".to_string()).unwrap()
);
p.remove(Param::File);
assert!(p.get_file(Param::File, &t.ctx).unwrap().is_none());
assert!(p.get_path(Param::File, &t.ctx).unwrap().is_none());
assert!(p.get_blob(Param::File, &t.ctx, false).unwrap().is_none());
}
}

View File

@@ -5,7 +5,7 @@ use std::io::Cursor;
use pgp::armor::BlockType;
use pgp::composed::{
Deserializable, KeyType as PgpKeyType, Message, SecretKeyParamsBuilder, SignedPublicKey,
SignedPublicSubKey, SignedSecretKey, SubkeyParamsBuilder,
SignedSecretKey, SubkeyParamsBuilder,
};
use pgp::crypto::{HashAlgorithm, SymmetricKeyAlgorithm};
use pgp::types::{CompressionAlgorithm, KeyTrait, SecretKeyTrait, StringToKey};
@@ -97,29 +97,18 @@ pub fn create_keypair(addr: impl AsRef<str>) -> Option<(Key, Key)> {
Some((Key::Public(public_key), Key::Secret(private_key)))
}
/// Select subkey of the public key to use for encryption.
///
/// Currently the first subkey is selected.
fn select_pk_for_encryption(key: &SignedPublicKey) -> Option<&SignedPublicSubKey> {
key.public_subkeys.iter().find(|_k|
// TODO: check if it is an encryption subkey
true)
}
pub fn pk_encrypt(
plain: &[u8],
public_keys_for_encryption: &Keyring,
private_key_for_signing: Option<&Key>,
) -> Result<String, Error> {
let lit_msg = Message::new_literal_bytes("", plain);
let pkeys: Vec<&SignedPublicSubKey> = public_keys_for_encryption
let pkeys: Vec<&SignedPublicKey> = public_keys_for_encryption
.keys()
.iter()
.filter_map(|key| {
key.as_ref()
.try_into()
.ok()
.and_then(select_pk_for_encryption)
let k: &Key = &key;
k.try_into().ok()
})
.collect();

View File

@@ -90,8 +90,7 @@ fn decode_openpgp(context: &Context, qr: &str) -> Lot {
// what is up with that param name?
let name = if let Some(encoded_name) = param.get(Param::SetLongitude) {
let encoded_name = encoded_name.replace("+", "%20"); // sometimes spaces are encoded as `+`
match percent_decode_str(&encoded_name).decode_utf8() {
match percent_decode_str(encoded_name).decode_utf8() {
Ok(name) => name.to_string(),
Err(err) => return format_err!("Invalid name: {}", err).into(),
}
@@ -105,8 +104,7 @@ fn decode_openpgp(context: &Context, qr: &str) -> Lot {
let grpname = if grpid.is_some() {
if let Some(encoded_name) = param.get(Param::GroupName) {
let encoded_name = encoded_name.replace("+", "%20"); // sometimes spaces are encoded as `+`
match percent_decode_str(&encoded_name).decode_utf8() {
match percent_decode_str(encoded_name).decode_utf8() {
Ok(name) => Some(name.to_string()),
Err(err) => return format_err!("Invalid group name: {}", err).into(),
}
@@ -150,7 +148,7 @@ fn decode_openpgp(context: &Context, qr: &str) -> Lot {
let (id, _) = chat::create_or_lookup_by_contact_id(context, lot.id, Blocked::Deaddrop)
.unwrap_or_default();
chat::add_info_msg(
chat::add_device_msg(
context,
id,
format!("{} verified.", peerstate.addr.unwrap_or_default()),
@@ -188,7 +186,7 @@ fn decode_mailto(context: &Context, qr: &str) -> Lot {
let addr = if let Some(query_index) = payload.find('?') {
&payload[..query_index]
} else {
payload
return format_err!("Invalid mailto found").into();
};
let addr = match normalize_address(addr) {
@@ -407,21 +405,13 @@ mod tests {
&ctx.ctx,
"mailto:stress@test.local?subject=hello&body=world",
);
println!("{:?}", res);
assert_eq!(res.get_state(), LotState::QrAddr);
assert_ne!(res.get_id(), 0);
let contact = Contact::get_by_id(&ctx.ctx, res.get_id()).unwrap();
assert_eq!(contact.get_addr(), "stress@test.local");
let res = check_qr(&ctx.ctx, "mailto:no-questionmark@example.org");
assert_eq!(res.get_state(), LotState::QrAddr);
assert_ne!(res.get_id(), 0);
let contact = Contact::get_by_id(&ctx.ctx, res.get_id()).unwrap();
assert_eq!(contact.get_addr(), "no-questionmark@example.org");
let res = check_qr(&ctx.ctx, "mailto:no-addr");
assert_eq!(res.get_state(), LotState::QrError);
assert!(res.get_text1().is_some());
}
#[test]
@@ -444,13 +434,12 @@ mod tests {
let res = check_qr(
&ctx.ctx,
"OPENPGP4FPR:79252762C34C5096AF57958F4FC3D21A81B0F0A7#a=cli%40deltachat.de&g=test%20%3F+test%20%21&x=h-0oKQf2CDK&i=9JEXlxAqGM0&s=0V7LzL9cxRL"
"OPENPGP4FPR:79252762C34C5096AF57958F4FC3D21A81B0F0A7#a=cli%40deltachat.de&g=testtesttest&x=h-0oKQf2CDK&i=9JEXlxAqGM0&s=0V7LzL9cxRL"
);
println!("{:?}", res);
assert_eq!(res.get_state(), LotState::QrAskVerifyGroup);
assert_ne!(res.get_id(), 0);
assert_eq!(res.get_text1().unwrap(), "test ? test !");
let contact = Contact::get_by_id(&ctx.ctx, res.get_id()).unwrap();
assert_eq!(contact.get_addr(), "cli@deltachat.de");
@@ -462,7 +451,7 @@ mod tests {
let res = check_qr(
&ctx.ctx,
"OPENPGP4FPR:79252762C34C5096AF57958F4FC3D21A81B0F0A7#a=cli%40deltachat.de&n=J%C3%B6rn%20P.+P.&i=TbnwJ6lSvD5&s=0ejvbdFSQxB"
"OPENPGP4FPR:79252762C34C5096AF57958F4FC3D21A81B0F0A7#a=cli%40deltachat.de&n=&i=TbnwJ6lSvD5&s=0ejvbdFSQxB"
);
println!("{:?}", res);
@@ -471,6 +460,5 @@ mod tests {
let contact = Contact::get_by_id(&ctx.ctx, res.get_id()).unwrap();
assert_eq!(contact.get_addr(), "cli@deltachat.de");
assert_eq!(contact.get_name(), "Jörn P. P.");
}
}

View File

@@ -257,7 +257,7 @@ fn send_handshake_msg(
msg.type_0 = Viewtype::Text;
msg.text = Some(format!("Secure-Join: {}", step));
msg.hidden = true;
msg.param.set_cmd(SystemMessage::SecurejoinMessage);
msg.param.set_int(Param::Cmd, 7);
if step.is_empty() {
msg.param.remove(Param::Arg);
} else {
@@ -278,7 +278,7 @@ fn send_handshake_msg(
ForcePlaintext::AddAutocryptHeader as i32,
);
} else {
msg.param.set_int(Param::GuaranteeE2ee, 1);
msg.param.set_int(Param::GuranteeE2ee, 1);
}
// TODO. handle cleanup on error
chat::send_msg(context, contact_chat_id, &mut msg).unwrap_or_default();
@@ -638,7 +638,7 @@ fn secure_connection_established(context: &Context, contact_chat_id: u32) {
"?"
};
let msg = context.stock_string_repl_str(StockMessage::ContactVerified, addr);
chat::add_info_msg(context, contact_chat_id, msg);
chat::add_device_msg(context, contact_chat_id, msg);
emit_event!(context, Event::ChatModified(contact_chat_id));
}
@@ -654,7 +654,7 @@ fn could_not_establish_secure_connection(context: &Context, contact_chat_id: u32
},
);
chat::add_info_msg(context, contact_chat_id, &msg);
chat::add_device_msg(context, contact_chat_id, &msg);
error!(context, "{} ({})", &msg, details);
}
@@ -735,7 +735,7 @@ pub fn handle_degrade_event(context: &Context, peerstate: &Peerstate) -> Result<
};
let msg = context.stock_string_repl_str(StockMessage::ContactSetupChanged, peeraddr);
chat::add_info_msg(context, contact_chat_id, msg);
chat::add_device_msg(context, contact_chat_id, msg);
emit_event!(context, Event::ChatModified(contact_chat_id));
}
}

View File

@@ -5,7 +5,7 @@ use crate::constants::*;
use crate::context::Context;
use crate::error::Error;
use crate::events::Event;
use crate::login_param::{dc_build_tls_config, LoginParam};
use crate::login_param::{dc_build_tls, LoginParam};
use crate::oauth2::*;
#[derive(DebugStub)]
@@ -44,59 +44,52 @@ impl Smtp {
}
/// Connect using the provided login params
pub fn connect(&mut self, context: &Context, lp: &LoginParam) -> Result<(), Error> {
pub fn connect(&mut self, context: &Context, lp: &LoginParam) -> bool {
if self.is_connected() {
warn!(context, "SMTP already connected.");
return Ok(());
return true;
}
if lp.send_server.is_empty() || lp.send_port == 0 {
context.call_cb(Event::ErrorNetwork("SMTP bad parameters.".into()));
bail!("SMTP Bad parameters");
}
self.from = match EmailAddress::new(lp.addr.clone()) {
Ok(addr) => Some(addr),
Err(err) => {
bail!("invalid login address {}: {}", lp.addr, err);
}
self.from = if let Ok(addr) = EmailAddress::new(lp.addr.clone()) {
Some(addr)
} else {
None
};
if self.from.is_none() {
// TODO: print error
return false;
}
let domain = &lp.send_server;
let port = lp.send_port as u16;
let tls_config = dc_build_tls_config(lp.smtp_certificate_checks);
let tls_parameters = ClientTlsParameters::new(domain.to_string(), tls_config);
let tls = dc_build_tls(lp.smtp_certificate_checks).unwrap();
let tls_parameters = ClientTlsParameters::new(domain.to_string(), tls);
let (creds, mechanism) = if 0 != lp.server_flags & (DC_LP_AUTH_OAUTH2 as i32) {
let creds = if 0 != lp.server_flags & (DC_LP_AUTH_OAUTH2 as i32) {
// oauth2
let addr = &lp.addr;
let send_pw = &lp.send_pw;
let access_token = dc_get_oauth2_access_token(context, addr, send_pw, false);
ensure!(
access_token.is_some(),
"could not get oaut2_access token addr={}",
addr
);
if access_token.is_none() {
return false;
}
let user = &lp.send_user;
(
lettre::smtp::authentication::Credentials::new(
user.to_string(),
access_token.unwrap_or_default(),
),
vec![lettre::smtp::authentication::Mechanism::Xoauth2],
lettre::smtp::authentication::Credentials::new(
user.to_string(),
access_token.unwrap_or_default(),
)
} else {
// plain
let user = lp.send_user.clone();
let pw = lp.send_pw.clone();
(
lettre::smtp::authentication::Credentials::new(user, pw),
vec![
lettre::smtp::authentication::Mechanism::Plain,
lettre::smtp::authentication::Mechanism::Login,
],
)
lettre::smtp::authentication::Credentials::new(user, pw)
};
let security = if 0
@@ -112,38 +105,28 @@ impl Smtp {
let client = client
.smtp_utf8(true)
.credentials(creds)
.authentication_mechanism(mechanism)
.connection_reuse(lettre::smtp::ConnectionReuseParameters::ReuseUnlimited);
let mut trans = client.transport();
match trans.connect() {
Ok(()) => {
self.transport = Some(trans);
self.transport_connected = true;
context.call_cb(Event::SmtpConnected(format!(
"SMTP-LOGIN as {} ok",
lp.send_user,
)));
return Ok(());
}
Err(err) => {
bail!("SMTP: failed to connect {:?}", err);
}
}
self.transport = Some(client.transport());
context.call_cb(Event::SmtpConnected(format!(
"SMTP-LOGIN as {} ok",
lp.send_user,
)));
true
}
Err(err) => {
bail!("SMTP: failed to setup connection {:?}", err);
warn!(context, "SMTP: failed to establish connection {:?}", err);
false
}
}
}
/// SMTP-Send a prepared mail to recipients.
/// on successful send out Ok() is returned.
/// returns boolean whether send was successful.
pub fn send<'a>(
&mut self,
context: &Context,
recipients: Vec<EmailAddress>,
message: Vec<u8>,
job_id: u32,
) -> Result<(), Error> {
let message_len = message.len();
@@ -154,15 +137,12 @@ impl Smtp {
.join(",");
if let Some(ref mut transport) = self.transport {
let envelope = match Envelope::new(self.from.clone(), recipients) {
Ok(env) => env,
Err(err) => {
bail!("{}", err);
}
};
let envelope = Envelope::new(self.from.clone(), recipients);
ensure!(envelope.is_ok(), "internal smtp-message construction fail");
let envelope = envelope.unwrap();
let mail = SendableEmail::new(
envelope,
format!("{}", job_id), // only used for internal logging
"mail-id".into(), // TODO: random id
message,
);

View File

@@ -11,6 +11,8 @@ use crate::error::{Error, Result};
use crate::param::*;
use crate::peerstate::*;
const DC_OPEN_READONLY: usize = 0x01;
/// A wrapper around the underlying Sqlite3 object.
#[derive(DebugStub)]
pub struct Sql {
@@ -40,8 +42,8 @@ impl Sql {
}
// return true on success, false on failure
pub fn open(&self, context: &Context, dbfile: &std::path::Path, readonly: bool) -> bool {
match open(context, self, dbfile, readonly) {
pub fn open(&self, context: &Context, dbfile: &std::path::Path, flags: libc::c_int) -> bool {
match open(context, self, dbfile, flags) {
Ok(_) => true,
Err(Error::SqlAlreadyOpen) => false,
Err(_) => {
@@ -92,7 +94,8 @@ impl Sql {
self.start_stmt(sql.to_string());
self.with_conn(|conn| {
let stmt = conn.prepare(sql)?;
g(stmt, conn)
let res = g(stmt, conn)?;
Ok(res)
})
}
@@ -105,7 +108,8 @@ impl Sql {
let stmt1 = conn.prepare(sql1)?;
let stmt2 = conn.prepare(sql2)?;
g(stmt1, stmt2, conn)
let res = g(stmt1, stmt2, conn)?;
Ok(res)
})
}
@@ -316,7 +320,7 @@ fn open(
context: &Context,
sql: &Sql,
dbfile: impl AsRef<std::path::Path>,
readonly: bool,
flags: libc::c_int,
) -> Result<()> {
if sql.is_open() {
error!(
@@ -328,7 +332,7 @@ fn open(
}
let mut open_flags = OpenFlags::SQLITE_OPEN_NO_MUTEX;
if readonly {
if 0 != (flags & DC_OPEN_READONLY as i32) {
open_flags.insert(OpenFlags::SQLITE_OPEN_READ_ONLY);
} else {
open_flags.insert(OpenFlags::SQLITE_OPEN_READ_WRITE);
@@ -347,7 +351,7 @@ fn open(
*sql.pool.write().unwrap() = Some(pool);
}
if !readonly {
if 0 == flags & DC_OPEN_READONLY as i32 {
let mut exists_before_update = 0;
let mut dbversion_before_update = 0;
/* Init tables to dbversion=0 */
@@ -383,8 +387,8 @@ fn open(
)?;
sql.execute(
"INSERT INTO contacts (id,name,origin) VALUES \
(1,'self',262144), (2,'info',262144), (3,'rsvd',262144), \
(4,'rsvd',262144), (5,'device',262144), (6,'rsvd',262144), \
(1,'self',262144), (2,'device',262144), (3,'rsvd',262144), \
(4,'rsvd',262144), (5,'rsvd',262144), (6,'rsvd',262144), \
(7,'rsvd',262144), (8,'rsvd',262144), (9,'rsvd',262144);",
params![],
)?;

View File

@@ -96,7 +96,7 @@ pub enum StockMessage {
SelfTalkSubTitle = 50,
#[strum(props(fallback = "Cannot login as %1$s."))]
CannotLogin = 60,
#[strum(props(fallback = "Could not connect to %1$s: %2$s"))]
#[strum(props(fallback = "Response from %1$s: %2$s"))]
ServerResponse = 61,
#[strum(props(fallback = "%1$s by %2$s."))]
MsgActionByUser = 62,
@@ -110,8 +110,6 @@ pub enum StockMessage {
Location = 66,
#[strum(props(fallback = "Sticker"))]
Sticker = 67,
#[strum(props(fallback = "Device Messages"))]
DeviceMessages = 68,
}
impl StockMessage {
@@ -194,7 +192,7 @@ impl Context {
/// placeholders with the string in `insert` and does the same for
/// `%2$s`, `%2$d` and `%2$@` for `insert2`.
/// (the `%1$@` variant is used on iOS, the other are used on Android and Desktop)
pub fn stock_string_repl_str2(
fn stock_string_repl_str2(
&self,
id: StockMessage,
insert: impl AsRef<str>,
@@ -336,7 +334,7 @@ mod tests {
assert_eq!(
t.ctx
.stock_string_repl_str2(StockMessage::ServerResponse, "foo", "bar"),
"Could not connect to foo: bar"
"Response from foo: bar"
);
}

View File

@@ -16,20 +16,18 @@ if __name__ == "__main__":
free = s.count("free(")
unsafe_fn = s.count("unsafe fn")
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, unsafe_fn, chars))
sum_unsafe, sum_free, sum_unsafe_fn, sum_chars, sum_libc = 0, 0, 0, 0, 0
sum_unsafe, sum_free, sum_unsafe_fn, sum_chars = 0, 0, 0, 0
for fn, unsafe, free, unsafe_fn, chars, libc in reversed(sorted(filestats, key=lambda x: sum(x[1:]))):
if unsafe + free + unsafe_fn + chars + libc == 0:
for fn, unsafe, free, unsafe_fn, chars in reversed(sorted(filestats, key=lambda x: sum(x[1:]))):
if unsafe + free + unsafe_fn + chars == 0:
continue
print("{0: <25} unsafe: {1: >3} free: {2: >3} unsafe-fn: {3: >3} chars: {4: >3} libc: {5: >3}".format(str(fn), unsafe, free, unsafe_fn, chars, libc))
print("{0: <25} unsafe: {1: >3} free: {2: >3} unsafe-fn: {3: >3} chars: {4: >3}".format(str(fn), unsafe, free, unsafe_fn, chars))
sum_unsafe += unsafe
sum_free += free
sum_unsafe_fn += unsafe_fn
sum_chars += chars
sum_libc += libc
print()
@@ -37,4 +35,3 @@ if __name__ == "__main__":
print("total free:", sum_free)
print("total unsafe-fn:", sum_unsafe_fn)
print("total c_chars:", sum_chars)
print("total libc:", sum_libc)

View File

@@ -143,8 +143,7 @@ pub fn get_field_date(imffields: *mut mailimf_fields) -> Result<i64, Error> {
fn mailimf_get_recipients_add_addr(recipients: &mut HashSet<String>, mb: *mut mailimf_mailbox) {
if !mb.is_null() {
let addr = to_string_lossy(unsafe { (*mb).mb_addr_spec });
let addr_norm = addr_normalize(&addr);
let addr_norm = addr_normalize(as_str(unsafe { (*mb).mb_addr_spec }));
recipients.insert(addr_norm.into());
}
}
@@ -383,8 +382,8 @@ pub fn mailimf_find_first_addr(mb_list: *const mailimf_mailbox_list) -> Option<S
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 { to_string_lossy((*mb).mb_addr_spec) };
return Some(addr_normalize(&addr).to_string());
let addr = unsafe { as_str((*mb).mb_addr_spec) };
return Some(addr_normalize(addr).to_string());
}
}
@@ -490,9 +489,7 @@ pub fn content_type_needs_encoding(content: *const mailmime_content) -> bool {
if (*(*content).ct_type).tp_type == MAILMIME_TYPE_COMPOSITE_TYPE as libc::c_int {
let composite = (*(*content).ct_type).tp_data.tp_composite_type;
match (*composite).ct_type as u32 {
MAILMIME_COMPOSITE_TYPE_MESSAGE => {
to_string_lossy((*content).ct_subtype) != "rfc822"
}
MAILMIME_COMPOSITE_TYPE_MESSAGE => as_str((*content).ct_subtype) != "rfc822",
MAILMIME_COMPOSITE_TYPE_MULTIPART => false,
_ => false,
}